From da05dfe70859f5bf7c6aa6c820bc6a37dc14ca7c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 30 Jul 2019 16:59:12 -0700 Subject: [PATCH 1/9] Add Black --- .devcontainer/devcontainer.json | 4 + .pre-commit-config.yaml | 8 + azure-pipelines-ci.yml | 17 +- .../components/rejseplanen/sensor.py | 140 ++++++------ .../components/zha/core/channels/__init__.py | 117 +++++----- homeassistant/components/zha/core/device.py | 200 ++++++++++-------- homeassistant/components/zha/entity.py | 77 +++---- homeassistant/util/__init__.py | 66 +++--- pylintrc | 2 + pyproject.toml | 3 + requirements_test.txt | 4 + requirements_test_all.txt | 4 + script/bootstrap | 2 +- script/check_format | 10 + script/setup | 1 + setup.cfg | 18 +- 16 files changed, 401 insertions(+), 272 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 pyproject.toml create mode 100755 script/check_format diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 12993e9ae1d..e70706f8af4 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -17,6 +17,10 @@ "python.pythonPath": "/usr/local/bin/python", "python.linting.pylintEnabled": true, "python.linting.enabled": true, + "python.formatting.provider": "black", + "editor.formatOnPaste": false, + "editor.formatOnSave": true, + "editor.formatOnType": true, "files.trimTrailingWhitespace": true, "editor.rulers": [80], "terminal.integrated.shell.linux": "/bin/bash", diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000000..5134f5f14aa --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,8 @@ +repos: +- repo: https://github.com/python/black + rev: 19.3b0 + hooks: + - id: black + args: + - --safe + - --quiet diff --git a/azure-pipelines-ci.yml b/azure-pipelines-ci.yml index c92d5a9f53b..cd30812613a 100644 --- a/azure-pipelines-ci.yml +++ b/azure-pipelines-ci.yml @@ -38,7 +38,7 @@ stages: python -m venv venv . venv/bin/activate - pip install -r requirements_test.txt + pip install -r requirements_test.txt -c homeassistant/package_constraints.txt displayName: 'Setup Env' - script: | . venv/bin/activate @@ -63,6 +63,21 @@ stages: . venv/bin/activate ./script/gen_requirements_all.py validate displayName: 'requirements_all validate' + - job: 'CheckFormat' + pool: + vmImage: 'ubuntu-latest' + container: $[ variables['PythonMain'] ] + steps: + - script: | + python -m venv venv + + . venv/bin/activate + pip install -r requirements_test.txt -c homeassistant/package_constraints.txt + displayName: 'Setup Env' + - script: | + . venv/bin/activate + ./script/check_format + displayName: 'Check Black formatting' - stage: 'Tests' dependsOn: diff --git a/homeassistant/components/rejseplanen/sensor.py b/homeassistant/components/rejseplanen/sensor.py index d99551a9528..3ba2b06eb02 100755 --- a/homeassistant/components/rejseplanen/sensor.py +++ b/homeassistant/components/rejseplanen/sensor.py @@ -21,42 +21,42 @@ from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -ATTR_STOP_ID = 'stop_id' -ATTR_STOP_NAME = 'stop' -ATTR_ROUTE = 'route' -ATTR_TYPE = 'type' +ATTR_STOP_ID = "stop_id" +ATTR_STOP_NAME = "stop" +ATTR_ROUTE = "route" +ATTR_TYPE = "type" ATTR_DIRECTION = "direction" -ATTR_DUE_IN = 'due_in' -ATTR_DUE_AT = 'due_at' -ATTR_NEXT_UP = 'next_departures' +ATTR_DUE_IN = "due_in" +ATTR_DUE_AT = "due_at" +ATTR_NEXT_UP = "next_departures" ATTRIBUTION = "Data provided by rejseplanen.dk" -CONF_STOP_ID = 'stop_id' -CONF_ROUTE = 'route' -CONF_DIRECTION = 'direction' -CONF_DEPARTURE_TYPE = 'departure_type' +CONF_STOP_ID = "stop_id" +CONF_ROUTE = "route" +CONF_DIRECTION = "direction" +CONF_DEPARTURE_TYPE = "departure_type" -DEFAULT_NAME = 'Next departure' -ICON = 'mdi:bus' +DEFAULT_NAME = "Next departure" +ICON = "mdi:bus" SCAN_INTERVAL = timedelta(minutes=1) -BUS_TYPES = ['BUS', 'EXB', 'TB'] -TRAIN_TYPES = ['LET', 'S', 'REG', 'IC', 'LYN', 'TOG'] -METRO_TYPES = ['M'] +BUS_TYPES = ["BUS", "EXB", "TB"] +TRAIN_TYPES = ["LET", "S", "REG", "IC", "LYN", "TOG"] +METRO_TYPES = ["M"] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_STOP_ID): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_ROUTE, default=[]): - vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_DIRECTION, default=[]): - vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_DEPARTURE_TYPE, default=[]): - vol.All(cv.ensure_list, - [vol.In([*BUS_TYPES, *TRAIN_TYPES, *METRO_TYPES])]) -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_STOP_ID): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_ROUTE, default=[]): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_DIRECTION, default=[]): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_DEPARTURE_TYPE, default=[]): vol.All( + cv.ensure_list, [vol.In([*BUS_TYPES, *TRAIN_TYPES, *METRO_TYPES])] + ), + } +) def due_in_minutes(timestamp): @@ -64,8 +64,9 @@ def due_in_minutes(timestamp): The timestamp should be in the format day.month.year hour:minute """ - diff = datetime.strptime( - timestamp, "%d.%m.%y %H:%M") - dt_util.now().replace(tzinfo=None) + diff = datetime.strptime(timestamp, "%d.%m.%y %H:%M") - dt_util.now().replace( + tzinfo=None + ) return int(diff.total_seconds() // 60) @@ -79,8 +80,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): departure_type = config[CONF_DEPARTURE_TYPE] data = PublicTransportData(stop_id, route, direction, departure_type) - add_devices([RejseplanenTransportSensor( - data, stop_id, route, direction, name)], True) + add_devices( + [RejseplanenTransportSensor(data, stop_id, route, direction, name)], True + ) class RejseplanenTransportSensor(Entity): @@ -124,14 +126,14 @@ class RejseplanenTransportSensor(Entity): ATTR_STOP_NAME: self._times[0][ATTR_STOP_NAME], ATTR_STOP_ID: self._stop_id, ATTR_ATTRIBUTION: ATTRIBUTION, - ATTR_NEXT_UP: next_up + ATTR_NEXT_UP: next_up, } return {k: v for k, v in params.items() if v} @property def unit_of_measurement(self): """Return the unit this state is expressed in.""" - return 'min' + return "min" @property def icon(self): @@ -148,7 +150,7 @@ class RejseplanenTransportSensor(Entity): pass -class PublicTransportData(): +class PublicTransportData: """The Class for handling the data retrieval.""" def __init__(self, stop_id, route, direction, departure_type): @@ -161,16 +163,21 @@ class PublicTransportData(): def empty_result(self): """Object returned when no departures are found.""" - return [{ATTR_DUE_IN: 'n/a', - ATTR_DUE_AT: 'n/a', - ATTR_TYPE: 'n/a', - ATTR_ROUTE: self.route, - ATTR_DIRECTION: 'n/a', - ATTR_STOP_NAME: 'n/a'}] + return [ + { + ATTR_DUE_IN: "n/a", + ATTR_DUE_AT: "n/a", + ATTR_TYPE: "n/a", + ATTR_ROUTE: self.route, + ATTR_DIRECTION: "n/a", + ATTR_STOP_NAME: "n/a", + } + ] def update(self): """Get the latest data from rejseplanen.""" import rjpl + self.info = [] def intersection(lst1, lst2): @@ -179,12 +186,9 @@ class PublicTransportData(): # Limit search to selected types, to get more results all_types = not bool(self.departure_type) - use_train = all_types or bool( - intersection(TRAIN_TYPES, self.departure_type)) - use_bus = all_types or bool( - intersection(BUS_TYPES, self.departure_type)) - use_metro = all_types or bool( - intersection(METRO_TYPES, self.departure_type)) + use_train = all_types or bool(intersection(TRAIN_TYPES, self.departure_type)) + use_bus = all_types or bool(intersection(BUS_TYPES, self.departure_type)) + use_metro = all_types or bool(intersection(METRO_TYPES, self.departure_type)) try: results = rjpl.departureBoard( @@ -192,7 +196,7 @@ class PublicTransportData(): timeout=5, useTrain=use_train, useBus=use_bus, - useMetro=use_metro + useMetro=use_metro, ) except rjpl.rjplAPIError as error: _LOGGER.debug("API returned error: %s", error) @@ -204,36 +208,40 @@ class PublicTransportData(): return # Filter result - results = [d for d in results if 'cancelled' not in d] + results = [d for d in results if "cancelled" not in d] if self.route: - results = [d for d in results if d['name'] in self.route] + results = [d for d in results if d["name"] in self.route] if self.direction: - results = [d for d in results if d['direction'] in self.direction] + results = [d for d in results if d["direction"] in self.direction] if self.departure_type: - results = [d for d in results if d['type'] in self.departure_type] + results = [d for d in results if d["type"] in self.departure_type] for item in results: - route = item.get('name') + route = item.get("name") - due_at_date = item.get('rtDate') - due_at_time = item.get('rtTime') + due_at_date = item.get("rtDate") + due_at_time = item.get("rtTime") if due_at_date is None: - due_at_date = item.get('date') # Scheduled date + due_at_date = item.get("date") # Scheduled date if due_at_time is None: - due_at_time = item.get('time') # Scheduled time + due_at_time = item.get("time") # Scheduled time - if (due_at_date is not None and - due_at_time is not None and - route is not None): - due_at = '{} {}'.format(due_at_date, due_at_time) + if ( + due_at_date is not None + and due_at_time is not None + and route is not None + ): + due_at = "{} {}".format(due_at_date, due_at_time) - departure_data = {ATTR_DUE_IN: due_in_minutes(due_at), - ATTR_DUE_AT: due_at, - ATTR_TYPE: item.get('type'), - ATTR_ROUTE: route, - ATTR_DIRECTION: item.get('direction'), - ATTR_STOP_NAME: item.get('stop')} + departure_data = { + ATTR_DUE_IN: due_in_minutes(due_at), + ATTR_DUE_AT: due_at, + ATTR_TYPE: item.get("type"), + ATTR_ROUTE: route, + ATTR_DIRECTION: item.get("direction"), + ATTR_STOP_NAME: item.get("stop"), + } self.info.append(departure_data) if not self.info: diff --git a/homeassistant/components/zha/core/channels/__init__.py b/homeassistant/components/zha/core/channels/__init__.py index d0e61caca64..b567f2cbc2e 100644 --- a/homeassistant/components/zha/core/channels/__init__.py +++ b/homeassistant/components/zha/core/channels/__init__.py @@ -14,11 +14,19 @@ from random import uniform from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_send from ..helpers import ( - configure_reporting, construct_unique_id, - safe_read, get_attr_id_by_name, bind_cluster, LogMixin) + configure_reporting, + construct_unique_id, + safe_read, + get_attr_id_by_name, + bind_cluster, + LogMixin, +) from ..const import ( - REPORT_CONFIG_DEFAULT, SIGNAL_ATTR_UPDATED, ATTRIBUTE_CHANNEL, - EVENT_RELAY_CHANNEL, ZDO_CHANNEL + REPORT_CONFIG_DEFAULT, + SIGNAL_ATTR_UPDATED, + ATTRIBUTE_CHANNEL, + EVENT_RELAY_CHANNEL, + ZDO_CHANNEL, ) from ..registries import CLUSTER_REPORT_CONFIGS @@ -33,32 +41,33 @@ def parse_and_log_command(channel, tsn, command_id, args): cmd, args, channel.cluster.cluster_id, - tsn + tsn, ) return cmd def decorate_command(channel, command): """Wrap a cluster command to make it safe.""" + @wraps(command) async def wrapper(*args, **kwds): from zigpy.exceptions import DeliveryError + try: result = await command(*args, **kwds) - channel.debug("executed command: %s %s %s %s", - command.__name__, - "{}: {}".format("with args", args), - "{}: {}".format("with kwargs", kwds), - "{}: {}".format("and result", result)) + channel.debug( + "executed command: %s %s %s %s", + command.__name__, + "{}: {}".format("with args", args), + "{}: {}".format("with kwargs", kwds), + "{}: {}".format("and result", result), + ) return result except (DeliveryError, Timeout) as ex: - channel.debug( - "command failed: %s exception: %s", - command.__name__, - str(ex) - ) + channel.debug("command failed: %s exception: %s", command.__name__, str(ex)) return ex + return wrapper @@ -80,13 +89,12 @@ class ZigbeeChannel(LogMixin): self._channel_name = cluster.ep_attribute if self.CHANNEL_NAME: self._channel_name = self.CHANNEL_NAME - self._generic_id = 'channel_0x{:04x}'.format(cluster.cluster_id) + self._generic_id = "channel_0x{:04x}".format(cluster.cluster_id) self._cluster = cluster self._zha_device = device self._unique_id = construct_unique_id(cluster) self._report_config = CLUSTER_REPORT_CONFIGS.get( - self._cluster.cluster_id, - [{'attr': 0, 'config': REPORT_CONFIG_DEFAULT}] + self._cluster.cluster_id, [{"attr": 0, "config": REPORT_CONFIG_DEFAULT}] ) self._status = ChannelStatus.CREATED self._cluster.add_listener(self) @@ -130,21 +138,24 @@ class ZigbeeChannel(LogMixin): manufacturer = None manufacturer_code = self._zha_device.manufacturer_code # Xiaomi devices don't need this and it disrupts pairing - if self._zha_device.manufacturer != 'LUMI': - if self.cluster.cluster_id >= 0xfc00 and manufacturer_code: + if self._zha_device.manufacturer != "LUMI": + if self.cluster.cluster_id >= 0xFC00 and manufacturer_code: manufacturer = manufacturer_code await bind_cluster(self._unique_id, self.cluster) if not self.cluster.bind_only: for report_config in self._report_config: - attr = report_config.get('attr') - min_report_interval, max_report_interval, change = \ - report_config.get('config') + attr = report_config.get("attr") + min_report_interval, max_report_interval, change = report_config.get( + "config" + ) await configure_reporting( - self._unique_id, self.cluster, attr, + self._unique_id, + self.cluster, + attr, min_report=min_report_interval, max_report=max_report_interval, reportable_change=change, - manufacturer=manufacturer + manufacturer=manufacturer, ) await asyncio.sleep(uniform(0.1, 0.5)) @@ -153,7 +164,7 @@ class ZigbeeChannel(LogMixin): async def async_initialize(self, from_cache): """Initialize channel.""" - self.debug('initializing channel: from_cache: %s', from_cache) + self.debug("initializing channel: from_cache: %s", from_cache) self._status = ChannelStatus.INITIALIZED @callback @@ -175,13 +186,13 @@ class ZigbeeChannel(LogMixin): def zha_send_event(self, cluster, command, args): """Relay events to hass.""" self._zha_device.hass.bus.async_fire( - 'zha_event', + "zha_event", { - 'unique_id': self._unique_id, - 'device_ieee': str(self._zha_device.ieee), - 'command': command, - 'args': args - } + "unique_id": self._unique_id, + "device_ieee": str(self._zha_device.ieee), + "command": command, + "args": args, + }, ) async def async_update(self): @@ -192,14 +203,14 @@ class ZigbeeChannel(LogMixin): """Get the value for an attribute.""" manufacturer = None manufacturer_code = self._zha_device.manufacturer_code - if self.cluster.cluster_id >= 0xfc00 and manufacturer_code: + if self.cluster.cluster_id >= 0xFC00 and manufacturer_code: manufacturer = manufacturer_code result = await safe_read( self._cluster, [attribute], allow_cache=from_cache, only_cache=from_cache, - manufacturer=manufacturer + manufacturer=manufacturer, ) return result.get(attribute) @@ -211,14 +222,10 @@ class ZigbeeChannel(LogMixin): def __getattr__(self, name): """Get attribute or a decorated cluster command.""" - if hasattr(self._cluster, name) and callable( - getattr(self._cluster, name)): + if hasattr(self._cluster, name) and callable(getattr(self._cluster, name)): command = getattr(self._cluster, name) command.__name__ = name - return decorate_command( - self, - command - ) + return decorate_command(self, command) return self.__getattribute__(name) @@ -230,7 +237,7 @@ class AttributeListeningChannel(ZigbeeChannel): def __init__(self, cluster, device): """Initialize AttributeListeningChannel.""" super().__init__(cluster, device) - attr = self._report_config[0].get('attr') + attr = self._report_config[0].get("attr") if isinstance(attr, str): self.value_attribute = get_attr_id_by_name(self.cluster, attr) else: @@ -243,13 +250,14 @@ class AttributeListeningChannel(ZigbeeChannel): async_dispatcher_send( self._zha_device.hass, "{}_{}".format(self.unique_id, SIGNAL_ATTR_UPDATED), - value + value, ) async def async_initialize(self, from_cache): """Initialize listener.""" await self.get_attribute_value( - self._report_config[0].get('attr'), from_cache=from_cache) + self._report_config[0].get("attr"), from_cache=from_cache + ) await super().async_initialize(from_cache) @@ -293,7 +301,8 @@ class ZDOChannel(LogMixin): async def async_initialize(self, from_cache): """Initialize channel.""" entry = self._zha_device.gateway.zha_storage.async_get_or_create( - self._zha_device) + self._zha_device + ) self.debug("entry loaded from storage: %s", entry) self._status = ChannelStatus.INITIALIZED @@ -320,21 +329,19 @@ class EventRelayChannel(ZigbeeChannel): self._cluster, SIGNAL_ATTR_UPDATED, { - 'attribute_id': attrid, - 'attribute_name': self._cluster.attributes.get( - attrid, - ['Unknown'])[0], - 'value': value - } + "attribute_id": attrid, + "attribute_name": self._cluster.attributes.get(attrid, ["Unknown"])[0], + "value": value, + }, ) @callback def cluster_command(self, tsn, command_id, args): """Handle a cluster command received on this cluster.""" - if self._cluster.server_commands is not None and \ - self._cluster.server_commands.get(command_id) is not None: + if ( + self._cluster.server_commands is not None + and self._cluster.server_commands.get(command_id) is not None + ): self.zha_send_event( - self._cluster, - self._cluster.server_commands.get(command_id)[0], - args + self._cluster, self._cluster.server_commands.get(command_id)[0], args ) diff --git a/homeassistant/components/zha/core/device.py b/homeassistant/components/zha/core/device.py index f5ff7aebfd3..c3bf22f6e03 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -12,18 +12,46 @@ import time from homeassistant.core import callback from homeassistant.helpers.dispatcher import ( - async_dispatcher_connect, async_dispatcher_send) + async_dispatcher_connect, + async_dispatcher_send, +) from homeassistant.helpers.event import async_track_time_interval from .channels import EventRelayChannel from .const import ( - ATTR_ARGS, ATTR_ATTRIBUTE, ATTR_CLUSTER_ID, ATTR_COMMAND, - ATTR_COMMAND_TYPE, ATTR_ENDPOINT_ID, ATTR_MANUFACTURER, ATTR_VALUE, - BATTERY_OR_UNKNOWN, CLIENT_COMMANDS, IEEE, IN, MAINS_POWERED, - MANUFACTURER_CODE, MODEL, NAME, NWK, OUT, POWER_CONFIGURATION_CHANNEL, - POWER_SOURCE, QUIRK_APPLIED, QUIRK_CLASS, SERVER, SERVER_COMMANDS, - SIGNAL_AVAILABLE, UNKNOWN_MANUFACTURER, UNKNOWN_MODEL, ZDO_CHANNEL, - LQI, RSSI, LAST_SEEN, ATTR_AVAILABLE) + ATTR_ARGS, + ATTR_ATTRIBUTE, + ATTR_CLUSTER_ID, + ATTR_COMMAND, + ATTR_COMMAND_TYPE, + ATTR_ENDPOINT_ID, + ATTR_MANUFACTURER, + ATTR_VALUE, + BATTERY_OR_UNKNOWN, + CLIENT_COMMANDS, + IEEE, + IN, + MAINS_POWERED, + MANUFACTURER_CODE, + MODEL, + NAME, + NWK, + OUT, + POWER_CONFIGURATION_CHANNEL, + POWER_SOURCE, + QUIRK_APPLIED, + QUIRK_CLASS, + SERVER, + SERVER_COMMANDS, + SIGNAL_AVAILABLE, + UNKNOWN_MANUFACTURER, + UNKNOWN_MODEL, + ZDO_CHANNEL, + LQI, + RSSI, + LAST_SEEN, + ATTR_AVAILABLE, +) from .helpers import LogMixin _LOGGER = logging.getLogger(__name__) @@ -51,22 +79,20 @@ class ZHADevice(LogMixin): self._all_channels = [] self._available = False self._available_signal = "{}_{}_{}".format( - self.name, self.ieee, SIGNAL_AVAILABLE) + self.name, self.ieee, SIGNAL_AVAILABLE + ) self._unsub = async_dispatcher_connect( - self.hass, - self._available_signal, - self.async_initialize + self.hass, self._available_signal, self.async_initialize ) from zigpy.quirks import CustomDevice + self.quirk_applied = isinstance(self._zigpy_device, CustomDevice) self.quirk_class = "{}.{}".format( self._zigpy_device.__class__.__module__, - self._zigpy_device.__class__.__name__ + self._zigpy_device.__class__.__name__, ) self._available_check = async_track_time_interval( - self.hass, - self._check_available, - _UPDATE_ALIVE_INTERVAL + self.hass, self._check_available, _UPDATE_ALIVE_INTERVAL ) self.status = DeviceStatus.CREATED @@ -184,15 +210,9 @@ class ZHADevice(LogMixin): """Set sensor availability.""" if self._available != available and available: # Update the state the first time the device comes online - async_dispatcher_send( - self.hass, - self._available_signal, - False - ) + async_dispatcher_send(self.hass, self._available_signal, False) async_dispatcher_send( - self.hass, - "{}_{}".format(self._available_signal, 'entity'), - available + self.hass, "{}_{}".format(self._available_signal, "entity"), available ) self._available = available @@ -215,14 +235,16 @@ class ZHADevice(LogMixin): LQI: self.lqi, RSSI: self.rssi, LAST_SEEN: update_time, - ATTR_AVAILABLE: self.available + ATTR_AVAILABLE: self.available, } def add_cluster_channel(self, cluster_channel): """Add cluster channel to device.""" # only keep 1 power configuration channel - if cluster_channel.name is POWER_CONFIGURATION_CHANNEL and \ - POWER_CONFIGURATION_CHANNEL in self.cluster_channels: + if ( + cluster_channel.name is POWER_CONFIGURATION_CHANNEL + and POWER_CONFIGURATION_CHANNEL in self.cluster_channels + ): return if isinstance(cluster_channel, EventRelayChannel): @@ -249,10 +271,9 @@ class ZHADevice(LogMixin): def get_key(channel): channel_key = "ZDO" - if hasattr(channel.cluster, 'cluster_id'): + if hasattr(channel.cluster, "cluster_id"): channel_key = "{}_{}".format( - channel.cluster.endpoint.endpoint_id, - channel.cluster.cluster_id + channel.cluster.endpoint.endpoint_id, channel.cluster.cluster_id ) return channel_key @@ -273,21 +294,23 @@ class ZHADevice(LogMixin): async def async_configure(self): """Configure the device.""" - self.debug('started configuration') + self.debug("started configuration") await self._execute_channel_tasks( - self.get_channels_to_configure(), 'async_configure') - self.debug('completed configuration') + self.get_channels_to_configure(), "async_configure" + ) + self.debug("completed configuration") entry = self.gateway.zha_storage.async_create_or_update(self) - self.debug('stored in registry: %s', entry) + self.debug("stored in registry: %s", entry) async def async_initialize(self, from_cache=False): """Initialize channels.""" - self.debug('started initialization') + self.debug("started initialization") await self._execute_channel_tasks( - self.all_channels, 'async_initialize', from_cache) - self.debug('power source: %s', self.power_source) + self.all_channels, "async_initialize", from_cache + ) + self.debug("power source: %s", self.power_source) self.status = DeviceStatus.INITIALIZED - self.debug('completed initialization') + self.debug("completed initialization") async def _execute_channel_tasks(self, channels, task_name, *args): """Gather and execute a set of CHANNEL tasks.""" @@ -299,11 +322,12 @@ class ZHADevice(LogMixin): # pylint: disable=E1111 if zdo_task is None: # We only want to do this once zdo_task = self._async_create_task( - semaphore, channel, task_name, *args) + semaphore, channel, task_name, *args + ) else: channel_tasks.append( - self._async_create_task( - semaphore, channel, task_name, *args)) + self._async_create_task(semaphore, channel, task_name, *args) + ) if zdo_task is not None: await zdo_task await asyncio.gather(*channel_tasks) @@ -332,10 +356,8 @@ class ZHADevice(LogMixin): def async_get_clusters(self): """Get all clusters for this device.""" return { - ep_id: { - IN: endpoint.in_clusters, - OUT: endpoint.out_clusters - } for (ep_id, endpoint) in self._zigpy_device.endpoints.items() + ep_id: {IN: endpoint.in_clusters, OUT: endpoint.out_clusters} + for (ep_id, endpoint) in self._zigpy_device.endpoints.items() if ep_id != 0 } @@ -343,15 +365,11 @@ class ZHADevice(LogMixin): def async_get_std_clusters(self): """Get ZHA and ZLL clusters for this device.""" from zigpy.profiles import zha, zll + return { - ep_id: { - IN: endpoint.in_clusters, - OUT: endpoint.out_clusters - } for (ep_id, endpoint) in self._zigpy_device.endpoints.items() - if ep_id != 0 and endpoint.profile_id in ( - zha.PROFILE_ID, - zll.PROFILE_ID - ) + ep_id: {IN: endpoint.in_clusters, OUT: endpoint.out_clusters} + for (ep_id, endpoint) in self._zigpy_device.endpoints.items() + if ep_id != 0 and endpoint.profile_id in (zha.PROFILE_ID, zll.PROFILE_ID) } @callback @@ -361,18 +379,15 @@ class ZHADevice(LogMixin): return clusters[endpoint_id][cluster_type][cluster_id] @callback - def async_get_cluster_attributes(self, endpoint_id, cluster_id, - cluster_type=IN): + def async_get_cluster_attributes(self, endpoint_id, cluster_id, cluster_type=IN): """Get zigbee attributes for specified cluster.""" - cluster = self.async_get_cluster(endpoint_id, cluster_id, - cluster_type) + cluster = self.async_get_cluster(endpoint_id, cluster_id, cluster_type) if cluster is None: return None return cluster.attributes @callback - def async_get_cluster_commands(self, endpoint_id, cluster_id, - cluster_type=IN): + def async_get_cluster_commands(self, endpoint_id, cluster_id, cluster_type=IN): """Get zigbee commands for specified cluster.""" cluster = self.async_get_cluster(endpoint_id, cluster_id, cluster_type) if cluster is None: @@ -382,64 +397,77 @@ class ZHADevice(LogMixin): SERVER_COMMANDS: cluster.server_commands, } - async def write_zigbee_attribute(self, endpoint_id, cluster_id, - attribute, value, cluster_type=IN, - manufacturer=None): + async def write_zigbee_attribute( + self, + endpoint_id, + cluster_id, + attribute, + value, + cluster_type=IN, + manufacturer=None, + ): """Write a value to a zigbee attribute for a cluster in this entity.""" cluster = self.async_get_cluster(endpoint_id, cluster_id, cluster_type) if cluster is None: return None from zigpy.exceptions import DeliveryError + try: response = await cluster.write_attributes( - {attribute: value}, - manufacturer=manufacturer + {attribute: value}, manufacturer=manufacturer ) self.debug( - 'set: %s for attr: %s to cluster: %s for ept: %s - res: %s', + "set: %s for attr: %s to cluster: %s for ept: %s - res: %s", value, attribute, cluster_id, endpoint_id, - response + response, ) return response except DeliveryError as exc: self.debug( - 'failed to set attribute: %s %s %s %s %s', - '{}: {}'.format(ATTR_VALUE, value), - '{}: {}'.format(ATTR_ATTRIBUTE, attribute), - '{}: {}'.format(ATTR_CLUSTER_ID, cluster_id), - '{}: {}'.format(ATTR_ENDPOINT_ID, endpoint_id), - exc + "failed to set attribute: %s %s %s %s %s", + "{}: {}".format(ATTR_VALUE, value), + "{}: {}".format(ATTR_ATTRIBUTE, attribute), + "{}: {}".format(ATTR_CLUSTER_ID, cluster_id), + "{}: {}".format(ATTR_ENDPOINT_ID, endpoint_id), + exc, ) return None - async def issue_cluster_command(self, endpoint_id, cluster_id, command, - command_type, args, cluster_type=IN, - manufacturer=None): + async def issue_cluster_command( + self, + endpoint_id, + cluster_id, + command, + command_type, + args, + cluster_type=IN, + manufacturer=None, + ): """Issue a command against specified zigbee cluster on this entity.""" cluster = self.async_get_cluster(endpoint_id, cluster_id, cluster_type) if cluster is None: return None response = None if command_type == SERVER: - response = await cluster.command(command, *args, - manufacturer=manufacturer, - expect_reply=True) + response = await cluster.command( + command, *args, manufacturer=manufacturer, expect_reply=True + ) else: response = await cluster.client_command(command, *args) self.debug( - 'Issued cluster command: %s %s %s %s %s %s %s', - '{}: {}'.format(ATTR_CLUSTER_ID, cluster_id), - '{}: {}'.format(ATTR_COMMAND, command), - '{}: {}'.format(ATTR_COMMAND_TYPE, command_type), - '{}: {}'.format(ATTR_ARGS, args), - '{}: {}'.format(ATTR_CLUSTER_ID, cluster_type), - '{}: {}'.format(ATTR_MANUFACTURER, manufacturer), - '{}: {}'.format(ATTR_ENDPOINT_ID, endpoint_id) + "Issued cluster command: %s %s %s %s %s %s %s", + "{}: {}".format(ATTR_CLUSTER_ID, cluster_id), + "{}: {}".format(ATTR_COMMAND, command), + "{}: {}".format(ATTR_COMMAND_TYPE, command_type), + "{}: {}".format(ATTR_ARGS, args), + "{}: {}".format(ATTR_CLUSTER_ID, cluster_type), + "{}: {}".format(ATTR_MANUFACTURER, manufacturer), + "{}: {}".format(ATTR_ENDPOINT_ID, endpoint_id), ) return response diff --git a/homeassistant/components/zha/entity.py b/homeassistant/components/zha/entity.py index 3008146656e..defac70f271 100644 --- a/homeassistant/components/zha/entity.py +++ b/homeassistant/components/zha/entity.py @@ -12,13 +12,19 @@ from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.util import slugify from .core.const import ( - ATTR_MANUFACTURER, DATA_ZHA, DATA_ZHA_BRIDGE_ID, DOMAIN, MODEL, NAME, - SIGNAL_REMOVE) + ATTR_MANUFACTURER, + DATA_ZHA, + DATA_ZHA_BRIDGE_ID, + DOMAIN, + MODEL, + NAME, + SIGNAL_REMOVE, +) from .core.helpers import LogMixin _LOGGER = logging.getLogger(__name__) -ENTITY_SUFFIX = 'entity_suffix' +ENTITY_SUFFIX = "entity_suffix" RESTART_GRACE_PERIOD = 7200 # 2 hours @@ -27,29 +33,28 @@ class ZhaEntity(RestoreEntity, LogMixin, entity.Entity): _domain = None # Must be overridden by subclasses - def __init__(self, unique_id, zha_device, channels, - skip_entity_id=False, **kwargs): + def __init__(self, unique_id, zha_device, channels, skip_entity_id=False, **kwargs): """Init ZHA entity.""" self._force_update = False self._should_poll = False self._unique_id = unique_id if not skip_entity_id: ieee = zha_device.ieee - ieeetail = ''.join(['%02x' % (o, ) for o in ieee[-4:]]) + ieeetail = "".join(["%02x" % (o,) for o in ieee[-4:]]) self.entity_id = "{}.{}_{}_{}_{}{}".format( self._domain, slugify(zha_device.manufacturer), slugify(zha_device.model), ieeetail, channels[0].cluster.endpoint.endpoint_id, - kwargs.get(ENTITY_SUFFIX, ''), + kwargs.get(ENTITY_SUFFIX, ""), ) self._state = None self._device_state_attributes = {} self._zha_device = zha_device self.cluster_channels = {} self._available = False - self._component = kwargs['component'] + self._component = kwargs["component"] self._unsubs = [] self.remove_future = None for channel in channels: @@ -89,15 +94,14 @@ class ZhaEntity(RestoreEntity, LogMixin, entity.Entity): def device_info(self): """Return a device description for device registry.""" zha_device_info = self._zha_device.device_info - ieee = zha_device_info['ieee'] + ieee = zha_device_info["ieee"] return { - 'connections': {(CONNECTION_ZIGBEE, ieee)}, - 'identifiers': {(DOMAIN, ieee)}, + "connections": {(CONNECTION_ZIGBEE, ieee)}, + "identifiers": {(DOMAIN, ieee)}, ATTR_MANUFACTURER: zha_device_info[ATTR_MANUFACTURER], MODEL: zha_device_info[MODEL], NAME: zha_device_info[NAME], - 'via_device': ( - DOMAIN, self.hass.data[DATA_ZHA][DATA_ZHA_BRIDGE_ID]), + "via_device": (DOMAIN, self.hass.data[DATA_ZHA][DATA_ZHA_BRIDGE_ID]), } @property @@ -112,9 +116,7 @@ class ZhaEntity(RestoreEntity, LogMixin, entity.Entity): def async_update_state_attribute(self, key, value): """Update a single device state attribute.""" - self._device_state_attributes.update({ - key: value - }) + self._device_state_attributes.update({key: value}) self.async_schedule_update_ha_state() def async_set_state(self, state): @@ -127,24 +129,34 @@ class ZhaEntity(RestoreEntity, LogMixin, entity.Entity): self.remove_future = asyncio.Future() await self.async_check_recently_seen() await self.async_accept_signal( - None, "{}_{}".format(self.zha_device.available_signal, 'entity'), + None, + "{}_{}".format(self.zha_device.available_signal, "entity"), self.async_set_available, - signal_override=True) + signal_override=True, + ) await self.async_accept_signal( - None, "{}_{}".format(SIGNAL_REMOVE, str(self.zha_device.ieee)), + None, + "{}_{}".format(SIGNAL_REMOVE, str(self.zha_device.ieee)), self.async_remove, - signal_override=True + signal_override=True, ) self._zha_device.gateway.register_entity_reference( - self._zha_device.ieee, self.entity_id, self._zha_device, - self.cluster_channels, self.device_info, self.remove_future) + self._zha_device.ieee, + self.entity_id, + self._zha_device, + self.cluster_channels, + self.device_info, + self.remove_future, + ) async def async_check_recently_seen(self): """Check if the device was seen within the last 2 hours.""" last_state = await self.async_get_last_state() - if last_state and self._zha_device.last_seen and ( - time.time() - self._zha_device.last_seen < - RESTART_GRACE_PERIOD): + if ( + last_state + and self._zha_device.last_seen + and (time.time() - self._zha_device.last_seen < RESTART_GRACE_PERIOD) + ): self.async_set_available(True) if not self.zha_device.is_mains_powered: # mains powered devices will get real time state @@ -167,24 +179,17 @@ class ZhaEntity(RestoreEntity, LogMixin, entity.Entity): async def async_update(self): """Retrieve latest state.""" for channel in self.cluster_channels.values(): - if hasattr(channel, 'async_update'): + if hasattr(channel, "async_update"): await channel.async_update() - async def async_accept_signal(self, channel, signal, func, - signal_override=False): + async def async_accept_signal(self, channel, signal, func, signal_override=False): """Accept a signal from a channel.""" unsub = None if signal_override: - unsub = async_dispatcher_connect( - self.hass, - signal, - func - ) + unsub = async_dispatcher_connect(self.hass, signal, func) else: unsub = async_dispatcher_connect( - self.hass, - "{}_{}".format(channel.unique_id, signal), - func + self.hass, "{}_{}".format(channel.unique_id, signal), func ) self._unsubs.append(unsub) diff --git a/homeassistant/util/__init__.py b/homeassistant/util/__init__.py index c353eed99b8..08c3a51e247 100644 --- a/homeassistant/util/__init__.py +++ b/homeassistant/util/__init__.py @@ -9,21 +9,29 @@ import random import string from functools import wraps from types import MappingProxyType -from typing import (Any, Optional, TypeVar, Callable, KeysView, Union, # noqa - Iterable, List, Dict, Iterator, Coroutine, MutableSet) +from typing import ( + Any, + Optional, + TypeVar, + Callable, + KeysView, + Union, # noqa + Iterable, + Coroutine, +) import slugify as unicode_slug from .dt import as_local, utcnow # pylint: disable=invalid-name -T = TypeVar('T') -U = TypeVar('U') -ENUM_T = TypeVar('ENUM_T', bound=enum.Enum) +T = TypeVar("T") +U = TypeVar("U") +ENUM_T = TypeVar("ENUM_T", bound=enum.Enum) # pylint: enable=invalid-name -RE_SANITIZE_FILENAME = re.compile(r'(~|\.\.|/|\\)') -RE_SANITIZE_PATH = re.compile(r'(~|\.(\.)+)') +RE_SANITIZE_FILENAME = re.compile(r"(~|\.\.|/|\\)") +RE_SANITIZE_PATH = re.compile(r"(~|\.(\.)+)") def sanitize_filename(filename: str) -> str: @@ -38,23 +46,24 @@ def sanitize_path(path: str) -> str: def slugify(text: str) -> str: """Slugify a given text.""" - return unicode_slug.slugify(text, separator='_') # type: ignore + return unicode_slug.slugify(text, separator="_") # type: ignore def repr_helper(inp: Any) -> str: """Help creating a more readable string representation of objects.""" if isinstance(inp, (dict, MappingProxyType)): return ", ".join( - repr_helper(key)+"="+repr_helper(item) for key, item - in inp.items()) + repr_helper(key) + "=" + repr_helper(item) for key, item in inp.items() + ) if isinstance(inp, datetime): return as_local(inp).isoformat() return str(inp) -def convert(value: Optional[T], to_type: Callable[[T], U], - default: Optional[U] = None) -> Optional[U]: +def convert( + value: Optional[T], to_type: Callable[[T], U], default: Optional[U] = None +) -> Optional[U]: """Convert value to to_type, returns default if fails.""" try: return default if value is None else to_type(value) @@ -63,8 +72,9 @@ def convert(value: Optional[T], to_type: Callable[[T], U], return default -def ensure_unique_string(preferred_string: str, current_strings: - Union[Iterable[str], KeysView[str]]) -> str: +def ensure_unique_string( + preferred_string: str, current_strings: Union[Iterable[str], KeysView[str]] +) -> str: """Return a string that is not present in current_strings. If preferred string exists will append _2, _3, .. @@ -88,14 +98,14 @@ def get_local_ip() -> str: sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Use Google Public DNS server to determine own IP - sock.connect(('8.8.8.8', 80)) + sock.connect(("8.8.8.8", 80)) return sock.getsockname()[0] # type: ignore except socket.error: try: return socket.gethostbyname(socket.gethostname()) except socket.gaierror: - return '127.0.0.1' + return "127.0.0.1" finally: sock.close() @@ -106,7 +116,7 @@ def get_random_string(length: int = 10) -> str: generator = random.SystemRandom() source_chars = string.ascii_letters + string.digits - return ''.join(generator.choice(source_chars) for _ in range(length)) + return "".join(generator.choice(source_chars) for _ in range(length)) class OrderedEnum(enum.Enum): @@ -158,8 +168,9 @@ class Throttle: Adds a datetime attribute `last_call` to the method. """ - def __init__(self, min_time: timedelta, - limit_no_throttle: Optional[timedelta] = None) -> None: + def __init__( + self, min_time: timedelta, limit_no_throttle: Optional[timedelta] = None + ) -> None: """Initialize the throttle.""" self.min_time = min_time self.limit_no_throttle = limit_no_throttle @@ -168,10 +179,13 @@ class Throttle: """Caller for the throttle.""" # Make sure we return a coroutine if the method is async. if asyncio.iscoroutinefunction(method): + async def throttled_value() -> None: """Stand-in function for when real func is being throttled.""" return None + else: + def throttled_value() -> None: # type: ignore """Stand-in function for when real func is being throttled.""" return None @@ -189,8 +203,10 @@ class Throttle: # All methods have the classname in their qualname separated by a '.' # Functions have a '.' in their qualname if defined inline, but will # be prefixed by '..' so we strip that out. - is_func = (not hasattr(method, '__self__') and - '.' not in method.__qualname__.split('..')[-1]) + is_func = ( + not hasattr(method, "__self__") + and "." not in method.__qualname__.split("..")[-1] + ) @wraps(method) def wrapper(*args: Any, **kwargs: Any) -> Union[Callable, Coroutine]: @@ -199,14 +215,14 @@ class Throttle: If we cannot acquire the lock, it is running so return None. """ # pylint: disable=protected-access - if hasattr(method, '__self__'): - host = getattr(method, '__self__') + if hasattr(method, "__self__"): + host = getattr(method, "__self__") elif is_func: host = wrapper else: host = args[0] if args else wrapper - if not hasattr(host, '_throttle'): + if not hasattr(host, "_throttle"): host._throttle = {} if id(self) not in host._throttle: @@ -217,7 +233,7 @@ class Throttle: return throttled_value() # Check if method is never called or no_throttle is given - force = kwargs.pop('no_throttle', False) or not throttle[1] + force = kwargs.pop("no_throttle", False) or not throttle[1] try: if force or utcnow() - throttle[1] > self.min_time: diff --git a/pylintrc b/pylintrc index 1ba0bf2c82a..bb4f1fe96d0 100644 --- a/pylintrc +++ b/pylintrc @@ -6,6 +6,7 @@ good-names=i,j,k,ex,Run,_,fp [MESSAGES CONTROL] # Reasons disabled: +# format - handled by black # locally-disabled - it spams too much # duplicate-code - unavoidable # cyclic-import - doesn't test if both import on load @@ -20,6 +21,7 @@ good-names=i,j,k,ex,Run,_,fp # not-an-iterable - https://github.com/PyCQA/pylint/issues/2311 # unnecessary-pass - readability for functions which only contain pass disable= + format, abstract-class-little-used, abstract-method, cyclic-import, diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000000..7a75060c8e9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[tool.black] +target-version = ["py36", "py37", "py38"] +exclude = 'generated' diff --git a/requirements_test.txt b/requirements_test.txt index ce63824d9aa..7589c9ec27d 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,7 +1,10 @@ # linters such as flake8 and pylint should be pinned, as new releases # make new things fail. Manually update these pins when pulling in a # new version + +# When updating this file, update .pre-commit-config.yaml too asynctest==0.13.0 +black==19.3b0 codecov==2.0.15 coveralls==1.2.0 flake8-docstrings==1.3.0 @@ -16,3 +19,4 @@ pytest-sugar==0.9.2 pytest-timeout==1.3.3 pytest==5.0.1 requests_mock==1.6.0 +pre-commit==1.17.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 16dca6badd5..c2e4cfef22d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2,7 +2,10 @@ # linters such as flake8 and pylint should be pinned, as new releases # make new things fail. Manually update these pins when pulling in a # new version + +# When updating this file, update .pre-commit-config.yaml too asynctest==0.13.0 +black==19.3b0 codecov==2.0.15 coveralls==1.2.0 flake8-docstrings==1.3.0 @@ -17,6 +20,7 @@ pytest-sugar==0.9.2 pytest-timeout==1.3.3 pytest==5.0.1 requests_mock==1.6.0 +pre-commit==1.17.0 # homeassistant.components.homekit diff --git a/script/bootstrap b/script/bootstrap index e7034f1c33c..ed6cd55be36 100755 --- a/script/bootstrap +++ b/script/bootstrap @@ -7,4 +7,4 @@ set -e cd "$(dirname "$0")/.." echo "Installing test dependencies..." -python3 -m pip install tox colorlog +python3 -m pip install tox colorlog pre-commit diff --git a/script/check_format b/script/check_format new file mode 100755 index 00000000000..ec403c723b3 --- /dev/null +++ b/script/check_format @@ -0,0 +1,10 @@ +#!/bin/sh +# Format code with black. + +cd "$(dirname "$0")/.." + +black \ + --check \ + --fast \ + --quiet \ + homeassistant tests script diff --git a/script/setup b/script/setup index 554389e063e..a8c9d628115 100755 --- a/script/setup +++ b/script/setup @@ -7,4 +7,5 @@ set -e cd "$(dirname "$0")/.." script/bootstrap +pre-commit install pip3 install -e . diff --git a/setup.cfg b/setup.cfg index 38999850cd3..c6ff25bb362 100644 --- a/setup.cfg +++ b/setup.cfg @@ -21,12 +21,27 @@ norecursedirs = .git testing_config [flake8] exclude = .venv,.git,.tox,docs,venv,bin,lib,deps,build +# To work with Black +max-line-length = 88 +# E501: line too long +# W503: Line break occurred before a binary operator +# E203: Whitespace before ':' +# D202 No blank lines allowed after function docstring +ignore = + E501, + W503, + E203, + D202 [isort] # https://github.com/timothycrosley/isort # https://github.com/timothycrosley/isort/wiki/isort-Settings # splits long import on multiple lines indented by 4 spaces -multi_line_output = 4 +multi_line_output = 3 +include_trailing_comma=True +force_grid_wrap=0 +use_parentheses=True +line_length=88 indent = " " # by default isort don't check module indexes not_skip = __init__.py @@ -37,4 +52,3 @@ default_section = THIRDPARTY known_first_party = homeassistant,tests forced_separate = tests combine_as_imports = true -use_parentheses = true From 4de97abc3aa83188666336ce0a015a5bab75bc8f Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 31 Jul 2019 12:25:30 -0700 Subject: [PATCH 2/9] Black --- homeassistant/__main__.py | 212 +- homeassistant/auth/__init__.py | 301 ++- homeassistant/auth/auth_store.py | 267 +- homeassistant/auth/const.py | 6 +- homeassistant/auth/mfa_modules/__init__.py | 76 +- .../auth/mfa_modules/insecure_example.py | 51 +- homeassistant/auth/mfa_modules/notify.py | 181 +- homeassistant/auth/mfa_modules/totp.py | 105 +- homeassistant/auth/models.py | 40 +- homeassistant/auth/permissions/__init__.py | 26 +- homeassistant/auth/permissions/const.py | 12 +- homeassistant/auth/permissions/entities.py | 79 +- homeassistant/auth/permissions/merge.py | 8 +- homeassistant/auth/permissions/models.py | 12 +- .../auth/permissions/system_policies.py | 16 +- homeassistant/auth/permissions/types.py | 10 +- homeassistant/auth/permissions/util.py | 28 +- homeassistant/auth/providers/__init__.py | 135 +- homeassistant/auth/providers/command_line.py | 69 +- homeassistant/auth/providers/homeassistant.py | 119 +- .../auth/providers/insecure_example.py | 79 +- .../auth/providers/legacy_api_password.py | 45 +- .../auth/providers/trusted_networks.py | 153 +- homeassistant/auth/util.py | 2 +- homeassistant/bootstrap.py | 258 +- homeassistant/components/__init__.py | 5 +- homeassistant/components/abode/__init__.py | 223 +- .../components/abode/alarm_control_panel.py | 15 +- .../components/abode/binary_sensor.py | 20 +- homeassistant/components/abode/camera.py | 6 +- homeassistant/components/abode/light.py | 18 +- homeassistant/components/abode/sensor.py | 28 +- homeassistant/components/abode/switch.py | 8 +- .../components/acer_projector/switch.py | 76 +- .../components/actiontec/device_tracker.py | 67 +- homeassistant/components/adguard/__init__.py | 51 +- .../components/adguard/config_flow.py | 52 +- homeassistant/components/adguard/const.py | 18 +- homeassistant/components/adguard/sensor.py | 78 +- homeassistant/components/adguard/switch.py | 44 +- homeassistant/components/ads/__init__.py | 145 +- homeassistant/components/ads/binary_sensor.py | 24 +- homeassistant/components/ads/cover.py | 134 +- homeassistant/components/ads/light.py | 55 +- homeassistant/components/ads/sensor.py | 38 +- homeassistant/components/ads/switch.py | 18 +- homeassistant/components/aftership/const.py | 2 +- homeassistant/components/aftership/sensor.py | 92 +- .../components/air_quality/__init__.py | 59 +- homeassistant/components/airvisual/sensor.py | 231 +- .../components/aladdin_connect/cover.py | 58 +- .../alarm_control_panel/__init__.py | 63 +- .../components/alarmdecoder/__init__.py | 170 +- .../alarmdecoder/alarm_control_panel.py | 42 +- .../components/alarmdecoder/binary_sensor.py | 68 +- .../components/alarmdecoder/sensor.py | 7 +- .../alarmdotcom/alarm_control_panel.py | 47 +- homeassistant/components/alert/__init__.py | 144 +- homeassistant/components/alexa/__init__.py | 91 +- homeassistant/components/alexa/auth.py | 39 +- .../components/alexa/capabilities.py | 180 +- homeassistant/components/alexa/const.py | 108 +- homeassistant/components/alexa/entities.py | 102 +- homeassistant/components/alexa/errors.py | 36 +- .../components/alexa/flash_briefings.py | 34 +- homeassistant/components/alexa/handlers.py | 530 ++-- homeassistant/components/alexa/intent.py | 158 +- homeassistant/components/alexa/messages.py | 78 +- homeassistant/components/alexa/smart_home.py | 50 +- .../components/alexa/smart_home_http.py | 18 +- .../components/alexa/state_report.py | 105 +- .../components/alpha_vantage/sensor.py | 122 +- homeassistant/components/amazon_polly/tts.py | 238 +- .../components/ambiclimate/__init__.py | 16 +- .../components/ambiclimate/climate.py | 99 +- .../components/ambiclimate/config_flow.py | 67 +- homeassistant/components/ambiclimate/const.py | 20 +- .../components/ambient_station/__init__.py | 529 ++-- .../ambient_station/binary_sensor.py | 52 +- .../components/ambient_station/config_flow.py | 27 +- .../components/ambient_station/const.py | 14 +- .../components/ambient_station/sensor.py | 52 +- homeassistant/components/amcrest/__init__.py | 214 +- .../components/amcrest/binary_sensor.py | 44 +- homeassistant/components/amcrest/camera.py | 268 +- homeassistant/components/amcrest/const.py | 8 +- homeassistant/components/amcrest/helpers.py | 12 +- homeassistant/components/amcrest/sensor.py | 52 +- homeassistant/components/amcrest/switch.py | 42 +- homeassistant/components/ampio/air_quality.py | 17 +- .../components/android_ip_webcam/__init__.py | 351 +-- .../android_ip_webcam/binary_sensor.py | 10 +- .../components/android_ip_webcam/sensor.py | 23 +- .../components/android_ip_webcam/switch.py | 29 +- .../components/androidtv/media_player.py | 284 +- .../components/anel_pwrctrl/switch.py | 39 +- .../components/anthemav/media_player.py | 88 +- .../components/apache_kafka/__init__.py | 64 +- homeassistant/components/apcupsd/__init__.py | 30 +- .../components/apcupsd/binary_sensor.py | 11 +- homeassistant/components/apcupsd/sensor.py | 185 +- homeassistant/components/api/__init__.py | 191 +- homeassistant/components/apns/notify.py | 105 +- homeassistant/components/apple_tv/__init__.py | 169 +- .../components/apple_tv/media_player.py | 72 +- homeassistant/components/apple_tv/remote.py | 3 +- .../components/aprs/device_tracker.py | 103 +- .../components/aqualogic/__init__.py | 29 +- homeassistant/components/aqualogic/sensor.py | 44 +- homeassistant/components/aqualogic/switch.py | 58 +- .../components/aquostv/media_player.py | 100 +- .../components/arcam_fmj/__init__.py | 55 +- .../components/arcam_fmj/media_player.py | 35 +- homeassistant/components/arduino/__init__.py | 45 +- homeassistant/components/arduino/sensor.py | 17 +- homeassistant/components/arduino/switch.py | 31 +- .../components/arest/binary_sensor.py | 54 +- homeassistant/components/arest/sensor.py | 131 +- homeassistant/components/arest/switch.py | 126 +- homeassistant/components/arlo/__init__.py | 50 +- .../components/arlo/alarm_control_panel.py | 48 +- homeassistant/components/arlo/camera.py | 68 +- homeassistant/components/arlo/sensor.py | 102 +- .../components/aruba/device_tracker.py | 74 +- homeassistant/components/arwn/sensor.py | 59 +- .../components/asterisk_cdr/mailbox.py | 21 +- .../components/asterisk_mbox/__init__.py | 83 +- .../components/asterisk_mbox/mailbox.py | 11 +- homeassistant/components/asuswrt/__init__.py | 86 +- .../components/asuswrt/device_tracker.py | 2 +- homeassistant/components/asuswrt/sensor.py | 29 +- homeassistant/components/august/__init__.py | 143 +- .../components/august/binary_sensor.py | 66 +- homeassistant/components/august/camera.py | 11 +- homeassistant/components/august/lock.py | 12 +- .../components/aurora/binary_sensor.py | 47 +- .../components/aurora_abb_powerone/sensor.py | 24 +- homeassistant/components/auth/__init__.py | 368 +-- homeassistant/components/auth/indieauth.py | 45 +- homeassistant/components/auth/login_flow.py | 139 +- .../components/auth/mfa_setup_flow.py | 109 +- .../components/automatic/device_tracker.py | 142 +- .../components/automation/__init__.py | 241 +- homeassistant/components/automation/device.py | 10 +- homeassistant/components/automation/event.py | 36 +- .../components/automation/geo_location.py | 79 +- .../components/automation/homeassistant.py | 42 +- .../components/automation/litejet.py | 52 +- homeassistant/components/automation/mqtt.py | 39 +- .../components/automation/numeric_state.py | 125 +- homeassistant/components/automation/state.py | 112 +- homeassistant/components/automation/sun.py | 28 +- .../components/automation/template.py | 86 +- homeassistant/components/automation/time.py | 19 +- .../components/automation/time_pattern.py | 37 +- .../components/automation/webhook.py | 31 +- homeassistant/components/automation/zone.py | 78 +- homeassistant/components/avea/light.py | 10 +- homeassistant/components/avion/light.py | 49 +- homeassistant/components/awair/sensor.py | 132 +- homeassistant/components/aws/__init__.py | 22 +- homeassistant/components/aws/config_flow.py | 4 +- homeassistant/components/aws/const.py | 2 +- homeassistant/components/aws/notify.py | 24 +- homeassistant/components/axis/__init__.py | 27 +- homeassistant/components/axis/axis_base.py | 17 +- .../components/axis/binary_sensor.py | 22 +- homeassistant/components/axis/camera.py | 42 +- homeassistant/components/axis/config_flow.py | 125 +- homeassistant/components/axis/const.py | 8 +- homeassistant/components/axis/device.py | 81 +- homeassistant/components/axis/switch.py | 21 +- .../components/azure_event_hub/__init__.py | 64 +- homeassistant/components/baidu/tts.py | 100 +- .../components/bayesian/binary_sensor.py | 195 +- homeassistant/components/bbb_gpio/__init__.py | 17 +- .../components/bbb_gpio/binary_sensor.py | 37 +- homeassistant/components/bbb_gpio/switch.py | 26 +- .../components/bbox/device_tracker.py | 28 +- homeassistant/components/bbox/sensor.py | 74 +- homeassistant/components/bh1750/sensor.py | 84 +- .../components/binary_sensor/__init__.py | 59 +- homeassistant/components/bitcoin/sensor.py | 149 +- homeassistant/components/bizkaibus/sensor.py | 22 +- .../components/blackbird/media_player.py | 85 +- homeassistant/components/blink/__init__.py | 156 +- .../components/blink/alarm_control_panel.py | 11 +- homeassistant/components/blink/camera.py | 4 +- homeassistant/components/blink/sensor.py | 11 +- .../components/blinksticklight/light.py | 29 +- homeassistant/components/blinkt/light.py | 37 +- homeassistant/components/blockchain/sensor.py | 25 +- homeassistant/components/bloomsky/__init__.py | 24 +- .../components/bloomsky/binary_sensor.py | 31 +- homeassistant/components/bloomsky/camera.py | 6 +- homeassistant/components/bloomsky/sensor.py | 69 +- .../components/bluesound/media_player.py | 567 ++-- .../bluetooth_le_tracker/device_tracker.py | 33 +- .../bluetooth_tracker/device_tracker.py | 63 +- homeassistant/components/bme280/sensor.py | 117 +- homeassistant/components/bme680/sensor.py | 225 +- .../bmw_connected_drive/__init__.py | 72 +- .../bmw_connected_drive/binary_sensor.py | 129 +- .../bmw_connected_drive/device_tracker.py | 19 +- .../components/bmw_connected_drive/lock.py | 23 +- .../components/bmw_connected_drive/sensor.py | 79 +- homeassistant/components/bom/camera.py | 111 +- homeassistant/components/bom/sensor.py | 200 +- homeassistant/components/bom/weather.py | 56 +- .../components/braviatv/media_player.py | 116 +- .../components/broadlink/__init__.py | 49 +- homeassistant/components/broadlink/const.py | 8 +- homeassistant/components/broadlink/sensor.py | 66 +- homeassistant/components/broadlink/switch.py | 129 +- .../components/brottsplatskartan/sensor.py | 60 +- homeassistant/components/browser/__init__.py | 28 +- homeassistant/components/brunt/cover.py | 71 +- .../bt_home_hub_5/device_tracker.py | 15 +- .../components/bt_smarthub/device_tracker.py | 33 +- homeassistant/components/buienradar/camera.py | 37 +- homeassistant/components/buienradar/sensor.py | 433 +-- .../components/buienradar/weather.py | 95 +- homeassistant/components/caldav/calendar.py | 139 +- homeassistant/components/calendar/__init__.py | 106 +- homeassistant/components/camera/__init__.py | 343 +-- homeassistant/components/camera/const.py | 6 +- homeassistant/components/camera/prefs.py | 9 +- homeassistant/components/canary/__init__.py | 60 +- .../components/canary/alarm_control_panel.py | 24 +- homeassistant/components/canary/camera.py | 48 +- homeassistant/components/canary/sensor.py | 21 +- homeassistant/components/cast/__init__.py | 12 +- homeassistant/components/cast/config_flow.py | 4 +- homeassistant/components/cast/const.py | 2 +- homeassistant/components/cast/media_player.py | 544 ++-- .../components/cert_expiry/sensor.py | 46 +- .../components/channels/media_player.py | 145 +- .../components/cisco_ios/device_tracker.py | 48 +- .../cisco_mobility_express/device_tracker.py | 50 +- .../components/cisco_webex_teams/notify.py | 33 +- homeassistant/components/ciscospark/notify.py | 30 +- homeassistant/components/citybikes/sensor.py | 180 +- .../components/clementine/media_player.py | 87 +- homeassistant/components/clickatell/notify.py | 20 +- homeassistant/components/clicksend/notify.py | 83 +- .../components/clicksend_tts/notify.py | 86 +- homeassistant/components/climate/__init__.py | 237 +- homeassistant/components/climate/const.py | 98 +- .../components/climate/reproduce_state.py | 44 +- homeassistant/components/cloud/__init__.py | 139 +- .../components/cloud/alexa_config.py | 53 +- .../components/cloud/binary_sensor.py | 9 +- homeassistant/components/cloud/client.py | 65 +- homeassistant/components/cloud/const.py | 58 +- .../components/cloud/google_config.py | 18 +- homeassistant/components/cloud/http_api.py | 436 ++-- homeassistant/components/cloud/prefs.py | 130 +- homeassistant/components/cloud/utils.py | 8 +- .../components/cloudflare/__init__.py | 28 +- homeassistant/components/cmus/media_player.py | 102 +- homeassistant/components/co2signal/sensor.py | 52 +- homeassistant/components/coinbase/__init__.py | 71 +- homeassistant/components/coinbase/sensor.py | 47 +- .../components/coinmarketcap/sensor.py | 130 +- .../components/comed_hourly_pricing/sensor.py | 60 +- .../components/comfoconnect/__init__.py | 74 +- homeassistant/components/comfoconnect/fan.py | 32 +- .../components/comfoconnect/sensor.py | 80 +- .../components/command_line/binary_sensor.py | 64 +- .../components/command_line/cover.py | 55 +- .../components/command_line/notify.py | 15 +- .../components/command_line/sensor.py | 68 +- .../components/command_line/switch.py | 65 +- .../concord232/alarm_control_panel.py | 56 +- .../components/concord232/binary_sensor.py | 85 +- homeassistant/components/config/__init__.py | 85 +- .../components/config/area_registry.py | 106 +- homeassistant/components/config/auth.py | 125 +- .../config/auth_provider_homeassistant.py | 138 +- homeassistant/components/config/automation.py | 19 +- .../components/config/config_entries.py | 151 +- homeassistant/components/config/core.py | 65 +- homeassistant/components/config/customize.py | 14 +- .../components/config/device_registry.py | 67 +- .../components/config/entity_registry.py | 141 +- homeassistant/components/config/group.py | 12 +- homeassistant/components/config/script.py | 17 +- homeassistant/components/config/zwave.py | 128 +- .../components/configurator/__init__.py | 90 +- .../components/conversation/__init__.py | 94 +- homeassistant/components/conversation/util.py | 17 +- .../components/coolmaster/climate.py | 84 +- homeassistant/components/counter/__init__.py | 111 +- homeassistant/components/cover/__init__.py | 157 +- .../components/cppm_tracker/device_tracker.py | 50 +- homeassistant/components/cpuspeed/sensor.py | 35 +- .../components/crimereports/sensor.py | 85 +- homeassistant/components/cups/sensor.py | 150 +- .../components/currencylayer/sensor.py | 58 +- homeassistant/components/daikin/__init__.py | 64 +- homeassistant/components/daikin/climate.py | 116 +- .../components/daikin/config_flow.py | 31 +- homeassistant/components/daikin/const.py | 30 +- homeassistant/components/daikin/sensor.py | 23 +- homeassistant/components/daikin/switch.py | 25 +- .../components/danfoss_air/__init__.py | 78 +- .../components/danfoss_air/binary_sensor.py | 4 +- .../components/danfoss_air/sensor.py | 67 +- .../components/danfoss_air/switch.py | 33 +- homeassistant/components/darksky/sensor.py | 742 ++++-- homeassistant/components/darksky/weather.py | 171 +- homeassistant/components/datadog/__init__.py | 70 +- .../components/ddwrt/device_tracker.py | 78 +- homeassistant/components/deconz/__init__.py | 133 +- .../components/deconz/binary_sensor.py | 27 +- homeassistant/components/deconz/climate.py | 30 +- .../components/deconz/config_flow.py | 81 +- homeassistant/components/deconz/const.py | 47 +- homeassistant/components/deconz/cover.py | 39 +- .../components/deconz/deconz_device.py | 23 +- homeassistant/components/deconz/gateway.py | 79 +- homeassistant/components/deconz/light.py | 107 +- homeassistant/components/deconz/scene.py | 10 +- homeassistant/components/deconz/sensor.py | 43 +- homeassistant/components/deconz/switch.py | 20 +- homeassistant/components/decora/light.py | 45 +- homeassistant/components/decora_wifi/light.py | 56 +- .../components/default_config/__init__.py | 4 +- homeassistant/components/delijn/sensor.py | 68 +- homeassistant/components/deluge/sensor.py | 72 +- homeassistant/components/deluge/switch.py | 42 +- homeassistant/components/demo/__init__.py | 263 +- homeassistant/components/demo/air_quality.py | 11 +- .../components/demo/alarm_control_panel.py | 89 +- .../components/demo/binary_sensor.py | 10 +- homeassistant/components/demo/calendar.py | 48 +- homeassistant/components/demo/camera.py | 15 +- homeassistant/components/demo/climate.py | 191 +- homeassistant/components/demo/cover.py | 45 +- .../components/demo/device_tracker.py | 18 +- homeassistant/components/demo/fan.py | 20 +- homeassistant/components/demo/geo_location.py | 46 +- .../components/demo/image_processing.py | 46 +- homeassistant/components/demo/light.py | 69 +- homeassistant/components/demo/lock.py | 12 +- homeassistant/components/demo/mailbox.py | 38 +- homeassistant/components/demo/media_player.py | 171 +- homeassistant/components/demo/notify.py | 2 +- homeassistant/components/demo/remote.py | 12 +- homeassistant/components/demo/sensor.py | 27 +- homeassistant/components/demo/switch.py | 10 +- homeassistant/components/demo/tts.py | 22 +- homeassistant/components/demo/vacuum.py | 121 +- homeassistant/components/demo/water_heater.py | 44 +- homeassistant/components/demo/weather.py | 113 +- .../components/denon/media_player.py | 172 +- .../components/denonavr/media_player.py | 126 +- .../components/deutsche_bahn/sensor.py | 60 +- .../components/device_automation/__init__.py | 38 +- .../device_sun_light_trigger/__init__.py | 123 +- .../components/device_tracker/__init__.py | 132 +- .../components/device_tracker/config_entry.py | 18 +- .../components/device_tracker/const.py | 44 +- .../components/device_tracker/legacy.py | 335 ++- .../components/device_tracker/setup.py | 109 +- homeassistant/components/dht/sensor.py | 87 +- .../components/dialogflow/__init__.py | 52 +- .../components/dialogflow/config_flow.py | 8 +- .../components/digital_ocean/__init__.py | 37 +- .../components/digital_ocean/binary_sensor.py | 33 +- .../components/digital_ocean/switch.py | 32 +- .../components/digitalloggers/switch.py | 59 +- .../components/directv/media_player.py | 225 +- homeassistant/components/discogs/sensor.py | 101 +- homeassistant/components/discord/notify.py | 26 +- .../components/discovery/__init__.py | 182 +- .../dlib_face_detect/image_processing.py | 23 +- .../dlib_face_identify/image_processing.py | 53 +- homeassistant/components/dlink/switch.py | 50 +- .../components/dlna_dmr/media_player.py | 191 +- homeassistant/components/dnsip/sensor.py | 43 +- homeassistant/components/dominos/__init__.py | 118 +- homeassistant/components/doorbird/__init__.py | 162 +- homeassistant/components/doorbird/camera.py | 38 +- homeassistant/components/doorbird/switch.py | 7 +- homeassistant/components/dovado/__init__.py | 34 +- homeassistant/components/dovado/notify.py | 3 +- homeassistant/components/dovado/sensor.py | 32 +- .../components/downloader/__init__.py | 92 +- homeassistant/components/dsmr/sensor.py | 230 +- .../components/dte_energy_bridge/sensor.py | 39 +- .../components/dublin_bus_transport/sensor.py | 89 +- homeassistant/components/duckdns/__init__.py | 52 +- .../components/duke_energy/sensor.py | 15 +- .../components/dunehd/media_player.py | 73 +- .../components/dwd_weather_warnings/sensor.py | 147 +- homeassistant/components/dweet/__init__.py | 40 +- homeassistant/components/dweet/sensor.py | 31 +- homeassistant/components/dyson/__init__.py | 75 +- homeassistant/components/dyson/air_quality.py | 40 +- homeassistant/components/dyson/climate.py | 34 +- homeassistant/components/dyson/fan.py | 262 +- homeassistant/components/dyson/sensor.py | 45 +- homeassistant/components/dyson/vacuum.py | 76 +- homeassistant/components/ebox/sensor.py | 74 +- homeassistant/components/ebusd/__init__.py | 77 +- homeassistant/components/ebusd/const.py | 184 +- homeassistant/components/ebusd/sensor.py | 33 +- .../components/ecoal_boiler/__init__.py | 92 +- homeassistant/components/ecobee/__init__.py | 56 +- .../components/ecobee/binary_sensor.py | 21 +- homeassistant/components/ecobee/climate.py | 321 ++- homeassistant/components/ecobee/notify.py | 11 +- homeassistant/components/ecobee/sensor.py | 31 +- homeassistant/components/ecobee/weather.py | 78 +- .../components/econet/water_heater.py | 119 +- homeassistant/components/ecovacs/__init__.py | 63 +- homeassistant/components/ecovacs/vacuum.py | 73 +- .../eddystone_temperature/sensor.py | 75 +- homeassistant/components/edimax/switch.py | 31 +- homeassistant/components/edp_redy/__init__.py | 56 +- homeassistant/components/edp_redy/sensor.py | 25 +- homeassistant/components/edp_redy/switch.py | 24 +- .../components/ee_brightbox/device_tracker.py | 53 +- homeassistant/components/efergy/sensor.py | 148 +- homeassistant/components/egardia/__init__.py | 124 +- .../components/egardia/alarm_control_panel.py | 80 +- .../components/egardia/binary_sensor.py | 23 +- .../components/eight_sleep/__init__.py | 142 +- .../components/eight_sleep/binary_sensor.py | 17 +- .../components/eight_sleep/sensor.py | 186 +- homeassistant/components/eliqonline/sensor.py | 29 +- homeassistant/components/elkm1/__init__.py | 212 +- .../components/elkm1/alarm_control_panel.py | 130 +- homeassistant/components/elkm1/climate.py | 63 +- homeassistant/components/elkm1/light.py | 11 +- homeassistant/components/elkm1/scene.py | 8 +- homeassistant/components/elkm1/sensor.py | 128 +- homeassistant/components/elkm1/switch.py | 10 +- homeassistant/components/elv/switch.py | 34 +- homeassistant/components/emby/media_player.py | 108 +- homeassistant/components/emoncms/sensor.py | 136 +- .../components/emoncms_history/__init__.py | 69 +- .../components/emulated_hue/__init__.py | 188 +- .../components/emulated_hue/hue_api.py | 296 ++- homeassistant/components/emulated_hue/upnp.py | 50 +- .../components/emulated_roku/__init__.py | 67 +- .../components/emulated_roku/binding.py | 125 +- .../components/emulated_roku/config_flow.py | 30 +- .../components/emulated_roku/const.py | 14 +- .../components/enigma2/media_player.py | 137 +- homeassistant/components/enocean/__init__.py | 35 +- .../components/enocean/binary_sensor.py | 37 +- homeassistant/components/enocean/light.py | 29 +- homeassistant/components/enocean/sensor.py | 97 +- homeassistant/components/enocean/switch.py | 56 +- .../components/enphase_envoy/sensor.py | 70 +- .../entur_public_transport/sensor.py | 136 +- .../components/environment_canada/camera.py | 48 +- .../components/environment_canada/sensor.py | 97 +- .../components/environment_canada/weather.py | 166 +- homeassistant/components/envirophat/sensor.py | 99 +- .../components/envisalink/__init__.py | 191 +- .../envisalink/alarm_control_panel.py | 108 +- .../components/envisalink/binary_sensor.py | 30 +- homeassistant/components/envisalink/sensor.py | 40 +- homeassistant/components/ephember/climate.py | 59 +- .../components/epson/media_player.py | 119 +- .../components/epsonworkforce/sensor.py | 34 +- .../components/eq3btsmart/climate.py | 54 +- homeassistant/components/esphome/__init__.py | 220 +- .../components/esphome/binary_sensor.py | 11 +- homeassistant/components/esphome/camera.py | 20 +- homeassistant/components/esphome/climate.py | 60 +- .../components/esphome/config_flow.py | 83 +- homeassistant/components/esphome/cover.py | 51 +- .../components/esphome/entry_data.py | 108 +- homeassistant/components/esphome/fan.py | 48 +- homeassistant/components/esphome/light.py | 70 +- homeassistant/components/esphome/sensor.py | 39 +- homeassistant/components/esphome/switch.py | 16 +- homeassistant/components/essent/sensor.py | 47 +- homeassistant/components/etherscan/sensor.py | 20 +- homeassistant/components/eufy/__init__.py | 85 +- homeassistant/components/eufy/light.py | 49 +- homeassistant/components/eufy/switch.py | 8 +- homeassistant/components/everlights/light.py | 45 +- homeassistant/components/evohome/__init__.py | 165 +- homeassistant/components/evohome/climate.py | 188 +- homeassistant/components/evohome/const.py | 28 +- .../components/evohome/water_heater.py | 47 +- homeassistant/components/facebook/notify.py | 83 +- .../components/facebox/image_processing.py | 178 +- homeassistant/components/fail2ban/sensor.py | 45 +- homeassistant/components/familyhub/camera.py | 16 +- homeassistant/components/fan/__init__.py | 96 +- .../components/fastdotcom/__init__.py | 39 +- homeassistant/components/fastdotcom/sensor.py | 11 +- homeassistant/components/fedex/sensor.py | 48 +- .../components/feedreader/__init__.py | 114 +- homeassistant/components/ffmpeg/__init__.py | 91 +- homeassistant/components/ffmpeg/camera.py | 41 +- .../components/ffmpeg_motion/binary_sensor.py | 63 +- .../components/ffmpeg_noise/binary_sensor.py | 55 +- homeassistant/components/fibaro/__init__.py | 333 +-- .../components/fibaro/binary_sensor.py | 28 +- homeassistant/components/fibaro/climate.py | 135 +- homeassistant/components/fibaro/cover.py | 10 +- homeassistant/components/fibaro/light.py | 77 +- homeassistant/components/fibaro/scene.py | 7 +- homeassistant/components/fibaro/sensor.py | 40 +- homeassistant/components/fibaro/switch.py | 8 +- homeassistant/components/fido/sensor.py | 96 +- homeassistant/components/file/notify.py | 29 +- homeassistant/components/file/sensor.py | 43 +- homeassistant/components/filesize/sensor.py | 28 +- homeassistant/components/filter/sensor.py | 341 ++- homeassistant/components/fints/sensor.py | 111 +- homeassistant/components/fitbit/sensor.py | 461 ++-- homeassistant/components/fixer/sensor.py | 31 +- .../components/fleetgo/device_tracker.py | 56 +- homeassistant/components/flexit/climate.py | 47 +- .../components/flic/binary_sensor.py | 98 +- homeassistant/components/flock/notify.py | 19 +- homeassistant/components/flunearyou/sensor.py | 167 +- homeassistant/components/flux/switch.py | 229 +- homeassistant/components/flux_led/light.py | 231 +- homeassistant/components/folder/sensor.py | 36 +- .../components/folder_watcher/__init__.py | 54 +- homeassistant/components/foobot/sensor.py | 90 +- .../components/fortigate/__init__.py | 48 +- .../components/fortigate/device_tracker.py | 28 +- .../components/fortios/device_tracker.py | 33 +- homeassistant/components/foscam/camera.py | 41 +- .../components/foursquare/__init__.py | 83 +- .../components/free_mobile/notify.py | 14 +- homeassistant/components/freebox/__init__.py | 50 +- .../components/freebox/device_tracker.py | 22 +- homeassistant/components/freebox/sensor.py | 17 +- homeassistant/components/freebox/switch.py | 15 +- homeassistant/components/freedns/__init__.py | 36 +- .../components/fritz/device_tracker.py | 38 +- homeassistant/components/fritzbox/__init__.py | 63 +- .../components/fritzbox/binary_sensor.py | 2 +- homeassistant/components/fritzbox/climate.py | 34 +- homeassistant/components/fritzbox/sensor.py | 11 +- homeassistant/components/fritzbox/switch.py | 29 +- .../components/fritzbox_callmonitor/sensor.py | 129 +- .../components/fritzbox_netmonitor/sensor.py | 44 +- homeassistant/components/fritzdect/switch.py | 68 +- homeassistant/components/fronius/sensor.py | 80 +- homeassistant/components/frontend/__init__.py | 323 +-- homeassistant/components/frontend/storage.py | 57 +- .../frontier_silicon/media_player.py | 99 +- homeassistant/components/futurenow/light.py | 74 +- homeassistant/components/garadget/cover.py | 183 +- homeassistant/components/gc100/__init__.py | 26 +- .../components/gc100/binary_sensor.py | 18 +- homeassistant/components/gc100/switch.py | 13 +- homeassistant/components/gearbest/sensor.py | 53 +- homeassistant/components/geizhals/sensor.py | 50 +- homeassistant/components/generic/camera.py | 81 +- .../components/generic_thermostat/climate.py | 217 +- .../components/geniushub/__init__.py | 75 +- .../components/geniushub/binary_sensor.py | 28 +- homeassistant/components/geniushub/climate.py | 52 +- homeassistant/components/geniushub/sensor.py | 67 +- .../components/geniushub/water_heater.py | 59 +- .../geo_json_events/geo_location.py | 78 +- .../components/geo_location/__init__.py | 15 +- .../components/geo_rss_events/sensor.py | 111 +- homeassistant/components/geofency/__init__.py | 117 +- .../components/geofency/config_flow.py | 6 +- homeassistant/components/geofency/const.py | 2 +- .../components/geofency/device_tracker.py | 35 +- homeassistant/components/github/sensor.py | 70 +- homeassistant/components/gitlab_ci/sensor.py | 81 +- homeassistant/components/gitter/sensor.py | 34 +- homeassistant/components/glances/sensor.py | 230 +- homeassistant/components/gntp/notify.py | 69 +- homeassistant/components/goalfeed/__init__.py | 49 +- homeassistant/components/gogogate2/cover.py | 54 +- homeassistant/components/google/__init__.py | 290 ++- homeassistant/components/google/calendar.py | 84 +- .../components/google_assistant/__init__.py | 84 +- .../components/google_assistant/const.py | 111 +- .../components/google_assistant/error.py | 15 +- .../components/google_assistant/helpers.py | 66 +- .../components/google_assistant/http.py | 19 +- .../components/google_assistant/smart_home.py | 131 +- .../components/google_assistant/trait.py | 917 +++---- homeassistant/components/google_cloud/tts.py | 183 +- .../components/google_domains/__init__.py | 52 +- .../components/google_maps/device_tracker.py | 74 +- .../components/google_pubsub/__init__.py | 62 +- .../components/google_translate/tts.py | 112 +- .../components/google_travel_time/sensor.py | 228 +- .../components/google_wifi/sensor.py | 108 +- .../components/googlehome/__init__.py | 68 +- .../components/googlehome/device_tracker.py | 53 +- homeassistant/components/googlehome/sensor.py | 34 +- .../components/gpmdp/media_player.py | 240 +- homeassistant/components/gpsd/sensor.py | 34 +- .../components/gpslogger/__init__.py | 70 +- .../components/gpslogger/config_flow.py | 6 +- homeassistant/components/gpslogger/const.py | 16 +- .../components/gpslogger/device_tracker.py | 42 +- homeassistant/components/graphite/__init__.py | 75 +- .../components/greeneye_monitor/__init__.py | 201 +- .../components/greeneye_monitor/sensor.py | 99 +- homeassistant/components/greenwave/light.py | 50 +- homeassistant/components/group/__init__.py | 311 ++- homeassistant/components/group/cover.py | 145 +- homeassistant/components/group/light.py | 109 +- homeassistant/components/group/notify.py | 30 +- .../components/group/reproduce_state.py | 25 +- .../components/gstreamer/media_player.py | 35 +- homeassistant/components/gtfs/sensor.py | 552 ++-- homeassistant/components/gtt/sensor.py | 24 +- homeassistant/components/habitica/__init__.py | 112 +- homeassistant/components/habitica/sensor.py | 13 +- homeassistant/components/hangouts/__init__.py | 121 +- .../components/hangouts/config_flow.py | 77 +- homeassistant/components/hangouts/const.py | 114 +- .../components/hangouts/hangouts_bot.py | 200 +- homeassistant/components/hangouts/intents.py | 8 +- homeassistant/components/hangouts/notify.py | 36 +- .../harman_kardon_avr/media_player.py | 38 +- homeassistant/components/harmony/remote.py | 205 +- homeassistant/components/hassio/__init__.py | 203 +- .../components/hassio/addon_panel.py | 8 +- homeassistant/components/hassio/auth.py | 20 +- homeassistant/components/hassio/const.py | 34 +- homeassistant/components/hassio/discovery.py | 23 +- homeassistant/components/hassio/handler.py | 45 +- homeassistant/components/hassio/http.py | 49 +- homeassistant/components/hassio/ingress.py | 61 +- .../components/haveibeenpwned/sensor.py | 43 +- homeassistant/components/hddtemp/sensor.py | 61 +- homeassistant/components/hdmi_cec/__init__.py | 289 ++- .../components/hdmi_cec/media_player.py | 97 +- homeassistant/components/hdmi_cec/switch.py | 9 +- homeassistant/components/heatmiser/climate.py | 62 +- homeassistant/components/heos/__init__.py | 183 +- homeassistant/components/heos/config_flow.py | 32 +- homeassistant/components/heos/const.py | 2 +- homeassistant/components/heos/media_player.py | 127 +- homeassistant/components/heos/services.py | 26 +- .../components/hikvision/binary_sensor.py | 150 +- .../components/hikvisioncam/switch.py | 36 +- homeassistant/components/hipchat/notify.py | 81 +- homeassistant/components/history/__init__.py | 220 +- .../components/history_graph/__init__.py | 29 +- .../components/history_stats/sensor.py | 127 +- .../components/hitron_coda/device_tracker.py | 64 +- homeassistant/components/hive/__init__.py | 41 +- .../components/hive/binary_sensor.py | 25 +- homeassistant/components/hive/climate.py | 44 +- homeassistant/components/hive/light.py | 62 +- homeassistant/components/hive/sensor.py | 28 +- homeassistant/components/hive/switch.py | 17 +- homeassistant/components/hive/water_heater.py | 35 +- homeassistant/components/hlk_sw16/__init__.py | 97 +- homeassistant/components/hlk_sw16/switch.py | 3 +- .../components/homeassistant/__init__.py | 111 +- .../components/homeassistant/scene.py | 58 +- homeassistant/components/homekit/__init__.py | 280 +- .../components/homekit/accessories.py | 131 +- homeassistant/components/homekit/const.py | 270 +- .../components/homekit/type_covers.py | 73 +- homeassistant/components/homekit/type_fans.py | 97 +- .../components/homekit/type_lights.py | 145 +- .../components/homekit/type_locks.py | 28 +- .../components/homekit/type_media_players.py | 285 +- .../homekit/type_security_systems.py | 45 +- .../components/homekit/type_sensors.py | 164 +- .../components/homekit/type_switches.py | 95 +- .../components/homekit/type_thermostats.py | 283 +- homeassistant/components/homekit/util.py | 154 +- .../components/homekit_controller/__init__.py | 90 +- .../homekit_controller/alarm_control_panel.py | 35 +- .../homekit_controller/binary_sensor.py | 24 +- .../components/homekit_controller/climate.py | 90 +- .../homekit_controller/config_flow.py | 158 +- .../homekit_controller/connection.py | 104 +- .../components/homekit_controller/const.py | 36 +- .../components/homekit_controller/cover.py | 113 +- .../components/homekit_controller/light.py | 66 +- .../components/homekit_controller/lock.py | 41 +- .../components/homekit_controller/sensor.py | 29 +- .../components/homekit_controller/storage.py | 19 +- .../components/homekit_controller/switch.py | 27 +- .../components/homematic/__init__.py | 763 +++--- .../components/homematic/binary_sensor.py | 33 +- homeassistant/components/homematic/climate.py | 38 +- homeassistant/components/homematic/cover.py | 12 +- homeassistant/components/homematic/light.py | 24 +- homeassistant/components/homematic/notify.py | 33 +- homeassistant/components/homematic/sensor.py | 91 +- .../components/homematicip_cloud/__init__.py | 162 +- .../homematicip_cloud/alarm_control_panel.py | 43 +- .../homematicip_cloud/binary_sensor.py | 177 +- .../components/homematicip_cloud/climate.py | 32 +- .../homematicip_cloud/config_flow.py | 57 +- .../components/homematicip_cloud/const.py | 32 +- .../components/homematicip_cloud/cover.py | 13 +- .../components/homematicip_cloud/device.py | 46 +- .../components/homematicip_cloud/hap.py | 53 +- .../components/homematicip_cloud/light.py | 89 +- .../components/homematicip_cloud/sensor.py | 199 +- .../components/homematicip_cloud/switch.py | 43 +- .../components/homematicip_cloud/weather.py | 22 +- .../components/homeworks/__init__.py | 90 +- homeassistant/components/homeworks/light.py | 30 +- homeassistant/components/honeywell/climate.py | 263 +- homeassistant/components/hook/switch.py | 69 +- .../components/horizon/media_player.py | 64 +- homeassistant/components/hp_ilo/sensor.py | 109 +- homeassistant/components/html5/notify.py | 369 +-- homeassistant/components/http/__init__.py | 182 +- homeassistant/components/http/auth.py | 136 +- homeassistant/components/http/ban.py | 91 +- homeassistant/components/http/const.py | 8 +- homeassistant/components/http/cors.py | 43 +- .../components/http/data_validator.py | 15 +- homeassistant/components/http/real_ip.py | 18 +- homeassistant/components/http/static.py | 5 +- homeassistant/components/http/view.py | 58 +- homeassistant/components/htu21d/sensor.py | 33 +- .../components/huawei_lte/__init__.py | 49 +- .../components/huawei_lte/device_tracker.py | 8 +- homeassistant/components/huawei_lte/notify.py | 18 +- homeassistant/components/huawei_lte/sensor.py | 121 +- .../huawei_router/device_tracker.py | 89 +- homeassistant/components/hue/__init__.py | 86 +- homeassistant/components/hue/binary_sensor.py | 20 +- homeassistant/components/hue/bridge.py | 90 +- homeassistant/components/hue/config_flow.py | 140 +- homeassistant/components/hue/const.py | 2 +- homeassistant/components/hue/light.py | 222 +- homeassistant/components/hue/sensor.py | 28 +- homeassistant/components/hue/sensor_base.py | 116 +- .../hunterdouglas_powerview/scene.py | 60 +- .../components/hydrawise/__init__.py | 91 +- .../components/hydrawise/binary_sensor.py | 48 +- homeassistant/components/hydrawise/sensor.py | 36 +- homeassistant/components/hydrawise/switch.py | 65 +- .../components/hydroquebec/sensor.py | 163 +- homeassistant/components/hyperion/light.py | 200 +- .../components/ialarm/alarm_control_panel.py | 40 +- .../components/icloud/device_tracker.py | 294 ++- .../components/idteck_prox/__init__.py | 38 +- homeassistant/components/ifttt/__init__.py | 59 +- .../components/ifttt/alarm_control_panel.py | 72 +- homeassistant/components/ifttt/config_flow.py | 8 +- homeassistant/components/iglo/light.py | 58 +- .../components/ign_sismologia/geo_location.py | 117 +- homeassistant/components/ihc/__init__.py | 384 +-- homeassistant/components/ihc/binary_sensor.py | 31 +- homeassistant/components/ihc/const.py | 42 +- homeassistant/components/ihc/ihcdevice.py | 28 +- homeassistant/components/ihc/light.py | 50 +- homeassistant/components/ihc/sensor.py | 13 +- homeassistant/components/ihc/switch.py | 31 +- homeassistant/components/ihc/util.py | 10 +- .../components/image_processing/__init__.py | 80 +- homeassistant/components/imap/sensor.py | 73 +- .../components/imap_email_content/sensor.py | 99 +- .../components/incomfort/__init__.py | 38 +- .../components/incomfort/binary_sensor.py | 16 +- homeassistant/components/incomfort/climate.py | 17 +- homeassistant/components/incomfort/sensor.py | 34 +- .../components/incomfort/water_heater.py | 28 +- homeassistant/components/influxdb/__init__.py | 240 +- homeassistant/components/influxdb/sensor.py | 137 +- .../components/input_boolean/__init__.py | 48 +- .../components/input_datetime/__init__.py | 125 +- .../components/input_number/__init__.py | 143 +- .../components/input_select/__init__.py | 101 +- .../components/input_text/__init__.py | 110 +- homeassistant/components/insteon/__init__.py | 669 ++--- .../components/insteon/binary_sensor.py | 34 +- homeassistant/components/insteon/cover.py | 28 +- homeassistant/components/insteon/fan.py | 36 +- homeassistant/components/insteon/light.py | 19 +- homeassistant/components/insteon/sensor.py | 16 +- homeassistant/components/insteon/switch.py | 18 +- .../components/integration/sensor.py | 139 +- .../components/intent_script/__init__.py | 74 +- homeassistant/components/ios/__init__.py | 267 +- homeassistant/components/ios/config_flow.py | 4 +- homeassistant/components/ios/notify.py | 53 +- homeassistant/components/ios/sensor.py | 37 +- homeassistant/components/iota/__init__.py | 44 +- homeassistant/components/iota/sensor.py | 39 +- homeassistant/components/iperf3/__init__.py | 123 +- homeassistant/components/iperf3/sensor.py | 22 +- homeassistant/components/ipma/__init__.py | 12 +- homeassistant/components/ipma/config_flow.py | 30 +- homeassistant/components/ipma/const.py | 9 +- homeassistant/components/ipma/weather.py | 100 +- homeassistant/components/iqvia/__init__.py | 111 +- homeassistant/components/iqvia/config_flow.py | 13 +- homeassistant/components/iqvia/const.py | 50 +- homeassistant/components/iqvia/sensor.py | 177 +- .../components/irish_rail_transport/sensor.py | 98 +- .../components/islamic_prayer_times/sensor.py | 89 +- homeassistant/components/iss/binary_sensor.py | 38 +- homeassistant/components/isy994/__init__.py | 346 ++- .../components/isy994/binary_sensor.py | 74 +- homeassistant/components/isy994/cover.py | 16 +- homeassistant/components/isy994/fan.py | 15 +- homeassistant/components/isy994/light.py | 5 +- homeassistant/components/isy994/lock.py | 20 +- homeassistant/components/isy994/sensor.py | 421 ++- homeassistant/components/isy994/switch.py | 13 +- homeassistant/components/itach/remote.py | 62 +- .../components/itunes/media_player.py | 256 +- .../components/jewish_calendar/sensor.py | 176 +- .../components/joaoapps_join/__init__.py | 129 +- .../components/joaoapps_join/notify.py | 62 +- homeassistant/components/juicenet/__init__.py | 15 +- homeassistant/components/juicenet/sensor.py | 74 +- homeassistant/components/kankun/switch.py | 60 +- .../keenetic_ndms2/device_tracker.py | 56 +- homeassistant/components/keyboard/__init__.py | 72 +- .../components/keyboard_remote/__init__.py | 94 +- homeassistant/components/kira/__init__.py | 94 +- homeassistant/components/kira/remote.py | 10 +- homeassistant/components/kira/sensor.py | 6 +- homeassistant/components/kiwi/lock.py | 59 +- homeassistant/components/knx/__init__.py | 230 +- homeassistant/components/knx/binary_sensor.py | 81 +- homeassistant/components/knx/climate.py | 209 +- homeassistant/components/knx/cover.py | 89 +- homeassistant/components/knx/light.py | 151 +- homeassistant/components/knx/notify.py | 36 +- homeassistant/components/knx/scene.py | 25 +- homeassistant/components/knx/sensor.py | 29 +- homeassistant/components/knx/switch.py | 25 +- homeassistant/components/kodi/media_player.py | 609 +++-- homeassistant/components/kodi/notify.py | 60 +- .../components/konnected/__init__.py | 449 ++-- .../components/konnected/binary_sensor.py | 29 +- homeassistant/components/konnected/const.py | 40 +- .../components/konnected/handlers.py | 41 +- homeassistant/components/konnected/sensor.py | 80 +- homeassistant/components/konnected/switch.py | 52 +- homeassistant/components/kwb/sensor.py | 65 +- homeassistant/components/lacrosse/sensor.py | 106 +- homeassistant/components/lametric/__init__.py | 32 +- homeassistant/components/lametric/notify.py | 84 +- homeassistant/components/lannouncer/notify.py | 28 +- homeassistant/components/lastfm/sensor.py | 28 +- .../components/launch_library/sensor.py | 25 +- homeassistant/components/lcn/__init__.py | 371 ++- homeassistant/components/lcn/binary_sensor.py | 35 +- homeassistant/components/lcn/climate.py | 39 +- homeassistant/components/lcn/const.py | 243 +- homeassistant/components/lcn/cover.py | 52 +- homeassistant/components/lcn/helpers.py | 59 +- homeassistant/components/lcn/light.py | 48 +- homeassistant/components/lcn/scene.py | 30 +- homeassistant/components/lcn/sensor.py | 45 +- homeassistant/components/lcn/services.py | 287 +- homeassistant/components/lcn/switch.py | 17 +- .../components/lg_netcast/media_player.py | 85 +- .../components/lg_soundbar/media_player.py | 123 +- homeassistant/components/life360/__init__.py | 168 +- .../components/life360/config_flow.py | 29 +- homeassistant/components/life360/const.py | 24 +- .../components/life360/device_tracker.py | 235 +- homeassistant/components/lifx/__init__.py | 40 +- homeassistant/components/lifx/config_flow.py | 3 +- homeassistant/components/lifx/const.py | 2 +- homeassistant/components/lifx/light.py | 338 +-- homeassistant/components/lifx_cloud/scene.py | 25 +- homeassistant/components/lifx_legacy/light.py | 92 +- homeassistant/components/light/__init__.py | 194 +- .../components/light/device_automation.py | 46 +- .../components/lightwave/__init__.py | 49 +- homeassistant/components/lightwave/light.py | 9 +- homeassistant/components/lightwave/switch.py | 3 +- .../components/limitlessled/light.py | 193 +- .../components/linksys_ap/device_tracker.py | 45 +- .../linksys_smart/device_tracker.py | 38 +- homeassistant/components/linky/sensor.py | 46 +- homeassistant/components/linode/__init__.py | 38 +- .../components/linode/binary_sensor.py | 29 +- homeassistant/components/linode/switch.py | 28 +- .../components/linux_battery/sensor.py | 69 +- homeassistant/components/lirc/__init__.py | 23 +- homeassistant/components/litejet/__init__.py | 37 +- homeassistant/components/litejet/light.py | 11 +- homeassistant/components/litejet/scene.py | 8 +- homeassistant/components/litejet/switch.py | 8 +- .../components/liveboxplaytv/media_player.py | 100 +- .../components/llamalab_automate/notify.py | 19 +- homeassistant/components/local_file/camera.py | 50 +- homeassistant/components/locative/__init__.py | 109 +- .../components/locative/config_flow.py | 6 +- .../components/locative/device_tracker.py | 21 +- homeassistant/components/lock/__init__.py | 46 +- homeassistant/components/lockitron/lock.py | 48 +- homeassistant/components/logbook/__init__.py | 389 +-- .../components/logentries/__init__.py | 39 +- homeassistant/components/logger/__init__.py | 71 +- .../components/logi_circle/__init__.py | 162 +- .../components/logi_circle/camera.py | 117 +- .../components/logi_circle/config_flow.py | 99 +- homeassistant/components/logi_circle/const.py | 47 +- .../components/logi_circle/sensor.py | 82 +- homeassistant/components/london_air/sensor.py | 148 +- .../components/london_underground/sensor.py | 46 +- homeassistant/components/loopenergy/sensor.py | 65 +- homeassistant/components/lovelace/__init__.py | 116 +- .../components/luci/device_tracker.py | 45 +- .../components/luftdaten/__init__.py | 139 +- .../components/luftdaten/config_flow.py | 43 +- homeassistant/components/luftdaten/const.py | 6 +- homeassistant/components/luftdaten/sensor.py | 40 +- homeassistant/components/lupusec/__init__.py | 44 +- .../components/lupusec/alarm_control_panel.py | 9 +- .../components/lupusec/binary_sensor.py | 3 +- homeassistant/components/lutron/__init__.py | 85 +- homeassistant/components/lutron/cover.py | 15 +- homeassistant/components/lutron/light.py | 7 +- homeassistant/components/lutron/scene.py | 14 +- homeassistant/components/lutron/switch.py | 4 +- .../components/lutron_caseta/__init__.py | 60 +- .../components/lutron_caseta/cover.py | 16 +- .../components/lutron_caseta/light.py | 15 +- .../components/lutron_caseta/scene.py | 7 +- .../components/lutron_caseta/switch.py | 3 +- homeassistant/components/lw12wifi/light.py | 54 +- homeassistant/components/lyft/sensor.py | 204 +- .../components/magicseaweed/sensor.py | 99 +- homeassistant/components/mailbox/__init__.py | 53 +- homeassistant/components/mailgun/__init__.py | 44 +- .../components/mailgun/config_flow.py | 8 +- homeassistant/components/mailgun/notify.py | 44 +- .../components/manual/alarm_control_panel.py | 231 +- .../manual_mqtt/alarm_control_panel.py | 275 +- homeassistant/components/map/__init__.py | 5 +- homeassistant/components/marytts/tts.py | 63 +- homeassistant/components/mastodon/notify.py | 32 +- homeassistant/components/matrix/__init__.py | 191 +- homeassistant/components/matrix/notify.py | 25 +- homeassistant/components/maxcube/__init__.py | 54 +- .../components/maxcube/binary_sensor.py | 8 +- homeassistant/components/maxcube/climate.py | 34 +- homeassistant/components/mcp23017/__init__.py | 2 +- .../components/mcp23017/binary_sensor.py | 43 +- homeassistant/components/mcp23017/switch.py | 26 +- .../components/media_extractor/__init__.py | 84 +- .../components/media_player/__init__.py | 388 +-- .../components/media_player/const.py | 84 +- .../media_player/reproduce_state.py | 56 +- .../components/mediaroom/media_player.py | 147 +- homeassistant/components/melissa/__init__.py | 24 +- homeassistant/components/melissa/climate.py | 63 +- .../components/meraki/device_tracker.py | 99 +- .../components/message_bird/notify.py | 23 +- homeassistant/components/met/__init__.py | 10 +- homeassistant/components/met/config_flow.py | 41 +- homeassistant/components/met/const.py | 11 +- homeassistant/components/met/weather.py | 80 +- .../components/meteo_france/__init__.py | 131 +- .../components/meteo_france/sensor.py | 79 +- .../components/meteo_france/weather.py | 29 +- .../components/meteoalarm/binary_sensor.py | 27 +- homeassistant/components/metoffice/sensor.py | 129 +- homeassistant/components/metoffice/weather.py | 37 +- homeassistant/components/mfi/sensor.py | 78 +- homeassistant/components/mfi/switch.py | 59 +- homeassistant/components/mhz19/sensor.py | 62 +- homeassistant/components/microsoft/tts.py | 138 +- .../components/microsoft_face/__init__.py | 142 +- .../microsoft_face_detect/image_processing.py | 56 +- .../image_processing.py | 61 +- homeassistant/components/miflora/sensor.py | 76 +- .../components/mikrotik/device_tracker.py | 179 +- homeassistant/components/mill/climate.py | 87 +- homeassistant/components/min_max/sensor.py | 101 +- homeassistant/components/mitemp_bt/sensor.py | 82 +- homeassistant/components/mjpeg/camera.py | 93 +- .../components/mobile_app/__init__.py | 47 +- .../components/mobile_app/binary_sensor.py | 26 +- .../components/mobile_app/config_flow.py | 13 +- homeassistant/components/mobile_app/const.py | 336 +-- .../components/mobile_app/device_tracker.py | 26 +- homeassistant/components/mobile_app/entity.py | 28 +- .../components/mobile_app/helpers.py | 84 +- .../components/mobile_app/http_api.py | 55 +- homeassistant/components/mobile_app/notify.py | 78 +- homeassistant/components/mobile_app/sensor.py | 27 +- .../components/mobile_app/webhook.py | 235 +- .../components/mobile_app/websocket_api.py | 88 +- homeassistant/components/mochad/__init__.py | 27 +- homeassistant/components/mochad/light.py | 53 +- homeassistant/components/mochad/switch.py | 53 +- homeassistant/components/modbus/__init__.py | 206 +- .../components/modbus/binary_sensor.py | 40 +- homeassistant/components/modbus/climate.py | 108 +- homeassistant/components/modbus/sensor.py | 172 +- homeassistant/components/modbus/switch.py | 170 +- .../components/modem_callerid/sensor.py | 51 +- .../components/mold_indicator/sensor.py | 254 +- .../components/monoprice/media_player.py | 104 +- homeassistant/components/moon/sensor.py | 31 +- homeassistant/components/mopar/__init__.py | 69 +- homeassistant/components/mopar/lock.py | 15 +- homeassistant/components/mopar/sensor.py | 24 +- homeassistant/components/mopar/switch.py | 11 +- .../components/mpchc/media_player.py | 86 +- homeassistant/components/mpd/media_player.py | 119 +- homeassistant/components/mqtt/__init__.py | 753 +++--- .../components/mqtt/alarm_control_panel.py | 183 +- .../components/mqtt/binary_sensor.py | 132 +- homeassistant/components/mqtt/camera.py | 55 +- homeassistant/components/mqtt/climate.py | 551 ++-- homeassistant/components/mqtt/config_flow.py | 59 +- homeassistant/components/mqtt/const.py | 8 +- homeassistant/components/mqtt/cover.py | 408 +-- .../components/mqtt/device_tracker.py | 13 +- homeassistant/components/mqtt/discovery.py | 440 ++-- homeassistant/components/mqtt/fan.py | 297 ++- .../components/mqtt/light/__init__.py | 57 +- .../components/mqtt/light/schema_basic.py | 647 +++-- .../components/mqtt/light/schema_json.py | 284 +- .../components/mqtt/light/schema_template.py | 256 +- homeassistant/components/mqtt/lock.py | 122 +- homeassistant/components/mqtt/sensor.py | 125 +- homeassistant/components/mqtt/server.py | 66 +- homeassistant/components/mqtt/subscription.py | 29 +- homeassistant/components/mqtt/switch.py | 127 +- .../components/mqtt/vacuum/__init__.py | 50 +- .../components/mqtt/vacuum/schema_legacy.py | 443 ++-- .../components/mqtt/vacuum/schema_state.py | 320 ++- .../components/mqtt_eventstream/__init__.py | 63 +- .../components/mqtt_json/device_tracker.py | 48 +- homeassistant/components/mqtt_room/sensor.py | 114 +- .../components/mqtt_statestream/__init__.py | 95 +- homeassistant/components/mvglive/sensor.py | 145 +- homeassistant/components/mychevy/__init__.py | 53 +- .../components/mychevy/binary_sensor.py | 19 +- homeassistant/components/mychevy/sensor.py | 35 +- homeassistant/components/mycroft/__init__.py | 12 +- homeassistant/components/mycroft/notify.py | 3 +- homeassistant/components/myq/cover.py | 43 +- .../components/mysensors/__init__.py | 123 +- .../components/mysensors/binary_sensor.py | 35 +- homeassistant/components/mysensors/climate.py | 78 +- homeassistant/components/mysensors/const.py | 170 +- homeassistant/components/mysensors/cover.py | 23 +- homeassistant/components/mysensors/device.py | 41 +- .../components/mysensors/device_tracker.py | 25 +- homeassistant/components/mysensors/gateway.py | 97 +- homeassistant/components/mysensors/handler.py | 20 +- homeassistant/components/mysensors/helpers.py | 60 +- homeassistant/components/mysensors/light.py | 54 +- homeassistant/components/mysensors/notify.py | 18 +- homeassistant/components/mysensors/sensor.py | 91 +- homeassistant/components/mysensors/switch.py | 86 +- .../components/mystrom/binary_sensor.py | 39 +- homeassistant/components/mystrom/light.py | 55 +- homeassistant/components/mystrom/switch.py | 25 +- .../components/mythicbeastsdns/__init__.py | 31 +- homeassistant/components/n26/__init__.py | 43 +- homeassistant/components/n26/sensor.py | 58 +- homeassistant/components/n26/switch.py | 3 +- homeassistant/components/nad/media_player.py | 198 +- .../components/namecheapdns/__init__.py | 31 +- homeassistant/components/nanoleaf/light.py | 86 +- homeassistant/components/neato/__init__.py | 315 ++- homeassistant/components/neato/camera.py | 6 +- homeassistant/components/neato/switch.py | 25 +- homeassistant/components/neato/vacuum.py | 216 +- .../nederlandse_spoorwegen/sensor.py | 146 +- homeassistant/components/nello/lock.py | 42 +- .../components/ness_alarm/__init__.py | 134 +- .../ness_alarm/alarm_control_panel.py | 17 +- .../components/ness_alarm/binary_sensor.py | 20 +- homeassistant/components/nest/__init__.py | 262 +- .../components/nest/binary_sensor.py | 102 +- homeassistant/components/nest/camera.py | 30 +- homeassistant/components/nest/climate.py | 91 +- homeassistant/components/nest/config_flow.py | 67 +- homeassistant/components/nest/const.py | 2 +- homeassistant/components/nest/local_auth.py | 12 +- homeassistant/components/nest/sensor.py | 136 +- homeassistant/components/netatmo/__init__.py | 167 +- .../components/netatmo/binary_sensor.py | 191 +- homeassistant/components/netatmo/camera.py | 77 +- homeassistant/components/netatmo/climate.py | 228 +- homeassistant/components/netatmo/const.py | 6 +- homeassistant/components/netatmo/sensor.py | 483 ++-- homeassistant/components/netdata/sensor.py | 79 +- .../components/netgear/device_tracker.py | 84 +- .../components/netgear_lte/__init__.py | 214 +- .../components/netgear_lte/binary_sensor.py | 3 +- .../components/netgear_lte/notify.py | 3 +- .../components/netgear_lte/sensor.py | 8 +- .../components/netgear_lte/sensor_types.py | 38 +- homeassistant/components/netio/switch.py | 80 +- .../components/neurio_energy/sensor.py | 50 +- homeassistant/components/nextbus/sensor.py | 153 +- .../components/nfandroidtv/notify.py | 250 +- .../components/niko_home_control/light.py | 38 +- homeassistant/components/nilu/air_quality.py | 148 +- .../components/nissan_leaf/__init__.py | 269 +- .../components/nissan_leaf/binary_sensor.py | 8 +- .../components/nissan_leaf/device_tracker.py | 22 +- .../components/nissan_leaf/sensor.py | 27 +- .../components/nissan_leaf/switch.py | 3 +- .../components/nmap_tracker/device_tracker.py | 68 +- homeassistant/components/nmbs/sensor.py | 132 +- homeassistant/components/no_ip/__init__.py | 64 +- homeassistant/components/noaa_tides/sensor.py | 75 +- .../components/norway_air/air_quality.py | 69 +- homeassistant/components/notify/__init__.py | 92 +- homeassistant/components/notion/__init__.py | 198 +- .../components/notion/binary_sensor.py | 65 +- .../components/notion/config_flow.py | 25 +- homeassistant/components/notion/const.py | 10 +- homeassistant/components/notion/sensor.py | 47 +- .../components/nsw_fuel_station/sensor.py | 97 +- .../geo_location.py | 136 +- homeassistant/components/nuheat/__init__.py | 24 +- homeassistant/components/nuheat/climate.py | 43 +- .../components/nuimo_controller/__init__.py | 87 +- homeassistant/components/nuki/lock.py | 70 +- homeassistant/components/nut/sensor.py | 274 +- .../components/nx584/alarm_control_panel.py | 51 +- .../components/nx584/binary_sensor.py | 57 +- homeassistant/components/nzbget/sensor.py | 99 +- .../components/oasa_telematics/sensor.py | 95 +- .../components/octoprint/__init__.py | 187 +- .../components/octoprint/binary_sensor.py | 30 +- homeassistant/components/octoprint/sensor.py | 73 +- homeassistant/components/oem/climate.py | 41 +- homeassistant/components/ohmconnect/sensor.py | 19 +- .../components/onboarding/__init__.py | 19 +- homeassistant/components/onboarding/const.py | 20 +- homeassistant/components/onboarding/views.py | 128 +- homeassistant/components/onewire/sensor.py | 110 +- .../components/onkyo/media_player.py | 258 +- homeassistant/components/onvif/camera.py | 232 +- .../openalpr_cloud/image_processing.py | 82 +- .../openalpr_local/image_processing.py | 100 +- .../components/opencv/image_processing.py | 107 +- homeassistant/components/openevse/sensor.py | 50 +- .../components/openexchangerates/sensor.py | 37 +- homeassistant/components/opengarage/cover.py | 103 +- .../components/openhardwaremonitor/sensor.py | 60 +- .../components/openhome/media_player.py | 62 +- .../components/opensensemap/air_quality.py | 21 +- homeassistant/components/opensky/sensor.py | 114 +- .../components/opentherm_gw/__init__.py | 329 ++- .../components/opentherm_gw/binary_sensor.py | 23 +- .../components/opentherm_gw/climate.py | 43 +- .../components/opentherm_gw/const.py | 304 ++- .../components/opentherm_gw/sensor.py | 19 +- homeassistant/components/openuv/__init__.py | 186 +- .../components/openuv/binary_sensor.py | 60 +- .../components/openuv/config_flow.py | 41 +- homeassistant/components/openuv/const.py | 2 +- homeassistant/components/openuv/sensor.py | 101 +- .../components/openweathermap/sensor.py | 136 +- .../components/openweathermap/weather.py | 193 +- homeassistant/components/opple/light.py | 52 +- .../components/orangepi_gpio/__init__.py | 34 +- .../components/orangepi_gpio/binary_sensor.py | 6 +- .../components/orangepi_gpio/const.py | 8 +- homeassistant/components/orvibo/switch.py | 40 +- .../components/osramlightify/light.py | 171 +- homeassistant/components/otp/sensor.py | 20 +- homeassistant/components/owlet/__init__.py | 41 +- .../components/owlet/binary_sensor.py | 19 +- homeassistant/components/owlet/const.py | 8 +- homeassistant/components/owlet/sensor.py | 37 +- .../components/owntracks/__init__.py | 196 +- .../components/owntracks/config_flow.py | 51 +- .../components/owntracks/device_tracker.py | 55 +- .../components/owntracks/messages.py | 178 +- .../panasonic_bluray/media_player.py | 53 +- .../panasonic_viera/media_player.py | 85 +- .../components/pandora/media_player.py | 162 +- .../components/panel_custom/__init__.py | 189 +- .../components/panel_iframe/__init__.py | 54 +- homeassistant/components/pencom/switch.py | 37 +- .../persistent_notification/__init__.py | 138 +- homeassistant/components/person/__init__.py | 243 +- .../components/philips_js/media_player.py | 99 +- homeassistant/components/pi_hole/sensor.py | 100 +- homeassistant/components/picotts/tts.py | 18 +- homeassistant/components/piglow/light.py | 24 +- homeassistant/components/pilight/__init__.py | 65 +- .../components/pilight/binary_sensor.py | 102 +- homeassistant/components/pilight/sensor.py | 45 +- homeassistant/components/pilight/switch.py | 107 +- .../components/ping/binary_sensor.py | 97 +- .../components/ping/device_tracker.py | 57 +- .../components/pioneer/media_player.py | 83 +- .../components/pjlink/media_player.py | 69 +- homeassistant/components/plaato/__init__.py | 93 +- .../components/plaato/config_flow.py | 6 +- homeassistant/components/plaato/const.py | 2 +- homeassistant/components/plaato/sensor.py | 59 +- homeassistant/components/plant/__init__.py | 227 +- homeassistant/components/plex/media_player.py | 500 ++-- homeassistant/components/plex/sensor.py | 98 +- .../components/plum_lightpad/__init__.py | 64 +- .../components/plum_lightpad/light.py | 53 +- .../components/pocketcasts/sensor.py | 13 +- homeassistant/components/point/__init__.py | 144 +- .../components/point/alarm_control_panel.py | 49 +- .../components/point/binary_sensor.py | 70 +- homeassistant/components/point/config_flow.py | 88 +- homeassistant/components/point/const.py | 16 +- homeassistant/components/point/sensor.py | 32 +- homeassistant/components/postnl/sensor.py | 32 +- .../components/prezzibenzina/sensor.py | 56 +- homeassistant/components/proliphix/climate.py | 33 +- .../components/prometheus/__init__.py | 134 +- homeassistant/components/prowl/notify.py | 36 +- .../components/proximity/__init__.py | 177 +- homeassistant/components/proxy/camera.py | 152 +- homeassistant/components/ps4/__init__.py | 79 +- homeassistant/components/ps4/config_flow.py | 91 +- homeassistant/components/ps4/const.py | 15 +- homeassistant/components/ps4/media_player.py | 180 +- homeassistant/components/ptvsd/__init__.py | 33 +- .../components/pulseaudio_loopback/switch.py | 69 +- homeassistant/components/push/camera.py | 91 +- homeassistant/components/pushbullet/notify.py | 62 +- homeassistant/components/pushbullet/sensor.py | 46 +- homeassistant/components/pushetta/notify.py | 32 +- homeassistant/components/pushover/notify.py | 48 +- homeassistant/components/pushsafer/notify.py | 109 +- homeassistant/components/pvoutput/sensor.py | 66 +- homeassistant/components/pyload/sensor.py | 88 +- .../components/python_script/__init__.py | 139 +- .../components/qbittorrent/sensor.py | 53 +- .../components/qld_bushfire/geo_location.py | 115 +- homeassistant/components/qnap/sensor.py | 304 +-- .../components/qrcode/image_processing.py | 13 +- .../quantum_gateway/device_tracker.py | 33 +- .../components/qwikswitch/__init__.py | 107 +- .../components/qwikswitch/binary_sensor.py | 23 +- homeassistant/components/qwikswitch/sensor.py | 16 +- homeassistant/components/rachio/__init__.py | 161 +- .../components/rachio/binary_sensor.py | 31 +- homeassistant/components/rachio/switch.py | 61 +- homeassistant/components/radarr/sensor.py | 154 +- .../components/radiotherm/climate.py | 96 +- homeassistant/components/rainbird/__init__.py | 21 +- homeassistant/components/rainbird/sensor.py | 20 +- homeassistant/components/rainbird/switch.py | 42 +- .../components/raincloud/__init__.py | 113 +- .../components/raincloud/binary_sensor.py | 35 +- homeassistant/components/raincloud/sensor.py | 26 +- homeassistant/components/raincloud/switch.py | 47 +- .../components/rainforest_eagle/sensor.py | 54 +- .../components/rainmachine/__init__.py | 362 +-- .../components/rainmachine/binary_sensor.py | 64 +- .../components/rainmachine/config_flow.py | 29 +- homeassistant/components/rainmachine/const.py | 12 +- .../components/rainmachine/sensor.py | 57 +- .../components/rainmachine/switch.py | 303 +-- .../components/random/binary_sensor.py | 21 +- homeassistant/components/random/sensor.py | 37 +- .../components/raspihats/__init__.py | 38 +- .../components/raspihats/binary_sensor.py | 72 +- homeassistant/components/raspihats/switch.py | 70 +- homeassistant/components/raspyrfm/switch.py | 93 +- .../components/recollect_waste/sensor.py | 49 +- homeassistant/components/recorder/__init__.py | 216 +- homeassistant/components/recorder/const.py | 2 +- .../components/recorder/migration.py | 153 +- homeassistant/components/recorder/models.py | 70 +- homeassistant/components/recorder/purge.py | 14 +- homeassistant/components/recorder/util.py | 16 +- homeassistant/components/recswitch/switch.py | 25 +- homeassistant/components/reddit/sensor.py | 95 +- .../components/remember_the_milk/__init__.py | 212 +- homeassistant/components/remote/__init__.py | 104 +- .../components/remote_rpi_gpio/__init__.py | 21 +- .../remote_rpi_gpio/binary_sensor.py | 52 +- .../components/remote_rpi_gpio/switch.py | 28 +- homeassistant/components/repetier/__init__.py | 242 +- homeassistant/components/repetier/sensor.py | 71 +- .../components/rest/binary_sensor.py | 77 +- homeassistant/components/rest/notify.py | 177 +- homeassistant/components/rest/sensor.py | 135 +- homeassistant/components/rest/switch.py | 125 +- .../components/rest_command/__init__.py | 74 +- homeassistant/components/rflink/__init__.py | 349 +-- .../components/rflink/binary_sensor.py | 59 +- homeassistant/components/rflink/cover.py | 67 +- homeassistant/components/rflink/light.py | 141 +- homeassistant/components/rflink/sensor.py | 121 +- homeassistant/components/rflink/switch.py | 80 +- homeassistant/components/rfxtrx/__init__.py | 229 +- .../components/rfxtrx/binary_sensor.py | 123 +- homeassistant/components/rfxtrx/cover.py | 43 +- homeassistant/components/rfxtrx/light.py | 53 +- homeassistant/components/rfxtrx/sensor.py | 52 +- homeassistant/components/rfxtrx/switch.py | 43 +- homeassistant/components/ring/__init__.py | 52 +- .../components/ring/binary_sensor.py | 60 +- homeassistant/components/ring/camera.py | 84 +- homeassistant/components/ring/sensor.py | 162 +- homeassistant/components/ripple/sensor.py | 19 +- .../components/rmvtransport/sensor.py | 179 +- homeassistant/components/rocketchat/notify.py | 44 +- homeassistant/components/roku/__init__.py | 69 +- homeassistant/components/roku/media_player.py | 51 +- homeassistant/components/roku/remote.py | 8 +- homeassistant/components/roomba/vacuum.py | 178 +- homeassistant/components/route53/__init__.py | 86 +- homeassistant/components/rova/sensor.py | 45 +- homeassistant/components/rpi_camera/camera.py | 119 +- homeassistant/components/rpi_gpio/__init__.py | 19 +- .../components/rpi_gpio/binary_sensor.py | 40 +- homeassistant/components/rpi_gpio/cover.py | 75 +- homeassistant/components/rpi_gpio/switch.py | 20 +- .../components/rpi_gpio_pwm/light.py | 101 +- homeassistant/components/rpi_pfio/__init__.py | 13 +- .../components/rpi_pfio/binary_sensor.py | 35 +- homeassistant/components/rpi_pfio/switch.py | 22 +- homeassistant/components/rpi_rf/switch.py | 66 +- .../components/rss_feed_template/__init__.py | 89 +- homeassistant/components/rtorrent/sensor.py | 47 +- .../components/russound_rio/media_player.py | 90 +- .../components/russound_rnet/media_player.py | 87 +- homeassistant/components/ruter/sensor.py | 42 +- homeassistant/components/sabnzbd/__init__.py | 147 +- homeassistant/components/sabnzbd/sensor.py | 17 +- .../components/samsungtv/media_player.py | 149 +- .../components/satel_integra/__init__.py | 144 +- .../satel_integra/alarm_control_panel.py | 61 +- .../components/satel_integra/binary_sensor.py | 32 +- .../components/satel_integra/switch.py | 27 +- homeassistant/components/scene/__init__.py | 36 +- homeassistant/components/scrape/sensor.py | 70 +- homeassistant/components/script/__init__.py | 106 +- homeassistant/components/scsgate/__init__.py | 43 +- homeassistant/components/scsgate/cover.py | 20 +- homeassistant/components/scsgate/light.py | 27 +- homeassistant/components/scsgate/switch.py | 49 +- homeassistant/components/season/sensor.py | 46 +- homeassistant/components/sendgrid/notify.py | 53 +- homeassistant/components/sense/__init__.py | 45 +- .../components/sense/binary_sensor.py | 96 +- homeassistant/components/sense/sensor.py | 30 +- homeassistant/components/sensehat/light.py | 23 +- homeassistant/components/sensehat/sensor.py | 37 +- homeassistant/components/sensibo/climate.py | 230 +- homeassistant/components/sensor/__init__.py | 23 +- homeassistant/components/serial/sensor.py | 40 +- homeassistant/components/serial_pm/sensor.py | 37 +- homeassistant/components/sesame/lock.py | 32 +- .../seven_segments/image_processing.py | 114 +- .../components/seventeentrack/sensor.py | 195 +- .../components/shell_command/__init__.py | 37 +- homeassistant/components/shiftr/__init__.py | 42 +- homeassistant/components/shodan/sensor.py | 26 +- .../components/shopping_list/__init__.py | 199 +- homeassistant/components/sht31/sensor.py | 41 +- homeassistant/components/sigfox/sensor.py | 74 +- homeassistant/components/simplepush/notify.py | 40 +- .../components/simplisafe/__init__.py | 145 +- .../simplisafe/alarm_control_panel.py | 119 +- .../components/simplisafe/config_flow.py | 23 +- homeassistant/components/simplisafe/const.py | 6 +- homeassistant/components/simulated/sensor.py | 86 +- homeassistant/components/sisyphus/__init__.py | 47 +- homeassistant/components/sisyphus/light.py | 10 +- .../components/sisyphus/media_player.py | 71 +- .../components/sky_hub/device_tracker.py | 32 +- homeassistant/components/skybeacon/sensor.py | 66 +- homeassistant/components/skybell/__init__.py | 60 +- .../components/skybell/binary_sensor.py | 35 +- homeassistant/components/skybell/camera.py | 29 +- homeassistant/components/skybell/light.py | 10 +- homeassistant/components/skybell/sensor.py | 32 +- homeassistant/components/skybell/switch.py | 26 +- homeassistant/components/slack/notify.py | 87 +- homeassistant/components/sleepiq/__init__.py | 42 +- homeassistant/components/sleepiq/sensor.py | 2 +- homeassistant/components/sma/sensor.py | 87 +- homeassistant/components/smappee/__init__.py | 191 +- homeassistant/components/smappee/sensor.py | 204 +- homeassistant/components/smappee/switch.py | 35 +- homeassistant/components/smarthab/__init__.py | 29 +- homeassistant/components/smarthab/cover.py | 21 +- homeassistant/components/smarthab/light.py | 10 +- .../components/smartthings/__init__.py | 232 +- .../components/smartthings/binary_sensor.py | 33 +- .../components/smartthings/climate.py | 206 +- .../components/smartthings/config_flow.py | 80 +- homeassistant/components/smartthings/const.py | 58 +- homeassistant/components/smartthings/cover.py | 47 +- homeassistant/components/smartthings/fan.py | 30 +- homeassistant/components/smartthings/light.py | 69 +- homeassistant/components/smartthings/lock.py | 27 +- homeassistant/components/smartthings/scene.py | 12 +- .../components/smartthings/sensor.py | 301 ++- .../components/smartthings/smartapp.py | 257 +- .../components/smartthings/switch.py | 15 +- homeassistant/components/smarty/__init__.py | 42 +- .../components/smarty/binary_sensor.py | 45 +- homeassistant/components/smarty/fan.py | 38 +- homeassistant/components/smarty/sensor.py | 110 +- homeassistant/components/smhi/__init__.py | 16 +- homeassistant/components/smhi/config_flow.py | 44 +- homeassistant/components/smhi/const.py | 9 +- homeassistant/components/smhi/weather.py | 106 +- homeassistant/components/smtp/notify.py | 165 +- homeassistant/components/snapcast/__init__.py | 37 +- .../components/snapcast/media_player.py | 115 +- homeassistant/components/snips/__init__.py | 273 +- homeassistant/components/snmp/const.py | 64 +- .../components/snmp/device_tracker.py | 51 +- homeassistant/components/snmp/sensor.py | 120 +- homeassistant/components/snmp/switch.py | 165 +- homeassistant/components/sochain/sensor.py | 31 +- .../components/socialblade/sensor.py | 20 +- homeassistant/components/solaredge/sensor.py | 170 +- .../components/solaredge_local/sensor.py | 54 +- homeassistant/components/solax/sensor.py | 18 +- homeassistant/components/somfy/__init__.py | 76 +- homeassistant/components/somfy/config_flow.py | 63 +- homeassistant/components/somfy/const.py | 6 +- homeassistant/components/somfy/cover.py | 35 +- .../components/somfy_mylink/__init__.py | 52 +- .../components/somfy_mylink/cover.py | 38 +- homeassistant/components/sonarr/sensor.py | 230 +- .../components/songpal/media_player.py | 104 +- homeassistant/components/sonos/__init__.py | 202 +- homeassistant/components/sonos/config_flow.py | 3 +- .../components/sonos/media_player.py | 291 ++- .../components/sony_projector/switch.py | 18 +- .../components/soundtouch/media_player.py | 208 +- homeassistant/components/spaceapi/__init__.py | 186 +- homeassistant/components/spc/__init__.py | 58 +- .../components/spc/alarm_control_panel.py | 36 +- homeassistant/components/spc/binary_sensor.py | 27 +- .../components/speedtestdotnet/__init__.py | 47 +- .../components/speedtestdotnet/const.py | 10 +- .../components/speedtestdotnet/sensor.py | 64 +- homeassistant/components/spider/__init__.py | 36 +- homeassistant/components/spider/climate.py | 38 +- homeassistant/components/spider/switch.py | 6 +- homeassistant/components/splunk/__init__.py | 83 +- homeassistant/components/spotcrime/sensor.py | 102 +- .../components/spotify/media_player.py | 218 +- homeassistant/components/sql/sensor.py | 48 +- .../components/squeezebox/media_player.py | 292 ++- homeassistant/components/srp_energy/sensor.py | 58 +- homeassistant/components/ssdp/__init__.py | 68 +- .../components/starlingbank/sensor.py | 57 +- homeassistant/components/startca/sensor.py | 106 +- homeassistant/components/statistics/sensor.py | 164 +- homeassistant/components/statsd/__init__.py | 55 +- .../components/steam_online/sensor.py | 41 +- .../components/stiebel_eltron/__init__.py | 31 +- .../components/stiebel_eltron/climate.py | 64 +- homeassistant/components/stream/__init__.py | 86 +- homeassistant/components/stream/const.py | 22 +- homeassistant/components/stream/core.py | 23 +- homeassistant/components/stream/hls.py | 53 +- homeassistant/components/stream/recorder.py | 19 +- homeassistant/components/stream/worker.py | 25 +- .../components/streamlabswater/__init__.py | 61 +- .../streamlabswater/binary_sensor.py | 17 +- .../components/streamlabswater/sensor.py | 31 +- homeassistant/components/stride/notify.py | 75 +- homeassistant/components/suez_water/sensor.py | 74 +- homeassistant/components/sun/__init__.py | 135 +- .../components/supervisord/sensor.py | 27 +- homeassistant/components/supla/__init__.py | 91 +- homeassistant/components/supla/cover.py | 20 +- .../swiss_hydrological_data/sensor.py | 79 +- .../swiss_public_transport/sensor.py | 72 +- .../components/swisscom/device_tracker.py | 40 +- homeassistant/components/switch/__init__.py | 44 +- homeassistant/components/switch/light.py | 45 +- homeassistant/components/switchbot/switch.py | 8 +- .../components/switcher_kis/__init__.py | 108 +- .../components/switcher_kis/switch.py | 68 +- homeassistant/components/switchmate/switch.py | 19 +- homeassistant/components/syncthru/sensor.py | 105 +- homeassistant/components/synology/camera.py | 47 +- .../components/synology_chat/notify.py | 36 +- .../components/synology_srm/device_tracker.py | 57 +- .../components/synologydsm/sensor.py | 184 +- homeassistant/components/syslog/notify.py | 87 +- .../components/system_health/__init__.py | 50 +- .../components/system_log/__init__.py | 91 +- .../components/systemmonitor/sensor.py | 184 +- homeassistant/components/sytadin/sensor.py | 55 +- homeassistant/components/tado/__init__.py | 54 +- homeassistant/components/tado/climate.py | 206 +- .../components/tado/device_tracker.py | 57 +- homeassistant/components/tado/sensor.py | 178 +- homeassistant/components/tahoma/__init__.py | 98 +- .../components/tahoma/binary_sensor.py | 22 +- homeassistant/components/tahoma/cover.py | 155 +- homeassistant/components/tahoma/scene.py | 6 +- homeassistant/components/tahoma/sensor.py | 66 +- homeassistant/components/tahoma/switch.py | 38 +- .../components/tank_utility/sensor.py | 29 +- .../components/tapsaff/binary_sensor.py | 19 +- homeassistant/components/tautulli/sensor.py | 68 +- homeassistant/components/tcp/sensor.py | 73 +- homeassistant/components/ted5000/sensor.py | 34 +- homeassistant/components/teksavvy/sensor.py | 80 +- homeassistant/components/telegram/notify.py | 47 +- .../components/telegram_bot/__init__.py | 631 +++-- .../components/telegram_bot/broadcast.py | 5 +- .../components/telegram_bot/polling.py | 8 +- .../components/telegram_bot/webhooks.py | 54 +- .../components/tellduslive/__init__.py | 127 +- .../components/tellduslive/binary_sensor.py | 10 +- .../components/tellduslive/config_flow.py | 85 +- homeassistant/components/tellduslive/const.py | 34 +- homeassistant/components/tellduslive/cover.py | 7 +- homeassistant/components/tellduslive/entry.py | 35 +- homeassistant/components/tellduslive/light.py | 17 +- .../components/tellduslive/sensor.py | 86 +- .../components/tellduslive/switch.py | 7 +- .../components/tellstick/__init__.py | 144 +- homeassistant/components/tellstick/cover.py | 25 +- homeassistant/components/tellstick/light.py | 30 +- homeassistant/components/tellstick/sensor.py | 89 +- homeassistant/components/tellstick/switch.py | 27 +- homeassistant/components/telnet/switch.py | 82 +- homeassistant/components/temper/sensor.py | 44 +- .../components/template/binary_sensor.py | 168 +- homeassistant/components/template/cover.py | 259 +- homeassistant/components/template/fan.py | 211 +- homeassistant/components/template/light.py | 159 +- homeassistant/components/template/lock.py | 97 +- homeassistant/components/template/sensor.py | 149 +- homeassistant/components/template/switch.py | 136 +- homeassistant/components/template/vacuum.py | 204 +- .../components/tensorflow/image_processing.py | 269 +- homeassistant/components/tesla/__init__.py | 60 +- .../components/tesla/binary_sensor.py | 6 +- homeassistant/components/tesla/climate.py | 13 +- .../components/tesla/device_tracker.py | 20 +- homeassistant/components/tesla/lock.py | 9 +- homeassistant/components/tesla/sensor.py | 27 +- homeassistant/components/tesla/switch.py | 10 +- homeassistant/components/tfiac/climate.py | 70 +- .../components/thermoworks_smoke/sensor.py | 88 +- .../components/thethingsnetwork/__init__.py | 39 +- .../components/thethingsnetwork/sensor.py | 48 +- .../components/thingspeak/__init__.py | 49 +- .../components/thinkingcleaner/sensor.py | 65 +- .../components/thinkingcleaner/switch.py | 38 +- .../components/thomson/device_tracker.py | 76 +- .../components/threshold/binary_sensor.py | 97 +- homeassistant/components/tibber/__init__.py | 28 +- homeassistant/components/tibber/notify.py | 5 +- homeassistant/components/tibber/sensor.py | 73 +- homeassistant/components/tikteck/light.py | 42 +- .../components/tile/device_tracker.py | 111 +- homeassistant/components/time_date/sensor.py | 80 +- homeassistant/components/timer/__init__.py | 107 +- homeassistant/components/tod/binary_sensor.py | 86 +- homeassistant/components/todoist/calendar.py | 275 +- homeassistant/components/tof/sensor.py | 51 +- .../components/tomato/device_tracker.py | 82 +- homeassistant/components/toon/__init__.py | 110 +- .../components/toon/binary_sensor.py | 90 +- homeassistant/components/toon/climate.py | 21 +- homeassistant/components/toon/config_flow.py | 85 +- homeassistant/components/toon/const.py | 26 +- homeassistant/components/toon/sensor.py | 187 +- homeassistant/components/torque/sensor.py | 42 +- .../components/totalconnect/__init__.py | 23 +- .../totalconnect/alarm_control_panel.py | 39 +- homeassistant/components/touchline/climate.py | 11 +- homeassistant/components/tplink/__init__.py | 71 +- homeassistant/components/tplink/common.py | 63 +- .../components/tplink/config_flow.py | 10 +- .../components/tplink/device_tracker.py | 268 +- homeassistant/components/tplink/light.py | 107 +- homeassistant/components/tplink/switch.py | 74 +- .../components/tplink_lte/__init__.py | 67 +- homeassistant/components/tplink_lte/notify.py | 4 +- homeassistant/components/traccar/const.py | 54 +- .../components/traccar/device_tracker.py | 261 +- .../components/trackr/device_tracker.py | 34 +- homeassistant/components/tradfri/__init__.py | 95 +- .../components/tradfri/config_flow.py | 94 +- homeassistant/components/tradfri/const.py | 8 +- homeassistant/components/tradfri/light.py | 120 +- homeassistant/components/tradfri/sensor.py | 33 +- homeassistant/components/tradfri/switch.py | 33 +- .../components/trafikverket_train/sensor.py | 108 +- .../trafikverket_weatherstation/sensor.py | 137 +- .../components/transmission/__init__.py | 142 +- .../components/transmission/sensor.py | 57 +- .../components/transmission/switch.py | 7 +- .../components/transport_nsw/sensor.py | 94 +- homeassistant/components/travisci/sensor.py | 93 +- .../components/trend/binary_sensor.py | 111 +- homeassistant/components/tts/__init__.py | 235 +- homeassistant/components/tuya/__init__.py | 81 +- homeassistant/components/tuya/climate.py | 30 +- homeassistant/components/tuya/cover.py | 9 +- homeassistant/components/tuya/fan.py | 8 +- homeassistant/components/tuya/light.py | 30 +- homeassistant/components/tuya/scene.py | 4 +- homeassistant/components/tuya/switch.py | 2 +- .../components/twentemilieu/__init__.py | 16 +- .../components/twentemilieu/sensor.py | 14 +- homeassistant/components/twilio/__init__.py | 32 +- .../components/twilio/config_flow.py | 9 +- .../components/twilio_call/notify.py | 26 +- homeassistant/components/twilio_sms/notify.py | 39 +- homeassistant/components/twitch/sensor.py | 38 +- homeassistant/components/twitter/notify.py | 175 +- .../components/ubee/device_tracker.py | 34 +- .../components/ubus/device_tracker.py | 127 +- .../components/ue_smart_radio/media_player.py | 88 +- .../components/uk_transport/sensor.py | 187 +- homeassistant/components/unifi/__init__.py | 62 +- homeassistant/components/unifi/config_flow.py | 74 +- homeassistant/components/unifi/const.py | 18 +- homeassistant/components/unifi/controller.py | 66 +- .../components/unifi/device_tracker.py | 169 +- homeassistant/components/unifi/switch.py | 106 +- .../components/unifi_direct/device_tracker.py | 39 +- .../components/universal/media_player.py | 187 +- .../components/upc_connect/device_tracker.py | 36 +- homeassistant/components/upcloud/__init__.py | 86 +- .../components/upcloud/binary_sensor.py | 9 +- homeassistant/components/upcloud/switch.py | 6 +- homeassistant/components/updater/__init__.py | 91 +- homeassistant/components/upnp/__init__.py | 144 +- homeassistant/components/upnp/config_flow.py | 6 +- homeassistant/components/upnp/const.py | 14 +- homeassistant/components/upnp/device.py | 77 +- homeassistant/components/upnp/sensor.py | 99 +- homeassistant/components/ups/sensor.py | 64 +- homeassistant/components/uptime/sensor.py | 24 +- .../components/uptimerobot/binary_sensor.py | 36 +- homeassistant/components/uscis/sensor.py | 22 +- .../usgs_earthquakes_feed/geo_location.py | 180 +- homeassistant/components/usps/__init__.py | 51 +- homeassistant/components/usps/camera.py | 6 +- homeassistant/components/usps/sensor.py | 25 +- .../components/utility_meter/__init__.py | 127 +- .../components/utility_meter/const.py | 44 +- .../components/utility_meter/sensor.py | 170 +- homeassistant/components/uvc/camera.py | 76 +- homeassistant/components/vacuum/__init__.py | 150 +- homeassistant/components/vallox/__init__.py | 152 +- homeassistant/components/vallox/fan.py | 65 +- homeassistant/components/vallox/sensor.py | 74 +- homeassistant/components/vasttrafik/sensor.py | 105 +- homeassistant/components/velbus/__init__.py | 82 +- .../components/velbus/binary_sensor.py | 10 +- homeassistant/components/velbus/climate.py | 18 +- .../components/velbus/config_flow.py | 42 +- homeassistant/components/velbus/cover.py | 22 +- homeassistant/components/velbus/sensor.py | 10 +- homeassistant/components/velbus/switch.py | 14 +- homeassistant/components/velux/__init__.py | 27 +- homeassistant/components/velux/cover.py | 34 +- homeassistant/components/velux/scene.py | 3 +- homeassistant/components/venstar/climate.py | 101 +- homeassistant/components/vera/__init__.py | 84 +- .../components/vera/binary_sensor.py | 11 +- homeassistant/components/vera/climate.py | 37 +- homeassistant/components/vera/cover.py | 11 +- homeassistant/components/vera/light.py | 17 +- homeassistant/components/vera/lock.py | 13 +- homeassistant/components/vera/scene.py | 13 +- homeassistant/components/vera/sensor.py | 27 +- homeassistant/components/vera/switch.py | 8 +- homeassistant/components/verisure/__init__.py | 124 +- .../verisure/alarm_control_panel.py | 36 +- .../components/verisure/binary_sensor.py | 35 +- homeassistant/components/verisure/camera.py | 34 +- homeassistant/components/verisure/lock.py | 53 +- homeassistant/components/verisure/sensor.py | 105 +- homeassistant/components/verisure/switch.py | 30 +- homeassistant/components/version/sensor.py | 73 +- homeassistant/components/vesync/__init__.py | 52 +- homeassistant/components/vesync/common.py | 6 +- .../components/vesync/config_flow.py | 11 +- homeassistant/components/vesync/const.py | 12 +- homeassistant/components/vesync/switch.py | 47 +- .../components/viaggiatreno/sensor.py | 94 +- .../components/vizio/media_player.py | 103 +- homeassistant/components/vlc/media_player.py | 59 +- .../components/vlc_telnet/media_player.py | 96 +- homeassistant/components/voicerss/tts.py | 172 +- .../components/volkszaehler/sensor.py | 50 +- .../components/volumio/media_player.py | 151 +- .../components/volvooncall/__init__.py | 204 +- .../components/volvooncall/binary_sensor.py | 6 +- .../components/volvooncall/device_tracker.py | 14 +- homeassistant/components/volvooncall/lock.py | 3 +- .../components/volvooncall/sensor.py | 3 +- .../components/volvooncall/switch.py | 3 +- homeassistant/components/vultr/__init__.py | 59 +- .../components/vultr/binary_sensor.py | 69 +- homeassistant/components/vultr/sensor.py | 37 +- homeassistant/components/vultr/switch.py | 68 +- homeassistant/components/w800rf32/__init__.py | 26 +- .../components/w800rf32/binary_sensor.py | 66 +- .../components/wake_on_lan/__init__.py | 38 +- .../components/wake_on_lan/switch.py | 59 +- homeassistant/components/waqi/sensor.py | 109 +- .../components/water_heater/__init__.py | 172 +- .../components/waterfurnace/__init__.py | 38 +- .../components/waterfurnace/sensor.py | 39 +- .../components/watson_iot/__init__.py | 141 +- homeassistant/components/watson_tts/tts.py | 88 +- .../components/waze_travel_time/sensor.py | 148 +- homeassistant/components/weather/__init__.py | 68 +- homeassistant/components/webhook/__init__.py | 43 +- homeassistant/components/weblink/__init__.py | 41 +- .../components/webostv/media_player.py | 231 +- homeassistant/components/webostv/notify.py | 29 +- .../components/websocket_api/__init__.py | 5 +- .../components/websocket_api/auth.py | 70 +- .../components/websocket_api/commands.py | 152 +- .../components/websocket_api/connection.py | 48 +- .../components/websocket_api/const.py | 28 +- .../components/websocket_api/decorators.py | 47 +- .../components/websocket_api/http.py | 64 +- .../components/websocket_api/messages.py | 36 +- .../components/websocket_api/permissions.py | 6 +- .../components/websocket_api/sensor.py | 15 +- homeassistant/components/wemo/__init__.py | 113 +- .../components/wemo/binary_sensor.py | 22 +- homeassistant/components/wemo/config_flow.py | 3 +- homeassistant/components/wemo/fan.py | 103 +- homeassistant/components/wemo/light.py | 54 +- homeassistant/components/wemo/switch.py | 86 +- homeassistant/components/whois/sensor.py | 59 +- homeassistant/components/wink/__init__.py | 565 ++-- .../components/wink/alarm_control_panel.py | 15 +- .../components/wink/binary_sensor.py | 70 +- homeassistant/components/wink/climate.py | 99 +- homeassistant/components/wink/cover.py | 12 +- homeassistant/components/wink/fan.py | 17 +- homeassistant/components/wink/light.py | 34 +- homeassistant/components/wink/lock.py | 143 +- homeassistant/components/wink/scene.py | 6 +- homeassistant/components/wink/sensor.py | 24 +- homeassistant/components/wink/switch.py | 10 +- homeassistant/components/wink/water_heater.py | 52 +- .../components/wirelesstag/__init__.py | 132 +- .../components/wirelesstag/binary_sensor.py | 71 +- .../components/wirelesstag/sensor.py | 46 +- .../components/wirelesstag/switch.py | 36 +- .../components/workday/binary_sensor.py | 184 +- homeassistant/components/worldclock/sensor.py | 24 +- .../components/worldtidesinfo/sensor.py | 72 +- .../components/worxlandroid/sensor.py | 102 +- homeassistant/components/wsdot/sensor.py | 68 +- .../components/wunderground/sensor.py | 1236 +++++---- .../components/wunderlist/__init__.py | 45 +- homeassistant/components/wwlln/__init__.py | 61 +- homeassistant/components/wwlln/config_flow.py | 40 +- homeassistant/components/wwlln/const.py | 6 +- .../components/wwlln/geo_location.py | 92 +- homeassistant/components/x10/light.py | 44 +- homeassistant/components/xbox_live/sensor.py | 64 +- homeassistant/components/xeoma/camera.py | 80 +- .../components/xfinity/device_tracker.py | 21 +- homeassistant/components/xiaomi/camera.py | 91 +- .../components/xiaomi/device_tracker.py | 61 +- .../components/xiaomi_aqara/__init__.py | 206 +- .../components/xiaomi_aqara/binary_sensor.py | 327 +-- .../components/xiaomi_aqara/cover.py | 29 +- .../components/xiaomi_aqara/light.py | 30 +- homeassistant/components/xiaomi_aqara/lock.py | 30 +- .../components/xiaomi_aqara/sensor.py | 93 +- .../components/xiaomi_aqara/switch.py | 113 +- .../components/xiaomi_miio/device_tracker.py | 33 +- homeassistant/components/xiaomi_miio/fan.py | 834 +++--- homeassistant/components/xiaomi_miio/light.py | 543 ++-- .../components/xiaomi_miio/remote.py | 150 +- .../components/xiaomi_miio/sensor.py | 79 +- .../components/xiaomi_miio/switch.py | 271 +- .../components/xiaomi_miio/vacuum.py | 394 +-- .../components/xiaomi_tv/media_player.py | 24 +- homeassistant/components/xmpp/notify.py | 233 +- homeassistant/components/xs1/__init__.py | 54 +- homeassistant/components/xs1/climate.py | 7 +- homeassistant/components/xs1/sensor.py | 6 +- homeassistant/components/xs1/switch.py | 5 +- .../yale_smart_alarm/alarm_control_panel.py | 48 +- .../components/yamaha/media_player.py | 162 +- .../yamaha_musiccast/media_player.py | 87 +- homeassistant/components/yandextts/tts.py | 113 +- homeassistant/components/yeelight/__init__.py | 165 +- .../components/yeelight/binary_sensor.py | 4 +- homeassistant/components/yeelight/light.py | 248 +- .../components/yeelightsunflower/light.py | 17 +- homeassistant/components/yessssms/notify.py | 40 +- homeassistant/components/yi/camera.py | 77 +- homeassistant/components/yr/sensor.py | 164 +- homeassistant/components/yweather/sensor.py | 111 +- homeassistant/components/yweather/weather.py | 87 +- homeassistant/components/zabbix/__init__.py | 40 +- homeassistant/components/zabbix/sensor.py | 59 +- homeassistant/components/zamg/sensor.py | 186 +- homeassistant/components/zamg/weather.py | 56 +- homeassistant/components/zengge/light.py | 38 +- homeassistant/components/zeroconf/__init__.py | 55 +- homeassistant/components/zestimate/sensor.py | 85 +- homeassistant/components/zha/__init__.py | 110 +- homeassistant/components/zha/api.py | 541 ++-- homeassistant/components/zha/binary_sensor.py | 83 +- homeassistant/components/zha/config_flow.py | 34 +- .../components/zha/core/channels/__init__.py | 8 +- .../components/zha/core/channels/closures.py | 14 +- .../components/zha/core/channels/general.py | 82 +- .../zha/core/channels/homeautomation.py | 7 +- .../components/zha/core/channels/hvac.py | 17 +- .../components/zha/core/channels/lighting.py | 13 +- .../components/zha/core/channels/registry.py | 58 +- .../components/zha/core/channels/security.py | 24 +- homeassistant/components/zha/core/const.py | 251 +- homeassistant/components/zha/core/device.py | 4 +- .../components/zha/core/discovery.py | 242 +- homeassistant/components/zha/core/gateway.py | 171 +- homeassistant/components/zha/core/helpers.py | 125 +- homeassistant/components/zha/core/patches.py | 26 +- .../components/zha/core/registries.py | 387 ++- homeassistant/components/zha/core/store.py | 33 +- .../components/zha/device_tracker.py | 45 +- homeassistant/components/zha/entity.py | 4 +- homeassistant/components/zha/fan.py | 52 +- homeassistant/components/zha/light.py | 123 +- homeassistant/components/zha/lock.py | 47 +- homeassistant/components/zha/sensor.py | 145 +- homeassistant/components/zha/switch.py | 35 +- .../components/zhong_hong/climate.py | 54 +- homeassistant/components/zigbee/__init__.py | 134 +- .../components/zigbee/binary_sensor.py | 13 +- homeassistant/components/zigbee/light.py | 12 +- homeassistant/components/zigbee/sensor.py | 25 +- homeassistant/components/zigbee/switch.py | 10 +- .../ziggo_mediabox_xl/media_player.py | 84 +- homeassistant/components/zone/__init__.py | 98 +- homeassistant/components/zone/config_flow.py | 38 +- homeassistant/components/zone/const.py | 10 +- homeassistant/components/zone/zone.py | 9 +- .../components/zoneminder/__init__.py | 73 +- .../components/zoneminder/binary_sensor.py | 5 +- homeassistant/components/zoneminder/camera.py | 12 +- homeassistant/components/zoneminder/sensor.py | 44 +- homeassistant/components/zoneminder/switch.py | 15 +- homeassistant/components/zwave/__init__.py | 910 ++++--- .../components/zwave/binary_sensor.py | 33 +- homeassistant/components/zwave/climate.py | 105 +- homeassistant/components/zwave/config_flow.py | 46 +- homeassistant/components/zwave/cover.py | 61 +- .../components/zwave/discovery_schemas.py | 533 ++-- homeassistant/components/zwave/fan.py | 33 +- homeassistant/components/zwave/light.py | 104 +- homeassistant/components/zwave/lock.py | 270 +- homeassistant/components/zwave/node_entity.py | 167 +- homeassistant/components/zwave/sensor.py | 29 +- homeassistant/components/zwave/switch.py | 19 +- homeassistant/components/zwave/util.py | 93 +- .../components/zwave/websocket_api.py | 12 +- homeassistant/components/zwave/workaround.py | 105 +- homeassistant/config.py | 533 ++-- homeassistant/config_entries.py | 323 ++- homeassistant/const.py | 668 ++--- homeassistant/core.py | 690 ++--- homeassistant/data_entry_flow.py | 213 +- homeassistant/exceptions.py | 21 +- homeassistant/helpers/__init__.py | 5 +- homeassistant/helpers/aiohttp_client.py | 83 +- homeassistant/helpers/area_registry.py | 50 +- homeassistant/helpers/check_config.py | 101 +- homeassistant/helpers/condition.py | 292 ++- homeassistant/helpers/config_entry_flow.py | 94 +- homeassistant/helpers/config_validation.py | 531 ++-- homeassistant/helpers/data_entry_flow.py | 44 +- homeassistant/helpers/deprecation.py | 26 +- homeassistant/helpers/device_registry.py | 229 +- homeassistant/helpers/discovery.py | 53 +- homeassistant/helpers/dispatcher.py | 29 +- homeassistant/helpers/entity.py | 125 +- homeassistant/helpers/entity_component.py | 129 +- homeassistant/helpers/entity_platform.py | 228 +- homeassistant/helpers/entity_registry.py | 222 +- homeassistant/helpers/entity_values.py | 9 +- homeassistant/helpers/entityfilter.py | 55 +- homeassistant/helpers/event.py | 103 +- homeassistant/helpers/icon.py | 18 +- homeassistant/helpers/intent.py | 141 +- homeassistant/helpers/json.py | 2 +- homeassistant/helpers/location.py | 19 +- homeassistant/helpers/logging.py | 6 +- homeassistant/helpers/restore_state.py | 85 +- homeassistant/helpers/script.py | 161 +- homeassistant/helpers/service.py | 174 +- homeassistant/helpers/signal.py | 12 +- homeassistant/helpers/state.py | 194 +- homeassistant/helpers/storage.py | 81 +- homeassistant/helpers/sun.py | 55 +- homeassistant/helpers/system_info.py | 34 +- homeassistant/helpers/temperature.py | 11 +- homeassistant/helpers/template.py | 299 ++- homeassistant/helpers/translation.py | 63 +- homeassistant/loader.py | 218 +- homeassistant/monkey_patch.py | 3 +- homeassistant/requirements.py | 38 +- homeassistant/scripts/__init__.py | 31 +- homeassistant/scripts/auth.py | 43 +- homeassistant/scripts/benchmark/__init__.py | 71 +- homeassistant/scripts/check_config.py | 204 +- homeassistant/scripts/credstash.py | 59 +- homeassistant/scripts/ensure_config.py | 23 +- homeassistant/scripts/keyring.py | 54 +- homeassistant/scripts/macos/__init__.py | 33 +- homeassistant/setup.py | 154 +- homeassistant/util/aiohttp.py | 20 +- homeassistant/util/async_.py | 67 +- homeassistant/util/color.py | 404 +-- homeassistant/util/decorator.py | 3 +- homeassistant/util/distance.py | 15 +- homeassistant/util/dt.py | 85 +- homeassistant/util/json.py | 34 +- homeassistant/util/location.py | 127 +- homeassistant/util/logging.py | 41 +- homeassistant/util/network.py | 14 +- homeassistant/util/package.py | 58 +- homeassistant/util/pressure.py | 16 +- homeassistant/util/ruamel_yaml.py | 50 +- homeassistant/util/ssl.py | 18 +- homeassistant/util/temperature.py | 17 +- homeassistant/util/unit_system.py | 130 +- homeassistant/util/volume.py | 21 +- homeassistant/util/yaml/__init__.py | 18 +- homeassistant/util/yaml/const.py | 4 +- homeassistant/util/yaml/dumper.py | 30 +- homeassistant/util/yaml/loader.py | 165 +- script/gen_requirements_all.py | 451 ++-- script/hassfest/__main__.py | 43 +- script/hassfest/codeowners.py | 33 +- script/hassfest/config_flow.py | 20 +- script/hassfest/dependencies.py | 33 +- script/hassfest/manifest.py | 50 +- script/hassfest/manifest_helper.py | 7 +- script/hassfest/model.py | 24 +- script/hassfest/services.py | 52 +- script/hassfest/ssdp.py | 42 +- script/hassfest/zeroconf.py | 76 +- script/inspect_schemas.py | 39 +- script/lazytox.py | 99 +- script/translations_download_split.py | 53 +- script/translations_upload_merge.py | 41 +- script/version_bump.py | 136 +- .../auth/mfa_modules/test_insecure_example.py | 179 +- tests/auth/mfa_modules/test_notify.py | 444 ++-- tests/auth/mfa_modules/test_totp.py | 165 +- tests/auth/permissions/test_entities.py | 233 +- tests/auth/permissions/test_merge.py | 34 +- .../auth/permissions/test_system_policies.py | 23 +- tests/auth/permissions/test_util.py | 17 +- tests/auth/providers/test_command_line.py | 79 +- tests/auth/providers/test_homeassistant.py | 244 +- tests/auth/providers/test_insecure_example.py | 68 +- .../providers/test_legacy_api_password.py | 37 +- tests/auth/providers/test_trusted_networks.py | 292 ++- tests/auth/test_auth_store.py | 87 +- tests/auth/test_init.py | 988 +++---- tests/auth/test_models.py | 35 +- tests/common.py | 374 +-- tests/components/adguard/test_config_flow.py | 218 +- .../air_quality/test_air_quality.py | 32 +- .../components/alarm_control_panel/common.py | 30 +- tests/components/alert/test_init.py | 76 +- tests/components/alexa/__init__.py | 136 +- tests/components/alexa/test_auth.py | 48 +- tests/components/alexa/test_capabilities.py | 396 +-- tests/components/alexa/test_entities.py | 14 +- .../components/alexa/test_flash_briefings.py | 88 +- tests/components/alexa/test_intent.py | 424 ++- tests/components/alexa/test_smart_home.py | 1313 +++++----- .../components/alexa/test_smart_home_http.py | 17 +- tests/components/alexa/test_state_report.py | 63 +- .../ambiclimate/test_config_flow.py | 111 +- .../ambient_station/test_config_flow.py | 82 +- tests/components/api/test_init.py | 236 +- tests/components/apns/test_notify.py | 301 +-- tests/components/aprs/test_device_tracker.py | 124 +- .../components/arcam_fmj/test_config_flow.py | 31 +- tests/components/arlo/test_sensor.py | 125 +- .../components/asuswrt/test_device_tracker.py | 59 +- tests/components/aurora/test_binary_sensor.py | 22 +- tests/components/auth/__init__.py | 38 +- tests/components/auth/test_indieauth.py | 131 +- tests/components/auth/test_init.py | 372 +-- tests/components/auth/test_init_link_user.py | 132 +- tests/components/auth/test_login_flow.py | 97 +- tests/components/auth/test_mfa_setup_flow.py | 139 +- .../automatic/test_device_tracker.py | 113 +- tests/components/automation/common.py | 8 +- tests/components/automation/test_event.py | 167 +- .../automation/test_geo_location.py | 374 +-- .../automation/test_homeassistant.py | 57 +- tests/components/automation/test_init.py | 1029 ++++---- tests/components/automation/test_litejet.py | 194 +- tests/components/automation/test_mqtt.py | 142 +- .../automation/test_numeric_state.py | 1365 +++++----- tests/components/automation/test_state.py | 1004 +++---- tests/components/automation/test_sun.py | 532 ++-- tests/components/automation/test_template.py | 807 +++--- tests/components/automation/test_time.py | 206 +- .../automation/test_time_pattern.py | 226 +- tests/components/automation/test_webhook.py | 101 +- tests/components/automation/test_zone.py | 263 +- tests/components/awair/test_sensor.py | 33 +- tests/components/aws/test_init.py | 273 +- tests/components/axis/test_binary_sensor.py | 75 +- tests/components/axis/test_camera.py | 47 +- tests/components/axis/test_config_flow.py | 303 ++- tests/components/axis/test_device.py | 111 +- tests/components/axis/test_init.py | 71 +- tests/components/axis/test_switch.py | 104 +- .../components/bayesian/test_binary_sensor.py | 294 +-- .../binary_sensor/test_binary_sensor.py | 20 +- .../components/blackbird/test_media_player.py | 235 +- tests/components/bom/test_sensor.py | 55 +- tests/components/broadlink/test_init.py | 56 +- tests/components/buienradar/test_camera.py | 132 +- tests/components/caldav/test_calendar.py | 208 +- tests/components/calendar/test_init.py | 25 +- tests/components/camera/common.py | 23 +- tests/components/camera/test_init.py | 360 +-- tests/components/canary/test_init.py | 33 +- tests/components/canary/test_sensor.py | 25 +- tests/components/cast/test_init.py | 49 +- tests/components/cast/test_media_player.py | 298 ++- tests/components/climate/common.py | 109 +- tests/components/climate/test_init.py | 17 +- .../climate/test_reproduce_state.py | 78 +- tests/components/cloud/__init__.py | 9 +- tests/components/cloud/conftest.py | 15 +- tests/components/cloud/test_alexa_config.py | 82 +- tests/components/cloud/test_binary_sensor.py | 21 +- tests/components/cloud/test_client.py | 208 +- tests/components/cloud/test_http_api.py | 906 +++---- tests/components/cloud/test_init.py | 183 +- tests/components/cloud/test_utils.py | 40 +- tests/components/coinmarketcap/test_sensor.py | 28 +- .../command_line/test_binary_sensor.py | 34 +- tests/components/command_line/test_cover.py | 76 +- tests/components/command_line/test_notify.py | 74 +- tests/components/command_line/test_sensor.py | 149 +- tests/components/command_line/test_switch.py | 148 +- tests/components/config/test_area_registry.py | 129 +- tests/components/config/test_auth.py | 259 +- .../test_auth_provider_homeassistant.py | 315 ++- tests/components/config/test_automation.py | 117 +- .../components/config/test_config_entries.py | 492 ++-- tests/components/config/test_core.py | 160 +- tests/components/config/test_customize.py | 82 +- .../components/config/test_device_registry.py | 99 +- .../components/config/test_entity_registry.py | 364 +-- tests/components/config/test_group.py | 82 +- tests/components/config/test_init.py | 27 +- tests/components/config/test_script.py | 22 +- tests/components/config/test_zwave.py | 339 +-- tests/components/configurator/test_init.py | 29 +- tests/components/conftest.py | 32 +- tests/components/conversation/test_init.py | 286 +- tests/components/counter/common.py | 21 +- tests/components/counter/test_init.py | 370 ++- tests/components/cover/test_init.py | 31 +- tests/components/daikin/test_config_flow.py | 58 +- tests/components/darksky/test_sensor.py | 176 +- tests/components/darksky/test_weather.py | 27 +- tests/components/datadog/test_init.py | 176 +- tests/components/deconz/test_binary_sensor.py | 54 +- tests/components/deconz/test_climate.py | 115 +- tests/components/deconz/test_config_flow.py | 338 +-- tests/components/deconz/test_cover.py | 74 +- tests/components/deconz/test_gateway.py | 110 +- tests/components/deconz/test_init.py | 294 ++- tests/components/deconz/test_light.py | 146 +- tests/components/deconz/test_scene.py | 42 +- tests/components/deconz/test_sensor.py | 65 +- tests/components/deconz/test_switch.py | 80 +- tests/components/default_config/test_init.py | 13 +- tests/components/demo/test_camera.py | 45 +- tests/components/demo/test_climate.py | 62 +- tests/components/demo/test_cover.py | 114 +- tests/components/demo/test_fan.py | 28 +- tests/components/demo/test_geo_location.py | 57 +- tests/components/demo/test_init.py | 12 +- tests/components/demo/test_light.py | 44 +- tests/components/demo/test_lock.py | 16 +- tests/components/demo/test_media_player.py | 152 +- tests/components/demo/test_notify.py | 119 +- tests/components/demo/test_remote.py | 16 +- tests/components/demo/test_vacuum.py | 122 +- tests/components/demo/test_water_heater.py | 53 +- .../components/device_automation/test_init.py | 56 +- .../device_sun_light_trigger/test_init.py | 91 +- tests/components/device_tracker/common.py | 49 +- .../device_tracker/test_entities.py | 22 +- tests/components/device_tracker/test_init.py | 549 ++-- tests/components/dialogflow/test_init.py | 329 +-- tests/components/directv/test_media_player.py | 354 +-- tests/components/discovery/test_init.py | 97 +- tests/components/dsmr/test_sensor.py | 121 +- .../dte_energy_bridge/test_sensor.py | 58 +- tests/components/duckdns/test_init.py | 83 +- tests/components/dyson/test_air_quality.py | 95 +- tests/components/dyson/test_climate.py | 65 +- tests/components/dyson/test_fan.py | 493 ++-- tests/components/dyson/test_init.py | 237 +- tests/components/dyson/test_sensor.py | 65 +- tests/components/dyson/test_vacuum.py | 7 +- tests/components/ecobee/test_climate.py | 273 +- .../ee_brightbox/test_device_tracker.py | 109 +- tests/components/efergy/test_sensor.py | 89 +- tests/components/emulated_hue/test_hue_api.py | 563 ++-- tests/components/emulated_hue/test_init.py | 117 +- tests/components/emulated_hue/test_upnp.py | 47 +- .../components/emulated_roku/test_binding.py | 63 +- .../emulated_roku/test_config_flow.py | 32 +- tests/components/emulated_roku/test_init.py | 101 +- tests/components/esphome/test_config_flow.py | 234 +- tests/components/everlights/test_light.py | 4 +- tests/components/facebook/test_notify.py | 39 +- .../facebox/test_image_processing.py | 231 +- tests/components/fail2ban/test_sensor.py | 277 +- tests/components/fan/common.py | 69 +- tests/components/fan/test_init.py | 6 +- tests/components/feedreader/test_init.py | 118 +- tests/components/ffmpeg/test_init.py | 42 +- tests/components/ffmpeg/test_sensor.py | 95 +- tests/components/fido/test_sensor.py | 47 +- tests/components/file/test_notify.py | 70 +- tests/components/file/test_sensor.py | 69 +- tests/components/filesize/test_sensor.py | 12 +- tests/components/filter/test_sensor.py | 139 +- tests/components/flux/test_switch.py | 874 ++++--- tests/components/folder/test_sensor.py | 28 +- tests/components/folder_watcher/test_init.py | 42 +- tests/components/foobot/test_sensor.py | 68 +- tests/components/freedns/test_init.py | 61 +- tests/components/fritzbox/test_climate.py | 43 +- tests/components/frontend/test_init.py | 268 +- tests/components/frontend/test_storage.py | 179 +- tests/components/generic/test_camera.py | 171 +- .../generic_thermostat/test_climate.py | 737 +++--- .../geo_json_events/test_geo_location.py | 145 +- .../components/geo_rss_events/test_sensor.py | 97 +- tests/components/geofency/test_init.py | 275 +- tests/components/google/conftest.py | 29 +- tests/components/google/test_calendar.py | 281 +- tests/components/google/test_init.py | 65 +- tests/components/google_assistant/__init__.py | 442 ++-- .../google_assistant/test_google_assistant.py | 458 ++-- .../components/google_assistant/test_init.py | 19 +- .../google_assistant/test_smart_home.py | 935 ++++--- .../components/google_assistant/test_trait.py | 1414 +++++----- tests/components/google_domains/test_init.py | 75 +- tests/components/google_pubsub/test_pubsub.py | 7 +- tests/components/google_translate/test_tts.py | 186 +- tests/components/google_wifi/test_sensor.py | 151 +- tests/components/gpslogger/test_init.py | 184 +- tests/components/graphite/test_init.py | 190 +- tests/components/group/common.py | 61 +- tests/components/group/test_cover.py | 224 +- tests/components/group/test_init.py | 445 ++-- tests/components/group/test_light.py | 551 ++-- tests/components/group/test_notify.py | 68 +- .../components/group/test_reproduce_state.py | 47 +- tests/components/hangouts/test_config_flow.py | 80 +- tests/components/hassio/__init__.py | 4 +- tests/components/hassio/conftest.py | 40 +- tests/components/hassio/test_addon_panel.py | 147 +- tests/components/hassio/test_auth.py | 86 +- tests/components/hassio/test_discovery.py | 208 +- tests/components/hassio/test_handler.py | 80 +- tests/components/hassio/test_http.py | 80 +- tests/components/hassio/test_ingress.py | 223 +- tests/components/hassio/test_init.py | 314 +-- tests/components/hddtemp/test_sensor.py | 230 +- tests/components/heos/conftest.py | 61 +- tests/components/heos/test_config_flow.py | 53 +- tests/components/heos/test_init.py | 63 +- tests/components/heos/test_media_player.py | 541 ++-- tests/components/heos/test_services.py | 31 +- tests/components/history/test_init.py | 488 ++-- tests/components/history_graph/test_init.py | 24 +- tests/components/history_stats/test_sensor.py | 174 +- tests/components/homeassistant/test_init.py | 252 +- tests/components/homekit/common.py | 6 +- tests/components/homekit/conftest.py | 17 +- tests/components/homekit/test_accessories.py | 204 +- .../homekit/test_get_accessories.py | 241 +- tests/components/homekit/test_homekit.py | 204 +- tests/components/homekit/test_type_covers.py | 81 +- tests/components/homekit/test_type_fans.py | 99 +- tests/components/homekit/test_type_lights.py | 90 +- tests/components/homekit/test_type_locks.py | 25 +- .../homekit/test_type_media_players.py | 156 +- .../homekit/test_type_security_systems.py | 39 +- tests/components/homekit/test_type_sensors.py | 100 +- .../components/homekit/test_type_switches.py | 82 +- .../homekit/test_type_thermostats.py | 436 ++-- tests/components/homekit/test_util.py | 253 +- tests/components/homekit_controller/common.py | 153 +- .../components/homekit_controller/conftest.py | 2 +- .../specific_devices/test_aqara_gateway.py | 37 +- .../specific_devices/test_ecobee3.py | 162 +- .../specific_devices/test_koogeek_ls1.py | 49 +- .../specific_devices/test_lennox_e30.py | 27 +- .../test_alarm_control_panel.py | 63 +- .../homekit_controller/test_binary_sensor.py | 23 +- .../homekit_controller/test_climate.py | 153 +- .../homekit_controller/test_config_flow.py | 861 +++--- .../homekit_controller/test_cover.py | 131 +- .../homekit_controller/test_light.py | 85 +- .../homekit_controller/test_lock.py | 33 +- .../homekit_controller/test_sensor.py | 33 +- .../homekit_controller/test_storage.py | 56 +- .../homekit_controller/test_switch.py | 39 +- tests/components/homematic/test_notify.py | 83 +- .../homematicip_cloud/test_config_flow.py | 139 +- .../components/homematicip_cloud/test_hap.py | 77 +- .../components/homematicip_cloud/test_init.py | 137 +- tests/components/honeywell/test_climate.py | 300 +-- tests/components/html5/test_notify.py | 430 ++- tests/components/http/__init__.py | 2 +- tests/components/http/test_auth.py | 223 +- tests/components/http/test_ban.py | 82 +- tests/components/http/test_cors.py | 133 +- tests/components/http/test_data_validator.py | 50 +- tests/components/http/test_init.py | 207 +- tests/components/http/test_real_ip.py | 60 +- tests/components/http/test_view.py | 22 +- tests/components/huawei_lte/test_init.py | 7 +- tests/components/hue/test_bridge.py | 37 +- tests/components/hue/test_config_flow.py | 349 ++- tests/components/hue/test_init.py | 171 +- tests/components/hue/test_light.py | 452 ++-- tests/components/hue/test_sensor_base.py | 252 +- tests/components/hydroquebec/test_sensor.py | 43 +- tests/components/ifttt/test_init.py | 25 +- .../ign_sismologia/test_geo_location.py | 134 +- .../components/image_processing/test_init.py | 251 +- .../imap_email_content/test_sensor.py | 165 +- tests/components/influxdb/test_init.py | 845 +++--- tests/components/input_boolean/test_init.py | 141 +- tests/components/input_datetime/test_init.py | 314 ++- tests/components/input_number/test_init.py | 218 +- tests/components/input_select/test_init.py | 329 ++- tests/components/input_text/test_init.py | 204 +- tests/components/integration/test_sensor.py | 161 +- tests/components/intent_script/test_init.py | 46 +- tests/components/ios/test_init.py | 38 +- tests/components/ipma/test_config_flow.py | 51 +- tests/components/ipma/test_weather.py | 117 +- tests/components/iqvia/test_config_flow.py | 42 +- .../islamic_prayer_times/test_sensor.py | 145 +- .../components/jewish_calendar/test_sensor.py | 747 ++++-- tests/components/kira/test_init.py | 56 +- tests/components/kira/test_remote.py | 18 +- tests/components/kira/test_sensor.py | 20 +- tests/components/light/common.py | 116 +- .../light/test_device_automation.py | 148 +- tests/components/light/test_init.py | 373 ++- tests/components/litejet/test_init.py | 24 +- tests/components/litejet/test_light.py | 49 +- tests/components/litejet/test_scene.py | 20 +- tests/components/litejet/test_switch.py | 34 +- tests/components/local_file/test_camera.py | 162 +- tests/components/locative/test_init.py | 222 +- tests/components/lock/common.py | 7 +- tests/components/logbook/test_init.py | 1472 ++++++----- tests/components/logentries/test_init.py | 72 +- tests/components/logger/test_init.py | 63 +- .../logi_circle/test_config_flow.py | 155 +- tests/components/london_air/test_sensor.py | 24 +- tests/components/lovelace/test_init.py | 191 +- .../components/luftdaten/test_config_flow.py | 65 +- tests/components/luftdaten/test_init.py | 17 +- tests/components/mailbox/test_init.py | 26 +- tests/components/mailgun/test_init.py | 185 +- .../manual/test_alarm_control_panel.py | 1557 +++++------ .../manual_mqtt/test_alarm_control_panel.py | 1827 +++++++------ tests/components/marytts/test_tts.py | 71 +- tests/components/media_player/common.py | 40 +- .../media_player/test_async_helpers.py | 72 +- tests/components/media_player/test_init.py | 85 +- .../media_player/test_reproduce_state.py | 166 +- tests/components/melissa/test_climate.py | 133 +- tests/components/melissa/test_init.py | 17 +- .../components/meraki/test_device_tracker.py | 3 +- tests/components/met/conftest.py | 14 +- tests/components/met/test_config_flow.py | 75 +- tests/components/met/test_weather.py | 35 +- tests/components/mfi/test_sensor.py | 117 +- tests/components/mfi/test_switch.py | 58 +- tests/components/mhz19/test_sensor.py | 103 +- tests/components/microsoft_face/test_init.py | 200 +- .../test_image_processing.py | 124 +- .../test_image_processing.py | 127 +- tests/components/min_max/test_sensor.py | 231 +- tests/components/mobile_app/conftest.py | 6 +- tests/components/mobile_app/const.py | 79 +- .../mobile_app/test_device_tracker.py | 154 +- tests/components/mobile_app/test_entity.py | 136 +- tests/components/mobile_app/test_http_api.py | 50 +- tests/components/mobile_app/test_notify.py | 50 +- tests/components/mobile_app/test_webhook.py | 165 +- .../mobile_app/test_websocket_api.py | 57 +- tests/components/mochad/test_light.py | 62 +- tests/components/mochad/test_switch.py | 38 +- .../components/mold_indicator/test_sensor.py | 287 +- .../components/monoprice/test_media_player.py | 258 +- tests/components/moon/test_sensor.py | 32 +- .../mqtt/test_alarm_control_panel.py | 911 ++++--- tests/components/mqtt/test_binary_sensor.py | 640 ++--- tests/components/mqtt/test_camera.py | 167 +- tests/components/mqtt/test_climate.py | 954 +++---- tests/components/mqtt/test_config_flow.py | 123 +- tests/components/mqtt/test_cover.py | 2310 +++++++++-------- tests/components/mqtt/test_device_tracker.py | 160 +- tests/components/mqtt/test_discovery.py | 308 ++- tests/components/mqtt/test_fan.py | 830 +++--- tests/components/mqtt/test_init.py | 625 +++-- tests/components/mqtt/test_legacy_vacuum.py | 736 +++--- tests/components/mqtt/test_light.py | 1431 +++++----- tests/components/mqtt/test_light_json.py | 1423 +++++----- tests/components/mqtt/test_light_template.py | 1004 +++---- tests/components/mqtt/test_lock.py | 587 +++-- tests/components/mqtt/test_sensor.py | 830 +++--- tests/components/mqtt/test_server.py | 62 +- tests/components/mqtt/test_state_vacuum.py | 582 ++--- tests/components/mqtt/test_subscription.py | 112 +- tests/components/mqtt/test_switch.py | 604 +++-- .../components/mqtt_eventstream/test_init.py | 91 +- .../mqtt_json/test_device_tracker.py | 186 +- tests/components/mqtt_room/test_sensor.py | 66 +- .../components/mqtt_statestream/test_init.py | 267 +- tests/components/mythicbeastsdns/test_init.py | 34 +- tests/components/namecheapdns/test_init.py | 75 +- tests/components/ness_alarm/test_init.py | 151 +- tests/components/nest/test_config_flow.py | 179 +- tests/components/nest/test_local_auth.py | 24 +- tests/components/nextbus/test_sensor.py | 266 +- tests/components/no_ip/test_init.py | 72 +- tests/components/notify/common.py | 11 +- tests/components/notion/test_config_flow.py | 54 +- .../nsw_fuel_station/test_sensor.py | 61 +- .../test_geo_location.py | 190 +- tests/components/nuheat/test_climate.py | 25 +- tests/components/nuheat/test_init.py | 11 +- tests/components/nx584/test_binary_sensor.py | 113 +- tests/components/onboarding/__init__.py | 4 +- tests/components/onboarding/test_init.py | 41 +- tests/components/onboarding/test_views.py | 229 +- .../openalpr_cloud/test_image_processing.py | 161 +- .../openalpr_local/test_image_processing.py | 100 +- .../openhardwaremonitor/test_sensor.py | 23 +- tests/components/openuv/test_config_flow.py | 43 +- .../components/owntracks/test_config_flow.py | 58 +- .../owntracks/test_device_tracker.py | 1039 ++++---- tests/components/owntracks/test_init.py | 110 +- tests/components/panel_custom/test_init.py | 211 +- tests/components/panel_iframe/test_init.py | 113 +- .../persistent_notification/test_init.py | 132 +- tests/components/person/test_init.py | 523 ++-- tests/components/pilight/test_init.py | 281 +- tests/components/pilight/test_sensor.py | 120 +- tests/components/plant/test_init.py | 195 +- tests/components/point/test_config_flow.py | 94 +- tests/components/prometheus/test_init.py | 18 +- tests/components/proximity/test_init.py | 782 +++--- tests/components/ps4/test_config_flow.py | 310 +-- tests/components/ps4/test_init.py | 219 +- tests/components/ps4/test_media_player.py | 342 +-- tests/components/ptvsd/test_ptvsd.py | 40 +- tests/components/push/test_camera.py | 60 +- tests/components/pushbullet/test_notify.py | 280 +- tests/components/python_script/test_init.py | 155 +- .../qld_bushfire/test_geo_location.py | 140 +- tests/components/qwikswitch/test_init.py | 45 +- tests/components/radarr/test_sensor.py | 563 ++-- .../rainmachine/test_config_flow.py | 60 +- tests/components/random/test_binary_sensor.py | 30 +- tests/components/random/test_sensor.py | 18 +- tests/components/recorder/models_original.py | 66 +- tests/components/recorder/test_init.py | 94 +- tests/components/recorder/test_migrate.py | 60 +- tests/components/recorder/test_models.py | 100 +- tests/components/recorder/test_purge.py | 88 +- tests/components/recorder/test_util.py | 13 +- tests/components/reddit/test_sensor.py | 164 +- .../components/remember_the_milk/test_init.py | 55 +- tests/components/remote/common.py | 35 +- tests/components/remote/test_init.py | 68 +- tests/components/rest/test_binary_sensor.py | 172 +- tests/components/rest/test_sensor.py | 331 ++- tests/components/rest/test_switch.py | 149 +- tests/components/rest_command/test_init.py | 185 +- tests/components/rflink/test_binary_sensor.py | 98 +- tests/components/rflink/test_cover.py | 380 ++- tests/components/rflink/test_init.py | 306 +-- tests/components/rflink/test_light.py | 579 ++--- tests/components/rflink/test_sensor.py | 184 +- tests/components/rflink/test_switch.py | 273 +- tests/components/rfxtrx/test_cover.py | 255 +- tests/components/rfxtrx/test_init.py | 191 +- tests/components/rfxtrx/test_light.py | 303 ++- tests/components/rfxtrx/test_sensor.py | 357 +-- tests/components/rfxtrx/test_switch.py | 363 ++- tests/components/ring/test_binary_sensor.py | 65 +- tests/components/ring/test_init.py | 56 +- tests/components/ring/test_sensor.py | 114 +- tests/components/rmvtransport/test_sensor.py | 315 ++- .../components/rss_feed_template/test_init.py | 38 +- .../components/samsungtv/test_media_player.py | 134 +- tests/components/scene/test_init.py | 95 +- tests/components/script/test_init.py | 216 +- tests/components/season/test_sensor.py | 225 +- .../components/seventeentrack/test_sensor.py | 165 +- tests/components/shell_command/test_init.py | 143 +- tests/components/shopping_list/test_init.py | 369 ++- tests/components/sigfox/test_sensor.py | 40 +- .../components/simplisafe/test_config_flow.py | 92 +- tests/components/simulated/test_sensor.py | 31 +- .../components/sleepiq/test_binary_sensor.py | 37 +- tests/components/sleepiq/test_init.py | 47 +- tests/components/sleepiq/test_sensor.py | 33 +- tests/components/smartthings/conftest.py | 181 +- .../smartthings/test_binary_sensor.py | 61 +- tests/components/smartthings/test_climate.py | 474 ++-- .../smartthings/test_config_flow.py | 220 +- tests/components/smartthings/test_cover.py | 141 +- tests/components/smartthings/test_fan.py | 103 +- tests/components/smartthings/test_init.py | 253 +- tests/components/smartthings/test_light.py | 214 +- tests/components/smartthings/test_lock.py | 81 +- tests/components/smartthings/test_scene.py | 23 +- tests/components/smartthings/test_sensor.py | 98 +- tests/components/smartthings/test_smartapp.py | 113 +- tests/components/smartthings/test_switch.py | 67 +- tests/components/smhi/test_config_flow.py | 192 +- tests/components/smhi/test_init.py | 2 +- tests/components/smhi/test_weather.py | 171 +- tests/components/smtp/test_notify.py | 65 +- tests/components/snips/test_init.py | 380 ++- tests/components/somfy/test_config_flow.py | 53 +- tests/components/sonarr/test_sensor.py | 1038 ++++---- tests/components/sonos/conftest.py | 25 +- tests/components/sonos/test_init.py | 41 +- tests/components/sonos/test_media_player.py | 2 +- .../soundtouch/test_media_player.py | 696 +++-- tests/components/spaceapi/test_init.py | 97 +- tests/components/spc/test_init.py | 69 +- tests/components/splunk/test_init.py | 150 +- tests/components/sql/test_sensor.py | 48 +- tests/components/srp_energy/test_sensor.py | 54 +- tests/components/ssdp/test_init.py | 86 +- tests/components/startca/test_sensor.py | 316 +-- tests/components/statistics/test_sensor.py | 337 +-- tests/components/statsd/test_init.py | 131 +- tests/components/stream/common.py | 22 +- tests/components/stream/test_hls.py | 23 +- tests/components/stream/test_init.py | 82 +- tests/components/stream/test_recorder.py | 24 +- tests/components/sun/test_init.py | 119 +- tests/components/switch/common.py | 9 +- tests/components/switch/test_init.py | 50 +- tests/components/switch/test_light.py | 91 +- tests/components/switcher_kis/conftest.py | 49 +- tests/components/switcher_kis/consts.py | 28 +- tests/components/switcher_kis/test_init.py | 121 +- tests/components/system_health/test_init.py | 91 +- tests/components/system_log/test_init.py | 180 +- tests/components/tcp/test_binary_sensor.py | 38 +- tests/components/tcp/test_sensor.py | 249 +- tests/components/teksavvy/test_sensor.py | 272 +- .../tellduslive/test_config_flow.py | 190 +- .../components/template/test_binary_sensor.py | 473 ++-- tests/components/template/test_cover.py | 1124 ++++---- tests/components/template/test_fan.py | 473 ++-- tests/components/template/test_light.py | 976 +++---- tests/components/template/test_lock.py | 354 +-- tests/components/template/test_sensor.py | 489 ++-- tests/components/template/test_switch.py | 568 ++-- tests/components/template/test_vacuum.py | 422 ++- .../threshold/test_binary_sensor.py | 391 ++- tests/components/time_date/test_sensor.py | 56 +- tests/components/timer/test_init.py | 170 +- tests/components/tod/test_binary_sensor.py | 760 +++--- .../components/tomato/test_device_tracker.py | 459 ++-- tests/components/toon/test_config_flow.py | 99 +- tests/components/tplink/test_common.py | 28 +- .../components/tplink/test_device_tracker.py | 54 +- tests/components/tplink/test_init.py | 76 +- tests/components/tradfri/conftest.py | 5 +- tests/components/tradfri/test_config_flow.py | 287 +- tests/components/tradfri/test_init.py | 64 +- tests/components/tradfri/test_light.py | 346 +-- tests/components/transport_nsw/test_sensor.py | 51 +- tests/components/trend/test_binary_sensor.py | 426 +-- tests/components/tts/test_init.py | 579 +++-- .../twentemilieu/test_config_flow.py | 19 +- tests/components/twilio/test_init.py | 29 +- tests/components/uk_transport/test_sensor.py | 91 +- tests/components/unifi/test_controller.py | 97 +- tests/components/unifi/test_device_tracker.py | 179 +- tests/components/unifi/test_init.py | 324 +-- tests/components/unifi/test_switch.py | 456 ++-- .../unifi_direct/test_device_tracker.py | 145 +- .../components/universal/test_media_player.py | 415 ++- .../upc_connect/test_device_tracker.py | 121 +- tests/components/updater/test_init.py | 133 +- tests/components/upnp/test_init.py | 70 +- tests/components/uptime/test_sensor.py | 89 +- .../test_geo_location.py | 188 +- tests/components/utility_meter/test_init.py | 91 +- tests/components/utility_meter/test_sensor.py | 193 +- tests/components/uvc/test_camera.py | 215 +- tests/components/vacuum/common.py | 60 +- tests/components/velbus/test_config_flow.py | 80 +- tests/components/verisure/test_lock.py | 139 +- tests/components/version/test_sensor.py | 25 +- tests/components/vesync/test_config_flow.py | 39 +- tests/components/voicerss/test_tts.py | 149 +- tests/components/vultr/test_binary_sensor.py | 137 +- tests/components/vultr/test_init.py | 17 +- tests/components/vultr/test_sensor.py | 119 +- tests/components/vultr/test_switch.py | 151 +- tests/components/wake_on_lan/test_init.py | 26 +- tests/components/wake_on_lan/test_switch.py | 175 +- tests/components/water_heater/common.py | 29 +- tests/components/weather/test_weather.py | 45 +- tests/components/webhook/test_init.py | 63 +- tests/components/weblink/test_init.py | 174 +- tests/components/webostv/test_media_player.py | 32 +- tests/components/websocket_api/__init__.py | 2 +- tests/components/websocket_api/conftest.py | 14 +- tests/components/websocket_api/test_auth.py | 169 +- .../components/websocket_api/test_commands.py | 441 ++-- .../websocket_api/test_connection.py | 26 +- tests/components/websocket_api/test_init.py | 77 +- tests/components/websocket_api/test_sensor.py | 20 +- .../components/workday/test_binary_sensor.py | 184 +- tests/components/worldclock/test_sensor.py | 16 +- tests/components/wsdot/test_sensor.py | 36 +- tests/components/wunderground/test_sensor.py | 186 +- tests/components/wwlln/conftest.py | 13 +- tests/components/wwlln/test_config_flow.py | 64 +- .../components/xiaomi/test_device_tracker.py | 294 +-- tests/components/xiaomi_miio/test_vacuum.py | 342 ++- tests/components/yamaha/test_media_player.py | 33 +- tests/components/yandextts/test_tts.py | 328 ++- tests/components/yessssms/test_notify.py | 108 +- tests/components/yr/test_sensor.py | 151 +- tests/components/yweather/test_sensor.py | 211 +- tests/components/yweather/test_weather.py | 122 +- tests/components/zeroconf/test_init.py | 76 +- tests/components/zha/common.py | 67 +- tests/components/zha/conftest.py | 33 +- tests/components/zha/test_api.py | 99 +- tests/components/zha/test_binary_sensor.py | 46 +- tests/components/zha/test_config_flow.py | 64 +- tests/components/zha/test_device_tracker.py | 48 +- tests/components/zha/test_fan.py | 71 +- tests/components/zha/test_light.py | 164 +- tests/components/zha/test_lock.py | 42 +- tests/components/zha/test_sensor.py | 75 +- tests/components/zha/test_switch.py | 46 +- tests/components/zone/test_config_flow.py | 53 +- tests/components/zone/test_init.py | 260 +- tests/components/zwave/conftest.py | 17 +- tests/components/zwave/test_binary_sensor.py | 30 +- tests/components/zwave/test_climate.py | 89 +- tests/components/zwave/test_cover.py | 128 +- tests/components/zwave/test_fan.py | 13 +- tests/components/zwave/test_init.py | 1098 ++++---- tests/components/zwave/test_light.py | 157 +- tests/components/zwave/test_lock.py | 172 +- tests/components/zwave/test_node_entity.py | 460 +++- tests/components/zwave/test_sensor.py | 69 +- tests/components/zwave/test_switch.py | 8 +- tests/components/zwave/test_workaround.py | 43 +- tests/conftest.py | 77 +- tests/helpers/test_aiohttp_client.py | 121 +- tests/helpers/test_area_registry.py | 79 +- tests/helpers/test_check_config.py | 93 +- tests/helpers/test_condition.py | 189 +- tests/helpers/test_config_entry_flow.py | 194 +- tests/helpers/test_config_validation.py | 565 ++-- tests/helpers/test_deprecation.py | 27 +- tests/helpers/test_device_registry.py | 423 ++- tests/helpers/test_discovery.py | 145 +- tests/helpers/test_dispatcher.py | 62 +- tests/helpers/test_entity.py | 90 +- tests/helpers/test_entity_component.py | 328 ++- tests/helpers/test_entity_platform.py | 490 ++-- tests/helpers/test_entity_registry.py | 276 +- tests/helpers/test_entity_values.py | 52 +- tests/helpers/test_entityfilter.py | 32 +- tests/helpers/test_event.py | 223 +- tests/helpers/test_icon.py | 49 +- tests/helpers/test_init.py | 35 +- tests/helpers/test_intent.py | 25 +- tests/helpers/test_json.py | 2 +- tests/helpers/test_location.py | 40 +- tests/helpers/test_restore_state.py | 179 +- tests/helpers/test_script.py | 597 +++-- tests/helpers/test_service.py | 598 +++-- tests/helpers/test_state.py | 162 +- tests/helpers/test_storage.py | 116 +- tests/helpers/test_sun.py | 139 +- tests/helpers/test_system_info.py | 2 +- tests/helpers/test_temperature.py | 11 +- tests/helpers/test_template.py | 1665 +++++++----- tests/helpers/test_translation.py | 137 +- tests/mock/zwave.py | 143 +- tests/scripts/test_auth.py | 81 +- tests/scripts/test_check_config.py | 179 +- tests/scripts/test_init.py | 13 +- tests/test_bootstrap.py | 243 +- tests/test_config.py | 821 +++--- tests/test_config_entries.py | 695 +++-- tests/test_core.py | 462 ++-- tests/test_data_entry_flow.py | 211 +- tests/test_loader.py | 173 +- tests/test_main.py | 17 +- tests/test_requirements.py | 155 +- tests/test_setup.py | 482 ++-- tests/test_util/aiohttp.py | 103 +- tests/test_util/test_aiohttp.py | 11 +- .../custom_components/test/device_tracker.py | 2 +- .../test/image_processing.py | 10 +- .../custom_components/test/light.py | 19 +- .../custom_components/test/switch.py | 19 +- .../test_embedded/__init__.py | 2 +- .../custom_components/test_embedded/switch.py | 5 +- .../test_package/__init__.py | 2 +- .../custom_components/test_standalone.py | 2 +- tests/util/test_aiohttp.py | 22 +- tests/util/test_async.py | 32 +- tests/util/test_color.py | 133 +- tests/util/test_distance.py | 25 +- tests/util/test_dt.py | 123 +- tests/util/test_init.py | 74 +- tests/util/test_json.py | 11 +- tests/util/test_location.py | 79 +- tests/util/test_logging.py | 25 +- tests/util/test_package.py | 167 +- tests/util/test_pressure.py | 53 +- tests/util/test_ruamel_yaml.py | 6 +- tests/util/test_unit_system.py | 100 +- tests/util/test_volume.py | 16 +- tests/util/test_yaml.py | 361 +-- 2676 files changed, 163166 insertions(+), 140084 deletions(-) diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index 53e24815ee6..d21bfb5a71a 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -7,9 +7,7 @@ import platform import subprocess import sys import threading -from typing import ( # noqa pylint: disable=unused-import - List, Dict, Any, TYPE_CHECKING -) +from typing import List, Dict, Any, TYPE_CHECKING # noqa pylint: disable=unused-import from homeassistant import monkey_patch from homeassistant.const import ( @@ -30,11 +28,12 @@ def set_loop() -> None: policy = None - if sys.platform == 'win32': - if hasattr(asyncio, 'WindowsProactorEventLoopPolicy'): + if sys.platform == "win32": + if hasattr(asyncio, "WindowsProactorEventLoopPolicy"): # pylint: disable=no-member policy = asyncio.WindowsProactorEventLoopPolicy() else: + class ProactorPolicy(BaseDefaultEventLoopPolicy): """Event loop policy to create proactor loops.""" @@ -56,28 +55,40 @@ def set_loop() -> None: def validate_python() -> None: """Validate that the right Python version is running.""" if sys.version_info[:3] < REQUIRED_PYTHON_VER: - print("Home Assistant requires at least Python {}.{}.{}".format( - *REQUIRED_PYTHON_VER)) + print( + "Home Assistant requires at least Python {}.{}.{}".format( + *REQUIRED_PYTHON_VER + ) + ) sys.exit(1) def ensure_config_path(config_dir: str) -> None: """Validate the configuration directory.""" import homeassistant.config as config_util - lib_dir = os.path.join(config_dir, 'deps') + + lib_dir = os.path.join(config_dir, "deps") # Test if configuration directory exists if not os.path.isdir(config_dir): if config_dir != config_util.get_default_config_dir(): - print(('Fatal Error: Specified configuration directory does ' - 'not exist {} ').format(config_dir)) + print( + ( + "Fatal Error: Specified configuration directory does " + "not exist {} " + ).format(config_dir) + ) sys.exit(1) try: os.mkdir(config_dir) except OSError: - print(('Fatal Error: Unable to create default configuration ' - 'directory {} ').format(config_dir)) + print( + ( + "Fatal Error: Unable to create default configuration " + "directory {} " + ).format(config_dir) + ) sys.exit(1) # Test if library directory exists @@ -85,20 +96,22 @@ def ensure_config_path(config_dir: str) -> None: try: os.mkdir(lib_dir) except OSError: - print(('Fatal Error: Unable to create library ' - 'directory {} ').format(lib_dir)) + print( + ("Fatal Error: Unable to create library " "directory {} ").format( + lib_dir + ) + ) sys.exit(1) -async def ensure_config_file(hass: 'core.HomeAssistant', config_dir: str) \ - -> str: +async def ensure_config_file(hass: "core.HomeAssistant", config_dir: str) -> str: """Ensure configuration file exists.""" import homeassistant.config as config_util - config_path = await config_util.async_ensure_config_exists( - hass, config_dir) + + config_path = await config_util.async_ensure_config_exists(hass, config_dir) if config_path is None: - print('Error getting configuration path') + print("Error getting configuration path") sys.exit(1) return config_path @@ -107,71 +120,72 @@ async def ensure_config_file(hass: 'core.HomeAssistant', config_dir: str) \ def get_arguments() -> argparse.Namespace: """Get parsed passed in arguments.""" import homeassistant.config as config_util + parser = argparse.ArgumentParser( - description="Home Assistant: Observe, Control, Automate.") - parser.add_argument('--version', action='version', version=__version__) + description="Home Assistant: Observe, Control, Automate." + ) + parser.add_argument("--version", action="version", version=__version__) parser.add_argument( - '-c', '--config', - metavar='path_to_config_dir', + "-c", + "--config", + metavar="path_to_config_dir", default=config_util.get_default_config_dir(), - help="Directory that contains the Home Assistant configuration") + help="Directory that contains the Home Assistant configuration", + ) parser.add_argument( - '--demo-mode', - action='store_true', - help='Start Home Assistant in demo mode') + "--demo-mode", action="store_true", help="Start Home Assistant in demo mode" + ) parser.add_argument( - '--debug', - action='store_true', - help='Start Home Assistant in debug mode') + "--debug", action="store_true", help="Start Home Assistant in debug mode" + ) parser.add_argument( - '--open-ui', - action='store_true', - help='Open the webinterface in a browser') + "--open-ui", action="store_true", help="Open the webinterface in a browser" + ) parser.add_argument( - '--skip-pip', - action='store_true', - help='Skips pip install of required packages on startup') + "--skip-pip", + action="store_true", + help="Skips pip install of required packages on startup", + ) parser.add_argument( - '-v', '--verbose', - action='store_true', - help="Enable verbose logging to file.") + "-v", "--verbose", action="store_true", help="Enable verbose logging to file." + ) parser.add_argument( - '--pid-file', - metavar='path_to_pid_file', + "--pid-file", + metavar="path_to_pid_file", default=None, - help='Path to PID file useful for running as daemon') + help="Path to PID file useful for running as daemon", + ) parser.add_argument( - '--log-rotate-days', + "--log-rotate-days", type=int, default=None, - help='Enables daily log rotation and keeps up to the specified days') + help="Enables daily log rotation and keeps up to the specified days", + ) parser.add_argument( - '--log-file', + "--log-file", type=str, default=None, - help='Log file to write to. If not set, CONFIG/home-assistant.log ' - 'is used') + help="Log file to write to. If not set, CONFIG/home-assistant.log " "is used", + ) parser.add_argument( - '--log-no-color', - action='store_true', - help="Disable color logs") + "--log-no-color", action="store_true", help="Disable color logs" + ) parser.add_argument( - '--runner', - action='store_true', - help='On restart exit with code {}'.format(RESTART_EXIT_CODE)) + "--runner", + action="store_true", + help="On restart exit with code {}".format(RESTART_EXIT_CODE), + ) parser.add_argument( - '--script', - nargs=argparse.REMAINDER, - help='Run one of the embedded scripts') + "--script", nargs=argparse.REMAINDER, help="Run one of the embedded scripts" + ) if os.name == "posix": parser.add_argument( - '--daemon', - action='store_true', - help='Run Home Assistant as daemon') + "--daemon", action="store_true", help="Run Home Assistant as daemon" + ) arguments = parser.parse_args() if os.name != "posix" or arguments.debug or arguments.runner: - setattr(arguments, 'daemon', False) + setattr(arguments, "daemon", False) return arguments @@ -192,8 +206,8 @@ def daemonize() -> None: sys.exit(0) # redirect standard file descriptors to devnull - infd = open(os.devnull, 'r') - outfd = open(os.devnull, 'a+') + infd = open(os.devnull, "r") + outfd = open(os.devnull, "a+") sys.stdout.flush() sys.stderr.flush() os.dup2(infd.fileno(), sys.stdin.fileno()) @@ -205,7 +219,7 @@ def check_pid(pid_file: str) -> None: """Check that Home Assistant is not already running.""" # Check pid file try: - with open(pid_file, 'r') as file: + with open(pid_file, "r") as file: pid = int(file.readline()) except IOError: # PID File does not exist @@ -220,7 +234,7 @@ def check_pid(pid_file: str) -> None: except OSError: # PID does not exist return - print('Fatal Error: HomeAssistant is already running.') + print("Fatal Error: HomeAssistant is already running.") sys.exit(1) @@ -228,10 +242,10 @@ def write_pid(pid_file: str) -> None: """Create a PID File.""" pid = os.getpid() try: - with open(pid_file, 'w') as file: + with open(pid_file, "w") as file: file.write(str(pid)) except IOError: - print('Fatal Error: Unable to write pid file {}'.format(pid_file)) + print("Fatal Error: Unable to write pid file {}".format(pid_file)) sys.exit(1) @@ -255,17 +269,15 @@ def closefds_osx(min_fd: int, max_fd: int) -> None: def cmdline() -> List[str]: """Collect path and arguments to re-execute the current hass instance.""" - if os.path.basename(sys.argv[0]) == '__main__.py': + if os.path.basename(sys.argv[0]) == "__main__.py": modulepath = os.path.dirname(sys.argv[0]) - os.environ['PYTHONPATH'] = os.path.dirname(modulepath) - return [sys.executable] + [arg for arg in sys.argv if - arg != '--daemon'] + os.environ["PYTHONPATH"] = os.path.dirname(modulepath) + return [sys.executable] + [arg for arg in sys.argv if arg != "--daemon"] - return [arg for arg in sys.argv if arg != '--daemon'] + return [arg for arg in sys.argv if arg != "--daemon"] -async def setup_and_run_hass(config_dir: str, - args: argparse.Namespace) -> int: +async def setup_and_run_hass(config_dir: str, args: argparse.Namespace) -> int: """Set up HASS and run.""" # pylint: disable=redefined-outer-name from homeassistant import bootstrap, core @@ -273,21 +285,29 @@ async def setup_and_run_hass(config_dir: str, hass = core.HomeAssistant() if args.demo_mode: - config = { - 'frontend': {}, - 'demo': {} - } # type: Dict[str, Any] + config = {"frontend": {}, "demo": {}} # type: Dict[str, Any] bootstrap.async_from_config_dict( - config, hass, config_dir=config_dir, verbose=args.verbose, - skip_pip=args.skip_pip, log_rotate_days=args.log_rotate_days, - log_file=args.log_file, log_no_color=args.log_no_color) + config, + hass, + config_dir=config_dir, + verbose=args.verbose, + skip_pip=args.skip_pip, + log_rotate_days=args.log_rotate_days, + log_file=args.log_file, + log_no_color=args.log_no_color, + ) else: config_file = await ensure_config_file(hass, config_dir) - print('Config directory:', config_dir) + print("Config directory:", config_dir) await bootstrap.async_from_config_file( - config_file, hass, verbose=args.verbose, skip_pip=args.skip_pip, - log_rotate_days=args.log_rotate_days, log_file=args.log_file, - log_no_color=args.log_no_color) + config_file, + hass, + verbose=args.verbose, + skip_pip=args.skip_pip, + log_rotate_days=args.log_rotate_days, + log_file=args.log_file, + log_no_color=args.log_no_color, + ) if args.open_ui: # Imported here to avoid importing asyncio before monkey patch @@ -297,12 +317,14 @@ async def setup_and_run_hass(config_dir: str, """Open the web interface in a browser.""" if hass.config.api is not None: import webbrowser + webbrowser.open(hass.config.api.base_url) run_callback_threadsafe( hass.loop, hass.bus.async_listen_once, - EVENT_HOMEASSISTANT_START, open_browser + EVENT_HOMEASSISTANT_START, + open_browser, ) return await hass.async_run() @@ -312,17 +334,17 @@ def try_to_restart() -> None: """Attempt to clean up state and start a new Home Assistant instance.""" # Things should be mostly shut down already at this point, now just try # to clean up things that may have been left behind. - sys.stderr.write('Home Assistant attempting to restart.\n') + sys.stderr.write("Home Assistant attempting to restart.\n") # Count remaining threads, ideally there should only be one non-daemonized # thread left (which is us). Nothing we really do with it, but it might be # useful when debugging shutdown/restart issues. try: - nthreads = sum(thread.is_alive() and not thread.daemon - for thread in threading.enumerate()) + nthreads = sum( + thread.is_alive() and not thread.daemon for thread in threading.enumerate() + ) if nthreads > 1: - sys.stderr.write( - "Found {} non-daemonic threads.\n".format(nthreads)) + sys.stderr.write("Found {} non-daemonic threads.\n".format(nthreads)) # Somehow we sometimes seem to trigger an assertion in the python threading # module. It seems we find threads that have no associated OS level thread @@ -336,7 +358,7 @@ def try_to_restart() -> None: except ValueError: max_fd = 256 - if platform.system() == 'Darwin': + if platform.system() == "Darwin": closefds_osx(3, max_fd) else: os.closerange(3, max_fd) @@ -355,15 +377,15 @@ def main() -> int: validate_python() monkey_patch_needed = sys.version_info[:3] < (3, 6, 3) - if monkey_patch_needed and os.environ.get('HASS_NO_MONKEY') != '1': + if monkey_patch_needed and os.environ.get("HASS_NO_MONKEY") != "1": monkey_patch.disable_c_asyncio() monkey_patch.patch_weakref_tasks() set_loop() # Run a simple daemon runner process on Windows to handle restarts - if os.name == 'nt' and '--runner' not in sys.argv: - nt_args = cmdline() + ['--runner'] + if os.name == "nt" and "--runner" not in sys.argv: + nt_args = cmdline() + ["--runner"] while True: try: subprocess.check_call(nt_args) @@ -378,6 +400,7 @@ def main() -> int: if args.script is not None: from homeassistant import scripts + return scripts.run(args.script) config_dir = os.path.join(os.getcwd(), args.config) @@ -392,6 +415,7 @@ def main() -> int: write_pid(args.pid_file) from homeassistant.util.async_ import asyncio_run + exit_code = asyncio_run(setup_and_run_hass(config_dir, args)) if exit_code == RESTART_EXIT_CODE and not args.runner: try_to_restart() diff --git a/homeassistant/auth/__init__.py b/homeassistant/auth/__init__.py index 2f9465d2398..f00687b828c 100644 --- a/homeassistant/auth/__init__.py +++ b/homeassistant/auth/__init__.py @@ -17,8 +17,8 @@ from .const import GROUP_ID_ADMIN from .mfa_modules import auth_mfa_module_from_config, MultiFactorAuthModule from .providers import auth_provider_from_config, AuthProvider, LoginFlow -EVENT_USER_ADDED = 'user_added' -EVENT_USER_REMOVED = 'user_removed' +EVENT_USER_ADDED = "user_added" +EVENT_USER_REMOVED = "user_removed" _LOGGER = logging.getLogger(__name__) _MfaModuleDict = Dict[str, MultiFactorAuthModule] @@ -27,9 +27,10 @@ _ProviderDict = Dict[_ProviderKey, AuthProvider] async def auth_manager_from_config( - hass: HomeAssistant, - provider_configs: List[Dict[str, Any]], - module_configs: List[Dict[str, Any]]) -> 'AuthManager': + hass: HomeAssistant, + provider_configs: List[Dict[str, Any]], + module_configs: List[Dict[str, Any]], +) -> "AuthManager": """Initialize an auth manager from config. CORE_CONFIG_SCHEMA will make sure do duplicated auth providers or @@ -38,8 +39,11 @@ async def auth_manager_from_config( store = auth_store.AuthStore(hass) if provider_configs: providers = await asyncio.gather( - *(auth_provider_from_config(hass, store, config) - for config in provider_configs)) + *( + auth_provider_from_config(hass, store, config) + for config in provider_configs + ) + ) else: providers = () # So returned auth providers are in same order as config @@ -50,8 +54,8 @@ async def auth_manager_from_config( if module_configs: modules = await asyncio.gather( - *(auth_mfa_module_from_config(hass, config) - for config in module_configs)) + *(auth_mfa_module_from_config(hass, config) for config in module_configs) + ) else: modules = () # So returned auth modules are in same order as config @@ -66,17 +70,21 @@ async def auth_manager_from_config( class AuthManager: """Manage the authentication for Home Assistant.""" - def __init__(self, hass: HomeAssistant, store: auth_store.AuthStore, - providers: _ProviderDict, mfa_modules: _MfaModuleDict) \ - -> None: + def __init__( + self, + hass: HomeAssistant, + store: auth_store.AuthStore, + providers: _ProviderDict, + mfa_modules: _MfaModuleDict, + ) -> None: """Initialize the auth manager.""" self.hass = hass self._store = store self._providers = providers self._mfa_modules = mfa_modules self.login_flow = data_entry_flow.FlowManager( - hass, self._async_create_login_flow, - self._async_finish_login_flow) + hass, self._async_create_login_flow, self._async_finish_login_flow + ) @property def support_legacy(self) -> bool: @@ -86,7 +94,7 @@ class AuthManager: Should be removed when we removed legacy_api_password auth providers. """ for provider_type, _ in self._providers: - if provider_type == 'legacy_api_password': + if provider_type == "legacy_api_password": return True return False @@ -100,20 +108,21 @@ class AuthManager: """Return a list of available auth modules.""" return list(self._mfa_modules.values()) - def get_auth_provider(self, provider_type: str, provider_id: str) \ - -> Optional[AuthProvider]: + def get_auth_provider( + self, provider_type: str, provider_id: str + ) -> Optional[AuthProvider]: """Return an auth provider, None if not found.""" return self._providers.get((provider_type, provider_id)) - def get_auth_providers(self, provider_type: str) \ - -> List[AuthProvider]: + def get_auth_providers(self, provider_type: str) -> List[AuthProvider]: """Return a List of auth provider of one type, Empty if not found.""" - return [provider - for (p_type, _), provider in self._providers.items() - if p_type == provider_type] + return [ + provider + for (p_type, _), provider in self._providers.items() + if p_type == provider_type + ] - def get_auth_mfa_module(self, module_id: str) \ - -> Optional[MultiFactorAuthModule]: + def get_auth_mfa_module(self, module_id: str) -> Optional[MultiFactorAuthModule]: """Return a multi-factor auth module, None if not found.""" return self._mfa_modules.get(module_id) @@ -135,7 +144,8 @@ class AuthManager: return await self._store.async_get_group(group_id) async def async_get_user_by_credentials( - self, credentials: models.Credentials) -> Optional[models.User]: + self, credentials: models.Credentials + ) -> Optional[models.User]: """Get a user by credential, return None if not found.""" for user in await self.async_get_users(): for creds in user.credentials: @@ -145,57 +155,50 @@ class AuthManager: return None async def async_create_system_user( - self, name: str, - group_ids: Optional[List[str]] = None) -> models.User: + self, name: str, group_ids: Optional[List[str]] = None + ) -> models.User: """Create a system user.""" user = await self._store.async_create_user( - name=name, - system_generated=True, - is_active=True, - group_ids=group_ids or [], + name=name, system_generated=True, is_active=True, group_ids=group_ids or [] ) - self.hass.bus.async_fire(EVENT_USER_ADDED, { - 'user_id': user.id - }) + self.hass.bus.async_fire(EVENT_USER_ADDED, {"user_id": user.id}) return user async def async_create_user(self, name: str) -> models.User: """Create a user.""" kwargs = { - 'name': name, - 'is_active': True, - 'group_ids': [GROUP_ID_ADMIN] + "name": name, + "is_active": True, + "group_ids": [GROUP_ID_ADMIN], } # type: Dict[str, Any] if await self._user_should_be_owner(): - kwargs['is_owner'] = True + kwargs["is_owner"] = True user = await self._store.async_create_user(**kwargs) - self.hass.bus.async_fire(EVENT_USER_ADDED, { - 'user_id': user.id - }) + self.hass.bus.async_fire(EVENT_USER_ADDED, {"user_id": user.id}) return user - async def async_get_or_create_user(self, credentials: models.Credentials) \ - -> models.User: + async def async_get_or_create_user( + self, credentials: models.Credentials + ) -> models.User: """Get or create a user.""" if not credentials.is_new: user = await self.async_get_user_by_credentials(credentials) if user is None: - raise ValueError('Unable to find the user.') + raise ValueError("Unable to find the user.") return user auth_provider = self._async_get_auth_provider(credentials) if auth_provider is None: - raise RuntimeError('Credential with unknown provider encountered') + raise RuntimeError("Credential with unknown provider encountered") - info = await auth_provider.async_user_meta_for_credentials( - credentials) + info = await auth_provider.async_user_meta_for_credentials(credentials) user = await self._store.async_create_user( credentials=credentials, @@ -204,14 +207,13 @@ class AuthManager: group_ids=[GROUP_ID_ADMIN], ) - self.hass.bus.async_fire(EVENT_USER_ADDED, { - 'user_id': user.id - }) + self.hass.bus.async_fire(EVENT_USER_ADDED, {"user_id": user.id}) return user - async def async_link_user(self, user: models.User, - credentials: models.Credentials) -> None: + async def async_link_user( + self, user: models.User, credentials: models.Credentials + ) -> None: """Link credentials to an existing user.""" await self._store.async_link_user(user, credentials) @@ -227,19 +229,20 @@ class AuthManager: await self._store.async_remove_user(user) - self.hass.bus.async_fire(EVENT_USER_REMOVED, { - 'user_id': user.id - }) + self.hass.bus.async_fire(EVENT_USER_REMOVED, {"user_id": user.id}) - async def async_update_user(self, user: models.User, - name: Optional[str] = None, - group_ids: Optional[List[str]] = None) -> None: + async def async_update_user( + self, + user: models.User, + name: Optional[str] = None, + group_ids: Optional[List[str]] = None, + ) -> None: """Update a user.""" kwargs = {} # type: Dict[str,Any] if name is not None: - kwargs['name'] = name + kwargs["name"] = name if group_ids is not None: - kwargs['group_ids'] = group_ids + kwargs["group_ids"] = group_ids await self._store.async_update_user(user, **kwargs) async def async_activate_user(self, user: models.User) -> None: @@ -249,47 +252,52 @@ class AuthManager: async def async_deactivate_user(self, user: models.User) -> None: """Deactivate a user.""" if user.is_owner: - raise ValueError('Unable to deactive the owner') + raise ValueError("Unable to deactive the owner") await self._store.async_deactivate_user(user) - async def async_remove_credentials( - self, credentials: models.Credentials) -> None: + async def async_remove_credentials(self, credentials: models.Credentials) -> None: """Remove credentials.""" provider = self._async_get_auth_provider(credentials) - if (provider is not None and - hasattr(provider, 'async_will_remove_credentials')): + if provider is not None and hasattr(provider, "async_will_remove_credentials"): # https://github.com/python/mypy/issues/1424 await provider.async_will_remove_credentials( # type: ignore - credentials) + credentials + ) await self._store.async_remove_credentials(credentials) - async def async_enable_user_mfa(self, user: models.User, - mfa_module_id: str, data: Any) -> None: + async def async_enable_user_mfa( + self, user: models.User, mfa_module_id: str, data: Any + ) -> None: """Enable a multi-factor auth module for user.""" if user.system_generated: - raise ValueError('System generated users cannot enable ' - 'multi-factor auth module.') + raise ValueError( + "System generated users cannot enable " "multi-factor auth module." + ) module = self.get_auth_mfa_module(mfa_module_id) if module is None: - raise ValueError('Unable find multi-factor auth module: {}' - .format(mfa_module_id)) + raise ValueError( + "Unable find multi-factor auth module: {}".format(mfa_module_id) + ) await module.async_setup_user(user.id, data) - async def async_disable_user_mfa(self, user: models.User, - mfa_module_id: str) -> None: + async def async_disable_user_mfa( + self, user: models.User, mfa_module_id: str + ) -> None: """Disable a multi-factor auth module for user.""" if user.system_generated: - raise ValueError('System generated users cannot disable ' - 'multi-factor auth module.') + raise ValueError( + "System generated users cannot disable " "multi-factor auth module." + ) module = self.get_auth_mfa_module(mfa_module_id) if module is None: - raise ValueError('Unable find multi-factor auth module: {}' - .format(mfa_module_id)) + raise ValueError( + "Unable find multi-factor auth module: {}".format(mfa_module_id) + ) await module.async_depose_user(user.id) @@ -302,20 +310,23 @@ class AuthManager: return modules async def async_create_refresh_token( - self, user: models.User, client_id: Optional[str] = None, - client_name: Optional[str] = None, - client_icon: Optional[str] = None, - token_type: Optional[str] = None, - access_token_expiration: timedelta = ACCESS_TOKEN_EXPIRATION) \ - -> models.RefreshToken: + self, + user: models.User, + client_id: Optional[str] = None, + client_name: Optional[str] = None, + client_icon: Optional[str] = None, + token_type: Optional[str] = None, + access_token_expiration: timedelta = ACCESS_TOKEN_EXPIRATION, + ) -> models.RefreshToken: """Create a new refresh token for a user.""" if not user.is_active: - raise ValueError('User is not active') + raise ValueError("User is not active") if user.system_generated and client_id is not None: raise ValueError( - 'System generated users cannot have refresh tokens connected ' - 'to a client.') + "System generated users cannot have refresh tokens connected " + "to a client." + ) if token_type is None: if user.system_generated: @@ -325,61 +336,76 @@ class AuthManager: if user.system_generated != (token_type == models.TOKEN_TYPE_SYSTEM): raise ValueError( - 'System generated users can only have system type ' - 'refresh tokens') + "System generated users can only have system type " "refresh tokens" + ) if token_type == models.TOKEN_TYPE_NORMAL and client_id is None: - raise ValueError('Client is required to generate a refresh token.') + raise ValueError("Client is required to generate a refresh token.") - if (token_type == models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN and - client_name is None): - raise ValueError('Client_name is required for long-lived access ' - 'token') + if ( + token_type == models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN + and client_name is None + ): + raise ValueError("Client_name is required for long-lived access " "token") if token_type == models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN: for token in user.refresh_tokens.values(): - if (token.client_name == client_name and token.token_type == - models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN): + if ( + token.client_name == client_name + and token.token_type == models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN + ): # Each client_name can only have one # long_lived_access_token type of refresh token - raise ValueError('{} already exists'.format(client_name)) + raise ValueError("{} already exists".format(client_name)) return await self._store.async_create_refresh_token( - user, client_id, client_name, client_icon, - token_type, access_token_expiration) + user, + client_id, + client_name, + client_icon, + token_type, + access_token_expiration, + ) async def async_get_refresh_token( - self, token_id: str) -> Optional[models.RefreshToken]: + self, token_id: str + ) -> Optional[models.RefreshToken]: """Get refresh token by id.""" return await self._store.async_get_refresh_token(token_id) async def async_get_refresh_token_by_token( - self, token: str) -> Optional[models.RefreshToken]: + self, token: str + ) -> Optional[models.RefreshToken]: """Get refresh token by token.""" return await self._store.async_get_refresh_token_by_token(token) - async def async_remove_refresh_token(self, - refresh_token: models.RefreshToken) \ - -> None: + async def async_remove_refresh_token( + self, refresh_token: models.RefreshToken + ) -> None: """Delete a refresh token.""" await self._store.async_remove_refresh_token(refresh_token) @callback - def async_create_access_token(self, - refresh_token: models.RefreshToken, - remote_ip: Optional[str] = None) -> str: + def async_create_access_token( + self, refresh_token: models.RefreshToken, remote_ip: Optional[str] = None + ) -> str: """Create a new access token.""" self._store.async_log_refresh_token_usage(refresh_token, remote_ip) now = dt_util.utcnow() - return jwt.encode({ - 'iss': refresh_token.id, - 'iat': now, - 'exp': now + refresh_token.access_token_expiration, - }, refresh_token.jwt_key, algorithm='HS256').decode() + return jwt.encode( + { + "iss": refresh_token.id, + "iat": now, + "exp": now + refresh_token.access_token_expiration, + }, + refresh_token.jwt_key, + algorithm="HS256", + ).decode() async def async_validate_access_token( - self, token: str) -> Optional[models.RefreshToken]: + self, token: str + ) -> Optional[models.RefreshToken]: """Return refresh token if an access token is valid.""" try: unverif_claims = jwt.decode(token, verify=False) @@ -387,23 +413,18 @@ class AuthManager: return None refresh_token = await self.async_get_refresh_token( - cast(str, unverif_claims.get('iss'))) + cast(str, unverif_claims.get("iss")) + ) if refresh_token is None: - jwt_key = '' - issuer = '' + jwt_key = "" + issuer = "" else: jwt_key = refresh_token.jwt_key issuer = refresh_token.id try: - jwt.decode( - token, - jwt_key, - leeway=10, - issuer=issuer, - algorithms=['HS256'] - ) + jwt.decode(token, jwt_key, leeway=10, issuer=issuer, algorithms=["HS256"]) except jwt.InvalidTokenError: return None @@ -413,31 +434,32 @@ class AuthManager: return refresh_token async def _async_create_login_flow( - self, handler: _ProviderKey, *, context: Optional[Dict], - data: Optional[Any]) -> data_entry_flow.FlowHandler: + self, handler: _ProviderKey, *, context: Optional[Dict], data: Optional[Any] + ) -> data_entry_flow.FlowHandler: """Create a login flow.""" auth_provider = self._providers[handler] return await auth_provider.async_login_flow(context) async def _async_finish_login_flow( - self, flow: LoginFlow, result: Dict[str, Any]) \ - -> Dict[str, Any]: + self, flow: LoginFlow, result: Dict[str, Any] + ) -> Dict[str, Any]: """Return a user as result of login flow.""" - if result['type'] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY: + if result["type"] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY: return result # we got final result - if isinstance(result['data'], models.User): - result['result'] = result['data'] + if isinstance(result["data"], models.User): + result["result"] = result["data"] return result - auth_provider = self._providers[result['handler']] + auth_provider = self._providers[result["handler"]] credentials = await auth_provider.async_get_or_create_credentials( - result['data']) + result["data"] + ) - if flow.context is not None and flow.context.get('credential_only'): - result['result'] = credentials + if flow.context is not None and flow.context.get("credential_only"): + result["result"] = credentials return result # multi-factor module cannot enabled for new credential @@ -452,15 +474,18 @@ class AuthManager: flow.available_mfa_modules = modules return await flow.async_step_select_mfa_module() - result['result'] = await self.async_get_or_create_user(credentials) + result["result"] = await self.async_get_or_create_user(credentials) return result @callback def _async_get_auth_provider( - self, credentials: models.Credentials) -> Optional[AuthProvider]: + self, credentials: models.Credentials + ) -> Optional[AuthProvider]: """Get auth provider from a set of credentials.""" - auth_provider_key = (credentials.auth_provider_type, - credentials.auth_provider_id) + auth_provider_key = ( + credentials.auth_provider_type, + credentials.auth_provider_id, + ) return self._providers.get(auth_provider_key) async def _user_should_be_owner(self) -> bool: diff --git a/homeassistant/auth/auth_store.py b/homeassistant/auth/auth_store.py index b9acc90d5c2..82db0bcf7a9 100644 --- a/homeassistant/auth/auth_store.py +++ b/homeassistant/auth/auth_store.py @@ -16,10 +16,10 @@ from .permissions import PermissionLookup, system_policies from .permissions.types import PolicyType # noqa: F401 STORAGE_VERSION = 1 -STORAGE_KEY = 'auth' -GROUP_NAME_ADMIN = 'Administrators' +STORAGE_KEY = "auth" +GROUP_NAME_ADMIN = "Administrators" GROUP_NAME_USER = "Users" -GROUP_NAME_READ_ONLY = 'Read Only' +GROUP_NAME_READ_ONLY = "Read Only" class AuthStore: @@ -37,8 +37,9 @@ class AuthStore: self._users = None # type: Optional[Dict[str, models.User]] self._groups = None # type: Optional[Dict[str, models.Group]] self._perm_lookup = None # type: Optional[PermissionLookup] - self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY, - private=True) + self._store = hass.helpers.storage.Store( + STORAGE_VERSION, STORAGE_KEY, private=True + ) self._lock = asyncio.Lock() async def async_get_groups(self) -> List[models.Group]: @@ -74,11 +75,14 @@ class AuthStore: return self._users.get(user_id) async def async_create_user( - self, name: Optional[str], is_owner: Optional[bool] = None, - is_active: Optional[bool] = None, - system_generated: Optional[bool] = None, - credentials: Optional[models.Credentials] = None, - group_ids: Optional[List[str]] = None) -> models.User: + self, + name: Optional[str], + is_owner: Optional[bool] = None, + is_active: Optional[bool] = None, + system_generated: Optional[bool] = None, + credentials: Optional[models.Credentials] = None, + group_ids: Optional[List[str]] = None, + ) -> models.User: """Create a new user.""" if self._users is None: await self._async_load() @@ -87,28 +91,28 @@ class AuthStore: assert self._groups is not None groups = [] - for group_id in (group_ids or []): + for group_id in group_ids or []: group = self._groups.get(group_id) if group is None: - raise ValueError('Invalid group specified {}'.format(group_id)) + raise ValueError("Invalid group specified {}".format(group_id)) groups.append(group) kwargs = { - 'name': name, + "name": name, # Until we get group management, we just put everyone in the # same group. - 'groups': groups, - 'perm_lookup': self._perm_lookup, + "groups": groups, + "perm_lookup": self._perm_lookup, } # type: Dict[str, Any] if is_owner is not None: - kwargs['is_owner'] = is_owner + kwargs["is_owner"] = is_owner if is_active is not None: - kwargs['is_active'] = is_active + kwargs["is_active"] = is_active if system_generated is not None: - kwargs['system_generated'] = system_generated + kwargs["system_generated"] = system_generated new_user = models.User(**kwargs) @@ -122,8 +126,9 @@ class AuthStore: await self.async_link_user(new_user, credentials) return new_user - async def async_link_user(self, user: models.User, - credentials: models.Credentials) -> None: + async def async_link_user( + self, user: models.User, credentials: models.Credentials + ) -> None: """Add credentials to an existing user.""" user.credentials.append(credentials) self._async_schedule_save() @@ -139,9 +144,12 @@ class AuthStore: self._async_schedule_save() async def async_update_user( - self, user: models.User, name: Optional[str] = None, - is_active: Optional[bool] = None, - group_ids: Optional[List[str]] = None) -> None: + self, + user: models.User, + name: Optional[str] = None, + is_active: Optional[bool] = None, + group_ids: Optional[List[str]] = None, + ) -> None: """Update a user.""" assert self._groups is not None @@ -156,10 +164,7 @@ class AuthStore: user.groups = groups user.invalidate_permission_cache() - for attr_name, value in ( - ('name', name), - ('is_active', is_active), - ): + for attr_name, value in (("name", name), ("is_active", is_active)): if value is not None: setattr(user, attr_name, value) @@ -175,8 +180,7 @@ class AuthStore: user.is_active = False self._async_schedule_save() - async def async_remove_credentials( - self, credentials: models.Credentials) -> None: + async def async_remove_credentials(self, credentials: models.Credentials) -> None: """Remove credentials.""" if self._users is None: await self._async_load() @@ -197,23 +201,25 @@ class AuthStore: self._async_schedule_save() async def async_create_refresh_token( - self, user: models.User, client_id: Optional[str] = None, - client_name: Optional[str] = None, - client_icon: Optional[str] = None, - token_type: str = models.TOKEN_TYPE_NORMAL, - access_token_expiration: timedelta = ACCESS_TOKEN_EXPIRATION) \ - -> models.RefreshToken: + self, + user: models.User, + client_id: Optional[str] = None, + client_name: Optional[str] = None, + client_icon: Optional[str] = None, + token_type: str = models.TOKEN_TYPE_NORMAL, + access_token_expiration: timedelta = ACCESS_TOKEN_EXPIRATION, + ) -> models.RefreshToken: """Create a new token for a user.""" kwargs = { - 'user': user, - 'client_id': client_id, - 'token_type': token_type, - 'access_token_expiration': access_token_expiration + "user": user, + "client_id": client_id, + "token_type": token_type, + "access_token_expiration": access_token_expiration, } # type: Dict[str, Any] if client_name: - kwargs['client_name'] = client_name + kwargs["client_name"] = client_name if client_icon: - kwargs['client_icon'] = client_icon + kwargs["client_icon"] = client_icon refresh_token = models.RefreshToken(**kwargs) user.refresh_tokens[refresh_token.id] = refresh_token @@ -222,7 +228,8 @@ class AuthStore: return refresh_token async def async_remove_refresh_token( - self, refresh_token: models.RefreshToken) -> None: + self, refresh_token: models.RefreshToken + ) -> None: """Remove a refresh token.""" if self._users is None: await self._async_load() @@ -234,7 +241,8 @@ class AuthStore: break async def async_get_refresh_token( - self, token_id: str) -> Optional[models.RefreshToken]: + self, token_id: str + ) -> Optional[models.RefreshToken]: """Get refresh token by id.""" if self._users is None: await self._async_load() @@ -248,7 +256,8 @@ class AuthStore: return None async def async_get_refresh_token_by_token( - self, token: str) -> Optional[models.RefreshToken]: + self, token: str + ) -> Optional[models.RefreshToken]: """Get refresh token by token.""" if self._users is None: await self._async_load() @@ -265,8 +274,8 @@ class AuthStore: @callback def async_log_refresh_token_usage( - self, refresh_token: models.RefreshToken, - remote_ip: Optional[str] = None) -> None: + self, refresh_token: models.RefreshToken, remote_ip: Optional[str] = None + ) -> None: """Update refresh token last used information.""" refresh_token.last_used_at = dt_util.utcnow() refresh_token.last_used_ip = remote_ip @@ -292,9 +301,7 @@ class AuthStore: if self._users is not None: return - self._perm_lookup = perm_lookup = PermissionLookup( - ent_reg, dev_reg - ) + self._perm_lookup = perm_lookup = PermissionLookup(ent_reg, dev_reg) if data is None: self._set_defaults() @@ -317,24 +324,24 @@ class AuthStore: # prevents crashing if user rolls back HA version after a new property # was added. - for group_dict in data.get('groups', []): + for group_dict in data.get("groups", []): policy = None # type: Optional[PolicyType] - if group_dict['id'] == GROUP_ID_ADMIN: + if group_dict["id"] == GROUP_ID_ADMIN: has_admin_group = True name = GROUP_NAME_ADMIN policy = system_policies.ADMIN_POLICY system_generated = True - elif group_dict['id'] == GROUP_ID_USER: + elif group_dict["id"] == GROUP_ID_USER: has_user_group = True name = GROUP_NAME_USER policy = system_policies.USER_POLICY system_generated = True - elif group_dict['id'] == GROUP_ID_READ_ONLY: + elif group_dict["id"] == GROUP_ID_READ_ONLY: has_read_only_group = True name = GROUP_NAME_READ_ONLY @@ -342,18 +349,18 @@ class AuthStore: system_generated = True else: - name = group_dict['name'] - policy = group_dict.get('policy') + name = group_dict["name"] + policy = group_dict.get("policy") system_generated = False # We don't want groups without a policy that are not system groups # This is part of migrating from state 1 if policy is None: - group_without_policy = group_dict['id'] + group_without_policy = group_dict["id"] continue - groups[group_dict['id']] = models.Group( - id=group_dict['id'], + groups[group_dict["id"]] = models.Group( + id=group_dict["id"], name=name, policy=policy, system_generated=system_generated, @@ -361,8 +368,7 @@ class AuthStore: # If there are no groups, add all existing users to the admin group. # This is part of migrating from state 2 - migrate_users_to_admin_group = (not groups and - group_without_policy is None) + migrate_users_to_admin_group = not groups and group_without_policy is None # If we find a no_policy_group, we need to migrate all users to the # admin group. We only do this if there are no other groups, as is @@ -385,82 +391,86 @@ class AuthStore: user_group = _system_user_group() groups[user_group.id] = user_group - for user_dict in data['users']: + for user_dict in data["users"]: # Collect the users group. user_groups = [] - for group_id in user_dict.get('group_ids', []): + for group_id in user_dict.get("group_ids", []): # This is part of migrating from state 1 if group_id == group_without_policy: group_id = GROUP_ID_ADMIN user_groups.append(groups[group_id]) # This is part of migrating from state 2 - if (not user_dict['system_generated'] and - migrate_users_to_admin_group): + if not user_dict["system_generated"] and migrate_users_to_admin_group: user_groups.append(groups[GROUP_ID_ADMIN]) - users[user_dict['id']] = models.User( - name=user_dict['name'], + users[user_dict["id"]] = models.User( + name=user_dict["name"], groups=user_groups, - id=user_dict['id'], - is_owner=user_dict['is_owner'], - is_active=user_dict['is_active'], - system_generated=user_dict['system_generated'], + id=user_dict["id"], + is_owner=user_dict["is_owner"], + is_active=user_dict["is_active"], + system_generated=user_dict["system_generated"], perm_lookup=perm_lookup, ) - for cred_dict in data['credentials']: - users[cred_dict['user_id']].credentials.append(models.Credentials( - id=cred_dict['id'], - is_new=False, - auth_provider_type=cred_dict['auth_provider_type'], - auth_provider_id=cred_dict['auth_provider_id'], - data=cred_dict['data'], - )) + for cred_dict in data["credentials"]: + users[cred_dict["user_id"]].credentials.append( + models.Credentials( + id=cred_dict["id"], + is_new=False, + auth_provider_type=cred_dict["auth_provider_type"], + auth_provider_id=cred_dict["auth_provider_id"], + data=cred_dict["data"], + ) + ) - for rt_dict in data['refresh_tokens']: + for rt_dict in data["refresh_tokens"]: # Filter out the old keys that don't have jwt_key (pre-0.76) - if 'jwt_key' not in rt_dict: + if "jwt_key" not in rt_dict: continue - created_at = dt_util.parse_datetime(rt_dict['created_at']) + created_at = dt_util.parse_datetime(rt_dict["created_at"]) if created_at is None: getLogger(__name__).error( - 'Ignoring refresh token %(id)s with invalid created_at ' - '%(created_at)s for user_id %(user_id)s', rt_dict) + "Ignoring refresh token %(id)s with invalid created_at " + "%(created_at)s for user_id %(user_id)s", + rt_dict, + ) continue - token_type = rt_dict.get('token_type') + token_type = rt_dict.get("token_type") if token_type is None: - if rt_dict['client_id'] is None: + if rt_dict["client_id"] is None: token_type = models.TOKEN_TYPE_SYSTEM else: token_type = models.TOKEN_TYPE_NORMAL # old refresh_token don't have last_used_at (pre-0.78) - last_used_at_str = rt_dict.get('last_used_at') + last_used_at_str = rt_dict.get("last_used_at") if last_used_at_str: last_used_at = dt_util.parse_datetime(last_used_at_str) else: last_used_at = None token = models.RefreshToken( - id=rt_dict['id'], - user=users[rt_dict['user_id']], - client_id=rt_dict['client_id'], + id=rt_dict["id"], + user=users[rt_dict["user_id"]], + client_id=rt_dict["client_id"], # use dict.get to keep backward compatibility - client_name=rt_dict.get('client_name'), - client_icon=rt_dict.get('client_icon'), + client_name=rt_dict.get("client_name"), + client_icon=rt_dict.get("client_icon"), token_type=token_type, created_at=created_at, access_token_expiration=timedelta( - seconds=rt_dict['access_token_expiration']), - token=rt_dict['token'], - jwt_key=rt_dict['jwt_key'], + seconds=rt_dict["access_token_expiration"] + ), + token=rt_dict["token"], + jwt_key=rt_dict["jwt_key"], last_used_at=last_used_at, - last_used_ip=rt_dict.get('last_used_ip'), + last_used_ip=rt_dict.get("last_used_ip"), ) - users[rt_dict['user_id']].refresh_tokens[token.id] = token + users[rt_dict["user_id"]].refresh_tokens[token.id] = token self._groups = groups self._users = users @@ -481,12 +491,12 @@ class AuthStore: users = [ { - 'id': user.id, - 'group_ids': [group.id for group in user.groups], - 'is_owner': user.is_owner, - 'is_active': user.is_active, - 'name': user.name, - 'system_generated': user.system_generated, + "id": user.id, + "group_ids": [group.id for group in user.groups], + "is_owner": user.is_owner, + "is_active": user.is_active, + "name": user.name, + "system_generated": user.system_generated, } for user in self._users.values() ] @@ -494,23 +504,23 @@ class AuthStore: groups = [] for group in self._groups.values(): g_dict = { - 'id': group.id, + "id": group.id, # Name not read for sys groups. Kept here for backwards compat - 'name': group.name + "name": group.name, } # type: Dict[str, Any] if not group.system_generated: - g_dict['policy'] = group.policy + g_dict["policy"] = group.policy groups.append(g_dict) credentials = [ { - 'id': credential.id, - 'user_id': user.id, - 'auth_provider_type': credential.auth_provider_type, - 'auth_provider_id': credential.auth_provider_id, - 'data': credential.data, + "id": credential.id, + "user_id": user.id, + "auth_provider_type": credential.auth_provider_type, + "auth_provider_id": credential.auth_provider_id, + "data": credential.data, } for user in self._users.values() for credential in user.credentials @@ -518,31 +528,30 @@ class AuthStore: refresh_tokens = [ { - 'id': refresh_token.id, - 'user_id': user.id, - 'client_id': refresh_token.client_id, - 'client_name': refresh_token.client_name, - 'client_icon': refresh_token.client_icon, - 'token_type': refresh_token.token_type, - 'created_at': refresh_token.created_at.isoformat(), - 'access_token_expiration': - refresh_token.access_token_expiration.total_seconds(), - 'token': refresh_token.token, - 'jwt_key': refresh_token.jwt_key, - 'last_used_at': - refresh_token.last_used_at.isoformat() - if refresh_token.last_used_at else None, - 'last_used_ip': refresh_token.last_used_ip, + "id": refresh_token.id, + "user_id": user.id, + "client_id": refresh_token.client_id, + "client_name": refresh_token.client_name, + "client_icon": refresh_token.client_icon, + "token_type": refresh_token.token_type, + "created_at": refresh_token.created_at.isoformat(), + "access_token_expiration": refresh_token.access_token_expiration.total_seconds(), + "token": refresh_token.token, + "jwt_key": refresh_token.jwt_key, + "last_used_at": refresh_token.last_used_at.isoformat() + if refresh_token.last_used_at + else None, + "last_used_ip": refresh_token.last_used_ip, } for user in self._users.values() for refresh_token in user.refresh_tokens.values() ] return { - 'users': users, - 'groups': groups, - 'credentials': credentials, - 'refresh_tokens': refresh_tokens, + "users": users, + "groups": groups, + "credentials": credentials, + "refresh_tokens": refresh_tokens, } def _set_defaults(self) -> None: diff --git a/homeassistant/auth/const.py b/homeassistant/auth/const.py index ef2d54ccbab..5e17e752bdd 100644 --- a/homeassistant/auth/const.py +++ b/homeassistant/auth/const.py @@ -4,6 +4,6 @@ from datetime import timedelta ACCESS_TOKEN_EXPIRATION = timedelta(minutes=30) MFA_SESSION_EXPIRATION = timedelta(minutes=5) -GROUP_ID_ADMIN = 'system-admin' -GROUP_ID_USER = 'system-users' -GROUP_ID_READ_ONLY = 'system-read-only' +GROUP_ID_ADMIN = "system-admin" +GROUP_ID_USER = "system-users" +GROUP_ID_READ_ONLY = "system-read-only" diff --git a/homeassistant/auth/mfa_modules/__init__.py b/homeassistant/auth/mfa_modules/__init__.py index 3313063679d..0706efbbb7a 100644 --- a/homeassistant/auth/mfa_modules/__init__.py +++ b/homeassistant/auth/mfa_modules/__init__.py @@ -15,14 +15,17 @@ from homeassistant.util.decorator import Registry MULTI_FACTOR_AUTH_MODULES = Registry() -MULTI_FACTOR_AUTH_MODULE_SCHEMA = vol.Schema({ - vol.Required(CONF_TYPE): str, - vol.Optional(CONF_NAME): str, - # Specify ID if you have two mfa auth module for same type. - vol.Optional(CONF_ID): str, -}, extra=vol.ALLOW_EXTRA) +MULTI_FACTOR_AUTH_MODULE_SCHEMA = vol.Schema( + { + vol.Required(CONF_TYPE): str, + vol.Optional(CONF_NAME): str, + # Specify ID if you have two mfa auth module for same type. + vol.Optional(CONF_ID): str, + }, + extra=vol.ALLOW_EXTRA, +) -DATA_REQS = 'mfa_auth_module_reqs_processed' +DATA_REQS = "mfa_auth_module_reqs_processed" _LOGGER = logging.getLogger(__name__) @@ -30,7 +33,7 @@ _LOGGER = logging.getLogger(__name__) class MultiFactorAuthModule: """Multi-factor Auth Module of validation function.""" - DEFAULT_TITLE = 'Unnamed auth module' + DEFAULT_TITLE = "Unnamed auth module" MAX_RETRY_TIME = 3 def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None: @@ -63,7 +66,7 @@ class MultiFactorAuthModule: """Return a voluptuous schema to define mfa auth module's input.""" raise NotImplementedError - async def async_setup_flow(self, user_id: str) -> 'SetupFlow': + async def async_setup_flow(self, user_id: str) -> "SetupFlow": """Return a data entry flow handler for setup module. Mfa module should extend SetupFlow @@ -82,8 +85,7 @@ class MultiFactorAuthModule: """Return whether user is setup.""" raise NotImplementedError - async def async_validate( - self, user_id: str, user_input: Dict[str, Any]) -> bool: + async def async_validate(self, user_id: str, user_input: Dict[str, Any]) -> bool: """Return True if validation passed.""" raise NotImplementedError @@ -91,17 +93,17 @@ class MultiFactorAuthModule: class SetupFlow(data_entry_flow.FlowHandler): """Handler for the setup flow.""" - def __init__(self, auth_module: MultiFactorAuthModule, - setup_schema: vol.Schema, - user_id: str) -> None: + def __init__( + self, auth_module: MultiFactorAuthModule, setup_schema: vol.Schema, user_id: str + ) -> None: """Initialize the setup flow.""" self._auth_module = auth_module self._setup_schema = setup_schema self._user_id = user_id async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None) \ - -> Dict[str, Any]: + self, user_input: Optional[Dict[str, str]] = None + ) -> Dict[str, Any]: """Handle the first step of setup flow. Return self.async_show_form(step_id='init') if user_input is None. @@ -110,23 +112,19 @@ class SetupFlow(data_entry_flow.FlowHandler): errors = {} # type: Dict[str, str] if user_input: - result = await self._auth_module.async_setup_user( - self._user_id, user_input) + result = await self._auth_module.async_setup_user(self._user_id, user_input) return self.async_create_entry( - title=self._auth_module.name, - data={'result': result} + title=self._auth_module.name, data={"result": result} ) return self.async_show_form( - step_id='init', - data_schema=self._setup_schema, - errors=errors + step_id="init", data_schema=self._setup_schema, errors=errors ) async def auth_mfa_module_from_config( - hass: HomeAssistant, config: Dict[str, Any]) \ - -> MultiFactorAuthModule: + hass: HomeAssistant, config: Dict[str, Any] +) -> MultiFactorAuthModule: """Initialize an auth module from a config.""" module_name = config[CONF_TYPE] module = await _load_mfa_module(hass, module_name) @@ -134,26 +132,29 @@ async def auth_mfa_module_from_config( try: config = module.CONFIG_SCHEMA(config) # type: ignore except vol.Invalid as err: - _LOGGER.error('Invalid configuration for multi-factor module %s: %s', - module_name, humanize_error(config, err)) + _LOGGER.error( + "Invalid configuration for multi-factor module %s: %s", + module_name, + humanize_error(config, err), + ) raise return MULTI_FACTOR_AUTH_MODULES[module_name](hass, config) # type: ignore -async def _load_mfa_module(hass: HomeAssistant, module_name: str) \ - -> types.ModuleType: +async def _load_mfa_module(hass: HomeAssistant, module_name: str) -> types.ModuleType: """Load an mfa auth module.""" - module_path = 'homeassistant.auth.mfa_modules.{}'.format(module_name) + module_path = "homeassistant.auth.mfa_modules.{}".format(module_name) try: module = importlib.import_module(module_path) except ImportError as err: - _LOGGER.error('Unable to load mfa module %s: %s', module_name, err) - raise HomeAssistantError('Unable to load mfa module {}: {}'.format( - module_name, err)) + _LOGGER.error("Unable to load mfa module %s: %s", module_name, err) + raise HomeAssistantError( + "Unable to load mfa module {}: {}".format(module_name, err) + ) - if hass.config.skip_pip or not hasattr(module, 'REQUIREMENTS'): + if hass.config.skip_pip or not hasattr(module, "REQUIREMENTS"): return module processed = hass.data.get(DATA_REQS) @@ -164,12 +165,13 @@ async def _load_mfa_module(hass: HomeAssistant, module_name: str) \ # https://github.com/python/mypy/issues/1424 req_success = await requirements.async_process_requirements( - hass, module_path, module.REQUIREMENTS) # type: ignore + hass, module_path, module.REQUIREMENTS + ) # type: ignore if not req_success: raise HomeAssistantError( - 'Unable to process requirements of mfa module {}'.format( - module_name)) + "Unable to process requirements of mfa module {}".format(module_name) + ) processed.add(module_name) return module diff --git a/homeassistant/auth/mfa_modules/insecure_example.py b/homeassistant/auth/mfa_modules/insecure_example.py index 9804cbcf635..a3f0d58c6b3 100644 --- a/homeassistant/auth/mfa_modules/insecure_example.py +++ b/homeassistant/auth/mfa_modules/insecure_example.py @@ -6,39 +6,45 @@ import voluptuous as vol from homeassistant.core import HomeAssistant -from . import MultiFactorAuthModule, MULTI_FACTOR_AUTH_MODULES, \ - MULTI_FACTOR_AUTH_MODULE_SCHEMA, SetupFlow +from . import ( + MultiFactorAuthModule, + MULTI_FACTOR_AUTH_MODULES, + MULTI_FACTOR_AUTH_MODULE_SCHEMA, + SetupFlow, +) -CONFIG_SCHEMA = MULTI_FACTOR_AUTH_MODULE_SCHEMA.extend({ - vol.Required('data'): [vol.Schema({ - vol.Required('user_id'): str, - vol.Required('pin'): str, - })] -}, extra=vol.PREVENT_EXTRA) +CONFIG_SCHEMA = MULTI_FACTOR_AUTH_MODULE_SCHEMA.extend( + { + vol.Required("data"): [ + vol.Schema({vol.Required("user_id"): str, vol.Required("pin"): str}) + ] + }, + extra=vol.PREVENT_EXTRA, +) _LOGGER = logging.getLogger(__name__) -@MULTI_FACTOR_AUTH_MODULES.register('insecure_example') +@MULTI_FACTOR_AUTH_MODULES.register("insecure_example") class InsecureExampleModule(MultiFactorAuthModule): """Example auth module validate pin.""" - DEFAULT_TITLE = 'Insecure Personal Identify Number' + DEFAULT_TITLE = "Insecure Personal Identify Number" def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None: """Initialize the user data store.""" super().__init__(hass, config) - self._data = config['data'] + self._data = config["data"] @property def input_schema(self) -> vol.Schema: """Validate login flow input data.""" - return vol.Schema({'pin': str}) + return vol.Schema({"pin": str}) @property def setup_schema(self) -> vol.Schema: """Validate async_setup_user input data.""" - return vol.Schema({'pin': str}) + return vol.Schema({"pin": str}) async def async_setup_flow(self, user_id: str) -> SetupFlow: """Return a data entry flow handler for setup module. @@ -50,21 +56,21 @@ class InsecureExampleModule(MultiFactorAuthModule): async def async_setup_user(self, user_id: str, setup_data: Any) -> Any: """Set up user to use mfa module.""" # data shall has been validate in caller - pin = setup_data['pin'] + pin = setup_data["pin"] for data in self._data: - if data['user_id'] == user_id: + if data["user_id"] == user_id: # already setup, override - data['pin'] = pin + data["pin"] = pin return - self._data.append({'user_id': user_id, 'pin': pin}) + self._data.append({"user_id": user_id, "pin": pin}) async def async_depose_user(self, user_id: str) -> None: """Remove user from mfa module.""" found = None for data in self._data: - if data['user_id'] == user_id: + if data["user_id"] == user_id: found = data break if found: @@ -73,17 +79,16 @@ class InsecureExampleModule(MultiFactorAuthModule): async def async_is_user_setup(self, user_id: str) -> bool: """Return whether user is setup.""" for data in self._data: - if data['user_id'] == user_id: + if data["user_id"] == user_id: return True return False - async def async_validate( - self, user_id: str, user_input: Dict[str, Any]) -> bool: + async def async_validate(self, user_id: str, user_input: Dict[str, Any]) -> bool: """Return True if validation passed.""" for data in self._data: - if data['user_id'] == user_id: + if data["user_id"] == user_id: # user_input has been validate in caller - if data['pin'] == user_input['pin']: + if data["pin"] == user_input["pin"]: return True return False diff --git a/homeassistant/auth/mfa_modules/notify.py b/homeassistant/auth/mfa_modules/notify.py index 396a0fb8d3f..4a41ff03ef6 100644 --- a/homeassistant/auth/mfa_modules/notify.py +++ b/homeassistant/auth/mfa_modules/notify.py @@ -15,26 +15,32 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ServiceNotFound from homeassistant.helpers import config_validation as cv -from . import MultiFactorAuthModule, MULTI_FACTOR_AUTH_MODULES, \ - MULTI_FACTOR_AUTH_MODULE_SCHEMA, SetupFlow +from . import ( + MultiFactorAuthModule, + MULTI_FACTOR_AUTH_MODULES, + MULTI_FACTOR_AUTH_MODULE_SCHEMA, + SetupFlow, +) -REQUIREMENTS = ['pyotp==2.2.7'] +REQUIREMENTS = ["pyotp==2.2.7"] -CONF_MESSAGE = 'message' +CONF_MESSAGE = "message" -CONFIG_SCHEMA = MULTI_FACTOR_AUTH_MODULE_SCHEMA.extend({ - vol.Optional(CONF_INCLUDE): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_EXCLUDE): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_MESSAGE, - default='{} is your Home Assistant login code'): str -}, extra=vol.PREVENT_EXTRA) +CONFIG_SCHEMA = MULTI_FACTOR_AUTH_MODULE_SCHEMA.extend( + { + vol.Optional(CONF_INCLUDE): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_EXCLUDE): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_MESSAGE, default="{} is your Home Assistant login code"): str, + }, + extra=vol.PREVENT_EXTRA, +) STORAGE_VERSION = 1 -STORAGE_KEY = 'auth_module.notify' -STORAGE_USERS = 'users' -STORAGE_USER_ID = 'user_id' +STORAGE_KEY = "auth_module.notify" +STORAGE_USERS = "users" +STORAGE_USER_ID = "user_id" -INPUT_FIELD_CODE = 'code' +INPUT_FIELD_CODE = "code" _LOGGER = logging.getLogger(__name__) @@ -42,24 +48,28 @@ _LOGGER = logging.getLogger(__name__) def _generate_secret() -> str: """Generate a secret.""" import pyotp + return str(pyotp.random_base32()) def _generate_random() -> int: """Generate a 8 digit number.""" import pyotp - return int(pyotp.random_base32(length=8, chars=list('1234567890'))) + + return int(pyotp.random_base32(length=8, chars=list("1234567890"))) def _generate_otp(secret: str, count: int) -> str: """Generate one time password.""" import pyotp + return str(pyotp.HOTP(secret).at(count)) def _verify_otp(secret: str, otp: str, count: int) -> bool: """Verify one time password.""" import pyotp + return bool(pyotp.HOTP(secret).verify(otp, count)) @@ -67,7 +77,7 @@ def _verify_otp(secret: str, otp: str, count: int) -> bool: class NotifySetting: """Store notify setting for one user.""" - secret = attr.ib(type=str, factory=_generate_secret) # not persistent + secret = attr.ib(type=str, factory=_generate_secret) # not persistent counter = attr.ib(type=int, factory=_generate_random) # not persistent notify_service = attr.ib(type=Optional[str], default=None) target = attr.ib(type=Optional[str], default=None) @@ -76,18 +86,19 @@ class NotifySetting: _UsersDict = Dict[str, NotifySetting] -@MULTI_FACTOR_AUTH_MODULES.register('notify') +@MULTI_FACTOR_AUTH_MODULES.register("notify") class NotifyAuthModule(MultiFactorAuthModule): """Auth module send hmac-based one time password by notify service.""" - DEFAULT_TITLE = 'Notify One-Time Password' + DEFAULT_TITLE = "Notify One-Time Password" def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None: """Initialize the user data store.""" super().__init__(hass, config) self._user_settings = None # type: Optional[_UsersDict] self._user_store = hass.helpers.storage.Store( - STORAGE_VERSION, STORAGE_KEY, private=True) + STORAGE_VERSION, STORAGE_KEY, private=True + ) self._include = config.get(CONF_INCLUDE, []) self._exclude = config.get(CONF_EXCLUDE, []) self._message_template = config[CONF_MESSAGE] @@ -119,22 +130,27 @@ class NotifyAuthModule(MultiFactorAuthModule): if self._user_settings is None: return - await self._user_store.async_save({STORAGE_USERS: { - user_id: attr.asdict( - notify_setting, filter=attr.filters.exclude( - attr.fields(NotifySetting).secret, - attr.fields(NotifySetting).counter, - )) - for user_id, notify_setting - in self._user_settings.items() - }}) + await self._user_store.async_save( + { + STORAGE_USERS: { + user_id: attr.asdict( + notify_setting, + filter=attr.filters.exclude( + attr.fields(NotifySetting).secret, + attr.fields(NotifySetting).counter, + ), + ) + for user_id, notify_setting in self._user_settings.items() + } + } + ) @callback def aync_get_available_notify_services(self) -> List[str]: """Return list of notify services.""" unordered_services = set() - for service in self.hass.services.async_services().get('notify', {}): + for service in self.hass.services.async_services().get("notify", {}): if service not in self._exclude: unordered_services.add(service) @@ -149,8 +165,8 @@ class NotifyAuthModule(MultiFactorAuthModule): Mfa module should extend SetupFlow """ return NotifySetupFlow( - self, self.input_schema, user_id, - self.aync_get_available_notify_services()) + self, self.input_schema, user_id, self.aync_get_available_notify_services() + ) async def async_setup_user(self, user_id: str, setup_data: Any) -> Any: """Set up auth module for user.""" @@ -159,8 +175,8 @@ class NotifyAuthModule(MultiFactorAuthModule): assert self._user_settings is not None self._user_settings[user_id] = NotifySetting( - notify_service=setup_data.get('notify_service'), - target=setup_data.get('target'), + notify_service=setup_data.get("notify_service"), + target=setup_data.get("target"), ) await self._async_save() @@ -182,8 +198,7 @@ class NotifyAuthModule(MultiFactorAuthModule): return user_id in self._user_settings - async def async_validate( - self, user_id: str, user_input: Dict[str, Any]) -> bool: + async def async_validate(self, user_id: str, user_input: Dict[str, Any]) -> bool: """Return True if validation passed.""" if self._user_settings is None: await self._async_load() @@ -195,9 +210,11 @@ class NotifyAuthModule(MultiFactorAuthModule): # user_input has been validate in caller return await self.hass.async_add_executor_job( - _verify_otp, notify_setting.secret, - user_input.get(INPUT_FIELD_CODE, ''), - notify_setting.counter) + _verify_otp, + notify_setting.secret, + user_input.get(INPUT_FIELD_CODE, ""), + notify_setting.counter, + ) async def async_initialize_login_mfa_step(self, user_id: str) -> None: """Generate code and notify user.""" @@ -207,7 +224,7 @@ class NotifyAuthModule(MultiFactorAuthModule): notify_setting = self._user_settings.get(user_id, None) if notify_setting is None: - raise ValueError('Cannot find user_id') + raise ValueError("Cannot find user_id") def generate_secret_and_one_time_password() -> str: """Generate and send one time password.""" @@ -215,11 +232,11 @@ class NotifyAuthModule(MultiFactorAuthModule): # secret and counter are not persistent notify_setting.secret = _generate_secret() notify_setting.counter = _generate_random() - return _generate_otp( - notify_setting.secret, notify_setting.counter) + return _generate_otp(notify_setting.secret, notify_setting.counter) code = await self.hass.async_add_executor_job( - generate_secret_and_one_time_password) + generate_secret_and_one_time_password + ) await self.async_notify_user(user_id, code) @@ -231,105 +248,107 @@ class NotifyAuthModule(MultiFactorAuthModule): notify_setting = self._user_settings.get(user_id, None) if notify_setting is None: - _LOGGER.error('Cannot find user %s', user_id) + _LOGGER.error("Cannot find user %s", user_id) return - await self.async_notify( # type: ignore - code, notify_setting.notify_service, notify_setting.target) + await self.async_notify( # type: ignore + code, notify_setting.notify_service, notify_setting.target + ) - async def async_notify(self, code: str, notify_service: str, - target: Optional[str] = None) -> None: + async def async_notify( + self, code: str, notify_service: str, target: Optional[str] = None + ) -> None: """Send code by notify service.""" - data = {'message': self._message_template.format(code)} + data = {"message": self._message_template.format(code)} if target: - data['target'] = [target] + data["target"] = [target] - await self.hass.services.async_call('notify', notify_service, data) + await self.hass.services.async_call("notify", notify_service, data) class NotifySetupFlow(SetupFlow): """Handler for the setup flow.""" - def __init__(self, auth_module: NotifyAuthModule, - setup_schema: vol.Schema, - user_id: str, - available_notify_services: List[str]) -> None: + def __init__( + self, + auth_module: NotifyAuthModule, + setup_schema: vol.Schema, + user_id: str, + available_notify_services: List[str], + ) -> None: """Initialize the setup flow.""" super().__init__(auth_module, setup_schema, user_id) # to fix typing complaint self._auth_module = auth_module # type: NotifyAuthModule self._available_notify_services = available_notify_services self._secret = None # type: Optional[str] - self._count = None # type: Optional[int] + self._count = None # type: Optional[int] self._notify_service = None # type: Optional[str] self._target = None # type: Optional[str] async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None) \ - -> Dict[str, Any]: + self, user_input: Optional[Dict[str, str]] = None + ) -> Dict[str, Any]: """Let user select available notify services.""" errors = {} # type: Dict[str, str] hass = self._auth_module.hass if user_input: - self._notify_service = user_input['notify_service'] - self._target = user_input.get('target') + self._notify_service = user_input["notify_service"] + self._target = user_input.get("target") self._secret = await hass.async_add_executor_job(_generate_secret) self._count = await hass.async_add_executor_job(_generate_random) return await self.async_step_setup() if not self._available_notify_services: - return self.async_abort(reason='no_available_service') + return self.async_abort(reason="no_available_service") schema = OrderedDict() # type: Dict[str, Any] - schema['notify_service'] = vol.In(self._available_notify_services) - schema['target'] = vol.Optional(str) + schema["notify_service"] = vol.In(self._available_notify_services) + schema["target"] = vol.Optional(str) return self.async_show_form( - step_id='init', - data_schema=vol.Schema(schema), - errors=errors + step_id="init", data_schema=vol.Schema(schema), errors=errors ) async def async_step_setup( - self, user_input: Optional[Dict[str, str]] = None) \ - -> Dict[str, Any]: + self, user_input: Optional[Dict[str, str]] = None + ) -> Dict[str, Any]: """Verify user can recevie one-time password.""" errors = {} # type: Dict[str, str] hass = self._auth_module.hass if user_input: verified = await hass.async_add_executor_job( - _verify_otp, self._secret, user_input['code'], self._count) + _verify_otp, self._secret, user_input["code"], self._count + ) if verified: await self._auth_module.async_setup_user( - self._user_id, { - 'notify_service': self._notify_service, - 'target': self._target, - }) - return self.async_create_entry( - title=self._auth_module.name, - data={} + self._user_id, + {"notify_service": self._notify_service, "target": self._target}, ) + return self.async_create_entry(title=self._auth_module.name, data={}) - errors['base'] = 'invalid_code' + errors["base"] = "invalid_code" # generate code every time, no retry logic assert self._secret and self._count code = await hass.async_add_executor_job( - _generate_otp, self._secret, self._count) + _generate_otp, self._secret, self._count + ) assert self._notify_service try: await self._auth_module.async_notify( - code, self._notify_service, self._target) + code, self._notify_service, self._target + ) except ServiceNotFound: - return self.async_abort(reason='notify_service_not_exist') + return self.async_abort(reason="notify_service_not_exist") return self.async_show_form( - step_id='setup', + step_id="setup", data_schema=self._setup_schema, - description_placeholders={'notify_service': self._notify_service}, + description_placeholders={"notify_service": self._notify_service}, errors=errors, ) diff --git a/homeassistant/auth/mfa_modules/totp.py b/homeassistant/auth/mfa_modules/totp.py index bb07d9e479f..22d153e3420 100644 --- a/homeassistant/auth/mfa_modules/totp.py +++ b/homeassistant/auth/mfa_modules/totp.py @@ -9,23 +9,26 @@ import voluptuous as vol from homeassistant.auth.models import User from homeassistant.core import HomeAssistant -from . import MultiFactorAuthModule, MULTI_FACTOR_AUTH_MODULES, \ - MULTI_FACTOR_AUTH_MODULE_SCHEMA, SetupFlow +from . import ( + MultiFactorAuthModule, + MULTI_FACTOR_AUTH_MODULES, + MULTI_FACTOR_AUTH_MODULE_SCHEMA, + SetupFlow, +) -REQUIREMENTS = ['pyotp==2.2.7', 'PyQRCode==1.2.1'] +REQUIREMENTS = ["pyotp==2.2.7", "PyQRCode==1.2.1"] -CONFIG_SCHEMA = MULTI_FACTOR_AUTH_MODULE_SCHEMA.extend({ -}, extra=vol.PREVENT_EXTRA) +CONFIG_SCHEMA = MULTI_FACTOR_AUTH_MODULE_SCHEMA.extend({}, extra=vol.PREVENT_EXTRA) STORAGE_VERSION = 1 -STORAGE_KEY = 'auth_module.totp' -STORAGE_USERS = 'users' -STORAGE_USER_ID = 'user_id' -STORAGE_OTA_SECRET = 'ota_secret' +STORAGE_KEY = "auth_module.totp" +STORAGE_USERS = "users" +STORAGE_USER_ID = "user_id" +STORAGE_OTA_SECRET = "ota_secret" -INPUT_FIELD_CODE = 'code' +INPUT_FIELD_CODE = "code" -DUMMY_SECRET = 'FPPTH34D4E3MI2HG' +DUMMY_SECRET = "FPPTH34D4E3MI2HG" _LOGGER = logging.getLogger(__name__) @@ -38,10 +41,15 @@ def _generate_qr_code(data: str) -> str: with BytesIO() as buffer: qr_code.svg(file=buffer, scale=4) - return '{}'.format( - buffer.getvalue().decode("ascii").replace('\n', '') - .replace('' - '' + ' Tuple[str, str, str]: ota_secret = pyotp.random_base32() url = pyotp.totp.TOTP(ota_secret).provisioning_uri( - username, issuer_name="Home Assistant") + username, issuer_name="Home Assistant" + ) image = _generate_qr_code(url) return ota_secret, url, image -@MULTI_FACTOR_AUTH_MODULES.register('totp') +@MULTI_FACTOR_AUTH_MODULES.register("totp") class TotpAuthModule(MultiFactorAuthModule): """Auth module validate time-based one time password.""" - DEFAULT_TITLE = 'Time-based One Time Password' + DEFAULT_TITLE = "Time-based One Time Password" MAX_RETRY_TIME = 5 def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None: @@ -68,7 +77,8 @@ class TotpAuthModule(MultiFactorAuthModule): super().__init__(hass, config) self._users = None # type: Optional[Dict[str, str]] self._user_store = hass.helpers.storage.Store( - STORAGE_VERSION, STORAGE_KEY, private=True) + STORAGE_VERSION, STORAGE_KEY, private=True + ) self._init_lock = asyncio.Lock() @property @@ -93,14 +103,13 @@ class TotpAuthModule(MultiFactorAuthModule): """Save data.""" await self._user_store.async_save({STORAGE_USERS: self._users}) - def _add_ota_secret(self, user_id: str, - secret: Optional[str] = None) -> str: + def _add_ota_secret(self, user_id: str, secret: Optional[str] = None) -> str: """Create a ota_secret for user.""" import pyotp ota_secret = secret or pyotp.random_base32() # type: str - self._users[user_id] = ota_secret # type: ignore + self._users[user_id] = ota_secret # type: ignore return ota_secret async def async_setup_flow(self, user_id: str) -> SetupFlow: @@ -108,7 +117,7 @@ class TotpAuthModule(MultiFactorAuthModule): Mfa module should extend SetupFlow """ - user = await self.hass.auth.async_get_user(user_id) # type: ignore + user = await self.hass.auth.async_get_user(user_id) # type: ignore return TotpSetupFlow(self, self.input_schema, user) async def async_setup_user(self, user_id: str, setup_data: Any) -> str: @@ -117,7 +126,8 @@ class TotpAuthModule(MultiFactorAuthModule): await self._async_load() result = await self.hass.async_add_executor_job( - self._add_ota_secret, user_id, setup_data.get('secret')) + self._add_ota_secret, user_id, setup_data.get("secret") + ) await self._async_save() return result @@ -127,7 +137,7 @@ class TotpAuthModule(MultiFactorAuthModule): if self._users is None: await self._async_load() - if self._users.pop(user_id, None): # type: ignore + if self._users.pop(user_id, None): # type: ignore await self._async_save() async def async_is_user_setup(self, user_id: str) -> bool: @@ -135,10 +145,9 @@ class TotpAuthModule(MultiFactorAuthModule): if self._users is None: await self._async_load() - return user_id in self._users # type: ignore + return user_id in self._users # type: ignore - async def async_validate( - self, user_id: str, user_input: Dict[str, Any]) -> bool: + async def async_validate(self, user_id: str, user_input: Dict[str, Any]) -> bool: """Return True if validation passed.""" if self._users is None: await self._async_load() @@ -146,7 +155,8 @@ class TotpAuthModule(MultiFactorAuthModule): # user_input has been validate in caller # set INPUT_FIELD_CODE as vol.Required is not user friendly return await self.hass.async_add_executor_job( - self._validate_2fa, user_id, user_input.get(INPUT_FIELD_CODE, '')) + self._validate_2fa, user_id, user_input.get(INPUT_FIELD_CODE, "") + ) def _validate_2fa(self, user_id: str, code: str) -> bool: """Validate two factor authentication code.""" @@ -165,9 +175,9 @@ class TotpAuthModule(MultiFactorAuthModule): class TotpSetupFlow(SetupFlow): """Handler for the setup flow.""" - def __init__(self, auth_module: TotpAuthModule, - setup_schema: vol.Schema, - user: User) -> None: + def __init__( + self, auth_module: TotpAuthModule, setup_schema: vol.Schema, user: User + ) -> None: """Initialize the setup flow.""" super().__init__(auth_module, setup_schema, user.id) # to fix typing complaint @@ -178,8 +188,8 @@ class TotpSetupFlow(SetupFlow): self._image = None # type Optional[str] async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None) \ - -> Dict[str, Any]: + self, user_input: Optional[Dict[str, str]] = None + ) -> Dict[str, Any]: """Handle the first step of setup flow. Return self.async_show_form(step_id='init') if user_input is None. @@ -191,30 +201,31 @@ class TotpSetupFlow(SetupFlow): if user_input: verified = await self.hass.async_add_executor_job( # type: ignore - pyotp.TOTP(self._ota_secret).verify, user_input['code']) + pyotp.TOTP(self._ota_secret).verify, user_input["code"] + ) if verified: result = await self._auth_module.async_setup_user( - self._user_id, {'secret': self._ota_secret}) + self._user_id, {"secret": self._ota_secret} + ) return self.async_create_entry( - title=self._auth_module.name, - data={'result': result} + title=self._auth_module.name, data={"result": result} ) - errors['base'] = 'invalid_code' + errors["base"] = "invalid_code" else: hass = self._auth_module.hass - self._ota_secret, self._url, self._image = \ - await hass.async_add_executor_job( # type: ignore - _generate_secret_and_qr_code, str(self._user.name)) + self._ota_secret, self._url, self._image = await hass.async_add_executor_job( # type: ignore + _generate_secret_and_qr_code, str(self._user.name) + ) return self.async_show_form( - step_id='init', + step_id="init", data_schema=self._setup_schema, description_placeholders={ - 'code': self._ota_secret, - 'url': self._url, - 'qr_code': self._image + "code": self._ota_secret, + "url": self._url, + "qr_code": self._image, }, - errors=errors + errors=errors, ) diff --git a/homeassistant/auth/models.py b/homeassistant/auth/models.py index 588d80047be..533d7672ee4 100644 --- a/homeassistant/auth/models.py +++ b/homeassistant/auth/models.py @@ -11,9 +11,9 @@ from . import permissions as perm_mdl from .const import GROUP_ID_ADMIN from .util import generate_secret -TOKEN_TYPE_NORMAL = 'normal' -TOKEN_TYPE_SYSTEM = 'system' -TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN = 'long_lived_access_token' +TOKEN_TYPE_NORMAL = "normal" +TOKEN_TYPE_SYSTEM = "system" +TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN = "long_lived_access_token" @attr.s(slots=True) @@ -32,7 +32,7 @@ class User: name = attr.ib(type=str) # type: Optional[str] perm_lookup = attr.ib( - type=perm_mdl.PermissionLookup, cmp=False, + type=perm_mdl.PermissionLookup, cmp=False ) # type: perm_mdl.PermissionLookup id = attr.ib(type=str, factory=lambda: uuid.uuid4().hex) is_owner = attr.ib(type=bool, default=False) @@ -42,9 +42,7 @@ class User: groups = attr.ib(type=List, factory=list, cmp=False) # type: List[Group] # List of credentials of a user. - credentials = attr.ib( - type=list, factory=list, cmp=False - ) # type: List[Credentials] + credentials = attr.ib(type=list, factory=list, cmp=False) # type: List[Credentials] # Tokens associated with a user. refresh_tokens = attr.ib( @@ -52,10 +50,7 @@ class User: ) # type: Dict[str, RefreshToken] _permissions = attr.ib( - type=Optional[perm_mdl.PolicyPermissions], - init=False, - cmp=False, - default=None, + type=Optional[perm_mdl.PolicyPermissions], init=False, cmp=False, default=None ) @property @@ -68,9 +63,9 @@ class User: return self._permissions self._permissions = perm_mdl.PolicyPermissions( - perm_mdl.merge_policies([ - group.policy for group in self.groups]), - self.perm_lookup) + perm_mdl.merge_policies([group.policy for group in self.groups]), + self.perm_lookup, + ) return self._permissions @@ -80,8 +75,7 @@ class User: if self.is_owner: return True - return self.is_active and any( - gr.id == GROUP_ID_ADMIN for gr in self.groups) + return self.is_active and any(gr.id == GROUP_ID_ADMIN for gr in self.groups) def invalidate_permission_cache(self) -> None: """Invalidate permission cache.""" @@ -97,10 +91,13 @@ class RefreshToken: access_token_expiration = attr.ib(type=timedelta) client_name = attr.ib(type=Optional[str], default=None) client_icon = attr.ib(type=Optional[str], default=None) - token_type = attr.ib(type=str, default=TOKEN_TYPE_NORMAL, - validator=attr.validators.in_(( - TOKEN_TYPE_NORMAL, TOKEN_TYPE_SYSTEM, - TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN))) + token_type = attr.ib( + type=str, + default=TOKEN_TYPE_NORMAL, + validator=attr.validators.in_( + (TOKEN_TYPE_NORMAL, TOKEN_TYPE_SYSTEM, TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN) + ), + ) id = attr.ib(type=str, factory=lambda: uuid.uuid4().hex) created_at = attr.ib(type=datetime, factory=dt_util.utcnow) token = attr.ib(type=str, factory=lambda: generate_secret(64)) @@ -124,5 +121,4 @@ class Credentials: is_new = attr.ib(type=bool, default=True) -UserMeta = NamedTuple("UserMeta", - [('name', Optional[str]), ('is_active', bool)]) +UserMeta = NamedTuple("UserMeta", [("name", Optional[str]), ("is_active", bool)]) diff --git a/homeassistant/auth/permissions/__init__.py b/homeassistant/auth/permissions/__init__.py index 0079f11447b..5680b0aecb2 100644 --- a/homeassistant/auth/permissions/__init__.py +++ b/homeassistant/auth/permissions/__init__.py @@ -1,8 +1,17 @@ """Permissions for Home Assistant.""" import logging from typing import ( # noqa: F401 - cast, Any, Callable, Dict, List, Mapping, Set, Tuple, Union, - TYPE_CHECKING) + cast, + Any, + Callable, + Dict, + List, + Mapping, + Set, + Tuple, + Union, + TYPE_CHECKING, +) import voluptuous as vol @@ -14,9 +23,7 @@ from .merge import merge_policies # noqa from .util import test_all -POLICY_SCHEMA = vol.Schema({ - vol.Optional(CAT_ENTITIES): ENTITY_POLICY_SCHEMA -}) +POLICY_SCHEMA = vol.Schema({vol.Optional(CAT_ENTITIES): ENTITY_POLICY_SCHEMA}) _LOGGER = logging.getLogger(__name__) @@ -47,8 +54,7 @@ class AbstractPermissions: class PolicyPermissions(AbstractPermissions): """Handle permissions.""" - def __init__(self, policy: PolicyType, - perm_lookup: PermissionLookup) -> None: + def __init__(self, policy: PolicyType, perm_lookup: PermissionLookup) -> None: """Initialize the permission class.""" self._policy = policy self._perm_lookup = perm_lookup @@ -59,14 +65,12 @@ class PolicyPermissions(AbstractPermissions): def _entity_func(self) -> Callable[[str, str], bool]: """Return a function that can test entity access.""" - return compile_entities(self._policy.get(CAT_ENTITIES), - self._perm_lookup) + return compile_entities(self._policy.get(CAT_ENTITIES), self._perm_lookup) def __eq__(self, other: Any) -> bool: """Equals check.""" # pylint: disable=protected-access - return (isinstance(other, PolicyPermissions) and - other._policy == self._policy) + return isinstance(other, PolicyPermissions) and other._policy == self._policy class _OwnerPermissions(AbstractPermissions): diff --git a/homeassistant/auth/permissions/const.py b/homeassistant/auth/permissions/const.py index d390d010dee..e6c44036a7e 100644 --- a/homeassistant/auth/permissions/const.py +++ b/homeassistant/auth/permissions/const.py @@ -1,8 +1,8 @@ """Permission constants.""" -CAT_ENTITIES = 'entities' -CAT_CONFIG_ENTRIES = 'config_entries' -SUBCAT_ALL = 'all' +CAT_ENTITIES = "entities" +CAT_CONFIG_ENTRIES = "config_entries" +SUBCAT_ALL = "all" -POLICY_READ = 'read' -POLICY_CONTROL = 'control' -POLICY_EDIT = 'edit' +POLICY_READ = "read" +POLICY_CONTROL = "control" +POLICY_EDIT = "edit" diff --git a/homeassistant/auth/permissions/entities.py b/homeassistant/auth/permissions/entities.py index 3d7fc80307e..2708693743a 100644 --- a/homeassistant/auth/permissions/entities.py +++ b/homeassistant/auth/permissions/entities.py @@ -7,51 +7,59 @@ import voluptuous as vol from .const import SUBCAT_ALL, POLICY_READ, POLICY_CONTROL, POLICY_EDIT from .models import PermissionLookup from .types import CategoryType, SubCategoryDict, ValueType + # pylint: disable=unused-import from .util import SubCatLookupType, lookup_all, compile_policy # noqa -SINGLE_ENTITY_SCHEMA = vol.Any(True, vol.Schema({ - vol.Optional(POLICY_READ): True, - vol.Optional(POLICY_CONTROL): True, - vol.Optional(POLICY_EDIT): True, -})) +SINGLE_ENTITY_SCHEMA = vol.Any( + True, + vol.Schema( + { + vol.Optional(POLICY_READ): True, + vol.Optional(POLICY_CONTROL): True, + vol.Optional(POLICY_EDIT): True, + } + ), +) -ENTITY_DOMAINS = 'domains' -ENTITY_AREAS = 'area_ids' -ENTITY_DEVICE_IDS = 'device_ids' -ENTITY_ENTITY_IDS = 'entity_ids' +ENTITY_DOMAINS = "domains" +ENTITY_AREAS = "area_ids" +ENTITY_DEVICE_IDS = "device_ids" +ENTITY_ENTITY_IDS = "entity_ids" -ENTITY_VALUES_SCHEMA = vol.Any(True, vol.Schema({ - str: SINGLE_ENTITY_SCHEMA -})) +ENTITY_VALUES_SCHEMA = vol.Any(True, vol.Schema({str: SINGLE_ENTITY_SCHEMA})) -ENTITY_POLICY_SCHEMA = vol.Any(True, vol.Schema({ - vol.Optional(SUBCAT_ALL): SINGLE_ENTITY_SCHEMA, - vol.Optional(ENTITY_AREAS): ENTITY_VALUES_SCHEMA, - vol.Optional(ENTITY_DEVICE_IDS): ENTITY_VALUES_SCHEMA, - vol.Optional(ENTITY_DOMAINS): ENTITY_VALUES_SCHEMA, - vol.Optional(ENTITY_ENTITY_IDS): ENTITY_VALUES_SCHEMA, -})) +ENTITY_POLICY_SCHEMA = vol.Any( + True, + vol.Schema( + { + vol.Optional(SUBCAT_ALL): SINGLE_ENTITY_SCHEMA, + vol.Optional(ENTITY_AREAS): ENTITY_VALUES_SCHEMA, + vol.Optional(ENTITY_DEVICE_IDS): ENTITY_VALUES_SCHEMA, + vol.Optional(ENTITY_DOMAINS): ENTITY_VALUES_SCHEMA, + vol.Optional(ENTITY_ENTITY_IDS): ENTITY_VALUES_SCHEMA, + } + ), +) -def _lookup_domain(perm_lookup: PermissionLookup, - domains_dict: SubCategoryDict, - entity_id: str) -> Optional[ValueType]: +def _lookup_domain( + perm_lookup: PermissionLookup, domains_dict: SubCategoryDict, entity_id: str +) -> Optional[ValueType]: """Look up entity permissions by domain.""" return domains_dict.get(entity_id.split(".", 1)[0]) -def _lookup_area(perm_lookup: PermissionLookup, area_dict: SubCategoryDict, - entity_id: str) -> Optional[ValueType]: +def _lookup_area( + perm_lookup: PermissionLookup, area_dict: SubCategoryDict, entity_id: str +) -> Optional[ValueType]: """Look up entity permissions by area.""" entity_entry = perm_lookup.entity_registry.async_get(entity_id) if entity_entry is None or entity_entry.device_id is None: return None - device_entry = perm_lookup.device_registry.async_get( - entity_entry.device_id - ) + device_entry = perm_lookup.device_registry.async_get(entity_entry.device_id) if device_entry is None or device_entry.area_id is None: return None @@ -59,9 +67,9 @@ def _lookup_area(perm_lookup: PermissionLookup, area_dict: SubCategoryDict, return area_dict.get(device_entry.area_id) -def _lookup_device(perm_lookup: PermissionLookup, - devices_dict: SubCategoryDict, - entity_id: str) -> Optional[ValueType]: +def _lookup_device( + perm_lookup: PermissionLookup, devices_dict: SubCategoryDict, entity_id: str +) -> Optional[ValueType]: """Look up entity permissions by device.""" entity_entry = perm_lookup.entity_registry.async_get(entity_id) @@ -71,15 +79,16 @@ def _lookup_device(perm_lookup: PermissionLookup, return devices_dict.get(entity_entry.device_id) -def _lookup_entity_id(perm_lookup: PermissionLookup, - entities_dict: SubCategoryDict, - entity_id: str) -> Optional[ValueType]: +def _lookup_entity_id( + perm_lookup: PermissionLookup, entities_dict: SubCategoryDict, entity_id: str +) -> Optional[ValueType]: """Look up entity permission by entity id.""" return entities_dict.get(entity_id) -def compile_entities(policy: CategoryType, perm_lookup: PermissionLookup) \ - -> Callable[[str, str], bool]: +def compile_entities( + policy: CategoryType, perm_lookup: PermissionLookup +) -> Callable[[str, str], bool]: """Compile policy into a function that tests policy.""" subcategories = OrderedDict() # type: SubCatLookupType subcategories[ENTITY_ENTITY_IDS] = _lookup_entity_id diff --git a/homeassistant/auth/permissions/merge.py b/homeassistant/auth/permissions/merge.py index ec6375a0e3d..f8b3639ad5a 100644 --- a/homeassistant/auth/permissions/merge.py +++ b/homeassistant/auth/permissions/merge.py @@ -1,6 +1,5 @@ """Merging of policies.""" -from typing import ( # noqa: F401 - cast, Dict, List, Set) +from typing import cast, Dict, List, Set # noqa: F401 from .types import PolicyType, CategoryType @@ -14,8 +13,9 @@ def merge_policies(policies: List[PolicyType]) -> PolicyType: if category in seen: continue seen.add(category) - new_policy[category] = _merge_policies([ - policy.get(category) for policy in policies]) + new_policy[category] = _merge_policies( + [policy.get(category) for policy in policies] + ) cast(PolicyType, new_policy) return new_policy diff --git a/homeassistant/auth/permissions/models.py b/homeassistant/auth/permissions/models.py index 10a76a4ec73..31bea635bbe 100644 --- a/homeassistant/auth/permissions/models.py +++ b/homeassistant/auth/permissions/models.py @@ -5,17 +5,13 @@ import attr if TYPE_CHECKING: # pylint: disable=unused-import - from homeassistant.helpers import ( # noqa - entity_registry as ent_reg, - ) - from homeassistant.helpers import ( # noqa - device_registry as dev_reg, - ) + from homeassistant.helpers import entity_registry as ent_reg # noqa + from homeassistant.helpers import device_registry as dev_reg # noqa @attr.s(slots=True) class PermissionLookup: """Class to hold data for permission lookups.""" - entity_registry = attr.ib(type='ent_reg.EntityRegistry') - device_registry = attr.ib(type='dev_reg.DeviceRegistry') + entity_registry = attr.ib(type="ent_reg.EntityRegistry") + device_registry = attr.ib(type="dev_reg.DeviceRegistry") diff --git a/homeassistant/auth/permissions/system_policies.py b/homeassistant/auth/permissions/system_policies.py index bf65c0a85a6..b40400304cc 100644 --- a/homeassistant/auth/permissions/system_policies.py +++ b/homeassistant/auth/permissions/system_policies.py @@ -1,18 +1,8 @@ """System policies.""" from .const import CAT_ENTITIES, SUBCAT_ALL, POLICY_READ -ADMIN_POLICY = { - CAT_ENTITIES: True, -} +ADMIN_POLICY = {CAT_ENTITIES: True} -USER_POLICY = { - CAT_ENTITIES: True, -} +USER_POLICY = {CAT_ENTITIES: True} -READ_ONLY_POLICY = { - CAT_ENTITIES: { - SUBCAT_ALL: { - POLICY_READ: True - } - } -} +READ_ONLY_POLICY = {CAT_ENTITIES: {SUBCAT_ALL: {POLICY_READ: True}}} diff --git a/homeassistant/auth/permissions/types.py b/homeassistant/auth/permissions/types.py index 5479e59dcb6..6ce394ebb92 100644 --- a/homeassistant/auth/permissions/types.py +++ b/homeassistant/auth/permissions/types.py @@ -7,17 +7,13 @@ ValueType = Union[ # Example: entities.all = { read: true, control: true } Mapping[str, bool], bool, - None + None, ] # Example: entities.domains = { light: … } SubCategoryDict = Mapping[str, ValueType] -SubCategoryType = Union[ - SubCategoryDict, - bool, - None -] +SubCategoryType = Union[SubCategoryDict, bool, None] CategoryType = Union[ # Example: entities.domains @@ -25,7 +21,7 @@ CategoryType = Union[ # Example: entities.all Mapping[str, ValueType], bool, - None + None, ] # Example: { entities: … } diff --git a/homeassistant/auth/permissions/util.py b/homeassistant/auth/permissions/util.py index 3173c179817..6b44cbf61d4 100644 --- a/homeassistant/auth/permissions/util.py +++ b/homeassistant/auth/permissions/util.py @@ -7,28 +7,28 @@ from .const import SUBCAT_ALL from .models import PermissionLookup from .types import CategoryType, SubCategoryDict, ValueType -LookupFunc = Callable[[PermissionLookup, SubCategoryDict, str], - Optional[ValueType]] +LookupFunc = Callable[[PermissionLookup, SubCategoryDict, str], Optional[ValueType]] SubCatLookupType = Dict[str, LookupFunc] -def lookup_all(perm_lookup: PermissionLookup, lookup_dict: SubCategoryDict, - object_id: str) -> ValueType: +def lookup_all( + perm_lookup: PermissionLookup, lookup_dict: SubCategoryDict, object_id: str +) -> ValueType: """Look up permission for all.""" # In case of ALL category, lookup_dict IS the schema. return cast(ValueType, lookup_dict) def compile_policy( - policy: CategoryType, subcategories: SubCatLookupType, - perm_lookup: PermissionLookup - ) -> Callable[[str, str], bool]: # noqa + policy: CategoryType, subcategories: SubCatLookupType, perm_lookup: PermissionLookup +) -> Callable[[str, str], bool]: # noqa """Compile policy into a function that tests policy. Subcategories are mapping key -> lookup function, ordered by highest priority first. """ # None, False, empty dict if not policy: + def apply_policy_deny_all(entity_id: str, key: str) -> bool: """Decline all.""" return False @@ -36,6 +36,7 @@ def compile_policy( return apply_policy_deny_all if policy is True: + def apply_policy_allow_all(entity_id: str, key: str) -> bool: """Approve all.""" return True @@ -54,8 +55,7 @@ def compile_policy( return lambda object_id, key: True if lookup_value is not None: - funcs.append(_gen_dict_test_func( - perm_lookup, lookup_func, lookup_value)) + funcs.append(_gen_dict_test_func(perm_lookup, lookup_func, lookup_value)) if len(funcs) == 1: func = funcs[0] @@ -79,15 +79,13 @@ def compile_policy( def _gen_dict_test_func( - perm_lookup: PermissionLookup, - lookup_func: LookupFunc, - lookup_dict: SubCategoryDict - ) -> Callable[[str, str], Optional[bool]]: # noqa + perm_lookup: PermissionLookup, lookup_func: LookupFunc, lookup_dict: SubCategoryDict +) -> Callable[[str, str], Optional[bool]]: # noqa """Generate a lookup function.""" + def test_value(object_id: str, key: str) -> Optional[bool]: """Test if permission is allowed based on the keys.""" - schema = lookup_func( - perm_lookup, lookup_dict, object_id) # type: ValueType + schema = lookup_func(perm_lookup, lookup_dict, object_id) # type: ValueType if schema is None or isinstance(schema, bool): return schema diff --git a/homeassistant/auth/providers/__init__.py b/homeassistant/auth/providers/__init__.py index 83cbbf4ae9d..c720cf0df64 100644 --- a/homeassistant/auth/providers/__init__.py +++ b/homeassistant/auth/providers/__init__.py @@ -19,25 +19,29 @@ from ..const import MFA_SESSION_EXPIRATION from ..models import Credentials, User, UserMeta # noqa: F401 _LOGGER = logging.getLogger(__name__) -DATA_REQS = 'auth_prov_reqs_processed' +DATA_REQS = "auth_prov_reqs_processed" AUTH_PROVIDERS = Registry() -AUTH_PROVIDER_SCHEMA = vol.Schema({ - vol.Required(CONF_TYPE): str, - vol.Optional(CONF_NAME): str, - # Specify ID if you have two auth providers for same type. - vol.Optional(CONF_ID): str, -}, extra=vol.ALLOW_EXTRA) +AUTH_PROVIDER_SCHEMA = vol.Schema( + { + vol.Required(CONF_TYPE): str, + vol.Optional(CONF_NAME): str, + # Specify ID if you have two auth providers for same type. + vol.Optional(CONF_ID): str, + }, + extra=vol.ALLOW_EXTRA, +) class AuthProvider: """Provider of user authentication.""" - DEFAULT_TITLE = 'Unnamed auth provider' + DEFAULT_TITLE = "Unnamed auth provider" - def __init__(self, hass: HomeAssistant, store: AuthStore, - config: Dict[str, Any]) -> None: + def __init__( + self, hass: HomeAssistant, store: AuthStore, config: Dict[str, Any] + ) -> None: """Initialize an auth provider.""" self.hass = hass self.store = store @@ -73,22 +77,22 @@ class AuthProvider: credentials for user in users for credentials in user.credentials - if (credentials.auth_provider_type == self.type and - credentials.auth_provider_id == self.id) + if ( + credentials.auth_provider_type == self.type + and credentials.auth_provider_id == self.id + ) ] @callback def async_create_credentials(self, data: Dict[str, str]) -> Credentials: """Create credentials.""" return Credentials( - auth_provider_type=self.type, - auth_provider_id=self.id, - data=data, + auth_provider_type=self.type, auth_provider_id=self.id, data=data ) # Implement by extending class - async def async_login_flow(self, context: Optional[Dict]) -> 'LoginFlow': + async def async_login_flow(self, context: Optional[Dict]) -> "LoginFlow": """Return the data flow for logging in with auth provider. Auth provider should extend LoginFlow and return an instance. @@ -96,12 +100,14 @@ class AuthProvider: raise NotImplementedError async def async_get_or_create_credentials( - self, flow_result: Dict[str, str]) -> Credentials: + self, flow_result: Dict[str, str] + ) -> Credentials: """Get credentials based on the flow result.""" raise NotImplementedError async def async_user_meta_for_credentials( - self, credentials: Credentials) -> UserMeta: + self, credentials: Credentials + ) -> UserMeta: """Return extra user metadata for credentials. Will be used to populate info when creating a new user. @@ -114,8 +120,8 @@ class AuthProvider: async def auth_provider_from_config( - hass: HomeAssistant, store: AuthStore, - config: Dict[str, Any]) -> AuthProvider: + hass: HomeAssistant, store: AuthStore, config: Dict[str, Any] +) -> AuthProvider: """Initialize an auth provider from a config.""" provider_name = config[CONF_TYPE] module = await load_auth_provider_module(hass, provider_name) @@ -123,25 +129,31 @@ async def auth_provider_from_config( try: config = module.CONFIG_SCHEMA(config) # type: ignore except vol.Invalid as err: - _LOGGER.error('Invalid configuration for auth provider %s: %s', - provider_name, humanize_error(config, err)) + _LOGGER.error( + "Invalid configuration for auth provider %s: %s", + provider_name, + humanize_error(config, err), + ) raise return AUTH_PROVIDERS[provider_name](hass, store, config) # type: ignore async def load_auth_provider_module( - hass: HomeAssistant, provider: str) -> types.ModuleType: + hass: HomeAssistant, provider: str +) -> types.ModuleType: """Load an auth provider.""" try: module = importlib.import_module( - 'homeassistant.auth.providers.{}'.format(provider)) + "homeassistant.auth.providers.{}".format(provider) + ) except ImportError as err: - _LOGGER.error('Unable to load auth provider %s: %s', provider, err) - raise HomeAssistantError('Unable to load auth provider {}: {}'.format( - provider, err)) + _LOGGER.error("Unable to load auth provider %s: %s", provider, err) + raise HomeAssistantError( + "Unable to load auth provider {}: {}".format(provider, err) + ) - if hass.config.skip_pip or not hasattr(module, 'REQUIREMENTS'): + if hass.config.skip_pip or not hasattr(module, "REQUIREMENTS"): return module processed = hass.data.get(DATA_REQS) @@ -154,12 +166,13 @@ async def load_auth_provider_module( # https://github.com/python/mypy/issues/1424 reqs = module.REQUIREMENTS # type: ignore req_success = await requirements.async_process_requirements( - hass, 'auth provider {}'.format(provider), reqs) + hass, "auth provider {}".format(provider), reqs + ) if not req_success: raise HomeAssistantError( - 'Unable to process requirements of auth provider {}'.format( - provider)) + "Unable to process requirements of auth provider {}".format(provider) + ) processed.add(provider) return module @@ -179,8 +192,8 @@ class LoginFlow(data_entry_flow.FlowHandler): self.user = None # type: Optional[User] async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None) \ - -> Dict[str, Any]: + self, user_input: Optional[Dict[str, str]] = None + ) -> Dict[str, Any]: """Handle the first step of login flow. Return self.async_show_form(step_id='init') if user_input is None. @@ -189,80 +202,75 @@ class LoginFlow(data_entry_flow.FlowHandler): raise NotImplementedError async def async_step_select_mfa_module( - self, user_input: Optional[Dict[str, str]] = None) \ - -> Dict[str, Any]: + self, user_input: Optional[Dict[str, str]] = None + ) -> Dict[str, Any]: """Handle the step of select mfa module.""" errors = {} if user_input is not None: - auth_module = user_input.get('multi_factor_auth_module') + auth_module = user_input.get("multi_factor_auth_module") if auth_module in self.available_mfa_modules: self._auth_module_id = auth_module return await self.async_step_mfa() - errors['base'] = 'invalid_auth_module' + errors["base"] = "invalid_auth_module" if len(self.available_mfa_modules) == 1: self._auth_module_id = list(self.available_mfa_modules.keys())[0] return await self.async_step_mfa() return self.async_show_form( - step_id='select_mfa_module', - data_schema=vol.Schema({ - 'multi_factor_auth_module': vol.In(self.available_mfa_modules) - }), + step_id="select_mfa_module", + data_schema=vol.Schema( + {"multi_factor_auth_module": vol.In(self.available_mfa_modules)} + ), errors=errors, ) async def async_step_mfa( - self, user_input: Optional[Dict[str, str]] = None) \ - -> Dict[str, Any]: + self, user_input: Optional[Dict[str, str]] = None + ) -> Dict[str, Any]: """Handle the step of mfa validation.""" assert self.user errors = {} - auth_module = self._auth_manager.get_auth_mfa_module( - self._auth_module_id) + auth_module = self._auth_manager.get_auth_mfa_module(self._auth_module_id) if auth_module is None: # Given an invalid input to async_step_select_mfa_module # will show invalid_auth_module error return await self.async_step_select_mfa_module(user_input={}) - if user_input is None and hasattr(auth_module, - 'async_initialize_login_mfa_step'): + if user_input is None and hasattr( + auth_module, "async_initialize_login_mfa_step" + ): try: await auth_module.async_initialize_login_mfa_step(self.user.id) except HomeAssistantError: - _LOGGER.exception('Error initializing MFA step') - return self.async_abort(reason='unknown_error') + _LOGGER.exception("Error initializing MFA step") + return self.async_abort(reason="unknown_error") if user_input is not None: expires = self.created_at + MFA_SESSION_EXPIRATION if dt_util.utcnow() > expires: - return self.async_abort( - reason='login_expired' - ) + return self.async_abort(reason="login_expired") - result = await auth_module.async_validate( - self.user.id, user_input) + result = await auth_module.async_validate(self.user.id, user_input) if not result: - errors['base'] = 'invalid_code' + errors["base"] = "invalid_code" self.invalid_mfa_times += 1 if self.invalid_mfa_times >= auth_module.MAX_RETRY_TIME > 0: - return self.async_abort( - reason='too_many_retry' - ) + return self.async_abort(reason="too_many_retry") if not errors: return await self.async_finish(self.user) description_placeholders = { - 'mfa_module_name': auth_module.name, - 'mfa_module_id': auth_module.id, + "mfa_module_name": auth_module.name, + "mfa_module_id": auth_module.id, } # type: Dict[str, Optional[str]] return self.async_show_form( - step_id='mfa', + step_id="mfa", data_schema=auth_module.input_schema, description_placeholders=description_placeholders, errors=errors, @@ -270,7 +278,4 @@ class LoginFlow(data_entry_flow.FlowHandler): async def async_finish(self, flow_result: Any) -> Dict: """Handle the pass of login flow.""" - return self.async_create_entry( - title=self._auth_provider.name, - data=flow_result - ) + return self.async_create_entry(title=self._auth_provider.name, data=flow_result) diff --git a/homeassistant/auth/providers/command_line.py b/homeassistant/auth/providers/command_line.py index 9cec34c1340..cdf1a533412 100644 --- a/homeassistant/auth/providers/command_line.py +++ b/homeassistant/auth/providers/command_line.py @@ -19,15 +19,16 @@ CONF_COMMAND = "command" CONF_ARGS = "args" CONF_META = "meta" -CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend({ - vol.Required(CONF_COMMAND): vol.All( - str, - os.path.normpath, - msg="must be an absolute path" - ), - vol.Optional(CONF_ARGS, default=None): vol.Any(vol.DefaultTo(list), [str]), - vol.Optional(CONF_META, default=False): bool, -}, extra=vol.PREVENT_EXTRA) +CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend( + { + vol.Required(CONF_COMMAND): vol.All( + str, os.path.normpath, msg="must be an absolute path" + ), + vol.Optional(CONF_ARGS, default=None): vol.Any(vol.DefaultTo(list), [str]), + vol.Optional(CONF_META, default=False): bool, + }, + extra=vol.PREVENT_EXTRA, +) _LOGGER = logging.getLogger(__name__) @@ -60,29 +61,27 @@ class CommandLineAuthProvider(AuthProvider): async def async_validate_login(self, username: str, password: str) -> None: """Validate a username and password.""" - env = { - "username": username, - "password": password, - } + env = {"username": username, "password": password} try: # pylint: disable=no-member process = await asyncio.subprocess.create_subprocess_exec( - self.config[CONF_COMMAND], *self.config[CONF_ARGS], + self.config[CONF_COMMAND], + *self.config[CONF_ARGS], env=env, - stdout=asyncio.subprocess.PIPE - if self.config[CONF_META] else None, + stdout=asyncio.subprocess.PIPE if self.config[CONF_META] else None, ) - stdout, _ = (await process.communicate()) + stdout, _ = await process.communicate() except OSError as err: # happens when command doesn't exist or permission is denied - _LOGGER.error("Error while authenticating %r: %s", - username, err) + _LOGGER.error("Error while authenticating %r: %s", username, err) raise InvalidAuthError if process.returncode != 0: - _LOGGER.error("User %r failed to authenticate, command exited " - "with code %d.", - username, process.returncode) + _LOGGER.error( + "User %r failed to authenticate, command exited " "with code %d.", + username, + process.returncode, + ) raise InvalidAuthError if self.config[CONF_META]: @@ -103,7 +102,7 @@ class CommandLineAuthProvider(AuthProvider): self._user_meta[username] = meta async def async_get_or_create_credentials( - self, flow_result: Dict[str, str] + self, flow_result: Dict[str, str] ) -> Credentials: """Get credentials based on the flow result.""" username = flow_result["username"] @@ -112,29 +111,24 @@ class CommandLineAuthProvider(AuthProvider): return credential # Create new credentials. - return self.async_create_credentials({ - "username": username, - }) + return self.async_create_credentials({"username": username}) async def async_user_meta_for_credentials( - self, credentials: Credentials + self, credentials: Credentials ) -> UserMeta: """Return extra user metadata for credentials. Currently, only name is supported. """ meta = self._user_meta.get(credentials.data["username"], {}) - return UserMeta( - name=meta.get("name"), - is_active=True, - ) + return UserMeta(name=meta.get("name"), is_active=True) class CommandLineLoginFlow(LoginFlow): """Handler for the login flow.""" async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None + self, user_input: Optional[Dict[str, str]] = None ) -> Dict[str, Any]: """Handle the step of the form.""" errors = {} @@ -142,10 +136,9 @@ class CommandLineLoginFlow(LoginFlow): if user_input is not None: user_input["username"] = user_input["username"].strip() try: - await cast(CommandLineAuthProvider, self._auth_provider) \ - .async_validate_login( - user_input["username"], user_input["password"] - ) + await cast( + CommandLineAuthProvider, self._auth_provider + ).async_validate_login(user_input["username"], user_input["password"]) except InvalidAuthError: errors["base"] = "invalid_auth" @@ -158,7 +151,5 @@ class CommandLineLoginFlow(LoginFlow): schema["password"] = str return self.async_show_form( - step_id="init", - data_schema=vol.Schema(schema), - errors=errors, + step_id="init", data_schema=vol.Schema(schema), errors=errors ) diff --git a/homeassistant/auth/providers/homeassistant.py b/homeassistant/auth/providers/homeassistant.py index 2187d272800..d8142a2ace2 100644 --- a/homeassistant/auth/providers/homeassistant.py +++ b/homeassistant/auth/providers/homeassistant.py @@ -19,14 +19,13 @@ from ..models import Credentials, UserMeta STORAGE_VERSION = 1 -STORAGE_KEY = 'auth_provider.homeassistant' +STORAGE_KEY = "auth_provider.homeassistant" def _disallow_id(conf: Dict[str, Any]) -> Dict[str, Any]: """Disallow ID in config.""" if CONF_ID in conf: - raise vol.Invalid( - 'ID is not allowed for the homeassistant auth provider.') + raise vol.Invalid("ID is not allowed for the homeassistant auth provider.") return conf @@ -51,8 +50,9 @@ class Data: def __init__(self, hass: HomeAssistant) -> None: """Initialize the user data store.""" self.hass = hass - self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY, - private=True) + self._store = hass.helpers.storage.Store( + STORAGE_VERSION, STORAGE_KEY, private=True + ) self._data = None # type: Optional[Dict[str, Any]] # Legacy mode will allow usernames to start/end with whitespace # and will compare usernames case-insensitive. @@ -72,14 +72,12 @@ class Data: data = await self._store.async_load() if data is None: - data = { - 'users': [] - } + data = {"users": []} seen = set() # type: Set[str] - for user in data['users']: - username = user['username'] + for user in data["users"]: + username = user["username"] # check if we have duplicates folded = username.casefold() @@ -90,7 +88,9 @@ class Data: logging.getLogger(__name__).warning( "Home Assistant auth provider is running in legacy mode " "because we detected usernames that are case-insensitive" - "equivalent. Please change the username: '%s'.", username) + "equivalent. Please change the username: '%s'.", + username, + ) break @@ -103,7 +103,9 @@ class Data: logging.getLogger(__name__).warning( "Home Assistant auth provider is running in legacy mode " "because we detected usernames that start or end in a " - "space. Please change the username: '%s'.", username) + "space. Please change the username: '%s'.", + username, + ) break @@ -112,7 +114,7 @@ class Data: @property def users(self) -> List[Dict[str, str]]: """Return users.""" - return self._data['users'] # type: ignore + return self._data["users"] # type: ignore def validate_login(self, username: str, password: str) -> None: """Validate a username and password. @@ -120,32 +122,30 @@ class Data: Raises InvalidAuth if auth invalid. """ username = self.normalize_username(username) - dummy = b'$2b$12$CiuFGszHx9eNHxPuQcwBWez4CwDTOcLTX5CbOpV6gef2nYuXkY7BO' + dummy = b"$2b$12$CiuFGszHx9eNHxPuQcwBWez4CwDTOcLTX5CbOpV6gef2nYuXkY7BO" found = None # Compare all users to avoid timing attacks. for user in self.users: - if self.normalize_username(user['username']) == username: + if self.normalize_username(user["username"]) == username: found = user if found is None: # check a hash to make timing the same as if user was found - bcrypt.checkpw(b'foo', - dummy) + bcrypt.checkpw(b"foo", dummy) raise InvalidAuth - user_hash = base64.b64decode(found['password']) + user_hash = base64.b64decode(found["password"]) # bcrypt.checkpw is timing-safe - if not bcrypt.checkpw(password.encode(), - user_hash): + if not bcrypt.checkpw(password.encode(), user_hash): raise InvalidAuth # pylint: disable=no-self-use def hash_password(self, password: str, for_storage: bool = False) -> bytes: """Encode a password.""" - hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12)) \ - # type: bytes + hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12)) + # type: bytes if for_storage: hashed = base64.b64encode(hashed) return hashed @@ -154,14 +154,17 @@ class Data: """Add a new authenticated user/pass.""" username = self.normalize_username(username) - if any(self.normalize_username(user['username']) == username - for user in self.users): + if any( + self.normalize_username(user["username"]) == username for user in self.users + ): raise InvalidUser - self.users.append({ - 'username': username, - 'password': self.hash_password(password, True).decode(), - }) + self.users.append( + { + "username": username, + "password": self.hash_password(password, True).decode(), + } + ) @callback def async_remove_auth(self, username: str) -> None: @@ -170,7 +173,7 @@ class Data: index = None for i, user in enumerate(self.users): - if self.normalize_username(user['username']) == username: + if self.normalize_username(user["username"]) == username: index = i break @@ -187,9 +190,8 @@ class Data: username = self.normalize_username(username) for user in self.users: - if self.normalize_username(user['username']) == username: - user['password'] = self.hash_password( - new_password, True).decode() + if self.normalize_username(user["username"]) == username: + user["password"] = self.hash_password(new_password, True).decode() break else: raise InvalidUser @@ -199,11 +201,11 @@ class Data: await self._store.async_save(self._data) -@AUTH_PROVIDERS.register('homeassistant') +@AUTH_PROVIDERS.register("homeassistant") class HassAuthProvider(AuthProvider): """Auth provider based on a local storage of users in HASS config dir.""" - DEFAULT_TITLE = 'Home Assistant Local' + DEFAULT_TITLE = "Home Assistant Local" def __init__(self, *args: Any, **kwargs: Any) -> None: """Initialize an Home Assistant auth provider.""" @@ -221,8 +223,7 @@ class HassAuthProvider(AuthProvider): await data.async_load() self.data = data - async def async_login_flow( - self, context: Optional[Dict]) -> LoginFlow: + async def async_login_flow(self, context: Optional[Dict]) -> LoginFlow: """Return a flow to login.""" return HassLoginFlow(self) @@ -233,41 +234,41 @@ class HassAuthProvider(AuthProvider): assert self.data is not None await self.hass.async_add_executor_job( - self.data.validate_login, username, password) + self.data.validate_login, username, password + ) async def async_get_or_create_credentials( - self, flow_result: Dict[str, str]) -> Credentials: + self, flow_result: Dict[str, str] + ) -> Credentials: """Get credentials based on the flow result.""" if self.data is None: await self.async_initialize() assert self.data is not None norm_username = self.data.normalize_username - username = norm_username(flow_result['username']) + username = norm_username(flow_result["username"]) for credential in await self.async_credentials(): - if norm_username(credential.data['username']) == username: + if norm_username(credential.data["username"]) == username: return credential # Create new credentials. - return self.async_create_credentials({ - 'username': username - }) + return self.async_create_credentials({"username": username}) async def async_user_meta_for_credentials( - self, credentials: Credentials) -> UserMeta: + self, credentials: Credentials + ) -> UserMeta: """Get extra info for this credential.""" - return UserMeta(name=credentials.data['username'], is_active=True) + return UserMeta(name=credentials.data["username"], is_active=True) - async def async_will_remove_credentials( - self, credentials: Credentials) -> None: + async def async_will_remove_credentials(self, credentials: Credentials) -> None: """When credentials get removed, also remove the auth.""" if self.data is None: await self.async_initialize() assert self.data is not None try: - self.data.async_remove_auth(credentials.data['username']) + self.data.async_remove_auth(credentials.data["username"]) await self.data.async_save() except InvalidUser: # Can happen if somehow we didn't clean up a credential @@ -278,29 +279,27 @@ class HassLoginFlow(LoginFlow): """Handler for the login flow.""" async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None) \ - -> Dict[str, Any]: + self, user_input: Optional[Dict[str, str]] = None + ) -> Dict[str, Any]: """Handle the step of the form.""" errors = {} if user_input is not None: try: - await cast(HassAuthProvider, self._auth_provider)\ - .async_validate_login(user_input['username'], - user_input['password']) + await cast(HassAuthProvider, self._auth_provider).async_validate_login( + user_input["username"], user_input["password"] + ) except InvalidAuth: - errors['base'] = 'invalid_auth' + errors["base"] = "invalid_auth" if not errors: - user_input.pop('password') + user_input.pop("password") return await self.async_finish(user_input) schema = OrderedDict() # type: Dict[str, type] - schema['username'] = str - schema['password'] = str + schema["username"] = str + schema["password"] = str return self.async_show_form( - step_id='init', - data_schema=vol.Schema(schema), - errors=errors, + step_id="init", data_schema=vol.Schema(schema), errors=errors ) diff --git a/homeassistant/auth/providers/insecure_example.py b/homeassistant/auth/providers/insecure_example.py index 72e3dfe140a..35524c3f5fc 100644 --- a/homeassistant/auth/providers/insecure_example.py +++ b/homeassistant/auth/providers/insecure_example.py @@ -12,23 +12,25 @@ from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, LoginFlow from ..models import Credentials, UserMeta -USER_SCHEMA = vol.Schema({ - vol.Required('username'): str, - vol.Required('password'): str, - vol.Optional('name'): str, -}) +USER_SCHEMA = vol.Schema( + { + vol.Required("username"): str, + vol.Required("password"): str, + vol.Optional("name"): str, + } +) -CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend({ - vol.Required('users'): [USER_SCHEMA] -}, extra=vol.PREVENT_EXTRA) +CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend( + {vol.Required("users"): [USER_SCHEMA]}, extra=vol.PREVENT_EXTRA +) class InvalidAuthError(HomeAssistantError): """Raised when submitting invalid authentication.""" -@AUTH_PROVIDERS.register('insecure_example') +@AUTH_PROVIDERS.register("insecure_example") class ExampleAuthProvider(AuthProvider): """Example auth provider based on hardcoded usernames and passwords.""" @@ -42,47 +44,48 @@ class ExampleAuthProvider(AuthProvider): user = None # Compare all users to avoid timing attacks. - for usr in self.config['users']: - if hmac.compare_digest(username.encode('utf-8'), - usr['username'].encode('utf-8')): + for usr in self.config["users"]: + if hmac.compare_digest( + username.encode("utf-8"), usr["username"].encode("utf-8") + ): user = usr if user is None: # Do one more compare to make timing the same as if user was found. - hmac.compare_digest(password.encode('utf-8'), - password.encode('utf-8')) + hmac.compare_digest(password.encode("utf-8"), password.encode("utf-8")) raise InvalidAuthError - if not hmac.compare_digest(user['password'].encode('utf-8'), - password.encode('utf-8')): + if not hmac.compare_digest( + user["password"].encode("utf-8"), password.encode("utf-8") + ): raise InvalidAuthError async def async_get_or_create_credentials( - self, flow_result: Dict[str, str]) -> Credentials: + self, flow_result: Dict[str, str] + ) -> Credentials: """Get credentials based on the flow result.""" - username = flow_result['username'] + username = flow_result["username"] for credential in await self.async_credentials(): - if credential.data['username'] == username: + if credential.data["username"] == username: return credential # Create new credentials. - return self.async_create_credentials({ - 'username': username - }) + return self.async_create_credentials({"username": username}) async def async_user_meta_for_credentials( - self, credentials: Credentials) -> UserMeta: + self, credentials: Credentials + ) -> UserMeta: """Return extra user metadata for credentials. Will be used to populate info when creating a new user. """ - username = credentials.data['username'] + username = credentials.data["username"] name = None - for user in self.config['users']: - if user['username'] == username: - name = user.get('name') + for user in self.config["users"]: + if user["username"] == username: + name = user.get("name") break return UserMeta(name=name, is_active=True) @@ -92,29 +95,27 @@ class ExampleLoginFlow(LoginFlow): """Handler for the login flow.""" async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None) \ - -> Dict[str, Any]: + self, user_input: Optional[Dict[str, str]] = None + ) -> Dict[str, Any]: """Handle the step of the form.""" errors = {} if user_input is not None: try: - cast(ExampleAuthProvider, self._auth_provider)\ - .async_validate_login(user_input['username'], - user_input['password']) + cast(ExampleAuthProvider, self._auth_provider).async_validate_login( + user_input["username"], user_input["password"] + ) except InvalidAuthError: - errors['base'] = 'invalid_auth' + errors["base"] = "invalid_auth" if not errors: - user_input.pop('password') + user_input.pop("password") return await self.async_finish(user_input) schema = OrderedDict() # type: Dict[str, type] - schema['username'] = str - schema['password'] = str + schema["username"] = str + schema["password"] = str return self.async_show_form( - step_id='init', - data_schema=vol.Schema(schema), - errors=errors, + step_id="init", data_schema=vol.Schema(schema), errors=errors ) diff --git a/homeassistant/auth/providers/legacy_api_password.py b/homeassistant/auth/providers/legacy_api_password.py index e85d831a325..018886388df 100644 --- a/homeassistant/auth/providers/legacy_api_password.py +++ b/homeassistant/auth/providers/legacy_api_password.py @@ -16,27 +16,26 @@ from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, LoginFlow from .. import AuthManager from ..models import Credentials, UserMeta, User -AUTH_PROVIDER_TYPE = 'legacy_api_password' -CONF_API_PASSWORD = 'api_password' +AUTH_PROVIDER_TYPE = "legacy_api_password" +CONF_API_PASSWORD = "api_password" -CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend({ - vol.Required(CONF_API_PASSWORD): cv.string, -}, extra=vol.PREVENT_EXTRA) +CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend( + {vol.Required(CONF_API_PASSWORD): cv.string}, extra=vol.PREVENT_EXTRA +) -LEGACY_USER_NAME = 'Legacy API password user' +LEGACY_USER_NAME = "Legacy API password user" class InvalidAuthError(HomeAssistantError): """Raised when submitting invalid authentication.""" -async def async_validate_password(hass: HomeAssistant, password: str)\ - -> Optional[User]: +async def async_validate_password(hass: HomeAssistant, password: str) -> Optional[User]: """Return a user if password is valid. None if not.""" auth = cast(AuthManager, hass.auth) # type: ignore providers = auth.get_auth_providers(AUTH_PROVIDER_TYPE) if not providers: - raise ValueError('Legacy API password provider not found') + raise ValueError("Legacy API password provider not found") try: provider = cast(LegacyApiPasswordAuthProvider, providers[0]) @@ -52,7 +51,7 @@ async def async_validate_password(hass: HomeAssistant, password: str)\ class LegacyApiPasswordAuthProvider(AuthProvider): """An auth provider support legacy api_password.""" - DEFAULT_TITLE = 'Legacy API Password' + DEFAULT_TITLE = "Legacy API Password" @property def api_password(self) -> str: @@ -68,12 +67,14 @@ class LegacyApiPasswordAuthProvider(AuthProvider): """Validate password.""" api_password = str(self.config[CONF_API_PASSWORD]) - if not hmac.compare_digest(api_password.encode('utf-8'), - password.encode('utf-8')): + if not hmac.compare_digest( + api_password.encode("utf-8"), password.encode("utf-8") + ): raise InvalidAuthError async def async_get_or_create_credentials( - self, flow_result: Dict[str, str]) -> Credentials: + self, flow_result: Dict[str, str] + ) -> Credentials: """Return credentials for this login.""" credentials = await self.async_credentials() if credentials: @@ -82,7 +83,8 @@ class LegacyApiPasswordAuthProvider(AuthProvider): return self.async_create_credentials({}) async def async_user_meta_for_credentials( - self, credentials: Credentials) -> UserMeta: + self, credentials: Credentials + ) -> UserMeta: """ Return info for the user. @@ -95,23 +97,22 @@ class LegacyLoginFlow(LoginFlow): """Handler for the login flow.""" async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None) \ - -> Dict[str, Any]: + self, user_input: Optional[Dict[str, str]] = None + ) -> Dict[str, Any]: """Handle the step of the form.""" errors = {} if user_input is not None: try: - cast(LegacyApiPasswordAuthProvider, self._auth_provider)\ - .async_validate_login(user_input['password']) + cast( + LegacyApiPasswordAuthProvider, self._auth_provider + ).async_validate_login(user_input["password"]) except InvalidAuthError: - errors['base'] = 'invalid_auth' + errors["base"] = "invalid_auth" if not errors: return await self.async_finish({}) return self.async_show_form( - step_id='init', - data_schema=vol.Schema({'password': str}), - errors=errors, + step_id="init", data_schema=vol.Schema({"password": str}), errors=errors ) diff --git a/homeassistant/auth/providers/trusted_networks.py b/homeassistant/auth/providers/trusted_networks.py index e8161a2bfb6..f71be436acf 100644 --- a/homeassistant/auth/providers/trusted_networks.py +++ b/homeassistant/auth/providers/trusted_networks.py @@ -3,8 +3,7 @@ It shows list of users if access from trusted network. Abort login flow if not access from trusted network. """ -from ipaddress import ip_network, IPv4Address, IPv6Address, IPv4Network,\ - IPv6Network +from ipaddress import ip_network, IPv4Address, IPv6Address, IPv4Network, IPv6Network from typing import Any, Dict, List, Optional, Union, cast import voluptuous as vol @@ -18,27 +17,32 @@ from ..models import Credentials, UserMeta IPAddress = Union[IPv4Address, IPv6Address] IPNetwork = Union[IPv4Network, IPv6Network] -CONF_TRUSTED_NETWORKS = 'trusted_networks' -CONF_TRUSTED_USERS = 'trusted_users' -CONF_GROUP = 'group' -CONF_ALLOW_BYPASS_LOGIN = 'allow_bypass_login' +CONF_TRUSTED_NETWORKS = "trusted_networks" +CONF_TRUSTED_USERS = "trusted_users" +CONF_GROUP = "group" +CONF_ALLOW_BYPASS_LOGIN = "allow_bypass_login" -CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend({ - vol.Required(CONF_TRUSTED_NETWORKS): vol.All( - cv.ensure_list, [ip_network] - ), - vol.Optional(CONF_TRUSTED_USERS, default={}): vol.Schema( - # we only validate the format of user_id or group_id - {ip_network: vol.All( - cv.ensure_list, - [vol.Or( - cv.uuid4_hex, - vol.Schema({vol.Required(CONF_GROUP): cv.uuid4_hex}), - )], - )} - ), - vol.Optional(CONF_ALLOW_BYPASS_LOGIN, default=False): cv.boolean, -}, extra=vol.PREVENT_EXTRA) +CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend( + { + vol.Required(CONF_TRUSTED_NETWORKS): vol.All(cv.ensure_list, [ip_network]), + vol.Optional(CONF_TRUSTED_USERS, default={}): vol.Schema( + # we only validate the format of user_id or group_id + { + ip_network: vol.All( + cv.ensure_list, + [ + vol.Or( + cv.uuid4_hex, + vol.Schema({vol.Required(CONF_GROUP): cv.uuid4_hex}), + ) + ], + ) + } + ), + vol.Optional(CONF_ALLOW_BYPASS_LOGIN, default=False): cv.boolean, + }, + extra=vol.PREVENT_EXTRA, +) class InvalidAuthError(HomeAssistantError): @@ -49,14 +53,14 @@ class InvalidUserError(HomeAssistantError): """Raised when try to login as invalid user.""" -@AUTH_PROVIDERS.register('trusted_networks') +@AUTH_PROVIDERS.register("trusted_networks") class TrustedNetworksAuthProvider(AuthProvider): """Trusted Networks auth provider. Allow passwordless access from trusted network. """ - DEFAULT_TITLE = 'Trusted Networks' + DEFAULT_TITLE = "Trusted Networks" @property def trusted_networks(self) -> List[IPNetwork]: @@ -76,49 +80,58 @@ class TrustedNetworksAuthProvider(AuthProvider): async def async_login_flow(self, context: Optional[Dict]) -> LoginFlow: """Return a flow to login.""" assert context is not None - ip_addr = cast(IPAddress, context.get('ip_address')) + ip_addr = cast(IPAddress, context.get("ip_address")) users = await self.store.async_get_users() - available_users = [user for user in users - if not user.system_generated and user.is_active] + available_users = [ + user for user in users if not user.system_generated and user.is_active + ] for ip_net, user_or_group_list in self.trusted_users.items(): if ip_addr in ip_net: - user_list = [user_id for user_id in user_or_group_list - if isinstance(user_id, str)] - group_list = [group[CONF_GROUP] for group in user_or_group_list - if isinstance(group, dict)] - flattened_group_list = [group for sublist in group_list - for group in sublist] + user_list = [ + user_id + for user_id in user_or_group_list + if isinstance(user_id, str) + ] + group_list = [ + group[CONF_GROUP] + for group in user_or_group_list + if isinstance(group, dict) + ] + flattened_group_list = [ + group for sublist in group_list for group in sublist + ] available_users = [ - user for user in available_users - if (user.id in user_list or - any([group.id in flattened_group_list - for group in user.groups])) + user + for user in available_users + if ( + user.id in user_list + or any( + [group.id in flattened_group_list for group in user.groups] + ) + ) ] break return TrustedNetworksLoginFlow( self, ip_addr, - { - user.id: user.name for user in available_users - }, + {user.id: user.name for user in available_users}, self.config[CONF_ALLOW_BYPASS_LOGIN], ) async def async_get_or_create_credentials( - self, flow_result: Dict[str, str]) -> Credentials: + self, flow_result: Dict[str, str] + ) -> Credentials: """Get credentials based on the flow result.""" - user_id = flow_result['user'] + user_id = flow_result["user"] users = await self.store.async_get_users() for user in users: - if (not user.system_generated and - user.is_active and - user.id == user_id): + if not user.system_generated and user.is_active and user.id == user_id: for credential in await self.async_credentials(): - if credential.data['user_id'] == user_id: + if credential.data["user_id"] == user_id: return credential - cred = self.async_create_credentials({'user_id': user_id}) + cred = self.async_create_credentials({"user_id": user_id}) await self.store.async_link_user(user, cred) return cred @@ -126,7 +139,8 @@ class TrustedNetworksAuthProvider(AuthProvider): raise InvalidUserError async def async_user_meta_for_credentials( - self, credentials: Credentials) -> UserMeta: + self, credentials: Credentials + ) -> UserMeta: """Return extra user metadata for credentials. Trusted network auth provider should never create new user. @@ -141,20 +155,24 @@ class TrustedNetworksAuthProvider(AuthProvider): Raise InvalidAuthError if trusted_networks is not configured. """ if not self.trusted_networks: - raise InvalidAuthError('trusted_networks is not configured') + raise InvalidAuthError("trusted_networks is not configured") - if not any(ip_addr in trusted_network for trusted_network - in self.trusted_networks): - raise InvalidAuthError('Not in trusted_networks') + if not any( + ip_addr in trusted_network for trusted_network in self.trusted_networks + ): + raise InvalidAuthError("Not in trusted_networks") class TrustedNetworksLoginFlow(LoginFlow): """Handler for the login flow.""" - def __init__(self, auth_provider: TrustedNetworksAuthProvider, - ip_addr: IPAddress, - available_users: Dict[str, Optional[str]], - allow_bypass_login: bool) -> None: + def __init__( + self, + auth_provider: TrustedNetworksAuthProvider, + ip_addr: IPAddress, + available_users: Dict[str, Optional[str]], + allow_bypass_login: bool, + ) -> None: """Initialize the login flow.""" super().__init__(auth_provider) self._available_users = available_users @@ -162,27 +180,26 @@ class TrustedNetworksLoginFlow(LoginFlow): self._allow_bypass_login = allow_bypass_login async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None) \ - -> Dict[str, Any]: + self, user_input: Optional[Dict[str, str]] = None + ) -> Dict[str, Any]: """Handle the step of the form.""" try: - cast(TrustedNetworksAuthProvider, self._auth_provider)\ - .async_validate_access(self._ip_address) + cast( + TrustedNetworksAuthProvider, self._auth_provider + ).async_validate_access(self._ip_address) except InvalidAuthError: - return self.async_abort( - reason='not_whitelisted' - ) + return self.async_abort(reason="not_whitelisted") if user_input is not None: return await self.async_finish(user_input) if self._allow_bypass_login and len(self._available_users) == 1: - return await self.async_finish({ - 'user': next(iter(self._available_users.keys())) - }) + return await self.async_finish( + {"user": next(iter(self._available_users.keys()))} + ) return self.async_show_form( - step_id='init', - data_schema=vol.Schema({'user': vol.In(self._available_users)}), + step_id="init", + data_schema=vol.Schema({"user": vol.In(self._available_users)}), ) diff --git a/homeassistant/auth/util.py b/homeassistant/auth/util.py index 402caae4618..83834fa7683 100644 --- a/homeassistant/auth/util.py +++ b/homeassistant/auth/util.py @@ -10,4 +10,4 @@ def generate_secret(entropy: int = 32) -> str: Event loop friendly. """ - return binascii.hexlify(os.urandom(entropy)).decode('ascii') + return binascii.hexlify(os.urandom(entropy)).decode("ascii") diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 650ca8e4111..b0eab0da0f3 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -20,32 +20,33 @@ from homeassistant.exceptions import HomeAssistantError _LOGGER = logging.getLogger(__name__) -ERROR_LOG_FILENAME = 'home-assistant.log' +ERROR_LOG_FILENAME = "home-assistant.log" # hass.data key for logging information. -DATA_LOGGING = 'logging' +DATA_LOGGING = "logging" -DEBUGGER_INTEGRATIONS = {'ptvsd', } -CORE_INTEGRATIONS = ('homeassistant', 'persistent_notification') -LOGGING_INTEGRATIONS = {'logger', 'system_log'} +DEBUGGER_INTEGRATIONS = {"ptvsd"} +CORE_INTEGRATIONS = ("homeassistant", "persistent_notification") +LOGGING_INTEGRATIONS = {"logger", "system_log"} STAGE_1_INTEGRATIONS = { # To record data - 'recorder', + "recorder", # To make sure we forward data to other instances - 'mqtt_eventstream', + "mqtt_eventstream", } -async def async_from_config_dict(config: Dict[str, Any], - hass: core.HomeAssistant, - config_dir: Optional[str] = None, - enable_log: bool = True, - verbose: bool = False, - skip_pip: bool = False, - log_rotate_days: Any = None, - log_file: Any = None, - log_no_color: bool = False) \ - -> Optional[core.HomeAssistant]: +async def async_from_config_dict( + config: Dict[str, Any], + hass: core.HomeAssistant, + config_dir: Optional[str] = None, + enable_log: bool = True, + verbose: bool = False, + skip_pip: bool = False, + log_rotate_days: Any = None, + log_file: Any = None, + log_no_color: bool = False, +) -> Optional[core.HomeAssistant]: """Try to configure Home Assistant from a configuration dictionary. Dynamically loads required components and its dependencies. @@ -54,28 +55,30 @@ async def async_from_config_dict(config: Dict[str, Any], start = time() if enable_log: - async_enable_logging(hass, verbose, log_rotate_days, log_file, - log_no_color) + async_enable_logging(hass, verbose, log_rotate_days, log_file, log_no_color) hass.config.skip_pip = skip_pip if skip_pip: - _LOGGER.warning("Skipping pip installation of required modules. " - "This may cause issues") + _LOGGER.warning( + "Skipping pip installation of required modules. " "This may cause issues" + ) core_config = config.get(core.DOMAIN, {}) - api_password = config.get('http', {}).get('api_password') - trusted_networks = config.get('http', {}).get('trusted_networks') + api_password = config.get("http", {}).get("api_password") + trusted_networks = config.get("http", {}).get("trusted_networks") try: await conf_util.async_process_ha_core_config( - hass, core_config, api_password, trusted_networks) + hass, core_config, api_password, trusted_networks + ) except vol.Invalid as config_err: - conf_util.async_log_exception( - config_err, 'homeassistant', core_config, hass) + conf_util.async_log_exception(config_err, "homeassistant", core_config, hass) return None except HomeAssistantError: - _LOGGER.error("Home Assistant core failed to initialize. " - "Further initialization aborted") + _LOGGER.error( + "Home Assistant core failed to initialize. " + "Further initialization aborted" + ) return None # Make a copy because we are mutating it. @@ -83,7 +86,8 @@ async def async_from_config_dict(config: Dict[str, Any], # Merge packages await conf_util.merge_packages_config( - hass, config, core_config.get(conf_util.CONF_PACKAGES, {})) + hass, config, core_config.get(conf_util.CONF_PACKAGES, {}) + ) hass.config_entries = config_entries.ConfigEntries(hass, config) await hass.config_entries.async_initialize() @@ -91,19 +95,20 @@ async def async_from_config_dict(config: Dict[str, Any], await _async_set_up_integrations(hass, config) stop = time() - _LOGGER.info("Home Assistant initialized in %.2fs", stop-start) + _LOGGER.info("Home Assistant initialized in %.2fs", stop - start) return hass -async def async_from_config_file(config_path: str, - hass: core.HomeAssistant, - verbose: bool = False, - skip_pip: bool = True, - log_rotate_days: Any = None, - log_file: Any = None, - log_no_color: bool = False)\ - -> Optional[core.HomeAssistant]: +async def async_from_config_file( + config_path: str, + hass: core.HomeAssistant, + verbose: bool = False, + skip_pip: bool = True, + log_rotate_days: Any = None, + log_file: Any = None, + log_no_color: bool = False, +) -> Optional[core.HomeAssistant]: """Read the configuration file and try to start all the functionality. Will add functionality to 'hass' parameter. @@ -116,15 +121,14 @@ async def async_from_config_file(config_path: str, if not is_virtual_env(): await async_mount_local_lib_path(config_dir) - async_enable_logging(hass, verbose, log_rotate_days, log_file, - log_no_color) + async_enable_logging(hass, verbose, log_rotate_days, log_file, log_no_color) - await hass.async_add_executor_job( - conf_util.process_ha_config_upgrade, hass) + await hass.async_add_executor_job(conf_util.process_ha_config_upgrade, hass) try: config_dict = await hass.async_add_executor_job( - conf_util.load_yaml_config_file, config_path) + conf_util.load_yaml_config_file, config_path + ) except HomeAssistantError as err: _LOGGER.error("Error loading %s: %s", config_path, err) return None @@ -132,43 +136,48 @@ async def async_from_config_file(config_path: str, clear_secret_cache() return await async_from_config_dict( - config_dict, hass, enable_log=False, skip_pip=skip_pip) + config_dict, hass, enable_log=False, skip_pip=skip_pip + ) @core.callback -def async_enable_logging(hass: core.HomeAssistant, - verbose: bool = False, - log_rotate_days: Optional[int] = None, - log_file: Optional[str] = None, - log_no_color: bool = False) -> None: +def async_enable_logging( + hass: core.HomeAssistant, + verbose: bool = False, + log_rotate_days: Optional[int] = None, + log_file: Optional[str] = None, + log_no_color: bool = False, +) -> None: """Set up the logging. This method must be run in the event loop. """ - fmt = ("%(asctime)s %(levelname)s (%(threadName)s) " - "[%(name)s] %(message)s") - datefmt = '%Y-%m-%d %H:%M:%S' + fmt = "%(asctime)s %(levelname)s (%(threadName)s) " "[%(name)s] %(message)s" + datefmt = "%Y-%m-%d %H:%M:%S" if not log_no_color: try: from colorlog import ColoredFormatter + # basicConfig must be called after importing colorlog in order to # ensure that the handlers it sets up wraps the correct streams. logging.basicConfig(level=logging.INFO) colorfmt = "%(log_color)s{}%(reset)s".format(fmt) - logging.getLogger().handlers[0].setFormatter(ColoredFormatter( - colorfmt, - datefmt=datefmt, - reset=True, - log_colors={ - 'DEBUG': 'cyan', - 'INFO': 'green', - 'WARNING': 'yellow', - 'ERROR': 'red', - 'CRITICAL': 'red', - } - )) + logging.getLogger().handlers[0].setFormatter( + ColoredFormatter( + colorfmt, + datefmt=datefmt, + reset=True, + log_colors={ + "DEBUG": "cyan", + "INFO": "green", + "WARNING": "yellow", + "ERROR": "red", + "CRITICAL": "red", + }, + ) + ) except ImportError: pass @@ -177,9 +186,9 @@ def async_enable_logging(hass: core.HomeAssistant, logging.basicConfig(format=fmt, datefmt=datefmt, level=logging.INFO) # Suppress overly verbose logs from libraries that aren't helpful - logging.getLogger('requests').setLevel(logging.WARNING) - logging.getLogger('urllib3').setLevel(logging.WARNING) - logging.getLogger('aiohttp.access').setLevel(logging.WARNING) + logging.getLogger("requests").setLevel(logging.WARNING) + logging.getLogger("urllib3").setLevel(logging.WARNING) + logging.getLogger("aiohttp.access").setLevel(logging.WARNING) # Log errors to a file if we have write access to file or config dir if log_file is None: @@ -192,16 +201,16 @@ def async_enable_logging(hass: core.HomeAssistant, # Check if we can write to the error log if it exists or that # we can create files in the containing directory if not. - if (err_path_exists and os.access(err_log_path, os.W_OK)) or \ - (not err_path_exists and os.access(err_dir, os.W_OK)): + if (err_path_exists and os.access(err_log_path, os.W_OK)) or ( + not err_path_exists and os.access(err_dir, os.W_OK) + ): if log_rotate_days: err_handler = logging.handlers.TimedRotatingFileHandler( - err_log_path, when='midnight', - backupCount=log_rotate_days) # type: logging.FileHandler + err_log_path, when="midnight", backupCount=log_rotate_days + ) # type: logging.FileHandler else: - err_handler = logging.FileHandler( - err_log_path, mode='w', delay=True) + err_handler = logging.FileHandler(err_log_path, mode="w", delay=True) err_handler.setLevel(logging.INFO if verbose else logging.WARNING) err_handler.setFormatter(logging.Formatter(fmt, datefmt=datefmt)) @@ -210,21 +219,19 @@ def async_enable_logging(hass: core.HomeAssistant, async def async_stop_async_handler(_: Any) -> None: """Cleanup async handler.""" - logging.getLogger('').removeHandler(async_handler) # type: ignore + logging.getLogger("").removeHandler(async_handler) # type: ignore await async_handler.async_close(blocking=True) - hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_CLOSE, async_stop_async_handler) + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_CLOSE, async_stop_async_handler) - logger = logging.getLogger('') + logger = logging.getLogger("") logger.addHandler(async_handler) # type: ignore logger.setLevel(logging.INFO) # Save the log file location for access by other components. hass.data[DATA_LOGGING] = err_log_path else: - _LOGGER.error( - "Unable to set up error log %s (access denied)", err_log_path) + _LOGGER.error("Unable to set up error log %s (access denied)", err_log_path) async def async_mount_local_lib_path(config_dir: str) -> str: @@ -232,7 +239,7 @@ async def async_mount_local_lib_path(config_dir: str) -> str: This function is a coroutine. """ - deps_dir = os.path.join(config_dir, 'deps') + deps_dir = os.path.join(config_dir, "deps") lib_dir = await async_get_user_site(deps_dir) if lib_dir not in sys.path: sys.path.insert(0, lib_dir) @@ -243,21 +250,21 @@ async def async_mount_local_lib_path(config_dir: str) -> str: def _get_domains(hass: core.HomeAssistant, config: Dict[str, Any]) -> Set[str]: """Get domains of components to set up.""" # Filter out the repeating and common config section [homeassistant] - domains = set(key.split(' ')[0] for key in config.keys() - if key != core.DOMAIN) + domains = set(key.split(" ")[0] for key in config.keys() if key != core.DOMAIN) # Add config entry domains domains.update(hass.config_entries.async_domains()) # type: ignore # Make sure the Hass.io component is loaded - if 'HASSIO' in os.environ: - domains.add('hassio') + if "HASSIO" in os.environ: + domains.add("hassio") return domains async def _async_set_up_integrations( - hass: core.HomeAssistant, config: Dict[str, Any]) -> None: + hass: core.HomeAssistant, config: Dict[str, Any] +) -> None: """Set up all the integrations.""" domains = _get_domains(hass, config) @@ -265,27 +272,33 @@ async def _async_set_up_integrations( debuggers = domains & DEBUGGER_INTEGRATIONS if debuggers: _LOGGER.debug("Starting up debuggers %s", debuggers) - await asyncio.gather(*( - async_setup_component(hass, domain, config) - for domain in debuggers)) + await asyncio.gather( + *(async_setup_component(hass, domain, config) for domain in debuggers) + ) domains -= DEBUGGER_INTEGRATIONS # Resolve all dependencies of all components so we can find the logging # and integrations that need faster initialization. - resolved_domains_task = asyncio.gather(*( - loader.async_component_dependencies(hass, domain) - for domain in domains - ), return_exceptions=True) + resolved_domains_task = asyncio.gather( + *(loader.async_component_dependencies(hass, domain) for domain in domains), + return_exceptions=True, + ) # Set up core. _LOGGER.debug("Setting up %s", CORE_INTEGRATIONS) - if not all(await asyncio.gather(*( - async_setup_component(hass, domain, config) - for domain in CORE_INTEGRATIONS - ))): - _LOGGER.error("Home Assistant core failed to initialize. " - "Further initialization aborted") + if not all( + await asyncio.gather( + *( + async_setup_component(hass, domain, config) + for domain in CORE_INTEGRATIONS + ) + ) + ): + _LOGGER.error( + "Home Assistant core failed to initialize. " + "Further initialization aborted" + ) return _LOGGER.debug("Home Assistant core initialized") @@ -305,36 +318,32 @@ async def _async_set_up_integrations( if logging_domains: _LOGGER.info("Setting up %s", logging_domains) - await asyncio.gather(*( - async_setup_component(hass, domain, config) - for domain in logging_domains - )) + await asyncio.gather( + *(async_setup_component(hass, domain, config) for domain in logging_domains) + ) # Kick off loading the registries. They don't need to be awaited. asyncio.gather( hass.helpers.device_registry.async_get_registry(), hass.helpers.entity_registry.async_get_registry(), - hass.helpers.area_registry.async_get_registry()) + hass.helpers.area_registry.async_get_registry(), + ) if stage_1_domains: - await asyncio.gather(*( - async_setup_component(hass, domain, config) - for domain in stage_1_domains - )) + await asyncio.gather( + *(async_setup_component(hass, domain, config) for domain in stage_1_domains) + ) # Load all integrations after_dependencies = {} # type: Dict[str, Set[str]] - for int_or_exc in await asyncio.gather(*( - loader.async_get_integration(hass, domain) - for domain in stage_2_domains - ), return_exceptions=True): + for int_or_exc in await asyncio.gather( + *(loader.async_get_integration(hass, domain) for domain in stage_2_domains), + return_exceptions=True, + ): # Exceptions are handled in async_setup_component. - if (isinstance(int_or_exc, loader.Integration) and - int_or_exc.after_dependencies): - after_dependencies[int_or_exc.domain] = set( - int_or_exc.after_dependencies - ) + if isinstance(int_or_exc, loader.Integration) and int_or_exc.after_dependencies: + after_dependencies[int_or_exc.domain] = set(int_or_exc.after_dependencies) last_load = None while stage_2_domains: @@ -344,8 +353,7 @@ async def _async_set_up_integrations( after_deps = after_dependencies.get(domain) # Load if integration has no after_dependencies or they are # all loaded - if (not after_deps or - not after_deps-hass.config.components): + if not after_deps or not after_deps - hass.config.components: domains_to_load.add(domain) if not domains_to_load or domains_to_load == last_load: @@ -353,10 +361,9 @@ async def _async_set_up_integrations( _LOGGER.debug("Setting up %s", domains_to_load) - await asyncio.gather(*( - async_setup_component(hass, domain, config) - for domain in domains_to_load - )) + await asyncio.gather( + *(async_setup_component(hass, domain, config) for domain in domains_to_load) + ) last_load = domains_to_load stage_2_domains -= domains_to_load @@ -366,10 +373,9 @@ async def _async_set_up_integrations( if stage_2_domains: _LOGGER.debug("Final set up: %s", stage_2_domains) - await asyncio.gather(*( - async_setup_component(hass, domain, config) - for domain in stage_2_domains - )) + await asyncio.gather( + *(async_setup_component(hass, domain, config) for domain in stage_2_domains) + ) # Wrap up startup await hass.async_block_till_done() diff --git a/homeassistant/components/__init__.py b/homeassistant/components/__init__.py index 2a95b2b9116..8c14276ccc9 100644 --- a/homeassistant/components/__init__.py +++ b/homeassistant/components/__init__.py @@ -31,11 +31,10 @@ def is_on(hass, entity_id=None): component = getattr(hass.components, domain) except ImportError: - _LOGGER.error('Failed to call %s.is_on: component not found', - domain) + _LOGGER.error("Failed to call %s.is_on: component not found", domain) continue - if not hasattr(component, 'is_on'): + if not hasattr(component, "is_on"): _LOGGER.warning("Integration %s has no is_on method.", domain) continue diff --git a/homeassistant/components/abode/__init__.py b/homeassistant/components/abode/__init__.py index 3a64a5e31f0..f43cbc50f98 100644 --- a/homeassistant/components/abode/__init__.py +++ b/homeassistant/components/abode/__init__.py @@ -6,9 +6,18 @@ from requests.exceptions import HTTPError, ConnectTimeout import voluptuous as vol from homeassistant.const import ( - ATTR_ATTRIBUTION, ATTR_DATE, ATTR_TIME, ATTR_ENTITY_ID, CONF_USERNAME, - CONF_PASSWORD, CONF_EXCLUDE, CONF_NAME, CONF_LIGHTS, - EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START) + ATTR_ATTRIBUTION, + ATTR_DATE, + ATTR_TIME, + ATTR_ENTITY_ID, + CONF_USERNAME, + CONF_PASSWORD, + CONF_EXCLUDE, + CONF_NAME, + CONF_LIGHTS, + EVENT_HOMEASSISTANT_STOP, + EVENT_HOMEASSISTANT_START, +) from homeassistant.helpers import config_validation as cv from homeassistant.helpers import discovery from homeassistant.helpers.entity import Entity @@ -17,77 +26,88 @@ _LOGGER = logging.getLogger(__name__) ATTRIBUTION = "Data provided by goabode.com" -CONF_POLLING = 'polling' +CONF_POLLING = "polling" -DOMAIN = 'abode' -DEFAULT_CACHEDB = './abodepy_cache.pickle' +DOMAIN = "abode" +DEFAULT_CACHEDB = "./abodepy_cache.pickle" -NOTIFICATION_ID = 'abode_notification' -NOTIFICATION_TITLE = 'Abode Security Setup' +NOTIFICATION_ID = "abode_notification" +NOTIFICATION_TITLE = "Abode Security Setup" -EVENT_ABODE_ALARM = 'abode_alarm' -EVENT_ABODE_ALARM_END = 'abode_alarm_end' -EVENT_ABODE_AUTOMATION = 'abode_automation' -EVENT_ABODE_FAULT = 'abode_panel_fault' -EVENT_ABODE_RESTORE = 'abode_panel_restore' +EVENT_ABODE_ALARM = "abode_alarm" +EVENT_ABODE_ALARM_END = "abode_alarm_end" +EVENT_ABODE_AUTOMATION = "abode_automation" +EVENT_ABODE_FAULT = "abode_panel_fault" +EVENT_ABODE_RESTORE = "abode_panel_restore" -SERVICE_SETTINGS = 'change_setting' -SERVICE_CAPTURE_IMAGE = 'capture_image' -SERVICE_TRIGGER = 'trigger_quick_action' +SERVICE_SETTINGS = "change_setting" +SERVICE_CAPTURE_IMAGE = "capture_image" +SERVICE_TRIGGER = "trigger_quick_action" -ATTR_DEVICE_ID = 'device_id' -ATTR_DEVICE_NAME = 'device_name' -ATTR_DEVICE_TYPE = 'device_type' -ATTR_EVENT_CODE = 'event_code' -ATTR_EVENT_NAME = 'event_name' -ATTR_EVENT_TYPE = 'event_type' -ATTR_EVENT_UTC = 'event_utc' -ATTR_SETTING = 'setting' -ATTR_USER_NAME = 'user_name' -ATTR_VALUE = 'value' +ATTR_DEVICE_ID = "device_id" +ATTR_DEVICE_NAME = "device_name" +ATTR_DEVICE_TYPE = "device_type" +ATTR_EVENT_CODE = "event_code" +ATTR_EVENT_NAME = "event_name" +ATTR_EVENT_TYPE = "event_type" +ATTR_EVENT_UTC = "event_utc" +ATTR_SETTING = "setting" +ATTR_USER_NAME = "user_name" +ATTR_VALUE = "value" ABODE_DEVICE_ID_LIST_SCHEMA = vol.Schema([str]) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_POLLING, default=False): cv.boolean, - vol.Optional(CONF_EXCLUDE, default=[]): ABODE_DEVICE_ID_LIST_SCHEMA, - vol.Optional(CONF_LIGHTS, default=[]): ABODE_DEVICE_ID_LIST_SCHEMA - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_POLLING, default=False): cv.boolean, + vol.Optional(CONF_EXCLUDE, default=[]): ABODE_DEVICE_ID_LIST_SCHEMA, + vol.Optional(CONF_LIGHTS, default=[]): ABODE_DEVICE_ID_LIST_SCHEMA, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) -CHANGE_SETTING_SCHEMA = vol.Schema({ - vol.Required(ATTR_SETTING): cv.string, - vol.Required(ATTR_VALUE): cv.string -}) +CHANGE_SETTING_SCHEMA = vol.Schema( + {vol.Required(ATTR_SETTING): cv.string, vol.Required(ATTR_VALUE): cv.string} +) -CAPTURE_IMAGE_SCHEMA = vol.Schema({ - ATTR_ENTITY_ID: cv.entity_ids, -}) +CAPTURE_IMAGE_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids}) -TRIGGER_SCHEMA = vol.Schema({ - ATTR_ENTITY_ID: cv.entity_ids, -}) +TRIGGER_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids}) ABODE_PLATFORMS = [ - 'alarm_control_panel', 'binary_sensor', 'lock', 'switch', 'cover', - 'camera', 'light', 'sensor' + "alarm_control_panel", + "binary_sensor", + "lock", + "switch", + "cover", + "camera", + "light", + "sensor", ] class AbodeSystem: """Abode System class.""" - def __init__(self, username, password, cache, - name, polling, exclude, lights): + def __init__(self, username, password, cache, name, polling, exclude, lights): """Initialize the system.""" import abodepy + self.abode = abodepy.Abode( - username, password, auto_login=True, get_devices=True, - get_automations=True, cache_path=cache) + username, + password, + auto_login=True, + get_devices=True, + get_automations=True, + cache_path=cache, + ) self.name = name self.polling = polling self.exclude = exclude @@ -106,9 +126,9 @@ class AbodeSystem: """Check if a switch device is configured as a light.""" import abodepy.helpers.constants as CONST - return (device.generic_type == CONST.TYPE_LIGHT or - (device.generic_type == CONST.TYPE_SWITCH and - device.device_id in self.lights)) + return device.generic_type == CONST.TYPE_LIGHT or ( + device.generic_type == CONST.TYPE_SWITCH and device.device_id in self.lights + ) def setup(hass, config): @@ -126,16 +146,18 @@ def setup(hass, config): try: cache = hass.config.path(DEFAULT_CACHEDB) hass.data[DOMAIN] = AbodeSystem( - username, password, cache, name, polling, exclude, lights) + username, password, cache, name, polling, exclude, lights + ) except (AbodeException, ConnectTimeout, HTTPError) as ex: _LOGGER.error("Unable to connect to Abode: %s", str(ex)) hass.components.persistent_notification.create( - 'Error: {}
' - 'You will need to restart hass after fixing.' - ''.format(ex), + "Error: {}
" + "You will need to restart hass after fixing." + "".format(ex), title=NOTIFICATION_TITLE, - notification_id=NOTIFICATION_ID) + notification_id=NOTIFICATION_ID, + ) return False setup_hass_services(hass) @@ -166,8 +188,11 @@ def setup_hass_services(hass): """Capture a new image.""" entity_ids = call.data.get(ATTR_ENTITY_ID) - target_devices = [device for device in hass.data[DOMAIN].devices - if device.entity_id in entity_ids] + target_devices = [ + device + for device in hass.data[DOMAIN].devices + if device.entity_id in entity_ids + ] for device in target_devices: device.capture() @@ -176,27 +201,31 @@ def setup_hass_services(hass): """Trigger a quick action.""" entity_ids = call.data.get(ATTR_ENTITY_ID, None) - target_devices = [device for device in hass.data[DOMAIN].devices - if device.entity_id in entity_ids] + target_devices = [ + device + for device in hass.data[DOMAIN].devices + if device.entity_id in entity_ids + ] for device in target_devices: device.trigger() hass.services.register( - DOMAIN, SERVICE_SETTINGS, change_setting, - schema=CHANGE_SETTING_SCHEMA) + DOMAIN, SERVICE_SETTINGS, change_setting, schema=CHANGE_SETTING_SCHEMA + ) hass.services.register( - DOMAIN, SERVICE_CAPTURE_IMAGE, capture_image, - schema=CAPTURE_IMAGE_SCHEMA) + DOMAIN, SERVICE_CAPTURE_IMAGE, capture_image, schema=CAPTURE_IMAGE_SCHEMA + ) hass.services.register( - DOMAIN, SERVICE_TRIGGER, trigger_quick_action, - schema=TRIGGER_SCHEMA) + DOMAIN, SERVICE_TRIGGER, trigger_quick_action, schema=TRIGGER_SCHEMA + ) def setup_hass_events(hass): """Home Assistant start and stop callbacks.""" + def startup(event): """Listen for push events.""" hass.data[DOMAIN].abode.events.start() @@ -222,28 +251,32 @@ def setup_abode_events(hass): def event_callback(event, event_json): """Handle an event callback from Abode.""" data = { - ATTR_DEVICE_ID: event_json.get(ATTR_DEVICE_ID, ''), - ATTR_DEVICE_NAME: event_json.get(ATTR_DEVICE_NAME, ''), - ATTR_DEVICE_TYPE: event_json.get(ATTR_DEVICE_TYPE, ''), - ATTR_EVENT_CODE: event_json.get(ATTR_EVENT_CODE, ''), - ATTR_EVENT_NAME: event_json.get(ATTR_EVENT_NAME, ''), - ATTR_EVENT_TYPE: event_json.get(ATTR_EVENT_TYPE, ''), - ATTR_EVENT_UTC: event_json.get(ATTR_EVENT_UTC, ''), - ATTR_USER_NAME: event_json.get(ATTR_USER_NAME, ''), - ATTR_DATE: event_json.get(ATTR_DATE, ''), - ATTR_TIME: event_json.get(ATTR_TIME, ''), + ATTR_DEVICE_ID: event_json.get(ATTR_DEVICE_ID, ""), + ATTR_DEVICE_NAME: event_json.get(ATTR_DEVICE_NAME, ""), + ATTR_DEVICE_TYPE: event_json.get(ATTR_DEVICE_TYPE, ""), + ATTR_EVENT_CODE: event_json.get(ATTR_EVENT_CODE, ""), + ATTR_EVENT_NAME: event_json.get(ATTR_EVENT_NAME, ""), + ATTR_EVENT_TYPE: event_json.get(ATTR_EVENT_TYPE, ""), + ATTR_EVENT_UTC: event_json.get(ATTR_EVENT_UTC, ""), + ATTR_USER_NAME: event_json.get(ATTR_USER_NAME, ""), + ATTR_DATE: event_json.get(ATTR_DATE, ""), + ATTR_TIME: event_json.get(ATTR_TIME, ""), } hass.bus.fire(event, data) - events = [TIMELINE.ALARM_GROUP, TIMELINE.ALARM_END_GROUP, - TIMELINE.PANEL_FAULT_GROUP, TIMELINE.PANEL_RESTORE_GROUP, - TIMELINE.AUTOMATION_GROUP] + events = [ + TIMELINE.ALARM_GROUP, + TIMELINE.ALARM_END_GROUP, + TIMELINE.PANEL_FAULT_GROUP, + TIMELINE.PANEL_RESTORE_GROUP, + TIMELINE.AUTOMATION_GROUP, + ] for event in events: hass.data[DOMAIN].abode.events.add_event_callback( - event, - partial(event_callback, event)) + event, partial(event_callback, event) + ) class AbodeDevice(Entity): @@ -258,7 +291,8 @@ class AbodeDevice(Entity): """Subscribe Abode events.""" self.hass.async_add_job( self._data.abode.events.add_device_callback, - self._device.device_id, self._update_callback + self._device.device_id, + self._update_callback, ) @property @@ -280,10 +314,10 @@ class AbodeDevice(Entity): """Return the state attributes.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, - 'device_id': self._device.device_id, - 'battery_low': self._device.battery_low, - 'no_response': self._device.no_response, - 'device_type': self._device.type + "device_id": self._device.device_id, + "battery_low": self._device.battery_low, + "no_response": self._device.no_response, + "device_type": self._device.type, } def _update_callback(self, device): @@ -305,7 +339,8 @@ class AbodeAutomation(Entity): if self._event: self.hass.async_add_job( self._data.abode.events.add_event_callback, - self._event, self._update_callback + self._event, + self._update_callback, ) @property @@ -327,9 +362,9 @@ class AbodeAutomation(Entity): """Return the state attributes.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, - 'automation_id': self._automation.automation_id, - 'type': self._automation.type, - 'sub_type': self._automation.sub_type + "automation_id": self._automation.automation_id, + "type": self._automation.type, + "sub_type": self._automation.sub_type, } def _update_callback(self, device): diff --git a/homeassistant/components/abode/alarm_control_panel.py b/homeassistant/components/abode/alarm_control_panel.py index d1d75b7417e..c5c10e65302 100644 --- a/homeassistant/components/abode/alarm_control_panel.py +++ b/homeassistant/components/abode/alarm_control_panel.py @@ -3,14 +3,17 @@ import logging import homeassistant.components.alarm_control_panel as alarm from homeassistant.const import ( - ATTR_ATTRIBUTION, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, - STATE_ALARM_DISARMED) + ATTR_ATTRIBUTION, + STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_HOME, + STATE_ALARM_DISARMED, +) from . import ATTRIBUTION, DOMAIN as ABODE_DOMAIN, AbodeDevice _LOGGER = logging.getLogger(__name__) -ICON = 'mdi:security' +ICON = "mdi:security" def setup_platform(hass, config, add_entities, discovery_info=None): @@ -72,7 +75,7 @@ class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanel): """Return the state attributes.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, - 'device_id': self._device.device_id, - 'battery_backup': self._device.battery, - 'cellular_backup': self._device.is_cellular, + "device_id": self._device.device_id, + "battery_backup": self._device.battery, + "cellular_backup": self._device.is_cellular, } diff --git a/homeassistant/components/abode/binary_sensor.py b/homeassistant/components/abode/binary_sensor.py index e3f74e9f4ec..e37f6a465a4 100644 --- a/homeassistant/components/abode/binary_sensor.py +++ b/homeassistant/components/abode/binary_sensor.py @@ -15,9 +15,13 @@ def setup_platform(hass, config, add_entities, discovery_info=None): data = hass.data[ABODE_DOMAIN] - device_types = [CONST.TYPE_CONNECTIVITY, CONST.TYPE_MOISTURE, - CONST.TYPE_MOTION, CONST.TYPE_OCCUPANCY, - CONST.TYPE_OPENING] + device_types = [ + CONST.TYPE_CONNECTIVITY, + CONST.TYPE_MOISTURE, + CONST.TYPE_MOTION, + CONST.TYPE_OCCUPANCY, + CONST.TYPE_OPENING, + ] devices = [] for device in data.abode.get_devices(generic_type=device_types): @@ -26,13 +30,15 @@ def setup_platform(hass, config, add_entities, discovery_info=None): devices.append(AbodeBinarySensor(data, device)) - for automation in data.abode.get_automations( - generic_type=CONST.TYPE_QUICK_ACTION): + for automation in data.abode.get_automations(generic_type=CONST.TYPE_QUICK_ACTION): if data.is_automation_excluded(automation): continue - devices.append(AbodeQuickActionBinarySensor( - data, automation, TIMELINE.AUTOMATION_EDIT_GROUP)) + devices.append( + AbodeQuickActionBinarySensor( + data, automation, TIMELINE.AUTOMATION_EDIT_GROUP + ) + ) data.devices.extend(devices) diff --git a/homeassistant/components/abode/camera.py b/homeassistant/components/abode/camera.py index d0e4e833029..95755a644e2 100644 --- a/homeassistant/components/abode/camera.py +++ b/homeassistant/components/abode/camera.py @@ -49,7 +49,8 @@ class AbodeCamera(AbodeDevice, Camera): self.hass.async_add_job( self._data.abode.events.add_timeline_callback, - self._event, self._capture_callback + self._event, + self._capture_callback, ) def capture(self): @@ -66,8 +67,7 @@ class AbodeCamera(AbodeDevice, Camera): """Attempt to download the most recent capture.""" if self._device.image_url: try: - self._response = requests.get( - self._device.image_url, stream=True) + self._response = requests.get(self._device.image_url, stream=True) self._response.raise_for_status() except requests.HTTPError as err: diff --git a/homeassistant/components/abode/light.py b/homeassistant/components/abode/light.py index 6b3e5025c51..8e6691560e5 100644 --- a/homeassistant/components/abode/light.py +++ b/homeassistant/components/abode/light.py @@ -3,10 +3,18 @@ import logging from math import ceil from homeassistant.components.light import ( - ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, SUPPORT_COLOR_TEMP, Light) + ATTR_BRIGHTNESS, + ATTR_COLOR_TEMP, + ATTR_HS_COLOR, + SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, + SUPPORT_COLOR_TEMP, + Light, +) from homeassistant.util.color import ( - color_temperature_kelvin_to_mired, color_temperature_mired_to_kelvin) + color_temperature_kelvin_to_mired, + color_temperature_mired_to_kelvin, +) from . import DOMAIN as ABODE_DOMAIN, AbodeDevice @@ -42,8 +50,8 @@ class AbodeLight(AbodeDevice, Light): """Turn on the light.""" if ATTR_COLOR_TEMP in kwargs and self._device.is_color_capable: self._device.set_color_temp( - int(color_temperature_mired_to_kelvin( - kwargs[ATTR_COLOR_TEMP]))) + int(color_temperature_mired_to_kelvin(kwargs[ATTR_COLOR_TEMP])) + ) if ATTR_HS_COLOR in kwargs and self._device.is_color_capable: self._device.set_color(kwargs[ATTR_HS_COLOR]) diff --git a/homeassistant/components/abode/sensor.py b/homeassistant/components/abode/sensor.py index b7e8fc1a118..ba28eab79c7 100644 --- a/homeassistant/components/abode/sensor.py +++ b/homeassistant/components/abode/sensor.py @@ -2,7 +2,10 @@ import logging from homeassistant.const import ( - DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE) + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_ILLUMINANCE, + DEVICE_CLASS_TEMPERATURE, +) from . import DOMAIN as ABODE_DOMAIN, AbodeDevice @@ -10,9 +13,9 @@ _LOGGER = logging.getLogger(__name__) # Sensor types: Name, icon SENSOR_TYPES = { - 'temp': ['Temperature', DEVICE_CLASS_TEMPERATURE], - 'humidity': ['Humidity', DEVICE_CLASS_HUMIDITY], - 'lux': ['Lux', DEVICE_CLASS_ILLUMINANCE], + "temp": ["Temperature", DEVICE_CLASS_TEMPERATURE], + "humidity": ["Humidity", DEVICE_CLASS_HUMIDITY], + "lux": ["Lux", DEVICE_CLASS_ILLUMINANCE], } @@ -42,8 +45,9 @@ class AbodeSensor(AbodeDevice): """Initialize a sensor for an Abode device.""" super().__init__(data, device) self._sensor_type = sensor_type - self._name = '{0} {1}'.format( - self._device.name, SENSOR_TYPES[self._sensor_type][0]) + self._name = "{0} {1}".format( + self._device.name, SENSOR_TYPES[self._sensor_type][0] + ) self._device_class = SENSOR_TYPES[self._sensor_type][1] @property @@ -59,19 +63,19 @@ class AbodeSensor(AbodeDevice): @property def state(self): """Return the state of the sensor.""" - if self._sensor_type == 'temp': + if self._sensor_type == "temp": return self._device.temp - if self._sensor_type == 'humidity': + if self._sensor_type == "humidity": return self._device.humidity - if self._sensor_type == 'lux': + if self._sensor_type == "lux": return self._device.lux @property def unit_of_measurement(self): """Return the units of measurement.""" - if self._sensor_type == 'temp': + if self._sensor_type == "temp": return self._device.temp_unit - if self._sensor_type == 'humidity': + if self._sensor_type == "humidity": return self._device.humidity_unit - if self._sensor_type == 'lux': + if self._sensor_type == "lux": return self._device.lux_unit diff --git a/homeassistant/components/abode/switch.py b/homeassistant/components/abode/switch.py index 74d1ea57bad..82a550df1a5 100644 --- a/homeassistant/components/abode/switch.py +++ b/homeassistant/components/abode/switch.py @@ -25,13 +25,13 @@ def setup_platform(hass, config, add_entities, discovery_info=None): devices.append(AbodeSwitch(data, device)) # Get all Abode automations that can be enabled/disabled - for automation in data.abode.get_automations( - generic_type=CONST.TYPE_AUTOMATION): + for automation in data.abode.get_automations(generic_type=CONST.TYPE_AUTOMATION): if data.is_automation_excluded(automation): continue - devices.append(AbodeAutomationSwitch( - data, automation, TIMELINE.AUTOMATION_EDIT_GROUP)) + devices.append( + AbodeAutomationSwitch(data, automation, TIMELINE.AUTOMATION_EDIT_GROUP) + ) data.devices.extend(devices) diff --git a/homeassistant/components/acer_projector/switch.py b/homeassistant/components/acer_projector/switch.py index 242f3f4a009..558cf84d0e1 100644 --- a/homeassistant/components/acer_projector/switch.py +++ b/homeassistant/components/acer_projector/switch.py @@ -4,50 +4,58 @@ import re import voluptuous as vol -from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) +from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA from homeassistant.const import ( - STATE_ON, STATE_OFF, STATE_UNKNOWN, CONF_NAME, CONF_FILENAME) + STATE_ON, + STATE_OFF, + STATE_UNKNOWN, + CONF_NAME, + CONF_FILENAME, +) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -CONF_TIMEOUT = 'timeout' -CONF_WRITE_TIMEOUT = 'write_timeout' +CONF_TIMEOUT = "timeout" +CONF_WRITE_TIMEOUT = "write_timeout" -DEFAULT_NAME = 'Acer Projector' +DEFAULT_NAME = "Acer Projector" DEFAULT_TIMEOUT = 1 DEFAULT_WRITE_TIMEOUT = 1 -ECO_MODE = 'ECO Mode' +ECO_MODE = "ECO Mode" -ICON = 'mdi:projector' +ICON = "mdi:projector" -INPUT_SOURCE = 'Input Source' +INPUT_SOURCE = "Input Source" -LAMP = 'Lamp' -LAMP_HOURS = 'Lamp Hours' +LAMP = "Lamp" +LAMP_HOURS = "Lamp Hours" -MODEL = 'Model' +MODEL = "Model" # Commands known to the projector CMD_DICT = { - LAMP: '* 0 Lamp ?\r', - LAMP_HOURS: '* 0 Lamp\r', - INPUT_SOURCE: '* 0 Src ?\r', - ECO_MODE: '* 0 IR 052\r', - MODEL: '* 0 IR 035\r', - STATE_ON: '* 0 IR 001\r', - STATE_OFF: '* 0 IR 002\r', + LAMP: "* 0 Lamp ?\r", + LAMP_HOURS: "* 0 Lamp\r", + INPUT_SOURCE: "* 0 Src ?\r", + ECO_MODE: "* 0 IR 052\r", + MODEL: "* 0 IR 035\r", + STATE_ON: "* 0 IR 001\r", + STATE_OFF: "* 0 IR 002\r", } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_FILENAME): cv.isdevice, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, - vol.Optional(CONF_WRITE_TIMEOUT, default=DEFAULT_WRITE_TIMEOUT): - cv.positive_int, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_FILENAME): cv.isdevice, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, + vol.Optional( + CONF_WRITE_TIMEOUT, default=DEFAULT_WRITE_TIMEOUT + ): cv.positive_int, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -66,9 +74,10 @@ class AcerSwitch(SwitchDevice): def __init__(self, serial_port, name, timeout, write_timeout, **kwargs): """Init of the Acer projector.""" import serial + self.ser = serial.Serial( - port=serial_port, timeout=timeout, write_timeout=write_timeout, - **kwargs) + port=serial_port, timeout=timeout, write_timeout=write_timeout, **kwargs + ) self._serial_port = serial_port self._name = name self._state = False @@ -82,6 +91,7 @@ class AcerSwitch(SwitchDevice): def _write_read(self, msg): """Write to the projector and read the return.""" import serial + ret = "" # Sometimes the projector won't answer for no reason or the projector # was disconnected during runtime. @@ -89,14 +99,14 @@ class AcerSwitch(SwitchDevice): try: if not self.ser.is_open: self.ser.open() - msg = msg.encode('utf-8') + msg = msg.encode("utf-8") self.ser.write(msg) # Size is an experience value there is no real limit. # AFAIK there is no limit and no end character so we will usually # need to wait for timeout - ret = self.ser.read_until(size=20).decode('utf-8') + ret = self.ser.read_until(size=20).decode("utf-8") except serial.SerialException: - _LOGGER.error('Problem communicating with %s', self._serial_port) + _LOGGER.error("Problem communicating with %s", self._serial_port) self.ser.close() return ret @@ -104,7 +114,7 @@ class AcerSwitch(SwitchDevice): """Write msg, obtain answer and format output.""" # answers are formatted as ***\answer\r*** awns = self._write_read(msg) - match = re.search(r'\r(.+)\r', awns) + match = re.search(r"\r(.+)\r", awns) if match: return match.group(1) return STATE_UNKNOWN @@ -133,10 +143,10 @@ class AcerSwitch(SwitchDevice): """Get the latest state from the projector.""" msg = CMD_DICT[LAMP] awns = self._write_read_format(msg) - if awns == 'Lamp 1': + if awns == "Lamp 1": self._state = True self._available = True - elif awns == 'Lamp 0': + elif awns == "Lamp 0": self._state = False self._available = True else: diff --git a/homeassistant/components/actiontec/device_tracker.py b/homeassistant/components/actiontec/device_tracker.py index 3f0c8786794..e07dd2622be 100644 --- a/homeassistant/components/actiontec/device_tracker.py +++ b/homeassistant/components/actiontec/device_tracker.py @@ -8,22 +8,28 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv import homeassistant.util.dt as dt_util from homeassistant.components.device_tracker import ( - DOMAIN, PLATFORM_SCHEMA, DeviceScanner) + DOMAIN, + PLATFORM_SCHEMA, + DeviceScanner, +) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME _LOGGER = logging.getLogger(__name__) _LEASES_REGEX = re.compile( - r'(?P([0-9]{1,3}[\.]){3}[0-9]{1,3})' + - r'\smac:\s(?P([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))' + - r'\svalid\sfor:\s(?P(-?\d+))' + - r'\ssec') + r"(?P([0-9]{1,3}[\.]){3}[0-9]{1,3})" + + r"\smac:\s(?P([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))" + + r"\svalid\sfor:\s(?P(-?\d+))" + + r"\ssec" +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_USERNAME): cv.string -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string, + } +) def get_scanner(hass, config): @@ -32,7 +38,7 @@ def get_scanner(hass, config): return scanner if scanner.success_init else None -Device = namedtuple('Device', ['mac', 'ip', 'last_update']) +Device = namedtuple("Device", ["mac", "ip", "last_update"]) class ActiontecDeviceScanner(DeviceScanner): @@ -75,9 +81,11 @@ class ActiontecDeviceScanner(DeviceScanner): actiontec_data = self.get_actiontec_data() if not actiontec_data: return False - self.last_results = [Device(data['mac'], name, now) - for name, data in actiontec_data.items() - if data['timevalid'] > -60] + self.last_results = [ + Device(data["mac"], name, now) + for name, data in actiontec_data.items() + if data["timevalid"] > -60 + ] _LOGGER.info("Scan successful") return True @@ -85,17 +93,16 @@ class ActiontecDeviceScanner(DeviceScanner): """Retrieve data from Actiontec MI424WR and return parsed result.""" try: telnet = telnetlib.Telnet(self.host) - telnet.read_until(b'Username: ') - telnet.write((self.username + '\n').encode('ascii')) - telnet.read_until(b'Password: ') - telnet.write((self.password + '\n').encode('ascii')) - prompt = telnet.read_until( - b'Wireless Broadband Router> ').split(b'\n')[-1] - telnet.write('firewall mac_cache_dump\n'.encode('ascii')) - telnet.write('\n'.encode('ascii')) + telnet.read_until(b"Username: ") + telnet.write((self.username + "\n").encode("ascii")) + telnet.read_until(b"Password: ") + telnet.write((self.password + "\n").encode("ascii")) + prompt = telnet.read_until(b"Wireless Broadband Router> ").split(b"\n")[-1] + telnet.write("firewall mac_cache_dump\n".encode("ascii")) + telnet.write("\n".encode("ascii")) telnet.read_until(prompt) - leases_result = telnet.read_until(prompt).split(b'\n')[1:-1] - telnet.write('exit\n'.encode('ascii')) + leases_result = telnet.read_until(prompt).split(b"\n")[1:-1] + telnet.write("exit\n".encode("ascii")) except EOFError: _LOGGER.exception("Unexpected response from router") return @@ -105,11 +112,11 @@ class ActiontecDeviceScanner(DeviceScanner): devices = {} for lease in leases_result: - match = _LEASES_REGEX.search(lease.decode('utf-8')) + match = _LEASES_REGEX.search(lease.decode("utf-8")) if match is not None: - devices[match.group('ip')] = { - 'ip': match.group('ip'), - 'mac': match.group('mac').upper(), - 'timevalid': int(match.group('timevalid')) - } + devices[match.group("ip")] = { + "ip": match.group("ip"), + "mac": match.group("mac").upper(), + "timevalid": int(match.group("timevalid")), + } return devices diff --git a/homeassistant/components/adguard/__init__.py b/homeassistant/components/adguard/__init__.py index 15b8b9978f6..ba716ae0f9c 100644 --- a/homeassistant/components/adguard/__init__.py +++ b/homeassistant/components/adguard/__init__.py @@ -6,13 +6,27 @@ from adguardhome import AdGuardHome, AdGuardHomeError import voluptuous as vol from homeassistant.components.adguard.const import ( - CONF_FORCE, DATA_ADGUARD_CLIENT, DATA_ADGUARD_VERION, DOMAIN, - SERVICE_ADD_URL, SERVICE_DISABLE_URL, SERVICE_ENABLE_URL, SERVICE_REFRESH, - SERVICE_REMOVE_URL) + CONF_FORCE, + DATA_ADGUARD_CLIENT, + DATA_ADGUARD_VERION, + DOMAIN, + SERVICE_ADD_URL, + SERVICE_DISABLE_URL, + SERVICE_ENABLE_URL, + SERVICE_REFRESH, + SERVICE_REMOVE_URL, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( - CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_SSL, CONF_URL, - CONF_USERNAME, CONF_VERIFY_SSL) + CONF_HOST, + CONF_NAME, + CONF_PASSWORD, + CONF_PORT, + CONF_SSL, + CONF_URL, + CONF_USERNAME, + CONF_VERIFY_SSL, +) from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.entity import Entity @@ -34,9 +48,7 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: return True -async def async_setup_entry( - hass: HomeAssistantType, entry: ConfigEntry -) -> bool: +async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: """Set up AdGuard Home from a config entry.""" session = async_get_clientsession(hass, entry.data[CONF_VERIFY_SSL]) adguard = AdGuardHome( @@ -52,7 +64,7 @@ async def async_setup_entry( hass.data.setdefault(DOMAIN, {})[DATA_ADGUARD_CLIENT] = adguard - for component in 'sensor', 'switch': + for component in "sensor", "switch": hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, component) ) @@ -98,9 +110,7 @@ async def async_setup_entry( return True -async def async_unload_entry( - hass: HomeAssistantType, entry: ConfigType -) -> bool: +async def async_unload_entry(hass: HomeAssistantType, entry: ConfigType) -> bool: """Unload AdGuard Home config entry.""" hass.services.async_remove(DOMAIN, SERVICE_ADD_URL) hass.services.async_remove(DOMAIN, SERVICE_REMOVE_URL) @@ -108,7 +118,7 @@ async def async_unload_entry( hass.services.async_remove(DOMAIN, SERVICE_DISABLE_URL) hass.services.async_remove(DOMAIN, SERVICE_REFRESH) - for component in 'sensor', 'switch': + for component in "sensor", "switch": await hass.config_entries.async_forward_entry_unload(entry, component) del hass.data[DOMAIN] @@ -166,15 +176,10 @@ class AdGuardHomeDeviceEntity(AdGuardHomeEntity): def device_info(self) -> Dict[str, Any]: """Return device information about this AdGuard Home instance.""" return { - 'identifiers': { - ( - DOMAIN, - self.adguard.host, - self.adguard.port, - self.adguard.base_path, - ) + "identifiers": { + (DOMAIN, self.adguard.host, self.adguard.port, self.adguard.base_path) }, - 'name': 'AdGuard Home', - 'manufacturer': 'AdGuard Team', - 'sw_version': self.hass.data[DOMAIN].get(DATA_ADGUARD_VERION), + "name": "AdGuard Home", + "manufacturer": "AdGuard Team", + "sw_version": self.hass.data[DOMAIN].get(DATA_ADGUARD_VERION), } diff --git a/homeassistant/components/adguard/config_flow.py b/homeassistant/components/adguard/config_flow.py index 9ef789f83a8..5a096aeceed 100644 --- a/homeassistant/components/adguard/config_flow.py +++ b/homeassistant/components/adguard/config_flow.py @@ -8,8 +8,13 @@ from homeassistant import config_entries from homeassistant.components.adguard.const import DOMAIN from homeassistant.config_entries import ConfigFlow from homeassistant.const import ( - CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_SSL, CONF_USERNAME, - CONF_VERIFY_SSL) + CONF_HOST, + CONF_PASSWORD, + CONF_PORT, + CONF_SSL, + CONF_USERNAME, + CONF_VERIFY_SSL, +) from homeassistant.helpers.aiohttp_client import async_get_clientsession _LOGGER = logging.getLogger(__name__) @@ -31,7 +36,7 @@ class AdGuardHomeFlowHandler(ConfigFlow): async def _show_setup_form(self, errors=None): """Show the setup form to the user.""" return self.async_show_form( - step_id='user', + step_id="user", data_schema=vol.Schema( { vol.Required(CONF_HOST): str, @@ -48,10 +53,8 @@ class AdGuardHomeFlowHandler(ConfigFlow): async def _show_hassio_form(self, errors=None): """Show the Hass.io confirmation form to the user.""" return self.async_show_form( - step_id='hassio_confirm', - description_placeholders={ - 'addon': self._hassio_discovery['addon'] - }, + step_id="hassio_confirm", + description_placeholders={"addon": self._hassio_discovery["addon"]}, data_schema=vol.Schema({}), errors=errors or {}, ) @@ -59,16 +62,14 @@ class AdGuardHomeFlowHandler(ConfigFlow): async def async_step_user(self, user_input=None): """Handle a flow initiated by the user.""" if self._async_current_entries(): - return self.async_abort(reason='single_instance_allowed') + return self.async_abort(reason="single_instance_allowed") if user_input is None: return await self._show_setup_form(user_input) errors = {} - session = async_get_clientsession( - self.hass, user_input[CONF_VERIFY_SSL] - ) + session = async_get_clientsession(self.hass, user_input[CONF_VERIFY_SSL]) adguard = AdGuardHome( user_input[CONF_HOST], @@ -84,7 +85,7 @@ class AdGuardHomeFlowHandler(ConfigFlow): try: await adguard.version() except AdGuardHomeConnectionError: - errors['base'] = 'connection_error' + errors["base"] = "connection_error" return await self._show_setup_form(errors) return self.async_create_entry( @@ -112,25 +113,30 @@ class AdGuardHomeFlowHandler(ConfigFlow): cur_entry = entries[0] - if (cur_entry.data[CONF_HOST] == user_input[CONF_HOST] and - cur_entry.data[CONF_PORT] == user_input[CONF_PORT]): - return self.async_abort(reason='single_instance_allowed') + if ( + cur_entry.data[CONF_HOST] == user_input[CONF_HOST] + and cur_entry.data[CONF_PORT] == user_input[CONF_PORT] + ): + return self.async_abort(reason="single_instance_allowed") is_loaded = cur_entry.state == config_entries.ENTRY_STATE_LOADED if is_loaded: await self.hass.config_entries.async_unload(cur_entry.entry_id) - self.hass.config_entries.async_update_entry(cur_entry, data={ - **cur_entry.data, - CONF_HOST: user_input[CONF_HOST], - CONF_PORT: user_input[CONF_PORT], - }) + self.hass.config_entries.async_update_entry( + cur_entry, + data={ + **cur_entry.data, + CONF_HOST: user_input[CONF_HOST], + CONF_PORT: user_input[CONF_PORT], + }, + ) if is_loaded: await self.hass.config_entries.async_setup(cur_entry.entry_id) - return self.async_abort(reason='existing_instance_updated') + return self.async_abort(reason="existing_instance_updated") async def async_step_hassio_confirm(self, user_input=None): """Confirm Hass.io discovery.""" @@ -152,11 +158,11 @@ class AdGuardHomeFlowHandler(ConfigFlow): try: await adguard.version() except AdGuardHomeConnectionError: - errors['base'] = 'connection_error' + errors["base"] = "connection_error" return await self._show_hassio_form(errors) return self.async_create_entry( - title=self._hassio_discovery['addon'], + title=self._hassio_discovery["addon"], data={ CONF_HOST: self._hassio_discovery[CONF_HOST], CONF_PORT: self._hassio_discovery[CONF_PORT], diff --git a/homeassistant/components/adguard/const.py b/homeassistant/components/adguard/const.py index 6bbabdafaf1..c77d76a70cf 100644 --- a/homeassistant/components/adguard/const.py +++ b/homeassistant/components/adguard/const.py @@ -1,14 +1,14 @@ """Constants for the AdGuard Home integration.""" -DOMAIN = 'adguard' +DOMAIN = "adguard" -DATA_ADGUARD_CLIENT = 'adguard_client' -DATA_ADGUARD_VERION = 'adguard_version' +DATA_ADGUARD_CLIENT = "adguard_client" +DATA_ADGUARD_VERION = "adguard_version" -CONF_FORCE = 'force' +CONF_FORCE = "force" -SERVICE_ADD_URL = 'add_url' -SERVICE_DISABLE_URL = 'disable_url' -SERVICE_ENABLE_URL = 'enable_url' -SERVICE_REFRESH = 'refresh' -SERVICE_REMOVE_URL = 'remove_url' +SERVICE_ADD_URL = "add_url" +SERVICE_DISABLE_URL = "disable_url" +SERVICE_ENABLE_URL = "enable_url" +SERVICE_REFRESH = "refresh" +SERVICE_REMOVE_URL = "remove_url" diff --git a/homeassistant/components/adguard/sensor.py b/homeassistant/components/adguard/sensor.py index abb5309b449..17e53270f25 100644 --- a/homeassistant/components/adguard/sensor.py +++ b/homeassistant/components/adguard/sensor.py @@ -6,7 +6,10 @@ from adguardhome import AdGuardHomeConnectionError from homeassistant.components.adguard import AdGuardHomeDeviceEntity from homeassistant.components.adguard.const import ( - DATA_ADGUARD_CLIENT, DATA_ADGUARD_VERION, DOMAIN) + DATA_ADGUARD_CLIENT, + DATA_ADGUARD_VERION, + DOMAIN, +) from homeassistant.config_entries import ConfigEntry from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.typing import HomeAssistantType @@ -18,7 +21,7 @@ PARALLEL_UPDATES = 4 async def async_setup_entry( - hass: HomeAssistantType, entry: ConfigEntry, async_add_entities + hass: HomeAssistantType, entry: ConfigEntry, async_add_entities ) -> None: """Set up AdGuard Home sensor based on a config entry.""" adguard = hass.data[DOMAIN][DATA_ADGUARD_CLIENT] @@ -48,12 +51,7 @@ class AdGuardHomeSensor(AdGuardHomeDeviceEntity): """Defines a AdGuard Home sensor.""" def __init__( - self, - adguard, - name: str, - icon: str, - measurement: str, - unit_of_measurement: str, + self, adguard, name: str, icon: str, measurement: str, unit_of_measurement: str ) -> None: """Initialize AdGuard Home sensor.""" self._state = None @@ -65,12 +63,12 @@ class AdGuardHomeSensor(AdGuardHomeDeviceEntity): @property def unique_id(self) -> str: """Return the unique ID for this sensor.""" - return '_'.join( + return "_".join( [ DOMAIN, self.adguard.host, str(self.adguard.port), - 'sensor', + "sensor", self.measurement, ] ) @@ -92,11 +90,7 @@ class AdGuardHomeDNSQueriesSensor(AdGuardHomeSensor): def __init__(self, adguard): """Initialize AdGuard Home sensor.""" super().__init__( - adguard, - 'AdGuard DNS Queries', - 'mdi:magnify', - 'dns_queries', - 'queries', + adguard, "AdGuard DNS Queries", "mdi:magnify", "dns_queries", "queries" ) async def _adguard_update(self) -> None: @@ -111,10 +105,10 @@ class AdGuardHomeBlockedFilteringSensor(AdGuardHomeSensor): """Initialize AdGuard Home sensor.""" super().__init__( adguard, - 'AdGuard DNS Queries Blocked', - 'mdi:magnify-close', - 'blocked_filtering', - 'queries', + "AdGuard DNS Queries Blocked", + "mdi:magnify-close", + "blocked_filtering", + "queries", ) async def _adguard_update(self) -> None: @@ -129,10 +123,10 @@ class AdGuardHomePercentageBlockedSensor(AdGuardHomeSensor): """Initialize AdGuard Home sensor.""" super().__init__( adguard, - 'AdGuard DNS Queries Blocked Ratio', - 'mdi:magnify-close', - 'blocked_percentage', - '%', + "AdGuard DNS Queries Blocked Ratio", + "mdi:magnify-close", + "blocked_percentage", + "%", ) async def _adguard_update(self) -> None: @@ -148,10 +142,10 @@ class AdGuardHomeReplacedParentalSensor(AdGuardHomeSensor): """Initialize AdGuard Home sensor.""" super().__init__( adguard, - 'AdGuard Parental Control Blocked', - 'mdi:human-male-girl', - 'blocked_parental', - 'requests', + "AdGuard Parental Control Blocked", + "mdi:human-male-girl", + "blocked_parental", + "requests", ) async def _adguard_update(self) -> None: @@ -166,10 +160,10 @@ class AdGuardHomeReplacedSafeBrowsingSensor(AdGuardHomeSensor): """Initialize AdGuard Home sensor.""" super().__init__( adguard, - 'AdGuard Safe Browsing Blocked', - 'mdi:shield-half-full', - 'blocked_safebrowsing', - 'requests', + "AdGuard Safe Browsing Blocked", + "mdi:shield-half-full", + "blocked_safebrowsing", + "requests", ) async def _adguard_update(self) -> None: @@ -184,10 +178,10 @@ class AdGuardHomeReplacedSafeSearchSensor(AdGuardHomeSensor): """Initialize AdGuard Home sensor.""" super().__init__( adguard, - 'Searches Safe Search Enforced', - 'mdi:shield-search', - 'enforced_safesearch', - 'requests', + "Searches Safe Search Enforced", + "mdi:shield-search", + "enforced_safesearch", + "requests", ) async def _adguard_update(self) -> None: @@ -202,10 +196,10 @@ class AdGuardHomeAverageProcessingTimeSensor(AdGuardHomeSensor): """Initialize AdGuard Home sensor.""" super().__init__( adguard, - 'AdGuard Average Processing Speed', - 'mdi:speedometer', - 'average_speed', - 'ms', + "AdGuard Average Processing Speed", + "mdi:speedometer", + "average_speed", + "ms", ) async def _adguard_update(self) -> None: @@ -220,11 +214,7 @@ class AdGuardHomeRulesCountSensor(AdGuardHomeSensor): def __init__(self, adguard): """Initialize AdGuard Home sensor.""" super().__init__( - adguard, - 'AdGuard Rules Count', - 'mdi:counter', - 'rules_count', - 'rules', + adguard, "AdGuard Rules Count", "mdi:counter", "rules_count", "rules" ) async def _adguard_update(self) -> None: diff --git a/homeassistant/components/adguard/switch.py b/homeassistant/components/adguard/switch.py index 601bf25b5b0..39cd1ef028d 100644 --- a/homeassistant/components/adguard/switch.py +++ b/homeassistant/components/adguard/switch.py @@ -6,7 +6,10 @@ from adguardhome import AdGuardHomeConnectionError, AdGuardHomeError from homeassistant.components.adguard import AdGuardHomeDeviceEntity from homeassistant.components.adguard.const import ( - DATA_ADGUARD_CLIENT, DATA_ADGUARD_VERION, DOMAIN) + DATA_ADGUARD_CLIENT, + DATA_ADGUARD_VERION, + DOMAIN, +) from homeassistant.config_entries import ConfigEntry from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.entity import ToggleEntity @@ -19,7 +22,7 @@ PARALLEL_UPDATES = 1 async def async_setup_entry( - hass: HomeAssistantType, entry: ConfigEntry, async_add_entities + hass: HomeAssistantType, entry: ConfigEntry, async_add_entities ) -> None: """Set up AdGuard Home switch based on a config entry.""" adguard = hass.data[DOMAIN][DATA_ADGUARD_CLIENT] @@ -54,14 +57,8 @@ class AdGuardHomeSwitch(ToggleEntity, AdGuardHomeDeviceEntity): @property def unique_id(self) -> str: """Return the unique ID for this sensor.""" - return '_'.join( - [ - DOMAIN, - self.adguard.host, - str(self.adguard.port), - 'switch', - self._key, - ] + return "_".join( + [DOMAIN, self.adguard.host, str(self.adguard.port), "switch", self._key] ) @property @@ -74,9 +71,7 @@ class AdGuardHomeSwitch(ToggleEntity, AdGuardHomeDeviceEntity): try: await self._adguard_turn_off() except AdGuardHomeError: - _LOGGER.error( - "An error occurred while turning off AdGuard Home switch." - ) + _LOGGER.error("An error occurred while turning off AdGuard Home switch.") self._available = False async def _adguard_turn_off(self) -> None: @@ -88,9 +83,7 @@ class AdGuardHomeSwitch(ToggleEntity, AdGuardHomeDeviceEntity): try: await self._adguard_turn_on() except AdGuardHomeError: - _LOGGER.error( - "An error occurred while turning on AdGuard Home switch." - ) + _LOGGER.error("An error occurred while turning on AdGuard Home switch.") self._available = False async def _adguard_turn_on(self) -> None: @@ -104,7 +97,7 @@ class AdGuardHomeProtectionSwitch(AdGuardHomeSwitch): def __init__(self, adguard) -> None: """Initialize AdGuard Home switch.""" super().__init__( - adguard, "AdGuard Protection", 'mdi:shield-check', 'protection' + adguard, "AdGuard Protection", "mdi:shield-check", "protection" ) async def _adguard_turn_off(self) -> None: @@ -126,7 +119,7 @@ class AdGuardHomeParentalSwitch(AdGuardHomeSwitch): def __init__(self, adguard) -> None: """Initialize AdGuard Home switch.""" super().__init__( - adguard, "AdGuard Parental Control", 'mdi:shield-check', 'parental' + adguard, "AdGuard Parental Control", "mdi:shield-check", "parental" ) async def _adguard_turn_off(self) -> None: @@ -148,7 +141,7 @@ class AdGuardHomeSafeSearchSwitch(AdGuardHomeSwitch): def __init__(self, adguard) -> None: """Initialize AdGuard Home switch.""" super().__init__( - adguard, "AdGuard Safe Search", 'mdi:shield-check', 'safesearch' + adguard, "AdGuard Safe Search", "mdi:shield-check", "safesearch" ) async def _adguard_turn_off(self) -> None: @@ -170,10 +163,7 @@ class AdGuardHomeSafeBrowsingSwitch(AdGuardHomeSwitch): def __init__(self, adguard) -> None: """Initialize AdGuard Home switch.""" super().__init__( - adguard, - "AdGuard Safe Browsing", - 'mdi:shield-check', - 'safebrowsing', + adguard, "AdGuard Safe Browsing", "mdi:shield-check", "safebrowsing" ) async def _adguard_turn_off(self) -> None: @@ -194,9 +184,7 @@ class AdGuardHomeFilteringSwitch(AdGuardHomeSwitch): def __init__(self, adguard) -> None: """Initialize AdGuard Home switch.""" - super().__init__( - adguard, "AdGuard Filtering", 'mdi:shield-check', 'filtering' - ) + super().__init__(adguard, "AdGuard Filtering", "mdi:shield-check", "filtering") async def _adguard_turn_off(self) -> None: """Turn off the switch.""" @@ -216,9 +204,7 @@ class AdGuardHomeQueryLogSwitch(AdGuardHomeSwitch): def __init__(self, adguard) -> None: """Initialize AdGuard Home switch.""" - super().__init__( - adguard, "AdGuard Query Log", 'mdi:shield-check', 'querylog' - ) + super().__init__(adguard, "AdGuard Query Log", "mdi:shield-check", "querylog") async def _adguard_turn_off(self) -> None: """Turn off the switch.""" diff --git a/homeassistant/components/ads/__init__.py b/homeassistant/components/ads/__init__.py index 920a2a034d7..1b4f11c7cc1 100644 --- a/homeassistant/components/ads/__init__.py +++ b/homeassistant/components/ads/__init__.py @@ -10,57 +10,76 @@ import async_timeout import voluptuous as vol from homeassistant.const import ( - CONF_DEVICE, CONF_IP_ADDRESS, CONF_PORT, EVENT_HOMEASSISTANT_STOP) + CONF_DEVICE, + CONF_IP_ADDRESS, + CONF_PORT, + EVENT_HOMEASSISTANT_STOP, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -DATA_ADS = 'data_ads' +DATA_ADS = "data_ads" # Supported Types -ADSTYPE_BOOL = 'bool' -ADSTYPE_BYTE = 'byte' -ADSTYPE_DINT = 'dint' -ADSTYPE_INT = 'int' -ADSTYPE_UDINT = 'udint' -ADSTYPE_UINT = 'uint' +ADSTYPE_BOOL = "bool" +ADSTYPE_BYTE = "byte" +ADSTYPE_DINT = "dint" +ADSTYPE_INT = "int" +ADSTYPE_UDINT = "udint" +ADSTYPE_UINT = "uint" -CONF_ADS_FACTOR = 'factor' -CONF_ADS_TYPE = 'adstype' -CONF_ADS_VALUE = 'value' -CONF_ADS_VAR = 'adsvar' -CONF_ADS_VAR_BRIGHTNESS = 'adsvar_brightness' -CONF_ADS_VAR_POSITION = 'adsvar_position' +CONF_ADS_FACTOR = "factor" +CONF_ADS_TYPE = "adstype" +CONF_ADS_VALUE = "value" +CONF_ADS_VAR = "adsvar" +CONF_ADS_VAR_BRIGHTNESS = "adsvar_brightness" +CONF_ADS_VAR_POSITION = "adsvar_position" -STATE_KEY_STATE = 'state' -STATE_KEY_BRIGHTNESS = 'brightness' -STATE_KEY_POSITION = 'position' +STATE_KEY_STATE = "state" +STATE_KEY_BRIGHTNESS = "brightness" +STATE_KEY_POSITION = "position" -DOMAIN = 'ads' +DOMAIN = "ads" -SERVICE_WRITE_DATA_BY_NAME = 'write_data_by_name' +SERVICE_WRITE_DATA_BY_NAME = "write_data_by_name" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_DEVICE): cv.string, - vol.Required(CONF_PORT): cv.port, - vol.Optional(CONF_IP_ADDRESS): cv.string, - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_DEVICE): cv.string, + vol.Required(CONF_PORT): cv.port, + vol.Optional(CONF_IP_ADDRESS): cv.string, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) -SCHEMA_SERVICE_WRITE_DATA_BY_NAME = vol.Schema({ - vol.Required(CONF_ADS_TYPE): - vol.In([ADSTYPE_INT, ADSTYPE_UINT, ADSTYPE_BYTE, ADSTYPE_BOOL, - ADSTYPE_DINT, ADSTYPE_UDINT]), - vol.Required(CONF_ADS_VALUE): vol.Coerce(int), - vol.Required(CONF_ADS_VAR): cv.string, -}) +SCHEMA_SERVICE_WRITE_DATA_BY_NAME = vol.Schema( + { + vol.Required(CONF_ADS_TYPE): vol.In( + [ + ADSTYPE_INT, + ADSTYPE_UINT, + ADSTYPE_BYTE, + ADSTYPE_BOOL, + ADSTYPE_DINT, + ADSTYPE_UDINT, + ] + ), + vol.Required(CONF_ADS_VALUE): vol.Coerce(int), + vol.Required(CONF_ADS_VAR): cv.string, + } +) def setup(hass, config): """Set up the ADS component.""" import pyads + conf = config[DOMAIN] net_id = conf.get(CONF_DEVICE) @@ -91,7 +110,10 @@ def setup(hass, config): except pyads.ADSError: _LOGGER.error( "Could not connect to ADS host (netid=%s, ip=%s, port=%s)", - net_id, ip_address, port) + net_id, + ip_address, + port, + ) return False hass.data[DATA_ADS] = ads @@ -109,15 +131,18 @@ def setup(hass, config): _LOGGER.error(err) hass.services.register( - DOMAIN, SERVICE_WRITE_DATA_BY_NAME, handle_write_data_by_name, - schema=SCHEMA_SERVICE_WRITE_DATA_BY_NAME) + DOMAIN, + SERVICE_WRITE_DATA_BY_NAME, + handle_write_data_by_name, + schema=SCHEMA_SERVICE_WRITE_DATA_BY_NAME, + ) return True # Tuple to hold data needed for notification NotificationItem = namedtuple( - 'NotificationItem', 'hnotify huser name plc_datatype callback' + "NotificationItem", "hnotify huser name plc_datatype callback" ) @@ -137,15 +162,17 @@ class AdsHub: def shutdown(self, *args, **kwargs): """Shutdown ADS connection.""" import pyads + _LOGGER.debug("Shutting down ADS") for notification_item in self._notification_items.values(): _LOGGER.debug( "Deleting device notification %d, %d", - notification_item.hnotify, notification_item.huser) + notification_item.hnotify, + notification_item.huser, + ) try: self._client.del_device_notification( - notification_item.hnotify, - notification_item.huser + notification_item.hnotify, notification_item.huser ) except pyads.ADSError as err: _LOGGER.error(err) @@ -161,6 +188,7 @@ class AdsHub: def write_by_name(self, name, value, plc_datatype): """Write a value to the device.""" import pyads + with self._lock: try: return self._client.write_by_name(name, value, plc_datatype) @@ -170,6 +198,7 @@ class AdsHub: def read_by_name(self, name, plc_datatype): """Read a value from the device.""" import pyads + with self._lock: try: return self._client.read_by_name(name, plc_datatype) @@ -179,22 +208,25 @@ class AdsHub: def add_device_notification(self, name, plc_datatype, callback): """Add a notification to the ADS devices.""" import pyads + attr = pyads.NotificationAttrib(ctypes.sizeof(plc_datatype)) with self._lock: try: hnotify, huser = self._client.add_device_notification( - name, attr, self._device_notification_callback) + name, attr, self._device_notification_callback + ) except pyads.ADSError as err: _LOGGER.error("Error subscribing to %s: %s", name, err) else: hnotify = int(hnotify) self._notification_items[hnotify] = NotificationItem( - hnotify, huser, name, plc_datatype, callback) + hnotify, huser, name, plc_datatype, callback + ) _LOGGER.debug( - "Added device notification %d for variable %s", - hnotify, name) + "Added device notification %d for variable %s", hnotify, name + ) def _device_notification_callback(self, notification, name): """Handle device notifications.""" @@ -213,17 +245,17 @@ class AdsHub: # Parse data to desired datatype if notification_item.plc_datatype == self.PLCTYPE_BOOL: - value = bool(struct.unpack('' - 'You will need to restart hass after fixing.' - ''.format(ex), + "Error: {}
" + "You will need to restart hass after fixing." + "".format(ex), title=NOTIFICATION_TITLE, - notification_id=NOTIFICATION_ID) + notification_id=NOTIFICATION_ID, + ) class AladdinDevice(CoverDevice): @@ -57,15 +67,15 @@ class AladdinDevice(CoverDevice): def __init__(self, acc, device): """Initialize the cover.""" self._acc = acc - self._device_id = device['device_id'] - self._number = device['door_number'] - self._name = device['name'] - self._status = STATES_MAP.get(device['status']) + self._device_id = device["device_id"] + self._number = device["door_number"] + self._name = device["name"] + self._status = STATES_MAP.get(device["status"]) @property def device_class(self): """Define this cover as a garage door.""" - return 'garage' + return "garage" @property def supported_features(self): @@ -75,7 +85,7 @@ class AladdinDevice(CoverDevice): @property def unique_id(self): """Return a unique ID.""" - return '{}-{}'.format(self._device_id, self._number) + return "{}-{}".format(self._device_id, self._number) @property def name(self): diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py index ff1dc6f8c53..ab0b810ee83 100644 --- a/homeassistant/components/alarm_control_panel/__init__.py +++ b/homeassistant/components/alarm_control_panel/__init__.py @@ -5,59 +5,65 @@ import logging import voluptuous as vol from homeassistant.const import ( - ATTR_CODE, ATTR_CODE_FORMAT, SERVICE_ALARM_TRIGGER, SERVICE_ALARM_DISARM, - SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY, SERVICE_ALARM_ARM_NIGHT, - SERVICE_ALARM_ARM_CUSTOM_BYPASS) + ATTR_CODE, + ATTR_CODE_FORMAT, + SERVICE_ALARM_TRIGGER, + SERVICE_ALARM_DISARM, + SERVICE_ALARM_ARM_HOME, + SERVICE_ALARM_ARM_AWAY, + SERVICE_ALARM_ARM_NIGHT, + SERVICE_ALARM_ARM_CUSTOM_BYPASS, +) from homeassistant.helpers.config_validation import ( # noqa - ENTITY_SERVICE_SCHEMA, PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE) + ENTITY_SERVICE_SCHEMA, + PLATFORM_SCHEMA, + PLATFORM_SCHEMA_BASE, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent -DOMAIN = 'alarm_control_panel' +DOMAIN = "alarm_control_panel" SCAN_INTERVAL = timedelta(seconds=30) -ATTR_CHANGED_BY = 'changed_by' -FORMAT_TEXT = 'text' -FORMAT_NUMBER = 'number' -ATTR_CODE_ARM_REQUIRED = 'code_arm_required' +ATTR_CHANGED_BY = "changed_by" +FORMAT_TEXT = "text" +FORMAT_NUMBER = "number" +ATTR_CODE_ARM_REQUIRED = "code_arm_required" -ENTITY_ID_FORMAT = DOMAIN + '.{}' +ENTITY_ID_FORMAT = DOMAIN + ".{}" -ALARM_SERVICE_SCHEMA = ENTITY_SERVICE_SCHEMA.extend({ - vol.Optional(ATTR_CODE): cv.string, -}) +ALARM_SERVICE_SCHEMA = ENTITY_SERVICE_SCHEMA.extend( + {vol.Optional(ATTR_CODE): cv.string} +) async def async_setup(hass, config): """Track states and offer events for sensors.""" component = hass.data[DOMAIN] = EntityComponent( - logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL) + logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL + ) await component.async_setup(config) component.async_register_entity_service( - SERVICE_ALARM_DISARM, ALARM_SERVICE_SCHEMA, - 'async_alarm_disarm' + SERVICE_ALARM_DISARM, ALARM_SERVICE_SCHEMA, "async_alarm_disarm" ) component.async_register_entity_service( - SERVICE_ALARM_ARM_HOME, ALARM_SERVICE_SCHEMA, - 'async_alarm_arm_home' + SERVICE_ALARM_ARM_HOME, ALARM_SERVICE_SCHEMA, "async_alarm_arm_home" ) component.async_register_entity_service( - SERVICE_ALARM_ARM_AWAY, ALARM_SERVICE_SCHEMA, - 'async_alarm_arm_away' + SERVICE_ALARM_ARM_AWAY, ALARM_SERVICE_SCHEMA, "async_alarm_arm_away" ) component.async_register_entity_service( - SERVICE_ALARM_ARM_NIGHT, ALARM_SERVICE_SCHEMA, - 'async_alarm_arm_night' + SERVICE_ALARM_ARM_NIGHT, ALARM_SERVICE_SCHEMA, "async_alarm_arm_night" ) component.async_register_entity_service( - SERVICE_ALARM_ARM_CUSTOM_BYPASS, ALARM_SERVICE_SCHEMA, - 'async_alarm_arm_custom_bypass' + SERVICE_ALARM_ARM_CUSTOM_BYPASS, + ALARM_SERVICE_SCHEMA, + "async_alarm_arm_custom_bypass", ) component.async_register_entity_service( - SERVICE_ALARM_TRIGGER, ALARM_SERVICE_SCHEMA, - 'async_alarm_trigger' + SERVICE_ALARM_TRIGGER, ALARM_SERVICE_SCHEMA, "async_alarm_trigger" ) return True @@ -156,8 +162,7 @@ class AlarmControlPanel(Entity): This method must be run in the event loop and returns a coroutine. """ - return self.hass.async_add_executor_job( - self.alarm_arm_custom_bypass, code) + return self.hass.async_add_executor_job(self.alarm_arm_custom_bypass, code) @property def state_attributes(self): @@ -165,6 +170,6 @@ class AlarmControlPanel(Entity): state_attr = { ATTR_CODE_FORMAT: self.code_format, ATTR_CHANGED_BY: self.changed_by, - ATTR_CODE_ARM_REQUIRED: self.code_arm_required + ATTR_CODE_ARM_REQUIRED: self.code_arm_required, } return state_attr diff --git a/homeassistant/components/alarmdecoder/__init__.py b/homeassistant/components/alarmdecoder/__init__.py index b4d1a2e0b9f..e0ff80ae9fa 100644 --- a/homeassistant/components/alarmdecoder/__init__.py +++ b/homeassistant/components/alarmdecoder/__init__.py @@ -12,85 +12,105 @@ from homeassistant.components.binary_sensor import DEVICE_CLASSES_SCHEMA _LOGGER = logging.getLogger(__name__) -DOMAIN = 'alarmdecoder' +DOMAIN = "alarmdecoder" -DATA_AD = 'alarmdecoder' +DATA_AD = "alarmdecoder" -CONF_DEVICE = 'device' -CONF_DEVICE_BAUD = 'baudrate' -CONF_DEVICE_PATH = 'path' -CONF_DEVICE_PORT = 'port' -CONF_DEVICE_TYPE = 'type' -CONF_PANEL_DISPLAY = 'panel_display' -CONF_ZONE_NAME = 'name' -CONF_ZONE_TYPE = 'type' -CONF_ZONE_LOOP = 'loop' -CONF_ZONE_RFID = 'rfid' -CONF_ZONES = 'zones' -CONF_RELAY_ADDR = 'relayaddr' -CONF_RELAY_CHAN = 'relaychan' +CONF_DEVICE = "device" +CONF_DEVICE_BAUD = "baudrate" +CONF_DEVICE_PATH = "path" +CONF_DEVICE_PORT = "port" +CONF_DEVICE_TYPE = "type" +CONF_PANEL_DISPLAY = "panel_display" +CONF_ZONE_NAME = "name" +CONF_ZONE_TYPE = "type" +CONF_ZONE_LOOP = "loop" +CONF_ZONE_RFID = "rfid" +CONF_ZONES = "zones" +CONF_RELAY_ADDR = "relayaddr" +CONF_RELAY_CHAN = "relaychan" -DEFAULT_DEVICE_TYPE = 'socket' -DEFAULT_DEVICE_HOST = 'localhost' +DEFAULT_DEVICE_TYPE = "socket" +DEFAULT_DEVICE_HOST = "localhost" DEFAULT_DEVICE_PORT = 10000 -DEFAULT_DEVICE_PATH = '/dev/ttyUSB0' +DEFAULT_DEVICE_PATH = "/dev/ttyUSB0" DEFAULT_DEVICE_BAUD = 115200 DEFAULT_PANEL_DISPLAY = False -DEFAULT_ZONE_TYPE = 'opening' +DEFAULT_ZONE_TYPE = "opening" -SIGNAL_PANEL_MESSAGE = 'alarmdecoder.panel_message' -SIGNAL_PANEL_ARM_AWAY = 'alarmdecoder.panel_arm_away' -SIGNAL_PANEL_ARM_HOME = 'alarmdecoder.panel_arm_home' -SIGNAL_PANEL_DISARM = 'alarmdecoder.panel_disarm' +SIGNAL_PANEL_MESSAGE = "alarmdecoder.panel_message" +SIGNAL_PANEL_ARM_AWAY = "alarmdecoder.panel_arm_away" +SIGNAL_PANEL_ARM_HOME = "alarmdecoder.panel_arm_home" +SIGNAL_PANEL_DISARM = "alarmdecoder.panel_disarm" -SIGNAL_ZONE_FAULT = 'alarmdecoder.zone_fault' -SIGNAL_ZONE_RESTORE = 'alarmdecoder.zone_restore' -SIGNAL_RFX_MESSAGE = 'alarmdecoder.rfx_message' -SIGNAL_REL_MESSAGE = 'alarmdecoder.rel_message' +SIGNAL_ZONE_FAULT = "alarmdecoder.zone_fault" +SIGNAL_ZONE_RESTORE = "alarmdecoder.zone_restore" +SIGNAL_RFX_MESSAGE = "alarmdecoder.rfx_message" +SIGNAL_REL_MESSAGE = "alarmdecoder.rel_message" -DEVICE_SOCKET_SCHEMA = vol.Schema({ - vol.Required(CONF_DEVICE_TYPE): 'socket', - vol.Optional(CONF_HOST, default=DEFAULT_DEVICE_HOST): cv.string, - vol.Optional(CONF_DEVICE_PORT, default=DEFAULT_DEVICE_PORT): cv.port}) +DEVICE_SOCKET_SCHEMA = vol.Schema( + { + vol.Required(CONF_DEVICE_TYPE): "socket", + vol.Optional(CONF_HOST, default=DEFAULT_DEVICE_HOST): cv.string, + vol.Optional(CONF_DEVICE_PORT, default=DEFAULT_DEVICE_PORT): cv.port, + } +) -DEVICE_SERIAL_SCHEMA = vol.Schema({ - vol.Required(CONF_DEVICE_TYPE): 'serial', - vol.Optional(CONF_DEVICE_PATH, default=DEFAULT_DEVICE_PATH): cv.string, - vol.Optional(CONF_DEVICE_BAUD, default=DEFAULT_DEVICE_BAUD): cv.string}) +DEVICE_SERIAL_SCHEMA = vol.Schema( + { + vol.Required(CONF_DEVICE_TYPE): "serial", + vol.Optional(CONF_DEVICE_PATH, default=DEFAULT_DEVICE_PATH): cv.string, + vol.Optional(CONF_DEVICE_BAUD, default=DEFAULT_DEVICE_BAUD): cv.string, + } +) -DEVICE_USB_SCHEMA = vol.Schema({ - vol.Required(CONF_DEVICE_TYPE): 'usb'}) +DEVICE_USB_SCHEMA = vol.Schema({vol.Required(CONF_DEVICE_TYPE): "usb"}) -ZONE_SCHEMA = vol.Schema({ - vol.Required(CONF_ZONE_NAME): cv.string, - vol.Optional(CONF_ZONE_TYPE, - default=DEFAULT_ZONE_TYPE): vol.Any(DEVICE_CLASSES_SCHEMA), - vol.Optional(CONF_ZONE_RFID): cv.string, - vol.Optional(CONF_ZONE_LOOP): - vol.All(vol.Coerce(int), vol.Range(min=1, max=4)), - vol.Inclusive(CONF_RELAY_ADDR, 'relaylocation', - 'Relay address and channel must exist together'): cv.byte, - vol.Inclusive(CONF_RELAY_CHAN, 'relaylocation', - 'Relay address and channel must exist together'): cv.byte}) +ZONE_SCHEMA = vol.Schema( + { + vol.Required(CONF_ZONE_NAME): cv.string, + vol.Optional(CONF_ZONE_TYPE, default=DEFAULT_ZONE_TYPE): vol.Any( + DEVICE_CLASSES_SCHEMA + ), + vol.Optional(CONF_ZONE_RFID): cv.string, + vol.Optional(CONF_ZONE_LOOP): vol.All(vol.Coerce(int), vol.Range(min=1, max=4)), + vol.Inclusive( + CONF_RELAY_ADDR, + "relaylocation", + "Relay address and channel must exist together", + ): cv.byte, + vol.Inclusive( + CONF_RELAY_CHAN, + "relaylocation", + "Relay address and channel must exist together", + ): cv.byte, + } +) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_DEVICE): vol.Any( - DEVICE_SOCKET_SCHEMA, DEVICE_SERIAL_SCHEMA, - DEVICE_USB_SCHEMA), - vol.Optional(CONF_PANEL_DISPLAY, - default=DEFAULT_PANEL_DISPLAY): cv.boolean, - vol.Optional(CONF_ZONES): {vol.Coerce(int): ZONE_SCHEMA}, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_DEVICE): vol.Any( + DEVICE_SOCKET_SCHEMA, DEVICE_SERIAL_SCHEMA, DEVICE_USB_SCHEMA + ), + vol.Optional( + CONF_PANEL_DISPLAY, default=DEFAULT_PANEL_DISPLAY + ): cv.boolean, + vol.Optional(CONF_ZONES): {vol.Coerce(int): ZONE_SCHEMA}, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): """Set up for the AlarmDecoder devices.""" from alarmdecoder import AlarmDecoder - from alarmdecoder.devices import (SocketDevice, SerialDevice, USBDevice) + from alarmdecoder.devices import SocketDevice, SerialDevice, USBDevice conf = config.get(DOMAIN) @@ -115,13 +135,15 @@ def setup(hass, config): def open_connection(now=None): """Open a connection to AlarmDecoder.""" from alarmdecoder.util import NoDeviceError + nonlocal restart try: controller.open(baud) except NoDeviceError: _LOGGER.debug("Failed to connect. Retrying in 5 seconds") hass.helpers.event.track_point_in_time( - open_connection, dt_util.utcnow() + timedelta(seconds=5)) + open_connection, dt_util.utcnow() + timedelta(seconds=5) + ) return _LOGGER.debug("Established a connection with the alarmdecoder") restart = True @@ -137,39 +159,34 @@ def setup(hass, config): def handle_message(sender, message): """Handle message from AlarmDecoder.""" - hass.helpers.dispatcher.dispatcher_send( - SIGNAL_PANEL_MESSAGE, message) + hass.helpers.dispatcher.dispatcher_send(SIGNAL_PANEL_MESSAGE, message) def handle_rfx_message(sender, message): """Handle RFX message from AlarmDecoder.""" - hass.helpers.dispatcher.dispatcher_send( - SIGNAL_RFX_MESSAGE, message) + hass.helpers.dispatcher.dispatcher_send(SIGNAL_RFX_MESSAGE, message) def zone_fault_callback(sender, zone): """Handle zone fault from AlarmDecoder.""" - hass.helpers.dispatcher.dispatcher_send( - SIGNAL_ZONE_FAULT, zone) + hass.helpers.dispatcher.dispatcher_send(SIGNAL_ZONE_FAULT, zone) def zone_restore_callback(sender, zone): """Handle zone restore from AlarmDecoder.""" - hass.helpers.dispatcher.dispatcher_send( - SIGNAL_ZONE_RESTORE, zone) + hass.helpers.dispatcher.dispatcher_send(SIGNAL_ZONE_RESTORE, zone) def handle_rel_message(sender, message): """Handle relay message from AlarmDecoder.""" - hass.helpers.dispatcher.dispatcher_send( - SIGNAL_REL_MESSAGE, message) + hass.helpers.dispatcher.dispatcher_send(SIGNAL_REL_MESSAGE, message) controller = False - if device_type == 'socket': + if device_type == "socket": host = device.get(CONF_HOST) port = device.get(CONF_DEVICE_PORT) controller = AlarmDecoder(SocketDevice(interface=(host, port))) - elif device_type == 'serial': + elif device_type == "serial": path = device.get(CONF_DEVICE_PATH) baud = device.get(CONF_DEVICE_BAUD) controller = AlarmDecoder(SerialDevice(interface=path)) - elif device_type == 'usb': + elif device_type == "usb": AlarmDecoder(USBDevice.find()) return False @@ -186,13 +203,12 @@ def setup(hass, config): hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_alarmdecoder) - load_platform(hass, 'alarm_control_panel', DOMAIN, conf, config) + load_platform(hass, "alarm_control_panel", DOMAIN, conf, config) if zones: - load_platform( - hass, 'binary_sensor', DOMAIN, {CONF_ZONES: zones}, config) + load_platform(hass, "binary_sensor", DOMAIN, {CONF_ZONES: zones}, config) if display: - load_platform(hass, 'sensor', DOMAIN, conf, config) + load_platform(hass, "sensor", DOMAIN, conf, config) return True diff --git a/homeassistant/components/alarmdecoder/alarm_control_panel.py b/homeassistant/components/alarmdecoder/alarm_control_panel.py index 51645b516b9..42f839bcd60 100644 --- a/homeassistant/components/alarmdecoder/alarm_control_panel.py +++ b/homeassistant/components/alarmdecoder/alarm_control_panel.py @@ -5,18 +5,20 @@ import voluptuous as vol import homeassistant.components.alarm_control_panel as alarm from homeassistant.const import ( - ATTR_CODE, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, - STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED) + ATTR_CODE, + STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_HOME, + STATE_ALARM_DISARMED, + STATE_ALARM_TRIGGERED, +) import homeassistant.helpers.config_validation as cv from . import DATA_AD, SIGNAL_PANEL_MESSAGE _LOGGER = logging.getLogger(__name__) -SERVICE_ALARM_TOGGLE_CHIME = 'alarmdecoder_alarm_toggle_chime' -ALARM_TOGGLE_CHIME_SCHEMA = vol.Schema({ - vol.Required(ATTR_CODE): cv.string, -}) +SERVICE_ALARM_TOGGLE_CHIME = "alarmdecoder_alarm_toggle_chime" +ALARM_TOGGLE_CHIME_SCHEMA = vol.Schema({vol.Required(ATTR_CODE): cv.string}) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -30,8 +32,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): device.alarm_toggle_chime(code) hass.services.register( - alarm.DOMAIN, SERVICE_ALARM_TOGGLE_CHIME, alarm_toggle_chime_handler, - schema=ALARM_TOGGLE_CHIME_SCHEMA) + alarm.DOMAIN, + SERVICE_ALARM_TOGGLE_CHIME, + alarm_toggle_chime_handler, + schema=ALARM_TOGGLE_CHIME_SCHEMA, + ) class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel): @@ -55,7 +60,8 @@ class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel): async def async_added_to_hass(self): """Register callbacks.""" self.hass.helpers.dispatcher.async_dispatcher_connect( - SIGNAL_PANEL_MESSAGE, self._message_callback) + SIGNAL_PANEL_MESSAGE, self._message_callback + ) def _message_callback(self, message): """Handle received messages.""" @@ -104,15 +110,15 @@ class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel): def device_state_attributes(self): """Return the state attributes.""" return { - 'ac_power': self._ac_power, - 'backlight_on': self._backlight_on, - 'battery_low': self._battery_low, - 'check_zone': self._check_zone, - 'chime': self._chime, - 'entry_delay_off': self._entry_delay_off, - 'programming_mode': self._programming_mode, - 'ready': self._ready, - 'zone_bypassed': self._zone_bypassed, + "ac_power": self._ac_power, + "backlight_on": self._backlight_on, + "battery_low": self._battery_low, + "check_zone": self._check_zone, + "chime": self._chime, + "entry_delay_off": self._entry_delay_off, + "programming_mode": self._programming_mode, + "ready": self._ready, + "zone_bypassed": self._zone_bypassed, } def alarm_disarm(self, code=None): diff --git a/homeassistant/components/alarmdecoder/binary_sensor.py b/homeassistant/components/alarmdecoder/binary_sensor.py index 91ff8b381b5..bbcc4fd6eae 100644 --- a/homeassistant/components/alarmdecoder/binary_sensor.py +++ b/homeassistant/components/alarmdecoder/binary_sensor.py @@ -4,20 +4,30 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice from . import ( - CONF_RELAY_ADDR, CONF_RELAY_CHAN, CONF_ZONE_LOOP, CONF_ZONE_NAME, - CONF_ZONE_RFID, CONF_ZONE_TYPE, CONF_ZONES, SIGNAL_REL_MESSAGE, - SIGNAL_RFX_MESSAGE, SIGNAL_ZONE_FAULT, SIGNAL_ZONE_RESTORE, ZONE_SCHEMA) + CONF_RELAY_ADDR, + CONF_RELAY_CHAN, + CONF_ZONE_LOOP, + CONF_ZONE_NAME, + CONF_ZONE_RFID, + CONF_ZONE_TYPE, + CONF_ZONES, + SIGNAL_REL_MESSAGE, + SIGNAL_RFX_MESSAGE, + SIGNAL_ZONE_FAULT, + SIGNAL_ZONE_RESTORE, + ZONE_SCHEMA, +) _LOGGER = logging.getLogger(__name__) -ATTR_RF_BIT0 = 'rf_bit0' -ATTR_RF_LOW_BAT = 'rf_low_battery' -ATTR_RF_SUPERVISED = 'rf_supervised' -ATTR_RF_BIT3 = 'rf_bit3' -ATTR_RF_LOOP3 = 'rf_loop3' -ATTR_RF_LOOP2 = 'rf_loop2' -ATTR_RF_LOOP4 = 'rf_loop4' -ATTR_RF_LOOP1 = 'rf_loop1' +ATTR_RF_BIT0 = "rf_bit0" +ATTR_RF_LOW_BAT = "rf_low_battery" +ATTR_RF_SUPERVISED = "rf_supervised" +ATTR_RF_BIT3 = "rf_bit3" +ATTR_RF_LOOP3 = "rf_loop3" +ATTR_RF_LOOP2 = "rf_loop2" +ATTR_RF_LOOP4 = "rf_loop4" +ATTR_RF_LOOP1 = "rf_loop1" def setup_platform(hass, config, add_entities, discovery_info=None): @@ -34,8 +44,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): relay_addr = device_config_data.get(CONF_RELAY_ADDR) relay_chan = device_config_data.get(CONF_RELAY_CHAN) device = AlarmDecoderBinarySensor( - zone_num, zone_name, zone_type, zone_rfid, zone_loop, relay_addr, - relay_chan) + zone_num, zone_name, zone_type, zone_rfid, zone_loop, relay_addr, relay_chan + ) devices.append(device) add_entities(devices) @@ -46,8 +56,16 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class AlarmDecoderBinarySensor(BinarySensorDevice): """Representation of an AlarmDecoder binary sensor.""" - def __init__(self, zone_number, zone_name, zone_type, zone_rfid, zone_loop, - relay_addr, relay_chan): + def __init__( + self, + zone_number, + zone_name, + zone_type, + zone_rfid, + zone_loop, + relay_addr, + relay_chan, + ): """Initialize the binary_sensor.""" self._zone_number = zone_number self._zone_type = zone_type @@ -62,16 +80,20 @@ class AlarmDecoderBinarySensor(BinarySensorDevice): async def async_added_to_hass(self): """Register callbacks.""" self.hass.helpers.dispatcher.async_dispatcher_connect( - SIGNAL_ZONE_FAULT, self._fault_callback) + SIGNAL_ZONE_FAULT, self._fault_callback + ) self.hass.helpers.dispatcher.async_dispatcher_connect( - SIGNAL_ZONE_RESTORE, self._restore_callback) + SIGNAL_ZONE_RESTORE, self._restore_callback + ) self.hass.helpers.dispatcher.async_dispatcher_connect( - SIGNAL_RFX_MESSAGE, self._rfx_message_callback) + SIGNAL_RFX_MESSAGE, self._rfx_message_callback + ) self.hass.helpers.dispatcher.async_dispatcher_connect( - SIGNAL_REL_MESSAGE, self._rel_message_callback) + SIGNAL_REL_MESSAGE, self._rel_message_callback + ) @property def name(self): @@ -130,9 +152,9 @@ class AlarmDecoderBinarySensor(BinarySensorDevice): def _rel_message_callback(self, message): """Update relay state.""" - if (self._relay_addr == message.address and - self._relay_chan == message.channel): - _LOGGER.debug("Relay %d:%d value:%d", message.address, - message.channel, message.value) + if self._relay_addr == message.address and self._relay_chan == message.channel: + _LOGGER.debug( + "Relay %d:%d value:%d", message.address, message.channel, message.value + ) self._state = message.value self.schedule_update_ha_state() diff --git a/homeassistant/components/alarmdecoder/sensor.py b/homeassistant/components/alarmdecoder/sensor.py index 9fb37d62376..196e8d704e1 100644 --- a/homeassistant/components/alarmdecoder/sensor.py +++ b/homeassistant/components/alarmdecoder/sensor.py @@ -24,13 +24,14 @@ class AlarmDecoderSensor(Entity): """Initialize the alarm panel.""" self._display = "" self._state = None - self._icon = 'mdi:alarm-check' - self._name = 'Alarm Panel Display' + self._icon = "mdi:alarm-check" + self._name = "Alarm Panel Display" async def async_added_to_hass(self): """Register callbacks.""" self.hass.helpers.dispatcher.async_dispatcher_connect( - SIGNAL_PANEL_MESSAGE, self._message_callback) + SIGNAL_PANEL_MESSAGE, self._message_callback + ) def _message_callback(self, message): if self._display != message.text: diff --git a/homeassistant/components/alarmdotcom/alarm_control_panel.py b/homeassistant/components/alarmdotcom/alarm_control_panel.py index 5919bf84f41..f80e8d6eb1e 100644 --- a/homeassistant/components/alarmdotcom/alarm_control_panel.py +++ b/homeassistant/components/alarmdotcom/alarm_control_panel.py @@ -7,25 +7,32 @@ import voluptuous as vol import homeassistant.components.alarm_control_panel as alarm from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_CODE, CONF_NAME, CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY, - STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED) + CONF_CODE, + CONF_NAME, + CONF_PASSWORD, + CONF_USERNAME, + STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_HOME, + STATE_ALARM_DISARMED, +) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'Alarm.com' +DEFAULT_NAME = "Alarm.com" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Optional(CONF_CODE): cv.positive_int, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Optional(CONF_CODE): cv.positive_int, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + } +) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up a Alarm.com control panel.""" name = config.get(CONF_NAME) code = config.get(CONF_CODE) @@ -43,7 +50,8 @@ class AlarmDotCom(alarm.AlarmControlPanel): def __init__(self, hass, name, code, username, password): """Initialize the Alarm.com status.""" from pyalarmdotcom import Alarmdotcom - _LOGGER.debug('Setting up Alarm.com...') + + _LOGGER.debug("Setting up Alarm.com...") self._hass = hass self._name = name self._code = str(code) if code else None @@ -51,8 +59,7 @@ class AlarmDotCom(alarm.AlarmControlPanel): self._password = password self._websession = async_get_clientsession(self._hass) self._state = None - self._alarm = Alarmdotcom( - username, password, self._websession, hass.loop) + self._alarm = Alarmdotcom(username, password, self._websession, hass.loop) async def async_login(self): """Login to Alarm.com.""" @@ -73,27 +80,25 @@ class AlarmDotCom(alarm.AlarmControlPanel): """Return one or more digits/characters.""" if self._code is None: return None - if isinstance(self._code, str) and re.search('^\\d+$', self._code): + if isinstance(self._code, str) and re.search("^\\d+$", self._code): return alarm.FORMAT_NUMBER return alarm.FORMAT_TEXT @property def state(self): """Return the state of the device.""" - if self._alarm.state.lower() == 'disarmed': + if self._alarm.state.lower() == "disarmed": return STATE_ALARM_DISARMED - if self._alarm.state.lower() == 'armed stay': + if self._alarm.state.lower() == "armed stay": return STATE_ALARM_ARMED_HOME - if self._alarm.state.lower() == 'armed away': + if self._alarm.state.lower() == "armed away": return STATE_ALARM_ARMED_AWAY return None @property def device_state_attributes(self): """Return the state attributes.""" - return { - 'sensor_status': self._alarm.sensor_status - } + return {"sensor_status": self._alarm.sensor_status} async def async_alarm_disarm(self, code=None): """Send disarm command.""" diff --git a/homeassistant/components/alert/__init__.py b/homeassistant/components/alert/__init__.py index a5b6d26d4fd..420d730933c 100644 --- a/homeassistant/components/alert/__init__.py +++ b/homeassistant/components/alert/__init__.py @@ -7,51 +7,65 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.notify import ( - ATTR_MESSAGE, ATTR_TITLE, ATTR_DATA, DOMAIN as DOMAIN_NOTIFY) + ATTR_MESSAGE, + ATTR_TITLE, + ATTR_DATA, + DOMAIN as DOMAIN_NOTIFY, +) from homeassistant.const import ( - CONF_ENTITY_ID, STATE_IDLE, CONF_NAME, CONF_STATE, STATE_ON, STATE_OFF, - SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, ATTR_ENTITY_ID) + CONF_ENTITY_ID, + STATE_IDLE, + CONF_NAME, + CONF_STATE, + STATE_ON, + STATE_OFF, + SERVICE_TURN_ON, + SERVICE_TURN_OFF, + SERVICE_TOGGLE, + ATTR_ENTITY_ID, +) from homeassistant.helpers import service, event from homeassistant.helpers.entity import ToggleEntity from homeassistant.util.dt import now _LOGGER = logging.getLogger(__name__) -DOMAIN = 'alert' -ENTITY_ID_FORMAT = DOMAIN + '.{}' +DOMAIN = "alert" +ENTITY_ID_FORMAT = DOMAIN + ".{}" -CONF_CAN_ACK = 'can_acknowledge' -CONF_NOTIFIERS = 'notifiers' -CONF_REPEAT = 'repeat' -CONF_SKIP_FIRST = 'skip_first' -CONF_ALERT_MESSAGE = 'message' -CONF_DONE_MESSAGE = 'done_message' -CONF_TITLE = 'title' -CONF_DATA = 'data' +CONF_CAN_ACK = "can_acknowledge" +CONF_NOTIFIERS = "notifiers" +CONF_REPEAT = "repeat" +CONF_SKIP_FIRST = "skip_first" +CONF_ALERT_MESSAGE = "message" +CONF_DONE_MESSAGE = "done_message" +CONF_TITLE = "title" +CONF_DATA = "data" DEFAULT_CAN_ACK = True DEFAULT_SKIP_FIRST = False -ALERT_SCHEMA = vol.Schema({ - vol.Required(CONF_NAME): cv.string, - vol.Required(CONF_ENTITY_ID): cv.entity_id, - vol.Required(CONF_STATE, default=STATE_ON): cv.string, - vol.Required(CONF_REPEAT): vol.All(cv.ensure_list, [vol.Coerce(float)]), - vol.Required(CONF_CAN_ACK, default=DEFAULT_CAN_ACK): cv.boolean, - vol.Required(CONF_SKIP_FIRST, default=DEFAULT_SKIP_FIRST): cv.boolean, - vol.Optional(CONF_ALERT_MESSAGE): cv.template, - vol.Optional(CONF_DONE_MESSAGE): cv.template, - vol.Optional(CONF_TITLE): cv.template, - vol.Optional(CONF_DATA): dict, - vol.Required(CONF_NOTIFIERS): cv.ensure_list}) +ALERT_SCHEMA = vol.Schema( + { + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_ENTITY_ID): cv.entity_id, + vol.Required(CONF_STATE, default=STATE_ON): cv.string, + vol.Required(CONF_REPEAT): vol.All(cv.ensure_list, [vol.Coerce(float)]), + vol.Required(CONF_CAN_ACK, default=DEFAULT_CAN_ACK): cv.boolean, + vol.Required(CONF_SKIP_FIRST, default=DEFAULT_SKIP_FIRST): cv.boolean, + vol.Optional(CONF_ALERT_MESSAGE): cv.template, + vol.Optional(CONF_DONE_MESSAGE): cv.template, + vol.Optional(CONF_TITLE): cv.template, + vol.Optional(CONF_DATA): dict, + vol.Required(CONF_NOTIFIERS): cv.ensure_list, + } +) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: cv.schema_with_slug_keys(ALERT_SCHEMA), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + {DOMAIN: cv.schema_with_slug_keys(ALERT_SCHEMA)}, extra=vol.ALLOW_EXTRA +) -ALERT_SERVICE_SCHEMA = vol.Schema({ - vol.Required(ATTR_ENTITY_ID): cv.entity_ids, -}) +ALERT_SERVICE_SCHEMA = vol.Schema({vol.Required(ATTR_ENTITY_ID): cv.entity_ids}) def is_on(hass, entity_id): @@ -79,11 +93,23 @@ async def async_setup(hass, config): title_template = cfg.get(CONF_TITLE) data = cfg.get(CONF_DATA) - entities.append(Alert(hass, object_id, name, - watched_entity_id, alert_state, repeat, - skip_first, message_template, - done_message_template, notifiers, - can_ack, title_template, data)) + entities.append( + Alert( + hass, + object_id, + name, + watched_entity_id, + alert_state, + repeat, + skip_first, + message_template, + done_message_template, + notifiers, + can_ack, + title_template, + data, + ) + ) if not entities: return False @@ -107,14 +133,17 @@ async def async_setup(hass, config): # Setup service calls hass.services.async_register( - DOMAIN, SERVICE_TURN_OFF, async_handle_alert_service, - schema=ALERT_SERVICE_SCHEMA) + DOMAIN, + SERVICE_TURN_OFF, + async_handle_alert_service, + schema=ALERT_SERVICE_SCHEMA, + ) hass.services.async_register( - DOMAIN, SERVICE_TURN_ON, async_handle_alert_service, - schema=ALERT_SERVICE_SCHEMA) + DOMAIN, SERVICE_TURN_ON, async_handle_alert_service, schema=ALERT_SERVICE_SCHEMA + ) hass.services.async_register( - DOMAIN, SERVICE_TOGGLE, async_handle_alert_service, - schema=ALERT_SERVICE_SCHEMA) + DOMAIN, SERVICE_TOGGLE, async_handle_alert_service, schema=ALERT_SERVICE_SCHEMA + ) tasks = [alert.async_update_ha_state() for alert in entities] if tasks: @@ -126,10 +155,22 @@ async def async_setup(hass, config): class Alert(ToggleEntity): """Representation of an alert.""" - def __init__(self, hass, entity_id, name, watched_entity_id, - state, repeat, skip_first, message_template, - done_message_template, notifiers, can_ack, title_template, - data): + def __init__( + self, + hass, + entity_id, + name, + watched_entity_id, + state, + repeat, + skip_first, + message_template, + done_message_template, + notifiers, + can_ack, + title_template, + data, + ): """Initialize the alert.""" self.hass = hass self._name = name @@ -162,7 +203,8 @@ class Alert(ToggleEntity): self.entity_id = ENTITY_ID_FORMAT.format(entity_id) event.async_track_state_change( - hass, watched_entity_id, self.watched_entity_change) + hass, watched_entity_id, self.watched_entity_change + ) @property def name(self): @@ -224,8 +266,9 @@ class Alert(ToggleEntity): """Schedule a notification.""" delay = self._delay[self._next_delay] next_msg = now() + delay - self._cancel = \ - event.async_track_point_in_time(self.hass, self._notify, next_msg) + self._cancel = event.async_track_point_in_time( + self.hass, self._notify, next_msg + ) self._next_delay = min(self._next_delay + 1, len(self._delay) - 1) async def _notify(self, *args): @@ -270,8 +313,7 @@ class Alert(ToggleEntity): _LOGGER.debug(msg_payload) for target in self._notifiers: - await self.hass.services.async_call( - DOMAIN_NOTIFY, target, msg_payload) + await self.hass.services.async_call(DOMAIN_NOTIFY, target, msg_payload) async def async_turn_on(self, **kwargs): """Async Unacknowledge alert.""" diff --git a/homeassistant/components/alexa/__init__.py b/homeassistant/components/alexa/__init__.py index a15d87175db..cb0d093bb48 100644 --- a/homeassistant/components/alexa/__init__.py +++ b/homeassistant/components/alexa/__init__.py @@ -9,45 +9,68 @@ from homeassistant.const import CONF_NAME from . import flash_briefings, intent, smart_home_http from .const import ( - CONF_AUDIO, CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_DISPLAY_URL, - CONF_ENDPOINT, CONF_TEXT, CONF_TITLE, CONF_UID, DOMAIN, CONF_FILTER, - CONF_ENTITY_CONFIG, CONF_DESCRIPTION, CONF_DISPLAY_CATEGORIES) + CONF_AUDIO, + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, + CONF_DISPLAY_URL, + CONF_ENDPOINT, + CONF_TEXT, + CONF_TITLE, + CONF_UID, + DOMAIN, + CONF_FILTER, + CONF_ENTITY_CONFIG, + CONF_DESCRIPTION, + CONF_DISPLAY_CATEGORIES, +) _LOGGER = logging.getLogger(__name__) -CONF_FLASH_BRIEFINGS = 'flash_briefings' -CONF_SMART_HOME = 'smart_home' +CONF_FLASH_BRIEFINGS = "flash_briefings" +CONF_SMART_HOME = "smart_home" -ALEXA_ENTITY_SCHEMA = vol.Schema({ - vol.Optional(CONF_DESCRIPTION): cv.string, - vol.Optional(CONF_DISPLAY_CATEGORIES): cv.string, - vol.Optional(CONF_NAME): cv.string, -}) - -SMART_HOME_SCHEMA = vol.Schema({ - vol.Optional(CONF_ENDPOINT): cv.string, - vol.Optional(CONF_CLIENT_ID): cv.string, - vol.Optional(CONF_CLIENT_SECRET): cv.string, - vol.Optional(CONF_FILTER, default={}): entityfilter.FILTER_SCHEMA, - vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: ALEXA_ENTITY_SCHEMA} -}) - -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: { - CONF_FLASH_BRIEFINGS: { - cv.string: vol.All(cv.ensure_list, [{ - vol.Optional(CONF_UID): cv.string, - vol.Required(CONF_TITLE): cv.template, - vol.Optional(CONF_AUDIO): cv.template, - vol.Required(CONF_TEXT, default=""): cv.template, - vol.Optional(CONF_DISPLAY_URL): cv.template, - }]), - }, - # vol.Optional here would mean we couldn't distinguish between an empty - # smart_home: and none at all. - CONF_SMART_HOME: vol.Any(SMART_HOME_SCHEMA, None), +ALEXA_ENTITY_SCHEMA = vol.Schema( + { + vol.Optional(CONF_DESCRIPTION): cv.string, + vol.Optional(CONF_DISPLAY_CATEGORIES): cv.string, + vol.Optional(CONF_NAME): cv.string, } -}, extra=vol.ALLOW_EXTRA) +) + +SMART_HOME_SCHEMA = vol.Schema( + { + vol.Optional(CONF_ENDPOINT): cv.string, + vol.Optional(CONF_CLIENT_ID): cv.string, + vol.Optional(CONF_CLIENT_SECRET): cv.string, + vol.Optional(CONF_FILTER, default={}): entityfilter.FILTER_SCHEMA, + vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: ALEXA_ENTITY_SCHEMA}, + } +) + +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: { + CONF_FLASH_BRIEFINGS: { + cv.string: vol.All( + cv.ensure_list, + [ + { + vol.Optional(CONF_UID): cv.string, + vol.Required(CONF_TITLE): cv.template, + vol.Optional(CONF_AUDIO): cv.template, + vol.Required(CONF_TEXT, default=""): cv.template, + vol.Optional(CONF_DISPLAY_URL): cv.template, + } + ], + ) + }, + # vol.Optional here would mean we couldn't distinguish between an empty + # smart_home: and none at all. + CONF_SMART_HOME: vol.Any(SMART_HOME_SCHEMA, None), + } + }, + extra=vol.ALLOW_EXTRA, +) async def async_setup(hass, config): diff --git a/homeassistant/components/alexa/auth.py b/homeassistant/components/alexa/auth.py index dd61018d739..d4633d938ed 100644 --- a/homeassistant/components/alexa/auth.py +++ b/homeassistant/components/alexa/auth.py @@ -13,12 +13,10 @@ from homeassistant.util import dt _LOGGER = logging.getLogger(__name__) LWA_TOKEN_URI = "https://api.amazon.com/auth/o2/token" -LWA_HEADERS = { - "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8" -} +LWA_HEADERS = {"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8"} PREEMPTIVE_REFRESH_TTL_IN_SECONDS = 300 -STORAGE_KEY = 'alexa_auth' +STORAGE_KEY = "alexa_auth" STORAGE_VERSION = 1 STORAGE_EXPIRE_TIME = "expire_time" STORAGE_ACCESS_TOKEN = "access_token" @@ -49,10 +47,12 @@ class Auth: "grant_type": "authorization_code", "code": accept_grant_code, "client_id": self.client_id, - "client_secret": self.client_secret + "client_secret": self.client_secret, } - _LOGGER.debug("Calling LWA to get the access token (first time), " - "with: %s", json.dumps(lwa_params)) + _LOGGER.debug( + "Calling LWA to get the access token (first time), " "with: %s", + json.dumps(lwa_params), + ) return await self._async_request_new_token(lwa_params) @@ -74,7 +74,7 @@ class Auth: "grant_type": "refresh_token", "refresh_token": self._prefs[STORAGE_REFRESH_TOKEN], "client_id": self.client_id, - "client_secret": self.client_secret + "client_secret": self.client_secret, } _LOGGER.debug("Calling LWA to refresh the access token.") @@ -88,7 +88,8 @@ class Auth: expire_time = dt.parse_datetime(self._prefs[STORAGE_EXPIRE_TIME]) preemptive_expire_time = expire_time - timedelta( - seconds=PREEMPTIVE_REFRESH_TTL_IN_SECONDS) + seconds=PREEMPTIVE_REFRESH_TTL_IN_SECONDS + ) return dt.utcnow() < preemptive_expire_time @@ -97,10 +98,12 @@ class Auth: try: session = aiohttp_client.async_get_clientsession(self.hass) with async_timeout.timeout(10): - response = await session.post(LWA_TOKEN_URI, - headers=LWA_HEADERS, - data=lwa_params, - allow_redirects=True) + response = await session.post( + LWA_TOKEN_URI, + headers=LWA_HEADERS, + data=lwa_params, + allow_redirects=True, + ) except (asyncio.TimeoutError, aiohttp.ClientError): _LOGGER.error("Timeout calling LWA to get auth token.") @@ -121,8 +124,9 @@ class Auth: expires_in = response_json["expires_in"] expire_time = dt.utcnow() + timedelta(seconds=expires_in) - await self._async_update_preferences(access_token, refresh_token, - expire_time.isoformat()) + await self._async_update_preferences( + access_token, refresh_token, expire_time.isoformat() + ) return access_token @@ -134,11 +138,10 @@ class Auth: self._prefs = { STORAGE_ACCESS_TOKEN: None, STORAGE_REFRESH_TOKEN: None, - STORAGE_EXPIRE_TIME: None + STORAGE_EXPIRE_TIME: None, } - async def _async_update_preferences(self, access_token, refresh_token, - expire_time): + async def _async_update_preferences(self, access_token, refresh_token, expire_time): """Update user preferences.""" if self._prefs is None: await self.async_load_preferences() diff --git a/homeassistant/components/alexa/capabilities.py b/homeassistant/components/alexa/capabilities.py index 61fc7e82e32..dfb97cd9db2 100644 --- a/homeassistant/components/alexa/capabilities.py +++ b/homeassistant/components/alexa/capabilities.py @@ -13,11 +13,7 @@ from homeassistant.const import ( STATE_UNLOCKED, ) import homeassistant.components.climate.const as climate -from homeassistant.components import ( - light, - fan, - cover, -) +from homeassistant.components import light, fan, cover import homeassistant.util.color as color_util from .const import ( @@ -85,35 +81,35 @@ class AlexaCapibility: def serialize_discovery(self): """Serialize according to the Discovery API.""" result = { - 'type': 'AlexaInterface', - 'interface': self.name(), - 'version': '3', - 'properties': { - 'supported': self.properties_supported(), - 'proactivelyReported': self.properties_proactively_reported(), - 'retrievable': self.properties_retrievable(), + "type": "AlexaInterface", + "interface": self.name(), + "version": "3", + "properties": { + "supported": self.properties_supported(), + "proactivelyReported": self.properties_proactively_reported(), + "retrievable": self.properties_retrievable(), }, } # pylint: disable=assignment-from-none supports_deactivation = self.supports_deactivation() if supports_deactivation is not None: - result['supportsDeactivation'] = supports_deactivation + result["supportsDeactivation"] = supports_deactivation return result def serialize_properties(self): """Return properties serialized for an API response.""" for prop in self.properties_supported(): - prop_name = prop['name'] + prop_name = prop["name"] # pylint: disable=assignment-from-no-return prop_value = self.get_property(prop_name) if prop_value is not None: yield { - 'name': prop_name, - 'namespace': self.name(), - 'value': prop_value, - 'timeOfSample': datetime.now().strftime(DATE_FORMAT), - 'uncertaintyInMilliseconds': 0 + "name": prop_name, + "namespace": self.name(), + "value": prop_value, + "timeOfSample": datetime.now().strftime(DATE_FORMAT), + "uncertaintyInMilliseconds": 0, } @@ -130,11 +126,11 @@ class AlexaEndpointHealth(AlexaCapibility): def name(self): """Return the Alexa API name of this interface.""" - return 'Alexa.EndpointHealth' + return "Alexa.EndpointHealth" def properties_supported(self): """Return what properties this entity supports.""" - return [{'name': 'connectivity'}] + return [{"name": "connectivity"}] def properties_proactively_reported(self): """Return True if properties asynchronously reported.""" @@ -146,12 +142,12 @@ class AlexaEndpointHealth(AlexaCapibility): def get_property(self, name): """Read and return a property.""" - if name != 'connectivity': + if name != "connectivity": raise UnsupportedProperty(name) if self.entity.state == STATE_UNAVAILABLE: - return {'value': 'UNREACHABLE'} - return {'value': 'OK'} + return {"value": "UNREACHABLE"} + return {"value": "OK"} class AlexaPowerController(AlexaCapibility): @@ -162,11 +158,11 @@ class AlexaPowerController(AlexaCapibility): def name(self): """Return the Alexa API name of this interface.""" - return 'Alexa.PowerController' + return "Alexa.PowerController" def properties_supported(self): """Return what properties this entity supports.""" - return [{'name': 'powerState'}] + return [{"name": "powerState"}] def properties_proactively_reported(self): """Return True if properties asynchronously reported.""" @@ -178,7 +174,7 @@ class AlexaPowerController(AlexaCapibility): def get_property(self, name): """Read and return a property.""" - if name != 'powerState': + if name != "powerState": raise UnsupportedProperty(name) if self.entity.domain == climate.DOMAIN: @@ -187,7 +183,7 @@ class AlexaPowerController(AlexaCapibility): else: is_on = self.entity.state != STATE_OFF - return 'ON' if is_on else 'OFF' + return "ON" if is_on else "OFF" class AlexaLockController(AlexaCapibility): @@ -198,11 +194,11 @@ class AlexaLockController(AlexaCapibility): def name(self): """Return the Alexa API name of this interface.""" - return 'Alexa.LockController' + return "Alexa.LockController" def properties_supported(self): """Return what properties this entity supports.""" - return [{'name': 'lockState'}] + return [{"name": "lockState"}] def properties_retrievable(self): """Return True if properties can be retrieved.""" @@ -214,14 +210,14 @@ class AlexaLockController(AlexaCapibility): def get_property(self, name): """Read and return a property.""" - if name != 'lockState': + if name != "lockState": raise UnsupportedProperty(name) if self.entity.state == STATE_LOCKED: - return 'LOCKED' + return "LOCKED" if self.entity.state == STATE_UNLOCKED: - return 'UNLOCKED' - return 'JAMMED' + return "UNLOCKED" + return "JAMMED" class AlexaSceneController(AlexaCapibility): @@ -237,7 +233,7 @@ class AlexaSceneController(AlexaCapibility): def name(self): """Return the Alexa API name of this interface.""" - return 'Alexa.SceneController' + return "Alexa.SceneController" class AlexaBrightnessController(AlexaCapibility): @@ -248,11 +244,11 @@ class AlexaBrightnessController(AlexaCapibility): def name(self): """Return the Alexa API name of this interface.""" - return 'Alexa.BrightnessController' + return "Alexa.BrightnessController" def properties_supported(self): """Return what properties this entity supports.""" - return [{'name': 'brightness'}] + return [{"name": "brightness"}] def properties_proactively_reported(self): """Return True if properties asynchronously reported.""" @@ -264,10 +260,10 @@ class AlexaBrightnessController(AlexaCapibility): def get_property(self, name): """Read and return a property.""" - if name != 'brightness': + if name != "brightness": raise UnsupportedProperty(name) - if 'brightness' in self.entity.attributes: - return round(self.entity.attributes['brightness'] / 255.0 * 100) + if "brightness" in self.entity.attributes: + return round(self.entity.attributes["brightness"] / 255.0 * 100) return 0 @@ -279,11 +275,11 @@ class AlexaColorController(AlexaCapibility): def name(self): """Return the Alexa API name of this interface.""" - return 'Alexa.ColorController' + return "Alexa.ColorController" def properties_supported(self): """Return what properties this entity supports.""" - return [{'name': 'color'}] + return [{"name": "color"}] def properties_retrievable(self): """Return True if properties can be retrieved.""" @@ -291,17 +287,15 @@ class AlexaColorController(AlexaCapibility): def get_property(self, name): """Read and return a property.""" - if name != 'color': + if name != "color": raise UnsupportedProperty(name) - hue, saturation = self.entity.attributes.get( - light.ATTR_HS_COLOR, (0, 0)) + hue, saturation = self.entity.attributes.get(light.ATTR_HS_COLOR, (0, 0)) return { - 'hue': hue, - 'saturation': saturation / 100.0, - 'brightness': self.entity.attributes.get( - light.ATTR_BRIGHTNESS, 0) / 255.0, + "hue": hue, + "saturation": saturation / 100.0, + "brightness": self.entity.attributes.get(light.ATTR_BRIGHTNESS, 0) / 255.0, } @@ -313,11 +307,11 @@ class AlexaColorTemperatureController(AlexaCapibility): def name(self): """Return the Alexa API name of this interface.""" - return 'Alexa.ColorTemperatureController' + return "Alexa.ColorTemperatureController" def properties_supported(self): """Return what properties this entity supports.""" - return [{'name': 'colorTemperatureInKelvin'}] + return [{"name": "colorTemperatureInKelvin"}] def properties_retrievable(self): """Return True if properties can be retrieved.""" @@ -325,11 +319,12 @@ class AlexaColorTemperatureController(AlexaCapibility): def get_property(self, name): """Read and return a property.""" - if name != 'colorTemperatureInKelvin': + if name != "colorTemperatureInKelvin": raise UnsupportedProperty(name) - if 'color_temp' in self.entity.attributes: + if "color_temp" in self.entity.attributes: return color_util.color_temperature_mired_to_kelvin( - self.entity.attributes['color_temp']) + self.entity.attributes["color_temp"] + ) return 0 @@ -341,11 +336,11 @@ class AlexaPercentageController(AlexaCapibility): def name(self): """Return the Alexa API name of this interface.""" - return 'Alexa.PercentageController' + return "Alexa.PercentageController" def properties_supported(self): """Return what properties this entity supports.""" - return [{'name': 'percentage'}] + return [{"name": "percentage"}] def properties_retrievable(self): """Return True if properties can be retrieved.""" @@ -353,7 +348,7 @@ class AlexaPercentageController(AlexaCapibility): def get_property(self, name): """Read and return a property.""" - if name != 'percentage': + if name != "percentage": raise UnsupportedProperty(name) if self.entity.domain == fan.DOMAIN: @@ -375,7 +370,7 @@ class AlexaSpeaker(AlexaCapibility): def name(self): """Return the Alexa API name of this interface.""" - return 'Alexa.Speaker' + return "Alexa.Speaker" class AlexaStepSpeaker(AlexaCapibility): @@ -386,7 +381,7 @@ class AlexaStepSpeaker(AlexaCapibility): def name(self): """Return the Alexa API name of this interface.""" - return 'Alexa.StepSpeaker' + return "Alexa.StepSpeaker" class AlexaPlaybackController(AlexaCapibility): @@ -397,7 +392,7 @@ class AlexaPlaybackController(AlexaCapibility): def name(self): """Return the Alexa API name of this interface.""" - return 'Alexa.PlaybackController' + return "Alexa.PlaybackController" class AlexaInputController(AlexaCapibility): @@ -408,7 +403,7 @@ class AlexaInputController(AlexaCapibility): def name(self): """Return the Alexa API name of this interface.""" - return 'Alexa.InputController' + return "Alexa.InputController" class AlexaTemperatureSensor(AlexaCapibility): @@ -424,11 +419,11 @@ class AlexaTemperatureSensor(AlexaCapibility): def name(self): """Return the Alexa API name of this interface.""" - return 'Alexa.TemperatureSensor' + return "Alexa.TemperatureSensor" def properties_supported(self): """Return what properties this entity supports.""" - return [{'name': 'temperature'}] + return [{"name": "temperature"}] def properties_proactively_reported(self): """Return True if properties asynchronously reported.""" @@ -440,19 +435,15 @@ class AlexaTemperatureSensor(AlexaCapibility): def get_property(self, name): """Read and return a property.""" - if name != 'temperature': + if name != "temperature": raise UnsupportedProperty(name) unit = self.entity.attributes.get(ATTR_UNIT_OF_MEASUREMENT) temp = self.entity.state if self.entity.domain == climate.DOMAIN: unit = self.hass.config.units.temperature_unit - temp = self.entity.attributes.get( - climate.ATTR_CURRENT_TEMPERATURE) - return { - 'value': float(temp), - 'scale': API_TEMP_UNITS[unit], - } + temp = self.entity.attributes.get(climate.ATTR_CURRENT_TEMPERATURE) + return {"value": float(temp), "scale": API_TEMP_UNITS[unit]} class AlexaContactSensor(AlexaCapibility): @@ -473,11 +464,11 @@ class AlexaContactSensor(AlexaCapibility): def name(self): """Return the Alexa API name of this interface.""" - return 'Alexa.ContactSensor' + return "Alexa.ContactSensor" def properties_supported(self): """Return what properties this entity supports.""" - return [{'name': 'detectionState'}] + return [{"name": "detectionState"}] def properties_proactively_reported(self): """Return True if properties asynchronously reported.""" @@ -489,12 +480,12 @@ class AlexaContactSensor(AlexaCapibility): def get_property(self, name): """Read and return a property.""" - if name != 'detectionState': + if name != "detectionState": raise UnsupportedProperty(name) if self.entity.state == STATE_ON: - return 'DETECTED' - return 'NOT_DETECTED' + return "DETECTED" + return "NOT_DETECTED" class AlexaMotionSensor(AlexaCapibility): @@ -510,11 +501,11 @@ class AlexaMotionSensor(AlexaCapibility): def name(self): """Return the Alexa API name of this interface.""" - return 'Alexa.MotionSensor' + return "Alexa.MotionSensor" def properties_supported(self): """Return what properties this entity supports.""" - return [{'name': 'detectionState'}] + return [{"name": "detectionState"}] def properties_proactively_reported(self): """Return True if properties asynchronously reported.""" @@ -526,12 +517,12 @@ class AlexaMotionSensor(AlexaCapibility): def get_property(self, name): """Read and return a property.""" - if name != 'detectionState': + if name != "detectionState": raise UnsupportedProperty(name) if self.entity.state == STATE_ON: - return 'DETECTED' - return 'NOT_DETECTED' + return "DETECTED" + return "NOT_DETECTED" class AlexaThermostatController(AlexaCapibility): @@ -547,17 +538,17 @@ class AlexaThermostatController(AlexaCapibility): def name(self): """Return the Alexa API name of this interface.""" - return 'Alexa.ThermostatController' + return "Alexa.ThermostatController" def properties_supported(self): """Return what properties this entity supports.""" - properties = [{'name': 'thermostatMode'}] + properties = [{"name": "thermostatMode"}] supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) if supported & climate.SUPPORT_TARGET_TEMPERATURE: - properties.append({'name': 'targetSetpoint'}) + properties.append({"name": "targetSetpoint"}) if supported & climate.SUPPORT_TARGET_TEMPERATURE_RANGE: - properties.append({'name': 'lowerSetpoint'}) - properties.append({'name': 'upperSetpoint'}) + properties.append({"name": "lowerSetpoint"}) + properties.append({"name": "upperSetpoint"}) return properties def properties_proactively_reported(self): @@ -570,7 +561,7 @@ class AlexaThermostatController(AlexaCapibility): def get_property(self, name): """Read and return a property.""" - if name == 'thermostatMode': + if name == "thermostatMode": preset = self.entity.attributes.get(climate.ATTR_PRESET_MODE) if preset in API_THERMOSTAT_PRESETS: @@ -580,17 +571,19 @@ class AlexaThermostatController(AlexaCapibility): if mode is None: _LOGGER.error( "%s (%s) has unsupported state value '%s'", - self.entity.entity_id, type(self.entity), - self.entity.state) + self.entity.entity_id, + type(self.entity), + self.entity.state, + ) raise UnsupportedProperty(name) return mode unit = self.hass.config.units.temperature_unit - if name == 'targetSetpoint': + if name == "targetSetpoint": temp = self.entity.attributes.get(ATTR_TEMPERATURE) - elif name == 'lowerSetpoint': + elif name == "lowerSetpoint": temp = self.entity.attributes.get(climate.ATTR_TARGET_TEMP_LOW) - elif name == 'upperSetpoint': + elif name == "upperSetpoint": temp = self.entity.attributes.get(climate.ATTR_TARGET_TEMP_HIGH) else: raise UnsupportedProperty(name) @@ -598,7 +591,4 @@ class AlexaThermostatController(AlexaCapibility): if temp is None: return None - return { - 'value': float(temp), - 'scale': API_TEMP_UNITS[unit], - } + return {"value": float(temp), "scale": API_TEMP_UNITS[unit]} diff --git a/homeassistant/components/alexa/const.py b/homeassistant/components/alexa/const.py index aacf017f911..282df1756d7 100644 --- a/homeassistant/components/alexa/const.py +++ b/homeassistant/components/alexa/const.py @@ -1,78 +1,68 @@ """Constants for the Alexa integration.""" from collections import OrderedDict -from homeassistant.const import ( - TEMP_CELSIUS, - TEMP_FAHRENHEIT, -) +from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.components.climate import const as climate from homeassistant.components import fan -DOMAIN = 'alexa' +DOMAIN = "alexa" # Flash briefing constants -CONF_UID = 'uid' -CONF_TITLE = 'title' -CONF_AUDIO = 'audio' -CONF_TEXT = 'text' -CONF_DISPLAY_URL = 'display_url' +CONF_UID = "uid" +CONF_TITLE = "title" +CONF_AUDIO = "audio" +CONF_TEXT = "text" +CONF_DISPLAY_URL = "display_url" -CONF_FILTER = 'filter' -CONF_ENTITY_CONFIG = 'entity_config' -CONF_ENDPOINT = 'endpoint' -CONF_CLIENT_ID = 'client_id' -CONF_CLIENT_SECRET = 'client_secret' +CONF_FILTER = "filter" +CONF_ENTITY_CONFIG = "entity_config" +CONF_ENDPOINT = "endpoint" +CONF_CLIENT_ID = "client_id" +CONF_CLIENT_SECRET = "client_secret" -ATTR_UID = 'uid' -ATTR_UPDATE_DATE = 'updateDate' -ATTR_TITLE_TEXT = 'titleText' -ATTR_STREAM_URL = 'streamUrl' -ATTR_MAIN_TEXT = 'mainText' -ATTR_REDIRECTION_URL = 'redirectionURL' +ATTR_UID = "uid" +ATTR_UPDATE_DATE = "updateDate" +ATTR_TITLE_TEXT = "titleText" +ATTR_STREAM_URL = "streamUrl" +ATTR_MAIN_TEXT = "mainText" +ATTR_REDIRECTION_URL = "redirectionURL" -SYN_RESOLUTION_MATCH = 'ER_SUCCESS_MATCH' +SYN_RESOLUTION_MATCH = "ER_SUCCESS_MATCH" -DATE_FORMAT = '%Y-%m-%dT%H:%M:%S.0Z' +DATE_FORMAT = "%Y-%m-%dT%H:%M:%S.0Z" -API_DIRECTIVE = 'directive' -API_ENDPOINT = 'endpoint' -API_EVENT = 'event' -API_CONTEXT = 'context' -API_HEADER = 'header' -API_PAYLOAD = 'payload' -API_SCOPE = 'scope' -API_CHANGE = 'change' +API_DIRECTIVE = "directive" +API_ENDPOINT = "endpoint" +API_EVENT = "event" +API_CONTEXT = "context" +API_HEADER = "header" +API_PAYLOAD = "payload" +API_SCOPE = "scope" +API_CHANGE = "change" -CONF_DESCRIPTION = 'description' -CONF_DISPLAY_CATEGORIES = 'display_categories' +CONF_DESCRIPTION = "description" +CONF_DISPLAY_CATEGORIES = "display_categories" -API_TEMP_UNITS = { - TEMP_FAHRENHEIT: 'FAHRENHEIT', - TEMP_CELSIUS: 'CELSIUS', -} +API_TEMP_UNITS = {TEMP_FAHRENHEIT: "FAHRENHEIT", TEMP_CELSIUS: "CELSIUS"} # Needs to be ordered dict for `async_api_set_thermostat_mode` which does a # reverse mapping of this dict and we want to map the first occurrance of OFF # back to HA state. -API_THERMOSTAT_MODES = OrderedDict([ - (climate.HVAC_MODE_HEAT, 'HEAT'), - (climate.HVAC_MODE_COOL, 'COOL'), - (climate.HVAC_MODE_HEAT_COOL, 'AUTO'), - (climate.HVAC_MODE_AUTO, 'AUTO'), - (climate.HVAC_MODE_OFF, 'OFF'), - (climate.HVAC_MODE_FAN_ONLY, 'OFF'), - (climate.HVAC_MODE_DRY, 'OFF'), -]) -API_THERMOSTAT_PRESETS = { - climate.PRESET_ECO: 'ECO' -} +API_THERMOSTAT_MODES = OrderedDict( + [ + (climate.HVAC_MODE_HEAT, "HEAT"), + (climate.HVAC_MODE_COOL, "COOL"), + (climate.HVAC_MODE_HEAT_COOL, "AUTO"), + (climate.HVAC_MODE_AUTO, "AUTO"), + (climate.HVAC_MODE_OFF, "OFF"), + (climate.HVAC_MODE_FAN_ONLY, "OFF"), + (climate.HVAC_MODE_DRY, "OFF"), + ] +) +API_THERMOSTAT_PRESETS = {climate.PRESET_ECO: "ECO"} -PERCENTAGE_FAN_MAP = { - fan.SPEED_LOW: 33, - fan.SPEED_MEDIUM: 66, - fan.SPEED_HIGH: 100, -} +PERCENTAGE_FAN_MAP = {fan.SPEED_LOW: 33, fan.SPEED_MEDIUM: 66, fan.SPEED_HIGH: 100} class Cause: @@ -84,25 +74,25 @@ class Cause: # Indicates that the event was caused by a customer interaction with an # application. For example, a customer switches on a light, or locks a door # using the Alexa app or an app provided by a device vendor. - APP_INTERACTION = 'APP_INTERACTION' + APP_INTERACTION = "APP_INTERACTION" # Indicates that the event was caused by a physical interaction with an # endpoint. For example manually switching on a light or manually locking a # door lock - PHYSICAL_INTERACTION = 'PHYSICAL_INTERACTION' + PHYSICAL_INTERACTION = "PHYSICAL_INTERACTION" # Indicates that the event was caused by the periodic poll of an appliance, # which found a change in value. For example, you might poll a temperature # sensor every hour, and send the updated temperature to Alexa. - PERIODIC_POLL = 'PERIODIC_POLL' + PERIODIC_POLL = "PERIODIC_POLL" # Indicates that the event was caused by the application of a device rule. # For example, a customer configures a rule to switch on a light if a # motion sensor detects motion. In this case, Alexa receives an event from # the motion sensor, and another event from the light to indicate that its # state change was caused by the rule. - RULE_TRIGGER = 'RULE_TRIGGER' + RULE_TRIGGER = "RULE_TRIGGER" # Indicates that the event was caused by a voice interaction with Alexa. # For example a user speaking to their Echo device. - VOICE_INTERACTION = 'VOICE_INTERACTION' + VOICE_INTERACTION = "VOICE_INTERACTION" diff --git a/homeassistant/components/alexa/entities.py b/homeassistant/components/alexa/entities.py index c7f4fd9b7ea..b060d35be90 100644 --- a/homeassistant/components/alexa/entities.py +++ b/homeassistant/components/alexa/entities.py @@ -14,8 +14,21 @@ from homeassistant.const import ( from homeassistant.util.decorator import Registry from homeassistant.components.climate import const as climate from homeassistant.components import ( - alert, automation, binary_sensor, cover, fan, group, - input_boolean, light, lock, media_player, scene, script, sensor, switch) + alert, + automation, + binary_sensor, + cover, + fan, + group, + input_boolean, + light, + lock, + media_player, + scene, + script, + sensor, + switch, +) from .const import CONF_DESCRIPTION, CONF_DISPLAY_CATEGORIES from .capabilities import ( @@ -129,7 +142,7 @@ class AlexaEntity: def alexa_id(self): """Return the Alexa API entity id.""" - return self.entity.entity_id.replace('.', '#') + return self.entity.entity_id.replace(".", "#") def display_categories(self): """Return a list of display categories.""" @@ -171,15 +184,13 @@ class AlexaEntity: def serialize_discovery(self): """Serialize the entity for discovery.""" return { - 'displayCategories': self.display_categories(), - 'cookie': {}, - 'endpointId': self.alexa_id(), - 'friendlyName': self.friendly_name(), - 'description': self.description(), - 'manufacturerName': 'Home Assistant', - 'capabilities': [ - i.serialize_discovery() for i in self.interfaces() - ] + "displayCategories": self.display_categories(), + "cookie": {}, + "endpointId": self.alexa_id(), + "friendlyName": self.friendly_name(), + "description": self.description(), + "manufacturerName": "Home Assistant", + "capabilities": [i.serialize_discovery() for i in self.interfaces()], } @@ -220,8 +231,10 @@ class GenericCapabilities(AlexaEntity): def interfaces(self): """Yield the supported interfaces.""" - return [AlexaPowerController(self.entity), - AlexaEndpointHealth(self.hass, self.entity)] + return [ + AlexaPowerController(self.entity), + AlexaEndpointHealth(self.hass, self.entity), + ] @ENTITY_ADAPTERS.register(switch.DOMAIN) @@ -234,8 +247,10 @@ class SwitchCapabilities(AlexaEntity): def interfaces(self): """Yield the supported interfaces.""" - return [AlexaPowerController(self.entity), - AlexaEndpointHealth(self.hass, self.entity)] + return [ + AlexaPowerController(self.entity), + AlexaEndpointHealth(self.hass, self.entity), + ] @ENTITY_ADAPTERS.register(climate.DOMAIN) @@ -249,8 +264,7 @@ class ClimateCapabilities(AlexaEntity): def interfaces(self): """Yield the supported interfaces.""" # If we support two modes, one being off, we allow turning on too. - if (climate.HVAC_MODE_OFF in - self.entity.attributes[climate.ATTR_HVAC_MODES]): + if climate.HVAC_MODE_OFF in self.entity.attributes[climate.ATTR_HVAC_MODES]: yield AlexaPowerController(self.entity) yield AlexaThermostatController(self.hass, self.entity) @@ -324,8 +338,10 @@ class LockCapabilities(AlexaEntity): def interfaces(self): """Yield the supported interfaces.""" - return [AlexaLockController(self.entity), - AlexaEndpointHealth(self.hass, self.entity)] + return [ + AlexaLockController(self.entity), + AlexaEndpointHealth(self.hass, self.entity), + ] @ENTITY_ADAPTERS.register(media_player.const.DOMAIN) @@ -345,16 +361,20 @@ class MediaPlayerCapabilities(AlexaEntity): if supported & media_player.const.SUPPORT_VOLUME_SET: yield AlexaSpeaker(self.entity) - step_volume_features = (media_player.const.SUPPORT_VOLUME_MUTE | - media_player.const.SUPPORT_VOLUME_STEP) + step_volume_features = ( + media_player.const.SUPPORT_VOLUME_MUTE + | media_player.const.SUPPORT_VOLUME_STEP + ) if supported & step_volume_features: yield AlexaStepSpeaker(self.entity) - playback_features = (media_player.const.SUPPORT_PLAY | - media_player.const.SUPPORT_PAUSE | - media_player.const.SUPPORT_STOP | - media_player.const.SUPPORT_NEXT_TRACK | - media_player.const.SUPPORT_PREVIOUS_TRACK) + playback_features = ( + media_player.const.SUPPORT_PLAY + | media_player.const.SUPPORT_PAUSE + | media_player.const.SUPPORT_STOP + | media_player.const.SUPPORT_NEXT_TRACK + | media_player.const.SUPPORT_PREVIOUS_TRACK + ) if supported & playback_features: yield AlexaPlaybackController(self.entity) @@ -369,7 +389,7 @@ class SceneCapabilities(AlexaEntity): def description(self): """Return the description of the entity.""" # Required description as per Amazon Scene docs - scene_fmt = '{} (Scene connected via Home Assistant)' + scene_fmt = "{} (Scene connected via Home Assistant)" return scene_fmt.format(AlexaEntity.description(self)) def default_display_categories(self): @@ -378,8 +398,7 @@ class SceneCapabilities(AlexaEntity): def interfaces(self): """Yield the supported interfaces.""" - return [AlexaSceneController(self.entity, - supports_deactivation=False)] + return [AlexaSceneController(self.entity, supports_deactivation=False)] @ENTITY_ADAPTERS.register(script.DOMAIN) @@ -392,9 +411,8 @@ class ScriptCapabilities(AlexaEntity): def interfaces(self): """Yield the supported interfaces.""" - can_cancel = bool(self.entity.attributes.get('can_cancel')) - return [AlexaSceneController(self.entity, - supports_deactivation=can_cancel)] + can_cancel = bool(self.entity.attributes.get("can_cancel")) + return [AlexaSceneController(self.entity, supports_deactivation=can_cancel)] @ENTITY_ADAPTERS.register(sensor.DOMAIN) @@ -410,10 +428,7 @@ class SensorCapabilities(AlexaEntity): def interfaces(self): """Yield the supported interfaces.""" attrs = self.entity.attributes - if attrs.get(ATTR_UNIT_OF_MEASUREMENT) in ( - TEMP_FAHRENHEIT, - TEMP_CELSIUS, - ): + if attrs.get(ATTR_UNIT_OF_MEASUREMENT) in (TEMP_FAHRENHEIT, TEMP_CELSIUS): yield AlexaTemperatureSensor(self.hass, self.entity) yield AlexaEndpointHealth(self.hass, self.entity) @@ -422,8 +437,8 @@ class SensorCapabilities(AlexaEntity): class BinarySensorCapabilities(AlexaEntity): """Class to represent BinarySensor capabilities.""" - TYPE_CONTACT = 'contact' - TYPE_MOTION = 'motion' + TYPE_CONTACT = "contact" + TYPE_MOTION = "motion" def default_display_categories(self): """Return the display categories for this entity.""" @@ -446,12 +461,7 @@ class BinarySensorCapabilities(AlexaEntity): def get_type(self): """Return the type of binary sensor.""" attrs = self.entity.attributes - if attrs.get(ATTR_DEVICE_CLASS) in ( - 'door', - 'garage_door', - 'opening', - 'window', - ): + if attrs.get(ATTR_DEVICE_CLASS) in ("door", "garage_door", "opening", "window"): return self.TYPE_CONTACT - if attrs.get(ATTR_DEVICE_CLASS) == 'motion': + if attrs.get(ATTR_DEVICE_CLASS) == "motion": return self.TYPE_MOTION diff --git a/homeassistant/components/alexa/errors.py b/homeassistant/components/alexa/errors.py index 76ec92edf8d..56202b23e20 100644 --- a/homeassistant/components/alexa/errors.py +++ b/homeassistant/components/alexa/errors.py @@ -35,12 +35,12 @@ class AlexaError(Exception): class AlexaInvalidEndpointError(AlexaError): """The endpoint in the request does not exist.""" - namespace = 'Alexa' - error_type = 'NO_SUCH_ENDPOINT' + namespace = "Alexa" + error_type = "NO_SUCH_ENDPOINT" def __init__(self, endpoint_id): """Initialize invalid endpoint error.""" - msg = 'The endpoint {} does not exist'.format(endpoint_id) + msg = "The endpoint {} does not exist".format(endpoint_id) AlexaError.__init__(self, msg) self.endpoint_id = endpoint_id @@ -48,38 +48,32 @@ class AlexaInvalidEndpointError(AlexaError): class AlexaInvalidValueError(AlexaError): """Class to represent InvalidValue errors.""" - namespace = 'Alexa' - error_type = 'INVALID_VALUE' + namespace = "Alexa" + error_type = "INVALID_VALUE" class AlexaUnsupportedThermostatModeError(AlexaError): """Class to represent UnsupportedThermostatMode errors.""" - namespace = 'Alexa.ThermostatController' - error_type = 'UNSUPPORTED_THERMOSTAT_MODE' + namespace = "Alexa.ThermostatController" + error_type = "UNSUPPORTED_THERMOSTAT_MODE" class AlexaTempRangeError(AlexaError): """Class to represent TempRange errors.""" - namespace = 'Alexa' - error_type = 'TEMPERATURE_VALUE_OUT_OF_RANGE' + namespace = "Alexa" + error_type = "TEMPERATURE_VALUE_OUT_OF_RANGE" def __init__(self, hass, temp, min_temp, max_temp): """Initialize TempRange error.""" unit = hass.config.units.temperature_unit temp_range = { - 'minimumValue': { - 'value': min_temp, - 'scale': API_TEMP_UNITS[unit], - }, - 'maximumValue': { - 'value': max_temp, - 'scale': API_TEMP_UNITS[unit], - }, + "minimumValue": {"value": min_temp, "scale": API_TEMP_UNITS[unit]}, + "maximumValue": {"value": max_temp, "scale": API_TEMP_UNITS[unit]}, } - payload = {'validRange': temp_range} - msg = 'The requested temperature {} is out of range'.format(temp) + payload = {"validRange": temp_range} + msg = "The requested temperature {} is out of range".format(temp) AlexaError.__init__(self, msg, payload) @@ -87,5 +81,5 @@ class AlexaTempRangeError(AlexaError): class AlexaBridgeUnreachableError(AlexaError): """Class to represent BridgeUnreachable errors.""" - namespace = 'Alexa' - error_type = 'BRIDGE_UNREACHABLE' + namespace = "Alexa" + error_type = "BRIDGE_UNREACHABLE" diff --git a/homeassistant/components/alexa/flash_briefings.py b/homeassistant/components/alexa/flash_briefings.py index 537f04b20be..708d1592e4c 100644 --- a/homeassistant/components/alexa/flash_briefings.py +++ b/homeassistant/components/alexa/flash_briefings.py @@ -9,27 +9,36 @@ from homeassistant.core import callback from homeassistant.helpers import template from .const import ( - ATTR_MAIN_TEXT, ATTR_REDIRECTION_URL, ATTR_STREAM_URL, ATTR_TITLE_TEXT, - ATTR_UID, ATTR_UPDATE_DATE, CONF_AUDIO, CONF_DISPLAY_URL, CONF_TEXT, - CONF_TITLE, CONF_UID, DATE_FORMAT) + ATTR_MAIN_TEXT, + ATTR_REDIRECTION_URL, + ATTR_STREAM_URL, + ATTR_TITLE_TEXT, + ATTR_UID, + ATTR_UPDATE_DATE, + CONF_AUDIO, + CONF_DISPLAY_URL, + CONF_TEXT, + CONF_TITLE, + CONF_UID, + DATE_FORMAT, +) _LOGGER = logging.getLogger(__name__) -FLASH_BRIEFINGS_API_ENDPOINT = '/api/alexa/flash_briefings/{briefing_id}' +FLASH_BRIEFINGS_API_ENDPOINT = "/api/alexa/flash_briefings/{briefing_id}" @callback def async_setup(hass, flash_briefing_config): """Activate Alexa component.""" - hass.http.register_view( - AlexaFlashBriefingView(hass, flash_briefing_config)) + hass.http.register_view(AlexaFlashBriefingView(hass, flash_briefing_config)) class AlexaFlashBriefingView(http.HomeAssistantView): """Handle Alexa Flash Briefing skill requests.""" url = FLASH_BRIEFINGS_API_ENDPOINT - name = 'api:alexa:flash_briefings' + name = "api:alexa:flash_briefings" def __init__(self, hass, flash_briefings): """Initialize Alexa view.""" @@ -40,13 +49,12 @@ class AlexaFlashBriefingView(http.HomeAssistantView): @callback def get(self, request, briefing_id): """Handle Alexa Flash Briefing request.""" - _LOGGER.debug("Received Alexa flash briefing request for: %s", - briefing_id) + _LOGGER.debug("Received Alexa flash briefing request for: %s", briefing_id) if self.flash_briefings.get(briefing_id) is None: err = "No configured Alexa flash briefing was found for: %s" _LOGGER.error(err, briefing_id) - return b'', 404 + return b"", 404 briefing = [] @@ -76,10 +84,8 @@ class AlexaFlashBriefingView(http.HomeAssistantView): output[ATTR_STREAM_URL] = item.get(CONF_AUDIO) if item.get(CONF_DISPLAY_URL) is not None: - if isinstance(item.get(CONF_DISPLAY_URL), - template.Template): - output[ATTR_REDIRECTION_URL] = \ - item[CONF_DISPLAY_URL].async_render() + if isinstance(item.get(CONF_DISPLAY_URL), template.Template): + output[ATTR_REDIRECTION_URL] = item[CONF_DISPLAY_URL].async_render() else: output[ATTR_REDIRECTION_URL] = item.get(CONF_DISPLAY_URL) diff --git a/homeassistant/components/alexa/handlers.py b/homeassistant/components/alexa/handlers.py index b66fbf82c0f..cd5b56d60e2 100644 --- a/homeassistant/components/alexa/handlers.py +++ b/homeassistant/components/alexa/handlers.py @@ -7,29 +7,44 @@ from homeassistant import core as ha from homeassistant.components import cover, fan, group, light, media_player from homeassistant.components.climate import const as climate from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE, SERVICE_LOCK, - SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, - SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_STOP, - SERVICE_SET_COVER_POSITION, SERVICE_TURN_OFF, SERVICE_TURN_ON, - SERVICE_UNLOCK, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_MUTE, - SERVICE_VOLUME_SET, SERVICE_VOLUME_UP, TEMP_CELSIUS, TEMP_FAHRENHEIT) + ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, + ATTR_TEMPERATURE, + SERVICE_LOCK, + SERVICE_MEDIA_NEXT_TRACK, + SERVICE_MEDIA_PAUSE, + SERVICE_MEDIA_PLAY, + SERVICE_MEDIA_PREVIOUS_TRACK, + SERVICE_MEDIA_STOP, + SERVICE_SET_COVER_POSITION, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + SERVICE_UNLOCK, + SERVICE_VOLUME_DOWN, + SERVICE_VOLUME_MUTE, + SERVICE_VOLUME_SET, + SERVICE_VOLUME_UP, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, +) import homeassistant.util.color as color_util from homeassistant.util.decorator import Registry from homeassistant.util.temperature import convert as convert_temperature -from .const import ( - API_TEMP_UNITS, API_THERMOSTAT_MODES, API_THERMOSTAT_PRESETS, Cause) +from .const import API_TEMP_UNITS, API_THERMOSTAT_MODES, API_THERMOSTAT_PRESETS, Cause from .entities import async_get_entities from .errors import ( - AlexaInvalidValueError, AlexaTempRangeError, - AlexaUnsupportedThermostatModeError) + AlexaInvalidValueError, + AlexaTempRangeError, + AlexaUnsupportedThermostatModeError, +) from .state_report import async_enable_proactive_mode _LOGGER = logging.getLogger(__name__) HANDLERS = Registry() -@HANDLERS.register(('Alexa.Discovery', 'Discover')) +@HANDLERS.register(("Alexa.Discovery", "Discover")) async def async_api_discovery(hass, config, directive, context): """Create a API formatted discovery response. @@ -42,19 +57,19 @@ async def async_api_discovery(hass, config, directive, context): ] return directive.response( - name='Discover.Response', - namespace='Alexa.Discovery', - payload={'endpoints': discovery_endpoints}, + name="Discover.Response", + namespace="Alexa.Discovery", + payload={"endpoints": discovery_endpoints}, ) -@HANDLERS.register(('Alexa.Authorization', 'AcceptGrant')) +@HANDLERS.register(("Alexa.Authorization", "AcceptGrant")) async def async_api_accept_grant(hass, config, directive, context): """Create a API formatted AcceptGrant response. Async friendly. """ - auth_code = directive.payload['grant']['code'] + auth_code = directive.payload["grant"]["code"] _LOGGER.debug("AcceptGrant code: %s", auth_code) if config.supports_auth: @@ -64,12 +79,11 @@ async def async_api_accept_grant(hass, config, directive, context): await async_enable_proactive_mode(hass, config) return directive.response( - name='AcceptGrant.Response', - namespace='Alexa.Authorization', - payload={}) + name="AcceptGrant.Response", namespace="Alexa.Authorization", payload={} + ) -@HANDLERS.register(('Alexa.PowerController', 'TurnOn')) +@HANDLERS.register(("Alexa.PowerController", "TurnOn")) async def async_api_turn_on(hass, config, directive, context): """Process a turn on request.""" entity = directive.entity @@ -82,19 +96,22 @@ async def async_api_turn_on(hass, config, directive, context): service = cover.SERVICE_OPEN_COVER elif domain == media_player.DOMAIN: supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) - power_features = (media_player.SUPPORT_TURN_ON | - media_player.SUPPORT_TURN_OFF) + power_features = media_player.SUPPORT_TURN_ON | media_player.SUPPORT_TURN_OFF if not supported & power_features: service = media_player.SERVICE_MEDIA_PLAY - await hass.services.async_call(domain, service, { - ATTR_ENTITY_ID: entity.entity_id - }, blocking=False, context=context) + await hass.services.async_call( + domain, + service, + {ATTR_ENTITY_ID: entity.entity_id}, + blocking=False, + context=context, + ) return directive.response() -@HANDLERS.register(('Alexa.PowerController', 'TurnOff')) +@HANDLERS.register(("Alexa.PowerController", "TurnOff")) async def async_api_turn_off(hass, config, directive, context): """Process a turn off request.""" entity = directive.entity @@ -107,89 +124,104 @@ async def async_api_turn_off(hass, config, directive, context): service = cover.SERVICE_CLOSE_COVER elif domain == media_player.DOMAIN: supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) - power_features = (media_player.SUPPORT_TURN_ON | - media_player.SUPPORT_TURN_OFF) + power_features = media_player.SUPPORT_TURN_ON | media_player.SUPPORT_TURN_OFF if not supported & power_features: service = media_player.SERVICE_MEDIA_STOP - await hass.services.async_call(domain, service, { - ATTR_ENTITY_ID: entity.entity_id - }, blocking=False, context=context) + await hass.services.async_call( + domain, + service, + {ATTR_ENTITY_ID: entity.entity_id}, + blocking=False, + context=context, + ) return directive.response() -@HANDLERS.register(('Alexa.BrightnessController', 'SetBrightness')) +@HANDLERS.register(("Alexa.BrightnessController", "SetBrightness")) async def async_api_set_brightness(hass, config, directive, context): """Process a set brightness request.""" entity = directive.entity - brightness = int(directive.payload['brightness']) + brightness = int(directive.payload["brightness"]) - await hass.services.async_call(entity.domain, SERVICE_TURN_ON, { - ATTR_ENTITY_ID: entity.entity_id, - light.ATTR_BRIGHTNESS_PCT: brightness, - }, blocking=False, context=context) + await hass.services.async_call( + entity.domain, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity.entity_id, light.ATTR_BRIGHTNESS_PCT: brightness}, + blocking=False, + context=context, + ) return directive.response() -@HANDLERS.register(('Alexa.BrightnessController', 'AdjustBrightness')) +@HANDLERS.register(("Alexa.BrightnessController", "AdjustBrightness")) async def async_api_adjust_brightness(hass, config, directive, context): """Process an adjust brightness request.""" entity = directive.entity - brightness_delta = int(directive.payload['brightnessDelta']) + brightness_delta = int(directive.payload["brightnessDelta"]) # read current state try: current = math.floor( - int(entity.attributes.get(light.ATTR_BRIGHTNESS)) / 255 * 100) + int(entity.attributes.get(light.ATTR_BRIGHTNESS)) / 255 * 100 + ) except ZeroDivisionError: current = 0 # set brightness brightness = max(0, brightness_delta + current) - await hass.services.async_call(entity.domain, SERVICE_TURN_ON, { - ATTR_ENTITY_ID: entity.entity_id, - light.ATTR_BRIGHTNESS_PCT: brightness, - }, blocking=False, context=context) + await hass.services.async_call( + entity.domain, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity.entity_id, light.ATTR_BRIGHTNESS_PCT: brightness}, + blocking=False, + context=context, + ) return directive.response() -@HANDLERS.register(('Alexa.ColorController', 'SetColor')) +@HANDLERS.register(("Alexa.ColorController", "SetColor")) async def async_api_set_color(hass, config, directive, context): """Process a set color request.""" entity = directive.entity rgb = color_util.color_hsb_to_RGB( - float(directive.payload['color']['hue']), - float(directive.payload['color']['saturation']), - float(directive.payload['color']['brightness']) + float(directive.payload["color"]["hue"]), + float(directive.payload["color"]["saturation"]), + float(directive.payload["color"]["brightness"]), ) - await hass.services.async_call(entity.domain, SERVICE_TURN_ON, { - ATTR_ENTITY_ID: entity.entity_id, - light.ATTR_RGB_COLOR: rgb, - }, blocking=False, context=context) + await hass.services.async_call( + entity.domain, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity.entity_id, light.ATTR_RGB_COLOR: rgb}, + blocking=False, + context=context, + ) return directive.response() -@HANDLERS.register(('Alexa.ColorTemperatureController', 'SetColorTemperature')) +@HANDLERS.register(("Alexa.ColorTemperatureController", "SetColorTemperature")) async def async_api_set_color_temperature(hass, config, directive, context): """Process a set color temperature request.""" entity = directive.entity - kelvin = int(directive.payload['colorTemperatureInKelvin']) + kelvin = int(directive.payload["colorTemperatureInKelvin"]) - await hass.services.async_call(entity.domain, SERVICE_TURN_ON, { - ATTR_ENTITY_ID: entity.entity_id, - light.ATTR_KELVIN: kelvin, - }, blocking=False, context=context) + await hass.services.async_call( + entity.domain, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity.entity_id, light.ATTR_KELVIN: kelvin}, + blocking=False, + context=context, + ) return directive.response() -@HANDLERS.register( - ('Alexa.ColorTemperatureController', 'DecreaseColorTemperature')) +@HANDLERS.register(("Alexa.ColorTemperatureController", "DecreaseColorTemperature")) async def async_api_decrease_color_temp(hass, config, directive, context): """Process a decrease color temperature request.""" entity = directive.entity @@ -197,16 +229,18 @@ async def async_api_decrease_color_temp(hass, config, directive, context): max_mireds = int(entity.attributes.get(light.ATTR_MAX_MIREDS)) value = min(max_mireds, current + 50) - await hass.services.async_call(entity.domain, SERVICE_TURN_ON, { - ATTR_ENTITY_ID: entity.entity_id, - light.ATTR_COLOR_TEMP: value, - }, blocking=False, context=context) + await hass.services.async_call( + entity.domain, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity.entity_id, light.ATTR_COLOR_TEMP: value}, + blocking=False, + context=context, + ) return directive.response() -@HANDLERS.register( - ('Alexa.ColorTemperatureController', 'IncreaseColorTemperature')) +@HANDLERS.register(("Alexa.ColorTemperatureController", "IncreaseColorTemperature")) async def async_api_increase_color_temp(hass, config, directive, context): """Process an increase color temperature request.""" entity = directive.entity @@ -214,63 +248,70 @@ async def async_api_increase_color_temp(hass, config, directive, context): min_mireds = int(entity.attributes.get(light.ATTR_MIN_MIREDS)) value = max(min_mireds, current - 50) - await hass.services.async_call(entity.domain, SERVICE_TURN_ON, { - ATTR_ENTITY_ID: entity.entity_id, - light.ATTR_COLOR_TEMP: value, - }, blocking=False, context=context) + await hass.services.async_call( + entity.domain, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity.entity_id, light.ATTR_COLOR_TEMP: value}, + blocking=False, + context=context, + ) return directive.response() -@HANDLERS.register(('Alexa.SceneController', 'Activate')) +@HANDLERS.register(("Alexa.SceneController", "Activate")) async def async_api_activate(hass, config, directive, context): """Process an activate request.""" entity = directive.entity domain = entity.domain - await hass.services.async_call(domain, SERVICE_TURN_ON, { - ATTR_ENTITY_ID: entity.entity_id - }, blocking=False, context=context) + await hass.services.async_call( + domain, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity.entity_id}, + blocking=False, + context=context, + ) payload = { - 'cause': {'type': Cause.VOICE_INTERACTION}, - 'timestamp': '%sZ' % (datetime.utcnow().isoformat(),) + "cause": {"type": Cause.VOICE_INTERACTION}, + "timestamp": "%sZ" % (datetime.utcnow().isoformat(),), } return directive.response( - name='ActivationStarted', - namespace='Alexa.SceneController', - payload=payload, + name="ActivationStarted", namespace="Alexa.SceneController", payload=payload ) -@HANDLERS.register(('Alexa.SceneController', 'Deactivate')) +@HANDLERS.register(("Alexa.SceneController", "Deactivate")) async def async_api_deactivate(hass, config, directive, context): """Process a deactivate request.""" entity = directive.entity domain = entity.domain - await hass.services.async_call(domain, SERVICE_TURN_OFF, { - ATTR_ENTITY_ID: entity.entity_id - }, blocking=False, context=context) + await hass.services.async_call( + domain, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: entity.entity_id}, + blocking=False, + context=context, + ) payload = { - 'cause': {'type': Cause.VOICE_INTERACTION}, - 'timestamp': '%sZ' % (datetime.utcnow().isoformat(),) + "cause": {"type": Cause.VOICE_INTERACTION}, + "timestamp": "%sZ" % (datetime.utcnow().isoformat(),), } return directive.response( - name='DeactivationStarted', - namespace='Alexa.SceneController', - payload=payload, + name="DeactivationStarted", namespace="Alexa.SceneController", payload=payload ) -@HANDLERS.register(('Alexa.PercentageController', 'SetPercentage')) +@HANDLERS.register(("Alexa.PercentageController", "SetPercentage")) async def async_api_set_percentage(hass, config, directive, context): """Process a set percentage request.""" entity = directive.entity - percentage = int(directive.payload['percentage']) + percentage = int(directive.payload["percentage"]) service = None data = {ATTR_ENTITY_ID: entity.entity_id} @@ -291,16 +332,17 @@ async def async_api_set_percentage(hass, config, directive, context): data[cover.ATTR_POSITION] = percentage await hass.services.async_call( - entity.domain, service, data, blocking=False, context=context) + entity.domain, service, data, blocking=False, context=context + ) return directive.response() -@HANDLERS.register(('Alexa.PercentageController', 'AdjustPercentage')) +@HANDLERS.register(("Alexa.PercentageController", "AdjustPercentage")) async def async_api_adjust_percentage(hass, config, directive, context): """Process an adjust percentage request.""" entity = directive.entity - percentage_delta = int(directive.payload['percentageDelta']) + percentage_delta = int(directive.payload["percentageDelta"]) service = None data = {ATTR_ENTITY_ID: entity.entity_id} @@ -338,44 +380,51 @@ async def async_api_adjust_percentage(hass, config, directive, context): data[cover.ATTR_POSITION] = max(0, percentage_delta + current) await hass.services.async_call( - entity.domain, service, data, blocking=False, context=context) + entity.domain, service, data, blocking=False, context=context + ) return directive.response() -@HANDLERS.register(('Alexa.LockController', 'Lock')) +@HANDLERS.register(("Alexa.LockController", "Lock")) async def async_api_lock(hass, config, directive, context): """Process a lock request.""" entity = directive.entity - await hass.services.async_call(entity.domain, SERVICE_LOCK, { - ATTR_ENTITY_ID: entity.entity_id - }, blocking=False, context=context) + await hass.services.async_call( + entity.domain, + SERVICE_LOCK, + {ATTR_ENTITY_ID: entity.entity_id}, + blocking=False, + context=context, + ) response = directive.response() - response.add_context_property({ - 'name': 'lockState', - 'namespace': 'Alexa.LockController', - 'value': 'LOCKED' - }) + response.add_context_property( + {"name": "lockState", "namespace": "Alexa.LockController", "value": "LOCKED"} + ) return response # Not supported by Alexa yet -@HANDLERS.register(('Alexa.LockController', 'Unlock')) +@HANDLERS.register(("Alexa.LockController", "Unlock")) async def async_api_unlock(hass, config, directive, context): """Process an unlock request.""" entity = directive.entity - await hass.services.async_call(entity.domain, SERVICE_UNLOCK, { - ATTR_ENTITY_ID: entity.entity_id - }, blocking=False, context=context) + await hass.services.async_call( + entity.domain, + SERVICE_UNLOCK, + {ATTR_ENTITY_ID: entity.entity_id}, + blocking=False, + context=context, + ) return directive.response() -@HANDLERS.register(('Alexa.Speaker', 'SetVolume')) +@HANDLERS.register(("Alexa.Speaker", "SetVolume")) async def async_api_set_volume(hass, config, directive, context): """Process a set volume request.""" - volume = round(float(directive.payload['volume'] / 100), 2) + volume = round(float(directive.payload["volume"] / 100), 2) entity = directive.entity data = { @@ -384,31 +433,31 @@ async def async_api_set_volume(hass, config, directive, context): } await hass.services.async_call( - entity.domain, SERVICE_VOLUME_SET, - data, blocking=False, context=context) + entity.domain, SERVICE_VOLUME_SET, data, blocking=False, context=context + ) return directive.response() -@HANDLERS.register(('Alexa.InputController', 'SelectInput')) +@HANDLERS.register(("Alexa.InputController", "SelectInput")) async def async_api_select_input(hass, config, directive, context): """Process a set input request.""" - media_input = directive.payload['input'] + media_input = directive.payload["input"] entity = directive.entity # attempt to map the ALL UPPERCASE payload name to a source - source_list = entity.attributes[ - media_player.const.ATTR_INPUT_SOURCE_LIST] or [] + source_list = entity.attributes[media_player.const.ATTR_INPUT_SOURCE_LIST] or [] for source in source_list: # response will always be space separated, so format the source in the # most likely way to find a match - formatted_source = source.lower().replace('-', ' ').replace('_', ' ') + formatted_source = source.lower().replace("-", " ").replace("_", " ") if formatted_source in media_input.lower(): media_input = source break else: - msg = 'failed to map input {} to a media source on {}'.format( - media_input, entity.entity_id) + msg = "failed to map input {} to a media source on {}".format( + media_input, entity.entity_id + ) raise AlexaInvalidValueError(msg) data = { @@ -417,20 +466,23 @@ async def async_api_select_input(hass, config, directive, context): } await hass.services.async_call( - entity.domain, media_player.SERVICE_SELECT_SOURCE, - data, blocking=False, context=context) + entity.domain, + media_player.SERVICE_SELECT_SOURCE, + data, + blocking=False, + context=context, + ) return directive.response() -@HANDLERS.register(('Alexa.Speaker', 'AdjustVolume')) +@HANDLERS.register(("Alexa.Speaker", "AdjustVolume")) async def async_api_adjust_volume(hass, config, directive, context): """Process an adjust volume request.""" - volume_delta = int(directive.payload['volume']) + volume_delta = int(directive.payload["volume"]) entity = directive.entity - current_level = entity.attributes.get( - media_player.const.ATTR_MEDIA_VOLUME_LEVEL) + current_level = entity.attributes.get(media_player.const.ATTR_MEDIA_VOLUME_LEVEL) # read current state try: @@ -446,43 +498,41 @@ async def async_api_adjust_volume(hass, config, directive, context): } await hass.services.async_call( - entity.domain, SERVICE_VOLUME_SET, - data, blocking=False, context=context) + entity.domain, SERVICE_VOLUME_SET, data, blocking=False, context=context + ) return directive.response() -@HANDLERS.register(('Alexa.StepSpeaker', 'AdjustVolume')) +@HANDLERS.register(("Alexa.StepSpeaker", "AdjustVolume")) async def async_api_adjust_volume_step(hass, config, directive, context): """Process an adjust volume step request.""" # media_player volume up/down service does not support specifying steps # each component handles it differently e.g. via config. # For now we use the volumeSteps returned to figure out if we # should step up/down - volume_step = directive.payload['volumeSteps'] + volume_step = directive.payload["volumeSteps"] entity = directive.entity - data = { - ATTR_ENTITY_ID: entity.entity_id, - } + data = {ATTR_ENTITY_ID: entity.entity_id} if volume_step > 0: await hass.services.async_call( - entity.domain, SERVICE_VOLUME_UP, - data, blocking=False, context=context) + entity.domain, SERVICE_VOLUME_UP, data, blocking=False, context=context + ) elif volume_step < 0: await hass.services.async_call( - entity.domain, SERVICE_VOLUME_DOWN, - data, blocking=False, context=context) + entity.domain, SERVICE_VOLUME_DOWN, data, blocking=False, context=context + ) return directive.response() -@HANDLERS.register(('Alexa.StepSpeaker', 'SetMute')) -@HANDLERS.register(('Alexa.Speaker', 'SetMute')) +@HANDLERS.register(("Alexa.StepSpeaker", "SetMute")) +@HANDLERS.register(("Alexa.Speaker", "SetMute")) async def async_api_set_mute(hass, config, directive, context): """Process a set mute request.""" - mute = bool(directive.payload['mute']) + mute = bool(directive.payload["mute"]) entity = directive.entity data = { @@ -491,83 +541,77 @@ async def async_api_set_mute(hass, config, directive, context): } await hass.services.async_call( - entity.domain, SERVICE_VOLUME_MUTE, - data, blocking=False, context=context) + entity.domain, SERVICE_VOLUME_MUTE, data, blocking=False, context=context + ) return directive.response() -@HANDLERS.register(('Alexa.PlaybackController', 'Play')) +@HANDLERS.register(("Alexa.PlaybackController", "Play")) async def async_api_play(hass, config, directive, context): """Process a play request.""" entity = directive.entity - data = { - ATTR_ENTITY_ID: entity.entity_id - } + data = {ATTR_ENTITY_ID: entity.entity_id} await hass.services.async_call( - entity.domain, SERVICE_MEDIA_PLAY, - data, blocking=False, context=context) + entity.domain, SERVICE_MEDIA_PLAY, data, blocking=False, context=context + ) return directive.response() -@HANDLERS.register(('Alexa.PlaybackController', 'Pause')) +@HANDLERS.register(("Alexa.PlaybackController", "Pause")) async def async_api_pause(hass, config, directive, context): """Process a pause request.""" entity = directive.entity - data = { - ATTR_ENTITY_ID: entity.entity_id - } + data = {ATTR_ENTITY_ID: entity.entity_id} await hass.services.async_call( - entity.domain, SERVICE_MEDIA_PAUSE, - data, blocking=False, context=context) + entity.domain, SERVICE_MEDIA_PAUSE, data, blocking=False, context=context + ) return directive.response() -@HANDLERS.register(('Alexa.PlaybackController', 'Stop')) +@HANDLERS.register(("Alexa.PlaybackController", "Stop")) async def async_api_stop(hass, config, directive, context): """Process a stop request.""" entity = directive.entity - data = { - ATTR_ENTITY_ID: entity.entity_id - } + data = {ATTR_ENTITY_ID: entity.entity_id} await hass.services.async_call( - entity.domain, SERVICE_MEDIA_STOP, - data, blocking=False, context=context) + entity.domain, SERVICE_MEDIA_STOP, data, blocking=False, context=context + ) return directive.response() -@HANDLERS.register(('Alexa.PlaybackController', 'Next')) +@HANDLERS.register(("Alexa.PlaybackController", "Next")) async def async_api_next(hass, config, directive, context): """Process a next request.""" entity = directive.entity - data = { - ATTR_ENTITY_ID: entity.entity_id - } + data = {ATTR_ENTITY_ID: entity.entity_id} await hass.services.async_call( - entity.domain, SERVICE_MEDIA_NEXT_TRACK, - data, blocking=False, context=context) + entity.domain, SERVICE_MEDIA_NEXT_TRACK, data, blocking=False, context=context + ) return directive.response() -@HANDLERS.register(('Alexa.PlaybackController', 'Previous')) +@HANDLERS.register(("Alexa.PlaybackController", "Previous")) async def async_api_previous(hass, config, directive, context): """Process a previous request.""" entity = directive.entity - data = { - ATTR_ENTITY_ID: entity.entity_id - } + data = {ATTR_ENTITY_ID: entity.entity_id} await hass.services.async_call( - entity.domain, SERVICE_MEDIA_PREVIOUS_TRACK, - data, blocking=False, context=context) + entity.domain, + SERVICE_MEDIA_PREVIOUS_TRACK, + data, + blocking=False, + context=context, + ) return directive.response() @@ -576,11 +620,11 @@ def temperature_from_object(hass, temp_obj, interval=False): """Get temperature from Temperature object in requested unit.""" to_unit = hass.config.units.temperature_unit from_unit = TEMP_CELSIUS - temp = float(temp_obj['value']) + temp = float(temp_obj["value"]) - if temp_obj['scale'] == 'FAHRENHEIT': + if temp_obj["scale"] == "FAHRENHEIT": from_unit = TEMP_FAHRENHEIT - elif temp_obj['scale'] == 'KELVIN': + elif temp_obj["scale"] == "KELVIN": # convert to Celsius if absolute temperature if not interval: temp -= 273.15 @@ -588,7 +632,7 @@ def temperature_from_object(hass, temp_obj, interval=False): return convert_temperature(temp, from_unit, to_unit, interval) -@HANDLERS.register(('Alexa.ThermostatController', 'SetTargetTemperature')) +@HANDLERS.register(("Alexa.ThermostatController", "SetTargetTemperature")) async def async_api_set_target_temp(hass, config, directive, context): """Process a set target temperature request.""" entity = directive.entity @@ -596,51 +640,59 @@ async def async_api_set_target_temp(hass, config, directive, context): max_temp = entity.attributes.get(climate.ATTR_MAX_TEMP) unit = hass.config.units.temperature_unit - data = { - ATTR_ENTITY_ID: entity.entity_id - } + data = {ATTR_ENTITY_ID: entity.entity_id} payload = directive.payload response = directive.response() - if 'targetSetpoint' in payload: - temp = temperature_from_object(hass, payload['targetSetpoint']) + if "targetSetpoint" in payload: + temp = temperature_from_object(hass, payload["targetSetpoint"]) if temp < min_temp or temp > max_temp: raise AlexaTempRangeError(hass, temp, min_temp, max_temp) data[ATTR_TEMPERATURE] = temp - response.add_context_property({ - 'name': 'targetSetpoint', - 'namespace': 'Alexa.ThermostatController', - 'value': {'value': temp, 'scale': API_TEMP_UNITS[unit]}, - }) - if 'lowerSetpoint' in payload: - temp_low = temperature_from_object(hass, payload['lowerSetpoint']) + response.add_context_property( + { + "name": "targetSetpoint", + "namespace": "Alexa.ThermostatController", + "value": {"value": temp, "scale": API_TEMP_UNITS[unit]}, + } + ) + if "lowerSetpoint" in payload: + temp_low = temperature_from_object(hass, payload["lowerSetpoint"]) if temp_low < min_temp or temp_low > max_temp: raise AlexaTempRangeError(hass, temp_low, min_temp, max_temp) data[climate.ATTR_TARGET_TEMP_LOW] = temp_low - response.add_context_property({ - 'name': 'lowerSetpoint', - 'namespace': 'Alexa.ThermostatController', - 'value': {'value': temp_low, 'scale': API_TEMP_UNITS[unit]}, - }) - if 'upperSetpoint' in payload: - temp_high = temperature_from_object(hass, payload['upperSetpoint']) + response.add_context_property( + { + "name": "lowerSetpoint", + "namespace": "Alexa.ThermostatController", + "value": {"value": temp_low, "scale": API_TEMP_UNITS[unit]}, + } + ) + if "upperSetpoint" in payload: + temp_high = temperature_from_object(hass, payload["upperSetpoint"]) if temp_high < min_temp or temp_high > max_temp: raise AlexaTempRangeError(hass, temp_high, min_temp, max_temp) data[climate.ATTR_TARGET_TEMP_HIGH] = temp_high - response.add_context_property({ - 'name': 'upperSetpoint', - 'namespace': 'Alexa.ThermostatController', - 'value': {'value': temp_high, 'scale': API_TEMP_UNITS[unit]}, - }) + response.add_context_property( + { + "name": "upperSetpoint", + "namespace": "Alexa.ThermostatController", + "value": {"value": temp_high, "scale": API_TEMP_UNITS[unit]}, + } + ) await hass.services.async_call( - entity.domain, climate.SERVICE_SET_TEMPERATURE, data, blocking=False, - context=context) + entity.domain, + climate.SERVICE_SET_TEMPERATURE, + data, + blocking=False, + context=context, + ) return response -@HANDLERS.register(('Alexa.ThermostatController', 'AdjustTargetTemperature')) +@HANDLERS.register(("Alexa.ThermostatController", "AdjustTargetTemperature")) async def async_api_adjust_target_temp(hass, config, directive, context): """Process an adjust target temperature request.""" entity = directive.entity @@ -649,53 +701,50 @@ async def async_api_adjust_target_temp(hass, config, directive, context): unit = hass.config.units.temperature_unit temp_delta = temperature_from_object( - hass, directive.payload['targetSetpointDelta'], interval=True) + hass, directive.payload["targetSetpointDelta"], interval=True + ) target_temp = float(entity.attributes.get(ATTR_TEMPERATURE)) + temp_delta if target_temp < min_temp or target_temp > max_temp: raise AlexaTempRangeError(hass, target_temp, min_temp, max_temp) - data = { - ATTR_ENTITY_ID: entity.entity_id, - ATTR_TEMPERATURE: target_temp, - } + data = {ATTR_ENTITY_ID: entity.entity_id, ATTR_TEMPERATURE: target_temp} response = directive.response() await hass.services.async_call( - entity.domain, climate.SERVICE_SET_TEMPERATURE, data, blocking=False, - context=context) - response.add_context_property({ - 'name': 'targetSetpoint', - 'namespace': 'Alexa.ThermostatController', - 'value': {'value': target_temp, 'scale': API_TEMP_UNITS[unit]}, - }) + entity.domain, + climate.SERVICE_SET_TEMPERATURE, + data, + blocking=False, + context=context, + ) + response.add_context_property( + { + "name": "targetSetpoint", + "namespace": "Alexa.ThermostatController", + "value": {"value": target_temp, "scale": API_TEMP_UNITS[unit]}, + } + ) return response -@HANDLERS.register(('Alexa.ThermostatController', 'SetThermostatMode')) +@HANDLERS.register(("Alexa.ThermostatController", "SetThermostatMode")) async def async_api_set_thermostat_mode(hass, config, directive, context): """Process a set thermostat mode request.""" entity = directive.entity - mode = directive.payload['thermostatMode'] - mode = mode if isinstance(mode, str) else mode['value'] + mode = directive.payload["thermostatMode"] + mode = mode if isinstance(mode, str) else mode["value"] - data = { - ATTR_ENTITY_ID: entity.entity_id, - } + data = {ATTR_ENTITY_ID: entity.entity_id} - ha_preset = next( - (k for k, v in API_THERMOSTAT_PRESETS.items() if v == mode), - None - ) + ha_preset = next((k for k, v in API_THERMOSTAT_PRESETS.items() if v == mode), None) if ha_preset: presets = entity.attributes.get(climate.ATTR_PRESET_MODES, []) if ha_preset not in presets: - msg = 'The requested thermostat mode {} is not supported'.format( - ha_preset - ) + msg = "The requested thermostat mode {} is not supported".format(ha_preset) raise AlexaUnsupportedThermostatModeError(msg) service = climate.SERVICE_SET_PRESET_MODE @@ -703,14 +752,9 @@ async def async_api_set_thermostat_mode(hass, config, directive, context): else: operation_list = entity.attributes.get(climate.ATTR_HVAC_MODES) - ha_mode = next( - (k for k, v in API_THERMOSTAT_MODES.items() if v == mode), - None - ) + ha_mode = next((k for k, v in API_THERMOSTAT_MODES.items() if v == mode), None) if ha_mode not in operation_list: - msg = 'The requested thermostat mode {} is not supported'.format( - mode - ) + msg = "The requested thermostat mode {} is not supported".format(mode) raise AlexaUnsupportedThermostatModeError(msg) service = climate.SERVICE_SET_HVAC_MODE @@ -718,18 +762,20 @@ async def async_api_set_thermostat_mode(hass, config, directive, context): response = directive.response() await hass.services.async_call( - climate.DOMAIN, service, data, - blocking=False, context=context) - response.add_context_property({ - 'name': 'thermostatMode', - 'namespace': 'Alexa.ThermostatController', - 'value': mode, - }) + climate.DOMAIN, service, data, blocking=False, context=context + ) + response.add_context_property( + { + "name": "thermostatMode", + "namespace": "Alexa.ThermostatController", + "value": mode, + } + ) return response -@HANDLERS.register(('Alexa', 'ReportState')) +@HANDLERS.register(("Alexa", "ReportState")) async def async_api_reportstate(hass, config, directive, context): """Process a ReportState request.""" - return directive.response(name='StateReport') + return directive.response(name="StateReport") diff --git a/homeassistant/components/alexa/intent.py b/homeassistant/components/alexa/intent.py index b30a7238b3e..edeb6865aad 100644 --- a/homeassistant/components/alexa/intent.py +++ b/homeassistant/components/alexa/intent.py @@ -14,27 +14,24 @@ _LOGGER = logging.getLogger(__name__) HANDLERS = Registry() -INTENTS_API_ENDPOINT = '/api/alexa' +INTENTS_API_ENDPOINT = "/api/alexa" class SpeechType(enum.Enum): """The Alexa speech types.""" - plaintext = 'PlainText' - ssml = 'SSML' + plaintext = "PlainText" + ssml = "SSML" -SPEECH_MAPPINGS = { - 'plain': SpeechType.plaintext, - 'ssml': SpeechType.ssml, -} +SPEECH_MAPPINGS = {"plain": SpeechType.plaintext, "ssml": SpeechType.ssml} class CardType(enum.Enum): """The Alexa card types.""" - simple = 'Simple' - link_account = 'LinkAccount' + simple = "Simple" + link_account = "LinkAccount" @callback @@ -51,44 +48,50 @@ class AlexaIntentsView(http.HomeAssistantView): """Handle Alexa requests.""" url = INTENTS_API_ENDPOINT - name = 'api:alexa' + name = "api:alexa" async def post(self, request): """Handle Alexa.""" - hass = request.app['hass'] + hass = request.app["hass"] message = await request.json() _LOGGER.debug("Received Alexa request: %s", message) try: response = await async_handle_message(hass, message) - return b'' if response is None else self.json(response) + return b"" if response is None else self.json(response) except UnknownRequest as err: _LOGGER.warning(str(err)) - return self.json(intent_error_response( - hass, message, str(err))) + return self.json(intent_error_response(hass, message, str(err))) except intent.UnknownIntent as err: _LOGGER.warning(str(err)) - return self.json(intent_error_response( - hass, message, - "This intent is not yet configured within Home Assistant.")) + return self.json( + intent_error_response( + hass, + message, + "This intent is not yet configured within Home Assistant.", + ) + ) except intent.InvalidSlotInfo as err: _LOGGER.error("Received invalid slot data from Alexa: %s", err) - return self.json(intent_error_response( - hass, message, - "Invalid slot information received for this intent.")) + return self.json( + intent_error_response( + hass, message, "Invalid slot information received for this intent." + ) + ) except intent.IntentError as err: _LOGGER.exception(str(err)) - return self.json(intent_error_response( - hass, message, "Error handling intent.")) + return self.json( + intent_error_response(hass, message, "Error handling intent.") + ) def intent_error_response(hass, message, error): """Return an Alexa response that will speak the error message.""" - alexa_intent_info = message.get('request').get('intent') + alexa_intent_info = message.get("request").get("intent") alexa_response = AlexaResponse(hass, alexa_intent_info) alexa_response.add_speech(SpeechType.plaintext, error) return alexa_response.as_dict() @@ -104,25 +107,25 @@ async def async_handle_message(hass, message): - intent.IntentError """ - req = message.get('request') - req_type = req['type'] + req = message.get("request") + req_type = req["type"] handler = HANDLERS.get(req_type) if not handler: - raise UnknownRequest('Received unknown request {}'.format(req_type)) + raise UnknownRequest("Received unknown request {}".format(req_type)) return await handler(hass, message) -@HANDLERS.register('SessionEndedRequest') +@HANDLERS.register("SessionEndedRequest") async def async_handle_session_end(hass, message): """Handle a session end request.""" return None -@HANDLERS.register('IntentRequest') -@HANDLERS.register('LaunchRequest') +@HANDLERS.register("IntentRequest") +@HANDLERS.register("LaunchRequest") async def async_handle_intent(hass, message): """Handle an intent request. @@ -132,33 +135,37 @@ async def async_handle_intent(hass, message): - intent.IntentError """ - req = message.get('request') - alexa_intent_info = req.get('intent') + req = message.get("request") + alexa_intent_info = req.get("intent") alexa_response = AlexaResponse(hass, alexa_intent_info) - if req['type'] == 'LaunchRequest': - intent_name = message.get('session', {}) \ - .get('application', {}) \ - .get('applicationId') + if req["type"] == "LaunchRequest": + intent_name = ( + message.get("session", {}).get("application", {}).get("applicationId") + ) else: - intent_name = alexa_intent_info['name'] + intent_name = alexa_intent_info["name"] intent_response = await intent.async_handle( - hass, DOMAIN, intent_name, - {key: {'value': value} for key, value - in alexa_response.variables.items()}) + hass, + DOMAIN, + intent_name, + {key: {"value": value} for key, value in alexa_response.variables.items()}, + ) for intent_speech, alexa_speech in SPEECH_MAPPINGS.items(): if intent_speech in intent_response.speech: alexa_response.add_speech( - alexa_speech, - intent_response.speech[intent_speech]['speech']) + alexa_speech, intent_response.speech[intent_speech]["speech"] + ) break - if 'simple' in intent_response.card: + if "simple" in intent_response.card: alexa_response.add_card( - CardType.simple, intent_response.card['simple']['title'], - intent_response.card['simple']['content']) + CardType.simple, + intent_response.card["simple"]["title"], + intent_response.card["simple"]["content"], + ) return alexa_response.as_dict() @@ -168,23 +175,23 @@ def resolve_slot_synonyms(key, request): # Default to the spoken slot value if more than one or none are found. For # reference to the request object structure, see the Alexa docs: # https://tinyurl.com/ybvm7jhs - resolved_value = request['value'] + resolved_value = request["value"] - if ('resolutions' in request and - 'resolutionsPerAuthority' in request['resolutions'] and - len(request['resolutions']['resolutionsPerAuthority']) >= 1): + if ( + "resolutions" in request + and "resolutionsPerAuthority" in request["resolutions"] + and len(request["resolutions"]["resolutionsPerAuthority"]) >= 1 + ): # Extract all of the possible values from each authority with a # successful match possible_values = [] - for entry in request['resolutions']['resolutionsPerAuthority']: - if entry['status']['code'] != SYN_RESOLUTION_MATCH: + for entry in request["resolutions"]["resolutionsPerAuthority"]: + if entry["status"]["code"] != SYN_RESOLUTION_MATCH: continue - possible_values.extend([item['value']['name'] - for item - in entry['values']]) + possible_values.extend([item["value"]["name"] for item in entry["values"]]) # If there is only one match use the resolved value, otherwise the # resolution cannot be determined, so use the spoken slot value @@ -192,9 +199,9 @@ def resolve_slot_synonyms(key, request): resolved_value = possible_values[0] else: _LOGGER.debug( - 'Found multiple synonym resolutions for slot value: {%s: %s}', + "Found multiple synonym resolutions for slot value: {%s: %s}", key, - request['value'] + request["value"], ) return resolved_value @@ -215,12 +222,12 @@ class AlexaResponse: # Intent is None if request was a LaunchRequest or SessionEndedRequest if intent_info is not None: - for key, value in intent_info.get('slots', {}).items(): + for key, value in intent_info.get("slots", {}).items(): # Only include slots with values - if 'value' not in value: + if "value" not in value: continue - _key = key.replace('.', '_') + _key = key.replace(".", "_") self.variables[_key] = resolve_slot_synonyms(key, value) @@ -228,9 +235,7 @@ class AlexaResponse: """Add a card to the response.""" assert self.card is None - card = { - "type": card_type.value - } + card = {"type": card_type.value} if card_type == CardType.link_account: self.card = card @@ -244,43 +249,36 @@ class AlexaResponse: """Add speech to the response.""" assert self.speech is None - key = 'ssml' if speech_type == SpeechType.ssml else 'text' + key = "ssml" if speech_type == SpeechType.ssml else "text" - self.speech = { - 'type': speech_type.value, - key: text - } + self.speech = {"type": speech_type.value, key: text} def add_reprompt(self, speech_type, text): """Add reprompt if user does not answer.""" assert self.reprompt is None - key = 'ssml' if speech_type == SpeechType.ssml else 'text' + key = "ssml" if speech_type == SpeechType.ssml else "text" self.reprompt = { - 'type': speech_type.value, - key: text.async_render(self.variables) + "type": speech_type.value, + key: text.async_render(self.variables), } def as_dict(self): """Return response in an Alexa valid dict.""" - response = { - 'shouldEndSession': self.should_end_session - } + response = {"shouldEndSession": self.should_end_session} if self.card is not None: - response['card'] = self.card + response["card"] = self.card if self.speech is not None: - response['outputSpeech'] = self.speech + response["outputSpeech"] = self.speech if self.reprompt is not None: - response['reprompt'] = { - 'outputSpeech': self.reprompt - } + response["reprompt"] = {"outputSpeech": self.reprompt} return { - 'version': '1.0', - 'sessionAttributes': self.session_attributes, - 'response': response, + "version": "1.0", + "sessionAttributes": self.session_attributes, + "response": response, } diff --git a/homeassistant/components/alexa/messages.py b/homeassistant/components/alexa/messages.py index c1b0ac9c025..2b7d15ac841 100644 --- a/homeassistant/components/alexa/messages.py +++ b/homeassistant/components/alexa/messages.py @@ -23,8 +23,8 @@ class AlexaDirective: def __init__(self, request): """Initialize a directive.""" self._directive = request[API_DIRECTIVE] - self.namespace = self._directive[API_HEADER]['namespace'] - self.name = self._directive[API_HEADER]['name'] + self.namespace = self._directive[API_HEADER]["namespace"] + self.name = self._directive[API_HEADER]["name"] self.payload = self._directive[API_PAYLOAD] self.has_endpoint = API_ENDPOINT in self._directive @@ -44,27 +44,23 @@ class AlexaDirective: Will raise AlexaInvalidEndpointError if the endpoint in the request is malformed or nonexistant. """ - _endpoint_id = self._directive[API_ENDPOINT]['endpointId'] - self.entity_id = _endpoint_id.replace('#', '.') + _endpoint_id = self._directive[API_ENDPOINT]["endpointId"] + self.entity_id = _endpoint_id.replace("#", ".") self.entity = hass.states.get(self.entity_id) if not self.entity or not config.should_expose(self.entity_id): raise AlexaInvalidEndpointError(_endpoint_id) - self.endpoint = ENTITY_ADAPTERS[self.entity.domain]( - hass, config, self.entity) + self.endpoint = ENTITY_ADAPTERS[self.entity.domain](hass, config, self.entity) - def response(self, - name='Response', - namespace='Alexa', - payload=None): + def response(self, name="Response", namespace="Alexa", payload=None): """Create an API formatted response. Async friendly. """ response = AlexaResponse(name, namespace, payload) - token = self._directive[API_HEADER].get('correlationToken') + token = self._directive[API_HEADER].get("correlationToken") if token: response.set_correlation_token(token) @@ -74,31 +70,30 @@ class AlexaDirective: return response def error( - self, - namespace='Alexa', - error_type='INTERNAL_ERROR', - error_message="", - payload=None + self, + namespace="Alexa", + error_type="INTERNAL_ERROR", + error_message="", + payload=None, ): """Create a API formatted error response. Async friendly. """ payload = payload or {} - payload['type'] = error_type - payload['message'] = error_message + payload["type"] = error_type + payload["message"] = error_message - _LOGGER.info("Request %s/%s error %s: %s", - self._directive[API_HEADER]['namespace'], - self._directive[API_HEADER]['name'], - error_type, error_message) - - return self.response( - name='ErrorResponse', - namespace=namespace, - payload=payload + _LOGGER.info( + "Request %s/%s error %s: %s", + self._directive[API_HEADER]["namespace"], + self._directive[API_HEADER]["name"], + error_type, + error_message, ) + return self.response(name="ErrorResponse", namespace=namespace, payload=payload) + class AlexaResponse: """Class to hold a response.""" @@ -109,10 +104,10 @@ class AlexaResponse: self._response = { API_EVENT: { API_HEADER: { - 'namespace': namespace, - 'name': name, - 'messageId': str(uuid4()), - 'payloadVersion': '3', + "namespace": namespace, + "name": name, + "messageId": str(uuid4()), + "payloadVersion": "3", }, API_PAYLOAD: payload, } @@ -121,12 +116,12 @@ class AlexaResponse: @property def name(self): """Return the name of this response.""" - return self._response[API_EVENT][API_HEADER]['name'] + return self._response[API_EVENT][API_HEADER]["name"] @property def namespace(self): """Return the namespace of this response.""" - return self._response[API_EVENT][API_HEADER]['namespace'] + return self._response[API_EVENT][API_HEADER]["namespace"] def set_correlation_token(self, token): """Set the correlationToken. @@ -134,7 +129,7 @@ class AlexaResponse: This should normally mirror the value from a request, and is set by AlexaDirective.response() usually. """ - self._response[API_EVENT][API_HEADER]['correlationToken'] = token + self._response[API_EVENT][API_HEADER]["correlationToken"] = token def set_endpoint_full(self, bearer_token, endpoint_id, cookie=None): """Set the endpoint dictionary. @@ -142,17 +137,14 @@ class AlexaResponse: This is used to send proactive messages to Alexa. """ self._response[API_EVENT][API_ENDPOINT] = { - API_SCOPE: { - 'type': 'BearerToken', - 'token': bearer_token - } + API_SCOPE: {"type": "BearerToken", "token": bearer_token} } if endpoint_id is not None: - self._response[API_EVENT][API_ENDPOINT]['endpointId'] = endpoint_id + self._response[API_EVENT][API_ENDPOINT]["endpointId"] = endpoint_id if cookie is not None: - self._response[API_EVENT][API_ENDPOINT]['cookie'] = cookie + self._response[API_EVENT][API_ENDPOINT]["cookie"] = cookie def set_endpoint(self, endpoint): """Set the endpoint. @@ -164,7 +156,7 @@ class AlexaResponse: def _properties(self): context = self._response.setdefault(API_CONTEXT, {}) - return context.setdefault('properties', []) + return context.setdefault("properties", []) def add_context_property(self, prop): """Add a property to the response context. @@ -189,10 +181,10 @@ class AlexaResponse: Handlers should be using .add_context_property(). """ properties = self._properties() - already_set = {(p['namespace'], p['name']) for p in properties} + already_set = {(p["namespace"], p["name"]) for p in properties} for prop in endpoint.serialize_properties(): - if (prop['namespace'], prop['name']) not in already_set: + if (prop["namespace"], prop["name"]) not in already_set: self.add_context_property(prop) def serialize(self): diff --git a/homeassistant/components/alexa/smart_home.py b/homeassistant/components/alexa/smart_home.py index 688828b20bd..2c34542e25c 100644 --- a/homeassistant/components/alexa/smart_home.py +++ b/homeassistant/components/alexa/smart_home.py @@ -4,32 +4,23 @@ import logging import homeassistant.core as ha from .const import API_DIRECTIVE, API_HEADER -from .errors import ( - AlexaError, - AlexaBridgeUnreachableError, -) +from .errors import AlexaError, AlexaBridgeUnreachableError from .handlers import HANDLERS from .messages import AlexaDirective _LOGGER = logging.getLogger(__name__) -EVENT_ALEXA_SMART_HOME = 'alexa_smart_home' +EVENT_ALEXA_SMART_HOME = "alexa_smart_home" -async def async_handle_message( - hass, - config, - request, - context=None, - enabled=True, -): +async def async_handle_message(hass, config, request, context=None, enabled=True): """Handle incoming API messages. If enabled is False, the response to all messagess will be a BRIDGE_UNREACHABLE error. This can be used if the API has been disabled in configuration. """ - assert request[API_DIRECTIVE][API_HEADER]['payloadVersion'] == '3' + assert request[API_DIRECTIVE][API_HEADER]["payloadVersion"] == "3" if context is None: context = ha.Context() @@ -39,7 +30,8 @@ async def async_handle_message( try: if not enabled: raise AlexaBridgeUnreachableError( - 'Alexa API not enabled in Home Assistant configuration') + "Alexa API not enabled in Home Assistant configuration" + ) if directive.has_endpoint: directive.load_entity(hass, config) @@ -51,30 +43,26 @@ async def async_handle_message( response.merge_context_properties(directive.endpoint) else: _LOGGER.warning( - "Unsupported API request %s/%s", - directive.namespace, - directive.name, + "Unsupported API request %s/%s", directive.namespace, directive.name ) response = directive.error() except AlexaError as err: response = directive.error( - error_type=err.error_type, - error_message=err.error_message) + error_type=err.error_type, error_message=err.error_message + ) - request_info = { - 'namespace': directive.namespace, - 'name': directive.name, - } + request_info = {"namespace": directive.namespace, "name": directive.name} if directive.has_endpoint: - request_info['entity_id'] = directive.entity_id + request_info["entity_id"] = directive.entity_id - hass.bus.async_fire(EVENT_ALEXA_SMART_HOME, { - 'request': request_info, - 'response': { - 'namespace': response.namespace, - 'name': response.name, - } - }, context=context) + hass.bus.async_fire( + EVENT_ALEXA_SMART_HOME, + { + "request": request_info, + "response": {"namespace": response.namespace, "name": response.name}, + }, + context=context, + ) return response.serialize() diff --git a/homeassistant/components/alexa/smart_home_http.py b/homeassistant/components/alexa/smart_home_http.py index 4636ee10bb7..7fdd4e3000a 100644 --- a/homeassistant/components/alexa/smart_home_http.py +++ b/homeassistant/components/alexa/smart_home_http.py @@ -11,13 +11,13 @@ from .const import ( CONF_CLIENT_SECRET, CONF_ENDPOINT, CONF_ENTITY_CONFIG, - CONF_FILTER + CONF_FILTER, ) from .state_report import async_enable_proactive_mode from .smart_home import async_handle_message _LOGGER = logging.getLogger(__name__) -SMART_HOME_HTTP_ENDPOINT = '/api/alexa/smart_home' +SMART_HOME_HTTP_ENDPOINT = "/api/alexa/smart_home" class AlexaConfig(AbstractConfig): @@ -29,8 +29,7 @@ class AlexaConfig(AbstractConfig): self._config = config if config.get(CONF_CLIENT_ID) and config.get(CONF_CLIENT_SECRET): - self._auth = Auth(hass, config[CONF_CLIENT_ID], - config[CONF_CLIENT_SECRET]) + self._auth = Auth(hass, config[CONF_CLIENT_ID], config[CONF_CLIENT_SECRET]) else: self._auth = None @@ -87,7 +86,7 @@ class SmartHomeView(HomeAssistantView): """Expose Smart Home v3 payload interface via HTTP POST.""" url = SMART_HOME_HTTP_ENDPOINT - name = 'api:alexa:smart_home' + name = "api:alexa:smart_home" def __init__(self, smart_home_config): """Initialize.""" @@ -100,15 +99,14 @@ class SmartHomeView(HomeAssistantView): Lambda, which will need to forward the requests to here and pass back the response. """ - hass = request.app['hass'] - user = request['hass_user'] + hass = request.app["hass"] + user = request["hass_user"] message = await request.json() _LOGGER.debug("Received Alexa Smart Home request: %s", message) response = await async_handle_message( - hass, self.smart_home_config, message, - context=core.Context(user_id=user.id) + hass, self.smart_home_config, message, context=core.Context(user_id=user.id) ) _LOGGER.debug("Sending Alexa Smart Home response: %s", response) - return b'' if response is None else self.json(response) + return b"" if response is None else self.json(response) diff --git a/homeassistant/components/alexa/state_report.py b/homeassistant/components/alexa/state_report.py index 022b38be59d..7e842889977 100644 --- a/homeassistant/components/alexa/state_report.py +++ b/homeassistant/components/alexa/state_report.py @@ -24,8 +24,7 @@ async def async_enable_proactive_mode(hass, smart_home_config): # Validate we can get access token. await smart_home_config.async_get_access_token() - async def async_entity_state_listener(changed_entity, old_state, - new_state): + async def async_entity_state_listener(changed_entity, old_state, new_state): if not new_state: return @@ -33,18 +32,18 @@ async def async_enable_proactive_mode(hass, smart_home_config): return if not smart_home_config.should_expose(changed_entity): - _LOGGER.debug("Not exposing %s because filtered by config", - changed_entity) + _LOGGER.debug("Not exposing %s because filtered by config", changed_entity) return - alexa_changed_entity = \ - ENTITY_ADAPTERS[new_state.domain](hass, smart_home_config, - new_state) + alexa_changed_entity = ENTITY_ADAPTERS[new_state.domain]( + hass, smart_home_config, new_state + ) for interface in alexa_changed_entity.interfaces(): if interface.properties_proactively_reported(): - await async_send_changereport_message(hass, smart_home_config, - alexa_changed_entity) + await async_send_changereport_message( + hass, smart_home_config, alexa_changed_entity + ) return return hass.helpers.event.async_track_state_change( @@ -59,9 +58,7 @@ async def async_send_changereport_message(hass, config, alexa_entity): """ token = await config.async_get_access_token() - headers = { - "Authorization": "Bearer {}".format(token) - } + headers = {"Authorization": "Bearer {}".format(token)} endpoint = alexa_entity.alexa_id() @@ -71,14 +68,10 @@ async def async_send_changereport_message(hass, config, alexa_entity): properties = list(alexa_entity.serialize_properties()) payload = { - API_CHANGE: { - 'cause': {'type': Cause.APP_INTERACTION}, - 'properties': properties - } + API_CHANGE: {"cause": {"type": Cause.APP_INTERACTION}, "properties": properties} } - message = AlexaResponse(name='ChangeReport', namespace='Alexa', - payload=payload) + message = AlexaResponse(name="ChangeReport", namespace="Alexa", payload=payload) message.set_endpoint_full(token, endpoint) message_serialized = message.serialize() @@ -86,10 +79,12 @@ async def async_send_changereport_message(hass, config, alexa_entity): try: with async_timeout.timeout(DEFAULT_TIMEOUT): - response = await session.post(config.endpoint, - headers=headers, - json=message_serialized, - allow_redirects=True) + response = await session.post( + config.endpoint, + headers=headers, + json=message_serialized, + allow_redirects=True, + ) except (asyncio.TimeoutError, aiohttp.ClientError): _LOGGER.error("Timeout sending report to Alexa.") @@ -102,9 +97,11 @@ async def async_send_changereport_message(hass, config, alexa_entity): if response.status != 202: response_json = json.loads(response_text) - _LOGGER.error("Error when sending ChangeReport to Alexa: %s: %s", - response_json["payload"]["code"], - response_json["payload"]["description"]) + _LOGGER.error( + "Error when sending ChangeReport to Alexa: %s: %s", + response_json["payload"]["code"], + response_json["payload"]["description"], + ) async def async_send_add_or_update_message(hass, config, entity_ids): @@ -114,35 +111,27 @@ async def async_send_add_or_update_message(hass, config, entity_ids): """ token = await config.async_get_access_token() - headers = { - "Authorization": "Bearer {}".format(token) - } + headers = {"Authorization": "Bearer {}".format(token)} endpoints = [] for entity_id in entity_ids: - domain = entity_id.split('.', 1)[0] - alexa_entity = ENTITY_ADAPTERS[domain]( - hass, config, hass.states.get(entity_id) - ) + domain = entity_id.split(".", 1)[0] + alexa_entity = ENTITY_ADAPTERS[domain](hass, config, hass.states.get(entity_id)) endpoints.append(alexa_entity.serialize_discovery()) - payload = { - 'endpoints': endpoints, - 'scope': { - 'type': 'BearerToken', - 'token': token, - } - } + payload = {"endpoints": endpoints, "scope": {"type": "BearerToken", "token": token}} message = AlexaResponse( - name='AddOrUpdateReport', namespace='Alexa.Discovery', payload=payload) + name="AddOrUpdateReport", namespace="Alexa.Discovery", payload=payload + ) message_serialized = message.serialize() session = hass.helpers.aiohttp_client.async_get_clientsession() - return await session.post(config.endpoint, headers=headers, - json=message_serialized, allow_redirects=True) + return await session.post( + config.endpoint, headers=headers, json=message_serialized, allow_redirects=True + ) async def async_send_delete_message(hass, config, entity_ids): @@ -152,34 +141,24 @@ async def async_send_delete_message(hass, config, entity_ids): """ token = await config.async_get_access_token() - headers = { - "Authorization": "Bearer {}".format(token) - } + headers = {"Authorization": "Bearer {}".format(token)} endpoints = [] for entity_id in entity_ids: - domain = entity_id.split('.', 1)[0] - alexa_entity = ENTITY_ADAPTERS[domain]( - hass, config, hass.states.get(entity_id) - ) - endpoints.append({ - 'endpointId': alexa_entity.alexa_id() - }) + domain = entity_id.split(".", 1)[0] + alexa_entity = ENTITY_ADAPTERS[domain](hass, config, hass.states.get(entity_id)) + endpoints.append({"endpointId": alexa_entity.alexa_id()}) - payload = { - 'endpoints': endpoints, - 'scope': { - 'type': 'BearerToken', - 'token': token, - } - } + payload = {"endpoints": endpoints, "scope": {"type": "BearerToken", "token": token}} - message = AlexaResponse(name='DeleteReport', namespace='Alexa.Discovery', - payload=payload) + message = AlexaResponse( + name="DeleteReport", namespace="Alexa.Discovery", payload=payload + ) message_serialized = message.serialize() session = hass.helpers.aiohttp_client.async_get_clientsession() - return await session.post(config.endpoint, headers=headers, - json=message_serialized, allow_redirects=True) + return await session.post( + config.endpoint, headers=headers, json=message_serialized, allow_redirects=True + ) diff --git a/homeassistant/components/alpha_vantage/sensor.py b/homeassistant/components/alpha_vantage/sensor.py index 9ea6797a56e..6d790e0719b 100644 --- a/homeassistant/components/alpha_vantage/sensor.py +++ b/homeassistant/components/alpha_vantage/sensor.py @@ -5,56 +5,59 @@ import logging import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ( - ATTR_ATTRIBUTION, CONF_API_KEY, CONF_CURRENCY, CONF_NAME) +from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY, CONF_CURRENCY, CONF_NAME import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -ATTR_CLOSE = 'close' -ATTR_HIGH = 'high' -ATTR_LOW = 'low' +ATTR_CLOSE = "close" +ATTR_HIGH = "high" +ATTR_LOW = "low" ATTRIBUTION = "Stock market information provided by Alpha Vantage" -CONF_FOREIGN_EXCHANGE = 'foreign_exchange' -CONF_FROM = 'from' -CONF_SYMBOL = 'symbol' -CONF_SYMBOLS = 'symbols' -CONF_TO = 'to' +CONF_FOREIGN_EXCHANGE = "foreign_exchange" +CONF_FROM = "from" +CONF_SYMBOL = "symbol" +CONF_SYMBOLS = "symbols" +CONF_TO = "to" ICONS = { - 'BTC': 'mdi:currency-btc', - 'EUR': 'mdi:currency-eur', - 'GBP': 'mdi:currency-gbp', - 'INR': 'mdi:currency-inr', - 'RUB': 'mdi:currency-rub', - 'TRY': 'mdi:currency-try', - 'USD': 'mdi:currency-usd', + "BTC": "mdi:currency-btc", + "EUR": "mdi:currency-eur", + "GBP": "mdi:currency-gbp", + "INR": "mdi:currency-inr", + "RUB": "mdi:currency-rub", + "TRY": "mdi:currency-try", + "USD": "mdi:currency-usd", } SCAN_INTERVAL = timedelta(minutes=5) -SYMBOL_SCHEMA = vol.Schema({ - vol.Required(CONF_SYMBOL): cv.string, - vol.Optional(CONF_CURRENCY): cv.string, - vol.Optional(CONF_NAME): cv.string, -}) +SYMBOL_SCHEMA = vol.Schema( + { + vol.Required(CONF_SYMBOL): cv.string, + vol.Optional(CONF_CURRENCY): cv.string, + vol.Optional(CONF_NAME): cv.string, + } +) -CURRENCY_SCHEMA = vol.Schema({ - vol.Required(CONF_FROM): cv.string, - vol.Required(CONF_TO): cv.string, - vol.Optional(CONF_NAME): cv.string, -}) +CURRENCY_SCHEMA = vol.Schema( + { + vol.Required(CONF_FROM): cv.string, + vol.Required(CONF_TO): cv.string, + vol.Optional(CONF_NAME): cv.string, + } +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_API_KEY): cv.string, - vol.Optional(CONF_FOREIGN_EXCHANGE): - vol.All(cv.ensure_list, [CURRENCY_SCHEMA]), - vol.Optional(CONF_SYMBOLS): - vol.All(cv.ensure_list, [SYMBOL_SCHEMA]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_API_KEY): cv.string, + vol.Optional(CONF_FOREIGN_EXCHANGE): vol.All(cv.ensure_list, [CURRENCY_SCHEMA]), + vol.Optional(CONF_SYMBOLS): vol.All(cv.ensure_list, [SYMBOL_SCHEMA]), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -67,9 +70,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): conversions = config.get(CONF_FOREIGN_EXCHANGE, []) if not symbols and not conversions: - msg = 'Warning: No symbols or currencies configured.' - hass.components.persistent_notification.create( - msg, 'Sensor alpha_vantage') + msg = "Warning: No symbols or currencies configured." + hass.components.persistent_notification.create(msg, "Sensor alpha_vantage") _LOGGER.warning(msg) return @@ -78,12 +80,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None): dev = [] for symbol in symbols: try: - _LOGGER.debug("Configuring timeseries for symbols: %s", - symbol[CONF_SYMBOL]) + _LOGGER.debug("Configuring timeseries for symbols: %s", symbol[CONF_SYMBOL]) timeseries.get_intraday(symbol[CONF_SYMBOL]) except ValueError: - _LOGGER.error( - "API Key is not valid or symbol '%s' not known", symbol) + _LOGGER.error("API Key is not valid or symbol '%s' not known", symbol) dev.append(AlphaVantageSensor(timeseries, symbol)) forex = ForeignExchange(key=api_key) @@ -92,12 +92,13 @@ def setup_platform(hass, config, add_entities, discovery_info=None): to_cur = conversion.get(CONF_TO) try: _LOGGER.debug("Configuring forex %s - %s", from_cur, to_cur) - forex.get_currency_exchange_rate( - from_currency=from_cur, to_currency=to_cur) + forex.get_currency_exchange_rate(from_currency=from_cur, to_currency=to_cur) except ValueError as error: _LOGGER.error( "API Key is not valid or currencies '%s'/'%s' not known", - from_cur, to_cur) + from_cur, + to_cur, + ) _LOGGER.debug(str(error)) dev.append(AlphaVantageForeignExchange(forex, conversion)) @@ -115,7 +116,7 @@ class AlphaVantageSensor(Entity): self._timeseries = timeseries self.values = None self._unit_of_measurement = symbol.get(CONF_CURRENCY, self._symbol) - self._icon = ICONS.get(symbol.get(CONF_CURRENCY, 'USD')) + self._icon = ICONS.get(symbol.get(CONF_CURRENCY, "USD")) @property def name(self): @@ -130,7 +131,7 @@ class AlphaVantageSensor(Entity): @property def state(self): """Return the state of the sensor.""" - return self.values['1. open'] + return self.values["1. open"] @property def device_state_attributes(self): @@ -138,9 +139,9 @@ class AlphaVantageSensor(Entity): if self.values is not None: return { ATTR_ATTRIBUTION: ATTRIBUTION, - ATTR_CLOSE: self.values['4. close'], - ATTR_HIGH: self.values['2. high'], - ATTR_LOW: self.values['3. low'], + ATTR_CLOSE: self.values["4. close"], + ATTR_HIGH: self.values["2. high"], + ATTR_LOW: self.values["3. low"], } @property @@ -167,9 +168,9 @@ class AlphaVantageForeignExchange(Entity): if CONF_NAME in config: self._name = config.get(CONF_NAME) else: - self._name = '{}/{}'.format(self._to_currency, self._from_currency) + self._name = "{}/{}".format(self._to_currency, self._from_currency) self._unit_of_measurement = self._to_currency - self._icon = ICONS.get(self._from_currency, 'USD') + self._icon = ICONS.get(self._from_currency, "USD") self.values = None @property @@ -185,7 +186,7 @@ class AlphaVantageForeignExchange(Entity): @property def state(self): """Return the state of the sensor.""" - return round(float(self.values['5. Exchange Rate']), 4) + return round(float(self.values["5. Exchange Rate"]), 4) @property def icon(self): @@ -204,9 +205,16 @@ class AlphaVantageForeignExchange(Entity): def update(self): """Get the latest data and updates the states.""" - _LOGGER.debug("Requesting new data for forex %s - %s", - self._from_currency, self._to_currency) + _LOGGER.debug( + "Requesting new data for forex %s - %s", + self._from_currency, + self._to_currency, + ) self.values, _ = self._foreign_exchange.get_currency_exchange_rate( - from_currency=self._from_currency, to_currency=self._to_currency) - _LOGGER.debug("Received new data for forex %s - %s", - self._from_currency, self._to_currency) + from_currency=self._from_currency, to_currency=self._to_currency + ) + _LOGGER.debug( + "Received new data for forex %s - %s", + self._from_currency, + self._to_currency, + ) diff --git a/homeassistant/components/amazon_polly/tts.py b/homeassistant/components/amazon_polly/tts.py index 4511a587a60..f27b0428072 100644 --- a/homeassistant/components/amazon_polly/tts.py +++ b/homeassistant/components/amazon_polly/tts.py @@ -8,108 +8,145 @@ import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -CONF_REGION = 'region_name' -CONF_ACCESS_KEY_ID = 'aws_access_key_id' -CONF_SECRET_ACCESS_KEY = 'aws_secret_access_key' -CONF_PROFILE_NAME = 'profile_name' -ATTR_CREDENTIALS = 'credentials' +CONF_REGION = "region_name" +CONF_ACCESS_KEY_ID = "aws_access_key_id" +CONF_SECRET_ACCESS_KEY = "aws_secret_access_key" +CONF_PROFILE_NAME = "profile_name" +ATTR_CREDENTIALS = "credentials" -DEFAULT_REGION = 'us-east-1' -SUPPORTED_REGIONS = ['us-east-1', 'us-east-2', 'us-west-1', 'us-west-2', - 'ca-central-1', 'eu-west-1', 'eu-central-1', 'eu-west-2', - 'eu-west-3', 'ap-southeast-1', 'ap-southeast-2', - 'ap-northeast-2', 'ap-northeast-1', 'ap-south-1', - 'sa-east-1'] - -CONF_VOICE = 'voice' -CONF_OUTPUT_FORMAT = 'output_format' -CONF_SAMPLE_RATE = 'sample_rate' -CONF_TEXT_TYPE = 'text_type' - -SUPPORTED_VOICES = [ - 'Zhiyu', # Chinese - 'Mads', 'Naja', # Danish - 'Ruben', 'Lotte', # Dutch - 'Russell', 'Nicole', # English Austrailian - 'Brian', 'Amy', 'Emma', # English - 'Aditi', 'Raveena', # English, Indian - 'Joey', 'Justin', 'Matthew', 'Ivy', 'Joanna', 'Kendra', 'Kimberly', - 'Salli', # English - 'Geraint', # English Welsh - 'Mathieu', 'Celine', 'Lea', # French - 'Chantal', # French Canadian - 'Hans', 'Marlene', 'Vicki', # German - 'Aditi', # Hindi - 'Karl', 'Dora', # Icelandic - 'Giorgio', 'Carla', 'Bianca', # Italian - 'Takumi', 'Mizuki', # Japanese - 'Seoyeon', # Korean - 'Liv', # Norwegian - 'Jacek', 'Jan', 'Ewa', 'Maja', # Polish - 'Ricardo', 'Vitoria', # Portuguese, Brazilian - 'Cristiano', 'Ines', # Portuguese, European - 'Carmen', # Romanian - 'Maxim', 'Tatyana', # Russian - 'Enrique', 'Conchita', 'Lucia', # Spanish European - 'Mia', # Spanish Mexican - 'Miguel', 'Penelope', # Spanish US - 'Astrid', # Swedish - 'Filiz', # Turkish - 'Gwyneth', # Welsh +DEFAULT_REGION = "us-east-1" +SUPPORTED_REGIONS = [ + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + "ca-central-1", + "eu-west-1", + "eu-central-1", + "eu-west-2", + "eu-west-3", + "ap-southeast-1", + "ap-southeast-2", + "ap-northeast-2", + "ap-northeast-1", + "ap-south-1", + "sa-east-1", ] -SUPPORTED_OUTPUT_FORMATS = ['mp3', 'ogg_vorbis', 'pcm'] +CONF_VOICE = "voice" +CONF_OUTPUT_FORMAT = "output_format" +CONF_SAMPLE_RATE = "sample_rate" +CONF_TEXT_TYPE = "text_type" -SUPPORTED_SAMPLE_RATES = ['8000', '16000', '22050'] +SUPPORTED_VOICES = [ + "Zhiyu", # Chinese + "Mads", + "Naja", # Danish + "Ruben", + "Lotte", # Dutch + "Russell", + "Nicole", # English Austrailian + "Brian", + "Amy", + "Emma", # English + "Aditi", + "Raveena", # English, Indian + "Joey", + "Justin", + "Matthew", + "Ivy", + "Joanna", + "Kendra", + "Kimberly", + "Salli", # English + "Geraint", # English Welsh + "Mathieu", + "Celine", + "Lea", # French + "Chantal", # French Canadian + "Hans", + "Marlene", + "Vicki", # German + "Aditi", # Hindi + "Karl", + "Dora", # Icelandic + "Giorgio", + "Carla", + "Bianca", # Italian + "Takumi", + "Mizuki", # Japanese + "Seoyeon", # Korean + "Liv", # Norwegian + "Jacek", + "Jan", + "Ewa", + "Maja", # Polish + "Ricardo", + "Vitoria", # Portuguese, Brazilian + "Cristiano", + "Ines", # Portuguese, European + "Carmen", # Romanian + "Maxim", + "Tatyana", # Russian + "Enrique", + "Conchita", + "Lucia", # Spanish European + "Mia", # Spanish Mexican + "Miguel", + "Penelope", # Spanish US + "Astrid", # Swedish + "Filiz", # Turkish + "Gwyneth", # Welsh +] + +SUPPORTED_OUTPUT_FORMATS = ["mp3", "ogg_vorbis", "pcm"] + +SUPPORTED_SAMPLE_RATES = ["8000", "16000", "22050"] SUPPORTED_SAMPLE_RATES_MAP = { - 'mp3': ['8000', '16000', '22050'], - 'ogg_vorbis': ['8000', '16000', '22050'], - 'pcm': ['8000', '16000'], + "mp3": ["8000", "16000", "22050"], + "ogg_vorbis": ["8000", "16000", "22050"], + "pcm": ["8000", "16000"], } -SUPPORTED_TEXT_TYPES = ['text', 'ssml'] +SUPPORTED_TEXT_TYPES = ["text", "ssml"] -CONTENT_TYPE_EXTENSIONS = { - 'audio/mpeg': 'mp3', - 'audio/ogg': 'ogg', - 'audio/pcm': 'pcm', -} +CONTENT_TYPE_EXTENSIONS = {"audio/mpeg": "mp3", "audio/ogg": "ogg", "audio/pcm": "pcm"} -DEFAULT_VOICE = 'Joanna' -DEFAULT_OUTPUT_FORMAT = 'mp3' -DEFAULT_TEXT_TYPE = 'text' +DEFAULT_VOICE = "Joanna" +DEFAULT_OUTPUT_FORMAT = "mp3" +DEFAULT_TEXT_TYPE = "text" -DEFAULT_SAMPLE_RATES = { - 'mp3': '22050', - 'ogg_vorbis': '22050', - 'pcm': '16000', -} +DEFAULT_SAMPLE_RATES = {"mp3": "22050", "ogg_vorbis": "22050", "pcm": "16000"} -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_REGION, default=DEFAULT_REGION): - vol.In(SUPPORTED_REGIONS), - vol.Inclusive(CONF_ACCESS_KEY_ID, ATTR_CREDENTIALS): cv.string, - vol.Inclusive(CONF_SECRET_ACCESS_KEY, ATTR_CREDENTIALS): cv.string, - vol.Exclusive(CONF_PROFILE_NAME, ATTR_CREDENTIALS): cv.string, - vol.Optional(CONF_VOICE, default=DEFAULT_VOICE): vol.In(SUPPORTED_VOICES), - vol.Optional(CONF_OUTPUT_FORMAT, default=DEFAULT_OUTPUT_FORMAT): - vol.In(SUPPORTED_OUTPUT_FORMATS), - vol.Optional(CONF_SAMPLE_RATE): - vol.All(cv.string, vol.In(SUPPORTED_SAMPLE_RATES)), - vol.Optional(CONF_TEXT_TYPE, default=DEFAULT_TEXT_TYPE): - vol.In(SUPPORTED_TEXT_TYPES), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_REGION, default=DEFAULT_REGION): vol.In(SUPPORTED_REGIONS), + vol.Inclusive(CONF_ACCESS_KEY_ID, ATTR_CREDENTIALS): cv.string, + vol.Inclusive(CONF_SECRET_ACCESS_KEY, ATTR_CREDENTIALS): cv.string, + vol.Exclusive(CONF_PROFILE_NAME, ATTR_CREDENTIALS): cv.string, + vol.Optional(CONF_VOICE, default=DEFAULT_VOICE): vol.In(SUPPORTED_VOICES), + vol.Optional(CONF_OUTPUT_FORMAT, default=DEFAULT_OUTPUT_FORMAT): vol.In( + SUPPORTED_OUTPUT_FORMATS + ), + vol.Optional(CONF_SAMPLE_RATE): vol.All( + cv.string, vol.In(SUPPORTED_SAMPLE_RATES) + ), + vol.Optional(CONF_TEXT_TYPE, default=DEFAULT_TEXT_TYPE): vol.In( + SUPPORTED_TEXT_TYPES + ), + } +) def get_engine(hass, config): """Set up Amazon Polly speech component.""" output_format = config.get(CONF_OUTPUT_FORMAT) - sample_rate = config.get( - CONF_SAMPLE_RATE, DEFAULT_SAMPLE_RATES[output_format]) + sample_rate = config.get(CONF_SAMPLE_RATE, DEFAULT_SAMPLE_RATES[output_format]) if sample_rate not in SUPPORTED_SAMPLE_RATES_MAP.get(output_format): - _LOGGER.error("%s is not a valid sample rate for %s", - sample_rate, output_format) + _LOGGER.error( + "%s is not a valid sample rate for %s", sample_rate, output_format + ) return None config[CONF_SAMPLE_RATE] = sample_rate @@ -131,7 +168,7 @@ def get_engine(hass, config): del config[CONF_ACCESS_KEY_ID] del config[CONF_SECRET_ACCESS_KEY] - polly_client = boto3.client('polly', **aws_config) + polly_client = boto3.client("polly", **aws_config) supported_languages = [] @@ -139,27 +176,25 @@ def get_engine(hass, config): all_voices_req = polly_client.describe_voices() - for voice in all_voices_req.get('Voices'): - all_voices[voice.get('Id')] = voice - if voice.get('LanguageCode') not in supported_languages: - supported_languages.append(voice.get('LanguageCode')) + for voice in all_voices_req.get("Voices"): + all_voices[voice.get("Id")] = voice + if voice.get("LanguageCode") not in supported_languages: + supported_languages.append(voice.get("LanguageCode")) - return AmazonPollyProvider( - polly_client, config, supported_languages, all_voices) + return AmazonPollyProvider(polly_client, config, supported_languages, all_voices) class AmazonPollyProvider(Provider): """Amazon Polly speech api provider.""" - def __init__(self, polly_client, config, supported_languages, - all_voices): + def __init__(self, polly_client, config, supported_languages, all_voices): """Initialize Amazon Polly provider for TTS.""" self.client = polly_client self.config = config self.supported_langs = supported_languages self.all_voices = all_voices self.default_voice = self.config.get(CONF_VOICE) - self.name = 'Amazon Polly' + self.name = "Amazon Polly" @property def supported_languages(self): @@ -169,7 +204,7 @@ class AmazonPollyProvider(Provider): @property def default_language(self): """Return the default language.""" - return self.all_voices.get(self.default_voice).get('LanguageCode') + return self.all_voices.get(self.default_voice).get("LanguageCode") @property def default_options(self): @@ -185,9 +220,8 @@ class AmazonPollyProvider(Provider): """Request TTS file from Polly.""" voice_id = options.get(CONF_VOICE, self.default_voice) voice_in_dict = self.all_voices.get(voice_id) - if language != voice_in_dict.get('LanguageCode'): - _LOGGER.error("%s does not support the %s language", - voice_id, language) + if language != voice_in_dict.get("LanguageCode"): + _LOGGER.error("%s does not support the %s language", voice_id, language) return None, None resp = self.client.synthesize_speech( @@ -195,8 +229,10 @@ class AmazonPollyProvider(Provider): SampleRate=self.config[CONF_SAMPLE_RATE], Text=message, TextType=self.config[CONF_TEXT_TYPE], - VoiceId=voice_id + VoiceId=voice_id, ) - return (CONTENT_TYPE_EXTENSIONS[resp.get('ContentType')], - resp.get('AudioStream').read()) + return ( + CONTENT_TYPE_EXTENSIONS[resp.get("ContentType")], + resp.get("AudioStream").read(), + ) diff --git a/homeassistant/components/ambiclimate/__init__.py b/homeassistant/components/ambiclimate/__init__.py index 07494ce6cf7..962c8c8a82d 100644 --- a/homeassistant/components/ambiclimate/__init__.py +++ b/homeassistant/components/ambiclimate/__init__.py @@ -12,11 +12,12 @@ _LOGGER = logging.getLogger(__name__) CONFIG_SCHEMA = vol.Schema( { - DOMAIN: - vol.Schema({ + DOMAIN: vol.Schema( + { vol.Required(CONF_CLIENT_ID): cv.string, vol.Required(CONF_CLIENT_SECRET): cv.string, - }) + } + ) }, extra=vol.ALLOW_EXTRA, ) @@ -30,15 +31,16 @@ async def async_setup(hass, config): conf = config[DOMAIN] config_flow.register_flow_implementation( - hass, conf[CONF_CLIENT_ID], - conf[CONF_CLIENT_SECRET]) + hass, conf[CONF_CLIENT_ID], conf[CONF_CLIENT_SECRET] + ) return True async def async_setup_entry(hass, entry): """Set up Ambiclimate from a config entry.""" - hass.async_create_task(hass.config_entries.async_forward_entry_setup( - entry, 'climate')) + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, "climate") + ) return True diff --git a/homeassistant/components/ambiclimate/climate.py b/homeassistant/components/ambiclimate/climate.py index 5bd000f6485..bb3e5ab2b25 100644 --- a/homeassistant/components/ambiclimate/climate.py +++ b/homeassistant/components/ambiclimate/climate.py @@ -7,35 +7,41 @@ import voluptuous as vol from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( - SUPPORT_TARGET_TEMPERATURE, HVAC_MODE_OFF, HVAC_MODE_HEAT) + SUPPORT_TARGET_TEMPERATURE, + HVAC_MODE_OFF, + HVAC_MODE_HEAT, +) from homeassistant.const import ATTR_NAME, ATTR_TEMPERATURE, TEMP_CELSIUS from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession -from .const import (ATTR_VALUE, CONF_CLIENT_ID, CONF_CLIENT_SECRET, - DOMAIN, SERVICE_COMFORT_FEEDBACK, SERVICE_COMFORT_MODE, - SERVICE_TEMPERATURE_MODE, STORAGE_KEY, STORAGE_VERSION) +from .const import ( + ATTR_VALUE, + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, + DOMAIN, + SERVICE_COMFORT_FEEDBACK, + SERVICE_COMFORT_MODE, + SERVICE_TEMPERATURE_MODE, + STORAGE_KEY, + STORAGE_VERSION, +) _LOGGER = logging.getLogger(__name__) SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE -SEND_COMFORT_FEEDBACK_SCHEMA = vol.Schema({ - vol.Required(ATTR_NAME): cv.string, - vol.Required(ATTR_VALUE): cv.string, -}) +SEND_COMFORT_FEEDBACK_SCHEMA = vol.Schema( + {vol.Required(ATTR_NAME): cv.string, vol.Required(ATTR_VALUE): cv.string} +) -SET_COMFORT_MODE_SCHEMA = vol.Schema({ - vol.Required(ATTR_NAME): cv.string, -}) +SET_COMFORT_MODE_SCHEMA = vol.Schema({vol.Required(ATTR_NAME): cv.string}) -SET_TEMPERATURE_MODE_SCHEMA = vol.Schema({ - vol.Required(ATTR_NAME): cv.string, - vol.Required(ATTR_VALUE): cv.string, -}) +SET_TEMPERATURE_MODE_SCHEMA = vol.Schema( + {vol.Required(ATTR_NAME): cv.string, vol.Required(ATTR_VALUE): cv.string} +) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Ambicliamte device.""" @@ -46,10 +52,12 @@ async def async_setup_entry(hass, entry, async_add_entities): store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) token_info = await store.async_load() - oauth = ambiclimate.AmbiclimateOAuth(config[CONF_CLIENT_ID], - config[CONF_CLIENT_SECRET], - config['callback_url'], - websession) + oauth = ambiclimate.AmbiclimateOAuth( + config[CONF_CLIENT_ID], + config[CONF_CLIENT_SECRET], + config["callback_url"], + websession, + ) try: token_info = await oauth.refresh_access_token(token_info) @@ -62,9 +70,9 @@ async def async_setup_entry(hass, entry, async_add_entities): await store.async_save(token_info) - data_connection = ambiclimate.AmbiclimateConnection(oauth, - token_info=token_info, - websession=websession) + data_connection = ambiclimate.AmbiclimateConnection( + oauth, token_info=token_info, websession=websession + ) if not await data_connection.find_devices(): _LOGGER.error("No devices found") @@ -88,10 +96,12 @@ async def async_setup_entry(hass, entry, async_add_entities): if device: await device.set_comfort_feedback(service.data[ATTR_VALUE]) - hass.services.async_register(DOMAIN, - SERVICE_COMFORT_FEEDBACK, - send_comfort_feedback, - schema=SEND_COMFORT_FEEDBACK_SCHEMA) + hass.services.async_register( + DOMAIN, + SERVICE_COMFORT_FEEDBACK, + send_comfort_feedback, + schema=SEND_COMFORT_FEEDBACK_SCHEMA, + ) async def set_comfort_mode(service): """Set comfort mode.""" @@ -100,10 +110,9 @@ async def async_setup_entry(hass, entry, async_add_entities): if device: await device.set_comfort_mode() - hass.services.async_register(DOMAIN, - SERVICE_COMFORT_MODE, - set_comfort_mode, - schema=SET_COMFORT_MODE_SCHEMA) + hass.services.async_register( + DOMAIN, SERVICE_COMFORT_MODE, set_comfort_mode, schema=SET_COMFORT_MODE_SCHEMA + ) async def set_temperature_mode(service): """Set temperature mode.""" @@ -112,10 +121,12 @@ async def async_setup_entry(hass, entry, async_add_entities): if device: await device.set_temperature_mode(service.data[ATTR_VALUE]) - hass.services.async_register(DOMAIN, - SERVICE_TEMPERATURE_MODE, - set_temperature_mode, - schema=SET_TEMPERATURE_MODE_SCHEMA) + hass.services.async_register( + DOMAIN, + SERVICE_TEMPERATURE_MODE, + set_temperature_mode, + schema=SET_TEMPERATURE_MODE_SCHEMA, + ) class AmbiclimateEntity(ClimateDevice): @@ -141,11 +152,9 @@ class AmbiclimateEntity(ClimateDevice): def device_info(self): """Return the device info.""" return { - 'identifiers': { - (DOMAIN, self.unique_id) - }, - 'name': self.name, - 'manufacturer': 'Ambiclimate', + "identifiers": {(DOMAIN, self.unique_id)}, + "name": self.name, + "manufacturer": "Ambiclimate", } @property @@ -156,7 +165,7 @@ class AmbiclimateEntity(ClimateDevice): @property def target_temperature(self): """Return the target temperature.""" - return self._data.get('target_temperature') + return self._data.get("target_temperature") @property def target_temperature_step(self): @@ -166,12 +175,12 @@ class AmbiclimateEntity(ClimateDevice): @property def current_temperature(self): """Return the current temperature.""" - return self._data.get('temperature') + return self._data.get("temperature") @property def current_humidity(self): """Return the current humidity.""" - return self._data.get('humidity') + return self._data.get("humidity") @property def min_temp(self): @@ -196,7 +205,7 @@ class AmbiclimateEntity(ClimateDevice): @property def hvac_mode(self): """Return current operation.""" - if self._data.get('power', '').lower() == 'on': + if self._data.get("power", "").lower() == "on": return HVAC_MODE_HEAT return HVAC_MODE_OFF diff --git a/homeassistant/components/ambiclimate/config_flow.py b/homeassistant/components/ambiclimate/config_flow.py index 9bbdfceb7b0..db6d42d1d5c 100644 --- a/homeassistant/components/ambiclimate/config_flow.py +++ b/homeassistant/components/ambiclimate/config_flow.py @@ -7,10 +7,17 @@ from homeassistant import config_entries from homeassistant.components.http import HomeAssistantView from homeassistant.core import callback from homeassistant.helpers.aiohttp_client import async_get_clientsession -from .const import (AUTH_CALLBACK_NAME, AUTH_CALLBACK_PATH, CONF_CLIENT_ID, - CONF_CLIENT_SECRET, DOMAIN, STORAGE_VERSION, STORAGE_KEY) +from .const import ( + AUTH_CALLBACK_NAME, + AUTH_CALLBACK_PATH, + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, + DOMAIN, + STORAGE_VERSION, + STORAGE_KEY, +) -DATA_AMBICLIMATE_IMPL = 'ambiclimate_flow_implementation' +DATA_AMBICLIMATE_IMPL = "ambiclimate_flow_implementation" _LOGGER = logging.getLogger(__name__) @@ -30,7 +37,7 @@ def register_flow_implementation(hass, client_id, client_secret): } -@config_entries.HANDLERS.register('ambiclimate') +@config_entries.HANDLERS.register("ambiclimate") class AmbiclimateFlowHandler(config_entries.ConfigFlow): """Handle a config flow.""" @@ -45,54 +52,52 @@ class AmbiclimateFlowHandler(config_entries.ConfigFlow): async def async_step_user(self, user_input=None): """Handle external yaml configuration.""" if self.hass.config_entries.async_entries(DOMAIN): - return self.async_abort(reason='already_setup') + return self.async_abort(reason="already_setup") config = self.hass.data.get(DATA_AMBICLIMATE_IMPL, {}) if not config: _LOGGER.debug("No config") - return self.async_abort(reason='no_config') + return self.async_abort(reason="no_config") return await self.async_step_auth() async def async_step_auth(self, user_input=None): """Handle a flow start.""" if self.hass.config_entries.async_entries(DOMAIN): - return self.async_abort(reason='already_setup') + return self.async_abort(reason="already_setup") errors = {} if user_input is not None: - errors['base'] = 'follow_link' + errors["base"] = "follow_link" if not self._registered_view: self._generate_view() return self.async_show_form( - step_id='auth', - description_placeholders={'authorization_url': - await self._get_authorize_url(), - 'cb_url': self._cb_url()}, + step_id="auth", + description_placeholders={ + "authorization_url": await self._get_authorize_url(), + "cb_url": self._cb_url(), + }, errors=errors, ) async def async_step_code(self, code=None): """Received code for authentication.""" if self.hass.config_entries.async_entries(DOMAIN): - return self.async_abort(reason='already_setup') + return self.async_abort(reason="already_setup") token_info = await self._get_token_info(code) if token_info is None: - return self.async_abort(reason='access_token') + return self.async_abort(reason="access_token") config = self.hass.data[DATA_AMBICLIMATE_IMPL].copy() - config['callback_url'] = self._cb_url() + config["callback_url"] = self._cb_url() - return self.async_create_entry( - title="Ambiclimate", - data=config, - ) + return self.async_create_entry(title="Ambiclimate", data=config) async def _get_token_info(self, code): oauth = self._generate_oauth() @@ -116,15 +121,16 @@ class AmbiclimateFlowHandler(config_entries.ConfigFlow): clientsession = async_get_clientsession(self.hass) callback_url = self._cb_url() - oauth = ambiclimate.AmbiclimateOAuth(config.get(CONF_CLIENT_ID), - config.get(CONF_CLIENT_SECRET), - callback_url, - clientsession) + oauth = ambiclimate.AmbiclimateOAuth( + config.get(CONF_CLIENT_ID), + config.get(CONF_CLIENT_SECRET), + callback_url, + clientsession, + ) return oauth def _cb_url(self): - return '{}{}'.format(self.hass.config.api.base_url, - AUTH_CALLBACK_PATH) + return "{}{}".format(self.hass.config.api.base_url, AUTH_CALLBACK_PATH) async def _get_authorize_url(self): oauth = self._generate_oauth() @@ -140,14 +146,13 @@ class AmbiclimateAuthCallbackView(HomeAssistantView): async def get(self, request): """Receive authorization token.""" - code = request.query.get('code') + code = request.query.get("code") if code is None: return "No code" - hass = request.app['hass'] + hass = request.app["hass"] hass.async_create_task( hass.config_entries.flow.async_init( - DOMAIN, - context={'source': 'code'}, - data=code, - )) + DOMAIN, context={"source": "code"}, data=code + ) + ) return "OK!" diff --git a/homeassistant/components/ambiclimate/const.py b/homeassistant/components/ambiclimate/const.py index b1b9f4c2767..833fef303f5 100644 --- a/homeassistant/components/ambiclimate/const.py +++ b/homeassistant/components/ambiclimate/const.py @@ -1,14 +1,14 @@ """Constants used by the Ambiclimate component.""" -ATTR_VALUE = 'value' -CONF_CLIENT_ID = 'client_id' -CONF_CLIENT_SECRET = 'client_secret' -DOMAIN = 'ambiclimate' -SERVICE_COMFORT_FEEDBACK = 'send_comfort_feedback' -SERVICE_COMFORT_MODE = 'set_comfort_mode' -SERVICE_TEMPERATURE_MODE = 'set_temperature_mode' -STORAGE_KEY = 'ambiclimate_auth' +ATTR_VALUE = "value" +CONF_CLIENT_ID = "client_id" +CONF_CLIENT_SECRET = "client_secret" +DOMAIN = "ambiclimate" +SERVICE_COMFORT_FEEDBACK = "send_comfort_feedback" +SERVICE_COMFORT_MODE = "set_comfort_mode" +SERVICE_TEMPERATURE_MODE = "set_temperature_mode" +STORAGE_KEY = "ambiclimate_auth" STORAGE_VERSION = 1 -AUTH_CALLBACK_NAME = 'api:ambiclimate' -AUTH_CALLBACK_PATH = '/api/ambiclimate' +AUTH_CALLBACK_NAME = "api:ambiclimate" +AUTH_CALLBACK_PATH = "/api/ambiclimate" diff --git a/homeassistant/components/ambient_station/__init__.py b/homeassistant/components/ambient_station/__init__.py index 30028abb1c4..82c29f79983 100644 --- a/homeassistant/components/ambient_station/__init__.py +++ b/homeassistant/components/ambient_station/__init__.py @@ -7,220 +7,235 @@ import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( - ATTR_NAME, ATTR_LOCATION, CONF_API_KEY, EVENT_HOMEASSISTANT_STOP) + ATTR_NAME, + ATTR_LOCATION, + CONF_API_KEY, + EVENT_HOMEASSISTANT_STOP, +) from homeassistant.core import callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import aiohttp_client, config_validation as cv from homeassistant.helpers.dispatcher import ( - async_dispatcher_connect, async_dispatcher_send) + async_dispatcher_connect, + async_dispatcher_send, +) from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_call_later from .config_flow import configured_instances from .const import ( - ATTR_LAST_DATA, CONF_APP_KEY, DATA_CLIENT, DOMAIN, TOPIC_UPDATE, - TYPE_BINARY_SENSOR, TYPE_SENSOR) + ATTR_LAST_DATA, + CONF_APP_KEY, + DATA_CLIENT, + DOMAIN, + TOPIC_UPDATE, + TYPE_BINARY_SENSOR, + TYPE_SENSOR, +) _LOGGER = logging.getLogger(__name__) -DATA_CONFIG = 'config' +DATA_CONFIG = "config" DEFAULT_SOCKET_MIN_RETRY = 15 DEFAULT_WATCHDOG_SECONDS = 5 * 60 -TYPE_24HOURRAININ = '24hourrainin' -TYPE_BAROMABSIN = 'baromabsin' -TYPE_BAROMRELIN = 'baromrelin' -TYPE_BATT1 = 'batt1' -TYPE_BATT10 = 'batt10' -TYPE_BATT2 = 'batt2' -TYPE_BATT3 = 'batt3' -TYPE_BATT4 = 'batt4' -TYPE_BATT5 = 'batt5' -TYPE_BATT6 = 'batt6' -TYPE_BATT7 = 'batt7' -TYPE_BATT8 = 'batt8' -TYPE_BATT9 = 'batt9' -TYPE_BATTOUT = 'battout' -TYPE_CO2 = 'co2' -TYPE_DAILYRAININ = 'dailyrainin' -TYPE_DEWPOINT = 'dewPoint' -TYPE_EVENTRAININ = 'eventrainin' -TYPE_FEELSLIKE = 'feelsLike' -TYPE_HOURLYRAININ = 'hourlyrainin' -TYPE_HUMIDITY = 'humidity' -TYPE_HUMIDITY1 = 'humidity1' -TYPE_HUMIDITY10 = 'humidity10' -TYPE_HUMIDITY2 = 'humidity2' -TYPE_HUMIDITY3 = 'humidity3' -TYPE_HUMIDITY4 = 'humidity4' -TYPE_HUMIDITY5 = 'humidity5' -TYPE_HUMIDITY6 = 'humidity6' -TYPE_HUMIDITY7 = 'humidity7' -TYPE_HUMIDITY8 = 'humidity8' -TYPE_HUMIDITY9 = 'humidity9' -TYPE_HUMIDITYIN = 'humidityin' -TYPE_LASTRAIN = 'lastRain' -TYPE_MAXDAILYGUST = 'maxdailygust' -TYPE_MONTHLYRAININ = 'monthlyrainin' -TYPE_RELAY1 = 'relay1' -TYPE_RELAY10 = 'relay10' -TYPE_RELAY2 = 'relay2' -TYPE_RELAY3 = 'relay3' -TYPE_RELAY4 = 'relay4' -TYPE_RELAY5 = 'relay5' -TYPE_RELAY6 = 'relay6' -TYPE_RELAY7 = 'relay7' -TYPE_RELAY8 = 'relay8' -TYPE_RELAY9 = 'relay9' -TYPE_SOILHUM1 = 'soilhum1' -TYPE_SOILHUM10 = 'soilhum10' -TYPE_SOILHUM2 = 'soilhum2' -TYPE_SOILHUM3 = 'soilhum3' -TYPE_SOILHUM4 = 'soilhum4' -TYPE_SOILHUM5 = 'soilhum5' -TYPE_SOILHUM6 = 'soilhum6' -TYPE_SOILHUM7 = 'soilhum7' -TYPE_SOILHUM8 = 'soilhum8' -TYPE_SOILHUM9 = 'soilhum9' -TYPE_SOILTEMP1F = 'soiltemp1f' -TYPE_SOILTEMP10F = 'soiltemp10f' -TYPE_SOILTEMP2F = 'soiltemp2f' -TYPE_SOILTEMP3F = 'soiltemp3f' -TYPE_SOILTEMP4F = 'soiltemp4f' -TYPE_SOILTEMP5F = 'soiltemp5f' -TYPE_SOILTEMP6F = 'soiltemp6f' -TYPE_SOILTEMP7F = 'soiltemp7f' -TYPE_SOILTEMP8F = 'soiltemp8f' -TYPE_SOILTEMP9F = 'soiltemp9f' -TYPE_SOLARRADIATION = 'solarradiation' -TYPE_SOLARRADIATION_LX = 'solarradiation_lx' -TYPE_TEMP10F = 'temp10f' -TYPE_TEMP1F = 'temp1f' -TYPE_TEMP2F = 'temp2f' -TYPE_TEMP3F = 'temp3f' -TYPE_TEMP4F = 'temp4f' -TYPE_TEMP5F = 'temp5f' -TYPE_TEMP6F = 'temp6f' -TYPE_TEMP7F = 'temp7f' -TYPE_TEMP8F = 'temp8f' -TYPE_TEMP9F = 'temp9f' -TYPE_TEMPF = 'tempf' -TYPE_TEMPINF = 'tempinf' -TYPE_TOTALRAININ = 'totalrainin' -TYPE_UV = 'uv' -TYPE_WEEKLYRAININ = 'weeklyrainin' -TYPE_WINDDIR = 'winddir' -TYPE_WINDDIR_AVG10M = 'winddir_avg10m' -TYPE_WINDDIR_AVG2M = 'winddir_avg2m' -TYPE_WINDGUSTDIR = 'windgustdir' -TYPE_WINDGUSTMPH = 'windgustmph' -TYPE_WINDSPDMPH_AVG10M = 'windspdmph_avg10m' -TYPE_WINDSPDMPH_AVG2M = 'windspdmph_avg2m' -TYPE_WINDSPEEDMPH = 'windspeedmph' -TYPE_YEARLYRAININ = 'yearlyrainin' +TYPE_24HOURRAININ = "24hourrainin" +TYPE_BAROMABSIN = "baromabsin" +TYPE_BAROMRELIN = "baromrelin" +TYPE_BATT1 = "batt1" +TYPE_BATT10 = "batt10" +TYPE_BATT2 = "batt2" +TYPE_BATT3 = "batt3" +TYPE_BATT4 = "batt4" +TYPE_BATT5 = "batt5" +TYPE_BATT6 = "batt6" +TYPE_BATT7 = "batt7" +TYPE_BATT8 = "batt8" +TYPE_BATT9 = "batt9" +TYPE_BATTOUT = "battout" +TYPE_CO2 = "co2" +TYPE_DAILYRAININ = "dailyrainin" +TYPE_DEWPOINT = "dewPoint" +TYPE_EVENTRAININ = "eventrainin" +TYPE_FEELSLIKE = "feelsLike" +TYPE_HOURLYRAININ = "hourlyrainin" +TYPE_HUMIDITY = "humidity" +TYPE_HUMIDITY1 = "humidity1" +TYPE_HUMIDITY10 = "humidity10" +TYPE_HUMIDITY2 = "humidity2" +TYPE_HUMIDITY3 = "humidity3" +TYPE_HUMIDITY4 = "humidity4" +TYPE_HUMIDITY5 = "humidity5" +TYPE_HUMIDITY6 = "humidity6" +TYPE_HUMIDITY7 = "humidity7" +TYPE_HUMIDITY8 = "humidity8" +TYPE_HUMIDITY9 = "humidity9" +TYPE_HUMIDITYIN = "humidityin" +TYPE_LASTRAIN = "lastRain" +TYPE_MAXDAILYGUST = "maxdailygust" +TYPE_MONTHLYRAININ = "monthlyrainin" +TYPE_RELAY1 = "relay1" +TYPE_RELAY10 = "relay10" +TYPE_RELAY2 = "relay2" +TYPE_RELAY3 = "relay3" +TYPE_RELAY4 = "relay4" +TYPE_RELAY5 = "relay5" +TYPE_RELAY6 = "relay6" +TYPE_RELAY7 = "relay7" +TYPE_RELAY8 = "relay8" +TYPE_RELAY9 = "relay9" +TYPE_SOILHUM1 = "soilhum1" +TYPE_SOILHUM10 = "soilhum10" +TYPE_SOILHUM2 = "soilhum2" +TYPE_SOILHUM3 = "soilhum3" +TYPE_SOILHUM4 = "soilhum4" +TYPE_SOILHUM5 = "soilhum5" +TYPE_SOILHUM6 = "soilhum6" +TYPE_SOILHUM7 = "soilhum7" +TYPE_SOILHUM8 = "soilhum8" +TYPE_SOILHUM9 = "soilhum9" +TYPE_SOILTEMP1F = "soiltemp1f" +TYPE_SOILTEMP10F = "soiltemp10f" +TYPE_SOILTEMP2F = "soiltemp2f" +TYPE_SOILTEMP3F = "soiltemp3f" +TYPE_SOILTEMP4F = "soiltemp4f" +TYPE_SOILTEMP5F = "soiltemp5f" +TYPE_SOILTEMP6F = "soiltemp6f" +TYPE_SOILTEMP7F = "soiltemp7f" +TYPE_SOILTEMP8F = "soiltemp8f" +TYPE_SOILTEMP9F = "soiltemp9f" +TYPE_SOLARRADIATION = "solarradiation" +TYPE_SOLARRADIATION_LX = "solarradiation_lx" +TYPE_TEMP10F = "temp10f" +TYPE_TEMP1F = "temp1f" +TYPE_TEMP2F = "temp2f" +TYPE_TEMP3F = "temp3f" +TYPE_TEMP4F = "temp4f" +TYPE_TEMP5F = "temp5f" +TYPE_TEMP6F = "temp6f" +TYPE_TEMP7F = "temp7f" +TYPE_TEMP8F = "temp8f" +TYPE_TEMP9F = "temp9f" +TYPE_TEMPF = "tempf" +TYPE_TEMPINF = "tempinf" +TYPE_TOTALRAININ = "totalrainin" +TYPE_UV = "uv" +TYPE_WEEKLYRAININ = "weeklyrainin" +TYPE_WINDDIR = "winddir" +TYPE_WINDDIR_AVG10M = "winddir_avg10m" +TYPE_WINDDIR_AVG2M = "winddir_avg2m" +TYPE_WINDGUSTDIR = "windgustdir" +TYPE_WINDGUSTMPH = "windgustmph" +TYPE_WINDSPDMPH_AVG10M = "windspdmph_avg10m" +TYPE_WINDSPDMPH_AVG2M = "windspdmph_avg2m" +TYPE_WINDSPEEDMPH = "windspeedmph" +TYPE_YEARLYRAININ = "yearlyrainin" SENSOR_TYPES = { - TYPE_24HOURRAININ: ('24 Hr Rain', 'in', TYPE_SENSOR, None), - TYPE_BAROMABSIN: ('Abs Pressure', 'inHg', TYPE_SENSOR, 'pressure'), - TYPE_BAROMRELIN: ('Rel Pressure', 'inHg', TYPE_SENSOR, 'pressure'), - TYPE_BATT10: ('Battery 10', None, TYPE_BINARY_SENSOR, 'battery'), - TYPE_BATT1: ('Battery 1', None, TYPE_BINARY_SENSOR, 'battery'), - TYPE_BATT2: ('Battery 2', None, TYPE_BINARY_SENSOR, 'battery'), - TYPE_BATT3: ('Battery 3', None, TYPE_BINARY_SENSOR, 'battery'), - TYPE_BATT4: ('Battery 4', None, TYPE_BINARY_SENSOR, 'battery'), - TYPE_BATT5: ('Battery 5', None, TYPE_BINARY_SENSOR, 'battery'), - TYPE_BATT6: ('Battery 6', None, TYPE_BINARY_SENSOR, 'battery'), - TYPE_BATT7: ('Battery 7', None, TYPE_BINARY_SENSOR, 'battery'), - TYPE_BATT8: ('Battery 8', None, TYPE_BINARY_SENSOR, 'battery'), - TYPE_BATT9: ('Battery 9', None, TYPE_BINARY_SENSOR, 'battery'), - TYPE_BATTOUT: ('Battery', None, TYPE_BINARY_SENSOR, 'battery'), - TYPE_CO2: ('co2', 'ppm', TYPE_SENSOR, None), - TYPE_DAILYRAININ: ('Daily Rain', 'in', TYPE_SENSOR, None), - TYPE_DEWPOINT: ('Dew Point', '°F', TYPE_SENSOR, 'temperature'), - TYPE_EVENTRAININ: ('Event Rain', 'in', TYPE_SENSOR, None), - TYPE_FEELSLIKE: ('Feels Like', '°F', TYPE_SENSOR, 'temperature'), - TYPE_HOURLYRAININ: ('Hourly Rain Rate', 'in/hr', TYPE_SENSOR, None), - TYPE_HUMIDITY10: ('Humidity 10', '%', TYPE_SENSOR, 'humidity'), - TYPE_HUMIDITY1: ('Humidity 1', '%', TYPE_SENSOR, 'humidity'), - TYPE_HUMIDITY2: ('Humidity 2', '%', TYPE_SENSOR, 'humidity'), - TYPE_HUMIDITY3: ('Humidity 3', '%', TYPE_SENSOR, 'humidity'), - TYPE_HUMIDITY4: ('Humidity 4', '%', TYPE_SENSOR, 'humidity'), - TYPE_HUMIDITY5: ('Humidity 5', '%', TYPE_SENSOR, 'humidity'), - TYPE_HUMIDITY6: ('Humidity 6', '%', TYPE_SENSOR, 'humidity'), - TYPE_HUMIDITY7: ('Humidity 7', '%', TYPE_SENSOR, 'humidity'), - TYPE_HUMIDITY8: ('Humidity 8', '%', TYPE_SENSOR, 'humidity'), - TYPE_HUMIDITY9: ('Humidity 9', '%', TYPE_SENSOR, 'humidity'), - TYPE_HUMIDITY: ('Humidity', '%', TYPE_SENSOR, 'humidity'), - TYPE_HUMIDITYIN: ('Humidity In', '%', TYPE_SENSOR, 'humidity'), - TYPE_LASTRAIN: ('Last Rain', None, TYPE_SENSOR, 'timestamp'), - TYPE_MAXDAILYGUST: ('Max Gust', 'mph', TYPE_SENSOR, None), - TYPE_MONTHLYRAININ: ('Monthly Rain', 'in', TYPE_SENSOR, None), - TYPE_RELAY10: ('Relay 10', None, TYPE_BINARY_SENSOR, 'connectivity'), - TYPE_RELAY1: ('Relay 1', None, TYPE_BINARY_SENSOR, 'connectivity'), - TYPE_RELAY2: ('Relay 2', None, TYPE_BINARY_SENSOR, 'connectivity'), - TYPE_RELAY3: ('Relay 3', None, TYPE_BINARY_SENSOR, 'connectivity'), - TYPE_RELAY4: ('Relay 4', None, TYPE_BINARY_SENSOR, 'connectivity'), - TYPE_RELAY5: ('Relay 5', None, TYPE_BINARY_SENSOR, 'connectivity'), - TYPE_RELAY6: ('Relay 6', None, TYPE_BINARY_SENSOR, 'connectivity'), - TYPE_RELAY7: ('Relay 7', None, TYPE_BINARY_SENSOR, 'connectivity'), - TYPE_RELAY8: ('Relay 8', None, TYPE_BINARY_SENSOR, 'connectivity'), - TYPE_RELAY9: ('Relay 9', None, TYPE_BINARY_SENSOR, 'connectivity'), - TYPE_SOILHUM10: ('Soil Humidity 10', '%', TYPE_SENSOR, 'humidity'), - TYPE_SOILHUM1: ('Soil Humidity 1', '%', TYPE_SENSOR, 'humidity'), - TYPE_SOILHUM2: ('Soil Humidity 2', '%', TYPE_SENSOR, 'humidity'), - TYPE_SOILHUM3: ('Soil Humidity 3', '%', TYPE_SENSOR, 'humidity'), - TYPE_SOILHUM4: ('Soil Humidity 4', '%', TYPE_SENSOR, 'humidity'), - TYPE_SOILHUM5: ('Soil Humidity 5', '%', TYPE_SENSOR, 'humidity'), - TYPE_SOILHUM6: ('Soil Humidity 6', '%', TYPE_SENSOR, 'humidity'), - TYPE_SOILHUM7: ('Soil Humidity 7', '%', TYPE_SENSOR, 'humidity'), - TYPE_SOILHUM8: ('Soil Humidity 8', '%', TYPE_SENSOR, 'humidity'), - TYPE_SOILHUM9: ('Soil Humidity 9', '%', TYPE_SENSOR, 'humidity'), - TYPE_SOILTEMP10F: ('Soil Temp 10', '°F', TYPE_SENSOR, 'temperature'), - TYPE_SOILTEMP1F: ('Soil Temp 1', '°F', TYPE_SENSOR, 'temperature'), - TYPE_SOILTEMP2F: ('Soil Temp 2', '°F', TYPE_SENSOR, 'temperature'), - TYPE_SOILTEMP3F: ('Soil Temp 3', '°F', TYPE_SENSOR, 'temperature'), - TYPE_SOILTEMP4F: ('Soil Temp 4', '°F', TYPE_SENSOR, 'temperature'), - TYPE_SOILTEMP5F: ('Soil Temp 5', '°F', TYPE_SENSOR, 'temperature'), - TYPE_SOILTEMP6F: ('Soil Temp 6', '°F', TYPE_SENSOR, 'temperature'), - TYPE_SOILTEMP7F: ('Soil Temp 7', '°F', TYPE_SENSOR, 'temperature'), - TYPE_SOILTEMP8F: ('Soil Temp 8', '°F', TYPE_SENSOR, 'temperature'), - TYPE_SOILTEMP9F: ('Soil Temp 9', '°F', TYPE_SENSOR, 'temperature'), - TYPE_SOLARRADIATION: ('Solar Rad', 'W/m^2', TYPE_SENSOR, None), - TYPE_SOLARRADIATION_LX: ( - 'Solar Rad (lx)', 'lx', TYPE_SENSOR, 'illuminance'), - TYPE_TEMP10F: ('Temp 10', '°F', TYPE_SENSOR, 'temperature'), - TYPE_TEMP1F: ('Temp 1', '°F', TYPE_SENSOR, 'temperature'), - TYPE_TEMP2F: ('Temp 2', '°F', TYPE_SENSOR, 'temperature'), - TYPE_TEMP3F: ('Temp 3', '°F', TYPE_SENSOR, 'temperature'), - TYPE_TEMP4F: ('Temp 4', '°F', TYPE_SENSOR, 'temperature'), - TYPE_TEMP5F: ('Temp 5', '°F', TYPE_SENSOR, 'temperature'), - TYPE_TEMP6F: ('Temp 6', '°F', TYPE_SENSOR, 'temperature'), - TYPE_TEMP7F: ('Temp 7', '°F', TYPE_SENSOR, 'temperature'), - TYPE_TEMP8F: ('Temp 8', '°F', TYPE_SENSOR, 'temperature'), - TYPE_TEMP9F: ('Temp 9', '°F', TYPE_SENSOR, 'temperature'), - TYPE_TEMPF: ('Temp', '°F', TYPE_SENSOR, 'temperature'), - TYPE_TEMPINF: ('Inside Temp', '°F', TYPE_SENSOR, 'temperature'), - TYPE_TOTALRAININ: ('Lifetime Rain', 'in', TYPE_SENSOR, None), - TYPE_UV: ('uv', 'Index', TYPE_SENSOR, None), - TYPE_WEEKLYRAININ: ('Weekly Rain', 'in', TYPE_SENSOR, None), - TYPE_WINDDIR: ('Wind Dir', '°', TYPE_SENSOR, None), - TYPE_WINDDIR_AVG10M: ('Wind Dir Avg 10m', '°', TYPE_SENSOR, None), - TYPE_WINDDIR_AVG2M: ('Wind Dir Avg 2m', 'mph', TYPE_SENSOR, None), - TYPE_WINDGUSTDIR: ('Gust Dir', '°', TYPE_SENSOR, None), - TYPE_WINDGUSTMPH: ('Wind Gust', 'mph', TYPE_SENSOR, None), - TYPE_WINDSPDMPH_AVG10M: ('Wind Avg 10m', 'mph', TYPE_SENSOR, None), - TYPE_WINDSPDMPH_AVG2M: ('Wind Avg 2m', 'mph', TYPE_SENSOR, None), - TYPE_WINDSPEEDMPH: ('Wind Speed', 'mph', TYPE_SENSOR, None), - TYPE_YEARLYRAININ: ('Yearly Rain', 'in', TYPE_SENSOR, None), + TYPE_24HOURRAININ: ("24 Hr Rain", "in", TYPE_SENSOR, None), + TYPE_BAROMABSIN: ("Abs Pressure", "inHg", TYPE_SENSOR, "pressure"), + TYPE_BAROMRELIN: ("Rel Pressure", "inHg", TYPE_SENSOR, "pressure"), + TYPE_BATT10: ("Battery 10", None, TYPE_BINARY_SENSOR, "battery"), + TYPE_BATT1: ("Battery 1", None, TYPE_BINARY_SENSOR, "battery"), + TYPE_BATT2: ("Battery 2", None, TYPE_BINARY_SENSOR, "battery"), + TYPE_BATT3: ("Battery 3", None, TYPE_BINARY_SENSOR, "battery"), + TYPE_BATT4: ("Battery 4", None, TYPE_BINARY_SENSOR, "battery"), + TYPE_BATT5: ("Battery 5", None, TYPE_BINARY_SENSOR, "battery"), + TYPE_BATT6: ("Battery 6", None, TYPE_BINARY_SENSOR, "battery"), + TYPE_BATT7: ("Battery 7", None, TYPE_BINARY_SENSOR, "battery"), + TYPE_BATT8: ("Battery 8", None, TYPE_BINARY_SENSOR, "battery"), + TYPE_BATT9: ("Battery 9", None, TYPE_BINARY_SENSOR, "battery"), + TYPE_BATTOUT: ("Battery", None, TYPE_BINARY_SENSOR, "battery"), + TYPE_CO2: ("co2", "ppm", TYPE_SENSOR, None), + TYPE_DAILYRAININ: ("Daily Rain", "in", TYPE_SENSOR, None), + TYPE_DEWPOINT: ("Dew Point", "°F", TYPE_SENSOR, "temperature"), + TYPE_EVENTRAININ: ("Event Rain", "in", TYPE_SENSOR, None), + TYPE_FEELSLIKE: ("Feels Like", "°F", TYPE_SENSOR, "temperature"), + TYPE_HOURLYRAININ: ("Hourly Rain Rate", "in/hr", TYPE_SENSOR, None), + TYPE_HUMIDITY10: ("Humidity 10", "%", TYPE_SENSOR, "humidity"), + TYPE_HUMIDITY1: ("Humidity 1", "%", TYPE_SENSOR, "humidity"), + TYPE_HUMIDITY2: ("Humidity 2", "%", TYPE_SENSOR, "humidity"), + TYPE_HUMIDITY3: ("Humidity 3", "%", TYPE_SENSOR, "humidity"), + TYPE_HUMIDITY4: ("Humidity 4", "%", TYPE_SENSOR, "humidity"), + TYPE_HUMIDITY5: ("Humidity 5", "%", TYPE_SENSOR, "humidity"), + TYPE_HUMIDITY6: ("Humidity 6", "%", TYPE_SENSOR, "humidity"), + TYPE_HUMIDITY7: ("Humidity 7", "%", TYPE_SENSOR, "humidity"), + TYPE_HUMIDITY8: ("Humidity 8", "%", TYPE_SENSOR, "humidity"), + TYPE_HUMIDITY9: ("Humidity 9", "%", TYPE_SENSOR, "humidity"), + TYPE_HUMIDITY: ("Humidity", "%", TYPE_SENSOR, "humidity"), + TYPE_HUMIDITYIN: ("Humidity In", "%", TYPE_SENSOR, "humidity"), + TYPE_LASTRAIN: ("Last Rain", None, TYPE_SENSOR, "timestamp"), + TYPE_MAXDAILYGUST: ("Max Gust", "mph", TYPE_SENSOR, None), + TYPE_MONTHLYRAININ: ("Monthly Rain", "in", TYPE_SENSOR, None), + TYPE_RELAY10: ("Relay 10", None, TYPE_BINARY_SENSOR, "connectivity"), + TYPE_RELAY1: ("Relay 1", None, TYPE_BINARY_SENSOR, "connectivity"), + TYPE_RELAY2: ("Relay 2", None, TYPE_BINARY_SENSOR, "connectivity"), + TYPE_RELAY3: ("Relay 3", None, TYPE_BINARY_SENSOR, "connectivity"), + TYPE_RELAY4: ("Relay 4", None, TYPE_BINARY_SENSOR, "connectivity"), + TYPE_RELAY5: ("Relay 5", None, TYPE_BINARY_SENSOR, "connectivity"), + TYPE_RELAY6: ("Relay 6", None, TYPE_BINARY_SENSOR, "connectivity"), + TYPE_RELAY7: ("Relay 7", None, TYPE_BINARY_SENSOR, "connectivity"), + TYPE_RELAY8: ("Relay 8", None, TYPE_BINARY_SENSOR, "connectivity"), + TYPE_RELAY9: ("Relay 9", None, TYPE_BINARY_SENSOR, "connectivity"), + TYPE_SOILHUM10: ("Soil Humidity 10", "%", TYPE_SENSOR, "humidity"), + TYPE_SOILHUM1: ("Soil Humidity 1", "%", TYPE_SENSOR, "humidity"), + TYPE_SOILHUM2: ("Soil Humidity 2", "%", TYPE_SENSOR, "humidity"), + TYPE_SOILHUM3: ("Soil Humidity 3", "%", TYPE_SENSOR, "humidity"), + TYPE_SOILHUM4: ("Soil Humidity 4", "%", TYPE_SENSOR, "humidity"), + TYPE_SOILHUM5: ("Soil Humidity 5", "%", TYPE_SENSOR, "humidity"), + TYPE_SOILHUM6: ("Soil Humidity 6", "%", TYPE_SENSOR, "humidity"), + TYPE_SOILHUM7: ("Soil Humidity 7", "%", TYPE_SENSOR, "humidity"), + TYPE_SOILHUM8: ("Soil Humidity 8", "%", TYPE_SENSOR, "humidity"), + TYPE_SOILHUM9: ("Soil Humidity 9", "%", TYPE_SENSOR, "humidity"), + TYPE_SOILTEMP10F: ("Soil Temp 10", "°F", TYPE_SENSOR, "temperature"), + TYPE_SOILTEMP1F: ("Soil Temp 1", "°F", TYPE_SENSOR, "temperature"), + TYPE_SOILTEMP2F: ("Soil Temp 2", "°F", TYPE_SENSOR, "temperature"), + TYPE_SOILTEMP3F: ("Soil Temp 3", "°F", TYPE_SENSOR, "temperature"), + TYPE_SOILTEMP4F: ("Soil Temp 4", "°F", TYPE_SENSOR, "temperature"), + TYPE_SOILTEMP5F: ("Soil Temp 5", "°F", TYPE_SENSOR, "temperature"), + TYPE_SOILTEMP6F: ("Soil Temp 6", "°F", TYPE_SENSOR, "temperature"), + TYPE_SOILTEMP7F: ("Soil Temp 7", "°F", TYPE_SENSOR, "temperature"), + TYPE_SOILTEMP8F: ("Soil Temp 8", "°F", TYPE_SENSOR, "temperature"), + TYPE_SOILTEMP9F: ("Soil Temp 9", "°F", TYPE_SENSOR, "temperature"), + TYPE_SOLARRADIATION: ("Solar Rad", "W/m^2", TYPE_SENSOR, None), + TYPE_SOLARRADIATION_LX: ("Solar Rad (lx)", "lx", TYPE_SENSOR, "illuminance"), + TYPE_TEMP10F: ("Temp 10", "°F", TYPE_SENSOR, "temperature"), + TYPE_TEMP1F: ("Temp 1", "°F", TYPE_SENSOR, "temperature"), + TYPE_TEMP2F: ("Temp 2", "°F", TYPE_SENSOR, "temperature"), + TYPE_TEMP3F: ("Temp 3", "°F", TYPE_SENSOR, "temperature"), + TYPE_TEMP4F: ("Temp 4", "°F", TYPE_SENSOR, "temperature"), + TYPE_TEMP5F: ("Temp 5", "°F", TYPE_SENSOR, "temperature"), + TYPE_TEMP6F: ("Temp 6", "°F", TYPE_SENSOR, "temperature"), + TYPE_TEMP7F: ("Temp 7", "°F", TYPE_SENSOR, "temperature"), + TYPE_TEMP8F: ("Temp 8", "°F", TYPE_SENSOR, "temperature"), + TYPE_TEMP9F: ("Temp 9", "°F", TYPE_SENSOR, "temperature"), + TYPE_TEMPF: ("Temp", "°F", TYPE_SENSOR, "temperature"), + TYPE_TEMPINF: ("Inside Temp", "°F", TYPE_SENSOR, "temperature"), + TYPE_TOTALRAININ: ("Lifetime Rain", "in", TYPE_SENSOR, None), + TYPE_UV: ("uv", "Index", TYPE_SENSOR, None), + TYPE_WEEKLYRAININ: ("Weekly Rain", "in", TYPE_SENSOR, None), + TYPE_WINDDIR: ("Wind Dir", "°", TYPE_SENSOR, None), + TYPE_WINDDIR_AVG10M: ("Wind Dir Avg 10m", "°", TYPE_SENSOR, None), + TYPE_WINDDIR_AVG2M: ("Wind Dir Avg 2m", "mph", TYPE_SENSOR, None), + TYPE_WINDGUSTDIR: ("Gust Dir", "°", TYPE_SENSOR, None), + TYPE_WINDGUSTMPH: ("Wind Gust", "mph", TYPE_SENSOR, None), + TYPE_WINDSPDMPH_AVG10M: ("Wind Avg 10m", "mph", TYPE_SENSOR, None), + TYPE_WINDSPDMPH_AVG2M: ("Wind Avg 2m", "mph", TYPE_SENSOR, None), + TYPE_WINDSPEEDMPH: ("Wind Speed", "mph", TYPE_SENSOR, None), + TYPE_YEARLYRAININ: ("Yearly Rain", "in", TYPE_SENSOR, None), } -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: - vol.Schema({ - vol.Required(CONF_APP_KEY): cv.string, - vol.Required(CONF_API_KEY): cv.string, - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_APP_KEY): cv.string, + vol.Required(CONF_API_KEY): cv.string, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) async def async_setup(hass, config): @@ -242,11 +257,10 @@ async def async_setup(hass, config): hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, - context={'source': SOURCE_IMPORT}, - data={ - CONF_API_KEY: conf[CONF_API_KEY], - CONF_APP_KEY: conf[CONF_APP_KEY] - })) + context={"source": SOURCE_IMPORT}, + data={CONF_API_KEY: conf[CONF_API_KEY], CONF_APP_KEY: conf[CONF_APP_KEY]}, + ) + ) return True @@ -257,18 +271,23 @@ async def async_setup_entry(hass, config_entry): try: ambient = AmbientStation( - hass, config_entry, + hass, + config_entry, Client( config_entry.data[CONF_API_KEY], - config_entry.data[CONF_APP_KEY], session)) + config_entry.data[CONF_APP_KEY], + session, + ), + ) hass.loop.create_task(ambient.ws_connect()) hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = ambient except WebsocketError as err: - _LOGGER.error('Config entry failed: %s', err) + _LOGGER.error("Config entry failed: %s", err) raise ConfigEntryNotReady hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_STOP, ambient.client.websocket.disconnect()) + EVENT_HOMEASSISTANT_STOP, ambient.client.websocket.disconnect() + ) return True @@ -278,9 +297,8 @@ async def async_unload_entry(hass, config_entry): ambient = hass.data[DOMAIN][DATA_CLIENT].pop(config_entry.entry_id) hass.async_create_task(ambient.ws_disconnect()) - for component in ('binary_sensor', 'sensor'): - await hass.config_entries.async_forward_entry_unload( - config_entry, component) + for component in ("binary_sensor", "sensor"): + await hass.config_entries.async_forward_entry_unload(config_entry, component) return True @@ -289,7 +307,7 @@ async def async_migrate_entry(hass, config_entry): """Migrate old entry.""" version = config_entry.version - _LOGGER.debug('Migrating from version %s', version) + _LOGGER.debug("Migrating from version %s", version) # 1 -> 2: Unique ID format changed, so delete and re-import: if version == 1: @@ -302,7 +320,7 @@ async def async_migrate_entry(hass, config_entry): version = config_entry.version = 2 hass.config_entries.async_update_entry(config_entry) - _LOGGER.info('Migration to version %s successful', version) + _LOGGER.info("Migration to version %s successful", version) return True @@ -327,71 +345,70 @@ class AmbientStation: await self.client.websocket.connect() except WebsocketError as err: _LOGGER.error("Error with the websocket connection: %s", err) - self._ws_reconnect_delay = min( - 2 * self._ws_reconnect_delay, 480) - async_call_later( - self._hass, self._ws_reconnect_delay, self.ws_connect) + self._ws_reconnect_delay = min(2 * self._ws_reconnect_delay, 480) + async_call_later(self._hass, self._ws_reconnect_delay, self.ws_connect) async def ws_connect(self): """Register handlers and connect to the websocket.""" + async def _ws_reconnect(event_time): """Forcibly disconnect from and reconnect to the websocket.""" - _LOGGER.debug('Watchdog expired; forcing socket reconnection') + _LOGGER.debug("Watchdog expired; forcing socket reconnection") await self.client.websocket.disconnect() await self._attempt_connect() def on_connect(): """Define a handler to fire when the websocket is connected.""" - _LOGGER.info('Connected to websocket') - _LOGGER.debug('Watchdog starting') + _LOGGER.info("Connected to websocket") + _LOGGER.debug("Watchdog starting") if self._watchdog_listener is not None: self._watchdog_listener() self._watchdog_listener = async_call_later( - self._hass, DEFAULT_WATCHDOG_SECONDS, _ws_reconnect) + self._hass, DEFAULT_WATCHDOG_SECONDS, _ws_reconnect + ) def on_data(data): """Define a handler to fire when the data is received.""" - mac_address = data['macAddress'] + mac_address = data["macAddress"] if data != self.stations[mac_address][ATTR_LAST_DATA]: - _LOGGER.debug('New data received: %s', data) + _LOGGER.debug("New data received: %s", data) self.stations[mac_address][ATTR_LAST_DATA] = data async_dispatcher_send(self._hass, TOPIC_UPDATE) - _LOGGER.debug('Resetting watchdog') + _LOGGER.debug("Resetting watchdog") self._watchdog_listener() self._watchdog_listener = async_call_later( - self._hass, DEFAULT_WATCHDOG_SECONDS, _ws_reconnect) + self._hass, DEFAULT_WATCHDOG_SECONDS, _ws_reconnect + ) def on_disconnect(): """Define a handler to fire when the websocket is disconnected.""" - _LOGGER.info('Disconnected from websocket') + _LOGGER.info("Disconnected from websocket") def on_subscribed(data): """Define a handler to fire when the subscription is set.""" - for station in data['devices']: - if station['macAddress'] in self.stations: + for station in data["devices"]: + if station["macAddress"] in self.stations: continue - _LOGGER.debug('New station subscription: %s', data) + _LOGGER.debug("New station subscription: %s", data) self.monitored_conditions = [ - k for k in station['lastData'] - if k in SENSOR_TYPES + k for k in station["lastData"] if k in SENSOR_TYPES ] # If the user is monitoring brightness (in W/m^2), # make sure we also add a calculated sensor for the # same data measured in lx: if TYPE_SOLARRADIATION in self.monitored_conditions: - self.monitored_conditions.append( - TYPE_SOLARRADIATION_LX) + self.monitored_conditions.append(TYPE_SOLARRADIATION_LX) - self.stations[station['macAddress']] = { - ATTR_LAST_DATA: station['lastData'], - ATTR_LOCATION: station.get('info', {}).get('location'), - ATTR_NAME: - station.get('info', {}).get( - 'name', station['macAddress']), + self.stations[station["macAddress"]] = { + ATTR_LAST_DATA: station["lastData"], + ATTR_LOCATION: station.get("info", {}).get("location"), + ATTR_NAME: station.get("info", {}).get( + "name", station["macAddress"] + ), } # If the websocket disconnects and reconnects, the on_subscribed @@ -399,10 +416,12 @@ class AmbientStation: # attempt forward setup of the config entry (because it will have # already been done): if not self._entry_setup_complete: - for component in ('binary_sensor', 'sensor'): + for component in ("binary_sensor", "sensor"): self._hass.async_create_task( self._hass.config_entries.async_forward_entry_setup( - self._config_entry, component)) + self._config_entry, component + ) + ) self._entry_setup_complete = True self._ws_reconnect_delay = DEFAULT_SOCKET_MIN_RETRY @@ -423,8 +442,8 @@ class AmbientWeatherEntity(Entity): """Define a base Ambient PWS entity.""" def __init__( - self, ambient, mac_address, station_name, sensor_type, - sensor_name, device_class): + self, ambient, mac_address, station_name, sensor_type, sensor_name, device_class + ): """Initialize the sensor.""" self._ambient = ambient self._device_class = device_class @@ -443,10 +462,18 @@ class AmbientWeatherEntity(Entity): # solarradiation_lx sensor shows as available if the solarradiation # sensor is available: if self._sensor_type == TYPE_SOLARRADIATION_LX: - return self._ambient.stations[self._mac_address][ - ATTR_LAST_DATA].get(TYPE_SOLARRADIATION) is not None - return self._ambient.stations[self._mac_address][ATTR_LAST_DATA].get( - self._sensor_type) is not None + return ( + self._ambient.stations[self._mac_address][ATTR_LAST_DATA].get( + TYPE_SOLARRADIATION + ) + is not None + ) + return ( + self._ambient.stations[self._mac_address][ATTR_LAST_DATA].get( + self._sensor_type + ) + is not None + ) @property def device_class(self): @@ -457,17 +484,15 @@ class AmbientWeatherEntity(Entity): def device_info(self): """Return device registry information for this entity.""" return { - 'identifiers': { - (DOMAIN, self._mac_address) - }, - 'name': self._station_name, - 'manufacturer': 'Ambient Weather', + "identifiers": {(DOMAIN, self._mac_address)}, + "name": self._station_name, + "manufacturer": "Ambient Weather", } @property def name(self): """Return the name of the sensor.""" - return '{0}_{1}'.format(self._station_name, self._sensor_name) + return "{0}_{1}".format(self._station_name, self._sensor_name) @property def should_poll(self): @@ -477,17 +502,19 @@ class AmbientWeatherEntity(Entity): @property def unique_id(self): """Return a unique, unchanging string that represents this sensor.""" - return '{0}_{1}'.format(self._mac_address, self._sensor_type) + return "{0}_{1}".format(self._mac_address, self._sensor_type) async def async_added_to_hass(self): """Register callbacks.""" + @callback def update(): """Update the state.""" self.async_schedule_update_ha_state(True) self._async_unsub_dispatcher_connect = async_dispatcher_connect( - self.hass, TOPIC_UPDATE, update) + self.hass, TOPIC_UPDATE, update + ) async def async_will_remove_from_hass(self): """Disconnect dispatcher listener when removed.""" diff --git a/homeassistant/components/ambient_station/binary_sensor.py b/homeassistant/components/ambient_station/binary_sensor.py index 798605a1aa2..3f02eb9f1e8 100644 --- a/homeassistant/components/ambient_station/binary_sensor.py +++ b/homeassistant/components/ambient_station/binary_sensor.py @@ -5,16 +5,26 @@ from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.const import ATTR_NAME from . import ( - SENSOR_TYPES, TYPE_BATT1, TYPE_BATT2, TYPE_BATT3, TYPE_BATT4, TYPE_BATT5, - TYPE_BATT6, TYPE_BATT7, TYPE_BATT8, TYPE_BATT9, TYPE_BATT10, TYPE_BATTOUT, - AmbientWeatherEntity) + SENSOR_TYPES, + TYPE_BATT1, + TYPE_BATT2, + TYPE_BATT3, + TYPE_BATT4, + TYPE_BATT5, + TYPE_BATT6, + TYPE_BATT7, + TYPE_BATT8, + TYPE_BATT9, + TYPE_BATT10, + TYPE_BATTOUT, + AmbientWeatherEntity, +) from .const import ATTR_LAST_DATA, DATA_CLIENT, DOMAIN, TYPE_BINARY_SENSOR _LOGGER = logging.getLogger(__name__) -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up Ambient PWS binary sensors based on the old way.""" pass @@ -30,8 +40,14 @@ async def async_setup_entry(hass, entry, async_add_entities): if kind == TYPE_BINARY_SENSOR: binary_sensor_list.append( AmbientWeatherBinarySensor( - ambient, mac_address, station[ATTR_NAME], condition, - name, device_class)) + ambient, + mac_address, + station[ATTR_NAME], + condition, + name, + device_class, + ) + ) async_add_entities(binary_sensor_list, True) @@ -42,15 +58,25 @@ class AmbientWeatherBinarySensor(AmbientWeatherEntity, BinarySensorDevice): @property def is_on(self): """Return the status of the sensor.""" - if self._sensor_type in (TYPE_BATT1, TYPE_BATT10, TYPE_BATT2, - TYPE_BATT3, TYPE_BATT4, TYPE_BATT5, - TYPE_BATT6, TYPE_BATT7, TYPE_BATT8, - TYPE_BATT9, TYPE_BATTOUT): + if self._sensor_type in ( + TYPE_BATT1, + TYPE_BATT10, + TYPE_BATT2, + TYPE_BATT3, + TYPE_BATT4, + TYPE_BATT5, + TYPE_BATT6, + TYPE_BATT7, + TYPE_BATT8, + TYPE_BATT9, + TYPE_BATTOUT, + ): return self._state == 0 return self._state == 1 async def async_update(self): """Fetch new state data for the entity.""" - self._state = self._ambient.stations[ - self._mac_address][ATTR_LAST_DATA].get(self._sensor_type) + self._state = self._ambient.stations[self._mac_address][ATTR_LAST_DATA].get( + self._sensor_type + ) diff --git a/homeassistant/components/ambient_station/config_flow.py b/homeassistant/components/ambient_station/config_flow.py index 895c140662f..256e55ba402 100644 --- a/homeassistant/components/ambient_station/config_flow.py +++ b/homeassistant/components/ambient_station/config_flow.py @@ -13,8 +13,8 @@ from .const import CONF_APP_KEY, DOMAIN def configured_instances(hass): """Return a set of configured Ambient PWS instances.""" return set( - entry.data[CONF_APP_KEY] - for entry in hass.config_entries.async_entries(DOMAIN)) + entry.data[CONF_APP_KEY] for entry in hass.config_entries.async_entries(DOMAIN) + ) @config_entries.HANDLERS.register(DOMAIN) @@ -26,15 +26,12 @@ class AmbientStationFlowHandler(config_entries.ConfigFlow): async def _show_form(self, errors=None): """Show the form to the user.""" - data_schema = vol.Schema({ - vol.Required(CONF_API_KEY): str, - vol.Required(CONF_APP_KEY): str, - }) + data_schema = vol.Schema( + {vol.Required(CONF_API_KEY): str, vol.Required(CONF_APP_KEY): str} + ) return self.async_show_form( - step_id='user', - data_schema=data_schema, - errors=errors if errors else {}, + step_id="user", data_schema=data_schema, errors=errors if errors else {} ) async def async_step_import(self, import_config): @@ -50,22 +47,22 @@ class AmbientStationFlowHandler(config_entries.ConfigFlow): return await self._show_form() if user_input[CONF_APP_KEY] in configured_instances(self.hass): - return await self._show_form({CONF_APP_KEY: 'identifier_exists'}) + return await self._show_form({CONF_APP_KEY: "identifier_exists"}) session = aiohttp_client.async_get_clientsession(self.hass) - client = Client( - user_input[CONF_API_KEY], user_input[CONF_APP_KEY], session) + client = Client(user_input[CONF_API_KEY], user_input[CONF_APP_KEY], session) try: devices = await client.api.get_devices() except AmbientError: - return await self._show_form({'base': 'invalid_key'}) + return await self._show_form({"base": "invalid_key"}) if not devices: - return await self._show_form({'base': 'no_devices'}) + return await self._show_form({"base": "no_devices"}) # The Application Key (which identifies each config entry) is too long # to show nicely in the UI, so we take the first 12 characters (similar # to how GitHub does it): return self.async_create_entry( - title=user_input[CONF_APP_KEY][:12], data=user_input) + title=user_input[CONF_APP_KEY][:12], data=user_input + ) diff --git a/homeassistant/components/ambient_station/const.py b/homeassistant/components/ambient_station/const.py index 27ec7afefaa..b2df34f2f28 100644 --- a/homeassistant/components/ambient_station/const.py +++ b/homeassistant/components/ambient_station/const.py @@ -1,13 +1,13 @@ """Define constants for the Ambient PWS component.""" -DOMAIN = 'ambient_station' +DOMAIN = "ambient_station" -ATTR_LAST_DATA = 'last_data' +ATTR_LAST_DATA = "last_data" -CONF_APP_KEY = 'app_key' +CONF_APP_KEY = "app_key" -DATA_CLIENT = 'data_client' +DATA_CLIENT = "data_client" -TOPIC_UPDATE = 'update' +TOPIC_UPDATE = "update" -TYPE_BINARY_SENSOR = 'binary_sensor' -TYPE_SENSOR = 'sensor' +TYPE_BINARY_SENSOR = "binary_sensor" +TYPE_SENSOR = "sensor" diff --git a/homeassistant/components/ambient_station/sensor.py b/homeassistant/components/ambient_station/sensor.py index 23d5fda1161..56425221e0d 100644 --- a/homeassistant/components/ambient_station/sensor.py +++ b/homeassistant/components/ambient_station/sensor.py @@ -4,15 +4,17 @@ import logging from homeassistant.const import ATTR_NAME from . import ( - SENSOR_TYPES, TYPE_SOLARRADIATION, TYPE_SOLARRADIATION_LX, - AmbientWeatherEntity) + SENSOR_TYPES, + TYPE_SOLARRADIATION, + TYPE_SOLARRADIATION_LX, + AmbientWeatherEntity, +) from .const import ATTR_LAST_DATA, DATA_CLIENT, DOMAIN, TYPE_SENSOR _LOGGER = logging.getLogger(__name__) -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up Ambient PWS sensors based on existing config.""" pass @@ -28,8 +30,15 @@ async def async_setup_entry(hass, entry, async_add_entities): if kind == TYPE_SENSOR: sensor_list.append( AmbientWeatherSensor( - ambient, mac_address, station[ATTR_NAME], condition, - name, device_class, unit)) + ambient, + mac_address, + station[ATTR_NAME], + condition, + name, + device_class, + unit, + ) + ) async_add_entities(sensor_list, True) @@ -38,16 +47,19 @@ class AmbientWeatherSensor(AmbientWeatherEntity): """Define an Ambient sensor.""" def __init__( - self, ambient, mac_address, station_name, sensor_type, sensor_name, - device_class, unit): + self, + ambient, + mac_address, + station_name, + sensor_type, + sensor_name, + device_class, + unit, + ): """Initialize the sensor.""" super().__init__( - ambient, - mac_address, - station_name, - sensor_type, - sensor_name, - device_class) + ambient, mac_address, station_name, sensor_type, sensor_name, device_class + ) self._unit = unit @@ -67,9 +79,11 @@ class AmbientWeatherSensor(AmbientWeatherEntity): # If the user requests the solarradiation_lx sensor, use the # value of the solarradiation sensor and apply a very accurate # approximation of converting sunlight W/m^2 to lx: - w_m2_brightness_val = self._ambient.stations[ - self._mac_address][ATTR_LAST_DATA].get(TYPE_SOLARRADIATION) - self._state = round(float(w_m2_brightness_val)/0.0079) + w_m2_brightness_val = self._ambient.stations[self._mac_address][ + ATTR_LAST_DATA + ].get(TYPE_SOLARRADIATION) + self._state = round(float(w_m2_brightness_val) / 0.0079) else: - self._state = self._ambient.stations[ - self._mac_address][ATTR_LAST_DATA].get(self._sensor_type) + self._state = self._ambient.stations[self._mac_address][ATTR_LAST_DATA].get( + self._sensor_type + ) diff --git a/homeassistant/components/amcrest/__init__.py b/homeassistant/components/amcrest/__init__.py index 1c9303b2c52..f915872abf0 100644 --- a/homeassistant/components/amcrest/__init__.py +++ b/homeassistant/components/amcrest/__init__.py @@ -13,14 +13,24 @@ from homeassistant.components.camera import DOMAIN as CAMERA from homeassistant.components.sensor import DOMAIN as SENSOR from homeassistant.components.switch import DOMAIN as SWITCH from homeassistant.const import ( - ATTR_ENTITY_ID, CONF_AUTHENTICATION, CONF_BINARY_SENSORS, CONF_HOST, - CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_SCAN_INTERVAL, CONF_SENSORS, - CONF_SWITCHES, CONF_USERNAME, ENTITY_MATCH_ALL, HTTP_BASIC_AUTHENTICATION) + ATTR_ENTITY_ID, + CONF_AUTHENTICATION, + CONF_BINARY_SENSORS, + CONF_HOST, + CONF_NAME, + CONF_PASSWORD, + CONF_PORT, + CONF_SCAN_INTERVAL, + CONF_SENSORS, + CONF_SWITCHES, + CONF_USERNAME, + ENTITY_MATCH_ALL, + HTTP_BASIC_AUTHENTICATION, +) from homeassistant.exceptions import Unauthorized, UnknownUser from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.dispatcher import ( - async_dispatcher_send, dispatcher_send) +from homeassistant.helpers.dispatcher import async_dispatcher_send, dispatcher_send from homeassistant.helpers.event import track_time_interval from homeassistant.helpers.service import async_extract_entity_ids @@ -33,31 +43,26 @@ from .switch import SWITCHES _LOGGER = logging.getLogger(__name__) -CONF_RESOLUTION = 'resolution' -CONF_STREAM_SOURCE = 'stream_source' -CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments' -CONF_CONTROL_LIGHT = 'control_light' +CONF_RESOLUTION = "resolution" +CONF_STREAM_SOURCE = "stream_source" +CONF_FFMPEG_ARGUMENTS = "ffmpeg_arguments" +CONF_CONTROL_LIGHT = "control_light" -DEFAULT_NAME = 'Amcrest Camera' +DEFAULT_NAME = "Amcrest Camera" DEFAULT_PORT = 80 -DEFAULT_RESOLUTION = 'high' -DEFAULT_ARGUMENTS = '-pred 1' +DEFAULT_RESOLUTION = "high" +DEFAULT_ARGUMENTS = "-pred 1" MAX_ERRORS = 5 RECHECK_INTERVAL = timedelta(minutes=1) -NOTIFICATION_ID = 'amcrest_notification' -NOTIFICATION_TITLE = 'Amcrest Camera Setup' +NOTIFICATION_ID = "amcrest_notification" +NOTIFICATION_TITLE = "Amcrest Camera Setup" -RESOLUTION_LIST = { - 'high': 0, - 'low': 1, -} +RESOLUTION_LIST = {"high": 0, "low": 1} SCAN_INTERVAL = timedelta(seconds=10) -AUTHENTICATION_LIST = { - 'basic': 'basic' -} +AUTHENTICATION_LIST = {"basic": "basic"} def _deprecated_sensor_values(sensors): @@ -66,8 +71,11 @@ def _deprecated_sensor_values(sensors): "The '%s' option value '%s' is deprecated, " "please remove it from your configuration and use " "the '%s' option with value '%s' instead", - CONF_SENSORS, SENSOR_MOTION_DETECTOR, CONF_BINARY_SENSORS, - BINARY_SENSOR_MOTION_DETECTED) + CONF_SENSORS, + SENSOR_MOTION_DETECTOR, + CONF_BINARY_SENSORS, + BINARY_SENSOR_MOTION_DETECTED, + ) return sensors @@ -77,7 +85,9 @@ def _deprecated_switches(config): "The '%s' option (with value %s) is deprecated, " "please remove it from your configuration and use " "services and attributes instead", - CONF_SWITCHES, config[CONF_SWITCHES]) + CONF_SWITCHES, + config[CONF_SWITCHES], + ) return config @@ -88,37 +98,41 @@ def _has_unique_names(devices): AMCREST_SCHEMA = vol.All( - vol.Schema({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION): - vol.All(vol.In(AUTHENTICATION_LIST)), - vol.Optional(CONF_RESOLUTION, default=DEFAULT_RESOLUTION): - vol.All(vol.In(RESOLUTION_LIST)), - vol.Optional(CONF_STREAM_SOURCE, default=STREAM_SOURCE_LIST[0]): - vol.All(vol.In(STREAM_SOURCE_LIST)), - vol.Optional(CONF_FFMPEG_ARGUMENTS, default=DEFAULT_ARGUMENTS): - cv.string, - vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL): - cv.time_period, - vol.Optional(CONF_BINARY_SENSORS): - vol.All(cv.ensure_list, [vol.In(BINARY_SENSORS)]), - vol.Optional(CONF_SENSORS): - vol.All(cv.ensure_list, [vol.In(SENSORS)], - _deprecated_sensor_values), - vol.Optional(CONF_SWITCHES): - vol.All(cv.ensure_list, [vol.In(SWITCHES)]), - vol.Optional(CONF_CONTROL_LIGHT, default=True): cv.boolean, - }), - _deprecated_switches + vol.Schema( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional( + CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION + ): vol.All(vol.In(AUTHENTICATION_LIST)), + vol.Optional(CONF_RESOLUTION, default=DEFAULT_RESOLUTION): vol.All( + vol.In(RESOLUTION_LIST) + ), + vol.Optional(CONF_STREAM_SOURCE, default=STREAM_SOURCE_LIST[0]): vol.All( + vol.In(STREAM_SOURCE_LIST) + ), + vol.Optional(CONF_FFMPEG_ARGUMENTS, default=DEFAULT_ARGUMENTS): cv.string, + vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL): cv.time_period, + vol.Optional(CONF_BINARY_SENSORS): vol.All( + cv.ensure_list, [vol.In(BINARY_SENSORS)] + ), + vol.Optional(CONF_SENSORS): vol.All( + cv.ensure_list, [vol.In(SENSORS)], _deprecated_sensor_values + ), + vol.Optional(CONF_SWITCHES): vol.All(cv.ensure_list, [vol.In(SWITCHES)]), + vol.Optional(CONF_CONTROL_LIGHT, default=True): cv.boolean, + } + ), + _deprecated_switches, ) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.All(cv.ensure_list, [AMCREST_SCHEMA], _has_unique_names) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + {DOMAIN: vol.All(cv.ensure_list, [AMCREST_SCHEMA], _has_unique_names)}, + extra=vol.ALLOW_EXTRA, +) # pylint: disable=too-many-ancestors @@ -132,8 +146,9 @@ class AmcrestChecker(Http): self._wrap_errors = 0 self._wrap_lock = threading.Lock() self._unsub_recheck = None - super().__init__(host, port, user, password, retries_connection=1, - timeout_protocol=3.05) + super().__init__( + host, port, user, password, retries_connection=1, timeout_protocol=3.05 + ) @property def available(self): @@ -148,17 +163,16 @@ class AmcrestChecker(Http): with self._wrap_lock: was_online = self.available self._wrap_errors += 1 - _LOGGER.debug('%s camera errs: %i', self._wrap_name, - self._wrap_errors) + _LOGGER.debug("%s camera errs: %i", self._wrap_name, self._wrap_errors) offline = not self.available if offline and was_online: - _LOGGER.error( - '%s camera offline: Too many errors', self._wrap_name) + _LOGGER.error("%s camera offline: Too many errors", self._wrap_name) dispatcher_send( - self._hass, - service_signal(SERVICE_UPDATE, self._wrap_name)) + self._hass, service_signal(SERVICE_UPDATE, self._wrap_name) + ) self._unsub_recheck = track_time_interval( - self._hass, self._wrap_test_online, RECHECK_INTERVAL) + self._hass, self._wrap_test_online, RECHECK_INTERVAL + ) raise with self._wrap_lock: was_offline = not self.available @@ -166,9 +180,8 @@ class AmcrestChecker(Http): if was_offline: self._unsub_recheck() self._unsub_recheck = None - _LOGGER.error('%s camera back online', self._wrap_name) - dispatcher_send( - self._hass, service_signal(SERVICE_UPDATE, self._wrap_name)) + _LOGGER.error("%s camera back online", self._wrap_name) + dispatcher_send(self._hass, service_signal(SERVICE_UPDATE, self._wrap_name)) return ret def _wrap_test_online(self, now): @@ -190,9 +203,8 @@ def setup(hass, config): try: api = AmcrestChecker( - hass, name, - device[CONF_HOST], device[CONF_PORT], - username, password) + hass, name, device[CONF_HOST], device[CONF_PORT], username, password + ) except LoginError as ex: _LOGGER.error("Login error for %s camera: %s", name, ex) @@ -214,41 +226,40 @@ def setup(hass, config): authentication = None hass.data[DATA_AMCREST][DEVICES][name] = AmcrestDevice( - api, authentication, ffmpeg_arguments, stream_source, - resolution, control_light) + api, + authentication, + ffmpeg_arguments, + stream_source, + resolution, + control_light, + ) - discovery.load_platform( - hass, CAMERA, DOMAIN, { - CONF_NAME: name, - }, config) + discovery.load_platform(hass, CAMERA, DOMAIN, {CONF_NAME: name}, config) if binary_sensors: discovery.load_platform( - hass, BINARY_SENSOR, DOMAIN, { - CONF_NAME: name, - CONF_BINARY_SENSORS: binary_sensors - }, config) + hass, + BINARY_SENSOR, + DOMAIN, + {CONF_NAME: name, CONF_BINARY_SENSORS: binary_sensors}, + config, + ) if sensors: discovery.load_platform( - hass, SENSOR, DOMAIN, { - CONF_NAME: name, - CONF_SENSORS: sensors, - }, config) + hass, SENSOR, DOMAIN, {CONF_NAME: name, CONF_SENSORS: sensors}, config + ) if switches: discovery.load_platform( - hass, SWITCH, DOMAIN, { - CONF_NAME: name, - CONF_SWITCHES: switches - }, config) + hass, SWITCH, DOMAIN, {CONF_NAME: name, CONF_SWITCHES: switches}, config + ) if not hass.data[DATA_AMCREST][DEVICES]: return False def have_permission(user, entity_id): - return not user or user.permissions.check_entity( - entity_id, POLICY_CONTROL) + return not user or user.permissions.check_entity(entity_id, POLICY_CONTROL) async def async_extract_from_service(call): if call.context.user_id: @@ -261,7 +272,8 @@ def setup(hass, config): if call.data.get(ATTR_ENTITY_ID) == ENTITY_MATCH_ALL: # Return all entity_ids user has permission to control. return [ - entity_id for entity_id in hass.data[DATA_AMCREST][CAMERAS] + entity_id + for entity_id in hass.data[DATA_AMCREST][CAMERAS] if have_permission(user, entity_id) ] @@ -272,9 +284,7 @@ def setup(hass, config): continue if not have_permission(user, entity_id): raise Unauthorized( - context=call.context, - entity_id=entity_id, - permission=POLICY_CONTROL + context=call.context, entity_id=entity_id, permission=POLICY_CONTROL ) entity_ids.append(entity_id) return entity_ids @@ -284,15 +294,10 @@ def setup(hass, config): for arg in CAMERA_SERVICES[call.service][2]: args.append(call.data[arg]) for entity_id in await async_extract_from_service(call): - async_dispatcher_send( - hass, - service_signal(call.service, entity_id), - *args - ) + async_dispatcher_send(hass, service_signal(call.service, entity_id), *args) for service, params in CAMERA_SERVICES.items(): - hass.services.async_register( - DOMAIN, service, async_service_handler, params[0]) + hass.services.async_register(DOMAIN, service, async_service_handler, params[0]) return True @@ -300,8 +305,15 @@ def setup(hass, config): class AmcrestDevice: """Representation of a base Amcrest discovery device.""" - def __init__(self, api, authentication, ffmpeg_arguments, - stream_source, resolution, control_light): + def __init__( + self, + api, + authentication, + ffmpeg_arguments, + stream_source, + resolution, + control_light, + ): """Initialize the entity.""" self.api = api self.authentication = authentication diff --git a/homeassistant/components/amcrest/binary_sensor.py b/homeassistant/components/amcrest/binary_sensor.py index 9489fc60d4d..f8b50d1114e 100644 --- a/homeassistant/components/amcrest/binary_sensor.py +++ b/homeassistant/components/amcrest/binary_sensor.py @@ -5,29 +5,35 @@ import logging from amcrest import AmcrestError from homeassistant.components.binary_sensor import ( - BinarySensorDevice, DEVICE_CLASS_CONNECTIVITY, DEVICE_CLASS_MOTION) + BinarySensorDevice, + DEVICE_CLASS_CONNECTIVITY, + DEVICE_CLASS_MOTION, +) from homeassistant.const import CONF_NAME, CONF_BINARY_SENSORS from homeassistant.helpers.dispatcher import async_dispatcher_connect from .const import ( - BINARY_SENSOR_SCAN_INTERVAL_SECS, DATA_AMCREST, DEVICES, SERVICE_UPDATE) + BINARY_SENSOR_SCAN_INTERVAL_SECS, + DATA_AMCREST, + DEVICES, + SERVICE_UPDATE, +) from .helpers import log_update_error, service_signal _LOGGER = logging.getLogger(__name__) SCAN_INTERVAL = timedelta(seconds=BINARY_SENSOR_SCAN_INTERVAL_SECS) -BINARY_SENSOR_MOTION_DETECTED = 'motion_detected' -BINARY_SENSOR_ONLINE = 'online' +BINARY_SENSOR_MOTION_DETECTED = "motion_detected" +BINARY_SENSOR_ONLINE = "online" # Binary sensor types are defined like: Name, device class BINARY_SENSORS = { - BINARY_SENSOR_MOTION_DETECTED: ('Motion Detected', DEVICE_CLASS_MOTION), - BINARY_SENSOR_ONLINE: ('Online', DEVICE_CLASS_CONNECTIVITY), + BINARY_SENSOR_MOTION_DETECTED: ("Motion Detected", DEVICE_CLASS_MOTION), + BINARY_SENSOR_ONLINE: ("Online", DEVICE_CLASS_CONNECTIVITY), } -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up a binary sensor for an Amcrest IP Camera.""" if discovery_info is None: return @@ -35,9 +41,12 @@ async def async_setup_platform(hass, config, async_add_entities, name = discovery_info[CONF_NAME] device = hass.data[DATA_AMCREST][DEVICES][name] async_add_entities( - [AmcrestBinarySensor(name, device, sensor_type) - for sensor_type in discovery_info[CONF_BINARY_SENSORS]], - True) + [ + AmcrestBinarySensor(name, device, sensor_type) + for sensor_type in discovery_info[CONF_BINARY_SENSORS] + ], + True, + ) class AmcrestBinarySensor(BinarySensorDevice): @@ -45,7 +54,7 @@ class AmcrestBinarySensor(BinarySensorDevice): def __init__(self, name, device, sensor_type): """Initialize entity.""" - self._name = '{} {}'.format(name, BINARY_SENSORS[sensor_type][0]) + self._name = "{} {}".format(name, BINARY_SENSORS[sensor_type][0]) self._signal_name = name self._api = device.api self._sensor_type = sensor_type @@ -82,7 +91,7 @@ class AmcrestBinarySensor(BinarySensorDevice): """Update entity.""" if not self.available: return - _LOGGER.debug('Updating %s binary sensor', self._name) + _LOGGER.debug("Updating %s binary sensor", self._name) try: if self._sensor_type == BINARY_SENSOR_MOTION_DETECTED: @@ -91,8 +100,7 @@ class AmcrestBinarySensor(BinarySensorDevice): elif self._sensor_type == BINARY_SENSOR_ONLINE: self._state = self._api.available except AmcrestError as error: - log_update_error( - _LOGGER, 'update', self.name, 'binary sensor', error) + log_update_error(_LOGGER, "update", self.name, "binary sensor", error) async def async_on_demand_update(self): """Update state.""" @@ -101,8 +109,10 @@ class AmcrestBinarySensor(BinarySensorDevice): async def async_added_to_hass(self): """Subscribe to update signal.""" self._unsub_dispatcher = async_dispatcher_connect( - self.hass, service_signal(SERVICE_UPDATE, self._signal_name), - self.async_on_demand_update) + self.hass, + service_signal(SERVICE_UPDATE, self._signal_name), + self.async_on_demand_update, + ) async def async_will_remove_from_hass(self): """Disconnect from update signal.""" diff --git a/homeassistant/components/amcrest/camera.py b/homeassistant/components/amcrest/camera.py index 685d92d5ae6..483bdb2c7cf 100644 --- a/homeassistant/components/amcrest/camera.py +++ b/homeassistant/components/amcrest/camera.py @@ -8,83 +8,85 @@ from amcrest import AmcrestError import voluptuous as vol from homeassistant.components.camera import ( - Camera, CAMERA_SERVICE_SCHEMA, SUPPORT_ON_OFF, SUPPORT_STREAM) + Camera, + CAMERA_SERVICE_SCHEMA, + SUPPORT_ON_OFF, + SUPPORT_STREAM, +) from homeassistant.components.ffmpeg import DATA_FFMPEG -from homeassistant.const import ( - CONF_NAME, STATE_ON, STATE_OFF) +from homeassistant.const import CONF_NAME, STATE_ON, STATE_OFF from homeassistant.helpers.aiohttp_client import ( - async_aiohttp_proxy_stream, async_aiohttp_proxy_web, - async_get_clientsession) + async_aiohttp_proxy_stream, + async_aiohttp_proxy_web, + async_get_clientsession, +) from homeassistant.helpers.dispatcher import async_dispatcher_connect from .const import ( - CAMERA_WEB_SESSION_TIMEOUT, CAMERAS, DATA_AMCREST, DEVICES, SERVICE_UPDATE) + CAMERA_WEB_SESSION_TIMEOUT, + CAMERAS, + DATA_AMCREST, + DEVICES, + SERVICE_UPDATE, +) from .helpers import log_update_error, service_signal _LOGGER = logging.getLogger(__name__) SCAN_INTERVAL = timedelta(seconds=15) -STREAM_SOURCE_LIST = [ - 'snapshot', - 'mjpeg', - 'rtsp', -] +STREAM_SOURCE_LIST = ["snapshot", "mjpeg", "rtsp"] -_SRV_EN_REC = 'enable_recording' -_SRV_DS_REC = 'disable_recording' -_SRV_EN_AUD = 'enable_audio' -_SRV_DS_AUD = 'disable_audio' -_SRV_EN_MOT_REC = 'enable_motion_recording' -_SRV_DS_MOT_REC = 'disable_motion_recording' -_SRV_GOTO = 'goto_preset' -_SRV_CBW = 'set_color_bw' -_SRV_TOUR_ON = 'start_tour' -_SRV_TOUR_OFF = 'stop_tour' +_SRV_EN_REC = "enable_recording" +_SRV_DS_REC = "disable_recording" +_SRV_EN_AUD = "enable_audio" +_SRV_DS_AUD = "disable_audio" +_SRV_EN_MOT_REC = "enable_motion_recording" +_SRV_DS_MOT_REC = "disable_motion_recording" +_SRV_GOTO = "goto_preset" +_SRV_CBW = "set_color_bw" +_SRV_TOUR_ON = "start_tour" +_SRV_TOUR_OFF = "stop_tour" -_ATTR_PRESET = 'preset' -_ATTR_COLOR_BW = 'color_bw' +_ATTR_PRESET = "preset" +_ATTR_COLOR_BW = "color_bw" -_CBW_COLOR = 'color' -_CBW_AUTO = 'auto' -_CBW_BW = 'bw' +_CBW_COLOR = "color" +_CBW_AUTO = "auto" +_CBW_BW = "bw" _CBW = [_CBW_COLOR, _CBW_AUTO, _CBW_BW] -_SRV_GOTO_SCHEMA = CAMERA_SERVICE_SCHEMA.extend({ - vol.Required(_ATTR_PRESET): vol.All(vol.Coerce(int), vol.Range(min=1)), -}) -_SRV_CBW_SCHEMA = CAMERA_SERVICE_SCHEMA.extend({ - vol.Required(_ATTR_COLOR_BW): vol.In(_CBW), -}) +_SRV_GOTO_SCHEMA = CAMERA_SERVICE_SCHEMA.extend( + {vol.Required(_ATTR_PRESET): vol.All(vol.Coerce(int), vol.Range(min=1))} +) +_SRV_CBW_SCHEMA = CAMERA_SERVICE_SCHEMA.extend( + {vol.Required(_ATTR_COLOR_BW): vol.In(_CBW)} +) CAMERA_SERVICES = { - _SRV_EN_REC: (CAMERA_SERVICE_SCHEMA, 'async_enable_recording', ()), - _SRV_DS_REC: (CAMERA_SERVICE_SCHEMA, 'async_disable_recording', ()), - _SRV_EN_AUD: (CAMERA_SERVICE_SCHEMA, 'async_enable_audio', ()), - _SRV_DS_AUD: (CAMERA_SERVICE_SCHEMA, 'async_disable_audio', ()), - _SRV_EN_MOT_REC: ( - CAMERA_SERVICE_SCHEMA, 'async_enable_motion_recording', ()), - _SRV_DS_MOT_REC: ( - CAMERA_SERVICE_SCHEMA, 'async_disable_motion_recording', ()), - _SRV_GOTO: (_SRV_GOTO_SCHEMA, 'async_goto_preset', (_ATTR_PRESET,)), - _SRV_CBW: (_SRV_CBW_SCHEMA, 'async_set_color_bw', (_ATTR_COLOR_BW,)), - _SRV_TOUR_ON: (CAMERA_SERVICE_SCHEMA, 'async_start_tour', ()), - _SRV_TOUR_OFF: (CAMERA_SERVICE_SCHEMA, 'async_stop_tour', ()), + _SRV_EN_REC: (CAMERA_SERVICE_SCHEMA, "async_enable_recording", ()), + _SRV_DS_REC: (CAMERA_SERVICE_SCHEMA, "async_disable_recording", ()), + _SRV_EN_AUD: (CAMERA_SERVICE_SCHEMA, "async_enable_audio", ()), + _SRV_DS_AUD: (CAMERA_SERVICE_SCHEMA, "async_disable_audio", ()), + _SRV_EN_MOT_REC: (CAMERA_SERVICE_SCHEMA, "async_enable_motion_recording", ()), + _SRV_DS_MOT_REC: (CAMERA_SERVICE_SCHEMA, "async_disable_motion_recording", ()), + _SRV_GOTO: (_SRV_GOTO_SCHEMA, "async_goto_preset", (_ATTR_PRESET,)), + _SRV_CBW: (_SRV_CBW_SCHEMA, "async_set_color_bw", (_ATTR_COLOR_BW,)), + _SRV_TOUR_ON: (CAMERA_SERVICE_SCHEMA, "async_start_tour", ()), + _SRV_TOUR_OFF: (CAMERA_SERVICE_SCHEMA, "async_stop_tour", ()), } _BOOL_TO_STATE = {True: STATE_ON, False: STATE_OFF} -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up an Amcrest IP Camera.""" if discovery_info is None: return name = discovery_info[CONF_NAME] device = hass.data[DATA_AMCREST][DEVICES][name] - async_add_entities([ - AmcrestCam(name, device, hass.data[DATA_FFMPEG])], True) + async_add_entities([AmcrestCam(name, device, hass.data[DATA_FFMPEG])], True) class AmcrestCam(Camera): @@ -118,56 +120,59 @@ class AmcrestCam(Camera): available = self.available if not available or not self.is_on: _LOGGER.warning( - 'Attempt to take snaphot when %s camera is %s', self.name, - 'offline' if not available else 'off') + "Attempt to take snaphot when %s camera is %s", + self.name, + "offline" if not available else "off", + ) return None async with self._snapshot_lock: try: # Send the request to snap a picture and return raw jpg data - response = await self.hass.async_add_executor_job( - self._api.snapshot) + response = await self.hass.async_add_executor_job(self._api.snapshot) return response.data except (AmcrestError, HTTPError) as error: - log_update_error( - _LOGGER, 'get image from', self.name, 'camera', error) + log_update_error(_LOGGER, "get image from", self.name, "camera", error) return None async def handle_async_mjpeg_stream(self, request): """Return an MJPEG stream.""" # The snapshot implementation is handled by the parent class - if self._stream_source == 'snapshot': + if self._stream_source == "snapshot": return await super().handle_async_mjpeg_stream(request) if not self.available: _LOGGER.warning( - 'Attempt to stream %s when %s camera is offline', - self._stream_source, self.name) + "Attempt to stream %s when %s camera is offline", + self._stream_source, + self.name, + ) return None - if self._stream_source == 'mjpeg': + if self._stream_source == "mjpeg": # stream an MJPEG image stream directly from the camera websession = async_get_clientsession(self.hass) streaming_url = self._api.mjpeg_url(typeno=self._resolution) stream_coro = websession.get( - streaming_url, auth=self._token, - timeout=CAMERA_WEB_SESSION_TIMEOUT) + streaming_url, auth=self._token, timeout=CAMERA_WEB_SESSION_TIMEOUT + ) - return await async_aiohttp_proxy_web( - self.hass, request, stream_coro) + return await async_aiohttp_proxy_web(self.hass, request, stream_coro) # streaming via ffmpeg from haffmpeg.camera import CameraMjpeg streaming_url = self._rtsp_url stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop) - await stream.open_camera( - streaming_url, extra_cmd=self._ffmpeg_arguments) + await stream.open_camera(streaming_url, extra_cmd=self._ffmpeg_arguments) try: stream_reader = await stream.get_reader() return await async_aiohttp_proxy_stream( - self.hass, request, stream_reader, - self._ffmpeg.ffmpeg_stream_content_type) + self.hass, + request, + stream_reader, + self._ffmpeg.ffmpeg_stream_content_type, + ) finally: await stream.close() @@ -191,10 +196,11 @@ class AmcrestCam(Camera): """Return the Amcrest-specific camera state attributes.""" attr = {} if self._audio_enabled is not None: - attr['audio'] = _BOOL_TO_STATE.get(self._audio_enabled) + attr["audio"] = _BOOL_TO_STATE.get(self._audio_enabled) if self._motion_recording_enabled is not None: - attr['motion_recording'] = _BOOL_TO_STATE.get( - self._motion_recording_enabled) + attr["motion_recording"] = _BOOL_TO_STATE.get( + self._motion_recording_enabled + ) if self._color_bw is not None: attr[_ATTR_COLOR_BW] = self._color_bw return attr @@ -249,13 +255,20 @@ class AmcrestCam(Camera): async def async_added_to_hass(self): """Subscribe to signals and add camera to list.""" for service, params in CAMERA_SERVICES.items(): - self._unsub_dispatcher.append(async_dispatcher_connect( + self._unsub_dispatcher.append( + async_dispatcher_connect( + self.hass, + service_signal(service, self.entity_id), + getattr(self, params[1]), + ) + ) + self._unsub_dispatcher.append( + async_dispatcher_connect( self.hass, - service_signal(service, self.entity_id), - getattr(self, params[1]))) - self._unsub_dispatcher.append(async_dispatcher_connect( - self.hass, service_signal(SERVICE_UPDATE, self._name), - self.async_on_demand_update)) + service_signal(SERVICE_UPDATE, self._name), + self.async_on_demand_update, + ) + ) self.hass.data[DATA_AMCREST][CAMERAS].append(self.entity_id) async def async_will_remove_from_hass(self): @@ -270,32 +283,29 @@ class AmcrestCam(Camera): if not self.available: self._update_succeeded = False return - _LOGGER.debug('Updating %s camera', self.name) + _LOGGER.debug("Updating %s camera", self.name) try: if self._brand is None: resp = self._api.vendor_information.strip() - if resp.startswith('vendor='): - self._brand = resp.split('=')[-1] + if resp.startswith("vendor="): + self._brand = resp.split("=")[-1] else: - self._brand = 'unknown' + self._brand = "unknown" if self._model is None: resp = self._api.device_type.strip() - if resp.startswith('type='): - self._model = resp.split('=')[-1] + if resp.startswith("type="): + self._model = resp.split("=")[-1] else: - self._model = 'unknown' + self._model = "unknown" self.is_streaming = self._api.video_enabled - self._is_recording = self._api.record_mode == 'Manual' - self._motion_detection_enabled = ( - self._api.is_motion_detector_on()) + self._is_recording = self._api.record_mode == "Manual" + self._motion_detection_enabled = self._api.is_motion_detector_on() self._audio_enabled = self._api.audio_enabled - self._motion_recording_enabled = ( - self._api.is_record_on_motion_detection()) + self._motion_recording_enabled = self._api.is_record_on_motion_detection() self._color_bw = _CBW[self._api.day_night_color] self._rtsp_url = self._api.rtsp_url(typeno=self._resolution) except AmcrestError as error: - log_update_error( - _LOGGER, 'get', self.name, 'camera attributes', error) + log_update_error(_LOGGER, "get", self.name, "camera attributes", error) self._update_succeeded = False else: self._update_succeeded = True @@ -338,13 +348,11 @@ class AmcrestCam(Camera): async def async_enable_motion_recording(self): """Call the job and enable motion recording.""" - await self.hass.async_add_executor_job(self._enable_motion_recording, - True) + await self.hass.async_add_executor_job(self._enable_motion_recording, True) async def async_disable_motion_recording(self): """Call the job and disable motion recording.""" - await self.hass.async_add_executor_job(self._enable_motion_recording, - False) + await self.hass.async_add_executor_job(self._enable_motion_recording, False) async def async_goto_preset(self, preset): """Call the job and move camera to preset position.""" @@ -375,8 +383,12 @@ class AmcrestCam(Camera): self._api.video_enabled = enable except AmcrestError as error: log_update_error( - _LOGGER, 'enable' if enable else 'disable', self.name, - 'camera video stream', error) + _LOGGER, + "enable" if enable else "disable", + self.name, + "camera video stream", + error, + ) else: self.is_streaming = enable self.schedule_update_ha_state() @@ -390,14 +402,17 @@ class AmcrestCam(Camera): # video stream off if recording is being turned on. if not self.is_streaming and enable: self._enable_video_stream(True) - rec_mode = {'Automatic': 0, 'Manual': 1} + rec_mode = {"Automatic": 0, "Manual": 1} try: - self._api.record_mode = rec_mode[ - 'Manual' if enable else 'Automatic'] + self._api.record_mode = rec_mode["Manual" if enable else "Automatic"] except AmcrestError as error: log_update_error( - _LOGGER, 'enable' if enable else 'disable', self.name, - 'camera recording', error) + _LOGGER, + "enable" if enable else "disable", + self.name, + "camera recording", + error, + ) else: self._is_recording = enable self.schedule_update_ha_state() @@ -408,8 +423,12 @@ class AmcrestCam(Camera): self._api.motion_detection = str(enable).lower() except AmcrestError as error: log_update_error( - _LOGGER, 'enable' if enable else 'disable', self.name, - 'camera motion detection', error) + _LOGGER, + "enable" if enable else "disable", + self.name, + "camera motion detection", + error, + ) else: self._motion_detection_enabled = enable self.schedule_update_ha_state() @@ -420,8 +439,12 @@ class AmcrestCam(Camera): self._api.audio_enabled = enable except AmcrestError as error: log_update_error( - _LOGGER, 'enable' if enable else 'disable', self.name, - 'camera audio stream', error) + _LOGGER, + "enable" if enable else "disable", + self.name, + "camera audio stream", + error, + ) else: self._audio_enabled = enable self.schedule_update_ha_state() @@ -432,12 +455,18 @@ class AmcrestCam(Camera): """Enable or disable indicator light.""" try: self._api.command( - 'configManager.cgi?action=setConfig&LightGlobal[0].Enable={}' - .format(str(enable).lower())) + "configManager.cgi?action=setConfig&LightGlobal[0].Enable={}".format( + str(enable).lower() + ) + ) except AmcrestError as error: log_update_error( - _LOGGER, 'enable' if enable else 'disable', self.name, - 'indicator light', error) + _LOGGER, + "enable" if enable else "disable", + self.name, + "indicator light", + error, + ) def _enable_motion_recording(self, enable): """Enable or disable motion recording.""" @@ -445,8 +474,12 @@ class AmcrestCam(Camera): self._api.motion_recording = str(enable).lower() except AmcrestError as error: log_update_error( - _LOGGER, 'enable' if enable else 'disable', self.name, - 'camera motion recording', error) + _LOGGER, + "enable" if enable else "disable", + self.name, + "camera motion recording", + error, + ) else: self._motion_recording_enabled = enable self.schedule_update_ha_state() @@ -454,12 +487,11 @@ class AmcrestCam(Camera): def _goto_preset(self, preset): """Move camera position and zoom to preset.""" try: - self._api.go_to_preset( - action='start', preset_point_number=preset) + self._api.go_to_preset(action="start", preset_point_number=preset) except AmcrestError as error: log_update_error( - _LOGGER, 'move', self.name, - 'camera to preset {}'.format(preset), error) + _LOGGER, "move", self.name, "camera to preset {}".format(preset), error + ) def _set_color_bw(self, cbw): """Set camera color mode.""" @@ -467,8 +499,8 @@ class AmcrestCam(Camera): self._api.day_night_color = _CBW.index(cbw) except AmcrestError as error: log_update_error( - _LOGGER, 'set', self.name, - 'camera color mode to {}'.format(cbw), error) + _LOGGER, "set", self.name, "camera color mode to {}".format(cbw), error + ) else: self._color_bw = cbw self.schedule_update_ha_state() @@ -479,5 +511,5 @@ class AmcrestCam(Camera): self._api.tour(start=start) except AmcrestError as error: log_update_error( - _LOGGER, 'start' if start else 'stop', self.name, - 'camera tour', error) + _LOGGER, "start" if start else "stop", self.name, "camera tour", error + ) diff --git a/homeassistant/components/amcrest/const.py b/homeassistant/components/amcrest/const.py index fe07659b48a..98d613634b5 100644 --- a/homeassistant/components/amcrest/const.py +++ b/homeassistant/components/amcrest/const.py @@ -1,11 +1,11 @@ """Constants for amcrest component.""" -DOMAIN = 'amcrest' +DOMAIN = "amcrest" DATA_AMCREST = DOMAIN -CAMERAS = 'cameras' -DEVICES = 'devices' +CAMERAS = "cameras" +DEVICES = "devices" BINARY_SENSOR_SCAN_INTERVAL_SECS = 5 CAMERA_WEB_SESSION_TIMEOUT = 10 SENSOR_SCAN_INTERVAL_SECS = 10 -SERVICE_UPDATE = 'update' +SERVICE_UPDATE = "update" diff --git a/homeassistant/components/amcrest/helpers.py b/homeassistant/components/amcrest/helpers.py index 69d7f5ef288..d24d6e0e707 100644 --- a/homeassistant/components/amcrest/helpers.py +++ b/homeassistant/components/amcrest/helpers.py @@ -4,14 +4,18 @@ from .const import DOMAIN def service_signal(service, ident=None): """Encode service and identifier into signal.""" - signal = '{}_{}'.format(DOMAIN, service) + signal = "{}_{}".format(DOMAIN, service) if ident: - signal += '_{}'.format(ident.replace('.', '_')) + signal += "_{}".format(ident.replace(".", "_")) return signal def log_update_error(logger, action, name, entity_type, error): """Log an update error.""" logger.error( - 'Could not %s %s %s due to error: %s', - action, name, entity_type, error.__class__.__name__) + "Could not %s %s %s due to error: %s", + action, + name, + entity_type, + error.__class__.__name__, + ) diff --git a/homeassistant/components/amcrest/sensor.py b/homeassistant/components/amcrest/sensor.py index 1788b9c62b0..b53f05273fa 100644 --- a/homeassistant/components/amcrest/sensor.py +++ b/homeassistant/components/amcrest/sensor.py @@ -8,27 +8,25 @@ from homeassistant.const import CONF_NAME, CONF_SENSORS from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity -from .const import ( - DATA_AMCREST, DEVICES, SENSOR_SCAN_INTERVAL_SECS, SERVICE_UPDATE) +from .const import DATA_AMCREST, DEVICES, SENSOR_SCAN_INTERVAL_SECS, SERVICE_UPDATE from .helpers import log_update_error, service_signal _LOGGER = logging.getLogger(__name__) SCAN_INTERVAL = timedelta(seconds=SENSOR_SCAN_INTERVAL_SECS) -SENSOR_MOTION_DETECTOR = 'motion_detector' -SENSOR_PTZ_PRESET = 'ptz_preset' -SENSOR_SDCARD = 'sdcard' +SENSOR_MOTION_DETECTOR = "motion_detector" +SENSOR_PTZ_PRESET = "ptz_preset" +SENSOR_SDCARD = "sdcard" # Sensor types are defined like: Name, units, icon SENSORS = { - SENSOR_MOTION_DETECTOR: ['Motion Detected', None, 'mdi:run'], - SENSOR_PTZ_PRESET: ['PTZ Preset', None, 'mdi:camera-iris'], - SENSOR_SDCARD: ['SD Used', '%', 'mdi:sd'], + SENSOR_MOTION_DETECTOR: ["Motion Detected", None, "mdi:run"], + SENSOR_PTZ_PRESET: ["PTZ Preset", None, "mdi:camera-iris"], + SENSOR_SDCARD: ["SD Used", "%", "mdi:sd"], } -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up a sensor for an Amcrest IP Camera.""" if discovery_info is None: return @@ -36,9 +34,12 @@ async def async_setup_platform( name = discovery_info[CONF_NAME] device = hass.data[DATA_AMCREST][DEVICES][name] async_add_entities( - [AmcrestSensor(name, device, sensor_type) - for sensor_type in discovery_info[CONF_SENSORS]], - True) + [ + AmcrestSensor(name, device, sensor_type) + for sensor_type in discovery_info[CONF_SENSORS] + ], + True, + ) class AmcrestSensor(Entity): @@ -46,7 +47,7 @@ class AmcrestSensor(Entity): def __init__(self, name, device, sensor_type): """Initialize a sensor for Amcrest camera.""" - self._name = '{} {}'.format(name, SENSORS[sensor_type][0]) + self._name = "{} {}".format(name, SENSORS[sensor_type][0]) self._signal_name = name self._api = device.api self._sensor_type = sensor_type @@ -95,7 +96,7 @@ class AmcrestSensor(Entity): try: if self._sensor_type == SENSOR_MOTION_DETECTOR: self._state = self._api.is_motion_detected - self._attrs['Record Mode'] = self._api.record_mode + self._attrs["Record Mode"] = self._api.record_mode elif self._sensor_type == SENSOR_PTZ_PRESET: self._state = self._api.ptz_presets_count @@ -103,20 +104,19 @@ class AmcrestSensor(Entity): elif self._sensor_type == SENSOR_SDCARD: storage = self._api.storage_all try: - self._attrs['Total'] = '{:.2f} {}'.format( - *storage['total']) + self._attrs["Total"] = "{:.2f} {}".format(*storage["total"]) except ValueError: - self._attrs['Total'] = '{} {}'.format(*storage['total']) + self._attrs["Total"] = "{} {}".format(*storage["total"]) try: - self._attrs['Used'] = '{:.2f} {}'.format(*storage['used']) + self._attrs["Used"] = "{:.2f} {}".format(*storage["used"]) except ValueError: - self._attrs['Used'] = '{} {}'.format(*storage['used']) + self._attrs["Used"] = "{} {}".format(*storage["used"]) try: - self._state = '{:.2f}'.format(storage['used_percent']) + self._state = "{:.2f}".format(storage["used_percent"]) except ValueError: - self._state = storage['used_percent'] + self._state = storage["used_percent"] except AmcrestError as error: - log_update_error(_LOGGER, 'update', self.name, 'sensor', error) + log_update_error(_LOGGER, "update", self.name, "sensor", error) async def async_on_demand_update(self): """Update state.""" @@ -125,8 +125,10 @@ class AmcrestSensor(Entity): async def async_added_to_hass(self): """Subscribe to update signal.""" self._unsub_dispatcher = async_dispatcher_connect( - self.hass, service_signal(SERVICE_UPDATE, self._signal_name), - self.async_on_demand_update) + self.hass, + service_signal(SERVICE_UPDATE, self._signal_name), + self.async_on_demand_update, + ) async def async_will_remove_from_hass(self): """Disconnect from update signal.""" diff --git a/homeassistant/components/amcrest/switch.py b/homeassistant/components/amcrest/switch.py index ec286b4f404..0c3390c16f9 100644 --- a/homeassistant/components/amcrest/switch.py +++ b/homeassistant/components/amcrest/switch.py @@ -12,17 +12,16 @@ from .helpers import log_update_error, service_signal _LOGGER = logging.getLogger(__name__) -MOTION_DETECTION = 'motion_detection' -MOTION_RECORDING = 'motion_recording' +MOTION_DETECTION = "motion_detection" +MOTION_RECORDING = "motion_recording" # Switch types are defined like: Name, icon SWITCHES = { - MOTION_DETECTION: ['Motion Detection', 'mdi:run-fast'], - MOTION_RECORDING: ['Motion Recording', 'mdi:record-rec'] + MOTION_DETECTION: ["Motion Detection", "mdi:run-fast"], + MOTION_RECORDING: ["Motion Recording", "mdi:record-rec"], } -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the IP Amcrest camera switch platform.""" if discovery_info is None: return @@ -30,9 +29,12 @@ async def async_setup_platform( name = discovery_info[CONF_NAME] device = hass.data[DATA_AMCREST][DEVICES][name] async_add_entities( - [AmcrestSwitch(name, device, setting) - for setting in discovery_info[CONF_SWITCHES]], - True) + [ + AmcrestSwitch(name, device, setting) + for setting in discovery_info[CONF_SWITCHES] + ], + True, + ) class AmcrestSwitch(ToggleEntity): @@ -40,7 +42,7 @@ class AmcrestSwitch(ToggleEntity): def __init__(self, name, device, setting): """Initialize the Amcrest switch.""" - self._name = '{} {}'.format(name, SWITCHES[setting][0]) + self._name = "{} {}".format(name, SWITCHES[setting][0]) self._signal_name = name self._api = device.api self._setting = setting @@ -64,11 +66,11 @@ class AmcrestSwitch(ToggleEntity): return try: if self._setting == MOTION_DETECTION: - self._api.motion_detection = 'true' + self._api.motion_detection = "true" elif self._setting == MOTION_RECORDING: - self._api.motion_recording = 'true' + self._api.motion_recording = "true" except AmcrestError as error: - log_update_error(_LOGGER, 'turn on', self.name, 'switch', error) + log_update_error(_LOGGER, "turn on", self.name, "switch", error) def turn_off(self, **kwargs): """Turn setting off.""" @@ -76,11 +78,11 @@ class AmcrestSwitch(ToggleEntity): return try: if self._setting == MOTION_DETECTION: - self._api.motion_detection = 'false' + self._api.motion_detection = "false" elif self._setting == MOTION_RECORDING: - self._api.motion_recording = 'false' + self._api.motion_recording = "false" except AmcrestError as error: - log_update_error(_LOGGER, 'turn off', self.name, 'switch', error) + log_update_error(_LOGGER, "turn off", self.name, "switch", error) @property def available(self): @@ -100,7 +102,7 @@ class AmcrestSwitch(ToggleEntity): detection = self._api.is_record_on_motion_detection() self._state = detection except AmcrestError as error: - log_update_error(_LOGGER, 'update', self.name, 'switch', error) + log_update_error(_LOGGER, "update", self.name, "switch", error) @property def icon(self): @@ -114,8 +116,10 @@ class AmcrestSwitch(ToggleEntity): async def async_added_to_hass(self): """Subscribe to update signal.""" self._unsub_dispatcher = async_dispatcher_connect( - self.hass, service_signal(SERVICE_UPDATE, self._signal_name), - self.async_on_demand_update) + self.hass, + service_signal(SERVICE_UPDATE, self._signal_name), + self.async_on_demand_update, + ) async def async_will_remove_from_hass(self): """Disconnect from update signal.""" diff --git a/homeassistant/components/ampio/air_quality.py b/homeassistant/components/ampio/air_quality.py index 339f490bae5..f55f20fc150 100644 --- a/homeassistant/components/ampio/air_quality.py +++ b/homeassistant/components/ampio/air_quality.py @@ -4,8 +4,7 @@ import logging import voluptuous as vol -from homeassistant.components.air_quality import ( - PLATFORM_SCHEMA, AirQualityEntity) +from homeassistant.components.air_quality import PLATFORM_SCHEMA, AirQualityEntity from homeassistant.const import CONF_NAME from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv @@ -13,18 +12,16 @@ from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -ATTRIBUTION = 'Data provided by Ampio' -CONF_STATION_ID = 'station_id' +ATTRIBUTION = "Data provided by Ampio" +CONF_STATION_ID = "station_id" SCAN_INTERVAL = timedelta(minutes=10) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_STATION_ID): cv.string, - vol.Optional(CONF_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_STATION_ID): cv.string, vol.Optional(CONF_NAME): cv.string} +) -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Ampio Smog air quality platform.""" from asmog import AmpioSmog diff --git a/homeassistant/components/android_ip_webcam/__init__.py b/homeassistant/components/android_ip_webcam/__init__.py index c9357c4cce0..33362bd37cc 100644 --- a/homeassistant/components/android_ip_webcam/__init__.py +++ b/homeassistant/components/android_ip_webcam/__init__.py @@ -7,140 +7,182 @@ import voluptuous as vol from homeassistant.core import callback from homeassistant.const import ( - CONF_NAME, CONF_HOST, CONF_PORT, CONF_USERNAME, CONF_PASSWORD, - CONF_SENSORS, CONF_SWITCHES, CONF_TIMEOUT, CONF_SCAN_INTERVAL, - CONF_PLATFORM) + CONF_NAME, + CONF_HOST, + CONF_PORT, + CONF_USERNAME, + CONF_PASSWORD, + CONF_SENSORS, + CONF_SWITCHES, + CONF_TIMEOUT, + CONF_SCAN_INTERVAL, + CONF_PLATFORM, +) from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import ( - async_dispatcher_send, async_dispatcher_connect) + async_dispatcher_send, + async_dispatcher_connect, +) from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.util.dt import utcnow -from homeassistant.components.mjpeg.camera import ( - CONF_MJPEG_URL, CONF_STILL_IMAGE_URL) +from homeassistant.components.mjpeg.camera import CONF_MJPEG_URL, CONF_STILL_IMAGE_URL _LOGGER = logging.getLogger(__name__) -ATTR_AUD_CONNS = 'Audio Connections' -ATTR_HOST = 'host' -ATTR_VID_CONNS = 'Video Connections' +ATTR_AUD_CONNS = "Audio Connections" +ATTR_HOST = "host" +ATTR_VID_CONNS = "Video Connections" -CONF_MOTION_SENSOR = 'motion_sensor' +CONF_MOTION_SENSOR = "motion_sensor" -DATA_IP_WEBCAM = 'android_ip_webcam' -DEFAULT_NAME = 'IP Webcam' +DATA_IP_WEBCAM = "android_ip_webcam" +DEFAULT_NAME = "IP Webcam" DEFAULT_PORT = 8080 DEFAULT_TIMEOUT = 10 -DOMAIN = 'android_ip_webcam' +DOMAIN = "android_ip_webcam" SCAN_INTERVAL = timedelta(seconds=10) -SIGNAL_UPDATE_DATA = 'android_ip_webcam_update' +SIGNAL_UPDATE_DATA = "android_ip_webcam_update" KEY_MAP = { - 'audio_connections': 'Audio Connections', - 'adet_limit': 'Audio Trigger Limit', - 'antibanding': 'Anti-banding', - 'audio_only': 'Audio Only', - 'battery_level': 'Battery Level', - 'battery_temp': 'Battery Temperature', - 'battery_voltage': 'Battery Voltage', - 'coloreffect': 'Color Effect', - 'exposure': 'Exposure Level', - 'exposure_lock': 'Exposure Lock', - 'ffc': 'Front-facing Camera', - 'flashmode': 'Flash Mode', - 'focus': 'Focus', - 'focus_homing': 'Focus Homing', - 'focus_region': 'Focus Region', - 'focusmode': 'Focus Mode', - 'gps_active': 'GPS Active', - 'idle': 'Idle', - 'ip_address': 'IPv4 Address', - 'ipv6_address': 'IPv6 Address', - 'ivideon_streaming': 'Ivideon Streaming', - 'light': 'Light Level', - 'mirror_flip': 'Mirror Flip', - 'motion': 'Motion', - 'motion_active': 'Motion Active', - 'motion_detect': 'Motion Detection', - 'motion_event': 'Motion Event', - 'motion_limit': 'Motion Limit', - 'night_vision': 'Night Vision', - 'night_vision_average': 'Night Vision Average', - 'night_vision_gain': 'Night Vision Gain', - 'orientation': 'Orientation', - 'overlay': 'Overlay', - 'photo_size': 'Photo Size', - 'pressure': 'Pressure', - 'proximity': 'Proximity', - 'quality': 'Quality', - 'scenemode': 'Scene Mode', - 'sound': 'Sound', - 'sound_event': 'Sound Event', - 'sound_timeout': 'Sound Timeout', - 'torch': 'Torch', - 'video_connections': 'Video Connections', - 'video_chunk_len': 'Video Chunk Length', - 'video_recording': 'Video Recording', - 'video_size': 'Video Size', - 'whitebalance': 'White Balance', - 'whitebalance_lock': 'White Balance Lock', - 'zoom': 'Zoom' + "audio_connections": "Audio Connections", + "adet_limit": "Audio Trigger Limit", + "antibanding": "Anti-banding", + "audio_only": "Audio Only", + "battery_level": "Battery Level", + "battery_temp": "Battery Temperature", + "battery_voltage": "Battery Voltage", + "coloreffect": "Color Effect", + "exposure": "Exposure Level", + "exposure_lock": "Exposure Lock", + "ffc": "Front-facing Camera", + "flashmode": "Flash Mode", + "focus": "Focus", + "focus_homing": "Focus Homing", + "focus_region": "Focus Region", + "focusmode": "Focus Mode", + "gps_active": "GPS Active", + "idle": "Idle", + "ip_address": "IPv4 Address", + "ipv6_address": "IPv6 Address", + "ivideon_streaming": "Ivideon Streaming", + "light": "Light Level", + "mirror_flip": "Mirror Flip", + "motion": "Motion", + "motion_active": "Motion Active", + "motion_detect": "Motion Detection", + "motion_event": "Motion Event", + "motion_limit": "Motion Limit", + "night_vision": "Night Vision", + "night_vision_average": "Night Vision Average", + "night_vision_gain": "Night Vision Gain", + "orientation": "Orientation", + "overlay": "Overlay", + "photo_size": "Photo Size", + "pressure": "Pressure", + "proximity": "Proximity", + "quality": "Quality", + "scenemode": "Scene Mode", + "sound": "Sound", + "sound_event": "Sound Event", + "sound_timeout": "Sound Timeout", + "torch": "Torch", + "video_connections": "Video Connections", + "video_chunk_len": "Video Chunk Length", + "video_recording": "Video Recording", + "video_size": "Video Size", + "whitebalance": "White Balance", + "whitebalance_lock": "White Balance Lock", + "zoom": "Zoom", } ICON_MAP = { - 'audio_connections': 'mdi:speaker', - 'battery_level': 'mdi:battery', - 'battery_temp': 'mdi:thermometer', - 'battery_voltage': 'mdi:battery-charging-100', - 'exposure_lock': 'mdi:camera', - 'ffc': 'mdi:camera-front-variant', - 'focus': 'mdi:image-filter-center-focus', - 'gps_active': 'mdi:crosshairs-gps', - 'light': 'mdi:flashlight', - 'motion': 'mdi:run', - 'night_vision': 'mdi:weather-night', - 'overlay': 'mdi:monitor', - 'pressure': 'mdi:gauge', - 'proximity': 'mdi:map-marker-radius', - 'quality': 'mdi:quality-high', - 'sound': 'mdi:speaker', - 'sound_event': 'mdi:speaker', - 'sound_timeout': 'mdi:speaker', - 'torch': 'mdi:white-balance-sunny', - 'video_chunk_len': 'mdi:video', - 'video_connections': 'mdi:eye', - 'video_recording': 'mdi:record-rec', - 'whitebalance_lock': 'mdi:white-balance-auto' + "audio_connections": "mdi:speaker", + "battery_level": "mdi:battery", + "battery_temp": "mdi:thermometer", + "battery_voltage": "mdi:battery-charging-100", + "exposure_lock": "mdi:camera", + "ffc": "mdi:camera-front-variant", + "focus": "mdi:image-filter-center-focus", + "gps_active": "mdi:crosshairs-gps", + "light": "mdi:flashlight", + "motion": "mdi:run", + "night_vision": "mdi:weather-night", + "overlay": "mdi:monitor", + "pressure": "mdi:gauge", + "proximity": "mdi:map-marker-radius", + "quality": "mdi:quality-high", + "sound": "mdi:speaker", + "sound_event": "mdi:speaker", + "sound_timeout": "mdi:speaker", + "torch": "mdi:white-balance-sunny", + "video_chunk_len": "mdi:video", + "video_connections": "mdi:eye", + "video_recording": "mdi:record-rec", + "whitebalance_lock": "mdi:white-balance-auto", } -SWITCHES = ['exposure_lock', 'ffc', 'focus', 'gps_active', - 'motion_detect', 'night_vision', 'overlay', - 'torch', 'whitebalance_lock', 'video_recording'] +SWITCHES = [ + "exposure_lock", + "ffc", + "focus", + "gps_active", + "motion_detect", + "night_vision", + "overlay", + "torch", + "whitebalance_lock", + "video_recording", +] -SENSORS = ['audio_connections', 'battery_level', 'battery_temp', - 'battery_voltage', 'light', 'motion', 'pressure', 'proximity', - 'sound', 'video_connections'] +SENSORS = [ + "audio_connections", + "battery_level", + "battery_temp", + "battery_voltage", + "light", + "motion", + "pressure", + "proximity", + "sound", + "video_connections", +] -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.All(cv.ensure_list, [vol.Schema({ - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, - vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL): - cv.time_period, - vol.Inclusive(CONF_USERNAME, 'authentication'): cv.string, - vol.Inclusive(CONF_PASSWORD, 'authentication'): cv.string, - vol.Optional(CONF_SWITCHES): - vol.All(cv.ensure_list, [vol.In(SWITCHES)]), - vol.Optional(CONF_SENSORS): - vol.All(cv.ensure_list, [vol.In(SENSORS)]), - vol.Optional(CONF_MOTION_SENSOR): cv.boolean, - })]) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.All( + cv.ensure_list, + [ + vol.Schema( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional( + CONF_TIMEOUT, default=DEFAULT_TIMEOUT + ): cv.positive_int, + vol.Optional( + CONF_SCAN_INTERVAL, default=SCAN_INTERVAL + ): cv.time_period, + vol.Inclusive(CONF_USERNAME, "authentication"): cv.string, + vol.Inclusive(CONF_PASSWORD, "authentication"): cv.string, + vol.Optional(CONF_SWITCHES): vol.All( + cv.ensure_list, [vol.In(SWITCHES)] + ), + vol.Optional(CONF_SENSORS): vol.All( + cv.ensure_list, [vol.In(SENSORS)] + ), + vol.Optional(CONF_MOTION_SENSOR): cv.boolean, + } + ) + ], + ) + }, + extra=vol.ALLOW_EXTRA, +) async def async_setup(hass, config): @@ -163,30 +205,33 @@ async def async_setup(hass, config): # Init ip webcam cam = PyDroidIPCam( - hass.loop, websession, host, cam_config[CONF_PORT], - username=username, password=password, - timeout=cam_config[CONF_TIMEOUT] + hass.loop, + websession, + host, + cam_config[CONF_PORT], + username=username, + password=password, + timeout=cam_config[CONF_TIMEOUT], ) if switches is None: - switches = [setting for setting in cam.enabled_settings - if setting in SWITCHES] + switches = [ + setting for setting in cam.enabled_settings if setting in SWITCHES + ] if sensors is None: - sensors = [sensor for sensor in cam.enabled_sensors - if sensor in SENSORS] - sensors.extend(['audio_connections', 'video_connections']) + sensors = [sensor for sensor in cam.enabled_sensors if sensor in SENSORS] + sensors.extend(["audio_connections", "video_connections"]) if motion is None: - motion = 'motion_active' in cam.enabled_sensors + motion = "motion_active" in cam.enabled_sensors async def async_update_data(now): """Update data from IP camera in SCAN_INTERVAL.""" await cam.update() async_dispatcher_send(hass, SIGNAL_UPDATE_DATA, host) - async_track_point_in_utc_time( - hass, async_update_data, utcnow() + interval) + async_track_point_in_utc_time(hass, async_update_data, utcnow() + interval) await async_update_data(None) @@ -194,42 +239,50 @@ async def async_setup(hass, config): webcams[host] = cam mjpeg_camera = { - CONF_PLATFORM: 'mjpeg', + CONF_PLATFORM: "mjpeg", CONF_MJPEG_URL: cam.mjpeg_url, CONF_STILL_IMAGE_URL: cam.image_url, CONF_NAME: name, } if username and password: - mjpeg_camera.update({ - CONF_USERNAME: username, - CONF_PASSWORD: password - }) + mjpeg_camera.update({CONF_USERNAME: username, CONF_PASSWORD: password}) - hass.async_create_task(discovery.async_load_platform( - hass, 'camera', 'mjpeg', mjpeg_camera, config)) + hass.async_create_task( + discovery.async_load_platform(hass, "camera", "mjpeg", mjpeg_camera, config) + ) if sensors: - hass.async_create_task(discovery.async_load_platform( - hass, 'sensor', DOMAIN, { - CONF_NAME: name, - CONF_HOST: host, - CONF_SENSORS: sensors, - }, config)) + hass.async_create_task( + discovery.async_load_platform( + hass, + "sensor", + DOMAIN, + {CONF_NAME: name, CONF_HOST: host, CONF_SENSORS: sensors}, + config, + ) + ) if switches: - hass.async_create_task(discovery.async_load_platform( - hass, 'switch', DOMAIN, { - CONF_NAME: name, - CONF_HOST: host, - CONF_SWITCHES: switches, - }, config)) + hass.async_create_task( + discovery.async_load_platform( + hass, + "switch", + DOMAIN, + {CONF_NAME: name, CONF_HOST: host, CONF_SWITCHES: switches}, + config, + ) + ) if motion: - hass.async_create_task(discovery.async_load_platform( - hass, 'binary_sensor', DOMAIN, { - CONF_HOST: host, - CONF_NAME: name, - }, config)) + hass.async_create_task( + discovery.async_load_platform( + hass, + "binary_sensor", + DOMAIN, + {CONF_HOST: host, CONF_NAME: name}, + config, + ) + ) tasks = [async_setup_ipcamera(conf) for conf in config[DOMAIN]] if tasks: @@ -248,6 +301,7 @@ class AndroidIPCamEntity(Entity): async def async_added_to_hass(self): """Register update dispatcher.""" + @callback def async_ipcam_update(host): """Update callback.""" @@ -255,8 +309,7 @@ class AndroidIPCamEntity(Entity): return self.async_schedule_update_ha_state(True) - async_dispatcher_connect( - self.hass, SIGNAL_UPDATE_DATA, async_ipcam_update) + async_dispatcher_connect(self.hass, SIGNAL_UPDATE_DATA, async_ipcam_update) @property def should_poll(self): @@ -275,9 +328,7 @@ class AndroidIPCamEntity(Entity): if self._ipcam.status_data is None: return state_attr - state_attr[ATTR_VID_CONNS] = \ - self._ipcam.status_data.get('video_connections') - state_attr[ATTR_AUD_CONNS] = \ - self._ipcam.status_data.get('audio_connections') + state_attr[ATTR_VID_CONNS] = self._ipcam.status_data.get("video_connections") + state_attr[ATTR_AUD_CONNS] = self._ipcam.status_data.get("audio_connections") return state_attr diff --git a/homeassistant/components/android_ip_webcam/binary_sensor.py b/homeassistant/components/android_ip_webcam/binary_sensor.py index dbe50d81862..d7bf009701d 100644 --- a/homeassistant/components/android_ip_webcam/binary_sensor.py +++ b/homeassistant/components/android_ip_webcam/binary_sensor.py @@ -4,8 +4,7 @@ from homeassistant.components.binary_sensor import BinarySensorDevice from . import CONF_HOST, CONF_NAME, DATA_IP_WEBCAM, KEY_MAP, AndroidIPCamEntity -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the IP Webcam binary sensors.""" if discovery_info is None: return @@ -14,8 +13,7 @@ async def async_setup_platform( name = discovery_info[CONF_NAME] ipcam = hass.data[DATA_IP_WEBCAM][host] - async_add_entities( - [IPWebcamBinarySensor(name, host, ipcam, 'motion_active')], True) + async_add_entities([IPWebcamBinarySensor(name, host, ipcam, "motion_active")], True) class IPWebcamBinarySensor(AndroidIPCamEntity, BinarySensorDevice): @@ -27,7 +25,7 @@ class IPWebcamBinarySensor(AndroidIPCamEntity, BinarySensorDevice): self._sensor = sensor self._mapped_name = KEY_MAP.get(self._sensor, self._sensor) - self._name = '{} {}'.format(name, self._mapped_name) + self._name = "{} {}".format(name, self._mapped_name) self._state = None self._unit = None @@ -49,4 +47,4 @@ class IPWebcamBinarySensor(AndroidIPCamEntity, BinarySensorDevice): @property def device_class(self): """Return the class of this device, from component DEVICE_CLASSES.""" - return 'motion' + return "motion" diff --git a/homeassistant/components/android_ip_webcam/sensor.py b/homeassistant/components/android_ip_webcam/sensor.py index 9748b6ba548..20f4acebca6 100644 --- a/homeassistant/components/android_ip_webcam/sensor.py +++ b/homeassistant/components/android_ip_webcam/sensor.py @@ -2,12 +2,17 @@ from homeassistant.helpers.icon import icon_for_battery_level from . import ( - CONF_HOST, CONF_NAME, CONF_SENSORS, DATA_IP_WEBCAM, ICON_MAP, KEY_MAP, - AndroidIPCamEntity) + CONF_HOST, + CONF_NAME, + CONF_SENSORS, + DATA_IP_WEBCAM, + ICON_MAP, + KEY_MAP, + AndroidIPCamEntity, +) -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the IP Webcam Sensor.""" if discovery_info is None: return @@ -34,7 +39,7 @@ class IPWebcamSensor(AndroidIPCamEntity): self._sensor = sensor self._mapped_name = KEY_MAP.get(self._sensor, self._sensor) - self._name = '{} {}'.format(name, self._mapped_name) + self._name = "{} {}".format(name, self._mapped_name) self._state = None self._unit = None @@ -55,17 +60,17 @@ class IPWebcamSensor(AndroidIPCamEntity): async def async_update(self): """Retrieve latest state.""" - if self._sensor in ('audio_connections', 'video_connections'): + if self._sensor in ("audio_connections", "video_connections"): if not self._ipcam.status_data: return self._state = self._ipcam.status_data.get(self._sensor) - self._unit = 'Connections' + self._unit = "Connections" else: self._state, self._unit = self._ipcam.export_sensor(self._sensor) @property def icon(self): """Return the icon for the sensor.""" - if self._sensor == 'battery_level' and self._state is not None: + if self._sensor == "battery_level" and self._state is not None: return icon_for_battery_level(int(self._state)) - return ICON_MAP.get(self._sensor, 'mdi:eye') + return ICON_MAP.get(self._sensor, "mdi:eye") diff --git a/homeassistant/components/android_ip_webcam/switch.py b/homeassistant/components/android_ip_webcam/switch.py index e894913f5a4..5b2f5dad5e1 100644 --- a/homeassistant/components/android_ip_webcam/switch.py +++ b/homeassistant/components/android_ip_webcam/switch.py @@ -2,12 +2,17 @@ from homeassistant.components.switch import SwitchDevice from . import ( - CONF_HOST, CONF_NAME, CONF_SWITCHES, DATA_IP_WEBCAM, ICON_MAP, KEY_MAP, - AndroidIPCamEntity) + CONF_HOST, + CONF_NAME, + CONF_SWITCHES, + DATA_IP_WEBCAM, + ICON_MAP, + KEY_MAP, + AndroidIPCamEntity, +) -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the IP Webcam switch platform.""" if discovery_info is None: return @@ -34,7 +39,7 @@ class IPWebcamSettingsSwitch(AndroidIPCamEntity, SwitchDevice): self._setting = setting self._mapped_name = KEY_MAP.get(self._setting, self._setting) - self._name = '{} {}'.format(name, self._mapped_name) + self._name = "{} {}".format(name, self._mapped_name) self._state = False @property @@ -53,11 +58,11 @@ class IPWebcamSettingsSwitch(AndroidIPCamEntity, SwitchDevice): async def async_turn_on(self, **kwargs): """Turn device on.""" - if self._setting == 'torch': + if self._setting == "torch": await self._ipcam.torch(activate=True) - elif self._setting == 'focus': + elif self._setting == "focus": await self._ipcam.focus(activate=True) - elif self._setting == 'video_recording': + elif self._setting == "video_recording": await self._ipcam.record(record=True) else: await self._ipcam.change_setting(self._setting, True) @@ -66,11 +71,11 @@ class IPWebcamSettingsSwitch(AndroidIPCamEntity, SwitchDevice): async def async_turn_off(self, **kwargs): """Turn device off.""" - if self._setting == 'torch': + if self._setting == "torch": await self._ipcam.torch(activate=False) - elif self._setting == 'focus': + elif self._setting == "focus": await self._ipcam.focus(activate=False) - elif self._setting == 'video_recording': + elif self._setting == "video_recording": await self._ipcam.record(record=False) else: await self._ipcam.change_setting(self._setting, False) @@ -80,4 +85,4 @@ class IPWebcamSettingsSwitch(AndroidIPCamEntity, SwitchDevice): @property def icon(self): """Return the icon for the switch.""" - return ICON_MAP.get(self._setting, 'mdi:flash') + return ICON_MAP.get(self._setting, "mdi:flash") diff --git a/homeassistant/components/androidtv/media_player.py b/homeassistant/components/androidtv/media_player.py index efdd32ecbc5..381f0bb7cf1 100644 --- a/homeassistant/components/androidtv/media_player.py +++ b/homeassistant/components/androidtv/media_player.py @@ -3,81 +3,113 @@ import functools import logging import voluptuous as vol -from homeassistant.components.media_player import ( - MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA from homeassistant.components.media_player.const import ( - SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, - SUPPORT_SELECT_SOURCE, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP) + SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, + SUPPORT_PLAY, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_SELECT_SOURCE, + SUPPORT_STOP, + SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, + SUPPORT_VOLUME_MUTE, + SUPPORT_VOLUME_STEP, +) from homeassistant.const import ( - ATTR_COMMAND, ATTR_ENTITY_ID, CONF_DEVICE_CLASS, CONF_HOST, CONF_NAME, - CONF_PORT, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING, - STATE_STANDBY) + ATTR_COMMAND, + ATTR_ENTITY_ID, + CONF_DEVICE_CLASS, + CONF_HOST, + CONF_NAME, + CONF_PORT, + STATE_IDLE, + STATE_OFF, + STATE_PAUSED, + STATE_PLAYING, + STATE_STANDBY, +) from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -ANDROIDTV_DOMAIN = 'androidtv' +ANDROIDTV_DOMAIN = "androidtv" _LOGGER = logging.getLogger(__name__) -SUPPORT_ANDROIDTV = SUPPORT_PAUSE | SUPPORT_PLAY | \ - SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PREVIOUS_TRACK | \ - SUPPORT_NEXT_TRACK | SUPPORT_STOP | SUPPORT_VOLUME_MUTE | \ - SUPPORT_VOLUME_STEP +SUPPORT_ANDROIDTV = ( + SUPPORT_PAUSE + | SUPPORT_PLAY + | SUPPORT_TURN_ON + | SUPPORT_TURN_OFF + | SUPPORT_PREVIOUS_TRACK + | SUPPORT_NEXT_TRACK + | SUPPORT_STOP + | SUPPORT_VOLUME_MUTE + | SUPPORT_VOLUME_STEP +) -SUPPORT_FIRETV = SUPPORT_PAUSE | SUPPORT_PLAY | \ - SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PREVIOUS_TRACK | \ - SUPPORT_NEXT_TRACK | SUPPORT_SELECT_SOURCE | SUPPORT_STOP +SUPPORT_FIRETV = ( + SUPPORT_PAUSE + | SUPPORT_PLAY + | SUPPORT_TURN_ON + | SUPPORT_TURN_OFF + | SUPPORT_PREVIOUS_TRACK + | SUPPORT_NEXT_TRACK + | SUPPORT_SELECT_SOURCE + | SUPPORT_STOP +) -CONF_ADBKEY = 'adbkey' -CONF_ADB_SERVER_IP = 'adb_server_ip' -CONF_ADB_SERVER_PORT = 'adb_server_port' -CONF_APPS = 'apps' -CONF_GET_SOURCES = 'get_sources' -CONF_TURN_ON_COMMAND = 'turn_on_command' -CONF_TURN_OFF_COMMAND = 'turn_off_command' +CONF_ADBKEY = "adbkey" +CONF_ADB_SERVER_IP = "adb_server_ip" +CONF_ADB_SERVER_PORT = "adb_server_port" +CONF_APPS = "apps" +CONF_GET_SOURCES = "get_sources" +CONF_TURN_ON_COMMAND = "turn_on_command" +CONF_TURN_OFF_COMMAND = "turn_off_command" -DEFAULT_NAME = 'Android TV' +DEFAULT_NAME = "Android TV" DEFAULT_PORT = 5555 DEFAULT_ADB_SERVER_PORT = 5037 DEFAULT_GET_SOURCES = True -DEFAULT_DEVICE_CLASS = 'auto' +DEFAULT_DEVICE_CLASS = "auto" -DEVICE_ANDROIDTV = 'androidtv' -DEVICE_FIRETV = 'firetv' +DEVICE_ANDROIDTV = "androidtv" +DEVICE_FIRETV = "firetv" DEVICE_CLASSES = [DEFAULT_DEVICE_CLASS, DEVICE_ANDROIDTV, DEVICE_FIRETV] -SERVICE_ADB_COMMAND = 'adb_command' +SERVICE_ADB_COMMAND = "adb_command" -SERVICE_ADB_COMMAND_SCHEMA = vol.Schema({ - vol.Required(ATTR_ENTITY_ID): cv.entity_ids, - vol.Required(ATTR_COMMAND): cv.string, -}) +SERVICE_ADB_COMMAND_SCHEMA = vol.Schema( + {vol.Required(ATTR_ENTITY_ID): cv.entity_ids, vol.Required(ATTR_COMMAND): cv.string} +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_DEVICE_CLASS, default=DEFAULT_DEVICE_CLASS): - vol.In(DEVICE_CLASSES), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_ADBKEY): cv.isfile, - vol.Optional(CONF_ADB_SERVER_IP): cv.string, - vol.Optional(CONF_ADB_SERVER_PORT, default=DEFAULT_ADB_SERVER_PORT): - cv.port, - vol.Optional(CONF_GET_SOURCES, default=DEFAULT_GET_SOURCES): cv.boolean, - vol.Optional(CONF_APPS, default=dict()): - vol.Schema({cv.string: cv.string}), - vol.Optional(CONF_TURN_ON_COMMAND): cv.string, - vol.Optional(CONF_TURN_OFF_COMMAND): cv.string -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_DEVICE_CLASS, default=DEFAULT_DEVICE_CLASS): vol.In( + DEVICE_CLASSES + ), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_ADBKEY): cv.isfile, + vol.Optional(CONF_ADB_SERVER_IP): cv.string, + vol.Optional(CONF_ADB_SERVER_PORT, default=DEFAULT_ADB_SERVER_PORT): cv.port, + vol.Optional(CONF_GET_SOURCES, default=DEFAULT_GET_SOURCES): cv.boolean, + vol.Optional(CONF_APPS, default=dict()): vol.Schema({cv.string: cv.string}), + vol.Optional(CONF_TURN_ON_COMMAND): cv.string, + vol.Optional(CONF_TURN_OFF_COMMAND): cv.string, + } +) # Translate from `AndroidTV` / `FireTV` reported state to HA state. -ANDROIDTV_STATES = {'off': STATE_OFF, - 'idle': STATE_IDLE, - 'standby': STATE_STANDBY, - 'playing': STATE_PLAYING, - 'paused': STATE_PAUSED} +ANDROIDTV_STATES = { + "off": STATE_OFF, + "idle": STATE_IDLE, + "standby": STATE_STANDBY, + "playing": STATE_PLAYING, + "paused": STATE_PAUSED, +} def setup_platform(hass, config, add_entities, discovery_info=None): @@ -86,14 +118,15 @@ def setup_platform(hass, config, add_entities, discovery_info=None): hass.data.setdefault(ANDROIDTV_DOMAIN, {}) - host = '{0}:{1}'.format(config[CONF_HOST], config[CONF_PORT]) + host = "{0}:{1}".format(config[CONF_HOST], config[CONF_PORT]) if CONF_ADB_SERVER_IP not in config: # Use "python-adb" (Python ADB implementation) adb_log = "using Python ADB implementation " if CONF_ADBKEY in config: - aftv = setup(host, config[CONF_ADBKEY], - device_class=config[CONF_DEVICE_CLASS]) + aftv = setup( + host, config[CONF_ADBKEY], device_class=config[CONF_DEVICE_CLASS] + ) adb_log += "with adbkey='{0}'".format(config[CONF_ADBKEY]) else: @@ -101,44 +134,52 @@ def setup_platform(hass, config, add_entities, discovery_info=None): adb_log += "without adbkey authentication" else: # Use "pure-python-adb" (communicate with ADB server) - aftv = setup(host, adb_server_ip=config[CONF_ADB_SERVER_IP], - adb_server_port=config[CONF_ADB_SERVER_PORT], - device_class=config[CONF_DEVICE_CLASS]) + aftv = setup( + host, + adb_server_ip=config[CONF_ADB_SERVER_IP], + adb_server_port=config[CONF_ADB_SERVER_PORT], + device_class=config[CONF_DEVICE_CLASS], + ) adb_log = "using ADB server at {0}:{1}".format( - config[CONF_ADB_SERVER_IP], config[CONF_ADB_SERVER_PORT]) + config[CONF_ADB_SERVER_IP], config[CONF_ADB_SERVER_PORT] + ) if not aftv.available: # Determine the name that will be used for the device in the log if CONF_NAME in config: device_name = config[CONF_NAME] elif config[CONF_DEVICE_CLASS] == DEVICE_ANDROIDTV: - device_name = 'Android TV device' + device_name = "Android TV device" elif config[CONF_DEVICE_CLASS] == DEVICE_FIRETV: - device_name = 'Fire TV device' + device_name = "Fire TV device" else: - device_name = 'Android TV / Fire TV device' + device_name = "Android TV / Fire TV device" - _LOGGER.warning("Could not connect to %s at %s %s", - device_name, host, adb_log) + _LOGGER.warning("Could not connect to %s at %s %s", device_name, host, adb_log) raise PlatformNotReady if host in hass.data[ANDROIDTV_DOMAIN]: _LOGGER.warning("Platform already setup on %s, skipping", host) else: if aftv.DEVICE_CLASS == DEVICE_ANDROIDTV: - device = AndroidTVDevice(aftv, config[CONF_NAME], - config[CONF_APPS], - config.get(CONF_TURN_ON_COMMAND), - config.get(CONF_TURN_OFF_COMMAND)) - device_name = config[CONF_NAME] if CONF_NAME in config \ - else 'Android TV' + device = AndroidTVDevice( + aftv, + config[CONF_NAME], + config[CONF_APPS], + config.get(CONF_TURN_ON_COMMAND), + config.get(CONF_TURN_OFF_COMMAND), + ) + device_name = config[CONF_NAME] if CONF_NAME in config else "Android TV" else: - device = FireTVDevice(aftv, config[CONF_NAME], config[CONF_APPS], - config[CONF_GET_SOURCES], - config.get(CONF_TURN_ON_COMMAND), - config.get(CONF_TURN_OFF_COMMAND)) - device_name = config[CONF_NAME] if CONF_NAME in config \ - else 'Fire TV' + device = FireTVDevice( + aftv, + config[CONF_NAME], + config[CONF_APPS], + config[CONF_GET_SOURCES], + config.get(CONF_TURN_ON_COMMAND), + config.get(CONF_TURN_OFF_COMMAND), + ) + device_name = config[CONF_NAME] if CONF_NAME in config else "Fire TV" add_entities([device]) _LOGGER.debug("Setup %s at %s%s", device_name, host, adb_log) @@ -151,26 +192,38 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Dispatch service calls to target entities.""" cmd = service.data.get(ATTR_COMMAND) entity_id = service.data.get(ATTR_ENTITY_ID) - target_devices = [dev for dev in hass.data[ANDROIDTV_DOMAIN].values() - if dev.entity_id in entity_id] + target_devices = [ + dev + for dev in hass.data[ANDROIDTV_DOMAIN].values() + if dev.entity_id in entity_id + ] for target_device in target_devices: output = target_device.adb_command(cmd) # log the output, if there is any if output: - _LOGGER.info("Output of command '%s' from '%s': %s", - cmd, target_device.entity_id, output) + _LOGGER.info( + "Output of command '%s' from '%s': %s", + cmd, + target_device.entity_id, + output, + ) - hass.services.register(ANDROIDTV_DOMAIN, SERVICE_ADB_COMMAND, - service_adb_command, - schema=SERVICE_ADB_COMMAND_SCHEMA) + hass.services.register( + ANDROIDTV_DOMAIN, + SERVICE_ADB_COMMAND, + service_adb_command, + schema=SERVICE_ADB_COMMAND_SCHEMA, + ) def adb_decorator(override_available=False): """Send an ADB command if the device is available and catch exceptions.""" + def _adb_decorator(func): """Wait if previous ADB commands haven't finished.""" + @functools.wraps(func) def _adb_exception_catcher(self, *args, **kwargs): # If the device is unavailable, don't do anything @@ -182,7 +235,9 @@ def adb_decorator(override_available=False): except self.exceptions as err: _LOGGER.error( "Failed to execute an ADB command. ADB connection re-" - "establishing attempt in the next update. Error: %s", err) + "establishing attempt in the next update. Error: %s", + err, + ) self._available = False # pylint: disable=protected-access return None @@ -194,8 +249,7 @@ def adb_decorator(override_available=False): class ADBDevice(MediaPlayerDevice): """Representation of an Android TV or Fire TV device.""" - def __init__(self, aftv, name, apps, turn_on_command, - turn_off_command): + def __init__(self, aftv, name, apps, turn_on_command, turn_off_command): """Initialize the Android TV / Fire TV device.""" from androidtv.constants import APPS, KEYS @@ -211,15 +265,23 @@ class ADBDevice(MediaPlayerDevice): # ADB exceptions to catch if not self.aftv.adb_server_ip: # Using "python-adb" (Python ADB implementation) - from adb.adb_protocol import (InvalidChecksumError, - InvalidCommandError, - InvalidResponseError) + from adb.adb_protocol import ( + InvalidChecksumError, + InvalidCommandError, + InvalidResponseError, + ) from adb.usb_exceptions import TcpTimeoutException - self.exceptions = (AttributeError, BrokenPipeError, TypeError, - ValueError, InvalidChecksumError, - InvalidCommandError, InvalidResponseError, - TcpTimeoutException) + self.exceptions = ( + AttributeError, + BrokenPipeError, + TypeError, + ValueError, + InvalidChecksumError, + InvalidCommandError, + InvalidResponseError, + TcpTimeoutException, + ) else: # Using "pure-python-adb" (communicate with ADB server) self.exceptions = (ConnectionResetError, RuntimeError) @@ -248,7 +310,7 @@ class ADBDevice(MediaPlayerDevice): @property def device_state_attributes(self): """Provide the last ADB command's response as an attribute.""" - return {'adb_response': self._adb_response} + return {"adb_response": self._adb_response} @property def name(self): @@ -311,12 +373,12 @@ class ADBDevice(MediaPlayerDevice): """Send an ADB command to an Android TV / Fire TV device.""" key = self._keys.get(cmd) if key: - self.aftv.adb_shell('input keyevent {}'.format(key)) + self.aftv.adb_shell("input keyevent {}".format(key)) self._adb_response = None self.schedule_update_ha_state() return - if cmd == 'GET_PROPERTIES': + if cmd == "GET_PROPERTIES": self._adb_response = str(self.aftv.get_properties_dict()) self.schedule_update_ha_state() return self._adb_response @@ -334,16 +396,14 @@ class ADBDevice(MediaPlayerDevice): class AndroidTVDevice(ADBDevice): """Representation of an Android TV device.""" - def __init__(self, aftv, name, apps, turn_on_command, - turn_off_command): + def __init__(self, aftv, name, apps, turn_on_command, turn_off_command): """Initialize the Android TV device.""" - super().__init__(aftv, name, apps, turn_on_command, - turn_off_command) + super().__init__(aftv, name, apps, turn_on_command, turn_off_command) self._device = None self._device_properties = self.aftv.device_properties self._is_volume_muted = None - self._unique_id = self._device_properties.get('serialno') + self._unique_id = self._device_properties.get("serialno") self._volume_level = None @adb_decorator(override_available=True) @@ -362,8 +422,9 @@ class AndroidTVDevice(ADBDevice): return # Get the updated state and attributes. - state, self._current_app, self._device, self._is_volume_muted, \ - self._volume_level = self.aftv.update() + state, self._current_app, self._device, self._is_volume_muted, self._volume_level = ( + self.aftv.update() + ) self._state = ANDROIDTV_STATES[state] @@ -416,11 +477,11 @@ class AndroidTVDevice(ADBDevice): class FireTVDevice(ADBDevice): """Representation of a Fire TV device.""" - def __init__(self, aftv, name, apps, get_sources, - turn_on_command, turn_off_command): + def __init__( + self, aftv, name, apps, get_sources, turn_on_command, turn_off_command + ): """Initialize the Fire TV device.""" - super().__init__(aftv, name, apps, turn_on_command, - turn_off_command) + super().__init__(aftv, name, apps, turn_on_command, turn_off_command) self._get_sources = get_sources self._running_apps = None @@ -441,8 +502,9 @@ class FireTVDevice(ADBDevice): return # Get the `state`, `current_app`, and `running_apps`. - state, self._current_app, self._running_apps = \ - self.aftv.update(self._get_sources) + state, self._current_app, self._running_apps = self.aftv.update( + self._get_sources + ) self._state = ANDROIDTV_STATES[state] @@ -474,7 +536,7 @@ class FireTVDevice(ADBDevice): opening it. """ if isinstance(source, str): - if not source.startswith('!'): + if not source.startswith("!"): self.aftv.launch_app(source) else: self.aftv.stop_app(source[1:].lstrip()) diff --git a/homeassistant/components/anel_pwrctrl/switch.py b/homeassistant/components/anel_pwrctrl/switch.py index 7552e35fe4b..6184465ef16 100644 --- a/homeassistant/components/anel_pwrctrl/switch.py +++ b/homeassistant/components/anel_pwrctrl/switch.py @@ -6,24 +6,26 @@ from datetime import timedelta import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) -from homeassistant.const import (CONF_HOST, CONF_PASSWORD, CONF_USERNAME) +from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -CONF_PORT_RECV = 'port_recv' -CONF_PORT_SEND = 'port_send' +CONF_PORT_RECV = "port_recv" +CONF_PORT_SEND = "port_send" MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=5) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_PORT_RECV): cv.port, - vol.Required(CONF_PORT_SEND): cv.port, - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_HOST): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_PORT_RECV): cv.port, + vol.Required(CONF_PORT_SEND): cv.port, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_HOST): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -38,8 +40,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): try: master = DeviceMaster( - username=username, password=password, read_port=port_send, - write_port=port_recv) + username=username, + password=password, + read_port=port_send, + write_port=port_recv, + ) master.query(ip_addr=host) except socket.error as ex: _LOGGER.error("Unable to discover PwrCtrl device: %s", str(ex)) @@ -49,8 +54,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for device in master.devices.values(): parent_device = PwrCtrlDevice(device) devices.extend( - PwrCtrlSwitch(switch, parent_device) - for switch in device.switches.values() + PwrCtrlSwitch(switch, parent_device) for switch in device.switches.values() ) add_entities(devices) @@ -72,9 +76,8 @@ class PwrCtrlSwitch(SwitchDevice): @property def unique_id(self): """Return the unique ID of the device.""" - return '{device}-{switch_idx}'.format( - device=self._port.device.host, - switch_idx=self._port.get_index() + return "{device}-{switch_idx}".format( + device=self._port.device.host, switch_idx=self._port.get_index() ) @property diff --git a/homeassistant/components/anthemav/media_player.py b/homeassistant/components/anthemav/media_player.py index 13c0ef338bc..a033470e5c9 100644 --- a/homeassistant/components/anthemav/media_player.py +++ b/homeassistant/components/anthemav/media_player.py @@ -3,34 +3,48 @@ import logging import voluptuous as vol -from homeassistant.components.media_player import ( - MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA from homeassistant.components.media_player.const import ( - SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET) + SUPPORT_SELECT_SOURCE, + SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, + SUPPORT_VOLUME_MUTE, + SUPPORT_VOLUME_SET, +) from homeassistant.const import ( - CONF_HOST, CONF_NAME, CONF_PORT, EVENT_HOMEASSISTANT_STOP, STATE_OFF, - STATE_ON) + CONF_HOST, + CONF_NAME, + CONF_PORT, + EVENT_HOMEASSISTANT_STOP, + STATE_OFF, + STATE_ON, +) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DOMAIN = 'anthemav' +DOMAIN = "anthemav" DEFAULT_PORT = 14999 -SUPPORT_ANTHEMAV = SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ - SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE +SUPPORT_ANTHEMAV = ( + SUPPORT_VOLUME_SET + | SUPPORT_VOLUME_MUTE + | SUPPORT_TURN_ON + | SUPPORT_TURN_OFF + | SUPPORT_SELECT_SOURCE +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + } +) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up our socket to the AVR.""" import anthemav @@ -47,8 +61,8 @@ async def async_setup_platform(hass, config, async_add_entities, hass.async_create_task(device.async_update_ha_state()) avr = await anthemav.Connection.create( - host=host, port=port, - update_callback=async_anthemav_update_callback) + host=host, port=port, update_callback=async_anthemav_update_callback + ) device = AnthemAVR(avr, name) @@ -84,12 +98,12 @@ class AnthemAVR(MediaPlayerDevice): @property def name(self): """Return name of device.""" - return self._name or self._lookup('model') + return self._name or self._lookup("model") @property def state(self): """Return state of power on/off.""" - pwrstate = self._lookup('power') + pwrstate = self._lookup("power") if pwrstate is True: return STATE_ON @@ -100,64 +114,64 @@ class AnthemAVR(MediaPlayerDevice): @property def is_volume_muted(self): """Return boolean reflecting mute state on device.""" - return self._lookup('mute', False) + return self._lookup("mute", False) @property def volume_level(self): """Return volume level from 0 to 1.""" - return self._lookup('volume_as_percentage', 0.0) + return self._lookup("volume_as_percentage", 0.0) @property def media_title(self): """Return current input name (closest we have to media title).""" - return self._lookup('input_name', 'No Source') + return self._lookup("input_name", "No Source") @property def app_name(self): """Return details about current video and audio stream.""" - return self._lookup('video_input_resolution_text', '') + ' ' \ - + self._lookup('audio_input_name', '') + return ( + self._lookup("video_input_resolution_text", "") + + " " + + self._lookup("audio_input_name", "") + ) @property def source(self): """Return currently selected input.""" - return self._lookup('input_name', "Unknown") + return self._lookup("input_name", "Unknown") @property def source_list(self): """Return all active, configured inputs.""" - return self._lookup('input_list', ["Unknown"]) + return self._lookup("input_list", ["Unknown"]) async def async_select_source(self, source): """Change AVR to the designated source (by name).""" - self._update_avr('input_name', source) + self._update_avr("input_name", source) async def async_turn_off(self): """Turn AVR power off.""" - self._update_avr('power', False) + self._update_avr("power", False) async def async_turn_on(self): """Turn AVR power on.""" - self._update_avr('power', True) + self._update_avr("power", True) async def async_set_volume_level(self, volume): """Set AVR volume (0 to 1).""" - self._update_avr('volume_as_percentage', volume) + self._update_avr("volume_as_percentage", volume) async def async_mute_volume(self, mute): """Engage AVR mute.""" - self._update_avr('mute', mute) + self._update_avr("mute", mute) def _update_avr(self, propname, value): """Update a property in the AVR.""" - _LOGGER.info( - "Sending command to AVR: set %s to %s", propname, str(value)) + _LOGGER.info("Sending command to AVR: set %s to %s", propname, str(value)) setattr(self.avr.protocol, propname, value) @property def dump_avrdata(self): """Return state of avr object for debugging forensics.""" attrs = vars(self) - return( - 'dump_avrdata: ' - + ', '.join('%s: %s' % item for item in attrs.items())) + return "dump_avrdata: " + ", ".join("%s: %s" % item for item in attrs.items()) diff --git a/homeassistant/components/apache_kafka/__init__.py b/homeassistant/components/apache_kafka/__init__.py index e8617eaf317..caf96c61fb8 100644 --- a/homeassistant/components/apache_kafka/__init__.py +++ b/homeassistant/components/apache_kafka/__init__.py @@ -7,26 +7,36 @@ from aiokafka import AIOKafkaProducer import voluptuous as vol from homeassistant.const import ( - CONF_IP_ADDRESS, CONF_PORT, EVENT_HOMEASSISTANT_STOP, EVENT_STATE_CHANGED, - STATE_UNAVAILABLE, STATE_UNKNOWN) + CONF_IP_ADDRESS, + CONF_PORT, + EVENT_HOMEASSISTANT_STOP, + EVENT_STATE_CHANGED, + STATE_UNAVAILABLE, + STATE_UNKNOWN, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entityfilter import FILTER_SCHEMA _LOGGER = logging.getLogger(__name__) -DOMAIN = 'apache_kafka' +DOMAIN = "apache_kafka" -CONF_FILTER = 'filter' -CONF_TOPIC = 'topic' +CONF_FILTER = "filter" +CONF_TOPIC = "topic" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_IP_ADDRESS): cv.string, - vol.Required(CONF_PORT): cv.port, - vol.Required(CONF_TOPIC): cv.string, - vol.Optional(CONF_FILTER, default={}): FILTER_SCHEMA, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_IP_ADDRESS): cv.string, + vol.Required(CONF_PORT): cv.port, + vol.Required(CONF_TOPIC): cv.string, + vol.Optional(CONF_FILTER, default={}): FILTER_SCHEMA, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) async def async_setup(hass, config): @@ -38,7 +48,8 @@ async def async_setup(hass, config): conf[CONF_IP_ADDRESS], conf[CONF_PORT], conf[CONF_TOPIC], - conf[CONF_FILTER]) + conf[CONF_FILTER], + ) hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, kafka.shutdown()) @@ -63,13 +74,7 @@ class DateTimeJSONEncoder(json.JSONEncoder): class KafkaManager: """Define a manager to buffer events to Kafka.""" - def __init__( - self, - hass, - ip_address, - port, - topic, - entities_filter): + def __init__(self, hass, ip_address, port, topic, entities_filter): """Initialize.""" self._encoder = DateTimeJSONEncoder() self._entities_filter = entities_filter @@ -83,16 +88,17 @@ class KafkaManager: def _encode_event(self, event): """Translate events into a binary JSON payload.""" - state = event.data.get('new_state') - if (state is None - or state.state in (STATE_UNKNOWN, '', STATE_UNAVAILABLE) - or not self._entities_filter(state.entity_id)): + state = event.data.get("new_state") + if ( + state is None + or state.state in (STATE_UNKNOWN, "", STATE_UNAVAILABLE) + or not self._entities_filter(state.entity_id) + ): return - return json.dumps( - obj=state.as_dict(), - default=self._encoder.encode - ).encode('utf-8') + return json.dumps(obj=state.as_dict(), default=self._encoder.encode).encode( + "utf-8" + ) async def start(self): """Start the Kafka manager.""" diff --git a/homeassistant/components/apcupsd/__init__.py b/homeassistant/components/apcupsd/__init__.py index d4649db0203..512bd01b72a 100644 --- a/homeassistant/components/apcupsd/__init__.py +++ b/homeassistant/components/apcupsd/__init__.py @@ -4,31 +4,36 @@ from datetime import timedelta import voluptuous as vol -from homeassistant.const import (CONF_HOST, CONF_PORT) +from homeassistant.const import CONF_HOST, CONF_PORT import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -CONF_TYPE = 'type' +CONF_TYPE = "type" DATA = None -DEFAULT_HOST = 'localhost' +DEFAULT_HOST = "localhost" DEFAULT_PORT = 3551 -DOMAIN = 'apcupsd' +DOMAIN = "apcupsd" -KEY_STATUS = 'STATUS' +KEY_STATUS = "STATUS" MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) -VALUE_ONLINE = 'ONLINE' +VALUE_ONLINE = "ONLINE" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): @@ -60,6 +65,7 @@ class APCUPSdData: def __init__(self, host, port): """Initialize the data object.""" from apcaccess import status + self._host = host self._port = port self._status = None diff --git a/homeassistant/components/apcupsd/binary_sensor.py b/homeassistant/components/apcupsd/binary_sensor.py index 367b3c2b9b5..62f0c90a447 100644 --- a/homeassistant/components/apcupsd/binary_sensor.py +++ b/homeassistant/components/apcupsd/binary_sensor.py @@ -1,16 +1,15 @@ """Support for tracking the online status of a UPS.""" import voluptuous as vol -from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) +from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv from homeassistant.components import apcupsd -DEFAULT_NAME = 'UPS Online Status' -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +DEFAULT_NAME = "UPS Online Status" +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string} +) def setup_platform(hass, config, add_entities, discovery_info=None): diff --git a/homeassistant/components/apcupsd/sensor.py b/homeassistant/components/apcupsd/sensor.py index ae1ad10223d..837e6e45c6c 100644 --- a/homeassistant/components/apcupsd/sensor.py +++ b/homeassistant/components/apcupsd/sensor.py @@ -6,101 +6,102 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA import homeassistant.helpers.config_validation as cv from homeassistant.components import apcupsd -from homeassistant.const import (TEMP_CELSIUS, CONF_RESOURCES, POWER_WATT) +from homeassistant.const import TEMP_CELSIUS, CONF_RESOURCES, POWER_WATT from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -SENSOR_PREFIX = 'UPS ' +SENSOR_PREFIX = "UPS " SENSOR_TYPES = { - 'alarmdel': ['Alarm Delay', '', 'mdi:alarm'], - 'ambtemp': ['Ambient Temperature', '', 'mdi:thermometer'], - 'apc': ['Status Data', '', 'mdi:information-outline'], - 'apcmodel': ['Model', '', 'mdi:information-outline'], - 'badbatts': ['Bad Batteries', '', 'mdi:information-outline'], - 'battdate': ['Battery Replaced', '', 'mdi:calendar-clock'], - 'battstat': ['Battery Status', '', 'mdi:information-outline'], - 'battv': ['Battery Voltage', 'V', 'mdi:flash'], - 'bcharge': ['Battery', '%', 'mdi:battery'], - 'cable': ['Cable Type', '', 'mdi:ethernet-cable'], - 'cumonbatt': ['Total Time on Battery', '', 'mdi:timer'], - 'date': ['Status Date', '', 'mdi:calendar-clock'], - 'dipsw': ['Dip Switch Settings', '', 'mdi:information-outline'], - 'dlowbatt': ['Low Battery Signal', '', 'mdi:clock-alert'], - 'driver': ['Driver', '', 'mdi:information-outline'], - 'dshutd': ['Shutdown Delay', '', 'mdi:timer'], - 'dwake': ['Wake Delay', '', 'mdi:timer'], - 'endapc': ['Date and Time', '', 'mdi:calendar-clock'], - 'extbatts': ['External Batteries', '', 'mdi:information-outline'], - 'firmware': ['Firmware Version', '', 'mdi:information-outline'], - 'hitrans': ['Transfer High', 'V', 'mdi:flash'], - 'hostname': ['Hostname', '', 'mdi:information-outline'], - 'humidity': ['Ambient Humidity', '%', 'mdi:water-percent'], - 'itemp': ['Internal Temperature', TEMP_CELSIUS, 'mdi:thermometer'], - 'lastxfer': ['Last Transfer', '', 'mdi:transfer'], - 'linefail': ['Input Voltage Status', '', 'mdi:information-outline'], - 'linefreq': ['Line Frequency', 'Hz', 'mdi:information-outline'], - 'linev': ['Input Voltage', 'V', 'mdi:flash'], - 'loadpct': ['Load', '%', 'mdi:gauge'], - 'loadapnt': ['Load Apparent Power', '%', 'mdi:gauge'], - 'lotrans': ['Transfer Low', 'V', 'mdi:flash'], - 'mandate': ['Manufacture Date', '', 'mdi:calendar'], - 'masterupd': ['Master Update', '', 'mdi:information-outline'], - 'maxlinev': ['Input Voltage High', 'V', 'mdi:flash'], - 'maxtime': ['Battery Timeout', '', 'mdi:timer-off'], - 'mbattchg': ['Battery Shutdown', '%', 'mdi:battery-alert'], - 'minlinev': ['Input Voltage Low', 'V', 'mdi:flash'], - 'mintimel': ['Shutdown Time', '', 'mdi:timer'], - 'model': ['Model', '', 'mdi:information-outline'], - 'nombattv': ['Battery Nominal Voltage', 'V', 'mdi:flash'], - 'nominv': ['Nominal Input Voltage', 'V', 'mdi:flash'], - 'nomoutv': ['Nominal Output Voltage', 'V', 'mdi:flash'], - 'nompower': ['Nominal Output Power', POWER_WATT, 'mdi:flash'], - 'nomapnt': ['Nominal Apparent Power', 'VA', 'mdi:flash'], - 'numxfers': ['Transfer Count', '', 'mdi:counter'], - 'outcurnt': ['Output Current', 'A', 'mdi:flash'], - 'outputv': ['Output Voltage', 'V', 'mdi:flash'], - 'reg1': ['Register 1 Fault', '', 'mdi:information-outline'], - 'reg2': ['Register 2 Fault', '', 'mdi:information-outline'], - 'reg3': ['Register 3 Fault', '', 'mdi:information-outline'], - 'retpct': ['Restore Requirement', '%', 'mdi:battery-alert'], - 'selftest': ['Last Self Test', '', 'mdi:calendar-clock'], - 'sense': ['Sensitivity', '', 'mdi:information-outline'], - 'serialno': ['Serial Number', '', 'mdi:information-outline'], - 'starttime': ['Startup Time', '', 'mdi:calendar-clock'], - 'statflag': ['Status Flag', '', 'mdi:information-outline'], - 'status': ['Status', '', 'mdi:information-outline'], - 'stesti': ['Self Test Interval', '', 'mdi:information-outline'], - 'timeleft': ['Time Left', '', 'mdi:clock-alert'], - 'tonbatt': ['Time on Battery', '', 'mdi:timer'], - 'upsmode': ['Mode', '', 'mdi:information-outline'], - 'upsname': ['Name', '', 'mdi:information-outline'], - 'version': ['Daemon Info', '', 'mdi:information-outline'], - 'xoffbat': ['Transfer from Battery', '', 'mdi:transfer'], - 'xoffbatt': ['Transfer from Battery', '', 'mdi:transfer'], - 'xonbatt': ['Transfer to Battery', '', 'mdi:transfer'], + "alarmdel": ["Alarm Delay", "", "mdi:alarm"], + "ambtemp": ["Ambient Temperature", "", "mdi:thermometer"], + "apc": ["Status Data", "", "mdi:information-outline"], + "apcmodel": ["Model", "", "mdi:information-outline"], + "badbatts": ["Bad Batteries", "", "mdi:information-outline"], + "battdate": ["Battery Replaced", "", "mdi:calendar-clock"], + "battstat": ["Battery Status", "", "mdi:information-outline"], + "battv": ["Battery Voltage", "V", "mdi:flash"], + "bcharge": ["Battery", "%", "mdi:battery"], + "cable": ["Cable Type", "", "mdi:ethernet-cable"], + "cumonbatt": ["Total Time on Battery", "", "mdi:timer"], + "date": ["Status Date", "", "mdi:calendar-clock"], + "dipsw": ["Dip Switch Settings", "", "mdi:information-outline"], + "dlowbatt": ["Low Battery Signal", "", "mdi:clock-alert"], + "driver": ["Driver", "", "mdi:information-outline"], + "dshutd": ["Shutdown Delay", "", "mdi:timer"], + "dwake": ["Wake Delay", "", "mdi:timer"], + "endapc": ["Date and Time", "", "mdi:calendar-clock"], + "extbatts": ["External Batteries", "", "mdi:information-outline"], + "firmware": ["Firmware Version", "", "mdi:information-outline"], + "hitrans": ["Transfer High", "V", "mdi:flash"], + "hostname": ["Hostname", "", "mdi:information-outline"], + "humidity": ["Ambient Humidity", "%", "mdi:water-percent"], + "itemp": ["Internal Temperature", TEMP_CELSIUS, "mdi:thermometer"], + "lastxfer": ["Last Transfer", "", "mdi:transfer"], + "linefail": ["Input Voltage Status", "", "mdi:information-outline"], + "linefreq": ["Line Frequency", "Hz", "mdi:information-outline"], + "linev": ["Input Voltage", "V", "mdi:flash"], + "loadpct": ["Load", "%", "mdi:gauge"], + "loadapnt": ["Load Apparent Power", "%", "mdi:gauge"], + "lotrans": ["Transfer Low", "V", "mdi:flash"], + "mandate": ["Manufacture Date", "", "mdi:calendar"], + "masterupd": ["Master Update", "", "mdi:information-outline"], + "maxlinev": ["Input Voltage High", "V", "mdi:flash"], + "maxtime": ["Battery Timeout", "", "mdi:timer-off"], + "mbattchg": ["Battery Shutdown", "%", "mdi:battery-alert"], + "minlinev": ["Input Voltage Low", "V", "mdi:flash"], + "mintimel": ["Shutdown Time", "", "mdi:timer"], + "model": ["Model", "", "mdi:information-outline"], + "nombattv": ["Battery Nominal Voltage", "V", "mdi:flash"], + "nominv": ["Nominal Input Voltage", "V", "mdi:flash"], + "nomoutv": ["Nominal Output Voltage", "V", "mdi:flash"], + "nompower": ["Nominal Output Power", POWER_WATT, "mdi:flash"], + "nomapnt": ["Nominal Apparent Power", "VA", "mdi:flash"], + "numxfers": ["Transfer Count", "", "mdi:counter"], + "outcurnt": ["Output Current", "A", "mdi:flash"], + "outputv": ["Output Voltage", "V", "mdi:flash"], + "reg1": ["Register 1 Fault", "", "mdi:information-outline"], + "reg2": ["Register 2 Fault", "", "mdi:information-outline"], + "reg3": ["Register 3 Fault", "", "mdi:information-outline"], + "retpct": ["Restore Requirement", "%", "mdi:battery-alert"], + "selftest": ["Last Self Test", "", "mdi:calendar-clock"], + "sense": ["Sensitivity", "", "mdi:information-outline"], + "serialno": ["Serial Number", "", "mdi:information-outline"], + "starttime": ["Startup Time", "", "mdi:calendar-clock"], + "statflag": ["Status Flag", "", "mdi:information-outline"], + "status": ["Status", "", "mdi:information-outline"], + "stesti": ["Self Test Interval", "", "mdi:information-outline"], + "timeleft": ["Time Left", "", "mdi:clock-alert"], + "tonbatt": ["Time on Battery", "", "mdi:timer"], + "upsmode": ["Mode", "", "mdi:information-outline"], + "upsname": ["Name", "", "mdi:information-outline"], + "version": ["Daemon Info", "", "mdi:information-outline"], + "xoffbat": ["Transfer from Battery", "", "mdi:transfer"], + "xoffbatt": ["Transfer from Battery", "", "mdi:transfer"], + "xonbatt": ["Transfer to Battery", "", "mdi:transfer"], } -SPECIFIC_UNITS = { - 'ITEMP': TEMP_CELSIUS -} +SPECIFIC_UNITS = {"ITEMP": TEMP_CELSIUS} INFERRED_UNITS = { - ' Minutes': 'min', - ' Seconds': 'sec', - ' Percent': '%', - ' Volts': 'V', - ' Ampere': 'A', - ' Volt-Ampere': 'VA', - ' Watts': POWER_WATT, - ' Hz': 'Hz', - ' C': TEMP_CELSIUS, - ' Percent Load Capacity': '%', + " Minutes": "min", + " Seconds": "sec", + " Percent": "%", + " Volts": "V", + " Ampere": "A", + " Volt-Ampere": "VA", + " Watts": POWER_WATT, + " Hz": "Hz", + " C": TEMP_CELSIUS, + " Percent Load Capacity": "%", } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_RESOURCES, default=[]): - vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_RESOURCES, default=[]): vol.All( + cv.ensure_list, [vol.In(SENSOR_TYPES)] + ) + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -112,12 +113,16 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if sensor_type not in SENSOR_TYPES: SENSOR_TYPES[sensor_type] = [ - sensor_type.title(), '', 'mdi:information-outline'] + sensor_type.title(), + "", + "mdi:information-outline", + ] if sensor_type.upper() not in apcupsd.DATA.status: _LOGGER.warning( "Sensor type: %s does not appear in the APCUPSd status output", - sensor_type) + sensor_type, + ) entities.append(APCUPSdSensor(apcupsd.DATA, sensor_type)) @@ -131,9 +136,10 @@ def infer_unit(value): pair. Else return the original value and None as the unit. """ from apcaccess.status import ALL_UNITS + for unit in ALL_UNITS: if value.endswith(unit): - return value[:-len(unit)], INFERRED_UNITS.get(unit, unit.strip()) + return value[: -len(unit)], INFERRED_UNITS.get(unit, unit.strip()) return value, None @@ -178,4 +184,5 @@ class APCUPSdSensor(Entity): self._inferred_unit = None else: self._state, self._inferred_unit = infer_unit( - self._data.status[self.type.upper()]) + self._data.status[self.type.upper()] + ) diff --git a/homeassistant/components/api/__init__.py b/homeassistant/components/api/__init__.py index feea4f21c9c..ee991535104 100644 --- a/homeassistant/components/api/__init__.py +++ b/homeassistant/components/api/__init__.py @@ -11,15 +11,28 @@ import voluptuous as vol from homeassistant.bootstrap import DATA_LOGGING from homeassistant.components.http import HomeAssistantView from homeassistant.const import ( - EVENT_HOMEASSISTANT_STOP, EVENT_TIME_CHANGED, HTTP_BAD_REQUEST, - HTTP_CREATED, HTTP_NOT_FOUND, MATCH_ALL, URL_API, URL_API_COMPONENTS, - URL_API_CONFIG, URL_API_DISCOVERY_INFO, URL_API_ERROR_LOG, URL_API_EVENTS, - URL_API_SERVICES, URL_API_STATES, URL_API_STATES_ENTITY, URL_API_STREAM, - URL_API_TEMPLATE, __version__) + EVENT_HOMEASSISTANT_STOP, + EVENT_TIME_CHANGED, + HTTP_BAD_REQUEST, + HTTP_CREATED, + HTTP_NOT_FOUND, + MATCH_ALL, + URL_API, + URL_API_COMPONENTS, + URL_API_CONFIG, + URL_API_DISCOVERY_INFO, + URL_API_ERROR_LOG, + URL_API_EVENTS, + URL_API_SERVICES, + URL_API_STATES, + URL_API_STATES_ENTITY, + URL_API_STREAM, + URL_API_TEMPLATE, + __version__, +) import homeassistant.core as ha from homeassistant.auth.permissions.const import POLICY_READ -from homeassistant.exceptions import ( - TemplateError, Unauthorized, ServiceNotFound) +from homeassistant.exceptions import TemplateError, Unauthorized, ServiceNotFound from homeassistant.helpers import template from homeassistant.helpers.service import async_get_all_descriptions from homeassistant.helpers.state import AsyncTrackStates @@ -27,13 +40,13 @@ from homeassistant.helpers.json import JSONEncoder _LOGGER = logging.getLogger(__name__) -ATTR_BASE_URL = 'base_url' -ATTR_LOCATION_NAME = 'location_name' -ATTR_REQUIRES_API_PASSWORD = 'requires_api_password' -ATTR_VERSION = 'version' +ATTR_BASE_URL = "base_url" +ATTR_LOCATION_NAME = "location_name" +ATTR_REQUIRES_API_PASSWORD = "requires_api_password" +ATTR_VERSION = "version" -DOMAIN = 'api' -STREAM_PING_PAYLOAD = 'ping' +DOMAIN = "api" +STREAM_PING_PAYLOAD = "ping" STREAM_PING_INTERVAL = 50 # seconds @@ -62,7 +75,7 @@ class APIStatusView(HomeAssistantView): """View to handle Status requests.""" url = URL_API - name = 'api:status' + name = "api:status" @ha.callback def get(self, request): @@ -74,19 +87,19 @@ class APIEventStream(HomeAssistantView): """View to handle EventStream requests.""" url = URL_API_STREAM - name = 'api:stream' + name = "api:stream" async def get(self, request): """Provide a streaming interface for the event bus.""" - if not request['hass_user'].is_admin: + if not request["hass_user"].is_admin: raise Unauthorized() - hass = request.app['hass'] + hass = request.app["hass"] stop_obj = object() to_write = asyncio.Queue() - restrict = request.query.get('restrict') + restrict = request.query.get("restrict") if restrict: - restrict = restrict.split(',') + [EVENT_HOMEASSISTANT_STOP] + restrict = restrict.split(",") + [EVENT_HOMEASSISTANT_STOP] async def forward_events(event): """Forward events to the open request.""" @@ -106,7 +119,7 @@ class APIEventStream(HomeAssistantView): await to_write.put(data) response = web.StreamResponse() - response.content_type = 'text/event-stream' + response.content_type = "text/event-stream" await response.prepare(request) unsub_stream = hass.bus.async_listen(MATCH_ALL, forward_events) @@ -126,9 +139,8 @@ class APIEventStream(HomeAssistantView): break msg = "data: {}\n\n".format(payload) - _LOGGER.debug( - "STREAM %s WRITING %s", id(stop_obj), msg.strip()) - await response.write(msg.encode('UTF-8')) + _LOGGER.debug("STREAM %s WRITING %s", id(stop_obj), msg.strip()) + await response.write(msg.encode("UTF-8")) except asyncio.TimeoutError: await to_write.put(STREAM_PING_PAYLOAD) @@ -146,12 +158,12 @@ class APIConfigView(HomeAssistantView): """View to handle Configuration requests.""" url = URL_API_CONFIG - name = 'api:config' + name = "api:config" @ha.callback def get(self, request): """Get current configuration.""" - return self.json(request.app['hass'].config.as_dict()) + return self.json(request.app["hass"].config.as_dict()) class APIDiscoveryView(HomeAssistantView): @@ -159,19 +171,21 @@ class APIDiscoveryView(HomeAssistantView): requires_auth = False url = URL_API_DISCOVERY_INFO - name = 'api:discovery' + name = "api:discovery" @ha.callback def get(self, request): """Get discovery information.""" - hass = request.app['hass'] - return self.json({ - ATTR_BASE_URL: hass.config.api.base_url, - ATTR_LOCATION_NAME: hass.config.location_name, - # always needs authentication - ATTR_REQUIRES_API_PASSWORD: True, - ATTR_VERSION: __version__, - }) + hass = request.app["hass"] + return self.json( + { + ATTR_BASE_URL: hass.config.api.base_url, + ATTR_LOCATION_NAME: hass.config.location_name, + # always needs authentication + ATTR_REQUIRES_API_PASSWORD: True, + ATTR_VERSION: __version__, + } + ) class APIStatesView(HomeAssistantView): @@ -183,11 +197,12 @@ class APIStatesView(HomeAssistantView): @ha.callback def get(self, request): """Get current states.""" - user = request['hass_user'] + user = request["hass_user"] entity_perm = user.permissions.check_entity states = [ - state for state in request.app['hass'].states.async_all() - if entity_perm(state.entity_id, 'read') + state + for state in request.app["hass"].states.async_all() + if entity_perm(state.entity_id, "read") ] return self.json(states) @@ -195,60 +210,60 @@ class APIStatesView(HomeAssistantView): class APIEntityStateView(HomeAssistantView): """View to handle EntityState requests.""" - url = '/api/states/{entity_id}' - name = 'api:entity-state' + url = "/api/states/{entity_id}" + name = "api:entity-state" @ha.callback def get(self, request, entity_id): """Retrieve state of entity.""" - user = request['hass_user'] + user = request["hass_user"] if not user.permissions.check_entity(entity_id, POLICY_READ): raise Unauthorized(entity_id=entity_id) - state = request.app['hass'].states.get(entity_id) + state = request.app["hass"].states.get(entity_id) if state: return self.json(state) return self.json_message("Entity not found.", HTTP_NOT_FOUND) async def post(self, request, entity_id): """Update state of entity.""" - if not request['hass_user'].is_admin: + if not request["hass_user"].is_admin: raise Unauthorized(entity_id=entity_id) - hass = request.app['hass'] + hass = request.app["hass"] try: data = await request.json() except ValueError: - return self.json_message( - "Invalid JSON specified.", HTTP_BAD_REQUEST) + return self.json_message("Invalid JSON specified.", HTTP_BAD_REQUEST) - new_state = data.get('state') + new_state = data.get("state") if new_state is None: return self.json_message("No state specified.", HTTP_BAD_REQUEST) - attributes = data.get('attributes') - force_update = data.get('force_update', False) + attributes = data.get("attributes") + force_update = data.get("force_update", False) is_new_state = hass.states.get(entity_id) is None # Write state - hass.states.async_set(entity_id, new_state, attributes, force_update, - self.context(request)) + hass.states.async_set( + entity_id, new_state, attributes, force_update, self.context(request) + ) # Read the state back for our response status_code = HTTP_CREATED if is_new_state else 200 resp = self.json(hass.states.get(entity_id), status_code) - resp.headers.add('Location', URL_API_STATES_ENTITY.format(entity_id)) + resp.headers.add("Location", URL_API_STATES_ENTITY.format(entity_id)) return resp @ha.callback def delete(self, request, entity_id): """Remove entity.""" - if not request['hass_user'].is_admin: + if not request["hass_user"].is_admin: raise Unauthorized(entity_id=entity_id) - if request.app['hass'].states.async_remove(entity_id): + if request.app["hass"].states.async_remove(entity_id): return self.json_message("Entity removed.") return self.json_message("Entity not found.", HTTP_NOT_FOUND) @@ -257,47 +272,49 @@ class APIEventListenersView(HomeAssistantView): """View to handle EventListeners requests.""" url = URL_API_EVENTS - name = 'api:event-listeners' + name = "api:event-listeners" @ha.callback def get(self, request): """Get event listeners.""" - return self.json(async_events_json(request.app['hass'])) + return self.json(async_events_json(request.app["hass"])) class APIEventView(HomeAssistantView): """View to handle Event requests.""" - url = '/api/events/{event_type}' - name = 'api:event' + url = "/api/events/{event_type}" + name = "api:event" async def post(self, request, event_type): """Fire events.""" - if not request['hass_user'].is_admin: + if not request["hass_user"].is_admin: raise Unauthorized() body = await request.text() try: event_data = json.loads(body) if body else None except ValueError: return self.json_message( - "Event data should be valid JSON.", HTTP_BAD_REQUEST) + "Event data should be valid JSON.", HTTP_BAD_REQUEST + ) if event_data is not None and not isinstance(event_data, dict): return self.json_message( - "Event data should be a JSON object", HTTP_BAD_REQUEST) + "Event data should be a JSON object", HTTP_BAD_REQUEST + ) # Special case handling for event STATE_CHANGED # We will try to convert state dicts back to State objects if event_type == ha.EVENT_STATE_CHANGED and event_data: - for key in ('old_state', 'new_state'): + for key in ("old_state", "new_state"): state = ha.State.from_dict(event_data.get(key)) if state: event_data[key] = state - request.app['hass'].bus.async_fire( - event_type, event_data, ha.EventOrigin.remote, - self.context(request)) + request.app["hass"].bus.async_fire( + event_type, event_data, ha.EventOrigin.remote, self.context(request) + ) return self.json_message("Event {} fired.".format(event_type)) @@ -306,37 +323,37 @@ class APIServicesView(HomeAssistantView): """View to handle Services requests.""" url = URL_API_SERVICES - name = 'api:services' + name = "api:services" async def get(self, request): """Get registered services.""" - services = await async_services_json(request.app['hass']) + services = await async_services_json(request.app["hass"]) return self.json(services) class APIDomainServicesView(HomeAssistantView): """View to handle DomainServices requests.""" - url = '/api/services/{domain}/{service}' - name = 'api:domain-services' + url = "/api/services/{domain}/{service}" + name = "api:domain-services" async def post(self, request, domain, service): """Call a service. Returns a list of changed states. """ - hass = request.app['hass'] + hass = request.app["hass"] body = await request.text() try: data = json.loads(body) if body else None except ValueError: - return self.json_message( - "Data should be valid JSON.", HTTP_BAD_REQUEST) + return self.json_message("Data should be valid JSON.", HTTP_BAD_REQUEST) with AsyncTrackStates(hass) as changed_states: try: await hass.services.async_call( - domain, service, data, True, self.context(request)) + domain, service, data, True, self.context(request) + ) except (vol.Invalid, ServiceNotFound): raise HTTPBadRequest() @@ -347,54 +364,56 @@ class APIComponentsView(HomeAssistantView): """View to handle Components requests.""" url = URL_API_COMPONENTS - name = 'api:components' + name = "api:components" @ha.callback def get(self, request): """Get current loaded components.""" - return self.json(request.app['hass'].config.components) + return self.json(request.app["hass"].config.components) class APITemplateView(HomeAssistantView): """View to handle Template requests.""" url = URL_API_TEMPLATE - name = 'api:template' + name = "api:template" async def post(self, request): """Render a template.""" - if not request['hass_user'].is_admin: + if not request["hass_user"].is_admin: raise Unauthorized() try: data = await request.json() - tpl = template.Template(data['template'], request.app['hass']) - return tpl.async_render(data.get('variables')) + tpl = template.Template(data["template"], request.app["hass"]) + return tpl.async_render(data.get("variables")) except (ValueError, TemplateError) as ex: return self.json_message( - "Error rendering template: {}".format(ex), HTTP_BAD_REQUEST) + "Error rendering template: {}".format(ex), HTTP_BAD_REQUEST + ) class APIErrorLog(HomeAssistantView): """View to fetch the API error log.""" url = URL_API_ERROR_LOG - name = 'api:error_log' + name = "api:error_log" async def get(self, request): """Retrieve API error log.""" - if not request['hass_user'].is_admin: + if not request["hass_user"].is_admin: raise Unauthorized() - return web.FileResponse(request.app['hass'].data[DATA_LOGGING]) + return web.FileResponse(request.app["hass"].data[DATA_LOGGING]) async def async_services_json(hass): """Generate services data to JSONify.""" descriptions = await async_get_all_descriptions(hass) - return [{'domain': key, 'services': value} - for key, value in descriptions.items()] + return [{"domain": key, "services": value} for key, value in descriptions.items()] def async_events_json(hass): """Generate event data to JSONify.""" - return [{'event': key, 'listener_count': value} - for key, value in hass.bus.async_listeners().items()] + return [ + {"event": key, "listener_count": value} + for key, value in hass.bus.async_listeners().items() + ] diff --git a/homeassistant/components/apns/notify.py b/homeassistant/components/apns/notify.py index ccf7c495f39..0b95cb9f0cb 100644 --- a/homeassistant/components/apns/notify.py +++ b/homeassistant/components/apns/notify.py @@ -10,29 +10,35 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import track_state_change from homeassistant.components.notify import ( - ATTR_DATA, ATTR_TARGET, DOMAIN, PLATFORM_SCHEMA, BaseNotificationService) + ATTR_DATA, + ATTR_TARGET, + DOMAIN, + PLATFORM_SCHEMA, + BaseNotificationService, +) -APNS_DEVICES = 'apns.yaml' -CONF_CERTFILE = 'cert_file' -CONF_TOPIC = 'topic' -CONF_SANDBOX = 'sandbox' -DEVICE_TRACKER_DOMAIN = 'device_tracker' -SERVICE_REGISTER = 'apns_register' +APNS_DEVICES = "apns.yaml" +CONF_CERTFILE = "cert_file" +CONF_TOPIC = "topic" +CONF_SANDBOX = "sandbox" +DEVICE_TRACKER_DOMAIN = "device_tracker" +SERVICE_REGISTER = "apns_register" -ATTR_PUSH_ID = 'push_id' +ATTR_PUSH_ID = "push_id" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_PLATFORM): 'apns', - vol.Required(CONF_NAME): cv.string, - vol.Required(CONF_CERTFILE): cv.isfile, - vol.Required(CONF_TOPIC): cv.string, - vol.Optional(CONF_SANDBOX, default=False): cv.boolean, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_PLATFORM): "apns", + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_CERTFILE): cv.isfile, + vol.Required(CONF_TOPIC): cv.string, + vol.Optional(CONF_SANDBOX, default=False): cv.boolean, + } +) -REGISTER_SERVICE_SCHEMA = vol.Schema({ - vol.Required(ATTR_PUSH_ID): cv.string, - vol.Optional(ATTR_NAME): cv.string, -}) +REGISTER_SERVICE_SCHEMA = vol.Schema( + {vol.Required(ATTR_PUSH_ID): cv.string, vol.Optional(ATTR_NAME): cv.string} +) def get_service(hass, config, discovery_info=None): @@ -44,8 +50,8 @@ def get_service(hass, config, discovery_info=None): service = ApnsNotificationService(hass, name, topic, sandbox, cert_file) hass.services.register( - DOMAIN, 'apns_{}'.format(name), service.register, - schema=REGISTER_SERVICE_SCHEMA) + DOMAIN, "apns_{}".format(name), service.register, schema=REGISTER_SERVICE_SCHEMA + ) return service @@ -92,7 +98,7 @@ class ApnsDevice: The full id of a device that is tracked by the device tracking component. """ - return '{}.{}'.format(DEVICE_TRACKER_DOMAIN, self.tracking_id) + return "{}.{}".format(DEVICE_TRACKER_DOMAIN, self.tracking_id) @property def disabled(self): @@ -118,13 +124,11 @@ def _write_device(out, device): """Write a single device to file.""" attributes = [] if device.name is not None: - attributes.append( - 'name: {}'.format(device.name)) + attributes.append("name: {}".format(device.name)) if device.tracking_device_id is not None: - attributes.append( - 'tracking_device_id: {}'.format(device.tracking_device_id)) + attributes.append("tracking_device_id: {}".format(device.tracking_device_id)) if device.disabled: - attributes.append('disabled: True') + attributes.append("disabled: True") out.write(device.push_id) out.write(": {") @@ -144,7 +148,7 @@ class ApnsNotificationService(BaseNotificationService): self.app_name = app_name self.sandbox = sandbox self.certificate = cert_file - self.yaml_path = hass.config.path(app_name + '_' + APNS_DEVICES) + self.yaml_path = hass.config.path(app_name + "_" + APNS_DEVICES) self.devices = {} self.device_states = {} self.topic = topic @@ -153,12 +157,11 @@ class ApnsNotificationService(BaseNotificationService): self.devices = { str(key): ApnsDevice( str(key), - value.get('name'), - value.get('tracking_device_id'), - value.get('disabled', False) + value.get("name"), + value.get("tracking_device_id"), + value.get("disabled", False), ) - for (key, value) in - load_yaml_config_file(self.yaml_path).items() + for (key, value) in load_yaml_config_file(self.yaml_path).items() } except FileNotFoundError: pass @@ -168,8 +171,7 @@ class ApnsNotificationService(BaseNotificationService): for (key, device) in self.devices.items() if device.tracking_device_id is not None ] - track_state_change( - hass, tracking_ids, self.device_state_changed_listener) + track_state_change(hass, tracking_ids, self.device_state_changed_listener) def device_state_changed_listener(self, entity_id, from_s, to_s): """ @@ -181,7 +183,7 @@ class ApnsNotificationService(BaseNotificationService): def write_devices(self): """Write all known devices to file.""" - with open(self.yaml_path, 'w+') as out: + with open(self.yaml_path, "w+") as out: for _, device in self.devices.items(): _write_device(out, device) @@ -191,14 +193,15 @@ class ApnsNotificationService(BaseNotificationService): device_name = call.data.get(ATTR_NAME) current_device = self.devices.get(push_id) - current_tracking_id = None if current_device is None \ - else current_device.tracking_device_id + current_tracking_id = ( + None if current_device is None else current_device.tracking_device_id + ) device = ApnsDevice(push_id, device_name, current_tracking_id) if current_device is None: self.devices[push_id] = device - with open(self.yaml_path, 'a') as out: + with open(self.yaml_path, "a") as out: _write_device(out, device) return True @@ -215,9 +218,8 @@ class ApnsNotificationService(BaseNotificationService): from apns2.errors import Unregistered apns = APNsClient( - self.certificate, - use_sandbox=self.sandbox, - use_alternative_port=False) + self.certificate, use_sandbox=self.sandbox, use_alternative_port=False + ) device_state = kwargs.get(ATTR_TARGET) message_data = kwargs.get(ATTR_DATA) @@ -230,15 +232,16 @@ class ApnsNotificationService(BaseNotificationService): elif isinstance(message, template_helper.Template): rendered_message = message.render() else: - rendered_message = '' + rendered_message = "" payload = Payload( alert=rendered_message, - badge=message_data.get('badge'), - sound=message_data.get('sound'), - category=message_data.get('category'), - custom=message_data.get('custom', {}), - content_available=message_data.get('content_available', False)) + badge=message_data.get("badge"), + sound=message_data.get("sound"), + category=message_data.get("category"), + custom=message_data.get("custom", {}), + content_available=message_data.get("content_available", False), + ) device_update = False @@ -246,13 +249,11 @@ class ApnsNotificationService(BaseNotificationService): if not device.disabled: state = None if device.tracking_device_id is not None: - state = self.device_states.get( - device.full_tracking_device_id) + state = self.device_states.get(device.full_tracking_device_id) if device_state is None or state == str(device_state): try: - apns.send_notification( - push_id, payload, topic=self.topic) + apns.send_notification(push_id, payload, topic=self.topic) except Unregistered: logging.error("Device %s has unregistered", push_id) device_update = True diff --git a/homeassistant/components/apple_tv/__init__.py b/homeassistant/components/apple_tv/__init__.py index 80da26195ee..51c2ee7e1a5 100644 --- a/homeassistant/components/apple_tv/__init__.py +++ b/homeassistant/components/apple_tv/__init__.py @@ -13,31 +13,31 @@ import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DOMAIN = 'apple_tv' +DOMAIN = "apple_tv" -SERVICE_SCAN = 'apple_tv_scan' -SERVICE_AUTHENTICATE = 'apple_tv_authenticate' +SERVICE_SCAN = "apple_tv_scan" +SERVICE_AUTHENTICATE = "apple_tv_authenticate" -ATTR_ATV = 'atv' -ATTR_POWER = 'power' +ATTR_ATV = "atv" +ATTR_POWER = "power" -CONF_LOGIN_ID = 'login_id' -CONF_START_OFF = 'start_off' -CONF_CREDENTIALS = 'credentials' +CONF_LOGIN_ID = "login_id" +CONF_START_OFF = "start_off" +CONF_CREDENTIALS = "credentials" -DEFAULT_NAME = 'Apple TV' +DEFAULT_NAME = "Apple TV" -DATA_APPLE_TV = 'data_apple_tv' -DATA_ENTITIES = 'data_apple_tv_entities' +DATA_APPLE_TV = "data_apple_tv" +DATA_ENTITIES = "data_apple_tv_entities" -KEY_CONFIG = 'apple_tv_configuring' +KEY_CONFIG = "apple_tv_configuring" -NOTIFICATION_AUTH_ID = 'apple_tv_auth_notification' -NOTIFICATION_AUTH_TITLE = 'Apple TV Authentication' -NOTIFICATION_SCAN_ID = 'apple_tv_scan_notification' -NOTIFICATION_SCAN_TITLE = 'Apple TV Scan' +NOTIFICATION_AUTH_ID = "apple_tv_auth_notification" +NOTIFICATION_AUTH_TITLE = "Apple TV Authentication" +NOTIFICATION_SCAN_ID = "apple_tv_scan_notification" +NOTIFICATION_SCAN_TITLE = "Apple TV Scan" -T = TypeVar('T') # pylint: disable=invalid-name +T = TypeVar("T") # pylint: disable=invalid-name # This version of ensure_list interprets an empty dict as no value @@ -48,22 +48,30 @@ def ensure_list(value: Union[T, Sequence[T]]) -> Sequence[T]: return value if isinstance(value, list) else [value] -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.All(ensure_list, [vol.Schema({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_LOGIN_ID): cv.string, - vol.Optional(CONF_CREDENTIALS): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_START_OFF, default=False): cv.boolean, - })]) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.All( + ensure_list, + [ + vol.Schema( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_LOGIN_ID): cv.string, + vol.Optional(CONF_CREDENTIALS): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_START_OFF, default=False): cv.boolean, + } + ) + ], + ) + }, + extra=vol.ALLOW_EXTRA, +) # Currently no attributes but it might change later APPLE_TV_SCAN_SCHEMA = vol.Schema({}) -APPLE_TV_AUTHENTICATE_SCHEMA = vol.Schema({ - ATTR_ENTITY_ID: cv.entity_ids, -}) +APPLE_TV_AUTHENTICATE_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids}) def request_configuration(hass, config, atv, credentials): @@ -73,54 +81,62 @@ def request_configuration(hass, config, atv, credentials): async def configuration_callback(callback_data): """Handle the submitted configuration.""" from pyatv import exceptions - pin = callback_data.get('pin') + + pin = callback_data.get("pin") try: await atv.airplay.finish_authentication(pin) hass.components.persistent_notification.async_create( - 'Authentication succeeded!

Add the following ' - 'to credentials: in your apple_tv configuration:

' - '{0}'.format(credentials), + "Authentication succeeded!

Add the following " + "to credentials: in your apple_tv configuration:

" + "{0}".format(credentials), title=NOTIFICATION_AUTH_TITLE, - notification_id=NOTIFICATION_AUTH_ID) + notification_id=NOTIFICATION_AUTH_ID, + ) except exceptions.DeviceAuthenticationError as ex: hass.components.persistent_notification.async_create( - 'Authentication failed! Did you enter correct PIN?

' - 'Details: {0}'.format(ex), + "Authentication failed! Did you enter correct PIN?

" + "Details: {0}".format(ex), title=NOTIFICATION_AUTH_TITLE, - notification_id=NOTIFICATION_AUTH_ID) + notification_id=NOTIFICATION_AUTH_ID, + ) hass.async_add_job(configurator.request_done, instance) instance = configurator.request_config( - 'Apple TV Authentication', configuration_callback, - description='Please enter PIN code shown on screen.', - submit_caption='Confirm', - fields=[{'id': 'pin', 'name': 'PIN Code', 'type': 'password'}] + "Apple TV Authentication", + configuration_callback, + description="Please enter PIN code shown on screen.", + submit_caption="Confirm", + fields=[{"id": "pin", "name": "PIN Code", "type": "password"}], ) async def scan_for_apple_tvs(hass): """Scan for devices and present a notification of the ones found.""" import pyatv + atvs = await pyatv.scan_for_apple_tvs(hass.loop, timeout=3) devices = [] for atv in atvs: login_id = atv.login_id if login_id is None: - login_id = 'Home Sharing disabled' - devices.append('Name: {0}
Host: {1}
Login ID: {2}'.format( - atv.name, atv.address, login_id)) + login_id = "Home Sharing disabled" + devices.append( + "Name: {0}
Host: {1}
Login ID: {2}".format( + atv.name, atv.address, login_id + ) + ) if not devices: - devices = ['No device(s) found'] + devices = ["No device(s) found"] hass.components.persistent_notification.async_create( - 'The following devices were found:

' + - '

'.join(devices), + "The following devices were found:

" + "

".join(devices), title=NOTIFICATION_SCAN_TITLE, - notification_id=NOTIFICATION_SCAN_ID) + notification_id=NOTIFICATION_SCAN_ID, + ) async def async_setup(hass, config): @@ -137,8 +153,11 @@ async def async_setup(hass, config): return if entity_ids: - devices = [device for device in hass.data[DATA_ENTITIES] - if device.entity_id in entity_ids] + devices = [ + device + for device in hass.data[DATA_ENTITIES] + if device.entity_id in entity_ids + ] else: devices = hass.data[DATA_ENTITIES] @@ -149,19 +168,22 @@ async def async_setup(hass, config): atv = device.atv credentials = await atv.airplay.generate_credentials() await atv.airplay.load_credentials(credentials) - _LOGGER.debug('Generated new credentials: %s', credentials) + _LOGGER.debug("Generated new credentials: %s", credentials) await atv.airplay.start_authentication() - hass.async_add_job(request_configuration, - hass, config, atv, credentials) + hass.async_add_job(request_configuration, hass, config, atv, credentials) async def atv_discovered(service, info): """Set up an Apple TV that was auto discovered.""" - await _setup_atv(hass, config, { - CONF_NAME: info['name'], - CONF_HOST: info['host'], - CONF_LOGIN_ID: info['properties']['hG'], - CONF_START_OFF: False - }) + await _setup_atv( + hass, + config, + { + CONF_NAME: info["name"], + CONF_HOST: info["host"], + CONF_LOGIN_ID: info["properties"]["hG"], + CONF_START_OFF: False, + }, + ) discovery.async_listen(hass, SERVICE_APPLE_TV, atv_discovered) @@ -170,12 +192,15 @@ async def async_setup(hass, config): await asyncio.wait(tasks) hass.services.async_register( - DOMAIN, SERVICE_SCAN, async_service_handler, - schema=APPLE_TV_SCAN_SCHEMA) + DOMAIN, SERVICE_SCAN, async_service_handler, schema=APPLE_TV_SCAN_SCHEMA + ) hass.services.async_register( - DOMAIN, SERVICE_AUTHENTICATE, async_service_handler, - schema=APPLE_TV_AUTHENTICATE_SCHEMA) + DOMAIN, + SERVICE_AUTHENTICATE, + async_service_handler, + schema=APPLE_TV_AUTHENTICATE_SCHEMA, + ) return True @@ -183,6 +208,7 @@ async def async_setup(hass, config): async def _setup_atv(hass, hass_config, atv_config): """Set up an Apple TV.""" import pyatv + name = atv_config.get(CONF_NAME) host = atv_config.get(CONF_HOST) login_id = atv_config.get(CONF_LOGIN_ID) @@ -199,16 +225,17 @@ async def _setup_atv(hass, hass_config, atv_config): await atv.airplay.load_credentials(credentials) power = AppleTVPowerManager(hass, atv, start_off) - hass.data[DATA_APPLE_TV][host] = { - ATTR_ATV: atv, - ATTR_POWER: power - } + hass.data[DATA_APPLE_TV][host] = {ATTR_ATV: atv, ATTR_POWER: power} - hass.async_create_task(discovery.async_load_platform( - hass, 'media_player', DOMAIN, atv_config, hass_config)) + hass.async_create_task( + discovery.async_load_platform( + hass, "media_player", DOMAIN, atv_config, hass_config + ) + ) - hass.async_create_task(discovery.async_load_platform( - hass, 'remote', DOMAIN, atv_config, hass_config)) + hass.async_create_task( + discovery.async_load_platform(hass, "remote", DOMAIN, atv_config, hass_config) + ) class AppleTVPowerManager: diff --git a/homeassistant/components/apple_tv/media_player.py b/homeassistant/components/apple_tv/media_player.py index 9698ef4c704..8ecaeab424c 100644 --- a/homeassistant/components/apple_tv/media_player.py +++ b/homeassistant/components/apple_tv/media_player.py @@ -3,12 +3,29 @@ import logging from homeassistant.components.media_player import MediaPlayerDevice from homeassistant.components.media_player.const import ( - MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO, SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, - SUPPORT_SEEK, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON) + MEDIA_TYPE_MUSIC, + MEDIA_TYPE_TVSHOW, + MEDIA_TYPE_VIDEO, + SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, + SUPPORT_PLAY, + SUPPORT_PLAY_MEDIA, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_SEEK, + SUPPORT_STOP, + SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, +) from homeassistant.const import ( - CONF_HOST, CONF_NAME, EVENT_HOMEASSISTANT_STOP, STATE_IDLE, STATE_OFF, - STATE_PAUSED, STATE_PLAYING, STATE_STANDBY) + CONF_HOST, + CONF_NAME, + EVENT_HOMEASSISTANT_STOP, + STATE_IDLE, + STATE_OFF, + STATE_PAUSED, + STATE_PLAYING, + STATE_STANDBY, +) from homeassistant.core import callback import homeassistant.util.dt as dt_util @@ -16,13 +33,20 @@ from . import ATTR_ATV, ATTR_POWER, DATA_APPLE_TV, DATA_ENTITIES _LOGGER = logging.getLogger(__name__) -SUPPORT_APPLE_TV = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PLAY_MEDIA | \ - SUPPORT_PAUSE | SUPPORT_PLAY | SUPPORT_SEEK | \ - SUPPORT_STOP | SUPPORT_NEXT_TRACK | SUPPORT_PREVIOUS_TRACK +SUPPORT_APPLE_TV = ( + SUPPORT_TURN_ON + | SUPPORT_TURN_OFF + | SUPPORT_PLAY_MEDIA + | SUPPORT_PAUSE + | SUPPORT_PLAY + | SUPPORT_SEEK + | SUPPORT_STOP + | SUPPORT_NEXT_TRACK + | SUPPORT_PREVIOUS_TRACK +) -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Apple TV platform.""" if not discovery_info: return @@ -89,15 +113,21 @@ class AppleTvDevice(MediaPlayerDevice): if self._playing: from pyatv import const + state = self._playing.play_state - if state in (const.PLAY_STATE_IDLE, const.PLAY_STATE_NO_MEDIA, - const.PLAY_STATE_LOADING): + if state in ( + const.PLAY_STATE_IDLE, + const.PLAY_STATE_NO_MEDIA, + const.PLAY_STATE_LOADING, + ): return STATE_IDLE if state == const.PLAY_STATE_PLAYING: return STATE_PLAYING - if state in (const.PLAY_STATE_PAUSED, - const.PLAY_STATE_FAST_FORWARD, - const.PLAY_STATE_FAST_BACKWARD): + if state in ( + const.PLAY_STATE_PAUSED, + const.PLAY_STATE_FAST_FORWARD, + const.PLAY_STATE_FAST_BACKWARD, + ): # Catch fast forward/backward here so "play" is default action return STATE_PAUSED return STATE_STANDBY # Bad or unknown state? @@ -111,8 +141,7 @@ class AppleTvDevice(MediaPlayerDevice): @callback def playstatus_error(self, updater, exception): """Inform about an error and restart push updates.""" - _LOGGER.warning('A %s error occurred: %s', - exception.__class__, exception) + _LOGGER.warning("A %s error occurred: %s", exception.__class__, exception) # This will wait 10 seconds before restarting push updates. If the # connection continues to fail, it will flood the log (every 10 @@ -127,6 +156,7 @@ class AppleTvDevice(MediaPlayerDevice): """Content type of current playing media.""" if self._playing: from pyatv import const + media_type = self._playing.media_type if media_type == const.MEDIA_TYPE_VIDEO: return MEDIA_TYPE_VIDEO @@ -169,7 +199,7 @@ class AppleTvDevice(MediaPlayerDevice): """Fetch media image of current playing image.""" state = self.state if self._playing and state not in [STATE_OFF, STATE_IDLE]: - return (await self.atv.metadata.artwork()), 'image/png' + return (await self.atv.metadata.artwork()), "image/png" return None, None @@ -178,11 +208,11 @@ class AppleTvDevice(MediaPlayerDevice): """Title of current playing media.""" if self._playing: if self.state == STATE_IDLE: - return 'Nothing playing' + return "Nothing playing" title = self._playing.title - return title if title else 'No title' + return title if title else "No title" - return 'Establishing a connection to {0}...'.format(self._name) + return "Establishing a connection to {0}...".format(self._name) @property def supported_features(self): diff --git a/homeassistant/components/apple_tv/remote.py b/homeassistant/components/apple_tv/remote.py index 2839e3a5324..1229b756e72 100644 --- a/homeassistant/components/apple_tv/remote.py +++ b/homeassistant/components/apple_tv/remote.py @@ -5,8 +5,7 @@ from homeassistant.const import CONF_HOST, CONF_NAME from . import ATTR_ATV, ATTR_POWER, DATA_APPLE_TV -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Apple TV remote platform.""" if not discovery_info: return diff --git a/homeassistant/components/aprs/device_tracker.py b/homeassistant/components/aprs/device_tracker.py index 3bde7021d7c..c5ae8ed8414 100644 --- a/homeassistant/components/aprs/device_tracker.py +++ b/homeassistant/components/aprs/device_tracker.py @@ -7,60 +7,61 @@ import voluptuous as vol from homeassistant.components.device_tracker import PLATFORM_SCHEMA from homeassistant.const import ( - ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE, - CONF_HOST, CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME, - EVENT_HOMEASSISTANT_STOP) + ATTR_GPS_ACCURACY, + ATTR_LATITUDE, + ATTR_LONGITUDE, + CONF_HOST, + CONF_PASSWORD, + CONF_TIMEOUT, + CONF_USERNAME, + EVENT_HOMEASSISTANT_STOP, +) import homeassistant.helpers.config_validation as cv from homeassistant.util import slugify -DOMAIN = 'aprs' +DOMAIN = "aprs" _LOGGER = logging.getLogger(__name__) -ATTR_ALTITUDE = 'altitude' -ATTR_COURSE = 'course' -ATTR_COMMENT = 'comment' -ATTR_FROM = 'from' -ATTR_FORMAT = 'format' -ATTR_POS_AMBIGUITY = 'posambiguity' -ATTR_SPEED = 'speed' +ATTR_ALTITUDE = "altitude" +ATTR_COURSE = "course" +ATTR_COMMENT = "comment" +ATTR_FROM = "from" +ATTR_FORMAT = "format" +ATTR_POS_AMBIGUITY = "posambiguity" +ATTR_SPEED = "speed" -CONF_CALLSIGNS = 'callsigns' +CONF_CALLSIGNS = "callsigns" -DEFAULT_HOST = 'rotate.aprs2.net' -DEFAULT_PASSWORD = '-1' +DEFAULT_HOST = "rotate.aprs2.net" +DEFAULT_PASSWORD = "-1" DEFAULT_TIMEOUT = 30.0 FILTER_PORT = 14580 -MSG_FORMATS = ['compressed', 'uncompressed', 'mic-e'] +MSG_FORMATS = ["compressed", "uncompressed", "mic-e"] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_CALLSIGNS): cv.ensure_list, - vol.Required(CONF_USERNAME): cv.string, - vol.Optional(CONF_PASSWORD, - default=DEFAULT_PASSWORD): cv.string, - vol.Optional(CONF_HOST, - default=DEFAULT_HOST): cv.string, - vol.Optional(CONF_TIMEOUT, - default=DEFAULT_TIMEOUT): vol.Coerce(float), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_CALLSIGNS): cv.ensure_list, + vol.Required(CONF_USERNAME): cv.string, + vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string, + vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, + vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): vol.Coerce(float), + } +) def make_filter(callsigns: list) -> str: """Make a server-side filter from a list of callsigns.""" - return ' '.join('b/{0}'.format(cs.upper()) for cs in callsigns) + return " ".join("b/{0}".format(cs.upper()) for cs in callsigns) def gps_accuracy(gps, posambiguity: int) -> int: """Calculate the GPS accuracy based on APRS posambiguity.""" import geopy.distance - pos_a_map = {0: 0, - 1: 1 / 600, - 2: 1 / 60, - 3: 1 / 6, - 4: 1} + pos_a_map = {0: 0, 1: 1 / 600, 2: 1 / 60, 3: 1 / 6, 4: 1} if posambiguity in pos_a_map: degrees = pos_a_map[posambiguity] @@ -69,8 +70,7 @@ def gps_accuracy(gps, posambiguity: int) -> int: accuracy = round(dist_m) else: - message = "APRS position ambiguity must be 0-4, not '{0}'.".format( - posambiguity) + message = "APRS position ambiguity must be 0-4, not '{0}'.".format(posambiguity) raise ValueError(message) return accuracy @@ -85,8 +85,7 @@ def setup_scanner(hass, config, see, discovery_info=None): password = config.get(CONF_PASSWORD) host = config.get(CONF_HOST) timeout = config.get(CONF_TIMEOUT) - aprs_listener = AprsListenerThread( - callsign, password, host, server_filter, see) + aprs_listener = AprsListenerThread(callsign, password, host, server_filter, see) def aprs_disconnect(event): """Stop the APRS connection.""" @@ -110,8 +109,9 @@ def setup_scanner(hass, config, see, discovery_info=None): class AprsListenerThread(threading.Thread): """APRS message listener.""" - def __init__(self, callsign: str, password: str, host: str, - server_filter: str, see): + def __init__( + self, callsign: str, password: str, host: str, server_filter: str, see + ): """Initialize the class.""" super().__init__() @@ -126,7 +126,8 @@ class AprsListenerThread(threading.Thread): self.start_success = False self.ais = aprslib.IS( - self.callsign, passwd=password, host=self.host, port=FILTER_PORT) + self.callsign, passwd=password, host=self.host, port=FILTER_PORT + ) def start_complete(self, success: bool, message: str): """Complete startup process.""" @@ -141,19 +142,21 @@ class AprsListenerThread(threading.Thread): from aprslib import LoginError try: - _LOGGER.info("Opening connection to %s with callsign %s.", - self.host, self.callsign) + _LOGGER.info( + "Opening connection to %s with callsign %s.", self.host, self.callsign + ) self.ais.connect() self.start_complete( True, - "Connected to {0} with callsign {1}.".format( - self.host, self.callsign)) + "Connected to {0} with callsign {1}.".format(self.host, self.callsign), + ) self.ais.consumer(callback=self.rx_msg, immortal=True) except (AprsConnectionError, LoginError) as err: self.start_complete(False, str(err)) except OSError: - _LOGGER.info("Closing connection to %s with callsign %s.", - self.host, self.callsign) + _LOGGER.info( + "Closing connection to %s with callsign %s.", self.host, self.callsign + ) def stop(self): """Close the connection to the APRS network.""" @@ -171,16 +174,12 @@ class AprsListenerThread(threading.Thread): if ATTR_POS_AMBIGUITY in msg: pos_amb = msg[ATTR_POS_AMBIGUITY] try: - attrs[ATTR_GPS_ACCURACY] = gps_accuracy((lat, lon), - pos_amb) + attrs[ATTR_GPS_ACCURACY] = gps_accuracy((lat, lon), pos_amb) except ValueError: _LOGGER.warning( - "APRS message contained invalid posambiguity: %s", - str(pos_amb)) - for attr in [ATTR_ALTITUDE, - ATTR_COMMENT, - ATTR_COURSE, - ATTR_SPEED]: + "APRS message contained invalid posambiguity: %s", str(pos_amb) + ) + for attr in [ATTR_ALTITUDE, ATTR_COMMENT, ATTR_COURSE, ATTR_SPEED]: if attr in msg: attrs[attr] = msg[attr] diff --git a/homeassistant/components/aqualogic/__init__.py b/homeassistant/components/aqualogic/__init__.py index 65718463218..cabe00b6c6d 100644 --- a/homeassistant/components/aqualogic/__init__.py +++ b/homeassistant/components/aqualogic/__init__.py @@ -6,24 +6,29 @@ import threading import voluptuous as vol -from homeassistant.const import (CONF_HOST, CONF_PORT, - EVENT_HOMEASSISTANT_START, - EVENT_HOMEASSISTANT_STOP) +from homeassistant.const import ( + CONF_HOST, + CONF_PORT, + EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP, +) from homeassistant.helpers import config_validation as cv _LOGGER = logging.getLogger(__name__) -DOMAIN = 'aqualogic' -UPDATE_TOPIC = DOMAIN + '_update' -CONF_UNIT = 'unit' +DOMAIN = "aqualogic" +UPDATE_TOPIC = DOMAIN + "_update" +CONF_UNIT = "unit" RECONNECT_INTERVAL = timedelta(seconds=10) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_PORT): cv.port, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + {vol.Required(CONF_HOST): cv.string, vol.Required(CONF_PORT): cv.port} + ) + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): diff --git a/homeassistant/components/aqualogic/sensor.py b/homeassistant/components/aqualogic/sensor.py index 454cdbd7f6b..1cc06fc446f 100644 --- a/homeassistant/components/aqualogic/sensor.py +++ b/homeassistant/components/aqualogic/sensor.py @@ -4,8 +4,7 @@ import logging import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ( - CONF_MONITORED_CONDITIONS, TEMP_CELSIUS, TEMP_FAHRENHEIT) +from homeassistant.const import CONF_MONITORED_CONDITIONS, TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -15,33 +14,35 @@ from . import DOMAIN, UPDATE_TOPIC _LOGGER = logging.getLogger(__name__) TEMP_UNITS = [TEMP_CELSIUS, TEMP_FAHRENHEIT] -PERCENT_UNITS = ['%', '%'] -SALT_UNITS = ['g/L', 'PPM'] -WATT_UNITS = ['W', 'W'] +PERCENT_UNITS = ["%", "%"] +SALT_UNITS = ["g/L", "PPM"] +WATT_UNITS = ["W", "W"] NO_UNITS = [None, None] # sensor_type [ description, unit, icon ] # sensor_type corresponds to property names in aqualogic.core.AquaLogic SENSOR_TYPES = { - 'air_temp': ['Air Temperature', TEMP_UNITS, 'mdi:thermometer'], - 'pool_temp': ['Pool Temperature', TEMP_UNITS, 'mdi:oil-temperature'], - 'spa_temp': ['Spa Temperature', TEMP_UNITS, 'mdi:oil-temperature'], - 'pool_chlorinator': ['Pool Chlorinator', PERCENT_UNITS, 'mdi:gauge'], - 'spa_chlorinator': ['Spa Chlorinator', PERCENT_UNITS, 'mdi:gauge'], - 'salt_level': ['Salt Level', SALT_UNITS, 'mdi:gauge'], - 'pump_speed': ['Pump Speed', PERCENT_UNITS, 'mdi:speedometer'], - 'pump_power': ['Pump Power', WATT_UNITS, 'mdi:gauge'], - 'status': ['Status', NO_UNITS, 'mdi:alert'] + "air_temp": ["Air Temperature", TEMP_UNITS, "mdi:thermometer"], + "pool_temp": ["Pool Temperature", TEMP_UNITS, "mdi:oil-temperature"], + "spa_temp": ["Spa Temperature", TEMP_UNITS, "mdi:oil-temperature"], + "pool_chlorinator": ["Pool Chlorinator", PERCENT_UNITS, "mdi:gauge"], + "spa_chlorinator": ["Spa Chlorinator", PERCENT_UNITS, "mdi:gauge"], + "salt_level": ["Salt Level", SALT_UNITS, "mdi:gauge"], + "pump_speed": ["Pump Speed", PERCENT_UNITS, "mdi:speedometer"], + "pump_power": ["Pump Power", WATT_UNITS, "mdi:gauge"], + "status": ["Status", NO_UNITS, "mdi:alert"], } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)): - vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]) -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)): vol.All( + cv.ensure_list, [vol.In(SENSOR_TYPES)] + ) + } +) -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the sensor platform.""" sensors = [] @@ -94,7 +95,8 @@ class AquaLogicSensor(Entity): async def async_added_to_hass(self): """Register callbacks.""" self.hass.helpers.dispatcher.async_dispatcher_connect( - UPDATE_TOPIC, self.async_update_callback) + UPDATE_TOPIC, self.async_update_callback + ) @callback def async_update_callback(self): diff --git a/homeassistant/components/aqualogic/switch.py b/homeassistant/components/aqualogic/switch.py index b8bd8e41244..b5a7a409647 100644 --- a/homeassistant/components/aqualogic/switch.py +++ b/homeassistant/components/aqualogic/switch.py @@ -13,26 +13,28 @@ from . import DOMAIN, UPDATE_TOPIC _LOGGER = logging.getLogger(__name__) SWITCH_TYPES = { - 'lights': 'Lights', - 'filter': 'Filter', - 'filter_low_speed': 'Filter Low Speed', - 'aux_1': 'Aux 1', - 'aux_2': 'Aux 2', - 'aux_3': 'Aux 3', - 'aux_4': 'Aux 4', - 'aux_5': 'Aux 5', - 'aux_6': 'Aux 6', - 'aux_7': 'Aux 7', + "lights": "Lights", + "filter": "Filter", + "filter_low_speed": "Filter Low Speed", + "aux_1": "Aux 1", + "aux_2": "Aux 2", + "aux_3": "Aux 3", + "aux_4": "Aux 4", + "aux_5": "Aux 5", + "aux_6": "Aux 6", + "aux_7": "Aux 7", } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SWITCH_TYPES)): - vol.All(cv.ensure_list, [vol.In(SWITCH_TYPES)]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SWITCH_TYPES)): vol.All( + cv.ensure_list, [vol.In(SWITCH_TYPES)] + ) + } +) -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the switch platform.""" switches = [] @@ -49,19 +51,20 @@ class AquaLogicSwitch(SwitchDevice): def __init__(self, processor, switch_type): """Initialize switch.""" from aqualogic.core import States + self._processor = processor self._type = switch_type self._state_name = { - 'lights': States.LIGHTS, - 'filter': States.FILTER, - 'filter_low_speed': States.FILTER_LOW_SPEED, - 'aux_1': States.AUX_1, - 'aux_2': States.AUX_2, - 'aux_3': States.AUX_3, - 'aux_4': States.AUX_4, - 'aux_5': States.AUX_5, - 'aux_6': States.AUX_6, - 'aux_7': States.AUX_7 + "lights": States.LIGHTS, + "filter": States.FILTER, + "filter_low_speed": States.FILTER_LOW_SPEED, + "aux_1": States.AUX_1, + "aux_2": States.AUX_2, + "aux_3": States.AUX_3, + "aux_4": States.AUX_4, + "aux_5": States.AUX_5, + "aux_6": States.AUX_6, + "aux_7": States.AUX_7, }[switch_type] @property @@ -100,7 +103,8 @@ class AquaLogicSwitch(SwitchDevice): async def async_added_to_hass(self): """Register callbacks.""" self.hass.helpers.dispatcher.async_dispatcher_connect( - UPDATE_TOPIC, self.async_update_callback) + UPDATE_TOPIC, self.async_update_callback + ) @callback def async_update_callback(self): diff --git a/homeassistant/components/aquostv/media_player.py b/homeassistant/components/aquostv/media_player.py index a4e88f02a59..016db478fc9 100644 --- a/homeassistant/components/aquostv/media_player.py +++ b/homeassistant/components/aquostv/media_player.py @@ -3,52 +3,76 @@ import logging import voluptuous as vol -from homeassistant.components.media_player import ( - MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA from homeassistant.components.media_player.const import ( - SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, - SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP) + SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, + SUPPORT_PLAY, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_SELECT_SOURCE, + SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, + SUPPORT_VOLUME_MUTE, + SUPPORT_VOLUME_SET, + SUPPORT_VOLUME_STEP, +) from homeassistant.const import ( - CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_TIMEOUT, - CONF_USERNAME, STATE_OFF, STATE_ON) + CONF_HOST, + CONF_NAME, + CONF_PASSWORD, + CONF_PORT, + CONF_TIMEOUT, + CONF_USERNAME, + STATE_OFF, + STATE_ON, +) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'Sharp Aquos TV' +DEFAULT_NAME = "Sharp Aquos TV" DEFAULT_PORT = 10002 -DEFAULT_USERNAME = 'admin' -DEFAULT_PASSWORD = 'password' +DEFAULT_USERNAME = "admin" +DEFAULT_PASSWORD = "password" DEFAULT_TIMEOUT = 0.5 DEFAULT_RETRIES = 2 -SUPPORT_SHARPTV = SUPPORT_TURN_OFF | \ - SUPPORT_NEXT_TRACK | SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | \ - SUPPORT_SELECT_SOURCE | SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_STEP | \ - SUPPORT_VOLUME_SET | SUPPORT_PLAY +SUPPORT_SHARPTV = ( + SUPPORT_TURN_OFF + | SUPPORT_NEXT_TRACK + | SUPPORT_PAUSE + | SUPPORT_PREVIOUS_TRACK + | SUPPORT_SELECT_SOURCE + | SUPPORT_VOLUME_MUTE + | SUPPORT_VOLUME_STEP + | SUPPORT_VOLUME_SET + | SUPPORT_PLAY +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, - vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string, - vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.string, - vol.Optional('retries', default=DEFAULT_RETRIES): cv.string, - vol.Optional('power_on_enabled', default=False): cv.boolean, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, + vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string, + vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.string, + vol.Optional("retries", default=DEFAULT_RETRIES): cv.string, + vol.Optional("power_on_enabled", default=False): cv.boolean, + } +) -SOURCES = {0: 'TV / Antenna', - 1: 'HDMI_IN_1', - 2: 'HDMI_IN_2', - 3: 'HDMI_IN_3', - 4: 'HDMI_IN_4', - 5: 'COMPONENT IN', - 6: 'VIDEO_IN_1', - 7: 'VIDEO_IN_2', - 8: 'PC_IN'} +SOURCES = { + 0: "TV / Antenna", + 1: "HDMI_IN_1", + 2: "HDMI_IN_2", + 3: "HDMI_IN_3", + 4: "HDMI_IN_4", + 5: "COMPONENT IN", + 6: "VIDEO_IN_1", + 7: "VIDEO_IN_2", + 8: "PC_IN", +} def setup_platform(hass, config, add_entities, discovery_info=None): @@ -59,11 +83,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): port = config.get(CONF_PORT) username = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) - power_on_enabled = config.get('power_on_enabled') + power_on_enabled = config.get("power_on_enabled") if discovery_info: - _LOGGER.debug('%s', discovery_info) - vals = discovery_info.split(':') + _LOGGER.debug("%s", discovery_info) + vals = discovery_info.split(":") if len(vals) > 1: port = vals[1] @@ -81,6 +105,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): def _retry(func): """Handle query retries.""" + def wrapper(obj, *args, **kwargs): """Wrap all query functions.""" update_retries = 5 @@ -92,6 +117,7 @@ def _retry(func): update_retries -= 1 if update_retries == 0: obj.set_state(STATE_OFF) + return wrapper diff --git a/homeassistant/components/arcam_fmj/__init__.py b/homeassistant/components/arcam_fmj/__init__.py index 0fffa2bbb5c..bdb3bf67bbe 100644 --- a/homeassistant/components/arcam_fmj/__init__.py +++ b/homeassistant/components/arcam_fmj/__init__.py @@ -44,10 +44,8 @@ def _zone_name_validator(config): for zone, zone_config in config[CONF_ZONE].items(): if CONF_NAME not in zone_config: zone_config[CONF_NAME] = "{} ({}:{}) - {}".format( - DEFAULT_NAME, - config[CONF_HOST], - config[CONF_PORT], - zone) + DEFAULT_NAME, config[CONF_HOST], config[CONF_PORT], zone + ) return config @@ -59,16 +57,19 @@ ZONE_SCHEMA = vol.Schema( ) DEVICE_SCHEMA = vol.Schema( - vol.All({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_PORT, default=DEFAULT_PORT): cv.positive_int, - vol.Optional( - CONF_ZONE, default={1: _optional_zone(None)} - ): {vol.In([1, 2]): _optional_zone}, - vol.Optional( - CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL - ): cv.positive_int, - }, _zone_name_validator) + vol.All( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PORT, default=DEFAULT_PORT): cv.positive_int, + vol.Optional(CONF_ZONE, default={1: _optional_zone(None)}): { + vol.In([1, 2]): _optional_zone + }, + vol.Optional( + CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL + ): cv.positive_int, + }, + _zone_name_validator, + ) ) CONFIG_SCHEMA = vol.Schema( @@ -82,37 +83,27 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType): hass.data[DOMAIN_DATA_CONFIG] = {} for device in config[DOMAIN]: - hass.data[DOMAIN_DATA_CONFIG][ - (device[CONF_HOST], device[CONF_PORT]) - ] = device + hass.data[DOMAIN_DATA_CONFIG][(device[CONF_HOST], device[CONF_PORT])] = device hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data={ - CONF_HOST: device[CONF_HOST], - CONF_PORT: device[CONF_PORT], - }, + data={CONF_HOST: device[CONF_HOST], CONF_PORT: device[CONF_PORT]}, ) ) return True -async def async_setup_entry( - hass: HomeAssistantType, entry: config_entries.ConfigEntry -): +async def async_setup_entry(hass: HomeAssistantType, entry: config_entries.ConfigEntry): """Set up an access point from a config entry.""" client = Client(entry.data[CONF_HOST], entry.data[CONF_PORT]) config = hass.data[DOMAIN_DATA_CONFIG].get( (entry.data[CONF_HOST], entry.data[CONF_PORT]), DEVICE_SCHEMA( - { - CONF_HOST: entry.data[CONF_HOST], - CONF_PORT: entry.data[CONF_PORT], - } + {CONF_HOST: entry.data[CONF_HOST], CONF_PORT: entry.data[CONF_PORT]} ), ) @@ -121,9 +112,7 @@ async def async_setup_entry( "config": config, } - asyncio.ensure_future( - _run_client(hass, client, config[CONF_SCAN_INTERVAL]) - ) + asyncio.ensure_future(_run_client(hass, client, config[CONF_SCAN_INTERVAL])) hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, "media_player") @@ -145,9 +134,7 @@ async def _run_client(hass, client, interval): hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _stop) def _listen(_): - hass.helpers.dispatcher.async_dispatcher_send( - SIGNAL_CLIENT_DATA, client.host - ) + hass.helpers.dispatcher.async_dispatcher_send(SIGNAL_CLIENT_DATA, client.host) while run: try: diff --git a/homeassistant/components/arcam_fmj/media_player.py b/homeassistant/components/arcam_fmj/media_player.py index b22f40a641d..5cbfe2dd482 100644 --- a/homeassistant/components/arcam_fmj/media_player.py +++ b/homeassistant/components/arcam_fmj/media_player.py @@ -2,12 +2,7 @@ import logging from typing import Optional -from arcam.fmj import ( - DecodeMode2CH, - DecodeModeMCH, - IncomingAudioFormat, - SourceCodes, -) +from arcam.fmj import DecodeMode2CH, DecodeModeMCH, IncomingAudioFormat, SourceCodes from arcam.fmj.state import State from homeassistant import config_entries @@ -45,9 +40,9 @@ _LOGGER = logging.getLogger(__name__) async def async_setup_entry( - hass: HomeAssistantType, - config_entry: config_entries.ConfigEntry, - async_add_entities, + hass: HomeAssistantType, + config_entry: config_entries.ConfigEntry, + async_add_entities, ): """Set up the configuration entry.""" data = hass.data[DOMAIN_DATA_ENTRIES][config_entry.entry_id] @@ -91,20 +86,14 @@ class ArcamFmj(MediaPlayerDevice): audio_format, _ = self._state.get_incoming_audio_format() return bool( audio_format - in ( - IncomingAudioFormat.PCM, - IncomingAudioFormat.ANALOGUE_DIRECT, - None, - ) + in (IncomingAudioFormat.PCM, IncomingAudioFormat.ANALOGUE_DIRECT, None) ) @property def device_info(self): """Return a device description for device registry.""" return { - "identifiers": { - (DOMAIN, self._state.client.host, self._state.client.port) - }, + "identifiers": {(DOMAIN, self._state.client.host, self._state.client.port)}, "model": "FMJ", "manufacturer": "Arcam", } @@ -153,9 +142,7 @@ class ArcamFmj(MediaPlayerDevice): if host == self._state.client.host: self.async_schedule_update_ha_state(force_refresh=True) - self.hass.helpers.dispatcher.async_dispatcher_connect( - SIGNAL_CLIENT_DATA, _data - ) + self.hass.helpers.dispatcher.async_dispatcher_connect(SIGNAL_CLIENT_DATA, _data) self.hass.helpers.dispatcher.async_dispatcher_connect( SIGNAL_CLIENT_STARTED, _started @@ -190,13 +177,9 @@ class ArcamFmj(MediaPlayerDevice): """Select a specific source.""" try: if self._get_2ch(): - await self._state.set_decode_mode_2ch( - DecodeMode2CH[sound_mode] - ) + await self._state.set_decode_mode_2ch(DecodeMode2CH[sound_mode]) else: - await self._state.set_decode_mode_mch( - DecodeModeMCH[sound_mode] - ) + await self._state.set_decode_mode_mch(DecodeModeMCH[sound_mode]) except KeyError: _LOGGER.error("Unsupported sound_mode %s", sound_mode) return diff --git a/homeassistant/components/arduino/__init__.py b/homeassistant/components/arduino/__init__.py index a6841e07564..4dcde93e749 100644 --- a/homeassistant/components/arduino/__init__.py +++ b/homeassistant/components/arduino/__init__.py @@ -3,8 +3,7 @@ import logging import voluptuous as vol -from homeassistant.const import ( - EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) +from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP from homeassistant.const import CONF_PORT import homeassistant.helpers.config_validation as cv @@ -12,13 +11,11 @@ _LOGGER = logging.getLogger(__name__) BOARD = None -DOMAIN = 'arduino' +DOMAIN = "arduino" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_PORT): cv.string, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + {DOMAIN: vol.Schema({vol.Required(CONF_PORT): cv.string})}, extra=vol.ALLOW_EXTRA +) def setup(hass, config): @@ -39,8 +36,10 @@ def setup(hass, config): _LOGGER.error("The StandardFirmata sketch should be 2.2 or newer") return False except IndexError: - _LOGGER.warning("The version of the StandardFirmata sketch was not" - "detected. This may lead to side effects") + _LOGGER.warning( + "The version of the StandardFirmata sketch was not" + "detected. This may lead to side effects" + ) def stop_arduino(event): """Stop the Arduino service.""" @@ -61,26 +60,22 @@ class ArduinoBoard: def __init__(self, port): """Initialize the board.""" from PyMata.pymata import PyMata + self._port = port self._board = PyMata(self._port, verbose=False) def set_mode(self, pin, direction, mode): """Set the mode and the direction of a given pin.""" - if mode == 'analog' and direction == 'in': - self._board.set_pin_mode( - pin, self._board.INPUT, self._board.ANALOG) - elif mode == 'analog' and direction == 'out': - self._board.set_pin_mode( - pin, self._board.OUTPUT, self._board.ANALOG) - elif mode == 'digital' and direction == 'in': - self._board.set_pin_mode( - pin, self._board.INPUT, self._board.DIGITAL) - elif mode == 'digital' and direction == 'out': - self._board.set_pin_mode( - pin, self._board.OUTPUT, self._board.DIGITAL) - elif mode == 'pwm': - self._board.set_pin_mode( - pin, self._board.OUTPUT, self._board.PWM) + if mode == "analog" and direction == "in": + self._board.set_pin_mode(pin, self._board.INPUT, self._board.ANALOG) + elif mode == "analog" and direction == "out": + self._board.set_pin_mode(pin, self._board.OUTPUT, self._board.ANALOG) + elif mode == "digital" and direction == "in": + self._board.set_pin_mode(pin, self._board.INPUT, self._board.DIGITAL) + elif mode == "digital" and direction == "out": + self._board.set_pin_mode(pin, self._board.OUTPUT, self._board.DIGITAL) + elif mode == "pwm": + self._board.set_pin_mode(pin, self._board.OUTPUT, self._board.PWM) def get_analog_inputs(self): """Get the values from the pins.""" diff --git a/homeassistant/components/arduino/sensor.py b/homeassistant/components/arduino/sensor.py index 0cc6e006b89..a92432537ca 100644 --- a/homeassistant/components/arduino/sensor.py +++ b/homeassistant/components/arduino/sensor.py @@ -11,17 +11,14 @@ import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -CONF_PINS = 'pins' -CONF_TYPE = 'analog' +CONF_PINS = "pins" +CONF_TYPE = "analog" -PIN_SCHEMA = vol.Schema({ - vol.Required(CONF_NAME): cv.string, -}) +PIN_SCHEMA = vol.Schema({vol.Required(CONF_NAME): cv.string}) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_PINS): - vol.Schema({cv.positive_int: PIN_SCHEMA}), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_PINS): vol.Schema({cv.positive_int: PIN_SCHEMA})} +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -46,7 +43,7 @@ class ArduinoSensor(Entity): self._pin = pin self._name = name self.pin_type = pin_type - self.direction = 'in' + self.direction = "in" self._value = None arduino.BOARD.set_mode(self._pin, self.direction, self.pin_type) diff --git a/homeassistant/components/arduino/switch.py b/homeassistant/components/arduino/switch.py index 92e91196a9a..63d83c8575e 100644 --- a/homeassistant/components/arduino/switch.py +++ b/homeassistant/components/arduino/switch.py @@ -4,27 +4,28 @@ import logging import voluptuous as vol from homeassistant.components import arduino -from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) +from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -CONF_PINS = 'pins' -CONF_TYPE = 'digital' -CONF_NEGATE = 'negate' -CONF_INITIAL = 'initial' +CONF_PINS = "pins" +CONF_TYPE = "digital" +CONF_NEGATE = "negate" +CONF_INITIAL = "initial" -PIN_SCHEMA = vol.Schema({ - vol.Required(CONF_NAME): cv.string, - vol.Optional(CONF_INITIAL, default=False): cv.boolean, - vol.Optional(CONF_NEGATE, default=False): cv.boolean, -}) +PIN_SCHEMA = vol.Schema( + { + vol.Required(CONF_NAME): cv.string, + vol.Optional(CONF_INITIAL, default=False): cv.boolean, + vol.Optional(CONF_NEGATE, default=False): cv.boolean, + } +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_PINS, default={}): - vol.Schema({cv.positive_int: PIN_SCHEMA}), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_PINS, default={}): vol.Schema({cv.positive_int: PIN_SCHEMA})} +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -50,7 +51,7 @@ class ArduinoSwitch(SwitchDevice): self._pin = pin self._name = options.get(CONF_NAME) self.pin_type = CONF_TYPE - self.direction = 'out' + self.direction = "out" self._state = options.get(CONF_INITIAL) diff --git a/homeassistant/components/arest/binary_sensor.py b/homeassistant/components/arest/binary_sensor.py index 3fd669a2bba..96ffa371864 100644 --- a/homeassistant/components/arest/binary_sensor.py +++ b/homeassistant/components/arest/binary_sensor.py @@ -6,9 +6,11 @@ import requests import voluptuous as vol from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA) -from homeassistant.const import ( - CONF_RESOURCE, CONF_PIN, CONF_NAME, CONF_DEVICE_CLASS) + BinarySensorDevice, + PLATFORM_SCHEMA, + DEVICE_CLASSES_SCHEMA, +) +from homeassistant.const import CONF_RESOURCE, CONF_PIN, CONF_NAME, CONF_DEVICE_CLASS from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv @@ -16,12 +18,14 @@ _LOGGER = logging.getLogger(__name__) MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_RESOURCE): cv.url, - vol.Optional(CONF_NAME): cv.string, - vol.Required(CONF_PIN): cv.string, - vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_RESOURCE): cv.url, + vol.Optional(CONF_NAME): cv.string, + vol.Required(CONF_PIN): cv.string, + vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -33,8 +37,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): try: response = requests.get(resource, timeout=10).json() except requests.exceptions.MissingSchema: - _LOGGER.error("Missing resource or schema in configuration. " - "Add http:// to your URL") + _LOGGER.error( + "Missing resource or schema in configuration. " "Add http:// to your URL" + ) return False except requests.exceptions.ConnectionError: _LOGGER.error("No route to device at %s", resource) @@ -42,9 +47,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None): arest = ArestData(resource, pin) - add_entities([ArestBinarySensor( - arest, resource, config.get(CONF_NAME, response[CONF_NAME]), - device_class, pin)], True) + add_entities( + [ + ArestBinarySensor( + arest, + resource, + config.get(CONF_NAME, response[CONF_NAME]), + device_class, + pin, + ) + ], + True, + ) class ArestBinarySensor(BinarySensorDevice): @@ -60,7 +74,8 @@ class ArestBinarySensor(BinarySensorDevice): if self._pin is not None: request = requests.get( - '{}/mode/{}/i'.format(self._resource, self._pin), timeout=10) + "{}/mode/{}/i".format(self._resource, self._pin), timeout=10 + ) if request.status_code != 200: _LOGGER.error("Can't set mode of %s", self._resource) @@ -72,7 +87,7 @@ class ArestBinarySensor(BinarySensorDevice): @property def is_on(self): """Return true if the binary sensor is on.""" - return bool(self.arest.data.get('state')) + return bool(self.arest.data.get("state")) @property def device_class(self): @@ -97,8 +112,9 @@ class ArestData: def update(self): """Get the latest data from aREST device.""" try: - response = requests.get('{}/digital/{}'.format( - self._resource, self._pin), timeout=10) - self.data = {'state': response.json()['return_value']} + response = requests.get( + "{}/digital/{}".format(self._resource, self._pin), timeout=10 + ) + self.data = {"state": response.json()["return_value"]} except requests.exceptions.ConnectionError: _LOGGER.error("No route to device '%s'", self._resource) diff --git a/homeassistant/components/arest/sensor.py b/homeassistant/components/arest/sensor.py index fc443cd60b6..533adeccb5e 100644 --- a/homeassistant/components/arest/sensor.py +++ b/homeassistant/components/arest/sensor.py @@ -7,8 +7,12 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE, CONF_RESOURCE, - CONF_MONITORED_VARIABLES, CONF_NAME) + CONF_UNIT_OF_MEASUREMENT, + CONF_VALUE_TEMPLATE, + CONF_RESOURCE, + CONF_MONITORED_VARIABLES, + CONF_NAME, +) from homeassistant.exceptions import TemplateError from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle @@ -18,25 +22,31 @@ _LOGGER = logging.getLogger(__name__) MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30) -CONF_FUNCTIONS = 'functions' -CONF_PINS = 'pins' +CONF_FUNCTIONS = "functions" +CONF_PINS = "pins" -DEFAULT_NAME = 'aREST sensor' +DEFAULT_NAME = "aREST sensor" -PIN_VARIABLE_SCHEMA = vol.Schema({ - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, - vol.Optional(CONF_VALUE_TEMPLATE): cv.template, -}) +PIN_VARIABLE_SCHEMA = vol.Schema( + { + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, + } +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_RESOURCE): cv.url, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PINS, default={}): - vol.Schema({cv.string: PIN_VARIABLE_SCHEMA}), - vol.Optional(CONF_MONITORED_VARIABLES, default={}): - vol.Schema({cv.string: PIN_VARIABLE_SCHEMA}), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_RESOURCE): cv.url, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PINS, default={}): vol.Schema( + {cv.string: PIN_VARIABLE_SCHEMA} + ), + vol.Optional(CONF_MONITORED_VARIABLES, default={}): vol.Schema( + {cv.string: PIN_VARIABLE_SCHEMA} + ), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -48,8 +58,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): try: response = requests.get(resource, timeout=10).json() except requests.exceptions.MissingSchema: - _LOGGER.error("Missing resource or schema in configuration. " - "Add http:// to your URL") + _LOGGER.error( + "Missing resource or schema in configuration. " "Add http:// to your URL" + ) return False except requests.exceptions.ConnectionError: _LOGGER.error("No route to device at %s", resource) @@ -66,7 +77,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): def _render(value): try: - return value_template.async_render({'value': value}) + return value_template.async_render({"value": value}) except TemplateError: _LOGGER.exception("Error parsing value") return value @@ -77,25 +88,37 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if var_conf is not None: for variable, var_data in var_conf.items(): - if variable not in response['variables']: + if variable not in response["variables"]: _LOGGER.error("Variable: %s does not exist", variable) continue renderer = make_renderer(var_data.get(CONF_VALUE_TEMPLATE)) - dev.append(ArestSensor( - arest, resource, config.get(CONF_NAME, response[CONF_NAME]), - var_data.get(CONF_NAME, variable), variable=variable, - unit_of_measurement=var_data.get(CONF_UNIT_OF_MEASUREMENT), - renderer=renderer)) + dev.append( + ArestSensor( + arest, + resource, + config.get(CONF_NAME, response[CONF_NAME]), + var_data.get(CONF_NAME, variable), + variable=variable, + unit_of_measurement=var_data.get(CONF_UNIT_OF_MEASUREMENT), + renderer=renderer, + ) + ) if pins is not None: for pinnum, pin in pins.items(): renderer = make_renderer(pin.get(CONF_VALUE_TEMPLATE)) - dev.append(ArestSensor( - ArestData(resource, pinnum), resource, - config.get(CONF_NAME, response[CONF_NAME]), pin.get(CONF_NAME), - pin=pinnum, unit_of_measurement=pin.get( - CONF_UNIT_OF_MEASUREMENT), renderer=renderer)) + dev.append( + ArestSensor( + ArestData(resource, pinnum), + resource, + config.get(CONF_NAME, response[CONF_NAME]), + pin.get(CONF_NAME), + pin=pinnum, + unit_of_measurement=pin.get(CONF_UNIT_OF_MEASUREMENT), + renderer=renderer, + ) + ) add_entities(dev, True) @@ -103,12 +126,21 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class ArestSensor(Entity): """Implementation of an aREST sensor for exposed variables.""" - def __init__(self, arest, resource, location, name, variable=None, - pin=None, unit_of_measurement=None, renderer=None): + def __init__( + self, + arest, + resource, + location, + name, + variable=None, + pin=None, + unit_of_measurement=None, + renderer=None, + ): """Initialize the sensor.""" self.arest = arest self._resource = resource - self._name = '{} {}'.format(location.title(), name.title()) + self._name = "{} {}".format(location.title(), name.title()) self._variable = variable self._pin = pin self._state = None @@ -117,7 +149,8 @@ class ArestSensor(Entity): if self._pin is not None: request = requests.get( - '{}/mode/{}/i'.format(self._resource, self._pin), timeout=10) + "{}/mode/{}/i".format(self._resource, self._pin), timeout=10 + ) if request.status_code != 200: _LOGGER.error("Can't set mode of %s", self._resource) @@ -136,11 +169,10 @@ class ArestSensor(Entity): """Return the state of the sensor.""" values = self.arest.data - if 'error' in values: - return values['error'] + if "error" in values: + return values["error"] - value = self._renderer( - values.get('value', values.get(self._variable, None))) + value = self._renderer(values.get("value", values.get(self._variable, None))) return value def update(self): @@ -169,17 +201,20 @@ class ArestData: try: if self._pin is None: response = requests.get(self._resource, timeout=10) - self.data = response.json()['variables'] + self.data = response.json()["variables"] else: try: - if str(self._pin[0]) == 'A': - response = requests.get('{}/analog/{}'.format( - self._resource, self._pin[1:]), timeout=10) - self.data = {'value': response.json()['return_value']} + if str(self._pin[0]) == "A": + response = requests.get( + "{}/analog/{}".format(self._resource, self._pin[1:]), + timeout=10, + ) + self.data = {"value": response.json()["return_value"]} except TypeError: - response = requests.get('{}/digital/{}'.format( - self._resource, self._pin), timeout=10) - self.data = {'value': response.json()['return_value']} + response = requests.get( + "{}/digital/{}".format(self._resource, self._pin), timeout=10 + ) + self.data = {"value": response.json()["return_value"]} self.available = True except requests.exceptions.ConnectionError: _LOGGER.error("No route to device %s", self._resource) diff --git a/homeassistant/components/arest/switch.py b/homeassistant/components/arest/switch.py index 717acc2f336..558df89100e 100644 --- a/homeassistant/components/arest/switch.py +++ b/homeassistant/components/arest/switch.py @@ -5,31 +5,37 @@ import logging import requests import voluptuous as vol -from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) -from homeassistant.const import (CONF_NAME, CONF_RESOURCE) +from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA +from homeassistant.const import CONF_NAME, CONF_RESOURCE import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -CONF_FUNCTIONS = 'functions' -CONF_PINS = 'pins' -CONF_INVERT = 'invert' +CONF_FUNCTIONS = "functions" +CONF_PINS = "pins" +CONF_INVERT = "invert" -DEFAULT_NAME = 'aREST switch' +DEFAULT_NAME = "aREST switch" -PIN_FUNCTION_SCHEMA = vol.Schema({ - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_INVERT, default=False): cv.boolean, -}) +PIN_FUNCTION_SCHEMA = vol.Schema( + { + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_INVERT, default=False): cv.boolean, + } +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_RESOURCE): cv.url, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PINS, default={}): - vol.Schema({cv.string: PIN_FUNCTION_SCHEMA}), - vol.Optional(CONF_FUNCTIONS, default={}): - vol.Schema({cv.string: PIN_FUNCTION_SCHEMA}), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_RESOURCE): cv.url, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PINS, default={}): vol.Schema( + {cv.string: PIN_FUNCTION_SCHEMA} + ), + vol.Optional(CONF_FUNCTIONS, default={}): vol.Schema( + {cv.string: PIN_FUNCTION_SCHEMA} + ), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -39,8 +45,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): try: response = requests.get(resource, timeout=10) except requests.exceptions.MissingSchema: - _LOGGER.error("Missing resource or schema in configuration. " - "Add http:// to your URL") + _LOGGER.error( + "Missing resource or schema in configuration. " "Add http:// to your URL" + ) return False except requests.exceptions.ConnectionError: _LOGGER.error("No route to device at %s", resource) @@ -49,15 +56,26 @@ def setup_platform(hass, config, add_entities, discovery_info=None): dev = [] pins = config.get(CONF_PINS) for pinnum, pin in pins.items(): - dev.append(ArestSwitchPin( - resource, config.get(CONF_NAME, response.json()[CONF_NAME]), - pin.get(CONF_NAME), pinnum, pin.get(CONF_INVERT))) + dev.append( + ArestSwitchPin( + resource, + config.get(CONF_NAME, response.json()[CONF_NAME]), + pin.get(CONF_NAME), + pinnum, + pin.get(CONF_INVERT), + ) + ) functions = config.get(CONF_FUNCTIONS) for funcname, func in functions.items(): - dev.append(ArestSwitchFunction( - resource, config.get(CONF_NAME, response.json()[CONF_NAME]), - func.get(CONF_NAME), funcname)) + dev.append( + ArestSwitchFunction( + resource, + config.get(CONF_NAME, response.json()[CONF_NAME]), + func.get(CONF_NAME), + funcname, + ) + ) add_entities(dev) @@ -68,7 +86,7 @@ class ArestSwitchBase(SwitchDevice): def __init__(self, resource, location, name): """Initialize the switch.""" self._resource = resource - self._name = '{} {}'.format(location.title(), name.title()) + self._name = "{} {}".format(location.title(), name.title()) self._state = None self._available = True @@ -96,15 +114,14 @@ class ArestSwitchFunction(ArestSwitchBase): super().__init__(resource, location, name) self._func = func - request = requests.get( - '{}/{}'.format(self._resource, self._func), timeout=10) + request = requests.get("{}/{}".format(self._resource, self._func), timeout=10) if request.status_code != 200: _LOGGER.error("Can't find function") return try: - request.json()['return_value'] + request.json()["return_value"] except KeyError: _LOGGER.error("No return_value received") except ValueError: @@ -113,33 +130,38 @@ class ArestSwitchFunction(ArestSwitchBase): def turn_on(self, **kwargs): """Turn the device on.""" request = requests.get( - '{}/{}'.format(self._resource, self._func), timeout=10, - params={'params': '1'}) + "{}/{}".format(self._resource, self._func), + timeout=10, + params={"params": "1"}, + ) if request.status_code == 200: self._state = True else: - _LOGGER.error( - "Can't turn on function %s at %s", self._func, self._resource) + _LOGGER.error("Can't turn on function %s at %s", self._func, self._resource) def turn_off(self, **kwargs): """Turn the device off.""" request = requests.get( - '{}/{}'.format(self._resource, self._func), timeout=10, - params={'params': '0'}) + "{}/{}".format(self._resource, self._func), + timeout=10, + params={"params": "0"}, + ) if request.status_code == 200: self._state = False else: _LOGGER.error( - "Can't turn off function %s at %s", self._func, self._resource) + "Can't turn off function %s at %s", self._func, self._resource + ) def update(self): """Get the latest data from aREST API and update the state.""" try: request = requests.get( - '{}/{}'.format(self._resource, self._func), timeout=10) - self._state = request.json()['return_value'] != 0 + "{}/{}".format(self._resource, self._func), timeout=10 + ) + self._state = request.json()["return_value"] != 0 self._available = True except requests.exceptions.ConnectionError: _LOGGER.warning("No route to device %s", self._resource) @@ -156,7 +178,8 @@ class ArestSwitchPin(ArestSwitchBase): self.invert = invert request = requests.get( - '{}/mode/{}/o'.format(self._resource, self._pin), timeout=10) + "{}/mode/{}/o".format(self._resource, self._pin), timeout=10 + ) if request.status_code != 200: _LOGGER.error("Can't set mode") self._available = False @@ -165,35 +188,34 @@ class ArestSwitchPin(ArestSwitchBase): """Turn the device on.""" turn_on_payload = int(not self.invert) request = requests.get( - '{}/digital/{}/{}'.format(self._resource, self._pin, - turn_on_payload), - timeout=10) + "{}/digital/{}/{}".format(self._resource, self._pin, turn_on_payload), + timeout=10, + ) if request.status_code == 200: self._state = True else: - _LOGGER.error( - "Can't turn on pin %s at %s", self._pin, self._resource) + _LOGGER.error("Can't turn on pin %s at %s", self._pin, self._resource) def turn_off(self, **kwargs): """Turn the device off.""" turn_off_payload = int(self.invert) request = requests.get( - '{}/digital/{}/{}'.format(self._resource, self._pin, - turn_off_payload), - timeout=10) + "{}/digital/{}/{}".format(self._resource, self._pin, turn_off_payload), + timeout=10, + ) if request.status_code == 200: self._state = False else: - _LOGGER.error( - "Can't turn off pin %s at %s", self._pin, self._resource) + _LOGGER.error("Can't turn off pin %s at %s", self._pin, self._resource) def update(self): """Get the latest data from aREST API and update the state.""" try: request = requests.get( - '{}/digital/{}'.format(self._resource, self._pin), timeout=10) + "{}/digital/{}".format(self._resource, self._pin), timeout=10 + ) status_value = int(self.invert) - self._state = request.json()['return_value'] != status_value + self._state = request.json()["return_value"] != status_value self._available = True except requests.exceptions.ConnectionError: _LOGGER.warning("No route to device %s", self._resource) diff --git a/homeassistant/components/arlo/__init__.py b/homeassistant/components/arlo/__init__.py index 38230c2f05f..80fa37b6787 100644 --- a/homeassistant/components/arlo/__init__.py +++ b/homeassistant/components/arlo/__init__.py @@ -6,8 +6,7 @@ import voluptuous as vol from requests.exceptions import HTTPError, ConnectTimeout from homeassistant.helpers import config_validation as cv -from homeassistant.const import ( - CONF_USERNAME, CONF_PASSWORD, CONF_SCAN_INTERVAL) +from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_SCAN_INTERVAL from homeassistant.helpers.event import track_time_interval from homeassistant.helpers.dispatcher import dispatcher_send @@ -15,25 +14,29 @@ _LOGGER = logging.getLogger(__name__) ATTRIBUTION = "Data provided by arlo.netgear.com" -DATA_ARLO = 'data_arlo' -DEFAULT_BRAND = 'Netgear Arlo' -DOMAIN = 'arlo' +DATA_ARLO = "data_arlo" +DEFAULT_BRAND = "Netgear Arlo" +DOMAIN = "arlo" -NOTIFICATION_ID = 'arlo_notification' -NOTIFICATION_TITLE = 'Arlo Component Setup' +NOTIFICATION_ID = "arlo_notification" +NOTIFICATION_TITLE = "Arlo Component Setup" SCAN_INTERVAL = timedelta(seconds=60) SIGNAL_UPDATE_ARLO = "arlo_update" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL): - cv.time_period, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL): cv.time_period, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): @@ -51,8 +54,7 @@ def setup(hass, config): return False # assign refresh period to base station thread - arlo_base_station = next(( - station for station in arlo.base_stations), None) + arlo_base_station = next((station for station in arlo.base_stations), None) if arlo_base_station is not None: arlo_base_station.refresh_rate = scan_interval.total_seconds() @@ -65,22 +67,22 @@ def setup(hass, config): except (ConnectTimeout, HTTPError) as ex: _LOGGER.error("Unable to connect to Netgear Arlo: %s", str(ex)) hass.components.persistent_notification.create( - 'Error: {}
' - 'You will need to restart hass after fixing.' - ''.format(ex), + "Error: {}
" + "You will need to restart hass after fixing." + "".format(ex), title=NOTIFICATION_TITLE, - notification_id=NOTIFICATION_ID) + notification_id=NOTIFICATION_ID, + ) return False def hub_refresh(event_time): """Call ArloHub to refresh information.""" _LOGGER.debug("Updating Arlo Hub component") - hass.data[DATA_ARLO].update(update_cameras=True, - update_base_station=True) + hass.data[DATA_ARLO].update(update_cameras=True, update_base_station=True) dispatcher_send(hass, SIGNAL_UPDATE_ARLO) # register service - hass.services.register(DOMAIN, 'update', hub_refresh) + hass.services.register(DOMAIN, "update", hub_refresh) # register scan interval for ArloHub track_time_interval(hass, hub_refresh, scan_interval) diff --git a/homeassistant/components/arlo/alarm_control_panel.py b/homeassistant/components/arlo/alarm_control_panel.py index a7addfb86ea..a56b2a63372 100644 --- a/homeassistant/components/arlo/alarm_control_panel.py +++ b/homeassistant/components/arlo/alarm_control_panel.py @@ -4,10 +4,16 @@ import logging import voluptuous as vol from homeassistant.components.alarm_control_panel import ( - PLATFORM_SCHEMA, AlarmControlPanel) + PLATFORM_SCHEMA, + AlarmControlPanel, +) from homeassistant.const import ( - ATTR_ATTRIBUTION, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, - STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED) + ATTR_ATTRIBUTION, + STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_HOME, + STATE_ALARM_ARMED_NIGHT, + STATE_ALARM_DISARMED, +) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -16,21 +22,23 @@ from . import ATTRIBUTION, DATA_ARLO, SIGNAL_UPDATE_ARLO _LOGGER = logging.getLogger(__name__) -ARMED = 'armed' +ARMED = "armed" -CONF_HOME_MODE_NAME = 'home_mode_name' -CONF_AWAY_MODE_NAME = 'away_mode_name' -CONF_NIGHT_MODE_NAME = 'night_mode_name' +CONF_HOME_MODE_NAME = "home_mode_name" +CONF_AWAY_MODE_NAME = "away_mode_name" +CONF_NIGHT_MODE_NAME = "night_mode_name" -DISARMED = 'disarmed' +DISARMED = "disarmed" -ICON = 'mdi:security' +ICON = "mdi:security" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_HOME_MODE_NAME, default=ARMED): cv.string, - vol.Optional(CONF_AWAY_MODE_NAME, default=ARMED): cv.string, - vol.Optional(CONF_NIGHT_MODE_NAME, default=ARMED): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_HOME_MODE_NAME, default=ARMED): cv.string, + vol.Optional(CONF_AWAY_MODE_NAME, default=ARMED): cv.string, + vol.Optional(CONF_NIGHT_MODE_NAME, default=ARMED): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -45,8 +53,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): night_mode_name = config.get(CONF_NIGHT_MODE_NAME) base_stations = [] for base_station in arlo.base_stations: - base_stations.append(ArloBaseStation(base_station, home_mode_name, - away_mode_name, night_mode_name)) + base_stations.append( + ArloBaseStation( + base_station, home_mode_name, away_mode_name, night_mode_name + ) + ) add_entities(base_stations, True) @@ -68,8 +79,7 @@ class ArloBaseStation(AlarmControlPanel): async def async_added_to_hass(self): """Register callbacks.""" - async_dispatcher_connect( - self.hass, SIGNAL_UPDATE_ARLO, self._update_callback) + async_dispatcher_connect(self.hass, SIGNAL_UPDATE_ARLO, self._update_callback) @callback def _update_callback(self): @@ -116,7 +126,7 @@ class ArloBaseStation(AlarmControlPanel): """Return the state attributes.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, - 'device_id': self._base_station.device_id + "device_id": self._base_station.device_id, } def _get_state_from_mode(self, mode): diff --git a/homeassistant/components/arlo/camera.py b/homeassistant/components/arlo/camera.py index 166e0781044..a05dc40a9ef 100644 --- a/homeassistant/components/arlo/camera.py +++ b/homeassistant/components/arlo/camera.py @@ -15,30 +15,26 @@ from . import DATA_ARLO, DEFAULT_BRAND, SIGNAL_UPDATE_ARLO _LOGGER = logging.getLogger(__name__) -ARLO_MODE_ARMED = 'armed' -ARLO_MODE_DISARMED = 'disarmed' +ARLO_MODE_ARMED = "armed" +ARLO_MODE_DISARMED = "disarmed" -ATTR_BRIGHTNESS = 'brightness' -ATTR_FLIPPED = 'flipped' -ATTR_MIRRORED = 'mirrored' -ATTR_MOTION = 'motion_detection_sensitivity' -ATTR_POWERSAVE = 'power_save_mode' -ATTR_SIGNAL_STRENGTH = 'signal_strength' -ATTR_UNSEEN_VIDEOS = 'unseen_videos' -ATTR_LAST_REFRESH = 'last_refresh' +ATTR_BRIGHTNESS = "brightness" +ATTR_FLIPPED = "flipped" +ATTR_MIRRORED = "mirrored" +ATTR_MOTION = "motion_detection_sensitivity" +ATTR_POWERSAVE = "power_save_mode" +ATTR_SIGNAL_STRENGTH = "signal_strength" +ATTR_UNSEEN_VIDEOS = "unseen_videos" +ATTR_LAST_REFRESH = "last_refresh" -CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments' -DEFAULT_ARGUMENTS = '-pred 1' +CONF_FFMPEG_ARGUMENTS = "ffmpeg_arguments" +DEFAULT_ARGUMENTS = "-pred 1" -POWERSAVE_MODE_MAPPING = { - 1: 'best_battery_life', - 2: 'optimized', - 3: 'best_video' -} +POWERSAVE_MODE_MAPPING = {1: "best_battery_life", 2: "optimized", 3: "best_video"} -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_FFMPEG_ARGUMENTS, default=DEFAULT_ARGUMENTS): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Optional(CONF_FFMPEG_ARGUMENTS, default=DEFAULT_ARGUMENTS): cv.string} +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -72,8 +68,7 @@ class ArloCam(Camera): async def async_added_to_hass(self): """Register callbacks.""" - async_dispatcher_connect( - self.hass, SIGNAL_UPDATE_ARLO, self._update_callback) + async_dispatcher_connect(self.hass, SIGNAL_UPDATE_ARLO, self._update_callback) @callback def _update_callback(self): @@ -83,23 +78,26 @@ class ArloCam(Camera): async def handle_async_mjpeg_stream(self, request): """Generate an HTTP MJPEG stream from the camera.""" from haffmpeg.camera import CameraMjpeg + video = self._camera.last_video if not video: - error_msg = \ - 'Video not found for {0}. Is it older than {1} days?'.format( - self.name, self._camera.min_days_vdo_cache) + error_msg = "Video not found for {0}. Is it older than {1} days?".format( + self.name, self._camera.min_days_vdo_cache + ) _LOGGER.error(error_msg) return stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop) - await stream.open_camera( - video.video_url, extra_cmd=self._ffmpeg_arguments) + await stream.open_camera(video.video_url, extra_cmd=self._ffmpeg_arguments) try: stream_reader = await stream.get_reader() return await async_aiohttp_proxy_stream( - self.hass, request, stream_reader, - self._ffmpeg.ffmpeg_stream_content_type) + self.hass, + request, + stream_reader, + self._ffmpeg.ffmpeg_stream_content_type, + ) finally: await stream.close() @@ -112,17 +110,21 @@ class ArloCam(Camera): def device_state_attributes(self): """Return the state attributes.""" return { - name: value for name, value in ( + name: value + for name, value in ( (ATTR_BATTERY_LEVEL, self._camera.battery_level), (ATTR_BRIGHTNESS, self._camera.brightness), (ATTR_FLIPPED, self._camera.flip_state), (ATTR_MIRRORED, self._camera.mirror_state), (ATTR_MOTION, self._camera.motion_detection_sensitivity), - (ATTR_POWERSAVE, POWERSAVE_MODE_MAPPING.get( - self._camera.powersave_mode)), + ( + ATTR_POWERSAVE, + POWERSAVE_MODE_MAPPING.get(self._camera.powersave_mode), + ), (ATTR_SIGNAL_STRENGTH, self._camera.signal_strength), (ATTR_UNSEEN_VIDEOS, self._camera.unseen_videos), - ) if value is not None + ) + if value is not None } @property diff --git a/homeassistant/components/arlo/sensor.py b/homeassistant/components/arlo/sensor.py index f83caec386b..aadd5a48d37 100644 --- a/homeassistant/components/arlo/sensor.py +++ b/homeassistant/components/arlo/sensor.py @@ -5,8 +5,12 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - ATTR_ATTRIBUTION, CONF_MONITORED_CONDITIONS, DEVICE_CLASS_HUMIDITY, - DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS) + ATTR_ATTRIBUTION, + CONF_MONITORED_CONDITIONS, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + TEMP_CELSIUS, +) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -19,20 +23,23 @@ _LOGGER = logging.getLogger(__name__) # sensor_type [ description, unit, icon ] SENSOR_TYPES = { - 'last_capture': ['Last', None, 'run-fast'], - 'total_cameras': ['Arlo Cameras', None, 'video'], - 'captured_today': ['Captured Today', None, 'file-video'], - 'battery_level': ['Battery Level', '%', 'battery-50'], - 'signal_strength': ['Signal Strength', None, 'signal'], - 'temperature': ['Temperature', TEMP_CELSIUS, 'thermometer'], - 'humidity': ['Humidity', '%', 'water-percent'], - 'air_quality': ['Air Quality', 'ppm', 'biohazard'] + "last_capture": ["Last", None, "run-fast"], + "total_cameras": ["Arlo Cameras", None, "video"], + "captured_today": ["Captured Today", None, "file-video"], + "battery_level": ["Battery Level", "%", "battery-50"], + "signal_strength": ["Signal Strength", None, "signal"], + "temperature": ["Temperature", TEMP_CELSIUS, "thermometer"], + "humidity": ["Humidity", "%", "water-percent"], + "air_quality": ["Air Quality", "ppm", "biohazard"], } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)): - vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)): vol.All( + cv.ensure_list, [vol.In(SENSOR_TYPES)] + ) + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -43,23 +50,24 @@ def setup_platform(hass, config, add_entities, discovery_info=None): sensors = [] for sensor_type in config.get(CONF_MONITORED_CONDITIONS): - if sensor_type == 'total_cameras': - sensors.append(ArloSensor( - SENSOR_TYPES[sensor_type][0], arlo, sensor_type)) + if sensor_type == "total_cameras": + sensors.append(ArloSensor(SENSOR_TYPES[sensor_type][0], arlo, sensor_type)) else: for camera in arlo.cameras: - if sensor_type in ('temperature', 'humidity', 'air_quality'): + if sensor_type in ("temperature", "humidity", "air_quality"): continue - name = '{0} {1}'.format( - SENSOR_TYPES[sensor_type][0], camera.name) + name = "{0} {1}".format(SENSOR_TYPES[sensor_type][0], camera.name) sensors.append(ArloSensor(name, camera, sensor_type)) for base_station in arlo.base_stations: - if sensor_type in ('temperature', 'humidity', 'air_quality') \ - and base_station.model_id == 'ABC1000': - name = '{0} {1}'.format( - SENSOR_TYPES[sensor_type][0], base_station.name) + if ( + sensor_type in ("temperature", "humidity", "air_quality") + and base_station.model_id == "ABC1000" + ): + name = "{0} {1}".format( + SENSOR_TYPES[sensor_type][0], base_station.name + ) sensors.append(ArloSensor(name, base_station, sensor_type)) add_entities(sensors, True) @@ -70,12 +78,12 @@ class ArloSensor(Entity): def __init__(self, name, device, sensor_type): """Initialize an Arlo sensor.""" - _LOGGER.debug('ArloSensor created for %s', name) + _LOGGER.debug("ArloSensor created for %s", name) self._name = name self._data = device self._sensor_type = sensor_type self._state = None - self._icon = 'mdi:{}'.format(SENSOR_TYPES.get(self._sensor_type)[2]) + self._icon = "mdi:{}".format(SENSOR_TYPES.get(self._sensor_type)[2]) @property def name(self): @@ -84,8 +92,7 @@ class ArloSensor(Entity): async def async_added_to_hass(self): """Register callbacks.""" - async_dispatcher_connect( - self.hass, SIGNAL_UPDATE_ARLO, self._update_callback) + async_dispatcher_connect(self.hass, SIGNAL_UPDATE_ARLO, self._update_callback) @callback def _update_callback(self): @@ -100,9 +107,10 @@ class ArloSensor(Entity): @property def icon(self): """Icon to use in the frontend, if any.""" - if self._sensor_type == 'battery_level' and self._state is not None: - return icon_for_battery_level(battery_level=int(self._state), - charging=False) + if self._sensor_type == "battery_level" and self._state is not None: + return icon_for_battery_level( + battery_level=int(self._state), charging=False + ) return self._icon @property @@ -113,57 +121,57 @@ class ArloSensor(Entity): @property def device_class(self): """Return the device class of the sensor.""" - if self._sensor_type == 'temperature': + if self._sensor_type == "temperature": return DEVICE_CLASS_TEMPERATURE - if self._sensor_type == 'humidity': + if self._sensor_type == "humidity": return DEVICE_CLASS_HUMIDITY return None def update(self): """Get the latest data and updates the state.""" _LOGGER.debug("Updating Arlo sensor %s", self.name) - if self._sensor_type == 'total_cameras': + if self._sensor_type == "total_cameras": self._state = len(self._data.cameras) - elif self._sensor_type == 'captured_today': + elif self._sensor_type == "captured_today": self._state = len(self._data.captured_today) - elif self._sensor_type == 'last_capture': + elif self._sensor_type == "last_capture": try: video = self._data.last_video self._state = video.created_at_pretty("%m-%d-%Y %H:%M:%S") except (AttributeError, IndexError): - error_msg = \ - 'Video not found for {0}. Older than {1} days?'.format( - self.name, self._data.min_days_vdo_cache) + error_msg = "Video not found for {0}. Older than {1} days?".format( + self.name, self._data.min_days_vdo_cache + ) _LOGGER.debug(error_msg) self._state = None - elif self._sensor_type == 'battery_level': + elif self._sensor_type == "battery_level": try: self._state = self._data.battery_level except TypeError: self._state = None - elif self._sensor_type == 'signal_strength': + elif self._sensor_type == "signal_strength": try: self._state = self._data.signal_strength except TypeError: self._state = None - elif self._sensor_type == 'temperature': + elif self._sensor_type == "temperature": try: self._state = self._data.ambient_temperature except TypeError: self._state = None - elif self._sensor_type == 'humidity': + elif self._sensor_type == "humidity": try: self._state = self._data.ambient_humidity except TypeError: self._state = None - elif self._sensor_type == 'air_quality': + elif self._sensor_type == "air_quality": try: self._state = self._data.ambient_air_quality except TypeError: @@ -175,9 +183,9 @@ class ArloSensor(Entity): attrs = {} attrs[ATTR_ATTRIBUTION] = ATTRIBUTION - attrs['brand'] = DEFAULT_BRAND + attrs["brand"] = DEFAULT_BRAND - if self._sensor_type != 'total_cameras': - attrs['model'] = self._data.model_id + if self._sensor_type != "total_cameras": + attrs["model"] = self._data.model_id return attrs diff --git a/homeassistant/components/aruba/device_tracker.py b/homeassistant/components/aruba/device_tracker.py index cde144e68f6..f93533b6beb 100644 --- a/homeassistant/components/aruba/device_tracker.py +++ b/homeassistant/components/aruba/device_tracker.py @@ -6,21 +6,27 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.device_tracker import ( - DOMAIN, PLATFORM_SCHEMA, DeviceScanner) + DOMAIN, + PLATFORM_SCHEMA, + DeviceScanner, +) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME _LOGGER = logging.getLogger(__name__) _DEVICES_REGEX = re.compile( - r'(?P([^\s]+)?)\s+' + - r'(?P([0-9]{1,3}[\.]){3}[0-9]{1,3})\s+' + - r'(?P([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))\s+') + r"(?P([^\s]+)?)\s+" + + r"(?P([0-9]{1,3}[\.]){3}[0-9]{1,3})\s+" + + r"(?P([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))\s+" +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_USERNAME): cv.string -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string, + } +) def get_scanner(hass, config): @@ -48,15 +54,15 @@ class ArubaDeviceScanner(DeviceScanner): def scan_devices(self): """Scan for new devices and return a list with found device IDs.""" self._update_info() - return [client['mac'] for client in self.last_results] + return [client["mac"] for client in self.last_results] def get_device_name(self, device): """Return the name of the given device or None if we don't know.""" if not self.last_results: return None for client in self.last_results: - if client['mac'] == device: - return client['name'] + if client["mac"] == device: + return client["name"] return None def _update_info(self): @@ -77,13 +83,21 @@ class ArubaDeviceScanner(DeviceScanner): def get_aruba_data(self): """Retrieve data from Aruba Access Point and return parsed result.""" import pexpect - connect = 'ssh {}@{}' + + connect = "ssh {}@{}" ssh = pexpect.spawn(connect.format(self.username, self.host)) - query = ssh.expect(['password:', pexpect.TIMEOUT, pexpect.EOF, - 'continue connecting (yes/no)?', - 'Host key verification failed.', - 'Connection refused', - 'Connection timed out'], timeout=120) + query = ssh.expect( + [ + "password:", + pexpect.TIMEOUT, + pexpect.EOF, + "continue connecting (yes/no)?", + "Host key verification failed.", + "Connection refused", + "Connection timed out", + ], + timeout=120, + ) if query == 1: _LOGGER.error("Timeout") return @@ -91,8 +105,8 @@ class ArubaDeviceScanner(DeviceScanner): _LOGGER.error("Unexpected response from router") return if query == 3: - ssh.sendline('yes') - ssh.expect('password:') + ssh.sendline("yes") + ssh.expect("password:") elif query == 4: _LOGGER.error("Host key changed") return @@ -103,19 +117,19 @@ class ArubaDeviceScanner(DeviceScanner): _LOGGER.error("Connection timed out") return ssh.sendline(self.password) - ssh.expect('#') - ssh.sendline('show clients') - ssh.expect('#') - devices_result = ssh.before.split(b'\r\n') - ssh.sendline('exit') + ssh.expect("#") + ssh.sendline("show clients") + ssh.expect("#") + devices_result = ssh.before.split(b"\r\n") + ssh.sendline("exit") devices = {} for device in devices_result: - match = _DEVICES_REGEX.search(device.decode('utf-8')) + match = _DEVICES_REGEX.search(device.decode("utf-8")) if match: - devices[match.group('ip')] = { - 'ip': match.group('ip'), - 'mac': match.group('mac').upper(), - 'name': match.group('name') + devices[match.group("ip")] = { + "ip": match.group("ip"), + "mac": match.group("mac").upper(), + "name": match.group("name"), } return devices diff --git a/homeassistant/components/arwn/sensor.py b/homeassistant/components/arwn/sensor.py index 94b552c6eba..23cd811a3e0 100644 --- a/homeassistant/components/arwn/sensor.py +++ b/homeassistant/components/arwn/sensor.py @@ -10,10 +10,10 @@ from homeassistant.util import slugify _LOGGER = logging.getLogger(__name__) -DOMAIN = 'arwn' +DOMAIN = "arwn" -DATA_ARWN = 'arwn' -TOPIC = 'arwn/#' +DATA_ARWN = "arwn" +TOPIC = "arwn/#" def discover_sensors(topic, payload): @@ -21,39 +21,41 @@ def discover_sensors(topic, payload): Async friendly. """ - parts = topic.split('/') - unit = payload.get('units', '') + parts = topic.split("/") + unit = payload.get("units", "") domain = parts[1] - if domain == 'temperature': + if domain == "temperature": name = parts[2] - if unit == 'F': + if unit == "F": unit = TEMP_FAHRENHEIT else: unit = TEMP_CELSIUS - return ArwnSensor(name, 'temp', unit) + return ArwnSensor(name, "temp", unit) if domain == "moisture": name = parts[2] + " Moisture" - return ArwnSensor(name, 'moisture', unit, "mdi:water-percent") + return ArwnSensor(name, "moisture", unit, "mdi:water-percent") if domain == "rain": if len(parts) >= 3 and parts[2] == "today": - return ArwnSensor("Rain Since Midnight", 'since_midnight', - "in", "mdi:water") - if domain == 'barometer': - return ArwnSensor('Barometer', 'pressure', unit, - "mdi:thermometer-lines") - if domain == 'wind': - return (ArwnSensor('Wind Speed', 'speed', unit, "mdi:speedometer"), - ArwnSensor('Wind Gust', 'gust', unit, "mdi:speedometer"), - ArwnSensor('Wind Direction', 'direction', '°', "mdi:compass")) + return ArwnSensor( + "Rain Since Midnight", "since_midnight", "in", "mdi:water" + ) + if domain == "barometer": + return ArwnSensor("Barometer", "pressure", unit, "mdi:thermometer-lines") + if domain == "wind": + return ( + ArwnSensor("Wind Speed", "speed", unit, "mdi:speedometer"), + ArwnSensor("Wind Gust", "gust", unit, "mdi:speedometer"), + ArwnSensor("Wind Direction", "direction", "°", "mdi:compass"), + ) def _slug(name): - return 'sensor.arwn_{}'.format(slugify(name)) + return "sensor.arwn_{}".format(slugify(name)) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the ARWN platform.""" + @callback def async_sensor_event_received(msg): """Process events as sensors. @@ -78,24 +80,25 @@ async def async_setup_platform(hass, config, async_add_entities, store = hass.data[DATA_ARWN] = {} if isinstance(sensors, ArwnSensor): - sensors = (sensors, ) + sensors = (sensors,) - if 'timestamp' in event: - del event['timestamp'] + if "timestamp" in event: + del event["timestamp"] for sensor in sensors: if sensor.name not in store: sensor.hass = hass sensor.set_event(event) store[sensor.name] = sensor - _LOGGER.debug("Registering new sensor %(name)s => %(event)s", - dict(name=sensor.name, event=event)) + _LOGGER.debug( + "Registering new sensor %(name)s => %(event)s", + dict(name=sensor.name, event=event), + ) async_add_entities((sensor,), True) else: store[sensor.name].set_event(event) - await mqtt.async_subscribe( - hass, TOPIC, async_sensor_event_received, 0) + await mqtt.async_subscribe(hass, TOPIC, async_sensor_event_received, 0) return True diff --git a/homeassistant/components/asterisk_cdr/mailbox.py b/homeassistant/components/asterisk_cdr/mailbox.py index 647067b60d4..4146ca9ddf9 100644 --- a/homeassistant/components/asterisk_cdr/mailbox.py +++ b/homeassistant/components/asterisk_cdr/mailbox.py @@ -11,7 +11,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect _LOGGER = logging.getLogger(__name__) -MAILBOX_NAME = 'asterisk_cdr' +MAILBOX_NAME = "asterisk_cdr" async def async_get_handler(hass, config, discovery_info=None): @@ -26,8 +26,7 @@ class AsteriskCDR(Mailbox): """Initialize Asterisk CDR.""" super().__init__(hass, name) self.cdr = [] - async_dispatcher_connect( - self.hass, SIGNAL_CDR_UPDATE, self._update_callback) + async_dispatcher_connect(self.hass, SIGNAL_CDR_UPDATE, self._update_callback) @callback def _update_callback(self, msg): @@ -40,16 +39,18 @@ class AsteriskCDR(Mailbox): cdr = [] for entry in self.hass.data[ASTERISK_DOMAIN].cdr: timestamp = datetime.datetime.strptime( - entry['time'], "%Y-%m-%d %H:%M:%S").timestamp() + entry["time"], "%Y-%m-%d %H:%M:%S" + ).timestamp() info = { - 'origtime': timestamp, - 'callerid': entry['callerid'], - 'duration': entry['duration'], + "origtime": timestamp, + "callerid": entry["callerid"], + "duration": entry["duration"], } - sha = hashlib.sha256(str(entry).encode('utf-8')).hexdigest() + sha = hashlib.sha256(str(entry).encode("utf-8")).hexdigest() msg = "Destination: {}\nApplication: {}\n Context: {}".format( - entry['dest'], entry['application'], entry['context']) - cdr.append({'info': info, 'sha': sha, 'text': msg}) + entry["dest"], entry["application"], entry["context"] + ) + cdr.append({"info": info, "sha": sha, "text": msg}) self.cdr = cdr async def async_get_messages(self): diff --git a/homeassistant/components/asterisk_mbox/__init__.py b/homeassistant/components/asterisk_mbox/__init__.py index a354226bbc0..6c9412d07d8 100644 --- a/homeassistant/components/asterisk_mbox/__init__.py +++ b/homeassistant/components/asterisk_mbox/__init__.py @@ -7,26 +7,30 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT from homeassistant.core import callback from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.dispatcher import ( - async_dispatcher_send, dispatcher_connect) +from homeassistant.helpers.dispatcher import async_dispatcher_send, dispatcher_connect _LOGGER = logging.getLogger(__name__) -DOMAIN = 'asterisk_mbox' +DOMAIN = "asterisk_mbox" SIGNAL_DISCOVER_PLATFORM = "asterisk_mbox.discover_platform" -SIGNAL_MESSAGE_REQUEST = 'asterisk_mbox.message_request' -SIGNAL_MESSAGE_UPDATE = 'asterisk_mbox.message_updated' -SIGNAL_CDR_UPDATE = 'asterisk_mbox.message_updated' -SIGNAL_CDR_REQUEST = 'asterisk_mbox.message_request' +SIGNAL_MESSAGE_REQUEST = "asterisk_mbox.message_request" +SIGNAL_MESSAGE_UPDATE = "asterisk_mbox.message_updated" +SIGNAL_CDR_UPDATE = "asterisk_mbox.message_updated" +SIGNAL_CDR_REQUEST = "asterisk_mbox.message_request" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_PORT): cv.port, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_PORT): cv.port, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): @@ -48,58 +52,63 @@ class AsteriskData: def __init__(self, hass, host, port, password, config): """Init the Asterisk data object.""" from asterisk_mbox import Client as asteriskClient + self.hass = hass self.config = config self.messages = None self.cdr = None - dispatcher_connect( - self.hass, SIGNAL_MESSAGE_REQUEST, self._request_messages) - dispatcher_connect( - self.hass, SIGNAL_CDR_REQUEST, self._request_cdr) - dispatcher_connect( - self.hass, SIGNAL_DISCOVER_PLATFORM, self._discover_platform) + dispatcher_connect(self.hass, SIGNAL_MESSAGE_REQUEST, self._request_messages) + dispatcher_connect(self.hass, SIGNAL_CDR_REQUEST, self._request_cdr) + dispatcher_connect(self.hass, SIGNAL_DISCOVER_PLATFORM, self._discover_platform) # Only connect after signal connection to ensure we don't miss any self.client = asteriskClient(host, port, password, self.handle_data) @callback def _discover_platform(self, component): _LOGGER.debug("Adding mailbox %s", component) - self.hass.async_create_task(discovery.async_load_platform( - self.hass, "mailbox", component, {}, self.config)) + self.hass.async_create_task( + discovery.async_load_platform( + self.hass, "mailbox", component, {}, self.config + ) + ) @callback def handle_data(self, command, msg): """Handle changes to the mailbox.""" from asterisk_mbox.commands import ( - CMD_MESSAGE_LIST, CMD_MESSAGE_CDR_AVAILABLE, CMD_MESSAGE_CDR) + CMD_MESSAGE_LIST, + CMD_MESSAGE_CDR_AVAILABLE, + CMD_MESSAGE_CDR, + ) if command == CMD_MESSAGE_LIST: - _LOGGER.debug("AsteriskVM sent updated message list: Len %d", - len(msg)) + _LOGGER.debug("AsteriskVM sent updated message list: Len %d", len(msg)) old_messages = self.messages self.messages = sorted( - msg, key=lambda item: item['info']['origtime'], reverse=True) + msg, key=lambda item: item["info"]["origtime"], reverse=True + ) if not isinstance(old_messages, list): - async_dispatcher_send( - self.hass, SIGNAL_DISCOVER_PLATFORM, DOMAIN) - async_dispatcher_send(self.hass, SIGNAL_MESSAGE_UPDATE, - self.messages) + async_dispatcher_send(self.hass, SIGNAL_DISCOVER_PLATFORM, DOMAIN) + async_dispatcher_send(self.hass, SIGNAL_MESSAGE_UPDATE, self.messages) elif command == CMD_MESSAGE_CDR: - _LOGGER.debug("AsteriskVM sent updated CDR list: Len %d", - len(msg.get('entries', []))) - self.cdr = msg['entries'] + _LOGGER.debug( + "AsteriskVM sent updated CDR list: Len %d", len(msg.get("entries", [])) + ) + self.cdr = msg["entries"] async_dispatcher_send(self.hass, SIGNAL_CDR_UPDATE, self.cdr) elif command == CMD_MESSAGE_CDR_AVAILABLE: if not isinstance(self.cdr, list): _LOGGER.debug("AsteriskVM adding CDR platform") self.cdr = [] - async_dispatcher_send(self.hass, SIGNAL_DISCOVER_PLATFORM, - "asterisk_cdr") + async_dispatcher_send( + self.hass, SIGNAL_DISCOVER_PLATFORM, "asterisk_cdr" + ) async_dispatcher_send(self.hass, SIGNAL_CDR_REQUEST) else: - _LOGGER.debug("AsteriskVM sent unknown message '%d' len: %d", - command, len(msg)) + _LOGGER.debug( + "AsteriskVM sent unknown message '%d' len: %d", command, len(msg) + ) @callback def _request_messages(self): diff --git a/homeassistant/components/asterisk_mbox/mailbox.py b/homeassistant/components/asterisk_mbox/mailbox.py index f79c8922214..4d3c255fd5b 100644 --- a/homeassistant/components/asterisk_mbox/mailbox.py +++ b/homeassistant/components/asterisk_mbox/mailbox.py @@ -1,8 +1,7 @@ """Support for the Asterisk Voicemail interface.""" import logging -from homeassistant.components.mailbox import ( - CONTENT_TYPE_MPEG, Mailbox, StreamError) +from homeassistant.components.mailbox import CONTENT_TYPE_MPEG, Mailbox, StreamError from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -10,8 +9,8 @@ from . import DOMAIN as ASTERISK_DOMAIN _LOGGER = logging.getLogger(__name__) -SIGNAL_MESSAGE_REQUEST = 'asterisk_mbox.message_request' -SIGNAL_MESSAGE_UPDATE = 'asterisk_mbox.message_updated' +SIGNAL_MESSAGE_REQUEST = "asterisk_mbox.message_request" +SIGNAL_MESSAGE_UPDATE = "asterisk_mbox.message_updated" async def async_get_handler(hass, config, discovery_info=None): @@ -26,7 +25,8 @@ class AsteriskMailbox(Mailbox): """Initialize Asterisk mailbox.""" super().__init__(hass, name) async_dispatcher_connect( - self.hass, SIGNAL_MESSAGE_UPDATE, self._update_callback) + self.hass, SIGNAL_MESSAGE_UPDATE, self._update_callback + ) @callback def _update_callback(self, msg): @@ -51,6 +51,7 @@ class AsteriskMailbox(Mailbox): async def async_get_media(self, msgid): """Return the media blob for the msgid.""" from asterisk_mbox import ServerError + client = self.hass.data[ASTERISK_DOMAIN].client try: return client.mp3(msgid, sync=True) diff --git a/homeassistant/components/asuswrt/__init__.py b/homeassistant/components/asuswrt/__init__.py index cc51a15f8e8..e0c6830adfe 100644 --- a/homeassistant/components/asuswrt/__init__.py +++ b/homeassistant/components/asuswrt/__init__.py @@ -4,53 +4,69 @@ import logging import voluptuous as vol from homeassistant.const import ( - CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT, CONF_MODE, - CONF_PROTOCOL) + CONF_HOST, + CONF_PASSWORD, + CONF_USERNAME, + CONF_PORT, + CONF_MODE, + CONF_PROTOCOL, +) from homeassistant.helpers import config_validation as cv from homeassistant.helpers.discovery import async_load_platform _LOGGER = logging.getLogger(__name__) -CONF_PUB_KEY = 'pub_key' -CONF_REQUIRE_IP = 'require_ip' -CONF_SENSORS = 'sensors' -CONF_SSH_KEY = 'ssh_key' +CONF_PUB_KEY = "pub_key" +CONF_REQUIRE_IP = "require_ip" +CONF_SENSORS = "sensors" +CONF_SSH_KEY = "ssh_key" DOMAIN = "asuswrt" DATA_ASUSWRT = DOMAIN DEFAULT_SSH_PORT = 22 -SECRET_GROUP = 'Password or SSH Key' -SENSOR_TYPES = ['upload_speed', 'download_speed', 'download', 'upload'] +SECRET_GROUP = "Password or SSH Key" +SENSOR_TYPES = ["upload_speed", "download_speed", "download", "upload"] -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Optional(CONF_PROTOCOL, default='ssh'): vol.In(['ssh', 'telnet']), - vol.Optional(CONF_MODE, default='router'): vol.In(['router', 'ap']), - vol.Optional(CONF_PORT, default=DEFAULT_SSH_PORT): cv.port, - vol.Optional(CONF_REQUIRE_IP, default=True): cv.boolean, - vol.Exclusive(CONF_PASSWORD, SECRET_GROUP): cv.string, - vol.Exclusive(CONF_SSH_KEY, SECRET_GROUP): cv.isfile, - vol.Exclusive(CONF_PUB_KEY, SECRET_GROUP): cv.isfile, - vol.Optional(CONF_SENSORS): vol.All( - cv.ensure_list, [vol.In(SENSOR_TYPES)]), - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Optional(CONF_PROTOCOL, default="ssh"): vol.In(["ssh", "telnet"]), + vol.Optional(CONF_MODE, default="router"): vol.In(["router", "ap"]), + vol.Optional(CONF_PORT, default=DEFAULT_SSH_PORT): cv.port, + vol.Optional(CONF_REQUIRE_IP, default=True): cv.boolean, + vol.Exclusive(CONF_PASSWORD, SECRET_GROUP): cv.string, + vol.Exclusive(CONF_SSH_KEY, SECRET_GROUP): cv.isfile, + vol.Exclusive(CONF_PUB_KEY, SECRET_GROUP): cv.isfile, + vol.Optional(CONF_SENSORS): vol.All( + cv.ensure_list, [vol.In(SENSOR_TYPES)] + ), + } + ) + }, + extra=vol.ALLOW_EXTRA, +) async def async_setup(hass, config): """Set up the asuswrt component.""" from aioasuswrt.asuswrt import AsusWrt + conf = config[DOMAIN] - api = AsusWrt(conf[CONF_HOST], conf.get(CONF_PORT), - conf.get(CONF_PROTOCOL) == 'telnet', - conf[CONF_USERNAME], - conf.get(CONF_PASSWORD, ''), - conf.get('ssh_key', conf.get('pub_key', '')), - conf.get(CONF_MODE), conf.get(CONF_REQUIRE_IP)) + api = AsusWrt( + conf[CONF_HOST], + conf.get(CONF_PORT), + conf.get(CONF_PROTOCOL) == "telnet", + conf[CONF_USERNAME], + conf.get(CONF_PASSWORD, ""), + conf.get("ssh_key", conf.get("pub_key", "")), + conf.get(CONF_MODE), + conf.get(CONF_REQUIRE_IP), + ) await api.connection.async_connect() if not api.is_connected: @@ -59,9 +75,13 @@ async def async_setup(hass, config): hass.data[DATA_ASUSWRT] = api - hass.async_create_task(async_load_platform( - hass, 'sensor', DOMAIN, config[DOMAIN].get(CONF_SENSORS), config)) - hass.async_create_task(async_load_platform( - hass, 'device_tracker', DOMAIN, {}, config)) + hass.async_create_task( + async_load_platform( + hass, "sensor", DOMAIN, config[DOMAIN].get(CONF_SENSORS), config + ) + ) + hass.async_create_task( + async_load_platform(hass, "device_tracker", DOMAIN, {}, config) + ) return True diff --git a/homeassistant/components/asuswrt/device_tracker.py b/homeassistant/components/asuswrt/device_tracker.py index a7b13abbc05..5e3297da8ff 100644 --- a/homeassistant/components/asuswrt/device_tracker.py +++ b/homeassistant/components/asuswrt/device_tracker.py @@ -47,6 +47,6 @@ class AsusWrtDeviceScanner(DeviceScanner): Return boolean if scanning successful. """ - _LOGGER.debug('Checking Devices') + _LOGGER.debug("Checking Devices") self.last_results = await self.connection.async_get_connected_devices() diff --git a/homeassistant/components/asuswrt/sensor.py b/homeassistant/components/asuswrt/sensor.py index 8ae629bd12d..b5ce8539f44 100644 --- a/homeassistant/components/asuswrt/sensor.py +++ b/homeassistant/components/asuswrt/sensor.py @@ -8,8 +8,7 @@ from . import DATA_ASUSWRT _LOGGER = logging.getLogger(__name__) -async def async_setup_platform( - hass, config, add_entities, discovery_info=None): +async def async_setup_platform(hass, config, add_entities, discovery_info=None): """Set up the asuswrt sensors.""" if discovery_info is None: return @@ -18,13 +17,13 @@ async def async_setup_platform( devices = [] - if 'download' in discovery_info: + if "download" in discovery_info: devices.append(AsuswrtTotalRXSensor(api)) - if 'upload' in discovery_info: + if "upload" in discovery_info: devices.append(AsuswrtTotalTXSensor(api)) - if 'download_speed' in discovery_info: + if "download_speed" in discovery_info: devices.append(AsuswrtRXSensor(api)) - if 'upload_speed' in discovery_info: + if "upload_speed" in discovery_info: devices.append(AsuswrtTXSensor(api)) add_entities(devices) @@ -33,7 +32,7 @@ async def async_setup_platform( class AsuswrtSensor(Entity): """Representation of a asuswrt sensor.""" - _name = 'generic' + _name = "generic" def __init__(self, api): """Initialize the sensor.""" @@ -61,8 +60,8 @@ class AsuswrtSensor(Entity): class AsuswrtRXSensor(AsuswrtSensor): """Representation of a asuswrt download speed sensor.""" - _name = 'Asuswrt Download Speed' - _unit = 'Mbit/s' + _name = "Asuswrt Download Speed" + _unit = "Mbit/s" @property def unit_of_measurement(self): @@ -79,8 +78,8 @@ class AsuswrtRXSensor(AsuswrtSensor): class AsuswrtTXSensor(AsuswrtSensor): """Representation of a asuswrt upload speed sensor.""" - _name = 'Asuswrt Upload Speed' - _unit = 'Mbit/s' + _name = "Asuswrt Upload Speed" + _unit = "Mbit/s" @property def unit_of_measurement(self): @@ -97,8 +96,8 @@ class AsuswrtTXSensor(AsuswrtSensor): class AsuswrtTotalRXSensor(AsuswrtSensor): """Representation of a asuswrt total download sensor.""" - _name = 'Asuswrt Download' - _unit = 'Gigabyte' + _name = "Asuswrt Download" + _unit = "Gigabyte" @property def unit_of_measurement(self): @@ -115,8 +114,8 @@ class AsuswrtTotalRXSensor(AsuswrtSensor): class AsuswrtTotalTXSensor(AsuswrtSensor): """Representation of a asuswrt total upload sensor.""" - _name = 'Asuswrt Upload' - _unit = 'Gigabyte' + _name = "Asuswrt Upload" + _unit = "Gigabyte" @property def unit_of_measurement(self): diff --git a/homeassistant/components/august/__init__.py b/homeassistant/components/august/__init__.py index e18c25706c1..93b5ec6ec78 100644 --- a/homeassistant/components/august/__init__.py +++ b/homeassistant/components/august/__init__.py @@ -7,7 +7,11 @@ from requests import RequestException import homeassistant.helpers.config_validation as cv from homeassistant.const import ( - CONF_PASSWORD, CONF_USERNAME, CONF_TIMEOUT, EVENT_HOMEASSISTANT_STOP) + CONF_PASSWORD, + CONF_USERNAME, + CONF_TIMEOUT, + EVENT_HOMEASSISTANT_STOP, +) from homeassistant.helpers import discovery from homeassistant.util import Throttle @@ -19,34 +23,37 @@ DEFAULT_TIMEOUT = 10 ACTIVITY_FETCH_LIMIT = 10 ACTIVITY_INITIAL_FETCH_LIMIT = 20 -CONF_LOGIN_METHOD = 'login_method' -CONF_INSTALL_ID = 'install_id' +CONF_LOGIN_METHOD = "login_method" +CONF_INSTALL_ID = "install_id" -NOTIFICATION_ID = 'august_notification' +NOTIFICATION_ID = "august_notification" NOTIFICATION_TITLE = "August Setup" -AUGUST_CONFIG_FILE = '.august.conf' +AUGUST_CONFIG_FILE = ".august.conf" -DATA_AUGUST = 'august' -DOMAIN = 'august' -DEFAULT_ENTITY_NAMESPACE = 'august' +DATA_AUGUST = "august" +DOMAIN = "august" +DEFAULT_ENTITY_NAMESPACE = "august" MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=5) DEFAULT_SCAN_INTERVAL = timedelta(seconds=5) -LOGIN_METHODS = ['phone', 'email'] +LOGIN_METHODS = ["phone", "email"] -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_LOGIN_METHOD): vol.In(LOGIN_METHODS), - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_INSTALL_ID): cv.string, - vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_LOGIN_METHOD): vol.In(LOGIN_METHODS), + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_INSTALL_ID): cv.string, + vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) -AUGUST_COMPONENTS = [ - 'camera', 'binary_sensor', 'lock' -] +AUGUST_COMPONENTS = ["camera", "binary_sensor", "lock"] def request_configuration(hass, config, api, authenticator): @@ -57,12 +64,12 @@ def request_configuration(hass, config, api, authenticator): """Run when the configuration callback is called.""" from august.authenticator import ValidationResult - result = authenticator.validate_verification_code( - data.get('verification_code')) + result = authenticator.validate_verification_code(data.get("verification_code")) if result == ValidationResult.INVALID_VERIFICATION_CODE: - configurator.notify_errors(_CONFIGURING[DOMAIN], - "Invalid verification code") + configurator.notify_errors( + _CONFIGURING[DOMAIN], "Invalid verification code" + ) elif result == ValidationResult.VALIDATED: setup_august(hass, config, api, authenticator) @@ -77,12 +84,11 @@ def request_configuration(hass, config, api, authenticator): NOTIFICATION_TITLE, august_configuration_callback, description="Please check your {} ({}) and enter the verification " - "code below".format(login_method, username), - submit_caption='Verify', - fields=[{ - 'id': 'verification_code', - 'name': "Verification code", - 'type': 'string'}] + "code below".format(login_method, username), + submit_caption="Verify", + fields=[ + {"id": "verification_code", "name": "Verification code", "type": "string"} + ], ) @@ -101,7 +107,8 @@ def setup_august(hass, config, api, authenticator): "You will need to restart hass after fixing." "".format(ex), title=NOTIFICATION_TITLE, - notification_id=NOTIFICATION_ID) + notification_id=NOTIFICATION_ID, + ) state = authentication.state @@ -109,8 +116,7 @@ def setup_august(hass, config, api, authenticator): if DOMAIN in _CONFIGURING: hass.components.configurator.request_done(_CONFIGURING.pop(DOMAIN)) - hass.data[DATA_AUGUST] = AugustData( - hass, api, authentication.access_token) + hass.data[DATA_AUGUST] = AugustData(hass, api, authentication.access_token) for component in AUGUST_COMPONENTS: discovery.load_platform(hass, component, DOMAIN, {}, config) @@ -147,7 +153,8 @@ def setup(hass, config): conf.get(CONF_USERNAME), conf.get(CONF_PASSWORD), install_id=conf.get(CONF_INSTALL_ID), - access_token_cache_file=hass.config.path(AUGUST_CONFIG_FILE)) + access_token_cache_file=hass.config.path(AUGUST_CONFIG_FILE), + ) def close_http_session(event): """Close API sessions used to connect to August.""" @@ -219,17 +226,17 @@ class AugustData: """Update data object with latest from August API.""" _LOGGER.debug("Start retrieving device activities") for house_id in self.house_ids: - _LOGGER.debug("Updating device activity for house id %s", - house_id) + _LOGGER.debug("Updating device activity for house id %s", house_id) - activities = self._api.get_house_activities(self._access_token, - house_id, - limit=limit) + activities = self._api.get_house_activities( + self._access_token, house_id, limit=limit + ) device_ids = {a.device_id for a in activities} for device_id in device_ids: - self._activities_by_id[device_id] = [a for a in activities if - a.device_id == device_id] + self._activities_by_id[device_id] = [ + a for a in activities if a.device_id == device_id + ] _LOGGER.debug("Completed retrieving device activities") def get_doorbell_detail(self, doorbell_id): @@ -243,15 +250,17 @@ class AugustData: _LOGGER.debug("Start retrieving doorbell details") for doorbell in self._doorbells: - _LOGGER.debug("Updating doorbell status for %s", - doorbell.device_name) + _LOGGER.debug("Updating doorbell status for %s", doorbell.device_name) try: - detail_by_id[doorbell.device_id] =\ - self._api.get_doorbell_detail( - self._access_token, doorbell.device_id) + detail_by_id[doorbell.device_id] = self._api.get_doorbell_detail( + self._access_token, doorbell.device_id + ) except RequestException as ex: - _LOGGER.error("Request error trying to retrieve doorbell" - " status for %s. %s", doorbell.device_name, ex) + _LOGGER.error( + "Request error trying to retrieve doorbell" " status for %s. %s", + doorbell.device_name, + ex, + ) detail_by_id[doorbell.device_id] = None except Exception: detail_by_id[doorbell.device_id] = None @@ -287,15 +296,18 @@ class AugustData: _LOGGER.debug("Start retrieving door status") for lock in self._locks: - _LOGGER.debug("Updating door status for %s", - lock.device_name) + _LOGGER.debug("Updating door status for %s", lock.device_name) try: state_by_id[lock.device_id] = self._api.get_lock_door_status( - self._access_token, lock.device_id) + self._access_token, lock.device_id + ) except RequestException as ex: - _LOGGER.error("Request error trying to retrieve door" - " status for %s. %s", lock.device_name, ex) + _LOGGER.error( + "Request error trying to retrieve door" " status for %s. %s", + lock.device_name, + ex, + ) state_by_id[lock.device_id] = None except Exception: state_by_id[lock.device_id] = None @@ -311,14 +323,17 @@ class AugustData: _LOGGER.debug("Start retrieving locks status") for lock in self._locks: - _LOGGER.debug("Updating lock status for %s", - lock.device_name) + _LOGGER.debug("Updating lock status for %s", lock.device_name) try: status_by_id[lock.device_id] = self._api.get_lock_status( - self._access_token, lock.device_id) + self._access_token, lock.device_id + ) except RequestException as ex: - _LOGGER.error("Request error trying to retrieve door" - " status for %s. %s", lock.device_name, ex) + _LOGGER.error( + "Request error trying to retrieve door" " status for %s. %s", + lock.device_name, + ex, + ) status_by_id[lock.device_id] = None except Exception: status_by_id[lock.device_id] = None @@ -326,10 +341,14 @@ class AugustData: try: detail_by_id[lock.device_id] = self._api.get_lock_detail( - self._access_token, lock.device_id) + self._access_token, lock.device_id + ) except RequestException as ex: - _LOGGER.error("Request error trying to retrieve door" - " details for %s. %s", lock.device_name, ex) + _LOGGER.error( + "Request error trying to retrieve door" " details for %s. %s", + lock.device_name, + ex, + ) detail_by_id[lock.device_id] = None except Exception: detail_by_id[lock.device_id] = None diff --git a/homeassistant/components/august/binary_sensor.py b/homeassistant/components/august/binary_sensor.py index d1f69645802..d68582d30c5 100644 --- a/homeassistant/components/august/binary_sensor.py +++ b/homeassistant/components/august/binary_sensor.py @@ -27,21 +27,21 @@ def _retrieve_online_state(data, doorbell): def _retrieve_motion_state(data, doorbell): from august.activity import ActivityType - return _activity_time_based_state(data, doorbell, - [ActivityType.DOORBELL_MOTION, - ActivityType.DOORBELL_DING]) + + return _activity_time_based_state( + data, doorbell, [ActivityType.DOORBELL_MOTION, ActivityType.DOORBELL_DING] + ) def _retrieve_ding_state(data, doorbell): from august.activity import ActivityType - return _activity_time_based_state(data, doorbell, - [ActivityType.DOORBELL_DING]) + + return _activity_time_based_state(data, doorbell, [ActivityType.DOORBELL_DING]) def _activity_time_based_state(data, doorbell, activity_types): """Get the latest state of the sensor.""" - latest = data.get_latest_device_activity(doorbell.device_id, - *activity_types) + latest = data.get_latest_device_activity(doorbell.device_id, *activity_types) if latest is not None: start = latest.activity_start_time @@ -51,14 +51,12 @@ def _activity_time_based_state(data, doorbell, activity_types): # Sensor types: Name, device_class, state_provider -SENSOR_TYPES_DOOR = { - 'door_open': ['Open', 'door', _retrieve_door_state], -} +SENSOR_TYPES_DOOR = {"door_open": ["Open", "door", _retrieve_door_state]} SENSOR_TYPES_DOORBELL = { - 'doorbell_ding': ['Ding', 'occupancy', _retrieve_ding_state], - 'doorbell_motion': ['Motion', 'motion', _retrieve_motion_state], - 'doorbell_online': ['Online', 'connectivity', _retrieve_online_state], + "doorbell_ding": ["Ding", "occupancy", _retrieve_ding_state], + "doorbell_motion": ["Motion", "motion", _retrieve_motion_state], + "doorbell_online": ["Online", "connectivity", _retrieve_online_state], } @@ -68,31 +66,33 @@ def setup_platform(hass, config, add_entities, discovery_info=None): devices = [] from august.lock import LockDoorStatus + for door in data.locks: for sensor_type in SENSOR_TYPES_DOOR: state_provider = SENSOR_TYPES_DOOR[sensor_type][2] if state_provider(data, door) is LockDoorStatus.UNKNOWN: _LOGGER.debug( "Not adding sensor class %s for lock %s ", - SENSOR_TYPES_DOOR[sensor_type][1], door.device_name + SENSOR_TYPES_DOOR[sensor_type][1], + door.device_name, ) continue _LOGGER.debug( "Adding sensor class %s for %s", - SENSOR_TYPES_DOOR[sensor_type][1], door.device_name + SENSOR_TYPES_DOOR[sensor_type][1], + door.device_name, ) devices.append(AugustDoorBinarySensor(data, sensor_type, door)) for doorbell in data.doorbells: for sensor_type in SENSOR_TYPES_DOORBELL: - _LOGGER.debug("Adding doorbell sensor class %s for %s", - SENSOR_TYPES_DOORBELL[sensor_type][1], - doorbell.device_name) - devices.append( - AugustDoorbellBinarySensor(data, sensor_type, - doorbell) + _LOGGER.debug( + "Adding doorbell sensor class %s for %s", + SENSOR_TYPES_DOORBELL[sensor_type][1], + doorbell.device_name, ) + devices.append(AugustDoorbellBinarySensor(data, sensor_type, doorbell)) add_entities(devices, True) @@ -126,8 +126,9 @@ class AugustDoorBinarySensor(BinarySensorDevice): @property def name(self): """Return the name of the binary sensor.""" - return "{} {}".format(self._door.device_name, - SENSOR_TYPES_DOOR[self._sensor_type][0]) + return "{} {}".format( + self._door.device_name, SENSOR_TYPES_DOOR[self._sensor_type][0] + ) def update(self): """Get the latest state of the sensor.""" @@ -136,14 +137,15 @@ class AugustDoorBinarySensor(BinarySensorDevice): self._available = self._state is not None from august.lock import LockDoorStatus + self._state = self._state == LockDoorStatus.OPEN @property def unique_id(self) -> str: """Get the unique of the door open binary sensor.""" - return '{:s}_{:s}'.format(self._door.device_id, - SENSOR_TYPES_DOOR[self._sensor_type][0] - .lower()) + return "{:s}_{:s}".format( + self._door.device_id, SENSOR_TYPES_DOOR[self._sensor_type][0].lower() + ) class AugustDoorbellBinarySensor(BinarySensorDevice): @@ -175,8 +177,9 @@ class AugustDoorbellBinarySensor(BinarySensorDevice): @property def name(self): """Return the name of the binary sensor.""" - return "{} {}".format(self._doorbell.device_name, - SENSOR_TYPES_DOORBELL[self._sensor_type][0]) + return "{} {}".format( + self._doorbell.device_name, SENSOR_TYPES_DOORBELL[self._sensor_type][0] + ) def update(self): """Get the latest state of the sensor.""" @@ -187,6 +190,7 @@ class AugustDoorbellBinarySensor(BinarySensorDevice): @property def unique_id(self) -> str: """Get the unique id of the doorbell sensor.""" - return '{:s}_{:s}'.format(self._doorbell.device_id, - SENSOR_TYPES_DOORBELL[self._sensor_type][0] - .lower()) + return "{:s}_{:s}".format( + self._doorbell.device_id, + SENSOR_TYPES_DOORBELL[self._sensor_type][0].lower(), + ) diff --git a/homeassistant/components/august/camera.py b/homeassistant/components/august/camera.py index 0bf8a28f904..a8335d1aa52 100644 --- a/homeassistant/components/august/camera.py +++ b/homeassistant/components/august/camera.py @@ -51,12 +51,12 @@ class AugustCamera(Camera): @property def brand(self): """Return the camera brand.""" - return 'August' + return "August" @property def model(self): """Return the camera model.""" - return 'Doorbell' + return "Doorbell" def camera_image(self): """Return bytes of camera image.""" @@ -64,12 +64,13 @@ class AugustCamera(Camera): if self._image_url is not latest.image_url: self._image_url = latest.image_url - self._image_content = requests.get(self._image_url, - timeout=self._timeout).content + self._image_content = requests.get( + self._image_url, timeout=self._timeout + ).content return self._image_content @property def unique_id(self) -> str: """Get the unique id of the camera.""" - return '{:s}_camera'.format(self._doorbell.device_id) + return "{:s}_camera".format(self._doorbell.device_id) diff --git a/homeassistant/components/august/lock.py b/homeassistant/components/august/lock.py index 5ad2bdc3b5b..e919c47dd4c 100644 --- a/homeassistant/components/august/lock.py +++ b/homeassistant/components/august/lock.py @@ -52,9 +52,10 @@ class AugustLock(LockDevice): self._lock_detail = self._data.get_lock_detail(self._lock.device_id) from august.activity import ActivityType + activity = self._data.get_latest_device_activity( - self._lock.device_id, - ActivityType.LOCK_OPERATION) + self._lock.device_id, ActivityType.LOCK_OPERATION + ) if activity is not None: self._changed_by = activity.operated_by @@ -73,6 +74,7 @@ class AugustLock(LockDevice): def is_locked(self): """Return true if device is on.""" from august.lock import LockStatus + return self._lock_status is LockStatus.LOCKED @property @@ -86,11 +88,9 @@ class AugustLock(LockDevice): if self._lock_detail is None: return None - return { - ATTR_BATTERY_LEVEL: self._lock_detail.battery_level, - } + return {ATTR_BATTERY_LEVEL: self._lock_detail.battery_level} @property def unique_id(self) -> str: """Get the unique id of the lock.""" - return '{:s}_lock'.format(self._lock.device_id) + return "{:s}_lock".format(self._lock.device_id) diff --git a/homeassistant/components/aurora/binary_sensor.py b/homeassistant/components/aurora/binary_sensor.py index 58546382a50..0d983f35e37 100644 --- a/homeassistant/components/aurora/binary_sensor.py +++ b/homeassistant/components/aurora/binary_sensor.py @@ -6,20 +6,18 @@ from aiohttp.hdrs import USER_AGENT import requests import voluptuous as vol -from homeassistant.components.binary_sensor import ( - PLATFORM_SCHEMA, BinarySensorDevice) +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice from homeassistant.const import CONF_NAME, ATTR_ATTRIBUTION import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -ATTRIBUTION = "Data provided by the National Oceanic and Atmospheric " \ - "Administration" -CONF_THRESHOLD = 'forecast_threshold' +ATTRIBUTION = "Data provided by the National Oceanic and Atmospheric " "Administration" +CONF_THRESHOLD = "forecast_threshold" -DEFAULT_DEVICE_CLASS = 'visible' -DEFAULT_NAME = 'Aurora Visibility' +DEFAULT_DEVICE_CLASS = "visible" +DEFAULT_NAME = "Aurora Visibility" DEFAULT_THRESHOLD = 75 HA_USER_AGENT = "Home Assistant Aurora Tracker v.0.1.0" @@ -28,10 +26,12 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5) URL = "http://services.swpc.noaa.gov/text/aurora-nowcast-map.txt" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_THRESHOLD, default=DEFAULT_THRESHOLD): cv.positive_int, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_THRESHOLD, default=DEFAULT_THRESHOLD): cv.positive_int, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -44,12 +44,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None): threshold = config.get(CONF_THRESHOLD) try: - aurora_data = AuroraData( - hass.config.latitude, hass.config.longitude, threshold) + aurora_data = AuroraData(hass.config.latitude, hass.config.longitude, threshold) aurora_data.update() except requests.exceptions.HTTPError as error: - _LOGGER.error( - "Connection to aurora forecast service failed: %s", error) + _LOGGER.error("Connection to aurora forecast service failed: %s", error) return False add_entities([AuroraSensor(aurora_data, name)], True) @@ -66,7 +64,7 @@ class AuroraSensor(BinarySensorDevice): @property def name(self): """Return the name of the sensor.""" - return '{}'.format(self._name) + return "{}".format(self._name) @property def is_on(self): @@ -84,8 +82,8 @@ class AuroraSensor(BinarySensorDevice): attrs = {} if self.aurora_data: - attrs['visibility_level'] = self.aurora_data.visibility_level - attrs['message'] = self.aurora_data.is_visible_text + attrs["visibility_level"] = self.aurora_data.visibility_level + attrs["message"] = self.aurora_data.is_visible_text attrs[ATTR_ATTRIBUTION] = ATTRIBUTION return attrs @@ -122,8 +120,7 @@ class AuroraData: self.is_visible_text = "nothing's out" except requests.exceptions.HTTPError as error: - _LOGGER.error( - "Connection to aurora forecast service failed: %s", error) + _LOGGER.error("Connection to aurora forecast service failed: %s", error) return False def get_aurora_forecast(self): @@ -136,9 +133,11 @@ class AuroraData: ] # Convert lat and long for data points in table - converted_latitude = round((self.latitude / 180) - * self.number_of_latitude_intervals) - converted_longitude = round((self.longitude / 360) - * self.number_of_longitude_intervals) + converted_latitude = round( + (self.latitude / 180) * self.number_of_latitude_intervals + ) + converted_longitude = round( + (self.longitude / 360) * self.number_of_longitude_intervals + ) return forecast_table[converted_latitude][converted_longitude] diff --git a/homeassistant/components/aurora_abb_powerone/sensor.py b/homeassistant/components/aurora_abb_powerone/sensor.py index d77fae246d7..456b5080484 100644 --- a/homeassistant/components/aurora_abb_powerone/sensor.py +++ b/homeassistant/components/aurora_abb_powerone/sensor.py @@ -7,8 +7,12 @@ from aurorapy.client import AuroraSerialClient, AuroraError from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_ADDRESS, CONF_DEVICE, CONF_NAME, DEVICE_CLASS_POWER, - POWER_WATT) + CONF_ADDRESS, + CONF_DEVICE, + CONF_NAME, + DEVICE_CLASS_POWER, + POWER_WATT, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -17,11 +21,13 @@ _LOGGER = logging.getLogger(__name__) DEFAULT_ADDRESS = 2 DEFAULT_NAME = "Solar PV" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_DEVICE): cv.string, - vol.Optional(CONF_ADDRESS, default=DEFAULT_ADDRESS): cv.positive_int, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_DEVICE): cv.string, + vol.Optional(CONF_ADDRESS, default=DEFAULT_ADDRESS): cv.positive_int, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -32,9 +38,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): name = config[CONF_NAME] _LOGGER.debug("Intitialising com port=%s address=%s", comport, address) - client = AuroraSerialClient(address, comport, parity='N', timeout=1) + client = AuroraSerialClient(address, comport, parity="N", timeout=1) - devices.append(AuroraABBSolarPVMonitorSensor(client, name, 'Power')) + devices.append(AuroraABBSolarPVMonitorSensor(client, name, "Power")) add_entities(devices, True) diff --git a/homeassistant/components/auth/__init__.py b/homeassistant/components/auth/__init__.py index f1deaf0cb85..d0da9d39fe8 100644 --- a/homeassistant/components/auth/__init__.py +++ b/homeassistant/components/auth/__init__.py @@ -121,8 +121,11 @@ from datetime import timedelta from aiohttp import web import voluptuous as vol -from homeassistant.auth.models import User, Credentials, \ - TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN +from homeassistant.auth.models import ( + User, + Credentials, + TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN, +) from homeassistant.loader import bind_hass from homeassistant.components import websocket_api from homeassistant.components.http import KEY_REAL_IP @@ -137,44 +140,46 @@ from . import indieauth from . import login_flow from . import mfa_setup_flow -DOMAIN = 'auth' -WS_TYPE_CURRENT_USER = 'auth/current_user' -SCHEMA_WS_CURRENT_USER = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_CURRENT_USER, -}) +DOMAIN = "auth" +WS_TYPE_CURRENT_USER = "auth/current_user" +SCHEMA_WS_CURRENT_USER = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {vol.Required("type"): WS_TYPE_CURRENT_USER} +) -WS_TYPE_LONG_LIVED_ACCESS_TOKEN = 'auth/long_lived_access_token' -SCHEMA_WS_LONG_LIVED_ACCESS_TOKEN = \ - websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_LONG_LIVED_ACCESS_TOKEN, - vol.Required('lifespan'): int, # days - vol.Required('client_name'): str, - vol.Optional('client_icon'): str, - }) +WS_TYPE_LONG_LIVED_ACCESS_TOKEN = "auth/long_lived_access_token" +SCHEMA_WS_LONG_LIVED_ACCESS_TOKEN = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + { + vol.Required("type"): WS_TYPE_LONG_LIVED_ACCESS_TOKEN, + vol.Required("lifespan"): int, # days + vol.Required("client_name"): str, + vol.Optional("client_icon"): str, + } +) -WS_TYPE_REFRESH_TOKENS = 'auth/refresh_tokens' -SCHEMA_WS_REFRESH_TOKENS = \ - websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_REFRESH_TOKENS, - }) +WS_TYPE_REFRESH_TOKENS = "auth/refresh_tokens" +SCHEMA_WS_REFRESH_TOKENS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {vol.Required("type"): WS_TYPE_REFRESH_TOKENS} +) -WS_TYPE_DELETE_REFRESH_TOKEN = 'auth/delete_refresh_token' -SCHEMA_WS_DELETE_REFRESH_TOKEN = \ - websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_DELETE_REFRESH_TOKEN, - vol.Required('refresh_token_id'): str, - }) +WS_TYPE_DELETE_REFRESH_TOKEN = "auth/delete_refresh_token" +SCHEMA_WS_DELETE_REFRESH_TOKEN = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + { + vol.Required("type"): WS_TYPE_DELETE_REFRESH_TOKEN, + vol.Required("refresh_token_id"): str, + } +) -WS_TYPE_SIGN_PATH = 'auth/sign_path' -SCHEMA_WS_SIGN_PATH = \ - websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_SIGN_PATH, - vol.Required('path'): str, - vol.Optional('expires', default=30): int, - }) +WS_TYPE_SIGN_PATH = "auth/sign_path" +SCHEMA_WS_SIGN_PATH = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + { + vol.Required("type"): WS_TYPE_SIGN_PATH, + vol.Required("path"): str, + vol.Optional("expires", default=30): int, + } +) -RESULT_TYPE_CREDENTIALS = 'credentials' -RESULT_TYPE_USER = 'user' +RESULT_TYPE_CREDENTIALS = "credentials" +RESULT_TYPE_USER = "user" _LOGGER = logging.getLogger(__name__) @@ -195,28 +200,23 @@ async def async_setup(hass, config): hass.http.register_view(LinkUserView(retrieve_result)) hass.components.websocket_api.async_register_command( - WS_TYPE_CURRENT_USER, websocket_current_user, - SCHEMA_WS_CURRENT_USER + WS_TYPE_CURRENT_USER, websocket_current_user, SCHEMA_WS_CURRENT_USER ) hass.components.websocket_api.async_register_command( WS_TYPE_LONG_LIVED_ACCESS_TOKEN, websocket_create_long_lived_access_token, - SCHEMA_WS_LONG_LIVED_ACCESS_TOKEN + SCHEMA_WS_LONG_LIVED_ACCESS_TOKEN, ) hass.components.websocket_api.async_register_command( - WS_TYPE_REFRESH_TOKENS, - websocket_refresh_tokens, - SCHEMA_WS_REFRESH_TOKENS + WS_TYPE_REFRESH_TOKENS, websocket_refresh_tokens, SCHEMA_WS_REFRESH_TOKENS ) hass.components.websocket_api.async_register_command( WS_TYPE_DELETE_REFRESH_TOKEN, websocket_delete_refresh_token, - SCHEMA_WS_DELETE_REFRESH_TOKEN + SCHEMA_WS_DELETE_REFRESH_TOKEN, ) hass.components.websocket_api.async_register_command( - WS_TYPE_SIGN_PATH, - websocket_sign_path, - SCHEMA_WS_SIGN_PATH + WS_TYPE_SIGN_PATH, websocket_sign_path, SCHEMA_WS_SIGN_PATH ) await login_flow.async_setup(hass, store_result) @@ -228,8 +228,8 @@ async def async_setup(hass, config): class TokenView(HomeAssistantView): """View to issue or revoke tokens.""" - url = '/auth/token' - name = 'api:auth:token' + url = "/auth/token" + name = "api:auth:token" requires_auth = False cors_allowed = True @@ -240,29 +240,29 @@ class TokenView(HomeAssistantView): @log_invalid_auth async def post(self, request): """Grant a token.""" - hass = request.app['hass'] + hass = request.app["hass"] data = await request.post() - grant_type = data.get('grant_type') + grant_type = data.get("grant_type") # IndieAuth 6.3.5 # The revocation endpoint is the same as the token endpoint. # The revocation request includes an additional parameter, # action=revoke. - if data.get('action') == 'revoke': + if data.get("action") == "revoke": return await self._async_handle_revoke_token(hass, data) - if grant_type == 'authorization_code': + if grant_type == "authorization_code": return await self._async_handle_auth_code( - hass, data, str(request[KEY_REAL_IP])) + hass, data, str(request[KEY_REAL_IP]) + ) - if grant_type == 'refresh_token': + if grant_type == "refresh_token": return await self._async_handle_refresh_token( - hass, data, str(request[KEY_REAL_IP])) + hass, data, str(request[KEY_REAL_IP]) + ) - return self.json({ - 'error': 'unsupported_grant_type', - }, status_code=400) + return self.json({"error": "unsupported_grant_type"}, status_code=400) async def _async_handle_revoke_token(self, hass, data): """Handle revoke token request.""" @@ -270,7 +270,7 @@ class TokenView(HomeAssistantView): # 2.2 The authorization server responds with HTTP status code 200 # if the token has been revoked successfully or if the client # submitted an invalid token. - token = data.get('token') + token = data.get("token") if token is None: return web.Response(status=200) @@ -285,117 +285,112 @@ class TokenView(HomeAssistantView): async def _async_handle_auth_code(self, hass, data, remote_addr): """Handle authorization code request.""" - client_id = data.get('client_id') + client_id = data.get("client_id") if client_id is None or not indieauth.verify_client_id(client_id): - return self.json({ - 'error': 'invalid_request', - 'error_description': 'Invalid client id', - }, status_code=400) + return self.json( + {"error": "invalid_request", "error_description": "Invalid client id"}, + status_code=400, + ) - code = data.get('code') + code = data.get("code") if code is None: - return self.json({ - 'error': 'invalid_request', - 'error_description': 'Invalid code', - }, status_code=400) + return self.json( + {"error": "invalid_request", "error_description": "Invalid code"}, + status_code=400, + ) user = self._retrieve_user(client_id, RESULT_TYPE_USER, code) if user is None or not isinstance(user, User): - return self.json({ - 'error': 'invalid_request', - 'error_description': 'Invalid code', - }, status_code=400) + return self.json( + {"error": "invalid_request", "error_description": "Invalid code"}, + status_code=400, + ) # refresh user user = await hass.auth.async_get_user(user.id) if not user.is_active: - return self.json({ - 'error': 'access_denied', - 'error_description': 'User is not active', - }, status_code=403) + return self.json( + {"error": "access_denied", "error_description": "User is not active"}, + status_code=403, + ) - refresh_token = await hass.auth.async_create_refresh_token(user, - client_id) - access_token = hass.auth.async_create_access_token( - refresh_token, remote_addr) + refresh_token = await hass.auth.async_create_refresh_token(user, client_id) + access_token = hass.auth.async_create_access_token(refresh_token, remote_addr) - return self.json({ - 'access_token': access_token, - 'token_type': 'Bearer', - 'refresh_token': refresh_token.token, - 'expires_in': - int(refresh_token.access_token_expiration.total_seconds()), - }) + return self.json( + { + "access_token": access_token, + "token_type": "Bearer", + "refresh_token": refresh_token.token, + "expires_in": int( + refresh_token.access_token_expiration.total_seconds() + ), + } + ) async def _async_handle_refresh_token(self, hass, data, remote_addr): """Handle authorization code request.""" - client_id = data.get('client_id') + client_id = data.get("client_id") if client_id is not None and not indieauth.verify_client_id(client_id): - return self.json({ - 'error': 'invalid_request', - 'error_description': 'Invalid client id', - }, status_code=400) + return self.json( + {"error": "invalid_request", "error_description": "Invalid client id"}, + status_code=400, + ) - token = data.get('refresh_token') + token = data.get("refresh_token") if token is None: - return self.json({ - 'error': 'invalid_request', - }, status_code=400) + return self.json({"error": "invalid_request"}, status_code=400) refresh_token = await hass.auth.async_get_refresh_token_by_token(token) if refresh_token is None: - return self.json({ - 'error': 'invalid_grant', - }, status_code=400) + return self.json({"error": "invalid_grant"}, status_code=400) if refresh_token.client_id != client_id: - return self.json({ - 'error': 'invalid_request', - }, status_code=400) + return self.json({"error": "invalid_request"}, status_code=400) - access_token = hass.auth.async_create_access_token( - refresh_token, remote_addr) + access_token = hass.auth.async_create_access_token(refresh_token, remote_addr) - return self.json({ - 'access_token': access_token, - 'token_type': 'Bearer', - 'expires_in': - int(refresh_token.access_token_expiration.total_seconds()), - }) + return self.json( + { + "access_token": access_token, + "token_type": "Bearer", + "expires_in": int( + refresh_token.access_token_expiration.total_seconds() + ), + } + ) class LinkUserView(HomeAssistantView): """View to link existing users to new credentials.""" - url = '/auth/link_user' - name = 'api:auth:link_user' + url = "/auth/link_user" + name = "api:auth:link_user" def __init__(self, retrieve_credentials): """Initialize the link user view.""" self._retrieve_credentials = retrieve_credentials - @RequestDataValidator(vol.Schema({ - 'code': str, - 'client_id': str, - })) + @RequestDataValidator(vol.Schema({"code": str, "client_id": str})) async def post(self, request, data): """Link a user.""" - hass = request.app['hass'] - user = request['hass_user'] + hass = request.app["hass"] + user = request["hass_user"] credentials = self._retrieve_credentials( - data['client_id'], RESULT_TYPE_CREDENTIALS, data['code']) + data["client_id"], RESULT_TYPE_CREDENTIALS, data["code"] + ) if credentials is None: - return self.json_message('Invalid code', status_code=400) + return self.json_message("Invalid code", status_code=400) await hass.auth.async_link_user(user, credentials) - return self.json_message('User linked') + return self.json_message("User linked") @callback @@ -411,11 +406,14 @@ def _create_auth_code_store(): elif isinstance(result, Credentials): result_type = RESULT_TYPE_CREDENTIALS else: - raise ValueError('result has to be either User or Credentials') + raise ValueError("result has to be either User or Credentials") code = uuid.uuid4().hex - temp_results[(client_id, result_type, code)] = \ - (dt_util.utcnow(), result_type, result) + temp_results[(client_id, result_type, code)] = ( + dt_util.utcnow(), + result_type, + result, + ) return code @callback @@ -443,89 +441,121 @@ def _create_auth_code_store(): @websocket_api.ws_require_user() @websocket_api.async_response async def websocket_current_user( - hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg): + hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg +): """Return the current user.""" user = connection.user enabled_modules = await hass.auth.async_get_enabled_mfa(user) connection.send_message( - websocket_api.result_message(msg['id'], { - 'id': user.id, - 'name': user.name, - 'is_owner': user.is_owner, - 'is_admin': user.is_admin, - 'credentials': [{'auth_provider_type': c.auth_provider_type, - 'auth_provider_id': c.auth_provider_id} - for c in user.credentials], - 'mfa_modules': [{ - 'id': module.id, - 'name': module.name, - 'enabled': module.id in enabled_modules, - } for module in hass.auth.auth_mfa_modules], - })) + websocket_api.result_message( + msg["id"], + { + "id": user.id, + "name": user.name, + "is_owner": user.is_owner, + "is_admin": user.is_admin, + "credentials": [ + { + "auth_provider_type": c.auth_provider_type, + "auth_provider_id": c.auth_provider_id, + } + for c in user.credentials + ], + "mfa_modules": [ + { + "id": module.id, + "name": module.name, + "enabled": module.id in enabled_modules, + } + for module in hass.auth.auth_mfa_modules + ], + }, + ) + ) @websocket_api.ws_require_user() @websocket_api.async_response async def websocket_create_long_lived_access_token( - hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg): + hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg +): """Create or a long-lived access token.""" refresh_token = await hass.auth.async_create_refresh_token( connection.user, - client_name=msg['client_name'], - client_icon=msg.get('client_icon'), + client_name=msg["client_name"], + client_icon=msg.get("client_icon"), token_type=TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN, - access_token_expiration=timedelta(days=msg['lifespan'])) + access_token_expiration=timedelta(days=msg["lifespan"]), + ) - access_token = hass.auth.async_create_access_token( - refresh_token) + access_token = hass.auth.async_create_access_token(refresh_token) - connection.send_message( - websocket_api.result_message(msg['id'], access_token)) + connection.send_message(websocket_api.result_message(msg["id"], access_token)) @websocket_api.ws_require_user() @callback def websocket_refresh_tokens( - hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg): + hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg +): """Return metadata of users refresh tokens.""" current_id = connection.refresh_token_id - connection.send_message(websocket_api.result_message(msg['id'], [{ - 'id': refresh.id, - 'client_id': refresh.client_id, - 'client_name': refresh.client_name, - 'client_icon': refresh.client_icon, - 'type': refresh.token_type, - 'created_at': refresh.created_at, - 'is_current': refresh.id == current_id, - 'last_used_at': refresh.last_used_at, - 'last_used_ip': refresh.last_used_ip, - } for refresh in connection.user.refresh_tokens.values()])) + connection.send_message( + websocket_api.result_message( + msg["id"], + [ + { + "id": refresh.id, + "client_id": refresh.client_id, + "client_name": refresh.client_name, + "client_icon": refresh.client_icon, + "type": refresh.token_type, + "created_at": refresh.created_at, + "is_current": refresh.id == current_id, + "last_used_at": refresh.last_used_at, + "last_used_ip": refresh.last_used_ip, + } + for refresh in connection.user.refresh_tokens.values() + ], + ) + ) @websocket_api.ws_require_user() @websocket_api.async_response async def websocket_delete_refresh_token( - hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg): + hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg +): """Handle a delete refresh token request.""" - refresh_token = connection.user.refresh_tokens.get(msg['refresh_token_id']) + refresh_token = connection.user.refresh_tokens.get(msg["refresh_token_id"]) if refresh_token is None: return websocket_api.error_message( - msg['id'], 'invalid_token_id', 'Received invalid token') + msg["id"], "invalid_token_id", "Received invalid token" + ) await hass.auth.async_remove_refresh_token(refresh_token) - connection.send_message( - websocket_api.result_message(msg['id'], {})) + connection.send_message(websocket_api.result_message(msg["id"], {})) @websocket_api.ws_require_user() @callback def websocket_sign_path( - hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg): + hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg +): """Handle a sign path request.""" - connection.send_message(websocket_api.result_message(msg['id'], { - 'path': async_sign_path(hass, connection.refresh_token_id, msg['path'], - timedelta(seconds=msg['expires'])) - })) + connection.send_message( + websocket_api.result_message( + msg["id"], + { + "path": async_sign_path( + hass, + connection.refresh_token_id, + msg["path"], + timedelta(seconds=msg["expires"]), + ) + }, + ) + ) diff --git a/homeassistant/components/auth/indieauth.py b/homeassistant/components/auth/indieauth.py index a56671c9dcd..6a0a516bee2 100644 --- a/homeassistant/components/auth/indieauth.py +++ b/homeassistant/components/auth/indieauth.py @@ -23,8 +23,8 @@ async def verify_redirect_uri(hass, client_id, redirect_uri): # Verify redirect url and client url have same scheme and domain. is_valid = ( - client_id_parts.scheme == redirect_parts.scheme and - client_id_parts.netloc == redirect_parts.netloc + client_id_parts.scheme == redirect_parts.scheme + and client_id_parts.netloc == redirect_parts.netloc ) if is_valid: @@ -47,13 +47,13 @@ class LinkTagParser(HTMLParser): def handle_starttag(self, tag, attrs): """Handle finding a start tag.""" - if tag != 'link': + if tag != "link": return attrs = dict(attrs) - if attrs.get('rel') == self.rel: - self.found.append(attrs.get('href')) + if attrs.get("rel") == self.rel: + self.found.append(attrs.get("href")) async def fetch_redirect_uris(hass, url): @@ -68,7 +68,7 @@ async def fetch_redirect_uris(hass, url): We do not implement extracting redirect uris from headers. """ - parser = LinkTagParser('redirect_uri') + parser = LinkTagParser("redirect_uri") chunks = 0 try: async with aiohttp.ClientSession() as session: @@ -87,12 +87,12 @@ async def fetch_redirect_uris(hass, url): _LOGGER.error("SSL error while looking up redirect_uri %s", url) pass except aiohttp.client_exceptions.ClientOSError as ex: - _LOGGER.error("OS error while looking up redirect_uri %s: %s", url, - ex.strerror) + _LOGGER.error("OS error while looking up redirect_uri %s: %s", url, ex.strerror) pass except aiohttp.client_exceptions.ClientConnectionError: - _LOGGER.error(("Low level connection error while looking up " - "redirect_uri %s"), url) + _LOGGER.error( + ("Low level connection error while looking up " "redirect_uri %s"), url + ) pass except aiohttp.client_exceptions.ClientError: _LOGGER.error("Unknown error while looking up redirect_uri %s", url) @@ -125,8 +125,8 @@ def _parse_url(url): # If a URL with no path component is ever encountered, # it MUST be treated as if it had the path /. - if parts.path == '': - parts = parts._replace(path='/') + if parts.path == "": + parts = parts._replace(path="/") return parts @@ -140,34 +140,35 @@ def _parse_client_id(client_id): # Client identifier URLs # MUST have either an https or http scheme - if parts.scheme not in ('http', 'https'): + if parts.scheme not in ("http", "https"): raise ValueError() # MUST contain a path component # Handled by url canonicalization. # MUST NOT contain single-dot or double-dot path segments - if any(segment in ('.', '..') for segment in parts.path.split('/')): + if any(segment in (".", "..") for segment in parts.path.split("/")): raise ValueError( - 'Client ID cannot contain single-dot or double-dot path segments') + "Client ID cannot contain single-dot or double-dot path segments" + ) # MUST NOT contain a fragment component - if parts.fragment != '': - raise ValueError('Client ID cannot contain a fragment') + if parts.fragment != "": + raise ValueError("Client ID cannot contain a fragment") # MUST NOT contain a username or password component if parts.username is not None: - raise ValueError('Client ID cannot contain username') + raise ValueError("Client ID cannot contain username") if parts.password is not None: - raise ValueError('Client ID cannot contain password') + raise ValueError("Client ID cannot contain password") # MAY contain a port try: # parts raises ValueError when port cannot be parsed as int parts.port except ValueError: - raise ValueError('Client ID contains invalid port') + raise ValueError("Client ID contains invalid port") # Additionally, hostnames # MUST be domain names or a loopback interface and @@ -183,7 +184,7 @@ def _parse_client_id(client_id): netloc = parts.netloc # Strip the [, ] from ipv6 addresses before parsing - if netloc[0] == '[' and netloc[-1] == ']': + if netloc[0] == "[" and netloc[-1] == "]": netloc = netloc[1:-1] address = ip_address(netloc) @@ -194,4 +195,4 @@ def _parse_client_id(client_id): if address is None or is_local(address): return parts - raise ValueError('Hostname should be a domain name or local IP address') + raise ValueError("Hostname should be a domain name or local IP address") diff --git a/homeassistant/components/auth/login_flow.py b/homeassistant/components/auth/login_flow.py index 7fd767f4a43..0f5da5d7527 100644 --- a/homeassistant/components/auth/login_flow.py +++ b/homeassistant/components/auth/login_flow.py @@ -71,8 +71,7 @@ import voluptuous as vol from homeassistant import data_entry_flow from homeassistant.components.http import KEY_REAL_IP -from homeassistant.components.http.ban import process_wrong_login, \ - log_invalid_auth +from homeassistant.components.http.ban import process_wrong_login, log_invalid_auth from homeassistant.components.http.data_validator import RequestDataValidator from homeassistant.components.http.view import HomeAssistantView from . import indieauth @@ -81,56 +80,55 @@ from . import indieauth async def async_setup(hass, store_result): """Component to allow users to login.""" hass.http.register_view(AuthProvidersView) - hass.http.register_view( - LoginFlowIndexView(hass.auth.login_flow, store_result)) - hass.http.register_view( - LoginFlowResourceView(hass.auth.login_flow, store_result)) + hass.http.register_view(LoginFlowIndexView(hass.auth.login_flow, store_result)) + hass.http.register_view(LoginFlowResourceView(hass.auth.login_flow, store_result)) class AuthProvidersView(HomeAssistantView): """View to get available auth providers.""" - url = '/auth/providers' - name = 'api:auth:providers' + url = "/auth/providers" + name = "api:auth:providers" requires_auth = False async def get(self, request): """Get available auth providers.""" - hass = request.app['hass'] + hass = request.app["hass"] if not hass.components.onboarding.async_is_user_onboarded(): return self.json_message( - message='Onboarding not finished', + message="Onboarding not finished", status_code=400, - message_code='onboarding_required' + message_code="onboarding_required", ) - return self.json([{ - 'name': provider.name, - 'id': provider.id, - 'type': provider.type, - } for provider in hass.auth.auth_providers]) + return self.json( + [ + {"name": provider.name, "id": provider.id, "type": provider.type} + for provider in hass.auth.auth_providers + ] + ) def _prepare_result_json(result): """Convert result to JSON.""" - if result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY: + if result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY: data = result.copy() - data.pop('result') - data.pop('data') + data.pop("result") + data.pop("data") return data - if result['type'] != data_entry_flow.RESULT_TYPE_FORM: + if result["type"] != data_entry_flow.RESULT_TYPE_FORM: return result import voluptuous_serialize data = result.copy() - schema = data['data_schema'] + schema = data["data_schema"] if schema is None: - data['data_schema'] = [] + data["data_schema"] = [] else: - data['data_schema'] = voluptuous_serialize.convert(schema) + data["data_schema"] = voluptuous_serialize.convert(schema) return data @@ -138,8 +136,8 @@ def _prepare_result_json(result): class LoginFlowIndexView(HomeAssistantView): """View to create a config flow.""" - url = '/auth/login_flow' - name = 'api:auth:login_flow' + url = "/auth/login_flow" + name = "api:auth:login_flow" requires_auth = False def __init__(self, flow_mgr, store_result): @@ -151,39 +149,45 @@ class LoginFlowIndexView(HomeAssistantView): """Do not allow index of flows in progress.""" return web.Response(status=405) - @RequestDataValidator(vol.Schema({ - vol.Required('client_id'): str, - vol.Required('handler'): vol.Any(str, list), - vol.Required('redirect_uri'): str, - vol.Optional('type', default='authorize'): str, - })) + @RequestDataValidator( + vol.Schema( + { + vol.Required("client_id"): str, + vol.Required("handler"): vol.Any(str, list), + vol.Required("redirect_uri"): str, + vol.Optional("type", default="authorize"): str, + } + ) + ) @log_invalid_auth async def post(self, request, data): """Create a new login flow.""" if not await indieauth.verify_redirect_uri( - request.app['hass'], data['client_id'], data['redirect_uri']): - return self.json_message('invalid client id or redirect uri', 400) + request.app["hass"], data["client_id"], data["redirect_uri"] + ): + return self.json_message("invalid client id or redirect uri", 400) - if isinstance(data['handler'], list): - handler = tuple(data['handler']) + if isinstance(data["handler"], list): + handler = tuple(data["handler"]) else: - handler = data['handler'] + handler = data["handler"] try: result = await self._flow_mgr.async_init( - handler, context={ - 'ip_address': request[KEY_REAL_IP], - 'credential_only': data.get('type') == 'link_user', - }) + handler, + context={ + "ip_address": request[KEY_REAL_IP], + "credential_only": data.get("type") == "link_user", + }, + ) except data_entry_flow.UnknownHandler: - return self.json_message('Invalid handler specified', 404) + return self.json_message("Invalid handler specified", 404) except data_entry_flow.UnknownStep: - return self.json_message('Handler does not support init', 400) + return self.json_message("Handler does not support init", 400) - if result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY: - result.pop('data') - result['result'] = self._store_result( - data['client_id'], result['result']) + if result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY: + result.pop("data") + result["result"] = self._store_result(data["client_id"], result["result"]) return self.json(result) return self.json(_prepare_result_json(result)) @@ -192,8 +196,8 @@ class LoginFlowIndexView(HomeAssistantView): class LoginFlowResourceView(HomeAssistantView): """View to interact with the flow manager.""" - url = '/auth/login_flow/{flow_id}' - name = 'api:auth:login_flow:resource' + url = "/auth/login_flow/{flow_id}" + name = "api:auth:login_flow:resource" requires_auth = False def __init__(self, flow_mgr, store_result): @@ -203,44 +207,43 @@ class LoginFlowResourceView(HomeAssistantView): async def get(self, request): """Do not allow getting status of a flow in progress.""" - return self.json_message('Invalid flow specified', 404) + return self.json_message("Invalid flow specified", 404) - @RequestDataValidator(vol.Schema({ - 'client_id': str - }, extra=vol.ALLOW_EXTRA)) + @RequestDataValidator(vol.Schema({"client_id": str}, extra=vol.ALLOW_EXTRA)) @log_invalid_auth async def post(self, request, flow_id, data): """Handle progressing a login flow request.""" - client_id = data.pop('client_id') + client_id = data.pop("client_id") if not indieauth.verify_client_id(client_id): - return self.json_message('Invalid client id', 400) + return self.json_message("Invalid client id", 400) try: # do not allow change ip during login flow for flow in self._flow_mgr.async_progress(): - if (flow['flow_id'] == flow_id and - flow['context']['ip_address'] != - request.get(KEY_REAL_IP)): - return self.json_message('IP address changed', 400) + if flow["flow_id"] == flow_id and flow["context"][ + "ip_address" + ] != request.get(KEY_REAL_IP): + return self.json_message("IP address changed", 400) result = await self._flow_mgr.async_configure(flow_id, data) except data_entry_flow.UnknownFlow: - return self.json_message('Invalid flow specified', 404) + return self.json_message("Invalid flow specified", 404) except vol.Invalid: - return self.json_message('User input malformed', 400) + return self.json_message("User input malformed", 400) - if result['type'] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY: + if result["type"] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY: # @log_invalid_auth does not work here since it returns HTTP 200 # need manually log failed login attempts - if (result.get('errors') is not None and - result['errors'].get('base') in ['invalid_auth', - 'invalid_code']): + if result.get("errors") is not None and result["errors"].get("base") in [ + "invalid_auth", + "invalid_code", + ]: await process_wrong_login(request) return self.json(_prepare_result_json(result)) - result.pop('data') - result['result'] = self._store_result(client_id, result['result']) + result.pop("data") + result["result"] = self._store_result(client_id, result["result"]) return self.json(result) @@ -249,6 +252,6 @@ class LoginFlowResourceView(HomeAssistantView): try: self._flow_mgr.async_abort(flow_id) except data_entry_flow.UnknownFlow: - return self.json_message('Invalid flow specified', 404) + return self.json_message("Invalid flow specified", 404) - return self.json_message('Flow aborted') + return self.json_message("Flow aborted") diff --git a/homeassistant/components/auth/mfa_setup_flow.py b/homeassistant/components/auth/mfa_setup_flow.py index 121d95aede3..89c3e87f78a 100644 --- a/homeassistant/components/auth/mfa_setup_flow.py +++ b/homeassistant/components/auth/mfa_setup_flow.py @@ -7,82 +7,93 @@ from homeassistant import data_entry_flow from homeassistant.components import websocket_api from homeassistant.core import callback, HomeAssistant -WS_TYPE_SETUP_MFA = 'auth/setup_mfa' -SCHEMA_WS_SETUP_MFA = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_SETUP_MFA, - vol.Exclusive('mfa_module_id', 'module_or_flow_id'): str, - vol.Exclusive('flow_id', 'module_or_flow_id'): str, - vol.Optional('user_input'): object, -}) +WS_TYPE_SETUP_MFA = "auth/setup_mfa" +SCHEMA_WS_SETUP_MFA = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + { + vol.Required("type"): WS_TYPE_SETUP_MFA, + vol.Exclusive("mfa_module_id", "module_or_flow_id"): str, + vol.Exclusive("flow_id", "module_or_flow_id"): str, + vol.Optional("user_input"): object, + } +) -WS_TYPE_DEPOSE_MFA = 'auth/depose_mfa' -SCHEMA_WS_DEPOSE_MFA = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_DEPOSE_MFA, - vol.Required('mfa_module_id'): str, -}) +WS_TYPE_DEPOSE_MFA = "auth/depose_mfa" +SCHEMA_WS_DEPOSE_MFA = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {vol.Required("type"): WS_TYPE_DEPOSE_MFA, vol.Required("mfa_module_id"): str} +) -DATA_SETUP_FLOW_MGR = 'auth_mfa_setup_flow_manager' +DATA_SETUP_FLOW_MGR = "auth_mfa_setup_flow_manager" _LOGGER = logging.getLogger(__name__) async def async_setup(hass): """Init mfa setup flow manager.""" + async def _async_create_setup_flow(handler, context, data): """Create a setup flow. hanlder is a mfa module.""" mfa_module = hass.auth.get_auth_mfa_module(handler) if mfa_module is None: - raise ValueError('Mfa module {} is not found'.format(handler)) + raise ValueError("Mfa module {} is not found".format(handler)) - user_id = data.pop('user_id') + user_id = data.pop("user_id") return await mfa_module.async_setup_flow(user_id) async def _async_finish_setup_flow(flow, flow_result): - _LOGGER.debug('flow_result: %s', flow_result) + _LOGGER.debug("flow_result: %s", flow_result) return flow_result hass.data[DATA_SETUP_FLOW_MGR] = data_entry_flow.FlowManager( - hass, _async_create_setup_flow, _async_finish_setup_flow) + hass, _async_create_setup_flow, _async_finish_setup_flow + ) hass.components.websocket_api.async_register_command( - WS_TYPE_SETUP_MFA, websocket_setup_mfa, SCHEMA_WS_SETUP_MFA) + WS_TYPE_SETUP_MFA, websocket_setup_mfa, SCHEMA_WS_SETUP_MFA + ) hass.components.websocket_api.async_register_command( - WS_TYPE_DEPOSE_MFA, websocket_depose_mfa, SCHEMA_WS_DEPOSE_MFA) + WS_TYPE_DEPOSE_MFA, websocket_depose_mfa, SCHEMA_WS_DEPOSE_MFA + ) @callback @websocket_api.ws_require_user(allow_system_user=False) def websocket_setup_mfa( - hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg): + hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg +): """Return a setup flow for mfa auth module.""" + async def async_setup_flow(msg): """Return a setup flow for mfa auth module.""" flow_manager = hass.data[DATA_SETUP_FLOW_MGR] - flow_id = msg.get('flow_id') + flow_id = msg.get("flow_id") if flow_id is not None: - result = await flow_manager.async_configure( - flow_id, msg.get('user_input')) + result = await flow_manager.async_configure(flow_id, msg.get("user_input")) connection.send_message( - websocket_api.result_message( - msg['id'], _prepare_result_json(result))) + websocket_api.result_message(msg["id"], _prepare_result_json(result)) + ) return - mfa_module_id = msg.get('mfa_module_id') + mfa_module_id = msg.get("mfa_module_id") mfa_module = hass.auth.get_auth_mfa_module(mfa_module_id) if mfa_module is None: - connection.send_message(websocket_api.error_message( - msg['id'], 'no_module', - 'MFA module {} is not found'.format(mfa_module_id))) + connection.send_message( + websocket_api.error_message( + msg["id"], + "no_module", + "MFA module {} is not found".format(mfa_module_id), + ) + ) return result = await flow_manager.async_init( - mfa_module_id, data={'user_id': connection.user.id}) + mfa_module_id, data={"user_id": connection.user.id} + ) connection.send_message( - websocket_api.result_message( - msg['id'], _prepare_result_json(result))) + websocket_api.result_message(msg["id"], _prepare_result_json(result)) + ) hass.async_create_task(async_setup_flow(msg)) @@ -90,45 +101,49 @@ def websocket_setup_mfa( @callback @websocket_api.ws_require_user(allow_system_user=False) def websocket_depose_mfa( - hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg): + hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg +): """Remove user from mfa module.""" + async def async_depose(msg): """Remove user from mfa auth module.""" - mfa_module_id = msg['mfa_module_id'] + mfa_module_id = msg["mfa_module_id"] try: await hass.auth.async_disable_user_mfa( - connection.user, msg['mfa_module_id']) + connection.user, msg["mfa_module_id"] + ) except ValueError as err: - connection.send_message(websocket_api.error_message( - msg['id'], 'disable_failed', - 'Cannot disable MFA Module {}: {}'.format( - mfa_module_id, err))) + connection.send_message( + websocket_api.error_message( + msg["id"], + "disable_failed", + "Cannot disable MFA Module {}: {}".format(mfa_module_id, err), + ) + ) return - connection.send_message( - websocket_api.result_message( - msg['id'], 'done')) + connection.send_message(websocket_api.result_message(msg["id"], "done")) hass.async_create_task(async_depose(msg)) def _prepare_result_json(result): """Convert result to JSON.""" - if result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY: + if result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY: data = result.copy() return data - if result['type'] != data_entry_flow.RESULT_TYPE_FORM: + if result["type"] != data_entry_flow.RESULT_TYPE_FORM: return result import voluptuous_serialize data = result.copy() - schema = data['data_schema'] + schema = data["data_schema"] if schema is None: - data['data_schema'] = [] + data["data_schema"] = [] else: - data['data_schema'] = voluptuous_serialize.convert(schema) + data["data_schema"] = voluptuous_serialize.convert(schema) return data diff --git a/homeassistant/components/automatic/device_tracker.py b/homeassistant/components/automatic/device_tracker.py index 04e069a04f9..09cf3f67114 100644 --- a/homeassistant/components/automatic/device_tracker.py +++ b/homeassistant/components/automatic/device_tracker.py @@ -9,8 +9,14 @@ from aiohttp import web import voluptuous as vol from homeassistant.components.device_tracker import ( - ATTR_ATTRIBUTES, ATTR_DEV_ID, ATTR_GPS, ATTR_GPS_ACCURACY, ATTR_HOST_NAME, - ATTR_MAC, PLATFORM_SCHEMA) + ATTR_ATTRIBUTES, + ATTR_DEV_ID, + ATTR_GPS, + ATTR_GPS_ACCURACY, + ATTR_HOST_NAME, + ATTR_MAC, + PLATFORM_SCHEMA, +) from homeassistant.components.http import HomeAssistantView from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import callback @@ -20,28 +26,30 @@ from homeassistant.helpers.event import async_track_time_interval _LOGGER = logging.getLogger(__name__) -ATTR_FUEL_LEVEL = 'fuel_level' -AUTOMATIC_CONFIG_FILE = '.automatic/session-{}.json' +ATTR_FUEL_LEVEL = "fuel_level" +AUTOMATIC_CONFIG_FILE = ".automatic/session-{}.json" -CONF_CLIENT_ID = 'client_id' -CONF_CURRENT_LOCATION = 'current_location' -CONF_DEVICES = 'devices' -CONF_SECRET = 'secret' +CONF_CLIENT_ID = "client_id" +CONF_CURRENT_LOCATION = "current_location" +CONF_DEVICES = "devices" +CONF_SECRET = "secret" -DATA_CONFIGURING = 'automatic_configurator_clients' -DATA_REFRESH_TOKEN = 'refresh_token' -DEFAULT_SCOPE = ['location', 'trip', 'vehicle:events', 'vehicle:profile'] +DATA_CONFIGURING = "automatic_configurator_clients" +DATA_REFRESH_TOKEN = "refresh_token" +DEFAULT_SCOPE = ["location", "trip", "vehicle:events", "vehicle:profile"] DEFAULT_TIMEOUT = 5 -EVENT_AUTOMATIC_UPDATE = 'automatic_update' +EVENT_AUTOMATIC_UPDATE = "automatic_update" -FULL_SCOPE = DEFAULT_SCOPE + ['current_location'] +FULL_SCOPE = DEFAULT_SCOPE + ["current_location"] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_CLIENT_ID): cv.string, - vol.Required(CONF_SECRET): cv.string, - vol.Optional(CONF_CURRENT_LOCATION, default=False): cv.boolean, - vol.Optional(CONF_DEVICES): vol.All(cv.ensure_list, [cv.string]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_CLIENT_ID): cv.string, + vol.Required(CONF_SECRET): cv.string, + vol.Optional(CONF_CURRENT_LOCATION, default=False): cv.boolean, + vol.Optional(CONF_DEVICES): vol.All(cv.ensure_list, [cv.string]), + } +) def _get_refresh_token_from_file(hass, filename): @@ -67,10 +75,8 @@ def _write_refresh_token_to_file(hass, filename, refresh_token): path = hass.config.path(filename) os.makedirs(os.path.dirname(path), exist_ok=True) - with open(path, 'w+') as data_file: - json.dump({ - DATA_REFRESH_TOKEN: refresh_token - }, data_file) + with open(path, "w+") as data_file: + json.dump({DATA_REFRESH_TOKEN: refresh_token}, data_file) @asyncio.coroutine @@ -86,20 +92,21 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None): client_id=config[CONF_CLIENT_ID], client_secret=config[CONF_SECRET], client_session=async_get_clientsession(hass), - request_kwargs={'timeout': DEFAULT_TIMEOUT}) + request_kwargs={"timeout": DEFAULT_TIMEOUT}, + ) filename = AUTOMATIC_CONFIG_FILE.format(config[CONF_CLIENT_ID]) refresh_token = yield from hass.async_add_job( - _get_refresh_token_from_file, hass, filename) + _get_refresh_token_from_file, hass, filename + ) @asyncio.coroutine def initialize_data(session): """Initialize the AutomaticData object from the created session.""" hass.async_add_job( - _write_refresh_token_to_file, hass, filename, - session.refresh_token) - data = AutomaticData( - hass, client, session, config.get(CONF_DEVICES), async_see) + _write_refresh_token_to_file, hass, filename, session.refresh_token + ) + data = AutomaticData(hass, client, session, config.get(CONF_DEVICES), async_see) # Load the initial vehicle data vehicles = yield from session.get_vehicles() @@ -112,8 +119,7 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None): if refresh_token is not None: try: - session = yield from client.create_session_from_refresh_token( - refresh_token) + session = yield from client.create_session_from_refresh_token(refresh_token) yield from initialize_data(session) return True except aioautomatic.exceptions.AutomaticError as err: @@ -121,8 +127,8 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None): configurator = hass.components.configurator request_id = configurator.async_request_config( - "Automatic", description=( - "Authorization required for Automatic device tracker."), + "Automatic", + description=("Authorization required for Automatic device tracker."), link_name="Click here to authorize Home Assistant.", link_url=client.generate_oauth_url(scope), entity_picture="/static/images/logo_automatic.png", @@ -132,8 +138,7 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None): def initialize_callback(code, state): """Call after OAuth2 response is returned.""" try: - session = yield from client.create_session_from_oauth_code( - code, state) + session = yield from client.create_session_from_oauth_code(code, state) yield from initialize_data(session) configurator.async_request_done(request_id) except aioautomatic.exceptions.AutomaticError as err: @@ -152,32 +157,32 @@ class AutomaticAuthCallbackView(HomeAssistantView): """Handle OAuth finish callback requests.""" requires_auth = False - url = '/api/automatic/callback' - name = 'api:automatic:callback' + url = "/api/automatic/callback" + name = "api:automatic:callback" @callback def get(self, request): # pylint: disable=no-self-use """Finish OAuth callback request.""" - hass = request.app['hass'] + hass = request.app["hass"] params = request.query - response = web.HTTPFound('/states') + response = web.HTTPFound("/states") - if 'state' not in params or 'code' not in params: - if 'error' in params: - _LOGGER.error( - "Error authorizing Automatic: %s", params['error']) + if "state" not in params or "code" not in params: + if "error" in params: + _LOGGER.error("Error authorizing Automatic: %s", params["error"]) return response - _LOGGER.error( - "Error authorizing Automatic. Invalid response returned") + _LOGGER.error("Error authorizing Automatic. Invalid response returned") return response - if DATA_CONFIGURING not in hass.data or \ - params['state'] not in hass.data[DATA_CONFIGURING]: + if ( + DATA_CONFIGURING not in hass.data + or params["state"] not in hass.data[DATA_CONFIGURING] + ): _LOGGER.error("Automatic configuration request not found") return response - code = params['code'] - state = params['state'] + code = params["code"] + state = params["state"] initialize_callback = hass.data[DATA_CONFIGURING][state] hass.async_create_task(initialize_callback(code, state)) @@ -201,7 +206,9 @@ class AutomaticData: self.client.on_app_event( lambda name, event: self.hass.async_create_task( - self.handle_event(name, event))) + self.handle_event(name, event) + ) + ) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.ws_close()) @@ -225,9 +232,11 @@ class AutomaticData: if event.created_at < self.vehicle_seen[event.vehicle.id]: # Skip events received out of order - _LOGGER.debug("Skipping out of order event. Event Created %s. " - "Last seen event: %s", event.created_at, - self.vehicle_seen[event.vehicle.id]) + _LOGGER.debug( + "Skipping out of order event. Event Created %s. " "Last seen event: %s", + event.created_at, + self.vehicle_seen[event.vehicle.id], + ) return self.vehicle_seen[event.vehicle.id] = event.created_at @@ -253,6 +262,7 @@ class AutomaticData: def ws_connect(self, now=None): """Open the websocket connection.""" import aioautomatic + self.ws_close_requested = False if self.ws_reconnect_handle is not None: @@ -260,16 +270,19 @@ class AutomaticData: try: ws_loop_future = yield from self.client.ws_connect() except aioautomatic.exceptions.UnauthorizedClientError: - _LOGGER.error("Client unauthorized for websocket connection. " - "Ensure Websocket is selected in the Automatic " - "developer application event delivery preferences") + _LOGGER.error( + "Client unauthorized for websocket connection. " + "Ensure Websocket is selected in the Automatic " + "developer application event delivery preferences" + ) return except aioautomatic.exceptions.AutomaticError as err: if self.ws_reconnect_handle is None: # Show log error and retry connection every 5 minutes _LOGGER.error("Error opening websocket connection: %s", err) self.ws_reconnect_handle = async_track_time_interval( - self.hass, self.ws_connect, timedelta(minutes=5)) + self.hass, self.ws_connect, timedelta(minutes=5) + ) return if self.ws_reconnect_handle is not None: @@ -312,8 +325,9 @@ class AutomaticData: name = vehicle.display_name if name is None: - name = ' '.join(filter(None, ( - str(vehicle.year), vehicle.make, vehicle.model))) + name = " ".join( + filter(None, (str(vehicle.year), vehicle.make, vehicle.model)) + ) if self.devices is not None and name not in self.devices: self.vehicle_info[vehicle.id] = None @@ -323,12 +337,9 @@ class AutomaticData: ATTR_DEV_ID: vehicle.id, ATTR_HOST_NAME: name, ATTR_MAC: vehicle.id, - ATTR_ATTRIBUTES: { - ATTR_FUEL_LEVEL: vehicle.fuel_level_percent, - } + ATTR_ATTRIBUTES: {ATTR_FUEL_LEVEL: vehicle.fuel_level_percent}, } - self.vehicle_seen[vehicle.id] = \ - vehicle.updated_at or vehicle.created_at + self.vehicle_seen[vehicle.id] = vehicle.updated_at or vehicle.created_at if vehicle.latest_location is not None: location = vehicle.latest_location @@ -339,8 +350,7 @@ class AutomaticData: trips = [] try: # Get the most recent trip for this vehicle - trips = yield from self.session.get_trips( - vehicle=vehicle.id, limit=1) + trips = yield from self.session.get_trips(vehicle=vehicle.id, limit=1) except aioautomatic.exceptions.AutomaticError as err: _LOGGER.error(str(err)) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index f0011f07561..935f6ea0d02 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -7,9 +7,18 @@ import logging import voluptuous as vol from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_NAME, CONF_ID, CONF_PLATFORM, - EVENT_AUTOMATION_TRIGGERED, EVENT_HOMEASSISTANT_START, SERVICE_RELOAD, - SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_ON) + ATTR_ENTITY_ID, + ATTR_NAME, + CONF_ID, + CONF_PLATFORM, + EVENT_AUTOMATION_TRIGGERED, + EVENT_HOMEASSISTANT_START, + SERVICE_RELOAD, + SERVICE_TOGGLE, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_ON, +) from homeassistant.core import Context, CoreState from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import condition, extract_domain_configs, script @@ -21,31 +30,31 @@ from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.loader import bind_hass from homeassistant.util.dt import parse_datetime, utcnow -DOMAIN = 'automation' -ENTITY_ID_FORMAT = DOMAIN + '.{}' +DOMAIN = "automation" +ENTITY_ID_FORMAT = DOMAIN + ".{}" -GROUP_NAME_ALL_AUTOMATIONS = 'all automations' +GROUP_NAME_ALL_AUTOMATIONS = "all automations" -CONF_ALIAS = 'alias' -CONF_HIDE_ENTITY = 'hide_entity' +CONF_ALIAS = "alias" +CONF_HIDE_ENTITY = "hide_entity" -CONF_CONDITION = 'condition' -CONF_ACTION = 'action' -CONF_TRIGGER = 'trigger' -CONF_CONDITION_TYPE = 'condition_type' -CONF_INITIAL_STATE = 'initial_state' +CONF_CONDITION = "condition" +CONF_ACTION = "action" +CONF_TRIGGER = "trigger" +CONF_CONDITION_TYPE = "condition_type" +CONF_INITIAL_STATE = "initial_state" -CONDITION_USE_TRIGGER_VALUES = 'use_trigger_values' -CONDITION_TYPE_AND = 'and' -CONDITION_TYPE_OR = 'or' +CONDITION_USE_TRIGGER_VALUES = "use_trigger_values" +CONDITION_TYPE_AND = "and" +CONDITION_TYPE_OR = "or" DEFAULT_CONDITION_TYPE = CONDITION_TYPE_AND DEFAULT_HIDE_ENTITY = False DEFAULT_INITIAL_STATE = True -ATTR_LAST_TRIGGERED = 'last_triggered' -ATTR_VARIABLES = 'variables' -SERVICE_TRIGGER = 'trigger' +ATTR_LAST_TRIGGERED = "last_triggered" +ATTR_VARIABLES = "variables" +SERVICE_TRIGGER = "trigger" _LOGGER = logging.getLogger(__name__) @@ -53,10 +62,11 @@ _LOGGER = logging.getLogger(__name__) def _platform_validator(config): """Validate it is a valid platform.""" try: - platform = importlib.import_module('.{}'.format(config[CONF_PLATFORM]), - __name__) + platform = importlib.import_module( + ".{}".format(config[CONF_PLATFORM]), __name__ + ) except ImportError: - raise vol.Invalid('Invalid platform specified') from None + raise vol.Invalid("Invalid platform specified") from None return platform.TRIGGER_SCHEMA(config) @@ -65,30 +75,30 @@ _TRIGGER_SCHEMA = vol.All( cv.ensure_list, [ vol.All( - vol.Schema({ - vol.Required(CONF_PLATFORM): str - }, extra=vol.ALLOW_EXTRA), - _platform_validator - ), - ] + vol.Schema({vol.Required(CONF_PLATFORM): str}, extra=vol.ALLOW_EXTRA), + _platform_validator, + ) + ], ) _CONDITION_SCHEMA = vol.All(cv.ensure_list, [cv.CONDITION_SCHEMA]) -PLATFORM_SCHEMA = vol.Schema({ - # str on purpose - CONF_ID: str, - CONF_ALIAS: cv.string, - vol.Optional(CONF_INITIAL_STATE): cv.boolean, - vol.Optional(CONF_HIDE_ENTITY, default=DEFAULT_HIDE_ENTITY): cv.boolean, - vol.Required(CONF_TRIGGER): _TRIGGER_SCHEMA, - vol.Optional(CONF_CONDITION): _CONDITION_SCHEMA, - vol.Required(CONF_ACTION): cv.SCRIPT_SCHEMA, -}) +PLATFORM_SCHEMA = vol.Schema( + { + # str on purpose + CONF_ID: str, + CONF_ALIAS: cv.string, + vol.Optional(CONF_INITIAL_STATE): cv.boolean, + vol.Optional(CONF_HIDE_ENTITY, default=DEFAULT_HIDE_ENTITY): cv.boolean, + vol.Required(CONF_TRIGGER): _TRIGGER_SCHEMA, + vol.Optional(CONF_CONDITION): _CONDITION_SCHEMA, + vol.Required(CONF_ACTION): cv.SCRIPT_SCHEMA, + } +) -TRIGGER_SERVICE_SCHEMA = ENTITY_SERVICE_SCHEMA.extend({ - vol.Optional(ATTR_VARIABLES, default={}): dict, -}) +TRIGGER_SERVICE_SCHEMA = ENTITY_SERVICE_SCHEMA.extend( + {vol.Optional(ATTR_VARIABLES, default={}): dict} +) RELOAD_SERVICE_SCHEMA = vol.Schema({}) @@ -105,8 +115,9 @@ def is_on(hass, entity_id): async def async_setup(hass, config): """Set up the automation.""" - component = EntityComponent(_LOGGER, DOMAIN, hass, - group_name=GROUP_NAME_ALL_AUTOMATIONS) + component = EntityComponent( + _LOGGER, DOMAIN, hass, group_name=GROUP_NAME_ALL_AUTOMATIONS + ) await _async_process_config(hass, config, component) @@ -114,10 +125,13 @@ async def async_setup(hass, config): """Handle automation triggers.""" tasks = [] for entity in await component.async_extract_from_service(service_call): - tasks.append(entity.async_trigger( - service_call.data.get(ATTR_VARIABLES), - skip_condition=True, - context=service_call.context)) + tasks.append( + entity.async_trigger( + service_call.data.get(ATTR_VARIABLES), + skip_condition=True, + context=service_call.context, + ) + ) if tasks: await asyncio.wait(tasks) @@ -125,7 +139,7 @@ async def async_setup(hass, config): async def turn_onoff_service_handler(service_call): """Handle automation turn on/off service calls.""" tasks = [] - method = 'async_{}'.format(service_call.service) + method = "async_{}".format(service_call.service) for entity in await component.async_extract_from_service(service_call): tasks.append(getattr(entity, method)()) @@ -152,21 +166,21 @@ async def async_setup(hass, config): await _async_process_config(hass, conf, component) hass.services.async_register( - DOMAIN, SERVICE_TRIGGER, trigger_service_handler, - schema=TRIGGER_SERVICE_SCHEMA) + DOMAIN, SERVICE_TRIGGER, trigger_service_handler, schema=TRIGGER_SERVICE_SCHEMA + ) hass.services.async_register( - DOMAIN, SERVICE_RELOAD, reload_service_handler, - schema=RELOAD_SERVICE_SCHEMA) + DOMAIN, SERVICE_RELOAD, reload_service_handler, schema=RELOAD_SERVICE_SCHEMA + ) hass.services.async_register( - DOMAIN, SERVICE_TOGGLE, toggle_service_handler, - schema=ENTITY_SERVICE_SCHEMA) + DOMAIN, SERVICE_TOGGLE, toggle_service_handler, schema=ENTITY_SERVICE_SCHEMA + ) for service in (SERVICE_TURN_ON, SERVICE_TURN_OFF): hass.services.async_register( - DOMAIN, service, turn_onoff_service_handler, - schema=ENTITY_SERVICE_SCHEMA) + DOMAIN, service, turn_onoff_service_handler, schema=ENTITY_SERVICE_SCHEMA + ) return True @@ -174,8 +188,16 @@ async def async_setup(hass, config): class AutomationEntity(ToggleEntity, RestoreEntity): """Entity to show status of entity.""" - def __init__(self, automation_id, name, async_attach_triggers, cond_func, - async_action, hidden, initial_state): + def __init__( + self, + automation_id, + name, + async_attach_triggers, + cond_func, + async_action, + hidden, + initial_state, + ): """Initialize an automation entity.""" self._id = automation_id self._name = name @@ -201,9 +223,7 @@ class AutomationEntity(ToggleEntity, RestoreEntity): @property def state_attributes(self): """Return the entity state attributes.""" - return { - ATTR_LAST_TRIGGERED: self._last_triggered - } + return {ATTR_LAST_TRIGGERED: self._last_triggered} @property def hidden(self) -> bool: @@ -213,8 +233,7 @@ class AutomationEntity(ToggleEntity, RestoreEntity): @property def is_on(self) -> bool: """Return True if entity is on.""" - return (self._async_detach_triggers is not None or - self._is_enabled) + return self._async_detach_triggers is not None or self._is_enabled async def async_added_to_hass(self) -> None: """Startup with initial state or previous state.""" @@ -223,23 +242,32 @@ class AutomationEntity(ToggleEntity, RestoreEntity): state = await self.async_get_last_state() if state: enable_automation = state.state == STATE_ON - last_triggered = state.attributes.get('last_triggered') + last_triggered = state.attributes.get("last_triggered") if last_triggered is not None: self._last_triggered = parse_datetime(last_triggered) - _LOGGER.debug("Loaded automation %s with state %s from state " - " storage last state %s", self.entity_id, - enable_automation, state) + _LOGGER.debug( + "Loaded automation %s with state %s from state " + " storage last state %s", + self.entity_id, + enable_automation, + state, + ) else: enable_automation = DEFAULT_INITIAL_STATE - _LOGGER.debug("Automation %s not in state storage, state %s from " - "default is used.", self.entity_id, - enable_automation) + _LOGGER.debug( + "Automation %s not in state storage, state %s from " "default is used.", + self.entity_id, + enable_automation, + ) if self._initial_state is not None: enable_automation = self._initial_state - _LOGGER.debug("Automation %s initial state %s overridden from " - "config initial_state", self.entity_id, - enable_automation) + _LOGGER.debug( + "Automation %s initial state %s overridden from " + "config initial_state", + self.entity_id, + enable_automation, + ) if enable_automation: await self.async_enable() @@ -252,8 +280,7 @@ class AutomationEntity(ToggleEntity, RestoreEntity): """Turn the entity off.""" await self.async_disable() - async def async_trigger(self, variables, skip_condition=False, - context=None): + async def async_trigger(self, variables, skip_condition=False, context=None): """Trigger automation. This method is a coroutine. @@ -266,10 +293,11 @@ class AutomationEntity(ToggleEntity, RestoreEntity): trigger_context = Context(parent_id=parent_id) self.async_set_context(trigger_context) - self.hass.bus.async_fire(EVENT_AUTOMATION_TRIGGERED, { - ATTR_NAME: self._name, - ATTR_ENTITY_ID: self.entity_id, - }, context=trigger_context) + self.hass.bus.async_fire( + EVENT_AUTOMATION_TRIGGERED, + {ATTR_NAME: self._name, ATTR_ENTITY_ID: self.entity_id}, + context=trigger_context, + ) await self._async_action(self.entity_id, variables, trigger_context) self._last_triggered = utcnow() await self.async_update_ha_state() @@ -292,22 +320,24 @@ class AutomationEntity(ToggleEntity, RestoreEntity): # HomeAssistant is starting up if self.hass.state != CoreState.not_running: self._async_detach_triggers = await self._async_attach_triggers( - self.async_trigger) + self.async_trigger + ) self.async_write_ha_state() return async def async_enable_automation(event): """Start automation on startup.""" # Don't do anything if no longer enabled or already attached - if (not self._is_enabled or - self._async_detach_triggers is not None): + if not self._is_enabled or self._async_detach_triggers is not None: return self._async_detach_triggers = await self._async_attach_triggers( - self.async_trigger) + self.async_trigger + ) self.hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_START, async_enable_automation) + EVENT_HOMEASSISTANT_START, async_enable_automation + ) self.async_write_ha_state() async def async_disable(self): @@ -329,9 +359,7 @@ class AutomationEntity(ToggleEntity, RestoreEntity): if self._id is None: return None - return { - CONF_ID: self._id - } + return {CONF_ID: self._id} async def _async_process_config(hass, config, component): @@ -346,14 +374,12 @@ async def _async_process_config(hass, config, component): for list_no, config_block in enumerate(conf): automation_id = config_block.get(CONF_ID) - name = config_block.get(CONF_ALIAS) or "{} {}".format(config_key, - list_no) + name = config_block.get(CONF_ALIAS) or "{} {}".format(config_key, list_no) hidden = config_block[CONF_HIDE_ENTITY] initial_state = config_block.get(CONF_INITIAL_STATE) - action = _async_get_action(hass, config_block.get(CONF_ACTION, {}), - name) + action = _async_get_action(hass, config_block.get(CONF_ACTION, {}), name) if CONF_CONDITION in config_block: cond_func = _async_process_if(hass, config, config_block) @@ -361,17 +387,27 @@ async def _async_process_config(hass, config, component): if cond_func is None: continue else: + def cond_func(variables): """Condition will always pass.""" return True async_attach_triggers = partial( - _async_process_trigger, hass, config, - config_block.get(CONF_TRIGGER, []), name + _async_process_trigger, + hass, + config, + config_block.get(CONF_TRIGGER, []), + name, ) entity = AutomationEntity( - automation_id, name, async_attach_triggers, cond_func, action, - hidden, initial_state) + automation_id, + name, + async_attach_triggers, + cond_func, + action, + hidden, + initial_state, + ) entities.append(entity) @@ -385,14 +421,14 @@ def _async_get_action(hass, config, name): async def action(entity_id, variables, context): """Execute an action.""" - _LOGGER.info('Executing %s', name) + _LOGGER.info("Executing %s", name) try: await script_obj.async_run(variables, context) except Exception as err: # pylint: disable=broad-except script_obj.async_log_exception( - _LOGGER, - 'Error while executing automation {}'.format(entity_id), err) + _LOGGER, "Error while executing automation {}".format(entity_id), err + ) return action @@ -406,7 +442,7 @@ def _async_process_if(hass, config, p_config): try: checks.append(condition.async_from_config(if_config, False)) except HomeAssistantError as ex: - _LOGGER.warning('Invalid condition: %s', ex) + _LOGGER.warning("Invalid condition: %s", ex) return None def if_action(variables=None): @@ -422,13 +458,10 @@ async def _async_process_trigger(hass, config, trigger_configs, name, action): This method is a coroutine. """ removes = [] - info = { - 'name': name - } + info = {"name": name} for conf in trigger_configs: - platform = importlib.import_module('.{}'.format(conf[CONF_PLATFORM]), - __name__) + platform = importlib.import_module(".{}".format(conf[CONF_PLATFORM]), __name__) remove = await platform.async_trigger(hass, conf, action, info) diff --git a/homeassistant/components/automation/device.py b/homeassistant/components/automation/device.py index 4e59018b41c..2bb70fa1c96 100644 --- a/homeassistant/components/automation/device.py +++ b/homeassistant/components/automation/device.py @@ -5,14 +5,14 @@ from homeassistant.const import CONF_DOMAIN, CONF_PLATFORM from homeassistant.loader import async_get_integration -TRIGGER_SCHEMA = vol.Schema({ - vol.Required(CONF_PLATFORM): 'device', - vol.Required(CONF_DOMAIN): str, -}, extra=vol.ALLOW_EXTRA) +TRIGGER_SCHEMA = vol.Schema( + {vol.Required(CONF_PLATFORM): "device", vol.Required(CONF_DOMAIN): str}, + extra=vol.ALLOW_EXTRA, +) async def async_trigger(hass, config, action, automation_info): """Listen for trigger.""" integration = await async_get_integration(hass, config[CONF_DOMAIN]) - platform = integration.get_platform('device_automation') + platform = integration.get_platform("device_automation") return await platform.async_trigger(hass, config, action, automation_info) diff --git a/homeassistant/components/automation/event.py b/homeassistant/components/automation/event.py index 6cc7e3dae7d..b353eb56196 100644 --- a/homeassistant/components/automation/event.py +++ b/homeassistant/components/automation/event.py @@ -7,24 +7,28 @@ from homeassistant.core import callback from homeassistant.const import CONF_PLATFORM from homeassistant.helpers import config_validation as cv -CONF_EVENT_TYPE = 'event_type' -CONF_EVENT_DATA = 'event_data' +CONF_EVENT_TYPE = "event_type" +CONF_EVENT_DATA = "event_data" _LOGGER = logging.getLogger(__name__) -TRIGGER_SCHEMA = vol.Schema({ - vol.Required(CONF_PLATFORM): 'event', - vol.Required(CONF_EVENT_TYPE): cv.string, - vol.Optional(CONF_EVENT_DATA): dict, -}) +TRIGGER_SCHEMA = vol.Schema( + { + vol.Required(CONF_PLATFORM): "event", + vol.Required(CONF_EVENT_TYPE): cv.string, + vol.Optional(CONF_EVENT_DATA): dict, + } +) async def async_trigger(hass, config, action, automation_info): """Listen for events based on configuration.""" event_type = config.get(CONF_EVENT_TYPE) - event_data_schema = vol.Schema( - config.get(CONF_EVENT_DATA), - extra=vol.ALLOW_EXTRA) if config.get(CONF_EVENT_DATA) else None + event_data_schema = ( + vol.Schema(config.get(CONF_EVENT_DATA), extra=vol.ALLOW_EXTRA) + if config.get(CONF_EVENT_DATA) + else None + ) @callback def handle_event(event): @@ -38,11 +42,11 @@ async def async_trigger(hass, config, action, automation_info): # If event data doesn't match requested schema, skip event return - hass.async_run_job(action({ - 'trigger': { - 'platform': 'event', - 'event': event, - }, - }, context=event.context)) + hass.async_run_job( + action( + {"trigger": {"platform": "event", "event": event}}, + context=event.context, + ) + ) return hass.bus.async_listen(event_type, handle_event) diff --git a/homeassistant/components/automation/geo_location.py b/homeassistant/components/automation/geo_location.py index 8f838ea6d6b..7c0994c4b30 100644 --- a/homeassistant/components/automation/geo_location.py +++ b/homeassistant/components/automation/geo_location.py @@ -4,27 +4,34 @@ import voluptuous as vol from homeassistant.components.geo_location import DOMAIN from homeassistant.core import callback from homeassistant.const import ( - CONF_EVENT, CONF_PLATFORM, CONF_SOURCE, CONF_ZONE, EVENT_STATE_CHANGED) -from homeassistant.helpers import ( - condition, config_validation as cv) + CONF_EVENT, + CONF_PLATFORM, + CONF_SOURCE, + CONF_ZONE, + EVENT_STATE_CHANGED, +) +from homeassistant.helpers import condition, config_validation as cv from homeassistant.helpers.config_validation import entity_domain -EVENT_ENTER = 'enter' -EVENT_LEAVE = 'leave' +EVENT_ENTER = "enter" +EVENT_LEAVE = "leave" DEFAULT_EVENT = EVENT_ENTER -TRIGGER_SCHEMA = vol.Schema({ - vol.Required(CONF_PLATFORM): 'geo_location', - vol.Required(CONF_SOURCE): cv.string, - vol.Required(CONF_ZONE): entity_domain('zone'), - vol.Required(CONF_EVENT, default=DEFAULT_EVENT): - vol.Any(EVENT_ENTER, EVENT_LEAVE), -}) +TRIGGER_SCHEMA = vol.Schema( + { + vol.Required(CONF_PLATFORM): "geo_location", + vol.Required(CONF_SOURCE): cv.string, + vol.Required(CONF_ZONE): entity_domain("zone"), + vol.Required(CONF_EVENT, default=DEFAULT_EVENT): vol.Any( + EVENT_ENTER, EVENT_LEAVE + ), + } +) def source_match(state, source): """Check if the state matches the provided source.""" - return state and state.attributes.get('source') == source + return state and state.attributes.get("source") == source async def async_trigger(hass, config, action, automation_info): @@ -37,13 +44,12 @@ async def async_trigger(hass, config, action, automation_info): def state_change_listener(event): """Handle specific state changes.""" # Skip if the event is not a geo_location entity. - if not event.data.get('entity_id').startswith(DOMAIN): + if not event.data.get("entity_id").startswith(DOMAIN): return # Skip if the event's source does not match the trigger's source. - from_state = event.data.get('old_state') - to_state = event.data.get('new_state') - if not source_match(from_state, source) \ - and not source_match(to_state, source): + from_state = event.data.get("old_state") + to_state = event.data.get("new_state") + if not source_match(from_state, source) and not source_match(to_state, source): return zone_state = hass.states.get(zone_entity_id) @@ -51,18 +57,29 @@ async def async_trigger(hass, config, action, automation_info): to_match = condition.zone(hass, zone_state, to_state) # pylint: disable=too-many-boolean-expressions - if trigger_event == EVENT_ENTER and not from_match and to_match or \ - trigger_event == EVENT_LEAVE and from_match and not to_match: - hass.async_run_job(action({ - 'trigger': { - 'platform': 'geo_location', - 'source': source, - 'entity_id': event.data.get('entity_id'), - 'from_state': from_state, - 'to_state': to_state, - 'zone': zone_state, - 'event': trigger_event, - }, - }, context=event.context)) + if ( + trigger_event == EVENT_ENTER + and not from_match + and to_match + or trigger_event == EVENT_LEAVE + and from_match + and not to_match + ): + hass.async_run_job( + action( + { + "trigger": { + "platform": "geo_location", + "source": source, + "entity_id": event.data.get("entity_id"), + "from_state": from_state, + "to_state": to_state, + "zone": zone_state, + "event": trigger_event, + } + }, + context=event.context, + ) + ) return hass.bus.async_listen(EVENT_STATE_CHANGED, state_change_listener) diff --git a/homeassistant/components/automation/homeassistant.py b/homeassistant/components/automation/homeassistant.py index 1b022316676..96931e62766 100644 --- a/homeassistant/components/automation/homeassistant.py +++ b/homeassistant/components/automation/homeassistant.py @@ -4,17 +4,18 @@ import logging import voluptuous as vol from homeassistant.core import callback, CoreState -from homeassistant.const import ( - CONF_PLATFORM, CONF_EVENT, EVENT_HOMEASSISTANT_STOP) +from homeassistant.const import CONF_PLATFORM, CONF_EVENT, EVENT_HOMEASSISTANT_STOP -EVENT_START = 'start' -EVENT_SHUTDOWN = 'shutdown' +EVENT_START = "start" +EVENT_SHUTDOWN = "shutdown" _LOGGER = logging.getLogger(__name__) -TRIGGER_SCHEMA = vol.Schema({ - vol.Required(CONF_PLATFORM): 'homeassistant', - vol.Required(CONF_EVENT): vol.Any(EVENT_START, EVENT_SHUTDOWN), -}) +TRIGGER_SCHEMA = vol.Schema( + { + vol.Required(CONF_PLATFORM): "homeassistant", + vol.Required(CONF_EVENT): vol.Any(EVENT_START, EVENT_SHUTDOWN), + } +) async def async_trigger(hass, config, action, automation_info): @@ -22,27 +23,24 @@ async def async_trigger(hass, config, action, automation_info): event = config.get(CONF_EVENT) if event == EVENT_SHUTDOWN: + @callback def hass_shutdown(event): """Execute when Home Assistant is shutting down.""" - hass.async_run_job(action({ - 'trigger': { - 'platform': 'homeassistant', - 'event': event, - }, - }, context=event.context)) + hass.async_run_job( + action( + {"trigger": {"platform": "homeassistant", "event": event}}, + context=event.context, + ) + ) - return hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, - hass_shutdown) + return hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, hass_shutdown) # Automation are enabled while hass is starting up, fire right away # Check state because a config reload shouldn't trigger it. if hass.state == CoreState.starting: - hass.async_run_job(action({ - 'trigger': { - 'platform': 'homeassistant', - 'event': event, - }, - })) + hass.async_run_job( + action({"trigger": {"platform": "homeassistant", "event": event}}) + ) return lambda: None diff --git a/homeassistant/components/automation/litejet.py b/homeassistant/components/automation/litejet.py index 51ec5baccfd..c642781ca66 100644 --- a/homeassistant/components/automation/litejet.py +++ b/homeassistant/components/automation/litejet.py @@ -11,18 +11,22 @@ from homeassistant.helpers.event import track_point_in_utc_time _LOGGER = logging.getLogger(__name__) -CONF_NUMBER = 'number' -CONF_HELD_MORE_THAN = 'held_more_than' -CONF_HELD_LESS_THAN = 'held_less_than' +CONF_NUMBER = "number" +CONF_HELD_MORE_THAN = "held_more_than" +CONF_HELD_LESS_THAN = "held_less_than" -TRIGGER_SCHEMA = vol.Schema({ - vol.Required(CONF_PLATFORM): 'litejet', - vol.Required(CONF_NUMBER): cv.positive_int, - vol.Optional(CONF_HELD_MORE_THAN): - vol.All(cv.time_period, cv.positive_timedelta), - vol.Optional(CONF_HELD_LESS_THAN): - vol.All(cv.time_period, cv.positive_timedelta) -}) +TRIGGER_SCHEMA = vol.Schema( + { + vol.Required(CONF_PLATFORM): "litejet", + vol.Required(CONF_NUMBER): cv.positive_int, + vol.Optional(CONF_HELD_MORE_THAN): vol.All( + cv.time_period, cv.positive_timedelta + ), + vol.Optional(CONF_HELD_LESS_THAN): vol.All( + cv.time_period, cv.positive_timedelta + ), + } +) async def async_trigger(hass, config, action, automation_info): @@ -36,14 +40,17 @@ async def async_trigger(hass, config, action, automation_info): @callback def call_action(): """Call action with right context.""" - hass.async_run_job(action, { - 'trigger': { - CONF_PLATFORM: 'litejet', - CONF_NUMBER: number, - CONF_HELD_MORE_THAN: held_more_than, - CONF_HELD_LESS_THAN: held_less_than + hass.async_run_job( + action, + { + "trigger": { + CONF_PLATFORM: "litejet", + CONF_NUMBER: number, + CONF_HELD_MORE_THAN: held_more_than, + CONF_HELD_LESS_THAN: held_less_than, + } }, - }) + ) # held_more_than and held_less_than: trigger on released (if in time range) # held_more_than: trigger after pressed with calculation @@ -64,9 +71,8 @@ async def async_trigger(hass, config, action, automation_info): hass.add_job(call_action) if held_more_than is not None and held_less_than is None: cancel_pressed_more_than = track_point_in_utc_time( - hass, - pressed_more_than_satisfied, - dt_util.utcnow() + held_more_than) + hass, pressed_more_than_satisfied, dt_util.utcnow() + held_more_than + ) def released(): """Handle the release of the LiteJet switch's button.""" @@ -81,8 +87,8 @@ async def async_trigger(hass, config, action, automation_info): if held_more_than is None or held_time > held_more_than: hass.add_job(call_action) - hass.data['litejet_system'].on_switch_pressed(number, pressed) - hass.data['litejet_system'].on_switch_released(number, released) + hass.data["litejet_system"].on_switch_pressed(number, pressed) + hass.data["litejet_system"].on_switch_released(number, released) def async_remove(): """Remove all subscriptions used for this trigger.""" diff --git a/homeassistant/components/automation/mqtt.py b/homeassistant/components/automation/mqtt.py index 837a22362b5..26c1ea5683d 100644 --- a/homeassistant/components/automation/mqtt.py +++ b/homeassistant/components/automation/mqtt.py @@ -5,19 +5,21 @@ import voluptuous as vol from homeassistant.core import callback from homeassistant.components import mqtt -from homeassistant.const import (CONF_PLATFORM, CONF_PAYLOAD) +from homeassistant.const import CONF_PLATFORM, CONF_PAYLOAD import homeassistant.helpers.config_validation as cv -CONF_ENCODING = 'encoding' -CONF_TOPIC = 'topic' -DEFAULT_ENCODING = 'utf-8' +CONF_ENCODING = "encoding" +CONF_TOPIC = "topic" +DEFAULT_ENCODING = "utf-8" -TRIGGER_SCHEMA = vol.Schema({ - vol.Required(CONF_PLATFORM): mqtt.DOMAIN, - vol.Required(CONF_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_PAYLOAD): cv.string, - vol.Optional(CONF_ENCODING, default=DEFAULT_ENCODING): cv.string, -}) +TRIGGER_SCHEMA = vol.Schema( + { + vol.Required(CONF_PLATFORM): mqtt.DOMAIN, + vol.Required(CONF_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_PAYLOAD): cv.string, + vol.Optional(CONF_ENCODING, default=DEFAULT_ENCODING): cv.string, + } +) async def async_trigger(hass, config, action, automation_info): @@ -31,21 +33,20 @@ async def async_trigger(hass, config, action, automation_info): """Listen for MQTT messages.""" if payload is None or payload == mqttmsg.payload: data = { - 'platform': 'mqtt', - 'topic': mqttmsg.topic, - 'payload': mqttmsg.payload, - 'qos': mqttmsg.qos, + "platform": "mqtt", + "topic": mqttmsg.topic, + "payload": mqttmsg.payload, + "qos": mqttmsg.qos, } try: - data['payload_json'] = json.loads(mqttmsg.payload) + data["payload_json"] = json.loads(mqttmsg.payload) except ValueError: pass - hass.async_run_job(action, { - 'trigger': data - }) + hass.async_run_job(action, {"trigger": data}) remove = await mqtt.async_subscribe( - hass, topic, mqtt_automation_listener, encoding=encoding) + hass, topic, mqtt_automation_listener, encoding=encoding + ) return remove diff --git a/homeassistant/components/automation/numeric_state.py b/homeassistant/components/automation/numeric_state.py index 7254914b72b..f990e599552 100644 --- a/homeassistant/components/automation/numeric_state.py +++ b/homeassistant/components/automation/numeric_state.py @@ -6,22 +6,33 @@ import voluptuous as vol from homeassistant import exceptions from homeassistant.core import callback from homeassistant.const import ( - CONF_VALUE_TEMPLATE, CONF_PLATFORM, CONF_ENTITY_ID, - CONF_BELOW, CONF_ABOVE, CONF_FOR) -from homeassistant.helpers.event import ( - async_track_state_change, async_track_same_state) + CONF_VALUE_TEMPLATE, + CONF_PLATFORM, + CONF_ENTITY_ID, + CONF_BELOW, + CONF_ABOVE, + CONF_FOR, +) +from homeassistant.helpers.event import async_track_state_change, async_track_same_state from homeassistant.helpers import condition, config_validation as cv, template -TRIGGER_SCHEMA = vol.All(vol.Schema({ - vol.Required(CONF_PLATFORM): 'numeric_state', - vol.Required(CONF_ENTITY_ID): cv.entity_ids, - vol.Optional(CONF_BELOW): vol.Coerce(float), - vol.Optional(CONF_ABOVE): vol.Coerce(float), - vol.Optional(CONF_VALUE_TEMPLATE): cv.template, - vol.Optional(CONF_FOR): vol.Any( - vol.All(cv.time_period, cv.positive_timedelta), - cv.template, cv.template_complex), -}), cv.has_at_least_one_key(CONF_BELOW, CONF_ABOVE)) +TRIGGER_SCHEMA = vol.All( + vol.Schema( + { + vol.Required(CONF_PLATFORM): "numeric_state", + vol.Required(CONF_ENTITY_ID): cv.entity_ids, + vol.Optional(CONF_BELOW): vol.Coerce(float), + vol.Optional(CONF_ABOVE): vol.Coerce(float), + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_FOR): vol.Any( + vol.All(cv.time_period, cv.positive_timedelta), + cv.template, + cv.template_complex, + ), + } + ), + cv.has_at_least_one_key(CONF_BELOW, CONF_ABOVE), +) _LOGGER = logging.getLogger(__name__) @@ -48,33 +59,40 @@ async def async_trigger(hass, config, action, automation_info): return False variables = { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': entity, - 'below': below, - 'above': above, + "trigger": { + "platform": "numeric_state", + "entity_id": entity, + "below": below, + "above": above, } } return condition.async_numeric_state( - hass, to_s, below, above, value_template, variables) + hass, to_s, below, above, value_template, variables + ) @callback def state_automation_listener(entity, from_s, to_s): """Listen for state changes and calls action.""" + @callback def call_action(): """Call action with right context.""" - hass.async_run_job(action({ - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': entity, - 'below': below, - 'above': above, - 'from_state': from_s, - 'to_state': to_s, - 'for': time_delta if not time_delta else period[entity], - } - }, context=to_s.context)) + hass.async_run_job( + action( + { + "trigger": { + "platform": "numeric_state", + "entity_id": entity, + "below": below, + "above": above, + "from_state": from_s, + "to_state": to_s, + "for": time_delta if not time_delta else period[entity], + } + }, + context=to_s.context, + ) + ) matching = check_numeric_state(entity, from_s, to_s) @@ -85,44 +103,49 @@ async def async_trigger(hass, config, action, automation_info): if time_delta: variables = { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': entity, - 'below': below, - 'above': above, + "trigger": { + "platform": "numeric_state", + "entity_id": entity, + "below": below, + "above": above, } } try: if isinstance(time_delta, template.Template): - period[entity] = vol.All( - cv.time_period, - cv.positive_timedelta)( - time_delta.async_render(variables)) + period[entity] = vol.All(cv.time_period, cv.positive_timedelta)( + time_delta.async_render(variables) + ) elif isinstance(time_delta, dict): time_delta_data = {} time_delta_data.update( - template.render_complex(time_delta, variables)) - period[entity] = vol.All( - cv.time_period, - cv.positive_timedelta)( - time_delta_data) + template.render_complex(time_delta, variables) + ) + period[entity] = vol.All(cv.time_period, cv.positive_timedelta)( + time_delta_data + ) else: period[entity] = time_delta except (exceptions.TemplateError, vol.Invalid) as ex: - _LOGGER.error("Error rendering '%s' for template: %s", - automation_info['name'], ex) + _LOGGER.error( + "Error rendering '%s' for template: %s", + automation_info["name"], + ex, + ) entities_triggered.discard(entity) return unsub_track_same[entity] = async_track_same_state( - hass, period[entity], call_action, entity_ids=entity, - async_check_same_func=check_numeric_state) + hass, + period[entity], + call_action, + entity_ids=entity, + async_check_same_func=check_numeric_state, + ) else: call_action() - unsub = async_track_state_change( - hass, entity_id, state_automation_listener) + unsub = async_track_state_change(hass, entity_id, state_automation_listener) @callback def async_remove(): diff --git a/homeassistant/components/automation/state.py b/homeassistant/components/automation/state.py index 9ee4ad5ac68..ccea3d9ec5a 100644 --- a/homeassistant/components/automation/state.py +++ b/homeassistant/components/automation/state.py @@ -7,25 +7,31 @@ from homeassistant import exceptions from homeassistant.core import callback from homeassistant.const import MATCH_ALL, CONF_PLATFORM, CONF_FOR from homeassistant.helpers import config_validation as cv, template -from homeassistant.helpers.event import ( - async_track_state_change, async_track_same_state) +from homeassistant.helpers.event import async_track_state_change, async_track_same_state _LOGGER = logging.getLogger(__name__) -CONF_ENTITY_ID = 'entity_id' -CONF_FROM = 'from' -CONF_TO = 'to' +CONF_ENTITY_ID = "entity_id" +CONF_FROM = "from" +CONF_TO = "to" -TRIGGER_SCHEMA = vol.All(vol.Schema({ - vol.Required(CONF_PLATFORM): 'state', - vol.Required(CONF_ENTITY_ID): cv.entity_ids, - # These are str on purpose. Want to catch YAML conversions - vol.Optional(CONF_FROM): str, - vol.Optional(CONF_TO): str, - vol.Optional(CONF_FOR): vol.Any( - vol.All(cv.time_period, cv.positive_timedelta), - cv.template, cv.template_complex), -}), cv.key_dependency(CONF_FOR, CONF_TO)) +TRIGGER_SCHEMA = vol.All( + vol.Schema( + { + vol.Required(CONF_PLATFORM): "state", + vol.Required(CONF_ENTITY_ID): cv.entity_ids, + # These are str on purpose. Want to catch YAML conversions + vol.Optional(CONF_FROM): str, + vol.Optional(CONF_TO): str, + vol.Optional(CONF_FOR): vol.Any( + vol.All(cv.time_period, cv.positive_timedelta), + cv.template, + cv.template_complex, + ), + } + ), + cv.key_dependency(CONF_FOR, CONF_TO), +) async def async_trigger(hass, config, action, automation_info): @@ -35,29 +41,39 @@ async def async_trigger(hass, config, action, automation_info): to_state = config.get(CONF_TO, MATCH_ALL) time_delta = config.get(CONF_FOR) template.attach(hass, time_delta) - match_all = (from_state == MATCH_ALL and to_state == MATCH_ALL) + match_all = from_state == MATCH_ALL and to_state == MATCH_ALL unsub_track_same = {} period = {} @callback def state_automation_listener(entity, from_s, to_s): """Listen for state changes and calls action.""" + @callback def call_action(): """Call action with right context.""" - hass.async_run_job(action({ - 'trigger': { - 'platform': 'state', - 'entity_id': entity, - 'from_state': from_s, - 'to_state': to_s, - 'for': time_delta if not time_delta else period[entity] - } - }, context=to_s.context)) + hass.async_run_job( + action( + { + "trigger": { + "platform": "state", + "entity_id": entity, + "from_state": from_s, + "to_state": to_s, + "for": time_delta if not time_delta else period[entity], + } + }, + context=to_s.context, + ) + ) # Ignore changes to state attributes if from/to is in use - if (not match_all and from_s is not None and to_s is not None and - from_s.state == to_s.state): + if ( + not match_all + and from_s is not None + and to_s is not None + and from_s.state == to_s.state + ): return if not time_delta: @@ -65,42 +81,44 @@ async def async_trigger(hass, config, action, automation_info): return variables = { - 'trigger': { - 'platform': 'state', - 'entity_id': entity, - 'from_state': from_s, - 'to_state': to_s, + "trigger": { + "platform": "state", + "entity_id": entity, + "from_state": from_s, + "to_state": to_s, } } try: if isinstance(time_delta, template.Template): - period[entity] = vol.All( - cv.time_period, - cv.positive_timedelta)( - time_delta.async_render(variables)) + period[entity] = vol.All(cv.time_period, cv.positive_timedelta)( + time_delta.async_render(variables) + ) elif isinstance(time_delta, dict): time_delta_data = {} - time_delta_data.update( - template.render_complex(time_delta, variables)) - period[entity] = vol.All( - cv.time_period, - cv.positive_timedelta)( - time_delta_data) + time_delta_data.update(template.render_complex(time_delta, variables)) + period[entity] = vol.All(cv.time_period, cv.positive_timedelta)( + time_delta_data + ) else: period[entity] = time_delta except (exceptions.TemplateError, vol.Invalid) as ex: - _LOGGER.error("Error rendering '%s' for template: %s", - automation_info['name'], ex) + _LOGGER.error( + "Error rendering '%s' for template: %s", automation_info["name"], ex + ) return unsub_track_same[entity] = async_track_same_state( - hass, period[entity], call_action, + hass, + period[entity], + call_action, lambda _, _2, to_state: to_state.state == to_s.state, - entity_ids=entity) + entity_ids=entity, + ) unsub = async_track_state_change( - hass, entity_id, state_automation_listener, from_state, to_state) + hass, entity_id, state_automation_listener, from_state, to_state + ) @callback def async_remove(): diff --git a/homeassistant/components/automation/sun.py b/homeassistant/components/automation/sun.py index 07fbf716e1c..e4d41830e0f 100644 --- a/homeassistant/components/automation/sun.py +++ b/homeassistant/components/automation/sun.py @@ -6,17 +6,23 @@ import voluptuous as vol from homeassistant.core import callback from homeassistant.const import ( - CONF_EVENT, CONF_OFFSET, CONF_PLATFORM, SUN_EVENT_SUNRISE) + CONF_EVENT, + CONF_OFFSET, + CONF_PLATFORM, + SUN_EVENT_SUNRISE, +) from homeassistant.helpers.event import async_track_sunrise, async_track_sunset import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -TRIGGER_SCHEMA = vol.Schema({ - vol.Required(CONF_PLATFORM): 'sun', - vol.Required(CONF_EVENT): cv.sun_event, - vol.Required(CONF_OFFSET, default=timedelta(0)): cv.time_period, -}) +TRIGGER_SCHEMA = vol.Schema( + { + vol.Required(CONF_PLATFORM): "sun", + vol.Required(CONF_EVENT): cv.sun_event, + vol.Required(CONF_OFFSET, default=timedelta(0)): cv.time_period, + } +) async def async_trigger(hass, config, action, automation_info): @@ -27,13 +33,9 @@ async def async_trigger(hass, config, action, automation_info): @callback def call_action(): """Call action with right context.""" - hass.async_run_job(action, { - 'trigger': { - 'platform': 'sun', - 'event': event, - 'offset': offset, - }, - }) + hass.async_run_job( + action, {"trigger": {"platform": "sun", "event": event, "offset": offset}} + ) if event == SUN_EVENT_SUNRISE: return async_track_sunrise(hass, call_action, offset) diff --git a/homeassistant/components/automation/template.py b/homeassistant/components/automation/template.py index 6a60c855781..a48f252312b 100644 --- a/homeassistant/components/automation/template.py +++ b/homeassistant/components/automation/template.py @@ -7,19 +7,22 @@ from homeassistant.core import callback from homeassistant.const import CONF_VALUE_TEMPLATE, CONF_PLATFORM, CONF_FOR from homeassistant import exceptions from homeassistant.helpers import condition -from homeassistant.helpers.event import ( - async_track_same_state, async_track_template) +from homeassistant.helpers.event import async_track_same_state, async_track_template from homeassistant.helpers import config_validation as cv, template _LOGGER = logging.getLogger(__name__) -TRIGGER_SCHEMA = IF_ACTION_SCHEMA = vol.Schema({ - vol.Required(CONF_PLATFORM): 'template', - vol.Required(CONF_VALUE_TEMPLATE): cv.template, - vol.Optional(CONF_FOR): vol.Any( - vol.All(cv.time_period, cv.positive_timedelta), - cv.template, cv.template_complex), -}) +TRIGGER_SCHEMA = IF_ACTION_SCHEMA = vol.Schema( + { + vol.Required(CONF_PLATFORM): "template", + vol.Required(CONF_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_FOR): vol.Any( + vol.All(cv.time_period, cv.positive_timedelta), + cv.template, + cv.template_complex, + ), + } +) async def async_trigger(hass, config, action, automation_info): @@ -38,57 +41,60 @@ async def async_trigger(hass, config, action, automation_info): @callback def call_action(): """Call action with right context.""" - hass.async_run_job(action({ - 'trigger': { - 'platform': 'template', - 'entity_id': entity_id, - 'from_state': from_s, - 'to_state': to_s, - 'for': time_delta if not time_delta else period - }, - }, context=(to_s.context if to_s else None))) + hass.async_run_job( + action( + { + "trigger": { + "platform": "template", + "entity_id": entity_id, + "from_state": from_s, + "to_state": to_s, + "for": time_delta if not time_delta else period, + } + }, + context=(to_s.context if to_s else None), + ) + ) if not time_delta: call_action() return variables = { - 'trigger': { - 'platform': 'template', - 'entity_id': entity_id, - 'from_state': from_s, - 'to_state': to_s, - }, + "trigger": { + "platform": "template", + "entity_id": entity_id, + "from_state": from_s, + "to_state": to_s, + } } try: if isinstance(time_delta, template.Template): - period = vol.All( - cv.time_period, - cv.positive_timedelta)( - time_delta.async_render(variables)) + period = vol.All(cv.time_period, cv.positive_timedelta)( + time_delta.async_render(variables) + ) elif isinstance(time_delta, dict): time_delta_data = {} - time_delta_data.update( - template.render_complex(time_delta, variables)) - period = vol.All( - cv.time_period, - cv.positive_timedelta)( - time_delta_data) + time_delta_data.update(template.render_complex(time_delta, variables)) + period = vol.All(cv.time_period, cv.positive_timedelta)(time_delta_data) else: period = time_delta except (exceptions.TemplateError, vol.Invalid) as ex: - _LOGGER.error("Error rendering '%s' for template: %s", - automation_info['name'], ex) + _LOGGER.error( + "Error rendering '%s' for template: %s", automation_info["name"], ex + ) return unsub_track_same = async_track_same_state( - hass, period, call_action, + hass, + period, + call_action, lambda _, _2, _3: condition.async_template(hass, value_template), - value_template.extract_entities()) + value_template.extract_entities(), + ) - unsub = async_track_template( - hass, value_template, template_listener) + unsub = async_track_template(hass, value_template, template_listener) @callback def async_remove(): diff --git a/homeassistant/components/automation/time.py b/homeassistant/components/automation/time.py index ce6d6eb4446..958c1f007bc 100644 --- a/homeassistant/components/automation/time.py +++ b/homeassistant/components/automation/time.py @@ -10,10 +10,9 @@ from homeassistant.helpers.event import async_track_time_change _LOGGER = logging.getLogger(__name__) -TRIGGER_SCHEMA = vol.Schema({ - vol.Required(CONF_PLATFORM): 'time', - vol.Required(CONF_AT): cv.time, -}) +TRIGGER_SCHEMA = vol.Schema( + {vol.Required(CONF_PLATFORM): "time", vol.Required(CONF_AT): cv.time} +) async def async_trigger(hass, config, action, automation_info): @@ -24,12 +23,8 @@ async def async_trigger(hass, config, action, automation_info): @callback def time_automation_listener(now): """Listen for time changes and calls action.""" - hass.async_run_job(action, { - 'trigger': { - 'platform': 'time', - 'now': now, - }, - }) + hass.async_run_job(action, {"trigger": {"platform": "time", "now": now}}) - return async_track_time_change(hass, time_automation_listener, - hour=hours, minute=minutes, second=seconds) + return async_track_time_change( + hass, time_automation_listener, hour=hours, minute=minutes, second=seconds + ) diff --git a/homeassistant/components/automation/time_pattern.py b/homeassistant/components/automation/time_pattern.py index da8bc9f8629..15180e07094 100644 --- a/homeassistant/components/automation/time_pattern.py +++ b/homeassistant/components/automation/time_pattern.py @@ -8,18 +8,23 @@ from homeassistant.const import CONF_PLATFORM from homeassistant.helpers import config_validation as cv from homeassistant.helpers.event import async_track_time_change -CONF_HOURS = 'hours' -CONF_MINUTES = 'minutes' -CONF_SECONDS = 'seconds' +CONF_HOURS = "hours" +CONF_MINUTES = "minutes" +CONF_SECONDS = "seconds" _LOGGER = logging.getLogger(__name__) -TRIGGER_SCHEMA = vol.All(vol.Schema({ - vol.Required(CONF_PLATFORM): 'time_pattern', - CONF_HOURS: vol.Any(vol.Coerce(int), vol.Coerce(str)), - CONF_MINUTES: vol.Any(vol.Coerce(int), vol.Coerce(str)), - CONF_SECONDS: vol.Any(vol.Coerce(int), vol.Coerce(str)), -}), cv.has_at_least_one_key(CONF_HOURS, CONF_MINUTES, CONF_SECONDS)) +TRIGGER_SCHEMA = vol.All( + vol.Schema( + { + vol.Required(CONF_PLATFORM): "time_pattern", + CONF_HOURS: vol.Any(vol.Coerce(int), vol.Coerce(str)), + CONF_MINUTES: vol.Any(vol.Coerce(int), vol.Coerce(str)), + CONF_SECONDS: vol.Any(vol.Coerce(int), vol.Coerce(str)), + } + ), + cv.has_at_least_one_key(CONF_HOURS, CONF_MINUTES, CONF_SECONDS), +) async def async_trigger(hass, config, action, automation_info): @@ -37,12 +42,10 @@ async def async_trigger(hass, config, action, automation_info): @callback def time_automation_listener(now): """Listen for time changes and calls action.""" - hass.async_run_job(action, { - 'trigger': { - 'platform': 'time_pattern', - 'now': now, - }, - }) + hass.async_run_job( + action, {"trigger": {"platform": "time_pattern", "now": now}} + ) - return async_track_time_change(hass, time_automation_listener, - hour=hours, minute=minutes, second=seconds) + return async_track_time_change( + hass, time_automation_listener, hour=hours, minute=minutes, second=seconds + ) diff --git a/homeassistant/components/automation/webhook.py b/homeassistant/components/automation/webhook.py index 37cab3cb8c0..ceb764cea96 100644 --- a/homeassistant/components/automation/webhook.py +++ b/homeassistant/components/automation/webhook.py @@ -11,38 +11,37 @@ import homeassistant.helpers.config_validation as cv from . import DOMAIN as AUTOMATION_DOMAIN -DEPENDENCIES = ('webhook',) +DEPENDENCIES = ("webhook",) _LOGGER = logging.getLogger(__name__) -TRIGGER_SCHEMA = vol.Schema({ - vol.Required(CONF_PLATFORM): 'webhook', - vol.Required(CONF_WEBHOOK_ID): cv.string, -}) +TRIGGER_SCHEMA = vol.Schema( + {vol.Required(CONF_PLATFORM): "webhook", vol.Required(CONF_WEBHOOK_ID): cv.string} +) async def _handle_webhook(action, hass, webhook_id, request): """Handle incoming webhook.""" - result = { - 'platform': 'webhook', - 'webhook_id': webhook_id, - } + result = {"platform": "webhook", "webhook_id": webhook_id} - if 'json' in request.headers.get(hdrs.CONTENT_TYPE, ''): - result['json'] = await request.json() + if "json" in request.headers.get(hdrs.CONTENT_TYPE, ""): + result["json"] = await request.json() else: - result['data'] = await request.post() + result["data"] = await request.post() - result['query'] = request.query - hass.async_run_job(action, {'trigger': result}) + result["query"] = request.query + hass.async_run_job(action, {"trigger": result}) async def async_trigger(hass, config, action, automation_info): """Trigger based on incoming webhooks.""" webhook_id = config.get(CONF_WEBHOOK_ID) hass.components.webhook.async_register( - AUTOMATION_DOMAIN, automation_info['name'], - webhook_id, partial(_handle_webhook, action)) + AUTOMATION_DOMAIN, + automation_info["name"], + webhook_id, + partial(_handle_webhook, action), + ) @callback def unregister(): diff --git a/homeassistant/components/automation/zone.py b/homeassistant/components/automation/zone.py index e2d79eede8d..1f0f558f0de 100644 --- a/homeassistant/components/automation/zone.py +++ b/homeassistant/components/automation/zone.py @@ -3,22 +3,29 @@ import voluptuous as vol from homeassistant.core import callback from homeassistant.const import ( - CONF_EVENT, CONF_ENTITY_ID, CONF_ZONE, MATCH_ALL, CONF_PLATFORM) + CONF_EVENT, + CONF_ENTITY_ID, + CONF_ZONE, + MATCH_ALL, + CONF_PLATFORM, +) from homeassistant.helpers.event import async_track_state_change -from homeassistant.helpers import ( - condition, config_validation as cv, location) +from homeassistant.helpers import condition, config_validation as cv, location -EVENT_ENTER = 'enter' -EVENT_LEAVE = 'leave' +EVENT_ENTER = "enter" +EVENT_LEAVE = "leave" DEFAULT_EVENT = EVENT_ENTER -TRIGGER_SCHEMA = vol.Schema({ - vol.Required(CONF_PLATFORM): 'zone', - vol.Required(CONF_ENTITY_ID): cv.entity_ids, - vol.Required(CONF_ZONE): cv.entity_id, - vol.Required(CONF_EVENT, default=DEFAULT_EVENT): - vol.Any(EVENT_ENTER, EVENT_LEAVE), -}) +TRIGGER_SCHEMA = vol.Schema( + { + vol.Required(CONF_PLATFORM): "zone", + vol.Required(CONF_ENTITY_ID): cv.entity_ids, + vol.Required(CONF_ZONE): cv.entity_id, + vol.Required(CONF_EVENT, default=DEFAULT_EVENT): vol.Any( + EVENT_ENTER, EVENT_LEAVE + ), + } +) async def async_trigger(hass, config, action, automation_info): @@ -30,8 +37,11 @@ async def async_trigger(hass, config, action, automation_info): @callback def zone_automation_listener(entity, from_s, to_s): """Listen for state changes and calls action.""" - if from_s and not location.has_location(from_s) or \ - not location.has_location(to_s): + if ( + from_s + and not location.has_location(from_s) + or not location.has_location(to_s) + ): return zone_state = hass.states.get(zone_entity_id) @@ -42,18 +52,30 @@ async def async_trigger(hass, config, action, automation_info): to_match = condition.zone(hass, zone_state, to_s) # pylint: disable=too-many-boolean-expressions - if event == EVENT_ENTER and not from_match and to_match or \ - event == EVENT_LEAVE and from_match and not to_match: - hass.async_run_job(action({ - 'trigger': { - 'platform': 'zone', - 'entity_id': entity, - 'from_state': from_s, - 'to_state': to_s, - 'zone': zone_state, - 'event': event, - }, - }, context=to_s.context)) + if ( + event == EVENT_ENTER + and not from_match + and to_match + or event == EVENT_LEAVE + and from_match + and not to_match + ): + hass.async_run_job( + action( + { + "trigger": { + "platform": "zone", + "entity_id": entity, + "from_state": from_s, + "to_state": to_s, + "zone": zone_state, + "event": event, + } + }, + context=to_s.context, + ) + ) - return async_track_state_change(hass, entity_id, zone_automation_listener, - MATCH_ALL, MATCH_ALL) + return async_track_state_change( + hass, entity_id, zone_automation_listener, MATCH_ALL, MATCH_ALL + ) diff --git a/homeassistant/components/avea/light.py b/homeassistant/components/avea/light.py index 35c18dabd54..e6ceedcf96d 100644 --- a/homeassistant/components/avea/light.py +++ b/homeassistant/components/avea/light.py @@ -3,15 +3,19 @@ import logging import avea from homeassistant.components.light import ( - ATTR_BRIGHTNESS, ATTR_HS_COLOR, SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, Light) + ATTR_BRIGHTNESS, + ATTR_HS_COLOR, + SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, + Light, +) from homeassistant.exceptions import PlatformNotReady import homeassistant.util.color as color_util _LOGGER = logging.getLogger(__name__) -SUPPORT_AVEA = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR) +SUPPORT_AVEA = SUPPORT_BRIGHTNESS | SUPPORT_COLOR def setup_platform(hass, config, add_entities, discovery_info=None): diff --git a/homeassistant/components/avion/light.py b/homeassistant/components/avion/light.py index b138b8bf61f..0c95b2bf736 100644 --- a/homeassistant/components/avion/light.py +++ b/homeassistant/components/avion/light.py @@ -6,38 +6,50 @@ import time import voluptuous as vol from homeassistant.components.light import ( - ATTR_BRIGHTNESS, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, Light) + ATTR_BRIGHTNESS, + PLATFORM_SCHEMA, + SUPPORT_BRIGHTNESS, + Light, +) from homeassistant.const import ( - CONF_API_KEY, CONF_DEVICES, CONF_ID, CONF_NAME, CONF_PASSWORD, - CONF_USERNAME) + CONF_API_KEY, + CONF_DEVICES, + CONF_ID, + CONF_NAME, + CONF_PASSWORD, + CONF_USERNAME, +) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) SUPPORT_AVION_LED = SUPPORT_BRIGHTNESS -DEVICE_SCHEMA = vol.Schema({ - vol.Required(CONF_API_KEY): cv.string, - vol.Optional(CONF_ID): cv.positive_int, - vol.Optional(CONF_NAME): cv.string, -}) +DEVICE_SCHEMA = vol.Schema( + { + vol.Required(CONF_API_KEY): cv.string, + vol.Optional(CONF_ID): cv.positive_int, + vol.Optional(CONF_NAME): cv.string, + } +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_DEVICES, default={}): {cv.string: DEVICE_SCHEMA}, - vol.Optional(CONF_USERNAME): cv.string, - vol.Optional(CONF_PASSWORD): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_DEVICES, default={}): {cv.string: DEVICE_SCHEMA}, + vol.Optional(CONF_USERNAME): cv.string, + vol.Optional(CONF_PASSWORD): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up an Avion switch.""" # pylint: disable=no-member - avion = importlib.import_module('avion') + avion = importlib.import_module("avion") lights = [] if CONF_USERNAME in config and CONF_PASSWORD in config: - devices = avion.get_devices( - config[CONF_USERNAME], config[CONF_PASSWORD]) + devices = avion.get_devices(config[CONF_USERNAME], config[CONF_PASSWORD]) for device in devices: lights.append(AvionLight(device)) @@ -47,7 +59,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): passphrase=device_config[CONF_API_KEY], name=device_config.get(CONF_NAME), object_id=device_config.get(CONF_ID), - connect=False) + connect=False, + ) lights.append(AvionLight(device)) add_entities(lights) @@ -102,7 +115,7 @@ class AvionLight(Light): def set_state(self, brightness): """Set the state of this lamp to the provided brightness.""" # pylint: disable=no-member - avion = importlib.import_module('avion') + avion = importlib.import_module("avion") # Bluetooth LE is unreliable, and the connection may drop at any # time. Make an effort to re-establish the link. diff --git a/homeassistant/components/awair/sensor.py b/homeassistant/components/awair/sensor.py index 71b74c7971e..85b5a0be191 100644 --- a/homeassistant/components/awair/sensor.py +++ b/homeassistant/components/awair/sensor.py @@ -7,8 +7,12 @@ import math import voluptuous as vol from homeassistant.const import ( - CONF_ACCESS_TOKEN, CONF_DEVICES, DEVICE_CLASS_HUMIDITY, - DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS) + CONF_ACCESS_TOKEN, + CONF_DEVICES, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + TEMP_CELSIUS, +) from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv @@ -17,48 +21,64 @@ from homeassistant.util import Throttle, dt _LOGGER = logging.getLogger(__name__) -ATTR_SCORE = 'score' -ATTR_TIMESTAMP = 'timestamp' -ATTR_LAST_API_UPDATE = 'last_api_update' -ATTR_COMPONENT = 'component' -ATTR_VALUE = 'value' -ATTR_SENSORS = 'sensors' +ATTR_SCORE = "score" +ATTR_TIMESTAMP = "timestamp" +ATTR_LAST_API_UPDATE = "last_api_update" +ATTR_COMPONENT = "component" +ATTR_VALUE = "value" +ATTR_SENSORS = "sensors" -CONF_UUID = 'uuid' +CONF_UUID = "uuid" -DEVICE_CLASS_PM2_5 = 'PM2.5' -DEVICE_CLASS_PM10 = 'PM10' -DEVICE_CLASS_CARBON_DIOXIDE = 'CO2' -DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS = 'VOC' -DEVICE_CLASS_SCORE = 'score' +DEVICE_CLASS_PM2_5 = "PM2.5" +DEVICE_CLASS_PM10 = "PM10" +DEVICE_CLASS_CARBON_DIOXIDE = "CO2" +DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS = "VOC" +DEVICE_CLASS_SCORE = "score" SENSOR_TYPES = { - 'TEMP': {'device_class': DEVICE_CLASS_TEMPERATURE, - 'unit_of_measurement': TEMP_CELSIUS, - 'icon': 'mdi:thermometer'}, - 'HUMID': {'device_class': DEVICE_CLASS_HUMIDITY, - 'unit_of_measurement': '%', - 'icon': 'mdi:water-percent'}, - 'CO2': {'device_class': DEVICE_CLASS_CARBON_DIOXIDE, - 'unit_of_measurement': 'ppm', - 'icon': 'mdi:periodic-table-co2'}, - 'VOC': {'device_class': DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, - 'unit_of_measurement': 'ppb', - 'icon': 'mdi:cloud'}, + "TEMP": { + "device_class": DEVICE_CLASS_TEMPERATURE, + "unit_of_measurement": TEMP_CELSIUS, + "icon": "mdi:thermometer", + }, + "HUMID": { + "device_class": DEVICE_CLASS_HUMIDITY, + "unit_of_measurement": "%", + "icon": "mdi:water-percent", + }, + "CO2": { + "device_class": DEVICE_CLASS_CARBON_DIOXIDE, + "unit_of_measurement": "ppm", + "icon": "mdi:periodic-table-co2", + }, + "VOC": { + "device_class": DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, + "unit_of_measurement": "ppb", + "icon": "mdi:cloud", + }, # Awair docs don't actually specify the size they measure for 'dust', # but 2.5 allows the sensor to show up in HomeKit - 'DUST': {'device_class': DEVICE_CLASS_PM2_5, - 'unit_of_measurement': 'µg/m3', - 'icon': 'mdi:cloud'}, - 'PM25': {'device_class': DEVICE_CLASS_PM2_5, - 'unit_of_measurement': 'µg/m3', - 'icon': 'mdi:cloud'}, - 'PM10': {'device_class': DEVICE_CLASS_PM10, - 'unit_of_measurement': 'µg/m3', - 'icon': 'mdi:cloud'}, - 'score': {'device_class': DEVICE_CLASS_SCORE, - 'unit_of_measurement': '%', - 'icon': 'mdi:percent'}, + "DUST": { + "device_class": DEVICE_CLASS_PM2_5, + "unit_of_measurement": "µg/m3", + "icon": "mdi:cloud", + }, + "PM25": { + "device_class": DEVICE_CLASS_PM2_5, + "unit_of_measurement": "µg/m3", + "icon": "mdi:cloud", + }, + "PM10": { + "device_class": DEVICE_CLASS_PM10, + "unit_of_measurement": "µg/m3", + "icon": "mdi:cloud", + }, + "score": { + "device_class": DEVICE_CLASS_SCORE, + "unit_of_measurement": "%", + "icon": "mdi:percent", + }, } AWAIR_QUOTA = 300 @@ -67,15 +87,14 @@ AWAIR_QUOTA = 300 # Don't bother asking us for state more often than that. SCAN_INTERVAL = timedelta(minutes=5) -AWAIR_DEVICE_SCHEMA = vol.Schema({ - vol.Required(CONF_UUID): cv.string, -}) +AWAIR_DEVICE_SCHEMA = vol.Schema({vol.Required(CONF_UUID): cv.string}) -PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({ - vol.Required(CONF_ACCESS_TOKEN): cv.string, - vol.Optional(CONF_DEVICES): vol.All( - cv.ensure_list, [AWAIR_DEVICE_SCHEMA]), -}) +PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_ACCESS_TOKEN): cv.string, + vol.Optional(CONF_DEVICES): vol.All(cv.ensure_list, [AWAIR_DEVICE_SCHEMA]), + } +) # Awair *heavily* throttles calls that get user information, @@ -84,8 +103,7 @@ PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({ # list of devices, and they may provide the same set of information # that the devices() call would return. However, the only thing # used at this time is the `uuid` value. -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Connect to the Awair API and find devices.""" from python_awair import AwairClient @@ -106,8 +124,7 @@ async def async_setup_platform(hass, config, async_add_entities, await awair_data.async_update() for sensor in SENSOR_TYPES: if sensor in awair_data.data: - awair_sensor = AwairSensor(awair_data, device, - sensor, throttle) + awair_sensor = AwairSensor(awair_data, device, sensor, throttle) all_devices.append(awair_sensor) async_add_entities(all_devices, True) @@ -116,8 +133,11 @@ async def async_setup_platform(hass, config, async_add_entities, _LOGGER.error("Awair API access_token invalid") except AwairClient.RatelimitError: _LOGGER.error("Awair API ratelimit exceeded.") - except (AwairClient.QueryError, AwairClient.NotFoundError, - AwairClient.GenericError) as error: + except ( + AwairClient.QueryError, + AwairClient.NotFoundError, + AwairClient.GenericError, + ) as error: _LOGGER.error("Unexpected Awair API error: %s", error) raise PlatformNotReady @@ -129,9 +149,9 @@ class AwairSensor(Entity): def __init__(self, data, device, sensor_type, throttle): """Initialize the sensor.""" self._uuid = device[CONF_UUID] - self._device_class = SENSOR_TYPES[sensor_type]['device_class'] - self._name = 'Awair {}'.format(self._device_class) - unit = SENSOR_TYPES[sensor_type]['unit_of_measurement'] + self._device_class = SENSOR_TYPES[sensor_type]["device_class"] + self._name = "Awair {}".format(self._device_class) + unit = SENSOR_TYPES[sensor_type]["unit_of_measurement"] self._unit_of_measurement = unit self._data = data self._type = sensor_type @@ -150,7 +170,7 @@ class AwairSensor(Entity): @property def icon(self): """Icon to use in the frontend.""" - return SENSOR_TYPES[self._type]['icon'] + return SENSOR_TYPES[self._type]["icon"] @property def state(self): diff --git a/homeassistant/components/aws/__init__.py b/homeassistant/components/aws/__init__.py index 5b9978fb3e6..1959cc05e80 100644 --- a/homeassistant/components/aws/__init__.py +++ b/homeassistant/components/aws/__init__.py @@ -39,11 +39,9 @@ AWS_CREDENTIAL_SCHEMA = vol.Schema( } ) -DEFAULT_CREDENTIAL = [{ - CONF_NAME: "default", - CONF_PROFILE_NAME: "default", - CONF_VALIDATE: False, -}] +DEFAULT_CREDENTIAL = [ + {CONF_NAME: "default", CONF_PROFILE_NAME: "default", CONF_VALIDATE: False} +] SUPPORTED_SERVICES = ["lambda", "sns", "sqs"] @@ -66,9 +64,9 @@ CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.Schema( { - vol.Optional( - CONF_CREDENTIALS, default=DEFAULT_CREDENTIAL - ): vol.All(cv.ensure_list, [AWS_CREDENTIAL_SCHEMA]), + vol.Optional(CONF_CREDENTIALS, default=DEFAULT_CREDENTIAL): vol.All( + cv.ensure_list, [AWS_CREDENTIAL_SCHEMA] + ), vol.Optional(CONF_NOTIFY, default=[]): vol.All( cv.ensure_list, [NOTIFY_PLATFORM_SCHEMA] ), @@ -111,9 +109,7 @@ async def async_setup_entry(hass, entry): if entry.source == config_entries.SOURCE_IMPORT: if conf is None: # user removed config from configuration.yaml, abort setup - hass.async_create_task( - hass.config_entries.async_remove(entry.entry_id) - ) + hass.async_create_task(hass.config_entries.async_remove(entry.entry_id)) return False if conf != entry.data: @@ -147,9 +143,7 @@ async def async_setup_entry(hass, entry): # have to use discovery to load platform. for notify_config in conf[CONF_NOTIFY]: hass.async_create_task( - discovery.async_load_platform( - hass, "notify", DOMAIN, notify_config, config - ) + discovery.async_load_platform(hass, "notify", DOMAIN, notify_config, config) ) return validation diff --git a/homeassistant/components/aws/config_flow.py b/homeassistant/components/aws/config_flow.py index c21f2a94137..6ac332b251c 100644 --- a/homeassistant/components/aws/config_flow.py +++ b/homeassistant/components/aws/config_flow.py @@ -17,6 +17,4 @@ class AWSFlowHandler(config_entries.ConfigFlow): if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") - return self.async_create_entry( - title="configuration.yaml", data=user_input - ) + return self.async_create_entry(title="configuration.yaml", data=user_input) diff --git a/homeassistant/components/aws/const.py b/homeassistant/components/aws/const.py index 4738547bdec..499f4413596 100644 --- a/homeassistant/components/aws/const.py +++ b/homeassistant/components/aws/const.py @@ -8,7 +8,7 @@ DATA_SESSIONS = "aws_sessions" CONF_ACCESS_KEY_ID = "aws_access_key_id" CONF_CONTEXT = "context" CONF_CREDENTIAL_NAME = "credential_name" -CONF_CREDENTIALS = 'credentials' +CONF_CREDENTIALS = "credentials" CONF_NOTIFY = "notify" CONF_PROFILE_NAME = "profile_name" CONF_REGION = "region_name" diff --git a/homeassistant/components/aws/notify.py b/homeassistant/components/aws/notify.py index 4b71ae425cb..fa1cf3fa363 100644 --- a/homeassistant/components/aws/notify.py +++ b/homeassistant/components/aws/notify.py @@ -32,15 +32,13 @@ async def get_available_regions(hass, service): # get_available_regions is not a coroutine since it does not perform # network I/O. But it still perform file I/O heavily, so put it into # an executor thread to unblock event loop - return await hass.async_add_executor_job( - session.get_available_regions, service - ) + return await hass.async_add_executor_job(session.get_available_regions, service) async def async_get_service(hass, config, discovery_info=None): """Get the AWS notification service.""" if discovery_info is None: - _LOGGER.error('Please config aws notify platform in aws component') + _LOGGER.error("Please config aws notify platform in aws component") return None import aiobotocore @@ -56,7 +54,9 @@ async def async_get_service(hass, config, discovery_info=None): if region_name not in available_regions: _LOGGER.error( "Region %s is not available for %s service, must in %s", - region_name, service, available_regions + region_name, + service, + available_regions, ) return None @@ -76,9 +76,7 @@ async def async_get_service(hass, config, discovery_info=None): if hass.data[DATA_SESSIONS]: session = next(iter(hass.data[DATA_SESSIONS].values())) else: - _LOGGER.error( - "Missing aws credential for %s", config[CONF_NAME] - ) + _LOGGER.error("Missing aws credential for %s", config[CONF_NAME]) return None if session is None: @@ -86,9 +84,7 @@ async def async_get_service(hass, config, discovery_info=None): if credential_name is not None: session = hass.data[DATA_SESSIONS].get(credential_name) if session is None: - _LOGGER.warning( - "No available aws session for %s", credential_name - ) + _LOGGER.warning("No available aws session for %s", credential_name) del aws_config[CONF_CREDENTIAL_NAME] if session is None: @@ -150,7 +146,7 @@ class AWSLambda(AWSNotify): json_payload = json.dumps(payload) async with self.session.create_client( - self.service, **self.aws_config + self.service, **self.aws_config ) as client: tasks = [] for target in kwargs.get(ATTR_TARGET, []): @@ -185,7 +181,7 @@ class AWSSNS(AWSNotify): subject = kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT) async with self.session.create_client( - self.service, **self.aws_config + self.service, **self.aws_config ) as client: tasks = [] for target in kwargs.get(ATTR_TARGET, []): @@ -225,7 +221,7 @@ class AWSSQS(AWSNotify): } async with self.session.create_client( - self.service, **self.aws_config + self.service, **self.aws_config ) as client: tasks = [] for target in kwargs.get(ATTR_TARGET, []): diff --git a/homeassistant/components/axis/__init__.py b/homeassistant/components/axis/__init__.py index e9e8a158a3b..bdda82b2145 100644 --- a/homeassistant/components/axis/__init__.py +++ b/homeassistant/components/axis/__init__.py @@ -4,17 +4,21 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.const import ( - CONF_DEVICE, CONF_MAC, CONF_NAME, CONF_TRIGGER_TIME, - EVENT_HOMEASSISTANT_STOP) + CONF_DEVICE, + CONF_MAC, + CONF_NAME, + CONF_TRIGGER_TIME, + EVENT_HOMEASSISTANT_STOP, +) from homeassistant.helpers import config_validation as cv from .config_flow import DEVICE_SCHEMA from .const import CONF_CAMERA, CONF_EVENTS, DEFAULT_TRIGGER_TIME, DOMAIN from .device import AxisNetworkDevice, get_device -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: cv.schema_with_slug_keys(DEVICE_SCHEMA), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + {DOMAIN: cv.schema_with_slug_keys(DEVICE_SCHEMA)}, extra=vol.ALLOW_EXTRA +) async def async_setup(hass, config): @@ -26,10 +30,13 @@ async def async_setup(hass, config): if CONF_NAME not in device_config: device_config[CONF_NAME] = device_name - hass.async_create_task(hass.config_entries.flow.async_init( - DOMAIN, context={'source': config_entries.SOURCE_IMPORT}, - data=device_config - )) + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=device_config, + ) + ) return True @@ -72,7 +79,7 @@ async def async_populate_options(hass, config_entry): options = { CONF_CAMERA: camera, CONF_EVENTS: True, - CONF_TRIGGER_TIME: DEFAULT_TRIGGER_TIME + CONF_TRIGGER_TIME: DEFAULT_TRIGGER_TIME, } hass.config_entries.async_update_entry(config_entry, options=options) diff --git a/homeassistant/components/axis/axis_base.py b/homeassistant/components/axis/axis_base.py index 9a8f53c8bde..3864ac344e1 100644 --- a/homeassistant/components/axis/axis_base.py +++ b/homeassistant/components/axis/axis_base.py @@ -17,8 +17,11 @@ class AxisEntityBase(Entity): async def async_added_to_hass(self): """Subscribe device events.""" - self.unsub_dispatcher.append(async_dispatcher_connect( - self.hass, self.device.event_reachable, self.update_callback)) + self.unsub_dispatcher.append( + async_dispatcher_connect( + self.hass, self.device.event_reachable, self.update_callback + ) + ) async def async_will_remove_from_hass(self) -> None: """Unsubscribe device events when removed.""" @@ -33,9 +36,7 @@ class AxisEntityBase(Entity): @property def device_info(self): """Return a device description for device registry.""" - return { - 'identifiers': {(AXIS_DOMAIN, self.device.serial)} - } + return {"identifiers": {(AXIS_DOMAIN, self.device.serial)}} @callback def update_callback(self, no_delay=None): @@ -71,8 +72,7 @@ class AxisEventBase(AxisEntityBase): @property def name(self): """Return the name of the event.""" - return '{} {} {}'.format( - self.device.name, self.event.TYPE, self.event.id) + return "{} {} {}".format(self.device.name, self.event.TYPE, self.event.id) @property def should_poll(self): @@ -82,5 +82,4 @@ class AxisEventBase(AxisEntityBase): @property def unique_id(self): """Return a unique identifier for this device.""" - return '{}-{}-{}'.format( - self.device.serial, self.event.topic, self.event.id) + return "{}-{}-{}".format(self.device.serial, self.event.topic, self.event.id) diff --git a/homeassistant/components/axis/binary_sensor.py b/homeassistant/components/axis/binary_sensor.py index 86a2a738b70..1d12e0b8d61 100644 --- a/homeassistant/components/axis/binary_sensor.py +++ b/homeassistant/components/axis/binary_sensor.py @@ -28,8 +28,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): if event.CLASS != CLASS_OUTPUT: async_add_entities([AxisBinarySensor(event, device)], True) - device.listeners.append(async_dispatcher_connect( - hass, device.event_new_sensor, async_add_sensor)) + device.listeners.append( + async_dispatcher_connect(hass, device.event_new_sensor, async_add_sensor) + ) class AxisBinarySensor(AxisEventBase, BinarySensorDevice): @@ -63,8 +64,8 @@ class AxisBinarySensor(AxisEventBase, BinarySensorDevice): self.remove_timer = None self.remove_timer = async_track_point_in_utc_time( - self.hass, _delay_update, - utcnow() + timedelta(seconds=delay)) + self.hass, _delay_update, utcnow() + timedelta(seconds=delay) + ) @property def is_on(self): @@ -74,10 +75,13 @@ class AxisBinarySensor(AxisEventBase, BinarySensorDevice): @property def name(self): """Return the name of the event.""" - if self.event.CLASS == CLASS_INPUT and self.event.id and \ - self.device.api.vapix.ports[self.event.id].name: - return '{} {}'.format( - self.device.name, - self.device.api.vapix.ports[self.event.id].name) + if ( + self.event.CLASS == CLASS_INPUT + and self.event.id + and self.device.api.vapix.ports[self.event.id].name + ): + return "{} {}".format( + self.device.name, self.device.api.vapix.ports[self.event.id].name + ) return super().name diff --git a/homeassistant/components/axis/camera.py b/homeassistant/components/axis/camera.py index c993e9d9f64..e7e0f7459f3 100644 --- a/homeassistant/components/axis/camera.py +++ b/homeassistant/components/axis/camera.py @@ -2,18 +2,30 @@ from homeassistant.components.camera import SUPPORT_STREAM from homeassistant.components.mjpeg.camera import ( - CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, MjpegCamera, filter_urllib3_logging) + CONF_MJPEG_URL, + CONF_STILL_IMAGE_URL, + MjpegCamera, + filter_urllib3_logging, +) from homeassistant.const import ( - CONF_AUTHENTICATION, CONF_DEVICE, CONF_HOST, CONF_MAC, CONF_NAME, - CONF_PASSWORD, CONF_PORT, CONF_USERNAME, HTTP_DIGEST_AUTHENTICATION) + CONF_AUTHENTICATION, + CONF_DEVICE, + CONF_HOST, + CONF_MAC, + CONF_NAME, + CONF_PASSWORD, + CONF_PORT, + CONF_USERNAME, + HTTP_DIGEST_AUTHENTICATION, +) from homeassistant.helpers.dispatcher import async_dispatcher_connect from .axis_base import AxisEntityBase from .const import DOMAIN as AXIS_DOMAIN -AXIS_IMAGE = 'http://{}:{}/axis-cgi/jpg/image.cgi' -AXIS_VIDEO = 'http://{}:{}/axis-cgi/mjpg/video.cgi' -AXIS_STREAM = 'rtsp://{}:{}@{}/axis-media/media.amp?videocodec=h264' +AXIS_IMAGE = "http://{}:{}/axis-cgi/jpg/image.cgi" +AXIS_VIDEO = "http://{}:{}/axis-cgi/mjpg/video.cgi" +AXIS_STREAM = "rtsp://{}:{}@{}/axis-media/media.amp?videocodec=h264" async def async_setup_entry(hass, config_entry, async_add_entities): @@ -29,10 +41,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities): CONF_PASSWORD: config_entry.data[CONF_DEVICE][CONF_PASSWORD], CONF_MJPEG_URL: AXIS_VIDEO.format( config_entry.data[CONF_DEVICE][CONF_HOST], - config_entry.data[CONF_DEVICE][CONF_PORT]), + config_entry.data[CONF_DEVICE][CONF_PORT], + ), CONF_STILL_IMAGE_URL: AXIS_IMAGE.format( config_entry.data[CONF_DEVICE][CONF_HOST], - config_entry.data[CONF_DEVICE][CONF_PORT]), + config_entry.data[CONF_DEVICE][CONF_PORT], + ), CONF_AUTHENTICATION: HTTP_DIGEST_AUTHENTICATION, } async_add_entities([AxisCamera(config, device)]) @@ -48,8 +62,11 @@ class AxisCamera(AxisEntityBase, MjpegCamera): async def async_added_to_hass(self): """Subscribe camera events.""" - self.unsub_dispatcher.append(async_dispatcher_connect( - self.hass, self.device.event_new_address, self._new_address)) + self.unsub_dispatcher.append( + async_dispatcher_connect( + self.hass, self.device.event_new_address, self._new_address + ) + ) await super().async_added_to_hass() @@ -63,7 +80,8 @@ class AxisCamera(AxisEntityBase, MjpegCamera): return AXIS_STREAM.format( self.device.config_entry.data[CONF_DEVICE][CONF_USERNAME], self.device.config_entry.data[CONF_DEVICE][CONF_PASSWORD], - self.device.host) + self.device.host, + ) def _new_address(self): """Set new device address for video stream.""" @@ -74,4 +92,4 @@ class AxisCamera(AxisEntityBase, MjpegCamera): @property def unique_id(self): """Return a unique identifier for this device.""" - return '{}-camera'.format(self.device.serial) + return "{}-camera".format(self.device.serial) diff --git a/homeassistant/components/axis/config_flow.py b/homeassistant/components/axis/config_flow.py index 410fb62c139..4b54982244b 100644 --- a/homeassistant/components/axis/config_flow.py +++ b/homeassistant/components/axis/config_flow.py @@ -4,8 +4,14 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.const import ( - CONF_DEVICE, CONF_HOST, CONF_MAC, CONF_NAME, CONF_PASSWORD, CONF_PORT, - CONF_USERNAME) + CONF_DEVICE, + CONF_HOST, + CONF_MAC, + CONF_NAME, + CONF_PASSWORD, + CONF_PORT, + CONF_USERNAME, +) from homeassistant.core import callback from homeassistant.helpers import config_validation as cv from homeassistant.util.json import load_json @@ -14,36 +20,40 @@ from .const import CONF_MODEL, DOMAIN from .device import get_device from .errors import AlreadyConfigured, AuthenticationRequired, CannotConnect -AXIS_OUI = {'00408C', 'ACCC8E', 'B8A44F'} +AXIS_OUI = {"00408C", "ACCC8E", "B8A44F"} -CONFIG_FILE = 'axis.conf' +CONFIG_FILE = "axis.conf" -EVENT_TYPES = ['motion', 'vmd3', 'pir', 'sound', - 'daynight', 'tampering', 'input'] +EVENT_TYPES = ["motion", "vmd3", "pir", "sound", "daynight", "tampering", "input"] -PLATFORMS = ['camera'] +PLATFORMS = ["camera"] AXIS_INCLUDE = EVENT_TYPES + PLATFORMS -AXIS_DEFAULT_HOST = '192.168.0.90' -AXIS_DEFAULT_USERNAME = 'root' -AXIS_DEFAULT_PASSWORD = 'pass' +AXIS_DEFAULT_HOST = "192.168.0.90" +AXIS_DEFAULT_USERNAME = "root" +AXIS_DEFAULT_PASSWORD = "pass" DEFAULT_PORT = 80 -DEVICE_SCHEMA = vol.Schema({ - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_HOST, default=AXIS_DEFAULT_HOST): cv.string, - vol.Optional(CONF_USERNAME, default=AXIS_DEFAULT_USERNAME): cv.string, - vol.Optional(CONF_PASSWORD, default=AXIS_DEFAULT_PASSWORD): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, -}, extra=vol.ALLOW_EXTRA) +DEVICE_SCHEMA = vol.Schema( + { + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_HOST, default=AXIS_DEFAULT_HOST): cv.string, + vol.Optional(CONF_USERNAME, default=AXIS_DEFAULT_USERNAME): cv.string, + vol.Optional(CONF_PASSWORD, default=AXIS_DEFAULT_PASSWORD): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + }, + extra=vol.ALLOW_EXTRA, +) @callback def configured_devices(hass): """Return a set of the configured devices.""" - return {entry.data[CONF_MAC]: entry for entry - in hass.config_entries.async_entries(DOMAIN)} + return { + entry.data[CONF_MAC]: entry + for entry in hass.config_entries.async_entries(DOMAIN) + } @config_entries.HANDLERS.register(DOMAIN) @@ -76,7 +86,7 @@ class AxisFlowHandler(config_entries.ConfigFlow): CONF_HOST: user_input[CONF_HOST], CONF_PORT: user_input[CONF_PORT], CONF_USERNAME: user_input[CONF_USERNAME], - CONF_PASSWORD: user_input[CONF_PASSWORD] + CONF_PASSWORD: user_input[CONF_PASSWORD], } device = await get_device(self.hass, self.device_config) @@ -90,26 +100,30 @@ class AxisFlowHandler(config_entries.ConfigFlow): return await self._create_entry() except AlreadyConfigured: - errors['base'] = 'already_configured' + errors["base"] = "already_configured" except AuthenticationRequired: - errors['base'] = 'faulty_credentials' + errors["base"] = "faulty_credentials" except CannotConnect: - errors['base'] = 'device_unavailable' + errors["base"] = "device_unavailable" - data = self.import_schema or self.discovery_schema or { - vol.Required(CONF_HOST): str, - vol.Required(CONF_USERNAME): str, - vol.Required(CONF_PASSWORD): str, - vol.Required(CONF_PORT, default=DEFAULT_PORT): int - } + data = ( + self.import_schema + or self.discovery_schema + or { + vol.Required(CONF_HOST): str, + vol.Required(CONF_USERNAME): str, + vol.Required(CONF_PASSWORD): str, + vol.Required(CONF_PORT, default=DEFAULT_PORT): int, + } + ) return self.async_show_form( - step_id='user', + step_id="user", description_placeholders=self.device_config, data_schema=vol.Schema(data), - errors=errors + errors=errors, ) async def _create_entry(self): @@ -119,8 +133,8 @@ class AxisFlowHandler(config_entries.ConfigFlow): """ if self.name is None: same_model = [ - entry.data[CONF_NAME] for entry - in self.hass.config_entries.async_entries(DOMAIN) + entry.data[CONF_NAME] + for entry in self.hass.config_entries.async_entries(DOMAIN) if entry.data[CONF_MODEL] == self.model ] @@ -138,10 +152,7 @@ class AxisFlowHandler(config_entries.ConfigFlow): } title = "{} - {}".format(self.model, self.serial_number) - return self.async_create_entry( - title=title, - data=data - ) + return self.async_create_entry(title=title, data=data) async def _update_entry(self, entry, host): """Update existing entry if it is the same device.""" @@ -153,38 +164,40 @@ class AxisFlowHandler(config_entries.ConfigFlow): This flow is triggered by the discovery component. """ - serialnumber = discovery_info['properties']['macaddress'] + serialnumber = discovery_info["properties"]["macaddress"] if serialnumber[:6] not in AXIS_OUI: - return self.async_abort(reason='not_axis_device') + return self.async_abort(reason="not_axis_device") - if discovery_info[CONF_HOST].startswith('169.254'): - return self.async_abort(reason='link_local_address') + if discovery_info[CONF_HOST].startswith("169.254"): + return self.async_abort(reason="link_local_address") # pylint: disable=unsupported-assignment-operation - self.context['macaddress'] = serialnumber + self.context["macaddress"] = serialnumber - if any(serialnumber == flow['context']['macaddress'] - for flow in self._async_in_progress()): - return self.async_abort(reason='already_in_progress') + if any( + serialnumber == flow["context"]["macaddress"] + for flow in self._async_in_progress() + ): + return self.async_abort(reason="already_in_progress") device_entries = configured_devices(self.hass) if serialnumber in device_entries: entry = device_entries[serialnumber] await self._update_entry(entry, discovery_info[CONF_HOST]) - return self.async_abort(reason='already_configured') + return self.async_abort(reason="already_configured") config_file = await self.hass.async_add_executor_job( - load_json, self.hass.config.path(CONFIG_FILE)) + load_json, self.hass.config.path(CONFIG_FILE) + ) if serialnumber not in config_file: self.discovery_schema = { - vol.Required( - CONF_HOST, default=discovery_info[CONF_HOST]): str, + vol.Required(CONF_HOST, default=discovery_info[CONF_HOST]): str, vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str, - vol.Required(CONF_PORT, default=discovery_info[CONF_PORT]): int + vol.Required(CONF_PORT, default=discovery_info[CONF_PORT]): int, } return await self.async_step_user() @@ -193,10 +206,10 @@ class AxisFlowHandler(config_entries.ConfigFlow): device_config[CONF_HOST] = discovery_info[CONF_HOST] if CONF_NAME not in device_config: - device_config[CONF_NAME] = discovery_info['hostname'] + device_config[CONF_NAME] = discovery_info["hostname"] except vol.Invalid: - return self.async_abort(reason='bad_config_file') + return self.async_abort(reason="bad_config_file") return await self.async_step_import(device_config) @@ -213,10 +226,8 @@ class AxisFlowHandler(config_entries.ConfigFlow): self.import_schema = { vol.Required(CONF_HOST, default=import_config[CONF_HOST]): str, - vol.Required( - CONF_USERNAME, default=import_config[CONF_USERNAME]): str, - vol.Required( - CONF_PASSWORD, default=import_config[CONF_PASSWORD]): str, - vol.Required(CONF_PORT, default=import_config[CONF_PORT]): int + vol.Required(CONF_USERNAME, default=import_config[CONF_USERNAME]): str, + vol.Required(CONF_PASSWORD, default=import_config[CONF_PASSWORD]): str, + vol.Required(CONF_PORT, default=import_config[CONF_PORT]): int, } return await self.async_step_user(user_input=import_config) diff --git a/homeassistant/components/axis/const.py b/homeassistant/components/axis/const.py index 5e730708591..7f0fd9c8947 100644 --- a/homeassistant/components/axis/const.py +++ b/homeassistant/components/axis/const.py @@ -3,10 +3,10 @@ import logging LOGGER = logging.getLogger(__package__) -DOMAIN = 'axis' +DOMAIN = "axis" -CONF_CAMERA = 'camera' -CONF_EVENTS = 'events' -CONF_MODEL = 'model' +CONF_CAMERA = "camera" +CONF_EVENTS = "events" +CONF_MODEL = "model" DEFAULT_TRIGGER_TIME = 0 diff --git a/homeassistant/components/axis/device.py b/homeassistant/components/axis/device.py index 32c5ac090e9..465d8c73b74 100644 --- a/homeassistant/components/axis/device.py +++ b/homeassistant/components/axis/device.py @@ -4,8 +4,14 @@ import asyncio import async_timeout from homeassistant.const import ( - CONF_DEVICE, CONF_HOST, CONF_MAC, CONF_NAME, CONF_PASSWORD, CONF_PORT, - CONF_USERNAME) + CONF_DEVICE, + CONF_HOST, + CONF_MAC, + CONF_NAME, + CONF_PASSWORD, + CONF_PORT, + CONF_USERNAME, +) from homeassistant.core import callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC @@ -53,30 +59,27 @@ class AxisNetworkDevice: async def async_update_device_registry(self): """Update device registry.""" - device_registry = await \ - self.hass.helpers.device_registry.async_get_registry() + device_registry = await self.hass.helpers.device_registry.async_get_registry() device_registry.async_get_or_create( config_entry_id=self.config_entry.entry_id, connections={(CONNECTION_NETWORK_MAC, self.serial)}, identifiers={(DOMAIN, self.serial)}, - manufacturer='Axis Communications AB', + manufacturer="Axis Communications AB", model="{} {}".format(self.model, self.product_type), name=self.name, - sw_version=self.fw_version + sw_version=self.fw_version, ) async def async_setup(self): """Set up the device.""" try: - self.api = await get_device( - self.hass, self.config_entry.data[CONF_DEVICE]) + self.api = await get_device(self.hass, self.config_entry.data[CONF_DEVICE]) except CannotConnect: raise ConfigEntryNotReady except Exception: # pylint: disable=broad-except - LOGGER.error( - 'Unknown error connecting with Axis device on %s', self.host) + LOGGER.error("Unknown error connecting with Axis device on %s", self.host) return False self.fw_version = self.api.vapix.params.firmware_version @@ -86,18 +89,22 @@ class AxisNetworkDevice: self.hass.async_create_task( self.hass.config_entries.async_forward_entry_setup( - self.config_entry, 'camera')) + self.config_entry, "camera" + ) + ) if self.config_entry.options[CONF_EVENTS]: - self.api.stream.connection_status_callback = \ + self.api.stream.connection_status_callback = ( self.async_connection_status_callback + ) self.api.enable_events(event_callback=self.async_event_callback) platform_tasks = [ self.hass.config_entries.async_forward_entry_setup( - self.config_entry, platform) - for platform in ['binary_sensor', 'switch'] + self.config_entry, platform + ) + for platform in ["binary_sensor", "switch"] ] self.hass.async_create_task(self.start(platform_tasks)) @@ -108,7 +115,7 @@ class AxisNetworkDevice: @property def event_new_address(self): """Device specific event to signal new device address.""" - return 'axis_new_address_{}'.format(self.serial) + return "axis_new_address_{}".format(self.serial) @staticmethod async def async_new_address_callback(hass, entry): @@ -124,7 +131,7 @@ class AxisNetworkDevice: @property def event_reachable(self): """Device specific event to signal a change in connection status.""" - return 'axis_reachable_{}'.format(self.serial) + return "axis_reachable_{}".format(self.serial) @callback def async_connection_status_callback(self, status): @@ -134,6 +141,7 @@ class AxisNetworkDevice: Only signal state change if state change is true. """ from axis.streammanager import SIGNAL_PLAYING + if self.available != (status == SIGNAL_PLAYING): self.available = not self.available async_dispatcher_send(self.hass, self.event_reachable, True) @@ -141,12 +149,12 @@ class AxisNetworkDevice: @property def event_new_sensor(self): """Device specific event to signal new sensor available.""" - return 'axis_add_sensor_{}'.format(self.serial) + return "axis_add_sensor_{}".format(self.serial) @callback def async_event_callback(self, action, event_id): """Call to configure events when initialized on event stream.""" - if action == 'add': + if action == "add": async_dispatcher_send(self.hass, self.event_new_sensor, event_id) async def start(self, platform_tasks): @@ -166,14 +174,17 @@ class AxisNetworkDevice: if self.config_entry.options[CONF_CAMERA]: platform_tasks.append( self.hass.config_entries.async_forward_entry_unload( - self.config_entry, 'camera')) + self.config_entry, "camera" + ) + ) if self.config_entry.options[CONF_EVENTS]: self.api.stop() platform_tasks += [ self.hass.config_entries.async_forward_entry_unload( - self.config_entry, platform) - for platform in ['binary_sensor', 'switch'] + self.config_entry, platform + ) + for platform in ["binary_sensor", "switch"] ] await asyncio.gather(*platform_tasks) @@ -190,10 +201,13 @@ async def get_device(hass, config): import axis device = axis.AxisDevice( - loop=hass.loop, host=config[CONF_HOST], + loop=hass.loop, + host=config[CONF_HOST], username=config[CONF_USERNAME], password=config[CONF_PASSWORD], - port=config[CONF_PORT], web_proto='http') + port=config[CONF_PORT], + web_proto="http", + ) device.vapix.initialize_params(preload_data=False) device.vapix.initialize_ports() @@ -202,28 +216,23 @@ async def get_device(hass, config): with async_timeout.timeout(15): await asyncio.gather( - hass.async_add_executor_job( - device.vapix.params.update_brand), - - hass.async_add_executor_job( - device.vapix.params.update_properties), - - hass.async_add_executor_job( - device.vapix.ports.update) + hass.async_add_executor_job(device.vapix.params.update_brand), + hass.async_add_executor_job(device.vapix.params.update_properties), + hass.async_add_executor_job(device.vapix.ports.update), ) return device except axis.Unauthorized: - LOGGER.warning("Connected to device at %s but not registered.", - config[CONF_HOST]) + LOGGER.warning( + "Connected to device at %s but not registered.", config[CONF_HOST] + ) raise AuthenticationRequired except (asyncio.TimeoutError, axis.RequestError): - LOGGER.error("Error connecting to the Axis device at %s", - config[CONF_HOST]) + LOGGER.error("Error connecting to the Axis device at %s", config[CONF_HOST]) raise CannotConnect except axis.AxisException: - LOGGER.exception('Unknown Axis communication error occurred') + LOGGER.exception("Unknown Axis communication error occurred") raise AuthenticationRequired diff --git a/homeassistant/components/axis/switch.py b/homeassistant/components/axis/switch.py index 852528120a5..a64ffc3fa85 100644 --- a/homeassistant/components/axis/switch.py +++ b/homeassistant/components/axis/switch.py @@ -24,8 +24,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): if event.CLASS == CLASS_OUTPUT: async_add_entities([AxisSwitch(event, device)], True) - device.listeners.append(async_dispatcher_connect( - hass, device.event_new_sensor, async_add_switch)) + device.listeners.append( + async_dispatcher_connect(hass, device.event_new_sensor, async_add_switch) + ) class AxisSwitch(AxisEventBase, SwitchDevice): @@ -38,22 +39,24 @@ class AxisSwitch(AxisEventBase, SwitchDevice): async def async_turn_on(self, **kwargs): """Turn on switch.""" - action = '/' + action = "/" await self.hass.async_add_executor_job( - self.device.api.vapix.ports[self.event.id].action, action) + self.device.api.vapix.ports[self.event.id].action, action + ) async def async_turn_off(self, **kwargs): """Turn off switch.""" - action = '\\' + action = "\\" await self.hass.async_add_executor_job( - self.device.api.vapix.ports[self.event.id].action, action) + self.device.api.vapix.ports[self.event.id].action, action + ) @property def name(self): """Return the name of the event.""" if self.event.id and self.device.api.vapix.ports[self.event.id].name: - return '{} {}'.format( - self.device.name, - self.device.api.vapix.ports[self.event.id].name) + return "{} {}".format( + self.device.name, self.device.api.vapix.ports[self.event.id].name + ) return super().name diff --git a/homeassistant/components/azure_event_hub/__init__.py b/homeassistant/components/azure_event_hub/__init__.py index c5362fe1821..371b8d1ea8d 100644 --- a/homeassistant/components/azure_event_hub/__init__.py +++ b/homeassistant/components/azure_event_hub/__init__.py @@ -7,8 +7,11 @@ import voluptuous as vol from azure.eventhub import EventData, EventHubClientAsync from homeassistant.const import ( - EVENT_HOMEASSISTANT_STOP, EVENT_STATE_CHANGED, STATE_UNAVAILABLE, - STATE_UNKNOWN) + EVENT_HOMEASSISTANT_STOP, + EVENT_STATE_CHANGED, + STATE_UNAVAILABLE, + STATE_UNKNOWN, +) from homeassistant.core import Event, HomeAssistant import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entityfilter import FILTER_SCHEMA @@ -16,23 +19,28 @@ from homeassistant.helpers.json import JSONEncoder _LOGGER = logging.getLogger(__name__) -DOMAIN = 'azure_event_hub' +DOMAIN = "azure_event_hub" -CONF_EVENT_HUB_NAMESPACE = 'event_hub_namespace' -CONF_EVENT_HUB_INSTANCE_NAME = 'event_hub_instance_name' -CONF_EVENT_HUB_SAS_POLICY = 'event_hub_sas_policy' -CONF_EVENT_HUB_SAS_KEY = 'event_hub_sas_key' -CONF_FILTER = 'filter' +CONF_EVENT_HUB_NAMESPACE = "event_hub_namespace" +CONF_EVENT_HUB_INSTANCE_NAME = "event_hub_instance_name" +CONF_EVENT_HUB_SAS_POLICY = "event_hub_sas_policy" +CONF_EVENT_HUB_SAS_KEY = "event_hub_sas_key" +CONF_FILTER = "filter" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_EVENT_HUB_NAMESPACE): cv.string, - vol.Required(CONF_EVENT_HUB_INSTANCE_NAME): cv.string, - vol.Required(CONF_EVENT_HUB_SAS_POLICY): cv.string, - vol.Required(CONF_EVENT_HUB_SAS_KEY): cv.string, - vol.Required(CONF_FILTER): FILTER_SCHEMA, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_EVENT_HUB_NAMESPACE): cv.string, + vol.Required(CONF_EVENT_HUB_INSTANCE_NAME): cv.string, + vol.Required(CONF_EVENT_HUB_SAS_POLICY): cv.string, + vol.Required(CONF_EVENT_HUB_SAS_KEY): cv.string, + vol.Required(CONF_FILTER): FILTER_SCHEMA, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) async def async_setup(hass: HomeAssistant, yaml_config: Dict[str, Any]): @@ -40,15 +48,16 @@ async def async_setup(hass: HomeAssistant, yaml_config: Dict[str, Any]): config = yaml_config[DOMAIN] event_hub_address = "amqps://{}.servicebus.windows.net/{}".format( - config[CONF_EVENT_HUB_NAMESPACE], - config[CONF_EVENT_HUB_INSTANCE_NAME]) + config[CONF_EVENT_HUB_NAMESPACE], config[CONF_EVENT_HUB_INSTANCE_NAME] + ) entities_filter = config[CONF_FILTER] client = EventHubClientAsync( event_hub_address, debug=True, username=config[CONF_EVENT_HUB_SAS_POLICY], - password=config[CONF_EVENT_HUB_SAS_KEY]) + password=config[CONF_EVENT_HUB_SAS_KEY], + ) async_sender = client.add_async_sender() await client.run_async() @@ -56,17 +65,16 @@ async def async_setup(hass: HomeAssistant, yaml_config: Dict[str, Any]): async def async_send_to_event_hub(event: Event): """Send states to Event Hub.""" - state = event.data.get('new_state') - if (state is None - or state.state in (STATE_UNKNOWN, '', STATE_UNAVAILABLE) - or not entities_filter(state.entity_id)): + state = event.data.get("new_state") + if ( + state is None + or state.state in (STATE_UNKNOWN, "", STATE_UNAVAILABLE) + or not entities_filter(state.entity_id) + ): return event_data = EventData( - json.dumps( - obj=state.as_dict(), - default=encoder.encode - ).encode('utf-8') + json.dumps(obj=state.as_dict(), default=encoder.encode).encode("utf-8") ) await async_sender.send(event_data) diff --git a/homeassistant/components/baidu/tts.py b/homeassistant/components/baidu/tts.py index faf62e92651..85737d1affd 100644 --- a/homeassistant/components/baidu/tts.py +++ b/homeassistant/components/baidu/tts.py @@ -9,49 +9,46 @@ import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -SUPPORTED_LANGUAGES = ['zh'] -DEFAULT_LANG = 'zh' +SUPPORTED_LANGUAGES = ["zh"] +DEFAULT_LANG = "zh" -CONF_APP_ID = 'app_id' -CONF_SECRET_KEY = 'secret_key' -CONF_SPEED = 'speed' -CONF_PITCH = 'pitch' -CONF_VOLUME = 'volume' -CONF_PERSON = 'person' +CONF_APP_ID = "app_id" +CONF_SECRET_KEY = "secret_key" +CONF_SPEED = "speed" +CONF_PITCH = "pitch" +CONF_VOLUME = "volume" +CONF_PERSON = "person" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_LANG, default=DEFAULT_LANG): vol.In(SUPPORTED_LANGUAGES), - vol.Required(CONF_APP_ID): cv.string, - vol.Required(CONF_API_KEY): cv.string, - vol.Required(CONF_SECRET_KEY): cv.string, - vol.Optional(CONF_SPEED, default=5): vol.All( - vol.Coerce(int), vol.Range(min=0, max=9) - ), - vol.Optional(CONF_PITCH, default=5): vol.All( - vol.Coerce(int), vol.Range(min=0, max=9) - ), - vol.Optional(CONF_VOLUME, default=5): vol.All( - vol.Coerce(int), vol.Range(min=0, max=15) - ), - vol.Optional(CONF_PERSON, default=0): vol.All( - vol.Coerce(int), vol.Range(min=0, max=4) - ), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_LANG, default=DEFAULT_LANG): vol.In(SUPPORTED_LANGUAGES), + vol.Required(CONF_APP_ID): cv.string, + vol.Required(CONF_API_KEY): cv.string, + vol.Required(CONF_SECRET_KEY): cv.string, + vol.Optional(CONF_SPEED, default=5): vol.All( + vol.Coerce(int), vol.Range(min=0, max=9) + ), + vol.Optional(CONF_PITCH, default=5): vol.All( + vol.Coerce(int), vol.Range(min=0, max=9) + ), + vol.Optional(CONF_VOLUME, default=5): vol.All( + vol.Coerce(int), vol.Range(min=0, max=15) + ), + vol.Optional(CONF_PERSON, default=0): vol.All( + vol.Coerce(int), vol.Range(min=0, max=4) + ), + } +) # Keys are options in the config file, and Values are options # required by Baidu TTS API. _OPTIONS = { - CONF_PERSON: 'per', - CONF_PITCH: 'pit', - CONF_SPEED: 'spd', - CONF_VOLUME: 'vol', + CONF_PERSON: "per", + CONF_PITCH: "pit", + CONF_SPEED: "spd", + CONF_VOLUME: "vol", } -SUPPORTED_OPTIONS = [ - CONF_PERSON, - CONF_PITCH, - CONF_SPEED, - CONF_VOLUME, -] +SUPPORTED_OPTIONS = [CONF_PERSON, CONF_PITCH, CONF_SPEED, CONF_VOLUME] def get_engine(hass, config): @@ -66,13 +63,13 @@ class BaiduTTSProvider(Provider): """Init Baidu TTS service.""" self.hass = hass self._lang = conf.get(CONF_LANG) - self._codec = 'mp3' - self.name = 'BaiduTTS' + self._codec = "mp3" + self.name = "BaiduTTS" self._app_data = { - 'appid': conf.get(CONF_APP_ID), - 'apikey': conf.get(CONF_API_KEY), - 'secretkey': conf.get(CONF_SECRET_KEY), + "appid": conf.get(CONF_APP_ID), + "apikey": conf.get(CONF_API_KEY), + "secretkey": conf.get(CONF_SECRET_KEY), } self._speech_conf_data = { @@ -110,31 +107,28 @@ class BaiduTTSProvider(Provider): def get_tts_audio(self, message, language, options=None): """Load TTS from BaiduTTS.""" from aip import AipSpeech + aip_speech = AipSpeech( - self._app_data['appid'], - self._app_data['apikey'], - self._app_data['secretkey'] + self._app_data["appid"], + self._app_data["apikey"], + self._app_data["secretkey"], ) if options is None: - result = aip_speech.synthesis( - message, language, 1, self._speech_conf_data - ) + result = aip_speech.synthesis(message, language, 1, self._speech_conf_data) else: speech_data = self._speech_conf_data.copy() for key, value in options.items(): speech_data[_OPTIONS[key]] = value - result = aip_speech.synthesis( - message, language, 1, speech_data - ) + result = aip_speech.synthesis(message, language, 1, speech_data) if isinstance(result, dict): _LOGGER.error( "Baidu TTS error-- err_no:%d; err_msg:%s; err_detail:%s", - result['err_no'], - result['err_msg'], - result['err_detail'] + result["err_no"], + result["err_msg"], + result["err_detail"], ) return None, None diff --git a/homeassistant/components/bayesian/binary_sensor.py b/homeassistant/components/bayesian/binary_sensor.py index 6b2395ef6d2..acefc5a3b26 100644 --- a/homeassistant/components/bayesian/binary_sensor.py +++ b/homeassistant/components/bayesian/binary_sensor.py @@ -3,66 +3,87 @@ from collections import OrderedDict import voluptuous as vol -from homeassistant.components.binary_sensor import ( - PLATFORM_SCHEMA, BinarySensorDevice) +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice from homeassistant.const import ( - CONF_ABOVE, CONF_BELOW, CONF_DEVICE_CLASS, CONF_ENTITY_ID, CONF_NAME, - CONF_PLATFORM, CONF_STATE, CONF_VALUE_TEMPLATE, STATE_UNKNOWN) + CONF_ABOVE, + CONF_BELOW, + CONF_DEVICE_CLASS, + CONF_ENTITY_ID, + CONF_NAME, + CONF_PLATFORM, + CONF_STATE, + CONF_VALUE_TEMPLATE, + STATE_UNKNOWN, +) from homeassistant.core import callback from homeassistant.helpers import condition import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_state_change -ATTR_OBSERVATIONS = 'observations' -ATTR_PROBABILITY = 'probability' -ATTR_PROBABILITY_THRESHOLD = 'probability_threshold' +ATTR_OBSERVATIONS = "observations" +ATTR_PROBABILITY = "probability" +ATTR_PROBABILITY_THRESHOLD = "probability_threshold" -CONF_OBSERVATIONS = 'observations' -CONF_PRIOR = 'prior' +CONF_OBSERVATIONS = "observations" +CONF_PRIOR = "prior" CONF_TEMPLATE = "template" -CONF_PROBABILITY_THRESHOLD = 'probability_threshold' -CONF_P_GIVEN_F = 'prob_given_false' -CONF_P_GIVEN_T = 'prob_given_true' -CONF_TO_STATE = 'to_state' +CONF_PROBABILITY_THRESHOLD = "probability_threshold" +CONF_P_GIVEN_F = "prob_given_false" +CONF_P_GIVEN_T = "prob_given_true" +CONF_TO_STATE = "to_state" DEFAULT_NAME = "Bayesian Binary Sensor" DEFAULT_PROBABILITY_THRESHOLD = 0.5 -NUMERIC_STATE_SCHEMA = vol.Schema({ - CONF_PLATFORM: 'numeric_state', - vol.Required(CONF_ENTITY_ID): cv.entity_id, - vol.Optional(CONF_ABOVE): vol.Coerce(float), - vol.Optional(CONF_BELOW): vol.Coerce(float), - vol.Required(CONF_P_GIVEN_T): vol.Coerce(float), - vol.Optional(CONF_P_GIVEN_F): vol.Coerce(float) -}, required=True) +NUMERIC_STATE_SCHEMA = vol.Schema( + { + CONF_PLATFORM: "numeric_state", + vol.Required(CONF_ENTITY_ID): cv.entity_id, + vol.Optional(CONF_ABOVE): vol.Coerce(float), + vol.Optional(CONF_BELOW): vol.Coerce(float), + vol.Required(CONF_P_GIVEN_T): vol.Coerce(float), + vol.Optional(CONF_P_GIVEN_F): vol.Coerce(float), + }, + required=True, +) -STATE_SCHEMA = vol.Schema({ - CONF_PLATFORM: CONF_STATE, - vol.Required(CONF_ENTITY_ID): cv.entity_id, - vol.Required(CONF_TO_STATE): cv.string, - vol.Required(CONF_P_GIVEN_T): vol.Coerce(float), - vol.Optional(CONF_P_GIVEN_F): vol.Coerce(float) -}, required=True) +STATE_SCHEMA = vol.Schema( + { + CONF_PLATFORM: CONF_STATE, + vol.Required(CONF_ENTITY_ID): cv.entity_id, + vol.Required(CONF_TO_STATE): cv.string, + vol.Required(CONF_P_GIVEN_T): vol.Coerce(float), + vol.Optional(CONF_P_GIVEN_F): vol.Coerce(float), + }, + required=True, +) -TEMPLATE_SCHEMA = vol.Schema({ - CONF_PLATFORM: CONF_TEMPLATE, - vol.Required(CONF_VALUE_TEMPLATE): cv.template, - vol.Required(CONF_P_GIVEN_T): vol.Coerce(float), - vol.Optional(CONF_P_GIVEN_F): vol.Coerce(float) -}, required=True) +TEMPLATE_SCHEMA = vol.Schema( + { + CONF_PLATFORM: CONF_TEMPLATE, + vol.Required(CONF_VALUE_TEMPLATE): cv.template, + vol.Required(CONF_P_GIVEN_T): vol.Coerce(float), + vol.Optional(CONF_P_GIVEN_F): vol.Coerce(float), + }, + required=True, +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_DEVICE_CLASS): cv.string, - vol.Required(CONF_OBSERVATIONS): - vol.Schema(vol.All(cv.ensure_list, - [vol.Any(NUMERIC_STATE_SCHEMA, STATE_SCHEMA, - TEMPLATE_SCHEMA)])), - vol.Required(CONF_PRIOR): vol.Coerce(float), - vol.Optional(CONF_PROBABILITY_THRESHOLD, - default=DEFAULT_PROBABILITY_THRESHOLD): vol.Coerce(float), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_DEVICE_CLASS): cv.string, + vol.Required(CONF_OBSERVATIONS): vol.Schema( + vol.All( + cv.ensure_list, + [vol.Any(NUMERIC_STATE_SCHEMA, STATE_SCHEMA, TEMPLATE_SCHEMA)], + ) + ), + vol.Required(CONF_PRIOR): vol.Coerce(float), + vol.Optional( + CONF_PROBABILITY_THRESHOLD, default=DEFAULT_PROBABILITY_THRESHOLD + ): vol.Coerce(float), + } +) def update_probability(prior, prob_true, prob_false): @@ -73,8 +94,7 @@ def update_probability(prior, prob_true, prob_false): return probability -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Bayesian Binary sensor.""" name = config.get(CONF_NAME) observations = config.get(CONF_OBSERVATIONS) @@ -82,17 +102,20 @@ async def async_setup_platform(hass, config, async_add_entities, probability_threshold = config.get(CONF_PROBABILITY_THRESHOLD) device_class = config.get(CONF_DEVICE_CLASS) - async_add_entities([ - BayesianBinarySensor( - name, prior, observations, probability_threshold, device_class) - ], True) + async_add_entities( + [ + BayesianBinarySensor( + name, prior, observations, probability_threshold, device_class + ) + ], + True, + ) class BayesianBinarySensor(BinarySensorDevice): """Representation of a Bayesian sensor.""" - def __init__(self, name, prior, observations, probability_threshold, - device_class): + def __init__(self, name, prior, observations, probability_threshold, device_class): """Initialize the Bayesian sensor.""" self._name = name self._observations = observations @@ -106,32 +129,31 @@ class BayesianBinarySensor(BinarySensorDevice): to_observe = set() for obs in self._observations: - if 'entity_id' in obs: - to_observe.update(set([obs.get('entity_id')])) - if 'value_template' in obs: - to_observe.update( - set(obs.get(CONF_VALUE_TEMPLATE).extract_entities())) + if "entity_id" in obs: + to_observe.update(set([obs.get("entity_id")])) + if "value_template" in obs: + to_observe.update(set(obs.get(CONF_VALUE_TEMPLATE).extract_entities())) self.entity_obs = dict.fromkeys(to_observe, []) for ind, obs in enumerate(self._observations): - obs['id'] = ind - if 'entity_id' in obs: - self.entity_obs[obs['entity_id']].append(obs) - if 'value_template' in obs: + obs["id"] = ind + if "entity_id" in obs: + self.entity_obs[obs["entity_id"]].append(obs) + if "value_template" in obs: for ent in obs.get(CONF_VALUE_TEMPLATE).extract_entities(): self.entity_obs[ent].append(obs) self.watchers = { - 'numeric_state': self._process_numeric_state, - 'state': self._process_state, - 'template': self._process_template + "numeric_state": self._process_numeric_state, + "state": self._process_state, + "template": self._process_template, } async def async_added_to_hass(self): """Call when entity about to be added.""" + @callback - def async_threshold_sensor_state_listener(entity, old_state, - new_state): + def async_threshold_sensor_state_listener(entity, old_state, new_state): """Handle sensor state changes.""" if new_state.state == STATE_UNKNOWN: return @@ -139,33 +161,32 @@ class BayesianBinarySensor(BinarySensorDevice): entity_obs_list = self.entity_obs[entity] for entity_obs in entity_obs_list: - platform = entity_obs['platform'] + platform = entity_obs["platform"] self.watchers[platform](entity_obs) prior = self.prior for obs in self.current_obs.values(): - prior = update_probability( - prior, obs['prob_true'], obs['prob_false']) + prior = update_probability(prior, obs["prob_true"], obs["prob_false"]) self.probability = prior self.hass.async_add_job(self.async_update_ha_state, True) async_track_state_change( - self.hass, self.entity_obs, async_threshold_sensor_state_listener) + self.hass, self.entity_obs, async_threshold_sensor_state_listener + ) def _update_current_obs(self, entity_observation, should_trigger): """Update current observation.""" - obs_id = entity_observation['id'] + obs_id = entity_observation["id"] if should_trigger: - prob_true = entity_observation['prob_given_true'] - prob_false = entity_observation.get( - 'prob_given_false', 1 - prob_true) + prob_true = entity_observation["prob_given_true"] + prob_false = entity_observation.get("prob_given_false", 1 - prob_true) self.current_obs[obs_id] = { - 'prob_true': prob_true, - 'prob_false': prob_false + "prob_true": prob_true, + "prob_false": prob_false, } else: @@ -173,21 +194,26 @@ class BayesianBinarySensor(BinarySensorDevice): def _process_numeric_state(self, entity_observation): """Add entity to current_obs if numeric state conditions are met.""" - entity = entity_observation['entity_id'] + entity = entity_observation["entity_id"] should_trigger = condition.async_numeric_state( - self.hass, entity, - entity_observation.get('below'), - entity_observation.get('above'), None, entity_observation) + self.hass, + entity, + entity_observation.get("below"), + entity_observation.get("above"), + None, + entity_observation, + ) self._update_current_obs(entity_observation, should_trigger) def _process_state(self, entity_observation): """Add entity to current observations if state conditions are met.""" - entity = entity_observation['entity_id'] + entity = entity_observation["entity_id"] should_trigger = condition.state( - self.hass, entity, entity_observation.get('to_state')) + self.hass, entity, entity_observation.get("to_state") + ) self._update_current_obs(entity_observation, should_trigger) @@ -196,7 +222,8 @@ class BayesianBinarySensor(BinarySensorDevice): template = entity_observation.get(CONF_VALUE_TEMPLATE) template.hass = self.hass should_trigger = condition.async_template( - self.hass, template, entity_observation) + self.hass, template, entity_observation + ) self._update_current_obs(entity_observation, should_trigger) @property diff --git a/homeassistant/components/bbb_gpio/__init__.py b/homeassistant/components/bbb_gpio/__init__.py index 85ea5753739..bfaa2a7c50d 100644 --- a/homeassistant/components/bbb_gpio/__init__.py +++ b/homeassistant/components/bbb_gpio/__init__.py @@ -1,12 +1,11 @@ """Support for controlling GPIO pins of a Beaglebone Black.""" import logging -from homeassistant.const import ( - EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) +from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP _LOGGER = logging.getLogger(__name__) -DOMAIN = 'bbb_gpio' +DOMAIN = "bbb_gpio" def setup(hass, config): @@ -30,6 +29,7 @@ def setup_output(pin): """Set up a GPIO as output.""" # pylint: disable=import-error from Adafruit_BBIO import GPIO + GPIO.setup(pin, GPIO.OUT) @@ -37,15 +37,15 @@ def setup_input(pin, pull_mode): """Set up a GPIO as input.""" # pylint: disable=import-error from Adafruit_BBIO import GPIO - GPIO.setup(pin, GPIO.IN, - GPIO.PUD_DOWN if pull_mode == 'DOWN' - else GPIO.PUD_UP) + + GPIO.setup(pin, GPIO.IN, GPIO.PUD_DOWN if pull_mode == "DOWN" else GPIO.PUD_UP) def write_output(pin, value): """Write a value to a GPIO.""" # pylint: disable=import-error from Adafruit_BBIO import GPIO + GPIO.output(pin, value) @@ -53,6 +53,7 @@ def read_input(pin): """Read a value from a GPIO.""" # pylint: disable=import-error from Adafruit_BBIO import GPIO + return GPIO.input(pin) is GPIO.HIGH @@ -60,5 +61,5 @@ def edge_detect(pin, event_callback, bounce): """Add detection for RISING and FALLING events.""" # pylint: disable=import-error from Adafruit_BBIO import GPIO - GPIO.add_event_detect( - pin, GPIO.BOTH, callback=event_callback, bouncetime=bounce) + + GPIO.add_event_detect(pin, GPIO.BOTH, callback=event_callback, bouncetime=bounce) diff --git a/homeassistant/components/bbb_gpio/binary_sensor.py b/homeassistant/components/bbb_gpio/binary_sensor.py index bcc45a4af32..105015da720 100644 --- a/homeassistant/components/bbb_gpio/binary_sensor.py +++ b/homeassistant/components/bbb_gpio/binary_sensor.py @@ -4,34 +4,33 @@ import logging import voluptuous as vol from homeassistant.components import bbb_gpio -from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) -from homeassistant.const import (DEVICE_DEFAULT_NAME, CONF_NAME) +from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA +from homeassistant.const import DEVICE_DEFAULT_NAME, CONF_NAME import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -CONF_PINS = 'pins' -CONF_BOUNCETIME = 'bouncetime' -CONF_INVERT_LOGIC = 'invert_logic' -CONF_PULL_MODE = 'pull_mode' +CONF_PINS = "pins" +CONF_BOUNCETIME = "bouncetime" +CONF_INVERT_LOGIC = "invert_logic" +CONF_PULL_MODE = "pull_mode" DEFAULT_BOUNCETIME = 50 DEFAULT_INVERT_LOGIC = False -DEFAULT_PULL_MODE = 'UP' +DEFAULT_PULL_MODE = "UP" -PIN_SCHEMA = vol.Schema({ - vol.Required(CONF_NAME): cv.string, - vol.Optional(CONF_BOUNCETIME, default=DEFAULT_BOUNCETIME): cv.positive_int, - vol.Optional(CONF_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean, - vol.Optional(CONF_PULL_MODE, default=DEFAULT_PULL_MODE): - vol.In(['UP', 'DOWN']) -}) +PIN_SCHEMA = vol.Schema( + { + vol.Required(CONF_NAME): cv.string, + vol.Optional(CONF_BOUNCETIME, default=DEFAULT_BOUNCETIME): cv.positive_int, + vol.Optional(CONF_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean, + vol.Optional(CONF_PULL_MODE, default=DEFAULT_PULL_MODE): vol.In(["UP", "DOWN"]), + } +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_PINS, default={}): - vol.Schema({cv.string: PIN_SCHEMA}), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_PINS, default={}): vol.Schema({cv.string: PIN_SCHEMA})} +) def setup_platform(hass, config, add_entities, discovery_info=None): diff --git a/homeassistant/components/bbb_gpio/switch.py b/homeassistant/components/bbb_gpio/switch.py index 49b4c5de19c..45f95609758 100644 --- a/homeassistant/components/bbb_gpio/switch.py +++ b/homeassistant/components/bbb_gpio/switch.py @@ -5,25 +5,27 @@ import voluptuous as vol from homeassistant.components.switch import PLATFORM_SCHEMA from homeassistant.components import bbb_gpio -from homeassistant.const import (DEVICE_DEFAULT_NAME, CONF_NAME) +from homeassistant.const import DEVICE_DEFAULT_NAME, CONF_NAME from homeassistant.helpers.entity import ToggleEntity import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -CONF_PINS = 'pins' -CONF_INITIAL = 'initial' -CONF_INVERT_LOGIC = 'invert_logic' +CONF_PINS = "pins" +CONF_INITIAL = "initial" +CONF_INVERT_LOGIC = "invert_logic" -PIN_SCHEMA = vol.Schema({ - vol.Required(CONF_NAME): cv.string, - vol.Optional(CONF_INITIAL, default=False): cv.boolean, - vol.Optional(CONF_INVERT_LOGIC, default=False): cv.boolean, -}) +PIN_SCHEMA = vol.Schema( + { + vol.Required(CONF_NAME): cv.string, + vol.Optional(CONF_INITIAL, default=False): cv.boolean, + vol.Optional(CONF_INVERT_LOGIC, default=False): cv.boolean, + } +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_PINS, default={}): vol.Schema({cv.string: PIN_SCHEMA}), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_PINS, default={}): vol.Schema({cv.string: PIN_SCHEMA})} +) def setup_platform(hass, config, add_entities, discovery_info=None): diff --git a/homeassistant/components/bbox/device_tracker.py b/homeassistant/components/bbox/device_tracker.py index f70969aa61b..b565d05685f 100644 --- a/homeassistant/components/bbox/device_tracker.py +++ b/homeassistant/components/bbox/device_tracker.py @@ -6,7 +6,10 @@ import logging import voluptuous as vol from homeassistant.components.device_tracker import ( - DOMAIN, PLATFORM_SCHEMA, DeviceScanner) + DOMAIN, + PLATFORM_SCHEMA, + DeviceScanner, +) from homeassistant.const import CONF_HOST import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle @@ -14,13 +17,13 @@ import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) -DEFAULT_HOST = '192.168.1.254' +DEFAULT_HOST = "192.168.1.254" MIN_TIME_BETWEEN_SCANS = timedelta(seconds=60) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string} +) def get_scanner(hass, config): @@ -30,7 +33,7 @@ def get_scanner(hass, config): return scanner if scanner.success_init else None -Device = namedtuple('Device', ['mac', 'name', 'ip', 'last_update']) +Device = namedtuple("Device", ["mac", "name", "ip", "last_update"]) class BboxDeviceScanner(DeviceScanner): @@ -56,8 +59,9 @@ class BboxDeviceScanner(DeviceScanner): def get_device_name(self, device): """Return the name of the given device or None if we don't know.""" - filter_named = [result.name for result in self.last_results if - result.mac == device] + filter_named = [ + result.name for result in self.last_results if result.mac == device + ] if filter_named: return filter_named[0] @@ -79,11 +83,13 @@ class BboxDeviceScanner(DeviceScanner): now = dt_util.now() last_results = [] for device in result: - if device['active'] != 1: + if device["active"] != 1: continue last_results.append( - Device(device['macaddress'], device['hostname'], - device['ipaddress'], now)) + Device( + device["macaddress"], device["hostname"], device["ipaddress"], now + ) + ) self.last_results = last_results diff --git a/homeassistant/components/bbox/sensor.py b/homeassistant/components/bbox/sensor.py index 80fa82b30fc..76621b7792b 100644 --- a/homeassistant/components/bbox/sensor.py +++ b/homeassistant/components/bbox/sensor.py @@ -7,38 +7,52 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ( - CONF_NAME, CONF_MONITORED_VARIABLES, ATTR_ATTRIBUTION) +from homeassistant.const import CONF_NAME, CONF_MONITORED_VARIABLES, ATTR_ATTRIBUTION from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -BANDWIDTH_MEGABITS_SECONDS = 'Mb/s' # type: str +BANDWIDTH_MEGABITS_SECONDS = "Mb/s" # type: str ATTRIBUTION = "Powered by Bouygues Telecom" -DEFAULT_NAME = 'Bbox' +DEFAULT_NAME = "Bbox" MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) # Sensor types are defined like so: Name, unit, icon SENSOR_TYPES = { - 'down_max_bandwidth': ['Maximum Download Bandwidth', - BANDWIDTH_MEGABITS_SECONDS, 'mdi:download'], - 'up_max_bandwidth': ['Maximum Upload Bandwidth', - BANDWIDTH_MEGABITS_SECONDS, 'mdi:upload'], - 'current_down_bandwidth': ['Currently Used Download Bandwidth', - BANDWIDTH_MEGABITS_SECONDS, 'mdi:download'], - 'current_up_bandwidth': ['Currently Used Upload Bandwidth', - BANDWIDTH_MEGABITS_SECONDS, 'mdi:upload'], + "down_max_bandwidth": [ + "Maximum Download Bandwidth", + BANDWIDTH_MEGABITS_SECONDS, + "mdi:download", + ], + "up_max_bandwidth": [ + "Maximum Upload Bandwidth", + BANDWIDTH_MEGABITS_SECONDS, + "mdi:upload", + ], + "current_down_bandwidth": [ + "Currently Used Download Bandwidth", + BANDWIDTH_MEGABITS_SECONDS, + "mdi:download", + ], + "current_up_bandwidth": [ + "Currently Used Upload Bandwidth", + BANDWIDTH_MEGABITS_SECONDS, + "mdi:upload", + ], } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_MONITORED_VARIABLES): - vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_MONITORED_VARIABLES): vol.All( + cv.ensure_list, [vol.In(SENSOR_TYPES)] + ), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -77,7 +91,7 @@ class BboxSensor(Entity): @property def name(self): """Return the name of the sensor.""" - return '{} {}'.format(self.client_name, self._name) + return "{} {}".format(self.client_name, self._name) @property def state(self): @@ -97,25 +111,19 @@ class BboxSensor(Entity): @property def device_state_attributes(self): """Return the state attributes.""" - return { - ATTR_ATTRIBUTION: ATTRIBUTION, - } + return {ATTR_ATTRIBUTION: ATTRIBUTION} def update(self): """Get the latest data from Bbox and update the state.""" self.bbox_data.update() - if self.type == 'down_max_bandwidth': - self._state = round( - self.bbox_data.data['rx']['maxBandwidth'] / 1000, 2) - elif self.type == 'up_max_bandwidth': - self._state = round( - self.bbox_data.data['tx']['maxBandwidth'] / 1000, 2) - elif self.type == 'current_down_bandwidth': - self._state = round(self.bbox_data.data['rx']['bandwidth'] / 1000, - 2) - elif self.type == 'current_up_bandwidth': - self._state = round(self.bbox_data.data['tx']['bandwidth'] / 1000, - 2) + if self.type == "down_max_bandwidth": + self._state = round(self.bbox_data.data["rx"]["maxBandwidth"] / 1000, 2) + elif self.type == "up_max_bandwidth": + self._state = round(self.bbox_data.data["tx"]["maxBandwidth"] / 1000, 2) + elif self.type == "current_down_bandwidth": + self._state = round(self.bbox_data.data["rx"]["bandwidth"] / 1000, 2) + elif self.type == "current_up_bandwidth": + self._state = round(self.bbox_data.data["tx"]["bandwidth"] / 1000, 2) class BboxData: diff --git a/homeassistant/components/bh1750/sensor.py b/homeassistant/components/bh1750/sensor.py index eaee023ce86..0a305c21adb 100644 --- a/homeassistant/components/bh1750/sensor.py +++ b/homeassistant/components/bh1750/sensor.py @@ -11,12 +11,12 @@ from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -CONF_I2C_ADDRESS = 'i2c_address' -CONF_I2C_BUS = 'i2c_bus' -CONF_OPERATION_MODE = 'operation_mode' -CONF_SENSITIVITY = 'sensitivity' -CONF_DELAY = 'measurement_delay_ms' -CONF_MULTIPLIER = 'multiplier' +CONF_I2C_ADDRESS = "i2c_address" +CONF_I2C_BUS = "i2c_bus" +CONF_OPERATION_MODE = "operation_mode" +CONF_SENSITIVITY = "sensitivity" +CONF_DELAY = "measurement_delay_ms" +CONF_MULTIPLIER = "multiplier" # Operation modes for BH1750 sensor (from the datasheet). Time typically 120ms # In one time measurements, device is set to Power Down after each sample. @@ -29,35 +29,36 @@ ONE_TIME_HIGH_RES_MODE_2 = "one_time_high_res_mode_2" OPERATION_MODES = { CONTINUOUS_LOW_RES_MODE: (0x13, True), # 4lx resolution CONTINUOUS_HIGH_RES_MODE_1: (0x10, True), # 1lx resolution. - CONTINUOUS_HIGH_RES_MODE_2: (0X11, True), # 0.5lx resolution. + CONTINUOUS_HIGH_RES_MODE_2: (0x11, True), # 0.5lx resolution. ONE_TIME_LOW_RES_MODE: (0x23, False), # 4lx resolution. ONE_TIME_HIGH_RES_MODE_1: (0x20, False), # 1lx resolution. ONE_TIME_HIGH_RES_MODE_2: (0x21, False), # 0.5lx resolution. } -SENSOR_UNIT = 'lx' -DEFAULT_NAME = 'BH1750 Light Sensor' -DEFAULT_I2C_ADDRESS = '0x23' +SENSOR_UNIT = "lx" +DEFAULT_NAME = "BH1750 Light Sensor" +DEFAULT_I2C_ADDRESS = "0x23" DEFAULT_I2C_BUS = 1 DEFAULT_MODE = CONTINUOUS_HIGH_RES_MODE_1 DEFAULT_DELAY_MS = 120 DEFAULT_SENSITIVITY = 69 # from 31 to 254 -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_I2C_ADDRESS, default=DEFAULT_I2C_ADDRESS): cv.string, - vol.Optional(CONF_I2C_BUS, default=DEFAULT_I2C_BUS): vol.Coerce(int), - vol.Optional(CONF_OPERATION_MODE, default=DEFAULT_MODE): - vol.In(OPERATION_MODES), - vol.Optional(CONF_SENSITIVITY, default=DEFAULT_SENSITIVITY): - cv.positive_int, - vol.Optional(CONF_DELAY, default=DEFAULT_DELAY_MS): cv.positive_int, - vol.Optional(CONF_MULTIPLIER, default=1.): vol.Range(min=0.1, max=10), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_I2C_ADDRESS, default=DEFAULT_I2C_ADDRESS): cv.string, + vol.Optional(CONF_I2C_BUS, default=DEFAULT_I2C_BUS): vol.Coerce(int), + vol.Optional(CONF_OPERATION_MODE, default=DEFAULT_MODE): vol.In( + OPERATION_MODES + ), + vol.Optional(CONF_SENSITIVITY, default=DEFAULT_SENSITIVITY): cv.positive_int, + vol.Optional(CONF_DELAY, default=DEFAULT_DELAY_MS): cv.positive_int, + vol.Optional(CONF_MULTIPLIER, default=1.0): vol.Range(min=0.1, max=10), + } +) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the BH1750 sensor.""" import smbus # pylint: disable=import-error from i2csense.bh1750 import BH1750 # pylint: disable=import-error @@ -70,20 +71,26 @@ async def async_setup_platform(hass, config, async_add_entities, bus = smbus.SMBus(bus_number) sensor = await hass.async_add_job( - partial(BH1750, bus, i2c_address, - operation_mode=operation_mode, - measurement_delay=config.get(CONF_DELAY), - sensitivity=config.get(CONF_SENSITIVITY), - logger=_LOGGER) + partial( + BH1750, + bus, + i2c_address, + operation_mode=operation_mode, + measurement_delay=config.get(CONF_DELAY), + sensitivity=config.get(CONF_SENSITIVITY), + logger=_LOGGER, + ) ) if not sensor.sample_ok: _LOGGER.error("BH1750 sensor not detected at %s", i2c_address) return False - dev = [BH1750Sensor(sensor, name, SENSOR_UNIT, - config.get(CONF_MULTIPLIER))] - _LOGGER.info("Setup of BH1750 light sensor at %s in mode %s is complete", - i2c_address, operation_mode) + dev = [BH1750Sensor(sensor, name, SENSOR_UNIT, config.get(CONF_MULTIPLIER))] + _LOGGER.info( + "Setup of BH1750 light sensor at %s in mode %s is complete", + i2c_address, + operation_mode, + ) async_add_entities(dev, True) @@ -91,7 +98,7 @@ async def async_setup_platform(hass, config, async_add_entities, class BH1750Sensor(Entity): """Implementation of the BH1750 sensor.""" - def __init__(self, bh1750_sensor, name, unit, multiplier=1.): + def __init__(self, bh1750_sensor, name, unit, multiplier=1.0): """Initialize the sensor.""" self._name = name self._unit_of_measurement = unit @@ -125,10 +132,9 @@ class BH1750Sensor(Entity): async def async_update(self): """Get the latest data from the BH1750 and update the states.""" await self.hass.async_add_job(self.bh1750_sensor.update) - if self.bh1750_sensor.sample_ok \ - and self.bh1750_sensor.light_level >= 0: - self._state = int(round(self.bh1750_sensor.light_level - * self._multiplier)) + if self.bh1750_sensor.sample_ok and self.bh1750_sensor.light_level >= 0: + self._state = int(round(self.bh1750_sensor.light_level * self._multiplier)) else: - _LOGGER.warning("Bad Update of sensor.%s: %s", - self.name, self.bh1750_sensor.light_level) + _LOGGER.warning( + "Bad Update of sensor.%s: %s", self.name, self.bh1750_sensor.light_level + ) diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py index 19054588ee7..951f4a423e5 100644 --- a/homeassistant/components/binary_sensor/__init__.py +++ b/homeassistant/components/binary_sensor/__init__.py @@ -7,83 +7,85 @@ import voluptuous as vol from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity import Entity -from homeassistant.const import (STATE_ON, STATE_OFF) +from homeassistant.const import STATE_ON, STATE_OFF from homeassistant.helpers.config_validation import ( # noqa - PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE) + PLATFORM_SCHEMA, + PLATFORM_SCHEMA_BASE, +) -DOMAIN = 'binary_sensor' +DOMAIN = "binary_sensor" SCAN_INTERVAL = timedelta(seconds=30) -ENTITY_ID_FORMAT = DOMAIN + '.{}' +ENTITY_ID_FORMAT = DOMAIN + ".{}" # On means low, Off means normal -DEVICE_CLASS_BATTERY = 'battery' +DEVICE_CLASS_BATTERY = "battery" # On means cold, Off means normal -DEVICE_CLASS_COLD = 'cold' +DEVICE_CLASS_COLD = "cold" # On means connected, Off means disconnected -DEVICE_CLASS_CONNECTIVITY = 'connectivity' +DEVICE_CLASS_CONNECTIVITY = "connectivity" # On means open, Off means closed -DEVICE_CLASS_DOOR = 'door' +DEVICE_CLASS_DOOR = "door" # On means open, Off means closed -DEVICE_CLASS_GARAGE_DOOR = 'garage_door' +DEVICE_CLASS_GARAGE_DOOR = "garage_door" # On means gas detected, Off means no gas (clear) -DEVICE_CLASS_GAS = 'gas' +DEVICE_CLASS_GAS = "gas" # On means hot, Off means normal -DEVICE_CLASS_HEAT = 'heat' +DEVICE_CLASS_HEAT = "heat" # On means light detected, Off means no light -DEVICE_CLASS_LIGHT = 'light' +DEVICE_CLASS_LIGHT = "light" # On means open (unlocked), Off means closed (locked) -DEVICE_CLASS_LOCK = 'lock' +DEVICE_CLASS_LOCK = "lock" # On means wet, Off means dry -DEVICE_CLASS_MOISTURE = 'moisture' +DEVICE_CLASS_MOISTURE = "moisture" # On means motion detected, Off means no motion (clear) -DEVICE_CLASS_MOTION = 'motion' +DEVICE_CLASS_MOTION = "motion" # On means moving, Off means not moving (stopped) -DEVICE_CLASS_MOVING = 'moving' +DEVICE_CLASS_MOVING = "moving" # On means occupied, Off means not occupied (clear) -DEVICE_CLASS_OCCUPANCY = 'occupancy' +DEVICE_CLASS_OCCUPANCY = "occupancy" # On means open, Off means closed -DEVICE_CLASS_OPENING = 'opening' +DEVICE_CLASS_OPENING = "opening" # On means plugged in, Off means unplugged -DEVICE_CLASS_PLUG = 'plug' +DEVICE_CLASS_PLUG = "plug" # On means power detected, Off means no power -DEVICE_CLASS_POWER = 'power' +DEVICE_CLASS_POWER = "power" # On means home, Off means away -DEVICE_CLASS_PRESENCE = 'presence' +DEVICE_CLASS_PRESENCE = "presence" # On means problem detected, Off means no problem (OK) -DEVICE_CLASS_PROBLEM = 'problem' +DEVICE_CLASS_PROBLEM = "problem" # On means unsafe, Off means safe -DEVICE_CLASS_SAFETY = 'safety' +DEVICE_CLASS_SAFETY = "safety" # On means smoke detected, Off means no smoke (clear) -DEVICE_CLASS_SMOKE = 'smoke' +DEVICE_CLASS_SMOKE = "smoke" # On means sound detected, Off means no sound (clear) -DEVICE_CLASS_SOUND = 'sound' +DEVICE_CLASS_SOUND = "sound" # On means vibration detected, Off means no vibration -DEVICE_CLASS_VIBRATION = 'vibration' +DEVICE_CLASS_VIBRATION = "vibration" # On means open, Off means closed -DEVICE_CLASS_WINDOW = 'window' +DEVICE_CLASS_WINDOW = "window" DEVICE_CLASSES = [ DEVICE_CLASS_BATTERY, @@ -117,7 +119,8 @@ DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.In(DEVICE_CLASSES)) async def async_setup(hass, config): """Track states and offer events for binary sensors.""" component = hass.data[DOMAIN] = EntityComponent( - logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL) + logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL + ) await component.async_setup(config) return True diff --git a/homeassistant/components/bitcoin/sensor.py b/homeassistant/components/bitcoin/sensor.py index 6ccb10f50e6..4d8d5643826 100644 --- a/homeassistant/components/bitcoin/sensor.py +++ b/homeassistant/components/bitcoin/sensor.py @@ -5,8 +5,7 @@ from datetime import timedelta import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ( - CONF_DISPLAY_OPTIONS, ATTR_ATTRIBUTION, CONF_CURRENCY) +from homeassistant.const import CONF_DISPLAY_OPTIONS, ATTR_ATTRIBUTION, CONF_CURRENCY import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -14,41 +13,44 @@ _LOGGER = logging.getLogger(__name__) ATTRIBUTION = "Data provided by blockchain.info" -DEFAULT_CURRENCY = 'USD' +DEFAULT_CURRENCY = "USD" -ICON = 'mdi:currency-btc' +ICON = "mdi:currency-btc" SCAN_INTERVAL = timedelta(minutes=5) OPTION_TYPES = { - 'exchangerate': ['Exchange rate (1 BTC)', None], - 'trade_volume_btc': ['Trade volume', 'BTC'], - 'miners_revenue_usd': ['Miners revenue', 'USD'], - 'btc_mined': ['Mined', 'BTC'], - 'trade_volume_usd': ['Trade volume', 'USD'], - 'difficulty': ['Difficulty', None], - 'minutes_between_blocks': ['Time between Blocks', 'min'], - 'number_of_transactions': ['No. of Transactions', None], - 'hash_rate': ['Hash rate', 'PH/s'], - 'timestamp': ['Timestamp', None], - 'mined_blocks': ['Mined Blocks', None], - 'blocks_size': ['Block size', None], - 'total_fees_btc': ['Total fees', 'BTC'], - 'total_btc_sent': ['Total sent', 'BTC'], - 'estimated_btc_sent': ['Estimated sent', 'BTC'], - 'total_btc': ['Total', 'BTC'], - 'total_blocks': ['Total Blocks', None], - 'next_retarget': ['Next retarget', None], - 'estimated_transaction_volume_usd': ['Est. Transaction volume', 'USD'], - 'miners_revenue_btc': ['Miners revenue', 'BTC'], - 'market_price_usd': ['Market price', 'USD'] + "exchangerate": ["Exchange rate (1 BTC)", None], + "trade_volume_btc": ["Trade volume", "BTC"], + "miners_revenue_usd": ["Miners revenue", "USD"], + "btc_mined": ["Mined", "BTC"], + "trade_volume_usd": ["Trade volume", "USD"], + "difficulty": ["Difficulty", None], + "minutes_between_blocks": ["Time between Blocks", "min"], + "number_of_transactions": ["No. of Transactions", None], + "hash_rate": ["Hash rate", "PH/s"], + "timestamp": ["Timestamp", None], + "mined_blocks": ["Mined Blocks", None], + "blocks_size": ["Block size", None], + "total_fees_btc": ["Total fees", "BTC"], + "total_btc_sent": ["Total sent", "BTC"], + "estimated_btc_sent": ["Estimated sent", "BTC"], + "total_btc": ["Total", "BTC"], + "total_blocks": ["Total Blocks", None], + "next_retarget": ["Next retarget", None], + "estimated_transaction_volume_usd": ["Est. Transaction volume", "USD"], + "miners_revenue_btc": ["Miners revenue", "BTC"], + "market_price_usd": ["Market price", "USD"], } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_DISPLAY_OPTIONS, default=[]): - vol.All(cv.ensure_list, [vol.In(OPTION_TYPES)]), - vol.Optional(CONF_CURRENCY, default=DEFAULT_CURRENCY): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_DISPLAY_OPTIONS, default=[]): vol.All( + cv.ensure_list, [vol.In(OPTION_TYPES)] + ), + vol.Optional(CONF_CURRENCY, default=DEFAULT_CURRENCY): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -104,9 +106,7 @@ class BitcoinSensor(Entity): @property def device_state_attributes(self): """Return the state attributes of the sensor.""" - return { - ATTR_ATTRIBUTION: ATTRIBUTION, - } + return {ATTR_ATTRIBUTION: ATTRIBUTION} def update(self): """Get the latest data and updates the states.""" @@ -114,52 +114,49 @@ class BitcoinSensor(Entity): stats = self.data.stats ticker = self.data.ticker - if self.type == 'exchangerate': + if self.type == "exchangerate": self._state = ticker[self._currency].p15min self._unit_of_measurement = self._currency - elif self.type == 'trade_volume_btc': - self._state = '{0:.1f}'.format(stats.trade_volume_btc) - elif self.type == 'miners_revenue_usd': - self._state = '{0:.0f}'.format(stats.miners_revenue_usd) - elif self.type == 'btc_mined': - self._state = '{}'.format(stats.btc_mined * 0.00000001) - elif self.type == 'trade_volume_usd': - self._state = '{0:.1f}'.format(stats.trade_volume_usd) - elif self.type == 'difficulty': - self._state = '{0:.0f}'.format(stats.difficulty) - elif self.type == 'minutes_between_blocks': - self._state = '{0:.2f}'.format(stats.minutes_between_blocks) - elif self.type == 'number_of_transactions': - self._state = '{}'.format(stats.number_of_transactions) - elif self.type == 'hash_rate': - self._state = '{0:.1f}'.format(stats.hash_rate * 0.000001) - elif self.type == 'timestamp': + elif self.type == "trade_volume_btc": + self._state = "{0:.1f}".format(stats.trade_volume_btc) + elif self.type == "miners_revenue_usd": + self._state = "{0:.0f}".format(stats.miners_revenue_usd) + elif self.type == "btc_mined": + self._state = "{}".format(stats.btc_mined * 0.00000001) + elif self.type == "trade_volume_usd": + self._state = "{0:.1f}".format(stats.trade_volume_usd) + elif self.type == "difficulty": + self._state = "{0:.0f}".format(stats.difficulty) + elif self.type == "minutes_between_blocks": + self._state = "{0:.2f}".format(stats.minutes_between_blocks) + elif self.type == "number_of_transactions": + self._state = "{}".format(stats.number_of_transactions) + elif self.type == "hash_rate": + self._state = "{0:.1f}".format(stats.hash_rate * 0.000001) + elif self.type == "timestamp": self._state = stats.timestamp - elif self.type == 'mined_blocks': - self._state = '{}'.format(stats.mined_blocks) - elif self.type == 'blocks_size': - self._state = '{0:.1f}'.format(stats.blocks_size) - elif self.type == 'total_fees_btc': - self._state = '{0:.2f}'.format(stats.total_fees_btc * 0.00000001) - elif self.type == 'total_btc_sent': - self._state = '{0:.2f}'.format(stats.total_btc_sent * 0.00000001) - elif self.type == 'estimated_btc_sent': - self._state = '{0:.2f}'.format( - stats.estimated_btc_sent * 0.00000001) - elif self.type == 'total_btc': - self._state = '{0:.2f}'.format(stats.total_btc * 0.00000001) - elif self.type == 'total_blocks': - self._state = '{0:.2f}'.format(stats.total_blocks) - elif self.type == 'next_retarget': - self._state = '{0:.2f}'.format(stats.next_retarget) - elif self.type == 'estimated_transaction_volume_usd': - self._state = '{0:.2f}'.format( - stats.estimated_transaction_volume_usd) - elif self.type == 'miners_revenue_btc': - self._state = '{0:.1f}'.format( - stats.miners_revenue_btc * 0.00000001) - elif self.type == 'market_price_usd': - self._state = '{0:.2f}'.format(stats.market_price_usd) + elif self.type == "mined_blocks": + self._state = "{}".format(stats.mined_blocks) + elif self.type == "blocks_size": + self._state = "{0:.1f}".format(stats.blocks_size) + elif self.type == "total_fees_btc": + self._state = "{0:.2f}".format(stats.total_fees_btc * 0.00000001) + elif self.type == "total_btc_sent": + self._state = "{0:.2f}".format(stats.total_btc_sent * 0.00000001) + elif self.type == "estimated_btc_sent": + self._state = "{0:.2f}".format(stats.estimated_btc_sent * 0.00000001) + elif self.type == "total_btc": + self._state = "{0:.2f}".format(stats.total_btc * 0.00000001) + elif self.type == "total_blocks": + self._state = "{0:.2f}".format(stats.total_blocks) + elif self.type == "next_retarget": + self._state = "{0:.2f}".format(stats.next_retarget) + elif self.type == "estimated_transaction_volume_usd": + self._state = "{0:.2f}".format(stats.estimated_transaction_volume_usd) + elif self.type == "miners_revenue_btc": + self._state = "{0:.1f}".format(stats.miners_revenue_btc * 0.00000001) + elif self.type == "market_price_usd": + self._state = "{0:.2f}".format(stats.market_price_usd) class BitcoinData: diff --git a/homeassistant/components/bizkaibus/sensor.py b/homeassistant/components/bizkaibus/sensor.py index 96e6ee5d56f..c54a61c66b1 100755 --- a/homeassistant/components/bizkaibus/sensor.py +++ b/homeassistant/components/bizkaibus/sensor.py @@ -13,18 +13,20 @@ from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -ATTR_DUE_IN = 'Due in' +ATTR_DUE_IN = "Due in" -CONF_STOP_ID = 'stopid' -CONF_ROUTE = 'route' +CONF_STOP_ID = "stopid" +CONF_ROUTE = "route" -DEFAULT_NAME = 'Next bus' +DEFAULT_NAME = "Next bus" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_STOP_ID): cv.string, - vol.Required(CONF_ROUTE): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_STOP_ID): cv.string, + vol.Required(CONF_ROUTE): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -61,7 +63,7 @@ class BizkaibusSensor(Entity): @property def unit_of_measurement(self): """Return the unit of measurement of the sensor.""" - return 'minutes' + return "minutes" def update(self): """Get the latest data from the webservice.""" diff --git a/homeassistant/components/blackbird/media_player.py b/homeassistant/components/blackbird/media_player.py index c603b51a244..a77fad69663 100644 --- a/homeassistant/components/blackbird/media_player.py +++ b/homeassistant/components/blackbird/media_player.py @@ -4,43 +4,45 @@ import socket import voluptuous as vol -from homeassistant.components.media_player import ( - MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA from homeassistant.components.media_player.const import ( - DOMAIN, SUPPORT_SELECT_SOURCE, - SUPPORT_TURN_OFF, SUPPORT_TURN_ON) + DOMAIN, + SUPPORT_SELECT_SOURCE, + SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, +) from homeassistant.const import ( - ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_PORT, CONF_TYPE, STATE_OFF, - STATE_ON) + ATTR_ENTITY_ID, + CONF_HOST, + CONF_NAME, + CONF_PORT, + CONF_TYPE, + STATE_OFF, + STATE_ON, +) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) SUPPORT_BLACKBIRD = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE -MEDIA_PLAYER_SCHEMA = vol.Schema({ - ATTR_ENTITY_ID: cv.comp_entity_ids, -}) +MEDIA_PLAYER_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.comp_entity_ids}) -ZONE_SCHEMA = vol.Schema({ - vol.Required(CONF_NAME): cv.string, -}) +ZONE_SCHEMA = vol.Schema({vol.Required(CONF_NAME): cv.string}) -SOURCE_SCHEMA = vol.Schema({ - vol.Required(CONF_NAME): cv.string, -}) +SOURCE_SCHEMA = vol.Schema({vol.Required(CONF_NAME): cv.string}) -CONF_ZONES = 'zones' -CONF_SOURCES = 'sources' +CONF_ZONES = "zones" +CONF_SOURCES = "sources" -DATA_BLACKBIRD = 'blackbird' +DATA_BLACKBIRD = "blackbird" -SERVICE_SETALLZONES = 'blackbird_set_all_zones' -ATTR_SOURCE = 'source' +SERVICE_SETALLZONES = "blackbird_set_all_zones" +ATTR_SOURCE = "source" -BLACKBIRD_SETALLZONES_SCHEMA = MEDIA_PLAYER_SCHEMA.extend({ - vol.Required(ATTR_SOURCE): cv.string -}) +BLACKBIRD_SETALLZONES_SCHEMA = MEDIA_PLAYER_SCHEMA.extend( + {vol.Required(ATTR_SOURCE): cv.string} +) # Valid zone ids: 1-8 @@ -51,12 +53,15 @@ SOURCE_IDS = vol.All(vol.Coerce(int), vol.Range(min=1, max=8)) PLATFORM_SCHEMA = vol.All( cv.has_at_least_one_key(CONF_PORT, CONF_HOST), - PLATFORM_SCHEMA.extend({ - vol.Exclusive(CONF_PORT, CONF_TYPE): cv.string, - vol.Exclusive(CONF_HOST, CONF_TYPE): cv.string, - vol.Required(CONF_ZONES): vol.Schema({ZONE_IDS: ZONE_SCHEMA}), - vol.Required(CONF_SOURCES): vol.Schema({SOURCE_IDS: SOURCE_SCHEMA}), - })) + PLATFORM_SCHEMA.extend( + { + vol.Exclusive(CONF_PORT, CONF_TYPE): cv.string, + vol.Exclusive(CONF_HOST, CONF_TYPE): cv.string, + vol.Required(CONF_ZONES): vol.Schema({ZONE_IDS: ZONE_SCHEMA}), + vol.Required(CONF_SOURCES): vol.Schema({SOURCE_IDS: SOURCE_SCHEMA}), + } + ), +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -87,8 +92,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): _LOGGER.error("Error connecting to the Blackbird controller") return - sources = {source_id: extra[CONF_NAME] for source_id, extra - in config[CONF_SOURCES].items()} + sources = { + source_id: extra[CONF_NAME] for source_id, extra in config[CONF_SOURCES].items() + } devices = [] for zone_id, extra in config[CONF_ZONES].items(): @@ -105,8 +111,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): entity_ids = service.data.get(ATTR_ENTITY_ID) source = service.data.get(ATTR_SOURCE) if entity_ids: - devices = [device for device in hass.data[DATA_BLACKBIRD].values() - if device.entity_id in entity_ids] + devices = [ + device + for device in hass.data[DATA_BLACKBIRD].values() + if device.entity_id in entity_ids + ] else: devices = hass.data[DATA_BLACKBIRD].values() @@ -115,8 +124,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if service.service == SERVICE_SETALLZONES: device.set_all_zones(source) - hass.services.register(DOMAIN, SERVICE_SETALLZONES, service_handle, - schema=BLACKBIRD_SETALLZONES_SCHEMA) + hass.services.register( + DOMAIN, SERVICE_SETALLZONES, service_handle, schema=BLACKBIRD_SETALLZONES_SCHEMA + ) class BlackbirdZone(MediaPlayerDevice): @@ -130,8 +140,9 @@ class BlackbirdZone(MediaPlayerDevice): # dict source name -> source_id self._source_name_id = {v: k for k, v in sources.items()} # ordered list of all source names - self._source_names = sorted(self._source_name_id.keys(), - key=lambda v: self._source_name_id[v]) + self._source_names = sorted( + self._source_name_id.keys(), key=lambda v: self._source_name_id[v] + ) self._zone_id = zone_id self._name = zone_name self._state = None diff --git a/homeassistant/components/blink/__init__.py b/homeassistant/components/blink/__init__.py index 74057c7b6bc..bd11572ba1c 100644 --- a/homeassistant/components/blink/__init__.py +++ b/homeassistant/components/blink/__init__.py @@ -3,106 +3,122 @@ import logging from datetime import timedelta import voluptuous as vol -from homeassistant.helpers import ( - config_validation as cv, discovery) +from homeassistant.helpers import config_validation as cv, discovery from homeassistant.const import ( - CONF_USERNAME, CONF_PASSWORD, CONF_NAME, CONF_SCAN_INTERVAL, - CONF_BINARY_SENSORS, CONF_SENSORS, CONF_FILENAME, - CONF_MONITORED_CONDITIONS, CONF_MODE, CONF_OFFSET, TEMP_FAHRENHEIT) + CONF_USERNAME, + CONF_PASSWORD, + CONF_NAME, + CONF_SCAN_INTERVAL, + CONF_BINARY_SENSORS, + CONF_SENSORS, + CONF_FILENAME, + CONF_MONITORED_CONDITIONS, + CONF_MODE, + CONF_OFFSET, + TEMP_FAHRENHEIT, +) _LOGGER = logging.getLogger(__name__) -DOMAIN = 'blink' -BLINK_DATA = 'blink' +DOMAIN = "blink" +BLINK_DATA = "blink" -CONF_CAMERA = 'camera' -CONF_ALARM_CONTROL_PANEL = 'alarm_control_panel' +CONF_CAMERA = "camera" +CONF_ALARM_CONTROL_PANEL = "alarm_control_panel" -DEFAULT_BRAND = 'Blink' +DEFAULT_BRAND = "Blink" DEFAULT_ATTRIBUTION = "Data provided by immedia-semi.com" SIGNAL_UPDATE_BLINK = "blink_update" DEFAULT_SCAN_INTERVAL = timedelta(seconds=300) -TYPE_CAMERA_ARMED = 'motion_enabled' -TYPE_MOTION_DETECTED = 'motion_detected' -TYPE_TEMPERATURE = 'temperature' -TYPE_BATTERY = 'battery' -TYPE_WIFI_STRENGTH = 'wifi_strength' +TYPE_CAMERA_ARMED = "motion_enabled" +TYPE_MOTION_DETECTED = "motion_detected" +TYPE_TEMPERATURE = "temperature" +TYPE_BATTERY = "battery" +TYPE_WIFI_STRENGTH = "wifi_strength" -SERVICE_REFRESH = 'blink_update' -SERVICE_TRIGGER = 'trigger_camera' -SERVICE_SAVE_VIDEO = 'save_video' +SERVICE_REFRESH = "blink_update" +SERVICE_TRIGGER = "trigger_camera" +SERVICE_SAVE_VIDEO = "save_video" BINARY_SENSORS = { - TYPE_CAMERA_ARMED: ['Camera Armed', 'mdi:verified'], - TYPE_MOTION_DETECTED: ['Motion Detected', 'mdi:run-fast'], + TYPE_CAMERA_ARMED: ["Camera Armed", "mdi:verified"], + TYPE_MOTION_DETECTED: ["Motion Detected", "mdi:run-fast"], } SENSORS = { - TYPE_TEMPERATURE: ['Temperature', TEMP_FAHRENHEIT, 'mdi:thermometer'], - TYPE_BATTERY: ['Battery', '', 'mdi:battery-80'], - TYPE_WIFI_STRENGTH: ['Wifi Signal', 'dBm', 'mdi:wifi-strength-2'], + TYPE_TEMPERATURE: ["Temperature", TEMP_FAHRENHEIT, "mdi:thermometer"], + TYPE_BATTERY: ["Battery", "", "mdi:battery-80"], + TYPE_WIFI_STRENGTH: ["Wifi Signal", "dBm", "mdi:wifi-strength-2"], } -BINARY_SENSOR_SCHEMA = vol.Schema({ - vol.Optional(CONF_MONITORED_CONDITIONS, default=list(BINARY_SENSORS)): - vol.All(cv.ensure_list, [vol.In(BINARY_SENSORS)]) -}) +BINARY_SENSOR_SCHEMA = vol.Schema( + { + vol.Optional(CONF_MONITORED_CONDITIONS, default=list(BINARY_SENSORS)): vol.All( + cv.ensure_list, [vol.In(BINARY_SENSORS)] + ) + } +) -SENSOR_SCHEMA = vol.Schema({ - vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSORS)): - vol.All(cv.ensure_list, [vol.In(SENSORS)]) -}) +SENSOR_SCHEMA = vol.Schema( + { + vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSORS)): vol.All( + cv.ensure_list, [vol.In(SENSORS)] + ) + } +) -SERVICE_TRIGGER_SCHEMA = vol.Schema({ - vol.Required(CONF_NAME): cv.string -}) +SERVICE_TRIGGER_SCHEMA = vol.Schema({vol.Required(CONF_NAME): cv.string}) -SERVICE_SAVE_VIDEO_SCHEMA = vol.Schema({ - vol.Required(CONF_NAME): cv.string, - vol.Required(CONF_FILENAME): cv.string, -}) +SERVICE_SAVE_VIDEO_SCHEMA = vol.Schema( + {vol.Required(CONF_NAME): cv.string, vol.Required(CONF_FILENAME): cv.string} +) CONFIG_SCHEMA = vol.Schema( { - DOMAIN: - vol.Schema({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL): - cv.time_period, - vol.Optional(CONF_BINARY_SENSORS, default={}): - BINARY_SENSOR_SCHEMA, - vol.Optional(CONF_SENSORS, default={}): SENSOR_SCHEMA, - vol.Optional(CONF_OFFSET, default=1): int, - vol.Optional(CONF_MODE, default=''): cv.string, - }) + DOMAIN: vol.Schema( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional( + CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL + ): cv.time_period, + vol.Optional(CONF_BINARY_SENSORS, default={}): BINARY_SENSOR_SCHEMA, + vol.Optional(CONF_SENSORS, default={}): SENSOR_SCHEMA, + vol.Optional(CONF_OFFSET, default=1): int, + vol.Optional(CONF_MODE, default=""): cv.string, + } + ) }, - extra=vol.ALLOW_EXTRA) + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): """Set up Blink System.""" from blinkpy import blinkpy + conf = config[BLINK_DATA] username = conf[CONF_USERNAME] password = conf[CONF_PASSWORD] scan_interval = conf[CONF_SCAN_INTERVAL] - is_legacy = bool(conf[CONF_MODE] == 'legacy') + is_legacy = bool(conf[CONF_MODE] == "legacy") motion_interval = conf[CONF_OFFSET] - hass.data[BLINK_DATA] = blinkpy.Blink(username=username, - password=password, - motion_interval=motion_interval, - legacy_subdomain=is_legacy) + hass.data[BLINK_DATA] = blinkpy.Blink( + username=username, + password=password, + motion_interval=motion_interval, + legacy_subdomain=is_legacy, + ) hass.data[BLINK_DATA].refresh_rate = scan_interval.total_seconds() hass.data[BLINK_DATA].start() platforms = [ - ('alarm_control_panel', {}), - ('binary_sensor', conf[CONF_BINARY_SENSORS]), - ('camera', {}), - ('sensor', conf[CONF_SENSORS]), + ("alarm_control_panel", {}), + ("binary_sensor", conf[CONF_BINARY_SENSORS]), + ("camera", {}), + ("sensor", conf[CONF_SENSORS]), ] for component, schema in platforms: @@ -125,14 +141,12 @@ def setup(hass, config): await async_handle_save_video_service(hass, call) hass.services.register(DOMAIN, SERVICE_REFRESH, blink_refresh) - hass.services.register(DOMAIN, - SERVICE_TRIGGER, - trigger_camera, - schema=SERVICE_TRIGGER_SCHEMA) - hass.services.register(DOMAIN, - SERVICE_SAVE_VIDEO, - async_save_video, - schema=SERVICE_SAVE_VIDEO_SCHEMA) + hass.services.register( + DOMAIN, SERVICE_TRIGGER, trigger_camera, schema=SERVICE_TRIGGER_SCHEMA + ) + hass.services.register( + DOMAIN, SERVICE_SAVE_VIDEO, async_save_video, schema=SERVICE_SAVE_VIDEO_SCHEMA + ) return True @@ -141,8 +155,7 @@ async def async_handle_save_video_service(hass, call): camera_name = call.data[CONF_NAME] video_path = call.data[CONF_FILENAME] if not hass.config.is_allowed_path(video_path): - _LOGGER.error( - "Can't write %s, no access to path!", video_path) + _LOGGER.error("Can't write %s, no access to path!", video_path) return def _write_video(camera_name, video_path): @@ -152,7 +165,6 @@ async def async_handle_save_video_service(hass, call): all_cameras[camera_name].video_to_file(video_path) try: - await hass.async_add_executor_job( - _write_video, camera_name, video_path) + await hass.async_add_executor_job(_write_video, camera_name, video_path) except OSError as err: _LOGGER.error("Can't write image to file: %s", err) diff --git a/homeassistant/components/blink/alarm_control_panel.py b/homeassistant/components/blink/alarm_control_panel.py index 8cc89d90b2f..adcefeddf23 100644 --- a/homeassistant/components/blink/alarm_control_panel.py +++ b/homeassistant/components/blink/alarm_control_panel.py @@ -3,13 +3,16 @@ import logging from homeassistant.components.alarm_control_panel import AlarmControlPanel from homeassistant.const import ( - ATTR_ATTRIBUTION, STATE_ALARM_ARMED_AWAY, STATE_ALARM_DISARMED) + ATTR_ATTRIBUTION, + STATE_ALARM_ARMED_AWAY, + STATE_ALARM_DISARMED, +) from . import BLINK_DATA, DEFAULT_ATTRIBUTION _LOGGER = logging.getLogger(__name__) -ICON = 'mdi:security' +ICON = "mdi:security" def setup_platform(hass, config, add_entities, discovery_info=None): @@ -58,8 +61,8 @@ class BlinkSyncModule(AlarmControlPanel): def device_state_attributes(self): """Return the state attributes.""" attr = self.sync.attributes - attr['network_info'] = self.data.networks - attr['associated_cameras'] = list(self.sync.cameras.keys()) + attr["network_info"] = self.data.networks + attr["associated_cameras"] = list(self.sync.cameras.keys()) attr[ATTR_ATTRIBUTION] = DEFAULT_ATTRIBUTION return attr diff --git a/homeassistant/components/blink/camera.py b/homeassistant/components/blink/camera.py index d1301319a81..5e8b5323f89 100644 --- a/homeassistant/components/blink/camera.py +++ b/homeassistant/components/blink/camera.py @@ -7,8 +7,8 @@ from . import BLINK_DATA, DEFAULT_BRAND _LOGGER = logging.getLogger(__name__) -ATTR_VIDEO_CLIP = 'video' -ATTR_IMAGE = 'image' +ATTR_VIDEO_CLIP = "video" +ATTR_IMAGE = "image" def setup_platform(hass, config, add_entities, discovery_info=None): diff --git a/homeassistant/components/blink/sensor.py b/homeassistant/components/blink/sensor.py index 6fb8be8e4ea..fba2d0bd493 100644 --- a/homeassistant/components/blink/sensor.py +++ b/homeassistant/components/blink/sensor.py @@ -28,8 +28,7 @@ class BlinkSensor(Entity): def __init__(self, data, camera, sensor_type): """Initialize sensors from Blink camera.""" name, units, icon = SENSORS[sensor_type] - self._name = "{} {} {}".format( - BLINK_DATA, camera, name) + self._name = "{} {} {}".format(BLINK_DATA, camera, name) self._camera_name = name self._type = sensor_type self.data = data @@ -39,8 +38,8 @@ class BlinkSensor(Entity): self._icon = icon self._unique_id = "{}-{}".format(self._camera.serial, self._type) self._sensor_key = self._type - if self._type == 'temperature': - self._sensor_key = 'temperature_calibrated' + if self._type == "temperature": + self._sensor_key = "temperature_calibrated" @property def name(self): @@ -75,5 +74,5 @@ class BlinkSensor(Entity): except KeyError: self._state = None _LOGGER.error( - "%s not a valid camera attribute. Did the API change?", - self._sensor_key) + "%s not a valid camera attribute. Did the API change?", self._sensor_key + ) diff --git a/homeassistant/components/blinksticklight/light.py b/homeassistant/components/blinksticklight/light.py index 8eab6afaeb7..5f3cb7ebfd1 100644 --- a/homeassistant/components/blinksticklight/light.py +++ b/homeassistant/components/blinksticklight/light.py @@ -4,24 +4,31 @@ import logging import voluptuous as vol from homeassistant.components.light import ( - ATTR_BRIGHTNESS, ATTR_HS_COLOR, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, Light, - PLATFORM_SCHEMA) + ATTR_BRIGHTNESS, + ATTR_HS_COLOR, + SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, + Light, + PLATFORM_SCHEMA, +) from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv import homeassistant.util.color as color_util _LOGGER = logging.getLogger(__name__) -CONF_SERIAL = 'serial' +CONF_SERIAL = "serial" -DEFAULT_NAME = 'Blinkstick' +DEFAULT_NAME = "Blinkstick" SUPPORT_BLINKSTICK = SUPPORT_BRIGHTNESS | SUPPORT_COLOR -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_SERIAL): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_SERIAL): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -94,9 +101,9 @@ class BlinkStickLight(Light): self._brightness = 255 rgb_color = color_util.color_hsv_to_RGB( - self._hs_color[0], self._hs_color[1], self._brightness / 255 * 100) - self._stick.set_color( - red=rgb_color[0], green=rgb_color[1], blue=rgb_color[2]) + self._hs_color[0], self._hs_color[1], self._brightness / 255 * 100 + ) + self._stick.set_color(red=rgb_color[0], green=rgb_color[1], blue=rgb_color[2]) def turn_off(self, **kwargs): """Turn the device off.""" diff --git a/homeassistant/components/blinkt/light.py b/homeassistant/components/blinkt/light.py index cb3e854b388..9fee72662c6 100644 --- a/homeassistant/components/blinkt/light.py +++ b/homeassistant/components/blinkt/light.py @@ -6,35 +6,40 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.light import ( - ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, ATTR_HS_COLOR, SUPPORT_COLOR, - Light, PLATFORM_SCHEMA) + ATTR_BRIGHTNESS, + SUPPORT_BRIGHTNESS, + ATTR_HS_COLOR, + SUPPORT_COLOR, + Light, + PLATFORM_SCHEMA, +) from homeassistant.const import CONF_NAME import homeassistant.util.color as color_util _LOGGER = logging.getLogger(__name__) -SUPPORT_BLINKT = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR) +SUPPORT_BLINKT = SUPPORT_BRIGHTNESS | SUPPORT_COLOR -DEFAULT_NAME = 'blinkt' +DEFAULT_NAME = "blinkt" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string} +) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Blinkt Light platform.""" # pylint: disable=no-member - blinkt = importlib.import_module('blinkt') + blinkt = importlib.import_module("blinkt") # ensure that the lights are off when exiting blinkt.set_clear_on_exit() name = config.get(CONF_NAME) - add_entities([ - BlinktLight(blinkt, name, index) for index in range(blinkt.NUM_PIXELS) - ]) + add_entities( + [BlinktLight(blinkt, name, index) for index in range(blinkt.NUM_PIXELS)] + ) class BlinktLight(Light): @@ -97,13 +102,11 @@ class BlinktLight(Light): if ATTR_BRIGHTNESS in kwargs: self._brightness = kwargs[ATTR_BRIGHTNESS] - percent_bright = (self._brightness / 255) + percent_bright = self._brightness / 255 rgb_color = color_util.color_hs_to_RGB(*self._hs_color) - self._blinkt.set_pixel(self._index, - rgb_color[0], - rgb_color[1], - rgb_color[2], - percent_bright) + self._blinkt.set_pixel( + self._index, rgb_color[0], rgb_color[1], rgb_color[2], percent_bright + ) self._blinkt.show() diff --git a/homeassistant/components/blockchain/sensor.py b/homeassistant/components/blockchain/sensor.py index 436e2979a6e..c95ccb3fed3 100644 --- a/homeassistant/components/blockchain/sensor.py +++ b/homeassistant/components/blockchain/sensor.py @@ -6,25 +6,27 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import (CONF_NAME, ATTR_ATTRIBUTION) +from homeassistant.const import CONF_NAME, ATTR_ATTRIBUTION from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) ATTRIBUTION = "Data provided by blockchain.info" -CONF_ADDRESSES = 'addresses' +CONF_ADDRESSES = "addresses" -DEFAULT_NAME = 'Bitcoin Balance' +DEFAULT_NAME = "Bitcoin Balance" -ICON = 'mdi:currency-btc' +ICON = "mdi:currency-btc" SCAN_INTERVAL = timedelta(minutes=5) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_ADDRESSES): [cv.string], - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_ADDRESSES): [cv.string], + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -50,7 +52,7 @@ class BlockchainSensor(Entity): self._name = name self.addresses = addresses self._state = None - self._unit_of_measurement = 'BTC' + self._unit_of_measurement = "BTC" @property def name(self): @@ -75,11 +77,10 @@ class BlockchainSensor(Entity): @property def device_state_attributes(self): """Return the state attributes of the sensor.""" - return { - ATTR_ATTRIBUTION: ATTRIBUTION, - } + return {ATTR_ATTRIBUTION: ATTRIBUTION} def update(self): """Get the latest state of the sensor.""" from pyblockchain import get_balance + self._state = get_balance(self.addresses) diff --git a/homeassistant/components/bloomsky/__init__.py b/homeassistant/components/bloomsky/__init__.py index 7a48d001689..dc0723730c4 100644 --- a/homeassistant/components/bloomsky/__init__.py +++ b/homeassistant/components/bloomsky/__init__.py @@ -14,19 +14,17 @@ from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) BLOOMSKY = None -BLOOMSKY_TYPE = ['camera', 'binary_sensor', 'sensor'] +BLOOMSKY_TYPE = ["camera", "binary_sensor", "sensor"] -DOMAIN = 'bloomsky' +DOMAIN = "bloomsky" # The BloomSky only updates every 5-8 minutes as per the API spec so there's # no point in polling the API more frequently MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_API_KEY): cv.string, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + {DOMAIN: vol.Schema({vol.Required(CONF_API_KEY): cv.string})}, extra=vol.ALLOW_EXTRA +) def setup(hass, config): @@ -49,12 +47,12 @@ class BloomSky: """Handle all communication with the BloomSky API.""" # API documentation at http://weatherlution.com/bloomsky-api/ - API_URL = 'http://api.bloomsky.com/api/skydata' + API_URL = "http://api.bloomsky.com/api/skydata" def __init__(self, api_key, is_metric): """Initialize the BookSky.""" self._api_key = api_key - self._endpoint_argument = 'unit=intl' if is_metric else '' + self._endpoint_argument = "unit=intl" if is_metric else "" self.devices = {} self.is_metric = is_metric _LOGGER.debug("Initial BloomSky device load...") @@ -66,7 +64,9 @@ class BloomSky: _LOGGER.debug("Fetching BloomSky update") response = requests.get( "{}?{}".format(self.API_URL, self._endpoint_argument), - headers={AUTHORIZATION: self._api_key}, timeout=10) + headers={AUTHORIZATION: self._api_key}, + timeout=10, + ) if response.status_code == 401: raise RuntimeError("Invalid API_KEY") if response.status_code == 405: @@ -76,6 +76,4 @@ class BloomSky: _LOGGER.error("Invalid HTTP response: %s", response.status_code) return # Create dictionary keyed off of the device unique id - self.devices.update({ - device['DeviceID']: device for device in response.json() - }) + self.devices.update({device["DeviceID"]: device for device in response.json()}) diff --git a/homeassistant/components/bloomsky/binary_sensor.py b/homeassistant/components/bloomsky/binary_sensor.py index b17c4e4c257..3a8242929c5 100644 --- a/homeassistant/components/bloomsky/binary_sensor.py +++ b/homeassistant/components/bloomsky/binary_sensor.py @@ -3,8 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) +from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA from homeassistant.const import CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv @@ -12,15 +11,15 @@ from . import BLOOMSKY _LOGGER = logging.getLogger(__name__) -SENSOR_TYPES = { - 'Rain': 'moisture', - 'Night': None, -} +SENSOR_TYPES = {"Rain": "moisture", "Night": None} -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)): - vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)): vol.All( + cv.ensure_list, [vol.In(SENSOR_TYPES)] + ) + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -30,8 +29,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for device in BLOOMSKY.devices.values(): for variable in sensors: - add_entities( - [BloomSkySensor(BLOOMSKY, device, variable)], True) + add_entities([BloomSkySensor(BLOOMSKY, device, variable)], True) class BloomSkySensor(BinarySensorDevice): @@ -40,11 +38,11 @@ class BloomSkySensor(BinarySensorDevice): def __init__(self, bs, device, sensor_name): """Initialize a BloomSky binary sensor.""" self._bloomsky = bs - self._device_id = device['DeviceID'] + self._device_id = device["DeviceID"] self._sensor_name = sensor_name - self._name = '{} {}'.format(device['DeviceName'], sensor_name) + self._name = "{} {}".format(device["DeviceName"], sensor_name) self._state = None - self._unique_id = '{}-{}'.format(self._device_id, self._sensor_name) + self._unique_id = "{}-{}".format(self._device_id, self._sensor_name) @property def unique_id(self): @@ -70,5 +68,4 @@ class BloomSkySensor(BinarySensorDevice): """Request an update from the BloomSky API.""" self._bloomsky.refresh_devices() - self._state = \ - self._bloomsky.devices[self._device_id]['Data'][self._sensor_name] + self._state = self._bloomsky.devices[self._device_id]["Data"][self._sensor_name] diff --git a/homeassistant/components/bloomsky/camera.py b/homeassistant/components/bloomsky/camera.py index a748ff2b5b8..9b8c4ab283f 100644 --- a/homeassistant/components/bloomsky/camera.py +++ b/homeassistant/components/bloomsky/camera.py @@ -20,8 +20,8 @@ class BloomSkyCamera(Camera): def __init__(self, bs, device): """Initialize access to the BloomSky camera images.""" super(BloomSkyCamera, self).__init__() - self._name = device['DeviceName'] - self._id = device['DeviceID'] + self._name = device["DeviceName"] + self._id = device["DeviceID"] self._bloomsky = bs self._url = "" self._last_url = "" @@ -34,7 +34,7 @@ class BloomSkyCamera(Camera): def camera_image(self): """Update the camera's image if it has changed.""" try: - self._url = self._bloomsky.devices[self._id]['Data']['ImageURL'] + self._url = self._bloomsky.devices[self._id]["Data"]["ImageURL"] self._bloomsky.refresh_devices() # If the URL hasn't changed then the image hasn't changed. if self._url != self._last_url: diff --git a/homeassistant/components/bloomsky/sensor.py b/homeassistant/components/bloomsky/sensor.py index 6dc679a173b..cca57bcae82 100644 --- a/homeassistant/components/bloomsky/sensor.py +++ b/homeassistant/components/bloomsky/sensor.py @@ -4,9 +4,7 @@ import logging import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import (TEMP_FAHRENHEIT, - TEMP_CELSIUS, - CONF_MONITORED_CONDITIONS) +from homeassistant.const import TEMP_FAHRENHEIT, TEMP_CELSIUS, CONF_MONITORED_CONDITIONS from homeassistant.helpers.entity import Entity import homeassistant.helpers.config_validation as cv @@ -15,34 +13,43 @@ from . import BLOOMSKY LOGGER = logging.getLogger(__name__) # These are the available sensors -SENSOR_TYPES = ['Temperature', - 'Humidity', - 'Pressure', - 'Luminance', - 'UVIndex', - 'Voltage'] +SENSOR_TYPES = [ + "Temperature", + "Humidity", + "Pressure", + "Luminance", + "UVIndex", + "Voltage", +] # Sensor units - these do not currently align with the API documentation -SENSOR_UNITS_IMPERIAL = {'Temperature': TEMP_FAHRENHEIT, - 'Humidity': '%', - 'Pressure': 'inHg', - 'Luminance': 'cd/m²', - 'Voltage': 'mV'} +SENSOR_UNITS_IMPERIAL = { + "Temperature": TEMP_FAHRENHEIT, + "Humidity": "%", + "Pressure": "inHg", + "Luminance": "cd/m²", + "Voltage": "mV", +} # Metric units -SENSOR_UNITS_METRIC = {'Temperature': TEMP_CELSIUS, - 'Humidity': '%', - 'Pressure': 'mbar', - 'Luminance': 'cd/m²', - 'Voltage': 'mV'} +SENSOR_UNITS_METRIC = { + "Temperature": TEMP_CELSIUS, + "Humidity": "%", + "Pressure": "mbar", + "Luminance": "cd/m²", + "Voltage": "mV", +} # Which sensors to format numerically -FORMAT_NUMBERS = ['Temperature', 'Pressure', 'Voltage'] +FORMAT_NUMBERS = ["Temperature", "Pressure", "Voltage"] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_MONITORED_CONDITIONS, default=SENSOR_TYPES): - vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_MONITORED_CONDITIONS, default=SENSOR_TYPES): vol.All( + cv.ensure_list, [vol.In(SENSOR_TYPES)] + ) + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -52,8 +59,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for device in BLOOMSKY.devices.values(): for variable in sensors: - add_entities( - [BloomSkySensor(BLOOMSKY, device, variable)], True) + add_entities([BloomSkySensor(BLOOMSKY, device, variable)], True) class BloomSkySensor(Entity): @@ -62,11 +68,11 @@ class BloomSkySensor(Entity): def __init__(self, bs, device, sensor_name): """Initialize a BloomSky sensor.""" self._bloomsky = bs - self._device_id = device['DeviceID'] + self._device_id = device["DeviceID"] self._sensor_name = sensor_name - self._name = '{} {}'.format(device['DeviceName'], sensor_name) + self._name = "{} {}".format(device["DeviceName"], sensor_name) self._state = None - self._unique_id = '{}-{}'.format(self._device_id, self._sensor_name) + self._unique_id = "{}-{}".format(self._device_id, self._sensor_name) @property def unique_id(self): @@ -94,10 +100,9 @@ class BloomSkySensor(Entity): """Request an update from the BloomSky API.""" self._bloomsky.refresh_devices() - state = \ - self._bloomsky.devices[self._device_id]['Data'][self._sensor_name] + state = self._bloomsky.devices[self._device_id]["Data"][self._sensor_name] if self._sensor_name in FORMAT_NUMBERS: - self._state = '{0:.2f}'.format(state) + self._state = "{0:.2f}".format(state) else: self._state = state diff --git a/homeassistant/components/bluesound/media_player.py b/homeassistant/components/bluesound/media_player.py index 2a3b3e35125..e5f264b5f73 100644 --- a/homeassistant/components/bluesound/media_player.py +++ b/homeassistant/components/bluesound/media_player.py @@ -10,18 +10,38 @@ from aiohttp.hdrs import CONNECTION, KEEP_ALIVE import async_timeout import voluptuous as vol -from homeassistant.components.media_player import ( - MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA from homeassistant.components.media_player.const import ( - ATTR_MEDIA_ENQUEUE, DOMAIN, MEDIA_TYPE_MUSIC, - SUPPORT_CLEAR_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, - SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET, SUPPORT_STOP, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP) + ATTR_MEDIA_ENQUEUE, + DOMAIN, + MEDIA_TYPE_MUSIC, + SUPPORT_CLEAR_PLAYLIST, + SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, + SUPPORT_PLAY, + SUPPORT_PLAY_MEDIA, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_SEEK, + SUPPORT_SELECT_SOURCE, + SUPPORT_SHUFFLE_SET, + SUPPORT_STOP, + SUPPORT_VOLUME_MUTE, + SUPPORT_VOLUME_SET, + SUPPORT_VOLUME_STEP, +) from homeassistant.const import ( - ATTR_ENTITY_ID, CONF_HOST, CONF_HOSTS, CONF_NAME, CONF_PORT, - EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, STATE_IDLE, STATE_OFF, - STATE_PAUSED, STATE_PLAYING) + ATTR_ENTITY_ID, + CONF_HOST, + CONF_HOSTS, + CONF_NAME, + CONF_PORT, + EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP, + STATE_IDLE, + STATE_OFF, + STATE_PAUSED, + STATE_PLAYING, +) from homeassistant.core import callback from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv @@ -31,54 +51,49 @@ import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) -ATTR_MASTER = 'master' +ATTR_MASTER = "master" -DATA_BLUESOUND = 'bluesound' +DATA_BLUESOUND = "bluesound" DEFAULT_PORT = 11000 NODE_OFFLINE_CHECK_TIMEOUT = 180 NODE_RETRY_INITIATION = timedelta(minutes=3) -SERVICE_CLEAR_TIMER = 'bluesound_clear_sleep_timer' -SERVICE_JOIN = 'bluesound_join' -SERVICE_SET_TIMER = 'bluesound_set_sleep_timer' -SERVICE_UNJOIN = 'bluesound_unjoin' -STATE_GROUPED = 'grouped' +SERVICE_CLEAR_TIMER = "bluesound_clear_sleep_timer" +SERVICE_JOIN = "bluesound_join" +SERVICE_SET_TIMER = "bluesound_set_sleep_timer" +SERVICE_UNJOIN = "bluesound_unjoin" +STATE_GROUPED = "grouped" SYNC_STATUS_INTERVAL = timedelta(minutes=5) UPDATE_CAPTURE_INTERVAL = timedelta(minutes=30) UPDATE_PRESETS_INTERVAL = timedelta(minutes=30) UPDATE_SERVICES_INTERVAL = timedelta(minutes=30) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_HOSTS): vol.All(cv.ensure_list, [{ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - }]) -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_HOSTS): vol.All( + cv.ensure_list, + [ + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + } + ], + ) + } +) -BS_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, -}) +BS_SCHEMA = vol.Schema({vol.Optional(ATTR_ENTITY_ID): cv.entity_ids}) -BS_JOIN_SCHEMA = BS_SCHEMA.extend({ - vol.Required(ATTR_MASTER): cv.entity_id, -}) +BS_JOIN_SCHEMA = BS_SCHEMA.extend({vol.Required(ATTR_MASTER): cv.entity_id}) SERVICE_TO_METHOD = { - SERVICE_JOIN: { - 'method': 'async_join', - 'schema': BS_JOIN_SCHEMA}, - SERVICE_UNJOIN: { - 'method': 'async_unjoin', - 'schema': BS_SCHEMA}, - SERVICE_SET_TIMER: { - 'method': 'async_increase_timer', - 'schema': BS_SCHEMA}, - SERVICE_CLEAR_TIMER: { - 'method': 'async_clear_timer', - 'schema': BS_SCHEMA} + SERVICE_JOIN: {"method": "async_join", "schema": BS_JOIN_SCHEMA}, + SERVICE_UNJOIN: {"method": "async_unjoin", "schema": BS_SCHEMA}, + SERVICE_SET_TIMER: {"method": "async_increase_timer", "schema": BS_SCHEMA}, + SERVICE_CLEAR_TIMER: {"method": "async_clear_timer", "schema": BS_SCHEMA}, } @@ -111,8 +126,7 @@ def _add_player(hass, async_add_entities, host, port=None, name=None): if hass.is_running: _start_polling() else: - hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_START, _start_polling) + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, _start_polling) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _stop_polling) @@ -125,23 +139,30 @@ def _add_player(hass, async_add_entities, host, port=None, name=None): hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, _init_player) -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Bluesound platforms.""" if DATA_BLUESOUND not in hass.data: hass.data[DATA_BLUESOUND] = [] if discovery_info: - _add_player(hass, async_add_entities, discovery_info.get(CONF_HOST), - discovery_info.get(CONF_PORT, None)) + _add_player( + hass, + async_add_entities, + discovery_info.get(CONF_HOST), + discovery_info.get(CONF_PORT, None), + ) return hosts = config.get(CONF_HOSTS, None) if hosts: for host in hosts: _add_player( - hass, async_add_entities, host.get(CONF_HOST), - host.get(CONF_PORT), host.get(CONF_NAME)) + hass, + async_add_entities, + host.get(CONF_HOST), + host.get(CONF_PORT), + host.get(CONF_NAME), + ) async def async_service_handler(service): """Map services to method of Bluesound devices.""" @@ -149,22 +170,27 @@ async def async_setup_platform( if not method: return - params = {key: value for key, value in service.data.items() - if key != ATTR_ENTITY_ID} + params = { + key: value for key, value in service.data.items() if key != ATTR_ENTITY_ID + } entity_ids = service.data.get(ATTR_ENTITY_ID) if entity_ids: - target_players = [player for player in hass.data[DATA_BLUESOUND] - if player.entity_id in entity_ids] + target_players = [ + player + for player in hass.data[DATA_BLUESOUND] + if player.entity_id in entity_ids + ] else: target_players = hass.data[DATA_BLUESOUND] for player in target_players: - await getattr(player, method['method'])(**params) + await getattr(player, method["method"])(**params) for service in SERVICE_TO_METHOD: - schema = SERVICE_TO_METHOD[service]['schema'] + schema = SERVICE_TO_METHOD[service]["schema"] hass.services.async_register( - DOMAIN, service, async_service_handler, schema=schema) + DOMAIN, service, async_service_handler, schema=schema + ) class BluesoundPlayer(MediaPlayerDevice): @@ -207,28 +233,30 @@ class BluesoundPlayer(MediaPlayerDevice): except ValueError: return -1 - async def force_update_sync_status( - self, on_updated_cb=None, raise_timeout=False): + async def force_update_sync_status(self, on_updated_cb=None, raise_timeout=False): """Update the internal status.""" resp = await self.send_bluesound_command( - 'SyncStatus', raise_timeout, raise_timeout) + "SyncStatus", raise_timeout, raise_timeout + ) if not resp: return None - self._sync_status = resp['SyncStatus'].copy() + self._sync_status = resp["SyncStatus"].copy() if not self._name: - self._name = self._sync_status.get('@name', self.host) + self._name = self._sync_status.get("@name", self.host) if not self._icon: - self._icon = self._sync_status.get('@icon', self.host) + self._icon = self._sync_status.get("@icon", self.host) - master = self._sync_status.get('master', None) + master = self._sync_status.get("master", None) if master is not None: self._is_master = False - master_host = master.get('#text') - master_device = [device for device in - self._hass.data[DATA_BLUESOUND] - if device.host == master_host] + master_host = master.get("#text") + master_device = [ + device + for device in self._hass.data[DATA_BLUESOUND] + if device.host == master_host + ] if master_device and master_host != self.host: self._master = master_device[0] @@ -238,7 +266,7 @@ class BluesoundPlayer(MediaPlayerDevice): else: if self._master is not None: self._master = None - slaves = self._sync_status.get('slave', None) + slaves = self._sync_status.get("slave", None) self._is_master = slaves is not None if on_updated_cb: @@ -251,11 +279,9 @@ class BluesoundPlayer(MediaPlayerDevice): while True: await self.async_update_status() - except (asyncio.TimeoutError, ClientError, - BluesoundPlayer._TimeoutException): + except (asyncio.TimeoutError, ClientError, BluesoundPlayer._TimeoutException): _LOGGER.info("Node %s is offline, retrying later", self._name) - await asyncio.sleep( - NODE_OFFLINE_CHECK_TIMEOUT) + await asyncio.sleep(NODE_OFFLINE_CHECK_TIMEOUT) self.start_polling() except CancelledError: @@ -266,8 +292,7 @@ class BluesoundPlayer(MediaPlayerDevice): def start_polling(self): """Start the polling task.""" - self._polling_task = self._hass.async_create_task( - self._start_poll_command()) + self._polling_task = self._hass.async_create_task(self._start_poll_command()) def stop_polling(self): """Stop the polling task.""" @@ -280,15 +305,14 @@ class BluesoundPlayer(MediaPlayerDevice): self._retry_remove() self._retry_remove = None - await self.force_update_sync_status( - self._init_callback, True) + await self.force_update_sync_status(self._init_callback, True) except (asyncio.TimeoutError, ClientError): _LOGGER.info("Node %s is offline, retrying later", self.host) self._retry_remove = async_track_time_interval( - self._hass, self.async_init, NODE_RETRY_INITIATION) + self._hass, self.async_init, NODE_RETRY_INITIATION + ) except Exception: - _LOGGER.exception( - "Unexpected when initiating error in %s", self.host) + _LOGGER.exception("Unexpected when initiating error in %s", self.host) raise async def async_update(self): @@ -302,14 +326,15 @@ class BluesoundPlayer(MediaPlayerDevice): await self.async_update_services() async def send_bluesound_command( - self, method, raise_timeout=False, allow_offline=False): + self, method, raise_timeout=False, allow_offline=False + ): """Send command to the player.""" import xmltodict if not self._is_online and not allow_offline: return - if method[0] == '/': + if method[0] == "/": method = method[1:] url = "http://{}:{}/{}".format(self.host, self.port, method) @@ -346,15 +371,16 @@ class BluesoundPlayer(MediaPlayerDevice): async def async_update_status(self): """Use the poll session to always get the status of the player.""" import xmltodict + response = None - url = 'Status' - etag = '' + url = "Status" + etag = "" if self._status is not None: - etag = self._status.get('@etag', '') + etag = self._status.get("@etag", "") - if etag != '': - url = 'Status?etag={}&timeout=120.0'.format(etag) + if etag != "": + url = "Status?etag={}&timeout=120.0".format(etag) url = "http://{}:{}/{}".format(self.host, self.port, url) _LOGGER.debug("Calling URL: %s", url) @@ -363,18 +389,18 @@ class BluesoundPlayer(MediaPlayerDevice): with async_timeout.timeout(125): response = await self._polling_session.get( - url, headers={CONNECTION: KEEP_ALIVE}) + url, headers={CONNECTION: KEEP_ALIVE} + ) if response.status == 200: result = await response.text() self._is_online = True self._last_status_update = dt_util.utcnow() - self._status = xmltodict.parse(result)['status'].copy() + self._status = xmltodict.parse(result)["status"].copy() - group_name = self._status.get('groupName', None) + group_name = self._status.get("groupName", None) if group_name != self._group_name: - _LOGGER.debug( - "Group name change detected on device: %s", self.host) + _LOGGER.debug("Group name change detected on device: %s", self.host) self._group_name = group_name # the sleep is needed to make sure that the # devices is synced @@ -395,16 +421,16 @@ class BluesoundPlayer(MediaPlayerDevice): _LOGGER.info("Status 595 returned, treating as timeout") raise BluesoundPlayer._TimeoutException() else: - _LOGGER.error("Error %s on %s. Trying one more time", - response.status, url) + _LOGGER.error( + "Error %s on %s. Trying one more time", response.status, url + ) except (asyncio.TimeoutError, ClientError): self._is_online = False self._last_status_update = None self._status = None self.async_schedule_update_ha_state() - _LOGGER.info( - "Client connection error, marking %s as offline", self._name) + _LOGGER.info("Client connection error, marking %s as offline", self._name) raise async def async_trigger_sync_on_all(self): @@ -415,90 +441,93 @@ class BluesoundPlayer(MediaPlayerDevice): await player.force_update_sync_status() @Throttle(SYNC_STATUS_INTERVAL) - async def async_update_sync_status( - self, on_updated_cb=None, raise_timeout=False): + async def async_update_sync_status(self, on_updated_cb=None, raise_timeout=False): """Update sync status.""" - await self.force_update_sync_status( - on_updated_cb, raise_timeout=False) + await self.force_update_sync_status(on_updated_cb, raise_timeout=False) @Throttle(UPDATE_CAPTURE_INTERVAL) async def async_update_captures(self): """Update Capture sources.""" - resp = await self.send_bluesound_command( - 'RadioBrowse?service=Capture') + resp = await self.send_bluesound_command("RadioBrowse?service=Capture") if not resp: return self._capture_items = [] def _create_capture_item(item): - self._capture_items.append({ - 'title': item.get('@text', ''), - 'name': item.get('@text', ''), - 'type': item.get('@serviceType', 'Capture'), - 'image': item.get('@image', ''), - 'url': item.get('@URL', '') - }) + self._capture_items.append( + { + "title": item.get("@text", ""), + "name": item.get("@text", ""), + "type": item.get("@serviceType", "Capture"), + "image": item.get("@image", ""), + "url": item.get("@URL", ""), + } + ) - if 'radiotime' in resp and 'item' in resp['radiotime']: - if isinstance(resp['radiotime']['item'], list): - for item in resp['radiotime']['item']: + if "radiotime" in resp and "item" in resp["radiotime"]: + if isinstance(resp["radiotime"]["item"], list): + for item in resp["radiotime"]["item"]: _create_capture_item(item) else: - _create_capture_item(resp['radiotime']['item']) + _create_capture_item(resp["radiotime"]["item"]) return self._capture_items @Throttle(UPDATE_PRESETS_INTERVAL) async def async_update_presets(self): """Update Presets.""" - resp = await self.send_bluesound_command('Presets') + resp = await self.send_bluesound_command("Presets") if not resp: return self._preset_items = [] def _create_preset_item(item): - self._preset_items.append({ - 'title': item.get('@name', ''), - 'name': item.get('@name', ''), - 'type': 'preset', - 'image': item.get('@image', ''), - 'is_raw_url': True, - 'url2': item.get('@url', ''), - 'url': 'Preset?id={}'.format(item.get('@id', '')) - }) + self._preset_items.append( + { + "title": item.get("@name", ""), + "name": item.get("@name", ""), + "type": "preset", + "image": item.get("@image", ""), + "is_raw_url": True, + "url2": item.get("@url", ""), + "url": "Preset?id={}".format(item.get("@id", "")), + } + ) - if 'presets' in resp and 'preset' in resp['presets']: - if isinstance(resp['presets']['preset'], list): - for item in resp['presets']['preset']: + if "presets" in resp and "preset" in resp["presets"]: + if isinstance(resp["presets"]["preset"], list): + for item in resp["presets"]["preset"]: _create_preset_item(item) else: - _create_preset_item(resp['presets']['preset']) + _create_preset_item(resp["presets"]["preset"]) return self._preset_items @Throttle(UPDATE_SERVICES_INTERVAL) async def async_update_services(self): """Update Services.""" - resp = await self.send_bluesound_command('Services') + resp = await self.send_bluesound_command("Services") if not resp: return self._services_items = [] def _create_service_item(item): - self._services_items.append({ - 'title': item.get('@displayname', ''), - 'name': item.get('@name', ''), - 'type': item.get('@type', ''), - 'image': item.get('@icon', ''), - 'url': item.get('@name', '') - }) + self._services_items.append( + { + "title": item.get("@displayname", ""), + "name": item.get("@name", ""), + "type": item.get("@type", ""), + "image": item.get("@icon", ""), + "url": item.get("@name", ""), + } + ) - if 'services' in resp and 'service' in resp['services']: - if isinstance(resp['services']['service'], list): - for item in resp['services']['service']: + if "services" in resp and "service" in resp["services"]: + if isinstance(resp["services"]["service"], list): + for item in resp["services"]["service"]: _create_service_item(item) else: - _create_service_item(resp['services']['service']) + _create_service_item(resp["services"]["service"]) return self._services_items @@ -516,21 +545,20 @@ class BluesoundPlayer(MediaPlayerDevice): if self.is_grouped and not self.is_master: return STATE_GROUPED - status = self._status.get('state', None) - if status in ('pause', 'stop'): + status = self._status.get("state", None) + if status in ("pause", "stop"): return STATE_PAUSED - if status in ('stream', 'play'): + if status in ("stream", "play"): return STATE_PLAYING return STATE_IDLE @property def media_title(self): """Title of current playing media.""" - if (self._status is None or - (self.is_grouped and not self.is_master)): + if self._status is None or (self.is_grouped and not self.is_master): return None - return self._status.get('title1', None) + return self._status.get("title1", None) @property def media_artist(self): @@ -541,34 +569,32 @@ class BluesoundPlayer(MediaPlayerDevice): if self.is_grouped and not self.is_master: return self._group_name - artist = self._status.get('artist', None) + artist = self._status.get("artist", None) if not artist: - artist = self._status.get('title2', None) + artist = self._status.get("title2", None) return artist @property def media_album_name(self): """Artist of current playing media (Music track only).""" - if (self._status is None or - (self.is_grouped and not self.is_master)): + if self._status is None or (self.is_grouped and not self.is_master): return None - album = self._status.get('album', None) + album = self._status.get("album", None) if not album: - album = self._status.get('title3', None) + album = self._status.get("title3", None) return album @property def media_image_url(self): """Image url of current playing media.""" - if (self._status is None or - (self.is_grouped and not self.is_master)): + if self._status is None or (self.is_grouped and not self.is_master): return None - url = self._status.get('image', None) + url = self._status.get("image", None) if not url: return - if url[0] == '/': + if url[0] == "/": url = "http://{}:{}{}".format(self.host, self.port, url) return url @@ -576,33 +602,30 @@ class BluesoundPlayer(MediaPlayerDevice): @property def media_position(self): """Position of current playing media in seconds.""" - if (self._status is None or - (self.is_grouped and not self.is_master)): + if self._status is None or (self.is_grouped and not self.is_master): return None mediastate = self.state if self._last_status_update is None or mediastate == STATE_IDLE: return None - position = self._status.get('secs', None) + position = self._status.get("secs", None) if position is None: return None position = float(position) if mediastate == STATE_PLAYING: - position += (dt_util.utcnow() - - self._last_status_update).total_seconds() + position += (dt_util.utcnow() - self._last_status_update).total_seconds() return position @property def media_duration(self): """Duration of current playing media in seconds.""" - if (self._status is None or - (self.is_grouped and not self.is_master)): + if self._status is None or (self.is_grouped and not self.is_master): return None - duration = self._status.get('totlen', None) + duration = self._status.get("totlen", None) if duration is None: return None return float(duration) @@ -615,9 +638,9 @@ class BluesoundPlayer(MediaPlayerDevice): @property def volume_level(self): """Volume level of the media player (0..1).""" - volume = self._status.get('volume', None) + volume = self._status.get("volume", None) if self.is_grouped: - volume = self._sync_status.get('@volume', None) + volume = self._sync_status.get("@volume", None) if volume is not None: return int(volume) / 100 @@ -644,22 +667,23 @@ class BluesoundPlayer(MediaPlayerDevice): @property def source_list(self): """List of available input sources.""" - if (self._status is None or - (self.is_grouped and not self.is_master)): + if self._status is None or (self.is_grouped and not self.is_master): return None sources = [] for source in self._preset_items: - sources.append(source['title']) + sources.append(source["title"]) - for source in [x for x in self._services_items - if x['type'] == 'LocalMusic' or - x['type'] == 'RadioService']: - sources.append(source['title']) + for source in [ + x + for x in self._services_items + if x["type"] == "LocalMusic" or x["type"] == "RadioService" + ]: + sources.append(source["title"]) for source in self._capture_items: - sources.append(source['title']) + sources.append(source["title"]) return sources @@ -668,67 +692,72 @@ class BluesoundPlayer(MediaPlayerDevice): """Name of the current input source.""" from urllib import parse - if (self._status is None or - (self.is_grouped and not self.is_master)): + if self._status is None or (self.is_grouped and not self.is_master): return None - current_service = self._status.get('service', '') - if current_service == '': - return '' - stream_url = self._status.get('streamUrl', '') + current_service = self._status.get("service", "") + if current_service == "": + return "" + stream_url = self._status.get("streamUrl", "") - if self._status.get('is_preset', '') == '1' and stream_url != '': + if self._status.get("is_preset", "") == "1" and stream_url != "": # This check doesn't work with all presets, for example playlists. # But it works with radio service_items will catch playlists. - items = [x for x in self._preset_items if 'url2' in x and - parse.unquote(x['url2']) == stream_url] + items = [ + x + for x in self._preset_items + if "url2" in x and parse.unquote(x["url2"]) == stream_url + ] if items: - return items[0]['title'] + return items[0]["title"] # This could be a bit difficult to detect. Bluetooth could be named # different things and there is not any way to match chooses in # capture list to current playing. It's a bit of guesswork. # This method will be needing some tweaking over time. - title = self._status.get('title1', '').lower() - if title == 'bluetooth' or stream_url == 'Capture:hw:2,0/44100/16/2': - items = [x for x in self._capture_items - if x['url'] == "Capture%3Abluez%3Abluetooth"] + title = self._status.get("title1", "").lower() + if title == "bluetooth" or stream_url == "Capture:hw:2,0/44100/16/2": + items = [ + x + for x in self._capture_items + if x["url"] == "Capture%3Abluez%3Abluetooth" + ] if items: - return items[0]['title'] + return items[0]["title"] - items = [x for x in self._capture_items if x['url'] == stream_url] + items = [x for x in self._capture_items if x["url"] == stream_url] if items: - return items[0]['title'] + return items[0]["title"] - if stream_url[:8] == 'Capture:': + if stream_url[:8] == "Capture:": stream_url = stream_url[8:] - idx = BluesoundPlayer._try_get_index(stream_url, ':') + idx = BluesoundPlayer._try_get_index(stream_url, ":") if idx > 0: stream_url = stream_url[:idx] for item in self._capture_items: - url = parse.unquote(item['url']) - if url[:8] == 'Capture:': + url = parse.unquote(item["url"]) + if url[:8] == "Capture:": url = url[8:] - idx = BluesoundPlayer._try_get_index(url, ':') + idx = BluesoundPlayer._try_get_index(url, ":") if idx > 0: url = url[:idx] if url.lower() == stream_url.lower(): - return item['title'] + return item["title"] - items = [x for x in self._capture_items - if x['name'] == current_service] + items = [x for x in self._capture_items if x["name"] == current_service] if items: - return items[0]['title'] + return items[0]["title"] - items = [x for x in self._services_items - if x['name'] == current_service] + items = [x for x in self._services_items if x["name"] == current_service] if items: - return items[0]['title'] + return items[0]["title"] - if self._status.get('streamUrl', '') != '': - _LOGGER.debug("Couldn't find source of stream URL: %s", - self._status.get('streamUrl', '')) + if self._status.get("streamUrl", "") != "": + _LOGGER.debug( + "Couldn't find source of stream URL: %s", + self._status.get("streamUrl", ""), + ) return None @property @@ -738,23 +767,33 @@ class BluesoundPlayer(MediaPlayerDevice): return None if self.is_grouped and not self.is_master: - return SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_SET | \ - SUPPORT_VOLUME_MUTE + return SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE supported = SUPPORT_CLEAR_PLAYLIST - if self._status.get('indexing', '0') == '0': - supported = supported | SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | \ - SUPPORT_NEXT_TRACK | SUPPORT_PLAY_MEDIA | \ - SUPPORT_STOP | SUPPORT_PLAY | SUPPORT_SELECT_SOURCE | \ - SUPPORT_SHUFFLE_SET + if self._status.get("indexing", "0") == "0": + supported = ( + supported + | SUPPORT_PAUSE + | SUPPORT_PREVIOUS_TRACK + | SUPPORT_NEXT_TRACK + | SUPPORT_PLAY_MEDIA + | SUPPORT_STOP + | SUPPORT_PLAY + | SUPPORT_SELECT_SOURCE + | SUPPORT_SHUFFLE_SET + ) current_vol = self.volume_level if current_vol is not None and current_vol >= 0: - supported = supported | SUPPORT_VOLUME_STEP | \ - SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE + supported = ( + supported + | SUPPORT_VOLUME_STEP + | SUPPORT_VOLUME_SET + | SUPPORT_VOLUME_MUTE + ) - if self._status.get('canSeek', '') == '1': + if self._status.get("canSeek", "") == "1": supported = supported | SUPPORT_SEEK return supported @@ -772,16 +811,22 @@ class BluesoundPlayer(MediaPlayerDevice): @property def shuffle(self): """Return true if shuffle is active.""" - return self._status.get('shuffle', '0') == '1' + return self._status.get("shuffle", "0") == "1" async def async_join(self, master): """Join the player to a group.""" - master_device = [device for device in self.hass.data[DATA_BLUESOUND] - if device.entity_id == master] + master_device = [ + device + for device in self.hass.data[DATA_BLUESOUND] + if device.entity_id == master + ] if master_device: - _LOGGER.debug("Trying to join player: %s to master: %s", - self.host, master_device[0].host) + _LOGGER.debug( + "Trying to join player: %s to master: %s", + self.host, + master_device[0].host, + ) await master_device[0].async_add_slave(self) else: @@ -798,24 +843,23 @@ class BluesoundPlayer(MediaPlayerDevice): async def async_add_slave(self, slave_device): """Add slave to master.""" return await self.send_bluesound_command( - '/AddSlave?slave={}&port={}'.format( - slave_device.host, slave_device.port)) + "/AddSlave?slave={}&port={}".format(slave_device.host, slave_device.port) + ) async def async_remove_slave(self, slave_device): """Remove slave to master.""" return await self.send_bluesound_command( - '/RemoveSlave?slave={}&port={}'.format( - slave_device.host, slave_device.port)) + "/RemoveSlave?slave={}&port={}".format(slave_device.host, slave_device.port) + ) async def async_increase_timer(self): """Increase sleep time on player.""" - sleep_time = await self.send_bluesound_command('/Sleep') + sleep_time = await self.send_bluesound_command("/Sleep") if sleep_time is None: - _LOGGER.error( - "Error while increasing sleep time on player: %s", self.host) + _LOGGER.error("Error while increasing sleep time on player: %s", self.host) return 0 - return int(sleep_time.get('sleep', '0')) + return int(sleep_time.get("sleep", "0")) async def async_clear_timer(self): """Clear sleep timer on player.""" @@ -825,31 +869,31 @@ class BluesoundPlayer(MediaPlayerDevice): async def async_set_shuffle(self, shuffle): """Enable or disable shuffle mode.""" - value = '1' if shuffle else '0' - return await self.send_bluesound_command( - '/Shuffle?state={}'.format(value)) + value = "1" if shuffle else "0" + return await self.send_bluesound_command("/Shuffle?state={}".format(value)) async def async_select_source(self, source): """Select input source.""" if self.is_grouped and not self.is_master: return - items = [x for x in self._preset_items if x['title'] == source] + items = [x for x in self._preset_items if x["title"] == source] if not items: - items = [x for x in self._services_items if x['title'] == source] + items = [x for x in self._services_items if x["title"] == source] if not items: - items = [x for x in self._capture_items if x['title'] == source] + items = [x for x in self._capture_items if x["title"] == source] if not items: return selected_source = items[0] - url = 'Play?url={}&preset_id&image={}'.format( - selected_source['url'], selected_source['image']) + url = "Play?url={}&preset_id&image={}".format( + selected_source["url"], selected_source["image"] + ) - if 'is_raw_url' in selected_source and selected_source['is_raw_url']: - url = selected_source['url'] + if "is_raw_url" in selected_source and selected_source["is_raw_url"]: + url = selected_source["url"] return await self.send_bluesound_command(url) @@ -858,19 +902,18 @@ class BluesoundPlayer(MediaPlayerDevice): if self.is_grouped and not self.is_master: return - return await self.send_bluesound_command('Clear') + return await self.send_bluesound_command("Clear") async def async_media_next_track(self): """Send media_next command to media player.""" if self.is_grouped and not self.is_master: return - cmd = 'Skip' - if self._status and 'actions' in self._status: - for action in self._status['actions']['action']: - if ('@name' in action and '@url' in action and - action['@name'] == 'skip'): - cmd = action['@url'] + cmd = "Skip" + if self._status and "actions" in self._status: + for action in self._status["actions"]["action"]: + if "@name" in action and "@url" in action and action["@name"] == "skip": + cmd = action["@url"] return await self.send_bluesound_command(cmd) @@ -879,12 +922,11 @@ class BluesoundPlayer(MediaPlayerDevice): if self.is_grouped and not self.is_master: return - cmd = 'Back' - if self._status and 'actions' in self._status: - for action in self._status['actions']['action']: - if ('@name' in action and '@url' in action and - action['@name'] == 'back'): - cmd = action['@url'] + cmd = "Back" + if self._status and "actions" in self._status: + for action in self._status["actions"]["action"]: + if "@name" in action and "@url" in action and action["@name"] == "back": + cmd = action["@url"] return await self.send_bluesound_command(cmd) @@ -893,29 +935,28 @@ class BluesoundPlayer(MediaPlayerDevice): if self.is_grouped and not self.is_master: return - return await self.send_bluesound_command('Play') + return await self.send_bluesound_command("Play") async def async_media_pause(self): """Send media_pause command to media player.""" if self.is_grouped and not self.is_master: return - return await self.send_bluesound_command('Pause') + return await self.send_bluesound_command("Pause") async def async_media_stop(self): """Send stop command.""" if self.is_grouped and not self.is_master: return - return await self.send_bluesound_command('Pause') + return await self.send_bluesound_command("Pause") async def async_media_seek(self, position): """Send media_seek command to media player.""" if self.is_grouped and not self.is_master: return - return await self.send_bluesound_command( - 'Play?seek={}'.format(float(position))) + return await self.send_bluesound_command("Play?seek={}".format(float(position))) async def async_play_media(self, media_type, media_id, **kwargs): """ @@ -926,7 +967,7 @@ class BluesoundPlayer(MediaPlayerDevice): if self.is_grouped and not self.is_master: return - url = 'Play?url={}'.format(media_id) + url = "Play?url={}".format(media_id) if kwargs.get(ATTR_MEDIA_ENQUEUE): return await self.send_bluesound_command(url) @@ -938,14 +979,14 @@ class BluesoundPlayer(MediaPlayerDevice): current_vol = self.volume_level if not current_vol or current_vol < 0: return - return self.async_set_volume_level(((current_vol*100)+1)/100) + return self.async_set_volume_level(((current_vol * 100) + 1) / 100) async def async_volume_down(self): """Volume down the media player.""" current_vol = self.volume_level if not current_vol or current_vol < 0: return - return self.async_set_volume_level(((current_vol*100)-1)/100) + return self.async_set_volume_level(((current_vol * 100) - 1) / 100) async def async_set_volume_level(self, volume): """Send volume_up command to media player.""" @@ -954,7 +995,8 @@ class BluesoundPlayer(MediaPlayerDevice): elif volume > 1: volume = 1 return await self.send_bluesound_command( - 'Volume?level=' + str(float(volume) * 100)) + "Volume?level=" + str(float(volume) * 100) + ) async def async_mute_volume(self, mute): """Send mute command to media player.""" @@ -962,6 +1004,7 @@ class BluesoundPlayer(MediaPlayerDevice): volume = self.volume_level if volume > 0: self._lastvol = volume - return await self.send_bluesound_command('Volume?level=0') + return await self.send_bluesound_command("Volume?level=0") return await self.send_bluesound_command( - 'Volume?level=' + str(float(self._lastvol) * 100)) + "Volume?level=" + str(float(self._lastvol) * 100) + ) diff --git a/homeassistant/components/bluetooth_le_tracker/device_tracker.py b/homeassistant/components/bluetooth_le_tracker/device_tracker.py index 6b5fcd7df06..8cba3032f54 100644 --- a/homeassistant/components/bluetooth_le_tracker/device_tracker.py +++ b/homeassistant/components/bluetooth_le_tracker/device_tracker.py @@ -3,10 +3,14 @@ import logging from homeassistant.helpers.event import track_point_in_utc_time from homeassistant.components.device_tracker.legacy import ( - YAML_DEVICES, async_load_config + YAML_DEVICES, + async_load_config, ) from homeassistant.components.device_tracker.const import ( - CONF_TRACK_NEW, CONF_SCAN_INTERVAL, SCAN_INTERVAL, SOURCE_TYPE_BLUETOOTH_LE + CONF_TRACK_NEW, + CONF_SCAN_INTERVAL, + SCAN_INTERVAL, + SOURCE_TYPE_BLUETOOTH_LE, ) from homeassistant.const import EVENT_HOMEASSISTANT_STOP import homeassistant.util.dt as dt_util @@ -14,9 +18,9 @@ from homeassistant.util.async_ import run_coroutine_threadsafe _LOGGER = logging.getLogger(__name__) -DATA_BLE = 'BLE' -DATA_BLE_ADAPTER = 'ADAPTER' -BLE_PREFIX = 'BLE_' +DATA_BLE = "BLE" +DATA_BLE_ADAPTER = "ADAPTER" +BLE_PREFIX = "BLE_" MIN_SEEN_NEW = 5 @@ -24,6 +28,7 @@ def setup_scanner(hass, config, see, discovery_info=None): """Set up the Bluetooth LE Scanner.""" # pylint: disable=import-error import pygatt + new_devices = {} hass.data.setdefault(DATA_BLE, {DATA_BLE_ADAPTER: None}) @@ -41,8 +46,7 @@ def setup_scanner(hass, config, see, discovery_info=None): """Mark a device as seen.""" if new_device: if address in new_devices: - _LOGGER.debug( - "Seen %s %s times", address, new_devices[address]) + _LOGGER.debug("Seen %s %s times", address, new_devices[address]) new_devices[address] += 1 if new_devices[address] >= MIN_SEEN_NEW: _LOGGER.debug("Adding %s to tracked devices", address) @@ -57,8 +61,11 @@ def setup_scanner(hass, config, see, discovery_info=None): if name is not None: name = name.strip("\x00") - see(mac=BLE_PREFIX + address, host_name=name, - source_type=SOURCE_TYPE_BLUETOOTH_LE) + see( + mac=BLE_PREFIX + address, + host_name=name, + source_type=SOURCE_TYPE_BLUETOOTH_LE, + ) def discover_ble_devices(): """Discover Bluetooth LE devices.""" @@ -68,7 +75,7 @@ def setup_scanner(hass, config, see, discovery_info=None): hass.data[DATA_BLE][DATA_BLE_ADAPTER] = adapter devs = adapter.scan() - devices = {x['address']: x['name'] for x in devs} + devices = {x["address"]: x["name"] for x in devs} _LOGGER.debug("Bluetooth LE devices discovered = %s", devices) except RuntimeError as error: _LOGGER.error("Error during Bluetooth LE scan: %s", error) @@ -83,8 +90,7 @@ def setup_scanner(hass, config, see, discovery_info=None): # We just need the devices so set consider_home and home range # to 0 for device in run_coroutine_threadsafe( - async_load_config(yaml_path, hass, 0), - hass.loop + async_load_config(yaml_path, hass, 0), hass.loop ).result(): # check if device is a valid bluetooth device if device.mac and device.mac[:4].upper() == BLE_PREFIX: @@ -118,8 +124,7 @@ def setup_scanner(hass, config, see, discovery_info=None): if track_new: for address in devs: - if address not in devs_to_track and \ - address not in devs_donot_track: + if address not in devs_to_track and address not in devs_donot_track: _LOGGER.info("Discovered Bluetooth LE device %s", address) see_device(address, devs[address], new_device=True) diff --git a/homeassistant/components/bluetooth_tracker/device_tracker.py b/homeassistant/components/bluetooth_tracker/device_tracker.py index 7b48252c6b6..65db87fa072 100644 --- a/homeassistant/components/bluetooth_tracker/device_tracker.py +++ b/homeassistant/components/bluetooth_tracker/device_tracker.py @@ -7,31 +7,39 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import track_point_in_utc_time from homeassistant.components.device_tracker import PLATFORM_SCHEMA from homeassistant.components.device_tracker.legacy import ( - YAML_DEVICES, async_load_config + YAML_DEVICES, + async_load_config, ) from homeassistant.components.device_tracker.const import ( - CONF_TRACK_NEW, CONF_SCAN_INTERVAL, SCAN_INTERVAL, DEFAULT_TRACK_NEW, - SOURCE_TYPE_BLUETOOTH, DOMAIN + CONF_TRACK_NEW, + CONF_SCAN_INTERVAL, + SCAN_INTERVAL, + DEFAULT_TRACK_NEW, + SOURCE_TYPE_BLUETOOTH, + DOMAIN, ) import homeassistant.util.dt as dt_util from homeassistant.util.async_ import run_coroutine_threadsafe _LOGGER = logging.getLogger(__name__) -BT_PREFIX = 'BT_' +BT_PREFIX = "BT_" -CONF_REQUEST_RSSI = 'request_rssi' +CONF_REQUEST_RSSI = "request_rssi" CONF_DEVICE_ID = "device_id" DEFAULT_DEVICE_ID = -1 -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_TRACK_NEW): cv.boolean, - vol.Optional(CONF_REQUEST_RSSI): cv.boolean, - vol.Optional(CONF_DEVICE_ID, default=DEFAULT_DEVICE_ID): - vol.All(vol.Coerce(int), vol.Range(min=-1)) -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_TRACK_NEW): cv.boolean, + vol.Optional(CONF_REQUEST_RSSI): cv.boolean, + vol.Optional(CONF_DEVICE_ID, default=DEFAULT_DEVICE_ID): vol.All( + vol.Coerce(int), vol.Range(min=-1) + ), + } +) def setup_scanner(hass, config, see, discovery_info=None): @@ -44,17 +52,25 @@ def setup_scanner(hass, config, see, discovery_info=None): """Mark a device as seen.""" attributes = {} if rssi is not None: - attributes['rssi'] = rssi - see(mac="{}{}".format(BT_PREFIX, mac), host_name=name, - attributes=attributes, source_type=SOURCE_TYPE_BLUETOOTH) + attributes["rssi"] = rssi + see( + mac="{}{}".format(BT_PREFIX, mac), + host_name=name, + attributes=attributes, + source_type=SOURCE_TYPE_BLUETOOTH, + ) device_id = config.get(CONF_DEVICE_ID) def discover_devices(): """Discover Bluetooth devices.""" result = bluetooth.discover_devices( - duration=8, lookup_names=True, flush_cache=True, - lookup_class=False, device_id=device_id) + duration=8, + lookup_names=True, + flush_cache=True, + lookup_class=False, + device_id=device_id, + ) _LOGGER.debug("Bluetooth devices discovered = %d", len(result)) return result @@ -66,8 +82,7 @@ def setup_scanner(hass, config, see, discovery_info=None): # We just need the devices so set consider_home and home range # to 0 for device in run_coroutine_threadsafe( - async_load_config(yaml_path, hass, 0), - hass.loop + async_load_config(yaml_path, hass, 0), hass.loop ).result(): # Check if device is a valid bluetooth device if device.mac and device.mac[:3].upper() == BT_PREFIX: @@ -80,8 +95,7 @@ def setup_scanner(hass, config, see, discovery_info=None): track_new = config.get(CONF_TRACK_NEW, DEFAULT_TRACK_NEW) if track_new: for dev in discover_devices(): - if dev[0] not in devs_to_track and \ - dev[0] not in devs_donot_track: + if dev[0] not in devs_to_track and dev[0] not in devs_donot_track: devs_to_track.append(dev[0]) see_device(dev[0], dev[1]) @@ -92,16 +106,14 @@ def setup_scanner(hass, config, see, discovery_info=None): def update_bluetooth(_): """Update Bluetooth and set timer for the next update.""" update_bluetooth_once() - track_point_in_utc_time( - hass, update_bluetooth, dt_util.utcnow() + interval) + track_point_in_utc_time(hass, update_bluetooth, dt_util.utcnow() + interval) def update_bluetooth_once(): """Lookup Bluetooth device and update status.""" try: if track_new: for dev in discover_devices(): - if dev[0] not in devs_to_track and \ - dev[0] not in devs_donot_track: + if dev[0] not in devs_to_track and dev[0] not in devs_donot_track: devs_to_track.append(dev[0]) for mac in devs_to_track: _LOGGER.debug("Scanning %s", mac) @@ -124,7 +136,6 @@ def setup_scanner(hass, config, see, discovery_info=None): update_bluetooth(dt_util.utcnow()) - hass.services.register( - DOMAIN, "bluetooth_tracker_update", handle_update_bluetooth) + hass.services.register(DOMAIN, "bluetooth_tracker_update", handle_update_bluetooth) return True diff --git a/homeassistant/components/bme280/sensor.py b/homeassistant/components/bme280/sensor.py index 66b4ba67258..bdd91e6dfe1 100644 --- a/homeassistant/components/bme280/sensor.py +++ b/homeassistant/components/bme280/sensor.py @@ -7,26 +7,25 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA import homeassistant.helpers.config_validation as cv -from homeassistant.const import ( - TEMP_FAHRENHEIT, CONF_NAME, CONF_MONITORED_CONDITIONS) +from homeassistant.const import TEMP_FAHRENHEIT, CONF_NAME, CONF_MONITORED_CONDITIONS from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from homeassistant.util.temperature import celsius_to_fahrenheit _LOGGER = logging.getLogger(__name__) -CONF_I2C_ADDRESS = 'i2c_address' -CONF_I2C_BUS = 'i2c_bus' -CONF_OVERSAMPLING_TEMP = 'oversampling_temperature' -CONF_OVERSAMPLING_PRES = 'oversampling_pressure' -CONF_OVERSAMPLING_HUM = 'oversampling_humidity' -CONF_OPERATION_MODE = 'operation_mode' -CONF_T_STANDBY = 'time_standby' -CONF_FILTER_MODE = 'filter_mode' -CONF_DELTA_TEMP = 'delta_temperature' +CONF_I2C_ADDRESS = "i2c_address" +CONF_I2C_BUS = "i2c_bus" +CONF_OVERSAMPLING_TEMP = "oversampling_temperature" +CONF_OVERSAMPLING_PRES = "oversampling_pressure" +CONF_OVERSAMPLING_HUM = "oversampling_humidity" +CONF_OPERATION_MODE = "operation_mode" +CONF_T_STANDBY = "time_standby" +CONF_FILTER_MODE = "filter_mode" +CONF_DELTA_TEMP = "delta_temperature" -DEFAULT_NAME = 'BME280 Sensor' -DEFAULT_I2C_ADDRESS = '0x76' +DEFAULT_NAME = "BME280 Sensor" +DEFAULT_I2C_ADDRESS = "0x76" DEFAULT_I2C_BUS = 1 DEFAULT_OVERSAMPLING_TEMP = 1 # Temperature oversampling x 1 DEFAULT_OVERSAMPLING_PRES = 1 # Pressure oversampling x 1 @@ -34,45 +33,48 @@ DEFAULT_OVERSAMPLING_HUM = 1 # Humidity oversampling x 1 DEFAULT_OPERATION_MODE = 3 # Normal mode (forced mode: 2) DEFAULT_T_STANDBY = 5 # Tstandby 5ms DEFAULT_FILTER_MODE = 0 # Filter off -DEFAULT_DELTA_TEMP = 0. +DEFAULT_DELTA_TEMP = 0.0 MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=3) -SENSOR_TEMP = 'temperature' -SENSOR_HUMID = 'humidity' -SENSOR_PRESS = 'pressure' +SENSOR_TEMP = "temperature" +SENSOR_HUMID = "humidity" +SENSOR_PRESS = "pressure" SENSOR_TYPES = { - SENSOR_TEMP: ['Temperature', None], - SENSOR_HUMID: ['Humidity', '%'], - SENSOR_PRESS: ['Pressure', 'mb'] + SENSOR_TEMP: ["Temperature", None], + SENSOR_HUMID: ["Humidity", "%"], + SENSOR_PRESS: ["Pressure", "mb"], } DEFAULT_MONITORED = [SENSOR_TEMP, SENSOR_HUMID, SENSOR_PRESS] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_I2C_ADDRESS, default=DEFAULT_I2C_ADDRESS): cv.string, - vol.Optional(CONF_MONITORED_CONDITIONS, default=DEFAULT_MONITORED): - vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), - vol.Optional(CONF_I2C_BUS, default=DEFAULT_I2C_BUS): vol.Coerce(int), - vol.Optional(CONF_OVERSAMPLING_TEMP, - default=DEFAULT_OVERSAMPLING_TEMP): vol.Coerce(int), - vol.Optional(CONF_OVERSAMPLING_PRES, - default=DEFAULT_OVERSAMPLING_PRES): vol.Coerce(int), - vol.Optional(CONF_OVERSAMPLING_HUM, - default=DEFAULT_OVERSAMPLING_HUM): vol.Coerce(int), - vol.Optional(CONF_OPERATION_MODE, - default=DEFAULT_OPERATION_MODE): vol.Coerce(int), - vol.Optional(CONF_T_STANDBY, - default=DEFAULT_T_STANDBY): vol.Coerce(int), - vol.Optional(CONF_FILTER_MODE, - default=DEFAULT_FILTER_MODE): vol.Coerce(int), - vol.Optional(CONF_DELTA_TEMP, - default=DEFAULT_DELTA_TEMP): vol.Coerce(float), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_I2C_ADDRESS, default=DEFAULT_I2C_ADDRESS): cv.string, + vol.Optional(CONF_MONITORED_CONDITIONS, default=DEFAULT_MONITORED): vol.All( + cv.ensure_list, [vol.In(SENSOR_TYPES)] + ), + vol.Optional(CONF_I2C_BUS, default=DEFAULT_I2C_BUS): vol.Coerce(int), + vol.Optional( + CONF_OVERSAMPLING_TEMP, default=DEFAULT_OVERSAMPLING_TEMP + ): vol.Coerce(int), + vol.Optional( + CONF_OVERSAMPLING_PRES, default=DEFAULT_OVERSAMPLING_PRES + ): vol.Coerce(int), + vol.Optional( + CONF_OVERSAMPLING_HUM, default=DEFAULT_OVERSAMPLING_HUM + ): vol.Coerce(int), + vol.Optional(CONF_OPERATION_MODE, default=DEFAULT_OPERATION_MODE): vol.Coerce( + int + ), + vol.Optional(CONF_T_STANDBY, default=DEFAULT_T_STANDBY): vol.Coerce(int), + vol.Optional(CONF_FILTER_MODE, default=DEFAULT_FILTER_MODE): vol.Coerce(int), + vol.Optional(CONF_DELTA_TEMP, default=DEFAULT_DELTA_TEMP): vol.Coerce(float), + } +) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the BME280 sensor.""" import smbus # pylint: disable=import-error from i2csense.bme280 import BME280 # pylint: disable=import-error @@ -83,15 +85,19 @@ async def async_setup_platform(hass, config, async_add_entities, bus = smbus.SMBus(config.get(CONF_I2C_BUS)) sensor = await hass.async_add_job( - partial(BME280, bus, i2c_address, - osrs_t=config.get(CONF_OVERSAMPLING_TEMP), - osrs_p=config.get(CONF_OVERSAMPLING_PRES), - osrs_h=config.get(CONF_OVERSAMPLING_HUM), - mode=config.get(CONF_OPERATION_MODE), - t_sb=config.get(CONF_T_STANDBY), - filter_mode=config.get(CONF_FILTER_MODE), - delta_temp=config.get(CONF_DELTA_TEMP), - logger=_LOGGER) + partial( + BME280, + bus, + i2c_address, + osrs_t=config.get(CONF_OVERSAMPLING_TEMP), + osrs_p=config.get(CONF_OVERSAMPLING_PRES), + osrs_h=config.get(CONF_OVERSAMPLING_HUM), + mode=config.get(CONF_OPERATION_MODE), + t_sb=config.get(CONF_T_STANDBY), + filter_mode=config.get(CONF_FILTER_MODE), + delta_temp=config.get(CONF_DELTA_TEMP), + logger=_LOGGER, + ) ) if not sensor.sample_ok: _LOGGER.error("BME280 sensor not detected at %s", i2c_address) @@ -102,8 +108,9 @@ async def async_setup_platform(hass, config, async_add_entities, dev = [] try: for variable in config[CONF_MONITORED_CONDITIONS]: - dev.append(BME280Sensor( - sensor_handler, variable, SENSOR_TYPES[variable][1], name)) + dev.append( + BME280Sensor(sensor_handler, variable, SENSOR_TYPES[variable][1], name) + ) except KeyError: pass @@ -140,7 +147,7 @@ class BME280Sensor(Entity): @property def name(self): """Return the name of the sensor.""" - return '{} {}'.format(self.client_name, self._name) + return "{} {}".format(self.client_name, self._name) @property def state(self): diff --git a/homeassistant/components/bme680/sensor.py b/homeassistant/components/bme680/sensor.py index 73fe827be6b..58b343b3de0 100644 --- a/homeassistant/components/bme680/sensor.py +++ b/homeassistant/components/bme680/sensor.py @@ -8,28 +8,27 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA import homeassistant.helpers.config_validation as cv -from homeassistant.const import ( - TEMP_FAHRENHEIT, CONF_NAME, CONF_MONITORED_CONDITIONS) +from homeassistant.const import TEMP_FAHRENHEIT, CONF_NAME, CONF_MONITORED_CONDITIONS from homeassistant.helpers.entity import Entity from homeassistant.util.temperature import celsius_to_fahrenheit _LOGGER = logging.getLogger(__name__) -CONF_I2C_ADDRESS = 'i2c_address' -CONF_I2C_BUS = 'i2c_bus' -CONF_OVERSAMPLING_TEMP = 'oversampling_temperature' -CONF_OVERSAMPLING_PRES = 'oversampling_pressure' -CONF_OVERSAMPLING_HUM = 'oversampling_humidity' -CONF_FILTER_SIZE = 'filter_size' -CONF_GAS_HEATER_TEMP = 'gas_heater_temperature' -CONF_GAS_HEATER_DURATION = 'gas_heater_duration' -CONF_AQ_BURN_IN_TIME = 'aq_burn_in_time' -CONF_AQ_HUM_BASELINE = 'aq_humidity_baseline' -CONF_AQ_HUM_WEIGHTING = 'aq_humidity_bias' -CONF_TEMP_OFFSET = 'temp_offset' +CONF_I2C_ADDRESS = "i2c_address" +CONF_I2C_BUS = "i2c_bus" +CONF_OVERSAMPLING_TEMP = "oversampling_temperature" +CONF_OVERSAMPLING_PRES = "oversampling_pressure" +CONF_OVERSAMPLING_HUM = "oversampling_humidity" +CONF_FILTER_SIZE = "filter_size" +CONF_GAS_HEATER_TEMP = "gas_heater_temperature" +CONF_GAS_HEATER_DURATION = "gas_heater_duration" +CONF_AQ_BURN_IN_TIME = "aq_burn_in_time" +CONF_AQ_HUM_BASELINE = "aq_humidity_baseline" +CONF_AQ_HUM_WEIGHTING = "aq_humidity_bias" +CONF_TEMP_OFFSET = "temp_offset" -DEFAULT_NAME = 'BME680 Sensor' +DEFAULT_NAME = "BME680 Sensor" DEFAULT_I2C_ADDRESS = 0x77 DEFAULT_I2C_BUS = 1 DEFAULT_OVERSAMPLING_TEMP = 8 # Temperature oversampling x 8 @@ -43,55 +42,65 @@ DEFAULT_AQ_HUM_BASELINE = 40 # 40%, an optimal indoor humidity. DEFAULT_AQ_HUM_WEIGHTING = 25 # 25% Weighting of humidity to gas in AQ score DEFAULT_TEMP_OFFSET = 0 # No calibration out of the box. -SENSOR_TEMP = 'temperature' -SENSOR_HUMID = 'humidity' -SENSOR_PRESS = 'pressure' -SENSOR_GAS = 'gas' -SENSOR_AQ = 'airquality' +SENSOR_TEMP = "temperature" +SENSOR_HUMID = "humidity" +SENSOR_PRESS = "pressure" +SENSOR_GAS = "gas" +SENSOR_AQ = "airquality" SENSOR_TYPES = { - SENSOR_TEMP: ['Temperature', None], - SENSOR_HUMID: ['Humidity', '%'], - SENSOR_PRESS: ['Pressure', 'mb'], - SENSOR_GAS: ['Gas Resistance', 'Ohms'], - SENSOR_AQ: ['Air Quality', '%'] + SENSOR_TEMP: ["Temperature", None], + SENSOR_HUMID: ["Humidity", "%"], + SENSOR_PRESS: ["Pressure", "mb"], + SENSOR_GAS: ["Gas Resistance", "Ohms"], + SENSOR_AQ: ["Air Quality", "%"], } DEFAULT_MONITORED = [SENSOR_TEMP, SENSOR_HUMID, SENSOR_PRESS, SENSOR_AQ] OVERSAMPLING_VALUES = set([0, 1, 2, 4, 8, 16]) FILTER_VALUES = set([0, 1, 3, 7, 15, 31, 63, 127]) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_I2C_ADDRESS, default=DEFAULT_I2C_ADDRESS): - cv.positive_int, - vol.Optional(CONF_MONITORED_CONDITIONS, default=DEFAULT_MONITORED): - vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), - vol.Optional(CONF_I2C_BUS, default=DEFAULT_I2C_BUS): cv.positive_int, - vol.Optional(CONF_OVERSAMPLING_TEMP, default=DEFAULT_OVERSAMPLING_TEMP): - vol.All(vol.Coerce(int), vol.In(OVERSAMPLING_VALUES)), - vol.Optional(CONF_OVERSAMPLING_PRES, default=DEFAULT_OVERSAMPLING_PRES): - vol.All(vol.Coerce(int), vol.In(OVERSAMPLING_VALUES)), - vol.Optional(CONF_OVERSAMPLING_HUM, default=DEFAULT_OVERSAMPLING_HUM): - vol.All(vol.Coerce(int), vol.In(OVERSAMPLING_VALUES)), - vol.Optional(CONF_FILTER_SIZE, default=DEFAULT_FILTER_SIZE): - vol.All(vol.Coerce(int), vol.In(FILTER_VALUES)), - vol.Optional(CONF_GAS_HEATER_TEMP, default=DEFAULT_GAS_HEATER_TEMP): - vol.All(vol.Coerce(int), vol.Range(200, 400)), - vol.Optional(CONF_GAS_HEATER_DURATION, - default=DEFAULT_GAS_HEATER_DURATION): - vol.All(vol.Coerce(int), vol.Range(1, 4032)), - vol.Optional(CONF_AQ_BURN_IN_TIME, default=DEFAULT_AQ_BURN_IN_TIME): - cv.positive_int, - vol.Optional(CONF_AQ_HUM_BASELINE, default=DEFAULT_AQ_HUM_BASELINE): - vol.All(vol.Coerce(int), vol.Range(1, 100)), - vol.Optional(CONF_AQ_HUM_WEIGHTING, default=DEFAULT_AQ_HUM_WEIGHTING): - vol.All(vol.Coerce(int), vol.Range(1, 100)), - vol.Optional(CONF_TEMP_OFFSET, default=DEFAULT_TEMP_OFFSET): - vol.All(vol.Coerce(float), vol.Range(-100.0, 100.0)), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_I2C_ADDRESS, default=DEFAULT_I2C_ADDRESS): cv.positive_int, + vol.Optional(CONF_MONITORED_CONDITIONS, default=DEFAULT_MONITORED): vol.All( + cv.ensure_list, [vol.In(SENSOR_TYPES)] + ), + vol.Optional(CONF_I2C_BUS, default=DEFAULT_I2C_BUS): cv.positive_int, + vol.Optional( + CONF_OVERSAMPLING_TEMP, default=DEFAULT_OVERSAMPLING_TEMP + ): vol.All(vol.Coerce(int), vol.In(OVERSAMPLING_VALUES)), + vol.Optional( + CONF_OVERSAMPLING_PRES, default=DEFAULT_OVERSAMPLING_PRES + ): vol.All(vol.Coerce(int), vol.In(OVERSAMPLING_VALUES)), + vol.Optional(CONF_OVERSAMPLING_HUM, default=DEFAULT_OVERSAMPLING_HUM): vol.All( + vol.Coerce(int), vol.In(OVERSAMPLING_VALUES) + ), + vol.Optional(CONF_FILTER_SIZE, default=DEFAULT_FILTER_SIZE): vol.All( + vol.Coerce(int), vol.In(FILTER_VALUES) + ), + vol.Optional(CONF_GAS_HEATER_TEMP, default=DEFAULT_GAS_HEATER_TEMP): vol.All( + vol.Coerce(int), vol.Range(200, 400) + ), + vol.Optional( + CONF_GAS_HEATER_DURATION, default=DEFAULT_GAS_HEATER_DURATION + ): vol.All(vol.Coerce(int), vol.Range(1, 4032)), + vol.Optional( + CONF_AQ_BURN_IN_TIME, default=DEFAULT_AQ_BURN_IN_TIME + ): cv.positive_int, + vol.Optional(CONF_AQ_HUM_BASELINE, default=DEFAULT_AQ_HUM_BASELINE): vol.All( + vol.Coerce(int), vol.Range(1, 100) + ), + vol.Optional(CONF_AQ_HUM_WEIGHTING, default=DEFAULT_AQ_HUM_WEIGHTING): vol.All( + vol.Coerce(int), vol.Range(1, 100) + ), + vol.Optional(CONF_TEMP_OFFSET, default=DEFAULT_TEMP_OFFSET): vol.All( + vol.Coerce(float), vol.Range(-100.0, 100.0) + ), + } +) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the BME680 sensor.""" SENSOR_TYPES[SENSOR_TEMP][1] = hass.config.units.temperature_unit name = config.get(CONF_NAME) @@ -102,8 +111,9 @@ async def async_setup_platform(hass, config, async_add_entities, dev = [] for variable in config[CONF_MONITORED_CONDITIONS]: - dev.append(BME680Sensor( - sensor_handler, variable, SENSOR_TYPES[variable][1], name)) + dev.append( + BME680Sensor(sensor_handler, variable, SENSOR_TYPES[variable][1], name) + ) async_add_entities(dev) return @@ -112,7 +122,8 @@ async def async_setup_platform(hass, config, async_add_entities, def _setup_bme680(config): """Set up and configure the BME680 sensor.""" from smbus import SMBus # pylint: disable=import-error - bme680 = importlib.import_module('bme680') + + bme680 = importlib.import_module("bme680") sensor_handler = None sensor = None @@ -129,20 +140,12 @@ def _setup_bme680(config): 2: bme680.OS_2X, 4: bme680.OS_4X, 8: bme680.OS_8X, - 16: bme680.OS_16X + 16: bme680.OS_16X, } - sensor.set_temperature_oversample( - os_lookup[config.get(CONF_OVERSAMPLING_TEMP)] - ) - sensor.set_temp_offset( - config.get(CONF_TEMP_OFFSET) - ) - sensor.set_humidity_oversample( - os_lookup[config.get(CONF_OVERSAMPLING_HUM)] - ) - sensor.set_pressure_oversample( - os_lookup[config.get(CONF_OVERSAMPLING_PRES)] - ) + sensor.set_temperature_oversample(os_lookup[config.get(CONF_OVERSAMPLING_TEMP)]) + sensor.set_temp_offset(config.get(CONF_TEMP_OFFSET)) + sensor.set_humidity_oversample(os_lookup[config.get(CONF_OVERSAMPLING_HUM)]) + sensor.set_pressure_oversample(os_lookup[config.get(CONF_OVERSAMPLING_PRES)]) # Configure IIR Filter filter_lookup = { @@ -153,16 +156,14 @@ def _setup_bme680(config): 15: bme680.FILTER_SIZE_15, 31: bme680.FILTER_SIZE_31, 63: bme680.FILTER_SIZE_63, - 127: bme680.FILTER_SIZE_127 + 127: bme680.FILTER_SIZE_127, } - sensor.set_filter( - filter_lookup[config.get(CONF_FILTER_SIZE)] - ) + sensor.set_filter(filter_lookup[config.get(CONF_FILTER_SIZE)]) # Configure the Gas Heater if ( - SENSOR_GAS in config[CONF_MONITORED_CONDITIONS] or - SENSOR_AQ in config[CONF_MONITORED_CONDITIONS] + SENSOR_GAS in config[CONF_MONITORED_CONDITIONS] + or SENSOR_AQ in config[CONF_MONITORED_CONDITIONS] ): sensor.set_gas_status(bme680.ENABLE_GAS_MEAS) sensor.set_gas_heater_duration(config[CONF_GAS_HEATER_DURATION]) @@ -176,11 +177,13 @@ def _setup_bme680(config): sensor_handler = BME680Handler( sensor, - (SENSOR_GAS in config[CONF_MONITORED_CONDITIONS] or - SENSOR_AQ in config[CONF_MONITORED_CONDITIONS]), + ( + SENSOR_GAS in config[CONF_MONITORED_CONDITIONS] + or SENSOR_AQ in config[CONF_MONITORED_CONDITIONS] + ), config[CONF_AQ_BURN_IN_TIME], config[CONF_AQ_HUM_BASELINE], - config[CONF_AQ_HUM_WEIGHTING] + config[CONF_AQ_HUM_WEIGHTING], ) sleep(0.5) # Wait for device to stabilize if not sensor_handler.sensor_data.temperature: @@ -205,8 +208,12 @@ class BME680Handler: self.air_quality = None def __init__( - self, sensor, gas_measurement=False, - burn_in_time=300, hum_baseline=40, hum_weighting=25 + self, + sensor, + gas_measurement=False, + burn_in_time=300, + hum_baseline=40, + hum_weighting=25, ): """Initialize the sensor handler.""" self.sensor_data = BME680Handler.SensorData() @@ -218,10 +225,11 @@ class BME680Handler: if gas_measurement: import threading + threading.Thread( target=self._run_gas_sensor, - kwargs={'burn_in_time': burn_in_time}, - name='BME680Handler_run_gas_sensor' + kwargs={"burn_in_time": burn_in_time}, + name="BME680Handler_run_gas_sensor", ).start() self.update(first_read=True) @@ -239,34 +247,31 @@ class BME680Handler: curr_time = time() burn_in_data = [] - _LOGGER.info("Beginning %d second gas sensor burn in for Air Quality", - burn_in_time) + _LOGGER.info( + "Beginning %d second gas sensor burn in for Air Quality", burn_in_time + ) while curr_time - start_time < burn_in_time: curr_time = time() - if ( - self._sensor.get_sensor_data() and - self._sensor.data.heat_stable - ): + if self._sensor.get_sensor_data() and self._sensor.data.heat_stable: gas_resistance = self._sensor.data.gas_resistance burn_in_data.append(gas_resistance) self.sensor_data.gas_resistance = gas_resistance - _LOGGER.debug("AQ Gas Resistance Baseline reading %2f Ohms", - gas_resistance) + _LOGGER.debug( + "AQ Gas Resistance Baseline reading %2f Ohms", gas_resistance + ) sleep(1) - _LOGGER.debug("AQ Gas Resistance Burn In Data (Size: %d): \n\t%s", - len(burn_in_data), burn_in_data) + _LOGGER.debug( + "AQ Gas Resistance Burn In Data (Size: %d): \n\t%s", + len(burn_in_data), + burn_in_data, + ) self._gas_baseline = sum(burn_in_data[-50:]) / 50.0 _LOGGER.info("Completed gas sensor burn in for Air Quality") _LOGGER.info("AQ Gas Resistance Baseline: %f", self._gas_baseline) while True: - if ( - self._sensor.get_sensor_data() and - self._sensor.data.heat_stable - ): - self.sensor_data.gas_resistance = ( - self._sensor.data.gas_resistance - ) + if self._sensor.get_sensor_data() and self._sensor.data.heat_stable: + self.sensor_data.gas_resistance = self._sensor.data.gas_resistance self.sensor_data.air_quality = self._calculate_aq_score() sleep(1) @@ -295,16 +300,10 @@ class BME680Handler: # Calculate hum_score as the distance from the hum_baseline. if hum_offset > 0: hum_score = ( - (100 - hum_baseline - hum_offset) / - (100 - hum_baseline) * - hum_weighting + (100 - hum_baseline - hum_offset) / (100 - hum_baseline) * hum_weighting ) else: - hum_score = ( - (hum_baseline + hum_offset) / - hum_baseline * - hum_weighting - ) + hum_score = (hum_baseline + hum_offset) / hum_baseline * hum_weighting # Calculate gas_score as the distance from the gas_baseline. if gas_offset > 0: @@ -332,7 +331,7 @@ class BME680Sensor(Entity): @property def name(self): """Return the name of the sensor.""" - return '{} {}'.format(self.client_name, self._name) + return "{} {}".format(self.client_name, self._name) @property def state(self): @@ -357,9 +356,7 @@ class BME680Sensor(Entity): elif self.type == SENSOR_PRESS: self._state = round(self.bme680_client.sensor_data.pressure, 1) elif self.type == SENSOR_GAS: - self._state = int( - round(self.bme680_client.sensor_data.gas_resistance, 0) - ) + self._state = int(round(self.bme680_client.sensor_data.gas_resistance, 0)) elif self.type == SENSOR_AQ: aq_score = self.bme680_client.sensor_data.air_quality if aq_score is not None: diff --git a/homeassistant/components/bmw_connected_drive/__init__.py b/homeassistant/components/bmw_connected_drive/__init__.py index 10c58696740..9b44012e758 100644 --- a/homeassistant/components/bmw_connected_drive/__init__.py +++ b/homeassistant/components/bmw_connected_drive/__init__.py @@ -4,46 +4,41 @@ import logging import voluptuous as vol -from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD) +from homeassistant.const import CONF_USERNAME, CONF_PASSWORD from homeassistant.helpers import discovery from homeassistant.helpers.event import track_utc_time_change import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DOMAIN = 'bmw_connected_drive' -CONF_REGION = 'region' -CONF_READ_ONLY = 'read_only' -ATTR_VIN = 'vin' +DOMAIN = "bmw_connected_drive" +CONF_REGION = "region" +CONF_READ_ONLY = "read_only" +ATTR_VIN = "vin" -ACCOUNT_SCHEMA = vol.Schema({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_REGION): vol.Any('north_america', 'china', - 'rest_of_world'), - vol.Optional(CONF_READ_ONLY, default=False): cv.boolean, -}) +ACCOUNT_SCHEMA = vol.Schema( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_REGION): vol.Any("north_america", "china", "rest_of_world"), + vol.Optional(CONF_READ_ONLY, default=False): cv.boolean, + } +) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: { - cv.string: ACCOUNT_SCHEMA - }, -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema({DOMAIN: {cv.string: ACCOUNT_SCHEMA}}, extra=vol.ALLOW_EXTRA) -SERVICE_SCHEMA = vol.Schema({ - vol.Required(ATTR_VIN): cv.string, -}) +SERVICE_SCHEMA = vol.Schema({vol.Required(ATTR_VIN): cv.string}) -BMW_COMPONENTS = ['binary_sensor', 'device_tracker', 'lock', 'sensor'] +BMW_COMPONENTS = ["binary_sensor", "device_tracker", "lock", "sensor"] UPDATE_INTERVAL = 5 # in minutes -SERVICE_UPDATE_STATE = 'update_state' +SERVICE_UPDATE_STATE = "update_state" _SERVICE_MAP = { - 'light_flash': 'trigger_remote_light_flash', - 'sound_horn': 'trigger_remote_horn', - 'activate_air_conditioning': 'trigger_remote_air_conditioning', + "light_flash": "trigger_remote_light_flash", + "sound_horn": "trigger_remote_horn", + "activate_air_conditioning": "trigger_remote_air_conditioning", } @@ -71,17 +66,15 @@ def setup(hass, config: dict): return True -def setup_account(account_config: dict, hass, name: str) \ - -> 'BMWConnectedDriveAccount': +def setup_account(account_config: dict, hass, name: str) -> "BMWConnectedDriveAccount": """Set up a new BMWConnectedDriveAccount based on the config.""" username = account_config[CONF_USERNAME] password = account_config[CONF_PASSWORD] region = account_config[CONF_REGION] read_only = account_config[CONF_READ_ONLY] - _LOGGER.debug('Adding new account %s', name) - cd_account = BMWConnectedDriveAccount( - username, password, region, name, read_only) + _LOGGER.debug("Adding new account %s", name) + cd_account = BMWConnectedDriveAccount(username, password, region, name, read_only) def execute_service(call): """Execute a service for a vehicle. @@ -97,19 +90,23 @@ def setup_account(account_config: dict, hass, name: str) \ function_name = _SERVICE_MAP[call.service] function_call = getattr(vehicle.remote_services, function_name) function_call() + if not read_only: # register the remote services for service in _SERVICE_MAP: hass.services.register( - DOMAIN, service, execute_service, schema=SERVICE_SCHEMA) + DOMAIN, service, execute_service, schema=SERVICE_SCHEMA + ) # update every UPDATE_INTERVAL minutes, starting now # this should even out the load on the servers now = datetime.datetime.now() track_utc_time_change( - hass, cd_account.update, + hass, + cd_account.update, minute=range(now.minute % UPDATE_INTERVAL, 60, UPDATE_INTERVAL), - second=now.second) + second=now.second, + ) return cd_account @@ -117,8 +114,9 @@ def setup_account(account_config: dict, hass, name: str) \ class BMWConnectedDriveAccount: """Representation of a BMW vehicle.""" - def __init__(self, username: str, password: str, region_str: str, - name: str, read_only) -> None: + def __init__( + self, username: str, password: str, region_str: str, name: str, read_only + ) -> None: """Constructor.""" from bimmer_connected.account import ConnectedDriveAccount from bimmer_connected.country_selector import get_region_from_name @@ -137,7 +135,9 @@ class BMWConnectedDriveAccount: """ _LOGGER.debug( "Updating vehicle state for account %s, notifying %d listeners", - self.name, len(self._update_listeners)) + self.name, + len(self._update_listeners), + ) try: self.account.update_vehicle_states() for listener in self._update_listeners: diff --git a/homeassistant/components/bmw_connected_drive/binary_sensor.py b/homeassistant/components/bmw_connected_drive/binary_sensor.py index 8769fcf7d62..d52bec330fb 100644 --- a/homeassistant/components/bmw_connected_drive/binary_sensor.py +++ b/homeassistant/components/bmw_connected_drive/binary_sensor.py @@ -9,17 +9,17 @@ from . import DOMAIN as BMW_DOMAIN _LOGGER = logging.getLogger(__name__) SENSOR_TYPES = { - 'lids': ['Doors', 'opening'], - 'windows': ['Windows', 'opening'], - 'door_lock_state': ['Door lock state', 'safety'], - 'lights_parking': ['Parking lights', 'light'], - 'condition_based_services': ['Condition based services', 'problem'], - 'check_control_messages': ['Control messages', 'problem'] + "lids": ["Doors", "opening"], + "windows": ["Windows", "opening"], + "door_lock_state": ["Door lock state", "safety"], + "lights_parking": ["Parking lights", "light"], + "condition_based_services": ["Condition based services", "problem"], + "check_control_messages": ["Control messages", "problem"], } SENSOR_TYPES_ELEC = { - 'charging_status': ['Charging status', 'power'], - 'connection_status': ['Connection status', 'plug'] + "charging_status": ["Charging status", "power"], + "connection_status": ["Connection status", "plug"], } SENSOR_TYPES_ELEC.update(SENSOR_TYPES) @@ -28,22 +28,23 @@ SENSOR_TYPES_ELEC.update(SENSOR_TYPES) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the BMW sensors.""" accounts = hass.data[BMW_DOMAIN] - _LOGGER.debug('Found BMW accounts: %s', - ', '.join([a.name for a in accounts])) + _LOGGER.debug("Found BMW accounts: %s", ", ".join([a.name for a in accounts])) devices = [] for account in accounts: for vehicle in account.account.vehicles: if vehicle.has_hv_battery: - _LOGGER.debug('BMW with a high voltage battery') + _LOGGER.debug("BMW with a high voltage battery") for key, value in sorted(SENSOR_TYPES_ELEC.items()): device = BMWConnectedDriveSensor( - account, vehicle, key, value[0], value[1]) + account, vehicle, key, value[0], value[1] + ) devices.append(device) elif vehicle.has_internal_combustion_engine: - _LOGGER.debug('BMW with an internal combustion engine') + _LOGGER.debug("BMW with an internal combustion engine") for key, value in sorted(SENSOR_TYPES.items()): device = BMWConnectedDriveSensor( - account, vehicle, key, value[0], value[1]) + account, vehicle, key, value[0], value[1] + ) devices.append(device) add_entities(devices, True) @@ -51,14 +52,13 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class BMWConnectedDriveSensor(BinarySensorDevice): """Representation of a BMW vehicle binary sensor.""" - def __init__(self, account, vehicle, attribute: str, sensor_name, - device_class): + def __init__(self, account, vehicle, attribute: str, sensor_name, device_class): """Constructor.""" self._account = account self._vehicle = vehicle self._attribute = attribute - self._name = '{} {}'.format(self._vehicle.name, self._attribute) - self._unique_id = '{}-{}'.format(self._vehicle.vin, self._attribute) + self._name = "{} {}".format(self._vehicle.name, self._attribute) + self._unique_id = "{}-{}".format(self._vehicle.vin, self._attribute) self._sensor_name = sensor_name self._device_class = device_class self._state = None @@ -95,43 +95,40 @@ class BMWConnectedDriveSensor(BinarySensorDevice): def device_state_attributes(self): """Return the state attributes of the binary sensor.""" vehicle_state = self._vehicle.state - result = { - 'car': self._vehicle.name - } + result = {"car": self._vehicle.name} - if self._attribute == 'lids': + if self._attribute == "lids": for lid in vehicle_state.lids: result[lid.name] = lid.state.value - elif self._attribute == 'windows': + elif self._attribute == "windows": for window in vehicle_state.windows: result[window.name] = window.state.value - elif self._attribute == 'door_lock_state': - result['door_lock_state'] = vehicle_state.door_lock_state.value - result['last_update_reason'] = vehicle_state.last_update_reason - elif self._attribute == 'lights_parking': - result['lights_parking'] = vehicle_state.parking_lights.value - elif self._attribute == 'condition_based_services': + elif self._attribute == "door_lock_state": + result["door_lock_state"] = vehicle_state.door_lock_state.value + result["last_update_reason"] = vehicle_state.last_update_reason + elif self._attribute == "lights_parking": + result["lights_parking"] = vehicle_state.parking_lights.value + elif self._attribute == "condition_based_services": for report in vehicle_state.condition_based_services: - result.update( - self._format_cbs_report(report)) - elif self._attribute == 'check_control_messages': + result.update(self._format_cbs_report(report)) + elif self._attribute == "check_control_messages": check_control_messages = vehicle_state.check_control_messages if not check_control_messages: - result['check_control_messages'] = 'OK' + result["check_control_messages"] = "OK" else: cbs_list = [] for message in check_control_messages: - cbs_list.append(message['ccmDescriptionShort']) - result['check_control_messages'] = cbs_list - elif self._attribute == 'charging_status': - result['charging_status'] = vehicle_state.charging_status.value + cbs_list.append(message["ccmDescriptionShort"]) + result["check_control_messages"] = cbs_list + elif self._attribute == "charging_status": + result["charging_status"] = vehicle_state.charging_status.value # pylint: disable=protected-access - result['last_charging_end_result'] = \ - vehicle_state._attributes['lastChargingEndResult'] - if self._attribute == 'connection_status': + result["last_charging_end_result"] = vehicle_state._attributes[ + "lastChargingEndResult" + ] + if self._attribute == "connection_status": # pylint: disable=protected-access - result['connection_status'] = \ - vehicle_state._attributes['connectionStatus'] + result["connection_status"] = vehicle_state._attributes["connectionStatus"] return sorted(result.items()) @@ -139,50 +136,54 @@ class BMWConnectedDriveSensor(BinarySensorDevice): """Read new state data from the library.""" from bimmer_connected.state import LockState from bimmer_connected.state import ChargingState + vehicle_state = self._vehicle.state # device class opening: On means open, Off means closed - if self._attribute == 'lids': + if self._attribute == "lids": _LOGGER.debug("Status of lid: %s", vehicle_state.all_lids_closed) self._state = not vehicle_state.all_lids_closed - if self._attribute == 'windows': + if self._attribute == "windows": self._state = not vehicle_state.all_windows_closed # device class safety: On means unsafe, Off means safe - if self._attribute == 'door_lock_state': + if self._attribute == "door_lock_state": # Possible values: LOCKED, SECURED, SELECTIVE_LOCKED, UNLOCKED - self._state = vehicle_state.door_lock_state not in \ - [LockState.LOCKED, LockState.SECURED] + self._state = vehicle_state.door_lock_state not in [ + LockState.LOCKED, + LockState.SECURED, + ] # device class light: On means light detected, Off means no light - if self._attribute == 'lights_parking': + if self._attribute == "lights_parking": self._state = vehicle_state.are_parking_lights_on # device class problem: On means problem detected, Off means no problem - if self._attribute == 'condition_based_services': + if self._attribute == "condition_based_services": self._state = not vehicle_state.are_all_cbs_ok - if self._attribute == 'check_control_messages': + if self._attribute == "check_control_messages": self._state = vehicle_state.has_check_control_messages # device class power: On means power detected, Off means no power - if self._attribute == 'charging_status': - self._state = vehicle_state.charging_status in \ - [ChargingState.CHARGING] + if self._attribute == "charging_status": + self._state = vehicle_state.charging_status in [ChargingState.CHARGING] # device class plug: On means device is plugged in, # Off means device is unplugged - if self._attribute == 'connection_status': + if self._attribute == "connection_status": # pylint: disable=protected-access - self._state = (vehicle_state._attributes['connectionStatus'] == - 'CONNECTED') + self._state = vehicle_state._attributes["connectionStatus"] == "CONNECTED" def _format_cbs_report(self, report): result = {} - service_type = report.service_type.lower().replace('_', ' ') - result['{} status'.format(service_type)] = report.state.value + service_type = report.service_type.lower().replace("_", " ") + result["{} status".format(service_type)] = report.state.value if report.due_date is not None: - result['{} date'.format(service_type)] = \ - report.due_date.strftime('%Y-%m-%d') + result["{} date".format(service_type)] = report.due_date.strftime( + "%Y-%m-%d" + ) if report.due_distance is not None: - distance = round(self.hass.config.units.length( - report.due_distance, LENGTH_KILOMETERS)) - result['{} distance'.format(service_type)] = '{} {}'.format( - distance, self.hass.config.units.length_unit) + distance = round( + self.hass.config.units.length(report.due_distance, LENGTH_KILOMETERS) + ) + result["{} distance".format(service_type)] = "{} {}".format( + distance, self.hass.config.units.length_unit + ) return result def update_callback(self): diff --git a/homeassistant/components/bmw_connected_drive/device_tracker.py b/homeassistant/components/bmw_connected_drive/device_tracker.py index 229488186ae..c4e835af0eb 100644 --- a/homeassistant/components/bmw_connected_drive/device_tracker.py +++ b/homeassistant/components/bmw_connected_drive/device_tracker.py @@ -11,8 +11,7 @@ _LOGGER = logging.getLogger(__name__) def setup_scanner(hass, config, see, discovery_info=None): """Set up the BMW tracker.""" accounts = hass.data[BMW_DOMAIN] - _LOGGER.debug('Found BMW accounts: %s', - ', '.join([a.name for a in accounts])) + _LOGGER.debug("Found BMW accounts: %s", ", ".join([a.name for a in accounts])) for account in accounts: for vehicle in account.account.vehicles: tracker = BMWDeviceTracker(see, vehicle) @@ -38,15 +37,15 @@ class BMWDeviceTracker: dev_id = slugify(self.vehicle.name) if not self.vehicle.state.is_vehicle_tracking_enabled: - _LOGGER.debug('Tracking is disabled for vehicle %s', dev_id) + _LOGGER.debug("Tracking is disabled for vehicle %s", dev_id) return - _LOGGER.debug('Updating %s', dev_id) - attrs = { - 'vin': self.vehicle.vin, - } + _LOGGER.debug("Updating %s", dev_id) + attrs = {"vin": self.vehicle.vin} self._see( - dev_id=dev_id, host_name=self.vehicle.name, - gps=self.vehicle.state.gps_position, attributes=attrs, - icon='mdi:car' + dev_id=dev_id, + host_name=self.vehicle.name, + gps=self.vehicle.state.gps_position, + attributes=attrs, + icon="mdi:car", ) diff --git a/homeassistant/components/bmw_connected_drive/lock.py b/homeassistant/components/bmw_connected_drive/lock.py index 455e1427b05..a16dbc6b341 100644 --- a/homeassistant/components/bmw_connected_drive/lock.py +++ b/homeassistant/components/bmw_connected_drive/lock.py @@ -12,13 +12,12 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the BMW Connected Drive lock.""" accounts = hass.data[BMW_DOMAIN] - _LOGGER.debug('Found BMW accounts: %s', - ', '.join([a.name for a in accounts])) + _LOGGER.debug("Found BMW accounts: %s", ", ".join([a.name for a in accounts])) devices = [] for account in accounts: if not account.read_only: for vehicle in account.account.vehicles: - device = BMWLock(account, vehicle, 'lock', 'BMW lock') + device = BMWLock(account, vehicle, "lock", "BMW lock") devices.append(device) add_entities(devices, True) @@ -31,8 +30,8 @@ class BMWLock(LockDevice): self._account = account self._vehicle = vehicle self._attribute = attribute - self._name = '{} {}'.format(self._vehicle.name, self._attribute) - self._unique_id = '{}-{}'.format(self._vehicle.vin, self._attribute) + self._name = "{} {}".format(self._vehicle.name, self._attribute) + self._unique_id = "{}-{}".format(self._vehicle.vin, self._attribute) self._sensor_name = sensor_name self._state = None @@ -59,8 +58,8 @@ class BMWLock(LockDevice): """Return the state attributes of the lock.""" vehicle_state = self._vehicle.state return { - 'car': self._vehicle.name, - 'door_lock_state': vehicle_state.door_lock_state.value + "car": self._vehicle.name, + "door_lock_state": vehicle_state.door_lock_state.value, } @property @@ -90,15 +89,15 @@ class BMWLock(LockDevice): """Update state of the lock.""" from bimmer_connected.state import LockState - _LOGGER.debug("%s: updating data for %s", self._vehicle.name, - self._attribute) + _LOGGER.debug("%s: updating data for %s", self._vehicle.name, self._attribute) vehicle_state = self._vehicle.state # Possible values: LOCKED, SECURED, SELECTIVE_LOCKED, UNLOCKED - self._state = STATE_LOCKED \ - if vehicle_state.door_lock_state \ - in [LockState.LOCKED, LockState.SECURED] \ + self._state = ( + STATE_LOCKED + if vehicle_state.door_lock_state in [LockState.LOCKED, LockState.SECURED] else STATE_UNLOCKED + ) def update_callback(self): """Schedule a state update.""" diff --git a/homeassistant/components/bmw_connected_drive/sensor.py b/homeassistant/components/bmw_connected_drive/sensor.py index 4d8b7adde1b..bc133fa4034 100644 --- a/homeassistant/components/bmw_connected_drive/sensor.py +++ b/homeassistant/components/bmw_connected_drive/sensor.py @@ -2,8 +2,12 @@ import logging from homeassistant.const import ( - CONF_UNIT_SYSTEM_IMPERIAL, LENGTH_KILOMETERS, LENGTH_MILES, VOLUME_GALLONS, - VOLUME_LITERS) + CONF_UNIT_SYSTEM_IMPERIAL, + LENGTH_KILOMETERS, + LENGTH_MILES, + VOLUME_GALLONS, + VOLUME_LITERS, +) from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level @@ -12,25 +16,25 @@ from . import DOMAIN as BMW_DOMAIN _LOGGER = logging.getLogger(__name__) ATTR_TO_HA_METRIC = { - 'mileage': ['mdi:speedometer', LENGTH_KILOMETERS], - 'remaining_range_total': ['mdi:ruler', LENGTH_KILOMETERS], - 'remaining_range_electric': ['mdi:ruler', LENGTH_KILOMETERS], - 'remaining_range_fuel': ['mdi:ruler', LENGTH_KILOMETERS], - 'max_range_electric': ['mdi:ruler', LENGTH_KILOMETERS], - 'remaining_fuel': ['mdi:gas-station', VOLUME_LITERS], - 'charging_time_remaining': ['mdi:update', 'h'], - 'charging_status': ['mdi:battery-charging', None], + "mileage": ["mdi:speedometer", LENGTH_KILOMETERS], + "remaining_range_total": ["mdi:ruler", LENGTH_KILOMETERS], + "remaining_range_electric": ["mdi:ruler", LENGTH_KILOMETERS], + "remaining_range_fuel": ["mdi:ruler", LENGTH_KILOMETERS], + "max_range_electric": ["mdi:ruler", LENGTH_KILOMETERS], + "remaining_fuel": ["mdi:gas-station", VOLUME_LITERS], + "charging_time_remaining": ["mdi:update", "h"], + "charging_status": ["mdi:battery-charging", None], } ATTR_TO_HA_IMPERIAL = { - 'mileage': ['mdi:speedometer', LENGTH_MILES], - 'remaining_range_total': ['mdi:ruler', LENGTH_MILES], - 'remaining_range_electric': ['mdi:ruler', LENGTH_MILES], - 'remaining_range_fuel': ['mdi:ruler', LENGTH_MILES], - 'max_range_electric': ['mdi:ruler', LENGTH_MILES], - 'remaining_fuel': ['mdi:gas-station', VOLUME_GALLONS], - 'charging_time_remaining': ['mdi:update', 'h'], - 'charging_status': ['mdi:battery-charging', None], + "mileage": ["mdi:speedometer", LENGTH_MILES], + "remaining_range_total": ["mdi:ruler", LENGTH_MILES], + "remaining_range_electric": ["mdi:ruler", LENGTH_MILES], + "remaining_range_fuel": ["mdi:ruler", LENGTH_MILES], + "max_range_electric": ["mdi:ruler", LENGTH_MILES], + "remaining_fuel": ["mdi:gas-station", VOLUME_GALLONS], + "charging_time_remaining": ["mdi:update", "h"], + "charging_status": ["mdi:battery-charging", None], } @@ -42,17 +46,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None): attribute_info = ATTR_TO_HA_METRIC accounts = hass.data[BMW_DOMAIN] - _LOGGER.debug('Found BMW accounts: %s', - ', '.join([a.name for a in accounts])) + _LOGGER.debug("Found BMW accounts: %s", ", ".join([a.name for a in accounts])) devices = [] for account in accounts: for vehicle in account.account.vehicles: for attribute_name in vehicle.drive_train_attributes: device = BMWConnectedDriveSensor( - account, vehicle, attribute_name, attribute_info) + account, vehicle, attribute_name, attribute_info + ) devices.append(device) device = BMWConnectedDriveSensor( - account, vehicle, 'mileage', attribute_info) + account, vehicle, "mileage", attribute_info + ) devices.append(device) add_entities(devices, True) @@ -66,8 +71,8 @@ class BMWConnectedDriveSensor(Entity): self._account = account self._attribute = attribute self._state = None - self._name = '{} {}'.format(self._vehicle.name, self._attribute) - self._unique_id = '{}-{}'.format(self._vehicle.vin, self._attribute) + self._name = "{} {}".format(self._vehicle.name, self._attribute) + self._unique_id = "{}-{}".format(self._vehicle.vin, self._attribute) self._attribute_info = attribute_info @property @@ -92,14 +97,14 @@ class BMWConnectedDriveSensor(Entity): def icon(self): """Icon to use in the frontend, if any.""" from bimmer_connected.state import ChargingState - vehicle_state = self._vehicle.state - charging_state = vehicle_state.charging_status in [ - ChargingState.CHARGING] - if self._attribute == 'charging_level_hv': + vehicle_state = self._vehicle.state + charging_state = vehicle_state.charging_status in [ChargingState.CHARGING] + + if self._attribute == "charging_level_hv": return icon_for_battery_level( - battery_level=vehicle_state.charging_level_hv, - charging=charging_state) + battery_level=vehicle_state.charging_level_hv, charging=charging_state + ) icon, _ = self._attribute_info.get(self._attribute, [None, None]) return icon @@ -121,25 +126,21 @@ class BMWConnectedDriveSensor(Entity): @property def device_state_attributes(self): """Return the state attributes of the sensor.""" - return { - 'car': self._vehicle.name - } + return {"car": self._vehicle.name} def update(self) -> None: """Read new state data from the library.""" - _LOGGER.debug('Updating %s', self._vehicle.name) + _LOGGER.debug("Updating %s", self._vehicle.name) vehicle_state = self._vehicle.state - if self._attribute == 'charging_status': + if self._attribute == "charging_status": self._state = getattr(vehicle_state, self._attribute).value elif self.unit_of_measurement == VOLUME_GALLONS: value = getattr(vehicle_state, self._attribute) - value_converted = self.hass.config.units.volume( - value, VOLUME_LITERS) + value_converted = self.hass.config.units.volume(value, VOLUME_LITERS) self._state = round(value_converted) elif self.unit_of_measurement == LENGTH_MILES: value = getattr(vehicle_state, self._attribute) - value_converted = self.hass.config.units.length( - value, LENGTH_KILOMETERS) + value_converted = self.hass.config.units.length(value, LENGTH_KILOMETERS) self._state = round(value_converted) else: self._state = getattr(vehicle_state, self._attribute) diff --git a/homeassistant/components/bom/camera.py b/homeassistant/components/bom/camera.py index 87ffd4ab791..3a5d6cdc503 100644 --- a/homeassistant/components/bom/camera.py +++ b/homeassistant/components/bom/camera.py @@ -5,22 +5,68 @@ from homeassistant.components.camera import PLATFORM_SCHEMA, Camera from homeassistant.const import CONF_ID, CONF_NAME from homeassistant.helpers import config_validation as cv -CONF_DELTA = 'delta' -CONF_FRAMES = 'frames' -CONF_LOCATION = 'location' -CONF_OUTFILE = 'filename' +CONF_DELTA = "delta" +CONF_FRAMES = "frames" +CONF_LOCATION = "location" +CONF_OUTFILE = "filename" LOCATIONS = [ - 'Adelaide', 'Albany', 'AliceSprings', 'Bairnsdale', 'Bowen', 'Brisbane', - 'Broome', 'Cairns', 'Canberra', 'Carnarvon', 'Ceduna', 'Dampier', 'Darwin', - 'Emerald', 'Esperance', 'Geraldton', 'Giles', 'Gladstone', 'Gove', - 'Grafton', 'Gympie', 'HallsCreek', 'Hobart', 'Kalgoorlie', 'Katherine', - 'Learmonth', 'Longreach', 'Mackay', 'Marburg', 'Melbourne', 'Mildura', - 'Moree', 'MorningtonIs', 'MountIsa', 'MtGambier', 'Namoi', 'Newcastle', - 'Newdegate', 'NorfolkIs', 'NWTasmania', 'Perth', 'PortHedland', - 'SellicksHill', 'SouthDoodlakine', 'Sydney', 'Townsville', 'WaggaWagga', - 'Warrego', 'Warruwi', 'Watheroo', 'Weipa', 'WillisIs', 'Wollongong', - 'Woomera', 'Wyndham', 'Yarrawonga', + "Adelaide", + "Albany", + "AliceSprings", + "Bairnsdale", + "Bowen", + "Brisbane", + "Broome", + "Cairns", + "Canberra", + "Carnarvon", + "Ceduna", + "Dampier", + "Darwin", + "Emerald", + "Esperance", + "Geraldton", + "Giles", + "Gladstone", + "Gove", + "Grafton", + "Gympie", + "HallsCreek", + "Hobart", + "Kalgoorlie", + "Katherine", + "Learmonth", + "Longreach", + "Mackay", + "Marburg", + "Melbourne", + "Mildura", + "Moree", + "MorningtonIs", + "MountIsa", + "MtGambier", + "Namoi", + "Newcastle", + "Newdegate", + "NorfolkIs", + "NWTasmania", + "Perth", + "PortHedland", + "SellicksHill", + "SouthDoodlakine", + "Sydney", + "Townsville", + "WaggaWagga", + "Warrego", + "Warruwi", + "Watheroo", + "Weipa", + "WillisIs", + "Wollongong", + "Woomera", + "Wyndham", + "Yarrawonga", ] @@ -29,32 +75,42 @@ def _validate_schema(config): if not all(config.get(x) for x in (CONF_ID, CONF_DELTA, CONF_FRAMES)): raise vol.Invalid( "Specify '{}', '{}' and '{}' when '{}' is unspecified".format( - CONF_ID, CONF_DELTA, CONF_FRAMES, CONF_LOCATION)) + CONF_ID, CONF_DELTA, CONF_FRAMES, CONF_LOCATION + ) + ) return config LOCATIONS_MSG = "Set '{}' to one of: {}".format( - CONF_LOCATION, ', '.join(sorted(LOCATIONS))) + CONF_LOCATION, ", ".join(sorted(LOCATIONS)) +) XOR_MSG = "Specify exactly one of '{}' or '{}'".format(CONF_ID, CONF_LOCATION) PLATFORM_SCHEMA = vol.All( - PLATFORM_SCHEMA.extend({ - vol.Exclusive(CONF_ID, 'xor', msg=XOR_MSG): cv.string, - vol.Exclusive(CONF_LOCATION, 'xor', msg=XOR_MSG): vol.In( - LOCATIONS, msg=LOCATIONS_MSG), - vol.Optional(CONF_DELTA): cv.positive_int, - vol.Optional(CONF_FRAMES): cv.positive_int, - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_OUTFILE): cv.string, - }), _validate_schema) + PLATFORM_SCHEMA.extend( + { + vol.Exclusive(CONF_ID, "xor", msg=XOR_MSG): cv.string, + vol.Exclusive(CONF_LOCATION, "xor", msg=XOR_MSG): vol.In( + LOCATIONS, msg=LOCATIONS_MSG + ), + vol.Optional(CONF_DELTA): cv.positive_int, + vol.Optional(CONF_FRAMES): cv.positive_int, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_OUTFILE): cv.string, + } + ), + _validate_schema, +) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up BOM radar-loop camera component.""" location = config.get(CONF_LOCATION) or "ID {}".format(config.get(CONF_ID)) name = config.get(CONF_NAME) or "BOM Radar Loop - {}".format(location) - args = [config.get(x) for x in (CONF_LOCATION, CONF_ID, CONF_DELTA, - CONF_FRAMES, CONF_OUTFILE)] + args = [ + config.get(x) + for x in (CONF_LOCATION, CONF_ID, CONF_DELTA, CONF_FRAMES, CONF_OUTFILE) + ] add_entities([BOMRadarCam(name, *args)]) @@ -64,6 +120,7 @@ class BOMRadarCam(Camera): def __init__(self, name, location, radar_id, delta, frames, outfile): """Initialize the component.""" from bomradarloop import BOMRadarLoop + super().__init__() self._name = name self._cam = BOMRadarLoop(location, radar_id, delta, frames, outfile) diff --git a/homeassistant/components/bom/sensor.py b/homeassistant/components/bom/sensor.py index 4c96315ec1f..790b2ddc74f 100644 --- a/homeassistant/components/bom/sensor.py +++ b/homeassistant/components/bom/sensor.py @@ -15,63 +15,68 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_MONITORED_CONDITIONS, TEMP_CELSIUS, CONF_NAME, ATTR_ATTRIBUTION, - CONF_LATITUDE, CONF_LONGITUDE) + CONF_MONITORED_CONDITIONS, + TEMP_CELSIUS, + CONF_NAME, + ATTR_ATTRIBUTION, + CONF_LATITUDE, + CONF_LONGITUDE, +) from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle -_RESOURCE = 'http://www.bom.gov.au/fwo/{}/{}.{}.json' +_RESOURCE = "http://www.bom.gov.au/fwo/{}/{}.{}.json" _LOGGER = logging.getLogger(__name__) -ATTR_LAST_UPDATE = 'last_update' -ATTR_SENSOR_ID = 'sensor_id' -ATTR_STATION_ID = 'station_id' -ATTR_STATION_NAME = 'station_name' -ATTR_ZONE_ID = 'zone_id' +ATTR_LAST_UPDATE = "last_update" +ATTR_SENSOR_ID = "sensor_id" +ATTR_STATION_ID = "station_id" +ATTR_STATION_NAME = "station_name" +ATTR_ZONE_ID = "zone_id" ATTRIBUTION = "Data provided by the Australian Bureau of Meteorology" -CONF_STATION = 'station' -CONF_ZONE_ID = 'zone_id' -CONF_WMO_ID = 'wmo_id' +CONF_STATION = "station" +CONF_ZONE_ID = "zone_id" +CONF_WMO_ID = "wmo_id" MIN_TIME_BETWEEN_UPDATES = datetime.timedelta(seconds=60) SENSOR_TYPES = { - 'wmo': ['wmo', None], - 'name': ['Station Name', None], - 'history_product': ['Zone', None], - 'local_date_time': ['Local Time', None], - 'local_date_time_full': ['Local Time Full', None], - 'aifstime_utc': ['UTC Time Full', None], - 'lat': ['Lat', None], - 'lon': ['Long', None], - 'apparent_t': ['Feels Like C', TEMP_CELSIUS], - 'cloud': ['Cloud', None], - 'cloud_base_m': ['Cloud Base', None], - 'cloud_oktas': ['Cloud Oktas', None], - 'cloud_type_id': ['Cloud Type ID', None], - 'cloud_type': ['Cloud Type', None], - 'delta_t': ['Delta Temp C', TEMP_CELSIUS], - 'gust_kmh': ['Wind Gust kmh', 'km/h'], - 'gust_kt': ['Wind Gust kt', 'kt'], - 'air_temp': ['Air Temp C', TEMP_CELSIUS], - 'dewpt': ['Dew Point C', TEMP_CELSIUS], - 'press': ['Pressure mb', 'mbar'], - 'press_qnh': ['Pressure qnh', 'qnh'], - 'press_msl': ['Pressure msl', 'msl'], - 'press_tend': ['Pressure Tend', None], - 'rain_trace': ['Rain Today', 'mm'], - 'rel_hum': ['Relative Humidity', '%'], - 'sea_state': ['Sea State', None], - 'swell_dir_worded': ['Swell Direction', None], - 'swell_height': ['Swell Height', 'm'], - 'swell_period': ['Swell Period', None], - 'vis_km': ['Visability km', 'km'], - 'weather': ['Weather', None], - 'wind_dir': ['Wind Direction', None], - 'wind_spd_kmh': ['Wind Speed kmh', 'km/h'], - 'wind_spd_kt': ['Wind Speed kt', 'kt'] + "wmo": ["wmo", None], + "name": ["Station Name", None], + "history_product": ["Zone", None], + "local_date_time": ["Local Time", None], + "local_date_time_full": ["Local Time Full", None], + "aifstime_utc": ["UTC Time Full", None], + "lat": ["Lat", None], + "lon": ["Long", None], + "apparent_t": ["Feels Like C", TEMP_CELSIUS], + "cloud": ["Cloud", None], + "cloud_base_m": ["Cloud Base", None], + "cloud_oktas": ["Cloud Oktas", None], + "cloud_type_id": ["Cloud Type ID", None], + "cloud_type": ["Cloud Type", None], + "delta_t": ["Delta Temp C", TEMP_CELSIUS], + "gust_kmh": ["Wind Gust kmh", "km/h"], + "gust_kt": ["Wind Gust kt", "kt"], + "air_temp": ["Air Temp C", TEMP_CELSIUS], + "dewpt": ["Dew Point C", TEMP_CELSIUS], + "press": ["Pressure mb", "mbar"], + "press_qnh": ["Pressure qnh", "qnh"], + "press_msl": ["Pressure msl", "msl"], + "press_tend": ["Pressure Tend", None], + "rain_trace": ["Rain Today", "mm"], + "rel_hum": ["Relative Humidity", "%"], + "sea_state": ["Sea State", None], + "swell_dir_worded": ["Swell Direction", None], + "swell_height": ["Swell Height", "m"], + "swell_period": ["Swell Period", None], + "vis_km": ["Visability km", "km"], + "weather": ["Weather", None], + "wind_dir": ["Wind Direction", None], + "wind_spd_kmh": ["Wind Speed kmh", "km/h"], + "wind_spd_kt": ["Wind Speed kt", "kt"], } @@ -79,20 +84,23 @@ def validate_station(station): """Check that the station ID is well-formed.""" if station is None: return - station = station.replace('.shtml', '') - if not re.fullmatch(r'ID[A-Z]\d\d\d\d\d\.\d\d\d\d\d', station): - raise vol.error.Invalid('Malformed station ID') + station = station.replace(".shtml", "") + if not re.fullmatch(r"ID[A-Z]\d\d\d\d\d\.\d\d\d\d\d", station): + raise vol.error.Invalid("Malformed station ID") return station -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Inclusive(CONF_ZONE_ID, 'Deprecated partial station ID'): cv.string, - vol.Inclusive(CONF_WMO_ID, 'Deprecated partial station ID'): cv.string, - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_STATION): validate_station, - vol.Required(CONF_MONITORED_CONDITIONS, default=[]): - vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Inclusive(CONF_ZONE_ID, "Deprecated partial station ID"): cv.string, + vol.Inclusive(CONF_WMO_ID, "Deprecated partial station ID"): cv.string, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_STATION): validate_station, + vol.Required(CONF_MONITORED_CONDITIONS, default=[]): vol.All( + cv.ensure_list, [vol.In(SENSOR_TYPES)] + ), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -104,13 +112,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if zone_id and wmo_id: _LOGGER.warning( "Using config %s, not %s and %s for BOM sensor", - CONF_STATION, CONF_ZONE_ID, CONF_WMO_ID) + CONF_STATION, + CONF_ZONE_ID, + CONF_WMO_ID, + ) elif zone_id and wmo_id: - station = '{}.{}'.format(zone_id, wmo_id) + station = "{}.{}".format(zone_id, wmo_id) else: station = closest_station( - config.get(CONF_LATITUDE), config.get(CONF_LONGITUDE), - hass.config.config_dir) + config.get(CONF_LATITUDE), + config.get(CONF_LONGITUDE), + hass.config.config_dir, + ) if station is None: _LOGGER.error("Could not get BOM weather station from lat/lon") return @@ -123,8 +136,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None): _LOGGER.error("Received error from BOM Current: %s", err) return - add_entities([BOMCurrentSensor(bom_data, variable, config.get(CONF_NAME)) - for variable in config[CONF_MONITORED_CONDITIONS]]) + add_entities( + [ + BOMCurrentSensor(bom_data, variable, config.get(CONF_NAME)) + for variable in config[CONF_MONITORED_CONDITIONS] + ] + ) class BOMCurrentSensor(Entity): @@ -140,10 +157,9 @@ class BOMCurrentSensor(Entity): def name(self): """Return the name of the sensor.""" if self.stationname is None: - return 'BOM {}'.format(SENSOR_TYPES[self._condition][0]) + return "BOM {}".format(SENSOR_TYPES[self._condition][0]) - return 'BOM {} {}'.format( - self.stationname, SENSOR_TYPES[self._condition][0]) + return "BOM {} {}".format(self.stationname, SENSOR_TYPES[self._condition][0]) @property def state(self): @@ -157,9 +173,9 @@ class BOMCurrentSensor(Entity): ATTR_ATTRIBUTION: ATTRIBUTION, ATTR_LAST_UPDATE: self.bom_data.last_updated, ATTR_SENSOR_ID: self._condition, - ATTR_STATION_ID: self.bom_data.latest_data['wmo'], - ATTR_STATION_NAME: self.bom_data.latest_data['name'], - ATTR_ZONE_ID: self.bom_data.latest_data['history_product'], + ATTR_STATION_ID: self.bom_data.latest_data["wmo"], + ATTR_STATION_NAME: self.bom_data.latest_data["name"], + ATTR_ZONE_ID: self.bom_data.latest_data["history_product"], } return attr @@ -179,7 +195,7 @@ class BOMCurrentData: def __init__(self, station_id): """Initialize the data object.""" - self._zone_id, self._wmo_id = station_id.split('.') + self._zone_id, self._wmo_id = station_id.split(".") self._data = None self.last_updated = None @@ -208,7 +224,7 @@ class BOMCurrentData: through the entire BOM provided dataset. """ condition_readings = (entry[condition] for entry in self._data) - return next((x for x in condition_readings if x != '-'), None) + return next((x for x in condition_readings if x != "-"), None) def should_update(self): """Determine whether an update should occur. @@ -236,17 +252,20 @@ class BOMCurrentData: "BOM was updated %s minutes ago, skipping update as" " < 35 minutes, Now: %s, LastUpdate: %s", (datetime.datetime.now() - self.last_updated), - datetime.datetime.now(), self.last_updated) + datetime.datetime.now(), + self.last_updated, + ) return try: result = requests.get(self._build_url(), timeout=10).json() - self._data = result['observations']['data'] + self._data = result["observations"]["data"] # set lastupdate using self._data[0] as the first element in the # array is the latest date in the json self.last_updated = datetime.datetime.strptime( - str(self._data[0]['local_date_time_full']), '%Y%m%d%H%M%S') + str(self._data[0]["local_date_time_full"]), "%Y%m%d%H%M%S" + ) return except ValueError as err: @@ -263,33 +282,34 @@ def _get_bom_stations(): """ latlon = {} with io.BytesIO() as file_obj: - with ftplib.FTP('ftp.bom.gov.au') as ftp: + with ftplib.FTP("ftp.bom.gov.au") as ftp: ftp.login() - ftp.cwd('anon2/home/ncc/metadata/sitelists') - ftp.retrbinary('RETR stations.zip', file_obj.write) + ftp.cwd("anon2/home/ncc/metadata/sitelists") + ftp.retrbinary("RETR stations.zip", file_obj.write) file_obj.seek(0) with zipfile.ZipFile(file_obj) as zipped: - with zipped.open('stations.txt') as station_txt: + with zipped.open("stations.txt") as station_txt: for _ in range(4): station_txt.readline() # skip header while True: line = station_txt.readline().decode().strip() if len(line) < 120: break # end while loop, ignoring any footer text - wmo, lat, lon = (line[a:b].strip() for a, b in - [(128, 134), (70, 78), (79, 88)]) - if wmo != '..': + wmo, lat, lon = ( + line[a:b].strip() for a, b in [(128, 134), (70, 78), (79, 88)] + ) + if wmo != "..": latlon[wmo] = (float(lat), float(lon)) zones = {} - pattern = (r'') - for state in ('nsw', 'vic', 'qld', 'wa', 'tas', 'nt'): - url = 'http://www.bom.gov.au/{0}/observations/{0}all.shtml'.format( - state) + pattern = ( + r'' + ) + for state in ("nsw", "vic", "qld", "wa", "tas", "nt"): + url = "http://www.bom.gov.au/{0}/observations/{0}all.shtml".format(state) for zone_id, wmo_id in re.findall(pattern, requests.get(url).text): zones[wmo_id] = zone_id - return {'{}.{}'.format(zones[k], k): latlon[k] - for k in set(latlon) & set(zones)} + return {"{}.{}".format(zones[k], k): latlon[k] for k in set(latlon) & set(zones)} def bom_stations(cache_dir): @@ -298,13 +318,13 @@ def bom_stations(cache_dir): Results from internet requests are cached as compressed JSON, making subsequent calls very much faster. """ - cache_file = os.path.join(cache_dir, '.bom-stations.json.gz') + cache_file = os.path.join(cache_dir, ".bom-stations.json.gz") if not os.path.isfile(cache_file): stations = _get_bom_stations() - with gzip.open(cache_file, 'wt') as cache: + with gzip.open(cache_file, "wt") as cache: json.dump(stations, cache, sort_keys=True) return stations - with gzip.open(cache_file, 'rt') as cache: + with gzip.open(cache_file, "rt") as cache: return {k: tuple(v) for k, v in json.load(cache).items()} diff --git a/homeassistant/components/bom/weather.py b/homeassistant/components/bom/weather.py index 2444192d87d..2513c7c4c40 100644 --- a/homeassistant/components/bom/weather.py +++ b/homeassistant/components/bom/weather.py @@ -4,28 +4,24 @@ import logging import voluptuous as vol from homeassistant.components.weather import PLATFORM_SCHEMA, WeatherEntity -from homeassistant.const import ( - CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, TEMP_CELSIUS) +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, TEMP_CELSIUS from homeassistant.helpers import config_validation as cv # Reuse data and API logic from the sensor implementation -from .sensor import ( - CONF_STATION, BOMCurrentData, closest_station, validate_station) +from .sensor import CONF_STATION, BOMCurrentData, closest_station, validate_station _LOGGER = logging.getLogger(__name__) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_STATION): validate_station, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_STATION): validate_station} +) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the BOM weather platform.""" station = config.get(CONF_STATION) or closest_station( - config.get(CONF_LATITUDE), - config.get(CONF_LONGITUDE), - hass.config.config_dir) + config.get(CONF_LATITUDE), config.get(CONF_LONGITUDE), hass.config.config_dir + ) if station is None: _LOGGER.error("Could not get BOM weather station from lat/lon") return False @@ -44,7 +40,7 @@ class BOMWeather(WeatherEntity): def __init__(self, bom_data, stationname=None): """Initialise the platform with a data instance and station name.""" self.bom_data = bom_data - self.stationname = stationname or self.bom_data.latest_data.get('name') + self.stationname = stationname or self.bom_data.latest_data.get("name") def update(self): """Update current conditions.""" @@ -53,19 +49,19 @@ class BOMWeather(WeatherEntity): @property def name(self): """Return the name of the sensor.""" - return 'BOM {}'.format(self.stationname or '(unknown station)') + return "BOM {}".format(self.stationname or "(unknown station)") @property def condition(self): """Return the current condition.""" - return self.bom_data.get_reading('weather') + return self.bom_data.get_reading("weather") # Now implement the WeatherEntity interface @property def temperature(self): """Return the platform temperature.""" - return self.bom_data.get_reading('air_temp') + return self.bom_data.get_reading("air_temp") @property def temperature_unit(self): @@ -75,27 +71,41 @@ class BOMWeather(WeatherEntity): @property def pressure(self): """Return the mean sea-level pressure.""" - return self.bom_data.get_reading('press_msl') + return self.bom_data.get_reading("press_msl") @property def humidity(self): """Return the relative humidity.""" - return self.bom_data.get_reading('rel_hum') + return self.bom_data.get_reading("rel_hum") @property def wind_speed(self): """Return the wind speed.""" - return self.bom_data.get_reading('wind_spd_kmh') + return self.bom_data.get_reading("wind_spd_kmh") @property def wind_bearing(self): """Return the wind bearing.""" - directions = ['N', 'NNE', 'NE', 'ENE', - 'E', 'ESE', 'SE', 'SSE', - 'S', 'SSW', 'SW', 'WSW', - 'W', 'WNW', 'NW', 'NNW'] + directions = [ + "N", + "NNE", + "NE", + "ENE", + "E", + "ESE", + "SE", + "SSE", + "S", + "SSW", + "SW", + "WSW", + "W", + "WNW", + "NW", + "NNW", + ] wind = {name: idx * 360 / 16 for idx, name in enumerate(directions)} - return wind.get(self.bom_data.get_reading('wind_dir')) + return wind.get(self.bom_data.get_reading("wind_dir")) @property def attribution(self): diff --git a/homeassistant/components/braviatv/media_player.py b/homeassistant/components/braviatv/media_player.py index 637e2922222..5072cb7b74c 100644 --- a/homeassistant/components/braviatv/media_player.py +++ b/homeassistant/components/braviatv/media_player.py @@ -5,40 +5,55 @@ import logging from getmac import get_mac_address import voluptuous as vol -from homeassistant.components.media_player import ( - MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA from homeassistant.components.media_player.const import ( - SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, - SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP) + SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, + SUPPORT_PLAY, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_SELECT_SOURCE, + SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, + SUPPORT_VOLUME_MUTE, + SUPPORT_VOLUME_SET, + SUPPORT_VOLUME_STEP, +) from homeassistant.const import CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON import homeassistant.helpers.config_validation as cv from homeassistant.util.json import load_json, save_json -BRAVIA_CONFIG_FILE = 'bravia.conf' +BRAVIA_CONFIG_FILE = "bravia.conf" -CLIENTID_PREFIX = 'HomeAssistant' +CLIENTID_PREFIX = "HomeAssistant" -DEFAULT_NAME = 'Sony Bravia TV' +DEFAULT_NAME = "Sony Bravia TV" -NICKNAME = 'Home Assistant' +NICKNAME = "Home Assistant" # Map ip to request id for configuring _CONFIGURING = {} _LOGGER = logging.getLogger(__name__) -SUPPORT_BRAVIA = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \ - SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET | \ - SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \ - SUPPORT_TURN_ON | SUPPORT_TURN_OFF | \ - SUPPORT_SELECT_SOURCE | SUPPORT_PLAY +SUPPORT_BRAVIA = ( + SUPPORT_PAUSE + | SUPPORT_VOLUME_STEP + | SUPPORT_VOLUME_MUTE + | SUPPORT_VOLUME_SET + | SUPPORT_PREVIOUS_TRACK + | SUPPORT_NEXT_TRACK + | SUPPORT_TURN_ON + | SUPPORT_TURN_OFF + | SUPPORT_SELECT_SOURCE + | SUPPORT_PLAY +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -54,8 +69,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): # Set up a configured TV host_ip, host_config = bravia_config.popitem() if host_ip == host: - pin = host_config['pin'] - mac = host_config['mac'] + pin = host_config["pin"] + mac = host_config["mac"] name = config.get(CONF_NAME) add_entities([BraviaTVDevice(host, mac, name, pin)]) return @@ -74,11 +89,11 @@ def setup_bravia(config, pin, hass, add_entities): try: if ipaddress.ip_address(host).version == 6: - mode = 'ip6' + mode = "ip6" else: - mode = 'ip' + mode = "ip" except ValueError: - mode = 'hostname' + mode = "hostname" mac = get_mac_address(**{mode: host}) # If we came here and configuring this host, mark as done @@ -91,7 +106,8 @@ def setup_bravia(config, pin, hass, add_entities): # Save config save_json( hass.config.path(BRAVIA_CONFIG_FILE), - {host: {'pin': pin, 'host': host, 'mac': mac}}) + {host: {"pin": pin, "host": host, "mac": mac}}, + ) add_entities([BraviaTVDevice(host, mac, name, pin)]) @@ -106,14 +122,15 @@ def request_configuration(config, hass, add_entities): # We got an error if this method is called while we are configuring if host in _CONFIGURING: configurator.notify_errors( - _CONFIGURING[host], "Failed to register, please try again.") + _CONFIGURING[host], "Failed to register, please try again." + ) return def bravia_configuration_callback(data): """Handle the entry of user PIN.""" from braviarc import braviarc - pin = data.get('pin') + pin = data.get("pin") braviarc = braviarc.BraviaRC(host) braviarc.connect(pin, CLIENTID_PREFIX, NICKNAME) if braviarc.is_connected(): @@ -122,12 +139,13 @@ def request_configuration(config, hass, add_entities): request_configuration(config, hass, add_entities) _CONFIGURING[host] = configurator.request_config( - name, bravia_configuration_callback, - description='Enter the Pin shown on your Sony Bravia TV.' + - 'If no Pin is shown, enter 0000 to let TV show you a Pin.', + name, + bravia_configuration_callback, + description="Enter the Pin shown on your Sony Bravia TV." + + "If no Pin is shown, enter 0000 to let TV show you a Pin.", description_image="/static/images/smart-tv.png", submit_caption="Confirm", - fields=[{'id': 'pin', 'name': 'Enter the pin', 'type': ''}] + fields=[{"id": "pin", "name": "Enter the pin", "type": ""}], ) @@ -169,7 +187,7 @@ class BraviaTVDevice(MediaPlayerDevice): def update(self): """Update TV info.""" if not self._braviarc.is_connected(): - if self._braviarc.get_power_status() != 'off': + if self._braviarc.get_power_status() != "off": self._braviarc.connect(self._pin, CLIENTID_PREFIX, NICKNAME) if not self._braviarc.is_connected(): return @@ -182,22 +200,21 @@ class BraviaTVDevice(MediaPlayerDevice): self._refresh_channels() power_status = self._braviarc.get_power_status() - if power_status == 'active': + if power_status == "active": self._state = STATE_ON playing_info = self._braviarc.get_playing_info() self._reset_playing_info() if playing_info is None or not playing_info: - self._channel_name = 'App' + self._channel_name = "App" else: - self._program_name = playing_info.get('programTitle') - self._channel_name = playing_info.get('title') - self._program_media_type = playing_info.get( - 'programMediaType') - self._channel_number = playing_info.get('dispNum') - self._source = playing_info.get('source') - self._content_uri = playing_info.get('uri') - self._duration = playing_info.get('durationSec') - self._start_date_time = playing_info.get('startDateTime') + self._program_name = playing_info.get("programTitle") + self._channel_name = playing_info.get("title") + self._program_media_type = playing_info.get("programMediaType") + self._channel_number = playing_info.get("dispNum") + self._source = playing_info.get("source") + self._content_uri = playing_info.get("uri") + self._duration = playing_info.get("durationSec") + self._start_date_time = playing_info.get("startDateTime") else: self._state = STATE_OFF @@ -219,15 +236,14 @@ class BraviaTVDevice(MediaPlayerDevice): """Refresh volume information.""" volume_info = self._braviarc.get_volume_info() if volume_info is not None: - self._volume = volume_info.get('volume') - self._min_volume = volume_info.get('minVolume') - self._max_volume = volume_info.get('maxVolume') - self._muted = volume_info.get('mute') + self._volume = volume_info.get("volume") + self._min_volume = volume_info.get("minVolume") + self._max_volume = volume_info.get("maxVolume") + self._muted = volume_info.get("mute") def _refresh_channels(self): if not self._source_list: - self._content_mapping = self._braviarc. \ - load_source_list() + self._content_mapping = self._braviarc.load_source_list() self._source_list = [] for key in self._content_mapping: self._source_list.append(key) @@ -276,7 +292,7 @@ class BraviaTVDevice(MediaPlayerDevice): if self._channel_name is not None: return_value = self._channel_name if self._program_name is not None: - return_value = return_value + ': ' + self._program_name + return_value = return_value + ": " + self._program_name return return_value @property diff --git a/homeassistant/components/broadlink/__init__.py b/homeassistant/components/broadlink/__init__.py index a1cc0a0caa3..5fb5af2732b 100644 --- a/homeassistant/components/broadlink/__init__.py +++ b/homeassistant/components/broadlink/__init__.py @@ -23,18 +23,18 @@ def data_packet(value): value = cv.string(value) extra = len(value) % 4 if extra > 0: - value = value + ('=' * (4 - extra)) + value = value + ("=" * (4 - extra)) return b64decode(value) -SERVICE_SEND_SCHEMA = vol.Schema({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_PACKET): vol.All(cv.ensure_list, [data_packet]) -}) +SERVICE_SEND_SCHEMA = vol.Schema( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PACKET): vol.All(cv.ensure_list, [data_packet]), + } +) -SERVICE_LEARN_SCHEMA = vol.Schema({ - vol.Required(CONF_HOST): cv.string, -}) +SERVICE_LEARN_SCHEMA = vol.Schema({vol.Required(CONF_HOST): cv.string}) def async_setup_service(hass, host, device): @@ -61,24 +61,24 @@ def async_setup_service(hass, host, device): _LOGGER.info("Press the key you want Home Assistant to learn") start_time = utcnow() while (utcnow() - start_time) < timedelta(seconds=20): - packet = await hass.async_add_executor_job( - device.check_data) + packet = await hass.async_add_executor_job(device.check_data) if packet: - data = b64encode(packet).decode('utf8') - log_msg = "Received packet is: {}".\ - format(data) + data = b64encode(packet).decode("utf8") + log_msg = "Received packet is: {}".format(data) _LOGGER.info(log_msg) hass.components.persistent_notification.async_create( - log_msg, title='Broadlink switch') + log_msg, title="Broadlink switch" + ) return await asyncio.sleep(1) _LOGGER.error("No signal was received") hass.components.persistent_notification.async_create( - "No signal was received", title='Broadlink switch') + "No signal was received", title="Broadlink switch" + ) hass.services.async_register( - DOMAIN, SERVICE_LEARN, _learn_command, - schema=SERVICE_LEARN_SCHEMA) + DOMAIN, SERVICE_LEARN, _learn_command, schema=SERVICE_LEARN_SCHEMA + ) if not hass.services.has_service(DOMAIN, SERVICE_SEND): @@ -89,18 +89,15 @@ def async_setup_service(hass, host, device): for packet in packets: for retry in range(DEFAULT_RETRY): try: - await hass.async_add_executor_job( - device.send_data, packet) + await hass.async_add_executor_job(device.send_data, packet) break except (socket.timeout, ValueError): try: - await hass.async_add_executor_job( - device.auth) + await hass.async_add_executor_job(device.auth) except socket.timeout: - if retry == DEFAULT_RETRY-1: - _LOGGER.error( - "Failed to send packet to device") + if retry == DEFAULT_RETRY - 1: + _LOGGER.error("Failed to send packet to device") hass.services.async_register( - DOMAIN, SERVICE_SEND, _send_packet, - schema=SERVICE_SEND_SCHEMA) + DOMAIN, SERVICE_SEND, _send_packet, schema=SERVICE_SEND_SCHEMA + ) diff --git a/homeassistant/components/broadlink/const.py b/homeassistant/components/broadlink/const.py index 1c4e0ae7948..c31a1540349 100644 --- a/homeassistant/components/broadlink/const.py +++ b/homeassistant/components/broadlink/const.py @@ -1,7 +1,7 @@ """Constants for broadlink platform.""" -CONF_PACKET = 'packet' +CONF_PACKET = "packet" -DOMAIN = 'broadlink' +DOMAIN = "broadlink" -SERVICE_LEARN = 'learn' -SERVICE_SEND = 'send' +SERVICE_LEARN = "learn" +SERVICE_SEND = "send" diff --git a/homeassistant/components/broadlink/sensor.py b/homeassistant/components/broadlink/sensor.py index d9a8121e635..98988965ca0 100644 --- a/homeassistant/components/broadlink/sensor.py +++ b/homeassistant/components/broadlink/sensor.py @@ -8,39 +8,48 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_HOST, CONF_MAC, CONF_MONITORED_CONDITIONS, CONF_NAME, TEMP_CELSIUS, - CONF_TIMEOUT, CONF_SCAN_INTERVAL) + CONF_HOST, + CONF_MAC, + CONF_MONITORED_CONDITIONS, + CONF_NAME, + TEMP_CELSIUS, + CONF_TIMEOUT, + CONF_SCAN_INTERVAL, +) from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -DEVICE_DEFAULT_NAME = 'Broadlink sensor' +DEVICE_DEFAULT_NAME = "Broadlink sensor" DEFAULT_TIMEOUT = 10 SCAN_INTERVAL = timedelta(seconds=300) SENSOR_TYPES = { - 'temperature': ['Temperature', TEMP_CELSIUS], - 'air_quality': ['Air Quality', ' '], - 'humidity': ['Humidity', '%'], - 'light': ['Light', ' '], - 'noise': ['Noise', ' '], + "temperature": ["Temperature", TEMP_CELSIUS], + "air_quality": ["Air Quality", " "], + "humidity": ["Humidity", "%"], + "light": ["Light", " "], + "noise": ["Noise", " "], } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME, default=DEVICE_DEFAULT_NAME): vol.Coerce(str), - vol.Optional(CONF_MONITORED_CONDITIONS, default=[]): - vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_MAC): cv.string, - vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEVICE_DEFAULT_NAME): vol.Coerce(str), + vol.Optional(CONF_MONITORED_CONDITIONS, default=[]): vol.All( + cv.ensure_list, [vol.In(SENSOR_TYPES)] + ), + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_MAC): cv.string, + vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Broadlink device sensors.""" host = config.get(CONF_HOST) - mac = config.get(CONF_MAC).encode().replace(b':', b'') + mac = config.get(CONF_MAC).encode().replace(b":", b"") mac_addr = binascii.unhexlify(mac) name = config.get(CONF_NAME) timeout = config.get(CONF_TIMEOUT) @@ -57,7 +66,7 @@ class BroadlinkSensor(Entity): def __init__(self, name, broadlink_data, sensor_type): """Initialize the sensor.""" - self._name = '{} {}'.format(name, SENSOR_TYPES[sensor_type][0]) + self._name = "{} {}".format(name, SENSOR_TYPES[sensor_type][0]) self._state = None self._is_available = False self._type = sensor_type @@ -105,19 +114,22 @@ class BroadlinkData: self.mac_addr = mac_addr self.timeout = timeout self._connect() - self._schema = vol.Schema({ - vol.Optional('temperature'): vol.Range(min=-50, max=150), - vol.Optional('humidity'): vol.Range(min=0, max=100), - vol.Optional('light'): vol.Any(0, 1, 2, 3), - vol.Optional('air_quality'): vol.Any(0, 1, 2, 3), - vol.Optional('noise'): vol.Any(0, 1, 2), - }) + self._schema = vol.Schema( + { + vol.Optional("temperature"): vol.Range(min=-50, max=150), + vol.Optional("humidity"): vol.Range(min=0, max=100), + vol.Optional("light"): vol.Any(0, 1, 2, 3), + vol.Optional("air_quality"): vol.Any(0, 1, 2, 3), + vol.Optional("noise"): vol.Any(0, 1, 2), + } + ) self.update = Throttle(interval)(self._update) if not self._auth(): _LOGGER.warning("Failed to connect to device") def _connect(self): import broadlink + self._device = broadlink.a1((self.ip_addr, 80), self.mac_addr, None) self._device.timeout = self.timeout @@ -135,7 +147,7 @@ class BroadlinkData: except (vol.Invalid, vol.MultipleInvalid): pass # Continue quietly if device returned malformed data if retry > 0 and self._auth(): - self._update(retry-1) + self._update(retry - 1) def _auth(self, retry=3): try: @@ -144,5 +156,5 @@ class BroadlinkData: auth = False if not auth and retry > 0: self._connect() - return self._auth(retry-1) + return self._auth(retry - 1) return auth diff --git a/homeassistant/components/broadlink/switch.py b/homeassistant/components/broadlink/switch.py index 5c67e9dbc28..2a7255f5a61 100644 --- a/homeassistant/components/broadlink/switch.py +++ b/homeassistant/components/broadlink/switch.py @@ -7,10 +7,21 @@ import socket import voluptuous as vol from homeassistant.components.switch import ( - ENTITY_ID_FORMAT, PLATFORM_SCHEMA, SwitchDevice) + ENTITY_ID_FORMAT, + PLATFORM_SCHEMA, + SwitchDevice, +) from homeassistant.const import ( - CONF_COMMAND_OFF, CONF_COMMAND_ON, CONF_FRIENDLY_NAME, CONF_HOST, CONF_MAC, - CONF_SWITCHES, CONF_TIMEOUT, CONF_TYPE, STATE_ON) + CONF_COMMAND_OFF, + CONF_COMMAND_ON, + CONF_FRIENDLY_NAME, + CONF_HOST, + CONF_MAC, + CONF_SWITCHES, + CONF_TIMEOUT, + CONF_TYPE, + STATE_ON, +) import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle, slugify from homeassistant.helpers.restore_state import RestoreEntity @@ -21,60 +32,76 @@ _LOGGER = logging.getLogger(__name__) TIME_BETWEEN_UPDATES = timedelta(seconds=5) -DEFAULT_NAME = 'Broadlink switch' +DEFAULT_NAME = "Broadlink switch" DEFAULT_TIMEOUT = 10 -CONF_SLOTS = 'slots' +CONF_SLOTS = "slots" -RM_TYPES = ['rm', 'rm2', 'rm_mini', 'rm_pro_phicomm', 'rm2_home_plus', - 'rm2_home_plus_gdt', 'rm2_pro_plus', 'rm2_pro_plus2', - 'rm2_pro_plus_bl', 'rm_mini_shate'] -SP1_TYPES = ['sp1'] -SP2_TYPES = ['sp2', 'honeywell_sp2', 'sp3', 'spmini2', 'spminiplus'] -MP1_TYPES = ['mp1'] +RM_TYPES = [ + "rm", + "rm2", + "rm_mini", + "rm_pro_phicomm", + "rm2_home_plus", + "rm2_home_plus_gdt", + "rm2_pro_plus", + "rm2_pro_plus2", + "rm2_pro_plus_bl", + "rm_mini_shate", +] +SP1_TYPES = ["sp1"] +SP2_TYPES = ["sp2", "honeywell_sp2", "sp3", "spmini2", "spminiplus"] +MP1_TYPES = ["mp1"] SWITCH_TYPES = RM_TYPES + SP1_TYPES + SP2_TYPES + MP1_TYPES -SWITCH_SCHEMA = vol.Schema({ - vol.Optional(CONF_COMMAND_OFF): data_packet, - vol.Optional(CONF_COMMAND_ON): data_packet, - vol.Optional(CONF_FRIENDLY_NAME): cv.string, -}) +SWITCH_SCHEMA = vol.Schema( + { + vol.Optional(CONF_COMMAND_OFF): data_packet, + vol.Optional(CONF_COMMAND_ON): data_packet, + vol.Optional(CONF_FRIENDLY_NAME): cv.string, + } +) -MP1_SWITCH_SLOT_SCHEMA = vol.Schema({ - vol.Optional('slot_1'): cv.string, - vol.Optional('slot_2'): cv.string, - vol.Optional('slot_3'): cv.string, - vol.Optional('slot_4'): cv.string -}) +MP1_SWITCH_SLOT_SCHEMA = vol.Schema( + { + vol.Optional("slot_1"): cv.string, + vol.Optional("slot_2"): cv.string, + vol.Optional("slot_3"): cv.string, + vol.Optional("slot_4"): cv.string, + } +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_SWITCHES, default={}): - cv.schema_with_slug_keys(SWITCH_SCHEMA), - vol.Optional(CONF_SLOTS, default={}): MP1_SWITCH_SLOT_SCHEMA, - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_MAC): cv.string, - vol.Optional(CONF_FRIENDLY_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_TYPE, default=SWITCH_TYPES[0]): vol.In(SWITCH_TYPES), - vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_SWITCHES, default={}): cv.schema_with_slug_keys( + SWITCH_SCHEMA + ), + vol.Optional(CONF_SLOTS, default={}): MP1_SWITCH_SLOT_SCHEMA, + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_MAC): cv.string, + vol.Optional(CONF_FRIENDLY_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_TYPE, default=SWITCH_TYPES[0]): vol.In(SWITCH_TYPES), + vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Broadlink switches.""" import broadlink + devices = config.get(CONF_SWITCHES) - slots = config.get('slots', {}) + slots = config.get("slots", {}) ip_addr = config.get(CONF_HOST) friendly_name = config.get(CONF_FRIENDLY_NAME) - mac_addr = binascii.unhexlify( - config.get(CONF_MAC).encode().replace(b':', b'')) + mac_addr = binascii.unhexlify(config.get(CONF_MAC).encode().replace(b":", b"")) switch_type = config.get(CONF_TYPE) def _get_mp1_slot_name(switch_friendly_name, slot): """Get slot name.""" - if not slots['slot_{}'.format(slot)]: - return '{} slot {}'.format(switch_friendly_name, slot) - return slots['slot_{}'.format(slot)] + if not slots["slot_{}".format(slot)]: + return "{} slot {}".format(switch_friendly_name, slot) + return slots["slot_{}".format(slot)] if switch_type in RM_TYPES: broadlink_device = broadlink.rm((ip_addr, 80), mac_addr, None) @@ -88,7 +115,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): device_config.get(CONF_FRIENDLY_NAME, object_id), broadlink_device, device_config.get(CONF_COMMAND_ON), - device_config.get(CONF_COMMAND_OFF) + device_config.get(CONF_COMMAND_OFF), ) ) elif switch_type in SP1_TYPES: @@ -103,8 +130,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): parent_device = BroadlinkMP1Switch(broadlink_device) for i in range(1, 5): slot = BroadlinkMP1Slot( - _get_mp1_slot_name(friendly_name, i), - broadlink_device, i, parent_device) + _get_mp1_slot_name(friendly_name, i), broadlink_device, i, parent_device + ) switches.append(slot) broadlink_device.timeout = config.get(CONF_TIMEOUT) @@ -186,7 +213,7 @@ class BroadlinkRMSwitch(SwitchDevice, RestoreEntity): return False if not self._auth(): return False - return self._sendpacket(packet, retry-1) + return self._sendpacket(packet, retry - 1) return True def _auth(self, retry=2): @@ -197,7 +224,7 @@ class BroadlinkRMSwitch(SwitchDevice, RestoreEntity): if retry < 1: _LOGGER.error("Timeout during authorization") if not auth and retry > 0: - return self._auth(retry-1) + return self._auth(retry - 1) return auth @@ -221,7 +248,7 @@ class BroadlinkSP1Switch(BroadlinkRMSwitch): return False if not self._auth(): return False - return self._sendpacket(packet, retry-1) + return self._sendpacket(packet, retry - 1) return True @@ -262,9 +289,9 @@ class BroadlinkSP2Switch(BroadlinkSP1Switch): return if not self._auth(): return - return self._update(retry-1) + return self._update(retry - 1) if state is None and retry > 0: - return self._update(retry-1) + return self._update(retry - 1) self._state = state self._load_power = load_power self._is_available = True @@ -297,7 +324,7 @@ class BroadlinkMP1Slot(BroadlinkRMSwitch): return False if not self._auth(): return False - return self._sendpacket(packet, max(0, retry-1)) + return self._sendpacket(packet, max(0, retry - 1)) self._is_available = True return True @@ -324,7 +351,7 @@ class BroadlinkMP1Switch: """Get status of outlet from cached status list.""" if self._states is None: return None - return self._states['s{}'.format(slot)] + return self._states["s{}".format(slot)] @Throttle(TIME_BETWEEN_UPDATES) def update(self): @@ -341,9 +368,9 @@ class BroadlinkMP1Switch: return if not self._auth(): return - return self._update(max(0, retry-1)) + return self._update(max(0, retry - 1)) if states is None and retry > 0: - return self._update(max(0, retry-1)) + return self._update(max(0, retry - 1)) self._states = states def _auth(self, retry=2): @@ -353,5 +380,5 @@ class BroadlinkMP1Switch: except OSError: auth = False if not auth and retry > 0: - return self._auth(retry-1) + return self._auth(retry - 1) return auth diff --git a/homeassistant/components/brottsplatskartan/sensor.py b/homeassistant/components/brottsplatskartan/sensor.py index c36c5c0ad1c..5a3f72c3ef2 100644 --- a/homeassistant/components/brottsplatskartan/sensor.py +++ b/homeassistant/components/brottsplatskartan/sensor.py @@ -8,34 +8,54 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - ATTR_ATTRIBUTION, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME) + ATTR_ATTRIBUTION, + CONF_LATITUDE, + CONF_LONGITUDE, + CONF_NAME, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -CONF_AREA = 'area' +CONF_AREA = "area" -DEFAULT_NAME = 'Brottsplatskartan' +DEFAULT_NAME = "Brottsplatskartan" SCAN_INTERVAL = timedelta(minutes=30) AREAS = [ - "Blekinge län", "Dalarnas län", "Gotlands län", "Gävleborgs län", - "Hallands län", "Jämtlands län", "Jönköpings län", "Kalmar län", - "Kronobergs län", "Norrbottens län", "Skåne län", "Stockholms län", - "Södermanlands län", "Uppsala län", "Värmlands län", "Västerbottens län", - "Västernorrlands län", "Västmanlands län", "Västra Götalands län", - "Örebro län", "Östergötlands län" + "Blekinge län", + "Dalarnas län", + "Gotlands län", + "Gävleborgs län", + "Hallands län", + "Jämtlands län", + "Jönköpings län", + "Kalmar län", + "Kronobergs län", + "Norrbottens län", + "Skåne län", + "Stockholms län", + "Södermanlands län", + "Uppsala län", + "Värmlands län", + "Västerbottens län", + "Västernorrlands län", + "Västmanlands län", + "Västra Götalands län", + "Örebro län", + "Östergötlands län", ] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Inclusive(CONF_LATITUDE, 'coordinates'): cv.latitude, - vol.Inclusive(CONF_LONGITUDE, 'coordinates'): cv.longitude, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_AREA, default=[]): - vol.All(cv.ensure_list, [vol.In(AREAS)]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Inclusive(CONF_LATITUDE, "coordinates"): cv.latitude, + vol.Inclusive(CONF_LONGITUDE, "coordinates"): cv.longitude, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_AREA, default=[]): vol.All(cv.ensure_list, [vol.In(AREAS)]), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -49,10 +69,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): # Every Home Assistant instance should have their own unique # app parameter: https://brottsplatskartan.se/sida/api - app = 'ha-{}'.format(uuid.getnode()) + app = "ha-{}".format(uuid.getnode()) bpk = brottsplatskartan.BrottsplatsKartan( - app=app, area=area, latitude=latitude, longitude=longitude) + app=app, area=area, latitude=latitude, longitude=longitude + ) add_entities([BrottsplatskartanSensor(bpk, name)], True) @@ -85,6 +106,7 @@ class BrottsplatskartanSensor(Entity): def update(self): """Update device state.""" import brottsplatskartan + incident_counts = defaultdict(int) incidents = self._brottsplatskartan.get_incidents() @@ -93,7 +115,7 @@ class BrottsplatskartanSensor(Entity): return for incident in incidents: - incident_type = incident.get('title_type') + incident_type = incident.get("title_type") incident_counts[incident_type] += 1 self._attributes = {ATTR_ATTRIBUTION: brottsplatskartan.ATTRIBUTION} diff --git a/homeassistant/components/browser/__init__.py b/homeassistant/components/browser/__init__.py index 1c002f21f5f..b163f16a5c4 100644 --- a/homeassistant/components/browser/__init__.py +++ b/homeassistant/components/browser/__init__.py @@ -1,26 +1,30 @@ """Support for launching a web browser on the host machine.""" import voluptuous as vol -ATTR_URL = 'url' -ATTR_URL_DEFAULT = 'https://www.google.com' +ATTR_URL = "url" +ATTR_URL_DEFAULT = "https://www.google.com" -DOMAIN = 'browser' +DOMAIN = "browser" -SERVICE_BROWSE_URL = 'browse_url' +SERVICE_BROWSE_URL = "browse_url" -SERVICE_BROWSE_URL_SCHEMA = vol.Schema({ - # pylint: disable=no-value-for-parameter - vol.Required(ATTR_URL, default=ATTR_URL_DEFAULT): vol.Url(), -}) +SERVICE_BROWSE_URL_SCHEMA = vol.Schema( + { + # pylint: disable=no-value-for-parameter + vol.Required(ATTR_URL, default=ATTR_URL_DEFAULT): vol.Url() + } +) def setup(hass, config): """Listen for browse_url events.""" import webbrowser - hass.services.register(DOMAIN, SERVICE_BROWSE_URL, - lambda service: - webbrowser.open(service.data[ATTR_URL]), - schema=SERVICE_BROWSE_URL_SCHEMA) + hass.services.register( + DOMAIN, + SERVICE_BROWSE_URL, + lambda service: webbrowser.open(service.data[ATTR_URL]), + schema=SERVICE_BROWSE_URL_SCHEMA, + ) return True diff --git a/homeassistant/components/brunt/cover.py b/homeassistant/components/brunt/cover.py index f9455ae0910..af809cc7878 100644 --- a/homeassistant/components/brunt/cover.py +++ b/homeassistant/components/brunt/cover.py @@ -4,58 +4,65 @@ import logging import voluptuous as vol -from homeassistant.const import ( - ATTR_ATTRIBUTION, CONF_PASSWORD, CONF_USERNAME) +from homeassistant.const import ATTR_ATTRIBUTION, CONF_PASSWORD, CONF_USERNAME from homeassistant.components.cover import ( - ATTR_POSITION, CoverDevice, - PLATFORM_SCHEMA, SUPPORT_CLOSE, - SUPPORT_OPEN, SUPPORT_SET_POSITION + ATTR_POSITION, + CoverDevice, + PLATFORM_SCHEMA, + SUPPORT_CLOSE, + SUPPORT_OPEN, + SUPPORT_SET_POSITION, ) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) COVER_FEATURES = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION -DEVICE_CLASS = 'window' +DEVICE_CLASS = "window" -ATTR_REQUEST_POSITION = 'request_position' -NOTIFICATION_ID = 'brunt_notification' -NOTIFICATION_TITLE = 'Brunt Cover Setup' -ATTRIBUTION = 'Based on an unofficial Brunt SDK.' +ATTR_REQUEST_POSITION = "request_position" +NOTIFICATION_ID = "brunt_notification" +NOTIFICATION_TITLE = "Brunt Cover Setup" +ATTRIBUTION = "Based on an unofficial Brunt SDK." CLOSED_POSITION = 0 OPEN_POSITION = 100 -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string} +) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the brunt platform.""" # pylint: disable=no-name-in-module from brunt import BruntAPI + username = config[CONF_USERNAME] password = config[CONF_PASSWORD] bapi = BruntAPI(username=username, password=password) try: - things = bapi.getThings()['things'] + things = bapi.getThings()["things"] if not things: _LOGGER.error("No things present in account.") else: - add_entities([BruntDevice( - bapi, thing['NAME'], - thing['thingUri']) for thing in things], True) + add_entities( + [ + BruntDevice(bapi, thing["NAME"], thing["thingUri"]) + for thing in things + ], + True, + ) except (TypeError, KeyError, NameError, ValueError) as ex: _LOGGER.error("%s", ex) hass.components.persistent_notification.create( - 'Error: {}
' - 'You will need to restart hass after fixing.' - ''.format(ex), + "Error: {}
" + "You will need to restart hass after fixing." + "".format(ex), title=NOTIFICATION_TITLE, - notification_id=NOTIFICATION_ID) + notification_id=NOTIFICATION_ID, + ) class BruntDevice(CoverDevice): @@ -91,7 +98,7 @@ class BruntDevice(CoverDevice): None is unknown, 0 is closed, 100 is fully open. """ - pos = self._state.get('currentPosition') + pos = self._state.get("currentPosition") return int(pos) if pos else None @property @@ -103,7 +110,7 @@ class BruntDevice(CoverDevice): to Brunt, at times there is a diff of 1 to current None is unknown, 0 is closed, 100 is fully open. """ - pos = self._state.get('requestPosition') + pos = self._state.get("requestPosition") return int(pos) if pos else None @property @@ -113,7 +120,7 @@ class BruntDevice(CoverDevice): None is unknown, 0 when stopped, 1 when opening, 2 when closing """ - mov = self._state.get('moveState') + mov = self._state.get("moveState") return int(mov) if mov else None @property @@ -131,7 +138,7 @@ class BruntDevice(CoverDevice): """Return the detailed device state attributes.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, - ATTR_REQUEST_POSITION: self.request_cover_position + ATTR_REQUEST_POSITION: self.request_cover_position, } @property @@ -152,8 +159,7 @@ class BruntDevice(CoverDevice): def update(self): """Poll the current state of the device.""" try: - self._state = self._bapi.getState( - thingUri=self._thing_uri).get('thing') + self._state = self._bapi.getState(thingUri=self._thing_uri).get("thing") self._available = True except (TypeError, KeyError, NameError, ValueError) as ex: _LOGGER.error("%s", ex) @@ -161,15 +167,14 @@ class BruntDevice(CoverDevice): def open_cover(self, **kwargs): """Set the cover to the open position.""" - self._bapi.changeRequestPosition( - OPEN_POSITION, thingUri=self._thing_uri) + self._bapi.changeRequestPosition(OPEN_POSITION, thingUri=self._thing_uri) def close_cover(self, **kwargs): """Set the cover to the closed position.""" - self._bapi.changeRequestPosition( - CLOSED_POSITION, thingUri=self._thing_uri) + self._bapi.changeRequestPosition(CLOSED_POSITION, thingUri=self._thing_uri) def set_cover_position(self, **kwargs): """Set the cover to a specific position.""" self._bapi.changeRequestPosition( - kwargs[ATTR_POSITION], thingUri=self._thing_uri) + kwargs[ATTR_POSITION], thingUri=self._thing_uri + ) diff --git a/homeassistant/components/bt_home_hub_5/device_tracker.py b/homeassistant/components/bt_home_hub_5/device_tracker.py index 65f88e05d1c..0a068a3981f 100644 --- a/homeassistant/components/bt_home_hub_5/device_tracker.py +++ b/homeassistant/components/bt_home_hub_5/device_tracker.py @@ -4,17 +4,20 @@ import logging import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.device_tracker import (DOMAIN, PLATFORM_SCHEMA, - DeviceScanner) +from homeassistant.components.device_tracker import ( + DOMAIN, + PLATFORM_SCHEMA, + DeviceScanner, +) from homeassistant.const import CONF_HOST _LOGGER = logging.getLogger(__name__) -CONF_DEFAULT_IP = '192.168.1.254' +CONF_DEFAULT_IP = "192.168.1.254" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_HOST, default=CONF_DEFAULT_IP): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Optional(CONF_HOST, default=CONF_DEFAULT_IP): cv.string} +) def get_scanner(hass, config): diff --git a/homeassistant/components/bt_smarthub/device_tracker.py b/homeassistant/components/bt_smarthub/device_tracker.py index adc873f56b3..58f409c2d4b 100644 --- a/homeassistant/components/bt_smarthub/device_tracker.py +++ b/homeassistant/components/bt_smarthub/device_tracker.py @@ -5,16 +5,19 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.device_tracker import ( - DOMAIN, PLATFORM_SCHEMA, DeviceScanner) + DOMAIN, + PLATFORM_SCHEMA, + DeviceScanner, +) from homeassistant.const import CONF_HOST _LOGGER = logging.getLogger(__name__) -CONF_DEFAULT_IP = '192.168.1.254' +CONF_DEFAULT_IP = "192.168.1.254" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_HOST, default=CONF_DEFAULT_IP): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Optional(CONF_HOST, default=CONF_DEFAULT_IP): cv.string} +) def get_scanner(hass, config): @@ -44,15 +47,15 @@ class BTSmartHubScanner(DeviceScanner): def scan_devices(self): """Scan for new devices and return a list with found device IDs.""" self._update_info() - return [client['mac'] for client in self.last_results] + return [client["mac"] for client in self.last_results] def get_device_name(self, device): """Return the name of the given device or None if we don't know.""" if not self.last_results: return None for client in self.last_results: - if client['mac'] == device: - return client['host'] + if client["mac"] == device: + return client["host"] return None def _update_info(self): @@ -72,18 +75,20 @@ class BTSmartHubScanner(DeviceScanner): def get_bt_smarthub_data(self): """Retrieve data from BT Smart Hub and return parsed result.""" import btsmarthub_devicelist + # Request data from bt smarthub into a list of dicts. data = btsmarthub_devicelist.get_devicelist( - router_ip=self.host, only_active_devices=True) + router_ip=self.host, only_active_devices=True + ) # Renaming keys from parsed result. devices = {} for device in data: try: - devices[device['UserHostName']] = { - 'ip': device['IPAddress'], - 'mac': device['PhysAddress'], - 'host': device['UserHostName'], - 'status': device['Active'] + devices[device["UserHostName"]] = { + "ip": device["IPAddress"], + "mac": device["PhysAddress"], + "host": device["UserHostName"], + "status": device["Active"], } except KeyError: pass diff --git a/homeassistant/components/buienradar/camera.py b/homeassistant/components/buienradar/camera.py index b390a86d622..1db9e2beaf9 100644 --- a/homeassistant/components/buienradar/camera.py +++ b/homeassistant/components/buienradar/camera.py @@ -16,11 +16,10 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.util import dt as dt_util -CONF_DIMENSION = 'dimension' -CONF_DELTA = 'delta' +CONF_DIMENSION = "dimension" +CONF_DELTA = "delta" -RADAR_MAP_URL_TEMPLATE = ('https://api.buienradar.nl/image/1.0/' - 'RadarMapNL?w={w}&h={h}') +RADAR_MAP_URL_TEMPLATE = "https://api.buienradar.nl/image/1.0/" "RadarMapNL?w={w}&h={h}" _LOG = logging.getLogger(__name__) @@ -28,16 +27,19 @@ _LOG = logging.getLogger(__name__) DIM_RANGE = vol.All(vol.Coerce(int), vol.Range(min=120, max=700)) PLATFORM_SCHEMA = vol.All( - PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_DIMENSION, default=512): DIM_RANGE, - vol.Optional(CONF_DELTA, default=600.0): vol.All(vol.Coerce(float), - vol.Range(min=0)), - vol.Optional(CONF_NAME, default="Buienradar loop"): cv.string, - })) + PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_DIMENSION, default=512): DIM_RANGE, + vol.Optional(CONF_DELTA, default=600.0): vol.All( + vol.Coerce(float), vol.Range(min=0) + ), + vol.Optional(CONF_NAME, default="Buienradar loop"): cv.string, + } + ) +) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up buienradar radar-loop camera component.""" dimension = config[CONF_DIMENSION] delta = config[CONF_DELTA] @@ -79,13 +81,13 @@ class BuienradarCam(Camera): # invariant: this condition is private to and owned by this instance. self._condition = asyncio.Condition() - self._last_image = None # type: Optional[bytes] + self._last_image = None # type: Optional[bytes] # value of the last seen last modified header self._last_modified = None # type: Optional[str] # loading status self._loading = False # deadline for image refresh - self.delta after last successful load - self._deadline = None # type: Optional[datetime] + self._deadline = None # type: Optional[datetime] @property def name(self) -> str: @@ -102,11 +104,10 @@ class BuienradarCam(Camera): """Retrieve new radar image and return whether this succeeded.""" session = async_get_clientsession(self.hass) - url = RADAR_MAP_URL_TEMPLATE.format(w=self._dimension, - h=self._dimension) + url = RADAR_MAP_URL_TEMPLATE.format(w=self._dimension, h=self._dimension) if self._last_modified: - headers = {'If-Modified-Since': self._last_modified} + headers = {"If-Modified-Since": self._last_modified} else: headers = {} @@ -118,7 +119,7 @@ class BuienradarCam(Camera): _LOG.debug("HTTP 304 - success") return True - last_modified = res.headers.get('Last-Modified', None) + last_modified = res.headers.get("Last-Modified", None) if last_modified: self._last_modified = last_modified diff --git a/homeassistant/components/buienradar/sensor.py b/homeassistant/components/buienradar/sensor.py index 2a18fe3212e..72da5164dab 100644 --- a/homeassistant/components/buienradar/sensor.py +++ b/homeassistant/components/buienradar/sensor.py @@ -9,8 +9,13 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - ATTR_ATTRIBUTION, CONF_LATITUDE, CONF_LONGITUDE, CONF_MONITORED_CONDITIONS, - CONF_NAME, TEMP_CELSIUS) + ATTR_ATTRIBUTION, + CONF_LATITUDE, + CONF_LONGITUDE, + CONF_MONITORED_CONDITIONS, + CONF_NAME, + TEMP_CELSIUS, +) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -19,9 +24,9 @@ from homeassistant.util import dt as dt_util _LOGGER = logging.getLogger(__name__) -MEASURED_LABEL = 'Measured' -TIMEFRAME_LABEL = 'Timeframe' -SYMBOL = 'symbol' +MEASURED_LABEL = "Measured" +TIMEFRAME_LABEL = "Timeframe" +SYMBOL = "symbol" # Schedule next call after (minutes): SCHEDULE_OK = 10 @@ -31,149 +36,152 @@ SCHEDULE_NOK = 2 # Supported sensor types: # Key: ['label', unit, icon] SENSOR_TYPES = { - 'stationname': ['Stationname', None, None], + "stationname": ["Stationname", None, None], # new in json api (>1.0.0): - 'barometerfc': ['Barometer value', None, 'mdi:gauge'], + "barometerfc": ["Barometer value", None, "mdi:gauge"], # new in json api (>1.0.0): - 'barometerfcname': ['Barometer', None, 'mdi:gauge'], + "barometerfcname": ["Barometer", None, "mdi:gauge"], # new in json api (>1.0.0): - 'barometerfcnamenl': ['Barometer', None, 'mdi:gauge'], - 'condition': ['Condition', None, None], - 'conditioncode': ['Condition code', None, None], - 'conditiondetailed': ['Detailed condition', None, None], - 'conditionexact': ['Full condition', None, None], - 'symbol': ['Symbol', None, None], + "barometerfcnamenl": ["Barometer", None, "mdi:gauge"], + "condition": ["Condition", None, None], + "conditioncode": ["Condition code", None, None], + "conditiondetailed": ["Detailed condition", None, None], + "conditionexact": ["Full condition", None, None], + "symbol": ["Symbol", None, None], # new in json api (>1.0.0): - 'feeltemperature': ['Feel temperature', TEMP_CELSIUS, 'mdi:thermometer'], - 'humidity': ['Humidity', '%', 'mdi:water-percent'], - 'temperature': ['Temperature', TEMP_CELSIUS, 'mdi:thermometer'], - 'groundtemperature': ['Ground temperature', TEMP_CELSIUS, - 'mdi:thermometer'], - 'windspeed': ['Wind speed', 'km/h', 'mdi:weather-windy'], - 'windforce': ['Wind force', 'Bft', 'mdi:weather-windy'], - 'winddirection': ['Wind direction', None, 'mdi:compass-outline'], - 'windazimuth': ['Wind direction azimuth', '°', 'mdi:compass-outline'], - 'pressure': ['Pressure', 'hPa', 'mdi:gauge'], - 'visibility': ['Visibility', 'km', None], - 'windgust': ['Wind gust', 'km/h', 'mdi:weather-windy'], - 'precipitation': ['Precipitation', 'mm/h', 'mdi:weather-pouring'], - 'irradiance': ['Irradiance', 'W/m2', 'mdi:sunglasses'], - 'precipitation_forecast_average': ['Precipitation forecast average', - 'mm/h', 'mdi:weather-pouring'], - 'precipitation_forecast_total': ['Precipitation forecast total', - 'mm', 'mdi:weather-pouring'], + "feeltemperature": ["Feel temperature", TEMP_CELSIUS, "mdi:thermometer"], + "humidity": ["Humidity", "%", "mdi:water-percent"], + "temperature": ["Temperature", TEMP_CELSIUS, "mdi:thermometer"], + "groundtemperature": ["Ground temperature", TEMP_CELSIUS, "mdi:thermometer"], + "windspeed": ["Wind speed", "km/h", "mdi:weather-windy"], + "windforce": ["Wind force", "Bft", "mdi:weather-windy"], + "winddirection": ["Wind direction", None, "mdi:compass-outline"], + "windazimuth": ["Wind direction azimuth", "°", "mdi:compass-outline"], + "pressure": ["Pressure", "hPa", "mdi:gauge"], + "visibility": ["Visibility", "km", None], + "windgust": ["Wind gust", "km/h", "mdi:weather-windy"], + "precipitation": ["Precipitation", "mm/h", "mdi:weather-pouring"], + "irradiance": ["Irradiance", "W/m2", "mdi:sunglasses"], + "precipitation_forecast_average": [ + "Precipitation forecast average", + "mm/h", + "mdi:weather-pouring", + ], + "precipitation_forecast_total": [ + "Precipitation forecast total", + "mm", + "mdi:weather-pouring", + ], # new in json api (>1.0.0): - 'rainlast24hour': ['Rain last 24h', 'mm', 'mdi:weather-pouring'], + "rainlast24hour": ["Rain last 24h", "mm", "mdi:weather-pouring"], # new in json api (>1.0.0): - 'rainlasthour': ['Rain last hour', 'mm', 'mdi:weather-pouring'], - 'temperature_1d': ['Temperature 1d', TEMP_CELSIUS, 'mdi:thermometer'], - 'temperature_2d': ['Temperature 2d', TEMP_CELSIUS, 'mdi:thermometer'], - 'temperature_3d': ['Temperature 3d', TEMP_CELSIUS, 'mdi:thermometer'], - 'temperature_4d': ['Temperature 4d', TEMP_CELSIUS, 'mdi:thermometer'], - 'temperature_5d': ['Temperature 5d', TEMP_CELSIUS, 'mdi:thermometer'], - 'mintemp_1d': ['Minimum temperature 1d', TEMP_CELSIUS, 'mdi:thermometer'], - 'mintemp_2d': ['Minimum temperature 2d', TEMP_CELSIUS, 'mdi:thermometer'], - 'mintemp_3d': ['Minimum temperature 3d', TEMP_CELSIUS, 'mdi:thermometer'], - 'mintemp_4d': ['Minimum temperature 4d', TEMP_CELSIUS, 'mdi:thermometer'], - 'mintemp_5d': ['Minimum temperature 5d', TEMP_CELSIUS, 'mdi:thermometer'], - 'rain_1d': ['Rain 1d', 'mm', 'mdi:weather-pouring'], - 'rain_2d': ['Rain 2d', 'mm', 'mdi:weather-pouring'], - 'rain_3d': ['Rain 3d', 'mm', 'mdi:weather-pouring'], - 'rain_4d': ['Rain 4d', 'mm', 'mdi:weather-pouring'], - 'rain_5d': ['Rain 5d', 'mm', 'mdi:weather-pouring'], + "rainlasthour": ["Rain last hour", "mm", "mdi:weather-pouring"], + "temperature_1d": ["Temperature 1d", TEMP_CELSIUS, "mdi:thermometer"], + "temperature_2d": ["Temperature 2d", TEMP_CELSIUS, "mdi:thermometer"], + "temperature_3d": ["Temperature 3d", TEMP_CELSIUS, "mdi:thermometer"], + "temperature_4d": ["Temperature 4d", TEMP_CELSIUS, "mdi:thermometer"], + "temperature_5d": ["Temperature 5d", TEMP_CELSIUS, "mdi:thermometer"], + "mintemp_1d": ["Minimum temperature 1d", TEMP_CELSIUS, "mdi:thermometer"], + "mintemp_2d": ["Minimum temperature 2d", TEMP_CELSIUS, "mdi:thermometer"], + "mintemp_3d": ["Minimum temperature 3d", TEMP_CELSIUS, "mdi:thermometer"], + "mintemp_4d": ["Minimum temperature 4d", TEMP_CELSIUS, "mdi:thermometer"], + "mintemp_5d": ["Minimum temperature 5d", TEMP_CELSIUS, "mdi:thermometer"], + "rain_1d": ["Rain 1d", "mm", "mdi:weather-pouring"], + "rain_2d": ["Rain 2d", "mm", "mdi:weather-pouring"], + "rain_3d": ["Rain 3d", "mm", "mdi:weather-pouring"], + "rain_4d": ["Rain 4d", "mm", "mdi:weather-pouring"], + "rain_5d": ["Rain 5d", "mm", "mdi:weather-pouring"], # new in json api (>1.0.0): - 'minrain_1d': ['Minimum rain 1d', 'mm', 'mdi:weather-pouring'], - 'minrain_2d': ['Minimum rain 2d', 'mm', 'mdi:weather-pouring'], - 'minrain_3d': ['Minimum rain 3d', 'mm', 'mdi:weather-pouring'], - 'minrain_4d': ['Minimum rain 4d', 'mm', 'mdi:weather-pouring'], - 'minrain_5d': ['Minimum rain 5d', 'mm', 'mdi:weather-pouring'], + "minrain_1d": ["Minimum rain 1d", "mm", "mdi:weather-pouring"], + "minrain_2d": ["Minimum rain 2d", "mm", "mdi:weather-pouring"], + "minrain_3d": ["Minimum rain 3d", "mm", "mdi:weather-pouring"], + "minrain_4d": ["Minimum rain 4d", "mm", "mdi:weather-pouring"], + "minrain_5d": ["Minimum rain 5d", "mm", "mdi:weather-pouring"], # new in json api (>1.0.0): - 'maxrain_1d': ['Maximum rain 1d', 'mm', 'mdi:weather-pouring'], - 'maxrain_2d': ['Maximum rain 2d', 'mm', 'mdi:weather-pouring'], - 'maxrain_3d': ['Maximum rain 3d', 'mm', 'mdi:weather-pouring'], - 'maxrain_4d': ['Maximum rain 4d', 'mm', 'mdi:weather-pouring'], - 'maxrain_5d': ['Maximum rain 5d', 'mm', 'mdi:weather-pouring'], - 'rainchance_1d': ['Rainchance 1d', '%', 'mdi:weather-pouring'], - 'rainchance_2d': ['Rainchance 2d', '%', 'mdi:weather-pouring'], - 'rainchance_3d': ['Rainchance 3d', '%', 'mdi:weather-pouring'], - 'rainchance_4d': ['Rainchance 4d', '%', 'mdi:weather-pouring'], - 'rainchance_5d': ['Rainchance 5d', '%', 'mdi:weather-pouring'], - 'sunchance_1d': ['Sunchance 1d', '%', 'mdi:weather-partlycloudy'], - 'sunchance_2d': ['Sunchance 2d', '%', 'mdi:weather-partlycloudy'], - 'sunchance_3d': ['Sunchance 3d', '%', 'mdi:weather-partlycloudy'], - 'sunchance_4d': ['Sunchance 4d', '%', 'mdi:weather-partlycloudy'], - 'sunchance_5d': ['Sunchance 5d', '%', 'mdi:weather-partlycloudy'], - 'windforce_1d': ['Wind force 1d', 'Bft', 'mdi:weather-windy'], - 'windforce_2d': ['Wind force 2d', 'Bft', 'mdi:weather-windy'], - 'windforce_3d': ['Wind force 3d', 'Bft', 'mdi:weather-windy'], - 'windforce_4d': ['Wind force 4d', 'Bft', 'mdi:weather-windy'], - 'windforce_5d': ['Wind force 5d', 'Bft', 'mdi:weather-windy'], - 'windspeed_1d': ['Wind speed 1d', 'km/h', 'mdi:weather-windy'], - 'windspeed_2d': ['Wind speed 2d', 'km/h', 'mdi:weather-windy'], - 'windspeed_3d': ['Wind speed 3d', 'km/h', 'mdi:weather-windy'], - 'windspeed_4d': ['Wind speed 4d', 'km/h', 'mdi:weather-windy'], - 'windspeed_5d': ['Wind speed 5d', 'km/h', 'mdi:weather-windy'], - 'winddirection_1d': ['Wind direction 1d', None, 'mdi:compass-outline'], - 'winddirection_2d': ['Wind direction 2d', None, 'mdi:compass-outline'], - 'winddirection_3d': ['Wind direction 3d', None, 'mdi:compass-outline'], - 'winddirection_4d': ['Wind direction 4d', None, 'mdi:compass-outline'], - 'winddirection_5d': ['Wind direction 5d', None, 'mdi:compass-outline'], - 'windazimuth_1d': ['Wind direction azimuth 1d', '°', - 'mdi:compass-outline'], - 'windazimuth_2d': ['Wind direction azimuth 2d', '°', - 'mdi:compass-outline'], - 'windazimuth_3d': ['Wind direction azimuth 3d', '°', - 'mdi:compass-outline'], - 'windazimuth_4d': ['Wind direction azimuth 4d', '°', - 'mdi:compass-outline'], - 'windazimuth_5d': ['Wind direction azimuth 5d', '°', - 'mdi:compass-outline'], - 'condition_1d': ['Condition 1d', None, None], - 'condition_2d': ['Condition 2d', None, None], - 'condition_3d': ['Condition 3d', None, None], - 'condition_4d': ['Condition 4d', None, None], - 'condition_5d': ['Condition 5d', None, None], - 'conditioncode_1d': ['Condition code 1d', None, None], - 'conditioncode_2d': ['Condition code 2d', None, None], - 'conditioncode_3d': ['Condition code 3d', None, None], - 'conditioncode_4d': ['Condition code 4d', None, None], - 'conditioncode_5d': ['Condition code 5d', None, None], - 'conditiondetailed_1d': ['Detailed condition 1d', None, None], - 'conditiondetailed_2d': ['Detailed condition 2d', None, None], - 'conditiondetailed_3d': ['Detailed condition 3d', None, None], - 'conditiondetailed_4d': ['Detailed condition 4d', None, None], - 'conditiondetailed_5d': ['Detailed condition 5d', None, None], - 'conditionexact_1d': ['Full condition 1d', None, None], - 'conditionexact_2d': ['Full condition 2d', None, None], - 'conditionexact_3d': ['Full condition 3d', None, None], - 'conditionexact_4d': ['Full condition 4d', None, None], - 'conditionexact_5d': ['Full condition 5d', None, None], - 'symbol_1d': ['Symbol 1d', None, None], - 'symbol_2d': ['Symbol 2d', None, None], - 'symbol_3d': ['Symbol 3d', None, None], - 'symbol_4d': ['Symbol 4d', None, None], - 'symbol_5d': ['Symbol 5d', None, None] + "maxrain_1d": ["Maximum rain 1d", "mm", "mdi:weather-pouring"], + "maxrain_2d": ["Maximum rain 2d", "mm", "mdi:weather-pouring"], + "maxrain_3d": ["Maximum rain 3d", "mm", "mdi:weather-pouring"], + "maxrain_4d": ["Maximum rain 4d", "mm", "mdi:weather-pouring"], + "maxrain_5d": ["Maximum rain 5d", "mm", "mdi:weather-pouring"], + "rainchance_1d": ["Rainchance 1d", "%", "mdi:weather-pouring"], + "rainchance_2d": ["Rainchance 2d", "%", "mdi:weather-pouring"], + "rainchance_3d": ["Rainchance 3d", "%", "mdi:weather-pouring"], + "rainchance_4d": ["Rainchance 4d", "%", "mdi:weather-pouring"], + "rainchance_5d": ["Rainchance 5d", "%", "mdi:weather-pouring"], + "sunchance_1d": ["Sunchance 1d", "%", "mdi:weather-partlycloudy"], + "sunchance_2d": ["Sunchance 2d", "%", "mdi:weather-partlycloudy"], + "sunchance_3d": ["Sunchance 3d", "%", "mdi:weather-partlycloudy"], + "sunchance_4d": ["Sunchance 4d", "%", "mdi:weather-partlycloudy"], + "sunchance_5d": ["Sunchance 5d", "%", "mdi:weather-partlycloudy"], + "windforce_1d": ["Wind force 1d", "Bft", "mdi:weather-windy"], + "windforce_2d": ["Wind force 2d", "Bft", "mdi:weather-windy"], + "windforce_3d": ["Wind force 3d", "Bft", "mdi:weather-windy"], + "windforce_4d": ["Wind force 4d", "Bft", "mdi:weather-windy"], + "windforce_5d": ["Wind force 5d", "Bft", "mdi:weather-windy"], + "windspeed_1d": ["Wind speed 1d", "km/h", "mdi:weather-windy"], + "windspeed_2d": ["Wind speed 2d", "km/h", "mdi:weather-windy"], + "windspeed_3d": ["Wind speed 3d", "km/h", "mdi:weather-windy"], + "windspeed_4d": ["Wind speed 4d", "km/h", "mdi:weather-windy"], + "windspeed_5d": ["Wind speed 5d", "km/h", "mdi:weather-windy"], + "winddirection_1d": ["Wind direction 1d", None, "mdi:compass-outline"], + "winddirection_2d": ["Wind direction 2d", None, "mdi:compass-outline"], + "winddirection_3d": ["Wind direction 3d", None, "mdi:compass-outline"], + "winddirection_4d": ["Wind direction 4d", None, "mdi:compass-outline"], + "winddirection_5d": ["Wind direction 5d", None, "mdi:compass-outline"], + "windazimuth_1d": ["Wind direction azimuth 1d", "°", "mdi:compass-outline"], + "windazimuth_2d": ["Wind direction azimuth 2d", "°", "mdi:compass-outline"], + "windazimuth_3d": ["Wind direction azimuth 3d", "°", "mdi:compass-outline"], + "windazimuth_4d": ["Wind direction azimuth 4d", "°", "mdi:compass-outline"], + "windazimuth_5d": ["Wind direction azimuth 5d", "°", "mdi:compass-outline"], + "condition_1d": ["Condition 1d", None, None], + "condition_2d": ["Condition 2d", None, None], + "condition_3d": ["Condition 3d", None, None], + "condition_4d": ["Condition 4d", None, None], + "condition_5d": ["Condition 5d", None, None], + "conditioncode_1d": ["Condition code 1d", None, None], + "conditioncode_2d": ["Condition code 2d", None, None], + "conditioncode_3d": ["Condition code 3d", None, None], + "conditioncode_4d": ["Condition code 4d", None, None], + "conditioncode_5d": ["Condition code 5d", None, None], + "conditiondetailed_1d": ["Detailed condition 1d", None, None], + "conditiondetailed_2d": ["Detailed condition 2d", None, None], + "conditiondetailed_3d": ["Detailed condition 3d", None, None], + "conditiondetailed_4d": ["Detailed condition 4d", None, None], + "conditiondetailed_5d": ["Detailed condition 5d", None, None], + "conditionexact_1d": ["Full condition 1d", None, None], + "conditionexact_2d": ["Full condition 2d", None, None], + "conditionexact_3d": ["Full condition 3d", None, None], + "conditionexact_4d": ["Full condition 4d", None, None], + "conditionexact_5d": ["Full condition 5d", None, None], + "symbol_1d": ["Symbol 1d", None, None], + "symbol_2d": ["Symbol 2d", None, None], + "symbol_3d": ["Symbol 3d", None, None], + "symbol_4d": ["Symbol 4d", None, None], + "symbol_5d": ["Symbol 5d", None, None], } -CONF_TIMEFRAME = 'timeframe' +CONF_TIMEFRAME = "timeframe" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_MONITORED_CONDITIONS, - default=['symbol', 'temperature']): vol.All( - cv.ensure_list, vol.Length(min=1), - [vol.In(SENSOR_TYPES.keys())]), - vol.Inclusive(CONF_LATITUDE, 'coordinates', - 'Latitude and longitude must exist together'): cv.latitude, - vol.Inclusive(CONF_LONGITUDE, 'coordinates', - 'Latitude and longitude must exist together'): cv.longitude, - vol.Optional(CONF_TIMEFRAME, default=60): - vol.All(vol.Coerce(int), vol.Range(min=5, max=120)), - vol.Optional(CONF_NAME, default='br'): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional( + CONF_MONITORED_CONDITIONS, default=["symbol", "temperature"] + ): vol.All(cv.ensure_list, vol.Length(min=1), [vol.In(SENSOR_TYPES.keys())]), + vol.Inclusive( + CONF_LATITUDE, "coordinates", "Latitude and longitude must exist together" + ): cv.latitude, + vol.Inclusive( + CONF_LONGITUDE, "coordinates", "Latitude and longitude must exist together" + ): cv.longitude, + vol.Optional(CONF_TIMEFRAME, default=60): vol.All( + vol.Coerce(int), vol.Range(min=5, max=120) + ), + vol.Optional(CONF_NAME, default="br"): cv.string, + } +) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Create the buienradar sensor.""" from .weather import DEFAULT_TIMEFRAME @@ -185,16 +193,17 @@ async def async_setup_platform(hass, config, async_add_entities, _LOGGER.error("Latitude or longitude not set in HomeAssistant config") return False - coordinates = {CONF_LATITUDE: float(latitude), - CONF_LONGITUDE: float(longitude)} + coordinates = {CONF_LATITUDE: float(latitude), CONF_LONGITUDE: float(longitude)} - _LOGGER.debug("Initializing buienradar sensor coordinate %s, timeframe %s", - coordinates, timeframe) + _LOGGER.debug( + "Initializing buienradar sensor coordinate %s, timeframe %s", + coordinates, + timeframe, + ) dev = [] for sensor_type in config[CONF_MONITORED_CONDITIONS]: - dev.append(BrSensor(sensor_type, config.get(CONF_NAME), - coordinates)) + dev.append(BrSensor(sensor_type, config.get(CONF_NAME), coordinates)) async_add_entities(dev) data = BrData(hass, coordinates, timeframe, dev) @@ -207,7 +216,7 @@ class BrSensor(Entity): def __init__(self, sensor_type, client_name, coordinates): """Initialize the sensor.""" - from buienradar.constants import (PRECIPITATION_FORECAST, CONDITION) + from buienradar.constants import PRECIPITATION_FORECAST, CONDITION self.client_name = client_name self._name = SENSOR_TYPES[sensor_type][0] @@ -221,8 +230,7 @@ class BrSensor(Entity): self._unique_id = self.uid(coordinates) # All continuous sensors should be forced to be updated - self._force_update = self.type != SYMBOL and \ - not self.type.startswith(CONDITION) + self._force_update = self.type != SYMBOL and not self.type.startswith(CONDITION) if self.type.startswith(PRECIPITATION_FORECAST): self._timeframe = None @@ -230,19 +238,32 @@ class BrSensor(Entity): def uid(self, coordinates): """Generate a unique id using coordinates and sensor type.""" # The combination of the location, name and sensor type is unique - return "%2.6f%2.6f%s" % (coordinates[CONF_LATITUDE], - coordinates[CONF_LONGITUDE], - self.type) + return "%2.6f%2.6f%s" % ( + coordinates[CONF_LATITUDE], + coordinates[CONF_LONGITUDE], + self.type, + ) def load_data(self, data): """Load the sensor with relevant data.""" # Find sensor - from buienradar.constants import (ATTRIBUTION, CONDITION, CONDCODE, - DETAILED, EXACT, EXACTNL, FORECAST, - IMAGE, MEASURED, - PRECIPITATION_FORECAST, STATIONNAME, - TIMEFRAME, VISIBILITY, WINDGUST, - WINDSPEED) + from buienradar.constants import ( + ATTRIBUTION, + CONDITION, + CONDCODE, + DETAILED, + EXACT, + EXACTNL, + FORECAST, + IMAGE, + MEASURED, + PRECIPITATION_FORECAST, + STATIONNAME, + TIMEFRAME, + VISIBILITY, + WINDGUST, + WINDSPEED, + ) # Check if we have a new measurement, # otherwise we do not have to update the sensor @@ -253,21 +274,23 @@ class BrSensor(Entity): self._stationname = data.get(STATIONNAME) self._measured = data.get(MEASURED) - if self.type.endswith('_1d') or \ - self.type.endswith('_2d') or \ - self.type.endswith('_3d') or \ - self.type.endswith('_4d') or \ - self.type.endswith('_5d'): + if ( + self.type.endswith("_1d") + or self.type.endswith("_2d") + or self.type.endswith("_3d") + or self.type.endswith("_4d") + or self.type.endswith("_5d") + ): # update forcasting sensors: fcday = 0 - if self.type.endswith('_2d'): + if self.type.endswith("_2d"): fcday = 1 - if self.type.endswith('_3d'): + if self.type.endswith("_3d"): fcday = 2 - if self.type.endswith('_4d'): + if self.type.endswith("_4d"): fcday = 3 - if self.type.endswith('_5d'): + if self.type.endswith("_5d"): fcday = 4 # update weather symbol & status text @@ -282,11 +305,11 @@ class BrSensor(Entity): new_state = condition.get(CONDITION, None) if self.type.startswith(SYMBOL): new_state = condition.get(EXACTNL, None) - if self.type.startswith('conditioncode'): + if self.type.startswith("conditioncode"): new_state = condition.get(CONDCODE, None) - if self.type.startswith('conditiondetailed'): + if self.type.startswith("conditiondetailed"): new_state = condition.get(DETAILED, None) - if self.type.startswith('conditionexact'): + if self.type.startswith("conditionexact"): new_state = condition.get(EXACT, None) img = condition.get(IMAGE, None) @@ -324,11 +347,11 @@ class BrSensor(Entity): new_state = condition.get(EXACTNL, None) if self.type == CONDITION: new_state = condition.get(CONDITION, None) - if self.type == 'conditioncode': + if self.type == "conditioncode": new_state = condition.get(CONDCODE, None) - if self.type == 'conditiondetailed': + if self.type == "conditiondetailed": new_state = condition.get(DETAILED, None) - if self.type == 'conditionexact': + if self.type == "conditionexact": new_state = condition.get(EXACT, None) img = condition.get(IMAGE, None) @@ -344,7 +367,7 @@ class BrSensor(Entity): # update nested precipitation forecast sensors nested = data.get(PRECIPITATION_FORECAST) self._timeframe = nested.get(TIMEFRAME) - self._state = nested.get(self.type[len(PRECIPITATION_FORECAST)+1:]) + self._state = nested.get(self.type[len(PRECIPITATION_FORECAST) + 1 :]) return True if self.type == WINDSPEED or self.type == WINDGUST: @@ -378,7 +401,7 @@ class BrSensor(Entity): @property def name(self): """Return the name of the sensor.""" - return '{} {}'.format(self.client_name, self._name) + return "{} {}".format(self.client_name, self._name) @property def state(self): @@ -398,7 +421,7 @@ class BrSensor(Entity): @property def device_state_attributes(self): """Return the state attributes.""" - from buienradar.constants import (PRECIPITATION_FORECAST) + from buienradar.constants import PRECIPITATION_FORECAST if self.type.startswith(PRECIPITATION_FORECAST): result = {ATTR_ATTRIBUTION: self._attribution} @@ -409,7 +432,7 @@ class BrSensor(Entity): result = { ATTR_ATTRIBUTION: self._attribution, - SENSOR_TYPES['stationname'][0]: self._stationname, + SENSOR_TYPES["stationname"][0]: self._stationname, } if self._measured is not None: # convert datetime (Europe/Amsterdam) into local datetime @@ -461,13 +484,11 @@ class BrData: """Schedule an update after minute minutes.""" _LOGGER.debug("Scheduling next update in %s minutes.", minute) nxt = dt_util.utcnow() + timedelta(minutes=minute) - async_track_point_in_utc_time(self.hass, self.async_update, - nxt) + async_track_point_in_utc_time(self.hass, self.async_update, nxt) async def get_data(self, url): """Load data from specified url.""" - from buienradar.constants import (CONTENT, - MESSAGE, STATUS_CODE, SUCCESS) + from buienradar.constants import CONTENT, MESSAGE, STATUS_CODE, SUCCESS _LOGGER.debug("Calling url: %s...", url) result = {SUCCESS: False, MESSAGE: None} @@ -494,20 +515,20 @@ class BrData: async def async_update(self, *_): """Update the data from buienradar.""" - from buienradar.constants import (CONTENT, DATA, MESSAGE, - STATUS_CODE, SUCCESS) - from buienradar.buienradar import (parse_data) - from buienradar.urls import (JSON_FEED_URL, - json_precipitation_forecast_url) + from buienradar.constants import CONTENT, DATA, MESSAGE, STATUS_CODE, SUCCESS + from buienradar.buienradar import parse_data + from buienradar.urls import JSON_FEED_URL, json_precipitation_forecast_url content = await self.get_data(JSON_FEED_URL) if content.get(SUCCESS) is not True: # unable to get the data - _LOGGER.warning("Unable to retrieve json data from Buienradar." - "(Msg: %s, status: %s,)", - content.get(MESSAGE), - content.get(STATUS_CODE),) + _LOGGER.warning( + "Unable to retrieve json data from Buienradar." + "(Msg: %s, status: %s,)", + content.get(MESSAGE), + content.get(STATUS_CODE), + ) # schedule new call await self.schedule_update(SCHEDULE_NOK) return @@ -520,27 +541,31 @@ class BrData: if raincontent.get(SUCCESS) is not True: # unable to get the data - _LOGGER.warning("Unable to retrieve raindata from Buienradar." - "(Msg: %s, status: %s,)", - raincontent.get(MESSAGE), - raincontent.get(STATUS_CODE),) + _LOGGER.warning( + "Unable to retrieve raindata from Buienradar." "(Msg: %s, status: %s,)", + raincontent.get(MESSAGE), + raincontent.get(STATUS_CODE), + ) # schedule new call await self.schedule_update(SCHEDULE_NOK) return - result = parse_data(content.get(CONTENT), - raincontent.get(CONTENT), - self.coordinates[CONF_LATITUDE], - self.coordinates[CONF_LONGITUDE], - self.timeframe, - False) + result = parse_data( + content.get(CONTENT), + raincontent.get(CONTENT), + self.coordinates[CONF_LATITUDE], + self.coordinates[CONF_LONGITUDE], + self.timeframe, + False, + ) _LOGGER.debug("Buienradar parsed data: %s", result) if result.get(SUCCESS) is not True: - if int(datetime.now().strftime('%H')) > 0: - _LOGGER.warning("Unable to parse data from Buienradar." - "(Msg: %s)", - result.get(MESSAGE),) + if int(datetime.now().strftime("%H")) > 0: + _LOGGER.warning( + "Unable to parse data from Buienradar." "(Msg: %s)", + result.get(MESSAGE), + ) await self.schedule_update(SCHEDULE_NOK) return @@ -552,24 +577,28 @@ class BrData: def attribution(self): """Return the attribution.""" from buienradar.constants import ATTRIBUTION + return self.data.get(ATTRIBUTION) @property def stationname(self): """Return the name of the selected weatherstation.""" from buienradar.constants import STATIONNAME + return self.data.get(STATIONNAME) @property def condition(self): """Return the condition.""" from buienradar.constants import CONDITION + return self.data.get(CONDITION) @property def temperature(self): """Return the temperature, or None.""" from buienradar.constants import TEMPERATURE + try: return float(self.data.get(TEMPERATURE)) except (ValueError, TypeError): @@ -579,6 +608,7 @@ class BrData: def pressure(self): """Return the pressure, or None.""" from buienradar.constants import PRESSURE + try: return float(self.data.get(PRESSURE)) except (ValueError, TypeError): @@ -588,6 +618,7 @@ class BrData: def humidity(self): """Return the humidity, or None.""" from buienradar.constants import HUMIDITY + try: return int(self.data.get(HUMIDITY)) except (ValueError, TypeError): @@ -597,6 +628,7 @@ class BrData: def visibility(self): """Return the visibility, or None.""" from buienradar.constants import VISIBILITY + try: return int(self.data.get(VISIBILITY)) except (ValueError, TypeError): @@ -606,6 +638,7 @@ class BrData: def wind_speed(self): """Return the windspeed, or None.""" from buienradar.constants import WINDSPEED + try: return float(self.data.get(WINDSPEED)) except (ValueError, TypeError): @@ -615,6 +648,7 @@ class BrData: def wind_bearing(self): """Return the wind bearing, or None.""" from buienradar.constants import WINDAZIMUTH + try: return int(self.data.get(WINDAZIMUTH)) except (ValueError, TypeError): @@ -624,4 +658,5 @@ class BrData: def forecast(self): """Return the forecast data.""" from buienradar.constants import FORECAST + return self.data.get(FORECAST) diff --git a/homeassistant/components/buienradar/weather.py b/homeassistant/components/buienradar/weather.py index 0c2a1e0e2cb..d8ae448c981 100644 --- a/homeassistant/components/buienradar/weather.py +++ b/homeassistant/components/buienradar/weather.py @@ -4,12 +4,17 @@ import logging import voluptuous as vol from homeassistant.components.weather import ( - ATTR_FORECAST_CONDITION, ATTR_FORECAST_TEMP, ATTR_FORECAST_TEMP_LOW, - ATTR_FORECAST_TIME, PLATFORM_SCHEMA, WeatherEntity, - ATTR_FORECAST_PRECIPITATION, ATTR_FORECAST_WIND_BEARING, - ATTR_FORECAST_WIND_SPEED) -from homeassistant.const import ( - CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, TEMP_CELSIUS) + ATTR_FORECAST_CONDITION, + ATTR_FORECAST_TEMP, + ATTR_FORECAST_TEMP_LOW, + ATTR_FORECAST_TIME, + PLATFORM_SCHEMA, + WeatherEntity, + ATTR_FORECAST_PRECIPITATION, + ATTR_FORECAST_WIND_BEARING, + ATTR_FORECAST_WIND_SPEED, +) +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, TEMP_CELSIUS from homeassistant.helpers import config_validation as cv # Reuse data and API logic from the sensor implementation @@ -17,40 +22,41 @@ from .sensor import BrData _LOGGER = logging.getLogger(__name__) -DATA_CONDITION = 'buienradar_condition' +DATA_CONDITION = "buienradar_condition" DEFAULT_TIMEFRAME = 60 -CONF_FORECAST = 'forecast' +CONF_FORECAST = "forecast" CONDITION_CLASSES = { - 'cloudy': ['c', 'p'], - 'fog': ['d', 'n'], - 'hail': [], - 'lightning': ['g'], - 'lightning-rainy': ['s'], - 'partlycloudy': ['b', 'j', 'o', 'r'], - 'pouring': ['l', 'q'], - 'rainy': ['f', 'h', 'k', 'm'], - 'snowy': ['u', 'i', 'v', 't'], - 'snowy-rainy': ['w'], - 'sunny': ['a'], - 'windy': [], - 'windy-variant': [], - 'exceptional': [], + "cloudy": ["c", "p"], + "fog": ["d", "n"], + "hail": [], + "lightning": ["g"], + "lightning-rainy": ["s"], + "partlycloudy": ["b", "j", "o", "r"], + "pouring": ["l", "q"], + "rainy": ["f", "h", "k", "m"], + "snowy": ["u", "i", "v", "t"], + "snowy-rainy": ["w"], + "sunny": ["a"], + "windy": [], + "windy-variant": [], + "exceptional": [], } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_LATITUDE): cv.latitude, - vol.Optional(CONF_LONGITUDE): cv.longitude, - vol.Optional(CONF_FORECAST, default=True): cv.boolean, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_LATITUDE): cv.latitude, + vol.Optional(CONF_LONGITUDE): cv.longitude, + vol.Optional(CONF_FORECAST, default=True): cv.boolean, + } +) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the buienradar platform.""" latitude = config.get(CONF_LATITUDE, hass.config.latitude) longitude = config.get(CONF_LONGITUDE, hass.config.longitude) @@ -59,14 +65,12 @@ async def async_setup_platform(hass, config, async_add_entities, _LOGGER.error("Latitude or longitude not set in Home Assistant config") return False - coordinates = {CONF_LATITUDE: float(latitude), - CONF_LONGITUDE: float(longitude)} + coordinates = {CONF_LATITUDE: float(latitude), CONF_LONGITUDE: float(longitude)} # create weather data: data = BrData(hass, coordinates, DEFAULT_TIMEFRAME, None) # create weather device: - _LOGGER.debug("Initializing buienradar weather: coordinates %s", - coordinates) + _LOGGER.debug("Initializing buienradar weather: coordinates %s", coordinates) # create condition helper if DATA_CONDITION not in hass.data: @@ -99,13 +103,14 @@ class BrWeather(WeatherEntity): @property def name(self): """Return the name of the sensor.""" - return self._stationname or 'BR {}'.format(self._data.stationname - or '(unknown station)') + return self._stationname or "BR {}".format( + self._data.stationname or "(unknown station)" + ) @property def condition(self): """Return the current condition.""" - from buienradar.constants import (CONDCODE) + from buienradar.constants import CONDCODE if self._data and self._data.condition: ccode = self._data.condition.get(CONDCODE) @@ -156,9 +161,16 @@ class BrWeather(WeatherEntity): @property def forecast(self): """Return the forecast array.""" - from buienradar.constants import (CONDITION, CONDCODE, RAIN, DATETIME, - MIN_TEMP, MAX_TEMP, WINDAZIMUTH, - WINDSPEED) + from buienradar.constants import ( + CONDITION, + CONDCODE, + RAIN, + DATETIME, + MIN_TEMP, + MAX_TEMP, + WINDAZIMUTH, + WINDSPEED, + ) if not self._forecast: return None @@ -180,8 +192,7 @@ class BrWeather(WeatherEntity): ATTR_FORECAST_TEMP: data_in.get(MAX_TEMP), ATTR_FORECAST_PRECIPITATION: data_in.get(RAIN), ATTR_FORECAST_WIND_BEARING: data_in.get(WINDAZIMUTH), - ATTR_FORECAST_WIND_SPEED: - round(data_in.get(WINDSPEED) * 3.6, 1) + ATTR_FORECAST_WIND_SPEED: round(data_in.get(WINDSPEED) * 3.6, 1), } fcdata_out.append(data_out) diff --git a/homeassistant/components/caldav/calendar.py b/homeassistant/components/caldav/calendar.py index befa1f40843..6251679b225 100644 --- a/homeassistant/components/caldav/calendar.py +++ b/homeassistant/components/caldav/calendar.py @@ -7,40 +7,55 @@ import re import voluptuous as vol from homeassistant.components.calendar import ( - ENTITY_ID_FORMAT, PLATFORM_SCHEMA, CalendarEventDevice, calculate_offset, - get_date, is_offset_reached) + ENTITY_ID_FORMAT, + PLATFORM_SCHEMA, + CalendarEventDevice, + calculate_offset, + get_date, + is_offset_reached, +) from homeassistant.const import ( - CONF_NAME, CONF_PASSWORD, CONF_URL, CONF_USERNAME, CONF_VERIFY_SSL) + CONF_NAME, + CONF_PASSWORD, + CONF_URL, + CONF_USERNAME, + CONF_VERIFY_SSL, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import generate_entity_id from homeassistant.util import Throttle, dt _LOGGER = logging.getLogger(__name__) -CONF_CALENDARS = 'calendars' -CONF_CUSTOM_CALENDARS = 'custom_calendars' -CONF_CALENDAR = 'calendar' -CONF_SEARCH = 'search' +CONF_CALENDARS = "calendars" +CONF_CUSTOM_CALENDARS = "custom_calendars" +CONF_CALENDAR = "calendar" +CONF_SEARCH = "search" -OFFSET = '!!' +OFFSET = "!!" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - # pylint: disable=no-value-for-parameter - vol.Required(CONF_URL): vol.Url(), - vol.Optional(CONF_CALENDARS, default=[]): - vol.All(cv.ensure_list, [cv.string]), - vol.Inclusive(CONF_USERNAME, 'authentication'): cv.string, - vol.Inclusive(CONF_PASSWORD, 'authentication'): cv.string, - vol.Optional(CONF_CUSTOM_CALENDARS, default=[]): - vol.All(cv.ensure_list, [ - vol.Schema({ - vol.Required(CONF_CALENDAR): cv.string, - vol.Required(CONF_NAME): cv.string, - vol.Required(CONF_SEARCH): cv.string, - }) - ]), - vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + # pylint: disable=no-value-for-parameter + vol.Required(CONF_URL): vol.Url(), + vol.Optional(CONF_CALENDARS, default=[]): vol.All(cv.ensure_list, [cv.string]), + vol.Inclusive(CONF_USERNAME, "authentication"): cv.string, + vol.Inclusive(CONF_PASSWORD, "authentication"): cv.string, + vol.Optional(CONF_CUSTOM_CALENDARS, default=[]): vol.All( + cv.ensure_list, + [ + vol.Schema( + { + vol.Required(CONF_CALENDAR): cv.string, + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_SEARCH): cv.string, + } + ) + ], + ), + vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean, + } +) MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15) @@ -54,7 +69,8 @@ def setup_platform(hass, config, add_entities, disc_info=None): password = config.get(CONF_PASSWORD) client = caldav.DAVClient( - url, None, username, password, ssl_verify_cert=config[CONF_VERIFY_SSL]) + url, None, username, password, ssl_verify_cert=config[CONF_VERIFY_SSL] + ) calendars = client.principal().calendars() @@ -62,8 +78,7 @@ def setup_platform(hass, config, add_entities, disc_info=None): for calendar in list(calendars): # If a calendar name was given in the configuration, # ignore all the others - if (config[CONF_CALENDARS] - and calendar.name not in config[CONF_CALENDARS]): + if config[CONF_CALENDARS] and calendar.name not in config[CONF_CALENDARS]: _LOGGER.debug("Ignoring calendar '%s'", calendar.name) continue @@ -75,20 +90,20 @@ def setup_platform(hass, config, add_entities, disc_info=None): name = cust_calendar[CONF_NAME] device_id = "{} {}".format( - cust_calendar[CONF_CALENDAR], cust_calendar[CONF_NAME]) - entity_id = generate_entity_id( - ENTITY_ID_FORMAT, device_id, hass=hass) + cust_calendar[CONF_CALENDAR], cust_calendar[CONF_NAME] + ) + entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id, hass=hass) calendar_devices.append( WebDavCalendarEventDevice( - name, calendar, entity_id, True, - cust_calendar[CONF_SEARCH])) + name, calendar, entity_id, True, cust_calendar[CONF_SEARCH] + ) + ) # Create a default calendar if there was no custom one if not config[CONF_CUSTOM_CALENDARS]: name = calendar.name device_id = calendar.name - entity_id = generate_entity_id( - ENTITY_ID_FORMAT, device_id, hass=hass) + entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id, hass=hass) calendar_devices.append( WebDavCalendarEventDevice(name, calendar, entity_id) ) @@ -110,9 +125,7 @@ class WebDavCalendarEventDevice(CalendarEventDevice): @property def device_state_attributes(self): """Return the device state attributes.""" - return { - 'offset_reached': self._offset_reached, - } + return {"offset_reached": self._offset_reached} @property def event(self): @@ -153,13 +166,14 @@ class WebDavCalendarData: async def async_get_events(self, hass, start_date, end_date): """Get all events in a specific time frame.""" # Get event list from the current calendar - vevent_list = await hass.async_add_job(self.calendar.date_search, - start_date, end_date) + vevent_list = await hass.async_add_job( + self.calendar.date_search, start_date, end_date + ) event_list = [] for event in vevent_list: vevent = event.instance.vevent uid = None - if hasattr(vevent, 'uid'): + if hasattr(vevent, "uid"): uid = vevent.uid.value data = { "uid": uid, @@ -170,8 +184,8 @@ class WebDavCalendarData: "description": self.get_attr_value(vevent, "description"), } - data['start'] = get_date(data['start']).isoformat() - data['end'] = get_date(data['end']).isoformat() + data["start"] = get_date(data["start"]).isoformat() + data["end"] = get_date(data["end"]).isoformat() event_list.append(data) @@ -183,30 +197,36 @@ class WebDavCalendarData: # We have to retrieve the results for the whole day as the server # won't return events that have already started results = self.calendar.date_search( - dt.start_of_local_day(), - dt.start_of_local_day() + timedelta(days=1) + dt.start_of_local_day(), dt.start_of_local_day() + timedelta(days=1) ) # dtstart can be a date or datetime depending if the event lasts a # whole day. Convert everything to datetime to be able to sort it - results.sort(key=lambda x: self.to_datetime( - x.instance.vevent.dtstart.value - )) + results.sort(key=lambda x: self.to_datetime(x.instance.vevent.dtstart.value)) - vevent = next(( - event.instance.vevent - for event in results - if (self.is_matching(event.instance.vevent, self.search) - and ( - not self.is_all_day(event.instance.vevent) - or self.include_all_day) - and not self.is_over(event.instance.vevent))), None) + vevent = next( + ( + event.instance.vevent + for event in results + if ( + self.is_matching(event.instance.vevent, self.search) + and ( + not self.is_all_day(event.instance.vevent) + or self.include_all_day + ) + and not self.is_over(event.instance.vevent) + ) + ), + None, + ) # If no matching event could be found if vevent is None: _LOGGER.debug( "No matching event found in the %d results for %s", - len(results), self.calendar.name) + len(results), + self.calendar.name, + ) self.event = None return @@ -216,7 +236,7 @@ class WebDavCalendarData: "start": self.get_hass_date(vevent.dtstart.value), "end": self.get_hass_date(self.get_end_date(vevent)), "location": self.get_attr_value(vevent, "location"), - "description": self.get_attr_value(vevent, "description") + "description": self.get_attr_value(vevent, "description"), } @staticmethod @@ -232,7 +252,8 @@ class WebDavCalendarData: or hasattr(vevent, "location") and pattern.match(vevent.location.value) or hasattr(vevent, "description") - and pattern.match(vevent.description.value)) + and pattern.match(vevent.description.value) + ) @staticmethod def is_all_day(vevent): diff --git a/homeassistant/components/calendar/__init__.py b/homeassistant/components/calendar/__init__.py index b02511470a4..e538b6b802a 100644 --- a/homeassistant/components/calendar/__init__.py +++ b/homeassistant/components/calendar/__init__.py @@ -8,7 +8,10 @@ from aiohttp import web from homeassistant.components import http from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.helpers.config_validation import ( # noqa - PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE, time_period_str) + PLATFORM_SCHEMA, + PLATFORM_SCHEMA_BASE, + time_period_str, +) from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.template import DATE_STR_FORMAT @@ -16,15 +19,16 @@ from homeassistant.util import dt _LOGGER = logging.getLogger(__name__) -DOMAIN = 'calendar' -ENTITY_ID_FORMAT = DOMAIN + '.{}' +DOMAIN = "calendar" +ENTITY_ID_FORMAT = DOMAIN + ".{}" SCAN_INTERVAL = timedelta(seconds=60) async def async_setup(hass, config): """Track states and offer events for calendars.""" component = hass.data[DOMAIN] = EntityComponent( - _LOGGER, DOMAIN, hass, SCAN_INTERVAL, DOMAIN) + _LOGGER, DOMAIN, hass, SCAN_INTERVAL, DOMAIN + ) hass.http.register_view(CalendarListView(component)) hass.http.register_view(CalendarEventView(component)) @@ -39,34 +43,35 @@ async def async_setup(hass, config): def get_date(date): """Get the dateTime from date or dateTime as a local.""" - if 'date' in date: - return dt.start_of_local_day(dt.dt.datetime.combine( - dt.parse_date(date['date']), dt.dt.time.min)) - return dt.as_local(dt.parse_datetime(date['dateTime'])) + if "date" in date: + return dt.start_of_local_day( + dt.dt.datetime.combine(dt.parse_date(date["date"]), dt.dt.time.min) + ) + return dt.as_local(dt.parse_datetime(date["dateTime"])) def normalize_event(event): """Normalize a calendar event.""" normalized_event = {} - start = event.get('start') - end = event.get('end') + start = event.get("start") + end = event.get("end") start = get_date(start) if start is not None else None end = get_date(end) if end is not None else None - normalized_event['dt_start'] = start - normalized_event['dt_end'] = end + normalized_event["dt_start"] = start + normalized_event["dt_end"] = end start = start.strftime(DATE_STR_FORMAT) if start is not None else None end = end.strftime(DATE_STR_FORMAT) if end is not None else None - normalized_event['start'] = start - normalized_event['end'] = end + normalized_event["start"] = start + normalized_event["end"] = end # cleanup the string so we don't have a bunch of double+ spaces - summary = event.get('summary', '') - normalized_event['message'] = re.sub(' +', '', summary).strip() - normalized_event['location'] = event.get('location', '') - normalized_event['description'] = event.get('description', '') - normalized_event['all_day'] = 'date' in event['start'] + summary = event.get("summary", "") + normalized_event["message"] = re.sub(" +", "", summary).strip() + normalized_event["location"] = event.get("location", "") + normalized_event["description"] = event.get("description", "") + normalized_event["all_day"] = "date" in event["start"] return normalized_event @@ -76,37 +81,36 @@ def calculate_offset(event, offset): Return the updated event with the offset_time included. """ - summary = event.get('summary', '') + summary = event.get("summary", "") # check if we have an offset tag in the message # time is HH:MM or MM - reg = '{}([+-]?[0-9]{{0,2}}(:[0-9]{{0,2}})?)'.format(offset) + reg = "{}([+-]?[0-9]{{0,2}}(:[0-9]{{0,2}})?)".format(offset) search = re.search(reg, summary) if search and search.group(1): time = search.group(1) - if ':' not in time: - if time[0] == '+' or time[0] == '-': - time = '{}0:{}'.format(time[0], time[1:]) + if ":" not in time: + if time[0] == "+" or time[0] == "-": + time = "{}0:{}".format(time[0], time[1:]) else: - time = '0:{}'.format(time) + time = "0:{}".format(time) offset_time = time_period_str(time) - summary = ( - summary[:search.start()] + summary[search.end():]).strip() - event['summary'] = summary + summary = (summary[: search.start()] + summary[search.end() :]).strip() + event["summary"] = summary else: offset_time = dt.dt.timedelta() # default it - event['offset_time'] = offset_time + event["offset_time"] = offset_time return event def is_offset_reached(event): """Have we reached the offset time specified in the event title.""" - start = get_date(event['start']) - if start is None or event['offset_time'] == dt.dt.timedelta(): + start = get_date(event["start"]) + if start is None or event["offset_time"] == dt.dt.timedelta(): return False - return start + event['offset_time'] <= dt.now(start.tzinfo) + return start + event["offset_time"] <= dt.now(start.tzinfo) class CalendarEventDevice(Entity): @@ -126,12 +130,12 @@ class CalendarEventDevice(Entity): event = normalize_event(event) return { - 'message': event['message'], - 'all_day': event['all_day'], - 'start_time': event['start'], - 'end_time': event['end'], - 'location': event['location'], - 'description': event['description'], + "message": event["message"], + "all_day": event["all_day"], + "start_time": event["start"], + "end_time": event["end"], + "location": event["location"], + "description": event["description"], } @property @@ -142,8 +146,8 @@ class CalendarEventDevice(Entity): return STATE_OFF event = normalize_event(event) - start = event['dt_start'] - end = event['dt_end'] + start = event["dt_start"] + end = event["dt_end"] if start is None or end is None: return STATE_OFF @@ -163,8 +167,8 @@ class CalendarEventDevice(Entity): class CalendarEventView(http.HomeAssistantView): """View to retrieve calendar content.""" - url = '/api/calendars/{entity_id}' - name = 'api:calendars:calendar' + url = "/api/calendars/{entity_id}" + name = "api:calendars:calendar" def __init__(self, component): """Initialize calendar view.""" @@ -173,8 +177,8 @@ class CalendarEventView(http.HomeAssistantView): async def get(self, request, entity_id): """Return calendar events.""" entity = self.component.get_entity(entity_id) - start = request.query.get('start') - end = request.query.get('end') + start = request.query.get("start") + end = request.query.get("end") if None in (start, end, entity): return web.Response(status=400) try: @@ -183,14 +187,15 @@ class CalendarEventView(http.HomeAssistantView): except (ValueError, AttributeError): return web.Response(status=400) event_list = await entity.async_get_events( - request.app['hass'], start_date, end_date) + request.app["hass"], start_date, end_date + ) return self.json(event_list) class CalendarListView(http.HomeAssistantView): """View to retrieve calendar list.""" - url = '/api/calendars' + url = "/api/calendars" name = "api:calendars" def __init__(self, component): @@ -199,14 +204,11 @@ class CalendarListView(http.HomeAssistantView): async def get(self, request): """Retrieve calendar list.""" - hass = request.app['hass'] + hass = request.app["hass"] calendar_list = [] for entity in self.component.entities: state = hass.states.get(entity.entity_id) - calendar_list.append({ - "name": state.name, - "entity_id": entity.entity_id, - }) + calendar_list.append({"name": state.name, "entity_id": entity.entity_id}) - return self.json(sorted(calendar_list, key=lambda x: x['name'])) + return self.json(sorted(calendar_list, key=lambda x: x["name"])) diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index c31f1b03b55..efe7a37b310 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -14,22 +14,37 @@ import async_timeout import voluptuous as vol from homeassistant.core import callback -from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, \ - SERVICE_TURN_ON, CONF_FILENAME +from homeassistant.const import ( + ATTR_ENTITY_ID, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + CONF_FILENAME, +) from homeassistant.exceptions import HomeAssistantError from homeassistant.loader import bind_hass from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.config_validation import ( # noqa - PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE) + PLATFORM_SCHEMA, + PLATFORM_SCHEMA_BASE, +) from homeassistant.components.http import HomeAssistantView, KEY_AUTHENTICATED from homeassistant.components.media_player.const import ( - ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, - SERVICE_PLAY_MEDIA, DOMAIN as DOMAIN_MP) + ATTR_MEDIA_CONTENT_ID, + ATTR_MEDIA_CONTENT_TYPE, + SERVICE_PLAY_MEDIA, + DOMAIN as DOMAIN_MP, +) from homeassistant.components.stream import request_stream from homeassistant.components.stream.const import ( - OUTPUT_FORMATS, FORMAT_CONTENT_TYPE, CONF_STREAM_SOURCE, CONF_LOOKBACK, - CONF_DURATION, SERVICE_RECORD, DOMAIN as DOMAIN_STREAM) + OUTPUT_FORMATS, + FORMAT_CONTENT_TYPE, + CONF_STREAM_SOURCE, + CONF_LOOKBACK, + CONF_DURATION, + SERVICE_RECORD, + DOMAIN as DOMAIN_STREAM, +) from homeassistant.components import websocket_api import homeassistant.helpers.config_validation as cv from homeassistant.setup import async_when_setup @@ -39,58 +54,62 @@ from .prefs import CameraPreferences _LOGGER = logging.getLogger(__name__) -SERVICE_ENABLE_MOTION = 'enable_motion_detection' -SERVICE_DISABLE_MOTION = 'disable_motion_detection' -SERVICE_SNAPSHOT = 'snapshot' -SERVICE_PLAY_STREAM = 'play_stream' +SERVICE_ENABLE_MOTION = "enable_motion_detection" +SERVICE_DISABLE_MOTION = "disable_motion_detection" +SERVICE_SNAPSHOT = "snapshot" +SERVICE_PLAY_STREAM = "play_stream" SCAN_INTERVAL = timedelta(seconds=30) -ENTITY_ID_FORMAT = DOMAIN + '.{}' +ENTITY_ID_FORMAT = DOMAIN + ".{}" -ATTR_FILENAME = 'filename' -ATTR_MEDIA_PLAYER = 'media_player' -ATTR_FORMAT = 'format' +ATTR_FILENAME = "filename" +ATTR_MEDIA_PLAYER = "media_player" +ATTR_FORMAT = "format" -STATE_RECORDING = 'recording' -STATE_STREAMING = 'streaming' -STATE_IDLE = 'idle' +STATE_RECORDING = "recording" +STATE_STREAMING = "streaming" +STATE_IDLE = "idle" # Bitfield of features supported by the camera entity SUPPORT_ON_OFF = 1 SUPPORT_STREAM = 2 -DEFAULT_CONTENT_TYPE = 'image/jpeg' -ENTITY_IMAGE_URL = '/api/camera_proxy/{0}?token={1}' +DEFAULT_CONTENT_TYPE = "image/jpeg" +ENTITY_IMAGE_URL = "/api/camera_proxy/{0}?token={1}" TOKEN_CHANGE_INTERVAL = timedelta(minutes=5) _RND = SystemRandom() MIN_STREAM_INTERVAL = 0.5 # seconds -CAMERA_SERVICE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids, -}) +CAMERA_SERVICE_SCHEMA = vol.Schema({vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids}) -CAMERA_SERVICE_SNAPSHOT = CAMERA_SERVICE_SCHEMA.extend({ - vol.Required(ATTR_FILENAME): cv.template -}) +CAMERA_SERVICE_SNAPSHOT = CAMERA_SERVICE_SCHEMA.extend( + {vol.Required(ATTR_FILENAME): cv.template} +) -CAMERA_SERVICE_PLAY_STREAM = CAMERA_SERVICE_SCHEMA.extend({ - vol.Required(ATTR_MEDIA_PLAYER): cv.entities_domain(DOMAIN_MP), - vol.Optional(ATTR_FORMAT, default='hls'): vol.In(OUTPUT_FORMATS), -}) +CAMERA_SERVICE_PLAY_STREAM = CAMERA_SERVICE_SCHEMA.extend( + { + vol.Required(ATTR_MEDIA_PLAYER): cv.entities_domain(DOMAIN_MP), + vol.Optional(ATTR_FORMAT, default="hls"): vol.In(OUTPUT_FORMATS), + } +) -CAMERA_SERVICE_RECORD = CAMERA_SERVICE_SCHEMA.extend({ - vol.Required(CONF_FILENAME): cv.template, - vol.Optional(CONF_DURATION, default=30): vol.Coerce(int), - vol.Optional(CONF_LOOKBACK, default=0): vol.Coerce(int), -}) +CAMERA_SERVICE_RECORD = CAMERA_SERVICE_SCHEMA.extend( + { + vol.Required(CONF_FILENAME): cv.template, + vol.Optional(CONF_DURATION, default=30): vol.Coerce(int), + vol.Optional(CONF_LOOKBACK, default=0): vol.Coerce(int), + } +) -WS_TYPE_CAMERA_THUMBNAIL = 'camera_thumbnail' -SCHEMA_WS_CAMERA_THUMBNAIL = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_CAMERA_THUMBNAIL, - vol.Required('entity_id'): cv.entity_id -}) +WS_TYPE_CAMERA_THUMBNAIL = "camera_thumbnail" +SCHEMA_WS_CAMERA_THUMBNAIL = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + { + vol.Required("type"): WS_TYPE_CAMERA_THUMBNAIL, + vol.Required("entity_id"): cv.entity_id, + } +) @attr.s @@ -111,11 +130,11 @@ async def async_request_stream(hass, entity_id, fmt): source = await camera.stream_source() if not source: - raise HomeAssistantError("{} does not support play stream service" - .format(camera.entity_id)) + raise HomeAssistantError( + "{} does not support play stream service".format(camera.entity_id) + ) - return request_stream(hass, source, fmt=fmt, - keepalive=camera_prefs.preload_stream) + return request_stream(hass, source, fmt=fmt, keepalive=camera_prefs.preload_stream) @bind_hass @@ -130,7 +149,7 @@ async def async_get_image(hass, entity_id, timeout=10): if image: return Image(camera.content_type, image) - raise HomeAssistantError('Unable to get image') + raise HomeAssistantError("Unable to get image") @bind_hass @@ -147,18 +166,21 @@ async def async_get_still_stream(request, image_cb, content_type, interval): This method must be run in the event loop. """ response = web.StreamResponse() - response.content_type = ('multipart/x-mixed-replace; ' - 'boundary=--frameboundary') + response.content_type = "multipart/x-mixed-replace; " "boundary=--frameboundary" await response.prepare(request) async def write_to_mjpeg_stream(img_bytes): """Write image to stream.""" - await response.write(bytes( - '--frameboundary\r\n' - 'Content-Type: {}\r\n' - 'Content-Length: {}\r\n\r\n'.format( - content_type, len(img_bytes)), - 'utf-8') + img_bytes + b'\r\n') + await response.write( + bytes( + "--frameboundary\r\n" + "Content-Type: {}\r\n" + "Content-Length: {}\r\n\r\n".format(content_type, len(img_bytes)), + "utf-8", + ) + + img_bytes + + b"\r\n" + ) last_image = None @@ -186,23 +208,24 @@ def _get_camera_from_entity_id(hass, entity_id): component = hass.data.get(DOMAIN) if component is None: - raise HomeAssistantError('Camera integration not set up') + raise HomeAssistantError("Camera integration not set up") camera = component.get_entity(entity_id) if camera is None: - raise HomeAssistantError('Camera not found') + raise HomeAssistantError("Camera not found") if not camera.is_on: - raise HomeAssistantError('Camera is off') + raise HomeAssistantError("Camera is off") return camera async def async_setup(hass, config): """Set up the camera component.""" - component = hass.data[DOMAIN] = \ - EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL) + component = hass.data[DOMAIN] = EntityComponent( + _LOGGER, DOMAIN, hass, SCAN_INTERVAL + ) prefs = CameraPreferences(hass) await prefs.async_initialize() @@ -211,13 +234,11 @@ async def async_setup(hass, config): hass.http.register_view(CameraImageView(component)) hass.http.register_view(CameraMjpegStream(component)) hass.components.websocket_api.async_register_command( - WS_TYPE_CAMERA_THUMBNAIL, websocket_camera_thumbnail, - SCHEMA_WS_CAMERA_THUMBNAIL + WS_TYPE_CAMERA_THUMBNAIL, websocket_camera_thumbnail, SCHEMA_WS_CAMERA_THUMBNAIL ) hass.components.websocket_api.async_register_command(ws_camera_stream) hass.components.websocket_api.async_register_command(websocket_get_prefs) - hass.components.websocket_api.async_register_command( - websocket_update_prefs) + hass.components.websocket_api.async_register_command(websocket_update_prefs) await component.async_setup(config) @@ -244,36 +265,30 @@ async def async_setup(hass, config): entity.async_update_token() hass.async_create_task(entity.async_update_ha_state()) - hass.helpers.event.async_track_time_interval( - update_tokens, TOKEN_CHANGE_INTERVAL) + hass.helpers.event.async_track_time_interval(update_tokens, TOKEN_CHANGE_INTERVAL) component.async_register_entity_service( - SERVICE_ENABLE_MOTION, CAMERA_SERVICE_SCHEMA, - 'async_enable_motion_detection' + SERVICE_ENABLE_MOTION, CAMERA_SERVICE_SCHEMA, "async_enable_motion_detection" ) component.async_register_entity_service( - SERVICE_DISABLE_MOTION, CAMERA_SERVICE_SCHEMA, - 'async_disable_motion_detection' + SERVICE_DISABLE_MOTION, CAMERA_SERVICE_SCHEMA, "async_disable_motion_detection" ) component.async_register_entity_service( - SERVICE_TURN_OFF, CAMERA_SERVICE_SCHEMA, - 'async_turn_off' + SERVICE_TURN_OFF, CAMERA_SERVICE_SCHEMA, "async_turn_off" ) component.async_register_entity_service( - SERVICE_TURN_ON, CAMERA_SERVICE_SCHEMA, - 'async_turn_on' + SERVICE_TURN_ON, CAMERA_SERVICE_SCHEMA, "async_turn_on" ) component.async_register_entity_service( - SERVICE_SNAPSHOT, CAMERA_SERVICE_SNAPSHOT, - async_handle_snapshot_service + SERVICE_SNAPSHOT, CAMERA_SERVICE_SNAPSHOT, async_handle_snapshot_service ) component.async_register_entity_service( - SERVICE_PLAY_STREAM, CAMERA_SERVICE_PLAY_STREAM, - async_handle_play_stream_service + SERVICE_PLAY_STREAM, + CAMERA_SERVICE_PLAY_STREAM, + async_handle_play_stream_service, ) component.async_register_entity_service( - SERVICE_RECORD, CAMERA_SERVICE_RECORD, - async_handle_record_service + SERVICE_RECORD, CAMERA_SERVICE_RECORD, async_handle_record_service ) return True @@ -360,8 +375,9 @@ class Camera(Entity): This method must be run in the event loop. """ - return await async_get_still_stream(request, self.async_camera_image, - self.content_type, interval) + return await async_get_still_stream( + request, self.async_camera_image, self.content_type, interval + ) async def handle_async_mjpeg_stream(self, request): """Serve an HTTP MJPEG stream from the camera. @@ -370,8 +386,7 @@ class Camera(Entity): a direct stream from the camera. This method must be run in the event loop. """ - return await self.handle_async_still_stream( - request, self.frame_interval) + return await self.handle_async_still_stream(request, self.frame_interval) @property def state(self): @@ -426,18 +441,16 @@ class Camera(Entity): @property def state_attributes(self): """Return the camera state attributes.""" - attrs = { - 'access_token': self.access_tokens[-1], - } + attrs = {"access_token": self.access_tokens[-1]} if self.model: - attrs['model_name'] = self.model + attrs["model_name"] = self.model if self.brand: - attrs['brand'] = self.brand + attrs["brand"] = self.brand if self.motion_detection_enabled: - attrs['motion_detection'] = self.motion_detection_enabled + attrs["motion_detection"] = self.motion_detection_enabled return attrs @@ -445,8 +458,8 @@ class Camera(Entity): def async_update_token(self): """Update the used token.""" self.access_tokens.append( - hashlib.sha256( - _RND.getrandbits(256).to_bytes(32, 'little')).hexdigest()) + hashlib.sha256(_RND.getrandbits(256).to_bytes(32, "little")).hexdigest() + ) class CameraView(HomeAssistantView): @@ -465,14 +478,16 @@ class CameraView(HomeAssistantView): if camera is None: raise web.HTTPNotFound() - authenticated = (request[KEY_AUTHENTICATED] or - request.query.get('token') in camera.access_tokens) + authenticated = ( + request[KEY_AUTHENTICATED] + or request.query.get("token") in camera.access_tokens + ) if not authenticated: raise web.HTTPUnauthorized() if not camera.is_on: - _LOGGER.debug('Camera is off.') + _LOGGER.debug("Camera is off.") raise web.HTTPServiceUnavailable() return await self.handle(request, camera) @@ -485,8 +500,8 @@ class CameraView(HomeAssistantView): class CameraImageView(CameraView): """Camera view to serve an image.""" - url = '/api/camera_proxy/{entity_id}' - name = 'api:camera:image' + url = "/api/camera_proxy/{entity_id}" + name = "api:camera:image" async def handle(self, request, camera): """Serve camera image.""" @@ -495,8 +510,7 @@ class CameraImageView(CameraView): image = await camera.async_camera_image() if image: - return web.Response(body=image, - content_type=camera.content_type) + return web.Response(body=image, content_type=camera.content_type) raise web.HTTPInternalServerError() @@ -504,21 +518,22 @@ class CameraImageView(CameraView): class CameraMjpegStream(CameraView): """Camera View to serve an MJPEG stream.""" - url = '/api/camera_proxy_stream/{entity_id}' - name = 'api:camera:stream' + url = "/api/camera_proxy_stream/{entity_id}" + name = "api:camera:stream" async def handle(self, request, camera): """Serve camera stream, possibly with interval.""" - interval = request.query.get('interval') + interval = request.query.get("interval") if interval is None: return await camera.handle_async_mjpeg_stream(request) try: # Compose camera stream from stills - interval = float(request.query.get('interval')) + interval = float(request.query.get("interval")) if interval < MIN_STREAM_INTERVAL: - raise ValueError("Stream interval must be be > {}" - .format(MIN_STREAM_INTERVAL)) + raise ValueError( + "Stream interval must be be > {}".format(MIN_STREAM_INTERVAL) + ) return await camera.handle_async_still_stream(request, interval) except ValueError: raise web.HTTPBadRequest() @@ -531,29 +546,37 @@ async def websocket_camera_thumbnail(hass, connection, msg): Async friendly. """ try: - image = await async_get_image(hass, msg['entity_id']) - await connection.send_big_result(msg['id'], { - 'content_type': image.content_type, - 'content': base64.b64encode(image.content).decode('utf-8') - }) + image = await async_get_image(hass, msg["entity_id"]) + await connection.send_big_result( + msg["id"], + { + "content_type": image.content_type, + "content": base64.b64encode(image.content).decode("utf-8"), + }, + ) except HomeAssistantError: - connection.send_message(websocket_api.error_message( - msg['id'], 'image_fetch_failed', 'Unable to fetch image')) + connection.send_message( + websocket_api.error_message( + msg["id"], "image_fetch_failed", "Unable to fetch image" + ) + ) @websocket_api.async_response -@websocket_api.websocket_command({ - vol.Required('type'): 'camera/stream', - vol.Required('entity_id'): cv.entity_id, - vol.Optional('format', default='hls'): vol.In(OUTPUT_FORMATS), -}) +@websocket_api.websocket_command( + { + vol.Required("type"): "camera/stream", + vol.Required("entity_id"): cv.entity_id, + vol.Optional("format", default="hls"): vol.In(OUTPUT_FORMATS), + } +) async def ws_camera_stream(hass, connection, msg): """Handle get camera stream websocket command. Async friendly. """ try: - entity_id = msg['entity_id'] + entity_id = msg["entity_id"] camera = _get_camera_from_entity_id(hass, entity_id) camera_prefs = hass.data[DATA_CAMERA_PREFS].get(entity_id) @@ -561,51 +584,54 @@ async def ws_camera_stream(hass, connection, msg): source = await camera.stream_source() if not source: - raise HomeAssistantError("{} does not support play stream service" - .format(camera.entity_id)) + raise HomeAssistantError( + "{} does not support play stream service".format(camera.entity_id) + ) - fmt = msg['format'] - url = request_stream(hass, source, fmt=fmt, - keepalive=camera_prefs.preload_stream) - connection.send_result(msg['id'], {'url': url}) + fmt = msg["format"] + url = request_stream( + hass, source, fmt=fmt, keepalive=camera_prefs.preload_stream + ) + connection.send_result(msg["id"], {"url": url}) except HomeAssistantError as ex: _LOGGER.error("Error requesting stream: %s", ex) - connection.send_error( - msg['id'], 'start_stream_failed', str(ex)) + connection.send_error(msg["id"], "start_stream_failed", str(ex)) except asyncio.TimeoutError: _LOGGER.error("Timeout getting stream source") connection.send_error( - msg['id'], 'start_stream_failed', "Timeout getting stream source") + msg["id"], "start_stream_failed", "Timeout getting stream source" + ) @websocket_api.async_response -@websocket_api.websocket_command({ - vol.Required('type'): 'camera/get_prefs', - vol.Required('entity_id'): cv.entity_id, -}) +@websocket_api.websocket_command( + {vol.Required("type"): "camera/get_prefs", vol.Required("entity_id"): cv.entity_id} +) async def websocket_get_prefs(hass, connection, msg): """Handle request for account info.""" - prefs = hass.data[DATA_CAMERA_PREFS].get(msg['entity_id']) - connection.send_result(msg['id'], prefs.as_dict()) + prefs = hass.data[DATA_CAMERA_PREFS].get(msg["entity_id"]) + connection.send_result(msg["id"], prefs.as_dict()) @websocket_api.async_response -@websocket_api.websocket_command({ - vol.Required('type'): 'camera/update_prefs', - vol.Required('entity_id'): cv.entity_id, - vol.Optional('preload_stream'): bool, -}) +@websocket_api.websocket_command( + { + vol.Required("type"): "camera/update_prefs", + vol.Required("entity_id"): cv.entity_id, + vol.Optional("preload_stream"): bool, + } +) async def websocket_update_prefs(hass, connection, msg): """Handle request for account info.""" prefs = hass.data[DATA_CAMERA_PREFS] changes = dict(msg) - changes.pop('id') - changes.pop('type') - entity_id = changes.pop('entity_id') + changes.pop("id") + changes.pop("type") + entity_id = changes.pop("entity_id") await prefs.async_update(entity_id, **changes) - connection.send_result(msg['id'], prefs.get(entity_id).as_dict()) + connection.send_result(msg["id"], prefs.get(entity_id).as_dict()) async def async_handle_snapshot_service(camera, service): @@ -614,25 +640,22 @@ async def async_handle_snapshot_service(camera, service): filename = service.data[ATTR_FILENAME] filename.hass = hass - snapshot_file = filename.async_render( - variables={ATTR_ENTITY_ID: camera}) + snapshot_file = filename.async_render(variables={ATTR_ENTITY_ID: camera}) # check if we allow to access to that file if not hass.config.is_allowed_path(snapshot_file): - _LOGGER.error( - "Can't write %s, no access to path!", snapshot_file) + _LOGGER.error("Can't write %s, no access to path!", snapshot_file) return image = await camera.async_camera_image() def _write_image(to_file, image_data): """Executor helper to write image.""" - with open(to_file, 'wb') as img_file: + with open(to_file, "wb") as img_file: img_file.write(image_data) try: - await hass.async_add_executor_job( - _write_image, snapshot_file, image) + await hass.async_add_executor_job(_write_image, snapshot_file, image) except OSError as err: _LOGGER.error("Can't write image to file: %s", err) @@ -643,25 +666,25 @@ async def async_handle_play_stream_service(camera, service_call): source = await camera.stream_source() if not source: - raise HomeAssistantError("{} does not support play stream service" - .format(camera.entity_id)) + raise HomeAssistantError( + "{} does not support play stream service".format(camera.entity_id) + ) hass = camera.hass camera_prefs = hass.data[DATA_CAMERA_PREFS].get(camera.entity_id) fmt = service_call.data[ATTR_FORMAT] entity_ids = service_call.data[ATTR_MEDIA_PLAYER] - url = request_stream(hass, source, fmt=fmt, - keepalive=camera_prefs.preload_stream) + url = request_stream(hass, source, fmt=fmt, keepalive=camera_prefs.preload_stream) data = { ATTR_ENTITY_ID: entity_ids, ATTR_MEDIA_CONTENT_ID: "{}{}".format(hass.config.api.base_url, url), - ATTR_MEDIA_CONTENT_TYPE: FORMAT_CONTENT_TYPE[fmt] + ATTR_MEDIA_CONTENT_TYPE: FORMAT_CONTENT_TYPE[fmt], } await hass.services.async_call( - DOMAIN_MP, SERVICE_PLAY_MEDIA, data, - blocking=True, context=service_call.context) + DOMAIN_MP, SERVICE_PLAY_MEDIA, data, blocking=True, context=service_call.context + ) async def async_handle_record_service(camera, call): @@ -670,14 +693,14 @@ async def async_handle_record_service(camera, call): source = await camera.stream_source() if not source: - raise HomeAssistantError("{} does not support record service" - .format(camera.entity_id)) + raise HomeAssistantError( + "{} does not support record service".format(camera.entity_id) + ) hass = camera.hass filename = call.data[CONF_FILENAME] filename.hass = hass - video_path = filename.async_render( - variables={ATTR_ENTITY_ID: camera}) + video_path = filename.async_render(variables={ATTR_ENTITY_ID: camera}) data = { CONF_STREAM_SOURCE: source, @@ -687,5 +710,5 @@ async def async_handle_record_service(camera, call): } await hass.services.async_call( - DOMAIN_STREAM, SERVICE_RECORD, data, - blocking=True, context=call.context) + DOMAIN_STREAM, SERVICE_RECORD, data, blocking=True, context=call.context + ) diff --git a/homeassistant/components/camera/const.py b/homeassistant/components/camera/const.py index f87ca47460e..563f0554f0f 100644 --- a/homeassistant/components/camera/const.py +++ b/homeassistant/components/camera/const.py @@ -1,6 +1,6 @@ """Constants for Camera component.""" -DOMAIN = 'camera' +DOMAIN = "camera" -DATA_CAMERA_PREFS = 'camera_prefs' +DATA_CAMERA_PREFS = "camera_prefs" -PREF_PRELOAD_STREAM = 'preload_stream' +PREF_PRELOAD_STREAM = "preload_stream" diff --git a/homeassistant/components/camera/prefs.py b/homeassistant/components/camera/prefs.py index 927929bdf6e..5e22b882d0a 100644 --- a/homeassistant/components/camera/prefs.py +++ b/homeassistant/components/camera/prefs.py @@ -41,15 +41,14 @@ class CameraPreferences: self._prefs = prefs - async def async_update(self, entity_id, *, preload_stream=_UNDEF, - stream_options=_UNDEF): + async def async_update( + self, entity_id, *, preload_stream=_UNDEF, stream_options=_UNDEF + ): """Update camera preferences.""" if not self._prefs.get(entity_id): self._prefs[entity_id] = {} - for key, value in ( - (PREF_PRELOAD_STREAM, preload_stream), - ): + for key, value in ((PREF_PRELOAD_STREAM, preload_stream),): if value is not _UNDEF: self._prefs[entity_id][key] = value diff --git a/homeassistant/components/canary/__init__.py b/homeassistant/components/canary/__init__.py index 52b38f14795..f23b6ad46c9 100644 --- a/homeassistant/components/canary/__init__.py +++ b/homeassistant/components/canary/__init__.py @@ -12,25 +12,28 @@ from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -NOTIFICATION_ID = 'canary_notification' -NOTIFICATION_TITLE = 'Canary Setup' +NOTIFICATION_ID = "canary_notification" +NOTIFICATION_TITLE = "Canary Setup" -DOMAIN = 'canary' -DATA_CANARY = 'canary' +DOMAIN = "canary" +DATA_CANARY = "canary" MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30) DEFAULT_TIMEOUT = 10 -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) -CANARY_COMPONENTS = [ - 'alarm_control_panel', 'camera', 'sensor' -] +CANARY_COMPONENTS = ["alarm_control_panel", "camera", "sensor"] def setup(hass, config): @@ -45,11 +48,12 @@ def setup(hass, config): except (ConnectTimeout, HTTPError) as ex: _LOGGER.error("Unable to connect to Canary service: %s", str(ex)) hass.components.persistent_notification.create( - 'Error: {}
' - 'You will need to restart hass after fixing.' - ''.format(ex), + "Error: {}
" + "You will need to restart hass after fixing." + "".format(ex), title=NOTIFICATION_TITLE, - notification_id=NOTIFICATION_ID) + notification_id=NOTIFICATION_ID, + ) return False for component in CANARY_COMPONENTS: @@ -64,6 +68,7 @@ class CanaryData: def __init__(self, username, password, timeout): """Init the Canary data object.""" from canary.api import Api + self._api = Api(username, password, timeout) self._locations_by_id = {} @@ -80,12 +85,14 @@ class CanaryData: self._locations_by_id[location_id] = location self._entries_by_location_id[location_id] = self._api.get_entries( - location_id, entry_type="motion", limit=1) + location_id, entry_type="motion", limit=1 + ) for device in location.devices: if device.is_online: - self._readings_by_device_id[device.device_id] = \ - self._api.get_latest_readings(device.device_id) + self._readings_by_device_id[ + device.device_id + ] = self._api.get_latest_readings(device.device_id) @property def locations(self): @@ -107,9 +114,14 @@ class CanaryData: def get_reading(self, device_id, sensor_type): """Return reading for device_id and sensor type.""" readings = self._readings_by_device_id.get(device_id, []) - return next(( - reading.value for reading in readings - if reading.sensor_type == sensor_type), None) + return next( + ( + reading.value + for reading in readings + if reading.sensor_type == sensor_type + ), + None, + ) def set_location_mode(self, location_id, mode_name, is_private=False): """Set location mode.""" diff --git a/homeassistant/components/canary/alarm_control_panel.py b/homeassistant/components/canary/alarm_control_panel.py index 7402d785532..42b5048bc1d 100644 --- a/homeassistant/components/canary/alarm_control_panel.py +++ b/homeassistant/components/canary/alarm_control_panel.py @@ -3,8 +3,11 @@ import logging from homeassistant.components.alarm_control_panel import AlarmControlPanel from homeassistant.const import ( - STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, - STATE_ALARM_DISARMED) + STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_HOME, + STATE_ALARM_ARMED_NIGHT, + STATE_ALARM_DISARMED, +) from . import DATA_CANARY @@ -39,8 +42,11 @@ class CanaryAlarm(AlarmControlPanel): @property def state(self): """Return the state of the device.""" - from canary.api import LOCATION_MODE_AWAY, LOCATION_MODE_HOME, \ - LOCATION_MODE_NIGHT + from canary.api import ( + LOCATION_MODE_AWAY, + LOCATION_MODE_HOME, + LOCATION_MODE_NIGHT, + ) location = self._data.get_location(self._location_id) @@ -60,27 +66,27 @@ class CanaryAlarm(AlarmControlPanel): def device_state_attributes(self): """Return the state attributes.""" location = self._data.get_location(self._location_id) - return { - 'private': location.is_private - } + return {"private": location.is_private} def alarm_disarm(self, code=None): """Send disarm command.""" location = self._data.get_location(self._location_id) - self._data.set_location_mode(self._location_id, location.mode.name, - True) + self._data.set_location_mode(self._location_id, location.mode.name, True) def alarm_arm_home(self, code=None): """Send arm home command.""" from canary.api import LOCATION_MODE_HOME + self._data.set_location_mode(self._location_id, LOCATION_MODE_HOME) def alarm_arm_away(self, code=None): """Send arm away command.""" from canary.api import LOCATION_MODE_AWAY + self._data.set_location_mode(self._location_id, LOCATION_MODE_AWAY) def alarm_arm_night(self, code=None): """Send arm night command.""" from canary.api import LOCATION_MODE_NIGHT + self._data.set_location_mode(self._location_id, LOCATION_MODE_NIGHT) diff --git a/homeassistant/components/canary/camera.py b/homeassistant/components/canary/camera.py index 9411ab2a41c..8a6d27b8916 100644 --- a/homeassistant/components/canary/camera.py +++ b/homeassistant/components/canary/camera.py @@ -15,14 +15,14 @@ from . import DATA_CANARY, DEFAULT_TIMEOUT _LOGGER = logging.getLogger(__name__) -CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments' -DEFAULT_ARGUMENTS = '-pred 1' +CONF_FFMPEG_ARGUMENTS = "ffmpeg_arguments" +DEFAULT_ARGUMENTS = "-pred 1" MIN_TIME_BETWEEN_SESSION_RENEW = timedelta(seconds=90) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_FFMPEG_ARGUMENTS, default=DEFAULT_ARGUMENTS): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Optional(CONF_FFMPEG_ARGUMENTS, default=DEFAULT_ARGUMENTS): cv.string} +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -34,8 +34,15 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for device in location.devices: if device.is_online: devices.append( - CanaryCamera(hass, data, location, device, DEFAULT_TIMEOUT, - config.get(CONF_FFMPEG_ARGUMENTS))) + CanaryCamera( + hass, + data, + location, + device, + DEFAULT_TIMEOUT, + config.get(CONF_FFMPEG_ARGUMENTS), + ) + ) add_entities(devices, True) @@ -75,11 +82,15 @@ class CanaryCamera(Camera): self.renew_live_stream_session() from haffmpeg.tools import ImageFrame, IMAGE_JPEG + ffmpeg = ImageFrame(self._ffmpeg.binary, loop=self.hass.loop) - image = await asyncio.shield(ffmpeg.get_image( - self._live_stream_session.live_stream_url, - output_format=IMAGE_JPEG, - extra_cmd=self._ffmpeg_arguments)) + image = await asyncio.shield( + ffmpeg.get_image( + self._live_stream_session.live_stream_url, + output_format=IMAGE_JPEG, + extra_cmd=self._ffmpeg_arguments, + ) + ) return image async def handle_async_mjpeg_stream(self, request): @@ -88,21 +99,24 @@ class CanaryCamera(Camera): return from haffmpeg.camera import CameraMjpeg + stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop) await stream.open_camera( - self._live_stream_session.live_stream_url, - extra_cmd=self._ffmpeg_arguments) + self._live_stream_session.live_stream_url, extra_cmd=self._ffmpeg_arguments + ) try: stream_reader = await stream.get_reader() return await async_aiohttp_proxy_stream( - self.hass, request, stream_reader, - self._ffmpeg.ffmpeg_stream_content_type) + self.hass, + request, + stream_reader, + self._ffmpeg.ffmpeg_stream_content_type, + ) finally: await stream.close() @Throttle(MIN_TIME_BETWEEN_SESSION_RENEW) def renew_live_stream_session(self): """Renew live stream session.""" - self._live_stream_session = self._data.get_live_stream_session( - self._device) + self._live_stream_session = self._data.get_live_stream_session(self._device) diff --git a/homeassistant/components/canary/sensor.py b/homeassistant/components/canary/sensor.py index 220abc9b387..dcb54a772a3 100644 --- a/homeassistant/components/canary/sensor.py +++ b/homeassistant/components/canary/sensor.py @@ -35,8 +35,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): device_type = device.device_type for sensor_type in SENSOR_TYPES: if device_type.get("name") in sensor_type[3]: - devices.append(CanarySensor(data, sensor_type, - location, device)) + devices.append( + CanarySensor(data, sensor_type, location, device) + ) add_entities(devices, True) @@ -52,9 +53,7 @@ class CanarySensor(Entity): self._sensor_value = None sensor_type_name = sensor_type[0].replace("_", " ").title() - self._name = '{} {} {}'.format(location.name, - device.name, - sensor_type_name) + self._name = "{} {} {}".format(location.name, device.name, sensor_type_name) @property def name(self): @@ -87,19 +86,16 @@ class CanarySensor(Entity): @property def device_state_attributes(self): """Return the state attributes.""" - if self._sensor_type[0] == "air_quality" \ - and self._sensor_value is not None: + if self._sensor_type[0] == "air_quality" and self._sensor_value is not None: air_quality = None - if self._sensor_value <= .4: + if self._sensor_value <= 0.4: air_quality = STATE_AIR_QUALITY_VERY_ABNORMAL - elif self._sensor_value <= .59: + elif self._sensor_value <= 0.59: air_quality = STATE_AIR_QUALITY_ABNORMAL elif self._sensor_value <= 1.0: air_quality = STATE_AIR_QUALITY_NORMAL - return { - ATTR_AIR_QUALITY: air_quality - } + return {ATTR_AIR_QUALITY: air_quality} return None @@ -108,6 +104,7 @@ class CanarySensor(Entity): self._data.update() from canary.api import SensorType + canary_sensor_type = None if self._sensor_type[0] == "air_quality": canary_sensor_type = SensorType.AIR_QUALITY diff --git a/homeassistant/components/cast/__init__.py b/homeassistant/components/cast/__init__.py index f91b90c1e08..cc112984f88 100644 --- a/homeassistant/components/cast/__init__.py +++ b/homeassistant/components/cast/__init__.py @@ -11,14 +11,18 @@ async def async_setup(hass, config): hass.data[DOMAIN] = conf or {} if conf is not None: - hass.async_create_task(hass.config_entries.flow.async_init( - DOMAIN, context={'source': config_entries.SOURCE_IMPORT})) + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_IMPORT} + ) + ) return True async def async_setup_entry(hass, entry): """Set up Cast from a config entry.""" - hass.async_create_task(hass.config_entries.async_forward_entry_setup( - entry, 'media_player')) + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, "media_player") + ) return True diff --git a/homeassistant/components/cast/config_flow.py b/homeassistant/components/cast/config_flow.py index 0f8696cf29c..c3c21944d02 100644 --- a/homeassistant/components/cast/config_flow.py +++ b/homeassistant/components/cast/config_flow.py @@ -12,5 +12,5 @@ async def _async_has_devices(hass): config_entry_flow.register_discovery_flow( - DOMAIN, 'Google Cast', _async_has_devices, - config_entries.CONN_CLASS_LOCAL_PUSH) + DOMAIN, "Google Cast", _async_has_devices, config_entries.CONN_CLASS_LOCAL_PUSH +) diff --git a/homeassistant/components/cast/const.py b/homeassistant/components/cast/const.py index 48bb87ca5d7..e9f9ba4c39d 100644 --- a/homeassistant/components/cast/const.py +++ b/homeassistant/components/cast/const.py @@ -1,3 +1,3 @@ """Consts for Cast integration.""" -DOMAIN = 'cast' +DOMAIN = "cast" diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index 07818c03057..2e61e3111e1 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -7,64 +7,86 @@ from typing import Optional, Tuple import attr import voluptuous as vol -from homeassistant.components.media_player import ( - PLATFORM_SCHEMA, MediaPlayerDevice) +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice from homeassistant.components.media_player.const import ( - MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, - SUPPORT_SEEK, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET) + MEDIA_TYPE_MOVIE, + MEDIA_TYPE_MUSIC, + MEDIA_TYPE_TVSHOW, + SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, + SUPPORT_PLAY, + SUPPORT_PLAY_MEDIA, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_SEEK, + SUPPORT_STOP, + SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, + SUPPORT_VOLUME_MUTE, + SUPPORT_VOLUME_SET, +) from homeassistant.const import ( - CONF_HOST, EVENT_HOMEASSISTANT_STOP, STATE_IDLE, STATE_OFF, STATE_PAUSED, - STATE_PLAYING) + CONF_HOST, + EVENT_HOMEASSISTANT_STOP, + STATE_IDLE, + STATE_OFF, + STATE_PAUSED, + STATE_PLAYING, +) from homeassistant.core import callback from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.dispatcher import ( - async_dispatcher_connect, dispatcher_send) +from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send from homeassistant.helpers.typing import ConfigType, HomeAssistantType import homeassistant.util.dt as dt_util from homeassistant.util.logging import async_create_catching_coro from . import DOMAIN as CAST_DOMAIN -DEPENDENCIES = ('cast',) +DEPENDENCIES = ("cast",) _LOGGER = logging.getLogger(__name__) -CONF_IGNORE_CEC = 'ignore_cec' -CAST_SPLASH = 'https://home-assistant.io/images/cast/splash.png' +CONF_IGNORE_CEC = "ignore_cec" +CAST_SPLASH = "https://home-assistant.io/images/cast/splash.png" DEFAULT_PORT = 8009 -SUPPORT_CAST = SUPPORT_PAUSE | SUPPORT_PLAY | SUPPORT_PLAY_MEDIA | \ - SUPPORT_STOP | SUPPORT_TURN_OFF | SUPPORT_TURN_ON | \ - SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET +SUPPORT_CAST = ( + SUPPORT_PAUSE + | SUPPORT_PLAY + | SUPPORT_PLAY_MEDIA + | SUPPORT_STOP + | SUPPORT_TURN_OFF + | SUPPORT_TURN_ON + | SUPPORT_VOLUME_MUTE + | SUPPORT_VOLUME_SET +) # Stores a threading.Lock that is held by the internal pychromecast discovery. -INTERNAL_DISCOVERY_RUNNING_KEY = 'cast_discovery_running' +INTERNAL_DISCOVERY_RUNNING_KEY = "cast_discovery_running" # Stores all ChromecastInfo we encountered through discovery or config as a set # If we find a chromecast with a new host, the old one will be removed again. -KNOWN_CHROMECAST_INFO_KEY = 'cast_known_chromecasts' +KNOWN_CHROMECAST_INFO_KEY = "cast_known_chromecasts" # Stores UUIDs of cast devices that were added as entities. Doesn't store # None UUIDs. -ADDED_CAST_DEVICES_KEY = 'cast_added_cast_devices' +ADDED_CAST_DEVICES_KEY = "cast_added_cast_devices" # Stores an audio group manager. -CAST_MULTIZONE_MANAGER_KEY = 'cast_multizone_manager' +CAST_MULTIZONE_MANAGER_KEY = "cast_multizone_manager" # Dispatcher signal fired with a ChromecastInfo every time we discover a new # Chromecast or receive it through configuration -SIGNAL_CAST_DISCOVERED = 'cast_discovered' +SIGNAL_CAST_DISCOVERED = "cast_discovered" # Dispatcher signal fired with a ChromecastInfo every time a Chromecast is # removed -SIGNAL_CAST_REMOVED = 'cast_removed' +SIGNAL_CAST_REMOVED = "cast_removed" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_HOST): cv.string, - vol.Optional(CONF_IGNORE_CEC, default=[]): - vol.All(cv.ensure_list, [cv.string]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_HOST): cv.string, + vol.Optional(CONF_IGNORE_CEC, default=[]): vol.All(cv.ensure_list, [cv.string]), + } +) @attr.s(slots=True, frozen=True) @@ -77,10 +99,11 @@ class ChromecastInfo: host = attr.ib(type=str) port = attr.ib(type=int) service = attr.ib(type=Optional[str], default=None) - uuid = attr.ib(type=Optional[str], converter=attr.converters.optional(str), - default=None) # always convert UUID to string if not None - manufacturer = attr.ib(type=str, default='') - model_name = attr.ib(type=str, default='') + uuid = attr.ib( + type=Optional[str], converter=attr.converters.optional(str), default=None + ) # always convert UUID to string if not None + manufacturer = attr.ib(type=str, default="") + model_name = attr.ib(type=str, default="") friendly_name = attr.ib(type=Optional[str], default=None) is_dynamic_group = attr.ib(type=Optional[bool], default=None) @@ -95,10 +118,16 @@ class ChromecastInfo: want_dynamic_group = self.is_audio_group have_dynamic_group = self.is_dynamic_group is not None have_all_except_dynamic_group = all( - attr.astuple(self, filter=attr.filters.exclude( - attr.fields(ChromecastInfo).is_dynamic_group))) - return (have_all_except_dynamic_group and - (not want_dynamic_group or have_dynamic_group)) + attr.astuple( + self, + filter=attr.filters.exclude( + attr.fields(ChromecastInfo).is_dynamic_group + ), + ) + ) + return have_all_except_dynamic_group and ( + not want_dynamic_group or have_dynamic_group + ) @property def host_port(self) -> Tuple[str, int]: @@ -106,11 +135,14 @@ class ChromecastInfo: return self.host, self.port -def _is_matching_dynamic_group(our_info: ChromecastInfo, - new_info: ChromecastInfo,) -> bool: - return (our_info.is_audio_group and - new_info.is_dynamic_group and - our_info.friendly_name == new_info.friendly_name) +def _is_matching_dynamic_group( + our_info: ChromecastInfo, new_info: ChromecastInfo +) -> bool: + return ( + our_info.is_audio_group + and new_info.is_dynamic_group + and our_info.friendly_name == new_info.friendly_name + ) def _fill_out_missing_chromecast_info(info: ChromecastInfo) -> ChromecastInfo: @@ -129,35 +161,40 @@ def _fill_out_missing_chromecast_info(info: ChromecastInfo) -> ChromecastInfo: dynamic_groups = [] if info.uuid: http_group_status = dial.get_multizone_status( - info.host, services=[info.service], - zconf=ChromeCastZeroconf.get_zeroconf()) + info.host, + services=[info.service], + zconf=ChromeCastZeroconf.get_zeroconf(), + ) if http_group_status is not None: - dynamic_groups = \ - [str(g.uuid) for g in http_group_status.dynamic_groups] + dynamic_groups = [str(g.uuid) for g in http_group_status.dynamic_groups] is_dynamic_group = info.uuid in dynamic_groups return ChromecastInfo( - service=info.service, host=info.host, port=info.port, + service=info.service, + host=info.host, + port=info.port, uuid=info.uuid, friendly_name=info.friendly_name, manufacturer=info.manufacturer, model_name=info.model_name, - is_dynamic_group=is_dynamic_group + is_dynamic_group=is_dynamic_group, ) http_device_status = dial.get_device_status( - info.host, services=[info.service], - zconf=ChromeCastZeroconf.get_zeroconf()) + info.host, services=[info.service], zconf=ChromeCastZeroconf.get_zeroconf() + ) if http_device_status is None: # HTTP dial didn't give us any new information. return info return ChromecastInfo( - service=info.service, host=info.host, port=info.port, + service=info.service, + host=info.host, + port=info.port, uuid=(info.uuid or http_device_status.uuid), friendly_name=(info.friendly_name or http_device_status.friendly_name), manufacturer=(info.manufacturer or http_device_status.manufacturer), - model_name=(info.model_name or http_device_status.model_name) + model_name=(info.model_name or http_device_status.model_name), ) @@ -171,8 +208,9 @@ def _discover_chromecast(hass: HomeAssistantType, info: ChromecastInfo): if info.uuid is not None: # Remove previous cast infos with same uuid from known chromecasts. - same_uuid = set(x for x in hass.data[KNOWN_CHROMECAST_INFO_KEY] - if info.uuid == x.uuid) + same_uuid = set( + x for x in hass.data[KNOWN_CHROMECAST_INFO_KEY] if info.uuid == x.uuid + ) hass.data[KNOWN_CHROMECAST_INFO_KEY] -= same_uuid hass.data[KNOWN_CHROMECAST_INFO_KEY].add(info) @@ -216,29 +254,36 @@ def _setup_internal_discovery(hass: HomeAssistantType) -> None: def internal_add_callback(name): """Handle zeroconf discovery of a new chromecast.""" mdns = listener.services[name] - _discover_chromecast(hass, ChromecastInfo( - service=name, - host=mdns[0], - port=mdns[1], - uuid=mdns[2], - model_name=mdns[3], - friendly_name=mdns[4], - )) + _discover_chromecast( + hass, + ChromecastInfo( + service=name, + host=mdns[0], + port=mdns[1], + uuid=mdns[2], + model_name=mdns[3], + friendly_name=mdns[4], + ), + ) def internal_remove_callback(name, mdns): """Handle zeroconf discovery of a removed chromecast.""" - _remove_chromecast(hass, ChromecastInfo( - service=name, - host=mdns[0], - port=mdns[1], - uuid=mdns[2], - model_name=mdns[3], - friendly_name=mdns[4], - )) + _remove_chromecast( + hass, + ChromecastInfo( + service=name, + host=mdns[0], + port=mdns[1], + uuid=mdns[2], + model_name=mdns[3], + friendly_name=mdns[4], + ), + ) _LOGGER.debug("Starting internal pychromecast discovery.") - listener, browser = pychromecast.start_discovery(internal_add_callback, - internal_remove_callback) + listener, browser = pychromecast.start_discovery( + internal_add_callback, internal_remove_callback + ) ChromeCastZeroconf.set_zeroconf(browser.zc) def stop_discovery(event): @@ -251,8 +296,7 @@ def _setup_internal_discovery(hass: HomeAssistantType) -> None: @callback -def _async_create_cast_device(hass: HomeAssistantType, - info: ChromecastInfo): +def _async_create_cast_device(hass: HomeAssistantType, info: ChromecastInfo): """Create a CastDevice Entity from the chromecast object. Returns None if the cast device has already been added. @@ -278,29 +322,30 @@ def _async_create_cast_device(hass: HomeAssistantType, return CastDevice(info) -async def async_setup_platform(hass: HomeAssistantType, config: ConfigType, - async_add_entities, discovery_info=None): +async def async_setup_platform( + hass: HomeAssistantType, config: ConfigType, async_add_entities, discovery_info=None +): """Set up thet Cast platform. Deprecated. """ _LOGGER.warning( - 'Setting configuration for Cast via platform is deprecated. ' - 'Configure via Cast integration instead.') - await _async_setup_platform( - hass, config, async_add_entities, discovery_info) + "Setting configuration for Cast via platform is deprecated. " + "Configure via Cast integration instead." + ) + await _async_setup_platform(hass, config, async_add_entities, discovery_info) async def async_setup_entry(hass, config_entry, async_add_entities): """Set up Cast from a config entry.""" - config = hass.data[CAST_DOMAIN].get('media_player', {}) + config = hass.data[CAST_DOMAIN].get("media_player", {}) if not isinstance(config, list): config = [config] # no pending task - done, _ = await asyncio.wait([ - _async_setup_platform(hass, cfg, async_add_entities, None) - for cfg in config]) + done, _ = await asyncio.wait( + [_async_setup_platform(hass, cfg, async_add_entities, None) for cfg in config] + ) if any([task.exception() for task in done]): exceptions = [task.exception() for task in done] for exception in exceptions: @@ -308,8 +353,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): raise PlatformNotReady -async def _async_setup_platform(hass: HomeAssistantType, config: ConfigType, - async_add_entities, discovery_info): +async def _async_setup_platform( + hass: HomeAssistantType, config: ConfigType, async_add_entities, discovery_info +): """Set up the cast platform.""" import pychromecast @@ -320,11 +366,9 @@ async def _async_setup_platform(hass: HomeAssistantType, config: ConfigType, info = None if discovery_info is not None: - info = ChromecastInfo(host=discovery_info['host'], - port=discovery_info['port']) + info = ChromecastInfo(host=discovery_info["host"], port=discovery_info["port"]) elif CONF_HOST in config: - info = ChromecastInfo(host=config[CONF_HOST], - port=DEFAULT_PORT) + info = ChromecastInfo(host=config[CONF_HOST], port=DEFAULT_PORT) @callback def async_cast_discovered(discover: ChromecastInfo) -> None: @@ -337,8 +381,7 @@ async def _async_setup_platform(hass: HomeAssistantType, config: ConfigType, if cast_device is not None: async_add_entities([cast_device]) - async_dispatcher_connect( - hass, SIGNAL_CAST_DISCOVERED, async_cast_discovered) + async_dispatcher_connect(hass, SIGNAL_CAST_DISCOVERED, async_cast_discovered) # Re-play the callback for all past chromecasts, store the objects in # a list to avoid concurrent modification resulting in exception. for chromecast in list(hass.data[KNOWN_CHROMECAST_INFO_KEY]): @@ -349,11 +392,13 @@ async def _async_setup_platform(hass: HomeAssistantType, config: ConfigType, # b) have an audio group cast device, we need internal discovery. hass.async_add_job(_setup_internal_discovery, hass) else: - info = await hass.async_add_job(_fill_out_missing_chromecast_info, - info) + info = await hass.async_add_job(_fill_out_missing_chromecast_info, info) if info.friendly_name is None: - _LOGGER.debug("Cannot retrieve detail information for chromecast" - " %s, the device may not be online", info) + _LOGGER.debug( + "Cannot retrieve detail information for chromecast" + " %s, the device may not be online", + info, + ) hass.async_add_job(_discover_chromecast, hass, info) @@ -374,8 +419,7 @@ class CastStatusListener: self._mz_mgr = mz_mgr chromecast.register_status_listener(self) - chromecast.socket_client.media_controller.register_status_listener( - self) + chromecast.socket_client.media_controller.register_status_listener(self) chromecast.register_connection_listener(self) # pylint: disable=protected-access if cast_device._cast_info.is_audio_group: @@ -415,8 +459,7 @@ class CastStatusListener: def multizone_new_media_status(self, group_uuid, media_status): """Handle reception of a new MediaStatus for a group.""" if self._valid: - self._cast_device.multizone_new_media_status( - group_uuid, media_status) + self._cast_device.multizone_new_media_status(group_uuid, media_status) def invalidate(self): """Invalidate this status listener. @@ -447,8 +490,7 @@ class DynamicGroupCastStatusListener: self._mz_mgr = mz_mgr chromecast.register_status_listener(self) - chromecast.socket_client.media_controller.register_status_listener( - self) + chromecast.socket_client.media_controller.register_status_listener(self) chromecast.register_connection_listener(self) self._mz_mgr.add_multizone(chromecast) @@ -464,8 +506,7 @@ class DynamicGroupCastStatusListener: def new_connection_status(self, connection_status): """Handle reception of a new ConnectionStatus.""" if self._valid: - self._cast_device.new_dynamic_group_connection_status( - connection_status) + self._cast_device.new_dynamic_group_connection_status(connection_status) def invalidate(self): """Invalidate this status listener. @@ -487,6 +528,7 @@ class CastDevice(MediaPlayerDevice): def __init__(self, cast_info): """Initialize the cast device.""" import pychromecast # noqa: pylint: disable=unused-import + self._cast_info = cast_info # type: ChromecastInfo self.services = None if cast_info.service: @@ -497,8 +539,8 @@ class CastDevice(MediaPlayerDevice): self.media_status = None self.media_status_received = None self._dynamic_group_cast_info = None # type: ChromecastInfo - self._dynamic_group_cast = None \ - # type: Optional[pychromecast.Chromecast] + self._dynamic_group_cast = None + # type: Optional[pychromecast.Chromecast] self.dynamic_group_media_status = None self.dynamic_group_media_status_received = None self.mz_media_status = {} @@ -507,13 +549,14 @@ class CastDevice(MediaPlayerDevice): self._available = False # type: bool self._dynamic_group_available = False # type: bool self._status_listener = None # type: Optional[CastStatusListener] - self._dynamic_group_status_listener = None \ - # type: Optional[DynamicGroupCastStatusListener] + self._dynamic_group_status_listener = None + # type: Optional[DynamicGroupCastStatusListener] self._add_remove_handler = None self._del_remove_handler = None async def async_added_to_hass(self): """Create chromecast object when added to hass.""" + @callback def async_cast_discovered(discover: ChromecastInfo): """Handle discovery of new Chromecast.""" @@ -521,10 +564,10 @@ class CastDevice(MediaPlayerDevice): # We can't handle empty UUIDs return if _is_matching_dynamic_group(self._cast_info, discover): - _LOGGER.debug("Discovered matching dynamic group: %s", - discover) - self.hass.async_create_task(async_create_catching_coro( - self.async_set_dynamic_group(discover))) + _LOGGER.debug("Discovered matching dynamic group: %s", discover) + self.hass.async_create_task( + async_create_catching_coro(self.async_set_dynamic_group(discover)) + ) return if self._cast_info.uuid != discover.uuid: @@ -533,51 +576,66 @@ class CastDevice(MediaPlayerDevice): if self.services is None: _LOGGER.warning( "[%s %s (%s:%s)] Received update for manually added Cast", - self.entity_id, self._cast_info.friendly_name, - self._cast_info.host, self._cast_info.port) + self.entity_id, + self._cast_info.friendly_name, + self._cast_info.host, + self._cast_info.port, + ) return _LOGGER.debug("Discovered chromecast with same UUID: %s", discover) - self.hass.async_create_task(async_create_catching_coro( - self.async_set_cast_info(discover))) + self.hass.async_create_task( + async_create_catching_coro(self.async_set_cast_info(discover)) + ) def async_cast_removed(discover: ChromecastInfo): """Handle removal of Chromecast.""" if self._cast_info.uuid is None: # We can't handle empty UUIDs return - if (self._dynamic_group_cast_info is not None and - self._dynamic_group_cast_info.uuid == discover.uuid): + if ( + self._dynamic_group_cast_info is not None + and self._dynamic_group_cast_info.uuid == discover.uuid + ): _LOGGER.debug("Removed matching dynamic group: %s", discover) - self.hass.async_create_task(async_create_catching_coro( - self.async_del_dynamic_group())) + self.hass.async_create_task( + async_create_catching_coro(self.async_del_dynamic_group()) + ) return if self._cast_info.uuid != discover.uuid: # Removed is not our device. return _LOGGER.debug("Removed chromecast with same UUID: %s", discover) - self.hass.async_create_task(async_create_catching_coro( - self.async_del_cast_info(discover))) + self.hass.async_create_task( + async_create_catching_coro(self.async_del_cast_info(discover)) + ) async def async_stop(event): """Disconnect socket on Home Assistant stop.""" await self._async_disconnect() self._add_remove_handler = async_dispatcher_connect( - self.hass, SIGNAL_CAST_DISCOVERED, - async_cast_discovered) + self.hass, SIGNAL_CAST_DISCOVERED, async_cast_discovered + ) self._del_remove_handler = async_dispatcher_connect( - self.hass, SIGNAL_CAST_REMOVED, - async_cast_removed) + self.hass, SIGNAL_CAST_REMOVED, async_cast_removed + ) self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_stop) - self.hass.async_create_task(async_create_catching_coro( - self.async_set_cast_info(self._cast_info))) + self.hass.async_create_task( + async_create_catching_coro(self.async_set_cast_info(self._cast_info)) + ) for info in self.hass.data[KNOWN_CHROMECAST_INFO_KEY]: if _is_matching_dynamic_group(self._cast_info, info): - _LOGGER.debug("[%s %s (%s:%s)] Found dynamic group: %s", - self.entity_id, self._cast_info.friendly_name, - self._cast_info.host, self._cast_info.port, info) - self.hass.async_create_task(async_create_catching_coro( - self.async_set_dynamic_group(info))) + _LOGGER.debug( + "[%s %s (%s:%s)] Found dynamic group: %s", + self.entity_id, + self._cast_info.friendly_name, + self._cast_info.host, + self._cast_info.port, + info, + ) + self.hass.async_create_task( + async_create_catching_coro(self.async_set_dynamic_group(info)) + ) break async def async_will_remove_from_hass(self) -> None: @@ -595,14 +653,20 @@ class CastDevice(MediaPlayerDevice): async def async_set_cast_info(self, cast_info): """Set the cast information and set up the chromecast object.""" import pychromecast + self._cast_info = cast_info if self.services is not None: if cast_info.service not in self.services: - _LOGGER.debug("[%s %s (%s:%s)] Got new service: %s (%s)", - self.entity_id, self._cast_info.friendly_name, - self._cast_info.host, self._cast_info.port, - cast_info.service, self.services) + _LOGGER.debug( + "[%s %s (%s:%s)] Got new service: %s (%s)", + self.entity_id, + self._cast_info.friendly_name, + self._cast_info.host, + self._cast_info.port, + cast_info.service, + self.services, + ) self.services.add(cast_info.service) @@ -615,33 +679,50 @@ class CastDevice(MediaPlayerDevice): if self.services is None: _LOGGER.debug( "[%s %s (%s:%s)] Connecting to cast device by host %s", - self.entity_id, self._cast_info.friendly_name, - self._cast_info.host, self._cast_info.port, cast_info) + self.entity_id, + self._cast_info.friendly_name, + self._cast_info.host, + self._cast_info.port, + cast_info, + ) chromecast = await self.hass.async_add_job( - pychromecast._get_chromecast_from_host, ( - cast_info.host, cast_info.port, cast_info.uuid, - cast_info.model_name, cast_info.friendly_name - )) + pychromecast._get_chromecast_from_host, + ( + cast_info.host, + cast_info.port, + cast_info.uuid, + cast_info.model_name, + cast_info.friendly_name, + ), + ) else: _LOGGER.debug( "[%s %s (%s:%s)] Connecting to cast device by service %s", - self.entity_id, self._cast_info.friendly_name, - self._cast_info.host, self._cast_info.port, self.services) + self.entity_id, + self._cast_info.friendly_name, + self._cast_info.host, + self._cast_info.port, + self.services, + ) chromecast = await self.hass.async_add_job( - pychromecast._get_chromecast_from_service, ( - self.services, ChromeCastZeroconf.get_zeroconf(), - cast_info.uuid, cast_info.model_name, - cast_info.friendly_name - )) + pychromecast._get_chromecast_from_service, + ( + self.services, + ChromeCastZeroconf.get_zeroconf(), + cast_info.uuid, + cast_info.model_name, + cast_info.friendly_name, + ), + ) self._chromecast = chromecast if CAST_MULTIZONE_MANAGER_KEY not in self.hass.data: from pychromecast.controllers.multizone import MultizoneManager + self.hass.data[CAST_MULTIZONE_MANAGER_KEY] = MultizoneManager() self.mz_mgr = self.hass.data[CAST_MULTIZONE_MANAGER_KEY] - self._status_listener = CastStatusListener( - self, chromecast, self.mz_mgr) + self._status_listener = CastStatusListener(self, chromecast, self.mz_mgr) self._available = False self.cast_status = chromecast.status self.media_status = chromecast.media_controller.status @@ -651,38 +732,55 @@ class CastDevice(MediaPlayerDevice): async def async_del_cast_info(self, cast_info): """Remove the service.""" self.services.discard(cast_info.service) - _LOGGER.debug("[%s %s (%s:%s)] Remove service: %s (%s)", - self.entity_id, self._cast_info.friendly_name, - self._cast_info.host, self._cast_info.port, - cast_info.service, self.services) + _LOGGER.debug( + "[%s %s (%s:%s)] Remove service: %s (%s)", + self.entity_id, + self._cast_info.friendly_name, + self._cast_info.host, + self._cast_info.port, + cast_info.service, + self.services, + ) async def async_set_dynamic_group(self, cast_info): """Set the cast information and set up the chromecast object.""" import pychromecast + _LOGGER.debug( "[%s %s (%s:%s)] Connecting to dynamic group by host %s", - self.entity_id, self._cast_info.friendly_name, - self._cast_info.host, self._cast_info.port, cast_info) + self.entity_id, + self._cast_info.friendly_name, + self._cast_info.host, + self._cast_info.port, + cast_info, + ) await self.async_del_dynamic_group() self._dynamic_group_cast_info = cast_info # pylint: disable=protected-access chromecast = await self.hass.async_add_executor_job( - pychromecast._get_chromecast_from_host, ( - cast_info.host, cast_info.port, cast_info.uuid, - cast_info.model_name, cast_info.friendly_name - )) + pychromecast._get_chromecast_from_host, + ( + cast_info.host, + cast_info.port, + cast_info.uuid, + cast_info.model_name, + cast_info.friendly_name, + ), + ) self._dynamic_group_cast = chromecast if CAST_MULTIZONE_MANAGER_KEY not in self.hass.data: from pychromecast.controllers.multizone import MultizoneManager + self.hass.data[CAST_MULTIZONE_MANAGER_KEY] = MultizoneManager() mz_mgr = self.hass.data[CAST_MULTIZONE_MANAGER_KEY] self._dynamic_group_status_listener = DynamicGroupCastStatusListener( - self, chromecast, mz_mgr) + self, chromecast, mz_mgr + ) self._dynamic_group_available = False self.dynamic_group_media_status = chromecast.media_controller.status self._dynamic_group_cast.start() @@ -691,16 +789,19 @@ class CastDevice(MediaPlayerDevice): async def async_del_dynamic_group(self): """Remove the dynamic group.""" cast_info = self._dynamic_group_cast_info - _LOGGER.debug("[%s %s (%s:%s)] Remove dynamic group: %s", - self.entity_id, self._cast_info.friendly_name, - self._cast_info.host, self._cast_info.port, - cast_info.service if cast_info else None) + _LOGGER.debug( + "[%s %s (%s:%s)] Remove dynamic group: %s", + self.entity_id, + self._cast_info.friendly_name, + self._cast_info.host, + self._cast_info.port, + cast_info.service if cast_info else None, + ) self._dynamic_group_available = False self._dynamic_group_cast_info = None if self._dynamic_group_cast is not None: - await self.hass.async_add_executor_job( - self._dynamic_group_cast.disconnect) + await self.hass.async_add_executor_job(self._dynamic_group_cast.disconnect) self._dynamic_group_invalidate() @@ -711,16 +812,19 @@ class CastDevice(MediaPlayerDevice): if self._chromecast is None: # Can't disconnect if not connected. return - _LOGGER.debug("[%s %s (%s:%s)] Disconnecting from chromecast socket.", - self.entity_id, self._cast_info.friendly_name, - self._cast_info.host, self._cast_info.port) + _LOGGER.debug( + "[%s %s (%s:%s)] Disconnecting from chromecast socket.", + self.entity_id, + self._cast_info.friendly_name, + self._cast_info.host, + self._cast_info.port, + ) self._available = False self.async_schedule_update_ha_state() await self.hass.async_add_executor_job(self._chromecast.disconnect) if self._dynamic_group_cast is not None: - await self.hass.async_add_executor_job( - self._dynamic_group_cast.disconnect) + await self.hass.async_add_executor_job(self._dynamic_group_cast.disconnect) self._invalidate() @@ -762,14 +866,19 @@ class CastDevice(MediaPlayerDevice): def new_connection_status(self, connection_status): """Handle updates of connection status.""" - from pychromecast.socket_client import CONNECTION_STATUS_CONNECTED, \ - CONNECTION_STATUS_DISCONNECTED + from pychromecast.socket_client import ( + CONNECTION_STATUS_CONNECTED, + CONNECTION_STATUS_DISCONNECTED, + ) _LOGGER.debug( "[%s %s (%s:%s)] Received cast device connection status: %s", - self.entity_id, self._cast_info.friendly_name, - self._cast_info.host, self._cast_info.port, - connection_status.status) + self.entity_id, + self._cast_info.friendly_name, + self._cast_info.host, + self._cast_info.port, + connection_status.status, + ) if connection_status.status == CONNECTION_STATUS_DISCONNECTED: self._available = False self._invalidate() @@ -783,9 +892,12 @@ class CastDevice(MediaPlayerDevice): # on state machine. _LOGGER.debug( "[%s %s (%s:%s)] Cast device availability changed: %s", - self.entity_id, self._cast_info.friendly_name, - self._cast_info.host, self._cast_info.port, - connection_status.status) + self.entity_id, + self._cast_info.friendly_name, + self._cast_info.host, + self._cast_info.port, + connection_status.status, + ) info = self._cast_info if info.friendly_name is None and not info.is_audio_group: # We couldn't find friendly_name when the cast was added, retry @@ -801,14 +913,19 @@ class CastDevice(MediaPlayerDevice): def new_dynamic_group_connection_status(self, connection_status): """Handle updates of connection status.""" - from pychromecast.socket_client import CONNECTION_STATUS_CONNECTED, \ - CONNECTION_STATUS_DISCONNECTED + from pychromecast.socket_client import ( + CONNECTION_STATUS_CONNECTED, + CONNECTION_STATUS_DISCONNECTED, + ) _LOGGER.debug( "[%s %s (%s:%s)] Received dynamic group connection status: %s", - self.entity_id, self._cast_info.friendly_name, - self._cast_info.host, self._cast_info.port, - connection_status.status) + self.entity_id, + self._cast_info.friendly_name, + self._cast_info.host, + self._cast_info.port, + connection_status.status, + ) if connection_status.status == CONNECTION_STATUS_DISCONNECTED: self._dynamic_group_available = False self._dynamic_group_invalidate() @@ -822,9 +939,12 @@ class CastDevice(MediaPlayerDevice): # on state machine. _LOGGER.debug( "[%s %s (%s:%s)] Dynamic group availability changed: %s", - self.entity_id, self._cast_info.friendly_name, - self._cast_info.host, self._cast_info.port, - connection_status.status) + self.entity_id, + self._cast_info.friendly_name, + self._cast_info.host, + self._cast_info.port, + connection_status.status, + ) self._dynamic_group_available = new_available self.schedule_update_ha_state() @@ -832,9 +952,13 @@ class CastDevice(MediaPlayerDevice): """Handle updates of audio group media status.""" _LOGGER.debug( "[%s %s (%s:%s)] Multizone %s media status: %s", - self.entity_id, self._cast_info.friendly_name, - self._cast_info.host, self._cast_info.port, - group_uuid, media_status) + self.entity_id, + self._cast_info.friendly_name, + self._cast_info.host, + self._cast_info.port, + group_uuid, + media_status, + ) self.mz_media_status[group_uuid] = media_status self.mz_media_status_received[group_uuid] = dt_util.utcnow() self.schedule_update_ha_state() @@ -850,18 +974,17 @@ class CastDevice(MediaPlayerDevice): media_status = self.media_status media_controller = self._chromecast.media_controller - if ((media_status is None or media_status.player_state == "UNKNOWN") - and self._dynamic_group_cast is not None): + if ( + media_status is None or media_status.player_state == "UNKNOWN" + ) and self._dynamic_group_cast is not None: media_status = self.dynamic_group_media_status - media_controller = \ - self._dynamic_group_cast.media_controller + media_controller = self._dynamic_group_cast.media_controller if media_status is None or media_status.player_state == "UNKNOWN": groups = self.mz_media_status for k, val in groups.items(): if val and val.player_state != "UNKNOWN": - media_controller = \ - self.mz_mgr.get_multizone_mediacontroller(k) + media_controller = self.mz_mgr.get_multizone_mediacontroller(k) break return media_controller @@ -879,8 +1002,7 @@ class CastDevice(MediaPlayerDevice): self._chromecast.quit_app() # The only way we can turn the Chromecast is on is by launching an app - self._chromecast.play_media(CAST_SPLASH, - pychromecast.STREAM_TYPE_BUFFERED) + self._chromecast.play_media(CAST_SPLASH, pychromecast.STREAM_TYPE_BUFFERED) def turn_off(self): """Turn off the cast device.""" @@ -949,12 +1071,10 @@ class CastDevice(MediaPlayerDevice): return None return { - 'name': cast_info.friendly_name, - 'identifiers': { - (CAST_DOMAIN, cast_info.uuid.replace('-', '')) - }, - 'model': cast_info.model_name, - 'manufacturer': cast_info.manufacturer, + "name": cast_info.friendly_name, + "identifiers": {(CAST_DOMAIN, cast_info.uuid.replace("-", ""))}, + "model": cast_info.model_name, + "manufacturer": cast_info.manufacturer, } def _media_status(self): @@ -967,8 +1087,9 @@ class CastDevice(MediaPlayerDevice): media_status = self.media_status media_status_received = self.media_status_received - if ((media_status is None or media_status.player_state == "UNKNOWN") - and self._dynamic_group_cast is not None): + if ( + media_status is None or media_status.player_state == "UNKNOWN" + ) and self._dynamic_group_cast is not None: media_status = self.dynamic_group_media_status media_status_received = self.dynamic_group_media_status_received @@ -1134,10 +1255,11 @@ class CastDevice(MediaPlayerDevice): def media_position(self): """Position of current playing media in seconds.""" media_status, _ = self._media_status() - if media_status is None or \ - not (media_status.player_is_playing or - media_status.player_is_paused or - media_status.player_is_idle): + if media_status is None or not ( + media_status.player_is_playing + or media_status.player_is_paused + or media_status.player_is_idle + ): return None return media_status.current_time diff --git a/homeassistant/components/cert_expiry/sensor.py b/homeassistant/components/cert_expiry/sensor.py index f1fff08755f..b1e0d819358 100644 --- a/homeassistant/components/cert_expiry/sensor.py +++ b/homeassistant/components/cert_expiry/sensor.py @@ -8,28 +8,35 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import (CONF_NAME, CONF_HOST, CONF_PORT, - EVENT_HOMEASSISTANT_START) +from homeassistant.const import ( + CONF_NAME, + CONF_HOST, + CONF_PORT, + EVENT_HOMEASSISTANT_START, +) from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'SSL Certificate Expiry' +DEFAULT_NAME = "SSL Certificate Expiry" DEFAULT_PORT = 443 SCAN_INTERVAL = timedelta(hours=12) TIMEOUT = 10.0 -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up certificate expiry sensor.""" + def run_setup(event): """Wait until Home Assistant is fully initialized before creating. @@ -39,8 +46,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): server_port = config.get(CONF_PORT) sensor_name = config.get(CONF_NAME) - add_entities([SSLCertificate(sensor_name, server_name, server_port)], - True) + add_entities([SSLCertificate(sensor_name, server_name, server_port)], True) # To allow checking of the HA certificate we must first be running. hass.bus.listen_once(EVENT_HOMEASSISTANT_START, run_setup) @@ -65,7 +71,7 @@ class SSLCertificate(Entity): @property def unit_of_measurement(self): """Return the unit this state is expressed in.""" - return 'days' + return "days" @property def state(self): @@ -75,7 +81,7 @@ class SSLCertificate(Entity): @property def icon(self): """Icon to use in the frontend, if any.""" - return 'mdi:certificate' + return "mdi:certificate" @property def available(self): @@ -87,10 +93,8 @@ class SSLCertificate(Entity): ctx = ssl.create_default_context() try: address = (self.server_name, self.server_port) - with socket.create_connection( - address, timeout=TIMEOUT) as sock: - with ctx.wrap_socket( - sock, server_hostname=address[0]) as ssock: + with socket.create_connection(address, timeout=TIMEOUT) as sock: + with ctx.wrap_socket(sock, server_hostname=address[0]) as ssock: cert = ssock.getpeercert() except socket.gaierror: @@ -98,17 +102,17 @@ class SSLCertificate(Entity): self._available = False return except socket.timeout: - _LOGGER.error( - "Connection timeout with server: %s", self.server_name) + _LOGGER.error("Connection timeout with server: %s", self.server_name) self._available = False return except OSError: - _LOGGER.error("Cannot fetch certificate from %s", - self.server_name, exc_info=1) + _LOGGER.error( + "Cannot fetch certificate from %s", self.server_name, exc_info=1 + ) self._available = False return - ts_seconds = ssl.cert_time_to_seconds(cert['notAfter']) + ts_seconds = ssl.cert_time_to_seconds(cert["notAfter"]) timestamp = datetime.fromtimestamp(ts_seconds) expiry = timestamp - datetime.today() self._available = True diff --git a/homeassistant/components/channels/media_player.py b/homeassistant/components/channels/media_player.py index abd3281d11a..6c3e18cdb05 100644 --- a/homeassistant/components/channels/media_player.py +++ b/homeassistant/components/channels/media_player.py @@ -3,54 +3,77 @@ import logging import voluptuous as vol -from homeassistant.components.media_player import ( - MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA from homeassistant.components.media_player.const import ( - DOMAIN, MEDIA_TYPE_CHANNEL, MEDIA_TYPE_EPISODE, MEDIA_TYPE_MOVIE, - MEDIA_TYPE_TVSHOW, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, - SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, - SUPPORT_SELECT_SOURCE, SUPPORT_STOP, SUPPORT_VOLUME_MUTE) + DOMAIN, + MEDIA_TYPE_CHANNEL, + MEDIA_TYPE_EPISODE, + MEDIA_TYPE_MOVIE, + MEDIA_TYPE_TVSHOW, + SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, + SUPPORT_PLAY, + SUPPORT_PLAY_MEDIA, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_SELECT_SOURCE, + SUPPORT_STOP, + SUPPORT_VOLUME_MUTE, +) from homeassistant.const import ( - ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_PORT, STATE_IDLE, STATE_PAUSED, - STATE_PLAYING) + ATTR_ENTITY_ID, + CONF_HOST, + CONF_NAME, + CONF_PORT, + STATE_IDLE, + STATE_PAUSED, + STATE_PLAYING, +) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DATA_CHANNELS = 'channels' -DEFAULT_NAME = 'Channels' +DATA_CHANNELS = "channels" +DEFAULT_NAME = "Channels" DEFAULT_PORT = 57000 -FEATURE_SUPPORT = SUPPORT_PLAY | SUPPORT_PAUSE | SUPPORT_STOP | \ - SUPPORT_VOLUME_MUTE | SUPPORT_NEXT_TRACK | SUPPORT_PREVIOUS_TRACK | \ - SUPPORT_PLAY_MEDIA | SUPPORT_SELECT_SOURCE +FEATURE_SUPPORT = ( + SUPPORT_PLAY + | SUPPORT_PAUSE + | SUPPORT_STOP + | SUPPORT_VOLUME_MUTE + | SUPPORT_NEXT_TRACK + | SUPPORT_PREVIOUS_TRACK + | SUPPORT_PLAY_MEDIA + | SUPPORT_SELECT_SOURCE +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + } +) -SERVICE_SEEK_FORWARD = 'channels_seek_forward' -SERVICE_SEEK_BACKWARD = 'channels_seek_backward' -SERVICE_SEEK_BY = 'channels_seek_by' +SERVICE_SEEK_FORWARD = "channels_seek_forward" +SERVICE_SEEK_BACKWARD = "channels_seek_backward" +SERVICE_SEEK_BY = "channels_seek_by" # Service call validation schemas -ATTR_SECONDS = 'seconds' +ATTR_SECONDS = "seconds" -CHANNELS_SCHEMA = vol.Schema({ - vol.Required(ATTR_ENTITY_ID): cv.entity_id, -}) +CHANNELS_SCHEMA = vol.Schema({vol.Required(ATTR_ENTITY_ID): cv.entity_id}) -CHANNELS_SEEK_BY_SCHEMA = CHANNELS_SCHEMA.extend({ - vol.Required(ATTR_SECONDS): vol.Coerce(int), -}) +CHANNELS_SEEK_BY_SCHEMA = CHANNELS_SCHEMA.extend( + {vol.Required(ATTR_SECONDS): vol.Coerce(int)} +) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Channels platform.""" device = ChannelsPlayer( - config.get(CONF_NAME), config.get(CONF_HOST), config.get(CONF_PORT)) + config.get(CONF_NAME), config.get(CONF_HOST), config.get(CONF_PORT) + ) if DATA_CHANNELS not in hass.data: hass.data[DATA_CHANNELS] = [] @@ -62,12 +85,17 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Handle service.""" entity_id = service.data.get(ATTR_ENTITY_ID) - device = next((device for device in hass.data[DATA_CHANNELS] if - device.entity_id == entity_id), None) + device = next( + ( + device + for device in hass.data[DATA_CHANNELS] + if device.entity_id == entity_id + ), + None, + ) if device is None: - _LOGGER.warning( - "Unable to find Channels with entity_id: %s", entity_id) + _LOGGER.warning("Unable to find Channels with entity_id: %s", entity_id) return if service.service == SERVICE_SEEK_FORWARD: @@ -75,18 +103,20 @@ def setup_platform(hass, config, add_entities, discovery_info=None): elif service.service == SERVICE_SEEK_BACKWARD: device.seek_backward() elif service.service == SERVICE_SEEK_BY: - seconds = service.data.get('seconds') + seconds = service.data.get("seconds") device.seek_by(seconds) hass.services.register( - DOMAIN, SERVICE_SEEK_FORWARD, service_handler, schema=CHANNELS_SCHEMA) + DOMAIN, SERVICE_SEEK_FORWARD, service_handler, schema=CHANNELS_SCHEMA + ) hass.services.register( - DOMAIN, SERVICE_SEEK_BACKWARD, service_handler, schema=CHANNELS_SCHEMA) + DOMAIN, SERVICE_SEEK_BACKWARD, service_handler, schema=CHANNELS_SCHEMA + ) hass.services.register( - DOMAIN, SERVICE_SEEK_BY, service_handler, - schema=CHANNELS_SEEK_BY_SCHEMA) + DOMAIN, SERVICE_SEEK_BY, service_handler, schema=CHANNELS_SEEK_BY_SCHEMA + ) class ChannelsPlayer(MediaPlayerDevice): @@ -124,28 +154,28 @@ class ChannelsPlayer(MediaPlayerDevice): def update_state(self, state_hash): """Update all the state properties with the passed in dictionary.""" - self.status = state_hash.get('status', "stopped") - self.muted = state_hash.get('muted', False) + self.status = state_hash.get("status", "stopped") + self.muted = state_hash.get("muted", False) - channel_hash = state_hash.get('channel') - np_hash = state_hash.get('now_playing') + channel_hash = state_hash.get("channel") + np_hash = state_hash.get("now_playing") if channel_hash: - self.channel_number = channel_hash.get('channel_number') - self.channel_name = channel_hash.get('channel_name') - self.channel_image_url = channel_hash.get('channel_image_url') + self.channel_number = channel_hash.get("channel_number") + self.channel_name = channel_hash.get("channel_name") + self.channel_image_url = channel_hash.get("channel_image_url") else: self.channel_number = None self.channel_name = None self.channel_image_url = None if np_hash: - self.now_playing_title = np_hash.get('title') - self.now_playing_episode_title = np_hash.get('episode_title') - self.now_playing_season_number = np_hash.get('season_number') - self.now_playing_episode_number = np_hash.get('episode_number') - self.now_playing_summary = np_hash.get('summary') - self.now_playing_image_url = np_hash.get('image_url') + self.now_playing_title = np_hash.get("title") + self.now_playing_episode_title = np_hash.get("episode_title") + self.now_playing_season_number = np_hash.get("season_number") + self.now_playing_episode_number = np_hash.get("episode_number") + self.now_playing_summary = np_hash.get("summary") + self.now_playing_image_url = np_hash.get("image_url") else: self.now_playing_title = None self.now_playing_episode_title = None @@ -162,13 +192,13 @@ class ChannelsPlayer(MediaPlayerDevice): @property def state(self): """Return the state of the player.""" - if self.status == 'stopped': + if self.status == "stopped": return STATE_IDLE - if self.status == 'paused': + if self.status == "paused": return STATE_PAUSED - if self.status == 'playing': + if self.status == "playing": return STATE_PLAYING return None @@ -181,7 +211,7 @@ class ChannelsPlayer(MediaPlayerDevice): @property def source_list(self): """List of favorite channels.""" - sources = [channel['name'] for channel in self.favorite_channels] + sources = [channel["name"] for channel in self.favorite_channels] return sources @property @@ -207,7 +237,7 @@ class ChannelsPlayer(MediaPlayerDevice): if self.channel_image_url: return self.channel_image_url - return 'https://getchannels.com/assets/img/icon-1024.png' + return "https://getchannels.com/assets/img/icon-1024.png" @property def media_title(self): @@ -267,8 +297,7 @@ class ChannelsPlayer(MediaPlayerDevice): if media_type == MEDIA_TYPE_CHANNEL: response = self.client.play_channel(media_id) self.update_state(response) - elif media_type in [MEDIA_TYPE_MOVIE, MEDIA_TYPE_EPISODE, - MEDIA_TYPE_TVSHOW]: + elif media_type in [MEDIA_TYPE_MOVIE, MEDIA_TYPE_EPISODE, MEDIA_TYPE_TVSHOW]: response = self.client.play_recording(media_id) self.update_state(response) diff --git a/homeassistant/components/cisco_ios/device_tracker.py b/homeassistant/components/cisco_ios/device_tracker.py index 5eb03970989..b442b24feb4 100644 --- a/homeassistant/components/cisco_ios/device_tracker.py +++ b/homeassistant/components/cisco_ios/device_tracker.py @@ -5,19 +5,23 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.device_tracker import ( - DOMAIN, PLATFORM_SCHEMA, DeviceScanner) -from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, \ - CONF_PORT + DOMAIN, + PLATFORM_SCHEMA, + DeviceScanner, +) +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT _LOGGER = logging.getLogger(__name__) PLATFORM_SCHEMA = vol.All( - PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Optional(CONF_PASSWORD, default=''): cv.string, - vol.Optional(CONF_PORT): cv.port, - }) + PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Optional(CONF_PASSWORD, default=""): cv.string, + vol.Optional(CONF_PORT): cv.port, + } + ) ) @@ -41,7 +45,7 @@ class CiscoDeviceScanner(DeviceScanner): self.last_results = {} self.success_init = self._update_info() - _LOGGER.info('cisco_ios scanner initialized') + _LOGGER.info("cisco_ios scanner initialized") def get_device_name(self, device): """Get the firmware doesn't save the name of the wireless device.""" @@ -101,15 +105,20 @@ class CiscoDeviceScanner(DeviceScanner): try: cisco_ssh = pxssh.pxssh() - cisco_ssh.login(self.host, self.username, self.password, - port=self.port, auto_prompt_reset=False) + cisco_ssh.login( + self.host, + self.username, + self.password, + port=self.port, + auto_prompt_reset=False, + ) # Find the hostname - initial_line = cisco_ssh.before.decode('utf-8').splitlines() + initial_line = cisco_ssh.before.decode("utf-8").splitlines() router_hostname = initial_line[len(initial_line) - 1] router_hostname += "#" # Set the discovered hostname as prompt - regex_expression = ('(?i)^%s' % router_hostname).encode() + regex_expression = ("(?i)^%s" % router_hostname).encode() cisco_ssh.PROMPT = re.compile(regex_expression, re.MULTILINE) # Allow full arp table to print at once cisco_ssh.sendline("terminal length 0") @@ -120,7 +129,7 @@ class CiscoDeviceScanner(DeviceScanner): devices_result = cisco_ssh.before - return devices_result.decode('utf-8') + return devices_result.decode("utf-8") except pxssh.ExceptionPxssh as px_e: _LOGGER.error("pxssh failed on login") _LOGGER.error(px_e) @@ -141,8 +150,9 @@ def _parse_cisco_mac_address(cisco_hardware_addr): Takes in cisco_hwaddr: HWAddr String from Cisco ARP table Returns a regular standard MAC address """ - cisco_hardware_addr = cisco_hardware_addr.replace('.', '') - blocks = [cisco_hardware_addr[x:x + 2] - for x in range(0, len(cisco_hardware_addr), 2)] + cisco_hardware_addr = cisco_hardware_addr.replace(".", "") + blocks = [ + cisco_hardware_addr[x : x + 2] for x in range(0, len(cisco_hardware_addr), 2) + ] - return ':'.join(blocks).upper() + return ":".join(blocks).upper() diff --git a/homeassistant/components/cisco_mobility_express/device_tracker.py b/homeassistant/components/cisco_mobility_express/device_tracker.py index 4af94588d3b..ca24fcb5c52 100644 --- a/homeassistant/components/cisco_mobility_express/device_tracker.py +++ b/homeassistant/components/cisco_mobility_express/device_tracker.py @@ -5,27 +5,38 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.device_tracker import ( - DOMAIN, PLATFORM_SCHEMA, DeviceScanner) + DOMAIN, + PLATFORM_SCHEMA, + DeviceScanner, +) from homeassistant.const import ( - CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_SSL, CONF_VERIFY_SSL) + CONF_HOST, + CONF_USERNAME, + CONF_PASSWORD, + CONF_SSL, + CONF_VERIFY_SSL, +) _LOGGER = logging.getLogger(__name__) DEFAULT_SSL = False DEFAULT_VERIFY_SSL = True -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean, - vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean, + vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean, + } +) def get_scanner(hass, config): """Validate the configuration and return a Cisco ME scanner.""" from ciscomobilityexpress.ciscome import CiscoMobilityExpress + config = config[DOMAIN] controller = CiscoMobilityExpress( @@ -33,7 +44,8 @@ def get_scanner(hass, config): config[CONF_USERNAME], config[CONF_PASSWORD], config.get(CONF_SSL), - config.get(CONF_VERIFY_SSL)) + config.get(CONF_VERIFY_SSL), + ) if not controller.is_logged_in(): return None return CiscoMEDeviceScanner(controller) @@ -55,9 +67,10 @@ class CiscoMEDeviceScanner(DeviceScanner): def get_device_name(self, device): """Return the name of the given device or None if we don't know.""" - name = next(( - result.clId for result in self.last_results - if result.macaddr == device), None) + name = next( + (result.clId for result in self.last_results if result.macaddr == device), + None, + ) return name def get_extra_attributes(self, device): @@ -67,13 +80,14 @@ class CiscoMEDeviceScanner(DeviceScanner): Some known extra attributes that may be returned in the device tuple include SSID, PT (eg 802.11ac), devtype (eg iPhone 7) among others. """ - device = next(( - result for result in self.last_results - if result.macaddr == device), None) + device = next( + (result for result in self.last_results if result.macaddr == device), None + ) return device._asdict() def _update_info(self): """Check the Cisco ME controller for devices.""" self.last_results = self.controller.get_associated_devices() - _LOGGER.debug("Cisco Mobility Express controller returned:" - " %s", self.last_results) + _LOGGER.debug( + "Cisco Mobility Express controller returned:" " %s", self.last_results + ) diff --git a/homeassistant/components/cisco_webex_teams/notify.py b/homeassistant/components/cisco_webex_teams/notify.py index 22f8679f618..9feac3207ad 100644 --- a/homeassistant/components/cisco_webex_teams/notify.py +++ b/homeassistant/components/cisco_webex_teams/notify.py @@ -4,23 +4,26 @@ import logging import voluptuous as vol from homeassistant.components.notify import ( - PLATFORM_SCHEMA, BaseNotificationService, ATTR_TITLE) -from homeassistant.const import (CONF_TOKEN) + PLATFORM_SCHEMA, + BaseNotificationService, + ATTR_TITLE, +) +from homeassistant.const import CONF_TOKEN import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -CONF_ROOM_ID = 'room_id' +CONF_ROOM_ID = "room_id" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_TOKEN): cv.string, - vol.Required(CONF_ROOM_ID): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_TOKEN): cv.string, vol.Required(CONF_ROOM_ID): cv.string} +) def get_service(hass, config, discovery_info=None): """Get the CiscoWebexTeams notification service.""" from webexteamssdk import WebexTeamsAPI, exceptions + client = WebexTeamsAPI(access_token=config[CONF_TOKEN]) try: # Validate the token & room_id @@ -29,9 +32,7 @@ def get_service(hass, config, discovery_info=None): _LOGGER.error(error) return None - return CiscoWebexTeamsNotificationService( - client, - config[CONF_ROOM_ID]) + return CiscoWebexTeamsNotificationService(client, config[CONF_ROOM_ID]) class CiscoWebexTeamsNotificationService(BaseNotificationService): @@ -45,14 +46,16 @@ class CiscoWebexTeamsNotificationService(BaseNotificationService): def send_message(self, message="", **kwargs): """Send a message to a user.""" from webexteamssdk import ApiError + title = "" if kwargs.get(ATTR_TITLE) is not None: title = "{}{}".format(kwargs.get(ATTR_TITLE), "
") try: - self.client.messages.create(roomId=self.room, - html="{}{}".format(title, message)) + self.client.messages.create( + roomId=self.room, html="{}{}".format(title, message) + ) except ApiError as api_error: - _LOGGER.error("Could not send CiscoWebexTeams notification. " - "Error: %s", - api_error) + _LOGGER.error( + "Could not send CiscoWebexTeams notification. " "Error: %s", api_error + ) diff --git a/homeassistant/components/ciscospark/notify.py b/homeassistant/components/ciscospark/notify.py index 320c342b143..67609766366 100644 --- a/homeassistant/components/ciscospark/notify.py +++ b/homeassistant/components/ciscospark/notify.py @@ -6,24 +6,26 @@ import voluptuous as vol from homeassistant.const import CONF_TOKEN import homeassistant.helpers.config_validation as cv -from homeassistant.components.notify import (ATTR_TITLE, PLATFORM_SCHEMA, - BaseNotificationService) +from homeassistant.components.notify import ( + ATTR_TITLE, + PLATFORM_SCHEMA, + BaseNotificationService, +) _LOGGER = logging.getLogger(__name__) -CONF_ROOMID = 'roomid' +CONF_ROOMID = "roomid" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_TOKEN): cv.string, - vol.Required(CONF_ROOMID): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_TOKEN): cv.string, vol.Required(CONF_ROOMID): cv.string} +) def get_service(hass, config, discovery_info=None): """Get the CiscoSpark notification service.""" return CiscoSparkNotificationService( - config.get(CONF_TOKEN), - config.get(CONF_ROOMID)) + config.get(CONF_TOKEN), config.get(CONF_ROOMID) + ) class CiscoSparkNotificationService(BaseNotificationService): @@ -32,6 +34,7 @@ class CiscoSparkNotificationService(BaseNotificationService): def __init__(self, token, default_room): """Initialize the service.""" from ciscosparkapi import CiscoSparkAPI + self._default_room = default_room self._token = token self._spark = CiscoSparkAPI(access_token=self._token) @@ -39,12 +42,13 @@ class CiscoSparkNotificationService(BaseNotificationService): def send_message(self, message="", **kwargs): """Send a message to a user.""" from ciscosparkapi import SparkApiError + try: title = "" if kwargs.get(ATTR_TITLE) is not None: title = kwargs.get(ATTR_TITLE) + ": " - self._spark.messages.create(roomId=self._default_room, - text=title + message) + self._spark.messages.create(roomId=self._default_room, text=title + message) except SparkApiError as api_error: - _LOGGER.error("Could not send CiscoSpark notification. Error: %s", - api_error) + _LOGGER.error( + "Could not send CiscoSpark notification. Error: %s", api_error + ) diff --git a/homeassistant/components/citybikes/sensor.py b/homeassistant/components/citybikes/sensor.py index fc751d96602..cb2647487ea 100644 --- a/homeassistant/components/citybikes/sensor.py +++ b/homeassistant/components/citybikes/sensor.py @@ -9,9 +9,19 @@ import voluptuous as vol from homeassistant.components.sensor import ENTITY_ID_FORMAT, PLATFORM_SCHEMA from homeassistant.const import ( - ATTR_ATTRIBUTION, ATTR_ID, ATTR_LATITUDE, ATTR_LOCATION, ATTR_LONGITUDE, - ATTR_NAME, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, CONF_RADIUS, - LENGTH_FEET, LENGTH_METERS) + ATTR_ATTRIBUTION, + ATTR_ID, + ATTR_LATITUDE, + ATTR_LOCATION, + ATTR_LONGITUDE, + ATTR_NAME, + CONF_LATITUDE, + CONF_LONGITUDE, + CONF_NAME, + CONF_RADIUS, + LENGTH_FEET, + LENGTH_METERS, +) from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv @@ -21,78 +31,95 @@ from homeassistant.util import distance, location _LOGGER = logging.getLogger(__name__) -ATTR_EMPTY_SLOTS = 'empty_slots' -ATTR_EXTRA = 'extra' -ATTR_FREE_BIKES = 'free_bikes' -ATTR_NETWORK = 'network' -ATTR_NETWORKS_LIST = 'networks' -ATTR_STATIONS_LIST = 'stations' -ATTR_TIMESTAMP = 'timestamp' -ATTR_UID = 'uid' +ATTR_EMPTY_SLOTS = "empty_slots" +ATTR_EXTRA = "extra" +ATTR_FREE_BIKES = "free_bikes" +ATTR_NETWORK = "network" +ATTR_NETWORKS_LIST = "networks" +ATTR_STATIONS_LIST = "stations" +ATTR_TIMESTAMP = "timestamp" +ATTR_UID = "uid" -CONF_NETWORK = 'network' -CONF_STATIONS_LIST = 'stations' +CONF_NETWORK = "network" +CONF_STATIONS_LIST = "stations" -DEFAULT_ENDPOINT = 'https://api.citybik.es/{uri}' -PLATFORM = 'citybikes' +DEFAULT_ENDPOINT = "https://api.citybik.es/{uri}" +PLATFORM = "citybikes" -MONITORED_NETWORKS = 'monitored-networks' +MONITORED_NETWORKS = "monitored-networks" -NETWORKS_URI = 'v2/networks' +NETWORKS_URI = "v2/networks" REQUEST_TIMEOUT = 5 # In seconds; argument to asyncio.timeout SCAN_INTERVAL = timedelta(minutes=5) # Timely, and doesn't suffocate the API -STATIONS_URI = 'v2/networks/{uid}?fields=network.stations' +STATIONS_URI = "v2/networks/{uid}?fields=network.stations" -CITYBIKES_ATTRIBUTION = "Information provided by the CityBikes Project "\ - "(https://citybik.es/#about)" +CITYBIKES_ATTRIBUTION = ( + "Information provided by the CityBikes Project " "(https://citybik.es/#about)" +) -CITYBIKES_NETWORKS = 'citybikes_networks' +CITYBIKES_NETWORKS = "citybikes_networks" PLATFORM_SCHEMA = vol.All( cv.has_at_least_one_key(CONF_RADIUS, CONF_STATIONS_LIST), - PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME, default=''): cv.string, - vol.Optional(CONF_NETWORK): cv.string, - vol.Inclusive(CONF_LATITUDE, 'coordinates'): cv.latitude, - vol.Inclusive(CONF_LONGITUDE, 'coordinates'): cv.longitude, - vol.Optional(CONF_RADIUS, 'station_filter'): cv.positive_int, - vol.Optional(CONF_STATIONS_LIST, 'station_filter'): - vol.All(cv.ensure_list, vol.Length(min=1), [cv.string]) - })) + PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=""): cv.string, + vol.Optional(CONF_NETWORK): cv.string, + vol.Inclusive(CONF_LATITUDE, "coordinates"): cv.latitude, + vol.Inclusive(CONF_LONGITUDE, "coordinates"): cv.longitude, + vol.Optional(CONF_RADIUS, "station_filter"): cv.positive_int, + vol.Optional(CONF_STATIONS_LIST, "station_filter"): vol.All( + cv.ensure_list, vol.Length(min=1), [cv.string] + ), + } + ), +) -NETWORK_SCHEMA = vol.Schema({ - vol.Required(ATTR_ID): cv.string, - vol.Required(ATTR_NAME): cv.string, - vol.Required(ATTR_LOCATION): vol.Schema({ +NETWORK_SCHEMA = vol.Schema( + { + vol.Required(ATTR_ID): cv.string, + vol.Required(ATTR_NAME): cv.string, + vol.Required(ATTR_LOCATION): vol.Schema( + { + vol.Required(ATTR_LATITUDE): cv.latitude, + vol.Required(ATTR_LONGITUDE): cv.longitude, + }, + extra=vol.REMOVE_EXTRA, + ), + }, + extra=vol.REMOVE_EXTRA, +) + +NETWORKS_RESPONSE_SCHEMA = vol.Schema( + {vol.Required(ATTR_NETWORKS_LIST): [NETWORK_SCHEMA]} +) + +STATION_SCHEMA = vol.Schema( + { + vol.Required(ATTR_FREE_BIKES): cv.positive_int, + vol.Required(ATTR_EMPTY_SLOTS): vol.Any(cv.positive_int, None), vol.Required(ATTR_LATITUDE): cv.latitude, vol.Required(ATTR_LONGITUDE): cv.longitude, - }, extra=vol.REMOVE_EXTRA), -}, extra=vol.REMOVE_EXTRA) + vol.Required(ATTR_ID): cv.string, + vol.Required(ATTR_NAME): cv.string, + vol.Required(ATTR_TIMESTAMP): cv.string, + vol.Optional(ATTR_EXTRA): vol.Schema( + {vol.Optional(ATTR_UID): cv.string}, extra=vol.REMOVE_EXTRA + ), + }, + extra=vol.REMOVE_EXTRA, +) -NETWORKS_RESPONSE_SCHEMA = vol.Schema({ - vol.Required(ATTR_NETWORKS_LIST): [NETWORK_SCHEMA], -}) - -STATION_SCHEMA = vol.Schema({ - vol.Required(ATTR_FREE_BIKES): cv.positive_int, - vol.Required(ATTR_EMPTY_SLOTS): vol.Any(cv.positive_int, None), - vol.Required(ATTR_LATITUDE): cv.latitude, - vol.Required(ATTR_LONGITUDE): cv.longitude, - vol.Required(ATTR_ID): cv.string, - vol.Required(ATTR_NAME): cv.string, - vol.Required(ATTR_TIMESTAMP): cv.string, - vol.Optional(ATTR_EXTRA): - vol.Schema({vol.Optional(ATTR_UID): cv.string}, extra=vol.REMOVE_EXTRA) -}, extra=vol.REMOVE_EXTRA) - -STATIONS_RESPONSE_SCHEMA = vol.Schema({ - vol.Required(ATTR_NETWORK): vol.Schema({ - vol.Required(ATTR_STATIONS_LIST): [STATION_SCHEMA] - }, extra=vol.REMOVE_EXTRA) -}) +STATIONS_RESPONSE_SCHEMA = vol.Schema( + { + vol.Required(ATTR_NETWORK): vol.Schema( + {vol.Required(ATTR_STATIONS_LIST): [STATION_SCHEMA]}, extra=vol.REMOVE_EXTRA + ) + } +) class CityBikesRequestError(Exception): @@ -116,13 +143,13 @@ async def async_citybikes_request(hass, uri, schema): except ValueError: _LOGGER.error("Received non-JSON data from CityBikes API endpoint") except vol.Invalid as err: - _LOGGER.error("Received unexpected JSON from CityBikes" - " API endpoint: %s", err) + _LOGGER.error( + "Received unexpected JSON from CityBikes" " API endpoint: %s", err + ) raise CityBikesRequestError -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the CityBikes platform.""" if PLATFORM not in hass.data: hass.data[PLATFORM] = {MONITORED_NETWORKS: {}} @@ -137,8 +164,7 @@ async def async_setup_platform(hass, config, async_add_entities, radius = distance.convert(radius, LENGTH_FEET, LENGTH_METERS) # Create a single instance of CityBikesNetworks. - networks = hass.data.setdefault( - CITYBIKES_NETWORKS, CityBikesNetworks(hass)) + networks = hass.data.setdefault(CITYBIKES_NETWORKS, CityBikesNetworks(hass)) if not network_id: network_id = await networks.get_closest_network_id(latitude, longitude) @@ -156,19 +182,17 @@ async def async_setup_platform(hass, config, async_add_entities, devices = [] for station in network.stations: dist = location.distance( - latitude, longitude, station[ATTR_LATITUDE], - station[ATTR_LONGITUDE]) + latitude, longitude, station[ATTR_LATITUDE], station[ATTR_LONGITUDE] + ) station_id = station[ATTR_ID] - station_uid = str(station.get(ATTR_EXTRA, {}).get(ATTR_UID, '')) + station_uid = str(station.get(ATTR_EXTRA, {}).get(ATTR_UID, "")) - if radius > dist or stations_list.intersection( - (station_id, station_uid)): + if radius > dist or stations_list.intersection((station_id, station_uid)): if name: uid = "_".join([network.network_id, name, station_id]) else: uid = "_".join([network.network_id, station_id]) - entity_id = async_generate_entity_id( - ENTITY_ID_FORMAT, uid, hass=hass) + entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, uid, hass=hass) devices.append(CityBikesStation(network, station_id, entity_id)) async_add_entities(devices, True) @@ -189,7 +213,8 @@ class CityBikesNetworks: await self.networks_loading.acquire() if self.networks is None: networks = await async_citybikes_request( - self.hass, NETWORKS_URI, NETWORKS_RESPONSE_SCHEMA) + self.hass, NETWORKS_URI, NETWORKS_RESPONSE_SCHEMA + ) self.networks = networks[ATTR_NETWORKS_LIST] result = None minimum_dist = None @@ -197,7 +222,8 @@ class CityBikesNetworks: network_latitude = network[ATTR_LOCATION][ATTR_LATITUDE] network_longitude = network[ATTR_LOCATION][ATTR_LONGITUDE] dist = location.distance( - latitude, longitude, network_latitude, network_longitude) + latitude, longitude, network_latitude, network_longitude + ) if minimum_dist is None or dist < minimum_dist: minimum_dist = dist result = network[ATTR_ID] @@ -223,8 +249,10 @@ class CityBikesNetwork: """Refresh the state of the network.""" try: network = await async_citybikes_request( - self.hass, STATIONS_URI.format(uid=self.network_id), - STATIONS_RESPONSE_SCHEMA) + self.hass, + STATIONS_URI.format(uid=self.network_id), + STATIONS_RESPONSE_SCHEMA, + ) self.stations = network[ATTR_NETWORK][ATTR_STATIONS_LIST] self.ready.set() except CityBikesRequestError: @@ -278,9 +306,9 @@ class CityBikesStation(Entity): @property def unit_of_measurement(self): """Return the unit of measurement.""" - return 'bikes' + return "bikes" @property def icon(self): """Return the icon.""" - return 'mdi:bike' + return "mdi:bike" diff --git a/homeassistant/components/clementine/media_player.py b/homeassistant/components/clementine/media_player.py index fc6e27be1bd..37ed97915c7 100644 --- a/homeassistant/components/clementine/media_player.py +++ b/homeassistant/components/clementine/media_player.py @@ -5,40 +5,59 @@ import time import voluptuous as vol -from homeassistant.components.media_player import ( - MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA from homeassistant.components.media_player.const import ( - MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, - SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, - SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP) + MEDIA_TYPE_MUSIC, + SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, + SUPPORT_PLAY, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_SELECT_SOURCE, + SUPPORT_VOLUME_SET, + SUPPORT_VOLUME_STEP, +) from homeassistant.const import ( - CONF_ACCESS_TOKEN, CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, - STATE_PAUSED, STATE_PLAYING) + CONF_ACCESS_TOKEN, + CONF_HOST, + CONF_NAME, + CONF_PORT, + STATE_OFF, + STATE_PAUSED, + STATE_PLAYING, +) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'Clementine Remote' +DEFAULT_NAME = "Clementine Remote" DEFAULT_PORT = 5500 SCAN_INTERVAL = timedelta(seconds=5) -SUPPORT_CLEMENTINE = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \ - SUPPORT_PREVIOUS_TRACK | SUPPORT_VOLUME_SET | \ - SUPPORT_NEXT_TRACK | \ - SUPPORT_SELECT_SOURCE | SUPPORT_PLAY +SUPPORT_CLEMENTINE = ( + SUPPORT_PAUSE + | SUPPORT_VOLUME_STEP + | SUPPORT_PREVIOUS_TRACK + | SUPPORT_VOLUME_SET + | SUPPORT_NEXT_TRACK + | SUPPORT_SELECT_SOURCE + | SUPPORT_PLAY +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_ACCESS_TOKEN): cv.positive_int, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_ACCESS_TOKEN): cv.positive_int, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Clementine platform.""" from clementineremote import ClementineRemote + host = config.get(CONF_HOST) port = config.get(CONF_PORT) token = config.get(CONF_ACCESS_TOKEN) @@ -59,9 +78,9 @@ class ClementineDevice(MediaPlayerDevice): self._volume = 0.0 self._track_id = 0 self._last_track_id = 0 - self._track_name = '' - self._track_artist = '' - self._track_album_name = '' + self._track_name = "" + self._track_artist = "" + self._track_album_name = "" self._state = None def update(self): @@ -69,11 +88,11 @@ class ClementineDevice(MediaPlayerDevice): try: client = self._client - if client.state == 'Playing': + if client.state == "Playing": self._state = STATE_PLAYING - elif client.state == 'Paused': + elif client.state == "Paused": self._state = STATE_PAUSED - elif client.state == 'Disconnected': + elif client.state == "Disconnected": self._state = STATE_OFF else: self._state = STATE_PAUSED @@ -84,10 +103,10 @@ class ClementineDevice(MediaPlayerDevice): self._volume = float(client.volume) if client.volume else 0.0 if client.current_track: - self._track_id = client.current_track['track_id'] - self._track_name = client.current_track['title'] - self._track_artist = client.current_track['track_artist'] - self._track_album_name = client.current_track['track_album'] + self._track_id = client.current_track["track_id"] + self._track_name = client.current_track["title"] + self._track_artist = client.current_track["track_artist"] + self._track_album_name = client.current_track["track_album"] except Exception: self._state = STATE_OFF @@ -114,7 +133,7 @@ class ClementineDevice(MediaPlayerDevice): source_name = "Unknown" client = self._client if client.active_playlist_id in client.playlists: - source_name = client.playlists[client.active_playlist_id]['name'] + source_name = client.playlists[client.active_playlist_id]["name"] return source_name @property @@ -126,9 +145,9 @@ class ClementineDevice(MediaPlayerDevice): def select_source(self, source): """Select input source.""" client = self._client - sources = [s for s in client.playlists.values() if s['name'] == source] + sources = [s for s in client.playlists.values() if s["name"] == source] if len(sources) == 1: - client.change_song(sources[0]['id'], 0) + client.change_song(sources[0]["id"], 0) @property def media_content_type(self): @@ -159,15 +178,15 @@ class ClementineDevice(MediaPlayerDevice): def media_image_hash(self): """Hash value for media image.""" if self._client.current_track: - return self._client.current_track['track_id'] + return self._client.current_track["track_id"] return None async def async_get_media_image(self): """Fetch media image of current playing image.""" if self._client.current_track: - image = bytes(self._client.current_track['art']) - return (image, 'image/png') + image = bytes(self._client.current_track["art"]) + return (image, "image/png") return None, None diff --git a/homeassistant/components/clickatell/notify.py b/homeassistant/components/clickatell/notify.py index b512a288ed5..26f2f30aeff 100644 --- a/homeassistant/components/clickatell/notify.py +++ b/homeassistant/components/clickatell/notify.py @@ -7,19 +7,17 @@ import voluptuous as vol from homeassistant.const import CONF_API_KEY, CONF_RECIPIENT import homeassistant.helpers.config_validation as cv -from homeassistant.components.notify import (PLATFORM_SCHEMA, - BaseNotificationService) +from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationService _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'clickatell' +DEFAULT_NAME = "clickatell" -BASE_API_URL = 'https://platform.clickatell.com/messages/http/send' +BASE_API_URL = "https://platform.clickatell.com/messages/http/send" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_API_KEY): cv.string, - vol.Required(CONF_RECIPIENT): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_API_KEY): cv.string, vol.Required(CONF_RECIPIENT): cv.string} +) def get_service(hass, config, discovery_info=None): @@ -37,11 +35,7 @@ class ClickatellNotificationService(BaseNotificationService): def send_message(self, message="", **kwargs): """Send a message to a user.""" - data = { - 'apiKey': self.api_key, - 'to': self.recipient, - 'content': message, - } + data = {"apiKey": self.api_key, "to": self.recipient, "content": message} resp = requests.get(BASE_API_URL, params=data, timeout=5) if (resp.status_code != 200) or (resp.status_code != 201): diff --git a/homeassistant/components/clicksend/notify.py b/homeassistant/components/clicksend/notify.py index 111ae63601f..1ec828b4a28 100644 --- a/homeassistant/components/clicksend/notify.py +++ b/homeassistant/components/clicksend/notify.py @@ -7,30 +7,39 @@ import requests import voluptuous as vol from homeassistant.const import ( - CONF_API_KEY, CONF_RECIPIENT, CONF_SENDER, CONF_USERNAME, - CONTENT_TYPE_JSON) + CONF_API_KEY, + CONF_RECIPIENT, + CONF_SENDER, + CONF_USERNAME, + CONTENT_TYPE_JSON, +) import homeassistant.helpers.config_validation as cv -from homeassistant.components.notify import (PLATFORM_SCHEMA, - BaseNotificationService) +from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationService _LOGGER = logging.getLogger(__name__) -BASE_API_URL = 'https://rest.clicksend.com/v3' -DEFAULT_SENDER = 'hass' +BASE_API_URL = "https://rest.clicksend.com/v3" +DEFAULT_SENDER = "hass" TIMEOUT = 5 HEADERS = {CONTENT_TYPE: CONTENT_TYPE_JSON} PLATFORM_SCHEMA = vol.Schema( - vol.All(PLATFORM_SCHEMA.extend({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_API_KEY): cv.string, - vol.Required(CONF_RECIPIENT, default=[]): - vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_SENDER, default=DEFAULT_SENDER): cv.string, - }),)) + vol.All( + PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_API_KEY): cv.string, + vol.Required(CONF_RECIPIENT, default=[]): vol.All( + cv.ensure_list, [cv.string] + ), + vol.Optional(CONF_SENDER, default=DEFAULT_SENDER): cv.string, + } + ) + ) +) def get_service(hass, config, discovery_info=None): @@ -55,37 +64,43 @@ class ClicksendNotificationService(BaseNotificationService): """Send a message to a user.""" data = {"messages": []} for recipient in self.recipients: - data["messages"].append({ - 'source': 'hass.notify', - 'from': self.sender, - 'to': recipient, - 'body': message, - }) + data["messages"].append( + { + "source": "hass.notify", + "from": self.sender, + "to": recipient, + "body": message, + } + ) api_url = "{}/sms/send".format(BASE_API_URL) - resp = requests.post(api_url, - data=json.dumps(data), - headers=HEADERS, - auth=(self.username, self.api_key), - timeout=TIMEOUT) + resp = requests.post( + api_url, + data=json.dumps(data), + headers=HEADERS, + auth=(self.username, self.api_key), + timeout=TIMEOUT, + ) if resp.status_code == 200: return obj = json.loads(resp.text) - response_msg = obj.get('response_msg') - response_code = obj.get('response_code') - _LOGGER.error("Error %s : %s (Code %s)", resp.status_code, - response_msg, response_code) + response_msg = obj.get("response_msg") + response_code = obj.get("response_code") + _LOGGER.error( + "Error %s : %s (Code %s)", resp.status_code, response_msg, response_code + ) def _authenticate(config): """Authenticate with ClickSend.""" - api_url = '{}/account'.format(BASE_API_URL) - resp = requests.get(api_url, - headers=HEADERS, - auth=(config[CONF_USERNAME], - config[CONF_API_KEY]), - timeout=TIMEOUT) + api_url = "{}/account".format(BASE_API_URL) + resp = requests.get( + api_url, + headers=HEADERS, + auth=(config[CONF_USERNAME], config[CONF_API_KEY]), + timeout=TIMEOUT, + ) if resp.status_code != 200: return False return True diff --git a/homeassistant/components/clicksend_tts/notify.py b/homeassistant/components/clicksend_tts/notify.py index feb4481fb56..7c73c346a33 100644 --- a/homeassistant/components/clicksend_tts/notify.py +++ b/homeassistant/components/clicksend_tts/notify.py @@ -7,34 +7,39 @@ import requests import voluptuous as vol from homeassistant.const import ( - CONF_API_KEY, CONF_RECIPIENT, CONF_USERNAME, CONTENT_TYPE_JSON) + CONF_API_KEY, + CONF_RECIPIENT, + CONF_USERNAME, + CONTENT_TYPE_JSON, +) import homeassistant.helpers.config_validation as cv -from homeassistant.components.notify import (PLATFORM_SCHEMA, - BaseNotificationService) +from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationService _LOGGER = logging.getLogger(__name__) -BASE_API_URL = 'https://rest.clicksend.com/v3' +BASE_API_URL = "https://rest.clicksend.com/v3" HEADERS = {CONTENT_TYPE: CONTENT_TYPE_JSON} -CONF_LANGUAGE = 'language' -CONF_VOICE = 'voice' -CONF_CALLER = 'caller' +CONF_LANGUAGE = "language" +CONF_VOICE = "voice" +CONF_CALLER = "caller" -DEFAULT_LANGUAGE = 'en-us' -DEFAULT_VOICE = 'female' +DEFAULT_LANGUAGE = "en-us" +DEFAULT_VOICE = "female" TIMEOUT = 5 -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_API_KEY): cv.string, - vol.Required(CONF_RECIPIENT): cv.string, - vol.Optional(CONF_LANGUAGE, default=DEFAULT_LANGUAGE): cv.string, - vol.Optional(CONF_VOICE, default=DEFAULT_VOICE): cv.string, - vol.Optional(CONF_CALLER): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_API_KEY): cv.string, + vol.Required(CONF_RECIPIENT): cv.string, + vol.Optional(CONF_LANGUAGE, default=DEFAULT_LANGUAGE): cv.string, + vol.Optional(CONF_VOICE, default=DEFAULT_VOICE): cv.string, + vol.Optional(CONF_CALLER): cv.string, + } +) def get_service(hass, config, discovery_info=None): @@ -62,31 +67,46 @@ class ClicksendNotificationService(BaseNotificationService): def send_message(self, message="", **kwargs): """Send a voice call to a user.""" - data = ({'messages': [{'source': 'hass.notify', 'from': self.caller, - 'to': self.recipient, 'body': message, - 'lang': self.language, 'voice': self.voice}]}) + data = { + "messages": [ + { + "source": "hass.notify", + "from": self.caller, + "to": self.recipient, + "body": message, + "lang": self.language, + "voice": self.voice, + } + ] + } api_url = "{}/voice/send".format(BASE_API_URL) - resp = requests.post(api_url, - data=json.dumps(data), - headers=HEADERS, - auth=(self.username, self.api_key), - timeout=TIMEOUT) + resp = requests.post( + api_url, + data=json.dumps(data), + headers=HEADERS, + auth=(self.username, self.api_key), + timeout=TIMEOUT, + ) if resp.status_code == 200: return obj = json.loads(resp.text) - response_msg = obj['response_msg'] - response_code = obj['response_code'] - _LOGGER.error("Error %s : %s (Code %s)", resp.status_code, - response_msg, response_code) + response_msg = obj["response_msg"] + response_code = obj["response_code"] + _LOGGER.error( + "Error %s : %s (Code %s)", resp.status_code, response_msg, response_code + ) def _authenticate(config): """Authenticate with ClickSend.""" - api_url = '{}/account'.format(BASE_API_URL) - resp = requests.get(api_url, headers=HEADERS, - auth=(config.get(CONF_USERNAME), - config.get(CONF_API_KEY)), timeout=TIMEOUT) + api_url = "{}/account".format(BASE_API_URL) + resp = requests.get( + api_url, + headers=HEADERS, + auth=(config.get(CONF_USERNAME), config.get(CONF_API_KEY)), + timeout=TIMEOUT, + ) if resp.status_code != 200: return False diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index 34f69e53b99..6c64d667254 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -7,31 +7,69 @@ from typing import Any, Dict, List, Optional import voluptuous as vol from homeassistant.const import ( - ATTR_TEMPERATURE, PRECISION_TENTHS, PRECISION_WHOLE, SERVICE_TURN_OFF, - SERVICE_TURN_ON, STATE_OFF, STATE_ON, TEMP_CELSIUS) + ATTR_TEMPERATURE, + PRECISION_TENTHS, + PRECISION_WHOLE, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, + TEMP_CELSIUS, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.config_validation import ( # noqa - ENTITY_SERVICE_SCHEMA, PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE) + ENTITY_SERVICE_SCHEMA, + PLATFORM_SCHEMA, + PLATFORM_SCHEMA_BASE, +) from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.temperature import display_temp as show_temp -from homeassistant.helpers.typing import ( - ConfigType, HomeAssistantType, ServiceDataType) +from homeassistant.helpers.typing import ConfigType, HomeAssistantType, ServiceDataType from homeassistant.util.temperature import convert as convert_temperature from .const import ( - ATTR_AUX_HEAT, ATTR_CURRENT_HUMIDITY, ATTR_CURRENT_TEMPERATURE, - ATTR_FAN_MODE, ATTR_FAN_MODES, ATTR_HUMIDITY, ATTR_HVAC_ACTIONS, - ATTR_HVAC_MODE, ATTR_HVAC_MODES, ATTR_MAX_HUMIDITY, ATTR_MAX_TEMP, - ATTR_MIN_HUMIDITY, ATTR_MIN_TEMP, ATTR_PRESET_MODE, ATTR_PRESET_MODES, - ATTR_SWING_MODE, ATTR_SWING_MODES, ATTR_TARGET_TEMP_HIGH, - ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_STEP, DOMAIN, HVAC_MODE_COOL, - HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, HVAC_MODES, - SERVICE_SET_AUX_HEAT, SERVICE_SET_FAN_MODE, SERVICE_SET_HUMIDITY, - SERVICE_SET_HVAC_MODE, SERVICE_SET_PRESET_MODE, SERVICE_SET_SWING_MODE, - SERVICE_SET_TEMPERATURE, SUPPORT_AUX_HEAT, SUPPORT_FAN_MODE, - SUPPORT_PRESET_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_HUMIDITY, - SUPPORT_TARGET_TEMPERATURE_RANGE, SUPPORT_TARGET_TEMPERATURE) + ATTR_AUX_HEAT, + ATTR_CURRENT_HUMIDITY, + ATTR_CURRENT_TEMPERATURE, + ATTR_FAN_MODE, + ATTR_FAN_MODES, + ATTR_HUMIDITY, + ATTR_HVAC_ACTIONS, + ATTR_HVAC_MODE, + ATTR_HVAC_MODES, + ATTR_MAX_HUMIDITY, + ATTR_MAX_TEMP, + ATTR_MIN_HUMIDITY, + ATTR_MIN_TEMP, + ATTR_PRESET_MODE, + ATTR_PRESET_MODES, + ATTR_SWING_MODE, + ATTR_SWING_MODES, + ATTR_TARGET_TEMP_HIGH, + ATTR_TARGET_TEMP_LOW, + ATTR_TARGET_TEMP_STEP, + DOMAIN, + HVAC_MODE_COOL, + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_OFF, + HVAC_MODES, + SERVICE_SET_AUX_HEAT, + SERVICE_SET_FAN_MODE, + SERVICE_SET_HUMIDITY, + SERVICE_SET_HVAC_MODE, + SERVICE_SET_PRESET_MODE, + SERVICE_SET_SWING_MODE, + SERVICE_SET_TEMPERATURE, + SUPPORT_AUX_HEAT, + SUPPORT_FAN_MODE, + SUPPORT_PRESET_MODE, + SUPPORT_SWING_MODE, + SUPPORT_TARGET_HUMIDITY, + SUPPORT_TARGET_TEMPERATURE_RANGE, + SUPPORT_TARGET_TEMPERATURE, +) from .reproduce_state import async_reproduce_states # noqa DEFAULT_MIN_TEMP = 7 @@ -39,88 +77,81 @@ DEFAULT_MAX_TEMP = 35 DEFAULT_MIN_HUMIDITY = 30 DEFAULT_MAX_HUMIDITY = 99 -ENTITY_ID_FORMAT = DOMAIN + '.{}' +ENTITY_ID_FORMAT = DOMAIN + ".{}" SCAN_INTERVAL = timedelta(seconds=60) -CONVERTIBLE_ATTRIBUTE = [ - ATTR_TEMPERATURE, - ATTR_TARGET_TEMP_LOW, - ATTR_TARGET_TEMP_HIGH, -] +CONVERTIBLE_ATTRIBUTE = [ATTR_TEMPERATURE, ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_HIGH] _LOGGER = logging.getLogger(__name__) -SET_AUX_HEAT_SCHEMA = ENTITY_SERVICE_SCHEMA.extend({ - vol.Required(ATTR_AUX_HEAT): cv.boolean, -}) -SET_TEMPERATURE_SCHEMA = vol.Schema(vol.All( - cv.has_at_least_one_key( - ATTR_TEMPERATURE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW), - ENTITY_SERVICE_SCHEMA.extend({ - vol.Exclusive(ATTR_TEMPERATURE, 'temperature'): vol.Coerce(float), - vol.Inclusive(ATTR_TARGET_TEMP_HIGH, 'temperature'): vol.Coerce(float), - vol.Inclusive(ATTR_TARGET_TEMP_LOW, 'temperature'): vol.Coerce(float), - vol.Optional(ATTR_HVAC_MODE): vol.In(HVAC_MODES), - }) -)) -SET_FAN_MODE_SCHEMA = ENTITY_SERVICE_SCHEMA.extend({ - vol.Required(ATTR_FAN_MODE): cv.string, -}) -SET_PRESET_MODE_SCHEMA = ENTITY_SERVICE_SCHEMA.extend({ - vol.Required(ATTR_PRESET_MODE): cv.string, -}) -SET_HVAC_MODE_SCHEMA = ENTITY_SERVICE_SCHEMA.extend({ - vol.Required(ATTR_HVAC_MODE): vol.In(HVAC_MODES), -}) -SET_HUMIDITY_SCHEMA = ENTITY_SERVICE_SCHEMA.extend({ - vol.Required(ATTR_HUMIDITY): vol.Coerce(float), -}) -SET_SWING_MODE_SCHEMA = ENTITY_SERVICE_SCHEMA.extend({ - vol.Required(ATTR_SWING_MODE): cv.string, -}) +SET_AUX_HEAT_SCHEMA = ENTITY_SERVICE_SCHEMA.extend( + {vol.Required(ATTR_AUX_HEAT): cv.boolean} +) +SET_TEMPERATURE_SCHEMA = vol.Schema( + vol.All( + cv.has_at_least_one_key( + ATTR_TEMPERATURE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW + ), + ENTITY_SERVICE_SCHEMA.extend( + { + vol.Exclusive(ATTR_TEMPERATURE, "temperature"): vol.Coerce(float), + vol.Inclusive(ATTR_TARGET_TEMP_HIGH, "temperature"): vol.Coerce(float), + vol.Inclusive(ATTR_TARGET_TEMP_LOW, "temperature"): vol.Coerce(float), + vol.Optional(ATTR_HVAC_MODE): vol.In(HVAC_MODES), + } + ), + ) +) +SET_FAN_MODE_SCHEMA = ENTITY_SERVICE_SCHEMA.extend( + {vol.Required(ATTR_FAN_MODE): cv.string} +) +SET_PRESET_MODE_SCHEMA = ENTITY_SERVICE_SCHEMA.extend( + {vol.Required(ATTR_PRESET_MODE): cv.string} +) +SET_HVAC_MODE_SCHEMA = ENTITY_SERVICE_SCHEMA.extend( + {vol.Required(ATTR_HVAC_MODE): vol.In(HVAC_MODES)} +) +SET_HUMIDITY_SCHEMA = ENTITY_SERVICE_SCHEMA.extend( + {vol.Required(ATTR_HUMIDITY): vol.Coerce(float)} +) +SET_SWING_MODE_SCHEMA = ENTITY_SERVICE_SCHEMA.extend( + {vol.Required(ATTR_SWING_MODE): cv.string} +) async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: """Set up climate devices.""" - component = hass.data[DOMAIN] = \ - EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL) + component = hass.data[DOMAIN] = EntityComponent( + _LOGGER, DOMAIN, hass, SCAN_INTERVAL + ) await component.async_setup(config) component.async_register_entity_service( - SERVICE_TURN_ON, ENTITY_SERVICE_SCHEMA, - 'async_turn_on' + SERVICE_TURN_ON, ENTITY_SERVICE_SCHEMA, "async_turn_on" ) component.async_register_entity_service( - SERVICE_TURN_OFF, ENTITY_SERVICE_SCHEMA, - 'async_turn_off' + SERVICE_TURN_OFF, ENTITY_SERVICE_SCHEMA, "async_turn_off" ) component.async_register_entity_service( - SERVICE_SET_HVAC_MODE, SET_HVAC_MODE_SCHEMA, - 'async_set_hvac_mode' + SERVICE_SET_HVAC_MODE, SET_HVAC_MODE_SCHEMA, "async_set_hvac_mode" ) component.async_register_entity_service( - SERVICE_SET_PRESET_MODE, SET_PRESET_MODE_SCHEMA, - 'async_set_preset_mode' + SERVICE_SET_PRESET_MODE, SET_PRESET_MODE_SCHEMA, "async_set_preset_mode" ) component.async_register_entity_service( - SERVICE_SET_AUX_HEAT, SET_AUX_HEAT_SCHEMA, - async_service_aux_heat + SERVICE_SET_AUX_HEAT, SET_AUX_HEAT_SCHEMA, async_service_aux_heat ) component.async_register_entity_service( - SERVICE_SET_TEMPERATURE, SET_TEMPERATURE_SCHEMA, - async_service_temperature_set + SERVICE_SET_TEMPERATURE, SET_TEMPERATURE_SCHEMA, async_service_temperature_set ) component.async_register_entity_service( - SERVICE_SET_HUMIDITY, SET_HUMIDITY_SCHEMA, - 'async_set_humidity' + SERVICE_SET_HUMIDITY, SET_HUMIDITY_SCHEMA, "async_set_humidity" ) component.async_register_entity_service( - SERVICE_SET_FAN_MODE, SET_FAN_MODE_SCHEMA, - 'async_set_fan_mode' + SERVICE_SET_FAN_MODE, SET_FAN_MODE_SCHEMA, "async_set_fan_mode" ) component.async_register_entity_service( - SERVICE_SET_SWING_MODE, SET_SWING_MODE_SCHEMA, - 'async_set_swing_mode' + SERVICE_SET_SWING_MODE, SET_SWING_MODE_SCHEMA, "async_set_swing_mode" ) return True @@ -158,14 +189,17 @@ class ClimateDevice(Entity): data = { ATTR_HVAC_MODES: self.hvac_modes, ATTR_CURRENT_TEMPERATURE: show_temp( - self.hass, self.current_temperature, self.temperature_unit, - self.precision), + self.hass, + self.current_temperature, + self.temperature_unit, + self.precision, + ), ATTR_MIN_TEMP: show_temp( - self.hass, self.min_temp, self.temperature_unit, - self.precision), + self.hass, self.min_temp, self.temperature_unit, self.precision + ), ATTR_MAX_TEMP: show_temp( - self.hass, self.max_temp, self.temperature_unit, - self.precision), + self.hass, self.max_temp, self.temperature_unit, self.precision + ), } if self.target_temperature_step: @@ -173,16 +207,25 @@ class ClimateDevice(Entity): if supported_features & SUPPORT_TARGET_TEMPERATURE: data[ATTR_TEMPERATURE] = show_temp( - self.hass, self.target_temperature, self.temperature_unit, - self.precision) + self.hass, + self.target_temperature, + self.temperature_unit, + self.precision, + ) if supported_features & SUPPORT_TARGET_TEMPERATURE_RANGE: data[ATTR_TARGET_TEMP_HIGH] = show_temp( - self.hass, self.target_temperature_high, self.temperature_unit, - self.precision) + self.hass, + self.target_temperature_high, + self.temperature_unit, + self.precision, + ) data[ATTR_TARGET_TEMP_LOW] = show_temp( - self.hass, self.target_temperature_low, self.temperature_unit, - self.precision) + self.hass, + self.target_temperature_low, + self.temperature_unit, + self.precision, + ) if self.current_humidity is not None: data[ATTR_CURRENT_HUMIDITY] = self.current_humidity @@ -345,7 +388,8 @@ class ClimateDevice(Entity): async def async_set_temperature(self, **kwargs) -> None: """Set new target temperature.""" await self.hass.async_add_executor_job( - ft.partial(self.set_temperature, **kwargs)) + ft.partial(self.set_temperature, **kwargs) + ) def set_humidity(self, humidity: int) -> None: """Set new target humidity.""" @@ -385,8 +429,7 @@ class ClimateDevice(Entity): async def async_set_preset_mode(self, preset_mode: str) -> None: """Set new preset mode.""" - await self.hass.async_add_executor_job( - self.set_preset_mode, preset_mode) + await self.hass.async_add_executor_job(self.set_preset_mode, preset_mode) def turn_aux_heat_on(self) -> None: """Turn auxiliary heater on.""" @@ -406,7 +449,7 @@ class ClimateDevice(Entity): async def async_turn_on(self) -> None: """Turn the entity on.""" - if hasattr(self, 'turn_on'): + if hasattr(self, "turn_on"): # pylint: disable=no-member await self.hass.async_add_executor_job(self.turn_on) return @@ -420,7 +463,7 @@ class ClimateDevice(Entity): async def async_turn_off(self) -> None: """Turn the entity off.""" - if hasattr(self, 'turn_off'): + if hasattr(self, "turn_off"): # pylint: disable=no-member await self.hass.async_add_executor_job(self.turn_off) return @@ -437,14 +480,16 @@ class ClimateDevice(Entity): @property def min_temp(self) -> float: """Return the minimum temperature.""" - return convert_temperature(DEFAULT_MIN_TEMP, TEMP_CELSIUS, - self.temperature_unit) + return convert_temperature( + DEFAULT_MIN_TEMP, TEMP_CELSIUS, self.temperature_unit + ) @property def max_temp(self) -> float: """Return the maximum temperature.""" - return convert_temperature(DEFAULT_MAX_TEMP, TEMP_CELSIUS, - self.temperature_unit) + return convert_temperature( + DEFAULT_MAX_TEMP, TEMP_CELSIUS, self.temperature_unit + ) @property def min_humidity(self) -> int: @@ -458,7 +503,7 @@ class ClimateDevice(Entity): async def async_service_aux_heat( - entity: ClimateDevice, service: ServiceDataType + entity: ClimateDevice, service: ServiceDataType ) -> None: """Handle aux heat service.""" if service.data[ATTR_AUX_HEAT]: @@ -468,7 +513,7 @@ async def async_service_aux_heat( async def async_service_temperature_set( - entity: ClimateDevice, service: ServiceDataType + entity: ClimateDevice, service: ServiceDataType ) -> None: """Handle set temperature service.""" hass = entity.hass @@ -477,9 +522,7 @@ async def async_service_temperature_set( for value, temp in service.data.items(): if value in CONVERTIBLE_ATTRIBUTE: kwargs[value] = convert_temperature( - temp, - hass.config.units.temperature_unit, - entity.temperature_unit + temp, hass.config.units.temperature_unit, entity.temperature_unit ) else: kwargs[value] = temp diff --git a/homeassistant/components/climate/const.py b/homeassistant/components/climate/const.py index 7b99442c2d7..ba13c03babd 100644 --- a/homeassistant/components/climate/const.py +++ b/homeassistant/components/climate/const.py @@ -1,26 +1,26 @@ """Provides the constants needed for component.""" # All activity disabled / Device is off/standby -HVAC_MODE_OFF = 'off' +HVAC_MODE_OFF = "off" # Heating -HVAC_MODE_HEAT = 'heat' +HVAC_MODE_HEAT = "heat" # Cooling -HVAC_MODE_COOL = 'cool' +HVAC_MODE_COOL = "cool" # The device supports heating/cooling to a range -HVAC_MODE_HEAT_COOL = 'heat_cool' +HVAC_MODE_HEAT_COOL = "heat_cool" # The temperature is set based on a schedule, learned behavior, AI or some # other related mechanism. User is not able to adjust the temperature -HVAC_MODE_AUTO = 'auto' +HVAC_MODE_AUTO = "auto" # Device is in Dry/Humidity mode -HVAC_MODE_DRY = 'dry' +HVAC_MODE_DRY = "dry" # Only the fan is on, not fan and another mode like cool -HVAC_MODE_FAN_ONLY = 'fan_only' +HVAC_MODE_FAN_ONLY = "fan_only" HVAC_MODES = [ HVAC_MODE_OFF, @@ -33,28 +33,28 @@ HVAC_MODES = [ ] # No preset is active -PRESET_NONE = 'none' +PRESET_NONE = "none" # Device is running an energy-saving mode -PRESET_ECO = 'eco' +PRESET_ECO = "eco" # Device is in away mode -PRESET_AWAY = 'away' +PRESET_AWAY = "away" # Device turn all valve full up -PRESET_BOOST = 'boost' +PRESET_BOOST = "boost" # Device is in comfort mode -PRESET_COMFORT = 'comfort' +PRESET_COMFORT = "comfort" # Device is in home mode -PRESET_HOME = 'home' +PRESET_HOME = "home" # Device is prepared for sleep -PRESET_SLEEP = 'sleep' +PRESET_SLEEP = "sleep" # Device is reacting to activity (e.g. movement sensors) -PRESET_ACTIVITY = 'activity' +PRESET_ACTIVITY = "activity" # Possible fan state @@ -77,49 +77,49 @@ SWING_HORIZONTAL = "horizontal" # This are support current states of HVAC -CURRENT_HVAC_OFF = 'off' -CURRENT_HVAC_HEAT = 'heating' -CURRENT_HVAC_COOL = 'cooling' -CURRENT_HVAC_DRY = 'drying' -CURRENT_HVAC_IDLE = 'idle' -CURRENT_HVAC_FAN = 'fan' +CURRENT_HVAC_OFF = "off" +CURRENT_HVAC_HEAT = "heating" +CURRENT_HVAC_COOL = "cooling" +CURRENT_HVAC_DRY = "drying" +CURRENT_HVAC_IDLE = "idle" +CURRENT_HVAC_FAN = "fan" -ATTR_AUX_HEAT = 'aux_heat' -ATTR_CURRENT_HUMIDITY = 'current_humidity' -ATTR_CURRENT_TEMPERATURE = 'current_temperature' -ATTR_FAN_MODES = 'fan_modes' -ATTR_FAN_MODE = 'fan_mode' -ATTR_PRESET_MODE = 'preset_mode' -ATTR_PRESET_MODES = 'preset_modes' -ATTR_HUMIDITY = 'humidity' -ATTR_MAX_HUMIDITY = 'max_humidity' -ATTR_MIN_HUMIDITY = 'min_humidity' -ATTR_MAX_TEMP = 'max_temp' -ATTR_MIN_TEMP = 'min_temp' -ATTR_HVAC_ACTIONS = 'hvac_action' -ATTR_HVAC_MODES = 'hvac_modes' -ATTR_HVAC_MODE = 'hvac_mode' -ATTR_SWING_MODES = 'swing_modes' -ATTR_SWING_MODE = 'swing_mode' -ATTR_TARGET_TEMP_HIGH = 'target_temp_high' -ATTR_TARGET_TEMP_LOW = 'target_temp_low' -ATTR_TARGET_TEMP_STEP = 'target_temp_step' +ATTR_AUX_HEAT = "aux_heat" +ATTR_CURRENT_HUMIDITY = "current_humidity" +ATTR_CURRENT_TEMPERATURE = "current_temperature" +ATTR_FAN_MODES = "fan_modes" +ATTR_FAN_MODE = "fan_mode" +ATTR_PRESET_MODE = "preset_mode" +ATTR_PRESET_MODES = "preset_modes" +ATTR_HUMIDITY = "humidity" +ATTR_MAX_HUMIDITY = "max_humidity" +ATTR_MIN_HUMIDITY = "min_humidity" +ATTR_MAX_TEMP = "max_temp" +ATTR_MIN_TEMP = "min_temp" +ATTR_HVAC_ACTIONS = "hvac_action" +ATTR_HVAC_MODES = "hvac_modes" +ATTR_HVAC_MODE = "hvac_mode" +ATTR_SWING_MODES = "swing_modes" +ATTR_SWING_MODE = "swing_mode" +ATTR_TARGET_TEMP_HIGH = "target_temp_high" +ATTR_TARGET_TEMP_LOW = "target_temp_low" +ATTR_TARGET_TEMP_STEP = "target_temp_step" DEFAULT_MIN_TEMP = 7 DEFAULT_MAX_TEMP = 35 DEFAULT_MIN_HUMITIDY = 30 DEFAULT_MAX_HUMIDITY = 99 -DOMAIN = 'climate' +DOMAIN = "climate" -SERVICE_SET_AUX_HEAT = 'set_aux_heat' -SERVICE_SET_FAN_MODE = 'set_fan_mode' -SERVICE_SET_PRESET_MODE = 'set_preset_mode' -SERVICE_SET_HUMIDITY = 'set_humidity' -SERVICE_SET_HVAC_MODE = 'set_hvac_mode' -SERVICE_SET_SWING_MODE = 'set_swing_mode' -SERVICE_SET_TEMPERATURE = 'set_temperature' +SERVICE_SET_AUX_HEAT = "set_aux_heat" +SERVICE_SET_FAN_MODE = "set_fan_mode" +SERVICE_SET_PRESET_MODE = "set_preset_mode" +SERVICE_SET_HUMIDITY = "set_humidity" +SERVICE_SET_HVAC_MODE = "set_hvac_mode" +SERVICE_SET_SWING_MODE = "set_swing_mode" +SERVICE_SET_TEMPERATURE = "set_temperature" SUPPORT_TARGET_TEMPERATURE = 1 SUPPORT_TARGET_TEMPERATURE_RANGE = 2 diff --git a/homeassistant/components/climate/reproduce_state.py b/homeassistant/components/climate/reproduce_state.py index c0f27477e0a..98f085c1e8d 100644 --- a/homeassistant/components/climate/reproduce_state.py +++ b/homeassistant/components/climate/reproduce_state.py @@ -26,36 +26,38 @@ from .const import ( ) -async def _async_reproduce_states(hass: HomeAssistantType, - state: State, - context: Optional[Context] = None) -> None: +async def _async_reproduce_states( + hass: HomeAssistantType, state: State, context: Optional[Context] = None +) -> None: """Reproduce component states.""" + async def call_service(service: str, keys: Iterable, data=None): """Call service with set of attributes given.""" data = data or {} - data['entity_id'] = state.entity_id + data["entity_id"] = state.entity_id for key in keys: if key in state.attributes: data[key] = state.attributes[key] await hass.services.async_call( - DOMAIN, service, data, - blocking=True, context=context) + DOMAIN, service, data, blocking=True, context=context + ) if state.state in HVAC_MODES: - await call_service( - SERVICE_SET_HVAC_MODE, [], {ATTR_HVAC_MODE: state.state}) + await call_service(SERVICE_SET_HVAC_MODE, [], {ATTR_HVAC_MODE: state.state}) if ATTR_AUX_HEAT in state.attributes: await call_service(SERVICE_SET_AUX_HEAT, [ATTR_AUX_HEAT]) - if (ATTR_TEMPERATURE in state.attributes) or \ - (ATTR_TARGET_TEMP_HIGH in state.attributes) or \ - (ATTR_TARGET_TEMP_LOW in state.attributes): - await call_service(SERVICE_SET_TEMPERATURE, - [ATTR_TEMPERATURE, - ATTR_TARGET_TEMP_HIGH, - ATTR_TARGET_TEMP_LOW]) + if ( + (ATTR_TEMPERATURE in state.attributes) + or (ATTR_TARGET_TEMP_HIGH in state.attributes) + or (ATTR_TARGET_TEMP_LOW in state.attributes) + ): + await call_service( + SERVICE_SET_TEMPERATURE, + [ATTR_TEMPERATURE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW], + ) if ATTR_PRESET_MODE in state.attributes: await call_service(SERVICE_SET_PRESET_MODE, [ATTR_PRESET_MODE]) @@ -68,10 +70,10 @@ async def _async_reproduce_states(hass: HomeAssistantType, @bind_hass -async def async_reproduce_states(hass: HomeAssistantType, - states: Iterable[State], - context: Optional[Context] = None) -> None: +async def async_reproduce_states( + hass: HomeAssistantType, states: Iterable[State], context: Optional[Context] = None +) -> None: """Reproduce component states.""" - await asyncio.gather(*( - _async_reproduce_states(hass, state, context) - for state in states)) + await asyncio.gather( + *(_async_reproduce_states(hass, state, context) for state in states) + ) diff --git a/homeassistant/components/cloud/__init__.py b/homeassistant/components/cloud/__init__.py index 3e17dd70841..8b295634c99 100644 --- a/homeassistant/components/cloud/__init__.py +++ b/homeassistant/components/cloud/__init__.py @@ -7,8 +7,12 @@ from homeassistant.auth.const import GROUP_ID_ADMIN from homeassistant.components.alexa import const as alexa_const from homeassistant.components.google_assistant import const as ga_c from homeassistant.const import ( - CONF_MODE, CONF_NAME, CONF_REGION, EVENT_HOMEASSISTANT_START, - EVENT_HOMEASSISTANT_STOP) + CONF_MODE, + CONF_NAME, + CONF_REGION, + EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP, +) from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv, entityfilter @@ -17,11 +21,23 @@ from homeassistant.util.aiohttp import MockRequest from . import http_api from .const import ( - CONF_ACME_DIRECTORY_SERVER, CONF_ALEXA, CONF_ALIASES, - CONF_CLOUDHOOK_CREATE_URL, CONF_COGNITO_CLIENT_ID, CONF_ENTITY_CONFIG, - CONF_FILTER, CONF_GOOGLE_ACTIONS, CONF_GOOGLE_ACTIONS_SYNC_URL, - CONF_RELAYER, CONF_REMOTE_API_URL, CONF_SUBSCRIPTION_INFO_URL, - CONF_USER_POOL_ID, DOMAIN, MODE_DEV, MODE_PROD, CONF_ALEXA_ACCESS_TOKEN_URL + CONF_ACME_DIRECTORY_SERVER, + CONF_ALEXA, + CONF_ALIASES, + CONF_CLOUDHOOK_CREATE_URL, + CONF_COGNITO_CLIENT_ID, + CONF_ENTITY_CONFIG, + CONF_FILTER, + CONF_GOOGLE_ACTIONS, + CONF_GOOGLE_ACTIONS_SYNC_URL, + CONF_RELAYER, + CONF_REMOTE_API_URL, + CONF_SUBSCRIPTION_INFO_URL, + CONF_USER_POOL_ID, + DOMAIN, + MODE_DEV, + MODE_PROD, + CONF_ALEXA_ACCESS_TOKEN_URL, ) from .prefs import CloudPreferences @@ -29,53 +45,63 @@ _LOGGER = logging.getLogger(__name__) DEFAULT_MODE = MODE_PROD -SERVICE_REMOTE_CONNECT = 'remote_connect' -SERVICE_REMOTE_DISCONNECT = 'remote_disconnect' +SERVICE_REMOTE_CONNECT = "remote_connect" +SERVICE_REMOTE_DISCONNECT = "remote_disconnect" -ALEXA_ENTITY_SCHEMA = vol.Schema({ - vol.Optional(alexa_const.CONF_DESCRIPTION): cv.string, - vol.Optional(alexa_const.CONF_DISPLAY_CATEGORIES): cv.string, - vol.Optional(CONF_NAME): cv.string, -}) +ALEXA_ENTITY_SCHEMA = vol.Schema( + { + vol.Optional(alexa_const.CONF_DESCRIPTION): cv.string, + vol.Optional(alexa_const.CONF_DISPLAY_CATEGORIES): cv.string, + vol.Optional(CONF_NAME): cv.string, + } +) -GOOGLE_ENTITY_SCHEMA = vol.Schema({ - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_ALIASES): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(ga_c.CONF_ROOM_HINT): cv.string, -}) +GOOGLE_ENTITY_SCHEMA = vol.Schema( + { + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_ALIASES): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(ga_c.CONF_ROOM_HINT): cv.string, + } +) -ASSISTANT_SCHEMA = vol.Schema({ - vol.Optional(CONF_FILTER, default=dict): entityfilter.FILTER_SCHEMA, -}) +ASSISTANT_SCHEMA = vol.Schema( + {vol.Optional(CONF_FILTER, default=dict): entityfilter.FILTER_SCHEMA} +) -ALEXA_SCHEMA = ASSISTANT_SCHEMA.extend({ - vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: ALEXA_ENTITY_SCHEMA} -}) +ALEXA_SCHEMA = ASSISTANT_SCHEMA.extend( + {vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: ALEXA_ENTITY_SCHEMA}} +) -GACTIONS_SCHEMA = ASSISTANT_SCHEMA.extend({ - vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: GOOGLE_ENTITY_SCHEMA}, -}) +GACTIONS_SCHEMA = ASSISTANT_SCHEMA.extend( + {vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: GOOGLE_ENTITY_SCHEMA}} +) # pylint: disable=no-value-for-parameter -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Optional(CONF_MODE, default=DEFAULT_MODE): - vol.In([MODE_DEV, MODE_PROD]), - vol.Optional(CONF_COGNITO_CLIENT_ID): str, - vol.Optional(CONF_USER_POOL_ID): str, - vol.Optional(CONF_REGION): str, - vol.Optional(CONF_RELAYER): str, - vol.Optional(CONF_GOOGLE_ACTIONS_SYNC_URL): vol.Url(), - vol.Optional(CONF_SUBSCRIPTION_INFO_URL): vol.Url(), - vol.Optional(CONF_CLOUDHOOK_CREATE_URL): vol.Url(), - vol.Optional(CONF_REMOTE_API_URL): vol.Url(), - vol.Optional(CONF_ACME_DIRECTORY_SERVER): vol.Url(), - vol.Optional(CONF_ALEXA): ALEXA_SCHEMA, - vol.Optional(CONF_GOOGLE_ACTIONS): GACTIONS_SCHEMA, - vol.Optional(CONF_ALEXA_ACCESS_TOKEN_URL): str, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Optional(CONF_MODE, default=DEFAULT_MODE): vol.In( + [MODE_DEV, MODE_PROD] + ), + vol.Optional(CONF_COGNITO_CLIENT_ID): str, + vol.Optional(CONF_USER_POOL_ID): str, + vol.Optional(CONF_REGION): str, + vol.Optional(CONF_RELAYER): str, + vol.Optional(CONF_GOOGLE_ACTIONS_SYNC_URL): vol.Url(), + vol.Optional(CONF_SUBSCRIPTION_INFO_URL): vol.Url(), + vol.Optional(CONF_CLOUDHOOK_CREATE_URL): vol.Url(), + vol.Optional(CONF_REMOTE_API_URL): vol.Url(), + vol.Optional(CONF_ACME_DIRECTORY_SERVER): vol.Url(), + vol.Optional(CONF_ALEXA): ALEXA_SCHEMA, + vol.Optional(CONF_GOOGLE_ACTIONS): GACTIONS_SCHEMA, + vol.Optional(CONF_ALEXA_ACCESS_TOKEN_URL): str, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) class CloudNotAvailable(HomeAssistantError): @@ -93,8 +119,7 @@ def async_is_logged_in(hass) -> bool: @callback def async_active_subscription(hass) -> bool: """Test if user has an active subscription.""" - return \ - async_is_logged_in(hass) and not hass.data[DOMAIN].subscription_expired + return async_is_logged_in(hass) and not hass.data[DOMAIN].subscription_expired @bind_hass @@ -104,7 +129,7 @@ async def async_create_cloudhook(hass, webhook_id: str) -> str: raise CloudNotAvailable hook = await hass.data[DOMAIN].cloudhooks.async_create(webhook_id, True) - return hook['cloudhook_url'] + return hook["cloudhook_url"] @bind_hass @@ -165,7 +190,8 @@ async def async_setup(hass, config): if user is None: user = await hass.auth.async_create_system_user( - 'Home Assistant Cloud', [GROUP_ID_ADMIN]) + "Home Assistant Cloud", [GROUP_ID_ADMIN] + ) await prefs.async_update(cloud_user=user.id) # Initialize Cloud @@ -195,9 +221,11 @@ async def async_setup(hass, config): await prefs.async_update(remote_enabled=False) hass.helpers.service.async_register_admin_service( - DOMAIN, SERVICE_REMOTE_CONNECT, _service_handler) + DOMAIN, SERVICE_REMOTE_CONNECT, _service_handler + ) hass.helpers.service.async_register_admin_service( - DOMAIN, SERVICE_REMOTE_DISCONNECT, _service_handler) + DOMAIN, SERVICE_REMOTE_DISCONNECT, _service_handler + ) loaded_binary_sensor = False @@ -209,8 +237,11 @@ async def async_setup(hass, config): return loaded_binary_sensor = True - hass.async_create_task(hass.helpers.discovery.async_load_platform( - 'binary_sensor', DOMAIN, {}, config)) + hass.async_create_task( + hass.helpers.discovery.async_load_platform( + "binary_sensor", DOMAIN, {}, config + ) + ) cloud.iot.register_on_connect(_on_connect) diff --git a/homeassistant/components/cloud/alexa_config.py b/homeassistant/components/cloud/alexa_config.py index a6aced474d6..d31bcfdfc40 100644 --- a/homeassistant/components/cloud/alexa_config.py +++ b/homeassistant/components/cloud/alexa_config.py @@ -20,8 +20,11 @@ from homeassistant.components.alexa import ( from .const import ( - CONF_ENTITY_CONFIG, CONF_FILTER, PREF_SHOULD_EXPOSE, DEFAULT_SHOULD_EXPOSE, - RequireRelink + CONF_ENTITY_CONFIG, + CONF_FILTER, + PREF_SHOULD_EXPOSE, + DEFAULT_SHOULD_EXPOSE, + RequireRelink, ) _LOGGER = logging.getLogger(__name__) @@ -49,7 +52,7 @@ class AlexaConfig(alexa_config.AbstractConfig): prefs.async_listen_updates(self._async_prefs_updated) hass.bus.async_listen( entity_registry.EVENT_ENTITY_REGISTRY_UPDATED, - self._handle_entity_registry_updated + self._handle_entity_registry_updated, ) @property @@ -90,8 +93,7 @@ class AlexaConfig(alexa_config.AbstractConfig): entity_configs = self._prefs.alexa_entity_configs entity_config = entity_configs.get(entity_id, {}) - return entity_config.get( - PREF_SHOULD_EXPOSE, DEFAULT_SHOULD_EXPOSE) + return entity_config.get(PREF_SHOULD_EXPOSE, DEFAULT_SHOULD_EXPOSE) async def async_get_access_token(self): """Get an access token.""" @@ -102,13 +104,13 @@ class AlexaConfig(alexa_config.AbstractConfig): body = await resp.json() if resp.status == 400: - if body['reason'] in ('RefreshTokenNotFound', 'UnknownRegion'): + if body["reason"] in ("RefreshTokenNotFound", "UnknownRegion"): if self.should_report_state: await self._prefs.async_update(alexa_report_state=False) self.hass.components.persistent_notification.async_create( "There was an error reporting state to Alexa ({}). " "Please re-link your Alexa skill via the Alexa app to " - "continue using it.".format(body['reason']), + "continue using it.".format(body["reason"]), "Alexa state reporting disabled", "cloud_alexa_report", ) @@ -116,9 +118,9 @@ class AlexaConfig(alexa_config.AbstractConfig): raise alexa_errors.NoTokenAvailable - self._token = body['access_token'] - self._endpoint = body['event_endpoint'] - self._token_valid = utcnow() + timedelta(seconds=body['expires_in']) + self._token = body["access_token"] + self._endpoint = body["event_endpoint"] + self._token_valid = utcnow() + timedelta(seconds=body["expires_in"]) return self._token async def _async_prefs_updated(self, prefs): @@ -136,15 +138,18 @@ class AlexaConfig(alexa_config.AbstractConfig): # If entity prefs are the same or we have filter in config.yaml, # don't sync. - if (self._cur_entity_prefs is prefs.alexa_entity_configs or - not self._config[CONF_FILTER].empty_filter): + if ( + self._cur_entity_prefs is prefs.alexa_entity_configs + or not self._config[CONF_FILTER].empty_filter + ): return if self._alexa_sync_unsub: self._alexa_sync_unsub() self._alexa_sync_unsub = async_call_later( - self.hass, SYNC_DELAY, self._sync_prefs) + self.hass, SYNC_DELAY, self._sync_prefs + ) async def _sync_prefs(self, _now): """Sync the updated preferences to Alexa.""" @@ -225,14 +230,16 @@ class AlexaConfig(alexa_config.AbstractConfig): tasks = [] if to_update: - tasks.append(alexa_state_report.async_send_add_or_update_message( - self.hass, self, to_update - )) + tasks.append( + alexa_state_report.async_send_add_or_update_message( + self.hass, self, to_update + ) + ) if to_remove: - tasks.append(alexa_state_report.async_send_delete_message( - self.hass, self, to_remove - )) + tasks.append( + alexa_state_report.async_send_delete_message(self.hass, self, to_remove) + ) try: with async_timeout.timeout(10): @@ -253,14 +260,14 @@ class AlexaConfig(alexa_config.AbstractConfig): if not self.enabled or not self._cloud.is_logged_in: return - action = event.data['action'] - entity_id = event.data['entity_id'] + action = event.data["action"] + entity_id = event.data["entity_id"] to_update = [] to_remove = [] - if action == 'create' and self.should_expose(entity_id): + if action == "create" and self.should_expose(entity_id): to_update.append(entity_id) - elif action == 'remove' and self.should_expose(entity_id): + elif action == "remove" and self.should_expose(entity_id): to_remove.append(entity_id) try: diff --git a/homeassistant/components/cloud/binary_sensor.py b/homeassistant/components/cloud/binary_sensor.py index 3e4aaf9cc84..2192eec8923 100644 --- a/homeassistant/components/cloud/binary_sensor.py +++ b/homeassistant/components/cloud/binary_sensor.py @@ -10,8 +10,7 @@ from .const import DISPATCHER_REMOTE_UPDATE, DOMAIN WAIT_UNTIL_CHANGE = 3 -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the cloud binary sensors.""" if discovery_info is None: return @@ -46,7 +45,7 @@ class CloudRemoteBinary(BinarySensorDevice): @property def device_class(self) -> str: """Return the class of this device, from component DEVICE_CLASSES.""" - return 'connectivity' + return "connectivity" @property def available(self) -> bool: @@ -60,13 +59,15 @@ class CloudRemoteBinary(BinarySensorDevice): async def async_added_to_hass(self): """Register update dispatcher.""" + async def async_state_update(data): """Update callback.""" await asyncio.sleep(WAIT_UNTIL_CHANGE) self.async_schedule_update_ha_state() self._unsub_dispatcher = async_dispatcher_connect( - self.hass, DISPATCHER_REMOTE_UPDATE, async_state_update) + self.hass, DISPATCHER_REMOTE_UPDATE, async_state_update + ) async def async_will_remove_from_hass(self): """Register update dispatcher.""" diff --git a/homeassistant/components/cloud/client.py b/homeassistant/components/cloud/client.py index d22e5bf37ba..07882d8dac2 100644 --- a/homeassistant/components/cloud/client.py +++ b/homeassistant/components/cloud/client.py @@ -28,10 +28,14 @@ _LOGGER = logging.getLogger(__name__) class CloudClient(Interface): """Interface class for Home Assistant Cloud.""" - def __init__(self, hass: HomeAssistantType, prefs: CloudPreferences, - websession: aiohttp.ClientSession, - alexa_user_config: Dict[str, Any], - google_user_config: Dict[str, Any]): + def __init__( + self, + hass: HomeAssistantType, + prefs: CloudPreferences, + websession: aiohttp.ClientSession, + alexa_user_config: Dict[str, Any], + google_user_config: Dict[str, Any], + ): """Initialize client interface to Cloud.""" self._hass = hass self._prefs = prefs @@ -83,7 +87,8 @@ class CloudClient(Interface): if self._alexa_config is None: assert self.cloud is not None self._alexa_config = alexa_config.AlexaConfig( - self._hass, self.alexa_user_config, self._prefs, self.cloud) + self._hass, self.alexa_user_config, self._prefs, self.cloud + ) return self._alexa_config @@ -93,7 +98,8 @@ class CloudClient(Interface): if not self._google_config: assert self.cloud is not None self._google_config = google_config.CloudGoogleConfig( - self.google_user_config, self._prefs, self.cloud) + self.google_user_config, self._prefs, self.cloud + ) return self._google_config @@ -101,8 +107,7 @@ class CloudClient(Interface): """Initialize the client.""" self.cloud = cloud - if (not self.alexa_config.should_report_state or - not self.cloud.is_logged_in): + if not self.alexa_config.should_report_state or not self.cloud.is_logged_in: return try: @@ -127,16 +132,13 @@ class CloudClient(Interface): if identifier.startswith("remote_"): async_dispatcher_send(self._hass, DISPATCHER_REMOTE_UPDATE, data) - async def async_alexa_message( - self, payload: Dict[Any, Any]) -> Dict[Any, Any]: + async def async_alexa_message(self, payload: Dict[Any, Any]) -> Dict[Any, Any]: """Process cloud alexa message to client.""" return await alexa_sh.async_handle_message( - self._hass, self.alexa_config, payload, - enabled=self._prefs.alexa_enabled + self._hass, self.alexa_config, payload, enabled=self._prefs.alexa_enabled ) - async def async_google_message( - self, payload: Dict[Any, Any]) -> Dict[Any, Any]: + async def async_google_message(self, payload: Dict[Any, Any]) -> Dict[Any, Any]: """Process cloud google message to client.""" if not self._prefs.google_enabled: return ga.turned_off_response(payload) @@ -145,44 +147,39 @@ class CloudClient(Interface): self._hass, self.google_config, self.prefs.cloud_user, payload ) - async def async_webhook_message( - self, payload: Dict[Any, Any]) -> Dict[Any, Any]: + async def async_webhook_message(self, payload: Dict[Any, Any]) -> Dict[Any, Any]: """Process cloud webhook message to client.""" - cloudhook_id = payload['cloudhook_id'] + cloudhook_id = payload["cloudhook_id"] found = None for cloudhook in self._prefs.cloudhooks.values(): - if cloudhook['cloudhook_id'] == cloudhook_id: + if cloudhook["cloudhook_id"] == cloudhook_id: found = cloudhook break if found is None: - return { - 'status': 200 - } + return {"status": 200} request = MockRequest( - content=payload['body'].encode('utf-8'), - headers=payload['headers'], - method=payload['method'], - query_string=payload['query'], + content=payload["body"].encode("utf-8"), + headers=payload["headers"], + method=payload["method"], + query_string=payload["query"], ) response = await self._hass.components.webhook.async_handle_webhook( - found['webhook_id'], request) + found["webhook_id"], request + ) response_dict = utils.aiohttp_serialize_response(response) - body = response_dict.get('body') + body = response_dict.get("body") return { - 'body': body, - 'status': response_dict['status'], - 'headers': { - 'Content-Type': response.content_type - } + "body": body, + "status": response_dict["status"], + "headers": {"Content-Type": response.content_type}, } - async def async_cloudhooks_update( - self, data: Dict[str, Dict[str, str]]) -> None: + async def async_cloudhooks_update(self, data: Dict[str, Dict[str, str]]) -> None: """Update local list of cloudhooks.""" await self._prefs.async_update(cloudhooks=data) diff --git a/homeassistant/components/cloud/const.py b/homeassistant/components/cloud/const.py index fdb36723fdb..df1b8ef165d 100644 --- a/homeassistant/components/cloud/const.py +++ b/homeassistant/components/cloud/const.py @@ -1,43 +1,43 @@ """Constants for the cloud component.""" -DOMAIN = 'cloud' +DOMAIN = "cloud" REQUEST_TIMEOUT = 10 -PREF_ENABLE_ALEXA = 'alexa_enabled' -PREF_ENABLE_GOOGLE = 'google_enabled' -PREF_ENABLE_REMOTE = 'remote_enabled' -PREF_GOOGLE_SECURE_DEVICES_PIN = 'google_secure_devices_pin' -PREF_CLOUDHOOKS = 'cloudhooks' -PREF_CLOUD_USER = 'cloud_user' -PREF_GOOGLE_ENTITY_CONFIGS = 'google_entity_configs' -PREF_ALEXA_ENTITY_CONFIGS = 'alexa_entity_configs' -PREF_ALEXA_REPORT_STATE = 'alexa_report_state' -PREF_OVERRIDE_NAME = 'override_name' -PREF_DISABLE_2FA = 'disable_2fa' -PREF_ALIASES = 'aliases' -PREF_SHOULD_EXPOSE = 'should_expose' +PREF_ENABLE_ALEXA = "alexa_enabled" +PREF_ENABLE_GOOGLE = "google_enabled" +PREF_ENABLE_REMOTE = "remote_enabled" +PREF_GOOGLE_SECURE_DEVICES_PIN = "google_secure_devices_pin" +PREF_CLOUDHOOKS = "cloudhooks" +PREF_CLOUD_USER = "cloud_user" +PREF_GOOGLE_ENTITY_CONFIGS = "google_entity_configs" +PREF_ALEXA_ENTITY_CONFIGS = "alexa_entity_configs" +PREF_ALEXA_REPORT_STATE = "alexa_report_state" +PREF_OVERRIDE_NAME = "override_name" +PREF_DISABLE_2FA = "disable_2fa" +PREF_ALIASES = "aliases" +PREF_SHOULD_EXPOSE = "should_expose" DEFAULT_SHOULD_EXPOSE = True DEFAULT_DISABLE_2FA = False DEFAULT_ALEXA_REPORT_STATE = False -CONF_ALEXA = 'alexa' -CONF_ALIASES = 'aliases' -CONF_COGNITO_CLIENT_ID = 'cognito_client_id' -CONF_ENTITY_CONFIG = 'entity_config' -CONF_FILTER = 'filter' -CONF_GOOGLE_ACTIONS = 'google_actions' -CONF_RELAYER = 'relayer' -CONF_USER_POOL_ID = 'user_pool_id' -CONF_GOOGLE_ACTIONS_SYNC_URL = 'google_actions_sync_url' -CONF_SUBSCRIPTION_INFO_URL = 'subscription_info_url' -CONF_CLOUDHOOK_CREATE_URL = 'cloudhook_create_url' -CONF_REMOTE_API_URL = 'remote_api_url' -CONF_ACME_DIRECTORY_SERVER = 'acme_directory_server' -CONF_ALEXA_ACCESS_TOKEN_URL = 'alexa_access_token_url' +CONF_ALEXA = "alexa" +CONF_ALIASES = "aliases" +CONF_COGNITO_CLIENT_ID = "cognito_client_id" +CONF_ENTITY_CONFIG = "entity_config" +CONF_FILTER = "filter" +CONF_GOOGLE_ACTIONS = "google_actions" +CONF_RELAYER = "relayer" +CONF_USER_POOL_ID = "user_pool_id" +CONF_GOOGLE_ACTIONS_SYNC_URL = "google_actions_sync_url" +CONF_SUBSCRIPTION_INFO_URL = "subscription_info_url" +CONF_CLOUDHOOK_CREATE_URL = "cloudhook_create_url" +CONF_REMOTE_API_URL = "remote_api_url" +CONF_ACME_DIRECTORY_SERVER = "acme_directory_server" +CONF_ALEXA_ACCESS_TOKEN_URL = "alexa_access_token_url" MODE_DEV = "development" MODE_PROD = "production" -DISPATCHER_REMOTE_UPDATE = 'cloud_remote_update' +DISPATCHER_REMOTE_UPDATE = "cloud_remote_update" class InvalidTrustedNetworks(Exception): diff --git a/homeassistant/components/cloud/google_config.py b/homeassistant/components/cloud/google_config.py index 5e95417cd33..8986f8f3995 100644 --- a/homeassistant/components/cloud/google_config.py +++ b/homeassistant/components/cloud/google_config.py @@ -3,8 +3,12 @@ from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES from homeassistant.components.google_assistant.helpers import AbstractConfig from .const import ( - PREF_SHOULD_EXPOSE, DEFAULT_SHOULD_EXPOSE, CONF_ENTITY_CONFIG, - PREF_DISABLE_2FA, DEFAULT_DISABLE_2FA) + PREF_SHOULD_EXPOSE, + DEFAULT_SHOULD_EXPOSE, + CONF_ENTITY_CONFIG, + PREF_DISABLE_2FA, + DEFAULT_DISABLE_2FA, +) class CloudGoogleConfig(AbstractConfig): @@ -36,17 +40,15 @@ class CloudGoogleConfig(AbstractConfig): if state.entity_id in CLOUD_NEVER_EXPOSED_ENTITIES: return False - if not self._config['filter'].empty_filter: - return self._config['filter'](state.entity_id) + if not self._config["filter"].empty_filter: + return self._config["filter"](state.entity_id) entity_configs = self._prefs.google_entity_configs entity_config = entity_configs.get(state.entity_id, {}) - return entity_config.get( - PREF_SHOULD_EXPOSE, DEFAULT_SHOULD_EXPOSE) + return entity_config.get(PREF_SHOULD_EXPOSE, DEFAULT_SHOULD_EXPOSE) def should_2fa(self, state): """If an entity should be checked for 2FA.""" entity_configs = self._prefs.google_entity_configs entity_config = entity_configs.get(state.entity_id, {}) - return not entity_config.get( - PREF_DISABLE_2FA, DEFAULT_DISABLE_2FA) + return not entity_config.get(PREF_DISABLE_2FA, DEFAULT_DISABLE_2FA) diff --git a/homeassistant/components/cloud/http_api.py b/homeassistant/components/cloud/http_api.py index e5f00873aab..c22cb3934d9 100644 --- a/homeassistant/components/cloud/http_api.py +++ b/homeassistant/components/cloud/http_api.py @@ -10,8 +10,7 @@ import voluptuous as vol from homeassistant.core import callback from homeassistant.components.http import HomeAssistantView -from homeassistant.components.http.data_validator import ( - RequestDataValidator) +from homeassistant.components.http.data_validator import RequestDataValidator from homeassistant.components import websocket_api from homeassistant.components.websocket_api import const as ws_const from homeassistant.components.alexa import ( @@ -21,78 +20,76 @@ from homeassistant.components.alexa import ( from homeassistant.components.google_assistant import helpers as google_helpers from .const import ( - DOMAIN, REQUEST_TIMEOUT, PREF_ENABLE_ALEXA, PREF_ENABLE_GOOGLE, - PREF_GOOGLE_SECURE_DEVICES_PIN, InvalidTrustedNetworks, - InvalidTrustedProxies, PREF_ALEXA_REPORT_STATE, RequireRelink) + DOMAIN, + REQUEST_TIMEOUT, + PREF_ENABLE_ALEXA, + PREF_ENABLE_GOOGLE, + PREF_GOOGLE_SECURE_DEVICES_PIN, + InvalidTrustedNetworks, + InvalidTrustedProxies, + PREF_ALEXA_REPORT_STATE, + RequireRelink, +) _LOGGER = logging.getLogger(__name__) -WS_TYPE_STATUS = 'cloud/status' -SCHEMA_WS_STATUS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_STATUS, -}) +WS_TYPE_STATUS = "cloud/status" +SCHEMA_WS_STATUS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {vol.Required("type"): WS_TYPE_STATUS} +) -WS_TYPE_SUBSCRIPTION = 'cloud/subscription' -SCHEMA_WS_SUBSCRIPTION = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_SUBSCRIPTION, -}) +WS_TYPE_SUBSCRIPTION = "cloud/subscription" +SCHEMA_WS_SUBSCRIPTION = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {vol.Required("type"): WS_TYPE_SUBSCRIPTION} +) -WS_TYPE_HOOK_CREATE = 'cloud/cloudhook/create' -SCHEMA_WS_HOOK_CREATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_HOOK_CREATE, - vol.Required('webhook_id'): str -}) +WS_TYPE_HOOK_CREATE = "cloud/cloudhook/create" +SCHEMA_WS_HOOK_CREATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {vol.Required("type"): WS_TYPE_HOOK_CREATE, vol.Required("webhook_id"): str} +) -WS_TYPE_HOOK_DELETE = 'cloud/cloudhook/delete' -SCHEMA_WS_HOOK_DELETE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_HOOK_DELETE, - vol.Required('webhook_id'): str -}) +WS_TYPE_HOOK_DELETE = "cloud/cloudhook/delete" +SCHEMA_WS_HOOK_DELETE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {vol.Required("type"): WS_TYPE_HOOK_DELETE, vol.Required("webhook_id"): str} +) _CLOUD_ERRORS = { - InvalidTrustedNetworks: - (500, 'Remote UI not compatible with 127.0.0.1/::1' - ' as a trusted network.'), - InvalidTrustedProxies: - (500, 'Remote UI not compatible with 127.0.0.1/::1' - ' as trusted proxies.'), + InvalidTrustedNetworks: ( + 500, + "Remote UI not compatible with 127.0.0.1/::1" " as a trusted network.", + ), + InvalidTrustedProxies: ( + 500, + "Remote UI not compatible with 127.0.0.1/::1" " as trusted proxies.", + ), } async def async_setup(hass): """Initialize the HTTP API.""" hass.components.websocket_api.async_register_command( - WS_TYPE_STATUS, websocket_cloud_status, - SCHEMA_WS_STATUS + WS_TYPE_STATUS, websocket_cloud_status, SCHEMA_WS_STATUS ) hass.components.websocket_api.async_register_command( - WS_TYPE_SUBSCRIPTION, websocket_subscription, - SCHEMA_WS_SUBSCRIPTION + WS_TYPE_SUBSCRIPTION, websocket_subscription, SCHEMA_WS_SUBSCRIPTION + ) + hass.components.websocket_api.async_register_command(websocket_update_prefs) + hass.components.websocket_api.async_register_command( + WS_TYPE_HOOK_CREATE, websocket_hook_create, SCHEMA_WS_HOOK_CREATE ) hass.components.websocket_api.async_register_command( - websocket_update_prefs) - hass.components.websocket_api.async_register_command( - WS_TYPE_HOOK_CREATE, websocket_hook_create, - SCHEMA_WS_HOOK_CREATE + WS_TYPE_HOOK_DELETE, websocket_hook_delete, SCHEMA_WS_HOOK_DELETE ) - hass.components.websocket_api.async_register_command( - WS_TYPE_HOOK_DELETE, websocket_hook_delete, - SCHEMA_WS_HOOK_DELETE - ) - hass.components.websocket_api.async_register_command( - websocket_remote_connect) - hass.components.websocket_api.async_register_command( - websocket_remote_disconnect) + hass.components.websocket_api.async_register_command(websocket_remote_connect) + hass.components.websocket_api.async_register_command(websocket_remote_disconnect) - hass.components.websocket_api.async_register_command( - google_assistant_list) - hass.components.websocket_api.async_register_command( - google_assistant_update) + hass.components.websocket_api.async_register_command(google_assistant_list) + hass.components.websocket_api.async_register_command(google_assistant_update) hass.components.websocket_api.async_register_command(alexa_list) hass.components.websocket_api.async_register_command(alexa_update) @@ -107,26 +104,22 @@ async def async_setup(hass): from hass_nabucasa import auth - _CLOUD_ERRORS.update({ - auth.UserNotFound: - (400, "User does not exist."), - auth.UserNotConfirmed: - (400, 'Email not confirmed.'), - auth.UserExists: - (400, 'An account with the given email already exists.'), - auth.Unauthenticated: - (401, 'Authentication failed.'), - auth.PasswordChangeRequired: - (400, 'Password change required.'), - asyncio.TimeoutError: - (502, 'Unable to reach the Home Assistant cloud.'), - aiohttp.ClientError: - (500, 'Error making internal request'), - }) + _CLOUD_ERRORS.update( + { + auth.UserNotFound: (400, "User does not exist."), + auth.UserNotConfirmed: (400, "Email not confirmed."), + auth.UserExists: (400, "An account with the given email already exists."), + auth.Unauthenticated: (401, "Authentication failed."), + auth.PasswordChangeRequired: (400, "Password change required."), + asyncio.TimeoutError: (502, "Unable to reach the Home Assistant cloud."), + aiohttp.ClientError: (500, "Error making internal request"), + } + ) def _handle_cloud_errors(handler): """Webview decorator to handle auth errors.""" + @wraps(handler) async def error_handler(view, request, *args, **kwargs): """Handle exceptions that raise from the wrapped request handler.""" @@ -137,14 +130,15 @@ def _handle_cloud_errors(handler): except Exception as err: # pylint: disable=broad-except status, msg = _process_cloud_exception(err, request.path) return view.json_message( - msg, status_code=status, - message_code=err.__class__.__name__.lower()) + msg, status_code=status, message_code=err.__class__.__name__.lower() + ) return error_handler def _ws_handle_cloud_errors(handler): """Websocket decorator to handle auth errors.""" + @wraps(handler) async def error_handler(hass, connection, msg): """Handle exceptions that raise from the wrapped handler.""" @@ -152,8 +146,8 @@ def _ws_handle_cloud_errors(handler): return await handler(hass, connection, msg) except Exception as err: # pylint: disable=broad-except - err_status, err_msg = _process_cloud_exception(err, msg['type']) - connection.send_error(msg['id'], err_status, err_msg) + err_status, err_msg = _process_cloud_exception(err, msg["type"]) + connection.send_error(msg["id"], err_status, err_msg) return error_handler @@ -162,22 +156,21 @@ def _process_cloud_exception(exc, where): """Process a cloud exception.""" err_info = _CLOUD_ERRORS.get(exc.__class__) if err_info is None: - _LOGGER.exception( - "Unexpected error processing request for %s", where) - err_info = (502, 'Unexpected error: {}'.format(exc)) + _LOGGER.exception("Unexpected error processing request for %s", where) + err_info = (502, "Unexpected error: {}".format(exc)) return err_info class GoogleActionsSyncView(HomeAssistantView): """Trigger a Google Actions Smart Home Sync.""" - url = '/api/cloud/google_actions/sync' - name = 'api:cloud:google_actions/sync' + url = "/api/cloud/google_actions/sync" + name = "api:cloud:google_actions/sync" @_handle_cloud_errors async def post(self, request): """Trigger a Google Actions sync.""" - hass = request.app['hass'] + hass = request.app["hass"] cloud = hass.data[DOMAIN] websession = hass.helpers.aiohttp_client.async_get_clientsession() @@ -186,9 +179,8 @@ class GoogleActionsSyncView(HomeAssistantView): with async_timeout.timeout(REQUEST_TIMEOUT): req = await websession.post( - cloud.google_actions_sync_url, headers={ - 'authorization': cloud.id_token - }) + cloud.google_actions_sync_url, headers={"authorization": cloud.id_token} + ) return self.json({}, status_code=req.status) @@ -196,110 +188,107 @@ class GoogleActionsSyncView(HomeAssistantView): class CloudLoginView(HomeAssistantView): """Login to Home Assistant cloud.""" - url = '/api/cloud/login' - name = 'api:cloud:login' + url = "/api/cloud/login" + name = "api:cloud:login" @_handle_cloud_errors - @RequestDataValidator(vol.Schema({ - vol.Required('email'): str, - vol.Required('password'): str, - })) + @RequestDataValidator( + vol.Schema({vol.Required("email"): str, vol.Required("password"): str}) + ) async def post(self, request, data): """Handle login request.""" - hass = request.app['hass'] + hass = request.app["hass"] cloud = hass.data[DOMAIN] with async_timeout.timeout(REQUEST_TIMEOUT): - await hass.async_add_job(cloud.auth.login, data['email'], - data['password']) + await hass.async_add_job(cloud.auth.login, data["email"], data["password"]) hass.async_add_job(cloud.iot.connect) - return self.json({'success': True}) + return self.json({"success": True}) class CloudLogoutView(HomeAssistantView): """Log out of the Home Assistant cloud.""" - url = '/api/cloud/logout' - name = 'api:cloud:logout' + url = "/api/cloud/logout" + name = "api:cloud:logout" @_handle_cloud_errors async def post(self, request): """Handle logout request.""" - hass = request.app['hass'] + hass = request.app["hass"] cloud = hass.data[DOMAIN] with async_timeout.timeout(REQUEST_TIMEOUT): await cloud.logout() - return self.json_message('ok') + return self.json_message("ok") class CloudRegisterView(HomeAssistantView): """Register on the Home Assistant cloud.""" - url = '/api/cloud/register' - name = 'api:cloud:register' + url = "/api/cloud/register" + name = "api:cloud:register" @_handle_cloud_errors - @RequestDataValidator(vol.Schema({ - vol.Required('email'): str, - vol.Required('password'): vol.All(str, vol.Length(min=6)), - })) + @RequestDataValidator( + vol.Schema( + { + vol.Required("email"): str, + vol.Required("password"): vol.All(str, vol.Length(min=6)), + } + ) + ) async def post(self, request, data): """Handle registration request.""" - hass = request.app['hass'] + hass = request.app["hass"] cloud = hass.data[DOMAIN] with async_timeout.timeout(REQUEST_TIMEOUT): await hass.async_add_job( - cloud.auth.register, data['email'], data['password']) + cloud.auth.register, data["email"], data["password"] + ) - return self.json_message('ok') + return self.json_message("ok") class CloudResendConfirmView(HomeAssistantView): """Resend email confirmation code.""" - url = '/api/cloud/resend_confirm' - name = 'api:cloud:resend_confirm' + url = "/api/cloud/resend_confirm" + name = "api:cloud:resend_confirm" @_handle_cloud_errors - @RequestDataValidator(vol.Schema({ - vol.Required('email'): str, - })) + @RequestDataValidator(vol.Schema({vol.Required("email"): str})) async def post(self, request, data): """Handle resending confirm email code request.""" - hass = request.app['hass'] + hass = request.app["hass"] cloud = hass.data[DOMAIN] with async_timeout.timeout(REQUEST_TIMEOUT): - await hass.async_add_job( - cloud.auth.resend_email_confirm, data['email']) + await hass.async_add_job(cloud.auth.resend_email_confirm, data["email"]) - return self.json_message('ok') + return self.json_message("ok") class CloudForgotPasswordView(HomeAssistantView): """View to start Forgot Password flow..""" - url = '/api/cloud/forgot_password' - name = 'api:cloud:forgot_password' + url = "/api/cloud/forgot_password" + name = "api:cloud:forgot_password" @_handle_cloud_errors - @RequestDataValidator(vol.Schema({ - vol.Required('email'): str, - })) + @RequestDataValidator(vol.Schema({vol.Required("email"): str})) async def post(self, request, data): """Handle forgot password request.""" - hass = request.app['hass'] + hass = request.app["hass"] cloud = hass.data[DOMAIN] with async_timeout.timeout(REQUEST_TIMEOUT): - await hass.async_add_job( - cloud.auth.forgot_password, data['email']) + await hass.async_add_job(cloud.auth.forgot_password, data["email"]) - return self.json_message('ok') + return self.json_message("ok") @callback @@ -310,19 +299,23 @@ def websocket_cloud_status(hass, connection, msg): """ cloud = hass.data[DOMAIN] connection.send_message( - websocket_api.result_message(msg['id'], _account_data(cloud))) + websocket_api.result_message(msg["id"], _account_data(cloud)) + ) def _require_cloud_login(handler): """Websocket decorator that requires cloud to be logged in.""" + @wraps(handler) def with_cloud_auth(hass, connection, msg): """Require to be logged into the cloud.""" cloud = hass.data[DOMAIN] if not cloud.is_logged_in: - connection.send_message(websocket_api.error_message( - msg['id'], 'not_logged_in', - 'You need to be logged in to the cloud.')) + connection.send_message( + websocket_api.error_message( + msg["id"], "not_logged_in", "You need to be logged in to the cloud." + ) + ) return handler(hass, connection, msg) @@ -335,22 +328,25 @@ def _require_cloud_login(handler): async def websocket_subscription(hass, connection, msg): """Handle request for account info.""" from hass_nabucasa.const import STATE_DISCONNECTED + cloud = hass.data[DOMAIN] with async_timeout.timeout(REQUEST_TIMEOUT): response = await cloud.fetch_subscription_info() if response.status != 200: - connection.send_message(websocket_api.error_message( - msg['id'], 'request_failed', 'Failed to request subscription')) + connection.send_message( + websocket_api.error_message( + msg["id"], "request_failed", "Failed to request subscription" + ) + ) data = await response.json() # Check if a user is subscribed but local info is outdated # In that case, let's refresh and reconnect - if data.get('provider') and not cloud.is_connected: - _LOGGER.debug( - "Found disconnected account with valid subscriotion, connecting") + if data.get("provider") and not cloud.is_connected: + _LOGGER.debug("Found disconnected account with valid subscriotion, connecting") await hass.async_add_executor_job(cloud.auth.renew_access_token) # Cancel reconnect in progress @@ -359,25 +355,27 @@ async def websocket_subscription(hass, connection, msg): hass.async_create_task(cloud.iot.connect()) - connection.send_message(websocket_api.result_message(msg['id'], data)) + connection.send_message(websocket_api.result_message(msg["id"], data)) @_require_cloud_login @websocket_api.async_response -@websocket_api.websocket_command({ - vol.Required('type'): 'cloud/update_prefs', - vol.Optional(PREF_ENABLE_GOOGLE): bool, - vol.Optional(PREF_ENABLE_ALEXA): bool, - vol.Optional(PREF_ALEXA_REPORT_STATE): bool, - vol.Optional(PREF_GOOGLE_SECURE_DEVICES_PIN): vol.Any(None, str), -}) +@websocket_api.websocket_command( + { + vol.Required("type"): "cloud/update_prefs", + vol.Optional(PREF_ENABLE_GOOGLE): bool, + vol.Optional(PREF_ENABLE_ALEXA): bool, + vol.Optional(PREF_ALEXA_REPORT_STATE): bool, + vol.Optional(PREF_GOOGLE_SECURE_DEVICES_PIN): vol.Any(None, str), + } +) async def websocket_update_prefs(hass, connection, msg): """Handle request for account info.""" cloud = hass.data[DOMAIN] changes = dict(msg) - changes.pop('id') - changes.pop('type') + changes.pop("id") + changes.pop("type") # If we turn alexa linking on, validate that we can fetch access token if changes.get(PREF_ALEXA_REPORT_STATE): @@ -385,20 +383,22 @@ async def websocket_update_prefs(hass, connection, msg): with async_timeout.timeout(10): await cloud.client.alexa_config.async_get_access_token() except asyncio.TimeoutError: - connection.send_error(msg['id'], 'alexa_timeout', - 'Timeout validating Alexa access token.') + connection.send_error( + msg["id"], "alexa_timeout", "Timeout validating Alexa access token." + ) return except (alexa_errors.NoTokenAvailable, RequireRelink): connection.send_error( - msg['id'], 'alexa_relink', - 'Please go to the Alexa app and re-link the Home Assistant ' - 'skill and then try to enable state reporting.' + msg["id"], + "alexa_relink", + "Please go to the Alexa app and re-link the Home Assistant " + "skill and then try to enable state reporting.", ) return await cloud.client.prefs.async_update(**changes) - connection.send_message(websocket_api.result_message(msg['id'])) + connection.send_message(websocket_api.result_message(msg["id"])) @_require_cloud_login @@ -407,8 +407,8 @@ async def websocket_update_prefs(hass, connection, msg): async def websocket_hook_create(hass, connection, msg): """Handle request for account info.""" cloud = hass.data[DOMAIN] - hook = await cloud.cloudhooks.async_create(msg['webhook_id'], False) - connection.send_message(websocket_api.result_message(msg['id'], hook)) + hook = await cloud.cloudhooks.async_create(msg["webhook_id"], False) + connection.send_message(websocket_api.result_message(msg["id"], hook)) @_require_cloud_login @@ -417,8 +417,8 @@ async def websocket_hook_create(hass, connection, msg): async def websocket_hook_delete(hass, connection, msg): """Handle request for account info.""" cloud = hass.data[DOMAIN] - await cloud.cloudhooks.async_delete(msg['webhook_id']) - connection.send_message(websocket_api.result_message(msg['id'])) + await cloud.cloudhooks.async_delete(msg["webhook_id"]) + connection.send_message(websocket_api.result_message(msg["id"])) def _account_data(cloud): @@ -426,10 +426,7 @@ def _account_data(cloud): from hass_nabucasa.const import STATE_DISCONNECTED if not cloud.is_logged_in: - return { - 'logged_in': False, - 'cloud': STATE_DISCONNECTED, - } + return {"logged_in": False, "cloud": STATE_DISCONNECTED} claims = cloud.claims client = cloud.client @@ -442,15 +439,15 @@ def _account_data(cloud): certificate = None return { - 'logged_in': True, - 'email': claims['email'], - 'cloud': cloud.iot.state, - 'prefs': client.prefs.as_dict(), - 'google_entities': client.google_user_config['filter'].config, - 'alexa_entities': client.alexa_user_config['filter'].config, - 'remote_domain': remote.instance_domain, - 'remote_connected': remote.is_connected, - 'remote_certificate': certificate, + "logged_in": True, + "email": claims["email"], + "cloud": cloud.iot.state, + "prefs": client.prefs.as_dict(), + "google_entities": client.google_user_config["filter"].config, + "alexa_entities": client.alexa_user_config["filter"].config, + "remote_domain": remote.instance_domain, + "remote_connected": remote.is_connected, + "remote_certificate": certificate, } @@ -458,139 +455,133 @@ def _account_data(cloud): @_require_cloud_login @websocket_api.async_response @_ws_handle_cloud_errors -@websocket_api.websocket_command({ - 'type': 'cloud/remote/connect' -}) +@websocket_api.websocket_command({"type": "cloud/remote/connect"}) async def websocket_remote_connect(hass, connection, msg): """Handle request for connect remote.""" cloud = hass.data[DOMAIN] await cloud.client.prefs.async_update(remote_enabled=True) await cloud.remote.connect() - connection.send_result(msg['id'], _account_data(cloud)) + connection.send_result(msg["id"], _account_data(cloud)) @websocket_api.require_admin @_require_cloud_login @websocket_api.async_response @_ws_handle_cloud_errors -@websocket_api.websocket_command({ - 'type': 'cloud/remote/disconnect' -}) +@websocket_api.websocket_command({"type": "cloud/remote/disconnect"}) async def websocket_remote_disconnect(hass, connection, msg): """Handle request for disconnect remote.""" cloud = hass.data[DOMAIN] await cloud.client.prefs.async_update(remote_enabled=False) await cloud.remote.disconnect() - connection.send_result(msg['id'], _account_data(cloud)) + connection.send_result(msg["id"], _account_data(cloud)) @websocket_api.require_admin @_require_cloud_login @websocket_api.async_response @_ws_handle_cloud_errors -@websocket_api.websocket_command({ - 'type': 'cloud/google_assistant/entities' -}) +@websocket_api.websocket_command({"type": "cloud/google_assistant/entities"}) async def google_assistant_list(hass, connection, msg): """List all google assistant entities.""" cloud = hass.data[DOMAIN] - entities = google_helpers.async_get_entities( - hass, cloud.client.google_config - ) + entities = google_helpers.async_get_entities(hass, cloud.client.google_config) result = [] for entity in entities: - result.append({ - 'entity_id': entity.entity_id, - 'traits': [trait.name for trait in entity.traits()], - 'might_2fa': entity.might_2fa(), - }) + result.append( + { + "entity_id": entity.entity_id, + "traits": [trait.name for trait in entity.traits()], + "might_2fa": entity.might_2fa(), + } + ) - connection.send_result(msg['id'], result) + connection.send_result(msg["id"], result) @websocket_api.require_admin @_require_cloud_login @websocket_api.async_response @_ws_handle_cloud_errors -@websocket_api.websocket_command({ - 'type': 'cloud/google_assistant/entities/update', - 'entity_id': str, - vol.Optional('should_expose'): bool, - vol.Optional('override_name'): str, - vol.Optional('aliases'): [str], - vol.Optional('disable_2fa'): bool, -}) +@websocket_api.websocket_command( + { + "type": "cloud/google_assistant/entities/update", + "entity_id": str, + vol.Optional("should_expose"): bool, + vol.Optional("override_name"): str, + vol.Optional("aliases"): [str], + vol.Optional("disable_2fa"): bool, + } +) async def google_assistant_update(hass, connection, msg): """Update google assistant config.""" cloud = hass.data[DOMAIN] changes = dict(msg) - changes.pop('type') - changes.pop('id') + changes.pop("type") + changes.pop("id") await cloud.client.prefs.async_update_google_entity_config(**changes) connection.send_result( - msg['id'], - cloud.client.prefs.google_entity_configs.get(msg['entity_id'])) + msg["id"], cloud.client.prefs.google_entity_configs.get(msg["entity_id"]) + ) @websocket_api.require_admin @_require_cloud_login @websocket_api.async_response @_ws_handle_cloud_errors -@websocket_api.websocket_command({ - 'type': 'cloud/alexa/entities' -}) +@websocket_api.websocket_command({"type": "cloud/alexa/entities"}) async def alexa_list(hass, connection, msg): """List all alexa entities.""" cloud = hass.data[DOMAIN] - entities = alexa_entities.async_get_entities( - hass, cloud.client.alexa_config - ) + entities = alexa_entities.async_get_entities(hass, cloud.client.alexa_config) result = [] for entity in entities: - result.append({ - 'entity_id': entity.entity_id, - 'display_categories': entity.default_display_categories(), - 'interfaces': [ifc.name() for ifc in entity.interfaces()], - }) + result.append( + { + "entity_id": entity.entity_id, + "display_categories": entity.default_display_categories(), + "interfaces": [ifc.name() for ifc in entity.interfaces()], + } + ) - connection.send_result(msg['id'], result) + connection.send_result(msg["id"], result) @websocket_api.require_admin @_require_cloud_login @websocket_api.async_response @_ws_handle_cloud_errors -@websocket_api.websocket_command({ - 'type': 'cloud/alexa/entities/update', - 'entity_id': str, - vol.Optional('should_expose'): bool, -}) +@websocket_api.websocket_command( + { + "type": "cloud/alexa/entities/update", + "entity_id": str, + vol.Optional("should_expose"): bool, + } +) async def alexa_update(hass, connection, msg): """Update alexa entity config.""" cloud = hass.data[DOMAIN] changes = dict(msg) - changes.pop('type') - changes.pop('id') + changes.pop("type") + changes.pop("id") await cloud.client.prefs.async_update_alexa_entity_config(**changes) connection.send_result( - msg['id'], - cloud.client.prefs.alexa_entity_configs.get(msg['entity_id'])) + msg["id"], cloud.client.prefs.alexa_entity_configs.get(msg["entity_id"]) + ) @websocket_api.require_admin @_require_cloud_login @websocket_api.async_response -@websocket_api.websocket_command({ - 'type': 'cloud/alexa/sync', -}) +@websocket_api.websocket_command({"type": "cloud/alexa/sync"}) async def alexa_sync(hass, connection, msg): """Sync with Alexa.""" cloud = hass.data[DOMAIN] @@ -600,14 +591,13 @@ async def alexa_sync(hass, connection, msg): success = await cloud.client.alexa_config.async_sync_entities() except alexa_errors.NoTokenAvailable: connection.send_error( - msg['id'], 'alexa_relink', - 'Please go to the Alexa app and re-link the Home Assistant ' - 'skill.' + msg["id"], + "alexa_relink", + "Please go to the Alexa app and re-link the Home Assistant " "skill.", ) return if success: - connection.send_result(msg['id']) + connection.send_result(msg["id"]) else: - connection.send_error( - msg['id'], ws_const.ERR_UNKNOWN_ERROR, 'Unknown error') + connection.send_error(msg["id"], ws_const.ERR_UNKNOWN_ERROR, "Unknown error") diff --git a/homeassistant/components/cloud/prefs.py b/homeassistant/components/cloud/prefs.py index a01a6dd4cb5..d6e78e87e25 100644 --- a/homeassistant/components/cloud/prefs.py +++ b/homeassistant/components/cloud/prefs.py @@ -5,12 +5,24 @@ from homeassistant.core import callback from homeassistant.util.logging import async_create_catching_coro from .const import ( - DOMAIN, PREF_ENABLE_ALEXA, PREF_ENABLE_GOOGLE, PREF_ENABLE_REMOTE, - PREF_GOOGLE_SECURE_DEVICES_PIN, PREF_CLOUDHOOKS, PREF_CLOUD_USER, - PREF_GOOGLE_ENTITY_CONFIGS, PREF_OVERRIDE_NAME, PREF_DISABLE_2FA, - PREF_ALIASES, PREF_SHOULD_EXPOSE, PREF_ALEXA_ENTITY_CONFIGS, - PREF_ALEXA_REPORT_STATE, DEFAULT_ALEXA_REPORT_STATE, - InvalidTrustedNetworks, InvalidTrustedProxies) + DOMAIN, + PREF_ENABLE_ALEXA, + PREF_ENABLE_GOOGLE, + PREF_ENABLE_REMOTE, + PREF_GOOGLE_SECURE_DEVICES_PIN, + PREF_CLOUDHOOKS, + PREF_CLOUD_USER, + PREF_GOOGLE_ENTITY_CONFIGS, + PREF_OVERRIDE_NAME, + PREF_DISABLE_2FA, + PREF_ALIASES, + PREF_SHOULD_EXPOSE, + PREF_ALEXA_ENTITY_CONFIGS, + PREF_ALEXA_REPORT_STATE, + DEFAULT_ALEXA_REPORT_STATE, + InvalidTrustedNetworks, + InvalidTrustedProxies, +) STORAGE_KEY = DOMAIN STORAGE_VERSION = 1 @@ -50,23 +62,30 @@ class CloudPreferences: """Listen for updates to the preferences.""" self._listeners.append(listener) - async def async_update(self, *, google_enabled=_UNDEF, - alexa_enabled=_UNDEF, remote_enabled=_UNDEF, - google_secure_devices_pin=_UNDEF, cloudhooks=_UNDEF, - cloud_user=_UNDEF, google_entity_configs=_UNDEF, - alexa_entity_configs=_UNDEF, - alexa_report_state=_UNDEF): + async def async_update( + self, + *, + google_enabled=_UNDEF, + alexa_enabled=_UNDEF, + remote_enabled=_UNDEF, + google_secure_devices_pin=_UNDEF, + cloudhooks=_UNDEF, + cloud_user=_UNDEF, + google_entity_configs=_UNDEF, + alexa_entity_configs=_UNDEF, + alexa_report_state=_UNDEF, + ): """Update user preferences.""" for key, value in ( - (PREF_ENABLE_GOOGLE, google_enabled), - (PREF_ENABLE_ALEXA, alexa_enabled), - (PREF_ENABLE_REMOTE, remote_enabled), - (PREF_GOOGLE_SECURE_DEVICES_PIN, google_secure_devices_pin), - (PREF_CLOUDHOOKS, cloudhooks), - (PREF_CLOUD_USER, cloud_user), - (PREF_GOOGLE_ENTITY_CONFIGS, google_entity_configs), - (PREF_ALEXA_ENTITY_CONFIGS, alexa_entity_configs), - (PREF_ALEXA_REPORT_STATE, alexa_report_state), + (PREF_ENABLE_GOOGLE, google_enabled), + (PREF_ENABLE_ALEXA, alexa_enabled), + (PREF_ENABLE_REMOTE, remote_enabled), + (PREF_GOOGLE_SECURE_DEVICES_PIN, google_secure_devices_pin), + (PREF_CLOUDHOOKS, cloudhooks), + (PREF_CLOUD_USER, cloud_user), + (PREF_GOOGLE_ENTITY_CONFIGS, google_entity_configs), + (PREF_ALEXA_ENTITY_CONFIGS, alexa_entity_configs), + (PREF_ALEXA_REPORT_STATE, alexa_report_state), ): if value is not _UNDEF: self._prefs[key] = value @@ -82,23 +101,27 @@ class CloudPreferences: await self._store.async_save(self._prefs) for listener in self._listeners: - self._hass.async_create_task( - async_create_catching_coro(listener(self)) - ) + self._hass.async_create_task(async_create_catching_coro(listener(self))) async def async_update_google_entity_config( - self, *, entity_id, override_name=_UNDEF, disable_2fa=_UNDEF, - aliases=_UNDEF, should_expose=_UNDEF): + self, + *, + entity_id, + override_name=_UNDEF, + disable_2fa=_UNDEF, + aliases=_UNDEF, + should_expose=_UNDEF, + ): """Update config for a Google entity.""" entities = self.google_entity_configs entity = entities.get(entity_id, {}) changes = {} for key, value in ( - (PREF_OVERRIDE_NAME, override_name), - (PREF_DISABLE_2FA, disable_2fa), - (PREF_ALIASES, aliases), - (PREF_SHOULD_EXPOSE, should_expose), + (PREF_OVERRIDE_NAME, override_name), + (PREF_DISABLE_2FA, disable_2fa), + (PREF_ALIASES, aliases), + (PREF_SHOULD_EXPOSE, should_expose), ): if value is not _UNDEF: changes[key] = value @@ -106,42 +129,29 @@ class CloudPreferences: if not changes: return - updated_entity = { - **entity, - **changes, - } + updated_entity = {**entity, **changes} - updated_entities = { - **entities, - entity_id: updated_entity, - } + updated_entities = {**entities, entity_id: updated_entity} await self.async_update(google_entity_configs=updated_entities) async def async_update_alexa_entity_config( - self, *, entity_id, should_expose=_UNDEF): + self, *, entity_id, should_expose=_UNDEF + ): """Update config for an Alexa entity.""" entities = self.alexa_entity_configs entity = entities.get(entity_id, {}) changes = {} - for key, value in ( - (PREF_SHOULD_EXPOSE, should_expose), - ): + for key, value in ((PREF_SHOULD_EXPOSE, should_expose),): if value is not _UNDEF: changes[key] = value if not changes: return - updated_entity = { - **entity, - **changes, - } + updated_entity = {**entity, **changes} - updated_entities = { - **entities, - entity_id: updated_entity, - } + updated_entities = {**entities, entity_id: updated_entity} await self.async_update(alexa_entity_configs=updated_entities) def as_dict(self): @@ -179,8 +189,7 @@ class CloudPreferences: @property def alexa_report_state(self): """Return if Alexa report state is enabled.""" - return self._prefs.get(PREF_ALEXA_REPORT_STATE, - DEFAULT_ALEXA_REPORT_STATE) + return self._prefs.get(PREF_ALEXA_REPORT_STATE, DEFAULT_ALEXA_REPORT_STATE) @property def google_enabled(self): @@ -215,11 +224,11 @@ class CloudPreferences: @property def _has_local_trusted_network(self) -> bool: """Return if we allow localhost to bypass auth.""" - local4 = ip_address('127.0.0.1') - local6 = ip_address('::1') + local4 = ip_address("127.0.0.1") + local6 = ip_address("::1") for prv in self._hass.auth.auth_providers: - if prv.type != 'trusted_networks': + if prv.type != "trusted_networks": continue for network in prv.trusted_networks: @@ -231,14 +240,15 @@ class CloudPreferences: @property def _has_local_trusted_proxies(self) -> bool: """Return if we allow localhost to be a proxy and use its data.""" - if not hasattr(self._hass, 'http'): + if not hasattr(self._hass, "http"): return False - local4 = ip_address('127.0.0.1') - local6 = ip_address('::1') + local4 = ip_address("127.0.0.1") + local6 = ip_address("::1") - if any(local4 in nwk or local6 in nwk - for nwk in self._hass.http.trusted_proxies): + if any( + local4 in nwk or local6 in nwk for nwk in self._hass.http.trusted_proxies + ): return True return False diff --git a/homeassistant/components/cloud/utils.py b/homeassistant/components/cloud/utils.py index 1d53681cbea..5040baada9a 100644 --- a/homeassistant/components/cloud/utils.py +++ b/homeassistant/components/cloud/utils.py @@ -14,12 +14,8 @@ def aiohttp_serialize_response(response: web.Response) -> Dict[str, Any]: # pylint: disable=protected-access body = body._value.decode(body.encoding) elif isinstance(body, bytes): - body = body.decode(response.charset or 'utf-8') + body = body.decode(response.charset or "utf-8") else: raise ValueError("Unknown payload encoding") - return { - 'status': response.status, - 'body': body, - 'headers': dict(response.headers), - } + return {"status": response.status, "body": body, "headers": dict(response.headers)} diff --git a/homeassistant/components/cloudflare/__init__.py b/homeassistant/components/cloudflare/__init__.py index ce88f820fe3..26feff069da 100644 --- a/homeassistant/components/cloudflare/__init__.py +++ b/homeassistant/components/cloudflare/__init__.py @@ -10,20 +10,25 @@ from homeassistant.helpers.event import track_time_interval _LOGGER = logging.getLogger(__name__) -CONF_RECORDS = 'records' +CONF_RECORDS = "records" -DOMAIN = 'cloudflare' +DOMAIN = "cloudflare" INTERVAL = timedelta(minutes=60) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_EMAIL): cv.string, - vol.Required(CONF_API_KEY): cv.string, - vol.Required(CONF_ZONE): cv.string, - vol.Required(CONF_RECORDS): vol.All(cv.ensure_list, [cv.string]), - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_EMAIL): cv.string, + vol.Required(CONF_API_KEY): cv.string, + vol.Required(CONF_ZONE): cv.string, + vol.Required(CONF_RECORDS): vol.All(cv.ensure_list, [cv.string]), + } + ) + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): @@ -45,8 +50,7 @@ def setup(hass, config): _update_cloudflare(cfupdate, email, key, zone, records) track_time_interval(hass, update_records_interval, INTERVAL) - hass.services.register( - DOMAIN, 'update_records', update_records_service) + hass.services.register(DOMAIN, "update_records", update_records_service) return True diff --git a/homeassistant/components/cmus/media_player.py b/homeassistant/components/cmus/media_player.py index 4f1dfc50536..a491bcc09ee 100644 --- a/homeassistant/components/cmus/media_player.py +++ b/homeassistant/components/cmus/media_player.py @@ -3,32 +3,56 @@ import logging import voluptuous as vol -from homeassistant.components.media_player import ( - MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA from homeassistant.components.media_player.const import ( - MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, - SUPPORT_SEEK, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_SET) + MEDIA_TYPE_MUSIC, + MEDIA_TYPE_PLAYLIST, + SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, + SUPPORT_PLAY, + SUPPORT_PLAY_MEDIA, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_SEEK, + SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, + SUPPORT_VOLUME_SET, +) from homeassistant.const import ( - CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT, STATE_OFF, STATE_PAUSED, - STATE_PLAYING) + CONF_HOST, + CONF_NAME, + CONF_PASSWORD, + CONF_PORT, + STATE_OFF, + STATE_PAUSED, + STATE_PLAYING, +) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'cmus' +DEFAULT_NAME = "cmus" DEFAULT_PORT = 3000 -SUPPORT_CMUS = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_TURN_OFF | \ - SUPPORT_TURN_ON | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \ - SUPPORT_PLAY_MEDIA | SUPPORT_SEEK | SUPPORT_PLAY +SUPPORT_CMUS = ( + SUPPORT_PAUSE + | SUPPORT_VOLUME_SET + | SUPPORT_TURN_OFF + | SUPPORT_TURN_ON + | SUPPORT_PREVIOUS_TRACK + | SUPPORT_NEXT_TRACK + | SUPPORT_PLAY_MEDIA + | SUPPORT_SEEK + | SUPPORT_PLAY +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Inclusive(CONF_HOST, 'remote'): cv.string, - vol.Inclusive(CONF_PASSWORD, 'remote'): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Inclusive(CONF_HOST, "remote"): cv.string, + vol.Inclusive(CONF_PASSWORD, "remote"): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + } +) def setup_platform(hass, config, add_entities, discover_info=None): @@ -57,12 +81,11 @@ class CmusDevice(MediaPlayerDevice): from pycmus import remote if server: - self.cmus = remote.PyCmus( - server=server, password=password, port=port) - auto_name = 'cmus-{}'.format(server) + self.cmus = remote.PyCmus(server=server, password=password, port=port) + auto_name = "cmus-{}".format(server) else: self.cmus = remote.PyCmus() - auto_name = 'cmus-local' + auto_name = "cmus-local" self._name = name or auto_name self.status = {} @@ -82,16 +105,16 @@ class CmusDevice(MediaPlayerDevice): @property def state(self): """Return the media state.""" - if self.status.get('status') == 'playing': + if self.status.get("status") == "playing": return STATE_PLAYING - if self.status.get('status') == 'paused': + if self.status.get("status") == "paused": return STATE_PAUSED return STATE_OFF @property def media_content_id(self): """Content ID of current playing media.""" - return self.status.get('file') + return self.status.get("file") @property def content_type(self): @@ -101,43 +124,43 @@ class CmusDevice(MediaPlayerDevice): @property def media_duration(self): """Duration of current playing media in seconds.""" - return self.status.get('duration') + return self.status.get("duration") @property def media_title(self): """Title of current playing media.""" - return self.status['tag'].get('title') + return self.status["tag"].get("title") @property def media_artist(self): """Artist of current playing media, music track only.""" - return self.status['tag'].get('artist') + return self.status["tag"].get("artist") @property def media_track(self): """Track number of current playing media, music track only.""" - return self.status['tag'].get('tracknumber') + return self.status["tag"].get("tracknumber") @property def media_album_name(self): """Album name of current playing media, music track only.""" - return self.status['tag'].get('album') + return self.status["tag"].get("album") @property def media_album_artist(self): """Album artist of current playing media, music track only.""" - return self.status['tag'].get('albumartist') + return self.status["tag"].get("albumartist") @property def volume_level(self): """Return the volume level.""" - left = self.status['set'].get('vol_left')[0] - right = self.status['set'].get('vol_right')[0] + left = self.status["set"].get("vol_left")[0] + right = self.status["set"].get("vol_right")[0] if left != right: volume = float(left + right) / 2 else: volume = left - return int(volume)/100 + return int(volume) / 100 @property def supported_features(self): @@ -158,8 +181,8 @@ class CmusDevice(MediaPlayerDevice): def volume_up(self): """Set the volume up.""" - left = self.status['set'].get('vol_left') - right = self.status['set'].get('vol_right') + left = self.status["set"].get("vol_left") + right = self.status["set"].get("vol_right") if left != right: current_volume = float(left + right) / 2 else: @@ -170,8 +193,8 @@ class CmusDevice(MediaPlayerDevice): def volume_down(self): """Set the volume down.""" - left = self.status['set'].get('vol_left') - right = self.status['set'].get('vol_right') + left = self.status["set"].get("vol_left") + right = self.status["set"].get("vol_right") if left != right: current_volume = float(left + right) / 2 else: @@ -187,7 +210,10 @@ class CmusDevice(MediaPlayerDevice): else: _LOGGER.error( "Invalid media type %s. Only %s and %s are supported", - media_type, MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST) + media_type, + MEDIA_TYPE_MUSIC, + MEDIA_TYPE_PLAYLIST, + ) def media_pause(self): """Send the pause command.""" diff --git a/homeassistant/components/co2signal/sensor.py b/homeassistant/components/co2signal/sensor.py index 990521d0418..d881482ed1a 100644 --- a/homeassistant/components/co2signal/sensor.py +++ b/homeassistant/components/co2signal/sensor.py @@ -5,7 +5,11 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.const import ( - ATTR_ATTRIBUTION, CONF_TOKEN, CONF_LATITUDE, CONF_LONGITUDE) + ATTR_ATTRIBUTION, + CONF_TOKEN, + CONF_LATITUDE, + CONF_LONGITUDE, +) from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.helpers.entity import Entity @@ -13,18 +17,22 @@ CONF_COUNTRY_CODE = "country_code" _LOGGER = logging.getLogger(__name__) -ATTRIBUTION = 'Data provided by CO2signal' +ATTRIBUTION = "Data provided by CO2signal" -MSG_LOCATION = "Please use either coordinates or the country code. " \ - "For the coordinates, " \ - "you need to use both latitude and longitude." +MSG_LOCATION = ( + "Please use either coordinates or the country code. " + "For the coordinates, " + "you need to use both latitude and longitude." +) CO2_INTENSITY_UNIT = "CO2eq/kWh" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_TOKEN): cv.string, - vol.Inclusive(CONF_LATITUDE, 'coords', msg=MSG_LOCATION): cv.latitude, - vol.Inclusive(CONF_LONGITUDE, 'coords', msg=MSG_LOCATION): cv.longitude, - vol.Optional(CONF_COUNTRY_CODE): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_TOKEN): cv.string, + vol.Inclusive(CONF_LATITUDE, "coords", msg=MSG_LOCATION): cv.latitude, + vol.Inclusive(CONF_LONGITUDE, "coords", msg=MSG_LOCATION): cv.longitude, + vol.Optional(CONF_COUNTRY_CODE): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -38,10 +46,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): devs = [] - devs.append(CO2Sensor(token, - country_code, - lat, - lon)) + devs.append(CO2Sensor(token, country_code, lat, lon)) add_entities(devs, True) @@ -59,11 +64,11 @@ class CO2Sensor(Entity): if country_code is not None: device_name = country_code else: - device_name = '{lat}/{lon}'\ - .format(lat=round(self._latitude, 2), - lon=round(self._longitude, 2)) + device_name = "{lat}/{lon}".format( + lat=round(self._latitude, 2), lon=round(self._longitude, 2) + ) - self._friendly_name = 'CO2 intensity - {}'.format(device_name) + self._friendly_name = "CO2 intensity - {}".format(device_name) @property def name(self): @@ -73,7 +78,7 @@ class CO2Sensor(Entity): @property def icon(self): """Icon to use in the frontend, if any.""" - return 'mdi:periodic-table-co2' + return "mdi:periodic-table-co2" @property def state(self): @@ -98,10 +103,11 @@ class CO2Sensor(Entity): if self._country_code is not None: self._data = CO2Signal.get_latest_carbon_intensity( - self._token, country_code=self._country_code) + self._token, country_code=self._country_code + ) else: self._data = CO2Signal.get_latest_carbon_intensity( - self._token, - latitude=self._latitude, longitude=self._longitude) + self._token, latitude=self._latitude, longitude=self._longitude + ) self._data = round(self._data, 2) diff --git a/homeassistant/components/coinbase/__init__.py b/homeassistant/components/coinbase/__init__.py index 21efd5f9b8e..6eca0616ca8 100644 --- a/homeassistant/components/coinbase/__init__.py +++ b/homeassistant/components/coinbase/__init__.py @@ -11,26 +11,33 @@ from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -DOMAIN = 'coinbase' +DOMAIN = "coinbase" -CONF_API_SECRET = 'api_secret' -CONF_ACCOUNT_CURRENCIES = 'account_balance_currencies' -CONF_EXCHANGE_CURRENCIES = 'exchange_rate_currencies' +CONF_API_SECRET = "api_secret" +CONF_ACCOUNT_CURRENCIES = "account_balance_currencies" +CONF_EXCHANGE_CURRENCIES = "exchange_rate_currencies" MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1) -DATA_COINBASE = 'coinbase_cache' +DATA_COINBASE = "coinbase_cache" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_API_KEY): cv.string, - vol.Required(CONF_API_SECRET): cv.string, - vol.Optional(CONF_ACCOUNT_CURRENCIES): - vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_EXCHANGE_CURRENCIES, default=[]): - vol.All(cv.ensure_list, [cv.string]) - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_API_KEY): cv.string, + vol.Required(CONF_API_SECRET): cv.string, + vol.Optional(CONF_ACCOUNT_CURRENCIES): vol.All( + cv.ensure_list, [cv.string] + ), + vol.Optional(CONF_EXCHANGE_CURRENCIES, default=[]): vol.All( + cv.ensure_list, [cv.string] + ), + } + ) + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): @@ -44,30 +51,25 @@ def setup(hass, config): account_currencies = config[DOMAIN].get(CONF_ACCOUNT_CURRENCIES) exchange_currencies = config[DOMAIN].get(CONF_EXCHANGE_CURRENCIES) - hass.data[DATA_COINBASE] = coinbase_data = CoinbaseData( - api_key, api_secret) + hass.data[DATA_COINBASE] = coinbase_data = CoinbaseData(api_key, api_secret) - if not hasattr(coinbase_data, 'accounts'): + if not hasattr(coinbase_data, "accounts"): return False for account in coinbase_data.accounts.data: - if (account_currencies is None or - account.currency in account_currencies): - load_platform(hass, - 'sensor', - DOMAIN, - {'account': account}, - config) + if account_currencies is None or account.currency in account_currencies: + load_platform(hass, "sensor", DOMAIN, {"account": account}, config) for currency in exchange_currencies: if currency not in coinbase_data.exchange_rates.rates: _LOGGER.warning("Currency %s not found", currency) continue native = coinbase_data.exchange_rates.currency - load_platform(hass, - 'sensor', - DOMAIN, - {'native_currency': native, - 'exchange_currency': currency}, - config) + load_platform( + hass, + "sensor", + DOMAIN, + {"native_currency": native, "exchange_currency": currency}, + config, + ) return True @@ -78,6 +80,7 @@ class CoinbaseData: def __init__(self, api_key, api_secret): """Init the coinbase data object.""" from coinbase.wallet.client import Client + self.client = Client(api_key, api_secret) self.update() @@ -85,9 +88,11 @@ class CoinbaseData: def update(self): """Get the latest data from coinbase.""" from coinbase.wallet.error import AuthenticationError + try: self.accounts = self.client.get_accounts() self.exchange_rates = self.client.get_exchange_rates() except AuthenticationError as coinbase_error: - _LOGGER.error("Authentication error connecting" - " to coinbase: %s", coinbase_error) + _LOGGER.error( + "Authentication error connecting" " to coinbase: %s", coinbase_error + ) diff --git a/homeassistant/components/coinbase/sensor.py b/homeassistant/components/coinbase/sensor.py index 9470999efbb..c5f53ef609d 100644 --- a/homeassistant/components/coinbase/sensor.py +++ b/homeassistant/components/coinbase/sensor.py @@ -5,33 +5,35 @@ from homeassistant.helpers.entity import Entity ATTR_NATIVE_BALANCE = "Balance in native currency" CURRENCY_ICONS = { - 'BTC': 'mdi:currency-btc', - 'ETH': 'mdi:currency-eth', - 'EUR': 'mdi:currency-eur', - 'LTC': 'mdi:litecoin', - 'USD': 'mdi:currency-usd' + "BTC": "mdi:currency-btc", + "ETH": "mdi:currency-eth", + "EUR": "mdi:currency-eur", + "LTC": "mdi:litecoin", + "USD": "mdi:currency-usd", } -DEFAULT_COIN_ICON = 'mdi:coin' +DEFAULT_COIN_ICON = "mdi:coin" ATTRIBUTION = "Data provided by coinbase.com" -DATA_COINBASE = 'coinbase_cache' +DATA_COINBASE = "coinbase_cache" def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Coinbase sensors.""" if discovery_info is None: return - if 'account' in discovery_info: - account = discovery_info['account'] + if "account" in discovery_info: + account = discovery_info["account"] sensor = AccountSensor( - hass.data[DATA_COINBASE], account['name'], - account['balance']['currency']) - if 'exchange_currency' in discovery_info: + hass.data[DATA_COINBASE], account["name"], account["balance"]["currency"] + ) + if "exchange_currency" in discovery_info: sensor = ExchangeRateSensor( - hass.data[DATA_COINBASE], discovery_info['exchange_currency'], - discovery_info['native_currency']) + hass.data[DATA_COINBASE], + discovery_info["exchange_currency"], + discovery_info["native_currency"], + ) add_entities([sensor], True) @@ -74,17 +76,18 @@ class AccountSensor(Entity): return { ATTR_ATTRIBUTION: ATTRIBUTION, ATTR_NATIVE_BALANCE: "{} {}".format( - self._native_balance, self._native_currency), + self._native_balance, self._native_currency + ), } def update(self): """Get the latest state of the sensor.""" self._coinbase_data.update() - for account in self._coinbase_data.accounts['data']: - if self._name == "Coinbase {}".format(account['name']): - self._state = account['balance']['amount'] - self._native_balance = account['native_balance']['amount'] - self._native_currency = account['native_balance']['currency'] + for account in self._coinbase_data.accounts["data"]: + if self._name == "Coinbase {}".format(account["name"]): + self._state = account["balance"]["amount"] + self._native_balance = account["native_balance"]["amount"] + self._native_currency = account["native_balance"]["currency"] class ExchangeRateSensor(Entity): @@ -121,9 +124,7 @@ class ExchangeRateSensor(Entity): @property def device_state_attributes(self): """Return the state attributes of the sensor.""" - return { - ATTR_ATTRIBUTION: ATTRIBUTION - } + return {ATTR_ATTRIBUTION: ATTRIBUTION} def update(self): """Get the latest state of the sensor.""" diff --git a/homeassistant/components/coinmarketcap/sensor.py b/homeassistant/components/coinmarketcap/sensor.py index 4d8af5a721d..fbe05187684 100644 --- a/homeassistant/components/coinmarketcap/sensor.py +++ b/homeassistant/components/coinmarketcap/sensor.py @@ -7,47 +7,48 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ( - ATTR_ATTRIBUTION, CONF_DISPLAY_CURRENCY) +from homeassistant.const import ATTR_ATTRIBUTION, CONF_DISPLAY_CURRENCY from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -ATTR_VOLUME_24H = 'volume_24h' -ATTR_AVAILABLE_SUPPLY = 'available_supply' -ATTR_CIRCULATING_SUPPLY = 'circulating_supply' -ATTR_MARKET_CAP = 'market_cap' -ATTR_PERCENT_CHANGE_24H = 'percent_change_24h' -ATTR_PERCENT_CHANGE_7D = 'percent_change_7d' -ATTR_PERCENT_CHANGE_1H = 'percent_change_1h' -ATTR_PRICE = 'price' -ATTR_RANK = 'rank' -ATTR_SYMBOL = 'symbol' -ATTR_TOTAL_SUPPLY = 'total_supply' +ATTR_VOLUME_24H = "volume_24h" +ATTR_AVAILABLE_SUPPLY = "available_supply" +ATTR_CIRCULATING_SUPPLY = "circulating_supply" +ATTR_MARKET_CAP = "market_cap" +ATTR_PERCENT_CHANGE_24H = "percent_change_24h" +ATTR_PERCENT_CHANGE_7D = "percent_change_7d" +ATTR_PERCENT_CHANGE_1H = "percent_change_1h" +ATTR_PRICE = "price" +ATTR_RANK = "rank" +ATTR_SYMBOL = "symbol" +ATTR_TOTAL_SUPPLY = "total_supply" ATTRIBUTION = "Data provided by CoinMarketCap" -CONF_CURRENCY_ID = 'currency_id' -CONF_DISPLAY_CURRENCY_DECIMALS = 'display_currency_decimals' +CONF_CURRENCY_ID = "currency_id" +CONF_DISPLAY_CURRENCY_DECIMALS = "display_currency_decimals" DEFAULT_CURRENCY_ID = 1 -DEFAULT_DISPLAY_CURRENCY = 'USD' +DEFAULT_DISPLAY_CURRENCY = "USD" DEFAULT_DISPLAY_CURRENCY_DECIMALS = 2 -ICON = 'mdi:currency-usd' +ICON = "mdi:currency-usd" SCAN_INTERVAL = timedelta(minutes=15) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_CURRENCY_ID, default=DEFAULT_CURRENCY_ID): - cv.positive_int, - vol.Optional(CONF_DISPLAY_CURRENCY, default=DEFAULT_DISPLAY_CURRENCY): - cv.string, - vol.Optional(CONF_DISPLAY_CURRENCY_DECIMALS, - default=DEFAULT_DISPLAY_CURRENCY_DECIMALS): - vol.All(vol.Coerce(int), vol.Range(min=1)), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_CURRENCY_ID, default=DEFAULT_CURRENCY_ID): cv.positive_int, + vol.Optional( + CONF_DISPLAY_CURRENCY, default=DEFAULT_DISPLAY_CURRENCY + ): cv.string, + vol.Optional( + CONF_DISPLAY_CURRENCY_DECIMALS, default=DEFAULT_DISPLAY_CURRENCY_DECIMALS + ): vol.All(vol.Coerce(int), vol.Range(min=1)), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -59,15 +60,25 @@ def setup_platform(hass, config, add_entities, discovery_info=None): try: CoinMarketCapData(currency_id, display_currency).update() except HTTPError: - _LOGGER.warning("Currency ID %s or display currency %s " - "is not available. Using 1 (bitcoin) " - "and USD.", currency_id, display_currency) + _LOGGER.warning( + "Currency ID %s or display currency %s " + "is not available. Using 1 (bitcoin) " + "and USD.", + currency_id, + display_currency, + ) currency_id = DEFAULT_CURRENCY_ID display_currency = DEFAULT_DISPLAY_CURRENCY - add_entities([CoinMarketCapSensor( - CoinMarketCapData( - currency_id, display_currency), display_currency_decimals)], True) + add_entities( + [ + CoinMarketCapSensor( + CoinMarketCapData(currency_id, display_currency), + display_currency_decimals, + ) + ], + True, + ) class CoinMarketCapSensor(Entity): @@ -83,14 +94,17 @@ class CoinMarketCapSensor(Entity): @property def name(self): """Return the name of the sensor.""" - return self._ticker.get('name') + return self._ticker.get("name") @property def state(self): """Return the state of the sensor.""" - return round(float( - self._ticker.get('quotes').get(self.data.display_currency) - .get('price')), self.display_currency_decimals) + return round( + float( + self._ticker.get("quotes").get(self.data.display_currency).get("price") + ), + self.display_currency_decimals, + ) @property def unit_of_measurement(self): @@ -106,32 +120,32 @@ class CoinMarketCapSensor(Entity): def device_state_attributes(self): """Return the state attributes of the sensor.""" return { - ATTR_VOLUME_24H: - self._ticker.get('quotes').get(self.data.display_currency) - .get('volume_24h'), + ATTR_VOLUME_24H: self._ticker.get("quotes") + .get(self.data.display_currency) + .get("volume_24h"), ATTR_ATTRIBUTION: ATTRIBUTION, - ATTR_CIRCULATING_SUPPLY: self._ticker.get('circulating_supply'), - ATTR_MARKET_CAP: - self._ticker.get('quotes').get(self.data.display_currency) - .get('market_cap'), - ATTR_PERCENT_CHANGE_24H: - self._ticker.get('quotes').get(self.data.display_currency) - .get('percent_change_24h'), - ATTR_PERCENT_CHANGE_7D: - self._ticker.get('quotes').get(self.data.display_currency) - .get('percent_change_7d'), - ATTR_PERCENT_CHANGE_1H: - self._ticker.get('quotes').get(self.data.display_currency) - .get('percent_change_1h'), - ATTR_RANK: self._ticker.get('rank'), - ATTR_SYMBOL: self._ticker.get('symbol'), - ATTR_TOTAL_SUPPLY: self._ticker.get('total_supply'), + ATTR_CIRCULATING_SUPPLY: self._ticker.get("circulating_supply"), + ATTR_MARKET_CAP: self._ticker.get("quotes") + .get(self.data.display_currency) + .get("market_cap"), + ATTR_PERCENT_CHANGE_24H: self._ticker.get("quotes") + .get(self.data.display_currency) + .get("percent_change_24h"), + ATTR_PERCENT_CHANGE_7D: self._ticker.get("quotes") + .get(self.data.display_currency) + .get("percent_change_7d"), + ATTR_PERCENT_CHANGE_1H: self._ticker.get("quotes") + .get(self.data.display_currency) + .get("percent_change_1h"), + ATTR_RANK: self._ticker.get("rank"), + ATTR_SYMBOL: self._ticker.get("symbol"), + ATTR_TOTAL_SUPPLY: self._ticker.get("total_supply"), } def update(self): """Get the latest data and updates the states.""" self.data.update() - self._ticker = self.data.ticker.get('data') + self._ticker = self.data.ticker.get("data") class CoinMarketCapData: @@ -146,5 +160,5 @@ class CoinMarketCapData: def update(self): """Get the latest data from coinmarketcap.com.""" from coinmarketcap import Market - self.ticker = Market().ticker( - self.currency_id, convert=self.display_currency) + + self.ticker = Market().ticker(self.currency_id, convert=self.display_currency) diff --git a/homeassistant/components/comed_hourly_pricing/sensor.py b/homeassistant/components/comed_hourly_pricing/sensor.py index 3c06bc0c2d7..90830d52236 100644 --- a/homeassistant/components/comed_hourly_pricing/sensor.py +++ b/homeassistant/components/comed_hourly_pricing/sensor.py @@ -9,52 +9,58 @@ import async_timeout import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ( - ATTR_ATTRIBUTION, CONF_NAME, CONF_OFFSET) +from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME, CONF_OFFSET from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -_RESOURCE = 'https://hourlypricing.comed.com/api' +_RESOURCE = "https://hourlypricing.comed.com/api" SCAN_INTERVAL = timedelta(minutes=5) ATTRIBUTION = "Data provided by ComEd Hourly Pricing service" -CONF_CURRENT_HOUR_AVERAGE = 'current_hour_average' -CONF_FIVE_MINUTE = 'five_minute' -CONF_MONITORED_FEEDS = 'monitored_feeds' -CONF_SENSOR_TYPE = 'type' +CONF_CURRENT_HOUR_AVERAGE = "current_hour_average" +CONF_FIVE_MINUTE = "five_minute" +CONF_MONITORED_FEEDS = "monitored_feeds" +CONF_SENSOR_TYPE = "type" SENSOR_TYPES = { - CONF_FIVE_MINUTE: ['ComEd 5 Minute Price', 'c'], - CONF_CURRENT_HOUR_AVERAGE: ['ComEd Current Hour Average Price', 'c'], + CONF_FIVE_MINUTE: ["ComEd 5 Minute Price", "c"], + CONF_CURRENT_HOUR_AVERAGE: ["ComEd Current Hour Average Price", "c"], } TYPES_SCHEMA = vol.In(SENSOR_TYPES) -SENSORS_SCHEMA = vol.Schema({ - vol.Required(CONF_SENSOR_TYPE): TYPES_SCHEMA, - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_OFFSET, default=0.0): vol.Coerce(float), -}) +SENSORS_SCHEMA = vol.Schema( + { + vol.Required(CONF_SENSOR_TYPE): TYPES_SCHEMA, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_OFFSET, default=0.0): vol.Coerce(float), + } +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_MONITORED_FEEDS): [SENSORS_SCHEMA], -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_MONITORED_FEEDS): [SENSORS_SCHEMA]} +) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the ComEd Hourly Pricing sensor.""" websession = async_get_clientsession(hass) dev = [] for variable in config[CONF_MONITORED_FEEDS]: - dev.append(ComedHourlyPricingSensor( - hass.loop, websession, variable[CONF_SENSOR_TYPE], - variable[CONF_OFFSET], variable.get(CONF_NAME))) + dev.append( + ComedHourlyPricingSensor( + hass.loop, + websession, + variable[CONF_SENSOR_TYPE], + variable[CONF_OFFSET], + variable.get(CONF_NAME), + ) + ) async_add_entities(dev, True) @@ -98,21 +104,19 @@ class ComedHourlyPricingSensor(Entity): async def async_update(self): """Get the ComEd Hourly Pricing data from the web service.""" try: - if self.type == CONF_FIVE_MINUTE or \ - self.type == CONF_CURRENT_HOUR_AVERAGE: + if self.type == CONF_FIVE_MINUTE or self.type == CONF_CURRENT_HOUR_AVERAGE: url_string = _RESOURCE if self.type == CONF_FIVE_MINUTE: - url_string += '?type=5minutefeed' + url_string += "?type=5minutefeed" else: - url_string += '?type=currenthouraverage' + url_string += "?type=currenthouraverage" with async_timeout.timeout(60): response = await self.websession.get(url_string) # The API responds with MIME type 'text/html' text = await response.text() data = json.loads(text) - self._state = round( - float(data[0]['price']) + self.offset, 2) + self._state = round(float(data[0]["price"]) + self.offset, 2) else: self._state = None diff --git a/homeassistant/components/comfoconnect/__init__.py b/homeassistant/components/comfoconnect/__init__.py index 3c50f3fb723..22e9d95bbd8 100644 --- a/homeassistant/components/comfoconnect/__init__.py +++ b/homeassistant/components/comfoconnect/__init__.py @@ -4,48 +4,59 @@ import logging import voluptuous as vol from homeassistant.const import ( - CONF_HOST, CONF_NAME, CONF_PIN, CONF_TOKEN, EVENT_HOMEASSISTANT_STOP) + CONF_HOST, + CONF_NAME, + CONF_PIN, + CONF_TOKEN, + EVENT_HOMEASSISTANT_STOP, +) from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import dispatcher_send _LOGGER = logging.getLogger(__name__) -DOMAIN = 'comfoconnect' +DOMAIN = "comfoconnect" -SIGNAL_COMFOCONNECT_UPDATE_RECEIVED = 'comfoconnect_update_received' +SIGNAL_COMFOCONNECT_UPDATE_RECEIVED = "comfoconnect_update_received" -ATTR_CURRENT_TEMPERATURE = 'current_temperature' -ATTR_CURRENT_HUMIDITY = 'current_humidity' -ATTR_OUTSIDE_TEMPERATURE = 'outside_temperature' -ATTR_OUTSIDE_HUMIDITY = 'outside_humidity' -ATTR_AIR_FLOW_SUPPLY = 'air_flow_supply' -ATTR_AIR_FLOW_EXHAUST = 'air_flow_exhaust' +ATTR_CURRENT_TEMPERATURE = "current_temperature" +ATTR_CURRENT_HUMIDITY = "current_humidity" +ATTR_OUTSIDE_TEMPERATURE = "outside_temperature" +ATTR_OUTSIDE_HUMIDITY = "outside_humidity" +ATTR_AIR_FLOW_SUPPLY = "air_flow_supply" +ATTR_AIR_FLOW_EXHAUST = "air_flow_exhaust" -CONF_USER_AGENT = 'user_agent' +CONF_USER_AGENT = "user_agent" -DEFAULT_NAME = 'ComfoAirQ' +DEFAULT_NAME = "ComfoAirQ" DEFAULT_PIN = 0 -DEFAULT_TOKEN = '00000000000000000000000000000001' -DEFAULT_USER_AGENT = 'Home Assistant' +DEFAULT_TOKEN = "00000000000000000000000000000001" +DEFAULT_USER_AGENT = "Home Assistant" DEVICE = None -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_TOKEN, default=DEFAULT_TOKEN): - vol.Length(min=32, max=32, msg='invalid token'), - vol.Optional(CONF_USER_AGENT, default=DEFAULT_USER_AGENT): cv.string, - vol.Optional(CONF_PIN, default=DEFAULT_PIN): cv.positive_int, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_TOKEN, default=DEFAULT_TOKEN): vol.Length( + min=32, max=32, msg="invalid token" + ), + vol.Optional(CONF_USER_AGENT, default=DEFAULT_USER_AGENT): cv.string, + vol.Optional(CONF_PIN, default=DEFAULT_PIN): cv.positive_int, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): """Set up the ComfoConnect bridge.""" - from pycomfoconnect import (Bridge) + from pycomfoconnect import Bridge conf = config[DOMAIN] host = conf.get(CONF_HOST) @@ -76,7 +87,7 @@ def setup(hass, config): hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, _shutdown) # Load platforms - discovery.load_platform(hass, 'fan', DOMAIN, {}, config) + discovery.load_platform(hass, "fan", DOMAIN, {}, config) return True @@ -86,15 +97,18 @@ class ComfoConnectBridge: def __init__(self, hass, bridge, name, token, friendly_name, pin): """Initialize the ComfoConnect bridge.""" - from pycomfoconnect import (ComfoConnect) + from pycomfoconnect import ComfoConnect self.data = {} self.name = name self.hass = hass self.comfoconnect = ComfoConnect( - bridge=bridge, local_uuid=bytes.fromhex(token), - local_devicename=friendly_name, pin=pin) + bridge=bridge, + local_uuid=bytes.fromhex(token), + local_devicename=friendly_name, + pin=pin, + ) self.comfoconnect.callback_sensor = self.sensor_callback def connect(self): @@ -112,7 +126,9 @@ class ComfoConnectBridge: _LOGGER.debug("Got value from bridge: %d = %d", var, value) from pycomfoconnect import ( - SENSOR_TEMPERATURE_EXTRACT, SENSOR_TEMPERATURE_OUTDOOR) + SENSOR_TEMPERATURE_EXTRACT, + SENSOR_TEMPERATURE_OUTDOOR, + ) if var in [SENSOR_TEMPERATURE_EXTRACT, SENSOR_TEMPERATURE_OUTDOOR]: self.data[var] = value / 10 diff --git a/homeassistant/components/comfoconnect/fan.py b/homeassistant/components/comfoconnect/fan.py index 56175f0bca0..6c90ab8cba1 100644 --- a/homeassistant/components/comfoconnect/fan.py +++ b/homeassistant/components/comfoconnect/fan.py @@ -2,20 +2,20 @@ import logging from homeassistant.components.fan import ( - SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, SPEED_OFF, SUPPORT_SET_SPEED, - FanEntity) + SPEED_HIGH, + SPEED_LOW, + SPEED_MEDIUM, + SPEED_OFF, + SUPPORT_SET_SPEED, + FanEntity, +) from homeassistant.helpers.dispatcher import dispatcher_connect from . import DOMAIN, SIGNAL_COMFOCONNECT_UPDATE_RECEIVED, ComfoConnectBridge _LOGGER = logging.getLogger(__name__) -SPEED_MAPPING = { - 0: SPEED_OFF, - 1: SPEED_LOW, - 2: SPEED_MEDIUM, - 3: SPEED_HIGH -} +SPEED_MAPPING = {0: SPEED_OFF, 1: SPEED_LOW, 2: SPEED_MEDIUM, 3: SPEED_HIGH} def setup_platform(hass, config, add_entities, discovery_info=None): @@ -44,8 +44,7 @@ class ComfoConnectFan(FanEntity): self.schedule_update_ha_state() # Register for dispatcher updates - dispatcher_connect( - hass, SIGNAL_COMFOCONNECT_UPDATE_RECEIVED, _handle_update) + dispatcher_connect(hass, SIGNAL_COMFOCONNECT_UPDATE_RECEIVED, _handle_update) @property def name(self): @@ -55,7 +54,7 @@ class ComfoConnectFan(FanEntity): @property def icon(self): """Return the icon to use in the frontend.""" - return 'mdi:air-conditioner' + return "mdi:air-conditioner" @property def supported_features(self) -> int: @@ -65,7 +64,7 @@ class ComfoConnectFan(FanEntity): @property def speed(self): """Return the current fan mode.""" - from pycomfoconnect import (SENSOR_FAN_SPEED_MODE) + from pycomfoconnect import SENSOR_FAN_SPEED_MODE try: speed = self._ccb.data[SENSOR_FAN_SPEED_MODE] @@ -90,11 +89,14 @@ class ComfoConnectFan(FanEntity): def set_speed(self, speed: str): """Set fan speed.""" - _LOGGER.debug('Changing fan speed to %s.', speed) + _LOGGER.debug("Changing fan speed to %s.", speed) from pycomfoconnect import ( - CMD_FAN_MODE_AWAY, CMD_FAN_MODE_LOW, CMD_FAN_MODE_MEDIUM, - CMD_FAN_MODE_HIGH) + CMD_FAN_MODE_AWAY, + CMD_FAN_MODE_LOW, + CMD_FAN_MODE_MEDIUM, + CMD_FAN_MODE_HIGH, + ) if speed == SPEED_OFF: self._ccb.comfoconnect.cmd_rmi_request(CMD_FAN_MODE_AWAY) diff --git a/homeassistant/components/comfoconnect/sensor.py b/homeassistant/components/comfoconnect/sensor.py index db2a9393e2b..4099804d413 100644 --- a/homeassistant/components/comfoconnect/sensor.py +++ b/homeassistant/components/comfoconnect/sensor.py @@ -6,9 +6,16 @@ from homeassistant.helpers.dispatcher import dispatcher_connect from homeassistant.helpers.entity import Entity from . import ( - ATTR_AIR_FLOW_EXHAUST, ATTR_AIR_FLOW_SUPPLY, ATTR_CURRENT_HUMIDITY, - ATTR_CURRENT_TEMPERATURE, ATTR_OUTSIDE_HUMIDITY, ATTR_OUTSIDE_TEMPERATURE, - DOMAIN, SIGNAL_COMFOCONNECT_UPDATE_RECEIVED, ComfoConnectBridge) + ATTR_AIR_FLOW_EXHAUST, + ATTR_AIR_FLOW_SUPPLY, + ATTR_CURRENT_HUMIDITY, + ATTR_CURRENT_TEMPERATURE, + ATTR_OUTSIDE_HUMIDITY, + ATTR_OUTSIDE_TEMPERATURE, + DOMAIN, + SIGNAL_COMFOCONNECT_UPDATE_RECEIVED, + ComfoConnectBridge, +) _LOGGER = logging.getLogger(__name__) @@ -18,47 +25,51 @@ SENSOR_TYPES = {} def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the ComfoConnect fan platform.""" from pycomfoconnect import ( - SENSOR_TEMPERATURE_EXTRACT, SENSOR_HUMIDITY_EXTRACT, - SENSOR_TEMPERATURE_OUTDOOR, SENSOR_HUMIDITY_OUTDOOR, - SENSOR_FAN_SUPPLY_FLOW, SENSOR_FAN_EXHAUST_FLOW) + SENSOR_TEMPERATURE_EXTRACT, + SENSOR_HUMIDITY_EXTRACT, + SENSOR_TEMPERATURE_OUTDOOR, + SENSOR_HUMIDITY_OUTDOOR, + SENSOR_FAN_SUPPLY_FLOW, + SENSOR_FAN_EXHAUST_FLOW, + ) global SENSOR_TYPES SENSOR_TYPES = { ATTR_CURRENT_TEMPERATURE: [ - 'Inside Temperature', + "Inside Temperature", TEMP_CELSIUS, - 'mdi:thermometer', - SENSOR_TEMPERATURE_EXTRACT + "mdi:thermometer", + SENSOR_TEMPERATURE_EXTRACT, ], ATTR_CURRENT_HUMIDITY: [ - 'Inside Humidity', - '%', - 'mdi:water-percent', - SENSOR_HUMIDITY_EXTRACT + "Inside Humidity", + "%", + "mdi:water-percent", + SENSOR_HUMIDITY_EXTRACT, ], ATTR_OUTSIDE_TEMPERATURE: [ - 'Outside Temperature', + "Outside Temperature", TEMP_CELSIUS, - 'mdi:thermometer', - SENSOR_TEMPERATURE_OUTDOOR + "mdi:thermometer", + SENSOR_TEMPERATURE_OUTDOOR, ], ATTR_OUTSIDE_HUMIDITY: [ - 'Outside Humidity', - '%', - 'mdi:water-percent', - SENSOR_HUMIDITY_OUTDOOR + "Outside Humidity", + "%", + "mdi:water-percent", + SENSOR_HUMIDITY_OUTDOOR, ], ATTR_AIR_FLOW_SUPPLY: [ - 'Supply airflow', - 'm³/h', - 'mdi:air-conditioner', - SENSOR_FAN_SUPPLY_FLOW + "Supply airflow", + "m³/h", + "mdi:air-conditioner", + SENSOR_FAN_SUPPLY_FLOW, ], ATTR_AIR_FLOW_EXHAUST: [ - 'Exhaust airflow', - 'm³/h', - 'mdi:air-conditioner', - SENSOR_FAN_EXHAUST_FLOW + "Exhaust airflow", + "m³/h", + "mdi:air-conditioner", + SENSOR_FAN_EXHAUST_FLOW, ], } @@ -69,8 +80,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): sensor_type = resource.lower() if sensor_type not in SENSOR_TYPES: - _LOGGER.warning("Sensor type: %s is not a valid sensor.", - sensor_type) + _LOGGER.warning("Sensor type: %s is not a valid sensor.", sensor_type) continue sensors.append( @@ -78,7 +88,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): hass, name="%s %s" % (ccb.name, SENSOR_TYPES[sensor_type][0]), ccb=ccb, - sensor_type=sensor_type + sensor_type=sensor_type, ) ) @@ -88,8 +98,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class ComfoConnectSensor(Entity): """Representation of a ComfoConnect sensor.""" - def __init__(self, hass, name, ccb: ComfoConnectBridge, - sensor_type) -> None: + def __init__(self, hass, name, ccb: ComfoConnectBridge, sensor_type) -> None: """Initialize the ComfoConnect sensor.""" self._ccb = ccb self._sensor_type = sensor_type @@ -101,12 +110,11 @@ class ComfoConnectSensor(Entity): def _handle_update(var): if var == self._sensor_id: - _LOGGER.debug('Dispatcher update for %s.', var) + _LOGGER.debug("Dispatcher update for %s.", var) self.schedule_update_ha_state() # Register for dispatcher updates - dispatcher_connect( - hass, SIGNAL_COMFOCONNECT_UPDATE_RECEIVED, _handle_update) + dispatcher_connect(hass, SIGNAL_COMFOCONNECT_UPDATE_RECEIVED, _handle_update) @property def state(self): diff --git a/homeassistant/components/command_line/binary_sensor.py b/homeassistant/components/command_line/binary_sensor.py index 860367d8091..eaa371be1a3 100644 --- a/homeassistant/components/command_line/binary_sensor.py +++ b/homeassistant/components/command_line/binary_sensor.py @@ -5,35 +5,44 @@ import logging import voluptuous as vol from homeassistant.components.binary_sensor import ( - DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, BinarySensorDevice) + DEVICE_CLASSES_SCHEMA, + PLATFORM_SCHEMA, + BinarySensorDevice, +) from homeassistant.const import ( - CONF_COMMAND, CONF_DEVICE_CLASS, CONF_NAME, CONF_PAYLOAD_OFF, - CONF_PAYLOAD_ON, CONF_VALUE_TEMPLATE) + CONF_COMMAND, + CONF_DEVICE_CLASS, + CONF_NAME, + CONF_PAYLOAD_OFF, + CONF_PAYLOAD_ON, + CONF_VALUE_TEMPLATE, +) import homeassistant.helpers.config_validation as cv from .sensor import CommandSensorData _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'Binary Command Sensor' -DEFAULT_PAYLOAD_ON = 'ON' -DEFAULT_PAYLOAD_OFF = 'OFF' +DEFAULT_NAME = "Binary Command Sensor" +DEFAULT_PAYLOAD_ON = "ON" +DEFAULT_PAYLOAD_OFF = "OFF" SCAN_INTERVAL = timedelta(seconds=60) -CONF_COMMAND_TIMEOUT = 'command_timeout' +CONF_COMMAND_TIMEOUT = "command_timeout" DEFAULT_TIMEOUT = 15 -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_COMMAND): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string, - vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, - vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, - vol.Optional(CONF_VALUE_TEMPLATE): cv.template, - vol.Optional( - CONF_COMMAND_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_COMMAND): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string, + vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, + vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_COMMAND_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -49,16 +58,22 @@ def setup_platform(hass, config, add_entities, discovery_info=None): value_template.hass = hass data = CommandSensorData(hass, command, command_timeout) - add_entities([CommandBinarySensor( - hass, data, name, device_class, payload_on, payload_off, - value_template)], True) + add_entities( + [ + CommandBinarySensor( + hass, data, name, device_class, payload_on, payload_off, value_template + ) + ], + True, + ) class CommandBinarySensor(BinarySensorDevice): """Representation of a command line binary sensor.""" - def __init__(self, hass, data, name, device_class, payload_on, - payload_off, value_template): + def __init__( + self, hass, data, name, device_class, payload_on, payload_off, value_template + ): """Initialize the Command line binary sensor.""" self._hass = hass self.data = data @@ -79,7 +94,7 @@ class CommandBinarySensor(BinarySensorDevice): """Return true if the binary sensor is on.""" return self._state - @ property + @property def device_class(self): """Return the class of the binary sensor.""" return self._device_class @@ -90,8 +105,7 @@ class CommandBinarySensor(BinarySensorDevice): value = self.data.value if self._value_template is not None: - value = self._value_template.render_with_possible_json_value( - value, False) + value = self._value_template.render_with_possible_json_value(value, False) if value == self._payload_on: self._state = True elif value == self._payload_off: diff --git a/homeassistant/components/command_line/cover.py b/homeassistant/components/command_line/cover.py index 7f3c5279905..c4413e78a00 100644 --- a/homeassistant/components/command_line/cover.py +++ b/homeassistant/components/command_line/cover.py @@ -4,26 +4,34 @@ import subprocess import voluptuous as vol -from homeassistant.components.cover import (CoverDevice, PLATFORM_SCHEMA) +from homeassistant.components.cover import CoverDevice, PLATFORM_SCHEMA from homeassistant.const import ( - CONF_COMMAND_CLOSE, CONF_COMMAND_OPEN, CONF_COMMAND_STATE, - CONF_COMMAND_STOP, CONF_COVERS, CONF_VALUE_TEMPLATE, CONF_FRIENDLY_NAME) + CONF_COMMAND_CLOSE, + CONF_COMMAND_OPEN, + CONF_COMMAND_STATE, + CONF_COMMAND_STOP, + CONF_COVERS, + CONF_VALUE_TEMPLATE, + CONF_FRIENDLY_NAME, +) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -COVER_SCHEMA = vol.Schema({ - vol.Optional(CONF_COMMAND_CLOSE, default='true'): cv.string, - vol.Optional(CONF_COMMAND_OPEN, default='true'): cv.string, - vol.Optional(CONF_COMMAND_STATE): cv.string, - vol.Optional(CONF_COMMAND_STOP, default='true'): cv.string, - vol.Optional(CONF_FRIENDLY_NAME): cv.string, - vol.Optional(CONF_VALUE_TEMPLATE): cv.template, -}) +COVER_SCHEMA = vol.Schema( + { + vol.Optional(CONF_COMMAND_CLOSE, default="true"): cv.string, + vol.Optional(CONF_COMMAND_OPEN, default="true"): cv.string, + vol.Optional(CONF_COMMAND_STATE): cv.string, + vol.Optional(CONF_COMMAND_STOP, default="true"): cv.string, + vol.Optional(CONF_FRIENDLY_NAME): cv.string, + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, + } +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_COVERS): cv.schema_with_slug_keys(COVER_SCHEMA), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_COVERS): cv.schema_with_slug_keys(COVER_SCHEMA)} +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -58,8 +66,16 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class CommandCover(CoverDevice): """Representation a command line cover.""" - def __init__(self, hass, name, command_open, command_close, command_stop, - command_state, value_template): + def __init__( + self, + hass, + name, + command_open, + command_close, + command_stop, + command_state, + value_template, + ): """Initialize the cover.""" self._hass = hass self._name = name @@ -75,7 +91,7 @@ class CommandCover(CoverDevice): """Execute the actual commands.""" _LOGGER.info("Running command: %s", command) - success = (subprocess.call(command, shell=True) == 0) + success = subprocess.call(command, shell=True) == 0 if not success: _LOGGER.error("Command failed: %s", command) @@ -89,7 +105,7 @@ class CommandCover(CoverDevice): try: return_value = subprocess.check_output(command, shell=True) - return return_value.strip().decode('utf-8') + return return_value.strip().decode("utf-8") except subprocess.CalledProcessError: _LOGGER.error("Command failed: %s", command) @@ -129,8 +145,7 @@ class CommandCover(CoverDevice): if self._command_state: payload = str(self._query_state()) if self._value_template: - payload = self._value_template.render_with_possible_json_value( - payload) + payload = self._value_template.render_with_possible_json_value(payload) self._state = int(payload) def open_cover(self, **kwargs): diff --git a/homeassistant/components/command_line/notify.py b/homeassistant/components/command_line/notify.py index 941be72aa81..e2581c8f065 100644 --- a/homeassistant/components/command_line/notify.py +++ b/homeassistant/components/command_line/notify.py @@ -7,15 +7,13 @@ import voluptuous as vol from homeassistant.const import CONF_COMMAND, CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.components.notify import (PLATFORM_SCHEMA, - BaseNotificationService) +from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationService _LOGGER = logging.getLogger(__name__) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_COMMAND): cv.string, - vol.Optional(CONF_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_COMMAND): cv.string, vol.Optional(CONF_NAME): cv.string} +) def get_service(hass, config, discovery_info=None): @@ -35,8 +33,9 @@ class CommandLineNotificationService(BaseNotificationService): def send_message(self, message="", **kwargs): """Send a message to a command line.""" try: - proc = subprocess.Popen(self.command, universal_newlines=True, - stdin=subprocess.PIPE, shell=True) + proc = subprocess.Popen( + self.command, universal_newlines=True, stdin=subprocess.PIPE, shell=True + ) proc.communicate(input=message) if proc.returncode != 0: _LOGGER.error("Command failed: %s", self.command) diff --git a/homeassistant/components/command_line/sensor.py b/homeassistant/components/command_line/sensor.py index 587cfe53d3c..85ba78ecd98 100644 --- a/homeassistant/components/command_line/sensor.py +++ b/homeassistant/components/command_line/sensor.py @@ -10,8 +10,12 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_COMMAND, CONF_NAME, CONF_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE, - STATE_UNKNOWN) + CONF_COMMAND, + CONF_NAME, + CONF_UNIT_OF_MEASUREMENT, + CONF_VALUE_TEMPLATE, + STATE_UNKNOWN, +) from homeassistant.exceptions import TemplateError from homeassistant.helpers import template import homeassistant.helpers.config_validation as cv @@ -19,23 +23,24 @@ from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -CONF_COMMAND_TIMEOUT = 'command_timeout' -CONF_JSON_ATTRIBUTES = 'json_attributes' +CONF_COMMAND_TIMEOUT = "command_timeout" +CONF_JSON_ATTRIBUTES = "json_attributes" -DEFAULT_NAME = 'Command Sensor' +DEFAULT_NAME = "Command Sensor" DEFAULT_TIMEOUT = 15 SCAN_INTERVAL = timedelta(seconds=60) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_COMMAND): cv.string, - vol.Optional(CONF_COMMAND_TIMEOUT, default=DEFAULT_TIMEOUT): - cv.positive_int, - vol.Optional(CONF_JSON_ATTRIBUTES): cv.ensure_list_csv, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, - vol.Optional(CONF_VALUE_TEMPLATE): cv.template, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_COMMAND): cv.string, + vol.Optional(CONF_COMMAND_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, + vol.Optional(CONF_JSON_ATTRIBUTES): cv.ensure_list_csv, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -50,15 +55,17 @@ def setup_platform(hass, config, add_entities, discovery_info=None): json_attributes = config.get(CONF_JSON_ATTRIBUTES) data = CommandSensorData(hass, command, command_timeout) - add_entities([CommandSensor( - hass, data, name, unit, value_template, json_attributes)], True) + add_entities( + [CommandSensor(hass, data, name, unit, value_template, json_attributes)], True + ) class CommandSensor(Entity): """Representation of a sensor that is using shell commands.""" - def __init__(self, hass, data, name, unit_of_measurement, value_template, - json_attributes): + def __init__( + self, hass, data, name, unit_of_measurement, value_template, json_attributes + ): """Initialize the sensor.""" self._hass = hass self.data = data @@ -100,14 +107,15 @@ class CommandSensor(Entity): try: json_dict = json.loads(value) if isinstance(json_dict, collections.Mapping): - self._attributes = {k: json_dict[k] for k in - self._json_attributes - if k in json_dict} + self._attributes = { + k: json_dict[k] + for k in self._json_attributes + if k in json_dict + } else: _LOGGER.warning("JSON result was not a dictionary") except ValueError: - _LOGGER.warning( - "Unable to parse output as JSON: %s", value) + _LOGGER.warning("Unable to parse output as JSON: %s", value) else: _LOGGER.warning("Empty reply found when expecting JSON data") @@ -115,7 +123,8 @@ class CommandSensor(Entity): value = STATE_UNKNOWN elif self._value_template is not None: self._state = self._value_template.render_with_possible_json_value( - value, STATE_UNKNOWN) + value, STATE_UNKNOWN + ) else: self._state = value @@ -137,13 +146,13 @@ class CommandSensorData: if command in cache: prog, args, args_compiled = cache[command] - elif ' ' not in command: + elif " " not in command: prog = command args = None args_compiled = None cache[command] = (prog, args, args_compiled) else: - prog, args = command.split(' ', 1) + prog, args = command.split(" ", 1) args_compiled = template.Template(args, self.hass) cache[command] = (prog, args, args_compiled) @@ -162,13 +171,14 @@ class CommandSensorData: shell = True else: # Template used. Construct the string used in the shell - command = str(' '.join([prog] + shlex.split(rendered_args))) + command = str(" ".join([prog] + shlex.split(rendered_args))) shell = True try: _LOGGER.debug("Running command: %s", command) return_value = subprocess.check_output( - command, shell=shell, timeout=self.timeout) - self.value = return_value.strip().decode('utf-8') + command, shell=shell, timeout=self.timeout + ) + self.value = return_value.strip().decode("utf-8") except subprocess.CalledProcessError: _LOGGER.error("Command failed: %s", command) except subprocess.TimeoutExpired: diff --git a/homeassistant/components/command_line/switch.py b/homeassistant/components/command_line/switch.py index 8d97198ad66..937e859197a 100644 --- a/homeassistant/components/command_line/switch.py +++ b/homeassistant/components/command_line/switch.py @@ -7,24 +7,34 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.switch import ( - SwitchDevice, PLATFORM_SCHEMA, ENTITY_ID_FORMAT) + SwitchDevice, + PLATFORM_SCHEMA, + ENTITY_ID_FORMAT, +) from homeassistant.const import ( - CONF_FRIENDLY_NAME, CONF_SWITCHES, CONF_VALUE_TEMPLATE, CONF_COMMAND_OFF, - CONF_COMMAND_ON, CONF_COMMAND_STATE) + CONF_FRIENDLY_NAME, + CONF_SWITCHES, + CONF_VALUE_TEMPLATE, + CONF_COMMAND_OFF, + CONF_COMMAND_ON, + CONF_COMMAND_STATE, +) _LOGGER = logging.getLogger(__name__) -SWITCH_SCHEMA = vol.Schema({ - vol.Optional(CONF_COMMAND_OFF, default='true'): cv.string, - vol.Optional(CONF_COMMAND_ON, default='true'): cv.string, - vol.Optional(CONF_COMMAND_STATE): cv.string, - vol.Optional(CONF_FRIENDLY_NAME): cv.string, - vol.Optional(CONF_VALUE_TEMPLATE): cv.template, -}) +SWITCH_SCHEMA = vol.Schema( + { + vol.Optional(CONF_COMMAND_OFF, default="true"): cv.string, + vol.Optional(CONF_COMMAND_ON, default="true"): cv.string, + vol.Optional(CONF_COMMAND_STATE): cv.string, + vol.Optional(CONF_FRIENDLY_NAME): cv.string, + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, + } +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_SWITCHES): cv.schema_with_slug_keys(SWITCH_SCHEMA), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_SWITCHES): cv.schema_with_slug_keys(SWITCH_SCHEMA)} +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -46,7 +56,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): device_config.get(CONF_COMMAND_ON), device_config.get(CONF_COMMAND_OFF), device_config.get(CONF_COMMAND_STATE), - value_template + value_template, ) ) @@ -60,8 +70,16 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class CommandSwitch(SwitchDevice): """Representation a switch that can be toggled using shell commands.""" - def __init__(self, hass, object_id, friendly_name, command_on, - command_off, command_state, value_template): + def __init__( + self, + hass, + object_id, + friendly_name, + command_on, + command_off, + command_state, + value_template, + ): """Initialize the switch.""" self._hass = hass self.entity_id = ENTITY_ID_FORMAT.format(object_id) @@ -77,7 +95,7 @@ class CommandSwitch(SwitchDevice): """Execute the actual commands.""" _LOGGER.info("Running command: %s", command) - success = (subprocess.call(command, shell=True) == 0) + success = subprocess.call(command, shell=True) == 0 if not success: _LOGGER.error("Command failed: %s", command) @@ -91,7 +109,7 @@ class CommandSwitch(SwitchDevice): try: return_value = subprocess.check_output(command, shell=True) - return return_value.strip().decode('utf-8') + return return_value.strip().decode("utf-8") except subprocess.CalledProcessError: _LOGGER.error("Command failed: %s", command) @@ -135,20 +153,17 @@ class CommandSwitch(SwitchDevice): if self._command_state: payload = str(self._query_state()) if self._value_template: - payload = self._value_template.render_with_possible_json_value( - payload) - self._state = (payload.lower() == 'true') + payload = self._value_template.render_with_possible_json_value(payload) + self._state = payload.lower() == "true" def turn_on(self, **kwargs): """Turn the device on.""" - if (CommandSwitch._switch(self._command_on) and - not self._command_state): + if CommandSwitch._switch(self._command_on) and not self._command_state: self._state = True self.schedule_update_ha_state() def turn_off(self, **kwargs): """Turn the device off.""" - if (CommandSwitch._switch(self._command_off) and - not self._command_state): + if CommandSwitch._switch(self._command_off) and not self._command_state: self._state = False self.schedule_update_ha_state() diff --git a/homeassistant/components/concord232/alarm_control_panel.py b/homeassistant/components/concord232/alarm_control_panel.py index c56e7e71531..2383700f42a 100644 --- a/homeassistant/components/concord232/alarm_control_panel.py +++ b/homeassistant/components/concord232/alarm_control_panel.py @@ -9,25 +9,34 @@ import homeassistant.components.alarm_control_panel as alarm import homeassistant.helpers.config_validation as cv from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_HOST, CONF_NAME, CONF_PORT, CONF_CODE, CONF_MODE, - STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED) + CONF_HOST, + CONF_NAME, + CONF_PORT, + CONF_CODE, + CONF_MODE, + STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_HOME, + STATE_ALARM_DISARMED, +) _LOGGER = logging.getLogger(__name__) -DEFAULT_HOST = 'localhost' -DEFAULT_NAME = 'CONCORD232' +DEFAULT_HOST = "localhost" +DEFAULT_NAME = "CONCORD232" DEFAULT_PORT = 5007 -DEFAULT_MODE = 'audible' +DEFAULT_MODE = "audible" SCAN_INTERVAL = datetime.timedelta(seconds=10) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_CODE): cv.string, - vol.Optional(CONF_MODE, default=DEFAULT_MODE): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_CODE): cv.string, + vol.Optional(CONF_MODE, default=DEFAULT_MODE): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -38,7 +47,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): host = config.get(CONF_HOST) port = config.get(CONF_PORT) - url = 'http://{}:{}'.format(host, port) + url = "http://{}:{}".format(host, port) try: add_entities([Concord232Alarm(url, name, code, mode)], True) @@ -82,16 +91,18 @@ class Concord232Alarm(alarm.AlarmControlPanel): try: part = self._alarm.list_partitions()[0] except requests.exceptions.ConnectionError as ex: - _LOGGER.error("Unable to connect to %(host)s: %(reason)s", - dict(host=self._url, reason=ex)) + _LOGGER.error( + "Unable to connect to %(host)s: %(reason)s", + dict(host=self._url, reason=ex), + ) return except IndexError: _LOGGER.error("Concord232 reports no partitions") return - if part['arming_level'] == 'Off': + if part["arming_level"] == "Off": self._state = STATE_ALARM_DISARMED - elif 'Home' in part['arming_level']: + elif "Home" in part["arming_level"]: self._state = STATE_ALARM_ARMED_HOME else: self._state = STATE_ALARM_ARMED_AWAY @@ -106,16 +117,16 @@ class Concord232Alarm(alarm.AlarmControlPanel): """Send arm home command.""" if not self._validate_code(code, STATE_ALARM_ARMED_HOME): return - if self._mode == 'silent': - self._alarm.arm('stay', 'silent') + if self._mode == "silent": + self._alarm.arm("stay", "silent") else: - self._alarm.arm('stay') + self._alarm.arm("stay") def alarm_arm_away(self, code=None): """Send arm away command.""" if not self._validate_code(code, STATE_ALARM_ARMED_AWAY): return - self._alarm.arm('away') + self._alarm.arm("away") def _validate_code(self, code, state): """Validate given code.""" @@ -124,8 +135,7 @@ class Concord232Alarm(alarm.AlarmControlPanel): if isinstance(self._code, str): alarm_code = self._code else: - alarm_code = self._code.render(from_state=self._state, - to_state=state) + alarm_code = self._code.render(from_state=self._state, to_state=state) check = not alarm_code or code == alarm_code if not check: _LOGGER.warning("Invalid code given for %s", state) diff --git a/homeassistant/components/concord232/binary_sensor.py b/homeassistant/components/concord232/binary_sensor.py index ae464da9798..89b6ab6af97 100644 --- a/homeassistant/components/concord232/binary_sensor.py +++ b/homeassistant/components/concord232/binary_sensor.py @@ -6,33 +6,37 @@ import requests import voluptuous as vol from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA, DEVICE_CLASSES) -from homeassistant.const import (CONF_HOST, CONF_PORT) + BinarySensorDevice, + PLATFORM_SCHEMA, + DEVICE_CLASSES, +) +from homeassistant.const import CONF_HOST, CONF_PORT import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -CONF_EXCLUDE_ZONES = 'exclude_zones' -CONF_ZONE_TYPES = 'zone_types' +CONF_EXCLUDE_ZONES = "exclude_zones" +CONF_ZONE_TYPES = "zone_types" -DEFAULT_HOST = 'localhost' -DEFAULT_NAME = 'Alarm' -DEFAULT_PORT = '5007' +DEFAULT_HOST = "localhost" +DEFAULT_NAME = "Alarm" +DEFAULT_PORT = "5007" DEFAULT_SSL = False SCAN_INTERVAL = datetime.timedelta(seconds=10) -ZONE_TYPES_SCHEMA = vol.Schema({ - cv.positive_int: vol.In(DEVICE_CLASSES), -}) +ZONE_TYPES_SCHEMA = vol.Schema({cv.positive_int: vol.In(DEVICE_CLASSES)}) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_EXCLUDE_ZONES, default=[]): - vol.All(cv.ensure_list, [cv.positive_int]), - vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_ZONE_TYPES, default={}): ZONE_TYPES_SCHEMA, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_EXCLUDE_ZONES, default=[]): vol.All( + cv.ensure_list, [cv.positive_int] + ), + vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_ZONE_TYPES, default={}): ZONE_TYPES_SCHEMA, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -47,7 +51,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): try: _LOGGER.debug("Initializing client") - client = concord232_client.Client('http://{}:{}'.format(host, port)) + client = concord232_client.Client("http://{}:{}".format(host, port)) client.zones = client.list_zones() client.last_zone_update = datetime.datetime.now() @@ -60,15 +64,17 @@ def setup_platform(hass, config, add_entities, discovery_info=None): # name mapping to different sensors in an unpredictable way. Sort # the zones by zone number to prevent this. - client.zones.sort(key=lambda zone: zone['number']) + client.zones.sort(key=lambda zone: zone["number"]) for zone in client.zones: - _LOGGER.info("Loading Zone found: %s", zone['name']) - if zone['number'] not in exclude: + _LOGGER.info("Loading Zone found: %s", zone["name"]) + if zone["number"] not in exclude: sensors.append( Concord232ZoneSensor( - hass, client, zone, zone_types.get( - zone['number'], get_opening_type(zone)) + hass, + client, + zone, + zone_types.get(zone["number"], get_opening_type(zone)), ) ) @@ -77,15 +83,15 @@ def setup_platform(hass, config, add_entities, discovery_info=None): def get_opening_type(zone): """Return the result of the type guessing from name.""" - if 'MOTION' in zone['name']: - return 'motion' - if 'KEY' in zone['name']: - return 'safety' - if 'SMOKE' in zone['name']: - return 'smoke' - if 'WATER' in zone['name']: - return 'water' - return 'opening' + if "MOTION" in zone["name"]: + return "motion" + if "KEY" in zone["name"]: + return "safety" + if "SMOKE" in zone["name"]: + return "smoke" + if "WATER" in zone["name"]: + return "water" + return "opening" class Concord232ZoneSensor(BinarySensorDevice): @@ -96,7 +102,7 @@ class Concord232ZoneSensor(BinarySensorDevice): self._hass = hass self._client = client self._zone = zone - self._number = zone['number'] + self._number = zone["number"] self._zone_type = zone_type @property @@ -112,13 +118,13 @@ class Concord232ZoneSensor(BinarySensorDevice): @property def name(self): """Return the name of the binary sensor.""" - return self._zone['name'] + return self._zone["name"] @property def is_on(self): """Return true if the binary sensor is on.""" # True means "faulted" or "open" or "abnormal state" - return bool(self._zone['state'] != 'Normal') + return bool(self._zone["state"] != "Normal") def update(self): """Get updated stats from API.""" @@ -127,8 +133,9 @@ class Concord232ZoneSensor(BinarySensorDevice): if last_update > datetime.timedelta(seconds=1): self._client.zones = self._client.list_zones() self._client.last_zone_update = datetime.datetime.now() - _LOGGER.debug("Updated from zone: %s", self._zone['name']) + _LOGGER.debug("Updated from zone: %s", self._zone["name"]) - if hasattr(self._client, 'zones'): - self._zone = next((x for x in self._client.zones - if x['number'] == self._number), None) + if hasattr(self._client, "zones"): + self._zone = next( + (x for x in self._client.zones if x["number"] == self._number), None + ) diff --git a/homeassistant/components/config/__init__.py b/homeassistant/components/config/__init__.py index 0cb76cc8c3b..5de11a032c5 100644 --- a/homeassistant/components/config/__init__.py +++ b/homeassistant/components/config/__init__.py @@ -11,31 +11,32 @@ from homeassistant.setup import ATTR_COMPONENT from homeassistant.components.http import HomeAssistantView from homeassistant.util.yaml import load_yaml, dump -DOMAIN = 'config' +DOMAIN = "config" SECTIONS = ( - 'area_registry', - 'auth', - 'auth_provider_homeassistant', - 'automation', - 'config_entries', - 'core', - 'customize', - 'device_registry', - 'entity_registry', - 'group', - 'script', + "area_registry", + "auth", + "auth_provider_homeassistant", + "automation", + "config_entries", + "core", + "customize", + "device_registry", + "entity_registry", + "group", + "script", ) -ON_DEMAND = ('zwave',) +ON_DEMAND = ("zwave",) async def async_setup(hass, config): """Set up the config component.""" hass.components.frontend.async_register_built_in_panel( - 'config', 'config', 'hass:settings', require_admin=True) + "config", "config", "hass:settings", require_admin=True + ) async def setup_panel(panel_name): """Set up a panel.""" - panel = importlib.import_module('.{}'.format(panel_name), __name__) + panel = importlib.import_module(".{}".format(panel_name), __name__) if not panel: return @@ -43,7 +44,7 @@ async def async_setup(hass, config): success = await panel.async_setup(hass) if success: - key = '{}.{}'.format(DOMAIN, panel_name) + key = "{}.{}".format(DOMAIN, panel_name) hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: key}) @callback @@ -70,11 +71,19 @@ async def async_setup(hass, config): class BaseEditConfigView(HomeAssistantView): """Configure a Group endpoint.""" - def __init__(self, component, config_type, path, key_schema, data_schema, - *, post_write_hook=None): + def __init__( + self, + component, + config_type, + path, + key_schema, + data_schema, + *, + post_write_hook=None, + ): """Initialize a config view.""" - self.url = '/api/config/%s/%s/{config_key}' % (component, config_type) - self.name = 'api:config:%s:%s' % (component, config_type) + self.url = "/api/config/%s/%s/{config_key}" % (component, config_type) + self.name = "api:config:%s:%s" % (component, config_type) self.path = path self.key_schema = key_schema self.data_schema = data_schema @@ -98,12 +107,12 @@ class BaseEditConfigView(HomeAssistantView): async def get(self, request, config_key): """Fetch device specific config.""" - hass = request.app['hass'] + hass = request.app["hass"] current = await self.read_config(hass) value = self._get_value(hass, current, config_key) if value is None: - return self.json_message('Resource not found', 404) + return self.json_message("Resource not found", 404) return self.json(value) @@ -112,21 +121,21 @@ class BaseEditConfigView(HomeAssistantView): try: data = await request.json() except ValueError: - return self.json_message('Invalid JSON specified', 400) + return self.json_message("Invalid JSON specified", 400) try: self.key_schema(config_key) except vol.Invalid as err: - return self.json_message('Key malformed: {}'.format(err), 400) + return self.json_message("Key malformed: {}".format(err), 400) try: # We just validate, we don't store that data because # we don't want to store the defaults. self.data_schema(data) except vol.Invalid as err: - return self.json_message('Message malformed: {}'.format(err), 400) + return self.json_message("Message malformed: {}".format(err), 400) - hass = request.app['hass'] + hass = request.app["hass"] path = hass.config.path(self.path) current = await self.read_config(hass) @@ -137,19 +146,17 @@ class BaseEditConfigView(HomeAssistantView): if self.post_write_hook is not None: hass.async_create_task(self.post_write_hook(hass)) - return self.json({ - 'result': 'ok', - }) + return self.json({"result": "ok"}) async def delete(self, request, config_key): """Remove an entry.""" - hass = request.app['hass'] + hass = request.app["hass"] current = await self.read_config(hass) value = self._get_value(hass, current, config_key) path = hass.config.path(self.path) if value is None: - return self.json_message('Resource not found', 404) + return self.json_message("Resource not found", 404) self._delete_value(hass, current, config_key) await hass.async_add_executor_job(_write, path, current) @@ -157,14 +164,11 @@ class BaseEditConfigView(HomeAssistantView): if self.post_write_hook is not None: hass.async_create_task(self.post_write_hook(hass)) - return self.json({ - 'result': 'ok', - }) + return self.json({"result": "ok"}) async def read_config(self, hass): """Read the config.""" - current = await hass.async_add_job( - _read, hass.config.path(self.path)) + current = await hass.async_add_job(_read, hass.config.path(self.path)) if not current: current = self._empty_config() return current @@ -199,8 +203,7 @@ class EditIdBasedConfigView(BaseEditConfigView): def _get_value(self, hass, data, config_key): """Get value.""" - return next( - (val for val in data if val.get(CONF_ID) == config_key), None) + return next((val for val in data if val.get(CONF_ID) == config_key), None) def _write_value(self, hass, data, config_key, new_value): """Set value.""" @@ -215,8 +218,8 @@ class EditIdBasedConfigView(BaseEditConfigView): def _delete_value(self, hass, data, config_key): """Delete value.""" index = next( - idx for idx, val in enumerate(data) - if val.get(CONF_ID) == config_key) + idx for idx, val in enumerate(data) if val.get(CONF_ID) == config_key + ) data.pop(index) @@ -233,5 +236,5 @@ def _write(path, data): # Do it before opening file. If dump causes error it will now not # truncate the file. data = dump(data) - with open(path, 'w', encoding='utf-8') as outfile: + with open(path, "w", encoding="utf-8") as outfile: outfile.write(data) diff --git a/homeassistant/components/config/area_registry.py b/homeassistant/components/config/area_registry.py index 06fc3eae34d..9c8853ac782 100644 --- a/homeassistant/components/config/area_registry.py +++ b/homeassistant/components/config/area_registry.py @@ -3,34 +3,36 @@ import voluptuous as vol from homeassistant.components import websocket_api from homeassistant.components.websocket_api.decorators import ( - async_response, require_admin) + async_response, + require_admin, +) from homeassistant.core import callback from homeassistant.helpers.area_registry import async_get_registry -WS_TYPE_LIST = 'config/area_registry/list' -SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_LIST, -}) +WS_TYPE_LIST = "config/area_registry/list" +SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {vol.Required("type"): WS_TYPE_LIST} +) -WS_TYPE_CREATE = 'config/area_registry/create' -SCHEMA_WS_CREATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_CREATE, - vol.Required('name'): str, -}) +WS_TYPE_CREATE = "config/area_registry/create" +SCHEMA_WS_CREATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {vol.Required("type"): WS_TYPE_CREATE, vol.Required("name"): str} +) -WS_TYPE_DELETE = 'config/area_registry/delete' -SCHEMA_WS_DELETE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_DELETE, - vol.Required('area_id'): str, -}) +WS_TYPE_DELETE = "config/area_registry/delete" +SCHEMA_WS_DELETE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {vol.Required("type"): WS_TYPE_DELETE, vol.Required("area_id"): str} +) -WS_TYPE_UPDATE = 'config/area_registry/update' -SCHEMA_WS_UPDATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_UPDATE, - vol.Required('area_id'): str, - vol.Required('name'): str, -}) +WS_TYPE_UPDATE = "config/area_registry/update" +SCHEMA_WS_UPDATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + { + vol.Required("type"): WS_TYPE_UPDATE, + vol.Required("area_id"): str, + vol.Required("name"): str, + } +) async def async_setup(hass): @@ -54,12 +56,15 @@ async def async_setup(hass): async def websocket_list_areas(hass, connection, msg): """Handle list areas command.""" registry = await async_get_registry(hass) - connection.send_message(websocket_api.result_message( - msg['id'], [{ - 'name': entry.name, - 'area_id': entry.id, - } for entry in registry.async_list_areas()] - )) + connection.send_message( + websocket_api.result_message( + msg["id"], + [ + {"name": entry.name, "area_id": entry.id} + for entry in registry.async_list_areas() + ], + ) + ) @require_admin @@ -68,15 +73,15 @@ async def websocket_create_area(hass, connection, msg): """Create area command.""" registry = await async_get_registry(hass) try: - entry = registry.async_create(msg['name']) + entry = registry.async_create(msg["name"]) except ValueError as err: - connection.send_message(websocket_api.error_message( - msg['id'], 'invalid_info', str(err) - )) + connection.send_message( + websocket_api.error_message(msg["id"], "invalid_info", str(err)) + ) else: - connection.send_message(websocket_api.result_message( - msg['id'], _entry_dict(entry) - )) + connection.send_message( + websocket_api.result_message(msg["id"], _entry_dict(entry)) + ) @require_admin @@ -86,15 +91,15 @@ async def websocket_delete_area(hass, connection, msg): registry = await async_get_registry(hass) try: - await registry.async_delete(msg['area_id']) + await registry.async_delete(msg["area_id"]) except KeyError: - connection.send_message(websocket_api.error_message( - msg['id'], 'invalid_info', "Area ID doesn't exist" - )) + connection.send_message( + websocket_api.error_message( + msg["id"], "invalid_info", "Area ID doesn't exist" + ) + ) else: - connection.send_message(websocket_api.result_message( - msg['id'], 'success' - )) + connection.send_message(websocket_api.result_message(msg["id"], "success")) @require_admin @@ -104,21 +109,18 @@ async def websocket_update_area(hass, connection, msg): registry = await async_get_registry(hass) try: - entry = registry.async_update(msg['area_id'], msg['name']) + entry = registry.async_update(msg["area_id"], msg["name"]) except ValueError as err: - connection.send_message(websocket_api.error_message( - msg['id'], 'invalid_info', str(err) - )) + connection.send_message( + websocket_api.error_message(msg["id"], "invalid_info", str(err)) + ) else: - connection.send_message(websocket_api.result_message( - msg['id'], _entry_dict(entry) - )) + connection.send_message( + websocket_api.result_message(msg["id"], _entry_dict(entry)) + ) @callback def _entry_dict(entry): """Convert entry to API format.""" - return { - 'area_id': entry.id, - 'name': entry.name - } + return {"area_id": entry.id, "name": entry.name} diff --git a/homeassistant/components/config/auth.py b/homeassistant/components/config/auth.py index e6451e09a98..977bae36083 100644 --- a/homeassistant/components/config/auth.py +++ b/homeassistant/components/config/auth.py @@ -4,37 +4,32 @@ import voluptuous as vol from homeassistant.components import websocket_api -WS_TYPE_LIST = 'config/auth/list' -SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_LIST, -}) +WS_TYPE_LIST = "config/auth/list" +SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {vol.Required("type"): WS_TYPE_LIST} +) -WS_TYPE_DELETE = 'config/auth/delete' -SCHEMA_WS_DELETE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_DELETE, - vol.Required('user_id'): str, -}) +WS_TYPE_DELETE = "config/auth/delete" +SCHEMA_WS_DELETE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {vol.Required("type"): WS_TYPE_DELETE, vol.Required("user_id"): str} +) -WS_TYPE_CREATE = 'config/auth/create' -SCHEMA_WS_CREATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_CREATE, - vol.Required('name'): str, -}) +WS_TYPE_CREATE = "config/auth/create" +SCHEMA_WS_CREATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {vol.Required("type"): WS_TYPE_CREATE, vol.Required("name"): str} +) async def async_setup(hass): """Enable the Home Assistant views.""" hass.components.websocket_api.async_register_command( - WS_TYPE_LIST, websocket_list, - SCHEMA_WS_LIST + WS_TYPE_LIST, websocket_list, SCHEMA_WS_LIST ) hass.components.websocket_api.async_register_command( - WS_TYPE_DELETE, websocket_delete, - SCHEMA_WS_DELETE + WS_TYPE_DELETE, websocket_delete, SCHEMA_WS_DELETE ) hass.components.websocket_api.async_register_command( - WS_TYPE_CREATE, websocket_create, - SCHEMA_WS_CREATE + WS_TYPE_CREATE, websocket_create, SCHEMA_WS_CREATE ) hass.components.websocket_api.async_register_command(websocket_update) return True @@ -46,91 +41,95 @@ async def websocket_list(hass, connection, msg): """Return a list of users.""" result = [_user_info(u) for u in await hass.auth.async_get_users()] - connection.send_message( - websocket_api.result_message(msg['id'], result)) + connection.send_message(websocket_api.result_message(msg["id"], result)) @websocket_api.require_admin @websocket_api.async_response async def websocket_delete(hass, connection, msg): """Delete a user.""" - if msg['user_id'] == connection.user.id: - connection.send_message(websocket_api.error_message( - msg['id'], 'no_delete_self', - 'Unable to delete your own account')) + if msg["user_id"] == connection.user.id: + connection.send_message( + websocket_api.error_message( + msg["id"], "no_delete_self", "Unable to delete your own account" + ) + ) return - user = await hass.auth.async_get_user(msg['user_id']) + user = await hass.auth.async_get_user(msg["user_id"]) if not user: - connection.send_message(websocket_api.error_message( - msg['id'], 'not_found', 'User not found')) + connection.send_message( + websocket_api.error_message(msg["id"], "not_found", "User not found") + ) return await hass.auth.async_remove_user(user) - connection.send_message( - websocket_api.result_message(msg['id'])) + connection.send_message(websocket_api.result_message(msg["id"])) @websocket_api.require_admin @websocket_api.async_response async def websocket_create(hass, connection, msg): """Create a user.""" - user = await hass.auth.async_create_user(msg['name']) + user = await hass.auth.async_create_user(msg["name"]) connection.send_message( - websocket_api.result_message(msg['id'], { - 'user': _user_info(user) - })) + websocket_api.result_message(msg["id"], {"user": _user_info(user)}) + ) @websocket_api.require_admin @websocket_api.async_response -@websocket_api.websocket_command({ - vol.Required('type'): 'config/auth/update', - vol.Required('user_id'): str, - vol.Optional('name'): str, - vol.Optional('group_ids'): [str] -}) +@websocket_api.websocket_command( + { + vol.Required("type"): "config/auth/update", + vol.Required("user_id"): str, + vol.Optional("name"): str, + vol.Optional("group_ids"): [str], + } +) async def websocket_update(hass, connection, msg): """Update a user.""" - user = await hass.auth.async_get_user(msg.pop('user_id')) + user = await hass.auth.async_get_user(msg.pop("user_id")) if not user: - connection.send_message(websocket_api.error_message( - msg['id'], websocket_api.const.ERR_NOT_FOUND, 'User not found')) + connection.send_message( + websocket_api.error_message( + msg["id"], websocket_api.const.ERR_NOT_FOUND, "User not found" + ) + ) return if user.system_generated: - connection.send_message(websocket_api.error_message( - msg['id'], 'cannot_modify_system_generated', - 'Unable to update system generated users.')) + connection.send_message( + websocket_api.error_message( + msg["id"], + "cannot_modify_system_generated", + "Unable to update system generated users.", + ) + ) return - msg.pop('type') - msg_id = msg.pop('id') + msg.pop("type") + msg_id = msg.pop("id") await hass.auth.async_update_user(user, **msg) connection.send_message( - websocket_api.result_message(msg_id, { - 'user': _user_info(user), - })) + websocket_api.result_message(msg_id, {"user": _user_info(user)}) + ) def _user_info(user): """Format a user.""" return { - 'id': user.id, - 'name': user.name, - 'is_owner': user.is_owner, - 'is_active': user.is_active, - 'system_generated': user.system_generated, - 'group_ids': [group.id for group in user.groups], - 'credentials': [ - { - 'type': c.auth_provider_type, - } for c in user.credentials - ] + "id": user.id, + "name": user.name, + "is_owner": user.is_owner, + "is_active": user.is_active, + "system_generated": user.system_generated, + "group_ids": [group.id for group in user.groups], + "credentials": [{"type": c.auth_provider_type} for c in user.credentials], } diff --git a/homeassistant/components/config/auth_provider_homeassistant.py b/homeassistant/components/config/auth_provider_homeassistant.py index f6fc4bc8cef..817675db238 100644 --- a/homeassistant/components/config/auth_provider_homeassistant.py +++ b/homeassistant/components/config/auth_provider_homeassistant.py @@ -5,41 +5,41 @@ from homeassistant.auth.providers import homeassistant as auth_ha from homeassistant.components import websocket_api -WS_TYPE_CREATE = 'config/auth_provider/homeassistant/create' -SCHEMA_WS_CREATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_CREATE, - vol.Required('user_id'): str, - vol.Required('username'): str, - vol.Required('password'): str, -}) +WS_TYPE_CREATE = "config/auth_provider/homeassistant/create" +SCHEMA_WS_CREATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + { + vol.Required("type"): WS_TYPE_CREATE, + vol.Required("user_id"): str, + vol.Required("username"): str, + vol.Required("password"): str, + } +) -WS_TYPE_DELETE = 'config/auth_provider/homeassistant/delete' -SCHEMA_WS_DELETE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_DELETE, - vol.Required('username'): str, -}) +WS_TYPE_DELETE = "config/auth_provider/homeassistant/delete" +SCHEMA_WS_DELETE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {vol.Required("type"): WS_TYPE_DELETE, vol.Required("username"): str} +) -WS_TYPE_CHANGE_PASSWORD = 'config/auth_provider/homeassistant/change_password' -SCHEMA_WS_CHANGE_PASSWORD = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_CHANGE_PASSWORD, - vol.Required('current_password'): str, - vol.Required('new_password'): str -}) +WS_TYPE_CHANGE_PASSWORD = "config/auth_provider/homeassistant/change_password" +SCHEMA_WS_CHANGE_PASSWORD = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + { + vol.Required("type"): WS_TYPE_CHANGE_PASSWORD, + vol.Required("current_password"): str, + vol.Required("new_password"): str, + } +) async def async_setup(hass): """Enable the Home Assistant views.""" hass.components.websocket_api.async_register_command( - WS_TYPE_CREATE, websocket_create, - SCHEMA_WS_CREATE + WS_TYPE_CREATE, websocket_create, SCHEMA_WS_CREATE ) hass.components.websocket_api.async_register_command( - WS_TYPE_DELETE, websocket_delete, - SCHEMA_WS_DELETE + WS_TYPE_DELETE, websocket_delete, SCHEMA_WS_DELETE ) hass.components.websocket_api.async_register_command( - WS_TYPE_CHANGE_PASSWORD, websocket_change_password, - SCHEMA_WS_CHANGE_PASSWORD + WS_TYPE_CHANGE_PASSWORD, websocket_change_password, SCHEMA_WS_CHANGE_PASSWORD ) return True @@ -47,10 +47,10 @@ async def async_setup(hass): def _get_provider(hass): """Get homeassistant auth provider.""" for prv in hass.auth.auth_providers: - if prv.type == 'homeassistant': + if prv.type == "homeassistant": return prv - raise RuntimeError('Provider not found') + raise RuntimeError("Provider not found") @websocket_api.require_admin @@ -60,34 +60,43 @@ async def websocket_create(hass, connection, msg): provider = _get_provider(hass) await provider.async_initialize() - user = await hass.auth.async_get_user(msg['user_id']) + user = await hass.auth.async_get_user(msg["user_id"]) if user is None: - connection.send_message(websocket_api.error_message( - msg['id'], 'not_found', 'User not found')) + connection.send_message( + websocket_api.error_message(msg["id"], "not_found", "User not found") + ) return if user.system_generated: - connection.send_message(websocket_api.error_message( - msg['id'], 'system_generated', - 'Cannot add credentials to a system generated user.')) + connection.send_message( + websocket_api.error_message( + msg["id"], + "system_generated", + "Cannot add credentials to a system generated user.", + ) + ) return try: await hass.async_add_executor_job( - provider.data.add_auth, msg['username'], msg['password']) + provider.data.add_auth, msg["username"], msg["password"] + ) except auth_ha.InvalidUser: - connection.send_message(websocket_api.error_message( - msg['id'], 'username_exists', 'Username already exists')) + connection.send_message( + websocket_api.error_message( + msg["id"], "username_exists", "Username already exists" + ) + ) return - credentials = await provider.async_get_or_create_credentials({ - 'username': msg['username'] - }) + credentials = await provider.async_get_or_create_credentials( + {"username": msg["username"]} + ) await hass.auth.async_link_user(user, credentials) await provider.data.async_save() - connection.send_message(websocket_api.result_message(msg['id'])) + connection.send_message(websocket_api.result_message(msg["id"])) @websocket_api.require_admin @@ -97,29 +106,30 @@ async def websocket_delete(hass, connection, msg): provider = _get_provider(hass) await provider.async_initialize() - credentials = await provider.async_get_or_create_credentials({ - 'username': msg['username'] - }) + credentials = await provider.async_get_or_create_credentials( + {"username": msg["username"]} + ) # if not new, an existing credential exists. # Removing the credential will also remove the auth. if not credentials.is_new: await hass.auth.async_remove_credentials(credentials) - connection.send_message( - websocket_api.result_message(msg['id'])) + connection.send_message(websocket_api.result_message(msg["id"])) return try: - provider.data.async_remove_auth(msg['username']) + provider.data.async_remove_auth(msg["username"]) await provider.data.async_save() except auth_ha.InvalidUser: - connection.send_message(websocket_api.error_message( - msg['id'], 'auth_not_found', 'Given username was not found.')) + connection.send_message( + websocket_api.error_message( + msg["id"], "auth_not_found", "Given username was not found." + ) + ) return - connection.send_message( - websocket_api.result_message(msg['id'])) + connection.send_message(websocket_api.result_message(msg["id"])) @websocket_api.async_response @@ -127,8 +137,9 @@ async def websocket_change_password(hass, connection, msg): """Change user password.""" user = connection.user if user is None: - connection.send_message(websocket_api.error_message( - msg['id'], 'user_not_found', 'User not found')) + connection.send_message( + websocket_api.error_message(msg["id"], "user_not_found", "User not found") + ) return provider = _get_provider(hass) @@ -137,25 +148,30 @@ async def websocket_change_password(hass, connection, msg): username = None for credential in user.credentials: if credential.auth_provider_type == provider.type: - username = credential.data['username'] + username = credential.data["username"] break if username is None: - connection.send_message(websocket_api.error_message( - msg['id'], 'credentials_not_found', 'Credentials not found')) + connection.send_message( + websocket_api.error_message( + msg["id"], "credentials_not_found", "Credentials not found" + ) + ) return try: - await provider.async_validate_login( - username, msg['current_password']) + await provider.async_validate_login(username, msg["current_password"]) except auth_ha.InvalidAuth: - connection.send_message(websocket_api.error_message( - msg['id'], 'invalid_password', 'Invalid password')) + connection.send_message( + websocket_api.error_message( + msg["id"], "invalid_password", "Invalid password" + ) + ) return await hass.async_add_executor_job( - provider.data.change_password, username, msg['new_password']) + provider.data.change_password, username, msg["new_password"] + ) await provider.data.async_save() - connection.send_message( - websocket_api.result_message(msg['id'])) + connection.send_message(websocket_api.result_message(msg["id"])) diff --git a/homeassistant/components/config/automation.py b/homeassistant/components/config/automation.py index 175d90ff59c..7ca71fc4f93 100644 --- a/homeassistant/components/config/automation.py +++ b/homeassistant/components/config/automation.py @@ -8,19 +8,26 @@ import homeassistant.helpers.config_validation as cv from . import EditIdBasedConfigView -CONFIG_PATH = 'automations.yaml' +CONFIG_PATH = "automations.yaml" async def async_setup(hass): """Set up the Automation config API.""" + async def hook(hass): """post_write_hook for Config View that reloads automations.""" await hass.services.async_call(DOMAIN, SERVICE_RELOAD) - hass.http.register_view(EditAutomationConfigView( - DOMAIN, 'config', CONFIG_PATH, cv.string, - PLATFORM_SCHEMA, post_write_hook=hook - )) + hass.http.register_view( + EditAutomationConfigView( + DOMAIN, + "config", + CONFIG_PATH, + cv.string, + PLATFORM_SCHEMA, + post_write_hook=hook, + ) + ) return True @@ -46,7 +53,7 @@ class EditAutomationConfigView(EditIdBasedConfigView): # Iterate through some keys that we want to have ordered in the output updated_value = OrderedDict() - for key in ('id', 'alias', 'trigger', 'condition', 'action'): + for key in ("id", "alias", "trigger", "condition", "action"): if key in cur_value: updated_value[key] = cur_value[key] if key in new_value: diff --git a/homeassistant/components/config/config_entries.py b/homeassistant/components/config/config_entries.py index 9687a407ccb..140b5a2b270 100644 --- a/homeassistant/components/config/config_entries.py +++ b/homeassistant/components/config/config_entries.py @@ -4,7 +4,9 @@ from homeassistant.auth.permissions.const import CAT_CONFIG_ENTRIES from homeassistant.components.http import HomeAssistantView from homeassistant.exceptions import Unauthorized from homeassistant.helpers.data_entry_flow import ( - FlowManagerIndexView, FlowManagerResourceView) + FlowManagerIndexView, + FlowManagerResourceView, +) from homeassistant.loader import async_get_config_flows @@ -12,32 +14,32 @@ async def async_setup(hass): """Enable the Home Assistant views.""" hass.http.register_view(ConfigManagerEntryIndexView) hass.http.register_view(ConfigManagerEntryResourceView) - hass.http.register_view( - ConfigManagerFlowIndexView(hass.config_entries.flow)) - hass.http.register_view( - ConfigManagerFlowResourceView(hass.config_entries.flow)) + hass.http.register_view(ConfigManagerFlowIndexView(hass.config_entries.flow)) + hass.http.register_view(ConfigManagerFlowResourceView(hass.config_entries.flow)) hass.http.register_view(ConfigManagerAvailableFlowView) hass.http.register_view( - OptionManagerFlowIndexView(hass.config_entries.options.flow)) + OptionManagerFlowIndexView(hass.config_entries.options.flow) + ) hass.http.register_view( - OptionManagerFlowResourceView(hass.config_entries.options.flow)) + OptionManagerFlowResourceView(hass.config_entries.options.flow) + ) return True def _prepare_json(result): """Convert result for JSON.""" - if result['type'] != data_entry_flow.RESULT_TYPE_FORM: + if result["type"] != data_entry_flow.RESULT_TYPE_FORM: return result import voluptuous_serialize data = result.copy() - schema = data['data_schema'] + schema = data["data_schema"] if schema is None: - data['data_schema'] = [] + data["data_schema"] = [] else: - data['data_schema'] = voluptuous_serialize.convert(schema) + data["data_schema"] = voluptuous_serialize.convert(schema) return data @@ -45,43 +47,49 @@ def _prepare_json(result): class ConfigManagerEntryIndexView(HomeAssistantView): """View to get available config entries.""" - url = '/api/config/config_entries/entry' - name = 'api:config:config_entries:entry' + url = "/api/config/config_entries/entry" + name = "api:config:config_entries:entry" async def get(self, request): """List available config entries.""" - hass = request.app['hass'] + hass = request.app["hass"] - return self.json([{ - 'entry_id': entry.entry_id, - 'domain': entry.domain, - 'title': entry.title, - 'source': entry.source, - 'state': entry.state, - 'connection_class': entry.connection_class, - 'supports_options': hasattr( - config_entries.HANDLERS.get(entry.domain), - 'async_get_options_flow'), - } for entry in hass.config_entries.async_entries()]) + return self.json( + [ + { + "entry_id": entry.entry_id, + "domain": entry.domain, + "title": entry.title, + "source": entry.source, + "state": entry.state, + "connection_class": entry.connection_class, + "supports_options": hasattr( + config_entries.HANDLERS.get(entry.domain), + "async_get_options_flow", + ), + } + for entry in hass.config_entries.async_entries() + ] + ) class ConfigManagerEntryResourceView(HomeAssistantView): """View to interact with a config entry.""" - url = '/api/config/config_entries/entry/{entry_id}' - name = 'api:config:config_entries:entry:resource' + url = "/api/config/config_entries/entry/{entry_id}" + name = "api:config:config_entries:entry:resource" async def delete(self, request, entry_id): """Delete a config entry.""" - if not request['hass_user'].is_admin: - raise Unauthorized(config_entry_id=entry_id, permission='remove') + if not request["hass_user"].is_admin: + raise Unauthorized(config_entry_id=entry_id, permission="remove") - hass = request.app['hass'] + hass = request.app["hass"] try: result = await hass.config_entries.async_remove(entry_id) except config_entries.UnknownEntry: - return self.json_message('Invalid entry specified', 404) + return self.json_message("Invalid entry specified", 404) return self.json(result) @@ -89,8 +97,8 @@ class ConfigManagerEntryResourceView(HomeAssistantView): class ConfigManagerFlowIndexView(FlowManagerIndexView): """View to create config flows.""" - url = '/api/config/config_entries/flow' - name = 'api:config:config_entries:flow' + url = "/api/config/config_entries/flow" + name = "api:config:config_entries:flow" async def get(self, request): """List flows that are in progress but not started by a user. @@ -98,89 +106,89 @@ class ConfigManagerFlowIndexView(FlowManagerIndexView): Example of a non-user initiated flow is a discovered Hue hub that requires user interaction to finish setup. """ - if not request['hass_user'].is_admin: - raise Unauthorized( - perm_category=CAT_CONFIG_ENTRIES, permission='add') + if not request["hass_user"].is_admin: + raise Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission="add") - hass = request.app['hass'] + hass = request.app["hass"] - return self.json([ - flw for flw in hass.config_entries.flow.async_progress() - if flw['context']['source'] != config_entries.SOURCE_USER]) + return self.json( + [ + flw + for flw in hass.config_entries.flow.async_progress() + if flw["context"]["source"] != config_entries.SOURCE_USER + ] + ) # pylint: disable=arguments-differ async def post(self, request): """Handle a POST request.""" - if not request['hass_user'].is_admin: - raise Unauthorized( - perm_category=CAT_CONFIG_ENTRIES, permission='add') + if not request["hass_user"].is_admin: + raise Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission="add") # pylint: disable=no-value-for-parameter return await super().post(request) def _prepare_result_json(self, result): """Convert result to JSON.""" - if result['type'] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY: + if result["type"] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY: return super()._prepare_result_json(result) data = result.copy() - data['result'] = data['result'].entry_id - data.pop('data') + data["result"] = data["result"].entry_id + data.pop("data") return data class ConfigManagerFlowResourceView(FlowManagerResourceView): """View to interact with the flow manager.""" - url = '/api/config/config_entries/flow/{flow_id}' - name = 'api:config:config_entries:flow:resource' + url = "/api/config/config_entries/flow/{flow_id}" + name = "api:config:config_entries:flow:resource" async def get(self, request, flow_id): """Get the current state of a data_entry_flow.""" - if not request['hass_user'].is_admin: - raise Unauthorized( - perm_category=CAT_CONFIG_ENTRIES, permission='add') + if not request["hass_user"].is_admin: + raise Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission="add") return await super().get(request, flow_id) # pylint: disable=arguments-differ async def post(self, request, flow_id): """Handle a POST request.""" - if not request['hass_user'].is_admin: - raise Unauthorized( - perm_category=CAT_CONFIG_ENTRIES, permission='add') + if not request["hass_user"].is_admin: + raise Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission="add") # pylint: disable=no-value-for-parameter return await super().post(request, flow_id) def _prepare_result_json(self, result): """Convert result to JSON.""" - if result['type'] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY: + if result["type"] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY: return super()._prepare_result_json(result) data = result.copy() - data['result'] = data['result'].entry_id - data.pop('data') + data["result"] = data["result"].entry_id + data.pop("data") return data class ConfigManagerAvailableFlowView(HomeAssistantView): """View to query available flows.""" - url = '/api/config/config_entries/flow_handlers' - name = 'api:config:config_entries:flow_handlers' + url = "/api/config/config_entries/flow_handlers" + name = "api:config:config_entries:flow_handlers" async def get(self, request): """List available flow handlers.""" - hass = request.app['hass'] + hass = request.app["hass"] return self.json(await async_get_config_flows(hass)) class OptionManagerFlowIndexView(FlowManagerIndexView): """View to create option flows.""" - url = '/api/config/config_entries/entry/option/flow' - name = 'api:config:config_entries:entry:resource:option:flow' + url = "/api/config/config_entries/entry/option/flow" + name = "api:config:config_entries:entry:resource:option:flow" # pylint: disable=arguments-differ async def post(self, request): @@ -188,9 +196,8 @@ class OptionManagerFlowIndexView(FlowManagerIndexView): handler in request is entry_id. """ - if not request['hass_user'].is_admin: - raise Unauthorized( - perm_category=CAT_CONFIG_ENTRIES, permission='edit') + if not request["hass_user"].is_admin: + raise Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission="edit") # pylint: disable=no-value-for-parameter return await super().post(request) @@ -199,23 +206,21 @@ class OptionManagerFlowIndexView(FlowManagerIndexView): class OptionManagerFlowResourceView(FlowManagerResourceView): """View to interact with the option flow manager.""" - url = '/api/config/config_entries/options/flow/{flow_id}' - name = 'api:config:config_entries:options:flow:resource' + url = "/api/config/config_entries/options/flow/{flow_id}" + name = "api:config:config_entries:options:flow:resource" async def get(self, request, flow_id): """Get the current state of a data_entry_flow.""" - if not request['hass_user'].is_admin: - raise Unauthorized( - perm_category=CAT_CONFIG_ENTRIES, permission='edit') + if not request["hass_user"].is_admin: + raise Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission="edit") return await super().get(request, flow_id) # pylint: disable=arguments-differ async def post(self, request, flow_id): """Handle a POST request.""" - if not request['hass_user'].is_admin: - raise Unauthorized( - perm_category=CAT_CONFIG_ENTRIES, permission='edit') + if not request["hass_user"].is_admin: + raise Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission="edit") # pylint: disable=no-value-for-parameter return await super().post(request, flow_id) diff --git a/homeassistant/components/config/core.py b/homeassistant/components/config/core.py index a83516bdc37..073f8f23d6c 100644 --- a/homeassistant/components/config/core.py +++ b/homeassistant/components/config/core.py @@ -5,9 +5,7 @@ import voluptuous as vol from homeassistant.components.http import HomeAssistantView from homeassistant.config import async_check_ha_config_file from homeassistant.components import websocket_api -from homeassistant.const import ( - CONF_UNIT_SYSTEM_METRIC, CONF_UNIT_SYSTEM_IMPERIAL -) +from homeassistant.const import CONF_UNIT_SYSTEM_METRIC, CONF_UNIT_SYSTEM_IMPERIAL from homeassistant.helpers import config_validation as cv from homeassistant.util import location @@ -23,52 +21,47 @@ async def async_setup(hass): class CheckConfigView(HomeAssistantView): """Hassbian packages endpoint.""" - url = '/api/config/core/check_config' - name = 'api:config:core:check_config' + url = "/api/config/core/check_config" + name = "api:config:core:check_config" async def post(self, request): """Validate configuration and return results.""" - errors = await async_check_ha_config_file(request.app['hass']) + errors = await async_check_ha_config_file(request.app["hass"]) - state = 'invalid' if errors else 'valid' + state = "invalid" if errors else "valid" - return self.json({ - "result": state, - "errors": errors, - }) + return self.json({"result": state, "errors": errors}) @websocket_api.require_admin @websocket_api.async_response -@websocket_api.websocket_command({ - 'type': 'config/core/update', - vol.Optional('latitude'): cv.latitude, - vol.Optional('longitude'): cv.longitude, - vol.Optional('elevation'): int, - vol.Optional('unit_system'): cv.unit_system, - vol.Optional('location_name'): str, - vol.Optional('time_zone'): cv.time_zone, -}) +@websocket_api.websocket_command( + { + "type": "config/core/update", + vol.Optional("latitude"): cv.latitude, + vol.Optional("longitude"): cv.longitude, + vol.Optional("elevation"): int, + vol.Optional("unit_system"): cv.unit_system, + vol.Optional("location_name"): str, + vol.Optional("time_zone"): cv.time_zone, + } +) async def websocket_update_config(hass, connection, msg): """Handle update core config command.""" data = dict(msg) - data.pop('id') - data.pop('type') + data.pop("id") + data.pop("type") try: await hass.config.async_update(**data) - connection.send_result(msg['id']) + connection.send_result(msg["id"]) except ValueError as err: - connection.send_error( - msg['id'], 'invalid_info', str(err) - ) + connection.send_error(msg["id"], "invalid_info", str(err)) @websocket_api.require_admin @websocket_api.async_response -@websocket_api.websocket_command({ - 'type': 'config/core/detect', -}) +@websocket_api.websocket_command({"type": "config/core/detect"}) async def websocket_detect_config(hass, connection, msg): """Detect core config.""" session = hass.helpers.aiohttp_client.async_get_clientsession() @@ -77,21 +70,21 @@ async def websocket_detect_config(hass, connection, msg): info = {} if location_info is None: - connection.send_result(msg['id'], info) + connection.send_result(msg["id"], info) return if location_info.use_metric: - info['unit_system'] = CONF_UNIT_SYSTEM_METRIC + info["unit_system"] = CONF_UNIT_SYSTEM_METRIC else: - info['unit_system'] = CONF_UNIT_SYSTEM_IMPERIAL + info["unit_system"] = CONF_UNIT_SYSTEM_IMPERIAL if location_info.latitude: - info['latitude'] = location_info.latitude + info["latitude"] = location_info.latitude if location_info.longitude: - info['longitude'] = location_info.longitude + info["longitude"] = location_info.longitude if location_info.time_zone: - info['time_zone'] = location_info.time_zone + info["time_zone"] = location_info.time_zone - connection.send_result(msg['id'], info) + connection.send_result(msg["id"], info) diff --git a/homeassistant/components/config/customize.py b/homeassistant/components/config/customize.py index 85e9c0e6886..ed75a8a04a6 100644 --- a/homeassistant/components/config/customize.py +++ b/homeassistant/components/config/customize.py @@ -6,19 +6,21 @@ import homeassistant.helpers.config_validation as cv from . import EditKeyBasedConfigView -CONFIG_PATH = 'customize.yaml' +CONFIG_PATH = "customize.yaml" async def async_setup(hass): """Set up the Customize config API.""" + async def hook(hass): """post_write_hook for Config View that reloads groups.""" await hass.services.async_call(DOMAIN, SERVICE_RELOAD_CORE_CONFIG) - hass.http.register_view(CustomizeConfigView( - 'customize', 'config', CONFIG_PATH, cv.entity_id, dict, - post_write_hook=hook - )) + hass.http.register_view( + CustomizeConfigView( + "customize", "config", CONFIG_PATH, cv.entity_id, dict, post_write_hook=hook + ) + ) return True @@ -29,7 +31,7 @@ class CustomizeConfigView(EditKeyBasedConfigView): def _get_value(self, hass, data, config_key): """Get value.""" customize = hass.data.get(DATA_CUSTOMIZE, {}).get(config_key) or {} - return {'global': customize, 'local': data.get(config_key, {})} + return {"global": customize, "local": data.get(config_key, {})} def _write_value(self, hass, data, config_key, new_value): """Set value.""" diff --git a/homeassistant/components/config/device_registry.py b/homeassistant/components/config/device_registry.py index 61b00bf6726..08f53f948fe 100644 --- a/homeassistant/components/config/device_registry.py +++ b/homeassistant/components/config/device_registry.py @@ -3,29 +3,32 @@ import voluptuous as vol from homeassistant.components import websocket_api from homeassistant.components.websocket_api.decorators import ( - async_response, require_admin) + async_response, + require_admin, +) from homeassistant.core import callback from homeassistant.helpers.device_registry import async_get_registry -WS_TYPE_LIST = 'config/device_registry/list' -SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_LIST, -}) +WS_TYPE_LIST = "config/device_registry/list" +SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {vol.Required("type"): WS_TYPE_LIST} +) -WS_TYPE_UPDATE = 'config/device_registry/update' -SCHEMA_WS_UPDATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_UPDATE, - vol.Required('device_id'): str, - vol.Optional('area_id'): vol.Any(str, None), - vol.Optional('name_by_user'): vol.Any(str, None), -}) +WS_TYPE_UPDATE = "config/device_registry/update" +SCHEMA_WS_UPDATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + { + vol.Required("type"): WS_TYPE_UPDATE, + vol.Required("device_id"): str, + vol.Optional("area_id"): vol.Any(str, None), + vol.Optional("name_by_user"): vol.Any(str, None), + } +) async def async_setup(hass): """Enable the Device Registry views.""" hass.components.websocket_api.async_register_command( - WS_TYPE_LIST, websocket_list_devices, - SCHEMA_WS_LIST + WS_TYPE_LIST, websocket_list_devices, SCHEMA_WS_LIST ) hass.components.websocket_api.async_register_command( WS_TYPE_UPDATE, websocket_update_device, SCHEMA_WS_UPDATE @@ -37,9 +40,11 @@ async def async_setup(hass): async def websocket_list_devices(hass, connection, msg): """Handle list devices command.""" registry = await async_get_registry(hass) - connection.send_message(websocket_api.result_message( - msg['id'], [_entry_dict(entry) for entry in registry.devices.values()] - )) + connection.send_message( + websocket_api.result_message( + msg["id"], [_entry_dict(entry) for entry in registry.devices.values()] + ) + ) @require_admin @@ -48,28 +53,26 @@ async def websocket_update_device(hass, connection, msg): """Handle update area websocket command.""" registry = await async_get_registry(hass) - msg.pop('type') - msg_id = msg.pop('id') + msg.pop("type") + msg_id = msg.pop("id") entry = registry.async_update_device(**msg) - connection.send_message(websocket_api.result_message( - msg_id, _entry_dict(entry) - )) + connection.send_message(websocket_api.result_message(msg_id, _entry_dict(entry))) @callback def _entry_dict(entry): """Convert entry to API format.""" return { - 'config_entries': list(entry.config_entries), - 'connections': list(entry.connections), - 'manufacturer': entry.manufacturer, - 'model': entry.model, - 'name': entry.name, - 'sw_version': entry.sw_version, - 'id': entry.id, - 'via_device_id': entry.via_device_id, - 'area_id': entry.area_id, - 'name_by_user': entry.name_by_user, + "config_entries": list(entry.config_entries), + "connections": list(entry.connections), + "manufacturer": entry.manufacturer, + "model": entry.model, + "name": entry.name, + "sw_version": entry.sw_version, + "id": entry.id, + "via_device_id": entry.via_device_id, + "area_id": entry.area_id, + "name_by_user": entry.name_by_user, } diff --git a/homeassistant/components/config/entity_registry.py b/homeassistant/components/config/entity_registry.py index 341b05f966b..431723893c1 100644 --- a/homeassistant/components/config/entity_registry.py +++ b/homeassistant/components/config/entity_registry.py @@ -6,53 +6,51 @@ from homeassistant.helpers.entity_registry import async_get_registry from homeassistant.components import websocket_api from homeassistant.components.websocket_api.const import ERR_NOT_FOUND from homeassistant.components.websocket_api.decorators import ( - async_response, require_admin) + async_response, + require_admin, +) from homeassistant.helpers import config_validation as cv -WS_TYPE_LIST = 'config/entity_registry/list' -SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_LIST, -}) +WS_TYPE_LIST = "config/entity_registry/list" +SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {vol.Required("type"): WS_TYPE_LIST} +) -WS_TYPE_GET = 'config/entity_registry/get' -SCHEMA_WS_GET = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_GET, - vol.Required('entity_id'): cv.entity_id -}) +WS_TYPE_GET = "config/entity_registry/get" +SCHEMA_WS_GET = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {vol.Required("type"): WS_TYPE_GET, vol.Required("entity_id"): cv.entity_id} +) -WS_TYPE_UPDATE = 'config/entity_registry/update' -SCHEMA_WS_UPDATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_UPDATE, - vol.Required('entity_id'): cv.entity_id, - # If passed in, we update value. Passing None will remove old value. - vol.Optional('name'): vol.Any(str, None), - vol.Optional('new_entity_id'): str, -}) +WS_TYPE_UPDATE = "config/entity_registry/update" +SCHEMA_WS_UPDATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + { + vol.Required("type"): WS_TYPE_UPDATE, + vol.Required("entity_id"): cv.entity_id, + # If passed in, we update value. Passing None will remove old value. + vol.Optional("name"): vol.Any(str, None), + vol.Optional("new_entity_id"): str, + } +) -WS_TYPE_REMOVE = 'config/entity_registry/remove' -SCHEMA_WS_REMOVE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_REMOVE, - vol.Required('entity_id'): cv.entity_id -}) +WS_TYPE_REMOVE = "config/entity_registry/remove" +SCHEMA_WS_REMOVE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {vol.Required("type"): WS_TYPE_REMOVE, vol.Required("entity_id"): cv.entity_id} +) async def async_setup(hass): """Enable the Entity Registry views.""" hass.components.websocket_api.async_register_command( - WS_TYPE_LIST, websocket_list_entities, - SCHEMA_WS_LIST + WS_TYPE_LIST, websocket_list_entities, SCHEMA_WS_LIST ) hass.components.websocket_api.async_register_command( - WS_TYPE_GET, websocket_get_entity, - SCHEMA_WS_GET + WS_TYPE_GET, websocket_get_entity, SCHEMA_WS_GET ) hass.components.websocket_api.async_register_command( - WS_TYPE_UPDATE, websocket_update_entity, - SCHEMA_WS_UPDATE + WS_TYPE_UPDATE, websocket_update_entity, SCHEMA_WS_UPDATE ) hass.components.websocket_api.async_register_command( - WS_TYPE_REMOVE, websocket_remove_entity, - SCHEMA_WS_REMOVE + WS_TYPE_REMOVE, websocket_remove_entity, SCHEMA_WS_REMOVE ) return True @@ -64,9 +62,11 @@ async def websocket_list_entities(hass, connection, msg): Async friendly. """ registry = await async_get_registry(hass) - connection.send_message(websocket_api.result_message( - msg['id'], [_entry_dict(entry) for entry in registry.entities.values()] - )) + connection.send_message( + websocket_api.result_message( + msg["id"], [_entry_dict(entry) for entry in registry.entities.values()] + ) + ) @async_response @@ -76,16 +76,15 @@ async def websocket_get_entity(hass, connection, msg): Async friendly. """ registry = await async_get_registry(hass) - entry = registry.entities.get(msg['entity_id']) + entry = registry.entities.get(msg["entity_id"]) if entry is None: - connection.send_message(websocket_api.error_message( - msg['id'], ERR_NOT_FOUND, 'Entity not found')) + connection.send_message( + websocket_api.error_message(msg["id"], ERR_NOT_FOUND, "Entity not found") + ) return - connection.send_message(websocket_api.result_message( - msg['id'], _entry_dict(entry) - )) + connection.send_message(websocket_api.result_message(msg["id"], _entry_dict(entry))) @require_admin @@ -97,35 +96,38 @@ async def websocket_update_entity(hass, connection, msg): """ registry = await async_get_registry(hass) - if msg['entity_id'] not in registry.entities: - connection.send_message(websocket_api.error_message( - msg['id'], ERR_NOT_FOUND, 'Entity not found')) + if msg["entity_id"] not in registry.entities: + connection.send_message( + websocket_api.error_message(msg["id"], ERR_NOT_FOUND, "Entity not found") + ) return changes = {} - if 'name' in msg: - changes['name'] = msg['name'] + if "name" in msg: + changes["name"] = msg["name"] - if 'new_entity_id' in msg and msg['new_entity_id'] != msg['entity_id']: - changes['new_entity_id'] = msg['new_entity_id'] - if hass.states.get(msg['new_entity_id']) is not None: - connection.send_message(websocket_api.error_message( - msg['id'], 'invalid_info', 'Entity is already registered')) + if "new_entity_id" in msg and msg["new_entity_id"] != msg["entity_id"]: + changes["new_entity_id"] = msg["new_entity_id"] + if hass.states.get(msg["new_entity_id"]) is not None: + connection.send_message( + websocket_api.error_message( + msg["id"], "invalid_info", "Entity is already registered" + ) + ) return try: if changes: - entry = registry.async_update_entity( - msg['entity_id'], **changes) + entry = registry.async_update_entity(msg["entity_id"], **changes) except ValueError as err: - connection.send_message(websocket_api.error_message( - msg['id'], 'invalid_info', str(err) - )) + connection.send_message( + websocket_api.error_message(msg["id"], "invalid_info", str(err)) + ) else: - connection.send_message(websocket_api.result_message( - msg['id'], _entry_dict(entry) - )) + connection.send_message( + websocket_api.result_message(msg["id"], _entry_dict(entry)) + ) @require_admin @@ -137,23 +139,24 @@ async def websocket_remove_entity(hass, connection, msg): """ registry = await async_get_registry(hass) - if msg['entity_id'] not in registry.entities: - connection.send_message(websocket_api.error_message( - msg['id'], ERR_NOT_FOUND, 'Entity not found')) + if msg["entity_id"] not in registry.entities: + connection.send_message( + websocket_api.error_message(msg["id"], ERR_NOT_FOUND, "Entity not found") + ) return - registry.async_remove(msg['entity_id']) - connection.send_message(websocket_api.result_message(msg['id'])) + registry.async_remove(msg["entity_id"]) + connection.send_message(websocket_api.result_message(msg["id"])) @callback def _entry_dict(entry): """Convert entry to API format.""" return { - 'config_entry_id': entry.config_entry_id, - 'device_id': entry.device_id, - 'disabled_by': entry.disabled_by, - 'entity_id': entry.entity_id, - 'name': entry.name, - 'platform': entry.platform, + "config_entry_id": entry.config_entry_id, + "device_id": entry.device_id, + "disabled_by": entry.disabled_by, + "entity_id": entry.entity_id, + "name": entry.name, + "platform": entry.platform, } diff --git a/homeassistant/components/config/group.py b/homeassistant/components/config/group.py index 60421bcc125..371bd98cf08 100644 --- a/homeassistant/components/config/group.py +++ b/homeassistant/components/config/group.py @@ -5,17 +5,19 @@ import homeassistant.helpers.config_validation as cv from . import EditKeyBasedConfigView -CONFIG_PATH = 'groups.yaml' +CONFIG_PATH = "groups.yaml" async def async_setup(hass): """Set up the Group config API.""" + async def hook(hass): """post_write_hook for Config View that reloads groups.""" await hass.services.async_call(DOMAIN, SERVICE_RELOAD) - hass.http.register_view(EditKeyBasedConfigView( - 'group', 'config', CONFIG_PATH, cv.slug, GROUP_SCHEMA, - post_write_hook=hook - )) + hass.http.register_view( + EditKeyBasedConfigView( + "group", "config", CONFIG_PATH, cv.slug, GROUP_SCHEMA, post_write_hook=hook + ) + ) return True diff --git a/homeassistant/components/config/script.py b/homeassistant/components/config/script.py index c8a58e5d72a..8ce163745f1 100644 --- a/homeassistant/components/config/script.py +++ b/homeassistant/components/config/script.py @@ -5,17 +5,24 @@ import homeassistant.helpers.config_validation as cv from . import EditKeyBasedConfigView -CONFIG_PATH = 'scripts.yaml' +CONFIG_PATH = "scripts.yaml" async def async_setup(hass): """Set up the script config API.""" + async def hook(hass): """post_write_hook for Config View that reloads scripts.""" await hass.services.async_call(DOMAIN, SERVICE_RELOAD) - hass.http.register_view(EditKeyBasedConfigView( - 'script', 'config', CONFIG_PATH, cv.slug, SCRIPT_ENTRY_SCHEMA, - post_write_hook=hook - )) + hass.http.register_view( + EditKeyBasedConfigView( + "script", + "config", + CONFIG_PATH, + cv.slug, + SCRIPT_ENTRY_SCHEMA, + post_write_hook=hook, + ) + ) return True diff --git a/homeassistant/components/config/zwave.py b/homeassistant/components/config/zwave.py index e7e39968401..eaed84fe24d 100644 --- a/homeassistant/components/config/zwave.py +++ b/homeassistant/components/config/zwave.py @@ -13,16 +13,21 @@ import homeassistant.helpers.config_validation as cv from . import EditKeyBasedConfigView _LOGGER = logging.getLogger(__name__) -CONFIG_PATH = 'zwave_device_config.yaml' -OZW_LOG_FILENAME = 'OZW_Log.txt' +CONFIG_PATH = "zwave_device_config.yaml" +OZW_LOG_FILENAME = "OZW_Log.txt" async def async_setup(hass): """Set up the Z-Wave config API.""" - hass.http.register_view(EditKeyBasedConfigView( - 'zwave', 'device_config', CONFIG_PATH, cv.entity_id, - DEVICE_CONFIG_SCHEMA_ENTRY - )) + hass.http.register_view( + EditKeyBasedConfigView( + "zwave", + "device_config", + CONFIG_PATH, + cv.entity_id, + DEVICE_CONFIG_SCHEMA_ENTRY, + ) + ) hass.http.register_view(ZWaveNodeValueView) hass.http.register_view(ZWaveNodeGroupView) hass.http.register_view(ZWaveNodeConfigView) @@ -40,23 +45,23 @@ class ZWaveLogView(HomeAssistantView): url = "/api/zwave/ozwlog" name = "api:zwave:ozwlog" -# pylint: disable=no-self-use + # pylint: disable=no-self-use async def get(self, request): """Retrieve the lines from ZWave log.""" try: - lines = int(request.query.get('lines', 0)) + lines = int(request.query.get("lines", 0)) except ValueError: - return Response(text='Invalid datetime', status=400) + return Response(text="Invalid datetime", status=400) - hass = request.app['hass'] + hass = request.app["hass"] response = await hass.async_add_job(self._get_log, hass, lines) - return Response(text='\n'.join(response)) + return Response(text="\n".join(response)) def _get_log(self, hass, lines): """Retrieve the logfile content.""" logfilepath = hass.config.path(OZW_LOG_FILENAME) - with open(logfilepath, 'r') as logfile: + with open(logfilepath, "r") as logfile: data = (line.rstrip() for line in logfile) if lines == 0: loglines = list(data) @@ -74,15 +79,13 @@ class ZWaveConfigWriteView(HomeAssistantView): @ha.callback def post(self, request): """Save cache configuration to zwcfg_xxxxx.xml.""" - hass = request.app['hass'] + hass = request.app["hass"] network = hass.data.get(const.DATA_NETWORK) if network is None: - return self.json_message('No Z-Wave network data found', - HTTP_NOT_FOUND) + return self.json_message("No Z-Wave network data found", HTTP_NOT_FOUND) _LOGGER.info("Z-Wave configuration written to file.") network.write_config() - return self.json_message('Z-Wave configuration saved to file.', - HTTP_OK) + return self.json_message("Z-Wave configuration saved to file.", HTTP_OK) class ZWaveNodeValueView(HomeAssistantView): @@ -95,7 +98,7 @@ class ZWaveNodeValueView(HomeAssistantView): def get(self, request, node_id): """Retrieve groups of node.""" nodeid = int(node_id) - hass = request.app['hass'] + hass = request.app["hass"] values_list = hass.data[const.DATA_ENTITY_VALUES] values_data = {} @@ -106,10 +109,10 @@ class ZWaveNodeValueView(HomeAssistantView): continue values_data[entity_values.primary.value_id] = { - 'label': entity_values.primary.label, - 'index': entity_values.primary.index, - 'instance': entity_values.primary.instance, - 'poll_intensity': entity_values.primary.poll_intensity, + "label": entity_values.primary.label, + "index": entity_values.primary.index, + "instance": entity_values.primary.instance, + "poll_intensity": entity_values.primary.poll_intensity, } return self.json(values_data) @@ -124,19 +127,20 @@ class ZWaveNodeGroupView(HomeAssistantView): def get(self, request, node_id): """Retrieve groups of node.""" nodeid = int(node_id) - hass = request.app['hass'] + hass = request.app["hass"] network = hass.data.get(const.DATA_NETWORK) node = network.nodes.get(nodeid) if node is None: - return self.json_message('Node not found', HTTP_NOT_FOUND) + return self.json_message("Node not found", HTTP_NOT_FOUND) groupdata = node.groups groups = {} for key, value in groupdata.items(): - groups[key] = {'associations': value.associations, - 'association_instances': - value.associations_instances, - 'label': value.label, - 'max_associations': value.max_associations} + groups[key] = { + "associations": value.associations, + "association_instances": value.associations_instances, + "label": value.label, + "max_associations": value.max_associations, + } return self.json(groups) @@ -150,22 +154,24 @@ class ZWaveNodeConfigView(HomeAssistantView): def get(self, request, node_id): """Retrieve configurations of node.""" nodeid = int(node_id) - hass = request.app['hass'] + hass = request.app["hass"] network = hass.data.get(const.DATA_NETWORK) node = network.nodes.get(nodeid) if node is None: - return self.json_message('Node not found', HTTP_NOT_FOUND) + return self.json_message("Node not found", HTTP_NOT_FOUND) config = {} - for value in ( - node.get_values(class_id=const.COMMAND_CLASS_CONFIGURATION) - .values()): - config[value.index] = {'label': value.label, - 'type': value.type, - 'help': value.help, - 'data_items': value.data_items, - 'data': value.data, - 'max': value.max, - 'min': value.min} + for value in node.get_values( + class_id=const.COMMAND_CLASS_CONFIGURATION + ).values(): + config[value.index] = { + "label": value.label, + "type": value.type, + "help": value.help, + "data_items": value.data_items, + "data": value.data, + "max": value.max, + "min": value.min, + } return self.json(config) @@ -179,22 +185,22 @@ class ZWaveUserCodeView(HomeAssistantView): def get(self, request, node_id): """Retrieve usercodes of node.""" nodeid = int(node_id) - hass = request.app['hass'] + hass = request.app["hass"] network = hass.data.get(const.DATA_NETWORK) node = network.nodes.get(nodeid) if node is None: - return self.json_message('Node not found', HTTP_NOT_FOUND) + return self.json_message("Node not found", HTTP_NOT_FOUND) usercodes = {} if not node.has_command_class(const.COMMAND_CLASS_USER_CODE): return self.json(usercodes) - for value in ( - node.get_values(class_id=const.COMMAND_CLASS_USER_CODE) - .values()): + for value in node.get_values(class_id=const.COMMAND_CLASS_USER_CODE).values(): if value.genre != const.GENRE_USER: continue - usercodes[value.index] = {'code': value.data, - 'label': value.label, - 'length': len(value.data)} + usercodes[value.index] = { + "code": value.data, + "label": value.label, + "length": len(value.data), + } return self.json(usercodes) @@ -207,22 +213,23 @@ class ZWaveProtectionView(HomeAssistantView): async def get(self, request, node_id): """Retrieve the protection commandclass options of node.""" nodeid = int(node_id) - hass = request.app['hass'] + hass = request.app["hass"] network = hass.data.get(const.DATA_NETWORK) def _fetch_protection(): """Get protection data.""" node = network.nodes.get(nodeid) if node is None: - return self.json_message('Node not found', HTTP_NOT_FOUND) + return self.json_message("Node not found", HTTP_NOT_FOUND) protection_options = {} if not node.has_command_class(const.COMMAND_CLASS_PROTECTION): return self.json(protection_options) protections = node.get_protections() protection_options = { - 'value_id': '{0:d}'.format(list(protections)[0]), - 'selected': node.get_protection_item(list(protections)[0]), - 'options': node.get_protection_items(list(protections)[0])} + "value_id": "{0:d}".format(list(protections)[0]), + "selected": node.get_protection_item(list(protections)[0]), + "options": node.get_protection_items(list(protections)[0]), + } return self.json(protection_options) return await hass.async_add_executor_job(_fetch_protection) @@ -230,7 +237,7 @@ class ZWaveProtectionView(HomeAssistantView): async def post(self, request, node_id): """Change the selected option in protection commandclass.""" nodeid = int(node_id) - hass = request.app['hass'] + hass = request.app["hass"] network = hass.data.get(const.DATA_NETWORK) protection_data = await request.json() @@ -240,15 +247,14 @@ class ZWaveProtectionView(HomeAssistantView): selection = protection_data["selection"] value_id = int(protection_data[const.ATTR_VALUE_ID]) if node is None: - return self.json_message('Node not found', HTTP_NOT_FOUND) + return self.json_message("Node not found", HTTP_NOT_FOUND) if not node.has_command_class(const.COMMAND_CLASS_PROTECTION): return self.json_message( - 'No protection commandclass on this node', HTTP_NOT_FOUND) + "No protection commandclass on this node", HTTP_NOT_FOUND + ) state = node.set_protection(value_id, selection) if not state: - return self.json_message( - 'Protection setting did not complete', 202) - return self.json_message( - 'Protection setting succsessfully set', HTTP_OK) + return self.json_message("Protection setting did not complete", 202) + return self.json_message("Protection setting succsessfully set", HTTP_OK) return await hass.async_add_executor_job(_set_protection) diff --git a/homeassistant/components/configurator/__init__.py b/homeassistant/components/configurator/__init__.py index 74d8339b1fa..99995959c23 100644 --- a/homeassistant/components/configurator/__init__.py +++ b/homeassistant/components/configurator/__init__.py @@ -10,50 +10,61 @@ import functools as ft import logging from homeassistant.core import callback as async_callback -from homeassistant.const import EVENT_TIME_CHANGED, ATTR_FRIENDLY_NAME, \ - ATTR_ENTITY_PICTURE +from homeassistant.const import ( + EVENT_TIME_CHANGED, + ATTR_FRIENDLY_NAME, + ATTR_ENTITY_PICTURE, +) from homeassistant.loader import bind_hass from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.util.async_ import run_callback_threadsafe _LOGGER = logging.getLogger(__name__) -_KEY_INSTANCE = 'configurator' +_KEY_INSTANCE = "configurator" -DATA_REQUESTS = 'configurator_requests' +DATA_REQUESTS = "configurator_requests" -ATTR_CONFIGURE_ID = 'configure_id' -ATTR_DESCRIPTION = 'description' -ATTR_DESCRIPTION_IMAGE = 'description_image' -ATTR_ERRORS = 'errors' -ATTR_FIELDS = 'fields' -ATTR_LINK_NAME = 'link_name' -ATTR_LINK_URL = 'link_url' -ATTR_SUBMIT_CAPTION = 'submit_caption' +ATTR_CONFIGURE_ID = "configure_id" +ATTR_DESCRIPTION = "description" +ATTR_DESCRIPTION_IMAGE = "description_image" +ATTR_ERRORS = "errors" +ATTR_FIELDS = "fields" +ATTR_LINK_NAME = "link_name" +ATTR_LINK_URL = "link_url" +ATTR_SUBMIT_CAPTION = "submit_caption" -DOMAIN = 'configurator' +DOMAIN = "configurator" -ENTITY_ID_FORMAT = DOMAIN + '.{}' +ENTITY_ID_FORMAT = DOMAIN + ".{}" -SERVICE_CONFIGURE = 'configure' -STATE_CONFIGURE = 'configure' -STATE_CONFIGURED = 'configured' +SERVICE_CONFIGURE = "configure" +STATE_CONFIGURE = "configure" +STATE_CONFIGURED = "configured" @bind_hass @async_callback def async_request_config( - hass, name, callback=None, description=None, description_image=None, - submit_caption=None, fields=None, link_name=None, link_url=None, - entity_picture=None): + hass, + name, + callback=None, + description=None, + description_image=None, + submit_caption=None, + fields=None, + link_name=None, + link_url=None, + entity_picture=None, +): """Create a new request for configuration. Will return an ID to be used for sequent calls. """ if link_name is not None and link_url is not None: - description += '\n\n[{}]({})'.format(link_name, link_url) + description += "\n\n[{}]({})".format(link_name, link_url) if description_image is not None: - description += '\n\n![Description image]({})'.format(description_image) + description += "\n\n![Description image]({})".format(description_image) instance = hass.data.get(_KEY_INSTANCE) @@ -61,7 +72,8 @@ def async_request_config( instance = hass.data[_KEY_INSTANCE] = Configurator(hass) request_id = instance.async_request_config( - name, callback, description, submit_caption, fields, entity_picture) + name, callback, description, submit_caption, fields, entity_picture + ) if DATA_REQUESTS not in hass.data: hass.data[DATA_REQUESTS] = {} @@ -87,8 +99,7 @@ def request_config(hass, *args, **kwargs): def async_notify_errors(hass, request_id, error): """Add errors to a config request.""" try: - hass.data[DATA_REQUESTS][request_id].async_notify_errors( - request_id, error) + hass.data[DATA_REQUESTS][request_id].async_notify_errors(request_id, error) except KeyError: # If request_id does not exist pass @@ -135,15 +146,15 @@ class Configurator: self._cur_id = 0 self._requests = {} hass.services.async_register( - DOMAIN, SERVICE_CONFIGURE, self.async_handle_service_call) + DOMAIN, SERVICE_CONFIGURE, self.async_handle_service_call + ) @async_callback def async_request_config( - self, name, callback, description, submit_caption, fields, - entity_picture): + self, name, callback, description, submit_caption, fields, entity_picture + ): """Set up a request for configuration.""" - entity_id = async_generate_entity_id( - ENTITY_ID_FORMAT, name, hass=self.hass) + entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, name, hass=self.hass) if fields is None: fields = [] @@ -159,12 +170,16 @@ class Configurator: ATTR_ENTITY_PICTURE: entity_picture, } - data.update({ - key: value for key, value in [ - (ATTR_DESCRIPTION, description), - (ATTR_SUBMIT_CAPTION, submit_caption), - ] if value is not None - }) + data.update( + { + key: value + for key, value in [ + (ATTR_DESCRIPTION, description), + (ATTR_SUBMIT_CAPTION, submit_caption), + ] + if value is not None + } + ) self.hass.states.async_set(entity_id, STATE_CONFIGURE, data) @@ -217,8 +232,7 @@ class Configurator: # field validation goes here? if callback: - await self.hass.async_add_job(callback, - call.data.get(ATTR_FIELDS, {})) + await self.hass.async_add_job(callback, call.data.get(ATTR_FIELDS, {})) def _generate_unique_id(self): """Generate a unique configurator ID.""" diff --git a/homeassistant/components/conversation/__init__.py b/homeassistant/components/conversation/__init__.py index bd577127fa0..9d7d510b10e 100644 --- a/homeassistant/components/conversation/__init__.py +++ b/homeassistant/components/conversation/__init__.py @@ -6,8 +6,7 @@ import voluptuous as vol from homeassistant import core from homeassistant.components import http -from homeassistant.components.cover import ( - INTENT_CLOSE_COVER, INTENT_OPEN_COVER) +from homeassistant.components.cover import INTENT_CLOSE_COVER, INTENT_OPEN_COVER from homeassistant.components.http.data_validator import RequestDataValidator from homeassistant.const import EVENT_COMPONENT_LOADED from homeassistant.core import callback @@ -19,31 +18,36 @@ from .util import create_matcher _LOGGER = logging.getLogger(__name__) -ATTR_TEXT = 'text' +ATTR_TEXT = "text" -DOMAIN = 'conversation' +DOMAIN = "conversation" -REGEX_TURN_COMMAND = re.compile(r'turn (?P(?: |\w)+) (?P\w+)') -REGEX_TYPE = type(re.compile('')) +REGEX_TURN_COMMAND = re.compile(r"turn (?P(?: |\w)+) (?P\w+)") +REGEX_TYPE = type(re.compile("")) UTTERANCES = { - 'cover': { - INTENT_OPEN_COVER: ['Open [the] [a] [an] {name}[s]'], - INTENT_CLOSE_COVER: ['Close [the] [a] [an] {name}[s]'] + "cover": { + INTENT_OPEN_COVER: ["Open [the] [a] [an] {name}[s]"], + INTENT_CLOSE_COVER: ["Close [the] [a] [an] {name}[s]"], } } -SERVICE_PROCESS = 'process' +SERVICE_PROCESS = "process" -SERVICE_PROCESS_SCHEMA = vol.Schema({ - vol.Required(ATTR_TEXT): cv.string, -}) +SERVICE_PROCESS_SCHEMA = vol.Schema({vol.Required(ATTR_TEXT): cv.string}) -CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({ - vol.Optional('intents'): vol.Schema({ - cv.string: vol.All(cv.ensure_list, [cv.string]) - }) -})}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Optional("intents"): vol.Schema( + {cv.string: vol.All(cv.ensure_list, [cv.string])} + ) + } + ) + }, + extra=vol.ALLOW_EXTRA, +) @core.callback @@ -79,7 +83,7 @@ async def async_setup(hass, config): if intents is None: intents = hass.data[DOMAIN] = {} - for intent_type, utterances in config.get('intents', {}).items(): + for intent_type, utterances in config.get("intents", {}).items(): conf = intents.get(intent_type) if conf is None: @@ -90,14 +94,15 @@ async def async_setup(hass, config): async def process(service): """Parse text into commands.""" text = service.data[ATTR_TEXT] - _LOGGER.debug('Processing: <%s>', text) + _LOGGER.debug("Processing: <%s>", text) try: await _process(hass, text) except intent.IntentHandleError as err: - _LOGGER.error('Error processing %s: %s', text, err) + _LOGGER.error("Error processing %s: %s", text, err) hass.services.async_register( - DOMAIN, SERVICE_PROCESS, process, schema=SERVICE_PROCESS_SCHEMA) + DOMAIN, SERVICE_PROCESS, process, schema=SERVICE_PROCESS_SCHEMA + ) hass.http.register_view(ConversationProcessView) @@ -105,18 +110,21 @@ async def async_setup(hass, config): # if a letter is not there. By removing 's' we can match singular and # plural names. - async_register(hass, intent.INTENT_TURN_ON, [ - 'Turn [the] [a] {name}[s] on', - 'Turn on [the] [a] [an] {name}[s]', - ]) - async_register(hass, intent.INTENT_TURN_OFF, [ - 'Turn [the] [a] [an] {name}[s] off', - 'Turn off [the] [a] [an] {name}[s]', - ]) - async_register(hass, intent.INTENT_TOGGLE, [ - 'Toggle [the] [a] [an] {name}[s]', - '[the] [a] [an] {name}[s] toggle', - ]) + async_register( + hass, + intent.INTENT_TURN_ON, + ["Turn [the] [a] {name}[s] on", "Turn on [the] [a] [an] {name}[s]"], + ) + async_register( + hass, + intent.INTENT_TURN_OFF, + ["Turn [the] [a] [an] {name}[s] off", "Turn off [the] [a] [an] {name}[s]"], + ) + async_register( + hass, + intent.INTENT_TOGGLE, + ["Toggle [the] [a] [an] {name}[s]", "[the] [a] [an] {name}[s] toggle"], + ) @callback def register_utterances(component): @@ -152,27 +160,27 @@ async def _process(hass, text): continue response = await hass.helpers.intent.async_handle( - DOMAIN, intent_type, - {key: {'value': value} for key, value - in match.groupdict().items()}, text) + DOMAIN, + intent_type, + {key: {"value": value} for key, value in match.groupdict().items()}, + text, + ) return response class ConversationProcessView(http.HomeAssistantView): """View to retrieve shopping list content.""" - url = '/api/conversation/process' + url = "/api/conversation/process" name = "api:conversation:process" - @RequestDataValidator(vol.Schema({ - vol.Required('text'): str, - })) + @RequestDataValidator(vol.Schema({vol.Required("text"): str})) async def post(self, request, data): """Send a request for processing.""" - hass = request.app['hass'] + hass = request.app["hass"] try: - intent_result = await _process(hass, data['text']) + intent_result = await _process(hass, data["text"]) except intent.IntentHandleError as err: intent_result = intent.IntentResponse() intent_result.async_set_speech(str(err)) diff --git a/homeassistant/components/conversation/util.py b/homeassistant/components/conversation/util.py index 60d861afdbe..4904cb9f990 100644 --- a/homeassistant/components/conversation/util.py +++ b/homeassistant/components/conversation/util.py @@ -6,13 +6,13 @@ def create_matcher(utterance): """Create a regex that matches the utterance.""" # Split utterance into parts that are type: NORMAL, GROUP or OPTIONAL # Pattern matches (GROUP|OPTIONAL): Change light to [the color] {name} - parts = re.split(r'({\w+}|\[[\w\s]+\] *)', utterance) + parts = re.split(r"({\w+}|\[[\w\s]+\] *)", utterance) # Pattern to extract name from GROUP part. Matches {name} - group_matcher = re.compile(r'{(\w+)}') + group_matcher = re.compile(r"{(\w+)}") # Pattern to extract text from OPTIONAL part. Matches [the color] - optional_matcher = re.compile(r'\[([\w ]+)\] *') + optional_matcher = re.compile(r"\[([\w ]+)\] *") - pattern = ['^'] + pattern = ["^"] for part in parts: group_match = group_matcher.match(part) optional_match = optional_matcher.match(part) @@ -24,12 +24,11 @@ def create_matcher(utterance): # Group part if group_match is not None: - pattern.append( - r'(?P<{}>[\w ]+?)\s*'.format(group_match.groups()[0])) + pattern.append(r"(?P<{}>[\w ]+?)\s*".format(group_match.groups()[0])) # Optional part elif optional_match is not None: - pattern.append(r'(?:{} *)?'.format(optional_match.groups()[0])) + pattern.append(r"(?:{} *)?".format(optional_match.groups()[0])) - pattern.append('$') - return re.compile(''.join(pattern), re.I) + pattern.append("$") + return re.compile("".join(pattern), re.I) diff --git a/homeassistant/components/coolmaster/climate.py b/homeassistant/components/coolmaster/climate.py index 378a1c0c281..7379d66777b 100644 --- a/homeassistant/components/coolmaster/climate.py +++ b/homeassistant/components/coolmaster/climate.py @@ -6,39 +6,59 @@ import voluptuous as vol from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA from homeassistant.components.climate.const import ( - HVAC_MODE_OFF, HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, SUPPORT_FAN_MODE, - SUPPORT_TARGET_TEMPERATURE) + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + HVAC_MODE_COOL, + HVAC_MODE_DRY, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_HEAT, + SUPPORT_FAN_MODE, + SUPPORT_TARGET_TEMPERATURE, +) from homeassistant.const import ( - ATTR_TEMPERATURE, CONF_HOST, CONF_PORT, TEMP_CELSIUS, TEMP_FAHRENHEIT) + ATTR_TEMPERATURE, + CONF_HOST, + CONF_PORT, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, +) import homeassistant.helpers.config_validation as cv -SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE) +SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE DEFAULT_PORT = 10102 -AVAILABLE_MODES = [HVAC_MODE_OFF, HVAC_MODE_HEAT, HVAC_MODE_COOL, - HVAC_MODE_DRY, HVAC_MODE_AUTO, HVAC_MODE_FAN_ONLY] +AVAILABLE_MODES = [ + HVAC_MODE_OFF, + HVAC_MODE_HEAT, + HVAC_MODE_COOL, + HVAC_MODE_DRY, + HVAC_MODE_AUTO, + HVAC_MODE_FAN_ONLY, +] CM_TO_HA_STATE = { - 'heat': HVAC_MODE_HEAT, - 'cool': HVAC_MODE_COOL, - 'auto': HVAC_MODE_AUTO, - 'dry': HVAC_MODE_DRY, - 'fan': HVAC_MODE_FAN_ONLY, + "heat": HVAC_MODE_HEAT, + "cool": HVAC_MODE_COOL, + "auto": HVAC_MODE_AUTO, + "dry": HVAC_MODE_DRY, + "fan": HVAC_MODE_FAN_ONLY, } HA_STATE_TO_CM = {value: key for key, value in CM_TO_HA_STATE.items()} -FAN_MODES = ['low', 'med', 'high', 'auto'] +FAN_MODES = ["low", "med", "high", "auto"] -CONF_SUPPORTED_MODES = 'supported_modes' -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_SUPPORTED_MODES, default=AVAILABLE_MODES): - vol.All(cv.ensure_list, [vol.In(AVAILABLE_MODES)]), -}) +CONF_SUPPORTED_MODES = "supported_modes" +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_SUPPORTED_MODES, default=AVAILABLE_MODES): vol.All( + cv.ensure_list, [vol.In(AVAILABLE_MODES)] + ), + } +) _LOGGER = logging.getLogger(__name__) @@ -58,8 +78,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): cool = CoolMasterNet(host, port=port) devices = cool.devices() - all_devices = [_build_entity(device, supported_modes) - for device in devices] + all_devices = [_build_entity(device, supported_modes) for device in devices] add_entities(all_devices, True) @@ -83,18 +102,18 @@ class CoolmasterClimate(ClimateDevice): def update(self): """Pull state from CoolMasterNet.""" status = self._device.status - self._target_temperature = status['thermostat'] - self._current_temperature = status['temperature'] - self._current_fan_mode = status['fan_speed'] - self._on = status['is_on'] + self._target_temperature = status["thermostat"] + self._current_temperature = status["temperature"] + self._current_fan_mode = status["fan_speed"] + self._on = status["is_on"] - device_mode = status['mode'] + device_mode = status["mode"] if self._on: self._hvac_mode = CM_TO_HA_STATE[device_mode] else: self._hvac_mode = HVAC_MODE_OFF - if status['unit'] == 'celsius': + if status["unit"] == "celsius": self._unit = TEMP_CELSIUS else: self._unit = TEMP_FAHRENHEIT @@ -153,20 +172,17 @@ class CoolmasterClimate(ClimateDevice): """Set new target temperatures.""" temp = kwargs.get(ATTR_TEMPERATURE) if temp is not None: - _LOGGER.debug("Setting temp of %s to %s", self.unique_id, - str(temp)) + _LOGGER.debug("Setting temp of %s to %s", self.unique_id, str(temp)) self._device.set_thermostat(str(temp)) def set_fan_mode(self, fan_mode): """Set new fan mode.""" - _LOGGER.debug("Setting fan mode of %s to %s", self.unique_id, - fan_mode) + _LOGGER.debug("Setting fan mode of %s to %s", self.unique_id, fan_mode) self._device.set_fan_speed(fan_mode) def set_hvac_mode(self, hvac_mode): """Set new operation mode.""" - _LOGGER.debug("Setting operation mode of %s to %s", self.unique_id, - hvac_mode) + _LOGGER.debug("Setting operation mode of %s to %s", self.unique_id, hvac_mode) if hvac_mode == HVAC_MODE_OFF: self.turn_off() diff --git a/homeassistant/components/counter/__init__.py b/homeassistant/components/counter/__init__.py index 5ca8dfb4de7..79877d63f14 100644 --- a/homeassistant/components/counter/__init__.py +++ b/homeassistant/components/counter/__init__.py @@ -3,8 +3,7 @@ import logging import voluptuous as vol -from homeassistant.const import ( - CONF_ICON, CONF_NAME, CONF_MAXIMUM, CONF_MINIMUM) +from homeassistant.const import CONF_ICON, CONF_NAME, CONF_MAXIMUM, CONF_MINIMUM import homeassistant.helpers.config_validation as cv from homeassistant.helpers.config_validation import ENTITY_SERVICE_SCHEMA @@ -13,48 +12,59 @@ from homeassistant.helpers.restore_state import RestoreEntity _LOGGER = logging.getLogger(__name__) -ATTR_INITIAL = 'initial' -ATTR_STEP = 'step' -ATTR_MINIMUM = 'minimum' -ATTR_MAXIMUM = 'maximum' +ATTR_INITIAL = "initial" +ATTR_STEP = "step" +ATTR_MINIMUM = "minimum" +ATTR_MAXIMUM = "maximum" -CONF_INITIAL = 'initial' -CONF_RESTORE = 'restore' -CONF_STEP = 'step' +CONF_INITIAL = "initial" +CONF_RESTORE = "restore" +CONF_STEP = "step" DEFAULT_INITIAL = 0 DEFAULT_STEP = 1 -DOMAIN = 'counter' +DOMAIN = "counter" -ENTITY_ID_FORMAT = DOMAIN + '.{}' +ENTITY_ID_FORMAT = DOMAIN + ".{}" -SERVICE_DECREMENT = 'decrement' -SERVICE_INCREMENT = 'increment' -SERVICE_RESET = 'reset' -SERVICE_CONFIGURE = 'configure' +SERVICE_DECREMENT = "decrement" +SERVICE_INCREMENT = "increment" +SERVICE_RESET = "reset" +SERVICE_CONFIGURE = "configure" -SERVICE_SCHEMA_CONFIGURE = ENTITY_SERVICE_SCHEMA.extend({ - vol.Optional(ATTR_MINIMUM): vol.Any(None, vol.Coerce(int)), - vol.Optional(ATTR_MAXIMUM): vol.Any(None, vol.Coerce(int)), - vol.Optional(ATTR_STEP): cv.positive_int, -}) +SERVICE_SCHEMA_CONFIGURE = ENTITY_SERVICE_SCHEMA.extend( + { + vol.Optional(ATTR_MINIMUM): vol.Any(None, vol.Coerce(int)), + vol.Optional(ATTR_MAXIMUM): vol.Any(None, vol.Coerce(int)), + vol.Optional(ATTR_STEP): cv.positive_int, + } +) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: cv.schema_with_slug_keys( - vol.Any({ - vol.Optional(CONF_ICON): cv.icon, - vol.Optional(CONF_INITIAL, default=DEFAULT_INITIAL): - cv.positive_int, - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_MAXIMUM, default=None): - vol.Any(None, vol.Coerce(int)), - vol.Optional(CONF_MINIMUM, default=None): - vol.Any(None, vol.Coerce(int)), - vol.Optional(CONF_RESTORE, default=True): cv.boolean, - vol.Optional(CONF_STEP, default=DEFAULT_STEP): cv.positive_int, - }, None) - ) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: cv.schema_with_slug_keys( + vol.Any( + { + vol.Optional(CONF_ICON): cv.icon, + vol.Optional( + CONF_INITIAL, default=DEFAULT_INITIAL + ): cv.positive_int, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_MAXIMUM, default=None): vol.Any( + None, vol.Coerce(int) + ), + vol.Optional(CONF_MINIMUM, default=None): vol.Any( + None, vol.Coerce(int) + ), + vol.Optional(CONF_RESTORE, default=True): cv.boolean, + vol.Optional(CONF_STEP, default=DEFAULT_STEP): cv.positive_int, + }, + None, + ) + ) + }, + extra=vol.ALLOW_EXTRA, +) async def async_setup(hass, config): @@ -75,24 +85,25 @@ async def async_setup(hass, config): minimum = cfg.get(CONF_MINIMUM) maximum = cfg.get(CONF_MAXIMUM) - entities.append(Counter(object_id, name, initial, minimum, maximum, - restore, step, icon)) + entities.append( + Counter(object_id, name, initial, minimum, maximum, restore, step, icon) + ) if not entities: return False component.async_register_entity_service( - SERVICE_INCREMENT, ENTITY_SERVICE_SCHEMA, - 'async_increment') + SERVICE_INCREMENT, ENTITY_SERVICE_SCHEMA, "async_increment" + ) component.async_register_entity_service( - SERVICE_DECREMENT, ENTITY_SERVICE_SCHEMA, - 'async_decrement') + SERVICE_DECREMENT, ENTITY_SERVICE_SCHEMA, "async_decrement" + ) component.async_register_entity_service( - SERVICE_RESET, ENTITY_SERVICE_SCHEMA, - 'async_reset') + SERVICE_RESET, ENTITY_SERVICE_SCHEMA, "async_reset" + ) component.async_register_entity_service( - SERVICE_CONFIGURE, SERVICE_SCHEMA_CONFIGURE, - 'async_configure') + SERVICE_CONFIGURE, SERVICE_SCHEMA_CONFIGURE, "async_configure" + ) await component.async_add_entities(entities) return True @@ -101,8 +112,7 @@ async def async_setup(hass, config): class Counter(RestoreEntity): """Representation of a counter.""" - def __init__(self, object_id, name, initial, minimum, maximum, - restore, step, icon): + def __init__(self, object_id, name, initial, minimum, maximum, restore, step, icon): """Initialize a counter.""" self.entity_id = ENTITY_ID_FORMAT.format(object_id) self._name = name @@ -136,10 +146,7 @@ class Counter(RestoreEntity): @property def state_attributes(self): """Return the state attributes.""" - ret = { - ATTR_INITIAL: self._initial, - ATTR_STEP: self._step, - } + ret = {ATTR_INITIAL: self._initial, ATTR_STEP: self._step} if self._min is not None: ret[CONF_MINIMUM] = self._min if self._max is not None: diff --git a/homeassistant/components/cover/__init__.py b/homeassistant/components/cover/__init__.py index fca16d9bfbe..696524f5792 100644 --- a/homeassistant/components/cover/__init__.py +++ b/homeassistant/components/cover/__init__.py @@ -9,37 +9,49 @@ from homeassistant.loader import bind_hass from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity import Entity from homeassistant.helpers.config_validation import ( # noqa - PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE) + PLATFORM_SCHEMA, + PLATFORM_SCHEMA_BASE, +) from homeassistant.helpers.config_validation import ENTITY_SERVICE_SCHEMA from homeassistant.components import group from homeassistant.helpers import intent from homeassistant.const import ( - SERVICE_OPEN_COVER, SERVICE_CLOSE_COVER, SERVICE_SET_COVER_POSITION, - SERVICE_STOP_COVER, SERVICE_TOGGLE, SERVICE_OPEN_COVER_TILT, - SERVICE_CLOSE_COVER_TILT, SERVICE_STOP_COVER_TILT, - SERVICE_SET_COVER_TILT_POSITION, SERVICE_TOGGLE_COVER_TILT, - STATE_OPEN, STATE_CLOSED, STATE_OPENING, STATE_CLOSING) + SERVICE_OPEN_COVER, + SERVICE_CLOSE_COVER, + SERVICE_SET_COVER_POSITION, + SERVICE_STOP_COVER, + SERVICE_TOGGLE, + SERVICE_OPEN_COVER_TILT, + SERVICE_CLOSE_COVER_TILT, + SERVICE_STOP_COVER_TILT, + SERVICE_SET_COVER_TILT_POSITION, + SERVICE_TOGGLE_COVER_TILT, + STATE_OPEN, + STATE_CLOSED, + STATE_OPENING, + STATE_CLOSING, +) _LOGGER = logging.getLogger(__name__) -DOMAIN = 'cover' +DOMAIN = "cover" SCAN_INTERVAL = timedelta(seconds=15) -GROUP_NAME_ALL_COVERS = 'all covers' -ENTITY_ID_ALL_COVERS = group.ENTITY_ID_FORMAT.format('all_covers') +GROUP_NAME_ALL_COVERS = "all covers" +ENTITY_ID_ALL_COVERS = group.ENTITY_ID_FORMAT.format("all_covers") -ENTITY_ID_FORMAT = DOMAIN + '.{}' +ENTITY_ID_FORMAT = DOMAIN + ".{}" # Refer to the cover dev docs for device class descriptions -DEVICE_CLASS_AWNING = 'awning' -DEVICE_CLASS_BLIND = 'blind' -DEVICE_CLASS_CURTAIN = 'curtain' -DEVICE_CLASS_DAMPER = 'damper' -DEVICE_CLASS_DOOR = 'door' -DEVICE_CLASS_GARAGE = 'garage' -DEVICE_CLASS_SHADE = 'shade' -DEVICE_CLASS_SHUTTER = 'shutter' -DEVICE_CLASS_WINDOW = 'window' +DEVICE_CLASS_AWNING = "awning" +DEVICE_CLASS_BLIND = "blind" +DEVICE_CLASS_CURTAIN = "curtain" +DEVICE_CLASS_DAMPER = "damper" +DEVICE_CLASS_DOOR = "door" +DEVICE_CLASS_GARAGE = "garage" +DEVICE_CLASS_SHADE = "shade" +DEVICE_CLASS_SHUTTER = "shutter" +DEVICE_CLASS_WINDOW = "window" DEVICE_CLASSES = [ DEVICE_CLASS_AWNING, DEVICE_CLASS_BLIND, @@ -49,7 +61,7 @@ DEVICE_CLASSES = [ DEVICE_CLASS_GARAGE, DEVICE_CLASS_SHADE, DEVICE_CLASS_SHUTTER, - DEVICE_CLASS_WINDOW + DEVICE_CLASS_WINDOW, ] DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.In(DEVICE_CLASSES)) @@ -62,23 +74,25 @@ SUPPORT_CLOSE_TILT = 32 SUPPORT_STOP_TILT = 64 SUPPORT_SET_TILT_POSITION = 128 -ATTR_CURRENT_POSITION = 'current_position' -ATTR_CURRENT_TILT_POSITION = 'current_tilt_position' -ATTR_POSITION = 'position' -ATTR_TILT_POSITION = 'tilt_position' +ATTR_CURRENT_POSITION = "current_position" +ATTR_CURRENT_TILT_POSITION = "current_tilt_position" +ATTR_POSITION = "position" +ATTR_TILT_POSITION = "tilt_position" -INTENT_OPEN_COVER = 'HassOpenCover' -INTENT_CLOSE_COVER = 'HassCloseCover' +INTENT_OPEN_COVER = "HassOpenCover" +INTENT_CLOSE_COVER = "HassCloseCover" -COVER_SET_COVER_POSITION_SCHEMA = ENTITY_SERVICE_SCHEMA.extend({ - vol.Required(ATTR_POSITION): - vol.All(vol.Coerce(int), vol.Range(min=0, max=100)), -}) +COVER_SET_COVER_POSITION_SCHEMA = ENTITY_SERVICE_SCHEMA.extend( + {vol.Required(ATTR_POSITION): vol.All(vol.Coerce(int), vol.Range(min=0, max=100))} +) -COVER_SET_COVER_TILT_POSITION_SCHEMA = ENTITY_SERVICE_SCHEMA.extend({ - vol.Required(ATTR_TILT_POSITION): - vol.All(vol.Coerce(int), vol.Range(min=0, max=100)), -}) +COVER_SET_COVER_TILT_POSITION_SCHEMA = ENTITY_SERVICE_SCHEMA.extend( + { + vol.Required(ATTR_TILT_POSITION): vol.All( + vol.Coerce(int), vol.Range(min=0, max=100) + ) + } +) @bind_hass @@ -91,66 +105,65 @@ def is_closed(hass, entity_id=None): async def async_setup(hass, config): """Track states and offer events for covers.""" component = hass.data[DOMAIN] = EntityComponent( - _LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_COVERS) + _LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_COVERS + ) await component.async_setup(config) component.async_register_entity_service( - SERVICE_OPEN_COVER, ENTITY_SERVICE_SCHEMA, - 'async_open_cover' + SERVICE_OPEN_COVER, ENTITY_SERVICE_SCHEMA, "async_open_cover" ) component.async_register_entity_service( - SERVICE_CLOSE_COVER, ENTITY_SERVICE_SCHEMA, - 'async_close_cover' + SERVICE_CLOSE_COVER, ENTITY_SERVICE_SCHEMA, "async_close_cover" ) component.async_register_entity_service( - SERVICE_SET_COVER_POSITION, COVER_SET_COVER_POSITION_SCHEMA, - 'async_set_cover_position' + SERVICE_SET_COVER_POSITION, + COVER_SET_COVER_POSITION_SCHEMA, + "async_set_cover_position", ) component.async_register_entity_service( - SERVICE_STOP_COVER, ENTITY_SERVICE_SCHEMA, - 'async_stop_cover' + SERVICE_STOP_COVER, ENTITY_SERVICE_SCHEMA, "async_stop_cover" ) component.async_register_entity_service( - SERVICE_TOGGLE, ENTITY_SERVICE_SCHEMA, - 'async_toggle' + SERVICE_TOGGLE, ENTITY_SERVICE_SCHEMA, "async_toggle" ) component.async_register_entity_service( - SERVICE_OPEN_COVER_TILT, ENTITY_SERVICE_SCHEMA, - 'async_open_cover_tilt' + SERVICE_OPEN_COVER_TILT, ENTITY_SERVICE_SCHEMA, "async_open_cover_tilt" ) component.async_register_entity_service( - SERVICE_CLOSE_COVER_TILT, ENTITY_SERVICE_SCHEMA, - 'async_close_cover_tilt' + SERVICE_CLOSE_COVER_TILT, ENTITY_SERVICE_SCHEMA, "async_close_cover_tilt" ) component.async_register_entity_service( - SERVICE_STOP_COVER_TILT, ENTITY_SERVICE_SCHEMA, - 'async_stop_cover_tilt' + SERVICE_STOP_COVER_TILT, ENTITY_SERVICE_SCHEMA, "async_stop_cover_tilt" ) component.async_register_entity_service( - SERVICE_SET_COVER_TILT_POSITION, COVER_SET_COVER_TILT_POSITION_SCHEMA, - 'async_set_cover_tilt_position' + SERVICE_SET_COVER_TILT_POSITION, + COVER_SET_COVER_TILT_POSITION_SCHEMA, + "async_set_cover_tilt_position", ) component.async_register_entity_service( - SERVICE_TOGGLE_COVER_TILT, ENTITY_SERVICE_SCHEMA, - 'async_toggle_tilt' + SERVICE_TOGGLE_COVER_TILT, ENTITY_SERVICE_SCHEMA, "async_toggle_tilt" ) - hass.helpers.intent.async_register(intent.ServiceIntentHandler( - INTENT_OPEN_COVER, DOMAIN, SERVICE_OPEN_COVER, - "Opened {}")) - hass.helpers.intent.async_register(intent.ServiceIntentHandler( - INTENT_CLOSE_COVER, DOMAIN, SERVICE_CLOSE_COVER, - "Closed {}")) + hass.helpers.intent.async_register( + intent.ServiceIntentHandler( + INTENT_OPEN_COVER, DOMAIN, SERVICE_OPEN_COVER, "Opened {}" + ) + ) + hass.helpers.intent.async_register( + intent.ServiceIntentHandler( + INTENT_CLOSE_COVER, DOMAIN, SERVICE_CLOSE_COVER, "Closed {}" + ) + ) return True @@ -224,8 +237,11 @@ class CoverDevice(Entity): if self.current_cover_tilt_position is not None: supported_features |= ( - SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | SUPPORT_STOP_TILT | - SUPPORT_SET_TILT_POSITION) + SUPPORT_OPEN_TILT + | SUPPORT_CLOSE_TILT + | SUPPORT_STOP_TILT + | SUPPORT_SET_TILT_POSITION + ) return supported_features @@ -291,8 +307,7 @@ class CoverDevice(Entity): This method must be run in the event loop and returns a coroutine. """ - return self.hass.async_add_job( - ft.partial(self.set_cover_position, **kwargs)) + return self.hass.async_add_job(ft.partial(self.set_cover_position, **kwargs)) def stop_cover(self, **kwargs): """Stop the cover.""" @@ -314,8 +329,7 @@ class CoverDevice(Entity): This method must be run in the event loop and returns a coroutine. """ - return self.hass.async_add_job( - ft.partial(self.open_cover_tilt, **kwargs)) + return self.hass.async_add_job(ft.partial(self.open_cover_tilt, **kwargs)) def close_cover_tilt(self, **kwargs): """Close the cover tilt.""" @@ -326,8 +340,7 @@ class CoverDevice(Entity): This method must be run in the event loop and returns a coroutine. """ - return self.hass.async_add_job( - ft.partial(self.close_cover_tilt, **kwargs)) + return self.hass.async_add_job(ft.partial(self.close_cover_tilt, **kwargs)) def set_cover_tilt_position(self, **kwargs): """Move the cover tilt to a specific position.""" @@ -339,7 +352,8 @@ class CoverDevice(Entity): This method must be run in the event loop and returns a coroutine. """ return self.hass.async_add_job( - ft.partial(self.set_cover_tilt_position, **kwargs)) + ft.partial(self.set_cover_tilt_position, **kwargs) + ) def stop_cover_tilt(self, **kwargs): """Stop the cover.""" @@ -350,8 +364,7 @@ class CoverDevice(Entity): This method must be run in the event loop and returns a coroutine. """ - return self.hass.async_add_job( - ft.partial(self.stop_cover_tilt, **kwargs)) + return self.hass.async_add_job(ft.partial(self.stop_cover_tilt, **kwargs)) def toggle_tilt(self, **kwargs) -> None: """Toggle the entity.""" diff --git a/homeassistant/components/cppm_tracker/device_tracker.py b/homeassistant/components/cppm_tracker/device_tracker.py index 608ce6dad6b..c1c62a26dd9 100755 --- a/homeassistant/components/cppm_tracker/device_tracker.py +++ b/homeassistant/components/cppm_tracker/device_tracker.py @@ -5,23 +5,25 @@ from datetime import timedelta import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.device_tracker import ( - PLATFORM_SCHEMA, DeviceScanner, DOMAIN -) -from homeassistant.const import ( - CONF_HOST, CONF_API_KEY + PLATFORM_SCHEMA, + DeviceScanner, + DOMAIN, ) +from homeassistant.const import CONF_HOST, CONF_API_KEY SCAN_INTERVAL = timedelta(seconds=120) -CLIENT_ID = 'client_id' +CLIENT_ID = "client_id" -GRANT_TYPE = 'client_credentials' +GRANT_TYPE = "client_credentials" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CLIENT_ID): cv.string, - vol.Required(CONF_API_KEY): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CLIENT_ID): cv.string, + vol.Required(CONF_API_KEY): cv.string, + } +) _LOGGER = logging.getLogger(__name__) @@ -29,11 +31,12 @@ _LOGGER = logging.getLogger(__name__) def get_scanner(hass, config): """Initialize Scanner.""" from clearpasspy import ClearPass + data = { - 'server': config[DOMAIN][CONF_HOST], - 'grant_type': GRANT_TYPE, - 'secret': config[DOMAIN][CONF_API_KEY], - 'client': config[DOMAIN][CLIENT_ID] + "server": config[DOMAIN][CONF_HOST], + "grant_type": GRANT_TYPE, + "secret": config[DOMAIN][CONF_API_KEY], + "client": config[DOMAIN][CLIENT_ID], } cppm = ClearPass(data) if cppm.access_token is None: @@ -53,25 +56,22 @@ class CPPMDeviceScanner(DeviceScanner): def scan_devices(self): """Initialize scanner.""" self.get_cppm_data() - return [device['mac'] for device in self.results] + return [device["mac"] for device in self.results] def get_device_name(self, device): """Retrieve device name.""" - name = next(( - result['name'] for result in self.results - if result['mac'] == device), None) + name = next( + (result["name"] for result in self.results if result["mac"] == device), None + ) return name def get_cppm_data(self): """Retrieve data from Aruba Clearpass and return parsed result.""" - endpoints = self._cppm.get_endpoints(100)['_embedded']['items'] + endpoints = self._cppm.get_endpoints(100)["_embedded"]["items"] devices = [] for item in endpoints: - if self._cppm.online_status(item['mac_address']): - device = { - 'mac': item['mac_address'], - 'name': item['mac_address'] - } + if self._cppm.online_status(item["mac_address"]): + device = {"mac": item["mac_address"], "name": item["mac_address"]} devices.append(device) else: continue diff --git a/homeassistant/components/cpuspeed/sensor.py b/homeassistant/components/cpuspeed/sensor.py index ef9cb218cd7..9484e770998 100644 --- a/homeassistant/components/cpuspeed/sensor.py +++ b/homeassistant/components/cpuspeed/sensor.py @@ -10,20 +10,20 @@ from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -ATTR_BRAND = 'Brand' -ATTR_HZ = 'GHz Advertised' -ATTR_ARCH = 'arch' +ATTR_BRAND = "Brand" +ATTR_HZ = "GHz Advertised" +ATTR_ARCH = "arch" -HZ_ACTUAL_RAW = 'hz_actual_raw' -HZ_ADVERTISED_RAW = 'hz_advertised_raw' +HZ_ACTUAL_RAW = "hz_actual_raw" +HZ_ADVERTISED_RAW = "hz_advertised_raw" -DEFAULT_NAME = 'CPU speed' +DEFAULT_NAME = "CPU speed" -ICON = 'mdi:pulse' +ICON = "mdi:pulse" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string} +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -41,7 +41,7 @@ class CpuSpeedSensor(Entity): self._name = name self._state = None self.info = None - self._unit_of_measurement = 'GHz' + self._unit_of_measurement = "GHz" @property def name(self): @@ -62,15 +62,10 @@ class CpuSpeedSensor(Entity): def device_state_attributes(self): """Return the state attributes.""" if self.info is not None: - attrs = { - ATTR_ARCH: self.info['arch'], - ATTR_BRAND: self.info['brand'], - } + attrs = {ATTR_ARCH: self.info["arch"], ATTR_BRAND: self.info["brand"]} if HZ_ADVERTISED_RAW in self.info: - attrs[ATTR_HZ] = round( - self.info[HZ_ADVERTISED_RAW][0] / 10 ** 9, 2 - ) + attrs[ATTR_HZ] = round(self.info[HZ_ADVERTISED_RAW][0] / 10 ** 9, 2) return attrs @property @@ -84,8 +79,6 @@ class CpuSpeedSensor(Entity): self.info = cpuinfo.get_cpu_info() if HZ_ACTUAL_RAW in self.info: - self._state = round( - float(self.info[HZ_ACTUAL_RAW][0]) / 10 ** 9, 2 - ) + self._state = round(float(self.info[HZ_ACTUAL_RAW][0]) / 10 ** 9, 2) else: self._state = None diff --git a/homeassistant/components/crimereports/sensor.py b/homeassistant/components/crimereports/sensor.py index 5e25d800247..2ad31e7513b 100644 --- a/homeassistant/components/crimereports/sensor.py +++ b/homeassistant/components/crimereports/sensor.py @@ -7,9 +7,18 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_INCLUDE, CONF_EXCLUDE, CONF_NAME, CONF_LATITUDE, CONF_LONGITUDE, - ATTR_ATTRIBUTION, ATTR_LATITUDE, ATTR_LONGITUDE, CONF_RADIUS, - LENGTH_KILOMETERS, LENGTH_METERS) + CONF_INCLUDE, + CONF_EXCLUDE, + CONF_NAME, + CONF_LATITUDE, + CONF_LONGITUDE, + ATTR_ATTRIBUTION, + ATTR_LATITUDE, + ATTR_LONGITUDE, + CONF_RADIUS, + LENGTH_KILOMETERS, + LENGTH_METERS, +) from homeassistant.helpers.entity import Entity from homeassistant.util import slugify from homeassistant.util.distance import convert @@ -18,20 +27,22 @@ import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DOMAIN = 'crimereports' +DOMAIN = "crimereports" -EVENT_INCIDENT = '{}_incident'.format(DOMAIN) +EVENT_INCIDENT = "{}_incident".format(DOMAIN) SCAN_INTERVAL = timedelta(minutes=30) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_NAME): cv.string, - vol.Required(CONF_RADIUS): vol.Coerce(float), - vol.Inclusive(CONF_LATITUDE, 'coordinates'): cv.latitude, - vol.Inclusive(CONF_LONGITUDE, 'coordinates'): cv.longitude, - vol.Optional(CONF_INCLUDE): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_EXCLUDE): vol.All(cv.ensure_list, [cv.string]) -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_RADIUS): vol.Coerce(float), + vol.Inclusive(CONF_LATITUDE, "coordinates"): cv.latitude, + vol.Inclusive(CONF_LONGITUDE, "coordinates"): cv.longitude, + vol.Optional(CONF_INCLUDE): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_EXCLUDE): vol.All(cv.ensure_list, [cv.string]), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -43,24 +54,27 @@ def setup_platform(hass, config, add_entities, discovery_info=None): include = config.get(CONF_INCLUDE) exclude = config.get(CONF_EXCLUDE) - add_entities([CrimeReportsSensor( - hass, name, latitude, longitude, radius, include, exclude)], True) + add_entities( + [CrimeReportsSensor(hass, name, latitude, longitude, radius, include, exclude)], + True, + ) class CrimeReportsSensor(Entity): """Representation of a Crime Reports Sensor.""" - def __init__(self, hass, name, latitude, longitude, radius, - include, exclude): + def __init__(self, hass, name, latitude, longitude, radius, include, exclude): """Initialize the Crime Reports sensor.""" import crimereports + self._hass = hass self._name = name self._include = include self._exclude = exclude radius_kilometers = convert(radius, LENGTH_METERS, LENGTH_KILOMETERS) self._crimereports = crimereports.CrimeReports( - (latitude, longitude), radius_kilometers) + (latitude, longitude), radius_kilometers + ) self._attributes = None self._state = None self._previous_incidents = set() @@ -83,36 +97,37 @@ class CrimeReportsSensor(Entity): def _incident_event(self, incident): """Fire if an event occurs.""" data = { - 'type': incident.get('type'), - 'description': incident.get('friendly_description'), - 'timestamp': incident.get('timestamp'), - 'location': incident.get('location') + "type": incident.get("type"), + "description": incident.get("friendly_description"), + "timestamp": incident.get("timestamp"), + "location": incident.get("location"), } - if incident.get('coordinates'): - data.update({ - ATTR_LATITUDE: incident.get('coordinates')[0], - ATTR_LONGITUDE: incident.get('coordinates')[1] - }) + if incident.get("coordinates"): + data.update( + { + ATTR_LATITUDE: incident.get("coordinates")[0], + ATTR_LONGITUDE: incident.get("coordinates")[1], + } + ) self._hass.bus.fire(EVENT_INCIDENT, data) def update(self): """Update device state.""" import crimereports + incident_counts = defaultdict(int) incidents = self._crimereports.get_incidents( - now().date(), include=self._include, exclude=self._exclude) + now().date(), include=self._include, exclude=self._exclude + ) fire_events = len(self._previous_incidents) > 0 if len(incidents) < len(self._previous_incidents): self._previous_incidents = set() for incident in incidents: - incident_type = slugify(incident.get('type')) + incident_type = slugify(incident.get("type")) incident_counts[incident_type] += 1 - if (fire_events and incident.get('id') - not in self._previous_incidents): + if fire_events and incident.get("id") not in self._previous_incidents: self._incident_event(incident) - self._previous_incidents.add(incident.get('id')) - self._attributes = { - ATTR_ATTRIBUTION: crimereports.ATTRIBUTION - } + self._previous_incidents.add(incident.get("id")) + self._attributes = {ATTR_ATTRIBUTION: crimereports.ATTRIBUTION} self._attributes.update(incident_counts) self._state = len(incidents) diff --git a/homeassistant/components/cups/sensor.py b/homeassistant/components/cups/sensor.py index ea171f94bf3..79bb050a617 100644 --- a/homeassistant/components/cups/sensor.py +++ b/homeassistant/components/cups/sensor.py @@ -13,45 +13,42 @@ from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -ATTR_MARKER_TYPE = 'marker_type' -ATTR_MARKER_LOW_LEVEL = 'marker_low_level' -ATTR_MARKER_HIGH_LEVEL = 'marker_high_level' -ATTR_PRINTER_NAME = 'printer_name' -ATTR_DEVICE_URI = 'device_uri' -ATTR_PRINTER_INFO = 'printer_info' -ATTR_PRINTER_IS_SHARED = 'printer_is_shared' -ATTR_PRINTER_LOCATION = 'printer_location' -ATTR_PRINTER_MODEL = 'printer_model' -ATTR_PRINTER_STATE_MESSAGE = 'printer_state_message' -ATTR_PRINTER_STATE_REASON = 'printer_state_reason' -ATTR_PRINTER_TYPE = 'printer_type' -ATTR_PRINTER_URI_SUPPORTED = 'printer_uri_supported' +ATTR_MARKER_TYPE = "marker_type" +ATTR_MARKER_LOW_LEVEL = "marker_low_level" +ATTR_MARKER_HIGH_LEVEL = "marker_high_level" +ATTR_PRINTER_NAME = "printer_name" +ATTR_DEVICE_URI = "device_uri" +ATTR_PRINTER_INFO = "printer_info" +ATTR_PRINTER_IS_SHARED = "printer_is_shared" +ATTR_PRINTER_LOCATION = "printer_location" +ATTR_PRINTER_MODEL = "printer_model" +ATTR_PRINTER_STATE_MESSAGE = "printer_state_message" +ATTR_PRINTER_STATE_REASON = "printer_state_reason" +ATTR_PRINTER_TYPE = "printer_type" +ATTR_PRINTER_URI_SUPPORTED = "printer_uri_supported" -CONF_PRINTERS = 'printers' -CONF_IS_CUPS_SERVER = 'is_cups_server' +CONF_PRINTERS = "printers" +CONF_IS_CUPS_SERVER = "is_cups_server" -DEFAULT_HOST = '127.0.0.1' +DEFAULT_HOST = "127.0.0.1" DEFAULT_PORT = 631 DEFAULT_IS_CUPS_SERVER = True -ICON_PRINTER = 'mdi:printer' -ICON_MARKER = 'mdi:water' +ICON_PRINTER = "mdi:printer" +ICON_MARKER = "mdi:water" SCAN_INTERVAL = timedelta(minutes=1) -PRINTER_STATES = { - 3: 'idle', - 4: 'printing', - 5: 'stopped', -} +PRINTER_STATES = {3: "idle", 4: "printing", 5: "stopped"} -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_PRINTERS): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_IS_CUPS_SERVER, - default=DEFAULT_IS_CUPS_SERVER): cv.boolean, - vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_PRINTERS): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_IS_CUPS_SERVER, default=DEFAULT_IS_CUPS_SERVER): cv.boolean, + vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -65,8 +62,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): data = CupsData(host, port, None) data.update() if data.available is False: - _LOGGER.error("Unable to connect to CUPS server: %s:%s", - host, port) + _LOGGER.error("Unable to connect to CUPS server: %s:%s", host, port) raise PlatformNotReady() dev = [] @@ -86,8 +82,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): data = CupsData(host, port, printers) data.update() if data.available is False: - _LOGGER.error("Unable to connect to IPP printer: %s:%s", - host, port) + _LOGGER.error("Unable to connect to IPP printer: %s:%s", host, port) raise PlatformNotReady() dev = [] @@ -122,7 +117,7 @@ class CupsSensor(Entity): if self._printer is None: return None - key = self._printer['printer-state'] + key = self._printer["printer-state"] return PRINTER_STATES.get(key, key) @property @@ -142,18 +137,15 @@ class CupsSensor(Entity): return None return { - ATTR_DEVICE_URI: self._printer['device-uri'], - ATTR_PRINTER_INFO: self._printer['printer-info'], - ATTR_PRINTER_IS_SHARED: self._printer['printer-is-shared'], - ATTR_PRINTER_LOCATION: self._printer['printer-location'], - ATTR_PRINTER_MODEL: self._printer['printer-make-and-model'], - ATTR_PRINTER_STATE_MESSAGE: - self._printer['printer-state-message'], - ATTR_PRINTER_STATE_REASON: - self._printer['printer-state-reasons'], - ATTR_PRINTER_TYPE: self._printer['printer-type'], - ATTR_PRINTER_URI_SUPPORTED: - self._printer['printer-uri-supported'], + ATTR_DEVICE_URI: self._printer["device-uri"], + ATTR_PRINTER_INFO: self._printer["printer-info"], + ATTR_PRINTER_IS_SHARED: self._printer["printer-is-shared"], + ATTR_PRINTER_LOCATION: self._printer["printer-location"], + ATTR_PRINTER_MODEL: self._printer["printer-make-and-model"], + ATTR_PRINTER_STATE_MESSAGE: self._printer["printer-state-message"], + ATTR_PRINTER_STATE_REASON: self._printer["printer-state-reasons"], + ATTR_PRINTER_TYPE: self._printer["printer-type"], + ATTR_PRINTER_URI_SUPPORTED: self._printer["printer-uri-supported"], } def update(self): @@ -179,7 +171,7 @@ class IPPSensor(Entity): @property def name(self): """Return the name of the sensor.""" - return self._attributes['printer-make-and-model'] + return self._attributes["printer-make-and-model"] @property def icon(self): @@ -197,7 +189,7 @@ class IPPSensor(Entity): if self._attributes is None: return None - key = self._attributes['printer-state'] + key = self._attributes["printer-state"] return PRINTER_STATES.get(key, key) @property @@ -208,25 +200,28 @@ class IPPSensor(Entity): state_attributes = {} - if 'printer-info' in self._attributes: - state_attributes[ATTR_PRINTER_INFO] = \ - self._attributes['printer-info'] + if "printer-info" in self._attributes: + state_attributes[ATTR_PRINTER_INFO] = self._attributes["printer-info"] - if 'printer-location' in self._attributes: - state_attributes[ATTR_PRINTER_LOCATION] = \ - self._attributes['printer-location'] + if "printer-location" in self._attributes: + state_attributes[ATTR_PRINTER_LOCATION] = self._attributes[ + "printer-location" + ] - if 'printer-state-message' in self._attributes: - state_attributes[ATTR_PRINTER_STATE_MESSAGE] = \ - self._attributes['printer-state-message'] + if "printer-state-message" in self._attributes: + state_attributes[ATTR_PRINTER_STATE_MESSAGE] = self._attributes[ + "printer-state-message" + ] - if 'printer-state-reasons' in self._attributes: - state_attributes[ATTR_PRINTER_STATE_REASON] = \ - self._attributes['printer-state-reasons'] + if "printer-state-reasons" in self._attributes: + state_attributes[ATTR_PRINTER_STATE_REASON] = self._attributes[ + "printer-state-reasons" + ] - if 'printer-uri-supported' in self._attributes: - state_attributes[ATTR_PRINTER_URI_SUPPORTED] = \ - self._attributes['printer-uri-supported'] + if "printer-uri-supported" in self._attributes: + state_attributes[ATTR_PRINTER_URI_SUPPORTED] = self._attributes[ + "printer-uri-supported" + ] return state_attributes @@ -248,7 +243,7 @@ class MarkerSensor(Entity): self.data = data self._name = name self._printer = printer - self._index = data.attributes[printer]['marker-names'].index(name) + self._index = data.attributes[printer]["marker-names"].index(name) self._is_cups = is_cups self._attributes = None @@ -268,7 +263,7 @@ class MarkerSensor(Entity): if self._attributes is None: return None - return self._attributes[self._printer]['marker-levels'][self._index] + return self._attributes[self._printer]["marker-levels"][self._index] @property def unit_of_measurement(self): @@ -281,30 +276,28 @@ class MarkerSensor(Entity): if self._attributes is None: return None - high_level = self._attributes[self._printer]['marker-high-levels'] + high_level = self._attributes[self._printer]["marker-high-levels"] if isinstance(high_level, list): high_level = high_level[self._index] - low_level = self._attributes[self._printer]['marker-low-levels'] + low_level = self._attributes[self._printer]["marker-low-levels"] if isinstance(low_level, list): low_level = low_level[self._index] - marker_types = self._attributes[self._printer]['marker-types'] + marker_types = self._attributes[self._printer]["marker-types"] if isinstance(marker_types, list): marker_types = marker_types[self._index] if self._is_cups: printer_name = self._printer else: - printer_name = \ - self._attributes[self._printer]['printer-make-and-model'] + printer_name = self._attributes[self._printer]["printer-make-and-model"] return { ATTR_MARKER_HIGH_LEVEL: high_level, ATTR_MARKER_LOW_LEVEL: low_level, ATTR_MARKER_TYPE: marker_types, - ATTR_PRINTER_NAME: printer_name - + ATTR_PRINTER_NAME: printer_name, } def update(self): @@ -322,27 +315,26 @@ class CupsData: self._host = host self._port = port self._ipp_printers = ipp_printers - self.is_cups = (ipp_printers is None) + self.is_cups = ipp_printers is None self.printers = None self.attributes = {} self.available = False def update(self): """Get the latest data from CUPS.""" - cups = importlib.import_module('cups') + cups = importlib.import_module("cups") try: conn = cups.Connection(host=self._host, port=self._port) if self.is_cups: self.printers = conn.getPrinters() for printer in self.printers: - self.attributes[printer] = conn.getPrinterAttributes( - name=printer) + self.attributes[printer] = conn.getPrinterAttributes(name=printer) else: for ipp_printer in self._ipp_printers: self.attributes[ipp_printer] = conn.getPrinterAttributes( - uri="ipp://{}:{}/{}" - .format(self._host, self._port, ipp_printer)) + uri="ipp://{}:{}/{}".format(self._host, self._port, ipp_printer) + ) self.available = True except RuntimeError: diff --git a/homeassistant/components/currencylayer/sensor.py b/homeassistant/components/currencylayer/sensor.py index bedd5f079ce..dbafae55187 100644 --- a/homeassistant/components/currencylayer/sensor.py +++ b/homeassistant/components/currencylayer/sensor.py @@ -8,46 +8,49 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_API_KEY, CONF_NAME, CONF_BASE, CONF_QUOTE, ATTR_ATTRIBUTION) + CONF_API_KEY, + CONF_NAME, + CONF_BASE, + CONF_QUOTE, + ATTR_ATTRIBUTION, +) from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -_RESOURCE = 'http://apilayer.net/api/live' +_RESOURCE = "http://apilayer.net/api/live" ATTRIBUTION = "Data provided by currencylayer.com" -DEFAULT_BASE = 'USD' -DEFAULT_NAME = 'CurrencyLayer Sensor' +DEFAULT_BASE = "USD" +DEFAULT_NAME = "CurrencyLayer Sensor" -ICON = 'mdi:currency' +ICON = "mdi:currency" SCAN_INTERVAL = timedelta(hours=2) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_API_KEY): cv.string, - vol.Required(CONF_QUOTE): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_BASE, default=DEFAULT_BASE): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_API_KEY): cv.string, + vol.Required(CONF_QUOTE): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_BASE, default=DEFAULT_BASE): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Currencylayer sensor.""" base = config.get(CONF_BASE) api_key = config.get(CONF_API_KEY) - parameters = { - 'source': base, - 'access_key': api_key, - 'format': 1, - } + parameters = {"source": base, "access_key": api_key, "format": 1} rest = CurrencylayerData(_RESOURCE, parameters) response = requests.get(_RESOURCE, params=parameters, timeout=10) sensors = [] - for variable in config['quote']: + for variable in config["quote"]: sensors.append(CurrencylayerSensor(rest, base, variable)) - if 'error' in response.json(): + if "error" in response.json(): return False add_entities(sensors, True) @@ -85,17 +88,14 @@ class CurrencylayerSensor(Entity): @property def device_state_attributes(self): """Return the state attributes of the sensor.""" - return { - ATTR_ATTRIBUTION: ATTRIBUTION, - } + return {ATTR_ATTRIBUTION: ATTRIBUTION} def update(self): """Update current date.""" self.rest.update() value = self.rest.data if value is not None: - self._state = round( - value['{}{}'.format(self._base, self._quote)], 4) + self._state = round(value["{}{}".format(self._base, self._quote)], 4) class CurrencylayerData: @@ -110,13 +110,11 @@ class CurrencylayerData: def update(self): """Get the latest data from Currencylayer.""" try: - result = requests.get( - self._resource, params=self._parameters, timeout=10) - if 'error' in result.json(): - raise ValueError(result.json()['error']['info']) - self.data = result.json()['quotes'] - _LOGGER.debug("Currencylayer data updated: %s", - result.json()['timestamp']) + result = requests.get(self._resource, params=self._parameters, timeout=10) + if "error" in result.json(): + raise ValueError(result.json()["error"]["info"]) + self.data = result.json()["quotes"] + _LOGGER.debug("Currencylayer data updated: %s", result.json()["timestamp"]) except ValueError as err: _LOGGER.error("Check Currencylayer API %s", err.args) self.data = None diff --git a/homeassistant/components/daikin/__init__.py b/homeassistant/components/daikin/__init__.py index edc447fe721..390e80d0916 100644 --- a/homeassistant/components/daikin/__init__.py +++ b/homeassistant/components/daikin/__init__.py @@ -19,20 +19,21 @@ from . import config_flow # noqa pylint_disable=unused-import _LOGGER = logging.getLogger(__name__) -DOMAIN = 'daikin' +DOMAIN = "daikin" PARALLEL_UPDATES = 0 MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) -COMPONENT_TYPES = ['climate', 'sensor', 'switch'] +COMPONENT_TYPES = ["climate", "sensor", "switch"] -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Optional( - CONF_HOSTS, default=[] - ): vol.All(cv.ensure_list, [cv.string]), - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + {vol.Optional(CONF_HOSTS, default=[]): vol.All(cv.ensure_list, [cv.string])} + ) + }, + extra=vol.ALLOW_EXTRA, +) async def async_setup(hass, config): @@ -44,15 +45,15 @@ async def async_setup(hass, config): if not hosts: hass.async_create_task( hass.config_entries.flow.async_init( - DOMAIN, context={'source': SOURCE_IMPORT})) + DOMAIN, context={"source": SOURCE_IMPORT} + ) + ) for host in hosts: hass.async_create_task( hass.config_entries.flow.async_init( - DOMAIN, - context={'source': SOURCE_IMPORT}, - data={ - CONF_HOST: host, - })) + DOMAIN, context={"source": SOURCE_IMPORT}, data={CONF_HOST: host} + ) + ) return True @@ -65,17 +66,19 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): hass.data.setdefault(DOMAIN, {}).update({entry.entry_id: daikin_api}) for component in COMPONENT_TYPES: hass.async_create_task( - hass.config_entries.async_forward_entry_setup( - entry, component)) + hass.config_entries.async_forward_entry_setup(entry, component) + ) return True async def async_unload_entry(hass, config_entry): """Unload a config entry.""" - await asyncio.wait([ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in COMPONENT_TYPES - ]) + await asyncio.wait( + [ + hass.config_entries.async_forward_entry_unload(config_entry, component) + for component in COMPONENT_TYPES + ] + ) hass.data[DOMAIN].pop(config_entry.entry_id) if not hass.data[DOMAIN]: hass.data.pop(DOMAIN) @@ -85,6 +88,7 @@ async def async_unload_entry(hass, config_entry): async def daikin_api_setup(hass, host): """Create a Daikin instance only once.""" from pydaikin.appliance import Appliance + session = hass.helpers.aiohttp_client.async_get_clientsession() try: with timeout(10): @@ -111,7 +115,7 @@ class DaikinApi: def __init__(self, device): """Initialize the Daikin Handle.""" self.device = device - self.name = device.values['name'] + self.name = device.values["name"] self.ip_address = device.ip self._available = True @@ -122,9 +126,7 @@ class DaikinApi: await self.device.update_status() self._available = True except ClientConnectionError: - _LOGGER.warning( - "Connection failed for %s", self.ip_address - ) + _LOGGER.warning("Connection failed for %s", self.ip_address) self._available = False @property @@ -142,10 +144,10 @@ class DaikinApi: """Return a device description for device registry.""" info = self.device.values return { - 'connections': {(CONNECTION_NETWORK_MAC, self.mac)}, - 'identifieres': self.mac, - 'manufacturer': 'Daikin', - 'model': info.get('model'), - 'name': info.get('name'), - 'sw_version': info.get('ver').replace('_', '.'), + "connections": {(CONNECTION_NETWORK_MAC, self.mac)}, + "identifieres": self.mac, + "manufacturer": "Daikin", + "model": info.get("model"), + "name": info.get("name"), + "sw_version": info.get("ver").replace("_", "."), } diff --git a/homeassistant/components/daikin/climate.py b/homeassistant/components/daikin/climate.py index 25bddc29b46..ddc5353250c 100644 --- a/homeassistant/components/daikin/climate.py +++ b/homeassistant/components/daikin/climate.py @@ -5,63 +5,73 @@ import voluptuous as vol from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice from homeassistant.components.climate.const import ( - ATTR_FAN_MODE, ATTR_HVAC_MODE, ATTR_PRESET_MODE, ATTR_SWING_MODE, - HVAC_MODE_COOL, HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, PRESET_AWAY, PRESET_NONE, - SUPPORT_FAN_MODE, SUPPORT_PRESET_MODE, SUPPORT_SWING_MODE, - SUPPORT_TARGET_TEMPERATURE) -from homeassistant.const import ( - ATTR_TEMPERATURE, CONF_HOST, CONF_NAME, TEMP_CELSIUS) + ATTR_FAN_MODE, + ATTR_HVAC_MODE, + ATTR_PRESET_MODE, + ATTR_SWING_MODE, + HVAC_MODE_COOL, + HVAC_MODE_DRY, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_OFF, + PRESET_AWAY, + PRESET_NONE, + SUPPORT_FAN_MODE, + SUPPORT_PRESET_MODE, + SUPPORT_SWING_MODE, + SUPPORT_TARGET_TEMPERATURE, +) +from homeassistant.const import ATTR_TEMPERATURE, CONF_HOST, CONF_NAME, TEMP_CELSIUS import homeassistant.helpers.config_validation as cv from . import DOMAIN as DAIKIN_DOMAIN from .const import ( - ATTR_INSIDE_TEMPERATURE, ATTR_OUTSIDE_TEMPERATURE, ATTR_STATE_OFF, - ATTR_STATE_ON, ATTR_TARGET_TEMPERATURE) + ATTR_INSIDE_TEMPERATURE, + ATTR_OUTSIDE_TEMPERATURE, + ATTR_STATE_OFF, + ATTR_STATE_ON, + ATTR_TARGET_TEMPERATURE, +) _LOGGER = logging.getLogger(__name__) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_HOST): cv.string, vol.Optional(CONF_NAME): cv.string} +) HA_STATE_TO_DAIKIN = { - HVAC_MODE_FAN_ONLY: 'fan', - HVAC_MODE_DRY: 'dry', - HVAC_MODE_COOL: 'cool', - HVAC_MODE_HEAT: 'hot', - HVAC_MODE_HEAT_COOL: 'auto', - HVAC_MODE_OFF: 'off', + HVAC_MODE_FAN_ONLY: "fan", + HVAC_MODE_DRY: "dry", + HVAC_MODE_COOL: "cool", + HVAC_MODE_HEAT: "hot", + HVAC_MODE_HEAT_COOL: "auto", + HVAC_MODE_OFF: "off", } DAIKIN_TO_HA_STATE = { - 'fan': HVAC_MODE_FAN_ONLY, - 'dry': HVAC_MODE_DRY, - 'cool': HVAC_MODE_COOL, - 'hot': HVAC_MODE_HEAT, - 'auto': HVAC_MODE_HEAT_COOL, - 'off': HVAC_MODE_OFF, + "fan": HVAC_MODE_FAN_ONLY, + "dry": HVAC_MODE_DRY, + "cool": HVAC_MODE_COOL, + "hot": HVAC_MODE_HEAT, + "auto": HVAC_MODE_HEAT_COOL, + "off": HVAC_MODE_OFF, } -HA_PRESET_TO_DAIKIN = { - PRESET_AWAY: 'on', - PRESET_NONE: 'off' -} +HA_PRESET_TO_DAIKIN = {PRESET_AWAY: "on", PRESET_NONE: "off"} HA_ATTR_TO_DAIKIN = { - ATTR_PRESET_MODE: 'en_hol', - ATTR_HVAC_MODE: 'mode', - ATTR_FAN_MODE: 'f_rate', - ATTR_SWING_MODE: 'f_dir', - ATTR_INSIDE_TEMPERATURE: 'htemp', - ATTR_OUTSIDE_TEMPERATURE: 'otemp', - ATTR_TARGET_TEMPERATURE: 'stemp' + ATTR_PRESET_MODE: "en_hol", + ATTR_HVAC_MODE: "mode", + ATTR_FAN_MODE: "f_rate", + ATTR_SWING_MODE: "f_dir", + ATTR_INSIDE_TEMPERATURE: "htemp", + ATTR_OUTSIDE_TEMPERATURE: "otemp", + ATTR_TARGET_TEMPERATURE: "stemp", } -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Old way of setting up the Daikin HVAC platform. Can only be called when a user accidentally mentions the platform in their @@ -90,7 +100,7 @@ class DaikinClimate(ClimateDevice): ATTR_SWING_MODE: list( map( str.title, - appliance.daikin_values(HA_ATTR_TO_DAIKIN[ATTR_SWING_MODE]) + appliance.daikin_values(HA_ATTR_TO_DAIKIN[ATTR_SWING_MODE]), ) ), } @@ -110,8 +120,7 @@ class DaikinClimate(ClimateDevice): """Set device settings using API.""" values = {} - for attr in [ATTR_TEMPERATURE, ATTR_FAN_MODE, ATTR_SWING_MODE, - ATTR_HVAC_MODE]: + for attr in [ATTR_TEMPERATURE, ATTR_FAN_MODE, ATTR_SWING_MODE, ATTR_HVAC_MODE]: value = settings.get(attr) if value is None: continue @@ -128,8 +137,7 @@ class DaikinClimate(ClimateDevice): # temperature elif attr == ATTR_TEMPERATURE: try: - values[HA_ATTR_TO_DAIKIN[ATTR_TARGET_TEMPERATURE]] = \ - str(int(value)) + values[HA_ATTR_TO_DAIKIN[ATTR_TARGET_TEMPERATURE]] = str(int(value)) except ValueError: _LOGGER.error("Invalid temperature %s", value) @@ -178,8 +186,7 @@ class DaikinClimate(ClimateDevice): @property def hvac_mode(self): """Return current operation ie. heat, cool, idle.""" - daikin_mode = self._api.device.represent( - HA_ATTR_TO_DAIKIN[ATTR_HVAC_MODE])[1] + daikin_mode = self._api.device.represent(HA_ATTR_TO_DAIKIN[ATTR_HVAC_MODE])[1] return DAIKIN_TO_HA_STATE.get(daikin_mode, HVAC_MODE_HEAT_COOL) @property @@ -194,8 +201,7 @@ class DaikinClimate(ClimateDevice): @property def fan_mode(self): """Return the fan setting.""" - return self._api.device.represent( - HA_ATTR_TO_DAIKIN[ATTR_FAN_MODE])[1].title() + return self._api.device.represent(HA_ATTR_TO_DAIKIN[ATTR_FAN_MODE])[1].title() async def async_set_fan_mode(self, fan_mode): """Set fan mode.""" @@ -209,8 +215,7 @@ class DaikinClimate(ClimateDevice): @property def swing_mode(self): """Return the fan setting.""" - return self._api.device.represent( - HA_ATTR_TO_DAIKIN[ATTR_SWING_MODE])[1].title() + return self._api.device.represent(HA_ATTR_TO_DAIKIN[ATTR_SWING_MODE])[1].title() async def async_set_swing_mode(self, swing_mode): """Set new target temperature.""" @@ -224,8 +229,10 @@ class DaikinClimate(ClimateDevice): @property def preset_mode(self): """Return the preset_mode.""" - if self._api.device.represent(HA_ATTR_TO_DAIKIN[ATTR_PRESET_MODE] - )[1] == HA_PRESET_TO_DAIKIN[PRESET_AWAY]: + if ( + self._api.device.represent(HA_ATTR_TO_DAIKIN[ATTR_PRESET_MODE])[1] + == HA_PRESET_TO_DAIKIN[PRESET_AWAY] + ): return PRESET_AWAY return PRESET_NONE @@ -251,10 +258,9 @@ class DaikinClimate(ClimateDevice): async def async_turn_off(self): """Turn device off.""" - await self._api.device.set({ - HA_ATTR_TO_DAIKIN[ATTR_HVAC_MODE]: - HA_STATE_TO_DAIKIN[HVAC_MODE_OFF] - }) + await self._api.device.set( + {HA_ATTR_TO_DAIKIN[ATTR_HVAC_MODE]: HA_STATE_TO_DAIKIN[HVAC_MODE_OFF]} + ) @property def device_info(self): diff --git a/homeassistant/components/daikin/config_flow.py b/homeassistant/components/daikin/config_flow.py index 7c214e77050..36d8ef0d383 100644 --- a/homeassistant/components/daikin/config_flow.py +++ b/homeassistant/components/daikin/config_flow.py @@ -14,7 +14,7 @@ from .const import KEY_IP, KEY_MAC _LOGGER = logging.getLogger(__name__) -@config_entries.HANDLERS.register('daikin') +@config_entries.HANDLERS.register("daikin") class FlowHandler(config_entries.ConfigFlow): """Handle a config flow.""" @@ -26,45 +26,37 @@ class FlowHandler(config_entries.ConfigFlow): # Check if mac already is registered for entry in self._async_current_entries(): if entry.data[KEY_MAC] == mac: - return self.async_abort(reason='already_configured') + return self.async_abort(reason="already_configured") - return self.async_create_entry( - title=host, - data={ - CONF_HOST: host, - KEY_MAC: mac - }) + return self.async_create_entry(title=host, data={CONF_HOST: host, KEY_MAC: mac}) async def _create_device(self, host): """Create device.""" from pydaikin.appliance import Appliance + try: device = Appliance( - host, - self.hass.helpers.aiohttp_client.async_get_clientsession(), + host, self.hass.helpers.aiohttp_client.async_get_clientsession() ) with timeout(10): await device.init() except asyncio.TimeoutError: - return self.async_abort(reason='device_timeout') + return self.async_abort(reason="device_timeout") except ClientError: _LOGGER.exception("ClientError") - return self.async_abort(reason='device_fail') + return self.async_abort(reason="device_fail") except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected error creating device") - return self.async_abort(reason='device_fail') + return self.async_abort(reason="device_fail") - mac = device.values.get('mac') + mac = device.values.get("mac") return await self._create_entry(host, mac) async def async_step_user(self, user_input=None): """User initiated config flow.""" if user_input is None: return self.async_show_form( - step_id='user', - data_schema=vol.Schema({ - vol.Required(CONF_HOST): str - }) + step_id="user", data_schema=vol.Schema({vol.Required(CONF_HOST): str}) ) return await self._create_device(user_input[CONF_HOST]) @@ -78,5 +70,4 @@ class FlowHandler(config_entries.ConfigFlow): async def async_step_discovery(self, user_input): """Initialize step from discovery.""" _LOGGER.info("Discovered device: %s", user_input) - return await self._create_entry(user_input[KEY_IP], - user_input[KEY_MAC]) + return await self._create_entry(user_input[KEY_IP], user_input[KEY_MAC]) diff --git a/homeassistant/components/daikin/const.py b/homeassistant/components/daikin/const.py index 69a8372cff4..ef24a51be89 100644 --- a/homeassistant/components/daikin/const.py +++ b/homeassistant/components/daikin/const.py @@ -1,27 +1,27 @@ """Constants for Daikin.""" from homeassistant.const import CONF_ICON, CONF_NAME, CONF_TYPE -ATTR_TARGET_TEMPERATURE = 'target_temperature' -ATTR_INSIDE_TEMPERATURE = 'inside_temperature' -ATTR_OUTSIDE_TEMPERATURE = 'outside_temperature' +ATTR_TARGET_TEMPERATURE = "target_temperature" +ATTR_INSIDE_TEMPERATURE = "inside_temperature" +ATTR_OUTSIDE_TEMPERATURE = "outside_temperature" -ATTR_STATE_ON = 'on' -ATTR_STATE_OFF = 'off' +ATTR_STATE_ON = "on" +ATTR_STATE_OFF = "off" -SENSOR_TYPE_TEMPERATURE = 'temperature' +SENSOR_TYPE_TEMPERATURE = "temperature" SENSOR_TYPES = { ATTR_INSIDE_TEMPERATURE: { - CONF_NAME: 'Inside Temperature', - CONF_ICON: 'mdi:thermometer', - CONF_TYPE: SENSOR_TYPE_TEMPERATURE + CONF_NAME: "Inside Temperature", + CONF_ICON: "mdi:thermometer", + CONF_TYPE: SENSOR_TYPE_TEMPERATURE, }, ATTR_OUTSIDE_TEMPERATURE: { - CONF_NAME: 'Outside Temperature', - CONF_ICON: 'mdi:thermometer', - CONF_TYPE: SENSOR_TYPE_TEMPERATURE - } + CONF_NAME: "Outside Temperature", + CONF_ICON: "mdi:thermometer", + CONF_TYPE: SENSOR_TYPE_TEMPERATURE, + }, } -KEY_MAC = 'mac' -KEY_IP = 'ip' +KEY_MAC = "mac" +KEY_IP = "ip" diff --git a/homeassistant/components/daikin/sensor.py b/homeassistant/components/daikin/sensor.py index d11f1039d6e..c55988b8dc1 100644 --- a/homeassistant/components/daikin/sensor.py +++ b/homeassistant/components/daikin/sensor.py @@ -7,14 +7,16 @@ from homeassistant.util.unit_system import UnitSystem from . import DOMAIN as DAIKIN_DOMAIN from .const import ( - ATTR_INSIDE_TEMPERATURE, ATTR_OUTSIDE_TEMPERATURE, SENSOR_TYPE_TEMPERATURE, - SENSOR_TYPES) + ATTR_INSIDE_TEMPERATURE, + ATTR_OUTSIDE_TEMPERATURE, + SENSOR_TYPE_TEMPERATURE, + SENSOR_TYPES, +) _LOGGER = logging.getLogger(__name__) -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Old way of setting up the Daikin sensors. Can only be called when a user accidentally mentions the platform in their @@ -29,17 +31,18 @@ async def async_setup_entry(hass, entry, async_add_entities): sensors = [ATTR_INSIDE_TEMPERATURE] if daikin_api.device.support_outside_temperature: sensors.append(ATTR_OUTSIDE_TEMPERATURE) - async_add_entities([ - DaikinClimateSensor(daikin_api, sensor, hass.config.units) - for sensor in sensors - ]) + async_add_entities( + [ + DaikinClimateSensor(daikin_api, sensor, hass.config.units) + for sensor in sensors + ] + ) class DaikinClimateSensor(Entity): """Representation of a Sensor.""" - def __init__(self, api, monitored_state, units: UnitSystem, - name=None) -> None: + def __init__(self, api, monitored_state, units: UnitSystem, name=None) -> None: """Initialize the sensor.""" self._api = api self._sensor = SENSOR_TYPES.get(monitored_state) diff --git a/homeassistant/components/daikin/switch.py b/homeassistant/components/daikin/switch.py index f1a058957fa..6290e6fecef 100644 --- a/homeassistant/components/daikin/switch.py +++ b/homeassistant/components/daikin/switch.py @@ -7,11 +7,10 @@ from . import DOMAIN as DAIKIN_DOMAIN _LOGGER = logging.getLogger(__name__) -ZONE_ICON = 'mdi:home-circle' +ZONE_ICON = "mdi:home-circle" -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Old way of setting up the platform. Can only be called when a user accidentally mentions the platform in their @@ -25,10 +24,13 @@ async def async_setup_entry(hass, entry, async_add_entities): daikin_api = hass.data[DAIKIN_DOMAIN][entry.entry_id] zones = daikin_api.device.zones if zones: - async_add_entities([ - DaikinZoneSwitch(daikin_api, zone_id) - for zone_id, zone in enumerate(zones) if zone != ('-', '0') - ]) + async_add_entities( + [ + DaikinZoneSwitch(daikin_api, zone_id) + for zone_id, zone in enumerate(zones) + if zone != ("-", "0") + ] + ) class DaikinZoneSwitch(ToggleEntity): @@ -52,13 +54,12 @@ class DaikinZoneSwitch(ToggleEntity): @property def name(self): """Return the name of the sensor.""" - return "{} {}".format(self._api.name, - self._api.device.zones[self._zone_id][0]) + return "{} {}".format(self._api.name, self._api.device.zones[self._zone_id][0]) @property def is_on(self): """Return the state of the sensor.""" - return self._api.device.zones[self._zone_id][1] == '1' + return self._api.device.zones[self._zone_id][1] == "1" @property def device_info(self): @@ -71,8 +72,8 @@ class DaikinZoneSwitch(ToggleEntity): async def async_turn_on(self, **kwargs): """Turn the zone on.""" - await self._api.device.set_zone(self._zone_id, '1') + await self._api.device.set_zone(self._zone_id, "1") async def async_turn_off(self, **kwargs): """Turn the zone off.""" - await self._api.device.set_zone(self._zone_id, '0') + await self._api.device.set_zone(self._zone_id, "0") diff --git a/homeassistant/components/danfoss_air/__init__.py b/homeassistant/components/danfoss_air/__init__.py index 6e86b16c02d..adfb13e722c 100644 --- a/homeassistant/components/danfoss_air/__init__.py +++ b/homeassistant/components/danfoss_air/__init__.py @@ -11,16 +11,14 @@ from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -DANFOSS_AIR_PLATFORMS = ['sensor', 'binary_sensor', 'switch'] -DOMAIN = 'danfoss_air' +DANFOSS_AIR_PLATFORMS = ["sensor", "binary_sensor", "switch"] +DOMAIN = "danfoss_air" MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_HOST): cv.string, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + {DOMAIN: vol.Schema({vol.Required(CONF_HOST): cv.string})}, extra=vol.ALLOW_EXTRA +) def setup(hass, config): @@ -59,35 +57,41 @@ class DanfossAir: """Use the data from Danfoss Air API.""" _LOGGER.debug("Fetching data from Danfoss Air CCM module") from pydanfossair.commands import ReadCommand - self._data[ReadCommand.exhaustTemperature] \ - = self._client.command(ReadCommand.exhaustTemperature) - self._data[ReadCommand.outdoorTemperature] \ - = self._client.command(ReadCommand.outdoorTemperature) - self._data[ReadCommand.supplyTemperature] \ - = self._client.command(ReadCommand.supplyTemperature) - self._data[ReadCommand.extractTemperature] \ - = self._client.command(ReadCommand.extractTemperature) - self._data[ReadCommand.humidity] \ - = round(self._client.command(ReadCommand.humidity), 2) - self._data[ReadCommand.filterPercent] \ - = round(self._client.command(ReadCommand.filterPercent), 2) - self._data[ReadCommand.bypass] \ - = self._client.command(ReadCommand.bypass) - self._data[ReadCommand.fan_step] \ - = self._client.command(ReadCommand.fan_step) - self._data[ReadCommand.supply_fan_speed] \ - = self._client.command(ReadCommand.supply_fan_speed) - self._data[ReadCommand.exhaust_fan_speed] \ - = self._client.command(ReadCommand.exhaust_fan_speed) - self._data[ReadCommand.away_mode] \ - = self._client.command(ReadCommand.away_mode) - self._data[ReadCommand.boost] \ - = self._client.command(ReadCommand.boost) - self._data[ReadCommand.battery_percent] \ - = self._client.command(ReadCommand.battery_percent) - self._data[ReadCommand.bypass] \ - = self._client.command(ReadCommand.bypass) - self._data[ReadCommand.automatic_bypass] \ - = self._client.command(ReadCommand.automatic_bypass) + + self._data[ReadCommand.exhaustTemperature] = self._client.command( + ReadCommand.exhaustTemperature + ) + self._data[ReadCommand.outdoorTemperature] = self._client.command( + ReadCommand.outdoorTemperature + ) + self._data[ReadCommand.supplyTemperature] = self._client.command( + ReadCommand.supplyTemperature + ) + self._data[ReadCommand.extractTemperature] = self._client.command( + ReadCommand.extractTemperature + ) + self._data[ReadCommand.humidity] = round( + self._client.command(ReadCommand.humidity), 2 + ) + self._data[ReadCommand.filterPercent] = round( + self._client.command(ReadCommand.filterPercent), 2 + ) + self._data[ReadCommand.bypass] = self._client.command(ReadCommand.bypass) + self._data[ReadCommand.fan_step] = self._client.command(ReadCommand.fan_step) + self._data[ReadCommand.supply_fan_speed] = self._client.command( + ReadCommand.supply_fan_speed + ) + self._data[ReadCommand.exhaust_fan_speed] = self._client.command( + ReadCommand.exhaust_fan_speed + ) + self._data[ReadCommand.away_mode] = self._client.command(ReadCommand.away_mode) + self._data[ReadCommand.boost] = self._client.command(ReadCommand.boost) + self._data[ReadCommand.battery_percent] = self._client.command( + ReadCommand.battery_percent + ) + self._data[ReadCommand.bypass] = self._client.command(ReadCommand.bypass) + self._data[ReadCommand.automatic_bypass] = self._client.command( + ReadCommand.automatic_bypass + ) _LOGGER.debug("Done fetching data from Danfoss Air CCM module") diff --git a/homeassistant/components/danfoss_air/binary_sensor.py b/homeassistant/components/danfoss_air/binary_sensor.py index 723b0d08801..d5aab8b35bb 100644 --- a/homeassistant/components/danfoss_air/binary_sensor.py +++ b/homeassistant/components/danfoss_air/binary_sensor.py @@ -7,6 +7,7 @@ from . import DOMAIN as DANFOSS_AIR_DOMAIN def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the available Danfoss Air sensors etc.""" from pydanfossair.commands import ReadCommand + data = hass.data[DANFOSS_AIR_DOMAIN] sensors = [ @@ -17,8 +18,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): dev = [] for sensor in sensors: - dev.append(DanfossAirBinarySensor( - data, sensor[0], sensor[1], sensor[2])) + dev.append(DanfossAirBinarySensor(data, sensor[0], sensor[1], sensor[2])) add_entities(dev, True) diff --git a/homeassistant/components/danfoss_air/sensor.py b/homeassistant/components/danfoss_air/sensor.py index a5dc2a2eb09..ea0002d0ac3 100644 --- a/homeassistant/components/danfoss_air/sensor.py +++ b/homeassistant/components/danfoss_air/sensor.py @@ -2,8 +2,11 @@ import logging from homeassistant.const import ( - DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, - TEMP_CELSIUS) + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + TEMP_CELSIUS, +) from homeassistant.helpers.entity import Entity from . import DOMAIN as DANFOSS_AIR_DOMAIN @@ -18,33 +21,47 @@ def setup_platform(hass, config, add_entities, discovery_info=None): data = hass.data[DANFOSS_AIR_DOMAIN] sensors = [ - ["Danfoss Air Exhaust Temperature", TEMP_CELSIUS, - ReadCommand.exhaustTemperature, DEVICE_CLASS_TEMPERATURE], - ["Danfoss Air Outdoor Temperature", TEMP_CELSIUS, - ReadCommand.outdoorTemperature, DEVICE_CLASS_TEMPERATURE], - ["Danfoss Air Supply Temperature", TEMP_CELSIUS, - ReadCommand.supplyTemperature, DEVICE_CLASS_TEMPERATURE], - ["Danfoss Air Extract Temperature", TEMP_CELSIUS, - ReadCommand.extractTemperature, DEVICE_CLASS_TEMPERATURE], - ["Danfoss Air Remaining Filter", '%', - ReadCommand.filterPercent, None], - ["Danfoss Air Humidity", '%', - ReadCommand.humidity, DEVICE_CLASS_HUMIDITY], - ["Danfoss Air Fan Step", '%', - ReadCommand.fan_step, None], - ["Dandoss Air Exhaust Fan Speed", 'RPM', - ReadCommand.exhaust_fan_speed, None], - ["Dandoss Air Supply Fan Speed", 'RPM', - ReadCommand.supply_fan_speed, None], - ["Dandoss Air Dial Battery", '%', - ReadCommand.battery_percent, DEVICE_CLASS_BATTERY] - ] + [ + "Danfoss Air Exhaust Temperature", + TEMP_CELSIUS, + ReadCommand.exhaustTemperature, + DEVICE_CLASS_TEMPERATURE, + ], + [ + "Danfoss Air Outdoor Temperature", + TEMP_CELSIUS, + ReadCommand.outdoorTemperature, + DEVICE_CLASS_TEMPERATURE, + ], + [ + "Danfoss Air Supply Temperature", + TEMP_CELSIUS, + ReadCommand.supplyTemperature, + DEVICE_CLASS_TEMPERATURE, + ], + [ + "Danfoss Air Extract Temperature", + TEMP_CELSIUS, + ReadCommand.extractTemperature, + DEVICE_CLASS_TEMPERATURE, + ], + ["Danfoss Air Remaining Filter", "%", ReadCommand.filterPercent, None], + ["Danfoss Air Humidity", "%", ReadCommand.humidity, DEVICE_CLASS_HUMIDITY], + ["Danfoss Air Fan Step", "%", ReadCommand.fan_step, None], + ["Dandoss Air Exhaust Fan Speed", "RPM", ReadCommand.exhaust_fan_speed, None], + ["Dandoss Air Supply Fan Speed", "RPM", ReadCommand.supply_fan_speed, None], + [ + "Dandoss Air Dial Battery", + "%", + ReadCommand.battery_percent, + DEVICE_CLASS_BATTERY, + ], + ] dev = [] for sensor in sensors: - dev.append(DanfossAir( - data, sensor[0], sensor[1], sensor[2], sensor[3])) + dev.append(DanfossAir(data, sensor[0], sensor[1], sensor[2], sensor[3])) add_entities(dev, True) diff --git a/homeassistant/components/danfoss_air/switch.py b/homeassistant/components/danfoss_air/switch.py index 4e7fce28dc7..8a1e0f9c746 100644 --- a/homeassistant/components/danfoss_air/switch.py +++ b/homeassistant/components/danfoss_air/switch.py @@ -15,25 +15,30 @@ def setup_platform(hass, config, add_entities, discovery_info=None): data = hass.data[DANFOSS_AIR_DOMAIN] switches = [ - ["Danfoss Air Boost", - ReadCommand.boost, - UpdateCommand.boost_activate, - UpdateCommand.boost_deactivate], - ["Danfoss Air Bypass", - ReadCommand.bypass, - UpdateCommand.bypass_activate, - UpdateCommand.bypass_deactivate], - ["Danfoss Air Automatic Bypass", - ReadCommand.automatic_bypass, - UpdateCommand.bypass_activate, - UpdateCommand.bypass_deactivate], + [ + "Danfoss Air Boost", + ReadCommand.boost, + UpdateCommand.boost_activate, + UpdateCommand.boost_deactivate, + ], + [ + "Danfoss Air Bypass", + ReadCommand.bypass, + UpdateCommand.bypass_activate, + UpdateCommand.bypass_deactivate, + ], + [ + "Danfoss Air Automatic Bypass", + ReadCommand.automatic_bypass, + UpdateCommand.bypass_activate, + UpdateCommand.bypass_deactivate, + ], ] dev = [] for switch in switches: - dev.append(DanfossAir( - data, switch[0], switch[1], switch[2], switch[3])) + dev.append(DanfossAir(data, switch[0], switch[1], switch[2], switch[3])) add_entities(dev) diff --git a/homeassistant/components/darksky/sensor.py b/homeassistant/components/darksky/sensor.py index 394541378e4..d6bc2db517e 100644 --- a/homeassistant/components/darksky/sensor.py +++ b/homeassistant/components/darksky/sensor.py @@ -3,14 +3,20 @@ import logging from datetime import timedelta import voluptuous as vol -from requests.exceptions import ( - ConnectionError as ConnectError, HTTPError, Timeout) +from requests.exceptions import ConnectionError as ConnectError, HTTPError, Timeout import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - ATTR_ATTRIBUTION, CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, - CONF_MONITORED_CONDITIONS, CONF_NAME, UNIT_UV_INDEX, CONF_SCAN_INTERVAL) + ATTR_ATTRIBUTION, + CONF_API_KEY, + CONF_LATITUDE, + CONF_LONGITUDE, + CONF_MONITORED_CONDITIONS, + CONF_NAME, + UNIT_UV_INDEX, + CONF_SCAN_INTERVAL, +) from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle @@ -18,187 +24,451 @@ _LOGGER = logging.getLogger(__name__) ATTRIBUTION = "Powered by Dark Sky" -CONF_FORECAST = 'forecast' -CONF_HOURLY_FORECAST = 'hourly_forecast' -CONF_LANGUAGE = 'language' -CONF_UNITS = 'units' +CONF_FORECAST = "forecast" +CONF_HOURLY_FORECAST = "hourly_forecast" +CONF_LANGUAGE = "language" +CONF_UNITS = "units" -DEFAULT_LANGUAGE = 'en' -DEFAULT_NAME = 'Dark Sky' +DEFAULT_LANGUAGE = "en" +DEFAULT_NAME = "Dark Sky" SCAN_INTERVAL = timedelta(seconds=300) DEPRECATED_SENSOR_TYPES = { - 'apparent_temperature_max', - 'apparent_temperature_min', - 'temperature_max', - 'temperature_min', + "apparent_temperature_max", + "apparent_temperature_min", + "temperature_max", + "temperature_min", } # Sensor types are defined like so: # Name, si unit, us unit, ca unit, uk unit, uk2 unit SENSOR_TYPES = { - 'summary': ['Summary', None, None, None, None, None, None, - ['currently', 'hourly', 'daily']], - 'minutely_summary': ['Minutely Summary', - None, None, None, None, None, None, []], - 'hourly_summary': ['Hourly Summary', None, None, None, None, None, None, - []], - 'daily_summary': ['Daily Summary', None, None, None, None, None, None, []], - 'icon': ['Icon', None, None, None, None, None, None, - ['currently', 'hourly', 'daily']], - 'nearest_storm_distance': ['Nearest Storm Distance', - 'km', 'mi', 'km', 'km', 'mi', - 'mdi:weather-lightning', ['currently']], - 'nearest_storm_bearing': ['Nearest Storm Bearing', - '°', '°', '°', '°', '°', - 'mdi:weather-lightning', ['currently']], - 'precip_type': ['Precip', None, None, None, None, None, - 'mdi:weather-pouring', - ['currently', 'minutely', 'hourly', 'daily']], - 'precip_intensity': ['Precip Intensity', - 'mm/h', 'in', 'mm/h', 'mm/h', 'mm/h', - 'mdi:weather-rainy', - ['currently', 'minutely', 'hourly', 'daily']], - 'precip_probability': ['Precip Probability', - '%', '%', '%', '%', '%', 'mdi:water-percent', - ['currently', 'minutely', 'hourly', 'daily']], - 'precip_accumulation': ['Precip Accumulation', - 'cm', 'in', 'cm', 'cm', 'cm', 'mdi:weather-snowy', - ['hourly', 'daily']], - 'temperature': ['Temperature', - '°C', '°F', '°C', '°C', '°C', 'mdi:thermometer', - ['currently', 'hourly']], - 'apparent_temperature': ['Apparent Temperature', - '°C', '°F', '°C', '°C', '°C', 'mdi:thermometer', - ['currently', 'hourly']], - 'dew_point': ['Dew Point', '°C', '°F', '°C', '°C', '°C', - 'mdi:thermometer', ['currently', 'hourly', 'daily']], - 'wind_speed': ['Wind Speed', 'm/s', 'mph', 'km/h', 'mph', 'mph', - 'mdi:weather-windy', ['currently', 'hourly', 'daily']], - 'wind_bearing': ['Wind Bearing', '°', '°', '°', '°', '°', 'mdi:compass', - ['currently', 'hourly', 'daily']], - 'wind_gust': ['Wind Gust', 'm/s', 'mph', 'km/h', 'mph', 'mph', - 'mdi:weather-windy-variant', - ['currently', 'hourly', 'daily']], - 'cloud_cover': ['Cloud Coverage', '%', '%', '%', '%', '%', - 'mdi:weather-partlycloudy', - ['currently', 'hourly', 'daily']], - 'humidity': ['Humidity', '%', '%', '%', '%', '%', 'mdi:water-percent', - ['currently', 'hourly', 'daily']], - 'pressure': ['Pressure', 'mbar', 'mbar', 'mbar', 'mbar', 'mbar', - 'mdi:gauge', ['currently', 'hourly', 'daily']], - 'visibility': ['Visibility', 'km', 'mi', 'km', 'km', 'mi', 'mdi:eye', - ['currently', 'hourly', 'daily']], - 'ozone': ['Ozone', 'DU', 'DU', 'DU', 'DU', 'DU', 'mdi:eye', - ['currently', 'hourly', 'daily']], - 'apparent_temperature_max': ['Daily High Apparent Temperature', - '°C', '°F', '°C', '°C', '°C', - 'mdi:thermometer', ['daily']], - 'apparent_temperature_high': ["Daytime High Apparent Temperature", - '°C', '°F', '°C', '°C', '°C', - 'mdi:thermometer', ['daily']], - 'apparent_temperature_min': ['Daily Low Apparent Temperature', - '°C', '°F', '°C', '°C', '°C', - 'mdi:thermometer', ['daily']], - 'apparent_temperature_low': ['Overnight Low Apparent Temperature', - '°C', '°F', '°C', '°C', '°C', - 'mdi:thermometer', ['daily']], - 'temperature_max': ['Daily High Temperature', - '°C', '°F', '°C', '°C', '°C', 'mdi:thermometer', - ['daily']], - 'temperature_high': ['Daytime High Temperature', - '°C', '°F', '°C', '°C', '°C', 'mdi:thermometer', - ['daily']], - 'temperature_min': ['Daily Low Temperature', - '°C', '°F', '°C', '°C', '°C', 'mdi:thermometer', - ['daily']], - 'temperature_low': ['Overnight Low Temperature', - '°C', '°F', '°C', '°C', '°C', 'mdi:thermometer', - ['daily']], - 'precip_intensity_max': ['Daily Max Precip Intensity', - 'mm/h', 'in', 'mm/h', 'mm/h', 'mm/h', - 'mdi:thermometer', ['daily']], - 'uv_index': ['UV Index', - UNIT_UV_INDEX, UNIT_UV_INDEX, UNIT_UV_INDEX, - UNIT_UV_INDEX, UNIT_UV_INDEX, 'mdi:weather-sunny', - ['currently', 'hourly', 'daily']], - 'moon_phase': ['Moon Phase', None, None, None, None, None, - 'mdi:weather-night', ['daily']], - 'sunrise_time': ['Sunrise', None, None, None, None, None, - 'mdi:white-balance-sunny', ['daily']], - 'sunset_time': ['Sunset', None, None, None, None, None, - 'mdi:weather-night', ['daily']], - 'alerts': ['Alerts', None, None, None, None, None, - 'mdi:alert-circle-outline', []] + "summary": [ + "Summary", + None, + None, + None, + None, + None, + None, + ["currently", "hourly", "daily"], + ], + "minutely_summary": ["Minutely Summary", None, None, None, None, None, None, []], + "hourly_summary": ["Hourly Summary", None, None, None, None, None, None, []], + "daily_summary": ["Daily Summary", None, None, None, None, None, None, []], + "icon": [ + "Icon", + None, + None, + None, + None, + None, + None, + ["currently", "hourly", "daily"], + ], + "nearest_storm_distance": [ + "Nearest Storm Distance", + "km", + "mi", + "km", + "km", + "mi", + "mdi:weather-lightning", + ["currently"], + ], + "nearest_storm_bearing": [ + "Nearest Storm Bearing", + "°", + "°", + "°", + "°", + "°", + "mdi:weather-lightning", + ["currently"], + ], + "precip_type": [ + "Precip", + None, + None, + None, + None, + None, + "mdi:weather-pouring", + ["currently", "minutely", "hourly", "daily"], + ], + "precip_intensity": [ + "Precip Intensity", + "mm/h", + "in", + "mm/h", + "mm/h", + "mm/h", + "mdi:weather-rainy", + ["currently", "minutely", "hourly", "daily"], + ], + "precip_probability": [ + "Precip Probability", + "%", + "%", + "%", + "%", + "%", + "mdi:water-percent", + ["currently", "minutely", "hourly", "daily"], + ], + "precip_accumulation": [ + "Precip Accumulation", + "cm", + "in", + "cm", + "cm", + "cm", + "mdi:weather-snowy", + ["hourly", "daily"], + ], + "temperature": [ + "Temperature", + "°C", + "°F", + "°C", + "°C", + "°C", + "mdi:thermometer", + ["currently", "hourly"], + ], + "apparent_temperature": [ + "Apparent Temperature", + "°C", + "°F", + "°C", + "°C", + "°C", + "mdi:thermometer", + ["currently", "hourly"], + ], + "dew_point": [ + "Dew Point", + "°C", + "°F", + "°C", + "°C", + "°C", + "mdi:thermometer", + ["currently", "hourly", "daily"], + ], + "wind_speed": [ + "Wind Speed", + "m/s", + "mph", + "km/h", + "mph", + "mph", + "mdi:weather-windy", + ["currently", "hourly", "daily"], + ], + "wind_bearing": [ + "Wind Bearing", + "°", + "°", + "°", + "°", + "°", + "mdi:compass", + ["currently", "hourly", "daily"], + ], + "wind_gust": [ + "Wind Gust", + "m/s", + "mph", + "km/h", + "mph", + "mph", + "mdi:weather-windy-variant", + ["currently", "hourly", "daily"], + ], + "cloud_cover": [ + "Cloud Coverage", + "%", + "%", + "%", + "%", + "%", + "mdi:weather-partlycloudy", + ["currently", "hourly", "daily"], + ], + "humidity": [ + "Humidity", + "%", + "%", + "%", + "%", + "%", + "mdi:water-percent", + ["currently", "hourly", "daily"], + ], + "pressure": [ + "Pressure", + "mbar", + "mbar", + "mbar", + "mbar", + "mbar", + "mdi:gauge", + ["currently", "hourly", "daily"], + ], + "visibility": [ + "Visibility", + "km", + "mi", + "km", + "km", + "mi", + "mdi:eye", + ["currently", "hourly", "daily"], + ], + "ozone": [ + "Ozone", + "DU", + "DU", + "DU", + "DU", + "DU", + "mdi:eye", + ["currently", "hourly", "daily"], + ], + "apparent_temperature_max": [ + "Daily High Apparent Temperature", + "°C", + "°F", + "°C", + "°C", + "°C", + "mdi:thermometer", + ["daily"], + ], + "apparent_temperature_high": [ + "Daytime High Apparent Temperature", + "°C", + "°F", + "°C", + "°C", + "°C", + "mdi:thermometer", + ["daily"], + ], + "apparent_temperature_min": [ + "Daily Low Apparent Temperature", + "°C", + "°F", + "°C", + "°C", + "°C", + "mdi:thermometer", + ["daily"], + ], + "apparent_temperature_low": [ + "Overnight Low Apparent Temperature", + "°C", + "°F", + "°C", + "°C", + "°C", + "mdi:thermometer", + ["daily"], + ], + "temperature_max": [ + "Daily High Temperature", + "°C", + "°F", + "°C", + "°C", + "°C", + "mdi:thermometer", + ["daily"], + ], + "temperature_high": [ + "Daytime High Temperature", + "°C", + "°F", + "°C", + "°C", + "°C", + "mdi:thermometer", + ["daily"], + ], + "temperature_min": [ + "Daily Low Temperature", + "°C", + "°F", + "°C", + "°C", + "°C", + "mdi:thermometer", + ["daily"], + ], + "temperature_low": [ + "Overnight Low Temperature", + "°C", + "°F", + "°C", + "°C", + "°C", + "mdi:thermometer", + ["daily"], + ], + "precip_intensity_max": [ + "Daily Max Precip Intensity", + "mm/h", + "in", + "mm/h", + "mm/h", + "mm/h", + "mdi:thermometer", + ["daily"], + ], + "uv_index": [ + "UV Index", + UNIT_UV_INDEX, + UNIT_UV_INDEX, + UNIT_UV_INDEX, + UNIT_UV_INDEX, + UNIT_UV_INDEX, + "mdi:weather-sunny", + ["currently", "hourly", "daily"], + ], + "moon_phase": [ + "Moon Phase", + None, + None, + None, + None, + None, + "mdi:weather-night", + ["daily"], + ], + "sunrise_time": [ + "Sunrise", + None, + None, + None, + None, + None, + "mdi:white-balance-sunny", + ["daily"], + ], + "sunset_time": [ + "Sunset", + None, + None, + None, + None, + None, + "mdi:weather-night", + ["daily"], + ], + "alerts": ["Alerts", None, None, None, None, None, "mdi:alert-circle-outline", []], } CONDITION_PICTURES = { - 'clear-day': ['/static/images/darksky/weather-sunny.svg', - 'mdi:weather-sunny'], - 'clear-night': ['/static/images/darksky/weather-night.svg', - 'mdi:weather-sunny'], - 'rain': ['/static/images/darksky/weather-pouring.svg', - 'mdi:weather-pouring'], - 'snow': ['/static/images/darksky/weather-snowy.svg', - 'mdi:weather-snowy'], - 'sleet': ['/static/images/darksky/weather-hail.svg', - 'mdi:weather-snowy-rainy'], - 'wind': ['/static/images/darksky/weather-windy.svg', - 'mdi:weather-windy'], - 'fog': ['/static/images/darksky/weather-fog.svg', - 'mdi:weather-fog'], - 'cloudy': ['/static/images/darksky/weather-cloudy.svg', - 'mdi:weather-cloudy'], - 'partly-cloudy-day': ['/static/images/darksky/weather-partlycloudy.svg', - 'mdi:weather-partlycloudy'], - 'partly-cloudy-night': ['/static/images/darksky/weather-cloudy.svg', - 'mdi:weather-partlycloudy'], + "clear-day": ["/static/images/darksky/weather-sunny.svg", "mdi:weather-sunny"], + "clear-night": ["/static/images/darksky/weather-night.svg", "mdi:weather-sunny"], + "rain": ["/static/images/darksky/weather-pouring.svg", "mdi:weather-pouring"], + "snow": ["/static/images/darksky/weather-snowy.svg", "mdi:weather-snowy"], + "sleet": ["/static/images/darksky/weather-hail.svg", "mdi:weather-snowy-rainy"], + "wind": ["/static/images/darksky/weather-windy.svg", "mdi:weather-windy"], + "fog": ["/static/images/darksky/weather-fog.svg", "mdi:weather-fog"], + "cloudy": ["/static/images/darksky/weather-cloudy.svg", "mdi:weather-cloudy"], + "partly-cloudy-day": [ + "/static/images/darksky/weather-partlycloudy.svg", + "mdi:weather-partlycloudy", + ], + "partly-cloudy-night": [ + "/static/images/darksky/weather-cloudy.svg", + "mdi:weather-partlycloudy", + ], } # Language Supported Codes LANGUAGE_CODES = [ - 'ar', 'az', 'be', 'bg', 'bn', 'bs', 'ca', 'cs', 'da', 'de', 'el', 'en', - 'ja', 'ka', 'kn', 'ko', 'eo', 'es', 'et', 'fi', 'fr', 'he', 'hi', 'hr', - 'hu', 'id', 'is', 'it', 'kw', 'lv', 'ml', 'mr', 'nb', 'nl', 'pa', 'pl', - 'pt', 'ro', 'ru', 'sk', 'sl', 'sr', 'sv', 'ta', 'te', 'tet', 'tr', 'uk', - 'ur', 'x-pig-latin', 'zh', 'zh-tw', + "ar", + "az", + "be", + "bg", + "bn", + "bs", + "ca", + "cs", + "da", + "de", + "el", + "en", + "ja", + "ka", + "kn", + "ko", + "eo", + "es", + "et", + "fi", + "fr", + "he", + "hi", + "hr", + "hu", + "id", + "is", + "it", + "kw", + "lv", + "ml", + "mr", + "nb", + "nl", + "pa", + "pl", + "pt", + "ro", + "ru", + "sk", + "sl", + "sr", + "sv", + "ta", + "te", + "tet", + "tr", + "uk", + "ur", + "x-pig-latin", + "zh", + "zh-tw", ] -ALLOWED_UNITS = ['auto', 'si', 'us', 'ca', 'uk', 'uk2'] +ALLOWED_UNITS = ["auto", "si", "us", "ca", "uk", "uk2"] -ALERTS_ATTRS = [ - 'time', - 'description', - 'expires', - 'severity', - 'uri', - 'regions', - 'title' -] +ALERTS_ATTRS = ["time", "description", "expires", "severity", "uri", "regions", "title"] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_MONITORED_CONDITIONS): - vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), - vol.Required(CONF_API_KEY): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_UNITS): vol.In(ALLOWED_UNITS), - vol.Optional(CONF_LANGUAGE, - default=DEFAULT_LANGUAGE): vol.In(LANGUAGE_CODES), - vol.Inclusive( - CONF_LATITUDE, - 'coordinates', - 'Latitude and longitude must exist together' - ): cv.latitude, - vol.Inclusive( - CONF_LONGITUDE, - 'coordinates', - 'Latitude and longitude must exist together' - ): cv.longitude, - vol.Optional(CONF_FORECAST): - vol.All(cv.ensure_list, [vol.Range(min=0, max=7)]), - vol.Optional(CONF_HOURLY_FORECAST): - vol.All(cv.ensure_list, [vol.Range(min=0, max=48)]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_MONITORED_CONDITIONS): vol.All( + cv.ensure_list, [vol.In(SENSOR_TYPES)] + ), + vol.Required(CONF_API_KEY): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_UNITS): vol.In(ALLOWED_UNITS), + vol.Optional(CONF_LANGUAGE, default=DEFAULT_LANGUAGE): vol.In(LANGUAGE_CODES), + vol.Inclusive( + CONF_LATITUDE, "coordinates", "Latitude and longitude must exist together" + ): cv.latitude, + vol.Inclusive( + CONF_LONGITUDE, "coordinates", "Latitude and longitude must exist together" + ): cv.longitude, + vol.Optional(CONF_FORECAST): vol.All(cv.ensure_list, [vol.Range(min=0, max=7)]), + vol.Optional(CONF_HOURLY_FORECAST): vol.All( + cv.ensure_list, [vol.Range(min=0, max=48)] + ), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -211,13 +481,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if CONF_UNITS in config: units = config[CONF_UNITS] elif hass.config.units.is_metric: - units = 'si' + units = "si" else: - units = 'us' + units = "us" forecast_data = DarkSkyData( - api_key=config.get(CONF_API_KEY, None), latitude=latitude, - longitude=longitude, units=units, language=language, interval=interval) + api_key=config.get(CONF_API_KEY, None), + latitude=latitude, + longitude=longitude, + units=units, + language=language, + interval=interval, + ) forecast_data.update() forecast_data.update_currently() @@ -233,22 +508,26 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for variable in config[CONF_MONITORED_CONDITIONS]: if variable in DEPRECATED_SENSOR_TYPES: _LOGGER.warning("Monitored condition %s is deprecated", variable) - if (not SENSOR_TYPES[variable][7] or - 'currently' in SENSOR_TYPES[variable][7]): - if variable == 'alerts': - sensors.append(DarkSkyAlertSensor( - forecast_data, variable, name)) + if not SENSOR_TYPES[variable][7] or "currently" in SENSOR_TYPES[variable][7]: + if variable == "alerts": + sensors.append(DarkSkyAlertSensor(forecast_data, variable, name)) else: sensors.append(DarkSkySensor(forecast_data, variable, name)) - if forecast is not None and 'daily' in SENSOR_TYPES[variable][7]: + if forecast is not None and "daily" in SENSOR_TYPES[variable][7]: for forecast_day in forecast: - sensors.append(DarkSkySensor( - forecast_data, variable, name, forecast_day=forecast_day)) - if forecast_hour is not None and 'hourly' in SENSOR_TYPES[variable][7]: + sensors.append( + DarkSkySensor( + forecast_data, variable, name, forecast_day=forecast_day + ) + ) + if forecast_hour is not None and "hourly" in SENSOR_TYPES[variable][7]: for forecast_h in forecast_hour: - sensors.append(DarkSkySensor( - forecast_data, variable, name, forecast_hour=forecast_h)) + sensors.append( + DarkSkySensor( + forecast_data, variable, name, forecast_hour=forecast_h + ) + ) add_entities(sensors, True) @@ -256,8 +535,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class DarkSkySensor(Entity): """Implementation of a Dark Sky sensor.""" - def __init__(self, forecast_data, sensor_type, name, - forecast_day=None, forecast_hour=None): + def __init__( + self, forecast_data, sensor_type, name, forecast_day=None, forecast_hour=None + ): """Initialize the sensor.""" self.client_name = name self._name = SENSOR_TYPES[sensor_type][0] @@ -273,13 +553,10 @@ class DarkSkySensor(Entity): def name(self): """Return the name of the sensor.""" if self.forecast_day is not None: - return '{} {} {}d'.format( - self.client_name, self._name, self.forecast_day) + return "{} {} {}d".format(self.client_name, self._name, self.forecast_day) if self.forecast_hour is not None: - return '{} {} {}h'.format( - self.client_name, self._name, self.forecast_hour) - return '{} {}'.format( - self.client_name, self._name) + return "{} {} {}h".format(self.client_name, self._name, self.forecast_hour) + return "{} {}".format(self.client_name, self._name) @property def state(self): @@ -299,7 +576,7 @@ class DarkSkySensor(Entity): @property def entity_picture(self): """Return the entity picture to use in the frontend, if any.""" - if self._icon is None or 'summary' not in self.type: + if self._icon is None or "summary" not in self.type: return None if self._icon in CONDITION_PICTURES: @@ -309,19 +586,15 @@ class DarkSkySensor(Entity): def update_unit_of_measurement(self): """Update units based on unit system.""" - unit_index = { - 'si': 1, - 'us': 2, - 'ca': 3, - 'uk': 4, - 'uk2': 5 - }.get(self.unit_system, 1) + unit_index = {"si": 1, "us": 2, "ca": 3, "uk": 4, "uk2": 5}.get( + self.unit_system, 1 + ) self._unit_of_measurement = SENSOR_TYPES[self.type][unit_index] @property def icon(self): """Icon to use in the frontend, if any.""" - if 'summary' in self.type and self._icon in CONDITION_PICTURES: + if "summary" in self.type and self._icon in CONDITION_PICTURES: return CONDITION_PICTURES[self._icon][1] return SENSOR_TYPES[self.type][6] @@ -329,9 +602,7 @@ class DarkSkySensor(Entity): @property def device_state_attributes(self): """Return the state attributes.""" - return { - ATTR_ATTRIBUTION: ATTRIBUTION, - } + return {ATTR_ATTRIBUTION: ATTRIBUTION} def update(self): """Get the latest data from Dark Sky and updates the states.""" @@ -342,32 +613,32 @@ class DarkSkySensor(Entity): self.forecast_data.update() self.update_unit_of_measurement() - if self.type == 'minutely_summary': + if self.type == "minutely_summary": self.forecast_data.update_minutely() minutely = self.forecast_data.data_minutely - self._state = getattr(minutely, 'summary', '') - self._icon = getattr(minutely, 'icon', '') - elif self.type == 'hourly_summary': + self._state = getattr(minutely, "summary", "") + self._icon = getattr(minutely, "icon", "") + elif self.type == "hourly_summary": self.forecast_data.update_hourly() hourly = self.forecast_data.data_hourly - self._state = getattr(hourly, 'summary', '') - self._icon = getattr(hourly, 'icon', '') + self._state = getattr(hourly, "summary", "") + self._icon = getattr(hourly, "icon", "") elif self.forecast_hour is not None: self.forecast_data.update_hourly() hourly = self.forecast_data.data_hourly - if hasattr(hourly, 'data'): + if hasattr(hourly, "data"): self._state = self.get_state(hourly.data[self.forecast_hour]) else: self._state = 0 - elif self.type == 'daily_summary': + elif self.type == "daily_summary": self.forecast_data.update_daily() daily = self.forecast_data.data_daily - self._state = getattr(daily, 'summary', '') - self._icon = getattr(daily, 'icon', '') + self._state = getattr(daily, "summary", "") + self._icon = getattr(daily, "icon", "") elif self.forecast_day is not None: self.forecast_data.update_daily() daily = self.forecast_data.data_daily - if hasattr(daily, 'data'): + if hasattr(daily, "data"): self._state = self.get_state(daily.data[self.forecast_day]) else: self._state = 0 @@ -388,21 +659,30 @@ class DarkSkySensor(Entity): if state is None: return state - if 'summary' in self.type: - self._icon = getattr(data, 'icon', '') + if "summary" in self.type: + self._icon = getattr(data, "icon", "") # Some state data needs to be rounded to whole values or converted to # percentages - if self.type in ['precip_probability', 'cloud_cover', 'humidity']: + if self.type in ["precip_probability", "cloud_cover", "humidity"]: return round(state * 100, 1) - if self.type in ['dew_point', 'temperature', 'apparent_temperature', - 'temperature_low', 'apparent_temperature_low', - 'temperature_min', 'apparent_temperature_min', - 'temperature_high', 'apparent_temperature_high', - 'temperature_max', 'apparent_temperature_max' - 'precip_accumulation', 'pressure', 'ozone', - 'uvIndex']: + if self.type in [ + "dew_point", + "temperature", + "apparent_temperature", + "temperature_low", + "apparent_temperature_low", + "temperature_min", + "apparent_temperature_min", + "temperature_high", + "apparent_temperature_high", + "temperature_max", + "apparent_temperature_max" "precip_accumulation", + "pressure", + "ozone", + "uvIndex", + ]: return round(state, 1) return state @@ -423,8 +703,7 @@ class DarkSkyAlertSensor(Entity): @property def name(self): """Return the name of the sensor.""" - return '{} {}'.format( - self.client_name, self._name) + return "{} {}".format(self.client_name, self._name) @property def state(self): @@ -469,7 +748,7 @@ class DarkSkyAlertSensor(Entity): for i, alert in enumerate(data): for attr in ALERTS_ATTRS: if multiple_alerts: - dkey = attr + '_' + str(i) + dkey = attr + "_" + str(i) else: dkey = attr alerts[dkey] = getattr(alert, attr) @@ -484,15 +763,14 @@ def convert_to_camel(data): This is not pythonic, but needed for certain situations. """ - components = data.split('_') + components = data.split("_") return components[0] + "".join(x.title() for x in components[1:]) class DarkSkyData: """Get the latest data from Darksky.""" - def __init__( - self, api_key, latitude, longitude, units, language, interval): + def __init__(self, api_key, latitude, longitude, units, language, interval): """Initialize the data object.""" self._api_key = api_key self.latitude = latitude @@ -522,12 +800,16 @@ class DarkSkyData: try: self.data = forecastio.load_forecast( - self._api_key, self.latitude, self.longitude, units=self.units, - lang=self.language) + self._api_key, + self.latitude, + self.longitude, + units=self.units, + lang=self.language, + ) except (ConnectError, HTTPError, Timeout, ValueError) as error: _LOGGER.error("Unable to connect to Dark Sky: %s", error) self.data = None - self.unit_system = self.data and self.data.json['flags']['units'] + self.unit_system = self.data and self.data.json["flags"]["units"] def _update_currently(self): """Update currently data.""" diff --git a/homeassistant/components/darksky/weather.py b/homeassistant/components/darksky/weather.py index 84de690504e..e95381cdf73 100644 --- a/homeassistant/components/darksky/weather.py +++ b/homeassistant/components/darksky/weather.py @@ -2,54 +2,71 @@ from datetime import datetime, timedelta import logging -from requests.exceptions import ( - ConnectionError as ConnectError, HTTPError, Timeout) +from requests.exceptions import ConnectionError as ConnectError, HTTPError, Timeout import voluptuous as vol from homeassistant.components.weather import ( - ATTR_FORECAST_CONDITION, ATTR_FORECAST_PRECIPITATION, ATTR_FORECAST_TEMP, - ATTR_FORECAST_TEMP_LOW, ATTR_FORECAST_TIME, ATTR_FORECAST_WIND_BEARING, - ATTR_FORECAST_WIND_SPEED, PLATFORM_SCHEMA, WeatherEntity) + ATTR_FORECAST_CONDITION, + ATTR_FORECAST_PRECIPITATION, + ATTR_FORECAST_TEMP, + ATTR_FORECAST_TEMP_LOW, + ATTR_FORECAST_TIME, + ATTR_FORECAST_WIND_BEARING, + ATTR_FORECAST_WIND_SPEED, + PLATFORM_SCHEMA, + WeatherEntity, +) from homeassistant.const import ( - CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_MODE, CONF_NAME, - PRESSURE_HPA, PRESSURE_INHG, TEMP_CELSIUS, TEMP_FAHRENHEIT) + CONF_API_KEY, + CONF_LATITUDE, + CONF_LONGITUDE, + CONF_MODE, + CONF_NAME, + PRESSURE_HPA, + PRESSURE_INHG, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, +) import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle from homeassistant.util.pressure import convert as convert_pressure + _LOGGER = logging.getLogger(__name__) ATTRIBUTION = "Powered by Dark Sky" -FORECAST_MODE = ['hourly', 'daily'] +FORECAST_MODE = ["hourly", "daily"] MAP_CONDITION = { - 'clear-day': 'sunny', - 'clear-night': 'clear-night', - 'rain': 'rainy', - 'snow': 'snowy', - 'sleet': 'snowy-rainy', - 'wind': 'windy', - 'fog': 'fog', - 'cloudy': 'cloudy', - 'partly-cloudy-day': 'partlycloudy', - 'partly-cloudy-night': 'partlycloudy', - 'hail': 'hail', - 'thunderstorm': 'lightning', - 'tornado': None, + "clear-day": "sunny", + "clear-night": "clear-night", + "rain": "rainy", + "snow": "snowy", + "sleet": "snowy-rainy", + "wind": "windy", + "fog": "fog", + "cloudy": "cloudy", + "partly-cloudy-day": "partlycloudy", + "partly-cloudy-night": "partlycloudy", + "hail": "hail", + "thunderstorm": "lightning", + "tornado": None, } -CONF_UNITS = 'units' +CONF_UNITS = "units" -DEFAULT_NAME = 'Dark Sky' +DEFAULT_NAME = "Dark Sky" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_API_KEY): cv.string, - vol.Optional(CONF_LATITUDE): cv.latitude, - vol.Optional(CONF_LONGITUDE): cv.longitude, - vol.Optional(CONF_MODE, default='hourly'): vol.In(FORECAST_MODE), - vol.Optional(CONF_UNITS): vol.In(['auto', 'si', 'us', 'ca', 'uk', 'uk2']), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_API_KEY): cv.string, + vol.Optional(CONF_LATITUDE): cv.latitude, + vol.Optional(CONF_LONGITUDE): cv.longitude, + vol.Optional(CONF_MODE, default="hourly"): vol.In(FORECAST_MODE), + vol.Optional(CONF_UNITS): vol.In(["auto", "si", "us", "ca", "uk", "uk2"]), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + } +) MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=3) @@ -63,10 +80,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): units = config.get(CONF_UNITS) if not units: - units = 'ca' if hass.config.units.is_metric else 'us' + units = "ca" if hass.config.units.is_metric else "us" - dark_sky = DarkSkyData( - config.get(CONF_API_KEY), latitude, longitude, units) + dark_sky = DarkSkyData(config.get(CONF_API_KEY), latitude, longitude, units) add_entities([DarkSkyWeather(name, dark_sky, mode)], True) @@ -98,54 +114,52 @@ class DarkSkyWeather(WeatherEntity): @property def temperature(self): """Return the temperature.""" - return self._ds_currently.get('temperature') + return self._ds_currently.get("temperature") @property def temperature_unit(self): """Return the unit of measurement.""" if self._dark_sky.units is None: return None - return TEMP_FAHRENHEIT if 'us' in self._dark_sky.units \ - else TEMP_CELSIUS + return TEMP_FAHRENHEIT if "us" in self._dark_sky.units else TEMP_CELSIUS @property def humidity(self): """Return the humidity.""" - return round(self._ds_currently.get('humidity') * 100.0, 2) + return round(self._ds_currently.get("humidity") * 100.0, 2) @property def wind_speed(self): """Return the wind speed.""" - return self._ds_currently.get('windSpeed') + return self._ds_currently.get("windSpeed") @property def wind_bearing(self): """Return the wind bearing.""" - return self._ds_currently.get('windBearing') + return self._ds_currently.get("windBearing") @property def ozone(self): """Return the ozone level.""" - return self._ds_currently.get('ozone') + return self._ds_currently.get("ozone") @property def pressure(self): """Return the pressure.""" - pressure = self._ds_currently.get('pressure') - if 'us' in self._dark_sky.units: - return round( - convert_pressure(pressure, PRESSURE_HPA, PRESSURE_INHG), 2) + pressure = self._ds_currently.get("pressure") + if "us" in self._dark_sky.units: + return round(convert_pressure(pressure, PRESSURE_HPA, PRESSURE_INHG), 2) return pressure @property def visibility(self): """Return the visibility.""" - return self._ds_currently.get('visibility') + return self._ds_currently.get("visibility") @property def condition(self): """Return the weather condition.""" - return MAP_CONDITION.get(self._ds_currently.get('icon')) + return MAP_CONDITION.get(self._ds_currently.get("icon")) @property def forecast(self): @@ -161,34 +175,37 @@ class DarkSkyWeather(WeatherEntity): data = None - if self._mode == 'daily': - data = [{ - ATTR_FORECAST_TIME: - datetime.fromtimestamp(entry.d.get('time')).isoformat(), - ATTR_FORECAST_TEMP: - entry.d.get('temperatureHigh'), - ATTR_FORECAST_TEMP_LOW: - entry.d.get('temperatureLow'), - ATTR_FORECAST_PRECIPITATION: - calc_precipitation(entry.d.get('precipIntensity'), 24), - ATTR_FORECAST_WIND_SPEED: - entry.d.get('windSpeed'), - ATTR_FORECAST_WIND_BEARING: - entry.d.get('windBearing'), - ATTR_FORECAST_CONDITION: - MAP_CONDITION.get(entry.d.get('icon')) - } for entry in self._ds_daily.data] + if self._mode == "daily": + data = [ + { + ATTR_FORECAST_TIME: datetime.fromtimestamp( + entry.d.get("time") + ).isoformat(), + ATTR_FORECAST_TEMP: entry.d.get("temperatureHigh"), + ATTR_FORECAST_TEMP_LOW: entry.d.get("temperatureLow"), + ATTR_FORECAST_PRECIPITATION: calc_precipitation( + entry.d.get("precipIntensity"), 24 + ), + ATTR_FORECAST_WIND_SPEED: entry.d.get("windSpeed"), + ATTR_FORECAST_WIND_BEARING: entry.d.get("windBearing"), + ATTR_FORECAST_CONDITION: MAP_CONDITION.get(entry.d.get("icon")), + } + for entry in self._ds_daily.data + ] else: - data = [{ - ATTR_FORECAST_TIME: - datetime.fromtimestamp(entry.d.get('time')).isoformat(), - ATTR_FORECAST_TEMP: - entry.d.get('temperature'), - ATTR_FORECAST_PRECIPITATION: - calc_precipitation(entry.d.get('precipIntensity'), 1), - ATTR_FORECAST_CONDITION: - MAP_CONDITION.get(entry.d.get('icon')) - } for entry in self._ds_hourly.data] + data = [ + { + ATTR_FORECAST_TIME: datetime.fromtimestamp( + entry.d.get("time") + ).isoformat(), + ATTR_FORECAST_TEMP: entry.d.get("temperature"), + ATTR_FORECAST_PRECIPITATION: calc_precipitation( + entry.d.get("precipIntensity"), 1 + ), + ATTR_FORECAST_CONDITION: MAP_CONDITION.get(entry.d.get("icon")), + } + for entry in self._ds_hourly.data + ] return data @@ -224,8 +241,8 @@ class DarkSkyData: try: self.data = forecastio.load_forecast( - self._api_key, self.latitude, self.longitude, - units=self.requested_units) + self._api_key, self.latitude, self.longitude, units=self.requested_units + ) self.currently = self.data.currently() self.hourly = self.data.hourly() self.daily = self.data.daily() @@ -238,4 +255,4 @@ class DarkSkyData: """Get the unit system of returned data.""" if self.data is None: return None - return self.data.json.get('flags').get('units') + return self.data.json.get("flags").get("units") diff --git a/homeassistant/components/datadog/__init__.py b/homeassistant/components/datadog/__init__.py index a59d828301c..1ad4ed9aab8 100644 --- a/homeassistant/components/datadog/__init__.py +++ b/homeassistant/components/datadog/__init__.py @@ -4,29 +4,40 @@ import logging import voluptuous as vol from homeassistant.const import ( - CONF_HOST, CONF_PORT, CONF_PREFIX, EVENT_LOGBOOK_ENTRY, - EVENT_STATE_CHANGED, STATE_UNKNOWN) + CONF_HOST, + CONF_PORT, + CONF_PREFIX, + EVENT_LOGBOOK_ENTRY, + EVENT_STATE_CHANGED, + STATE_UNKNOWN, +) from homeassistant.helpers import state as state_helper import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -CONF_RATE = 'rate' -DEFAULT_HOST = 'localhost' +CONF_RATE = "rate" +DEFAULT_HOST = "localhost" DEFAULT_PORT = 8125 -DEFAULT_PREFIX = 'hass' +DEFAULT_PREFIX = "hass" DEFAULT_RATE = 1 -DOMAIN = 'datadog' +DOMAIN = "datadog" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_HOST, default=DEFAULT_HOST): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_PREFIX, default=DEFAULT_PREFIX): cv.string, - vol.Optional(CONF_RATE, default=DEFAULT_RATE): - vol.All(vol.Coerce(int), vol.Range(min=1)), - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_HOST, default=DEFAULT_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_PREFIX, default=DEFAULT_PREFIX): cv.string, + vol.Optional(CONF_RATE, default=DEFAULT_RATE): vol.All( + vol.Coerce(int), vol.Range(min=1) + ), + } + ) + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): @@ -43,28 +54,28 @@ def setup(hass, config): def logbook_entry_listener(event): """Listen for logbook entries and send them as events.""" - name = event.data.get('name') - message = event.data.get('message') + name = event.data.get("name") + message = event.data.get("message") statsd.event( title="Home Assistant", text="%%% \n **{}** {} \n %%%".format(name, message), tags=[ - "entity:{}".format(event.data.get('entity_id')), - "domain:{}".format(event.data.get('domain')) - ] + "entity:{}".format(event.data.get("entity_id")), + "domain:{}".format(event.data.get("domain")), + ], ) - _LOGGER.debug('Sent event %s', event.data.get('entity_id')) + _LOGGER.debug("Sent event %s", event.data.get("entity_id")) def state_changed_listener(event): """Listen for new messages on the bus and sends them to Datadog.""" - state = event.data.get('new_state') + state = event.data.get("new_state") if state is None or state.state == STATE_UNKNOWN: return - if state.attributes.get('hidden') is True: + if state.attributes.get("hidden") is True: return states = dict(state.attributes) @@ -73,23 +84,20 @@ def setup(hass, config): for key, value in states.items(): if isinstance(value, (float, int)): - attribute = "{}.{}".format(metric, key.replace(' ', '_')) - statsd.gauge( - attribute, value, sample_rate=sample_rate, tags=tags) + attribute = "{}.{}".format(metric, key.replace(" ", "_")) + statsd.gauge(attribute, value, sample_rate=sample_rate, tags=tags) - _LOGGER.debug( - "Sent metric %s: %s (tags: %s)", attribute, value, tags) + _LOGGER.debug("Sent metric %s: %s (tags: %s)", attribute, value, tags) try: value = state_helper.state_as_number(state) except ValueError: - _LOGGER.debug( - "Error sending %s: %s (tags: %s)", metric, state.state, tags) + _LOGGER.debug("Error sending %s: %s (tags: %s)", metric, state.state, tags) return statsd.gauge(metric, value, sample_rate=sample_rate, tags=tags) - _LOGGER.debug('Sent metric %s: %s (tags: %s)', metric, value, tags) + _LOGGER.debug("Sent metric %s: %s (tags: %s)", metric, value, tags) hass.bus.listen(EVENT_LOGBOOK_ENTRY, logbook_entry_listener) hass.bus.listen(EVENT_STATE_CHANGED, state_changed_listener) diff --git a/homeassistant/components/ddwrt/device_tracker.py b/homeassistant/components/ddwrt/device_tracker.py index e412e33fa17..4a40561b9e3 100644 --- a/homeassistant/components/ddwrt/device_tracker.py +++ b/homeassistant/components/ddwrt/device_tracker.py @@ -6,29 +6,39 @@ import requests import voluptuous as vol from homeassistant.components.device_tracker import ( - DOMAIN, PLATFORM_SCHEMA, DeviceScanner) + DOMAIN, + PLATFORM_SCHEMA, + DeviceScanner, +) from homeassistant.const import ( - CONF_HOST, CONF_PASSWORD, CONF_SSL, CONF_USERNAME, CONF_VERIFY_SSL) + CONF_HOST, + CONF_PASSWORD, + CONF_SSL, + CONF_USERNAME, + CONF_VERIFY_SSL, +) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -_DDWRT_DATA_REGEX = re.compile(r'\{(\w+)::([^\}]*)\}') -_MAC_REGEX = re.compile(r'(([0-9A-Fa-f]{1,2}\:){5}[0-9A-Fa-f]{1,2})') +_DDWRT_DATA_REGEX = re.compile(r"\{(\w+)::([^\}]*)\}") +_MAC_REGEX = re.compile(r"(([0-9A-Fa-f]{1,2}\:){5}[0-9A-Fa-f]{1,2})") DEFAULT_SSL = False DEFAULT_VERIFY_SSL = True -CONF_WIRELESS_ONLY = 'wireless_only' +CONF_WIRELESS_ONLY = "wireless_only" DEFAULT_WIRELESS_ONLY = True -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean, - vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean, - vol.Optional(CONF_WIRELESS_ONLY, default=DEFAULT_WIRELESS_ONLY): cv.boolean -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean, + vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean, + vol.Optional(CONF_WIRELESS_ONLY, default=DEFAULT_WIRELESS_ONLY): cv.boolean, + } +) def get_scanner(hass, config): @@ -44,7 +54,7 @@ class DdWrtDeviceScanner(DeviceScanner): def __init__(self, config): """Initialize the DD-WRT scanner.""" - self.protocol = 'https' if config[CONF_SSL] else 'http' + self.protocol = "https" if config[CONF_SSL] else "http" self.verify_ssl = config[CONF_VERIFY_SSL] self.host = config[CONF_HOST] self.username = config[CONF_USERNAME] @@ -55,11 +65,10 @@ class DdWrtDeviceScanner(DeviceScanner): self.mac2name = {} # Test the router is accessible - url = '{}://{}/Status_Wireless.live.asp'.format( - self.protocol, self.host) + url = "{}://{}/Status_Wireless.live.asp".format(self.protocol, self.host) data = self.get_ddwrt_data(url) if not data: - raise ConnectionError('Cannot connect to DD-Wrt router') + raise ConnectionError("Cannot connect to DD-Wrt router") def scan_devices(self): """Scan for new devices and return a list with found device IDs.""" @@ -71,22 +80,20 @@ class DdWrtDeviceScanner(DeviceScanner): """Return the name of the given device or None if we don't know.""" # If not initialised and not already scanned and not found. if device not in self.mac2name: - url = '{}://{}/Status_Lan.live.asp'.format( - self.protocol, self.host) + url = "{}://{}/Status_Lan.live.asp".format(self.protocol, self.host) data = self.get_ddwrt_data(url) if not data: return None - dhcp_leases = data.get('dhcp_leases', None) + dhcp_leases = data.get("dhcp_leases", None) if not dhcp_leases: return None # Remove leading and trailing quotes and spaces - cleaned_str = dhcp_leases.replace( - "\"", "").replace("\'", "").replace(" ", "") - elements = cleaned_str.split(',') + cleaned_str = dhcp_leases.replace('"', "").replace("'", "").replace(" ", "") + elements = cleaned_str.split(",") num_clients = int(len(elements) / 5) self.mac2name = {} for idx in range(0, num_clients): @@ -107,9 +114,8 @@ class DdWrtDeviceScanner(DeviceScanner): """ _LOGGER.info("Checking ARP") - endpoint = 'Wireless' if self.wireless_only else 'Lan' - url = '{}://{}/Status_{}.live.asp'.format( - self.protocol, self.host, endpoint) + endpoint = "Wireless" if self.wireless_only else "Lan" + url = "{}://{}/Status_{}.live.asp".format(self.protocol, self.host, endpoint) data = self.get_ddwrt_data(url) if not data: @@ -118,9 +124,9 @@ class DdWrtDeviceScanner(DeviceScanner): self.last_results = [] if self.wireless_only: - active_clients = data.get('active_wireless', None) + active_clients = data.get("active_wireless", None) else: - active_clients = data.get('arp_table', None) + active_clients = data.get("arp_table", None) if not active_clients: return False @@ -130,8 +136,7 @@ class DdWrtDeviceScanner(DeviceScanner): clean_str = active_clients.strip().strip("'") elements = clean_str.split("','") - self.last_results.extend(item for item in elements - if _MAC_REGEX.match(item)) + self.last_results.extend(item for item in elements if _MAC_REGEX.match(item)) return True @@ -139,8 +144,11 @@ class DdWrtDeviceScanner(DeviceScanner): """Retrieve data from DD-WRT and return parsed result.""" try: response = requests.get( - url, auth=(self.username, self.password), - timeout=4, verify=self.verify_ssl) + url, + auth=(self.username, self.password), + timeout=4, + verify=self.verify_ssl, + ) except requests.exceptions.Timeout: _LOGGER.exception("Connection to the router timed out") return @@ -149,12 +157,12 @@ class DdWrtDeviceScanner(DeviceScanner): if response.status_code == 401: # Authentication error _LOGGER.exception( - "Failed to authenticate, check your username and password") + "Failed to authenticate, check your username and password" + ) return _LOGGER.error("Invalid response from DD-WRT: %s", response) def _parse_ddwrt_response(data_str): """Parse the DD-WRT data format.""" - return { - key: val for key, val in _DDWRT_DATA_REGEX.findall(data_str)} + return {key: val for key, val in _DDWRT_DATA_REGEX.findall(data_str)} diff --git a/homeassistant/components/deconz/__init__.py b/homeassistant/components/deconz/__init__.py index 71e03da70b7..68974d12253 100644 --- a/homeassistant/components/deconz/__init__.py +++ b/homeassistant/components/deconz/__init__.py @@ -3,42 +3,60 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.const import ( - CONF_API_KEY, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP) + CONF_API_KEY, + CONF_HOST, + CONF_PORT, + EVENT_HOMEASSISTANT_STOP, +) from homeassistant.helpers import config_validation as cv # Loading the config flow file will register the flow from .config_flow import get_master_gateway from .const import ( - CONF_ALLOW_CLIP_SENSOR, CONF_ALLOW_DECONZ_GROUPS, CONF_BRIDGEID, - CONF_MASTER_GATEWAY, DEFAULT_PORT, DOMAIN, _LOGGER) + CONF_ALLOW_CLIP_SENSOR, + CONF_ALLOW_DECONZ_GROUPS, + CONF_BRIDGEID, + CONF_MASTER_GATEWAY, + DEFAULT_PORT, + DOMAIN, + _LOGGER, +) from .gateway import DeconzGateway -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Optional(CONF_API_KEY): cv.string, - vol.Optional(CONF_HOST): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Optional(CONF_API_KEY): cv.string, + vol.Optional(CONF_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) -SERVICE_DECONZ = 'configure' +SERVICE_DECONZ = "configure" -SERVICE_FIELD = 'field' -SERVICE_ENTITY = 'entity' -SERVICE_DATA = 'data' +SERVICE_FIELD = "field" +SERVICE_ENTITY = "entity" +SERVICE_DATA = "data" -SERVICE_SCHEMA = vol.All(vol.Schema({ - vol.Optional(SERVICE_ENTITY): cv.entity_id, - vol.Optional(SERVICE_FIELD): cv.matches_regex('/.*'), - vol.Required(SERVICE_DATA): dict, - vol.Optional(CONF_BRIDGEID): str -}), cv.has_at_least_one_key(SERVICE_ENTITY, SERVICE_FIELD)) +SERVICE_SCHEMA = vol.All( + vol.Schema( + { + vol.Optional(SERVICE_ENTITY): cv.entity_id, + vol.Optional(SERVICE_FIELD): cv.matches_regex("/.*"), + vol.Required(SERVICE_DATA): dict, + vol.Optional(CONF_BRIDGEID): str, + } + ), + cv.has_at_least_one_key(SERVICE_ENTITY, SERVICE_FIELD), +) -SERVICE_DEVICE_REFRESH = 'device_refresh' +SERVICE_DEVICE_REFRESH = "device_refresh" -SERVICE_DEVICE_REFRESCH_SCHEMA = vol.All(vol.Schema({ - vol.Optional(CONF_BRIDGEID): str -})) +SERVICE_DEVICE_REFRESCH_SCHEMA = vol.All(vol.Schema({vol.Optional(CONF_BRIDGEID): str})) async def async_setup(hass, config): @@ -48,10 +66,13 @@ async def async_setup(hass, config): """ if not hass.config_entries.async_entries(DOMAIN) and DOMAIN in config: deconz_config = config[DOMAIN] - hass.async_create_task(hass.config_entries.flow.async_init( - DOMAIN, context={'source': config_entries.SOURCE_IMPORT}, - data=deconz_config - )) + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=deconz_config, + ) + ) return True @@ -92,7 +113,7 @@ async def async_setup_entry(hass, config_entry): See Dresden Elektroniks REST API documentation for details: http://dresden-elektronik.github.io/deconz-rest-doc/rest/ """ - field = call.data.get(SERVICE_FIELD, '') + field = call.data.get(SERVICE_FIELD, "") entity_id = call.data.get(SERVICE_ENTITY) data = call.data[SERVICE_DATA] @@ -104,13 +125,14 @@ async def async_setup_entry(hass, config_entry): try: field = gateway.deconz_ids[entity_id] + field except KeyError: - _LOGGER.error('Could not find the entity %s', entity_id) + _LOGGER.error("Could not find the entity %s", entity_id) return await gateway.api.async_put_state(field, data) hass.services.async_register( - DOMAIN, SERVICE_DECONZ, async_configure, schema=SERVICE_SCHEMA) + DOMAIN, SERVICE_DECONZ, async_configure, schema=SERVICE_SCHEMA + ) async def async_refresh_devices(call): """Refresh available devices from deCONZ.""" @@ -126,32 +148,47 @@ async def async_setup_entry(hass, config_entry): await gateway.api.async_load_parameters() gateway.async_add_device_callback( - 'group', [group - for group_id, group in gateway.api.groups.items() - if group_id not in groups] + "group", + [ + group + for group_id, group in gateway.api.groups.items() + if group_id not in groups + ], ) gateway.async_add_device_callback( - 'light', [light - for light_id, light in gateway.api.lights.items() - if light_id not in lights] + "light", + [ + light + for light_id, light in gateway.api.lights.items() + if light_id not in lights + ], ) gateway.async_add_device_callback( - 'scene', [scene - for scene_id, scene in gateway.api.scenes.items() - if scene_id not in scenes] + "scene", + [ + scene + for scene_id, scene in gateway.api.scenes.items() + if scene_id not in scenes + ], ) gateway.async_add_device_callback( - 'sensor', [sensor - for sensor_id, sensor in gateway.api.sensors.items() - if sensor_id not in sensors] + "sensor", + [ + sensor + for sensor_id, sensor in gateway.api.sensors.items() + if sensor_id not in sensors + ], ) hass.services.async_register( - DOMAIN, SERVICE_DEVICE_REFRESH, async_refresh_devices, - schema=SERVICE_DEVICE_REFRESCH_SCHEMA) + DOMAIN, + SERVICE_DEVICE_REFRESH, + async_refresh_devices, + schema=SERVICE_DEVICE_REFRESCH_SCHEMA, + ) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, gateway.shutdown) return True @@ -183,10 +220,8 @@ async def async_populate_options(hass, config_entry): options = { CONF_MASTER_GATEWAY: master, - CONF_ALLOW_CLIP_SENSOR: config_entry.data.get( - CONF_ALLOW_CLIP_SENSOR, False), - CONF_ALLOW_DECONZ_GROUPS: config_entry.data.get( - CONF_ALLOW_DECONZ_GROUPS, True) + CONF_ALLOW_CLIP_SENSOR: config_entry.data.get(CONF_ALLOW_CLIP_SENSOR, False), + CONF_ALLOW_DECONZ_GROUPS: config_entry.data.get(CONF_ALLOW_DECONZ_GROUPS, True), } hass.config_entries.async_update_entry(config_entry, options=options) diff --git a/homeassistant/components/deconz/binary_sensor.py b/homeassistant/components/deconz/binary_sensor.py index 6fe8b4324b3..0b5d3173812 100644 --- a/homeassistant/components/deconz/binary_sensor.py +++ b/homeassistant/components/deconz/binary_sensor.py @@ -10,13 +10,12 @@ from .const import ATTR_DARK, ATTR_ON, NEW_SENSOR from .deconz_device import DeconzDevice from .gateway import get_gateway_from_config_entry -ATTR_ORIENTATION = 'orientation' -ATTR_TILTANGLE = 'tiltangle' -ATTR_VIBRATIONSTRENGTH = 'vibrationstrength' +ATTR_ORIENTATION = "orientation" +ATTR_TILTANGLE = "tiltangle" +ATTR_VIBRATIONSTRENGTH = "vibrationstrength" -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Old way of setting up deCONZ platforms.""" pass @@ -32,16 +31,19 @@ async def async_setup_entry(hass, config_entry, async_add_entities): for sensor in sensors: - if sensor.BINARY and \ - not (not gateway.allow_clip_sensor and - sensor.type.startswith('CLIP')): + if sensor.BINARY and not ( + not gateway.allow_clip_sensor and sensor.type.startswith("CLIP") + ): entities.append(DeconzBinarySensor(sensor, gateway)) async_add_entities(entities, True) - gateway.listeners.append(async_dispatcher_connect( - hass, gateway.async_event_new_device(NEW_SENSOR), async_add_sensor)) + gateway.listeners.append( + async_dispatcher_connect( + hass, gateway.async_event_new_device(NEW_SENSOR), async_add_sensor + ) + ) async_add_sensor(gateway.api.sensors.values()) @@ -53,7 +55,7 @@ class DeconzBinarySensor(DeconzDevice, BinarySensorDevice): def async_update_callback(self, force_update=False): """Update the sensor's state.""" changed = set(self._device.changed_keys) - keys = {'battery', 'on', 'reachable', 'state'} + keys = {"battery", "on", "reachable", "state"} if force_update or any(key in changed for key in keys): self.async_schedule_update_ha_state() @@ -85,8 +87,7 @@ class DeconzBinarySensor(DeconzDevice, BinarySensorDevice): if self._device.secondary_temperature is not None: attr[ATTR_TEMPERATURE] = self._device.secondary_temperature - if self._device.type in Presence.ZHATYPE and \ - self._device.dark is not None: + if self._device.type in Presence.ZHATYPE and self._device.dark is not None: attr[ATTR_DARK] = self._device.dark elif self._device.type in Vibration.ZHATYPE: diff --git a/homeassistant/components/deconz/climate.py b/homeassistant/components/deconz/climate.py index 2f21b68ea09..d20833d5a82 100644 --- a/homeassistant/components/deconz/climate.py +++ b/homeassistant/components/deconz/climate.py @@ -3,9 +3,11 @@ from pydeconz.sensor import Thermostat from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( - HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_TARGET_TEMPERATURE) -from homeassistant.const import ( - ATTR_BATTERY_LEVEL, ATTR_TEMPERATURE, TEMP_CELSIUS) + HVAC_MODE_HEAT, + HVAC_MODE_OFF, + SUPPORT_TARGET_TEMPERATURE, +) +from homeassistant.const import ATTR_BATTERY_LEVEL, ATTR_TEMPERATURE, TEMP_CELSIUS from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -16,8 +18,7 @@ from .gateway import get_gateway_from_config_entry SUPPORT_HVAC = [HVAC_MODE_HEAT, HVAC_MODE_OFF] -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Old way of setting up deCONZ platforms.""" pass @@ -36,16 +37,19 @@ async def async_setup_entry(hass, config_entry, async_add_entities): for sensor in sensors: - if sensor.type in Thermostat.ZHATYPE and \ - not (not gateway.allow_clip_sensor and - sensor.type.startswith('CLIP')): + if sensor.type in Thermostat.ZHATYPE and not ( + not gateway.allow_clip_sensor and sensor.type.startswith("CLIP") + ): entities.append(DeconzThermostat(sensor, gateway)) async_add_entities(entities, True) - gateway.listeners.append(async_dispatcher_connect( - hass, gateway.async_event_new_device(NEW_SENSOR), async_add_climate)) + gateway.listeners.append( + async_dispatcher_connect( + hass, gateway.async_event_new_device(NEW_SENSOR), async_add_climate + ) + ) async_add_climate(gateway.api.sensors.values()) @@ -91,16 +95,16 @@ class DeconzThermostat(DeconzDevice, ClimateDevice): data = {} if ATTR_TEMPERATURE in kwargs: - data['heatsetpoint'] = kwargs[ATTR_TEMPERATURE] * 100 + data["heatsetpoint"] = kwargs[ATTR_TEMPERATURE] * 100 await self._device.async_set_config(data) async def async_set_hvac_mode(self, hvac_mode): """Set new target hvac mode.""" if hvac_mode == HVAC_MODE_HEAT: - data = {'mode': 'auto'} + data = {"mode": "auto"} elif hvac_mode == HVAC_MODE_OFF: - data = {'mode': 'off'} + data = {"mode": "off"} await self._device.async_set_config(data) diff --git a/homeassistant/components/deconz/config_flow.py b/homeassistant/components/deconz/config_flow.py index 1e5eabd0a99..f4b8d3ebe02 100644 --- a/homeassistant/components/deconz/config_flow.py +++ b/homeassistant/components/deconz/config_flow.py @@ -5,8 +5,7 @@ import async_timeout import voluptuous as vol from pydeconz.errors import ResponseError, RequestError -from pydeconz.utils import ( - async_discovery, async_get_api_key, async_get_bridgeid) +from pydeconz.utils import async_discovery, async_get_api_key, async_get_bridgeid from homeassistant import config_entries from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT @@ -15,16 +14,18 @@ from homeassistant.helpers import aiohttp_client from .const import CONF_BRIDGEID, DEFAULT_PORT, DOMAIN -DECONZ_MANUFACTURERURL = 'http://www.dresden-elektronik.de' -CONF_SERIAL = 'serial' -ATTR_UUID = 'udn' +DECONZ_MANUFACTURERURL = "http://www.dresden-elektronik.de" +CONF_SERIAL = "serial" +ATTR_UUID = "udn" @callback def configured_gateways(hass): """Return a set of all configured gateways.""" - return {entry.data[CONF_BRIDGEID]: entry for entry - in hass.config_entries.async_entries(DOMAIN)} + return { + entry.data[CONF_BRIDGEID]: entry + for entry in hass.config_entries.async_entries(DOMAIN) + } @callback @@ -89,18 +90,18 @@ class DeconzFlowHandler(config_entries.ConfigFlow): hosts.append(bridge[CONF_HOST]) return self.async_show_form( - step_id='init', - data_schema=vol.Schema({ - vol.Required(CONF_HOST): vol.In(hosts) - }) + step_id="init", + data_schema=vol.Schema({vol.Required(CONF_HOST): vol.In(hosts)}), ) return self.async_show_form( - step_id='init', - data_schema=vol.Schema({ - vol.Required(CONF_HOST): str, - vol.Required(CONF_PORT, default=DEFAULT_PORT): int, - }), + step_id="init", + data_schema=vol.Schema( + { + vol.Required(CONF_HOST): str, + vol.Required(CONF_PORT, default=DEFAULT_PORT): int, + } + ), ) async def async_step_link(self, user_input=None): @@ -112,20 +113,16 @@ class DeconzFlowHandler(config_entries.ConfigFlow): try: with async_timeout.timeout(10): - api_key = await async_get_api_key( - session, **self.deconz_config) + api_key = await async_get_api_key(session, **self.deconz_config) except (ResponseError, RequestError, asyncio.TimeoutError): - errors['base'] = 'no_key' + errors["base"] = "no_key" else: self.deconz_config[CONF_API_KEY] = api_key return await self._create_entry() - return self.async_show_form( - step_id='link', - errors=errors, - ) + return self.async_show_form(step_id="link", errors=errors) async def _create_entry(self): """Create entry for gateway.""" @@ -134,16 +131,15 @@ class DeconzFlowHandler(config_entries.ConfigFlow): try: with async_timeout.timeout(10): - self.deconz_config[CONF_BRIDGEID] = \ - await async_get_bridgeid( - session, **self.deconz_config) + self.deconz_config[CONF_BRIDGEID] = await async_get_bridgeid( + session, **self.deconz_config + ) except asyncio.TimeoutError: - return self.async_abort(reason='no_bridges') + return self.async_abort(reason="no_bridges") return self.async_create_entry( - title='deCONZ-' + self.deconz_config[CONF_BRIDGEID], - data=self.deconz_config + title="deCONZ-" + self.deconz_config[CONF_BRIDGEID], data=self.deconz_config ) async def _update_entry(self, entry, host): @@ -153,13 +149,12 @@ class DeconzFlowHandler(config_entries.ConfigFlow): async def async_step_ssdp(self, discovery_info): """Handle a discovered deCONZ bridge.""" - from homeassistant.components.ssdp import ( - ATTR_MANUFACTURERURL, ATTR_SERIAL) + from homeassistant.components.ssdp import ATTR_MANUFACTURERURL, ATTR_SERIAL if discovery_info[ATTR_MANUFACTURERURL] != DECONZ_MANUFACTURERURL: - return self.async_abort(reason='not_deconz_bridge') + return self.async_abort(reason="not_deconz_bridge") - uuid = discovery_info[ATTR_UUID].replace('uuid:', '') + uuid = discovery_info[ATTR_UUID].replace("uuid:", "") gateways = { gateway.api.config.uuid: gateway for gateway in self.hass.data.get(DOMAIN, {}).values() @@ -168,12 +163,14 @@ class DeconzFlowHandler(config_entries.ConfigFlow): if uuid in gateways: entry = gateways[uuid].config_entry await self._update_entry(entry, discovery_info[CONF_HOST]) - return self.async_abort(reason='updated_instance') + return self.async_abort(reason="updated_instance") bridgeid = discovery_info[ATTR_SERIAL] - if any(bridgeid == flow['context'][CONF_BRIDGEID] - for flow in self._async_in_progress()): - return self.async_abort(reason='already_in_progress') + if any( + bridgeid == flow["context"][CONF_BRIDGEID] + for flow in self._async_in_progress() + ): + return self.async_abort(reason="already_in_progress") # pylint: disable=unsupported-assignment-operation self.context[CONF_BRIDGEID] = bridgeid @@ -215,7 +212,7 @@ class DeconzFlowHandler(config_entries.ConfigFlow): if bridgeid in gateway_entries: entry = gateway_entries[bridgeid] await self._update_entry(entry, user_input[CONF_HOST]) - return self.async_abort(reason='updated_instance') + return self.async_abort(reason="updated_instance") self._hassio_discovery = user_input @@ -228,14 +225,12 @@ class DeconzFlowHandler(config_entries.ConfigFlow): CONF_HOST: self._hassio_discovery[CONF_HOST], CONF_PORT: self._hassio_discovery[CONF_PORT], CONF_BRIDGEID: self._hassio_discovery[CONF_SERIAL], - CONF_API_KEY: self._hassio_discovery[CONF_API_KEY] + CONF_API_KEY: self._hassio_discovery[CONF_API_KEY], } return await self._create_entry() return self.async_show_form( - step_id='hassio_confirm', - description_placeholders={ - 'addon': self._hassio_discovery['addon'] - } + step_id="hassio_confirm", + description_placeholders={"addon": self._hassio_discovery["addon"]}, ) diff --git a/homeassistant/components/deconz/const.py b/homeassistant/components/deconz/const.py index bf0f5884073..ba6172120ec 100644 --- a/homeassistant/components/deconz/const.py +++ b/homeassistant/components/deconz/const.py @@ -1,38 +1,45 @@ """Constants for the deCONZ component.""" import logging -_LOGGER = logging.getLogger('.') +_LOGGER = logging.getLogger(".") -DOMAIN = 'deconz' +DOMAIN = "deconz" DEFAULT_PORT = 80 DEFAULT_ALLOW_CLIP_SENSOR = False DEFAULT_ALLOW_DECONZ_GROUPS = False -CONF_ALLOW_CLIP_SENSOR = 'allow_clip_sensor' -CONF_ALLOW_DECONZ_GROUPS = 'allow_deconz_groups' -CONF_BRIDGEID = 'bridgeid' -CONF_MASTER_GATEWAY = 'master' +CONF_ALLOW_CLIP_SENSOR = "allow_clip_sensor" +CONF_ALLOW_DECONZ_GROUPS = "allow_deconz_groups" +CONF_BRIDGEID = "bridgeid" +CONF_MASTER_GATEWAY = "master" -SUPPORTED_PLATFORMS = ['binary_sensor', 'climate', 'cover', - 'light', 'scene', 'sensor', 'switch'] +SUPPORTED_PLATFORMS = [ + "binary_sensor", + "climate", + "cover", + "light", + "scene", + "sensor", + "switch", +] -NEW_GROUP = 'group' -NEW_LIGHT = 'light' -NEW_SCENE = 'scene' -NEW_SENSOR = 'sensor' +NEW_GROUP = "group" +NEW_LIGHT = "light" +NEW_SCENE = "scene" +NEW_SENSOR = "sensor" NEW_DEVICE = { - NEW_GROUP: 'deconz_new_group_{}', - NEW_LIGHT: 'deconz_new_light_{}', - NEW_SCENE: 'deconz_new_scene_{}', - NEW_SENSOR: 'deconz_new_sensor_{}' + NEW_GROUP: "deconz_new_group_{}", + NEW_LIGHT: "deconz_new_light_{}", + NEW_SCENE: "deconz_new_scene_{}", + NEW_SENSOR: "deconz_new_sensor_{}", } -ATTR_DARK = 'dark' -ATTR_OFFSET = 'offset' -ATTR_ON = 'on' -ATTR_VALVE = 'valve' +ATTR_DARK = "dark" +ATTR_OFFSET = "offset" +ATTR_ON = "on" +ATTR_VALVE = "valve" DAMPERS = ["Level controllable output"] WINDOW_COVERS = ["Window covering device"] diff --git a/homeassistant/components/deconz/cover.py b/homeassistant/components/deconz/cover.py index a89e7fdd595..caa46e10f99 100644 --- a/homeassistant/components/deconz/cover.py +++ b/homeassistant/components/deconz/cover.py @@ -1,7 +1,12 @@ """Support for deCONZ covers.""" from homeassistant.components.cover import ( - ATTR_POSITION, CoverDevice, SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_STOP, - SUPPORT_SET_POSITION) + ATTR_POSITION, + CoverDevice, + SUPPORT_CLOSE, + SUPPORT_OPEN, + SUPPORT_STOP, + SUPPORT_SET_POSITION, +) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -9,11 +14,10 @@ from .const import COVER_TYPES, DAMPERS, NEW_LIGHT, WINDOW_COVERS from .deconz_device import DeconzDevice from .gateway import get_gateway_from_config_entry -ZIGBEE_SPEC = ['lumi.curtain'] +ZIGBEE_SPEC = ["lumi.curtain"] -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Old way of setting up deCONZ platforms.""" pass @@ -41,8 +45,11 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) - gateway.listeners.append(async_dispatcher_connect( - hass, gateway.async_event_new_device(NEW_LIGHT), async_add_cover)) + gateway.listeners.append( + async_dispatcher_connect( + hass, gateway.async_event_new_device(NEW_LIGHT), async_add_cover + ) + ) async_add_cover(gateway.api.lights.values()) @@ -75,9 +82,9 @@ class DeconzCover(DeconzDevice, CoverDevice): def device_class(self): """Return the class of the cover.""" if self._device.type in DAMPERS: - return 'damper' + return "damper" if self._device.type in WINDOW_COVERS: - return 'window' + return "window" @property def supported_features(self): @@ -87,11 +94,11 @@ class DeconzCover(DeconzDevice, CoverDevice): async def async_set_cover_position(self, **kwargs): """Move the cover to a specific position.""" position = kwargs[ATTR_POSITION] - data = {'on': False} + data = {"on": False} if position > 0: - data['on'] = True - data['bri'] = int(position / 100 * 255) + data["on"] = True + data["bri"] = int(position / 100 * 255) await self._device.async_set_state(data) @@ -107,7 +114,7 @@ class DeconzCover(DeconzDevice, CoverDevice): async def async_stop_cover(self, **kwargs): """Stop cover.""" - data = {'bri_inc': 0} + data = {"bri_inc": 0} await self._device.async_set_state(data) @@ -127,10 +134,10 @@ class DeconzCoverZigbeeSpec(DeconzCover): async def async_set_cover_position(self, **kwargs): """Move the cover to a specific position.""" position = kwargs[ATTR_POSITION] - data = {'on': False} + data = {"on": False} if position < 100: - data['on'] = True - data['bri'] = 255 - int(position / 100 * 255) + data["on"] = True + data["bri"] = 255 - int(position / 100 * 255) await self._device.async_set_state(data) diff --git a/homeassistant/components/deconz/deconz_device.py b/homeassistant/components/deconz/deconz_device.py index 8745cb2141a..389ed11e437 100644 --- a/homeassistant/components/deconz/deconz_device.py +++ b/homeassistant/components/deconz/deconz_device.py @@ -21,8 +21,8 @@ class DeconzDevice(Entity): self._device.register_async_callback(self.async_update_callback) self.gateway.deconz_ids[self.entity_id] = self._device.deconz_id self.unsub_dispatcher = async_dispatcher_connect( - self.hass, self.gateway.event_reachable, - self.async_update_callback) + self.hass, self.gateway.event_reachable, self.async_update_callback + ) async def async_will_remove_from_hass(self) -> None: """Disconnect device object when removed.""" @@ -58,19 +58,18 @@ class DeconzDevice(Entity): @property def device_info(self): """Return a device description for device registry.""" - if (self._device.uniqueid is None or - self._device.uniqueid.count(':') != 7): + if self._device.uniqueid is None or self._device.uniqueid.count(":") != 7: return None - serial = self._device.uniqueid.split('-', 1)[0] + serial = self._device.uniqueid.split("-", 1)[0] bridgeid = self.gateway.api.config.bridgeid return { - 'connections': {(CONNECTION_ZIGBEE, serial)}, - 'identifiers': {(DECONZ_DOMAIN, serial)}, - 'manufacturer': self._device.manufacturer, - 'model': self._device.modelid, - 'name': self._device.name, - 'sw_version': self._device.swversion, - 'via_device': (DECONZ_DOMAIN, bridgeid), + "connections": {(CONNECTION_ZIGBEE, serial)}, + "identifiers": {(DECONZ_DOMAIN, serial)}, + "manufacturer": self._device.manufacturer, + "model": self._device.modelid, + "name": self._device.name, + "sw_version": self._device.swversion, + "via_device": (DECONZ_DOMAIN, bridgeid), } diff --git a/homeassistant/components/deconz/gateway.py b/homeassistant/components/deconz/gateway.py index f5d398fcd2f..8eca227f0cd 100644 --- a/homeassistant/components/deconz/gateway.py +++ b/homeassistant/components/deconz/gateway.py @@ -11,12 +11,22 @@ from homeassistant.core import EventOrigin, callback from homeassistant.helpers import aiohttp_client from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.dispatcher import ( - async_dispatcher_connect, async_dispatcher_send) + async_dispatcher_connect, + async_dispatcher_send, +) from homeassistant.util import slugify from .const import ( - _LOGGER, CONF_ALLOW_CLIP_SENSOR, CONF_ALLOW_DECONZ_GROUPS, CONF_BRIDGEID, - CONF_MASTER_GATEWAY, DOMAIN, NEW_DEVICE, NEW_SENSOR, SUPPORTED_PLATFORMS) + _LOGGER, + CONF_ALLOW_CLIP_SENSOR, + CONF_ALLOW_DECONZ_GROUPS, + CONF_BRIDGEID, + CONF_MASTER_GATEWAY, + DOMAIN, + NEW_DEVICE, + NEW_SENSOR, + SUPPORTED_PLATFORMS, +) from .errors import AuthenticationRequired, CannotConnect @@ -62,16 +72,15 @@ class DeconzGateway: async def async_update_device_registry(self): """Update device registry.""" - device_registry = await \ - self.hass.helpers.device_registry.async_get_registry() + device_registry = await self.hass.helpers.device_registry.async_get_registry() device_registry.async_get_or_create( config_entry_id=self.config_entry.entry_id, connections={(CONNECTION_NETWORK_MAC, self.api.config.mac)}, identifiers={(DOMAIN, self.api.config.bridgeid)}, - manufacturer='Dresden Elektronik', + manufacturer="Dresden Elektronik", model=self.api.config.modelid, name=self.api.config.name, - sw_version=self.api.config.swversion + sw_version=self.api.config.swversion, ) async def async_setup(self): @@ -80,25 +89,31 @@ class DeconzGateway: try: self.api = await get_gateway( - hass, self.config_entry.data, self.async_add_device_callback, - self.async_connection_status_callback + hass, + self.config_entry.data, + self.async_add_device_callback, + self.async_connection_status_callback, ) except CannotConnect: raise ConfigEntryNotReady except Exception: # pylint: disable=broad-except - _LOGGER.error('Error connecting with deCONZ gateway') + _LOGGER.error("Error connecting with deCONZ gateway") return False for component in SUPPORTED_PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup( - self.config_entry, component)) + self.config_entry, component + ) + ) - self.listeners.append(async_dispatcher_connect( - hass, self.async_event_new_device(NEW_SENSOR), - self.async_add_remote)) + self.listeners.append( + async_dispatcher_connect( + hass, self.async_event_new_device(NEW_SENSOR), self.async_add_remote + ) + ) self.async_add_remote(self.api.sensors.values()) @@ -123,7 +138,7 @@ class DeconzGateway: @property def event_reachable(self): """Gateway specific event to signal a change in connection status.""" - return 'deconz_reachable_{}'.format(self.bridgeid) + return "deconz_reachable_{}".format(self.bridgeid) @callback def async_connection_status_callback(self, available): @@ -142,15 +157,16 @@ class DeconzGateway: if not isinstance(device, list): device = [device] async_dispatcher_send( - self.hass, self.async_event_new_device(device_type), device) + self.hass, self.async_event_new_device(device_type), device + ) @callback def async_add_remote(self, sensors): """Set up remote from deCONZ.""" for sensor in sensors: - if sensor.type in Switch.ZHATYPE and \ - not (not self.allow_clip_sensor and - sensor.type.startswith('CLIP')): + if sensor.type in Switch.ZHATYPE and not ( + not self.allow_clip_sensor and sensor.type.startswith("CLIP") + ): self.events.append(DeconzEvent(self.hass, sensor)) @callback @@ -171,7 +187,8 @@ class DeconzGateway: for component in SUPPORTED_PLATFORMS: await self.hass.config_entries.async_forward_entry_unload( - self.config_entry, component) + self.config_entry, component + ) for unsub_dispatcher in self.listeners: unsub_dispatcher() @@ -185,14 +202,19 @@ class DeconzGateway: return True -async def get_gateway(hass, config, async_add_device_callback, - async_connection_status_callback): +async def get_gateway( + hass, config, async_add_device_callback, async_connection_status_callback +): """Create a gateway object and verify configuration.""" session = aiohttp_client.async_get_clientsession(hass) - deconz = DeconzSession(hass.loop, session, **config, - async_add_device=async_add_device_callback, - connection_status=async_connection_status_callback) + deconz = DeconzSession( + hass.loop, + session, + **config, + async_add_device=async_add_device_callback, + connection_status=async_connection_status_callback, + ) try: with async_timeout.timeout(10): await deconz.async_load_parameters() @@ -203,8 +225,7 @@ async def get_gateway(hass, config, async_add_device_callback, raise AuthenticationRequired except (asyncio.TimeoutError, errors.RequestError): - _LOGGER.error( - "Error connecting to deCONZ gateway at %s", config[CONF_HOST]) + _LOGGER.error("Error connecting to deCONZ gateway at %s", config[CONF_HOST]) raise CannotConnect @@ -220,7 +241,7 @@ class DeconzEvent: self._hass = hass self._device = device self._device.register_async_callback(self.async_update_callback) - self._event = 'deconz_{}'.format(CONF_EVENT) + self._event = "deconz_{}".format(CONF_EVENT) self._id = slugify(self._device.name) _LOGGER.debug("deCONZ event created: %s", self._id) @@ -233,6 +254,6 @@ class DeconzEvent: @callback def async_update_callback(self, force_update=False): """Fire the event if reason is that state is updated.""" - if 'state' in self._device.changed_keys: + if "state" in self._device.changed_keys: data = {CONF_ID: self._id, CONF_EVENT: self._device.state} self._hass.bus.async_fire(self._event, data, EventOrigin.remote) diff --git a/homeassistant/components/deconz/light.py b/homeassistant/components/deconz/light.py index 557957b6a7a..b68aa6f0779 100644 --- a/homeassistant/components/deconz/light.py +++ b/homeassistant/components/deconz/light.py @@ -1,21 +1,38 @@ """Support for deCONZ lights.""" from homeassistant.components.light import ( - ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH, ATTR_HS_COLOR, - ATTR_TRANSITION, EFFECT_COLORLOOP, FLASH_LONG, FLASH_SHORT, - SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, - SUPPORT_FLASH, SUPPORT_TRANSITION, Light) + ATTR_BRIGHTNESS, + ATTR_COLOR_TEMP, + ATTR_EFFECT, + ATTR_FLASH, + ATTR_HS_COLOR, + ATTR_TRANSITION, + EFFECT_COLORLOOP, + FLASH_LONG, + FLASH_SHORT, + SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, + SUPPORT_COLOR_TEMP, + SUPPORT_EFFECT, + SUPPORT_FLASH, + SUPPORT_TRANSITION, + Light, +) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect import homeassistant.util.color as color_util from .const import ( - COVER_TYPES, DOMAIN as DECONZ_DOMAIN, NEW_GROUP, NEW_LIGHT, SWITCH_TYPES) + COVER_TYPES, + DOMAIN as DECONZ_DOMAIN, + NEW_GROUP, + NEW_LIGHT, + SWITCH_TYPES, +) from .deconz_device import DeconzDevice from .gateway import get_gateway_from_config_entry -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Old way of setting up deCONZ platforms.""" pass @@ -35,8 +52,11 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) - gateway.listeners.append(async_dispatcher_connect( - hass, gateway.async_event_new_device(NEW_LIGHT), async_add_light)) + gateway.listeners.append( + async_dispatcher_connect( + hass, gateway.async_event_new_device(NEW_LIGHT), async_add_light + ) + ) @callback def async_add_group(groups): @@ -49,8 +69,11 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) - gateway.listeners.append(async_dispatcher_connect( - hass, gateway.async_event_new_device(NEW_GROUP), async_add_group)) + gateway.listeners.append( + async_dispatcher_connect( + hass, gateway.async_event_new_device(NEW_GROUP), async_add_group + ) + ) async_add_light(gateway.api.lights.values()) async_add_group(gateway.api.groups.values()) @@ -89,7 +112,7 @@ class DeconzLight(DeconzDevice, Light): @property def color_temp(self): """Return the CT color value.""" - if self._device.colormode != 'ct': + if self._device.colormode != "ct": return None return self._device.ct @@ -97,7 +120,7 @@ class DeconzLight(DeconzDevice, Light): @property def hs_color(self): """Return the hs color value.""" - if self._device.colormode in ('xy', 'hs') and self._device.xy: + if self._device.colormode in ("xy", "hs") and self._device.xy: return color_util.color_xy_to_hs(*self._device.xy) return None @@ -113,51 +136,51 @@ class DeconzLight(DeconzDevice, Light): async def async_turn_on(self, **kwargs): """Turn on light.""" - data = {'on': True} + data = {"on": True} if ATTR_COLOR_TEMP in kwargs: - data['ct'] = kwargs[ATTR_COLOR_TEMP] + data["ct"] = kwargs[ATTR_COLOR_TEMP] if ATTR_HS_COLOR in kwargs: - data['xy'] = color_util.color_hs_to_xy(*kwargs[ATTR_HS_COLOR]) + data["xy"] = color_util.color_hs_to_xy(*kwargs[ATTR_HS_COLOR]) if ATTR_BRIGHTNESS in kwargs: - data['bri'] = kwargs[ATTR_BRIGHTNESS] + data["bri"] = kwargs[ATTR_BRIGHTNESS] if ATTR_TRANSITION in kwargs: - data['transitiontime'] = int(kwargs[ATTR_TRANSITION] * 10) + data["transitiontime"] = int(kwargs[ATTR_TRANSITION] * 10) if ATTR_FLASH in kwargs: if kwargs[ATTR_FLASH] == FLASH_SHORT: - data['alert'] = 'select' - del data['on'] + data["alert"] = "select" + del data["on"] elif kwargs[ATTR_FLASH] == FLASH_LONG: - data['alert'] = 'lselect' - del data['on'] + data["alert"] = "lselect" + del data["on"] if ATTR_EFFECT in kwargs: if kwargs[ATTR_EFFECT] == EFFECT_COLORLOOP: - data['effect'] = 'colorloop' + data["effect"] = "colorloop" else: - data['effect'] = 'none' + data["effect"] = "none" await self._device.async_set_state(data) async def async_turn_off(self, **kwargs): """Turn off light.""" - data = {'on': False} + data = {"on": False} if ATTR_TRANSITION in kwargs: - data['bri'] = 0 - data['transitiontime'] = int(kwargs[ATTR_TRANSITION] * 10) + data["bri"] = 0 + data["transitiontime"] = int(kwargs[ATTR_TRANSITION] * 10) if ATTR_FLASH in kwargs: if kwargs[ATTR_FLASH] == FLASH_SHORT: - data['alert'] = 'select' - del data['on'] + data["alert"] = "select" + del data["on"] elif kwargs[ATTR_FLASH] == FLASH_LONG: - data['alert'] = 'lselect' - del data['on'] + data["alert"] = "lselect" + del data["on"] await self._device.async_set_state(data) @@ -165,10 +188,10 @@ class DeconzLight(DeconzDevice, Light): def device_state_attributes(self): """Return the device state attributes.""" attributes = {} - attributes['is_deconz_group'] = self._device.type == 'LightGroup' + attributes["is_deconz_group"] = self._device.type == "LightGroup" - if self._device.type == 'LightGroup': - attributes['all_on'] = self._device.all_on + if self._device.type == "LightGroup": + attributes["all_on"] = self._device.all_on return attributes @@ -180,9 +203,9 @@ class DeconzGroup(DeconzLight): """Set up group and create an unique id.""" super().__init__(device, gateway) - self._unique_id = '{}-{}'.format( - self.gateway.api.config.bridgeid, - self._device.deconz_id) + self._unique_id = "{}-{}".format( + self.gateway.api.config.bridgeid, self._device.deconz_id + ) @property def unique_id(self): @@ -195,9 +218,9 @@ class DeconzGroup(DeconzLight): bridgeid = self.gateway.api.config.bridgeid return { - 'identifiers': {(DECONZ_DOMAIN, self.unique_id)}, - 'manufacturer': 'Dresden Elektronik', - 'model': 'deCONZ group', - 'name': self._device.name, - 'via_device': (DECONZ_DOMAIN, bridgeid), + "identifiers": {(DECONZ_DOMAIN, self.unique_id)}, + "manufacturer": "Dresden Elektronik", + "model": "deCONZ group", + "name": self._device.name, + "via_device": (DECONZ_DOMAIN, bridgeid), } diff --git a/homeassistant/components/deconz/scene.py b/homeassistant/components/deconz/scene.py index c8cfa9674c5..ede60e3ef45 100644 --- a/homeassistant/components/deconz/scene.py +++ b/homeassistant/components/deconz/scene.py @@ -7,8 +7,7 @@ from .const import NEW_SCENE from .gateway import get_gateway_from_config_entry -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Old way of setting up deCONZ platforms.""" pass @@ -27,8 +26,11 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) - gateway.listeners.append(async_dispatcher_connect( - hass, gateway.async_event_new_device(NEW_SCENE), async_add_scene)) + gateway.listeners.append( + async_dispatcher_connect( + hass, gateway.async_event_new_device(NEW_SCENE), async_add_scene + ) + ) async_add_scene(gateway.api.scenes.values()) diff --git a/homeassistant/components/deconz/sensor.py b/homeassistant/components/deconz/sensor.py index a46a4bbf55c..dad3c25cc38 100644 --- a/homeassistant/components/deconz/sensor.py +++ b/homeassistant/components/deconz/sensor.py @@ -2,7 +2,11 @@ from pydeconz.sensor import Consumption, Daylight, LightLevel, Power, Switch from homeassistant.const import ( - ATTR_BATTERY_LEVEL, ATTR_TEMPERATURE, ATTR_VOLTAGE, DEVICE_CLASS_BATTERY) + ATTR_BATTERY_LEVEL, + ATTR_TEMPERATURE, + ATTR_VOLTAGE, + DEVICE_CLASS_BATTERY, +) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.util import slugify @@ -11,14 +15,13 @@ from .const import ATTR_DARK, ATTR_ON, NEW_SENSOR from .deconz_device import DeconzDevice from .gateway import get_gateway_from_config_entry -ATTR_CURRENT = 'current' -ATTR_POWER = 'power' -ATTR_DAYLIGHT = 'daylight' -ATTR_EVENT_ID = 'event_id' +ATTR_CURRENT = "current" +ATTR_POWER = "power" +ATTR_DAYLIGHT = "daylight" +ATTR_EVENT_ID = "event_id" -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Old way of setting up deCONZ platforms.""" pass @@ -34,9 +37,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): for sensor in sensors: - if not sensor.BINARY and \ - not (not gateway.allow_clip_sensor and - sensor.type.startswith('CLIP')): + if not sensor.BINARY and not ( + not gateway.allow_clip_sensor and sensor.type.startswith("CLIP") + ): if sensor.type in Switch.ZHATYPE: if sensor.battery: @@ -47,8 +50,11 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) - gateway.listeners.append(async_dispatcher_connect( - hass, gateway.async_event_new_device(NEW_SENSOR), async_add_sensor)) + gateway.listeners.append( + async_dispatcher_connect( + hass, gateway.async_event_new_device(NEW_SENSOR), async_add_sensor + ) + ) async_add_sensor(gateway.api.sensors.values()) @@ -60,7 +66,7 @@ class DeconzSensor(DeconzDevice): def async_update_callback(self, force_update=False): """Update the sensor's state.""" changed = set(self._device.changed_keys) - keys = {'battery', 'on', 'reachable', 'state'} + keys = {"battery", "on", "reachable", "state"} if force_update or any(key in changed for key in keys): self.async_schedule_update_ha_state() @@ -103,8 +109,7 @@ class DeconzSensor(DeconzDevice): elif self._device.type in Daylight.ZHATYPE: attr[ATTR_DAYLIGHT] = self._device.daylight - elif self._device.type in LightLevel.ZHATYPE and \ - self._device.dark is not None: + elif self._device.type in LightLevel.ZHATYPE and self._device.dark is not None: attr[ATTR_DARK] = self._device.dark elif self._device.type in Power.ZHATYPE: @@ -121,14 +126,14 @@ class DeconzBattery(DeconzDevice): """Register dispatcher callback for update of battery state.""" super().__init__(device, gateway) - self._name = '{} {}'.format(self._device.name, 'Battery Level') + self._name = "{} {}".format(self._device.name, "Battery Level") self._unit_of_measurement = "%" @callback def async_update_callback(self, force_update=False): """Update the battery's state, if needed.""" changed = set(self._device.changed_keys) - keys = {'battery', 'reachable'} + keys = {"battery", "reachable"} if force_update or any(key in changed for key in keys): self.async_schedule_update_ha_state() @@ -155,7 +160,5 @@ class DeconzBattery(DeconzDevice): @property def device_state_attributes(self): """Return the state attributes of the battery.""" - attr = { - ATTR_EVENT_ID: slugify(self._device.name), - } + attr = {ATTR_EVENT_ID: slugify(self._device.name)} return attr diff --git a/homeassistant/components/deconz/switch.py b/homeassistant/components/deconz/switch.py index dd06dba9583..7ce40789802 100644 --- a/homeassistant/components/deconz/switch.py +++ b/homeassistant/components/deconz/switch.py @@ -8,8 +8,7 @@ from .deconz_device import DeconzDevice from .gateway import get_gateway_from_config_entry -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Old way of setting up deCONZ platforms.""" pass @@ -36,8 +35,11 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) - gateway.listeners.append(async_dispatcher_connect( - hass, gateway.async_event_new_device(NEW_LIGHT), async_add_switch)) + gateway.listeners.append( + async_dispatcher_connect( + hass, gateway.async_event_new_device(NEW_LIGHT), async_add_switch + ) + ) async_add_switch(gateway.api.lights.values()) @@ -52,12 +54,12 @@ class DeconzPowerPlug(DeconzDevice, SwitchDevice): async def async_turn_on(self, **kwargs): """Turn on switch.""" - data = {'on': True} + data = {"on": True} await self._device.async_set_state(data) async def async_turn_off(self, **kwargs): """Turn off switch.""" - data = {'on': False} + data = {"on": False} await self._device.async_set_state(data) @@ -67,14 +69,14 @@ class DeconzSiren(DeconzDevice, SwitchDevice): @property def is_on(self): """Return true if switch is on.""" - return self._device.alert == 'lselect' + return self._device.alert == "lselect" async def async_turn_on(self, **kwargs): """Turn on switch.""" - data = {'alert': 'lselect'} + data = {"alert": "lselect"} await self._device.async_set_state(data) async def async_turn_off(self, **kwargs): """Turn off switch.""" - data = {'alert': 'none'} + data = {"alert": "none"} await self._device.async_set_state(data) diff --git a/homeassistant/components/decora/light.py b/homeassistant/components/decora/light.py index 2f6c050b79e..cad98f9d8a4 100644 --- a/homeassistant/components/decora/light.py +++ b/homeassistant/components/decora/light.py @@ -8,26 +8,29 @@ import voluptuous as vol from homeassistant.const import CONF_API_KEY, CONF_DEVICES, CONF_NAME from homeassistant.components.light import ( - ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light, - PLATFORM_SCHEMA) + ATTR_BRIGHTNESS, + SUPPORT_BRIGHTNESS, + Light, + PLATFORM_SCHEMA, +) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -SUPPORT_DECORA_LED = (SUPPORT_BRIGHTNESS) +SUPPORT_DECORA_LED = SUPPORT_BRIGHTNESS -DEVICE_SCHEMA = vol.Schema({ - vol.Optional(CONF_NAME): cv.string, - vol.Required(CONF_API_KEY): cv.string, -}) +DEVICE_SCHEMA = vol.Schema( + {vol.Optional(CONF_NAME): cv.string, vol.Required(CONF_API_KEY): cv.string} +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_DEVICES, default={}): {cv.string: DEVICE_SCHEMA}, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Optional(CONF_DEVICES, default={}): {cv.string: DEVICE_SCHEMA}} +) def retry(method): """Retry bluetooth commands.""" + @wraps(method) def wrapper_retry(device, *args, **kwargs): """Try send command and retry on error.""" @@ -41,12 +44,14 @@ def retry(method): return None try: return method(device, *args, **kwargs) - except (decora.decoraException, AttributeError, - bluepy.btle.BTLEException): - _LOGGER.warning("Decora connect error for device %s. " - "Reconnecting...", device.name) + except (decora.decoraException, AttributeError, bluepy.btle.BTLEException): + _LOGGER.warning( + "Decora connect error for device %s. " "Reconnecting...", + device.name, + ) # pylint: disable=protected-access device._switch.connect() + return wrapper_retry @@ -55,9 +60,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): lights = [] for address, device_config in config[CONF_DEVICES].items(): device = {} - device['name'] = device_config[CONF_NAME] - device['key'] = device_config[CONF_API_KEY] - device['address'] = address + device["name"] = device_config[CONF_NAME] + device["key"] = device_config[CONF_API_KEY] + device["address"] = address light = DecoraLight(device) lights.append(light) @@ -70,10 +75,10 @@ class DecoraLight(Light): def __init__(self, device): """Initialize the light.""" # pylint: disable=no-member - decora = importlib.import_module('decora') + decora = importlib.import_module("decora") - self._name = device['name'] - self._address = device['address'] + self._name = device["name"] + self._address = device["address"] self._key = device["key"] self._switch = decora.decora(self._address, self._key) self._brightness = 0 diff --git a/homeassistant/components/decora_wifi/light.py b/homeassistant/components/decora_wifi/light.py index 390af765b62..77f3a6387ed 100644 --- a/homeassistant/components/decora_wifi/light.py +++ b/homeassistant/components/decora_wifi/light.py @@ -5,23 +5,25 @@ import logging import voluptuous as vol from homeassistant.components.light import ( - ATTR_BRIGHTNESS, ATTR_TRANSITION, Light, - PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, SUPPORT_TRANSITION) -from homeassistant.const import ( - CONF_USERNAME, CONF_PASSWORD, - EVENT_HOMEASSISTANT_STOP) + ATTR_BRIGHTNESS, + ATTR_TRANSITION, + Light, + PLATFORM_SCHEMA, + SUPPORT_BRIGHTNESS, + SUPPORT_TRANSITION, +) +from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) # Validation of the user's configuration -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string} +) -NOTIFICATION_ID = 'leviton_notification' -NOTIFICATION_TITLE = 'myLeviton Decora Setup' +NOTIFICATION_ID = "leviton_notification" +NOTIFICATION_TITLE = "myLeviton Decora Setup" def setup_platform(hass, config, add_entities, discovery_info=None): @@ -41,10 +43,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): # If login failed, notify user. if success is None: - msg = 'Failed to log into myLeviton Services. Check credentials.' + msg = "Failed to log into myLeviton Services. Check credentials." _LOGGER.error(msg) hass.components.persistent_notification.create( - msg, title=NOTIFICATION_TITLE, notification_id=NOTIFICATION_ID) + msg, title=NOTIFICATION_TITLE, notification_id=NOTIFICATION_ID + ) return False # Gather all the available devices... @@ -52,8 +55,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): all_switches = [] for permission in perms: if permission.residentialAccountId is not None: - acct = ResidentialAccount( - session, permission.residentialAccountId) + acct = ResidentialAccount(session, permission.residentialAccountId) for residence in acct.get_residences(): for switch in residence.get_iot_switches(): all_switches.append(switch) @@ -64,7 +66,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(DecoraWifiLight(sw) for sw in all_switches) except ValueError: - _LOGGER.error('Failed to communicate with myLeviton Service.') + _LOGGER.error("Failed to communicate with myLeviton Service.") # Listen for the stop event and log out. def logout(event): @@ -73,7 +75,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if session is not None: Person.logout(session) except ValueError: - _LOGGER.error('Failed to log out of myLeviton Service.') + _LOGGER.error("Failed to log out of myLeviton Service.") hass.bus.listen(EVENT_HOMEASSISTANT_STOP, logout) @@ -105,39 +107,39 @@ class DecoraWifiLight(Light): @property def is_on(self): """Return true if switch is on.""" - return self._switch.power == 'ON' + return self._switch.power == "ON" def turn_on(self, **kwargs): """Instruct the switch to turn on & adjust brightness.""" - attribs = {'power': 'ON'} + attribs = {"power": "ON"} if ATTR_BRIGHTNESS in kwargs: - min_level = self._switch.data.get('minLevel', 0) - max_level = self._switch.data.get('maxLevel', 100) + min_level = self._switch.data.get("minLevel", 0) + max_level = self._switch.data.get("maxLevel", 100) brightness = int(kwargs[ATTR_BRIGHTNESS] * max_level / 255) brightness = max(brightness, min_level) - attribs['brightness'] = brightness + attribs["brightness"] = brightness if ATTR_TRANSITION in kwargs: transition = int(kwargs[ATTR_TRANSITION]) - attribs['fadeOnTime'] = attribs['fadeOffTime'] = transition + attribs["fadeOnTime"] = attribs["fadeOffTime"] = transition try: self._switch.update_attributes(attribs) except ValueError: - _LOGGER.error('Failed to turn on myLeviton switch.') + _LOGGER.error("Failed to turn on myLeviton switch.") def turn_off(self, **kwargs): """Instruct the switch to turn off.""" - attribs = {'power': 'OFF'} + attribs = {"power": "OFF"} try: self._switch.update_attributes(attribs) except ValueError: - _LOGGER.error('Failed to turn off myLeviton switch.') + _LOGGER.error("Failed to turn off myLeviton switch.") def update(self): """Fetch new state data for this switch.""" try: self._switch.refresh() except ValueError: - _LOGGER.error('Failed to update myLeviton switch data.') + _LOGGER.error("Failed to update myLeviton switch data.") diff --git a/homeassistant/components/default_config/__init__.py b/homeassistant/components/default_config/__init__.py index 23add299b2f..506904a500a 100644 --- a/homeassistant/components/default_config/__init__.py +++ b/homeassistant/components/default_config/__init__.py @@ -6,7 +6,7 @@ except ImportError: from homeassistant.setup import async_setup_component -DOMAIN = 'default_config' +DOMAIN = "default_config" async def async_setup(hass, config): @@ -14,4 +14,4 @@ async def async_setup(hass, config): if av is None: return True - return await async_setup_component(hass, 'stream', config) + return await async_setup_component(hass, "stream", config) diff --git a/homeassistant/components/delijn/sensor.py b/homeassistant/components/delijn/sensor.py index ce679af3ad2..e704ef6556f 100644 --- a/homeassistant/components/delijn/sensor.py +++ b/homeassistant/components/delijn/sensor.py @@ -5,8 +5,7 @@ from pydelijn.api import Passages import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ( - ATTR_ATTRIBUTION, DEVICE_CLASS_TIMESTAMP) +from homeassistant.const import ATTR_ATTRIBUTION, DEVICE_CLASS_TIMESTAMP from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -15,23 +14,27 @@ _LOGGER = logging.getLogger(__name__) ATTRIBUTION = "Data provided by data.delijn.be" -CONF_NEXT_DEPARTURE = 'next_departure' -CONF_STOP_ID = 'stop_id' -CONF_API_KEY = 'api_key' -CONF_NUMBER_OF_DEPARTURES = 'number_of_departures' +CONF_NEXT_DEPARTURE = "next_departure" +CONF_STOP_ID = "stop_id" +CONF_API_KEY = "api_key" +CONF_NUMBER_OF_DEPARTURES = "number_of_departures" -DEFAULT_NAME = 'De Lijn' +DEFAULT_NAME = "De Lijn" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_API_KEY): cv.string, - vol.Required(CONF_NEXT_DEPARTURE): [{ - vol.Required(CONF_STOP_ID): cv.string, - vol.Optional(CONF_NUMBER_OF_DEPARTURES, default=5): cv.positive_int}] -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_API_KEY): cv.string, + vol.Required(CONF_NEXT_DEPARTURE): [ + { + vol.Required(CONF_STOP_ID): cv.string, + vol.Optional(CONF_NUMBER_OF_DEPARTURES, default=5): cv.positive_int, + } + ], + } +) -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Create the sensor.""" api_key = config[CONF_API_KEY] name = DEFAULT_NAME @@ -42,12 +45,9 @@ async def async_setup_platform( for nextpassage in config[CONF_NEXT_DEPARTURE]: stop_id = nextpassage[CONF_STOP_ID] number_of_departures = nextpassage[CONF_NUMBER_OF_DEPARTURES] - line = Passages(hass.loop, - stop_id, - number_of_departures, - api_key, - session, - True) + line = Passages( + hass.loop, stop_id, number_of_departures, api_key, session, True + ) await line.get_passages() if line.passages is None: _LOGGER.warning("No data recieved from De Lijn") @@ -75,21 +75,19 @@ class DeLijnPublicTransportSensor(Entity): return try: first = self.line.passages[0] - if first['due_at_realtime'] is not None: - first_passage = first['due_at_realtime'] + if first["due_at_realtime"] is not None: + first_passage = first["due_at_realtime"] else: - first_passage = first['due_at_schedule'] + first_passage = first["due_at_schedule"] self._state = first_passage - self._name = first['stopname'] - self._attributes['stopname'] = first['stopname'] - self._attributes['line_number_public'] = first[ - 'line_number_public'] - self._attributes['line_transport_type'] = first[ - 'line_transport_type'] - self._attributes['final_destination'] = first['final_destination'] - self._attributes['due_at_schedule'] = first['due_at_schedule'] - self._attributes['due_at_realtime'] = first['due_at_realtime'] - self._attributes['next_passages'] = self.line.passages + self._name = first["stopname"] + self._attributes["stopname"] = first["stopname"] + self._attributes["line_number_public"] = first["line_number_public"] + self._attributes["line_transport_type"] = first["line_transport_type"] + self._attributes["final_destination"] = first["final_destination"] + self._attributes["due_at_schedule"] = first["due_at_schedule"] + self._attributes["due_at_realtime"] = first["due_at_realtime"] + self._attributes["next_passages"] = self.line.passages except (KeyError, IndexError) as error: _LOGGER.debug("Error getting data from De Lijn: %s", error) @@ -111,7 +109,7 @@ class DeLijnPublicTransportSensor(Entity): @property def icon(self): """Return the icon of the sensor.""" - return 'mdi:bus' + return "mdi:bus" @property def device_state_attributes(self): diff --git a/homeassistant/components/deluge/sensor.py b/homeassistant/components/deluge/sensor.py index 1002ae51077..8b42b6175ce 100644 --- a/homeassistant/components/deluge/sensor.py +++ b/homeassistant/components/deluge/sensor.py @@ -6,33 +6,42 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_NAME, CONF_PORT, - CONF_MONITORED_VARIABLES, STATE_IDLE) + CONF_HOST, + CONF_PASSWORD, + CONF_USERNAME, + CONF_NAME, + CONF_PORT, + CONF_MONITORED_VARIABLES, + STATE_IDLE, +) from homeassistant.helpers.entity import Entity from homeassistant.exceptions import PlatformNotReady _LOGGER = logging.getLogger(__name__) _THROTTLED_REFRESH = None -DEFAULT_NAME = 'Deluge' +DEFAULT_NAME = "Deluge" DEFAULT_PORT = 58846 DHT_UPLOAD = 1000 DHT_DOWNLOAD = 1000 SENSOR_TYPES = { - 'current_status': ['Status', None], - 'download_speed': ['Down Speed', 'kB/s'], - 'upload_speed': ['Up Speed', 'kB/s'], + "current_status": ["Status", None], + "download_speed": ["Down Speed", "kB/s"], + "upload_speed": ["Up Speed", "kB/s"], } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Required(CONF_USERNAME): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_MONITORED_VARIABLES, default=[]): vol.All( - cv.ensure_list, [vol.In(SENSOR_TYPES)]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Required(CONF_USERNAME): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_MONITORED_VARIABLES, default=[]): vol.All( + cv.ensure_list, [vol.In(SENSOR_TYPES)] + ), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -75,7 +84,7 @@ class DelugeSensor(Entity): @property def name(self): """Return the name of the sensor.""" - return '{} {}'.format(self.client_name, self._name) + return "{} {}".format(self.client_name, self._name) @property def state(self): @@ -95,40 +104,45 @@ class DelugeSensor(Entity): def update(self): """Get the latest data from Deluge and updates the state.""" from deluge_client import FailedToReconnectException + try: - self.data = self.client.call('core.get_session_status', - ['upload_rate', 'download_rate', - 'dht_upload_rate', - 'dht_download_rate']) + self.data = self.client.call( + "core.get_session_status", + [ + "upload_rate", + "download_rate", + "dht_upload_rate", + "dht_download_rate", + ], + ) self._available = True except FailedToReconnectException: _LOGGER.error("Connection to Deluge Daemon Lost") self._available = False return - upload = self.data[b'upload_rate'] - self.data[b'dht_upload_rate'] - download = self.data[b'download_rate'] - self.data[ - b'dht_download_rate'] + upload = self.data[b"upload_rate"] - self.data[b"dht_upload_rate"] + download = self.data[b"download_rate"] - self.data[b"dht_download_rate"] - if self.type == 'current_status': + if self.type == "current_status": if self.data: if upload > 0 and download > 0: - self._state = 'Up/Down' + self._state = "Up/Down" elif upload > 0 and download == 0: - self._state = 'Seeding' + self._state = "Seeding" elif upload == 0 and download > 0: - self._state = 'Downloading' + self._state = "Downloading" else: self._state = STATE_IDLE else: self._state = None if self.data: - if self.type == 'download_speed': + if self.type == "download_speed": kb_spd = float(download) kb_spd = kb_spd / 1024 self._state = round(kb_spd, 2 if kb_spd < 0.1 else 1) - elif self.type == 'upload_speed': + elif self.type == "upload_speed": kb_spd = float(upload) kb_spd = kb_spd / 1024 self._state = round(kb_spd, 2 if kb_spd < 0.1 else 1) diff --git a/homeassistant/components/deluge/switch.py b/homeassistant/components/deluge/switch.py index d72ce9a5308..981ef129b47 100644 --- a/homeassistant/components/deluge/switch.py +++ b/homeassistant/components/deluge/switch.py @@ -6,23 +6,31 @@ import voluptuous as vol from homeassistant.components.switch import PLATFORM_SCHEMA from homeassistant.exceptions import PlatformNotReady from homeassistant.const import ( - CONF_HOST, CONF_NAME, CONF_PORT, CONF_PASSWORD, CONF_USERNAME, STATE_OFF, - STATE_ON) + CONF_HOST, + CONF_NAME, + CONF_PORT, + CONF_PASSWORD, + CONF_USERNAME, + STATE_OFF, + STATE_ON, +) from homeassistant.helpers.entity import ToggleEntity import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'Deluge Switch' +DEFAULT_NAME = "Deluge Switch" DEFAULT_PORT = 58846 -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Required(CONF_USERNAME): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Required(CONF_USERNAME): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -77,20 +85,22 @@ class DelugeSwitch(ToggleEntity): def turn_on(self, **kwargs): """Turn the device on.""" - torrent_ids = self.deluge_client.call('core.get_session_state') - self.deluge_client.call('core.resume_torrent', torrent_ids) + torrent_ids = self.deluge_client.call("core.get_session_state") + self.deluge_client.call("core.resume_torrent", torrent_ids) def turn_off(self, **kwargs): """Turn the device off.""" - torrent_ids = self.deluge_client.call('core.get_session_state') - self.deluge_client.call('core.pause_torrent', torrent_ids) + torrent_ids = self.deluge_client.call("core.get_session_state") + self.deluge_client.call("core.pause_torrent", torrent_ids) def update(self): """Get the latest data from deluge and updates the state.""" from deluge_client import FailedToReconnectException + try: - torrent_list = self.deluge_client.call('core.get_torrents_status', - {}, ['paused']) + torrent_list = self.deluge_client.call( + "core.get_torrents_status", {}, ["paused"] + ) self._available = True except FailedToReconnectException: _LOGGER.error("Connection to Deluge Daemon Lost") diff --git a/homeassistant/components/demo/__init__.py b/homeassistant/components/demo/__init__.py index c61673afda6..f8b61167ef7 100644 --- a/homeassistant/components/demo/__init__.py +++ b/homeassistant/components/demo/__init__.py @@ -7,27 +7,27 @@ from homeassistant import bootstrap import homeassistant.core as ha from homeassistant.const import ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_START -DOMAIN = 'demo' +DOMAIN = "demo" _LOGGER = logging.getLogger(__name__) COMPONENTS_WITH_DEMO_PLATFORM = [ - 'air_quality', - 'alarm_control_panel', - 'binary_sensor', - 'calendar', - 'camera', - 'climate', - 'cover', - 'device_tracker', - 'fan', - 'image_processing', - 'light', - 'lock', - 'media_player', - 'notify', - 'sensor', - 'switch', - 'tts', - 'mailbox', + "air_quality", + "alarm_control_panel", + "binary_sensor", + "calendar", + "camera", + "climate", + "cover", + "device_tracker", + "fan", + "image_processing", + "light", + "lock", + "media_player", + "notify", + "sensor", + "switch", + "tts", + "mailbox", ] @@ -41,9 +41,9 @@ async def async_setup(hass, config): # Set up demo platforms for component in COMPONENTS_WITH_DEMO_PLATFORM: - hass.async_create_task(hass.helpers.discovery.async_load_platform( - component, DOMAIN, {}, config, - )) + hass.async_create_task( + hass.helpers.discovery.async_load_platform(component, DOMAIN, {}, config) + ) # Set up sun if not hass.config.latitude: @@ -52,45 +52,77 @@ async def async_setup(hass, config): if not hass.config.longitude: hass.config.longitude = 117.22743 - tasks = [ - bootstrap.async_setup_component(hass, 'sun', config) - ] + tasks = [bootstrap.async_setup_component(hass, "sun", config)] # Set up input select - tasks.append(bootstrap.async_setup_component( - hass, 'input_select', - {'input_select': - {'living_room_preset': {'options': ['Visitors', - 'Visitors with kids', - 'Home Alone']}, - 'who_cooks': {'icon': 'mdi:panda', - 'initial': 'Anne Therese', - 'name': 'Cook today', - 'options': ['Paulus', 'Anne Therese']}}})) + tasks.append( + bootstrap.async_setup_component( + hass, + "input_select", + { + "input_select": { + "living_room_preset": { + "options": ["Visitors", "Visitors with kids", "Home Alone"] + }, + "who_cooks": { + "icon": "mdi:panda", + "initial": "Anne Therese", + "name": "Cook today", + "options": ["Paulus", "Anne Therese"], + }, + } + }, + ) + ) # Set up input boolean - tasks.append(bootstrap.async_setup_component( - hass, 'input_boolean', - {'input_boolean': {'notify': { - 'icon': 'mdi:car', - 'initial': False, - 'name': 'Notify Anne Therese is home'}}})) + tasks.append( + bootstrap.async_setup_component( + hass, + "input_boolean", + { + "input_boolean": { + "notify": { + "icon": "mdi:car", + "initial": False, + "name": "Notify Anne Therese is home", + } + } + }, + ) + ) # Set up input boolean - tasks.append(bootstrap.async_setup_component( - hass, 'input_number', - {'input_number': { - 'noise_allowance': {'icon': 'mdi:bell-ring', - 'min': 0, - 'max': 10, - 'name': 'Allowed Noise', - 'unit_of_measurement': 'dB'}}})) + tasks.append( + bootstrap.async_setup_component( + hass, + "input_number", + { + "input_number": { + "noise_allowance": { + "icon": "mdi:bell-ring", + "min": 0, + "max": 10, + "name": "Allowed Noise", + "unit_of_measurement": "dB", + } + } + }, + ) + ) # Set up weblink - tasks.append(bootstrap.async_setup_component( - hass, 'weblink', - {'weblink': {'entities': [{'name': 'Router', - 'url': 'http://192.168.1.1'}]}})) + tasks.append( + bootstrap.async_setup_component( + hass, + "weblink", + { + "weblink": { + "entities": [{"name": "Router", "url": "http://192.168.1.1"}] + } + }, + ) + ) results = await asyncio.gather(*tasks) @@ -99,8 +131,8 @@ async def async_setup(hass, config): # Set up example persistent notification hass.components.persistent_notification.async_create( - 'This is an example of a persistent notification.', - title='Example Notification') + "This is an example of a persistent notification.", title="Example Notification" + ) # Set up configurator configurator_ids = [] @@ -113,20 +145,23 @@ async def async_setup(hass, config): # First time it is called, pretend it failed. if len(configurator_ids) == 1: configurator.notify_errors( - configurator_ids[0], - "Failed to register, please try again.") + configurator_ids[0], "Failed to register, please try again." + ) configurator_ids.append(0) else: configurator.request_done(configurator_ids[0]) request_id = configurator.async_request_config( - "Philips Hue", hue_configuration_callback, - description=("Press the button on the bridge to register Philips " - "Hue with Home Assistant."), + "Philips Hue", + hue_configuration_callback, + description=( + "Press the button on the bridge to register Philips " + "Hue with Home Assistant." + ), description_image="/static/images/config_philips_hue.jpg", - fields=[{'id': 'username', 'name': 'Username'}], - submit_caption="I have pressed the button" + fields=[{"id": "username", "name": "Username"}], + submit_caption="I have pressed the button", ) configurator_ids.append(request_id) @@ -141,55 +176,75 @@ async def async_setup(hass, config): async def finish_setup(hass, config): """Finish set up once demo platforms are set up.""" - lights = sorted(hass.states.async_entity_ids('light')) - switches = sorted(hass.states.async_entity_ids('switch')) + lights = sorted(hass.states.async_entity_ids("light")) + switches = sorted(hass.states.async_entity_ids("switch")) # Set up history graph await bootstrap.async_setup_component( - hass, 'history_graph', - {'history_graph': {'switches': { - 'name': 'Recent Switches', - 'entities': switches, - 'hours_to_show': 1, - 'refresh': 60 - }}} + hass, + "history_graph", + { + "history_graph": { + "switches": { + "name": "Recent Switches", + "entities": switches, + "hours_to_show": 1, + "refresh": 60, + } + } + }, ) # Set up scripts await bootstrap.async_setup_component( - hass, 'script', - {'script': { - 'demo': { - 'alias': 'Toggle {}'.format(lights[0].split('.')[1]), - 'sequence': [{ - 'service': 'light.turn_off', - 'data': {ATTR_ENTITY_ID: lights[0]} - }, { - 'delay': {'seconds': 5} - }, { - 'service': 'light.turn_on', - 'data': {ATTR_ENTITY_ID: lights[0]} - }, { - 'delay': {'seconds': 5} - }, { - 'service': 'light.turn_off', - 'data': {ATTR_ENTITY_ID: lights[0]} - }] - }}}) + hass, + "script", + { + "script": { + "demo": { + "alias": "Toggle {}".format(lights[0].split(".")[1]), + "sequence": [ + { + "service": "light.turn_off", + "data": {ATTR_ENTITY_ID: lights[0]}, + }, + {"delay": {"seconds": 5}}, + { + "service": "light.turn_on", + "data": {ATTR_ENTITY_ID: lights[0]}, + }, + {"delay": {"seconds": 5}}, + { + "service": "light.turn_off", + "data": {ATTR_ENTITY_ID: lights[0]}, + }, + ], + } + } + }, + ) # Set up scenes await bootstrap.async_setup_component( - hass, 'scene', - {'scene': [ - {'name': 'Romantic lights', - 'entities': { - lights[0]: True, - lights[1]: {'state': 'on', 'xy_color': [0.33, 0.66], - 'brightness': 200}, - }}, - {'name': 'Switch on and off', - 'entities': { - switches[0]: True, - switches[1]: False, - }}, - ]}) + hass, + "scene", + { + "scene": [ + { + "name": "Romantic lights", + "entities": { + lights[0]: True, + lights[1]: { + "state": "on", + "xy_color": [0.33, 0.66], + "brightness": 200, + }, + }, + }, + { + "name": "Switch on and off", + "entities": {switches[0]: True, switches[1]: False}, + }, + ] + }, + ) diff --git a/homeassistant/components/demo/air_quality.py b/homeassistant/components/demo/air_quality.py index 77e5c0b2b1a..0b41d9c87e9 100644 --- a/homeassistant/components/demo/air_quality.py +++ b/homeassistant/components/demo/air_quality.py @@ -4,10 +4,9 @@ from homeassistant.components.air_quality import AirQualityEntity def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Air Quality.""" - add_entities([ - DemoAirQuality('Home', 14, 23, 100), - DemoAirQuality('Office', 4, 16, None) - ]) + add_entities( + [DemoAirQuality("Home", 14, 23, 100), DemoAirQuality("Office", 4, 16, None)] + ) class DemoAirQuality(AirQualityEntity): @@ -23,7 +22,7 @@ class DemoAirQuality(AirQualityEntity): @property def name(self): """Return the name of the sensor.""" - return '{} {}'.format('Demo Air Quality', self._name) + return "{} {}".format("Demo Air Quality", self._name) @property def should_poll(self): @@ -48,4 +47,4 @@ class DemoAirQuality(AirQualityEntity): @property def attribution(self): """Return the attribution.""" - return 'Powered by Home Assistant' + return "Powered by Home Assistant" diff --git a/homeassistant/components/demo/alarm_control_panel.py b/homeassistant/components/demo/alarm_control_panel.py index a960848eee7..378ba3b18dd 100644 --- a/homeassistant/components/demo/alarm_control_panel.py +++ b/homeassistant/components/demo/alarm_control_panel.py @@ -2,43 +2,58 @@ import datetime from homeassistant.components.manual.alarm_control_panel import ManualAlarm from homeassistant.const import ( - STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_CUSTOM_BYPASS, - STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, - STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED, CONF_DELAY_TIME, - CONF_PENDING_TIME, CONF_TRIGGER_TIME) + STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_CUSTOM_BYPASS, + STATE_ALARM_ARMED_HOME, + STATE_ALARM_ARMED_NIGHT, + STATE_ALARM_DISARMED, + STATE_ALARM_TRIGGERED, + CONF_DELAY_TIME, + CONF_PENDING_TIME, + CONF_TRIGGER_TIME, +) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Demo alarm control panel platform.""" - async_add_entities([ - ManualAlarm(hass, 'Alarm', '1234', None, True, False, { - STATE_ALARM_ARMED_AWAY: { - CONF_DELAY_TIME: datetime.timedelta(seconds=0), - CONF_PENDING_TIME: datetime.timedelta(seconds=5), - CONF_TRIGGER_TIME: datetime.timedelta(seconds=10), - }, - STATE_ALARM_ARMED_HOME: { - CONF_DELAY_TIME: datetime.timedelta(seconds=0), - CONF_PENDING_TIME: datetime.timedelta(seconds=5), - CONF_TRIGGER_TIME: datetime.timedelta(seconds=10), - }, - STATE_ALARM_ARMED_NIGHT: { - CONF_DELAY_TIME: datetime.timedelta(seconds=0), - CONF_PENDING_TIME: datetime.timedelta(seconds=5), - CONF_TRIGGER_TIME: datetime.timedelta(seconds=10), - }, - STATE_ALARM_DISARMED: { - CONF_DELAY_TIME: datetime.timedelta(seconds=0), - CONF_TRIGGER_TIME: datetime.timedelta(seconds=10), - }, - STATE_ALARM_ARMED_CUSTOM_BYPASS: { - CONF_DELAY_TIME: datetime.timedelta(seconds=0), - CONF_PENDING_TIME: datetime.timedelta(seconds=5), - CONF_TRIGGER_TIME: datetime.timedelta(seconds=10), - }, - STATE_ALARM_TRIGGERED: { - CONF_PENDING_TIME: datetime.timedelta(seconds=5), - }, - }), - ]) + async_add_entities( + [ + ManualAlarm( + hass, + "Alarm", + "1234", + None, + True, + False, + { + STATE_ALARM_ARMED_AWAY: { + CONF_DELAY_TIME: datetime.timedelta(seconds=0), + CONF_PENDING_TIME: datetime.timedelta(seconds=5), + CONF_TRIGGER_TIME: datetime.timedelta(seconds=10), + }, + STATE_ALARM_ARMED_HOME: { + CONF_DELAY_TIME: datetime.timedelta(seconds=0), + CONF_PENDING_TIME: datetime.timedelta(seconds=5), + CONF_TRIGGER_TIME: datetime.timedelta(seconds=10), + }, + STATE_ALARM_ARMED_NIGHT: { + CONF_DELAY_TIME: datetime.timedelta(seconds=0), + CONF_PENDING_TIME: datetime.timedelta(seconds=5), + CONF_TRIGGER_TIME: datetime.timedelta(seconds=10), + }, + STATE_ALARM_DISARMED: { + CONF_DELAY_TIME: datetime.timedelta(seconds=0), + CONF_TRIGGER_TIME: datetime.timedelta(seconds=10), + }, + STATE_ALARM_ARMED_CUSTOM_BYPASS: { + CONF_DELAY_TIME: datetime.timedelta(seconds=0), + CONF_PENDING_TIME: datetime.timedelta(seconds=5), + CONF_TRIGGER_TIME: datetime.timedelta(seconds=10), + }, + STATE_ALARM_TRIGGERED: { + CONF_PENDING_TIME: datetime.timedelta(seconds=5) + }, + }, + ) + ] + ) diff --git a/homeassistant/components/demo/binary_sensor.py b/homeassistant/components/demo/binary_sensor.py index 437497e4fac..96c2b66fa5b 100644 --- a/homeassistant/components/demo/binary_sensor.py +++ b/homeassistant/components/demo/binary_sensor.py @@ -4,10 +4,12 @@ from homeassistant.components.binary_sensor import BinarySensorDevice def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Demo binary sensor platform.""" - add_entities([ - DemoBinarySensor('Basement Floor Wet', False, 'moisture'), - DemoBinarySensor('Movement Backyard', True, 'motion'), - ]) + add_entities( + [ + DemoBinarySensor("Basement Floor Wet", False, "moisture"), + DemoBinarySensor("Movement Backyard", True, "motion"), + ] + ) class DemoBinarySensor(BinarySensorDevice): diff --git a/homeassistant/components/demo/calendar.py b/homeassistant/components/demo/calendar.py index 9a685357b10..4ae836466f0 100644 --- a/homeassistant/components/demo/calendar.py +++ b/homeassistant/components/demo/calendar.py @@ -10,10 +10,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Demo Calendar platform.""" calendar_data_future = DemoGoogleCalendarDataFuture() calendar_data_current = DemoGoogleCalendarDataCurrent() - add_entities([ - DemoGoogleCalendar(hass, calendar_data_future, 'Calendar 1'), - DemoGoogleCalendar(hass, calendar_data_current, 'Calendar 2'), - ]) + add_entities( + [ + DemoGoogleCalendar(hass, calendar_data_future, "Calendar 1"), + DemoGoogleCalendar(hass, calendar_data_current, "Calendar 2"), + ] + ) class DemoGoogleCalendarData: @@ -24,9 +26,9 @@ class DemoGoogleCalendarData: async def async_get_events(self, hass, start_date, end_date): """Get all events in a specific time frame.""" event = copy.copy(self.event) - event['title'] = event['summary'] - event['start'] = get_date(event['start']).isoformat() - event['end'] = get_date(event['end']).isoformat() + event["title"] = event["summary"] + event["start"] = get_date(event["start"]).isoformat() + event["end"] = get_date(event["end"]).isoformat() return [event] @@ -35,17 +37,15 @@ class DemoGoogleCalendarDataFuture(DemoGoogleCalendarData): def __init__(self): """Set the event to a future event.""" - one_hour_from_now = dt_util.now() \ - + dt_util.dt.timedelta(minutes=30) + one_hour_from_now = dt_util.now() + dt_util.dt.timedelta(minutes=30) self.event = { - 'start': { - 'dateTime': one_hour_from_now.isoformat() + "start": {"dateTime": one_hour_from_now.isoformat()}, + "end": { + "dateTime": ( + one_hour_from_now + dt_util.dt.timedelta(minutes=60) + ).isoformat() }, - 'end': { - 'dateTime': (one_hour_from_now + dt_util.dt. - timedelta(minutes=60)).isoformat() - }, - 'summary': 'Future Event', + "summary": "Future Event", } @@ -54,17 +54,15 @@ class DemoGoogleCalendarDataCurrent(DemoGoogleCalendarData): def __init__(self): """Set the event data.""" - middle_of_event = dt_util.now() \ - - dt_util.dt.timedelta(minutes=30) + middle_of_event = dt_util.now() - dt_util.dt.timedelta(minutes=30) self.event = { - 'start': { - 'dateTime': middle_of_event.isoformat() + "start": {"dateTime": middle_of_event.isoformat()}, + "end": { + "dateTime": ( + middle_of_event + dt_util.dt.timedelta(minutes=60) + ).isoformat() }, - 'end': { - 'dateTime': (middle_of_event + dt_util.dt. - timedelta(minutes=60)).isoformat() - }, - 'summary': 'Current Event', + "summary": "Current Event", } diff --git a/homeassistant/components/demo/camera.py b/homeassistant/components/demo/camera.py index 95c7df58200..7ac5fc17c69 100644 --- a/homeassistant/components/demo/camera.py +++ b/homeassistant/components/demo/camera.py @@ -7,12 +7,9 @@ from homeassistant.components.camera import SUPPORT_ON_OFF, Camera _LOGGER = logging.getLogger(__name__) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Demo camera platform.""" - async_add_entities([ - DemoCamera('Demo camera') - ]) + async_add_entities([DemoCamera("Demo camera")]) class DemoCamera(Camera): @@ -31,10 +28,10 @@ class DemoCamera(Camera): self._images_index = (self._images_index + 1) % 4 image_path = os.path.join( - os.path.dirname(__file__), - 'demo_{}.jpg'.format(self._images_index)) - _LOGGER.debug('Loading camera_image: %s', image_path) - with open(image_path, 'rb') as file: + os.path.dirname(__file__), "demo_{}.jpg".format(self._images_index) + ) + _LOGGER.debug("Loading camera_image: %s", image_path) + with open(image_path, "rb") as file: return file.read() @property diff --git a/homeassistant/components/demo/climate.py b/homeassistant/components/demo/climate.py index 4e8654ac16b..f5117b7986c 100644 --- a/homeassistant/components/demo/climate.py +++ b/homeassistant/components/demo/climate.py @@ -1,12 +1,24 @@ """Demo platform that offers a fake climate device.""" from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( - ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, CURRENT_HVAC_COOL, - CURRENT_HVAC_HEAT, HVAC_MODE_COOL, HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, HVAC_MODES, SUPPORT_AUX_HEAT, - SUPPORT_FAN_MODE, SUPPORT_PRESET_MODE, SUPPORT_SWING_MODE, - SUPPORT_TARGET_HUMIDITY, SUPPORT_TARGET_TEMPERATURE, - SUPPORT_TARGET_TEMPERATURE_RANGE, HVAC_MODE_AUTO) + ATTR_TARGET_TEMP_HIGH, + ATTR_TARGET_TEMP_LOW, + CURRENT_HVAC_COOL, + CURRENT_HVAC_HEAT, + HVAC_MODE_COOL, + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_OFF, + HVAC_MODES, + SUPPORT_AUX_HEAT, + SUPPORT_FAN_MODE, + SUPPORT_PRESET_MODE, + SUPPORT_SWING_MODE, + SUPPORT_TARGET_HUMIDITY, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_TARGET_TEMPERATURE_RANGE, + HVAC_MODE_AUTO, +) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT SUPPORT_FLAGS = 0 @@ -14,108 +26,105 @@ SUPPORT_FLAGS = 0 def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Demo climate devices.""" - add_entities([ - DemoClimate( - name='HeatPump', - target_temperature=68, - unit_of_measurement=TEMP_FAHRENHEIT, - preset=None, - current_temperature=77, - fan_mode=None, - target_humidity=None, - current_humidity=None, - swing_mode=None, - hvac_mode=HVAC_MODE_HEAT, - hvac_action=CURRENT_HVAC_HEAT, - aux=None, - target_temp_high=None, - target_temp_low=None, - hvac_modes=[HVAC_MODE_HEAT, HVAC_MODE_OFF] - ), - DemoClimate( - name='Hvac', - target_temperature=21, - unit_of_measurement=TEMP_CELSIUS, - preset=None, - current_temperature=22, - fan_mode='On High', - target_humidity=67, - current_humidity=54, - swing_mode='Off', - hvac_mode=HVAC_MODE_COOL, - hvac_action=CURRENT_HVAC_COOL, - aux=False, - target_temp_high=None, - target_temp_low=None, - hvac_modes=[mode for mode in HVAC_MODES - if mode != HVAC_MODE_HEAT_COOL] - ), - DemoClimate( - name='Ecobee', - target_temperature=None, - unit_of_measurement=TEMP_CELSIUS, - preset='home', - preset_modes=['home', 'eco'], - current_temperature=23, - fan_mode='Auto Low', - target_humidity=None, - current_humidity=None, - swing_mode='Auto', - hvac_mode=HVAC_MODE_HEAT_COOL, - hvac_action=None, - aux=None, - target_temp_high=24, - target_temp_low=21, - hvac_modes=[HVAC_MODE_HEAT_COOL, HVAC_MODE_COOL, - HVAC_MODE_HEAT]) - ]) + add_entities( + [ + DemoClimate( + name="HeatPump", + target_temperature=68, + unit_of_measurement=TEMP_FAHRENHEIT, + preset=None, + current_temperature=77, + fan_mode=None, + target_humidity=None, + current_humidity=None, + swing_mode=None, + hvac_mode=HVAC_MODE_HEAT, + hvac_action=CURRENT_HVAC_HEAT, + aux=None, + target_temp_high=None, + target_temp_low=None, + hvac_modes=[HVAC_MODE_HEAT, HVAC_MODE_OFF], + ), + DemoClimate( + name="Hvac", + target_temperature=21, + unit_of_measurement=TEMP_CELSIUS, + preset=None, + current_temperature=22, + fan_mode="On High", + target_humidity=67, + current_humidity=54, + swing_mode="Off", + hvac_mode=HVAC_MODE_COOL, + hvac_action=CURRENT_HVAC_COOL, + aux=False, + target_temp_high=None, + target_temp_low=None, + hvac_modes=[mode for mode in HVAC_MODES if mode != HVAC_MODE_HEAT_COOL], + ), + DemoClimate( + name="Ecobee", + target_temperature=None, + unit_of_measurement=TEMP_CELSIUS, + preset="home", + preset_modes=["home", "eco"], + current_temperature=23, + fan_mode="Auto Low", + target_humidity=None, + current_humidity=None, + swing_mode="Auto", + hvac_mode=HVAC_MODE_HEAT_COOL, + hvac_action=None, + aux=None, + target_temp_high=24, + target_temp_low=21, + hvac_modes=[HVAC_MODE_HEAT_COOL, HVAC_MODE_COOL, HVAC_MODE_HEAT], + ), + ] + ) class DemoClimate(ClimateDevice): """Representation of a demo climate device.""" def __init__( - self, - name, - target_temperature, - unit_of_measurement, - preset, - current_temperature, - fan_mode, - target_humidity, - current_humidity, - swing_mode, - hvac_mode, - hvac_action, - aux, - target_temp_high, - target_temp_low, - hvac_modes, - preset_modes=None, + self, + name, + target_temperature, + unit_of_measurement, + preset, + current_temperature, + fan_mode, + target_humidity, + current_humidity, + swing_mode, + hvac_mode, + hvac_action, + aux, + target_temp_high, + target_temp_low, + hvac_modes, + preset_modes=None, ): """Initialize the climate device.""" self._name = name self._support_flags = SUPPORT_FLAGS if target_temperature is not None: - self._support_flags = \ - self._support_flags | SUPPORT_TARGET_TEMPERATURE + self._support_flags = self._support_flags | SUPPORT_TARGET_TEMPERATURE if preset is not None: self._support_flags = self._support_flags | SUPPORT_PRESET_MODE if fan_mode is not None: self._support_flags = self._support_flags | SUPPORT_FAN_MODE if target_humidity is not None: - self._support_flags = \ - self._support_flags | SUPPORT_TARGET_HUMIDITY + self._support_flags = self._support_flags | SUPPORT_TARGET_HUMIDITY if swing_mode is not None: self._support_flags = self._support_flags | SUPPORT_SWING_MODE if hvac_action is not None: self._support_flags = self._support_flags if aux is not None: self._support_flags = self._support_flags | SUPPORT_AUX_HEAT - if (HVAC_MODE_HEAT_COOL in hvac_modes or - HVAC_MODE_AUTO in hvac_modes): - self._support_flags = \ - self._support_flags | SUPPORT_TARGET_TEMPERATURE_RANGE + if HVAC_MODE_HEAT_COOL in hvac_modes or HVAC_MODE_AUTO in hvac_modes: + self._support_flags = self._support_flags | SUPPORT_TARGET_TEMPERATURE_RANGE self._target_temperature = target_temperature self._target_humidity = target_humidity self._unit_of_measurement = unit_of_measurement @@ -128,9 +137,9 @@ class DemoClimate(ClimateDevice): self._hvac_mode = hvac_mode self._aux = aux self._current_swing_mode = swing_mode - self._fan_modes = ['On Low', 'On High', 'Auto Low', 'Auto High', 'Off'] + self._fan_modes = ["On Low", "On High", "Auto Low", "Auto High", "Off"] self._hvac_modes = hvac_modes - self._swing_modes = ['Auto', '1', '2', '3', 'Off'] + self._swing_modes = ["Auto", "1", "2", "3", "Off"] self._target_temperature_high = target_temp_high self._target_temperature_low = target_temp_low @@ -238,8 +247,10 @@ class DemoClimate(ClimateDevice): """Set new target temperatures.""" if kwargs.get(ATTR_TEMPERATURE) is not None: self._target_temperature = kwargs.get(ATTR_TEMPERATURE) - if kwargs.get(ATTR_TARGET_TEMP_HIGH) is not None and \ - kwargs.get(ATTR_TARGET_TEMP_LOW) is not None: + if ( + kwargs.get(ATTR_TARGET_TEMP_HIGH) is not None + and kwargs.get(ATTR_TARGET_TEMP_LOW) is not None + ): self._target_temperature_high = kwargs.get(ATTR_TARGET_TEMP_HIGH) self._target_temperature_low = kwargs.get(ATTR_TARGET_TEMP_LOW) self.async_write_ha_state() diff --git a/homeassistant/components/demo/cover.py b/homeassistant/components/demo/cover.py index aa2931a987a..180312eefa3 100644 --- a/homeassistant/components/demo/cover.py +++ b/homeassistant/components/demo/cover.py @@ -2,26 +2,43 @@ from homeassistant.helpers.event import track_utc_time_change from homeassistant.components.cover import ( - ATTR_POSITION, ATTR_TILT_POSITION, SUPPORT_CLOSE, SUPPORT_OPEN, - CoverDevice) + ATTR_POSITION, + ATTR_TILT_POSITION, + SUPPORT_CLOSE, + SUPPORT_OPEN, + CoverDevice, +) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Demo covers.""" - add_entities([ - DemoCover(hass, 'Kitchen Window'), - DemoCover(hass, 'Hall Window', 10), - DemoCover(hass, 'Living Room Window', 70, 50), - DemoCover(hass, 'Garage Door', device_class='garage', - supported_features=(SUPPORT_OPEN | SUPPORT_CLOSE)), - ]) + add_entities( + [ + DemoCover(hass, "Kitchen Window"), + DemoCover(hass, "Hall Window", 10), + DemoCover(hass, "Living Room Window", 70, 50), + DemoCover( + hass, + "Garage Door", + device_class="garage", + supported_features=(SUPPORT_OPEN | SUPPORT_CLOSE), + ), + ] + ) class DemoCover(CoverDevice): """Representation of a demo cover.""" - def __init__(self, hass, name, position=None, tilt_position=None, - device_class=None, supported_features=None): + def __init__( + self, + hass, + name, + position=None, + tilt_position=None, + device_class=None, + supported_features=None, + ): """Initialize the cover.""" self.hass = hass self._name = name @@ -178,7 +195,8 @@ class DemoCover(CoverDevice): """Listen for changes in cover.""" if self._unsub_listener_cover is None: self._unsub_listener_cover = track_utc_time_change( - self.hass, self._time_changed_cover) + self.hass, self._time_changed_cover + ) def _time_changed_cover(self, now): """Track time changes.""" @@ -198,7 +216,8 @@ class DemoCover(CoverDevice): """Listen for changes in cover tilt.""" if self._unsub_listener_cover_tilt is None: self._unsub_listener_cover_tilt = track_utc_time_change( - self.hass, self._time_changed_cover_tilt) + self.hass, self._time_changed_cover_tilt + ) def _time_changed_cover_tilt(self, now): """Track time changes.""" diff --git a/homeassistant/components/demo/device_tracker.py b/homeassistant/components/demo/device_tracker.py index ff038d7009e..fba8095efd6 100644 --- a/homeassistant/components/demo/device_tracker.py +++ b/homeassistant/components/demo/device_tracker.py @@ -6,6 +6,7 @@ from homeassistant.components.device_tracker import DOMAIN def setup_scanner(hass, config, see, discovery_info=None): """Set up the demo tracker.""" + def offset(): """Return random offset.""" return (random.randrange(500, 2000)) / 2e5 * random.choice((-1, 1)) @@ -15,27 +16,26 @@ def setup_scanner(hass, config, see, discovery_info=None): see( dev_id=dev_id, host_name=name, - gps=(hass.config.latitude + offset(), - hass.config.longitude + offset()), + gps=(hass.config.latitude + offset(), hass.config.longitude + offset()), gps_accuracy=random.randrange(50, 150), - battery=random.randrange(10, 90) + battery=random.randrange(10, 90), ) def observe(call=None): """Observe three entities.""" - random_see('demo_paulus', 'Paulus') - random_see('demo_anne_therese', 'Anne Therese') + random_see("demo_paulus", "Paulus") + random_see("demo_anne_therese", "Anne Therese") observe() see( - dev_id='demo_home_boy', - host_name='Home Boy', + dev_id="demo_home_boy", + host_name="Home Boy", gps=[hass.config.latitude - 0.00002, hass.config.longitude + 0.00002], gps_accuracy=20, - battery=53 + battery=53, ) - hass.services.register(DOMAIN, 'demo', observe) + hass.services.register(DOMAIN, "demo", observe) return True diff --git a/homeassistant/components/demo/fan.py b/homeassistant/components/demo/fan.py index 4710bbecfe1..cdeed5dbfec 100644 --- a/homeassistant/components/demo/fan.py +++ b/homeassistant/components/demo/fan.py @@ -2,8 +2,14 @@ from homeassistant.const import STATE_OFF from homeassistant.components.fan import ( - SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, SUPPORT_DIRECTION, SUPPORT_OSCILLATE, - SUPPORT_SET_SPEED, FanEntity) + SPEED_HIGH, + SPEED_LOW, + SPEED_MEDIUM, + SUPPORT_DIRECTION, + SUPPORT_OSCILLATE, + SUPPORT_SET_SPEED, + FanEntity, +) FULL_SUPPORT = SUPPORT_SET_SPEED | SUPPORT_OSCILLATE | SUPPORT_DIRECTION LIMITED_SUPPORT = SUPPORT_SET_SPEED @@ -11,10 +17,12 @@ LIMITED_SUPPORT = SUPPORT_SET_SPEED def setup_platform(hass, config, add_entities_callback, discovery_info=None): """Set up the demo fan platform.""" - add_entities_callback([ - DemoFan(hass, "Living Room Fan", FULL_SUPPORT), - DemoFan(hass, "Ceiling Fan", LIMITED_SUPPORT), - ]) + add_entities_callback( + [ + DemoFan(hass, "Living Room Fan", FULL_SUPPORT), + DemoFan(hass, "Ceiling Fan", LIMITED_SUPPORT), + ] + ) class DemoFan(FanEntity): diff --git a/homeassistant/components/demo/geo_location.py b/homeassistant/components/demo/geo_location.py index 6b91faac92f..6a7aa7ddce1 100644 --- a/homeassistant/components/demo/geo_location.py +++ b/homeassistant/components/demo/geo_location.py @@ -12,17 +12,30 @@ from homeassistant.components.geo_location import GeolocationEvent _LOGGER = logging.getLogger(__name__) AVG_KM_PER_DEGREE = 111.0 -DEFAULT_UNIT_OF_MEASUREMENT = 'km' +DEFAULT_UNIT_OF_MEASUREMENT = "km" DEFAULT_UPDATE_INTERVAL = timedelta(minutes=1) MAX_RADIUS_IN_KM = 50 NUMBER_OF_DEMO_DEVICES = 5 -EVENT_NAMES = ["Bushfire", "Hazard Reduction", "Grass Fire", "Burn off", - "Structure Fire", "Fire Alarm", "Thunderstorm", "Tornado", - "Cyclone", "Waterspout", "Dust Storm", "Blizzard", "Ice Storm", - "Earthquake", "Tsunami"] +EVENT_NAMES = [ + "Bushfire", + "Hazard Reduction", + "Grass Fire", + "Burn off", + "Structure Fire", + "Fire Alarm", + "Thunderstorm", + "Tornado", + "Cyclone", + "Waterspout", + "Dust Storm", + "Blizzard", + "Ice Storm", + "Earthquake", + "Tsunami", +] -SOURCE = 'demo' +SOURCE = "demo" def setup_platform(hass, config, add_entities, discovery_info=None): @@ -47,24 +60,26 @@ class DemoManager: home_longitude = self._hass.config.longitude # Approx. 111km per degree (north-south). - radius_in_degrees = random.random() * MAX_RADIUS_IN_KM / \ - AVG_KM_PER_DEGREE + radius_in_degrees = random.random() * MAX_RADIUS_IN_KM / AVG_KM_PER_DEGREE radius_in_km = radius_in_degrees * AVG_KM_PER_DEGREE angle = random.random() * 2 * pi # Compute coordinates based on radius and angle. Adjust longitude value # based on HA's latitude. latitude = home_latitude + radius_in_degrees * sin(angle) - longitude = home_longitude + radius_in_degrees * cos(angle) / \ - cos(radians(home_latitude)) + longitude = home_longitude + radius_in_degrees * cos(angle) / cos( + radians(home_latitude) + ) event_name = random.choice(EVENT_NAMES) - return DemoGeolocationEvent(event_name, radius_in_km, latitude, - longitude, DEFAULT_UNIT_OF_MEASUREMENT) + return DemoGeolocationEvent( + event_name, radius_in_km, latitude, longitude, DEFAULT_UNIT_OF_MEASUREMENT + ) def _init_regular_updates(self): """Schedule regular updates based on configured time interval.""" - track_time_interval(self._hass, lambda now: self._update(), - DEFAULT_UPDATE_INTERVAL) + track_time_interval( + self._hass, lambda now: self._update(), DEFAULT_UPDATE_INTERVAL + ) def _update(self, count=1): """Remove events and add new random events.""" @@ -89,8 +104,7 @@ class DemoManager: class DemoGeolocationEvent(GeolocationEvent): """This represents a demo geolocation event.""" - def __init__(self, name, distance, latitude, longitude, - unit_of_measurement): + def __init__(self, name, distance, latitude, longitude, unit_of_measurement): """Initialize entity with data provided.""" self._name = name self._distance = distance diff --git a/homeassistant/components/demo/image_processing.py b/homeassistant/components/demo/image_processing.py index acb97e4ebd6..348045e47b2 100644 --- a/homeassistant/components/demo/image_processing.py +++ b/homeassistant/components/demo/image_processing.py @@ -1,19 +1,24 @@ """Support for the demo image processing.""" from homeassistant.components.image_processing import ( - ImageProcessingFaceEntity, ATTR_CONFIDENCE, ATTR_NAME, ATTR_AGE, - ATTR_GENDER - ) + ImageProcessingFaceEntity, + ATTR_CONFIDENCE, + ATTR_NAME, + ATTR_AGE, + ATTR_GENDER, +) from homeassistant.components.openalpr_local.image_processing import ( - ImageProcessingAlprEntity) + ImageProcessingAlprEntity, +) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the demo image processing platform.""" - add_entities([ - DemoImageProcessingAlpr('camera.demo_camera', "Demo Alpr"), - DemoImageProcessingFace( - 'camera.demo_camera', "Demo Face") - ]) + add_entities( + [ + DemoImageProcessingAlpr("camera.demo_camera", "Demo Alpr"), + DemoImageProcessingFace("camera.demo_camera", "Demo Face"), + ] + ) class DemoImageProcessingAlpr(ImageProcessingAlprEntity): @@ -44,10 +49,10 @@ class DemoImageProcessingAlpr(ImageProcessingAlprEntity): def process_image(self, image): """Process image.""" demo_data = { - 'AC3829': 98.3, - 'BE392034': 95.5, - 'CD02394': 93.4, - 'DF923043': 90.8 + "AC3829": 98.3, + "BE392034": 95.5, + "CD02394": 93.4, + "DF923043": 90.8, } self.process_plates(demo_data, 1) @@ -83,19 +88,12 @@ class DemoImageProcessingFace(ImageProcessingFaceEntity): demo_data = [ { ATTR_CONFIDENCE: 98.34, - ATTR_NAME: 'Hans', + ATTR_NAME: "Hans", ATTR_AGE: 16.0, - ATTR_GENDER: 'male', - }, - { - ATTR_NAME: 'Helena', - ATTR_AGE: 28.0, - ATTR_GENDER: 'female', - }, - { - ATTR_CONFIDENCE: 62.53, - ATTR_NAME: 'Luna', + ATTR_GENDER: "male", }, + {ATTR_NAME: "Helena", ATTR_AGE: 28.0, ATTR_GENDER: "female"}, + {ATTR_CONFIDENCE: 62.53, ATTR_NAME: "Luna"}, ] self.process_faces(demo_data, 4) diff --git a/homeassistant/components/demo/light.py b/homeassistant/components/demo/light.py index 285866c6eb8..f8b1b511511 100644 --- a/homeassistant/components/demo/light.py +++ b/homeassistant/components/demo/light.py @@ -2,41 +2,68 @@ import random from homeassistant.components.light import ( - ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_HS_COLOR, - ATTR_WHITE_VALUE, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, - SUPPORT_EFFECT, SUPPORT_WHITE_VALUE, Light) + ATTR_BRIGHTNESS, + ATTR_COLOR_TEMP, + ATTR_EFFECT, + ATTR_HS_COLOR, + ATTR_WHITE_VALUE, + SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, + SUPPORT_COLOR_TEMP, + SUPPORT_EFFECT, + SUPPORT_WHITE_VALUE, + Light, +) -LIGHT_COLORS = [ - (56, 86), - (345, 75), -] +LIGHT_COLORS = [(56, 86), (345, 75)] -LIGHT_EFFECT_LIST = ['rainbow', 'none'] +LIGHT_EFFECT_LIST = ["rainbow", "none"] LIGHT_TEMPS = [240, 380] -SUPPORT_DEMO = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_EFFECT | - SUPPORT_COLOR | SUPPORT_WHITE_VALUE) +SUPPORT_DEMO = ( + SUPPORT_BRIGHTNESS + | SUPPORT_COLOR_TEMP + | SUPPORT_EFFECT + | SUPPORT_COLOR + | SUPPORT_WHITE_VALUE +) def setup_platform(hass, config, add_entities_callback, discovery_info=None): """Set up the demo light platform.""" - add_entities_callback([ - DemoLight(1, "Bed Light", False, True, effect_list=LIGHT_EFFECT_LIST, - effect=LIGHT_EFFECT_LIST[0]), - DemoLight(2, "Ceiling Lights", True, True, - LIGHT_COLORS[0], LIGHT_TEMPS[1]), - DemoLight(3, "Kitchen Lights", True, True, - LIGHT_COLORS[1], LIGHT_TEMPS[0]) - ]) + add_entities_callback( + [ + DemoLight( + 1, + "Bed Light", + False, + True, + effect_list=LIGHT_EFFECT_LIST, + effect=LIGHT_EFFECT_LIST[0], + ), + DemoLight(2, "Ceiling Lights", True, True, LIGHT_COLORS[0], LIGHT_TEMPS[1]), + DemoLight(3, "Kitchen Lights", True, True, LIGHT_COLORS[1], LIGHT_TEMPS[0]), + ] + ) class DemoLight(Light): """Representation of a demo light.""" - def __init__(self, unique_id, name, state, available=False, hs_color=None, - ct=None, brightness=180, white=200, effect_list=None, - effect=None): + def __init__( + self, + unique_id, + name, + state, + available=False, + hs_color=None, + ct=None, + brightness=180, + white=200, + effect_list=None, + effect=None, + ): """Initialize the light.""" self._unique_id = unique_id self._name = name diff --git a/homeassistant/components/demo/lock.py b/homeassistant/components/demo/lock.py index cd15a434138..d8fb244b9bc 100644 --- a/homeassistant/components/demo/lock.py +++ b/homeassistant/components/demo/lock.py @@ -6,11 +6,13 @@ from homeassistant.components.lock import SUPPORT_OPEN, LockDevice def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Demo lock platform.""" - add_entities([ - DemoLock('Front Door', STATE_LOCKED), - DemoLock('Kitchen Door', STATE_UNLOCKED), - DemoLock('Openable Lock', STATE_LOCKED, True) - ]) + add_entities( + [ + DemoLock("Front Door", STATE_LOCKED), + DemoLock("Kitchen Door", STATE_UNLOCKED), + DemoLock("Openable Lock", STATE_LOCKED, True), + ] + ) class DemoLock(LockDevice): diff --git a/homeassistant/components/demo/mailbox.py b/homeassistant/components/demo/mailbox.py index fcffc44eefb..77030623c9d 100644 --- a/homeassistant/components/demo/mailbox.py +++ b/homeassistant/components/demo/mailbox.py @@ -3,13 +3,12 @@ from hashlib import sha1 import logging import os -from homeassistant.components.mailbox import ( - CONTENT_TYPE_MPEG, Mailbox, StreamError) +from homeassistant.components.mailbox import CONTENT_TYPE_MPEG, Mailbox, StreamError from homeassistant.util import dt _LOGGER = logging.getLogger(__name__) -MAILBOX_NAME = 'DemoMailbox' +MAILBOX_NAME = "DemoMailbox" async def async_get_handler(hass, config, discovery_info=None): @@ -26,19 +25,17 @@ class DemoMailbox(Mailbox): self._messages = {} txt = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " for idx in range(0, 10): - msgtime = int(dt.as_timestamp( - dt.utcnow()) - 3600 * 24 * (10 - idx)) - msgtxt = "Message {}. {}".format( - idx + 1, txt * (1 + idx * (idx % 2))) - msgsha = sha1(msgtxt.encode('utf-8')).hexdigest() + msgtime = int(dt.as_timestamp(dt.utcnow()) - 3600 * 24 * (10 - idx)) + msgtxt = "Message {}. {}".format(idx + 1, txt * (1 + idx * (idx % 2))) + msgsha = sha1(msgtxt.encode("utf-8")).hexdigest() msg = { - 'info': { - 'origtime': msgtime, - 'callerid': 'John Doe <212-555-1212>', - 'duration': '10', + "info": { + "origtime": msgtime, + "callerid": "John Doe <212-555-1212>", + "duration": "10", }, - 'text': msgtxt, - 'sha': msgsha, + "text": msgtxt, + "sha": msgsha, } self._messages[msgsha] = msg @@ -62,16 +59,17 @@ class DemoMailbox(Mailbox): if msgid not in self._messages: raise StreamError("Message not found") - audio_path = os.path.join( - os.path.dirname(__file__), 'tts.mp3') - with open(audio_path, 'rb') as file: + audio_path = os.path.join(os.path.dirname(__file__), "tts.mp3") + with open(audio_path, "rb") as file: return file.read() async def async_get_messages(self): """Return a list of the current messages.""" - return sorted(self._messages.values(), - key=lambda item: item['info']['origtime'], - reverse=True) + return sorted( + self._messages.values(), + key=lambda item: item["info"]["origtime"], + reverse=True, + ) def async_delete(self, msgid): """Delete the specified messages.""" diff --git a/homeassistant/components/demo/media_player.py b/homeassistant/components/demo/media_player.py index e293632b71e..e3f69be3020 100644 --- a/homeassistant/components/demo/media_player.py +++ b/homeassistant/components/demo/media_player.py @@ -1,50 +1,92 @@ """Demo implementation of the media player.""" from homeassistant.components.media_player import MediaPlayerDevice from homeassistant.components.media_player.const import ( - MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, - SUPPORT_CLEAR_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, - SUPPORT_SELECT_SOUND_MODE, SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET, - SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP) + MEDIA_TYPE_MOVIE, + MEDIA_TYPE_MUSIC, + MEDIA_TYPE_TVSHOW, + SUPPORT_CLEAR_PLAYLIST, + SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, + SUPPORT_PLAY, + SUPPORT_PLAY_MEDIA, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_SEEK, + SUPPORT_SELECT_SOUND_MODE, + SUPPORT_SELECT_SOURCE, + SUPPORT_SHUFFLE_SET, + SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, + SUPPORT_VOLUME_MUTE, + SUPPORT_VOLUME_SET, + SUPPORT_VOLUME_STEP, +) from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING import homeassistant.util.dt as dt_util def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the media player demo platform.""" - add_entities([ - DemoYoutubePlayer( - 'Living Room', 'eyU3bRy2x44', - '♥♥ The Best Fireplace Video (3 hours)', 300), - DemoYoutubePlayer( - 'Bedroom', 'kxopViU98Xo', 'Epic sax guy 10 hours', 360000), - DemoMusicPlayer(), DemoTVShowPlayer(), - ]) + add_entities( + [ + DemoYoutubePlayer( + "Living Room", + "eyU3bRy2x44", + "♥♥ The Best Fireplace Video (3 hours)", + 300, + ), + DemoYoutubePlayer( + "Bedroom", "kxopViU98Xo", "Epic sax guy 10 hours", 360000 + ), + DemoMusicPlayer(), + DemoTVShowPlayer(), + ] + ) -YOUTUBE_COVER_URL_FORMAT = 'https://img.youtube.com/vi/{}/hqdefault.jpg' -SOUND_MODE_LIST = ['Dummy Music', 'Dummy Movie'] -DEFAULT_SOUND_MODE = 'Dummy Music' +YOUTUBE_COVER_URL_FORMAT = "https://img.youtube.com/vi/{}/hqdefault.jpg" +SOUND_MODE_LIST = ["Dummy Music", "Dummy Movie"] +DEFAULT_SOUND_MODE = "Dummy Music" -YOUTUBE_PLAYER_SUPPORT = \ - SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ - SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PLAY_MEDIA | SUPPORT_PLAY | \ - SUPPORT_SHUFFLE_SET | SUPPORT_SELECT_SOUND_MODE | SUPPORT_SELECT_SOURCE | \ - SUPPORT_SEEK +YOUTUBE_PLAYER_SUPPORT = ( + SUPPORT_PAUSE + | SUPPORT_VOLUME_SET + | SUPPORT_VOLUME_MUTE + | SUPPORT_TURN_ON + | SUPPORT_TURN_OFF + | SUPPORT_PLAY_MEDIA + | SUPPORT_PLAY + | SUPPORT_SHUFFLE_SET + | SUPPORT_SELECT_SOUND_MODE + | SUPPORT_SELECT_SOURCE + | SUPPORT_SEEK +) -MUSIC_PLAYER_SUPPORT = \ - SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ - SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_CLEAR_PLAYLIST | \ - SUPPORT_PLAY | SUPPORT_SHUFFLE_SET | SUPPORT_VOLUME_STEP | \ - SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \ - SUPPORT_SELECT_SOUND_MODE +MUSIC_PLAYER_SUPPORT = ( + SUPPORT_PAUSE + | SUPPORT_VOLUME_SET + | SUPPORT_VOLUME_MUTE + | SUPPORT_TURN_ON + | SUPPORT_TURN_OFF + | SUPPORT_CLEAR_PLAYLIST + | SUPPORT_PLAY + | SUPPORT_SHUFFLE_SET + | SUPPORT_VOLUME_STEP + | SUPPORT_PREVIOUS_TRACK + | SUPPORT_NEXT_TRACK + | SUPPORT_SELECT_SOUND_MODE +) -NETFLIX_PLAYER_SUPPORT = \ - SUPPORT_PAUSE | SUPPORT_TURN_ON | SUPPORT_TURN_OFF | \ - SUPPORT_SELECT_SOURCE | SUPPORT_PLAY | SUPPORT_SHUFFLE_SET | \ - SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \ - SUPPORT_SELECT_SOUND_MODE +NETFLIX_PLAYER_SUPPORT = ( + SUPPORT_PAUSE + | SUPPORT_TURN_ON + | SUPPORT_TURN_OFF + | SUPPORT_SELECT_SOURCE + | SUPPORT_PLAY + | SUPPORT_SHUFFLE_SET + | SUPPORT_PREVIOUS_TRACK + | SUPPORT_NEXT_TRACK + | SUPPORT_SELECT_SOUND_MODE +) class AbstractDemoPlayer(MediaPlayerDevice): @@ -170,7 +212,7 @@ class DemoYoutubePlayer(AbstractDemoPlayer): self.youtube_id = youtube_id self._media_title = media_title self._duration = duration - self._progress = int(duration * .15) + self._progress = int(duration * 0.15) self._progress_updated_at = dt_util.utcnow() @property @@ -217,8 +259,7 @@ class DemoYoutubePlayer(AbstractDemoPlayer): position = self._progress if self._player_state == STATE_PLAYING: - position += (dt_util.utcnow() - - self._progress_updated_at).total_seconds() + position += (dt_util.utcnow() - self._progress_updated_at).total_seconds() return position @@ -249,36 +290,37 @@ class DemoMusicPlayer(AbstractDemoPlayer): # We only implement the methods that we support tracks = [ - ('Technohead', 'I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)'), - ('Paul Elstak', 'Luv U More'), - ('Dune', 'Hardcore Vibes'), - ('Nakatomi', 'Children Of The Night'), - ('Party Animals', - 'Have You Ever Been Mellow? (Flamman & Abraxas Radio Mix)'), - ('Rob G.*', 'Ecstasy, You Got What I Need'), - ('Lipstick', "I'm A Raver"), - ('4 Tune Fairytales', 'My Little Fantasy (Radio Edit)'), - ('Prophet', "The Big Boys Don't Cry"), - ('Lovechild', 'All Out Of Love (DJ Weirdo & Sim Remix)'), - ('Stingray & Sonic Driver', 'Cold As Ice (El Bruto Remix)'), - ('Highlander', 'Hold Me Now (Bass-D & King Matthew Remix)'), - ('Juggernaut', 'Ruffneck Rules Da Artcore Scene (12" Edit)'), - ('Diss Reaction', 'Jiiieehaaaa '), - ('Flamman And Abraxas', 'Good To Go (Radio Mix)'), - ('Critical Mass', 'Dancing Together'), - ('Charly Lownoise & Mental Theo', - 'Ultimate Sex Track (Bass-D & King Matthew Remix)'), + ("Technohead", "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)"), + ("Paul Elstak", "Luv U More"), + ("Dune", "Hardcore Vibes"), + ("Nakatomi", "Children Of The Night"), + ("Party Animals", "Have You Ever Been Mellow? (Flamman & Abraxas Radio Mix)"), + ("Rob G.*", "Ecstasy, You Got What I Need"), + ("Lipstick", "I'm A Raver"), + ("4 Tune Fairytales", "My Little Fantasy (Radio Edit)"), + ("Prophet", "The Big Boys Don't Cry"), + ("Lovechild", "All Out Of Love (DJ Weirdo & Sim Remix)"), + ("Stingray & Sonic Driver", "Cold As Ice (El Bruto Remix)"), + ("Highlander", "Hold Me Now (Bass-D & King Matthew Remix)"), + ("Juggernaut", 'Ruffneck Rules Da Artcore Scene (12" Edit)'), + ("Diss Reaction", "Jiiieehaaaa "), + ("Flamman And Abraxas", "Good To Go (Radio Mix)"), + ("Critical Mass", "Dancing Together"), + ( + "Charly Lownoise & Mental Theo", + "Ultimate Sex Track (Bass-D & King Matthew Remix)", + ), ] def __init__(self): """Initialize the demo device.""" - super().__init__('Walkman') + super().__init__("Walkman") self._cur_track = 0 @property def media_content_id(self): """Return the content ID of current playing media.""" - return 'bounzz-1' + return "bounzz-1" @property def media_content_type(self): @@ -293,8 +335,7 @@ class DemoMusicPlayer(AbstractDemoPlayer): @property def media_image_url(self): """Return the image url of current playing media.""" - return 'https://graph.facebook.com/v2.5/107771475912710/' \ - 'picture?type=large' + return "https://graph.facebook.com/v2.5/107771475912710/" "picture?type=large" @property def media_title(self): @@ -348,15 +389,15 @@ class DemoTVShowPlayer(AbstractDemoPlayer): def __init__(self): """Initialize the demo device.""" - super().__init__('Lounge room') + super().__init__("Lounge room") self._cur_episode = 1 self._episode_count = 13 - self._source = 'dvd' + self._source = "dvd" @property def media_content_id(self): """Return the content ID of current playing media.""" - return 'house-of-cards-1' + return "house-of-cards-1" @property def media_content_type(self): @@ -371,17 +412,17 @@ class DemoTVShowPlayer(AbstractDemoPlayer): @property def media_image_url(self): """Return the image url of current playing media.""" - return 'https://graph.facebook.com/v2.5/HouseofCards/picture?width=400' + return "https://graph.facebook.com/v2.5/HouseofCards/picture?width=400" @property def media_title(self): """Return the title of current playing media.""" - return 'Chapter {}'.format(self._cur_episode) + return "Chapter {}".format(self._cur_episode) @property def media_series_title(self): """Return the series title of current playing media (TV Show only).""" - return 'House of Cards' + return "House of Cards" @property def media_season(self): diff --git a/homeassistant/components/demo/notify.py b/homeassistant/components/demo/notify.py index 92aaea6882d..f390c042ce4 100644 --- a/homeassistant/components/demo/notify.py +++ b/homeassistant/components/demo/notify.py @@ -23,5 +23,5 @@ class DemoNotificationService(BaseNotificationService): def send_message(self, message="", **kwargs): """Send a message to a user.""" - kwargs['message'] = message + kwargs["message"] = message self.hass.bus.fire(EVENT_NOTIFY, kwargs) diff --git a/homeassistant/components/demo/remote.py b/homeassistant/components/demo/remote.py index b28330fdc67..4a363781e2e 100644 --- a/homeassistant/components/demo/remote.py +++ b/homeassistant/components/demo/remote.py @@ -5,10 +5,12 @@ from homeassistant.const import DEVICE_DEFAULT_NAME def setup_platform(hass, config, add_entities_callback, discovery_info=None): """Set up the demo remotes.""" - add_entities_callback([ - DemoRemote('Remote One', False, None), - DemoRemote('Remote Two', True, 'mdi:remote'), - ]) + add_entities_callback( + [ + DemoRemote("Remote One", False, None), + DemoRemote("Remote Two", True, "mdi:remote"), + ] + ) class DemoRemote(RemoteDevice): @@ -45,7 +47,7 @@ class DemoRemote(RemoteDevice): def device_state_attributes(self): """Return device state attributes.""" if self._last_command_sent is not None: - return {'last_command_sent': self._last_command_sent} + return {"last_command_sent": self._last_command_sent} def turn_on(self, **kwargs): """Turn the remote on.""" diff --git a/homeassistant/components/demo/sensor.py b/homeassistant/components/demo/sensor.py index ea35c729517..78bd88b42cf 100644 --- a/homeassistant/components/demo/sensor.py +++ b/homeassistant/components/demo/sensor.py @@ -1,24 +1,29 @@ """Demo platform that has a couple of fake sensors.""" from homeassistant.const import ( - ATTR_BATTERY_LEVEL, TEMP_CELSIUS, DEVICE_CLASS_HUMIDITY, - DEVICE_CLASS_TEMPERATURE) + ATTR_BATTERY_LEVEL, + TEMP_CELSIUS, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, +) from homeassistant.helpers.entity import Entity def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Demo sensors.""" - add_entities([ - DemoSensor('Outside Temperature', 15.6, DEVICE_CLASS_TEMPERATURE, - TEMP_CELSIUS, 12), - DemoSensor('Outside Humidity', 54, DEVICE_CLASS_HUMIDITY, '%', None), - ]) + add_entities( + [ + DemoSensor( + "Outside Temperature", 15.6, DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, 12 + ), + DemoSensor("Outside Humidity", 54, DEVICE_CLASS_HUMIDITY, "%", None), + ] + ) class DemoSensor(Entity): """Representation of a Demo sensor.""" - def __init__(self, name, state, device_class, - unit_of_measurement, battery): + def __init__(self, name, state, device_class, unit_of_measurement, battery): """Initialize the sensor.""" self._name = name self._state = state @@ -55,6 +60,4 @@ class DemoSensor(Entity): def device_state_attributes(self): """Return the state attributes.""" if self._battery: - return { - ATTR_BATTERY_LEVEL: self._battery, - } + return {ATTR_BATTERY_LEVEL: self._battery} diff --git a/homeassistant/components/demo/switch.py b/homeassistant/components/demo/switch.py index e7a3b1741a2..65860867ed6 100644 --- a/homeassistant/components/demo/switch.py +++ b/homeassistant/components/demo/switch.py @@ -5,10 +5,12 @@ from homeassistant.const import DEVICE_DEFAULT_NAME def setup_platform(hass, config, add_entities_callback, discovery_info=None): """Set up the demo switches.""" - add_entities_callback([ - DemoSwitch('Decorative Lights', True, None, True), - DemoSwitch('AC', False, 'mdi:air-conditioner', False) - ]) + add_entities_callback( + [ + DemoSwitch("Decorative Lights", True, None, True), + DemoSwitch("AC", False, "mdi:air-conditioner", False), + ] + ) class DemoSwitch(SwitchDevice): diff --git a/homeassistant/components/demo/tts.py b/homeassistant/components/demo/tts.py index bf18bc1630f..ae083e50454 100644 --- a/homeassistant/components/demo/tts.py +++ b/homeassistant/components/demo/tts.py @@ -5,15 +5,13 @@ import voluptuous as vol from homeassistant.components.tts import CONF_LANG, PLATFORM_SCHEMA, Provider -SUPPORT_LANGUAGES = [ - 'en', 'de' -] +SUPPORT_LANGUAGES = ["en", "de"] -DEFAULT_LANG = 'en' +DEFAULT_LANG = "en" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_LANG, default=DEFAULT_LANG): vol.In(SUPPORT_LANGUAGES), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Optional(CONF_LANG, default=DEFAULT_LANG): vol.In(SUPPORT_LANGUAGES)} +) def get_engine(hass, config): @@ -27,7 +25,7 @@ class DemoProvider(Provider): def __init__(self, lang): """Initialize demo provider.""" self._lang = lang - self.name = 'Demo' + self.name = "Demo" @property def default_language(self): @@ -42,15 +40,15 @@ class DemoProvider(Provider): @property def supported_options(self): """Return list of supported options like voice, emotionen.""" - return ['voice', 'age'] + return ["voice", "age"] def get_tts_audio(self, message, language, options=None): """Load TTS from demo.""" - filename = os.path.join(os.path.dirname(__file__), 'tts.mp3') + filename = os.path.join(os.path.dirname(__file__), "tts.mp3") try: - with open(filename, 'rb') as voice: + with open(filename, "rb") as voice: data = voice.read() except OSError: return (None, None) - return ('mp3', data) + return ("mp3", data) diff --git a/homeassistant/components/demo/vacuum.py b/homeassistant/components/demo/vacuum.py index dfb9c4e943e..ffd3e768b11 100644 --- a/homeassistant/components/demo/vacuum.py +++ b/homeassistant/components/demo/vacuum.py @@ -2,51 +2,92 @@ import logging from homeassistant.components.vacuum import ( - ATTR_CLEANED_AREA, STATE_CLEANING, STATE_DOCKED, STATE_IDLE, STATE_PAUSED, - STATE_RETURNING, SUPPORT_BATTERY, SUPPORT_CLEAN_SPOT, SUPPORT_FAN_SPEED, - SUPPORT_LOCATE, SUPPORT_PAUSE, SUPPORT_RETURN_HOME, SUPPORT_SEND_COMMAND, - SUPPORT_START, SUPPORT_STATE, SUPPORT_STATUS, SUPPORT_STOP, - SUPPORT_TURN_OFF, SUPPORT_TURN_ON, StateVacuumDevice, VacuumDevice) + ATTR_CLEANED_AREA, + STATE_CLEANING, + STATE_DOCKED, + STATE_IDLE, + STATE_PAUSED, + STATE_RETURNING, + SUPPORT_BATTERY, + SUPPORT_CLEAN_SPOT, + SUPPORT_FAN_SPEED, + SUPPORT_LOCATE, + SUPPORT_PAUSE, + SUPPORT_RETURN_HOME, + SUPPORT_SEND_COMMAND, + SUPPORT_START, + SUPPORT_STATE, + SUPPORT_STATUS, + SUPPORT_STOP, + SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, + StateVacuumDevice, + VacuumDevice, +) _LOGGER = logging.getLogger(__name__) SUPPORT_MINIMAL_SERVICES = SUPPORT_TURN_ON | SUPPORT_TURN_OFF -SUPPORT_BASIC_SERVICES = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | \ - SUPPORT_STATUS | SUPPORT_BATTERY +SUPPORT_BASIC_SERVICES = ( + SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_STATUS | SUPPORT_BATTERY +) -SUPPORT_MOST_SERVICES = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_STOP | \ - SUPPORT_RETURN_HOME | SUPPORT_STATUS | SUPPORT_BATTERY +SUPPORT_MOST_SERVICES = ( + SUPPORT_TURN_ON + | SUPPORT_TURN_OFF + | SUPPORT_STOP + | SUPPORT_RETURN_HOME + | SUPPORT_STATUS + | SUPPORT_BATTERY +) -SUPPORT_ALL_SERVICES = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PAUSE | \ - SUPPORT_STOP | SUPPORT_RETURN_HOME | \ - SUPPORT_FAN_SPEED | SUPPORT_SEND_COMMAND | \ - SUPPORT_LOCATE | SUPPORT_STATUS | SUPPORT_BATTERY | \ - SUPPORT_CLEAN_SPOT +SUPPORT_ALL_SERVICES = ( + SUPPORT_TURN_ON + | SUPPORT_TURN_OFF + | SUPPORT_PAUSE + | SUPPORT_STOP + | SUPPORT_RETURN_HOME + | SUPPORT_FAN_SPEED + | SUPPORT_SEND_COMMAND + | SUPPORT_LOCATE + | SUPPORT_STATUS + | SUPPORT_BATTERY + | SUPPORT_CLEAN_SPOT +) -SUPPORT_STATE_SERVICES = SUPPORT_STATE | SUPPORT_PAUSE | SUPPORT_STOP | \ - SUPPORT_RETURN_HOME | SUPPORT_FAN_SPEED | \ - SUPPORT_BATTERY | SUPPORT_CLEAN_SPOT | SUPPORT_START +SUPPORT_STATE_SERVICES = ( + SUPPORT_STATE + | SUPPORT_PAUSE + | SUPPORT_STOP + | SUPPORT_RETURN_HOME + | SUPPORT_FAN_SPEED + | SUPPORT_BATTERY + | SUPPORT_CLEAN_SPOT + | SUPPORT_START +) -FAN_SPEEDS = ['min', 'medium', 'high', 'max'] -DEMO_VACUUM_COMPLETE = '0_Ground_floor' -DEMO_VACUUM_MOST = '1_First_floor' -DEMO_VACUUM_BASIC = '2_Second_floor' -DEMO_VACUUM_MINIMAL = '3_Third_floor' -DEMO_VACUUM_NONE = '4_Fourth_floor' -DEMO_VACUUM_STATE = '5_Fifth_floor' +FAN_SPEEDS = ["min", "medium", "high", "max"] +DEMO_VACUUM_COMPLETE = "0_Ground_floor" +DEMO_VACUUM_MOST = "1_First_floor" +DEMO_VACUUM_BASIC = "2_Second_floor" +DEMO_VACUUM_MINIMAL = "3_Third_floor" +DEMO_VACUUM_NONE = "4_Fourth_floor" +DEMO_VACUUM_STATE = "5_Fifth_floor" def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Demo vacuums.""" - add_entities([ - DemoVacuum(DEMO_VACUUM_COMPLETE, SUPPORT_ALL_SERVICES), - DemoVacuum(DEMO_VACUUM_MOST, SUPPORT_MOST_SERVICES), - DemoVacuum(DEMO_VACUUM_BASIC, SUPPORT_BASIC_SERVICES), - DemoVacuum(DEMO_VACUUM_MINIMAL, SUPPORT_MINIMAL_SERVICES), - DemoVacuum(DEMO_VACUUM_NONE, 0), - StateDemoVacuum(DEMO_VACUUM_STATE), - ]) + add_entities( + [ + DemoVacuum(DEMO_VACUUM_COMPLETE, SUPPORT_ALL_SERVICES), + DemoVacuum(DEMO_VACUUM_MOST, SUPPORT_MOST_SERVICES), + DemoVacuum(DEMO_VACUUM_BASIC, SUPPORT_BASIC_SERVICES), + DemoVacuum(DEMO_VACUUM_MINIMAL, SUPPORT_MINIMAL_SERVICES), + DemoVacuum(DEMO_VACUUM_NONE, 0), + StateDemoVacuum(DEMO_VACUUM_STATE), + ] + ) class DemoVacuum(VacuumDevice): @@ -57,7 +98,7 @@ class DemoVacuum(VacuumDevice): self._name = name self._supported_features = supported_features self._state = False - self._status = 'Charging' + self._status = "Charging" self._fan_speed = FAN_SPEEDS[1] self._cleaned_area = 0 self._battery_level = 100 @@ -125,7 +166,7 @@ class DemoVacuum(VacuumDevice): self._state = True self._cleaned_area += 5.32 self._battery_level -= 2 - self._status = 'Cleaning' + self._status = "Cleaning" self.schedule_update_ha_state() def turn_off(self, **kwargs): @@ -134,7 +175,7 @@ class DemoVacuum(VacuumDevice): return self._state = False - self._status = 'Charging' + self._status = "Charging" self.schedule_update_ha_state() def stop(self, **kwargs): @@ -143,7 +184,7 @@ class DemoVacuum(VacuumDevice): return self._state = False - self._status = 'Stopping the current task' + self._status = "Stopping the current task" self.schedule_update_ha_state() def clean_spot(self, **kwargs): @@ -172,11 +213,11 @@ class DemoVacuum(VacuumDevice): self._state = not self._state if self._state: - self._status = 'Resuming the current task' + self._status = "Resuming the current task" self._cleaned_area += 1.32 self._battery_level -= 1 else: - self._status = 'Pausing the current task' + self._status = "Pausing the current task" self.schedule_update_ha_state() def set_fan_speed(self, fan_speed, **kwargs): @@ -194,7 +235,7 @@ class DemoVacuum(VacuumDevice): return self._state = False - self._status = 'Returning home...' + self._status = "Returning home..." self._battery_level += 5 self.schedule_update_ha_state() @@ -203,7 +244,7 @@ class DemoVacuum(VacuumDevice): if self.supported_features & SUPPORT_SEND_COMMAND == 0: return - self._status = 'Executing {}({})'.format(command, params) + self._status = "Executing {}({})".format(command, params) self._state = True self.schedule_update_ha_state() diff --git a/homeassistant/components/demo/water_heater.py b/homeassistant/components/demo/water_heater.py index 6ee17bf0088..d1d53e058c6 100644 --- a/homeassistant/components/demo/water_heater.py +++ b/homeassistant/components/demo/water_heater.py @@ -2,34 +2,38 @@ from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.components.water_heater import ( - SUPPORT_AWAY_MODE, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, - WaterHeaterDevice) + SUPPORT_AWAY_MODE, + SUPPORT_OPERATION_MODE, + SUPPORT_TARGET_TEMPERATURE, + WaterHeaterDevice, +) -SUPPORT_FLAGS_HEATER = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE | - SUPPORT_AWAY_MODE) +SUPPORT_FLAGS_HEATER = ( + SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE | SUPPORT_AWAY_MODE +) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Demo water_heater devices.""" - add_entities([ - DemoWaterHeater( - 'Demo Water Heater', 119, TEMP_FAHRENHEIT, False, 'eco'), - DemoWaterHeater( - 'Demo Water Heater Celsius', 45, TEMP_CELSIUS, True, 'eco'), - ]) + add_entities( + [ + DemoWaterHeater("Demo Water Heater", 119, TEMP_FAHRENHEIT, False, "eco"), + DemoWaterHeater("Demo Water Heater Celsius", 45, TEMP_CELSIUS, True, "eco"), + ] + ) class DemoWaterHeater(WaterHeaterDevice): """Representation of a demo water_heater device.""" - def __init__(self, name, target_temperature, unit_of_measurement, - away, current_operation): + def __init__( + self, name, target_temperature, unit_of_measurement, away, current_operation + ): """Initialize the water_heater device.""" self._name = name self._support_flags = SUPPORT_FLAGS_HEATER if target_temperature is not None: - self._support_flags = \ - self._support_flags | SUPPORT_TARGET_TEMPERATURE + self._support_flags = self._support_flags | SUPPORT_TARGET_TEMPERATURE if away is not None: self._support_flags = self._support_flags | SUPPORT_AWAY_MODE if current_operation is not None: @@ -38,9 +42,15 @@ class DemoWaterHeater(WaterHeaterDevice): self._unit_of_measurement = unit_of_measurement self._away = away self._current_operation = current_operation - self._operation_list = ['eco', 'electric', 'performance', - 'high_demand', 'heat_pump', 'gas', - 'off'] + self._operation_list = [ + "eco", + "electric", + "performance", + "high_demand", + "heat_pump", + "gas", + "off", + ] @property def supported_features(self): diff --git a/homeassistant/components/demo/weather.py b/homeassistant/components/demo/weather.py index d20e91b1f93..b81a2193bb5 100644 --- a/homeassistant/components/demo/weather.py +++ b/homeassistant/components/demo/weather.py @@ -2,49 +2,91 @@ from datetime import datetime, timedelta from homeassistant.components.weather import ( - ATTR_FORECAST_CONDITION, ATTR_FORECAST_PRECIPITATION, ATTR_FORECAST_TEMP, - ATTR_FORECAST_TEMP_LOW, ATTR_FORECAST_TIME, WeatherEntity) + ATTR_FORECAST_CONDITION, + ATTR_FORECAST_PRECIPITATION, + ATTR_FORECAST_TEMP, + ATTR_FORECAST_TEMP_LOW, + ATTR_FORECAST_TIME, + WeatherEntity, +) from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT CONDITION_CLASSES = { - 'cloudy': [], - 'fog': [], - 'hail': [], - 'lightning': [], - 'lightning-rainy': [], - 'partlycloudy': [], - 'pouring': [], - 'rainy': ['shower rain'], - 'snowy': [], - 'snowy-rainy': [], - 'sunny': ['sunshine'], - 'windy': [], - 'windy-variant': [], - 'exceptional': [], + "cloudy": [], + "fog": [], + "hail": [], + "lightning": [], + "lightning-rainy": [], + "partlycloudy": [], + "pouring": [], + "rainy": ["shower rain"], + "snowy": [], + "snowy-rainy": [], + "sunny": ["sunshine"], + "windy": [], + "windy-variant": [], + "exceptional": [], } def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Demo weather.""" - add_entities([ - DemoWeather('South', 'Sunshine', 21.6414, 92, 1099, 0.5, TEMP_CELSIUS, - [['rainy', 1, 22, 15], ['rainy', 5, 19, 8], - ['cloudy', 0, 15, 9], ['sunny', 0, 12, 6], - ['partlycloudy', 2, 14, 7], ['rainy', 15, 18, 7], - ['fog', 0.2, 21, 12]]), - DemoWeather('North', 'Shower rain', -12, 54, 987, 4.8, TEMP_FAHRENHEIT, - [['snowy', 2, -10, -15], ['partlycloudy', 1, -13, -14], - ['sunny', 0, -18, -22], ['sunny', 0.1, -23, -23], - ['snowy', 4, -19, -20], ['sunny', 0.3, -14, -19], - ['sunny', 0, -9, -12]]) - ]) + add_entities( + [ + DemoWeather( + "South", + "Sunshine", + 21.6414, + 92, + 1099, + 0.5, + TEMP_CELSIUS, + [ + ["rainy", 1, 22, 15], + ["rainy", 5, 19, 8], + ["cloudy", 0, 15, 9], + ["sunny", 0, 12, 6], + ["partlycloudy", 2, 14, 7], + ["rainy", 15, 18, 7], + ["fog", 0.2, 21, 12], + ], + ), + DemoWeather( + "North", + "Shower rain", + -12, + 54, + 987, + 4.8, + TEMP_FAHRENHEIT, + [ + ["snowy", 2, -10, -15], + ["partlycloudy", 1, -13, -14], + ["sunny", 0, -18, -22], + ["sunny", 0.1, -23, -23], + ["snowy", 4, -19, -20], + ["sunny", 0.3, -14, -19], + ["sunny", 0, -9, -12], + ], + ), + ] + ) class DemoWeather(WeatherEntity): """Representation of a weather condition.""" - def __init__(self, name, condition, temperature, humidity, pressure, - wind_speed, temperature_unit, forecast): + def __init__( + self, + name, + condition, + temperature, + humidity, + pressure, + wind_speed, + temperature_unit, + forecast, + ): """Initialize the Demo weather.""" self._name = name self._condition = condition @@ -58,7 +100,7 @@ class DemoWeather(WeatherEntity): @property def name(self): """Return the name of the sensor.""" - return '{} {}'.format('Demo Weather', self._name) + return "{} {}".format("Demo Weather", self._name) @property def should_poll(self): @@ -93,13 +135,14 @@ class DemoWeather(WeatherEntity): @property def condition(self): """Return the weather condition.""" - return [k for k, v in CONDITION_CLASSES.items() if - self._condition.lower() in v][0] + return [ + k for k, v in CONDITION_CLASSES.items() if self._condition.lower() in v + ][0] @property def attribution(self): """Return the attribution.""" - return 'Powered by Home Assistant' + return "Powered by Home Assistant" @property def forecast(self): @@ -113,7 +156,7 @@ class DemoWeather(WeatherEntity): ATTR_FORECAST_CONDITION: entry[0], ATTR_FORECAST_PRECIPITATION: entry[1], ATTR_FORECAST_TEMP: entry[2], - ATTR_FORECAST_TEMP_LOW: entry[3] + ATTR_FORECAST_TEMP_LOW: entry[3], } reftime = reftime + timedelta(hours=4) forecast_data.append(data_dict) diff --git a/homeassistant/components/denon/media_player.py b/homeassistant/components/denon/media_player.py index 07f6fcc7f9c..d9ff52b47d0 100644 --- a/homeassistant/components/denon/media_player.py +++ b/homeassistant/components/denon/media_player.py @@ -4,40 +4,74 @@ import telnetlib import voluptuous as vol -from homeassistant.components.media_player import ( - MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA from homeassistant.components.media_player.const import ( - SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, - SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_STOP, - SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET) + SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, + SUPPORT_PLAY, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_SELECT_SOURCE, + SUPPORT_STOP, + SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, + SUPPORT_VOLUME_MUTE, + SUPPORT_VOLUME_SET, +) from homeassistant.const import CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'Music station' +DEFAULT_NAME = "Music station" -SUPPORT_DENON = SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ - SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE \ +SUPPORT_DENON = ( + SUPPORT_VOLUME_SET + | SUPPORT_VOLUME_MUTE + | SUPPORT_TURN_ON + | SUPPORT_TURN_OFF + | SUPPORT_SELECT_SOURCE +) +SUPPORT_MEDIA_MODES = ( + SUPPORT_PAUSE + | SUPPORT_STOP + | SUPPORT_PREVIOUS_TRACK + | SUPPORT_NEXT_TRACK + | SUPPORT_PLAY +) -SUPPORT_MEDIA_MODES = SUPPORT_PAUSE | SUPPORT_STOP | \ - SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_PLAY +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + } +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +NORMAL_INPUTS = { + "Cd": "CD", + "Dvd": "DVD", + "Blue ray": "BD", + "TV": "TV", + "Satelite / Cable": "SAT/CBL", + "Game": "GAME", + "Game2": "GAME2", + "Video Aux": "V.AUX", + "Dock": "DOCK", +} -NORMAL_INPUTS = {'Cd': 'CD', 'Dvd': 'DVD', 'Blue ray': 'BD', 'TV': 'TV', - 'Satelite / Cable': 'SAT/CBL', 'Game': 'GAME', - 'Game2': 'GAME2', 'Video Aux': 'V.AUX', 'Dock': 'DOCK'} - -MEDIA_MODES = {'Tuner': 'TUNER', 'Media server': 'SERVER', - 'Ipod dock': 'IPOD', 'Net/USB': 'NET/USB', - 'Rapsody': 'RHAPSODY', 'Napster': 'NAPSTER', - 'Pandora': 'PANDORA', 'LastFM': 'LASTFM', - 'Flickr': 'FLICKR', 'Favorites': 'FAVORITES', - 'Internet Radio': 'IRADIO', 'USB/IPOD': 'USB/IPOD'} +MEDIA_MODES = { + "Tuner": "TUNER", + "Media server": "SERVER", + "Ipod dock": "IPOD", + "Net/USB": "NET/USB", + "Rapsody": "RHAPSODY", + "Napster": "NAPSTER", + "Pandora": "PANDORA", + "LastFM": "LASTFM", + "Flickr": "FLICKR", + "Favorites": "FAVORITES", + "Internet Radio": "IRADIO", + "USB/IPOD": "USB/IPOD", +} # Sub-modes of 'NET/USB' # {'USB': 'USB', 'iPod Direct': 'IPD', 'Internet Radio': 'IRP', @@ -59,34 +93,34 @@ class DenonDevice(MediaPlayerDevice): """Initialize the Denon device.""" self._name = name self._host = host - self._pwstate = 'PWSTANDBY' + self._pwstate = "PWSTANDBY" self._volume = 0 # Initial value 60dB, changed if we get a MVMAX self._volume_max = 60 self._source_list = NORMAL_INPUTS.copy() self._source_list.update(MEDIA_MODES) self._muted = False - self._mediasource = '' - self._mediainfo = '' + self._mediasource = "" + self._mediainfo = "" self._should_setup_sources = True def _setup_sources(self, telnet): # NSFRN - Network name - nsfrn = self.telnet_request(telnet, 'NSFRN ?')[len('NSFRN '):] + nsfrn = self.telnet_request(telnet, "NSFRN ?")[len("NSFRN ") :] if nsfrn: self._name = nsfrn # SSFUN - Configured sources with names self._source_list = {} - for line in self.telnet_request(telnet, 'SSFUN ?', all_lines=True): - source, configured_name = line[len('SSFUN'):].split(" ", 1) + for line in self.telnet_request(telnet, "SSFUN ?", all_lines=True): + source, configured_name = line[len("SSFUN") :].split(" ", 1) self._source_list[configured_name] = source # SSSOD - Deleted sources - for line in self.telnet_request(telnet, 'SSSOD ?', all_lines=True): - source, status = line[len('SSSOD'):].split(" ", 1) - if status == 'DEL': + for line in self.telnet_request(telnet, "SSSOD ?", all_lines=True): + source, status = line[len("SSSOD") :].split(" ", 1) + if status == "DEL": for pretty_name, name in self._source_list.items(): if source == name: del self._source_list[pretty_name] @@ -96,24 +130,24 @@ class DenonDevice(MediaPlayerDevice): def telnet_request(cls, telnet, command, all_lines=False): """Execute `command` and return the response.""" _LOGGER.debug("Sending: %s", command) - telnet.write(command.encode('ASCII') + b'\r') + telnet.write(command.encode("ASCII") + b"\r") lines = [] while True: - line = telnet.read_until(b'\r', timeout=0.2) + line = telnet.read_until(b"\r", timeout=0.2) if not line: break - lines.append(line.decode('ASCII').strip()) + lines.append(line.decode("ASCII").strip()) _LOGGER.debug("Received: %s", line) if all_lines: return lines - return lines[0] if lines else '' + return lines[0] if lines else "" def telnet_command(self, command): """Establish a telnet connection and sends `command`.""" telnet = telnetlib.Telnet(self._host) _LOGGER.debug("Sending: %s", command) - telnet.write(command.encode('ASCII') + b'\r') + telnet.write(command.encode("ASCII") + b"\r") telnet.read_very_eager() # skip response telnet.close() @@ -128,23 +162,32 @@ class DenonDevice(MediaPlayerDevice): self._setup_sources(telnet) self._should_setup_sources = False - self._pwstate = self.telnet_request(telnet, 'PW?') - for line in self.telnet_request(telnet, 'MV?', all_lines=True): - if line.startswith('MVMAX '): + self._pwstate = self.telnet_request(telnet, "PW?") + for line in self.telnet_request(telnet, "MV?", all_lines=True): + if line.startswith("MVMAX "): # only grab two digit max, don't care about any half digit - self._volume_max = int(line[len('MVMAX '):len('MVMAX XX')]) + self._volume_max = int(line[len("MVMAX ") : len("MVMAX XX")]) continue - if line.startswith('MV'): - self._volume = int(line[len('MV'):]) - self._muted = (self.telnet_request(telnet, 'MU?') == 'MUON') - self._mediasource = self.telnet_request(telnet, 'SI?')[len('SI'):] + if line.startswith("MV"): + self._volume = int(line[len("MV") :]) + self._muted = self.telnet_request(telnet, "MU?") == "MUON" + self._mediasource = self.telnet_request(telnet, "SI?")[len("SI") :] if self._mediasource in MEDIA_MODES.values(): self._mediainfo = "" - answer_codes = ["NSE0", "NSE1X", "NSE2X", "NSE3X", "NSE4", "NSE5", - "NSE6", "NSE7", "NSE8"] - for line in self.telnet_request(telnet, 'NSE', all_lines=True): - self._mediainfo += line[len(answer_codes.pop(0)):] + '\n' + answer_codes = [ + "NSE0", + "NSE1X", + "NSE2X", + "NSE3X", + "NSE4", + "NSE5", + "NSE6", + "NSE7", + "NSE8", + ] + for line in self.telnet_request(telnet, "NSE", all_lines=True): + self._mediainfo += line[len(answer_codes.pop(0)) :] + "\n" else: self._mediainfo = self.source @@ -159,9 +202,9 @@ class DenonDevice(MediaPlayerDevice): @property def state(self): """Return the state of the device.""" - if self._pwstate == 'PWSTANDBY': + if self._pwstate == "PWSTANDBY": return STATE_OFF - if self._pwstate == 'PWON': + if self._pwstate == "PWON": return STATE_ON return None @@ -202,49 +245,48 @@ class DenonDevice(MediaPlayerDevice): def turn_off(self): """Turn off media player.""" - self.telnet_command('PWSTANDBY') + self.telnet_command("PWSTANDBY") def volume_up(self): """Volume up media player.""" - self.telnet_command('MVUP') + self.telnet_command("MVUP") def volume_down(self): """Volume down media player.""" - self.telnet_command('MVDOWN') + self.telnet_command("MVDOWN") def set_volume_level(self, volume): """Set volume level, range 0..1.""" - self.telnet_command('MV' + - str(round(volume * self._volume_max)).zfill(2)) + self.telnet_command("MV" + str(round(volume * self._volume_max)).zfill(2)) def mute_volume(self, mute): """Mute (true) or unmute (false) media player.""" - self.telnet_command('MU' + ('ON' if mute else 'OFF')) + self.telnet_command("MU" + ("ON" if mute else "OFF")) def media_play(self): """Play media player.""" - self.telnet_command('NS9A') + self.telnet_command("NS9A") def media_pause(self): """Pause media player.""" - self.telnet_command('NS9B') + self.telnet_command("NS9B") def media_stop(self): """Pause media player.""" - self.telnet_command('NS9C') + self.telnet_command("NS9C") def media_next_track(self): """Send the next track command.""" - self.telnet_command('NS9D') + self.telnet_command("NS9D") def media_previous_track(self): """Send the previous track command.""" - self.telnet_command('NS9E') + self.telnet_command("NS9E") def turn_on(self): """Turn the media player on.""" - self.telnet_command('PWON') + self.telnet_command("PWON") def select_source(self, source): """Select input source.""" - self.telnet_command('SI' + self._source_list.get(source)) + self.telnet_command("SI" + self._source_list.get(source)) diff --git a/homeassistant/components/denonavr/media_player.py b/homeassistant/components/denonavr/media_player.py index da416ce8045..51fc890c873 100644 --- a/homeassistant/components/denonavr/media_player.py +++ b/homeassistant/components/denonavr/media_player.py @@ -5,57 +5,85 @@ import logging import voluptuous as vol -from homeassistant.components.media_player import ( - MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA from homeassistant.components.media_player.const import ( - MEDIA_TYPE_CHANNEL, MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, - SUPPORT_SELECT_SOUND_MODE, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP) + MEDIA_TYPE_CHANNEL, + MEDIA_TYPE_MUSIC, + SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, + SUPPORT_PLAY, + SUPPORT_PLAY_MEDIA, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_SELECT_SOUND_MODE, + SUPPORT_SELECT_SOURCE, + SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, + SUPPORT_VOLUME_MUTE, + SUPPORT_VOLUME_SET, + SUPPORT_VOLUME_STEP, +) from homeassistant.const import ( - CONF_HOST, CONF_NAME, CONF_TIMEOUT, CONF_ZONE, STATE_OFF, STATE_ON, - STATE_PAUSED, STATE_PLAYING) + CONF_HOST, + CONF_NAME, + CONF_TIMEOUT, + CONF_ZONE, + STATE_OFF, + STATE_ON, + STATE_PAUSED, + STATE_PLAYING, +) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -ATTR_SOUND_MODE_RAW = 'sound_mode_raw' +ATTR_SOUND_MODE_RAW = "sound_mode_raw" -CONF_INVALID_ZONES_ERR = 'Invalid Zone (expected Zone2 or Zone3)' -CONF_SHOW_ALL_SOURCES = 'show_all_sources' -CONF_VALID_ZONES = ['Zone2', 'Zone3'] -CONF_ZONES = 'zones' +CONF_INVALID_ZONES_ERR = "Invalid Zone (expected Zone2 or Zone3)" +CONF_SHOW_ALL_SOURCES = "show_all_sources" +CONF_VALID_ZONES = ["Zone2", "Zone3"] +CONF_ZONES = "zones" DEFAULT_SHOW_SOURCES = False DEFAULT_TIMEOUT = 2 -KEY_DENON_CACHE = 'denonavr_hosts' +KEY_DENON_CACHE = "denonavr_hosts" -SUPPORT_DENON = SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_MUTE | \ - SUPPORT_TURN_ON | SUPPORT_TURN_OFF | \ - SUPPORT_SELECT_SOURCE | SUPPORT_VOLUME_SET +SUPPORT_DENON = ( + SUPPORT_VOLUME_STEP + | SUPPORT_VOLUME_MUTE + | SUPPORT_TURN_ON + | SUPPORT_TURN_OFF + | SUPPORT_SELECT_SOURCE + | SUPPORT_VOLUME_SET +) -SUPPORT_MEDIA_MODES = SUPPORT_PLAY_MEDIA | \ - SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | \ - SUPPORT_NEXT_TRACK | SUPPORT_VOLUME_SET | SUPPORT_PLAY +SUPPORT_MEDIA_MODES = ( + SUPPORT_PLAY_MEDIA + | SUPPORT_PAUSE + | SUPPORT_PREVIOUS_TRACK + | SUPPORT_NEXT_TRACK + | SUPPORT_VOLUME_SET + | SUPPORT_PLAY +) -DENON_ZONE_SCHEMA = vol.Schema({ - vol.Required(CONF_ZONE): vol.In(CONF_VALID_ZONES, CONF_INVALID_ZONES_ERR), - vol.Optional(CONF_NAME): cv.string, -}) +DENON_ZONE_SCHEMA = vol.Schema( + { + vol.Required(CONF_ZONE): vol.In(CONF_VALID_ZONES, CONF_INVALID_ZONES_ERR), + vol.Optional(CONF_NAME): cv.string, + } +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_HOST): cv.string, - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_SHOW_ALL_SOURCES, default=DEFAULT_SHOW_SOURCES): - cv.boolean, - vol.Optional(CONF_ZONES): - vol.All(cv.ensure_list, [DENON_ZONE_SCHEMA]), - vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_HOST): cv.string, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_SHOW_ALL_SOURCES, default=DEFAULT_SHOW_SOURCES): cv.boolean, + vol.Optional(CONF_ZONES): vol.All(cv.ensure_list, [DENON_ZONE_SCHEMA]), + vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, + } +) -NewHost = namedtuple('NewHost', ['host', 'name']) +NewHost = namedtuple("NewHost", ["host", "name"]) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -92,8 +120,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): # 2. option: discovery using netdisco if discovery_info is not None: - host = discovery_info.get('host') - name = discovery_info.get('name') + host = discovery_info.get("host") + name = discovery_info.get("name") new_hosts.append(NewHost(host=host, name=name)) # 3. option: discovery using denonavr library @@ -103,17 +131,19 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for d_receiver in d_receivers: host = d_receiver["host"] name = d_receiver["friendlyName"] - new_hosts.append( - NewHost(host=host, name=name)) + new_hosts.append(NewHost(host=host, name=name)) for entry in new_hosts: # Check if host not in cache, append it and save for later # starting if entry.host not in cache: new_device = denonavr.DenonAVR( - host=entry.host, name=entry.name, - show_all_inputs=show_all_sources, timeout=timeout, - add_zones=add_zones) + host=entry.host, + name=entry.name, + show_all_inputs=show_all_sources, + timeout=timeout, + add_zones=add_zones, + ) for new_zone in new_device.zones.values(): receivers.append(DenonDevice(new_zone)) cache.add(host) @@ -156,8 +186,9 @@ class DenonDevice(MediaPlayerDevice): self._sound_mode_list = None self._supported_features_base = SUPPORT_DENON - self._supported_features_base |= (self._sound_mode_support and - SUPPORT_SELECT_SOUND_MODE) + self._supported_features_base |= ( + self._sound_mode_support and SUPPORT_SELECT_SOUND_MODE + ) def update(self): """Get the latest status information from device.""" @@ -305,8 +336,11 @@ class DenonDevice(MediaPlayerDevice): def device_state_attributes(self): """Return device specific state attributes.""" attributes = {} - if (self._sound_mode_raw is not None and self._sound_mode_support and - self._power == 'ON'): + if ( + self._sound_mode_raw is not None + and self._sound_mode_support + and self._power == "ON" + ): attributes[ATTR_SOUND_MODE_RAW] = self._sound_mode_raw return attributes diff --git a/homeassistant/components/deutsche_bahn/sensor.py b/homeassistant/components/deutsche_bahn/sensor.py index c6761c58e57..db094bb9b12 100644 --- a/homeassistant/components/deutsche_bahn/sensor.py +++ b/homeassistant/components/deutsche_bahn/sensor.py @@ -11,23 +11,25 @@ import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) -CONF_DESTINATION = 'to' -CONF_START = 'from' -CONF_OFFSET = 'offset' +CONF_DESTINATION = "to" +CONF_START = "from" +CONF_OFFSET = "offset" DEFAULT_OFFSET = timedelta(minutes=0) -CONF_ONLY_DIRECT = 'only_direct' +CONF_ONLY_DIRECT = "only_direct" DEFAULT_ONLY_DIRECT = False -ICON = 'mdi:train' +ICON = "mdi:train" SCAN_INTERVAL = timedelta(minutes=2) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_DESTINATION): cv.string, - vol.Required(CONF_START): cv.string, - vol.Optional(CONF_OFFSET, default=DEFAULT_OFFSET): cv.time_period, - vol.Optional(CONF_ONLY_DIRECT, default=DEFAULT_ONLY_DIRECT): cv.boolean, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_DESTINATION): cv.string, + vol.Required(CONF_START): cv.string, + vol.Optional(CONF_OFFSET, default=DEFAULT_OFFSET): cv.time_period, + vol.Optional(CONF_ONLY_DIRECT, default=DEFAULT_ONLY_DIRECT): cv.boolean, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -37,8 +39,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): offset = config.get(CONF_OFFSET) only_direct = config.get(CONF_ONLY_DIRECT) - add_entities([DeutscheBahnSensor(start, destination, - offset, only_direct)], True) + add_entities([DeutscheBahnSensor(start, destination, offset, only_direct)], True) class DeutscheBahnSensor(Entity): @@ -46,7 +47,7 @@ class DeutscheBahnSensor(Entity): def __init__(self, start, goal, offset, only_direct): """Initialize the sensor.""" - self._name = '{} to {}'.format(start, goal) + self._name = "{} to {}".format(start, goal) self.data = SchieneData(start, goal, offset, only_direct) self._state = None @@ -70,17 +71,17 @@ class DeutscheBahnSensor(Entity): """Return the state attributes.""" connections = self.data.connections[0] if len(self.data.connections) > 1: - connections['next'] = self.data.connections[1]['departure'] + connections["next"] = self.data.connections[1]["departure"] if len(self.data.connections) > 2: - connections['next_on'] = self.data.connections[2]['departure'] + connections["next_on"] = self.data.connections[2]["departure"] return connections def update(self): """Get the latest delay from bahn.de and updates the state.""" self.data.update() - self._state = self.data.connections[0].get('departure', 'Unknown') - if self.data.connections[0].get('delay', 0) != 0: - self._state += " + {}".format(self.data.connections[0]['delay']) + self._state = self.data.connections[0].get("departure", "Unknown") + if self.data.connections[0].get("delay", 0) != 0: + self._state += " + {}".format(self.data.connections[0]["delay"]) class SchieneData: @@ -100,9 +101,11 @@ class SchieneData: def update(self): """Update the connection data.""" self.connections = self.schiene.connections( - self.start, self.goal, - dt_util.as_local(dt_util.utcnow()+self.offset), - self.only_direct) + self.start, + self.goal, + dt_util.as_local(dt_util.utcnow() + self.offset), + self.only_direct, + ) if not self.connections: self.connections = [{}] @@ -110,10 +113,9 @@ class SchieneData: for con in self.connections: # Detail info is not useful. Having a more consistent interface # simplifies usage of template sensors. - if 'details' in con: - con.pop('details') - delay = con.get('delay', {'delay_departure': 0, - 'delay_arrival': 0}) - con['delay'] = delay['delay_departure'] - con['delay_arrival'] = delay['delay_arrival'] - con['ontime'] = con.get('ontime', False) + if "details" in con: + con.pop("details") + delay = con.get("delay", {"delay_departure": 0, "delay_arrival": 0}) + con["delay"] = delay["delay_departure"] + con["delay_arrival"] = delay["delay_arrival"] + con["ontime"] = con.get("ontime", False) diff --git a/homeassistant/components/device_automation/__init__.py b/homeassistant/components/device_automation/__init__.py index 1d4bc71e3de..b1f319b0a6a 100644 --- a/homeassistant/components/device_automation/__init__.py +++ b/homeassistant/components/device_automation/__init__.py @@ -9,7 +9,7 @@ from homeassistant.core import split_entity_id from homeassistant.helpers.entity_registry import async_entries_for_device from homeassistant.loader import async_get_integration, IntegrationNotFound -DOMAIN = 'device_automation' +DOMAIN = "device_automation" _LOGGER = logging.getLogger(__name__) @@ -17,7 +17,8 @@ _LOGGER = logging.getLogger(__name__) async def async_setup(hass, config): """Set up device automation.""" hass.components.websocket_api.async_register_command( - websocket_device_automation_list_triggers) + websocket_device_automation_list_triggers + ) return True @@ -27,16 +28,16 @@ async def _async_get_device_automation_triggers(hass, domain, device_id): try: integration = await async_get_integration(hass, domain) except IntegrationNotFound: - _LOGGER.warning('Integration %s not found', domain) + _LOGGER.warning("Integration %s not found", domain) return None try: - platform = integration.get_platform('device_automation') + platform = integration.get_platform("device_automation") except ImportError: # The domain does not have device automations return None - if hasattr(platform, 'async_get_triggers'): + if hasattr(platform, "async_get_triggers"): return await platform.async_get_triggers(hass, device_id) @@ -44,7 +45,8 @@ async def async_get_device_automation_triggers(hass, device_id): """List device triggers.""" device_registry, entity_registry = await asyncio.gather( hass.helpers.device_registry.async_get_registry(), - hass.helpers.entity_registry.async_get_registry()) + hass.helpers.entity_registry.async_get_registry(), + ) domains = set() triggers = [] @@ -57,10 +59,12 @@ async def async_get_device_automation_triggers(hass, device_id): for entity in entities: domains.add(split_entity_id(entity.entity_id)[0]) - device_triggers = await asyncio.gather(*( - _async_get_device_automation_triggers(hass, domain, device_id) - for domain in domains - )) + device_triggers = await asyncio.gather( + *( + _async_get_device_automation_triggers(hass, domain, device_id) + for domain in domains + ) + ) for device_trigger in device_triggers: if device_trigger is not None: triggers.extend(device_trigger) @@ -69,12 +73,14 @@ async def async_get_device_automation_triggers(hass, device_id): @websocket_api.async_response -@websocket_api.websocket_command({ - vol.Required('type'): 'device_automation/list_triggers', - vol.Required('device_id'): str, -}) +@websocket_api.websocket_command( + { + vol.Required("type"): "device_automation/list_triggers", + vol.Required("device_id"): str, + } +) async def websocket_device_automation_list_triggers(hass, connection, msg): """Handle request for device triggers.""" - device_id = msg['device_id'] + device_id = msg["device_id"] triggers = await async_get_device_automation_triggers(hass, device_id) - connection.send_result(msg['id'], {'triggers': triggers}) + connection.send_result(msg["id"], {"triggers": triggers}) diff --git a/homeassistant/components/device_sun_light_trigger/__init__.py b/homeassistant/components/device_sun_light_trigger/__init__.py index 945f8368671..1b71b44369d 100644 --- a/homeassistant/components/device_sun_light_trigger/__init__.py +++ b/homeassistant/components/device_sun_light_trigger/__init__.py @@ -7,36 +7,54 @@ import voluptuous as vol from homeassistant.core import callback import homeassistant.util.dt as dt_util from homeassistant.components.light import ( - ATTR_PROFILE, ATTR_TRANSITION, DOMAIN as DOMAIN_LIGHT) + ATTR_PROFILE, + ATTR_TRANSITION, + DOMAIN as DOMAIN_LIGHT, +) from homeassistant.const import ( - ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_HOME, - STATE_NOT_HOME, SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET) + ATTR_ENTITY_ID, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_HOME, + STATE_NOT_HOME, + SUN_EVENT_SUNRISE, + SUN_EVENT_SUNSET, +) from homeassistant.helpers.event import ( - async_track_point_in_utc_time, async_track_state_change) + async_track_point_in_utc_time, + async_track_state_change, +) from homeassistant.helpers.sun import is_up, get_astral_event_next import homeassistant.helpers.config_validation as cv -DOMAIN = 'device_sun_light_trigger' -CONF_DEVICE_GROUP = 'device_group' -CONF_DISABLE_TURN_OFF = 'disable_turn_off' -CONF_LIGHT_GROUP = 'light_group' -CONF_LIGHT_PROFILE = 'light_profile' +DOMAIN = "device_sun_light_trigger" +CONF_DEVICE_GROUP = "device_group" +CONF_DISABLE_TURN_OFF = "disable_turn_off" +CONF_LIGHT_GROUP = "light_group" +CONF_LIGHT_PROFILE = "light_profile" DEFAULT_DISABLE_TURN_OFF = False -DEFAULT_LIGHT_PROFILE = 'relax' +DEFAULT_LIGHT_PROFILE = "relax" LIGHT_TRANSITION_TIME = timedelta(minutes=15) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Optional(CONF_DEVICE_GROUP): cv.entity_id, - vol.Optional(CONF_DISABLE_TURN_OFF, default=DEFAULT_DISABLE_TURN_OFF): - cv.boolean, - vol.Optional(CONF_LIGHT_GROUP): cv.string, - vol.Optional(CONF_LIGHT_PROFILE, default=DEFAULT_LIGHT_PROFILE): - cv.string, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Optional(CONF_DEVICE_GROUP): cv.entity_id, + vol.Optional( + CONF_DISABLE_TURN_OFF, default=DEFAULT_DISABLE_TURN_OFF + ): cv.boolean, + vol.Optional(CONF_LIGHT_GROUP): cv.string, + vol.Optional( + CONF_LIGHT_PROFILE, default=DEFAULT_LIGHT_PROFILE + ): cv.string, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) async def async_setup(hass, config): @@ -49,10 +67,8 @@ async def async_setup(hass, config): disable_turn_off = conf.get(CONF_DISABLE_TURN_OFF) light_group = conf.get(CONF_LIGHT_GROUP, light.ENTITY_ID_ALL_LIGHTS) light_profile = conf.get(CONF_LIGHT_PROFILE) - device_group = conf.get( - CONF_DEVICE_GROUP, device_tracker.ENTITY_ID_ALL_DEVICES) - device_entity_ids = group.get_entity_ids( - device_group, device_tracker.DOMAIN) + device_group = conf.get(CONF_DEVICE_GROUP, device_tracker.ENTITY_ID_ALL_DEVICES) + device_entity_ids = group.get_entity_ids(device_group, device_tracker.DOMAIN) if not device_entity_ids: logger.error("No devices found to track") @@ -83,13 +99,19 @@ async def async_setup(hass, config): return hass.async_create_task( hass.services.async_call( - DOMAIN_LIGHT, SERVICE_TURN_ON, { + DOMAIN_LIGHT, + SERVICE_TURN_ON, + { ATTR_ENTITY_ID: light_id, ATTR_TRANSITION: LIGHT_TRANSITION_TIME.seconds, - ATTR_PROFILE: light_profile})) + ATTR_PROFILE: light_profile, + }, + ) + ) def async_turn_on_factory(light_id): """Generate turn on callbacks as factory.""" + @callback def async_turn_on_light(now): """Turn on specific light.""" @@ -112,12 +134,14 @@ async def async_setup(hass, config): for index, light_id in enumerate(light_ids): async_track_point_in_utc_time( - hass, async_turn_on_factory(light_id), - start_point + index * LIGHT_TRANSITION_TIME) + hass, + async_turn_on_factory(light_id), + start_point + index * LIGHT_TRANSITION_TIME, + ) - async_track_point_in_utc_time(hass, schedule_light_turn_on, - get_astral_event_next(hass, - SUN_EVENT_SUNRISE)) + async_track_point_in_utc_time( + hass, schedule_light_turn_on, get_astral_event_next(hass, SUN_EVENT_SUNRISE) + ) # If the sun is already above horizon schedule the time-based pre-sun set # event. @@ -139,16 +163,19 @@ async def async_setup(hass, config): logger.info("Home coming event for %s. Turning lights on", entity) hass.async_create_task( hass.services.async_call( - DOMAIN_LIGHT, SERVICE_TURN_ON, - {ATTR_ENTITY_ID: light_ids, ATTR_PROFILE: light_profile})) + DOMAIN_LIGHT, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: light_ids, ATTR_PROFILE: light_profile}, + ) + ) # Are we in the time span were we would turn on the lights # if someone would be home? # Check this by seeing if current time is later then the point # in time when we would start putting the lights on. - elif (start_point and - start_point < now < get_astral_event_next(hass, - SUN_EVENT_SUNSET)): + elif start_point and start_point < now < get_astral_event_next( + hass, SUN_EVENT_SUNSET + ): # Check for every light if it would be on if someone was home # when the fading in started and turn it on if so @@ -156,8 +183,9 @@ async def async_setup(hass, config): if now > start_point + index * LIGHT_TRANSITION_TIME: hass.async_create_task( hass.services.async_call( - DOMAIN_LIGHT, SERVICE_TURN_ON, - {ATTR_ENTITY_ID: light_id})) + DOMAIN_LIGHT, SERVICE_TURN_ON, {ATTR_ENTITY_ID: light_id} + ) + ) else: # If this light didn't happen to be turned on yet so @@ -165,8 +193,12 @@ async def async_setup(hass, config): break async_track_state_change( - hass, device_entity_ids, check_light_on_dev_state_change, - STATE_NOT_HOME, STATE_HOME) + hass, + device_entity_ids, + check_light_on_dev_state_change, + STATE_NOT_HOME, + STATE_HOME, + ) if disable_turn_off: return True @@ -177,14 +209,15 @@ async def async_setup(hass, config): if not group.is_on(light_group): return - logger.info( - "Everyone has left but there are lights on. Turning them off") + logger.info("Everyone has left but there are lights on. Turning them off") hass.async_create_task( hass.services.async_call( - DOMAIN_LIGHT, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: light_ids})) + DOMAIN_LIGHT, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: light_ids} + ) + ) async_track_state_change( - hass, device_group, turn_off_lights_when_all_leave, - STATE_HOME, STATE_NOT_HOME) + hass, device_group, turn_off_lights_when_all_leave, STATE_HOME, STATE_NOT_HOME + ) return True diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index 4c67e6fa65d..84bc76e0b04 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -14,7 +14,8 @@ from homeassistant.const import ATTR_GPS_ACCURACY, STATE_HOME from . import legacy, setup from .config_entry import ( # noqa # pylint: disable=unused-import - async_setup_entry, async_unload_entry + async_setup_entry, + async_unload_entry, ) from .legacy import DeviceScanner # noqa # pylint: disable=unused-import from .const import ( @@ -43,43 +44,57 @@ from .const import ( SOURCE_TYPE_ROUTER, ) -ENTITY_ID_ALL_DEVICES = group.ENTITY_ID_FORMAT.format('all_devices') +ENTITY_ID_ALL_DEVICES = group.ENTITY_ID_FORMAT.format("all_devices") -SERVICE_SEE = 'see' +SERVICE_SEE = "see" -SOURCE_TYPES = (SOURCE_TYPE_GPS, SOURCE_TYPE_ROUTER, - SOURCE_TYPE_BLUETOOTH, SOURCE_TYPE_BLUETOOTH_LE) +SOURCE_TYPES = ( + SOURCE_TYPE_GPS, + SOURCE_TYPE_ROUTER, + SOURCE_TYPE_BLUETOOTH, + SOURCE_TYPE_BLUETOOTH_LE, +) -NEW_DEVICE_DEFAULTS_SCHEMA = vol.Any(None, vol.Schema({ - vol.Optional(CONF_TRACK_NEW, default=DEFAULT_TRACK_NEW): cv.boolean, - vol.Optional(CONF_AWAY_HIDE, default=DEFAULT_AWAY_HIDE): cv.boolean, -})) -PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_SCAN_INTERVAL): cv.time_period, - vol.Optional(CONF_TRACK_NEW): cv.boolean, - vol.Optional(CONF_CONSIDER_HOME, - default=DEFAULT_CONSIDER_HOME): vol.All( - cv.time_period, cv.positive_timedelta), - vol.Optional(CONF_NEW_DEVICE_DEFAULTS, - default={}): NEW_DEVICE_DEFAULTS_SCHEMA -}) +NEW_DEVICE_DEFAULTS_SCHEMA = vol.Any( + None, + vol.Schema( + { + vol.Optional(CONF_TRACK_NEW, default=DEFAULT_TRACK_NEW): cv.boolean, + vol.Optional(CONF_AWAY_HIDE, default=DEFAULT_AWAY_HIDE): cv.boolean, + } + ), +) +PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_SCAN_INTERVAL): cv.time_period, + vol.Optional(CONF_TRACK_NEW): cv.boolean, + vol.Optional(CONF_CONSIDER_HOME, default=DEFAULT_CONSIDER_HOME): vol.All( + cv.time_period, cv.positive_timedelta + ), + vol.Optional(CONF_NEW_DEVICE_DEFAULTS, default={}): NEW_DEVICE_DEFAULTS_SCHEMA, + } +) PLATFORM_SCHEMA_BASE = cv.PLATFORM_SCHEMA_BASE.extend(PLATFORM_SCHEMA.schema) -SERVICE_SEE_PAYLOAD_SCHEMA = vol.Schema(vol.All( - cv.has_at_least_one_key(ATTR_MAC, ATTR_DEV_ID), { - ATTR_MAC: cv.string, - ATTR_DEV_ID: cv.string, - ATTR_HOST_NAME: cv.string, - ATTR_LOCATION_NAME: cv.string, - ATTR_GPS: cv.gps, - ATTR_GPS_ACCURACY: cv.positive_int, - ATTR_BATTERY: cv.positive_int, - ATTR_ATTRIBUTES: dict, - ATTR_SOURCE_TYPE: vol.In(SOURCE_TYPES), - ATTR_CONSIDER_HOME: cv.time_period, - # Temp workaround for iOS app introduced in 0.65 - vol.Optional('battery_status'): str, - vol.Optional('hostname'): str, - })) +SERVICE_SEE_PAYLOAD_SCHEMA = vol.Schema( + vol.All( + cv.has_at_least_one_key(ATTR_MAC, ATTR_DEV_ID), + { + ATTR_MAC: cv.string, + ATTR_DEV_ID: cv.string, + ATTR_HOST_NAME: cv.string, + ATTR_LOCATION_NAME: cv.string, + ATTR_GPS: cv.gps, + ATTR_GPS_ACCURACY: cv.positive_int, + ATTR_BATTERY: cv.positive_int, + ATTR_ATTRIBUTES: dict, + ATTR_SOURCE_TYPE: vol.In(SOURCE_TYPES), + ATTR_CONSIDER_HOME: cv.time_period, + # Temp workaround for iOS app introduced in 0.65 + vol.Optional("battery_status"): str, + vol.Optional("hostname"): str, + }, + ) +) @bind_hass @@ -90,19 +105,31 @@ def is_on(hass: HomeAssistantType, entity_id: str = None): return hass.states.is_state(entity, STATE_HOME) -def see(hass: HomeAssistantType, mac: str = None, dev_id: str = None, - host_name: str = None, location_name: str = None, - gps: GPSType = None, gps_accuracy=None, - battery: int = None, attributes: dict = None): +def see( + hass: HomeAssistantType, + mac: str = None, + dev_id: str = None, + host_name: str = None, + location_name: str = None, + gps: GPSType = None, + gps_accuracy=None, + battery: int = None, + attributes: dict = None, +): """Call service to notify you see device.""" - data = {key: value for key, value in - ((ATTR_MAC, mac), - (ATTR_DEV_ID, dev_id), - (ATTR_HOST_NAME, host_name), - (ATTR_LOCATION_NAME, location_name), - (ATTR_GPS, gps), - (ATTR_GPS_ACCURACY, gps_accuracy), - (ATTR_BATTERY, battery)) if value is not None} + data = { + key: value + for key, value in ( + (ATTR_MAC, mac), + (ATTR_DEV_ID, dev_id), + (ATTR_HOST_NAME, host_name), + (ATTR_LOCATION_NAME, location_name), + (ATTR_GPS, gps), + (ATTR_GPS_ACCURACY, gps_accuracy), + (ATTR_BATTERY, battery), + ) + if value is not None + } if attributes: data[ATTR_ATTRIBUTES] = attributes hass.services.call(DOMAIN, SERVICE_SEE, data) @@ -126,8 +153,7 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType): async def async_platform_discovered(p_type, info): """Load a platform.""" - platform = await setup.async_create_platform_type( - hass, config, p_type, {}) + platform = await setup.async_create_platform_type(hass, config, p_type, {}) if platform is None or platform.type != PLATFORM_TYPE_LEGACY: return @@ -138,18 +164,20 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType): # Clean up stale devices async_track_utc_time_change( - hass, tracker.async_update_stale, second=range(0, 60, 5)) + hass, tracker.async_update_stale, second=range(0, 60, 5) + ) async def async_see_service(call): """Service to see a device.""" # Temp workaround for iOS, introduced in 0.65 data = dict(call.data) - data.pop('hostname', None) - data.pop('battery_status', None) + data.pop("hostname", None) + data.pop("battery_status", None) await tracker.async_see(**data) hass.services.async_register( - DOMAIN, SERVICE_SEE, async_see_service, SERVICE_SEE_PAYLOAD_SCHEMA) + DOMAIN, SERVICE_SEE, async_see_service, SERVICE_SEE_PAYLOAD_SCHEMA + ) # restore await tracker.async_setup_tracked_device() diff --git a/homeassistant/components/device_tracker/config_entry.py b/homeassistant/components/device_tracker/config_entry.py index 6efcd7826a4..460f1198409 100644 --- a/homeassistant/components/device_tracker/config_entry.py +++ b/homeassistant/components/device_tracker/config_entry.py @@ -13,11 +13,7 @@ from homeassistant.const import ( from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent -from .const import ( - ATTR_SOURCE_TYPE, - DOMAIN, - LOGGER, -) +from .const import ATTR_SOURCE_TYPE, DOMAIN, LOGGER async def async_setup_entry(hass, entry): @@ -25,9 +21,7 @@ async def async_setup_entry(hass, entry): component = hass.data.get(DOMAIN) # type: Optional[EntityComponent] if component is None: - component = hass.data[DOMAIN] = EntityComponent( - LOGGER, DOMAIN, hass - ) + component = hass.data[DOMAIN] = EntityComponent(LOGGER, DOMAIN, hass) return await component.async_setup_entry(entry) @@ -56,9 +50,7 @@ class BaseTrackerEntity(Entity): @property def state_attributes(self): """Return the device state attributes.""" - attr = { - ATTR_SOURCE_TYPE: self.source_type - } + attr = {ATTR_SOURCE_TYPE: self.source_type} if self.battery_level: attr[ATTR_BATTERY_LEVEL] = self.battery_level @@ -100,8 +92,8 @@ class TrackerEntity(BaseTrackerEntity): if self.latitude is not None: zone_state = zone.async_active_zone( - self.hass, self.latitude, self.longitude, - self.location_accuracy) + self.hass, self.latitude, self.longitude, self.location_accuracy + ) if zone_state is None: state = STATE_NOT_HOME elif zone_state.entity_id == zone.ENTITY_ID_HOME: diff --git a/homeassistant/components/device_tracker/const.py b/homeassistant/components/device_tracker/const.py index 18ec486e693..1778a87b36a 100644 --- a/homeassistant/components/device_tracker/const.py +++ b/homeassistant/components/device_tracker/const.py @@ -4,37 +4,37 @@ import logging LOGGER = logging.getLogger(__package__) -DOMAIN = 'device_tracker' -ENTITY_ID_FORMAT = DOMAIN + '.{}' +DOMAIN = "device_tracker" +ENTITY_ID_FORMAT = DOMAIN + ".{}" -PLATFORM_TYPE_LEGACY = 'legacy' -PLATFORM_TYPE_ENTITY = 'entity_platform' +PLATFORM_TYPE_LEGACY = "legacy" +PLATFORM_TYPE_ENTITY = "entity_platform" -SOURCE_TYPE_GPS = 'gps' -SOURCE_TYPE_ROUTER = 'router' -SOURCE_TYPE_BLUETOOTH = 'bluetooth' -SOURCE_TYPE_BLUETOOTH_LE = 'bluetooth_le' +SOURCE_TYPE_GPS = "gps" +SOURCE_TYPE_ROUTER = "router" +SOURCE_TYPE_BLUETOOTH = "bluetooth" +SOURCE_TYPE_BLUETOOTH_LE = "bluetooth_le" -CONF_SCAN_INTERVAL = 'interval_seconds' +CONF_SCAN_INTERVAL = "interval_seconds" SCAN_INTERVAL = timedelta(seconds=12) -CONF_TRACK_NEW = 'track_new_devices' +CONF_TRACK_NEW = "track_new_devices" DEFAULT_TRACK_NEW = True -CONF_AWAY_HIDE = 'hide_if_away' +CONF_AWAY_HIDE = "hide_if_away" DEFAULT_AWAY_HIDE = False -CONF_CONSIDER_HOME = 'consider_home' +CONF_CONSIDER_HOME = "consider_home" DEFAULT_CONSIDER_HOME = timedelta(seconds=180) -CONF_NEW_DEVICE_DEFAULTS = 'new_device_defaults' +CONF_NEW_DEVICE_DEFAULTS = "new_device_defaults" -ATTR_ATTRIBUTES = 'attributes' -ATTR_BATTERY = 'battery' -ATTR_DEV_ID = 'dev_id' -ATTR_GPS = 'gps' -ATTR_HOST_NAME = 'host_name' -ATTR_LOCATION_NAME = 'location_name' -ATTR_MAC = 'mac' -ATTR_SOURCE_TYPE = 'source_type' -ATTR_CONSIDER_HOME = 'consider_home' +ATTR_ATTRIBUTES = "attributes" +ATTR_BATTERY = "battery" +ATTR_DEV_ID = "dev_id" +ATTR_GPS = "gps" +ATTR_HOST_NAME = "host_name" +ATTR_LOCATION_NAME = "location_name" +ATTR_MAC = "mac" +ATTR_SOURCE_TYPE = "source_type" +ATTR_CONSIDER_HOME = "consider_home" diff --git a/homeassistant/components/device_tracker/legacy.py b/homeassistant/components/device_tracker/legacy.py index 1a2e7c854e5..67e35df00a1 100644 --- a/homeassistant/components/device_tracker/legacy.py +++ b/homeassistant/components/device_tracker/legacy.py @@ -8,8 +8,13 @@ import voluptuous as vol from homeassistant.core import callback from homeassistant.components import zone from homeassistant.components.group import ( - ATTR_ADD_ENTITIES, ATTR_ENTITIES, ATTR_OBJECT_ID, ATTR_VISIBLE, - DOMAIN as DOMAIN_GROUP, SERVICE_SET) + ATTR_ADD_ENTITIES, + ATTR_ENTITIES, + ATTR_OBJECT_ID, + ATTR_VISIBLE, + DOMAIN as DOMAIN_GROUP, + SERVICE_SET, +) from homeassistant.components.zone import async_active_zone from homeassistant.config import load_yaml_config_file, async_log_exception from homeassistant.exceptions import HomeAssistantError @@ -22,9 +27,19 @@ import homeassistant.util.dt as dt_util from homeassistant.util.yaml import dump from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_GPS_ACCURACY, ATTR_ICON, ATTR_LATITUDE, - ATTR_LONGITUDE, ATTR_NAME, CONF_ICON, CONF_MAC, CONF_NAME, - DEVICE_DEFAULT_NAME, STATE_NOT_HOME, STATE_HOME) + ATTR_ENTITY_ID, + ATTR_GPS_ACCURACY, + ATTR_ICON, + ATTR_LATITUDE, + ATTR_LONGITUDE, + ATTR_NAME, + CONF_ICON, + CONF_MAC, + CONF_NAME, + DEVICE_DEFAULT_NAME, + STATE_NOT_HOME, + STATE_HOME, +) from .const import ( ATTR_BATTERY, @@ -44,9 +59,9 @@ from .const import ( SOURCE_TYPE_GPS, ) -YAML_DEVICES = 'known_devices.yaml' -GROUP_NAME_ALL_DEVICES = 'all devices' -EVENT_NEW_DEVICE = 'device_tracker_new_device' +YAML_DEVICES = "known_devices.yaml" +GROUP_NAME_ALL_DEVICES = "all devices" +EVENT_NEW_DEVICE = "device_tracker_new_device" async def get_tracker(hass, config): @@ -63,75 +78,116 @@ async def get_tracker(hass, config): track_new = defaults.get(CONF_TRACK_NEW, DEFAULT_TRACK_NEW) devices = await async_load_config(yaml_path, hass, consider_home) - tracker = DeviceTracker( - hass, consider_home, track_new, defaults, devices) + tracker = DeviceTracker(hass, consider_home, track_new, defaults, devices) return tracker class DeviceTracker: """Representation of a device tracker.""" - def __init__(self, hass: HomeAssistantType, consider_home: timedelta, - track_new: bool, defaults: dict, - devices: Sequence) -> None: + def __init__( + self, + hass: HomeAssistantType, + consider_home: timedelta, + track_new: bool, + defaults: dict, + devices: Sequence, + ) -> None: """Initialize a device tracker.""" self.hass = hass self.devices = {dev.dev_id: dev for dev in devices} self.mac_to_dev = {dev.mac: dev for dev in devices if dev.mac} self.consider_home = consider_home - self.track_new = track_new if track_new is not None \ + self.track_new = ( + track_new + if track_new is not None else defaults.get(CONF_TRACK_NEW, DEFAULT_TRACK_NEW) + ) self.defaults = defaults self.group = None self._is_updating = asyncio.Lock() for dev in devices: if self.devices[dev.dev_id] is not dev: - LOGGER.warning('Duplicate device IDs detected %s', dev.dev_id) + LOGGER.warning("Duplicate device IDs detected %s", dev.dev_id) if dev.mac and self.mac_to_dev[dev.mac] is not dev: - LOGGER.warning('Duplicate device MAC addresses detected %s', - dev.mac) + LOGGER.warning("Duplicate device MAC addresses detected %s", dev.mac) - def see(self, mac: str = None, dev_id: str = None, host_name: str = None, - location_name: str = None, gps: GPSType = None, - gps_accuracy: int = None, battery: int = None, - attributes: dict = None, source_type: str = SOURCE_TYPE_GPS, - picture: str = None, icon: str = None, - consider_home: timedelta = None): + def see( + self, + mac: str = None, + dev_id: str = None, + host_name: str = None, + location_name: str = None, + gps: GPSType = None, + gps_accuracy: int = None, + battery: int = None, + attributes: dict = None, + source_type: str = SOURCE_TYPE_GPS, + picture: str = None, + icon: str = None, + consider_home: timedelta = None, + ): """Notify the device tracker that you see a device.""" self.hass.add_job( - self.async_see(mac, dev_id, host_name, location_name, gps, - gps_accuracy, battery, attributes, source_type, - picture, icon, consider_home) + self.async_see( + mac, + dev_id, + host_name, + location_name, + gps, + gps_accuracy, + battery, + attributes, + source_type, + picture, + icon, + consider_home, + ) ) async def async_see( - self, mac: str = None, dev_id: str = None, host_name: str = None, - location_name: str = None, gps: GPSType = None, - gps_accuracy: int = None, battery: int = None, - attributes: dict = None, source_type: str = SOURCE_TYPE_GPS, - picture: str = None, icon: str = None, - consider_home: timedelta = None): + self, + mac: str = None, + dev_id: str = None, + host_name: str = None, + location_name: str = None, + gps: GPSType = None, + gps_accuracy: int = None, + battery: int = None, + attributes: dict = None, + source_type: str = SOURCE_TYPE_GPS, + picture: str = None, + icon: str = None, + consider_home: timedelta = None, + ): """Notify the device tracker that you see a device. This method is a coroutine. """ registry = await async_get_registry(self.hass) if mac is None and dev_id is None: - raise HomeAssistantError('Neither mac or device id passed in') + raise HomeAssistantError("Neither mac or device id passed in") if mac is not None: mac = str(mac).upper() device = self.mac_to_dev.get(mac) if not device: - dev_id = util.slugify(host_name or '') or util.slugify(mac) + dev_id = util.slugify(host_name or "") or util.slugify(mac) else: dev_id = cv.slug(str(dev_id).lower()) device = self.devices.get(dev_id) if device: await device.async_seen( - host_name, location_name, gps, gps_accuracy, battery, - attributes, source_type, consider_home) + host_name, + location_name, + gps, + gps_accuracy, + battery, + attributes, + source_type, + consider_home, + ) if device.track: await device.async_update_ha_state() return @@ -140,24 +196,36 @@ class DeviceTracker: entity_id = ENTITY_ID_FORMAT.format(dev_id) if registry.async_is_registered(entity_id): LOGGER.error( - "The see service is not supported for this entity %s", - entity_id) + "The see service is not supported for this entity %s", entity_id + ) return # If no device can be found, create it dev_id = util.ensure_unique_string(dev_id, self.devices.keys()) device = Device( - self.hass, consider_home or self.consider_home, self.track_new, - dev_id, mac, (host_name or dev_id).replace('_', ' '), - picture=picture, icon=icon, - hide_if_away=self.defaults.get(CONF_AWAY_HIDE, DEFAULT_AWAY_HIDE)) + self.hass, + consider_home or self.consider_home, + self.track_new, + dev_id, + mac, + (host_name or dev_id).replace("_", " "), + picture=picture, + icon=icon, + hide_if_away=self.defaults.get(CONF_AWAY_HIDE, DEFAULT_AWAY_HIDE), + ) self.devices[dev_id] = device if mac is not None: self.mac_to_dev[mac] = device await device.async_seen( - host_name, location_name, gps, gps_accuracy, battery, attributes, - source_type) + host_name, + location_name, + gps, + gps_accuracy, + battery, + attributes, + source_type, + ) if device.track: await device.async_update_ha_state() @@ -166,22 +234,31 @@ class DeviceTracker: if self.group and self.track_new: self.hass.async_create_task( self.hass.async_call( - DOMAIN_GROUP, SERVICE_SET, { + DOMAIN_GROUP, + SERVICE_SET, + { ATTR_OBJECT_ID: util.slugify(GROUP_NAME_ALL_DEVICES), ATTR_VISIBLE: False, ATTR_NAME: GROUP_NAME_ALL_DEVICES, - ATTR_ADD_ENTITIES: [device.entity_id]})) + ATTR_ADD_ENTITIES: [device.entity_id], + }, + ) + ) - self.hass.bus.async_fire(EVENT_NEW_DEVICE, { - ATTR_ENTITY_ID: device.entity_id, - ATTR_HOST_NAME: device.host_name, - ATTR_MAC: device.mac, - }) + self.hass.bus.async_fire( + EVENT_NEW_DEVICE, + { + ATTR_ENTITY_ID: device.entity_id, + ATTR_HOST_NAME: device.host_name, + ATTR_MAC: device.mac, + }, + ) # update known_devices.yaml self.hass.async_create_task( self.async_update_config( - self.hass.config.path(YAML_DEVICES), dev_id, device) + self.hass.config.path(YAML_DEVICES), dev_id, device + ) ) async def async_update_config(self, path, dev_id, device): @@ -191,8 +268,8 @@ class DeviceTracker: """ async with self._is_updating: await self.hass.async_add_executor_job( - update_config, self.hass.config.path(YAML_DEVICES), - dev_id, device) + update_config, self.hass.config.path(YAML_DEVICES), dev_id, device + ) @callback def async_setup_group(self): @@ -200,16 +277,20 @@ class DeviceTracker: This method must be run in the event loop. """ - entity_ids = [dev.entity_id for dev in self.devices.values() - if dev.track] + entity_ids = [dev.entity_id for dev in self.devices.values() if dev.track] self.hass.async_create_task( self.hass.services.async_call( - DOMAIN_GROUP, SERVICE_SET, { + DOMAIN_GROUP, + SERVICE_SET, + { ATTR_OBJECT_ID: util.slugify(GROUP_NAME_ALL_DEVICES), ATTR_VISIBLE: False, ATTR_NAME: GROUP_NAME_ALL_DEVICES, - ATTR_ENTITIES: entity_ids})) + ATTR_ENTITIES: entity_ids, + }, + ) + ) @callback def async_update_stale(self, now: dt_util.dt.datetime): @@ -218,8 +299,7 @@ class DeviceTracker: This method must be run in the event loop. """ for device in self.devices.values(): - if (device.track and device.last_update_home) and \ - device.stale(now): + if (device.track and device.last_update_home) and device.stale(now): self.hass.async_create_task(device.async_update_ha_state(True)) async def async_setup_tracked_device(self): @@ -227,6 +307,7 @@ class DeviceTracker: This method is a coroutine. """ + async def async_init_single_device(dev): """Init a single device_tracker entity.""" await dev.async_added_to_hass() @@ -235,8 +316,9 @@ class DeviceTracker: tasks = [] for device in self.devices.values(): if device.track and not device.last_seen: - tasks.append(self.hass.async_create_task( - async_init_single_device(device))) + tasks.append( + self.hass.async_create_task(async_init_single_device(device)) + ) if tasks: await asyncio.wait(tasks) @@ -259,10 +341,19 @@ class Device(RestoreEntity): last_update_home = False _state = STATE_NOT_HOME - def __init__(self, hass: HomeAssistantType, consider_home: timedelta, - track: bool, dev_id: str, mac: str, name: str = None, - picture: str = None, gravatar: str = None, icon: str = None, - hide_if_away: bool = False) -> None: + def __init__( + self, + hass: HomeAssistantType, + consider_home: timedelta, + track: bool, + dev_id: str, + mac: str, + name: str = None, + picture: str = None, + gravatar: str = None, + icon: str = None, + hide_if_away: bool = False, + ) -> None: """Initialize a device.""" self.hass = hass self.entity_id = ENTITY_ID_FORMAT.format(dev_id) @@ -313,9 +404,7 @@ class Device(RestoreEntity): @property def state_attributes(self): """Return the device state attributes.""" - attr = { - ATTR_SOURCE_TYPE: self.source_type - } + attr = {ATTR_SOURCE_TYPE: self.source_type} if self.gps: attr[ATTR_LATITUDE] = self.gps[0] @@ -338,11 +427,16 @@ class Device(RestoreEntity): return self.away_hide and self.state != STATE_HOME async def async_seen( - self, host_name: str = None, location_name: str = None, - gps: GPSType = None, gps_accuracy=0, battery: int = None, - attributes: dict = None, - source_type: str = SOURCE_TYPE_GPS, - consider_home: timedelta = None): + self, + host_name: str = None, + location_name: str = None, + gps: GPSType = None, + gps_accuracy=0, + battery: int = None, + attributes: dict = None, + source_type: str = SOURCE_TYPE_GPS, + consider_home: timedelta = None, + ): """Mark the device as seen.""" self.source_type = source_type self.last_seen = dt_util.utcnow() @@ -364,8 +458,7 @@ class Device(RestoreEntity): except (ValueError, TypeError, IndexError): self.gps = None self.gps_accuracy = 0 - LOGGER.warning( - "Could not parse gps value for %s: %s", self.dev_id, gps) + LOGGER.warning("Could not parse gps value for %s: %s", self.dev_id, gps) # pylint: disable=not-an-iterable await self.async_update() @@ -375,8 +468,10 @@ class Device(RestoreEntity): Async friendly. """ - return self.last_seen is None or \ - (now or dt_util.utcnow()) - self.last_seen > self.consider_home + return ( + self.last_seen is None + or (now or dt_util.utcnow()) - self.last_seen > self.consider_home + ) def mark_stale(self): """Mark the device state as stale.""" @@ -395,7 +490,8 @@ class Device(RestoreEntity): self._state = self.location_name elif self.gps is not None and self.source_type == SOURCE_TYPE_GPS: zone_state = async_active_zone( - self.hass, self.gps[0], self.gps[1], self.gps_accuracy) + self.hass, self.gps[0], self.gps[1], self.gps_accuracy + ) if zone_state is None: self._state = STATE_NOT_HOME elif zone_state.entity_id == zone.ENTITY_ID_HOME: @@ -415,20 +511,22 @@ class Device(RestoreEntity): if not state: return self._state = state.state - self.last_update_home = (state.state == STATE_HOME) + self.last_update_home = state.state == STATE_HOME self.last_seen = dt_util.utcnow() for attr, var in ( - (ATTR_SOURCE_TYPE, 'source_type'), - (ATTR_GPS_ACCURACY, 'gps_accuracy'), - (ATTR_BATTERY, 'battery'), + (ATTR_SOURCE_TYPE, "source_type"), + (ATTR_GPS_ACCURACY, "gps_accuracy"), + (ATTR_BATTERY, "battery"), ): if attr in state.attributes: setattr(self, var, state.attributes[attr]) if ATTR_LONGITUDE in state.attributes: - self.gps = (state.attributes[ATTR_LATITUDE], - state.attributes[ATTR_LONGITUDE]) + self.gps = ( + state.attributes[ATTR_LATITUDE], + state.attributes[ATTR_LONGITUDE], + ) class DeviceScanner: @@ -470,28 +568,32 @@ class DeviceScanner: return self.hass.async_add_job(self.get_extra_attributes, device) -async def async_load_config(path: str, hass: HomeAssistantType, - consider_home: timedelta): +async def async_load_config( + path: str, hass: HomeAssistantType, consider_home: timedelta +): """Load devices from YAML configuration file. This method is a coroutine. """ - dev_schema = vol.Schema({ - vol.Required(CONF_NAME): cv.string, - vol.Optional(CONF_ICON, default=None): vol.Any(None, cv.icon), - vol.Optional('track', default=False): cv.boolean, - vol.Optional(CONF_MAC, default=None): - vol.Any(None, vol.All(cv.string, vol.Upper)), - vol.Optional(CONF_AWAY_HIDE, default=DEFAULT_AWAY_HIDE): cv.boolean, - vol.Optional('gravatar', default=None): vol.Any(None, cv.string), - vol.Optional('picture', default=None): vol.Any(None, cv.string), - vol.Optional(CONF_CONSIDER_HOME, default=consider_home): vol.All( - cv.time_period, cv.positive_timedelta), - }) + dev_schema = vol.Schema( + { + vol.Required(CONF_NAME): cv.string, + vol.Optional(CONF_ICON, default=None): vol.Any(None, cv.icon), + vol.Optional("track", default=False): cv.boolean, + vol.Optional(CONF_MAC, default=None): vol.Any( + None, vol.All(cv.string, vol.Upper) + ), + vol.Optional(CONF_AWAY_HIDE, default=DEFAULT_AWAY_HIDE): cv.boolean, + vol.Optional("gravatar", default=None): vol.Any(None, cv.string), + vol.Optional("picture", default=None): vol.Any(None, cv.string), + vol.Optional(CONF_CONSIDER_HOME, default=consider_home): vol.All( + cv.time_period, cv.positive_timedelta + ), + } + ) result = [] try: - devices = await hass.async_add_job( - load_yaml_config_file, path) + devices = await hass.async_add_job(load_yaml_config_file, path) except HomeAssistantError as err: LOGGER.error("Unable to load %s: %s", path, str(err)) return [] @@ -500,10 +602,10 @@ async def async_load_config(path: str, hass: HomeAssistantType, for dev_id, device in devices.items(): # Deprecated option. We just ignore it to avoid breaking change - device.pop('vendor', None) + device.pop("vendor", None) try: device = dev_schema(device) - device['dev_id'] = cv.slugify(dev_id) + device["dev_id"] = cv.slugify(dev_id) except vol.Invalid as exp: async_log_exception(exp, dev_id, devices, hass) else: @@ -513,16 +615,18 @@ async def async_load_config(path: str, hass: HomeAssistantType, def update_config(path: str, dev_id: str, device: Device): """Add device to YAML configuration file.""" - with open(path, 'a') as out: - device = {device.dev_id: { - ATTR_NAME: device.name, - ATTR_MAC: device.mac, - ATTR_ICON: device.icon, - 'picture': device.config_picture, - 'track': device.track, - CONF_AWAY_HIDE: device.away_hide, - }} - out.write('\n') + with open(path, "a") as out: + device = { + device.dev_id: { + ATTR_NAME: device.name, + ATTR_MAC: device.mac, + ATTR_ICON: device.icon, + "picture": device.config_picture, + "track": device.track, + CONF_AWAY_HIDE: device.away_hide, + } + } + out.write("\n") out.write(dump(device)) @@ -532,5 +636,6 @@ def get_gravatar_for_email(email: str): Async friendly. """ import hashlib - url = 'https://www.gravatar.com/avatar/{}.jpg?s=80&d=wavatar' - return url.format(hashlib.md5(email.encode('utf-8').lower()).hexdigest()) + + url = "https://www.gravatar.com/avatar/{}.jpg?s=80&d=wavatar" + return url.format(hashlib.md5(email.encode("utf-8").lower()).hexdigest()) diff --git a/homeassistant/components/device_tracker/setup.py b/homeassistant/components/device_tracker/setup.py index 4b4ce6ac1c4..e6edb5f63ac 100644 --- a/homeassistant/components/device_tracker/setup.py +++ b/homeassistant/components/device_tracker/setup.py @@ -12,10 +12,7 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.helpers.event import async_track_time_interval from homeassistant.util import dt as dt_util -from homeassistant.const import ( - ATTR_LATITUDE, - ATTR_LONGITUDE, -) +from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE from .const import ( @@ -33,10 +30,10 @@ class DeviceTrackerPlatform: """Class to hold platform information.""" LEGACY_SETUP = ( - 'async_get_scanner', - 'get_scanner', - 'async_setup_scanner', - 'setup_scanner', + "async_get_scanner", + "get_scanner", + "async_setup_scanner", + "setup_scanner", ) name = attr.ib(type=str) @@ -46,9 +43,7 @@ class DeviceTrackerPlatform: @property def type(self): """Return platform type.""" - for methods, platform_type in ( - (self.LEGACY_SETUP, PLATFORM_TYPE_LEGACY), - ): + for methods, platform_type in ((self.LEGACY_SETUP, PLATFORM_TYPE_LEGACY),): for meth in methods: if hasattr(self.platform, meth): return platform_type @@ -61,26 +56,33 @@ class DeviceTrackerPlatform: try: scanner = None setup = None - if hasattr(self.platform, 'async_get_scanner'): + if hasattr(self.platform, "async_get_scanner"): scanner = await self.platform.async_get_scanner( - hass, {DOMAIN: self.config}) - elif hasattr(self.platform, 'get_scanner'): + hass, {DOMAIN: self.config} + ) + elif hasattr(self.platform, "get_scanner"): scanner = await hass.async_add_job( - self.platform.get_scanner, hass, {DOMAIN: self.config}) - elif hasattr(self.platform, 'async_setup_scanner'): + self.platform.get_scanner, hass, {DOMAIN: self.config} + ) + elif hasattr(self.platform, "async_setup_scanner"): setup = await self.platform.async_setup_scanner( - hass, self.config, tracker.async_see, discovery_info) - elif hasattr(self.platform, 'setup_scanner'): + hass, self.config, tracker.async_see, discovery_info + ) + elif hasattr(self.platform, "setup_scanner"): setup = await hass.async_add_job( - self.platform.setup_scanner, hass, self.config, - tracker.see, discovery_info) + self.platform.setup_scanner, + hass, + self.config, + tracker.see, + discovery_info, + ) else: - raise HomeAssistantError( - "Invalid legacy device_tracker platform.") + raise HomeAssistantError("Invalid legacy device_tracker platform.") if scanner: async_setup_scanner_platform( - hass, self.config, scanner, tracker.async_see, self.type) + hass, self.config, scanner, tracker.async_see, self.type + ) return if not setup: @@ -95,27 +97,32 @@ async def async_extract_config(hass, config): """Extract device tracker config and split between legacy and modern.""" legacy = [] - for platform in await asyncio.gather(*( + for platform in await asyncio.gather( + *( async_create_platform_type(hass, config, p_type, p_config) for p_type, p_config in config_per_platform(config, DOMAIN) - )): + ) + ): if platform is None: continue if platform.type == PLATFORM_TYPE_LEGACY: legacy.append(platform) else: - raise ValueError("Unable to determine type for {}: {}".format( - platform.name, platform.type)) + raise ValueError( + "Unable to determine type for {}: {}".format( + platform.name, platform.type + ) + ) return legacy -async def async_create_platform_type(hass, config, p_type, p_config) \ - -> Optional[DeviceTrackerPlatform]: +async def async_create_platform_type( + hass, config, p_type, p_config +) -> Optional[DeviceTrackerPlatform]: """Determine type of platform.""" - platform = await async_prepare_setup_platform( - hass, config, DOMAIN, p_type) + platform = await async_prepare_setup_platform(hass, config, DOMAIN, p_type) if platform is None: return None @@ -124,9 +131,13 @@ async def async_create_platform_type(hass, config, p_type, p_config) \ @callback -def async_setup_scanner_platform(hass: HomeAssistantType, config: ConfigType, - scanner: Any, async_see_device: Callable, - platform: str): +def async_setup_scanner_platform( + hass: HomeAssistantType, + config: ConfigType, + scanner: Any, + async_see_device: Callable, + platform: str, +): """Set up the connect scanner-based platform to device tracker. This method must be run in the event loop. @@ -143,7 +154,10 @@ def async_setup_scanner_platform(hass: HomeAssistantType, config: ConfigType, if update_lock.locked(): LOGGER.warning( "Updating device list from %s took longer than the scheduled " - "scan interval %s", platform, interval) + "scan interval %s", + platform, + interval, + ) return async with update_lock: @@ -157,26 +171,27 @@ def async_setup_scanner_platform(hass: HomeAssistantType, config: ConfigType, seen.add(mac) try: - extra_attributes = \ - await scanner.async_get_extra_attributes(mac) + extra_attributes = await scanner.async_get_extra_attributes(mac) except NotImplementedError: extra_attributes = dict() kwargs = { - 'mac': mac, - 'host_name': host_name, - 'source_type': SOURCE_TYPE_ROUTER, - 'attributes': { - 'scanner': scanner.__class__.__name__, - **extra_attributes - } + "mac": mac, + "host_name": host_name, + "source_type": SOURCE_TYPE_ROUTER, + "attributes": { + "scanner": scanner.__class__.__name__, + **extra_attributes, + }, } zone_home = hass.states.get(hass.components.zone.ENTITY_ID_HOME) if zone_home: - kwargs['gps'] = [zone_home.attributes[ATTR_LATITUDE], - zone_home.attributes[ATTR_LONGITUDE]] - kwargs['gps_accuracy'] = 0 + kwargs["gps"] = [ + zone_home.attributes[ATTR_LATITUDE], + zone_home.attributes[ATTR_LONGITUDE], + ] + kwargs["gps_accuracy"] = 0 hass.async_create_task(async_see_device(**kwargs)) diff --git a/homeassistant/components/dht/sensor.py b/homeassistant/components/dht/sensor.py index d544bfa74e8..6ea5e7a46a2 100644 --- a/homeassistant/components/dht/sensor.py +++ b/homeassistant/components/dht/sensor.py @@ -6,42 +6,46 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA import homeassistant.helpers.config_validation as cv -from homeassistant.const import ( - TEMP_FAHRENHEIT, CONF_NAME, CONF_MONITORED_CONDITIONS) +from homeassistant.const import TEMP_FAHRENHEIT, CONF_NAME, CONF_MONITORED_CONDITIONS from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from homeassistant.util.temperature import celsius_to_fahrenheit _LOGGER = logging.getLogger(__name__) -CONF_PIN = 'pin' -CONF_SENSOR = 'sensor' -CONF_HUMIDITY_OFFSET = 'humidity_offset' -CONF_TEMPERATURE_OFFSET = 'temperature_offset' +CONF_PIN = "pin" +CONF_SENSOR = "sensor" +CONF_HUMIDITY_OFFSET = "humidity_offset" +CONF_TEMPERATURE_OFFSET = "temperature_offset" -DEFAULT_NAME = 'DHT Sensor' +DEFAULT_NAME = "DHT Sensor" # DHT11 is able to deliver data once per second, DHT22 once every two MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30) -SENSOR_TEMPERATURE = 'temperature' -SENSOR_HUMIDITY = 'humidity' +SENSOR_TEMPERATURE = "temperature" +SENSOR_HUMIDITY = "humidity" SENSOR_TYPES = { - SENSOR_TEMPERATURE: ['Temperature', None], - SENSOR_HUMIDITY: ['Humidity', '%'] + SENSOR_TEMPERATURE: ["Temperature", None], + SENSOR_HUMIDITY: ["Humidity", "%"], } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_SENSOR): cv.string, - vol.Required(CONF_PIN): cv.string, - vol.Optional(CONF_MONITORED_CONDITIONS, default=[]): - vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_TEMPERATURE_OFFSET, default=0): - vol.All(vol.Coerce(float), vol.Range(min=-100, max=100)), - vol.Optional(CONF_HUMIDITY_OFFSET, default=0): - vol.All(vol.Coerce(float), vol.Range(min=-100, max=100)) -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_SENSOR): cv.string, + vol.Required(CONF_PIN): cv.string, + vol.Optional(CONF_MONITORED_CONDITIONS, default=[]): vol.All( + cv.ensure_list, [vol.In(SENSOR_TYPES)] + ), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_TEMPERATURE_OFFSET, default=0): vol.All( + vol.Coerce(float), vol.Range(min=-100, max=100) + ), + vol.Optional(CONF_HUMIDITY_OFFSET, default=0): vol.All( + vol.Coerce(float), vol.Range(min=-100, max=100) + ), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -69,9 +73,16 @@ def setup_platform(hass, config, add_entities, discovery_info=None): try: for variable in config[CONF_MONITORED_CONDITIONS]: - dev.append(DHTSensor( - data, variable, SENSOR_TYPES[variable][1], name, - temperature_offset, humidity_offset)) + dev.append( + DHTSensor( + data, + variable, + SENSOR_TYPES[variable][1], + name, + temperature_offset, + humidity_offset, + ) + ) except KeyError: pass @@ -81,8 +92,15 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class DHTSensor(Entity): """Implementation of the DHT sensor.""" - def __init__(self, dht_client, sensor_type, temp_unit, name, - temperature_offset, humidity_offset): + def __init__( + self, + dht_client, + sensor_type, + temp_unit, + name, + temperature_offset, + humidity_offset, + ): """Initialize the sensor.""" self.client_name = name self._name = SENSOR_TYPES[sensor_type][0] @@ -97,7 +115,7 @@ class DHTSensor(Entity): @property def name(self): """Return the name of the sensor.""" - return '{} {}'.format(self.client_name, self._name) + return "{} {}".format(self.client_name, self._name) @property def state(self): @@ -118,16 +136,18 @@ class DHTSensor(Entity): if self.type == SENSOR_TEMPERATURE and SENSOR_TEMPERATURE in data: temperature = data[SENSOR_TEMPERATURE] - _LOGGER.debug("Temperature %.1f \u00b0C + offset %.1f", - temperature, temperature_offset) + _LOGGER.debug( + "Temperature %.1f \u00b0C + offset %.1f", + temperature, + temperature_offset, + ) if -20 <= temperature < 80: self._state = round(temperature + temperature_offset, 1) if self.temp_unit == TEMP_FAHRENHEIT: self._state = round(celsius_to_fahrenheit(temperature), 1) elif self.type == SENSOR_HUMIDITY and SENSOR_HUMIDITY in data: humidity = data[SENSOR_HUMIDITY] - _LOGGER.debug("Humidity %.1f%% + offset %.1f", - humidity, humidity_offset) + _LOGGER.debug("Humidity %.1f%% + offset %.1f", humidity, humidity_offset) if 0 <= humidity <= 100: self._state = round(humidity + humidity_offset, 1) @@ -145,8 +165,7 @@ class DHTClient: @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): """Get the latest data the DHT sensor.""" - humidity, temperature = self.adafruit_dht.read_retry( - self.sensor, self.pin) + humidity, temperature = self.adafruit_dht.read_retry(self.sensor, self.pin) if temperature: self.data[SENSOR_TEMPERATURE] = temperature if humidity: diff --git a/homeassistant/components/dialogflow/__init__.py b/homeassistant/components/dialogflow/__init__.py index 3bf11a46098..4f2876df296 100644 --- a/homeassistant/components/dialogflow/__init__.py +++ b/homeassistant/components/dialogflow/__init__.py @@ -15,9 +15,7 @@ _LOGGER = logging.getLogger(__name__) SOURCE = "Home Assistant Dialogflow" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: {} -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema({DOMAIN: {}}, extra=vol.ALLOW_EXTRA) class DialogFlowError(HomeAssistantError): @@ -37,7 +35,7 @@ async def handle_webhook(hass, webhook_id, request): try: response = await async_handle_message(hass, message) - return b'' if response is None else web.json_response(response) + return b"" if response is None else web.json_response(response) except DialogFlowError as err: _LOGGER.warning(str(err)) @@ -47,8 +45,7 @@ async def handle_webhook(hass, webhook_id, request): _LOGGER.warning(str(err)) return web.json_response( dialogflow_error_response( - message, - "This intent is not yet configured within Home Assistant." + message, "This intent is not yet configured within Home Assistant." ) ) @@ -56,21 +53,22 @@ async def handle_webhook(hass, webhook_id, request): _LOGGER.warning(str(err)) return web.json_response( dialogflow_error_response( - message, - "Invalid slot information received for this intent." + message, "Invalid slot information received for this intent." ) ) except intent.IntentError as err: _LOGGER.warning(str(err)) return web.json_response( - dialogflow_error_response(message, "Error handling intent.")) + dialogflow_error_response(message, "Error handling intent.") + ) async def async_setup_entry(hass, entry): """Configure based on config entry.""" hass.components.webhook.async_register( - DOMAIN, 'DialogFlow', entry.data[CONF_WEBHOOK_ID], handle_webhook) + DOMAIN, "DialogFlow", entry.data[CONF_WEBHOOK_ID], handle_webhook + ) return True @@ -86,36 +84,38 @@ async_remove_entry = config_entry_flow.webhook_async_remove_entry def dialogflow_error_response(message, error): """Return a response saying the error message.""" - dialogflow_response = DialogflowResponse(message['result']['parameters']) + dialogflow_response = DialogflowResponse(message["result"]["parameters"]) dialogflow_response.add_speech(error) return dialogflow_response.as_dict() async def async_handle_message(hass, message): """Handle a DialogFlow message.""" - req = message.get('result') - action_incomplete = req['actionIncomplete'] + req = message.get("result") + action_incomplete = req["actionIncomplete"] if action_incomplete: return None - action = req.get('action', '') - parameters = req.get('parameters').copy() + action = req.get("action", "") + parameters = req.get("parameters").copy() parameters["dialogflow_query"] = message dialogflow_response = DialogflowResponse(parameters) if action == "": raise DialogFlowError( - "You have not defined an action in your Dialogflow intent.") + "You have not defined an action in your Dialogflow intent." + ) intent_response = await intent.async_handle( - hass, DOMAIN, action, - {key: {'value': value} for key, value - in parameters.items()}) + hass, + DOMAIN, + action, + {key: {"value": value} for key, value in parameters.items()}, + ) - if 'plain' in intent_response.speech: - dialogflow_response.add_speech( - intent_response.speech['plain']['speech']) + if "plain" in intent_response.speech: + dialogflow_response.add_speech(intent_response.speech["plain"]["speech"]) return dialogflow_response.as_dict() @@ -129,7 +129,7 @@ class DialogflowResponse: self.parameters = {} # Parameter names replace '.' and '-' for '_' for key, value in parameters.items(): - underscored_key = key.replace('.', '_').replace('-', '_') + underscored_key = key.replace(".", "_").replace("-", "_") self.parameters[underscored_key] = value def add_speech(self, text): @@ -143,8 +143,4 @@ class DialogflowResponse: def as_dict(self): """Return response in a Dialogflow valid dictionary.""" - return { - 'speech': self.speech, - 'displayText': self.speech, - 'source': SOURCE, - } + return {"speech": self.speech, "displayText": self.speech, "source": SOURCE} diff --git a/homeassistant/components/dialogflow/config_flow.py b/homeassistant/components/dialogflow/config_flow.py index aa6f9f6f515..8e5be873f72 100644 --- a/homeassistant/components/dialogflow/config_flow.py +++ b/homeassistant/components/dialogflow/config_flow.py @@ -5,9 +5,9 @@ from .const import DOMAIN config_entry_flow.register_webhook_flow( DOMAIN, - 'Dialogflow Webhook', + "Dialogflow Webhook", { - 'dialogflow_url': 'https://dialogflow.com/docs/fulfillment#webhook', - 'docs_url': 'https://www.home-assistant.io/components/dialogflow/' - } + "dialogflow_url": "https://dialogflow.com/docs/fulfillment#webhook", + "docs_url": "https://www.home-assistant.io/components/dialogflow/", + }, ) diff --git a/homeassistant/components/digital_ocean/__init__.py b/homeassistant/components/digital_ocean/__init__.py index 9e034b2428d..18dfb49365a 100644 --- a/homeassistant/components/digital_ocean/__init__.py +++ b/homeassistant/components/digital_ocean/__init__.py @@ -10,31 +10,30 @@ import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -ATTR_CREATED_AT = 'created_at' -ATTR_DROPLET_ID = 'droplet_id' -ATTR_DROPLET_NAME = 'droplet_name' -ATTR_FEATURES = 'features' -ATTR_IPV4_ADDRESS = 'ipv4_address' -ATTR_IPV6_ADDRESS = 'ipv6_address' -ATTR_MEMORY = 'memory' -ATTR_REGION = 'region' -ATTR_VCPUS = 'vcpus' +ATTR_CREATED_AT = "created_at" +ATTR_DROPLET_ID = "droplet_id" +ATTR_DROPLET_NAME = "droplet_name" +ATTR_FEATURES = "features" +ATTR_IPV4_ADDRESS = "ipv4_address" +ATTR_IPV6_ADDRESS = "ipv6_address" +ATTR_MEMORY = "memory" +ATTR_REGION = "region" +ATTR_VCPUS = "vcpus" -ATTRIBUTION = 'Data provided by Digital Ocean' +ATTRIBUTION = "Data provided by Digital Ocean" -CONF_DROPLETS = 'droplets' +CONF_DROPLETS = "droplets" -DATA_DIGITAL_OCEAN = 'data_do' -DIGITAL_OCEAN_PLATFORMS = ['switch', 'binary_sensor'] -DOMAIN = 'digital_ocean' +DATA_DIGITAL_OCEAN = "data_do" +DIGITAL_OCEAN_PLATFORMS = ["switch", "binary_sensor"] +DOMAIN = "digital_ocean" MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_ACCESS_TOKEN): cv.string, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + {DOMAIN: vol.Schema({vol.Required(CONF_ACCESS_TOKEN): cv.string})}, + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): diff --git a/homeassistant/components/digital_ocean/binary_sensor.py b/homeassistant/components/digital_ocean/binary_sensor.py index 83406247a07..50c87907774 100644 --- a/homeassistant/components/digital_ocean/binary_sensor.py +++ b/homeassistant/components/digital_ocean/binary_sensor.py @@ -3,23 +3,32 @@ import logging import voluptuous as vol -from homeassistant.components.binary_sensor import ( - PLATFORM_SCHEMA, BinarySensorDevice) +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice from homeassistant.const import ATTR_ATTRIBUTION import homeassistant.helpers.config_validation as cv from . import ( - ATTR_CREATED_AT, ATTR_DROPLET_ID, ATTR_DROPLET_NAME, ATTR_FEATURES, - ATTR_IPV4_ADDRESS, ATTR_IPV6_ADDRESS, ATTR_MEMORY, ATTR_REGION, ATTR_VCPUS, - ATTRIBUTION, CONF_DROPLETS, DATA_DIGITAL_OCEAN) + ATTR_CREATED_AT, + ATTR_DROPLET_ID, + ATTR_DROPLET_NAME, + ATTR_FEATURES, + ATTR_IPV4_ADDRESS, + ATTR_IPV6_ADDRESS, + ATTR_MEMORY, + ATTR_REGION, + ATTR_VCPUS, + ATTRIBUTION, + CONF_DROPLETS, + DATA_DIGITAL_OCEAN, +) _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'Droplet' -DEFAULT_DEVICE_CLASS = 'moving' -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_DROPLETS): vol.All(cv.ensure_list, [cv.string]), -}) +DEFAULT_NAME = "Droplet" +DEFAULT_DEVICE_CLASS = "moving" +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_DROPLETS): vol.All(cv.ensure_list, [cv.string])} +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -59,7 +68,7 @@ class DigitalOceanBinarySensor(BinarySensorDevice): @property def is_on(self): """Return true if the binary sensor is on.""" - return self.data.status == 'active' + return self.data.status == "active" @property def device_class(self): @@ -78,7 +87,7 @@ class DigitalOceanBinarySensor(BinarySensorDevice): ATTR_IPV4_ADDRESS: self.data.ip_address, ATTR_IPV6_ADDRESS: self.data.ip_v6_address, ATTR_MEMORY: self.data.memory, - ATTR_REGION: self.data.region['name'], + ATTR_REGION: self.data.region["name"], ATTR_VCPUS: self.data.vcpus, } diff --git a/homeassistant/components/digital_ocean/switch.py b/homeassistant/components/digital_ocean/switch.py index 8016ccef0ea..95d2e15a510 100644 --- a/homeassistant/components/digital_ocean/switch.py +++ b/homeassistant/components/digital_ocean/switch.py @@ -8,17 +8,27 @@ from homeassistant.const import ATTR_ATTRIBUTION import homeassistant.helpers.config_validation as cv from . import ( - ATTR_CREATED_AT, ATTR_DROPLET_ID, ATTR_DROPLET_NAME, ATTR_FEATURES, - ATTR_IPV4_ADDRESS, ATTR_IPV6_ADDRESS, ATTR_MEMORY, ATTR_REGION, ATTR_VCPUS, - ATTRIBUTION, CONF_DROPLETS, DATA_DIGITAL_OCEAN) + ATTR_CREATED_AT, + ATTR_DROPLET_ID, + ATTR_DROPLET_NAME, + ATTR_FEATURES, + ATTR_IPV4_ADDRESS, + ATTR_IPV6_ADDRESS, + ATTR_MEMORY, + ATTR_REGION, + ATTR_VCPUS, + ATTRIBUTION, + CONF_DROPLETS, + DATA_DIGITAL_OCEAN, +) _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'Droplet' +DEFAULT_NAME = "Droplet" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_DROPLETS): vol.All(cv.ensure_list, [cv.string]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_DROPLETS): vol.All(cv.ensure_list, [cv.string])} +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -58,7 +68,7 @@ class DigitalOceanSwitch(SwitchDevice): @property def is_on(self): """Return true if switch is on.""" - return self.data.status == 'active' + return self.data.status == "active" @property def device_state_attributes(self): @@ -72,18 +82,18 @@ class DigitalOceanSwitch(SwitchDevice): ATTR_IPV4_ADDRESS: self.data.ip_address, ATTR_IPV6_ADDRESS: self.data.ip_v6_address, ATTR_MEMORY: self.data.memory, - ATTR_REGION: self.data.region['name'], + ATTR_REGION: self.data.region["name"], ATTR_VCPUS: self.data.vcpus, } def turn_on(self, **kwargs): """Boot-up the droplet.""" - if self.data.status != 'active': + if self.data.status != "active": self.data.power_on() def turn_off(self, **kwargs): """Shutdown the droplet.""" - if self.data.status == 'active': + if self.data.status == "active": self.data.power_off() def update(self): diff --git a/homeassistant/components/digitalloggers/switch.py b/homeassistant/components/digitalloggers/switch.py index 4d1a87c44f9..d80385d0f54 100644 --- a/homeassistant/components/digitalloggers/switch.py +++ b/homeassistant/components/digitalloggers/switch.py @@ -4,34 +4,43 @@ from datetime import timedelta import voluptuous as vol -from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) +from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA from homeassistant.const import ( - CONF_HOST, CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_TIMEOUT) + CONF_HOST, + CONF_NAME, + CONF_USERNAME, + CONF_PASSWORD, + CONF_TIMEOUT, +) import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -CONF_CYCLETIME = 'cycletime' +CONF_CYCLETIME = "cycletime" -DEFAULT_NAME = 'DINRelay' -DEFAULT_USERNAME = 'admin' -DEFAULT_PASSWORD = 'admin' +DEFAULT_NAME = "DINRelay" +DEFAULT_USERNAME = "admin" +DEFAULT_PASSWORD = "admin" DEFAULT_TIMEOUT = 20 DEFAULT_CYCLETIME = 2 MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=5) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, - vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string, - vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): - vol.All(vol.Coerce(int), vol.Range(min=1, max=600)), - vol.Optional(CONF_CYCLETIME, default=DEFAULT_CYCLETIME): - vol.All(vol.Coerce(int), vol.Range(min=1, max=600)), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, + vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string, + vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): vol.All( + vol.Coerce(int), vol.Range(min=1, max=600) + ), + vol.Optional(CONF_CYCLETIME, default=DEFAULT_CYCLETIME): vol.All( + vol.Coerce(int), vol.Range(min=1, max=600) + ), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -46,8 +55,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): cycl = config.get(CONF_CYCLETIME) power_switch = dlipower.PowerSwitch( - hostname=host, userid=user, password=pswd, - timeout=tout, cycletime=cycl + hostname=host, userid=user, password=pswd, timeout=tout, cycletime=cycl ) if not power_switch.verify(): @@ -58,8 +66,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): parent_device = DINRelayDevice(power_switch) outlets.extend( - DINRelay(controller_name, parent_device, outlet) - for outlet in power_switch[0:] + DINRelay(controller_name, parent_device, outlet) for outlet in power_switch[0:] ) add_entities(outlets) @@ -76,15 +83,12 @@ class DINRelay(SwitchDevice): self._outlet_number = self._outlet.outlet_number self._name = self._outlet.description - self._state = self._outlet.state == 'ON' + self._state = self._outlet.state == "ON" @property def name(self): """Return the display name of this relay.""" - return '{}_{}'.format( - self._controller_name, - self._name - ) + return "{}_{}".format(self._controller_name, self._name) @property def is_on(self): @@ -108,11 +112,10 @@ class DINRelay(SwitchDevice): """Trigger update for all switches on the parent device.""" self._parent_device.update() - outlet_status = self._parent_device.get_outlet_status( - self._outlet_number) + outlet_status = self._parent_device.get_outlet_status(self._outlet_number) self._name = outlet_status[1] - self._state = outlet_status[2] == 'ON' + self._state = outlet_status[2] == "ON" class DINRelayDevice: diff --git a/homeassistant/components/directv/media_player.py b/homeassistant/components/directv/media_player.py index aaffd44d572..2be2544cec1 100644 --- a/homeassistant/components/directv/media_player.py +++ b/homeassistant/components/directv/media_player.py @@ -3,45 +3,73 @@ import logging import requests import voluptuous as vol -from homeassistant.components.media_player import ( - MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA from homeassistant.components.media_player.const import ( - MEDIA_TYPE_CHANNEL, MEDIA_TYPE_MOVIE, MEDIA_TYPE_TVSHOW, - SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON) + MEDIA_TYPE_CHANNEL, + MEDIA_TYPE_MOVIE, + MEDIA_TYPE_TVSHOW, + SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, + SUPPORT_PLAY, + SUPPORT_PLAY_MEDIA, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_STOP, + SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, +) from homeassistant.const import ( - CONF_DEVICE, CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_PAUSED, - STATE_PLAYING) + CONF_DEVICE, + CONF_HOST, + CONF_NAME, + CONF_PORT, + STATE_OFF, + STATE_PAUSED, + STATE_PLAYING, +) import homeassistant.helpers.config_validation as cv import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) -ATTR_MEDIA_CURRENTLY_RECORDING = 'media_currently_recording' -ATTR_MEDIA_RATING = 'media_rating' -ATTR_MEDIA_RECORDED = 'media_recorded' -ATTR_MEDIA_START_TIME = 'media_start_time' +ATTR_MEDIA_CURRENTLY_RECORDING = "media_currently_recording" +ATTR_MEDIA_RATING = "media_rating" +ATTR_MEDIA_RECORDED = "media_recorded" +ATTR_MEDIA_START_TIME = "media_start_time" -DEFAULT_DEVICE = '0' +DEFAULT_DEVICE = "0" DEFAULT_NAME = "DirecTV Receiver" DEFAULT_PORT = 8080 -SUPPORT_DTV = SUPPORT_PAUSE | SUPPORT_TURN_ON | SUPPORT_TURN_OFF | \ - SUPPORT_PLAY_MEDIA | SUPPORT_STOP | SUPPORT_NEXT_TRACK | \ - SUPPORT_PREVIOUS_TRACK | SUPPORT_PLAY +SUPPORT_DTV = ( + SUPPORT_PAUSE + | SUPPORT_TURN_ON + | SUPPORT_TURN_OFF + | SUPPORT_PLAY_MEDIA + | SUPPORT_STOP + | SUPPORT_NEXT_TRACK + | SUPPORT_PREVIOUS_TRACK + | SUPPORT_PLAY +) -SUPPORT_DTV_CLIENT = SUPPORT_PAUSE | \ - SUPPORT_PLAY_MEDIA | SUPPORT_STOP | SUPPORT_NEXT_TRACK | \ - SUPPORT_PREVIOUS_TRACK | SUPPORT_PLAY +SUPPORT_DTV_CLIENT = ( + SUPPORT_PAUSE + | SUPPORT_PLAY_MEDIA + | SUPPORT_STOP + | SUPPORT_NEXT_TRACK + | SUPPORT_PREVIOUS_TRACK + | SUPPORT_PLAY +) -DATA_DIRECTV = 'data_directv' +DATA_DIRECTV = "data_directv" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_DEVICE, default=DEFAULT_DEVICE): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_DEVICE, default=DEFAULT_DEVICE): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -50,21 +78,29 @@ def setup_platform(hass, config, add_entities, discovery_info=None): hosts = [] if CONF_HOST in config: - _LOGGER.debug("Adding configured device %s with client address %s ", - config.get(CONF_NAME), config.get(CONF_DEVICE)) - hosts.append([ - config.get(CONF_NAME), config.get(CONF_HOST), - config.get(CONF_PORT), config.get(CONF_DEVICE) - ]) + _LOGGER.debug( + "Adding configured device %s with client address %s ", + config.get(CONF_NAME), + config.get(CONF_DEVICE), + ) + hosts.append( + [ + config.get(CONF_NAME), + config.get(CONF_HOST), + config.get(CONF_PORT), + config.get(CONF_DEVICE), + ] + ) elif discovery_info: - host = discovery_info.get('host') - name = 'DirecTV_{}'.format(discovery_info.get('serial', '')) + host = discovery_info.get("host") + name = "DirecTV_{}".format(discovery_info.get("serial", "")) # Attempt to discover additional RVU units _LOGGER.debug("Doing discovery of DirecTV devices on %s", host) from DirectPy import DIRECTV + dtv = DIRECTV(host, DEFAULT_PORT) try: resp = dtv.get_locations() @@ -73,12 +109,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): # Make sure that this device is not already configured # Comparing based on host (IP) and clientAddr. _LOGGER.debug("Request exception %s trying to get locations", ex) - resp = { - 'locations': [{ - 'locationName': name, - 'clientAddr': DEFAULT_DEVICE - }] - } + resp = {"locations": [{"locationName": name, "clientAddr": DEFAULT_DEVICE}]} _LOGGER.debug("Known devices: %s", known_devices) for loc in resp.get("locations") or []: @@ -88,18 +119,28 @@ def setup_platform(hass, config, add_entities, discovery_info=None): # Make sure that this device is not already configured # Comparing based on host (IP) and clientAddr. if (host, loc["clientAddr"]) in known_devices: - _LOGGER.debug("Discovered device %s on host %s with " - "client address %s is already " - "configured", - str.title(loc["locationName"]), - host, loc["clientAddr"]) + _LOGGER.debug( + "Discovered device %s on host %s with " + "client address %s is already " + "configured", + str.title(loc["locationName"]), + host, + loc["clientAddr"], + ) else: - _LOGGER.debug("Adding discovered device %s with" - " client address %s", - str.title(loc["locationName"]), - loc["clientAddr"]) - hosts.append([str.title(loc["locationName"]), host, - DEFAULT_PORT, loc["clientAddr"]]) + _LOGGER.debug( + "Adding discovered device %s with" " client address %s", + str.title(loc["locationName"]), + loc["clientAddr"], + ) + hosts.append( + [ + str.title(loc["locationName"]), + host, + DEFAULT_PORT, + loc["clientAddr"], + ] + ) dtvs = [] @@ -116,6 +157,7 @@ class DirecTvDevice(MediaPlayerDevice): def __init__(self, name, host, port, device): """Initialize the device.""" from DirectPy import DIRECTV + self.dtv = DIRECTV(host, port, device) self._name = name self._is_standby = True @@ -124,14 +166,13 @@ class DirecTvDevice(MediaPlayerDevice): self._paused = None self._last_position = None self._is_recorded = None - self._is_client = device != '0' + self._is_client = device != "0" self._assumed_state = None self._available = False self._first_error_timestamp = None if self._is_client: - _LOGGER.debug("Created DirecTV client %s for device %s", - self._name, device) + _LOGGER.debug("Created DirecTV client %s for device %s", self._name, device) else: _LOGGER.debug("Created DirecTV device for %s", self._name) @@ -150,22 +191,22 @@ class DirecTvDevice(MediaPlayerDevice): self._last_update = None else: self._current = self.dtv.get_tuned() - if self._current['status']['code'] == 200: + if self._current["status"]["code"] == 200: self._first_error_timestamp = None - self._is_recorded = self._current.get('uniqueId')\ - is not None - self._paused = self._last_position == \ - self._current['offset'] + self._is_recorded = self._current.get("uniqueId") is not None + self._paused = self._last_position == self._current["offset"] self._assumed_state = self._is_recorded - self._last_position = self._current['offset'] - self._last_update = dt_util.utcnow() if not self._paused \ - or self._last_update is None else self._last_update + self._last_position = self._current["offset"] + self._last_update = ( + dt_util.utcnow() + if not self._paused or self._last_update is None + else self._last_update + ) else: # If an error is received then only set to unavailable if # this started at least 1 minute ago. log_message = "{}: Invalid status {} received".format( - self.entity_id, - self._current['status']['code'] + self.entity_id, self._current["status"]["code"] ) if self._check_state_available(): _LOGGER.debug(log_message) @@ -173,13 +214,17 @@ class DirecTvDevice(MediaPlayerDevice): _LOGGER.error(log_message) except requests.RequestException as ex: - _LOGGER.error("%s: Request error trying to update current status: " - "%s", self.entity_id, ex) + _LOGGER.error( + "%s: Request error trying to update current status: " "%s", + self.entity_id, + ex, + ) self._check_state_available() except Exception as ex: - _LOGGER.error("%s: Exception trying to update current status: %s", - self.entity_id, ex) + _LOGGER.error( + "%s: Exception trying to update current status: %s", self.entity_id, ex + ) self._available = False if not self._first_error_timestamp: self._first_error_timestamp = dt_util.utcnow() @@ -201,8 +246,7 @@ class DirecTvDevice(MediaPlayerDevice): """Return device specific state attributes.""" attributes = {} if not self._is_standby: - attributes[ATTR_MEDIA_CURRENTLY_RECORDING] =\ - self.media_currently_recording + attributes[ATTR_MEDIA_CURRENTLY_RECORDING] = self.media_currently_recording attributes[ATTR_MEDIA_RATING] = self.media_rating attributes[ATTR_MEDIA_RECORDED] = self.media_recorded attributes[ATTR_MEDIA_START_TIME] = self.media_start_time @@ -245,7 +289,7 @@ class DirecTvDevice(MediaPlayerDevice): if self._is_standby: return None - return self._current['programId'] + return self._current["programId"] @property def media_content_type(self): @@ -253,7 +297,7 @@ class DirecTvDevice(MediaPlayerDevice): if self._is_standby: return None - if 'episodeTitle' in self._current: + if "episodeTitle" in self._current: return MEDIA_TYPE_TVSHOW return MEDIA_TYPE_MOVIE @@ -264,7 +308,7 @@ class DirecTvDevice(MediaPlayerDevice): if self._is_standby: return None - return self._current['duration'] + return self._current["duration"] @property def media_position(self): @@ -291,7 +335,7 @@ class DirecTvDevice(MediaPlayerDevice): if self._is_standby: return None - return self._current['title'] + return self._current["title"] @property def media_series_title(self): @@ -299,7 +343,7 @@ class DirecTvDevice(MediaPlayerDevice): if self._is_standby: return None - return self._current.get('episodeTitle') + return self._current.get("episodeTitle") @property def media_channel(self): @@ -307,8 +351,7 @@ class DirecTvDevice(MediaPlayerDevice): if self._is_standby: return None - return "{} ({})".format( - self._current['callsign'], self._current['major']) + return "{} ({})".format(self._current["callsign"], self._current["major"]) @property def source(self): @@ -316,7 +359,7 @@ class DirecTvDevice(MediaPlayerDevice): if self._is_standby: return None - return self._current['major'] + return self._current["major"] @property def supported_features(self): @@ -329,7 +372,7 @@ class DirecTvDevice(MediaPlayerDevice): if self._is_standby: return None - return self._current['isRecording'] + return self._current["isRecording"] @property def media_rating(self): @@ -337,7 +380,7 @@ class DirecTvDevice(MediaPlayerDevice): if self._is_standby: return None - return self._current['rating'] + return self._current["rating"] @property def media_recorded(self): @@ -353,8 +396,7 @@ class DirecTvDevice(MediaPlayerDevice): if self._is_standby: return None - return dt_util.as_local( - dt_util.utc_from_timestamp(self._current['startTime'])) + return dt_util.as_local(dt_util.utc_from_timestamp(self._current["startTime"])) def turn_on(self): """Turn on the receiver.""" @@ -362,7 +404,7 @@ class DirecTvDevice(MediaPlayerDevice): raise NotImplementedError() _LOGGER.debug("Turn on %s", self._name) - self.dtv.key_press('poweron') + self.dtv.key_press("poweron") def turn_off(self): """Turn off the receiver.""" @@ -370,38 +412,41 @@ class DirecTvDevice(MediaPlayerDevice): raise NotImplementedError() _LOGGER.debug("Turn off %s", self._name) - self.dtv.key_press('poweroff') + self.dtv.key_press("poweroff") def media_play(self): """Send play command.""" _LOGGER.debug("Play on %s", self._name) - self.dtv.key_press('play') + self.dtv.key_press("play") def media_pause(self): """Send pause command.""" _LOGGER.debug("Pause on %s", self._name) - self.dtv.key_press('pause') + self.dtv.key_press("pause") def media_stop(self): """Send stop command.""" _LOGGER.debug("Stop on %s", self._name) - self.dtv.key_press('stop') + self.dtv.key_press("stop") def media_previous_track(self): """Send rewind command.""" _LOGGER.debug("Rewind on %s", self._name) - self.dtv.key_press('rew') + self.dtv.key_press("rew") def media_next_track(self): """Send fast forward command.""" _LOGGER.debug("Fast forward on %s", self._name) - self.dtv.key_press('ffwd') + self.dtv.key_press("ffwd") def play_media(self, media_type, media_id, **kwargs): """Select input source.""" if media_type != MEDIA_TYPE_CHANNEL: - _LOGGER.error("Invalid media type %s. Only %s is supported", - media_type, MEDIA_TYPE_CHANNEL) + _LOGGER.error( + "Invalid media type %s. Only %s is supported", + media_type, + MEDIA_TYPE_CHANNEL, + ) return _LOGGER.debug("Changing channel on %s to %s", self._name, media_id) diff --git a/homeassistant/components/discogs/sensor.py b/homeassistant/components/discogs/sensor.py index f9f821668f9..64528f4ca5e 100644 --- a/homeassistant/components/discogs/sensor.py +++ b/homeassistant/components/discogs/sensor.py @@ -7,53 +7,60 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - ATTR_ATTRIBUTION, CONF_MONITORED_CONDITIONS, CONF_NAME, CONF_TOKEN) + ATTR_ATTRIBUTION, + CONF_MONITORED_CONDITIONS, + CONF_NAME, + CONF_TOKEN, +) from homeassistant.helpers.aiohttp_client import SERVER_SOFTWARE import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -ATTR_IDENTITY = 'identity' +ATTR_IDENTITY = "identity" ATTRIBUTION = "Data provided by Discogs" -DEFAULT_NAME = 'Discogs' +DEFAULT_NAME = "Discogs" -ICON_RECORD = 'mdi:album' -ICON_PLAYER = 'mdi:record-player' -UNIT_RECORDS = 'records' +ICON_RECORD = "mdi:album" +ICON_PLAYER = "mdi:record-player" +UNIT_RECORDS = "records" SCAN_INTERVAL = timedelta(minutes=10) -SENSOR_COLLECTION_TYPE = 'collection' -SENSOR_WANTLIST_TYPE = 'wantlist' -SENSOR_RANDOM_RECORD_TYPE = 'random_record' +SENSOR_COLLECTION_TYPE = "collection" +SENSOR_WANTLIST_TYPE = "wantlist" +SENSOR_RANDOM_RECORD_TYPE = "random_record" SENSORS = { SENSOR_COLLECTION_TYPE: { - 'name': 'Collection', - 'icon': ICON_RECORD, - 'unit_of_measurement': UNIT_RECORDS + "name": "Collection", + "icon": ICON_RECORD, + "unit_of_measurement": UNIT_RECORDS, }, SENSOR_WANTLIST_TYPE: { - 'name': 'Wantlist', - 'icon': ICON_RECORD, - 'unit_of_measurement': UNIT_RECORDS + "name": "Wantlist", + "icon": ICON_RECORD, + "unit_of_measurement": UNIT_RECORDS, }, SENSOR_RANDOM_RECORD_TYPE: { - 'name': 'Random Record', - 'icon': ICON_PLAYER, - 'unit_of_measurement': None + "name": "Random Record", + "icon": ICON_PLAYER, + "unit_of_measurement": None, }, } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_TOKEN): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSORS)): - vol.All(cv.ensure_list, [vol.In(SENSORS)]) -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_TOKEN): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSORS)): vol.All( + cv.ensure_list, [vol.In(SENSORS)] + ), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -64,14 +71,13 @@ def setup_platform(hass, config, add_entities, discovery_info=None): name = config[CONF_NAME] try: - discogs_client = discogs_client.Client( - SERVER_SOFTWARE, user_token=token) + discogs_client = discogs_client.Client(SERVER_SOFTWARE, user_token=token) discogs_data = { - 'user': discogs_client.identity().name, - 'folders': discogs_client.identity().collection_folders, - 'collection_count': discogs_client.identity().num_collection, - 'wantlist_count': discogs_client.identity().num_wantlist + "user": discogs_client.identity().name, + "folders": discogs_client.identity().collection_folders, + "collection_count": discogs_client.identity().num_collection, + "wantlist_count": discogs_client.identity().num_wantlist, } except discogs_client.exceptions.HTTPError: _LOGGER.error("API token is not valid") @@ -98,7 +104,7 @@ class DiscogsSensor(Entity): @property def name(self): """Return the name of the sensor.""" - return "{} {}".format(self._name, SENSORS[self._type]['name']) + return "{} {}".format(self._name, SENSORS[self._type]["name"]) @property def state(self): @@ -108,12 +114,12 @@ class DiscogsSensor(Entity): @property def icon(self): """Return the icon to use in the frontend, if any.""" - return SENSORS[self._type]['icon'] + return SENSORS[self._type]["icon"] @property def unit_of_measurement(self): """Return the unit this state is expressed in.""" - return SENSORS[self._type]['unit_of_measurement'] + return SENSORS[self._type]["unit_of_measurement"] @property def device_state_attributes(self): @@ -124,38 +130,39 @@ class DiscogsSensor(Entity): if self._type != SENSOR_RANDOM_RECORD_TYPE: return { ATTR_ATTRIBUTION: ATTRIBUTION, - ATTR_IDENTITY: self._discogs_data['user'], + ATTR_IDENTITY: self._discogs_data["user"], } return { - 'cat_no': self._attrs['labels'][0]['catno'], - 'cover_image': self._attrs['cover_image'], - 'format': "{} ({})".format( - self._attrs['formats'][0]['name'], - self._attrs['formats'][0]['descriptions'][0]), - 'label': self._attrs['labels'][0]['name'], - 'released': self._attrs['year'], + "cat_no": self._attrs["labels"][0]["catno"], + "cover_image": self._attrs["cover_image"], + "format": "{} ({})".format( + self._attrs["formats"][0]["name"], + self._attrs["formats"][0]["descriptions"][0], + ), + "label": self._attrs["labels"][0]["name"], + "released": self._attrs["year"], ATTR_ATTRIBUTION: ATTRIBUTION, - ATTR_IDENTITY: self._discogs_data['user'], + ATTR_IDENTITY: self._discogs_data["user"], } def get_random_record(self): """Get a random record suggestion from the user's collection.""" # Index 0 in the folders is the 'All' folder - collection = self._discogs_data['folders'][0] + collection = self._discogs_data["folders"][0] random_index = random.randrange(collection.count) random_record = collection.releases[random_index].release self._attrs = random_record.data return "{} - {}".format( - random_record.data['artists'][0]['name'], - random_record.data['title']) + random_record.data["artists"][0]["name"], random_record.data["title"] + ) def update(self): """Set state to the amount of records in user's collection.""" if self._type == SENSOR_COLLECTION_TYPE: - self._state = self._discogs_data['collection_count'] + self._state = self._discogs_data["collection_count"] elif self._type == SENSOR_WANTLIST_TYPE: - self._state = self._discogs_data['wantlist_count'] + self._state = self._discogs_data["wantlist_count"] else: self._state = self.get_random_record() diff --git a/homeassistant/components/discord/notify.py b/homeassistant/components/discord/notify.py index 75a434a3739..17ff0a192d0 100644 --- a/homeassistant/components/discord/notify.py +++ b/homeassistant/components/discord/notify.py @@ -7,17 +7,18 @@ import voluptuous as vol from homeassistant.const import CONF_TOKEN import homeassistant.helpers.config_validation as cv -from homeassistant.components.notify import (ATTR_DATA, ATTR_TARGET, - PLATFORM_SCHEMA, - BaseNotificationService) +from homeassistant.components.notify import ( + ATTR_DATA, + ATTR_TARGET, + PLATFORM_SCHEMA, + BaseNotificationService, +) _LOGGER = logging.getLogger(__name__) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_TOKEN): cv.string -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({vol.Required(CONF_TOKEN): cv.string}) -ATTR_IMAGES = 'images' +ATTR_IMAGES = "images" def get_service(hass, config, discovery_info=None): @@ -60,8 +61,8 @@ class DiscordNotificationService(BaseNotificationService): for image in data.get(ATTR_IMAGES): image_exists = await self.hass.async_add_executor_job( - self.file_exists, - image) + self.file_exists, image + ) if image_exists: images.append(image) @@ -78,9 +79,7 @@ class DiscordNotificationService(BaseNotificationService): channel = discord_bot.get_channel(channelid) if channel is None: - _LOGGER.warning( - "Channel not found for id: %s", - channelid) + _LOGGER.warning("Channel not found for id: %s", channelid) continue # Must create new instances of File for each channel. @@ -91,8 +90,7 @@ class DiscordNotificationService(BaseNotificationService): files.append(discord.File(image)) await channel.send(message, files=files) - except (discord.errors.HTTPException, - discord.errors.NotFound) as error: + except (discord.errors.HTTPException, discord.errors.NotFound) as error: _LOGGER.warning("Communication error: %s", error) await discord_bot.logout() await discord_bot.close() diff --git a/homeassistant/components/discovery/__init__.py b/homeassistant/components/discovery/__init__.py index 229e64ad682..5f1fd335d45 100644 --- a/homeassistant/components/discovery/__init__.py +++ b/homeassistant/components/discovery/__init__.py @@ -20,106 +20,110 @@ from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.helpers.discovery import async_load_platform, async_discover import homeassistant.util.dt as dt_util -DOMAIN = 'discovery' +DOMAIN = "discovery" SCAN_INTERVAL = timedelta(seconds=300) -SERVICE_APPLE_TV = 'apple_tv' -SERVICE_DAIKIN = 'daikin' -SERVICE_DLNA_DMR = 'dlna_dmr' -SERVICE_ENIGMA2 = 'enigma2' -SERVICE_FREEBOX = 'freebox' -SERVICE_HASS_IOS_APP = 'hass_ios' -SERVICE_HASSIO = 'hassio' -SERVICE_HEOS = 'heos' -SERVICE_IGD = 'igd' -SERVICE_KONNECTED = 'konnected' -SERVICE_MOBILE_APP = 'hass_mobile_app' -SERVICE_NETGEAR = 'netgear_router' -SERVICE_OCTOPRINT = 'octoprint' -SERVICE_ROKU = 'roku' -SERVICE_SABNZBD = 'sabnzbd' -SERVICE_SAMSUNG_PRINTER = 'samsung_printer' -SERVICE_TELLDUSLIVE = 'tellstick' -SERVICE_YEELIGHT = 'yeelight' -SERVICE_WEMO = 'belkin_wemo' -SERVICE_WINK = 'wink' -SERVICE_XIAOMI_GW = 'xiaomi_gw' +SERVICE_APPLE_TV = "apple_tv" +SERVICE_DAIKIN = "daikin" +SERVICE_DLNA_DMR = "dlna_dmr" +SERVICE_ENIGMA2 = "enigma2" +SERVICE_FREEBOX = "freebox" +SERVICE_HASS_IOS_APP = "hass_ios" +SERVICE_HASSIO = "hassio" +SERVICE_HEOS = "heos" +SERVICE_IGD = "igd" +SERVICE_KONNECTED = "konnected" +SERVICE_MOBILE_APP = "hass_mobile_app" +SERVICE_NETGEAR = "netgear_router" +SERVICE_OCTOPRINT = "octoprint" +SERVICE_ROKU = "roku" +SERVICE_SABNZBD = "sabnzbd" +SERVICE_SAMSUNG_PRINTER = "samsung_printer" +SERVICE_TELLDUSLIVE = "tellstick" +SERVICE_YEELIGHT = "yeelight" +SERVICE_WEMO = "belkin_wemo" +SERVICE_WINK = "wink" +SERVICE_XIAOMI_GW = "xiaomi_gw" CONFIG_ENTRY_HANDLERS = { - SERVICE_DAIKIN: 'daikin', - SERVICE_TELLDUSLIVE: 'tellduslive', - SERVICE_IGD: 'upnp', + SERVICE_DAIKIN: "daikin", + SERVICE_TELLDUSLIVE: "tellduslive", + SERVICE_IGD: "upnp", } SERVICE_HANDLERS = { - SERVICE_MOBILE_APP: ('mobile_app', None), - SERVICE_HASS_IOS_APP: ('ios', None), - SERVICE_NETGEAR: ('device_tracker', None), - SERVICE_HASSIO: ('hassio', None), - SERVICE_APPLE_TV: ('apple_tv', None), - SERVICE_ENIGMA2: ('media_player', 'enigma2'), - SERVICE_ROKU: ('roku', None), - SERVICE_WINK: ('wink', None), - SERVICE_XIAOMI_GW: ('xiaomi_aqara', None), - SERVICE_SABNZBD: ('sabnzbd', None), - SERVICE_SAMSUNG_PRINTER: ('sensor', 'syncthru'), - SERVICE_KONNECTED: ('konnected', None), - SERVICE_OCTOPRINT: ('octoprint', None), - SERVICE_FREEBOX: ('freebox', None), - SERVICE_YEELIGHT: ('yeelight', None), - 'panasonic_viera': ('media_player', 'panasonic_viera'), - 'plex_mediaserver': ('media_player', 'plex'), - 'yamaha': ('media_player', 'yamaha'), - 'logitech_mediaserver': ('media_player', 'squeezebox'), - 'directv': ('media_player', 'directv'), - 'denonavr': ('media_player', 'denonavr'), - 'samsung_tv': ('media_player', 'samsungtv'), - 'frontier_silicon': ('media_player', 'frontier_silicon'), - 'openhome': ('media_player', 'openhome'), - 'harmony': ('remote', 'harmony'), - 'bose_soundtouch': ('media_player', 'soundtouch'), - 'bluesound': ('media_player', 'bluesound'), - 'songpal': ('media_player', 'songpal'), - 'kodi': ('media_player', 'kodi'), - 'volumio': ('media_player', 'volumio'), - 'lg_smart_device': ('media_player', 'lg_soundbar'), - 'nanoleaf_aurora': ('light', 'nanoleaf'), + SERVICE_MOBILE_APP: ("mobile_app", None), + SERVICE_HASS_IOS_APP: ("ios", None), + SERVICE_NETGEAR: ("device_tracker", None), + SERVICE_HASSIO: ("hassio", None), + SERVICE_APPLE_TV: ("apple_tv", None), + SERVICE_ENIGMA2: ("media_player", "enigma2"), + SERVICE_ROKU: ("roku", None), + SERVICE_WINK: ("wink", None), + SERVICE_XIAOMI_GW: ("xiaomi_aqara", None), + SERVICE_SABNZBD: ("sabnzbd", None), + SERVICE_SAMSUNG_PRINTER: ("sensor", "syncthru"), + SERVICE_KONNECTED: ("konnected", None), + SERVICE_OCTOPRINT: ("octoprint", None), + SERVICE_FREEBOX: ("freebox", None), + SERVICE_YEELIGHT: ("yeelight", None), + "panasonic_viera": ("media_player", "panasonic_viera"), + "plex_mediaserver": ("media_player", "plex"), + "yamaha": ("media_player", "yamaha"), + "logitech_mediaserver": ("media_player", "squeezebox"), + "directv": ("media_player", "directv"), + "denonavr": ("media_player", "denonavr"), + "samsung_tv": ("media_player", "samsungtv"), + "frontier_silicon": ("media_player", "frontier_silicon"), + "openhome": ("media_player", "openhome"), + "harmony": ("remote", "harmony"), + "bose_soundtouch": ("media_player", "soundtouch"), + "bluesound": ("media_player", "bluesound"), + "songpal": ("media_player", "songpal"), + "kodi": ("media_player", "kodi"), + "volumio": ("media_player", "volumio"), + "lg_smart_device": ("media_player", "lg_soundbar"), + "nanoleaf_aurora": ("light", "nanoleaf"), } -OPTIONAL_SERVICE_HANDLERS = { - SERVICE_DLNA_DMR: ('media_player', 'dlna_dmr'), -} +OPTIONAL_SERVICE_HANDLERS = {SERVICE_DLNA_DMR: ("media_player", "dlna_dmr")} MIGRATED_SERVICE_HANDLERS = [ - 'axis', - 'deconz', - 'esphome', - 'google_cast', + "axis", + "deconz", + "esphome", + "google_cast", SERVICE_HEOS, - 'homekit', - 'ikea_tradfri', - 'philips_hue', - 'sonos', + "homekit", + "ikea_tradfri", + "philips_hue", + "sonos", SERVICE_WEMO, ] -DEFAULT_ENABLED = list(CONFIG_ENTRY_HANDLERS) + list(SERVICE_HANDLERS) + \ - MIGRATED_SERVICE_HANDLERS -DEFAULT_DISABLED = list(OPTIONAL_SERVICE_HANDLERS) + \ - MIGRATED_SERVICE_HANDLERS +DEFAULT_ENABLED = ( + list(CONFIG_ENTRY_HANDLERS) + list(SERVICE_HANDLERS) + MIGRATED_SERVICE_HANDLERS +) +DEFAULT_DISABLED = list(OPTIONAL_SERVICE_HANDLERS) + MIGRATED_SERVICE_HANDLERS -CONF_IGNORE = 'ignore' -CONF_ENABLE = 'enable' +CONF_IGNORE = "ignore" +CONF_ENABLE = "enable" -CONFIG_SCHEMA = vol.Schema({ - vol.Optional(DOMAIN): vol.Schema({ - vol.Optional(CONF_IGNORE, default=[]): - vol.All(cv.ensure_list, [vol.In(DEFAULT_ENABLED)]), - vol.Optional(CONF_ENABLE, default=[]): - vol.All(cv.ensure_list, [ - vol.In(DEFAULT_DISABLED + DEFAULT_ENABLED)]), - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + vol.Optional(DOMAIN): vol.Schema( + { + vol.Optional(CONF_IGNORE, default=[]): vol.All( + cv.ensure_list, [vol.In(DEFAULT_ENABLED)] + ), + vol.Optional(CONF_ENABLE, default=[]): vol.All( + cv.ensure_list, [vol.In(DEFAULT_DISABLED + DEFAULT_ENABLED)] + ), + } + ) + }, + extra=vol.ALLOW_EXTRA, +) async def async_setup(hass, config): @@ -131,7 +135,7 @@ async def async_setup(hass, config): already_discovered = set() # Disable zeroconf logging, it spams - logging.getLogger('zeroconf').setLevel(logging.CRITICAL) + logging.getLogger("zeroconf").setLevel(logging.CRITICAL) if DOMAIN in config: # Platforms ignore by config @@ -170,8 +174,8 @@ async def async_setup(hass, config): if service in CONFIG_ENTRY_HANDLERS: await hass.config_entries.flow.async_init( CONFIG_ENTRY_HANDLERS[service], - context={'source': config_entries.SOURCE_DISCOVERY}, - data=info + context={"source": config_entries.SOURCE_DISCOVERY}, + data=info, ) return @@ -192,8 +196,7 @@ async def async_setup(hass, config): if platform is None: await async_discover(hass, service, info, component, config) else: - await async_load_platform( - hass, component, platform, info, config) + await async_load_platform(hass, component, platform, info, config) async def scan_devices(now): """Scan for devices.""" @@ -206,7 +209,8 @@ async def async_setup(hass, config): logger.error("Network is unreachable") async_track_point_in_utc_time( - hass, scan_devices, dt_util.utcnow() + SCAN_INTERVAL) + hass, scan_devices, dt_util.utcnow() + SCAN_INTERVAL + ) @callback def schedule_first(event): diff --git a/homeassistant/components/dlib_face_detect/image_processing.py b/homeassistant/components/dlib_face_detect/image_processing.py index 0bc657a615d..749e536e2e8 100644 --- a/homeassistant/components/dlib_face_detect/image_processing.py +++ b/homeassistant/components/dlib_face_detect/image_processing.py @@ -3,23 +3,28 @@ import logging import io from homeassistant.core import split_entity_id + # pylint: disable=unused-import from homeassistant.components.image_processing import PLATFORM_SCHEMA # noqa from homeassistant.components.image_processing import ( - ImageProcessingFaceEntity, CONF_SOURCE, CONF_ENTITY_ID, CONF_NAME) + ImageProcessingFaceEntity, + CONF_SOURCE, + CONF_ENTITY_ID, + CONF_NAME, +) _LOGGER = logging.getLogger(__name__) -ATTR_LOCATION = 'location' +ATTR_LOCATION = "location" def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Dlib Face detection platform.""" entities = [] for camera in config[CONF_SOURCE]: - entities.append(DlibFaceDetectEntity( - camera[CONF_ENTITY_ID], camera.get(CONF_NAME) - )) + entities.append( + DlibFaceDetectEntity(camera[CONF_ENTITY_ID], camera.get(CONF_NAME)) + ) add_entities(entities) @@ -36,8 +41,7 @@ class DlibFaceDetectEntity(ImageProcessingFaceEntity): if name: self._name = name else: - self._name = "Dlib Face {0}".format( - split_entity_id(camera_entity)[1]) + self._name = "Dlib Face {0}".format(split_entity_id(camera_entity)[1]) @property def camera_entity(self): @@ -54,13 +58,12 @@ class DlibFaceDetectEntity(ImageProcessingFaceEntity): import face_recognition # pylint: disable=import-error fak_file = io.BytesIO(image) - fak_file.name = 'snapshot.jpg' + fak_file.name = "snapshot.jpg" fak_file.seek(0) image = face_recognition.load_image_file(fak_file) face_locations = face_recognition.face_locations(image) - face_locations = [{ATTR_LOCATION: location} - for location in face_locations] + face_locations = [{ATTR_LOCATION: location} for location in face_locations] self.process_faces(face_locations, len(face_locations)) diff --git a/homeassistant/components/dlib_face_identify/image_processing.py b/homeassistant/components/dlib_face_identify/image_processing.py index 1dd7b6e4626..d4851be28c8 100644 --- a/homeassistant/components/dlib_face_identify/image_processing.py +++ b/homeassistant/components/dlib_face_identify/image_processing.py @@ -6,29 +6,40 @@ import voluptuous as vol from homeassistant.core import split_entity_id from homeassistant.components.image_processing import ( - ImageProcessingFaceEntity, PLATFORM_SCHEMA, CONF_SOURCE, CONF_ENTITY_ID, - CONF_NAME, CONF_CONFIDENCE) + ImageProcessingFaceEntity, + PLATFORM_SCHEMA, + CONF_SOURCE, + CONF_ENTITY_ID, + CONF_NAME, + CONF_CONFIDENCE, +) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -ATTR_NAME = 'name' -CONF_FACES = 'faces' +ATTR_NAME = "name" +CONF_FACES = "faces" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_FACES): {cv.string: cv.isfile}, - vol.Optional(CONF_CONFIDENCE, default=0.6): vol.Coerce(float), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_FACES): {cv.string: cv.isfile}, + vol.Optional(CONF_CONFIDENCE, default=0.6): vol.Coerce(float), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Dlib Face detection platform.""" entities = [] for camera in config[CONF_SOURCE]: - entities.append(DlibFaceIdentifyEntity( - camera[CONF_ENTITY_ID], config[CONF_FACES], camera.get(CONF_NAME), - config[CONF_CONFIDENCE] - )) + entities.append( + DlibFaceIdentifyEntity( + camera[CONF_ENTITY_ID], + config[CONF_FACES], + camera.get(CONF_NAME), + config[CONF_CONFIDENCE], + ) + ) add_entities(entities) @@ -40,6 +51,7 @@ class DlibFaceIdentifyEntity(ImageProcessingFaceEntity): """Initialize Dlib face identify entry.""" # pylint: disable=import-error import face_recognition + super().__init__() self._camera = camera_entity @@ -47,15 +59,13 @@ class DlibFaceIdentifyEntity(ImageProcessingFaceEntity): if name: self._name = name else: - self._name = "Dlib Face {0}".format( - split_entity_id(camera_entity)[1]) + self._name = "Dlib Face {0}".format(split_entity_id(camera_entity)[1]) self._faces = {} for face_name, face_file in faces.items(): try: image = face_recognition.load_image_file(face_file) - self._faces[face_name] = \ - face_recognition.face_encodings(image)[0] + self._faces[face_name] = face_recognition.face_encodings(image)[0] except IndexError as err: _LOGGER.error("Failed to parse %s. Error: %s", face_file, err) @@ -77,7 +87,7 @@ class DlibFaceIdentifyEntity(ImageProcessingFaceEntity): import face_recognition fak_file = io.BytesIO(image) - fak_file.name = 'snapshot.jpg' + fak_file.name = "snapshot.jpg" fak_file.seek(0) image = face_recognition.load_image_file(fak_file) @@ -87,12 +97,9 @@ class DlibFaceIdentifyEntity(ImageProcessingFaceEntity): for unknown_face in unknowns: for name, face in self._faces.items(): result = face_recognition.compare_faces( - [face], - unknown_face, - tolerance=self._tolerance) + [face], unknown_face, tolerance=self._tolerance + ) if result[0]: - found.append({ - ATTR_NAME: name - }) + found.append({ATTR_NAME: name}) self.process_faces(found, len(unknowns)) diff --git a/homeassistant/components/dlink/switch.py b/homeassistant/components/dlink/switch.py index 7164bb2310a..25091e14dbd 100644 --- a/homeassistant/components/dlink/switch.py +++ b/homeassistant/components/dlink/switch.py @@ -7,30 +7,37 @@ import voluptuous as vol from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice from homeassistant.const import ( - ATTR_TEMPERATURE, CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME, - TEMP_CELSIUS) + ATTR_TEMPERATURE, + CONF_HOST, + CONF_NAME, + CONF_PASSWORD, + CONF_USERNAME, + TEMP_CELSIUS, +) import homeassistant.helpers.config_validation as cv from homeassistant.util import dt as dt_util _LOGGER = logging.getLogger(__name__) -ATTR_TOTAL_CONSUMPTION = 'total_consumption' +ATTR_TOTAL_CONSUMPTION = "total_consumption" -CONF_USE_LEGACY_PROTOCOL = 'use_legacy_protocol' +CONF_USE_LEGACY_PROTOCOL = "use_legacy_protocol" DEFAULT_NAME = "D-Link Smart Plug W215" -DEFAULT_PASSWORD = '' -DEFAULT_USERNAME = 'admin' +DEFAULT_PASSWORD = "" +DEFAULT_USERNAME = "admin" SCAN_INTERVAL = timedelta(minutes=2) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string, - vol.Required(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_USE_LEGACY_PROTOCOL, default=False): cv.boolean, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string, + vol.Required(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_USE_LEGACY_PROTOCOL, default=False): cv.boolean, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -67,8 +74,7 @@ class SmartPlugSwitch(SwitchDevice): def device_state_attributes(self): """Return the state attributes of the device.""" try: - ui_temp = self.units.temperature( - int(self.data.temperature), TEMP_CELSIUS) + ui_temp = self.units.temperature(int(self.data.temperature), TEMP_CELSIUS) temperature = ui_temp except (ValueError, TypeError): temperature = None @@ -96,15 +102,15 @@ class SmartPlugSwitch(SwitchDevice): @property def is_on(self): """Return true if switch is on.""" - return self.data.state == 'ON' + return self.data.state == "ON" def turn_on(self, **kwargs): """Turn the switch on.""" - self.data.smartplug.state = 'ON' + self.data.smartplug.state = "ON" def turn_off(self, **kwargs): """Turn the switch off.""" - self.data.smartplug.state = 'OFF' + self.data.smartplug.state = "OFF" def update(self): """Get the latest data from the smart plug and updates the states.""" @@ -133,20 +139,20 @@ class SmartPlugData: def update(self): """Get the latest data from the smart plug.""" if self._last_tried is not None: - last_try_s = (dt_util.now() - self._last_tried).total_seconds()/60 - retry_seconds = min(self._n_tried*2, 10) - last_try_s + last_try_s = (dt_util.now() - self._last_tried).total_seconds() / 60 + retry_seconds = min(self._n_tried * 2, 10) - last_try_s if self._n_tried > 0 and retry_seconds > 0: _LOGGER.warning("Waiting %s s to retry", retry_seconds) return - _state = 'unknown' + _state = "unknown" try: self._last_tried = dt_util.now() _state = self.smartplug.state except urllib.error.HTTPError: _LOGGER.error("D-Link connection problem") - if _state == 'unknown': + if _state == "unknown": self._n_tried += 1 self.available = False _LOGGER.warning("Failed to connect to D-Link switch") diff --git a/homeassistant/components/dlna_dmr/media_player.py b/homeassistant/components/dlna_dmr/media_player.py index dd348d1fbbc..c7c488950cc 100644 --- a/homeassistant/components/dlna_dmr/media_player.py +++ b/homeassistant/components/dlna_dmr/media_player.py @@ -9,18 +9,36 @@ from typing import Optional import aiohttp import voluptuous as vol -from homeassistant.components.media_player import ( - MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA from homeassistant.components.media_player.const import ( - MEDIA_TYPE_CHANNEL, MEDIA_TYPE_EPISODE, MEDIA_TYPE_IMAGE, - MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, - MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO, - SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_STOP, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET) + MEDIA_TYPE_CHANNEL, + MEDIA_TYPE_EPISODE, + MEDIA_TYPE_IMAGE, + MEDIA_TYPE_MOVIE, + MEDIA_TYPE_MUSIC, + MEDIA_TYPE_PLAYLIST, + MEDIA_TYPE_TVSHOW, + MEDIA_TYPE_VIDEO, + SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, + SUPPORT_PLAY, + SUPPORT_PLAY_MEDIA, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_SEEK, + SUPPORT_STOP, + SUPPORT_VOLUME_MUTE, + SUPPORT_VOLUME_SET, +) from homeassistant.const import ( - CONF_NAME, CONF_URL, EVENT_HOMEASSISTANT_STOP, STATE_IDLE, STATE_OFF, - STATE_ON, STATE_PAUSED, STATE_PLAYING) + CONF_NAME, + CONF_URL, + EVENT_HOMEASSISTANT_STOP, + STATE_IDLE, + STATE_OFF, + STATE_ON, + STATE_PAUSED, + STATE_PLAYING, +) from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.typing import HomeAssistantType @@ -29,50 +47,54 @@ from homeassistant.util import get_local_ip _LOGGER = logging.getLogger(__name__) -DLNA_DMR_DATA = 'dlna_dmr' +DLNA_DMR_DATA = "dlna_dmr" -DEFAULT_NAME = 'DLNA Digital Media Renderer' +DEFAULT_NAME = "DLNA Digital Media Renderer" DEFAULT_LISTEN_PORT = 8301 -CONF_LISTEN_IP = 'listen_ip' -CONF_LISTEN_PORT = 'listen_port' -CONF_CALLBACK_URL_OVERRIDE = 'callback_url_override' +CONF_LISTEN_IP = "listen_ip" +CONF_LISTEN_PORT = "listen_port" +CONF_CALLBACK_URL_OVERRIDE = "callback_url_override" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_URL): cv.string, - vol.Optional(CONF_LISTEN_IP): cv.string, - vol.Optional(CONF_LISTEN_PORT, default=DEFAULT_LISTEN_PORT): cv.port, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_CALLBACK_URL_OVERRIDE): cv.url, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_URL): cv.string, + vol.Optional(CONF_LISTEN_IP): cv.string, + vol.Optional(CONF_LISTEN_PORT, default=DEFAULT_LISTEN_PORT): cv.port, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_CALLBACK_URL_OVERRIDE): cv.url, + } +) HOME_ASSISTANT_UPNP_CLASS_MAPPING = { - MEDIA_TYPE_MUSIC: 'object.item.audioItem', - MEDIA_TYPE_TVSHOW: 'object.item.videoItem', - MEDIA_TYPE_MOVIE: 'object.item.videoItem', - MEDIA_TYPE_VIDEO: 'object.item.videoItem', - MEDIA_TYPE_EPISODE: 'object.item.videoItem', - MEDIA_TYPE_CHANNEL: 'object.item.videoItem', - MEDIA_TYPE_IMAGE: 'object.item.imageItem', - MEDIA_TYPE_PLAYLIST: 'object.item.playlist', + MEDIA_TYPE_MUSIC: "object.item.audioItem", + MEDIA_TYPE_TVSHOW: "object.item.videoItem", + MEDIA_TYPE_MOVIE: "object.item.videoItem", + MEDIA_TYPE_VIDEO: "object.item.videoItem", + MEDIA_TYPE_EPISODE: "object.item.videoItem", + MEDIA_TYPE_CHANNEL: "object.item.videoItem", + MEDIA_TYPE_IMAGE: "object.item.imageItem", + MEDIA_TYPE_PLAYLIST: "object.item.playlist", } -UPNP_CLASS_DEFAULT = 'object.item' +UPNP_CLASS_DEFAULT = "object.item" HOME_ASSISTANT_UPNP_MIME_TYPE_MAPPING = { - MEDIA_TYPE_MUSIC: 'audio/*', - MEDIA_TYPE_TVSHOW: 'video/*', - MEDIA_TYPE_MOVIE: 'video/*', - MEDIA_TYPE_VIDEO: 'video/*', - MEDIA_TYPE_EPISODE: 'video/*', - MEDIA_TYPE_CHANNEL: 'video/*', - MEDIA_TYPE_IMAGE: 'image/*', - MEDIA_TYPE_PLAYLIST: 'playlist/*', + MEDIA_TYPE_MUSIC: "audio/*", + MEDIA_TYPE_TVSHOW: "video/*", + MEDIA_TYPE_MOVIE: "video/*", + MEDIA_TYPE_VIDEO: "video/*", + MEDIA_TYPE_EPISODE: "video/*", + MEDIA_TYPE_CHANNEL: "video/*", + MEDIA_TYPE_IMAGE: "image/*", + MEDIA_TYPE_PLAYLIST: "playlist/*", } def catch_request_errors(): """Catch asyncio.TimeoutError, aiohttp.ClientError errors.""" + def call_wrapper(func): """Call wrapper for decorator.""" + @functools.wraps(func) def wrapper(self, *args, **kwargs): """Catch asyncio.TimeoutError, aiohttp.ClientError errors.""" @@ -87,75 +109,79 @@ def catch_request_errors(): async def async_start_event_handler( - hass: HomeAssistantType, - server_host: str, - server_port: int, - requester, - callback_url_override: Optional[str] = None): + hass: HomeAssistantType, + server_host: str, + server_port: int, + requester, + callback_url_override: Optional[str] = None, +): """Register notify view.""" hass_data = hass.data[DLNA_DMR_DATA] - if 'event_handler' in hass_data: - return hass_data['event_handler'] + if "event_handler" in hass_data: + return hass_data["event_handler"] # start event handler from async_upnp_client.aiohttp import AiohttpNotifyServer + server = AiohttpNotifyServer( requester, listen_port=server_port, listen_host=server_host, - callback_url=callback_url_override) + callback_url=callback_url_override, + ) await server.start_server() - _LOGGER.info( - 'UPNP/DLNA event handler listening, url: %s', server.callback_url) - hass_data['notify_server'] = server - hass_data['event_handler'] = server.event_handler + _LOGGER.info("UPNP/DLNA event handler listening, url: %s", server.callback_url) + hass_data["notify_server"] = server + hass_data["event_handler"] = server.event_handler # register for graceful shutdown async def async_stop_server(event): """Stop server.""" - _LOGGER.debug('Stopping UPNP/DLNA event handler') + _LOGGER.debug("Stopping UPNP/DLNA event handler") await server.stop_server() + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_stop_server) - return hass_data['event_handler'] + return hass_data["event_handler"] async def async_setup_platform( - hass: HomeAssistantType, - config, - async_add_entities, - discovery_info=None): + hass: HomeAssistantType, config, async_add_entities, discovery_info=None +): """Set up DLNA DMR platform.""" if config.get(CONF_URL) is not None: url = config[CONF_URL] name = config.get(CONF_NAME) elif discovery_info is not None: - url = discovery_info['ssdp_description'] - name = discovery_info.get('name') + url = discovery_info["ssdp_description"] + name = discovery_info.get("name") if DLNA_DMR_DATA not in hass.data: hass.data[DLNA_DMR_DATA] = {} - if 'lock' not in hass.data[DLNA_DMR_DATA]: - hass.data[DLNA_DMR_DATA]['lock'] = asyncio.Lock() + if "lock" not in hass.data[DLNA_DMR_DATA]: + hass.data[DLNA_DMR_DATA]["lock"] = asyncio.Lock() # build upnp/aiohttp requester from async_upnp_client.aiohttp import AiohttpSessionRequester + session = async_get_clientsession(hass) requester = AiohttpSessionRequester(session, True) # ensure event handler has been started - with await hass.data[DLNA_DMR_DATA]['lock']: + with await hass.data[DLNA_DMR_DATA]["lock"]: server_host = config.get(CONF_LISTEN_IP) if server_host is None: server_host = get_local_ip() server_port = config.get(CONF_LISTEN_PORT, DEFAULT_LISTEN_PORT) callback_url_override = config.get(CONF_CALLBACK_URL_OVERRIDE) event_handler = await async_start_event_handler( - hass, server_host, server_port, requester, callback_url_override) + hass, server_host, server_port, requester, callback_url_override + ) # create upnp device from async_upnp_client import UpnpFactory + factory = UpnpFactory(requester, disable_state_variable_validation=True) try: upnp_device = await factory.async_create_device(url) @@ -164,6 +190,7 @@ async def async_setup_platform( # wrap with DmrDevice from async_upnp_client.profiles.dlna import DmrDevice + dlna_device = DmrDevice(upnp_device, event_handler) # create our own device @@ -189,8 +216,7 @@ class DlnaDmrDevice(MediaPlayerDevice): # Register unsubscribe on stop bus = self.hass.bus - bus.async_listen_once( - EVENT_HOMEASSISTANT_STOP, self._async_on_hass_stop) + bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self._async_on_hass_stop) @property def available(self): @@ -199,7 +225,7 @@ class DlnaDmrDevice(MediaPlayerDevice): async def _async_on_hass_stop(self, event): """Event handler on HASS stop.""" - with await self.hass.data[DLNA_DMR_DATA]['lock']: + with await self.hass.data[DLNA_DMR_DATA]["lock"]: await self._device.async_unsubscribe_services() async def async_update(self): @@ -216,10 +242,10 @@ class DlnaDmrDevice(MediaPlayerDevice): # do we need to (re-)subscribe? now = datetime.now() - should_renew = self._subscription_renew_time and \ - now >= self._subscription_renew_time - if should_renew or \ - not was_available and self._available: + should_renew = ( + self._subscription_renew_time and now >= self._subscription_renew_time + ) + if should_renew or not was_available and self._available: try: timeout = await self._device.async_subscribe_services() self._subscription_renew_time = datetime.now() + timeout / 2 @@ -282,7 +308,7 @@ class DlnaDmrDevice(MediaPlayerDevice): async def async_media_pause(self): """Send pause command.""" if not self._device.can_pause: - _LOGGER.debug('Cannot do Pause') + _LOGGER.debug("Cannot do Pause") return await self._device.async_pause() @@ -291,7 +317,7 @@ class DlnaDmrDevice(MediaPlayerDevice): async def async_media_play(self): """Send play command.""" if not self._device.can_play: - _LOGGER.debug('Cannot do Play') + _LOGGER.debug("Cannot do Play") return await self._device.async_play() @@ -300,7 +326,7 @@ class DlnaDmrDevice(MediaPlayerDevice): async def async_media_stop(self): """Send stop command.""" if not self._device.can_stop: - _LOGGER.debug('Cannot do Stop') + _LOGGER.debug("Cannot do Stop") return await self._device.async_stop() @@ -309,7 +335,7 @@ class DlnaDmrDevice(MediaPlayerDevice): async def async_media_seek(self, position): """Send seek command.""" if not self._device.can_seek_rel_time: - _LOGGER.debug('Cannot do Seek/rel_time') + _LOGGER.debug("Cannot do Seek/rel_time") return time = timedelta(seconds=position) @@ -319,10 +345,10 @@ class DlnaDmrDevice(MediaPlayerDevice): async def async_play_media(self, media_type, media_id, **kwargs): """Play a piece of media.""" title = "Home Assistant" - mime_type = HOME_ASSISTANT_UPNP_MIME_TYPE_MAPPING.get(media_type, - media_type) - upnp_class = HOME_ASSISTANT_UPNP_CLASS_MAPPING.get(media_type, - UPNP_CLASS_DEFAULT) + mime_type = HOME_ASSISTANT_UPNP_MIME_TYPE_MAPPING.get(media_type, media_type) + upnp_class = HOME_ASSISTANT_UPNP_CLASS_MAPPING.get( + media_type, UPNP_CLASS_DEFAULT + ) # Stop current playing media if self._device.can_stop: @@ -330,11 +356,13 @@ class DlnaDmrDevice(MediaPlayerDevice): # Queue media await self._device.async_set_transport_uri( - media_id, title, mime_type, upnp_class) + media_id, title, mime_type, upnp_class + ) await self._device.async_wait_for_can_play() # If already playing, no need to call Play from async_upnp_client.profiles.dlna import DeviceState + if self._device.state == DeviceState.PLAYING: return @@ -345,7 +373,7 @@ class DlnaDmrDevice(MediaPlayerDevice): async def async_media_previous_track(self): """Send previous track command.""" if not self._device.can_previous: - _LOGGER.debug('Cannot do Previous') + _LOGGER.debug("Cannot do Previous") return await self._device.async_previous() @@ -354,7 +382,7 @@ class DlnaDmrDevice(MediaPlayerDevice): async def async_media_next_track(self): """Send next track command.""" if not self._device.can_next: - _LOGGER.debug('Cannot do Next') + _LOGGER.debug("Cannot do Next") return await self._device.async_next() @@ -376,6 +404,7 @@ class DlnaDmrDevice(MediaPlayerDevice): return STATE_OFF from async_upnp_client.profiles.dlna import DeviceState + if self._device.state is None: return STATE_ON if self._device.state == DeviceState.PLAYING: diff --git a/homeassistant/components/dnsip/sensor.py b/homeassistant/components/dnsip/sensor.py index 337a68a77ce..0053d5a95ea 100644 --- a/homeassistant/components/dnsip/sensor.py +++ b/homeassistant/components/dnsip/sensor.py @@ -11,30 +11,31 @@ from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -CONF_HOSTNAME = 'hostname' -CONF_IPV6 = 'ipv6' -CONF_RESOLVER = 'resolver' -CONF_RESOLVER_IPV6 = 'resolver_ipv6' +CONF_HOSTNAME = "hostname" +CONF_IPV6 = "ipv6" +CONF_RESOLVER = "resolver" +CONF_RESOLVER_IPV6 = "resolver_ipv6" -DEFAULT_HOSTNAME = 'myip.opendns.com' +DEFAULT_HOSTNAME = "myip.opendns.com" DEFAULT_IPV6 = False -DEFAULT_NAME = 'myip' -DEFAULT_RESOLVER = '208.67.222.222' -DEFAULT_RESOLVER_IPV6 = '2620:0:ccc::2' +DEFAULT_NAME = "myip" +DEFAULT_RESOLVER = "208.67.222.222" +DEFAULT_RESOLVER_IPV6 = "2620:0:ccc::2" SCAN_INTERVAL = timedelta(seconds=120) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_HOSTNAME, default=DEFAULT_HOSTNAME): cv.string, - vol.Optional(CONF_RESOLVER, default=DEFAULT_RESOLVER): cv.string, - vol.Optional(CONF_RESOLVER_IPV6, default=DEFAULT_RESOLVER_IPV6): cv.string, - vol.Optional(CONF_IPV6, default=DEFAULT_IPV6): cv.boolean, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_HOSTNAME, default=DEFAULT_HOSTNAME): cv.string, + vol.Optional(CONF_RESOLVER, default=DEFAULT_RESOLVER): cv.string, + vol.Optional(CONF_RESOLVER_IPV6, default=DEFAULT_RESOLVER_IPV6): cv.string, + vol.Optional(CONF_IPV6, default=DEFAULT_IPV6): cv.boolean, + } +) -async def async_setup_platform( - hass, config, async_add_devices, discovery_info=None): +async def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Set up the DNS IP sensor.""" hostname = config.get(CONF_HOSTNAME) name = config.get(CONF_NAME) @@ -49,8 +50,7 @@ async def async_setup_platform( else: resolver = config.get(CONF_RESOLVER) - async_add_devices([WanIpSensor( - hass, name, hostname, resolver, ipv6)], True) + async_add_devices([WanIpSensor(hass, name, hostname, resolver, ipv6)], True) class WanIpSensor(Entity): @@ -65,7 +65,7 @@ class WanIpSensor(Entity): self.hostname = hostname self.resolver = aiodns.DNSResolver() self.resolver.nameservers = [resolver] - self.querytype = 'AAAA' if ipv6 else 'A' + self.querytype = "AAAA" if ipv6 else "A" self._state = None @property @@ -83,8 +83,7 @@ class WanIpSensor(Entity): from aiodns.error import DNSError try: - response = await self.resolver.query( - self.hostname, self.querytype) + response = await self.resolver.query(self.hostname, self.querytype) except DNSError as err: _LOGGER.warning("Exception while resolving host: %s", err) response = None diff --git a/homeassistant/components/dominos/__init__.py b/homeassistant/components/dominos/__init__.py index 3c5cb3ed6ec..59869ed0a97 100644 --- a/homeassistant/components/dominos/__init__.py +++ b/homeassistant/components/dominos/__init__.py @@ -14,42 +14,50 @@ from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) # The domain of your component. Should be equal to the name of your component. -DOMAIN = 'dominos' -ENTITY_ID_FORMAT = DOMAIN + '.{}' +DOMAIN = "dominos" +ENTITY_ID_FORMAT = DOMAIN + ".{}" -ATTR_COUNTRY = 'country_code' -ATTR_FIRST_NAME = 'first_name' -ATTR_LAST_NAME = 'last_name' -ATTR_EMAIL = 'email' -ATTR_PHONE = 'phone' -ATTR_ADDRESS = 'address' -ATTR_ORDERS = 'orders' -ATTR_SHOW_MENU = 'show_menu' -ATTR_ORDER_ENTITY = 'order_entity_id' -ATTR_ORDER_NAME = 'name' -ATTR_ORDER_CODES = 'codes' +ATTR_COUNTRY = "country_code" +ATTR_FIRST_NAME = "first_name" +ATTR_LAST_NAME = "last_name" +ATTR_EMAIL = "email" +ATTR_PHONE = "phone" +ATTR_ADDRESS = "address" +ATTR_ORDERS = "orders" +ATTR_SHOW_MENU = "show_menu" +ATTR_ORDER_ENTITY = "order_entity_id" +ATTR_ORDER_NAME = "name" +ATTR_ORDER_CODES = "codes" MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=10) MIN_TIME_BETWEEN_STORE_UPDATES = timedelta(minutes=3330) -_ORDERS_SCHEMA = vol.Schema({ - vol.Required(ATTR_ORDER_NAME): cv.string, - vol.Required(ATTR_ORDER_CODES): vol.All(cv.ensure_list, [cv.string]), -}) +_ORDERS_SCHEMA = vol.Schema( + { + vol.Required(ATTR_ORDER_NAME): cv.string, + vol.Required(ATTR_ORDER_CODES): vol.All(cv.ensure_list, [cv.string]), + } +) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(ATTR_COUNTRY): cv.string, - vol.Required(ATTR_FIRST_NAME): cv.string, - vol.Required(ATTR_LAST_NAME): cv.string, - vol.Required(ATTR_EMAIL): cv.string, - vol.Required(ATTR_PHONE): cv.string, - vol.Required(ATTR_ADDRESS): cv.string, - vol.Optional(ATTR_SHOW_MENU): cv.boolean, - vol.Optional(ATTR_ORDERS, default=[]): vol.All( - cv.ensure_list, [_ORDERS_SCHEMA]), - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(ATTR_COUNTRY): cv.string, + vol.Required(ATTR_FIRST_NAME): cv.string, + vol.Required(ATTR_LAST_NAME): cv.string, + vol.Required(ATTR_EMAIL): cv.string, + vol.Required(ATTR_PHONE): cv.string, + vol.Required(ATTR_ADDRESS): cv.string, + vol.Optional(ATTR_SHOW_MENU): cv.boolean, + vol.Optional(ATTR_ORDERS, default=[]): vol.All( + cv.ensure_list, [_ORDERS_SCHEMA] + ), + } + ) + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): @@ -61,7 +69,7 @@ def setup(hass, config): entities = [] conf = config[DOMAIN] - hass.services.register(DOMAIN, 'order', dominos.handle_order) + hass.services.register(DOMAIN, "order", dominos.handle_order) if conf.get(ATTR_SHOW_MENU): hass.http.register_view(DominosProductListView(dominos)) @@ -77,7 +85,7 @@ def setup(hass, config): return True -class Dominos(): +class Dominos: """Main Dominos service.""" def __init__(self, hass, config): @@ -85,16 +93,18 @@ class Dominos(): conf = config[DOMAIN] from pizzapi import Address, Customer from pizzapi.address import StoreException + self.hass = hass self.customer = Customer( conf.get(ATTR_FIRST_NAME), conf.get(ATTR_LAST_NAME), conf.get(ATTR_EMAIL), conf.get(ATTR_PHONE), - conf.get(ATTR_ADDRESS)) + conf.get(ATTR_ADDRESS), + ) self.address = Address( - *self.customer.address.split(','), - country=conf.get(ATTR_COUNTRY)) + *self.customer.address.split(","), country=conf.get(ATTR_COUNTRY) + ) self.country = conf.get(ATTR_COUNTRY) try: self.closest_store = self.address.closest_store() @@ -105,8 +115,11 @@ class Dominos(): """Handle ordering pizza.""" entity_ids = call.data.get(ATTR_ORDER_ENTITY, None) - target_orders = [order for order in self.hass.data[DOMAIN]['entities'] - if order.entity_id in entity_ids] + target_orders = [ + order + for order in self.hass.data[DOMAIN]["entities"] + if order.entity_id in entity_ids + ] for order in target_orders: order.place() @@ -115,6 +128,7 @@ class Dominos(): def update_closest_store(self): """Update the shared closest store (if open).""" from pizzapi.address import StoreException + try: self.closest_store = self.address.closest_store() return True @@ -126,19 +140,19 @@ class Dominos(): """Return the products from the closest stores menu.""" self.update_closest_store() if self.closest_store is None: - _LOGGER.warning('Cannot get menu. Store may be closed') + _LOGGER.warning("Cannot get menu. Store may be closed") return [] menu = self.closest_store.get_menu() product_entries = [] for product in menu.products: item = {} - if isinstance(product.menu_data['Variants'], list): - variants = ', '.join(product.menu_data['Variants']) + if isinstance(product.menu_data["Variants"], list): + variants = ", ".join(product.menu_data["Variants"]) else: - variants = product.menu_data['Variants'] - item['name'] = product.name - item['variants'] = variants + variants = product.menu_data["Variants"] + item["name"] = product.name + item["variants"] = variants product_entries.append(item) return product_entries @@ -147,7 +161,7 @@ class Dominos(): class DominosProductListView(http.HomeAssistantView): """View to retrieve product list content.""" - url = '/api/dominos' + url = "/api/dominos" name = "api:dominos" def __init__(self, dominos): @@ -165,8 +179,8 @@ class DominosOrder(Entity): def __init__(self, order_info, dominos): """Set up the entity.""" - self._name = order_info['name'] - self._product_codes = order_info['codes'] + self._name = order_info["name"] + self._product_codes = order_info["codes"] self._orderable = False self.dominos = dominos @@ -189,13 +203,14 @@ class DominosOrder(Entity): def state(self): """Return the state either closed, orderable or unorderable.""" if self.dominos.closest_store is None: - return 'closed' - return 'orderable' if self._orderable else 'unorderable' + return "closed" + return "orderable" if self._orderable else "unorderable" @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): """Update the order state and refreshes the store.""" from pizzapi.address import StoreException + try: self.dominos.update_closest_store() except StoreException: @@ -221,7 +236,8 @@ class DominosOrder(Entity): self.dominos.closest_store, self.dominos.customer, self.dominos.address, - self.dominos.country) + self.dominos.country, + ) for code in self._product_codes: order.add_item(code) @@ -231,10 +247,12 @@ class DominosOrder(Entity): def place(self): """Place the order.""" from pizzapi.address import StoreException + try: order = self.order() order.place() except StoreException: self._orderable = False _LOGGER.warning( - 'Attempted to order Dominos - Order invalid or store closed') + "Attempted to order Dominos - Order invalid or store closed" + ) diff --git a/homeassistant/components/doorbird/__init__.py b/homeassistant/components/doorbird/__init__.py index 62d3584603a..3afa9c58e66 100644 --- a/homeassistant/components/doorbird/__init__.py +++ b/homeassistant/components/doorbird/__init__.py @@ -6,38 +6,47 @@ import voluptuous as vol from homeassistant.components.http import HomeAssistantView from homeassistant.const import ( - CONF_DEVICES, CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_TOKEN, - CONF_USERNAME) + CONF_DEVICES, + CONF_HOST, + CONF_NAME, + CONF_PASSWORD, + CONF_TOKEN, + CONF_USERNAME, +) import homeassistant.helpers.config_validation as cv from homeassistant.util import dt as dt_util, slugify _LOGGER = logging.getLogger(__name__) -DOMAIN = 'doorbird' +DOMAIN = "doorbird" -API_URL = '/api/{}'.format(DOMAIN) +API_URL = "/api/{}".format(DOMAIN) -CONF_CUSTOM_URL = 'hass_url_override' -CONF_EVENTS = 'events' +CONF_CUSTOM_URL = "hass_url_override" +CONF_EVENTS = "events" -RESET_DEVICE_FAVORITES = 'doorbird_reset_favorites' +RESET_DEVICE_FAVORITES = "doorbird_reset_favorites" -DEVICE_SCHEMA = vol.Schema({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_TOKEN): cv.string, - vol.Optional(CONF_EVENTS, default=[]): vol.All( - cv.ensure_list, [cv.string]), - vol.Optional(CONF_CUSTOM_URL): cv.string, - vol.Optional(CONF_NAME): cv.string -}) +DEVICE_SCHEMA = vol.Schema( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_TOKEN): cv.string, + vol.Optional(CONF_EVENTS, default=[]): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_CUSTOM_URL): cv.string, + vol.Optional(CONF_NAME): cv.string, + } +) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_DEVICES): vol.All(cv.ensure_list, [DEVICE_SCHEMA]) - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + {vol.Required(CONF_DEVICES): vol.All(cv.ensure_list, [DEVICE_SCHEMA])} + ) + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): @@ -56,25 +65,32 @@ def setup(hass, config): custom_url = doorstation_config.get(CONF_CUSTOM_URL) events = doorstation_config.get(CONF_EVENTS) token = doorstation_config.get(CONF_TOKEN) - name = (doorstation_config.get(CONF_NAME) - or 'DoorBird {}'.format(index + 1)) + name = doorstation_config.get(CONF_NAME) or "DoorBird {}".format(index + 1) device = DoorBird(device_ip, username, password) status = device.ready() if status[0]: - doorstation = ConfiguredDoorBird(device, name, events, custom_url, - token) + doorstation = ConfiguredDoorBird(device, name, events, custom_url, token) doorstations.append(doorstation) - _LOGGER.info('Connected to DoorBird "%s" as %s@%s', - doorstation.name, username, device_ip) + _LOGGER.info( + 'Connected to DoorBird "%s" as %s@%s', + doorstation.name, + username, + device_ip, + ) elif status[1] == 401: - _LOGGER.error("Authorization rejected by DoorBird for %s@%s", - username, device_ip) + _LOGGER.error( + "Authorization rejected by DoorBird for %s@%s", username, device_ip + ) return False else: - _LOGGER.error("Could not connect to DoorBird as %s@%s: Error %s", - username, device_ip, str(status[1])) + _LOGGER.error( + "Could not connect to DoorBird as %s@%s: Error %s", + username, + device_ip, + str(status[1]), + ) return False # Subscribe to doorbell or motion events @@ -83,12 +99,13 @@ def setup(hass, config): doorstation.register_events(hass) except HTTPError: hass.components.persistent_notification.create( - 'Doorbird configuration failed. Please verify that API ' - 'Operator permission is enabled for the Doorbird user. ' - 'A restart will be required once permissions have been ' - 'verified.', - title='Doorbird Configuration Failure', - notification_id='doorbird_schedule_error') + "Doorbird configuration failed. Please verify that API " + "Operator permission is enabled for the Doorbird user. " + "A restart will be required once permissions have been " + "verified.", + title="Doorbird Configuration Failure", + notification_id="doorbird_schedule_error", + ) return False @@ -96,7 +113,7 @@ def setup(hass, config): def _reset_device_favorites_handler(event): """Handle clearing favorites on device.""" - token = event.data.get('token') + token = event.data.get("token") if token is None: return @@ -104,7 +121,7 @@ def setup(hass, config): doorstation = get_doorstation_by_token(hass, token) if doorstation is None: - _LOGGER.error('Device not found for provided token.') + _LOGGER.error("Device not found for provided token.") # Clear webhooks favorites = doorstation.device.favorites() @@ -125,7 +142,7 @@ def get_doorstation_by_token(hass, token): return doorstation -class ConfiguredDoorBird(): +class ConfiguredDoorBird: """Attach additional information to pass along with configured device.""" def __init__(self, device, name, events, custom_url, token): @@ -170,8 +187,7 @@ class ConfiguredDoorBird(): self._register_event(hass_url, event) - _LOGGER.info('Successfully registered URL for %s on %s', - event, self.name) + _LOGGER.info("Successfully registered URL for %s on %s", event, self.name) @property def slug(self): @@ -179,33 +195,37 @@ class ConfiguredDoorBird(): return slugify(self._name) def _get_event_name(self, event): - return '{}_{}'.format(self.slug, event) + return "{}_{}".format(self.slug, event) def _register_event(self, hass_url, event): """Add a schedule entry in the device for a sensor.""" - url = '{}{}/{}?token={}'.format(hass_url, API_URL, event, self._token) + url = "{}{}/{}?token={}".format(hass_url, API_URL, event, self._token) # Register HA URL as webhook if not already, then get the ID if not self.webhook_is_registered(url): - self.device.change_favorite('http', 'Home Assistant ({})' - .format(event), url) + self.device.change_favorite( + "http", "Home Assistant ({})".format(event), url + ) fav_id = self.get_webhook_id(url) if not fav_id: - _LOGGER.warning('Could not find favorite for URL "%s". ' - 'Skipping sensor "%s"', url, event) + _LOGGER.warning( + 'Could not find favorite for URL "%s". ' 'Skipping sensor "%s"', + url, + event, + ) return def webhook_is_registered(self, url, favs=None) -> bool: """Return whether the given URL is registered as a device favorite.""" favs = favs if favs else self.device.favorites() - if 'http' not in favs: + if "http" not in favs: return False - for fav in favs['http'].values(): - if fav['value'] == url: + for fav in favs["http"].values(): + if fav["value"] == url: return True return False @@ -218,11 +238,11 @@ class ConfiguredDoorBird(): """ favs = favs if favs else self.device.favorites() - if 'http' not in favs: + if "http" not in favs: return None - for fav_id in favs['http']: - if favs['http'][fav_id]['value'] == url: + for fav_id in favs["http"]: + if favs["http"][fav_id]["value"] == url: return fav_id return None @@ -230,11 +250,11 @@ class ConfiguredDoorBird(): def get_event_data(self): """Get data to pass along with HA event.""" return { - 'timestamp': dt_util.utcnow().isoformat(), - 'live_video_url': self._device.live_video_url, - 'live_image_url': self._device.live_image_url, - 'rtsp_live_video_url': self._device.rtsp_live_video_url, - 'html5_viewer_url': self._device.html5_viewer_url + "timestamp": dt_util.utcnow().isoformat(), + "live_video_url": self._device.live_video_url, + "live_image_url": self._device.live_image_url, + "rtsp_live_video_url": self._device.rtsp_live_video_url, + "html5_viewer_url": self._device.html5_viewer_url, } @@ -243,34 +263,34 @@ class DoorBirdRequestView(HomeAssistantView): requires_auth = False url = API_URL - name = API_URL[1:].replace('/', ':') - extra_urls = [API_URL + '/{event}'] + name = API_URL[1:].replace("/", ":") + extra_urls = [API_URL + "/{event}"] # pylint: disable=no-self-use async def get(self, request, event): """Respond to requests from the device.""" from aiohttp import web - hass = request.app['hass'] - token = request.query.get('token') + hass = request.app["hass"] + + token = request.query.get("token") device = get_doorstation_by_token(hass, token) if device is None: - return web.Response(status=401, text='Invalid token provided.') + return web.Response(status=401, text="Invalid token provided.") if device: event_data = device.get_event_data() else: event_data = {} - if event == 'clear': - hass.bus.async_fire(RESET_DEVICE_FAVORITES, - {'token': token}) + if event == "clear": + hass.bus.async_fire(RESET_DEVICE_FAVORITES, {"token": token}) - message = 'HTTP Favorites cleared for {}'.format(device.slug) + message = "HTTP Favorites cleared for {}".format(device.slug) return web.Response(status=200, text=message) - hass.bus.async_fire('{}_{}'.format(DOMAIN, event), event_data) + hass.bus.async_fire("{}_{}".format(DOMAIN, event), event_data) - return web.Response(status=200, text='OK') + return web.Response(status=200, text="OK") diff --git a/homeassistant/components/doorbird/camera.py b/homeassistant/components/doorbird/camera.py index b4bd40c442c..eaae3f19236 100644 --- a/homeassistant/components/doorbird/camera.py +++ b/homeassistant/components/doorbird/camera.py @@ -21,26 +21,30 @@ _LOGGER = logging.getLogger(__name__) _TIMEOUT = 10 # seconds -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the DoorBird camera platform.""" for doorstation in hass.data[DOORBIRD_DOMAIN]: device = doorstation.device - async_add_entities([ - DoorBirdCamera( - device.live_image_url, - _CAMERA_LIVE.format(doorstation.name), - _LIVE_INTERVAL, - device.rtsp_live_video_url), - DoorBirdCamera( - device.history_image_url(1, 'doorbell'), - _CAMERA_LAST_VISITOR.format(doorstation.name), - _LAST_VISITOR_INTERVAL), - DoorBirdCamera( - device.history_image_url(1, 'motionsensor'), - _CAMERA_LAST_MOTION.format(doorstation.name), - _LAST_MOTION_INTERVAL), - ]) + async_add_entities( + [ + DoorBirdCamera( + device.live_image_url, + _CAMERA_LIVE.format(doorstation.name), + _LIVE_INTERVAL, + device.rtsp_live_video_url, + ), + DoorBirdCamera( + device.history_image_url(1, "doorbell"), + _CAMERA_LAST_VISITOR.format(doorstation.name), + _LAST_VISITOR_INTERVAL, + ), + DoorBirdCamera( + device.history_image_url(1, "motionsensor"), + _CAMERA_LAST_MOTION.format(doorstation.name), + _LAST_MOTION_INTERVAL, + ), + ] + ) class DoorBirdCamera(Camera): diff --git a/homeassistant/components/doorbird/switch.py b/homeassistant/components/doorbird/switch.py index f3b1f5f059e..a907099cba4 100644 --- a/homeassistant/components/doorbird/switch.py +++ b/homeassistant/components/doorbird/switch.py @@ -8,7 +8,7 @@ from . import DOMAIN as DOORBIRD_DOMAIN _LOGGER = logging.getLogger(__name__) -IR_RELAY = '__ir_light__' +IR_RELAY = "__ir_light__" def setup_platform(hass, config, add_entities, discovery_info=None): @@ -16,7 +16,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): switches = [] for doorstation in hass.data[DOORBIRD_DOMAIN]: - relays = doorstation.device.info()['RELAYS'] + relays = doorstation.device.info()["RELAYS"] relays.append(IR_RELAY) for relay in relays: @@ -71,8 +71,7 @@ class DoorBirdSwitch(SwitchDevice): def turn_off(self, **kwargs): """Turn off the relays is not needed. They are time-based.""" - raise NotImplementedError( - "DoorBird relays cannot be manually turned off.") + raise NotImplementedError("DoorBird relays cannot be manually turned off.") def update(self): """Wait for the correct amount of assumed time to pass.""" diff --git a/homeassistant/components/dovado/__init__.py b/homeassistant/components/dovado/__init__.py index 2a240c2a79e..29f0cc59392 100644 --- a/homeassistant/components/dovado/__init__.py +++ b/homeassistant/components/dovado/__init__.py @@ -6,20 +6,26 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.const import ( - CONF_USERNAME, CONF_PASSWORD, CONF_HOST, CONF_PORT, - DEVICE_DEFAULT_NAME) + CONF_USERNAME, + CONF_PASSWORD, + CONF_HOST, + CONF_PORT, + DEVICE_DEFAULT_NAME, +) from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -DOMAIN = 'dovado' +DOMAIN = "dovado" -CONFIG_SCHEMA = vol.Schema({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_HOST): cv.string, - vol.Optional(CONF_PORT): cv.port, -}) +CONFIG_SCHEMA = vol.Schema( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_HOST): cv.string, + vol.Optional(CONF_PORT): cv.port, + } +) MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30) @@ -30,8 +36,11 @@ def setup(hass, config): hass.data[DOMAIN] = DovadoData( dovado.Dovado( - config[CONF_USERNAME], config[CONF_PASSWORD], - config.get(CONF_HOST), config.get(CONF_PORT)) + config[CONF_USERNAME], + config[CONF_PASSWORD], + config.get(CONF_HOST), + config.get(CONF_PORT), + ) ) return True @@ -56,8 +65,7 @@ class DovadoData: self.state = self._client.state or {} if not self.state: return False - self.state.update( - connected=self.state.get("modem status") == "CONNECTED") + self.state.update(connected=self.state.get("modem status") == "CONNECTED") _LOGGER.debug("Received: %s", self.state) return True except OSError as error: diff --git a/homeassistant/components/dovado/notify.py b/homeassistant/components/dovado/notify.py index f9d9e5574a1..02ce994b1df 100644 --- a/homeassistant/components/dovado/notify.py +++ b/homeassistant/components/dovado/notify.py @@ -1,8 +1,7 @@ """Support for SMS notifications from the Dovado router.""" import logging -from homeassistant.components.notify import ( - ATTR_TARGET, BaseNotificationService) +from homeassistant.components.notify import ATTR_TARGET, BaseNotificationService from . import DOMAIN as DOVADO_DOMAIN diff --git a/homeassistant/components/dovado/sensor.py b/homeassistant/components/dovado/sensor.py index 7a825118fc6..d3374c8d02a 100644 --- a/homeassistant/components/dovado/sensor.py +++ b/homeassistant/components/dovado/sensor.py @@ -16,26 +16,23 @@ _LOGGER = logging.getLogger(__name__) MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30) -SENSOR_UPLOAD = 'upload' -SENSOR_DOWNLOAD = 'download' -SENSOR_SIGNAL = 'signal' -SENSOR_NETWORK = 'network' -SENSOR_SMS_UNREAD = 'sms' +SENSOR_UPLOAD = "upload" +SENSOR_DOWNLOAD = "download" +SENSOR_SIGNAL = "signal" +SENSOR_NETWORK = "network" +SENSOR_SMS_UNREAD = "sms" SENSORS = { - SENSOR_NETWORK: ('signal strength', 'Network', None, - 'mdi:access-point-network'), - SENSOR_SIGNAL: ('signal strength', 'Signal Strength', '%', 'mdi:signal'), - SENSOR_SMS_UNREAD: ('sms unread', 'SMS unread', '', - 'mdi:message-text-outline'), - SENSOR_UPLOAD: ('traffic modem tx', 'Sent', 'GB', 'mdi:cloud-upload'), - SENSOR_DOWNLOAD: ('traffic modem rx', 'Received', 'GB', - 'mdi:cloud-download'), + SENSOR_NETWORK: ("signal strength", "Network", None, "mdi:access-point-network"), + SENSOR_SIGNAL: ("signal strength", "Signal Strength", "%", "mdi:signal"), + SENSOR_SMS_UNREAD: ("sms unread", "SMS unread", "", "mdi:message-text-outline"), + SENSOR_UPLOAD: ("traffic modem tx", "Sent", "GB", "mdi:cloud-upload"), + SENSOR_DOWNLOAD: ("traffic modem rx", "Received", "GB", "mdi:cloud-download"), } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_SENSORS): vol.All(cv.ensure_list, [vol.In(SENSORS)]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_SENSORS): vol.All(cv.ensure_list, [vol.In(SENSORS)])} +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -103,5 +100,4 @@ class DovadoSensor(Entity): @property def device_state_attributes(self): """Return the state attributes.""" - return {k: v for k, v in self._data.state.items() - if k not in ['date', 'time']} + return {k: v for k, v in self._data.state.items() if k not in ["date", "time"]} diff --git a/homeassistant/components/downloader/__init__.py b/homeassistant/components/downloader/__init__.py index 7e169acc5a3..0fe589f2765 100644 --- a/homeassistant/components/downloader/__init__.py +++ b/homeassistant/components/downloader/__init__.py @@ -12,31 +12,32 @@ from homeassistant.util import sanitize_filename _LOGGER = logging.getLogger(__name__) -ATTR_FILENAME = 'filename' -ATTR_SUBDIR = 'subdir' -ATTR_URL = 'url' -ATTR_OVERWRITE = 'overwrite' +ATTR_FILENAME = "filename" +ATTR_SUBDIR = "subdir" +ATTR_URL = "url" +ATTR_OVERWRITE = "overwrite" -CONF_DOWNLOAD_DIR = 'download_dir' +CONF_DOWNLOAD_DIR = "download_dir" -DOMAIN = 'downloader' -DOWNLOAD_FAILED_EVENT = 'download_failed' -DOWNLOAD_COMPLETED_EVENT = 'download_completed' +DOMAIN = "downloader" +DOWNLOAD_FAILED_EVENT = "download_failed" +DOWNLOAD_COMPLETED_EVENT = "download_completed" -SERVICE_DOWNLOAD_FILE = 'download_file' +SERVICE_DOWNLOAD_FILE = "download_file" -SERVICE_DOWNLOAD_FILE_SCHEMA = vol.Schema({ - vol.Required(ATTR_URL): cv.url, - vol.Optional(ATTR_SUBDIR): cv.string, - vol.Optional(ATTR_FILENAME): cv.string, - vol.Optional(ATTR_OVERWRITE, default=False): cv.boolean, -}) +SERVICE_DOWNLOAD_FILE_SCHEMA = vol.Schema( + { + vol.Required(ATTR_URL): cv.url, + vol.Optional(ATTR_SUBDIR): cv.string, + vol.Optional(ATTR_FILENAME): cv.string, + vol.Optional(ATTR_OVERWRITE, default=False): cv.boolean, + } +) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_DOWNLOAD_DIR): cv.string, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + {DOMAIN: vol.Schema({vol.Required(CONF_DOWNLOAD_DIR): cv.string})}, + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): @@ -49,13 +50,14 @@ def setup(hass, config): if not os.path.isdir(download_path): _LOGGER.error( - "Download path %s does not exist. File Downloader not active", - download_path) + "Download path %s does not exist. File Downloader not active", download_path + ) return False def download_file(service): """Start thread to download file specified in the URL.""" + def do_download(): """Download the file.""" try: @@ -76,20 +78,18 @@ def setup(hass, config): if req.status_code != 200: _LOGGER.warning( - "downloading '%s' failed, status_code=%d", - url, - req.status_code) + "downloading '%s' failed, status_code=%d", url, req.status_code + ) hass.bus.fire( - "{}_{}".format(DOMAIN, DOWNLOAD_FAILED_EVENT), { - 'url': url, - 'filename': filename - }) + "{}_{}".format(DOMAIN, DOWNLOAD_FAILED_EVENT), + {"url": url, "filename": filename}, + ) else: - if filename is None and \ - 'content-disposition' in req.headers: - match = re.findall(r"filename=(\S+)", - req.headers['content-disposition']) + if filename is None and "content-disposition" in req.headers: + match = re.findall( + r"filename=(\S+)", req.headers["content-disposition"] + ) if match: filename = match[0].strip("'\" ") @@ -98,7 +98,7 @@ def setup(hass, config): filename = os.path.basename(url).strip() if not filename: - filename = 'ha_download' + filename = "ha_download" # Remove stuff to ruin paths filename = sanitize_filename(filename) @@ -130,24 +130,22 @@ def setup(hass, config): _LOGGER.debug("%s -> %s", url, final_path) - with open(final_path, 'wb') as fil: + with open(final_path, "wb") as fil: for chunk in req.iter_content(1024): fil.write(chunk) _LOGGER.debug("Downloading of %s done", url) hass.bus.fire( - "{}_{}".format(DOMAIN, DOWNLOAD_COMPLETED_EVENT), { - 'url': url, - 'filename': filename - }) + "{}_{}".format(DOMAIN, DOWNLOAD_COMPLETED_EVENT), + {"url": url, "filename": filename}, + ) except requests.exceptions.ConnectionError: _LOGGER.exception("ConnectionError occurred for %s", url) hass.bus.fire( - "{}_{}".format(DOMAIN, DOWNLOAD_FAILED_EVENT), { - 'url': url, - 'filename': filename - }) + "{}_{}".format(DOMAIN, DOWNLOAD_FAILED_EVENT), + {"url": url, "filename": filename}, + ) # Remove file if we started downloading but failed if final_path and os.path.isfile(final_path): @@ -155,7 +153,11 @@ def setup(hass, config): threading.Thread(target=do_download).start() - hass.services.register(DOMAIN, SERVICE_DOWNLOAD_FILE, download_file, - schema=SERVICE_DOWNLOAD_FILE_SCHEMA) + hass.services.register( + DOMAIN, + SERVICE_DOWNLOAD_FILE, + download_file, + schema=SERVICE_DOWNLOAD_FILE_SCHEMA, + ) return True diff --git a/homeassistant/components/dsmr/sensor.py b/homeassistant/components/dsmr/sensor.py index 15b2b7fd0de..5aeb88e6399 100644 --- a/homeassistant/components/dsmr/sensor.py +++ b/homeassistant/components/dsmr/sensor.py @@ -7,166 +7,97 @@ import logging import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ( - CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP) +from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP from homeassistant.core import CoreState import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -CONF_DSMR_VERSION = 'dsmr_version' -CONF_RECONNECT_INTERVAL = 'reconnect_interval' -CONF_PRECISION = 'precision' +CONF_DSMR_VERSION = "dsmr_version" +CONF_RECONNECT_INTERVAL = "reconnect_interval" +CONF_PRECISION = "precision" -DEFAULT_DSMR_VERSION = '2.2' -DEFAULT_PORT = '/dev/ttyUSB0' +DEFAULT_DSMR_VERSION = "2.2" +DEFAULT_PORT = "/dev/ttyUSB0" DEFAULT_PRECISION = 3 -DOMAIN = 'dsmr' +DOMAIN = "dsmr" -ICON_GAS = 'mdi:fire' -ICON_POWER = 'mdi:flash' -ICON_POWER_FAILURE = 'mdi:flash-off' -ICON_SWELL_SAG = 'mdi:pulse' +ICON_GAS = "mdi:fire" +ICON_POWER = "mdi:flash" +ICON_POWER_FAILURE = "mdi:flash-off" +ICON_SWELL_SAG = "mdi:pulse" # Smart meter sends telegram every 10 seconds MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=10) RECONNECT_INTERVAL = 5 -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.string, - vol.Optional(CONF_HOST): cv.string, - vol.Optional(CONF_DSMR_VERSION, default=DEFAULT_DSMR_VERSION): vol.All( - cv.string, vol.In(['5', '4', '2.2'])), - vol.Optional(CONF_RECONNECT_INTERVAL, default=30): int, - vol.Optional(CONF_PRECISION, default=DEFAULT_PRECISION): vol.Coerce(int), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.string, + vol.Optional(CONF_HOST): cv.string, + vol.Optional(CONF_DSMR_VERSION, default=DEFAULT_DSMR_VERSION): vol.All( + cv.string, vol.In(["5", "4", "2.2"]) + ), + vol.Optional(CONF_RECONNECT_INTERVAL, default=30): int, + vol.Optional(CONF_PRECISION, default=DEFAULT_PRECISION): vol.Coerce(int), + } +) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the DSMR sensor.""" # Suppress logging - logging.getLogger('dsmr_parser').setLevel(logging.ERROR) + logging.getLogger("dsmr_parser").setLevel(logging.ERROR) from dsmr_parser import obis_references as obis_ref - from dsmr_parser.clients.protocol import ( - create_dsmr_reader, create_tcp_dsmr_reader) + from dsmr_parser.clients.protocol import create_dsmr_reader, create_tcp_dsmr_reader import serial dsmr_version = config[CONF_DSMR_VERSION] # Define list of name,obis mappings to generate entities obis_mapping = [ - [ - 'Power Consumption', - obis_ref.CURRENT_ELECTRICITY_USAGE - ], - [ - 'Power Production', - obis_ref.CURRENT_ELECTRICITY_DELIVERY - ], - [ - 'Power Tariff', - obis_ref.ELECTRICITY_ACTIVE_TARIFF - ], - [ - 'Power Consumption (low)', - obis_ref.ELECTRICITY_USED_TARIFF_1 - ], - [ - 'Power Consumption (normal)', - obis_ref.ELECTRICITY_USED_TARIFF_2 - ], - [ - 'Power Production (low)', - obis_ref.ELECTRICITY_DELIVERED_TARIFF_1 - ], - [ - 'Power Production (normal)', - obis_ref.ELECTRICITY_DELIVERED_TARIFF_2 - ], - [ - 'Power Consumption Phase L1', - obis_ref.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE - ], - [ - 'Power Consumption Phase L2', - obis_ref.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE - ], - [ - 'Power Consumption Phase L3', - obis_ref.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE - ], - [ - 'Power Production Phase L1', - obis_ref.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE - ], - [ - 'Power Production Phase L2', - obis_ref.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE - ], - [ - 'Power Production Phase L3', - obis_ref.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE - ], - [ - 'Long Power Failure Count', - obis_ref.LONG_POWER_FAILURE_COUNT - ], - [ - 'Voltage Sags Phase L1', - obis_ref.VOLTAGE_SAG_L1_COUNT - ], - [ - 'Voltage Sags Phase L2', - obis_ref.VOLTAGE_SAG_L2_COUNT - ], - [ - 'Voltage Sags Phase L3', - obis_ref.VOLTAGE_SAG_L3_COUNT - ], - [ - 'Voltage Swells Phase L1', - obis_ref.VOLTAGE_SWELL_L1_COUNT - ], - [ - 'Voltage Swells Phase L2', - obis_ref.VOLTAGE_SWELL_L2_COUNT - ], - [ - 'Voltage Swells Phase L3', - obis_ref.VOLTAGE_SWELL_L3_COUNT - ], - [ - 'Voltage Phase L1', - obis_ref.INSTANTANEOUS_VOLTAGE_L1 - ], - [ - 'Voltage Phase L2', - obis_ref.INSTANTANEOUS_VOLTAGE_L2 - ], - [ - 'Voltage Phase L3', - obis_ref.INSTANTANEOUS_VOLTAGE_L3 - ], + ["Power Consumption", obis_ref.CURRENT_ELECTRICITY_USAGE], + ["Power Production", obis_ref.CURRENT_ELECTRICITY_DELIVERY], + ["Power Tariff", obis_ref.ELECTRICITY_ACTIVE_TARIFF], + ["Power Consumption (low)", obis_ref.ELECTRICITY_USED_TARIFF_1], + ["Power Consumption (normal)", obis_ref.ELECTRICITY_USED_TARIFF_2], + ["Power Production (low)", obis_ref.ELECTRICITY_DELIVERED_TARIFF_1], + ["Power Production (normal)", obis_ref.ELECTRICITY_DELIVERED_TARIFF_2], + ["Power Consumption Phase L1", obis_ref.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE], + ["Power Consumption Phase L2", obis_ref.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE], + ["Power Consumption Phase L3", obis_ref.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE], + ["Power Production Phase L1", obis_ref.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE], + ["Power Production Phase L2", obis_ref.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE], + ["Power Production Phase L3", obis_ref.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE], + ["Long Power Failure Count", obis_ref.LONG_POWER_FAILURE_COUNT], + ["Voltage Sags Phase L1", obis_ref.VOLTAGE_SAG_L1_COUNT], + ["Voltage Sags Phase L2", obis_ref.VOLTAGE_SAG_L2_COUNT], + ["Voltage Sags Phase L3", obis_ref.VOLTAGE_SAG_L3_COUNT], + ["Voltage Swells Phase L1", obis_ref.VOLTAGE_SWELL_L1_COUNT], + ["Voltage Swells Phase L2", obis_ref.VOLTAGE_SWELL_L2_COUNT], + ["Voltage Swells Phase L3", obis_ref.VOLTAGE_SWELL_L3_COUNT], + ["Voltage Phase L1", obis_ref.INSTANTANEOUS_VOLTAGE_L1], + ["Voltage Phase L2", obis_ref.INSTANTANEOUS_VOLTAGE_L2], + ["Voltage Phase L3", obis_ref.INSTANTANEOUS_VOLTAGE_L3], ] # Generate device entities devices = [DSMREntity(name, obis, config) for name, obis in obis_mapping] # Protocol version specific obis - if dsmr_version in ('4', '5'): + if dsmr_version in ("4", "5"): gas_obis = obis_ref.HOURLY_GAS_METER_READING else: gas_obis = obis_ref.GAS_METER_READING # Add gas meter reading and derivative for usage devices += [ - DSMREntity('Gas Consumption', gas_obis, config), - DerivativeDSMREntity('Hourly Gas Consumption', gas_obis, config), + DSMREntity("Gas Consumption", gas_obis, config), + DerivativeDSMREntity("Hourly Gas Consumption", gas_obis, config), ] async_add_entities(devices) @@ -182,23 +113,33 @@ async def async_setup_platform(hass, config, async_add_entities, # serial and calls update_entities_telegram to update entities on arrival if CONF_HOST in config: reader_factory = partial( - create_tcp_dsmr_reader, config[CONF_HOST], config[CONF_PORT], - config[CONF_DSMR_VERSION], update_entities_telegram, - loop=hass.loop) + create_tcp_dsmr_reader, + config[CONF_HOST], + config[CONF_PORT], + config[CONF_DSMR_VERSION], + update_entities_telegram, + loop=hass.loop, + ) else: reader_factory = partial( - create_dsmr_reader, config[CONF_PORT], config[CONF_DSMR_VERSION], - update_entities_telegram, loop=hass.loop) + create_dsmr_reader, + config[CONF_PORT], + config[CONF_DSMR_VERSION], + update_entities_telegram, + loop=hass.loop, + ) async def connect_and_reconnect(): """Connect to DSMR and keep reconnecting until Home Assistant stops.""" while hass.state != CoreState.stopping: # Start DSMR asyncio.Protocol reader try: - transport, protocol = await hass.loop.create_task( - reader_factory()) - except (serial.serialutil.SerialException, ConnectionRefusedError, - TimeoutError): + transport, protocol = await hass.loop.create_task(reader_factory()) + except ( + serial.serialutil.SerialException, + ConnectionRefusedError, + TimeoutError, + ): # Log any error while establishing connection and drop to retry # connection wait _LOGGER.exception("Error connecting to DSMR") @@ -207,7 +148,8 @@ async def async_setup_platform(hass, config, async_add_entities, if transport: # Register listener to close transport on HA shutdown stop_listener = hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_STOP, transport.close) + EVENT_HOMEASSISTANT_STOP, transport.close + ) # Wait for reader to close await protocol.wait_closed() @@ -257,13 +199,13 @@ class DSMREntity(Entity): @property def icon(self): """Icon to use in the frontend, if any.""" - if 'Sags' in self._name or 'Swells' in self.name: + if "Sags" in self._name or "Swells" in self.name: return ICON_SWELL_SAG - if 'Failure' in self._name: + if "Failure" in self._name: return ICON_POWER_FAILURE - if 'Power' in self._name: + if "Power" in self._name: return ICON_POWER - if 'Gas' in self._name: + if "Gas" in self._name: return ICON_GAS @property @@ -271,7 +213,7 @@ class DSMREntity(Entity): """Return the state of sensor, if available, translate if needed.""" from dsmr_parser import obis_references as obis - value = self.get_dsmr_object_attr('value') + value = self.get_dsmr_object_attr("value") if self._obis == obis.ELECTRICITY_ACTIVE_TARIFF: return self.translate_tariff(value) @@ -289,17 +231,17 @@ class DSMREntity(Entity): @property def unit_of_measurement(self): """Return the unit of measurement of this entity, if any.""" - return self.get_dsmr_object_attr('unit') + return self.get_dsmr_object_attr("unit") @staticmethod def translate_tariff(value): """Convert 2/1 to normal/low.""" # DSMR V2.2: Note: Rate code 1 is used for low rate and rate code 2 is # used for normal rate. - if value == '0002': - return 'normal' - if value == '0001': - return 'low' + if value == "0002": + return "normal" + if value == "0001": + return "low" return None @@ -331,9 +273,9 @@ class DerivativeDSMREntity(DSMREntity): """ # check if the timestamp for the object differs from the previous one - timestamp = self.get_dsmr_object_attr('datetime') + timestamp = self.get_dsmr_object_attr("datetime") if timestamp and timestamp != self._previous_timestamp: - current_reading = self.get_dsmr_object_attr('value') + current_reading = self.get_dsmr_object_attr("value") if self._previous_reading is None: # Can't calculate rate without previous datapoint @@ -352,6 +294,6 @@ class DerivativeDSMREntity(DSMREntity): @property def unit_of_measurement(self): """Return the unit of measurement of this entity, per hour, if any.""" - unit = self.get_dsmr_object_attr('unit') + unit = self.get_dsmr_object_attr("unit") if unit: - return unit + '/h' + return unit + "/h" diff --git a/homeassistant/components/dte_energy_bridge/sensor.py b/homeassistant/components/dte_energy_bridge/sensor.py index 8610a1e7f70..b904d004c61 100644 --- a/homeassistant/components/dte_energy_bridge/sensor.py +++ b/homeassistant/components/dte_energy_bridge/sensor.py @@ -10,20 +10,23 @@ from homeassistant.const import CONF_NAME _LOGGER = logging.getLogger(__name__) -CONF_IP_ADDRESS = 'ip' -CONF_VERSION = 'version' +CONF_IP_ADDRESS = "ip" +CONF_VERSION = "version" -DEFAULT_NAME = 'Current Energy Usage' +DEFAULT_NAME = "Current Energy Usage" DEFAULT_VERSION = 1 -ICON = 'mdi:flash' +ICON = "mdi:flash" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_IP_ADDRESS): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_VERSION, default=DEFAULT_VERSION): - vol.All(vol.Coerce(int), vol.Any(1, 2)) -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_IP_ADDRESS): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_VERSION, default=DEFAULT_VERSION): vol.All( + vol.Coerce(int), vol.Any(1, 2) + ), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -81,14 +84,16 @@ class DteEnergyBridgeSensor(Entity): response = requests.get(self._url, timeout=5) except (requests.exceptions.RequestException, ValueError): _LOGGER.warning( - 'Could not update status for DTE Energy Bridge (%s)', - self._name) + "Could not update status for DTE Energy Bridge (%s)", self._name + ) return if response.status_code != 200: _LOGGER.warning( - 'Invalid status_code from DTE Energy Bridge: %s (%s)', - response.status_code, self._name) + "Invalid status_code from DTE Energy Bridge: %s (%s)", + response.status_code, + self._name, + ) return response_split = response.text.split() @@ -96,7 +101,9 @@ class DteEnergyBridgeSensor(Entity): if len(response_split) != 2: _LOGGER.warning( 'Invalid response from DTE Energy Bridge: "%s" (%s)', - response.text, self._name) + response.text, + self._name, + ) return val = float(response_split[0]) @@ -107,7 +114,7 @@ class DteEnergyBridgeSensor(Entity): # Limiting to version 1 because version 2 apparently always returns # values in the format 000000.000 kW, but the scaling is Watts # NOT kWatts - if self._version == 1 and '.' in response_split[0]: + if self._version == 1 and "." in response_split[0]: self._state = val else: self._state = val / 1000 diff --git a/homeassistant/components/dublin_bus_transport/sensor.py b/homeassistant/components/dublin_bus_transport/sensor.py index 7a70d7af3a7..203cfb1e27c 100644 --- a/homeassistant/components/dublin_bus_transport/sensor.py +++ b/homeassistant/components/dublin_bus_transport/sensor.py @@ -20,30 +20,32 @@ from homeassistant.const import CONF_NAME, ATTR_ATTRIBUTION from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -_RESOURCE = 'https://data.dublinked.ie/cgi-bin/rtpi/realtimebusinformation' +_RESOURCE = "https://data.dublinked.ie/cgi-bin/rtpi/realtimebusinformation" -ATTR_STOP_ID = 'Stop ID' -ATTR_ROUTE = 'Route' -ATTR_DUE_IN = 'Due in' -ATTR_DUE_AT = 'Due at' -ATTR_NEXT_UP = 'Later Bus' +ATTR_STOP_ID = "Stop ID" +ATTR_ROUTE = "Route" +ATTR_DUE_IN = "Due in" +ATTR_DUE_AT = "Due at" +ATTR_NEXT_UP = "Later Bus" ATTRIBUTION = "Data provided by data.dublinked.ie" -CONF_STOP_ID = 'stopid' -CONF_ROUTE = 'route' +CONF_STOP_ID = "stopid" +CONF_ROUTE = "route" -DEFAULT_NAME = 'Next Bus' -ICON = 'mdi:bus' +DEFAULT_NAME = "Next Bus" +ICON = "mdi:bus" SCAN_INTERVAL = timedelta(minutes=1) -TIME_STR_FORMAT = '%H:%M' +TIME_STR_FORMAT = "%H:%M" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_STOP_ID): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_ROUTE, default=""): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_STOP_ID): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_ROUTE, default=""): cv.string, + } +) def due_in_minutes(timestamp): @@ -51,8 +53,9 @@ def due_in_minutes(timestamp): The timestamp should be in the format day/month/year hour/minute/second """ - diff = datetime.strptime( - timestamp, "%d/%m/%Y %H:%M:%S") - dt_util.now().replace(tzinfo=None) + diff = datetime.strptime(timestamp, "%d/%m/%Y %H:%M:%S") - dt_util.now().replace( + tzinfo=None + ) return str(int(diff.total_seconds() / 60)) @@ -103,13 +106,13 @@ class DublinPublicTransportSensor(Entity): ATTR_STOP_ID: self._stop, ATTR_ROUTE: self._times[0][ATTR_ROUTE], ATTR_ATTRIBUTION: ATTRIBUTION, - ATTR_NEXT_UP: next_up + ATTR_NEXT_UP: next_up, } @property def unit_of_measurement(self): """Return the unit this state is expressed in.""" - return 'min' + return "min" @property def icon(self): @@ -133,48 +136,48 @@ class PublicTransportData: """Initialize the data object.""" self.stop = stop self.route = route - self.info = [{ATTR_DUE_AT: 'n/a', - ATTR_ROUTE: self.route, - ATTR_DUE_IN: 'n/a'}] + self.info = [{ATTR_DUE_AT: "n/a", ATTR_ROUTE: self.route, ATTR_DUE_IN: "n/a"}] def update(self): """Get the latest data from opendata.ch.""" params = {} - params['stopid'] = self.stop + params["stopid"] = self.stop if self.route: - params['routeid'] = self.route + params["routeid"] = self.route - params['maxresults'] = 2 - params['format'] = 'json' + params["maxresults"] = 2 + params["format"] = "json" response = requests.get(_RESOURCE, params, timeout=10) if response.status_code != 200: - self.info = [{ATTR_DUE_AT: 'n/a', - ATTR_ROUTE: self.route, - ATTR_DUE_IN: 'n/a'}] + self.info = [ + {ATTR_DUE_AT: "n/a", ATTR_ROUTE: self.route, ATTR_DUE_IN: "n/a"} + ] return result = response.json() - if str(result['errorcode']) != '0': - self.info = [{ATTR_DUE_AT: 'n/a', - ATTR_ROUTE: self.route, - ATTR_DUE_IN: 'n/a'}] + if str(result["errorcode"]) != "0": + self.info = [ + {ATTR_DUE_AT: "n/a", ATTR_ROUTE: self.route, ATTR_DUE_IN: "n/a"} + ] return self.info = [] - for item in result['results']: - due_at = item.get('departuredatetime') - route = item.get('route') + for item in result["results"]: + due_at = item.get("departuredatetime") + route = item.get("route") if due_at is not None and route is not None: - bus_data = {ATTR_DUE_AT: due_at, - ATTR_ROUTE: route, - ATTR_DUE_IN: due_in_minutes(due_at)} + bus_data = { + ATTR_DUE_AT: due_at, + ATTR_ROUTE: route, + ATTR_DUE_IN: due_in_minutes(due_at), + } self.info.append(bus_data) if not self.info: - self.info = [{ATTR_DUE_AT: 'n/a', - ATTR_ROUTE: self.route, - ATTR_DUE_IN: 'n/a'}] + self.info = [ + {ATTR_DUE_AT: "n/a", ATTR_ROUTE: self.route, ATTR_DUE_IN: "n/a"} + ] diff --git a/homeassistant/components/duckdns/__init__.py b/homeassistant/components/duckdns/__init__.py index 9899a0af98e..7d677580177 100644 --- a/homeassistant/components/duckdns/__init__.py +++ b/homeassistant/components/duckdns/__init__.py @@ -11,26 +11,29 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession _LOGGER = logging.getLogger(__name__) -ATTR_TXT = 'txt' +ATTR_TXT = "txt" -DOMAIN = 'duckdns' +DOMAIN = "duckdns" INTERVAL = timedelta(minutes=5) -SERVICE_SET_TXT = 'set_txt' +SERVICE_SET_TXT = "set_txt" -UPDATE_URL = 'https://www.duckdns.org/update' +UPDATE_URL = "https://www.duckdns.org/update" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_DOMAIN): cv.string, - vol.Required(CONF_ACCESS_TOKEN): cv.string, - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_DOMAIN): cv.string, + vol.Required(CONF_ACCESS_TOKEN): cv.string, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) -SERVICE_TXT_SCHEMA = vol.Schema({ - vol.Required(ATTR_TXT): vol.Any(None, cv.string) -}) +SERVICE_TXT_SCHEMA = vol.Schema({vol.Required(ATTR_TXT): vol.Any(None, cv.string)}) async def async_setup(hass, config): @@ -50,13 +53,12 @@ async def async_setup(hass, config): async def update_domain_service(call): """Update the DuckDNS entry.""" - await _update_duckdns( - session, domain, token, txt=call.data[ATTR_TXT]) + await _update_duckdns(session, domain, token, txt=call.data[ATTR_TXT]) async_track_time_interval(hass, update_domain_interval, INTERVAL) hass.services.async_register( - DOMAIN, SERVICE_SET_TXT, update_domain_service, - schema=SERVICE_TXT_SCHEMA) + DOMAIN, SERVICE_SET_TXT, update_domain_service, schema=SERVICE_TXT_SCHEMA + ) return result @@ -64,29 +66,25 @@ async def async_setup(hass, config): _SENTINEL = object() -async def _update_duckdns(session, domain, token, *, txt=_SENTINEL, - clear=False): +async def _update_duckdns(session, domain, token, *, txt=_SENTINEL, clear=False): """Update DuckDNS.""" - params = { - 'domains': domain, - 'token': token, - } + params = {"domains": domain, "token": token} if txt is not _SENTINEL: if txt is None: # Pass in empty txt value to indicate it's clearing txt record - params['txt'] = '' + params["txt"] = "" clear = True else: - params['txt'] = txt + params["txt"] = txt if clear: - params['clear'] = 'true' + params["clear"] = "true" resp = await session.get(UPDATE_URL, params=params) body = await resp.text() - if body != 'OK': + if body != "OK": _LOGGER.warning("Updating DuckDNS domain failed: %s", domain) return False diff --git a/homeassistant/components/duke_energy/sensor.py b/homeassistant/components/duke_energy/sensor.py index e364e35048b..b8a9bec5db8 100644 --- a/homeassistant/components/duke_energy/sensor.py +++ b/homeassistant/components/duke_energy/sensor.py @@ -10,10 +10,9 @@ import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string} +) LAST_BILL_USAGE = "last_bills_usage" LAST_BILL_AVERAGE_USAGE = "last_bills_average_usage" @@ -25,9 +24,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): from pydukeenergy.api import DukeEnergy, DukeEnergyException try: - duke = DukeEnergy(config[CONF_USERNAME], - config[CONF_PASSWORD], - update_interval=120) + duke = DukeEnergy( + config[CONF_USERNAME], config[CONF_PASSWORD], update_interval=120 + ) except DukeEnergyException: _LOGGER.error("Failed to set up Duke Energy") return @@ -68,7 +67,7 @@ class DukeEnergyMeter(Entity): attributes = { LAST_BILL_USAGE: self.duke_meter.get_total(), LAST_BILL_AVERAGE_USAGE: self.duke_meter.get_average(), - LAST_BILL_DAYS_BILLED: self.duke_meter.get_days_billed() + LAST_BILL_DAYS_BILLED: self.duke_meter.get_days_billed(), } return attributes diff --git a/homeassistant/components/dunehd/media_player.py b/homeassistant/components/dunehd/media_player.py index a5698c74654..95e8cac3dbd 100644 --- a/homeassistant/components/dunehd/media_player.py +++ b/homeassistant/components/dunehd/media_player.py @@ -1,30 +1,47 @@ """DuneHD implementation of the media player.""" import voluptuous as vol -from homeassistant.components.media_player import ( - MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA from homeassistant.components.media_player.const import ( - SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, - SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, - SUPPORT_TURN_ON) + SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, + SUPPORT_PLAY, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_SELECT_SOURCE, + SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, +) from homeassistant.const import ( - CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON, STATE_PAUSED, STATE_PLAYING) + CONF_HOST, + CONF_NAME, + STATE_OFF, + STATE_ON, + STATE_PAUSED, + STATE_PLAYING, +) import homeassistant.helpers.config_validation as cv -DEFAULT_NAME = 'DuneHD' +DEFAULT_NAME = "DuneHD" -CONF_SOURCES = 'sources' +CONF_SOURCES = "sources" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_SOURCES): vol.Schema({cv.string: cv.string}), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_SOURCES): vol.Schema({cv.string: cv.string}), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + } +) -DUNEHD_PLAYER_SUPPORT = \ - SUPPORT_PAUSE | SUPPORT_TURN_ON | SUPPORT_TURN_OFF | \ - SUPPORT_SELECT_SOURCE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \ - SUPPORT_PLAY +DUNEHD_PLAYER_SUPPORT = ( + SUPPORT_PAUSE + | SUPPORT_TURN_ON + | SUPPORT_TURN_OFF + | SUPPORT_SELECT_SOURCE + | SUPPORT_PREVIOUS_TRACK + | SUPPORT_NEXT_TRACK + | SUPPORT_PLAY +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -59,13 +76,13 @@ class DuneHDPlayerEntity(MediaPlayerDevice): def state(self): """Return player state.""" state = STATE_OFF - if 'playback_position' in self._state: + if "playback_position" in self._state: state = STATE_PLAYING - if self._state['player_state'] in ('playing', 'buffering'): + if self._state["player_state"] in ("playing", "buffering"): state = STATE_PLAYING - if int(self._state.get('playback_speed', 1234)) == 0: + if int(self._state.get("playback_speed", 1234)) == 0: state = STATE_PAUSED - if self._state['player_state'] == 'navigator': + if self._state["player_state"] == "navigator": state = STATE_ON return state @@ -77,12 +94,12 @@ class DuneHDPlayerEntity(MediaPlayerDevice): @property def volume_level(self): """Return the volume level of the media player (0..1).""" - return int(self._state.get('playback_volume', 0)) / 100 + return int(self._state.get("playback_volume", 0)) / 100 @property def is_volume_muted(self): """Return a boolean if volume is currently muted.""" - return int(self._state.get('playback_mute', 0)) == 1 + return int(self._state.get("playback_mute", 0)) == 1 @property def source_list(self): @@ -133,16 +150,16 @@ class DuneHDPlayerEntity(MediaPlayerDevice): self.__update_title() if self._media_title: return self._media_title - return self._state.get('playback_url', 'Not playing') + return self._state.get("playback_url", "Not playing") def __update_title(self): - if self._state['player_state'] == 'bluray_playback': - self._media_title = 'Blu-Ray' - elif 'playback_url' in self._state: + if self._state["player_state"] == "bluray_playback": + self._media_title = "Blu-Ray" + elif "playback_url" in self._state: sources = self._sources sval = sources.values() skey = sources.keys() - pburl = self._state['playback_url'] + pburl = self._state["playback_url"] if pburl in sval: self._media_title = list(skey)[list(sval).index(pburl)] else: diff --git a/homeassistant/components/dwd_weather_warnings/sensor.py b/homeassistant/components/dwd_weather_warnings/sensor.py index 9e61c9e3196..a019a5c7b3a 100644 --- a/homeassistant/components/dwd_weather_warnings/sensor.py +++ b/homeassistant/components/dwd_weather_warnings/sensor.py @@ -21,8 +21,7 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ( - ATTR_ATTRIBUTION, CONF_NAME, CONF_MONITORED_CONDITIONS) +from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME, CONF_MONITORED_CONDITIONS from homeassistant.util import Throttle import homeassistant.util.dt as dt_util from homeassistant.components.rest.sensor import RestData @@ -31,26 +30,34 @@ _LOGGER = logging.getLogger(__name__) ATTRIBUTION = "Data provided by DWD" -DEFAULT_NAME = 'DWD-Weather-Warnings' +DEFAULT_NAME = "DWD-Weather-Warnings" -CONF_REGION_NAME = 'region_name' +CONF_REGION_NAME = "region_name" SCAN_INTERVAL = timedelta(minutes=15) MONITORED_CONDITIONS = { - 'current_warning_level': ['Current Warning Level', - None, 'mdi:close-octagon-outline'], - 'advance_warning_level': ['Advance Warning Level', - None, 'mdi:close-octagon-outline'], + "current_warning_level": [ + "Current Warning Level", + None, + "mdi:close-octagon-outline", + ], + "advance_warning_level": [ + "Advance Warning Level", + None, + "mdi:close-octagon-outline", + ], } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_REGION_NAME): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_MONITORED_CONDITIONS, - default=list(MONITORED_CONDITIONS)): - vol.All(cv.ensure_list, [vol.In(MONITORED_CONDITIONS)]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_REGION_NAME): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional( + CONF_MONITORED_CONDITIONS, default=list(MONITORED_CONDITIONS) + ): vol.All(cv.ensure_list, [vol.In(MONITORED_CONDITIONS)]), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -60,8 +67,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None): api = DwdWeatherWarningsAPI(region_name) - sensors = [DwdWeatherWarningsSensor(api, name, condition) - for condition in config[CONF_MONITORED_CONDITIONS]] + sensors = [ + DwdWeatherWarningsSensor(api, name, condition) + for condition in config[CONF_MONITORED_CONDITIONS] + ] add_entities(sensors, True) @@ -106,50 +115,50 @@ class DwdWeatherWarningsSensor(Entity): @property def device_state_attributes(self): """Return the state attributes of the DWD-Weather-Warnings.""" - data = { - ATTR_ATTRIBUTION: ATTRIBUTION, - 'region_name': self._api.region_name - } + data = {ATTR_ATTRIBUTION: ATTRIBUTION, "region_name": self._api.region_name} if self._api.region_id is not None: - data['region_id'] = self._api.region_id + data["region_id"] = self._api.region_id if self._api.region_state is not None: - data['region_state'] = self._api.region_state + data["region_state"] = self._api.region_state - if self._api.data['time'] is not None: - data['last_update'] = dt_util.as_local( - dt_util.utc_from_timestamp(self._api.data['time'] / 1000)) + if self._api.data["time"] is not None: + data["last_update"] = dt_util.as_local( + dt_util.utc_from_timestamp(self._api.data["time"] / 1000) + ) - if self._var_id == 'current_warning_level': - prefix = 'current' - elif self._var_id == 'advance_warning_level': - prefix = 'advance' + if self._var_id == "current_warning_level": + prefix = "current" + elif self._var_id == "advance_warning_level": + prefix = "advance" else: - raise Exception('Unknown warning type') + raise Exception("Unknown warning type") - data['warning_count'] = self._api.data[prefix + '_warning_count'] + data["warning_count"] = self._api.data[prefix + "_warning_count"] i = 0 - for event in self._api.data[prefix + '_warnings']: + for event in self._api.data[prefix + "_warnings"]: i = i + 1 - data['warning_{}_name'.format(i)] = event['event'] - data['warning_{}_level'.format(i)] = event['level'] - data['warning_{}_type'.format(i)] = event['type'] - if event['headline']: - data['warning_{}_headline'.format(i)] = event['headline'] - if event['description']: - data['warning_{}_description'.format(i)] = event['description'] - if event['instruction']: - data['warning_{}_instruction'.format(i)] = event['instruction'] + data["warning_{}_name".format(i)] = event["event"] + data["warning_{}_level".format(i)] = event["level"] + data["warning_{}_type".format(i)] = event["type"] + if event["headline"]: + data["warning_{}_headline".format(i)] = event["headline"] + if event["description"]: + data["warning_{}_description".format(i)] = event["description"] + if event["instruction"]: + data["warning_{}_instruction".format(i)] = event["instruction"] - if event['start'] is not None: - data['warning_{}_start'.format(i)] = dt_util.as_local( - dt_util.utc_from_timestamp(event['start'] / 1000)) + if event["start"] is not None: + data["warning_{}_start".format(i)] = dt_util.as_local( + dt_util.utc_from_timestamp(event["start"] / 1000) + ) - if event['end'] is not None: - data['warning_{}_end'.format(i)] = dt_util.as_local( - dt_util.utc_from_timestamp(event['end'] / 1000)) + if event["end"] is not None: + data["warning_{}_end".format(i)] = dt_util.as_local( + dt_util.utc_from_timestamp(event["end"] / 1000) + ) return data @@ -169,13 +178,13 @@ class DwdWeatherWarningsAPI: def __init__(self, region_name): """Initialize the data object.""" resource = "{}{}{}?{}".format( - 'https://', - 'www.dwd.de', - '/DWD/warnungen/warnapp_landkreise/json/warnings.json', - 'jsonp=loadWarnings' + "https://", + "www.dwd.de", + "/DWD/warnungen/warnapp_landkreise/json/warnings.json", + "jsonp=loadWarnings", ) - self._rest = RestData('GET', resource, None, None, None, True) + self._rest = RestData("GET", resource, None, None, None, True) self.region_name = region_name self.region_id = None self.region_state = None @@ -189,20 +198,21 @@ class DwdWeatherWarningsAPI: try: self._rest.update() - json_string = self._rest.data[24:len(self._rest.data) - 2] + json_string = self._rest.data[24 : len(self._rest.data) - 2] json_obj = json.loads(json_string) - data = {'time': json_obj['time']} + data = {"time": json_obj["time"]} for mykey, myvalue in { - 'current': 'warnings', - 'advance': 'vorabInformation' + "current": "warnings", + "advance": "vorabInformation", }.items(): - _LOGGER.debug("Found %d %s global DWD warnings", - len(json_obj[myvalue]), mykey) + _LOGGER.debug( + "Found %d %s global DWD warnings", len(json_obj[myvalue]), mykey + ) - data['{}_warning_level'.format(mykey)] = 0 + data["{}_warning_level".format(mykey)] = 0 my_warnings = [] if self.region_id is not None: @@ -214,26 +224,25 @@ class DwdWeatherWarningsAPI: # loop through all items to find warnings, region_id # and region_state for region_name for key in json_obj[myvalue]: - my_region = json_obj[myvalue][key][0]['regionName'] + my_region = json_obj[myvalue][key][0]["regionName"] if my_region != self.region_name: continue my_warnings = json_obj[myvalue][key] - my_state = json_obj[myvalue][key][0]['stateShort'] + my_state = json_obj[myvalue][key][0]["stateShort"] self.region_id = key self.region_state = my_state break # Get max warning level - maxlevel = data['{}_warning_level'.format(mykey)] + maxlevel = data["{}_warning_level".format(mykey)] for event in my_warnings: - if event['level'] >= maxlevel: - data['{}_warning_level'.format(mykey)] = event['level'] + if event["level"] >= maxlevel: + data["{}_warning_level".format(mykey)] = event["level"] - data['{}_warning_count'.format(mykey)] = len(my_warnings) - data['{}_warnings'.format(mykey)] = my_warnings + data["{}_warning_count".format(mykey)] = len(my_warnings) + data["{}_warnings".format(mykey)] = my_warnings - _LOGGER.debug("Found %d %s local DWD warnings", - len(my_warnings), mykey) + _LOGGER.debug("Found %d %s local DWD warnings", len(my_warnings), mykey) self.data = data self.available = True diff --git a/homeassistant/components/dweet/__init__.py b/homeassistant/components/dweet/__init__.py index 148eeeec9a4..bf1298479c3 100644 --- a/homeassistant/components/dweet/__init__.py +++ b/homeassistant/components/dweet/__init__.py @@ -5,24 +5,34 @@ from datetime import timedelta import voluptuous as vol from homeassistant.const import ( - CONF_NAME, CONF_WHITELIST, EVENT_STATE_CHANGED, STATE_UNKNOWN) + CONF_NAME, + CONF_WHITELIST, + EVENT_STATE_CHANGED, + STATE_UNKNOWN, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers import state as state_helper from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -DOMAIN = 'dweet' +DOMAIN = "dweet" MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=1) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_NAME): cv.string, - vol.Required(CONF_WHITELIST, default=[]): - vol.All(cv.ensure_list, [cv.entity_id]), - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_WHITELIST, default=[]): vol.All( + cv.ensure_list, [cv.entity_id] + ), + } + ) + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): @@ -34,9 +44,12 @@ def setup(hass, config): def dweet_event_listener(event): """Listen for new messages on the bus and sends them to Dweet.io.""" - state = event.data.get('new_state') - if state is None or state.state in (STATE_UNKNOWN, '') \ - or state.entity_id not in whitelist: + state = event.data.get("new_state") + if ( + state is None + or state.state in (STATE_UNKNOWN, "") + or state.entity_id not in whitelist + ): return try: @@ -44,7 +57,7 @@ def setup(hass, config): except ValueError: _state = state.state - json_body[state.attributes.get('friendly_name')] = _state + json_body[state.attributes.get("friendly_name")] = _state send_data(name, json_body) @@ -57,6 +70,7 @@ def setup(hass, config): def send_data(name, msg): """Send the collected data to Dweet.io.""" import dweepy + try: dweepy.dweet_for(name, msg) except dweepy.DweepyError: diff --git a/homeassistant/components/dweet/sensor.py b/homeassistant/components/dweet/sensor.py index 55f3c5341a3..937de9b030a 100644 --- a/homeassistant/components/dweet/sensor.py +++ b/homeassistant/components/dweet/sensor.py @@ -8,21 +8,27 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_NAME, CONF_VALUE_TEMPLATE, CONF_UNIT_OF_MEASUREMENT, CONF_DEVICE) + CONF_NAME, + CONF_VALUE_TEMPLATE, + CONF_UNIT_OF_MEASUREMENT, + CONF_DEVICE, +) from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'Dweet.io Sensor' +DEFAULT_NAME = "Dweet.io Sensor" SCAN_INTERVAL = timedelta(minutes=1) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_DEVICE): cv.string, - vol.Required(CONF_VALUE_TEMPLATE): cv.template, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_DEVICE): cv.string, + vol.Required(CONF_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -37,12 +43,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None): value_template.hass = hass try: - content = json.dumps(dweepy.get_latest_dweet_for(device)[0]['content']) + content = json.dumps(dweepy.get_latest_dweet_for(device)[0]["content"]) except dweepy.DweepyError: _LOGGER.error("Device/thing %s could not be found", device) return - if value_template.render_with_possible_json_value(content) == '': + if value_template.render_with_possible_json_value(content) == "": _LOGGER.error("%s was not found", value_template) return @@ -85,9 +91,10 @@ class DweetSensor(Entity): if self.dweet.data is None: self._state = None else: - values = json.dumps(self.dweet.data[0]['content']) + values = json.dumps(self.dweet.data[0]["content"]) self._state = self._value_template.render_with_possible_json_value( - values, None) + values, None + ) class DweetData: diff --git a/homeassistant/components/dyson/__init__.py b/homeassistant/components/dyson/__init__.py index fdba263d4ca..7f247be6bcc 100644 --- a/homeassistant/components/dyson/__init__.py +++ b/homeassistant/components/dyson/__init__.py @@ -4,33 +4,36 @@ import logging import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.const import ( - CONF_DEVICES, CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME) +from homeassistant.const import CONF_DEVICES, CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME from homeassistant.helpers import discovery _LOGGER = logging.getLogger(__name__) -CONF_LANGUAGE = 'language' -CONF_RETRY = 'retry' +CONF_LANGUAGE = "language" +CONF_RETRY = "retry" DEFAULT_TIMEOUT = 5 DEFAULT_RETRY = 10 -DYSON_DEVICES = 'dyson_devices' -DYSON_PLATFORMS = ['sensor', 'fan', 'vacuum', 'climate', 'air_quality'] +DYSON_DEVICES = "dyson_devices" +DYSON_PLATFORMS = ["sensor", "fan", "vacuum", "climate", "air_quality"] -DOMAIN = 'dyson' +DOMAIN = "dyson" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_LANGUAGE): cv.string, - vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, - vol.Optional(CONF_RETRY, default=DEFAULT_RETRY): cv.positive_int, - vol.Optional(CONF_DEVICES, default=[]): - vol.All(cv.ensure_list, [dict]), - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_LANGUAGE): cv.string, + vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, + vol.Optional(CONF_RETRY, default=DEFAULT_RETRY): cv.positive_int, + vol.Optional(CONF_DEVICES, default=[]): vol.All(cv.ensure_list, [dict]), + } + ) + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): @@ -41,9 +44,12 @@ def setup(hass, config): hass.data[DYSON_DEVICES] = [] from libpurecool.dyson import DysonAccount - dyson_account = DysonAccount(config[DOMAIN].get(CONF_USERNAME), - config[DOMAIN].get(CONF_PASSWORD), - config[DOMAIN].get(CONF_LANGUAGE)) + + dyson_account = DysonAccount( + config[DOMAIN].get(CONF_USERNAME), + config[DOMAIN].get(CONF_PASSWORD), + config[DOMAIN].get(CONF_LANGUAGE), + ) logged = dyson_account.login() @@ -59,8 +65,9 @@ def setup(hass, config): if CONF_DEVICES in config[DOMAIN] and config[DOMAIN].get(CONF_DEVICES): configured_devices = config[DOMAIN].get(CONF_DEVICES) for device in configured_devices: - dyson_device = next((d for d in dyson_devices if - d.serial == device["device_id"]), None) + dyson_device = next( + (d for d in dyson_devices if d.serial == device["device_id"]), None + ) if dyson_device: try: connected = dyson_device.connect(device["device_ip"]) @@ -68,20 +75,26 @@ def setup(hass, config): _LOGGER.info("Connected to device %s", dyson_device) hass.data[DYSON_DEVICES].append(dyson_device) else: - _LOGGER.warning("Unable to connect to device %s", - dyson_device) + _LOGGER.warning("Unable to connect to device %s", dyson_device) except OSError as ose: - _LOGGER.error("Unable to connect to device %s: %s", - str(dyson_device.network_device), str(ose)) + _LOGGER.error( + "Unable to connect to device %s: %s", + str(dyson_device.network_device), + str(ose), + ) else: _LOGGER.warning( - "Unable to find device %s in Dyson account", - device["device_id"]) + "Unable to find device %s in Dyson account", device["device_id"] + ) else: # Not yet reliable for device in dyson_devices: - _LOGGER.info("Trying to connect to device %s with timeout=%i " - "and retry=%i", device, timeout, retry) + _LOGGER.info( + "Trying to connect to device %s with timeout=%i " "and retry=%i", + device, + timeout, + retry, + ) connected = device.auto_connect(timeout, retry) if connected: _LOGGER.info("Connected to device %s", device) diff --git a/homeassistant/components/dyson/air_quality.py b/homeassistant/components/dyson/air_quality.py index 238b8b6934d..0276e47ed61 100644 --- a/homeassistant/components/dyson/air_quality.py +++ b/homeassistant/components/dyson/air_quality.py @@ -4,13 +4,13 @@ import logging from homeassistant.components.air_quality import AirQualityEntity, DOMAIN from . import DYSON_DEVICES -ATTRIBUTION = 'Dyson purifier air quality sensor' +ATTRIBUTION = "Dyson purifier air quality sensor" _LOGGER = logging.getLogger(__name__) -DYSON_AIQ_DEVICES = 'dyson_aiq_devices' +DYSON_AIQ_DEVICES = "dyson_aiq_devices" -ATTR_VOC = 'volatile_organic_compounds' +ATTR_VOC = "volatile_organic_compounds" def setup_platform(hass, config, add_entities, discovery_info=None): @@ -25,8 +25,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): # Get Dyson Devices from parent component device_ids = [device.unique_id for device in hass.data[DYSON_AIQ_DEVICES]] for device in hass.data[DYSON_DEVICES]: - if isinstance(device, DysonPureCool) and \ - device.serial not in device_ids: + if isinstance(device, DysonPureCool) and device.serial not in device_ids: hass.data[DYSON_AIQ_DEVICES].append(DysonAirSensor(device)) add_entities(hass.data[DYSON_AIQ_DEVICES]) @@ -43,18 +42,20 @@ class DysonAirSensor(AirQualityEntity): async def async_added_to_hass(self): """Call when entity is added to hass.""" self.hass.async_add_executor_job( - self._device.add_message_listener, self.on_message) + self._device.add_message_listener, self.on_message + ) def on_message(self, message): """Handle new messages which are received from the fan.""" - from libpurecool.dyson_pure_state_v2 import \ - DysonEnvironmentalSensorV2State + from libpurecool.dyson_pure_state_v2 import DysonEnvironmentalSensorV2State - _LOGGER.debug('%s: Message received for %s device: %s', - DOMAIN, self.name, message) - if (self._old_value is None or - self._old_value != self._device.environmental_state) and \ - isinstance(message, DysonEnvironmentalSensorV2State): + _LOGGER.debug( + "%s: Message received for %s device: %s", DOMAIN, self.name, message + ) + if ( + self._old_value is None + or self._old_value != self._device.environmental_state + ) and isinstance(message, DysonEnvironmentalSensorV2State): self._old_value = self._device.environmental_state self.schedule_update_ha_state() @@ -76,10 +77,12 @@ class DysonAirSensor(AirQualityEntity): @property def air_quality_index(self): """Return the Air Quality Index (AQI).""" - return max(self.particulate_matter_2_5, - self.particulate_matter_10, - self.nitrogen_dioxide, - self.volatile_organic_compounds) + return max( + self.particulate_matter_2_5, + self.particulate_matter_10, + self.nitrogen_dioxide, + self.volatile_organic_compounds, + ) @property def particulate_matter_2_5(self): @@ -106,8 +109,7 @@ class DysonAirSensor(AirQualityEntity): def volatile_organic_compounds(self): """Return the VOC (Volatile Organic Compounds) level.""" if self._device.environmental_state: - return int(self._device. - environmental_state.volatile_organic_compounds) + return int(self._device.environmental_state.volatile_organic_compounds) return None @property diff --git a/homeassistant/components/dyson/climate.py b/homeassistant/components/dyson/climate.py index f86579a316a..90c19e9de88 100644 --- a/homeassistant/components/dyson/climate.py +++ b/homeassistant/components/dyson/climate.py @@ -7,9 +7,16 @@ from libpurecool.dyson_pure_hotcool_link import DysonPureHotCoolLink from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( - CURRENT_HVAC_COOL, CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, HVAC_MODE_COOL, - HVAC_MODE_HEAT, SUPPORT_FAN_MODE, FAN_FOCUS, - FAN_DIFFUSE, SUPPORT_TARGET_TEMPERATURE) + CURRENT_HVAC_COOL, + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, + HVAC_MODE_COOL, + HVAC_MODE_HEAT, + SUPPORT_FAN_MODE, + FAN_FOCUS, + FAN_DIFFUSE, + SUPPORT_TARGET_TEMPERATURE, +) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS from . import DYSON_DEVICES @@ -28,9 +35,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # Get Dyson Devices from parent component. add_devices( - [DysonPureHotCoolLinkDevice(device) - for device in hass.data[DYSON_DEVICES] - if isinstance(device, DysonPureHotCoolLink)] + [ + DysonPureHotCoolLinkDevice(device) + for device in hass.data[DYSON_DEVICES] + if isinstance(device, DysonPureHotCoolLink) + ] ) @@ -44,16 +53,14 @@ class DysonPureHotCoolLinkDevice(ClimateDevice): async def async_added_to_hass(self): """Call when entity is added to hass.""" - self.hass.async_add_job( - self._device.add_message_listener, self.on_message) + self.hass.async_add_job(self._device.add_message_listener, self.on_message) def on_message(self, message): """Call when new messages received from the climate.""" if not isinstance(message, DysonPureHotCoolState): return - _LOGGER.debug( - "Message received for climate device %s : %s", self.name, message) + _LOGGER.debug("Message received for climate device %s : %s", self.name, message) self.schedule_update_ha_state() @property @@ -82,8 +89,7 @@ class DysonPureHotCoolLinkDevice(ClimateDevice): if self._device.environmental_state: temperature_kelvin = self._device.environmental_state.temperature if temperature_kelvin != 0: - self._current_temp = float("{0:.1f}".format( - temperature_kelvin - 273)) + self._current_temp = float("{0:.1f}".format(temperature_kelvin - 273)) return self._current_temp @property @@ -154,8 +160,8 @@ class DysonPureHotCoolLinkDevice(ClimateDevice): target_temp = min(self.max_temp, target_temp) target_temp = max(self.min_temp, target_temp) self._device.set_configuration( - heat_target=HeatTarget.celsius(target_temp), - heat_mode=HeatMode.HEAT_ON) + heat_target=HeatTarget.celsius(target_temp), heat_mode=HeatMode.HEAT_ON + ) def set_fan_mode(self, fan_mode): """Set new fan mode.""" diff --git a/homeassistant/components/dyson/fan.py b/homeassistant/components/dyson/fan.py index 65ff093d6d5..0165044b839 100644 --- a/homeassistant/components/dyson/fan.py +++ b/homeassistant/components/dyson/fan.py @@ -9,64 +9,81 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.fan import ( - SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, FanEntity, - SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH) + SUPPORT_OSCILLATE, + SUPPORT_SET_SPEED, + FanEntity, + SPEED_LOW, + SPEED_MEDIUM, + SPEED_HIGH, +) from homeassistant.const import ATTR_ENTITY_ID from . import DYSON_DEVICES _LOGGER = logging.getLogger(__name__) -ATTR_NIGHT_MODE = 'night_mode' -ATTR_AUTO_MODE = 'auto_mode' -ATTR_ANGLE_LOW = 'angle_low' -ATTR_ANGLE_HIGH = 'angle_high' -ATTR_FLOW_DIRECTION_FRONT = 'flow_direction_front' -ATTR_TIMER = 'timer' -ATTR_HEPA_FILTER = 'hepa_filter' -ATTR_CARBON_FILTER = 'carbon_filter' -ATTR_DYSON_SPEED = 'dyson_speed' -ATTR_DYSON_SPEED_LIST = 'dyson_speed_list' +ATTR_NIGHT_MODE = "night_mode" +ATTR_AUTO_MODE = "auto_mode" +ATTR_ANGLE_LOW = "angle_low" +ATTR_ANGLE_HIGH = "angle_high" +ATTR_FLOW_DIRECTION_FRONT = "flow_direction_front" +ATTR_TIMER = "timer" +ATTR_HEPA_FILTER = "hepa_filter" +ATTR_CARBON_FILTER = "carbon_filter" +ATTR_DYSON_SPEED = "dyson_speed" +ATTR_DYSON_SPEED_LIST = "dyson_speed_list" -DYSON_DOMAIN = 'dyson' -DYSON_FAN_DEVICES = 'dyson_fan_devices' +DYSON_DOMAIN = "dyson" +DYSON_FAN_DEVICES = "dyson_fan_devices" -SERVICE_SET_NIGHT_MODE = 'set_night_mode' -SERVICE_SET_AUTO_MODE = 'set_auto_mode' -SERVICE_SET_ANGLE = 'set_angle' -SERVICE_SET_FLOW_DIRECTION_FRONT = 'set_flow_direction_front' -SERVICE_SET_TIMER = 'set_timer' -SERVICE_SET_DYSON_SPEED = 'set_speed' +SERVICE_SET_NIGHT_MODE = "set_night_mode" +SERVICE_SET_AUTO_MODE = "set_auto_mode" +SERVICE_SET_ANGLE = "set_angle" +SERVICE_SET_FLOW_DIRECTION_FRONT = "set_flow_direction_front" +SERVICE_SET_TIMER = "set_timer" +SERVICE_SET_DYSON_SPEED = "set_speed" -DYSON_SET_NIGHT_MODE_SCHEMA = vol.Schema({ - vol.Required(ATTR_ENTITY_ID): cv.entity_id, - vol.Required(ATTR_NIGHT_MODE): cv.boolean, -}) +DYSON_SET_NIGHT_MODE_SCHEMA = vol.Schema( + { + vol.Required(ATTR_ENTITY_ID): cv.entity_id, + vol.Required(ATTR_NIGHT_MODE): cv.boolean, + } +) -SET_AUTO_MODE_SCHEMA = vol.Schema({ - vol.Required(ATTR_ENTITY_ID): cv.entity_id, - vol.Required(ATTR_AUTO_MODE): cv.boolean, -}) +SET_AUTO_MODE_SCHEMA = vol.Schema( + { + vol.Required(ATTR_ENTITY_ID): cv.entity_id, + vol.Required(ATTR_AUTO_MODE): cv.boolean, + } +) -SET_ANGLE_SCHEMA = vol.Schema({ - vol.Required(ATTR_ENTITY_ID): cv.entity_id, - vol.Required(ATTR_ANGLE_LOW): cv.positive_int, - vol.Required(ATTR_ANGLE_HIGH): cv.positive_int -}) +SET_ANGLE_SCHEMA = vol.Schema( + { + vol.Required(ATTR_ENTITY_ID): cv.entity_id, + vol.Required(ATTR_ANGLE_LOW): cv.positive_int, + vol.Required(ATTR_ANGLE_HIGH): cv.positive_int, + } +) -SET_FLOW_DIRECTION_FRONT_SCHEMA = vol.Schema({ - vol.Required(ATTR_ENTITY_ID): cv.entity_id, - vol.Required(ATTR_FLOW_DIRECTION_FRONT): cv.boolean -}) +SET_FLOW_DIRECTION_FRONT_SCHEMA = vol.Schema( + { + vol.Required(ATTR_ENTITY_ID): cv.entity_id, + vol.Required(ATTR_FLOW_DIRECTION_FRONT): cv.boolean, + } +) -SET_TIMER_SCHEMA = vol.Schema({ - vol.Required(ATTR_ENTITY_ID): cv.entity_id, - vol.Required(ATTR_TIMER): cv.positive_int -}) +SET_TIMER_SCHEMA = vol.Schema( + { + vol.Required(ATTR_ENTITY_ID): cv.entity_id, + vol.Required(ATTR_TIMER): cv.positive_int, + } +) -SET_DYSON_SPEED_SCHEMA = vol.Schema({ - vol.Required(ATTR_ENTITY_ID): cv.entity_id, - vol.Required(ATTR_DYSON_SPEED): cv.positive_int -}) +SET_DYSON_SPEED_SCHEMA = vol.Schema( + { + vol.Required(ATTR_ENTITY_ID): cv.entity_id, + vol.Required(ATTR_DYSON_SPEED): cv.positive_int, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -99,11 +116,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None): def service_handle(service): """Handle the Dyson services.""" entity_id = service.data[ATTR_ENTITY_ID] - fan_device = next((fan for fan in hass.data[DYSON_FAN_DEVICES] if - fan.entity_id == entity_id), None) + fan_device = next( + (fan for fan in hass.data[DYSON_FAN_DEVICES] if fan.entity_id == entity_id), + None, + ) if fan_device is None: - _LOGGER.warning("Unable to find Dyson fan device %s", - str(entity_id)) + _LOGGER.warning("Unable to find Dyson fan device %s", str(entity_id)) return if service.service == SERVICE_SET_NIGHT_MODE: @@ -113,12 +131,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None): fan_device.set_auto_mode(service.data[ATTR_AUTO_MODE]) if service.service == SERVICE_SET_ANGLE: - fan_device.set_angle(service.data[ATTR_ANGLE_LOW], - service.data[ATTR_ANGLE_HIGH]) + fan_device.set_angle( + service.data[ATTR_ANGLE_LOW], service.data[ATTR_ANGLE_HIGH] + ) if service.service == SERVICE_SET_FLOW_DIRECTION_FRONT: - fan_device.set_flow_direction_front( - service.data[ATTR_FLOW_DIRECTION_FRONT]) + fan_device.set_flow_direction_front(service.data[ATTR_FLOW_DIRECTION_FRONT]) if service.service == SERVICE_SET_TIMER: fan_device.set_timer(service.data[ATTR_TIMER]) @@ -128,28 +146,40 @@ def setup_platform(hass, config, add_entities, discovery_info=None): # Register dyson service(s) hass.services.register( - DYSON_DOMAIN, SERVICE_SET_NIGHT_MODE, service_handle, - schema=DYSON_SET_NIGHT_MODE_SCHEMA) + DYSON_DOMAIN, + SERVICE_SET_NIGHT_MODE, + service_handle, + schema=DYSON_SET_NIGHT_MODE_SCHEMA, + ) if has_purecool_devices: hass.services.register( - DYSON_DOMAIN, SERVICE_SET_AUTO_MODE, service_handle, - schema=SET_AUTO_MODE_SCHEMA) + DYSON_DOMAIN, + SERVICE_SET_AUTO_MODE, + service_handle, + schema=SET_AUTO_MODE_SCHEMA, + ) hass.services.register( - DYSON_DOMAIN, SERVICE_SET_ANGLE, service_handle, - schema=SET_ANGLE_SCHEMA) + DYSON_DOMAIN, SERVICE_SET_ANGLE, service_handle, schema=SET_ANGLE_SCHEMA + ) hass.services.register( - DYSON_DOMAIN, SERVICE_SET_FLOW_DIRECTION_FRONT, service_handle, - schema=SET_FLOW_DIRECTION_FRONT_SCHEMA) + DYSON_DOMAIN, + SERVICE_SET_FLOW_DIRECTION_FRONT, + service_handle, + schema=SET_FLOW_DIRECTION_FRONT_SCHEMA, + ) hass.services.register( - DYSON_DOMAIN, SERVICE_SET_TIMER, service_handle, - schema=SET_TIMER_SCHEMA) + DYSON_DOMAIN, SERVICE_SET_TIMER, service_handle, schema=SET_TIMER_SCHEMA + ) hass.services.register( - DYSON_DOMAIN, SERVICE_SET_DYSON_SPEED, service_handle, - schema=SET_DYSON_SPEED_SCHEMA) + DYSON_DOMAIN, + SERVICE_SET_DYSON_SPEED, + service_handle, + schema=SET_DYSON_SPEED_SCHEMA, + ) class DysonPureCoolLinkDevice(FanEntity): @@ -163,16 +193,14 @@ class DysonPureCoolLinkDevice(FanEntity): async def async_added_to_hass(self): """Call when entity is added to hass.""" - self.hass.async_add_job( - self._device.add_message_listener, self.on_message) + self.hass.async_add_job(self._device.add_message_listener, self.on_message) def on_message(self, message): """Call when new messages received from the fan.""" from libpurecool.dyson_pure_state import DysonPureCoolState if isinstance(message, DysonPureCoolState): - _LOGGER.debug("Message received for fan device %s: %s", self.name, - message) + _LOGGER.debug("Message received for fan device %s: %s", self.name, message) self.schedule_update_ha_state() @property @@ -194,9 +222,8 @@ class DysonPureCoolLinkDevice(FanEntity): if speed == FanSpeed.FAN_SPEED_AUTO.value: self._device.set_configuration(fan_mode=FanMode.AUTO) else: - fan_speed = FanSpeed('{0:04d}'.format(int(speed))) - self._device.set_configuration( - fan_mode=FanMode.FAN, fan_speed=fan_speed) + fan_speed = FanSpeed("{0:04d}".format(int(speed))) + self._device.set_configuration(fan_mode=FanMode.FAN, fan_speed=fan_speed) def turn_on(self, speed: str = None, **kwargs) -> None: """Turn on the fan.""" @@ -207,9 +234,10 @@ class DysonPureCoolLinkDevice(FanEntity): if speed == FanSpeed.FAN_SPEED_AUTO.value: self._device.set_configuration(fan_mode=FanMode.AUTO) else: - fan_speed = FanSpeed('{0:04d}'.format(int(speed))) + fan_speed = FanSpeed("{0:04d}".format(int(speed))) self._device.set_configuration( - fan_mode=FanMode.FAN, fan_speed=fan_speed) + fan_mode=FanMode.FAN, fan_speed=fan_speed + ) else: # Speed not set, just turn on self._device.set_configuration(fan_mode=FanMode.FAN) @@ -225,15 +253,12 @@ class DysonPureCoolLinkDevice(FanEntity): """Turn on/off oscillating.""" from libpurecool.const import Oscillation - _LOGGER.debug("Turn oscillation %s for device %s", oscillating, - self.name) + _LOGGER.debug("Turn oscillation %s for device %s", oscillating, self.name) if oscillating: - self._device.set_configuration( - oscillation=Oscillation.OSCILLATION_ON) + self._device.set_configuration(oscillation=Oscillation.OSCILLATION_ON) else: - self._device.set_configuration( - oscillation=Oscillation.OSCILLATION_OFF) + self._device.set_configuration(oscillation=Oscillation.OSCILLATION_OFF) @property def oscillating(self): @@ -322,10 +347,7 @@ class DysonPureCoolLinkDevice(FanEntity): @property def device_state_attributes(self) -> dict: """Return optional state attributes.""" - return { - ATTR_NIGHT_MODE: self.night_mode, - ATTR_AUTO_MODE: self.auto_mode - } + return {ATTR_NIGHT_MODE: self.night_mode, ATTR_AUTO_MODE: self.auto_mode} class DysonPureCoolDevice(FanEntity): @@ -338,15 +360,15 @@ class DysonPureCoolDevice(FanEntity): async def async_added_to_hass(self): """Call when entity is added to hass.""" self.hass.async_add_executor_job( - self._device.add_message_listener, self.on_message) + self._device.add_message_listener, self.on_message + ) def on_message(self, message): """Call when new messages received from the fan.""" from libpurecool.dyson_pure_state_v2 import DysonPureCoolV2State if isinstance(message, DysonPureCoolV2State): - _LOGGER.debug("Message received for fan device %s: %s", self.name, - message) + _LOGGER.debug("Message received for fan device %s: %s", self.name, message) self.schedule_update_ha_state() @property @@ -371,6 +393,7 @@ class DysonPureCoolDevice(FanEntity): def set_speed(self, speed: str) -> None: """Set the speed of the fan.""" from libpurecool.const import FanSpeed + if speed == SPEED_LOW: self._device.set_fan_speed(FanSpeed.FAN_SPEED_4) elif speed == SPEED_MEDIUM: @@ -389,13 +412,12 @@ class DysonPureCoolDevice(FanEntity): _LOGGER.debug("Set exact speed for fan %s", self.name) - fan_speed = FanSpeed('{0:04d}'.format(int(speed))) + fan_speed = FanSpeed("{0:04d}".format(int(speed))) self._device.set_fan_speed(fan_speed) def oscillate(self, oscillating: bool) -> None: """Turn on/off oscillating.""" - _LOGGER.debug("Turn oscillation %s for device %s", oscillating, - self.name) + _LOGGER.debug("Turn oscillation %s for device %s", oscillating, self.name) if oscillating: self._device.enable_oscillation() @@ -404,8 +426,7 @@ class DysonPureCoolDevice(FanEntity): def set_night_mode(self, night_mode: bool) -> None: """Turn on/off night mode.""" - _LOGGER.debug("Turn night mode %s for device %s", night_mode, - self.name) + _LOGGER.debug("Turn night mode %s for device %s", night_mode, self.name) if night_mode: self._device.enable_night_mode() @@ -414,8 +435,7 @@ class DysonPureCoolDevice(FanEntity): def set_auto_mode(self, auto_mode: bool) -> None: """Turn auto mode on/off.""" - _LOGGER.debug("Turn auto mode %s for device %s", auto_mode, - self.name) + _LOGGER.debug("Turn auto mode %s for device %s", auto_mode, self.name) if auto_mode: self._device.enable_auto_mode() else: @@ -423,16 +443,21 @@ class DysonPureCoolDevice(FanEntity): def set_angle(self, angle_low: int, angle_high: int) -> None: """Set device angle.""" - _LOGGER.debug("set low %s and high angle %s for device %s", - angle_low, angle_high, self.name) + _LOGGER.debug( + "set low %s and high angle %s for device %s", + angle_low, + angle_high, + self.name, + ) self._device.enable_oscillation(angle_low, angle_high) - def set_flow_direction_front(self, - flow_direction_front: bool) -> None: + def set_flow_direction_front(self, flow_direction_front: bool) -> None: """Set frontal airflow direction.""" - _LOGGER.debug("Set frontal flow direction to %s for device %s", - flow_direction_front, - self.name) + _LOGGER.debug( + "Set frontal flow direction to %s for device %s", + flow_direction_front, + self.name, + ) if flow_direction_front: self._device.enable_frontal_direction() @@ -441,8 +466,7 @@ class DysonPureCoolDevice(FanEntity): def set_timer(self, timer) -> None: """Set timer.""" - _LOGGER.debug("Set timer to %s for device %s", timer, - self.name) + _LOGGER.debug("Set timer to %s for device %s", timer, self.name) if timer == 0: self._device.disable_sleep_timer() @@ -465,17 +489,19 @@ class DysonPureCoolDevice(FanEntity): """Return the current speed.""" from libpurecool.const import FanSpeed - speed_map = {FanSpeed.FAN_SPEED_1.value: SPEED_LOW, - FanSpeed.FAN_SPEED_2.value: SPEED_LOW, - FanSpeed.FAN_SPEED_3.value: SPEED_LOW, - FanSpeed.FAN_SPEED_4.value: SPEED_LOW, - FanSpeed.FAN_SPEED_AUTO.value: SPEED_MEDIUM, - FanSpeed.FAN_SPEED_5.value: SPEED_MEDIUM, - FanSpeed.FAN_SPEED_6.value: SPEED_MEDIUM, - FanSpeed.FAN_SPEED_7.value: SPEED_MEDIUM, - FanSpeed.FAN_SPEED_8.value: SPEED_HIGH, - FanSpeed.FAN_SPEED_9.value: SPEED_HIGH, - FanSpeed.FAN_SPEED_10.value: SPEED_HIGH} + speed_map = { + FanSpeed.FAN_SPEED_1.value: SPEED_LOW, + FanSpeed.FAN_SPEED_2.value: SPEED_LOW, + FanSpeed.FAN_SPEED_3.value: SPEED_LOW, + FanSpeed.FAN_SPEED_4.value: SPEED_LOW, + FanSpeed.FAN_SPEED_AUTO.value: SPEED_MEDIUM, + FanSpeed.FAN_SPEED_5.value: SPEED_MEDIUM, + FanSpeed.FAN_SPEED_6.value: SPEED_MEDIUM, + FanSpeed.FAN_SPEED_7.value: SPEED_MEDIUM, + FanSpeed.FAN_SPEED_8.value: SPEED_HIGH, + FanSpeed.FAN_SPEED_9.value: SPEED_HIGH, + FanSpeed.FAN_SPEED_10.value: SPEED_HIGH, + } return speed_map[self._device.state.speed] @@ -512,7 +538,7 @@ class DysonPureCoolDevice(FanEntity): @property def flow_direction_front(self): """Return frontal flow direction.""" - return self._device.state.front_direction == 'ON' + return self._device.state.front_direction == "ON" @property def timer(self): @@ -538,6 +564,7 @@ class DysonPureCoolDevice(FanEntity): def dyson_speed_list(self) -> list: """Get the list of available dyson speeds.""" from libpurecool.const import FanSpeed + return [ int(FanSpeed.FAN_SPEED_1.value), int(FanSpeed.FAN_SPEED_2.value), @@ -559,8 +586,7 @@ class DysonPureCoolDevice(FanEntity): @property def supported_features(self) -> int: """Flag supported features.""" - return SUPPORT_OSCILLATE | \ - SUPPORT_SET_SPEED + return SUPPORT_OSCILLATE | SUPPORT_SET_SPEED @property def device_state_attributes(self) -> dict: @@ -575,5 +601,5 @@ class DysonPureCoolDevice(FanEntity): ATTR_HEPA_FILTER: self.hepa_filter, ATTR_CARBON_FILTER: self.carbon_filter, ATTR_DYSON_SPEED: self.dyson_speed, - ATTR_DYSON_SPEED_LIST: self.dyson_speed_list + ATTR_DYSON_SPEED_LIST: self.dyson_speed_list, } diff --git a/homeassistant/components/dyson/sensor.py b/homeassistant/components/dyson/sensor.py index 9cd1c915c57..f89823b143f 100644 --- a/homeassistant/components/dyson/sensor.py +++ b/homeassistant/components/dyson/sensor.py @@ -6,21 +6,21 @@ from homeassistant.helpers.entity import Entity from . import DYSON_DEVICES SENSOR_UNITS = { - 'air_quality': None, - 'dust': None, - 'filter_life': 'hours', - 'humidity': '%', + "air_quality": None, + "dust": None, + "filter_life": "hours", + "humidity": "%", } SENSOR_ICONS = { - 'air_quality': 'mdi:fan', - 'dust': 'mdi:cloud', - 'filter_life': 'mdi:filter-outline', - 'humidity': 'mdi:water-percent', - 'temperature': 'mdi:thermometer', + "air_quality": "mdi:fan", + "dust": "mdi:cloud", + "filter_life": "mdi:filter-outline", + "humidity": "mdi:water-percent", + "temperature": "mdi:thermometer", } -DYSON_SENSOR_DEVICES = 'dyson_sensor_devices' +DYSON_SENSOR_DEVICES = "dyson_sensor_devices" _LOGGER = logging.getLogger(__name__) @@ -38,13 +38,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None): devices = hass.data[DYSON_SENSOR_DEVICES] # Get Dyson Devices from parent component - device_ids = [device.unique_id for device in - hass.data[DYSON_SENSOR_DEVICES]] + device_ids = [device.unique_id for device in hass.data[DYSON_SENSOR_DEVICES]] for device in hass.data[DYSON_DEVICES]: if isinstance(device, DysonPureCool): - if '{}-{}'.format(device.serial, 'temperature') not in device_ids: + if "{}-{}".format(device.serial, "temperature") not in device_ids: devices.append(DysonTemperatureSensor(device, unit)) - if '{}-{}'.format(device.serial, 'humidity') not in device_ids: + if "{}-{}".format(device.serial, "humidity") not in device_ids: devices.append(DysonHumiditySensor(device)) elif isinstance(device, DysonPureCoolLink): devices.append(DysonFilterLifeSensor(device)) @@ -68,14 +67,14 @@ class DysonSensor(Entity): async def async_added_to_hass(self): """Call when entity is added to hass.""" self.hass.async_add_executor_job( - self._device.add_message_listener, self.on_message) + self._device.add_message_listener, self.on_message + ) def on_message(self, message): """Handle new messages which are received from the fan.""" # Prevent refreshing if not needed if self._old_value is None or self._old_value != self.state: - _LOGGER.debug("Message received for %s device: %s", self.name, - message) + _LOGGER.debug("Message received for %s device: %s", self.name, message) self._old_value = self.state self.schedule_update_ha_state() @@ -102,7 +101,7 @@ class DysonSensor(Entity): @property def unique_id(self): """Return the sensor's unique id.""" - return '{}-{}'.format(self._device.serial, self._sensor_type) + return "{}-{}".format(self._device.serial, self._sensor_type) class DysonFilterLifeSensor(DysonSensor): @@ -110,7 +109,7 @@ class DysonFilterLifeSensor(DysonSensor): def __init__(self, device): """Create a new Dyson Filter Life sensor.""" - super().__init__(device, 'filter_life') + super().__init__(device, "filter_life") self._name = "{} Filter Life".format(self._device.name) @property @@ -126,7 +125,7 @@ class DysonDustSensor(DysonSensor): def __init__(self, device): """Create a new Dyson Dust sensor.""" - super().__init__(device, 'dust') + super().__init__(device, "dust") self._name = "{} Dust".format(self._device.name) @property @@ -142,7 +141,7 @@ class DysonHumiditySensor(DysonSensor): def __init__(self, device): """Create a new Dyson Humidity sensor.""" - super().__init__(device, 'humidity') + super().__init__(device, "humidity") self._name = "{} Humidity".format(self._device.name) @property @@ -160,7 +159,7 @@ class DysonTemperatureSensor(DysonSensor): def __init__(self, device, unit): """Create a new Dyson Temperature sensor.""" - super().__init__(device, 'temperature') + super().__init__(device, "temperature") self._name = "{} Temperature".format(self._device.name) self._unit = unit @@ -187,7 +186,7 @@ class DysonAirQualitySensor(DysonSensor): def __init__(self, device): """Create a new Dyson Air Quality sensor.""" - super().__init__(device, 'air_quality') + super().__init__(device, "air_quality") self._name = "{} AQI".format(self._device.name) @property diff --git a/homeassistant/components/dyson/vacuum.py b/homeassistant/components/dyson/vacuum.py index 0bb2368f690..cef5f0c9961 100644 --- a/homeassistant/components/dyson/vacuum.py +++ b/homeassistant/components/dyson/vacuum.py @@ -2,24 +2,38 @@ import logging from homeassistant.components.vacuum import ( - SUPPORT_BATTERY, SUPPORT_FAN_SPEED, SUPPORT_PAUSE, SUPPORT_RETURN_HOME, - SUPPORT_STATUS, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, - VacuumDevice) + SUPPORT_BATTERY, + SUPPORT_FAN_SPEED, + SUPPORT_PAUSE, + SUPPORT_RETURN_HOME, + SUPPORT_STATUS, + SUPPORT_STOP, + SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, + VacuumDevice, +) from homeassistant.helpers.icon import icon_for_battery_level from . import DYSON_DEVICES _LOGGER = logging.getLogger(__name__) -ATTR_CLEAN_ID = 'clean_id' -ATTR_FULL_CLEAN_TYPE = 'full_clean_type' -ATTR_POSITION = 'position' +ATTR_CLEAN_ID = "clean_id" +ATTR_FULL_CLEAN_TYPE = "full_clean_type" +ATTR_POSITION = "position" DYSON_360_EYE_DEVICES = "dyson_360_eye_devices" -SUPPORT_DYSON = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PAUSE | \ - SUPPORT_RETURN_HOME | SUPPORT_FAN_SPEED | SUPPORT_STATUS | \ - SUPPORT_BATTERY | SUPPORT_STOP +SUPPORT_DYSON = ( + SUPPORT_TURN_ON + | SUPPORT_TURN_OFF + | SUPPORT_PAUSE + | SUPPORT_RETURN_HOME + | SUPPORT_FAN_SPEED + | SUPPORT_STATUS + | SUPPORT_BATTERY + | SUPPORT_STOP +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -31,8 +45,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): hass.data[DYSON_360_EYE_DEVICES] = [] # Get Dyson Devices from parent component - for device in [d for d in hass.data[DYSON_DEVICES] if - isinstance(d, Dyson360Eye)]: + for device in [d for d in hass.data[DYSON_DEVICES] if isinstance(d, Dyson360Eye)]: dyson_entity = Dyson360EyeDevice(device) hass.data[DYSON_360_EYE_DEVICES].append(dyson_entity) @@ -50,8 +63,7 @@ class Dyson360EyeDevice(VacuumDevice): async def async_added_to_hass(self): """Call when entity is added to hass.""" - self.hass.async_add_job( - self._device.add_message_listener, self.on_message) + self.hass.async_add_job(self._device.add_message_listener, self.on_message) def on_message(self, message): """Handle a new messages that was received from the vacuum.""" @@ -75,6 +87,7 @@ class Dyson360EyeDevice(VacuumDevice): def status(self): """Return the status of the vacuum cleaner.""" from libpurecool.const import Dyson360EyeMode + dyson_labels = { Dyson360EyeMode.INACTIVE_CHARGING: "Stopped - Charging", Dyson360EyeMode.INACTIVE_CHARGED: "Stopped - Charged", @@ -83,13 +96,11 @@ class Dyson360EyeDevice(VacuumDevice): Dyson360EyeMode.FULL_CLEAN_ABORTED: "Returning home", Dyson360EyeMode.FULL_CLEAN_INITIATED: "Start cleaning", Dyson360EyeMode.FAULT_USER_RECOVERABLE: "Error - device blocked", - Dyson360EyeMode.FAULT_REPLACE_ON_DOCK: - "Error - Replace device on dock", + Dyson360EyeMode.FAULT_REPLACE_ON_DOCK: "Error - Replace device on dock", Dyson360EyeMode.FULL_CLEAN_FINISHED: "Finished", - Dyson360EyeMode.FULL_CLEAN_NEEDS_CHARGE: "Need charging" + Dyson360EyeMode.FULL_CLEAN_NEEDS_CHARGE: "Need charging", } - return dyson_labels.get( - self._device.state.state, self._device.state.state) + return dyson_labels.get(self._device.state.state, self._device.state.state) @property def battery_level(self): @@ -100,10 +111,8 @@ class Dyson360EyeDevice(VacuumDevice): def fan_speed(self): """Return the fan speed of the vacuum cleaner.""" from libpurecool.const import PowerMode - speed_labels = { - PowerMode.MAX: "Max", - PowerMode.QUIET: "Quiet" - } + + speed_labels = {PowerMode.MAX: "Max", PowerMode.QUIET: "Quiet"} return speed_labels[self._device.state.power_mode] @property @@ -114,9 +123,7 @@ class Dyson360EyeDevice(VacuumDevice): @property def device_state_attributes(self): """Return the specific state attributes of this vacuum cleaner.""" - return { - ATTR_POSITION: str(self._device.state.position) - } + return {ATTR_POSITION: str(self._device.state.position)} @property def is_on(self) -> bool: @@ -126,7 +133,7 @@ class Dyson360EyeDevice(VacuumDevice): return self._device.state.state in [ Dyson360EyeMode.FULL_CLEAN_INITIATED, Dyson360EyeMode.FULL_CLEAN_ABORTED, - Dyson360EyeMode.FULL_CLEAN_RUNNING + Dyson360EyeMode.FULL_CLEAN_RUNNING, ] @property @@ -144,10 +151,10 @@ class Dyson360EyeDevice(VacuumDevice): """Return the battery icon for the vacuum cleaner.""" from libpurecool.const import Dyson360EyeMode - charging = self._device.state.state in [ - Dyson360EyeMode.INACTIVE_CHARGING] + charging = self._device.state.state in [Dyson360EyeMode.INACTIVE_CHARGING] return icon_for_battery_level( - battery_level=self.battery_level, charging=charging) + battery_level=self.battery_level, charging=charging + ) def turn_on(self, **kwargs): """Turn the vacuum on.""" @@ -174,10 +181,7 @@ class Dyson360EyeDevice(VacuumDevice): from libpurecool.const import PowerMode _LOGGER.debug("Set fan speed %s on device %s", fan_speed, self.name) - power_modes = { - "Quiet": PowerMode.QUIET, - "Max": PowerMode.MAX - } + power_modes = {"Quiet": PowerMode.QUIET, "Max": PowerMode.MAX} self._device.set_power_mode(power_modes[fan_speed]) def start_pause(self, **kwargs): @@ -187,8 +191,10 @@ class Dyson360EyeDevice(VacuumDevice): if self._device.state.state in [Dyson360EyeMode.FULL_CLEAN_PAUSED]: _LOGGER.debug("Resume device %s", self.name) self._device.resume() - elif self._device.state.state in [Dyson360EyeMode.INACTIVE_CHARGED, - Dyson360EyeMode.INACTIVE_CHARGING]: + elif self._device.state.state in [ + Dyson360EyeMode.INACTIVE_CHARGED, + Dyson360EyeMode.INACTIVE_CHARGING, + ]: _LOGGER.debug("Start device %s", self.name) self._device.start() else: diff --git a/homeassistant/components/ebox/sensor.py b/homeassistant/components/ebox/sensor.py index aaf3384d55f..1482ab34c68 100644 --- a/homeassistant/components/ebox/sensor.py +++ b/homeassistant/components/ebox/sensor.py @@ -14,8 +14,11 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_USERNAME, CONF_PASSWORD, - CONF_NAME, CONF_MONITORED_VARIABLES) + CONF_USERNAME, + CONF_PASSWORD, + CONF_NAME, + CONF_MONITORED_VARIABLES, +) from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from homeassistant.exceptions import PlatformNotReady @@ -23,47 +26,46 @@ from homeassistant.exceptions import PlatformNotReady _LOGGER = logging.getLogger(__name__) -GIGABITS = 'Gb' # type: str -PRICE = 'CAD' # type: str -DAYS = 'days' # type: str -PERCENT = '%' # type: str +GIGABITS = "Gb" # type: str +PRICE = "CAD" # type: str +DAYS = "days" # type: str +PERCENT = "%" # type: str -DEFAULT_NAME = 'EBox' +DEFAULT_NAME = "EBox" REQUESTS_TIMEOUT = 15 SCAN_INTERVAL = timedelta(minutes=15) MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15) SENSOR_TYPES = { - 'usage': ['Usage', PERCENT, 'mdi:percent'], - 'balance': ['Balance', PRICE, 'mdi:square-inc-cash'], - 'limit': ['Data limit', GIGABITS, 'mdi:download'], - 'days_left': ['Days left', DAYS, 'mdi:calendar-today'], - 'before_offpeak_download': - ['Download before offpeak', GIGABITS, 'mdi:download'], - 'before_offpeak_upload': - ['Upload before offpeak', GIGABITS, 'mdi:upload'], - 'before_offpeak_total': - ['Total before offpeak', GIGABITS, 'mdi:download'], - 'offpeak_download': ['Offpeak download', GIGABITS, 'mdi:download'], - 'offpeak_upload': ['Offpeak Upload', GIGABITS, 'mdi:upload'], - 'offpeak_total': ['Offpeak Total', GIGABITS, 'mdi:download'], - 'download': ['Download', GIGABITS, 'mdi:download'], - 'upload': ['Upload', GIGABITS, 'mdi:upload'], - 'total': ['Total', GIGABITS, 'mdi:download'], + "usage": ["Usage", PERCENT, "mdi:percent"], + "balance": ["Balance", PRICE, "mdi:square-inc-cash"], + "limit": ["Data limit", GIGABITS, "mdi:download"], + "days_left": ["Days left", DAYS, "mdi:calendar-today"], + "before_offpeak_download": ["Download before offpeak", GIGABITS, "mdi:download"], + "before_offpeak_upload": ["Upload before offpeak", GIGABITS, "mdi:upload"], + "before_offpeak_total": ["Total before offpeak", GIGABITS, "mdi:download"], + "offpeak_download": ["Offpeak download", GIGABITS, "mdi:download"], + "offpeak_upload": ["Offpeak Upload", GIGABITS, "mdi:upload"], + "offpeak_total": ["Offpeak Total", GIGABITS, "mdi:download"], + "download": ["Download", GIGABITS, "mdi:download"], + "upload": ["Upload", GIGABITS, "mdi:upload"], + "total": ["Total", GIGABITS, "mdi:download"], } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_MONITORED_VARIABLES): - vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_MONITORED_VARIABLES): vol.All( + cv.ensure_list, [vol.In(SENSOR_TYPES)] + ), + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + } +) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the EBox sensor.""" username = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) @@ -74,6 +76,7 @@ async def async_setup_platform(hass, config, async_add_entities, name = config.get(CONF_NAME) from pyebox.client import PyEboxError + try: await ebox_data.async_update() except PyEboxError as exp: @@ -103,7 +106,7 @@ class EBoxSensor(Entity): @property def name(self): """Return the name of the sensor.""" - return '{} {}'.format(self.client_name, self._name) + return "{} {}".format(self.client_name, self._name) @property def state(self): @@ -133,14 +136,15 @@ class EBoxData: def __init__(self, username, password, httpsession): """Initialize the data object.""" from pyebox import EboxClient - self.client = EboxClient(username, password, - REQUESTS_TIMEOUT, httpsession) + + self.client = EboxClient(username, password, REQUESTS_TIMEOUT, httpsession) self.data = {} @Throttle(MIN_TIME_BETWEEN_UPDATES) async def async_update(self): """Get the latest data from Ebox.""" from pyebox.client import PyEboxError + try: await self.client.fetch_data() except PyEboxError as exp: diff --git a/homeassistant/components/ebusd/__init__.py b/homeassistant/components/ebusd/__init__.py index c3e72bfd764..e11de446e40 100644 --- a/homeassistant/components/ebusd/__init__.py +++ b/homeassistant/components/ebusd/__init__.py @@ -6,20 +6,24 @@ import socket import voluptuous as vol from homeassistant.const import ( - CONF_NAME, CONF_HOST, CONF_PORT, CONF_MONITORED_CONDITIONS) + CONF_NAME, + CONF_HOST, + CONF_PORT, + CONF_MONITORED_CONDITIONS, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.discovery import load_platform from homeassistant.util import Throttle -from .const import (DOMAIN, SENSOR_TYPES) +from .const import DOMAIN, SENSOR_TYPES _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'ebusd' +DEFAULT_NAME = "ebusd" DEFAULT_PORT = 8888 -CONF_CIRCUIT = 'circuit' +CONF_CIRCUIT = "circuit" CACHE_TTL = 900 -SERVICE_EBUSD_WRITE = 'ebusd_write' +SERVICE_EBUSD_WRITE = "ebusd_write" MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=15) @@ -29,24 +33,27 @@ def verify_ebusd_config(config): circuit = config[CONF_CIRCUIT] for condition in config[CONF_MONITORED_CONDITIONS]: if condition not in SENSOR_TYPES[circuit]: - raise vol.Invalid( - "Condition '" + condition + "' not in '" + circuit + "'.") + raise vol.Invalid("Condition '" + condition + "' not in '" + circuit + "'.") return config -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema( - vol.All({ - vol.Required(CONF_CIRCUIT): cv.string, - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_MONITORED_CONDITIONS, default=[]): - cv.ensure_list, - }, - verify_ebusd_config) - ) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + vol.All( + { + vol.Required(CONF_CIRCUIT): cv.string, + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_MONITORED_CONDITIONS, default=[]): cv.ensure_list, + }, + verify_ebusd_config, + ) + ) + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): @@ -55,24 +62,23 @@ def setup(hass, config): name = conf[CONF_NAME] circuit = conf[CONF_CIRCUIT] monitored_conditions = conf.get(CONF_MONITORED_CONDITIONS) - server_address = ( - conf.get(CONF_HOST), conf.get(CONF_PORT)) + server_address = (conf.get(CONF_HOST), conf.get(CONF_PORT)) try: _LOGGER.debug("Ebusd integration setup started") import ebusdpy + ebusdpy.init(server_address) hass.data[DOMAIN] = EbusdData(server_address, circuit) sensor_config = { CONF_MONITORED_CONDITIONS: monitored_conditions, - 'client_name': name, - 'sensor_types': SENSOR_TYPES[circuit] + "client_name": name, + "sensor_types": SENSOR_TYPES[circuit], } - load_platform(hass, 'sensor', DOMAIN, sensor_config, config) + load_platform(hass, "sensor", DOMAIN, sensor_config, config) - hass.services.register( - DOMAIN, SERVICE_EBUSD_WRITE, hass.data[DOMAIN].write) + hass.services.register(DOMAIN, SERVICE_EBUSD_WRITE, hass.data[DOMAIN].write) _LOGGER.debug("Ebusd integration setup completed") return True @@ -97,9 +103,10 @@ class EbusdData: try: _LOGGER.debug("Opening socket to ebusd %s", name) command_result = ebusdpy.read( - self._address, self._circuit, name, stype, CACHE_TTL) + self._address, self._circuit, name, stype, CACHE_TTL + ) if command_result is not None: - if 'ERR:' in command_result: + if "ERR:" in command_result: _LOGGER.warning(command_result) else: self.value[name] = command_result @@ -110,15 +117,15 @@ class EbusdData: def write(self, call): """Call write methon on ebusd.""" import ebusdpy - name = call.data.get('name') - value = call.data.get('value') + + name = call.data.get("name") + value = call.data.get("value") try: _LOGGER.debug("Opening socket to ebusd %s", name) - command_result = ebusdpy.write( - self._address, self._circuit, name, value) + command_result = ebusdpy.write(self._address, self._circuit, name, value) if command_result is not None: - if 'done' not in command_result: - _LOGGER.warning('Write command failed: %s', name) + if "done" not in command_result: + _LOGGER.warning("Write command failed: %s", name) except RuntimeError as err: _LOGGER.error(err) diff --git a/homeassistant/components/ebusd/const.py b/homeassistant/components/ebusd/const.py index 3821bd8ce15..7587d0cd42d 100644 --- a/homeassistant/components/ebusd/const.py +++ b/homeassistant/components/ebusd/const.py @@ -1,102 +1,104 @@ """Constants for ebus component.""" from homeassistant.const import ENERGY_KILO_WATT_HOUR -DOMAIN = 'ebusd' +DOMAIN = "ebusd" # SensorTypes: # 0='decimal', 1='time-schedule', 2='switch', 3='string', 4='value;status' SENSOR_TYPES = { - '700': { - 'ActualFlowTemperatureDesired': - ['Hc1ActualFlowTempDesired', '°C', 'mdi:thermometer', 0], - 'MaxFlowTemperatureDesired': - ['Hc1MaxFlowTempDesired', '°C', 'mdi:thermometer', 0], - 'MinFlowTemperatureDesired': - ['Hc1MinFlowTempDesired', '°C', 'mdi:thermometer', 0], - 'PumpStatus': - ['Hc1PumpStatus', None, 'mdi:toggle-switch', 2], - 'HCSummerTemperatureLimit': - ['Hc1SummerTempLimit', '°C', 'mdi:weather-sunny', 0], - 'HolidayTemperature': - ['HolidayTemp', '°C', 'mdi:thermometer', 0], - 'HWTemperatureDesired': - ['HwcTempDesired', '°C', 'mdi:thermometer', 0], - 'HWTimerMonday': - ['hwcTimer.Monday', None, 'mdi:timer', 1], - 'HWTimerTuesday': - ['hwcTimer.Tuesday', None, 'mdi:timer', 1], - 'HWTimerWednesday': - ['hwcTimer.Wednesday', None, 'mdi:timer', 1], - 'HWTimerThursday': - ['hwcTimer.Thursday', None, 'mdi:timer', 1], - 'HWTimerFriday': - ['hwcTimer.Friday', None, 'mdi:timer', 1], - 'HWTimerSaturday': - ['hwcTimer.Saturday', None, 'mdi:timer', 1], - 'HWTimerSunday': - ['hwcTimer.Sunday', None, 'mdi:timer', 1], - 'WaterPressure': - ['WaterPressure', 'bar', 'mdi:water-pump', 0], - 'Zone1RoomZoneMapping': - ['z1RoomZoneMapping', None, 'mdi:label', 0], - 'Zone1NightTemperature': - ['z1NightTemp', '°C', 'mdi:weather-night', 0], - 'Zone1DayTemperature': - ['z1DayTemp', '°C', 'mdi:weather-sunny', 0], - 'Zone1HolidayTemperature': - ['z1HolidayTemp', '°C', 'mdi:thermometer', 0], - 'Zone1RoomTemperature': - ['z1RoomTemp', '°C', 'mdi:thermometer', 0], - 'Zone1ActualRoomTemperatureDesired': - ['z1ActualRoomTempDesired', '°C', 'mdi:thermometer', 0], - 'Zone1TimerMonday': - ['z1Timer.Monday', None, 'mdi:timer', 1], - 'Zone1TimerTuesday': - ['z1Timer.Tuesday', None, 'mdi:timer', 1], - 'Zone1TimerWednesday': - ['z1Timer.Wednesday', None, 'mdi:timer', 1], - 'Zone1TimerThursday': - ['z1Timer.Thursday', None, 'mdi:timer', 1], - 'Zone1TimerFriday': - ['z1Timer.Friday', None, 'mdi:timer', 1], - 'Zone1TimerSaturday': - ['z1Timer.Saturday', None, 'mdi:timer', 1], - 'Zone1TimerSunday': - ['z1Timer.Sunday', None, 'mdi:timer', 1], - 'Zone1OperativeMode': - ['z1OpMode', None, 'mdi:math-compass', 3], - 'ContinuosHeating': - ['ContinuosHeating', '°C', 'mdi:weather-snowy', 0], - 'PowerEnergyConsumptionLastMonth': - ['PrEnergySumHcLastMonth', ENERGY_KILO_WATT_HOUR, 'mdi:flash', 0], - 'PowerEnergyConsumptionThisMonth': - ['PrEnergySumHcThisMonth', ENERGY_KILO_WATT_HOUR, 'mdi:flash', 0] + "700": { + "ActualFlowTemperatureDesired": [ + "Hc1ActualFlowTempDesired", + "°C", + "mdi:thermometer", + 0, + ], + "MaxFlowTemperatureDesired": [ + "Hc1MaxFlowTempDesired", + "°C", + "mdi:thermometer", + 0, + ], + "MinFlowTemperatureDesired": [ + "Hc1MinFlowTempDesired", + "°C", + "mdi:thermometer", + 0, + ], + "PumpStatus": ["Hc1PumpStatus", None, "mdi:toggle-switch", 2], + "HCSummerTemperatureLimit": [ + "Hc1SummerTempLimit", + "°C", + "mdi:weather-sunny", + 0, + ], + "HolidayTemperature": ["HolidayTemp", "°C", "mdi:thermometer", 0], + "HWTemperatureDesired": ["HwcTempDesired", "°C", "mdi:thermometer", 0], + "HWTimerMonday": ["hwcTimer.Monday", None, "mdi:timer", 1], + "HWTimerTuesday": ["hwcTimer.Tuesday", None, "mdi:timer", 1], + "HWTimerWednesday": ["hwcTimer.Wednesday", None, "mdi:timer", 1], + "HWTimerThursday": ["hwcTimer.Thursday", None, "mdi:timer", 1], + "HWTimerFriday": ["hwcTimer.Friday", None, "mdi:timer", 1], + "HWTimerSaturday": ["hwcTimer.Saturday", None, "mdi:timer", 1], + "HWTimerSunday": ["hwcTimer.Sunday", None, "mdi:timer", 1], + "WaterPressure": ["WaterPressure", "bar", "mdi:water-pump", 0], + "Zone1RoomZoneMapping": ["z1RoomZoneMapping", None, "mdi:label", 0], + "Zone1NightTemperature": ["z1NightTemp", "°C", "mdi:weather-night", 0], + "Zone1DayTemperature": ["z1DayTemp", "°C", "mdi:weather-sunny", 0], + "Zone1HolidayTemperature": ["z1HolidayTemp", "°C", "mdi:thermometer", 0], + "Zone1RoomTemperature": ["z1RoomTemp", "°C", "mdi:thermometer", 0], + "Zone1ActualRoomTemperatureDesired": [ + "z1ActualRoomTempDesired", + "°C", + "mdi:thermometer", + 0, + ], + "Zone1TimerMonday": ["z1Timer.Monday", None, "mdi:timer", 1], + "Zone1TimerTuesday": ["z1Timer.Tuesday", None, "mdi:timer", 1], + "Zone1TimerWednesday": ["z1Timer.Wednesday", None, "mdi:timer", 1], + "Zone1TimerThursday": ["z1Timer.Thursday", None, "mdi:timer", 1], + "Zone1TimerFriday": ["z1Timer.Friday", None, "mdi:timer", 1], + "Zone1TimerSaturday": ["z1Timer.Saturday", None, "mdi:timer", 1], + "Zone1TimerSunday": ["z1Timer.Sunday", None, "mdi:timer", 1], + "Zone1OperativeMode": ["z1OpMode", None, "mdi:math-compass", 3], + "ContinuosHeating": ["ContinuosHeating", "°C", "mdi:weather-snowy", 0], + "PowerEnergyConsumptionLastMonth": [ + "PrEnergySumHcLastMonth", + ENERGY_KILO_WATT_HOUR, + "mdi:flash", + 0, + ], + "PowerEnergyConsumptionThisMonth": [ + "PrEnergySumHcThisMonth", + ENERGY_KILO_WATT_HOUR, + "mdi:flash", + 0, + ], }, - 'ehp': { - 'HWTemperature': - ['HwcTemp', '°C', 'mdi:thermometer', 4], - 'OutsideTemp': - ['OutsideTemp', '°C', 'mdi:thermometer', 4] + "ehp": { + "HWTemperature": ["HwcTemp", "°C", "mdi:thermometer", 4], + "OutsideTemp": ["OutsideTemp", "°C", "mdi:thermometer", 4], + }, + "bai": { + "ReturnTemperature": ["ReturnTemp", "°C", "mdi:thermometer", 4], + "CentralHeatingPump": ["WP", None, "mdi:toggle-switch", 2], + "HeatingSwitch": ["HeatingSwitch", None, "mdi:toggle-switch", 2], + "FlowTemperature": ["FlowTemp", "°C", "mdi:thermometer", 4], + "Flame": ["Flame", None, "mdi:toggle-switch", 2], + "PowerEnergyConsumptionHeatingCircuit": [ + "PrEnergySumHc1", + ENERGY_KILO_WATT_HOUR, + "mdi:flash", + 0, + ], + "PowerEnergyConsumptionHotWaterCircuit": [ + "PrEnergySumHwc1", + ENERGY_KILO_WATT_HOUR, + "mdi:flash", + 0, + ], + "RoomThermostat": ["DCRoomthermostat", None, "mdi:toggle-switch", 2], + "HeatingPartLoad": ["PartloadHcKW", ENERGY_KILO_WATT_HOUR, "mdi:flash", 0], }, - 'bai': { - 'ReturnTemperature': - ['ReturnTemp', '°C', 'mdi:thermometer', 4], - 'CentralHeatingPump': - ['WP', None, 'mdi:toggle-switch', 2], - 'HeatingSwitch': - ['HeatingSwitch', None, 'mdi:toggle-switch', 2], - 'FlowTemperature': - ['FlowTemp', '°C', 'mdi:thermometer', 4], - 'Flame': - ['Flame', None, 'mdi:toggle-switch', 2], - 'PowerEnergyConsumptionHeatingCircuit': - ['PrEnergySumHc1', ENERGY_KILO_WATT_HOUR, 'mdi:flash', 0], - 'PowerEnergyConsumptionHotWaterCircuit': - ['PrEnergySumHwc1', ENERGY_KILO_WATT_HOUR, 'mdi:flash', 0], - 'RoomThermostat': - ['DCRoomthermostat', None, 'mdi:toggle-switch', 2], - 'HeatingPartLoad': - ['PartloadHcKW', ENERGY_KILO_WATT_HOUR, 'mdi:flash', 0] - } } diff --git a/homeassistant/components/ebusd/sensor.py b/homeassistant/components/ebusd/sensor.py index f73bb09b509..37b7d2dd060 100644 --- a/homeassistant/components/ebusd/sensor.py +++ b/homeassistant/components/ebusd/sensor.py @@ -6,12 +6,12 @@ from homeassistant.helpers.entity import Entity from .const import DOMAIN -TIME_FRAME1_BEGIN = 'time_frame1_begin' -TIME_FRAME1_END = 'time_frame1_end' -TIME_FRAME2_BEGIN = 'time_frame2_begin' -TIME_FRAME2_END = 'time_frame2_end' -TIME_FRAME3_BEGIN = 'time_frame3_begin' -TIME_FRAME3_END = 'time_frame3_end' +TIME_FRAME1_BEGIN = "time_frame1_begin" +TIME_FRAME1_END = "time_frame1_end" +TIME_FRAME2_BEGIN = "time_frame2_begin" +TIME_FRAME2_END = "time_frame2_end" +TIME_FRAME3_BEGIN = "time_frame3_begin" +TIME_FRAME3_END = "time_frame3_end" _LOGGER = logging.getLogger(__name__) @@ -19,13 +19,14 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Ebus sensor.""" ebusd_api = hass.data[DOMAIN] - monitored_conditions = discovery_info['monitored_conditions'] - name = discovery_info['client_name'] + monitored_conditions = discovery_info["monitored_conditions"] + name = discovery_info["client_name"] dev = [] for condition in monitored_conditions: - dev.append(EbusdSensor( - ebusd_api, discovery_info['sensor_types'][condition], name)) + dev.append( + EbusdSensor(ebusd_api, discovery_info["sensor_types"][condition], name) + ) add_entities(dev, True) @@ -43,7 +44,7 @@ class EbusdSensor(Entity): @property def name(self): """Return the name of the sensor.""" - return '{} {}'.format(self._client_name, self._name) + return "{} {}".format(self._client_name, self._name) @property def state(self): @@ -60,17 +61,17 @@ class EbusdSensor(Entity): TIME_FRAME2_BEGIN: None, TIME_FRAME2_END: None, TIME_FRAME3_BEGIN: None, - TIME_FRAME3_END: None + TIME_FRAME3_END: None, } - time_frame = self._state.split(';') + time_frame = self._state.split(";") for index, item in enumerate(sorted(schedule.items())): if index < len(time_frame): - parsed = datetime.datetime.strptime( - time_frame[index], '%H:%M') + parsed = datetime.datetime.strptime(time_frame[index], "%H:%M") parsed = parsed.replace( datetime.datetime.now().year, datetime.datetime.now().month, - datetime.datetime.now().day) + datetime.datetime.now().day, + ) schedule[item[0]] = parsed.isoformat() return schedule return None diff --git a/homeassistant/components/ecoal_boiler/__init__.py b/homeassistant/components/ecoal_boiler/__init__.py index 796324d9337..40769c9990a 100644 --- a/homeassistant/components/ecoal_boiler/__init__.py +++ b/homeassistant/components/ecoal_boiler/__init__.py @@ -3,16 +3,21 @@ import logging import voluptuous as vol -from homeassistant.const import (CONF_HOST, CONF_PASSWORD, CONF_USERNAME, - CONF_MONITORED_CONDITIONS, CONF_SENSORS, - CONF_SWITCHES) +from homeassistant.const import ( + CONF_HOST, + CONF_PASSWORD, + CONF_USERNAME, + CONF_MONITORED_CONDITIONS, + CONF_SENSORS, + CONF_SWITCHES, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.discovery import load_platform _LOGGER = logging.getLogger(__name__) DOMAIN = "ecoal_boiler" -DATA_ECOAL_BOILER = 'data_' + DOMAIN +DATA_ECOAL_BOILER = "data_" + DOMAIN DEFAULT_USERNAME = "admin" DEFAULT_PASSWORD = "admin" @@ -29,37 +34,48 @@ AVAILABLE_PUMPS = { # Available temp sensor ids with assigned HA names # Available as sensors AVAILABLE_SENSORS = { - "outdoor_temp": 'Outdoor temperature', - "indoor_temp": 'Indoor temperature', - "indoor2_temp": 'Indoor temperature 2', - "domestic_hot_water_temp": 'Domestic hot water temperature', - "target_domestic_hot_water_temp": 'Target hot water temperature', - "feedwater_in_temp": 'Feedwater input temperature', - "feedwater_out_temp": 'Feedwater output temperature', - "target_feedwater_temp": 'Target feedwater temperature', - "fuel_feeder_temp": 'Fuel feeder temperature', - "exhaust_temp": 'Exhaust temperature', + "outdoor_temp": "Outdoor temperature", + "indoor_temp": "Indoor temperature", + "indoor2_temp": "Indoor temperature 2", + "domestic_hot_water_temp": "Domestic hot water temperature", + "target_domestic_hot_water_temp": "Target hot water temperature", + "feedwater_in_temp": "Feedwater input temperature", + "feedwater_out_temp": "Feedwater output temperature", + "target_feedwater_temp": "Target feedwater temperature", + "fuel_feeder_temp": "Fuel feeder temperature", + "exhaust_temp": "Exhaust temperature", } -SWITCH_SCHEMA = vol.Schema({ - vol.Optional(CONF_MONITORED_CONDITIONS, default=list(AVAILABLE_PUMPS)): - vol.All(cv.ensure_list, [vol.In(AVAILABLE_PUMPS)]) -}) +SWITCH_SCHEMA = vol.Schema( + { + vol.Optional(CONF_MONITORED_CONDITIONS, default=list(AVAILABLE_PUMPS)): vol.All( + cv.ensure_list, [vol.In(AVAILABLE_PUMPS)] + ) + } +) -SENSOR_SCHEMA = vol.Schema({ - vol.Optional(CONF_MONITORED_CONDITIONS, default=list(AVAILABLE_SENSORS)): - vol.All(cv.ensure_list, [vol.In(AVAILABLE_SENSORS)]) -}) +SENSOR_SCHEMA = vol.Schema( + { + vol.Optional( + CONF_MONITORED_CONDITIONS, default=list(AVAILABLE_SENSORS) + ): vol.All(cv.ensure_list, [vol.In(AVAILABLE_SENSORS)]) + } +) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string, - vol.Optional(CONF_SENSORS, default={}): SENSOR_SCHEMA, - vol.Optional(CONF_SWITCHES, default={}): SWITCH_SCHEMA, - vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string, + vol.Optional(CONF_SENSORS, default={}): SENSOR_SCHEMA, + vol.Optional(CONF_SWITCHES, default={}): SWITCH_SCHEMA, + vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass, hass_config): @@ -74,16 +90,18 @@ def setup(hass, hass_config): ecoal_contr = ECoalController(host, username, passwd) if ecoal_contr.version is None: # Wrong credentials nor network config - _LOGGER.error("Unable to read controller status from %s@%s" - " (wrong host/credentials)", username, host, ) + _LOGGER.error( + "Unable to read controller status from %s@%s" " (wrong host/credentials)", + username, + host, + ) return False - _LOGGER.debug("Detected controller version: %r @%s", - ecoal_contr.version, host, ) + _LOGGER.debug("Detected controller version: %r @%s", ecoal_contr.version, host) hass.data[DATA_ECOAL_BOILER] = ecoal_contr # Setup switches switches = conf[CONF_SWITCHES][CONF_MONITORED_CONDITIONS] - load_platform(hass, 'switch', DOMAIN, switches, hass_config) + load_platform(hass, "switch", DOMAIN, switches, hass_config) # Setup temp sensors sensors = conf[CONF_SENSORS][CONF_MONITORED_CONDITIONS] - load_platform(hass, 'sensor', DOMAIN, sensors, hass_config) + load_platform(hass, "sensor", DOMAIN, sensors, hass_config) return True diff --git a/homeassistant/components/ecobee/__init__.py b/homeassistant/components/ecobee/__init__.py index 5f9ae6a919d..e69884af59f 100644 --- a/homeassistant/components/ecobee/__init__.py +++ b/homeassistant/components/ecobee/__init__.py @@ -14,30 +14,36 @@ from homeassistant.util.json import save_json _CONFIGURING = {} _LOGGER = logging.getLogger(__name__) -CONF_HOLD_TEMP = 'hold_temp' +CONF_HOLD_TEMP = "hold_temp" -DOMAIN = 'ecobee' +DOMAIN = "ecobee" -ECOBEE_CONFIG_FILE = 'ecobee.conf' +ECOBEE_CONFIG_FILE = "ecobee.conf" MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=180) NETWORK = None -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Optional(CONF_API_KEY): cv.string, - vol.Optional(CONF_HOLD_TEMP, default=False): cv.boolean, - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Optional(CONF_API_KEY): cv.string, + vol.Optional(CONF_HOLD_TEMP, default=False): cv.boolean, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) def request_configuration(network, hass, config): """Request configuration steps from the user.""" configurator = hass.components.configurator - if 'ecobee' in _CONFIGURING: + if "ecobee" in _CONFIGURING: configurator.notify_errors( - _CONFIGURING['ecobee'], "Failed to register, please try again.") + _CONFIGURING["ecobee"], "Failed to register, please try again." + ) return @@ -47,13 +53,15 @@ def request_configuration(network, hass, config): network.update() setup_ecobee(hass, network, config) - _CONFIGURING['ecobee'] = configurator.request_config( - "Ecobee", ecobee_configuration_callback, + _CONFIGURING["ecobee"] = configurator.request_config( + "Ecobee", + ecobee_configuration_callback, description=( - 'Please authorize this app at https://www.ecobee.com/consumer' - 'portal/index.html with pin code: ' + network.pin), + "Please authorize this app at https://www.ecobee.com/consumer" + "portal/index.html with pin code: " + network.pin + ), description_image="/static/images/config_ecobee_thermostat.png", - submit_caption="I have authorized the app." + submit_caption="I have authorized the app.", ) @@ -64,17 +72,16 @@ def setup_ecobee(hass, network, config): request_configuration(network, hass, config) return - if 'ecobee' in _CONFIGURING: + if "ecobee" in _CONFIGURING: configurator = hass.components.configurator - configurator.request_done(_CONFIGURING.pop('ecobee')) + configurator.request_done(_CONFIGURING.pop("ecobee")) hold_temp = config[DOMAIN].get(CONF_HOLD_TEMP) - discovery.load_platform( - hass, 'climate', DOMAIN, {'hold_temp': hold_temp}, config) - discovery.load_platform(hass, 'sensor', DOMAIN, {}, config) - discovery.load_platform(hass, 'binary_sensor', DOMAIN, {}, config) - discovery.load_platform(hass, 'weather', DOMAIN, {}, config) + discovery.load_platform(hass, "climate", DOMAIN, {"hold_temp": hold_temp}, config) + discovery.load_platform(hass, "sensor", DOMAIN, {}, config) + discovery.load_platform(hass, "binary_sensor", DOMAIN, {}, config) + discovery.load_platform(hass, "weather", DOMAIN, {}, config) class EcobeeData: @@ -83,6 +90,7 @@ class EcobeeData: def __init__(self, config_file): """Init the Ecobee data object.""" from pyecobee import Ecobee + self.ecobee = Ecobee(config_file) @Throttle(MIN_TIME_BETWEEN_UPDATES) @@ -100,7 +108,7 @@ def setup(hass, config): """ global NETWORK - if 'ecobee' in _CONFIGURING: + if "ecobee" in _CONFIGURING: return # Create ecobee.conf if it doesn't exist diff --git a/homeassistant/components/ecobee/binary_sensor.py b/homeassistant/components/ecobee/binary_sensor.py index 0989b9ded97..a3cd49ff458 100644 --- a/homeassistant/components/ecobee/binary_sensor.py +++ b/homeassistant/components/ecobee/binary_sensor.py @@ -2,7 +2,7 @@ from homeassistant.components import ecobee from homeassistant.components.binary_sensor import BinarySensorDevice -ECOBEE_CONFIG_FILE = 'ecobee.conf' +ECOBEE_CONFIG_FILE = "ecobee.conf" def setup_platform(hass, config, add_entities, discovery_info=None): @@ -13,11 +13,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): dev = list() for index in range(len(data.ecobee.thermostats)): for sensor in data.ecobee.get_remote_sensors(index): - for item in sensor['capability']: - if item['type'] != 'occupancy': + for item in sensor["capability"]: + if item["type"] != "occupancy": continue - dev.append(EcobeeBinarySensor(sensor['name'], index)) + dev.append(EcobeeBinarySensor(sensor["name"], index)) add_entities(dev, True) @@ -27,11 +27,11 @@ class EcobeeBinarySensor(BinarySensorDevice): def __init__(self, sensor_name, sensor_index): """Initialize the Ecobee sensor.""" - self._name = sensor_name + ' Occupancy' + self._name = sensor_name + " Occupancy" self.sensor_name = sensor_name self.index = sensor_index self._state = None - self._device_class = 'occupancy' + self._device_class = "occupancy" @property def name(self): @@ -41,7 +41,7 @@ class EcobeeBinarySensor(BinarySensorDevice): @property def is_on(self): """Return the status of the sensor.""" - return self._state == 'true' + return self._state == "true" @property def device_class(self): @@ -53,7 +53,6 @@ class EcobeeBinarySensor(BinarySensorDevice): data = ecobee.NETWORK data.update() for sensor in data.ecobee.get_remote_sensors(self.index): - for item in sensor['capability']: - if (item['type'] == 'occupancy' and - self.sensor_name == sensor['name']): - self._state = item['value'] + for item in sensor["capability"]: + if item["type"] == "occupancy" and self.sensor_name == sensor["name"]: + self._state = item["value"] diff --git a/homeassistant/components/ecobee/climate.py b/homeassistant/components/ecobee/climate.py index e8c19f5cd69..6520a3aadba 100644 --- a/homeassistant/components/ecobee/climate.py +++ b/homeassistant/components/ecobee/climate.py @@ -8,40 +8,61 @@ import voluptuous as vol from homeassistant.components import ecobee from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( - DOMAIN, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_AUTO, HVAC_MODE_OFF, - ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_HIGH, SUPPORT_TARGET_TEMPERATURE, - SUPPORT_AUX_HEAT, SUPPORT_TARGET_TEMPERATURE_RANGE, SUPPORT_FAN_MODE, - PRESET_AWAY, FAN_AUTO, FAN_ON, CURRENT_HVAC_IDLE, CURRENT_HVAC_HEAT, - CURRENT_HVAC_COOL, SUPPORT_PRESET_MODE, PRESET_NONE, CURRENT_HVAC_FAN, + DOMAIN, + HVAC_MODE_COOL, + HVAC_MODE_HEAT, + HVAC_MODE_AUTO, + HVAC_MODE_OFF, + ATTR_TARGET_TEMP_LOW, + ATTR_TARGET_TEMP_HIGH, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_AUX_HEAT, + SUPPORT_TARGET_TEMPERATURE_RANGE, + SUPPORT_FAN_MODE, + PRESET_AWAY, + FAN_AUTO, + FAN_ON, + CURRENT_HVAC_IDLE, + CURRENT_HVAC_HEAT, + CURRENT_HVAC_COOL, + SUPPORT_PRESET_MODE, + PRESET_NONE, + CURRENT_HVAC_FAN, CURRENT_HVAC_DRY, ) from homeassistant.const import ( - ATTR_ENTITY_ID, STATE_ON, ATTR_TEMPERATURE, TEMP_FAHRENHEIT) + ATTR_ENTITY_ID, + STATE_ON, + ATTR_TEMPERATURE, + TEMP_FAHRENHEIT, +) import homeassistant.helpers.config_validation as cv _CONFIGURING = {} _LOGGER = logging.getLogger(__name__) -ATTR_FAN_MIN_ON_TIME = 'fan_min_on_time' -ATTR_RESUME_ALL = 'resume_all' +ATTR_FAN_MIN_ON_TIME = "fan_min_on_time" +ATTR_RESUME_ALL = "resume_all" DEFAULT_RESUME_ALL = False -PRESET_TEMPERATURE = 'temp' -PRESET_VACATION = 'vacation' -PRESET_HOLD_NEXT_TRANSITION = 'next_transition' -PRESET_HOLD_INDEFINITE = 'indefinite' -AWAY_MODE = 'awayMode' -PRESET_HOME = 'home' -PRESET_SLEEP = 'sleep' +PRESET_TEMPERATURE = "temp" +PRESET_VACATION = "vacation" +PRESET_HOLD_NEXT_TRANSITION = "next_transition" +PRESET_HOLD_INDEFINITE = "indefinite" +AWAY_MODE = "awayMode" +PRESET_HOME = "home" +PRESET_SLEEP = "sleep" # Order matters, because for reverse mapping we don't want to map HEAT to AUX -ECOBEE_HVAC_TO_HASS = collections.OrderedDict([ - ('heat', HVAC_MODE_HEAT), - ('cool', HVAC_MODE_COOL), - ('auto', HVAC_MODE_AUTO), - ('off', HVAC_MODE_OFF), - ('auxHeatOnly', HVAC_MODE_HEAT), -]) +ECOBEE_HVAC_TO_HASS = collections.OrderedDict( + [ + ("heat", HVAC_MODE_HEAT), + ("cool", HVAC_MODE_COOL), + ("auto", HVAC_MODE_AUTO), + ("off", HVAC_MODE_OFF), + ("auxHeatOnly", HVAC_MODE_HEAT), + ] +) ECOBEE_HVAC_ACTION_TO_HASS = { # Map to None if we do not know how to represent. @@ -63,8 +84,8 @@ ECOBEE_HVAC_ACTION_TO_HASS = { } PRESET_TO_ECOBEE_HOLD = { - PRESET_HOLD_NEXT_TRANSITION: 'nextTransition', - PRESET_HOLD_INDEFINITE: 'indefinite', + PRESET_HOLD_NEXT_TRANSITION: "nextTransition", + PRESET_HOLD_INDEFINITE: "indefinite", } PRESET_MODES = [ @@ -74,25 +95,33 @@ PRESET_MODES = [ PRESET_HOME, PRESET_SLEEP, PRESET_HOLD_NEXT_TRANSITION, - PRESET_HOLD_INDEFINITE + PRESET_HOLD_INDEFINITE, ] -SERVICE_SET_FAN_MIN_ON_TIME = 'ecobee_set_fan_min_on_time' -SERVICE_RESUME_PROGRAM = 'ecobee_resume_program' +SERVICE_SET_FAN_MIN_ON_TIME = "ecobee_set_fan_min_on_time" +SERVICE_RESUME_PROGRAM = "ecobee_resume_program" -SET_FAN_MIN_ON_TIME_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, - vol.Required(ATTR_FAN_MIN_ON_TIME): vol.Coerce(int), -}) +SET_FAN_MIN_ON_TIME_SCHEMA = vol.Schema( + { + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Required(ATTR_FAN_MIN_ON_TIME): vol.Coerce(int), + } +) -RESUME_PROGRAM_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, - vol.Optional(ATTR_RESUME_ALL, default=DEFAULT_RESUME_ALL): cv.boolean, -}) +RESUME_PROGRAM_SCHEMA = vol.Schema( + { + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_RESUME_ALL, default=DEFAULT_RESUME_ALL): cv.boolean, + } +) -SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE | - SUPPORT_AUX_HEAT | SUPPORT_TARGET_TEMPERATURE_RANGE | - SUPPORT_FAN_MODE) +SUPPORT_FLAGS = ( + SUPPORT_TARGET_TEMPERATURE + | SUPPORT_PRESET_MODE + | SUPPORT_AUX_HEAT + | SUPPORT_TARGET_TEMPERATURE_RANGE + | SUPPORT_FAN_MODE +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -100,12 +129,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if discovery_info is None: return data = ecobee.NETWORK - hold_temp = discovery_info['hold_temp'] + hold_temp = discovery_info["hold_temp"] _LOGGER.info( - "Loading ecobee thermostat component with hold_temp set to %s", - hold_temp) - devices = [Thermostat(data, index, hold_temp) - for index in range(len(data.ecobee.thermostats))] + "Loading ecobee thermostat component with hold_temp set to %s", hold_temp + ) + devices = [ + Thermostat(data, index, hold_temp) + for index in range(len(data.ecobee.thermostats)) + ] add_entities(devices) def fan_min_on_time_set_service(service): @@ -114,8 +145,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): fan_min_on_time = service.data[ATTR_FAN_MIN_ON_TIME] if entity_id: - target_thermostats = [device for device in devices - if device.entity_id in entity_id] + target_thermostats = [ + device for device in devices if device.entity_id in entity_id + ] else: target_thermostats = devices @@ -130,8 +162,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): resume_all = service.data.get(ATTR_RESUME_ALL) if entity_id: - target_thermostats = [device for device in devices - if device.entity_id in entity_id] + target_thermostats = [ + device for device in devices if device.entity_id in entity_id + ] else: target_thermostats = devices @@ -141,12 +174,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None): thermostat.schedule_update_ha_state(True) hass.services.register( - DOMAIN, SERVICE_SET_FAN_MIN_ON_TIME, fan_min_on_time_set_service, - schema=SET_FAN_MIN_ON_TIME_SCHEMA) + DOMAIN, + SERVICE_SET_FAN_MIN_ON_TIME, + fan_min_on_time_set_service, + schema=SET_FAN_MIN_ON_TIME_SCHEMA, + ) hass.services.register( - DOMAIN, SERVICE_RESUME_PROGRAM, resume_program_set_service, - schema=RESUME_PROGRAM_SCHEMA) + DOMAIN, + SERVICE_RESUME_PROGRAM, + resume_program_set_service, + schema=RESUME_PROGRAM_SCHEMA, + ) class Thermostat(ClimateDevice): @@ -156,17 +195,16 @@ class Thermostat(ClimateDevice): """Initialize the thermostat.""" self.data = data self.thermostat_index = thermostat_index - self.thermostat = self.data.ecobee.get_thermostat( - self.thermostat_index) - self._name = self.thermostat['name'] + self.thermostat = self.data.ecobee.get_thermostat(self.thermostat_index) + self._name = self.thermostat["name"] self.hold_temp = hold_temp self.vacation = None self._climate_list = self.climate_list self._operation_list = [] - if self.thermostat['settings']['heatStages']: + if self.thermostat["settings"]["heatStages"]: self._operation_list.append(HVAC_MODE_HEAT) - if self.thermostat['settings']['coolStages']: + if self.thermostat["settings"]["coolStages"]: self._operation_list.append(HVAC_MODE_COOL) if len(self._operation_list) == 2: self._operation_list.insert(0, HVAC_MODE_AUTO) @@ -183,8 +221,7 @@ class Thermostat(ClimateDevice): else: self.data.update() - self.thermostat = self.data.ecobee.get_thermostat( - self.thermostat_index) + self.thermostat = self.data.ecobee.get_thermostat(self.thermostat_index) @property def supported_features(self): @@ -194,7 +231,7 @@ class Thermostat(ClimateDevice): @property def name(self): """Return the name of the Ecobee Thermostat.""" - return self.thermostat['name'] + return self.thermostat["name"] @property def temperature_unit(self): @@ -204,20 +241,20 @@ class Thermostat(ClimateDevice): @property def current_temperature(self): """Return the current temperature.""" - return self.thermostat['runtime']['actualTemperature'] / 10.0 + return self.thermostat["runtime"]["actualTemperature"] / 10.0 @property def target_temperature_low(self): """Return the lower bound temperature we try to reach.""" if self.hvac_mode == HVAC_MODE_AUTO: - return self.thermostat['runtime']['desiredHeat'] / 10.0 + return self.thermostat["runtime"]["desiredHeat"] / 10.0 return None @property def target_temperature_high(self): """Return the upper bound temperature we try to reach.""" if self.hvac_mode == HVAC_MODE_AUTO: - return self.thermostat['runtime']['desiredCool'] / 10.0 + return self.thermostat["runtime"]["desiredCool"] / 10.0 return None @property @@ -226,22 +263,22 @@ class Thermostat(ClimateDevice): if self.hvac_mode == HVAC_MODE_AUTO: return None if self.hvac_mode == HVAC_MODE_HEAT: - return self.thermostat['runtime']['desiredHeat'] / 10.0 + return self.thermostat["runtime"]["desiredHeat"] / 10.0 if self.hvac_mode == HVAC_MODE_COOL: - return self.thermostat['runtime']['desiredCool'] / 10.0 + return self.thermostat["runtime"]["desiredCool"] / 10.0 return None @property def fan(self): """Return the current fan status.""" - if 'fan' in self.thermostat['equipmentStatus']: + if "fan" in self.thermostat["equipmentStatus"]: return STATE_ON return HVAC_MODE_OFF @property def fan_mode(self): """Return the fan setting.""" - return self.thermostat['runtime']['desiredFanMode'] + return self.thermostat["runtime"]["desiredFanMode"] @property def fan_modes(self): @@ -251,29 +288,28 @@ class Thermostat(ClimateDevice): @property def preset_mode(self): """Return current preset mode.""" - events = self.thermostat['events'] + events = self.thermostat["events"] for event in events: - if not event['running']: + if not event["running"]: continue - if event['type'] == 'hold': - if event['holdClimateRef'] == 'away': - if int(event['endDate'][0:4]) - \ - int(event['startDate'][0:4]) <= 1: + if event["type"] == "hold": + if event["holdClimateRef"] == "away": + if int(event["endDate"][0:4]) - int(event["startDate"][0:4]) <= 1: # A temporary hold from away climate is a hold return PRESET_AWAY # A permanent hold from away climate return PRESET_AWAY - if event['holdClimateRef'] != "": + if event["holdClimateRef"] != "": # Any other hold based on climate - return event['holdClimateRef'] + return event["holdClimateRef"] # Any hold not based on a climate is a temp hold return PRESET_TEMPERATURE - if event['type'].startswith('auto'): + if event["type"].startswith("auto"): # All auto modes are treated as holds - return event['type'][4:].lower() - if event['type'] == 'vacation': - self.vacation = event['name'] + return event["type"][4:].lower() + if event["type"] == "vacation": + self.vacation = event["name"] return PRESET_VACATION return None @@ -281,7 +317,7 @@ class Thermostat(ClimateDevice): @property def hvac_mode(self): """Return current operation.""" - return ECOBEE_HVAC_TO_HASS[self.thermostat['settings']['hvacMode']] + return ECOBEE_HVAC_TO_HASS[self.thermostat["settings"]["hvacMode"]] @property def hvac_modes(self): @@ -291,15 +327,15 @@ class Thermostat(ClimateDevice): @property def climate_mode(self): """Return current mode, as the user-visible name.""" - cur = self.thermostat['program']['currentClimateRef'] - climates = self.thermostat['program']['climates'] - current = list(filter(lambda x: x['climateRef'] == cur, climates)) - return current[0]['name'] + cur = self.thermostat["program"]["currentClimateRef"] + climates = self.thermostat["program"]["climates"] + current = list(filter(lambda x: x["climateRef"] == cur, climates)) + return current[0]["name"] @property def current_humidity(self) -> Optional[int]: """Return the current humidity.""" - return self.thermostat['runtime']['actualHumidity'] + return self.thermostat["runtime"]["actualHumidity"] @property def hvac_action(self): @@ -311,17 +347,21 @@ class Thermostat(ClimateDevice): We are unable to map all actions to HA equivalents. """ - if self.thermostat['equipmentStatus'] == "": + if self.thermostat["equipmentStatus"] == "": return CURRENT_HVAC_IDLE actions = [ - ECOBEE_HVAC_ACTION_TO_HASS[status] for status in - self.thermostat['equipmentStatus'].split(",") + ECOBEE_HVAC_ACTION_TO_HASS[status] + for status in self.thermostat["equipmentStatus"].split(",") if ECOBEE_HVAC_ACTION_TO_HASS[status] is not None ] - for action in (CURRENT_HVAC_HEAT, CURRENT_HVAC_COOL, - CURRENT_HVAC_DRY, CURRENT_HVAC_FAN): + for action in ( + CURRENT_HVAC_HEAT, + CURRENT_HVAC_COOL, + CURRENT_HVAC_DRY, + CURRENT_HVAC_FAN, + ): if action in actions: return action @@ -330,19 +370,19 @@ class Thermostat(ClimateDevice): @property def device_state_attributes(self): """Return device specific state attributes.""" - status = self.thermostat['equipmentStatus'] + status = self.thermostat["equipmentStatus"] return { "fan": self.fan, "climate_mode": self.climate_mode, "equipment_running": status, "climate_list": self.climate_list, - "fan_min_on_time": self.thermostat['settings']['fanMinOnTime'] + "fan_min_on_time": self.thermostat["settings"]["fanMinOnTime"], } @property def is_aux_heat(self): """Return true if aux heater.""" - return 'auxHeat' in self.thermostat['equipmentStatus'] + return "auxHeat" in self.thermostat["equipmentStatus"] def set_preset_mode(self, preset_mode): """Activate a preset.""" @@ -353,28 +393,30 @@ class Thermostat(ClimateDevice): # If we are currently in vacation mode, cancel it. if self.preset_mode == PRESET_VACATION: - self.data.ecobee.delete_vacation( - self.thermostat_index, self.vacation) + self.data.ecobee.delete_vacation(self.thermostat_index, self.vacation) if preset_mode == PRESET_AWAY: - self.data.ecobee.set_climate_hold(self.thermostat_index, 'away', - 'indefinite') + self.data.ecobee.set_climate_hold( + self.thermostat_index, "away", "indefinite" + ) elif preset_mode == PRESET_TEMPERATURE: self.set_temp_hold(self.current_temperature) - elif preset_mode in ( - PRESET_HOLD_NEXT_TRANSITION, PRESET_HOLD_INDEFINITE): + elif preset_mode in (PRESET_HOLD_NEXT_TRANSITION, PRESET_HOLD_INDEFINITE): self.data.ecobee.set_climate_hold( - self.thermostat_index, PRESET_TO_ECOBEE_HOLD[preset_mode], - self.hold_preference()) + self.thermostat_index, + PRESET_TO_ECOBEE_HOLD[preset_mode], + self.hold_preference(), + ) elif preset_mode == PRESET_NONE: self.data.ecobee.resume_program(self.thermostat_index) else: self.data.ecobee.set_climate_hold( - self.thermostat_index, preset_mode, self.hold_preference()) + self.thermostat_index, preset_mode, self.hold_preference() + ) @property def preset_modes(self): @@ -386,38 +428,45 @@ class Thermostat(ClimateDevice): if cool_temp is not None: cool_temp_setpoint = cool_temp else: - cool_temp_setpoint = ( - self.thermostat['runtime']['desiredCool'] / 10.0) + cool_temp_setpoint = self.thermostat["runtime"]["desiredCool"] / 10.0 if heat_temp is not None: heat_temp_setpoint = heat_temp else: - heat_temp_setpoint = ( - self.thermostat['runtime']['desiredCool'] / 10.0) + heat_temp_setpoint = self.thermostat["runtime"]["desiredCool"] / 10.0 - self.data.ecobee.set_hold_temp(self.thermostat_index, - cool_temp_setpoint, heat_temp_setpoint, - self.hold_preference()) - _LOGGER.debug("Setting ecobee hold_temp to: heat=%s, is=%s, " - "cool=%s, is=%s", heat_temp, - isinstance(heat_temp, (int, float)), cool_temp, - isinstance(cool_temp, (int, float))) + self.data.ecobee.set_hold_temp( + self.thermostat_index, + cool_temp_setpoint, + heat_temp_setpoint, + self.hold_preference(), + ) + _LOGGER.debug( + "Setting ecobee hold_temp to: heat=%s, is=%s, " "cool=%s, is=%s", + heat_temp, + isinstance(heat_temp, (int, float)), + cool_temp, + isinstance(cool_temp, (int, float)), + ) self.update_without_throttle = True def set_fan_mode(self, fan_mode): """Set the fan mode. Valid values are "on" or "auto".""" - if fan_mode.lower() != STATE_ON and \ - fan_mode.lower() != HVAC_MODE_AUTO: + if fan_mode.lower() != STATE_ON and fan_mode.lower() != HVAC_MODE_AUTO: error = "Invalid fan_mode value: Valid values are 'on' or 'auto'" _LOGGER.error(error) return - cool_temp = self.thermostat['runtime']['desiredCool'] / 10.0 - heat_temp = self.thermostat['runtime']['desiredHeat'] / 10.0 - self.data.ecobee.set_fan_mode(self.thermostat_index, fan_mode, - cool_temp, heat_temp, - self.hold_preference()) + cool_temp = self.thermostat["runtime"]["desiredCool"] / 10.0 + heat_temp = self.thermostat["runtime"]["desiredHeat"] / 10.0 + self.data.ecobee.set_fan_mode( + self.thermostat_index, + fan_mode, + cool_temp, + heat_temp, + self.hold_preference(), + ) _LOGGER.info("Setting fan mode to: %s", fan_mode) @@ -432,12 +481,11 @@ class Thermostat(ClimateDevice): heatCoolMinDelta property. https://www.ecobee.com/home/developer/api/examples/ex5.shtml """ - if self.hvac_mode == HVAC_MODE_HEAT or \ - self.hvac_mode == HVAC_MODE_COOL: + if self.hvac_mode == HVAC_MODE_HEAT or self.hvac_mode == HVAC_MODE_COOL: heat_temp = temp cool_temp = temp else: - delta = self.thermostat['settings']['heatCoolMinDelta'] / 10 + delta = self.thermostat["settings"]["heatCoolMinDelta"] / 10 heat_temp = temp - delta cool_temp = temp + delta self.set_auto_temp_hold(heat_temp, cool_temp) @@ -448,14 +496,14 @@ class Thermostat(ClimateDevice): high_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH) temp = kwargs.get(ATTR_TEMPERATURE) - if self.hvac_mode == HVAC_MODE_AUTO and \ - (low_temp is not None or high_temp is not None): + if self.hvac_mode == HVAC_MODE_AUTO and ( + low_temp is not None or high_temp is not None + ): self.set_auto_temp_hold(low_temp, high_temp) elif temp is not None: self.set_temp_hold(temp) else: - _LOGGER.error( - "Missing valid arguments for set_temperature in %s", kwargs) + _LOGGER.error("Missing valid arguments for set_temperature in %s", kwargs) def set_humidity(self, humidity): """Set the humidity level.""" @@ -463,8 +511,9 @@ class Thermostat(ClimateDevice): def set_hvac_mode(self, hvac_mode): """Set HVAC mode (auto, auxHeatOnly, cool, heat, off).""" - ecobee_value = next((k for k, v in ECOBEE_HVAC_TO_HASS.items() - if v == hvac_mode), None) + ecobee_value = next( + (k for k, v in ECOBEE_HVAC_TO_HASS.items() if v == hvac_mode), None + ) if ecobee_value is None: _LOGGER.error("Invalid mode for set_hvac_mode: %s", hvac_mode) return @@ -473,30 +522,30 @@ class Thermostat(ClimateDevice): def set_fan_min_on_time(self, fan_min_on_time): """Set the minimum fan on time.""" - self.data.ecobee.set_fan_min_on_time( - self.thermostat_index, fan_min_on_time) + self.data.ecobee.set_fan_min_on_time(self.thermostat_index, fan_min_on_time) self.update_without_throttle = True def resume_program(self, resume_all): """Resume the thermostat schedule program.""" self.data.ecobee.resume_program( - self.thermostat_index, 'true' if resume_all else 'false') + self.thermostat_index, "true" if resume_all else "false" + ) self.update_without_throttle = True def hold_preference(self): """Return user preference setting for hold time.""" # Values returned from thermostat are 'useEndTime4hour', # 'useEndTime2hour', 'nextTransition', 'indefinite', 'askMe' - default = self.thermostat['settings']['holdAction'] - if default == 'nextTransition': + default = self.thermostat["settings"]["holdAction"] + if default == "nextTransition": return default # add further conditions if other hold durations should be # supported; note that this should not include 'indefinite' # as an indefinite away hold is interpreted as away_mode - return 'nextTransition' + return "nextTransition" @property def climate_list(self): """Return the list of climates currently available.""" - climates = self.thermostat['program']['climates'] - return list(map((lambda x: x['name']), climates)) + climates = self.thermostat["program"]["climates"] + return list(map((lambda x: x["name"]), climates)) diff --git a/homeassistant/components/ecobee/notify.py b/homeassistant/components/ecobee/notify.py index d6e4e8f0c63..bb6861a1492 100644 --- a/homeassistant/components/ecobee/notify.py +++ b/homeassistant/components/ecobee/notify.py @@ -5,16 +5,15 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components import ecobee -from homeassistant.components.notify import ( - BaseNotificationService, PLATFORM_SCHEMA) +from homeassistant.components.notify import BaseNotificationService, PLATFORM_SCHEMA _LOGGER = logging.getLogger(__name__) -CONF_INDEX = 'index' +CONF_INDEX = "index" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_INDEX, default=0): cv.positive_int, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Optional(CONF_INDEX, default=0): cv.positive_int} +) def get_service(hass, config, discovery_info=None): diff --git a/homeassistant/components/ecobee/sensor.py b/homeassistant/components/ecobee/sensor.py index 436903a645f..d21f937dd20 100644 --- a/homeassistant/components/ecobee/sensor.py +++ b/homeassistant/components/ecobee/sensor.py @@ -1,14 +1,17 @@ """Support for Ecobee sensors.""" from homeassistant.components import ecobee from homeassistant.const import ( - DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, TEMP_FAHRENHEIT) + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + TEMP_FAHRENHEIT, +) from homeassistant.helpers.entity import Entity -ECOBEE_CONFIG_FILE = 'ecobee.conf' +ECOBEE_CONFIG_FILE = "ecobee.conf" SENSOR_TYPES = { - 'temperature': ['Temperature', TEMP_FAHRENHEIT], - 'humidity': ['Humidity', '%'] + "temperature": ["Temperature", TEMP_FAHRENHEIT], + "humidity": ["Humidity", "%"], } @@ -20,11 +23,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): dev = list() for index in range(len(data.ecobee.thermostats)): for sensor in data.ecobee.get_remote_sensors(index): - for item in sensor['capability']: - if item['type'] not in ('temperature', 'humidity'): + for item in sensor["capability"]: + if item["type"] not in ("temperature", "humidity"): continue - dev.append(EcobeeSensor(sensor['name'], item['type'], index)) + dev.append(EcobeeSensor(sensor["name"], item["type"], index)) add_entities(dev, True) @@ -34,7 +37,7 @@ class EcobeeSensor(Entity): def __init__(self, sensor_name, sensor_type, sensor_index): """Initialize the sensor.""" - self._name = '{} {}'.format(sensor_name, SENSOR_TYPES[sensor_type][0]) + self._name = "{} {}".format(sensor_name, SENSOR_TYPES[sensor_type][0]) self.sensor_name = sensor_name self.type = sensor_type self.index = sensor_index @@ -68,11 +71,9 @@ class EcobeeSensor(Entity): data = ecobee.NETWORK data.update() for sensor in data.ecobee.get_remote_sensors(self.index): - for item in sensor['capability']: - if (item['type'] == self.type and - self.sensor_name == sensor['name']): - if (self.type == 'temperature' and - item['value'] != 'unknown'): - self._state = float(item['value']) / 10 + for item in sensor["capability"]: + if item["type"] == self.type and self.sensor_name == sensor["name"]: + if self.type == "temperature" and item["value"] != "unknown": + self._state = float(item["value"]) / 10 else: - self._state = item['value'] + self._state = item["value"] diff --git a/homeassistant/components/ecobee/weather.py b/homeassistant/components/ecobee/weather.py index f5058434f38..0680ef67f82 100644 --- a/homeassistant/components/ecobee/weather.py +++ b/homeassistant/components/ecobee/weather.py @@ -3,14 +3,19 @@ from datetime import datetime from homeassistant.components import ecobee from homeassistant.components.weather import ( - ATTR_FORECAST_CONDITION, ATTR_FORECAST_TEMP, ATTR_FORECAST_TEMP_LOW, - ATTR_FORECAST_TIME, ATTR_FORECAST_WIND_SPEED, WeatherEntity) + ATTR_FORECAST_CONDITION, + ATTR_FORECAST_TEMP, + ATTR_FORECAST_TEMP_LOW, + ATTR_FORECAST_TIME, + ATTR_FORECAST_WIND_SPEED, + WeatherEntity, +) from homeassistant.const import TEMP_FAHRENHEIT -ATTR_FORECAST_TEMP_HIGH = 'temphigh' -ATTR_FORECAST_PRESSURE = 'pressure' -ATTR_FORECAST_VISIBILITY = 'visibility' -ATTR_FORECAST_HUMIDITY = 'humidity' +ATTR_FORECAST_TEMP_HIGH = "temphigh" +ATTR_FORECAST_PRESSURE = "pressure" +ATTR_FORECAST_VISIBILITY = "visibility" +ATTR_FORECAST_HUMIDITY = "humidity" MISSING_DATA = -5002 @@ -23,8 +28,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): data = ecobee.NETWORK for index in range(len(data.ecobee.thermostats)): thermostat = data.ecobee.get_thermostat(index) - if 'weather' in thermostat: - dev.append(EcobeeWeather(thermostat['name'], index)) + if "weather" in thermostat: + dev.append(EcobeeWeather(thermostat["name"], index)) add_entities(dev, True) @@ -41,7 +46,7 @@ class EcobeeWeather(WeatherEntity): def get_forecast(self, index, param): """Retrieve forecast parameter.""" try: - forecast = self.weather['forecasts'][index] + forecast = self.weather["forecasts"][index] return forecast[param] except (ValueError, IndexError, KeyError): raise ValueError @@ -55,7 +60,7 @@ class EcobeeWeather(WeatherEntity): def condition(self): """Return the current condition.""" try: - return self.get_forecast(0, 'condition') + return self.get_forecast(0, "condition") except ValueError: return None @@ -63,7 +68,7 @@ class EcobeeWeather(WeatherEntity): def temperature(self): """Return the temperature.""" try: - return float(self.get_forecast(0, 'temperature')) / 10 + return float(self.get_forecast(0, "temperature")) / 10 except ValueError: return None @@ -76,7 +81,7 @@ class EcobeeWeather(WeatherEntity): def pressure(self): """Return the pressure.""" try: - return int(self.get_forecast(0, 'pressure')) + return int(self.get_forecast(0, "pressure")) except ValueError: return None @@ -84,7 +89,7 @@ class EcobeeWeather(WeatherEntity): def humidity(self): """Return the humidity.""" try: - return int(self.get_forecast(0, 'relativeHumidity')) + return int(self.get_forecast(0, "relativeHumidity")) except ValueError: return None @@ -92,7 +97,7 @@ class EcobeeWeather(WeatherEntity): def visibility(self): """Return the visibility.""" try: - return int(self.get_forecast(0, 'visibility')) + return int(self.get_forecast(0, "visibility")) except ValueError: return None @@ -100,7 +105,7 @@ class EcobeeWeather(WeatherEntity): def wind_speed(self): """Return the wind speed.""" try: - return int(self.get_forecast(0, 'windSpeed')) + return int(self.get_forecast(0, "windSpeed")) except ValueError: return None @@ -108,7 +113,7 @@ class EcobeeWeather(WeatherEntity): def wind_bearing(self): """Return the wind direction.""" try: - return int(self.get_forecast(0, 'windBearing')) + return int(self.get_forecast(0, "windBearing")) except ValueError: return None @@ -116,8 +121,8 @@ class EcobeeWeather(WeatherEntity): def attribution(self): """Return the attribution.""" if self.weather: - station = self.weather.get('weatherStation', "UNKNOWN") - time = self.weather.get('timestamp', "UNKNOWN") + station = self.weather.get("weatherStation", "UNKNOWN") + time = self.weather.get("timestamp", "UNKNOWN") return "Ecobee weather provided by {} at {}".format(station, time) return None @@ -126,28 +131,27 @@ class EcobeeWeather(WeatherEntity): """Return the forecast array.""" try: forecasts = [] - for day in self.weather['forecasts']: - date_time = datetime.strptime(day['dateTime'], - '%Y-%m-%d %H:%M:%S').isoformat() + for day in self.weather["forecasts"]: + date_time = datetime.strptime( + day["dateTime"], "%Y-%m-%d %H:%M:%S" + ).isoformat() forecast = { ATTR_FORECAST_TIME: date_time, - ATTR_FORECAST_CONDITION: day['condition'], - ATTR_FORECAST_TEMP: float(day['tempHigh']) / 10, + ATTR_FORECAST_CONDITION: day["condition"], + ATTR_FORECAST_TEMP: float(day["tempHigh"]) / 10, } - if day['tempHigh'] == MISSING_DATA: + if day["tempHigh"] == MISSING_DATA: break - if day['tempLow'] != MISSING_DATA: - forecast[ATTR_FORECAST_TEMP_LOW] = \ - float(day['tempLow']) / 10 - if day['pressure'] != MISSING_DATA: - forecast[ATTR_FORECAST_PRESSURE] = int(day['pressure']) - if day['windSpeed'] != MISSING_DATA: - forecast[ATTR_FORECAST_WIND_SPEED] = int(day['windSpeed']) - if day['visibility'] != MISSING_DATA: - forecast[ATTR_FORECAST_WIND_SPEED] = int(day['visibility']) - if day['relativeHumidity'] != MISSING_DATA: - forecast[ATTR_FORECAST_HUMIDITY] = \ - int(day['relativeHumidity']) + if day["tempLow"] != MISSING_DATA: + forecast[ATTR_FORECAST_TEMP_LOW] = float(day["tempLow"]) / 10 + if day["pressure"] != MISSING_DATA: + forecast[ATTR_FORECAST_PRESSURE] = int(day["pressure"]) + if day["windSpeed"] != MISSING_DATA: + forecast[ATTR_FORECAST_WIND_SPEED] = int(day["windSpeed"]) + if day["visibility"] != MISSING_DATA: + forecast[ATTR_FORECAST_WIND_SPEED] = int(day["visibility"]) + if day["relativeHumidity"] != MISSING_DATA: + forecast[ATTR_FORECAST_HUMIDITY] = int(day["relativeHumidity"]) forecasts.append(forecast) return forecasts except (ValueError, IndexError, KeyError): @@ -158,4 +162,4 @@ class EcobeeWeather(WeatherEntity): data = ecobee.NETWORK data.update() thermostat = data.ecobee.get_thermostat(self._index) - self.weather = thermostat.get('weather', None) + self.weather = thermostat.get("weather", None) diff --git a/homeassistant/components/econet/water_heater.py b/homeassistant/components/econet/water_heater.py index 4c47e24d705..1c8deae5b99 100644 --- a/homeassistant/components/econet/water_heater.py +++ b/homeassistant/components/econet/water_heater.py @@ -5,58 +5,71 @@ import logging import voluptuous as vol from homeassistant.components.water_heater import ( - DOMAIN, PLATFORM_SCHEMA, STATE_ECO, STATE_ELECTRIC, STATE_GAS, - STATE_HEAT_PUMP, STATE_HIGH_DEMAND, STATE_OFF, STATE_PERFORMANCE, - SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, WaterHeaterDevice) + DOMAIN, + PLATFORM_SCHEMA, + STATE_ECO, + STATE_ELECTRIC, + STATE_GAS, + STATE_HEAT_PUMP, + STATE_HIGH_DEMAND, + STATE_OFF, + STATE_PERFORMANCE, + SUPPORT_OPERATION_MODE, + SUPPORT_TARGET_TEMPERATURE, + WaterHeaterDevice, +) from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_TEMPERATURE, CONF_PASSWORD, CONF_USERNAME, - TEMP_FAHRENHEIT) + ATTR_ENTITY_ID, + ATTR_TEMPERATURE, + CONF_PASSWORD, + CONF_USERNAME, + TEMP_FAHRENHEIT, +) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -ATTR_VACATION_START = 'next_vacation_start_date' -ATTR_VACATION_END = 'next_vacation_end_date' -ATTR_ON_VACATION = 'on_vacation' -ATTR_TODAYS_ENERGY_USAGE = 'todays_energy_usage' -ATTR_IN_USE = 'in_use' +ATTR_VACATION_START = "next_vacation_start_date" +ATTR_VACATION_END = "next_vacation_end_date" +ATTR_ON_VACATION = "on_vacation" +ATTR_TODAYS_ENERGY_USAGE = "todays_energy_usage" +ATTR_IN_USE = "in_use" -ATTR_START_DATE = 'start_date' -ATTR_END_DATE = 'end_date' +ATTR_START_DATE = "start_date" +ATTR_END_DATE = "end_date" -SUPPORT_FLAGS_HEATER = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE) +SUPPORT_FLAGS_HEATER = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE -SERVICE_ADD_VACATION = 'econet_add_vacation' -SERVICE_DELETE_VACATION = 'econet_delete_vacation' +SERVICE_ADD_VACATION = "econet_add_vacation" +SERVICE_DELETE_VACATION = "econet_delete_vacation" -ADD_VACATION_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, - vol.Optional(ATTR_START_DATE): cv.positive_int, - vol.Required(ATTR_END_DATE): cv.positive_int, -}) +ADD_VACATION_SCHEMA = vol.Schema( + { + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_START_DATE): cv.positive_int, + vol.Required(ATTR_END_DATE): cv.positive_int, + } +) -DELETE_VACATION_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, -}) +DELETE_VACATION_SCHEMA = vol.Schema({vol.Optional(ATTR_ENTITY_ID): cv.entity_ids}) -ECONET_DATA = 'econet' +ECONET_DATA = "econet" ECONET_STATE_TO_HA = { - 'Energy Saver': STATE_ECO, - 'gas': STATE_GAS, - 'High Demand': STATE_HIGH_DEMAND, - 'Off': STATE_OFF, - 'Performance': STATE_PERFORMANCE, - 'Heat Pump Only': STATE_HEAT_PUMP, - 'Electric-Only': STATE_ELECTRIC, - 'Electric': STATE_ELECTRIC, - 'Heat Pump': STATE_HEAT_PUMP + "Energy Saver": STATE_ECO, + "gas": STATE_GAS, + "High Demand": STATE_HIGH_DEMAND, + "Off": STATE_OFF, + "Performance": STATE_PERFORMANCE, + "Heat Pump Only": STATE_HEAT_PUMP, + "Electric-Only": STATE_ELECTRIC, + "Electric": STATE_ELECTRIC, + "Heat Pump": STATE_HEAT_PUMP, } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string} +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -64,7 +77,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): from pyeconet.api import PyEcoNet hass.data[ECONET_DATA] = {} - hass.data[ECONET_DATA]['water_heaters'] = [] + hass.data[ECONET_DATA]["water_heaters"] = [] username = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) @@ -72,17 +85,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None): econet = PyEcoNet(username, password) water_heaters = econet.get_water_heaters() hass_water_heaters = [ - EcoNetWaterHeater(water_heater) for water_heater in water_heaters] + EcoNetWaterHeater(water_heater) for water_heater in water_heaters + ] add_entities(hass_water_heaters) - hass.data[ECONET_DATA]['water_heaters'].extend(hass_water_heaters) + hass.data[ECONET_DATA]["water_heaters"].extend(hass_water_heaters) def service_handle(service): """Handle the service calls.""" - entity_ids = service.data.get('entity_id') - all_heaters = hass.data[ECONET_DATA]['water_heaters'] + entity_ids = service.data.get("entity_id") + all_heaters = hass.data[ECONET_DATA]["water_heaters"] _heaters = [ - x for x in all_heaters - if not entity_ids or x.entity_id in entity_ids] + x for x in all_heaters if not entity_ids or x.entity_id in entity_ids + ] for _water_heater in _heaters: if service.service == SERVICE_ADD_VACATION: @@ -95,11 +109,13 @@ def setup_platform(hass, config, add_entities, discovery_info=None): _water_heater.schedule_update_ha_state(True) - hass.services.register(DOMAIN, SERVICE_ADD_VACATION, service_handle, - schema=ADD_VACATION_SCHEMA) + hass.services.register( + DOMAIN, SERVICE_ADD_VACATION, service_handle, schema=ADD_VACATION_SCHEMA + ) - hass.services.register(DOMAIN, SERVICE_DELETE_VACATION, service_handle, - schema=DELETE_VACATION_SCHEMA) + hass.services.register( + DOMAIN, SERVICE_DELETE_VACATION, service_handle, schema=DELETE_VACATION_SCHEMA + ) class EcoNetWaterHeater(WaterHeaterDevice): @@ -118,8 +134,11 @@ class EcoNetWaterHeater(WaterHeaterDevice): self.ha_state_to_econet[value] = key for mode in self.supported_modes: if mode not in ECONET_STATE_TO_HA: - error = "Invalid operation mode mapping. " + mode + \ - " doesn't map. Please report this." + error = ( + "Invalid operation mode mapping. " + + mode + + " doesn't map. Please report this." + ) _LOGGER.error(error) @property diff --git a/homeassistant/components/ecovacs/__init__.py b/homeassistant/components/ecovacs/__init__.py index da87af722a6..76566912d12 100644 --- a/homeassistant/components/ecovacs/__init__.py +++ b/homeassistant/components/ecovacs/__init__.py @@ -5,8 +5,7 @@ import string import voluptuous as vol -from homeassistant.const import ( - CONF_PASSWORD, CONF_USERNAME, EVENT_HOMEASSISTANT_STOP) +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, EVENT_HOMEASSISTANT_STOP from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv @@ -17,19 +16,24 @@ DOMAIN = "ecovacs" CONF_COUNTRY = "country" CONF_CONTINENT = "continent" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_COUNTRY): vol.All(vol.Lower, cv.string), - vol.Required(CONF_CONTINENT): vol.All(vol.Lower, cv.string), - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_COUNTRY): vol.All(vol.Lower, cv.string), + vol.Required(CONF_CONTINENT): vol.All(vol.Lower, cv.string), + } + ) + }, + extra=vol.ALLOW_EXTRA, +) ECOVACS_DEVICES = "ecovacs_devices" # Generate a random device ID on each bootup -ECOVACS_API_DEVICEID = ''.join( +ECOVACS_API_DEVICEID = "".join( random.choice(string.ascii_uppercase + string.digits) for _ in range(8) ) @@ -42,11 +46,13 @@ def setup(hass, config): from sucks import EcoVacsAPI, VacBot - ecovacs_api = EcoVacsAPI(ECOVACS_API_DEVICEID, - config[DOMAIN].get(CONF_USERNAME), - EcoVacsAPI.md5(config[DOMAIN].get(CONF_PASSWORD)), - config[DOMAIN].get(CONF_COUNTRY), - config[DOMAIN].get(CONF_CONTINENT)) + ecovacs_api = EcoVacsAPI( + ECOVACS_API_DEVICEID, + config[DOMAIN].get(CONF_USERNAME), + EcoVacsAPI.md5(config[DOMAIN].get(CONF_PASSWORD)), + config[DOMAIN].get(CONF_COUNTRY), + config[DOMAIN].get(CONF_CONTINENT), + ) devices = ecovacs_api.devices() _LOGGER.debug("Ecobot devices: %s", devices) @@ -54,21 +60,26 @@ def setup(hass, config): for device in devices: _LOGGER.info( "Discovered Ecovacs device on account: %s with nickname %s", - device['did'], device['nick']) - vacbot = VacBot(ecovacs_api.uid, - ecovacs_api.REALM, - ecovacs_api.resource, - ecovacs_api.user_access_token, - device, - config[DOMAIN].get(CONF_CONTINENT).lower(), - monitor=True) + device["did"], + device["nick"], + ) + vacbot = VacBot( + ecovacs_api.uid, + ecovacs_api.REALM, + ecovacs_api.resource, + ecovacs_api.user_access_token, + device, + config[DOMAIN].get(CONF_CONTINENT).lower(), + monitor=True, + ) hass.data[ECOVACS_DEVICES].append(vacbot) def stop(event: object) -> None: """Shut down open connections to Ecovacs XMPP server.""" for device in hass.data[ECOVACS_DEVICES]: - _LOGGER.info("Shutting down connection to Ecovacs device %s", - device.vacuum['did']) + _LOGGER.info( + "Shutting down connection to Ecovacs device %s", device.vacuum["did"] + ) device.disconnect() # Listen for HA stop to disconnect. diff --git a/homeassistant/components/ecovacs/vacuum.py b/homeassistant/components/ecovacs/vacuum.py index ee374871d31..fdaf6291be5 100644 --- a/homeassistant/components/ecovacs/vacuum.py +++ b/homeassistant/components/ecovacs/vacuum.py @@ -2,9 +2,18 @@ import logging from homeassistant.components.vacuum import ( - SUPPORT_BATTERY, SUPPORT_CLEAN_SPOT, SUPPORT_FAN_SPEED, SUPPORT_LOCATE, - SUPPORT_RETURN_HOME, SUPPORT_SEND_COMMAND, SUPPORT_STATUS, SUPPORT_STOP, - SUPPORT_TURN_OFF, SUPPORT_TURN_ON, VacuumDevice) + SUPPORT_BATTERY, + SUPPORT_CLEAN_SPOT, + SUPPORT_FAN_SPEED, + SUPPORT_LOCATE, + SUPPORT_RETURN_HOME, + SUPPORT_SEND_COMMAND, + SUPPORT_STATUS, + SUPPORT_STOP, + SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, + VacuumDevice, +) from homeassistant.helpers.icon import icon_for_battery_level from . import ECOVACS_DEVICES @@ -12,12 +21,20 @@ from . import ECOVACS_DEVICES _LOGGER = logging.getLogger(__name__) SUPPORT_ECOVACS = ( - SUPPORT_BATTERY | SUPPORT_RETURN_HOME | SUPPORT_CLEAN_SPOT | - SUPPORT_STOP | SUPPORT_TURN_OFF | SUPPORT_TURN_ON | SUPPORT_LOCATE | - SUPPORT_STATUS | SUPPORT_SEND_COMMAND | SUPPORT_FAN_SPEED) + SUPPORT_BATTERY + | SUPPORT_RETURN_HOME + | SUPPORT_CLEAN_SPOT + | SUPPORT_STOP + | SUPPORT_TURN_OFF + | SUPPORT_TURN_ON + | SUPPORT_LOCATE + | SUPPORT_STATUS + | SUPPORT_SEND_COMMAND + | SUPPORT_FAN_SPEED +) -ATTR_ERROR = 'error' -ATTR_COMPONENT_PREFIX = 'component_' +ATTR_ERROR = "error" +ATTR_COMPONENT_PREFIX = "component_" def setup_platform(hass, config, add_entities, discovery_info=None): @@ -36,11 +53,11 @@ class EcovacsVacuum(VacuumDevice): """Initialize the Ecovacs Vacuum.""" self.device = device self.device.connect_and_wait_until_ready() - if self.device.vacuum.get('nick', None) is not None: - self._name = '{}'.format(self.device.vacuum['nick']) + if self.device.vacuum.get("nick", None) is not None: + self._name = "{}".format(self.device.vacuum["nick"]) else: # In case there is no nickname defined, use the device id - self._name = '{}'.format(self.device.vacuum['did']) + self._name = "{}".format(self.device.vacuum["did"]) self._fan_speed = None self._error = None @@ -48,12 +65,9 @@ class EcovacsVacuum(VacuumDevice): async def async_added_to_hass(self) -> None: """Set up the event listeners now that hass is ready.""" - self.device.statusEvents.subscribe(lambda _: - self.schedule_update_ha_state()) - self.device.batteryEvents.subscribe(lambda _: - self.schedule_update_ha_state()) - self.device.lifespanEvents.subscribe(lambda _: - self.schedule_update_ha_state()) + self.device.statusEvents.subscribe(lambda _: self.schedule_update_ha_state()) + self.device.batteryEvents.subscribe(lambda _: self.schedule_update_ha_state()) + self.device.lifespanEvents.subscribe(lambda _: self.schedule_update_ha_state()) self.device.errorEvents.subscribe(self.on_error) def on_error(self, error): @@ -62,15 +76,14 @@ class EcovacsVacuum(VacuumDevice): This will not change the entity's state. If the error caused the state to change, that will come through as a separate on_status event """ - if error == 'no_error': + if error == "no_error": self._error = None else: self._error = error - self.hass.bus.fire('ecovacs_error', { - 'entity_id': self.entity_id, - 'error': error - }) + self.hass.bus.fire( + "ecovacs_error", {"entity_id": self.entity_id, "error": error} + ) self.schedule_update_ha_state() @property @@ -81,7 +94,7 @@ class EcovacsVacuum(VacuumDevice): @property def unique_id(self) -> str: """Return an unique ID.""" - return self.device.vacuum.get('did', None) + return self.device.vacuum.get("did", None) @property def is_on(self): @@ -111,13 +124,15 @@ class EcovacsVacuum(VacuumDevice): def return_to_base(self, **kwargs): """Set the vacuum cleaner to return to the dock.""" from sucks import Charge + self.device.run(Charge()) @property def battery_icon(self): """Return the battery icon for the vacuum cleaner.""" return icon_for_battery_level( - battery_level=self.battery_level, charging=self.is_charging) + battery_level=self.battery_level, charging=self.is_charging + ) @property def battery_level(self): @@ -136,11 +151,13 @@ class EcovacsVacuum(VacuumDevice): def fan_speed_list(self): """Get the list of available fan speed steps of the vacuum cleaner.""" from sucks import FAN_SPEED_NORMAL, FAN_SPEED_HIGH + return [FAN_SPEED_NORMAL, FAN_SPEED_HIGH] def turn_on(self, **kwargs): """Turn the vacuum on and start cleaning.""" from sucks import Clean + self.device.run(Clean()) def turn_off(self, **kwargs): @@ -150,28 +167,32 @@ class EcovacsVacuum(VacuumDevice): def stop(self, **kwargs): """Stop the vacuum cleaner.""" from sucks import Stop + self.device.run(Stop()) def clean_spot(self, **kwargs): """Perform a spot clean-up.""" from sucks import Spot + self.device.run(Spot()) def locate(self, **kwargs): """Locate the vacuum cleaner.""" from sucks import PlaySound + self.device.run(PlaySound()) def set_fan_speed(self, fan_speed, **kwargs): """Set fan speed.""" if self.is_on: from sucks import Clean - self.device.run(Clean( - mode=self.device.clean_status, speed=fan_speed)) + + self.device.run(Clean(mode=self.device.clean_status, speed=fan_speed)) def send_command(self, command, params=None, **kwargs): """Send a command to a vacuum cleaner.""" from sucks import VacBotCommand + self.device.run(VacBotCommand(command, params)) @property diff --git a/homeassistant/components/eddystone_temperature/sensor.py b/homeassistant/components/eddystone_temperature/sensor.py index aad279934e5..5492582ebed 100644 --- a/homeassistant/components/eddystone_temperature/sensor.py +++ b/homeassistant/components/eddystone_temperature/sensor.py @@ -13,28 +13,36 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_NAME, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, - STATE_UNKNOWN, TEMP_CELSIUS) + CONF_NAME, + EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP, + STATE_UNKNOWN, + TEMP_CELSIUS, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -CONF_BEACONS = 'beacons' -CONF_BT_DEVICE_ID = 'bt_device_id' -CONF_INSTANCE = 'instance' -CONF_NAMESPACE = 'namespace' +CONF_BEACONS = "beacons" +CONF_BT_DEVICE_ID = "bt_device_id" +CONF_INSTANCE = "instance" +CONF_NAMESPACE = "namespace" -BEACON_SCHEMA = vol.Schema({ - vol.Required(CONF_NAMESPACE): cv.string, - vol.Required(CONF_INSTANCE): cv.string, - vol.Optional(CONF_NAME): cv.string -}) +BEACON_SCHEMA = vol.Schema( + { + vol.Required(CONF_NAMESPACE): cv.string, + vol.Required(CONF_INSTANCE): cv.string, + vol.Optional(CONF_NAME): cv.string, + } +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_BT_DEVICE_ID, default=0): cv.positive_int, - vol.Required(CONF_BEACONS): vol.Schema({cv.string: BEACON_SCHEMA}), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_BT_DEVICE_ID, default=0): cv.positive_int, + vol.Required(CONF_BEACONS): vol.Schema({cv.string: BEACON_SCHEMA}), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -80,8 +88,12 @@ def get_from_conf(config, config_key, length): """Retrieve value from config and validate length.""" string = config.get(config_key) if len(string) != length: - _LOGGER.error("Error in config parameter %s: Must be exactly %d " - "bytes. Device will not be added", config_key, length/2) + _LOGGER.error( + "Error in config parameter %s: Must be exactly %d " + "bytes. Device will not be added", + config_key, + length / 2, + ) return None return string @@ -133,16 +145,22 @@ class Monitor: def callback(bt_addr, _, packet, additional_info): """Handle new packets.""" self.process_packet( - additional_info['namespace'], additional_info['instance'], - packet.temperature) + additional_info["namespace"], + additional_info["instance"], + packet.temperature, + ) from beacontools import ( # pylint: disable=import-error - BeaconScanner, EddystoneFilter, EddystoneTLMFrame) - device_filters = [EddystoneFilter(d.namespace, d.instance) - for d in devices] + BeaconScanner, + EddystoneFilter, + EddystoneTLMFrame, + ) + + device_filters = [EddystoneFilter(d.namespace, d.instance) for d in devices] self.scanner = BeaconScanner( - callback, bt_device_id, device_filters, EddystoneTLMFrame) + callback, bt_device_id, device_filters, EddystoneTLMFrame + ) self.scanning = False def start(self): @@ -151,13 +169,13 @@ class Monitor: self.scanner.start() self.scanning = True else: - _LOGGER.debug( - "start() called, but scanner is already running") + _LOGGER.debug("start() called, but scanner is already running") def process_packet(self, namespace, instance, temperature): """Assign temperature to device.""" - _LOGGER.debug("Received temperature for <%s,%s>: %d", - namespace, instance, temperature) + _LOGGER.debug( + "Received temperature for <%s,%s>: %d", namespace, instance, temperature + ) for dev in self.devices: if dev.namespace == namespace and dev.instance == instance: @@ -173,5 +191,4 @@ class Monitor: _LOGGER.debug("Stopped") self.scanning = False else: - _LOGGER.debug( - "stop() called but scanner was not running") + _LOGGER.debug("stop() called but scanner was not running") diff --git a/homeassistant/components/edimax/switch.py b/homeassistant/components/edimax/switch.py index 535ae65800f..f1d8f8046ef 100644 --- a/homeassistant/components/edimax/switch.py +++ b/homeassistant/components/edimax/switch.py @@ -3,23 +3,24 @@ import logging import voluptuous as vol -from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) -from homeassistant.const import ( - CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME) +from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA +from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'Edimax Smart Plug' -DEFAULT_PASSWORD = '1234' -DEFAULT_USERNAME = 'admin' +DEFAULT_NAME = "Edimax Smart Plug" +DEFAULT_PASSWORD = "1234" +DEFAULT_USERNAME = "admin" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string, - vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string, + vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -66,11 +67,11 @@ class SmartPlugSwitch(SwitchDevice): def turn_on(self, **kwargs): """Turn the switch on.""" - self.smartplug.state = 'ON' + self.smartplug.state = "ON" def turn_off(self, **kwargs): """Turn the switch off.""" - self.smartplug.state = 'OFF' + self.smartplug.state = "OFF" def update(self): """Update edimax switch.""" @@ -84,4 +85,4 @@ class SmartPlugSwitch(SwitchDevice): except (TypeError, ValueError): self._now_energy_day = None - self._state = self.smartplug.state == 'ON' + self._state = self.smartplug.state == "ON" diff --git a/homeassistant/components/edp_redy/__init__.py b/homeassistant/components/edp_redy/__init__.py index af012064194..8c079078176 100644 --- a/homeassistant/components/edp_redy/__init__.py +++ b/homeassistant/components/edp_redy/__init__.py @@ -4,8 +4,7 @@ import logging import voluptuous as vol -from homeassistant.const import ( - CONF_PASSWORD, CONF_USERNAME, EVENT_HOMEASSISTANT_START) +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, EVENT_HOMEASSISTANT_START from homeassistant.core import callback from homeassistant.helpers import aiohttp_client, discovery, dispatcher import homeassistant.helpers.config_validation as cv @@ -15,27 +14,34 @@ from homeassistant.util import dt as dt_util _LOGGER = logging.getLogger(__name__) -DOMAIN = 'edp_redy' -EDP_REDY = 'edp_redy' -DATA_UPDATE_TOPIC = '{0}_data_update'.format(DOMAIN) +DOMAIN = "edp_redy" +EDP_REDY = "edp_redy" +DATA_UPDATE_TOPIC = "{0}_data_update".format(DOMAIN) UPDATE_INTERVAL = 60 -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) async def async_setup(hass, config): """Set up the EDP re:dy component.""" from edp_redy import EdpRedySession - session = EdpRedySession(config[DOMAIN][CONF_USERNAME], - config[DOMAIN][CONF_PASSWORD], - aiohttp_client.async_get_clientsession(hass), - hass.loop) + session = EdpRedySession( + config[DOMAIN][CONF_USERNAME], + config[DOMAIN][CONF_PASSWORD], + aiohttp_client.async_get_clientsession(hass), + hass.loop, + ) hass.data[EDP_REDY] = session platform_loaded = False @@ -46,16 +52,18 @@ async def async_setup(hass, config): nonlocal platform_loaded # pylint: disable=used-before-assignment if not platform_loaded: - for component in ['sensor', 'switch']: - await discovery.async_load_platform(hass, component, - DOMAIN, {}, config) + for component in ["sensor", "switch"]: + await discovery.async_load_platform( + hass, component, DOMAIN, {}, config + ) platform_loaded = True dispatcher.async_dispatcher_send(hass, DATA_UPDATE_TOPIC) # schedule next update - async_track_point_in_time(hass, async_update_and_sched, - time + timedelta(seconds=UPDATE_INTERVAL)) + async_track_point_in_time( + hass, async_update_and_sched, time + timedelta(seconds=UPDATE_INTERVAL) + ) async def start_component(event): _LOGGER.debug("Starting updates") @@ -84,7 +92,8 @@ class EdpRedyDevice(Entity): async def async_added_to_hass(self): """Subscribe to the data updates topic.""" dispatcher.async_dispatcher_connect( - self.hass, DATA_UPDATE_TOPIC, self._data_updated) + self.hass, DATA_UPDATE_TOPIC, self._data_updated + ) @property def name(self): @@ -120,8 +129,7 @@ class EdpRedyDevice(Entity): """Parse data received from the server.""" if "OutOfOrder" in data: try: - self._is_available = not data['OutOfOrder'] + self._is_available = not data["OutOfOrder"] except ValueError: - _LOGGER.error( - "Could not parse OutOfOrder for %s", self._id) + _LOGGER.error("Could not parse OutOfOrder for %s", self._id) self._is_available = False diff --git a/homeassistant/components/edp_redy/sensor.py b/homeassistant/components/edp_redy/sensor.py index cf9766ede66..f8fffefb5da 100644 --- a/homeassistant/components/edp_redy/sensor.py +++ b/homeassistant/components/edp_redy/sensor.py @@ -9,11 +9,10 @@ from . import EDP_REDY, EdpRedyDevice _LOGGER = logging.getLogger(__name__) # Load power in watts (W) -ATTR_ACTIVE_POWER = 'active_power' +ATTR_ACTIVE_POWER = "active_power" -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Perform the setup for re:dy devices.""" from edp_redy.session import ACTIVE_POWER_ID @@ -22,13 +21,14 @@ async def async_setup_platform( # Create sensors for modules for device_json in session.modules_dict.values(): - if 'HA_POWER_METER' not in device_json['Capabilities']: + if "HA_POWER_METER" not in device_json["Capabilities"]: continue devices.append(EdpRedyModuleSensor(session, device_json)) # Create a sensor for global active power - devices.append(EdpRedySensor(session, ACTIVE_POWER_ID, "Power Home", - 'mdi:flash', POWER_WATT)) + devices.append( + EdpRedySensor(session, ACTIVE_POWER_ID, "Power Home", "mdi:flash", POWER_WATT) + ) async_add_entities(devices, True) @@ -72,8 +72,9 @@ class EdpRedyModuleSensor(EdpRedyDevice, Entity): def __init__(self, session, device_json): """Initialize the sensor.""" - super().__init__(session, device_json['PKID'], - "Power {0}".format(device_json['Name'])) + super().__init__( + session, device_json["PKID"], "Power {0}".format(device_json["Name"]) + ) @property def state(self): @@ -83,7 +84,7 @@ class EdpRedyModuleSensor(EdpRedyDevice, Entity): @property def icon(self): """Return the icon to use in the frontend.""" - return 'mdi:flash' + return "mdi:flash" @property def unit_of_measurement(self): @@ -104,10 +105,10 @@ class EdpRedyModuleSensor(EdpRedyDevice, Entity): _LOGGER.debug("Sensor data: %s", str(data)) - for state_var in data['StateVars']: - if state_var['Name'] == 'ActivePower': + for state_var in data["StateVars"]: + if state_var["Name"] == "ActivePower": try: - self._state = float(state_var['Value']) * 1000 + self._state = float(state_var["Value"]) * 1000 except ValueError: _LOGGER.error("Could not parse power for %s", self._id) self._state = 0 diff --git a/homeassistant/components/edp_redy/switch.py b/homeassistant/components/edp_redy/switch.py index 3f6dfe6b82d..18078fab537 100644 --- a/homeassistant/components/edp_redy/switch.py +++ b/homeassistant/components/edp_redy/switch.py @@ -8,16 +8,15 @@ from . import EDP_REDY, EdpRedyDevice _LOGGER = logging.getLogger(__name__) # Load power in watts (W) -ATTR_ACTIVE_POWER = 'active_power' +ATTR_ACTIVE_POWER = "active_power" -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Perform the setup for re:dy devices.""" session = hass.data[EDP_REDY] devices = [] for device_json in session.modules_dict.values(): - if 'HA_SWITCH' not in device_json['Capabilities']: + if "HA_SWITCH" not in device_json["Capabilities"]: continue devices.append(EdpRedySwitch(session, device_json)) @@ -29,14 +28,14 @@ class EdpRedySwitch(EdpRedyDevice, SwitchDevice): def __init__(self, session, device_json): """Initialize the switch.""" - super().__init__(session, device_json['PKID'], device_json['Name']) + super().__init__(session, device_json["PKID"], device_json["Name"]) self._active_power = None @property def icon(self): """Return the icon to use in the frontend.""" - return 'mdi:power-plug' + return "mdi:power-plug" @property def is_on(self): @@ -66,8 +65,7 @@ class EdpRedySwitch(EdpRedyDevice, SwitchDevice): self.async_schedule_update_ha_state() async def _async_send_state_cmd(self, state): - state_json = {'devModuleId': self._id, 'key': 'RelayState', - 'value': state} + state_json = {"devModuleId": self._id, "key": "RelayState", "value": state} return await self._session.async_set_state_var(state_json) async def async_update(self): @@ -82,12 +80,12 @@ class EdpRedySwitch(EdpRedyDevice, SwitchDevice): """Parse data received from the server.""" super()._parse_data(data) - for state_var in data['StateVars']: - if state_var['Name'] == 'RelayState': - self._state = state_var['Value'] == 'true' - elif state_var['Name'] == 'ActivePower': + for state_var in data["StateVars"]: + if state_var["Name"] == "RelayState": + self._state = state_var["Value"] == "true" + elif state_var["Name"] == "ActivePower": try: - self._active_power = float(state_var['Value']) * 1000 + self._active_power = float(state_var["Value"]) * 1000 except ValueError: _LOGGER.error("Could not parse power for %s", self._id) self._active_power = None diff --git a/homeassistant/components/ee_brightbox/device_tracker.py b/homeassistant/components/ee_brightbox/device_tracker.py index 6af5065ed2e..81dbf9eab1f 100644 --- a/homeassistant/components/ee_brightbox/device_tracker.py +++ b/homeassistant/components/ee_brightbox/device_tracker.py @@ -4,24 +4,29 @@ import logging import voluptuous as vol from homeassistant.components.device_tracker import ( - DOMAIN, PLATFORM_SCHEMA, DeviceScanner) + DOMAIN, + PLATFORM_SCHEMA, + DeviceScanner, +) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -CONF_VERSION = 'version' +CONF_VERSION = "version" -CONF_DEFAULT_IP = '192.168.1.1' -CONF_DEFAULT_USERNAME = 'admin' +CONF_DEFAULT_IP = "192.168.1.1" +CONF_DEFAULT_USERNAME = "admin" CONF_DEFAULT_VERSION = 2 -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_VERSION, default=CONF_DEFAULT_VERSION): cv.positive_int, - vol.Required(CONF_HOST, default=CONF_DEFAULT_IP): cv.string, - vol.Required(CONF_USERNAME, default=CONF_DEFAULT_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_VERSION, default=CONF_DEFAULT_VERSION): cv.positive_int, + vol.Required(CONF_HOST, default=CONF_DEFAULT_IP): cv.string, + vol.Required(CONF_USERNAME, default=CONF_DEFAULT_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + } +) def get_scanner(hass, config): @@ -55,18 +60,18 @@ class EEBrightBoxScanner(DeviceScanner): from eebrightbox import EEBrightBox with EEBrightBox(self.config) as ee_brightbox: - self.devices = {d['mac']: d for d in ee_brightbox.get_devices()} + self.devices = {d["mac"]: d for d in ee_brightbox.get_devices()} - macs = [d['mac'] for d in self.devices.values() if d['activity_ip']] + macs = [d["mac"] for d in self.devices.values() if d["activity_ip"]] - _LOGGER.debug('Scan devices %s', macs) + _LOGGER.debug("Scan devices %s", macs) return macs def get_device_name(self, device): """Get the name of a device from hostname.""" if device in self.devices: - return self.devices[device]['hostname'] or None + return self.devices[device]["hostname"] or None return None @@ -81,20 +86,20 @@ class EEBrightBoxScanner(DeviceScanner): - last_active """ port_map = { - 'wl1': 'wifi5Ghz', - 'wl0': 'wifi2.4Ghz', - 'eth0': 'eth0', - 'eth1': 'eth1', - 'eth2': 'eth2', - 'eth3': 'eth3', + "wl1": "wifi5Ghz", + "wl0": "wifi2.4Ghz", + "eth0": "eth0", + "eth1": "eth1", + "eth2": "eth2", + "eth3": "eth3", } if device in self.devices: return { - 'ip': self.devices[device]['ip'], - 'mac': self.devices[device]['mac'], - 'port': port_map[self.devices[device]['port']], - 'last_active': self.devices[device]['time_last_active'], + "ip": self.devices[device]["ip"], + "mac": self.devices[device]["mac"], + "port": port_map[self.devices[device]["port"]], + "last_active": self.devices[device]["time_last_active"], } return {} diff --git a/homeassistant/components/efergy/sensor.py b/homeassistant/components/efergy/sensor.py index eb8912abe18..53c89097a59 100644 --- a/homeassistant/components/efergy/sensor.py +++ b/homeassistant/components/efergy/sensor.py @@ -5,51 +5,54 @@ import requests import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import (CONF_CURRENCY, POWER_WATT, - ENERGY_KILO_WATT_HOUR) +from homeassistant.const import CONF_CURRENCY, POWER_WATT, ENERGY_KILO_WATT_HOUR import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -_RESOURCE = 'https://engage.efergy.com/mobile_proxy/' +_RESOURCE = "https://engage.efergy.com/mobile_proxy/" -CONF_APPTOKEN = 'app_token' -CONF_UTC_OFFSET = 'utc_offset' -CONF_MONITORED_VARIABLES = 'monitored_variables' -CONF_SENSOR_TYPE = 'type' +CONF_APPTOKEN = "app_token" +CONF_UTC_OFFSET = "utc_offset" +CONF_MONITORED_VARIABLES = "monitored_variables" +CONF_SENSOR_TYPE = "type" -CONF_PERIOD = 'period' +CONF_PERIOD = "period" -CONF_INSTANT = 'instant_readings' -CONF_AMOUNT = 'amount' -CONF_BUDGET = 'budget' -CONF_COST = 'cost' -CONF_CURRENT_VALUES = 'current_values' +CONF_INSTANT = "instant_readings" +CONF_AMOUNT = "amount" +CONF_BUDGET = "budget" +CONF_COST = "cost" +CONF_CURRENT_VALUES = "current_values" -DEFAULT_PERIOD = 'year' -DEFAULT_UTC_OFFSET = '0' +DEFAULT_PERIOD = "year" +DEFAULT_UTC_OFFSET = "0" SENSOR_TYPES = { - CONF_INSTANT: ['Energy Usage', POWER_WATT], - CONF_AMOUNT: ['Energy Consumed', ENERGY_KILO_WATT_HOUR], - CONF_BUDGET: ['Energy Budget', None], - CONF_COST: ['Energy Cost', None], - CONF_CURRENT_VALUES: ['Per-Device Usage', POWER_WATT] + CONF_INSTANT: ["Energy Usage", POWER_WATT], + CONF_AMOUNT: ["Energy Consumed", ENERGY_KILO_WATT_HOUR], + CONF_BUDGET: ["Energy Budget", None], + CONF_COST: ["Energy Cost", None], + CONF_CURRENT_VALUES: ["Per-Device Usage", POWER_WATT], } TYPES_SCHEMA = vol.In(SENSOR_TYPES) -SENSORS_SCHEMA = vol.Schema({ - vol.Required(CONF_SENSOR_TYPE): TYPES_SCHEMA, - vol.Optional(CONF_CURRENCY, default=''): cv.string, - vol.Optional(CONF_PERIOD, default=DEFAULT_PERIOD): cv.string, -}) +SENSORS_SCHEMA = vol.Schema( + { + vol.Required(CONF_SENSOR_TYPE): TYPES_SCHEMA, + vol.Optional(CONF_CURRENCY, default=""): cv.string, + vol.Optional(CONF_PERIOD, default=DEFAULT_PERIOD): cv.string, + } +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_APPTOKEN): cv.string, - vol.Optional(CONF_UTC_OFFSET, default=DEFAULT_UTC_OFFSET): cv.string, - vol.Required(CONF_MONITORED_VARIABLES): [SENSORS_SCHEMA] -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_APPTOKEN): cv.string, + vol.Optional(CONF_UTC_OFFSET, default=DEFAULT_UTC_OFFSET): cv.string, + vol.Required(CONF_MONITORED_VARIABLES): [SENSORS_SCHEMA], + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -60,17 +63,31 @@ def setup_platform(hass, config, add_entities, discovery_info=None): dev = [] for variable in config[CONF_MONITORED_VARIABLES]: if variable[CONF_SENSOR_TYPE] == CONF_CURRENT_VALUES: - url_string = '{}getCurrentValuesSummary?token={}'.format( - _RESOURCE, app_token) + url_string = "{}getCurrentValuesSummary?token={}".format( + _RESOURCE, app_token + ) response = requests.get(url_string, timeout=10) for sensor in response.json(): - sid = sensor['sid'] - dev.append(EfergySensor( - variable[CONF_SENSOR_TYPE], app_token, utc_offset, - variable[CONF_PERIOD], variable[CONF_CURRENCY], sid)) - dev.append(EfergySensor( - variable[CONF_SENSOR_TYPE], app_token, utc_offset, - variable[CONF_PERIOD], variable[CONF_CURRENCY])) + sid = sensor["sid"] + dev.append( + EfergySensor( + variable[CONF_SENSOR_TYPE], + app_token, + utc_offset, + variable[CONF_PERIOD], + variable[CONF_CURRENCY], + sid, + ) + ) + dev.append( + EfergySensor( + variable[CONF_SENSOR_TYPE], + app_token, + utc_offset, + variable[CONF_PERIOD], + variable[CONF_CURRENCY], + ) + ) add_entities(dev, True) @@ -78,12 +95,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class EfergySensor(Entity): """Implementation of an Efergy sensor.""" - def __init__(self, sensor_type, app_token, utc_offset, period, - currency, sid=None): + def __init__(self, sensor_type, app_token, utc_offset, period, currency, sid=None): """Initialize the sensor.""" self.sid = sid if sid: - self._name = 'efergy_{}'.format(sid) + self._name = "efergy_{}".format(sid) else: self._name = SENSOR_TYPES[sensor_type][0] self.type = sensor_type @@ -92,9 +108,8 @@ class EfergySensor(Entity): self._state = None self.period = period self.currency = currency - if self.type == 'cost': - self._unit_of_measurement = '{}/{}'.format( - self.currency, self.period) + if self.type == "cost": + self._unit_of_measurement = "{}/{}".format(self.currency, self.period) else: self._unit_of_measurement = SENSOR_TYPES[sensor_type][1] @@ -116,33 +131,34 @@ class EfergySensor(Entity): def update(self): """Get the Efergy monitor data from the web service.""" try: - if self.type == 'instant_readings': - url_string = '{}getInstant?token={}'.format( - _RESOURCE, self.app_token) + if self.type == "instant_readings": + url_string = "{}getInstant?token={}".format(_RESOURCE, self.app_token) response = requests.get(url_string, timeout=10) - self._state = response.json()['reading'] - elif self.type == 'amount': - url_string = '{}getEnergy?token={}&offset={}&period={}'.format( - _RESOURCE, self.app_token, self.utc_offset, self.period) + self._state = response.json()["reading"] + elif self.type == "amount": + url_string = "{}getEnergy?token={}&offset={}&period={}".format( + _RESOURCE, self.app_token, self.utc_offset, self.period + ) response = requests.get(url_string, timeout=10) - self._state = response.json()['sum'] - elif self.type == 'budget': - url_string = '{}getBudget?token={}'.format( - _RESOURCE, self.app_token) + self._state = response.json()["sum"] + elif self.type == "budget": + url_string = "{}getBudget?token={}".format(_RESOURCE, self.app_token) response = requests.get(url_string, timeout=10) - self._state = response.json()['status'] - elif self.type == 'cost': - url_string = '{}getCost?token={}&offset={}&period={}'.format( - _RESOURCE, self.app_token, self.utc_offset, self.period) + self._state = response.json()["status"] + elif self.type == "cost": + url_string = "{}getCost?token={}&offset={}&period={}".format( + _RESOURCE, self.app_token, self.utc_offset, self.period + ) response = requests.get(url_string, timeout=10) - self._state = response.json()['sum'] - elif self.type == 'current_values': - url_string = '{}getCurrentValuesSummary?token={}'.format( - _RESOURCE, self.app_token) + self._state = response.json()["sum"] + elif self.type == "current_values": + url_string = "{}getCurrentValuesSummary?token={}".format( + _RESOURCE, self.app_token + ) response = requests.get(url_string, timeout=10) for sensor in response.json(): - if self.sid == sensor['sid']: - measurement = next(iter(sensor['data'][0].values())) + if self.sid == sensor["sid"]: + measurement = next(iter(sensor["data"][0].values())) self._state = measurement else: self._state = None diff --git a/homeassistant/components/egardia/__init__.py b/homeassistant/components/egardia/__init__.py index cf0bb20f0fc..e17ea8f065d 100644 --- a/homeassistant/components/egardia/__init__.py +++ b/homeassistant/components/egardia/__init__.py @@ -5,67 +5,82 @@ import requests import voluptuous as vol from homeassistant.const import ( - CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_USERNAME, - EVENT_HOMEASSISTANT_STOP) + CONF_HOST, + CONF_NAME, + CONF_PASSWORD, + CONF_PORT, + CONF_USERNAME, + EVENT_HOMEASSISTANT_STOP, +) from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -ATTR_DISCOVER_DEVICES = 'egardia_sensor' +ATTR_DISCOVER_DEVICES = "egardia_sensor" -CONF_REPORT_SERVER_CODES = 'report_server_codes' -CONF_REPORT_SERVER_ENABLED = 'report_server_enabled' -CONF_REPORT_SERVER_PORT = 'report_server_port' -CONF_VERSION = 'version' +CONF_REPORT_SERVER_CODES = "report_server_codes" +CONF_REPORT_SERVER_ENABLED = "report_server_enabled" +CONF_REPORT_SERVER_PORT = "report_server_port" +CONF_VERSION = "version" -DEFAULT_NAME = 'Egardia' +DEFAULT_NAME = "Egardia" DEFAULT_PORT = 80 DEFAULT_REPORT_SERVER_ENABLED = False DEFAULT_REPORT_SERVER_PORT = 52010 -DEFAULT_VERSION = 'GATE-01' -DOMAIN = 'egardia' +DEFAULT_VERSION = "GATE-01" +DOMAIN = "egardia" -EGARDIA_DEVICE = 'egardiadevice' -EGARDIA_NAME = 'egardianame' -EGARDIA_REPORT_SERVER_CODES = 'egardia_rs_codes' -EGARDIA_REPORT_SERVER_ENABLED = 'egardia_rs_enabled' -EGARDIA_SERVER = 'egardia_server' +EGARDIA_DEVICE = "egardiadevice" +EGARDIA_NAME = "egardianame" +EGARDIA_REPORT_SERVER_CODES = "egardia_rs_codes" +EGARDIA_REPORT_SERVER_ENABLED = "egardia_rs_enabled" +EGARDIA_SERVER = "egardia_server" -NOTIFICATION_ID = 'egardia_notification' -NOTIFICATION_TITLE = 'Egardia' +NOTIFICATION_ID = "egardia_notification" +NOTIFICATION_TITLE = "Egardia" -REPORT_SERVER_CODES_IGNORE = 'ignore' +REPORT_SERVER_CODES_IGNORE = "ignore" -SERVER_CODE_SCHEMA = vol.Schema({ - vol.Optional('arm'): vol.All(cv.ensure_list_csv, [cv.string]), - vol.Optional('disarm'): vol.All(cv.ensure_list_csv, [cv.string]), - vol.Optional('armhome'): vol.All(cv.ensure_list_csv, [cv.string]), - vol.Optional('triggered'): vol.All(cv.ensure_list_csv, [cv.string]), - vol.Optional('ignore'): vol.All(cv.ensure_list_csv, [cv.string]), -}) +SERVER_CODE_SCHEMA = vol.Schema( + { + vol.Optional("arm"): vol.All(cv.ensure_list_csv, [cv.string]), + vol.Optional("disarm"): vol.All(cv.ensure_list_csv, [cv.string]), + vol.Optional("armhome"): vol.All(cv.ensure_list_csv, [cv.string]), + vol.Optional("triggered"): vol.All(cv.ensure_list_csv, [cv.string]), + vol.Optional("ignore"): vol.All(cv.ensure_list_csv, [cv.string]), + } +) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Optional(CONF_VERSION, default=DEFAULT_VERSION): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_REPORT_SERVER_CODES, default={}): SERVER_CODE_SCHEMA, - vol.Optional(CONF_REPORT_SERVER_ENABLED, - default=DEFAULT_REPORT_SERVER_ENABLED): cv.boolean, - vol.Optional(CONF_REPORT_SERVER_PORT, - default=DEFAULT_REPORT_SERVER_PORT): cv.port, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Optional(CONF_VERSION, default=DEFAULT_VERSION): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_REPORT_SERVER_CODES, default={}): SERVER_CODE_SCHEMA, + vol.Optional( + CONF_REPORT_SERVER_ENABLED, default=DEFAULT_REPORT_SERVER_ENABLED + ): cv.boolean, + vol.Optional( + CONF_REPORT_SERVER_PORT, default=DEFAULT_REPORT_SERVER_PORT + ): cv.port, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): """Set up the Egardia platform.""" from pythonegardia import egardiadevice from pythonegardia import egardiaserver + conf = config[DOMAIN] username = conf.get(CONF_USERNAME) password = conf.get(CONF_PASSWORD) @@ -76,10 +91,13 @@ def setup(hass, config): rs_port = conf.get(CONF_REPORT_SERVER_PORT) try: device = hass.data[EGARDIA_DEVICE] = egardiadevice.EgardiaDevice( - host, port, username, password, '', version) + host, port, username, password, "", version + ) except requests.exceptions.RequestException: - _LOGGER.error("An error occurred accessing your Egardia device. " - "Please check configuration") + _LOGGER.error( + "An error occurred accessing your Egardia device. " + "Please check configuration" + ) return False except egardiadevice.UnauthorizedError: _LOGGER.error("Unable to authorize. Wrong password or username") @@ -89,11 +107,12 @@ def setup(hass, config): _LOGGER.debug("Setting up EgardiaServer") try: if EGARDIA_SERVER not in hass.data: - server = egardiaserver.EgardiaServer('', rs_port) + server = egardiaserver.EgardiaServer("", rs_port) bound = server.bind() if not bound: - raise IOError("Binding error occurred while " + - "starting EgardiaServer.") + raise IOError( + "Binding error occurred while " + "starting EgardiaServer." + ) hass.data[EGARDIA_SERVER] = server server.start() @@ -105,16 +124,17 @@ def setup(hass, config): hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, handle_stop_event) except IOError: - _LOGGER.error( - "Binding error occurred while starting EgardiaServer") + _LOGGER.error("Binding error occurred while starting EgardiaServer") return False - discovery.load_platform(hass, 'alarm_control_panel', DOMAIN, - discovered=conf, hass_config=config) + discovery.load_platform( + hass, "alarm_control_panel", DOMAIN, discovered=conf, hass_config=config + ) # Get the sensors from the device and add those sensors = device.getsensors() - discovery.load_platform(hass, 'binary_sensor', DOMAIN, - {ATTR_DISCOVER_DEVICES: sensors}, config) + discovery.load_platform( + hass, "binary_sensor", DOMAIN, {ATTR_DISCOVER_DEVICES: sensors}, config + ) return True diff --git a/homeassistant/components/egardia/alarm_control_panel.py b/homeassistant/components/egardia/alarm_control_panel.py index ab48181f9ed..22a458ae9aa 100644 --- a/homeassistant/components/egardia/alarm_control_panel.py +++ b/homeassistant/components/egardia/alarm_control_panel.py @@ -5,24 +5,32 @@ import requests import homeassistant.components.alarm_control_panel as alarm from homeassistant.const import ( - STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, - STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED) + STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_HOME, + STATE_ALARM_ARMED_NIGHT, + STATE_ALARM_DISARMED, + STATE_ALARM_TRIGGERED, +) from . import ( - CONF_REPORT_SERVER_CODES, CONF_REPORT_SERVER_ENABLED, - CONF_REPORT_SERVER_PORT, EGARDIA_DEVICE, EGARDIA_SERVER, - REPORT_SERVER_CODES_IGNORE) + CONF_REPORT_SERVER_CODES, + CONF_REPORT_SERVER_ENABLED, + CONF_REPORT_SERVER_PORT, + EGARDIA_DEVICE, + EGARDIA_SERVER, + REPORT_SERVER_CODES_IGNORE, +) _LOGGER = logging.getLogger(__name__) STATES = { - 'ARM': STATE_ALARM_ARMED_AWAY, - 'DAY HOME': STATE_ALARM_ARMED_HOME, - 'DISARM': STATE_ALARM_DISARMED, - 'ARMHOME': STATE_ALARM_ARMED_HOME, - 'HOME': STATE_ALARM_ARMED_HOME, - 'NIGHT HOME': STATE_ALARM_ARMED_NIGHT, - 'TRIGGERED': STATE_ALARM_TRIGGERED + "ARM": STATE_ALARM_ARMED_AWAY, + "DAY HOME": STATE_ALARM_ARMED_HOME, + "DISARM": STATE_ALARM_DISARMED, + "ARMHOME": STATE_ALARM_ARMED_HOME, + "HOME": STATE_ALARM_ARMED_HOME, + "NIGHT HOME": STATE_ALARM_ARMED_NIGHT, + "TRIGGERED": STATE_ALARM_TRIGGERED, } @@ -31,11 +39,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if discovery_info is None: return device = EgardiaAlarm( - discovery_info['name'], + discovery_info["name"], hass.data[EGARDIA_DEVICE], discovery_info[CONF_REPORT_SERVER_ENABLED], discovery_info.get(CONF_REPORT_SERVER_CODES), - discovery_info[CONF_REPORT_SERVER_PORT]) + discovery_info[CONF_REPORT_SERVER_PORT], + ) add_entities([device], True) @@ -43,8 +52,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class EgardiaAlarm(alarm.AlarmControlPanel): """Representation of a Egardia alarm.""" - def __init__(self, name, egardiasystem, - rs_enabled=False, rs_codes=None, rs_port=52010): + def __init__( + self, name, egardiasystem, rs_enabled=False, rs_codes=None, rs_port=52010 + ): """Initialize the Egardia alarm.""" self._name = name self._egardiasystem = egardiasystem @@ -57,8 +67,7 @@ class EgardiaAlarm(alarm.AlarmControlPanel): """Add Egardiaserver callback if enabled.""" if self._rs_enabled: _LOGGER.debug("Registering callback to Egardiaserver") - self.hass.data[EGARDIA_SERVER].register_callback( - self.handle_status_event) + self.hass.data[EGARDIA_SERVER].register_callback(self.handle_status_event) @property def name(self): @@ -79,7 +88,7 @@ class EgardiaAlarm(alarm.AlarmControlPanel): def handle_status_event(self, event): """Handle the Egardia system status event.""" - statuscode = event.get('status') + statuscode = event.get("status") if statuscode is not None: status = self.lookupstatusfromcode(statuscode) self.parsestatus(status) @@ -87,10 +96,15 @@ class EgardiaAlarm(alarm.AlarmControlPanel): def lookupstatusfromcode(self, statuscode): """Look at the rs_codes and returns the status from the code.""" - status = next(( - status_group.upper() for status_group, codes - in self._rs_codes.items() for code in codes - if statuscode == code), 'UNKNOWN') + status = next( + ( + status_group.upper() + for status_group, codes in self._rs_codes.items() + for code in codes + if statuscode == code + ), + "UNKNOWN", + ) return status def parsestatus(self, status): @@ -115,21 +129,29 @@ class EgardiaAlarm(alarm.AlarmControlPanel): try: self._egardiasystem.alarm_disarm() except requests.exceptions.RequestException as err: - _LOGGER.error("Egardia device exception occurred when " - "sending disarm command: %s", err) + _LOGGER.error( + "Egardia device exception occurred when " "sending disarm command: %s", + err, + ) def alarm_arm_home(self, code=None): """Send arm home command.""" try: self._egardiasystem.alarm_arm_home() except requests.exceptions.RequestException as err: - _LOGGER.error("Egardia device exception occurred when " - "sending arm home command: %s", err) + _LOGGER.error( + "Egardia device exception occurred when " + "sending arm home command: %s", + err, + ) def alarm_arm_away(self, code=None): """Send arm away command.""" try: self._egardiasystem.alarm_arm_away() except requests.exceptions.RequestException as err: - _LOGGER.error("Egardia device exception occurred when " - "sending arm away command: %s", err) + _LOGGER.error( + "Egardia device exception occurred when " + "sending arm away command: %s", + err, + ) diff --git a/homeassistant/components/egardia/binary_sensor.py b/homeassistant/components/egardia/binary_sensor.py index 965b2dd1d55..157e4f1fe8c 100644 --- a/homeassistant/components/egardia/binary_sensor.py +++ b/homeassistant/components/egardia/binary_sensor.py @@ -9,17 +9,15 @@ from . import ATTR_DISCOVER_DEVICES, EGARDIA_DEVICE _LOGGER = logging.getLogger(__name__) EGARDIA_TYPE_TO_DEVICE_CLASS = { - 'IR Sensor': 'motion', - 'Door Contact': 'opening', - 'IR': 'motion', + "IR Sensor": "motion", + "Door Contact": "opening", + "IR": "motion", } -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Initialize the platform.""" - if (discovery_info is None or - discovery_info[ATTR_DISCOVER_DEVICES] is None): + if discovery_info is None or discovery_info[ATTR_DISCOVER_DEVICES] is None: return disc_info = discovery_info[ATTR_DISCOVER_DEVICES] @@ -27,14 +25,17 @@ async def async_setup_platform(hass, config, async_add_entities, async_add_entities( ( EgardiaBinarySensor( - sensor_id=disc_info[sensor]['id'], - name=disc_info[sensor]['name'], + sensor_id=disc_info[sensor]["id"], + name=disc_info[sensor]["name"], egardia_system=hass.data[EGARDIA_DEVICE], device_class=EGARDIA_TYPE_TO_DEVICE_CLASS.get( - disc_info[sensor]['type'], None) + disc_info[sensor]["type"], None + ), ) for sensor in disc_info - ), True) + ), + True, + ) class EgardiaBinarySensor(BinarySensorDevice): diff --git a/homeassistant/components/eight_sleep/__init__.py b/homeassistant/components/eight_sleep/__init__.py index d74218796a3..2479ea5440f 100644 --- a/homeassistant/components/eight_sleep/__init__.py +++ b/homeassistant/components/eight_sleep/__init__.py @@ -6,76 +6,86 @@ import voluptuous as vol from homeassistant.core import callback from homeassistant.const import ( - CONF_USERNAME, CONF_PASSWORD, CONF_SENSORS, CONF_BINARY_SENSORS, - ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_STOP) + CONF_USERNAME, + CONF_PASSWORD, + CONF_SENSORS, + CONF_BINARY_SENSORS, + ATTR_ENTITY_ID, + EVENT_HOMEASSISTANT_STOP, +) from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import ( - async_dispatcher_send, async_dispatcher_connect) + async_dispatcher_send, + async_dispatcher_connect, +) from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.util.dt import utcnow _LOGGER = logging.getLogger(__name__) -CONF_PARTNER = 'partner' +CONF_PARTNER = "partner" -DATA_EIGHT = 'eight_sleep' +DATA_EIGHT = "eight_sleep" DEFAULT_PARTNER = False -DOMAIN = 'eight_sleep' +DOMAIN = "eight_sleep" -HEAT_ENTITY = 'heat' -USER_ENTITY = 'user' +HEAT_ENTITY = "heat" +USER_ENTITY = "user" HEAT_SCAN_INTERVAL = timedelta(seconds=60) USER_SCAN_INTERVAL = timedelta(seconds=300) -SIGNAL_UPDATE_HEAT = 'eight_heat_update' -SIGNAL_UPDATE_USER = 'eight_user_update' +SIGNAL_UPDATE_HEAT = "eight_heat_update" +SIGNAL_UPDATE_USER = "eight_user_update" NAME_MAP = { - 'left_current_sleep': 'Left Sleep Session', - 'left_last_sleep': 'Left Previous Sleep Session', - 'left_bed_state': 'Left Bed State', - 'left_presence': 'Left Bed Presence', - 'left_bed_temp': 'Left Bed Temperature', - 'left_sleep_stage': 'Left Sleep Stage', - 'right_current_sleep': 'Right Sleep Session', - 'right_last_sleep': 'Right Previous Sleep Session', - 'right_bed_state': 'Right Bed State', - 'right_presence': 'Right Bed Presence', - 'right_bed_temp': 'Right Bed Temperature', - 'right_sleep_stage': 'Right Sleep Stage', - 'room_temp': 'Room Temperature', + "left_current_sleep": "Left Sleep Session", + "left_last_sleep": "Left Previous Sleep Session", + "left_bed_state": "Left Bed State", + "left_presence": "Left Bed Presence", + "left_bed_temp": "Left Bed Temperature", + "left_sleep_stage": "Left Sleep Stage", + "right_current_sleep": "Right Sleep Session", + "right_last_sleep": "Right Previous Sleep Session", + "right_bed_state": "Right Bed State", + "right_presence": "Right Bed Presence", + "right_bed_temp": "Right Bed Temperature", + "right_sleep_stage": "Right Sleep Stage", + "room_temp": "Room Temperature", } -SENSORS = ['current_sleep', - 'last_sleep', - 'bed_state', - 'bed_temp', - 'sleep_stage'] +SENSORS = ["current_sleep", "last_sleep", "bed_state", "bed_temp", "sleep_stage"] -SERVICE_HEAT_SET = 'heat_set' +SERVICE_HEAT_SET = "heat_set" -ATTR_TARGET_HEAT = 'target' -ATTR_HEAT_DURATION = 'duration' +ATTR_TARGET_HEAT = "target" +ATTR_HEAT_DURATION = "duration" VALID_TARGET_HEAT = vol.All(vol.Coerce(int), vol.Clamp(min=0, max=100)) VALID_DURATION = vol.All(vol.Coerce(int), vol.Clamp(min=0, max=28800)) -SERVICE_EIGHT_SCHEMA = vol.Schema({ - ATTR_ENTITY_ID: cv.entity_ids, - ATTR_TARGET_HEAT: VALID_TARGET_HEAT, - ATTR_HEAT_DURATION: VALID_DURATION, - }) +SERVICE_EIGHT_SCHEMA = vol.Schema( + { + ATTR_ENTITY_ID: cv.entity_ids, + ATTR_TARGET_HEAT: VALID_TARGET_HEAT, + ATTR_HEAT_DURATION: VALID_DURATION, + } +) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_PARTNER, default=DEFAULT_PARTNER): cv.boolean, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_PARTNER, default=DEFAULT_PARTNER): cv.boolean, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) async def async_setup(hass, config): @@ -88,7 +98,7 @@ async def async_setup(hass, config): partner = conf.get(CONF_PARTNER) if hass.config.time_zone is None: - _LOGGER.error('Timezone is not set in Home Assistant.') + _LOGGER.error("Timezone is not set in Home Assistant.") return False timezone = hass.config.time_zone @@ -109,7 +119,8 @@ async def async_setup(hass, config): async_dispatcher_send(hass, SIGNAL_UPDATE_HEAT) async_track_point_in_utc_time( - hass, async_update_heat_data, utcnow() + HEAT_SCAN_INTERVAL) + hass, async_update_heat_data, utcnow() + HEAT_SCAN_INTERVAL + ) async def async_update_user_data(now): """Update user data from eight in USER_SCAN_INTERVAL.""" @@ -117,7 +128,8 @@ async def async_setup(hass, config): async_dispatcher_send(hass, SIGNAL_UPDATE_USER) async_track_point_in_utc_time( - hass, async_update_user_data, utcnow() + USER_SCAN_INTERVAL) + hass, async_update_user_data, utcnow() + USER_SCAN_INTERVAL + ) await async_update_heat_data(None) await async_update_user_data(None) @@ -129,22 +141,24 @@ async def async_setup(hass, config): for user in eight.users: obj = eight.users[user] for sensor in SENSORS: - sensors.append('{}_{}'.format(obj.side, sensor)) - binary_sensors.append('{}_presence'.format(obj.side)) - sensors.append('room_temp') + sensors.append("{}_{}".format(obj.side, sensor)) + binary_sensors.append("{}_presence".format(obj.side)) + sensors.append("room_temp") else: # No users, cannot continue return False - hass.async_create_task(discovery.async_load_platform( - hass, 'sensor', DOMAIN, { - CONF_SENSORS: sensors, - }, config)) + hass.async_create_task( + discovery.async_load_platform( + hass, "sensor", DOMAIN, {CONF_SENSORS: sensors}, config + ) + ) - hass.async_create_task(discovery.async_load_platform( - hass, 'binary_sensor', DOMAIN, { - CONF_BINARY_SENSORS: binary_sensors, - }, config)) + hass.async_create_task( + discovery.async_load_platform( + hass, "binary_sensor", DOMAIN, {CONF_BINARY_SENSORS: binary_sensors}, config + ) + ) async def async_service_handler(service): """Handle eight sleep service calls.""" @@ -155,7 +169,7 @@ async def async_setup(hass, config): duration = params.pop(ATTR_HEAT_DURATION, 0) for sens in sensor: - side = sens.split('_')[1] + side = sens.split("_")[1] userid = eight.fetch_userid(side) usrobj = eight.users[userid] await usrobj.set_heating_level(target, duration) @@ -164,8 +178,8 @@ async def async_setup(hass, config): # Register services hass.services.async_register( - DOMAIN, SERVICE_HEAT_SET, async_service_handler, - schema=SERVICE_EIGHT_SCHEMA) + DOMAIN, SERVICE_HEAT_SET, async_service_handler, schema=SERVICE_EIGHT_SCHEMA + ) async def stop_eight(event): """Handle stopping eight api session.""" @@ -185,13 +199,13 @@ class EightSleepUserEntity(Entity): async def async_added_to_hass(self): """Register update dispatcher.""" + @callback def async_eight_user_update(): """Update callback.""" self.async_schedule_update_ha_state(True) - async_dispatcher_connect( - self.hass, SIGNAL_UPDATE_USER, async_eight_user_update) + async_dispatcher_connect(self.hass, SIGNAL_UPDATE_USER, async_eight_user_update) @property def should_poll(self): @@ -208,13 +222,13 @@ class EightSleepHeatEntity(Entity): async def async_added_to_hass(self): """Register update dispatcher.""" + @callback def async_eight_heat_update(): """Update callback.""" self.async_schedule_update_ha_state(True) - async_dispatcher_connect( - self.hass, SIGNAL_UPDATE_HEAT, async_eight_heat_update) + async_dispatcher_connect(self.hass, SIGNAL_UPDATE_HEAT, async_eight_heat_update) @property def should_poll(self): diff --git a/homeassistant/components/eight_sleep/binary_sensor.py b/homeassistant/components/eight_sleep/binary_sensor.py index b3842106723..7d7ebecafee 100644 --- a/homeassistant/components/eight_sleep/binary_sensor.py +++ b/homeassistant/components/eight_sleep/binary_sensor.py @@ -8,13 +8,12 @@ from . import CONF_BINARY_SENSORS, DATA_EIGHT, NAME_MAP, EightSleepHeatEntity _LOGGER = logging.getLogger(__name__) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the eight sleep binary sensor.""" if discovery_info is None: return - name = 'Eight' + name = "Eight" sensors = discovery_info[CONF_BINARY_SENSORS] eight = hass.data[DATA_EIGHT] @@ -35,15 +34,19 @@ class EightHeatSensor(EightSleepHeatEntity, BinarySensorDevice): self._sensor = sensor self._mapped_name = NAME_MAP.get(self._sensor, self._sensor) - self._name = '{} {}'.format(name, self._mapped_name) + self._name = "{} {}".format(name, self._mapped_name) self._state = None - self._side = self._sensor.split('_')[0] + self._side = self._sensor.split("_")[0] self._userid = self._eight.fetch_userid(self._side) self._usrobj = self._eight.users[self._userid] - _LOGGER.debug("Presence Sensor: %s, Side: %s, User: %s", - self._sensor, self._side, self._userid) + _LOGGER.debug( + "Presence Sensor: %s, Side: %s, User: %s", + self._sensor, + self._side, + self._userid, + ) @property def name(self): diff --git a/homeassistant/components/eight_sleep/sensor.py b/homeassistant/components/eight_sleep/sensor.py index b7b0f588155..afc06986ea6 100644 --- a/homeassistant/components/eight_sleep/sensor.py +++ b/homeassistant/components/eight_sleep/sensor.py @@ -2,53 +2,56 @@ import logging from . import ( - CONF_SENSORS, DATA_EIGHT, NAME_MAP, EightSleepHeatEntity, - EightSleepUserEntity) + CONF_SENSORS, + DATA_EIGHT, + NAME_MAP, + EightSleepHeatEntity, + EightSleepUserEntity, +) -ATTR_ROOM_TEMP = 'Room Temperature' -ATTR_AVG_ROOM_TEMP = 'Average Room Temperature' -ATTR_BED_TEMP = 'Bed Temperature' -ATTR_AVG_BED_TEMP = 'Average Bed Temperature' -ATTR_RESP_RATE = 'Respiratory Rate' -ATTR_AVG_RESP_RATE = 'Average Respiratory Rate' -ATTR_HEART_RATE = 'Heart Rate' -ATTR_AVG_HEART_RATE = 'Average Heart Rate' -ATTR_SLEEP_DUR = 'Time Slept' -ATTR_LIGHT_PERC = 'Light Sleep %' -ATTR_DEEP_PERC = 'Deep Sleep %' -ATTR_REM_PERC = 'REM Sleep %' -ATTR_TNT = 'Tosses & Turns' -ATTR_SLEEP_STAGE = 'Sleep Stage' -ATTR_TARGET_HEAT = 'Target Heating Level' -ATTR_ACTIVE_HEAT = 'Heating Active' -ATTR_DURATION_HEAT = 'Heating Time Remaining' -ATTR_PROCESSING = 'Processing' -ATTR_SESSION_START = 'Session Start' +ATTR_ROOM_TEMP = "Room Temperature" +ATTR_AVG_ROOM_TEMP = "Average Room Temperature" +ATTR_BED_TEMP = "Bed Temperature" +ATTR_AVG_BED_TEMP = "Average Bed Temperature" +ATTR_RESP_RATE = "Respiratory Rate" +ATTR_AVG_RESP_RATE = "Average Respiratory Rate" +ATTR_HEART_RATE = "Heart Rate" +ATTR_AVG_HEART_RATE = "Average Heart Rate" +ATTR_SLEEP_DUR = "Time Slept" +ATTR_LIGHT_PERC = "Light Sleep %" +ATTR_DEEP_PERC = "Deep Sleep %" +ATTR_REM_PERC = "REM Sleep %" +ATTR_TNT = "Tosses & Turns" +ATTR_SLEEP_STAGE = "Sleep Stage" +ATTR_TARGET_HEAT = "Target Heating Level" +ATTR_ACTIVE_HEAT = "Heating Active" +ATTR_DURATION_HEAT = "Heating Time Remaining" +ATTR_PROCESSING = "Processing" +ATTR_SESSION_START = "Session Start" _LOGGER = logging.getLogger(__name__) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the eight sleep sensors.""" if discovery_info is None: return - name = 'Eight' + name = "Eight" sensors = discovery_info[CONF_SENSORS] eight = hass.data[DATA_EIGHT] if hass.config.units.is_metric: - units = 'si' + units = "si" else: - units = 'us' + units = "us" all_sensors = [] for sensor in sensors: - if 'bed_state' in sensor: + if "bed_state" in sensor: all_sensors.append(EightHeatSensor(name, eight, sensor)) - elif 'room_temp' in sensor: + elif "room_temp" in sensor: all_sensors.append(EightRoomSensor(name, eight, sensor, units)) else: all_sensors.append(EightUserSensor(name, eight, sensor, units)) @@ -65,15 +68,19 @@ class EightHeatSensor(EightSleepHeatEntity): self._sensor = sensor self._mapped_name = NAME_MAP.get(self._sensor, self._sensor) - self._name = '{} {}'.format(name, self._mapped_name) + self._name = "{} {}".format(name, self._mapped_name) self._state = None - self._side = self._sensor.split('_')[0] + self._side = self._sensor.split("_")[0] self._userid = self._eight.fetch_userid(self._side) self._usrobj = self._eight.users[self._userid] - _LOGGER.debug("Heat Sensor: %s, Side: %s, User: %s", - self._sensor, self._side, self._userid) + _LOGGER.debug( + "Heat Sensor: %s, Side: %s, User: %s", + self._sensor, + self._side, + self._userid, + ) @property def name(self): @@ -88,7 +95,7 @@ class EightHeatSensor(EightSleepHeatEntity): @property def unit_of_measurement(self): """Return the unit the value is expressed in.""" - return '%' + return "%" async def async_update(self): """Retrieve latest state.""" @@ -113,19 +120,23 @@ class EightUserSensor(EightSleepUserEntity): super().__init__(eight) self._sensor = sensor - self._sensor_root = self._sensor.split('_', 1)[1] + self._sensor_root = self._sensor.split("_", 1)[1] self._mapped_name = NAME_MAP.get(self._sensor, self._sensor) - self._name = '{} {}'.format(name, self._mapped_name) + self._name = "{} {}".format(name, self._mapped_name) self._state = None self._attr = None self._units = units - self._side = self._sensor.split('_', 1)[0] + self._side = self._sensor.split("_", 1)[0] self._userid = self._eight.fetch_userid(self._side) self._usrobj = self._eight.users[self._userid] - _LOGGER.debug("User Sensor: %s, Side: %s, User: %s", - self._sensor, self._side, self._userid) + _LOGGER.debug( + "User Sensor: %s, Side: %s, User: %s", + self._sensor, + self._side, + self._userid, + ) @property def name(self): @@ -140,40 +151,40 @@ class EightUserSensor(EightSleepUserEntity): @property def unit_of_measurement(self): """Return the unit the value is expressed in.""" - if 'current_sleep' in self._sensor or 'last_sleep' in self._sensor: - return 'Score' - if 'bed_temp' in self._sensor: - if self._units == 'si': - return '°C' - return '°F' + if "current_sleep" in self._sensor or "last_sleep" in self._sensor: + return "Score" + if "bed_temp" in self._sensor: + if self._units == "si": + return "°C" + return "°F" return None @property def icon(self): """Icon to use in the frontend, if any.""" - if 'bed_temp' in self._sensor: - return 'mdi:thermometer' + if "bed_temp" in self._sensor: + return "mdi:thermometer" async def async_update(self): """Retrieve latest state.""" _LOGGER.debug("Updating User sensor: %s", self._sensor) - if 'current' in self._sensor: + if "current" in self._sensor: self._state = self._usrobj.current_sleep_score self._attr = self._usrobj.current_values - elif 'last' in self._sensor: + elif "last" in self._sensor: self._state = self._usrobj.last_sleep_score self._attr = self._usrobj.last_values - elif 'bed_temp' in self._sensor: - temp = self._usrobj.current_values['bed_temp'] + elif "bed_temp" in self._sensor: + temp = self._usrobj.current_values["bed_temp"] try: - if self._units == 'si': + if self._units == "si": self._state = round(temp, 2) else: - self._state = round((temp*1.8)+32, 2) + self._state = round((temp * 1.8) + 32, 2) except TypeError: self._state = None - elif 'sleep_stage' in self._sensor: - self._state = self._usrobj.current_values['stage'] + elif "sleep_stage" in self._sensor: + self._state = self._usrobj.current_values["stage"] @property def device_state_attributes(self): @@ -182,56 +193,59 @@ class EightUserSensor(EightSleepUserEntity): # Skip attributes if sensor type doesn't support return None - state_attr = {ATTR_SESSION_START: self._attr['date']} - state_attr[ATTR_TNT] = self._attr['tnt'] - state_attr[ATTR_PROCESSING] = self._attr['processing'] + state_attr = {ATTR_SESSION_START: self._attr["date"]} + state_attr[ATTR_TNT] = self._attr["tnt"] + state_attr[ATTR_PROCESSING] = self._attr["processing"] - sleep_time = sum(self._attr['breakdown'].values()) - \ - self._attr['breakdown']['awake'] + sleep_time = ( + sum(self._attr["breakdown"].values()) - self._attr["breakdown"]["awake"] + ) state_attr[ATTR_SLEEP_DUR] = sleep_time try: - state_attr[ATTR_LIGHT_PERC] = round(( - self._attr['breakdown']['light'] / sleep_time) * 100, 2) + state_attr[ATTR_LIGHT_PERC] = round( + (self._attr["breakdown"]["light"] / sleep_time) * 100, 2 + ) except ZeroDivisionError: state_attr[ATTR_LIGHT_PERC] = 0 try: - state_attr[ATTR_DEEP_PERC] = round(( - self._attr['breakdown']['deep'] / sleep_time) * 100, 2) + state_attr[ATTR_DEEP_PERC] = round( + (self._attr["breakdown"]["deep"] / sleep_time) * 100, 2 + ) except ZeroDivisionError: state_attr[ATTR_DEEP_PERC] = 0 try: - state_attr[ATTR_REM_PERC] = round(( - self._attr['breakdown']['rem'] / sleep_time) * 100, 2) + state_attr[ATTR_REM_PERC] = round( + (self._attr["breakdown"]["rem"] / sleep_time) * 100, 2 + ) except ZeroDivisionError: state_attr[ATTR_REM_PERC] = 0 try: - if self._units == 'si': - room_temp = round(self._attr['room_temp'], 2) + if self._units == "si": + room_temp = round(self._attr["room_temp"], 2) else: - room_temp = round((self._attr['room_temp']*1.8)+32, 2) + room_temp = round((self._attr["room_temp"] * 1.8) + 32, 2) except TypeError: room_temp = None try: - if self._units == 'si': - bed_temp = round(self._attr['bed_temp'], 2) + if self._units == "si": + bed_temp = round(self._attr["bed_temp"], 2) else: - bed_temp = round((self._attr['bed_temp']*1.8)+32, 2) + bed_temp = round((self._attr["bed_temp"] * 1.8) + 32, 2) except TypeError: bed_temp = None - if 'current' in self._sensor_root: - state_attr[ATTR_RESP_RATE] = round(self._attr['resp_rate'], 2) - state_attr[ATTR_HEART_RATE] = round(self._attr['heart_rate'], 2) - state_attr[ATTR_SLEEP_STAGE] = self._attr['stage'] + if "current" in self._sensor_root: + state_attr[ATTR_RESP_RATE] = round(self._attr["resp_rate"], 2) + state_attr[ATTR_HEART_RATE] = round(self._attr["heart_rate"], 2) + state_attr[ATTR_SLEEP_STAGE] = self._attr["stage"] state_attr[ATTR_ROOM_TEMP] = room_temp state_attr[ATTR_BED_TEMP] = bed_temp - elif 'last' in self._sensor_root: - state_attr[ATTR_AVG_RESP_RATE] = round(self._attr['resp_rate'], 2) - state_attr[ATTR_AVG_HEART_RATE] = round( - self._attr['heart_rate'], 2) + elif "last" in self._sensor_root: + state_attr[ATTR_AVG_RESP_RATE] = round(self._attr["resp_rate"], 2) + state_attr[ATTR_AVG_HEART_RATE] = round(self._attr["heart_rate"], 2) state_attr[ATTR_AVG_ROOM_TEMP] = room_temp state_attr[ATTR_AVG_BED_TEMP] = bed_temp @@ -247,7 +261,7 @@ class EightRoomSensor(EightSleepUserEntity): self._sensor = sensor self._mapped_name = NAME_MAP.get(self._sensor, self._sensor) - self._name = '{} {}'.format(name, self._mapped_name) + self._name = "{} {}".format(name, self._mapped_name) self._state = None self._attr = None self._units = units @@ -267,21 +281,21 @@ class EightRoomSensor(EightSleepUserEntity): _LOGGER.debug("Updating Room sensor: %s", self._sensor) temp = self._eight.room_temperature() try: - if self._units == 'si': + if self._units == "si": self._state = round(temp, 2) else: - self._state = round((temp*1.8)+32, 2) + self._state = round((temp * 1.8) + 32, 2) except TypeError: self._state = None @property def unit_of_measurement(self): """Return the unit the value is expressed in.""" - if self._units == 'si': - return '°C' - return '°F' + if self._units == "si": + return "°C" + return "°F" @property def icon(self): """Icon to use in the frontend, if any.""" - return 'mdi:thermometer' + return "mdi:thermometer" diff --git a/homeassistant/components/eliqonline/sensor.py b/homeassistant/components/eliqonline/sensor.py index 03d6ad89591..1f21263a4d6 100644 --- a/homeassistant/components/eliqonline/sensor.py +++ b/homeassistant/components/eliqonline/sensor.py @@ -6,32 +6,33 @@ import asyncio import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import (CONF_ACCESS_TOKEN, CONF_NAME, POWER_WATT) +from homeassistant.const import CONF_ACCESS_TOKEN, CONF_NAME, POWER_WATT from homeassistant.helpers.entity import Entity import homeassistant.helpers.config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession _LOGGER = logging.getLogger(__name__) -CONF_CHANNEL_ID = 'channel_id' +CONF_CHANNEL_ID = "channel_id" -DEFAULT_NAME = 'ELIQ Online' +DEFAULT_NAME = "ELIQ Online" -ICON = 'mdi:gauge' +ICON = "mdi:gauge" SCAN_INTERVAL = timedelta(seconds=60) UNIT_OF_MEASUREMENT = POWER_WATT -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_ACCESS_TOKEN): cv.string, - vol.Required(CONF_CHANNEL_ID): cv.positive_int, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_ACCESS_TOKEN): cv.string, + vol.Required(CONF_CHANNEL_ID): cv.positive_int, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + } +) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the ELIQ Online sensor.""" import eliqonline @@ -40,8 +41,7 @@ async def async_setup_platform(hass, config, async_add_entities, channel_id = config.get(CONF_CHANNEL_ID) session = async_get_clientsession(hass) - api = eliqonline.API(session=session, - access_token=access_token) + api = eliqonline.API(session=session, access_token=access_token) try: _LOGGER.debug("Probing for access to ELIQ Online API") @@ -92,5 +92,4 @@ class EliqSensor(Entity): except KeyError: _LOGGER.warning("Invalid response from ELIQ Online API") except (OSError, asyncio.TimeoutError) as error: - _LOGGER.warning("Could not connect to the ELIQ Online API: %s", - error) + _LOGGER.warning("Could not connect to the ELIQ Online API: %s", error) diff --git a/homeassistant/components/elkm1/__init__.py b/homeassistant/components/elkm1/__init__.py index f933228cc2f..e26749e6f6b 100644 --- a/homeassistant/components/elkm1/__init__.py +++ b/homeassistant/components/elkm1/__init__.py @@ -4,62 +4,75 @@ import re import voluptuous as vol from homeassistant.const import ( - CONF_EXCLUDE, CONF_HOST, CONF_INCLUDE, CONF_PASSWORD, - CONF_TEMPERATURE_UNIT, CONF_USERNAME) + CONF_EXCLUDE, + CONF_HOST, + CONF_INCLUDE, + CONF_PASSWORD, + CONF_TEMPERATURE_UNIT, + CONF_USERNAME, +) from homeassistant.core import HomeAssistant, callback # noqa from homeassistant.helpers import config_validation as cv from homeassistant.helpers import discovery from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import ConfigType # noqa -DOMAIN = 'elkm1' +DOMAIN = "elkm1" -CONF_AREA = 'area' -CONF_COUNTER = 'counter' -CONF_ENABLED = 'enabled' -CONF_KEYPAD = 'keypad' -CONF_OUTPUT = 'output' -CONF_PLC = 'plc' -CONF_SETTING = 'setting' -CONF_TASK = 'task' -CONF_THERMOSTAT = 'thermostat' -CONF_ZONE = 'zone' -CONF_PREFIX = 'prefix' +CONF_AREA = "area" +CONF_COUNTER = "counter" +CONF_ENABLED = "enabled" +CONF_KEYPAD = "keypad" +CONF_OUTPUT = "output" +CONF_PLC = "plc" +CONF_SETTING = "setting" +CONF_TASK = "task" +CONF_THERMOSTAT = "thermostat" +CONF_ZONE = "zone" +CONF_PREFIX = "prefix" _LOGGER = logging.getLogger(__name__) -SUPPORTED_DOMAINS = ['alarm_control_panel', 'climate', 'light', 'scene', - 'sensor', 'switch'] +SUPPORTED_DOMAINS = [ + "alarm_control_panel", + "climate", + "light", + "scene", + "sensor", + "switch", +] -SPEAK_SERVICE_SCHEMA = vol.Schema({ - vol.Required('number'): - vol.All(vol.Coerce(int), vol.Range(min=0, max=999)), - vol.Optional('prefix', default=''): cv.string -}) +SPEAK_SERVICE_SCHEMA = vol.Schema( + { + vol.Required("number"): vol.All(vol.Coerce(int), vol.Range(min=0, max=999)), + vol.Optional("prefix", default=""): cv.string, + } +) def _host_validator(config): """Validate that a host is properly configured.""" - if config[CONF_HOST].startswith('elks://'): + if config[CONF_HOST].startswith("elks://"): if CONF_USERNAME not in config or CONF_PASSWORD not in config: raise vol.Invalid("Specify username and password for elks://") - elif not config[CONF_HOST].startswith('elk://') and not config[ - CONF_HOST].startswith('serial://'): + elif not config[CONF_HOST].startswith("elk://") and not config[ + CONF_HOST + ].startswith("serial://"): raise vol.Invalid("Invalid host URL") return config def _elk_range_validator(rng): def _housecode_to_int(val): - match = re.search(r'^([a-p])(0[1-9]|1[0-6]|[1-9])$', val.lower()) + match = re.search(r"^([a-p])(0[1-9]|1[0-6]|[1-9])$", val.lower()) if match: - return (ord(match.group(1)) - ord('a')) * 16 + int(match.group(2)) + return (ord(match.group(1)) - ord("a")) * 16 + int(match.group(2)) raise vol.Invalid("Invalid range") def _elk_value(val): return int(val) if val.isdigit() else _housecode_to_int(val) - vals = [s.strip() for s in str(rng).split('-')] + vals = [s.strip() for s in str(rng).split("-")] start = _elk_value(vals[0]) end = start if len(vals) == 1 else _elk_value(vals[1]) return (start, end) @@ -76,34 +89,38 @@ def _has_all_unique_prefixes(value): return value -DEVICE_SCHEMA_SUBDOMAIN = vol.Schema({ - vol.Optional(CONF_ENABLED, default=True): cv.boolean, - vol.Optional(CONF_INCLUDE, default=[]): [_elk_range_validator], - vol.Optional(CONF_EXCLUDE, default=[]): [_elk_range_validator], -}) +DEVICE_SCHEMA_SUBDOMAIN = vol.Schema( + { + vol.Optional(CONF_ENABLED, default=True): cv.boolean, + vol.Optional(CONF_INCLUDE, default=[]): [_elk_range_validator], + vol.Optional(CONF_EXCLUDE, default=[]): [_elk_range_validator], + } +) -DEVICE_SCHEMA = vol.Schema({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_PREFIX, default=''): vol.All(cv.string, vol.Lower), - vol.Optional(CONF_USERNAME, default=''): cv.string, - vol.Optional(CONF_PASSWORD, default=''): cv.string, - vol.Optional(CONF_TEMPERATURE_UNIT, default='F'): - cv.temperature_unit, - vol.Optional(CONF_AREA, default={}): DEVICE_SCHEMA_SUBDOMAIN, - vol.Optional(CONF_COUNTER, default={}): DEVICE_SCHEMA_SUBDOMAIN, - vol.Optional(CONF_KEYPAD, default={}): DEVICE_SCHEMA_SUBDOMAIN, - vol.Optional(CONF_OUTPUT, default={}): DEVICE_SCHEMA_SUBDOMAIN, - vol.Optional(CONF_PLC, default={}): DEVICE_SCHEMA_SUBDOMAIN, - vol.Optional(CONF_SETTING, default={}): DEVICE_SCHEMA_SUBDOMAIN, - vol.Optional(CONF_TASK, default={}): DEVICE_SCHEMA_SUBDOMAIN, - vol.Optional(CONF_THERMOSTAT, default={}): DEVICE_SCHEMA_SUBDOMAIN, - vol.Optional(CONF_ZONE, default={}): DEVICE_SCHEMA_SUBDOMAIN, -}, _host_validator) +DEVICE_SCHEMA = vol.Schema( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_PREFIX, default=""): vol.All(cv.string, vol.Lower), + vol.Optional(CONF_USERNAME, default=""): cv.string, + vol.Optional(CONF_PASSWORD, default=""): cv.string, + vol.Optional(CONF_TEMPERATURE_UNIT, default="F"): cv.temperature_unit, + vol.Optional(CONF_AREA, default={}): DEVICE_SCHEMA_SUBDOMAIN, + vol.Optional(CONF_COUNTER, default={}): DEVICE_SCHEMA_SUBDOMAIN, + vol.Optional(CONF_KEYPAD, default={}): DEVICE_SCHEMA_SUBDOMAIN, + vol.Optional(CONF_OUTPUT, default={}): DEVICE_SCHEMA_SUBDOMAIN, + vol.Optional(CONF_PLC, default={}): DEVICE_SCHEMA_SUBDOMAIN, + vol.Optional(CONF_SETTING, default={}): DEVICE_SCHEMA_SUBDOMAIN, + vol.Optional(CONF_TASK, default={}): DEVICE_SCHEMA_SUBDOMAIN, + vol.Optional(CONF_THERMOSTAT, default={}): DEVICE_SCHEMA_SUBDOMAIN, + vol.Optional(CONF_ZONE, default={}): DEVICE_SCHEMA_SUBDOMAIN, + }, + _host_validator, +) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.All(cv.ensure_list, [DEVICE_SCHEMA], - _has_all_unique_prefixes) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + {DOMAIN: vol.All(cv.ensure_list, [DEVICE_SCHEMA], _has_all_unique_prefixes)}, + extra=vol.ALLOW_EXTRA, +) async def async_setup(hass: HomeAssistant, hass_config: ConfigType) -> bool: @@ -130,82 +147,87 @@ async def async_setup(hass: HomeAssistant, hass_config: ConfigType) -> bool: for rng in ranges: if not rng[0] <= rng[1] <= len(values): raise vol.Invalid("Invalid range {}".format(rng)) - values[rng[0]-1:rng[1]] = [set_to] * (rng[1] - rng[0] + 1) + values[rng[0] - 1 : rng[1]] = [set_to] * (rng[1] - rng[0] + 1) for index, conf in enumerate(hass_config[DOMAIN]): - _LOGGER.debug("Setting up elkm1 #%d - %s", index, conf['host']) + _LOGGER.debug("Setting up elkm1 #%d - %s", index, conf["host"]) - config = {'temperature_unit': conf[CONF_TEMPERATURE_UNIT]} - config['panel'] = {'enabled': True, 'included': [True]} + config = {"temperature_unit": conf[CONF_TEMPERATURE_UNIT]} + config["panel"] = {"enabled": True, "included": [True]} for item, max_ in configs.items(): - config[item] = {'enabled': conf[item][CONF_ENABLED], - 'included': [not conf[item]['include']] * max_} + config[item] = { + "enabled": conf[item][CONF_ENABLED], + "included": [not conf[item]["include"]] * max_, + } try: - _included(conf[item]['include'], True, - config[item]['included']) - _included(conf[item]['exclude'], False, - config[item]['included']) + _included(conf[item]["include"], True, config[item]["included"]) + _included(conf[item]["exclude"], False, config[item]["included"]) except (ValueError, vol.Invalid) as err: _LOGGER.error("Config item: %s; %s", item, err) return False prefix = conf[CONF_PREFIX] - elk = elkm1.Elk({'url': conf[CONF_HOST], 'userid': - conf[CONF_USERNAME], - 'password': conf[CONF_PASSWORD]}) + elk = elkm1.Elk( + { + "url": conf[CONF_HOST], + "userid": conf[CONF_USERNAME], + "password": conf[CONF_PASSWORD], + } + ) elk.connect() devices[prefix] = elk - elk_datas[prefix] = {'elk': elk, - 'prefix': prefix, - 'config': config, - 'keypads': {}} + elk_datas[prefix] = { + "elk": elk, + "prefix": prefix, + "config": config, + "keypads": {}, + } _create_elk_services(hass, devices) hass.data[DOMAIN] = elk_datas for component in SUPPORTED_DOMAINS: hass.async_create_task( - discovery.async_load_platform(hass, component, DOMAIN, {}, - hass_config)) + discovery.async_load_platform(hass, component, DOMAIN, {}, hass_config) + ) return True def _create_elk_services(hass, elks): def _speak_word_service(service): - prefix = service.data['prefix'] + prefix = service.data["prefix"] elk = elks.get(prefix) if elk is None: - _LOGGER.error("No elk m1 with prefix for speak_word: '%s'", - prefix) + _LOGGER.error("No elk m1 with prefix for speak_word: '%s'", prefix) return - elk.panel.speak_word(service.data['number']) + elk.panel.speak_word(service.data["number"]) def _speak_phrase_service(service): - prefix = service.data['prefix'] + prefix = service.data["prefix"] elk = elks.get(prefix) if elk is None: - _LOGGER.error("No elk m1 with prefix for speak_phrase: '%s'", - prefix) + _LOGGER.error("No elk m1 with prefix for speak_phrase: '%s'", prefix) return - elk.panel.speak_phrase(service.data['number']) + elk.panel.speak_phrase(service.data["number"]) hass.services.async_register( - DOMAIN, 'speak_word', _speak_word_service, SPEAK_SERVICE_SCHEMA) + DOMAIN, "speak_word", _speak_word_service, SPEAK_SERVICE_SCHEMA + ) hass.services.async_register( - DOMAIN, 'speak_phrase', _speak_phrase_service, SPEAK_SERVICE_SCHEMA) + DOMAIN, "speak_phrase", _speak_phrase_service, SPEAK_SERVICE_SCHEMA + ) -def create_elk_entities(elk_data, elk_elements, element_type, - class_, entities): +def create_elk_entities(elk_data, elk_elements, element_type, class_, entities): """Create the ElkM1 devices of a particular class.""" - if elk_data['config'][element_type]['enabled']: - elk = elk_data['elk'] + if elk_data["config"][element_type]["enabled"]: + elk = elk_data["elk"] _LOGGER.debug("Creating elk entities for %s", elk) for element in elk_elements: - if elk_data['config'][element_type]['included'][element.index]: + if elk_data["config"][element_type]["included"][element.index]: entities.append(class_(element, elk, elk_data)) return entities @@ -217,8 +239,8 @@ class ElkEntity(Entity): """Initialize the base of all Elk devices.""" self._elk = elk self._element = element - self._prefix = elk_data['prefix'] - self._temperature_unit = elk_data['config']['temperature_unit'] + self._prefix = elk_data["prefix"] + self._temperature_unit = elk_data["config"]["temperature_unit"] # unique_id starts with elkm1_ iff there is no prefix # it starts with elkm1m_{prefix} iff there is a prefix # this is to avoid a conflict between @@ -228,12 +250,12 @@ class ElkEntity(Entity): # we could have used elkm1__foo_bar for the latter, but that # would have been a breaking change if self._prefix != "": - uid_start = 'elkm1m_{prefix}'.format(prefix=self._prefix) + uid_start = "elkm1m_{prefix}".format(prefix=self._prefix) else: - uid_start = 'elkm1' - self._unique_id = '{uid_start}_{name}'.format( - uid_start=uid_start, - name=self._element.default_name('_')).lower() + uid_start = "elkm1" + self._unique_id = "{uid_start}_{name}".format( + uid_start=uid_start, name=self._element.default_name("_") + ).lower() @property def name(self): @@ -263,7 +285,7 @@ class ElkEntity(Entity): def initial_attrs(self): """Return the underlying element's attributes as a dict.""" attrs = {} - attrs['index'] = self._element.index + 1 + attrs["index"] = self._element.index + 1 return attrs def _element_changed(self, element, changeset): diff --git a/homeassistant/components/elkm1/alarm_control_panel.py b/homeassistant/components/elkm1/alarm_control_panel.py index 5cf9107c39a..275d94efa66 100644 --- a/homeassistant/components/elkm1/alarm_control_panel.py +++ b/homeassistant/components/elkm1/alarm_control_panel.py @@ -3,37 +3,49 @@ import voluptuous as vol import homeassistant.components.alarm_control_panel as alarm from homeassistant.const import ( - ATTR_CODE, ATTR_ENTITY_ID, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, - STATE_ALARM_ARMED_NIGHT, STATE_ALARM_ARMING, STATE_ALARM_DISARMED, - STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED) + ATTR_CODE, + ATTR_ENTITY_ID, + STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_HOME, + STATE_ALARM_ARMED_NIGHT, + STATE_ALARM_ARMING, + STATE_ALARM_DISARMED, + STATE_ALARM_PENDING, + STATE_ALARM_TRIGGERED, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import ( - async_dispatcher_connect, async_dispatcher_send) + async_dispatcher_connect, + async_dispatcher_send, +) from . import DOMAIN as ELK_DOMAIN, ElkEntity, create_elk_entities -SIGNAL_ARM_ENTITY = 'elkm1_arm' -SIGNAL_DISPLAY_MESSAGE = 'elkm1_display_message' +SIGNAL_ARM_ENTITY = "elkm1_arm" +SIGNAL_DISPLAY_MESSAGE = "elkm1_display_message" -ELK_ALARM_SERVICE_SCHEMA = vol.Schema({ - vol.Required(ATTR_ENTITY_ID, default=[]): cv.entity_ids, - vol.Required(ATTR_CODE): vol.All(vol.Coerce(int), vol.Range(0, 999999)), -}) +ELK_ALARM_SERVICE_SCHEMA = vol.Schema( + { + vol.Required(ATTR_ENTITY_ID, default=[]): cv.entity_ids, + vol.Required(ATTR_CODE): vol.All(vol.Coerce(int), vol.Range(0, 999999)), + } +) -DISPLAY_MESSAGE_SERVICE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID, default=[]): cv.entity_ids, - vol.Optional('clear', default=2): vol.All(vol.Coerce(int), - vol.In([0, 1, 2])), - vol.Optional('beep', default=False): cv.boolean, - vol.Optional('timeout', default=0): vol.All(vol.Coerce(int), - vol.Range(min=0, max=65535)), - vol.Optional('line1', default=''): cv.string, - vol.Optional('line2', default=''): cv.string, -}) +DISPLAY_MESSAGE_SERVICE_SCHEMA = vol.Schema( + { + vol.Optional(ATTR_ENTITY_ID, default=[]): cv.entity_ids, + vol.Optional("clear", default=2): vol.All(vol.Coerce(int), vol.In([0, 1, 2])), + vol.Optional("beep", default=False): cv.boolean, + vol.Optional("timeout", default=0): vol.All( + vol.Coerce(int), vol.Range(min=0, max=65535) + ), + vol.Optional("line1", default=""): cv.string, + vol.Optional("line2", default=""): cv.string, + } +) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the ElkM1 alarm platform.""" if discovery_info is None: return @@ -41,15 +53,13 @@ async def async_setup_platform(hass, config, async_add_entities, elk_datas = hass.data[ELK_DOMAIN] entities = [] for elk_data in elk_datas.values(): - elk = elk_data['elk'] - entities = create_elk_entities(elk_data, elk.areas, - 'area', ElkArea, entities) + elk = elk_data["elk"] + entities = create_elk_entities(elk_data, elk.areas, "area", ElkArea, entities) async_add_entities(entities, True) def _dispatch(signal, entity_ids, *args): for entity_id in entity_ids: - async_dispatcher_send( - hass, '{}_{}'.format(signal, entity_id), *args) + async_dispatcher_send(hass, "{}_{}".format(signal, entity_id), *args) def _arm_service(service): entity_ids = service.data.get(ATTR_ENTITY_ID, []) @@ -59,27 +69,36 @@ async def async_setup_platform(hass, config, async_add_entities, for service in _arm_services(): hass.services.async_register( - alarm.DOMAIN, service, _arm_service, ELK_ALARM_SERVICE_SCHEMA) + alarm.DOMAIN, service, _arm_service, ELK_ALARM_SERVICE_SCHEMA + ) def _display_message_service(service): entity_ids = service.data.get(ATTR_ENTITY_ID, []) data = service.data - args = (data['clear'], data['beep'], data['timeout'], - data['line1'], data['line2']) + args = ( + data["clear"], + data["beep"], + data["timeout"], + data["line1"], + data["line2"], + ) _dispatch(SIGNAL_DISPLAY_MESSAGE, entity_ids, *args) hass.services.async_register( - alarm.DOMAIN, 'elkm1_alarm_display_message', - _display_message_service, DISPLAY_MESSAGE_SERVICE_SCHEMA) + alarm.DOMAIN, + "elkm1_alarm_display_message", + _display_message_service, + DISPLAY_MESSAGE_SERVICE_SCHEMA, + ) def _arm_services(): from elkm1_lib.const import ArmLevel return { - 'elkm1_alarm_arm_vacation': ArmLevel.ARMED_VACATION.value, - 'elkm1_alarm_arm_home_instant': ArmLevel.ARMED_STAY_INSTANT.value, - 'elkm1_alarm_arm_night_instant': ArmLevel.ARMED_NIGHT_INSTANT.value, + "elkm1_alarm_arm_vacation": ArmLevel.ARMED_VACATION.value, + "elkm1_alarm_arm_home_instant": ArmLevel.ARMED_STAY_INSTANT.value, + "elkm1_alarm_arm_night_instant": ArmLevel.ARMED_NIGHT_INSTANT.value, } @@ -89,7 +108,7 @@ class ElkArea(ElkEntity, alarm.AlarmControlPanel): def __init__(self, element, elk, elk_data): """Initialize Area as Alarm Control Panel.""" super().__init__(element, elk, elk_data) - self._changed_by_entity_id = '' + self._changed_by_entity_id = "" self._state = None async def async_added_to_hass(self): @@ -98,18 +117,23 @@ class ElkArea(ElkEntity, alarm.AlarmControlPanel): for keypad in self._elk.keypads: keypad.add_callback(self._watch_keypad) async_dispatcher_connect( - self.hass, '{}_{}'.format(SIGNAL_ARM_ENTITY, self.entity_id), - self._arm_service) + self.hass, + "{}_{}".format(SIGNAL_ARM_ENTITY, self.entity_id), + self._arm_service, + ) async_dispatcher_connect( - self.hass, '{}_{}'.format(SIGNAL_DISPLAY_MESSAGE, self.entity_id), - self._display_message) + self.hass, + "{}_{}".format(SIGNAL_DISPLAY_MESSAGE, self.entity_id), + self._display_message, + ) def _watch_keypad(self, keypad, changeset): if keypad.area != self._element.index: return - if changeset.get('last_user') is not None: - self._changed_by_entity_id = self.hass.data[ - ELK_DOMAIN][self._prefix]['keypads'].get(keypad.index, '') + if changeset.get("last_user") is not None: + self._changed_by_entity_id = self.hass.data[ELK_DOMAIN][self._prefix][ + "keypads" + ].get(keypad.index, "") self.async_schedule_update_ha_state(True) @property @@ -129,17 +153,16 @@ class ElkArea(ElkEntity, alarm.AlarmControlPanel): attrs = self.initial_attrs() elmt = self._element - attrs['is_exit'] = elmt.is_exit - attrs['timer1'] = elmt.timer1 - attrs['timer2'] = elmt.timer2 + attrs["is_exit"] = elmt.is_exit + attrs["timer1"] = elmt.timer1 + attrs["timer2"] = elmt.timer2 if elmt.armed_status is not None: - attrs['armed_status'] = \ - ArmedStatus(elmt.armed_status).name.lower() + attrs["armed_status"] = ArmedStatus(elmt.armed_status).name.lower() if elmt.arm_up_state is not None: - attrs['arm_up_state'] = ArmUpState(elmt.arm_up_state).name.lower() + attrs["arm_up_state"] = ArmUpState(elmt.arm_up_state).name.lower() if elmt.alarm_state is not None: - attrs['alarm_state'] = AlarmState(elmt.alarm_state).name.lower() - attrs['changed_by_entity_id'] = self._changed_by_entity_id + attrs["alarm_state"] = AlarmState(elmt.alarm_state).name.lower() + attrs["changed_by_entity_id"] = self._changed_by_entity_id return attrs def _element_changed(self, element, changeset): @@ -160,8 +183,9 @@ class ElkArea(ElkEntity, alarm.AlarmControlPanel): elif self._area_is_in_alarm_state(): self._state = STATE_ALARM_TRIGGERED elif self._entry_exit_timer_is_running(): - self._state = STATE_ALARM_ARMING \ - if self._element.is_exit else STATE_ALARM_PENDING + self._state = ( + STATE_ALARM_ARMING if self._element.is_exit else STATE_ALARM_PENDING + ) else: self._state = elk_state_to_hass_state[self._element.armed_status] diff --git a/homeassistant/components/elkm1/climate.py b/homeassistant/components/elkm1/climate.py index 36d44756857..58273e71222 100644 --- a/homeassistant/components/elkm1/climate.py +++ b/homeassistant/components/elkm1/climate.py @@ -1,22 +1,32 @@ """Support for control of Elk-M1 connected thermostats.""" from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( - ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, HVAC_MODE_AUTO, + ATTR_TARGET_TEMP_HIGH, + ATTR_TARGET_TEMP_LOW, + HVAC_MODE_AUTO, HVAC_MODE_COOL, - HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_AUX_HEAT, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_HEAT, + HVAC_MODE_OFF, + SUPPORT_AUX_HEAT, SUPPORT_FAN_MODE, - SUPPORT_TARGET_TEMPERATURE_RANGE) + SUPPORT_TARGET_TEMPERATURE_RANGE, +) from homeassistant.const import PRECISION_WHOLE, STATE_ON from . import DOMAIN as ELK_DOMAIN, ElkEntity, create_elk_entities -SUPPORT_HVAC = [HVAC_MODE_OFF, HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_AUTO, - HVAC_MODE_FAN_ONLY] +SUPPORT_HVAC = [ + HVAC_MODE_OFF, + HVAC_MODE_HEAT, + HVAC_MODE_COOL, + HVAC_MODE_AUTO, + HVAC_MODE_FAN_ONLY, +] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Create the Elk-M1 thermostat platform.""" if discovery_info is None: return @@ -24,10 +34,10 @@ async def async_setup_platform(hass, config, async_add_entities, elk_datas = hass.data[ELK_DOMAIN] entities = [] for elk_data in elk_datas.values(): - elk = elk_data['elk'] - entities = create_elk_entities(elk_data, elk.thermostats, - 'thermostat', ElkThermostat, - entities) + elk = elk_data["elk"] + entities = create_elk_entities( + elk_data, elk.thermostats, "thermostat", ElkThermostat, entities + ) async_add_entities(entities, True) @@ -42,8 +52,7 @@ class ElkThermostat(ElkEntity, ClimateDevice): @property def supported_features(self): """Return the list of supported features.""" - return (SUPPORT_FAN_MODE | SUPPORT_AUX_HEAT - | SUPPORT_TARGET_TEMPERATURE_RANGE) + return SUPPORT_FAN_MODE | SUPPORT_AUX_HEAT | SUPPORT_TARGET_TEMPERATURE_RANGE @property def temperature_unit(self): @@ -59,8 +68,10 @@ class ElkThermostat(ElkEntity, ClimateDevice): def target_temperature(self): """Return the temperature we are trying to reach.""" from elkm1_lib.const import ThermostatMode + if (self._element.mode == ThermostatMode.HEAT.value) or ( - self._element.mode == ThermostatMode.EMERGENCY_HEAT.value): + self._element.mode == ThermostatMode.EMERGENCY_HEAT.value + ): return self._element.heat_setpoint if self._element.mode == ThermostatMode.COOL.value: return self._element.cool_setpoint @@ -105,6 +116,7 @@ class ElkThermostat(ElkEntity, ClimateDevice): def is_aux_heat(self): """Return if aux heater is on.""" from elkm1_lib.const import ThermostatMode + return self._element.mode == ThermostatMode.EMERGENCY_HEAT.value @property @@ -121,6 +133,7 @@ class ElkThermostat(ElkEntity, ClimateDevice): def fan_mode(self): """Return the fan setting.""" from elkm1_lib.const import ThermostatFan + if self._element.fan == ThermostatFan.AUTO.value: return HVAC_MODE_AUTO if self._element.fan == ThermostatFan.ON.value: @@ -129,6 +142,7 @@ class ElkThermostat(ElkEntity, ClimateDevice): def _elk_set(self, mode, fan): from elkm1_lib.const import ThermostatSetting + if mode is not None: self._element.set(ThermostatSetting.MODE.value, mode) if fan is not None: @@ -137,25 +151,26 @@ class ElkThermostat(ElkEntity, ClimateDevice): async def async_set_hvac_mode(self, hvac_mode): """Set thermostat operation mode.""" from elkm1_lib.const import ThermostatFan, ThermostatMode + settings = { - HVAC_MODE_OFF: - (ThermostatMode.OFF.value, ThermostatFan.AUTO.value), + HVAC_MODE_OFF: (ThermostatMode.OFF.value, ThermostatFan.AUTO.value), HVAC_MODE_HEAT: (ThermostatMode.HEAT.value, None), HVAC_MODE_COOL: (ThermostatMode.COOL.value, None), HVAC_MODE_AUTO: (ThermostatMode.AUTO.value, None), - HVAC_MODE_FAN_ONLY: - (ThermostatMode.OFF.value, ThermostatFan.ON.value) + HVAC_MODE_FAN_ONLY: (ThermostatMode.OFF.value, ThermostatFan.ON.value), } self._elk_set(settings[hvac_mode][0], settings[hvac_mode][1]) async def async_turn_aux_heat_on(self): """Turn auxiliary heater on.""" from elkm1_lib.const import ThermostatMode + self._elk_set(ThermostatMode.EMERGENCY_HEAT.value, None) async def async_turn_aux_heat_off(self): """Turn auxiliary heater off.""" from elkm1_lib.const import ThermostatMode + self._elk_set(ThermostatMode.HEAT.value, None) @property @@ -166,6 +181,7 @@ class ElkThermostat(ElkEntity, ClimateDevice): async def async_set_fan_mode(self, fan_mode): """Set new target fan mode.""" from elkm1_lib.const import ThermostatFan + if fan_mode == HVAC_MODE_AUTO: self._elk_set(None, ThermostatFan.AUTO.value) elif fan_mode == STATE_ON: @@ -174,17 +190,17 @@ class ElkThermostat(ElkEntity, ClimateDevice): async def async_set_temperature(self, **kwargs): """Set new target temperature.""" from elkm1_lib.const import ThermostatSetting + low_temp = kwargs.get(ATTR_TARGET_TEMP_LOW) high_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH) if low_temp is not None: - self._element.set( - ThermostatSetting.HEAT_SETPOINT.value, round(low_temp)) + self._element.set(ThermostatSetting.HEAT_SETPOINT.value, round(low_temp)) if high_temp is not None: - self._element.set( - ThermostatSetting.COOL_SETPOINT.value, round(high_temp)) + self._element.set(ThermostatSetting.COOL_SETPOINT.value, round(high_temp)) def _element_changed(self, element, changeset): from elkm1_lib.const import ThermostatFan, ThermostatMode + mode_to_state = { ThermostatMode.OFF.value: HVAC_MODE_OFF, ThermostatMode.COOL.value: HVAC_MODE_COOL, @@ -193,6 +209,5 @@ class ElkThermostat(ElkEntity, ClimateDevice): ThermostatMode.AUTO.value: HVAC_MODE_AUTO, } self._state = mode_to_state.get(self._element.mode) - if self._state == HVAC_MODE_OFF and \ - self._element.fan == ThermostatFan.ON.value: + if self._state == HVAC_MODE_OFF and self._element.fan == ThermostatFan.ON.value: self._state = HVAC_MODE_FAN_ONLY diff --git a/homeassistant/components/elkm1/light.py b/homeassistant/components/elkm1/light.py index 50e91b091ee..10a9ae1b931 100644 --- a/homeassistant/components/elkm1/light.py +++ b/homeassistant/components/elkm1/light.py @@ -1,21 +1,18 @@ """Support for control of ElkM1 lighting (X10, UPB, etc).""" -from homeassistant.components.light import ( - ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light) +from homeassistant.components.light import ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light from . import DOMAIN as ELK_DOMAIN, ElkEntity, create_elk_entities -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Elk light platform.""" if discovery_info is None: return elk_datas = hass.data[ELK_DOMAIN] entities = [] for elk_data in elk_datas.values(): - elk = elk_data['elk'] - create_elk_entities(elk_data, elk.lights, - 'plc', ElkLight, entities) + elk = elk_data["elk"] + create_elk_entities(elk_data, elk.lights, "plc", ElkLight, entities) async_add_entities(entities, True) diff --git a/homeassistant/components/elkm1/scene.py b/homeassistant/components/elkm1/scene.py index 2133c0822e0..dc5ea39d154 100644 --- a/homeassistant/components/elkm1/scene.py +++ b/homeassistant/components/elkm1/scene.py @@ -4,17 +4,15 @@ from homeassistant.components.scene import Scene from . import DOMAIN as ELK_DOMAIN, ElkEntity, create_elk_entities -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Create the Elk-M1 scene platform.""" if discovery_info is None: return elk_datas = hass.data[ELK_DOMAIN] entities = [] for elk_data in elk_datas.values(): - elk = elk_data['elk'] - entities = create_elk_entities(elk_data, elk.tasks, - 'task', ElkTask, entities) + elk = elk_data["elk"] + entities = create_elk_entities(elk_data, elk.tasks, "task", ElkTask, entities) async_add_entities(entities, True) diff --git a/homeassistant/components/elkm1/sensor.py b/homeassistant/components/elkm1/sensor.py index f5811412cc8..3f524b778db 100644 --- a/homeassistant/components/elkm1/sensor.py +++ b/homeassistant/components/elkm1/sensor.py @@ -2,8 +2,7 @@ from . import DOMAIN as ELK_DOMAIN, ElkEntity, create_elk_entities -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Create the Elk-M1 sensor platform.""" if discovery_info is None: return @@ -11,17 +10,20 @@ async def async_setup_platform( elk_datas = hass.data[ELK_DOMAIN] entities = [] for elk_data in elk_datas.values(): - elk = elk_data['elk'] + elk = elk_data["elk"] entities = create_elk_entities( - elk_data, elk.counters, 'counter', ElkCounter, entities) + elk_data, elk.counters, "counter", ElkCounter, entities + ) entities = create_elk_entities( - elk_data, elk.keypads, 'keypad', ElkKeypad, entities) + elk_data, elk.keypads, "keypad", ElkKeypad, entities + ) entities = create_elk_entities( - elk_data, [elk.panel], 'panel', ElkPanel, entities) + elk_data, [elk.panel], "panel", ElkPanel, entities + ) entities = create_elk_entities( - elk_data, elk.settings, 'setting', ElkSetting, entities) - entities = create_elk_entities( - elk_data, elk.zones, 'zone', ElkZone, entities) + elk_data, elk.settings, "setting", ElkSetting, entities + ) + entities = create_elk_entities(elk_data, elk.zones, "zone", ElkZone, entities) async_add_entities(entities, True) @@ -50,7 +52,7 @@ class ElkCounter(ElkSensor): @property def icon(self): """Icon to use in the frontend.""" - return 'mdi:numeric' + return "mdi:numeric" def _element_changed(self, element, changeset): self._state = self._element.value @@ -72,7 +74,7 @@ class ElkKeypad(ElkSensor): @property def icon(self): """Icon to use in the frontend.""" - return 'mdi:thermometer-lines' + return "mdi:thermometer-lines" @property def device_state_attributes(self): @@ -80,13 +82,13 @@ class ElkKeypad(ElkSensor): from elkm1_lib.util import username attrs = self.initial_attrs() - attrs['area'] = self._element.area + 1 - attrs['temperature'] = self._element.temperature - attrs['last_user_time'] = self._element.last_user_time.isoformat() - attrs['last_user'] = self._element.last_user + 1 - attrs['code'] = self._element.code - attrs['last_user_name'] = username(self._elk, self._element.last_user) - attrs['last_keypress'] = self._element.last_keypress + attrs["area"] = self._element.area + 1 + attrs["temperature"] = self._element.temperature + attrs["last_user_time"] = self._element.last_user_time.isoformat() + attrs["last_user"] = self._element.last_user + 1 + attrs["code"] = self._element.code + attrs["last_user_name"] = username(self._elk, self._element.last_user) + attrs["last_keypress"] = self._element.last_keypress return attrs def _element_changed(self, element, changeset): @@ -97,7 +99,7 @@ class ElkKeypad(ElkSensor): await super().async_added_to_hass() elk_datas = self.hass.data[ELK_DOMAIN] for elk_data in elk_datas.values(): - elk_data['keypads'][self._element.index] = self.entity_id + elk_data["keypads"][self._element.index] = self.entity_id class ElkPanel(ElkSensor): @@ -112,15 +114,16 @@ class ElkPanel(ElkSensor): def device_state_attributes(self): """Attributes of the sensor.""" attrs = self.initial_attrs() - attrs['system_trouble_status'] = self._element.system_trouble_status + attrs["system_trouble_status"] = self._element.system_trouble_status return attrs def _element_changed(self, element, changeset): if self._elk.is_connected(): - self._state = 'Paused' if self._element.remote_programming_status \ - else 'Connected' + self._state = ( + "Paused" if self._element.remote_programming_status else "Connected" + ) else: - self._state = 'Disconnected' + self._state = "Disconnected" class ElkSetting(ElkSensor): @@ -129,7 +132,7 @@ class ElkSetting(ElkSensor): @property def icon(self): """Icon to use in the frontend.""" - return 'mdi:numeric' + return "mdi:numeric" def _element_changed(self, element, changeset): self._state = self._element.value @@ -138,9 +141,9 @@ class ElkSetting(ElkSensor): def device_state_attributes(self): """Attributes of the sensor.""" from elkm1_lib.const import SettingFormat + attrs = self.initial_attrs() - attrs['value_format'] = SettingFormat( - self._element.value_format).name.lower() + attrs["value_format"] = SettingFormat(self._element.value_format).name.lower() return attrs @@ -151,52 +154,53 @@ class ElkZone(ElkSensor): def icon(self): """Icon to use in the frontend.""" from elkm1_lib.const import ZoneType + zone_icons = { - ZoneType.FIRE_ALARM.value: 'fire', - ZoneType.FIRE_VERIFIED.value: 'fire', - ZoneType.FIRE_SUPERVISORY.value: 'fire', - ZoneType.KEYFOB.value: 'key', - ZoneType.NON_ALARM.value: 'alarm-off', - ZoneType.MEDICAL_ALARM.value: 'medical-bag', - ZoneType.POLICE_ALARM.value: 'alarm-light', - ZoneType.POLICE_NO_INDICATION.value: 'alarm-light', - ZoneType.KEY_MOMENTARY_ARM_DISARM.value: 'power', - ZoneType.KEY_MOMENTARY_ARM_AWAY.value: 'power', - ZoneType.KEY_MOMENTARY_ARM_STAY.value: 'power', - ZoneType.KEY_MOMENTARY_DISARM.value: 'power', - ZoneType.KEY_ON_OFF.value: 'toggle-switch', - ZoneType.MUTE_AUDIBLES.value: 'volume-mute', - ZoneType.POWER_SUPERVISORY.value: 'power-plug', - ZoneType.TEMPERATURE.value: 'thermometer-lines', - ZoneType.ANALOG_ZONE.value: 'speedometer', - ZoneType.PHONE_KEY.value: 'phone-classic', - ZoneType.INTERCOM_KEY.value: 'deskphone' + ZoneType.FIRE_ALARM.value: "fire", + ZoneType.FIRE_VERIFIED.value: "fire", + ZoneType.FIRE_SUPERVISORY.value: "fire", + ZoneType.KEYFOB.value: "key", + ZoneType.NON_ALARM.value: "alarm-off", + ZoneType.MEDICAL_ALARM.value: "medical-bag", + ZoneType.POLICE_ALARM.value: "alarm-light", + ZoneType.POLICE_NO_INDICATION.value: "alarm-light", + ZoneType.KEY_MOMENTARY_ARM_DISARM.value: "power", + ZoneType.KEY_MOMENTARY_ARM_AWAY.value: "power", + ZoneType.KEY_MOMENTARY_ARM_STAY.value: "power", + ZoneType.KEY_MOMENTARY_DISARM.value: "power", + ZoneType.KEY_ON_OFF.value: "toggle-switch", + ZoneType.MUTE_AUDIBLES.value: "volume-mute", + ZoneType.POWER_SUPERVISORY.value: "power-plug", + ZoneType.TEMPERATURE.value: "thermometer-lines", + ZoneType.ANALOG_ZONE.value: "speedometer", + ZoneType.PHONE_KEY.value: "phone-classic", + ZoneType.INTERCOM_KEY.value: "deskphone", } - return 'mdi:{}'.format( - zone_icons.get(self._element.definition, 'alarm-bell')) + return "mdi:{}".format(zone_icons.get(self._element.definition, "alarm-bell")) @property def device_state_attributes(self): """Attributes of the sensor.""" - from elkm1_lib.const import ( - ZoneLogicalStatus, ZonePhysicalStatus, ZoneType) + from elkm1_lib.const import ZoneLogicalStatus, ZonePhysicalStatus, ZoneType attrs = self.initial_attrs() - attrs['physical_status'] = ZonePhysicalStatus( - self._element.physical_status).name.lower() - attrs['logical_status'] = ZoneLogicalStatus( - self._element.logical_status).name.lower() - attrs['definition'] = ZoneType( - self._element.definition).name.lower() - attrs['area'] = self._element.area + 1 - attrs['bypassed'] = self._element.bypassed - attrs['triggered_alarm'] = self._element.triggered_alarm + attrs["physical_status"] = ZonePhysicalStatus( + self._element.physical_status + ).name.lower() + attrs["logical_status"] = ZoneLogicalStatus( + self._element.logical_status + ).name.lower() + attrs["definition"] = ZoneType(self._element.definition).name.lower() + attrs["area"] = self._element.area + 1 + attrs["bypassed"] = self._element.bypassed + attrs["triggered_alarm"] = self._element.triggered_alarm return attrs @property def temperature_unit(self): """Return the temperature unit.""" from elkm1_lib.const import ZoneType + if self._element.definition == ZoneType.TEMPERATURE.value: return self._temperature_unit return None @@ -205,10 +209,11 @@ class ElkZone(ElkSensor): def unit_of_measurement(self): """Return the unit of measurement.""" from elkm1_lib.const import ZoneType + if self._element.definition == ZoneType.TEMPERATURE.value: return self._temperature_unit if self._element.definition == ZoneType.ANALOG_ZONE.value: - return 'V' + return "V" return None def _element_changed(self, element, changeset): @@ -220,5 +225,6 @@ class ElkZone(ElkSensor): elif self._element.definition == ZoneType.ANALOG_ZONE.value: self._state = self._element.voltage else: - self._state = pretty_const(ZoneLogicalStatus( - self._element.logical_status).name) + self._state = pretty_const( + ZoneLogicalStatus(self._element.logical_status).name + ) diff --git a/homeassistant/components/elkm1/switch.py b/homeassistant/components/elkm1/switch.py index 2030ffe20ee..e6dd82dc0ac 100644 --- a/homeassistant/components/elkm1/switch.py +++ b/homeassistant/components/elkm1/switch.py @@ -4,17 +4,17 @@ from homeassistant.components.switch import SwitchDevice from . import DOMAIN as ELK_DOMAIN, ElkEntity, create_elk_entities -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Create the Elk-M1 switch platform.""" if discovery_info is None: return elk_datas = hass.data[ELK_DOMAIN] entities = [] for elk_data in elk_datas.values(): - elk = elk_data['elk'] - entities = create_elk_entities(elk_data, elk.outputs, - 'output', ElkOutput, entities) + elk = elk_data["elk"] + entities = create_elk_entities( + elk_data, elk.outputs, "output", ElkOutput, entities + ) async_add_entities(entities, True) diff --git a/homeassistant/components/elv/switch.py b/homeassistant/components/elv/switch.py index bd97d10cecf..c6258e244e9 100644 --- a/homeassistant/components/elv/switch.py +++ b/homeassistant/components/elv/switch.py @@ -4,21 +4,25 @@ import logging import voluptuous as vol from homeassistant.components.switch import ( - SwitchDevice, PLATFORM_SCHEMA, ATTR_CURRENT_POWER_W) -from homeassistant.const import ( - CONF_NAME, CONF_DEVICE, EVENT_HOMEASSISTANT_STOP) + SwitchDevice, + PLATFORM_SCHEMA, + ATTR_CURRENT_POWER_W, +) +from homeassistant.const import CONF_NAME, CONF_DEVICE, EVENT_HOMEASSISTANT_STOP import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -ATTR_TOTAL_ENERGY_KWH = 'total_energy_kwh' +ATTR_TOTAL_ENERGY_KWH = "total_energy_kwh" -DEFAULT_NAME = 'PCA 301' +DEFAULT_NAME = "PCA 301" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Required(CONF_DEVICE): cv.string -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Required(CONF_DEVICE): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -32,8 +36,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): try: pca = pypca.PCA(usb_device) pca.open() - entities = [SmartPlugSwitch(pca, device, name) - for device in pca.get_devices()] + entities = [SmartPlugSwitch(pca, device, name) for device in pca.get_devices()] add_entities(entities, True) except SerialException as exc: @@ -89,15 +92,16 @@ class SmartPlugSwitch(SwitchDevice): """Update the PCA switch's state.""" try: self._emeter_params[ATTR_CURRENT_POWER_W] = "{:.1f}".format( - self._pca.get_current_power(self._device_id)) + self._pca.get_current_power(self._device_id) + ) self._emeter_params[ATTR_TOTAL_ENERGY_KWH] = "{:.2f}".format( - self._pca.get_total_consumption(self._device_id)) + self._pca.get_total_consumption(self._device_id) + ) self._available = True self._state = self._pca.get_state(self._device_id) except (OSError) as ex: if self._available: - _LOGGER.warning( - "Could not read state for %s: %s", self.name, ex) + _LOGGER.warning("Could not read state for %s: %s", self.name, ex) self._available = False diff --git a/homeassistant/components/emby/media_player.py b/homeassistant/components/emby/media_player.py index fa1c096707b..409dd8ec472 100644 --- a/homeassistant/components/emby/media_player.py +++ b/homeassistant/components/emby/media_player.py @@ -3,28 +3,44 @@ import logging import voluptuous as vol -from homeassistant.components.media_player import ( - MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA from homeassistant.components.media_player.const import ( - MEDIA_TYPE_CHANNEL, MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, - SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, - SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_STOP) + MEDIA_TYPE_CHANNEL, + MEDIA_TYPE_MOVIE, + MEDIA_TYPE_MUSIC, + MEDIA_TYPE_TVSHOW, + SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, + SUPPORT_PLAY, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_SEEK, + SUPPORT_STOP, +) from homeassistant.const import ( - CONF_API_KEY, CONF_HOST, CONF_PORT, CONF_SSL, DEVICE_DEFAULT_NAME, - EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, STATE_IDLE, STATE_OFF, - STATE_PAUSED, STATE_PLAYING) + CONF_API_KEY, + CONF_HOST, + CONF_PORT, + CONF_SSL, + DEVICE_DEFAULT_NAME, + EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP, + STATE_IDLE, + STATE_OFF, + STATE_PAUSED, + STATE_PLAYING, +) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) -CONF_AUTO_HIDE = 'auto_hide' +CONF_AUTO_HIDE = "auto_hide" -MEDIA_TYPE_TRAILER = 'trailer' -MEDIA_TYPE_GENERIC_VIDEO = 'video' +MEDIA_TYPE_TRAILER = "trailer" +MEDIA_TYPE_GENERIC_VIDEO = "video" -DEFAULT_HOST = 'localhost' +DEFAULT_HOST = "localhost" DEFAULT_PORT = 8096 DEFAULT_SSL_PORT = 8920 DEFAULT_SSL = False @@ -32,20 +48,27 @@ DEFAULT_AUTO_HIDE = False _LOGGER = logging.getLogger(__name__) -SUPPORT_EMBY = SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \ - SUPPORT_STOP | SUPPORT_SEEK | SUPPORT_PLAY +SUPPORT_EMBY = ( + SUPPORT_PAUSE + | SUPPORT_PREVIOUS_TRACK + | SUPPORT_NEXT_TRACK + | SUPPORT_STOP + | SUPPORT_SEEK + | SUPPORT_PLAY +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_API_KEY): cv.string, - vol.Optional(CONF_AUTO_HIDE, default=DEFAULT_AUTO_HIDE): cv.boolean, - vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, - vol.Optional(CONF_PORT): cv.port, - vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_API_KEY): cv.string, + vol.Optional(CONF_AUTO_HIDE, default=DEFAULT_AUTO_HIDE): cv.boolean, + vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, + vol.Optional(CONF_PORT): cv.port, + vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean, + } +) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Emby platform.""" from pyemby import EmbyServer @@ -72,14 +95,16 @@ async def async_setup_platform(hass, config, async_add_entities, active_devices = [] for dev_id in emby.devices: active_devices.append(dev_id) - if dev_id not in active_emby_devices and \ - dev_id not in inactive_emby_devices: + if ( + dev_id not in active_emby_devices + and dev_id not in inactive_emby_devices + ): new = EmbyDevice(emby, dev_id) active_emby_devices[dev_id] = new new_devices.append(new) elif dev_id in inactive_emby_devices: - if emby.devices[dev_id].state != 'Off': + if emby.devices[dev_id].state != "Off": add = inactive_emby_devices.pop(dev_id) active_emby_devices[dev_id] = add _LOGGER.debug("Showing %s, item: %s", dev_id, add) @@ -135,8 +160,7 @@ class EmbyDevice(MediaPlayerDevice): async def async_added_to_hass(self): """Register callback.""" - self.emby.add_update_callback( - self.async_update_callback, self.device_id) + self.emby.add_update_callback(self.async_update_callback, self.device_id) @callback def async_update_callback(self, msg): @@ -184,8 +208,10 @@ class EmbyDevice(MediaPlayerDevice): @property def name(self): """Return the name of the device.""" - return ('Emby - {} - {}'.format(self.device.client, self.device.name) - or DEVICE_DEFAULT_NAME) + return ( + "Emby - {} - {}".format(self.device.client, self.device.name) + or DEVICE_DEFAULT_NAME + ) @property def should_poll(self): @@ -196,13 +222,13 @@ class EmbyDevice(MediaPlayerDevice): def state(self): """Return the state of the device.""" state = self.device.state - if state == 'Paused': + if state == "Paused": return STATE_PAUSED - if state == 'Playing': + if state == "Playing": return STATE_PLAYING - if state == 'Idle': + if state == "Idle": return STATE_IDLE - if state == 'Off': + if state == "Off": return STATE_OFF @property @@ -220,19 +246,19 @@ class EmbyDevice(MediaPlayerDevice): def media_content_type(self): """Content type of current playing media.""" media_type = self.device.media_type - if media_type == 'Episode': + if media_type == "Episode": return MEDIA_TYPE_TVSHOW - if media_type == 'Movie': + if media_type == "Movie": return MEDIA_TYPE_MOVIE - if media_type == 'Trailer': + if media_type == "Trailer": return MEDIA_TYPE_TRAILER - if media_type == 'Music': + if media_type == "Music": return MEDIA_TYPE_MUSIC - if media_type == 'Video': + if media_type == "Video": return MEDIA_TYPE_GENERIC_VIDEO - if media_type == 'Audio': + if media_type == "Audio": return MEDIA_TYPE_MUSIC - if media_type == 'TvChannel': + if media_type == "TvChannel": return MEDIA_TYPE_CHANNEL return None diff --git a/homeassistant/components/emoncms/sensor.py b/homeassistant/components/emoncms/sensor.py index 6e059e1a30f..8d79b771fb9 100644 --- a/homeassistant/components/emoncms/sensor.py +++ b/homeassistant/components/emoncms/sensor.py @@ -8,52 +8,65 @@ import requests import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_API_KEY, CONF_URL, CONF_VALUE_TEMPLATE, CONF_UNIT_OF_MEASUREMENT, - CONF_ID, CONF_SCAN_INTERVAL, STATE_UNKNOWN, POWER_WATT) + CONF_API_KEY, + CONF_URL, + CONF_VALUE_TEMPLATE, + CONF_UNIT_OF_MEASUREMENT, + CONF_ID, + CONF_SCAN_INTERVAL, + STATE_UNKNOWN, + POWER_WATT, +) from homeassistant.helpers.entity import Entity from homeassistant.helpers import template from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -ATTR_FEEDID = 'FeedId' -ATTR_FEEDNAME = 'FeedName' -ATTR_LASTUPDATETIME = 'LastUpdated' -ATTR_LASTUPDATETIMESTR = 'LastUpdatedStr' -ATTR_SIZE = 'Size' -ATTR_TAG = 'Tag' -ATTR_USERID = 'UserId' +ATTR_FEEDID = "FeedId" +ATTR_FEEDNAME = "FeedName" +ATTR_LASTUPDATETIME = "LastUpdated" +ATTR_LASTUPDATETIMESTR = "LastUpdatedStr" +ATTR_SIZE = "Size" +ATTR_TAG = "Tag" +ATTR_USERID = "UserId" -CONF_EXCLUDE_FEEDID = 'exclude_feed_id' -CONF_ONLY_INCLUDE_FEEDID = 'include_only_feed_id' -CONF_SENSOR_NAMES = 'sensor_names' +CONF_EXCLUDE_FEEDID = "exclude_feed_id" +CONF_ONLY_INCLUDE_FEEDID = "include_only_feed_id" +CONF_SENSOR_NAMES = "sensor_names" DECIMALS = 2 DEFAULT_UNIT = POWER_WATT MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=5) -ONLY_INCL_EXCL_NONE = 'only_include_exclude_or_none' +ONLY_INCL_EXCL_NONE = "only_include_exclude_or_none" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_API_KEY): cv.string, - vol.Required(CONF_URL): cv.string, - vol.Required(CONF_ID): cv.positive_int, - vol.Exclusive(CONF_ONLY_INCLUDE_FEEDID, ONLY_INCL_EXCL_NONE): - vol.All(cv.ensure_list, [cv.positive_int]), - vol.Exclusive(CONF_EXCLUDE_FEEDID, ONLY_INCL_EXCL_NONE): - vol.All(cv.ensure_list, [cv.positive_int]), - vol.Optional(CONF_SENSOR_NAMES): - vol.All({cv.positive_int: vol.All(cv.string, vol.Length(min=1))}), - vol.Optional(CONF_VALUE_TEMPLATE): cv.template, - vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=DEFAULT_UNIT): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_API_KEY): cv.string, + vol.Required(CONF_URL): cv.string, + vol.Required(CONF_ID): cv.positive_int, + vol.Exclusive(CONF_ONLY_INCLUDE_FEEDID, ONLY_INCL_EXCL_NONE): vol.All( + cv.ensure_list, [cv.positive_int] + ), + vol.Exclusive(CONF_EXCLUDE_FEEDID, ONLY_INCL_EXCL_NONE): vol.All( + cv.ensure_list, [cv.positive_int] + ), + vol.Optional(CONF_SENSOR_NAMES): vol.All( + {cv.positive_int: vol.All(cv.string, vol.Length(min=1))} + ), + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=DEFAULT_UNIT): cv.string, + } +) def get_id(sensorid, feedtag, feedname, feedid, feeduserid): """Return unique identifier for feed / sensor.""" return "emoncms{}_{}_{}_{}_{}".format( - sensorid, feedtag, feedname, feedid, feeduserid) + sensorid, feedtag, feedname, feedid, feeduserid + ) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -94,30 +107,40 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if sensor_names is not None: name = sensor_names.get(int(elem["id"]), None) - sensors.append(EmonCmsSensor(hass, data, name, value_template, - unit_of_measurement, str(sensorid), - elem)) + sensors.append( + EmonCmsSensor( + hass, + data, + name, + value_template, + unit_of_measurement, + str(sensorid), + elem, + ) + ) add_entities(sensors) class EmonCmsSensor(Entity): """Implementation of an Emoncms sensor.""" - def __init__(self, hass, data, name, value_template, - unit_of_measurement, sensorid, elem): + def __init__( + self, hass, data, name, value_template, unit_of_measurement, sensorid, elem + ): """Initialize the sensor.""" if name is None: # Suppress ID in sensor name if it's 1, since most people won't # have more than one EmonCMS source and it's redundant to show the # ID if there's only one. - id_for_name = '' if str(sensorid) == '1' else sensorid + id_for_name = "" if str(sensorid) == "1" else sensorid # Use the feed name assigned in EmonCMS or fall back to the feed ID - feed_name = elem.get('name') or 'Feed {}'.format(elem['id']) + feed_name = elem.get("name") or "Feed {}".format(elem["id"]) self._name = "EmonCMS{} {}".format(id_for_name, feed_name) else: self._name = name self._identifier = get_id( - sensorid, elem["tag"], elem["name"], elem["id"], elem["userid"]) + sensorid, elem["tag"], elem["name"], elem["id"], elem["userid"] + ) self._hass = hass self._data = data self._value_template = value_template @@ -127,7 +150,8 @@ class EmonCmsSensor(Entity): if self._value_template is not None: self._state = self._value_template.render_with_possible_json_value( - elem["value"], STATE_UNKNOWN) + elem["value"], STATE_UNKNOWN + ) else: self._state = round(float(elem["value"]), DECIMALS) @@ -156,8 +180,7 @@ class EmonCmsSensor(Entity): ATTR_SIZE: self._elem["size"], ATTR_USERID: self._elem["userid"], ATTR_LASTUPDATETIME: self._elem["time"], - ATTR_LASTUPDATETIMESTR: template.timestamp_local( - float(self._elem["time"])), + ATTR_LASTUPDATETIMESTR: template.timestamp_local(float(self._elem["time"])), } def update(self): @@ -167,11 +190,21 @@ class EmonCmsSensor(Entity): if self._data.data is None: return - elem = next((elem for elem in self._data.data - if get_id(self._sensorid, elem["tag"], - elem["name"], elem["id"], - elem["userid"]) == self._identifier), - None) + elem = next( + ( + elem + for elem in self._data.data + if get_id( + self._sensorid, + elem["tag"], + elem["name"], + elem["id"], + elem["userid"], + ) + == self._identifier + ), + None, + ) if elem is None: return @@ -180,7 +213,8 @@ class EmonCmsSensor(Entity): if self._value_template is not None: self._state = self._value_template.render_with_possible_json_value( - elem["value"], STATE_UNKNOWN) + elem["value"], STATE_UNKNOWN + ) else: self._state = round(float(elem["value"]), DECIMALS) @@ -191,7 +225,7 @@ class EmonCmsData: def __init__(self, hass, url, apikey, interval): """Initialize the data object.""" self._apikey = apikey - self._url = '{}/feed/list.json'.format(url) + self._url = "{}/feed/list.json".format(url) self._interval = interval self._hass = hass self.data = None @@ -202,7 +236,8 @@ class EmonCmsData: try: parameters = {"apikey": self._apikey} req = requests.get( - self._url, params=parameters, allow_redirects=True, timeout=5) + self._url, params=parameters, allow_redirects=True, timeout=5 + ) except requests.exceptions.RequestException as exception: _LOGGER.error(exception) return @@ -210,6 +245,9 @@ class EmonCmsData: if req.status_code == 200: self.data = req.json() else: - _LOGGER.error("Please verify if the specified config value " - "'%s' is correct! (HTTP Status_code = %d)", - CONF_URL, req.status_code) + _LOGGER.error( + "Please verify if the specified config value " + "'%s' is correct! (HTTP Status_code = %d)", + CONF_URL, + req.status_code, + ) diff --git a/homeassistant/components/emoncms_history/__init__.py b/homeassistant/components/emoncms_history/__init__.py index 45fb358cecc..779a25872f9 100644 --- a/homeassistant/components/emoncms_history/__init__.py +++ b/homeassistant/components/emoncms_history/__init__.py @@ -7,26 +7,36 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.const import ( - CONF_API_KEY, CONF_WHITELIST, CONF_URL, STATE_UNKNOWN, STATE_UNAVAILABLE, - CONF_SCAN_INTERVAL) + CONF_API_KEY, + CONF_WHITELIST, + CONF_URL, + STATE_UNKNOWN, + STATE_UNAVAILABLE, + CONF_SCAN_INTERVAL, +) from homeassistant.helpers import state as state_helper from homeassistant.helpers.event import track_point_in_time from homeassistant.util import dt as dt_util _LOGGER = logging.getLogger(__name__) -DOMAIN = 'emoncms_history' -CONF_INPUTNODE = 'inputnode' +DOMAIN = "emoncms_history" +CONF_INPUTNODE = "inputnode" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_API_KEY): cv.string, - vol.Required(CONF_URL): cv.string, - vol.Required(CONF_INPUTNODE): cv.positive_int, - vol.Required(CONF_WHITELIST): cv.entity_ids, - vol.Optional(CONF_SCAN_INTERVAL, default=30): cv.positive_int, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_API_KEY): cv.string, + vol.Required(CONF_URL): cv.string, + vol.Required(CONF_INPUTNODE): cv.positive_int, + vol.Required(CONF_WHITELIST): cv.entity_ids, + vol.Optional(CONF_SCAN_INTERVAL, default=30): cv.positive_int, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): @@ -37,12 +47,12 @@ def setup(hass, config): def send_data(url, apikey, node, payload): """Send payload data to Emoncms.""" try: - fullurl = '{}/input/post.json'.format(url) + fullurl = "{}/input/post.json".format(url) data = {"apikey": apikey, "data": payload} parameters = {"node": node} req = requests.post( - fullurl, params=parameters, data=data, allow_redirects=True, - timeout=5) + fullurl, params=parameters, data=data, allow_redirects=True, timeout=5 + ) except requests.exceptions.RequestException: _LOGGER.error("Error saving data '%s' to '%s'", payload, fullurl) @@ -51,7 +61,10 @@ def setup(hass, config): if req.status_code != 200: _LOGGER.error( "Error saving data %s to %s (http status code = %d)", - payload, fullurl, req.status_code) + payload, + fullurl, + req.status_code, + ) def update_emoncms(time): """Send whitelisted entities states regularly to Emoncms.""" @@ -60,8 +73,7 @@ def setup(hass, config): for entity_id in whitelist: state = hass.states.get(entity_id) - if state is None or state.state in ( - STATE_UNKNOWN, '', STATE_UNAVAILABLE): + if state is None or state.state in (STATE_UNKNOWN, "", STATE_UNAVAILABLE): continue try: @@ -70,15 +82,20 @@ def setup(hass, config): continue if payload_dict: - payload = "{%s}" % ",".join("{}:{}".format(key, val) - for key, val in - payload_dict.items()) + payload = "{%s}" % ",".join( + "{}:{}".format(key, val) for key, val in payload_dict.items() + ) - send_data(conf.get(CONF_URL), conf.get(CONF_API_KEY), - str(conf.get(CONF_INPUTNODE)), payload) + send_data( + conf.get(CONF_URL), + conf.get(CONF_API_KEY), + str(conf.get(CONF_INPUTNODE)), + payload, + ) - track_point_in_time(hass, update_emoncms, time + - timedelta(seconds=conf.get(CONF_SCAN_INTERVAL))) + track_point_in_time( + hass, update_emoncms, time + timedelta(seconds=conf.get(CONF_SCAN_INTERVAL)) + ) update_emoncms(dt_util.utcnow()) return True diff --git a/homeassistant/components/emulated_hue/__init__.py b/homeassistant/components/emulated_hue/__init__.py index 2ef0aaca134..b9e3ecaf093 100644 --- a/homeassistant/components/emulated_hue/__init__.py +++ b/homeassistant/components/emulated_hue/__init__.py @@ -5,9 +5,7 @@ from aiohttp import web import voluptuous as vol from homeassistant import util -from homeassistant.const import ( - EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, -) +from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.deprecation import get_deprecated import homeassistant.helpers.config_validation as cv @@ -15,66 +13,85 @@ from homeassistant.util.json import load_json, save_json from homeassistant.components.http import real_ip from .hue_api import ( - HueUsernameView, HueAllLightsStateView, HueOneLightStateView, - HueOneLightChangeView, HueGroupView, HueAllGroupsStateView) + HueUsernameView, + HueAllLightsStateView, + HueOneLightStateView, + HueOneLightChangeView, + HueGroupView, + HueAllGroupsStateView, +) from .upnp import DescriptionXmlView, UPNPResponderThread -DOMAIN = 'emulated_hue' +DOMAIN = "emulated_hue" _LOGGER = logging.getLogger(__name__) -NUMBERS_FILE = 'emulated_hue_ids.json' +NUMBERS_FILE = "emulated_hue_ids.json" -CONF_ADVERTISE_IP = 'advertise_ip' -CONF_ADVERTISE_PORT = 'advertise_port' -CONF_ENTITIES = 'entities' -CONF_ENTITY_HIDDEN = 'hidden' -CONF_ENTITY_NAME = 'name' -CONF_EXPOSE_BY_DEFAULT = 'expose_by_default' -CONF_EXPOSED_DOMAINS = 'exposed_domains' -CONF_HOST_IP = 'host_ip' -CONF_LISTEN_PORT = 'listen_port' -CONF_OFF_MAPS_TO_ON_DOMAINS = 'off_maps_to_on_domains' -CONF_TYPE = 'type' -CONF_UPNP_BIND_MULTICAST = 'upnp_bind_multicast' +CONF_ADVERTISE_IP = "advertise_ip" +CONF_ADVERTISE_PORT = "advertise_port" +CONF_ENTITIES = "entities" +CONF_ENTITY_HIDDEN = "hidden" +CONF_ENTITY_NAME = "name" +CONF_EXPOSE_BY_DEFAULT = "expose_by_default" +CONF_EXPOSED_DOMAINS = "exposed_domains" +CONF_HOST_IP = "host_ip" +CONF_LISTEN_PORT = "listen_port" +CONF_OFF_MAPS_TO_ON_DOMAINS = "off_maps_to_on_domains" +CONF_TYPE = "type" +CONF_UPNP_BIND_MULTICAST = "upnp_bind_multicast" -TYPE_ALEXA = 'alexa' -TYPE_GOOGLE = 'google_home' +TYPE_ALEXA = "alexa" +TYPE_GOOGLE = "google_home" DEFAULT_LISTEN_PORT = 8300 DEFAULT_UPNP_BIND_MULTICAST = True -DEFAULT_OFF_MAPS_TO_ON_DOMAINS = ['script', 'scene'] +DEFAULT_OFF_MAPS_TO_ON_DOMAINS = ["script", "scene"] DEFAULT_EXPOSE_BY_DEFAULT = True DEFAULT_EXPOSED_DOMAINS = [ - 'switch', 'light', 'group', 'input_boolean', 'media_player', 'fan' + "switch", + "light", + "group", + "input_boolean", + "media_player", + "fan", ] DEFAULT_TYPE = TYPE_GOOGLE -CONFIG_ENTITY_SCHEMA = vol.Schema({ - vol.Optional(CONF_ENTITY_NAME): cv.string, - vol.Optional(CONF_ENTITY_HIDDEN): cv.boolean -}) +CONFIG_ENTITY_SCHEMA = vol.Schema( + { + vol.Optional(CONF_ENTITY_NAME): cv.string, + vol.Optional(CONF_ENTITY_HIDDEN): cv.boolean, + } +) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Optional(CONF_HOST_IP): cv.string, - vol.Optional(CONF_LISTEN_PORT, default=DEFAULT_LISTEN_PORT): cv.port, - vol.Optional(CONF_ADVERTISE_IP): cv.string, - vol.Optional(CONF_ADVERTISE_PORT): cv.port, - vol.Optional(CONF_UPNP_BIND_MULTICAST): cv.boolean, - vol.Optional(CONF_OFF_MAPS_TO_ON_DOMAINS): cv.ensure_list, - vol.Optional(CONF_EXPOSE_BY_DEFAULT): cv.boolean, - vol.Optional(CONF_EXPOSED_DOMAINS): cv.ensure_list, - vol.Optional(CONF_TYPE, default=DEFAULT_TYPE): - vol.Any(TYPE_ALEXA, TYPE_GOOGLE), - vol.Optional(CONF_ENTITIES): - vol.Schema({cv.entity_id: CONFIG_ENTITY_SCHEMA}) - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Optional(CONF_HOST_IP): cv.string, + vol.Optional(CONF_LISTEN_PORT, default=DEFAULT_LISTEN_PORT): cv.port, + vol.Optional(CONF_ADVERTISE_IP): cv.string, + vol.Optional(CONF_ADVERTISE_PORT): cv.port, + vol.Optional(CONF_UPNP_BIND_MULTICAST): cv.boolean, + vol.Optional(CONF_OFF_MAPS_TO_ON_DOMAINS): cv.ensure_list, + vol.Optional(CONF_EXPOSE_BY_DEFAULT): cv.boolean, + vol.Optional(CONF_EXPOSED_DOMAINS): cv.ensure_list, + vol.Optional(CONF_TYPE, default=DEFAULT_TYPE): vol.Any( + TYPE_ALEXA, TYPE_GOOGLE + ), + vol.Optional(CONF_ENTITIES): vol.Schema( + {cv.entity_id: CONFIG_ENTITY_SCHEMA} + ), + } + ) + }, + extra=vol.ALLOW_EXTRA, +) -ATTR_EMULATED_HUE = 'emulated_hue' -ATTR_EMULATED_HUE_NAME = 'emulated_hue_name' -ATTR_EMULATED_HUE_HIDDEN = 'emulated_hue_hidden' +ATTR_EMULATED_HUE = "emulated_hue" +ATTR_EMULATED_HUE_NAME = "emulated_hue_name" +ATTR_EMULATED_HUE_HIDDEN = "emulated_hue_hidden" async def async_setup(hass, yaml_config): @@ -82,7 +99,7 @@ async def async_setup(hass, yaml_config): config = Config(hass, yaml_config.get(DOMAIN, {})) app = web.Application() - app['hass'] = hass + app["hass"] = hass real_ip.setup_real_ip(app, False, []) # We misunderstood the startup signal. You're not allowed to change @@ -103,9 +120,12 @@ async def async_setup(hass, yaml_config): HueGroupView(config).register(app, app.router) upnp_listener = UPNPResponderThread( - config.host_ip_addr, config.listen_port, - config.upnp_bind_multicast, config.advertise_ip, - config.advertise_port) + config.host_ip_addr, + config.listen_port, + config.upnp_bind_multicast, + config.advertise_ip, + config.advertise_port, + ) async def stop_emulated_hue_bridge(event): """Stop the emulated hue bridge.""" @@ -129,14 +149,15 @@ async def async_setup(hass, yaml_config): try: await site.start() except OSError as error: - _LOGGER.error("Failed to create HTTP server at port %d: %s", - config.listen_port, error) + _LOGGER.error( + "Failed to create HTTP server at port %d: %s", config.listen_port, error + ) else: hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_STOP, stop_emulated_hue_bridge) + EVENT_HOMEASSISTANT_STOP, stop_emulated_hue_bridge + ) - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, - start_emulated_hue_bridge) + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, start_emulated_hue_bridge) return True @@ -153,8 +174,9 @@ class Config: if self.type == TYPE_ALEXA: _LOGGER.warning( - 'Emulated Hue running in legacy mode because type has been ' - 'specified. More info at https://goo.gl/M6tgz8') + "Emulated Hue running in legacy mode because type has been " + "specified. More info at https://goo.gl/M6tgz8" + ) # Get the IP address that will be passed to the Echo during discovery self.host_ip_addr = conf.get(CONF_HOST_IP) @@ -162,20 +184,22 @@ class Config: self.host_ip_addr = util.get_local_ip() _LOGGER.info( "Listen IP address not specified, auto-detected address is %s", - self.host_ip_addr) + self.host_ip_addr, + ) # Get the port that the Hue bridge will listen on self.listen_port = conf.get(CONF_LISTEN_PORT) if not isinstance(self.listen_port, int): self.listen_port = DEFAULT_LISTEN_PORT _LOGGER.info( - "Listen port not specified, defaulting to %s", - self.listen_port) + "Listen port not specified, defaulting to %s", self.listen_port + ) # Get whether or not UPNP binds to multicast address (239.255.255.250) # or to the unicast address (host_ip_addr) self.upnp_bind_multicast = conf.get( - CONF_UPNP_BIND_MULTICAST, DEFAULT_UPNP_BIND_MULTICAST) + CONF_UPNP_BIND_MULTICAST, DEFAULT_UPNP_BIND_MULTICAST + ) # Get domains that cause both "on" and "off" commands to map to "on" # This is primarily useful for things like scenes or scripts, which @@ -187,19 +211,17 @@ class Config: # Get whether or not entities should be exposed by default, or if only # explicitly marked ones will be exposed self.expose_by_default = conf.get( - CONF_EXPOSE_BY_DEFAULT, DEFAULT_EXPOSE_BY_DEFAULT) + CONF_EXPOSE_BY_DEFAULT, DEFAULT_EXPOSE_BY_DEFAULT + ) # Get domains that are exposed by default when expose_by_default is # True - self.exposed_domains = conf.get( - CONF_EXPOSED_DOMAINS, DEFAULT_EXPOSED_DOMAINS) + self.exposed_domains = conf.get(CONF_EXPOSED_DOMAINS, DEFAULT_EXPOSED_DOMAINS) # Calculated effective advertised IP and port for network isolation - self.advertise_ip = conf.get( - CONF_ADVERTISE_IP) or self.host_ip_addr + self.advertise_ip = conf.get(CONF_ADVERTISE_IP) or self.host_ip_addr - self.advertise_port = conf.get( - CONF_ADVERTISE_PORT) or self.listen_port + self.advertise_port = conf.get(CONF_ADVERTISE_PORT) or self.listen_port self.entities = conf.get(CONF_ENTITIES, {}) @@ -216,7 +238,7 @@ class Config: if entity_id == ent_id: return number - number = '1' + number = "1" if self.numbers: number = str(max(int(k) for k in self.numbers) + 1) self.numbers[number] = entity_id @@ -237,8 +259,10 @@ class Config: def get_entity_name(self, entity): """Get the name of an entity.""" - if entity.entity_id in self.entities and \ - CONF_ENTITY_NAME in self.entities[entity.entity_id]: + if ( + entity.entity_id in self.entities + and CONF_ENTITY_NAME in self.entities[entity.entity_id] + ): return self.entities[entity.entity_id][CONF_ENTITY_NAME] return entity.attributes.get(ATTR_EMULATED_HUE_NAME, entity.name) @@ -248,7 +272,7 @@ class Config: Async friendly. """ - if entity.attributes.get('view') is not None: + if entity.attributes.get("view") is not None: # Ignore entities that are views return False @@ -256,10 +280,11 @@ class Config: explicit_expose = entity.attributes.get(ATTR_EMULATED_HUE, None) explicit_hidden = entity.attributes.get(ATTR_EMULATED_HUE_HIDDEN, None) - if entity.entity_id in self.entities and \ - CONF_ENTITY_HIDDEN in self.entities[entity.entity_id]: - explicit_hidden = \ - self.entities[entity.entity_id][CONF_ENTITY_HIDDEN] + if ( + entity.entity_id in self.entities + and CONF_ENTITY_HIDDEN in self.entities[entity.entity_id] + ): + explicit_hidden = self.entities[entity.entity_id][CONF_ENTITY_HIDDEN] if explicit_expose is True or explicit_hidden is False: expose = True @@ -267,16 +292,17 @@ class Config: expose = False else: expose = None - get_deprecated(entity.attributes, ATTR_EMULATED_HUE_HIDDEN, - ATTR_EMULATED_HUE, None) - domain_exposed_by_default = \ + get_deprecated( + entity.attributes, ATTR_EMULATED_HUE_HIDDEN, ATTR_EMULATED_HUE, None + ) + domain_exposed_by_default = ( self.expose_by_default and domain in self.exposed_domains + ) # Expose an entity if the entity's domain is exposed by default and # the configuration doesn't explicitly exclude it from being # exposed, or if the entity is explicitly exposed - is_default_exposed = \ - domain_exposed_by_default and expose is not False + is_default_exposed = domain_exposed_by_default and expose is not False return is_default_exposed or expose diff --git a/homeassistant/components/emulated_hue/hue_api.py b/homeassistant/components/emulated_hue/hue_api.py index 3d51848686e..6d59d777e8b 100644 --- a/homeassistant/components/emulated_hue/hue_api.py +++ b/homeassistant/components/emulated_hue/hue_api.py @@ -5,33 +5,66 @@ from aiohttp import web from homeassistant import core from homeassistant.components import ( - climate, cover, fan, light, media_player, scene, script) + climate, + cover, + fan, + light, + media_player, + scene, + script, +) from homeassistant.components.climate.const import ( - SERVICE_SET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE) + SERVICE_SET_TEMPERATURE, + SUPPORT_TARGET_TEMPERATURE, +) from homeassistant.components.cover import ( - ATTR_CURRENT_POSITION, ATTR_POSITION, SERVICE_SET_COVER_POSITION, - SUPPORT_SET_POSITION) + ATTR_CURRENT_POSITION, + ATTR_POSITION, + SERVICE_SET_COVER_POSITION, + SUPPORT_SET_POSITION, +) from homeassistant.components.fan import ( - ATTR_SPEED, SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, SPEED_OFF, - SUPPORT_SET_SPEED) + ATTR_SPEED, + SPEED_HIGH, + SPEED_LOW, + SPEED_MEDIUM, + SPEED_OFF, + SUPPORT_SET_SPEED, +) from homeassistant.components.http import HomeAssistantView from homeassistant.components.http.const import KEY_REAL_IP from homeassistant.components.light import ( - ATTR_BRIGHTNESS, ATTR_HS_COLOR, SUPPORT_BRIGHTNESS, SUPPORT_COLOR) + ATTR_BRIGHTNESS, + ATTR_HS_COLOR, + SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, +) from homeassistant.components.media_player.const import ( - ATTR_MEDIA_VOLUME_LEVEL, SUPPORT_VOLUME_SET) + ATTR_MEDIA_VOLUME_LEVEL, + SUPPORT_VOLUME_SET, +) from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE, - HTTP_BAD_REQUEST, HTTP_NOT_FOUND, SERVICE_CLOSE_COVER, SERVICE_OPEN_COVER, - SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_VOLUME_SET, STATE_OFF, STATE_ON) + ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, + ATTR_TEMPERATURE, + HTTP_BAD_REQUEST, + HTTP_NOT_FOUND, + SERVICE_CLOSE_COVER, + SERVICE_OPEN_COVER, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + SERVICE_VOLUME_SET, + STATE_OFF, + STATE_ON, +) from homeassistant.util.network import is_local _LOGGER = logging.getLogger(__name__) -HUE_API_STATE_ON = 'on' -HUE_API_STATE_BRI = 'bri' -HUE_API_STATE_HUE = 'hue' -HUE_API_STATE_SAT = 'sat' +HUE_API_STATE_ON = "on" +HUE_API_STATE_BRI = "bri" +HUE_API_STATE_HUE = "hue" +HUE_API_STATE_SAT = "sat" HUE_API_STATE_HUE_MAX = 65535.0 HUE_API_STATE_SAT_MAX = 254.0 @@ -45,9 +78,9 @@ STATE_SATURATION = HUE_API_STATE_SAT class HueUsernameView(HomeAssistantView): """Handle requests to create a username for the emulated hue bridge.""" - url = '/api' - name = 'emulated_hue:api:create_username' - extra_urls = ['/api/'] + url = "/api" + name = "emulated_hue:api:create_username" + extra_urls = ["/api/"] requires_auth = False async def post(self, request): @@ -55,24 +88,22 @@ class HueUsernameView(HomeAssistantView): try: data = await request.json() except ValueError: - return self.json_message('Invalid JSON', HTTP_BAD_REQUEST) + return self.json_message("Invalid JSON", HTTP_BAD_REQUEST) - if 'devicetype' not in data: - return self.json_message('devicetype not specified', - HTTP_BAD_REQUEST) + if "devicetype" not in data: + return self.json_message("devicetype not specified", HTTP_BAD_REQUEST) if not is_local(request[KEY_REAL_IP]): - return self.json_message('only local IPs allowed', - HTTP_BAD_REQUEST) + return self.json_message("only local IPs allowed", HTTP_BAD_REQUEST) - return self.json([{'success': {'username': '12345678901234567890'}}]) + return self.json([{"success": {"username": "12345678901234567890"}}]) class HueAllGroupsStateView(HomeAssistantView): """Group handler.""" - url = '/api/{username}/groups' - name = 'emulated_hue:all_groups:state' + url = "/api/{username}/groups" + name = "emulated_hue:all_groups:state" requires_auth = False def __init__(self, config): @@ -83,18 +114,16 @@ class HueAllGroupsStateView(HomeAssistantView): def get(self, request, username): """Process a request to make the Brilliant Lightpad work.""" if not is_local(request[KEY_REAL_IP]): - return self.json_message('only local IPs allowed', - HTTP_BAD_REQUEST) + return self.json_message("only local IPs allowed", HTTP_BAD_REQUEST) - return self.json({ - }) + return self.json({}) class HueGroupView(HomeAssistantView): """Group handler to get Logitech Pop working.""" - url = '/api/{username}/groups/0/action' - name = 'emulated_hue:groups:state' + url = "/api/{username}/groups/0/action" + name = "emulated_hue:groups:state" requires_auth = False def __init__(self, config): @@ -105,23 +134,26 @@ class HueGroupView(HomeAssistantView): def put(self, request, username): """Process a request to make the Logitech Pop working.""" if not is_local(request[KEY_REAL_IP]): - return self.json_message('only local IPs allowed', - HTTP_BAD_REQUEST) + return self.json_message("only local IPs allowed", HTTP_BAD_REQUEST) - return self.json([{ - 'error': { - 'address': '/groups/0/action/scene', - 'type': 7, - 'description': 'invalid value, dummy for parameter, scene' - } - }]) + return self.json( + [ + { + "error": { + "address": "/groups/0/action/scene", + "type": 7, + "description": "invalid value, dummy for parameter, scene", + } + } + ] + ) class HueAllLightsStateView(HomeAssistantView): """Handle requests for getting and setting info about entities.""" - url = '/api/{username}/lights' - name = 'emulated_hue:lights:state' + url = "/api/{username}/lights" + name = "emulated_hue:lights:state" requires_auth = False def __init__(self, config): @@ -132,10 +164,9 @@ class HueAllLightsStateView(HomeAssistantView): def get(self, request, username): """Process a request to get the list of available lights.""" if not is_local(request[KEY_REAL_IP]): - return self.json_message('only local IPs allowed', - HTTP_BAD_REQUEST) + return self.json_message("only local IPs allowed", HTTP_BAD_REQUEST) - hass = request.app['hass'] + hass = request.app["hass"] json_response = {} for entity in hass.states.async_all(): @@ -143,8 +174,7 @@ class HueAllLightsStateView(HomeAssistantView): state = get_entity_state(self.config, entity) number = self.config.entity_id_to_number(entity.entity_id) - json_response[number] = entity_to_json(self.config, - entity, state) + json_response[number] = entity_to_json(self.config, entity, state) return self.json(json_response) @@ -152,8 +182,8 @@ class HueAllLightsStateView(HomeAssistantView): class HueOneLightStateView(HomeAssistantView): """Handle requests for getting and setting info about entities.""" - url = '/api/{username}/lights/{entity_id}' - name = 'emulated_hue:light:state' + url = "/api/{username}/lights/{entity_id}" + name = "emulated_hue:light:state" requires_auth = False def __init__(self, config): @@ -164,19 +194,18 @@ class HueOneLightStateView(HomeAssistantView): def get(self, request, username, entity_id): """Process a request to get the state of an individual light.""" if not is_local(request[KEY_REAL_IP]): - return self.json_message('only local IPs allowed', - HTTP_BAD_REQUEST) + return self.json_message("only local IPs allowed", HTTP_BAD_REQUEST) - hass = request.app['hass'] + hass = request.app["hass"] entity_id = self.config.number_to_entity_id(entity_id) entity = hass.states.get(entity_id) if entity is None: - _LOGGER.error('Entity not found: %s', entity_id) + _LOGGER.error("Entity not found: %s", entity_id) return web.Response(text="Entity not found", status=404) if not self.config.is_entity_exposed(entity): - _LOGGER.error('Entity not exposed: %s', entity_id) + _LOGGER.error("Entity not exposed: %s", entity_id) return web.Response(text="Entity not exposed", status=404) state = get_entity_state(self.config, entity) @@ -189,8 +218,8 @@ class HueOneLightStateView(HomeAssistantView): class HueOneLightChangeView(HomeAssistantView): """Handle requests for getting and setting info about entities.""" - url = '/api/{username}/lights/{entity_number}/state' - name = 'emulated_hue:light:state' + url = "/api/{username}/lights/{entity_number}/state" + name = "emulated_hue:light:state" requires_auth = False def __init__(self, config): @@ -200,38 +229,37 @@ class HueOneLightChangeView(HomeAssistantView): async def put(self, request, username, entity_number): """Process a request to set the state of an individual light.""" if not is_local(request[KEY_REAL_IP]): - return self.json_message('only local IPs allowed', - HTTP_BAD_REQUEST) + return self.json_message("only local IPs allowed", HTTP_BAD_REQUEST) config = self.config - hass = request.app['hass'] + hass = request.app["hass"] entity_id = config.number_to_entity_id(entity_number) if entity_id is None: - _LOGGER.error('Unknown entity number: %s', entity_number) - return self.json_message('Entity not found', HTTP_NOT_FOUND) + _LOGGER.error("Unknown entity number: %s", entity_number) + return self.json_message("Entity not found", HTTP_NOT_FOUND) entity = hass.states.get(entity_id) if entity is None: - _LOGGER.error('Entity not found: %s', entity_id) - return self.json_message('Entity not found', HTTP_NOT_FOUND) + _LOGGER.error("Entity not found: %s", entity_id) + return self.json_message("Entity not found", HTTP_NOT_FOUND) if not config.is_entity_exposed(entity): - _LOGGER.error('Entity not exposed: %s', entity_id) + _LOGGER.error("Entity not exposed: %s", entity_id) return web.Response(text="Entity not exposed", status=404) try: request_json = await request.json() except ValueError: - _LOGGER.error('Received invalid json') - return self.json_message('Invalid JSON', HTTP_BAD_REQUEST) + _LOGGER.error("Received invalid json") + return self.json_message("Invalid JSON", HTTP_BAD_REQUEST) # Parse the request into requested "on" status and brightness parsed = parse_hue_api_put_light_body(request_json, entity) if parsed is None: - _LOGGER.error('Unable to parse data: %s', request_json) + _LOGGER.error("Unable to parse data: %s", request_json) return web.Response(text="Bad request", status=400) # Choose general HA domain @@ -270,12 +298,12 @@ class HueOneLightChangeView(HomeAssistantView): # If the requested entity is a script add some variables elif entity.domain == script.DOMAIN: - data['variables'] = { - 'requested_state': STATE_ON if parsed[STATE_ON] else STATE_OFF + data["variables"] = { + "requested_state": STATE_ON if parsed[STATE_ON] else STATE_OFF } if parsed[STATE_BRIGHTNESS] is not None: - data['variables']['requested_level'] = parsed[STATE_BRIGHTNESS] + data["variables"]["requested_level"] = parsed[STATE_BRIGHTNESS] # If the requested entity is a climate, set the temperature elif entity.domain == climate.DOMAIN: @@ -297,8 +325,7 @@ class HueOneLightChangeView(HomeAssistantView): domain = entity.domain service = SERVICE_VOLUME_SET # Convert 0-100 to 0.0-1.0 - data[ATTR_MEDIA_VOLUME_LEVEL] = \ - parsed[STATE_BRIGHTNESS] / 100.0 + data[ATTR_MEDIA_VOLUME_LEVEL] = parsed[STATE_BRIGHTNESS] / 100.0 # If the requested entity is a cover, convert to open_cover/close_cover elif entity.domain == cover.DOMAIN: @@ -343,27 +370,42 @@ class HueOneLightChangeView(HomeAssistantView): # Separate call to turn on needed if turn_on_needed: - hass.async_create_task(hass.services.async_call( - core.DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id}, - blocking=True)) + hass.async_create_task( + hass.services.async_call( + core.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity_id}, + blocking=True, + ) + ) if service is not None: - hass.async_create_task(hass.services.async_call( - domain, service, data, blocking=True)) + hass.async_create_task( + hass.services.async_call(domain, service, data, blocking=True) + ) - json_response = \ - [create_hue_success_response( - entity_id, HUE_API_STATE_ON, parsed[STATE_ON])] + json_response = [ + create_hue_success_response(entity_id, HUE_API_STATE_ON, parsed[STATE_ON]) + ] if parsed[STATE_BRIGHTNESS] is not None: - json_response.append(create_hue_success_response( - entity_id, HUE_API_STATE_BRI, parsed[STATE_BRIGHTNESS])) + json_response.append( + create_hue_success_response( + entity_id, HUE_API_STATE_BRI, parsed[STATE_BRIGHTNESS] + ) + ) if parsed[STATE_HUE] is not None: - json_response.append(create_hue_success_response( - entity_id, HUE_API_STATE_HUE, parsed[STATE_HUE])) + json_response.append( + create_hue_success_response( + entity_id, HUE_API_STATE_HUE, parsed[STATE_HUE] + ) + ) if parsed[STATE_SATURATION] is not None: - json_response.append(create_hue_success_response( - entity_id, HUE_API_STATE_SAT, parsed[STATE_SATURATION])) + json_response.append( + create_hue_success_response( + entity_id, HUE_API_STATE_SAT, parsed[STATE_SATURATION] + ) + ) return self.json(json_response) @@ -396,32 +438,32 @@ def parse_hue_api_put_light_body(request_json, entity): if HUE_API_STATE_HUE in request_json: try: # Clamp brightness from 0 to 65535 - data[STATE_HUE] = \ - max(0, min(int(request_json[HUE_API_STATE_HUE]), - HUE_API_STATE_HUE_MAX)) + data[STATE_HUE] = max( + 0, min(int(request_json[HUE_API_STATE_HUE]), HUE_API_STATE_HUE_MAX) + ) except ValueError: return None if HUE_API_STATE_SAT in request_json: try: # Clamp saturation from 0 to 254 - data[STATE_SATURATION] = \ - max(0, min(int(request_json[HUE_API_STATE_SAT]), - HUE_API_STATE_SAT_MAX)) + data[STATE_SATURATION] = max( + 0, min(int(request_json[HUE_API_STATE_SAT]), HUE_API_STATE_SAT_MAX) + ) except ValueError: return None if HUE_API_STATE_BRI in request_json: try: # Clamp brightness from 0 to 255 - data[STATE_BRIGHTNESS] = \ - max(0, min(int(request_json[HUE_API_STATE_BRI]), - HUE_API_STATE_BRI_MAX)) + data[STATE_BRIGHTNESS] = max( + 0, min(int(request_json[HUE_API_STATE_BRI]), HUE_API_STATE_BRI_MAX) + ) except ValueError: return None if entity.domain == light.DOMAIN: - data[STATE_ON] = (data[STATE_BRIGHTNESS] > 0) + data[STATE_ON] = data[STATE_BRIGHTNESS] > 0 if not entity_features & SUPPORT_BRIGHTNESS: data[STATE_BRIGHTNESS] = None @@ -430,8 +472,12 @@ def parse_hue_api_put_light_body(request_json, entity): data[STATE_ON] = True elif entity.domain in [ - script.DOMAIN, media_player.DOMAIN, - fan.DOMAIN, cover.DOMAIN, climate.DOMAIN]: + script.DOMAIN, + media_player.DOMAIN, + fan.DOMAIN, + cover.DOMAIN, + climate.DOMAIN, + ]: # Convert 0-255 to 0-100 level = (data[STATE_BRIGHTNESS] / HUE_API_STATE_BRI_MAX) * 100 data[STATE_BRIGHTNESS] = round(level) @@ -447,7 +493,7 @@ def get_entity_state(config, entity): STATE_BRIGHTNESS: None, STATE_HUE: None, STATE_ON: False, - STATE_SATURATION: None + STATE_SATURATION: None, } if cached_state is None: @@ -460,8 +506,7 @@ def get_entity_state(config, entity): sat = hue_sat[1] # convert hass hs values back to hue hs values data[STATE_HUE] = int((hue / 360.0) * HUE_API_STATE_HUE_MAX) - data[STATE_SATURATION] = \ - int((sat / 100.0) * HUE_API_STATE_SAT_MAX) + data[STATE_SATURATION] = int((sat / 100.0) * HUE_API_STATE_SAT_MAX) else: data[STATE_BRIGHTNESS] = 0 data[STATE_HUE] = 0 @@ -480,10 +525,10 @@ def get_entity_state(config, entity): data[STATE_BRIGHTNESS] = round(temperature * 255 / 100) elif entity.domain == media_player.DOMAIN: level = entity.attributes.get( - ATTR_MEDIA_VOLUME_LEVEL, 1.0 if data[STATE_ON] else 0.0) + ATTR_MEDIA_VOLUME_LEVEL, 1.0 if data[STATE_ON] else 0.0 + ) # Convert 0.0-1.0 to 0-255 - data[STATE_BRIGHTNESS] = \ - round(min(1.0, level) * HUE_API_STATE_BRI_MAX) + data[STATE_BRIGHTNESS] = round(min(1.0, level) * HUE_API_STATE_BRI_MAX) elif entity.domain == fan.DOMAIN: speed = entity.attributes.get(ATTR_SPEED, 0) # Convert 0.0-1.0 to 0-255 @@ -520,35 +565,30 @@ def entity_to_json(config, entity, state): entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) if entity_features & SUPPORT_BRIGHTNESS: return { - 'state': - { + "state": { HUE_API_STATE_ON: state[STATE_ON], HUE_API_STATE_BRI: state[STATE_BRIGHTNESS], HUE_API_STATE_HUE: state[STATE_HUE], HUE_API_STATE_SAT: state[STATE_SATURATION], - 'reachable': True + "reachable": True, }, - 'type': 'Dimmable light', - 'name': config.get_entity_name(entity), - 'modelid': 'HASS123', - 'uniqueid': entity.entity_id, - 'swversion': '123' - } - return { - 'state': - { - HUE_API_STATE_ON: state[STATE_ON], - 'reachable': True - }, - 'type': 'On/off light', - 'name': config.get_entity_name(entity), - 'modelid': 'HASS123', - 'uniqueid': entity.entity_id, - 'swversion': '123' + "type": "Dimmable light", + "name": config.get_entity_name(entity), + "modelid": "HASS123", + "uniqueid": entity.entity_id, + "swversion": "123", } + return { + "state": {HUE_API_STATE_ON: state[STATE_ON], "reachable": True}, + "type": "On/off light", + "name": config.get_entity_name(entity), + "modelid": "HASS123", + "uniqueid": entity.entity_id, + "swversion": "123", + } def create_hue_success_response(entity_id, attr, value): """Create a success response for an attribute set on a light.""" - success_key = '/lights/{}/state/{}'.format(entity_id, attr) - return {'success': {success_key: value}} + success_key = "/lights/{}/state/{}".format(entity_id, attr) + return {"success": {success_key: value}} diff --git a/homeassistant/components/emulated_hue/upnp.py b/homeassistant/components/emulated_hue/upnp.py index a163d4b2e91..412dfdd673e 100644 --- a/homeassistant/components/emulated_hue/upnp.py +++ b/homeassistant/components/emulated_hue/upnp.py @@ -15,8 +15,8 @@ _LOGGER = logging.getLogger(__name__) class DescriptionXmlView(HomeAssistantView): """Handles requests for the description.xml file.""" - url = '/description.xml' - name = 'description:xml' + url = "/description.xml" + name = "description:xml" requires_auth = False def __init__(self, config): @@ -49,9 +49,10 @@ class DescriptionXmlView(HomeAssistantView): """ resp_text = xml_template.format( - self.config.advertise_ip, self.config.advertise_port) + self.config.advertise_ip, self.config.advertise_port + ) - return web.Response(text=resp_text, content_type='text/xml') + return web.Response(text=resp_text, content_type="text/xml") class UPNPResponderThread(threading.Thread): @@ -59,8 +60,14 @@ class UPNPResponderThread(threading.Thread): _interrupted = False - def __init__(self, host_ip_addr, listen_port, upnp_bind_multicast, - advertise_ip, advertise_port): + def __init__( + self, + host_ip_addr, + listen_port, + upnp_bind_multicast, + advertise_ip, + advertise_port, + ): """Initialize the class.""" threading.Thread.__init__(self) @@ -81,9 +88,11 @@ USN: uuid:Socket-1_0-221438K0100073::urn:schemas-upnp-org:device:basic:1 """ - self.upnp_response = resp_template.format( - advertise_ip, advertise_port).replace("\n", "\r\n") \ - .encode('utf-8') + self.upnp_response = ( + resp_template.format(advertise_ip, advertise_port) + .replace("\n", "\r\n") + .encode("utf-8") + ) def run(self): """Run the server.""" @@ -95,15 +104,14 @@ USN: uuid:Socket-1_0-221438K0100073::urn:schemas-upnp-org:device:basic:1 ssdp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) ssdp_socket.setsockopt( - socket.SOL_IP, - socket.IP_MULTICAST_IF, - socket.inet_aton(self.host_ip_addr)) + socket.SOL_IP, socket.IP_MULTICAST_IF, socket.inet_aton(self.host_ip_addr) + ) ssdp_socket.setsockopt( socket.SOL_IP, socket.IP_ADD_MEMBERSHIP, - socket.inet_aton("239.255.255.250") + - socket.inet_aton(self.host_ip_addr)) + socket.inet_aton("239.255.255.250") + socket.inet_aton(self.host_ip_addr), + ) if self.upnp_bind_multicast: ssdp_socket.bind(("", 1900)) @@ -116,9 +124,7 @@ USN: uuid:Socket-1_0-221438K0100073::urn:schemas-upnp-org:device:basic:1 return try: - read, _, _ = select.select( - [ssdp_socket], [], - [ssdp_socket], 2) + read, _, _ = select.select([ssdp_socket], [], [ssdp_socket], 2) if ssdp_socket in read: data, addr = ssdp_socket.recvfrom(1024) @@ -130,16 +136,16 @@ USN: uuid:Socket-1_0-221438K0100073::urn:schemas-upnp-org:device:basic:1 clean_socket_close(ssdp_socket) return - _LOGGER.error("UPNP Responder socket exception occurred: %s", - ex.__str__) + _LOGGER.error( + "UPNP Responder socket exception occurred: %s", ex.__str__ + ) # without the following continue, a second exception occurs # because the data object has not been initialized continue - if "M-SEARCH" in data.decode('utf-8', errors='ignore'): + if "M-SEARCH" in data.decode("utf-8", errors="ignore"): # SSDP M-SEARCH method received, respond to it with our info - resp_socket = socket.socket( - socket.AF_INET, socket.SOCK_DGRAM) + resp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) resp_socket.sendto(self.upnp_response, addr) resp_socket.close() diff --git a/homeassistant/components/emulated_roku/__init__.py b/homeassistant/components/emulated_roku/__init__.py index 72d4dff72db..4e577929644 100644 --- a/homeassistant/components/emulated_roku/__init__.py +++ b/homeassistant/components/emulated_roku/__init__.py @@ -8,24 +8,38 @@ import homeassistant.helpers.config_validation as cv from .binding import EmulatedRoku from .config_flow import configured_servers from .const import ( - CONF_ADVERTISE_IP, CONF_ADVERTISE_PORT, CONF_HOST_IP, CONF_LISTEN_PORT, - CONF_SERVERS, CONF_UPNP_BIND_MULTICAST, DOMAIN) + CONF_ADVERTISE_IP, + CONF_ADVERTISE_PORT, + CONF_HOST_IP, + CONF_LISTEN_PORT, + CONF_SERVERS, + CONF_UPNP_BIND_MULTICAST, + DOMAIN, +) -SERVER_CONFIG_SCHEMA = vol.Schema({ - vol.Required(CONF_NAME): cv.string, - vol.Required(CONF_LISTEN_PORT): cv.port, - vol.Optional(CONF_HOST_IP): cv.string, - vol.Optional(CONF_ADVERTISE_IP): cv.string, - vol.Optional(CONF_ADVERTISE_PORT): cv.port, - vol.Optional(CONF_UPNP_BIND_MULTICAST): cv.boolean -}) +SERVER_CONFIG_SCHEMA = vol.Schema( + { + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_LISTEN_PORT): cv.port, + vol.Optional(CONF_HOST_IP): cv.string, + vol.Optional(CONF_ADVERTISE_IP): cv.string, + vol.Optional(CONF_ADVERTISE_PORT): cv.port, + vol.Optional(CONF_UPNP_BIND_MULTICAST): cv.boolean, + } +) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_SERVERS): - vol.All(cv.ensure_list, [SERVER_CONFIG_SCHEMA]), - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_SERVERS): vol.All( + cv.ensure_list, [SERVER_CONFIG_SCHEMA] + ) + } + ) + }, + extra=vol.ALLOW_EXTRA, +) async def async_setup(hass, config): @@ -39,11 +53,11 @@ async def async_setup(hass, config): for entry in conf[CONF_SERVERS]: if entry[CONF_NAME] not in existing_servers: - hass.async_create_task(hass.config_entries.flow.async_init( - DOMAIN, - context={'source': config_entries.SOURCE_IMPORT}, - data=entry - )) + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=entry + ) + ) return True @@ -62,8 +76,15 @@ async def async_setup_entry(hass, config_entry): advertise_port = config.get(CONF_ADVERTISE_PORT) upnp_bind_multicast = config.get(CONF_UPNP_BIND_MULTICAST) - server = EmulatedRoku(hass, name, host_ip, listen_port, - advertise_ip, advertise_port, upnp_bind_multicast) + server = EmulatedRoku( + hass, + name, + host_ip, + listen_port, + advertise_ip, + advertise_port, + upnp_bind_multicast, + ) hass.data[DOMAIN][name] = server diff --git a/homeassistant/components/emulated_roku/binding.py b/homeassistant/components/emulated_roku/binding.py index b6a6719a37b..4c98af69848 100644 --- a/homeassistant/components/emulated_roku/binding.py +++ b/homeassistant/components/emulated_roku/binding.py @@ -1,30 +1,37 @@ """Bridge between emulated_roku and Home Assistant.""" import logging -from homeassistant.const import ( - EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) +from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP from homeassistant.core import CoreState, EventOrigin LOGGER = logging.getLogger(__package__) -EVENT_ROKU_COMMAND = 'roku_command' +EVENT_ROKU_COMMAND = "roku_command" -ATTR_COMMAND_TYPE = 'type' -ATTR_SOURCE_NAME = 'source_name' -ATTR_KEY = 'key' -ATTR_APP_ID = 'app_id' +ATTR_COMMAND_TYPE = "type" +ATTR_SOURCE_NAME = "source_name" +ATTR_KEY = "key" +ATTR_APP_ID = "app_id" -ROKU_COMMAND_KEYDOWN = 'keydown' -ROKU_COMMAND_KEYUP = 'keyup' -ROKU_COMMAND_KEYPRESS = 'keypress' -ROKU_COMMAND_LAUNCH = 'launch' +ROKU_COMMAND_KEYDOWN = "keydown" +ROKU_COMMAND_KEYUP = "keyup" +ROKU_COMMAND_KEYPRESS = "keypress" +ROKU_COMMAND_LAUNCH = "launch" class EmulatedRoku: """Manages an emulated_roku server.""" - def __init__(self, hass, name, host_ip, listen_port, - advertise_ip, advertise_port, upnp_bind_multicast): + def __init__( + self, + hass, + name, + host_ip, + listen_port, + advertise_ip, + advertise_port, + upnp_bind_multicast, + ): """Initialize the properties.""" self.hass = hass @@ -44,8 +51,7 @@ class EmulatedRoku: async def setup(self): """Start the emulated_roku server.""" - from emulated_roku import EmulatedRokuServer, \ - EmulatedRokuCommandHandler + from emulated_roku import EmulatedRokuServer, EmulatedRokuCommandHandler class EventCommandHandler(EmulatedRokuCommandHandler): """emulated_roku command handler to turn commands into events.""" @@ -55,47 +61,70 @@ class EmulatedRoku: def on_keydown(self, roku_usn, key): """Handle keydown event.""" - self.hass.bus.async_fire(EVENT_ROKU_COMMAND, { - ATTR_SOURCE_NAME: roku_usn, - ATTR_COMMAND_TYPE: ROKU_COMMAND_KEYDOWN, - ATTR_KEY: key - }, EventOrigin.local) + self.hass.bus.async_fire( + EVENT_ROKU_COMMAND, + { + ATTR_SOURCE_NAME: roku_usn, + ATTR_COMMAND_TYPE: ROKU_COMMAND_KEYDOWN, + ATTR_KEY: key, + }, + EventOrigin.local, + ) def on_keyup(self, roku_usn, key): """Handle keyup event.""" - self.hass.bus.async_fire(EVENT_ROKU_COMMAND, { - ATTR_SOURCE_NAME: roku_usn, - ATTR_COMMAND_TYPE: ROKU_COMMAND_KEYUP, - ATTR_KEY: key - }, EventOrigin.local) + self.hass.bus.async_fire( + EVENT_ROKU_COMMAND, + { + ATTR_SOURCE_NAME: roku_usn, + ATTR_COMMAND_TYPE: ROKU_COMMAND_KEYUP, + ATTR_KEY: key, + }, + EventOrigin.local, + ) def on_keypress(self, roku_usn, key): """Handle keypress event.""" - self.hass.bus.async_fire(EVENT_ROKU_COMMAND, { - ATTR_SOURCE_NAME: roku_usn, - ATTR_COMMAND_TYPE: ROKU_COMMAND_KEYPRESS, - ATTR_KEY: key - }, EventOrigin.local) + self.hass.bus.async_fire( + EVENT_ROKU_COMMAND, + { + ATTR_SOURCE_NAME: roku_usn, + ATTR_COMMAND_TYPE: ROKU_COMMAND_KEYPRESS, + ATTR_KEY: key, + }, + EventOrigin.local, + ) def launch(self, roku_usn, app_id): """Handle launch event.""" - self.hass.bus.async_fire(EVENT_ROKU_COMMAND, { - ATTR_SOURCE_NAME: roku_usn, - ATTR_COMMAND_TYPE: ROKU_COMMAND_LAUNCH, - ATTR_APP_ID: app_id - }, EventOrigin.local) + self.hass.bus.async_fire( + EVENT_ROKU_COMMAND, + { + ATTR_SOURCE_NAME: roku_usn, + ATTR_COMMAND_TYPE: ROKU_COMMAND_LAUNCH, + ATTR_APP_ID: app_id, + }, + EventOrigin.local, + ) - LOGGER.debug("Intializing emulated_roku %s on %s:%s", - self.roku_usn, self.host_ip, self.listen_port) + LOGGER.debug( + "Intializing emulated_roku %s on %s:%s", + self.roku_usn, + self.host_ip, + self.listen_port, + ) handler = EventCommandHandler(self.hass) self._api_server = EmulatedRokuServer( - self.hass.loop, handler, - self.roku_usn, self.host_ip, self.listen_port, + self.hass.loop, + handler, + self.roku_usn, + self.host_ip, + self.listen_port, advertise_ip=self.advertise_ip, advertise_port=self.advertise_port, - bind_multicast=self.bind_multicast + bind_multicast=self.bind_multicast, ) async def emulated_roku_stop(event): @@ -111,22 +140,26 @@ class EmulatedRoku: self._unsub_start_listener = None await self._api_server.start() except OSError: - LOGGER.exception("Failed to start Emulated Roku %s on %s:%s", - self.roku_usn, self.host_ip, self.listen_port) + LOGGER.exception( + "Failed to start Emulated Roku %s on %s:%s", + self.roku_usn, + self.host_ip, + self.listen_port, + ) # clean up inconsistent state on errors await emulated_roku_stop(None) else: self._unsub_stop_listener = self.hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_STOP, - emulated_roku_stop) + EVENT_HOMEASSISTANT_STOP, emulated_roku_stop + ) # start immediately if already running if self.hass.state == CoreState.running: await emulated_roku_start(None) else: self._unsub_start_listener = self.hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_START, - emulated_roku_start) + EVENT_HOMEASSISTANT_START, emulated_roku_start + ) return True diff --git a/homeassistant/components/emulated_roku/config_flow.py b/homeassistant/components/emulated_roku/config_flow.py index d08ad09f1c0..0a6d54693ef 100644 --- a/homeassistant/components/emulated_roku/config_flow.py +++ b/homeassistant/components/emulated_roku/config_flow.py @@ -11,8 +11,9 @@ from .const import CONF_LISTEN_PORT, DEFAULT_NAME, DEFAULT_PORT, DOMAIN @callback def configured_servers(hass): """Return a set of the configured servers.""" - return set(entry.data[CONF_NAME] for entry - in hass.config_entries.async_entries(DOMAIN)) + return set( + entry.data[CONF_NAME] for entry in hass.config_entries.async_entries(DOMAIN) + ) @config_entries.HANDLERS.register(DOMAIN) @@ -30,12 +31,9 @@ class EmulatedRokuFlowHandler(config_entries.ConfigFlow): name = user_input[CONF_NAME] if name in configured_servers(self.hass): - return self.async_abort(reason='name_exists') + return self.async_abort(reason="name_exists") - return self.async_create_entry( - title=name, - data=user_input - ) + return self.async_create_entry(title=name, data=user_input) servers_num = len(configured_servers(self.hass)) @@ -47,14 +45,16 @@ class EmulatedRokuFlowHandler(config_entries.ConfigFlow): default_port = DEFAULT_PORT return self.async_show_form( - step_id='user', - data_schema=vol.Schema({ - vol.Required(CONF_NAME, - default=default_name): str, - vol.Required(CONF_LISTEN_PORT, - default=default_port): vol.Coerce(int) - }), - errors=errors + step_id="user", + data_schema=vol.Schema( + { + vol.Required(CONF_NAME, default=default_name): str, + vol.Required(CONF_LISTEN_PORT, default=default_port): vol.Coerce( + int + ), + } + ), + errors=errors, ) async def async_step_import(self, import_config): diff --git a/homeassistant/components/emulated_roku/const.py b/homeassistant/components/emulated_roku/const.py index 25ea3adaa84..6c780367188 100644 --- a/homeassistant/components/emulated_roku/const.py +++ b/homeassistant/components/emulated_roku/const.py @@ -1,12 +1,12 @@ """Constants for the emulated_roku component.""" -DOMAIN = 'emulated_roku' +DOMAIN = "emulated_roku" -CONF_SERVERS = 'servers' -CONF_LISTEN_PORT = 'listen_port' -CONF_HOST_IP = 'host_ip' -CONF_ADVERTISE_IP = 'advertise_ip' -CONF_ADVERTISE_PORT = 'advertise_port' -CONF_UPNP_BIND_MULTICAST = 'upnp_bind_multicast' +CONF_SERVERS = "servers" +CONF_LISTEN_PORT = "listen_port" +CONF_HOST_IP = "host_ip" +CONF_ADVERTISE_IP = "advertise_ip" +CONF_ADVERTISE_PORT = "advertise_port" +CONF_UPNP_BIND_MULTICAST = "upnp_bind_multicast" DEFAULT_NAME = "Home Assistant" DEFAULT_PORT = 8060 diff --git a/homeassistant/components/enigma2/media_player.py b/homeassistant/components/enigma2/media_player.py index 4662c707637..5b0e705f392 100644 --- a/homeassistant/components/enigma2/media_player.py +++ b/homeassistant/components/enigma2/media_player.py @@ -4,57 +4,84 @@ import logging import voluptuous as vol from homeassistant.components.media_player import MediaPlayerDevice -from homeassistant.helpers.config_validation import (PLATFORM_SCHEMA) +from homeassistant.helpers.config_validation import PLATFORM_SCHEMA from homeassistant.components.media_player.const import ( - SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_ON, - SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_STOP, - SUPPORT_SELECT_SOURCE, SUPPORT_VOLUME_STEP, MEDIA_TYPE_TVSHOW) + SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_TURN_ON, + SUPPORT_TURN_OFF, + SUPPORT_VOLUME_MUTE, + SUPPORT_VOLUME_SET, + SUPPORT_STOP, + SUPPORT_SELECT_SOURCE, + SUPPORT_VOLUME_STEP, + MEDIA_TYPE_TVSHOW, +) from homeassistant.const import ( - CONF_HOST, CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_SSL, - STATE_OFF, STATE_ON, STATE_PLAYING, CONF_PORT) + CONF_HOST, + CONF_NAME, + CONF_USERNAME, + CONF_PASSWORD, + CONF_SSL, + STATE_OFF, + STATE_ON, + STATE_PLAYING, + CONF_PORT, +) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -ATTR_MEDIA_CURRENTLY_RECORDING = 'media_currently_recording' -ATTR_MEDIA_DESCRIPTION = 'media_description' -ATTR_MEDIA_END_TIME = 'media_end_time' -ATTR_MEDIA_START_TIME = 'media_start_time' +ATTR_MEDIA_CURRENTLY_RECORDING = "media_currently_recording" +ATTR_MEDIA_DESCRIPTION = "media_description" +ATTR_MEDIA_END_TIME = "media_end_time" +ATTR_MEDIA_START_TIME = "media_start_time" CONF_USE_CHANNEL_ICON = "use_channel_icon" CONF_DEEP_STANDBY = "deep_standby" CONF_MAC_ADDRESS = "mac_address" CONF_SOURCE_BOUQUET = "source_bouquet" -DEFAULT_NAME = 'Enigma2 Media Player' +DEFAULT_NAME = "Enigma2 Media Player" DEFAULT_PORT = 80 DEFAULT_SSL = False DEFAULT_USE_CHANNEL_ICON = False -DEFAULT_USERNAME = 'root' -DEFAULT_PASSWORD = 'dreambox' +DEFAULT_USERNAME = "root" +DEFAULT_PASSWORD = "dreambox" DEFAULT_DEEP_STANDBY = False -DEFAULT_MAC_ADDRESS = '' -DEFAULT_SOURCE_BOUQUET = '' +DEFAULT_MAC_ADDRESS = "" +DEFAULT_SOURCE_BOUQUET = "" -SUPPORTED_ENIGMA2 = SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ - SUPPORT_TURN_OFF | SUPPORT_NEXT_TRACK | SUPPORT_STOP | \ - SUPPORT_PREVIOUS_TRACK | SUPPORT_VOLUME_STEP | \ - SUPPORT_TURN_ON | SUPPORT_PAUSE | SUPPORT_SELECT_SOURCE +SUPPORTED_ENIGMA2 = ( + SUPPORT_VOLUME_SET + | SUPPORT_VOLUME_MUTE + | SUPPORT_TURN_OFF + | SUPPORT_NEXT_TRACK + | SUPPORT_STOP + | SUPPORT_PREVIOUS_TRACK + | SUPPORT_VOLUME_STEP + | SUPPORT_TURN_ON + | SUPPORT_PAUSE + | SUPPORT_SELECT_SOURCE +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, - vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string, - vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean, - vol.Optional(CONF_USE_CHANNEL_ICON, - default=DEFAULT_USE_CHANNEL_ICON): cv.boolean, - vol.Optional(CONF_DEEP_STANDBY, default=DEFAULT_DEEP_STANDBY): cv.boolean, - vol.Optional(CONF_MAC_ADDRESS, default=DEFAULT_MAC_ADDRESS): cv.string, - vol.Optional(CONF_SOURCE_BOUQUET, - default=DEFAULT_SOURCE_BOUQUET): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, + vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string, + vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean, + vol.Optional( + CONF_USE_CHANNEL_ICON, default=DEFAULT_USE_CHANNEL_ICON + ): cv.boolean, + vol.Optional(CONF_DEEP_STANDBY, default=DEFAULT_DEEP_STANDBY): cv.boolean, + vol.Optional(CONF_MAC_ADDRESS, default=DEFAULT_MAC_ADDRESS): cv.string, + vol.Optional(CONF_SOURCE_BOUQUET, default=DEFAULT_SOURCE_BOUQUET): cv.string, + } +) def setup_platform(hass, config, add_devices, discovery_info=None): @@ -64,8 +91,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # which is not useful as OpenWebif never runs on that port. # So use the default port instead. config[CONF_PORT] = DEFAULT_PORT - config[CONF_NAME] = discovery_info['hostname'] - config[CONF_HOST] = discovery_info['host'] + config[CONF_NAME] = discovery_info["hostname"] + config[CONF_HOST] = discovery_info["host"] config[CONF_USERNAME] = DEFAULT_USERNAME config[CONF_PASSWORD] = DEFAULT_PASSWORD config[CONF_SSL] = DEFAULT_SSL @@ -75,16 +102,18 @@ def setup_platform(hass, config, add_devices, discovery_info=None): config[CONF_SOURCE_BOUQUET] = DEFAULT_SOURCE_BOUQUET from openwebif.api import CreateDevice - device = \ - CreateDevice(host=config[CONF_HOST], - port=config.get(CONF_PORT), - username=config.get(CONF_USERNAME), - password=config.get(CONF_PASSWORD), - is_https=config.get(CONF_SSL), - prefer_picon=config.get(CONF_USE_CHANNEL_ICON), - mac_address=config.get(CONF_MAC_ADDRESS), - turn_off_to_deep=config.get(CONF_DEEP_STANDBY), - source_bouquet=config.get(CONF_SOURCE_BOUQUET)) + + device = CreateDevice( + host=config[CONF_HOST], + port=config.get(CONF_PORT), + username=config.get(CONF_USERNAME), + password=config.get(CONF_PASSWORD), + is_https=config.get(CONF_SSL), + prefer_picon=config.get(CONF_USE_CHANNEL_ICON), + mac_address=config.get(CONF_MAC_ADDRESS), + turn_off_to_deep=config.get(CONF_DEEP_STANDBY), + source_bouquet=config.get(CONF_SOURCE_BOUQUET), + ) add_devices([Enigma2Device(config[CONF_NAME], device)], True) @@ -227,13 +256,15 @@ class Enigma2Device(MediaPlayerDevice): """ attributes = {} if not self.e2_box.in_standby: - attributes[ATTR_MEDIA_CURRENTLY_RECORDING] = \ - self.e2_box.status_info['isRecording'] - attributes[ATTR_MEDIA_DESCRIPTION] = \ - self.e2_box.status_info['currservice_fulldescription'] - attributes[ATTR_MEDIA_START_TIME] = \ - self.e2_box.status_info['currservice_begin'] - attributes[ATTR_MEDIA_END_TIME] = \ - self.e2_box.status_info['currservice_end'] + attributes[ATTR_MEDIA_CURRENTLY_RECORDING] = self.e2_box.status_info[ + "isRecording" + ] + attributes[ATTR_MEDIA_DESCRIPTION] = self.e2_box.status_info[ + "currservice_fulldescription" + ] + attributes[ATTR_MEDIA_START_TIME] = self.e2_box.status_info[ + "currservice_begin" + ] + attributes[ATTR_MEDIA_END_TIME] = self.e2_box.status_info["currservice_end"] return attributes diff --git a/homeassistant/components/enocean/__init__.py b/homeassistant/components/enocean/__init__.py index 9d51821082a..b75c8f001c0 100644 --- a/homeassistant/components/enocean/__init__.py +++ b/homeassistant/components/enocean/__init__.py @@ -9,17 +9,15 @@ import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DOMAIN = 'enocean' -DATA_ENOCEAN = 'enocean' +DOMAIN = "enocean" +DATA_ENOCEAN = "enocean" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_DEVICE): cv.string, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + {DOMAIN: vol.Schema({vol.Required(CONF_DEVICE): cv.string})}, extra=vol.ALLOW_EXTRA +) -SIGNAL_RECEIVE_MESSAGE = 'enocean.receive_message' -SIGNAL_SEND_MESSAGE = 'enocean.send_message' +SIGNAL_RECEIVE_MESSAGE = "enocean.receive_message" +SIGNAL_SEND_MESSAGE = "enocean.send_message" def setup(hass, config): @@ -37,12 +35,13 @@ class EnOceanDongle: def __init__(self, hass, ser): """Initialize the EnOcean dongle.""" from enocean.communicators.serialcommunicator import SerialCommunicator - self.__communicator = SerialCommunicator( - port=ser, callback=self.callback) + + self.__communicator = SerialCommunicator(port=ser, callback=self.callback) self.__communicator.start() self.hass = hass self.hass.helpers.dispatcher.dispatcher_connect( - SIGNAL_SEND_MESSAGE, self._send_message_callback) + SIGNAL_SEND_MESSAGE, self._send_message_callback + ) def _send_message_callback(self, command): """Send a command through the EnOcean dongle.""" @@ -55,10 +54,10 @@ class EnOceanDongle: is an incoming packet. """ from enocean.protocol.packet import RadioPacket + if isinstance(packet, RadioPacket): _LOGGER.debug("Received radio packet: %s", packet) - self.hass.helpers.dispatcher.dispatcher_send( - SIGNAL_RECEIVE_MESSAGE, packet) + self.hass.helpers.dispatcher.dispatcher_send(SIGNAL_RECEIVE_MESSAGE, packet) class EnOceanDevice(Entity): @@ -72,11 +71,13 @@ class EnOceanDevice(Entity): async def async_added_to_hass(self): """Register callbacks.""" self.hass.helpers.dispatcher.async_dispatcher_connect( - SIGNAL_RECEIVE_MESSAGE, self._message_received_callback) + SIGNAL_RECEIVE_MESSAGE, self._message_received_callback + ) def _message_received_callback(self, packet): """Handle incoming packets.""" from enocean.utils import combine_hex + if packet.sender_int == combine_hex(self.dev_id): self.value_changed(packet) @@ -87,6 +88,6 @@ class EnOceanDevice(Entity): def send_command(self, data, optional, packet_type): """Send a command via the EnOcean dongle.""" from enocean.protocol.packet import Packet + packet = Packet(packet_type, data=data, optional=optional) - self.hass.helpers.dispatcher.dispatcher_send( - SIGNAL_SEND_MESSAGE, packet) + self.hass.helpers.dispatcher.dispatcher_send(SIGNAL_SEND_MESSAGE, packet) diff --git a/homeassistant/components/enocean/binary_sensor.py b/homeassistant/components/enocean/binary_sensor.py index 5e0a3b31817..4ff1b461129 100644 --- a/homeassistant/components/enocean/binary_sensor.py +++ b/homeassistant/components/enocean/binary_sensor.py @@ -5,21 +5,26 @@ import voluptuous as vol from homeassistant.components import enocean from homeassistant.components.binary_sensor import ( - DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, BinarySensorDevice) + DEVICE_CLASSES_SCHEMA, + PLATFORM_SCHEMA, + BinarySensorDevice, +) from homeassistant.const import CONF_DEVICE_CLASS, CONF_ID, CONF_NAME import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'EnOcean binary sensor' -DEPENDENCIES = ['enocean'] -EVENT_BUTTON_PRESSED = 'button_pressed' +DEFAULT_NAME = "EnOcean binary sensor" +DEPENDENCIES = ["enocean"] +EVENT_BUTTON_PRESSED = "button_pressed" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_ID): vol.All(cv.ensure_list, [vol.Coerce(int)]), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_ID): vol.All(cv.ensure_list, [vol.Coerce(int)]), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -97,8 +102,12 @@ class EnOceanBinarySensor(enocean.EnOceanDevice, BinarySensorDevice): elif action == 0x15: self.which = 10 self.onoff = 1 - self.hass.bus.fire(EVENT_BUTTON_PRESSED, - {'id': self.dev_id, - 'pushed': pushed, - 'which': self.which, - 'onoff': self.onoff}) + self.hass.bus.fire( + EVENT_BUTTON_PRESSED, + { + "id": self.dev_id, + "pushed": pushed, + "which": self.which, + "onoff": self.onoff, + }, + ) diff --git a/homeassistant/components/enocean/light.py b/homeassistant/components/enocean/light.py index d40b2c01df6..a1d2b22cdb4 100644 --- a/homeassistant/components/enocean/light.py +++ b/homeassistant/components/enocean/light.py @@ -6,23 +6,28 @@ import voluptuous as vol from homeassistant.components import enocean from homeassistant.components.light import ( - ATTR_BRIGHTNESS, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, Light) + ATTR_BRIGHTNESS, + PLATFORM_SCHEMA, + SUPPORT_BRIGHTNESS, + Light, +) from homeassistant.const import CONF_ID, CONF_NAME import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -CONF_SENDER_ID = 'sender_id' +CONF_SENDER_ID = "sender_id" -DEFAULT_NAME = 'EnOcean Light' +DEFAULT_NAME = "EnOcean Light" SUPPORT_ENOCEAN = SUPPORT_BRIGHTNESS -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_ID, default=[]): - vol.All(cv.ensure_list, [vol.Coerce(int)]), - vol.Required(CONF_SENDER_ID): vol.All(cv.ensure_list, [vol.Coerce(int)]), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_ID, default=[]): vol.All(cv.ensure_list, [vol.Coerce(int)]), + vol.Required(CONF_SENDER_ID): vol.All(cv.ensure_list, [vol.Coerce(int)]), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -77,7 +82,7 @@ class EnOceanLight(enocean.EnOceanDevice, Light): bval = math.floor(self._brightness / 256.0 * 100.0) if bval == 0: bval = 1 - command = [0xa5, 0x02, bval, 0x01, 0x09] + command = [0xA5, 0x02, bval, 0x01, 0x09] command.extend(self._sender_id) command.extend([0x00]) self.send_command(command, [], 0x01) @@ -85,7 +90,7 @@ class EnOceanLight(enocean.EnOceanDevice, Light): def turn_off(self, **kwargs): """Turn the light source off.""" - command = [0xa5, 0x02, 0x00, 0x01, 0x09] + command = [0xA5, 0x02, 0x00, 0x01, 0x09] command.extend(self._sender_id) command.extend([0x00]) self.send_command(command, [], 0x01) @@ -97,7 +102,7 @@ class EnOceanLight(enocean.EnOceanDevice, Light): Dimmer devices like Eltako FUD61 send telegram in different RORGs. We only care about the 4BS (0xA5). """ - if packet.data[0] == 0xa5 and packet.data[1] == 0x02: + if packet.data[0] == 0xA5 and packet.data[1] == 0x02: val = packet.data[2] self._brightness = math.floor(val / 100.0 * 256.0) self._on_state = bool(val != 0) diff --git a/homeassistant/components/enocean/sensor.py b/homeassistant/components/enocean/sensor.py index 62d0277946f..2e6b5bdb986 100644 --- a/homeassistant/components/enocean/sensor.py +++ b/homeassistant/components/enocean/sensor.py @@ -6,51 +6,59 @@ import voluptuous as vol from homeassistant.components import enocean from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_DEVICE_CLASS, CONF_ID, CONF_NAME, DEVICE_CLASS_HUMIDITY, - DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, POWER_WATT) + CONF_DEVICE_CLASS, + CONF_ID, + CONF_NAME, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + TEMP_CELSIUS, + POWER_WATT, +) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -CONF_MAX_TEMP = 'max_temp' -CONF_MIN_TEMP = 'min_temp' -CONF_RANGE_FROM = 'range_from' -CONF_RANGE_TO = 'range_to' +CONF_MAX_TEMP = "max_temp" +CONF_MIN_TEMP = "min_temp" +CONF_RANGE_FROM = "range_from" +CONF_RANGE_TO = "range_to" -DEFAULT_NAME = 'EnOcean sensor' +DEFAULT_NAME = "EnOcean sensor" -DEVICE_CLASS_POWER = 'powersensor' +DEVICE_CLASS_POWER = "powersensor" SENSOR_TYPES = { DEVICE_CLASS_HUMIDITY: { - 'name': 'Humidity', - 'unit': '%', - 'icon': 'mdi:water-percent', - 'class': DEVICE_CLASS_HUMIDITY, + "name": "Humidity", + "unit": "%", + "icon": "mdi:water-percent", + "class": DEVICE_CLASS_HUMIDITY, }, DEVICE_CLASS_POWER: { - 'name': 'Power', - 'unit': POWER_WATT, - 'icon': 'mdi:power-plug', - 'class': DEVICE_CLASS_POWER, + "name": "Power", + "unit": POWER_WATT, + "icon": "mdi:power-plug", + "class": DEVICE_CLASS_POWER, }, DEVICE_CLASS_TEMPERATURE: { - 'name': 'Temperature', - 'unit': TEMP_CELSIUS, - 'icon': 'mdi:thermometer', - 'class': DEVICE_CLASS_TEMPERATURE, + "name": "Temperature", + "unit": TEMP_CELSIUS, + "icon": "mdi:thermometer", + "class": DEVICE_CLASS_TEMPERATURE, }, } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_ID): vol.All(cv.ensure_list, [vol.Coerce(int)]), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_DEVICE_CLASS, default=DEVICE_CLASS_POWER): cv.string, - vol.Optional(CONF_MAX_TEMP, default=40): vol.Coerce(int), - vol.Optional(CONF_MIN_TEMP, default=0): vol.Coerce(int), - vol.Optional(CONF_RANGE_FROM, default=255): cv.positive_int, - vol.Optional(CONF_RANGE_TO, default=0): cv.positive_int, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_ID): vol.All(cv.ensure_list, [vol.Coerce(int)]), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_DEVICE_CLASS, default=DEVICE_CLASS_POWER): cv.string, + vol.Optional(CONF_MAX_TEMP, default=40): vol.Coerce(int), + vol.Optional(CONF_MIN_TEMP, default=0): vol.Coerce(int), + vol.Optional(CONF_RANGE_FROM, default=255): cv.positive_int, + vol.Optional(CONF_RANGE_TO, default=0): cv.positive_int, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -64,8 +72,13 @@ def setup_platform(hass, config, add_entities, discovery_info=None): temp_max = config.get(CONF_MAX_TEMP) range_from = config.get(CONF_RANGE_FROM) range_to = config.get(CONF_RANGE_TO) - add_entities([EnOceanTemperatureSensor( - dev_id, dev_name, temp_min, temp_max, range_from, range_to)]) + add_entities( + [ + EnOceanTemperatureSensor( + dev_id, dev_name, temp_min, temp_max, range_from, range_to + ) + ] + ) elif dev_class == DEVICE_CLASS_HUMIDITY: add_entities([EnOceanHumiditySensor(dev_id, dev_name)]) @@ -81,11 +94,12 @@ class EnOceanSensor(enocean.EnOceanDevice): """Initialize the EnOcean sensor device.""" super().__init__(dev_id, dev_name) self._sensor_type = sensor_type - self._device_class = SENSOR_TYPES[self._sensor_type]['class'] - self._dev_name = '{} {}'.format( - SENSOR_TYPES[self._sensor_type]['name'], dev_name) - self._unit_of_measurement = SENSOR_TYPES[self._sensor_type]['unit'] - self._icon = SENSOR_TYPES[self._sensor_type]['icon'] + self._device_class = SENSOR_TYPES[self._sensor_type]["class"] + self._dev_name = "{} {}".format( + SENSOR_TYPES[self._sensor_type]["name"], dev_name + ) + self._unit_of_measurement = SENSOR_TYPES[self._sensor_type]["unit"] + self._icon = SENSOR_TYPES[self._sensor_type]["icon"] self._state = None @property @@ -133,10 +147,10 @@ class EnOceanPowerSensor(EnOceanSensor): if packet.rorg != 0xA5: return packet.parse_eep(0x12, 0x01) - if packet.parsed['DT']['raw_value'] == 1: + if packet.parsed["DT"]["raw_value"] == 1: # this packet reports the current value - raw_val = packet.parsed['MR']['raw_value'] - divisor = packet.parsed['DIV']['raw_value'] + raw_val = packet.parsed["MR"]["raw_value"] + divisor = packet.parsed["DIV"]["raw_value"] self._state = raw_val / (10 ** divisor) self.schedule_update_ha_state() @@ -159,8 +173,7 @@ class EnOceanTemperatureSensor(EnOceanSensor): - A5-10-10 to A5-10-14 """ - def __init__(self, dev_id, dev_name, scale_min, scale_max, - range_from, range_to): + def __init__(self, dev_id, dev_name, scale_min, scale_max, range_from, range_to): """Initialize the EnOcean temperature sensor device.""" super().__init__(dev_id, dev_name, DEVICE_CLASS_TEMPERATURE) self._scale_min = scale_min @@ -170,7 +183,7 @@ class EnOceanTemperatureSensor(EnOceanSensor): def value_changed(self, packet): """Update the internal state of the sensor.""" - if packet.data[0] != 0xa5: + if packet.data[0] != 0xA5: return temp_scale = self._scale_max - self._scale_min temp_range = self.range_to - self.range_from diff --git a/homeassistant/components/enocean/switch.py b/homeassistant/components/enocean/switch.py index 48d53949a47..92642e329d9 100644 --- a/homeassistant/components/enocean/switch.py +++ b/homeassistant/components/enocean/switch.py @@ -11,14 +11,16 @@ from homeassistant.helpers.entity import ToggleEntity _LOGGER = logging.getLogger(__name__) -CONF_CHANNEL = 'channel' -DEFAULT_NAME = 'EnOcean Switch' +CONF_CHANNEL = "channel" +DEFAULT_NAME = "EnOcean Switch" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_ID): vol.All(cv.ensure_list, [vol.Coerce(int)]), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_CHANNEL, default=0): cv.positive_int, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_ID): vol.All(cv.ensure_list, [vol.Coerce(int)]), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_CHANNEL, default=0): cv.positive_int, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -53,42 +55,46 @@ class EnOceanSwitch(enocean.EnOceanDevice, ToggleEntity): def turn_on(self, **kwargs): """Turn on the switch.""" - optional = [0x03, ] + optional = [0x03] optional.extend(self.dev_id) - optional.extend([0xff, 0x00]) - self.send_command(data=[0xD2, 0x01, self.channel & 0xFF, 0x64, 0x00, - 0x00, 0x00, 0x00, 0x00], optional=optional, - packet_type=0x01) + optional.extend([0xFF, 0x00]) + self.send_command( + data=[0xD2, 0x01, self.channel & 0xFF, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00], + optional=optional, + packet_type=0x01, + ) self._on_state = True def turn_off(self, **kwargs): """Turn off the switch.""" - optional = [0x03, ] + optional = [0x03] optional.extend(self.dev_id) - optional.extend([0xff, 0x00]) - self.send_command(data=[0xD2, 0x01, self.channel & 0xFF, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00], optional=optional, - packet_type=0x01) + optional.extend([0xFF, 0x00]) + self.send_command( + data=[0xD2, 0x01, self.channel & 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], + optional=optional, + packet_type=0x01, + ) self._on_state = False def value_changed(self, packet): """Update the internal state of the switch.""" - if packet.data[0] == 0xa5: + if packet.data[0] == 0xA5: # power meter telegram, turn on if > 10 watts packet.parse_eep(0x12, 0x01) - if packet.parsed['DT']['raw_value'] == 1: - raw_val = packet.parsed['MR']['raw_value'] - divisor = packet.parsed['DIV']['raw_value'] + if packet.parsed["DT"]["raw_value"] == 1: + raw_val = packet.parsed["MR"]["raw_value"] + divisor = packet.parsed["DIV"]["raw_value"] watts = raw_val / (10 ** divisor) if watts > 1: self._on_state = True self.schedule_update_ha_state() - elif packet.data[0] == 0xd2: + elif packet.data[0] == 0xD2: # actuator status telegram packet.parse_eep(0x01, 0x01) - if packet.parsed['CMD']['raw_value'] == 4: - channel = packet.parsed['IO']['raw_value'] - output = packet.parsed['OV']['raw_value'] + if packet.parsed["CMD"]["raw_value"] == 4: + channel = packet.parsed["IO"]["raw_value"] + output = packet.parsed["OV"]["raw_value"] if channel == self.channel: self._on_state = output > 0 self.schedule_update_ha_state() diff --git a/homeassistant/components/enphase_envoy/sensor.py b/homeassistant/components/enphase_envoy/sensor.py index b859313a41e..2cc46632dda 100644 --- a/homeassistant/components/enphase_envoy/sensor.py +++ b/homeassistant/components/enphase_envoy/sensor.py @@ -7,7 +7,11 @@ from homeassistant.helpers.entity import Entity from homeassistant.components.sensor import PLATFORM_SCHEMA import homeassistant.helpers.config_validation as cv from homeassistant.const import ( - CONF_IP_ADDRESS, CONF_MONITORED_CONDITIONS, POWER_WATT, ENERGY_WATT_HOUR) + CONF_IP_ADDRESS, + CONF_MONITORED_CONDITIONS, + POWER_WATT, + ENERGY_WATT_HOUR, +) _LOGGER = logging.getLogger(__name__) @@ -15,32 +19,36 @@ _LOGGER = logging.getLogger(__name__) SENSORS = { "production": ("Envoy Current Energy Production", POWER_WATT), "daily_production": ("Envoy Today's Energy Production", ENERGY_WATT_HOUR), - "seven_days_production": ("Envoy Last Seven Days Energy Production", - ENERGY_WATT_HOUR), - "lifetime_production": ("Envoy Lifetime Energy Production", - ENERGY_WATT_HOUR), + "seven_days_production": ( + "Envoy Last Seven Days Energy Production", + ENERGY_WATT_HOUR, + ), + "lifetime_production": ("Envoy Lifetime Energy Production", ENERGY_WATT_HOUR), "consumption": ("Envoy Current Energy Consumption", POWER_WATT), - "daily_consumption": ("Envoy Today's Energy Consumption", - ENERGY_WATT_HOUR), - "seven_days_consumption": ("Envoy Last Seven Days Energy Consumption", - ENERGY_WATT_HOUR), - "lifetime_consumption": ("Envoy Lifetime Energy Consumption", - ENERGY_WATT_HOUR), - "inverters": ("Envoy Inverter", POWER_WATT) - } + "daily_consumption": ("Envoy Today's Energy Consumption", ENERGY_WATT_HOUR), + "seven_days_consumption": ( + "Envoy Last Seven Days Energy Consumption", + ENERGY_WATT_HOUR, + ), + "lifetime_consumption": ("Envoy Lifetime Energy Consumption", ENERGY_WATT_HOUR), + "inverters": ("Envoy Inverter", POWER_WATT), +} -ICON = 'mdi:flash' +ICON = "mdi:flash" CONST_DEFAULT_HOST = "envoy" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_IP_ADDRESS, default=CONST_DEFAULT_HOST): cv.string, - vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSORS)): - vol.All(cv.ensure_list, [vol.In(list(SENSORS))])}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_IP_ADDRESS, default=CONST_DEFAULT_HOST): cv.string, + vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSORS)): vol.All( + cv.ensure_list, [vol.In(list(SENSORS))] + ), + } +) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Enphase Envoy sensor.""" from envoy_reader.envoy_reader import EnvoyReader @@ -54,13 +62,20 @@ async def async_setup_platform(hass, config, async_add_entities, inverters = await EnvoyReader(ip_address).inverters_production() if isinstance(inverters, dict): for inverter in inverters: - entities.append(Envoy(ip_address, condition, - "{} {}".format(SENSORS[condition][0], - inverter), - SENSORS[condition][1])) + entities.append( + Envoy( + ip_address, + condition, + "{} {}".format(SENSORS[condition][0], inverter), + SENSORS[condition][1], + ) + ) else: - entities.append(Envoy(ip_address, condition, SENSORS[condition][0], - SENSORS[condition][1])) + entities.append( + Envoy( + ip_address, condition, SENSORS[condition][0], SENSORS[condition][1] + ) + ) async_add_entities(entities) @@ -108,8 +123,7 @@ class Envoy(Entity): self._state = None elif self._type == "inverters": - inverters = await (EnvoyReader(self._ip_address) - .inverters_production()) + inverters = await (EnvoyReader(self._ip_address).inverters_production()) if isinstance(inverters, dict): serial_number = self._name.split(" ")[2] self._state = inverters[serial_number] diff --git a/homeassistant/components/entur_public_transport/sensor.py b/homeassistant/components/entur_public_transport/sensor.py index 61b183b9408..46ba62ba3fa 100644 --- a/homeassistant/components/entur_public_transport/sensor.py +++ b/homeassistant/components/entur_public_transport/sensor.py @@ -6,8 +6,12 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - ATTR_ATTRIBUTION, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, - CONF_SHOW_ON_MAP) + ATTR_ATTRIBUTION, + CONF_LATITUDE, + CONF_LONGITUDE, + CONF_NAME, + CONF_SHOW_ON_MAP, +) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -16,58 +20,61 @@ import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) -API_CLIENT_NAME = 'homeassistant-homeassistant' +API_CLIENT_NAME = "homeassistant-homeassistant" ATTRIBUTION = "Data provided by entur.org under NLOD" -CONF_STOP_IDS = 'stop_ids' -CONF_EXPAND_PLATFORMS = 'expand_platforms' -CONF_WHITELIST_LINES = 'line_whitelist' -CONF_OMIT_NON_BOARDING = 'omit_non_boarding' -CONF_NUMBER_OF_DEPARTURES = 'number_of_departures' +CONF_STOP_IDS = "stop_ids" +CONF_EXPAND_PLATFORMS = "expand_platforms" +CONF_WHITELIST_LINES = "line_whitelist" +CONF_OMIT_NON_BOARDING = "omit_non_boarding" +CONF_NUMBER_OF_DEPARTURES = "number_of_departures" -DEFAULT_NAME = 'Entur' -DEFAULT_ICON_KEY = 'bus' +DEFAULT_NAME = "Entur" +DEFAULT_ICON_KEY = "bus" ICONS = { - 'air': 'mdi:airplane', - 'bus': 'mdi:bus', - 'metro': 'mdi:subway', - 'rail': 'mdi:train', - 'tram': 'mdi:tram', - 'water': 'mdi:ferry', + "air": "mdi:airplane", + "bus": "mdi:bus", + "metro": "mdi:subway", + "rail": "mdi:train", + "tram": "mdi:tram", + "water": "mdi:ferry", } SCAN_INTERVAL = timedelta(seconds=45) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_STOP_IDS): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_EXPAND_PLATFORMS, default=True): cv.boolean, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_SHOW_ON_MAP, default=False): cv.boolean, - vol.Optional(CONF_WHITELIST_LINES, default=[]): cv.ensure_list, - vol.Optional(CONF_OMIT_NON_BOARDING, default=True): cv.boolean, - vol.Optional(CONF_NUMBER_OF_DEPARTURES, default=2): - vol.All(cv.positive_int, vol.Range(min=2, max=10)) -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_STOP_IDS): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_EXPAND_PLATFORMS, default=True): cv.boolean, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_SHOW_ON_MAP, default=False): cv.boolean, + vol.Optional(CONF_WHITELIST_LINES, default=[]): cv.ensure_list, + vol.Optional(CONF_OMIT_NON_BOARDING, default=True): cv.boolean, + vol.Optional(CONF_NUMBER_OF_DEPARTURES, default=2): vol.All( + cv.positive_int, vol.Range(min=2, max=10) + ), + } +) -ATTR_STOP_ID = 'stop_id' +ATTR_STOP_ID = "stop_id" -ATTR_ROUTE = 'route' -ATTR_ROUTE_ID = 'route_id' -ATTR_EXPECTED_AT = 'due_at' -ATTR_DELAY = 'delay' -ATTR_REALTIME = 'real_time' +ATTR_ROUTE = "route" +ATTR_ROUTE_ID = "route_id" +ATTR_EXPECTED_AT = "due_at" +ATTR_DELAY = "delay" +ATTR_REALTIME = "real_time" -ATTR_NEXT_UP_IN = 'next_due_in' -ATTR_NEXT_UP_ROUTE = 'next_route' -ATTR_NEXT_UP_ROUTE_ID = 'next_route_id' -ATTR_NEXT_UP_AT = 'next_due_at' -ATTR_NEXT_UP_DELAY = 'next_delay' -ATTR_NEXT_UP_REALTIME = 'next_real_time' +ATTR_NEXT_UP_IN = "next_due_in" +ATTR_NEXT_UP_ROUTE = "next_route" +ATTR_NEXT_UP_ROUTE_ID = "next_route_id" +ATTR_NEXT_UP_AT = "next_due_at" +ATTR_NEXT_UP_DELAY = "next_delay" +ATTR_NEXT_UP_REALTIME = "next_real_time" -ATTR_TRANSPORT_MODE = 'transport_mode' +ATTR_TRANSPORT_MODE = "transport_mode" def due_in_minutes(timestamp: datetime) -> int: @@ -78,8 +85,7 @@ def due_in_minutes(timestamp: datetime) -> int: return int(diff.total_seconds() / 60) -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Entur public transport sensor.""" from enturclient import EnturPublicTransportData @@ -94,13 +100,15 @@ async def async_setup_platform( stops = [s for s in stop_ids if "StopPlace" in s] quays = [s for s in stop_ids if "Quay" in s] - data = EnturPublicTransportData(API_CLIENT_NAME, - stops=stops, - quays=quays, - line_whitelist=line_whitelist, - omit_non_boarding=omit_non_boarding, - number_of_departures=number_of_departures, - web_session=async_get_clientsession(hass)) + data = EnturPublicTransportData( + API_CLIENT_NAME, + stops=stops, + quays=quays, + line_whitelist=line_whitelist, + omit_non_boarding=omit_non_boarding, + number_of_departures=number_of_departures, + web_session=async_get_clientsession(hass), + ) if expand: await data.expand_all_quays() @@ -111,13 +119,13 @@ async def async_setup_platform( entities = [] for place in data.all_stop_places_quays(): try: - given_name = "{} {}".format( - name, data.get_stop_info(place).name) + given_name = "{} {}".format(name, data.get_stop_info(place).name) except KeyError: given_name = "{} {}".format(name, place) entities.append( - EnturPublicTransportSensor(proxy, given_name, place, show_on_map)) + EnturPublicTransportSensor(proxy, given_name, place, show_on_map) + ) async_add_entities(entities, True) @@ -145,8 +153,7 @@ class EnturProxy: class EnturPublicTransportSensor(Entity): """Implementation of a Entur public transport sensor.""" - def __init__( - self, api: EnturProxy, name: str, stop: str, show_on_map: bool): + def __init__(self, api: EnturProxy, name: str, stop: str, show_on_map: bool): """Initialize the sensor.""" self.api = api self._stop = stop @@ -176,7 +183,7 @@ class EnturPublicTransportSensor(Entity): @property def unit_of_measurement(self) -> str: """Return the unit this state is expressed in.""" - return 'min' + return "min" @property def icon(self) -> str: @@ -204,13 +211,13 @@ class EnturPublicTransportSensor(Entity): return self._state = due_in_minutes(calls[0].expected_departure_time) - self._icon = ICONS.get( - calls[0].transport_mode, ICONS[DEFAULT_ICON_KEY]) + self._icon = ICONS.get(calls[0].transport_mode, ICONS[DEFAULT_ICON_KEY]) self._attributes[ATTR_ROUTE] = calls[0].front_display self._attributes[ATTR_ROUTE_ID] = calls[0].line_id - self._attributes[ATTR_EXPECTED_AT] = calls[0]\ - .expected_departure_time.strftime("%H:%M") + self._attributes[ATTR_EXPECTED_AT] = calls[0].expected_departure_time.strftime( + "%H:%M" + ) self._attributes[ATTR_REALTIME] = calls[0].is_realtime self._attributes[ATTR_DELAY] = calls[0].delay_in_min @@ -220,10 +227,12 @@ class EnturPublicTransportSensor(Entity): self._attributes[ATTR_NEXT_UP_ROUTE] = calls[1].front_display self._attributes[ATTR_NEXT_UP_ROUTE_ID] = calls[1].line_id - self._attributes[ATTR_NEXT_UP_AT] = calls[1]\ - .expected_departure_time.strftime("%H:%M") - self._attributes[ATTR_NEXT_UP_IN] = "{} min"\ - .format(due_in_minutes(calls[1].expected_departure_time)) + self._attributes[ATTR_NEXT_UP_AT] = calls[1].expected_departure_time.strftime( + "%H:%M" + ) + self._attributes[ATTR_NEXT_UP_IN] = "{} min".format( + due_in_minutes(calls[1].expected_departure_time) + ) self._attributes[ATTR_NEXT_UP_REALTIME] = calls[1].is_realtime self._attributes[ATTR_NEXT_UP_DELAY] = calls[1].delay_in_min @@ -235,4 +244,5 @@ class EnturPublicTransportSensor(Entity): self._attributes[key_name] = "{}{} {}".format( "" if bool(call.is_realtime) else "ca. ", call.expected_departure_time.strftime("%H:%M"), - call.front_display) + call.front_display, + ) diff --git a/homeassistant/components/environment_canada/camera.py b/homeassistant/components/environment_canada/camera.py index 7a42c770841..709e4251fbf 100755 --- a/homeassistant/components/environment_canada/camera.py +++ b/homeassistant/components/environment_canada/camera.py @@ -9,33 +9,38 @@ import logging import voluptuous as vol -from homeassistant.components.camera import ( - PLATFORM_SCHEMA, Camera) +from homeassistant.components.camera import PLATFORM_SCHEMA, Camera from homeassistant.const import ( - CONF_NAME, CONF_LATITUDE, CONF_LONGITUDE, ATTR_ATTRIBUTION) + CONF_NAME, + CONF_LATITUDE, + CONF_LONGITUDE, + ATTR_ATTRIBUTION, +) from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -ATTR_STATION = 'station' -ATTR_LOCATION = 'location' +ATTR_STATION = "station" +ATTR_LOCATION = "location" CONF_ATTRIBUTION = "Data provided by Environment Canada" -CONF_STATION = 'station' -CONF_LOOP = 'loop' -CONF_PRECIP_TYPE = 'precip_type' +CONF_STATION = "station" +CONF_LOOP = "loop" +CONF_PRECIP_TYPE = "precip_type" MIN_TIME_BETWEEN_UPDATES = datetime.timedelta(minutes=10) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_LOOP, default=True): cv.boolean, - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_STATION): cv.string, - vol.Inclusive(CONF_LATITUDE, 'latlon'): cv.latitude, - vol.Inclusive(CONF_LONGITUDE, 'latlon'): cv.longitude, - vol.Optional(CONF_PRECIP_TYPE): ['RAIN', 'SNOW'], -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_LOOP, default=True): cv.boolean, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_STATION): cv.string, + vol.Inclusive(CONF_LATITUDE, "latlon"): cv.latitude, + vol.Inclusive(CONF_LONGITUDE, "latlon"): cv.longitude, + vol.Optional(CONF_PRECIP_TYPE): ["RAIN", "SNOW"], + } +) def setup_platform(hass, config, add_devices, discovery_info=None): @@ -43,8 +48,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): from env_canada import ECRadar if config.get(CONF_STATION): - radar_object = ECRadar(station_id=config[CONF_STATION], - precip_type=config.get(CONF_PRECIP_TYPE)) + radar_object = ECRadar( + station_id=config[CONF_STATION], precip_type=config.get(CONF_PRECIP_TYPE) + ) else: lat = config.get(CONF_LATITUDE, hass.config.latitude) lon = config.get(CONF_LONGITUDE, hass.config.longitude) @@ -62,7 +68,7 @@ class ECCamera(Camera): self.radar_object = radar_object self.camera_name = camera_name - self.content_type = 'image/gif' + self.content_type = "image/gif" self.image = None def camera_image(self): @@ -75,7 +81,7 @@ class ECCamera(Camera): """Return the name of the camera.""" if self.camera_name is not None: return self.camera_name - return ' '.join([self.radar_object.station_name, 'Radar']) + return " ".join([self.radar_object.station_name, "Radar"]) @property def device_state_attributes(self): @@ -83,7 +89,7 @@ class ECCamera(Camera): attr = { ATTR_ATTRIBUTION: CONF_ATTRIBUTION, ATTR_LOCATION: self.radar_object.station_name, - ATTR_STATION: self.radar_object.station_code + ATTR_STATION: self.radar_object.station_code, } return attr diff --git a/homeassistant/components/environment_canada/sensor.py b/homeassistant/components/environment_canada/sensor.py index 70b98eeebdd..0182e7c67ed 100755 --- a/homeassistant/components/environment_canada/sensor.py +++ b/homeassistant/components/environment_canada/sensor.py @@ -12,8 +12,12 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - TEMP_CELSIUS, CONF_LATITUDE, CONF_LONGITUDE, ATTR_ATTRIBUTION, - ATTR_LOCATION) + TEMP_CELSIUS, + CONF_LATITUDE, + CONF_LONGITUDE, + ATTR_ATTRIBUTION, + ATTR_LOCATION, +) from homeassistant.helpers.entity import Entity import homeassistant.helpers.config_validation as cv @@ -21,32 +25,33 @@ _LOGGER = logging.getLogger(__name__) SCAN_INTERVAL = timedelta(minutes=10) -ATTR_UPDATED = 'updated' -ATTR_STATION = 'station' -ATTR_DETAIL = 'alert detail' -ATTR_TIME = 'alert time' +ATTR_UPDATED = "updated" +ATTR_STATION = "station" +ATTR_DETAIL = "alert detail" +ATTR_TIME = "alert time" CONF_ATTRIBUTION = "Data provided by Environment Canada" -CONF_STATION = 'station' -CONF_LANGUAGE = 'language' +CONF_STATION = "station" +CONF_LANGUAGE = "language" def validate_station(station): """Check that the station ID is well-formed.""" if station is None: return - if not re.fullmatch(r'[A-Z]{2}/s0000\d{3}', station): + if not re.fullmatch(r"[A-Z]{2}/s0000\d{3}", station): raise vol.error.Invalid('Station ID must be of the form "XX/s0000###"') return station -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_LANGUAGE, default='english'): - vol.In(['english', 'french']), - vol.Optional(CONF_STATION): validate_station, - vol.Inclusive(CONF_LATITUDE, 'latlon'): cv.latitude, - vol.Inclusive(CONF_LONGITUDE, 'latlon'): cv.longitude, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_LANGUAGE, default="english"): vol.In(["english", "french"]), + vol.Optional(CONF_STATION): validate_station, + vol.Inclusive(CONF_LATITUDE, "latlon"): cv.latitude, + vol.Inclusive(CONF_LONGITUDE, "latlon"): cv.longitude, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -54,20 +59,17 @@ def setup_platform(hass, config, add_entities, discovery_info=None): from env_canada import ECData if config.get(CONF_STATION): - ec_data = ECData(station_id=config[CONF_STATION], - language=config.get(CONF_LANGUAGE)) + ec_data = ECData( + station_id=config[CONF_STATION], language=config.get(CONF_LANGUAGE) + ) else: lat = config.get(CONF_LATITUDE, hass.config.latitude) lon = config.get(CONF_LONGITUDE, hass.config.longitude) - ec_data = ECData(coordinates=(lat, lon), - language=config.get(CONF_LANGUAGE)) + ec_data = ECData(coordinates=(lat, lon), language=config.get(CONF_LANGUAGE)) sensor_list = list(ec_data.conditions.keys()) + list(ec_data.alerts.keys()) - sensor_list.remove('icon_code') - add_entities([ECSensor(sensor_type, - ec_data) - for sensor_type in sensor_list], - True) + sensor_list.remove("icon_code") + add_entities([ECSensor(sensor_type, ec_data) for sensor_type in sensor_list], True) class ECSensor(Entity): @@ -118,39 +120,38 @@ class ECSensor(Entity): metadata = self.ec_data.metadata sensor_data = conditions.get(self.sensor_type) - self._unique_id = '{}-{}'.format(metadata['location'], - self.sensor_type) + self._unique_id = "{}-{}".format(metadata["location"], self.sensor_type) self._attr = {} - self._name = sensor_data.get('label') - value = sensor_data.get('value') + self._name = sensor_data.get("label") + value = sensor_data.get("value") if isinstance(value, list): - self._state = ' | '.join([str(s.get('title')) - for s in value]) - self._attr.update({ - ATTR_DETAIL: ' | '.join([str(s.get('detail')) - for s in value]), - ATTR_TIME: ' | '.join([str(s.get('date')) - for s in value]) - }) + self._state = " | ".join([str(s.get("title")) for s in value]) + self._attr.update( + { + ATTR_DETAIL: " | ".join([str(s.get("detail")) for s in value]), + ATTR_TIME: " | ".join([str(s.get("date")) for s in value]), + } + ) else: self._state = value - if sensor_data.get('unit') == 'C': + if sensor_data.get("unit") == "C": self._unit = TEMP_CELSIUS else: - self._unit = sensor_data.get('unit') + self._unit = sensor_data.get("unit") - timestamp = metadata.get('timestamp') + timestamp = metadata.get("timestamp") if timestamp: - updated_utc = datetime.strptime(timestamp, - '%Y%m%d%H%M%S').isoformat() + updated_utc = datetime.strptime(timestamp, "%Y%m%d%H%M%S").isoformat() else: updated_utc = None - self._attr.update({ - ATTR_ATTRIBUTION: CONF_ATTRIBUTION, - ATTR_UPDATED: updated_utc, - ATTR_LOCATION: metadata.get('location'), - ATTR_STATION: metadata.get('station'), - }) + self._attr.update( + { + ATTR_ATTRIBUTION: CONF_ATTRIBUTION, + ATTR_UPDATED: updated_utc, + ATTR_LOCATION: metadata.get("location"), + ATTR_STATION: metadata.get("station"), + } + ) diff --git a/homeassistant/components/environment_canada/weather.py b/homeassistant/components/environment_canada/weather.py index 0be659138fb..ebb6b0cd51f 100644 --- a/homeassistant/components/environment_canada/weather.py +++ b/homeassistant/components/environment_canada/weather.py @@ -12,19 +12,23 @@ from env_canada import ECData import voluptuous as vol from homeassistant.components.weather import ( - ATTR_FORECAST_CONDITION, ATTR_FORECAST_TEMP, ATTR_FORECAST_TEMP_LOW, - ATTR_FORECAST_TIME, PLATFORM_SCHEMA, WeatherEntity) -from homeassistant.const import ( - CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, TEMP_CELSIUS) + ATTR_FORECAST_CONDITION, + ATTR_FORECAST_TEMP, + ATTR_FORECAST_TEMP_LOW, + ATTR_FORECAST_TIME, + PLATFORM_SCHEMA, + WeatherEntity, +) +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, TEMP_CELSIUS from homeassistant.util import Throttle import homeassistant.util.dt as dt import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -CONF_FORECAST = 'forecast' +CONF_FORECAST = "forecast" CONF_ATTRIBUTION = "Data provided by Environment Canada" -CONF_STATION = 'station' +CONF_STATION = "station" MIN_TIME_BETWEEN_UPDATES = datetime.timedelta(minutes=10) @@ -33,34 +37,37 @@ def validate_station(station): """Check that the station ID is well-formed.""" if station is None: return - if not re.fullmatch(r'[A-Z]{2}/s0000\d{3}', station): + if not re.fullmatch(r"[A-Z]{2}/s0000\d{3}", station): raise vol.error.Invalid('Station ID must be of the form "XX/s0000###"') return station -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_STATION): validate_station, - vol.Inclusive(CONF_LATITUDE, 'latlon'): cv.latitude, - vol.Inclusive(CONF_LONGITUDE, 'latlon'): cv.longitude, - vol.Optional(CONF_FORECAST, default='daily'): - vol.In(['daily', 'hourly']), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_STATION): validate_station, + vol.Inclusive(CONF_LATITUDE, "latlon"): cv.latitude, + vol.Inclusive(CONF_LONGITUDE, "latlon"): cv.longitude, + vol.Optional(CONF_FORECAST, default="daily"): vol.In(["daily", "hourly"]), + } +) # Icon codes from http://dd.weatheroffice.ec.gc.ca/citypage_weather/ # docs/current_conditions_icon_code_descriptions_e.csv -ICON_CONDITION_MAP = {'sunny': [0, 1], - 'clear-night': [30, 31], - 'partlycloudy': [2, 3, 4, 5, 22, 32, 33, 34, 35], - 'cloudy': [10], - 'rainy': [6, 9, 11, 12, 28, 36], - 'lightning-rainy': [19, 39, 46, 47], - 'pouring': [13], - 'snowy-rainy': [7, 14, 15, 27, 37], - 'snowy': [8, 16, 17, 18, 25, 26, 38, 40], - 'windy': [43], - 'fog': [20, 21, 23, 24, 44], - 'hail': [26, 27]} +ICON_CONDITION_MAP = { + "sunny": [0, 1], + "clear-night": [30, 31], + "partlycloudy": [2, 3, 4, 5, 22, 32, 33, 34, 35], + "cloudy": [10], + "rainy": [6, 9, 11, 12, 28, 36], + "lightning-rainy": [19, 39, 46, 47], + "pouring": [13], + "snowy-rainy": [7, 14, 15, 27, 37], + "snowy": [8, 16, 17, 18, 25, 26, 38, 40], + "windy": [43], + "fog": [20, 21, 23, 24, 44], + "hail": [26, 27], +} def setup_platform(hass, config, add_devices, discovery_info=None): @@ -94,15 +101,15 @@ class ECWeather(WeatherEntity): """Return the name of the weather entity.""" if self.platform_name: return self.platform_name - return self.ec_data.metadata.get('location') + return self.ec_data.metadata.get("location") @property def temperature(self): """Return the temperature.""" - if self.ec_data.conditions.get('temperature').get('value'): - return float(self.ec_data.conditions['temperature']['value']) - if self.ec_data.hourly_forecasts[0].get('temperature'): - return float(self.ec_data.hourly_forecasts[0]['temperature']) + if self.ec_data.conditions.get("temperature").get("value"): + return float(self.ec_data.conditions["temperature"]["value"]) + if self.ec_data.hourly_forecasts[0].get("temperature"): + return float(self.ec_data.hourly_forecasts[0]["temperature"]) return None @property @@ -113,36 +120,36 @@ class ECWeather(WeatherEntity): @property def humidity(self): """Return the humidity.""" - if self.ec_data.conditions.get('humidity').get('value'): - return float(self.ec_data.conditions['humidity']['value']) + if self.ec_data.conditions.get("humidity").get("value"): + return float(self.ec_data.conditions["humidity"]["value"]) return None @property def wind_speed(self): """Return the wind speed.""" - if self.ec_data.conditions.get('wind_speed').get('value'): - return float(self.ec_data.conditions['wind_speed']['value']) + if self.ec_data.conditions.get("wind_speed").get("value"): + return float(self.ec_data.conditions["wind_speed"]["value"]) return None @property def wind_bearing(self): """Return the wind bearing.""" - if self.ec_data.conditions.get('wind_bearing').get('value'): - return float(self.ec_data.conditions['wind_bearing']['value']) + if self.ec_data.conditions.get("wind_bearing").get("value"): + return float(self.ec_data.conditions["wind_bearing"]["value"]) return None @property def pressure(self): """Return the pressure.""" - if self.ec_data.conditions.get('pressure').get('value'): - return 10 * float(self.ec_data.conditions['pressure']['value']) + if self.ec_data.conditions.get("pressure").get("value"): + return 10 * float(self.ec_data.conditions["pressure"]["value"]) return None @property def visibility(self): """Return the visibility.""" - if self.ec_data.conditions.get('visibility').get('value'): - return float(self.ec_data.conditions['visibility']['value']) + if self.ec_data.conditions.get("visibility").get("value"): + return float(self.ec_data.conditions["visibility"]["value"]) return None @property @@ -150,14 +157,14 @@ class ECWeather(WeatherEntity): """Return the weather condition.""" icon_code = None - if self.ec_data.conditions.get('icon_code').get('value'): - icon_code = self.ec_data.conditions['icon_code']['value'] - elif self.ec_data.hourly_forecasts[0].get('icon_code'): - icon_code = self.ec_data.hourly_forecasts[0]['icon_code'] + if self.ec_data.conditions.get("icon_code").get("value"): + icon_code = self.ec_data.conditions["icon_code"]["value"] + elif self.ec_data.hourly_forecasts[0].get("icon_code"): + icon_code = self.ec_data.hourly_forecasts[0]["icon_code"] if icon_code: return icon_code_to_condition(int(icon_code)) - return '' + return "" @property def forecast(self): @@ -174,42 +181,51 @@ def get_forecast(ec_data, forecast_type): """Build the forecast array.""" forecast_array = [] - if forecast_type == 'daily': + if forecast_type == "daily": half_days = ec_data.daily_forecasts - if half_days[0]['temperature_class'] == 'high': - forecast_array.append({ - ATTR_FORECAST_TIME: dt.now().isoformat(), - ATTR_FORECAST_TEMP: int(half_days[0]['temperature']), - ATTR_FORECAST_TEMP_LOW: int(half_days[1]['temperature']), - ATTR_FORECAST_CONDITION: icon_code_to_condition( - int(half_days[0]['icon_code'])) - }) + if half_days[0]["temperature_class"] == "high": + forecast_array.append( + { + ATTR_FORECAST_TIME: dt.now().isoformat(), + ATTR_FORECAST_TEMP: int(half_days[0]["temperature"]), + ATTR_FORECAST_TEMP_LOW: int(half_days[1]["temperature"]), + ATTR_FORECAST_CONDITION: icon_code_to_condition( + int(half_days[0]["icon_code"]) + ), + } + ) half_days = half_days[2:] else: half_days = half_days[1:] - for day, high, low in zip(range(1, 6), - range(0, 9, 2), - range(1, 10, 2)): - forecast_array.append({ - ATTR_FORECAST_TIME: - (dt.now() + datetime.timedelta(days=day)).isoformat(), - ATTR_FORECAST_TEMP: int(half_days[high]['temperature']), - ATTR_FORECAST_TEMP_LOW: int(half_days[low]['temperature']), - ATTR_FORECAST_CONDITION: icon_code_to_condition( - int(half_days[high]['icon_code'])) - }) + for day, high, low in zip(range(1, 6), range(0, 9, 2), range(1, 10, 2)): + forecast_array.append( + { + ATTR_FORECAST_TIME: ( + dt.now() + datetime.timedelta(days=day) + ).isoformat(), + ATTR_FORECAST_TEMP: int(half_days[high]["temperature"]), + ATTR_FORECAST_TEMP_LOW: int(half_days[low]["temperature"]), + ATTR_FORECAST_CONDITION: icon_code_to_condition( + int(half_days[high]["icon_code"]) + ), + } + ) - elif forecast_type == 'hourly': + elif forecast_type == "hourly": hours = ec_data.hourly_forecasts for hour in range(0, 24): - forecast_array.append({ - ATTR_FORECAST_TIME: dt.as_local(datetime.datetime.strptime( - hours[hour]['period'], '%Y%m%d%H%M')).isoformat(), - ATTR_FORECAST_TEMP: int(hours[hour]['temperature']), - ATTR_FORECAST_CONDITION: icon_code_to_condition( - int(hours[hour]['icon_code'])) - }) + forecast_array.append( + { + ATTR_FORECAST_TIME: dt.as_local( + datetime.datetime.strptime(hours[hour]["period"], "%Y%m%d%H%M") + ).isoformat(), + ATTR_FORECAST_TEMP: int(hours[hour]["temperature"]), + ATTR_FORECAST_CONDITION: icon_code_to_condition( + int(hours[hour]["icon_code"]) + ), + } + ) return forecast_array diff --git a/homeassistant/components/envirophat/sensor.py b/homeassistant/components/envirophat/sensor.py index 6d792df2421..459d21ab698 100644 --- a/homeassistant/components/envirophat/sensor.py +++ b/homeassistant/components/envirophat/sensor.py @@ -6,49 +6,52 @@ from datetime import timedelta import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import (TEMP_CELSIUS, CONF_DISPLAY_OPTIONS, CONF_NAME) +from homeassistant.const import TEMP_CELSIUS, CONF_DISPLAY_OPTIONS, CONF_NAME import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'envirophat' -CONF_USE_LEDS = 'use_leds' +DEFAULT_NAME = "envirophat" +CONF_USE_LEDS = "use_leds" MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) SENSOR_TYPES = { - 'light': ['light', ' ', 'mdi:weather-sunny'], - 'light_red': ['light_red', ' ', 'mdi:invert-colors'], - 'light_green': ['light_green', ' ', 'mdi:invert-colors'], - 'light_blue': ['light_blue', ' ', 'mdi:invert-colors'], - 'accelerometer_x': ['accelerometer_x', 'G', 'mdi:earth'], - 'accelerometer_y': ['accelerometer_y', 'G', 'mdi:earth'], - 'accelerometer_z': ['accelerometer_z', 'G', 'mdi:earth'], - 'magnetometer_x': ['magnetometer_x', ' ', 'mdi:magnet'], - 'magnetometer_y': ['magnetometer_y', ' ', 'mdi:magnet'], - 'magnetometer_z': ['magnetometer_z', ' ', 'mdi:magnet'], - 'temperature': ['temperature', TEMP_CELSIUS, 'mdi:thermometer'], - 'pressure': ['pressure', 'hPa', 'mdi:gauge'], - 'voltage_0': ['voltage_0', 'V', 'mdi:flash'], - 'voltage_1': ['voltage_1', 'V', 'mdi:flash'], - 'voltage_2': ['voltage_2', 'V', 'mdi:flash'], - 'voltage_3': ['voltage_3', 'V', 'mdi:flash'], + "light": ["light", " ", "mdi:weather-sunny"], + "light_red": ["light_red", " ", "mdi:invert-colors"], + "light_green": ["light_green", " ", "mdi:invert-colors"], + "light_blue": ["light_blue", " ", "mdi:invert-colors"], + "accelerometer_x": ["accelerometer_x", "G", "mdi:earth"], + "accelerometer_y": ["accelerometer_y", "G", "mdi:earth"], + "accelerometer_z": ["accelerometer_z", "G", "mdi:earth"], + "magnetometer_x": ["magnetometer_x", " ", "mdi:magnet"], + "magnetometer_y": ["magnetometer_y", " ", "mdi:magnet"], + "magnetometer_z": ["magnetometer_z", " ", "mdi:magnet"], + "temperature": ["temperature", TEMP_CELSIUS, "mdi:thermometer"], + "pressure": ["pressure", "hPa", "mdi:gauge"], + "voltage_0": ["voltage_0", "V", "mdi:flash"], + "voltage_1": ["voltage_1", "V", "mdi:flash"], + "voltage_2": ["voltage_2", "V", "mdi:flash"], + "voltage_3": ["voltage_3", "V", "mdi:flash"], } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_DISPLAY_OPTIONS, default=list(SENSOR_TYPES)): - [vol.In(SENSOR_TYPES)], - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_USE_LEDS, default=False): cv.boolean -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_DISPLAY_OPTIONS, default=list(SENSOR_TYPES)): [ + vol.In(SENSOR_TYPES) + ], + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_USE_LEDS, default=False): cv.boolean, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Sense HAT sensor platform.""" try: - envirophat = importlib.import_module('envirophat') + envirophat = importlib.import_module("envirophat") except OSError: _LOGGER.error("No Enviro pHAT was found.") return False @@ -97,37 +100,37 @@ class EnvirophatSensor(Entity): """Get the latest data and updates the states.""" self.data.update() - if self.type == 'light': + if self.type == "light": self._state = self.data.light - if self.type == 'light_red': + if self.type == "light_red": self._state = self.data.light_red - if self.type == 'light_green': + if self.type == "light_green": self._state = self.data.light_green - if self.type == 'light_blue': + if self.type == "light_blue": self._state = self.data.light_blue - if self.type == 'accelerometer_x': + if self.type == "accelerometer_x": self._state = self.data.accelerometer_x - if self.type == 'accelerometer_y': + if self.type == "accelerometer_y": self._state = self.data.accelerometer_y - if self.type == 'accelerometer_z': + if self.type == "accelerometer_z": self._state = self.data.accelerometer_z - if self.type == 'magnetometer_x': + if self.type == "magnetometer_x": self._state = self.data.magnetometer_x - if self.type == 'magnetometer_y': + if self.type == "magnetometer_y": self._state = self.data.magnetometer_y - if self.type == 'magnetometer_z': + if self.type == "magnetometer_z": self._state = self.data.magnetometer_z - if self.type == 'temperature': + if self.type == "temperature": self._state = self.data.temperature - if self.type == 'pressure': + if self.type == "pressure": self._state = self.data.pressure - if self.type == 'voltage_0': + if self.type == "voltage_0": self._state = self.data.voltage_0 - if self.type == 'voltage_1': + if self.type == "voltage_1": self._state = self.data.voltage_1 - if self.type == 'voltage_2': + if self.type == "voltage_2": self._state = self.data.voltage_2 - if self.type == 'voltage_3': + if self.type == "voltage_3": self._state = self.data.voltage_3 @@ -164,18 +167,19 @@ class EnvirophatData: if self.use_leds: self.envirophat.leds.on() # the three color values scaled against the overall light, 0-255 - self.light_red, self.light_green, self.light_blue = \ - self.envirophat.light.rgb() + self.light_red, self.light_green, self.light_blue = self.envirophat.light.rgb() if self.use_leds: self.envirophat.leds.off() # accelerometer readings in G - self.accelerometer_x, self.accelerometer_y, self.accelerometer_z = \ + self.accelerometer_x, self.accelerometer_y, self.accelerometer_z = ( self.envirophat.motion.accelerometer() + ) # raw magnetometer reading - self.magnetometer_x, self.magnetometer_y, self.magnetometer_z = \ + self.magnetometer_x, self.magnetometer_y, self.magnetometer_z = ( self.envirophat.motion.magnetometer() + ) # temperature resolution of BMP280 sensor: 0.01°C self.temperature = round(self.envirophat.weather.temperature(), 2) @@ -185,5 +189,6 @@ class EnvirophatData: self.pressure = round(self.envirophat.weather.pressure() / 100.0, 3) # Voltage sensor, reading between 0-3.3V - self.voltage_0, self.voltage_1, self.voltage_2, self.voltage_3 = \ + self.voltage_0, self.voltage_1, self.voltage_2, self.voltage_3 = ( self.envirophat.analog.read_all() + ) diff --git a/homeassistant/components/envisalink/__init__.py b/homeassistant/components/envisalink/__init__.py index 84b98846c2a..76d6a7e369c 100644 --- a/homeassistant/components/envisalink/__init__.py +++ b/homeassistant/components/envisalink/__init__.py @@ -6,85 +6,94 @@ import voluptuous as vol from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.const import EVENT_HOMEASSISTANT_STOP, CONF_TIMEOUT, \ - CONF_HOST +from homeassistant.const import EVENT_HOMEASSISTANT_STOP, CONF_TIMEOUT, CONF_HOST from homeassistant.helpers.entity import Entity from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.dispatcher import async_dispatcher_send _LOGGER = logging.getLogger(__name__) -DOMAIN = 'envisalink' +DOMAIN = "envisalink" -DATA_EVL = 'envisalink' +DATA_EVL = "envisalink" -CONF_CODE = 'code' -CONF_EVL_KEEPALIVE = 'keepalive_interval' -CONF_EVL_PORT = 'port' -CONF_EVL_VERSION = 'evl_version' -CONF_PANEL_TYPE = 'panel_type' -CONF_PANIC = 'panic_type' -CONF_PARTITIONNAME = 'name' -CONF_PARTITIONS = 'partitions' -CONF_PASS = 'password' -CONF_USERNAME = 'user_name' -CONF_ZONEDUMP_INTERVAL = 'zonedump_interval' -CONF_ZONENAME = 'name' -CONF_ZONES = 'zones' -CONF_ZONETYPE = 'type' +CONF_CODE = "code" +CONF_EVL_KEEPALIVE = "keepalive_interval" +CONF_EVL_PORT = "port" +CONF_EVL_VERSION = "evl_version" +CONF_PANEL_TYPE = "panel_type" +CONF_PANIC = "panic_type" +CONF_PARTITIONNAME = "name" +CONF_PARTITIONS = "partitions" +CONF_PASS = "password" +CONF_USERNAME = "user_name" +CONF_ZONEDUMP_INTERVAL = "zonedump_interval" +CONF_ZONENAME = "name" +CONF_ZONES = "zones" +CONF_ZONETYPE = "type" DEFAULT_PORT = 4025 DEFAULT_EVL_VERSION = 3 DEFAULT_KEEPALIVE = 60 DEFAULT_ZONEDUMP_INTERVAL = 30 -DEFAULT_ZONETYPE = 'opening' -DEFAULT_PANIC = 'Police' +DEFAULT_ZONETYPE = "opening" +DEFAULT_PANIC = "Police" DEFAULT_TIMEOUT = 10 -SIGNAL_ZONE_UPDATE = 'envisalink.zones_updated' -SIGNAL_PARTITION_UPDATE = 'envisalink.partition_updated' -SIGNAL_KEYPAD_UPDATE = 'envisalink.keypad_updated' +SIGNAL_ZONE_UPDATE = "envisalink.zones_updated" +SIGNAL_PARTITION_UPDATE = "envisalink.partition_updated" +SIGNAL_KEYPAD_UPDATE = "envisalink.keypad_updated" -ZONE_SCHEMA = vol.Schema({ - vol.Required(CONF_ZONENAME): cv.string, - vol.Optional(CONF_ZONETYPE, default=DEFAULT_ZONETYPE): cv.string}) +ZONE_SCHEMA = vol.Schema( + { + vol.Required(CONF_ZONENAME): cv.string, + vol.Optional(CONF_ZONETYPE, default=DEFAULT_ZONETYPE): cv.string, + } +) -PARTITION_SCHEMA = vol.Schema({ - vol.Required(CONF_PARTITIONNAME): cv.string}) +PARTITION_SCHEMA = vol.Schema({vol.Required(CONF_PARTITIONNAME): cv.string}) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_PANEL_TYPE): - vol.All(cv.string, vol.In(['HONEYWELL', 'DSC'])), - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASS): cv.string, - vol.Optional(CONF_CODE): cv.string, - vol.Optional(CONF_PANIC, default=DEFAULT_PANIC): cv.string, - vol.Optional(CONF_ZONES): {vol.Coerce(int): ZONE_SCHEMA}, - vol.Optional(CONF_PARTITIONS): {vol.Coerce(int): PARTITION_SCHEMA}, - vol.Optional(CONF_EVL_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_EVL_VERSION, default=DEFAULT_EVL_VERSION): - vol.All(vol.Coerce(int), vol.Range(min=3, max=4)), - vol.Optional(CONF_EVL_KEEPALIVE, default=DEFAULT_KEEPALIVE): - vol.All(vol.Coerce(int), vol.Range(min=15)), - vol.Optional( - CONF_ZONEDUMP_INTERVAL, - default=DEFAULT_ZONEDUMP_INTERVAL): vol.Coerce(int), - vol.Optional( - CONF_TIMEOUT, - default=DEFAULT_TIMEOUT): vol.Coerce(int), - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PANEL_TYPE): vol.All( + cv.string, vol.In(["HONEYWELL", "DSC"]) + ), + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASS): cv.string, + vol.Optional(CONF_CODE): cv.string, + vol.Optional(CONF_PANIC, default=DEFAULT_PANIC): cv.string, + vol.Optional(CONF_ZONES): {vol.Coerce(int): ZONE_SCHEMA}, + vol.Optional(CONF_PARTITIONS): {vol.Coerce(int): PARTITION_SCHEMA}, + vol.Optional(CONF_EVL_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_EVL_VERSION, default=DEFAULT_EVL_VERSION): vol.All( + vol.Coerce(int), vol.Range(min=3, max=4) + ), + vol.Optional(CONF_EVL_KEEPALIVE, default=DEFAULT_KEEPALIVE): vol.All( + vol.Coerce(int), vol.Range(min=15) + ), + vol.Optional( + CONF_ZONEDUMP_INTERVAL, default=DEFAULT_ZONEDUMP_INTERVAL + ): vol.Coerce(int), + vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): vol.Coerce(int), + } + ) + }, + extra=vol.ALLOW_EXTRA, +) -SERVICE_CUSTOM_FUNCTION = 'invoke_custom_function' -ATTR_CUSTOM_FUNCTION = 'pgm' -ATTR_PARTITION = 'partition' +SERVICE_CUSTOM_FUNCTION = "invoke_custom_function" +ATTR_CUSTOM_FUNCTION = "pgm" +ATTR_PARTITION = "partition" -SERVICE_SCHEMA = vol.Schema({ - vol.Required(ATTR_CUSTOM_FUNCTION): cv.string, - vol.Required(ATTR_PARTITION): cv.string, -}) +SERVICE_SCHEMA = vol.Schema( + { + vol.Required(ATTR_CUSTOM_FUNCTION): cv.string, + vol.Required(ATTR_PARTITION): cv.string, + } +) async def async_setup(hass, config): @@ -109,8 +118,17 @@ async def async_setup(hass, config): sync_connect = asyncio.Future() controller = EnvisalinkAlarmPanel( - host, port, panel_type, version, user, password, zone_dump, - keep_alive, hass.loop, connection_timeout) + host, + port, + panel_type, + version, + user, + password, + zone_dump, + keep_alive, + hass.loop, + connection_timeout, + ) hass.data[DATA_EVL] = controller @callback @@ -132,8 +150,7 @@ async def async_setup(hass, config): """Handle a successful connection.""" _LOGGER.info("Established a connection with the Envisalink") if not sync_connect.done(): - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, - stop_envisalink) + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_envisalink) sync_connect.set_result(True) @callback @@ -183,30 +200,34 @@ async def async_setup(hass, config): # Load sub-components for Envisalink if partitions: - hass.async_create_task(async_load_platform( - hass, 'alarm_control_panel', 'envisalink', { - CONF_PARTITIONS: partitions, - CONF_CODE: code, - CONF_PANIC: panic_type - }, config - )) - hass.async_create_task(async_load_platform( - hass, 'sensor', 'envisalink', { - CONF_PARTITIONS: partitions, - CONF_CODE: code - }, config - )) + hass.async_create_task( + async_load_platform( + hass, + "alarm_control_panel", + "envisalink", + {CONF_PARTITIONS: partitions, CONF_CODE: code, CONF_PANIC: panic_type}, + config, + ) + ) + hass.async_create_task( + async_load_platform( + hass, + "sensor", + "envisalink", + {CONF_PARTITIONS: partitions, CONF_CODE: code}, + config, + ) + ) if zones: - hass.async_create_task(async_load_platform( - hass, 'binary_sensor', 'envisalink', { - CONF_ZONES: zones - }, config - )) + hass.async_create_task( + async_load_platform( + hass, "binary_sensor", "envisalink", {CONF_ZONES: zones}, config + ) + ) - hass.services.async_register(DOMAIN, - SERVICE_CUSTOM_FUNCTION, - handle_custom_function, - schema=SERVICE_SCHEMA) + hass.services.async_register( + DOMAIN, SERVICE_CUSTOM_FUNCTION, handle_custom_function, schema=SERVICE_SCHEMA + ) return True diff --git a/homeassistant/components/envisalink/alarm_control_panel.py b/homeassistant/components/envisalink/alarm_control_panel.py index 91a59d8f842..81e656708c5 100644 --- a/homeassistant/components/envisalink/alarm_control_panel.py +++ b/homeassistant/components/envisalink/alarm_control_panel.py @@ -5,31 +5,44 @@ import voluptuous as vol import homeassistant.components.alarm_control_panel as alarm from homeassistant.const import ( - ATTR_ENTITY_ID, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, - STATE_ALARM_DISARMED, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, - STATE_UNKNOWN) + ATTR_ENTITY_ID, + STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_HOME, + STATE_ALARM_DISARMED, + STATE_ALARM_PENDING, + STATE_ALARM_TRIGGERED, + STATE_UNKNOWN, +) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_connect from . import ( - CONF_CODE, CONF_PANIC, CONF_PARTITIONNAME, DATA_EVL, PARTITION_SCHEMA, - SIGNAL_KEYPAD_UPDATE, SIGNAL_PARTITION_UPDATE, EnvisalinkDevice) + CONF_CODE, + CONF_PANIC, + CONF_PARTITIONNAME, + DATA_EVL, + PARTITION_SCHEMA, + SIGNAL_KEYPAD_UPDATE, + SIGNAL_PARTITION_UPDATE, + EnvisalinkDevice, +) _LOGGER = logging.getLogger(__name__) -SERVICE_ALARM_KEYPRESS = 'envisalink_alarm_keypress' -ATTR_KEYPRESS = 'keypress' -ALARM_KEYPRESS_SCHEMA = vol.Schema({ - vol.Required(ATTR_ENTITY_ID): cv.entity_ids, - vol.Required(ATTR_KEYPRESS): cv.string -}) +SERVICE_ALARM_KEYPRESS = "envisalink_alarm_keypress" +ATTR_KEYPRESS = "keypress" +ALARM_KEYPRESS_SCHEMA = vol.Schema( + { + vol.Required(ATTR_ENTITY_ID): cv.entity_ids, + vol.Required(ATTR_KEYPRESS): cv.string, + } +) -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Perform the setup for Envisalink alarm panels.""" - configured_partitions = discovery_info['partitions'] + configured_partitions = discovery_info["partitions"] code = discovery_info[CONF_CODE] panic_type = discovery_info[CONF_PANIC] @@ -37,9 +50,14 @@ async def async_setup_platform( for part_num in configured_partitions: device_config_data = PARTITION_SCHEMA(configured_partitions[part_num]) device = EnvisalinkAlarm( - hass, part_num, device_config_data[CONF_PARTITIONNAME], code, - panic_type, hass.data[DATA_EVL].alarm_state['partition'][part_num], - hass.data[DATA_EVL]) + hass, + part_num, + device_config_data[CONF_PARTITIONNAME], + code, + panic_type, + hass.data[DATA_EVL].alarm_state["partition"][part_num], + hass.data[DATA_EVL], + ) devices.append(device) async_add_entities(devices) @@ -50,15 +68,19 @@ async def async_setup_platform( entity_ids = service.data.get(ATTR_ENTITY_ID) keypress = service.data.get(ATTR_KEYPRESS) - target_devices = [device for device in devices - if device.entity_id in entity_ids] + target_devices = [ + device for device in devices if device.entity_id in entity_ids + ] for device in target_devices: device.async_alarm_keypress(keypress) hass.services.async_register( - alarm.DOMAIN, SERVICE_ALARM_KEYPRESS, alarm_keypress_handler, - schema=ALARM_KEYPRESS_SCHEMA) + alarm.DOMAIN, + SERVICE_ALARM_KEYPRESS, + alarm_keypress_handler, + schema=ALARM_KEYPRESS_SCHEMA, + ) return True @@ -66,8 +88,9 @@ async def async_setup_platform( class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel): """Representation of an Envisalink-based alarm panel.""" - def __init__(self, hass, partition_number, alarm_name, code, panic_type, - info, controller): + def __init__( + self, hass, partition_number, alarm_name, code, panic_type, info, controller + ): """Initialize the alarm panel.""" self._partition_number = partition_number self._code = code @@ -78,10 +101,10 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel): async def async_added_to_hass(self): """Register callbacks.""" + async_dispatcher_connect(self.hass, SIGNAL_KEYPAD_UPDATE, self._update_callback) async_dispatcher_connect( - self.hass, SIGNAL_KEYPAD_UPDATE, self._update_callback) - async_dispatcher_connect( - self.hass, SIGNAL_PARTITION_UPDATE, self._update_callback) + self.hass, SIGNAL_PARTITION_UPDATE, self._update_callback + ) @callback def _update_callback(self, partition): @@ -101,46 +124,50 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel): """Return the state of the device.""" state = STATE_UNKNOWN - if self._info['status']['alarm']: + if self._info["status"]["alarm"]: state = STATE_ALARM_TRIGGERED - elif self._info['status']['armed_away']: + elif self._info["status"]["armed_away"]: state = STATE_ALARM_ARMED_AWAY - elif self._info['status']['armed_stay']: + elif self._info["status"]["armed_stay"]: state = STATE_ALARM_ARMED_HOME - elif self._info['status']['exit_delay']: + elif self._info["status"]["exit_delay"]: state = STATE_ALARM_PENDING - elif self._info['status']['entry_delay']: + elif self._info["status"]["entry_delay"]: state = STATE_ALARM_PENDING - elif self._info['status']['alpha']: + elif self._info["status"]["alpha"]: state = STATE_ALARM_DISARMED return state async def async_alarm_disarm(self, code=None): """Send disarm command.""" if code: - self.hass.data[DATA_EVL].disarm_partition( - str(code), self._partition_number) + self.hass.data[DATA_EVL].disarm_partition(str(code), self._partition_number) else: self.hass.data[DATA_EVL].disarm_partition( - str(self._code), self._partition_number) + str(self._code), self._partition_number + ) async def async_alarm_arm_home(self, code=None): """Send arm home command.""" if code: self.hass.data[DATA_EVL].arm_stay_partition( - str(code), self._partition_number) + str(code), self._partition_number + ) else: self.hass.data[DATA_EVL].arm_stay_partition( - str(self._code), self._partition_number) + str(self._code), self._partition_number + ) async def async_alarm_arm_away(self, code=None): """Send arm away command.""" if code: self.hass.data[DATA_EVL].arm_away_partition( - str(code), self._partition_number) + str(code), self._partition_number + ) else: self.hass.data[DATA_EVL].arm_away_partition( - str(self._code), self._partition_number) + str(self._code), self._partition_number + ) async def async_alarm_trigger(self, code=None): """Alarm trigger command. Will be used to trigger a panic alarm.""" @@ -151,4 +178,5 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel): """Send custom keypress.""" if keypress: self.hass.data[DATA_EVL].keypresses_to_partition( - self._partition_number, keypress) + self._partition_number, keypress + ) diff --git a/homeassistant/components/envisalink/binary_sensor.py b/homeassistant/components/envisalink/binary_sensor.py index bf47749d228..fbe9824d067 100644 --- a/homeassistant/components/envisalink/binary_sensor.py +++ b/homeassistant/components/envisalink/binary_sensor.py @@ -9,16 +9,20 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.util import dt as dt_util from . import ( - CONF_ZONENAME, CONF_ZONETYPE, DATA_EVL, SIGNAL_ZONE_UPDATE, ZONE_SCHEMA, - EnvisalinkDevice) + CONF_ZONENAME, + CONF_ZONETYPE, + DATA_EVL, + SIGNAL_ZONE_UPDATE, + ZONE_SCHEMA, + EnvisalinkDevice, +) _LOGGER = logging.getLogger(__name__) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Envisalink binary sensor devices.""" - configured_zones = discovery_info['zones'] + configured_zones = discovery_info["zones"] devices = [] for zone_num in configured_zones: @@ -28,8 +32,8 @@ async def async_setup_platform(hass, config, async_add_entities, zone_num, device_config_data[CONF_ZONENAME], device_config_data[CONF_ZONETYPE], - hass.data[DATA_EVL].alarm_state['zone'][zone_num], - hass.data[DATA_EVL] + hass.data[DATA_EVL].alarm_state["zone"][zone_num], + hass.data[DATA_EVL], ) devices.append(device) @@ -39,19 +43,17 @@ async def async_setup_platform(hass, config, async_add_entities, class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice): """Representation of an Envisalink binary sensor.""" - def __init__(self, hass, zone_number, zone_name, zone_type, info, - controller): + def __init__(self, hass, zone_number, zone_name, zone_type, info, controller): """Initialize the binary_sensor.""" self._zone_type = zone_type self._zone_number = zone_number - _LOGGER.debug('Setting up zone: %s', zone_name) + _LOGGER.debug("Setting up zone: %s", zone_name) super().__init__(zone_name, info, controller) async def async_added_to_hass(self): """Register callbacks.""" - async_dispatcher_connect( - self.hass, SIGNAL_ZONE_UPDATE, self._update_callback) + async_dispatcher_connect(self.hass, SIGNAL_ZONE_UPDATE, self._update_callback) @property def device_state_attributes(self): @@ -67,7 +69,7 @@ class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice): # interval, so we subtract it from the current second-accurate time # unless it is already at the maximum value, in which case we set it # to None since we can't determine the actual value. - seconds_ago = self._info['last_fault'] + seconds_ago = self._info["last_fault"] if seconds_ago < 65536 * 5: now = dt_util.now().replace(microsecond=0) delta = datetime.timedelta(seconds=seconds_ago) @@ -81,7 +83,7 @@ class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice): @property def is_on(self): """Return true if sensor is on.""" - return self._info['status']['open'] + return self._info["status"]["open"] @property def device_class(self): diff --git a/homeassistant/components/envisalink/sensor.py b/homeassistant/components/envisalink/sensor.py index 2652a7e2137..05ad0783fac 100644 --- a/homeassistant/components/envisalink/sensor.py +++ b/homeassistant/components/envisalink/sensor.py @@ -6,24 +6,31 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity from . import ( - CONF_PARTITIONNAME, DATA_EVL, PARTITION_SCHEMA, SIGNAL_KEYPAD_UPDATE, - SIGNAL_PARTITION_UPDATE, EnvisalinkDevice) + CONF_PARTITIONNAME, + DATA_EVL, + PARTITION_SCHEMA, + SIGNAL_KEYPAD_UPDATE, + SIGNAL_PARTITION_UPDATE, + EnvisalinkDevice, +) _LOGGER = logging.getLogger(__name__) -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Perform the setup for Envisalink sensor devices.""" - configured_partitions = discovery_info['partitions'] + configured_partitions = discovery_info["partitions"] devices = [] for part_num in configured_partitions: device_config_data = PARTITION_SCHEMA(configured_partitions[part_num]) device = EnvisalinkSensor( - hass, device_config_data[CONF_PARTITIONNAME], part_num, - hass.data[DATA_EVL].alarm_state['partition'][part_num], - hass.data[DATA_EVL]) + hass, + device_config_data[CONF_PARTITIONNAME], + part_num, + hass.data[DATA_EVL].alarm_state["partition"][part_num], + hass.data[DATA_EVL], + ) devices.append(device) @@ -33,21 +40,20 @@ async def async_setup_platform( class EnvisalinkSensor(EnvisalinkDevice, Entity): """Representation of an Envisalink keypad.""" - def __init__(self, hass, partition_name, partition_number, info, - controller): + def __init__(self, hass, partition_name, partition_number, info, controller): """Initialize the sensor.""" - self._icon = 'mdi:alarm' + self._icon = "mdi:alarm" self._partition_number = partition_number _LOGGER.debug("Setting up sensor for partition: %s", partition_name) - super().__init__(partition_name + ' Keypad', info, controller) + super().__init__(partition_name + " Keypad", info, controller) async def async_added_to_hass(self): """Register callbacks.""" + async_dispatcher_connect(self.hass, SIGNAL_KEYPAD_UPDATE, self._update_callback) async_dispatcher_connect( - self.hass, SIGNAL_KEYPAD_UPDATE, self._update_callback) - async_dispatcher_connect( - self.hass, SIGNAL_PARTITION_UPDATE, self._update_callback) + self.hass, SIGNAL_PARTITION_UPDATE, self._update_callback + ) @property def icon(self): @@ -57,12 +63,12 @@ class EnvisalinkSensor(EnvisalinkDevice, Entity): @property def state(self): """Return the overall state.""" - return self._info['status']['alpha'] + return self._info["status"]["alpha"] @property def device_state_attributes(self): """Return the state attributes.""" - return self._info['status'] + return self._info["status"] @callback def _update_callback(self, partition): diff --git a/homeassistant/components/ephember/climate.py b/homeassistant/components/ephember/climate.py index 09b0fc0c5fd..0e35b8bbee7 100644 --- a/homeassistant/components/ephember/climate.py +++ b/homeassistant/components/ephember/climate.py @@ -5,11 +5,20 @@ import voluptuous as vol from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA from homeassistant.components.climate.const import ( - HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, SUPPORT_AUX_HEAT, - SUPPORT_TARGET_TEMPERATURE, HVAC_MODE_OFF, CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE) + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + SUPPORT_AUX_HEAT, + SUPPORT_TARGET_TEMPERATURE, + HVAC_MODE_OFF, + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, +) from homeassistant.const import ( - ATTR_TEMPERATURE, TEMP_CELSIUS, CONF_USERNAME, CONF_PASSWORD) + ATTR_TEMPERATURE, + TEMP_CELSIUS, + CONF_USERNAME, + CONF_PASSWORD, +) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) @@ -19,15 +28,14 @@ SCAN_INTERVAL = timedelta(seconds=120) OPERATION_LIST = [HVAC_MODE_HEAT_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string} +) EPH_TO_HA_STATE = { - 'AUTO': HVAC_MODE_HEAT_COOL, - 'ON': HVAC_MODE_HEAT, - 'OFF': HVAC_MODE_OFF + "AUTO": HVAC_MODE_HEAT_COOL, + "ON": HVAC_MODE_HEAT, + "OFF": HVAC_MODE_OFF, } HA_STATE_TO_EPH = {value: key for key, value in EPH_TO_HA_STATE.items()} @@ -58,9 +66,9 @@ class EphEmberThermostat(ClimateDevice): def __init__(self, ember, zone): """Initialize the thermostat.""" self._ember = ember - self._zone_name = zone['name'] + self._zone_name = zone["name"] self._zone = zone - self._hot_water = zone['isHotWater'] + self._hot_water = zone["isHotWater"] @property def supported_features(self): @@ -68,8 +76,7 @@ class EphEmberThermostat(ClimateDevice): if self._hot_water: return SUPPORT_AUX_HEAT - return (SUPPORT_TARGET_TEMPERATURE | - SUPPORT_AUX_HEAT) + return SUPPORT_TARGET_TEMPERATURE | SUPPORT_AUX_HEAT @property def name(self): @@ -84,12 +91,12 @@ class EphEmberThermostat(ClimateDevice): @property def current_temperature(self): """Return the current temperature.""" - return self._zone['currentTemperature'] + return self._zone["currentTemperature"] @property def target_temperature(self): """Return the temperature we try to reach.""" - return self._zone['targetTemperature'] + return self._zone["targetTemperature"] @property def target_temperature_step(self): @@ -102,7 +109,7 @@ class EphEmberThermostat(ClimateDevice): @property def hvac_action(self): """Return current HVAC action.""" - if self._zone['isCurrentlyActive']: + if self._zone["isCurrentlyActive"]: return CURRENT_HVAC_HEAT return CURRENT_HVAC_IDLE @@ -111,7 +118,8 @@ class EphEmberThermostat(ClimateDevice): def hvac_mode(self): """Return current operation ie. heat, cool, idle.""" from pyephember.pyephember import ZoneMode - mode = ZoneMode(self._zone['mode']) + + mode = ZoneMode(self._zone["mode"]) return self.map_mode_eph_hass(mode) @property @@ -130,12 +138,13 @@ class EphEmberThermostat(ClimateDevice): @property def is_aux_heat(self): """Return true if aux heater.""" - return self._zone['isBoostActive'] + return self._zone["isBoostActive"] def turn_aux_heat_on(self): """Turn auxiliary heater on.""" self._ember.activate_boost_by_name( - self._zone_name, self._zone['targetTemperature']) + self._zone_name, self._zone["targetTemperature"] + ) def turn_aux_heat_off(self): """Turn auxiliary heater off.""" @@ -156,15 +165,14 @@ class EphEmberThermostat(ClimateDevice): if temperature > self.max_temp or temperature < self.min_temp: return - self._ember.set_target_temperture_by_name(self._zone_name, - int(temperature)) + self._ember.set_target_temperture_by_name(self._zone_name, int(temperature)) @property def min_temp(self): """Return the minimum temperature.""" # Hot water temp doesn't support being changed if self._hot_water: - return self._zone['targetTemperature'] + return self._zone["targetTemperature"] return 5 @@ -172,7 +180,7 @@ class EphEmberThermostat(ClimateDevice): def max_temp(self): """Return the maximum temperature.""" if self._hot_water: - return self._zone['targetTemperature'] + return self._zone["targetTemperature"] return 35 @@ -184,6 +192,7 @@ class EphEmberThermostat(ClimateDevice): def map_mode_hass_eph(operation_mode): """Map from home assistant mode to eph mode.""" from pyephember.pyephember import ZoneMode + return getattr(ZoneMode, HA_STATE_TO_EPH.get(operation_mode), None) @staticmethod diff --git a/homeassistant/components/epson/media_player.py b/homeassistant/components/epson/media_player.py index e8e54a425cf..435ef582da8 100644 --- a/homeassistant/components/epson/media_player.py +++ b/homeassistant/components/epson/media_player.py @@ -3,48 +3,65 @@ import logging import voluptuous as vol -from homeassistant.components.media_player import ( - MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA from homeassistant.components.media_player.const import ( - DOMAIN, SUPPORT_NEXT_TRACK, - SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP) + DOMAIN, + SUPPORT_NEXT_TRACK, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_SELECT_SOURCE, + SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, + SUPPORT_VOLUME_MUTE, + SUPPORT_VOLUME_STEP, +) from homeassistant.const import ( - ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_PORT, CONF_SSL, STATE_OFF, - STATE_ON) + ATTR_ENTITY_ID, + CONF_HOST, + CONF_NAME, + CONF_PORT, + CONF_SSL, + STATE_OFF, + STATE_ON, +) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -ATTR_CMODE = 'cmode' +ATTR_CMODE = "cmode" -DATA_EPSON = 'epson' -DEFAULT_NAME = 'EPSON Projector' +DATA_EPSON = "epson" +DEFAULT_NAME = "EPSON Projector" -SERVICE_SELECT_CMODE = 'epson_select_cmode' +SERVICE_SELECT_CMODE = "epson_select_cmode" SUPPORT_CMODE = 33001 -SUPPORT_EPSON = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE |\ - SUPPORT_CMODE | SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_STEP | \ - SUPPORT_NEXT_TRACK | SUPPORT_PREVIOUS_TRACK +SUPPORT_EPSON = ( + SUPPORT_TURN_ON + | SUPPORT_TURN_OFF + | SUPPORT_SELECT_SOURCE + | SUPPORT_CMODE + | SUPPORT_VOLUME_MUTE + | SUPPORT_VOLUME_STEP + | SUPPORT_NEXT_TRACK + | SUPPORT_PREVIOUS_TRACK +) -MEDIA_PLAYER_SCHEMA = vol.Schema({ - ATTR_ENTITY_ID: cv.comp_entity_ids, -}) +MEDIA_PLAYER_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.comp_entity_ids}) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PORT, default=80): cv.port, - vol.Optional(CONF_SSL, default=False): cv.boolean, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PORT, default=80): cv.port, + vol.Optional(CONF_SSL, default=False): cv.boolean, + } +) -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Epson media player platform.""" - from epson_projector.const import (CMODE_LIST_SET) + from epson_projector.const import CMODE_LIST_SET if DATA_EPSON not in hass.data: hass.data[DATA_EPSON] = [] @@ -54,8 +71,9 @@ async def async_setup_platform( port = config.get(CONF_PORT) ssl = config.get(CONF_SSL) - epson = EpsonProjector(async_get_clientsession( - hass, verify_ssl=False), name, host, port, ssl) + epson = EpsonProjector( + async_get_clientsession(hass, verify_ssl=False), name, host, port, ssl + ) hass.data[DATA_EPSON].append(epson) async_add_entities([epson], update_before_add=True) @@ -64,8 +82,11 @@ async def async_setup_platform( """Handle for services.""" entity_ids = service.data.get(ATTR_ENTITY_ID) if entity_ids: - devices = [device for device in hass.data[DATA_EPSON] - if device.entity_id in entity_ids] + devices = [ + device + for device in hass.data[DATA_EPSON] + if device.entity_id in entity_ids + ] else: devices = hass.data[DATA_EPSON] for device in devices: @@ -74,12 +95,12 @@ async def async_setup_platform( await device.select_cmode(cmode) device.async_schedule_update_ha_state(True) - epson_schema = MEDIA_PLAYER_SCHEMA.extend({ - vol.Required(ATTR_CMODE): vol.All(cv.string, vol.Any(*CMODE_LIST_SET)) - }) + epson_schema = MEDIA_PLAYER_SCHEMA.extend( + {vol.Required(ATTR_CMODE): vol.All(cv.string, vol.Any(*CMODE_LIST_SET))} + ) hass.services.async_register( - DOMAIN, SERVICE_SELECT_CMODE, async_service_handler, - schema=epson_schema) + DOMAIN, SERVICE_SELECT_CMODE, async_service_handler, schema=epson_schema + ) class EpsonProjector(MediaPlayerDevice): @@ -91,8 +112,7 @@ class EpsonProjector(MediaPlayerDevice): from epson_projector.const import DEFAULT_SOURCES self._name = name - self._projector = epson.Projector( - host, websession=websession, port=port) + self._projector = epson.Projector(host, websession=websession, port=port) self._cmode = None self._source_list = list(DEFAULT_SOURCES.values()) self._source = None @@ -102,8 +122,16 @@ class EpsonProjector(MediaPlayerDevice): async def async_update(self): """Update state of device.""" from epson_projector.const import ( - EPSON_CODES, POWER, CMODE, CMODE_LIST, SOURCE, VOLUME, BUSY, - SOURCE_LIST) + EPSON_CODES, + POWER, + CMODE, + CMODE_LIST, + SOURCE, + VOLUME, + BUSY, + SOURCE_LIST, + ) + is_turned_on = await self._projector.get_property(POWER) _LOGGER.debug("Project turn on/off status: %s", is_turned_on) if is_turned_on and is_turned_on == EPSON_CODES[POWER]: @@ -138,12 +166,14 @@ class EpsonProjector(MediaPlayerDevice): async def async_turn_on(self): """Turn on epson.""" from epson_projector.const import TURN_ON + if self._state == STATE_OFF: await self._projector.send_command(TURN_ON) async def async_turn_off(self): """Turn off epson.""" from epson_projector.const import TURN_OFF + if self._state == STATE_ON: await self._projector.send_command(TURN_OFF) @@ -164,48 +194,57 @@ class EpsonProjector(MediaPlayerDevice): async def select_cmode(self, cmode): """Set color mode in Epson.""" - from epson_projector.const import (CMODE_LIST_SET) + from epson_projector.const import CMODE_LIST_SET + await self._projector.send_command(CMODE_LIST_SET[cmode]) async def async_select_source(self, source): """Select input source.""" from epson_projector.const import INV_SOURCES + selected_source = INV_SOURCES[source] await self._projector.send_command(selected_source) async def async_mute_volume(self, mute): """Mute (true) or unmute (false) sound.""" from epson_projector.const import MUTE + await self._projector.send_command(MUTE) async def async_volume_up(self): """Increase volume.""" from epson_projector.const import VOL_UP + await self._projector.send_command(VOL_UP) async def async_volume_down(self): """Decrease volume.""" from epson_projector.const import VOL_DOWN + await self._projector.send_command(VOL_DOWN) async def async_media_play(self): """Play media via Epson.""" from epson_projector.const import PLAY + await self._projector.send_command(PLAY) async def async_media_pause(self): """Pause media via Epson.""" from epson_projector.const import PAUSE + await self._projector.send_command(PAUSE) async def async_media_next_track(self): """Skip to next.""" from epson_projector.const import FAST + await self._projector.send_command(FAST) async def async_media_previous_track(self): """Skip to previous.""" from epson_projector.const import BACK + await self._projector.send_command(BACK) @property diff --git a/homeassistant/components/epsonworkforce/sensor.py b/homeassistant/components/epsonworkforce/sensor.py index 4f9ea4a1dd0..99e2723bf4a 100644 --- a/homeassistant/components/epsonworkforce/sensor.py +++ b/homeassistant/components/epsonworkforce/sensor.py @@ -10,22 +10,25 @@ from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -REQUIREMENTS = ['epsonprinter==0.0.9'] +REQUIREMENTS = ["epsonprinter==0.0.9"] _LOGGER = logging.getLogger(__name__) MONITORED_CONDITIONS = { - 'black': ['Ink level Black', '%', 'mdi:water'], - 'photoblack': ['Ink level Photoblack', '%', 'mdi:water'], - 'magenta': ['Ink level Magenta', '%', 'mdi:water'], - 'cyan': ['Ink level Cyan', '%', 'mdi:water'], - 'yellow': ['Ink level Yellow', '%', 'mdi:water'], - 'clean': ['Cleaning level', '%', 'mdi:water'], + "black": ["Ink level Black", "%", "mdi:water"], + "photoblack": ["Ink level Photoblack", "%", "mdi:water"], + "magenta": ["Ink level Magenta", "%", "mdi:water"], + "cyan": ["Ink level Cyan", "%", "mdi:water"], + "yellow": ["Ink level Yellow", "%", "mdi:water"], + "clean": ["Cleaning level", "%", "mdi:water"], } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_MONITORED_CONDITIONS): - vol.All(cv.ensure_list, [vol.In(MONITORED_CONDITIONS)]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_MONITORED_CONDITIONS): vol.All( + cv.ensure_list, [vol.In(MONITORED_CONDITIONS)] + ), + } +) SCAN_INTERVAL = timedelta(minutes=60) @@ -34,12 +37,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None): host = config.get(CONF_HOST) from epsonprinter_pkg.epsonprinterapi import EpsonPrinterAPI + api = EpsonPrinterAPI(host) if not api.available: raise PlatformNotReady() - sensors = [EpsonPrinterCartridge(api, condition) - for condition in config[CONF_MONITORED_CONDITIONS]] + sensors = [ + EpsonPrinterCartridge(api, condition) + for condition in config[CONF_MONITORED_CONDITIONS] + ] add_devices(sensors, True) diff --git a/homeassistant/components/eq3btsmart/climate.py b/homeassistant/components/eq3btsmart/climate.py index 90b6b7e134e..219578c34be 100644 --- a/homeassistant/components/eq3btsmart/climate.py +++ b/homeassistant/components/eq3btsmart/climate.py @@ -6,21 +6,33 @@ import voluptuous as vol from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice from homeassistant.components.climate.const import ( - HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF, PRESET_AWAY, PRESET_BOOST, - SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE, PRESET_NONE) + HVAC_MODE_AUTO, + HVAC_MODE_HEAT, + HVAC_MODE_OFF, + PRESET_AWAY, + PRESET_BOOST, + SUPPORT_PRESET_MODE, + SUPPORT_TARGET_TEMPERATURE, + PRESET_NONE, +) from homeassistant.const import ( - ATTR_TEMPERATURE, CONF_DEVICES, CONF_MAC, PRECISION_HALVES, TEMP_CELSIUS) + ATTR_TEMPERATURE, + CONF_DEVICES, + CONF_MAC, + PRECISION_HALVES, + TEMP_CELSIUS, +) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -STATE_BOOST = 'boost' +STATE_BOOST = "boost" -ATTR_STATE_WINDOW_OPEN = 'window_open' -ATTR_STATE_VALVE = 'valve' -ATTR_STATE_LOCKED = 'is_locked' -ATTR_STATE_LOW_BAT = 'low_battery' -ATTR_STATE_AWAY_END = 'away_end' +ATTR_STATE_WINDOW_OPEN = "window_open" +ATTR_STATE_VALVE = "valve" +ATTR_STATE_LOCKED = "is_locked" +ATTR_STATE_LOW_BAT = "low_battery" +ATTR_STATE_AWAY_END = "away_end" EQ_TO_HA_HVAC = { eq3.Mode.Open: HVAC_MODE_HEAT, @@ -34,28 +46,19 @@ EQ_TO_HA_HVAC = { HA_TO_EQ_HVAC = { HVAC_MODE_HEAT: eq3.Mode.Manual, HVAC_MODE_OFF: eq3.Mode.Closed, - HVAC_MODE_AUTO: eq3.Mode.Auto + HVAC_MODE_AUTO: eq3.Mode.Auto, } -EQ_TO_HA_PRESET = { - eq3.Mode.Boost: PRESET_BOOST, - eq3.Mode.Away: PRESET_AWAY, -} +EQ_TO_HA_PRESET = {eq3.Mode.Boost: PRESET_BOOST, eq3.Mode.Away: PRESET_AWAY} -HA_TO_EQ_PRESET = { - PRESET_BOOST: eq3.Mode.Boost, - PRESET_AWAY: eq3.Mode.Away, -} +HA_TO_EQ_PRESET = {PRESET_BOOST: eq3.Mode.Boost, PRESET_AWAY: eq3.Mode.Away} -DEVICE_SCHEMA = vol.Schema({ - vol.Required(CONF_MAC): cv.string, -}) +DEVICE_SCHEMA = vol.Schema({vol.Required(CONF_MAC): cv.string}) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_DEVICES): - vol.Schema({cv.string: DEVICE_SCHEMA}), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_DEVICES): vol.Schema({cv.string: DEVICE_SCHEMA})} +) SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE @@ -189,6 +192,7 @@ class EQ3BTSmartThermostat(ClimateDevice): """Update the data from the thermostat.""" # pylint: disable=import-error,no-name-in-module from bluepy.btle import BTLEException + try: self._thermostat.update() except BTLEException as ex: diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py index db5aeea2aa1..8780d2b67ae 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -5,14 +5,25 @@ import math from typing import Any, Callable, Dict, List, Optional from aioesphomeapi import ( - APIClient, APIConnectionError, DeviceInfo, EntityInfo, EntityState, - HomeassistantServiceCall, UserService, UserServiceArgType) + APIClient, + APIConnectionError, + DeviceInfo, + EntityInfo, + EntityState, + HomeassistantServiceCall, + UserService, + UserServiceArgType, +) import voluptuous as vol from homeassistant import const from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( - CONF_HOST, CONF_PASSWORD, CONF_PORT, EVENT_HOMEASSISTANT_STOP) + CONF_HOST, + CONF_PASSWORD, + CONF_PORT, + EVENT_HOMEASSISTANT_STOP, +) from homeassistant.core import Event, State, callback from homeassistant.exceptions import TemplateError from homeassistant.helpers import template @@ -29,14 +40,19 @@ from homeassistant.helpers.typing import ConfigType, HomeAssistantType # Import config flow so that it's added to the registry from .config_flow import EsphomeFlowHandler # noqa from .entry_data import ( - DATA_KEY, DISPATCHER_ON_DEVICE_UPDATE, DISPATCHER_ON_LIST, - DISPATCHER_ON_STATE, DISPATCHER_REMOVE_ENTITY, DISPATCHER_UPDATE_ENTITY, - RuntimeEntryData) + DATA_KEY, + DISPATCHER_ON_DEVICE_UPDATE, + DISPATCHER_ON_LIST, + DISPATCHER_ON_STATE, + DISPATCHER_REMOVE_ENTITY, + DISPATCHER_UPDATE_ENTITY, + RuntimeEntryData, +) -DOMAIN = 'esphome' +DOMAIN = "esphome" _LOGGER = logging.getLogger(__name__) -STORAGE_KEY = 'esphome.{}' +STORAGE_KEY = "esphome.{}" STORAGE_VERSION = 1 # No config schema - only configuration entry @@ -51,8 +67,7 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: return True -async def async_setup_entry(hass: HomeAssistantType, - entry: ConfigEntry) -> bool: +async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: """Set up the esphome component.""" hass.data.setdefault(DATA_KEY, {}) @@ -60,16 +75,20 @@ async def async_setup_entry(hass: HomeAssistantType, port = entry.data[CONF_PORT] password = entry.data[CONF_PASSWORD] - cli = APIClient(hass.loop, host, port, password, - client_info="Home Assistant {}".format(const.__version__)) + cli = APIClient( + hass.loop, + host, + port, + password, + client_info="Home Assistant {}".format(const.__version__), + ) # Store client in per-config-entry hass.data - store = Store(hass, STORAGE_VERSION, STORAGE_KEY.format(entry.entry_id), - encoder=JSONEncoder) + store = Store( + hass, STORAGE_VERSION, STORAGE_KEY.format(entry.entry_id), encoder=JSONEncoder + ) entry_data = hass.data[DATA_KEY][entry.entry_id] = RuntimeEntryData( - client=cli, - entry_id=entry.entry_id, - store=store, + client=cli, entry_id=entry.entry_id, store=store ) async def on_stop(event: Event) -> None: @@ -88,34 +107,39 @@ async def async_setup_entry(hass: HomeAssistantType, @callback def async_on_service_call(service: HomeassistantServiceCall) -> None: """Call service when user automation in ESPHome config is triggered.""" - domain, service_name = service.service.split('.', 1) + domain, service_name = service.service.split(".", 1) service_data = service.data if service.data_template: try: - data_template = {key: Template(value) for key, value in - service.data_template.items()} + data_template = { + key: Template(value) for key, value in service.data_template.items() + } template.attach(hass, data_template) - service_data.update(template.render_complex( - data_template, service.variables)) + service_data.update( + template.render_complex(data_template, service.variables) + ) except TemplateError as ex: - _LOGGER.error('Error rendering data template: %s', ex) + _LOGGER.error("Error rendering data template: %s", ex) return if service.is_event: # ESPHome uses servicecall packet for both events and service calls # Ensure the user can only send events of form 'esphome.xyz' - if domain != 'esphome': - _LOGGER.error("Can only generate events under esphome " - "domain!") + if domain != "esphome": + _LOGGER.error("Can only generate events under esphome " "domain!") return hass.bus.async_fire(service.service, service_data) else: - hass.async_create_task(hass.services.async_call( - domain, service_name, service_data, blocking=True)) + hass.async_create_task( + hass.services.async_call( + domain, service_name, service_data, blocking=True + ) + ) - async def send_home_assistant_state(entity_id: str, _, - new_state: Optional[State]) -> None: + async def send_home_assistant_state( + entity_id: str, _, new_state: Optional[State] + ) -> None: """Forward Home Assistant states to ESPHome.""" if new_state is None: return @@ -124,30 +148,27 @@ async def async_setup_entry(hass: HomeAssistantType, @callback def async_on_state_subscription(entity_id: str) -> None: """Subscribe and forward states for requested entities.""" - unsub = async_track_state_change( - hass, entity_id, send_home_assistant_state) + unsub = async_track_state_change(hass, entity_id, send_home_assistant_state) entry_data.disconnect_callbacks.append(unsub) # Send initial state - hass.async_create_task(send_home_assistant_state( - entity_id, None, hass.states.get(entity_id))) + hass.async_create_task( + send_home_assistant_state(entity_id, None, hass.states.get(entity_id)) + ) async def on_login() -> None: """Subscribe to states and list entities on successful API login.""" try: entry_data.device_info = await cli.device_info() entry_data.available = True - await _async_setup_device_registry(hass, entry, - entry_data.device_info) + await _async_setup_device_registry(hass, entry, entry_data.device_info) entry_data.async_update_device_state(hass) entity_infos, services = await cli.list_entities_services() - await entry_data.async_update_static_infos( - hass, entry, entity_infos) + await entry_data.async_update_static_infos(hass, entry, entity_infos) await _setup_services(hass, entry_data, services) await cli.subscribe_states(async_on_state) await cli.subscribe_service_calls(async_on_service_call) - await cli.subscribe_home_assistant_states( - async_on_state_subscription) + await cli.subscribe_home_assistant_states(async_on_state_subscription) hass.async_create_task(entry_data.async_save_to_store()) except APIConnectionError as err: @@ -155,8 +176,7 @@ async def async_setup_entry(hass: HomeAssistantType, # Re-connection logic will trigger after this await cli.disconnect() - try_connect = await _setup_auto_reconnect_logic(hass, cli, entry, host, - on_login) + try_connect = await _setup_auto_reconnect_logic(hass, cli, entry, host, on_login) async def complete_setup() -> None: """Complete the config entry setup.""" @@ -172,10 +192,11 @@ async def async_setup_entry(hass: HomeAssistantType, return True -async def _setup_auto_reconnect_logic(hass: HomeAssistantType, - cli: APIClient, - entry: ConfigEntry, host: str, on_login): +async def _setup_auto_reconnect_logic( + hass: HomeAssistantType, cli: APIClient, entry: ConfigEntry, host: str, on_login +): """Set up the re-connect logic for the API client.""" + async def try_connect(tries: int = 0, is_disconnect: bool = True) -> None: """Try connecting to the API client. Will retry if not successful.""" if entry.entry_id not in hass.data[DOMAIN]: @@ -207,19 +228,19 @@ async def _setup_auto_reconnect_logic(hass: HomeAssistantType, # notify HA of connectivity directly, but for new we'll use a # really short reconnect interval. tries = min(tries, 10) # prevent OverflowError - wait_time = int(round(min(1.8**tries, 60.0))) + wait_time = int(round(min(1.8 ** tries, 60.0))) _LOGGER.info("Trying to reconnect in %s seconds", wait_time) await asyncio.sleep(wait_time) try: await cli.connect(on_stop=try_connect, login=True) except APIConnectionError as error: - _LOGGER.info("Can't connect to ESPHome API for %s: %s", - host, error) + _LOGGER.info("Can't connect to ESPHome API for %s: %s", host, error) # Schedule re-connect in event loop in order not to delay HA # startup. First connect is scheduled in tracked tasks. data.reconnect_task = hass.loop.create_task( - try_connect(tries + 1, is_disconnect=False)) + try_connect(tries + 1, is_disconnect=False) + ) else: _LOGGER.info("Successfully connected to %s", host) hass.async_create_task(on_login()) @@ -227,30 +248,28 @@ async def _setup_auto_reconnect_logic(hass: HomeAssistantType, return try_connect -async def _async_setup_device_registry(hass: HomeAssistantType, - entry: ConfigEntry, - device_info: DeviceInfo): +async def _async_setup_device_registry( + hass: HomeAssistantType, entry: ConfigEntry, device_info: DeviceInfo +): """Set up device registry feature for a particular config entry.""" sw_version = device_info.esphome_version if device_info.compilation_time: - sw_version += ' ({})'.format(device_info.compilation_time) + sw_version += " ({})".format(device_info.compilation_time) device_registry = await dr.async_get_registry(hass) device_registry.async_get_or_create( config_entry_id=entry.entry_id, - connections={ - (dr.CONNECTION_NETWORK_MAC, device_info.mac_address) - }, + connections={(dr.CONNECTION_NETWORK_MAC, device_info.mac_address)}, name=device_info.name, - manufacturer='espressif', + manufacturer="espressif", model=device_info.model, sw_version=sw_version, ) -async def _register_service(hass: HomeAssistantType, - entry_data: RuntimeEntryData, - service: UserService): - service_name = '{}_{}'.format(entry_data.device_info.name, service.name) +async def _register_service( + hass: HomeAssistantType, entry_data: RuntimeEntryData, service: UserService +): + service_name = "{}_{}".format(entry_data.device_info.name, service.name) schema = {} for arg in service.args: schema[vol.Required(arg.name)] = { @@ -267,13 +286,14 @@ async def _register_service(hass: HomeAssistantType, async def execute_service(call): await entry_data.client.execute_service(service, call.data) - hass.services.async_register(DOMAIN, service_name, execute_service, - vol.Schema(schema)) + hass.services.async_register( + DOMAIN, service_name, execute_service, vol.Schema(schema) + ) -async def _setup_services(hass: HomeAssistantType, - entry_data: RuntimeEntryData, - services: List[UserService]): +async def _setup_services( + hass: HomeAssistantType, entry_data: RuntimeEntryData, services: List[UserService] +): old_services = entry_data.services.copy() to_unregister = [] to_register = [] @@ -295,16 +315,16 @@ async def _setup_services(hass: HomeAssistantType, entry_data.services = {serv.key: serv for serv in services} for service in to_unregister: - service_name = '{}_{}'.format(entry_data.device_info.name, - service.name) + service_name = "{}_{}".format(entry_data.device_info.name, service.name) hass.services.async_remove(DOMAIN, service_name) for service in to_register: await _register_service(hass, entry_data, service) -async def _cleanup_instance(hass: HomeAssistantType, - entry: ConfigEntry) -> RuntimeEntryData: +async def _cleanup_instance( + hass: HomeAssistantType, entry: ConfigEntry +) -> RuntimeEntryData: """Cleanup the esphome client if it exists.""" data = hass.data[DATA_KEY].pop(entry.entry_id) # type: RuntimeEntryData if data.reconnect_task is not None: @@ -317,28 +337,27 @@ async def _cleanup_instance(hass: HomeAssistantType, return data -async def async_unload_entry(hass: HomeAssistantType, - entry: ConfigEntry) -> bool: +async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: """Unload an esphome config entry.""" entry_data = await _cleanup_instance(hass, entry) tasks = [] for platform in entry_data.loaded_platforms: - tasks.append(hass.config_entries.async_forward_entry_unload( - entry, platform)) + tasks.append(hass.config_entries.async_forward_entry_unload(entry, platform)) if tasks: await asyncio.wait(tasks) return True -async def platform_async_setup_entry(hass: HomeAssistantType, - entry: ConfigEntry, - async_add_entities, - *, - component_key: str, - info_type, - entity_type, - state_type - ) -> None: +async def platform_async_setup_entry( + hass: HomeAssistantType, + entry: ConfigEntry, + async_add_entities, + *, + component_key: str, + info_type, + entity_type, + state_type, +) -> None: """Set up an esphome platform. This method is in charge of receiving, distributing and storing @@ -399,6 +418,7 @@ def esphome_state_property(func): This checks if the state object in the entity is set, and prevents writing NAN values to the Home Assistant state machine. """ + @property def _wrapper(self): # pylint: disable=protected-access @@ -410,6 +430,7 @@ def esphome_state_property(func): # (not JSON serializable) return None return val + return _wrapper @@ -452,26 +473,28 @@ class EsphomeEntity(Entity): async def async_added_to_hass(self) -> None: """Register callbacks.""" kwargs = { - 'entry_id': self._entry_id, - 'component_key': self._component_key, - 'key': self._key, + "entry_id": self._entry_id, + "component_key": self._component_key, + "key": self._key, } self._remove_callbacks.append( - async_dispatcher_connect(self.hass, - DISPATCHER_UPDATE_ENTITY.format(**kwargs), - self._on_update) - ) - - self._remove_callbacks.append( - async_dispatcher_connect(self.hass, - DISPATCHER_REMOVE_ENTITY.format(**kwargs), - self.async_remove) + async_dispatcher_connect( + self.hass, DISPATCHER_UPDATE_ENTITY.format(**kwargs), self._on_update + ) ) self._remove_callbacks.append( async_dispatcher_connect( - self.hass, DISPATCHER_ON_DEVICE_UPDATE.format(**kwargs), - self.async_schedule_update_ha_state) + self.hass, DISPATCHER_REMOVE_ENTITY.format(**kwargs), self.async_remove + ) + ) + + self._remove_callbacks.append( + async_dispatcher_connect( + self.hass, + DISPATCHER_ON_DEVICE_UPDATE.format(**kwargs), + self.async_schedule_update_ha_state, + ) ) async def _on_update(self) -> None: @@ -530,8 +553,7 @@ class EsphomeEntity(Entity): def device_info(self) -> Dict[str, Any]: """Return device registry information for this entity.""" return { - 'connections': {(dr.CONNECTION_NETWORK_MAC, - self._device_info.mac_address)} + "connections": {(dr.CONNECTION_NETWORK_MAC, self._device_info.mac_address)} } @property diff --git a/homeassistant/components/esphome/binary_sensor.py b/homeassistant/components/esphome/binary_sensor.py index 75a7235c58f..4e684638bb7 100644 --- a/homeassistant/components/esphome/binary_sensor.py +++ b/homeassistant/components/esphome/binary_sensor.py @@ -14,10 +14,13 @@ _LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass, entry, async_add_entities): """Set up ESPHome binary sensors based on a config entry.""" await platform_async_setup_entry( - hass, entry, async_add_entities, - component_key='binary_sensor', - info_type=BinarySensorInfo, entity_type=EsphomeBinarySensor, - state_type=BinarySensorState + hass, + entry, + async_add_entities, + component_key="binary_sensor", + info_type=BinarySensorInfo, + entity_type=EsphomeBinarySensor, + state_type=BinarySensorState, ) diff --git a/homeassistant/components/esphome/camera.py b/homeassistant/components/esphome/camera.py index 54f774bc426..cc2e0cede23 100644 --- a/homeassistant/components/esphome/camera.py +++ b/homeassistant/components/esphome/camera.py @@ -15,14 +15,18 @@ from . import EsphomeEntity, platform_async_setup_entry _LOGGER = logging.getLogger(__name__) -async def async_setup_entry(hass: HomeAssistantType, - entry: ConfigEntry, async_add_entities) -> None: +async def async_setup_entry( + hass: HomeAssistantType, entry: ConfigEntry, async_add_entities +) -> None: """Set up esphome cameras based on a config entry.""" await platform_async_setup_entry( - hass, entry, async_add_entities, - component_key='camera', - info_type=CameraInfo, entity_type=EsphomeCamera, - state_type=CameraState + hass, + entry, + async_add_entities, + component_key="camera", + info_type=CameraInfo, + entity_type=EsphomeCamera, + state_type=CameraState, ) @@ -74,5 +78,5 @@ class EsphomeCamera(Camera, EsphomeEntity): async def handle_async_mjpeg_stream(self, request): """Serve an HTTP MJPEG stream from the camera.""" return await camera.async_get_still_stream( - request, self._async_camera_stream_image, - camera.DEFAULT_CONTENT_TYPE, 0.0) + request, self._async_camera_stream_image, camera.DEFAULT_CONTENT_TYPE, 0.0 + ) diff --git a/homeassistant/components/esphome/climate.py b/homeassistant/components/esphome/climate.py index d4cc7940768..7337aec4541 100644 --- a/homeassistant/components/esphome/climate.py +++ b/homeassistant/components/esphome/climate.py @@ -6,18 +6,32 @@ from aioesphomeapi import ClimateInfo, ClimateMode, ClimateState from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( - ATTR_HVAC_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, - HVAC_MODE_HEAT_COOL, HVAC_MODE_COOL, HVAC_MODE_HEAT, - SUPPORT_TARGET_TEMPERATURE, SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE_RANGE, PRESET_AWAY, - HVAC_MODE_OFF) + ATTR_HVAC_MODE, + ATTR_TARGET_TEMP_HIGH, + ATTR_TARGET_TEMP_LOW, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_COOL, + HVAC_MODE_HEAT, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_PRESET_MODE, + SUPPORT_TARGET_TEMPERATURE_RANGE, + PRESET_AWAY, + HVAC_MODE_OFF, +) from homeassistant.const import ( - ATTR_TEMPERATURE, PRECISION_HALVES, PRECISION_TENTHS, PRECISION_WHOLE, - TEMP_CELSIUS) + ATTR_TEMPERATURE, + PRECISION_HALVES, + PRECISION_TENTHS, + PRECISION_WHOLE, + TEMP_CELSIUS, +) from . import ( - EsphomeEntity, esphome_map_enum, esphome_state_property, - platform_async_setup_entry) + EsphomeEntity, + esphome_map_enum, + esphome_state_property, + platform_async_setup_entry, +) _LOGGER = logging.getLogger(__name__) @@ -25,10 +39,13 @@ _LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass, entry, async_add_entities): """Set up ESPHome climate devices based on a config entry.""" await platform_async_setup_entry( - hass, entry, async_add_entities, - component_key='climate', - info_type=ClimateInfo, entity_type=EsphomeClimateDevice, - state_type=ClimateState + hass, + entry, + async_add_entities, + component_key="climate", + info_type=ClimateInfo, + entity_type=EsphomeClimateDevice, + state_type=ClimateState, ) @@ -141,27 +158,24 @@ class EsphomeClimateDevice(EsphomeEntity, ClimateDevice): async def async_set_temperature(self, **kwargs) -> None: """Set new target temperature (and operation mode if set).""" - data = {'key': self._static_info.key} + data = {"key": self._static_info.key} if ATTR_HVAC_MODE in kwargs: - data['mode'] = _climate_modes.from_hass( - kwargs[ATTR_HVAC_MODE]) + data["mode"] = _climate_modes.from_hass(kwargs[ATTR_HVAC_MODE]) if ATTR_TEMPERATURE in kwargs: - data['target_temperature'] = kwargs[ATTR_TEMPERATURE] + data["target_temperature"] = kwargs[ATTR_TEMPERATURE] if ATTR_TARGET_TEMP_LOW in kwargs: - data['target_temperature_low'] = kwargs[ATTR_TARGET_TEMP_LOW] + data["target_temperature_low"] = kwargs[ATTR_TARGET_TEMP_LOW] if ATTR_TARGET_TEMP_HIGH in kwargs: - data['target_temperature_high'] = kwargs[ATTR_TARGET_TEMP_HIGH] + data["target_temperature_high"] = kwargs[ATTR_TARGET_TEMP_HIGH] await self._client.climate_command(**data) async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set new target operation mode.""" await self._client.climate_command( - key=self._static_info.key, - mode=_climate_modes.from_hass(hvac_mode), + key=self._static_info.key, mode=_climate_modes.from_hass(hvac_mode) ) async def async_set_preset_mode(self, preset_mode): """Set preset mode.""" away = preset_mode == PRESET_AWAY - await self._client.climate_command(key=self._static_info.key, - away=away) + await self._client.climate_command(key=self._static_info.key, away=away) diff --git a/homeassistant/components/esphome/config_flow.py b/homeassistant/components/esphome/config_flow.py index 2ce749d6ae9..35389d055d6 100644 --- a/homeassistant/components/esphome/config_flow.py +++ b/homeassistant/components/esphome/config_flow.py @@ -10,7 +10,7 @@ from homeassistant.helpers import ConfigType from .entry_data import DATA_KEY, RuntimeEntryData -@config_entries.HANDLERS.register('esphome') +@config_entries.HANDLERS.register("esphome") class EsphomeFlowHandler(config_entries.ConfigFlow): """Handle a esphome config flow.""" @@ -23,43 +23,40 @@ class EsphomeFlowHandler(config_entries.ConfigFlow): self._port = None # type: Optional[int] self._password = None # type: Optional[str] - async def async_step_user(self, user_input: Optional[ConfigType] = None, - error: Optional[str] = None): + async def async_step_user( + self, user_input: Optional[ConfigType] = None, error: Optional[str] = None + ): """Handle a flow initialized by the user.""" if user_input is not None: return await self._async_authenticate_or_add(user_input) fields = OrderedDict() - fields[vol.Required('host', default=self._host or vol.UNDEFINED)] = str - fields[vol.Optional('port', default=self._port or 6053)] = int + fields[vol.Required("host", default=self._host or vol.UNDEFINED)] = str + fields[vol.Optional("port", default=self._port or 6053)] = int errors = {} if error is not None: - errors['base'] = error + errors["base"] = error return self.async_show_form( - step_id='user', - data_schema=vol.Schema(fields), - errors=errors + step_id="user", data_schema=vol.Schema(fields), errors=errors ) @property def _name(self): - return self.context.get('name') + return self.context.get("name") @_name.setter def _name(self, value): # pylint: disable=unsupported-assignment-operation - self.context['name'] = value - self.context['title_placeholders'] = { - 'name': self._name - } + self.context["name"] = value + self.context["title_placeholders"] = {"name": self._name} def _set_user_input(self, user_input): if user_input is None: return - self._host = user_input['host'] - self._port = user_input['port'] + self._host = user_input["host"] + self._port = user_input["port"] async def _async_authenticate_or_add(self, user_input): self._set_user_input(user_input) @@ -79,42 +76,42 @@ class EsphomeFlowHandler(config_entries.ConfigFlow): if user_input is not None: return await self._async_authenticate_or_add(None) return self.async_show_form( - step_id='discovery_confirm', - description_placeholders={'name': self._name}, + step_id="discovery_confirm", description_placeholders={"name": self._name} ) async def async_step_zeroconf(self, user_input: ConfigType): """Handle zeroconf discovery.""" # Hostname is format: livingroom.local. - local_name = user_input['hostname'][:-1] - node_name = local_name[:-len('.local')] - address = user_input['properties'].get('address', local_name) + local_name = user_input["hostname"][:-1] + node_name = local_name[: -len(".local")] + address = user_input["properties"].get("address", local_name) # Check if already configured for entry in self._async_current_entries(): already_configured = False - if entry.data['host'] == address: + if entry.data["host"] == address: # Is this address already configured? already_configured = True elif entry.entry_id in self.hass.data.get(DATA_KEY, {}): # Does a config entry with this name already exist? data = self.hass.data[DATA_KEY][ - entry.entry_id] # type: RuntimeEntryData + entry.entry_id + ] # type: RuntimeEntryData # Node names are unique in the network if data.device_info is not None: already_configured = data.device_info.name == node_name if already_configured: - return self.async_abort(reason='already_configured') + return self.async_abort(reason="already_configured") self._host = address - self._port = user_input['port'] + self._port = user_input["port"] self._name = node_name # Check if flow for this device already in progress for flow in self._async_in_progress(): - if flow['context'].get('name') == node_name: - return self.async_abort(reason='already_configured') + if flow["context"].get("name") == node_name: + return self.async_abort(reason="already_configured") return await self.async_step_discovery_confirm() @@ -122,17 +119,17 @@ class EsphomeFlowHandler(config_entries.ConfigFlow): return self.async_create_entry( title=self._name, data={ - 'host': self._host, - 'port': self._port, + "host": self._host, + "port": self._port, # The API uses protobuf, so empty string denotes absence - 'password': self._password or '', - } + "password": self._password or "", + }, ) async def async_step_authenticate(self, user_input=None, error=None): """Handle getting password for authentication.""" if user_input is not None: - self._password = user_input['password'] + self._password = user_input["password"] error = await self.try_login() if error: return await self.async_step_authenticate(error=error) @@ -140,30 +137,28 @@ class EsphomeFlowHandler(config_entries.ConfigFlow): errors = {} if error is not None: - errors['base'] = error + errors["base"] = error return self.async_show_form( - step_id='authenticate', - data_schema=vol.Schema({ - vol.Required('password'): str - }), - description_placeholders={'name': self._name}, - errors=errors + step_id="authenticate", + data_schema=vol.Schema({vol.Required("password"): str}), + description_placeholders={"name": self._name}, + errors=errors, ) async def fetch_device_info(self): """Fetch device info from API and return any errors.""" from aioesphomeapi import APIClient, APIConnectionError - cli = APIClient(self.hass.loop, self._host, self._port, '') + cli = APIClient(self.hass.loop, self._host, self._port, "") try: await cli.connect() device_info = await cli.device_info() except APIConnectionError as err: - if 'resolving' in str(err): - return 'resolve_error', None - return 'connection_error', None + if "resolving" in str(err): + return "resolve_error", None + return "connection_error", None finally: await cli.disconnect(force=True) @@ -179,6 +174,6 @@ class EsphomeFlowHandler(config_entries.ConfigFlow): await cli.connect(login=True) except APIConnectionError: await cli.disconnect(force=True) - return 'invalid_password' + return "invalid_password" return None diff --git a/homeassistant/components/esphome/cover.py b/homeassistant/components/esphome/cover.py index b69b62075db..7da2fcee380 100644 --- a/homeassistant/components/esphome/cover.py +++ b/homeassistant/components/esphome/cover.py @@ -5,9 +5,17 @@ from typing import Optional from aioesphomeapi import CoverInfo, CoverState from homeassistant.components.cover import ( - ATTR_POSITION, ATTR_TILT_POSITION, SUPPORT_CLOSE, SUPPORT_CLOSE_TILT, - SUPPORT_OPEN, SUPPORT_OPEN_TILT, SUPPORT_SET_POSITION, - SUPPORT_SET_TILT_POSITION, SUPPORT_STOP, CoverDevice) + ATTR_POSITION, + ATTR_TILT_POSITION, + SUPPORT_CLOSE, + SUPPORT_CLOSE_TILT, + SUPPORT_OPEN, + SUPPORT_OPEN_TILT, + SUPPORT_SET_POSITION, + SUPPORT_SET_TILT_POSITION, + SUPPORT_STOP, + CoverDevice, +) from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType @@ -16,14 +24,18 @@ from . import EsphomeEntity, esphome_state_property, platform_async_setup_entry _LOGGER = logging.getLogger(__name__) -async def async_setup_entry(hass: HomeAssistantType, - entry: ConfigEntry, async_add_entities) -> None: +async def async_setup_entry( + hass: HomeAssistantType, entry: ConfigEntry, async_add_entities +) -> None: """Set up ESPHome covers based on a config entry.""" await platform_async_setup_entry( - hass, entry, async_add_entities, - component_key='cover', - info_type=CoverInfo, entity_type=EsphomeCover, - state_type=CoverState + hass, + entry, + async_add_entities, + component_key="cover", + info_type=CoverInfo, + entity_type=EsphomeCover, + state_type=CoverState, ) @@ -41,8 +53,7 @@ class EsphomeCover(EsphomeEntity, CoverDevice): if self._static_info.supports_position: flags |= SUPPORT_SET_POSITION if self._static_info.supports_tilt: - flags |= (SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | - SUPPORT_SET_TILT_POSITION) + flags |= SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | SUPPORT_SET_TILT_POSITION return flags @property @@ -69,12 +80,14 @@ class EsphomeCover(EsphomeEntity, CoverDevice): def is_opening(self) -> bool: """Return if the cover is opening or not.""" from aioesphomeapi import CoverOperation + return self._state.current_operation == CoverOperation.IS_OPENING @esphome_state_property def is_closing(self) -> bool: """Return if the cover is closing or not.""" from aioesphomeapi import CoverOperation + return self._state.current_operation == CoverOperation.IS_CLOSING @esphome_state_property @@ -93,13 +106,11 @@ class EsphomeCover(EsphomeEntity, CoverDevice): async def async_open_cover(self, **kwargs) -> None: """Open the cover.""" - await self._client.cover_command(key=self._static_info.key, - position=1.0) + await self._client.cover_command(key=self._static_info.key, position=1.0) async def async_close_cover(self, **kwargs) -> None: """Close cover.""" - await self._client.cover_command(key=self._static_info.key, - position=0.0) + await self._client.cover_command(key=self._static_info.key, position=0.0) async def async_stop_cover(self, **kwargs) -> None: """Stop the cover.""" @@ -107,8 +118,9 @@ class EsphomeCover(EsphomeEntity, CoverDevice): async def async_set_cover_position(self, **kwargs) -> None: """Move the cover to a specific position.""" - await self._client.cover_command(key=self._static_info.key, - position=kwargs[ATTR_POSITION] / 100) + await self._client.cover_command( + key=self._static_info.key, position=kwargs[ATTR_POSITION] / 100 + ) async def async_open_cover_tilt(self, **kwargs) -> None: """Open the cover tilt.""" @@ -120,5 +132,6 @@ class EsphomeCover(EsphomeEntity, CoverDevice): async def async_set_cover_tilt_position(self, **kwargs) -> None: """Move the cover tilt to a specific position.""" - await self._client.cover_command(key=self._static_info.key, - tilt=kwargs[ATTR_TILT_POSITION] / 100) + await self._client.cover_command( + key=self._static_info.key, tilt=kwargs[ATTR_TILT_POSITION] / 100 + ) diff --git a/homeassistant/components/esphome/entry_data.py b/homeassistant/components/esphome/entry_data.py index 4e78718b760..b7f9ad9b347 100644 --- a/homeassistant/components/esphome/entry_data.py +++ b/homeassistant/components/esphome/entry_data.py @@ -3,10 +3,21 @@ import asyncio from typing import Any, Callable, Dict, List, Optional, Tuple, Set from aioesphomeapi import ( - COMPONENT_TYPE_TO_INFO, DeviceInfo, EntityInfo, EntityState, UserService, + COMPONENT_TYPE_TO_INFO, + DeviceInfo, + EntityInfo, + EntityState, + UserService, BinarySensorInfo, - CameraInfo, ClimateInfo, CoverInfo, FanInfo, LightInfo, SensorInfo, - SwitchInfo, TextSensorInfo) + CameraInfo, + ClimateInfo, + CoverInfo, + FanInfo, + LightInfo, + SensorInfo, + SwitchInfo, + TextSensorInfo, +) import attr from homeassistant.config_entries import ConfigEntry @@ -14,24 +25,24 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.storage import Store from homeassistant.helpers.typing import HomeAssistantType -DATA_KEY = 'esphome' -DISPATCHER_UPDATE_ENTITY = 'esphome_{entry_id}_update_{component_key}_{key}' -DISPATCHER_REMOVE_ENTITY = 'esphome_{entry_id}_remove_{component_key}_{key}' -DISPATCHER_ON_LIST = 'esphome_{entry_id}_on_list' -DISPATCHER_ON_DEVICE_UPDATE = 'esphome_{entry_id}_on_device_update' -DISPATCHER_ON_STATE = 'esphome_{entry_id}_on_state' +DATA_KEY = "esphome" +DISPATCHER_UPDATE_ENTITY = "esphome_{entry_id}_update_{component_key}_{key}" +DISPATCHER_REMOVE_ENTITY = "esphome_{entry_id}_remove_{component_key}_{key}" +DISPATCHER_ON_LIST = "esphome_{entry_id}_on_list" +DISPATCHER_ON_DEVICE_UPDATE = "esphome_{entry_id}_on_device_update" +DISPATCHER_ON_STATE = "esphome_{entry_id}_on_state" # Mapping from ESPHome info type to HA platform INFO_TYPE_TO_PLATFORM = { - BinarySensorInfo: 'binary_sensor', - CameraInfo: 'camera', - ClimateInfo: 'climate', - CoverInfo: 'cover', - FanInfo: 'fan', - LightInfo: 'light', - SensorInfo: 'sensor', - SwitchInfo: 'switch', - TextSensorInfo: 'sensor', + BinarySensorInfo: "binary_sensor", + CameraInfo: "camera", + ClimateInfo: "climate", + CoverInfo: "cover", + FanInfo: "fan", + LightInfo: "light", + SensorInfo: "sensor", + SwitchInfo: "switch", + TextSensorInfo: "sensor", } @@ -40,12 +51,12 @@ class RuntimeEntryData: """Store runtime data for esphome config entries.""" entry_id = attr.ib(type=str) - client = attr.ib(type='APIClient') + client = attr.ib(type="APIClient") store = attr.ib(type=Store) reconnect_task = attr.ib(type=Optional[asyncio.Task], default=None) state = attr.ib(type=Dict[str, Dict[str, Any]], factory=dict) info = attr.ib(type=Dict[str, Dict[str, Any]], factory=dict) - services = attr.ib(type=Dict[int, 'UserService'], factory=dict) + services = attr.ib(type=Dict[int, "UserService"], factory=dict) available = attr.ib(type=bool, default=False) device_info = attr.ib(type=DeviceInfo, default=None) cleanup_callbacks = attr.ib(type=List[Callable[[], None]], factory=list) @@ -53,36 +64,41 @@ class RuntimeEntryData: loaded_platforms = attr.ib(type=Set[str], factory=set) platform_load_lock = attr.ib(type=asyncio.Lock, factory=asyncio.Lock) - def async_update_entity(self, hass: HomeAssistantType, component_key: str, - key: int) -> None: + def async_update_entity( + self, hass: HomeAssistantType, component_key: str, key: int + ) -> None: """Schedule the update of an entity.""" signal = DISPATCHER_UPDATE_ENTITY.format( - entry_id=self.entry_id, component_key=component_key, key=key) + entry_id=self.entry_id, component_key=component_key, key=key + ) async_dispatcher_send(hass, signal) - def async_remove_entity(self, hass: HomeAssistantType, component_key: str, - key: int) -> None: + def async_remove_entity( + self, hass: HomeAssistantType, component_key: str, key: int + ) -> None: """Schedule the removal of an entity.""" signal = DISPATCHER_REMOVE_ENTITY.format( - entry_id=self.entry_id, component_key=component_key, key=key) + entry_id=self.entry_id, component_key=component_key, key=key + ) async_dispatcher_send(hass, signal) - async def _ensure_platforms_loaded(self, hass: HomeAssistantType, - entry: ConfigEntry, - platforms: Set[str]): + async def _ensure_platforms_loaded( + self, hass: HomeAssistantType, entry: ConfigEntry, platforms: Set[str] + ): async with self.platform_load_lock: needed = platforms - self.loaded_platforms tasks = [] for platform in needed: - tasks.append(hass.config_entries.async_forward_entry_setup( - entry, platform)) + tasks.append( + hass.config_entries.async_forward_entry_setup(entry, platform) + ) if tasks: await asyncio.wait(tasks) self.loaded_platforms |= needed async def async_update_static_infos( - self, hass: HomeAssistantType, entry: ConfigEntry, - infos: List[EntityInfo]) -> None: + self, hass: HomeAssistantType, entry: ConfigEntry, infos: List[EntityInfo] + ) -> None: """Distribute an update of static infos to all platforms.""" # First, load all platforms needed_platforms = set() @@ -97,8 +113,7 @@ class RuntimeEntryData: signal = DISPATCHER_ON_LIST.format(entry_id=self.entry_id) async_dispatcher_send(hass, signal, infos) - def async_update_state(self, hass: HomeAssistantType, - state: EntityState) -> None: + def async_update_state(self, hass: HomeAssistantType, state: EntityState) -> None: """Distribute an update of state information to all platforms.""" signal = DISPATCHER_ON_STATE.format(entry_id=self.entry_id) async_dispatcher_send(hass, signal, state) @@ -108,15 +123,15 @@ class RuntimeEntryData: signal = DISPATCHER_ON_DEVICE_UPDATE.format(entry_id=self.entry_id) async_dispatcher_send(hass, signal) - async def async_load_from_store(self) -> Tuple[List[EntityInfo], - List[UserService]]: + async def async_load_from_store(self) -> Tuple[List[EntityInfo], List[UserService]]: """Load the retained data from store and return de-serialized data.""" restored = await self.store.async_load() if restored is None: return [], [] - self.device_info = _attr_obj_from_dict(DeviceInfo, - **restored.pop('device_info')) + self.device_info = _attr_obj_from_dict( + DeviceInfo, **restored.pop("device_info") + ) infos = [] for comp_type, restored_infos in restored.items(): if comp_type not in COMPONENT_TYPE_TO_INFO: @@ -125,26 +140,21 @@ class RuntimeEntryData: cls = COMPONENT_TYPE_TO_INFO[comp_type] infos.append(_attr_obj_from_dict(cls, **info)) services = [] - for service in restored.get('services', []): + for service in restored.get("services", []): services.append(UserService.from_dict(service)) return infos, services async def async_save_to_store(self) -> None: """Generate dynamic data to store and save it to the filesystem.""" - store_data = { - 'device_info': attr.asdict(self.device_info), - 'services': [] - } + store_data = {"device_info": attr.asdict(self.device_info), "services": []} for comp_type, infos in self.info.items(): - store_data[comp_type] = [attr.asdict(info) - for info in infos.values()] + store_data[comp_type] = [attr.asdict(info) for info in infos.values()] for service in self.services.values(): - store_data['services'].append(service.to_dict()) + store_data["services"].append(service.to_dict()) await self.store.async_save(store_data) def _attr_obj_from_dict(cls, **kwargs): - return cls(**{key: kwargs[key] for key in attr.fields_dict(cls) - if key in kwargs}) + return cls(**{key: kwargs[key] for key in attr.fields_dict(cls) if key in kwargs}) diff --git a/homeassistant/components/esphome/fan.py b/homeassistant/components/esphome/fan.py index 255bdaa8cb1..44059673f15 100644 --- a/homeassistant/components/esphome/fan.py +++ b/homeassistant/components/esphome/fan.py @@ -5,26 +5,39 @@ from typing import List, Optional from aioesphomeapi import FanInfo, FanSpeed, FanState from homeassistant.components.fan import ( - SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, SPEED_OFF, SUPPORT_OSCILLATE, - SUPPORT_SET_SPEED, FanEntity) + SPEED_HIGH, + SPEED_LOW, + SPEED_MEDIUM, + SPEED_OFF, + SUPPORT_OSCILLATE, + SUPPORT_SET_SPEED, + FanEntity, +) from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType from . import ( - EsphomeEntity, esphome_map_enum, esphome_state_property, - platform_async_setup_entry) + EsphomeEntity, + esphome_map_enum, + esphome_state_property, + platform_async_setup_entry, +) _LOGGER = logging.getLogger(__name__) -async def async_setup_entry(hass: HomeAssistantType, - entry: ConfigEntry, async_add_entities) -> None: +async def async_setup_entry( + hass: HomeAssistantType, entry: ConfigEntry, async_add_entities +) -> None: """Set up ESPHome fans based on a config entry.""" await platform_async_setup_entry( - hass, entry, async_add_entities, - component_key='fan', - info_type=FanInfo, entity_type=EsphomeFan, - state_type=FanState + hass, + entry, + async_add_entities, + component_key="fan", + info_type=FanInfo, + entity_type=EsphomeFan, + state_type=FanState, ) @@ -55,17 +68,17 @@ class EsphomeFan(EsphomeEntity, FanEntity): return await self._client.fan_command( - self._static_info.key, speed=_fan_speeds.from_hass(speed)) + self._static_info.key, speed=_fan_speeds.from_hass(speed) + ) - async def async_turn_on(self, speed: Optional[str] = None, - **kwargs) -> None: + async def async_turn_on(self, speed: Optional[str] = None, **kwargs) -> None: """Turn on the fan.""" if speed == SPEED_OFF: await self.async_turn_off() return - data = {'key': self._static_info.key, 'state': True} + data = {"key": self._static_info.key, "state": True} if speed is not None: - data['speed'] = _fan_speeds.from_hass(speed) + data["speed"] = _fan_speeds.from_hass(speed) await self._client.fan_command(**data) # pylint: disable=arguments-differ @@ -75,8 +88,9 @@ class EsphomeFan(EsphomeEntity, FanEntity): async def async_oscillate(self, oscillating: bool) -> None: """Oscillate the fan.""" - await self._client.fan_command(key=self._static_info.key, - oscillating=oscillating) + await self._client.fan_command( + key=self._static_info.key, oscillating=oscillating + ) @esphome_state_property def is_on(self) -> Optional[bool]: diff --git a/homeassistant/components/esphome/light.py b/homeassistant/components/esphome/light.py index f94229d61cc..e455d5581d1 100644 --- a/homeassistant/components/esphome/light.py +++ b/homeassistant/components/esphome/light.py @@ -5,10 +5,24 @@ from typing import List, Optional, Tuple from aioesphomeapi import LightInfo, LightState from homeassistant.components.light import ( - ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH, ATTR_HS_COLOR, - ATTR_TRANSITION, ATTR_WHITE_VALUE, FLASH_LONG, FLASH_SHORT, - SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, - SUPPORT_FLASH, SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE, Light) + ATTR_BRIGHTNESS, + ATTR_COLOR_TEMP, + ATTR_EFFECT, + ATTR_FLASH, + ATTR_HS_COLOR, + ATTR_TRANSITION, + ATTR_WHITE_VALUE, + FLASH_LONG, + FLASH_SHORT, + SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, + SUPPORT_COLOR_TEMP, + SUPPORT_EFFECT, + SUPPORT_FLASH, + SUPPORT_TRANSITION, + SUPPORT_WHITE_VALUE, + Light, +) from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType import homeassistant.util.color as color_util @@ -18,20 +32,21 @@ from . import EsphomeEntity, esphome_state_property, platform_async_setup_entry _LOGGER = logging.getLogger(__name__) -FLASH_LENGTHS = { - FLASH_SHORT: 2, - FLASH_LONG: 10, -} +FLASH_LENGTHS = {FLASH_SHORT: 2, FLASH_LONG: 10} -async def async_setup_entry(hass: HomeAssistantType, - entry: ConfigEntry, async_add_entities) -> None: +async def async_setup_entry( + hass: HomeAssistantType, entry: ConfigEntry, async_add_entities +) -> None: """Set up ESPHome lights based on a config entry.""" await platform_async_setup_entry( - hass, entry, async_add_entities, - component_key='light', - info_type=LightInfo, entity_type=EsphomeLight, - state_type=LightState + hass, + entry, + async_add_entities, + component_key="light", + info_type=LightInfo, + entity_type=EsphomeLight, + state_type=LightState, ) @@ -53,32 +68,32 @@ class EsphomeLight(EsphomeEntity, Light): async def async_turn_on(self, **kwargs) -> None: """Turn the entity on.""" - data = {'key': self._static_info.key, 'state': True} + data = {"key": self._static_info.key, "state": True} if ATTR_HS_COLOR in kwargs: hue, sat = kwargs[ATTR_HS_COLOR] red, green, blue = color_util.color_hsv_to_RGB(hue, sat, 100) - data['rgb'] = (red / 255, green / 255, blue / 255) + data["rgb"] = (red / 255, green / 255, blue / 255) if ATTR_FLASH in kwargs: - data['flash'] = FLASH_LENGTHS[kwargs[ATTR_FLASH]] + data["flash"] = FLASH_LENGTHS[kwargs[ATTR_FLASH]] if ATTR_TRANSITION in kwargs: - data['transition_length'] = kwargs[ATTR_TRANSITION] + data["transition_length"] = kwargs[ATTR_TRANSITION] if ATTR_BRIGHTNESS in kwargs: - data['brightness'] = kwargs[ATTR_BRIGHTNESS] / 255 + data["brightness"] = kwargs[ATTR_BRIGHTNESS] / 255 if ATTR_COLOR_TEMP in kwargs: - data['color_temperature'] = kwargs[ATTR_COLOR_TEMP] + data["color_temperature"] = kwargs[ATTR_COLOR_TEMP] if ATTR_EFFECT in kwargs: - data['effect'] = kwargs[ATTR_EFFECT] + data["effect"] = kwargs[ATTR_EFFECT] if ATTR_WHITE_VALUE in kwargs: - data['white'] = kwargs[ATTR_WHITE_VALUE] / 255 + data["white"] = kwargs[ATTR_WHITE_VALUE] / 255 await self._client.light_command(**data) async def async_turn_off(self, **kwargs) -> None: """Turn the entity off.""" - data = {'key': self._static_info.key, 'state': False} + data = {"key": self._static_info.key, "state": False} if ATTR_FLASH in kwargs: - data['flash'] = FLASH_LENGTHS[kwargs[ATTR_FLASH]] + data["flash"] = FLASH_LENGTHS[kwargs[ATTR_FLASH]] if ATTR_TRANSITION in kwargs: - data['transition_length'] = kwargs[ATTR_TRANSITION] + data["transition_length"] = kwargs[ATTR_TRANSITION] await self._client.light_command(**data) @esphome_state_property @@ -90,9 +105,8 @@ class EsphomeLight(EsphomeEntity, Light): def hs_color(self) -> Optional[Tuple[float, float]]: """Return the hue and saturation color value [float, float].""" return color_util.color_RGB_to_hs( - self._state.red * 255, - self._state.green * 255, - self._state.blue * 255) + self._state.red * 255, self._state.green * 255, self._state.blue * 255 + ) @esphome_state_property def color_temp(self) -> Optional[float]: diff --git a/homeassistant/components/esphome/sensor.py b/homeassistant/components/esphome/sensor.py index a5a530b49f1..3168bae7ec8 100644 --- a/homeassistant/components/esphome/sensor.py +++ b/homeassistant/components/esphome/sensor.py @@ -3,8 +3,7 @@ import logging import math from typing import Optional -from aioesphomeapi import ( - SensorInfo, SensorState, TextSensorInfo, TextSensorState) +from aioesphomeapi import SensorInfo, SensorState, TextSensorInfo, TextSensorState from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType @@ -14,20 +13,27 @@ from . import EsphomeEntity, esphome_state_property, platform_async_setup_entry _LOGGER = logging.getLogger(__name__) -async def async_setup_entry(hass: HomeAssistantType, - entry: ConfigEntry, async_add_entities) -> None: +async def async_setup_entry( + hass: HomeAssistantType, entry: ConfigEntry, async_add_entities +) -> None: """Set up esphome sensors based on a config entry.""" await platform_async_setup_entry( - hass, entry, async_add_entities, - component_key='sensor', - info_type=SensorInfo, entity_type=EsphomeSensor, - state_type=SensorState + hass, + entry, + async_add_entities, + component_key="sensor", + info_type=SensorInfo, + entity_type=EsphomeSensor, + state_type=SensorState, ) await platform_async_setup_entry( - hass, entry, async_add_entities, - component_key='text_sensor', - info_type=TextSensorInfo, entity_type=EsphomeTextSensor, - state_type=TextSensorState + hass, + entry, + async_add_entities, + component_key="text_sensor", + info_type=TextSensorInfo, + entity_type=EsphomeTextSensor, + state_type=TextSensorState, ) @@ -52,8 +58,9 @@ class EsphomeSensor(EsphomeEntity): """Return the state of the entity.""" if math.isnan(self._state.state): return None - return '{:.{prec}f}'.format( - self._state.state, prec=self._static_info.accuracy_decimals) + return "{:.{prec}f}".format( + self._state.state, prec=self._static_info.accuracy_decimals + ) @property def unit_of_measurement(self) -> str: @@ -65,11 +72,11 @@ class EsphomeTextSensor(EsphomeEntity): """A text sensor implementation for ESPHome.""" @property - def _static_info(self) -> 'TextSensorInfo': + def _static_info(self) -> "TextSensorInfo": return super()._static_info @property - def _state(self) -> Optional['TextSensorState']: + def _state(self) -> Optional["TextSensorState"]: return super()._state @property diff --git a/homeassistant/components/esphome/switch.py b/homeassistant/components/esphome/switch.py index d209df8cd83..f66bfaa39f3 100644 --- a/homeassistant/components/esphome/switch.py +++ b/homeassistant/components/esphome/switch.py @@ -13,14 +13,18 @@ from . import EsphomeEntity, esphome_state_property, platform_async_setup_entry _LOGGER = logging.getLogger(__name__) -async def async_setup_entry(hass: HomeAssistantType, - entry: ConfigEntry, async_add_entities) -> None: +async def async_setup_entry( + hass: HomeAssistantType, entry: ConfigEntry, async_add_entities +) -> None: """Set up ESPHome switches based on a config entry.""" await platform_async_setup_entry( - hass, entry, async_add_entities, - component_key='switch', - info_type=SwitchInfo, entity_type=EsphomeSwitch, - state_type=SwitchState + hass, + entry, + async_add_entities, + component_key="switch", + info_type=SwitchInfo, + entity_type=EsphomeSwitch, + state_type=SwitchState, ) diff --git a/homeassistant/components/essent/sensor.py b/homeassistant/components/essent/sensor.py index e77b256abb7..83d3164e3ff 100644 --- a/homeassistant/components/essent/sensor.py +++ b/homeassistant/components/essent/sensor.py @@ -5,18 +5,16 @@ from pyessent import PyEssent import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ( - CONF_PASSWORD, CONF_USERNAME, ENERGY_KILO_WATT_HOUR) +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, ENERGY_KILO_WATT_HOUR import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle SCAN_INTERVAL = timedelta(hours=1) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string} +) def setup_platform(hass, config, add_devices, discovery_info=None): @@ -28,26 +26,31 @@ def setup_platform(hass, config, add_devices, discovery_info=None): meters = [] for meter in essent.retrieve_meters(): data = essent.retrieve_meter_data(meter) - for tariff in data['values']['LVR'].keys(): - meters.append(EssentMeter( - essent, - meter, - data['type'], - tariff, - data['values']['LVR'][tariff]['unit'])) + for tariff in data["values"]["LVR"].keys(): + meters.append( + EssentMeter( + essent, + meter, + data["type"], + tariff, + data["values"]["LVR"][tariff]["unit"], + ) + ) if not meters: hass.components.persistent_notification.create( - 'Couldn\'t find any meter readings. ' - 'Please ensure Verbruiks Manager is enabled in Mijn Essent ' - 'and at least one reading has been logged to Meterstanden.', - title='Essent', notification_id='essent_notification') + "Couldn't find any meter readings. " + "Please ensure Verbruiks Manager is enabled in Mijn Essent " + "and at least one reading has been logged to Meterstanden.", + title="Essent", + notification_id="essent_notification", + ) return add_devices(meters, True) -class EssentBase(): +class EssentBase: """Essent Base.""" def __init__(self, username, password): @@ -72,8 +75,7 @@ class EssentBase(): essent = PyEssent(self._username, self._password) eans = essent.get_EANs() for possible_meter in eans: - meter_data = essent.read_meter( - possible_meter, only_last_meter_reading=True) + meter_data = essent.read_meter(possible_meter, only_last_meter_reading=True) if meter_data: self._meter_data[possible_meter] = meter_data @@ -103,7 +105,7 @@ class EssentMeter(Entity): @property def unit_of_measurement(self): """Return the unit of measurement.""" - if self._unit.lower() == 'kwh': + if self._unit.lower() == "kwh": return ENERGY_KILO_WATT_HOUR return self._unit @@ -118,4 +120,5 @@ class EssentMeter(Entity): # Set our value self._state = next( - iter(data['values']['LVR'][self._tariff]['records'].values())) + iter(data["values"]["LVR"][self._tariff]["records"].values()) + ) diff --git a/homeassistant/components/etherscan/sensor.py b/homeassistant/components/etherscan/sensor.py index 83805ec4d20..9cabb2762b0 100644 --- a/homeassistant/components/etherscan/sensor.py +++ b/homeassistant/components/etherscan/sensor.py @@ -4,23 +4,24 @@ from datetime import timedelta import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ( - ATTR_ATTRIBUTION, CONF_ADDRESS, CONF_NAME, CONF_TOKEN) +from homeassistant.const import ATTR_ATTRIBUTION, CONF_ADDRESS, CONF_NAME, CONF_TOKEN import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity ATTRIBUTION = "Data provided by etherscan.io" -CONF_TOKEN_ADDRESS = 'token_address' +CONF_TOKEN_ADDRESS = "token_address" SCAN_INTERVAL = timedelta(minutes=5) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_ADDRESS): cv.string, - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_TOKEN): cv.string, - vol.Optional(CONF_TOKEN_ADDRESS): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_ADDRESS): cv.string, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_TOKEN): cv.string, + vol.Optional(CONF_TOKEN_ADDRESS): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -75,6 +76,7 @@ class EtherscanSensor(Entity): def update(self): """Get the latest state of the sensor.""" from pyetherscan import get_balance + if self._token_address: self._state = get_balance(self._address, self._token_address) elif self._token: diff --git a/homeassistant/components/eufy/__init__.py b/homeassistant/components/eufy/__init__.py index 8425780b76b..df6aed3582f 100644 --- a/homeassistant/components/eufy/__init__.py +++ b/homeassistant/components/eufy/__init__.py @@ -4,39 +4,53 @@ import logging import voluptuous as vol from homeassistant.const import ( - CONF_ACCESS_TOKEN, CONF_ADDRESS, CONF_DEVICES, CONF_NAME, CONF_PASSWORD, - CONF_TYPE, CONF_USERNAME) + CONF_ACCESS_TOKEN, + CONF_ADDRESS, + CONF_DEVICES, + CONF_NAME, + CONF_PASSWORD, + CONF_TYPE, + CONF_USERNAME, +) from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DOMAIN = 'eufy' +DOMAIN = "eufy" -DEVICE_SCHEMA = vol.Schema({ - vol.Required(CONF_ADDRESS): cv.string, - vol.Required(CONF_ACCESS_TOKEN): cv.string, - vol.Required(CONF_TYPE): cv.string, - vol.Optional(CONF_NAME): cv.string -}) +DEVICE_SCHEMA = vol.Schema( + { + vol.Required(CONF_ADDRESS): cv.string, + vol.Required(CONF_ACCESS_TOKEN): cv.string, + vol.Required(CONF_TYPE): cv.string, + vol.Optional(CONF_NAME): cv.string, + } +) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Optional(CONF_DEVICES, default=[]): - vol.All(cv.ensure_list, [DEVICE_SCHEMA]), - vol.Inclusive(CONF_USERNAME, 'authentication'): cv.string, - vol.Inclusive(CONF_PASSWORD, 'authentication'): cv.string, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Optional(CONF_DEVICES, default=[]): vol.All( + cv.ensure_list, [DEVICE_SCHEMA] + ), + vol.Inclusive(CONF_USERNAME, "authentication"): cv.string, + vol.Inclusive(CONF_PASSWORD, "authentication"): cv.string, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) EUFY_DISPATCH = { - 'T1011': 'light', - 'T1012': 'light', - 'T1013': 'light', - 'T1201': 'switch', - 'T1202': 'switch', - 'T1203': 'switch', - 'T1211': 'switch' + "T1011": "light", + "T1012": "light", + "T1013": "light", + "T1201": "switch", + "T1202": "switch", + "T1203": "switch", + "T1211": "switch", } @@ -45,25 +59,24 @@ def setup(hass, config): import lakeside if CONF_USERNAME in config[DOMAIN] and CONF_PASSWORD in config[DOMAIN]: - data = lakeside.get_devices(config[DOMAIN][CONF_USERNAME], - config[DOMAIN][CONF_PASSWORD]) + data = lakeside.get_devices( + config[DOMAIN][CONF_USERNAME], config[DOMAIN][CONF_PASSWORD] + ) for device in data: - kind = device['type'] + kind = device["type"] if kind not in EUFY_DISPATCH: continue - discovery.load_platform(hass, EUFY_DISPATCH[kind], DOMAIN, device, - config) + discovery.load_platform(hass, EUFY_DISPATCH[kind], DOMAIN, device, config) for device_info in config[DOMAIN][CONF_DEVICES]: - kind = device_info['type'] + kind = device_info["type"] if kind not in EUFY_DISPATCH: continue device = {} - device['address'] = device_info['address'] - device['code'] = device_info['access_token'] - device['type'] = device_info['type'] - device['name'] = device_info['name'] - discovery.load_platform(hass, EUFY_DISPATCH[kind], DOMAIN, device, - config) + device["address"] = device_info["address"] + device["code"] = device_info["access_token"] + device["type"] = device_info["type"] + device["name"] = device_info["name"] + discovery.load_platform(hass, EUFY_DISPATCH[kind], DOMAIN, device, config) return True diff --git a/homeassistant/components/eufy/light.py b/homeassistant/components/eufy/light.py index 1d08e42fff7..f5359e6f2f6 100644 --- a/homeassistant/components/eufy/light.py +++ b/homeassistant/components/eufy/light.py @@ -2,14 +2,21 @@ import logging from homeassistant.components.light import ( - ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, SUPPORT_BRIGHTNESS, - SUPPORT_COLOR_TEMP, SUPPORT_COLOR, Light) + ATTR_BRIGHTNESS, + ATTR_COLOR_TEMP, + ATTR_HS_COLOR, + SUPPORT_BRIGHTNESS, + SUPPORT_COLOR_TEMP, + SUPPORT_COLOR, + Light, +) import homeassistant.util.color as color_util from homeassistant.util.color import ( color_temperature_mired_to_kelvin as mired_to_kelvin, - color_temperature_kelvin_to_mired as kelvin_to_mired) + color_temperature_kelvin_to_mired as kelvin_to_mired, +) _LOGGER = logging.getLogger(__name__) @@ -35,10 +42,10 @@ class EufyLight(Light): self._brightness = None self._hs = None self._state = None - self._name = device['name'] - self._address = device['address'] - self._code = device['code'] - self._type = device['type'] + self._name = device["name"] + self._address = device["address"] + self._code = device["code"] + self._type = device["type"] self._bulb = lakeside.bulb(self._address, self._code, self._type) self._colormode = False if self._type == "T1011": @@ -46,8 +53,7 @@ class EufyLight(Light): elif self._type == "T1012": self._features = SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP elif self._type == "T1013": - self._features = SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | \ - SUPPORT_COLOR + self._features = SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_COLOR self._bulb.connect() def update(self): @@ -96,9 +102,9 @@ class EufyLight(Light): @property def color_temp(self): """Return the color temperature of this light.""" - temp_in_k = int(EUFY_MIN_KELVIN + (self._temp * - (EUFY_MAX_KELVIN - EUFY_MIN_KELVIN) - / 100)) + temp_in_k = int( + EUFY_MIN_KELVIN + (self._temp * (EUFY_MAX_KELVIN - EUFY_MIN_KELVIN) / 100) + ) return kelvin_to_mired(temp_in_k) @property @@ -131,28 +137,29 @@ class EufyLight(Light): self._colormode = False temp_in_k = mired_to_kelvin(colortemp) relative_temp = temp_in_k - EUFY_MIN_KELVIN - temp = int(relative_temp * 100 / - (EUFY_MAX_KELVIN - EUFY_MIN_KELVIN)) + temp = int(relative_temp * 100 / (EUFY_MAX_KELVIN - EUFY_MIN_KELVIN)) else: temp = None if hs is not None: - rgb = color_util.color_hsv_to_RGB( - hs[0], hs[1], brightness / 255 * 100) + rgb = color_util.color_hsv_to_RGB(hs[0], hs[1], brightness / 255 * 100) self._colormode = True elif self._colormode: rgb = color_util.color_hsv_to_RGB( - self._hs[0], self._hs[1], brightness / 255 * 100) + self._hs[0], self._hs[1], brightness / 255 * 100 + ) else: rgb = None try: - self._bulb.set_state(power=True, brightness=brightness, - temperature=temp, colors=rgb) + self._bulb.set_state( + power=True, brightness=brightness, temperature=temp, colors=rgb + ) except BrokenPipeError: self._bulb.connect() - self._bulb.set_state(power=True, brightness=brightness, - temperature=temp, colors=rgb) + self._bulb.set_state( + power=True, brightness=brightness, temperature=temp, colors=rgb + ) def turn_off(self, **kwargs): """Turn the specified light off.""" diff --git a/homeassistant/components/eufy/switch.py b/homeassistant/components/eufy/switch.py index 3216bfed69e..3d05ef5d351 100644 --- a/homeassistant/components/eufy/switch.py +++ b/homeassistant/components/eufy/switch.py @@ -21,10 +21,10 @@ class EufySwitch(SwitchDevice): import lakeside self._state = None - self._name = device['name'] - self._address = device['address'] - self._code = device['code'] - self._type = device['type'] + self._name = device["name"] + self._address = device["address"] + self._code = device["code"] + self._type = device["type"] self._switch = lakeside.switch(self._address, self._code, self._type) self._switch.connect() diff --git a/homeassistant/components/everlights/light.py b/homeassistant/components/everlights/light.py index c5fb025370d..21629360ac7 100644 --- a/homeassistant/components/everlights/light.py +++ b/homeassistant/components/everlights/light.py @@ -7,9 +7,15 @@ import voluptuous as vol from homeassistant.const import CONF_HOSTS from homeassistant.components.light import ( - ATTR_BRIGHTNESS, ATTR_HS_COLOR, ATTR_EFFECT, - SUPPORT_BRIGHTNESS, SUPPORT_EFFECT, SUPPORT_COLOR, - Light, PLATFORM_SCHEMA) + ATTR_BRIGHTNESS, + ATTR_HS_COLOR, + ATTR_EFFECT, + SUPPORT_BRIGHTNESS, + SUPPORT_EFFECT, + SUPPORT_COLOR, + Light, + PLATFORM_SCHEMA, +) import homeassistant.helpers.config_validation as cv import homeassistant.util.color as color_util from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -17,36 +23,35 @@ from homeassistant.exceptions import PlatformNotReady _LOGGER = logging.getLogger(__name__) -SUPPORT_EVERLIGHTS = (SUPPORT_EFFECT | SUPPORT_BRIGHTNESS | SUPPORT_COLOR) +SUPPORT_EVERLIGHTS = SUPPORT_EFFECT | SUPPORT_BRIGHTNESS | SUPPORT_COLOR SCAN_INTERVAL = timedelta(minutes=1) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOSTS): vol.All(cv.ensure_list, [cv.string]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_HOSTS): vol.All(cv.ensure_list, [cv.string])} +) NAME_FORMAT = "EverLights {} Zone {}" def color_rgb_to_int(red: int, green: int, blue: int) -> int: """Return a RGB color as an integer.""" - return red*256*256+green*256+blue + return red * 256 * 256 + green * 256 + blue def color_int_to_rgb(value: int) -> Tuple[int, int, int]: """Return an RGB tuple from an integer.""" - return (value >> 16, (value >> 8) & 0xff, value & 0xff) + return (value >> 16, (value >> 8) & 0xFF, value & 0xFF) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the EverLights lights from configuration.yaml.""" import pyeverlights + lights = [] for ipaddr in config[CONF_HOSTS]: - api = pyeverlights.EverLights(ipaddr, - async_get_clientsession(hass)) + api = pyeverlights.EverLights(ipaddr, async_get_clientsession(hass)) try: status = await api.get_status() @@ -57,10 +62,8 @@ async def async_setup_platform(hass, config, async_add_entities, raise PlatformNotReady else: - lights.append(EverLightsLight(api, pyeverlights.ZONE_1, - status, effects)) - lights.append(EverLightsLight(api, pyeverlights.ZONE_2, - status, effects)) + lights.append(EverLightsLight(api, pyeverlights.ZONE_1, status, effects)) + lights.append(EverLightsLight(api, pyeverlights.ZONE_2, status, effects)) async_add_entities(lights) @@ -74,7 +77,7 @@ class EverLightsLight(Light): self._channel = channel self._status = status self._effects = effects - self._mac = status['mac'] + self._mac = status["mac"] self._error_reported = False self._hs_color = [255, 255] self._brightness = 255 @@ -84,7 +87,7 @@ class EverLightsLight(Light): @property def unique_id(self) -> str: """Return a unique ID.""" - return '{}-{}'.format(self._mac, self._channel) + return "{}-{}".format(self._mac, self._channel) @property def available(self) -> bool: @@ -99,7 +102,7 @@ class EverLightsLight(Light): @property def is_on(self): """Return true if device is on.""" - return self._status['ch{}Active'.format(self._channel)] == 1 + return self._status["ch{}Active".format(self._channel)] == 1 @property def brightness(self): @@ -141,7 +144,7 @@ class EverLightsLight(Light): brightness = hsv[2] / 100 * 255 else: - rgb = color_util.color_hsv_to_RGB(*hs_color, brightness/255*100) + rgb = color_util.color_hsv_to_RGB(*hs_color, brightness / 255 * 100) colors = [color_rgb_to_int(*rgb)] await self._api.set_pattern(self._channel, colors) diff --git a/homeassistant/components/evohome/__init__.py b/homeassistant/components/evohome/__init__.py index b81d32a20f4..5fcab599e88 100644 --- a/homeassistant/components/evohome/__init__.py +++ b/homeassistant/components/evohome/__init__.py @@ -12,16 +12,26 @@ import voluptuous as vol import evohomeclient2 from homeassistant.const import ( - CONF_ACCESS_TOKEN, CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME, - HTTP_SERVICE_UNAVAILABLE, HTTP_TOO_MANY_REQUESTS, TEMP_CELSIUS) + CONF_ACCESS_TOKEN, + CONF_PASSWORD, + CONF_SCAN_INTERVAL, + CONF_USERNAME, + HTTP_SERVICE_UNAVAILABLE, + HTTP_TOO_MANY_REQUESTS, + TEMP_CELSIUS, +) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.discovery import load_platform from homeassistant.helpers.dispatcher import ( - async_dispatcher_connect, async_dispatcher_send) + async_dispatcher_connect, + async_dispatcher_send, +) from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import ( - async_track_point_in_utc_time, track_time_interval) + async_track_point_in_utc_time, + track_time_interval, +) from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.util.dt import parse_datetime, utcnow @@ -29,22 +39,28 @@ from .const import DOMAIN, STORAGE_VERSION, STORAGE_KEY, GWS, TCS _LOGGER = logging.getLogger(__name__) -CONF_ACCESS_TOKEN_EXPIRES = 'access_token_expires' -CONF_REFRESH_TOKEN = 'refresh_token' +CONF_ACCESS_TOKEN_EXPIRES = "access_token_expires" +CONF_REFRESH_TOKEN = "refresh_token" -CONF_LOCATION_IDX = 'location_idx' +CONF_LOCATION_IDX = "location_idx" SCAN_INTERVAL_DEFAULT = timedelta(seconds=300) SCAN_INTERVAL_MINIMUM = timedelta(seconds=60) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_LOCATION_IDX, default=0): cv.positive_int, - vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL_DEFAULT): - vol.All(cv.time_period, vol.Range(min=SCAN_INTERVAL_MINIMUM)), - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_LOCATION_IDX, default=0): cv.positive_int, + vol.Optional( + CONF_SCAN_INTERVAL, default=SCAN_INTERVAL_DEFAULT + ): vol.All(cv.time_period, vol.Range(min=SCAN_INTERVAL_MINIMUM)), + } + ) + }, + extra=vol.ALLOW_EXTRA, +) def _local_dt_to_utc(dt_naive: datetime) -> datetime: @@ -70,7 +86,7 @@ def _handle_exception(err) -> bool: "Failed to (re)authenticate with the vendor's server. " "Check that your username and password are correct. " "Message is: %s", - err + err, ) return False @@ -80,7 +96,7 @@ def _handle_exception(err) -> bool: "Unable to connect with the vendor's server. " "Check your network and the vendor's status page." "Message is: %s", - err + err, ) return False @@ -95,7 +111,8 @@ def _handle_exception(err) -> bool: if err.response.status_code == HTTP_TOO_MANY_REQUESTS: _LOGGER.warning( "The vendor's API rate limit has been exceeded. " - "Consider increasing the %s.", CONF_SCAN_INTERVAL + "Consider increasing the %s.", + CONF_SCAN_INTERVAL, ) return False @@ -108,13 +125,11 @@ def setup(hass: HomeAssistantType, hass_config: ConfigType) -> bool: if not broker.init_client(): return False - load_platform(hass, 'climate', DOMAIN, {}, hass_config) + load_platform(hass, "climate", DOMAIN, {}, hass_config) if broker.tcs.hotwater: - load_platform(hass, 'water_heater', DOMAIN, {}, hass_config) + load_platform(hass, "water_heater", DOMAIN, {}, hass_config) - track_time_interval( - hass, broker.update, hass_config[DOMAIN][CONF_SCAN_INTERVAL] - ) + track_time_interval(hass, broker.update, hass_config[DOMAIN][CONF_SCAN_INTERVAL]) return True @@ -133,16 +148,16 @@ class EvoBroker: self._app_storage = {} hass.data[DOMAIN] = {} - hass.data[DOMAIN]['broker'] = self + hass.data[DOMAIN]["broker"] = self def init_client(self) -> bool: """Initialse the evohome data broker. Return True if this is successful, otherwise return False. """ - refresh_token, access_token, access_token_expires = \ - asyncio.run_coroutine_threadsafe( - self._load_auth_tokens(), self.hass.loop).result() + refresh_token, access_token, access_token_expires = asyncio.run_coroutine_threadsafe( + self._load_auth_tokens(), self.hass.loop + ).result() # evohomeclient2 uses naive/local datetimes if access_token_expires is not None: @@ -154,16 +169,18 @@ class EvoBroker: self.params[CONF_PASSWORD], refresh_token=refresh_token, access_token=access_token, - access_token_expires=access_token_expires + access_token_expires=access_token_expires, ) - except (requests.exceptions.RequestException, - evohomeclient2.AuthenticationError) as err: + except ( + requests.exceptions.RequestException, + evohomeclient2.AuthenticationError, + ) as err: if not _handle_exception(err): return False finally: - self.params[CONF_PASSWORD] = 'REDACTED' + self.params[CONF_PASSWORD] = "REDACTED" self.hass.add_job(self._save_auth_tokens()) @@ -176,23 +193,28 @@ class EvoBroker: "Config error: '%s' = %s, but its valid range is 0-%s. " "Unable to continue. " "Fix any configuration errors and restart HA.", - CONF_LOCATION_IDX, loc_idx, len(client.installation_info) - 1 + CONF_LOCATION_IDX, + loc_idx, + len(client.installation_info) - 1, ) return False - self.tcs = client.locations[loc_idx]._gateways[0]._control_systems[0] # noqa: E501; pylint: disable=protected-access + self.tcs = ( + client.locations[loc_idx]._gateways[0]._control_systems[0] + ) # noqa: E501; pylint: disable=protected-access _LOGGER.debug("Config = %s", self.config) if _LOGGER.isEnabledFor(logging.DEBUG): # don't do an I/O unless required _LOGGER.debug( - "Status = %s", - client.locations[loc_idx].status()[GWS][0][TCS][0]) + "Status = %s", client.locations[loc_idx].status()[GWS][0][TCS][0] + ) return True - async def _load_auth_tokens(self) -> Tuple[ - Optional[str], Optional[str], Optional[datetime]]: + async def _load_auth_tokens( + self + ) -> Tuple[Optional[str], Optional[str], Optional[datetime]]: store = self.hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) app_storage = self._app_storage = await store.async_load() @@ -214,14 +236,12 @@ class EvoBroker: async def _save_auth_tokens(self, *args) -> None: # evohomeclient2 uses naive/local datetimes - access_token_expires = _local_dt_to_utc( - self.client.access_token_expires) + access_token_expires = _local_dt_to_utc(self.client.access_token_expires) self._app_storage[CONF_USERNAME] = self.params[CONF_USERNAME] self._app_storage[CONF_REFRESH_TOKEN] = self.client.refresh_token self._app_storage[CONF_ACCESS_TOKEN] = self.client.access_token - self._app_storage[CONF_ACCESS_TOKEN_EXPIRES] = \ - access_token_expires.isoformat() + self._app_storage[CONF_ACCESS_TOKEN_EXPIRES] = access_token_expires.isoformat() store = self.hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) await store.async_save(self._app_storage) @@ -229,7 +249,7 @@ class EvoBroker: async_track_point_in_utc_time( self.hass, self._save_auth_tokens, - access_token_expires + self.params[CONF_SCAN_INTERVAL] + access_token_expires + self.params[CONF_SCAN_INTERVAL], ) def update(self, *args, **kwargs) -> None: @@ -243,16 +263,18 @@ class EvoBroker: try: status = self.client.locations[loc_idx].status()[GWS][0][TCS][0] - except (requests.exceptions.RequestException, - evohomeclient2.AuthenticationError) as err: + except ( + requests.exceptions.RequestException, + evohomeclient2.AuthenticationError, + ) as err: _handle_exception(err) else: - self.timers['statusUpdated'] = utcnow() + self.timers["statusUpdated"] = utcnow() _LOGGER.debug("Status = %s", status) # inform the evohome devices that state data has been updated - async_dispatcher_send(self.hass, DOMAIN, {'signal': 'refresh'}) + async_dispatcher_send(self.hass, DOMAIN, {"signal": "refresh"}) class EvoDevice(Entity): @@ -275,7 +297,7 @@ class EvoDevice(Entity): @callback def _refresh(self, packet): - if packet['signal'] == 'refresh': + if packet["signal"] == "refresh": self.async_schedule_update_ha_state(force_refresh=True) @property @@ -284,46 +306,47 @@ class EvoDevice(Entity): Only Zones & DHW controllers (but not the TCS) can have schedules. """ - if not self._schedule['DailySchedules']: + if not self._schedule["DailySchedules"]: return {} switchpoints = {} day_time = datetime.now() - day_of_week = int(day_time.strftime('%w')) # 0 is Sunday + day_of_week = int(day_time.strftime("%w")) # 0 is Sunday # Iterate today's switchpoints until past the current time of day... - day = self._schedule['DailySchedules'][day_of_week] + day = self._schedule["DailySchedules"][day_of_week] sp_idx = -1 # last switchpoint of the day before - for i, tmp in enumerate(day['Switchpoints']): - if day_time.strftime('%H:%M:%S') > tmp['TimeOfDay']: + for i, tmp in enumerate(day["Switchpoints"]): + if day_time.strftime("%H:%M:%S") > tmp["TimeOfDay"]: sp_idx = i # current setpoint else: break # Did the current SP start yesterday? Does the next start SP tomorrow? current_sp_day = -1 if sp_idx == -1 else 0 - next_sp_day = 1 if sp_idx + 1 == len(day['Switchpoints']) else 0 + next_sp_day = 1 if sp_idx + 1 == len(day["Switchpoints"]) else 0 for key, offset, idx in [ - ('current', current_sp_day, sp_idx), - ('next', next_sp_day, (sp_idx + 1) * (1 - next_sp_day))]: + ("current", current_sp_day, sp_idx), + ("next", next_sp_day, (sp_idx + 1) * (1 - next_sp_day)), + ]: spt = switchpoints[key] = {} - sp_date = (day_time + timedelta(days=offset)).strftime('%Y-%m-%d') - day = self._schedule['DailySchedules'][(day_of_week + offset) % 7] - switchpoint = day['Switchpoints'][idx] + sp_date = (day_time + timedelta(days=offset)).strftime("%Y-%m-%d") + day = self._schedule["DailySchedules"][(day_of_week + offset) % 7] + switchpoint = day["Switchpoints"][idx] dt_naive = datetime.strptime( - '{}T{}'.format(sp_date, switchpoint['TimeOfDay']), - '%Y-%m-%dT%H:%M:%S') + "{}T{}".format(sp_date, switchpoint["TimeOfDay"]), "%Y-%m-%dT%H:%M:%S" + ) - spt['from'] = _local_dt_to_utc(dt_naive).isoformat() + spt["from"] = _local_dt_to_utc(dt_naive).isoformat() try: - spt['temperature'] = switchpoint['heatSetpoint'] + spt["temperature"] = switchpoint["heatSetpoint"] except KeyError: - spt['state'] = switchpoint['DhwState'] + spt["state"] = switchpoint["DhwState"] return switchpoints @@ -342,13 +365,13 @@ class EvoDevice(Entity): """Return the Evohome-specific state attributes.""" status = {} for attr in self._state_attributes: - if attr != 'setpoints': + if attr != "setpoints": status[attr] = getattr(self._evo_device, attr) - if 'setpoints' in self._state_attributes: - status['setpoints'] = self.setpoints + if "setpoints" in self._state_attributes: + status["setpoints"] = self.setpoints - return {'status': status} + return {"status": status} @property def icon(self) -> str: @@ -376,8 +399,10 @@ class EvoDevice(Entity): def _update_schedule(self) -> None: """Get the latest state data.""" - if not self._schedule.get('DailySchedules') or \ - parse_datetime(self.setpoints['next']['from']) < utcnow(): + if ( + not self._schedule.get("DailySchedules") + or parse_datetime(self.setpoints["next"]["from"]) < utcnow() + ): self._schedule = self._evo_device.schedule() def update(self) -> None: diff --git a/homeassistant/components/evohome/climate.py b/homeassistant/components/evohome/climate.py index 26021389ba4..d9789d319da 100644 --- a/homeassistant/components/evohome/climate.py +++ b/homeassistant/components/evohome/climate.py @@ -8,28 +8,44 @@ import evohomeclient2 from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( - CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, CURRENT_HVAC_OFF, - HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF, - PRESET_AWAY, PRESET_ECO, PRESET_HOME, PRESET_NONE, - SUPPORT_TARGET_TEMPERATURE, SUPPORT_PRESET_MODE) + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, + CURRENT_HVAC_OFF, + HVAC_MODE_AUTO, + HVAC_MODE_HEAT, + HVAC_MODE_OFF, + PRESET_AWAY, + PRESET_ECO, + PRESET_HOME, + PRESET_NONE, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_PRESET_MODE, +) from homeassistant.const import PRECISION_TENTHS from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.util.dt import parse_datetime from . import CONF_LOCATION_IDX, _handle_exception, EvoDevice from .const import ( - DOMAIN, EVO_RESET, EVO_AUTO, EVO_AUTOECO, EVO_AWAY, EVO_CUSTOM, EVO_DAYOFF, - EVO_HEATOFF, EVO_FOLLOW, EVO_TEMPOVER, EVO_PERMOVER) + DOMAIN, + EVO_RESET, + EVO_AUTO, + EVO_AUTOECO, + EVO_AWAY, + EVO_CUSTOM, + EVO_DAYOFF, + EVO_HEATOFF, + EVO_FOLLOW, + EVO_TEMPOVER, + EVO_PERMOVER, +) _LOGGER = logging.getLogger(__name__) -PRESET_RESET = 'Reset' # reset all child zones to EVO_FOLLOW -PRESET_CUSTOM = 'Custom' +PRESET_RESET = "Reset" # reset all child zones to EVO_FOLLOW +PRESET_CUSTOM = "Custom" -HA_HVAC_TO_TCS = { - HVAC_MODE_OFF: EVO_HEATOFF, - HVAC_MODE_HEAT: EVO_AUTO, -} +HA_HVAC_TO_TCS = {HVAC_MODE_OFF: EVO_HEATOFF, HVAC_MODE_HEAT: EVO_AUTO} TCS_PRESET_TO_HA = { EVO_AWAY: PRESET_AWAY, @@ -43,29 +59,37 @@ HA_PRESET_TO_TCS = {v: k for k, v in TCS_PRESET_TO_HA.items()} EVO_PRESET_TO_HA = { EVO_FOLLOW: PRESET_NONE, - EVO_TEMPOVER: 'temporary', - EVO_PERMOVER: 'permanent', + EVO_TEMPOVER: "temporary", + EVO_PERMOVER: "permanent", } HA_PRESET_TO_EVO = {v: k for k, v in EVO_PRESET_TO_HA.items()} -def setup_platform(hass: HomeAssistantType, hass_config: ConfigType, - add_entities, discovery_info=None) -> None: +def setup_platform( + hass: HomeAssistantType, hass_config: ConfigType, add_entities, discovery_info=None +) -> None: """Create the evohome Controller, and its Zones, if any.""" - broker = hass.data[DOMAIN]['broker'] + broker = hass.data[DOMAIN]["broker"] loc_idx = broker.params[CONF_LOCATION_IDX] _LOGGER.debug( "Found Location/Controller, id=%s [%s], name=%s (location_idx=%s)", - broker.tcs.systemId, broker.tcs.modelType, broker.tcs.location.name, - loc_idx) + broker.tcs.systemId, + broker.tcs.modelType, + broker.tcs.location.name, + loc_idx, + ) # special case of RoundThermostat (is single zone) - if broker.config['zones'][0]['modelType'] == 'RoundModulation': + if broker.config["zones"][0]["modelType"] == "RoundModulation": zone = list(broker.tcs.zones.values())[0] _LOGGER.debug( "Found %s, id=%s [%s], name=%s", - zone.zoneType, zone.zoneId, zone.modelType, zone.name) + zone.zoneType, + zone.zoneId, + zone.modelType, + zone.name, + ) add_entities([EvoThermostat(broker, zone)], update_before_add=True) return @@ -76,7 +100,11 @@ def setup_platform(hass: HomeAssistantType, hass_config: ConfigType, for zone in broker.tcs.zones.values(): _LOGGER.debug( "Found %s, id=%s [%s], name=%s", - zone.zoneType, zone.zoneId, zone.modelType, zone.name) + zone.zoneType, + zone.zoneId, + zone.modelType, + zone.name, + ) zones.append(EvoZone(broker, zone)) add_entities([controller] + zones, update_before_add=True) @@ -91,16 +119,19 @@ class EvoClimateDevice(EvoDevice, ClimateDevice): self._preset_modes = None - def _set_temperature(self, temperature: float, - until: Optional[datetime] = None) -> None: + def _set_temperature( + self, temperature: float, until: Optional[datetime] = None + ) -> None: """Set a new target temperature for the Zone. until == None means indefinitely (i.e. PermanentOverride) """ try: self._evo_device.set_temperature(temperature, until) - except (requests.exceptions.RequestException, - evohomeclient2.AuthenticationError) as err: + except ( + requests.exceptions.RequestException, + evohomeclient2.AuthenticationError, + ) as err: _handle_exception(err) def _set_zone_mode(self, op_mode: str) -> None: @@ -124,27 +155,33 @@ class EvoClimateDevice(EvoDevice, ClimateDevice): if op_mode == EVO_FOLLOW: try: self._evo_device.cancel_temp_override() - except (requests.exceptions.RequestException, - evohomeclient2.AuthenticationError) as err: + except ( + requests.exceptions.RequestException, + evohomeclient2.AuthenticationError, + ) as err: _handle_exception(err) return - temperature = self._evo_device.setpointStatus['targetHeatTemperature'] + temperature = self._evo_device.setpointStatus["targetHeatTemperature"] until = None # EVO_PERMOVER - if op_mode == EVO_TEMPOVER and self._schedule['DailySchedules']: + if op_mode == EVO_TEMPOVER and self._schedule["DailySchedules"]: self._update_schedule() - if self._schedule['DailySchedules']: - until = parse_datetime(self.setpoints['next']['from']) + if self._schedule["DailySchedules"]: + until = parse_datetime(self.setpoints["next"]["from"]) self._set_temperature(temperature, until=until) def _set_tcs_mode(self, op_mode: str) -> None: """Set the Controller to any of its native EVO_* operating modes.""" try: - self._evo_tcs._set_status(op_mode) # noqa: E501; pylint: disable=protected-access - except (requests.exceptions.RequestException, - evohomeclient2.AuthenticationError) as err: + self._evo_tcs._set_status( + op_mode + ) # noqa: E501; pylint: disable=protected-access + except ( + requests.exceptions.RequestException, + evohomeclient2.AuthenticationError, + ) as err: _handle_exception(err) @property @@ -166,22 +203,24 @@ class EvoZone(EvoClimateDevice): super().__init__(evo_broker, evo_device) self._name = evo_device.name - self._icon = 'mdi:radiator' + self._icon = "mdi:radiator" - self._precision = \ - self._evo_device.setpointCapabilities['valueResolution'] + self._precision = self._evo_device.setpointCapabilities["valueResolution"] self._state_attributes = [ - 'zoneId', 'activeFaults', 'setpointStatus', 'temperatureStatus', - 'setpoints'] + "zoneId", + "activeFaults", + "setpointStatus", + "temperatureStatus", + "setpoints", + ] - self._supported_features = SUPPORT_PRESET_MODE | \ - SUPPORT_TARGET_TEMPERATURE + self._supported_features = SUPPORT_PRESET_MODE | SUPPORT_TARGET_TEMPERATURE self._preset_modes = list(HA_PRESET_TO_EVO) @property def hvac_mode(self) -> str: """Return the current operating mode of the evohome Zone.""" - if self._evo_tcs.systemModeStatus['mode'] in [EVO_AWAY, EVO_HEATOFF]: + if self._evo_tcs.systemModeStatus["mode"] in [EVO_AWAY, EVO_HEATOFF]: return HVAC_MODE_AUTO is_off = self.target_temperature <= self.min_temp return HVAC_MODE_OFF if is_off else HVAC_MODE_HEAT @@ -189,7 +228,7 @@ class EvoZone(EvoClimateDevice): @property def hvac_action(self) -> Optional[str]: """Return the current running hvac operation if supported.""" - if self._evo_tcs.systemModeStatus['mode'] == EVO_HEATOFF: + if self._evo_tcs.systemModeStatus["mode"] == EVO_HEATOFF: return CURRENT_HVAC_OFF if self.target_temperature <= self.min_temp: return CURRENT_HVAC_OFF @@ -200,23 +239,27 @@ class EvoZone(EvoClimateDevice): @property def current_temperature(self) -> Optional[float]: """Return the current temperature of the evohome Zone.""" - return (self._evo_device.temperatureStatus['temperature'] - if self._evo_device.temperatureStatus['isAvailable'] else None) + return ( + self._evo_device.temperatureStatus["temperature"] + if self._evo_device.temperatureStatus["isAvailable"] + else None + ) @property def target_temperature(self) -> float: """Return the target temperature of the evohome Zone.""" - if self._evo_tcs.systemModeStatus['mode'] == EVO_HEATOFF: - return self._evo_device.setpointCapabilities['minHeatSetpoint'] - return self._evo_device.setpointStatus['targetHeatTemperature'] + if self._evo_tcs.systemModeStatus["mode"] == EVO_HEATOFF: + return self._evo_device.setpointCapabilities["minHeatSetpoint"] + return self._evo_device.setpointStatus["targetHeatTemperature"] @property def preset_mode(self) -> Optional[str]: """Return the current preset mode, e.g., home, away, temp.""" - if self._evo_tcs.systemModeStatus['mode'] in [EVO_AWAY, EVO_HEATOFF]: - return TCS_PRESET_TO_HA.get(self._evo_tcs.systemModeStatus['mode']) + if self._evo_tcs.systemModeStatus["mode"] in [EVO_AWAY, EVO_HEATOFF]: + return TCS_PRESET_TO_HA.get(self._evo_tcs.systemModeStatus["mode"]) return EVO_PRESET_TO_HA.get( - self._evo_device.setpointStatus['setpointMode'], 'follow') + self._evo_device.setpointStatus["setpointMode"], "follow" + ) @property def min_temp(self) -> float: @@ -224,7 +267,7 @@ class EvoZone(EvoClimateDevice): The default is 5, but is user-configurable within 5-35 (in Celsius). """ - return self._evo_device.setpointCapabilities['minHeatSetpoint'] + return self._evo_device.setpointCapabilities["minHeatSetpoint"] @property def max_temp(self) -> float: @@ -232,15 +275,15 @@ class EvoZone(EvoClimateDevice): The default is 35, but is user-configurable within 5-35 (in Celsius). """ - return self._evo_device.setpointCapabilities['maxHeatSetpoint'] + return self._evo_device.setpointCapabilities["maxHeatSetpoint"] def set_temperature(self, **kwargs) -> None: """Set a new target temperature.""" - until = kwargs.get('until') + until = kwargs.get("until") if until: until = parse_datetime(until) - self._set_temperature(kwargs['temperature'], until) + self._set_temperature(kwargs["temperature"], until) def set_hvac_mode(self, hvac_mode: str) -> None: """Set an operating mode for the Zone.""" @@ -270,11 +313,10 @@ class EvoController(EvoClimateDevice): super().__init__(evo_broker, evo_device) self._name = evo_device.location.name - self._icon = 'mdi:thermostat' + self._icon = "mdi:thermostat" self._precision = PRECISION_TENTHS - self._state_attributes = [ - 'systemId', 'activeFaults', 'systemModeStatus'] + self._state_attributes = ["systemId", "activeFaults", "systemModeStatus"] self._supported_features = SUPPORT_PRESET_MODE self._preset_modes = list(HA_PRESET_TO_TCS) @@ -282,7 +324,7 @@ class EvoController(EvoClimateDevice): @property def hvac_mode(self) -> str: """Return the current operating mode of the evohome Controller.""" - tcs_mode = self._evo_tcs.systemModeStatus['mode'] + tcs_mode = self._evo_tcs.systemModeStatus["mode"] return HVAC_MODE_OFF if tcs_mode == EVO_HEATOFF else HVAC_MODE_HEAT @property @@ -291,15 +333,17 @@ class EvoController(EvoClimateDevice): Controllers do not have a current temp, but one is expected by HA. """ - temps = [z.temperatureStatus['temperature'] - for z in self._evo_tcs.zones.values() - if z.temperatureStatus['isAvailable']] + temps = [ + z.temperatureStatus["temperature"] + for z in self._evo_tcs.zones.values() + if z.temperatureStatus["isAvailable"] + ] return round(sum(temps) / len(temps), 1) if temps else None @property def preset_mode(self) -> Optional[str]: """Return the current preset mode, e.g., home, away, temp.""" - return TCS_PRESET_TO_HA.get(self._evo_tcs.systemModeStatus['mode']) + return TCS_PRESET_TO_HA.get(self._evo_tcs.systemModeStatus["mode"]) def set_temperature(self, **kwargs) -> None: """Do nothing. @@ -340,17 +384,17 @@ class EvoThermostat(EvoZone): @property def device_state_attributes(self) -> Dict[str, Any]: """Return the device-specific state attributes.""" - status = super().device_state_attributes['status'] + status = super().device_state_attributes["status"] - status['systemModeStatus'] = self._evo_tcs.systemModeStatus - status['activeFaults'] += self._evo_tcs.activeFaults + status["systemModeStatus"] = self._evo_tcs.systemModeStatus + status["activeFaults"] += self._evo_tcs.activeFaults - return {'status': status} + return {"status": status} @property def hvac_mode(self) -> str: """Return the current operating mode.""" - if self._evo_tcs.systemModeStatus['mode'] == EVO_HEATOFF: + if self._evo_tcs.systemModeStatus["mode"] == EVO_HEATOFF: return HVAC_MODE_OFF return super().hvac_mode @@ -358,8 +402,10 @@ class EvoThermostat(EvoZone): @property def preset_mode(self) -> Optional[str]: """Return the current preset mode, e.g., home, away, temp.""" - if self._evo_tcs.systemModeStatus['mode'] == EVO_AUTOECO and \ - self._evo_device.setpointStatus['setpointMode'] == EVO_FOLLOW: + if ( + self._evo_tcs.systemModeStatus["mode"] == EVO_AUTOECO + and self._evo_device.setpointStatus["setpointMode"] == EVO_FOLLOW + ): return PRESET_ECO return super().preset_mode diff --git a/homeassistant/components/evohome/const.py b/homeassistant/components/evohome/const.py index d1a22a844f6..a0480d62a10 100644 --- a/homeassistant/components/evohome/const.py +++ b/homeassistant/components/evohome/const.py @@ -1,25 +1,25 @@ """Support for (EMEA/EU-based) Honeywell TCC climate systems.""" -DOMAIN = 'evohome' +DOMAIN = "evohome" STORAGE_VERSION = 1 STORAGE_KEY = DOMAIN # The Parent's (i.e. TCS, Controller's) operating mode is one of: -EVO_RESET = 'AutoWithReset' -EVO_AUTO = 'Auto' -EVO_AUTOECO = 'AutoWithEco' -EVO_AWAY = 'Away' -EVO_DAYOFF = 'DayOff' -EVO_CUSTOM = 'Custom' -EVO_HEATOFF = 'HeatingOff' +EVO_RESET = "AutoWithReset" +EVO_AUTO = "Auto" +EVO_AUTOECO = "AutoWithEco" +EVO_AWAY = "Away" +EVO_DAYOFF = "DayOff" +EVO_CUSTOM = "Custom" +EVO_HEATOFF = "HeatingOff" # The Childs' operating mode is one of: -EVO_FOLLOW = 'FollowSchedule' # the operating mode is 'inherited' from the TCS -EVO_TEMPOVER = 'TemporaryOverride' -EVO_PERMOVER = 'PermanentOverride' +EVO_FOLLOW = "FollowSchedule" # the operating mode is 'inherited' from the TCS +EVO_TEMPOVER = "TemporaryOverride" +EVO_PERMOVER = "PermanentOverride" # These are used only to help prevent E501 (line too long) violations -GWS = 'gateways' -TCS = 'temperatureControlSystems' +GWS = "gateways" +TCS = "temperatureControlSystems" -EVO_STRFTIME = '%Y-%m-%dT%H:%M:%SZ' +EVO_STRFTIME = "%Y-%m-%dT%H:%M:%SZ" diff --git a/homeassistant/components/evohome/water_heater.py b/homeassistant/components/evohome/water_heater.py index 6f5952bdfb0..6309f07a000 100644 --- a/homeassistant/components/evohome/water_heater.py +++ b/homeassistant/components/evohome/water_heater.py @@ -6,7 +6,9 @@ import requests.exceptions import evohomeclient2 from homeassistant.components.water_heater import ( - SUPPORT_OPERATION_MODE, WaterHeaterDevice) + SUPPORT_OPERATION_MODE, + WaterHeaterDevice, +) from homeassistant.const import PRECISION_WHOLE, STATE_OFF, STATE_ON from homeassistant.util.dt import parse_datetime @@ -15,20 +17,19 @@ from .const import DOMAIN, EVO_STRFTIME, EVO_FOLLOW, EVO_TEMPOVER, EVO_PERMOVER _LOGGER = logging.getLogger(__name__) -HA_STATE_TO_EVO = {STATE_ON: 'On', STATE_OFF: 'Off'} +HA_STATE_TO_EVO = {STATE_ON: "On", STATE_OFF: "Off"} EVO_STATE_TO_HA = {v: k for k, v in HA_STATE_TO_EVO.items()} HA_OPMODE_TO_DHW = {STATE_ON: EVO_FOLLOW, STATE_OFF: EVO_PERMOVER} -def setup_platform(hass, hass_config, add_entities, - discovery_info=None) -> None: +def setup_platform(hass, hass_config, add_entities, discovery_info=None) -> None: """Create the DHW controller.""" - broker = hass.data[DOMAIN]['broker'] + broker = hass.data[DOMAIN]["broker"] _LOGGER.debug( - "Found %s, id: %s", - broker.tcs.hotwater.zone_type, broker.tcs.hotwater.zoneId) + "Found %s, id: %s", broker.tcs.hotwater.zone_type, broker.tcs.hotwater.zoneId + ) evo_dhw = EvoDHW(broker, broker.tcs.hotwater) @@ -42,13 +43,17 @@ class EvoDHW(EvoDevice, WaterHeaterDevice): """Initialize the evohome DHW controller.""" super().__init__(evo_broker, evo_device) - self._name = 'DHW controller' - self._icon = 'mdi:thermometer-lines' + self._name = "DHW controller" + self._icon = "mdi:thermometer-lines" self._precision = PRECISION_WHOLE self._state_attributes = [ - 'dhwId', 'activeFaults', 'stateStatus', 'temperatureStatus', - 'setpoints'] + "dhwId", + "activeFaults", + "stateStatus", + "temperatureStatus", + "setpoints", + ] self._supported_features = SUPPORT_OPERATION_MODE self._operation_list = list(HA_OPMODE_TO_DHW) @@ -56,7 +61,7 @@ class EvoDHW(EvoDevice, WaterHeaterDevice): @property def current_operation(self) -> str: """Return the current operating mode (On, or Off).""" - return EVO_STATE_TO_HA[self._evo_device.stateStatus['state']] + return EVO_STATE_TO_HA[self._evo_device.stateStatus["state"]] @property def operation_list(self) -> List[str]: @@ -66,25 +71,27 @@ class EvoDHW(EvoDevice, WaterHeaterDevice): @property def current_temperature(self) -> float: """Return the current temperature.""" - return self._evo_device.temperatureStatus['temperature'] + return self._evo_device.temperatureStatus["temperature"] def set_operation_mode(self, operation_mode: str) -> None: """Set new operation mode for a DHW controller.""" op_mode = HA_OPMODE_TO_DHW[operation_mode] - state = '' if op_mode == EVO_FOLLOW else HA_STATE_TO_EVO[STATE_OFF] + state = "" if op_mode == EVO_FOLLOW else HA_STATE_TO_EVO[STATE_OFF] until = None # EVO_FOLLOW, EVO_PERMOVER - if op_mode == EVO_TEMPOVER and self._schedule['DailySchedules']: + if op_mode == EVO_TEMPOVER and self._schedule["DailySchedules"]: self._update_schedule() - if self._schedule['DailySchedules']: - until = parse_datetime(self.setpoints['next']['from']) + if self._schedule["DailySchedules"]: + until = parse_datetime(self.setpoints["next"]["from"]) until = until.strftime(EVO_STRFTIME) - data = {'Mode': op_mode, 'State': state, 'UntilTime': until} + data = {"Mode": op_mode, "State": state, "UntilTime": until} try: self._evo_device._set_dhw(data) # pylint: disable=protected-access - except (requests.exceptions.RequestException, - evohomeclient2.AuthenticationError) as err: + except ( + requests.exceptions.RequestException, + evohomeclient2.AuthenticationError, + ) as err: _handle_exception(err) diff --git a/homeassistant/components/facebook/notify.py b/homeassistant/components/facebook/notify.py index 625b922927c..452b81c0f16 100644 --- a/homeassistant/components/facebook/notify.py +++ b/homeassistant/components/facebook/notify.py @@ -9,20 +9,23 @@ import voluptuous as vol from homeassistant.const import CONTENT_TYPE_JSON import homeassistant.helpers.config_validation as cv -from homeassistant.components.notify import (ATTR_DATA, ATTR_TARGET, - PLATFORM_SCHEMA, - BaseNotificationService) +from homeassistant.components.notify import ( + ATTR_DATA, + ATTR_TARGET, + PLATFORM_SCHEMA, + BaseNotificationService, +) _LOGGER = logging.getLogger(__name__) -CONF_PAGE_ACCESS_TOKEN = 'page_access_token' -BASE_URL = 'https://graph.facebook.com/v2.6/me/messages' -CREATE_BROADCAST_URL = 'https://graph.facebook.com/v2.11/me/message_creatives' -SEND_BROADCAST_URL = 'https://graph.facebook.com/v2.11/me/broadcast_messages' +CONF_PAGE_ACCESS_TOKEN = "page_access_token" +BASE_URL = "https://graph.facebook.com/v2.6/me/messages" +CREATE_BROADCAST_URL = "https://graph.facebook.com/v2.11/me/message_creatives" +SEND_BROADCAST_URL = "https://graph.facebook.com/v2.11/me/broadcast_messages" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_PAGE_ACCESS_TOKEN): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_PAGE_ACCESS_TOKEN): cv.string} +) def get_service(hass, config, discovery_info=None): @@ -39,7 +42,7 @@ class FacebookNotificationService(BaseNotificationService): def send_message(self, message="", **kwargs): """Send some message.""" - payload = {'access_token': self.page_access_token} + payload = {"access_token": self.page_access_token} targets = kwargs.get(ATTR_TARGET) data = kwargs.get(ATTR_DATA) @@ -48,36 +51,40 @@ class FacebookNotificationService(BaseNotificationService): if data is not None: body_message.update(data) # Only one of text or attachment can be specified - if 'attachment' in body_message: - body_message.pop('text') + if "attachment" in body_message: + body_message.pop("text") if not targets: _LOGGER.error("At least 1 target is required") return # broadcast message - if targets[0].lower() == 'broadcast': + if targets[0].lower() == "broadcast": broadcast_create_body = {"messages": [body_message]} _LOGGER.debug("Broadcast body %s : ", broadcast_create_body) - resp = requests.post(CREATE_BROADCAST_URL, - data=json.dumps(broadcast_create_body), - params=payload, - headers={CONTENT_TYPE: CONTENT_TYPE_JSON}, - timeout=10) + resp = requests.post( + CREATE_BROADCAST_URL, + data=json.dumps(broadcast_create_body), + params=payload, + headers={CONTENT_TYPE: CONTENT_TYPE_JSON}, + timeout=10, + ) _LOGGER.debug("FB Messager broadcast id %s : ", resp.json()) # at this point we get broadcast id broadcast_body = { - "message_creative_id": resp.json().get('message_creative_id'), + "message_creative_id": resp.json().get("message_creative_id"), "notification_type": "REGULAR", } - resp = requests.post(SEND_BROADCAST_URL, - data=json.dumps(broadcast_body), - params=payload, - headers={CONTENT_TYPE: CONTENT_TYPE_JSON}, - timeout=10) + resp = requests.post( + SEND_BROADCAST_URL, + data=json.dumps(broadcast_body), + params=payload, + headers={CONTENT_TYPE: CONTENT_TYPE_JSON}, + timeout=10, + ) if resp.status_code != 200: log_error(resp) @@ -86,19 +93,19 @@ class FacebookNotificationService(BaseNotificationService): for target in targets: # If the target starts with a "+", it's a phone number, # otherwise it's a user id. - if target.startswith('+'): + if target.startswith("+"): recipient = {"phone_number": target} else: recipient = {"id": target} - body = { - "recipient": recipient, - "message": body_message - } - resp = requests.post(BASE_URL, data=json.dumps(body), - params=payload, - headers={CONTENT_TYPE: CONTENT_TYPE_JSON}, - timeout=10) + body = {"recipient": recipient, "message": body_message} + resp = requests.post( + BASE_URL, + data=json.dumps(body), + params=payload, + headers={CONTENT_TYPE: CONTENT_TYPE_JSON}, + timeout=10, + ) if resp.status_code != 200: log_error(resp) @@ -106,9 +113,9 @@ class FacebookNotificationService(BaseNotificationService): def log_error(response): """Log error message.""" obj = response.json() - error_message = obj['error']['message'] - error_code = obj['error']['code'] + error_message = obj["error"]["message"] + error_code = obj["error"]["code"] _LOGGER.error( - "Error %s : %s (Code %s)", response.status_code, error_message, - error_code) + "Error %s : %s (Code %s)", response.status_code, error_message, error_code + ) diff --git a/homeassistant/components/facebox/image_processing.py b/homeassistant/components/facebox/image_processing.py index 2b4f184c3fd..a1e686bcbd0 100644 --- a/homeassistant/components/facebox/image_processing.py +++ b/homeassistant/components/facebox/image_processing.py @@ -5,60 +5,72 @@ import logging import requests import voluptuous as vol -from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_NAME) +from homeassistant.const import ATTR_ENTITY_ID, ATTR_NAME from homeassistant.core import split_entity_id import homeassistant.helpers.config_validation as cv from homeassistant.components.image_processing import ( - PLATFORM_SCHEMA, ImageProcessingFaceEntity, ATTR_CONFIDENCE, CONF_SOURCE, - CONF_ENTITY_ID, CONF_NAME, DOMAIN) + PLATFORM_SCHEMA, + ImageProcessingFaceEntity, + ATTR_CONFIDENCE, + CONF_SOURCE, + CONF_ENTITY_ID, + CONF_NAME, + DOMAIN, +) from homeassistant.const import ( - CONF_IP_ADDRESS, CONF_PORT, CONF_PASSWORD, CONF_USERNAME, - HTTP_BAD_REQUEST, HTTP_OK, HTTP_UNAUTHORIZED) + CONF_IP_ADDRESS, + CONF_PORT, + CONF_PASSWORD, + CONF_USERNAME, + HTTP_BAD_REQUEST, + HTTP_OK, + HTTP_UNAUTHORIZED, +) _LOGGER = logging.getLogger(__name__) -ATTR_BOUNDING_BOX = 'bounding_box' -ATTR_CLASSIFIER = 'classifier' -ATTR_IMAGE_ID = 'image_id' -ATTR_ID = 'id' -ATTR_MATCHED = 'matched' -FACEBOX_NAME = 'name' -CLASSIFIER = 'facebox' -DATA_FACEBOX = 'facebox_classifiers' -FILE_PATH = 'file_path' -SERVICE_TEACH_FACE = 'facebox_teach_face' +ATTR_BOUNDING_BOX = "bounding_box" +ATTR_CLASSIFIER = "classifier" +ATTR_IMAGE_ID = "image_id" +ATTR_ID = "id" +ATTR_MATCHED = "matched" +FACEBOX_NAME = "name" +CLASSIFIER = "facebox" +DATA_FACEBOX = "facebox_classifiers" +FILE_PATH = "file_path" +SERVICE_TEACH_FACE = "facebox_teach_face" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_IP_ADDRESS): cv.string, - vol.Required(CONF_PORT): cv.port, - vol.Optional(CONF_USERNAME): cv.string, - vol.Optional(CONF_PASSWORD): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_IP_ADDRESS): cv.string, + vol.Required(CONF_PORT): cv.port, + vol.Optional(CONF_USERNAME): cv.string, + vol.Optional(CONF_PASSWORD): cv.string, + } +) -SERVICE_TEACH_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, - vol.Required(ATTR_NAME): cv.string, - vol.Required(FILE_PATH): cv.string, -}) +SERVICE_TEACH_SCHEMA = vol.Schema( + { + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Required(ATTR_NAME): cv.string, + vol.Required(FILE_PATH): cv.string, + } +) def check_box_health(url, username, password): """Check the health of the classifier and return its id if healthy.""" kwargs = {} if username: - kwargs['auth'] = requests.auth.HTTPBasicAuth(username, password) + kwargs["auth"] = requests.auth.HTTPBasicAuth(username, password) try: - response = requests.get( - url, - **kwargs - ) + response = requests.get(url, **kwargs) if response.status_code == HTTP_UNAUTHORIZED: _LOGGER.error("AuthenticationError on %s", CLASSIFIER) return None if response.status_code == HTTP_OK: - return response.json()['hostname'] + return response.json()["hostname"] except requests.exceptions.ConnectionError: _LOGGER.error("ConnectionError: Is %s running?", CLASSIFIER) return None @@ -66,14 +78,15 @@ def check_box_health(url, username, password): def encode_image(image): """base64 encode an image stream.""" - base64_img = base64.b64encode(image).decode('ascii') + base64_img = base64.b64encode(image).decode("ascii") return base64_img def get_matched_faces(faces): """Return the name and rounded confidence of matched faces.""" - return {face['name']: round(face['confidence'], 2) - for face in faces if face['matched']} + return { + face["name"]: round(face["confidence"], 2) for face in faces if face["matched"] + } def parse_faces(api_faces): @@ -81,15 +94,15 @@ def parse_faces(api_faces): known_faces = [] for entry in api_faces: face = {} - if entry['matched']: # This data is only in matched faces. - face[FACEBOX_NAME] = entry['name'] - face[ATTR_IMAGE_ID] = entry['id'] + if entry["matched"]: # This data is only in matched faces. + face[FACEBOX_NAME] = entry["name"] + face[ATTR_IMAGE_ID] = entry["id"] else: # Lets be explicit. face[FACEBOX_NAME] = None face[ATTR_IMAGE_ID] = None - face[ATTR_CONFIDENCE] = round(100.0*entry['confidence'], 2) - face[ATTR_MATCHED] = entry['matched'] - face[ATTR_BOUNDING_BOX] = entry['rect'] + face[ATTR_CONFIDENCE] = round(100.0 * entry["confidence"], 2) + face[ATTR_MATCHED] = entry["matched"] + face[ATTR_BOUNDING_BOX] = entry["rect"] known_faces.append(face) return known_faces @@ -98,13 +111,9 @@ def post_image(url, image, username, password): """Post an image to the classifier.""" kwargs = {} if username: - kwargs['auth'] = requests.auth.HTTPBasicAuth(username, password) + kwargs["auth"] = requests.auth.HTTPBasicAuth(username, password) try: - response = requests.post( - url, - json={"base64": encode_image(image)}, - **kwargs - ) + response = requests.post(url, json={"base64": encode_image(image)}, **kwargs) if response.status_code == HTTP_UNAUTHORIZED: _LOGGER.error("AuthenticationError on %s", CLASSIFIER) return None @@ -118,20 +127,24 @@ def teach_file(url, name, file_path, username, password): """Teach the classifier a name associated with a file.""" kwargs = {} if username: - kwargs['auth'] = requests.auth.HTTPBasicAuth(username, password) + kwargs["auth"] = requests.auth.HTTPBasicAuth(username, password) try: - with open(file_path, 'rb') as open_file: + with open(file_path, "rb") as open_file: response = requests.post( url, data={FACEBOX_NAME: name, ATTR_ID: file_path}, - files={'file': open_file}, - **kwargs - ) + files={"file": open_file}, + **kwargs, + ) if response.status_code == HTTP_UNAUTHORIZED: _LOGGER.error("AuthenticationError on %s", CLASSIFIER) elif response.status_code == HTTP_BAD_REQUEST: - _LOGGER.error("%s teaching of file %s failed with message:%s", - CLASSIFIER, file_path, response.text) + _LOGGER.error( + "%s teaching of file %s failed with message:%s", + CLASSIFIER, + file_path, + response.text, + ) except requests.exceptions.ConnectionError: _LOGGER.error("ConnectionError: Is %s running?", CLASSIFIER) @@ -142,8 +155,7 @@ def valid_file_path(file_path): cv.isfile(file_path) return True except vol.Invalid: - _LOGGER.error( - "%s error: Invalid file path: %s", CLASSIFIER, file_path) + _LOGGER.error("%s error: Invalid file path: %s", CLASSIFIER, file_path) return False @@ -164,15 +176,21 @@ def setup_platform(hass, config, add_entities, discovery_info=None): entities = [] for camera in config[CONF_SOURCE]: facebox = FaceClassifyEntity( - ip_address, port, username, password, hostname, - camera[CONF_ENTITY_ID], camera.get(CONF_NAME)) + ip_address, + port, + username, + password, + hostname, + camera[CONF_ENTITY_ID], + camera.get(CONF_NAME), + ) entities.append(facebox) hass.data[DATA_FACEBOX].append(facebox) add_entities(entities) def service_handle(service): """Handle for services.""" - entity_ids = service.data.get('entity_id') + entity_ids = service.data.get("entity_id") classifiers = hass.data[DATA_FACEBOX] if entity_ids: @@ -184,21 +202,20 @@ def setup_platform(hass, config, add_entities, discovery_info=None): classifier.teach(name, file_path) hass.services.register( - DOMAIN, SERVICE_TEACH_FACE, service_handle, - schema=SERVICE_TEACH_SCHEMA) + DOMAIN, SERVICE_TEACH_FACE, service_handle, schema=SERVICE_TEACH_SCHEMA + ) class FaceClassifyEntity(ImageProcessingFaceEntity): """Perform a face classification.""" - def __init__(self, ip_address, port, username, password, hostname, - camera_entity, name=None): + def __init__( + self, ip_address, port, username, password, hostname, camera_entity, name=None + ): """Init with the API key and model id.""" super().__init__() - self._url_check = "http://{}:{}/{}/check".format( - ip_address, port, CLASSIFIER) - self._url_teach = "http://{}:{}/{}/teach".format( - ip_address, port, CLASSIFIER) + self._url_check = "http://{}:{}/{}/check".format(ip_address, port, CLASSIFIER) + self._url_teach = "http://{}:{}/{}/teach".format(ip_address, port, CLASSIFIER) self._username = username self._password = password self._hostname = hostname @@ -212,13 +229,12 @@ class FaceClassifyEntity(ImageProcessingFaceEntity): def process_image(self, image): """Process an image.""" - response = post_image( - self._url_check, image, self._username, self._password) + response = post_image(self._url_check, image, self._username, self._password) if response: response_json = response.json() - if response_json['success']: - total_faces = response_json['facesCount'] - faces = parse_faces(response_json['faces']) + if response_json["success"]: + total_faces = response_json["facesCount"] + faces = parse_faces(response_json["faces"]) self._matched = get_matched_faces(faces) self.process_faces(faces, total_faces) @@ -229,11 +245,11 @@ class FaceClassifyEntity(ImageProcessingFaceEntity): def teach(self, name, file_path): """Teach classifier a face name.""" - if (not self.hass.config.is_allowed_path(file_path) - or not valid_file_path(file_path)): + if not self.hass.config.is_allowed_path(file_path) or not valid_file_path( + file_path + ): return - teach_file( - self._url_teach, name, file_path, self._username, self._password) + teach_file(self._url_teach, name, file_path, self._username, self._password) @property def camera_entity(self): @@ -249,7 +265,7 @@ class FaceClassifyEntity(ImageProcessingFaceEntity): def device_state_attributes(self): """Return the classifier attributes.""" return { - 'matched_faces': self._matched, - 'total_matched_faces': len(self._matched), - 'hostname': self._hostname - } + "matched_faces": self._matched, + "total_matched_faces": len(self._matched), + "hostname": self._hostname, + } diff --git a/homeassistant/components/fail2ban/sensor.py b/homeassistant/components/fail2ban/sensor.py index 78b11b1942b..d5e3d6064ea 100644 --- a/homeassistant/components/fail2ban/sensor.py +++ b/homeassistant/components/fail2ban/sensor.py @@ -15,31 +15,30 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ( - CONF_NAME, CONF_FILE_PATH -) +from homeassistant.const import CONF_NAME, CONF_FILE_PATH from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -CONF_JAILS = 'jails' +CONF_JAILS = "jails" -DEFAULT_NAME = 'fail2ban' -DEFAULT_LOG = '/var/log/fail2ban.log' +DEFAULT_NAME = "fail2ban" +DEFAULT_LOG = "/var/log/fail2ban.log" -STATE_CURRENT_BANS = 'current_bans' -STATE_ALL_BANS = 'total_bans' +STATE_CURRENT_BANS = "current_bans" +STATE_ALL_BANS = "total_bans" SCAN_INTERVAL = timedelta(seconds=120) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_JAILS): vol.All(cv.ensure_list, vol.Length(min=1)), - vol.Optional(CONF_FILE_PATH): cv.isfile, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_JAILS): vol.All(cv.ensure_list, vol.Length(min=1)), + vol.Optional(CONF_FILE_PATH): cv.isfile, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + } +) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the fail2ban sensor.""" name = config.get(CONF_NAME) jails = config.get(CONF_JAILS) @@ -58,7 +57,7 @@ class BanSensor(Entity): def __init__(self, name, jail, log_parser): """Initialize the sensor.""" - self._name = '{} {}'.format(name, jail) + self._name = "{} {}".format(name, jail) self.jail = jail self.ban_dict = {STATE_CURRENT_BANS: [], STATE_ALL_BANS: []} self.last_ban = None @@ -91,7 +90,7 @@ class BanSensor(Entity): for entry in self.log_parser.data: _LOGGER.debug(entry) current_ip = entry[1] - if entry[0] == 'Ban': + if entry[0] == "Ban": if current_ip not in self.ban_dict[STATE_CURRENT_BANS]: self.ban_dict[STATE_CURRENT_BANS].append(current_ip) if current_ip not in self.ban_dict[STATE_ALL_BANS]: @@ -99,14 +98,14 @@ class BanSensor(Entity): if len(self.ban_dict[STATE_ALL_BANS]) > 10: self.ban_dict[STATE_ALL_BANS].pop(0) - elif entry[0] == 'Unban': + elif entry[0] == "Unban": if current_ip in self.ban_dict[STATE_CURRENT_BANS]: self.ban_dict[STATE_CURRENT_BANS].remove(current_ip) if self.ban_dict[STATE_CURRENT_BANS]: self.last_ban = self.ban_dict[STATE_CURRENT_BANS][-1] else: - self.last_ban = 'None' + self.last_ban = "None" class BanLogParser: @@ -122,10 +121,8 @@ class BanLogParser: """Read the fail2ban log and find entries for jail.""" self.data = list() try: - with open(self.log_file, 'r', encoding='utf-8') as file_data: + with open(self.log_file, "r", encoding="utf-8") as file_data: self.data = self.ip_regex[jail].findall(file_data.read()) - except (IndexError, FileNotFoundError, IsADirectoryError, - UnboundLocalError): - _LOGGER.warning("File not present: %s", - os.path.basename(self.log_file)) + except (IndexError, FileNotFoundError, IsADirectoryError, UnboundLocalError): + _LOGGER.warning("File not present: %s", os.path.basename(self.log_file)) diff --git a/homeassistant/components/familyhub/camera.py b/homeassistant/components/familyhub/camera.py index e9a8bcd94a6..546d95f24d1 100644 --- a/homeassistant/components/familyhub/camera.py +++ b/homeassistant/components/familyhub/camera.py @@ -10,18 +10,20 @@ import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'FamilyHub Camera' +DEFAULT_NAME = "FamilyHub Camera" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_IP_ADDRESS): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_IP_ADDRESS): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + } +) -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Family Hub Camera.""" from pyfamilyhublocal import FamilyHubCam + address = config.get(CONF_IP_ADDRESS) name = config.get(CONF_NAME) diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index eab477354ba..235e8cf5fad 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -6,69 +6,71 @@ import logging import voluptuous as vol from homeassistant.components import group -from homeassistant.const import ( - SERVICE_TURN_ON, SERVICE_TOGGLE, SERVICE_TURN_OFF) +from homeassistant.const import SERVICE_TURN_ON, SERVICE_TOGGLE, SERVICE_TURN_OFF from homeassistant.loader import bind_hass from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.config_validation import ( # noqa - ENTITY_SERVICE_SCHEMA, PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE) + ENTITY_SERVICE_SCHEMA, + PLATFORM_SCHEMA, + PLATFORM_SCHEMA_BASE, +) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DOMAIN = 'fan' +DOMAIN = "fan" SCAN_INTERVAL = timedelta(seconds=30) -GROUP_NAME_ALL_FANS = 'all fans' +GROUP_NAME_ALL_FANS = "all fans" ENTITY_ID_ALL_FANS = group.ENTITY_ID_FORMAT.format(GROUP_NAME_ALL_FANS) -ENTITY_ID_FORMAT = DOMAIN + '.{}' +ENTITY_ID_FORMAT = DOMAIN + ".{}" # Bitfield of features supported by the fan entity SUPPORT_SET_SPEED = 1 SUPPORT_OSCILLATE = 2 SUPPORT_DIRECTION = 4 -SERVICE_SET_SPEED = 'set_speed' -SERVICE_OSCILLATE = 'oscillate' -SERVICE_SET_DIRECTION = 'set_direction' +SERVICE_SET_SPEED = "set_speed" +SERVICE_OSCILLATE = "oscillate" +SERVICE_SET_DIRECTION = "set_direction" -SPEED_OFF = 'off' -SPEED_LOW = 'low' -SPEED_MEDIUM = 'medium' -SPEED_HIGH = 'high' +SPEED_OFF = "off" +SPEED_LOW = "low" +SPEED_MEDIUM = "medium" +SPEED_HIGH = "high" -DIRECTION_FORWARD = 'forward' -DIRECTION_REVERSE = 'reverse' +DIRECTION_FORWARD = "forward" +DIRECTION_REVERSE = "reverse" -ATTR_SPEED = 'speed' -ATTR_SPEED_LIST = 'speed_list' -ATTR_OSCILLATING = 'oscillating' -ATTR_DIRECTION = 'direction' +ATTR_SPEED = "speed" +ATTR_SPEED_LIST = "speed_list" +ATTR_OSCILLATING = "oscillating" +ATTR_DIRECTION = "direction" PROP_TO_ATTR = { - 'speed': ATTR_SPEED, - 'speed_list': ATTR_SPEED_LIST, - 'oscillating': ATTR_OSCILLATING, - 'direction': ATTR_DIRECTION, + "speed": ATTR_SPEED, + "speed_list": ATTR_SPEED_LIST, + "oscillating": ATTR_OSCILLATING, + "direction": ATTR_DIRECTION, } # type: dict -FAN_SET_SPEED_SCHEMA = ENTITY_SERVICE_SCHEMA.extend({ - vol.Required(ATTR_SPEED): cv.string -}) # type: dict +FAN_SET_SPEED_SCHEMA = ENTITY_SERVICE_SCHEMA.extend( + {vol.Required(ATTR_SPEED): cv.string} +) # type: dict -FAN_TURN_ON_SCHEMA = ENTITY_SERVICE_SCHEMA.extend({ - vol.Optional(ATTR_SPEED): cv.string -}) # type: dict +FAN_TURN_ON_SCHEMA = ENTITY_SERVICE_SCHEMA.extend( + {vol.Optional(ATTR_SPEED): cv.string} +) # type: dict -FAN_OSCILLATE_SCHEMA = ENTITY_SERVICE_SCHEMA.extend({ - vol.Required(ATTR_OSCILLATING): cv.boolean -}) # type: dict +FAN_OSCILLATE_SCHEMA = ENTITY_SERVICE_SCHEMA.extend( + {vol.Required(ATTR_OSCILLATING): cv.boolean} +) # type: dict -FAN_SET_DIRECTION_SCHEMA = ENTITY_SERVICE_SCHEMA.extend({ - vol.Optional(ATTR_DIRECTION): cv.string -}) # type: dict +FAN_SET_DIRECTION_SCHEMA = ENTITY_SERVICE_SCHEMA.extend( + {vol.Optional(ATTR_DIRECTION): cv.string} +) # type: dict @bind_hass @@ -82,33 +84,28 @@ def is_on(hass, entity_id: str = None) -> bool: async def async_setup(hass, config: dict): """Expose fan control via statemachine and services.""" component = hass.data[DOMAIN] = EntityComponent( - _LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_FANS) + _LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_FANS + ) await component.async_setup(config) component.async_register_entity_service( - SERVICE_TURN_ON, FAN_TURN_ON_SCHEMA, - 'async_turn_on' + SERVICE_TURN_ON, FAN_TURN_ON_SCHEMA, "async_turn_on" ) component.async_register_entity_service( - SERVICE_TURN_OFF, ENTITY_SERVICE_SCHEMA, - 'async_turn_off' + SERVICE_TURN_OFF, ENTITY_SERVICE_SCHEMA, "async_turn_off" ) component.async_register_entity_service( - SERVICE_TOGGLE, ENTITY_SERVICE_SCHEMA, - 'async_toggle' + SERVICE_TOGGLE, ENTITY_SERVICE_SCHEMA, "async_toggle" ) component.async_register_entity_service( - SERVICE_SET_SPEED, FAN_SET_SPEED_SCHEMA, - 'async_set_speed' + SERVICE_SET_SPEED, FAN_SET_SPEED_SCHEMA, "async_set_speed" ) component.async_register_entity_service( - SERVICE_OSCILLATE, FAN_OSCILLATE_SCHEMA, - 'async_oscillate' + SERVICE_OSCILLATE, FAN_OSCILLATE_SCHEMA, "async_oscillate" ) component.async_register_entity_service( - SERVICE_SET_DIRECTION, FAN_SET_DIRECTION_SCHEMA, - 'async_set_direction' + SERVICE_SET_DIRECTION, FAN_SET_DIRECTION_SCHEMA, "async_set_direction" ) return True @@ -164,8 +161,7 @@ class FanEntity(ToggleEntity): """ if speed is SPEED_OFF: return self.async_turn_off() - return self.hass.async_add_job( - ft.partial(self.turn_on, speed, **kwargs)) + return self.hass.async_add_job(ft.partial(self.turn_on, speed, **kwargs)) def oscillate(self, oscillating: bool) -> None: """Oscillate the fan.""" diff --git a/homeassistant/components/fastdotcom/__init__.py b/homeassistant/components/fastdotcom/__init__.py index 3fe860a81fd..a40c2597222 100644 --- a/homeassistant/components/fastdotcom/__init__.py +++ b/homeassistant/components/fastdotcom/__init__.py @@ -10,22 +10,28 @@ from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.dispatcher import dispatcher_send from homeassistant.helpers.event import async_track_time_interval -DOMAIN = 'fastdotcom' -DATA_UPDATED = '{}_data_updated'.format(DOMAIN) +DOMAIN = "fastdotcom" +DATA_UPDATED = "{}_data_updated".format(DOMAIN) _LOGGER = logging.getLogger(__name__) -CONF_MANUAL = 'manual' +CONF_MANUAL = "manual" DEFAULT_INTERVAL = timedelta(hours=1) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_INTERVAL): - vol.All(cv.time_period, cv.positive_timedelta), - vol.Optional(CONF_MANUAL, default=False): cv.boolean, - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_INTERVAL): vol.All( + cv.time_period, cv.positive_timedelta + ), + vol.Optional(CONF_MANUAL, default=False): cv.boolean, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) async def async_setup(hass, config): @@ -34,19 +40,15 @@ async def async_setup(hass, config): data = hass.data[DOMAIN] = SpeedtestData(hass) if not conf[CONF_MANUAL]: - async_track_time_interval( - hass, data.update, conf[CONF_SCAN_INTERVAL] - ) + async_track_time_interval(hass, data.update, conf[CONF_SCAN_INTERVAL]) def update(call=None): """Service call to manually update the data.""" data.update() - hass.services.async_register(DOMAIN, 'speedtest', update) + hass.services.async_register(DOMAIN, "speedtest", update) - hass.async_create_task( - async_load_platform(hass, 'sensor', DOMAIN, {}, config) - ) + hass.async_create_task(async_load_platform(hass, "sensor", DOMAIN, {}, config)) return True @@ -62,6 +64,7 @@ class SpeedtestData: def update(self, now=None): """Get the latest data from fast.com.""" from fastdotcom import fast_com + _LOGGER.debug("Executing fast.com speedtest") - self.data = {'download': fast_com()} + self.data = {"download": fast_com()} dispatcher_send(self._hass, DATA_UPDATED) diff --git a/homeassistant/components/fastdotcom/sensor.py b/homeassistant/components/fastdotcom/sensor.py index c9af8e53ab8..6d9445ce159 100644 --- a/homeassistant/components/fastdotcom/sensor.py +++ b/homeassistant/components/fastdotcom/sensor.py @@ -9,13 +9,12 @@ from . import DATA_UPDATED, DOMAIN as FASTDOTCOM_DOMAIN _LOGGER = logging.getLogger(__name__) -ICON = 'mdi:speedometer' +ICON = "mdi:speedometer" -UNIT_OF_MEASUREMENT = 'Mbit/s' +UNIT_OF_MEASUREMENT = "Mbit/s" -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Fast.com sensor.""" async_add_entities([SpeedtestSensor(hass.data[FASTDOTCOM_DOMAIN])]) @@ -25,7 +24,7 @@ class SpeedtestSensor(RestoreEntity): def __init__(self, speedtest_data): """Initialize the sensor.""" - self._name = 'Fast.com Download' + self._name = "Fast.com Download" self.speedtest_client = speedtest_data self._state = None @@ -71,7 +70,7 @@ class SpeedtestSensor(RestoreEntity): data = self.speedtest_client.data if data is None: return - self._state = data['download'] + self._state = data["download"] @callback def _schedule_immediate_update(self): diff --git a/homeassistant/components/fedex/sensor.py b/homeassistant/components/fedex/sensor.py index aec1cee053c..a18d6a4c651 100644 --- a/homeassistant/components/fedex/sensor.py +++ b/homeassistant/components/fedex/sensor.py @@ -7,8 +7,13 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import (CONF_NAME, CONF_USERNAME, CONF_PASSWORD, - ATTR_ATTRIBUTION, CONF_SCAN_INTERVAL) +from homeassistant.const import ( + CONF_NAME, + CONF_USERNAME, + CONF_PASSWORD, + ATTR_ATTRIBUTION, + CONF_SCAN_INTERVAL, +) from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from homeassistant.util import slugify @@ -16,21 +21,23 @@ from homeassistant.util.dt import now, parse_date _LOGGER = logging.getLogger(__name__) -COOKIE = 'fedexdeliverymanager_cookies.pickle' +COOKIE = "fedexdeliverymanager_cookies.pickle" -DOMAIN = 'fedex' +DOMAIN = "fedex" -ICON = 'mdi:package-variant-closed' +ICON = "mdi:package-variant-closed" -STATUS_DELIVERED = 'delivered' +STATUS_DELIVERED = "delivered" SCAN_INTERVAL = timedelta(seconds=1800) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_NAME): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -43,8 +50,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): try: cookie = hass.config.path(COOKIE) session = fedexdeliverymanager.get_session( - config.get(CONF_USERNAME), config.get(CONF_PASSWORD), - cookie_path=cookie) + config.get(CONF_USERNAME), config.get(CONF_PASSWORD), cookie_path=cookie + ) except fedexdeliverymanager.FedexError: _LOGGER.exception("Could not connect to Fedex Delivery Manager") return False @@ -76,22 +83,23 @@ class FedexSensor(Entity): @property def unit_of_measurement(self): """Return the unit of measurement of this entity, if any.""" - return 'packages' + return "packages" def _update(self): """Update device state.""" import fedexdeliverymanager + status_counts = defaultdict(int) for package in fedexdeliverymanager.get_packages(self._session): - status = slugify(package['primary_status']) - skip = status == STATUS_DELIVERED and \ - parse_date(package['delivery_date']) < now().date() + status = slugify(package["primary_status"]) + skip = ( + status == STATUS_DELIVERED + and parse_date(package["delivery_date"]) < now().date() + ) if skip: continue status_counts[status] += 1 - self._attributes = { - ATTR_ATTRIBUTION: fedexdeliverymanager.ATTRIBUTION - } + self._attributes = {ATTR_ATTRIBUTION: fedexdeliverymanager.ATTRIBUTION} self._attributes.update(status_counts) self._state = sum(status_counts.values()) diff --git a/homeassistant/components/feedreader/__init__.py b/homeassistant/components/feedreader/__init__.py index d2acb674ec7..cdd76a56e16 100644 --- a/homeassistant/components/feedreader/__init__.py +++ b/homeassistant/components/feedreader/__init__.py @@ -13,25 +13,30 @@ import homeassistant.helpers.config_validation as cv _LOGGER = getLogger(__name__) -CONF_URLS = 'urls' -CONF_MAX_ENTRIES = 'max_entries' +CONF_URLS = "urls" +CONF_MAX_ENTRIES = "max_entries" DEFAULT_MAX_ENTRIES = 20 DEFAULT_SCAN_INTERVAL = timedelta(hours=1) -DOMAIN = 'feedreader' +DOMAIN = "feedreader" -EVENT_FEEDREADER = 'feedreader' +EVENT_FEEDREADER = "feedreader" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: { - vol.Required(CONF_URLS): vol.All(cv.ensure_list, [cv.url]), - vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL): - cv.time_period, - vol.Optional(CONF_MAX_ENTRIES, default=DEFAULT_MAX_ENTRIES): - cv.positive_int - } -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: { + vol.Required(CONF_URLS): vol.All(cv.ensure_list, [cv.url]), + vol.Optional( + CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL + ): cv.time_period, + vol.Optional( + CONF_MAX_ENTRIES, default=DEFAULT_MAX_ENTRIES + ): cv.positive_int, + } + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): @@ -41,8 +46,9 @@ def setup(hass, config): max_entries = config.get(DOMAIN).get(CONF_MAX_ENTRIES) data_file = hass.config.path("{}.pickle".format(DOMAIN)) storage = StoredData(data_file) - feeds = [FeedManager(url, scan_interval, max_entries, hass, storage) for - url in urls] + feeds = [ + FeedManager(url, scan_interval, max_entries, hass, storage) for url in urls + ] return len(feeds) > 0 @@ -63,8 +69,7 @@ class FeedManager: self._has_published_parsed = False self._event_type = EVENT_FEEDREADER self._feed_id = url - hass.bus.listen_once( - EVENT_HOMEASSISTANT_START, lambda _: self._update()) + hass.bus.listen_once(EVENT_HOMEASSISTANT_START, lambda _: self._update()) self._init_regular_updates(hass) def _log_no_entries(self): @@ -73,8 +78,7 @@ class FeedManager: def _init_regular_updates(self, hass): """Schedule regular updates at the top of the clock.""" - track_time_interval(hass, lambda now: self._update(), - self._scan_interval) + track_time_interval(hass, lambda now: self._update(), self._scan_interval) @property def last_update_successful(self): @@ -84,12 +88,13 @@ class FeedManager: def _update(self): """Update the feed and publish new entries to the event bus.""" import feedparser + _LOGGER.info("Fetching new data from feed %s", self._url) - self._feed = feedparser.parse(self._url, - etag=None if not self._feed - else self._feed.get('etag'), - modified=None if not self._feed - else self._feed.get('modified')) + self._feed = feedparser.parse( + self._url, + etag=None if not self._feed else self._feed.get("etag"), + modified=None if not self._feed else self._feed.get("modified"), + ) if not self._feed: _LOGGER.error("Error fetching feed data from %s", self._url) self._last_update_successful = False @@ -101,18 +106,23 @@ class FeedManager: # If an error is detected here, log error message but continue # processing the feed entries if present. if self._feed.bozo != 0: - _LOGGER.error("Error parsing feed %s: %s", self._url, - self._feed.bozo_exception) + _LOGGER.error( + "Error parsing feed %s: %s", self._url, self._feed.bozo_exception + ) # Using etag and modified, if there's no new data available, # the entries list will be empty if self._feed.entries: - _LOGGER.debug("%s entri(es) available in feed %s", - len(self._feed.entries), self._url) + _LOGGER.debug( + "%s entri(es) available in feed %s", + len(self._feed.entries), + self._url, + ) self._filter_entries() self._publish_new_entries() if self._has_published_parsed: self._storage.put_timestamp( - self._feed_id, self._last_entry_timestamp) + self._feed_id, self._last_entry_timestamp + ) else: self._log_no_entries() self._last_update_successful = True @@ -121,23 +131,26 @@ class FeedManager: def _filter_entries(self): """Filter the entries provided and return the ones to keep.""" if len(self._feed.entries) > self._max_entries: - _LOGGER.debug("Processing only the first %s entries " - "in feed %s", self._max_entries, self._url) - self._feed.entries = self._feed.entries[0:self._max_entries] + _LOGGER.debug( + "Processing only the first %s entries " "in feed %s", + self._max_entries, + self._url, + ) + self._feed.entries = self._feed.entries[0 : self._max_entries] def _update_and_fire_entry(self, entry): """Update last_entry_timestamp and fire entry.""" # We are lucky, `published_parsed` data available, let's make use of # it to publish only new available entries since the last run - if 'published_parsed' in entry.keys(): + if "published_parsed" in entry.keys(): self._has_published_parsed = True self._last_entry_timestamp = max( - entry.published_parsed, self._last_entry_timestamp) + entry.published_parsed, self._last_entry_timestamp + ) else: self._has_published_parsed = False - _LOGGER.debug("No published_parsed info available for entry %s", - entry) - entry.update({'feed_url': self._url}) + _LOGGER.debug("No published_parsed info available for entry %s", entry) + entry.update({"feed_url": self._url}) self._hass.bus.fire(self._event_type, entry) def _publish_new_entries(self): @@ -148,12 +161,12 @@ class FeedManager: self._firstrun = False else: # Set last entry timestamp as epoch time if not available - self._last_entry_timestamp = \ - datetime.utcfromtimestamp(0).timetuple() + self._last_entry_timestamp = datetime.utcfromtimestamp(0).timetuple() for entry in self._feed.entries: if self._firstrun or ( - 'published_parsed' in entry.keys() and - entry.published_parsed > self._last_entry_timestamp): + "published_parsed" in entry.keys() + and entry.published_parsed > self._last_entry_timestamp + ): self._update_and_fire_entry(entry) new_entries = True else: @@ -179,12 +192,13 @@ class StoredData: if self._cache_outdated and exists(self._data_file): try: _LOGGER.debug("Fetching data from file %s", self._data_file) - with self._lock, open(self._data_file, 'rb') as myfile: + with self._lock, open(self._data_file, "rb") as myfile: self._data = pickle.load(myfile) or {} self._cache_outdated = False except: # noqa: E722 pylint: disable=bare-except - _LOGGER.error("Error loading data from pickled file %s", - self._data_file) + _LOGGER.error( + "Error loading data from pickled file %s", self._data_file + ) def get_timestamp(self, feed_id): """Return stored timestamp for given feed id (usually the url).""" @@ -194,13 +208,15 @@ class StoredData: def put_timestamp(self, feed_id, timestamp): """Update timestamp for given feed id (usually the url).""" self._fetch_data() - with self._lock, open(self._data_file, 'wb') as myfile: + with self._lock, open(self._data_file, "wb") as myfile: self._data.update({feed_id: timestamp}) - _LOGGER.debug("Overwriting feed %s timestamp in storage file %s", - feed_id, self._data_file) + _LOGGER.debug( + "Overwriting feed %s timestamp in storage file %s", + feed_id, + self._data_file, + ) try: pickle.dump(self._data, myfile) except: # noqa: E722 pylint: disable=bare-except - _LOGGER.error( - "Error saving pickled data to %s", self._data_file) + _LOGGER.error("Error saving pickled data to %s", self._data_file) self._cache_outdated = True diff --git a/homeassistant/components/ffmpeg/__init__.py b/homeassistant/components/ffmpeg/__init__.py index 7252e617c5a..51e1cac3859 100644 --- a/homeassistant/components/ffmpeg/__init__.py +++ b/homeassistant/components/ffmpeg/__init__.py @@ -6,53 +6,56 @@ import voluptuous as vol from homeassistant.core import callback from homeassistant.const import ( - ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) + ATTR_ENTITY_ID, + EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP, +) from homeassistant.helpers.dispatcher import ( - async_dispatcher_send, async_dispatcher_connect) + async_dispatcher_send, + async_dispatcher_connect, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -DOMAIN = 'ffmpeg' +DOMAIN = "ffmpeg" _LOGGER = logging.getLogger(__name__) -SERVICE_START = 'start' -SERVICE_STOP = 'stop' -SERVICE_RESTART = 'restart' +SERVICE_START = "start" +SERVICE_STOP = "stop" +SERVICE_RESTART = "restart" -SIGNAL_FFMPEG_START = 'ffmpeg.start' -SIGNAL_FFMPEG_STOP = 'ffmpeg.stop' -SIGNAL_FFMPEG_RESTART = 'ffmpeg.restart' +SIGNAL_FFMPEG_START = "ffmpeg.start" +SIGNAL_FFMPEG_STOP = "ffmpeg.stop" +SIGNAL_FFMPEG_RESTART = "ffmpeg.restart" -DATA_FFMPEG = 'ffmpeg' +DATA_FFMPEG = "ffmpeg" -CONF_INITIAL_STATE = 'initial_state' -CONF_INPUT = 'input' -CONF_FFMPEG_BIN = 'ffmpeg_bin' -CONF_EXTRA_ARGUMENTS = 'extra_arguments' -CONF_OUTPUT = 'output' +CONF_INITIAL_STATE = "initial_state" +CONF_INPUT = "input" +CONF_FFMPEG_BIN = "ffmpeg_bin" +CONF_EXTRA_ARGUMENTS = "extra_arguments" +CONF_OUTPUT = "output" -DEFAULT_BINARY = 'ffmpeg' +DEFAULT_BINARY = "ffmpeg" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Optional(CONF_FFMPEG_BIN, default=DEFAULT_BINARY): cv.string, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + {vol.Optional(CONF_FFMPEG_BIN, default=DEFAULT_BINARY): cv.string} + ) + }, + extra=vol.ALLOW_EXTRA, +) -SERVICE_FFMPEG_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, -}) +SERVICE_FFMPEG_SCHEMA = vol.Schema({vol.Optional(ATTR_ENTITY_ID): cv.entity_ids}) async def async_setup(hass, config): """Set up the FFmpeg component.""" conf = config.get(DOMAIN, {}) - manager = FFmpegManager( - hass, - conf.get(CONF_FFMPEG_BIN, DEFAULT_BINARY) - ) + manager = FFmpegManager(hass, conf.get(CONF_FFMPEG_BIN, DEFAULT_BINARY)) await manager.async_get_version() @@ -69,16 +72,16 @@ async def async_setup(hass, config): async_dispatcher_send(hass, SIGNAL_FFMPEG_RESTART, entity_ids) hass.services.async_register( - DOMAIN, SERVICE_START, async_service_handle, - schema=SERVICE_FFMPEG_SCHEMA) + DOMAIN, SERVICE_START, async_service_handle, schema=SERVICE_FFMPEG_SCHEMA + ) hass.services.async_register( - DOMAIN, SERVICE_STOP, async_service_handle, - schema=SERVICE_FFMPEG_SCHEMA) + DOMAIN, SERVICE_STOP, async_service_handle, schema=SERVICE_FFMPEG_SCHEMA + ) hass.services.async_register( - DOMAIN, SERVICE_RESTART, async_service_handle, - schema=SERVICE_FFMPEG_SCHEMA) + DOMAIN, SERVICE_RESTART, async_service_handle, schema=SERVICE_FFMPEG_SCHEMA + ) hass.data[DATA_FFMPEG] = manager return True @@ -119,9 +122,9 @@ class FFmpegManager: def ffmpeg_stream_content_type(self): """Return HTTP content type for ffmpeg stream.""" if self._major_version is not None and self._major_version > 3: - return 'multipart/x-mixed-replace;boundary=ffmpeg' + return "multipart/x-mixed-replace;boundary=ffmpeg" - return 'multipart/x-mixed-replace;boundary=ffserver' + return "multipart/x-mixed-replace;boundary=ffserver" class FFmpegBase(Entity): @@ -138,11 +141,12 @@ class FFmpegBase(Entity): This method is a coroutine. """ async_dispatcher_connect( - self.hass, SIGNAL_FFMPEG_START, self._async_start_ffmpeg) + self.hass, SIGNAL_FFMPEG_START, self._async_start_ffmpeg + ) + async_dispatcher_connect(self.hass, SIGNAL_FFMPEG_STOP, self._async_stop_ffmpeg) async_dispatcher_connect( - self.hass, SIGNAL_FFMPEG_STOP, self._async_stop_ffmpeg) - async_dispatcher_connect( - self.hass, SIGNAL_FFMPEG_RESTART, self._async_restart_ffmpeg) + self.hass, SIGNAL_FFMPEG_RESTART, self._async_restart_ffmpeg + ) # register start/stop self._async_register_events() @@ -184,12 +188,12 @@ class FFmpegBase(Entity): @callback def _async_register_events(self): """Register a FFmpeg process/device.""" + async def async_shutdown_handle(event): """Stop FFmpeg process.""" await self._async_stop_ffmpeg(None) - self.hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_STOP, async_shutdown_handle) + self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_shutdown_handle) # start on startup if not self.initial_state: @@ -200,5 +204,4 @@ class FFmpegBase(Entity): await self._async_start_ffmpeg(None) self.async_schedule_update_ha_state() - self.hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_START, async_start_handle) + self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, async_start_handle) diff --git a/homeassistant/components/ffmpeg/camera.py b/homeassistant/components/ffmpeg/camera.py index 20b4e538085..598ffe36bd4 100644 --- a/homeassistant/components/ffmpeg/camera.py +++ b/homeassistant/components/ffmpeg/camera.py @@ -4,8 +4,7 @@ import logging import voluptuous as vol -from homeassistant.components.camera import ( - PLATFORM_SCHEMA, Camera, SUPPORT_STREAM) +from homeassistant.components.camera import PLATFORM_SCHEMA, Camera, SUPPORT_STREAM from homeassistant.const import CONF_NAME from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream import homeassistant.helpers.config_validation as cv @@ -14,18 +13,19 @@ from . import CONF_EXTRA_ARGUMENTS, CONF_INPUT, DATA_FFMPEG _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'FFmpeg' +DEFAULT_NAME = "FFmpeg" DEFAULT_ARGUMENTS = "-pred 1" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_INPUT): cv.string, - vol.Optional(CONF_EXTRA_ARGUMENTS, default=DEFAULT_ARGUMENTS): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_INPUT): cv.string, + vol.Optional(CONF_EXTRA_ARGUMENTS, default=DEFAULT_ARGUMENTS): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + } +) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up a FFmpeg camera.""" async_add_entities([FFmpegCamera(hass, config)]) @@ -49,16 +49,19 @@ class FFmpegCamera(Camera): async def stream_source(self): """Return the stream source.""" - return self._input.split(' ')[-1] + return self._input.split(" ")[-1] async def async_camera_image(self): """Return a still image response from the camera.""" from haffmpeg.tools import ImageFrame, IMAGE_JPEG + ffmpeg = ImageFrame(self._manager.binary, loop=self.hass.loop) - image = await asyncio.shield(ffmpeg.get_image( - self._input, output_format=IMAGE_JPEG, - extra_cmd=self._extra_arguments)) + image = await asyncio.shield( + ffmpeg.get_image( + self._input, output_format=IMAGE_JPEG, extra_cmd=self._extra_arguments + ) + ) return image async def handle_async_mjpeg_stream(self, request): @@ -66,14 +69,16 @@ class FFmpegCamera(Camera): from haffmpeg.camera import CameraMjpeg stream = CameraMjpeg(self._manager.binary, loop=self.hass.loop) - await stream.open_camera( - self._input, extra_cmd=self._extra_arguments) + await stream.open_camera(self._input, extra_cmd=self._extra_arguments) try: stream_reader = await stream.get_reader() return await async_aiohttp_proxy_stream( - self.hass, request, stream_reader, - self._manager.ffmpeg_stream_content_type) + self.hass, + request, + stream_reader, + self._manager.ffmpeg_stream_content_type, + ) finally: await stream.close() diff --git a/homeassistant/components/ffmpeg_motion/binary_sensor.py b/homeassistant/components/ffmpeg_motion/binary_sensor.py index 03aacf3aafb..235a9e4b009 100644 --- a/homeassistant/components/ffmpeg_motion/binary_sensor.py +++ b/homeassistant/components/ffmpeg_motion/binary_sensor.py @@ -5,41 +5,49 @@ import voluptuous as vol from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) +from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA from homeassistant.components.ffmpeg import ( - FFmpegBase, DATA_FFMPEG, CONF_INPUT, CONF_EXTRA_ARGUMENTS, - CONF_INITIAL_STATE) + FFmpegBase, + DATA_FFMPEG, + CONF_INPUT, + CONF_EXTRA_ARGUMENTS, + CONF_INITIAL_STATE, +) from homeassistant.const import CONF_NAME _LOGGER = logging.getLogger(__name__) -CONF_RESET = 'reset' -CONF_CHANGES = 'changes' -CONF_REPEAT = 'repeat' -CONF_REPEAT_TIME = 'repeat_time' +CONF_RESET = "reset" +CONF_CHANGES = "changes" +CONF_REPEAT = "repeat" +CONF_REPEAT_TIME = "repeat_time" -DEFAULT_NAME = 'FFmpeg Motion' +DEFAULT_NAME = "FFmpeg Motion" DEFAULT_INIT_STATE = True -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_INPUT): cv.string, - vol.Optional(CONF_INITIAL_STATE, default=DEFAULT_INIT_STATE): cv.boolean, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string, - vol.Optional(CONF_RESET, default=10): - vol.All(vol.Coerce(int), vol.Range(min=1)), - vol.Optional(CONF_CHANGES, default=10): - vol.All(vol.Coerce(float), vol.Range(min=0, max=99)), - vol.Inclusive(CONF_REPEAT, 'repeat'): - vol.All(vol.Coerce(int), vol.Range(min=1)), - vol.Inclusive(CONF_REPEAT_TIME, 'repeat'): - vol.All(vol.Coerce(int), vol.Range(min=1)), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_INPUT): cv.string, + vol.Optional(CONF_INITIAL_STATE, default=DEFAULT_INIT_STATE): cv.boolean, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string, + vol.Optional(CONF_RESET, default=10): vol.All( + vol.Coerce(int), vol.Range(min=1) + ), + vol.Optional(CONF_CHANGES, default=10): vol.All( + vol.Coerce(float), vol.Range(min=0, max=99) + ), + vol.Inclusive(CONF_REPEAT, "repeat"): vol.All( + vol.Coerce(int), vol.Range(min=1) + ), + vol.Inclusive(CONF_REPEAT_TIME, "repeat"): vol.All( + vol.Coerce(int), vol.Range(min=1) + ), + } +) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the FFmpeg binary motion sensor.""" manager = hass.data[DATA_FFMPEG] entity = FFmpegMotion(hass, manager, config) @@ -82,8 +90,7 @@ class FFmpegMotion(FFmpegBinarySensor): from haffmpeg.sensor import SensorMotion super().__init__(config) - self.ffmpeg = SensorMotion( - manager.binary, hass.loop, self._async_callback) + self.ffmpeg = SensorMotion(manager.binary, hass.loop, self._async_callback) async def _async_start_ffmpeg(self, entity_ids): """Start a FFmpeg instance. @@ -110,4 +117,4 @@ class FFmpegMotion(FFmpegBinarySensor): @property def device_class(self): """Return the class of this sensor, from DEVICE_CLASSES.""" - return 'motion' + return "motion" diff --git a/homeassistant/components/ffmpeg_noise/binary_sensor.py b/homeassistant/components/ffmpeg_noise/binary_sensor.py index 7fbda8ca18b..00e5dbb682f 100644 --- a/homeassistant/components/ffmpeg_noise/binary_sensor.py +++ b/homeassistant/components/ffmpeg_noise/binary_sensor.py @@ -5,38 +5,44 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.binary_sensor import PLATFORM_SCHEMA -from homeassistant.components.ffmpeg_motion.binary_sensor import ( - FFmpegBinarySensor) +from homeassistant.components.ffmpeg_motion.binary_sensor import FFmpegBinarySensor from homeassistant.components.ffmpeg import ( - DATA_FFMPEG, CONF_INPUT, CONF_OUTPUT, CONF_EXTRA_ARGUMENTS, - CONF_INITIAL_STATE) + DATA_FFMPEG, + CONF_INPUT, + CONF_OUTPUT, + CONF_EXTRA_ARGUMENTS, + CONF_INITIAL_STATE, +) from homeassistant.const import CONF_NAME _LOGGER = logging.getLogger(__name__) -CONF_PEAK = 'peak' -CONF_DURATION = 'duration' -CONF_RESET = 'reset' +CONF_PEAK = "peak" +CONF_DURATION = "duration" +CONF_RESET = "reset" -DEFAULT_NAME = 'FFmpeg Noise' +DEFAULT_NAME = "FFmpeg Noise" DEFAULT_INIT_STATE = True -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_INPUT): cv.string, - vol.Optional(CONF_INITIAL_STATE, default=DEFAULT_INIT_STATE): cv.boolean, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string, - vol.Optional(CONF_OUTPUT): cv.string, - vol.Optional(CONF_PEAK, default=-30): vol.Coerce(int), - vol.Optional(CONF_DURATION, default=1): - vol.All(vol.Coerce(int), vol.Range(min=1)), - vol.Optional(CONF_RESET, default=10): - vol.All(vol.Coerce(int), vol.Range(min=1)), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_INPUT): cv.string, + vol.Optional(CONF_INITIAL_STATE, default=DEFAULT_INIT_STATE): cv.boolean, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string, + vol.Optional(CONF_OUTPUT): cv.string, + vol.Optional(CONF_PEAK, default=-30): vol.Coerce(int), + vol.Optional(CONF_DURATION, default=1): vol.All( + vol.Coerce(int), vol.Range(min=1) + ), + vol.Optional(CONF_RESET, default=10): vol.All( + vol.Coerce(int), vol.Range(min=1) + ), + } +) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the FFmpeg noise binary sensor.""" manager = hass.data[DATA_FFMPEG] entity = FFmpegNoise(hass, manager, config) @@ -51,8 +57,7 @@ class FFmpegNoise(FFmpegBinarySensor): from haffmpeg.sensor import SensorNoise super().__init__(config) - self.ffmpeg = SensorNoise( - manager.binary, hass.loop, self._async_callback) + self.ffmpeg = SensorNoise(manager.binary, hass.loop, self._async_callback) async def _async_start_ffmpeg(self, entity_ids): """Start a FFmpeg instance. @@ -77,4 +82,4 @@ class FFmpegNoise(FFmpegBinarySensor): @property def device_class(self): """Return the class of this sensor, from DEVICE_CLASSES.""" - return 'sound' + return "sound" diff --git a/homeassistant/components/fibaro/__init__.py b/homeassistant/components/fibaro/__init__.py index f78afbf10e5..d47c2b0c2d2 100644 --- a/homeassistant/components/fibaro/__init__.py +++ b/homeassistant/components/fibaro/__init__.py @@ -5,9 +5,17 @@ from typing import Optional import voluptuous as vol from homeassistant.const import ( - ATTR_ARMED, ATTR_BATTERY_LEVEL, CONF_DEVICE_CLASS, CONF_EXCLUDE, - CONF_ICON, CONF_PASSWORD, CONF_URL, CONF_USERNAME, - CONF_WHITE_VALUE, EVENT_HOMEASSISTANT_STOP) + ATTR_ARMED, + ATTR_BATTERY_LEVEL, + CONF_DEVICE_CLASS, + CONF_EXCLUDE, + CONF_ICON, + CONF_PASSWORD, + CONF_URL, + CONF_USERNAME, + CONF_WHITE_VALUE, + EVENT_HOMEASSISTANT_STOP, +) from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -15,72 +23,88 @@ from homeassistant.util import convert, slugify _LOGGER = logging.getLogger(__name__) -ATTR_CURRENT_ENERGY_KWH = 'current_energy_kwh' -ATTR_CURRENT_POWER_W = 'current_power_w' +ATTR_CURRENT_ENERGY_KWH = "current_energy_kwh" +ATTR_CURRENT_POWER_W = "current_power_w" -CONF_COLOR = 'color' -CONF_DEVICE_CONFIG = 'device_config' -CONF_DIMMING = 'dimming' -CONF_GATEWAYS = 'gateways' -CONF_PLUGINS = 'plugins' -CONF_RESET_COLOR = 'reset_color' -DOMAIN = 'fibaro' -FIBARO_CONTROLLERS = 'fibaro_controllers' -FIBARO_DEVICES = 'fibaro_devices' -FIBARO_COMPONENTS = ['binary_sensor', 'climate', 'cover', 'light', - 'scene', 'sensor', 'switch'] +CONF_COLOR = "color" +CONF_DEVICE_CONFIG = "device_config" +CONF_DIMMING = "dimming" +CONF_GATEWAYS = "gateways" +CONF_PLUGINS = "plugins" +CONF_RESET_COLOR = "reset_color" +DOMAIN = "fibaro" +FIBARO_CONTROLLERS = "fibaro_controllers" +FIBARO_DEVICES = "fibaro_devices" +FIBARO_COMPONENTS = [ + "binary_sensor", + "climate", + "cover", + "light", + "scene", + "sensor", + "switch", +] FIBARO_TYPEMAP = { - 'com.fibaro.multilevelSensor': "sensor", - 'com.fibaro.binarySwitch': 'switch', - 'com.fibaro.multilevelSwitch': 'switch', - 'com.fibaro.FGD212': 'light', - 'com.fibaro.FGR': 'cover', - 'com.fibaro.doorSensor': 'binary_sensor', - 'com.fibaro.doorWindowSensor': 'binary_sensor', - 'com.fibaro.FGMS001': 'binary_sensor', - 'com.fibaro.heatDetector': 'binary_sensor', - 'com.fibaro.lifeDangerSensor': 'binary_sensor', - 'com.fibaro.smokeSensor': 'binary_sensor', - 'com.fibaro.remoteSwitch': 'switch', - 'com.fibaro.sensor': 'sensor', - 'com.fibaro.colorController': 'light', - 'com.fibaro.securitySensor': 'binary_sensor', - 'com.fibaro.hvac': 'climate', - 'com.fibaro.setpoint': 'climate', - 'com.fibaro.FGT001': 'climate', - 'com.fibaro.thermostatDanfoss': 'climate' + "com.fibaro.multilevelSensor": "sensor", + "com.fibaro.binarySwitch": "switch", + "com.fibaro.multilevelSwitch": "switch", + "com.fibaro.FGD212": "light", + "com.fibaro.FGR": "cover", + "com.fibaro.doorSensor": "binary_sensor", + "com.fibaro.doorWindowSensor": "binary_sensor", + "com.fibaro.FGMS001": "binary_sensor", + "com.fibaro.heatDetector": "binary_sensor", + "com.fibaro.lifeDangerSensor": "binary_sensor", + "com.fibaro.smokeSensor": "binary_sensor", + "com.fibaro.remoteSwitch": "switch", + "com.fibaro.sensor": "sensor", + "com.fibaro.colorController": "light", + "com.fibaro.securitySensor": "binary_sensor", + "com.fibaro.hvac": "climate", + "com.fibaro.setpoint": "climate", + "com.fibaro.FGT001": "climate", + "com.fibaro.thermostatDanfoss": "climate", } -DEVICE_CONFIG_SCHEMA_ENTRY = vol.Schema({ - vol.Optional(CONF_DIMMING): cv.boolean, - vol.Optional(CONF_COLOR): cv.boolean, - vol.Optional(CONF_WHITE_VALUE): cv.boolean, - vol.Optional(CONF_RESET_COLOR): cv.boolean, - vol.Optional(CONF_DEVICE_CLASS): cv.string, - vol.Optional(CONF_ICON): cv.string, -}) +DEVICE_CONFIG_SCHEMA_ENTRY = vol.Schema( + { + vol.Optional(CONF_DIMMING): cv.boolean, + vol.Optional(CONF_COLOR): cv.boolean, + vol.Optional(CONF_WHITE_VALUE): cv.boolean, + vol.Optional(CONF_RESET_COLOR): cv.boolean, + vol.Optional(CONF_DEVICE_CLASS): cv.string, + vol.Optional(CONF_ICON): cv.string, + } +) FIBARO_ID_LIST_SCHEMA = vol.Schema([cv.string]) -GATEWAY_CONFIG = vol.Schema({ - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_URL): cv.url, - vol.Optional(CONF_PLUGINS, default=False): cv.boolean, - vol.Optional(CONF_EXCLUDE, default=[]): FIBARO_ID_LIST_SCHEMA, - vol.Optional(CONF_DEVICE_CONFIG, default={}): - vol.Schema({cv.string: DEVICE_CONFIG_SCHEMA_ENTRY}) -}, extra=vol.ALLOW_EXTRA) +GATEWAY_CONFIG = vol.Schema( + { + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_URL): cv.url, + vol.Optional(CONF_PLUGINS, default=False): cv.boolean, + vol.Optional(CONF_EXCLUDE, default=[]): FIBARO_ID_LIST_SCHEMA, + vol.Optional(CONF_DEVICE_CONFIG, default={}): vol.Schema( + {cv.string: DEVICE_CONFIG_SCHEMA_ENTRY} + ), + }, + extra=vol.ALLOW_EXTRA, +) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_GATEWAYS): vol.All(cv.ensure_list, [GATEWAY_CONFIG]), - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + {vol.Required(CONF_GATEWAYS): vol.All(cv.ensure_list, [GATEWAY_CONFIG])} + ) + }, + extra=vol.ALLOW_EXTRA, +) -class FibaroController(): +class FibaroController: """Initiate Fibaro Controller Class.""" def __init__(self, config): @@ -88,7 +112,8 @@ class FibaroController(): from fiblary3.client.v4.client import Client as FibaroClient self._client = FibaroClient( - config[CONF_URL], config[CONF_USERNAME], config[CONF_PASSWORD]) + config[CONF_URL], config[CONF_USERNAME], config[CONF_PASSWORD] + ) self._scene_map = None # Whether to import devices from plugins self._import_plugins = config[CONF_PLUGINS] @@ -99,7 +124,7 @@ class FibaroController(): self._callbacks = {} # Update value callbacks by deviceId self._state_handler = None # Fiblary's StateHandler object self._excluded_devices = config[CONF_EXCLUDE] - self.hub_serial = None # Unique serial number of the hub + self.hub_serial = None # Unique serial number of the hub def connect(self): """Start the communication with the Fibaro controller.""" @@ -108,12 +133,12 @@ class FibaroController(): info = self._client.info.get() self.hub_serial = slugify(info.serialNumber) except AssertionError: - _LOGGER.error("Can't connect to Fibaro HC. " - "Please check URL.") + _LOGGER.error("Can't connect to Fibaro HC. " "Please check URL.") return False if login is None or login.status is False: - _LOGGER.error("Invalid login for Fibaro HC. " - "Please check username and password") + _LOGGER.error( + "Invalid login for Fibaro HC. " "Please check username and password" + ) return False self._room_map = {room.id: room for room in self._client.rooms.list()} @@ -124,6 +149,7 @@ class FibaroController(): def enable_state_handler(self): """Start StateHandler thread for monitoring updates.""" from fiblary3.client.v4.client import StateHandler + self._state_handler = StateHandler(self._client, self._on_state_change) def disable_state_handler(self): @@ -134,28 +160,26 @@ class FibaroController(): def _on_state_change(self, state): """Handle change report received from the HomeCenter.""" callback_set = set() - for change in state.get('changes', []): + for change in state.get("changes", []): try: - dev_id = change.pop('id') + dev_id = change.pop("id") if dev_id not in self._device_map.keys(): continue device = self._device_map[dev_id] for property_name, value in change.items(): - if property_name == 'log': + if property_name == "log": if value and value != "transfer OK": - _LOGGER.debug("LOG %s: %s", - device.friendly_name, value) + _LOGGER.debug("LOG %s: %s", device.friendly_name, value) continue - if property_name == 'logTemp': + if property_name == "logTemp": continue if property_name in device.properties: - device.properties[property_name] = \ - value - _LOGGER.debug("<- %s.%s = %s", device.ha_id, - property_name, str(value)) + device.properties[property_name] = value + _LOGGER.debug( + "<- %s.%s = %s", device.ha_id, property_name, str(value) + ) else: - _LOGGER.warning("%s.%s not found", device.ha_id, - property_name) + _LOGGER.warning("%s.%s not found", device.ha_id, property_name) if dev_id in self._callbacks: callback_set.add(dev_id) except (ValueError, KeyError): @@ -170,8 +194,10 @@ class FibaroController(): def get_children(self, device_id): """Get a list of child devices.""" return [ - device for device in self._device_map.values() - if device.parentId == device_id] + device + for device in self._device_map.values() + if device.parentId == device_id + ] def get_siblings(self, device_id): """Get the siblings of a device.""" @@ -182,29 +208,31 @@ class FibaroController(): """Map device to HA device type.""" # Use our lookup table to identify device type device_type = None - if 'type' in device: + if "type" in device: device_type = FIBARO_TYPEMAP.get(device.type) - if device_type is None and 'baseType' in device: + if device_type is None and "baseType" in device: device_type = FIBARO_TYPEMAP.get(device.baseType) # We can also identify device type by its capabilities if device_type is None: - if 'setBrightness' in device.actions: - device_type = 'light' - elif 'turnOn' in device.actions: - device_type = 'switch' - elif 'open' in device.actions: - device_type = 'cover' - elif 'value' in device.properties: - if device.properties.value in ('true', 'false'): - device_type = 'binary_sensor' + if "setBrightness" in device.actions: + device_type = "light" + elif "turnOn" in device.actions: + device_type = "switch" + elif "open" in device.actions: + device_type = "cover" + elif "value" in device.properties: + if device.properties.value in ("true", "false"): + device_type = "binary_sensor" else: - device_type = 'sensor' + device_type = "sensor" # Switches that control lights should show up as lights - if device_type == 'switch' and \ - device.properties.get('isLight', 'false') == 'true': - device_type = 'light' + if ( + device_type == "switch" + and device.properties.get("isLight", "false") == "true" + ): + device_type = "light" return device_type def _read_scenes(self): @@ -215,17 +243,17 @@ class FibaroController(): continue device.fibaro_controller = self if device.roomID == 0: - room_name = 'Unknown' + room_name = "Unknown" else: room_name = self._room_map[device.roomID].name device.room_name = room_name - device.friendly_name = '{} {}'.format(room_name, device.name) - device.ha_id = 'scene_{}_{}_{}'.format( - slugify(room_name), slugify(device.name), device.id) - device.unique_id_str = "{}.scene.{}".format( - self.hub_serial, device.id) + device.friendly_name = "{} {}".format(room_name, device.name) + device.ha_id = "scene_{}_{}_{}".format( + slugify(room_name), slugify(device.name), device.id + ) + device.unique_id_str = "{}.scene.{}".format(self.hub_serial, device.id) self._scene_map[device.id] = device - self.fibaro_devices['scene'].append(device) + self.fibaro_devices["scene"].append(device) def _read_devices(self): """Read and process the device list.""" @@ -237,42 +265,48 @@ class FibaroController(): try: device.fibaro_controller = self if device.roomID == 0: - room_name = 'Unknown' + room_name = "Unknown" else: room_name = self._room_map[device.roomID].name device.room_name = room_name - device.friendly_name = room_name + ' ' + device.name - device.ha_id = '{}_{}_{}'.format( - slugify(room_name), slugify(device.name), device.id) - if device.enabled and \ - ('isPlugin' not in device or - (not device.isPlugin or self._import_plugins)) and \ - device.ha_id not in self._excluded_devices: + device.friendly_name = room_name + " " + device.name + device.ha_id = "{}_{}_{}".format( + slugify(room_name), slugify(device.name), device.id + ) + if ( + device.enabled + and ( + "isPlugin" not in device + or (not device.isPlugin or self._import_plugins) + ) + and device.ha_id not in self._excluded_devices + ): device.mapped_type = self._map_device_to_type(device) - device.device_config = \ - self._device_config.get(device.ha_id, {}) + device.device_config = self._device_config.get(device.ha_id, {}) else: device.mapped_type = None dtype = device.mapped_type if dtype: - device.unique_id_str = "{}.{}".format( - self.hub_serial, device.id) + device.unique_id_str = "{}.{}".format(self.hub_serial, device.id) self._device_map[device.id] = device - if dtype != 'climate': + if dtype != "climate": self.fibaro_devices[dtype].append(device) else: # if a sibling of this has been added, skip this one # otherwise add the first visible device in the group # which is a hack, but solves a problem with FGT having # hidden compatibility devices before the real device - if last_climate_parent != device.parentId and \ - device.visible: + if last_climate_parent != device.parentId and device.visible: self.fibaro_devices[dtype].append(device) last_climate_parent = device.parentId - _LOGGER.debug("%s (%s, %s) -> %s %s", - device.ha_id, device.type, - device.baseType, dtype, - str(device)) + _LOGGER.debug( + "%s (%s, %s) -> %s %s", + device.ha_id, + device.type, + device.baseType, + dtype, + str(device), + ) except (KeyError, ValueError): pass @@ -298,12 +332,12 @@ def setup(hass, base_config): hass.data[FIBARO_CONTROLLERS][controller.hub_serial] = controller for component in FIBARO_COMPONENTS: hass.data[FIBARO_DEVICES][component].extend( - controller.fibaro_devices[component]) + controller.fibaro_devices[component] + ) if hass.data[FIBARO_CONTROLLERS]: for component in FIBARO_COMPONENTS: - discovery.load_platform(hass, component, DOMAIN, {}, - base_config) + discovery.load_platform(hass, component, DOMAIN, {}, base_config) for controller in hass.data[FIBARO_CONTROLLERS].values(): controller.enable_state_handler() hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_fibaro) @@ -333,35 +367,37 @@ class FibaroDevice(Entity): @property def level(self): """Get the level of Fibaro device.""" - if 'value' in self.fibaro_device.properties: + if "value" in self.fibaro_device.properties: return self.fibaro_device.properties.value return None @property def level2(self): """Get the tilt level of Fibaro device.""" - if 'value2' in self.fibaro_device.properties: + if "value2" in self.fibaro_device.properties: return self.fibaro_device.properties.value2 return None def dont_know_message(self, action): """Make a warning in case we don't know how to perform an action.""" - _LOGGER.warning("Not sure how to setValue: %s " - "(available actions: %s)", str(self.ha_id), - str(self.fibaro_device.actions)) + _LOGGER.warning( + "Not sure how to setValue: %s " "(available actions: %s)", + str(self.ha_id), + str(self.fibaro_device.actions), + ) def set_level(self, level): """Set the level of Fibaro device.""" self.action("setValue", level) - if 'value' in self.fibaro_device.properties: + if "value" in self.fibaro_device.properties: self.fibaro_device.properties.value = level - if 'brightness' in self.fibaro_device.properties: + if "brightness" in self.fibaro_device.properties: self.fibaro_device.properties.brightness = level def set_level2(self, level): """Set the level2 of Fibaro device.""" self.action("setValue2", level) - if 'value2' in self.fibaro_device.properties: + if "value2" in self.fibaro_device.properties: self.fibaro_device.properties.value2 = level def call_turn_on(self): @@ -380,15 +416,13 @@ class FibaroDevice(Entity): white = int(max(0, min(255, white))) color_str = "{},{},{},{}".format(red, green, blue, white) self.fibaro_device.properties.color = color_str - self.action("setColor", str(red), str(green), - str(blue), str(white)) + self.action("setColor", str(red), str(green), str(blue), str(white)) def action(self, cmd, *args): """Perform an action on the Fibaro HC.""" if cmd in self.fibaro_device.actions: getattr(self.fibaro_device, cmd)(*args) - _LOGGER.debug("-> %s.%s%s called", str(self.ha_id), - str(cmd), str(args)) + _LOGGER.debug("-> %s.%s%s called", str(self.ha_id), str(cmd), str(args)) else: self.dont_know_message(cmd) @@ -400,7 +434,7 @@ class FibaroDevice(Entity): @property def current_power_w(self): """Return the current power usage in W.""" - if 'power' in self.fibaro_device.properties: + if "power" in self.fibaro_device.properties: power = self.fibaro_device.properties.power if power: return convert(power, float, 0.0) @@ -410,10 +444,12 @@ class FibaroDevice(Entity): @property def current_binary_state(self): """Return the current binary state.""" - if self.fibaro_device.properties.value == 'false': + if self.fibaro_device.properties.value == "false": return False - if self.fibaro_device.properties.value == 'true' or \ - int(self.fibaro_device.properties.value) > 0: + if ( + self.fibaro_device.properties.value == "true" + or int(self.fibaro_device.properties.value) > 0 + ): return True return False @@ -442,19 +478,22 @@ class FibaroDevice(Entity): attr = {} try: - if 'battery' in self.fibaro_device.interfaces: - attr[ATTR_BATTERY_LEVEL] = \ - int(self.fibaro_device.properties.batteryLevel) - if 'fibaroAlarmArm' in self.fibaro_device.interfaces: + if "battery" in self.fibaro_device.interfaces: + attr[ATTR_BATTERY_LEVEL] = int( + self.fibaro_device.properties.batteryLevel + ) + if "fibaroAlarmArm" in self.fibaro_device.interfaces: attr[ATTR_ARMED] = bool(self.fibaro_device.properties.armed) - if 'power' in self.fibaro_device.interfaces: + if "power" in self.fibaro_device.interfaces: attr[ATTR_CURRENT_POWER_W] = convert( - self.fibaro_device.properties.power, float, 0.0) - if 'energy' in self.fibaro_device.interfaces: + self.fibaro_device.properties.power, float, 0.0 + ) + if "energy" in self.fibaro_device.interfaces: attr[ATTR_CURRENT_ENERGY_KWH] = convert( - self.fibaro_device.properties.energy, float, 0.0) + self.fibaro_device.properties.energy, float, 0.0 + ) except (ValueError, KeyError): pass - attr['fibaro_id'] = self.fibaro_device.id + attr["fibaro_id"] = self.fibaro_device.id return attr diff --git a/homeassistant/components/fibaro/binary_sensor.py b/homeassistant/components/fibaro/binary_sensor.py index 44448227a1c..af2c2a9401a 100644 --- a/homeassistant/components/fibaro/binary_sensor.py +++ b/homeassistant/components/fibaro/binary_sensor.py @@ -1,8 +1,7 @@ """Support for Fibaro binary sensors.""" import logging -from homeassistant.components.binary_sensor import ( - ENTITY_ID_FORMAT, BinarySensorDevice) +from homeassistant.components.binary_sensor import ENTITY_ID_FORMAT, BinarySensorDevice from homeassistant.const import CONF_DEVICE_CLASS, CONF_ICON from . import FIBARO_DEVICES, FibaroDevice @@ -10,13 +9,13 @@ from . import FIBARO_DEVICES, FibaroDevice _LOGGER = logging.getLogger(__name__) SENSOR_TYPES = { - 'com.fibaro.floodSensor': ['Flood', 'mdi:water', 'flood'], - 'com.fibaro.motionSensor': ['Motion', 'mdi:run', 'motion'], - 'com.fibaro.doorSensor': ['Door', 'mdi:window-open', 'door'], - 'com.fibaro.windowSensor': ['Window', 'mdi:window-open', 'window'], - 'com.fibaro.smokeSensor': ['Smoke', 'mdi:smoking', 'smoke'], - 'com.fibaro.FGMS001': ['Motion', 'mdi:run', 'motion'], - 'com.fibaro.heatDetector': ['Heat', 'mdi:fire', 'heat'], + "com.fibaro.floodSensor": ["Flood", "mdi:water", "flood"], + "com.fibaro.motionSensor": ["Motion", "mdi:run", "motion"], + "com.fibaro.doorSensor": ["Door", "mdi:window-open", "door"], + "com.fibaro.windowSensor": ["Window", "mdi:window-open", "window"], + "com.fibaro.smokeSensor": ["Smoke", "mdi:smoking", "smoke"], + "com.fibaro.FGMS001": ["Motion", "mdi:run", "motion"], + "com.fibaro.heatDetector": ["Heat", "mdi:fire", "heat"], } @@ -26,8 +25,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return add_entities( - [FibaroBinarySensor(device) - for device in hass.data[FIBARO_DEVICES]['binary_sensor']], True) + [ + FibaroBinarySensor(device) + for device in hass.data[FIBARO_DEVICES]["binary_sensor"] + ], + True, + ) class FibaroBinarySensor(FibaroDevice, BinarySensorDevice): @@ -51,8 +54,7 @@ class FibaroBinarySensor(FibaroDevice, BinarySensorDevice): self._device_class = None self._icon = None # device_config overrides: - self._device_class = devconf.get(CONF_DEVICE_CLASS, - self._device_class) + self._device_class = devconf.get(CONF_DEVICE_CLASS, self._device_class) self._icon = devconf.get(CONF_ICON, self._icon) @property diff --git a/homeassistant/components/fibaro/climate.py b/homeassistant/components/fibaro/climate.py index 6a4d5429618..4670a3f0c62 100644 --- a/homeassistant/components/fibaro/climate.py +++ b/homeassistant/components/fibaro/climate.py @@ -3,38 +3,47 @@ import logging from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( - HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, HVAC_MODE_OFF, PRESET_AWAY, PRESET_BOOST, SUPPORT_FAN_MODE, - SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE) + HVAC_MODE_AUTO, + HVAC_MODE_COOL, + HVAC_MODE_DRY, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_HEAT, + HVAC_MODE_OFF, + PRESET_AWAY, + PRESET_BOOST, + SUPPORT_FAN_MODE, + SUPPORT_PRESET_MODE, + SUPPORT_TARGET_TEMPERATURE, +) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT from . import FIBARO_DEVICES, FibaroDevice -PRESET_RESUME = 'resume' -PRESET_MOIST = 'moist' -PRESET_FURNACE = 'furnace' -PRESET_CHANGEOVER = 'changeover' -PRESET_ECO_HEAT = 'eco_heat' -PRESET_ECO_COOL = 'eco_cool' -PRESET_FORCE_OPEN = 'force_open' +PRESET_RESUME = "resume" +PRESET_MOIST = "moist" +PRESET_FURNACE = "furnace" +PRESET_CHANGEOVER = "changeover" +PRESET_ECO_HEAT = "eco_heat" +PRESET_ECO_COOL = "eco_cool" +PRESET_FORCE_OPEN = "force_open" _LOGGER = logging.getLogger(__name__) # SDS13781-10 Z-Wave Application Command Class Specification 2019-01-04 # Table 128, Thermostat Fan Mode Set version 4::Fan Mode encoding FANMODES = { - 0: 'off', - 1: 'low', - 2: 'auto_high', - 3: 'medium', - 4: 'auto_medium', - 5: 'high', - 6: 'circulation', - 7: 'humidity_circulation', - 8: 'left_right', - 9: 'up_down', - 10: 'quiet', - 128: 'auto' + 0: "off", + 1: "low", + 2: "auto_high", + 3: "medium", + 4: "auto_medium", + 5: "high", + 6: "circulation", + 7: "humidity_circulation", + 8: "left_right", + 9: "up_down", + 10: "quiet", + 128: "auto", } HA_FANMODES = {v: k for k, v in FANMODES.items()} @@ -90,8 +99,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return add_entities( - [FibaroThermostat(device) - for device in hass.data[FIBARO_DEVICES]['climate']], True) + [FibaroThermostat(device) for device in hass.data[FIBARO_DEVICES]["climate"]], + True, + ) class FibaroThermostat(FibaroDevice, ClimateDevice): @@ -105,39 +115,40 @@ class FibaroThermostat(FibaroDevice, ClimateDevice): self._op_mode_device = None self._fan_mode_device = None self._support_flags = 0 - self.entity_id = 'climate.{}'.format(self.ha_id) + self.entity_id = "climate.{}".format(self.ha_id) self._hvac_support = [] self._preset_support = [] self._fan_support = [] - siblings = fibaro_device.fibaro_controller.get_siblings( - fibaro_device.id) - tempunit = 'C' + siblings = fibaro_device.fibaro_controller.get_siblings(fibaro_device.id) + tempunit = "C" for device in siblings: - if device.type == 'com.fibaro.temperatureSensor': + if device.type == "com.fibaro.temperatureSensor": self._temp_sensor_device = FibaroDevice(device) tempunit = device.properties.unit - if 'setTargetLevel' in device.actions or \ - 'setThermostatSetpoint' in device.actions: + if ( + "setTargetLevel" in device.actions + or "setThermostatSetpoint" in device.actions + ): self._target_temp_device = FibaroDevice(device) self._support_flags |= SUPPORT_TARGET_TEMPERATURE tempunit = device.properties.unit - if 'setMode' in device.actions or \ - 'setOperatingMode' in device.actions: + if "setMode" in device.actions or "setOperatingMode" in device.actions: self._op_mode_device = FibaroDevice(device) self._support_flags |= SUPPORT_PRESET_MODE - if 'setFanMode' in device.actions: + if "setFanMode" in device.actions: self._fan_mode_device = FibaroDevice(device) self._support_flags |= SUPPORT_FAN_MODE - if tempunit == 'F': + if tempunit == "F": self._unit_of_temp = TEMP_FAHRENHEIT else: self._unit_of_temp = TEMP_CELSIUS if self._fan_mode_device: - fan_modes = self._fan_mode_device.fibaro_device.\ - properties.supportedModes.split(",") + fan_modes = self._fan_mode_device.fibaro_device.properties.supportedModes.split( + "," + ) for mode in fan_modes: mode = int(mode) if mode not in FANMODES: @@ -162,29 +173,27 @@ class FibaroThermostat(FibaroDevice, ClimateDevice): async def async_added_to_hass(self): """Call when entity is added to hass.""" - _LOGGER.debug("Climate %s\n" - "- _temp_sensor_device %s\n" - "- _target_temp_device %s\n" - "- _op_mode_device %s\n" - "- _fan_mode_device %s", - self.ha_id, - self._temp_sensor_device.ha_id - if self._temp_sensor_device else "None", - self._target_temp_device.ha_id - if self._target_temp_device else "None", - self._op_mode_device.ha_id - if self._op_mode_device else "None", - self._fan_mode_device.ha_id - if self._fan_mode_device else "None") + _LOGGER.debug( + "Climate %s\n" + "- _temp_sensor_device %s\n" + "- _target_temp_device %s\n" + "- _op_mode_device %s\n" + "- _fan_mode_device %s", + self.ha_id, + self._temp_sensor_device.ha_id if self._temp_sensor_device else "None", + self._target_temp_device.ha_id if self._target_temp_device else "None", + self._op_mode_device.ha_id if self._op_mode_device else "None", + self._fan_mode_device.ha_id if self._fan_mode_device else "None", + ) await super().async_added_to_hass() # Register update callback for child devices siblings = self.fibaro_device.fibaro_controller.get_siblings( - self.fibaro_device.id) + self.fibaro_device.id + ) for device in siblings: if device != self.fibaro_device: - self.controller.register(device.id, - self._update_callback) + self.controller.register(device.id, self._update_callback) @property def supported_features(self): @@ -219,8 +228,7 @@ class FibaroThermostat(FibaroDevice, ClimateDevice): return 6 # Fan only if "operatingMode" in self._op_mode_device.fibaro_device.properties: - return int(self._op_mode_device.fibaro_device. - properties.operatingMode) + return int(self._op_mode_device.fibaro_device.properties.operatingMode) return int(self._op_mode_device.fibaro_device.properties.mode) @@ -244,8 +252,7 @@ class FibaroThermostat(FibaroDevice, ClimateDevice): return if "setOperatingMode" in self._op_mode_device.fibaro_device.actions: - self._op_mode_device.action( - "setOperatingMode", HA_OPMODES_HVAC[hvac_mode]) + self._op_mode_device.action("setOperatingMode", HA_OPMODES_HVAC[hvac_mode]) elif "setMode" in self._op_mode_device.fibaro_device.actions: self._op_mode_device.action("setMode", HA_OPMODES_HVAC[hvac_mode]) @@ -259,8 +266,7 @@ class FibaroThermostat(FibaroDevice, ClimateDevice): return None if "operatingMode" in self._op_mode_device.fibaro_device.properties: - mode = int(self._op_mode_device.fibaro_device. - properties.operatingMode) + mode = int(self._op_mode_device.fibaro_device.properties.operatingMode) else: mode = int(self._op_mode_device.fibaro_device.properties.mode) @@ -284,10 +290,10 @@ class FibaroThermostat(FibaroDevice, ClimateDevice): return if "setOperatingMode" in self._op_mode_device.fibaro_device.actions: self._op_mode_device.action( - "setOperatingMode", HA_OPMODES_PRESET[preset_mode]) + "setOperatingMode", HA_OPMODES_PRESET[preset_mode] + ) elif "setMode" in self._op_mode_device.fibaro_device.actions: - self._op_mode_device.action( - "setMode", HA_OPMODES_PRESET[preset_mode]) + self._op_mode_device.action("setMode", HA_OPMODES_PRESET[preset_mode]) @property def temperature_unit(self): @@ -316,7 +322,6 @@ class FibaroThermostat(FibaroDevice, ClimateDevice): target = self._target_temp_device if temperature is not None: if "setThermostatSetpoint" in target.fibaro_device.actions: - target.action("setThermostatSetpoint", - self.fibaro_op_mode, temperature) + target.action("setThermostatSetpoint", self.fibaro_op_mode, temperature) else: target.action("setTargetLevel", temperature) diff --git a/homeassistant/components/fibaro/cover.py b/homeassistant/components/fibaro/cover.py index 0ccbed0144b..fe9c0990fa8 100644 --- a/homeassistant/components/fibaro/cover.py +++ b/homeassistant/components/fibaro/cover.py @@ -2,7 +2,11 @@ import logging from homeassistant.components.cover import ( - ATTR_POSITION, ATTR_TILT_POSITION, ENTITY_ID_FORMAT, CoverDevice) + ATTR_POSITION, + ATTR_TILT_POSITION, + ENTITY_ID_FORMAT, + CoverDevice, +) from . import FIBARO_DEVICES, FibaroDevice @@ -15,8 +19,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return add_entities( - [FibaroCover(device) for - device in hass.data[FIBARO_DEVICES]['cover']], True) + [FibaroCover(device) for device in hass.data[FIBARO_DEVICES]["cover"]], True + ) class FibaroCover(FibaroDevice, CoverDevice): diff --git a/homeassistant/components/fibaro/light.py b/homeassistant/components/fibaro/light.py index a741de972f0..ba77942a448 100644 --- a/homeassistant/components/fibaro/light.py +++ b/homeassistant/components/fibaro/light.py @@ -4,13 +4,19 @@ from functools import partial import logging from homeassistant.components.light import ( - ATTR_BRIGHTNESS, ATTR_HS_COLOR, ATTR_WHITE_VALUE, ENTITY_ID_FORMAT, - SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_WHITE_VALUE, Light) + ATTR_BRIGHTNESS, + ATTR_HS_COLOR, + ATTR_WHITE_VALUE, + ENTITY_ID_FORMAT, + SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, + SUPPORT_WHITE_VALUE, + Light, +) from homeassistant.const import CONF_WHITE_VALUE import homeassistant.util.color as color_util -from . import ( - CONF_COLOR, CONF_DIMMING, CONF_RESET_COLOR, FIBARO_DEVICES, FibaroDevice) +from . import CONF_COLOR, CONF_DIMMING, CONF_RESET_COLOR, FIBARO_DEVICES, FibaroDevice _LOGGER = logging.getLogger(__name__) @@ -32,17 +38,14 @@ def scaleto100(value): return max(0, min(100, ((value * 100.0) / 255.0))) -async def async_setup_platform(hass, - config, - async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Perform the setup for Fibaro controller devices.""" if discovery_info is None: return async_add_entities( - [FibaroLight(device) - for device in hass.data[FIBARO_DEVICES]['light']], True) + [FibaroLight(device) for device in hass.data[FIBARO_DEVICES]["light"]], True + ) class FibaroLight(FibaroDevice, Light): @@ -59,10 +62,11 @@ class FibaroLight(FibaroDevice, Light): devconf = fibaro_device.device_config self._reset_color = devconf.get(CONF_RESET_COLOR, False) - supports_color = 'color' in fibaro_device.properties and \ - 'setColor' in fibaro_device.actions - supports_dimming = 'levelChange' in fibaro_device.interfaces - supports_white_v = 'setW' in fibaro_device.actions + supports_color = ( + "color" in fibaro_device.properties and "setColor" in fibaro_device.actions + ) + supports_dimming = "levelChange" in fibaro_device.interfaces + supports_white_v = "setW" in fibaro_device.actions # Configuration can overrride default capability detection if devconf.get(CONF_DIMMING, supports_dimming): @@ -98,8 +102,7 @@ class FibaroLight(FibaroDevice, Light): async def async_turn_on(self, **kwargs): """Turn the light on.""" async with self._update_lock: - await self.hass.async_add_executor_job( - partial(self._turn_on, **kwargs)) + await self.hass.async_add_executor_job(partial(self._turn_on, **kwargs)) def _turn_on(self, **kwargs): """Really turn the light on.""" @@ -119,10 +122,12 @@ class FibaroLight(FibaroDevice, Light): self._brightness = scaleto100(target_brightness) if self._supported_flags & SUPPORT_COLOR: - if self._reset_color and \ - kwargs.get(ATTR_WHITE_VALUE) is None and \ - kwargs.get(ATTR_HS_COLOR) is None and \ - kwargs.get(ATTR_BRIGHTNESS) is None: + if ( + self._reset_color + and kwargs.get(ATTR_WHITE_VALUE) is None + and kwargs.get(ATTR_HS_COLOR) is None + and kwargs.get(ATTR_BRIGHTNESS) is None + ): self._color = (100, 0) # Update based on parameters @@ -133,9 +138,10 @@ class FibaroLight(FibaroDevice, Light): round(rgb[0] * self._brightness / 100.0), round(rgb[1] * self._brightness / 100.0), round(rgb[2] * self._brightness / 100.0), - round(self._white * self._brightness / 100.0)) + round(self._white * self._brightness / 100.0), + ) - if self.state == 'off': + if self.state == "off": self.set_level(int(self._brightness)) return @@ -153,14 +159,16 @@ class FibaroLight(FibaroDevice, Light): async def async_turn_off(self, **kwargs): """Turn the light off.""" async with self._update_lock: - await self.hass.async_add_executor_job( - partial(self._turn_off, **kwargs)) + await self.hass.async_add_executor_job(partial(self._turn_off, **kwargs)) def _turn_off(self, **kwargs): """Really turn the light off.""" # Let's save the last brightness level before we switch it off - if (self._supported_flags & SUPPORT_BRIGHTNESS) and \ - self._brightness and self._brightness > 0: + if ( + (self._supported_flags & SUPPORT_BRIGHTNESS) + and self._brightness + and self._brightness > 0 + ): self._last_brightness = self._brightness self._brightness = 0 self.call_turn_off() @@ -185,18 +193,17 @@ class FibaroLight(FibaroDevice, Light): if self._brightness > 99: self._brightness = 100 # Color handling - if self._supported_flags & SUPPORT_COLOR and \ - 'color' in self.fibaro_device.properties and \ - ',' in self.fibaro_device.properties.color: + if ( + self._supported_flags & SUPPORT_COLOR + and "color" in self.fibaro_device.properties + and "," in self.fibaro_device.properties.color + ): # Fibaro communicates the color as an 'R, G, B, W' string rgbw_s = self.fibaro_device.properties.color - if rgbw_s == '0,0,0,0' and\ - 'lastColorSet' in self.fibaro_device.properties: + if rgbw_s == "0,0,0,0" and "lastColorSet" in self.fibaro_device.properties: rgbw_s = self.fibaro_device.properties.lastColorSet rgbw_list = [int(i) for i in rgbw_s.split(",")][:4] if rgbw_list[0] or rgbw_list[1] or rgbw_list[2]: self._color = color_util.color_RGB_to_hs(*rgbw_list[:3]) - if (self._supported_flags & SUPPORT_WHITE_VALUE) and \ - self.brightness != 0: - self._white = min(255, max(0, rgbw_list[3]*100.0 / - self._brightness)) + if (self._supported_flags & SUPPORT_WHITE_VALUE) and self.brightness != 0: + self._white = min(255, max(0, rgbw_list[3] * 100.0 / self._brightness)) diff --git a/homeassistant/components/fibaro/scene.py b/homeassistant/components/fibaro/scene.py index f9f96844319..06d11bc1f5c 100644 --- a/homeassistant/components/fibaro/scene.py +++ b/homeassistant/components/fibaro/scene.py @@ -8,15 +8,14 @@ from . import FIBARO_DEVICES, FibaroDevice _LOGGER = logging.getLogger(__name__) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Perform the setup for Fibaro scenes.""" if discovery_info is None: return async_add_entities( - [FibaroScene(scene) - for scene in hass.data[FIBARO_DEVICES]['scene']], True) + [FibaroScene(scene) for scene in hass.data[FIBARO_DEVICES]["scene"]], True + ) class FibaroScene(FibaroDevice, Scene): diff --git a/homeassistant/components/fibaro/sensor.py b/homeassistant/components/fibaro/sensor.py index db9d103d87e..1e0bae212f8 100644 --- a/homeassistant/components/fibaro/sensor.py +++ b/homeassistant/components/fibaro/sensor.py @@ -3,23 +3,27 @@ import logging from homeassistant.components.sensor import ENTITY_ID_FORMAT from homeassistant.const import ( - DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, - TEMP_CELSIUS, TEMP_FAHRENHEIT) + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_ILLUMINANCE, + DEVICE_CLASS_TEMPERATURE, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, +) from homeassistant.helpers.entity import Entity from . import FIBARO_DEVICES, FibaroDevice SENSOR_TYPES = { - 'com.fibaro.temperatureSensor': - ['Temperature', None, None, DEVICE_CLASS_TEMPERATURE], - 'com.fibaro.smokeSensor': - ['Smoke', 'ppm', 'mdi:fire', None], - 'CO2': - ['CO2', 'ppm', 'mdi:cloud', None], - 'com.fibaro.humiditySensor': - ['Humidity', '%', None, DEVICE_CLASS_HUMIDITY], - 'com.fibaro.lightSensor': - ['Light', 'lx', None, DEVICE_CLASS_ILLUMINANCE] + "com.fibaro.temperatureSensor": [ + "Temperature", + None, + None, + DEVICE_CLASS_TEMPERATURE, + ], + "com.fibaro.smokeSensor": ["Smoke", "ppm", "mdi:fire", None], + "CO2": ["CO2", "ppm", "mdi:cloud", None], + "com.fibaro.humiditySensor": ["Humidity", "%", None, DEVICE_CLASS_HUMIDITY], + "com.fibaro.lightSensor": ["Light", "lx", None, DEVICE_CLASS_ILLUMINANCE], } _LOGGER = logging.getLogger(__name__) @@ -31,8 +35,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return add_entities( - [FibaroSensor(device) - for device in hass.data[FIBARO_DEVICES]['sensor']], True) + [FibaroSensor(device) for device in hass.data[FIBARO_DEVICES]["sensor"]], True + ) class FibaroSensor(FibaroDevice, Entity): @@ -54,11 +58,11 @@ class FibaroSensor(FibaroDevice, Entity): self._device_class = None try: if not self._unit: - if self.fibaro_device.properties.unit == 'lux': - self._unit = 'lx' - elif self.fibaro_device.properties.unit == 'C': + if self.fibaro_device.properties.unit == "lux": + self._unit = "lx" + elif self.fibaro_device.properties.unit == "C": self._unit = TEMP_CELSIUS - elif self.fibaro_device.properties.unit == 'F': + elif self.fibaro_device.properties.unit == "F": self._unit = TEMP_FAHRENHEIT else: self._unit = self.fibaro_device.properties.unit diff --git a/homeassistant/components/fibaro/switch.py b/homeassistant/components/fibaro/switch.py index f134b424484..4bb8c34d579 100644 --- a/homeassistant/components/fibaro/switch.py +++ b/homeassistant/components/fibaro/switch.py @@ -15,8 +15,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return add_entities( - [FibaroSwitch(device) for - device in hass.data[FIBARO_DEVICES]['switch']], True) + [FibaroSwitch(device) for device in hass.data[FIBARO_DEVICES]["switch"]], True + ) class FibaroSwitch(FibaroDevice, SwitchDevice): @@ -41,14 +41,14 @@ class FibaroSwitch(FibaroDevice, SwitchDevice): @property def current_power_w(self): """Return the current power usage in W.""" - if 'power' in self.fibaro_device.interfaces: + if "power" in self.fibaro_device.interfaces: return convert(self.fibaro_device.properties.power, float, 0.0) return None @property def today_energy_kwh(self): """Return the today total energy usage in kWh.""" - if 'energy' in self.fibaro_device.interfaces: + if "energy" in self.fibaro_device.interfaces: return convert(self.fibaro_device.properties.energy, float, 0.0) return None diff --git a/homeassistant/components/fido/sensor.py b/homeassistant/components/fido/sensor.py index ea66acaf808..e556903638c 100644 --- a/homeassistant/components/fido/sensor.py +++ b/homeassistant/components/fido/sensor.py @@ -14,61 +14,63 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_USERNAME, CONF_PASSWORD, - CONF_NAME, CONF_MONITORED_VARIABLES) + CONF_USERNAME, + CONF_PASSWORD, + CONF_NAME, + CONF_MONITORED_VARIABLES, +) from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -KILOBITS = 'Kb' # type: str -PRICE = 'CAD' # type: str -MESSAGES = 'messages' # type: str -MINUTES = 'minutes' # type: str +KILOBITS = "Kb" # type: str +PRICE = "CAD" # type: str +MESSAGES = "messages" # type: str +MINUTES = "minutes" # type: str -DEFAULT_NAME = 'Fido' +DEFAULT_NAME = "Fido" REQUESTS_TIMEOUT = 15 MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15) SENSOR_TYPES = { - 'fido_dollar': ['Fido dollar', PRICE, 'mdi:square-inc-cash'], - 'balance': ['Balance', PRICE, 'mdi:square-inc-cash'], - 'data_used': ['Data used', KILOBITS, 'mdi:download'], - 'data_limit': ['Data limit', KILOBITS, 'mdi:download'], - 'data_remaining': ['Data remaining', KILOBITS, 'mdi:download'], - 'text_used': ['Text used', MESSAGES, 'mdi:message-text'], - 'text_limit': ['Text limit', MESSAGES, 'mdi:message-text'], - 'text_remaining': ['Text remaining', MESSAGES, 'mdi:message-text'], - 'mms_used': ['MMS used', MESSAGES, 'mdi:message-image'], - 'mms_limit': ['MMS limit', MESSAGES, 'mdi:message-image'], - 'mms_remaining': ['MMS remaining', MESSAGES, 'mdi:message-image'], - 'text_int_used': ['International text used', - MESSAGES, 'mdi:message-alert'], - 'text_int_limit': ['International text limit', - MESSAGES, 'mdi:message-alert'], - 'text_int_remaining': ['International remaining', - MESSAGES, 'mdi:message-alert'], - 'talk_used': ['Talk used', MINUTES, 'mdi:cellphone'], - 'talk_limit': ['Talk limit', MINUTES, 'mdi:cellphone'], - 'talk_remaining': ['Talk remaining', MINUTES, 'mdi:cellphone'], - 'other_talk_used': ['Other Talk used', MINUTES, 'mdi:cellphone'], - 'other_talk_limit': ['Other Talk limit', MINUTES, 'mdi:cellphone'], - 'other_talk_remaining': ['Other Talk remaining', MINUTES, 'mdi:cellphone'], + "fido_dollar": ["Fido dollar", PRICE, "mdi:square-inc-cash"], + "balance": ["Balance", PRICE, "mdi:square-inc-cash"], + "data_used": ["Data used", KILOBITS, "mdi:download"], + "data_limit": ["Data limit", KILOBITS, "mdi:download"], + "data_remaining": ["Data remaining", KILOBITS, "mdi:download"], + "text_used": ["Text used", MESSAGES, "mdi:message-text"], + "text_limit": ["Text limit", MESSAGES, "mdi:message-text"], + "text_remaining": ["Text remaining", MESSAGES, "mdi:message-text"], + "mms_used": ["MMS used", MESSAGES, "mdi:message-image"], + "mms_limit": ["MMS limit", MESSAGES, "mdi:message-image"], + "mms_remaining": ["MMS remaining", MESSAGES, "mdi:message-image"], + "text_int_used": ["International text used", MESSAGES, "mdi:message-alert"], + "text_int_limit": ["International text limit", MESSAGES, "mdi:message-alert"], + "text_int_remaining": ["International remaining", MESSAGES, "mdi:message-alert"], + "talk_used": ["Talk used", MINUTES, "mdi:cellphone"], + "talk_limit": ["Talk limit", MINUTES, "mdi:cellphone"], + "talk_remaining": ["Talk remaining", MINUTES, "mdi:cellphone"], + "other_talk_used": ["Other Talk used", MINUTES, "mdi:cellphone"], + "other_talk_limit": ["Other Talk limit", MINUTES, "mdi:cellphone"], + "other_talk_remaining": ["Other Talk remaining", MINUTES, "mdi:cellphone"], } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_MONITORED_VARIABLES): - vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_MONITORED_VARIABLES): vol.All( + cv.ensure_list, [vol.In(SENSOR_TYPES)] + ), + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + } +) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Fido sensor.""" username = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) @@ -106,7 +108,7 @@ class FidoSensor(Entity): @property def name(self): """Return the name of the sensor.""" - return '{} {} {}'.format(self.client_name, self._number, self._name) + return "{} {} {}".format(self.client_name, self._number, self._name) @property def state(self): @@ -126,19 +128,16 @@ class FidoSensor(Entity): @property def device_state_attributes(self): """Return the state attributes of the sensor.""" - return { - 'number': self._number, - } + return {"number": self._number} async def async_update(self): """Get the latest data from Fido and update the state.""" await self.fido_data.async_update() - if self.type == 'balance': + if self.type == "balance": if self.fido_data.data.get(self.type) is not None: self._state = round(self.fido_data.data[self.type], 2) else: - if self.fido_data.data.get(self._number, {}).get(self.type) \ - is not None: + if self.fido_data.data.get(self._number, {}).get(self.type) is not None: self._state = self.fido_data.data[self._number][self.type] self._state = round(self._state, 2) @@ -149,14 +148,15 @@ class FidoData: def __init__(self, username, password, httpsession): """Initialize the data object.""" from pyfido import FidoClient - self.client = FidoClient(username, password, - REQUESTS_TIMEOUT, httpsession) + + self.client = FidoClient(username, password, REQUESTS_TIMEOUT, httpsession) self.data = {} @Throttle(MIN_TIME_BETWEEN_UPDATES) async def async_update(self): """Get the latest data from Fido.""" from pyfido.client import PyFidoError + try: await self.client.fetch_data() except PyFidoError as exp: diff --git a/homeassistant/components/file/notify.py b/homeassistant/components/file/notify.py index 07718dcf36c..f4d31a5fd6f 100644 --- a/homeassistant/components/file/notify.py +++ b/homeassistant/components/file/notify.py @@ -9,14 +9,20 @@ import homeassistant.helpers.config_validation as cv import homeassistant.util.dt as dt_util from homeassistant.components.notify import ( - ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, BaseNotificationService) + ATTR_TITLE, + ATTR_TITLE_DEFAULT, + PLATFORM_SCHEMA, + BaseNotificationService, +) -CONF_TIMESTAMP = 'timestamp' +CONF_TIMESTAMP = "timestamp" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_FILENAME): cv.string, - vol.Optional(CONF_TIMESTAMP, default=False): cv.boolean, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_FILENAME): cv.string, + vol.Optional(CONF_TIMESTAMP, default=False): cv.boolean, + } +) _LOGGER = logging.getLogger(__name__) @@ -39,16 +45,17 @@ class FileNotificationService(BaseNotificationService): def send_message(self, message="", **kwargs): """Send a message to a file.""" - with open(self.filepath, 'a') as file: + with open(self.filepath, "a") as file: if os.stat(self.filepath).st_size == 0: - title = '{} notifications (Log started: {})\n{}\n'.format( + title = "{} notifications (Log started: {})\n{}\n".format( kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT), dt_util.utcnow().isoformat(), - '-' * 80) + "-" * 80, + ) file.write(title) if self.add_timestamp: - text = '{} {}\n'.format(dt_util.utcnow().isoformat(), message) + text = "{} {}\n".format(dt_util.utcnow().isoformat(), message) else: - text = '{}\n'.format(message) + text = "{}\n".format(message) file.write(text) diff --git a/homeassistant/components/file/sensor.py b/homeassistant/components/file/sensor.py index a618c1e56dc..60f04b18f24 100644 --- a/homeassistant/components/file/sensor.py +++ b/homeassistant/components/file/sensor.py @@ -6,28 +6,28 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ( - CONF_VALUE_TEMPLATE, CONF_NAME, CONF_UNIT_OF_MEASUREMENT) +from homeassistant.const import CONF_VALUE_TEMPLATE, CONF_NAME, CONF_UNIT_OF_MEASUREMENT from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -CONF_FILE_PATH = 'file_path' +CONF_FILE_PATH = "file_path" -DEFAULT_NAME = 'File' +DEFAULT_NAME = "File" -ICON = 'mdi:file' +ICON = "mdi:file" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_FILE_PATH): cv.isfile, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_VALUE_TEMPLATE): cv.template, - vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_FILE_PATH): cv.isfile, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, + } +) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the file sensor.""" file_path = config.get(CONF_FILE_PATH) name = config.get(CONF_NAME) @@ -38,8 +38,7 @@ async def async_setup_platform(hass, config, async_add_entities, value_template.hass = hass if hass.config.is_allowed_path(file_path): - async_add_entities( - [FileSensor(name, file_path, unit, value_template)], True) + async_add_entities([FileSensor(name, file_path, unit, value_template)], True) else: _LOGGER.error("'%s' is not a whitelisted directory", file_path) @@ -78,18 +77,20 @@ class FileSensor(Entity): def update(self): """Get the latest entry from a file and updates the state.""" try: - with open(self._file_path, 'r', encoding='utf-8') as file_data: + with open(self._file_path, "r", encoding="utf-8") as file_data: for line in file_data: data = line data = data.strip() - except (IndexError, FileNotFoundError, IsADirectoryError, - UnboundLocalError): - _LOGGER.warning("File or data not present at the moment: %s", - os.path.basename(self._file_path)) + except (IndexError, FileNotFoundError, IsADirectoryError, UnboundLocalError): + _LOGGER.warning( + "File or data not present at the moment: %s", + os.path.basename(self._file_path), + ) return if self._val_tpl is not None: self._state = self._val_tpl.async_render_with_possible_json_value( - data, None) + data, None + ) else: self._state = data diff --git a/homeassistant/components/filesize/sensor.py b/homeassistant/components/filesize/sensor.py index 3e1394c72d6..a4b9bc5cd76 100644 --- a/homeassistant/components/filesize/sensor.py +++ b/homeassistant/components/filesize/sensor.py @@ -12,13 +12,12 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA _LOGGER = logging.getLogger(__name__) -CONF_FILE_PATHS = 'file_paths' -ICON = 'mdi:file' +CONF_FILE_PATHS = "file_paths" +ICON = "mdi:file" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_FILE_PATHS): - vol.All(cv.ensure_list, [cv.isfile]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_FILE_PATHS): vol.All(cv.ensure_list, [cv.isfile])} +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -26,8 +25,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): sensors = [] for path in config.get(CONF_FILE_PATHS): if not hass.config.is_allowed_path(path): - _LOGGER.error( - "Filepath %s is not valid or allowed", path) + _LOGGER.error("Filepath %s is not valid or allowed", path) continue else: sensors.append(Filesize(path)) @@ -41,11 +39,11 @@ class Filesize(Entity): def __init__(self, path): """Initialize the data object.""" - self._path = path # Need to check its a valid path + self._path = path # Need to check its a valid path self._size = None self._last_updated = None self._name = path.split("/")[-1] - self._unit_of_measurement = 'MB' + self._unit_of_measurement = "MB" def update(self): """Update the sensor.""" @@ -63,7 +61,7 @@ class Filesize(Entity): def state(self): """Return the size of the file in MB.""" decimals = 2 - state_mb = round(self._size/1e6, decimals) + state_mb = round(self._size / 1e6, decimals) return state_mb @property @@ -75,10 +73,10 @@ class Filesize(Entity): def device_state_attributes(self): """Return other details about the sensor state.""" attr = { - 'path': self._path, - 'last_updated': self._last_updated, - 'bytes': self._size, - } + "path": self._path, + "last_updated": self._last_updated, + "bytes": self._size, + } return attr @property diff --git a/homeassistant/components/filter/sensor.py b/homeassistant/components/filter/sensor.py index 734caa31270..b1ce967d6cd 100644 --- a/homeassistant/components/filter/sensor.py +++ b/homeassistant/components/filter/sensor.py @@ -12,8 +12,14 @@ import voluptuous as vol from homeassistant.core import callback from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_NAME, CONF_ENTITY_ID, ATTR_UNIT_OF_MEASUREMENT, ATTR_ENTITY_ID, - ATTR_ICON, STATE_UNKNOWN, STATE_UNAVAILABLE) + CONF_NAME, + CONF_ENTITY_ID, + ATTR_UNIT_OF_MEASUREMENT, + ATTR_ENTITY_ID, + ATTR_ICON, + STATE_UNKNOWN, + STATE_UNAVAILABLE, +) import homeassistant.helpers.config_validation as cv from homeassistant.util.decorator import Registry from homeassistant.helpers.entity import Entity @@ -23,25 +29,25 @@ import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) -FILTER_NAME_RANGE = 'range' -FILTER_NAME_LOWPASS = 'lowpass' -FILTER_NAME_OUTLIER = 'outlier' -FILTER_NAME_THROTTLE = 'throttle' -FILTER_NAME_TIME_THROTTLE = 'time_throttle' -FILTER_NAME_TIME_SMA = 'time_simple_moving_average' +FILTER_NAME_RANGE = "range" +FILTER_NAME_LOWPASS = "lowpass" +FILTER_NAME_OUTLIER = "outlier" +FILTER_NAME_THROTTLE = "throttle" +FILTER_NAME_TIME_THROTTLE = "time_throttle" +FILTER_NAME_TIME_SMA = "time_simple_moving_average" FILTERS = Registry() -CONF_FILTERS = 'filters' -CONF_FILTER_NAME = 'filter' -CONF_FILTER_WINDOW_SIZE = 'window_size' -CONF_FILTER_PRECISION = 'precision' -CONF_FILTER_RADIUS = 'radius' -CONF_FILTER_TIME_CONSTANT = 'time_constant' -CONF_FILTER_LOWER_BOUND = 'lower_bound' -CONF_FILTER_UPPER_BOUND = 'upper_bound' -CONF_TIME_SMA_TYPE = 'type' +CONF_FILTERS = "filters" +CONF_FILTER_NAME = "filter" +CONF_FILTER_WINDOW_SIZE = "window_size" +CONF_FILTER_PRECISION = "precision" +CONF_FILTER_RADIUS = "radius" +CONF_FILTER_TIME_CONSTANT = "time_constant" +CONF_FILTER_LOWER_BOUND = "lower_bound" +CONF_FILTER_UPPER_BOUND = "upper_bound" +CONF_TIME_SMA_TYPE = "type" -TIME_SMA_LAST = 'last' +TIME_SMA_LAST = "last" WINDOW_SIZE_UNIT_NUMBER_EVENTS = 1 WINDOW_SIZE_UNIT_TIME = 2 @@ -52,79 +58,104 @@ DEFAULT_FILTER_RADIUS = 2.0 DEFAULT_FILTER_TIME_CONSTANT = 10 NAME_TEMPLATE = "{} filter" -ICON = 'mdi:chart-line-variant' +ICON = "mdi:chart-line-variant" -FILTER_SCHEMA = vol.Schema({ - vol.Optional(CONF_FILTER_PRECISION, - default=DEFAULT_PRECISION): vol.Coerce(int), -}) +FILTER_SCHEMA = vol.Schema( + {vol.Optional(CONF_FILTER_PRECISION, default=DEFAULT_PRECISION): vol.Coerce(int)} +) -FILTER_OUTLIER_SCHEMA = FILTER_SCHEMA.extend({ - vol.Required(CONF_FILTER_NAME): FILTER_NAME_OUTLIER, - vol.Optional(CONF_FILTER_WINDOW_SIZE, - default=DEFAULT_WINDOW_SIZE): vol.Coerce(int), - vol.Optional(CONF_FILTER_RADIUS, - default=DEFAULT_FILTER_RADIUS): vol.Coerce(float), -}) +FILTER_OUTLIER_SCHEMA = FILTER_SCHEMA.extend( + { + vol.Required(CONF_FILTER_NAME): FILTER_NAME_OUTLIER, + vol.Optional(CONF_FILTER_WINDOW_SIZE, default=DEFAULT_WINDOW_SIZE): vol.Coerce( + int + ), + vol.Optional(CONF_FILTER_RADIUS, default=DEFAULT_FILTER_RADIUS): vol.Coerce( + float + ), + } +) -FILTER_LOWPASS_SCHEMA = FILTER_SCHEMA.extend({ - vol.Required(CONF_FILTER_NAME): FILTER_NAME_LOWPASS, - vol.Optional(CONF_FILTER_WINDOW_SIZE, - default=DEFAULT_WINDOW_SIZE): vol.Coerce(int), - vol.Optional(CONF_FILTER_TIME_CONSTANT, - default=DEFAULT_FILTER_TIME_CONSTANT): vol.Coerce(int), -}) +FILTER_LOWPASS_SCHEMA = FILTER_SCHEMA.extend( + { + vol.Required(CONF_FILTER_NAME): FILTER_NAME_LOWPASS, + vol.Optional(CONF_FILTER_WINDOW_SIZE, default=DEFAULT_WINDOW_SIZE): vol.Coerce( + int + ), + vol.Optional( + CONF_FILTER_TIME_CONSTANT, default=DEFAULT_FILTER_TIME_CONSTANT + ): vol.Coerce(int), + } +) -FILTER_RANGE_SCHEMA = vol.Schema({ - vol.Required(CONF_FILTER_NAME): FILTER_NAME_RANGE, - vol.Optional(CONF_FILTER_LOWER_BOUND): vol.Coerce(float), - vol.Optional(CONF_FILTER_UPPER_BOUND): vol.Coerce(float), -}) +FILTER_RANGE_SCHEMA = vol.Schema( + { + vol.Required(CONF_FILTER_NAME): FILTER_NAME_RANGE, + vol.Optional(CONF_FILTER_LOWER_BOUND): vol.Coerce(float), + vol.Optional(CONF_FILTER_UPPER_BOUND): vol.Coerce(float), + } +) -FILTER_TIME_SMA_SCHEMA = FILTER_SCHEMA.extend({ - vol.Required(CONF_FILTER_NAME): FILTER_NAME_TIME_SMA, - vol.Optional(CONF_TIME_SMA_TYPE, - default=TIME_SMA_LAST): vol.In( - [TIME_SMA_LAST]), +FILTER_TIME_SMA_SCHEMA = FILTER_SCHEMA.extend( + { + vol.Required(CONF_FILTER_NAME): FILTER_NAME_TIME_SMA, + vol.Optional(CONF_TIME_SMA_TYPE, default=TIME_SMA_LAST): vol.In( + [TIME_SMA_LAST] + ), + vol.Required(CONF_FILTER_WINDOW_SIZE): vol.All( + cv.time_period, cv.positive_timedelta + ), + } +) - vol.Required(CONF_FILTER_WINDOW_SIZE): vol.All(cv.time_period, - cv.positive_timedelta) -}) +FILTER_THROTTLE_SCHEMA = FILTER_SCHEMA.extend( + { + vol.Required(CONF_FILTER_NAME): FILTER_NAME_THROTTLE, + vol.Optional(CONF_FILTER_WINDOW_SIZE, default=DEFAULT_WINDOW_SIZE): vol.Coerce( + int + ), + } +) -FILTER_THROTTLE_SCHEMA = FILTER_SCHEMA.extend({ - vol.Required(CONF_FILTER_NAME): FILTER_NAME_THROTTLE, - vol.Optional(CONF_FILTER_WINDOW_SIZE, - default=DEFAULT_WINDOW_SIZE): vol.Coerce(int), -}) +FILTER_TIME_THROTTLE_SCHEMA = FILTER_SCHEMA.extend( + { + vol.Required(CONF_FILTER_NAME): FILTER_NAME_TIME_THROTTLE, + vol.Required(CONF_FILTER_WINDOW_SIZE): vol.All( + cv.time_period, cv.positive_timedelta + ), + } +) -FILTER_TIME_THROTTLE_SCHEMA = FILTER_SCHEMA.extend({ - vol.Required(CONF_FILTER_NAME): FILTER_NAME_TIME_THROTTLE, - vol.Required(CONF_FILTER_WINDOW_SIZE): vol.All(cv.time_period, - cv.positive_timedelta) -}) - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_ENTITY_ID): cv.entity_id, - vol.Optional(CONF_NAME): cv.string, - vol.Required(CONF_FILTERS): vol.All(cv.ensure_list, - [vol.Any(FILTER_OUTLIER_SCHEMA, - FILTER_LOWPASS_SCHEMA, - FILTER_TIME_SMA_SCHEMA, - FILTER_THROTTLE_SCHEMA, - FILTER_TIME_THROTTLE_SCHEMA, - FILTER_RANGE_SCHEMA)]) -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_ENTITY_ID): cv.entity_id, + vol.Optional(CONF_NAME): cv.string, + vol.Required(CONF_FILTERS): vol.All( + cv.ensure_list, + [ + vol.Any( + FILTER_OUTLIER_SCHEMA, + FILTER_LOWPASS_SCHEMA, + FILTER_TIME_SMA_SCHEMA, + FILTER_THROTTLE_SCHEMA, + FILTER_TIME_THROTTLE_SCHEMA, + FILTER_RANGE_SCHEMA, + ) + ], + ), + } +) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the template sensors.""" name = config.get(CONF_NAME) entity_id = config.get(CONF_ENTITY_ID) - filters = [FILTERS[_filter.pop(CONF_FILTER_NAME)]( - entity=entity_id, **_filter) - for _filter in config[CONF_FILTERS]] + filters = [ + FILTERS[_filter.pop(CONF_FILTER_NAME)](entity=entity_id, **_filter) + for _filter in config[CONF_FILTERS] + ] async_add_entities([SensorFilter(name, entity_id, filters)]) @@ -143,9 +174,9 @@ class SensorFilter(Entity): async def async_added_to_hass(self): """Register callbacks.""" + @callback - def filter_sensor_state_listener(entity, old_state, new_state, - update_ha=True): + def filter_sensor_state_listener(entity, old_state, new_state, update_ha=True): """Handle device state changes.""" if new_state.state in [STATE_UNKNOWN, STATE_UNAVAILABLE]: return @@ -155,76 +186,94 @@ class SensorFilter(Entity): try: for filt in self._filters: filtered_state = filt.filter_state(copy(temp_state)) - _LOGGER.debug("%s(%s=%s) -> %s", filt.name, - self._entity, - temp_state.state, - "skip" if filt.skip_processing else - filtered_state.state) + _LOGGER.debug( + "%s(%s=%s) -> %s", + filt.name, + self._entity, + temp_state.state, + "skip" if filt.skip_processing else filtered_state.state, + ) if filt.skip_processing: return temp_state = filtered_state except ValueError: - _LOGGER.error("Could not convert state: %s to number", - self._state) + _LOGGER.error("Could not convert state: %s to number", self._state) return self._state = temp_state.state if self._icon is None: - self._icon = new_state.attributes.get( - ATTR_ICON, ICON) + self._icon = new_state.attributes.get(ATTR_ICON, ICON) if self._unit_of_measurement is None: self._unit_of_measurement = new_state.attributes.get( - ATTR_UNIT_OF_MEASUREMENT) + ATTR_UNIT_OF_MEASUREMENT + ) if update_ha: self.async_schedule_update_ha_state() - if 'recorder' in self.hass.config.components: + if "recorder" in self.hass.config.components: history_list = [] largest_window_items = 0 largest_window_time = timedelta(0) # Determine the largest window_size by type for filt in self._filters: - if filt.window_unit == WINDOW_SIZE_UNIT_NUMBER_EVENTS\ - and largest_window_items < filt.window_size: + if ( + filt.window_unit == WINDOW_SIZE_UNIT_NUMBER_EVENTS + and largest_window_items < filt.window_size + ): largest_window_items = filt.window_size - elif filt.window_unit == WINDOW_SIZE_UNIT_TIME\ - and largest_window_time < filt.window_size: + elif ( + filt.window_unit == WINDOW_SIZE_UNIT_TIME + and largest_window_time < filt.window_size + ): largest_window_time = filt.window_size # Retrieve the largest window_size of each type if largest_window_items > 0: - filter_history = await self.hass.async_add_job(partial( - history.get_last_state_changes, self.hass, - largest_window_items, entity_id=self._entity)) - history_list.extend( - [state for state in filter_history[self._entity]]) + filter_history = await self.hass.async_add_job( + partial( + history.get_last_state_changes, + self.hass, + largest_window_items, + entity_id=self._entity, + ) + ) + history_list.extend([state for state in filter_history[self._entity]]) if largest_window_time > timedelta(seconds=0): start = dt_util.utcnow() - largest_window_time - filter_history = await self.hass.async_add_job(partial( - history.state_changes_during_period, self.hass, - start, entity_id=self._entity)) + filter_history = await self.hass.async_add_job( + partial( + history.state_changes_during_period, + self.hass, + start, + entity_id=self._entity, + ) + ) history_list.extend( - [state for state in filter_history[self._entity] - if state not in history_list]) + [ + state + for state in filter_history[self._entity] + if state not in history_list + ] + ) # Sort the window states history_list = sorted(history_list, key=lambda s: s.last_updated) - _LOGGER.debug("Loading from history: %s", - [(s.state, s.last_updated) for s in history_list]) + _LOGGER.debug( + "Loading from history: %s", + [(s.state, s.last_updated) for s in history_list], + ) # Replay history through the filter chain prev_state = None for state in history_list: - filter_sensor_state_listener( - self._entity, prev_state, state, False) + filter_sensor_state_listener(self._entity, prev_state, state, False) prev_state = state - async_track_state_change( - self.hass, self._entity, filter_sensor_state_listener) + async_track_state_change(self.hass, self._entity, filter_sensor_state_listener) @property def name(self): @@ -254,9 +303,7 @@ class SensorFilter(Entity): @property def device_state_attributes(self): """Return the state attributes of the sensor.""" - state_attr = { - ATTR_ENTITY_ID: self._entity - } + state_attr = {ATTR_ENTITY_ID: self._entity} return state_attr @@ -353,8 +400,7 @@ class RangeFilter(Filter): lower_bound (float): band lower bound """ - def __init__(self, entity, - lower_bound=None, upper_bound=None): + def __init__(self, entity, lower_bound=None, upper_bound=None): """Initialize Filter.""" super().__init__(FILTER_NAME_RANGE, entity=entity) self._lower_bound = lower_bound @@ -363,24 +409,28 @@ class RangeFilter(Filter): def _filter_state(self, new_state): """Implement the range filter.""" - if (self._upper_bound is not None - and new_state.state > self._upper_bound): + if self._upper_bound is not None and new_state.state > self._upper_bound: - self._stats_internal['erasures_up'] += 1 + self._stats_internal["erasures_up"] += 1 - _LOGGER.debug("Upper outlier nr. %s in %s: %s", - self._stats_internal['erasures_up'], - self._entity, new_state) + _LOGGER.debug( + "Upper outlier nr. %s in %s: %s", + self._stats_internal["erasures_up"], + self._entity, + new_state, + ) new_state.state = self._upper_bound - elif (self._lower_bound is not None - and new_state.state < self._lower_bound): + elif self._lower_bound is not None and new_state.state < self._lower_bound: - self._stats_internal['erasures_low'] += 1 + self._stats_internal["erasures_low"] += 1 - _LOGGER.debug("Lower outlier nr. %s in %s: %s", - self._stats_internal['erasures_low'], - self._entity, new_state) + _LOGGER.debug( + "Lower outlier nr. %s in %s: %s", + self._stats_internal["erasures_low"], + self._entity, + new_state, + ) new_state.state = self._lower_bound return new_state @@ -405,17 +455,20 @@ class OutlierFilter(Filter): def _filter_state(self, new_state): """Implement the outlier filter.""" - median = statistics.median([s.state for s in self.states]) \ - if self.states else 0 - if (len(self.states) == self.states.maxlen and - abs(new_state.state - median) > - self._radius): + median = statistics.median([s.state for s in self.states]) if self.states else 0 + if ( + len(self.states) == self.states.maxlen + and abs(new_state.state - median) > self._radius + ): - self._stats_internal['erasures'] += 1 + self._stats_internal["erasures"] += 1 - _LOGGER.debug("Outlier nr. %s in %s: %s", - self._stats_internal['erasures'], - self._entity, new_state) + _LOGGER.debug( + "Outlier nr. %s in %s: %s", + self._stats_internal["erasures"], + self._entity, + new_state, + ) new_state.state = median return new_state @@ -440,8 +493,9 @@ class LowPassFilter(Filter): new_weight = 1.0 / self._time_constant prev_weight = 1.0 - new_weight - new_state.state = prev_weight * self.states[-1].state +\ - new_weight * new_state.state + new_state.state = ( + prev_weight * self.states[-1].state + new_weight * new_state.state + ) return new_state @@ -456,8 +510,9 @@ class TimeSMAFilter(Filter): type (enum): type of algorithm used to connect discrete values """ - def __init__(self, window_size, precision, entity, - type): # pylint: disable=redefined-builtin + def __init__( + self, window_size, precision, entity, type + ): # pylint: disable=redefined-builtin """Initialize Filter.""" super().__init__(FILTER_NAME_TIME_SMA, window_size, precision, entity) self._time_window = window_size @@ -481,8 +536,7 @@ class TimeSMAFilter(Filter): start = new_state.timestamp - self._time_window prev_state = self.last_leak or self.queue[0] for state in self.queue: - moving_sum += (state.timestamp-start).total_seconds()\ - * prev_state.state + moving_sum += (state.timestamp - start).total_seconds() * prev_state.state start = state.timestamp prev_state = state @@ -522,8 +576,7 @@ class TimeThrottleFilter(Filter): def __init__(self, window_size, precision, entity): """Initialize Filter.""" - super().__init__(FILTER_NAME_TIME_THROTTLE, - window_size, precision, entity) + super().__init__(FILTER_NAME_TIME_THROTTLE, window_size, precision, entity) self._time_window = window_size self._last_emitted_at = None diff --git a/homeassistant/components/fints/sensor.py b/homeassistant/components/fints/sensor.py index cb993ada8da..7a1760ea3d5 100644 --- a/homeassistant/components/fints/sensor.py +++ b/homeassistant/components/fints/sensor.py @@ -14,33 +14,37 @@ _LOGGER = logging.getLogger(__name__) SCAN_INTERVAL = timedelta(hours=4) -ICON = 'mdi:currency-eur' +ICON = "mdi:currency-eur" -BankCredentials = namedtuple('BankCredentials', 'blz login pin url') +BankCredentials = namedtuple("BankCredentials", "blz login pin url") -CONF_BIN = 'bank_identification_number' -CONF_ACCOUNTS = 'accounts' -CONF_HOLDINGS = 'holdings' -CONF_ACCOUNT = 'account' +CONF_BIN = "bank_identification_number" +CONF_ACCOUNTS = "accounts" +CONF_HOLDINGS = "holdings" +CONF_ACCOUNT = "account" ATTR_ACCOUNT = CONF_ACCOUNT -ATTR_BANK = 'bank' -ATTR_ACCOUNT_TYPE = 'account_type' +ATTR_BANK = "bank" +ATTR_ACCOUNT_TYPE = "account_type" -SCHEMA_ACCOUNTS = vol.Schema({ - vol.Required(CONF_ACCOUNT): cv.string, - vol.Optional(CONF_NAME, default=None): vol.Any(None, cv.string), -}) +SCHEMA_ACCOUNTS = vol.Schema( + { + vol.Required(CONF_ACCOUNT): cv.string, + vol.Optional(CONF_NAME, default=None): vol.Any(None, cv.string), + } +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_BIN): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PIN): cv.string, - vol.Required(CONF_URL): cv.string, - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_ACCOUNTS, default=[]): cv.ensure_list(SCHEMA_ACCOUNTS), - vol.Optional(CONF_HOLDINGS, default=[]): cv.ensure_list(SCHEMA_ACCOUNTS), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_BIN): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PIN): cv.string, + vol.Required(CONF_URL): cv.string, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_ACCOUNTS, default=[]): cv.ensure_list(SCHEMA_ACCOUNTS), + vol.Optional(CONF_HOLDINGS, default=[]): cv.ensure_list(SCHEMA_ACCOUNTS), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -49,15 +53,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None): Login to the bank and get a list of existing accounts. Create a sensor for each account. """ - credentials = BankCredentials(config[CONF_BIN], config[CONF_USERNAME], - config[CONF_PIN], config[CONF_URL]) + credentials = BankCredentials( + config[CONF_BIN], config[CONF_USERNAME], config[CONF_PIN], config[CONF_URL] + ) fints_name = config.get(CONF_NAME, config[CONF_BIN]) - account_config = {acc[CONF_ACCOUNT]: acc[CONF_NAME] - for acc in config[CONF_ACCOUNTS]} + account_config = { + acc[CONF_ACCOUNT]: acc[CONF_NAME] for acc in config[CONF_ACCOUNTS] + } - holdings_config = {acc[CONF_ACCOUNT]: acc[CONF_NAME] - for acc in config[CONF_HOLDINGS]} + holdings_config = { + acc[CONF_ACCOUNT]: acc[CONF_NAME] for acc in config[CONF_HOLDINGS] + } client = FinTsClient(credentials, fints_name) balance_accounts, holdings_accounts = client.detect_accounts() @@ -65,31 +72,29 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for account in balance_accounts: if config[CONF_ACCOUNTS] and account.iban not in account_config: - _LOGGER.info('skipping account %s for bank %s', - account.iban, fints_name) + _LOGGER.info("skipping account %s for bank %s", account.iban, fints_name) continue account_name = account_config.get(account.iban) if not account_name: - account_name = '{} - {}'.format(fints_name, account.iban) + account_name = "{} - {}".format(fints_name, account.iban) accounts.append(FinTsAccount(client, account, account_name)) - _LOGGER.debug('Creating account %s for bank %s', - account.iban, fints_name) + _LOGGER.debug("Creating account %s for bank %s", account.iban, fints_name) for account in holdings_accounts: - if config[CONF_HOLDINGS] and \ - account.accountnumber not in holdings_config: - _LOGGER.info('skipping holdings %s for bank %s', - account.accountnumber, fints_name) + if config[CONF_HOLDINGS] and account.accountnumber not in holdings_config: + _LOGGER.info( + "skipping holdings %s for bank %s", account.accountnumber, fints_name + ) continue account_name = holdings_config.get(account.accountnumber) if not account_name: - account_name = '{} - {}'.format( - fints_name, account.accountnumber) + account_name = "{} - {}".format(fints_name, account.accountnumber) accounts.append(FinTsHoldingsAccount(client, account, account_name)) - _LOGGER.debug('Creating holdings %s for bank %s', - account.accountnumber, fints_name) + _LOGGER.debug( + "Creating holdings %s for bank %s", account.accountnumber, fints_name + ) add_entities(accounts, True) @@ -114,13 +119,18 @@ class FinTsClient: object and also think about potential concurrency problems. """ from fints.client import FinTS3PinTanClient + return FinTS3PinTanClient( - self._credentials.blz, self._credentials.login, - self._credentials.pin, self._credentials.url) + self._credentials.blz, + self._credentials.login, + self._credentials.pin, + self._credentials.url, + ) def detect_accounts(self): """Identify the accounts of the bank.""" from fints.dialog import FinTSDialogError + balance_accounts = [] holdings_accounts = [] for account in self.client.get_sepa_accounts(): @@ -155,7 +165,7 @@ class FinTsAccount(Entity): self._client = client # type: FinTsClient self._account = account self._name = name # type: str - self._balance = None # type: float + self._balance = None # type: float self._currency = None # type: str @property @@ -172,7 +182,7 @@ class FinTsAccount(Entity): balance = bank.get_balance(self._account) self._balance = balance.amount.amount self._currency = balance.amount.currency - _LOGGER.debug('updated balance of account %s', self.name) + _LOGGER.debug("updated balance of account %s", self.name) @property def name(self) -> str: @@ -192,10 +202,7 @@ class FinTsAccount(Entity): @property def device_state_attributes(self) -> dict: """Additional attributes of the sensor.""" - attributes = { - ATTR_ACCOUNT: self._account.iban, - ATTR_ACCOUNT_TYPE: 'balance', - } + attributes = {ATTR_ACCOUNT: self._account.iban, ATTR_ACCOUNT_TYPE: "balance"} if self._client.name: attributes[ATTR_BANK] = self._client.name return attributes @@ -253,16 +260,16 @@ class FinTsHoldingsAccount(Entity): """ attributes = { ATTR_ACCOUNT: self._account.accountnumber, - ATTR_ACCOUNT_TYPE: 'holdings', + ATTR_ACCOUNT_TYPE: "holdings", } if self._client.name: attributes[ATTR_BANK] = self._client.name for holding in self._holdings: - total_name = '{} total'.format(holding.name) + total_name = "{} total".format(holding.name) attributes[total_name] = holding.total_value - pieces_name = '{} pieces'.format(holding.name) + pieces_name = "{} pieces".format(holding.name) attributes[pieces_name] = holding.pieces - price_name = '{} price'.format(holding.name) + price_name = "{} price".format(holding.name) attributes[price_name] = holding.market_value return attributes diff --git a/homeassistant/components/fitbit/sensor.py b/homeassistant/components/fitbit/sensor.py index 889920239ed..830914ce113 100644 --- a/homeassistant/components/fitbit/sensor.py +++ b/homeassistant/components/fitbit/sensor.py @@ -20,130 +20,134 @@ from homeassistant.util.json import load_json, save_json _CONFIGURING = {} _LOGGER = logging.getLogger(__name__) -ATTR_ACCESS_TOKEN = 'access_token' -ATTR_REFRESH_TOKEN = 'refresh_token' -ATTR_CLIENT_ID = 'client_id' -ATTR_CLIENT_SECRET = 'client_secret' -ATTR_LAST_SAVED_AT = 'last_saved_at' +ATTR_ACCESS_TOKEN = "access_token" +ATTR_REFRESH_TOKEN = "refresh_token" +ATTR_CLIENT_ID = "client_id" +ATTR_CLIENT_SECRET = "client_secret" +ATTR_LAST_SAVED_AT = "last_saved_at" -CONF_MONITORED_RESOURCES = 'monitored_resources' -CONF_CLOCK_FORMAT = 'clock_format' -ATTRIBUTION = 'Data provided by Fitbit.com' +CONF_MONITORED_RESOURCES = "monitored_resources" +CONF_CLOCK_FORMAT = "clock_format" +ATTRIBUTION = "Data provided by Fitbit.com" -FITBIT_AUTH_CALLBACK_PATH = '/api/fitbit/callback' -FITBIT_AUTH_START = '/api/fitbit' -FITBIT_CONFIG_FILE = 'fitbit.conf' -FITBIT_DEFAULT_RESOURCES = ['activities/steps'] +FITBIT_AUTH_CALLBACK_PATH = "/api/fitbit/callback" +FITBIT_AUTH_START = "/api/fitbit" +FITBIT_CONFIG_FILE = "fitbit.conf" +FITBIT_DEFAULT_RESOURCES = ["activities/steps"] SCAN_INTERVAL = datetime.timedelta(minutes=30) -DEFAULT_CONFIG = { - 'client_id': 'CLIENT_ID_HERE', - 'client_secret': 'CLIENT_SECRET_HERE' -} +DEFAULT_CONFIG = {"client_id": "CLIENT_ID_HERE", "client_secret": "CLIENT_SECRET_HERE"} FITBIT_RESOURCES_LIST = { - 'activities/activityCalories': ['Activity Calories', 'cal', 'fire'], - 'activities/calories': ['Calories', 'cal', 'fire'], - 'activities/caloriesBMR': ['Calories BMR', 'cal', 'fire'], - 'activities/distance': ['Distance', '', 'map-marker'], - 'activities/elevation': ['Elevation', '', 'walk'], - 'activities/floors': ['Floors', 'floors', 'walk'], - 'activities/heart': ['Resting Heart Rate', 'bpm', 'heart-pulse'], - 'activities/minutesFairlyActive': - ['Minutes Fairly Active', 'minutes', 'walk'], - 'activities/minutesLightlyActive': - ['Minutes Lightly Active', 'minutes', 'walk'], - 'activities/minutesSedentary': - ['Minutes Sedentary', 'minutes', 'seat-recline-normal'], - 'activities/minutesVeryActive': ['Minutes Very Active', 'minutes', 'run'], - 'activities/steps': ['Steps', 'steps', 'walk'], - 'activities/tracker/activityCalories': - ['Tracker Activity Calories', 'cal', 'fire'], - 'activities/tracker/calories': ['Tracker Calories', 'cal', 'fire'], - 'activities/tracker/distance': ['Tracker Distance', '', 'map-marker'], - 'activities/tracker/elevation': ['Tracker Elevation', '', 'walk'], - 'activities/tracker/floors': ['Tracker Floors', 'floors', 'walk'], - 'activities/tracker/minutesFairlyActive': - ['Tracker Minutes Fairly Active', 'minutes', 'walk'], - 'activities/tracker/minutesLightlyActive': - ['Tracker Minutes Lightly Active', 'minutes', 'walk'], - 'activities/tracker/minutesSedentary': - ['Tracker Minutes Sedentary', 'minutes', 'seat-recline-normal'], - 'activities/tracker/minutesVeryActive': - ['Tracker Minutes Very Active', 'minutes', 'run'], - 'activities/tracker/steps': ['Tracker Steps', 'steps', 'walk'], - 'body/bmi': ['BMI', 'BMI', 'human'], - 'body/fat': ['Body Fat', '%', 'human'], - 'body/weight': ['Weight', '', 'human'], - 'devices/battery': ['Battery', None, None], - 'sleep/awakeningsCount': - ['Awakenings Count', 'times awaken', 'sleep'], - 'sleep/efficiency': ['Sleep Efficiency', '%', 'sleep'], - 'sleep/minutesAfterWakeup': ['Minutes After Wakeup', 'minutes', 'sleep'], - 'sleep/minutesAsleep': ['Sleep Minutes Asleep', 'minutes', 'sleep'], - 'sleep/minutesAwake': ['Sleep Minutes Awake', 'minutes', 'sleep'], - 'sleep/minutesToFallAsleep': - ['Sleep Minutes to Fall Asleep', 'minutes', 'sleep'], - 'sleep/startTime': ['Sleep Start Time', None, 'clock'], - 'sleep/timeInBed': ['Sleep Time in Bed', 'minutes', 'hotel'] + "activities/activityCalories": ["Activity Calories", "cal", "fire"], + "activities/calories": ["Calories", "cal", "fire"], + "activities/caloriesBMR": ["Calories BMR", "cal", "fire"], + "activities/distance": ["Distance", "", "map-marker"], + "activities/elevation": ["Elevation", "", "walk"], + "activities/floors": ["Floors", "floors", "walk"], + "activities/heart": ["Resting Heart Rate", "bpm", "heart-pulse"], + "activities/minutesFairlyActive": ["Minutes Fairly Active", "minutes", "walk"], + "activities/minutesLightlyActive": ["Minutes Lightly Active", "minutes", "walk"], + "activities/minutesSedentary": [ + "Minutes Sedentary", + "minutes", + "seat-recline-normal", + ], + "activities/minutesVeryActive": ["Minutes Very Active", "minutes", "run"], + "activities/steps": ["Steps", "steps", "walk"], + "activities/tracker/activityCalories": ["Tracker Activity Calories", "cal", "fire"], + "activities/tracker/calories": ["Tracker Calories", "cal", "fire"], + "activities/tracker/distance": ["Tracker Distance", "", "map-marker"], + "activities/tracker/elevation": ["Tracker Elevation", "", "walk"], + "activities/tracker/floors": ["Tracker Floors", "floors", "walk"], + "activities/tracker/minutesFairlyActive": [ + "Tracker Minutes Fairly Active", + "minutes", + "walk", + ], + "activities/tracker/minutesLightlyActive": [ + "Tracker Minutes Lightly Active", + "minutes", + "walk", + ], + "activities/tracker/minutesSedentary": [ + "Tracker Minutes Sedentary", + "minutes", + "seat-recline-normal", + ], + "activities/tracker/minutesVeryActive": [ + "Tracker Minutes Very Active", + "minutes", + "run", + ], + "activities/tracker/steps": ["Tracker Steps", "steps", "walk"], + "body/bmi": ["BMI", "BMI", "human"], + "body/fat": ["Body Fat", "%", "human"], + "body/weight": ["Weight", "", "human"], + "devices/battery": ["Battery", None, None], + "sleep/awakeningsCount": ["Awakenings Count", "times awaken", "sleep"], + "sleep/efficiency": ["Sleep Efficiency", "%", "sleep"], + "sleep/minutesAfterWakeup": ["Minutes After Wakeup", "minutes", "sleep"], + "sleep/minutesAsleep": ["Sleep Minutes Asleep", "minutes", "sleep"], + "sleep/minutesAwake": ["Sleep Minutes Awake", "minutes", "sleep"], + "sleep/minutesToFallAsleep": ["Sleep Minutes to Fall Asleep", "minutes", "sleep"], + "sleep/startTime": ["Sleep Start Time", None, "clock"], + "sleep/timeInBed": ["Sleep Time in Bed", "minutes", "hotel"], } FITBIT_MEASUREMENTS = { - 'en_US': { - 'duration': 'ms', - 'distance': 'mi', - 'elevation': 'ft', - 'height': 'in', - 'weight': 'lbs', - 'body': 'in', - 'liquids': 'fl. oz.', - 'blood glucose': 'mg/dL', - 'battery': '', + "en_US": { + "duration": "ms", + "distance": "mi", + "elevation": "ft", + "height": "in", + "weight": "lbs", + "body": "in", + "liquids": "fl. oz.", + "blood glucose": "mg/dL", + "battery": "", }, - 'en_GB': { - 'duration': 'milliseconds', - 'distance': 'kilometers', - 'elevation': 'meters', - 'height': 'centimeters', - 'weight': 'stone', - 'body': 'centimeters', - 'liquids': 'milliliters', - 'blood glucose': 'mmol/L', - 'battery': '', + "en_GB": { + "duration": "milliseconds", + "distance": "kilometers", + "elevation": "meters", + "height": "centimeters", + "weight": "stone", + "body": "centimeters", + "liquids": "milliliters", + "blood glucose": "mmol/L", + "battery": "", }, - 'metric': { - 'duration': 'milliseconds', - 'distance': 'kilometers', - 'elevation': 'meters', - 'height': 'centimeters', - 'weight': 'kilograms', - 'body': 'centimeters', - 'liquids': 'milliliters', - 'blood glucose': 'mmol/L', - 'battery': '', + "metric": { + "duration": "milliseconds", + "distance": "kilometers", + "elevation": "meters", + "height": "centimeters", + "weight": "kilograms", + "body": "centimeters", + "liquids": "milliliters", + "blood glucose": "mmol/L", + "battery": "", + }, +} + +BATTERY_LEVELS = {"High": 100, "Medium": 50, "Low": 20, "Empty": 0} + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional( + CONF_MONITORED_RESOURCES, default=FITBIT_DEFAULT_RESOURCES + ): vol.All(cv.ensure_list, [vol.In(FITBIT_RESOURCES_LIST)]), + vol.Optional(CONF_CLOCK_FORMAT, default="24H"): vol.In(["12H", "24H"]), + vol.Optional(CONF_UNIT_SYSTEM, default="default"): vol.In( + ["en_GB", "en_US", "metric", "default"] + ), } -} - -BATTERY_LEVELS = { - 'High': 100, - 'Medium': 50, - 'Low': 20, - 'Empty': 0 -} - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_MONITORED_RESOURCES, default=FITBIT_DEFAULT_RESOURCES): - vol.All(cv.ensure_list, [vol.In(FITBIT_RESOURCES_LIST)]), - vol.Optional(CONF_CLOCK_FORMAT, default='24H'): - vol.In(['12H', '24H']), - vol.Optional(CONF_UNIT_SYSTEM, default='default'): - vol.In(['en_GB', 'en_US', 'metric', 'default']) -}) +) -def request_app_setup(hass, config, add_entities, config_path, - discovery_info=None): +def request_app_setup(hass, config, add_entities, config_path, discovery_info=None): """Assist user with configuring the Fitbit dev application.""" configurator = hass.components.configurator @@ -153,17 +157,17 @@ def request_app_setup(hass, config, add_entities, config_path, if os.path.isfile(config_path): config_file = load_json(config_path) if config_file == DEFAULT_CONFIG: - error_msg = ("You didn't correctly modify fitbit.conf", - " please try again") - configurator.notify_errors(_CONFIGURING['fitbit'], - error_msg) + error_msg = ( + "You didn't correctly modify fitbit.conf", + " please try again", + ) + configurator.notify_errors(_CONFIGURING["fitbit"], error_msg) else: setup_platform(hass, config, add_entities, discovery_info) else: setup_platform(hass, config, add_entities, discovery_info) - start_url = "{}{}".format(hass.config.api.base_url, - FITBIT_AUTH_CALLBACK_PATH) + start_url = "{}{}".format(hass.config.api.base_url, FITBIT_AUTH_CALLBACK_PATH) description = """Please create a Fitbit developer app at https://dev.fitbit.com/apps/new. @@ -172,14 +176,18 @@ def request_app_setup(hass, config, add_entities, config_path, They will provide you a Client ID and secret. These need to be saved into the file located at: {}. Then come back here and hit the below button. - """.format(start_url, config_path) + """.format( + start_url, config_path + ) submit = "I have saved my Client ID and Client Secret into fitbit.conf." - _CONFIGURING['fitbit'] = configurator.request_config( - 'Fitbit', fitbit_configuration_callback, - description=description, submit_caption=submit, - description_image="/static/images/config_fitbit_app.png" + _CONFIGURING["fitbit"] = configurator.request_config( + "Fitbit", + fitbit_configuration_callback, + description=description, + submit_caption=submit, + description_image="/static/images/config_fitbit_app.png", ) @@ -188,21 +196,23 @@ def request_oauth_completion(hass): configurator = hass.components.configurator if "fitbit" in _CONFIGURING: configurator.notify_errors( - _CONFIGURING['fitbit'], "Failed to register, please try again.") + _CONFIGURING["fitbit"], "Failed to register, please try again." + ) return def fitbit_configuration_callback(callback_data): """Handle configuration updates.""" - start_url = '{}{}'.format(hass.config.api.base_url, FITBIT_AUTH_START) + start_url = "{}{}".format(hass.config.api.base_url, FITBIT_AUTH_START) description = "Please authorize Fitbit by visiting {}".format(start_url) - _CONFIGURING['fitbit'] = configurator.request_config( - 'Fitbit', fitbit_configuration_callback, + _CONFIGURING["fitbit"] = configurator.request_config( + "Fitbit", + fitbit_configuration_callback, description=description, - submit_caption="I have authorized Fitbit." + submit_caption="I have authorized Fitbit.", ) @@ -213,12 +223,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None): config_file = load_json(config_path) if config_file == DEFAULT_CONFIG: request_app_setup( - hass, config, add_entities, config_path, discovery_info=None) + hass, config, add_entities, config_path, discovery_info=None + ) return False else: save_json(config_path, DEFAULT_CONFIG) - request_app_setup( - hass, config, add_entities, config_path, discovery_info=None) + request_app_setup(hass, config, add_entities, config_path, discovery_info=None) return False if "fitbit" in _CONFIGURING: @@ -230,25 +240,26 @@ def setup_platform(hass, config, add_entities, discovery_info=None): refresh_token = config_file.get(ATTR_REFRESH_TOKEN) expires_at = config_file.get(ATTR_LAST_SAVED_AT) if None not in (access_token, refresh_token): - authd_client = fitbit.Fitbit(config_file.get(ATTR_CLIENT_ID), - config_file.get(ATTR_CLIENT_SECRET), - access_token=access_token, - refresh_token=refresh_token, - expires_at=expires_at, - refresh_cb=lambda x: None) + authd_client = fitbit.Fitbit( + config_file.get(ATTR_CLIENT_ID), + config_file.get(ATTR_CLIENT_SECRET), + access_token=access_token, + refresh_token=refresh_token, + expires_at=expires_at, + refresh_cb=lambda x: None, + ) if int(time.time()) - expires_at > 3600: authd_client.client.refresh_token() unit_system = config.get(CONF_UNIT_SYSTEM) - if unit_system == 'default': - authd_client.system = authd_client. \ - user_profile_get()["user"]["locale"] - if authd_client.system != 'en_GB': + if unit_system == "default": + authd_client.system = authd_client.user_profile_get()["user"]["locale"] + if authd_client.system != "en_GB": if hass.config.units.is_metric: - authd_client.system = 'metric' + authd_client.system = "metric" else: - authd_client.system = 'en_US' + authd_client.system = "en_US" else: authd_client.system = unit_system @@ -258,33 +269,54 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for resource in config.get(CONF_MONITORED_RESOURCES): # monitor battery for all linked FitBit devices - if resource == 'devices/battery': + if resource == "devices/battery": for dev_extra in registered_devs: - dev.append(FitbitSensor( - authd_client, config_path, resource, - hass.config.units.is_metric, clock_format, dev_extra)) + dev.append( + FitbitSensor( + authd_client, + config_path, + resource, + hass.config.units.is_metric, + clock_format, + dev_extra, + ) + ) else: - dev.append(FitbitSensor( - authd_client, config_path, resource, - hass.config.units.is_metric, clock_format)) + dev.append( + FitbitSensor( + authd_client, + config_path, + resource, + hass.config.units.is_metric, + clock_format, + ) + ) add_entities(dev, True) else: oauth = fitbit.api.FitbitOauth2Client( - config_file.get(ATTR_CLIENT_ID), - config_file.get(ATTR_CLIENT_SECRET)) + config_file.get(ATTR_CLIENT_ID), config_file.get(ATTR_CLIENT_SECRET) + ) - redirect_uri = '{}{}'.format(hass.config.api.base_url, - FITBIT_AUTH_CALLBACK_PATH) + redirect_uri = "{}{}".format( + hass.config.api.base_url, FITBIT_AUTH_CALLBACK_PATH + ) fitbit_auth_start_url, _ = oauth.authorize_token_url( redirect_uri=redirect_uri, - scope=['activity', 'heartrate', 'nutrition', 'profile', - 'settings', 'sleep', 'weight']) + scope=[ + "activity", + "heartrate", + "nutrition", + "profile", + "settings", + "sleep", + "weight", + ], + ) hass.http.register_redirect(FITBIT_AUTH_START, fitbit_auth_start_url) - hass.http.register_view(FitbitAuthCallbackView( - config, add_entities, oauth)) + hass.http.register_view(FitbitAuthCallbackView(config, add_entities, oauth)) request_oauth_completion(hass) @@ -294,7 +326,7 @@ class FitbitAuthCallbackView(HomeAssistantView): requires_auth = False url = FITBIT_AUTH_CALLBACK_PATH - name = 'api:fitbit:callback' + name = "api:fitbit:callback" def __init__(self, config, add_entities, oauth): """Initialize the OAuth callback view.""" @@ -308,30 +340,34 @@ class FitbitAuthCallbackView(HomeAssistantView): from oauthlib.oauth2.rfc6749.errors import MismatchingStateError from oauthlib.oauth2.rfc6749.errors import MissingTokenError - hass = request.app['hass'] + hass = request.app["hass"] data = request.query response_message = """Fitbit has been successfully authorized! You can close this window now!""" result = None - if data.get('code') is not None: - redirect_uri = '{}{}'.format( - hass.config.api.base_url, FITBIT_AUTH_CALLBACK_PATH) + if data.get("code") is not None: + redirect_uri = "{}{}".format( + hass.config.api.base_url, FITBIT_AUTH_CALLBACK_PATH + ) try: - result = self.oauth.fetch_access_token(data.get('code'), - redirect_uri) + result = self.oauth.fetch_access_token(data.get("code"), redirect_uri) except MissingTokenError as error: _LOGGER.error("Missing token: %s", error) response_message = """Something went wrong when attempting authenticating with Fitbit. The error - encountered was {}. Please try again!""".format(error) + encountered was {}. Please try again!""".format( + error + ) except MismatchingStateError as error: _LOGGER.error("Mismatched state, CSRF error: %s", error) response_message = """Something went wrong when attempting authenticating with Fitbit. The error - encountered was {}. Please try again!""".format(error) + encountered was {}. Please try again!""".format( + error + ) else: _LOGGER.error("Unknown error when authing") response_message = """Something went wrong when @@ -347,20 +383,21 @@ class FitbitAuthCallbackView(HomeAssistantView): """ html_response = """Fitbit Auth -

{}

""".format(response_message) +

{}

""".format( + response_message + ) if result: config_contents = { - ATTR_ACCESS_TOKEN: result.get('access_token'), - ATTR_REFRESH_TOKEN: result.get('refresh_token'), + ATTR_ACCESS_TOKEN: result.get("access_token"), + ATTR_REFRESH_TOKEN: result.get("refresh_token"), ATTR_CLIENT_ID: self.oauth.client_id, ATTR_CLIENT_SECRET: self.oauth.client_secret, - ATTR_LAST_SAVED_AT: int(time.time()) + ATTR_LAST_SAVED_AT: int(time.time()), } save_json(hass.config.path(FITBIT_CONFIG_FILE), config_contents) - hass.async_add_job(setup_platform, hass, self.config, - self.add_entities) + hass.async_add_job(setup_platform, hass, self.config, self.add_entities) return html_response @@ -368,8 +405,9 @@ class FitbitAuthCallbackView(HomeAssistantView): class FitbitSensor(Entity): """Implementation of a Fitbit sensor.""" - def __init__(self, client, config_path, resource_type, - is_metric, clock_format, extra=None): + def __init__( + self, client, config_path, resource_type, is_metric, clock_format, extra=None + ): """Initialize the Fitbit sensor.""" self.client = client self.config_path = config_path @@ -379,17 +417,17 @@ class FitbitSensor(Entity): self.extra = extra self._name = FITBIT_RESOURCES_LIST[self.resource_type][0] if self.extra: - self._name = '{0} Battery'.format(self.extra.get('deviceVersion')) + self._name = "{0} Battery".format(self.extra.get("deviceVersion")) unit_type = FITBIT_RESOURCES_LIST[self.resource_type][1] if unit_type == "": - split_resource = self.resource_type.split('/') + split_resource = self.resource_type.split("/") try: measurement_system = FITBIT_MEASUREMENTS[self.client.system] except KeyError: if self.is_metric: - measurement_system = FITBIT_MEASUREMENTS['metric'] + measurement_system = FITBIT_MEASUREMENTS["metric"] else: - measurement_system = FITBIT_MEASUREMENTS['en_US'] + measurement_system = FITBIT_MEASUREMENTS["en_US"] unit_type = measurement_system[split_resource[-1]] self._unit_of_measurement = unit_type self._state = 0 @@ -412,11 +450,10 @@ class FitbitSensor(Entity): @property def icon(self): """Icon to use in the frontend, if any.""" - if self.resource_type == 'devices/battery' and self.extra: - battery_level = BATTERY_LEVELS[self.extra.get('battery')] - return icon_for_battery_level( - battery_level=battery_level, charging=None) - return 'mdi:{}'.format(FITBIT_RESOURCES_LIST[self.resource_type][2]) + if self.resource_type == "devices/battery" and self.extra: + battery_level = BATTERY_LEVELS[self.extra.get("battery")] + return icon_for_battery_level(battery_level=battery_level, charging=None) + return "mdi:{}".format(FITBIT_RESOURCES_LIST[self.resource_type][2]) @property def device_state_attributes(self): @@ -426,43 +463,42 @@ class FitbitSensor(Entity): attrs[ATTR_ATTRIBUTION] = ATTRIBUTION if self.extra: - attrs['model'] = self.extra.get('deviceVersion') - attrs['type'] = self.extra.get('type').lower() + attrs["model"] = self.extra.get("deviceVersion") + attrs["type"] = self.extra.get("type").lower() return attrs def update(self): """Get the latest data from the Fitbit API and update the states.""" - if self.resource_type == 'devices/battery' and self.extra: - self._state = self.extra.get('battery') + if self.resource_type == "devices/battery" and self.extra: + self._state = self.extra.get("battery") else: container = self.resource_type.replace("/", "-") - response = self.client.time_series(self.resource_type, period='7d') - raw_state = response[container][-1].get('value') - if self.resource_type == 'activities/distance': - self._state = format(float(raw_state), '.2f') - elif self.resource_type == 'activities/tracker/distance': - self._state = format(float(raw_state), '.2f') - elif self.resource_type == 'body/bmi': - self._state = format(float(raw_state), '.1f') - elif self.resource_type == 'body/fat': - self._state = format(float(raw_state), '.1f') - elif self.resource_type == 'body/weight': - self._state = format(float(raw_state), '.1f') - elif self.resource_type == 'sleep/startTime': - if raw_state == '': - self._state = '-' - elif self.clock_format == '12H': - hours, minutes = raw_state.split(':') + response = self.client.time_series(self.resource_type, period="7d") + raw_state = response[container][-1].get("value") + if self.resource_type == "activities/distance": + self._state = format(float(raw_state), ".2f") + elif self.resource_type == "activities/tracker/distance": + self._state = format(float(raw_state), ".2f") + elif self.resource_type == "body/bmi": + self._state = format(float(raw_state), ".1f") + elif self.resource_type == "body/fat": + self._state = format(float(raw_state), ".1f") + elif self.resource_type == "body/weight": + self._state = format(float(raw_state), ".1f") + elif self.resource_type == "sleep/startTime": + if raw_state == "": + self._state = "-" + elif self.clock_format == "12H": + hours, minutes = raw_state.split(":") hours, minutes = int(hours), int(minutes) - setting = 'AM' + setting = "AM" if hours > 12: - setting = 'PM' + setting = "PM" hours -= 12 elif hours == 0: hours = 12 - self._state = '{}:{:02d} {}'.format(hours, minutes, - setting) + self._state = "{}:{:02d} {}".format(hours, minutes, setting) else: self._state = raw_state else: @@ -470,20 +506,19 @@ class FitbitSensor(Entity): self._state = raw_state else: try: - self._state = '{0:,}'.format(int(raw_state)) + self._state = "{0:,}".format(int(raw_state)) except TypeError: self._state = raw_state - if self.resource_type == 'activities/heart': - self._state = response[container][-1]. \ - get('value').get('restingHeartRate') + if self.resource_type == "activities/heart": + self._state = response[container][-1].get("value").get("restingHeartRate") token = self.client.client.session.token config_contents = { - ATTR_ACCESS_TOKEN: token.get('access_token'), - ATTR_REFRESH_TOKEN: token.get('refresh_token'), + ATTR_ACCESS_TOKEN: token.get("access_token"), + ATTR_REFRESH_TOKEN: token.get("refresh_token"), ATTR_CLIENT_ID: self.client.client.client_id, ATTR_CLIENT_SECRET: self.client.client.client_secret, - ATTR_LAST_SAVED_AT: int(time.time()) + ATTR_LAST_SAVED_AT: int(time.time()), } save_json(self.config_path, config_contents) diff --git a/homeassistant/components/fixer/sensor.py b/homeassistant/components/fixer/sensor.py index 4cf2b0b9243..a97f77138db 100644 --- a/homeassistant/components/fixer/sensor.py +++ b/homeassistant/components/fixer/sensor.py @@ -11,24 +11,26 @@ from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -ATTR_EXCHANGE_RATE = 'Exchange rate' -ATTR_TARGET = 'Target currency' +ATTR_EXCHANGE_RATE = "Exchange rate" +ATTR_TARGET = "Target currency" ATTRIBUTION = "Data provided by the European Central Bank (ECB)" -CONF_TARGET = 'target' +CONF_TARGET = "target" -DEFAULT_BASE = 'USD' -DEFAULT_NAME = 'Exchange rate' +DEFAULT_BASE = "USD" +DEFAULT_NAME = "Exchange rate" -ICON = 'mdi:currency-usd' +ICON = "mdi:currency-usd" SCAN_INTERVAL = timedelta(days=1) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_API_KEY): cv.string, - vol.Required(CONF_TARGET): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_API_KEY): cv.string, + vol.Required(CONF_TARGET): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -80,7 +82,7 @@ class ExchangeRateSensor(Entity): if self.data.rate is not None: return { ATTR_ATTRIBUTION: ATTRIBUTION, - ATTR_EXCHANGE_RATE: self.data.rate['rates'][self._target], + ATTR_EXCHANGE_RATE: self.data.rate["rates"][self._target], ATTR_TARGET: self._target, } @@ -92,7 +94,7 @@ class ExchangeRateSensor(Entity): def update(self): """Get the latest data and updates the states.""" self.data.update() - self._state = round(self.data.rate['rates'][self._target], 3) + self._state = round(self.data.rate["rates"][self._target], 3) class ExchangeData: @@ -105,8 +107,7 @@ class ExchangeData: self.api_key = api_key self.rate = None self.target_currency = target_currency - self.exchange = Fixerio( - symbols=[self.target_currency], access_key=self.api_key) + self.exchange = Fixerio(symbols=[self.target_currency], access_key=self.api_key) def update(self): """Get the latest data from Fixer.io.""" diff --git a/homeassistant/components/fleetgo/device_tracker.py b/homeassistant/components/fleetgo/device_tracker.py index dac5d654c41..0561530345c 100644 --- a/homeassistant/components/fleetgo/device_tracker.py +++ b/homeassistant/components/fleetgo/device_tracker.py @@ -11,25 +11,26 @@ from homeassistant.helpers.event import track_utc_time_change _LOGGER = logging.getLogger(__name__) -CONF_CLIENT_ID = 'client_id' -CONF_CLIENT_SECRET = 'client_secret' -CONF_INCLUDE = 'include' +CONF_CLIENT_ID = "client_id" +CONF_CLIENT_SECRET = "client_secret" +CONF_INCLUDE = "include" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_CLIENT_ID): cv.string, - vol.Required(CONF_CLIENT_SECRET): cv.string, - vol.Optional(CONF_INCLUDE, default=[]): - vol.All(cv.ensure_list, [cv.string]) -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_CLIENT_ID): cv.string, + vol.Required(CONF_CLIENT_SECRET): cv.string, + vol.Optional(CONF_INCLUDE, default=[]): vol.All(cv.ensure_list, [cv.string]), + } +) def setup_scanner(hass, config: dict, see, discovery_info=None): """Set up the DeviceScanner and check if login is valid.""" scanner = FleetGoDeviceScanner(config, see) if not scanner.login(hass): - _LOGGER.error('FleetGO authentication failed') + _LOGGER.error("FleetGO authentication failed") return False return True @@ -44,17 +45,19 @@ class FleetGoDeviceScanner: self._include = config.get(CONF_INCLUDE) self._see = see - self._api = API(config.get(CONF_CLIENT_ID), - config.get(CONF_CLIENT_SECRET), - config.get(CONF_USERNAME), - config.get(CONF_PASSWORD)) + self._api = API( + config.get(CONF_CLIENT_ID), + config.get(CONF_CLIENT_SECRET), + config.get(CONF_USERNAME), + config.get(CONF_PASSWORD), + ) def setup(self, hass): """Set up a timer and start gathering devices.""" self._refresh() - track_utc_time_change(hass, - lambda now: self._refresh(), - second=range(0, 60, 30)) + track_utc_time_change( + hass, lambda now: self._refresh(), second=range(0, 60, 30) + ) def login(self, hass): """Perform a login on the FleetGO API.""" @@ -69,16 +72,17 @@ class FleetGoDeviceScanner: devices = self._api.get_devices() for device in devices: - if (not self._include or - device.license_plate in self._include): + if not self._include or device.license_plate in self._include: if device.active or device.current_address is None: device.get_map_details() - self._see(dev_id=device.plate_as_id, - gps=(device.latitude, device.longitude), - attributes=device.state_attributes, - icon='mdi:car') + self._see( + dev_id=device.plate_as_id, + gps=(device.latitude, device.longitude), + attributes=device.state_attributes, + icon="mdi:car", + ) except requests.exceptions.ConnectionError: - _LOGGER.error('ConnectionError: Could not connect to FleetGO') + _LOGGER.error("ConnectionError: Could not connect to FleetGO") diff --git a/homeassistant/components/flexit/climate.py b/homeassistant/components/flexit/climate.py index 86789285e60..951033849b6 100644 --- a/homeassistant/components/flexit/climate.py +++ b/homeassistant/components/flexit/climate.py @@ -16,21 +16,32 @@ from typing import List import voluptuous as vol from homeassistant.const import ( - CONF_NAME, CONF_SLAVE, TEMP_CELSIUS, - ATTR_TEMPERATURE, DEVICE_DEFAULT_NAME) + CONF_NAME, + CONF_SLAVE, + TEMP_CELSIUS, + ATTR_TEMPERATURE, + DEVICE_DEFAULT_NAME, +) from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA from homeassistant.components.climate.const import ( SUPPORT_TARGET_TEMPERATURE, - SUPPORT_FAN_MODE, HVAC_MODE_COOL) + SUPPORT_FAN_MODE, + HVAC_MODE_COOL, +) from homeassistant.components.modbus import ( - CONF_HUB, DEFAULT_HUB, DOMAIN as MODBUS_DOMAIN) + CONF_HUB, + DEFAULT_HUB, + DOMAIN as MODBUS_DOMAIN, +) import homeassistant.helpers.config_validation as cv -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_HUB, default=DEFAULT_HUB): cv.string, - vol.Required(CONF_SLAVE): vol.All(int, vol.Range(min=0, max=32)), - vol.Optional(CONF_NAME, default=DEVICE_DEFAULT_NAME): cv.string -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_HUB, default=DEFAULT_HUB): cv.string, + vol.Required(CONF_SLAVE): vol.All(int, vol.Range(min=0, max=32)), + vol.Optional(CONF_NAME, default=DEVICE_DEFAULT_NAME): cv.string, + } +) _LOGGER = logging.getLogger(__name__) @@ -51,6 +62,7 @@ class Flexit(ClimateDevice): def __init__(self, hub, modbus_slave, name): """Initialize the unit.""" from pyflexit import pyflexit + self._hub = hub self._name = name self._slave = modbus_slave @@ -58,7 +70,7 @@ class Flexit(ClimateDevice): self._current_temperature = None self._current_fan_mode = None self._current_operation = None - self._fan_modes = ['Off', 'Low', 'Medium', 'High'] + self._fan_modes = ["Off", "Low", "Medium", "High"] self._current_operation = None self._filter_hours = None self._filter_alarm = None @@ -81,8 +93,7 @@ class Flexit(ClimateDevice): self._target_temperature = self.unit.get_target_temp self._current_temperature = self.unit.get_temp - self._current_fan_mode =\ - self._fan_modes[self.unit.get_fan_speed] + self._current_fan_mode = self._fan_modes[self.unit.get_fan_speed] self._filter_hours = self.unit.get_filter_hours # Mechanical heat recovery, 0-100% self._heat_recovery = self.unit.get_heat_recovery @@ -101,12 +112,12 @@ class Flexit(ClimateDevice): def device_state_attributes(self): """Return device specific state attributes.""" return { - 'filter_hours': self._filter_hours, - 'filter_alarm': self._filter_alarm, - 'heat_recovery': self._heat_recovery, - 'heating': self._heating, - 'heater_enabled': self._heater_enabled, - 'cooling': self._cooling + "filter_hours": self._filter_hours, + "filter_alarm": self._filter_alarm, + "heat_recovery": self._heat_recovery, + "heating": self._heating, + "heater_enabled": self._heater_enabled, + "cooling": self._cooling, } @property diff --git a/homeassistant/components/flic/binary_sensor.py b/homeassistant/components/flic/binary_sensor.py index 3381550b578..4fa97334889 100644 --- a/homeassistant/components/flic/binary_sensor.py +++ b/homeassistant/components/flic/binary_sensor.py @@ -6,39 +6,45 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.const import ( - CONF_HOST, CONF_PORT, CONF_DISCOVERY, CONF_TIMEOUT, - EVENT_HOMEASSISTANT_STOP) -from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) + CONF_HOST, + CONF_PORT, + CONF_DISCOVERY, + CONF_TIMEOUT, + EVENT_HOMEASSISTANT_STOP, +) +from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA _LOGGER = logging.getLogger(__name__) DEFAULT_TIMEOUT = 3 -CLICK_TYPE_SINGLE = 'single' -CLICK_TYPE_DOUBLE = 'double' -CLICK_TYPE_HOLD = 'hold' +CLICK_TYPE_SINGLE = "single" +CLICK_TYPE_DOUBLE = "double" +CLICK_TYPE_HOLD = "hold" CLICK_TYPES = [CLICK_TYPE_SINGLE, CLICK_TYPE_DOUBLE, CLICK_TYPE_HOLD] -CONF_IGNORED_CLICK_TYPES = 'ignored_click_types' +CONF_IGNORED_CLICK_TYPES = "ignored_click_types" -DEFAULT_HOST = 'localhost' +DEFAULT_HOST = "localhost" DEFAULT_PORT = 5551 -EVENT_NAME = 'flic_click' -EVENT_DATA_NAME = 'button_name' -EVENT_DATA_ADDRESS = 'button_address' -EVENT_DATA_TYPE = 'click_type' -EVENT_DATA_QUEUED_TIME = 'queued_time' +EVENT_NAME = "flic_click" +EVENT_DATA_NAME = "button_name" +EVENT_DATA_ADDRESS = "button_address" +EVENT_DATA_TYPE = "click_type" +EVENT_DATA_QUEUED_TIME = "queued_time" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_DISCOVERY, default=True): cv.boolean, - vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, - vol.Optional(CONF_IGNORED_CLICK_TYPES): - vol.All(cv.ensure_list, [vol.In(CLICK_TYPES)]) -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_DISCOVERY, default=True): cv.boolean, + vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, + vol.Optional(CONF_IGNORED_CLICK_TYPES): vol.All( + cv.ensure_list, [vol.In(CLICK_TYPES)] + ), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -65,15 +71,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if discovery: start_scanning(config, add_entities, client) - hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, - lambda event: client.close()) + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, lambda event: client.close()) # Start the pyflic event handling thread threading.Thread(target=client.handle_events).start() def get_info_callback(items): """Add entities for already verified buttons.""" - addresses = items['bd_addr_of_verified_buttons'] or [] + addresses = items["bd_addr_of_verified_buttons"] or [] for address in addresses: setup_button(hass, config, add_entities, client, address) @@ -93,7 +98,8 @@ def start_scanning(config, add_entities, client): _LOGGER.info("Found new button %s", address) elif result != pyflic.ScanWizardResult.WizardFailedTimeout: _LOGGER.warning( - "Failed to connect to button %s. Reason: %s", address, result) + "Failed to connect to button %s. Reason: %s", address, result + ) # Restart scan wizard start_scanning(config, add_entities, client) @@ -160,7 +166,7 @@ class FlicButton(BinarySensorDevice): @property def name(self): """Return the name of the device.""" - return 'flic_{}'.format(self.address.replace(':', '')) + return "flic_{}".format(self.address.replace(":", "")) @property def address(self): @@ -180,21 +186,28 @@ class FlicButton(BinarySensorDevice): @property def device_state_attributes(self): """Return device specific state attributes.""" - return {'address': self.address} + return {"address": self.address} def _queued_event_check(self, click_type, time_diff): """Generate a log message and returns true if timeout exceeded.""" time_string = "{:d} {}".format( - time_diff, 'second' if time_diff == 1 else 'seconds') + time_diff, "second" if time_diff == 1 else "seconds" + ) if time_diff > self._timeout: _LOGGER.warning( "Queued %s dropped for %s. Time in queue was %s", - click_type, self.address, time_string) + click_type, + self.address, + time_string, + ) return True _LOGGER.info( "Queued %s allowed for %s. Time in queue was %s", - click_type, self.address, time_string) + click_type, + self.address, + time_string, + ) return False def _on_up_down(self, channel, click_type, was_queued, time_diff): @@ -218,18 +231,21 @@ class FlicButton(BinarySensorDevice): if hass_click_type in self._ignored_click_types: return - self._hass.bus.fire(EVENT_NAME, { - EVENT_DATA_NAME: self.name, - EVENT_DATA_ADDRESS: self.address, - EVENT_DATA_QUEUED_TIME: time_diff, - EVENT_DATA_TYPE: hass_click_type - }) + self._hass.bus.fire( + EVENT_NAME, + { + EVENT_DATA_NAME: self.name, + EVENT_DATA_ADDRESS: self.address, + EVENT_DATA_QUEUED_TIME: time_diff, + EVENT_DATA_TYPE: hass_click_type, + }, + ) - def _connection_status_changed( - self, channel, connection_status, disconnect_reason): + def _connection_status_changed(self, channel, connection_status, disconnect_reason): """Remove device, if button disconnects.""" import pyflic if connection_status == pyflic.ConnectionStatus.Disconnected: - _LOGGER.warning("Button (%s) disconnected. Reason: %s", - self.address, disconnect_reason) + _LOGGER.warning( + "Button (%s) disconnected. Reason: %s", self.address, disconnect_reason + ) diff --git a/homeassistant/components/flock/notify.py b/homeassistant/components/flock/notify.py index 93a478611db..07abd097c87 100644 --- a/homeassistant/components/flock/notify.py +++ b/homeassistant/components/flock/notify.py @@ -9,21 +9,18 @@ from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.components.notify import (PLATFORM_SCHEMA, - BaseNotificationService) +from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationService _LOGGER = logging.getLogger(__name__) -_RESOURCE = 'https://api.flock.com/hooks/sendMessage/' +_RESOURCE = "https://api.flock.com/hooks/sendMessage/" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_ACCESS_TOKEN): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({vol.Required(CONF_ACCESS_TOKEN): cv.string}) async def get_service(hass, config, discovery_info=None): """Get the Flock notification service.""" access_token = config.get(CONF_ACCESS_TOKEN) - url = '{}{}'.format(_RESOURCE, access_token) + url = "{}{}".format(_RESOURCE, access_token) session = async_get_clientsession(hass) return FlockNotificationService(url, session) @@ -39,7 +36,7 @@ class FlockNotificationService(BaseNotificationService): async def async_send_message(self, message, **kwargs): """Send the message to the user.""" - payload = {'text': message} + payload = {"text": message} _LOGGER.debug("Attempting to call Flock at %s", self._url) @@ -48,9 +45,11 @@ class FlockNotificationService(BaseNotificationService): response = await self._session.post(self._url, json=payload) result = await response.json() - if response.status != 200 or 'error' in result: + if response.status != 200 or "error" in result: _LOGGER.error( "Flock service returned HTTP status %d, response %s", - response.status, result) + response.status, + result, + ) except asyncio.TimeoutError: _LOGGER.error("Timeout accessing Flock at %s", self._url) diff --git a/homeassistant/components/flunearyou/sensor.py b/homeassistant/components/flunearyou/sensor.py index 148a3ee4159..97453c41af0 100644 --- a/homeassistant/components/flunearyou/sensor.py +++ b/homeassistant/components/flunearyou/sensor.py @@ -7,72 +7,78 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - ATTR_ATTRIBUTION, ATTR_STATE, CONF_LATITUDE, CONF_MONITORED_CONDITIONS, - CONF_LONGITUDE) + ATTR_ATTRIBUTION, + ATTR_STATE, + CONF_LATITUDE, + CONF_MONITORED_CONDITIONS, + CONF_LONGITUDE, +) from homeassistant.helpers import aiohttp_client from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -ATTR_CITY = 'city' -ATTR_REPORTED_DATE = 'reported_date' -ATTR_REPORTED_LATITUDE = 'reported_latitude' -ATTR_REPORTED_LONGITUDE = 'reported_longitude' -ATTR_STATE_REPORTS_LAST_WEEK = 'state_reports_last_week' -ATTR_STATE_REPORTS_THIS_WEEK = 'state_reports_this_week' -ATTR_ZIP_CODE = 'zip_code' +ATTR_CITY = "city" +ATTR_REPORTED_DATE = "reported_date" +ATTR_REPORTED_LATITUDE = "reported_latitude" +ATTR_REPORTED_LONGITUDE = "reported_longitude" +ATTR_STATE_REPORTS_LAST_WEEK = "state_reports_last_week" +ATTR_STATE_REPORTS_THIS_WEEK = "state_reports_this_week" +ATTR_ZIP_CODE = "zip_code" -DEFAULT_ATTRIBUTION = 'Data provided by Flu Near You' +DEFAULT_ATTRIBUTION = "Data provided by Flu Near You" MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=10) SCAN_INTERVAL = timedelta(minutes=30) -CATEGORY_CDC_REPORT = 'cdc_report' -CATEGORY_USER_REPORT = 'user_report' +CATEGORY_CDC_REPORT = "cdc_report" +CATEGORY_USER_REPORT = "user_report" -TYPE_CDC_LEVEL = 'level' -TYPE_CDC_LEVEL2 = 'level2' -TYPE_USER_CHICK = 'chick' -TYPE_USER_DENGUE = 'dengue' -TYPE_USER_FLU = 'flu' -TYPE_USER_LEPTO = 'lepto' -TYPE_USER_NO_SYMPTOMS = 'none' -TYPE_USER_SYMPTOMS = 'symptoms' -TYPE_USER_TOTAL = 'total' +TYPE_CDC_LEVEL = "level" +TYPE_CDC_LEVEL2 = "level2" +TYPE_USER_CHICK = "chick" +TYPE_USER_DENGUE = "dengue" +TYPE_USER_FLU = "flu" +TYPE_USER_LEPTO = "lepto" +TYPE_USER_NO_SYMPTOMS = "none" +TYPE_USER_SYMPTOMS = "symptoms" +TYPE_USER_TOTAL = "total" EXTENDED_TYPE_MAPPING = { - TYPE_USER_FLU: 'ili', - TYPE_USER_NO_SYMPTOMS: 'no_symptoms', - TYPE_USER_TOTAL: 'total_surveys', + TYPE_USER_FLU: "ili", + TYPE_USER_NO_SYMPTOMS: "no_symptoms", + TYPE_USER_TOTAL: "total_surveys", } SENSORS = { CATEGORY_CDC_REPORT: [ - (TYPE_CDC_LEVEL, 'CDC Level', 'mdi:biohazard', None), - (TYPE_CDC_LEVEL2, 'CDC Level 2', 'mdi:biohazard', None), + (TYPE_CDC_LEVEL, "CDC Level", "mdi:biohazard", None), + (TYPE_CDC_LEVEL2, "CDC Level 2", "mdi:biohazard", None), ], CATEGORY_USER_REPORT: [ - (TYPE_USER_CHICK, 'Avian Flu Symptoms', 'mdi:alert', 'reports'), - (TYPE_USER_DENGUE, 'Dengue Fever Symptoms', 'mdi:alert', 'reports'), - (TYPE_USER_FLU, 'Flu Symptoms', 'mdi:alert', 'reports'), - (TYPE_USER_LEPTO, 'Leptospirosis Symptoms', 'mdi:alert', 'reports'), - (TYPE_USER_NO_SYMPTOMS, 'No Symptoms', 'mdi:alert', 'reports'), - (TYPE_USER_SYMPTOMS, 'Flu-like Symptoms', 'mdi:alert', 'reports'), - (TYPE_USER_TOTAL, 'Total Symptoms', 'mdi:alert', 'reports'), - ] + (TYPE_USER_CHICK, "Avian Flu Symptoms", "mdi:alert", "reports"), + (TYPE_USER_DENGUE, "Dengue Fever Symptoms", "mdi:alert", "reports"), + (TYPE_USER_FLU, "Flu Symptoms", "mdi:alert", "reports"), + (TYPE_USER_LEPTO, "Leptospirosis Symptoms", "mdi:alert", "reports"), + (TYPE_USER_NO_SYMPTOMS, "No Symptoms", "mdi:alert", "reports"), + (TYPE_USER_SYMPTOMS, "Flu-like Symptoms", "mdi:alert", "reports"), + (TYPE_USER_TOTAL, "Total Symptoms", "mdi:alert", "reports"), + ], } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_LATITUDE): cv.latitude, - vol.Optional(CONF_LONGITUDE): cv.longitude, - vol.Required(CONF_MONITORED_CONDITIONS, default=list(SENSORS)): - vol.All(cv.ensure_list, [vol.In(SENSORS)]) -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_LATITUDE): cv.latitude, + vol.Optional(CONF_LONGITUDE): cv.longitude, + vol.Required(CONF_MONITORED_CONDITIONS, default=list(SENSORS)): vol.All( + cv.ensure_list, [vol.In(SENSORS)] + ), + } +) -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Configure the platform and add the sensors.""" from pyflunearyou import Client @@ -82,8 +88,8 @@ async def async_setup_platform( longitude = config.get(CONF_LONGITUDE, hass.config.longitude) fny = FluNearYouData( - Client(websession), latitude, longitude, - config[CONF_MONITORED_CONDITIONS]) + Client(websession), latitude, longitude, config[CONF_MONITORED_CONDITIONS] + ) await fny.async_update() sensors = [ @@ -137,8 +143,7 @@ class FluNearYouSensor(Entity): @property def unique_id(self): """Return a unique, HASS-friendly identifier for this entity.""" - return '{0},{1}_{2}'.format( - self.fny.latitude, self.fny.longitude, self._kind) + return "{0},{1}_{2}".format(self.fny.latitude, self.fny.longitude, self._kind) @property def unit_of_measurement(self): @@ -153,37 +158,51 @@ class FluNearYouSensor(Entity): user_data = self.fny.data.get(CATEGORY_USER_REPORT) if self._category == CATEGORY_CDC_REPORT and cdc_data: - self._attrs.update({ - ATTR_REPORTED_DATE: cdc_data['week_date'], - ATTR_STATE: cdc_data['name'], - }) + self._attrs.update( + { + ATTR_REPORTED_DATE: cdc_data["week_date"], + ATTR_STATE: cdc_data["name"], + } + ) self._state = cdc_data[self._kind] elif self._category == CATEGORY_USER_REPORT and user_data: - self._attrs.update({ - ATTR_CITY: user_data['local']['city'].split('(')[0], - ATTR_REPORTED_LATITUDE: user_data['local']['latitude'], - ATTR_REPORTED_LONGITUDE: user_data['local']['longitude'], - ATTR_STATE: user_data['state']['name'], - ATTR_ZIP_CODE: user_data['local']['zip'], - }) + self._attrs.update( + { + ATTR_CITY: user_data["local"]["city"].split("(")[0], + ATTR_REPORTED_LATITUDE: user_data["local"]["latitude"], + ATTR_REPORTED_LONGITUDE: user_data["local"]["longitude"], + ATTR_STATE: user_data["state"]["name"], + ATTR_ZIP_CODE: user_data["local"]["zip"], + } + ) - if self._kind in user_data['state']['data']: + if self._kind in user_data["state"]["data"]: states_key = self._kind elif self._kind in EXTENDED_TYPE_MAPPING: states_key = EXTENDED_TYPE_MAPPING[self._kind] - self._attrs[ATTR_STATE_REPORTS_THIS_WEEK] = user_data['state'][ - 'data'][states_key] - self._attrs[ATTR_STATE_REPORTS_LAST_WEEK] = user_data['state'][ - 'last_week_data'][states_key] + self._attrs[ATTR_STATE_REPORTS_THIS_WEEK] = user_data["state"]["data"][ + states_key + ] + self._attrs[ATTR_STATE_REPORTS_LAST_WEEK] = user_data["state"][ + "last_week_data" + ][states_key] if self._kind == TYPE_USER_TOTAL: self._state = sum( - v for k, v in user_data['local'].items() if k in ( - TYPE_USER_CHICK, TYPE_USER_DENGUE, TYPE_USER_FLU, - TYPE_USER_LEPTO, TYPE_USER_SYMPTOMS)) + v + for k, v in user_data["local"].items() + if k + in ( + TYPE_USER_CHICK, + TYPE_USER_DENGUE, + TYPE_USER_FLU, + TYPE_USER_LEPTO, + TYPE_USER_SYMPTOMS, + ) + ) else: - self._state = user_data['local'][self._kind] + self._state = user_data["local"][self._kind] class FluNearYouData: @@ -202,17 +221,15 @@ class FluNearYouData: """Update Flu Near You data.""" from pyflunearyou.errors import FluNearYouError - for key, method in [(CATEGORY_CDC_REPORT, - self._client.cdc_reports.status_by_coordinates), - (CATEGORY_USER_REPORT, - self._client.user_reports.status_by_coordinates)]: + for key, method in [ + (CATEGORY_CDC_REPORT, self._client.cdc_reports.status_by_coordinates), + (CATEGORY_USER_REPORT, self._client.user_reports.status_by_coordinates), + ]: if key in self._sensor_types: try: - self.data[key] = await method( - self.latitude, self.longitude) + self.data[key] = await method(self.latitude, self.longitude) except FluNearYouError as err: - _LOGGER.error( - 'There was an error with "%s" data: %s', key, err) + _LOGGER.error('There was an error with "%s" data: %s', key, err) self.data[key] = {} - _LOGGER.debug('New data stored: %s', self.data) + _LOGGER.debug("New data stored: %s", self.data) diff --git a/homeassistant/components/flux/switch.py b/homeassistant/components/flux/switch.py index f0134f04d89..800ccd1938f 100644 --- a/homeassistant/components/flux/switch.py +++ b/homeassistant/components/flux/switch.py @@ -13,60 +13,83 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.light import ( - is_on, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_RGB_COLOR, ATTR_TRANSITION, - ATTR_WHITE_VALUE, ATTR_XY_COLOR, DOMAIN as LIGHT_DOMAIN, VALID_TRANSITION) + is_on, + ATTR_BRIGHTNESS, + ATTR_COLOR_TEMP, + ATTR_RGB_COLOR, + ATTR_TRANSITION, + ATTR_WHITE_VALUE, + ATTR_XY_COLOR, + DOMAIN as LIGHT_DOMAIN, + VALID_TRANSITION, +) from homeassistant.components.switch import DOMAIN, SwitchDevice from homeassistant.const import ( - ATTR_ENTITY_ID, CONF_NAME, CONF_PLATFORM, CONF_LIGHTS, CONF_MODE, - SERVICE_TURN_ON, SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET) + ATTR_ENTITY_ID, + CONF_NAME, + CONF_PLATFORM, + CONF_LIGHTS, + CONF_MODE, + SERVICE_TURN_ON, + SUN_EVENT_SUNRISE, + SUN_EVENT_SUNSET, +) from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.sun import get_astral_event_date from homeassistant.util import slugify from homeassistant.util.color import ( - color_temperature_to_rgb, color_RGB_to_xy_brightness, - color_temperature_kelvin_to_mired) + color_temperature_to_rgb, + color_RGB_to_xy_brightness, + color_temperature_kelvin_to_mired, +) from homeassistant.util.dt import utcnow as dt_utcnow, as_local _LOGGER = logging.getLogger(__name__) -CONF_START_TIME = 'start_time' -CONF_STOP_TIME = 'stop_time' -CONF_START_CT = 'start_colortemp' -CONF_SUNSET_CT = 'sunset_colortemp' -CONF_STOP_CT = 'stop_colortemp' -CONF_BRIGHTNESS = 'brightness' -CONF_DISABLE_BRIGHTNESS_ADJUST = 'disable_brightness_adjust' -CONF_INTERVAL = 'interval' +CONF_START_TIME = "start_time" +CONF_STOP_TIME = "stop_time" +CONF_START_CT = "start_colortemp" +CONF_SUNSET_CT = "sunset_colortemp" +CONF_STOP_CT = "stop_colortemp" +CONF_BRIGHTNESS = "brightness" +CONF_DISABLE_BRIGHTNESS_ADJUST = "disable_brightness_adjust" +CONF_INTERVAL = "interval" -MODE_XY = 'xy' -MODE_MIRED = 'mired' -MODE_RGB = 'rgb' +MODE_XY = "xy" +MODE_MIRED = "mired" +MODE_RGB = "rgb" DEFAULT_MODE = MODE_XY -PLATFORM_SCHEMA = vol.Schema({ - vol.Required(CONF_PLATFORM): 'flux', - vol.Required(CONF_LIGHTS): cv.entity_ids, - vol.Optional(CONF_NAME, default="Flux"): cv.string, - vol.Optional(CONF_START_TIME): cv.time, - vol.Optional(CONF_STOP_TIME): cv.time, - vol.Optional(CONF_START_CT, default=4000): - vol.All(vol.Coerce(int), vol.Range(min=1000, max=40000)), - vol.Optional(CONF_SUNSET_CT, default=3000): - vol.All(vol.Coerce(int), vol.Range(min=1000, max=40000)), - vol.Optional(CONF_STOP_CT, default=1900): - vol.All(vol.Coerce(int), vol.Range(min=1000, max=40000)), - vol.Optional(CONF_BRIGHTNESS): - vol.All(vol.Coerce(int), vol.Range(min=0, max=255)), - vol.Optional(CONF_DISABLE_BRIGHTNESS_ADJUST): cv.boolean, - vol.Optional(CONF_MODE, default=DEFAULT_MODE): - vol.Any(MODE_XY, MODE_MIRED, MODE_RGB), - vol.Optional(CONF_INTERVAL, default=30): cv.positive_int, - vol.Optional(ATTR_TRANSITION, default=30): VALID_TRANSITION -}) +PLATFORM_SCHEMA = vol.Schema( + { + vol.Required(CONF_PLATFORM): "flux", + vol.Required(CONF_LIGHTS): cv.entity_ids, + vol.Optional(CONF_NAME, default="Flux"): cv.string, + vol.Optional(CONF_START_TIME): cv.time, + vol.Optional(CONF_STOP_TIME): cv.time, + vol.Optional(CONF_START_CT, default=4000): vol.All( + vol.Coerce(int), vol.Range(min=1000, max=40000) + ), + vol.Optional(CONF_SUNSET_CT, default=3000): vol.All( + vol.Coerce(int), vol.Range(min=1000, max=40000) + ), + vol.Optional(CONF_STOP_CT, default=1900): vol.All( + vol.Coerce(int), vol.Range(min=1000, max=40000) + ), + vol.Optional(CONF_BRIGHTNESS): vol.All( + vol.Coerce(int), vol.Range(min=0, max=255) + ), + vol.Optional(CONF_DISABLE_BRIGHTNESS_ADJUST): cv.boolean, + vol.Optional(CONF_MODE, default=DEFAULT_MODE): vol.Any( + MODE_XY, MODE_MIRED, MODE_RGB + ), + vol.Optional(CONF_INTERVAL, default=30): cv.positive_int, + vol.Optional(ATTR_TRANSITION, default=30): VALID_TRANSITION, + } +) -async def async_set_lights_xy(hass, lights, x_val, y_val, brightness, - transition): +async def async_set_lights_xy(hass, lights, x_val, y_val, brightness, transition): """Set color of array of lights.""" for light in lights: if is_on(hass, light): @@ -78,8 +101,7 @@ async def async_set_lights_xy(hass, lights, x_val, y_val, brightness, service_data[ATTR_WHITE_VALUE] = brightness if transition is not None: service_data[ATTR_TRANSITION] = transition - await hass.services.async_call( - LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) + await hass.services.async_call(LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) async def async_set_lights_temp(hass, lights, mired, brightness, transition): @@ -93,8 +115,7 @@ async def async_set_lights_temp(hass, lights, mired, brightness, transition): service_data[ATTR_BRIGHTNESS] = brightness if transition is not None: service_data[ATTR_TRANSITION] = transition - await hass.services.async_call( - LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) + await hass.services.async_call(LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) async def async_set_lights_rgb(hass, lights, rgb, transition): @@ -106,12 +127,10 @@ async def async_set_lights_rgb(hass, lights, rgb, transition): service_data[ATTR_RGB_COLOR] = rgb if transition is not None: service_data[ATTR_TRANSITION] = transition - await hass.services.async_call( - LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) + await hass.services.async_call(LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Flux switches.""" name = config.get(CONF_NAME) lights = config.get(CONF_LIGHTS) @@ -125,27 +144,50 @@ async def async_setup_platform(hass, config, async_add_entities, mode = config.get(CONF_MODE) interval = config.get(CONF_INTERVAL) transition = config.get(ATTR_TRANSITION) - flux = FluxSwitch(name, hass, lights, start_time, stop_time, - start_colortemp, sunset_colortemp, stop_colortemp, - brightness, disable_brightness_adjust, mode, interval, - transition) + flux = FluxSwitch( + name, + hass, + lights, + start_time, + stop_time, + start_colortemp, + sunset_colortemp, + stop_colortemp, + brightness, + disable_brightness_adjust, + mode, + interval, + transition, + ) async_add_entities([flux]) async def async_update(call=None): """Update lights.""" await flux.async_flux_update() - service_name = slugify("{} {}".format(name, 'update')) + service_name = slugify("{} {}".format(name, "update")) hass.services.async_register(DOMAIN, service_name, async_update) class FluxSwitch(SwitchDevice): """Representation of a Flux switch.""" - def __init__(self, name, hass, lights, start_time, stop_time, - start_colortemp, sunset_colortemp, stop_colortemp, - brightness, disable_brightness_adjust, mode, interval, - transition): + def __init__( + self, + name, + hass, + lights, + start_time, + stop_time, + start_colortemp, + sunset_colortemp, + stop_colortemp, + brightness, + disable_brightness_adjust, + mode, + interval, + transition, + ): """Initialize the Flux switch.""" self._name = name self.hass = hass @@ -180,7 +222,8 @@ class FluxSwitch(SwitchDevice): self.unsub_tracker = async_track_time_interval( self.hass, self.async_flux_update, - datetime.timedelta(seconds=self._interval)) + datetime.timedelta(seconds=self._interval), + ) # Make initial update await self.async_flux_update() @@ -217,7 +260,7 @@ class FluxSwitch(SwitchDevice): if start_time < now < sunset: # Daytime - time_state = 'day' + time_state = "day" temp_range = abs(self._start_colortemp - self._sunset_colortemp) day_length = int(sunset.timestamp() - start_time.timestamp()) seconds_from_start = int(now.timestamp() - start_time.timestamp()) @@ -229,7 +272,7 @@ class FluxSwitch(SwitchDevice): temp = self._start_colortemp + temp_offset else: # Night time - time_state = 'night' + time_state = "night" if now < stop_time: if stop_time < start_time and stop_time.day == sunset.day: @@ -238,10 +281,8 @@ class FluxSwitch(SwitchDevice): else: sunset_time = sunset - night_length = int(stop_time.timestamp() - - sunset_time.timestamp()) - seconds_from_sunset = int(now.timestamp() - - sunset_time.timestamp()) + night_length = int(stop_time.timestamp() - sunset_time.timestamp()) + seconds_from_sunset = int(now.timestamp() - sunset_time.timestamp()) percentage_complete = seconds_from_sunset / night_length else: percentage_complete = 1 @@ -258,44 +299,60 @@ class FluxSwitch(SwitchDevice): if self._disable_brightness_adjust: brightness = None if self._mode == MODE_XY: - await async_set_lights_xy(self.hass, self._lights, x_val, - y_val, brightness, self._transition) - _LOGGER.info("Lights updated to x:%s y:%s brightness:%s, %s%% " - "of %s cycle complete at %s", x_val, y_val, - brightness, round( - percentage_complete * 100), time_state, now) + await async_set_lights_xy( + self.hass, self._lights, x_val, y_val, brightness, self._transition + ) + _LOGGER.info( + "Lights updated to x:%s y:%s brightness:%s, %s%% " + "of %s cycle complete at %s", + x_val, + y_val, + brightness, + round(percentage_complete * 100), + time_state, + now, + ) elif self._mode == MODE_RGB: - await async_set_lights_rgb(self.hass, self._lights, rgb, - self._transition) - _LOGGER.info("Lights updated to rgb:%s, %s%% " - "of %s cycle complete at %s", rgb, - round(percentage_complete * 100), time_state, now) + await async_set_lights_rgb(self.hass, self._lights, rgb, self._transition) + _LOGGER.info( + "Lights updated to rgb:%s, %s%% " "of %s cycle complete at %s", + rgb, + round(percentage_complete * 100), + time_state, + now, + ) else: # Convert to mired and clamp to allowed values mired = color_temperature_kelvin_to_mired(temp) - await async_set_lights_temp(self.hass, self._lights, mired, - brightness, self._transition) - _LOGGER.info("Lights updated to mired:%s brightness:%s, %s%% " - "of %s cycle complete at %s", mired, brightness, - round(percentage_complete * 100), time_state, now) + await async_set_lights_temp( + self.hass, self._lights, mired, brightness, self._transition + ) + _LOGGER.info( + "Lights updated to mired:%s brightness:%s, %s%% " + "of %s cycle complete at %s", + mired, + brightness, + round(percentage_complete * 100), + time_state, + now, + ) def find_start_time(self, now): """Return sunrise or start_time if given.""" if self._start_time: sunrise = now.replace( - hour=self._start_time.hour, minute=self._start_time.minute, - second=0) + hour=self._start_time.hour, minute=self._start_time.minute, second=0 + ) else: - sunrise = get_astral_event_date(self.hass, SUN_EVENT_SUNRISE, - now.date()) + sunrise = get_astral_event_date(self.hass, SUN_EVENT_SUNRISE, now.date()) return sunrise def find_stop_time(self, now): """Return dusk or stop_time if given.""" if self._stop_time: dusk = now.replace( - hour=self._stop_time.hour, minute=self._stop_time.minute, - second=0) + hour=self._stop_time.hour, minute=self._stop_time.minute, second=0 + ) else: - dusk = get_astral_event_date(self.hass, 'dusk', now.date()) + dusk = get_astral_event_date(self.hass, "dusk", now.date()) return dusk diff --git a/homeassistant/components/flux_led/light.py b/homeassistant/components/flux_led/light.py index 02bc20ccb1d..cef0387111a 100644 --- a/homeassistant/components/flux_led/light.py +++ b/homeassistant/components/flux_led/light.py @@ -9,33 +9,45 @@ import voluptuous as vol from homeassistant.const import CONF_DEVICES, CONF_NAME, CONF_PROTOCOL from homeassistant.components.light import ( - ATTR_BRIGHTNESS, ATTR_HS_COLOR, ATTR_EFFECT, ATTR_WHITE_VALUE, - ATTR_COLOR_TEMP, EFFECT_COLORLOOP, EFFECT_RANDOM, SUPPORT_BRIGHTNESS, - SUPPORT_EFFECT, SUPPORT_COLOR, SUPPORT_WHITE_VALUE, SUPPORT_COLOR_TEMP, - Light, PLATFORM_SCHEMA) + ATTR_BRIGHTNESS, + ATTR_HS_COLOR, + ATTR_EFFECT, + ATTR_WHITE_VALUE, + ATTR_COLOR_TEMP, + EFFECT_COLORLOOP, + EFFECT_RANDOM, + SUPPORT_BRIGHTNESS, + SUPPORT_EFFECT, + SUPPORT_COLOR, + SUPPORT_WHITE_VALUE, + SUPPORT_COLOR_TEMP, + Light, + PLATFORM_SCHEMA, +) import homeassistant.helpers.config_validation as cv import homeassistant.util.color as color_util _LOGGER = logging.getLogger(__name__) -CONF_AUTOMATIC_ADD = 'automatic_add' -CONF_CUSTOM_EFFECT = 'custom_effect' -CONF_COLORS = 'colors' -CONF_SPEED_PCT = 'speed_pct' -CONF_TRANSITION = 'transition' -ATTR_MODE = 'mode' +CONF_AUTOMATIC_ADD = "automatic_add" +CONF_CUSTOM_EFFECT = "custom_effect" +CONF_COLORS = "colors" +CONF_SPEED_PCT = "speed_pct" +CONF_TRANSITION = "transition" +ATTR_MODE = "mode" -DOMAIN = 'flux_led' +DOMAIN = "flux_led" -SUPPORT_FLUX_LED = (SUPPORT_BRIGHTNESS | SUPPORT_EFFECT | - SUPPORT_COLOR | SUPPORT_COLOR_TEMP) +SUPPORT_FLUX_LED = ( + SUPPORT_BRIGHTNESS | SUPPORT_EFFECT | SUPPORT_COLOR | SUPPORT_COLOR_TEMP +) -MODE_RGB = 'rgb' -MODE_RGBW = 'rgbw' +MODE_RGB = "rgb" +MODE_RGBW = "rgbw" # This mode enables white value to be controlled by brightness. # RGB value is ignored when this mode is specified. -MODE_WHITE = 'w' +MODE_WHITE = "w" # Constant color temp values for 2 flux_led special modes # Warm-white and Cool-white. Details on #23704 @@ -43,94 +55,107 @@ COLOR_TEMP_WARM_WHITE = 333 COLOR_TEMP_COOL_WHITE = 250 # List of supported effects which aren't already declared in LIGHT -EFFECT_RED_FADE = 'red_fade' -EFFECT_GREEN_FADE = 'green_fade' -EFFECT_BLUE_FADE = 'blue_fade' -EFFECT_YELLOW_FADE = 'yellow_fade' -EFFECT_CYAN_FADE = 'cyan_fade' -EFFECT_PURPLE_FADE = 'purple_fade' -EFFECT_WHITE_FADE = 'white_fade' -EFFECT_RED_GREEN_CROSS_FADE = 'rg_cross_fade' -EFFECT_RED_BLUE_CROSS_FADE = 'rb_cross_fade' -EFFECT_GREEN_BLUE_CROSS_FADE = 'gb_cross_fade' -EFFECT_COLORSTROBE = 'colorstrobe' -EFFECT_RED_STROBE = 'red_strobe' -EFFECT_GREEN_STROBE = 'green_strobe' -EFFECT_BLUE_STROBE = 'blue_strobe' -EFFECT_YELLOW_STROBE = 'yellow_strobe' -EFFECT_CYAN_STROBE = 'cyan_strobe' -EFFECT_PURPLE_STROBE = 'purple_strobe' -EFFECT_WHITE_STROBE = 'white_strobe' -EFFECT_COLORJUMP = 'colorjump' -EFFECT_CUSTOM = 'custom' +EFFECT_RED_FADE = "red_fade" +EFFECT_GREEN_FADE = "green_fade" +EFFECT_BLUE_FADE = "blue_fade" +EFFECT_YELLOW_FADE = "yellow_fade" +EFFECT_CYAN_FADE = "cyan_fade" +EFFECT_PURPLE_FADE = "purple_fade" +EFFECT_WHITE_FADE = "white_fade" +EFFECT_RED_GREEN_CROSS_FADE = "rg_cross_fade" +EFFECT_RED_BLUE_CROSS_FADE = "rb_cross_fade" +EFFECT_GREEN_BLUE_CROSS_FADE = "gb_cross_fade" +EFFECT_COLORSTROBE = "colorstrobe" +EFFECT_RED_STROBE = "red_strobe" +EFFECT_GREEN_STROBE = "green_strobe" +EFFECT_BLUE_STROBE = "blue_strobe" +EFFECT_YELLOW_STROBE = "yellow_strobe" +EFFECT_CYAN_STROBE = "cyan_strobe" +EFFECT_PURPLE_STROBE = "purple_strobe" +EFFECT_WHITE_STROBE = "white_strobe" +EFFECT_COLORJUMP = "colorjump" +EFFECT_CUSTOM = "custom" EFFECT_MAP = { - EFFECT_COLORLOOP: 0x25, - EFFECT_RED_FADE: 0x26, - EFFECT_GREEN_FADE: 0x27, - EFFECT_BLUE_FADE: 0x28, - EFFECT_YELLOW_FADE: 0x29, - EFFECT_CYAN_FADE: 0x2a, - EFFECT_PURPLE_FADE: 0x2b, - EFFECT_WHITE_FADE: 0x2c, - EFFECT_RED_GREEN_CROSS_FADE: 0x2d, - EFFECT_RED_BLUE_CROSS_FADE: 0x2e, - EFFECT_GREEN_BLUE_CROSS_FADE: 0x2f, - EFFECT_COLORSTROBE: 0x30, - EFFECT_RED_STROBE: 0x31, - EFFECT_GREEN_STROBE: 0x32, - EFFECT_BLUE_STROBE: 0x33, - EFFECT_YELLOW_STROBE: 0x34, - EFFECT_CYAN_STROBE: 0x35, - EFFECT_PURPLE_STROBE: 0x36, - EFFECT_WHITE_STROBE: 0x37, - EFFECT_COLORJUMP: 0x38 + EFFECT_COLORLOOP: 0x25, + EFFECT_RED_FADE: 0x26, + EFFECT_GREEN_FADE: 0x27, + EFFECT_BLUE_FADE: 0x28, + EFFECT_YELLOW_FADE: 0x29, + EFFECT_CYAN_FADE: 0x2A, + EFFECT_PURPLE_FADE: 0x2B, + EFFECT_WHITE_FADE: 0x2C, + EFFECT_RED_GREEN_CROSS_FADE: 0x2D, + EFFECT_RED_BLUE_CROSS_FADE: 0x2E, + EFFECT_GREEN_BLUE_CROSS_FADE: 0x2F, + EFFECT_COLORSTROBE: 0x30, + EFFECT_RED_STROBE: 0x31, + EFFECT_GREEN_STROBE: 0x32, + EFFECT_BLUE_STROBE: 0x33, + EFFECT_YELLOW_STROBE: 0x34, + EFFECT_CYAN_STROBE: 0x35, + EFFECT_PURPLE_STROBE: 0x36, + EFFECT_WHITE_STROBE: 0x37, + EFFECT_COLORJUMP: 0x38, } EFFECT_CUSTOM_CODE = 0x60 -TRANSITION_GRADUAL = 'gradual' -TRANSITION_JUMP = 'jump' -TRANSITION_STROBE = 'strobe' +TRANSITION_GRADUAL = "gradual" +TRANSITION_JUMP = "jump" +TRANSITION_STROBE = "strobe" FLUX_EFFECT_LIST = sorted(list(EFFECT_MAP)) + [EFFECT_RANDOM] -CUSTOM_EFFECT_SCHEMA = vol.Schema({ - vol.Required(CONF_COLORS): - vol.All(cv.ensure_list, vol.Length(min=1, max=16), - [vol.All(vol.ExactSequence((cv.byte, cv.byte, cv.byte)), - vol.Coerce(tuple))]), - vol.Optional(CONF_SPEED_PCT, default=50): - vol.All(vol.Range(min=0, max=100), vol.Coerce(int)), - vol.Optional(CONF_TRANSITION, default=TRANSITION_GRADUAL): - vol.All(cv.string, vol.In( - [TRANSITION_GRADUAL, TRANSITION_JUMP, TRANSITION_STROBE])), -}) +CUSTOM_EFFECT_SCHEMA = vol.Schema( + { + vol.Required(CONF_COLORS): vol.All( + cv.ensure_list, + vol.Length(min=1, max=16), + [ + vol.All( + vol.ExactSequence((cv.byte, cv.byte, cv.byte)), vol.Coerce(tuple) + ) + ], + ), + vol.Optional(CONF_SPEED_PCT, default=50): vol.All( + vol.Range(min=0, max=100), vol.Coerce(int) + ), + vol.Optional(CONF_TRANSITION, default=TRANSITION_GRADUAL): vol.All( + cv.string, vol.In([TRANSITION_GRADUAL, TRANSITION_JUMP, TRANSITION_STROBE]) + ), + } +) -DEVICE_SCHEMA = vol.Schema({ - vol.Optional(CONF_NAME): cv.string, - vol.Optional(ATTR_MODE, default=MODE_RGBW): - vol.All(cv.string, vol.In([MODE_RGBW, MODE_RGB, MODE_WHITE])), - vol.Optional(CONF_PROTOCOL): - vol.All(cv.string, vol.In(['ledenet'])), - vol.Optional(CONF_CUSTOM_EFFECT): CUSTOM_EFFECT_SCHEMA, -}) +DEVICE_SCHEMA = vol.Schema( + { + vol.Optional(CONF_NAME): cv.string, + vol.Optional(ATTR_MODE, default=MODE_RGBW): vol.All( + cv.string, vol.In([MODE_RGBW, MODE_RGB, MODE_WHITE]) + ), + vol.Optional(CONF_PROTOCOL): vol.All(cv.string, vol.In(["ledenet"])), + vol.Optional(CONF_CUSTOM_EFFECT): CUSTOM_EFFECT_SCHEMA, + } +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_DEVICES, default={}): {cv.string: DEVICE_SCHEMA}, - vol.Optional(CONF_AUTOMATIC_ADD, default=False): cv.boolean, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_DEVICES, default={}): {cv.string: DEVICE_SCHEMA}, + vol.Optional(CONF_AUTOMATIC_ADD, default=False): cv.boolean, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Flux lights.""" import flux_led + lights = [] light_ips = [] for ipaddr, device_config in config.get(CONF_DEVICES, {}).items(): device = {} - device['name'] = device_config[CONF_NAME] - device['ipaddr'] = ipaddr + device["name"] = device_config[CONF_NAME] + device["ipaddr"] = ipaddr device[CONF_PROTOCOL] = device_config.get(CONF_PROTOCOL) device[ATTR_MODE] = device_config[ATTR_MODE] device[CONF_CUSTOM_EFFECT] = device_config.get(CONF_CUSTOM_EFFECT) @@ -146,10 +171,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None): scanner = flux_led.BulbScanner() scanner.scan(timeout=10) for device in scanner.getBulbInfo(): - ipaddr = device['ipaddr'] + ipaddr = device["ipaddr"] if ipaddr in light_ips: continue - device['name'] = '{} {}'.format(device['id'], ipaddr) + device["name"] = "{} {}".format(device["id"], ipaddr) device[ATTR_MODE] = None device[CONF_PROTOCOL] = None device[CONF_CUSTOM_EFFECT] = None @@ -164,8 +189,8 @@ class FluxLight(Light): def __init__(self, device): """Initialize the light.""" - self._name = device['name'] - self._ipaddr = device['ipaddr'] + self._name = device["name"] + self._ipaddr = device["ipaddr"] self._protocol = device[CONF_PROTOCOL] self._mode = device[ATTR_MODE] self._custom_effect = device[CONF_CUSTOM_EFFECT] @@ -261,8 +286,7 @@ class FluxLight(Light): async def async_turn_on(self, **kwargs): """Turn the specified or all lights on and wait for state.""" - await self.hass.async_add_executor_job(partial(self._turn_on, - **kwargs)) + await self.hass.async_add_executor_job(partial(self._turn_on, **kwargs)) # The bulb needs a bit to tell its new values, # so we wait 1 second before updating await sleep(1) @@ -277,8 +301,9 @@ class FluxLight(Light): white = kwargs.get(ATTR_WHITE_VALUE) color_temp = kwargs.get(ATTR_COLOR_TEMP) - if all(item is None for item in - [hs_color, brightness, effect, white, color_temp]): + if all( + item is None for item in [hs_color, brightness, effect, white, color_temp] + ): return # handle W only mode (use brightness instead of white value) @@ -291,15 +316,18 @@ class FluxLight(Light): if effect is not None: # Random color effect if effect == EFFECT_RANDOM: - self._bulb.setRgb(random.randint(0, 255), - random.randint(0, 255), - random.randint(0, 255)) + self._bulb.setRgb( + random.randint(0, 255), + random.randint(0, 255), + random.randint(0, 255), + ) elif effect == EFFECT_CUSTOM: if self._custom_effect: self._bulb.setCustomPattern( self._custom_effect[CONF_COLORS], self._custom_effect[CONF_SPEED_PCT], - self._custom_effect[CONF_TRANSITION]) + self._custom_effect[CONF_TRANSITION], + ) # Effect selection elif effect in EFFECT_MAP: self._bulb.setPresetPattern(EFFECT_MAP[effect], 50) @@ -314,8 +342,7 @@ class FluxLight(Light): elif color_temp == COLOR_TEMP_COOL_WHITE: self._bulb.setRgbw(w2=brightness) else: - self._bulb.setRgbw( - *color_util.color_temperature_to_rgb(color_temp)) + self._bulb.setRgbw(*color_util.color_temperature_to_rgb(color_temp)) return # Preserve current brightness on color/white level change @@ -324,8 +351,7 @@ class FluxLight(Light): brightness = self.brightness color = (hs_color[0], hs_color[1], brightness / 255 * 100) elif brightness is not None: - color = (self._color[0], self._color[1], - brightness / 255 * 100) + color = (self._color[0], self._color[1], brightness / 255 * 100) # handle RGBW mode if self._mode == MODE_RGBW: if white is None: @@ -349,8 +375,9 @@ class FluxLight(Light): except socket.error: self._disconnect() if not self._error_reported: - _LOGGER.warning("Failed to connect to bulb %s, %s", - self._ipaddr, self._name) + _LOGGER.warning( + "Failed to connect to bulb %s, %s", self._ipaddr, self._name + ) self._error_reported = True return self._bulb.update_state(retry=2) diff --git a/homeassistant/components/folder/sensor.py b/homeassistant/components/folder/sensor.py index d742166a192..b06d77d93c9 100644 --- a/homeassistant/components/folder/sensor.py +++ b/homeassistant/components/folder/sensor.py @@ -12,16 +12,18 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA _LOGGER = logging.getLogger(__name__) -CONF_FOLDER_PATHS = 'folder' -CONF_FILTER = 'filter' -DEFAULT_FILTER = '*' +CONF_FOLDER_PATHS = "folder" +CONF_FILTER = "filter" +DEFAULT_FILTER = "*" SCAN_INTERVAL = timedelta(minutes=1) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_FOLDER_PATHS): cv.isdir, - vol.Optional(CONF_FILTER, default=DEFAULT_FILTER): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_FOLDER_PATHS): cv.isdir, + vol.Optional(CONF_FILTER, default=DEFAULT_FILTER): cv.string, + } +) def get_files_list(folder_path, filter_term): @@ -51,17 +53,17 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class Folder(Entity): """Representation of a folder.""" - ICON = 'mdi:folder' + ICON = "mdi:folder" def __init__(self, folder_path, filter_term): """Initialize the data object.""" - folder_path = os.path.join(folder_path, '') # If no trailing / add it - self._folder_path = folder_path # Need to check its a valid path + folder_path = os.path.join(folder_path, "") # If no trailing / add it + self._folder_path = folder_path # Need to check its a valid path self._filter_term = filter_term self._number_of_files = None self._size = None self._name = os.path.split(os.path.split(folder_path)[0])[1] - self._unit_of_measurement = 'MB' + self._unit_of_measurement = "MB" def update(self): """Update the sensor.""" @@ -78,7 +80,7 @@ class Folder(Entity): def state(self): """Return the state of the sensor.""" decimals = 2 - size_mb = round(self._size/1e6, decimals) + size_mb = round(self._size / 1e6, decimals) return size_mb @property @@ -90,11 +92,11 @@ class Folder(Entity): def device_state_attributes(self): """Return other details about the sensor state.""" attr = { - 'path': self._folder_path, - 'filter': self._filter_term, - 'number_of_files': self._number_of_files, - 'bytes': self._size, - } + "path": self._folder_path, + "filter": self._filter_term, + "number_of_files": self._number_of_files, + "bytes": self._size, + } return attr @property diff --git a/homeassistant/components/folder_watcher/__init__.py b/homeassistant/components/folder_watcher/__init__.py index 411f6b480dc..b328744aaba 100644 --- a/homeassistant/components/folder_watcher/__init__.py +++ b/homeassistant/components/folder_watcher/__init__.py @@ -4,24 +4,34 @@ import os import voluptuous as vol -from homeassistant.const import ( - EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) +from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -CONF_FOLDER = 'folder' -CONF_PATTERNS = 'patterns' -DEFAULT_PATTERN = '*' +CONF_FOLDER = "folder" +CONF_PATTERNS = "patterns" +DEFAULT_PATTERN = "*" DOMAIN = "folder_watcher" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.All(cv.ensure_list, [vol.Schema({ - vol.Required(CONF_FOLDER): cv.isdir, - vol.Optional(CONF_PATTERNS, default=[DEFAULT_PATTERN]): - vol.All(cv.ensure_list, [cv.string]), - })]) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.All( + cv.ensure_list, + [ + vol.Schema( + { + vol.Required(CONF_FOLDER): cv.isdir, + vol.Optional(CONF_PATTERNS, default=[DEFAULT_PATTERN]): vol.All( + cv.ensure_list, [cv.string] + ), + } + ) + ], + ) + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): @@ -56,12 +66,14 @@ def create_event_handler(patterns, hass): if not event.is_directory: folder, file_name = os.path.split(event.src_path) self.hass.bus.fire( - DOMAIN, { + DOMAIN, + { "event_type": event.event_type, - 'path': event.src_path, - 'file': file_name, - 'folder': folder, - }) + "path": event.src_path, + "file": file_name, + "folder": folder, + }, + ) def on_modified(self, event): """File modified.""" @@ -82,17 +94,17 @@ def create_event_handler(patterns, hass): return EventHandler(patterns, hass) -class Watcher(): +class Watcher: """Class for starting Watchdog.""" def __init__(self, path, patterns, hass): """Initialise the watchdog observer.""" from watchdog.observers import Observer + self._observer = Observer() self._observer.schedule( - create_event_handler(patterns, hass), - path, - recursive=True) + create_event_handler(patterns, hass), path, recursive=True + ) hass.bus.listen_once(EVENT_HOMEASSISTANT_START, self.startup) hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, self.shutdown) diff --git a/homeassistant/components/foobot/sensor.py b/homeassistant/components/foobot/sensor.py index f59392bde98..8ec3541d188 100644 --- a/homeassistant/components/foobot/sensor.py +++ b/homeassistant/components/foobot/sensor.py @@ -9,7 +9,12 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.exceptions import PlatformNotReady from homeassistant.const import ( - ATTR_TIME, ATTR_TEMPERATURE, CONF_TOKEN, CONF_USERNAME, TEMP_CELSIUS) + ATTR_TIME, + ATTR_TEMPERATURE, + CONF_TOKEN, + CONF_USERNAME, + TEMP_CELSIUS, +) from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.config_validation import PLATFORM_SCHEMA from homeassistant.helpers.entity import Entity @@ -18,62 +23,63 @@ from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -ATTR_HUMIDITY = 'humidity' -ATTR_PM2_5 = 'PM2.5' -ATTR_CARBON_DIOXIDE = 'CO2' -ATTR_VOLATILE_ORGANIC_COMPOUNDS = 'VOC' -ATTR_FOOBOT_INDEX = 'index' +ATTR_HUMIDITY = "humidity" +ATTR_PM2_5 = "PM2.5" +ATTR_CARBON_DIOXIDE = "CO2" +ATTR_VOLATILE_ORGANIC_COMPOUNDS = "VOC" +ATTR_FOOBOT_INDEX = "index" -SENSOR_TYPES = {'time': [ATTR_TIME, 's'], - 'pm': [ATTR_PM2_5, 'µg/m3', 'mdi:cloud'], - 'tmp': [ATTR_TEMPERATURE, TEMP_CELSIUS, 'mdi:thermometer'], - 'hum': [ATTR_HUMIDITY, '%', 'mdi:water-percent'], - 'co2': [ATTR_CARBON_DIOXIDE, 'ppm', - 'mdi:periodic-table-co2'], - 'voc': [ATTR_VOLATILE_ORGANIC_COMPOUNDS, 'ppb', - 'mdi:cloud'], - 'allpollu': [ATTR_FOOBOT_INDEX, '%', 'mdi:percent']} +SENSOR_TYPES = { + "time": [ATTR_TIME, "s"], + "pm": [ATTR_PM2_5, "µg/m3", "mdi:cloud"], + "tmp": [ATTR_TEMPERATURE, TEMP_CELSIUS, "mdi:thermometer"], + "hum": [ATTR_HUMIDITY, "%", "mdi:water-percent"], + "co2": [ATTR_CARBON_DIOXIDE, "ppm", "mdi:periodic-table-co2"], + "voc": [ATTR_VOLATILE_ORGANIC_COMPOUNDS, "ppb", "mdi:cloud"], + "allpollu": [ATTR_FOOBOT_INDEX, "%", "mdi:percent"], +} SCAN_INTERVAL = timedelta(minutes=10) PARALLEL_UPDATES = 1 TIMEOUT = 10 -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_TOKEN): cv.string, - vol.Required(CONF_USERNAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_TOKEN): cv.string, vol.Required(CONF_USERNAME): cv.string} +) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the devices associated with the account.""" from foobot_async import FoobotClient token = config.get(CONF_TOKEN) username = config.get(CONF_USERNAME) - client = FoobotClient(token, username, - async_get_clientsession(hass), - timeout=TIMEOUT) + client = FoobotClient( + token, username, async_get_clientsession(hass), timeout=TIMEOUT + ) dev = [] try: devices = await client.get_devices() _LOGGER.debug("The following devices were found: %s", devices) for device in devices: - foobot_data = FoobotData(client, device['uuid']) + foobot_data = FoobotData(client, device["uuid"]) for sensor_type in SENSOR_TYPES: - if sensor_type == 'time': + if sensor_type == "time": continue foobot_sensor = FoobotSensor(foobot_data, device, sensor_type) dev.append(foobot_sensor) - except (aiohttp.client_exceptions.ClientConnectorError, - asyncio.TimeoutError, FoobotClient.TooManyRequests, - FoobotClient.InternalError): - _LOGGER.exception('Failed to connect to foobot servers.') + except ( + aiohttp.client_exceptions.ClientConnectorError, + asyncio.TimeoutError, + FoobotClient.TooManyRequests, + FoobotClient.InternalError, + ): + _LOGGER.exception("Failed to connect to foobot servers.") raise PlatformNotReady except FoobotClient.ClientError: - _LOGGER.error('Failed to fetch data from foobot servers.') + _LOGGER.error("Failed to fetch data from foobot servers.") return async_add_entities(dev, True) @@ -83,10 +89,9 @@ class FoobotSensor(Entity): def __init__(self, data, device, sensor_type): """Initialize the sensor.""" - self._uuid = device['uuid'] + self._uuid = device["uuid"] self.foobot_data = data - self._name = 'Foobot {} {}'.format(device['name'], - SENSOR_TYPES[sensor_type][0]) + self._name = "Foobot {} {}".format(device["name"], SENSOR_TYPES[sensor_type][0]) self.type = sensor_type self._unit_of_measurement = SENSOR_TYPES[sensor_type][1] @@ -105,7 +110,7 @@ class FoobotSensor(Entity): """Return the state of the device.""" try: data = self.foobot_data.data[self.type] - except(KeyError, TypeError): + except (KeyError, TypeError): data = None return data @@ -138,12 +143,15 @@ class FoobotData(Entity): """Get the data from Foobot API.""" interval = SCAN_INTERVAL.total_seconds() try: - response = await self._client.get_last_data(self._uuid, - interval, - interval + 1) - except (aiohttp.client_exceptions.ClientConnectorError, - asyncio.TimeoutError, self._client.TooManyRequests, - self._client.InternalError): + response = await self._client.get_last_data( + self._uuid, interval, interval + 1 + ) + except ( + aiohttp.client_exceptions.ClientConnectorError, + asyncio.TimeoutError, + self._client.TooManyRequests, + self._client.InternalError, + ): _LOGGER.debug("Couldn't fetch data") return False _LOGGER.debug("The data response is: %s", response) diff --git a/homeassistant/components/fortigate/__init__.py b/homeassistant/components/fortigate/__init__.py index 7ae3b1e0e92..d1f6eb52333 100644 --- a/homeassistant/components/fortigate/__init__.py +++ b/homeassistant/components/fortigate/__init__.py @@ -4,26 +4,36 @@ import logging import voluptuous as vol from homeassistant.const import ( - CONF_DEVICES, CONF_HOST, CONF_API_KEY, CONF_USERNAME, - EVENT_HOMEASSISTANT_STOP) + CONF_DEVICES, + CONF_HOST, + CONF_API_KEY, + CONF_USERNAME, + EVENT_HOMEASSISTANT_STOP, +) from homeassistant.helpers import config_validation as cv from homeassistant.helpers.discovery import async_load_platform _LOGGER = logging.getLogger(__name__) -DOMAIN = 'fortigate' +DOMAIN = "fortigate" DATA_FGT = DOMAIN -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_API_KEY): cv.string, - vol.Optional(CONF_DEVICES, default=[]): - vol.All(cv.ensure_list, [cv.string]), - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_API_KEY): cv.string, + vol.Optional(CONF_DEVICES, default=[]): vol.All( + cv.ensure_list, [cv.string] + ), + } + ) + }, + extra=vol.ALLOW_EXTRA, +) async def async_setup(hass, config): @@ -35,9 +45,7 @@ async def async_setup(hass, config): api_key = conf[CONF_API_KEY] devices = conf[CONF_DEVICES] - is_success = await async_setup_fortigate( - hass, config, host, user, api_key, devices - ) + is_success = await async_setup_fortigate(hass, config, host, user, api_key, devices) return is_success @@ -54,13 +62,11 @@ async def async_setup_fortigate(hass, config, host, user, api_key, devices): _LOGGER.error("Failed to connect to Fortigate") return False - hass.data[DATA_FGT] = { - 'fgt': fgt, - 'devices': devices - } + hass.data[DATA_FGT] = {"fgt": fgt, "devices": devices} - hass.async_create_task(async_load_platform( - hass, 'device_tracker', DOMAIN, {}, config)) + hass.async_create_task( + async_load_platform(hass, "device_tracker", DOMAIN, {}, config) + ) async def close_fgt(event): """Close Fortigate connection on HA Stop.""" diff --git a/homeassistant/components/fortigate/device_tracker.py b/homeassistant/components/fortigate/device_tracker.py index b8aa7060857..b51dc6843aa 100644 --- a/homeassistant/components/fortigate/device_tracker.py +++ b/homeassistant/components/fortigate/device_tracker.py @@ -18,14 +18,12 @@ async def async_get_scanner(hass, config): return scanner if scanner.success_init else None -Device = namedtuple('Device', ['hostname', 'mac']) +Device = namedtuple("Device", ["hostname", "mac"]) def _build_device(device_dict): """Return a Device from data.""" - return Device( - device_dict['hostname'], - device_dict['mac']) + return Device(device_dict["hostname"], device_dict["mac"]) class FortigateDeviceScanner(DeviceScanner): @@ -35,17 +33,16 @@ class FortigateDeviceScanner(DeviceScanner): """Initialize the scanner.""" self.last_results = {} self.success_init = False - self.connection = hass_data['fgt'] - self.devices = hass_data['devices'] + self.connection = hass_data["fgt"] + self.devices = hass_data["devices"] def get_results(self): """Get the results from the Fortigate.""" - results = self.connection.get( - DETECTED_DEVICES, "vdom=root")[1]['results'] + results = self.connection.get(DETECTED_DEVICES, "vdom=root")[1]["results"] ret = [] for result in results: - if 'hostname' not in result: + if "hostname" not in result: continue ret.append(result) @@ -65,9 +62,10 @@ class FortigateDeviceScanner(DeviceScanner): async def get_device_name(self, device): """Return the name of the given device or None if we don't know.""" - name = next(( - result.hostname for result in self.last_results - if result.mac == device), None) + name = next( + (result.hostname for result in self.last_results if result.mac == device), + None, + ) return name async def async_update_info(self): @@ -76,14 +74,12 @@ class FortigateDeviceScanner(DeviceScanner): hosts = self.get_results() - all_results = [_build_device(device) for device in hosts - if device['is_online']] + all_results = [_build_device(device) for device in hosts if device["is_online"]] # If the 'devices' configuration field is filled if self.devices is not None: last_results = [ - device for device in all_results - if device.hostname in self.devices + device for device in all_results if device.hostname in self.devices ] _LOGGER.debug(last_results) # If the 'devices' configuration field is not filled diff --git a/homeassistant/components/fortios/device_tracker.py b/homeassistant/components/fortios/device_tracker.py index dc99a81f874..51bce5429f6 100755 --- a/homeassistant/components/fortios/device_tracker.py +++ b/homeassistant/components/fortios/device_tracker.py @@ -10,7 +10,10 @@ from fortiosapi import FortiOSAPI import homeassistant.helpers.config_validation as cv from homeassistant.components.device_tracker import ( - DOMAIN, PLATFORM_SCHEMA, DeviceScanner) + DOMAIN, + PLATFORM_SCHEMA, + DeviceScanner, +) from homeassistant.const import CONF_HOST, CONF_TOKEN from homeassistant.const import CONF_VERIFY_SSL @@ -18,11 +21,13 @@ _LOGGER = logging.getLogger(__name__) DEFAULT_VERIFY_SSL = False -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_TOKEN): cv.string, - vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_TOKEN): cv.string, + vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean, + } +) def get_scanner(hass, config): @@ -56,15 +61,15 @@ class FortiOSDeviceScanner(DeviceScanner): def update(self): """Update clients from the device.""" - clients_json = self._fgt.monitor('user/device/select', '') + clients_json = self._fgt.monitor("user/device/select", "") self._clients_json = clients_json self._clients = [] if clients_json: - for client in clients_json['results']: - if client['last_seen'] < 180: - self._clients.append(client['mac'].upper()) + for client in clients_json["results"]: + if client["last_seen"] < 180: + self._clients.append(client["mac"].upper()) def scan_devices(self): """Scan for new devices and return a list with found device IDs.""" @@ -80,13 +85,13 @@ class FortiOSDeviceScanner(DeviceScanner): data = self._clients_json if data == 0: - _LOGGER.error('No json results to get device names') + _LOGGER.error("No json results to get device names") return None - for client in data['results']: - if client['mac'] == device: + for client in data["results"]: + if client["mac"] == device: try: - name = client['host']['name'] + name = client["host"]["name"] _LOGGER.debug("Getting device name=%s", name) return name except KeyError as kex: diff --git a/homeassistant/components/foscam/camera.py b/homeassistant/components/foscam/camera.py index 3bb000380d7..37f792cec45 100644 --- a/homeassistant/components/foscam/camera.py +++ b/homeassistant/components/foscam/camera.py @@ -3,30 +3,30 @@ import logging import voluptuous as vol -from homeassistant.components.camera import ( - Camera, PLATFORM_SCHEMA, SUPPORT_STREAM) -from homeassistant.const import ( - CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_PORT) +from homeassistant.components.camera import Camera, PLATFORM_SCHEMA, SUPPORT_STREAM +from homeassistant.const import CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_PORT from homeassistant.helpers import config_validation as cv _LOGGER = logging.getLogger(__name__) -CONF_IP = 'ip' -CONF_RTSP_PORT = 'rtsp_port' +CONF_IP = "ip" +CONF_RTSP_PORT = "rtsp_port" -DEFAULT_NAME = 'Foscam Camera' +DEFAULT_NAME = "Foscam Camera" DEFAULT_PORT = 88 FOSCAM_COMM_ERROR = -8 -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_IP): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_RTSP_PORT): cv.port -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_IP): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_RTSP_PORT): cv.port, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -51,14 +51,14 @@ class FoscamCam(Camera): self._motion_status = False self._foscam_session = FoscamCamera( - ip_address, port, self._username, self._password, verbose=False) + ip_address, port, self._username, self._password, verbose=False + ) self._rtsp_port = device_info.get(CONF_RTSP_PORT) if not self._rtsp_port: result, response = self._foscam_session.get_port_info() if result == 0: - self._rtsp_port = response.get('rtspPort') or \ - response.get('mediaPort') + self._rtsp_port = response.get("rtspPort") or response.get("mediaPort") def camera_image(self): """Return a still image response from the camera.""" @@ -80,11 +80,12 @@ class FoscamCam(Camera): async def stream_source(self): """Return the stream source.""" if self._rtsp_port: - return 'rtsp://{}:{}@{}:{}/videoMain'.format( + return "rtsp://{}:{}@{}:{}/videoMain".format( self._username, self._password, self._foscam_session.host, - self._rtsp_port) + self._rtsp_port, + ) return None @property diff --git a/homeassistant/components/foursquare/__init__.py b/homeassistant/components/foursquare/__init__.py index dd834999888..7efb989e142 100644 --- a/homeassistant/components/foursquare/__init__.py +++ b/homeassistant/components/foursquare/__init__.py @@ -10,33 +10,40 @@ from homeassistant.components.http import HomeAssistantView _LOGGER = logging.getLogger(__name__) -CONF_PUSH_SECRET = 'push_secret' +CONF_PUSH_SECRET = "push_secret" -DOMAIN = 'foursquare' +DOMAIN = "foursquare" -EVENT_CHECKIN = 'foursquare.checkin' -EVENT_PUSH = 'foursquare.push' +EVENT_CHECKIN = "foursquare.checkin" +EVENT_PUSH = "foursquare.push" -SERVICE_CHECKIN = 'checkin' +SERVICE_CHECKIN = "checkin" -CHECKIN_SERVICE_SCHEMA = vol.Schema({ - vol.Optional('alt'): cv.string, - vol.Optional('altAcc'): cv.string, - vol.Optional('broadcast'): cv.string, - vol.Optional('eventId'): cv.string, - vol.Optional('ll'): cv.string, - vol.Optional('llAcc'): cv.string, - vol.Optional('mentions'): cv.string, - vol.Optional('shout'): cv.string, - vol.Required('venueId'): cv.string, -}) +CHECKIN_SERVICE_SCHEMA = vol.Schema( + { + vol.Optional("alt"): cv.string, + vol.Optional("altAcc"): cv.string, + vol.Optional("broadcast"): cv.string, + vol.Optional("eventId"): cv.string, + vol.Optional("ll"): cv.string, + vol.Optional("llAcc"): cv.string, + vol.Optional("mentions"): cv.string, + vol.Optional("shout"): cv.string, + vol.Required("venueId"): cv.string, + } +) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_ACCESS_TOKEN): cv.string, - vol.Required(CONF_PUSH_SECRET): cv.string, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_ACCESS_TOKEN): cv.string, + vol.Required(CONF_PUSH_SECRET): cv.string, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): @@ -45,22 +52,27 @@ def setup(hass, config): def checkin_user(call): """Check a user in on Swarm.""" - url = ("https://api.foursquare.com/v2/checkins/add" - "?oauth_token={}" - "&v=20160802" - "&m=swarm").format(config[CONF_ACCESS_TOKEN]) + url = ( + "https://api.foursquare.com/v2/checkins/add" + "?oauth_token={}" + "&v=20160802" + "&m=swarm" + ).format(config[CONF_ACCESS_TOKEN]) response = requests.post(url, data=call.data, timeout=10) if response.status_code not in (200, 201): _LOGGER.exception( "Error checking in user. Response %d: %s:", - response.status_code, response.reason) + response.status_code, + response.reason, + ) hass.bus.fire(EVENT_CHECKIN, response.text) # Register our service with Home Assistant. - hass.services.register(DOMAIN, 'checkin', checkin_user, - schema=CHECKIN_SERVICE_SCHEMA) + hass.services.register( + DOMAIN, "checkin", checkin_user, schema=CHECKIN_SERVICE_SCHEMA + ) hass.http.register_view(FoursquarePushReceiver(config[CONF_PUSH_SECRET])) @@ -83,15 +95,16 @@ class FoursquarePushReceiver(HomeAssistantView): try: data = await request.json() except ValueError: - return self.json_message('Invalid JSON', HTTP_BAD_REQUEST) + return self.json_message("Invalid JSON", HTTP_BAD_REQUEST) - secret = data.pop('secret', None) + secret = data.pop("secret", None) _LOGGER.debug("Received Foursquare push: %s", data) if self.push_secret != secret: - _LOGGER.error("Received Foursquare push with invalid" - "push secret: %s", secret) - return self.json_message('Incorrect secret', HTTP_BAD_REQUEST) + _LOGGER.error( + "Received Foursquare push with invalid" "push secret: %s", secret + ) + return self.json_message("Incorrect secret", HTTP_BAD_REQUEST) - request.app['hass'].bus.async_fire(EVENT_PUSH, data) + request.app["hass"].bus.async_fire(EVENT_PUSH, data) diff --git a/homeassistant/components/free_mobile/notify.py b/homeassistant/components/free_mobile/notify.py index c7dacd44019..5733e3c19c0 100644 --- a/homeassistant/components/free_mobile/notify.py +++ b/homeassistant/components/free_mobile/notify.py @@ -6,21 +6,18 @@ import voluptuous as vol from homeassistant.const import CONF_ACCESS_TOKEN, CONF_USERNAME import homeassistant.helpers.config_validation as cv -from homeassistant.components.notify import (PLATFORM_SCHEMA, - BaseNotificationService) +from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationService _LOGGER = logging.getLogger(__name__) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_ACCESS_TOKEN): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_ACCESS_TOKEN): cv.string} +) def get_service(hass, config, discovery_info=None): """Get the Free Mobile SMS notification service.""" - return FreeSMSNotificationService( - config[CONF_USERNAME], config[CONF_ACCESS_TOKEN]) + return FreeSMSNotificationService(config[CONF_USERNAME], config[CONF_ACCESS_TOKEN]) class FreeSMSNotificationService(BaseNotificationService): @@ -29,6 +26,7 @@ class FreeSMSNotificationService(BaseNotificationService): def __init__(self, username, access_token): """Initialize the service.""" from freesms import FreeClient + self.free_client = FreeClient(username, access_token) def send_message(self, message="", **kwargs): diff --git a/homeassistant/components/freebox/__init__.py b/homeassistant/components/freebox/__init__.py index 2cd9f6b3572..0bffedd46dc 100644 --- a/homeassistant/components/freebox/__init__.py +++ b/homeassistant/components/freebox/__init__.py @@ -14,14 +14,16 @@ _LOGGER = logging.getLogger(__name__) DOMAIN = "freebox" DATA_FREEBOX = DOMAIN -FREEBOX_CONFIG_FILE = 'freebox.conf' +FREEBOX_CONFIG_FILE = "freebox.conf" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_PORT): cv.port, - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + {vol.Required(CONF_HOST): cv.string, vol.Required(CONF_PORT): cv.port} + ) + }, + extra=vol.ALLOW_EXTRA, +) async def async_setup(hass, config): @@ -30,8 +32,8 @@ async def async_setup(hass, config): async def discovery_dispatch(service, discovery_info): if conf is None: - host = discovery_info.get('properties', {}).get('api_domain') - port = discovery_info.get('properties', {}).get('https_port') + host = discovery_info.get("properties", {}).get("api_domain") + port = discovery_info.get("properties", {}).get("https_port") _LOGGER.info("Discovered Freebox server: %s:%s", host, port) await async_setup_freebox(hass, config, host, port) @@ -51,33 +53,29 @@ async def async_setup_freebox(hass, config, host, port): from aiofreepybox.exceptions import HttpRequestError app_desc = { - 'app_id': 'hass', - 'app_name': 'Home Assistant', - 'app_version': '0.65', - 'device_name': socket.gethostname() - } + "app_id": "hass", + "app_name": "Home Assistant", + "app_version": "0.65", + "device_name": socket.gethostname(), + } token_file = hass.config.path(FREEBOX_CONFIG_FILE) - api_version = 'v4' + api_version = "v4" - fbx = Freepybox( - app_desc=app_desc, - token_file=token_file, - api_version=api_version) + fbx = Freepybox(app_desc=app_desc, token_file=token_file, api_version=api_version) try: await fbx.open(host, port) except HttpRequestError: - _LOGGER.exception('Failed to connect to Freebox') + _LOGGER.exception("Failed to connect to Freebox") else: hass.data[DATA_FREEBOX] = fbx - hass.async_create_task(async_load_platform( - hass, 'sensor', DOMAIN, {}, config)) - hass.async_create_task(async_load_platform( - hass, 'device_tracker', DOMAIN, {}, config)) - hass.async_create_task(async_load_platform( - hass, 'switch', DOMAIN, {}, config)) + hass.async_create_task(async_load_platform(hass, "sensor", DOMAIN, {}, config)) + hass.async_create_task( + async_load_platform(hass, "device_tracker", DOMAIN, {}, config) + ) + hass.async_create_task(async_load_platform(hass, "switch", DOMAIN, {}, config)) async def close_fbx(event): """Close Freebox connection on HA Stop.""" diff --git a/homeassistant/components/freebox/device_tracker.py b/homeassistant/components/freebox/device_tracker.py index 40c1967f60f..63cf869990d 100644 --- a/homeassistant/components/freebox/device_tracker.py +++ b/homeassistant/components/freebox/device_tracker.py @@ -15,14 +15,16 @@ async def async_get_scanner(hass, config): await scanner.async_connect() return scanner if scanner.success_init else None -Device = namedtuple('Device', ['id', 'name', 'ip']) + +Device = namedtuple("Device", ["id", "name", "ip"]) def _build_device(device_dict): return Device( - device_dict['l2ident']['id'], - device_dict['primary_name'], - device_dict['l3connectivities'][0]['addr']) + device_dict["l2ident"]["id"], + device_dict["primary_name"], + device_dict["l3connectivities"][0]["addr"], + ) class FreeboxDeviceScanner(DeviceScanner): @@ -47,19 +49,17 @@ class FreeboxDeviceScanner(DeviceScanner): async def get_device_name(self, device): """Return the name of the given device or None if we don't know.""" - name = next(( - result.name for result in self.last_results - if result.id == device), None) + name = next( + (result.name for result in self.last_results if result.id == device), None + ) return name async def async_update_info(self): """Ensure the information from the Freebox router is up to date.""" - _LOGGER.debug('Checking Devices') + _LOGGER.debug("Checking Devices") hosts = await self.connection.lan.get_hosts_list() - last_results = [_build_device(device) - for device in hosts - if device['active']] + last_results = [_build_device(device) for device in hosts if device["active"]] self.last_results = last_results diff --git a/homeassistant/components/freebox/sensor.py b/homeassistant/components/freebox/sensor.py index 8dcc5f54b2e..e8f96586300 100644 --- a/homeassistant/components/freebox/sensor.py +++ b/homeassistant/components/freebox/sensor.py @@ -8,8 +8,7 @@ from . import DATA_FREEBOX _LOGGER = logging.getLogger(__name__) -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the sensors.""" fbx = hass.data[DATA_FREEBOX] async_add_entities([FbxRXSensor(fbx), FbxTXSensor(fbx)], True) @@ -18,7 +17,7 @@ async def async_setup_platform( class FbxSensor(Entity): """Representation of a freebox sensor.""" - _name = 'generic' + _name = "generic" def __init__(self, fbx): """Initialize the sensor.""" @@ -44,8 +43,8 @@ class FbxSensor(Entity): class FbxRXSensor(FbxSensor): """Update the Freebox RxSensor.""" - _name = 'Freebox download speed' - _unit = 'KB/s' + _name = "Freebox download speed" + _unit = "KB/s" @property def unit_of_measurement(self): @@ -56,14 +55,14 @@ class FbxRXSensor(FbxSensor): """Get the value from fetched datas.""" await super().async_update() if self._datas is not None: - self._state = round(self._datas['rate_down'] / 1000, 2) + self._state = round(self._datas["rate_down"] / 1000, 2) class FbxTXSensor(FbxSensor): """Update the Freebox TxSensor.""" - _name = 'Freebox upload speed' - _unit = 'KB/s' + _name = "Freebox upload speed" + _unit = "KB/s" @property def unit_of_measurement(self): @@ -74,4 +73,4 @@ class FbxTXSensor(FbxSensor): """Get the value from fetched datas.""" await super().async_update() if self._datas is not None: - self._state = round(self._datas['rate_up'] / 1000, 2) + self._state = round(self._datas["rate_up"] / 1000, 2) diff --git a/homeassistant/components/freebox/switch.py b/homeassistant/components/freebox/switch.py index e0c24d2b9f9..b6655c9634f 100644 --- a/homeassistant/components/freebox/switch.py +++ b/homeassistant/components/freebox/switch.py @@ -8,8 +8,7 @@ from . import DATA_FREEBOX _LOGGER = logging.getLogger(__name__) -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the switch.""" fbx = hass.data[DATA_FREEBOX] async_add_entities([FbxWifiSwitch(fbx)], True) @@ -20,7 +19,7 @@ class FbxWifiSwitch(SwitchDevice): def __init__(self, fbx): """Initilize the Wifi switch.""" - self._name = 'Freebox WiFi' + self._name = "Freebox WiFi" self._state = None self._fbx = fbx @@ -42,9 +41,11 @@ class FbxWifiSwitch(SwitchDevice): try: await self._fbx.wifi.set_global_config(wifi_config) except InsufficientPermissionsError: - _LOGGER.warning('Home Assistant does not have permissions to' - ' modify the Freebox settings. Please refer' - ' to documentation.') + _LOGGER.warning( + "Home Assistant does not have permissions to" + " modify the Freebox settings. Please refer" + " to documentation." + ) async def async_turn_on(self, **kwargs): """Turn the switch on.""" @@ -57,5 +58,5 @@ class FbxWifiSwitch(SwitchDevice): async def async_update(self): """Get the state and update it.""" datas = await self._fbx.wifi.get_global_config() - active = datas['enabled'] + active = datas["enabled"] self._state = bool(active) diff --git a/homeassistant/components/freedns/__init__.py b/homeassistant/components/freedns/__init__.py index 6125057ca33..30f80280c1f 100644 --- a/homeassistant/components/freedns/__init__.py +++ b/homeassistant/components/freedns/__init__.py @@ -8,27 +8,31 @@ import async_timeout import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.const import ( - CONF_ACCESS_TOKEN, CONF_SCAN_INTERVAL, CONF_URL -) +from homeassistant.const import CONF_ACCESS_TOKEN, CONF_SCAN_INTERVAL, CONF_URL _LOGGER = logging.getLogger(__name__) -DOMAIN = 'freedns' +DOMAIN = "freedns" DEFAULT_INTERVAL = timedelta(minutes=10) TIMEOUT = 10 -UPDATE_URL = 'https://freedns.afraid.org/dynamic/update.php' +UPDATE_URL = "https://freedns.afraid.org/dynamic/update.php" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Exclusive(CONF_URL, DOMAIN): cv.string, - vol.Exclusive(CONF_ACCESS_TOKEN, DOMAIN): cv.string, - vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_INTERVAL): - vol.All(cv.time_period, cv.positive_timedelta), - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Exclusive(CONF_URL, DOMAIN): cv.string, + vol.Exclusive(CONF_ACCESS_TOKEN, DOMAIN): cv.string, + vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_INTERVAL): vol.All( + cv.time_period, cv.positive_timedelta + ), + } + ) + }, + extra=vol.ALLOW_EXTRA, +) async def async_setup(hass, config): @@ -40,8 +44,7 @@ async def async_setup(hass, config): session = hass.helpers.aiohttp_client.async_get_clientsession() - result = await _update_freedns( - hass, session, url, auth_token) + result = await _update_freedns(hass, session, url, auth_token) if result is False: return False @@ -51,7 +54,8 @@ async def async_setup(hass, config): await _update_freedns(hass, session, url, auth_token) hass.helpers.event.async_track_time_interval( - update_domain_callback, update_interval) + update_domain_callback, update_interval + ) return True diff --git a/homeassistant/components/fritz/device_tracker.py b/homeassistant/components/fritz/device_tracker.py index fc9f65633ff..f34cda67ad9 100644 --- a/homeassistant/components/fritz/device_tracker.py +++ b/homeassistant/components/fritz/device_tracker.py @@ -5,18 +5,23 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.device_tracker import ( - DOMAIN, PLATFORM_SCHEMA, DeviceScanner) + DOMAIN, + PLATFORM_SCHEMA, + DeviceScanner, +) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME _LOGGER = logging.getLogger(__name__) -CONF_DEFAULT_IP = '169.254.1.1' # This IP is valid for all FRITZ!Box routers. +CONF_DEFAULT_IP = "169.254.1.1" # This IP is valid for all FRITZ!Box routers. -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_HOST, default=CONF_DEFAULT_IP): cv.string, - vol.Optional(CONF_PASSWORD, default='admin'): cv.string, - vol.Optional(CONF_USERNAME, default=''): cv.string -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_HOST, default=CONF_DEFAULT_IP): cv.string, + vol.Optional(CONF_PASSWORD, default="admin"): cv.string, + vol.Optional(CONF_USERNAME, default=""): cv.string, + } +) def get_scanner(hass, config): @@ -41,7 +46,8 @@ class FritzBoxScanner(DeviceScanner): # Establish a connection to the FRITZ!Box. try: self.fritz_box = fc.FritzHosts( - address=self.host, user=self.username, password=self.password) + address=self.host, user=self.username, password=self.password + ) except (ValueError, TypeError): self.fritz_box = None @@ -51,27 +57,25 @@ class FritzBoxScanner(DeviceScanner): self.success_init = False if self.success_init: - _LOGGER.info("Successfully connected to %s", - self.fritz_box.modelname) + _LOGGER.info("Successfully connected to %s", self.fritz_box.modelname) self._update_info() else: - _LOGGER.error("Failed to establish connection to FRITZ!Box " - "with IP: %s", self.host) + _LOGGER.error( + "Failed to establish connection to FRITZ!Box " "with IP: %s", self.host + ) def scan_devices(self): """Scan for new devices and return a list of found device ids.""" self._update_info() active_hosts = [] for known_host in self.last_results: - if known_host['status'] == '1' and known_host.get('mac'): - active_hosts.append(known_host['mac']) + if known_host["status"] == "1" and known_host.get("mac"): + active_hosts.append(known_host["mac"]) return active_hosts def get_device_name(self, device): """Return the name of the given device or None if is not known.""" - ret = self.fritz_box.get_specific_host_entry(device).get( - 'NewHostName' - ) + ret = self.fritz_box.get_specific_host_entry(device).get("NewHostName") if ret == {}: return None return ret diff --git a/homeassistant/components/fritzbox/__init__.py b/homeassistant/components/fritzbox/__init__.py index 610c6874140..a053bc6c7ca 100644 --- a/homeassistant/components/fritzbox/__init__.py +++ b/homeassistant/components/fritzbox/__init__.py @@ -5,36 +5,49 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.const import ( - CONF_DEVICES, CONF_HOST, CONF_PASSWORD, CONF_USERNAME, - EVENT_HOMEASSISTANT_STOP) + CONF_DEVICES, + CONF_HOST, + CONF_PASSWORD, + CONF_USERNAME, + EVENT_HOMEASSISTANT_STOP, +) from homeassistant.helpers import discovery _LOGGER = logging.getLogger(__name__) -SUPPORTED_DOMAINS = ['binary_sensor', 'climate', 'switch', 'sensor'] +SUPPORTED_DOMAINS = ["binary_sensor", "climate", "switch", "sensor"] -DOMAIN = 'fritzbox' +DOMAIN = "fritzbox" -ATTR_STATE_BATTERY_LOW = 'battery_low' -ATTR_STATE_DEVICE_LOCKED = 'device_locked' -ATTR_STATE_HOLIDAY_MODE = 'holiday_mode' -ATTR_STATE_LOCKED = 'locked' -ATTR_STATE_SUMMER_MODE = 'summer_mode' -ATTR_STATE_WINDOW_OPEN = 'window_open' +ATTR_STATE_BATTERY_LOW = "battery_low" +ATTR_STATE_DEVICE_LOCKED = "device_locked" +ATTR_STATE_HOLIDAY_MODE = "holiday_mode" +ATTR_STATE_LOCKED = "locked" +ATTR_STATE_SUMMER_MODE = "summer_mode" +ATTR_STATE_WINDOW_OPEN = "window_open" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_DEVICES): - vol.All(cv.ensure_list, [ - vol.Schema({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_USERNAME): cv.string, - }), - ]), - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_DEVICES): vol.All( + cv.ensure_list, + [ + vol.Schema( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string, + } + ) + ], + ) + } + ) + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): @@ -48,14 +61,12 @@ def setup(hass, config): host = device.get(CONF_HOST) username = device.get(CONF_USERNAME) password = device.get(CONF_PASSWORD) - fritzbox = Fritzhome(host=host, user=username, - password=password) + fritzbox = Fritzhome(host=host, user=username, password=password) try: fritzbox.login() _LOGGER.info("Connected to device %s", device) except LoginError: - _LOGGER.warning("Login to Fritz!Box %s as %s failed", - host, username) + _LOGGER.warning("Login to Fritz!Box %s as %s failed", host, username) continue fritz_list.append(fritzbox) diff --git a/homeassistant/components/fritzbox/binary_sensor.py b/homeassistant/components/fritzbox/binary_sensor.py index a763a3b3b0e..3d8d676d1d0 100644 --- a/homeassistant/components/fritzbox/binary_sensor.py +++ b/homeassistant/components/fritzbox/binary_sensor.py @@ -40,7 +40,7 @@ class FritzboxBinarySensor(BinarySensorDevice): @property def device_class(self): """Return the class of this sensor.""" - return 'window' + return "window" @property def is_on(self): diff --git a/homeassistant/components/fritzbox/climate.py b/homeassistant/components/fritzbox/climate.py index 012d79587b4..40c16930206 100644 --- a/homeassistant/components/fritzbox/climate.py +++ b/homeassistant/components/fritzbox/climate.py @@ -5,16 +5,30 @@ import requests from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( - ATTR_HVAC_MODE, HVAC_MODE_HEAT, PRESET_ECO, PRESET_COMFORT, - SUPPORT_TARGET_TEMPERATURE, HVAC_MODE_OFF, SUPPORT_PRESET_MODE) + ATTR_HVAC_MODE, + HVAC_MODE_HEAT, + PRESET_ECO, + PRESET_COMFORT, + SUPPORT_TARGET_TEMPERATURE, + HVAC_MODE_OFF, + SUPPORT_PRESET_MODE, +) from homeassistant.const import ( - ATTR_BATTERY_LEVEL, ATTR_TEMPERATURE, PRECISION_HALVES, - TEMP_CELSIUS) + ATTR_BATTERY_LEVEL, + ATTR_TEMPERATURE, + PRECISION_HALVES, + TEMP_CELSIUS, +) from . import ( - ATTR_STATE_BATTERY_LOW, ATTR_STATE_DEVICE_LOCKED, ATTR_STATE_HOLIDAY_MODE, - ATTR_STATE_LOCKED, ATTR_STATE_SUMMER_MODE, ATTR_STATE_WINDOW_OPEN, - DOMAIN as FRITZBOX_DOMAIN) + ATTR_STATE_BATTERY_LOW, + ATTR_STATE_DEVICE_LOCKED, + ATTR_STATE_HOLIDAY_MODE, + ATTR_STATE_LOCKED, + ATTR_STATE_SUMMER_MODE, + ATTR_STATE_WINDOW_OPEN, + DOMAIN as FRITZBOX_DOMAIN, +) _LOGGER = logging.getLogger(__name__) @@ -25,7 +39,7 @@ OPERATION_LIST = [HVAC_MODE_HEAT, HVAC_MODE_OFF] MIN_TEMPERATURE = 8 MAX_TEMPERATURE = 28 -PRESET_MANUAL = 'manual' +PRESET_MANUAL = "manual" # special temperatures for on/off in Fritz!Box API (modified by pyfritzhome) ON_API_TEMPERATURE = 127.0 @@ -112,8 +126,8 @@ class FritzboxThermostat(ClimateDevice): def hvac_mode(self): """Return the current operation mode.""" if ( - self._target_temperature == OFF_REPORT_SET_TEMPERATURE or - self._target_temperature == OFF_API_TEMPERATURE + self._target_temperature == OFF_REPORT_SET_TEMPERATURE + or self._target_temperature == OFF_API_TEMPERATURE ): return HVAC_MODE_OFF diff --git a/homeassistant/components/fritzbox/sensor.py b/homeassistant/components/fritzbox/sensor.py index 123d8835318..4454ea35bbe 100644 --- a/homeassistant/components/fritzbox/sensor.py +++ b/homeassistant/components/fritzbox/sensor.py @@ -6,8 +6,7 @@ import requests from homeassistant.const import TEMP_CELSIUS from homeassistant.helpers.entity import Entity -from . import ( - ATTR_STATE_DEVICE_LOCKED, ATTR_STATE_LOCKED, DOMAIN as FRITZBOX_DOMAIN) +from . import ATTR_STATE_DEVICE_LOCKED, ATTR_STATE_LOCKED, DOMAIN as FRITZBOX_DOMAIN _LOGGER = logging.getLogger(__name__) @@ -21,9 +20,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for fritz in fritz_list: device_list = fritz.get_devices() for device in device_list: - if (device.has_temperature_sensor - and not device.has_switch - and not device.has_thermostat): + if ( + device.has_temperature_sensor + and not device.has_switch + and not device.has_thermostat + ): devices.append(FritzBoxTempSensor(device, fritz)) add_entities(devices) diff --git a/homeassistant/components/fritzbox/switch.py b/homeassistant/components/fritzbox/switch.py index ae1219cefda..c51c952ab06 100644 --- a/homeassistant/components/fritzbox/switch.py +++ b/homeassistant/components/fritzbox/switch.py @@ -4,19 +4,17 @@ import logging import requests from homeassistant.components.switch import SwitchDevice -from homeassistant.const import ( - ATTR_TEMPERATURE, ENERGY_KILO_WATT_HOUR, TEMP_CELSIUS) +from homeassistant.const import ATTR_TEMPERATURE, ENERGY_KILO_WATT_HOUR, TEMP_CELSIUS -from . import ( - ATTR_STATE_DEVICE_LOCKED, ATTR_STATE_LOCKED, DOMAIN as FRITZBOX_DOMAIN) +from . import ATTR_STATE_DEVICE_LOCKED, ATTR_STATE_LOCKED, DOMAIN as FRITZBOX_DOMAIN _LOGGER = logging.getLogger(__name__) -ATTR_TOTAL_CONSUMPTION = 'total_consumption' -ATTR_TOTAL_CONSUMPTION_UNIT = 'total_consumption_unit' +ATTR_TOTAL_CONSUMPTION = "total_consumption" +ATTR_TOTAL_CONSUMPTION_UNIT = "total_consumption_unit" ATTR_TOTAL_CONSUMPTION_UNIT_VALUE = ENERGY_KILO_WATT_HOUR -ATTR_TEMPERATURE_UNIT = 'temperature_unit' +ATTR_TEMPERATURE_UNIT = "temperature_unit" def setup_platform(hass, config, add_entities, discovery_info=None): @@ -81,15 +79,16 @@ class FritzboxSwitch(SwitchDevice): if self._device.has_powermeter: attrs[ATTR_TOTAL_CONSUMPTION] = "{:.3f}".format( - (self._device.energy or 0.0) / 1000) - attrs[ATTR_TOTAL_CONSUMPTION_UNIT] = \ - ATTR_TOTAL_CONSUMPTION_UNIT_VALUE + (self._device.energy or 0.0) / 1000 + ) + attrs[ATTR_TOTAL_CONSUMPTION_UNIT] = ATTR_TOTAL_CONSUMPTION_UNIT_VALUE if self._device.has_temperature_sensor: - attrs[ATTR_TEMPERATURE] = \ - str(self.hass.config.units.temperature( - self._device.temperature, TEMP_CELSIUS)) - attrs[ATTR_TEMPERATURE_UNIT] = \ - self.hass.config.units.temperature_unit + attrs[ATTR_TEMPERATURE] = str( + self.hass.config.units.temperature( + self._device.temperature, TEMP_CELSIUS + ) + ) + attrs[ATTR_TEMPERATURE_UNIT] = self.hass.config.units.temperature_unit return attrs @property diff --git a/homeassistant/components/fritzbox_callmonitor/sensor.py b/homeassistant/components/fritzbox_callmonitor/sensor.py index 95c0879996f..4dada44f4e5 100644 --- a/homeassistant/components/fritzbox_callmonitor/sensor.py +++ b/homeassistant/components/fritzbox_callmonitor/sensor.py @@ -9,44 +9,50 @@ import re import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import (CONF_HOST, CONF_PORT, CONF_NAME, - CONF_PASSWORD, CONF_USERNAME, - EVENT_HOMEASSISTANT_STOP) +from homeassistant.const import ( + CONF_HOST, + CONF_PORT, + CONF_NAME, + CONF_PASSWORD, + CONF_USERNAME, + EVENT_HOMEASSISTANT_STOP, +) from homeassistant.helpers.entity import Entity import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -CONF_PHONEBOOK = 'phonebook' -CONF_PREFIXES = 'prefixes' +CONF_PHONEBOOK = "phonebook" +CONF_PREFIXES = "prefixes" -DEFAULT_HOST = '169.254.1.1' # IP valid for all Fritz!Box routers -DEFAULT_NAME = 'Phone' +DEFAULT_HOST = "169.254.1.1" # IP valid for all Fritz!Box routers +DEFAULT_NAME = "Phone" DEFAULT_PORT = 1012 INTERVAL_RECONNECT = 60 -VALUE_CALL = 'dialing' -VALUE_CONNECT = 'talking' -VALUE_DEFAULT = 'idle' -VALUE_DISCONNECT = 'idle' -VALUE_RING = 'ringing' +VALUE_CALL = "dialing" +VALUE_CONNECT = "talking" +VALUE_DEFAULT = "idle" +VALUE_DISCONNECT = "idle" +VALUE_RING = "ringing" # Return cached results if phonebook was downloaded less then this time ago. MIN_TIME_PHONEBOOK_UPDATE = datetime.timedelta(hours=6) SCAN_INTERVAL = datetime.timedelta(hours=3) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_PASSWORD, default='admin'): cv.string, - vol.Optional(CONF_USERNAME, default=''): cv.string, - vol.Optional(CONF_PHONEBOOK, default=0): cv.positive_int, - vol.Optional(CONF_PREFIXES, default=[]): - vol.All(cv.ensure_list, [cv.string]) -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_PASSWORD, default="admin"): cv.string, + vol.Optional(CONF_USERNAME, default=""): cv.string, + vol.Optional(CONF_PHONEBOOK, default=0): cv.positive_int, + vol.Optional(CONF_PREFIXES, default=[]): vol.All(cv.ensure_list, [cv.string]), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -61,12 +67,16 @@ def setup_platform(hass, config, add_entities, discovery_info=None): try: phonebook = FritzBoxPhonebook( - host=host, port=port, username=username, password=password, - phonebook_id=phonebook_id, prefixes=prefixes) + host=host, + port=port, + username=username, + password=password, + phonebook_id=phonebook_id, + prefixes=prefixes, + ) except: # noqa: E722 pylint: disable=bare-except phonebook = None - _LOGGER.warning("Phonebook with ID %s not found on Fritz!Box", - phonebook_id) + _LOGGER.warning("Phonebook with ID %s not found on Fritz!Box", phonebook_id) sensor = FritzBoxCallSensor(name=name, phonebook=phonebook) @@ -78,10 +88,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): def _stop_listener(_event): monitor.stopped.set() - hass.bus.listen_once( - EVENT_HOMEASSISTANT_STOP, - _stop_listener - ) + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, _stop_listener) return monitor.sock is not None @@ -127,7 +134,7 @@ class FritzBoxCallSensor(Entity): def number_to_name(self, number): """Return a name for a given phone number.""" if self.phonebook is None: - return 'unknown' + return "unknown" return self.phonebook.get_name(number) def update(self): @@ -149,7 +156,7 @@ class FritzBoxCallMonitor: def connect(self): """Connect to the Fritz!Box.""" - _LOGGER.debug('Setting up socket...') + _LOGGER.debug("Setting up socket...") self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.settimeout(10) self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) @@ -158,12 +165,13 @@ class FritzBoxCallMonitor: threading.Thread(target=self._listen).start() except socket.error as err: self.sock = None - _LOGGER.error("Cannot connect to %s on port %s: %s", - self.host, self.port, err) + _LOGGER.error( + "Cannot connect to %s on port %s: %s", self.host, self.port, err + ) def _listen(self): """Listen to incoming or outgoing calls.""" - _LOGGER.debug('Connection established, waiting for response...') + _LOGGER.debug("Connection established, waiting for response...") while not self.stopped.isSet(): try: response = self.sock.recv(2048) @@ -171,12 +179,12 @@ class FritzBoxCallMonitor: # if no response after 10 seconds, just recv again continue response = str(response, "utf-8") - _LOGGER.debug('Received %s', response) + _LOGGER.debug("Received %s", response) if not response: # if the response is empty, the connection has been lost. # try to reconnect - _LOGGER.warning('Connection lost, reconnecting...') + _LOGGER.warning("Connection lost, reconnecting...") self.sock = None while self.sock is None: self.connect() @@ -194,20 +202,24 @@ class FritzBoxCallMonitor: isotime = datetime.datetime.strptime(line[0], df_in).strftime(df_out) if line[1] == "RING": self._sensor.set_state(VALUE_RING) - att = {"type": "incoming", - "from": line[3], - "to": line[4], - "device": line[5], - "initiated": isotime} + att = { + "type": "incoming", + "from": line[3], + "to": line[4], + "device": line[5], + "initiated": isotime, + } att["from_name"] = self._sensor.number_to_name(att["from"]) self._sensor.set_attributes(att) elif line[1] == "CALL": self._sensor.set_state(VALUE_CALL) - att = {"type": "outgoing", - "from": line[4], - "to": line[5], - "device": line[6], - "initiated": isotime} + att = { + "type": "outgoing", + "from": line[4], + "to": line[5], + "device": line[6], + "initiated": isotime, + } att["to_name"] = self._sensor.number_to_name(att["to"]) self._sensor.set_attributes(att) elif line[1] == "CONNECT": @@ -225,8 +237,7 @@ class FritzBoxCallMonitor: class FritzBoxPhonebook: """This connects to a FritzBox router and downloads its phone book.""" - def __init__(self, host, port, username, password, - phonebook_id=0, prefixes=None): + def __init__(self, host, port, username, password, phonebook_id=0, prefixes=None): """Initialize the class.""" self.host = host self.username = username @@ -238,9 +249,11 @@ class FritzBoxPhonebook: self.prefixes = prefixes or [] import fritzconnection as fc # pylint: disable=import-error + # Establish a connection to the FRITZ!Box. self.fph = fc.FritzPhonebook( - address=self.host, user=self.username, password=self.password) + address=self.host, user=self.username, password=self.password + ) if self.phonebook_id not in self.fph.list_phonebooks: raise ValueError("Phonebook with this ID not found.") @@ -251,16 +264,18 @@ class FritzBoxPhonebook: def update_phonebook(self): """Update the phone book dictionary.""" self.phonebook_dict = self.fph.get_all_names(self.phonebook_id) - self.number_dict = {re.sub(r'[^\d\+]', '', nr): name - for name, nrs in self.phonebook_dict.items() - for nr in nrs} + self.number_dict = { + re.sub(r"[^\d\+]", "", nr): name + for name, nrs in self.phonebook_dict.items() + for nr in nrs + } _LOGGER.info("Fritz!Box phone book successfully updated") def get_name(self, number): """Return a name for a given phone number.""" - number = re.sub(r'[^\d\+]', '', str(number)) + number = re.sub(r"[^\d\+]", "", str(number)) if self.number_dict is None: - return 'unknown' + return "unknown" try: return self.number_dict[number] except KeyError: @@ -272,7 +287,7 @@ class FritzBoxPhonebook: except KeyError: pass try: - return self.number_dict[prefix + number.lstrip('0')] + return self.number_dict[prefix + number.lstrip("0")] except KeyError: pass - return 'unknown' + return "unknown" diff --git a/homeassistant/components/fritzbox_netmonitor/sensor.py b/homeassistant/components/fritzbox_netmonitor/sensor.py index ec8e38bb24b..9d07e7a8055 100644 --- a/homeassistant/components/fritzbox_netmonitor/sensor.py +++ b/homeassistant/components/fritzbox_netmonitor/sensor.py @@ -6,39 +6,41 @@ from requests.exceptions import RequestException import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import (CONF_NAME, CONF_HOST, STATE_UNAVAILABLE) +from homeassistant.const import CONF_NAME, CONF_HOST, STATE_UNAVAILABLE from homeassistant.helpers.entity import Entity import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -CONF_DEFAULT_NAME = 'fritz_netmonitor' -CONF_DEFAULT_IP = '169.254.1.1' # This IP is valid for all FRITZ!Box routers. +CONF_DEFAULT_NAME = "fritz_netmonitor" +CONF_DEFAULT_IP = "169.254.1.1" # This IP is valid for all FRITZ!Box routers. -ATTR_BYTES_RECEIVED = 'bytes_received' -ATTR_BYTES_SENT = 'bytes_sent' -ATTR_TRANSMISSION_RATE_UP = 'transmission_rate_up' -ATTR_TRANSMISSION_RATE_DOWN = 'transmission_rate_down' -ATTR_EXTERNAL_IP = 'external_ip' -ATTR_IS_CONNECTED = 'is_connected' -ATTR_IS_LINKED = 'is_linked' -ATTR_MAX_BYTE_RATE_DOWN = 'max_byte_rate_down' -ATTR_MAX_BYTE_RATE_UP = 'max_byte_rate_up' -ATTR_UPTIME = 'uptime' -ATTR_WAN_ACCESS_TYPE = 'wan_access_type' +ATTR_BYTES_RECEIVED = "bytes_received" +ATTR_BYTES_SENT = "bytes_sent" +ATTR_TRANSMISSION_RATE_UP = "transmission_rate_up" +ATTR_TRANSMISSION_RATE_DOWN = "transmission_rate_down" +ATTR_EXTERNAL_IP = "external_ip" +ATTR_IS_CONNECTED = "is_connected" +ATTR_IS_LINKED = "is_linked" +ATTR_MAX_BYTE_RATE_DOWN = "max_byte_rate_down" +ATTR_MAX_BYTE_RATE_UP = "max_byte_rate_up" +ATTR_UPTIME = "uptime" +ATTR_WAN_ACCESS_TYPE = "wan_access_type" MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=5) -STATE_ONLINE = 'online' -STATE_OFFLINE = 'offline' +STATE_ONLINE = "online" +STATE_OFFLINE = "offline" -ICON = 'mdi:web' +ICON = "mdi:web" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME, default=CONF_DEFAULT_NAME): cv.string, - vol.Optional(CONF_HOST, default=CONF_DEFAULT_IP): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=CONF_DEFAULT_NAME): cv.string, + vol.Optional(CONF_HOST, default=CONF_DEFAULT_IP): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): diff --git a/homeassistant/components/fritzdect/switch.py b/homeassistant/components/fritzdect/switch.py index d3cd00a73f5..22a44a11133 100644 --- a/homeassistant/components/fritzdect/switch.py +++ b/homeassistant/components/fritzdect/switch.py @@ -5,32 +5,39 @@ from requests.exceptions import RequestException, HTTPError import voluptuous as vol -from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) +from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA from homeassistant.const import ( - CONF_HOST, CONF_PASSWORD, CONF_USERNAME, POWER_WATT, ENERGY_KILO_WATT_HOUR) + CONF_HOST, + CONF_PASSWORD, + CONF_USERNAME, + POWER_WATT, + ENERGY_KILO_WATT_HOUR, +) import homeassistant.helpers.config_validation as cv from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE _LOGGER = logging.getLogger(__name__) # Standard Fritz Box IP -DEFAULT_HOST = 'fritz.box' +DEFAULT_HOST = "fritz.box" -ATTR_CURRENT_CONSUMPTION = 'current_consumption' -ATTR_CURRENT_CONSUMPTION_UNIT = 'current_consumption_unit' +ATTR_CURRENT_CONSUMPTION = "current_consumption" +ATTR_CURRENT_CONSUMPTION_UNIT = "current_consumption_unit" ATTR_CURRENT_CONSUMPTION_UNIT_VALUE = POWER_WATT -ATTR_TOTAL_CONSUMPTION = 'total_consumption' -ATTR_TOTAL_CONSUMPTION_UNIT = 'total_consumption_unit' +ATTR_TOTAL_CONSUMPTION = "total_consumption" +ATTR_TOTAL_CONSUMPTION_UNIT = "total_consumption_unit" ATTR_TOTAL_CONSUMPTION_UNIT_VALUE = ENERGY_KILO_WATT_HOUR -ATTR_TEMPERATURE_UNIT = 'temperature_unit' +ATTR_TEMPERATURE_UNIT = "temperature_unit" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -77,24 +84,27 @@ class FritzDectSwitch(SwitchDevice): """Return the state attributes of the device.""" attrs = {} - if self.data.has_powermeter and \ - self.data.current_consumption is not None and \ - self.data.total_consumption is not None: + if ( + self.data.has_powermeter + and self.data.current_consumption is not None + and self.data.total_consumption is not None + ): attrs[ATTR_CURRENT_CONSUMPTION] = "{:.1f}".format( - self.data.current_consumption) + self.data.current_consumption + ) attrs[ATTR_CURRENT_CONSUMPTION_UNIT] = "{}".format( - ATTR_CURRENT_CONSUMPTION_UNIT_VALUE) - attrs[ATTR_TOTAL_CONSUMPTION] = "{:.3f}".format( - self.data.total_consumption) + ATTR_CURRENT_CONSUMPTION_UNIT_VALUE + ) + attrs[ATTR_TOTAL_CONSUMPTION] = "{:.3f}".format(self.data.total_consumption) attrs[ATTR_TOTAL_CONSUMPTION_UNIT] = "{}".format( - ATTR_TOTAL_CONSUMPTION_UNIT_VALUE) + ATTR_TOTAL_CONSUMPTION_UNIT_VALUE + ) - if self.data.has_temperature and \ - self.data.temperature is not None: + if self.data.has_temperature and self.data.temperature is not None: attrs[ATTR_TEMPERATURE] = "{}".format( - self.units.temperature(self.data.temperature, TEMP_CELSIUS)) - attrs[ATTR_TEMPERATURE_UNIT] = "{}".format( - self.units.temperature_unit) + self.units.temperature(self.data.temperature, TEMP_CELSIUS) + ) + attrs[ATTR_TEMPERATURE_UNIT] = "{}".format(self.units.temperature_unit) return attrs @property @@ -186,7 +196,7 @@ class FritzDectSwitchData: self.temperature = None self.current_consumption = None self.total_consumption = None - raise Exception('Request to actor registry failed') + raise Exception("Request to actor registry failed") if actor is None: _LOGGER.error("Actor could not be found") @@ -194,7 +204,7 @@ class FritzDectSwitchData: self.temperature = None self.current_consumption = None self.total_consumption = None - raise Exception('Actor could not be found') + raise Exception("Actor could not be found") try: self.state = actor.get_state() @@ -206,7 +216,7 @@ class FritzDectSwitchData: self.temperature = None self.current_consumption = None self.total_consumption = None - raise Exception('Request to actor failed') + raise Exception("Request to actor failed") self.temperature = actor.temperature self.has_switch = actor.has_switch diff --git a/homeassistant/components/fronius/sensor.py b/homeassistant/components/fronius/sensor.py index 07d2e984f23..ec1922e0d56 100644 --- a/homeassistant/components/fronius/sensor.py +++ b/homeassistant/components/fronius/sensor.py @@ -6,22 +6,26 @@ import voluptuous as vol from pyfronius import Fronius from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import (CONF_RESOURCE, CONF_SENSOR_TYPE, CONF_DEVICE, - CONF_MONITORED_CONDITIONS) +from homeassistant.const import ( + CONF_RESOURCE, + CONF_SENSOR_TYPE, + CONF_DEVICE, + CONF_MONITORED_CONDITIONS, +) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -CONF_SCOPE = 'scope' +CONF_SCOPE = "scope" -TYPE_INVERTER = 'inverter' -TYPE_STORAGE = 'storage' -TYPE_METER = 'meter' -TYPE_POWER_FLOW = 'power_flow' -SCOPE_DEVICE = 'device' -SCOPE_SYSTEM = 'system' +TYPE_INVERTER = "inverter" +TYPE_STORAGE = "storage" +TYPE_METER = "meter" +TYPE_POWER_FLOW = "power_flow" +SCOPE_DEVICE = "device" +SCOPE_SYSTEM = "system" DEFAULT_SCOPE = SCOPE_DEVICE DEFAULT_DEVICE = 0 @@ -43,27 +47,33 @@ def _device_id_validator(config): return config -PLATFORM_SCHEMA = vol.Schema(vol.All(PLATFORM_SCHEMA.extend({ - vol.Required(CONF_RESOURCE): - cv.url, - vol.Required(CONF_MONITORED_CONDITIONS): - vol.All( - cv.ensure_list, - [{ - vol.Required(CONF_SENSOR_TYPE): vol.In(SENSOR_TYPES), - vol.Optional(CONF_SCOPE, default=DEFAULT_SCOPE): - vol.In(SCOPE_TYPES), - vol.Optional(CONF_DEVICE): - vol.All(vol.Coerce(int), vol.Range(min=0)) - }] - ) -}), _device_id_validator)) +PLATFORM_SCHEMA = vol.Schema( + vol.All( + PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_RESOURCE): cv.url, + vol.Required(CONF_MONITORED_CONDITIONS): vol.All( + cv.ensure_list, + [ + { + vol.Required(CONF_SENSOR_TYPE): vol.In(SENSOR_TYPES), + vol.Optional(CONF_SCOPE, default=DEFAULT_SCOPE): vol.In( + SCOPE_TYPES + ), + vol.Optional(CONF_DEVICE): vol.All( + vol.Coerce(int), vol.Range(min=0) + ), + } + ], + ), + } + ), + _device_id_validator, + ) +) -async def async_setup_platform(hass, - config, - async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up of Fronius platform.""" session = async_get_clientsession(hass) fronius = Fronius(session, config[CONF_RESOURCE]) @@ -73,7 +83,7 @@ async def async_setup_platform(hass, device = condition[CONF_DEVICE] name = "Fronius {} {} {}".format( - condition[CONF_SENSOR_TYPE].replace('_', ' ').capitalize(), + condition[CONF_SENSOR_TYPE].replace("_", " ").capitalize(), device, config[CONF_RESOURCE], ) @@ -133,15 +143,17 @@ class FroniusSensor(Entity): except ConnectionError: _LOGGER.error("Failed to update: connection error") except ValueError: - _LOGGER.error("Failed to update: invalid response returned." - "Maybe the configured device is not supported") + _LOGGER.error( + "Failed to update: invalid response returned." + "Maybe the configured device is not supported" + ) if values: - self._state = values['status']['Code'] + self._state = values["status"]["Code"] attributes = {} for key in values: - if 'value' in values[key]: - attributes[key] = values[key].get('value', 0) + if "value" in values[key]: + attributes[key] = values[key].get("value", 0) self._attributes = attributes async def _update(self): diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index d311baf8ae1..b769d6b9aea 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -28,89 +28,93 @@ mimetypes.add_type("text/css", ".css") mimetypes.add_type("application/javascript", ".js") -DOMAIN = 'frontend' -CONF_THEMES = 'themes' -CONF_EXTRA_HTML_URL = 'extra_html_url' -CONF_EXTRA_HTML_URL_ES5 = 'extra_html_url_es5' -CONF_EXTRA_MODULE_URL = 'extra_module_url' -CONF_EXTRA_JS_URL_ES5 = 'extra_js_url_es5' -CONF_FRONTEND_REPO = 'development_repo' -CONF_JS_VERSION = 'javascript_version' -EVENT_PANELS_UPDATED = 'panels_updated' +DOMAIN = "frontend" +CONF_THEMES = "themes" +CONF_EXTRA_HTML_URL = "extra_html_url" +CONF_EXTRA_HTML_URL_ES5 = "extra_html_url_es5" +CONF_EXTRA_MODULE_URL = "extra_module_url" +CONF_EXTRA_JS_URL_ES5 = "extra_js_url_es5" +CONF_FRONTEND_REPO = "development_repo" +CONF_JS_VERSION = "javascript_version" +EVENT_PANELS_UPDATED = "panels_updated" -DEFAULT_THEME_COLOR = '#03A9F4' +DEFAULT_THEME_COLOR = "#03A9F4" MANIFEST_JSON = { - 'background_color': '#FFFFFF', - 'description': - 'Home automation platform that puts local control and privacy first.', - 'dir': 'ltr', - 'display': 'standalone', - 'icons': [], - 'lang': 'en-US', - 'name': 'Home Assistant', - 'short_name': 'Assistant', - 'start_url': '/?homescreen=1', - 'theme_color': DEFAULT_THEME_COLOR + "background_color": "#FFFFFF", + "description": "Home automation platform that puts local control and privacy first.", + "dir": "ltr", + "display": "standalone", + "icons": [], + "lang": "en-US", + "name": "Home Assistant", + "short_name": "Assistant", + "start_url": "/?homescreen=1", + "theme_color": DEFAULT_THEME_COLOR, } for size in (192, 384, 512, 1024): - MANIFEST_JSON['icons'].append({ - 'src': '/static/icons/favicon-{size}x{size}.png'.format(size=size), - 'sizes': '{size}x{size}'.format(size=size), - 'type': 'image/png' - }) + MANIFEST_JSON["icons"].append( + { + "src": "/static/icons/favicon-{size}x{size}.png".format(size=size), + "sizes": "{size}x{size}".format(size=size), + "type": "image/png", + } + ) -DATA_PANELS = 'frontend_panels' -DATA_JS_VERSION = 'frontend_js_version' -DATA_EXTRA_HTML_URL = 'frontend_extra_html_url' -DATA_EXTRA_HTML_URL_ES5 = 'frontend_extra_html_url_es5' -DATA_EXTRA_MODULE_URL = 'frontend_extra_module_url' -DATA_EXTRA_JS_URL_ES5 = 'frontend_extra_js_url_es5' -DATA_THEMES = 'frontend_themes' -DATA_DEFAULT_THEME = 'frontend_default_theme' -DEFAULT_THEME = 'default' +DATA_PANELS = "frontend_panels" +DATA_JS_VERSION = "frontend_js_version" +DATA_EXTRA_HTML_URL = "frontend_extra_html_url" +DATA_EXTRA_HTML_URL_ES5 = "frontend_extra_html_url_es5" +DATA_EXTRA_MODULE_URL = "frontend_extra_module_url" +DATA_EXTRA_JS_URL_ES5 = "frontend_extra_js_url_es5" +DATA_THEMES = "frontend_themes" +DATA_DEFAULT_THEME = "frontend_default_theme" +DEFAULT_THEME = "default" -PRIMARY_COLOR = 'primary-color' +PRIMARY_COLOR = "primary-color" _LOGGER = logging.getLogger(__name__) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Optional(CONF_FRONTEND_REPO): cv.isdir, - vol.Optional(CONF_THEMES): vol.Schema({ - cv.string: {cv.string: cv.string} - }), - vol.Optional(CONF_EXTRA_HTML_URL): - vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_EXTRA_MODULE_URL): - vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_EXTRA_JS_URL_ES5): - vol.All(cv.ensure_list, [cv.string]), - # We no longer use these options. - vol.Optional(CONF_EXTRA_HTML_URL_ES5): cv.match_all, - vol.Optional(CONF_JS_VERSION): cv.match_all, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Optional(CONF_FRONTEND_REPO): cv.isdir, + vol.Optional(CONF_THEMES): vol.Schema( + {cv.string: {cv.string: cv.string}} + ), + vol.Optional(CONF_EXTRA_HTML_URL): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_EXTRA_MODULE_URL): vol.All( + cv.ensure_list, [cv.string] + ), + vol.Optional(CONF_EXTRA_JS_URL_ES5): vol.All( + cv.ensure_list, [cv.string] + ), + # We no longer use these options. + vol.Optional(CONF_EXTRA_HTML_URL_ES5): cv.match_all, + vol.Optional(CONF_JS_VERSION): cv.match_all, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) -SERVICE_SET_THEME = 'set_theme' -SERVICE_RELOAD_THEMES = 'reload_themes' -SERVICE_SET_THEME_SCHEMA = vol.Schema({ - vol.Required(CONF_NAME): cv.string, -}) -WS_TYPE_GET_PANELS = 'get_panels' -SCHEMA_GET_PANELS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_GET_PANELS, -}) -WS_TYPE_GET_THEMES = 'frontend/get_themes' -SCHEMA_GET_THEMES = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_GET_THEMES, -}) -WS_TYPE_GET_TRANSLATIONS = 'frontend/get_translations' -SCHEMA_GET_TRANSLATIONS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_GET_TRANSLATIONS, - vol.Required('language'): str, -}) +SERVICE_SET_THEME = "set_theme" +SERVICE_RELOAD_THEMES = "reload_themes" +SERVICE_SET_THEME_SCHEMA = vol.Schema({vol.Required(CONF_NAME): cv.string}) +WS_TYPE_GET_PANELS = "get_panels" +SCHEMA_GET_PANELS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {vol.Required("type"): WS_TYPE_GET_PANELS} +) +WS_TYPE_GET_THEMES = "frontend/get_themes" +SCHEMA_GET_THEMES = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {vol.Required("type"): WS_TYPE_GET_THEMES} +) +WS_TYPE_GET_TRANSLATIONS = "frontend/get_translations" +SCHEMA_GET_TRANSLATIONS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {vol.Required("type"): WS_TYPE_GET_TRANSLATIONS, vol.Required("language"): str} +) class Panel: @@ -134,8 +138,15 @@ class Panel: # If the panel should only be visible to admins require_admin = False - def __init__(self, component_name, sidebar_title, sidebar_icon, - frontend_url_path, config, require_admin): + def __init__( + self, + component_name, + sidebar_title, + sidebar_icon, + frontend_url_path, + config, + require_admin, + ): """Initialize a built-in panel.""" self.component_name = component_name self.sidebar_title = sidebar_title @@ -148,24 +159,35 @@ class Panel: def to_response(self): """Panel as dictionary.""" return { - 'component_name': self.component_name, - 'icon': self.sidebar_icon, - 'title': self.sidebar_title, - 'config': self.config, - 'url_path': self.frontend_url_path, - 'require_admin': self.require_admin, + "component_name": self.component_name, + "icon": self.sidebar_icon, + "title": self.sidebar_title, + "config": self.config, + "url_path": self.frontend_url_path, + "require_admin": self.require_admin, } @bind_hass @callback -def async_register_built_in_panel(hass, component_name, - sidebar_title=None, sidebar_icon=None, - frontend_url_path=None, config=None, - require_admin=False): +def async_register_built_in_panel( + hass, + component_name, + sidebar_title=None, + sidebar_icon=None, + frontend_url_path=None, + config=None, + require_admin=False, +): """Register a built-in panel.""" - panel = Panel(component_name, sidebar_title, sidebar_icon, - frontend_url_path, config, require_admin) + panel = Panel( + component_name, + sidebar_title, + sidebar_icon, + frontend_url_path, + config, + require_admin, + ) panels = hass.data.setdefault(DATA_PANELS, {}) @@ -217,9 +239,10 @@ def add_manifest_json_key(key, val): def _frontend_root(dev_repo_path): """Return root path to the frontend files.""" if dev_repo_path is not None: - return pathlib.Path(dev_repo_path) / 'hass_frontend' + return pathlib.Path(dev_repo_path) / "hass_frontend" import hass_frontend + return hass_frontend.where() @@ -227,12 +250,14 @@ async def async_setup(hass, config): """Set up the serving of the frontend.""" await async_setup_frontend_storage(hass) hass.components.websocket_api.async_register_command( - WS_TYPE_GET_PANELS, websocket_get_panels, SCHEMA_GET_PANELS) + WS_TYPE_GET_PANELS, websocket_get_panels, SCHEMA_GET_PANELS + ) hass.components.websocket_api.async_register_command( - WS_TYPE_GET_THEMES, websocket_get_themes, SCHEMA_GET_THEMES) + WS_TYPE_GET_THEMES, websocket_get_themes, SCHEMA_GET_THEMES + ) hass.components.websocket_api.async_register_command( - WS_TYPE_GET_TRANSLATIONS, websocket_get_translations, - SCHEMA_GET_TRANSLATIONS) + WS_TYPE_GET_TRANSLATIONS, websocket_get_translations, SCHEMA_GET_TRANSLATIONS + ) hass.http.register_view(ManifestJSONView) conf = config.get(DOMAIN, {}) @@ -242,37 +267,44 @@ async def async_setup(hass, config): root_path = _frontend_root(repo_path) for path, should_cache in ( - ("service_worker.js", False), - ("robots.txt", False), - ("onboarding.html", True), - ("static", True), - ("frontend_latest", True), - ("frontend_es5", True), + ("service_worker.js", False), + ("robots.txt", False), + ("onboarding.html", True), + ("static", True), + ("frontend_latest", True), + ("frontend_es5", True), ): hass.http.register_static_path( - "/{}".format(path), str(root_path / path), should_cache) + "/{}".format(path), str(root_path / path), should_cache + ) hass.http.register_static_path( - "/auth/authorize", str(root_path / "authorize.html"), False) + "/auth/authorize", str(root_path / "authorize.html"), False + ) - local = hass.config.path('www') + local = hass.config.path("www") if os.path.isdir(local): hass.http.register_static_path("/local", local, not is_dev) hass.http.app.router.register_resource(IndexView(repo_path, hass)) - for panel in ('kiosk', 'states', 'profile'): + for panel in ("kiosk", "states", "profile"): async_register_built_in_panel(hass, panel) # To smooth transition to new urls, add redirects to new urls of dev tools # Added June 27, 2019. Can be removed in 2021. - for panel in ('event', 'info', 'service', 'state', 'template', 'mqtt'): - hass.http.register_redirect('/dev-{}'.format(panel), - '/developer-tools/{}'.format(panel)) + for panel in ("event", "info", "service", "state", "template", "mqtt"): + hass.http.register_redirect( + "/dev-{}".format(panel), "/developer-tools/{}".format(panel) + ) async_register_built_in_panel( - hass, "developer-tools", require_admin=True, - sidebar_title="developer_tools", sidebar_icon="hass:hammer") + hass, + "developer-tools", + require_admin=True, + sidebar_title="developer_tools", + sidebar_icon="hass:hammer", + ) if DATA_EXTRA_HTML_URL not in hass.data: hass.data[DATA_EXTRA_HTML_URL] = set() @@ -313,13 +345,12 @@ def _async_setup_themes(hass, themes): name = hass.data[DATA_DEFAULT_THEME] themes = hass.data[DATA_THEMES] if name != DEFAULT_THEME and PRIMARY_COLOR in themes[name]: - MANIFEST_JSON['theme_color'] = themes[name][PRIMARY_COLOR] + MANIFEST_JSON["theme_color"] = themes[name][PRIMARY_COLOR] else: - MANIFEST_JSON['theme_color'] = DEFAULT_THEME_COLOR - hass.bus.async_fire(EVENT_THEMES_UPDATED, { - 'themes': themes, - 'default_theme': name, - }) + MANIFEST_JSON["theme_color"] = DEFAULT_THEME_COLOR + hass.bus.async_fire( + EVENT_THEMES_UPDATED, {"themes": themes, "default_theme": name} + ) @callback def set_theme(call): @@ -344,7 +375,8 @@ def _async_setup_themes(hass, themes): update_theme_and_fire_event() hass.services.async_register( - DOMAIN, SERVICE_SET_THEME, set_theme, schema=SERVICE_SET_THEME_SCHEMA) + DOMAIN, SERVICE_SET_THEME, set_theme, schema=SERVICE_SET_THEME_SCHEMA + ) hass.services.async_register(DOMAIN, SERVICE_RELOAD_THEMES, reload_themes) @@ -361,12 +393,12 @@ class IndexView(web_urldispatcher.AbstractResource): @property def canonical(self) -> str: """Return resource's canonical path.""" - return '/' + return "/" @property def _route(self): """Return the index route.""" - return web_urldispatcher.ResourceRoute('GET', self.get, self) + return web_urldispatcher.ResourceRoute("GET", self.get, self) def url_for(self, **kwargs: str) -> URL: """Construct url for resource with additional params.""" @@ -377,14 +409,16 @@ class IndexView(web_urldispatcher.AbstractResource): Return (UrlMappingMatchInfo, allowed_methods) pair. """ - if (request.path != '/' and - request.url.parts[1] not in self.hass.data[DATA_PANELS]): + if ( + request.path != "/" + and request.url.parts[1] not in self.hass.data[DATA_PANELS] + ): return None, set() if request.method != hdrs.METH_GET: - return None, {'GET'} + return None, {"GET"} - return web_urldispatcher.UrlMappingMatchInfo({}, self._route), {'GET'} + return web_urldispatcher.UrlMappingMatchInfo({}, self._route), {"GET"} def add_prefix(self, prefix: str) -> None: """Add a prefix to processed URLs. @@ -394,9 +428,7 @@ class IndexView(web_urldispatcher.AbstractResource): def get_info(self): """Return a dict with additional info useful for introspection.""" - return { - 'panels': list(self.hass.data[DATA_PANELS]) - } + return {"panels": list(self.hass.data[DATA_PANELS])} def freeze(self) -> None: """Freeze the resource.""" @@ -410,9 +442,7 @@ class IndexView(web_urldispatcher.AbstractResource): """Get template.""" tpl = self._template_cache if tpl is None: - with open( - str(_frontend_root(self.repo_path) / 'index.html') - ) as file: + with open(str(_frontend_root(self.repo_path) / "index.html")) as file: tpl = jinja2.Template(file.read()) # Cache template if not running from repository @@ -423,12 +453,10 @@ class IndexView(web_urldispatcher.AbstractResource): async def get(self, request: web.Request): """Serve the index page for panel pages.""" - hass = request.app['hass'] + hass = request.app["hass"] if not hass.components.onboarding.async_is_onboarded(): - return web.Response(status=302, headers={ - 'location': '/onboarding.html' - }) + return web.Response(status=302, headers={"location": "/onboarding.html"}) template = self._template_cache @@ -437,12 +465,12 @@ class IndexView(web_urldispatcher.AbstractResource): return web.Response( text=template.render( - theme_color=MANIFEST_JSON['theme_color'], + theme_color=MANIFEST_JSON["theme_color"], extra_urls=hass.data[DATA_EXTRA_HTML_URL], extra_modules=hass.data[DATA_EXTRA_MODULE_URL], extra_js_es5=hass.data[DATA_EXTRA_JS_URL_ES5], ), - content_type='text/html' + content_type="text/html", ) def __len__(self) -> int: @@ -458,11 +486,11 @@ class ManifestJSONView(HomeAssistantView): """View to return a manifest.json.""" requires_auth = False - url = '/manifest.json' - name = 'manifestjson' + url = "/manifest.json" + name = "manifestjson" @callback - def get(self, request): # pylint: disable=no-self-use + def get(self, request): # pylint: disable=no-self-use """Return the manifest.json.""" msg = json.dumps(MANIFEST_JSON, sort_keys=True) return web.Response(text=msg, content_type="application/manifest+json") @@ -478,10 +506,10 @@ def websocket_get_panels(hass, connection, msg): panels = { panel_key: panel.to_response() for panel_key, panel in connection.hass.data[DATA_PANELS].items() - if user_is_admin or not panel.require_admin} + if user_is_admin or not panel.require_admin + } - connection.send_message(websocket_api.result_message( - msg['id'], panels)) + connection.send_message(websocket_api.result_message(msg["id"], panels)) @callback @@ -490,10 +518,15 @@ def websocket_get_themes(hass, connection, msg): Async friendly. """ - connection.send_message(websocket_api.result_message(msg['id'], { - 'themes': hass.data[DATA_THEMES], - 'default_theme': hass.data[DATA_DEFAULT_THEME], - })) + connection.send_message( + websocket_api.result_message( + msg["id"], + { + "themes": hass.data[DATA_THEMES], + "default_theme": hass.data[DATA_DEFAULT_THEME], + }, + ) + ) @websocket_api.async_response @@ -502,9 +535,7 @@ async def websocket_get_translations(hass, connection, msg): Async friendly. """ - resources = await async_get_translations(hass, msg['language']) - connection.send_message(websocket_api.result_message( - msg['id'], { - 'resources': resources, - } - )) + resources = await async_get_translations(hass, msg["language"]) + connection.send_message( + websocket_api.result_message(msg["id"], {"resources": resources}) + ) diff --git a/homeassistant/components/frontend/storage.py b/homeassistant/components/frontend/storage.py index 17aae14c820..56f23da5253 100644 --- a/homeassistant/components/frontend/storage.py +++ b/homeassistant/components/frontend/storage.py @@ -4,24 +4,21 @@ import voluptuous as vol from homeassistant.components import websocket_api -DATA_STORAGE = 'frontend_storage' +DATA_STORAGE = "frontend_storage" STORAGE_VERSION_USER_DATA = 1 -STORAGE_KEY_USER_DATA = 'frontend.user_data_{}' +STORAGE_KEY_USER_DATA = "frontend.user_data_{}" async def async_setup_frontend_storage(hass): """Set up frontend storage.""" hass.data[DATA_STORAGE] = ({}, {}) - hass.components.websocket_api.async_register_command( - websocket_set_user_data - ) - hass.components.websocket_api.async_register_command( - websocket_get_user_data - ) + hass.components.websocket_api.async_register_command(websocket_set_user_data) + hass.components.websocket_api.async_register_command(websocket_get_user_data) def with_store(orig_func): """Decorate function to provide data.""" + @wraps(orig_func) async def with_store_func(hass, connection, msg): """Provide user specific data and store to function.""" @@ -32,25 +29,24 @@ def with_store(orig_func): if store is None: store = stores[user_id] = hass.helpers.storage.Store( STORAGE_VERSION_USER_DATA, - STORAGE_KEY_USER_DATA.format(connection.user.id) + STORAGE_KEY_USER_DATA.format(connection.user.id), ) if user_id not in data: data[user_id] = await store.async_load() or {} - await orig_func( - hass, connection, msg, - store, - data[user_id], - ) + await orig_func(hass, connection, msg, store, data[user_id]) + return with_store_func -@websocket_api.websocket_command({ - vol.Required('type'): 'frontend/set_user_data', - vol.Required('key'): str, - vol.Required('value'): vol.Any(bool, str, int, float, dict, list, None), -}) +@websocket_api.websocket_command( + { + vol.Required("type"): "frontend/set_user_data", + vol.Required("key"): str, + vol.Required("value"): vol.Any(bool, str, int, float, dict, list, None), + } +) @websocket_api.async_response @with_store async def websocket_set_user_data(hass, connection, msg, store, data): @@ -58,17 +54,14 @@ async def websocket_set_user_data(hass, connection, msg, store, data): Async friendly. """ - data[msg['key']] = msg['value'] + data[msg["key"]] = msg["value"] await store.async_save(data) - connection.send_message(websocket_api.result_message( - msg['id'], - )) + connection.send_message(websocket_api.result_message(msg["id"])) -@websocket_api.websocket_command({ - vol.Required('type'): 'frontend/get_user_data', - vol.Optional('key'): str, -}) +@websocket_api.websocket_command( + {vol.Required("type"): "frontend/get_user_data", vol.Optional("key"): str} +) @websocket_api.async_response @with_store async def websocket_get_user_data(hass, connection, msg, store, data): @@ -76,8 +69,8 @@ async def websocket_get_user_data(hass, connection, msg, store, data): Async friendly. """ - connection.send_message(websocket_api.result_message( - msg['id'], { - 'value': data.get(msg['key']) if 'key' in msg else data - } - )) + connection.send_message( + websocket_api.result_message( + msg["id"], {"value": data.get(msg["key"]) if "key" in msg else data} + ) + ) diff --git a/homeassistant/components/frontier_silicon/media_player.py b/homeassistant/components/frontier_silicon/media_player.py index 64aa1d3a012..8ab379b050b 100644 --- a/homeassistant/components/frontier_silicon/media_player.py +++ b/homeassistant/components/frontier_silicon/media_player.py @@ -3,46 +3,73 @@ import logging import voluptuous as vol -from homeassistant.components.media_player import ( - MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA from homeassistant.components.media_player.const import ( - MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, - SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, - SUPPORT_SELECT_SOURCE, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP) + MEDIA_TYPE_MUSIC, + SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, + SUPPORT_PLAY, + SUPPORT_PLAY_MEDIA, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_SEEK, + SUPPORT_SELECT_SOURCE, + SUPPORT_STOP, + SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, + SUPPORT_VOLUME_MUTE, + SUPPORT_VOLUME_SET, + SUPPORT_VOLUME_STEP, +) from homeassistant.const import ( - CONF_HOST, CONF_PASSWORD, CONF_PORT, STATE_OFF, STATE_PAUSED, - STATE_PLAYING, STATE_UNKNOWN) + CONF_HOST, + CONF_PASSWORD, + CONF_PORT, + STATE_OFF, + STATE_PAUSED, + STATE_PLAYING, + STATE_UNKNOWN, +) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -SUPPORT_FRONTIER_SILICON = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | \ - SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_STEP | \ - SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK | \ - SUPPORT_PLAY_MEDIA | SUPPORT_PLAY | SUPPORT_STOP | SUPPORT_TURN_ON | \ - SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE +SUPPORT_FRONTIER_SILICON = ( + SUPPORT_PAUSE + | SUPPORT_VOLUME_SET + | SUPPORT_VOLUME_MUTE + | SUPPORT_VOLUME_STEP + | SUPPORT_PREVIOUS_TRACK + | SUPPORT_NEXT_TRACK + | SUPPORT_SEEK + | SUPPORT_PLAY_MEDIA + | SUPPORT_PLAY + | SUPPORT_STOP + | SUPPORT_TURN_ON + | SUPPORT_TURN_OFF + | SUPPORT_SELECT_SOURCE +) DEFAULT_PORT = 80 -DEFAULT_PASSWORD = '1234' -DEVICE_URL = 'http://{0}:{1}/device' +DEFAULT_PASSWORD = "1234" +DEVICE_URL = "http://{0}:{1}/device" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string, + } +) -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Frontier Silicon platform.""" import requests if discovery_info is not None: async_add_entities( - [AFSAPIDevice( - discovery_info['ssdp_description'], DEFAULT_PASSWORD)], True) + [AFSAPIDevice(discovery_info["ssdp_description"], DEFAULT_PASSWORD)], True + ) return True host = config.get(CONF_HOST) @@ -51,12 +78,14 @@ async def async_setup_platform( try: async_add_entities( - [AFSAPIDevice(DEVICE_URL.format(host, port), password)], True) + [AFSAPIDevice(DEVICE_URL.format(host, port), password)], True + ) _LOGGER.debug("FSAPI device %s:%s -> %s", host, port, password) return True except requests.exceptions.RequestException: - _LOGGER.error("Could not add the FSAPI device at %s:%s -> %s", - host, port, password) + _LOGGER.error( + "Could not add the FSAPI device at %s:%s -> %s", host, port, password + ) return False @@ -161,10 +190,10 @@ class AFSAPIDevice(MediaPlayerDevice): status = await fs_device.get_play_status() self._state = { - 'playing': STATE_PLAYING, - 'paused': STATE_PAUSED, - 'stopped': STATE_OFF, - 'unknown': STATE_UNKNOWN, + "playing": STATE_PLAYING, + "paused": STATE_PAUSED, + "stopped": STATE_OFF, + "unknown": STATE_UNKNOWN, None: STATE_OFF, }.get(status, STATE_UNKNOWN) @@ -172,7 +201,7 @@ class AFSAPIDevice(MediaPlayerDevice): info_name = await fs_device.get_play_name() info_text = await fs_device.get_play_text() - self._title = ' - '.join(filter(None, [info_name, info_text])) + self._title = " - ".join(filter(None, [info_name, info_text])) self._artist = await fs_device.get_play_artist() self._album_name = await fs_device.get_play_album() @@ -208,7 +237,7 @@ class AFSAPIDevice(MediaPlayerDevice): async def async_media_play_pause(self): """Send play/pause command.""" - if 'playing' in self._state: + if "playing" in self._state: await self.fs_device.pause() else: await self.fs_device.play() @@ -239,12 +268,12 @@ class AFSAPIDevice(MediaPlayerDevice): async def async_volume_up(self): """Send volume up command.""" volume = await self.fs_device.get_volume() - await self.fs_device.set_volume(volume+1) + await self.fs_device.set_volume(volume + 1) async def async_volume_down(self): """Send volume down command.""" volume = await self.fs_device.get_volume() - await self.fs_device.set_volume(volume-1) + await self.fs_device.set_volume(volume - 1) async def async_set_volume_level(self, volume): """Set volume command.""" diff --git a/homeassistant/components/futurenow/light.py b/homeassistant/components/futurenow/light.py index 91ec8b0794d..eba768f82e3 100644 --- a/homeassistant/components/futurenow/light.py +++ b/homeassistant/components/futurenow/light.py @@ -4,31 +4,37 @@ import logging import voluptuous as vol -from homeassistant.const import ( - CONF_NAME, CONF_HOST, CONF_PORT, CONF_DEVICES) +from homeassistant.const import CONF_NAME, CONF_HOST, CONF_PORT, CONF_DEVICES from homeassistant.components.light import ( - ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light, - PLATFORM_SCHEMA) + ATTR_BRIGHTNESS, + SUPPORT_BRIGHTNESS, + Light, + PLATFORM_SCHEMA, +) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -CONF_DRIVER = 'driver' -CONF_DRIVER_FNIP6X10AD = 'FNIP6x10ad' -CONF_DRIVER_FNIP8X10A = 'FNIP8x10a' +CONF_DRIVER = "driver" +CONF_DRIVER_FNIP6X10AD = "FNIP6x10ad" +CONF_DRIVER_FNIP8X10A = "FNIP8x10a" CONF_DRIVER_TYPES = [CONF_DRIVER_FNIP6X10AD, CONF_DRIVER_FNIP8X10A] -DEVICE_SCHEMA = vol.Schema({ - vol.Required(CONF_NAME): cv.string, - vol.Optional('dimmable', default=False): cv.boolean, -}) +DEVICE_SCHEMA = vol.Schema( + { + vol.Required(CONF_NAME): cv.string, + vol.Optional("dimmable", default=False): cv.boolean, + } +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_DRIVER): vol.In(CONF_DRIVER_TYPES), - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_PORT): cv.port, - vol.Required(CONF_DEVICES): {cv.string: DEVICE_SCHEMA}, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_DRIVER): vol.In(CONF_DRIVER_TYPES), + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PORT): cv.port, + vol.Required(CONF_DEVICES): {cv.string: DEVICE_SCHEMA}, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -36,12 +42,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None): lights = [] for channel, device_config in config[CONF_DEVICES].items(): device = {} - device['name'] = device_config[CONF_NAME] - device['dimmable'] = device_config['dimmable'] - device['channel'] = channel - device['driver'] = config[CONF_DRIVER] - device['host'] = config[CONF_HOST] - device['port'] = config[CONF_PORT] + device["name"] = device_config[CONF_NAME] + device["dimmable"] = device_config["dimmable"] + device["channel"] = channel + device["driver"] = config[CONF_DRIVER] + device["host"] = config[CONF_HOST] + device["port"] = config[CONF_PORT] lights.append(FutureNowLight(device)) add_entities(lights, True) @@ -64,21 +70,21 @@ class FutureNowLight(Light): """Initialize the light.""" import pyfnip - self._name = device['name'] - self._dimmable = device['dimmable'] - self._channel = device['channel'] + self._name = device["name"] + self._dimmable = device["dimmable"] + self._channel = device["channel"] self._brightness = None self._last_brightness = 255 self._state = None - if device['driver'] == CONF_DRIVER_FNIP6X10AD: - self._light = pyfnip.FNIP6x2adOutput(device['host'], - device['port'], - self._channel) - if device['driver'] == CONF_DRIVER_FNIP8X10A: - self._light = pyfnip.FNIP8x10aOutput(device['host'], - device['port'], - self._channel) + if device["driver"] == CONF_DRIVER_FNIP6X10AD: + self._light = pyfnip.FNIP6x2adOutput( + device["host"], device["port"], self._channel + ) + if device["driver"] == CONF_DRIVER_FNIP8X10A: + self._light = pyfnip.FNIP8x10aOutput( + device["host"], device["port"], self._channel + ) @property def name(self): diff --git a/homeassistant/components/garadget/cover.py b/homeassistant/components/garadget/cover.py index b3c7c7c1215..2e52b49c5f4 100644 --- a/homeassistant/components/garadget/cover.py +++ b/homeassistant/components/garadget/cover.py @@ -8,42 +8,51 @@ import homeassistant.helpers.config_validation as cv from homeassistant.components.cover import CoverDevice, PLATFORM_SCHEMA from homeassistant.helpers.event import track_utc_time_change from homeassistant.const import ( - CONF_DEVICE, CONF_USERNAME, CONF_PASSWORD, CONF_ACCESS_TOKEN, CONF_NAME, - STATE_CLOSED, STATE_OPEN, CONF_COVERS) + CONF_DEVICE, + CONF_USERNAME, + CONF_PASSWORD, + CONF_ACCESS_TOKEN, + CONF_NAME, + STATE_CLOSED, + STATE_OPEN, + CONF_COVERS, +) _LOGGER = logging.getLogger(__name__) -ATTR_AVAILABLE = 'available' -ATTR_SENSOR_STRENGTH = 'sensor_reflection_rate' -ATTR_SIGNAL_STRENGTH = 'wifi_signal_strength' -ATTR_TIME_IN_STATE = 'time_in_state' +ATTR_AVAILABLE = "available" +ATTR_SENSOR_STRENGTH = "sensor_reflection_rate" +ATTR_SIGNAL_STRENGTH = "wifi_signal_strength" +ATTR_TIME_IN_STATE = "time_in_state" -DEFAULT_NAME = 'Garadget' +DEFAULT_NAME = "Garadget" -STATE_CLOSING = 'closing' -STATE_OFFLINE = 'offline' -STATE_OPENING = 'opening' -STATE_STOPPED = 'stopped' +STATE_CLOSING = "closing" +STATE_OFFLINE = "offline" +STATE_OPENING = "opening" +STATE_STOPPED = "stopped" STATES_MAP = { - 'open': STATE_OPEN, - 'opening': STATE_OPENING, - 'closed': STATE_CLOSED, - 'closing': STATE_CLOSING, - 'stopped': STATE_STOPPED + "open": STATE_OPEN, + "opening": STATE_OPENING, + "closed": STATE_CLOSED, + "closing": STATE_CLOSING, + "stopped": STATE_STOPPED, } -COVER_SCHEMA = vol.Schema({ - vol.Optional(CONF_ACCESS_TOKEN): cv.string, - vol.Optional(CONF_DEVICE): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PASSWORD): cv.string, - vol.Optional(CONF_USERNAME): cv.string, -}) +COVER_SCHEMA = vol.Schema( + { + vol.Optional(CONF_ACCESS_TOKEN): cv.string, + vol.Optional(CONF_DEVICE): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PASSWORD): cv.string, + vol.Optional(CONF_USERNAME): cv.string, + } +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_COVERS): cv.schema_with_slug_keys(COVER_SCHEMA), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_COVERS): cv.schema_with_slug_keys(COVER_SCHEMA)} +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -53,11 +62,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for device_id, device_config in devices.items(): args = { - 'name': device_config.get(CONF_NAME), - 'device_id': device_config.get(CONF_DEVICE, device_id), - 'username': device_config.get(CONF_USERNAME), - 'password': device_config.get(CONF_PASSWORD), - 'access_token': device_config.get(CONF_ACCESS_TOKEN) + "name": device_config.get(CONF_NAME), + "device_id": device_config.get(CONF_DEVICE, device_id), + "username": device_config.get(CONF_USERNAME), + "password": device_config.get(CONF_PASSWORD), + "access_token": device_config.get(CONF_ACCESS_TOKEN), } covers.append(GaradgetCover(hass, args)) @@ -70,14 +79,14 @@ class GaradgetCover(CoverDevice): def __init__(self, hass, args): """Initialize the cover.""" - self.particle_url = 'https://api.particle.io' + self.particle_url = "https://api.particle.io" self.hass = hass - self._name = args['name'] - self.device_id = args['device_id'] - self.access_token = args['access_token'] + self._name = args["name"] + self.device_id = args["device_id"] + self.access_token = args["access_token"] self.obtained_token = False - self._username = args['username'] - self._password = args['password'] + self._username = args["username"] + self._password = args["password"] self._state = None self.time_in_state = None self.signal = None @@ -91,19 +100,20 @@ class GaradgetCover(CoverDevice): try: if self._name is None: - doorconfig = self._get_variable('doorConfig') - if doorconfig['nme'] is not None: - self._name = doorconfig['nme'] + doorconfig = self._get_variable("doorConfig") + if doorconfig["nme"] is not None: + self._name = doorconfig["nme"] self.update() except requests.exceptions.ConnectionError as ex: - _LOGGER.error( - "Unable to connect to server: %(reason)s", dict(reason=ex)) + _LOGGER.error("Unable to connect to server: %(reason)s", dict(reason=ex)) self._state = STATE_OFFLINE self._available = False self._name = DEFAULT_NAME except KeyError: - _LOGGER.warning("Garadget device %(device)s seems to be offline", - dict(device=self.device_id)) + _LOGGER.warning( + "Garadget device %(device)s seems to be offline", + dict(device=self.device_id), + ) self._name = DEFAULT_NAME self._state = STATE_OFFLINE self._available = False @@ -158,30 +168,27 @@ class GaradgetCover(CoverDevice): @property def device_class(self): """Return the class of this device, from component DEVICE_CLASSES.""" - return 'garage' + return "garage" def get_token(self): """Get new token for usage during this session.""" args = { - 'grant_type': 'password', - 'username': self._username, - 'password': self._password + "grant_type": "password", + "username": self._username, + "password": self._password, } - url = '{}/oauth/token'.format(self.particle_url) - ret = requests.post( - url, auth=('particle', 'particle'), data=args, timeout=10) + url = "{}/oauth/token".format(self.particle_url) + ret = requests.post(url, auth=("particle", "particle"), data=args, timeout=10) try: - return ret.json()['access_token'] + return ret.json()["access_token"] except KeyError: _LOGGER.error("Unable to retrieve access token") def remove_token(self): """Remove authorization token from API.""" - url = '{}/v1/access_tokens/{}'.format( - self.particle_url, self.access_token) - ret = requests.delete( - url, auth=(self._username, self._password), timeout=10) + url = "{}/v1/access_tokens/{}".format(self.particle_url, self.access_token) + ret = requests.delete(url, auth=(self._username, self._password), timeout=10) return ret.text def _start_watcher(self, command): @@ -189,7 +196,8 @@ class GaradgetCover(CoverDevice): _LOGGER.debug("Starting Watcher for command: %s ", command) if self._unsub_listener_cover is None: self._unsub_listener_cover = track_utc_time_change( - self.hass, self._check_state) + self.hass, self._check_state + ) def _check_state(self, now): """Check the state of the service during an operation.""" @@ -197,42 +205,43 @@ class GaradgetCover(CoverDevice): def close_cover(self, **kwargs): """Close the cover.""" - if self._state not in ['close', 'closing']: - ret = self._put_command('setState', 'close') - self._start_watcher('close') - return ret.get('return_value') == 1 + if self._state not in ["close", "closing"]: + ret = self._put_command("setState", "close") + self._start_watcher("close") + return ret.get("return_value") == 1 def open_cover(self, **kwargs): """Open the cover.""" - if self._state not in ['open', 'opening']: - ret = self._put_command('setState', 'open') - self._start_watcher('open') - return ret.get('return_value') == 1 + if self._state not in ["open", "opening"]: + ret = self._put_command("setState", "open") + self._start_watcher("open") + return ret.get("return_value") == 1 def stop_cover(self, **kwargs): """Stop the door where it is.""" - if self._state not in ['stopped']: - ret = self._put_command('setState', 'stop') - self._start_watcher('stop') - return ret['return_value'] == 1 + if self._state not in ["stopped"]: + ret = self._put_command("setState", "stop") + self._start_watcher("stop") + return ret["return_value"] == 1 def update(self): """Get updated status from API.""" try: - status = self._get_variable('doorStatus') - _LOGGER.debug("Current Status: %s", status['status']) - self._state = STATES_MAP.get(status['status'], None) - self.time_in_state = status['time'] - self.signal = status['signal'] - self.sensor = status['sensor'] + status = self._get_variable("doorStatus") + _LOGGER.debug("Current Status: %s", status["status"]) + self._state = STATES_MAP.get(status["status"], None) + self.time_in_state = status["time"] + self.signal = status["signal"] + self.sensor = status["sensor"] self._available = True except requests.exceptions.ConnectionError as ex: - _LOGGER.error( - "Unable to connect to server: %(reason)s", dict(reason=ex)) + _LOGGER.error("Unable to connect to server: %(reason)s", dict(reason=ex)) self._state = STATE_OFFLINE except KeyError: - _LOGGER.warning("Garadget device %(device)s seems to be offline", - dict(device=self.device_id)) + _LOGGER.warning( + "Garadget device %(device)s seems to be offline", + dict(device=self.device_id), + ) self._state = STATE_OFFLINE if self._state not in [STATE_CLOSING, STATE_OPENING]: @@ -242,21 +251,21 @@ class GaradgetCover(CoverDevice): def _get_variable(self, var): """Get latest status.""" - url = '{}/v1/devices/{}/{}?access_token={}'.format( - self.particle_url, self.device_id, var, self.access_token) + url = "{}/v1/devices/{}/{}?access_token={}".format( + self.particle_url, self.device_id, var, self.access_token + ) ret = requests.get(url, timeout=10) result = {} - for pairs in ret.json()['result'].split('|'): - key = pairs.split('=') + for pairs in ret.json()["result"].split("|"): + key = pairs.split("=") result[key[0]] = key[1] return result def _put_command(self, func, arg=None): """Send commands to API.""" - params = {'access_token': self.access_token} + params = {"access_token": self.access_token} if arg: - params['command'] = arg - url = '{}/v1/devices/{}/{}'.format( - self.particle_url, self.device_id, func) + params["command"] = arg + url = "{}/v1/devices/{}/{}".format(self.particle_url, self.device_id, func) ret = requests.post(url, data=params, timeout=10) return ret.json() diff --git a/homeassistant/components/gc100/__init__.py b/homeassistant/components/gc100/__init__.py index b875d045cc0..19303fdc6d2 100644 --- a/homeassistant/components/gc100/__init__.py +++ b/homeassistant/components/gc100/__init__.py @@ -3,25 +3,29 @@ import logging import voluptuous as vol -from homeassistant.const import ( - EVENT_HOMEASSISTANT_STOP, CONF_HOST, CONF_PORT) +from homeassistant.const import EVENT_HOMEASSISTANT_STOP, CONF_HOST, CONF_PORT import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -CONF_PORTS = 'ports' +CONF_PORTS = "ports" DEFAULT_PORT = 4998 -DOMAIN = 'gc100' +DOMAIN = "gc100" -DATA_GC100 = 'gc100' +DATA_GC100 = "gc100" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) # pylint: disable=no-member diff --git a/homeassistant/components/gc100/binary_sensor.py b/homeassistant/components/gc100/binary_sensor.py index 4ba68a17799..a2f8ba4a0a2 100644 --- a/homeassistant/components/gc100/binary_sensor.py +++ b/homeassistant/components/gc100/binary_sensor.py @@ -1,20 +1,17 @@ """Support for binary sensor using GC100.""" import voluptuous as vol -from homeassistant.components.binary_sensor import ( - PLATFORM_SCHEMA, BinarySensorDevice) +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice from homeassistant.const import DEVICE_DEFAULT_NAME import homeassistant.helpers.config_validation as cv from . import CONF_PORTS, DATA_GC100 -_SENSORS_SCHEMA = vol.Schema({ - cv.string: cv.string, -}) +_SENSORS_SCHEMA = vol.Schema({cv.string: cv.string}) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_PORTS): vol.All(cv.ensure_list, [_SENSORS_SCHEMA]) -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_PORTS): vol.All(cv.ensure_list, [_SENSORS_SCHEMA])} +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -23,8 +20,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ports = config.get(CONF_PORTS) for port in ports: for port_addr, port_name in port.items(): - binary_sensors.append(GC100BinarySensor( - port_name, port_addr, hass.data[DATA_GC100])) + binary_sensors.append( + GC100BinarySensor(port_name, port_addr, hass.data[DATA_GC100]) + ) add_entities(binary_sensors, True) diff --git a/homeassistant/components/gc100/switch.py b/homeassistant/components/gc100/switch.py index eea98a4dc23..6be42e35deb 100644 --- a/homeassistant/components/gc100/switch.py +++ b/homeassistant/components/gc100/switch.py @@ -8,13 +8,11 @@ from homeassistant.helpers.entity import ToggleEntity from . import CONF_PORTS, DATA_GC100 -_SWITCH_SCHEMA = vol.Schema({ - cv.string: cv.string, -}) +_SWITCH_SCHEMA = vol.Schema({cv.string: cv.string}) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_PORTS): vol.All(cv.ensure_list, [_SWITCH_SCHEMA]) -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_PORTS): vol.All(cv.ensure_list, [_SWITCH_SCHEMA])} +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -23,8 +21,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ports = config.get(CONF_PORTS) for port in ports: for port_addr, port_name in port.items(): - switches.append(GC100Switch( - port_name, port_addr, hass.data[DATA_GC100])) + switches.append(GC100Switch(port_name, port_addr, hass.data[DATA_GC100])) add_entities(switches, True) diff --git a/homeassistant/components/gearbest/sensor.py b/homeassistant/components/gearbest/sensor.py index ee0ee6d4e3b..bad9b335e73 100644 --- a/homeassistant/components/gearbest/sensor.py +++ b/homeassistant/components/gearbest/sensor.py @@ -9,37 +9,40 @@ import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import track_time_interval -from homeassistant.const import (CONF_NAME, CONF_ID, CONF_URL, CONF_CURRENCY) +from homeassistant.const import CONF_NAME, CONF_ID, CONF_URL, CONF_CURRENCY _LOGGER = logging.getLogger(__name__) -CONF_ITEMS = 'items' +CONF_ITEMS = "items" -ICON = 'mdi:coin' -MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=2*60*60) # 2h -MIN_TIME_BETWEEN_CURRENCY_UPDATES = timedelta(seconds=12*60*60) # 12h +ICON = "mdi:coin" +MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=2 * 60 * 60) # 2h +MIN_TIME_BETWEEN_CURRENCY_UPDATES = timedelta(seconds=12 * 60 * 60) # 12h _ITEM_SCHEMA = vol.All( - vol.Schema({ - vol.Exclusive(CONF_URL, 'XOR'): cv.string, - vol.Exclusive(CONF_ID, 'XOR'): cv.string, - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_CURRENCY): cv.string - }), cv.has_at_least_one_key(CONF_URL, CONF_ID) + vol.Schema( + { + vol.Exclusive(CONF_URL, "XOR"): cv.string, + vol.Exclusive(CONF_ID, "XOR"): cv.string, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_CURRENCY): cv.string, + } + ), + cv.has_at_least_one_key(CONF_URL, CONF_ID), ) _ITEMS_SCHEMA = vol.Schema([_ITEM_SCHEMA]) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_ITEMS): _ITEMS_SCHEMA, - vol.Required(CONF_CURRENCY): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_ITEMS): _ITEMS_SCHEMA, vol.Required(CONF_CURRENCY): cv.string} +) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Gearbest sensor.""" from gearbest_parser import CurrencyConverter + currency = config.get(CONF_CURRENCY) sensors = [] @@ -58,9 +61,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Update currency list.""" converter.update() - track_time_interval(hass, - currency_update, - MIN_TIME_BETWEEN_CURRENCY_UPDATES) + track_time_interval(hass, currency_update, MIN_TIME_BETWEEN_CURRENCY_UPDATES) add_entities(sensors, True) @@ -75,9 +76,9 @@ class GearbestSensor(Entity): self._name = item.get(CONF_NAME) self._parser = GearbestParser() self._parser.set_currency_converter(converter) - self._item = self._parser.load(item.get(CONF_ID), - item.get(CONF_URL), - item.get(CONF_CURRENCY, currency)) + self._item = self._parser.load( + item.get(CONF_ID), item.get(CONF_URL), item.get(CONF_CURRENCY, currency) + ) if self._item is None: raise ValueError("id and url could not be resolved") @@ -109,10 +110,12 @@ class GearbestSensor(Entity): @property def device_state_attributes(self): """Return the state attributes.""" - attrs = {'name': self._item.name, - 'description': self._item.description, - 'currency': self._item.currency, - 'url': self._item.url} + attrs = { + "name": self._item.name, + "description": self._item.description, + "currency": self._item.currency, + "url": self._item.url, + } return attrs @Throttle(MIN_TIME_BETWEEN_UPDATES) diff --git a/homeassistant/components/geizhals/sensor.py b/homeassistant/components/geizhals/sensor.py index 03c263f54ab..28fe10ec5f5 100644 --- a/homeassistant/components/geizhals/sensor.py +++ b/homeassistant/components/geizhals/sensor.py @@ -12,25 +12,22 @@ from homeassistant.const import CONF_NAME _LOGGER = logging.getLogger(__name__) -CONF_DESCRIPTION = 'description' -CONF_PRODUCT_ID = 'product_id' -CONF_LOCALE = 'locale' +CONF_DESCRIPTION = "description" +CONF_PRODUCT_ID = "product_id" +CONF_LOCALE = "locale" -ICON = 'mdi:coin' +ICON = "mdi:coin" MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=120) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_NAME): cv.string, - vol.Required(CONF_PRODUCT_ID): cv.positive_int, - vol.Optional(CONF_DESCRIPTION, default='Price'): cv.string, - vol.Optional(CONF_LOCALE, default='DE'): vol.In( - ['AT', - 'EU', - 'DE', - 'UK', - 'PL']), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_PRODUCT_ID): cv.positive_int, + vol.Optional(CONF_DESCRIPTION, default="Price"): cv.string, + vol.Optional(CONF_LOCALE, default="DE"): vol.In(["AT", "EU", "DE", "UK", "PL"]), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -40,8 +37,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): product_id = config.get(CONF_PRODUCT_ID) domain = config.get(CONF_LOCALE) - add_entities([Geizwatch(name, description, product_id, domain)], - True) + add_entities([Geizwatch(name, description, product_id, domain)], True) class Geizwatch(Entity): @@ -82,15 +78,17 @@ class Geizwatch(Entity): def device_state_attributes(self): """Return the state attributes.""" while len(self._device.prices) < 4: - self._device.prices.append('None') - attrs = {'device_name': self._device.name, - 'description': self.description, - 'unit_of_measurement': self._device.price_currency, - 'product_id': self.product_id, - 'price1': self._device.prices[0], - 'price2': self._device.prices[1], - 'price3': self._device.prices[2], - 'price4': self._device.prices[3]} + self._device.prices.append("None") + attrs = { + "device_name": self._device.name, + "description": self.description, + "unit_of_measurement": self._device.price_currency, + "product_id": self.product_id, + "price1": self._device.prices[0], + "price2": self._device.prices[1], + "price3": self._device.prices[2], + "price4": self._device.prices[3], + } return attrs @Throttle(MIN_TIME_BETWEEN_UPDATES) diff --git a/homeassistant/components/generic/camera.py b/homeassistant/components/generic/camera.py index 8b98d84c06d..307142ed989 100644 --- a/homeassistant/components/generic/camera.py +++ b/homeassistant/components/generic/camera.py @@ -9,42 +9,54 @@ from requests.auth import HTTPDigestAuth import voluptuous as vol from homeassistant.const import ( - CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_AUTHENTICATION, - HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION, CONF_VERIFY_SSL) + CONF_NAME, + CONF_USERNAME, + CONF_PASSWORD, + CONF_AUTHENTICATION, + HTTP_BASIC_AUTHENTICATION, + HTTP_DIGEST_AUTHENTICATION, + CONF_VERIFY_SSL, +) from homeassistant.exceptions import TemplateError from homeassistant.components.camera import ( - PLATFORM_SCHEMA, DEFAULT_CONTENT_TYPE, SUPPORT_STREAM, Camera) + PLATFORM_SCHEMA, + DEFAULT_CONTENT_TYPE, + SUPPORT_STREAM, + Camera, +) from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers import config_validation as cv from homeassistant.util.async_ import run_coroutine_threadsafe _LOGGER = logging.getLogger(__name__) -CONF_CONTENT_TYPE = 'content_type' -CONF_LIMIT_REFETCH_TO_URL_CHANGE = 'limit_refetch_to_url_change' -CONF_STILL_IMAGE_URL = 'still_image_url' -CONF_STREAM_SOURCE = 'stream_source' -CONF_FRAMERATE = 'framerate' +CONF_CONTENT_TYPE = "content_type" +CONF_LIMIT_REFETCH_TO_URL_CHANGE = "limit_refetch_to_url_change" +CONF_STILL_IMAGE_URL = "still_image_url" +CONF_STREAM_SOURCE = "stream_source" +CONF_FRAMERATE = "framerate" -DEFAULT_NAME = 'Generic Camera' +DEFAULT_NAME = "Generic Camera" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_STILL_IMAGE_URL): cv.template, - vol.Optional(CONF_STREAM_SOURCE, default=None): vol.Any(None, cv.string), - vol.Optional(CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION): - vol.In([HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION]), - vol.Optional(CONF_LIMIT_REFETCH_TO_URL_CHANGE, default=False): cv.boolean, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PASSWORD): cv.string, - vol.Optional(CONF_USERNAME): cv.string, - vol.Optional(CONF_CONTENT_TYPE, default=DEFAULT_CONTENT_TYPE): cv.string, - vol.Optional(CONF_FRAMERATE, default=2): cv.positive_int, - vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_STILL_IMAGE_URL): cv.template, + vol.Optional(CONF_STREAM_SOURCE, default=None): vol.Any(None, cv.string), + vol.Optional(CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION): vol.In( + [HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION] + ), + vol.Optional(CONF_LIMIT_REFETCH_TO_URL_CHANGE, default=False): cv.boolean, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PASSWORD): cv.string, + vol.Optional(CONF_USERNAME): cv.string, + vol.Optional(CONF_CONTENT_TYPE, default=DEFAULT_CONTENT_TYPE): cv.string, + vol.Optional(CONF_FRAMERATE, default=2): cv.positive_int, + vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean, + } +) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up a generic IP Camera.""" async_add_entities([GenericCamera(hass, config)]) @@ -94,15 +106,15 @@ class GenericCamera(Camera): def camera_image(self): """Return bytes of camera image.""" return run_coroutine_threadsafe( - self.async_camera_image(), self.hass.loop).result() + self.async_camera_image(), self.hass.loop + ).result() async def async_camera_image(self): """Return a still image response from the camera.""" try: url = self._still_image_url.async_render() except TemplateError as err: - _LOGGER.error( - "Error parsing template %s: %s", self._still_image_url, err) + _LOGGER.error("Error parsing template %s: %s", self._still_image_url, err) return self._last_image if url == self._last_url and self._limit_refetch: @@ -110,26 +122,27 @@ class GenericCamera(Camera): # aiohttp don't support DigestAuth yet if self._authentication == HTTP_DIGEST_AUTHENTICATION: + def fetch(): """Read image from a URL.""" try: - response = requests.get(url, timeout=10, auth=self._auth, - verify=self.verify_ssl) + response = requests.get( + url, timeout=10, auth=self._auth, verify=self.verify_ssl + ) return response.content except requests.exceptions.RequestException as error: _LOGGER.error("Error getting camera image: %s", error) return self._last_image - self._last_image = await self.hass.async_add_job( - fetch) + self._last_image = await self.hass.async_add_job(fetch) # async else: try: websession = async_get_clientsession( - self.hass, verify_ssl=self.verify_ssl) + self.hass, verify_ssl=self.verify_ssl + ) with async_timeout.timeout(10): - response = await websession.get( - url, auth=self._auth) + response = await websession.get(url, auth=self._auth) self._last_image = await response.read() except asyncio.TimeoutError: _LOGGER.error("Timeout getting image from: %s", self._name) diff --git a/homeassistant/components/generic_thermostat/climate.py b/homeassistant/components/generic_thermostat/climate.py index 409c44d1bca..13e23b962c9 100644 --- a/homeassistant/components/generic_thermostat/climate.py +++ b/homeassistant/components/generic_thermostat/climate.py @@ -6,65 +6,86 @@ import voluptuous as vol from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice from homeassistant.components.climate.const import ( - ATTR_PRESET_MODE, CURRENT_HVAC_COOL, CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, - CURRENT_HVAC_OFF, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF, - PRESET_AWAY, SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE, PRESET_NONE) + ATTR_PRESET_MODE, + CURRENT_HVAC_COOL, + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, + CURRENT_HVAC_OFF, + HVAC_MODE_COOL, + HVAC_MODE_HEAT, + HVAC_MODE_OFF, + PRESET_AWAY, + SUPPORT_PRESET_MODE, + SUPPORT_TARGET_TEMPERATURE, + PRESET_NONE, +) from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_TEMPERATURE, CONF_NAME, EVENT_HOMEASSISTANT_START, - PRECISION_HALVES, PRECISION_TENTHS, PRECISION_WHOLE, SERVICE_TURN_OFF, - SERVICE_TURN_ON, STATE_ON, STATE_UNKNOWN) + ATTR_ENTITY_ID, + ATTR_TEMPERATURE, + CONF_NAME, + EVENT_HOMEASSISTANT_START, + PRECISION_HALVES, + PRECISION_TENTHS, + PRECISION_WHOLE, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_ON, + STATE_UNKNOWN, +) from homeassistant.core import DOMAIN as HA_DOMAIN, callback from homeassistant.helpers import condition import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import ( - async_track_state_change, async_track_time_interval) + async_track_state_change, + async_track_time_interval, +) from homeassistant.helpers.restore_state import RestoreEntity _LOGGER = logging.getLogger(__name__) DEFAULT_TOLERANCE = 0.3 -DEFAULT_NAME = 'Generic Thermostat' +DEFAULT_NAME = "Generic Thermostat" -CONF_HEATER = 'heater' -CONF_SENSOR = 'target_sensor' -CONF_MIN_TEMP = 'min_temp' -CONF_MAX_TEMP = 'max_temp' -CONF_TARGET_TEMP = 'target_temp' -CONF_AC_MODE = 'ac_mode' -CONF_MIN_DUR = 'min_cycle_duration' -CONF_COLD_TOLERANCE = 'cold_tolerance' -CONF_HOT_TOLERANCE = 'hot_tolerance' -CONF_KEEP_ALIVE = 'keep_alive' -CONF_INITIAL_HVAC_MODE = 'initial_hvac_mode' -CONF_AWAY_TEMP = 'away_temp' -CONF_PRECISION = 'precision' +CONF_HEATER = "heater" +CONF_SENSOR = "target_sensor" +CONF_MIN_TEMP = "min_temp" +CONF_MAX_TEMP = "max_temp" +CONF_TARGET_TEMP = "target_temp" +CONF_AC_MODE = "ac_mode" +CONF_MIN_DUR = "min_cycle_duration" +CONF_COLD_TOLERANCE = "cold_tolerance" +CONF_HOT_TOLERANCE = "hot_tolerance" +CONF_KEEP_ALIVE = "keep_alive" +CONF_INITIAL_HVAC_MODE = "initial_hvac_mode" +CONF_AWAY_TEMP = "away_temp" +CONF_PRECISION = "precision" SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HEATER): cv.entity_id, - vol.Required(CONF_SENSOR): cv.entity_id, - vol.Optional(CONF_AC_MODE): cv.boolean, - vol.Optional(CONF_MAX_TEMP): vol.Coerce(float), - vol.Optional(CONF_MIN_DUR): vol.All(cv.time_period, cv.positive_timedelta), - vol.Optional(CONF_MIN_TEMP): vol.Coerce(float), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_COLD_TOLERANCE, default=DEFAULT_TOLERANCE): vol.Coerce( - float), - vol.Optional(CONF_HOT_TOLERANCE, default=DEFAULT_TOLERANCE): vol.Coerce( - float), - vol.Optional(CONF_TARGET_TEMP): vol.Coerce(float), - vol.Optional(CONF_KEEP_ALIVE): vol.All( - cv.time_period, cv.positive_timedelta), - vol.Optional(CONF_INITIAL_HVAC_MODE): - vol.In([HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF]), - vol.Optional(CONF_AWAY_TEMP): vol.Coerce(float), - vol.Optional(CONF_PRECISION): vol.In( - [PRECISION_TENTHS, PRECISION_HALVES, PRECISION_WHOLE]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HEATER): cv.entity_id, + vol.Required(CONF_SENSOR): cv.entity_id, + vol.Optional(CONF_AC_MODE): cv.boolean, + vol.Optional(CONF_MAX_TEMP): vol.Coerce(float), + vol.Optional(CONF_MIN_DUR): vol.All(cv.time_period, cv.positive_timedelta), + vol.Optional(CONF_MIN_TEMP): vol.Coerce(float), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_COLD_TOLERANCE, default=DEFAULT_TOLERANCE): vol.Coerce(float), + vol.Optional(CONF_HOT_TOLERANCE, default=DEFAULT_TOLERANCE): vol.Coerce(float), + vol.Optional(CONF_TARGET_TEMP): vol.Coerce(float), + vol.Optional(CONF_KEEP_ALIVE): vol.All(cv.time_period, cv.positive_timedelta), + vol.Optional(CONF_INITIAL_HVAC_MODE): vol.In( + [HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF] + ), + vol.Optional(CONF_AWAY_TEMP): vol.Coerce(float), + vol.Optional(CONF_PRECISION): vol.In( + [PRECISION_TENTHS, PRECISION_HALVES, PRECISION_WHOLE] + ), + } +) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the generic thermostat platform.""" name = config.get(CONF_NAME) heater_entity_id = config.get(CONF_HEATER) @@ -82,20 +103,50 @@ async def async_setup_platform(hass, config, async_add_entities, precision = config.get(CONF_PRECISION) unit = hass.config.units.temperature_unit - async_add_entities([GenericThermostat( - name, heater_entity_id, sensor_entity_id, min_temp, max_temp, - target_temp, ac_mode, min_cycle_duration, cold_tolerance, - hot_tolerance, keep_alive, initial_hvac_mode, away_temp, - precision, unit)]) + async_add_entities( + [ + GenericThermostat( + name, + heater_entity_id, + sensor_entity_id, + min_temp, + max_temp, + target_temp, + ac_mode, + min_cycle_duration, + cold_tolerance, + hot_tolerance, + keep_alive, + initial_hvac_mode, + away_temp, + precision, + unit, + ) + ] + ) class GenericThermostat(ClimateDevice, RestoreEntity): """Representation of a Generic Thermostat device.""" - def __init__(self, name, heater_entity_id, sensor_entity_id, - min_temp, max_temp, target_temp, ac_mode, min_cycle_duration, - cold_tolerance, hot_tolerance, keep_alive, - initial_hvac_mode, away_temp, precision, unit): + def __init__( + self, + name, + heater_entity_id, + sensor_entity_id, + min_temp, + max_temp, + target_temp, + ac_mode, + min_cycle_duration, + cold_tolerance, + hot_tolerance, + keep_alive, + initial_hvac_mode, + away_temp, + precision, + unit, + ): """Initialize the thermostat.""" self._name = name self.heater_entity_id = heater_entity_id @@ -131,13 +182,16 @@ class GenericThermostat(ClimateDevice, RestoreEntity): # Add listener async_track_state_change( - self.hass, self.sensor_entity_id, self._async_sensor_changed) + self.hass, self.sensor_entity_id, self._async_sensor_changed + ) async_track_state_change( - self.hass, self.heater_entity_id, self._async_switch_changed) + self.hass, self.heater_entity_id, self._async_switch_changed + ) if self._keep_alive: async_track_time_interval( - self.hass, self._async_control_heating, self._keep_alive) + self.hass, self._async_control_heating, self._keep_alive + ) @callback def _async_startup(event): @@ -146,8 +200,7 @@ class GenericThermostat(ClimateDevice, RestoreEntity): if sensor_state and sensor_state.state != STATE_UNKNOWN: self._async_update_temp(sensor_state) - self.hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_START, _async_startup) + self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, _async_startup) # Check If we have an old state old_state = await self.async_get_last_state() @@ -160,11 +213,12 @@ class GenericThermostat(ClimateDevice, RestoreEntity): self._target_temp = self.max_temp else: self._target_temp = self.min_temp - _LOGGER.warning("Undefined target temperature," - "falling back to %s", self._target_temp) + _LOGGER.warning( + "Undefined target temperature," "falling back to %s", + self._target_temp, + ) else: - self._target_temp = float( - old_state.attributes[ATTR_TEMPERATURE]) + self._target_temp = float(old_state.attributes[ATTR_TEMPERATURE]) if old_state.attributes.get(ATTR_PRESET_MODE) == PRESET_AWAY: self._is_away = True if not self._hvac_mode and old_state.state: @@ -177,8 +231,9 @@ class GenericThermostat(ClimateDevice, RestoreEntity): self._target_temp = self.max_temp else: self._target_temp = self.min_temp - _LOGGER.warning("No previously saved temperature, setting to %s", - self._target_temp) + _LOGGER.warning( + "No previously saved temperature, setting to %s", self._target_temp + ) # Set default state to off if not self._hvac_mode: @@ -326,12 +381,14 @@ class GenericThermostat(ClimateDevice, RestoreEntity): async def _async_control_heating(self, time=None, force=False): """Check if we need to turn heating on or off.""" async with self._temp_lock: - if not self._active and None not in (self._cur_temp, - self._target_temp): + if not self._active and None not in (self._cur_temp, self._target_temp): self._active = True - _LOGGER.info("Obtained current and target temperature. " - "Generic thermostat active. %s, %s", - self._cur_temp, self._target_temp) + _LOGGER.info( + "Obtained current and target temperature. " + "Generic thermostat active. %s, %s", + self._cur_temp, + self._target_temp, + ) if not self._active or self._hvac_mode == HVAC_MODE_OFF: return @@ -347,27 +404,25 @@ class GenericThermostat(ClimateDevice, RestoreEntity): else: current_state = HVAC_MODE_OFF long_enough = condition.state( - self.hass, self.heater_entity_id, current_state, - self.min_cycle_duration) + self.hass, + self.heater_entity_id, + current_state, + self.min_cycle_duration, + ) if not long_enough: return - too_cold = \ - self._target_temp - self._cur_temp >= self._cold_tolerance - too_hot = \ - self._cur_temp - self._target_temp >= self._hot_tolerance + too_cold = self._target_temp - self._cur_temp >= self._cold_tolerance + too_hot = self._cur_temp - self._target_temp >= self._hot_tolerance if self._is_device_active: - if (self.ac_mode and too_cold) or \ - (not self.ac_mode and too_hot): - _LOGGER.info("Turning off heater %s", - self.heater_entity_id) + if (self.ac_mode and too_cold) or (not self.ac_mode and too_hot): + _LOGGER.info("Turning off heater %s", self.heater_entity_id) await self._async_heater_turn_off() elif time is not None: # The time argument is passed only in keep-alive case await self._async_heater_turn_on() else: - if (self.ac_mode and too_hot) or \ - (not self.ac_mode and too_cold): + if (self.ac_mode and too_hot) or (not self.ac_mode and too_cold): _LOGGER.info("Turning on heater %s", self.heater_entity_id) await self._async_heater_turn_on() elif time is not None: diff --git a/homeassistant/components/geniushub/__init__.py b/homeassistant/components/geniushub/__init__.py index 7eb24fa8f95..8075c835f37 100644 --- a/homeassistant/components/geniushub/__init__.py +++ b/homeassistant/components/geniushub/__init__.py @@ -6,8 +6,7 @@ import voluptuous as vol from geniushubclient import GeniusHubClient -from homeassistant.const import ( - CONF_HOST, CONF_PASSWORD, CONF_TOKEN, CONF_USERNAME) +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_TOKEN, CONF_USERNAME from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.discovery import async_load_platform @@ -16,57 +15,58 @@ from homeassistant.helpers.event import async_track_time_interval _LOGGER = logging.getLogger(__name__) -DOMAIN = 'geniushub' +DOMAIN = "geniushub" SCAN_INTERVAL = timedelta(seconds=60) -_V1_API_SCHEMA = vol.Schema({ - vol.Required(CONF_TOKEN): cv.string, -}) -_V3_API_SCHEMA = vol.Schema({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, -}) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Any( - _V3_API_SCHEMA, - _V1_API_SCHEMA, - ) -}, extra=vol.ALLOW_EXTRA) +_V1_API_SCHEMA = vol.Schema({vol.Required(CONF_TOKEN): cv.string}) +_V3_API_SCHEMA = vol.Schema( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + } +) +CONFIG_SCHEMA = vol.Schema( + {DOMAIN: vol.Any(_V3_API_SCHEMA, _V1_API_SCHEMA)}, extra=vol.ALLOW_EXTRA +) async def async_setup(hass, hass_config): """Create a Genius Hub system.""" kwargs = dict(hass_config[DOMAIN]) if CONF_HOST in kwargs: - args = (kwargs.pop(CONF_HOST), ) + args = (kwargs.pop(CONF_HOST),) else: - args = (kwargs.pop(CONF_TOKEN), ) + args = (kwargs.pop(CONF_TOKEN),) hass.data[DOMAIN] = {} - data = hass.data[DOMAIN]['data'] = GeniusData(hass, args, kwargs) + data = hass.data[DOMAIN]["data"] = GeniusData(hass, args, kwargs) try: await data._client.hub.update() # pylint: disable=protected-access except AssertionError: # assert response.status == HTTP_OK - _LOGGER.warning( - "Setup failed, check your configuration.", - exc_info=True) + _LOGGER.warning("Setup failed, check your configuration.", exc_info=True) return False - _LOGGER.debug("zones_raw = %s", data._client.hub._zones_raw) # noqa; pylint: disable=protected-access - _LOGGER.debug("devices_raw = %s", data._client.hub._devices_raw) # noqa; pylint: disable=protected-access + _LOGGER.debug( + "zones_raw = %s", data._client.hub._zones_raw + ) # noqa; pylint: disable=protected-access + _LOGGER.debug( + "devices_raw = %s", data._client.hub._devices_raw + ) # noqa; pylint: disable=protected-access async_track_time_interval(hass, data.async_update, SCAN_INTERVAL) - for platform in ['climate', 'water_heater']: - hass.async_create_task(async_load_platform( - hass, platform, DOMAIN, {}, hass_config)) + for platform in ["climate", "water_heater"]: + hass.async_create_task( + async_load_platform(hass, platform, DOMAIN, {}, hass_config) + ) if data._client.api_version == 3: # pylint: disable=protected-access - for platform in ['sensor', 'binary_sensor']: - hass.async_create_task(async_load_platform( - hass, platform, DOMAIN, {}, hass_config)) + for platform in ["sensor", "binary_sensor"]: + hass.async_create_task( + async_load_platform(hass, platform, DOMAIN, {}, hass_config) + ) return True @@ -77,8 +77,9 @@ class GeniusData: def __init__(self, hass, args, kwargs): """Initialize the geniushub client.""" self._hass = hass - self._client = hass.data[DOMAIN]['client'] = GeniusHubClient( - *args, **kwargs, session=async_get_clientsession(hass)) + self._client = hass.data[DOMAIN]["client"] = GeniusHubClient( + *args, **kwargs, session=async_get_clientsession(hass) + ) async def async_update(self, now, **kwargs): """Update the geniushub client's data.""" @@ -88,7 +89,11 @@ class GeniusData: _LOGGER.warning("Update failed.", exc_info=True) return - _LOGGER.debug("zones_raw = %s", self._client.hub._zones_raw) # noqa; pylint: disable=protected-access - _LOGGER.debug("devices_raw = %s", self._client.hub._devices_raw) # noqa; pylint: disable=protected-access + _LOGGER.debug( + "zones_raw = %s", self._client.hub._zones_raw + ) # noqa; pylint: disable=protected-access + _LOGGER.debug( + "devices_raw = %s", self._client.hub._devices_raw + ) # noqa; pylint: disable=protected-access async_dispatcher_send(self._hass, DOMAIN) diff --git a/homeassistant/components/geniushub/binary_sensor.py b/homeassistant/components/geniushub/binary_sensor.py index 07f8bd5111f..7e34206531c 100644 --- a/homeassistant/components/geniushub/binary_sensor.py +++ b/homeassistant/components/geniushub/binary_sensor.py @@ -10,17 +10,17 @@ from . import DOMAIN _LOGGER = logging.getLogger(__name__) -GH_IS_SWITCH = ['Dual Channel Receiver', 'Electric Switch', 'Smart Plug'] +GH_IS_SWITCH = ["Dual Channel Receiver", "Electric Switch", "Smart Plug"] -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Genius Hub sensor entities.""" - client = hass.data[DOMAIN]['client'] + client = hass.data[DOMAIN]["client"] devices = [d for d in client.hub.device_objs if d.type is not None] - switches = [GeniusBinarySensor(client, d) - for d in devices if d.type[:21] in GH_IS_SWITCH] + switches = [ + GeniusBinarySensor(client, d) for d in devices if d.type[:21] in GH_IS_SWITCH + ] async_add_entities(switches) @@ -33,10 +33,10 @@ class GeniusBinarySensor(BinarySensorDevice): self._client = client self._device = device - if device.type[:21] == 'Dual Channel Receiver': - self._name = 'Dual Channel Receiver {}'.format(device.id) + if device.type[:21] == "Dual Channel Receiver": + self._name = "Dual Channel Receiver {}".format(device.id) else: - self._name = '{} {}'.format(device.type, device.id) + self._name = "{} {}".format(device.type, device.id) async def async_added_to_hass(self): """Set up a listener when this entity is added to HA.""" @@ -59,16 +59,18 @@ class GeniusBinarySensor(BinarySensorDevice): @property def is_on(self): """Return the status of the sensor.""" - return self._device.state['outputOnOff'] + return self._device.state["outputOnOff"] @property def device_state_attributes(self): """Return the device state attributes.""" attrs = {} - attrs['assigned_zone'] = self._device.assignedZones[0]['name'] + attrs["assigned_zone"] = self._device.assignedZones[0]["name"] - last_comms = self._device._raw_json['childValues']['lastComms']['val'] # noqa; pylint: disable=protected-access + last_comms = self._device._raw_json["childValues"]["lastComms"][ + "val" + ] # noqa; pylint: disable=protected-access if last_comms != 0: - attrs['last_comms'] = utc_from_timestamp(last_comms).isoformat() + attrs["last_comms"] = utc_from_timestamp(last_comms).isoformat() return {**attrs} diff --git a/homeassistant/components/geniushub/climate.py b/homeassistant/components/geniushub/climate.py index 18155f7e114..ae1d714dd2b 100644 --- a/homeassistant/components/geniushub/climate.py +++ b/homeassistant/components/geniushub/climate.py @@ -4,8 +4,13 @@ from typing import Any, Awaitable, Dict, Optional, List from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( - HVAC_MODE_OFF, HVAC_MODE_HEAT, PRESET_BOOST, PRESET_ACTIVITY, - SUPPORT_TARGET_TEMPERATURE, SUPPORT_PRESET_MODE) + HVAC_MODE_OFF, + HVAC_MODE_HEAT, + PRESET_BOOST, + PRESET_ACTIVITY, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_PRESET_MODE, +) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -14,34 +19,34 @@ from . import DOMAIN _LOGGER = logging.getLogger(__name__) -ATTR_DURATION = 'duration' +ATTR_DURATION = "duration" -GH_ZONES = ['radiator'] +GH_ZONES = ["radiator"] # temperature is repeated here, as it gives access to high-precision temps -GH_STATE_ATTRS = ['mode', 'temperature', 'type', 'occupied', 'override'] +GH_STATE_ATTRS = ["mode", "temperature", "type", "occupied", "override"] # GeniusHub Zones support: Off, Timer, Override/Boost, Footprint & Linked modes -HA_HVAC_TO_GH = { - HVAC_MODE_OFF: 'off', - HVAC_MODE_HEAT: 'timer' -} +HA_HVAC_TO_GH = {HVAC_MODE_OFF: "off", HVAC_MODE_HEAT: "timer"} GH_HVAC_TO_HA = {v: k for k, v in HA_HVAC_TO_GH.items()} -HA_PRESET_TO_GH = { - PRESET_ACTIVITY: 'footprint', - PRESET_BOOST: 'override' -} +HA_PRESET_TO_GH = {PRESET_ACTIVITY: "footprint", PRESET_BOOST: "override"} GH_PRESET_TO_HA = {v: k for k, v in HA_PRESET_TO_GH.items()} -async def async_setup_platform(hass, hass_config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, hass_config, async_add_entities, discovery_info=None +): """Set up the Genius Hub climate entities.""" - client = hass.data[DOMAIN]['client'] + client = hass.data[DOMAIN]["client"] - async_add_entities([GeniusClimateZone(client, z) - for z in client.hub.zone_objs if z.type in GH_ZONES]) + async_add_entities( + [ + GeniusClimateZone(client, z) + for z in client.hub.zone_objs + if z.type in GH_ZONES + ] + ) class GeniusClimateZone(ClimateDevice): @@ -52,7 +57,7 @@ class GeniusClimateZone(ClimateDevice): self._client = client self._zone = zone - if hasattr(self._zone, 'occupied'): # has a movement sensor + if hasattr(self._zone, "occupied"): # has a movement sensor self._preset_modes = list(HA_PRESET_TO_GH) else: self._preset_modes = [PRESET_BOOST] @@ -74,7 +79,7 @@ class GeniusClimateZone(ClimateDevice): def device_state_attributes(self) -> Dict[str, Any]: """Return the device state attributes.""" tmp = self._zone.__dict__.items() - return {'status': {k: v for k, v in tmp if k in GH_STATE_ATTRS}} + return {"status": {k: v for k, v in tmp if k in GH_STATE_ATTRS}} @property def should_poll(self) -> bool: @@ -138,8 +143,9 @@ class GeniusClimateZone(ClimateDevice): async def async_set_temperature(self, **kwargs) -> Awaitable[None]: """Set a new target temperature for this zone.""" - await self._zone.set_override(kwargs[ATTR_TEMPERATURE], - kwargs.get(ATTR_DURATION, 3600)) + await self._zone.set_override( + kwargs[ATTR_TEMPERATURE], kwargs.get(ATTR_DURATION, 3600) + ) async def async_set_hvac_mode(self, hvac_mode: str) -> Awaitable[None]: """Set a new hvac mode.""" @@ -147,4 +153,4 @@ class GeniusClimateZone(ClimateDevice): async def async_set_preset_mode(self, preset_mode: str) -> Awaitable[None]: """Set a new preset mode.""" - await self._zone.set_mode(HA_PRESET_TO_GH.get(preset_mode, 'timer')) + await self._zone.set_mode(HA_PRESET_TO_GH.get(preset_mode, "timer")) diff --git a/homeassistant/components/geniushub/sensor.py b/homeassistant/components/geniushub/sensor.py index a14eb4b7a8e..9b1063c6683 100644 --- a/homeassistant/components/geniushub/sensor.py +++ b/homeassistant/components/geniushub/sensor.py @@ -12,26 +12,26 @@ from . import DOMAIN _LOGGER = logging.getLogger(__name__) -GH_HAS_BATTERY = [ - 'Room Thermostat', 'Genius Valve', 'Room Sensor', 'Radiator Valve'] +GH_HAS_BATTERY = ["Room Thermostat", "Genius Valve", "Room Sensor", "Radiator Valve"] GH_LEVEL_MAPPING = { - 'error': 'Errors', - 'warning': 'Warnings', - 'information': 'Information' + "error": "Errors", + "warning": "Warnings", + "information": "Information", } -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Genius Hub sensor entities.""" - client = hass.data[DOMAIN]['client'] + client = hass.data[DOMAIN]["client"] - sensors = [GeniusDevice(client, d) - for d in client.hub.device_objs if d.type in GH_HAS_BATTERY] + sensors = [ + GeniusDevice(client, d) + for d in client.hub.device_objs + if d.type in GH_HAS_BATTERY + ] - issues = [GeniusIssue(client, i) - for i in list(GH_LEVEL_MAPPING)] + issues = [GeniusIssue(client, i) for i in list(GH_LEVEL_MAPPING)] async_add_entities(sensors + issues, update_before_add=True) @@ -44,7 +44,7 @@ class GeniusDevice(Entity): self._client = client self._device = device - self._name = '{} {}'.format(device.type, device.id) + self._name = "{} {}".format(device.type, device.id) async def async_added_to_hass(self): """Set up a listener when this entity is added to HA.""" @@ -62,26 +62,28 @@ class GeniusDevice(Entity): @property def icon(self): """Return the icon of the sensor.""" - values = self._device._raw_json['childValues'] # noqa; pylint: disable=protected-access + values = self._device._raw_json[ + "childValues" + ] # noqa; pylint: disable=protected-access - last_comms = utc_from_timestamp(values['lastComms']['val']) - if 'WakeUp_Interval' in values: - interval = timedelta(seconds=values['WakeUp_Interval']['val']) + last_comms = utc_from_timestamp(values["lastComms"]["val"]) + if "WakeUp_Interval" in values: + interval = timedelta(seconds=values["WakeUp_Interval"]["val"]) else: interval = timedelta(minutes=20) if last_comms < utcnow() - interval * 3: - return 'mdi:battery-unknown' + return "mdi:battery-unknown" - battery_level = self._device.state['batteryLevel'] + battery_level = self._device.state["batteryLevel"] if battery_level == 255: - return 'mdi:battery-unknown' + return "mdi:battery-unknown" if battery_level < 40: - return 'mdi:battery-alert' + return "mdi:battery-alert" - icon = 'mdi:battery' + icon = "mdi:battery" if battery_level <= 95: - icon += '-{}'.format(int(round(battery_level / 10 - .01)) * 10) + icon += "-{}".format(int(round(battery_level / 10 - 0.01)) * 10) return icon @@ -93,7 +95,7 @@ class GeniusDevice(Entity): @property def unit_of_measurement(self): """Return the unit of measurement of the sensor.""" - return '%' + return "%" @property def should_poll(self) -> bool: @@ -103,17 +105,19 @@ class GeniusDevice(Entity): @property def state(self): """Return the state of the sensor.""" - level = self._device.state.get('batteryLevel', 255) + level = self._device.state.get("batteryLevel", 255) return level if level != 255 else 0 @property def device_state_attributes(self): """Return the device state attributes.""" attrs = {} - attrs['assigned_zone'] = self._device.assignedZones[0]['name'] + attrs["assigned_zone"] = self._device.assignedZones[0]["name"] - last_comms = self._device._raw_json['childValues']['lastComms']['val'] # noqa; pylint: disable=protected-access - attrs['last_comms'] = utc_from_timestamp(last_comms).isoformat() + last_comms = self._device._raw_json["childValues"]["lastComms"][ + "val" + ] # noqa; pylint: disable=protected-access + attrs["last_comms"] = utc_from_timestamp(last_comms).isoformat() return {**attrs} @@ -154,9 +158,10 @@ class GeniusIssue(Entity): @property def device_state_attributes(self): """Return the device state attributes.""" - return {'{}_list'.format(self._level): self._issues} + return {"{}_list".format(self._level): self._issues} async def async_update(self): """Process the sensor's state data.""" - self._issues = [i['description'] - for i in self._hub.issues if i['level'] == self._level] + self._issues = [ + i["description"] for i in self._hub.issues if i["level"] == self._level + ] diff --git a/homeassistant/components/geniushub/water_heater.py b/homeassistant/components/geniushub/water_heater.py index 3b40bafa699..9e27ec5f190 100644 --- a/homeassistant/components/geniushub/water_heater.py +++ b/homeassistant/components/geniushub/water_heater.py @@ -3,56 +3,55 @@ import logging from homeassistant.components.water_heater import ( WaterHeaterDevice, - SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE) -from homeassistant.const import ( - ATTR_TEMPERATURE, STATE_OFF, TEMP_CELSIUS) + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_OPERATION_MODE, +) +from homeassistant.const import ATTR_TEMPERATURE, STATE_OFF, TEMP_CELSIUS from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from . import DOMAIN -STATE_AUTO = 'auto' -STATE_MANUAL = 'manual' +STATE_AUTO = "auto" +STATE_MANUAL = "manual" _LOGGER = logging.getLogger(__name__) -GH_HEATERS = ['hot water temperature'] +GH_HEATERS = ["hot water temperature"] -GH_SUPPORT_FLAGS = \ - SUPPORT_TARGET_TEMPERATURE | \ - SUPPORT_OPERATION_MODE +GH_SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE # HA does not have SUPPORT_ON_OFF for water_heater GH_MAX_TEMP = 80.0 GH_MIN_TEMP = 30.0 # Genius Hub HW supports only Off, Override/Boost & Timer modes -HA_OPMODE_TO_GH = { - STATE_OFF: 'off', - STATE_AUTO: 'timer', - STATE_MANUAL: 'override', -} +HA_OPMODE_TO_GH = {STATE_OFF: "off", STATE_AUTO: "timer", STATE_MANUAL: "override"} GH_STATE_TO_HA = { - 'off': STATE_OFF, - 'timer': STATE_AUTO, - 'footprint': None, - 'away': None, - 'override': STATE_MANUAL, - 'early': None, - 'test': None, - 'linked': None, - 'other': None, + "off": STATE_OFF, + "timer": STATE_AUTO, + "footprint": None, + "away": None, + "override": STATE_MANUAL, + "early": None, + "test": None, + "linked": None, + "other": None, } -GH_STATE_ATTRS = ['type', 'override'] +GH_STATE_ATTRS = ["type", "override"] -async def async_setup_platform(hass, hass_config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, hass_config, async_add_entities, discovery_info=None +): """Set up the Genius Hub water_heater entities.""" - client = hass.data[DOMAIN]['client'] + client = hass.data[DOMAIN]["client"] - entities = [GeniusWaterHeater(client, z) - for z in client.hub.zone_objs if z.type in GH_HEATERS] + entities = [ + GeniusWaterHeater(client, z) + for z in client.hub.zone_objs + if z.type in GH_HEATERS + ] async_add_entities(entities) @@ -84,7 +83,7 @@ class GeniusWaterHeater(WaterHeaterDevice): def device_state_attributes(self): """Return the device state attributes.""" tmp = self._boiler.__dict__.items() - return {'status': {k: v for k, v in tmp if k in GH_STATE_ATTRS}} + return {"status": {k: v for k, v in tmp if k in GH_STATE_ATTRS}} @property def should_poll(self) -> bool: diff --git a/homeassistant/components/geo_json_events/geo_location.py b/homeassistant/components/geo_json_events/geo_location.py index f7d79ae7145..2bf309e2450 100644 --- a/homeassistant/components/geo_json_events/geo_location.py +++ b/homeassistant/components/geo_json_events/geo_location.py @@ -5,49 +5,57 @@ from typing import Optional import voluptuous as vol -from homeassistant.components.geo_location import ( - PLATFORM_SCHEMA, GeolocationEvent) +from homeassistant.components.geo_location import PLATFORM_SCHEMA, GeolocationEvent from homeassistant.const import ( - CONF_LATITUDE, CONF_LONGITUDE, CONF_RADIUS, CONF_SCAN_INTERVAL, CONF_URL, - EVENT_HOMEASSISTANT_START) + CONF_LATITUDE, + CONF_LONGITUDE, + CONF_RADIUS, + CONF_SCAN_INTERVAL, + CONF_URL, + EVENT_HOMEASSISTANT_START, +) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.dispatcher import ( - async_dispatcher_connect, dispatcher_send) +from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send from homeassistant.helpers.event import track_time_interval _LOGGER = logging.getLogger(__name__) -ATTR_EXTERNAL_ID = 'external_id' +ATTR_EXTERNAL_ID = "external_id" DEFAULT_RADIUS_IN_KM = 20.0 -DEFAULT_UNIT_OF_MEASUREMENT = 'km' +DEFAULT_UNIT_OF_MEASUREMENT = "km" SCAN_INTERVAL = timedelta(minutes=5) -SIGNAL_DELETE_ENTITY = 'geo_json_events_delete_{}' -SIGNAL_UPDATE_ENTITY = 'geo_json_events_update_{}' +SIGNAL_DELETE_ENTITY = "geo_json_events_delete_{}" +SIGNAL_UPDATE_ENTITY = "geo_json_events_update_{}" -SOURCE = 'geo_json_events' +SOURCE = "geo_json_events" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_URL): cv.string, - vol.Optional(CONF_LATITUDE): cv.latitude, - vol.Optional(CONF_LONGITUDE): cv.longitude, - vol.Optional(CONF_RADIUS, default=DEFAULT_RADIUS_IN_KM): vol.Coerce(float), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_URL): cv.string, + vol.Optional(CONF_LATITUDE): cv.latitude, + vol.Optional(CONF_LONGITUDE): cv.longitude, + vol.Optional(CONF_RADIUS, default=DEFAULT_RADIUS_IN_KM): vol.Coerce(float), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the GeoJSON Events platform.""" url = config[CONF_URL] scan_interval = config.get(CONF_SCAN_INTERVAL, SCAN_INTERVAL) - coordinates = (config.get(CONF_LATITUDE, hass.config.latitude), - config.get(CONF_LONGITUDE, hass.config.longitude)) + coordinates = ( + config.get(CONF_LATITUDE, hass.config.latitude), + config.get(CONF_LONGITUDE, hass.config.longitude), + ) radius_in_km = config[CONF_RADIUS] # Initialize the entity manager. feed = GeoJsonFeedEntityManager( - hass, add_entities, scan_interval, coordinates, url, radius_in_km) + hass, add_entities, scan_interval, coordinates, url, radius_in_km + ) def start_feed_manager(event): """Start feed manager.""" @@ -59,15 +67,21 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class GeoJsonFeedEntityManager: """Feed Entity Manager for GeoJSON feeds.""" - def __init__(self, hass, add_entities, scan_interval, coordinates, url, - radius_in_km): + def __init__( + self, hass, add_entities, scan_interval, coordinates, url, radius_in_km + ): """Initialize the GeoJSON Feed Manager.""" from geojson_client.generic_feed import GenericFeedManager self._hass = hass self._feed_manager = GenericFeedManager( - self._generate_entity, self._update_entity, self._remove_entity, - coordinates, url, filter_radius=radius_in_km) + self._generate_entity, + self._update_entity, + self._remove_entity, + coordinates, + url, + filter_radius=radius_in_km, + ) self._add_entities = add_entities self._scan_interval = scan_interval @@ -79,8 +93,8 @@ class GeoJsonFeedEntityManager: def _init_regular_updates(self): """Schedule regular updates at the specified interval.""" track_time_interval( - self._hass, lambda now: self._feed_manager.update(), - self._scan_interval) + self._hass, lambda now: self._feed_manager.update(), self._scan_interval + ) def get_entry(self, external_id): """Get feed entry by external id.""" @@ -118,11 +132,15 @@ class GeoJsonLocationEvent(GeolocationEvent): async def async_added_to_hass(self): """Call when entity is added to hass.""" self._remove_signal_delete = async_dispatcher_connect( - self.hass, SIGNAL_DELETE_ENTITY.format(self._external_id), - self._delete_callback) + self.hass, + SIGNAL_DELETE_ENTITY.format(self._external_id), + self._delete_callback, + ) self._remove_signal_update = async_dispatcher_connect( - self.hass, SIGNAL_UPDATE_ENTITY.format(self._external_id), - self._update_callback) + self.hass, + SIGNAL_UPDATE_ENTITY.format(self._external_id), + self._update_callback, + ) @callback def _delete_callback(self): diff --git a/homeassistant/components/geo_location/__init__.py b/homeassistant/components/geo_location/__init__.py index 23792e32a2b..869f96901c1 100644 --- a/homeassistant/components/geo_location/__init__.py +++ b/homeassistant/components/geo_location/__init__.py @@ -5,18 +5,20 @@ from typing import Optional from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE from homeassistant.helpers.config_validation import ( # noqa - PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE) + PLATFORM_SCHEMA, + PLATFORM_SCHEMA_BASE, +) from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent _LOGGER = logging.getLogger(__name__) -ATTR_DISTANCE = 'distance' -ATTR_SOURCE = 'source' +ATTR_DISTANCE = "distance" +ATTR_SOURCE = "source" -DOMAIN = 'geo_location' +DOMAIN = "geo_location" -ENTITY_ID_FORMAT = DOMAIN + '.{}' +ENTITY_ID_FORMAT = DOMAIN + ".{}" SCAN_INTERVAL = timedelta(seconds=60) @@ -24,7 +26,8 @@ SCAN_INTERVAL = timedelta(seconds=60) async def async_setup(hass, config): """Set up the Geolocation component.""" component = hass.data[DOMAIN] = EntityComponent( - _LOGGER, DOMAIN, hass, SCAN_INTERVAL) + _LOGGER, DOMAIN, hass, SCAN_INTERVAL + ) await component.async_setup(config) return True diff --git a/homeassistant/components/geo_rss_events/sensor.py b/homeassistant/components/geo_rss_events/sensor.py index f900812385b..a4d13bdef9d 100644 --- a/homeassistant/components/geo_rss_events/sensor.py +++ b/homeassistant/components/geo_rss_events/sensor.py @@ -16,38 +16,45 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_UNIT_OF_MEASUREMENT, CONF_NAME, - CONF_LATITUDE, CONF_LONGITUDE, CONF_RADIUS, CONF_URL) + CONF_UNIT_OF_MEASUREMENT, + CONF_NAME, + CONF_LATITUDE, + CONF_LONGITUDE, + CONF_RADIUS, + CONF_URL, +) from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -ATTR_CATEGORY = 'category' -ATTR_DISTANCE = 'distance' -ATTR_TITLE = 'title' +ATTR_CATEGORY = "category" +ATTR_DISTANCE = "distance" +ATTR_TITLE = "title" -CONF_CATEGORIES = 'categories' +CONF_CATEGORIES = "categories" -DEFAULT_ICON = 'mdi:alert' +DEFAULT_ICON = "mdi:alert" DEFAULT_NAME = "Event Service" DEFAULT_RADIUS_IN_KM = 20.0 -DEFAULT_UNIT_OF_MEASUREMENT = 'Events' +DEFAULT_UNIT_OF_MEASUREMENT = "Events" -DOMAIN = 'geo_rss_events' +DOMAIN = "geo_rss_events" SCAN_INTERVAL = timedelta(minutes=5) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_URL): cv.string, - vol.Optional(CONF_LATITUDE): cv.latitude, - vol.Optional(CONF_LONGITUDE): cv.longitude, - vol.Optional(CONF_RADIUS, default=DEFAULT_RADIUS_IN_KM): vol.Coerce(float), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_CATEGORIES, default=[]): - vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_UNIT_OF_MEASUREMENT, - default=DEFAULT_UNIT_OF_MEASUREMENT): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_URL): cv.string, + vol.Optional(CONF_LATITUDE): cv.latitude, + vol.Optional(CONF_LONGITUDE): cv.longitude, + vol.Optional(CONF_RADIUS, default=DEFAULT_RADIUS_IN_KM): vol.Coerce(float), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_CATEGORIES, default=[]): vol.All(cv.ensure_list, [cv.string]), + vol.Optional( + CONF_UNIT_OF_MEASUREMENT, default=DEFAULT_UNIT_OF_MEASUREMENT + ): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -60,21 +67,31 @@ def setup_platform(hass, config, add_entities, discovery_info=None): categories = config.get(CONF_CATEGORIES) unit_of_measurement = config.get(CONF_UNIT_OF_MEASUREMENT) - _LOGGER.debug("latitude=%s, longitude=%s, url=%s, radius=%s", - latitude, longitude, url, radius_in_km) + _LOGGER.debug( + "latitude=%s, longitude=%s, url=%s, radius=%s", + latitude, + longitude, + url, + radius_in_km, + ) # Create all sensors based on categories. devices = [] if not categories: - device = GeoRssServiceSensor((latitude, longitude), url, - radius_in_km, None, name, - unit_of_measurement) + device = GeoRssServiceSensor( + (latitude, longitude), url, radius_in_km, None, name, unit_of_measurement + ) devices.append(device) else: for category in categories: - device = GeoRssServiceSensor((latitude, longitude), url, - radius_in_km, category, name, - unit_of_measurement) + device = GeoRssServiceSensor( + (latitude, longitude), + url, + radius_in_km, + category, + name, + unit_of_measurement, + ) devices.append(device) add_entities(devices, True) @@ -82,8 +99,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class GeoRssServiceSensor(Entity): """Representation of a Sensor.""" - def __init__(self, coordinates, url, radius, category, service_name, - unit_of_measurement): + def __init__( + self, coordinates, url, radius, category, service_name, unit_of_measurement + ): """Initialize the sensor.""" self._category = category self._service_name = service_name @@ -91,16 +109,20 @@ class GeoRssServiceSensor(Entity): self._state_attributes = None self._unit_of_measurement = unit_of_measurement from georss_client.generic_feed import GenericFeed - self._feed = GenericFeed(coordinates, url, filter_radius=radius, - filter_categories=None if not category - else [category]) + + self._feed = GenericFeed( + coordinates, + url, + filter_radius=radius, + filter_categories=None if not category else [category], + ) @property def name(self): """Return the name of the sensor.""" - return '{} {}'.format(self._service_name, - 'Any' if self._category is None - else self._category) + return "{} {}".format( + self._service_name, "Any" if self._category is None else self._category + ) @property def state(self): @@ -125,24 +147,25 @@ class GeoRssServiceSensor(Entity): def update(self): """Update this sensor from the GeoRSS service.""" import georss_client + status, feed_entries = self._feed.update() if status == georss_client.UPDATE_OK: - _LOGGER.debug("Adding events to sensor %s: %s", self.entity_id, - feed_entries) + _LOGGER.debug( + "Adding events to sensor %s: %s", self.entity_id, feed_entries + ) self._state = len(feed_entries) # And now compute the attributes from the filtered events. matrix = {} for entry in feed_entries: - matrix[entry.title] = '{:.0f}km'.format( - entry.distance_to_home) + matrix[entry.title] = "{:.0f}km".format(entry.distance_to_home) self._state_attributes = matrix elif status == georss_client.UPDATE_OK_NO_DATA: - _LOGGER.debug("Update successful, but no data received from %s", - self._feed) + _LOGGER.debug("Update successful, but no data received from %s", self._feed) # Don't change the state or state attributes. else: - _LOGGER.warning("Update not successful, no data received from %s", - self._feed) + _LOGGER.warning( + "Update not successful, no data received from %s", self._feed + ) # If no events were found due to an error then just set state to # zero. self._state = 0 diff --git a/homeassistant/components/geofency/__init__.py b/homeassistant/components/geofency/__init__.py index 944879788de..6835103968a 100644 --- a/homeassistant/components/geofency/__init__.py +++ b/homeassistant/components/geofency/__init__.py @@ -6,8 +6,14 @@ import voluptuous as vol from homeassistant.components.device_tracker import DOMAIN as DEVICE_TRACKER from homeassistant.const import ( - ATTR_LATITUDE, ATTR_LONGITUDE, ATTR_NAME, CONF_WEBHOOK_ID, HTTP_OK, - HTTP_UNPROCESSABLE_ENTITY, STATE_NOT_HOME) + ATTR_LATITUDE, + ATTR_LONGITUDE, + ATTR_NAME, + CONF_WEBHOOK_ID, + HTTP_OK, + HTTP_UNPROCESSABLE_ENTITY, + STATE_NOT_HOME, +) from homeassistant.helpers import config_entry_flow import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send @@ -17,46 +23,55 @@ from .const import DOMAIN _LOGGER = logging.getLogger(__name__) -CONF_MOBILE_BEACONS = 'mobile_beacons' +CONF_MOBILE_BEACONS = "mobile_beacons" -CONFIG_SCHEMA = vol.Schema({ - vol.Optional(DOMAIN): vol.Schema({ - vol.Optional(CONF_MOBILE_BEACONS, default=[]): vol.All( - cv.ensure_list, [cv.string]), - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + vol.Optional(DOMAIN): vol.Schema( + { + vol.Optional(CONF_MOBILE_BEACONS, default=[]): vol.All( + cv.ensure_list, [cv.string] + ) + } + ) + }, + extra=vol.ALLOW_EXTRA, +) -ATTR_ADDRESS = 'address' -ATTR_BEACON_ID = 'beaconUUID' -ATTR_CURRENT_LATITUDE = 'currentLatitude' -ATTR_CURRENT_LONGITUDE = 'currentLongitude' -ATTR_DEVICE = 'device' -ATTR_ENTRY = 'entry' +ATTR_ADDRESS = "address" +ATTR_BEACON_ID = "beaconUUID" +ATTR_CURRENT_LATITUDE = "currentLatitude" +ATTR_CURRENT_LONGITUDE = "currentLongitude" +ATTR_DEVICE = "device" +ATTR_ENTRY = "entry" -BEACON_DEV_PREFIX = 'beacon' +BEACON_DEV_PREFIX = "beacon" -LOCATION_ENTRY = '1' -LOCATION_EXIT = '0' +LOCATION_ENTRY = "1" +LOCATION_EXIT = "0" -TRACKER_UPDATE = '{}_tracker_update'.format(DOMAIN) +TRACKER_UPDATE = "{}_tracker_update".format(DOMAIN) def _address(value: str) -> str: r"""Coerce address by replacing '\n' with ' '.""" - return value.replace('\n', ' ') + return value.replace("\n", " ") -WEBHOOK_SCHEMA = vol.Schema({ - vol.Required(ATTR_ADDRESS): vol.All(cv.string, _address), - vol.Required(ATTR_DEVICE): vol.All(cv.string, slugify), - vol.Required(ATTR_ENTRY): vol.Any(LOCATION_ENTRY, LOCATION_EXIT), - vol.Required(ATTR_LATITUDE): cv.latitude, - vol.Required(ATTR_LONGITUDE): cv.longitude, - vol.Required(ATTR_NAME): vol.All(cv.string, slugify), - vol.Optional(ATTR_CURRENT_LATITUDE): cv.latitude, - vol.Optional(ATTR_CURRENT_LONGITUDE): cv.longitude, - vol.Optional(ATTR_BEACON_ID): cv.string, -}, extra=vol.ALLOW_EXTRA) +WEBHOOK_SCHEMA = vol.Schema( + { + vol.Required(ATTR_ADDRESS): vol.All(cv.string, _address), + vol.Required(ATTR_DEVICE): vol.All(cv.string, slugify), + vol.Required(ATTR_ENTRY): vol.Any(LOCATION_ENTRY, LOCATION_EXIT), + vol.Required(ATTR_LATITUDE): cv.latitude, + vol.Required(ATTR_LONGITUDE): cv.longitude, + vol.Required(ATTR_NAME): vol.All(cv.string, slugify), + vol.Optional(ATTR_CURRENT_LATITUDE): cv.latitude, + vol.Optional(ATTR_CURRENT_LONGITUDE): cv.longitude, + vol.Optional(ATTR_BEACON_ID): cv.string, + }, + extra=vol.ALLOW_EXTRA, +) async def async_setup(hass, hass_config): @@ -64,9 +79,9 @@ async def async_setup(hass, hass_config): config = hass_config.get(DOMAIN, {}) mobile_beacons = config.get(CONF_MOBILE_BEACONS, []) hass.data[DOMAIN] = { - 'beacons': [slugify(beacon) for beacon in mobile_beacons], - 'devices': set(), - 'unsub_device_tracker': {} + "beacons": [slugify(beacon) for beacon in mobile_beacons], + "devices": set(), + "unsub_device_tracker": {}, } return True @@ -76,15 +91,12 @@ async def handle_webhook(hass, webhook_id, request): try: data = WEBHOOK_SCHEMA(dict(await request.post())) except vol.MultipleInvalid as error: - return web.Response( - text=error.error_message, - status=HTTP_UNPROCESSABLE_ENTITY - ) + return web.Response(text=error.error_message, status=HTTP_UNPROCESSABLE_ENTITY) - if _is_mobile_beacon(data, hass.data[DOMAIN]['beacons']): + if _is_mobile_beacon(data, hass.data[DOMAIN]["beacons"]): return _set_location(hass, data, None) - if data['entry'] == LOCATION_ENTRY: - location_name = data['name'] + if data["entry"] == LOCATION_ENTRY: + location_name = data["name"] else: location_name = STATE_NOT_HOME if ATTR_CURRENT_LATITUDE in data: @@ -96,14 +108,14 @@ async def handle_webhook(hass, webhook_id, request): def _is_mobile_beacon(data, mobile_beacons): """Check if we have a mobile beacon.""" - return ATTR_BEACON_ID in data and data['name'] in mobile_beacons + return ATTR_BEACON_ID in data and data["name"] in mobile_beacons def _device_name(data): """Return name of device tracker.""" if ATTR_BEACON_ID in data: - return "{}_{}".format(BEACON_DEV_PREFIX, data['name']) - return data['device'] + return "{}_{}".format(BEACON_DEV_PREFIX, data["name"]) + return data["device"] def _set_location(hass, data, location_name): @@ -111,17 +123,22 @@ def _set_location(hass, data, location_name): device = _device_name(data) async_dispatcher_send( - hass, TRACKER_UPDATE, device, - (data[ATTR_LATITUDE], data[ATTR_LONGITUDE]), location_name, data) + hass, + TRACKER_UPDATE, + device, + (data[ATTR_LATITUDE], data[ATTR_LONGITUDE]), + location_name, + data, + ) - return web.Response( - text="Setting location for {}".format(device), status=HTTP_OK) + return web.Response(text="Setting location for {}".format(device), status=HTTP_OK) async def async_setup_entry(hass, entry): """Configure based on config entry.""" hass.components.webhook.async_register( - DOMAIN, 'Geofency', entry.data[CONF_WEBHOOK_ID], handle_webhook) + DOMAIN, "Geofency", entry.data[CONF_WEBHOOK_ID], handle_webhook + ) hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, DEVICE_TRACKER) @@ -132,7 +149,7 @@ async def async_setup_entry(hass, entry): async def async_unload_entry(hass, entry): """Unload a config entry.""" hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID]) - hass.data[DOMAIN]['unsub_device_tracker'].pop(entry.entry_id)() + hass.data[DOMAIN]["unsub_device_tracker"].pop(entry.entry_id)() await hass.config_entries.async_forward_entry_unload(entry, DEVICE_TRACKER) return True diff --git a/homeassistant/components/geofency/config_flow.py b/homeassistant/components/geofency/config_flow.py index 422343b16bb..d354e6e245d 100644 --- a/homeassistant/components/geofency/config_flow.py +++ b/homeassistant/components/geofency/config_flow.py @@ -5,8 +5,6 @@ from .const import DOMAIN config_entry_flow.register_webhook_flow( DOMAIN, - 'Geofency Webhook', - { - 'docs_url': 'https://www.home-assistant.io/components/geofency/' - } + "Geofency Webhook", + {"docs_url": "https://www.home-assistant.io/components/geofency/"}, ) diff --git a/homeassistant/components/geofency/const.py b/homeassistant/components/geofency/const.py index f42fb97f168..b0c54a4d407 100644 --- a/homeassistant/components/geofency/const.py +++ b/homeassistant/components/geofency/const.py @@ -1,3 +1,3 @@ """Const for Geofency.""" -DOMAIN = 'geofency' +DOMAIN = "geofency" diff --git a/homeassistant/components/geofency/device_tracker.py b/homeassistant/components/geofency/device_tracker.py index 3400e7ea35d..09e9d46ce6d 100644 --- a/homeassistant/components/geofency/device_tracker.py +++ b/homeassistant/components/geofency/device_tracker.py @@ -1,15 +1,10 @@ """Support for the Geofency device tracker platform.""" import logging -from homeassistant.const import ( - ATTR_LATITUDE, - ATTR_LONGITUDE, -) +from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE from homeassistant.core import callback from homeassistant.components.device_tracker import SOURCE_TYPE_GPS -from homeassistant.components.device_tracker.config_entry import ( - TrackerEntity -) +from homeassistant.components.device_tracker.config_entry import TrackerEntity from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers import device_registry @@ -21,20 +16,20 @@ _LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass, config_entry, async_add_entities): """Set up Geofency config entry.""" + @callback def _receive_data(device, gps, location_name, attributes): """Fire HA event to set location.""" - if device in hass.data[GF_DOMAIN]['devices']: + if device in hass.data[GF_DOMAIN]["devices"]: return - hass.data[GF_DOMAIN]['devices'].add(device) + hass.data[GF_DOMAIN]["devices"].add(device) - async_add_entities([GeofencyEntity( - device, gps, location_name, attributes - )]) + async_add_entities([GeofencyEntity(device, gps, location_name, attributes)]) - hass.data[GF_DOMAIN]['unsub_device_tracker'][config_entry.entry_id] = \ - async_dispatcher_connect(hass, TRACKER_UPDATE, _receive_data) + hass.data[GF_DOMAIN]["unsub_device_tracker"][ + config_entry.entry_id + ] = async_dispatcher_connect(hass, TRACKER_UPDATE, _receive_data) # Restore previously loaded devices dev_reg = await device_registry.async_get_registry(hass) @@ -46,7 +41,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): } if dev_ids: - hass.data[GF_DOMAIN]['devices'].update(dev_ids) + hass.data[GF_DOMAIN]["devices"].update(dev_ids) async_add_entities(GeofencyEntity(dev_id) for dev_id in dev_ids) return True @@ -102,10 +97,7 @@ class GeofencyEntity(TrackerEntity, RestoreEntity): @property def device_info(self): """Return the device info.""" - return { - 'name': self._name, - 'identifiers': {(GF_DOMAIN, self._unique_id)}, - } + return {"name": self._name, "identifiers": {(GF_DOMAIN, self._unique_id)}} @property def source_type(self): @@ -116,7 +108,8 @@ class GeofencyEntity(TrackerEntity, RestoreEntity): """Register state update callback.""" await super().async_added_to_hass() self._unsub_dispatcher = async_dispatcher_connect( - self.hass, TRACKER_UPDATE, self._async_receive_data) + self.hass, TRACKER_UPDATE, self._async_receive_data + ) if self._attributes: return @@ -134,7 +127,7 @@ class GeofencyEntity(TrackerEntity, RestoreEntity): """Clean up after entity before removal.""" await super().async_will_remove_from_hass() self._unsub_dispatcher() - self.hass.data[GF_DOMAIN]['devices'].remove(self._unique_id) + self.hass.data[GF_DOMAIN]["devices"].remove(self._unique_id) @callback def _async_receive_data(self, device, gps, location_name, attributes): diff --git a/homeassistant/components/github/sensor.py b/homeassistant/components/github/sensor.py index d552d2c65cc..a85364ebeca 100644 --- a/homeassistant/components/github/sensor.py +++ b/homeassistant/components/github/sensor.py @@ -5,39 +5,44 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - ATTR_NAME, CONF_ACCESS_TOKEN, CONF_NAME, CONF_PATH, CONF_URL) + ATTR_NAME, + CONF_ACCESS_TOKEN, + CONF_NAME, + CONF_PATH, + CONF_URL, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -CONF_REPOS = 'repositories' +CONF_REPOS = "repositories" -ATTR_LATEST_COMMIT_MESSAGE = 'latest_commit_message' -ATTR_LATEST_COMMIT_SHA = 'latest_commit_sha' -ATTR_LATEST_RELEASE_URL = 'latest_release_url' -ATTR_LATEST_OPEN_ISSUE_URL = 'latest_open_issue_url' -ATTR_OPEN_ISSUES = 'open_issues' -ATTR_LATEST_OPEN_PULL_REQUEST_URL = 'latest_open_pull_request_url' -ATTR_OPEN_PULL_REQUESTS = 'open_pull_requests' -ATTR_PATH = 'path' -ATTR_STARGAZERS = 'stargazers' +ATTR_LATEST_COMMIT_MESSAGE = "latest_commit_message" +ATTR_LATEST_COMMIT_SHA = "latest_commit_sha" +ATTR_LATEST_RELEASE_URL = "latest_release_url" +ATTR_LATEST_OPEN_ISSUE_URL = "latest_open_issue_url" +ATTR_OPEN_ISSUES = "open_issues" +ATTR_LATEST_OPEN_PULL_REQUEST_URL = "latest_open_pull_request_url" +ATTR_OPEN_PULL_REQUESTS = "open_pull_requests" +ATTR_PATH = "path" +ATTR_STARGAZERS = "stargazers" -DEFAULT_NAME = 'GitHub' +DEFAULT_NAME = "GitHub" SCAN_INTERVAL = timedelta(seconds=300) -REPO_SCHEMA = vol.Schema({ - vol.Required(CONF_PATH): cv.string, - vol.Optional(CONF_NAME): cv.string -}) +REPO_SCHEMA = vol.Schema( + {vol.Required(CONF_PATH): cv.string, vol.Optional(CONF_NAME): cv.string} +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_ACCESS_TOKEN): cv.string, - vol.Optional(CONF_URL): cv.url, - vol.Required(CONF_REPOS): - vol.All(cv.ensure_list, [REPO_SCHEMA]) -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_ACCESS_TOKEN): cv.string, + vol.Optional(CONF_URL): cv.url, + vol.Required(CONF_REPOS): vol.All(cv.ensure_list, [REPO_SCHEMA]), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -47,11 +52,13 @@ def setup_platform(hass, config, add_entities, discovery_info=None): data = GitHubData( repository=repository, access_token=config.get(CONF_ACCESS_TOKEN), - server_url=config.get(CONF_URL) + server_url=config.get(CONF_URL), ) if data.setup_error is True: - _LOGGER.error("Error setting up GitHub platform. %s", - "Check previous errors for details") + _LOGGER.error( + "Error setting up GitHub platform. %s", + "Check previous errors for details", + ) return sensors.append(GitHubSensor(data)) add_entities(sensors, True) @@ -110,13 +117,13 @@ class GitHubSensor(Entity): ATTR_OPEN_ISSUES: self._open_issue_count, ATTR_LATEST_OPEN_PULL_REQUEST_URL: self._latest_open_pr_url, ATTR_OPEN_PULL_REQUESTS: self._pull_request_count, - ATTR_STARGAZERS: self._stargazers + ATTR_STARGAZERS: self._stargazers, } @property def icon(self): """Return the icon to use in the frontend.""" - return 'mdi:github-circle' + return "mdi:github-circle" def update(self): """Collect updated data from GitHub API.""" @@ -136,7 +143,7 @@ class GitHubSensor(Entity): self._stargazers = self._github_data.stargazers -class GitHubData(): +class GitHubData: """GitHub Data object.""" def __init__(self, repository, access_token=None, server_url=None): @@ -150,8 +157,7 @@ class GitHubData(): try: if server_url is not None: server_url += "/api/v3" - self._github_obj = github.Github( - access_token, base_url=server_url) + self._github_obj = github.Github(access_token, base_url=server_url) else: self._github_obj = github.Github(access_token) @@ -182,13 +188,13 @@ class GitHubData(): self.stargazers = repo.stargazers_count - open_issues = repo.get_issues(state='open', sort='created') + open_issues = repo.get_issues(state="open", sort="created") if open_issues is not None: self.open_issue_count = open_issues.totalCount if open_issues.totalCount > 0: self.latest_open_issue_url = open_issues[0].html_url - open_pull_requests = repo.get_pulls(state='open', sort='created') + open_pull_requests = repo.get_pulls(state="open", sort="created") if open_pull_requests is not None: self.pull_request_count = open_pull_requests.totalCount if open_pull_requests.totalCount > 0: diff --git a/homeassistant/components/gitlab_ci/sensor.py b/homeassistant/components/gitlab_ci/sensor.py index 1d59a5e4f21..d8055c88f30 100644 --- a/homeassistant/components/gitlab_ci/sensor.py +++ b/homeassistant/components/gitlab_ci/sensor.py @@ -6,40 +6,47 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - ATTR_ATTRIBUTION, CONF_NAME, CONF_SCAN_INTERVAL, CONF_TOKEN, CONF_URL) + ATTR_ATTRIBUTION, + CONF_NAME, + CONF_SCAN_INTERVAL, + CONF_TOKEN, + CONF_URL, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -ATTR_BUILD_BRANCH = 'build branch' -ATTR_BUILD_COMMIT_DATE = 'commit date' -ATTR_BUILD_COMMIT_ID = 'commit id' -ATTR_BUILD_DURATION = 'build_duration' -ATTR_BUILD_FINISHED = 'build_finished' -ATTR_BUILD_ID = 'build id' -ATTR_BUILD_STARTED = 'build_started' -ATTR_BUILD_STATUS = 'build_status' +ATTR_BUILD_BRANCH = "build branch" +ATTR_BUILD_COMMIT_DATE = "commit date" +ATTR_BUILD_COMMIT_ID = "commit id" +ATTR_BUILD_DURATION = "build_duration" +ATTR_BUILD_FINISHED = "build_finished" +ATTR_BUILD_ID = "build id" +ATTR_BUILD_STARTED = "build_started" +ATTR_BUILD_STATUS = "build_status" ATTRIBUTION = "Information provided by https://gitlab.com/" -CONF_GITLAB_ID = 'gitlab_id' +CONF_GITLAB_ID = "gitlab_id" -DEFAULT_NAME = 'GitLab CI Status' -DEFAULT_URL = 'https://gitlab.com' +DEFAULT_NAME = "GitLab CI Status" +DEFAULT_URL = "https://gitlab.com" -ICON_HAPPY = 'mdi:emoticon-happy' -ICON_OTHER = 'mdi:git' -ICON_SAD = 'mdi:emoticon-sad' +ICON_HAPPY = "mdi:emoticon-happy" +ICON_OTHER = "mdi:git" +ICON_SAD = "mdi:emoticon-sad" SCAN_INTERVAL = timedelta(seconds=300) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_GITLAB_ID): cv.string, - vol.Required(CONF_TOKEN): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_URL, default=DEFAULT_URL): cv.string -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_GITLAB_ID): cv.string, + vol.Required(CONF_TOKEN): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_URL, default=DEFAULT_URL): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -52,7 +59,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): priv_token=config[CONF_TOKEN], gitlab_id=config[CONF_GITLAB_ID], interval=_interval, - url=_url + url=_url, ) add_entities([GitLabSensor(_gitlab_data, _name)], True) @@ -102,15 +109,15 @@ class GitLabSensor(Entity): ATTR_BUILD_COMMIT_ID: self._commit_id, ATTR_BUILD_COMMIT_DATE: self._commit_date, ATTR_BUILD_ID: self._build_id, - ATTR_BUILD_BRANCH: self._branch + ATTR_BUILD_BRANCH: self._branch, } @property def icon(self): """Return the icon to use in the frontend.""" - if self._state == 'success': + if self._state == "success": return ICON_HAPPY - if self._state == 'failed': + if self._state == "failed": return ICON_SAD return ICON_OTHER @@ -129,15 +136,15 @@ class GitLabSensor(Entity): self._available = self._gitlab_data.available -class GitLabData(): +class GitLabData: """GitLab Data object.""" def __init__(self, gitlab_id, priv_token, interval, url): """Fetch data from GitLab API for most recent CI job.""" import gitlab + self._gitlab_id = gitlab_id - self._gitlab = gitlab.Gitlab( - url, private_token=priv_token, per_page=1) + self._gitlab = gitlab.Gitlab(url, private_token=priv_token, per_page=1) self._gitlab.auth() self._gitlab_exceptions = gitlab.exceptions self.update = Throttle(interval)(self._update) @@ -157,15 +164,15 @@ class GitLabData(): _projects = self._gitlab.projects.get(self._gitlab_id) _last_pipeline = _projects.pipelines.list(page=1)[0] _last_job = _last_pipeline.jobs.list(page=1)[0] - self.status = _last_pipeline.attributes.get('status') - self.started_at = _last_job.attributes.get('started_at') - self.finished_at = _last_job.attributes.get('finished_at') - self.duration = _last_job.attributes.get('duration') - _commit = _last_job.attributes.get('commit') - self.commit_id = _commit.get('id') - self.commit_date = _commit.get('committed_date') - self.build_id = _last_job.attributes.get('id') - self.branch = _last_job.attributes.get('ref') + self.status = _last_pipeline.attributes.get("status") + self.started_at = _last_job.attributes.get("started_at") + self.finished_at = _last_job.attributes.get("finished_at") + self.duration = _last_job.attributes.get("duration") + _commit = _last_job.attributes.get("commit") + self.commit_id = _commit.get("id") + self.commit_date = _commit.get("committed_date") + self.build_id = _last_job.attributes.get("id") + self.branch = _last_job.attributes.get("ref") self.available = True except self._gitlab_exceptions.GitlabAuthenticationError as erra: _LOGGER.error("Authentication Error: %s", erra) diff --git a/homeassistant/components/gitter/sensor.py b/homeassistant/components/gitter/sensor.py index 06fb6e3a3b5..f124849a193 100644 --- a/homeassistant/components/gitter/sensor.py +++ b/homeassistant/components/gitter/sensor.py @@ -10,20 +10,22 @@ from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -ATTR_MENTION = 'mention' -ATTR_ROOM = 'room' -ATTR_USERNAME = 'username' +ATTR_MENTION = "mention" +ATTR_ROOM = "room" +ATTR_USERNAME = "username" -DEFAULT_NAME = 'Gitter messages' -DEFAULT_ROOM = 'home-assistant/home-assistant' +DEFAULT_NAME = "Gitter messages" +DEFAULT_ROOM = "home-assistant/home-assistant" -ICON = 'mdi:message-settings-variant' +ICON = "mdi:message-settings-variant" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_API_KEY): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_ROOM, default=DEFAULT_ROOM): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_API_KEY): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_ROOM, default=DEFAULT_ROOM): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -37,7 +39,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): gitter = GitterClient(api_key) try: - username = gitter.auth.get_my_id['name'] + username = gitter.auth.get_my_id["name"] except GitterTokenError: _LOGGER.error("Token is not valid") return @@ -56,7 +58,7 @@ class GitterSensor(Entity): self._username = username self._state = None self._mention = 0 - self._unit_of_measurement = 'Msg' + self._unit_of_measurement = "Msg" @property def name(self): @@ -97,8 +99,8 @@ class GitterSensor(Entity): _LOGGER.error(error) return - if 'error' not in data.keys(): - self._mention = len(data['mention']) - self._state = len(data['chat']) + if "error" not in data.keys(): + self._mention = len(data["mention"]) + self._state = len(data["chat"]) else: _LOGGER.error("Not joined: %s", self._room) diff --git a/homeassistant/components/glances/sensor.py b/homeassistant/components/glances/sensor.py index 81a6b900e76..3d630378e42 100644 --- a/homeassistant/components/glances/sensor.py +++ b/homeassistant/components/glances/sensor.py @@ -6,8 +6,17 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_HOST, CONF_NAME, CONF_PORT, CONF_USERNAME, CONF_PASSWORD, CONF_SSL, - CONF_VERIFY_SSL, CONF_RESOURCES, STATE_UNAVAILABLE, TEMP_CELSIUS) + CONF_HOST, + CONF_NAME, + CONF_PORT, + CONF_USERNAME, + CONF_PASSWORD, + CONF_SSL, + CONF_VERIFY_SSL, + CONF_RESOURCES, + STATE_UNAVAILABLE, + TEMP_CELSIUS, +) from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv @@ -16,53 +25,55 @@ from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -CONF_VERSION = 'version' +CONF_VERSION = "version" -DEFAULT_HOST = 'localhost' -DEFAULT_NAME = 'Glances' -DEFAULT_PORT = '61208' +DEFAULT_HOST = "localhost" +DEFAULT_NAME = "Glances" +DEFAULT_PORT = "61208" DEFAULT_VERSION = 2 MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1) SENSOR_TYPES = { - 'disk_use_percent': ['Disk used percent', '%', 'mdi:harddisk'], - 'disk_use': ['Disk used', 'GiB', 'mdi:harddisk'], - 'disk_free': ['Disk free', 'GiB', 'mdi:harddisk'], - 'memory_use_percent': ['RAM used percent', '%', 'mdi:memory'], - 'memory_use': ['RAM used', 'MiB', 'mdi:memory'], - 'memory_free': ['RAM free', 'MiB', 'mdi:memory'], - 'swap_use_percent': ['Swap used percent', '%', 'mdi:memory'], - 'swap_use': ['Swap used', 'GiB', 'mdi:memory'], - 'swap_free': ['Swap free', 'GiB', 'mdi:memory'], - 'processor_load': ['CPU load', '15 min', 'mdi:memory'], - 'process_running': ['Running', 'Count', 'mdi:memory'], - 'process_total': ['Total', 'Count', 'mdi:memory'], - 'process_thread': ['Thread', 'Count', 'mdi:memory'], - 'process_sleeping': ['Sleeping', 'Count', 'mdi:memory'], - 'cpu_use_percent': ['CPU used', '%', 'mdi:memory'], - 'cpu_temp': ['CPU Temp', TEMP_CELSIUS, 'mdi:thermometer'], - 'docker_active': ['Containers active', '', 'mdi:docker'], - 'docker_cpu_use': ['Containers CPU used', '%', 'mdi:docker'], - 'docker_memory_use': ['Containers RAM used', 'MiB', 'mdi:docker'], + "disk_use_percent": ["Disk used percent", "%", "mdi:harddisk"], + "disk_use": ["Disk used", "GiB", "mdi:harddisk"], + "disk_free": ["Disk free", "GiB", "mdi:harddisk"], + "memory_use_percent": ["RAM used percent", "%", "mdi:memory"], + "memory_use": ["RAM used", "MiB", "mdi:memory"], + "memory_free": ["RAM free", "MiB", "mdi:memory"], + "swap_use_percent": ["Swap used percent", "%", "mdi:memory"], + "swap_use": ["Swap used", "GiB", "mdi:memory"], + "swap_free": ["Swap free", "GiB", "mdi:memory"], + "processor_load": ["CPU load", "15 min", "mdi:memory"], + "process_running": ["Running", "Count", "mdi:memory"], + "process_total": ["Total", "Count", "mdi:memory"], + "process_thread": ["Thread", "Count", "mdi:memory"], + "process_sleeping": ["Sleeping", "Count", "mdi:memory"], + "cpu_use_percent": ["CPU used", "%", "mdi:memory"], + "cpu_temp": ["CPU Temp", TEMP_CELSIUS, "mdi:thermometer"], + "docker_active": ["Containers active", "", "mdi:docker"], + "docker_cpu_use": ["Containers CPU used", "%", "mdi:docker"], + "docker_memory_use": ["Containers RAM used", "MiB", "mdi:docker"], } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST, default=DEFAULT_HOST): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_USERNAME): cv.string, - vol.Optional(CONF_PASSWORD): cv.string, - vol.Optional(CONF_SSL, default=False): cv.boolean, - vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean, - vol.Optional(CONF_RESOURCES, default=['disk_use']): - vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), - vol.Optional(CONF_VERSION, default=DEFAULT_VERSION): vol.In([2, 3]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST, default=DEFAULT_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_USERNAME): cv.string, + vol.Optional(CONF_PASSWORD): cv.string, + vol.Optional(CONF_SSL, default=False): cv.boolean, + vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean, + vol.Optional(CONF_RESOURCES, default=["disk_use"]): vol.All( + cv.ensure_list, [vol.In(SENSOR_TYPES)] + ), + vol.Optional(CONF_VERSION, default=DEFAULT_VERSION): vol.In([2, 3]), + } +) -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Glances sensors.""" from glances_api import Glances @@ -78,8 +89,17 @@ async def async_setup_platform( session = async_get_clientsession(hass, verify_ssl) glances = GlancesData( - Glances(hass.loop, session, host=host, port=port, version=version, - username=username, password=password, ssl=ssl)) + Glances( + hass.loop, + session, + host=host, + port=port, + version=version, + username=username, + password=password, + ssl=ssl, + ) + ) await glances.async_update() @@ -107,7 +127,7 @@ class GlancesSensor(Entity): @property def name(self): """Return the name of the sensor.""" - return '{} {}'.format(self._name, SENSOR_TYPES[self.type][0]) + return "{} {}".format(self._name, SENSOR_TYPES[self.type][0]) @property def icon(self): @@ -135,80 +155,94 @@ class GlancesSensor(Entity): value = self.glances.api.data if value is not None: - if self.type == 'disk_use_percent': - self._state = value['fs'][0]['percent'] - elif self.type == 'disk_use': - self._state = round(value['fs'][0]['used'] / 1024**3, 1) - elif self.type == 'disk_free': + if self.type == "disk_use_percent": + self._state = value["fs"][0]["percent"] + elif self.type == "disk_use": + self._state = round(value["fs"][0]["used"] / 1024 ** 3, 1) + elif self.type == "disk_free": try: - self._state = round(value['fs'][0]['free'] / 1024**3, 1) + self._state = round(value["fs"][0]["free"] / 1024 ** 3, 1) except KeyError: - self._state = round((value['fs'][0]['size'] - - value['fs'][0]['used']) / 1024**3, 1) - elif self.type == 'memory_use_percent': - self._state = value['mem']['percent'] - elif self.type == 'memory_use': - self._state = round(value['mem']['used'] / 1024**2, 1) - elif self.type == 'memory_free': - self._state = round(value['mem']['free'] / 1024**2, 1) - elif self.type == 'swap_use_percent': - self._state = value['memswap']['percent'] - elif self.type == 'swap_use': - self._state = round(value['memswap']['used'] / 1024**3, 1) - elif self.type == 'swap_free': - self._state = round(value['memswap']['free'] / 1024**3, 1) - elif self.type == 'processor_load': + self._state = round( + (value["fs"][0]["size"] - value["fs"][0]["used"]) / 1024 ** 3, 1 + ) + elif self.type == "memory_use_percent": + self._state = value["mem"]["percent"] + elif self.type == "memory_use": + self._state = round(value["mem"]["used"] / 1024 ** 2, 1) + elif self.type == "memory_free": + self._state = round(value["mem"]["free"] / 1024 ** 2, 1) + elif self.type == "swap_use_percent": + self._state = value["memswap"]["percent"] + elif self.type == "swap_use": + self._state = round(value["memswap"]["used"] / 1024 ** 3, 1) + elif self.type == "swap_free": + self._state = round(value["memswap"]["free"] / 1024 ** 3, 1) + elif self.type == "processor_load": # Windows systems don't provide load details try: - self._state = value['load']['min15'] + self._state = value["load"]["min15"] except KeyError: - self._state = value['cpu']['total'] - elif self.type == 'process_running': - self._state = value['processcount']['running'] - elif self.type == 'process_total': - self._state = value['processcount']['total'] - elif self.type == 'process_thread': - self._state = value['processcount']['thread'] - elif self.type == 'process_sleeping': - self._state = value['processcount']['sleeping'] - elif self.type == 'cpu_use_percent': - self._state = value['quicklook']['cpu'] - elif self.type == 'cpu_temp': - for sensor in value['sensors']: - if sensor['label'] in ['CPU', "CPU Temperature", - "Package id 0", "Physical id 0", - "cpu_thermal 1", "cpu-thermal 1", - "exynos-therm 1", "soc_thermal 1", - "soc-thermal 1", "aml_thermal"]: - self._state = sensor['value'] - elif self.type == 'docker_active': + self._state = value["cpu"]["total"] + elif self.type == "process_running": + self._state = value["processcount"]["running"] + elif self.type == "process_total": + self._state = value["processcount"]["total"] + elif self.type == "process_thread": + self._state = value["processcount"]["thread"] + elif self.type == "process_sleeping": + self._state = value["processcount"]["sleeping"] + elif self.type == "cpu_use_percent": + self._state = value["quicklook"]["cpu"] + elif self.type == "cpu_temp": + for sensor in value["sensors"]: + if sensor["label"] in [ + "CPU", + "CPU Temperature", + "Package id 0", + "Physical id 0", + "cpu_thermal 1", + "cpu-thermal 1", + "exynos-therm 1", + "soc_thermal 1", + "soc-thermal 1", + "aml_thermal", + ]: + self._state = sensor["value"] + elif self.type == "docker_active": count = 0 try: - for container in value['docker']['containers']: - if container['Status'] == 'running' or \ - 'Up' in container['Status']: + for container in value["docker"]["containers"]: + if ( + container["Status"] == "running" + or "Up" in container["Status"] + ): count += 1 self._state = count except KeyError: self._state = count - elif self.type == 'docker_cpu_use': + elif self.type == "docker_cpu_use": cpu_use = 0.0 try: - for container in value['docker']['containers']: - if container['Status'] == 'running' or \ - 'Up' in container['Status']: - cpu_use += container['cpu']['total'] + for container in value["docker"]["containers"]: + if ( + container["Status"] == "running" + or "Up" in container["Status"] + ): + cpu_use += container["cpu"]["total"] self._state = round(cpu_use, 1) except KeyError: self._state = STATE_UNAVAILABLE - elif self.type == 'docker_memory_use': + elif self.type == "docker_memory_use": mem_use = 0.0 try: - for container in value['docker']['containers']: - if container['Status'] == 'running' or \ - 'Up' in container['Status']: - mem_use += container['memory']['usage'] - self._state = round(mem_use / 1024**2, 1) + for container in value["docker"]["containers"]: + if ( + container["Status"] == "running" + or "Up" in container["Status"] + ): + mem_use += container["memory"]["usage"] + self._state = round(mem_use / 1024 ** 2, 1) except KeyError: self._state = STATE_UNAVAILABLE diff --git a/homeassistant/components/gntp/notify.py b/homeassistant/components/gntp/notify.py index 005043c1384..48c02cf0ba8 100644 --- a/homeassistant/components/gntp/notify.py +++ b/homeassistant/components/gntp/notify.py @@ -8,46 +8,60 @@ from homeassistant.const import CONF_PASSWORD, CONF_PORT import homeassistant.helpers.config_validation as cv from homeassistant.components.notify import ( - ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, BaseNotificationService) + ATTR_TITLE, + ATTR_TITLE_DEFAULT, + PLATFORM_SCHEMA, + BaseNotificationService, +) _LOGGER = logging.getLogger(__name__) -_GNTP_LOGGER = logging.getLogger('gntp') +_GNTP_LOGGER = logging.getLogger("gntp") _GNTP_LOGGER.setLevel(logging.ERROR) -CONF_APP_NAME = 'app_name' -CONF_APP_ICON = 'app_icon' -CONF_HOSTNAME = 'hostname' +CONF_APP_NAME = "app_name" +CONF_APP_ICON = "app_icon" +CONF_HOSTNAME = "hostname" -DEFAULT_APP_NAME = 'HomeAssistant' -DEFAULT_HOST = 'localhost' +DEFAULT_APP_NAME = "HomeAssistant" +DEFAULT_HOST = "localhost" DEFAULT_PORT = 23053 -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_APP_NAME, default=DEFAULT_APP_NAME): cv.string, - vol.Optional(CONF_APP_ICON): vol.Url, - vol.Optional(CONF_HOSTNAME, default=DEFAULT_HOST): cv.string, - vol.Optional(CONF_PASSWORD): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_APP_NAME, default=DEFAULT_APP_NAME): cv.string, + vol.Optional(CONF_APP_ICON): vol.Url, + vol.Optional(CONF_HOSTNAME, default=DEFAULT_HOST): cv.string, + vol.Optional(CONF_PASSWORD): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + } +) def get_service(hass, config, discovery_info=None): """Get the GNTP notification service.""" if config.get(CONF_APP_ICON) is None: - icon_file = os.path.join(os.path.dirname(__file__), "..", "frontend", - "www_static", "icons", "favicon-192x192.png") - with open(icon_file, 'rb') as file: + icon_file = os.path.join( + os.path.dirname(__file__), + "..", + "frontend", + "www_static", + "icons", + "favicon-192x192.png", + ) + with open(icon_file, "rb") as file: app_icon = file.read() else: app_icon = config.get(CONF_APP_ICON) - return GNTPNotificationService(config.get(CONF_APP_NAME), - app_icon, - config.get(CONF_HOSTNAME), - config.get(CONF_PASSWORD), - config.get(CONF_PORT)) + return GNTPNotificationService( + config.get(CONF_APP_NAME), + app_icon, + config.get(CONF_HOSTNAME), + config.get(CONF_PASSWORD), + config.get(CONF_PORT), + ) class GNTPNotificationService(BaseNotificationService): @@ -57,13 +71,14 @@ class GNTPNotificationService(BaseNotificationService): """Initialize the service.""" import gntp.notifier import gntp.errors + self.gntp = gntp.notifier.GrowlNotifier( applicationName=app_name, notifications=["Notification"], applicationIcon=app_icon, hostname=hostname, password=password, - port=port + port=port, ) try: self.gntp.register() @@ -73,6 +88,8 @@ class GNTPNotificationService(BaseNotificationService): def send_message(self, message="", **kwargs): """Send a message to a user.""" - self.gntp.notify(noteType="Notification", - title=kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT), - description=message) + self.gntp.notify( + noteType="Notification", + title=kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT), + description=message, + ) diff --git a/homeassistant/components/goalfeed/__init__.py b/homeassistant/components/goalfeed/__init__.py index 4a7e4ea980a..3a14eb2831d 100644 --- a/homeassistant/components/goalfeed/__init__.py +++ b/homeassistant/components/goalfeed/__init__.py @@ -9,23 +9,29 @@ from homeassistant.const import CONF_PASSWORD, CONF_USERNAME # Version downgraded due to regression in library # For details: https://github.com/nlsdfnbch/Pysher/issues/38 -DOMAIN = 'goalfeed' +DOMAIN = "goalfeed" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) -GOALFEED_HOST = 'feed.goalfeed.ca' -GOALFEED_AUTH_ENDPOINT = 'https://goalfeed.ca/feed/auth' -GOALFEED_APP_ID = 'bfd4ed98c1ff22c04074' +GOALFEED_HOST = "feed.goalfeed.ca" +GOALFEED_AUTH_ENDPOINT = "https://goalfeed.ca/feed/auth" +GOALFEED_APP_ID = "bfd4ed98c1ff22c04074" def setup(hass, config): """Set up the Goalfeed component.""" import pysher + conf = config[DOMAIN] username = conf.get(CONF_USERNAME) password = conf.get(CONF_PASSWORD) @@ -34,24 +40,25 @@ def setup(hass, config): """Handle goal events.""" goal = json.loads(json.loads(data)) - hass.bus.fire('goal', event_data=goal) + hass.bus.fire("goal", event_data=goal) def connect_handler(data): """Handle connection.""" post_data = { - 'username': username, - 'password': password, - 'connection_info': data} - resp = requests.post( - GOALFEED_AUTH_ENDPOINT, post_data, timeout=30).json() + "username": username, + "password": password, + "connection_info": data, + } + resp = requests.post(GOALFEED_AUTH_ENDPOINT, post_data, timeout=30).json() - channel = pusher.subscribe('private-goals', resp['auth']) - channel.bind('goal', goal_handler) + channel = pusher.subscribe("private-goals", resp["auth"]) + channel.bind("goal", goal_handler) - pusher = pysher.Pusher(GOALFEED_APP_ID, secure=False, port=8080, - custom_host=GOALFEED_HOST) + pusher = pysher.Pusher( + GOALFEED_APP_ID, secure=False, port=8080, custom_host=GOALFEED_HOST + ) - pusher.connection.bind('pusher:connection_established', connect_handler) + pusher.connection.bind("pusher:connection_established", connect_handler) pusher.connect() return True diff --git a/homeassistant/components/gogogate2/cover.py b/homeassistant/components/gogogate2/cover.py index 610c131bda5..26aecee2504 100644 --- a/homeassistant/components/gogogate2/cover.py +++ b/homeassistant/components/gogogate2/cover.py @@ -3,26 +3,31 @@ import logging import voluptuous as vol -from homeassistant.components.cover import ( - CoverDevice, SUPPORT_OPEN, SUPPORT_CLOSE) +from homeassistant.components.cover import CoverDevice, SUPPORT_OPEN, SUPPORT_CLOSE from homeassistant.const import ( - CONF_USERNAME, CONF_PASSWORD, STATE_CLOSED, - CONF_IP_ADDRESS, CONF_NAME) + CONF_USERNAME, + CONF_PASSWORD, + STATE_CLOSED, + CONF_IP_ADDRESS, + CONF_NAME, +) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'gogogate2' +DEFAULT_NAME = "gogogate2" -NOTIFICATION_ID = 'gogogate2_notification' -NOTIFICATION_TITLE = 'Gogogate2 Cover Setup' +NOTIFICATION_ID = "gogogate2_notification" +NOTIFICATION_TITLE = "Gogogate2 Cover Setup" -COVER_SCHEMA = vol.Schema({ - vol.Required(CONF_IP_ADDRESS): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +COVER_SCHEMA = vol.Schema( + { + vol.Required(CONF_IP_ADDRESS): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -39,20 +44,19 @@ def setup_platform(hass, config, add_entities, discovery_info=None): try: devices = mygogogate2.get_devices() if devices is False: - raise ValueError( - "Username or Password is incorrect or no devices found") + raise ValueError("Username or Password is incorrect or no devices found") - add_entities(MyGogogate2Device( - mygogogate2, door, name) for door in devices) + add_entities(MyGogogate2Device(mygogogate2, door, name) for door in devices) except (TypeError, KeyError, NameError, ValueError) as ex: _LOGGER.error("%s", ex) hass.components.persistent_notification.create( - 'Error: {}
' - 'You will need to restart hass after fixing.' - ''.format(ex), + "Error: {}
" + "You will need to restart hass after fixing." + "".format(ex), title=NOTIFICATION_TITLE, - notification_id=NOTIFICATION_ID) + notification_id=NOTIFICATION_ID, + ) class MyGogogate2Device(CoverDevice): @@ -61,9 +65,9 @@ class MyGogogate2Device(CoverDevice): def __init__(self, mygogogate2, device, name): """Initialize with API object, device id.""" self.mygogogate2 = mygogogate2 - self.device_id = device['door'] - self._name = name or device['name'] - self._status = device['status'] + self.device_id = device["door"] + self._name = name or device["name"] + self._status = device["status"] self._available = None @property @@ -79,7 +83,7 @@ class MyGogogate2Device(CoverDevice): @property def device_class(self): """Return the class of this device, from component DEVICE_CLASSES.""" - return 'garage' + return "garage" @property def supported_features(self): diff --git a/homeassistant/components/google/__init__.py b/homeassistant/components/google/__init__.py index dc1c7b1d5e5..41901a71704 100644 --- a/homeassistant/components/google/__init__.py +++ b/homeassistant/components/google/__init__.py @@ -15,78 +15,89 @@ from homeassistant.util import convert, dt _LOGGER = logging.getLogger(__name__) -DOMAIN = 'google' -ENTITY_ID_FORMAT = DOMAIN + '.{}' +DOMAIN = "google" +ENTITY_ID_FORMAT = DOMAIN + ".{}" -CONF_CLIENT_ID = 'client_id' -CONF_CLIENT_SECRET = 'client_secret' -CONF_TRACK_NEW = 'track_new_calendar' +CONF_CLIENT_ID = "client_id" +CONF_CLIENT_SECRET = "client_secret" +CONF_TRACK_NEW = "track_new_calendar" -CONF_CAL_ID = 'cal_id' -CONF_DEVICE_ID = 'device_id' -CONF_NAME = 'name' -CONF_ENTITIES = 'entities' -CONF_TRACK = 'track' -CONF_SEARCH = 'search' -CONF_OFFSET = 'offset' -CONF_IGNORE_AVAILABILITY = 'ignore_availability' -CONF_MAX_RESULTS = 'max_results' +CONF_CAL_ID = "cal_id" +CONF_DEVICE_ID = "device_id" +CONF_NAME = "name" +CONF_ENTITIES = "entities" +CONF_TRACK = "track" +CONF_SEARCH = "search" +CONF_OFFSET = "offset" +CONF_IGNORE_AVAILABILITY = "ignore_availability" +CONF_MAX_RESULTS = "max_results" DEFAULT_CONF_TRACK_NEW = True -DEFAULT_CONF_OFFSET = '!!' +DEFAULT_CONF_OFFSET = "!!" -EVENT_CALENDAR_ID = 'calendar_id' -EVENT_DESCRIPTION = 'description' -EVENT_END_CONF = 'end' -EVENT_END_DATE = 'end_date' -EVENT_END_DATETIME = 'end_date_time' -EVENT_IN = 'in' -EVENT_IN_DAYS = 'days' -EVENT_IN_WEEKS = 'weeks' -EVENT_START_CONF = 'start' -EVENT_START_DATE = 'start_date' -EVENT_START_DATETIME = 'start_date_time' -EVENT_SUMMARY = 'summary' -EVENT_TYPES_CONF = 'event_types' +EVENT_CALENDAR_ID = "calendar_id" +EVENT_DESCRIPTION = "description" +EVENT_END_CONF = "end" +EVENT_END_DATE = "end_date" +EVENT_END_DATETIME = "end_date_time" +EVENT_IN = "in" +EVENT_IN_DAYS = "days" +EVENT_IN_WEEKS = "weeks" +EVENT_START_CONF = "start" +EVENT_START_DATE = "start_date" +EVENT_START_DATETIME = "start_date_time" +EVENT_SUMMARY = "summary" +EVENT_TYPES_CONF = "event_types" -NOTIFICATION_ID = 'google_calendar_notification' +NOTIFICATION_ID = "google_calendar_notification" NOTIFICATION_TITLE = "Google Calendar Setup" GROUP_NAME_ALL_CALENDARS = "Google Calendar Sensors" -SERVICE_SCAN_CALENDARS = 'scan_for_calendars' -SERVICE_FOUND_CALENDARS = 'found_calendar' -SERVICE_ADD_EVENT = 'add_event' +SERVICE_SCAN_CALENDARS = "scan_for_calendars" +SERVICE_FOUND_CALENDARS = "found_calendar" +SERVICE_ADD_EVENT = "add_event" -DATA_INDEX = 'google_calendars' +DATA_INDEX = "google_calendars" -YAML_DEVICES = '{}_calendars.yaml'.format(DOMAIN) -SCOPES = 'https://www.googleapis.com/auth/calendar' +YAML_DEVICES = "{}_calendars.yaml".format(DOMAIN) +SCOPES = "https://www.googleapis.com/auth/calendar" -TOKEN_FILE = '.{}.token'.format(DOMAIN) +TOKEN_FILE = ".{}.token".format(DOMAIN) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_CLIENT_ID): cv.string, - vol.Required(CONF_CLIENT_SECRET): cv.string, - vol.Optional(CONF_TRACK_NEW): cv.boolean, - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_CLIENT_ID): cv.string, + vol.Required(CONF_CLIENT_SECRET): cv.string, + vol.Optional(CONF_TRACK_NEW): cv.boolean, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) -_SINGLE_CALSEARCH_CONFIG = vol.Schema({ - vol.Required(CONF_NAME): cv.string, - vol.Required(CONF_DEVICE_ID): cv.string, - vol.Optional(CONF_IGNORE_AVAILABILITY, default=True): cv.boolean, - vol.Optional(CONF_OFFSET): cv.string, - vol.Optional(CONF_SEARCH): cv.string, - vol.Optional(CONF_TRACK): cv.boolean, - vol.Optional(CONF_MAX_RESULTS): cv.positive_int, -}) +_SINGLE_CALSEARCH_CONFIG = vol.Schema( + { + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_DEVICE_ID): cv.string, + vol.Optional(CONF_IGNORE_AVAILABILITY, default=True): cv.boolean, + vol.Optional(CONF_OFFSET): cv.string, + vol.Optional(CONF_SEARCH): cv.string, + vol.Optional(CONF_TRACK): cv.boolean, + vol.Optional(CONF_MAX_RESULTS): cv.positive_int, + } +) -DEVICE_SCHEMA = vol.Schema({ - vol.Required(CONF_CAL_ID): cv.string, - vol.Required(CONF_ENTITIES, None): - vol.All(cv.ensure_list, [_SINGLE_CALSEARCH_CONFIG]), -}, extra=vol.ALLOW_EXTRA) +DEVICE_SCHEMA = vol.Schema( + { + vol.Required(CONF_CAL_ID): cv.string, + vol.Required(CONF_ENTITIES, None): vol.All( + cv.ensure_list, [_SINGLE_CALSEARCH_CONFIG] + ), + }, + extra=vol.ALLOW_EXTRA, +) _EVENT_IN_TYPES = vol.Schema( { @@ -104,8 +115,7 @@ ADD_EVENT_SERVICE_SCHEMA = vol.Schema( vol.Exclusive(EVENT_END_DATE, EVENT_END_CONF): cv.date, vol.Exclusive(EVENT_START_DATETIME, EVENT_START_CONF): cv.datetime, vol.Exclusive(EVENT_END_DATETIME, EVENT_END_CONF): cv.datetime, - vol.Exclusive(EVENT_IN, EVENT_START_CONF, EVENT_END_CONF): - _EVENT_IN_TYPES + vol.Exclusive(EVENT_IN, EVENT_START_CONF, EVENT_END_CONF): _EVENT_IN_TYPES, } ) @@ -117,42 +127,47 @@ def do_authentication(hass, hass_config, config): until we have an access token. """ from oauth2client.client import ( - OAuth2WebServerFlow, OAuth2DeviceCodeError, FlowExchangeError) + OAuth2WebServerFlow, + OAuth2DeviceCodeError, + FlowExchangeError, + ) from oauth2client.file import Storage oauth = OAuth2WebServerFlow( client_id=config[CONF_CLIENT_ID], client_secret=config[CONF_CLIENT_SECRET], - scope='https://www.googleapis.com/auth/calendar', - redirect_uri='Home-Assistant.io', + scope="https://www.googleapis.com/auth/calendar", + redirect_uri="Home-Assistant.io", ) try: dev_flow = oauth.step1_get_device_and_user_codes() except OAuth2DeviceCodeError as err: hass.components.persistent_notification.create( - 'Error: {}
You will need to restart hass after fixing.' - ''.format(err), + "Error: {}
You will need to restart hass after fixing." "".format(err), title=NOTIFICATION_TITLE, - notification_id=NOTIFICATION_ID) + notification_id=NOTIFICATION_ID, + ) return False hass.components.persistent_notification.create( - 'In order to authorize Home-Assistant to view your calendars ' + "In order to authorize Home-Assistant to view your calendars " 'you must visit:
{} and enter ' - 'code: {}'.format(dev_flow.verification_url, - dev_flow.verification_url, - dev_flow.user_code), - title=NOTIFICATION_TITLE, notification_id=NOTIFICATION_ID + "code: {}".format( + dev_flow.verification_url, dev_flow.verification_url, dev_flow.user_code + ), + title=NOTIFICATION_TITLE, + notification_id=NOTIFICATION_ID, ) def step2_exchange(now): """Keep trying to validate the user_code until it expires.""" if now >= dt.as_local(dev_flow.user_code_expiry): hass.components.persistent_notification.create( - 'Authentication code expired, please restart ' - 'Home-Assistant and try again', + "Authentication code expired, please restart " + "Home-Assistant and try again", title=NOTIFICATION_TITLE, - notification_id=NOTIFICATION_ID) + notification_id=NOTIFICATION_ID, + ) listener() try: @@ -166,12 +181,15 @@ def do_authentication(hass, hass_config, config): do_setup(hass, hass_config, config) listener() hass.components.persistent_notification.create( - 'We are all setup now. Check {} for calendars that have ' - 'been found'.format(YAML_DEVICES), - title=NOTIFICATION_TITLE, notification_id=NOTIFICATION_ID) + "We are all setup now. Check {} for calendars that have " + "been found".format(YAML_DEVICES), + title=NOTIFICATION_TITLE, + notification_id=NOTIFICATION_ID, + ) listener = track_time_change( - hass, step2_exchange, second=range(0, 60, dev_flow.interval)) + hass, step2_exchange, second=range(0, 60, dev_flow.interval) + ) return True @@ -207,9 +225,9 @@ def check_correct_scopes(token_file): return True -def setup_services(hass, hass_config, track_new_found_calendars, - calendar_service): +def setup_services(hass, hass_config, track_new_found_calendars, calendar_service): """Set up the service listeners.""" + def _found_calendar(call): """Check if we know about a calendar and generate PLATFORM_DISCOVER.""" calendar = get_calendar_info(hass, call.data) @@ -219,29 +237,29 @@ def setup_services(hass, hass_config, track_new_found_calendars, hass.data[DATA_INDEX].update({calendar[CONF_CAL_ID]: calendar}) update_config( - hass.config.path(YAML_DEVICES), - hass.data[DATA_INDEX][calendar[CONF_CAL_ID]] + hass.config.path(YAML_DEVICES), hass.data[DATA_INDEX][calendar[CONF_CAL_ID]] ) - discovery.load_platform(hass, 'calendar', DOMAIN, - hass.data[DATA_INDEX][calendar[CONF_CAL_ID]], - hass_config) + discovery.load_platform( + hass, + "calendar", + DOMAIN, + hass.data[DATA_INDEX][calendar[CONF_CAL_ID]], + hass_config, + ) - hass.services.register( - DOMAIN, SERVICE_FOUND_CALENDARS, _found_calendar) + hass.services.register(DOMAIN, SERVICE_FOUND_CALENDARS, _found_calendar) def _scan_for_calendars(service): """Scan for new calendars.""" service = calendar_service.get() cal_list = service.calendarList() - calendars = cal_list.list().execute()['items'] + calendars = cal_list.list().execute()["items"] for calendar in calendars: - calendar['track'] = track_new_found_calendars - hass.services.call(DOMAIN, SERVICE_FOUND_CALENDARS, - calendar) + calendar["track"] = track_new_found_calendars + hass.services.call(DOMAIN, SERVICE_FOUND_CALENDARS, calendar) - hass.services.register( - DOMAIN, SERVICE_SCAN_CALENDARS, _scan_for_calendars) + hass.services.register(DOMAIN, SERVICE_SCAN_CALENDARS, _scan_for_calendars) def _add_event(call): """Add a new event to calendar.""" @@ -253,45 +271,40 @@ def setup_services(hass, hass_config, track_new_found_calendars, if EVENT_IN_DAYS in call.data[EVENT_IN]: now = datetime.now() - start_in = now + timedelta( - days=call.data[EVENT_IN][EVENT_IN_DAYS]) + start_in = now + timedelta(days=call.data[EVENT_IN][EVENT_IN_DAYS]) end_in = start_in + timedelta(days=1) - start = {'date': start_in.strftime('%Y-%m-%d')} - end = {'date': end_in.strftime('%Y-%m-%d')} + start = {"date": start_in.strftime("%Y-%m-%d")} + end = {"date": end_in.strftime("%Y-%m-%d")} elif EVENT_IN_WEEKS in call.data[EVENT_IN]: now = datetime.now() - start_in = now + timedelta( - weeks=call.data[EVENT_IN][EVENT_IN_WEEKS]) + start_in = now + timedelta(weeks=call.data[EVENT_IN][EVENT_IN_WEEKS]) end_in = start_in + timedelta(days=1) - start = {'date': start_in.strftime('%Y-%m-%d')} - end = {'date': end_in.strftime('%Y-%m-%d')} + start = {"date": start_in.strftime("%Y-%m-%d")} + end = {"date": end_in.strftime("%Y-%m-%d")} elif EVENT_START_DATE in call.data: - start = {'date': str(call.data[EVENT_START_DATE])} - end = {'date': str(call.data[EVENT_END_DATE])} + start = {"date": str(call.data[EVENT_START_DATE])} + end = {"date": str(call.data[EVENT_END_DATE])} elif EVENT_START_DATETIME in call.data: - start_dt = str(call.data[EVENT_START_DATETIME] - .strftime('%Y-%m-%dT%H:%M:%S')) - end_dt = str(call.data[EVENT_END_DATETIME] - .strftime('%Y-%m-%dT%H:%M:%S')) - start = {'dateTime': start_dt, - 'timeZone': str(hass.config.time_zone)} - end = {'dateTime': end_dt, - 'timeZone': str(hass.config.time_zone)} + start_dt = str( + call.data[EVENT_START_DATETIME].strftime("%Y-%m-%dT%H:%M:%S") + ) + end_dt = str(call.data[EVENT_END_DATETIME].strftime("%Y-%m-%dT%H:%M:%S")) + start = {"dateTime": start_dt, "timeZone": str(hass.config.time_zone)} + end = {"dateTime": end_dt, "timeZone": str(hass.config.time_zone)} event = { - 'summary': call.data[EVENT_SUMMARY], - 'description': call.data[EVENT_DESCRIPTION], - 'start': start, - 'end': end, + "summary": call.data[EVENT_SUMMARY], + "description": call.data[EVENT_DESCRIPTION], + "start": start, + "end": end, } - service_data = {'calendarId': call.data[EVENT_CALENDAR_ID], - 'body': event} + service_data = {"calendarId": call.data[EVENT_CALENDAR_ID], "body": event} event = service.events().insert(**service_data).execute() hass.services.register( @@ -306,14 +319,13 @@ def do_setup(hass, hass_config, config): hass.data[DATA_INDEX] = load_config(hass.config.path(YAML_DEVICES)) calendar_service = GoogleCalendarService(hass.config.path(TOKEN_FILE)) - track_new_found_calendars = convert(config.get(CONF_TRACK_NEW), - bool, DEFAULT_CONF_TRACK_NEW) - setup_services(hass, hass_config, track_new_found_calendars, - calendar_service) + track_new_found_calendars = convert( + config.get(CONF_TRACK_NEW), bool, DEFAULT_CONF_TRACK_NEW + ) + setup_services(hass, hass_config, track_new_found_calendars, calendar_service) for calendar in hass.data[DATA_INDEX].values(): - discovery.load_platform(hass, 'calendar', DOMAIN, calendar, - hass_config) + discovery.load_platform(hass, "calendar", DOMAIN, calendar, hass_config) # Look for any new calendars hass.services.call(DOMAIN, SERVICE_SCAN_CALENDARS, None) @@ -332,24 +344,31 @@ class GoogleCalendarService: import httplib2 from oauth2client.file import Storage from googleapiclient import discovery as google_discovery + credentials = Storage(self.token_file).get() http = credentials.authorize(httplib2.Http()) service = google_discovery.build( - 'calendar', 'v3', http=http, cache_discovery=False) + "calendar", "v3", http=http, cache_discovery=False + ) return service def get_calendar_info(hass, calendar): """Convert data from Google into DEVICE_SCHEMA.""" - calendar_info = DEVICE_SCHEMA({ - CONF_CAL_ID: calendar['id'], - CONF_ENTITIES: [{ - CONF_TRACK: calendar['track'], - CONF_NAME: calendar['summary'], - CONF_DEVICE_ID: generate_entity_id( - '{}', calendar['summary'], hass=hass), - }] - }) + calendar_info = DEVICE_SCHEMA( + { + CONF_CAL_ID: calendar["id"], + CONF_ENTITIES: [ + { + CONF_TRACK: calendar["track"], + CONF_NAME: calendar["summary"], + CONF_DEVICE_ID: generate_entity_id( + "{}", calendar["summary"], hass=hass + ), + } + ], + } + ) return calendar_info @@ -361,8 +380,7 @@ def load_config(path): data = yaml.safe_load(file) for calendar in data: try: - calendars.update({calendar[CONF_CAL_ID]: - DEVICE_SCHEMA(calendar)}) + calendars.update({calendar[CONF_CAL_ID]: DEVICE_SCHEMA(calendar)}) except VoluptuousError as exception: # keep going _LOGGER.warning("Calendar Invalid Data: %s", exception) @@ -375,6 +393,6 @@ def load_config(path): def update_config(path, calendar): """Write the google_calendar_devices.yaml.""" - with open(path, 'a') as out: - out.write('\n') + with open(path, "a") as out: + out.write("\n") yaml.dump([calendar], out, default_flow_style=False) diff --git a/homeassistant/components/google/calendar.py b/homeassistant/components/google/calendar.py index 66d6b61f75b..31e9f186a4e 100644 --- a/homeassistant/components/google/calendar.py +++ b/homeassistant/components/google/calendar.py @@ -4,21 +4,35 @@ from datetime import timedelta import logging from homeassistant.components.calendar import ( - ENTITY_ID_FORMAT, CalendarEventDevice, calculate_offset, is_offset_reached) + ENTITY_ID_FORMAT, + CalendarEventDevice, + calculate_offset, + is_offset_reached, +) from homeassistant.helpers.entity import generate_entity_id from homeassistant.util import Throttle, dt from . import ( - CONF_CAL_ID, CONF_DEVICE_ID, CONF_ENTITIES, CONF_IGNORE_AVAILABILITY, - CONF_MAX_RESULTS, CONF_NAME, CONF_OFFSET, CONF_SEARCH, CONF_TRACK, - DEFAULT_CONF_OFFSET, TOKEN_FILE, GoogleCalendarService) + CONF_CAL_ID, + CONF_DEVICE_ID, + CONF_ENTITIES, + CONF_IGNORE_AVAILABILITY, + CONF_MAX_RESULTS, + CONF_NAME, + CONF_OFFSET, + CONF_SEARCH, + CONF_TRACK, + DEFAULT_CONF_OFFSET, + TOKEN_FILE, + GoogleCalendarService, +) _LOGGER = logging.getLogger(__name__) DEFAULT_GOOGLE_SEARCH_PARAMS = { - 'orderBy': 'startTime', - 'maxResults': 5, - 'singleEvents': True, + "orderBy": "startTime", + "maxResults": 5, + "singleEvents": True, } MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15) @@ -38,9 +52,11 @@ def setup_platform(hass, config, add_entities, disc_info=None): if not data[CONF_TRACK]: continue entity_id = generate_entity_id( - ENTITY_ID_FORMAT, data[CONF_DEVICE_ID], hass=hass) + ENTITY_ID_FORMAT, data[CONF_DEVICE_ID], hass=hass + ) entity = GoogleCalendarEventDevice( - calendar_service, disc_info[CONF_CAL_ID], data, entity_id) + calendar_service, disc_info[CONF_CAL_ID], data, entity_id + ) entities.append(entity) add_entities(entities, True) @@ -52,9 +68,12 @@ class GoogleCalendarEventDevice(CalendarEventDevice): def __init__(self, calendar_service, calendar, data, entity_id): """Create the Calendar event device.""" self.data = GoogleCalendarData( - calendar_service, calendar, - data.get(CONF_SEARCH), data.get(CONF_IGNORE_AVAILABILITY), - data.get(CONF_MAX_RESULTS)) + calendar_service, + calendar, + data.get(CONF_SEARCH), + data.get(CONF_IGNORE_AVAILABILITY), + data.get(CONF_MAX_RESULTS), + ) self._event = None self._name = data[CONF_NAME] self._offset = data.get(CONF_OFFSET, DEFAULT_CONF_OFFSET) @@ -64,9 +83,7 @@ class GoogleCalendarEventDevice(CalendarEventDevice): @property def device_state_attributes(self): """Return the device state attributes.""" - return { - 'offset_reached': self._offset_reached, - } + return {"offset_reached": self._offset_reached} @property def event(self): @@ -97,8 +114,9 @@ class GoogleCalendarEventDevice(CalendarEventDevice): class GoogleCalendarData: """Class to utilize calendar service object to get next event.""" - def __init__(self, calendar_service, calendar_id, search, - ignore_availability, max_results): + def __init__( + self, calendar_service, calendar_id, search, ignore_availability, max_results + ): """Set up how we are going to search the google calendar.""" self.calendar_service = calendar_service self.calendar_id = calendar_id @@ -117,33 +135,30 @@ class GoogleCalendarData: _LOGGER.error("Unable to connect to Google") return None, None params = dict(DEFAULT_GOOGLE_SEARCH_PARAMS) - params['calendarId'] = self.calendar_id + params["calendarId"] = self.calendar_id if self.max_results: - params['maxResults'] = self.max_results + params["maxResults"] = self.max_results if self.search: - params['q'] = self.search + params["q"] = self.search return service, params async def async_get_events(self, hass, start_date, end_date): """Get all events in a specific time frame.""" - service, params = await hass.async_add_executor_job( - self._prepare_query) + service, params = await hass.async_add_executor_job(self._prepare_query) if service is None: return [] - params['timeMin'] = start_date.isoformat('T') - params['timeMax'] = end_date.isoformat('T') + params["timeMin"] = start_date.isoformat("T") + params["timeMax"] = end_date.isoformat("T") events = await hass.async_add_executor_job(service.events) - result = await hass.async_add_executor_job( - events.list(**params).execute) + result = await hass.async_add_executor_job(events.list(**params).execute) - items = result.get('items', []) + items = result.get("items", []) event_list = [] for item in items: - if (not self.ignore_availability - and 'transparency' in item.keys()): - if item['transparency'] == 'opaque': + if not self.ignore_availability and "transparency" in item.keys(): + if item["transparency"] == "opaque": event_list.append(item) else: event_list.append(item) @@ -155,18 +170,17 @@ class GoogleCalendarData: service, params = self._prepare_query() if service is None: return - params['timeMin'] = dt.now().isoformat('T') + params["timeMin"] = dt.now().isoformat("T") events = service.events() result = events.list(**params).execute() - items = result.get('items', []) + items = result.get("items", []) new_event = None for item in items: - if (not self.ignore_availability - and 'transparency' in item.keys()): - if item['transparency'] == 'opaque': + if not self.ignore_availability and "transparency" in item.keys(): + if item["transparency"] == "opaque": new_event = item break else: diff --git a/homeassistant/components/google_assistant/__init__.py b/homeassistant/components/google_assistant/__init__.py index 1e0ac6d9363..61e0c70b6b3 100644 --- a/homeassistant/components/google_assistant/__init__.py +++ b/homeassistant/components/google_assistant/__init__.py @@ -16,11 +16,21 @@ from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import ( - DOMAIN, CONF_PROJECT_ID, CONF_EXPOSE_BY_DEFAULT, DEFAULT_EXPOSE_BY_DEFAULT, - CONF_EXPOSED_DOMAINS, DEFAULT_EXPOSED_DOMAINS, CONF_API_KEY, - SERVICE_REQUEST_SYNC, REQUEST_SYNC_BASE_URL, CONF_ENTITY_CONFIG, - CONF_EXPOSE, CONF_ALIASES, CONF_ROOM_HINT, CONF_ALLOW_UNLOCK, - CONF_SECURE_DEVICES_PIN + DOMAIN, + CONF_PROJECT_ID, + CONF_EXPOSE_BY_DEFAULT, + DEFAULT_EXPOSE_BY_DEFAULT, + CONF_EXPOSED_DOMAINS, + DEFAULT_EXPOSED_DOMAINS, + CONF_API_KEY, + SERVICE_REQUEST_SYNC, + REQUEST_SYNC_BASE_URL, + CONF_ENTITY_CONFIG, + CONF_EXPOSE, + CONF_ALIASES, + CONF_ROOM_HINT, + CONF_ALLOW_UNLOCK, + CONF_SECURE_DEVICES_PIN, ) from .const import EVENT_COMMAND_RECEIVED, EVENT_SYNC_RECEIVED # noqa: F401 from .const import EVENT_QUERY_RECEIVED # noqa: F401 @@ -28,31 +38,37 @@ from .http import async_register_http _LOGGER = logging.getLogger(__name__) -ENTITY_SCHEMA = vol.Schema({ - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_EXPOSE): cv.boolean, - vol.Optional(CONF_ALIASES): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_ROOM_HINT): cv.string, -}) +ENTITY_SCHEMA = vol.Schema( + { + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_EXPOSE): cv.boolean, + vol.Optional(CONF_ALIASES): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_ROOM_HINT): cv.string, + } +) GOOGLE_ASSISTANT_SCHEMA = vol.All( - cv.deprecated(CONF_ALLOW_UNLOCK, invalidation_version='0.95'), - vol.Schema({ - vol.Required(CONF_PROJECT_ID): cv.string, - vol.Optional(CONF_EXPOSE_BY_DEFAULT, - default=DEFAULT_EXPOSE_BY_DEFAULT): cv.boolean, - vol.Optional(CONF_EXPOSED_DOMAINS, - default=DEFAULT_EXPOSED_DOMAINS): cv.ensure_list, - vol.Optional(CONF_API_KEY): cv.string, - vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: ENTITY_SCHEMA}, - vol.Optional(CONF_ALLOW_UNLOCK): cv.boolean, - # str on purpose, makes sure it is configured correctly. - vol.Optional(CONF_SECURE_DEVICES_PIN): str, - }, extra=vol.PREVENT_EXTRA)) + cv.deprecated(CONF_ALLOW_UNLOCK, invalidation_version="0.95"), + vol.Schema( + { + vol.Required(CONF_PROJECT_ID): cv.string, + vol.Optional( + CONF_EXPOSE_BY_DEFAULT, default=DEFAULT_EXPOSE_BY_DEFAULT + ): cv.boolean, + vol.Optional( + CONF_EXPOSED_DOMAINS, default=DEFAULT_EXPOSED_DOMAINS + ): cv.ensure_list, + vol.Optional(CONF_API_KEY): cv.string, + vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: ENTITY_SCHEMA}, + vol.Optional(CONF_ALLOW_UNLOCK): cv.boolean, + # str on purpose, makes sure it is configured correctly. + vol.Optional(CONF_SECURE_DEVICES_PIN): str, + }, + extra=vol.PREVENT_EXTRA, + ), +) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: GOOGLE_ASSISTANT_SCHEMA -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema({DOMAIN: GOOGLE_ASSISTANT_SCHEMA}, extra=vol.ALLOW_EXTRA) async def async_setup(hass: HomeAssistant, yaml_config: Dict[str, Any]): @@ -66,24 +82,24 @@ async def async_setup(hass: HomeAssistant, yaml_config: Dict[str, Any]): websession = async_get_clientsession(hass) try: with async_timeout.timeout(15): - agent_user_id = call.data.get('agent_user_id') or \ - call.context.user_id + agent_user_id = call.data.get("agent_user_id") or call.context.user_id res = await websession.post( REQUEST_SYNC_BASE_URL, - params={'key': api_key}, - json={'agent_user_id': agent_user_id}) + params={"key": api_key}, + json={"agent_user_id": agent_user_id}, + ) _LOGGER.info("Submitted request_sync request to Google") res.raise_for_status() except aiohttp.ClientResponseError: body = await res.read() - _LOGGER.error( - 'request_sync request failed: %d %s', res.status, body) + _LOGGER.error("request_sync request failed: %d %s", res.status, body) except (asyncio.TimeoutError, aiohttp.ClientError): _LOGGER.error("Could not contact Google for request_sync") # Register service only if api key is provided if api_key is not None: hass.services.async_register( - DOMAIN, SERVICE_REQUEST_SYNC, request_sync_service_handler) + DOMAIN, SERVICE_REQUEST_SYNC, request_sync_service_handler + ) return True diff --git a/homeassistant/components/google_assistant/const.py b/homeassistant/components/google_assistant/const.py index ebded79447e..1d266d23d3f 100644 --- a/homeassistant/components/google_assistant/const.py +++ b/homeassistant/components/google_assistant/const.py @@ -16,48 +16,60 @@ from homeassistant.components import ( switch, vacuum, ) -DOMAIN = 'google_assistant' -GOOGLE_ASSISTANT_API_ENDPOINT = '/api/google_assistant' +DOMAIN = "google_assistant" -CONF_EXPOSE = 'expose' -CONF_ENTITY_CONFIG = 'entity_config' -CONF_EXPOSE_BY_DEFAULT = 'expose_by_default' -CONF_EXPOSED_DOMAINS = 'exposed_domains' -CONF_PROJECT_ID = 'project_id' -CONF_ALIASES = 'aliases' -CONF_API_KEY = 'api_key' -CONF_ROOM_HINT = 'room' -CONF_ALLOW_UNLOCK = 'allow_unlock' -CONF_SECURE_DEVICES_PIN = 'secure_devices_pin' +GOOGLE_ASSISTANT_API_ENDPOINT = "/api/google_assistant" + +CONF_EXPOSE = "expose" +CONF_ENTITY_CONFIG = "entity_config" +CONF_EXPOSE_BY_DEFAULT = "expose_by_default" +CONF_EXPOSED_DOMAINS = "exposed_domains" +CONF_PROJECT_ID = "project_id" +CONF_ALIASES = "aliases" +CONF_API_KEY = "api_key" +CONF_ROOM_HINT = "room" +CONF_ALLOW_UNLOCK = "allow_unlock" +CONF_SECURE_DEVICES_PIN = "secure_devices_pin" DEFAULT_EXPOSE_BY_DEFAULT = True DEFAULT_EXPOSED_DOMAINS = [ - 'climate', 'cover', 'fan', 'group', 'input_boolean', 'light', - 'media_player', 'scene', 'script', 'switch', 'vacuum', 'lock', - 'binary_sensor', 'sensor' + "climate", + "cover", + "fan", + "group", + "input_boolean", + "light", + "media_player", + "scene", + "script", + "switch", + "vacuum", + "lock", + "binary_sensor", + "sensor", ] -PREFIX_TYPES = 'action.devices.types.' -TYPE_CAMERA = PREFIX_TYPES + 'CAMERA' -TYPE_LIGHT = PREFIX_TYPES + 'LIGHT' -TYPE_SWITCH = PREFIX_TYPES + 'SWITCH' -TYPE_VACUUM = PREFIX_TYPES + 'VACUUM' -TYPE_SCENE = PREFIX_TYPES + 'SCENE' -TYPE_FAN = PREFIX_TYPES + 'FAN' -TYPE_THERMOSTAT = PREFIX_TYPES + 'THERMOSTAT' -TYPE_LOCK = PREFIX_TYPES + 'LOCK' -TYPE_BLINDS = PREFIX_TYPES + 'BLINDS' -TYPE_GARAGE = PREFIX_TYPES + 'GARAGE' -TYPE_OUTLET = PREFIX_TYPES + 'OUTLET' -TYPE_SENSOR = PREFIX_TYPES + 'SENSOR' -TYPE_DOOR = PREFIX_TYPES + 'DOOR' -TYPE_TV = PREFIX_TYPES + 'TV' -TYPE_SPEAKER = PREFIX_TYPES + 'SPEAKER' +PREFIX_TYPES = "action.devices.types." +TYPE_CAMERA = PREFIX_TYPES + "CAMERA" +TYPE_LIGHT = PREFIX_TYPES + "LIGHT" +TYPE_SWITCH = PREFIX_TYPES + "SWITCH" +TYPE_VACUUM = PREFIX_TYPES + "VACUUM" +TYPE_SCENE = PREFIX_TYPES + "SCENE" +TYPE_FAN = PREFIX_TYPES + "FAN" +TYPE_THERMOSTAT = PREFIX_TYPES + "THERMOSTAT" +TYPE_LOCK = PREFIX_TYPES + "LOCK" +TYPE_BLINDS = PREFIX_TYPES + "BLINDS" +TYPE_GARAGE = PREFIX_TYPES + "GARAGE" +TYPE_OUTLET = PREFIX_TYPES + "OUTLET" +TYPE_SENSOR = PREFIX_TYPES + "SENSOR" +TYPE_DOOR = PREFIX_TYPES + "DOOR" +TYPE_TV = PREFIX_TYPES + "TV" +TYPE_SPEAKER = PREFIX_TYPES + "SPEAKER" -SERVICE_REQUEST_SYNC = 'request_sync' -HOMEGRAPH_URL = 'https://homegraph.googleapis.com/' -REQUEST_SYNC_BASE_URL = HOMEGRAPH_URL + 'v1/devices:requestSync' +SERVICE_REQUEST_SYNC = "request_sync" +HOMEGRAPH_URL = "https://homegraph.googleapis.com/" +REQUEST_SYNC_BASE_URL = HOMEGRAPH_URL + "v1/devices:requestSync" # Error codes used for SmartHomeError class # https://developers.google.com/actions/reference/smarthome/errors-exceptions @@ -65,20 +77,20 @@ ERR_DEVICE_OFFLINE = "deviceOffline" ERR_DEVICE_NOT_FOUND = "deviceNotFound" ERR_VALUE_OUT_OF_RANGE = "valueOutOfRange" ERR_NOT_SUPPORTED = "notSupported" -ERR_PROTOCOL_ERROR = 'protocolError' -ERR_UNKNOWN_ERROR = 'unknownError' -ERR_FUNCTION_NOT_SUPPORTED = 'functionNotSupported' +ERR_PROTOCOL_ERROR = "protocolError" +ERR_UNKNOWN_ERROR = "unknownError" +ERR_FUNCTION_NOT_SUPPORTED = "functionNotSupported" -ERR_CHALLENGE_NEEDED = 'challengeNeeded' -ERR_CHALLENGE_NOT_SETUP = 'challengeFailedNotSetup' -ERR_TOO_MANY_FAILED_ATTEMPTS = 'tooManyFailedAttempts' -ERR_PIN_INCORRECT = 'pinIncorrect' -ERR_USER_CANCELLED = 'userCancelled' +ERR_CHALLENGE_NEEDED = "challengeNeeded" +ERR_CHALLENGE_NOT_SETUP = "challengeFailedNotSetup" +ERR_TOO_MANY_FAILED_ATTEMPTS = "tooManyFailedAttempts" +ERR_PIN_INCORRECT = "pinIncorrect" +ERR_USER_CANCELLED = "userCancelled" # Event types -EVENT_COMMAND_RECEIVED = 'google_assistant_command' -EVENT_QUERY_RECEIVED = 'google_assistant_query' -EVENT_SYNC_RECEIVED = 'google_assistant_sync' +EVENT_COMMAND_RECEIVED = "google_assistant_command" +EVENT_QUERY_RECEIVED = "google_assistant_query" +EVENT_SYNC_RECEIVED = "google_assistant_sync" DOMAIN_TO_GOOGLE_TYPES = { camera.DOMAIN: TYPE_CAMERA, @@ -102,8 +114,7 @@ DEVICE_CLASS_TO_GOOGLE_TYPES = { (switch.DOMAIN, switch.DEVICE_CLASS_SWITCH): TYPE_SWITCH, (switch.DOMAIN, switch.DEVICE_CLASS_OUTLET): TYPE_OUTLET, (binary_sensor.DOMAIN, binary_sensor.DEVICE_CLASS_DOOR): TYPE_DOOR, - (binary_sensor.DOMAIN, binary_sensor.DEVICE_CLASS_GARAGE_DOOR): - TYPE_GARAGE, + (binary_sensor.DOMAIN, binary_sensor.DEVICE_CLASS_GARAGE_DOOR): TYPE_GARAGE, (binary_sensor.DOMAIN, binary_sensor.DEVICE_CLASS_LOCK): TYPE_SENSOR, (binary_sensor.DOMAIN, binary_sensor.DEVICE_CLASS_OPENING): TYPE_SENSOR, (binary_sensor.DOMAIN, binary_sensor.DEVICE_CLASS_WINDOW): TYPE_SENSOR, @@ -112,6 +123,6 @@ DEVICE_CLASS_TO_GOOGLE_TYPES = { (sensor.DOMAIN, sensor.DEVICE_CLASS_TEMPERATURE): TYPE_SENSOR, } -CHALLENGE_ACK_NEEDED = 'ackNeeded' -CHALLENGE_PIN_NEEDED = 'pinNeeded' -CHALLENGE_FAILED_PIN_NEEDED = 'challengeFailedPinNeeded' +CHALLENGE_ACK_NEEDED = "ackNeeded" +CHALLENGE_PIN_NEEDED = "pinNeeded" +CHALLENGE_FAILED_PIN_NEEDED = "challengeFailedPinNeeded" diff --git a/homeassistant/components/google_assistant/error.py b/homeassistant/components/google_assistant/error.py index 3aef1e9408d..a2ff72511c7 100644 --- a/homeassistant/components/google_assistant/error.py +++ b/homeassistant/components/google_assistant/error.py @@ -15,9 +15,7 @@ class SmartHomeError(Exception): def to_response(self): """Convert to a response format.""" - return { - 'errorCode': self.code - } + return {"errorCode": self.code} class ChallengeNeeded(SmartHomeError): @@ -28,15 +26,14 @@ class ChallengeNeeded(SmartHomeError): def __init__(self, challenge_type): """Initialize challenge needed error.""" - super().__init__(ERR_CHALLENGE_NEEDED, - 'Challenge needed: {}'.format(challenge_type)) + super().__init__( + ERR_CHALLENGE_NEEDED, "Challenge needed: {}".format(challenge_type) + ) self.challenge_type = challenge_type def to_response(self): """Convert to a response format.""" return { - 'errorCode': self.code, - 'challengeNeeded': { - 'type': self.challenge_type - } + "errorCode": self.code, + "challengeNeeded": {"type": self.challenge_type}, } diff --git a/homeassistant/components/google_assistant/helpers.py b/homeassistant/components/google_assistant/helpers.py index 87c4fb78f3a..066ed0057ac 100644 --- a/homeassistant/components/google_assistant/helpers.py +++ b/homeassistant/components/google_assistant/helpers.py @@ -5,14 +5,20 @@ from typing import List from homeassistant.core import Context, callback from homeassistant.const import ( - CONF_NAME, STATE_UNAVAILABLE, ATTR_SUPPORTED_FEATURES, - ATTR_DEVICE_CLASS, CLOUD_NEVER_EXPOSED_ENTITIES + CONF_NAME, + STATE_UNAVAILABLE, + ATTR_SUPPORTED_FEATURES, + ATTR_DEVICE_CLASS, + CLOUD_NEVER_EXPOSED_ENTITIES, ) from . import trait from .const import ( - DOMAIN_TO_GOOGLE_TYPES, CONF_ALIASES, ERR_FUNCTION_NOT_SUPPORTED, - DEVICE_CLASS_TO_GOOGLE_TYPES, CONF_ROOM_HINT + DOMAIN_TO_GOOGLE_TYPES, + CONF_ALIASES, + ERR_FUNCTION_NOT_SUPPORTED, + DEVICE_CLASS_TO_GOOGLE_TYPES, + CONF_ROOM_HINT, ) from .error import SmartHomeError @@ -88,9 +94,11 @@ class GoogleEntity: features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) device_class = state.attributes.get(ATTR_DEVICE_CLASS) - self._traits = [Trait(self.hass, state, self.config) - for Trait in trait.TRAITS - if Trait.supported(domain, features, device_class)] + self._traits = [ + Trait(self.hass, state, self.config) + for Trait in trait.TRAITS + if Trait.supported(domain, features, device_class) + ] return self._traits @callback @@ -106,8 +114,9 @@ class GoogleEntity: features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) device_class = state.attributes.get(ATTR_DEVICE_CLASS) - return any(trait.might_2fa(domain, features, device_class) - for trait in self.traits()) + return any( + trait.might_2fa(domain, features, device_class) for trait in self.traits() + ) async def sync_serialize(self): """Serialize entity for a SYNC response. @@ -123,31 +132,28 @@ class GoogleEntity: traits = self.traits() - device_type = get_google_type(domain, - device_class) + device_type = get_google_type(domain, device_class) device = { - 'id': state.entity_id, - 'name': { - 'name': name - }, - 'attributes': {}, - 'traits': [trait.name for trait in traits], - 'willReportState': False, - 'type': device_type, + "id": state.entity_id, + "name": {"name": name}, + "attributes": {}, + "traits": [trait.name for trait in traits], + "willReportState": False, + "type": device_type, } # use aliases aliases = entity_config.get(CONF_ALIASES) if aliases: - device['name']['nicknames'] = aliases + device["name"]["nicknames"] = aliases for trt in traits: - device['attributes'].update(trt.sync_attributes()) + device["attributes"].update(trt.sync_attributes()) room = entity_config.get(CONF_ROOM_HINT) if room: - device['roomHint'] = room + device["roomHint"] = room return device dev_reg, ent_reg, area_reg = await gather( @@ -166,7 +172,7 @@ class GoogleEntity: area_entry = area_reg.areas.get(device_entry.area_id) if area_entry and area_entry.name: - device['roomHint'] = area_entry.name + device["roomHint"] = area_entry.name return device @@ -179,9 +185,9 @@ class GoogleEntity: state = self.state if state.state == STATE_UNAVAILABLE: - return {'online': False} + return {"online": False} - attrs = {'online': True} + attrs = {"online": True} for trt in self.traits(): deep_update(attrs, trt.query_attributes()) @@ -193,9 +199,9 @@ class GoogleEntity: https://developers.google.com/actions/smarthome/create-app#actiondevicesexecute """ - command = command_payload['command'] - params = command_payload.get('params', {}) - challenge = command_payload.get('challenge', {}) + command = command_payload["command"] + params = command_payload.get("params", {}) + challenge = command_payload.get("challenge", {}) executed = False for trt in self.traits(): if trt.can_execute(command, params): @@ -206,8 +212,8 @@ class GoogleEntity: if not executed: raise SmartHomeError( ERR_FUNCTION_NOT_SUPPORTED, - 'Unable to execute {} for {}'.format(command, - self.state.entity_id)) + "Unable to execute {} for {}".format(command, self.state.entity_id), + ) @callback def async_update(self): diff --git a/homeassistant/components/google_assistant/http.py b/homeassistant/components/google_assistant/http.py index 8f6d441d498..24502462512 100644 --- a/homeassistant/components/google_assistant/http.py +++ b/homeassistant/components/google_assistant/http.py @@ -49,24 +49,23 @@ class GoogleConfig(AbstractConfig): expose_by_default = self._config.get(CONF_EXPOSE_BY_DEFAULT) exposed_domains = self._config.get(CONF_EXPOSED_DOMAINS) - if state.attributes.get('view') is not None: + if state.attributes.get("view") is not None: # Ignore entities that are views return False if state.entity_id in CLOUD_NEVER_EXPOSED_ENTITIES: return False - explicit_expose = \ - self.entity_config.get(state.entity_id, {}).get(CONF_EXPOSE) + explicit_expose = self.entity_config.get(state.entity_id, {}).get(CONF_EXPOSE) - domain_exposed_by_default = \ + domain_exposed_by_default = ( expose_by_default and state.domain in exposed_domains + ) # Expose an entity if the entity's domain is exposed by default and # the configuration doesn't explicitly exclude it from being # exposed, or if the entity is explicitly exposed - is_default_exposed = \ - domain_exposed_by_default and explicit_expose is not False + is_default_exposed = domain_exposed_by_default and explicit_expose is not False return is_default_exposed or explicit_expose @@ -85,7 +84,7 @@ class GoogleAssistantView(HomeAssistantView): """Handle Google Assistant requests.""" url = GOOGLE_ASSISTANT_API_ENDPOINT - name = 'api:google_assistant' + name = "api:google_assistant" requires_auth = True def __init__(self, config): @@ -96,8 +95,6 @@ class GoogleAssistantView(HomeAssistantView): """Handle Google Assistant requests.""" message = await request.json() # type: dict result = await async_handle_message( - request.app['hass'], - self.config, - request['hass_user'].id, - message) + request.app["hass"], self.config, request["hass_user"].id, message + ) return self.json(result) diff --git a/homeassistant/components/google_assistant/smart_home.py b/homeassistant/components/google_assistant/smart_home.py index ef8be50fda7..2cb440f9181 100644 --- a/homeassistant/components/google_assistant/smart_home.py +++ b/homeassistant/components/google_assistant/smart_home.py @@ -8,8 +8,12 @@ from homeassistant.util.decorator import Registry from homeassistant.const import ATTR_ENTITY_ID from .const import ( - ERR_PROTOCOL_ERROR, ERR_DEVICE_OFFLINE, ERR_UNKNOWN_ERROR, - EVENT_COMMAND_RECEIVED, EVENT_SYNC_RECEIVED, EVENT_QUERY_RECEIVED + ERR_PROTOCOL_ERROR, + ERR_DEVICE_OFFLINE, + ERR_UNKNOWN_ERROR, + EVENT_COMMAND_RECEIVED, + EVENT_SYNC_RECEIVED, + EVENT_QUERY_RECEIVED, ) from .helpers import RequestData, GoogleEntity, async_get_entities from .error import SmartHomeError @@ -20,112 +24,107 @@ _LOGGER = logging.getLogger(__name__) async def async_handle_message(hass, config, user_id, message): """Handle incoming API messages.""" - request_id = message.get('requestId') # type: str + request_id = message.get("requestId") # type: str data = RequestData(config, user_id, request_id) response = await _process(hass, data, message) - if response and 'errorCode' in response['payload']: - _LOGGER.error('Error handling message %s: %s', - message, response['payload']) + if response and "errorCode" in response["payload"]: + _LOGGER.error("Error handling message %s: %s", message, response["payload"]) return response async def _process(hass, data, message): """Process a message.""" - inputs = message.get('inputs') # type: list + inputs = message.get("inputs") # type: list if len(inputs) != 1: return { - 'requestId': data.request_id, - 'payload': {'errorCode': ERR_PROTOCOL_ERROR} + "requestId": data.request_id, + "payload": {"errorCode": ERR_PROTOCOL_ERROR}, } - handler = HANDLERS.get(inputs[0].get('intent')) + handler = HANDLERS.get(inputs[0].get("intent")) if handler is None: return { - 'requestId': data.request_id, - 'payload': {'errorCode': ERR_PROTOCOL_ERROR} + "requestId": data.request_id, + "payload": {"errorCode": ERR_PROTOCOL_ERROR}, } try: - result = await handler(hass, data, inputs[0].get('payload')) + result = await handler(hass, data, inputs[0].get("payload")) except SmartHomeError as err: - return { - 'requestId': data.request_id, - 'payload': {'errorCode': err.code} - } + return {"requestId": data.request_id, "payload": {"errorCode": err.code}} except Exception: # pylint: disable=broad-except - _LOGGER.exception('Unexpected error') + _LOGGER.exception("Unexpected error") return { - 'requestId': data.request_id, - 'payload': {'errorCode': ERR_UNKNOWN_ERROR} + "requestId": data.request_id, + "payload": {"errorCode": ERR_UNKNOWN_ERROR}, } if result is None: return None - return {'requestId': data.request_id, 'payload': result} + return {"requestId": data.request_id, "payload": result} -@HANDLERS.register('action.devices.SYNC') +@HANDLERS.register("action.devices.SYNC") async def async_devices_sync(hass, data, payload): """Handle action.devices.SYNC request. https://developers.google.com/actions/smarthome/create-app#actiondevicessync """ hass.bus.async_fire( - EVENT_SYNC_RECEIVED, - {'request_id': data.request_id}, - context=data.context) + EVENT_SYNC_RECEIVED, {"request_id": data.request_id}, context=data.context + ) - devices = await asyncio.gather(*( - entity.sync_serialize() for entity in - async_get_entities(hass, data.config) - if data.config.should_expose(entity.state) - )) + devices = await asyncio.gather( + *( + entity.sync_serialize() + for entity in async_get_entities(hass, data.config) + if data.config.should_expose(entity.state) + ) + ) response = { - 'agentUserId': data.config.agent_user_id or data.context.user_id, - 'devices': devices, + "agentUserId": data.config.agent_user_id or data.context.user_id, + "devices": devices, } return response -@HANDLERS.register('action.devices.QUERY') +@HANDLERS.register("action.devices.QUERY") async def async_devices_query(hass, data, payload): """Handle action.devices.QUERY request. https://developers.google.com/actions/smarthome/create-app#actiondevicesquery """ devices = {} - for device in payload.get('devices', []): - devid = device['id'] + for device in payload.get("devices", []): + devid = device["id"] state = hass.states.get(devid) hass.bus.async_fire( EVENT_QUERY_RECEIVED, - { - 'request_id': data.request_id, - ATTR_ENTITY_ID: devid, - }, - context=data.context) + {"request_id": data.request_id, ATTR_ENTITY_ID: devid}, + context=data.context, + ) if not state: # If we can't find a state, the device is offline - devices[devid] = {'online': False} + devices[devid] = {"online": False} continue entity = GoogleEntity(hass, data.config, state) devices[devid] = entity.query_serialize() - return {'devices': devices} + return {"devices": devices} -@HANDLERS.register('action.devices.EXECUTE') +@HANDLERS.register("action.devices.EXECUTE") async def handle_devices_execute(hass, data, payload): """Handle action.devices.EXECUTE request. @@ -134,19 +133,19 @@ async def handle_devices_execute(hass, data, payload): entities = {} results = {} - for command in payload['commands']: - for device, execution in product(command['devices'], - command['execution']): - entity_id = device['id'] + for command in payload["commands"]: + for device, execution in product(command["devices"], command["execution"]): + entity_id = device["id"] hass.bus.async_fire( EVENT_COMMAND_RECEIVED, { - 'request_id': data.request_id, + "request_id": data.request_id, ATTR_ENTITY_ID: entity_id, - 'execution': execution + "execution": execution, }, - context=data.context) + context=data.context, + ) # Happens if error occurred. Skip entity for further processing if entity_id in results: @@ -157,9 +156,9 @@ async def handle_devices_execute(hass, data, payload): if state is None: results[entity_id] = { - 'ids': [entity_id], - 'status': 'ERROR', - 'errorCode': ERR_DEVICE_OFFLINE + "ids": [entity_id], + "status": "ERROR", + "errorCode": ERR_DEVICE_OFFLINE, } continue @@ -169,9 +168,9 @@ async def handle_devices_execute(hass, data, payload): await entities[entity_id].execute(data, execution) except SmartHomeError as err: results[entity_id] = { - 'ids': [entity_id], - 'status': 'ERROR', - **err.to_response() + "ids": [entity_id], + "status": "ERROR", + **err.to_response(), } final_results = list(results.values()) @@ -182,16 +181,18 @@ async def handle_devices_execute(hass, data, payload): entity.async_update() - final_results.append({ - 'ids': [entity.entity_id], - 'status': 'SUCCESS', - 'states': entity.query_serialize(), - }) + final_results.append( + { + "ids": [entity.entity_id], + "status": "SUCCESS", + "states": entity.query_serialize(), + } + ) - return {'commands': final_results} + return {"commands": final_results} -@HANDLERS.register('action.devices.DISCONNECT') +@HANDLERS.register("action.devices.DISCONNECT") async def async_devices_disconnect(hass, data, payload): """Handle action.devices.DISCONNECT request. @@ -203,6 +204,6 @@ async def async_devices_disconnect(hass, data, payload): def turned_off_response(message): """Return a device turned off response.""" return { - 'requestId': message.get('requestId'), - 'payload': {'errorCode': 'deviceTurnedOff'} + "requestId": message.get("requestId"), + "payload": {"errorCode": "deviceTurnedOff"}, } diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py index 2d7b7edd6ba..5fa7d49b885 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -48,41 +48,43 @@ from .error import SmartHomeError, ChallengeNeeded _LOGGER = logging.getLogger(__name__) -PREFIX_TRAITS = 'action.devices.traits.' -TRAIT_CAMERA_STREAM = PREFIX_TRAITS + 'CameraStream' -TRAIT_ONOFF = PREFIX_TRAITS + 'OnOff' -TRAIT_DOCK = PREFIX_TRAITS + 'Dock' -TRAIT_STARTSTOP = PREFIX_TRAITS + 'StartStop' -TRAIT_BRIGHTNESS = PREFIX_TRAITS + 'Brightness' -TRAIT_COLOR_SETTING = PREFIX_TRAITS + 'ColorSetting' -TRAIT_SCENE = PREFIX_TRAITS + 'Scene' -TRAIT_TEMPERATURE_SETTING = PREFIX_TRAITS + 'TemperatureSetting' -TRAIT_LOCKUNLOCK = PREFIX_TRAITS + 'LockUnlock' -TRAIT_FANSPEED = PREFIX_TRAITS + 'FanSpeed' -TRAIT_MODES = PREFIX_TRAITS + 'Modes' -TRAIT_OPENCLOSE = PREFIX_TRAITS + 'OpenClose' -TRAIT_VOLUME = PREFIX_TRAITS + 'Volume' +PREFIX_TRAITS = "action.devices.traits." +TRAIT_CAMERA_STREAM = PREFIX_TRAITS + "CameraStream" +TRAIT_ONOFF = PREFIX_TRAITS + "OnOff" +TRAIT_DOCK = PREFIX_TRAITS + "Dock" +TRAIT_STARTSTOP = PREFIX_TRAITS + "StartStop" +TRAIT_BRIGHTNESS = PREFIX_TRAITS + "Brightness" +TRAIT_COLOR_SETTING = PREFIX_TRAITS + "ColorSetting" +TRAIT_SCENE = PREFIX_TRAITS + "Scene" +TRAIT_TEMPERATURE_SETTING = PREFIX_TRAITS + "TemperatureSetting" +TRAIT_LOCKUNLOCK = PREFIX_TRAITS + "LockUnlock" +TRAIT_FANSPEED = PREFIX_TRAITS + "FanSpeed" +TRAIT_MODES = PREFIX_TRAITS + "Modes" +TRAIT_OPENCLOSE = PREFIX_TRAITS + "OpenClose" +TRAIT_VOLUME = PREFIX_TRAITS + "Volume" -PREFIX_COMMANDS = 'action.devices.commands.' -COMMAND_ONOFF = PREFIX_COMMANDS + 'OnOff' -COMMAND_GET_CAMERA_STREAM = PREFIX_COMMANDS + 'GetCameraStream' -COMMAND_DOCK = PREFIX_COMMANDS + 'Dock' -COMMAND_STARTSTOP = PREFIX_COMMANDS + 'StartStop' -COMMAND_PAUSEUNPAUSE = PREFIX_COMMANDS + 'PauseUnpause' -COMMAND_BRIGHTNESS_ABSOLUTE = PREFIX_COMMANDS + 'BrightnessAbsolute' -COMMAND_COLOR_ABSOLUTE = PREFIX_COMMANDS + 'ColorAbsolute' -COMMAND_ACTIVATE_SCENE = PREFIX_COMMANDS + 'ActivateScene' +PREFIX_COMMANDS = "action.devices.commands." +COMMAND_ONOFF = PREFIX_COMMANDS + "OnOff" +COMMAND_GET_CAMERA_STREAM = PREFIX_COMMANDS + "GetCameraStream" +COMMAND_DOCK = PREFIX_COMMANDS + "Dock" +COMMAND_STARTSTOP = PREFIX_COMMANDS + "StartStop" +COMMAND_PAUSEUNPAUSE = PREFIX_COMMANDS + "PauseUnpause" +COMMAND_BRIGHTNESS_ABSOLUTE = PREFIX_COMMANDS + "BrightnessAbsolute" +COMMAND_COLOR_ABSOLUTE = PREFIX_COMMANDS + "ColorAbsolute" +COMMAND_ACTIVATE_SCENE = PREFIX_COMMANDS + "ActivateScene" COMMAND_THERMOSTAT_TEMPERATURE_SETPOINT = ( - PREFIX_COMMANDS + 'ThermostatTemperatureSetpoint') + PREFIX_COMMANDS + "ThermostatTemperatureSetpoint" +) COMMAND_THERMOSTAT_TEMPERATURE_SET_RANGE = ( - PREFIX_COMMANDS + 'ThermostatTemperatureSetRange') -COMMAND_THERMOSTAT_SET_MODE = PREFIX_COMMANDS + 'ThermostatSetMode' -COMMAND_LOCKUNLOCK = PREFIX_COMMANDS + 'LockUnlock' -COMMAND_FANSPEED = PREFIX_COMMANDS + 'SetFanSpeed' -COMMAND_MODES = PREFIX_COMMANDS + 'SetModes' -COMMAND_OPENCLOSE = PREFIX_COMMANDS + 'OpenClose' -COMMAND_SET_VOLUME = PREFIX_COMMANDS + 'setVolume' -COMMAND_VOLUME_RELATIVE = PREFIX_COMMANDS + 'volumeRelative' + PREFIX_COMMANDS + "ThermostatTemperatureSetRange" +) +COMMAND_THERMOSTAT_SET_MODE = PREFIX_COMMANDS + "ThermostatSetMode" +COMMAND_LOCKUNLOCK = PREFIX_COMMANDS + "LockUnlock" +COMMAND_FANSPEED = PREFIX_COMMANDS + "SetFanSpeed" +COMMAND_MODES = PREFIX_COMMANDS + "SetModes" +COMMAND_OPENCLOSE = PREFIX_COMMANDS + "OpenClose" +COMMAND_SET_VOLUME = PREFIX_COMMANDS + "setVolume" +COMMAND_VOLUME_RELATIVE = PREFIX_COMMANDS + "volumeRelative" TRAITS = [] @@ -96,8 +98,8 @@ def register_trait(trait): def _google_temp_unit(units): """Return Google temperature unit.""" if units == TEMP_FAHRENHEIT: - return 'F' - return 'C' + return "F" + return "C" class _Trait: @@ -141,9 +143,7 @@ class BrightnessTrait(_Trait): """ name = TRAIT_BRIGHTNESS - commands = [ - COMMAND_BRIGHTNESS_ABSOLUTE - ] + commands = [COMMAND_BRIGHTNESS_ABSOLUTE] @staticmethod def supported(domain, features, device_class): @@ -165,7 +165,7 @@ class BrightnessTrait(_Trait): if domain == light.DOMAIN: brightness = self.state.attributes.get(light.ATTR_BRIGHTNESS) if brightness is not None: - response['brightness'] = int(100 * (brightness / 255)) + response["brightness"] = int(100 * (brightness / 255)) return response @@ -175,10 +175,15 @@ class BrightnessTrait(_Trait): if domain == light.DOMAIN: await self.hass.services.async_call( - light.DOMAIN, light.SERVICE_TURN_ON, { + light.DOMAIN, + light.SERVICE_TURN_ON, + { ATTR_ENTITY_ID: self.state.entity_id, - light.ATTR_BRIGHTNESS_PCT: params['brightness'] - }, blocking=True, context=data.context) + light.ATTR_BRIGHTNESS_PCT: params["brightness"], + }, + blocking=True, + context=data.context, + ) @register_trait @@ -189,9 +194,7 @@ class CameraStreamTrait(_Trait): """ name = TRAIT_CAMERA_STREAM - commands = [ - COMMAND_GET_CAMERA_STREAM - ] + commands = [COMMAND_GET_CAMERA_STREAM] stream_info = None @@ -206,11 +209,9 @@ class CameraStreamTrait(_Trait): def sync_attributes(self): """Return stream attributes for a sync request.""" return { - 'cameraStreamSupportedProtocols': [ - "hls", - ], - 'cameraStreamNeedAuthToken': False, - 'cameraStreamNeedDrmEncryption': False, + "cameraStreamSupportedProtocols": ["hls"], + "cameraStreamNeedAuthToken": False, + "cameraStreamNeedDrmEncryption": False, } def query_attributes(self): @@ -220,9 +221,10 @@ class CameraStreamTrait(_Trait): async def execute(self, command, data, params, challenge): """Execute a get camera stream command.""" url = await self.hass.components.camera.async_request_stream( - self.state.entity_id, 'hls') + self.state.entity_id, "hls" + ) self.stream_info = { - 'cameraStreamAccessUrl': self.hass.config.api.base_url + url + "cameraStreamAccessUrl": self.hass.config.api.base_url + url } @@ -234,9 +236,7 @@ class OnOffTrait(_Trait): """ name = TRAIT_ONOFF - commands = [ - COMMAND_ONOFF - ] + commands = [COMMAND_ONOFF] @staticmethod def supported(domain, features, device_class): @@ -256,7 +256,7 @@ class OnOffTrait(_Trait): def query_attributes(self): """Return OnOff query attributes.""" - return {'on': self.state.state != STATE_OFF} + return {"on": self.state.state != STATE_OFF} async def execute(self, command, data, params, challenge): """Execute an OnOff command.""" @@ -264,15 +264,19 @@ class OnOffTrait(_Trait): if domain == group.DOMAIN: service_domain = HA_DOMAIN - service = SERVICE_TURN_ON if params['on'] else SERVICE_TURN_OFF + service = SERVICE_TURN_ON if params["on"] else SERVICE_TURN_OFF else: service_domain = domain - service = SERVICE_TURN_ON if params['on'] else SERVICE_TURN_OFF + service = SERVICE_TURN_ON if params["on"] else SERVICE_TURN_OFF - await self.hass.services.async_call(service_domain, service, { - ATTR_ENTITY_ID: self.state.entity_id - }, blocking=True, context=data.context) + await self.hass.services.async_call( + service_domain, + service, + {ATTR_ENTITY_ID: self.state.entity_id}, + blocking=True, + context=data.context, + ) @register_trait @@ -283,9 +287,7 @@ class ColorSettingTrait(_Trait): """ name = TRAIT_COLOR_SETTING - commands = [ - COMMAND_COLOR_ABSOLUTE - ] + commands = [COMMAND_COLOR_ABSOLUTE] @staticmethod def supported(domain, features, device_class): @@ -293,8 +295,7 @@ class ColorSettingTrait(_Trait): if domain != light.DOMAIN: return False - return (features & light.SUPPORT_COLOR_TEMP or - features & light.SUPPORT_COLOR) + return features & light.SUPPORT_COLOR_TEMP or features & light.SUPPORT_COLOR def sync_attributes(self): """Return color temperature attributes for a sync request.""" @@ -303,18 +304,18 @@ class ColorSettingTrait(_Trait): response = {} if features & light.SUPPORT_COLOR: - response['colorModel'] = 'hsv' + response["colorModel"] = "hsv" if features & light.SUPPORT_COLOR_TEMP: # Max Kelvin is Min Mireds K = 1000000 / mireds # Min Kevin is Max Mireds K = 1000000 / mireds - response['colorTemperatureRange'] = { - 'temperatureMaxK': - color_util.color_temperature_mired_to_kelvin( - attrs.get(light.ATTR_MIN_MIREDS)), - 'temperatureMinK': - color_util.color_temperature_mired_to_kelvin( - attrs.get(light.ATTR_MAX_MIREDS)), + response["colorTemperatureRange"] = { + "temperatureMaxK": color_util.color_temperature_mired_to_kelvin( + attrs.get(light.ATTR_MIN_MIREDS) + ), + "temperatureMinK": color_util.color_temperature_mired_to_kelvin( + attrs.get(light.ATTR_MAX_MIREDS) + ), } return response @@ -328,72 +329,89 @@ class ColorSettingTrait(_Trait): color_hs = self.state.attributes.get(light.ATTR_HS_COLOR) brightness = self.state.attributes.get(light.ATTR_BRIGHTNESS, 1) if color_hs is not None: - color['spectrumHsv'] = { - 'hue': color_hs[0], - 'saturation': color_hs[1]/100, - 'value': brightness/255, + color["spectrumHsv"] = { + "hue": color_hs[0], + "saturation": color_hs[1] / 100, + "value": brightness / 255, } if features & light.SUPPORT_COLOR_TEMP: temp = self.state.attributes.get(light.ATTR_COLOR_TEMP) # Some faulty integrations might put 0 in here, raising exception. if temp == 0: - _LOGGER.warning('Entity %s has incorrect color temperature %s', - self.state.entity_id, temp) + _LOGGER.warning( + "Entity %s has incorrect color temperature %s", + self.state.entity_id, + temp, + ) elif temp is not None: - color['temperatureK'] = \ - color_util.color_temperature_mired_to_kelvin(temp) + color["temperatureK"] = color_util.color_temperature_mired_to_kelvin( + temp + ) response = {} if color: - response['color'] = color + response["color"] = color return response async def execute(self, command, data, params, challenge): """Execute a color temperature command.""" - if 'temperature' in params['color']: + if "temperature" in params["color"]: temp = color_util.color_temperature_kelvin_to_mired( - params['color']['temperature']) + params["color"]["temperature"] + ) min_temp = self.state.attributes[light.ATTR_MIN_MIREDS] max_temp = self.state.attributes[light.ATTR_MAX_MIREDS] if temp < min_temp or temp > max_temp: raise SmartHomeError( ERR_VALUE_OUT_OF_RANGE, - "Temperature should be between {} and {}".format(min_temp, - max_temp)) + "Temperature should be between {} and {}".format( + min_temp, max_temp + ), + ) await self.hass.services.async_call( - light.DOMAIN, SERVICE_TURN_ON, { - ATTR_ENTITY_ID: self.state.entity_id, - light.ATTR_COLOR_TEMP: temp, - }, blocking=True, context=data.context) + light.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: self.state.entity_id, light.ATTR_COLOR_TEMP: temp}, + blocking=True, + context=data.context, + ) - elif 'spectrumRGB' in params['color']: + elif "spectrumRGB" in params["color"]: # Convert integer to hex format and left pad with 0's till length 6 - hex_value = "{0:06x}".format(params['color']['spectrumRGB']) + hex_value = "{0:06x}".format(params["color"]["spectrumRGB"]) color = color_util.color_RGB_to_hs( - *color_util.rgb_hex_to_rgb_list(hex_value)) + *color_util.rgb_hex_to_rgb_list(hex_value) + ) await self.hass.services.async_call( - light.DOMAIN, SERVICE_TURN_ON, { - ATTR_ENTITY_ID: self.state.entity_id, - light.ATTR_HS_COLOR: color - }, blocking=True, context=data.context) + light.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: self.state.entity_id, light.ATTR_HS_COLOR: color}, + blocking=True, + context=data.context, + ) - elif 'spectrumHSV' in params['color']: - color = params['color']['spectrumHSV'] - saturation = color['saturation'] * 100 - brightness = color['value'] * 255 + elif "spectrumHSV" in params["color"]: + color = params["color"]["spectrumHSV"] + saturation = color["saturation"] * 100 + brightness = color["value"] * 255 await self.hass.services.async_call( - light.DOMAIN, SERVICE_TURN_ON, { + light.DOMAIN, + SERVICE_TURN_ON, + { ATTR_ENTITY_ID: self.state.entity_id, - light.ATTR_HS_COLOR: [color['hue'], saturation], - light.ATTR_BRIGHTNESS: brightness - }, blocking=True, context=data.context) + light.ATTR_HS_COLOR: [color["hue"], saturation], + light.ATTR_BRIGHTNESS: brightness, + }, + blocking=True, + context=data.context, + ) @register_trait @@ -404,9 +422,7 @@ class SceneTrait(_Trait): """ name = TRAIT_SCENE - commands = [ - COMMAND_ACTIVATE_SCENE - ] + commands = [COMMAND_ACTIVATE_SCENE] @staticmethod def supported(domain, features, device_class): @@ -426,10 +442,12 @@ class SceneTrait(_Trait): """Execute a scene command.""" # Don't block for scripts as they can be slow. await self.hass.services.async_call( - self.state.domain, SERVICE_TURN_ON, { - ATTR_ENTITY_ID: self.state.entity_id - }, blocking=self.state.domain != script.DOMAIN, - context=data.context) + self.state.domain, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: self.state.entity_id}, + blocking=self.state.domain != script.DOMAIN, + context=data.context, + ) @register_trait @@ -440,9 +458,7 @@ class DockTrait(_Trait): """ name = TRAIT_DOCK - commands = [ - COMMAND_DOCK - ] + commands = [COMMAND_DOCK] @staticmethod def supported(domain, features, device_class): @@ -455,14 +471,17 @@ class DockTrait(_Trait): def query_attributes(self): """Return dock query attributes.""" - return {'isDocked': self.state.state == vacuum.STATE_DOCKED} + return {"isDocked": self.state.state == vacuum.STATE_DOCKED} async def execute(self, command, data, params, challenge): """Execute a dock command.""" await self.hass.services.async_call( - self.state.domain, vacuum.SERVICE_RETURN_TO_BASE, { - ATTR_ENTITY_ID: self.state.entity_id - }, blocking=True, context=data.context) + self.state.domain, + vacuum.SERVICE_RETURN_TO_BASE, + {ATTR_ENTITY_ID: self.state.entity_id}, + blocking=True, + context=data.context, + ) @register_trait @@ -473,10 +492,7 @@ class StartStopTrait(_Trait): """ name = TRAIT_STARTSTOP - commands = [ - COMMAND_STARTSTOP, - COMMAND_PAUSEUNPAUSE - ] + commands = [COMMAND_STARTSTOP, COMMAND_PAUSEUNPAUSE] @staticmethod def supported(domain, features, device_class): @@ -485,41 +501,55 @@ class StartStopTrait(_Trait): def sync_attributes(self): """Return StartStop attributes for a sync request.""" - return {'pausable': - self.state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) - & vacuum.SUPPORT_PAUSE != 0} + return { + "pausable": self.state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) + & vacuum.SUPPORT_PAUSE + != 0 + } def query_attributes(self): """Return StartStop query attributes.""" return { - 'isRunning': self.state.state == vacuum.STATE_CLEANING, - 'isPaused': self.state.state == vacuum.STATE_PAUSED, + "isRunning": self.state.state == vacuum.STATE_CLEANING, + "isPaused": self.state.state == vacuum.STATE_PAUSED, } async def execute(self, command, data, params, challenge): """Execute a StartStop command.""" if command == COMMAND_STARTSTOP: - if params['start']: + if params["start"]: await self.hass.services.async_call( - self.state.domain, vacuum.SERVICE_START, { - ATTR_ENTITY_ID: self.state.entity_id - }, blocking=True, context=data.context) + self.state.domain, + vacuum.SERVICE_START, + {ATTR_ENTITY_ID: self.state.entity_id}, + blocking=True, + context=data.context, + ) else: await self.hass.services.async_call( - self.state.domain, vacuum.SERVICE_STOP, { - ATTR_ENTITY_ID: self.state.entity_id - }, blocking=True, context=data.context) + self.state.domain, + vacuum.SERVICE_STOP, + {ATTR_ENTITY_ID: self.state.entity_id}, + blocking=True, + context=data.context, + ) elif command == COMMAND_PAUSEUNPAUSE: - if params['pause']: + if params["pause"]: await self.hass.services.async_call( - self.state.domain, vacuum.SERVICE_PAUSE, { - ATTR_ENTITY_ID: self.state.entity_id - }, blocking=True, context=data.context) + self.state.domain, + vacuum.SERVICE_PAUSE, + {ATTR_ENTITY_ID: self.state.entity_id}, + blocking=True, + context=data.context, + ) else: await self.hass.services.async_call( - self.state.domain, vacuum.SERVICE_START, { - ATTR_ENTITY_ID: self.state.entity_id - }, blocking=True, context=data.context) + self.state.domain, + vacuum.SERVICE_START, + {ATTR_ENTITY_ID: self.state.entity_id}, + blocking=True, + context=data.context, + ) @register_trait @@ -538,19 +568,17 @@ class TemperatureSettingTrait(_Trait): # We do not support "on" as we are unable to know how to restore # the last mode. hvac_to_google = { - climate.HVAC_MODE_HEAT: 'heat', - climate.HVAC_MODE_COOL: 'cool', - climate.HVAC_MODE_OFF: 'off', - climate.HVAC_MODE_AUTO: 'auto', - climate.HVAC_MODE_HEAT_COOL: 'heatcool', - climate.HVAC_MODE_FAN_ONLY: 'fan-only', - climate.HVAC_MODE_DRY: 'dry', + climate.HVAC_MODE_HEAT: "heat", + climate.HVAC_MODE_COOL: "cool", + climate.HVAC_MODE_OFF: "off", + climate.HVAC_MODE_AUTO: "auto", + climate.HVAC_MODE_HEAT_COOL: "heatcool", + climate.HVAC_MODE_FAN_ONLY: "fan-only", + climate.HVAC_MODE_DRY: "dry", } google_to_hvac = {value: key for key, value in hvac_to_google.items()} - preset_to_google = { - climate.PRESET_ECO: 'eco' - } + preset_to_google = {climate.PRESET_ECO: "eco"} google_to_preset = {value: key for key, value in preset_to_google.items()} @staticmethod @@ -559,8 +587,9 @@ class TemperatureSettingTrait(_Trait): if domain == climate.DOMAIN: return True - return (domain == sensor.DOMAIN - and device_class == sensor.DEVICE_CLASS_TEMPERATURE) + return ( + domain == sensor.DOMAIN and device_class == sensor.DEVICE_CLASS_TEMPERATURE + ) @property def climate_google_modes(self): @@ -585,8 +614,9 @@ class TemperatureSettingTrait(_Trait): response = {} attrs = self.state.attributes domain = self.state.domain - response['thermostatTemperatureUnit'] = _google_temp_unit( - self.hass.config.units.temperature_unit) + response["thermostatTemperatureUnit"] = _google_temp_unit( + self.hass.config.units.temperature_unit + ) if domain == sensor.DOMAIN: device_class = attrs.get(ATTR_DEVICE_CLASS) @@ -595,10 +625,11 @@ class TemperatureSettingTrait(_Trait): elif domain == climate.DOMAIN: modes = self.climate_google_modes - if 'off' in modes and any(mode in modes for mode - in ('heatcool', 'heat', 'cool')): - modes.append('on') - response['availableThermostatModes'] = ','.join(modes) + if "off" in modes and any( + mode in modes for mode in ("heatcool", "heat", "cool") + ): + modes.append("on") + response["availableThermostatModes"] = ",".join(modes) return response @@ -613,12 +644,9 @@ class TemperatureSettingTrait(_Trait): if device_class == sensor.DEVICE_CLASS_TEMPERATURE: current_temp = self.state.state if current_temp is not None: - response['thermostatTemperatureAmbient'] = \ - round(temp_util.convert( - float(current_temp), - unit, - TEMP_CELSIUS - ), 1) + response["thermostatTemperatureAmbient"] = round( + temp_util.convert(float(current_temp), unit, TEMP_CELSIUS), 1 + ) elif domain == climate.DOMAIN: operation = self.state.state @@ -626,52 +654,48 @@ class TemperatureSettingTrait(_Trait): supported = attrs.get(ATTR_SUPPORTED_FEATURES, 0) if preset in self.preset_to_google: - response['thermostatMode'] = self.preset_to_google[preset] + response["thermostatMode"] = self.preset_to_google[preset] else: - response['thermostatMode'] = self.hvac_to_google.get(operation) + response["thermostatMode"] = self.hvac_to_google.get(operation) current_temp = attrs.get(climate.ATTR_CURRENT_TEMPERATURE) if current_temp is not None: - response['thermostatTemperatureAmbient'] = \ - round(temp_util.convert( - current_temp, - unit, - TEMP_CELSIUS - ), 1) + response["thermostatTemperatureAmbient"] = round( + temp_util.convert(current_temp, unit, TEMP_CELSIUS), 1 + ) current_humidity = attrs.get(climate.ATTR_CURRENT_HUMIDITY) if current_humidity is not None: - response['thermostatHumidityAmbient'] = current_humidity + response["thermostatHumidityAmbient"] = current_humidity - if operation in (climate.HVAC_MODE_AUTO, - climate.HVAC_MODE_HEAT_COOL): + if operation in (climate.HVAC_MODE_AUTO, climate.HVAC_MODE_HEAT_COOL): if supported & climate.SUPPORT_TARGET_TEMPERATURE_RANGE: - response['thermostatTemperatureSetpointHigh'] = \ - round(temp_util.convert( - attrs[climate.ATTR_TARGET_TEMP_HIGH], - unit, TEMP_CELSIUS), 1) - response['thermostatTemperatureSetpointLow'] = \ - round(temp_util.convert( - attrs[climate.ATTR_TARGET_TEMP_LOW], - unit, TEMP_CELSIUS), 1) + response["thermostatTemperatureSetpointHigh"] = round( + temp_util.convert( + attrs[climate.ATTR_TARGET_TEMP_HIGH], unit, TEMP_CELSIUS + ), + 1, + ) + response["thermostatTemperatureSetpointLow"] = round( + temp_util.convert( + attrs[climate.ATTR_TARGET_TEMP_LOW], unit, TEMP_CELSIUS + ), + 1, + ) else: target_temp = attrs.get(ATTR_TEMPERATURE) if target_temp is not None: target_temp = round( - temp_util.convert( - target_temp, - unit, - TEMP_CELSIUS - ), 1) - response['thermostatTemperatureSetpointHigh'] = \ - target_temp - response['thermostatTemperatureSetpointLow'] = \ - target_temp + temp_util.convert(target_temp, unit, TEMP_CELSIUS), 1 + ) + response["thermostatTemperatureSetpointHigh"] = target_temp + response["thermostatTemperatureSetpointLow"] = target_temp else: target_temp = attrs.get(ATTR_TEMPERATURE) if target_temp is not None: - response['thermostatTemperatureSetpoint'] = round( - temp_util.convert(target_temp, unit, TEMP_CELSIUS), 1) + response["thermostatTemperatureSetpoint"] = round( + temp_util.convert(target_temp, unit, TEMP_CELSIUS), 1 + ) return response @@ -680,8 +704,8 @@ class TemperatureSettingTrait(_Trait): domain = self.state.domain if domain == sensor.DOMAIN: raise SmartHomeError( - ERR_NOT_SUPPORTED, - 'Execute is not supported by sensor') + ERR_NOT_SUPPORTED, "Execute is not supported by sensor" + ) # All sent in temperatures are always in Celsius unit = self.hass.config.units.temperature_unit @@ -690,27 +714,31 @@ class TemperatureSettingTrait(_Trait): if command == COMMAND_THERMOSTAT_TEMPERATURE_SETPOINT: temp = temp_util.convert( - params['thermostatTemperatureSetpoint'], TEMP_CELSIUS, - unit) + params["thermostatTemperatureSetpoint"], TEMP_CELSIUS, unit + ) if unit == TEMP_FAHRENHEIT: temp = round(temp) if temp < min_temp or temp > max_temp: raise SmartHomeError( ERR_VALUE_OUT_OF_RANGE, - "Temperature should be between {} and {}".format(min_temp, - max_temp)) + "Temperature should be between {} and {}".format( + min_temp, max_temp + ), + ) await self.hass.services.async_call( - climate.DOMAIN, climate.SERVICE_SET_TEMPERATURE, { - ATTR_ENTITY_ID: self.state.entity_id, - ATTR_TEMPERATURE: temp - }, blocking=True, context=data.context) + climate.DOMAIN, + climate.SERVICE_SET_TEMPERATURE, + {ATTR_ENTITY_ID: self.state.entity_id, ATTR_TEMPERATURE: temp}, + blocking=True, + context=data.context, + ) elif command == COMMAND_THERMOSTAT_TEMPERATURE_SET_RANGE: temp_high = temp_util.convert( - params['thermostatTemperatureSetpointHigh'], TEMP_CELSIUS, - unit) + params["thermostatTemperatureSetpointHigh"], TEMP_CELSIUS, unit + ) if unit == TEMP_FAHRENHEIT: temp_high = round(temp_high) @@ -718,11 +746,12 @@ class TemperatureSettingTrait(_Trait): raise SmartHomeError( ERR_VALUE_OUT_OF_RANGE, "Upper bound for temperature range should be between " - "{} and {}".format(min_temp, max_temp)) + "{} and {}".format(min_temp, max_temp), + ) temp_low = temp_util.convert( - params['thermostatTemperatureSetpointLow'], TEMP_CELSIUS, - unit) + params["thermostatTemperatureSetpointLow"], TEMP_CELSIUS, unit + ) if unit == TEMP_FAHRENHEIT: temp_low = round(temp_low) @@ -730,12 +759,11 @@ class TemperatureSettingTrait(_Trait): raise SmartHomeError( ERR_VALUE_OUT_OF_RANGE, "Lower bound for temperature range should be between " - "{} and {}".format(min_temp, max_temp)) + "{} and {}".format(min_temp, max_temp), + ) supported = self.state.attributes.get(ATTR_SUPPORTED_FEATURES) - svc_data = { - ATTR_ENTITY_ID: self.state.entity_id, - } + svc_data = {ATTR_ENTITY_ID: self.state.entity_id} if supported & climate.SUPPORT_TARGET_TEMPERATURE_RANGE: svc_data[climate.ATTR_TARGET_TEMP_HIGH] = temp_high @@ -744,51 +772,60 @@ class TemperatureSettingTrait(_Trait): svc_data[ATTR_TEMPERATURE] = (temp_high + temp_low) / 2 await self.hass.services.async_call( - climate.DOMAIN, climate.SERVICE_SET_TEMPERATURE, svc_data, - blocking=True, context=data.context) + climate.DOMAIN, + climate.SERVICE_SET_TEMPERATURE, + svc_data, + blocking=True, + context=data.context, + ) elif command == COMMAND_THERMOSTAT_SET_MODE: - target_mode = params['thermostatMode'] + target_mode = params["thermostatMode"] supported = self.state.attributes.get(ATTR_SUPPORTED_FEATURES) - if target_mode == 'on': + if target_mode == "on": await self.hass.services.async_call( - climate.DOMAIN, SERVICE_TURN_ON, - { - ATTR_ENTITY_ID: self.state.entity_id - }, - blocking=True, context=data.context + climate.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: self.state.entity_id}, + blocking=True, + context=data.context, ) return - if target_mode == 'off': + if target_mode == "off": await self.hass.services.async_call( - climate.DOMAIN, SERVICE_TURN_OFF, - { - ATTR_ENTITY_ID: self.state.entity_id - }, - blocking=True, context=data.context + climate.DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: self.state.entity_id}, + blocking=True, + context=data.context, ) return if target_mode in self.google_to_preset: await self.hass.services.async_call( - climate.DOMAIN, climate.SERVICE_SET_PRESET_MODE, + climate.DOMAIN, + climate.SERVICE_SET_PRESET_MODE, { - climate.ATTR_PRESET_MODE: - self.google_to_preset[target_mode], - ATTR_ENTITY_ID: self.state.entity_id + climate.ATTR_PRESET_MODE: self.google_to_preset[target_mode], + ATTR_ENTITY_ID: self.state.entity_id, }, - blocking=True, context=data.context + blocking=True, + context=data.context, ) return await self.hass.services.async_call( - climate.DOMAIN, climate.SERVICE_SET_HVAC_MODE, { + climate.DOMAIN, + climate.SERVICE_SET_HVAC_MODE, + { ATTR_ENTITY_ID: self.state.entity_id, - climate.ATTR_HVAC_MODE: - self.google_to_hvac[target_mode], - }, blocking=True, context=data.context) + climate.ATTR_HVAC_MODE: self.google_to_hvac[target_mode], + }, + blocking=True, + context=data.context, + ) @register_trait @@ -799,9 +836,7 @@ class LockUnlockTrait(_Trait): """ name = TRAIT_LOCKUNLOCK - commands = [ - COMMAND_LOCKUNLOCK - ] + commands = [COMMAND_LOCKUNLOCK] @staticmethod def supported(domain, features, device_class): @@ -819,19 +854,23 @@ class LockUnlockTrait(_Trait): def query_attributes(self): """Return LockUnlock query attributes.""" - return {'isLocked': self.state.state == STATE_LOCKED} + return {"isLocked": self.state.state == STATE_LOCKED} async def execute(self, command, data, params, challenge): """Execute an LockUnlock command.""" - if params['lock']: + if params["lock"]: service = lock.SERVICE_LOCK else: _verify_pin_challenge(data, self.state, challenge) service = lock.SERVICE_UNLOCK - await self.hass.services.async_call(lock.DOMAIN, service, { - ATTR_ENTITY_ID: self.state.entity_id - }, blocking=True, context=data.context) + await self.hass.services.async_call( + lock.DOMAIN, + service, + {ATTR_ENTITY_ID: self.state.entity_id}, + blocking=True, + context=data.context, + ) @register_trait @@ -842,17 +881,13 @@ class FanSpeedTrait(_Trait): """ name = TRAIT_FANSPEED - commands = [ - COMMAND_FANSPEED - ] + commands = [COMMAND_FANSPEED] speed_synonyms = { - fan.SPEED_OFF: ['stop', 'off'], - fan.SPEED_LOW: ['slow', 'low', 'slowest', 'lowest'], - fan.SPEED_MEDIUM: ['medium', 'mid', 'middle'], - fan.SPEED_HIGH: [ - 'high', 'max', 'fast', 'highest', 'fastest', 'maximum' - ] + fan.SPEED_OFF: ["stop", "off"], + fan.SPEED_LOW: ["slow", "low", "slowest", "lowest"], + fan.SPEED_MEDIUM: ["medium", "mid", "middle"], + fan.SPEED_HIGH: ["high", "max", "fast", "highest", "fastest", "maximum"], } @staticmethod @@ -872,20 +907,18 @@ class FanSpeedTrait(_Trait): continue speed = { "speed_name": mode, - "speed_values": [{ - "speed_synonym": self.speed_synonyms.get(mode), - "lang": 'en' - }] + "speed_values": [ + {"speed_synonym": self.speed_synonyms.get(mode), "lang": "en"} + ], } speeds.append(speed) return { - 'availableFanSpeeds': { - 'speeds': speeds, - 'ordered': True - }, - "reversible": bool(self.state.attributes.get( - ATTR_SUPPORTED_FEATURES, 0) & fan.SUPPORT_DIRECTION) + "availableFanSpeeds": {"speeds": speeds, "ordered": True}, + "reversible": bool( + self.state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) + & fan.SUPPORT_DIRECTION + ), } def query_attributes(self): @@ -895,19 +928,21 @@ class FanSpeedTrait(_Trait): speed = attrs.get(fan.ATTR_SPEED) if speed is not None: - response['on'] = speed != fan.SPEED_OFF - response['online'] = True - response['currentFanSpeedSetting'] = speed + response["on"] = speed != fan.SPEED_OFF + response["online"] = True + response["currentFanSpeedSetting"] = speed return response async def execute(self, command, data, params, challenge): """Execute an SetFanSpeed command.""" await self.hass.services.async_call( - fan.DOMAIN, fan.SERVICE_SET_SPEED, { - ATTR_ENTITY_ID: self.state.entity_id, - fan.ATTR_SPEED: params['fanSpeed'] - }, blocking=True, context=data.context) + fan.DOMAIN, + fan.SERVICE_SET_SPEED, + {ATTR_ENTITY_ID: self.state.entity_id, fan.ATTR_SPEED: params["fanSpeed"]}, + blocking=True, + context=data.context, + ) @register_trait @@ -918,92 +953,100 @@ class ModesTrait(_Trait): """ name = TRAIT_MODES - commands = [ - COMMAND_MODES - ] + commands = [COMMAND_MODES] # Google requires specific mode names and settings. Here is the full list. # https://developers.google.com/actions/reference/smarthome/traits/modes # All settings are mapped here as of 2018-11-28 and can be used for other # entity types. - HA_TO_GOOGLE = { - media_player.ATTR_INPUT_SOURCE: "input source", - } + HA_TO_GOOGLE = {media_player.ATTR_INPUT_SOURCE: "input source"} SUPPORTED_MODE_SETTINGS = { - 'xsmall': [ - 'xsmall', 'extra small', 'min', 'minimum', 'tiny', 'xs'], - 'small': ['small', 'half'], - 'large': ['large', 'big', 'full'], - 'xlarge': ['extra large', 'xlarge', 'xl'], - 'Cool': ['cool', 'rapid cool', 'rapid cooling'], - 'Heat': ['heat'], 'Low': ['low'], - 'Medium': ['medium', 'med', 'mid', 'half'], - 'High': ['high'], - 'Auto': ['auto', 'automatic'], - 'Bake': ['bake'], 'Roast': ['roast'], - 'Convection Bake': ['convection bake', 'convect bake'], - 'Convection Roast': ['convection roast', 'convect roast'], - 'Favorite': ['favorite'], - 'Broil': ['broil'], - 'Warm': ['warm'], - 'Off': ['off'], - 'On': ['on'], - 'Normal': [ - 'normal', 'normal mode', 'normal setting', 'standard', - 'schedule', 'original', 'default', 'old settings' + "xsmall": ["xsmall", "extra small", "min", "minimum", "tiny", "xs"], + "small": ["small", "half"], + "large": ["large", "big", "full"], + "xlarge": ["extra large", "xlarge", "xl"], + "Cool": ["cool", "rapid cool", "rapid cooling"], + "Heat": ["heat"], + "Low": ["low"], + "Medium": ["medium", "med", "mid", "half"], + "High": ["high"], + "Auto": ["auto", "automatic"], + "Bake": ["bake"], + "Roast": ["roast"], + "Convection Bake": ["convection bake", "convect bake"], + "Convection Roast": ["convection roast", "convect roast"], + "Favorite": ["favorite"], + "Broil": ["broil"], + "Warm": ["warm"], + "Off": ["off"], + "On": ["on"], + "Normal": [ + "normal", + "normal mode", + "normal setting", + "standard", + "schedule", + "original", + "default", + "old settings", ], - 'None': ['none'], - 'Tap Cold': ['tap cold'], - 'Cold Warm': ['cold warm'], - 'Hot': ['hot'], - 'Extra Hot': ['extra hot'], - 'Eco': ['eco'], - 'Wool': ['wool', 'fleece'], - 'Turbo': ['turbo'], - 'Rinse': ['rinse', 'rinsing', 'rinse wash'], - 'Away': ['away', 'holiday'], - 'maximum': ['maximum'], - 'media player': ['media player'], - 'chromecast': ['chromecast'], - 'tv': [ - 'tv', 'television', 'tv position', 'television position', - 'watching tv', 'watching tv position', 'entertainment', - 'entertainment position' + "None": ["none"], + "Tap Cold": ["tap cold"], + "Cold Warm": ["cold warm"], + "Hot": ["hot"], + "Extra Hot": ["extra hot"], + "Eco": ["eco"], + "Wool": ["wool", "fleece"], + "Turbo": ["turbo"], + "Rinse": ["rinse", "rinsing", "rinse wash"], + "Away": ["away", "holiday"], + "maximum": ["maximum"], + "media player": ["media player"], + "chromecast": ["chromecast"], + "tv": [ + "tv", + "television", + "tv position", + "television position", + "watching tv", + "watching tv position", + "entertainment", + "entertainment position", ], - 'am fm': ['am fm', 'am radio', 'fm radio'], - 'internet radio': ['internet radio'], - 'satellite': ['satellite'], - 'game console': ['game console'], - 'antifrost': ['antifrost', 'anti-frost'], - 'boost': ['boost'], - 'Clock': ['clock'], - 'Message': ['message'], - 'Messages': ['messages'], - 'News': ['news'], - 'Disco': ['disco'], - 'antifreeze': ['antifreeze', 'anti-freeze', 'anti freeze'], - 'balanced': ['balanced', 'normal'], - 'swing': ['swing'], - 'media': ['media', 'media mode'], - 'panic': ['panic'], - 'ring': ['ring'], - 'frozen': ['frozen', 'rapid frozen', 'rapid freeze'], - 'cotton': ['cotton', 'cottons'], - 'blend': ['blend', 'mix'], - 'baby wash': ['baby wash'], - 'synthetics': ['synthetic', 'synthetics', 'compose'], - 'hygiene': ['hygiene', 'sterilization'], - 'smart': ['smart', 'intelligent', 'intelligence'], - 'comfortable': ['comfortable', 'comfort'], - 'manual': ['manual'], - 'energy saving': ['energy saving'], - 'sleep': ['sleep'], - 'quick wash': ['quick wash', 'fast wash'], - 'cold': ['cold'], - 'airsupply': ['airsupply', 'air supply'], - 'dehumidification': ['dehumidication', 'dehumidify'], - 'game': ['game', 'game mode'] + "am fm": ["am fm", "am radio", "fm radio"], + "internet radio": ["internet radio"], + "satellite": ["satellite"], + "game console": ["game console"], + "antifrost": ["antifrost", "anti-frost"], + "boost": ["boost"], + "Clock": ["clock"], + "Message": ["message"], + "Messages": ["messages"], + "News": ["news"], + "Disco": ["disco"], + "antifreeze": ["antifreeze", "anti-freeze", "anti freeze"], + "balanced": ["balanced", "normal"], + "swing": ["swing"], + "media": ["media", "media mode"], + "panic": ["panic"], + "ring": ["ring"], + "frozen": ["frozen", "rapid frozen", "rapid freeze"], + "cotton": ["cotton", "cottons"], + "blend": ["blend", "mix"], + "baby wash": ["baby wash"], + "synthetics": ["synthetic", "synthetics", "compose"], + "hygiene": ["hygiene", "sterilization"], + "smart": ["smart", "intelligent", "intelligence"], + "comfortable": ["comfortable", "comfort"], + "manual": ["manual"], + "energy saving": ["energy saving"], + "sleep": ["sleep"], + "quick wash": ["quick wash", "fast wash"], + "cold": ["cold"], + "airsupply": ["airsupply", "air supply"], + "dehumidification": ["dehumidication", "dehumidify"], + "game": ["game", "game mode"], } @staticmethod @@ -1017,19 +1060,17 @@ class ModesTrait(_Trait): def sync_attributes(self): """Return mode attributes for a sync request.""" sources_list = self.state.attributes.get( - media_player.ATTR_INPUT_SOURCE_LIST, []) + media_player.ATTR_INPUT_SOURCE_LIST, [] + ) modes = [] sources = {} if sources_list: sources = { "name": self.HA_TO_GOOGLE.get(media_player.ATTR_INPUT_SOURCE), - "name_values": [{ - "name_synonym": ['input source'], - "lang": "en" - }], + "name_values": [{"name_synonym": ["input source"], "lang": "en"}], "settings": [], - "ordered": False + "ordered": False, } for source in sources_list: if source in self.SUPPORTED_MODE_SETTINGS: @@ -1042,18 +1083,15 @@ class ModesTrait(_Trait): else: continue - sources['settings'].append( + sources["settings"].append( { "setting_name": src, - "setting_values": [{ - "setting_synonym": synonyms, - "lang": "en" - }] + "setting_values": [{"setting_synonym": synonyms, "lang": "en"}], } ) if sources: modes.append(sources) - payload = {'availableModes': modes} + payload = {"availableModes": modes} return payload @@ -1064,35 +1102,42 @@ class ModesTrait(_Trait): mode_settings = {} if attrs.get(media_player.ATTR_INPUT_SOURCE_LIST): - mode_settings.update({ - media_player.ATTR_INPUT_SOURCE: attrs.get( - media_player.ATTR_INPUT_SOURCE) - }) + mode_settings.update( + { + media_player.ATTR_INPUT_SOURCE: attrs.get( + media_player.ATTR_INPUT_SOURCE + ) + } + ) if mode_settings: - response['on'] = self.state.state != STATE_OFF - response['online'] = True - response['currentModeSettings'] = mode_settings + response["on"] = self.state.state != STATE_OFF + response["online"] = True + response["currentModeSettings"] = mode_settings return response async def execute(self, command, data, params, challenge): """Execute an SetModes command.""" - settings = params.get('updateModeSettings') + settings = params.get("updateModeSettings") requested_source = settings.get( - self.HA_TO_GOOGLE.get(media_player.ATTR_INPUT_SOURCE)) + self.HA_TO_GOOGLE.get(media_player.ATTR_INPUT_SOURCE) + ) if requested_source: - for src in self.state.attributes.get( - media_player.ATTR_INPUT_SOURCE_LIST): + for src in self.state.attributes.get(media_player.ATTR_INPUT_SOURCE_LIST): if src.lower() == requested_source.lower(): source = src await self.hass.services.async_call( media_player.DOMAIN, - media_player.SERVICE_SELECT_SOURCE, { + media_player.SERVICE_SELECT_SOURCE, + { ATTR_ENTITY_ID: self.state.entity_id, - media_player.ATTR_INPUT_SOURCE: source - }, blocking=True, context=data.context) + media_player.ATTR_INPUT_SOURCE: source, + }, + blocking=True, + context=data.context, + ) @register_trait @@ -1106,9 +1151,7 @@ class OpenCloseTrait(_Trait): COVER_2FA = (cover.DEVICE_CLASS_DOOR, cover.DEVICE_CLASS_GARAGE) name = TRAIT_OPENCLOSE - commands = [ - COMMAND_OPENCLOSE - ] + commands = [COMMAND_OPENCLOSE] override_position = None @@ -1129,14 +1172,13 @@ class OpenCloseTrait(_Trait): @staticmethod def might_2fa(domain, features, device_class): """Return if the trait might ask for 2FA.""" - return (domain == cover.DOMAIN and - device_class in OpenCloseTrait.COVER_2FA) + return domain == cover.DOMAIN and device_class in OpenCloseTrait.COVER_2FA def sync_attributes(self): """Return opening direction.""" response = {} if self.state.domain == binary_sensor.DOMAIN: - response['queryOnlyOpenClose'] = True + response["queryOnlyOpenClose"] = True return response def query_attributes(self): @@ -1145,37 +1187,37 @@ class OpenCloseTrait(_Trait): response = {} if self.override_position is not None: - response['openPercent'] = self.override_position + response["openPercent"] = self.override_position elif domain == cover.DOMAIN: # When it's an assumed state, we will return that querying state # is not supported. if self.state.attributes.get(ATTR_ASSUMED_STATE): raise SmartHomeError( - ERR_NOT_SUPPORTED, - 'Querying state is not supported') + ERR_NOT_SUPPORTED, "Querying state is not supported" + ) if self.state.state == STATE_UNKNOWN: raise SmartHomeError( - ERR_NOT_SUPPORTED, - 'Querying state is not supported') + ERR_NOT_SUPPORTED, "Querying state is not supported" + ) position = self.override_position or self.state.attributes.get( cover.ATTR_CURRENT_POSITION ) if position is not None: - response['openPercent'] = position + response["openPercent"] = position elif self.state.state != cover.STATE_CLOSED: - response['openPercent'] = 100 + response["openPercent"] = 100 else: - response['openPercent'] = 0 + response["openPercent"] = 0 elif domain == binary_sensor.DOMAIN: if self.state.state == STATE_ON: - response['openPercent'] = 100 + response["openPercent"] = 100 else: - response['openPercent'] = 0 + response["openPercent"] = 0 return response @@ -1186,34 +1228,40 @@ class OpenCloseTrait(_Trait): if domain == cover.DOMAIN: svc_params = {ATTR_ENTITY_ID: self.state.entity_id} - if params['openPercent'] == 0: + if params["openPercent"] == 0: service = cover.SERVICE_CLOSE_COVER should_verify = False - elif params['openPercent'] == 100: + elif params["openPercent"] == 100: service = cover.SERVICE_OPEN_COVER should_verify = True - elif (self.state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) & - cover.SUPPORT_SET_POSITION): + elif ( + self.state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) + & cover.SUPPORT_SET_POSITION + ): service = cover.SERVICE_SET_COVER_POSITION should_verify = True - svc_params[cover.ATTR_POSITION] = params['openPercent'] + svc_params[cover.ATTR_POSITION] = params["openPercent"] else: raise SmartHomeError( - ERR_FUNCTION_NOT_SUPPORTED, - 'Setting a position is not supported') + ERR_FUNCTION_NOT_SUPPORTED, "Setting a position is not supported" + ) - if (should_verify and - self.state.attributes.get(ATTR_DEVICE_CLASS) - in OpenCloseTrait.COVER_2FA): + if ( + should_verify + and self.state.attributes.get(ATTR_DEVICE_CLASS) + in OpenCloseTrait.COVER_2FA + ): _verify_pin_challenge(data, self.state, challenge) await self.hass.services.async_call( - cover.DOMAIN, service, svc_params, - blocking=True, context=data.context) + cover.DOMAIN, service, svc_params, blocking=True, context=data.context + ) - if (self.state.attributes.get(ATTR_ASSUMED_STATE) or - self.state.state == STATE_UNKNOWN): - self.override_position = params['openPercent'] + if ( + self.state.attributes.get(ATTR_ASSUMED_STATE) + or self.state.state == STATE_UNKNOWN + ): + self.override_position = params["openPercent"] @register_trait @@ -1224,10 +1272,7 @@ class VolumeTrait(_Trait): """ name = TRAIT_VOLUME - commands = [ - COMMAND_SET_VOLUME, - COMMAND_VOLUME_RELATIVE, - ] + commands = [COMMAND_SET_VOLUME, COMMAND_VOLUME_RELATIVE] @staticmethod def supported(domain, features, device_class): @@ -1245,40 +1290,44 @@ class VolumeTrait(_Trait): """Return brightness query attributes.""" response = {} - level = self.state.attributes.get( - media_player.ATTR_MEDIA_VOLUME_LEVEL) - muted = self.state.attributes.get( - media_player.ATTR_MEDIA_VOLUME_MUTED) + level = self.state.attributes.get(media_player.ATTR_MEDIA_VOLUME_LEVEL) + muted = self.state.attributes.get(media_player.ATTR_MEDIA_VOLUME_MUTED) if level is not None: # Convert 0.0-1.0 to 0-100 - response['currentVolume'] = int(level * 100) - response['isMuted'] = bool(muted) + response["currentVolume"] = int(level * 100) + response["isMuted"] = bool(muted) return response async def _execute_set_volume(self, data, params): - level = params['volumeLevel'] + level = params["volumeLevel"] await self.hass.services.async_call( media_player.DOMAIN, - media_player.SERVICE_VOLUME_SET, { + media_player.SERVICE_VOLUME_SET, + { ATTR_ENTITY_ID: self.state.entity_id, - media_player.ATTR_MEDIA_VOLUME_LEVEL: - level / 100 - }, blocking=True, context=data.context) + media_player.ATTR_MEDIA_VOLUME_LEVEL: level / 100, + }, + blocking=True, + context=data.context, + ) async def _execute_volume_relative(self, data, params): # This could also support up/down commands using relativeSteps - relative = params['volumeRelativeLevel'] - current = self.state.attributes.get( - media_player.ATTR_MEDIA_VOLUME_LEVEL) + relative = params["volumeRelativeLevel"] + current = self.state.attributes.get(media_player.ATTR_MEDIA_VOLUME_LEVEL) await self.hass.services.async_call( - media_player.DOMAIN, media_player.SERVICE_VOLUME_SET, { + media_player.DOMAIN, + media_player.SERVICE_VOLUME_SET, + { ATTR_ENTITY_ID: self.state.entity_id, - media_player.ATTR_MEDIA_VOLUME_LEVEL: - current + relative / 100 - }, blocking=True, context=data.context) + media_player.ATTR_MEDIA_VOLUME_LEVEL: current + relative / 100, + }, + blocking=True, + context=data.context, + ) async def execute(self, command, data, params, challenge): """Execute a brightness command.""" @@ -1287,8 +1336,7 @@ class VolumeTrait(_Trait): elif command == COMMAND_VOLUME_RELATIVE: await self._execute_volume_relative(data, params) else: - raise SmartHomeError( - ERR_NOT_SUPPORTED, 'Command not supported') + raise SmartHomeError(ERR_NOT_SUPPORTED, "Command not supported") def _verify_pin_challenge(data, state, challenge): @@ -1297,13 +1345,12 @@ def _verify_pin_challenge(data, state, challenge): return if not data.config.secure_devices_pin: - raise SmartHomeError( - ERR_CHALLENGE_NOT_SETUP, 'Challenge is not set up') + raise SmartHomeError(ERR_CHALLENGE_NOT_SETUP, "Challenge is not set up") if not challenge: raise ChallengeNeeded(CHALLENGE_PIN_NEEDED) - pin = challenge.get('pin') + pin = challenge.get("pin") if pin != data.config.secure_devices_pin: raise ChallengeNeeded(CHALLENGE_FAILED_PIN_NEEDED) @@ -1311,5 +1358,5 @@ def _verify_pin_challenge(data, state, challenge): def _verify_ack_challenge(data, state, challenge): """Verify a pin challenge.""" - if not challenge or not challenge.get('ack'): + if not challenge or not challenge.get("ack"): raise ChallengeNeeded(CHALLENGE_ACK_NEEDED) diff --git a/homeassistant/components/google_cloud/tts.py b/homeassistant/components/google_cloud/tts.py index 696d4da3223..54c5806101b 100644 --- a/homeassistant/components/google_cloud/tts.py +++ b/homeassistant/components/google_cloud/tts.py @@ -12,29 +12,55 @@ import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -CONF_KEY_FILE = 'key_file' -CONF_GENDER = 'gender' -CONF_VOICE = 'voice' -CONF_ENCODING = 'encoding' -CONF_SPEED = 'speed' -CONF_PITCH = 'pitch' -CONF_GAIN = 'gain' -CONF_PROFILES = 'profiles' +CONF_KEY_FILE = "key_file" +CONF_GENDER = "gender" +CONF_VOICE = "voice" +CONF_ENCODING = "encoding" +CONF_SPEED = "speed" +CONF_PITCH = "pitch" +CONF_GAIN = "gain" +CONF_PROFILES = "profiles" SUPPORTED_LANGUAGES = [ - 'cs-CZ', 'da-DK', 'de-DE', 'el-GR', 'en-AU', 'en-GB', 'en-IN', 'en-US', - 'es-ES', 'fi-FI', 'fil-PH', 'fr-CA', 'fr-FR', 'hi-IN', 'hu-HU', 'id-ID', - 'it-IT', 'ja-JP', 'ko-KR', 'nb-NO', 'nl-NL', 'pl-PL', 'pt-BR', 'pt-PT', - 'ru-RU', 'sk-SK', 'sv-SE', 'tr-TR', 'uk-UA', 'vi-VN', + "cs-CZ", + "da-DK", + "de-DE", + "el-GR", + "en-AU", + "en-GB", + "en-IN", + "en-US", + "es-ES", + "fi-FI", + "fil-PH", + "fr-CA", + "fr-FR", + "hi-IN", + "hu-HU", + "id-ID", + "it-IT", + "ja-JP", + "ko-KR", + "nb-NO", + "nl-NL", + "pl-PL", + "pt-BR", + "pt-PT", + "ru-RU", + "sk-SK", + "sv-SE", + "tr-TR", + "uk-UA", + "vi-VN", ] -DEFAULT_LANG = 'en-US' +DEFAULT_LANG = "en-US" -DEFAULT_GENDER = 'NEUTRAL' +DEFAULT_GENDER = "NEUTRAL" -VOICE_REGEX = r'[a-z]{2,3}-[A-Z]{2}-(Standard|Wavenet)-[A-Z]|' -DEFAULT_VOICE = '' +VOICE_REGEX = r"[a-z]{2,3}-[A-Z]{2}-(Standard|Wavenet)-[A-Z]|" +DEFAULT_VOICE = "" -DEFAULT_ENCODING = 'MP3' +DEFAULT_ENCODING = "MP3" MIN_SPEED = 0.25 MAX_SPEED = 4.0 @@ -70,42 +96,30 @@ SUPPORTED_OPTIONS = [ ] GENDER_SCHEMA = vol.All( - vol.Upper, - vol.In(texttospeech.enums.SsmlVoiceGender.__members__) + vol.Upper, vol.In(texttospeech.enums.SsmlVoiceGender.__members__) ) VOICE_SCHEMA = cv.matches_regex(VOICE_REGEX) SCHEMA_ENCODING = vol.All( - vol.Upper, - vol.In(texttospeech.enums.AudioEncoding.__members__) -) -SPEED_SCHEMA = vol.All( - vol.Coerce(float), - vol.Clamp(min=MIN_SPEED, max=MAX_SPEED) -) -PITCH_SCHEMA = vol.All( - vol.Coerce(float), - vol.Clamp(min=MIN_PITCH, max=MAX_PITCH) -) -GAIN_SCHEMA = vol.All( - vol.Coerce(float), - vol.Clamp(min=MIN_GAIN, max=MAX_GAIN) -) -PROFILES_SCHEMA = vol.All( - cv.ensure_list, - [vol.In(SUPPORTED_PROFILES)] + vol.Upper, vol.In(texttospeech.enums.AudioEncoding.__members__) ) +SPEED_SCHEMA = vol.All(vol.Coerce(float), vol.Clamp(min=MIN_SPEED, max=MAX_SPEED)) +PITCH_SCHEMA = vol.All(vol.Coerce(float), vol.Clamp(min=MIN_PITCH, max=MAX_PITCH)) +GAIN_SCHEMA = vol.All(vol.Coerce(float), vol.Clamp(min=MIN_GAIN, max=MAX_GAIN)) +PROFILES_SCHEMA = vol.All(cv.ensure_list, [vol.In(SUPPORTED_PROFILES)]) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_KEY_FILE): cv.string, - vol.Optional(CONF_LANG, default=DEFAULT_LANG): vol.In(SUPPORTED_LANGUAGES), - vol.Optional(CONF_GENDER, default=DEFAULT_GENDER): GENDER_SCHEMA, - vol.Optional(CONF_VOICE, default=DEFAULT_VOICE): VOICE_SCHEMA, - vol.Optional(CONF_ENCODING, default=DEFAULT_ENCODING): SCHEMA_ENCODING, - vol.Optional(CONF_SPEED, default=DEFAULT_SPEED): SPEED_SCHEMA, - vol.Optional(CONF_PITCH, default=DEFAULT_PITCH): PITCH_SCHEMA, - vol.Optional(CONF_GAIN, default=DEFAULT_GAIN): GAIN_SCHEMA, - vol.Optional(CONF_PROFILES, default=[]): PROFILES_SCHEMA, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_KEY_FILE): cv.string, + vol.Optional(CONF_LANG, default=DEFAULT_LANG): vol.In(SUPPORTED_LANGUAGES), + vol.Optional(CONF_GENDER, default=DEFAULT_GENDER): GENDER_SCHEMA, + vol.Optional(CONF_VOICE, default=DEFAULT_VOICE): VOICE_SCHEMA, + vol.Optional(CONF_ENCODING, default=DEFAULT_ENCODING): SCHEMA_ENCODING, + vol.Optional(CONF_SPEED, default=DEFAULT_SPEED): SPEED_SCHEMA, + vol.Optional(CONF_PITCH, default=DEFAULT_PITCH): PITCH_SCHEMA, + vol.Optional(CONF_GAIN, default=DEFAULT_GAIN): GAIN_SCHEMA, + vol.Optional(CONF_PROFILES, default=[]): PROFILES_SCHEMA, + } +) async def async_get_engine(hass, config): @@ -127,7 +141,7 @@ async def async_get_engine(hass, config): config.get(CONF_SPEED), config.get(CONF_PITCH), config.get(CONF_GAIN), - config.get(CONF_PROFILES) + config.get(CONF_PROFILES), ) @@ -135,21 +149,21 @@ class GoogleCloudTTSProvider(Provider): """The Google Cloud TTS API provider.""" def __init__( - self, - hass, - key_file=None, - language=DEFAULT_LANG, - gender=DEFAULT_GENDER, - voice=DEFAULT_VOICE, - encoding=DEFAULT_ENCODING, - speed=1.0, - pitch=0, - gain=0, - profiles=None + self, + hass, + key_file=None, + language=DEFAULT_LANG, + gender=DEFAULT_GENDER, + voice=DEFAULT_VOICE, + encoding=DEFAULT_ENCODING, + speed=1.0, + pitch=0, + gain=0, + profiles=None, ): """Init Google Cloud TTS service.""" self.hass = hass - self.name = 'Google Cloud TTS' + self.name = "Google Cloud TTS" self._language = language self._gender = gender self._voice = voice @@ -160,8 +174,9 @@ class GoogleCloudTTSProvider(Provider): self._profiles = profiles if key_file: - self._client = texttospeech \ - .TextToSpeechClient.from_service_account_json(key_file) + self._client = texttospeech.TextToSpeechClient.from_service_account_json( + key_file + ) else: self._client = texttospeech.TextToSpeechClient() @@ -190,21 +205,22 @@ class GoogleCloudTTSProvider(Provider): CONF_SPEED: self._speed, CONF_PITCH: self._pitch, CONF_GAIN: self._gain, - CONF_PROFILES: self._profiles + CONF_PROFILES: self._profiles, } async def async_get_tts_audio(self, message, language, options=None): """Load TTS from google.""" - options_schema = vol.Schema({ - vol.Optional(CONF_GENDER, default=self._gender): GENDER_SCHEMA, - vol.Optional(CONF_VOICE, default=self._voice): VOICE_SCHEMA, - vol.Optional(CONF_ENCODING, default=DEFAULT_ENCODING): - SCHEMA_ENCODING, - vol.Optional(CONF_SPEED, default=self._speed): SPEED_SCHEMA, - vol.Optional(CONF_PITCH, default=self._speed): SPEED_SCHEMA, - vol.Optional(CONF_GAIN, default=DEFAULT_GAIN): GAIN_SCHEMA, - vol.Optional(CONF_PROFILES, default=[]): PROFILES_SCHEMA, - }) + options_schema = vol.Schema( + { + vol.Optional(CONF_GENDER, default=self._gender): GENDER_SCHEMA, + vol.Optional(CONF_VOICE, default=self._voice): VOICE_SCHEMA, + vol.Optional(CONF_ENCODING, default=DEFAULT_ENCODING): SCHEMA_ENCODING, + vol.Optional(CONF_SPEED, default=self._speed): SPEED_SCHEMA, + vol.Optional(CONF_PITCH, default=self._speed): SPEED_SCHEMA, + vol.Optional(CONF_GAIN, default=DEFAULT_GAIN): GAIN_SCHEMA, + vol.Optional(CONF_PROFILES, default=[]): PROFILES_SCHEMA, + } + ) options = options_schema(options) _encoding = options[CONF_ENCODING] @@ -214,16 +230,12 @@ class GoogleCloudTTSProvider(Provider): try: # pylint: disable=no-member - synthesis_input = texttospeech.types.SynthesisInput( - text=message - ) + synthesis_input = texttospeech.types.SynthesisInput(text=message) voice = texttospeech.types.VoiceSelectionParams( language_code=language, - ssml_gender=texttospeech.enums.SsmlVoiceGender[ - options[CONF_GENDER] - ], - name=_voice + ssml_gender=texttospeech.enums.SsmlVoiceGender[options[CONF_GENDER]], + name=_voice, ) audio_config = texttospeech.types.AudioConfig( @@ -237,18 +249,13 @@ class GoogleCloudTTSProvider(Provider): with async_timeout.timeout(10, loop=self.hass.loop): response = await self.hass.async_add_executor_job( - self._client.synthesize_speech, - synthesis_input, - voice, - audio_config + self._client.synthesize_speech, synthesis_input, voice, audio_config ) return _encoding, response.audio_content except asyncio.TimeoutError as ex: _LOGGER.error("Timeout for Google Cloud TTS call: %s", ex) except Exception as ex: # pylint: disable=broad-except - _LOGGER.exception( - "Error occured during Google Cloud TTS call: %s", ex - ) + _LOGGER.exception("Error occured during Google Cloud TTS call: %s", ex) return None, None diff --git a/homeassistant/components/google_domains/__init__.py b/homeassistant/components/google_domains/__init__.py index 2d3736d2ec3..8f975db6fd8 100644 --- a/homeassistant/components/google_domains/__init__.py +++ b/homeassistant/components/google_domains/__init__.py @@ -8,27 +8,31 @@ import async_timeout import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.const import ( - CONF_DOMAIN, CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME) +from homeassistant.const import CONF_DOMAIN, CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME _LOGGER = logging.getLogger(__name__) -DOMAIN = 'google_domains' +DOMAIN = "google_domains" INTERVAL = timedelta(minutes=5) DEFAULT_TIMEOUT = 10 -UPDATE_URL = 'https://{}:{}@domains.google.com/nic/update' +UPDATE_URL = "https://{}:{}@domains.google.com/nic/update" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_DOMAIN): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_DOMAIN): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) async def async_setup(hass, config): @@ -41,47 +45,41 @@ async def async_setup(hass, config): session = hass.helpers.aiohttp_client.async_get_clientsession() result = await _update_google_domains( - hass, session, domain, user, password, timeout) + hass, session, domain, user, password, timeout + ) if not result: return False async def update_domain_interval(now): """Update the Google Domains entry.""" - await _update_google_domains( - hass, session, domain, user, password, timeout) + await _update_google_domains(hass, session, domain, user, password, timeout) - hass.helpers.event.async_track_time_interval( - update_domain_interval, INTERVAL) + hass.helpers.event.async_track_time_interval(update_domain_interval, INTERVAL) return True -async def _update_google_domains( - hass, session, domain, user, password, timeout): +async def _update_google_domains(hass, session, domain, user, password, timeout): """Update Google Domains.""" url = UPDATE_URL.format(user, password) - params = { - 'hostname': domain - } + params = {"hostname": domain} try: with async_timeout.timeout(timeout): resp = await session.get(url, params=params) body = await resp.text() - if body.startswith('good') or body.startswith('nochg'): + if body.startswith("good") or body.startswith("nochg"): return True - _LOGGER.warning('Updating Google Domains failed: %s => %s', - domain, body) + _LOGGER.warning("Updating Google Domains failed: %s => %s", domain, body) except aiohttp.ClientError: _LOGGER.warning("Can't connect to Google Domains API") except asyncio.TimeoutError: - _LOGGER.warning("Timeout from Google Domains API for domain: %s", - domain) + _LOGGER.warning("Timeout from Google Domains API for domain: %s", domain) return False diff --git a/homeassistant/components/google_maps/device_tracker.py b/homeassistant/components/google_maps/device_tracker.py index 4791cb25b47..0887aa19bfb 100644 --- a/homeassistant/components/google_maps/device_tracker.py +++ b/homeassistant/components/google_maps/device_tracker.py @@ -6,11 +6,14 @@ from locationsharinglib import Service from locationsharinglib.locationsharinglibexceptions import InvalidCookies import voluptuous as vol -from homeassistant.components.device_tracker import ( - PLATFORM_SCHEMA, SOURCE_TYPE_GPS) +from homeassistant.components.device_tracker import PLATFORM_SCHEMA, SOURCE_TYPE_GPS from homeassistant.const import ( - ATTR_BATTERY_CHARGING, ATTR_BATTERY_LEVEL, ATTR_ID, CONF_SCAN_INTERVAL, - CONF_USERNAME) + ATTR_BATTERY_CHARGING, + ATTR_BATTERY_LEVEL, + ATTR_ID, + CONF_SCAN_INTERVAL, + CONF_USERNAME, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import track_time_interval from homeassistant.helpers.typing import ConfigType @@ -18,19 +21,21 @@ from homeassistant.util import dt as dt_util, slugify _LOGGER = logging.getLogger(__name__) -ATTR_ADDRESS = 'address' -ATTR_FULL_NAME = 'full_name' -ATTR_LAST_SEEN = 'last_seen' -ATTR_NICKNAME = 'nickname' +ATTR_ADDRESS = "address" +ATTR_FULL_NAME = "full_name" +ATTR_LAST_SEEN = "last_seen" +ATTR_NICKNAME = "nickname" -CONF_MAX_GPS_ACCURACY = 'max_gps_accuracy' +CONF_MAX_GPS_ACCURACY = "max_gps_accuracy" -CREDENTIALS_FILE = '.google_maps_location_sharing.cookies' +CREDENTIALS_FILE = ".google_maps_location_sharing.cookies" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_USERNAME): cv.string, - vol.Optional(CONF_MAX_GPS_ACCURACY, default=100000): vol.Coerce(float), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Optional(CONF_MAX_GPS_ACCURACY, default=100000): vol.Coerce(float), + } +) def setup_scanner(hass, config: ConfigType, see, discovery_info=None): @@ -47,40 +52,47 @@ class GoogleMapsScanner: self.see = see self.username = config[CONF_USERNAME] self.max_gps_accuracy = config[CONF_MAX_GPS_ACCURACY] - self.scan_interval = \ - timedelta(seconds=config.get(CONF_SCAN_INTERVAL, 60)) + self.scan_interval = timedelta(seconds=config.get(CONF_SCAN_INTERVAL, 60)) - credfile = "{}.{}".format(hass.config.path(CREDENTIALS_FILE), - slugify(self.username)) + credfile = "{}.{}".format( + hass.config.path(CREDENTIALS_FILE), slugify(self.username) + ) try: self.service = Service(credfile, self.username) self._update_info() - track_time_interval( - hass, self._update_info, self.scan_interval) + track_time_interval(hass, self._update_info, self.scan_interval) self.success_init = True except InvalidCookies: - _LOGGER.error("You have specified invalid login credentials. " - "Please make sure you have saved your credentials" - " in the following file: %s", credfile) + _LOGGER.error( + "You have specified invalid login credentials. " + "Please make sure you have saved your credentials" + " in the following file: %s", + credfile, + ) self.success_init = False def _update_info(self, now=None): for person in self.service.get_all_people(): try: - dev_id = 'google_maps_{0}'.format(slugify(person.id)) + dev_id = "google_maps_{0}".format(slugify(person.id)) except TypeError: _LOGGER.warning("No location(s) shared with this account") return - if self.max_gps_accuracy is not None and \ - person.accuracy > self.max_gps_accuracy: - _LOGGER.info("Ignoring %s update because expected GPS " - "accuracy %s is not met: %s", - person.nickname, self.max_gps_accuracy, - person.accuracy) + if ( + self.max_gps_accuracy is not None + and person.accuracy > self.max_gps_accuracy + ): + _LOGGER.info( + "Ignoring %s update because expected GPS " + "accuracy %s is not met: %s", + person.nickname, + self.max_gps_accuracy, + person.accuracy, + ) continue attrs = { @@ -90,7 +102,7 @@ class GoogleMapsScanner: ATTR_LAST_SEEN: dt_util.as_utc(person.datetime), ATTR_NICKNAME: person.nickname, ATTR_BATTERY_CHARGING: person.charging, - ATTR_BATTERY_LEVEL: person.battery_level + ATTR_BATTERY_LEVEL: person.battery_level, } self.see( dev_id=dev_id, diff --git a/homeassistant/components/google_pubsub/__init__.py b/homeassistant/components/google_pubsub/__init__.py index 8aaa7a17ac4..e108d249797 100644 --- a/homeassistant/components/google_pubsub/__init__.py +++ b/homeassistant/components/google_pubsub/__init__.py @@ -7,29 +7,33 @@ from typing import Any, Dict import voluptuous as vol -from homeassistant.const import ( - EVENT_STATE_CHANGED, STATE_UNAVAILABLE, STATE_UNKNOWN) +from homeassistant.const import EVENT_STATE_CHANGED, STATE_UNAVAILABLE, STATE_UNKNOWN from homeassistant.core import Event, HomeAssistant import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entityfilter import FILTER_SCHEMA _LOGGER = logging.getLogger(__name__) -DOMAIN = 'google_pubsub' +DOMAIN = "google_pubsub" -CONF_PROJECT_ID = 'project_id' -CONF_TOPIC_NAME = 'topic_name' -CONF_SERVICE_PRINCIPAL = 'credentials_json' -CONF_FILTER = 'filter' +CONF_PROJECT_ID = "project_id" +CONF_TOPIC_NAME = "topic_name" +CONF_SERVICE_PRINCIPAL = "credentials_json" +CONF_FILTER = "filter" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_PROJECT_ID): cv.string, - vol.Required(CONF_TOPIC_NAME): cv.string, - vol.Required(CONF_SERVICE_PRINCIPAL): cv.string, - vol.Required(CONF_FILTER): FILTER_SCHEMA, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_PROJECT_ID): cv.string, + vol.Required(CONF_TOPIC_NAME): cv.string, + vol.Required(CONF_SERVICE_PRINCIPAL): cv.string, + vol.Required(CONF_FILTER): FILTER_SCHEMA, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass: HomeAssistant, yaml_config: Dict[str, Any]): @@ -40,7 +44,8 @@ def setup(hass: HomeAssistant, yaml_config: Dict[str, Any]): project_id = config[CONF_PROJECT_ID] topic_name = config[CONF_TOPIC_NAME] service_principal_path = os.path.join( - hass.config.config_dir, config[CONF_SERVICE_PRINCIPAL]) + hass.config.config_dir, config[CONF_SERVICE_PRINCIPAL] + ) if not os.path.isfile(service_principal_path): _LOGGER.error("Path to credentials file cannot be found") @@ -48,29 +53,26 @@ def setup(hass: HomeAssistant, yaml_config: Dict[str, Any]): entities_filter = config[CONF_FILTER] - publisher = (pubsub_v1 - .PublisherClient - .from_service_account_json(service_principal_path) - ) + publisher = pubsub_v1.PublisherClient.from_service_account_json( + service_principal_path + ) - topic_path = publisher.topic_path(project_id, # pylint: disable=E1101 - topic_name) + topic_path = publisher.topic_path(project_id, topic_name) # pylint: disable=E1101 encoder = DateTimeJSONEncoder() def send_to_pubsub(event: Event): """Send states to Pub/Sub.""" - state = event.data.get('new_state') - if (state is None - or state.state in (STATE_UNKNOWN, '', STATE_UNAVAILABLE) - or not entities_filter(state.entity_id)): + state = event.data.get("new_state") + if ( + state is None + or state.state in (STATE_UNKNOWN, "", STATE_UNAVAILABLE) + or not entities_filter(state.entity_id) + ): return as_dict = state.as_dict() - data = json.dumps( - obj=as_dict, - default=encoder.encode - ).encode('utf-8') + data = json.dumps(obj=as_dict, default=encoder.encode).encode("utf-8") publisher.publish(topic_path, data=data) diff --git a/homeassistant/components/google_translate/tts.py b/homeassistant/components/google_translate/tts.py index 0f067cf13b9..cdf6dbd402e 100644 --- a/homeassistant/components/google_translate/tts.py +++ b/homeassistant/components/google_translate/tts.py @@ -18,18 +18,67 @@ GOOGLE_SPEECH_URL = "https://translate.google.com/translate_tts" MESSAGE_SIZE = 148 SUPPORT_LANGUAGES = [ - 'af', 'sq', 'ar', 'hy', 'bn', 'ca', 'zh', 'zh-cn', 'zh-tw', 'zh-yue', - 'hr', 'cs', 'da', 'nl', 'en', 'en-au', 'en-uk', 'en-us', 'eo', 'fi', - 'fr', 'de', 'el', 'hi', 'hu', 'is', 'id', 'it', 'ja', 'ko', 'la', 'lv', - 'mk', 'no', 'pl', 'pt', 'pt-br', 'ro', 'ru', 'sr', 'sk', 'es', 'es-es', - 'es-mx', 'es-us', 'sw', 'sv', 'ta', 'th', 'tr', 'vi', 'cy', 'uk', 'bg-BG' + "af", + "sq", + "ar", + "hy", + "bn", + "ca", + "zh", + "zh-cn", + "zh-tw", + "zh-yue", + "hr", + "cs", + "da", + "nl", + "en", + "en-au", + "en-uk", + "en-us", + "eo", + "fi", + "fr", + "de", + "el", + "hi", + "hu", + "is", + "id", + "it", + "ja", + "ko", + "la", + "lv", + "mk", + "no", + "pl", + "pt", + "pt-br", + "ro", + "ru", + "sr", + "sk", + "es", + "es-es", + "es-mx", + "es-us", + "sw", + "sv", + "ta", + "th", + "tr", + "vi", + "cy", + "uk", + "bg-BG", ] -DEFAULT_LANG = 'en' +DEFAULT_LANG = "en" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_LANG, default=DEFAULT_LANG): vol.In(SUPPORT_LANGUAGES), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Optional(CONF_LANG, default=DEFAULT_LANG): vol.In(SUPPORT_LANGUAGES)} +) async def async_get_engine(hass, config): @@ -46,11 +95,13 @@ class GoogleProvider(Provider): self._lang = lang self.headers = { REFERER: "http://translate.google.com/", - USER_AGENT: ("Mozilla/5.0 (Windows NT 10.0; WOW64) " - "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/47.0.2526.106 Safari/537.36"), + USER_AGENT: ( + "Mozilla/5.0 (Windows NT 10.0; WOW64) " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/47.0.2526.106 Safari/537.36" + ), } - self.name = 'Google' + self.name = "Google" @property def default_language(self): @@ -70,32 +121,31 @@ class GoogleProvider(Provider): websession = async_get_clientsession(self.hass) message_parts = self._split_message_to_parts(message) - data = b'' + data = b"" for idx, part in enumerate(message_parts): - part_token = await self.hass.async_add_job( - token.calculate_token, part) + part_token = await self.hass.async_add_job(token.calculate_token, part) url_param = { - 'ie': 'UTF-8', - 'tl': language, - 'q': yarl.URL(part).raw_path, - 'tk': part_token, - 'total': len(message_parts), - 'idx': idx, - 'client': 'tw-ob', - 'textlen': len(part), + "ie": "UTF-8", + "tl": language, + "q": yarl.URL(part).raw_path, + "tk": part_token, + "total": len(message_parts), + "idx": idx, + "client": "tw-ob", + "textlen": len(part), } try: with async_timeout.timeout(10): request = await websession.get( - GOOGLE_SPEECH_URL, params=url_param, - headers=self.headers + GOOGLE_SPEECH_URL, params=url_param, headers=self.headers ) if request.status != 200: - _LOGGER.error("Error %d on load URL %s", - request.status, request.url) + _LOGGER.error( + "Error %d on load URL %s", request.status, request.url + ) return None, None data += await request.read() @@ -103,7 +153,7 @@ class GoogleProvider(Provider): _LOGGER.error("Timeout for google speech") return None, None - return 'mp3', data + return "mp3", data @staticmethod def _split_message_to_parts(message): @@ -113,13 +163,13 @@ class GoogleProvider(Provider): punc = "!()[]?.,;:" punc_list = [re.escape(c) for c in punc] - pattern = '|'.join(punc_list) + pattern = "|".join(punc_list) parts = re.split(pattern, message) def split_by_space(fullstring): """Split a string by space.""" if len(fullstring) > MESSAGE_SIZE: - idx = fullstring.rfind(' ', 0, MESSAGE_SIZE) + idx = fullstring.rfind(" ", 0, MESSAGE_SIZE) return [fullstring[:idx]] + split_by_space(fullstring[idx:]) return [fullstring] diff --git a/homeassistant/components/google_travel_time/sensor.py b/homeassistant/components/google_travel_time/sensor.py index 1a6d347ad7f..32947867958 100644 --- a/homeassistant/components/google_travel_time/sensor.py +++ b/homeassistant/components/google_travel_time/sensor.py @@ -9,8 +9,14 @@ import homeassistant.helpers.config_validation as cv import homeassistant.util.dt as dt_util from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_API_KEY, CONF_NAME, EVENT_HOMEASSISTANT_START, ATTR_LATITUDE, - ATTR_LONGITUDE, ATTR_ATTRIBUTION, CONF_MODE) + CONF_API_KEY, + CONF_NAME, + EVENT_HOMEASSISTANT_START, + ATTR_LATITUDE, + ATTR_LONGITUDE, + ATTR_ATTRIBUTION, + CONF_MODE, +) from homeassistant.helpers import location from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle @@ -19,57 +25,110 @@ _LOGGER = logging.getLogger(__name__) ATTRIBUTION = "Powered by Google" -CONF_DESTINATION = 'destination' -CONF_OPTIONS = 'options' -CONF_ORIGIN = 'origin' -CONF_TRAVEL_MODE = 'travel_mode' +CONF_DESTINATION = "destination" +CONF_OPTIONS = "options" +CONF_ORIGIN = "origin" +CONF_TRAVEL_MODE = "travel_mode" -DEFAULT_NAME = 'Google Travel Time' +DEFAULT_NAME = "Google Travel Time" MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5) -ALL_LANGUAGES = ['ar', 'bg', 'bn', 'ca', 'cs', 'da', 'de', 'el', 'en', 'es', - 'eu', 'fa', 'fi', 'fr', 'gl', 'gu', 'hi', 'hr', 'hu', 'id', - 'it', 'iw', 'ja', 'kn', 'ko', 'lt', 'lv', 'ml', 'mr', 'nl', - 'no', 'pl', 'pt', 'pt-BR', 'pt-PT', 'ro', 'ru', 'sk', 'sl', - 'sr', 'sv', 'ta', 'te', 'th', 'tl', 'tr', 'uk', 'vi', - 'zh-CN', 'zh-TW'] +ALL_LANGUAGES = [ + "ar", + "bg", + "bn", + "ca", + "cs", + "da", + "de", + "el", + "en", + "es", + "eu", + "fa", + "fi", + "fr", + "gl", + "gu", + "hi", + "hr", + "hu", + "id", + "it", + "iw", + "ja", + "kn", + "ko", + "lt", + "lv", + "ml", + "mr", + "nl", + "no", + "pl", + "pt", + "pt-BR", + "pt-PT", + "ro", + "ru", + "sk", + "sl", + "sr", + "sv", + "ta", + "te", + "th", + "tl", + "tr", + "uk", + "vi", + "zh-CN", + "zh-TW", +] -AVOID = ['tolls', 'highways', 'ferries', 'indoor'] -TRANSIT_PREFS = ['less_walking', 'fewer_transfers'] -TRANSPORT_TYPE = ['bus', 'subway', 'train', 'tram', 'rail'] -TRAVEL_MODE = ['driving', 'walking', 'bicycling', 'transit'] -TRAVEL_MODEL = ['best_guess', 'pessimistic', 'optimistic'] -UNITS = ['metric', 'imperial'] +AVOID = ["tolls", "highways", "ferries", "indoor"] +TRANSIT_PREFS = ["less_walking", "fewer_transfers"] +TRANSPORT_TYPE = ["bus", "subway", "train", "tram", "rail"] +TRAVEL_MODE = ["driving", "walking", "bicycling", "transit"] +TRAVEL_MODEL = ["best_guess", "pessimistic", "optimistic"] +UNITS = ["metric", "imperial"] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_API_KEY): cv.string, - vol.Required(CONF_DESTINATION): cv.string, - vol.Required(CONF_ORIGIN): cv.string, - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_TRAVEL_MODE): vol.In(TRAVEL_MODE), - vol.Optional(CONF_OPTIONS, default={CONF_MODE: 'driving'}): vol.All( - dict, vol.Schema({ - vol.Optional(CONF_MODE, default='driving'): vol.In(TRAVEL_MODE), - vol.Optional('language'): vol.In(ALL_LANGUAGES), - vol.Optional('avoid'): vol.In(AVOID), - vol.Optional('units'): vol.In(UNITS), - vol.Exclusive('arrival_time', 'time'): cv.string, - vol.Exclusive('departure_time', 'time'): cv.string, - vol.Optional('traffic_model'): vol.In(TRAVEL_MODEL), - vol.Optional('transit_mode'): vol.In(TRANSPORT_TYPE), - vol.Optional('transit_routing_preference'): vol.In(TRANSIT_PREFS) - })) -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_API_KEY): cv.string, + vol.Required(CONF_DESTINATION): cv.string, + vol.Required(CONF_ORIGIN): cv.string, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_TRAVEL_MODE): vol.In(TRAVEL_MODE), + vol.Optional(CONF_OPTIONS, default={CONF_MODE: "driving"}): vol.All( + dict, + vol.Schema( + { + vol.Optional(CONF_MODE, default="driving"): vol.In(TRAVEL_MODE), + vol.Optional("language"): vol.In(ALL_LANGUAGES), + vol.Optional("avoid"): vol.In(AVOID), + vol.Optional("units"): vol.In(UNITS), + vol.Exclusive("arrival_time", "time"): cv.string, + vol.Exclusive("departure_time", "time"): cv.string, + vol.Optional("traffic_model"): vol.In(TRAVEL_MODEL), + vol.Optional("transit_mode"): vol.In(TRANSPORT_TYPE), + vol.Optional("transit_routing_preference"): vol.In(TRANSIT_PREFS), + } + ), + ), + } +) -TRACKABLE_DOMAINS = ['device_tracker', 'sensor', 'zone', 'person'] -DATA_KEY = 'google_travel_time' +TRACKABLE_DOMAINS = ["device_tracker", "sensor", "zone", "person"] +DATA_KEY = "google_travel_time" def convert_time_to_utc(timestr): """Take a string like 08:00:00 and convert it to a unix timestamp.""" combined = datetime.combine( - dt_util.start_of_local_day(), dt_util.parse_time(timestr)) + dt_util.start_of_local_day(), dt_util.parse_time(timestr) + ) if combined < datetime.now(): combined = combined + timedelta(days=1) return dt_util.as_timestamp(combined) @@ -77,6 +136,7 @@ def convert_time_to_utc(timestr): def setup_platform(hass, config, add_entities_callback, discovery_info=None): """Set up the Google travel time platform.""" + def run_setup(event): """ Delay the setup until Home Assistant is fully initialized. @@ -86,15 +146,17 @@ def setup_platform(hass, config, add_entities_callback, discovery_info=None): hass.data.setdefault(DATA_KEY, []) options = config.get(CONF_OPTIONS) - if options.get('units') is None: - options['units'] = hass.config.units.name + if options.get("units") is None: + options["units"] = hass.config.units.name travel_mode = config.get(CONF_TRAVEL_MODE) mode = options.get(CONF_MODE) if travel_mode is not None: - wstr = ("Google Travel Time: travel_mode is deprecated, please " - "add mode to the options dictionary instead!") + wstr = ( + "Google Travel Time: travel_mode is deprecated, please " + "add mode to the options dictionary instead!" + ) _LOGGER.warning(wstr) if mode is None: options[CONF_MODE] = travel_mode @@ -107,7 +169,8 @@ def setup_platform(hass, config, add_entities_callback, discovery_info=None): destination = config.get(CONF_DESTINATION) sensor = GoogleTravelTimeSensor( - hass, name, api_key, origin, destination, options) + hass, name, api_key, origin, destination, options + ) hass.data[DATA_KEY].append(sensor) if sensor.valid_api_connection: @@ -125,27 +188,28 @@ class GoogleTravelTimeSensor(Entity): self._hass = hass self._name = name self._options = options - self._unit_of_measurement = 'min' + self._unit_of_measurement = "min" self._matrix = None self.valid_api_connection = True # Check if location is a trackable entity - if origin.split('.', 1)[0] in TRACKABLE_DOMAINS: + if origin.split(".", 1)[0] in TRACKABLE_DOMAINS: self._origin_entity_id = origin else: self._origin = origin - if destination.split('.', 1)[0] in TRACKABLE_DOMAINS: + if destination.split(".", 1)[0] in TRACKABLE_DOMAINS: self._destination_entity_id = destination else: self._destination = destination import googlemaps + self._client = googlemaps.Client(api_key, timeout=10) try: self.update() except googlemaps.exceptions.ApiError as exp: - _LOGGER .error(exp) + _LOGGER.error(exp) self.valid_api_connection = False return @@ -155,11 +219,11 @@ class GoogleTravelTimeSensor(Entity): if self._matrix is None: return None - _data = self._matrix['rows'][0]['elements'][0] - if 'duration_in_traffic' in _data: - return round(_data['duration_in_traffic']['value']/60) - if 'duration' in _data: - return round(_data['duration']['value']/60) + _data = self._matrix["rows"][0]["elements"][0] + if "duration_in_traffic" in _data: + return round(_data["duration_in_traffic"]["value"] / 60) + if "duration" in _data: + return round(_data["duration"]["value"] / 60) return None @property @@ -175,16 +239,16 @@ class GoogleTravelTimeSensor(Entity): res = self._matrix.copy() res.update(self._options) - del res['rows'] - _data = self._matrix['rows'][0]['elements'][0] - if 'duration_in_traffic' in _data: - res['duration_in_traffic'] = _data['duration_in_traffic']['text'] - if 'duration' in _data: - res['duration'] = _data['duration']['text'] - if 'distance' in _data: - res['distance'] = _data['distance']['text'] - res['origin'] = self._origin - res['destination'] = self._destination + del res["rows"] + _data = self._matrix["rows"][0]["elements"][0] + if "duration_in_traffic" in _data: + res["duration_in_traffic"] = _data["duration_in_traffic"]["text"] + if "duration" in _data: + res["duration"] = _data["duration"]["text"] + if "distance" in _data: + res["distance"] = _data["distance"]["text"] + res["origin"] = self._origin + res["destination"] = self._destination res[ATTR_ATTRIBUTION] = ATTRIBUTION return res @@ -197,27 +261,25 @@ class GoogleTravelTimeSensor(Entity): def update(self): """Get the latest data from Google.""" options_copy = self._options.copy() - dtime = options_copy.get('departure_time') - atime = options_copy.get('arrival_time') - if dtime is not None and ':' in dtime: - options_copy['departure_time'] = convert_time_to_utc(dtime) + dtime = options_copy.get("departure_time") + atime = options_copy.get("arrival_time") + if dtime is not None and ":" in dtime: + options_copy["departure_time"] = convert_time_to_utc(dtime) elif dtime is not None: - options_copy['departure_time'] = dtime + options_copy["departure_time"] = dtime elif atime is None: - options_copy['departure_time'] = 'now' + options_copy["departure_time"] = "now" - if atime is not None and ':' in atime: - options_copy['arrival_time'] = convert_time_to_utc(atime) + if atime is not None and ":" in atime: + options_copy["arrival_time"] = convert_time_to_utc(atime) elif atime is not None: - options_copy['arrival_time'] = atime + options_copy["arrival_time"] = atime # Convert device_trackers to google friendly location - if hasattr(self, '_origin_entity_id'): - self._origin = self._get_location_from_entity( - self._origin_entity_id - ) + if hasattr(self, "_origin_entity_id"): + self._origin = self._get_location_from_entity(self._origin_entity_id) - if hasattr(self, '_destination_entity_id'): + if hasattr(self, "_destination_entity_id"): self._destination = self._get_location_from_entity( self._destination_entity_id ) @@ -227,7 +289,8 @@ class GoogleTravelTimeSensor(Entity): if self._destination is not None and self._origin is not None: self._matrix = self._client.distance_matrix( - self._origin, self._destination, **options_copy) + self._origin, self._destination, **options_copy + ) def _get_location_from_entity(self, entity_id): """Get the location from the entity state or attributes.""" @@ -246,8 +309,7 @@ class GoogleTravelTimeSensor(Entity): zone_entity = self._hass.states.get("zone.%s" % entity.state) if location.has_location(zone_entity): _LOGGER.debug( - "%s is in %s, getting zone location", - entity_id, zone_entity.entity_id + "%s is in %s, getting zone location", entity_id, zone_entity.entity_id ) return self._get_location_from_attributes(zone_entity) @@ -267,7 +329,7 @@ class GoogleTravelTimeSensor(Entity): def _resolve_zone(self, friendly_name): entities = self._hass.states.all() for entity in entities: - if entity.domain == 'zone' and entity.name == friendly_name: + if entity.domain == "zone" and entity.name == friendly_name: return self._get_location_from_attributes(entity) return friendly_name diff --git a/homeassistant/components/google_wifi/sensor.py b/homeassistant/components/google_wifi/sensor.py index 202e2a0eb46..a910e91b164 100644 --- a/homeassistant/components/google_wifi/sensor.py +++ b/homeassistant/components/google_wifi/sensor.py @@ -9,66 +9,52 @@ from homeassistant.util import dt import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_NAME, CONF_HOST, CONF_MONITORED_CONDITIONS, STATE_UNKNOWN) + CONF_NAME, + CONF_HOST, + CONF_MONITORED_CONDITIONS, + STATE_UNKNOWN, +) from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -ATTR_CURRENT_VERSION = 'current_version' -ATTR_LAST_RESTART = 'last_restart' -ATTR_LOCAL_IP = 'local_ip' -ATTR_NEW_VERSION = 'new_version' -ATTR_STATUS = 'status' -ATTR_UPTIME = 'uptime' +ATTR_CURRENT_VERSION = "current_version" +ATTR_LAST_RESTART = "last_restart" +ATTR_LOCAL_IP = "local_ip" +ATTR_NEW_VERSION = "new_version" +ATTR_STATUS = "status" +ATTR_UPTIME = "uptime" -DEFAULT_HOST = 'testwifi.here' -DEFAULT_NAME = 'google_wifi' +DEFAULT_HOST = "testwifi.here" +DEFAULT_NAME = "google_wifi" -ENDPOINT = '/api/v1/status' +ENDPOINT = "/api/v1/status" MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=1) MONITORED_CONDITIONS = { ATTR_CURRENT_VERSION: [ - ['software', 'softwareVersion'], + ["software", "softwareVersion"], None, - 'mdi:checkbox-marked-circle-outline' + "mdi:checkbox-marked-circle-outline", ], - ATTR_NEW_VERSION: [ - ['software', 'updateNewVersion'], - None, - 'mdi:update' - ], - ATTR_UPTIME: [ - ['system', 'uptime'], - 'days', - 'mdi:timelapse' - ], - ATTR_LAST_RESTART: [ - ['system', 'uptime'], - None, - 'mdi:restart' - ], - ATTR_LOCAL_IP: [ - ['wan', 'localIpAddress'], - None, - 'mdi:access-point-network' - ], - ATTR_STATUS: [ - ['wan', 'online'], - None, - 'mdi:google' - ] + ATTR_NEW_VERSION: [["software", "updateNewVersion"], None, "mdi:update"], + ATTR_UPTIME: [["system", "uptime"], "days", "mdi:timelapse"], + ATTR_LAST_RESTART: [["system", "uptime"], None, "mdi:restart"], + ATTR_LOCAL_IP: [["wan", "localIpAddress"], None, "mdi:access-point-network"], + ATTR_STATUS: [["wan", "online"], None, "mdi:google"], } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, - vol.Optional(CONF_MONITORED_CONDITIONS, - default=list(MONITORED_CONDITIONS)): - vol.All(cv.ensure_list, [vol.In(MONITORED_CONDITIONS)]), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, + vol.Optional( + CONF_MONITORED_CONDITIONS, default=list(MONITORED_CONDITIONS) + ): vol.All(cv.ensure_list, [vol.In(MONITORED_CONDITIONS)]), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -102,7 +88,7 @@ class GoogleWifiSensor(Entity): @property def name(self): """Return the name of the sensor.""" - return '{}_{}'.format(self._name, self._var_name) + return "{}_{}".format(self._name, self._var_name) @property def icon(self): @@ -138,9 +124,9 @@ class GoogleWifiAPI: def __init__(self, host, conditions): """Initialize the data object.""" - uri = 'http://' + uri = "http://" resource = "{}{}{}".format(uri, host, ENDPOINT) - self._request = requests.Request('GET', resource).prepare() + self._request = requests.Request("GET", resource).prepare() self.raw_data = None self.conditions = conditions self.data = { @@ -149,7 +135,7 @@ class GoogleWifiAPI: ATTR_UPTIME: STATE_UNKNOWN, ATTR_LAST_RESTART: STATE_UNKNOWN, ATTR_LOCAL_IP: STATE_UNKNOWN, - ATTR_STATUS: STATE_UNKNOWN + ATTR_STATUS: STATE_UNKNOWN, } self.available = True self.update() @@ -178,28 +164,28 @@ class GoogleWifiAPI: if primary_key in self.raw_data: sensor_value = self.raw_data[primary_key][sensor_key] # Format sensor for better readability - if (attr_key == ATTR_NEW_VERSION and - sensor_value == '0.0.0.0'): - sensor_value = 'Latest' + if attr_key == ATTR_NEW_VERSION and sensor_value == "0.0.0.0": + sensor_value = "Latest" elif attr_key == ATTR_UPTIME: sensor_value = round(sensor_value / (3600 * 24), 2) elif attr_key == ATTR_LAST_RESTART: - last_restart = ( - dt.now() - timedelta(seconds=sensor_value)) - sensor_value = last_restart.strftime( - '%Y-%m-%d %H:%M:%S') + last_restart = dt.now() - timedelta(seconds=sensor_value) + sensor_value = last_restart.strftime("%Y-%m-%d %H:%M:%S") elif attr_key == ATTR_STATUS: if sensor_value: - sensor_value = 'Online' + sensor_value = "Online" else: - sensor_value = 'Offline' + sensor_value = "Offline" elif attr_key == ATTR_LOCAL_IP: - if not self.raw_data['wan']['online']: + if not self.raw_data["wan"]["online"]: sensor_value = STATE_UNKNOWN self.data[attr_key] = sensor_value except KeyError: - _LOGGER.error("Router does not support %s field. " - "Please remove %s from monitored_conditions", - sensor_key, attr_key) + _LOGGER.error( + "Router does not support %s field. " + "Please remove %s from monitored_conditions", + sensor_key, + attr_key, + ) self.data[attr_key] = STATE_UNKNOWN diff --git a/homeassistant/components/googlehome/__init__.py b/homeassistant/components/googlehome/__init__.py index 073081a9634..01e17708fb3 100644 --- a/homeassistant/components/googlehome/__init__.py +++ b/homeassistant/components/googlehome/__init__.py @@ -10,35 +10,42 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession _LOGGER = logging.getLogger(__name__) -DOMAIN = 'googlehome' -CLIENT = 'googlehome_client' +DOMAIN = "googlehome" +CLIENT = "googlehome_client" -NAME = 'GoogleHome' +NAME = "GoogleHome" -CONF_DEVICE_TYPES = 'device_types' -CONF_RSSI_THRESHOLD = 'rssi_threshold' -CONF_TRACK_ALARMS = 'track_alarms' -CONF_TRACK_DEVICES = 'track_devices' +CONF_DEVICE_TYPES = "device_types" +CONF_RSSI_THRESHOLD = "rssi_threshold" +CONF_TRACK_ALARMS = "track_alarms" +CONF_TRACK_DEVICES = "track_devices" DEVICE_TYPES = [1, 2, 3] DEFAULT_RSSI_THRESHOLD = -70 -DEVICE_CONFIG = vol.Schema({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_DEVICE_TYPES, default=DEVICE_TYPES): - vol.All(cv.ensure_list, [vol.In(DEVICE_TYPES)]), - vol.Optional(CONF_RSSI_THRESHOLD, default=DEFAULT_RSSI_THRESHOLD): - vol.Coerce(int), - vol.Optional(CONF_TRACK_ALARMS, default=False): cv.boolean, - vol.Optional(CONF_TRACK_DEVICES, default=True): cv.boolean, -}) +DEVICE_CONFIG = vol.Schema( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_DEVICE_TYPES, default=DEVICE_TYPES): vol.All( + cv.ensure_list, [vol.In(DEVICE_TYPES)] + ), + vol.Optional(CONF_RSSI_THRESHOLD, default=DEFAULT_RSSI_THRESHOLD): vol.Coerce( + int + ), + vol.Optional(CONF_TRACK_ALARMS, default=False): cv.boolean, + vol.Optional(CONF_TRACK_DEVICES, default=True): cv.boolean, + } +) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_DEVICES): vol.All(cv.ensure_list, [DEVICE_CONFIG]), - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + {vol.Required(CONF_DEVICES): vol.All(cv.ensure_list, [DEVICE_CONFIG])} + ) + }, + extra=vol.ALLOW_EXTRA, +) async def async_setup(hass, config): @@ -47,16 +54,18 @@ async def async_setup(hass, config): hass.data[CLIENT] = GoogleHomeClient(hass) for device in config[DOMAIN][CONF_DEVICES]: - hass.data[DOMAIN][device['host']] = {} + hass.data[DOMAIN][device["host"]] = {} if device[CONF_TRACK_DEVICES]: hass.async_create_task( discovery.async_load_platform( - hass, 'device_tracker', DOMAIN, device, config)) + hass, "device_tracker", DOMAIN, device, config + ) + ) if device[CONF_TRACK_ALARMS]: hass.async_create_task( - discovery.async_load_platform( - hass, 'sensor', DOMAIN, device, config)) + discovery.async_load_platform(hass, "sensor", DOMAIN, device, config) + ) return True @@ -72,6 +81,7 @@ class GoogleHomeClient: async def update_info(self, host): """Update data from Google Home.""" from googledevices.api.connect import Cast + _LOGGER.debug("Updating Google Home info for %s", host) session = async_get_clientsession(self.hass) @@ -79,11 +89,12 @@ class GoogleHomeClient: device_info_data = await device_info.get_device_info() self._connected = bool(device_info_data) - self.hass.data[DOMAIN][host]['info'] = device_info_data + self.hass.data[DOMAIN][host]["info"] = device_info_data async def update_bluetooth(self, host): """Update bluetooth from Google Home.""" from googledevices.api.connect import Cast + _LOGGER.debug("Updating Google Home bluetooth for %s", host) session = async_get_clientsession(self.hass) @@ -92,15 +103,16 @@ class GoogleHomeClient: await asyncio.sleep(5) bluetooth_data = await bluetooth.get_scan_result() - self.hass.data[DOMAIN][host]['bluetooth'] = bluetooth_data + self.hass.data[DOMAIN][host]["bluetooth"] = bluetooth_data async def update_alarms(self, host): """Update alarms from Google Home.""" from googledevices.api.connect import Cast + _LOGGER.debug("Updating Google Home bluetooth for %s", host) session = async_get_clientsession(self.hass) assistant = await Cast(host, self.hass.loop, session).assistant() alarms_data = await assistant.get_alarms() - self.hass.data[DOMAIN][host]['alarms'] = alarms_data + self.hass.data[DOMAIN][host]["alarms"] = alarms_data diff --git a/homeassistant/components/googlehome/device_tracker.py b/homeassistant/components/googlehome/device_tracker.py index 3b6bc5d341c..58350afa430 100644 --- a/homeassistant/components/googlehome/device_tracker.py +++ b/homeassistant/components/googlehome/device_tracker.py @@ -16,11 +16,11 @@ DEFAULT_SCAN_INTERVAL = timedelta(seconds=10) async def async_setup_scanner(hass, config, async_see, discovery_info=None): """Validate the configuration and return a Google Home scanner.""" if discovery_info is None: - _LOGGER.warning( - "To use this you need to configure the 'googlehome' component") + _LOGGER.warning("To use this you need to configure the 'googlehome' component") return False - scanner = GoogleHomeDeviceScanner(hass, hass.data[CLIENT], - discovery_info, async_see) + scanner = GoogleHomeDeviceScanner( + hass, hass.data[CLIENT], discovery_info, async_see + ) return await scanner.async_init() @@ -31,49 +31,50 @@ class GoogleHomeDeviceScanner(DeviceScanner): """Initialize the scanner.""" self.async_see = async_see self.hass = hass - self.rssi = config['rssi_threshold'] - self.device_types = config['device_types'] - self.host = config['host'] + self.rssi = config["rssi_threshold"] + self.device_types = config["device_types"] + self.host = config["host"] self.client = client async def async_init(self): """Further initialize connection to Google Home.""" await self.client.update_info(self.host) data = self.hass.data[GOOGLEHOME_DOMAIN][self.host] - info = data.get('info', {}) + info = data.get("info", {}) connected = bool(info) if connected: await self.async_update() - async_track_time_interval(self.hass, - self.async_update, - DEFAULT_SCAN_INTERVAL) + async_track_time_interval( + self.hass, self.async_update, DEFAULT_SCAN_INTERVAL + ) return connected async def async_update(self, now=None): """Ensure the information from Google Home is up to date.""" - _LOGGER.debug('Checking Devices on %s', self.host) + _LOGGER.debug("Checking Devices on %s", self.host) await self.client.update_bluetooth(self.host) data = self.hass.data[GOOGLEHOME_DOMAIN][self.host] - info = data.get('info') - bluetooth = data.get('bluetooth') + info = data.get("info") + bluetooth = data.get("bluetooth") if info is None or bluetooth is None: return - google_home_name = info.get('name', NAME) + google_home_name = info.get("name", NAME) for device in bluetooth: - if (device['device_type'] not in - self.device_types or device['rssi'] < self.rssi): + if ( + device["device_type"] not in self.device_types + or device["rssi"] < self.rssi + ): continue - name = "{} {}".format(self.host, device['mac_address']) + name = "{} {}".format(self.host, device["mac_address"]) attributes = {} - attributes['btle_mac_address'] = device['mac_address'] - attributes['ghname'] = google_home_name - attributes['rssi'] = device['rssi'] - attributes['source_type'] = 'bluetooth' - if device['name']: - attributes['name'] = device['name'] + attributes["btle_mac_address"] = device["mac_address"] + attributes["ghname"] = google_home_name + attributes["rssi"] = device["rssi"] + attributes["source_type"] = "bluetooth" + if device["name"]: + attributes["name"] = device["name"] - await self.async_see(dev_id=slugify(name), - attributes=attributes) + await self.async_see(dev_id=slugify(name), attributes=attributes) diff --git a/homeassistant/components/googlehome/sensor.py b/homeassistant/components/googlehome/sensor.py index 088f4352fa3..6a578e14f5a 100644 --- a/homeassistant/components/googlehome/sensor.py +++ b/homeassistant/components/googlehome/sensor.py @@ -12,30 +12,26 @@ SCAN_INTERVAL = timedelta(seconds=10) _LOGGER = logging.getLogger(__name__) -ICON = 'mdi:alarm' +ICON = "mdi:alarm" -SENSOR_TYPES = { - 'timer': 'Timer', - 'alarm': 'Alarm', -} +SENSOR_TYPES = {"timer": "Timer", "alarm": "Alarm"} -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the googlehome sensor platform.""" if discovery_info is None: - _LOGGER.warning( - "To use this you need to configure the 'googlehome' component") + _LOGGER.warning("To use this you need to configure the 'googlehome' component") return - await hass.data[CLIENT].update_info(discovery_info['host']) - data = hass.data[GOOGLEHOME_DOMAIN][discovery_info['host']] - info = data.get('info', {}) + await hass.data[CLIENT].update_info(discovery_info["host"]) + data = hass.data[GOOGLEHOME_DOMAIN][discovery_info["host"]] + info = data.get("info", {}) devices = [] for condition in SENSOR_TYPES: - device = GoogleHomeAlarm(hass.data[CLIENT], condition, - discovery_info, info.get('name', NAME)) + device = GoogleHomeAlarm( + hass.data[CLIENT], condition, discovery_info, info.get("name", NAME) + ) devices.append(device) async_add_entities(devices, True) @@ -46,7 +42,7 @@ class GoogleHomeAlarm(Entity): def __init__(self, client, condition, config, name): """Initialize the GoogleHomeAlarm sensor.""" - self._host = config['host'] + self._host = config["host"] self._client = client self._condition = condition self._name = None @@ -59,14 +55,14 @@ class GoogleHomeAlarm(Entity): await self._client.update_alarms(self._host) data = self.hass.data[GOOGLEHOME_DOMAIN][self._host] - alarms = data.get('alarms')[self._condition] + alarms = data.get("alarms")[self._condition] if not alarms: self._available = False return self._available = True - time_date = dt_util.utc_from_timestamp(min(element['fire_time'] - for element in alarms) - / 1000) + time_date = dt_util.utc_from_timestamp( + min(element["fire_time"] for element in alarms) / 1000 + ) self._state = time_date.isoformat() @property diff --git a/homeassistant/components/gpmdp/media_player.py b/homeassistant/components/gpmdp/media_player.py index 76253d32db8..90522a84ce5 100644 --- a/homeassistant/components/gpmdp/media_player.py +++ b/homeassistant/components/gpmdp/media_player.py @@ -6,95 +6,134 @@ import time import voluptuous as vol -from homeassistant.components.media_player import ( - MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA from homeassistant.components.media_player.const import ( - MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, - SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_VOLUME_SET) + MEDIA_TYPE_MUSIC, + SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, + SUPPORT_PLAY, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_SEEK, + SUPPORT_VOLUME_SET, +) from homeassistant.const import ( - CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_PAUSED, STATE_PLAYING) + CONF_HOST, + CONF_NAME, + CONF_PORT, + STATE_OFF, + STATE_PAUSED, + STATE_PLAYING, +) import homeassistant.helpers.config_validation as cv from homeassistant.util.json import load_json, save_json _CONFIGURING = {} _LOGGER = logging.getLogger(__name__) -DEFAULT_HOST = 'localhost' -DEFAULT_NAME = 'GPM Desktop Player' +DEFAULT_HOST = "localhost" +DEFAULT_NAME = "GPM Desktop Player" DEFAULT_PORT = 5672 -GPMDP_CONFIG_FILE = 'gpmpd.conf' +GPMDP_CONFIG_FILE = "gpmpd.conf" -SUPPORT_GPMDP = SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \ - SUPPORT_SEEK | SUPPORT_VOLUME_SET | SUPPORT_PLAY +SUPPORT_GPMDP = ( + SUPPORT_PAUSE + | SUPPORT_PREVIOUS_TRACK + | SUPPORT_NEXT_TRACK + | SUPPORT_SEEK + | SUPPORT_VOLUME_SET + | SUPPORT_PLAY +) -PLAYBACK_DICT = {'0': STATE_PAUSED, # Stopped - '1': STATE_PAUSED, - '2': STATE_PLAYING} +PLAYBACK_DICT = {"0": STATE_PAUSED, "1": STATE_PAUSED, "2": STATE_PLAYING} # Stopped -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + } +) def request_configuration(hass, config, url, add_entities_callback): """Request configuration steps from the user.""" configurator = hass.components.configurator - if 'gpmdp' in _CONFIGURING: + if "gpmdp" in _CONFIGURING: configurator.notify_errors( - _CONFIGURING['gpmdp'], "Failed to register, please try again.") + _CONFIGURING["gpmdp"], "Failed to register, please try again." + ) return from websocket import create_connection + websocket = create_connection((url), timeout=1) - websocket.send(json.dumps({ - 'namespace': 'connect', - 'method': 'connect', - 'arguments': ['Home Assistant'] - })) + websocket.send( + json.dumps( + { + "namespace": "connect", + "method": "connect", + "arguments": ["Home Assistant"], + } + ) + ) def gpmdp_configuration_callback(callback_data): """Handle configuration changes.""" while True: from websocket import _exceptions + try: msg = json.loads(websocket.recv()) except _exceptions.WebSocketConnectionClosedException: continue - if msg['channel'] != 'connect': + if msg["channel"] != "connect": continue - if msg['payload'] != "CODE_REQUIRED": + if msg["payload"] != "CODE_REQUIRED": continue - pin = callback_data.get('pin') - websocket.send(json.dumps({'namespace': 'connect', - 'method': 'connect', - 'arguments': ['Home Assistant', pin]})) + pin = callback_data.get("pin") + websocket.send( + json.dumps( + { + "namespace": "connect", + "method": "connect", + "arguments": ["Home Assistant", pin], + } + ) + ) tmpmsg = json.loads(websocket.recv()) - if tmpmsg['channel'] == 'time': - _LOGGER.error("Error setting up GPMDP. Please pause " - "the desktop player and try again") + if tmpmsg["channel"] == "time": + _LOGGER.error( + "Error setting up GPMDP. Please pause " + "the desktop player and try again" + ) break - code = tmpmsg['payload'] - if code == 'CODE_REQUIRED': + code = tmpmsg["payload"] + if code == "CODE_REQUIRED": continue - setup_gpmdp(hass, config, code, - add_entities_callback) + setup_gpmdp(hass, config, code, add_entities_callback) save_json(hass.config.path(GPMDP_CONFIG_FILE), {"CODE": code}) - websocket.send(json.dumps({'namespace': 'connect', - 'method': 'connect', - 'arguments': ['Home Assistant', code]})) + websocket.send( + json.dumps( + { + "namespace": "connect", + "method": "connect", + "arguments": ["Home Assistant", code], + } + ) + ) websocket.close() break - _CONFIGURING['gpmdp'] = configurator.request_config( - DEFAULT_NAME, gpmdp_configuration_callback, + _CONFIGURING["gpmdp"] = configurator.request_config( + DEFAULT_NAME, + gpmdp_configuration_callback, description=( - 'Enter the pin that is displayed in the ' - 'Google Play Music Desktop Player.'), + "Enter the pin that is displayed in the " + "Google Play Music Desktop Player." + ), submit_caption="Submit", - fields=[{'id': 'pin', 'name': 'Pin Code', 'type': 'number'}] + fields=[{"id": "pin", "name": "Pin Code", "type": "number"}], ) @@ -103,15 +142,15 @@ def setup_gpmdp(hass, config, code, add_entities): name = config.get(CONF_NAME) host = config.get(CONF_HOST) port = config.get(CONF_PORT) - url = 'ws://{}:{}'.format(host, port) + url = "ws://{}:{}".format(host, port) if not code: request_configuration(hass, config, url, add_entities) return - if 'gpmdp' in _CONFIGURING: + if "gpmdp" in _CONFIGURING: configurator = hass.components.configurator - configurator.request_done(_CONFIGURING.pop('gpmdp')) + configurator.request_done(_CONFIGURING.pop("gpmdp")) add_entities([GPMDP(name, url, code)], True) @@ -120,9 +159,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the GPMDP platform.""" codeconfig = load_json(hass.config.path(GPMDP_CONFIG_FILE)) if codeconfig: - code = codeconfig.get('CODE') + code = codeconfig.get("CODE") elif discovery_info is not None: - if 'gpmdp' in _CONFIGURING: + if "gpmdp" in _CONFIGURING: return code = None else: @@ -136,6 +175,7 @@ class GPMDP(MediaPlayerDevice): def __init__(self, name, url, code): """Initialize the media player.""" from websocket import create_connection + self._connection = create_connection self._url = url self._authorization_code = code @@ -156,40 +196,52 @@ class GPMDP(MediaPlayerDevice): if self._ws is None: try: self._ws = self._connection((self._url), timeout=1) - msg = json.dumps({'namespace': 'connect', - 'method': 'connect', - 'arguments': ['Home Assistant', - self._authorization_code]}) + msg = json.dumps( + { + "namespace": "connect", + "method": "connect", + "arguments": ["Home Assistant", self._authorization_code], + } + ) self._ws.send(msg) - except (socket.timeout, ConnectionRefusedError, - ConnectionResetError): + except (socket.timeout, ConnectionRefusedError, ConnectionResetError): self._ws = None return self._ws def send_gpmdp_msg(self, namespace, method, with_id=True): """Send ws messages to GPMDP and verify request id in response.""" from websocket import _exceptions + try: websocket = self.get_ws() if websocket is None: self._status = STATE_OFF return self._request_id += 1 - websocket.send(json.dumps({'namespace': namespace, - 'method': method, - 'requestID': self._request_id})) + websocket.send( + json.dumps( + { + "namespace": namespace, + "method": method, + "requestID": self._request_id, + } + ) + ) if not with_id: return while True: msg = json.loads(websocket.recv()) - if 'requestID' in msg: - if msg['requestID'] == self._request_id: + if "requestID" in msg: + if msg["requestID"] == self._request_id: return msg - except (ConnectionRefusedError, ConnectionResetError, - _exceptions.WebSocketTimeoutException, - _exceptions.WebSocketProtocolException, - _exceptions.WebSocketPayloadException, - _exceptions.WebSocketConnectionClosedException): + except ( + ConnectionRefusedError, + ConnectionResetError, + _exceptions.WebSocketTimeoutException, + _exceptions.WebSocketProtocolException, + _exceptions.WebSocketPayloadException, + _exceptions.WebSocketConnectionClosedException, + ): self._ws = None def update(self): @@ -197,22 +249,22 @@ class GPMDP(MediaPlayerDevice): time.sleep(1) try: self._available = True - playstate = self.send_gpmdp_msg('playback', 'getPlaybackState') + playstate = self.send_gpmdp_msg("playback", "getPlaybackState") if playstate is None: return - self._status = PLAYBACK_DICT[str(playstate['value'])] - time_data = self.send_gpmdp_msg('playback', 'getCurrentTime') + self._status = PLAYBACK_DICT[str(playstate["value"])] + time_data = self.send_gpmdp_msg("playback", "getCurrentTime") if time_data is not None: - self._seek_position = int(time_data['value'] / 1000) - track_data = self.send_gpmdp_msg('playback', 'getCurrentTrack') + self._seek_position = int(time_data["value"] / 1000) + track_data = self.send_gpmdp_msg("playback", "getCurrentTrack") if track_data is not None: - self._title = track_data['value']['title'] - self._artist = track_data['value']['artist'] - self._albumart = track_data['value']['albumArt'] - self._duration = int(track_data['value']['duration'] / 1000) - volume_data = self.send_gpmdp_msg('volume', 'getVolume') + self._title = track_data["value"]["title"] + self._artist = track_data["value"]["artist"] + self._albumart = track_data["value"]["albumArt"] + self._duration = int(track_data["value"]["duration"] / 1000) + volume_data = self.send_gpmdp_msg("volume", "getVolume") if volume_data is not None: - self._volume = volume_data['value'] / 100 + self._volume = volume_data["value"] / 100 except OSError: self._available = False @@ -273,21 +325,21 @@ class GPMDP(MediaPlayerDevice): def media_next_track(self): """Send media_next command to media player.""" - self.send_gpmdp_msg('playback', 'forward', False) + self.send_gpmdp_msg("playback", "forward", False) def media_previous_track(self): """Send media_previous command to media player.""" - self.send_gpmdp_msg('playback', 'rewind', False) + self.send_gpmdp_msg("playback", "rewind", False) def media_play(self): """Send media_play command to media player.""" - self.send_gpmdp_msg('playback', 'playPause', False) + self.send_gpmdp_msg("playback", "playPause", False) self._status = STATE_PLAYING self.schedule_update_ha_state() def media_pause(self): """Send media_pause command to media player.""" - self.send_gpmdp_msg('playback', 'playPause', False) + self.send_gpmdp_msg("playback", "playPause", False) self._status = STATE_PAUSED self.schedule_update_ha_state() @@ -296,9 +348,15 @@ class GPMDP(MediaPlayerDevice): websocket = self.get_ws() if websocket is None: return - websocket.send(json.dumps({'namespace': 'playback', - 'method': 'setCurrentTime', - 'arguments': [position*1000]})) + websocket.send( + json.dumps( + { + "namespace": "playback", + "method": "setCurrentTime", + "arguments": [position * 1000], + } + ) + ) self.schedule_update_ha_state() def volume_up(self): @@ -322,7 +380,13 @@ class GPMDP(MediaPlayerDevice): websocket = self.get_ws() if websocket is None: return - websocket.send(json.dumps({'namespace': 'volume', - 'method': 'setVolume', - 'arguments': [volume*100]})) + websocket.send( + json.dumps( + { + "namespace": "volume", + "method": "setVolume", + "arguments": [volume * 100], + } + ) + ) self.schedule_update_ha_state() diff --git a/homeassistant/components/gpsd/sensor.py b/homeassistant/components/gpsd/sensor.py index cccf59a822a..ab4545256ae 100644 --- a/homeassistant/components/gpsd/sensor.py +++ b/homeassistant/components/gpsd/sensor.py @@ -5,28 +5,34 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - ATTR_LATITUDE, ATTR_LONGITUDE, CONF_HOST, CONF_PORT, - CONF_NAME) + ATTR_LATITUDE, + ATTR_LONGITUDE, + CONF_HOST, + CONF_PORT, + CONF_NAME, +) from homeassistant.helpers.entity import Entity import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -ATTR_CLIMB = 'climb' -ATTR_ELEVATION = 'elevation' -ATTR_GPS_TIME = 'gps_time' -ATTR_MODE = 'mode' -ATTR_SPEED = 'speed' +ATTR_CLIMB = "climb" +ATTR_ELEVATION = "elevation" +ATTR_GPS_TIME = "gps_time" +ATTR_MODE = "mode" +ATTR_SPEED = "speed" -DEFAULT_HOST = 'localhost' -DEFAULT_NAME = 'GPS' +DEFAULT_HOST = "localhost" +DEFAULT_NAME = "GPS" DEFAULT_PORT = 2947 -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): diff --git a/homeassistant/components/gpslogger/__init__.py b/homeassistant/components/gpslogger/__init__.py index 869b4b66987..839adec2f5b 100644 --- a/homeassistant/components/gpslogger/__init__.py +++ b/homeassistant/components/gpslogger/__init__.py @@ -6,8 +6,13 @@ from aiohttp import web import homeassistant.helpers.config_validation as cv from homeassistant.components.device_tracker import ATTR_BATTERY -from homeassistant.const import HTTP_UNPROCESSABLE_ENTITY, \ - HTTP_OK, ATTR_LATITUDE, ATTR_LONGITUDE, CONF_WEBHOOK_ID +from homeassistant.const import ( + HTTP_UNPROCESSABLE_ENTITY, + HTTP_OK, + ATTR_LATITUDE, + ATTR_LONGITUDE, + CONF_WEBHOOK_ID, +) from homeassistant.helpers import config_entry_flow from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.components.device_tracker import DOMAIN as DEVICE_TRACKER @@ -24,7 +29,7 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -TRACKER_UPDATE = '{}_tracker_update'.format(DOMAIN) +TRACKER_UPDATE = "{}_tracker_update".format(DOMAIN) DEFAULT_ACCURACY = 200 @@ -33,29 +38,28 @@ DEFAULT_BATTERY = -1 def _id(value: str) -> str: """Coerce id by removing '-'.""" - return value.replace('-', '') + return value.replace("-", "") -WEBHOOK_SCHEMA = vol.Schema({ - vol.Required(ATTR_DEVICE): _id, - vol.Required(ATTR_LATITUDE): cv.latitude, - vol.Required(ATTR_LONGITUDE): cv.longitude, - vol.Optional(ATTR_ACCURACY, default=DEFAULT_ACCURACY): vol.Coerce(float), - vol.Optional(ATTR_ACTIVITY): cv.string, - vol.Optional(ATTR_ALTITUDE): vol.Coerce(float), - vol.Optional(ATTR_BATTERY, default=DEFAULT_BATTERY): vol.Coerce(float), - vol.Optional(ATTR_DIRECTION): vol.Coerce(float), - vol.Optional(ATTR_PROVIDER): cv.string, - vol.Optional(ATTR_SPEED): vol.Coerce(float), -}) +WEBHOOK_SCHEMA = vol.Schema( + { + vol.Required(ATTR_DEVICE): _id, + vol.Required(ATTR_LATITUDE): cv.latitude, + vol.Required(ATTR_LONGITUDE): cv.longitude, + vol.Optional(ATTR_ACCURACY, default=DEFAULT_ACCURACY): vol.Coerce(float), + vol.Optional(ATTR_ACTIVITY): cv.string, + vol.Optional(ATTR_ALTITUDE): vol.Coerce(float), + vol.Optional(ATTR_BATTERY, default=DEFAULT_BATTERY): vol.Coerce(float), + vol.Optional(ATTR_DIRECTION): vol.Coerce(float), + vol.Optional(ATTR_PROVIDER): cv.string, + vol.Optional(ATTR_SPEED): vol.Coerce(float), + } +) async def async_setup(hass, hass_config): """Set up the GPSLogger component.""" - hass.data[DOMAIN] = { - 'devices': set(), - 'unsub_device_tracker': {}, - } + hass.data[DOMAIN] = {"devices": set(), "unsub_device_tracker": {}} return True @@ -64,36 +68,36 @@ async def handle_webhook(hass, webhook_id, request): try: data = WEBHOOK_SCHEMA(dict(await request.post())) except vol.MultipleInvalid as error: - return web.Response( - text=error.error_message, - status=HTTP_UNPROCESSABLE_ENTITY - ) + return web.Response(text=error.error_message, status=HTTP_UNPROCESSABLE_ENTITY) attrs = { ATTR_SPEED: data.get(ATTR_SPEED), ATTR_DIRECTION: data.get(ATTR_DIRECTION), ATTR_ALTITUDE: data.get(ATTR_ALTITUDE), ATTR_PROVIDER: data.get(ATTR_PROVIDER), - ATTR_ACTIVITY: data.get(ATTR_ACTIVITY) + ATTR_ACTIVITY: data.get(ATTR_ACTIVITY), } device = data[ATTR_DEVICE] async_dispatcher_send( - hass, TRACKER_UPDATE, device, + hass, + TRACKER_UPDATE, + device, (data[ATTR_LATITUDE], data[ATTR_LONGITUDE]), - data[ATTR_BATTERY], data[ATTR_ACCURACY], attrs) - - return web.Response( - text='Setting location for {}'.format(device), - status=HTTP_OK + data[ATTR_BATTERY], + data[ATTR_ACCURACY], + attrs, ) + return web.Response(text="Setting location for {}".format(device), status=HTTP_OK) + async def async_setup_entry(hass, entry): """Configure based on config entry.""" hass.components.webhook.async_register( - DOMAIN, 'GPSLogger', entry.data[CONF_WEBHOOK_ID], handle_webhook) + DOMAIN, "GPSLogger", entry.data[CONF_WEBHOOK_ID], handle_webhook + ) hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, DEVICE_TRACKER) @@ -104,7 +108,7 @@ async def async_setup_entry(hass, entry): async def async_unload_entry(hass, entry): """Unload a config entry.""" hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID]) - hass.data[DOMAIN]['unsub_device_tracker'].pop(entry.entry_id)() + hass.data[DOMAIN]["unsub_device_tracker"].pop(entry.entry_id)() await hass.config_entries.async_forward_entry_unload(entry, DEVICE_TRACKER) return True diff --git a/homeassistant/components/gpslogger/config_flow.py b/homeassistant/components/gpslogger/config_flow.py index f48d9abc680..a572bddd0e9 100644 --- a/homeassistant/components/gpslogger/config_flow.py +++ b/homeassistant/components/gpslogger/config_flow.py @@ -5,8 +5,6 @@ from .const import DOMAIN config_entry_flow.register_webhook_flow( DOMAIN, - 'GPSLogger Webhook', - { - 'docs_url': 'https://www.home-assistant.io/components/gpslogger/' - } + "GPSLogger Webhook", + {"docs_url": "https://www.home-assistant.io/components/gpslogger/"}, ) diff --git a/homeassistant/components/gpslogger/const.py b/homeassistant/components/gpslogger/const.py index 870c5310f29..48dc9e7a431 100644 --- a/homeassistant/components/gpslogger/const.py +++ b/homeassistant/components/gpslogger/const.py @@ -1,11 +1,11 @@ """Const for GPSLogger.""" -DOMAIN = 'gpslogger' +DOMAIN = "gpslogger" -ATTR_ALTITUDE = 'altitude' -ATTR_ACCURACY = 'accuracy' -ATTR_ACTIVITY = 'activity' -ATTR_DEVICE = 'device' -ATTR_DIRECTION = 'direction' -ATTR_PROVIDER = 'provider' -ATTR_SPEED = 'speed' +ATTR_ALTITUDE = "altitude" +ATTR_ACCURACY = "accuracy" +ATTR_ACTIVITY = "activity" +ATTR_DEVICE = "device" +ATTR_DIRECTION = "direction" +ATTR_PROVIDER = "provider" +ATTR_SPEED = "speed" diff --git a/homeassistant/components/gpslogger/device_tracker.py b/homeassistant/components/gpslogger/device_tracker.py index 254c9d2b391..c9dbbfee026 100644 --- a/homeassistant/components/gpslogger/device_tracker.py +++ b/homeassistant/components/gpslogger/device_tracker.py @@ -9,9 +9,7 @@ from homeassistant.const import ( ATTR_LONGITUDE, ) from homeassistant.components.device_tracker import SOURCE_TYPE_GPS -from homeassistant.components.device_tracker.config_entry import ( - TrackerEntity -) +from homeassistant.components.device_tracker.config_entry import TrackerEntity from homeassistant.helpers import device_registry from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.restore_state import RestoreEntity @@ -29,23 +27,22 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -async def async_setup_entry(hass: HomeAssistantType, entry, - async_add_entities): +async def async_setup_entry(hass: HomeAssistantType, entry, async_add_entities): """Configure a dispatcher connection based on a config entry.""" + @callback def _receive_data(device, gps, battery, accuracy, attrs): """Receive set location.""" - if device in hass.data[GPL_DOMAIN]['devices']: + if device in hass.data[GPL_DOMAIN]["devices"]: return - hass.data[GPL_DOMAIN]['devices'].add(device) + hass.data[GPL_DOMAIN]["devices"].add(device) - async_add_entities([GPSLoggerEntity( - device, gps, battery, accuracy, attrs - )]) + async_add_entities([GPSLoggerEntity(device, gps, battery, accuracy, attrs)]) - hass.data[GPL_DOMAIN]['unsub_device_tracker'][entry.entry_id] = \ - async_dispatcher_connect(hass, TRACKER_UPDATE, _receive_data) + hass.data[GPL_DOMAIN]["unsub_device_tracker"][ + entry.entry_id + ] = async_dispatcher_connect(hass, TRACKER_UPDATE, _receive_data) # Restore previously loaded devices dev_reg = await device_registry.async_get_registry(hass) @@ -60,7 +57,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry, entities = [] for dev_id in dev_ids: - hass.data[GPL_DOMAIN]['devices'].add(dev_id) + hass.data[GPL_DOMAIN]["devices"].add(dev_id) entity = GPSLoggerEntity(dev_id, None, None, None, None) entities.append(entity) @@ -70,8 +67,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry, class GPSLoggerEntity(TrackerEntity, RestoreEntity): """Represent a tracked device.""" - def __init__( - self, device, location, battery, accuracy, attributes): + def __init__(self, device, location, battery, accuracy, attributes): """Set up Geofency entity.""" self._accuracy = accuracy self._attributes = attributes @@ -124,10 +120,7 @@ class GPSLoggerEntity(TrackerEntity, RestoreEntity): @property def device_info(self): """Return the device info.""" - return { - 'name': self._name, - 'identifiers': {(GPL_DOMAIN, self._unique_id)}, - } + return {"name": self._name, "identifiers": {(GPL_DOMAIN, self._unique_id)}} @property def source_type(self): @@ -138,7 +131,8 @@ class GPSLoggerEntity(TrackerEntity, RestoreEntity): """Register state update callback.""" await super().async_added_to_hass() self._unsub_dispatcher = async_dispatcher_connect( - self.hass, TRACKER_UPDATE, self._async_receive_data) + self.hass, TRACKER_UPDATE, self._async_receive_data + ) # don't restore if we got created with data if self._location is not None: @@ -159,10 +153,7 @@ class GPSLoggerEntity(TrackerEntity, RestoreEntity): return attr = state.attributes - self._location = ( - attr.get(ATTR_LATITUDE), - attr.get(ATTR_LONGITUDE), - ) + self._location = (attr.get(ATTR_LATITUDE), attr.get(ATTR_LONGITUDE)) self._accuracy = attr.get(ATTR_GPS_ACCURACY) self._attributes = { ATTR_ALTITUDE: attr.get(ATTR_ALTITUDE), @@ -179,8 +170,7 @@ class GPSLoggerEntity(TrackerEntity, RestoreEntity): self._unsub_dispatcher() @callback - def _async_receive_data(self, device, location, battery, accuracy, - attributes): + def _async_receive_data(self, device, location, battery, accuracy, attributes): """Mark the device as seen.""" if device != self.name: return diff --git a/homeassistant/components/graphite/__init__.py b/homeassistant/components/graphite/__init__.py index e3f9e359f5a..550a0ce1d13 100644 --- a/homeassistant/components/graphite/__init__.py +++ b/homeassistant/components/graphite/__init__.py @@ -9,24 +9,34 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.const import ( - CONF_HOST, CONF_PORT, CONF_PREFIX, EVENT_HOMEASSISTANT_START, - EVENT_HOMEASSISTANT_STOP, EVENT_STATE_CHANGED) + CONF_HOST, + CONF_PORT, + CONF_PREFIX, + EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP, + EVENT_STATE_CHANGED, +) from homeassistant.helpers import state _LOGGER = logging.getLogger(__name__) -DEFAULT_HOST = 'localhost' +DEFAULT_HOST = "localhost" DEFAULT_PORT = 2003 -DEFAULT_PREFIX = 'ha' -DOMAIN = 'graphite' +DEFAULT_PREFIX = "ha" +DOMAIN = "graphite" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_PREFIX, default=DEFAULT_PREFIX): cv.string, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_PREFIX, default=DEFAULT_PREFIX): cv.string, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): @@ -59,7 +69,7 @@ class GraphiteFeeder(threading.Thread): self._host = host self._port = port # rstrip any trailing dots in case they think they need it - self._prefix = prefix.rstrip('.') + self._prefix = prefix.rstrip(".") self._queue = queue.Queue() self._quit_object = object() self._we_started = False @@ -67,8 +77,7 @@ class GraphiteFeeder(threading.Thread): hass.bus.listen_once(EVENT_HOMEASSISTANT_START, self.start_listen) hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, self.shutdown) hass.bus.listen(EVENT_STATE_CHANGED, self.event_listener) - _LOGGER.debug("Graphite feeding to %s:%i initialized", - self._host, self._port) + _LOGGER.debug("Graphite feeding to %s:%i initialized", self._host, self._port) def start_listen(self, event): """Start event-processing thread.""" @@ -87,16 +96,15 @@ class GraphiteFeeder(threading.Thread): _LOGGER.debug("Received event") self._queue.put(event) else: - _LOGGER.error( - "Graphite feeder thread has died, not queuing event") + _LOGGER.error("Graphite feeder thread has died, not queuing event") def _send_to_graphite(self, data): """Send data to Graphite.""" sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(10) sock.connect((self._host, self._port)) - sock.sendall(data.encode('ascii')) - sock.send('\n'.encode('ascii')) + sock.sendall(data.encode("ascii")) + sock.send("\n".encode("ascii")) sock.close() def _report_attributes(self, entity_id, new_state): @@ -104,19 +112,20 @@ class GraphiteFeeder(threading.Thread): now = time.time() things = dict(new_state.attributes) try: - things['state'] = state.state_as_number(new_state) + things["state"] = state.state_as_number(new_state) except ValueError: pass - lines = ['%s.%s.%s %f %i' % (self._prefix, - entity_id, key.replace(' ', '_'), - value, now) - for key, value in things.items() - if isinstance(value, (float, int))] + lines = [ + "%s.%s.%s %f %i" + % (self._prefix, entity_id, key.replace(" ", "_"), value, now) + for key, value in things.items() + if isinstance(value, (float, int)) + ] if not lines: return _LOGGER.debug("Sending to graphite: %s", lines) try: - self._send_to_graphite('\n'.join(lines)) + self._send_to_graphite("\n".join(lines)) except socket.gaierror: _LOGGER.error("Unable to connect to host %s", self._host) except socket.error: @@ -130,19 +139,19 @@ class GraphiteFeeder(threading.Thread): _LOGGER.debug("Event processing thread stopped") self._queue.task_done() return - if event.event_type == EVENT_STATE_CHANGED and \ - event.data.get('new_state'): - _LOGGER.debug("Processing STATE_CHANGED event for %s", - event.data['entity_id']) + if event.event_type == EVENT_STATE_CHANGED and event.data.get("new_state"): + _LOGGER.debug( + "Processing STATE_CHANGED event for %s", event.data["entity_id"] + ) try: self._report_attributes( - event.data['entity_id'], event.data['new_state']) + event.data["entity_id"], event.data["new_state"] + ) except Exception: # pylint: disable=broad-except # Catch this so we can avoid the thread dying and # make it visible. _LOGGER.exception("Failed to process STATE_CHANGED event") else: - _LOGGER.warning( - "Processing unexpected event type %s", event.event_type) + _LOGGER.warning("Processing unexpected event type %s", event.event_type) self._queue.task_done() diff --git a/homeassistant/components/greeneye_monitor/__init__.py b/homeassistant/components/greeneye_monitor/__init__.py index 0f12c3cd479..cb67ac7faa4 100644 --- a/homeassistant/components/greeneye_monitor/__init__.py +++ b/homeassistant/components/greeneye_monitor/__init__.py @@ -7,102 +7,105 @@ from homeassistant.const import ( CONF_NAME, CONF_PORT, CONF_TEMPERATURE_UNIT, - EVENT_HOMEASSISTANT_STOP) + EVENT_HOMEASSISTANT_STOP, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.discovery import async_load_platform _LOGGER = logging.getLogger(__name__) -CONF_CHANNELS = 'channels' -CONF_COUNTED_QUANTITY = 'counted_quantity' -CONF_COUNTED_QUANTITY_PER_PULSE = 'counted_quantity_per_pulse' -CONF_MONITOR_SERIAL_NUMBER = 'monitor' -CONF_MONITORS = 'monitors' -CONF_NET_METERING = 'net_metering' -CONF_NUMBER = 'number' -CONF_PULSE_COUNTERS = 'pulse_counters' -CONF_SERIAL_NUMBER = 'serial_number' -CONF_SENSORS = 'sensors' -CONF_SENSOR_TYPE = 'sensor_type' -CONF_TEMPERATURE_SENSORS = 'temperature_sensors' -CONF_TIME_UNIT = 'time_unit' +CONF_CHANNELS = "channels" +CONF_COUNTED_QUANTITY = "counted_quantity" +CONF_COUNTED_QUANTITY_PER_PULSE = "counted_quantity_per_pulse" +CONF_MONITOR_SERIAL_NUMBER = "monitor" +CONF_MONITORS = "monitors" +CONF_NET_METERING = "net_metering" +CONF_NUMBER = "number" +CONF_PULSE_COUNTERS = "pulse_counters" +CONF_SERIAL_NUMBER = "serial_number" +CONF_SENSORS = "sensors" +CONF_SENSOR_TYPE = "sensor_type" +CONF_TEMPERATURE_SENSORS = "temperature_sensors" +CONF_TIME_UNIT = "time_unit" -DATA_GREENEYE_MONITOR = 'greeneye_monitor' -DOMAIN = 'greeneye_monitor' +DATA_GREENEYE_MONITOR = "greeneye_monitor" +DOMAIN = "greeneye_monitor" -SENSOR_TYPE_CURRENT = 'current_sensor' -SENSOR_TYPE_PULSE_COUNTER = 'pulse_counter' -SENSOR_TYPE_TEMPERATURE = 'temperature_sensor' +SENSOR_TYPE_CURRENT = "current_sensor" +SENSOR_TYPE_PULSE_COUNTER = "pulse_counter" +SENSOR_TYPE_TEMPERATURE = "temperature_sensor" -TEMPERATURE_UNIT_CELSIUS = 'C' +TEMPERATURE_UNIT_CELSIUS = "C" -TIME_UNIT_SECOND = 's' -TIME_UNIT_MINUTE = 'min' -TIME_UNIT_HOUR = 'h' +TIME_UNIT_SECOND = "s" +TIME_UNIT_MINUTE = "min" +TIME_UNIT_HOUR = "h" -TEMPERATURE_SENSOR_SCHEMA = vol.Schema({ - vol.Required(CONF_NUMBER): vol.Range(1, 8), - vol.Required(CONF_NAME): cv.string, -}) +TEMPERATURE_SENSOR_SCHEMA = vol.Schema( + {vol.Required(CONF_NUMBER): vol.Range(1, 8), vol.Required(CONF_NAME): cv.string} +) -TEMPERATURE_SENSORS_SCHEMA = vol.Schema({ - vol.Required(CONF_TEMPERATURE_UNIT): cv.temperature_unit, - vol.Required(CONF_SENSORS): vol.All(cv.ensure_list, - [TEMPERATURE_SENSOR_SCHEMA]), -}) +TEMPERATURE_SENSORS_SCHEMA = vol.Schema( + { + vol.Required(CONF_TEMPERATURE_UNIT): cv.temperature_unit, + vol.Required(CONF_SENSORS): vol.All( + cv.ensure_list, [TEMPERATURE_SENSOR_SCHEMA] + ), + } +) -PULSE_COUNTER_SCHEMA = vol.Schema({ - vol.Required(CONF_NUMBER): vol.Range(1, 4), - vol.Required(CONF_NAME): cv.string, - vol.Required(CONF_COUNTED_QUANTITY): cv.string, - vol.Optional( - CONF_COUNTED_QUANTITY_PER_PULSE, default=1.0): vol.Coerce(float), - vol.Optional(CONF_TIME_UNIT, default=TIME_UNIT_SECOND): vol.Any( - TIME_UNIT_SECOND, - TIME_UNIT_MINUTE, - TIME_UNIT_HOUR), -}) +PULSE_COUNTER_SCHEMA = vol.Schema( + { + vol.Required(CONF_NUMBER): vol.Range(1, 4), + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_COUNTED_QUANTITY): cv.string, + vol.Optional(CONF_COUNTED_QUANTITY_PER_PULSE, default=1.0): vol.Coerce(float), + vol.Optional(CONF_TIME_UNIT, default=TIME_UNIT_SECOND): vol.Any( + TIME_UNIT_SECOND, TIME_UNIT_MINUTE, TIME_UNIT_HOUR + ), + } +) PULSE_COUNTERS_SCHEMA = vol.All(cv.ensure_list, [PULSE_COUNTER_SCHEMA]) -CHANNEL_SCHEMA = vol.Schema({ - vol.Required(CONF_NUMBER): vol.Range(1, 48), - vol.Required(CONF_NAME): cv.string, - vol.Optional(CONF_NET_METERING, default=False): cv.boolean, -}) +CHANNEL_SCHEMA = vol.Schema( + { + vol.Required(CONF_NUMBER): vol.Range(1, 48), + vol.Required(CONF_NAME): cv.string, + vol.Optional(CONF_NET_METERING, default=False): cv.boolean, + } +) CHANNELS_SCHEMA = vol.All(cv.ensure_list, [CHANNEL_SCHEMA]) -MONITOR_SCHEMA = vol.Schema({ - vol.Required(CONF_SERIAL_NUMBER): - vol.All( +MONITOR_SCHEMA = vol.Schema( + { + vol.Required(CONF_SERIAL_NUMBER): vol.All( cv.string, vol.Length( min=8, max=8, msg="GEM serial number must be specified as an 8-character " - "string (including leading zeroes)."), - vol.Coerce(int)), - vol.Optional(CONF_CHANNELS, default=[]): CHANNELS_SCHEMA, - vol.Optional( - CONF_TEMPERATURE_SENSORS, - default={ - CONF_TEMPERATURE_UNIT: TEMPERATURE_UNIT_CELSIUS, - CONF_SENSORS: [], - }): TEMPERATURE_SENSORS_SCHEMA, - vol.Optional(CONF_PULSE_COUNTERS, default=[]): PULSE_COUNTERS_SCHEMA, -}) + "string (including leading zeroes).", + ), + vol.Coerce(int), + ), + vol.Optional(CONF_CHANNELS, default=[]): CHANNELS_SCHEMA, + vol.Optional( + CONF_TEMPERATURE_SENSORS, + default={CONF_TEMPERATURE_UNIT: TEMPERATURE_UNIT_CELSIUS, CONF_SENSORS: []}, + ): TEMPERATURE_SENSORS_SCHEMA, + vol.Optional(CONF_PULSE_COUNTERS, default=[]): PULSE_COUNTERS_SCHEMA, + } +) MONITORS_SCHEMA = vol.All(cv.ensure_list, [MONITOR_SCHEMA]) -COMPONENT_SCHEMA = vol.Schema({ - vol.Required(CONF_PORT): cv.port, - vol.Required(CONF_MONITORS): MONITORS_SCHEMA, -}) +COMPONENT_SCHEMA = vol.Schema( + {vol.Required(CONF_PORT): cv.port, vol.Required(CONF_MONITORS): MONITORS_SCHEMA} +) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: COMPONENT_SCHEMA, -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema({DOMAIN: COMPONENT_SCHEMA}, extra=vol.ALLOW_EXTRA) async def async_setup(hass, config): @@ -124,49 +127,53 @@ async def async_setup(hass, config): all_sensors = [] for monitor_config in server_config[CONF_MONITORS]: monitor_serial_number = { - CONF_MONITOR_SERIAL_NUMBER: monitor_config[CONF_SERIAL_NUMBER], + CONF_MONITOR_SERIAL_NUMBER: monitor_config[CONF_SERIAL_NUMBER] } channel_configs = monitor_config[CONF_CHANNELS] for channel_config in channel_configs: - all_sensors.append({ - CONF_SENSOR_TYPE: SENSOR_TYPE_CURRENT, - **monitor_serial_number, - **channel_config, - }) + all_sensors.append( + { + CONF_SENSOR_TYPE: SENSOR_TYPE_CURRENT, + **monitor_serial_number, + **channel_config, + } + ) - sensor_configs = \ - monitor_config[CONF_TEMPERATURE_SENSORS] + sensor_configs = monitor_config[CONF_TEMPERATURE_SENSORS] if sensor_configs: temperature_unit = { - CONF_TEMPERATURE_UNIT: sensor_configs[CONF_TEMPERATURE_UNIT], + CONF_TEMPERATURE_UNIT: sensor_configs[CONF_TEMPERATURE_UNIT] } for sensor_config in sensor_configs[CONF_SENSORS]: - all_sensors.append({ - CONF_SENSOR_TYPE: SENSOR_TYPE_TEMPERATURE, - **monitor_serial_number, - **temperature_unit, - **sensor_config, - }) + all_sensors.append( + { + CONF_SENSOR_TYPE: SENSOR_TYPE_TEMPERATURE, + **monitor_serial_number, + **temperature_unit, + **sensor_config, + } + ) counter_configs = monitor_config[CONF_PULSE_COUNTERS] for counter_config in counter_configs: - all_sensors.append({ - CONF_SENSOR_TYPE: SENSOR_TYPE_PULSE_COUNTER, - **monitor_serial_number, - **counter_config, - }) + all_sensors.append( + { + CONF_SENSOR_TYPE: SENSOR_TYPE_PULSE_COUNTER, + **monitor_serial_number, + **counter_config, + } + ) if not all_sensors: - _LOGGER.error("Configuration must specify at least one " - "channel, pulse counter or temperature sensor") + _LOGGER.error( + "Configuration must specify at least one " + "channel, pulse counter or temperature sensor" + ) return False - hass.async_create_task(async_load_platform( - hass, - 'sensor', - DOMAIN, - all_sensors, - config)) + hass.async_create_task( + async_load_platform(hass, "sensor", DOMAIN, all_sensors, config) + ) return True diff --git a/homeassistant/components/greeneye_monitor/sensor.py b/homeassistant/components/greeneye_monitor/sensor.py index 499d5351ad4..c4b5fc67898 100644 --- a/homeassistant/components/greeneye_monitor/sensor.py +++ b/homeassistant/components/greeneye_monitor/sensor.py @@ -23,21 +23,17 @@ from . import ( _LOGGER = logging.getLogger(__name__) -DATA_PULSES = 'pulses' -DATA_WATT_SECONDS = 'watt_seconds' +DATA_PULSES = "pulses" +DATA_WATT_SECONDS = "watt_seconds" UNIT_WATTS = POWER_WATT -COUNTER_ICON = 'mdi:counter' -CURRENT_SENSOR_ICON = 'mdi:flash' -TEMPERATURE_ICON = 'mdi:thermometer' +COUNTER_ICON = "mdi:counter" +CURRENT_SENSOR_ICON = "mdi:flash" +TEMPERATURE_ICON = "mdi:thermometer" -async def async_setup_platform( - hass, - config, - async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up a single GEM temperature sensor.""" if not discovery_info: return @@ -46,25 +42,34 @@ async def async_setup_platform( for sensor in discovery_info: sensor_type = sensor[CONF_SENSOR_TYPE] if sensor_type == SENSOR_TYPE_CURRENT: - entities.append(CurrentSensor( - sensor[CONF_MONITOR_SERIAL_NUMBER], - sensor[CONF_NUMBER], - sensor[CONF_NAME], - sensor[CONF_NET_METERING])) + entities.append( + CurrentSensor( + sensor[CONF_MONITOR_SERIAL_NUMBER], + sensor[CONF_NUMBER], + sensor[CONF_NAME], + sensor[CONF_NET_METERING], + ) + ) elif sensor_type == SENSOR_TYPE_PULSE_COUNTER: - entities.append(PulseCounter( - sensor[CONF_MONITOR_SERIAL_NUMBER], - sensor[CONF_NUMBER], - sensor[CONF_NAME], - sensor[CONF_COUNTED_QUANTITY], - sensor[CONF_TIME_UNIT], - sensor[CONF_COUNTED_QUANTITY_PER_PULSE])) + entities.append( + PulseCounter( + sensor[CONF_MONITOR_SERIAL_NUMBER], + sensor[CONF_NUMBER], + sensor[CONF_NAME], + sensor[CONF_COUNTED_QUANTITY], + sensor[CONF_TIME_UNIT], + sensor[CONF_COUNTED_QUANTITY_PER_PULSE], + ) + ) elif sensor_type == SENSOR_TYPE_TEMPERATURE: - entities.append(TemperatureSensor( - sensor[CONF_MONITOR_SERIAL_NUMBER], - sensor[CONF_NUMBER], - sensor[CONF_NAME], - sensor[CONF_TEMPERATURE_UNIT])) + entities.append( + TemperatureSensor( + sensor[CONF_MONITOR_SERIAL_NUMBER], + sensor[CONF_NUMBER], + sensor[CONF_NAME], + sensor[CONF_TEMPERATURE_UNIT], + ) + ) async_add_entities(entities) @@ -141,7 +146,7 @@ class CurrentSensor(GEMSensor): def __init__(self, monitor_serial_number, number, name, net_metering): """Construct the entity.""" - super().__init__(monitor_serial_number, name, 'current', number) + super().__init__(monitor_serial_number, name, "current", number) self._net_metering = net_metering def _get_sensor(self, monitor): @@ -176,24 +181,23 @@ class CurrentSensor(GEMSensor): else: watt_seconds = self._sensor.absolute_watt_seconds - return { - DATA_WATT_SECONDS: watt_seconds - } + return {DATA_WATT_SECONDS: watt_seconds} class PulseCounter(GEMSensor): """Entity showing rate of change in one pulse counter of the monitor.""" def __init__( - self, - monitor_serial_number, - number, - name, - counted_quantity, - time_unit, - counted_quantity_per_pulse): + self, + monitor_serial_number, + number, + name, + counted_quantity, + time_unit, + counted_quantity_per_pulse, + ): """Construct the entity.""" - super().__init__(monitor_serial_number, name, 'pulse', number) + super().__init__(monitor_serial_number, name, "pulse", number) self._counted_quantity = counted_quantity self._counted_quantity_per_pulse = counted_quantity_per_pulse self._time_unit = time_unit @@ -212,9 +216,11 @@ class PulseCounter(GEMSensor): if not self._sensor or self._sensor.pulses_per_second is None: return None - return (self._sensor.pulses_per_second * - self._counted_quantity_per_pulse * - self._seconds_per_time_unit) + return ( + self._sensor.pulses_per_second + * self._counted_quantity_per_pulse + * self._seconds_per_time_unit + ) @property def _seconds_per_time_unit(self): @@ -230,8 +236,7 @@ class PulseCounter(GEMSensor): def unit_of_measurement(self): """Return the unit of measurement for this pulse counter.""" return "{counted_quantity}/{time_unit}".format( - counted_quantity=self._counted_quantity, - time_unit=self._time_unit, + counted_quantity=self._counted_quantity, time_unit=self._time_unit ) @property @@ -240,9 +245,7 @@ class PulseCounter(GEMSensor): if not self._sensor: return None - return { - DATA_PULSES: self._sensor.pulses - } + return {DATA_PULSES: self._sensor.pulses} class TemperatureSensor(GEMSensor): @@ -250,7 +253,7 @@ class TemperatureSensor(GEMSensor): def __init__(self, monitor_serial_number, number, name, unit): """Construct the entity.""" - super().__init__(monitor_serial_number, name, 'temp', number) + super().__init__(monitor_serial_number, name, "temp", number) self._unit = unit def _get_sensor(self, monitor): diff --git a/homeassistant/components/greenwave/light.py b/homeassistant/components/greenwave/light.py index a8418a01ac2..5a6ce2c51c2 100644 --- a/homeassistant/components/greenwave/light.py +++ b/homeassistant/components/greenwave/light.py @@ -5,21 +5,24 @@ from datetime import timedelta import voluptuous as vol from homeassistant.components.light import ( - ATTR_BRIGHTNESS, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, Light) + ATTR_BRIGHTNESS, + PLATFORM_SCHEMA, + SUPPORT_BRIGHTNESS, + Light, +) from homeassistant.const import CONF_HOST import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -CONF_VERSION = 'version' +CONF_VERSION = "version" SUPPORTED_FEATURES = SUPPORT_BRIGHTNESS -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_VERSION): cv.positive_int, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_HOST): cv.string, vol.Required(CONF_VERSION): cv.positive_int} +) MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1) @@ -28,25 +31,28 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Greenwave Reality Platform.""" import greenwavereality as greenwave import os + host = config.get(CONF_HOST) - tokenfile = hass.config.path('.greenwave') + tokenfile = hass.config.path(".greenwave") if config.get(CONF_VERSION) == 3: if os.path.exists(tokenfile): with open(tokenfile) as tokenfile: token = tokenfile.read() else: try: - token = greenwave.grab_token(host, 'hass', 'homeassistant') + token = greenwave.grab_token(host, "hass", "homeassistant") except PermissionError: - _LOGGER.error('The Gateway Is Not In Sync Mode') + _LOGGER.error("The Gateway Is Not In Sync Mode") raise with open(tokenfile, "w+") as tokenfile: tokenfile.write(token) else: token = None bulbs = greenwave.grab_bulbs(host, token) - add_entities(GreenwaveLight(device, host, token, GatewayData(host, token)) - for device in bulbs.values()) + add_entities( + GreenwaveLight(device, host, token, GatewayData(host, token)) + for device in bulbs.values() + ) class GreenwaveLight(Light): @@ -55,9 +61,10 @@ class GreenwaveLight(Light): def __init__(self, light, host, token, gatewaydata): """Initialize a Greenwave Reality Light.""" import greenwavereality as greenwave - self._did = int(light['did']) - self._name = light['name'] - self._state = int(light['state']) + + self._did = int(light["did"]) + self._name = light["name"] + self._state = int(light["state"]) self._brightness = greenwave.hass_brightness(light) self._host = host self._online = greenwave.check_online(light) @@ -92,27 +99,28 @@ class GreenwaveLight(Light): def turn_on(self, **kwargs): """Instruct the light to turn on.""" import greenwavereality as greenwave - temp_brightness = int((kwargs.get(ATTR_BRIGHTNESS, 255) - / 255) * 100) - greenwave.set_brightness(self._host, self._did, - temp_brightness, self._token) + + temp_brightness = int((kwargs.get(ATTR_BRIGHTNESS, 255) / 255) * 100) + greenwave.set_brightness(self._host, self._did, temp_brightness, self._token) greenwave.turn_on(self._host, self._did, self._token) def turn_off(self, **kwargs): """Instruct the light to turn off.""" import greenwavereality as greenwave + greenwave.turn_off(self._host, self._did, self._token) def update(self): """Fetch new state data for this light.""" import greenwavereality as greenwave + self._gatewaydata.update() bulbs = self._gatewaydata.greenwave - self._state = int(bulbs[self._did]['state']) + self._state = int(bulbs[self._did]["state"]) self._brightness = greenwave.hass_brightness(bulbs[self._did]) self._online = greenwave.check_online(bulbs[self._did]) - self._name = bulbs[self._did]['name'] + self._name = bulbs[self._did]["name"] class GatewayData: @@ -121,6 +129,7 @@ class GatewayData: def __init__(self, host, token): """Initialize the data object.""" import greenwavereality as greenwave + self._host = host self._token = token self._greenwave = greenwave.grab_bulbs(host, token) @@ -134,5 +143,6 @@ class GatewayData: def update(self): """Get the latest data from the gateway.""" import greenwavereality as greenwave + self._greenwave = greenwave.grab_bulbs(self._host, self._token) return self._greenwave diff --git a/homeassistant/components/group/__init__.py b/homeassistant/components/group/__init__.py index 3fab39f56ec..fc10fa2f737 100644 --- a/homeassistant/components/group/__init__.py +++ b/homeassistant/components/group/__init__.py @@ -6,10 +6,25 @@ import voluptuous as vol from homeassistant import core as ha from homeassistant.const import ( - ATTR_ENTITY_ID, CONF_ICON, CONF_NAME, STATE_CLOSED, STATE_HOME, - STATE_NOT_HOME, STATE_OFF, STATE_ON, STATE_OPEN, STATE_LOCKED, - STATE_UNLOCKED, STATE_OK, STATE_PROBLEM, STATE_UNKNOWN, - ATTR_ASSUMED_STATE, SERVICE_RELOAD, ATTR_NAME, ATTR_ICON) + ATTR_ENTITY_ID, + CONF_ICON, + CONF_NAME, + STATE_CLOSED, + STATE_HOME, + STATE_NOT_HOME, + STATE_OFF, + STATE_ON, + STATE_OPEN, + STATE_LOCKED, + STATE_UNLOCKED, + STATE_OK, + STATE_PROBLEM, + STATE_UNKNOWN, + ATTR_ASSUMED_STATE, + SERVICE_RELOAD, + ATTR_NAME, + ATTR_ICON, +) from homeassistant.core import callback from homeassistant.loader import bind_hass from homeassistant.helpers.entity import Entity, async_generate_entity_id @@ -21,52 +36,52 @@ from homeassistant.util.async_ import run_coroutine_threadsafe from .reproduce_state import async_reproduce_states # noqa -DOMAIN = 'group' +DOMAIN = "group" -ENTITY_ID_FORMAT = DOMAIN + '.{}' +ENTITY_ID_FORMAT = DOMAIN + ".{}" -CONF_ENTITIES = 'entities' -CONF_VIEW = 'view' -CONF_CONTROL = 'control' -CONF_ALL = 'all' +CONF_ENTITIES = "entities" +CONF_VIEW = "view" +CONF_CONTROL = "control" +CONF_ALL = "all" -ATTR_ADD_ENTITIES = 'add_entities' -ATTR_AUTO = 'auto' -ATTR_CONTROL = 'control' -ATTR_ENTITIES = 'entities' -ATTR_OBJECT_ID = 'object_id' -ATTR_ORDER = 'order' -ATTR_VIEW = 'view' -ATTR_VISIBLE = 'visible' -ATTR_ALL = 'all' +ATTR_ADD_ENTITIES = "add_entities" +ATTR_AUTO = "auto" +ATTR_CONTROL = "control" +ATTR_ENTITIES = "entities" +ATTR_OBJECT_ID = "object_id" +ATTR_ORDER = "order" +ATTR_VIEW = "view" +ATTR_VISIBLE = "visible" +ATTR_ALL = "all" -SERVICE_SET_VISIBILITY = 'set_visibility' -SERVICE_SET = 'set' -SERVICE_REMOVE = 'remove' +SERVICE_SET_VISIBILITY = "set_visibility" +SERVICE_SET = "set" +SERVICE_REMOVE = "remove" -CONTROL_TYPES = vol.In(['hidden', None]) +CONTROL_TYPES = vol.In(["hidden", None]) -SET_VISIBILITY_SERVICE_SCHEMA = ENTITY_SERVICE_SCHEMA.extend({ - vol.Required(ATTR_VISIBLE): cv.boolean -}) +SET_VISIBILITY_SERVICE_SCHEMA = ENTITY_SERVICE_SCHEMA.extend( + {vol.Required(ATTR_VISIBLE): cv.boolean} +) RELOAD_SERVICE_SCHEMA = vol.Schema({}) -SET_SERVICE_SCHEMA = vol.Schema({ - vol.Required(ATTR_OBJECT_ID): cv.slug, - vol.Optional(ATTR_NAME): cv.string, - vol.Optional(ATTR_VIEW): cv.boolean, - vol.Optional(ATTR_ICON): cv.string, - vol.Optional(ATTR_CONTROL): CONTROL_TYPES, - vol.Optional(ATTR_VISIBLE): cv.boolean, - vol.Optional(ATTR_ALL): cv.boolean, - vol.Exclusive(ATTR_ENTITIES, 'entities'): cv.entity_ids, - vol.Exclusive(ATTR_ADD_ENTITIES, 'entities'): cv.entity_ids, -}) +SET_SERVICE_SCHEMA = vol.Schema( + { + vol.Required(ATTR_OBJECT_ID): cv.slug, + vol.Optional(ATTR_NAME): cv.string, + vol.Optional(ATTR_VIEW): cv.boolean, + vol.Optional(ATTR_ICON): cv.string, + vol.Optional(ATTR_CONTROL): CONTROL_TYPES, + vol.Optional(ATTR_VISIBLE): cv.boolean, + vol.Optional(ATTR_ALL): cv.boolean, + vol.Exclusive(ATTR_ENTITIES, "entities"): cv.entity_ids, + vol.Exclusive(ATTR_ADD_ENTITIES, "entities"): cv.entity_ids, + } +) -REMOVE_SERVICE_SCHEMA = vol.Schema({ - vol.Required(ATTR_OBJECT_ID): cv.slug, -}) +REMOVE_SERVICE_SCHEMA = vol.Schema({vol.Required(ATTR_OBJECT_ID): cv.slug}) _LOGGER = logging.getLogger(__name__) @@ -79,23 +94,30 @@ def _conf_preprocess(value): return value -GROUP_SCHEMA = vol.Schema({ - vol.Optional(CONF_ENTITIES): vol.Any(cv.entity_ids, None), - CONF_VIEW: cv.boolean, - CONF_NAME: cv.string, - CONF_ICON: cv.icon, - CONF_CONTROL: CONTROL_TYPES, - CONF_ALL: cv.boolean, -}) +GROUP_SCHEMA = vol.Schema( + { + vol.Optional(CONF_ENTITIES): vol.Any(cv.entity_ids, None), + CONF_VIEW: cv.boolean, + CONF_NAME: cv.string, + CONF_ICON: cv.icon, + CONF_CONTROL: CONTROL_TYPES, + CONF_ALL: cv.boolean, + } +) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({cv.match_all: vol.All(_conf_preprocess, GROUP_SCHEMA)}) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + {DOMAIN: vol.Schema({cv.match_all: vol.All(_conf_preprocess, GROUP_SCHEMA)})}, + extra=vol.ALLOW_EXTRA, +) # List of ON/OFF state tuples for groupable states -_GROUP_TYPES = [(STATE_ON, STATE_OFF), (STATE_HOME, STATE_NOT_HOME), - (STATE_OPEN, STATE_CLOSED), (STATE_LOCKED, STATE_UNLOCKED), - (STATE_PROBLEM, STATE_OK)] +_GROUP_TYPES = [ + (STATE_ON, STATE_OFF), + (STATE_HOME, STATE_NOT_HOME), + (STATE_OPEN, STATE_CLOSED), + (STATE_LOCKED, STATE_UNLOCKED), + (STATE_PROBLEM, STATE_OK), +] def _get_group_on_off(state): @@ -144,9 +166,10 @@ def expand_entity_ids(hass, entity_ids): child_entities = list(child_entities) child_entities.remove(entity_id) found_ids.extend( - ent_id for ent_id - in expand_entity_ids(hass, child_entities) - if ent_id not in found_ids) + ent_id + for ent_id in expand_entity_ids(hass, child_entities) + if ent_id not in found_ids + ) else: if entity_id not in found_ids: @@ -174,10 +197,9 @@ def get_entity_ids(hass, entity_id, domain_filter=None): if not domain_filter: return entity_ids - domain_filter = domain_filter.lower() + '.' + domain_filter = domain_filter.lower() + "." - return [ent_id for ent_id in entity_ids - if ent_id.startswith(domain_filter)] + return [ent_id for ent_id in entity_ids if ent_id.startswith(domain_filter)] async def async_setup(hass, config): @@ -201,8 +223,8 @@ async def async_setup(hass, config): await component.async_add_entities(auto) hass.services.async_register( - DOMAIN, SERVICE_RELOAD, reload_service_handler, - schema=RELOAD_SERVICE_SCHEMA) + DOMAIN, SERVICE_RELOAD, reload_service_handler, schema=RELOAD_SERVICE_SCHEMA + ) service_lock = asyncio.Lock() @@ -219,26 +241,31 @@ async def async_setup(hass, config): # new group if service.service == SERVICE_SET and group is None: - entity_ids = service.data.get(ATTR_ENTITIES) or \ - service.data.get(ATTR_ADD_ENTITIES) or None + entity_ids = ( + service.data.get(ATTR_ENTITIES) + or service.data.get(ATTR_ADD_ENTITIES) + or None + ) - extra_arg = {attr: service.data[attr] for attr in ( - ATTR_VISIBLE, ATTR_ICON, ATTR_VIEW, ATTR_CONTROL - ) if service.data.get(attr) is not None} + extra_arg = { + attr: service.data[attr] + for attr in (ATTR_VISIBLE, ATTR_ICON, ATTR_VIEW, ATTR_CONTROL) + if service.data.get(attr) is not None + } await Group.async_create_group( - hass, service.data.get(ATTR_NAME, object_id), + hass, + service.data.get(ATTR_NAME, object_id), object_id=object_id, entity_ids=entity_ids, user_defined=False, mode=service.data.get(ATTR_ALL), - **extra_arg + **extra_arg, ) return if group is None: - _LOGGER.warning("%s:Group '%s' doesn't exist!", - service.service, object_id) + _LOGGER.warning("%s:Group '%s' doesn't exist!", service.service, object_id) return # update group @@ -288,12 +315,12 @@ async def async_setup(hass, config): await component.async_remove_entity(entity_id) hass.services.async_register( - DOMAIN, SERVICE_SET, locked_service_handler, - schema=SET_SERVICE_SCHEMA) + DOMAIN, SERVICE_SET, locked_service_handler, schema=SET_SERVICE_SCHEMA + ) hass.services.async_register( - DOMAIN, SERVICE_REMOVE, groups_service_handler, - schema=REMOVE_SERVICE_SCHEMA) + DOMAIN, SERVICE_REMOVE, groups_service_handler, schema=REMOVE_SERVICE_SCHEMA + ) async def visibility_service_handler(service): """Change visibility of a group.""" @@ -301,7 +328,8 @@ async def async_setup(hass, config): tasks = [] for group in await component.async_extract_from_service( - service, expand_group=False): + service, expand_group=False + ): group.visible = visible tasks.append(group.async_update_ha_state()) @@ -309,8 +337,11 @@ async def async_setup(hass, config): await asyncio.wait(tasks) hass.services.async_register( - DOMAIN, SERVICE_SET_VISIBILITY, visibility_service_handler, - schema=SET_VISIBILITY_SERVICE_SCHEMA) + DOMAIN, + SERVICE_SET_VISIBILITY, + visibility_service_handler, + schema=SET_VISIBILITY_SERVICE_SCHEMA, + ) return True @@ -328,16 +359,33 @@ async def _async_process_config(hass, config, component): # Don't create tasks and await them all. The order is important as # groups get a number based on creation order. await Group.async_create_group( - hass, name, entity_ids, icon=icon, view=view, - control=control, object_id=object_id, mode=mode) + hass, + name, + entity_ids, + icon=icon, + view=view, + control=control, + object_id=object_id, + mode=mode, + ) class Group(Entity): """Track a group of entity ids.""" - def __init__(self, hass, name, order=None, visible=True, icon=None, - view=False, control=None, user_defined=True, entity_ids=None, - mode=None): + def __init__( + self, + hass, + name, + order=None, + visible=True, + icon=None, + view=False, + control=None, + user_defined=True, + entity_ids=None, + mode=None, + ): """Initialize a group. This Object has factory function for creation. @@ -364,41 +412,74 @@ class Group(Entity): self._async_unsub_state_changed = None @staticmethod - def create_group(hass, name, entity_ids=None, user_defined=True, - visible=True, icon=None, view=False, control=None, - object_id=None, mode=None): + def create_group( + hass, + name, + entity_ids=None, + user_defined=True, + visible=True, + icon=None, + view=False, + control=None, + object_id=None, + mode=None, + ): """Initialize a group.""" return run_coroutine_threadsafe( Group.async_create_group( - hass, name, entity_ids, user_defined, visible, icon, view, - control, object_id, mode), - hass.loop).result() + hass, + name, + entity_ids, + user_defined, + visible, + icon, + view, + control, + object_id, + mode, + ), + hass.loop, + ).result() @staticmethod - async def async_create_group(hass, name, entity_ids=None, - user_defined=True, visible=True, icon=None, - view=False, control=None, object_id=None, - mode=None): + async def async_create_group( + hass, + name, + entity_ids=None, + user_defined=True, + visible=True, + icon=None, + view=False, + control=None, + object_id=None, + mode=None, + ): """Initialize a group. This method must be run in the event loop. """ group = Group( - hass, name, + hass, + name, order=len(hass.states.async_entity_ids(DOMAIN)), - visible=visible, icon=icon, view=view, control=control, - user_defined=user_defined, entity_ids=entity_ids, mode=mode + visible=visible, + icon=icon, + view=view, + control=control, + user_defined=user_defined, + entity_ids=entity_ids, + mode=mode, ) group.entity_id = async_generate_entity_id( - ENTITY_ID_FORMAT, object_id or name, hass=hass) + ENTITY_ID_FORMAT, object_id or name, hass=hass + ) # If called before the platform async_setup is called (test cases) component = hass.data.get(DOMAIN) if component is None: - component = hass.data[DOMAIN] = \ - EntityComponent(_LOGGER, DOMAIN, hass) + component = hass.data[DOMAIN] = EntityComponent(_LOGGER, DOMAIN, hass) await component.async_add_entities([group], True) @@ -444,10 +525,7 @@ class Group(Entity): @property def state_attributes(self): """Return the state attributes for the group.""" - data = { - ATTR_ENTITY_ID: self.tracking, - ATTR_ORDER: self._order, - } + data = {ATTR_ENTITY_ID: self.tracking, ATTR_ORDER: self._order} if not self.user_defined: data[ATTR_AUTO] = True if self.view: @@ -515,8 +593,7 @@ class Group(Entity): self._async_unsub_state_changed() self._async_unsub_state_changed = None - async def _async_state_changed_listener(self, entity_id, old_state, - new_state): + async def _async_state_changed_listener(self, entity_id, old_state, new_state): """Respond to a member state changing. This method must be run in the event loop. @@ -562,8 +639,7 @@ class Group(Entity): states = self._tracking_states for state in states: - gr_on, gr_off = \ - _get_group_on_off(state.state) + gr_on, gr_off = _get_group_on_off(state.state) if gr_on is not None: break else: @@ -577,11 +653,11 @@ class Group(Entity): return # pylint: disable=too-many-boolean-expressions - if tr_state is None or ((gr_state == gr_on and - tr_state.state == gr_off) or - (gr_state == gr_off and - tr_state.state == gr_on) or - tr_state.state not in (gr_on, gr_off)): + if tr_state is None or ( + (gr_state == gr_on and tr_state.state == gr_off) + or (gr_state == gr_off and tr_state.state == gr_on) + or tr_state.state not in (gr_on, gr_off) + ): if states is None: states = self._tracking_states @@ -593,14 +669,17 @@ class Group(Entity): elif tr_state.state in (gr_on, gr_off): self._state = tr_state.state - if tr_state is None or self._assumed_state and \ - not tr_state.attributes.get(ATTR_ASSUMED_STATE): + if ( + tr_state is None + or self._assumed_state + and not tr_state.attributes.get(ATTR_ASSUMED_STATE) + ): if states is None: states = self._tracking_states self._assumed_state = self.mode( - state.attributes.get(ATTR_ASSUMED_STATE) for state - in states) + state.attributes.get(ATTR_ASSUMED_STATE) for state in states + ) elif tr_state.attributes.get(ATTR_ASSUMED_STATE): self._assumed_state = True diff --git a/homeassistant/components/group/cover.py b/homeassistant/components/group/cover.py index 385d20949d6..faa4ddfc87d 100644 --- a/homeassistant/components/group/cover.py +++ b/homeassistant/components/group/cover.py @@ -4,41 +4,63 @@ import logging import voluptuous as vol from homeassistant.const import ( - ATTR_ASSUMED_STATE, ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, CONF_ENTITIES, - CONF_NAME, STATE_CLOSED) + ATTR_ASSUMED_STATE, + ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, + CONF_ENTITIES, + CONF_NAME, + STATE_CLOSED, +) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_state_change from homeassistant.components.cover import ( - ATTR_CURRENT_POSITION, ATTR_CURRENT_TILT_POSITION, ATTR_POSITION, - ATTR_TILT_POSITION, DOMAIN, PLATFORM_SCHEMA, SERVICE_CLOSE_COVER, - SERVICE_CLOSE_COVER_TILT, SERVICE_OPEN_COVER, SERVICE_OPEN_COVER_TILT, - SERVICE_SET_COVER_POSITION, SERVICE_SET_COVER_TILT_POSITION, - SERVICE_STOP_COVER, SERVICE_STOP_COVER_TILT, SUPPORT_CLOSE, - SUPPORT_CLOSE_TILT, SUPPORT_OPEN, SUPPORT_OPEN_TILT, SUPPORT_SET_POSITION, - SUPPORT_SET_TILT_POSITION, SUPPORT_STOP, SUPPORT_STOP_TILT, CoverDevice) + ATTR_CURRENT_POSITION, + ATTR_CURRENT_TILT_POSITION, + ATTR_POSITION, + ATTR_TILT_POSITION, + DOMAIN, + PLATFORM_SCHEMA, + SERVICE_CLOSE_COVER, + SERVICE_CLOSE_COVER_TILT, + SERVICE_OPEN_COVER, + SERVICE_OPEN_COVER_TILT, + SERVICE_SET_COVER_POSITION, + SERVICE_SET_COVER_TILT_POSITION, + SERVICE_STOP_COVER, + SERVICE_STOP_COVER_TILT, + SUPPORT_CLOSE, + SUPPORT_CLOSE_TILT, + SUPPORT_OPEN, + SUPPORT_OPEN_TILT, + SUPPORT_SET_POSITION, + SUPPORT_SET_TILT_POSITION, + SUPPORT_STOP, + SUPPORT_STOP_TILT, + CoverDevice, +) _LOGGER = logging.getLogger(__name__) -KEY_OPEN_CLOSE = 'open_close' -KEY_STOP = 'stop' -KEY_POSITION = 'position' +KEY_OPEN_CLOSE = "open_close" +KEY_STOP = "stop" +KEY_POSITION = "position" -DEFAULT_NAME = 'Cover Group' +DEFAULT_NAME = "Cover Group" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Required(CONF_ENTITIES): cv.entities_domain(DOMAIN), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Required(CONF_ENTITIES): cv.entities_domain(DOMAIN), + } +) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Group Cover platform.""" - async_add_entities( - [CoverGroup(config[CONF_NAME], config[CONF_ENTITIES])]) + async_add_entities([CoverGroup(config[CONF_NAME], config[CONF_ENTITIES])]) class CoverGroup(CoverDevice): @@ -54,14 +76,13 @@ class CoverGroup(CoverDevice): self._assumed_state = True self._entities = entities - self._covers = {KEY_OPEN_CLOSE: set(), KEY_STOP: set(), - KEY_POSITION: set()} - self._tilts = {KEY_OPEN_CLOSE: set(), KEY_STOP: set(), - KEY_POSITION: set()} + self._covers = {KEY_OPEN_CLOSE: set(), KEY_STOP: set(), KEY_POSITION: set()} + self._tilts = {KEY_OPEN_CLOSE: set(), KEY_STOP: set(), KEY_POSITION: set()} @callback - def update_supported_features(self, entity_id, old_state, new_state, - update_state=True): + def update_supported_features( + self, entity_id, old_state, new_state, update_state=True + ): """Update dictionaries with supported features.""" if not new_state: for values in self._covers.values(): @@ -107,10 +128,12 @@ class CoverGroup(CoverDevice): """Register listeners.""" for entity_id in self._entities: new_state = self.hass.states.get(entity_id) - self.update_supported_features(entity_id, None, new_state, - update_state=False) - async_track_state_change(self.hass, self._entities, - self.update_supported_features) + self.update_supported_features( + entity_id, None, new_state, update_state=False + ) + async_track_state_change( + self.hass, self._entities, self.update_supported_features + ) await self.async_update() @property @@ -152,51 +175,63 @@ class CoverGroup(CoverDevice): """Move the covers up.""" data = {ATTR_ENTITY_ID: self._covers[KEY_OPEN_CLOSE]} await self.hass.services.async_call( - DOMAIN, SERVICE_OPEN_COVER, data, blocking=True) + DOMAIN, SERVICE_OPEN_COVER, data, blocking=True + ) async def async_close_cover(self, **kwargs): """Move the covers down.""" data = {ATTR_ENTITY_ID: self._covers[KEY_OPEN_CLOSE]} await self.hass.services.async_call( - DOMAIN, SERVICE_CLOSE_COVER, data, blocking=True) + DOMAIN, SERVICE_CLOSE_COVER, data, blocking=True + ) async def async_stop_cover(self, **kwargs): """Fire the stop action.""" data = {ATTR_ENTITY_ID: self._covers[KEY_STOP]} await self.hass.services.async_call( - DOMAIN, SERVICE_STOP_COVER, data, blocking=True) + DOMAIN, SERVICE_STOP_COVER, data, blocking=True + ) async def async_set_cover_position(self, **kwargs): """Set covers position.""" - data = {ATTR_ENTITY_ID: self._covers[KEY_POSITION], - ATTR_POSITION: kwargs[ATTR_POSITION]} + data = { + ATTR_ENTITY_ID: self._covers[KEY_POSITION], + ATTR_POSITION: kwargs[ATTR_POSITION], + } await self.hass.services.async_call( - DOMAIN, SERVICE_SET_COVER_POSITION, data, blocking=True) + DOMAIN, SERVICE_SET_COVER_POSITION, data, blocking=True + ) async def async_open_cover_tilt(self, **kwargs): """Tilt covers open.""" data = {ATTR_ENTITY_ID: self._tilts[KEY_OPEN_CLOSE]} await self.hass.services.async_call( - DOMAIN, SERVICE_OPEN_COVER_TILT, data, blocking=True) + DOMAIN, SERVICE_OPEN_COVER_TILT, data, blocking=True + ) async def async_close_cover_tilt(self, **kwargs): """Tilt covers closed.""" data = {ATTR_ENTITY_ID: self._tilts[KEY_OPEN_CLOSE]} await self.hass.services.async_call( - DOMAIN, SERVICE_CLOSE_COVER_TILT, data, blocking=True) + DOMAIN, SERVICE_CLOSE_COVER_TILT, data, blocking=True + ) async def async_stop_cover_tilt(self, **kwargs): """Stop cover tilt.""" data = {ATTR_ENTITY_ID: self._tilts[KEY_STOP]} await self.hass.services.async_call( - DOMAIN, SERVICE_STOP_COVER_TILT, data, blocking=True) + DOMAIN, SERVICE_STOP_COVER_TILT, data, blocking=True + ) async def async_set_cover_tilt_position(self, **kwargs): """Set tilt position.""" - data = {ATTR_ENTITY_ID: self._tilts[KEY_POSITION], - ATTR_TILT_POSITION: kwargs[ATTR_TILT_POSITION]} + data = { + ATTR_ENTITY_ID: self._tilts[KEY_POSITION], + ATTR_TILT_POSITION: kwargs[ATTR_TILT_POSITION], + } await self.hass.services.async_call( - DOMAIN, SERVICE_SET_COVER_TILT_POSITION, data, blocking=True) + DOMAIN, SERVICE_SET_COVER_TILT_POSITION, data, blocking=True + ) async def async_update(self): """Update state and attributes.""" @@ -244,18 +279,18 @@ class CoverGroup(CoverDevice): self._tilt_position = position supported_features = 0 - supported_features |= SUPPORT_OPEN | SUPPORT_CLOSE \ - if self._covers[KEY_OPEN_CLOSE] else 0 - supported_features |= SUPPORT_STOP \ - if self._covers[KEY_STOP] else 0 - supported_features |= SUPPORT_SET_POSITION \ - if self._covers[KEY_POSITION] else 0 - supported_features |= SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT \ - if self._tilts[KEY_OPEN_CLOSE] else 0 - supported_features |= SUPPORT_STOP_TILT \ - if self._tilts[KEY_STOP] else 0 - supported_features |= SUPPORT_SET_TILT_POSITION \ - if self._tilts[KEY_POSITION] else 0 + supported_features |= ( + SUPPORT_OPEN | SUPPORT_CLOSE if self._covers[KEY_OPEN_CLOSE] else 0 + ) + supported_features |= SUPPORT_STOP if self._covers[KEY_STOP] else 0 + supported_features |= SUPPORT_SET_POSITION if self._covers[KEY_POSITION] else 0 + supported_features |= ( + SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT if self._tilts[KEY_OPEN_CLOSE] else 0 + ) + supported_features |= SUPPORT_STOP_TILT if self._tilts[KEY_STOP] else 0 + supported_features |= ( + SUPPORT_SET_TILT_POSITION if self._tilts[KEY_POSITION] else 0 + ) self._supported_features = supported_features if not self._assumed_state: diff --git a/homeassistant/components/group/light.py b/homeassistant/components/group/light.py index 170e93398a1..f0d29d923c8 100644 --- a/homeassistant/components/group/light.py +++ b/homeassistant/components/group/light.py @@ -8,40 +8,66 @@ import voluptuous as vol from homeassistant.components import light from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, CONF_ENTITIES, CONF_NAME, - STATE_ON, STATE_UNAVAILABLE) + ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, + CONF_ENTITIES, + CONF_NAME, + STATE_ON, + STATE_UNAVAILABLE, +) from homeassistant.core import State, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.components.light import ( - ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_EFFECT_LIST, - ATTR_FLASH, ATTR_HS_COLOR, ATTR_MAX_MIREDS, ATTR_MIN_MIREDS, - ATTR_TRANSITION, ATTR_WHITE_VALUE, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, SUPPORT_FLASH, - SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE) + ATTR_BRIGHTNESS, + ATTR_COLOR_TEMP, + ATTR_EFFECT, + ATTR_EFFECT_LIST, + ATTR_FLASH, + ATTR_HS_COLOR, + ATTR_MAX_MIREDS, + ATTR_MIN_MIREDS, + ATTR_TRANSITION, + ATTR_WHITE_VALUE, + PLATFORM_SCHEMA, + SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, + SUPPORT_COLOR_TEMP, + SUPPORT_EFFECT, + SUPPORT_FLASH, + SUPPORT_TRANSITION, + SUPPORT_WHITE_VALUE, +) _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'Light Group' +DEFAULT_NAME = "Light Group" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Required(CONF_ENTITIES): cv.entities_domain(light.DOMAIN) -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Required(CONF_ENTITIES): cv.entities_domain(light.DOMAIN), + } +) -SUPPORT_GROUP_LIGHT = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_EFFECT - | SUPPORT_FLASH | SUPPORT_COLOR | SUPPORT_TRANSITION - | SUPPORT_WHITE_VALUE) +SUPPORT_GROUP_LIGHT = ( + SUPPORT_BRIGHTNESS + | SUPPORT_COLOR_TEMP + | SUPPORT_EFFECT + | SUPPORT_FLASH + | SUPPORT_COLOR + | SUPPORT_TRANSITION + | SUPPORT_WHITE_VALUE +) -async def async_setup_platform(hass: HomeAssistantType, config: ConfigType, - async_add_entities, - discovery_info=None) -> None: +async def async_setup_platform( + hass: HomeAssistantType, config: ConfigType, async_add_entities, discovery_info=None +) -> None: """Initialize light.group platform.""" - async_add_entities([LightGroup(config.get(CONF_NAME), - config[CONF_ENTITIES])]) + async_add_entities([LightGroup(config.get(CONF_NAME), config[CONF_ENTITIES])]) class LightGroup(light.Light): @@ -66,14 +92,17 @@ class LightGroup(light.Light): async def async_added_to_hass(self) -> None: """Register callbacks.""" + @callback - def async_state_changed_listener(entity_id: str, old_state: State, - new_state: State): + def async_state_changed_listener( + entity_id: str, old_state: State, new_state: State + ): """Handle child updates.""" self.async_schedule_update_ha_state(True) self._async_unsub_state_changed = async_track_state_change( - self.hass, self._entity_ids, async_state_changed_listener) + self.hass, self._entity_ids, async_state_changed_listener + ) await self.async_update() async def async_will_remove_from_hass(self): @@ -173,7 +202,8 @@ class LightGroup(light.Light): data[ATTR_FLASH] = kwargs[ATTR_FLASH] await self.hass.services.async_call( - light.DOMAIN, light.SERVICE_TURN_ON, data, blocking=True) + light.DOMAIN, light.SERVICE_TURN_ON, data, blocking=True + ) async def async_turn_off(self, **kwargs): """Forward the turn_off command to all lights in the light group.""" @@ -183,7 +213,8 @@ class LightGroup(light.Light): data[ATTR_TRANSITION] = kwargs[ATTR_TRANSITION] await self.hass.services.async_call( - light.DOMAIN, light.SERVICE_TURN_OFF, data, blocking=True) + light.DOMAIN, light.SERVICE_TURN_OFF, data, blocking=True + ) async def async_update(self): """Query all members and determine the light group state.""" @@ -192,25 +223,24 @@ class LightGroup(light.Light): on_states = [state for state in states if state.state == STATE_ON] self._is_on = len(on_states) > 0 - self._available = any(state.state != STATE_UNAVAILABLE - for state in states) + self._available = any(state.state != STATE_UNAVAILABLE for state in states) self._brightness = _reduce_attribute(on_states, ATTR_BRIGHTNESS) - self._hs_color = _reduce_attribute( - on_states, ATTR_HS_COLOR, reduce=_mean_tuple) + self._hs_color = _reduce_attribute(on_states, ATTR_HS_COLOR, reduce=_mean_tuple) self._white_value = _reduce_attribute(on_states, ATTR_WHITE_VALUE) self._color_temp = _reduce_attribute(on_states, ATTR_COLOR_TEMP) self._min_mireds = _reduce_attribute( - states, ATTR_MIN_MIREDS, default=154, reduce=min) + states, ATTR_MIN_MIREDS, default=154, reduce=min + ) self._max_mireds = _reduce_attribute( - states, ATTR_MAX_MIREDS, default=500, reduce=max) + states, ATTR_MAX_MIREDS, default=500, reduce=max + ) self._effect_list = None - all_effect_lists = list( - _find_state_attributes(states, ATTR_EFFECT_LIST)) + all_effect_lists = list(_find_state_attributes(states, ATTR_EFFECT_LIST)) if all_effect_lists: # Merge all effects from all effect_lists with a union merge. self._effect_list = list(set().union(*all_effect_lists)) @@ -232,8 +262,7 @@ class LightGroup(light.Light): self._supported_features &= SUPPORT_GROUP_LIGHT -def _find_state_attributes(states: List[State], - key: str) -> Iterator[Any]: +def _find_state_attributes(states: List[State], key: str) -> Iterator[Any]: """Find attributes with matching key from states.""" for state in states: value = state.attributes.get(key) @@ -251,10 +280,12 @@ def _mean_tuple(*args): return tuple(sum(l) / len(l) for l in zip(*args)) -def _reduce_attribute(states: List[State], - key: str, - default: Optional[Any] = None, - reduce: Callable[..., Any] = _mean_int) -> Any: +def _reduce_attribute( + states: List[State], + key: str, + default: Optional[Any] = None, + reduce: Callable[..., Any] = _mean_int, +) -> Any: """Find the first attribute matching key from states. If none are found, return default. diff --git a/homeassistant/components/group/notify.py b/homeassistant/components/group/notify.py index e13499878e9..3d3c644fea9 100644 --- a/homeassistant/components/group/notify.py +++ b/homeassistant/components/group/notify.py @@ -10,18 +10,25 @@ from homeassistant.const import ATTR_SERVICE import homeassistant.helpers.config_validation as cv from homeassistant.components.notify import ( - ATTR_DATA, ATTR_MESSAGE, DOMAIN, PLATFORM_SCHEMA, BaseNotificationService) + ATTR_DATA, + ATTR_MESSAGE, + DOMAIN, + PLATFORM_SCHEMA, + BaseNotificationService, +) _LOGGER = logging.getLogger(__name__) -CONF_SERVICES = 'services' +CONF_SERVICES = "services" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_SERVICES): vol.All(cv.ensure_list, [{ - vol.Required(ATTR_SERVICE): cv.slug, - vol.Optional(ATTR_DATA): dict, - }]) -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_SERVICES): vol.All( + cv.ensure_list, + [{vol.Required(ATTR_SERVICE): cv.slug, vol.Optional(ATTR_DATA): dict}], + ) + } +) def update(input_dict, update_source): @@ -61,8 +68,11 @@ class GroupNotifyPlatform(BaseNotificationService): sending_payload = deepcopy(payload.copy()) if entity.get(ATTR_DATA) is not None: update(sending_payload, entity.get(ATTR_DATA)) - tasks.append(self.hass.services.async_call( - DOMAIN, entity.get(ATTR_SERVICE), sending_payload)) + tasks.append( + self.hass.services.async_call( + DOMAIN, entity.get(ATTR_SERVICE), sending_payload + ) + ) if tasks: await asyncio.wait(tasks) diff --git a/homeassistant/components/group/reproduce_state.py b/homeassistant/components/group/reproduce_state.py index 1cf1793e6f6..f2170c4df16 100644 --- a/homeassistant/components/group/reproduce_state.py +++ b/homeassistant/components/group/reproduce_state.py @@ -7,22 +7,25 @@ from homeassistant.loader import bind_hass @bind_hass -async def async_reproduce_states(hass: HomeAssistantType, - states: Iterable[State], - context: Optional[Context] = None) -> None: +async def async_reproduce_states( + hass: HomeAssistantType, states: Iterable[State], context: Optional[Context] = None +) -> None: """Reproduce component states.""" from . import get_entity_ids from homeassistant.helpers.state import async_reproduce_state + states_copy = [] for state in states: members = get_entity_ids(hass, state.entity_id) for member in members: states_copy.append( - State(member, - state.state, - state.attributes, - last_changed=state.last_changed, - last_updated=state.last_updated, - context=state.context)) - await async_reproduce_state(hass, states_copy, blocking=True, - context=context) + State( + member, + state.state, + state.attributes, + last_changed=state.last_changed, + last_updated=state.last_updated, + context=state.context, + ) + ) + await async_reproduce_state(hass, states_copy, blocking=True, context=context) diff --git a/homeassistant/components/gstreamer/media_player.py b/homeassistant/components/gstreamer/media_player.py index f7404010513..a213587bc0e 100644 --- a/homeassistant/components/gstreamer/media_player.py +++ b/homeassistant/components/gstreamer/media_player.py @@ -3,32 +3,41 @@ import logging import voluptuous as vol -from homeassistant.components.media_player import ( - MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA from homeassistant.components.media_player.const import ( - MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, - SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_VOLUME_SET) + MEDIA_TYPE_MUSIC, + SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, + SUPPORT_PLAY, + SUPPORT_PLAY_MEDIA, + SUPPORT_VOLUME_SET, +) from homeassistant.const import CONF_NAME, EVENT_HOMEASSISTANT_STOP, STATE_IDLE import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -CONF_PIPELINE = 'pipeline' +CONF_PIPELINE = "pipeline" -DOMAIN = 'gstreamer' +DOMAIN = "gstreamer" -SUPPORT_GSTREAMER = SUPPORT_VOLUME_SET | SUPPORT_PLAY | SUPPORT_PAUSE |\ - SUPPORT_PLAY_MEDIA | SUPPORT_NEXT_TRACK +SUPPORT_GSTREAMER = ( + SUPPORT_VOLUME_SET + | SUPPORT_PLAY + | SUPPORT_PAUSE + | SUPPORT_PLAY_MEDIA + | SUPPORT_NEXT_TRACK +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_PIPELINE): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_PIPELINE): cv.string} +) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Gstreamer platform.""" from gsp import GstreamerPlayer + name = config.get(CONF_NAME) pipeline = config.get(CONF_PIPELINE) player = GstreamerPlayer(pipeline) @@ -73,7 +82,7 @@ class GstreamerDevice(MediaPlayerDevice): def play_media(self, media_type, media_id, **kwargs): """Play media.""" if media_type != MEDIA_TYPE_MUSIC: - _LOGGER.error('invalid media type') + _LOGGER.error("invalid media type") return self._player.queue(media_id) diff --git a/homeassistant/components/gtfs/sensor.py b/homeassistant/components/gtfs/sensor.py index 0a9301f8c33..b5c1000681d 100644 --- a/homeassistant/components/gtfs/sensor.py +++ b/homeassistant/components/gtfs/sensor.py @@ -9,8 +9,12 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - ATTR_ATTRIBUTION, CONF_NAME, CONF_OFFSET, DEVICE_CLASS_TIMESTAMP, - STATE_UNKNOWN) + ATTR_ATTRIBUTION, + CONF_NAME, + CONF_OFFSET, + DEVICE_CLASS_TIMESTAMP, + STATE_UNKNOWN, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import ConfigType, HomeAssistantType @@ -19,111 +23,104 @@ import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) -ATTR_ARRIVAL = 'arrival' -ATTR_BICYCLE = 'trip_bikes_allowed_state' -ATTR_DAY = 'day' -ATTR_FIRST = 'first' -ATTR_DROP_OFF_DESTINATION = 'destination_stop_drop_off_type_state' -ATTR_DROP_OFF_ORIGIN = 'origin_stop_drop_off_type_state' -ATTR_INFO = 'info' +ATTR_ARRIVAL = "arrival" +ATTR_BICYCLE = "trip_bikes_allowed_state" +ATTR_DAY = "day" +ATTR_FIRST = "first" +ATTR_DROP_OFF_DESTINATION = "destination_stop_drop_off_type_state" +ATTR_DROP_OFF_ORIGIN = "origin_stop_drop_off_type_state" +ATTR_INFO = "info" ATTR_OFFSET = CONF_OFFSET -ATTR_LAST = 'last' -ATTR_LOCATION_DESTINATION = 'destination_station_location_type_name' -ATTR_LOCATION_ORIGIN = 'origin_station_location_type_name' -ATTR_PICKUP_DESTINATION = 'destination_stop_pickup_type_state' -ATTR_PICKUP_ORIGIN = 'origin_stop_pickup_type_state' -ATTR_ROUTE_TYPE = 'route_type_name' -ATTR_TIMEPOINT_DESTINATION = 'destination_stop_timepoint_exact' -ATTR_TIMEPOINT_ORIGIN = 'origin_stop_timepoint_exact' -ATTR_WHEELCHAIR = 'trip_wheelchair_access_available' -ATTR_WHEELCHAIR_DESTINATION = \ - 'destination_station_wheelchair_boarding_available' -ATTR_WHEELCHAIR_ORIGIN = 'origin_station_wheelchair_boarding_available' +ATTR_LAST = "last" +ATTR_LOCATION_DESTINATION = "destination_station_location_type_name" +ATTR_LOCATION_ORIGIN = "origin_station_location_type_name" +ATTR_PICKUP_DESTINATION = "destination_stop_pickup_type_state" +ATTR_PICKUP_ORIGIN = "origin_stop_pickup_type_state" +ATTR_ROUTE_TYPE = "route_type_name" +ATTR_TIMEPOINT_DESTINATION = "destination_stop_timepoint_exact" +ATTR_TIMEPOINT_ORIGIN = "origin_stop_timepoint_exact" +ATTR_WHEELCHAIR = "trip_wheelchair_access_available" +ATTR_WHEELCHAIR_DESTINATION = "destination_station_wheelchair_boarding_available" +ATTR_WHEELCHAIR_ORIGIN = "origin_station_wheelchair_boarding_available" -CONF_DATA = 'data' -CONF_DESTINATION = 'destination' -CONF_ORIGIN = 'origin' -CONF_TOMORROW = 'include_tomorrow' +CONF_DATA = "data" +CONF_DESTINATION = "destination" +CONF_ORIGIN = "origin" +CONF_TOMORROW = "include_tomorrow" -DEFAULT_NAME = 'GTFS Sensor' -DEFAULT_PATH = 'gtfs' +DEFAULT_NAME = "GTFS Sensor" +DEFAULT_PATH = "gtfs" BICYCLE_ALLOWED_DEFAULT = STATE_UNKNOWN -BICYCLE_ALLOWED_OPTIONS = { - 1: True, - 2: False, -} +BICYCLE_ALLOWED_OPTIONS = {1: True, 2: False} DROP_OFF_TYPE_DEFAULT = STATE_UNKNOWN DROP_OFF_TYPE_OPTIONS = { - 0: 'Regular', - 1: 'Not Available', - 2: 'Call Agency', - 3: 'Contact Driver', + 0: "Regular", + 1: "Not Available", + 2: "Call Agency", + 3: "Contact Driver", } -ICON = 'mdi:train' +ICON = "mdi:train" ICONS = { - 0: 'mdi:tram', - 1: 'mdi:subway', - 2: 'mdi:train', - 3: 'mdi:bus', - 4: 'mdi:ferry', - 5: 'mdi:train-variant', - 6: 'mdi:gondola', - 7: 'mdi:stairs', + 0: "mdi:tram", + 1: "mdi:subway", + 2: "mdi:train", + 3: "mdi:bus", + 4: "mdi:ferry", + 5: "mdi:train-variant", + 6: "mdi:gondola", + 7: "mdi:stairs", } -LOCATION_TYPE_DEFAULT = 'Stop' +LOCATION_TYPE_DEFAULT = "Stop" LOCATION_TYPE_OPTIONS = { - 0: 'Station', - 1: 'Stop', + 0: "Station", + 1: "Stop", 2: "Station Entrance/Exit", - 3: 'Other', + 3: "Other", } PICKUP_TYPE_DEFAULT = STATE_UNKNOWN PICKUP_TYPE_OPTIONS = { - 0: 'Regular', + 0: "Regular", 1: "None Available", 2: "Call Agency", 3: "Contact Driver", } ROUTE_TYPE_OPTIONS = { - 0: 'Tram', - 1: 'Subway', - 2: 'Rail', - 3: 'Bus', - 4: 'Ferry', + 0: "Tram", + 1: "Subway", + 2: "Rail", + 3: "Bus", + 4: "Ferry", 5: "Cable Tram", 6: "Aerial Lift", - 7: 'Funicular', + 7: "Funicular", } TIMEPOINT_DEFAULT = True -TIMEPOINT_OPTIONS = { - 0: False, - 1: True, -} +TIMEPOINT_OPTIONS = {0: False, 1: True} WHEELCHAIR_ACCESS_DEFAULT = STATE_UNKNOWN -WHEELCHAIR_ACCESS_OPTIONS = { - 1: True, - 2: False, -} +WHEELCHAIR_ACCESS_OPTIONS = {1: True, 2: False} WHEELCHAIR_BOARDING_DEFAULT = STATE_UNKNOWN -WHEELCHAIR_BOARDING_OPTIONS = { - 1: True, - 2: False, -} +WHEELCHAIR_BOARDING_OPTIONS = {1: True, 2: False} -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # type: ignore - vol.Required(CONF_ORIGIN): cv.string, - vol.Required(CONF_DESTINATION): cv.string, - vol.Required(CONF_DATA): cv.string, - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_OFFSET, default=0): cv.time_period, - vol.Optional(CONF_TOMORROW, default=False): cv.boolean, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { # type: ignore + vol.Required(CONF_ORIGIN): cv.string, + vol.Required(CONF_DESTINATION): cv.string, + vol.Required(CONF_DATA): cv.string, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_OFFSET, default=0): cv.time_period, + vol.Optional(CONF_TOMORROW, default=False): cv.boolean, + } +) -def get_next_departure(schedule: Any, start_station_id: Any, - end_station_id: Any, offset: cv.time_period, - include_tomorrow: bool = False) -> dict: +def get_next_departure( + schedule: Any, + start_station_id: Any, + end_station_id: Any, + offset: cv.time_period, + include_tomorrow: bool = False, +) -> dict: """Get the next departure for the given schedule.""" now = datetime.datetime.now() + offset now_date = now.strftime(dt_util.DATE_STR_FORMAT) @@ -138,10 +135,10 @@ def get_next_departure(schedule: Any, start_station_id: Any, # up to an overkill maximum in case of a departure every minute for those # days. limit = 24 * 60 * 60 * 2 - tomorrow_select = tomorrow_where = tomorrow_order = '' + tomorrow_select = tomorrow_where = tomorrow_order = "" if include_tomorrow: limit = int(limit / 2 * 3) - tomorrow_name = tomorrow.strftime('%A').lower() + tomorrow_name = tomorrow.strftime("%A").lower() tomorrow_select = "calendar.{} AS tomorrow,".format(tomorrow_name) tomorrow_where = "OR calendar.{} = 1".format(tomorrow_name) tomorrow_order = "calendar.{} DESC,".format(tomorrow_name) @@ -195,74 +192,67 @@ def get_next_departure(schedule: Any, start_station_id: Any, {tomorrow_order} origin_stop_time.departure_time LIMIT :limit - """.format(yesterday_name=yesterday.strftime('%A').lower(), - today_name=now.strftime('%A').lower(), - tomorrow_select=tomorrow_select, - tomorrow_where=tomorrow_where, - tomorrow_order=tomorrow_order) - result = schedule.engine.execute(text(sql_query), - origin_station_id=start_station_id, - end_station_id=end_station_id, - today=now_date, - limit=limit) + """.format( + yesterday_name=yesterday.strftime("%A").lower(), + today_name=now.strftime("%A").lower(), + tomorrow_select=tomorrow_select, + tomorrow_where=tomorrow_where, + tomorrow_order=tomorrow_order, + ) + result = schedule.engine.execute( + text(sql_query), + origin_station_id=start_station_id, + end_station_id=end_station_id, + today=now_date, + limit=limit, + ) # Create lookup timetable for today and possibly tomorrow, taking into # account any departures from yesterday scheduled after midnight, # as long as all departures are within the calendar date range. timetable = {} yesterday_start = today_start = tomorrow_start = None - yesterday_last = today_last = '' + yesterday_last = today_last = "" for row in result: - if row['yesterday'] == 1 and yesterday_date >= row['start_date']: - extras = { - 'day': 'yesterday', - 'first': None, - 'last': False, - } + if row["yesterday"] == 1 and yesterday_date >= row["start_date"]: + extras = {"day": "yesterday", "first": None, "last": False} if yesterday_start is None: - yesterday_start = row['origin_depart_date'] - if yesterday_start != row['origin_depart_date']: - idx = '{} {}'.format(now_date, - row['origin_depart_time']) + yesterday_start = row["origin_depart_date"] + if yesterday_start != row["origin_depart_date"]: + idx = "{} {}".format(now_date, row["origin_depart_time"]) timetable[idx] = {**row, **extras} yesterday_last = idx - if row['today'] == 1: - extras = { - 'day': 'today', - 'first': False, - 'last': False, - } + if row["today"] == 1: + extras = {"day": "today", "first": False, "last": False} if today_start is None: - today_start = row['origin_depart_date'] - extras['first'] = True - if today_start == row['origin_depart_date']: + today_start = row["origin_depart_date"] + extras["first"] = True + if today_start == row["origin_depart_date"]: idx_prefix = now_date else: idx_prefix = tomorrow_date - idx = '{} {}'.format(idx_prefix, row['origin_depart_time']) + idx = "{} {}".format(idx_prefix, row["origin_depart_time"]) timetable[idx] = {**row, **extras} today_last = idx - if 'tomorrow' in row and row['tomorrow'] == 1 and tomorrow_date <= \ - row['end_date']: - extras = { - 'day': 'tomorrow', - 'first': False, - 'last': None, - } + if ( + "tomorrow" in row + and row["tomorrow"] == 1 + and tomorrow_date <= row["end_date"] + ): + extras = {"day": "tomorrow", "first": False, "last": None} if tomorrow_start is None: - tomorrow_start = row['origin_depart_date'] - extras['first'] = True - if tomorrow_start == row['origin_depart_date']: - idx = '{} {}'.format(tomorrow_date, - row['origin_depart_time']) + tomorrow_start = row["origin_depart_date"] + extras["first"] = True + if tomorrow_start == row["origin_depart_date"]: + idx = "{} {}".format(tomorrow_date, row["origin_depart_time"]) timetable[idx] = {**row, **extras} # Flag last departures. for idx in filter(None, [yesterday_last, today_last]): - timetable[idx]['last'] = True + timetable[idx]["last"] = True _LOGGER.debug("Timetable: %s", sorted(timetable.keys())) @@ -270,8 +260,9 @@ def get_next_departure(schedule: Any, start_station_id: Any, for key in sorted(timetable.keys()): if dt_util.parse_datetime(key) > now: item = timetable[key] - _LOGGER.debug("Departure found for station %s @ %s -> %s", - start_station_id, key, item) + _LOGGER.debug( + "Departure found for station %s @ %s -> %s", start_station_id, key, item + ) break if item == {}: @@ -280,69 +271,72 @@ def get_next_departure(schedule: Any, start_station_id: Any, # Format arrival and departure dates and times, accounting for the # possibility of times crossing over midnight. origin_arrival = now - if item['origin_arrival_time'] > item['origin_depart_time']: + if item["origin_arrival_time"] > item["origin_depart_time"]: origin_arrival -= datetime.timedelta(days=1) - origin_arrival_time = '{} {}'.format( - origin_arrival.strftime(dt_util.DATE_STR_FORMAT), - item['origin_arrival_time']) + origin_arrival_time = "{} {}".format( + origin_arrival.strftime(dt_util.DATE_STR_FORMAT), item["origin_arrival_time"] + ) - origin_depart_time = '{} {}'.format(now_date, item['origin_depart_time']) + origin_depart_time = "{} {}".format(now_date, item["origin_depart_time"]) dest_arrival = now - if item['dest_arrival_time'] < item['origin_depart_time']: + if item["dest_arrival_time"] < item["origin_depart_time"]: dest_arrival += datetime.timedelta(days=1) - dest_arrival_time = '{} {}'.format( - dest_arrival.strftime(dt_util.DATE_STR_FORMAT), - item['dest_arrival_time']) + dest_arrival_time = "{} {}".format( + dest_arrival.strftime(dt_util.DATE_STR_FORMAT), item["dest_arrival_time"] + ) dest_depart = dest_arrival - if item['dest_depart_time'] < item['dest_arrival_time']: + if item["dest_depart_time"] < item["dest_arrival_time"]: dest_depart += datetime.timedelta(days=1) - dest_depart_time = '{} {}'.format( - dest_depart.strftime(dt_util.DATE_STR_FORMAT), - item['dest_depart_time']) + dest_depart_time = "{} {}".format( + dest_depart.strftime(dt_util.DATE_STR_FORMAT), item["dest_depart_time"] + ) depart_time = dt_util.parse_datetime(origin_depart_time) arrival_time = dt_util.parse_datetime(dest_arrival_time) origin_stop_time = { - 'Arrival Time': origin_arrival_time, - 'Departure Time': origin_depart_time, - 'Drop Off Type': item['origin_drop_off_type'], - 'Pickup Type': item['origin_pickup_type'], - 'Shape Dist Traveled': item['origin_dist_traveled'], - 'Headsign': item['origin_stop_headsign'], - 'Sequence': item['origin_stop_sequence'], - 'Timepoint': item['origin_stop_timepoint'], + "Arrival Time": origin_arrival_time, + "Departure Time": origin_depart_time, + "Drop Off Type": item["origin_drop_off_type"], + "Pickup Type": item["origin_pickup_type"], + "Shape Dist Traveled": item["origin_dist_traveled"], + "Headsign": item["origin_stop_headsign"], + "Sequence": item["origin_stop_sequence"], + "Timepoint": item["origin_stop_timepoint"], } destination_stop_time = { - 'Arrival Time': dest_arrival_time, - 'Departure Time': dest_depart_time, - 'Drop Off Type': item['dest_drop_off_type'], - 'Pickup Type': item['dest_pickup_type'], - 'Shape Dist Traveled': item['dest_dist_traveled'], - 'Headsign': item['dest_stop_headsign'], - 'Sequence': item['dest_stop_sequence'], - 'Timepoint': item['dest_stop_timepoint'], + "Arrival Time": dest_arrival_time, + "Departure Time": dest_depart_time, + "Drop Off Type": item["dest_drop_off_type"], + "Pickup Type": item["dest_pickup_type"], + "Shape Dist Traveled": item["dest_dist_traveled"], + "Headsign": item["dest_stop_headsign"], + "Sequence": item["dest_stop_sequence"], + "Timepoint": item["dest_stop_timepoint"], } return { - 'trip_id': item['trip_id'], - 'route_id': item['route_id'], - 'day': item['day'], - 'first': item['first'], - 'last': item['last'], - 'departure_time': depart_time, - 'arrival_time': arrival_time, - 'origin_stop_time': origin_stop_time, - 'destination_stop_time': destination_stop_time, + "trip_id": item["trip_id"], + "route_id": item["route_id"], + "day": item["day"], + "first": item["first"], + "last": item["last"], + "departure_time": depart_time, + "arrival_time": arrival_time, + "origin_stop_time": origin_stop_time, + "destination_stop_time": destination_stop_time, } -def setup_platform(hass: HomeAssistantType, config: ConfigType, - add_entities: Callable[[list], None], - discovery_info: Optional[dict] = None) -> None: +def setup_platform( + hass: HomeAssistantType, + config: ConfigType, + add_entities: Callable[[list], None], + discovery_info: Optional[dict] = None, +) -> None: """Set up the GTFS sensor.""" gtfs_dir = hass.config.path(DEFAULT_PATH) data = config[CONF_DATA] @@ -371,17 +365,23 @@ def setup_platform(hass: HomeAssistantType, config: ConfigType, if not gtfs.feeds: pygtfs.append_feed(gtfs, os.path.join(gtfs_dir, data)) - add_entities([ - GTFSDepartureSensor(gtfs, name, origin, destination, offset, - include_tomorrow)]) + add_entities( + [GTFSDepartureSensor(gtfs, name, origin, destination, offset, include_tomorrow)] + ) class GTFSDepartureSensor(Entity): """Implementation of a GTFS departure sensor.""" - def __init__(self, pygtfs: Any, name: Optional[Any], origin: Any, - destination: Any, offset: cv.time_period, - include_tomorrow: bool) -> None: + def __init__( + self, + pygtfs: Any, + name: Optional[Any], + origin: Any, + destination: Any, + offset: cv.time_period, + include_tomorrow: bool, + ) -> None: """Initialize the sensor.""" self._pygtfs = pygtfs self.origin = origin @@ -392,7 +392,7 @@ class GTFSDepartureSensor(Entity): self._available = False self._icon = ICON - self._name = '' + self._name = "" self._state = None # type: Optional[str] self._attributes = {} # type: dict @@ -452,8 +452,9 @@ class GTFSDepartureSensor(Entity): stops = self._pygtfs.stops_by_id(self.destination) if not stops: self._available = False - _LOGGER.warning("Destination stop ID %s not found", - self.destination) + _LOGGER.warning( + "Destination stop ID %s not found", self.destination + ) return self._destination = stops[0] @@ -461,43 +462,47 @@ class GTFSDepartureSensor(Entity): # Fetch next departure self._departure = get_next_departure( - self._pygtfs, self.origin, self.destination, self._offset, - self._include_tomorrow) + self._pygtfs, + self.origin, + self.destination, + self._offset, + self._include_tomorrow, + ) # Define the state as a UTC timestamp with ISO 8601 format if not self._departure: self._state = None else: self._state = dt_util.as_utc( - self._departure['departure_time']).isoformat() + self._departure["departure_time"] + ).isoformat() # Fetch trip and route details once, unless updated if not self._departure: self._trip = None else: - trip_id = self._departure['trip_id'] + trip_id = self._departure["trip_id"] if not self._trip or self._trip.trip_id != trip_id: _LOGGER.debug("Fetching trip details for %s", trip_id) self._trip = self._pygtfs.trips_by_id(trip_id)[0] - route_id = self._departure['route_id'] + route_id = self._departure["route_id"] if not self._route or self._route.route_id != route_id: _LOGGER.debug("Fetching route details for %s", route_id) self._route = self._pygtfs.routes_by_id(route_id)[0] # Fetch agency details exactly once if self._agency is None and self._route: - _LOGGER.debug("Fetching agency details for %s", - self._route.agency_id) + _LOGGER.debug("Fetching agency details for %s", self._route.agency_id) try: - self._agency = self._pygtfs.agencies_by_id( - self._route.agency_id)[0] + self._agency = self._pygtfs.agencies_by_id(self._route.agency_id)[0] except IndexError: _LOGGER.warning( "Agency ID '%s' was not found in agency table, " "you may want to update the routes database table " "to fix this missing reference", - self._route.agency_id) + self._route.agency_id, + ) self._agency = False # Assign attributes, icon and name @@ -508,33 +513,33 @@ class GTFSDepartureSensor(Entity): else: self._icon = ICON - name = '{agency} {origin} to {destination} next departure' + name = "{agency} {origin} to {destination} next departure" if not self._departure: - name = '{default}' - self._name = (self._custom_name or - name.format(agency=getattr(self._agency, - 'agency_name', - DEFAULT_NAME), - default=DEFAULT_NAME, - origin=self.origin, - destination=self.destination)) + name = "{default}" + self._name = self._custom_name or name.format( + agency=getattr(self._agency, "agency_name", DEFAULT_NAME), + default=DEFAULT_NAME, + origin=self.origin, + destination=self.destination, + ) def update_attributes(self) -> None: """Update state attributes.""" # Add departure information if self._departure: self._attributes[ATTR_ARRIVAL] = dt_util.as_utc( - self._departure['arrival_time']).isoformat() + self._departure["arrival_time"] + ).isoformat() - self._attributes[ATTR_DAY] = self._departure['day'] + self._attributes[ATTR_DAY] = self._departure["day"] if self._departure[ATTR_FIRST] is not None: - self._attributes[ATTR_FIRST] = self._departure['first'] + self._attributes[ATTR_FIRST] = self._departure["first"] elif ATTR_FIRST in self._attributes: del self._attributes[ATTR_FIRST] if self._departure[ATTR_LAST] is not None: - self._attributes[ATTR_LAST] = self._departure['last'] + self._attributes[ATTR_LAST] = self._departure["last"] elif ATTR_LAST in self._attributes: del self._attributes[ATTR_LAST] else: @@ -551,8 +556,11 @@ class GTFSDepartureSensor(Entity): self._attributes[ATTR_OFFSET] = self._offset.seconds / 60 if self._state is None: - self._attributes[ATTR_INFO] = "No more departures" if \ - self._include_tomorrow else "No more departures today" + self._attributes[ATTR_INFO] = ( + "No more departures" + if self._include_tomorrow + else "No more departures today" + ) elif ATTR_INFO in self._attributes: del self._attributes[ATTR_INFO] @@ -562,113 +570,115 @@ class GTFSDepartureSensor(Entity): del self._attributes[ATTR_ATTRIBUTION] # Add extra metadata - key = 'agency_id' + key = "agency_id" if self._agency and key not in self._attributes: - self.append_keys(self.dict_for_table(self._agency), 'Agency') + self.append_keys(self.dict_for_table(self._agency), "Agency") - key = 'origin_station_stop_id' + key = "origin_station_stop_id" if self._origin and key not in self._attributes: - self.append_keys(self.dict_for_table(self._origin), - "Origin Station") - self._attributes[ATTR_LOCATION_ORIGIN] = \ - LOCATION_TYPE_OPTIONS.get( - self._origin.location_type, - LOCATION_TYPE_DEFAULT) - self._attributes[ATTR_WHEELCHAIR_ORIGIN] = \ - WHEELCHAIR_BOARDING_OPTIONS.get( - self._origin.wheelchair_boarding, - WHEELCHAIR_BOARDING_DEFAULT) + self.append_keys(self.dict_for_table(self._origin), "Origin Station") + self._attributes[ATTR_LOCATION_ORIGIN] = LOCATION_TYPE_OPTIONS.get( + self._origin.location_type, LOCATION_TYPE_DEFAULT + ) + self._attributes[ATTR_WHEELCHAIR_ORIGIN] = WHEELCHAIR_BOARDING_OPTIONS.get( + self._origin.wheelchair_boarding, WHEELCHAIR_BOARDING_DEFAULT + ) - key = 'destination_station_stop_id' + key = "destination_station_stop_id" if self._destination and key not in self._attributes: - self.append_keys(self.dict_for_table(self._destination), - "Destination Station") - self._attributes[ATTR_LOCATION_DESTINATION] = \ - LOCATION_TYPE_OPTIONS.get( - self._destination.location_type, - LOCATION_TYPE_DEFAULT) - self._attributes[ATTR_WHEELCHAIR_DESTINATION] = \ - WHEELCHAIR_BOARDING_OPTIONS.get( - self._destination.wheelchair_boarding, - WHEELCHAIR_BOARDING_DEFAULT) + self.append_keys( + self.dict_for_table(self._destination), "Destination Station" + ) + self._attributes[ATTR_LOCATION_DESTINATION] = LOCATION_TYPE_OPTIONS.get( + self._destination.location_type, LOCATION_TYPE_DEFAULT + ) + self._attributes[ + ATTR_WHEELCHAIR_DESTINATION + ] = WHEELCHAIR_BOARDING_OPTIONS.get( + self._destination.wheelchair_boarding, WHEELCHAIR_BOARDING_DEFAULT + ) # Manage Route metadata - key = 'route_id' + key = "route_id" if not self._route and key in self._attributes: - self.remove_keys('Route') - elif self._route and (key not in self._attributes or - self._attributes[key] != self._route.route_id): - self.append_keys(self.dict_for_table(self._route), 'Route') - self._attributes[ATTR_ROUTE_TYPE] = \ - ROUTE_TYPE_OPTIONS[self._route.route_type] + self.remove_keys("Route") + elif self._route and ( + key not in self._attributes or self._attributes[key] != self._route.route_id + ): + self.append_keys(self.dict_for_table(self._route), "Route") + self._attributes[ATTR_ROUTE_TYPE] = ROUTE_TYPE_OPTIONS[ + self._route.route_type + ] # Manage Trip metadata - key = 'trip_id' + key = "trip_id" if not self._trip and key in self._attributes: - self.remove_keys('Trip') - elif self._trip and (key not in self._attributes or - self._attributes[key] != self._trip.trip_id): - self.append_keys(self.dict_for_table(self._trip), 'Trip') + self.remove_keys("Trip") + elif self._trip and ( + key not in self._attributes or self._attributes[key] != self._trip.trip_id + ): + self.append_keys(self.dict_for_table(self._trip), "Trip") self._attributes[ATTR_BICYCLE] = BICYCLE_ALLOWED_OPTIONS.get( - self._trip.bikes_allowed, - BICYCLE_ALLOWED_DEFAULT) + self._trip.bikes_allowed, BICYCLE_ALLOWED_DEFAULT + ) self._attributes[ATTR_WHEELCHAIR] = WHEELCHAIR_ACCESS_OPTIONS.get( - self._trip.wheelchair_accessible, - WHEELCHAIR_ACCESS_DEFAULT) + self._trip.wheelchair_accessible, WHEELCHAIR_ACCESS_DEFAULT + ) # Manage Stop Times metadata - prefix = 'origin_stop' + prefix = "origin_stop" if self._departure: - self.append_keys(self._departure['origin_stop_time'], prefix) + self.append_keys(self._departure["origin_stop_time"], prefix) self._attributes[ATTR_DROP_OFF_ORIGIN] = DROP_OFF_TYPE_OPTIONS.get( - self._departure['origin_stop_time']['Drop Off Type'], - DROP_OFF_TYPE_DEFAULT) + self._departure["origin_stop_time"]["Drop Off Type"], + DROP_OFF_TYPE_DEFAULT, + ) self._attributes[ATTR_PICKUP_ORIGIN] = PICKUP_TYPE_OPTIONS.get( - self._departure['origin_stop_time']['Pickup Type'], - PICKUP_TYPE_DEFAULT) + self._departure["origin_stop_time"]["Pickup Type"], PICKUP_TYPE_DEFAULT + ) self._attributes[ATTR_TIMEPOINT_ORIGIN] = TIMEPOINT_OPTIONS.get( - self._departure['origin_stop_time']['Timepoint'], - TIMEPOINT_DEFAULT) + self._departure["origin_stop_time"]["Timepoint"], TIMEPOINT_DEFAULT + ) else: self.remove_keys(prefix) - prefix = 'destination_stop' + prefix = "destination_stop" if self._departure: - self.append_keys(self._departure['destination_stop_time'], prefix) - self._attributes[ATTR_DROP_OFF_DESTINATION] = \ - DROP_OFF_TYPE_OPTIONS.get( - self._departure['destination_stop_time']['Drop Off Type'], - DROP_OFF_TYPE_DEFAULT) - self._attributes[ATTR_PICKUP_DESTINATION] = \ - PICKUP_TYPE_OPTIONS.get( - self._departure['destination_stop_time']['Pickup Type'], - PICKUP_TYPE_DEFAULT) - self._attributes[ATTR_TIMEPOINT_DESTINATION] = \ - TIMEPOINT_OPTIONS.get( - self._departure['destination_stop_time']['Timepoint'], - TIMEPOINT_DEFAULT) + self.append_keys(self._departure["destination_stop_time"], prefix) + self._attributes[ATTR_DROP_OFF_DESTINATION] = DROP_OFF_TYPE_OPTIONS.get( + self._departure["destination_stop_time"]["Drop Off Type"], + DROP_OFF_TYPE_DEFAULT, + ) + self._attributes[ATTR_PICKUP_DESTINATION] = PICKUP_TYPE_OPTIONS.get( + self._departure["destination_stop_time"]["Pickup Type"], + PICKUP_TYPE_DEFAULT, + ) + self._attributes[ATTR_TIMEPOINT_DESTINATION] = TIMEPOINT_OPTIONS.get( + self._departure["destination_stop_time"]["Timepoint"], TIMEPOINT_DEFAULT + ) else: self.remove_keys(prefix) @staticmethod def dict_for_table(resource: Any) -> dict: """Return a dictionary for the SQLAlchemy resource given.""" - return dict((col, getattr(resource, col)) - for col in resource.__table__.columns.keys()) + return dict( + (col, getattr(resource, col)) for col in resource.__table__.columns.keys() + ) - def append_keys(self, resource: dict, prefix: Optional[str] = None) -> \ - None: + def append_keys(self, resource: dict, prefix: Optional[str] = None) -> None: """Properly format key val pairs to append to attributes.""" for attr, val in resource.items(): - if val == '' or val is None or attr == 'feed_id': + if val == "" or val is None or attr == "feed_id": continue key = attr if prefix and not key.startswith(prefix): - key = '{} {}'.format(prefix, key) + key = "{} {}".format(prefix, key) key = slugify(key) self._attributes[key] = val def remove_keys(self, prefix: str) -> None: """Remove attributes whose key starts with prefix.""" - self._attributes = {k: v for k, v in self._attributes.items() if - not k.startswith(prefix)} + self._attributes = { + k: v for k, v in self._attributes.items() if not k.startswith(prefix) + } diff --git a/homeassistant/components/gtt/sensor.py b/homeassistant/components/gtt/sensor.py index ecabd5f0a71..43f13c94620 100644 --- a/homeassistant/components/gtt/sensor.py +++ b/homeassistant/components/gtt/sensor.py @@ -11,17 +11,16 @@ import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -CONF_STOP = 'stop' -CONF_BUS_NAME = 'bus_name' +CONF_STOP = "stop" +CONF_BUS_NAME = "bus_name" -ICON = 'mdi:train' +ICON = "mdi:train" SCAN_INTERVAL = timedelta(minutes=2) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_STOP): cv.string, - vol.Optional(CONF_BUS_NAME): cv.string -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_STOP): cv.string, vol.Optional(CONF_BUS_NAME): cv.string} +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -39,7 +38,7 @@ class GttSensor(Entity): """Initialize the Gtt sensor.""" self.data = GttData(stop, bus_name) self._state = None - self._name = 'Stop {}'.format(stop) + self._name = "Stop {}".format(stop) @property def name(self): @@ -64,9 +63,7 @@ class GttSensor(Entity): @property def device_state_attributes(self): """Return the state attributes of the sensor.""" - attr = { - 'bus_name': self.data.state_bus['bus_name'] - } + attr = {"bus_name": self.data.state_bus["bus_name"]} return attr def update(self): @@ -82,6 +79,7 @@ class GttData: def __init__(self, stop, bus_name): """Initialize the GttData class.""" from pygtt import PyGTT + self._pygtt = PyGTT() self._stop = stop self._bus_name = bus_name @@ -102,13 +100,13 @@ class GttData: def get_bus_by_name(self): """Get the bus by name.""" for bus in self.bus_list: - if bus['bus_name'] == self._bus_name: + if bus["bus_name"] == self._bus_name: return bus def get_datetime(bus): """Get the datetime from a bus.""" - bustime = datetime.strptime(bus['time'][0]['run'], "%H:%M") + bustime = datetime.strptime(bus["time"][0]["run"], "%H:%M") now = datetime.now() bustime = bustime.replace(year=now.year, month=now.month, day=now.day) if bustime < now: diff --git a/homeassistant/components/habitica/__init__.py b/homeassistant/components/habitica/__init__.py index 611e8df006a..f94a09e4da5 100644 --- a/homeassistant/components/habitica/__init__.py +++ b/homeassistant/components/habitica/__init__.py @@ -5,44 +5,48 @@ import logging import voluptuous as vol from homeassistant.const import ( - CONF_API_KEY, CONF_NAME, CONF_PATH, CONF_SENSORS, CONF_URL) + CONF_API_KEY, + CONF_NAME, + CONF_PATH, + CONF_SENSORS, + CONF_URL, +) from homeassistant.helpers import config_validation as cv, discovery from homeassistant.helpers.aiohttp_client import async_get_clientsession _LOGGER = logging.getLogger(__name__) -CONF_API_USER = 'api_user' +CONF_API_USER = "api_user" -DEFAULT_URL = 'https://habitica.com' -DOMAIN = 'habitica' +DEFAULT_URL = "https://habitica.com" +DOMAIN = "habitica" -ST = SensorType = namedtuple('SensorType', [ - 'name', 'icon', 'unit', 'path' -]) +ST = SensorType = namedtuple("SensorType", ["name", "icon", "unit", "path"]) SENSORS_TYPES = { - 'name': ST('Name', None, '', ['profile', 'name']), - 'hp': ST('HP', 'mdi:heart', 'HP', ['stats', 'hp']), - 'maxHealth': ST('max HP', 'mdi:heart', 'HP', ['stats', 'maxHealth']), - 'mp': ST('Mana', 'mdi:auto-fix', 'MP', ['stats', 'mp']), - 'maxMP': ST('max Mana', 'mdi:auto-fix', 'MP', ['stats', 'maxMP']), - 'exp': ST('EXP', 'mdi:star', 'EXP', ['stats', 'exp']), - 'toNextLevel': ST( - 'Next Lvl', 'mdi:star', 'EXP', ['stats', 'toNextLevel']), - 'lvl': ST( - 'Lvl', 'mdi:arrow-up-bold-circle-outline', 'Lvl', ['stats', 'lvl']), - 'gp': ST('Gold', 'mdi:coin', 'Gold', ['stats', 'gp']), - 'class': ST('Class', 'mdi:sword', '', ['stats', 'class']) + "name": ST("Name", None, "", ["profile", "name"]), + "hp": ST("HP", "mdi:heart", "HP", ["stats", "hp"]), + "maxHealth": ST("max HP", "mdi:heart", "HP", ["stats", "maxHealth"]), + "mp": ST("Mana", "mdi:auto-fix", "MP", ["stats", "mp"]), + "maxMP": ST("max Mana", "mdi:auto-fix", "MP", ["stats", "maxMP"]), + "exp": ST("EXP", "mdi:star", "EXP", ["stats", "exp"]), + "toNextLevel": ST("Next Lvl", "mdi:star", "EXP", ["stats", "toNextLevel"]), + "lvl": ST("Lvl", "mdi:arrow-up-bold-circle-outline", "Lvl", ["stats", "lvl"]), + "gp": ST("Gold", "mdi:coin", "Gold", ["stats", "gp"]), + "class": ST("Class", "mdi:sword", "", ["stats", "class"]), } -INSTANCE_SCHEMA = vol.Schema({ - vol.Optional(CONF_URL, default=DEFAULT_URL): cv.url, - vol.Optional(CONF_NAME): cv.string, - vol.Required(CONF_API_USER): cv.string, - vol.Required(CONF_API_KEY): cv.string, - vol.Optional(CONF_SENSORS, default=list(SENSORS_TYPES)): - vol.All(cv.ensure_list, vol.Unique(), [vol.In(list(SENSORS_TYPES))]), -}) +INSTANCE_SCHEMA = vol.Schema( + { + vol.Optional(CONF_URL, default=DEFAULT_URL): cv.url, + vol.Optional(CONF_NAME): cv.string, + vol.Required(CONF_API_USER): cv.string, + vol.Required(CONF_API_KEY): cv.string, + vol.Optional(CONF_SENSORS, default=list(SENSORS_TYPES)): vol.All( + cv.ensure_list, vol.Unique(), [vol.In(list(SENSORS_TYPES))] + ), + } +) has_unique_values = vol.Schema(vol.Unique()) # pylint: disable=invalid-name # because we want a handy alias @@ -59,33 +63,31 @@ def has_all_unique_users_names(value): """Validate that all user's names are unique and set if any is set.""" names = [user.get(CONF_NAME) for user in value] if None in names and any(name is not None for name in names): - raise vol.Invalid( - 'user names of all users must be set if any is set') + raise vol.Invalid("user names of all users must be set if any is set") if not all(name is None for name in names): has_unique_values(names) return value INSTANCE_LIST_SCHEMA = vol.All( - cv.ensure_list, has_all_unique_users, has_all_unique_users_names, - [INSTANCE_SCHEMA]) + cv.ensure_list, has_all_unique_users, has_all_unique_users_names, [INSTANCE_SCHEMA] +) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: INSTANCE_LIST_SCHEMA -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema({DOMAIN: INSTANCE_LIST_SCHEMA}, extra=vol.ALLOW_EXTRA) -SERVICE_API_CALL = 'api_call' +SERVICE_API_CALL = "api_call" ATTR_NAME = CONF_NAME ATTR_PATH = CONF_PATH -ATTR_ARGS = 'args' -EVENT_API_CALL_SUCCESS = '{0}_{1}_{2}'.format( - DOMAIN, SERVICE_API_CALL, 'success') +ATTR_ARGS = "args" +EVENT_API_CALL_SUCCESS = "{0}_{1}_{2}".format(DOMAIN, SERVICE_API_CALL, "success") -SERVICE_API_CALL_SCHEMA = vol.Schema({ - vol.Required(ATTR_NAME): str, - vol.Required(ATTR_PATH): vol.All(cv.ensure_list, [str]), - vol.Optional(ATTR_ARGS): dict, -}) +SERVICE_API_CALL_SCHEMA = vol.Schema( + { + vol.Required(ATTR_NAME): str, + vol.Required(ATTR_PATH): vol.All(cv.ensure_list, [str]), + vol.Optional(ATTR_ARGS): dict, + } +) async def async_setup(hass, config): @@ -107,17 +109,22 @@ async def async_setup(hass, config): username = instance[CONF_API_USER] password = instance[CONF_API_KEY] name = instance.get(CONF_NAME) - config_dict = {'url': url, 'login': username, 'password': password} + config_dict = {"url": url, "login": username, "password": password} api = HAHabitipyAsync(config_dict) user = await api.user.get() if name is None: - name = user['profile']['name'] + name = user["profile"]["name"] data[name] = api if CONF_SENSORS in instance: hass.async_create_task( discovery.async_load_platform( - hass, 'sensor', DOMAIN, - {'name': name, 'sensors': instance[CONF_SENSORS]}, config)) + hass, + "sensor", + DOMAIN, + {"name": name, "sensors": instance[CONF_SENSORS]}, + config, + ) + ) async def handle_api_call(call): name = call.data[ATTR_NAME] @@ -131,15 +138,16 @@ async def async_setup(hass, config): api = api[element] except KeyError: _LOGGER.error( - "API_CALL: Path %s is invalid for API on '{%s}' element", - path, element) + "API_CALL: Path %s is invalid for API on '{%s}' element", path, element + ) return kwargs = call.data.get(ATTR_ARGS, {}) data = await api(**kwargs) hass.bus.async_fire( - EVENT_API_CALL_SUCCESS, {'name': name, 'path': path, 'data': data}) + EVENT_API_CALL_SUCCESS, {"name": name, "path": path, "data": data} + ) hass.services.async_register( - DOMAIN, SERVICE_API_CALL, handle_api_call, - schema=SERVICE_API_CALL_SCHEMA) + DOMAIN, SERVICE_API_CALL, handle_api_call, schema=SERVICE_API_CALL_SCHEMA + ) return True diff --git a/homeassistant/components/habitica/sensor.py b/homeassistant/components/habitica/sensor.py index fb3a5670c2b..e70d0eb696a 100644 --- a/homeassistant/components/habitica/sensor.py +++ b/homeassistant/components/habitica/sensor.py @@ -11,8 +11,7 @@ _LOGGER = logging.getLogger(__name__) MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15) -async def async_setup_platform( - hass, config, async_add_devices, discovery_info=None): +async def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Set up the habitica platform.""" if discovery_info is None: return @@ -21,10 +20,9 @@ async def async_setup_platform( sensors = discovery_info[habitica.CONF_SENSORS] sensor_data = HabitipyData(hass.data[habitica.DOMAIN][name]) await sensor_data.update() - async_add_devices([ - HabitipySensor(name, sensor, sensor_data) - for sensor in sensors - ], True) + async_add_devices( + [HabitipySensor(name, sensor, sensor_data) for sensor in sensors], True + ) class HabitipyData: @@ -68,8 +66,7 @@ class HabitipySensor(Entity): @property def name(self): """Return the name of the sensor.""" - return "{0}_{1}_{2}".format( - habitica.DOMAIN, self._name, self._sensor_name) + return "{0}_{1}_{2}".format(habitica.DOMAIN, self._name, self._sensor_name) @property def state(self): diff --git a/homeassistant/components/hangouts/__init__.py b/homeassistant/components/hangouts/__init__.py index 50936ac62a0..885ac5d1670 100644 --- a/homeassistant/components/hangouts/__init__.py +++ b/homeassistant/components/hangouts/__init__.py @@ -12,26 +12,44 @@ import homeassistant.helpers.config_validation as cv from .intents import HelpIntent from .config_flow import HangoutsFlowHandler # noqa: F401 from .const import ( - CONF_BOT, CONF_DEFAULT_CONVERSATIONS, CONF_ERROR_SUPPRESSED_CONVERSATIONS, - CONF_INTENTS, CONF_MATCHERS, CONF_REFRESH_TOKEN, CONF_SENTENCES, DOMAIN, - EVENT_HANGOUTS_CONNECTED, EVENT_HANGOUTS_CONVERSATIONS_CHANGED, - EVENT_HANGOUTS_CONVERSATIONS_RESOLVED, INTENT_HELP, INTENT_SCHEMA, - MESSAGE_SCHEMA, SERVICE_RECONNECT, SERVICE_SEND_MESSAGE, SERVICE_UPDATE, - TARGETS_SCHEMA) + CONF_BOT, + CONF_DEFAULT_CONVERSATIONS, + CONF_ERROR_SUPPRESSED_CONVERSATIONS, + CONF_INTENTS, + CONF_MATCHERS, + CONF_REFRESH_TOKEN, + CONF_SENTENCES, + DOMAIN, + EVENT_HANGOUTS_CONNECTED, + EVENT_HANGOUTS_CONVERSATIONS_CHANGED, + EVENT_HANGOUTS_CONVERSATIONS_RESOLVED, + INTENT_HELP, + INTENT_SCHEMA, + MESSAGE_SCHEMA, + SERVICE_RECONNECT, + SERVICE_SEND_MESSAGE, + SERVICE_UPDATE, + TARGETS_SCHEMA, +) _LOGGER = logging.getLogger(__name__) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Optional(CONF_INTENTS, default={}): vol.Schema({ - cv.string: INTENT_SCHEMA - }), - vol.Optional(CONF_DEFAULT_CONVERSATIONS, default=[]): - [TARGETS_SCHEMA], - vol.Optional(CONF_ERROR_SUPPRESSED_CONVERSATIONS, default=[]): - [TARGETS_SCHEMA], - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Optional(CONF_INTENTS, default={}): vol.Schema( + {cv.string: INTENT_SCHEMA} + ), + vol.Optional(CONF_DEFAULT_CONVERSATIONS, default=[]): [TARGETS_SCHEMA], + vol.Optional(CONF_ERROR_SUPPRESSED_CONVERSATIONS, default=[]): [ + TARGETS_SCHEMA + ], + } + ) + }, + extra=vol.ALLOW_EXTRA, +) async def async_setup(hass, config): @@ -50,14 +68,16 @@ async def async_setup(hass, config): hass.data[DOMAIN] = { CONF_INTENTS: config[CONF_INTENTS], CONF_DEFAULT_CONVERSATIONS: config[CONF_DEFAULT_CONVERSATIONS], - CONF_ERROR_SUPPRESSED_CONVERSATIONS: - config[CONF_ERROR_SUPPRESSED_CONVERSATIONS], + CONF_ERROR_SUPPRESSED_CONVERSATIONS: config[ + CONF_ERROR_SUPPRESSED_CONVERSATIONS + ], } - if (hass.data[DOMAIN][CONF_INTENTS] and - INTENT_HELP not in hass.data[DOMAIN][CONF_INTENTS]): - hass.data[DOMAIN][CONF_INTENTS][INTENT_HELP] = { - CONF_SENTENCES: ['HELP']} + if ( + hass.data[DOMAIN][CONF_INTENTS] + and INTENT_HELP not in hass.data[DOMAIN][CONF_INTENTS] + ): + hass.data[DOMAIN][CONF_INTENTS][INTENT_HELP] = {CONF_SENTENCES: ["HELP"]} for data in hass.data[DOMAIN][CONF_INTENTS].values(): matchers = [] @@ -66,9 +86,11 @@ async def async_setup(hass, config): data[CONF_MATCHERS] = matchers - hass.async_create_task(hass.config_entries.flow.async_init( - DOMAIN, context={'source': config_entries.SOURCE_IMPORT} - )) + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_IMPORT} + ) + ) return True @@ -85,46 +107,47 @@ async def async_setup_entry(hass, config): config.data.get(CONF_REFRESH_TOKEN), hass.data[DOMAIN][CONF_INTENTS], hass.data[DOMAIN][CONF_DEFAULT_CONVERSATIONS], - hass.data[DOMAIN][CONF_ERROR_SUPPRESSED_CONVERSATIONS]) + hass.data[DOMAIN][CONF_ERROR_SUPPRESSED_CONVERSATIONS], + ) hass.data[DOMAIN][CONF_BOT] = bot except GoogleAuthError as exception: _LOGGER.error("Hangouts failed to log in: %s", str(exception)) return False dispatcher.async_dispatcher_connect( - hass, - EVENT_HANGOUTS_CONNECTED, - bot.async_handle_update_users_and_conversations) + hass, EVENT_HANGOUTS_CONNECTED, bot.async_handle_update_users_and_conversations + ) dispatcher.async_dispatcher_connect( - hass, - EVENT_HANGOUTS_CONVERSATIONS_CHANGED, - bot.async_resolve_conversations) + hass, EVENT_HANGOUTS_CONVERSATIONS_CHANGED, bot.async_resolve_conversations + ) dispatcher.async_dispatcher_connect( hass, EVENT_HANGOUTS_CONVERSATIONS_RESOLVED, - bot.async_update_conversation_commands) + bot.async_update_conversation_commands, + ) - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, - bot.async_handle_hass_stop) + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, bot.async_handle_hass_stop) await bot.async_connect() - hass.services.async_register(DOMAIN, SERVICE_SEND_MESSAGE, - bot.async_handle_send_message, - schema=MESSAGE_SCHEMA) - hass.services.async_register(DOMAIN, - SERVICE_UPDATE, - bot. - async_handle_update_users_and_conversations, - schema=vol.Schema({})) + hass.services.async_register( + DOMAIN, + SERVICE_SEND_MESSAGE, + bot.async_handle_send_message, + schema=MESSAGE_SCHEMA, + ) + hass.services.async_register( + DOMAIN, + SERVICE_UPDATE, + bot.async_handle_update_users_and_conversations, + schema=vol.Schema({}), + ) - hass.services.async_register(DOMAIN, - SERVICE_RECONNECT, - bot. - async_handle_reconnect, - schema=vol.Schema({})) + hass.services.async_register( + DOMAIN, SERVICE_RECONNECT, bot.async_handle_reconnect, schema=vol.Schema({}) + ) intent.async_register(hass, HelpIntent(hass)) diff --git a/homeassistant/components/hangouts/config_flow.py b/homeassistant/components/hangouts/config_flow.py index 743c49abfdf..8e262d8b40f 100644 --- a/homeassistant/components/hangouts/config_flow.py +++ b/homeassistant/components/hangouts/config_flow.py @@ -7,8 +7,12 @@ from homeassistant import config_entries from homeassistant.const import CONF_EMAIL, CONF_PASSWORD from homeassistant.core import callback -from .const import CONF_2FA, CONF_REFRESH_TOKEN, CONF_AUTH_CODE, \ - DOMAIN as HANGOUTS_DOMAIN +from .const import ( + CONF_2FA, + CONF_REFRESH_TOKEN, + CONF_AUTH_CODE, + DOMAIN as HANGOUTS_DOMAIN, +) @callback @@ -41,26 +45,31 @@ class HangoutsFlowHandler(config_entries.ConfigFlow): if user_input is not None: from hangups import get_auth - from .hangups_utils import (HangoutsCredentials, - HangoutsRefreshToken, - GoogleAuthError, Google2FAError) + from .hangups_utils import ( + HangoutsCredentials, + HangoutsRefreshToken, + GoogleAuthError, + Google2FAError, + ) + user_email = user_input[CONF_EMAIL] user_password = user_input[CONF_PASSWORD] user_auth_code = user_input.get(CONF_AUTH_CODE) manual_login = user_auth_code is not None user_pin = None - self._credentials = HangoutsCredentials(user_email, - user_password, - user_pin, - user_auth_code) + self._credentials = HangoutsCredentials( + user_email, user_password, user_pin, user_auth_code + ) self._refresh_token = HangoutsRefreshToken(None) try: await self.hass.async_add_executor_job( - functools.partial(get_auth, - self._credentials, - self._refresh_token, - manual_login=manual_login) + functools.partial( + get_auth, + self._credentials, + self._refresh_token, + manual_login=manual_login, + ) ) return await self.async_step_final() @@ -68,19 +77,21 @@ class HangoutsFlowHandler(config_entries.ConfigFlow): if isinstance(err, Google2FAError): return await self.async_step_2fa() msg = str(err) - if msg == 'Unknown verification code input': - errors['base'] = 'invalid_2fa_method' + if msg == "Unknown verification code input": + errors["base"] = "invalid_2fa_method" else: - errors['base'] = 'invalid_login' + errors["base"] = "invalid_login" return self.async_show_form( - step_id='user', - data_schema=vol.Schema({ - vol.Required(CONF_EMAIL): str, - vol.Required(CONF_PASSWORD): str, - vol.Optional(CONF_AUTH_CODE): str - }), - errors=errors + step_id="user", + data_schema=vol.Schema( + { + vol.Required(CONF_EMAIL): str, + vol.Required(CONF_PASSWORD): str, + vol.Optional(CONF_AUTH_CODE): str, + } + ), + errors=errors, ) async def async_step_2fa(self, user_input=None): @@ -90,22 +101,21 @@ class HangoutsFlowHandler(config_entries.ConfigFlow): if user_input is not None: from hangups import get_auth from .hangups_utils import GoogleAuthError + self._credentials.set_verification_code(user_input[CONF_2FA]) try: - await self.hass.async_add_executor_job(get_auth, - self._credentials, - self._refresh_token) + await self.hass.async_add_executor_job( + get_auth, self._credentials, self._refresh_token + ) return await self.async_step_final() except GoogleAuthError: - errors['base'] = 'invalid_2fa' + errors["base"] = "invalid_2fa" return self.async_show_form( step_id=CONF_2FA, - data_schema=vol.Schema({ - vol.Required(CONF_2FA): str, - }), - errors=errors + data_schema=vol.Schema({vol.Required(CONF_2FA): str}), + errors=errors, ) async def async_step_final(self): @@ -114,8 +124,9 @@ class HangoutsFlowHandler(config_entries.ConfigFlow): title=self._credentials.get_email(), data={ CONF_EMAIL: self._credentials.get_email(), - CONF_REFRESH_TOKEN: self._refresh_token.get() - }) + CONF_REFRESH_TOKEN: self._refresh_token.get(), + }, + ) async def async_step_import(self, _): """Handle a flow import.""" diff --git a/homeassistant/components/hangouts/const.py b/homeassistant/components/hangouts/const.py index f664e769b9f..0508bf48703 100644 --- a/homeassistant/components/hangouts/const.py +++ b/homeassistant/components/hangouts/const.py @@ -3,77 +3,83 @@ import logging import voluptuous as vol -from homeassistant.components.notify import ( - ATTR_DATA, ATTR_MESSAGE, ATTR_TARGET) +from homeassistant.components.notify import ATTR_DATA, ATTR_MESSAGE, ATTR_TARGET import homeassistant.helpers.config_validation as cv -_LOGGER = logging.getLogger('.') +_LOGGER = logging.getLogger(".") -DOMAIN = 'hangouts' +DOMAIN = "hangouts" -CONF_2FA = '2fa' -CONF_AUTH_CODE = 'authorization_code' -CONF_REFRESH_TOKEN = 'refresh_token' -CONF_BOT = 'bot' +CONF_2FA = "2fa" +CONF_AUTH_CODE = "authorization_code" +CONF_REFRESH_TOKEN = "refresh_token" +CONF_BOT = "bot" -CONF_CONVERSATIONS = 'conversations' -CONF_DEFAULT_CONVERSATIONS = 'default_conversations' -CONF_ERROR_SUPPRESSED_CONVERSATIONS = 'error_suppressed_conversations' +CONF_CONVERSATIONS = "conversations" +CONF_DEFAULT_CONVERSATIONS = "default_conversations" +CONF_ERROR_SUPPRESSED_CONVERSATIONS = "error_suppressed_conversations" -CONF_INTENTS = 'intents' -CONF_INTENT_TYPE = 'intent_type' -CONF_SENTENCES = 'sentences' -CONF_MATCHERS = 'matchers' +CONF_INTENTS = "intents" +CONF_INTENT_TYPE = "intent_type" +CONF_SENTENCES = "sentences" +CONF_MATCHERS = "matchers" -INTENT_HELP = 'HangoutsHelp' +INTENT_HELP = "HangoutsHelp" -EVENT_HANGOUTS_CONNECTED = 'hangouts_connected' -EVENT_HANGOUTS_DISCONNECTED = 'hangouts_disconnected' -EVENT_HANGOUTS_USERS_CHANGED = 'hangouts_users_changed' -EVENT_HANGOUTS_CONVERSATIONS_CHANGED = 'hangouts_conversations_changed' -EVENT_HANGOUTS_CONVERSATIONS_RESOLVED = 'hangouts_conversations_resolved' -EVENT_HANGOUTS_MESSAGE_RECEIVED = 'hangouts_message_received' +EVENT_HANGOUTS_CONNECTED = "hangouts_connected" +EVENT_HANGOUTS_DISCONNECTED = "hangouts_disconnected" +EVENT_HANGOUTS_USERS_CHANGED = "hangouts_users_changed" +EVENT_HANGOUTS_CONVERSATIONS_CHANGED = "hangouts_conversations_changed" +EVENT_HANGOUTS_CONVERSATIONS_RESOLVED = "hangouts_conversations_resolved" +EVENT_HANGOUTS_MESSAGE_RECEIVED = "hangouts_message_received" -CONF_CONVERSATION_ID = 'id' -CONF_CONVERSATION_NAME = 'name' +CONF_CONVERSATION_ID = "id" +CONF_CONVERSATION_NAME = "name" -SERVICE_SEND_MESSAGE = 'send_message' -SERVICE_UPDATE = 'update' -SERVICE_RECONNECT = 'reconnect' +SERVICE_SEND_MESSAGE = "send_message" +SERVICE_UPDATE = "update" +SERVICE_RECONNECT = "reconnect" TARGETS_SCHEMA = vol.All( - vol.Schema({ - vol.Exclusive(CONF_CONVERSATION_ID, 'id or name'): cv.string, - vol.Exclusive(CONF_CONVERSATION_NAME, 'id or name'): cv.string - }), - cv.has_at_least_one_key(CONF_CONVERSATION_ID, CONF_CONVERSATION_NAME) + vol.Schema( + { + vol.Exclusive(CONF_CONVERSATION_ID, "id or name"): cv.string, + vol.Exclusive(CONF_CONVERSATION_NAME, "id or name"): cv.string, + } + ), + cv.has_at_least_one_key(CONF_CONVERSATION_ID, CONF_CONVERSATION_NAME), +) +MESSAGE_SEGMENT_SCHEMA = vol.Schema( + { + vol.Required("text"): cv.string, + vol.Optional("is_bold"): cv.boolean, + vol.Optional("is_italic"): cv.boolean, + vol.Optional("is_strikethrough"): cv.boolean, + vol.Optional("is_underline"): cv.boolean, + vol.Optional("parse_str"): cv.boolean, + vol.Optional("link_target"): cv.string, + } +) +MESSAGE_DATA_SCHEMA = vol.Schema( + {vol.Optional("image_file"): cv.string, vol.Optional("image_url"): cv.string} ) -MESSAGE_SEGMENT_SCHEMA = vol.Schema({ - vol.Required('text'): cv.string, - vol.Optional('is_bold'): cv.boolean, - vol.Optional('is_italic'): cv.boolean, - vol.Optional('is_strikethrough'): cv.boolean, - vol.Optional('is_underline'): cv.boolean, - vol.Optional('parse_str'): cv.boolean, - vol.Optional('link_target'): cv.string -}) -MESSAGE_DATA_SCHEMA = vol.Schema({ - vol.Optional('image_file'): cv.string, - vol.Optional('image_url'): cv.string -}) -MESSAGE_SCHEMA = vol.Schema({ - vol.Required(ATTR_TARGET): [TARGETS_SCHEMA], - vol.Required(ATTR_MESSAGE): [MESSAGE_SEGMENT_SCHEMA], - vol.Optional(ATTR_DATA): MESSAGE_DATA_SCHEMA -}) +MESSAGE_SCHEMA = vol.Schema( + { + vol.Required(ATTR_TARGET): [TARGETS_SCHEMA], + vol.Required(ATTR_MESSAGE): [MESSAGE_SEGMENT_SCHEMA], + vol.Optional(ATTR_DATA): MESSAGE_DATA_SCHEMA, + } +) INTENT_SCHEMA = vol.All( # Basic Schema - vol.Schema({ - vol.Required(CONF_SENTENCES): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_CONVERSATIONS): [TARGETS_SCHEMA] - }), + vol.Schema( + { + vol.Required(CONF_SENTENCES): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_CONVERSATIONS): [TARGETS_SCHEMA], + } + ) ) diff --git a/homeassistant/components/hangouts/hangouts_bot.py b/homeassistant/components/hangouts/hangouts_bot.py index fe72c50de77..d444e852cca 100644 --- a/homeassistant/components/hangouts/hangouts_bot.py +++ b/homeassistant/components/hangouts/hangouts_bot.py @@ -9,11 +9,21 @@ from homeassistant.helpers import dispatcher, intent from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import ( - ATTR_DATA, ATTR_MESSAGE, ATTR_TARGET, CONF_CONVERSATION_ID, - CONF_CONVERSATION_NAME, CONF_CONVERSATIONS, CONF_MATCHERS, DOMAIN, - EVENT_HANGOUTS_CONNECTED, EVENT_HANGOUTS_CONVERSATIONS_CHANGED, - EVENT_HANGOUTS_CONVERSATIONS_RESOLVED, EVENT_HANGOUTS_DISCONNECTED, - EVENT_HANGOUTS_MESSAGE_RECEIVED, INTENT_HELP) + ATTR_DATA, + ATTR_MESSAGE, + ATTR_TARGET, + CONF_CONVERSATION_ID, + CONF_CONVERSATION_NAME, + CONF_CONVERSATIONS, + CONF_MATCHERS, + DOMAIN, + EVENT_HANGOUTS_CONNECTED, + EVENT_HANGOUTS_CONVERSATIONS_CHANGED, + EVENT_HANGOUTS_CONVERSATIONS_RESOLVED, + EVENT_HANGOUTS_DISCONNECTED, + EVENT_HANGOUTS_MESSAGE_RECEIVED, + INTENT_HELP, +) _LOGGER = logging.getLogger(__name__) @@ -21,8 +31,9 @@ _LOGGER = logging.getLogger(__name__) class HangoutsBot: """The Hangouts Bot.""" - def __init__(self, hass, refresh_token, intents, - default_convs, error_suppressed_convs): + def __init__( + self, hass, refresh_token, intents, default_convs, error_suppressed_convs + ): """Set up the client.""" self.hass = hass self._connected = False @@ -41,8 +52,10 @@ class HangoutsBot: self._error_suppressed_conv_ids = None dispatcher.async_dispatcher_connect( - self.hass, EVENT_HANGOUTS_MESSAGE_RECEIVED, - self._async_handle_conversation_message) + self.hass, + EVENT_HANGOUTS_MESSAGE_RECEIVED, + self._async_handle_conversation_message, + ) def _resolve_conversation_id(self, obj): if CONF_CONVERSATION_ID in obj: @@ -70,14 +83,15 @@ class HangoutsBot: conv_id = self._resolve_conversation_id(conversation) if conv_id is not None: conversations.append(conv_id) - data['_' + CONF_CONVERSATIONS] = conversations + data["_" + CONF_CONVERSATIONS] = conversations elif self._default_conv_ids: - data['_' + CONF_CONVERSATIONS] = self._default_conv_ids + data["_" + CONF_CONVERSATIONS] = self._default_conv_ids else: - data['_' + CONF_CONVERSATIONS] = \ - [conv.id_ for conv in self._conversation_list.get_all()] + data["_" + CONF_CONVERSATIONS] = [ + conv.id_ for conv in self._conversation_list.get_all() + ] - for conv_id in data['_' + CONF_CONVERSATIONS]: + for conv_id in data["_" + CONF_CONVERSATIONS]: if conv_id not in self._conversation_intents: self._conversation_intents[conv_id] = {} @@ -85,11 +99,13 @@ class HangoutsBot: try: self._conversation_list.on_event.remove_observer( - self._async_handle_conversation_event) + self._async_handle_conversation_event + ) except ValueError: pass self._conversation_list.on_event.add_observer( - self._async_handle_conversation_event) + self._async_handle_conversation_event + ) def async_resolve_conversations(self, _): """Resolve the list of default and error suppressed conversations.""" @@ -105,34 +121,36 @@ class HangoutsBot: conv_id = self._resolve_conversation_id(conversation) if conv_id is not None: self._error_suppressed_conv_ids.append(conv_id) - dispatcher.async_dispatcher_send(self.hass, - EVENT_HANGOUTS_CONVERSATIONS_RESOLVED) + dispatcher.async_dispatcher_send( + self.hass, EVENT_HANGOUTS_CONVERSATIONS_RESOLVED + ) async def _async_handle_conversation_event(self, event): from hangups import ChatMessageEvent - if isinstance(event, ChatMessageEvent): - dispatcher.async_dispatcher_send(self.hass, - EVENT_HANGOUTS_MESSAGE_RECEIVED, - event.conversation_id, - event.user_id, event) - async def _async_handle_conversation_message(self, - conv_id, user_id, event): + if isinstance(event, ChatMessageEvent): + dispatcher.async_dispatcher_send( + self.hass, + EVENT_HANGOUTS_MESSAGE_RECEIVED, + event.conversation_id, + event.user_id, + event, + ) + + async def _async_handle_conversation_message(self, conv_id, user_id, event): """Handle a message sent to a conversation.""" user = self._user_list.get_user(user_id) if user.is_self: return message = event.text - _LOGGER.debug("Handling message '%s' from %s", - message, user.full_name) + _LOGGER.debug("Handling message '%s' from %s", message, user.full_name) intents = self._conversation_intents.get(conv_id) if intents is not None: is_error = False try: - intent_result = await self._async_process(intents, message, - conv_id) + intent_result = await self._async_process(intents, message, conv_id) except (intent.UnknownIntent, intent.IntentHandleError) as err: is_error = True intent_result = intent.IntentResponse() @@ -141,18 +159,20 @@ class HangoutsBot: if intent_result is None: is_error = True intent_result = intent.IntentResponse() - intent_result.async_set_speech( - "Sorry, I didn't understand that") + intent_result.async_set_speech("Sorry, I didn't understand that") - message = intent_result.as_dict().get('speech', {})\ - .get('plain', {}).get('speech') + message = ( + intent_result.as_dict().get("speech", {}).get("plain", {}).get("speech") + ) if (message is not None) and not ( - is_error and conv_id in self._error_suppressed_conv_ids): + is_error and conv_id in self._error_suppressed_conv_ids + ): await self._async_send_message( - [{'text': message, 'parse_str': True}], + [{"text": message, "parse_str": True}], [{CONF_CONVERSATION_ID: conv_id}], - None) + None, + ) async def _async_process(self, intents, text, conv_id): """Detect a matching intent.""" @@ -164,13 +184,15 @@ class HangoutsBot: continue if intent_type == INTENT_HELP: return await self.hass.helpers.intent.async_handle( - DOMAIN, intent_type, - {'conv_id': {'value': conv_id}}, text) + DOMAIN, intent_type, {"conv_id": {"value": conv_id}}, text + ) return await self.hass.helpers.intent.async_handle( - DOMAIN, intent_type, - {key: {'value': value} - for key, value in match.groupdict().items()}, text) + DOMAIN, + intent_type, + {key: {"value": value} for key, value in match.groupdict().items()}, + text, + ) async def async_connect(self): """Login to the Google Hangouts.""" @@ -178,9 +200,12 @@ class HangoutsBot: from hangups import Client from hangups import get_auth + session = await self.hass.async_add_executor_job( - get_auth, HangoutsCredentials(None, None, None), - HangoutsRefreshToken(self._refresh_token)) + get_auth, + HangoutsCredentials(None, None, None), + HangoutsRefreshToken(self._refresh_token), + ) self._client = Client(session) self._client.on_connect.add_observer(self._on_connect) @@ -189,18 +214,17 @@ class HangoutsBot: self.hass.loop.create_task(self._client.connect()) def _on_connect(self): - _LOGGER.debug('Connected!') + _LOGGER.debug("Connected!") self._connected = True dispatcher.async_dispatcher_send(self.hass, EVENT_HANGOUTS_CONNECTED) async def _on_disconnect(self): """Handle disconnecting.""" if self._connected: - _LOGGER.debug('Connection lost! Reconnect...') + _LOGGER.debug("Connection lost! Reconnect...") await self.async_connect() else: - dispatcher.async_dispatcher_send(self.hass, - EVENT_HANGOUTS_DISCONNECTED) + dispatcher.async_dispatcher_send(self.hass, EVENT_HANGOUTS_DISCONNECTED) async def async_disconnect(self): """Disconnect the client if it is connected.""" @@ -217,11 +241,11 @@ class HangoutsBot: for target in targets: conversation = None if CONF_CONVERSATION_ID in target: - conversation = self._conversation_list.get( - target[CONF_CONVERSATION_ID]) + conversation = self._conversation_list.get(target[CONF_CONVERSATION_ID]) elif CONF_CONVERSATION_NAME in target: conversation = self._resolve_conversation_name( - target[CONF_CONVERSATION_NAME]) + target[CONF_CONVERSATION_NAME] + ) if conversation is not None: conversations.append(conversation) @@ -229,31 +253,32 @@ class HangoutsBot: return False from hangups import ChatMessageSegment, hangouts_pb2 + messages = [] for segment in message: if messages: - messages.append(ChatMessageSegment('', - segment_type=hangouts_pb2. - SEGMENT_TYPE_LINE_BREAK)) - if 'parse_str' in segment and segment['parse_str']: - messages.extend(ChatMessageSegment.from_str(segment['text'])) + messages.append( + ChatMessageSegment( + "", segment_type=hangouts_pb2.SEGMENT_TYPE_LINE_BREAK + ) + ) + if "parse_str" in segment and segment["parse_str"]: + messages.extend(ChatMessageSegment.from_str(segment["text"])) else: - if 'parse_str' in segment: - del segment['parse_str'] + if "parse_str" in segment: + del segment["parse_str"] messages.append(ChatMessageSegment(**segment)) image_file = None if data: - if data.get('image_url'): - uri = data.get('image_url') + if data.get("image_url"): + uri = data.get("image_url") try: websession = async_get_clientsession(self.hass) async with websession.get(uri, timeout=5) as response: if response.status != 200: _LOGGER.error( - 'Fetch image failed, %s, %s', - response.status, - response + "Fetch image failed, %s, %s", response.status, response ) image_file = None else: @@ -261,21 +286,16 @@ class HangoutsBot: image_file = io.BytesIO(image_data) image_file.name = "image.png" except (asyncio.TimeoutError, aiohttp.ClientError) as error: - _LOGGER.error( - 'Failed to fetch image, %s', - type(error) - ) + _LOGGER.error("Failed to fetch image, %s", type(error)) image_file = None - elif data.get('image_file'): - uri = data.get('image_file') + elif data.get("image_file"): + uri = data.get("image_file") if self.hass.config.is_allowed_path(uri): try: - image_file = open(uri, 'rb') + image_file = open(uri, "rb") except IOError as error: _LOGGER.error( - 'Image file I/O error(%s): %s', - error.errno, - error.strerror + "Image file I/O error(%s): %s", error.errno, error.strerror ) else: _LOGGER.error('Path "%s" not allowed', uri) @@ -287,29 +307,37 @@ class HangoutsBot: async def _async_list_conversations(self): import hangups - self._user_list, self._conversation_list = \ - (await hangups.build_user_conversation_list(self._client)) + + self._user_list, self._conversation_list = await hangups.build_user_conversation_list( + self._client + ) conversations = {} for i, conv in enumerate(self._conversation_list.get_all()): users_in_conversation = [] for user in conv.users: users_in_conversation.append(user.full_name) - conversations[str(i)] = {CONF_CONVERSATION_ID: str(conv.id_), - CONF_CONVERSATION_NAME: conv.name, - 'users': users_in_conversation} + conversations[str(i)] = { + CONF_CONVERSATION_ID: str(conv.id_), + CONF_CONVERSATION_NAME: conv.name, + "users": users_in_conversation, + } - self.hass.states.async_set("{}.conversations".format(DOMAIN), - len(self._conversation_list.get_all()), - attributes=conversations) - dispatcher.async_dispatcher_send(self.hass, - EVENT_HANGOUTS_CONVERSATIONS_CHANGED, - conversations) + self.hass.states.async_set( + "{}.conversations".format(DOMAIN), + len(self._conversation_list.get_all()), + attributes=conversations, + ) + dispatcher.async_dispatcher_send( + self.hass, EVENT_HANGOUTS_CONVERSATIONS_CHANGED, conversations + ) async def async_handle_send_message(self, service): """Handle the send_message service.""" - await self._async_send_message(service.data[ATTR_MESSAGE], - service.data[ATTR_TARGET], - service.data.get(ATTR_DATA, {})) + await self._async_send_message( + service.data[ATTR_MESSAGE], + service.data[ATTR_TARGET], + service.data.get(ATTR_DATA, {}), + ) async def async_handle_update_users_and_conversations(self, _=None): """Handle the update_users_and_conversations service.""" diff --git a/homeassistant/components/hangouts/intents.py b/homeassistant/components/hangouts/intents.py index 3887a644700..a26da7a4872 100644 --- a/homeassistant/components/hangouts/intents.py +++ b/homeassistant/components/hangouts/intents.py @@ -9,9 +9,7 @@ class HelpIntent(intent.IntentHandler): """Handle Help intents.""" intent_type = INTENT_HELP - slot_schema = { - 'conv_id': cv.string - } + slot_schema = {"conv_id": cv.string} def __init__(self, hass): """Set up the intent.""" @@ -20,13 +18,13 @@ class HelpIntent(intent.IntentHandler): async def async_handle(self, intent_obj): """Handle the intent.""" slots = self.async_validate_slots(intent_obj.slots) - conv_id = slots['conv_id']['value'] + conv_id = slots["conv_id"]["value"] intents = self.hass.data[DOMAIN][CONF_BOT].get_intents(conv_id) response = intent_obj.create_response() help_text = "I understand the following sentences:" for intent_data in intents.values(): - for sentence in intent_data['sentences']: + for sentence in intent_data["sentences"]: help_text += "\n'{}'".format(sentence) response.async_set_speech(help_text) diff --git a/homeassistant/components/hangouts/notify.py b/homeassistant/components/hangouts/notify.py index e88f80afbcd..01e4208fd48 100644 --- a/homeassistant/components/hangouts/notify.py +++ b/homeassistant/components/hangouts/notify.py @@ -4,17 +4,25 @@ import logging import voluptuous as vol from homeassistant.components.notify import ( - ATTR_DATA, ATTR_MESSAGE, ATTR_TARGET, PLATFORM_SCHEMA, - BaseNotificationService) + ATTR_DATA, + ATTR_MESSAGE, + ATTR_TARGET, + PLATFORM_SCHEMA, + BaseNotificationService, +) from .const import ( - CONF_DEFAULT_CONVERSATIONS, DOMAIN, SERVICE_SEND_MESSAGE, TARGETS_SCHEMA) + CONF_DEFAULT_CONVERSATIONS, + DOMAIN, + SERVICE_SEND_MESSAGE, + TARGETS_SCHEMA, +) _LOGGER = logging.getLogger(__name__) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_DEFAULT_CONVERSATIONS): [TARGETS_SCHEMA] -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_DEFAULT_CONVERSATIONS): [TARGETS_SCHEMA]} +) def get_service(hass, config, discovery_info=None): @@ -35,21 +43,19 @@ class HangoutsNotificationService(BaseNotificationService): if ATTR_TARGET in kwargs: target_conversations = [] for target in kwargs.get(ATTR_TARGET): - target_conversations.append({'id': target}) + target_conversations.append({"id": target}) else: target_conversations = self._default_conversations messages = [] - if 'title' in kwargs: - messages.append({'text': kwargs['title'], 'is_bold': True}) + if "title" in kwargs: + messages.append({"text": kwargs["title"], "is_bold": True}) - messages.append({'text': message, 'parse_str': True}) - service_data = { - ATTR_TARGET: target_conversations, - ATTR_MESSAGE: messages, - } + messages.append({"text": message, "parse_str": True}) + service_data = {ATTR_TARGET: target_conversations, ATTR_MESSAGE: messages} if kwargs[ATTR_DATA]: service_data[ATTR_DATA] = kwargs[ATTR_DATA] return self.hass.services.call( - DOMAIN, SERVICE_SEND_MESSAGE, service_data=service_data) + DOMAIN, SERVICE_SEND_MESSAGE, service_data=service_data + ) diff --git a/homeassistant/components/harman_kardon_avr/media_player.py b/homeassistant/components/harman_kardon_avr/media_player.py index dc200f39b9c..01948943adf 100644 --- a/homeassistant/components/harman_kardon_avr/media_player.py +++ b/homeassistant/components/harman_kardon_avr/media_player.py @@ -4,28 +4,36 @@ import logging import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.media_player import ( - MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA from homeassistant.components.media_player.const import ( - SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, - SUPPORT_TURN_ON, SUPPORT_SELECT_SOURCE) -from homeassistant.const import ( - CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_ON) + SUPPORT_TURN_OFF, + SUPPORT_VOLUME_MUTE, + SUPPORT_VOLUME_STEP, + SUPPORT_TURN_ON, + SUPPORT_SELECT_SOURCE, +) +from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_ON _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'Harman Kardon AVR' +DEFAULT_NAME = "Harman Kardon AVR" DEFAULT_PORT = 10025 -SUPPORT_HARMAN_KARDON_AVR = SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_MUTE | \ - SUPPORT_TURN_OFF | SUPPORT_TURN_ON | \ - SUPPORT_SELECT_SOURCE +SUPPORT_HARMAN_KARDON_AVR = ( + SUPPORT_VOLUME_STEP + | SUPPORT_VOLUME_MUTE + | SUPPORT_TURN_OFF + | SUPPORT_TURN_ON + | SUPPORT_SELECT_SOURCE +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + } +) def setup_platform(hass, config, add_entities, discover_info=None): diff --git a/homeassistant/components/harmony/remote.py b/homeassistant/components/harmony/remote.py index c4aebb1bdcb..b78f276bf28 100644 --- a/homeassistant/components/harmony/remote.py +++ b/homeassistant/components/harmony/remote.py @@ -7,10 +7,21 @@ import voluptuous as vol from homeassistant.components import remote from homeassistant.components.remote import ( - ATTR_ACTIVITY, ATTR_DELAY_SECS, ATTR_DEVICE, ATTR_HOLD_SECS, - ATTR_NUM_REPEATS, DEFAULT_DELAY_SECS, DOMAIN, PLATFORM_SCHEMA) + ATTR_ACTIVITY, + ATTR_DELAY_SECS, + ATTR_DEVICE, + ATTR_HOLD_SECS, + ATTR_NUM_REPEATS, + DEFAULT_DELAY_SECS, + DOMAIN, + PLATFORM_SCHEMA, +) from homeassistant.const import ( - ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_PORT, EVENT_HOMEASSISTANT_STOP + ATTR_ENTITY_ID, + CONF_HOST, + CONF_NAME, + CONF_PORT, + EVENT_HOMEASSISTANT_STOP, ) import homeassistant.helpers.config_validation as cv from homeassistant.exceptions import PlatformNotReady @@ -18,37 +29,37 @@ from homeassistant.util import slugify _LOGGER = logging.getLogger(__name__) -ATTR_CHANNEL = 'channel' -ATTR_CURRENT_ACTIVITY = 'current_activity' +ATTR_CHANNEL = "channel" +ATTR_CURRENT_ACTIVITY = "current_activity" DEFAULT_PORT = 8088 DEVICES = [] -CONF_DEVICE_CACHE = 'harmony_device_cache' +CONF_DEVICE_CACHE = "harmony_device_cache" -SERVICE_SYNC = 'harmony_sync' -SERVICE_CHANGE_CHANNEL = 'harmony_change_channel' +SERVICE_SYNC = "harmony_sync" +SERVICE_CHANGE_CHANNEL = "harmony_change_channel" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(ATTR_ACTIVITY): cv.string, - vol.Required(CONF_NAME): cv.string, - vol.Optional(ATTR_DELAY_SECS, default=DEFAULT_DELAY_SECS): - vol.Coerce(float), - vol.Optional(CONF_HOST): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(ATTR_ACTIVITY): cv.string, + vol.Required(CONF_NAME): cv.string, + vol.Optional(ATTR_DELAY_SECS, default=DEFAULT_DELAY_SECS): vol.Coerce(float), + vol.Optional(CONF_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + } +) -HARMONY_SYNC_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, -}) +HARMONY_SYNC_SCHEMA = vol.Schema({vol.Optional(ATTR_ENTITY_ID): cv.entity_ids}) -HARMONY_CHANGE_CHANNEL_SCHEMA = vol.Schema({ - vol.Required(ATTR_ENTITY_ID): cv.entity_ids, - vol.Required(ATTR_CHANNEL): cv.positive_int, -}) +HARMONY_CHANGE_CHANNEL_SCHEMA = vol.Schema( + { + vol.Required(ATTR_ENTITY_ID): cv.entity_ids, + vol.Required(ATTR_CHANNEL): cv.positive_int, + } +) -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Harmony platform.""" activity = None @@ -57,9 +68,14 @@ async def async_setup_platform( if discovery_info: # Find the discovered device in the list of user configurations - override = next((c for c in hass.data[CONF_DEVICE_CACHE] - if c.get(CONF_NAME) == discovery_info.get(CONF_NAME)), - None) + override = next( + ( + c + for c in hass.data[CONF_DEVICE_CACHE] + if c.get(CONF_NAME) == discovery_info.get(CONF_NAME) + ), + None, + ) port = DEFAULT_PORT delay_secs = DEFAULT_DELAY_SECS @@ -68,21 +84,14 @@ async def async_setup_platform( delay_secs = override.get(ATTR_DELAY_SECS) port = override.get(CONF_PORT, DEFAULT_PORT) - host = ( - discovery_info.get(CONF_NAME), - discovery_info.get(CONF_HOST), - port) + host = (discovery_info.get(CONF_NAME), discovery_info.get(CONF_HOST), port) # Ignore hub name when checking if this hub is known - ip and port only if host[1:] in ((h.host, h.port) for h in DEVICES): _LOGGER.debug("Discovered host already known: %s", host) return elif CONF_HOST in config: - host = ( - config.get(CONF_NAME), - config.get(CONF_HOST), - config.get(CONF_PORT), - ) + host = (config.get(CONF_NAME), config.get(CONF_HOST), config.get(CONF_PORT)) activity = config.get(ATTR_ACTIVITY) delay_secs = config.get(ATTR_DELAY_SECS) else: @@ -90,14 +99,21 @@ async def async_setup_platform( return name, address, port = host - _LOGGER.info("Loading Harmony Platform: %s at %s:%s, startup activity: %s", - name, address, port, activity) + _LOGGER.info( + "Loading Harmony Platform: %s at %s:%s, startup activity: %s", + name, + address, + port, + activity, + ) harmony_conf_file = hass.config.path( - '{}{}{}'.format('harmony_', slugify(name), '.conf')) + "{}{}{}".format("harmony_", slugify(name), ".conf") + ) try: device = HarmonyRemote( - name, address, port, activity, harmony_conf_file, delay_secs) + name, address, port, activity, harmony_conf_file, delay_secs + ) if not await device.connect(): raise PlatformNotReady @@ -111,21 +127,23 @@ async def async_setup_platform( def register_services(hass): """Register all services for harmony devices.""" hass.services.async_register( - DOMAIN, SERVICE_SYNC, _sync_service, - schema=HARMONY_SYNC_SCHEMA) + DOMAIN, SERVICE_SYNC, _sync_service, schema=HARMONY_SYNC_SCHEMA + ) hass.services.async_register( - DOMAIN, SERVICE_CHANGE_CHANNEL, _change_channel_service, - schema=HARMONY_CHANGE_CHANNEL_SCHEMA) + DOMAIN, + SERVICE_CHANGE_CHANNEL, + _change_channel_service, + schema=HARMONY_CHANGE_CHANNEL_SCHEMA, + ) async def _apply_service(service, service_func, *service_func_args): """Handle services to apply.""" - entity_ids = service.data.get('entity_id') + entity_ids = service.data.get("entity_id") if entity_ids: - _devices = [device for device in DEVICES - if device.entity_id in entity_ids] + _devices = [device for device in DEVICES if device.entity_id in entity_ids] else: _devices = DEVICES @@ -170,7 +188,7 @@ class HarmonyRemote(remote.RemoteDevice): new_activity=self.new_activity, config_updated=self.new_config, connect=self.got_connected, - disconnect=self.got_disconnected + disconnect=self.got_disconnected, ) # Store Harmony HUB config, this will also update our current @@ -207,7 +225,7 @@ class HarmonyRemote(remote.RemoteDevice): @property def is_on(self): """Return False if PowerOff is the current activity, otherwise True.""" - return self._current_activity not in [None, 'PowerOff'] + return self._current_activity not in [None, "PowerOff"] @property def available(self): @@ -233,8 +251,7 @@ class HarmonyRemote(remote.RemoteDevice): def new_activity(self, activity_info: tuple) -> None: """Call for updating the current activity.""" activity_id, activity_name = activity_info - _LOGGER.debug("%s: activity reported as: %s", self._name, - activity_name) + _LOGGER.debug("%s: activity reported as: %s", self._name, activity_name) self._current_activity = activity_name self._state = bool(activity_id != -1) self._available = True @@ -275,34 +292,30 @@ class HarmonyRemote(remote.RemoteDevice): if activity: activity_id = None - if activity.isdigit() or activity == '-1': + if activity.isdigit() or activity == "-1": _LOGGER.debug("%s: Activity is numeric", self.name) if self._client.get_activity_name(int(activity)): activity_id = activity if activity_id is None: _LOGGER.debug("%s: Find activity ID based on name", self.name) - activity_id = self._client.get_activity_id( - str(activity).strip()) + activity_id = self._client.get_activity_id(str(activity).strip()) if activity_id is None: - _LOGGER.error("%s: Activity %s is invalid", - self.name, activity) + _LOGGER.error("%s: Activity %s is invalid", self.name, activity) return try: await self._client.start_activity(activity_id) except aioexc.TimeOut: - _LOGGER.error("%s: Starting activity %s timed-out", - self.name, - activity) + _LOGGER.error("%s: Starting activity %s timed-out", self.name, activity) else: - _LOGGER.error("%s: No activity specified with turn_on service", - self.name) + _LOGGER.error("%s: No activity specified with turn_on service", self.name) async def async_turn_off(self, **kwargs): """Start the PowerOff activity.""" import aioharmony.exceptions as aioexc + _LOGGER.debug("%s: Turn Off", self.name) try: await self._client.power_off() @@ -323,14 +336,14 @@ class HarmonyRemote(remote.RemoteDevice): device_id = None if device.isdigit(): - _LOGGER.debug("%s: Device %s is numeric", - self.name, device) + _LOGGER.debug("%s: Device %s is numeric", self.name, device) if self._client.get_device_name(int(device)): device_id = device if device_id is None: - _LOGGER.debug("%s: Find device ID %s based on device name", - self.name, device) + _LOGGER.debug( + "%s: Find device ID %s based on device name", self.name, device + ) device_id = self._client.get_device_id(str(device).strip()) if device_id is None: @@ -340,18 +353,20 @@ class HarmonyRemote(remote.RemoteDevice): num_repeats = kwargs[ATTR_NUM_REPEATS] delay_secs = kwargs.get(ATTR_DELAY_SECS, self._delay_secs) hold_secs = kwargs[ATTR_HOLD_SECS] - _LOGGER.debug("Sending commands to device %s holding for %s seconds " - "with a delay of %s seconds", - device, hold_secs, delay_secs) + _LOGGER.debug( + "Sending commands to device %s holding for %s seconds " + "with a delay of %s seconds", + device, + hold_secs, + delay_secs, + ) # Creating list of commands to send. snd_cmnd_list = [] for _ in range(num_repeats): for single_command in command: send_command = SendCommandDevice( - device=device_id, - command=single_command, - delay=hold_secs + device=device_id, command=single_command, delay=hold_secs ) snd_cmnd_list.append(send_command) if delay_secs > 0: @@ -365,26 +380,23 @@ class HarmonyRemote(remote.RemoteDevice): return for result in result_list: - _LOGGER.error("Sending command %s to device %s failed with code " - "%s: %s", - result.command.command, - result.command.device, - result.code, - result.msg - ) + _LOGGER.error( + "Sending command %s to device %s failed with code " "%s: %s", + result.command.command, + result.command.device, + result.code, + result.msg, + ) async def change_channel(self, channel): """Change the channel using Harmony remote.""" import aioharmony.exceptions as aioexc - _LOGGER.debug("%s: Changing channel to %s", - self.name, channel) + _LOGGER.debug("%s: Changing channel to %s", self.name, channel) try: await self._client.change_channel(channel) except aioexc.TimeOut: - _LOGGER.error("%s: Changing channel to %s timed-out", - self.name, - channel) + _LOGGER.error("%s: Changing channel to %s timed-out", self.name, channel) async def sync(self): """Sync the Harmony device with the web service.""" @@ -394,25 +406,26 @@ class HarmonyRemote(remote.RemoteDevice): try: await self._client.sync() except aioexc.TimeOut: - _LOGGER.error("%s: Syncing hub with Harmony cloud timed-out", - self.name) + _LOGGER.error("%s: Syncing hub with Harmony cloud timed-out", self.name) else: await self.hass.async_add_executor_job(self.write_config_file) def write_config_file(self): """Write Harmony configuration file.""" - _LOGGER.debug("%s: Writing hub config to file: %s", - self.name, - self._config_path) + _LOGGER.debug( + "%s: Writing hub config to file: %s", self.name, self._config_path + ) if self._client.config is None: - _LOGGER.warning("%s: No configuration received from hub", - self.name) + _LOGGER.warning("%s: No configuration received from hub", self.name) return try: - with open(self._config_path, 'w+', encoding='utf-8') as file_out: - json.dump(self._client.json_config, file_out, - sort_keys=True, indent=4) + with open(self._config_path, "w+", encoding="utf-8") as file_out: + json.dump(self._client.json_config, file_out, sort_keys=True, indent=4) except IOError as exc: - _LOGGER.error("%s: Unable to write HUB configuration to %s: %s", - self.name, self._config_path, exc) + _LOGGER.error( + "%s: Unable to write HUB configuration to %s: %s", + self.name, + self._config_path, + exc, + ) diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index 7e8afdc5312..801c20b5c2b 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -9,8 +9,11 @@ from homeassistant.auth.const import GROUP_ID_ADMIN from homeassistant.components.homeassistant import SERVICE_CHECK_CONFIG import homeassistant.config as conf_util from homeassistant.const import ( - ATTR_NAME, SERVICE_HOMEASSISTANT_RESTART, SERVICE_HOMEASSISTANT_STOP, - EVENT_CORE_CONFIG_UPDATE) + ATTR_NAME, + SERVICE_HOMEASSISTANT_RESTART, + SERVICE_HOMEASSISTANT_STOP, + EVENT_CORE_CONFIG_UPDATE, +) from homeassistant.core import DOMAIN as HASS_DOMAIN, callback from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv @@ -26,90 +29,97 @@ from .ingress import async_setup_ingress_view _LOGGER = logging.getLogger(__name__) -DOMAIN = 'hassio' +DOMAIN = "hassio" STORAGE_KEY = DOMAIN STORAGE_VERSION = 1 -CONF_FRONTEND_REPO = 'development_repo' +CONF_FRONTEND_REPO = "development_repo" -CONFIG_SCHEMA = vol.Schema({ - vol.Optional(DOMAIN): vol.Schema({ - vol.Optional(CONF_FRONTEND_REPO): cv.isdir, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + {vol.Optional(DOMAIN): vol.Schema({vol.Optional(CONF_FRONTEND_REPO): cv.isdir})}, + extra=vol.ALLOW_EXTRA, +) -DATA_HOMEASSISTANT_VERSION = 'hassio_hass_version' +DATA_HOMEASSISTANT_VERSION = "hassio_hass_version" HASSIO_UPDATE_INTERVAL = timedelta(minutes=55) -SERVICE_ADDON_START = 'addon_start' -SERVICE_ADDON_STOP = 'addon_stop' -SERVICE_ADDON_RESTART = 'addon_restart' -SERVICE_ADDON_STDIN = 'addon_stdin' -SERVICE_HOST_SHUTDOWN = 'host_shutdown' -SERVICE_HOST_REBOOT = 'host_reboot' -SERVICE_SNAPSHOT_FULL = 'snapshot_full' -SERVICE_SNAPSHOT_PARTIAL = 'snapshot_partial' -SERVICE_RESTORE_FULL = 'restore_full' -SERVICE_RESTORE_PARTIAL = 'restore_partial' +SERVICE_ADDON_START = "addon_start" +SERVICE_ADDON_STOP = "addon_stop" +SERVICE_ADDON_RESTART = "addon_restart" +SERVICE_ADDON_STDIN = "addon_stdin" +SERVICE_HOST_SHUTDOWN = "host_shutdown" +SERVICE_HOST_REBOOT = "host_reboot" +SERVICE_SNAPSHOT_FULL = "snapshot_full" +SERVICE_SNAPSHOT_PARTIAL = "snapshot_partial" +SERVICE_RESTORE_FULL = "restore_full" +SERVICE_RESTORE_PARTIAL = "restore_partial" -ATTR_ADDON = 'addon' -ATTR_INPUT = 'input' -ATTR_SNAPSHOT = 'snapshot' -ATTR_ADDONS = 'addons' -ATTR_FOLDERS = 'folders' -ATTR_HOMEASSISTANT = 'homeassistant' -ATTR_PASSWORD = 'password' +ATTR_ADDON = "addon" +ATTR_INPUT = "input" +ATTR_SNAPSHOT = "snapshot" +ATTR_ADDONS = "addons" +ATTR_FOLDERS = "folders" +ATTR_HOMEASSISTANT = "homeassistant" +ATTR_PASSWORD = "password" SCHEMA_NO_DATA = vol.Schema({}) -SCHEMA_ADDON = vol.Schema({ - vol.Required(ATTR_ADDON): cv.slug, -}) +SCHEMA_ADDON = vol.Schema({vol.Required(ATTR_ADDON): cv.slug}) -SCHEMA_ADDON_STDIN = SCHEMA_ADDON.extend({ - vol.Required(ATTR_INPUT): vol.Any(dict, cv.string) -}) +SCHEMA_ADDON_STDIN = SCHEMA_ADDON.extend( + {vol.Required(ATTR_INPUT): vol.Any(dict, cv.string)} +) -SCHEMA_SNAPSHOT_FULL = vol.Schema({ - vol.Optional(ATTR_NAME): cv.string, - vol.Optional(ATTR_PASSWORD): cv.string, -}) +SCHEMA_SNAPSHOT_FULL = vol.Schema( + {vol.Optional(ATTR_NAME): cv.string, vol.Optional(ATTR_PASSWORD): cv.string} +) -SCHEMA_SNAPSHOT_PARTIAL = SCHEMA_SNAPSHOT_FULL.extend({ - vol.Optional(ATTR_FOLDERS): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(ATTR_ADDONS): vol.All(cv.ensure_list, [cv.string]), -}) +SCHEMA_SNAPSHOT_PARTIAL = SCHEMA_SNAPSHOT_FULL.extend( + { + vol.Optional(ATTR_FOLDERS): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(ATTR_ADDONS): vol.All(cv.ensure_list, [cv.string]), + } +) -SCHEMA_RESTORE_FULL = vol.Schema({ - vol.Required(ATTR_SNAPSHOT): cv.slug, - vol.Optional(ATTR_PASSWORD): cv.string, -}) +SCHEMA_RESTORE_FULL = vol.Schema( + {vol.Required(ATTR_SNAPSHOT): cv.slug, vol.Optional(ATTR_PASSWORD): cv.string} +) -SCHEMA_RESTORE_PARTIAL = SCHEMA_RESTORE_FULL.extend({ - vol.Optional(ATTR_HOMEASSISTANT): cv.boolean, - vol.Optional(ATTR_FOLDERS): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(ATTR_ADDONS): vol.All(cv.ensure_list, [cv.string]), -}) +SCHEMA_RESTORE_PARTIAL = SCHEMA_RESTORE_FULL.extend( + { + vol.Optional(ATTR_HOMEASSISTANT): cv.boolean, + vol.Optional(ATTR_FOLDERS): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(ATTR_ADDONS): vol.All(cv.ensure_list, [cv.string]), + } +) MAP_SERVICE_API = { - SERVICE_ADDON_START: ('/addons/{addon}/start', SCHEMA_ADDON, 60, False), - SERVICE_ADDON_STOP: ('/addons/{addon}/stop', SCHEMA_ADDON, 60, False), - SERVICE_ADDON_RESTART: - ('/addons/{addon}/restart', SCHEMA_ADDON, 60, False), - SERVICE_ADDON_STDIN: - ('/addons/{addon}/stdin', SCHEMA_ADDON_STDIN, 60, False), - SERVICE_HOST_SHUTDOWN: ('/host/shutdown', SCHEMA_NO_DATA, 60, False), - SERVICE_HOST_REBOOT: ('/host/reboot', SCHEMA_NO_DATA, 60, False), - SERVICE_SNAPSHOT_FULL: - ('/snapshots/new/full', SCHEMA_SNAPSHOT_FULL, 300, True), - SERVICE_SNAPSHOT_PARTIAL: - ('/snapshots/new/partial', SCHEMA_SNAPSHOT_PARTIAL, 300, True), - SERVICE_RESTORE_FULL: - ('/snapshots/{snapshot}/restore/full', SCHEMA_RESTORE_FULL, 300, True), - SERVICE_RESTORE_PARTIAL: - ('/snapshots/{snapshot}/restore/partial', SCHEMA_RESTORE_PARTIAL, 300, - True), + SERVICE_ADDON_START: ("/addons/{addon}/start", SCHEMA_ADDON, 60, False), + SERVICE_ADDON_STOP: ("/addons/{addon}/stop", SCHEMA_ADDON, 60, False), + SERVICE_ADDON_RESTART: ("/addons/{addon}/restart", SCHEMA_ADDON, 60, False), + SERVICE_ADDON_STDIN: ("/addons/{addon}/stdin", SCHEMA_ADDON_STDIN, 60, False), + SERVICE_HOST_SHUTDOWN: ("/host/shutdown", SCHEMA_NO_DATA, 60, False), + SERVICE_HOST_REBOOT: ("/host/reboot", SCHEMA_NO_DATA, 60, False), + SERVICE_SNAPSHOT_FULL: ("/snapshots/new/full", SCHEMA_SNAPSHOT_FULL, 300, True), + SERVICE_SNAPSHOT_PARTIAL: ( + "/snapshots/new/partial", + SCHEMA_SNAPSHOT_PARTIAL, + 300, + True, + ), + SERVICE_RESTORE_FULL: ( + "/snapshots/{snapshot}/restore/full", + SCHEMA_RESTORE_FULL, + 300, + True, + ), + SERVICE_RESTORE_PARTIAL: ( + "/snapshots/{snapshot}/restore/partial", + SCHEMA_RESTORE_PARTIAL, + 300, + True, + ), } @@ -136,13 +146,13 @@ def is_hassio(hass): async def async_setup(hass, config): """Set up the Hass.io component.""" # Check local setup - for env in ('HASSIO', 'HASSIO_TOKEN'): + for env in ("HASSIO", "HASSIO_TOKEN"): if os.environ.get(env): continue _LOGGER.error("Missing %s environment variable.", env) return False - host = os.environ['HASSIO'] + host = os.environ["HASSIO"] websession = hass.helpers.aiohttp_client.async_get_clientsession() hass.data[DOMAIN] = hassio = HassIO(hass.loop, websession, host) @@ -156,44 +166,42 @@ async def async_setup(hass, config): data = {} refresh_token = None - if 'hassio_user' in data: - user = await hass.auth.async_get_user(data['hassio_user']) + if "hassio_user" in data: + user = await hass.auth.async_get_user(data["hassio_user"]) if user and user.refresh_tokens: refresh_token = list(user.refresh_tokens.values())[0] # Migrate old hass.io users to be admin. if not user.is_admin: - await hass.auth.async_update_user( - user, group_ids=[GROUP_ID_ADMIN]) + await hass.auth.async_update_user(user, group_ids=[GROUP_ID_ADMIN]) if refresh_token is None: - user = await hass.auth.async_create_system_user( - 'Hass.io', [GROUP_ID_ADMIN]) + user = await hass.auth.async_create_system_user("Hass.io", [GROUP_ID_ADMIN]) refresh_token = await hass.auth.async_create_refresh_token(user) - data['hassio_user'] = user.id + data["hassio_user"] = user.id await store.async_save(data) # This overrides the normal API call that would be forwarded development_repo = config.get(DOMAIN, {}).get(CONF_FRONTEND_REPO) if development_repo is not None: hass.http.register_static_path( - '/api/hassio/app', - os.path.join(development_repo, 'hassio/build'), False) + "/api/hassio/app", os.path.join(development_repo, "hassio/build"), False + ) hass.http.register_view(HassIOView(host, websession)) - if 'frontend' in hass.config.components: + if "frontend" in hass.config.components: await hass.components.panel_custom.async_register_panel( - frontend_url_path='hassio', - webcomponent_name='hassio-main', - sidebar_title='Hass.io', - sidebar_icon='hass:home-assistant', - js_url='/api/hassio/app/entrypoint.js', + frontend_url_path="hassio", + webcomponent_name="hassio-main", + sidebar_title="Hass.io", + sidebar_icon="hass:home-assistant", + js_url="/api/hassio/app/entrypoint.js", embed_iframe=True, require_admin=True, ) - await hassio.update_hass_api(config.get('http', {}), refresh_token.token) + await hassio.update_hass_api(config.get("http", {}), refresh_token.token) async def push_config(_): """Push core config to Hass.io.""" @@ -221,25 +229,28 @@ async def async_setup(hass, config): try: await hassio.send_command( api_command.format(addon=addon, snapshot=snapshot), - payload=payload, timeout=MAP_SERVICE_API[service.service][2] + payload=payload, + timeout=MAP_SERVICE_API[service.service][2], ) except HassioAPIError as err: _LOGGER.error("Error on Hass.io API: %s", err) for service, settings in MAP_SERVICE_API.items(): hass.services.async_register( - DOMAIN, service, async_service_handler, schema=settings[1]) + DOMAIN, service, async_service_handler, schema=settings[1] + ) async def update_homeassistant_version(now): """Update last available Home Assistant version.""" try: data = await hassio.get_homeassistant_info() - hass.data[DATA_HOMEASSISTANT_VERSION] = data['last_version'] + hass.data[DATA_HOMEASSISTANT_VERSION] = data["last_version"] except HassioAPIError as err: _LOGGER.warning("Can't read last version: %s", err) hass.helpers.event.async_track_point_in_utc_time( - update_homeassistant_version, utcnow() + HASSIO_UPDATE_INTERVAL) + update_homeassistant_version, utcnow() + HASSIO_UPDATE_INTERVAL + ) # Fetch last version await update_homeassistant_version(None) @@ -259,17 +270,21 @@ async def async_setup(hass, config): _LOGGER.error(errors) hass.components.persistent_notification.async_create( "Config error. See dev-info panel for details.", - "Config validating", "{0}.check_config".format(HASS_DOMAIN)) + "Config validating", + "{0}.check_config".format(HASS_DOMAIN), + ) return if call.service == SERVICE_HOMEASSISTANT_RESTART: await hassio.restart_homeassistant() # Mock core services - for service in (SERVICE_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_RESTART, - SERVICE_CHECK_CONFIG): - hass.services.async_register( - HASS_DOMAIN, service, async_handle_core_service) + for service in ( + SERVICE_HOMEASSISTANT_STOP, + SERVICE_HOMEASSISTANT_RESTART, + SERVICE_CHECK_CONFIG, + ): + hass.services.async_register(HASS_DOMAIN, service, async_handle_core_service) # Init discovery Hass.io feature async_setup_discovery_view(hass, hassio) diff --git a/homeassistant/components/hassio/addon_panel.py b/homeassistant/components/hassio/addon_panel.py index e85c8f12247..b60c864a893 100644 --- a/homeassistant/components/hassio/addon_panel.py +++ b/homeassistant/components/hassio/addon_panel.py @@ -81,13 +81,11 @@ def _register_panel(hass, addon, data): """ return hass.components.panel_custom.async_register_panel( frontend_url_path=addon, - webcomponent_name='hassio-main', + webcomponent_name="hassio-main", sidebar_title=data[ATTR_TITLE], sidebar_icon=data[ATTR_ICON], - js_url='/api/hassio/app/entrypoint.js', + js_url="/api/hassio/app/entrypoint.js", embed_iframe=True, require_admin=data[ATTR_ADMIN], - config={ - "ingress": addon - } + config={"ingress": addon}, ) diff --git a/homeassistant/components/hassio/auth.py b/homeassistant/components/hassio/auth.py index 85ae6473562..19e4c63b5a5 100644 --- a/homeassistant/components/hassio/auth.py +++ b/homeassistant/components/hassio/auth.py @@ -20,11 +20,14 @@ from .const import ATTR_ADDON, ATTR_PASSWORD, ATTR_USERNAME _LOGGER = logging.getLogger(__name__) -SCHEMA_API_AUTH = vol.Schema({ - vol.Required(ATTR_USERNAME): cv.string, - vol.Required(ATTR_PASSWORD): cv.string, - vol.Required(ATTR_ADDON): cv.string, -}, extra=vol.ALLOW_EXTRA) +SCHEMA_API_AUTH = vol.Schema( + { + vol.Required(ATTR_USERNAME): cv.string, + vol.Required(ATTR_PASSWORD): cv.string, + vol.Required(ATTR_ADDON): cv.string, + }, + extra=vol.ALLOW_EXTRA, +) @callback @@ -47,10 +50,9 @@ class HassIOAuth(HomeAssistantView): @RequestDataValidator(SCHEMA_API_AUTH) async def post(self, request, data): """Handle new discovery requests.""" - hassio_ip = os.environ['HASSIO'].split(':')[0] + hassio_ip = os.environ["HASSIO"].split(":")[0] if request[KEY_REAL_IP] != ip_address(hassio_ip): - _LOGGER.error( - "Invalid auth request from %s", request[KEY_REAL_IP]) + _LOGGER.error("Invalid auth request from %s", request[KEY_REAL_IP]) raise HTTPForbidden() await self._check_login(data[ATTR_USERNAME], data[ATTR_PASSWORD]) @@ -58,7 +60,7 @@ class HassIOAuth(HomeAssistantView): def _get_provider(self): """Return Homeassistant auth provider.""" - prv = self.hass.auth.get_auth_provider('homeassistant', None) + prv = self.hass.auth.get_auth_provider("homeassistant", None) if prv is not None: return prv diff --git a/homeassistant/components/hassio/const.py b/homeassistant/components/hassio/const.py index 9656346cd2c..ffccb325395 100644 --- a/homeassistant/components/hassio/const.py +++ b/homeassistant/components/hassio/const.py @@ -1,21 +1,21 @@ """Hass.io const variables.""" -ATTR_ADDONS = 'addons' -ATTR_DISCOVERY = 'discovery' -ATTR_ADDON = 'addon' -ATTR_NAME = 'name' -ATTR_SERVICE = 'service' -ATTR_CONFIG = 'config' -ATTR_UUID = 'uuid' -ATTR_USERNAME = 'username' -ATTR_PASSWORD = 'password' -ATTR_PANELS = 'panels' -ATTR_ENABLE = 'enable' -ATTR_TITLE = 'title' -ATTR_ICON = 'icon' -ATTR_ADMIN = 'admin' +ATTR_ADDONS = "addons" +ATTR_DISCOVERY = "discovery" +ATTR_ADDON = "addon" +ATTR_NAME = "name" +ATTR_SERVICE = "service" +ATTR_CONFIG = "config" +ATTR_UUID = "uuid" +ATTR_USERNAME = "username" +ATTR_PASSWORD = "password" +ATTR_PANELS = "panels" +ATTR_ENABLE = "enable" +ATTR_TITLE = "title" +ATTR_ICON = "icon" +ATTR_ADMIN = "admin" -X_HASSIO = 'X-Hassio-Key' +X_HASSIO = "X-Hassio-Key" X_INGRESS_PATH = "X-Ingress-Path" -X_HASS_USER_ID = 'X-Hass-User-ID' -X_HASS_IS_ADMIN = 'X-Hass-Is-Admin' +X_HASS_USER_ID = "X-Hass-User-ID" +X_HASS_IS_ADMIN = "X-Hass-Is-Admin" diff --git a/homeassistant/components/hassio/discovery.py b/homeassistant/components/hassio/discovery.py index 90953d634c3..55336735133 100644 --- a/homeassistant/components/hassio/discovery.py +++ b/homeassistant/components/hassio/discovery.py @@ -10,8 +10,13 @@ from homeassistant.core import callback from homeassistant.components.http import HomeAssistantView from .const import ( - ATTR_ADDON, ATTR_CONFIG, ATTR_DISCOVERY, ATTR_NAME, ATTR_SERVICE, - ATTR_UUID) + ATTR_ADDON, + ATTR_CONFIG, + ATTR_DISCOVERY, + ATTR_NAME, + ATTR_SERVICE, + ATTR_UUID, +) from .handler import HassioAPIError _LOGGER = logging.getLogger(__name__) @@ -32,13 +37,16 @@ def async_setup_discovery_view(hass: HomeAssistantView, hassio): _LOGGER.error("Can't read discover info: %s", err) return - jobs = [hassio_discovery.async_process_new(discovery) - for discovery in data[ATTR_DISCOVERY]] + jobs = [ + hassio_discovery.async_process_new(discovery) + for discovery in data[ATTR_DISCOVERY] + ] if jobs: await asyncio.wait(jobs) hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_START, _async_discovery_start_handler) + EVENT_HOMEASSISTANT_START, _async_discovery_start_handler + ) class HassIODiscovery(HomeAssistantView): @@ -86,7 +94,8 @@ class HassIODiscovery(HomeAssistantView): # Use config flow await self.hass.config_entries.flow.async_init( - service, context={'source': 'hassio'}, data=config_data) + service, context={"source": "hassio"}, data=config_data + ) async def async_process_del(self, data): """Process remove discovery entry.""" @@ -104,6 +113,6 @@ class HassIODiscovery(HomeAssistantView): # Use config flow for entry in self.hass.config_entries.async_entries(service): - if entry.source != 'hassio': + if entry.source != "hassio": continue await self.hass.config_entries.async_remove(entry) diff --git a/homeassistant/components/hassio/handler.py b/homeassistant/components/hassio/handler.py index 5e7932acbae..10f21556fb3 100644 --- a/homeassistant/components/hassio/handler.py +++ b/homeassistant/components/hassio/handler.py @@ -24,11 +24,12 @@ class HassioAPIError(RuntimeError): def _api_bool(funct): """Return a boolean.""" + async def _wrapper(*argv, **kwargs): """Wrap function.""" try: data = await funct(*argv, **kwargs) - return data['result'] == "ok" + return data["result"] == "ok" except HassioAPIError: return False @@ -37,12 +38,13 @@ def _api_bool(funct): def _api_data(funct): """Return data of an api.""" + async def _wrapper(*argv, **kwargs): """Wrap function.""" data = await funct(*argv, **kwargs) - if data['result'] == "ok": - return data['data'] - raise HassioAPIError(data['message']) + if data["result"] == "ok": + return data["data"] + raise HassioAPIError(data["message"]) return _wrapper @@ -78,8 +80,7 @@ class HassIO: This method return a coroutine. """ - return self.send_command( - "/addons/{}/info".format(addon), method="get") + return self.send_command("/addons/{}/info".format(addon), method="get") @_api_data def get_ingress_panels(self): @@ -126,18 +127,17 @@ class HassIO: """Update Home Assistant API data on Hass.io.""" port = http_config.get(CONF_SERVER_PORT) or SERVER_PORT options = { - 'ssl': CONF_SSL_CERTIFICATE in http_config, - 'port': port, - 'watchdog': True, - 'refresh_token': refresh_token, + "ssl": CONF_SSL_CERTIFICATE in http_config, + "port": port, + "watchdog": True, + "refresh_token": refresh_token, } if CONF_SERVER_HOST in http_config: - options['watchdog'] = False + options["watchdog"] = False _LOGGER.warning("Don't use 'server_host' options with Hass.io") - return await self.send_command("/homeassistant/options", - payload=options) + return await self.send_command("/homeassistant/options", payload=options) @_api_bool def update_hass_timezone(self, timezone): @@ -145,12 +145,9 @@ class HassIO: This method return a coroutine. """ - return self.send_command("/supervisor/options", payload={ - 'timezone': timezone - }) + return self.send_command("/supervisor/options", payload={"timezone": timezone}) - async def send_command(self, command, method="post", payload=None, - timeout=10): + async def send_command(self, command, method="post", payload=None, timeout=10): """Send API command to Hass.io. This method is a coroutine. @@ -158,14 +155,14 @@ class HassIO: try: with async_timeout.timeout(timeout): request = await self.websession.request( - method, "http://{}{}".format(self._ip, command), - json=payload, headers={ - X_HASSIO: os.environ.get('HASSIO_TOKEN', "") - }) + method, + "http://{}{}".format(self._ip, command), + json=payload, + headers={X_HASSIO: os.environ.get("HASSIO_TOKEN", "")}, + ) if request.status not in (200, 400): - _LOGGER.error( - "%s return code %d.", command, request.status) + _LOGGER.error("%s return code %d.", command, request.status) raise HassioAPIError() answer = await request.json() diff --git a/homeassistant/components/hassio/http.py b/homeassistant/components/hassio/http.py index a9c5deda9f9..f42aaca4438 100644 --- a/homeassistant/components/hassio/http.py +++ b/homeassistant/components/hassio/http.py @@ -19,24 +19,19 @@ _LOGGER = logging.getLogger(__name__) NO_TIMEOUT = re.compile( - r'^(?:' - r'|homeassistant/update' - r'|hassos/update' - r'|hassos/update/cli' - r'|supervisor/update' - r'|addons/[^/]+/(?:update|install|rebuild)' - r'|snapshots/.+/full' - r'|snapshots/.+/partial' - r'|snapshots/[^/]+/(?:upload|download)' - r')$' + r"^(?:" + r"|homeassistant/update" + r"|hassos/update" + r"|hassos/update/cli" + r"|supervisor/update" + r"|addons/[^/]+/(?:update|install|rebuild)" + r"|snapshots/.+/full" + r"|snapshots/.+/partial" + r"|snapshots/[^/]+/(?:upload|download)" + r")$" ) -NO_AUTH = re.compile( - r'^(?:' - r'|app/.*' - r'|addons/[^/]+/logo' - r')$' -) +NO_AUTH = re.compile(r"^(?:" r"|app/.*" r"|addons/[^/]+/logo" r")$") class HassIOView(HomeAssistantView): @@ -52,7 +47,7 @@ class HassIOView(HomeAssistantView): self._websession = websession async def _handle( - self, request: web.Request, path: str + self, request: web.Request, path: str ) -> Union[web.Response, web.StreamResponse]: """Route data to Hass.io.""" if _need_auth(path) and not request[KEY_AUTHENTICATED]: @@ -64,7 +59,7 @@ class HassIOView(HomeAssistantView): post = _handle async def _command_proxy( - self, path: str, request: web.Request + self, path: str, request: web.Request ) -> Union[web.Response, web.StreamResponse]: """Return a client request with proxy origin for Hass.io supervisor. @@ -80,8 +75,10 @@ class HassIOView(HomeAssistantView): method = getattr(self._websession, request.method.lower()) client = await method( - "http://{}/{}".format(self._host, path), data=data, - headers=headers, timeout=read_timeout + "http://{}/{}".format(self._host, path), + data=data, + headers=headers, + timeout=read_timeout, ) # Simple request @@ -89,9 +86,7 @@ class HassIOView(HomeAssistantView): # Return Response body = await client.read() return web.Response( - content_type=client.content_type, - status=client.status, - body=body, + content_type=client.content_type, status=client.status, body=body ) # Stream response @@ -116,15 +111,15 @@ class HassIOView(HomeAssistantView): def _init_header(request: web.Request) -> Dict[str, str]: """Create initial header.""" headers = { - X_HASSIO: os.environ.get('HASSIO_TOKEN', ""), + X_HASSIO: os.environ.get("HASSIO_TOKEN", ""), CONTENT_TYPE: request.content_type, } # Add user data - user = request.get('hass_user') + user = request.get("hass_user") if user is not None: - headers[X_HASS_USER_ID] = request['hass_user'].id - headers[X_HASS_IS_ADMIN] = str(int(request['hass_user'].is_admin)) + headers[X_HASS_USER_ID] = request["hass_user"].id + headers[X_HASS_IS_ADMIN] = str(int(request["hass_user"].is_admin)) return headers diff --git a/homeassistant/components/hassio/ingress.py b/homeassistant/components/hassio/ingress.py index 250d50681dc..84e2b096362 100644 --- a/homeassistant/components/hassio/ingress.py +++ b/homeassistant/components/hassio/ingress.py @@ -45,7 +45,7 @@ class HassIOIngress(HomeAssistantView): return "http://{}/ingress/{}/{}".format(self._host, token, path) async def _handle( - self, request: web.Request, token: str, path: str + self, request: web.Request, token: str, path: str ) -> Union[web.Response, web.StreamResponse, web.WebSocketResponse]: """Route data to Hass.io ingress service.""" try: @@ -69,14 +69,13 @@ class HassIOIngress(HomeAssistantView): options = _handle async def _handle_websocket( - self, request: web.Request, token: str, path: str + self, request: web.Request, token: str, path: str ) -> web.WebSocketResponse: """Ingress route for websocket.""" if hdrs.SEC_WEBSOCKET_PROTOCOL in request.headers: req_protocols = [ str(proto.strip()) - for proto in - request.headers[hdrs.SEC_WEBSOCKET_PROTOCOL].split(",") + for proto in request.headers[hdrs.SEC_WEBSOCKET_PROTOCOL].split(",") ] else: req_protocols = () @@ -96,8 +95,11 @@ class HassIOIngress(HomeAssistantView): # Start proxy async with self._websession.ws_connect( - url, headers=source_header, protocols=req_protocols, - autoclose=False, autoping=False, + url, + headers=source_header, + protocols=req_protocols, + autoclose=False, + autoping=False, ) as ws_client: # Proxy requests await asyncio.wait( @@ -105,13 +107,13 @@ class HassIOIngress(HomeAssistantView): _websocket_forward(ws_server, ws_client), _websocket_forward(ws_client, ws_server), ], - return_when=asyncio.FIRST_COMPLETED + return_when=asyncio.FIRST_COMPLETED, ) return ws_server async def _handle_request( - self, request: web.Request, token: str, path: str + self, request: web.Request, token: str, path: str ) -> Union[web.Response, web.StreamResponse]: """Ingress route for request.""" url = self._create_url(token, path) @@ -119,30 +121,31 @@ class HassIOIngress(HomeAssistantView): source_header = _init_header(request, token) async with self._websession.request( - request.method, - url, - headers=source_header, - params=request.query, - allow_redirects=False, - data=data + request.method, + url, + headers=source_header, + params=request.query, + allow_redirects=False, + data=data, ) as result: headers = _response_header(result) # Simple request - if hdrs.CONTENT_LENGTH in result.headers and \ - int(result.headers.get(hdrs.CONTENT_LENGTH, 0)) < 4194000: + if ( + hdrs.CONTENT_LENGTH in result.headers + and int(result.headers.get(hdrs.CONTENT_LENGTH, 0)) < 4194000 + ): # Return Response body = await result.read() return web.Response( headers=headers, status=result.status, content_type=result.content_type, - body=body + body=body, ) # Stream response - response = web.StreamResponse( - status=result.status, headers=headers) + response = web.StreamResponse(status=result.status, headers=headers) response.content_type = result.content_type try: @@ -157,7 +160,7 @@ class HassIOIngress(HomeAssistantView): def _init_header( - request: web.Request, token: str + request: web.Request, token: str ) -> Union[CIMultiDict, Dict[str, str]]: """Create initial header.""" headers = {} @@ -169,14 +172,14 @@ def _init_header( headers[name] = value # Inject token / cleanup later on Supervisor - headers[X_HASSIO] = os.environ.get('HASSIO_TOKEN', "") + headers[X_HASSIO] = os.environ.get("HASSIO_TOKEN", "") # Ingress information headers[X_INGRESS_PATH] = "/api/hassio_ingress/{}".format(token) # Set X-Forwarded-For forward_for = request.headers.get(hdrs.X_FORWARDED_FOR) - connected_ip = ip_address(request.transport.get_extra_info('peername')[0]) + connected_ip = ip_address(request.transport.get_extra_info("peername")[0]) if forward_for: forward_for = "{}, {!s}".format(forward_for, connected_ip) else: @@ -203,8 +206,12 @@ def _response_header(response: aiohttp.ClientResponse) -> Dict[str, str]: headers = {} for name, value in response.headers.items(): - if name in (hdrs.TRANSFER_ENCODING, hdrs.CONTENT_LENGTH, - hdrs.CONTENT_TYPE, hdrs.CONTENT_ENCODING): + if name in ( + hdrs.TRANSFER_ENCODING, + hdrs.CONTENT_LENGTH, + hdrs.CONTENT_TYPE, + hdrs.CONTENT_ENCODING, + ): continue headers[name] = value @@ -215,8 +222,10 @@ def _is_websocket(request: web.Request) -> bool: """Return True if request is a websocket.""" headers = request.headers - if "upgrade" in headers.get(hdrs.CONNECTION, "").lower() and \ - headers.get(hdrs.UPGRADE, "").lower() == "websocket": + if ( + "upgrade" in headers.get(hdrs.CONNECTION, "").lower() + and headers.get(hdrs.UPGRADE, "").lower() == "websocket" + ): return True return False diff --git a/homeassistant/components/haveibeenpwned/sensor.py b/homeassistant/components/haveibeenpwned/sensor.py index 72cc5ced3ef..d78756b9543 100644 --- a/homeassistant/components/haveibeenpwned/sensor.py +++ b/homeassistant/components/haveibeenpwned/sensor.py @@ -25,11 +25,11 @@ HA_USER_AGENT = "Home Assistant HaveIBeenPwned Sensor Component" MIN_TIME_BETWEEN_FORCED_UPDATES = timedelta(seconds=5) MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15) -URL = 'https://haveibeenpwned.com/api/v2/breachedaccount/' +URL = "https://haveibeenpwned.com/api/v2/breachedaccount/" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_EMAIL): vol.All(cv.ensure_list, [cv.string]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_EMAIL): vol.All(cv.ensure_list, [cv.string])} +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -77,11 +77,13 @@ class HaveIBeenPwnedSensor(Entity): return val for idx, value in enumerate(self._data.data[self._email]): - tmpname = "breach {}".format(idx+1) + tmpname = "breach {}".format(idx + 1) tmpvalue = "{} {}".format( value["Title"], - dt_util.as_local(dt_util.parse_datetime( - value["AddedDate"])).strftime(DATE_STR_FORMAT)) + dt_util.as_local(dt_util.parse_datetime(value["AddedDate"])).strftime( + DATE_STR_FORMAT + ), + ) val[tmpname] = tmpvalue return val @@ -103,8 +105,10 @@ class HaveIBeenPwnedSensor(Entity): # normal using update if self._email not in self._data.data: track_point_in_time( - self.hass, self.update_nothrottle, - dt_util.now() + MIN_TIME_BETWEEN_FORCED_UPDATES) + self.hass, + self.update_nothrottle, + dt_util.now() + MIN_TIME_BETWEEN_FORCED_UPDATES, + ) return self._state = len(self._data.data[self._email]) @@ -147,17 +151,20 @@ class HaveIBeenPwnedData: _LOGGER.debug("Checking for breaches for email: %s", self._email) req = requests.get( - url, headers={USER_AGENT: HA_USER_AGENT}, allow_redirects=True, - timeout=5) + url, + headers={USER_AGENT: HA_USER_AGENT}, + allow_redirects=True, + timeout=5, + ) except requests.exceptions.RequestException: _LOGGER.error("Failed fetching data for %s", self._email) return if req.status_code == 200: - self.data[self._email] = sorted(req.json(), - key=lambda k: k["AddedDate"], - reverse=True) + self.data[self._email] = sorted( + req.json(), key=lambda k: k["AddedDate"], reverse=True + ) # Only goto next email if we had data so that # the forced updates try this current email again @@ -171,6 +178,8 @@ class HaveIBeenPwnedData: self.set_next_email() else: - _LOGGER.error("Failed fetching data for %s" - "(HTTP Status_code = %d)", self._email, - req.status_code) + _LOGGER.error( + "Failed fetching data for %s" "(HTTP Status_code = %d)", + self._email, + req.status_code, + ) diff --git a/homeassistant/components/hddtemp/sensor.py b/homeassistant/components/hddtemp/sensor.py index 6d96f244f58..fa3b5fd256c 100644 --- a/homeassistant/components/hddtemp/sensor.py +++ b/homeassistant/components/hddtemp/sensor.py @@ -9,27 +9,35 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_NAME, CONF_HOST, CONF_PORT, TEMP_CELSIUS, TEMP_FAHRENHEIT, CONF_DISKS) + CONF_NAME, + CONF_HOST, + CONF_PORT, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, + CONF_DISKS, +) from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -ATTR_DEVICE = 'device' -ATTR_MODEL = 'model' +ATTR_DEVICE = "device" +ATTR_MODEL = "model" -DEFAULT_HOST = 'localhost' +DEFAULT_HOST = "localhost" DEFAULT_PORT = 7634 -DEFAULT_NAME = 'HD Temperature' +DEFAULT_NAME = "HD Temperature" DEFAULT_TIMEOUT = 5 SCAN_INTERVAL = timedelta(minutes=1) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_DISKS, default=[]): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_DISKS, default=[]): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -43,7 +51,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): hddtemp.update() if not disks: - disks = [next(iter(hddtemp.data)).split('|')[0]] + disks = [next(iter(hddtemp.data)).split("|")[0]] dev = [] for disk in disks: @@ -59,7 +67,7 @@ class HddTempSensor(Entity): """Initialize a HDDTemp sensor.""" self.hddtemp = hddtemp self.disk = disk - self._name = '{} {}'.format(name, disk) + self._name = "{} {}".format(name, disk) self._state = None self._details = None self._unit = None @@ -83,19 +91,16 @@ class HddTempSensor(Entity): def device_state_attributes(self): """Return the state attributes of the sensor.""" if self._details is not None: - return { - ATTR_DEVICE: self._details[0], - ATTR_MODEL: self._details[1], - } + return {ATTR_DEVICE: self._details[0], ATTR_MODEL: self._details[1]} def update(self): """Get the latest data from HDDTemp daemon and updates the state.""" self.hddtemp.update() if self.hddtemp.data and self.disk in self.hddtemp.data: - self._details = self.hddtemp.data[self.disk].split('|') + self._details = self.hddtemp.data[self.disk].split("|") self._state = self._details[2] - if self._details is not None and self._details[3] == 'F': + if self._details is not None and self._details[3] == "F": self._unit = TEMP_FAHRENHEIT else: self._unit = TEMP_CELSIUS @@ -115,15 +120,17 @@ class HddTempData: def update(self): """Get the latest data from HDDTemp running as daemon.""" try: - connection = Telnet( - host=self.host, port=self.port, timeout=DEFAULT_TIMEOUT) - data = connection.read_all().decode( - 'ascii').lstrip('|').rstrip('|').split('||') - self.data = {data[i].split('|')[0]: data[i] - for i in range(0, len(data), 1)} + connection = Telnet(host=self.host, port=self.port, timeout=DEFAULT_TIMEOUT) + data = ( + connection.read_all() + .decode("ascii") + .lstrip("|") + .rstrip("|") + .split("||") + ) + self.data = {data[i].split("|")[0]: data[i] for i in range(0, len(data), 1)} except ConnectionRefusedError: - _LOGGER.error("HDDTemp is not available at %s:%s", - self.host, self.port) + _LOGGER.error("HDDTemp is not available at %s:%s", self.host, self.port) self.data = None except socket.gaierror: _LOGGER.error("HDDTemp host not found %s:%s", self.host, self.port) diff --git a/homeassistant/components/hdmi_cec/__init__.py b/homeassistant/components/hdmi_cec/__init__.py index 189cc748d5d..969925182fd 100644 --- a/homeassistant/components/hdmi_cec/__init__.py +++ b/homeassistant/components/hdmi_cec/__init__.py @@ -9,27 +9,35 @@ import voluptuous as vol from homeassistant.components.media_player import DOMAIN as MEDIA_PLAYER from homeassistant.components.switch import DOMAIN as SWITCH from homeassistant.const import ( - CONF_DEVICES, CONF_HOST, CONF_PLATFORM, EVENT_HOMEASSISTANT_START, - EVENT_HOMEASSISTANT_STOP, STATE_IDLE, STATE_OFF, STATE_ON, STATE_PAUSED, - STATE_PLAYING) + CONF_DEVICES, + CONF_HOST, + CONF_PLATFORM, + EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP, + STATE_IDLE, + STATE_OFF, + STATE_ON, + STATE_PAUSED, + STATE_PLAYING, +) from homeassistant.core import HomeAssistant from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -DOMAIN = 'hdmi_cec' +DOMAIN = "hdmi_cec" _LOGGER = logging.getLogger(__name__) DEFAULT_DISPLAY_NAME = "HA" -CONF_TYPES = 'types' +CONF_TYPES = "types" -ICON_UNKNOWN = 'mdi:help' -ICON_AUDIO = 'mdi:speaker' -ICON_PLAYER = 'mdi:play' -ICON_TUNER = 'mdi:radio' -ICON_RECORDER = 'mdi:microphone' -ICON_TV = 'mdi:television' +ICON_UNKNOWN = "mdi:help" +ICON_AUDIO = "mdi:speaker" +ICON_PLAYER = "mdi:play" +ICON_TUNER = "mdi:radio" +ICON_RECORDER = "mdi:microphone" +ICON_TV = "mdi:television" ICONS_BY_TYPE = { 0: ICON_TV, 1: ICON_RECORDER, @@ -40,85 +48,100 @@ ICONS_BY_TYPE = { CEC_DEVICES = defaultdict(list) -CMD_UP = 'up' -CMD_DOWN = 'down' -CMD_MUTE = 'mute' -CMD_UNMUTE = 'unmute' -CMD_MUTE_TOGGLE = 'toggle mute' -CMD_PRESS = 'press' -CMD_RELEASE = 'release' +CMD_UP = "up" +CMD_DOWN = "down" +CMD_MUTE = "mute" +CMD_UNMUTE = "unmute" +CMD_MUTE_TOGGLE = "toggle mute" +CMD_PRESS = "press" +CMD_RELEASE = "release" -EVENT_CEC_COMMAND_RECEIVED = 'cec_command_received' -EVENT_CEC_KEYPRESS_RECEIVED = 'cec_keypress_received' +EVENT_CEC_COMMAND_RECEIVED = "cec_command_received" +EVENT_CEC_KEYPRESS_RECEIVED = "cec_keypress_received" -ATTR_PHYSICAL_ADDRESS = 'physical_address' -ATTR_TYPE_ID = 'type_id' -ATTR_VENDOR_NAME = 'vendor_name' -ATTR_VENDOR_ID = 'vendor_id' -ATTR_DEVICE = 'device' -ATTR_TYPE = 'type' -ATTR_KEY = 'key' -ATTR_DUR = 'dur' -ATTR_SRC = 'src' -ATTR_DST = 'dst' -ATTR_CMD = 'cmd' -ATTR_ATT = 'att' -ATTR_RAW = 'raw' -ATTR_DIR = 'dir' -ATTR_ABT = 'abt' -ATTR_NEW = 'new' -ATTR_ON = 'on' -ATTR_OFF = 'off' -ATTR_TOGGLE = 'toggle' +ATTR_PHYSICAL_ADDRESS = "physical_address" +ATTR_TYPE_ID = "type_id" +ATTR_VENDOR_NAME = "vendor_name" +ATTR_VENDOR_ID = "vendor_id" +ATTR_DEVICE = "device" +ATTR_TYPE = "type" +ATTR_KEY = "key" +ATTR_DUR = "dur" +ATTR_SRC = "src" +ATTR_DST = "dst" +ATTR_CMD = "cmd" +ATTR_ATT = "att" +ATTR_RAW = "raw" +ATTR_DIR = "dir" +ATTR_ABT = "abt" +ATTR_NEW = "new" +ATTR_ON = "on" +ATTR_OFF = "off" +ATTR_TOGGLE = "toggle" _VOL_HEX = vol.Any(vol.Coerce(int), lambda x: int(x, 16)) -SERVICE_SEND_COMMAND = 'send_command' -SERVICE_SEND_COMMAND_SCHEMA = vol.Schema({ - vol.Optional(ATTR_CMD): _VOL_HEX, - vol.Optional(ATTR_SRC): _VOL_HEX, - vol.Optional(ATTR_DST): _VOL_HEX, - vol.Optional(ATTR_ATT): _VOL_HEX, - vol.Optional(ATTR_RAW): vol.Coerce(str), -}, extra=vol.PREVENT_EXTRA) +SERVICE_SEND_COMMAND = "send_command" +SERVICE_SEND_COMMAND_SCHEMA = vol.Schema( + { + vol.Optional(ATTR_CMD): _VOL_HEX, + vol.Optional(ATTR_SRC): _VOL_HEX, + vol.Optional(ATTR_DST): _VOL_HEX, + vol.Optional(ATTR_ATT): _VOL_HEX, + vol.Optional(ATTR_RAW): vol.Coerce(str), + }, + extra=vol.PREVENT_EXTRA, +) -SERVICE_VOLUME = 'volume' -SERVICE_VOLUME_SCHEMA = vol.Schema({ - vol.Optional(CMD_UP): vol.Any(CMD_PRESS, CMD_RELEASE, vol.Coerce(int)), - vol.Optional(CMD_DOWN): vol.Any(CMD_PRESS, CMD_RELEASE, vol.Coerce(int)), - vol.Optional(CMD_MUTE): vol.Any(ATTR_ON, ATTR_OFF, ATTR_TOGGLE), -}, extra=vol.PREVENT_EXTRA) +SERVICE_VOLUME = "volume" +SERVICE_VOLUME_SCHEMA = vol.Schema( + { + vol.Optional(CMD_UP): vol.Any(CMD_PRESS, CMD_RELEASE, vol.Coerce(int)), + vol.Optional(CMD_DOWN): vol.Any(CMD_PRESS, CMD_RELEASE, vol.Coerce(int)), + vol.Optional(CMD_MUTE): vol.Any(ATTR_ON, ATTR_OFF, ATTR_TOGGLE), + }, + extra=vol.PREVENT_EXTRA, +) -SERVICE_UPDATE_DEVICES = 'update' -SERVICE_UPDATE_DEVICES_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({}) -}, extra=vol.PREVENT_EXTRA) +SERVICE_UPDATE_DEVICES = "update" +SERVICE_UPDATE_DEVICES_SCHEMA = vol.Schema( + {DOMAIN: vol.Schema({})}, extra=vol.PREVENT_EXTRA +) -SERVICE_SELECT_DEVICE = 'select_device' +SERVICE_SELECT_DEVICE = "select_device" -SERVICE_POWER_ON = 'power_on' -SERVICE_STANDBY = 'standby' +SERVICE_POWER_ON = "power_on" +SERVICE_STANDBY = "standby" # pylint: disable=unnecessary-lambda -DEVICE_SCHEMA = vol.Schema({ - vol.All(cv.positive_int): - vol.Any(lambda devices: DEVICE_SCHEMA(devices), cv.string) -}) +DEVICE_SCHEMA = vol.Schema( + { + vol.All(cv.positive_int): vol.Any( + lambda devices: DEVICE_SCHEMA(devices), cv.string + ) + } +) -CONF_DISPLAY_NAME = 'osd_name' +CONF_DISPLAY_NAME = "osd_name" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Optional(CONF_DEVICES): - vol.Any(DEVICE_SCHEMA, vol.Schema({ - vol.All(cv.string): vol.Any(cv.string)})), - vol.Optional(CONF_PLATFORM): vol.Any(SWITCH, MEDIA_PLAYER), - vol.Optional(CONF_HOST): cv.string, - vol.Optional(CONF_DISPLAY_NAME): cv.string, - vol.Optional(CONF_TYPES, default={}): - vol.Schema({cv.entity_id: vol.Any(MEDIA_PLAYER, SWITCH)}) - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Optional(CONF_DEVICES): vol.Any( + DEVICE_SCHEMA, vol.Schema({vol.All(cv.string): vol.Any(cv.string)}) + ), + vol.Optional(CONF_PLATFORM): vol.Any(SWITCH, MEDIA_PLAYER), + vol.Optional(CONF_HOST): cv.string, + vol.Optional(CONF_DISPLAY_NAME): cv.string, + vol.Optional(CONF_TYPES, default={}): vol.Schema( + {cv.entity_id: vol.Any(MEDIA_PLAYER, SWITCH)} + ), + } + ) + }, + extra=vol.ALLOW_EXTRA, +) def pad_physical_address(addr): @@ -133,6 +156,7 @@ def parse_mapping(mapping, parents=None): for addr, val in mapping.items(): if isinstance(addr, (str,)) and isinstance(val, (str,)): from pycec.network import PhysicalAddress + yield (addr, PhysicalAddress(val)) else: cur = parents + [addr] @@ -146,9 +170,16 @@ def setup(hass: HomeAssistant, base_config): """Set up the CEC capability.""" from pycec.network import HDMINetwork from pycec.commands import CecCommand, KeyReleaseCommand, KeyPressCommand - from pycec.const import KEY_VOLUME_UP, KEY_VOLUME_DOWN, KEY_MUTE_ON, \ - KEY_MUTE_OFF, KEY_MUTE_TOGGLE, ADDR_AUDIOSYSTEM, ADDR_BROADCAST, \ - ADDR_UNREGISTERED + from pycec.const import ( + KEY_VOLUME_UP, + KEY_VOLUME_DOWN, + KEY_MUTE_ON, + KEY_MUTE_OFF, + KEY_MUTE_TOGGLE, + ADDR_AUDIOSYSTEM, + ADDR_BROADCAST, + ADDR_UNREGISTERED, + ) from pycec.cec import CecAdapter from pycec.tcp import TcpAdapter @@ -164,10 +195,12 @@ def setup(hass: HomeAssistant, base_config): loop = ( # Create own thread if more than 1 CPU - hass.loop if multiprocessing.cpu_count() < 2 else None) + hass.loop + if multiprocessing.cpu_count() < 2 + else None + ) host = base_config[DOMAIN].get(CONF_HOST, None) - display_name = base_config[DOMAIN].get( - CONF_DISPLAY_NAME, DEFAULT_DISPLAY_NAME) + display_name = base_config[DOMAIN].get(CONF_DISPLAY_NAME, DEFAULT_DISPLAY_NAME) if host: adapter = TcpAdapter(host, name=display_name, activate_source=False) else: @@ -176,8 +209,11 @@ def setup(hass: HomeAssistant, base_config): def _volume(call): """Increase/decrease volume and mute/unmute system.""" - mute_key_mapping = {ATTR_TOGGLE: KEY_MUTE_TOGGLE, ATTR_ON: KEY_MUTE_ON, - ATTR_OFF: KEY_MUTE_OFF} + mute_key_mapping = { + ATTR_TOGGLE: KEY_MUTE_TOGGLE, + ATTR_ON: KEY_MUTE_ON, + ATTR_OFF: KEY_MUTE_OFF, + } for cmd, att in call.data.items(): if cmd == CMD_UP: _process_volume(KEY_VOLUME_UP, att) @@ -185,10 +221,9 @@ def setup(hass: HomeAssistant, base_config): _process_volume(KEY_VOLUME_DOWN, att) elif cmd == CMD_MUTE: hdmi_network.send_command( - KeyPressCommand(mute_key_mapping[att], - dst=ADDR_AUDIOSYSTEM)) - hdmi_network.send_command( - KeyReleaseCommand(dst=ADDR_AUDIOSYSTEM)) + KeyPressCommand(mute_key_mapping[att], dst=ADDR_AUDIOSYSTEM) + ) + hdmi_network.send_command(KeyReleaseCommand(dst=ADDR_AUDIOSYSTEM)) _LOGGER.info("Audio muted") else: _LOGGER.warning("Unknown command %s", cmd) @@ -197,17 +232,14 @@ def setup(hass: HomeAssistant, base_config): if isinstance(att, (str,)): att = att.strip() if att == CMD_PRESS: - hdmi_network.send_command( - KeyPressCommand(cmd, dst=ADDR_AUDIOSYSTEM)) + hdmi_network.send_command(KeyPressCommand(cmd, dst=ADDR_AUDIOSYSTEM)) elif att == CMD_RELEASE: hdmi_network.send_command(KeyReleaseCommand(dst=ADDR_AUDIOSYSTEM)) else: att = 1 if att == "" else int(att) for _ in range(0, att): - hdmi_network.send_command( - KeyPressCommand(cmd, dst=ADDR_AUDIOSYSTEM)) - hdmi_network.send_command( - KeyReleaseCommand(dst=ADDR_AUDIOSYSTEM)) + hdmi_network.send_command(KeyPressCommand(cmd, dst=ADDR_AUDIOSYSTEM)) + hdmi_network.send_command(KeyReleaseCommand(dst=ADDR_AUDIOSYSTEM)) def _tx(call): """Send CEC command.""" @@ -258,11 +290,12 @@ def setup(hass: HomeAssistant, base_config): entity = hass.states.get(addr) _LOGGER.debug("Selecting entity %s", entity) if entity is not None: - addr = entity.attributes['physical_address'] + addr = entity.attributes["physical_address"] _LOGGER.debug("Address acquired: %s", addr) if addr is None: - _LOGGER.error("Device %s has not physical address", - call.data[ATTR_DEVICE]) + _LOGGER.error( + "Device %s has not physical address", call.data[ATTR_DEVICE] + ) return if not isinstance(addr, (PhysicalAddress,)): addr = PhysicalAddress(addr) @@ -279,24 +312,34 @@ def setup(hass: HomeAssistant, base_config): def _new_device(device): """Handle new devices which are detected by HDMI network.""" - key = '{}.{}'.format(DOMAIN, device.name) + key = "{}.{}".format(DOMAIN, device.name) hass.data[key] = device ent_platform = base_config[DOMAIN][CONF_TYPES].get(key, platform) discovery.load_platform( - hass, ent_platform, DOMAIN, discovered={ATTR_NEW: [key]}, - hass_config=base_config) + hass, + ent_platform, + DOMAIN, + discovered={ATTR_NEW: [key]}, + hass_config=base_config, + ) def _shutdown(call): hdmi_network.stop() def _start_cec(event): """Register services and start HDMI network to watch for devices.""" - hass.services.register(DOMAIN, SERVICE_SEND_COMMAND, _tx, - SERVICE_SEND_COMMAND_SCHEMA) - hass.services.register(DOMAIN, SERVICE_VOLUME, _volume, - schema=SERVICE_VOLUME_SCHEMA) - hass.services.register(DOMAIN, SERVICE_UPDATE_DEVICES, _update, - schema=SERVICE_UPDATE_DEVICES_SCHEMA) + hass.services.register( + DOMAIN, SERVICE_SEND_COMMAND, _tx, SERVICE_SEND_COMMAND_SCHEMA + ) + hass.services.register( + DOMAIN, SERVICE_VOLUME, _volume, schema=SERVICE_VOLUME_SCHEMA + ) + hass.services.register( + DOMAIN, + SERVICE_UPDATE_DEVICES, + _update, + schema=SERVICE_UPDATE_DEVICES_SCHEMA, + ) hass.services.register(DOMAIN, SERVICE_POWER_ON, _power_on) hass.services.register(DOMAIN, SERVICE_STANDBY, _standby) hass.services.register(DOMAIN, SERVICE_SELECT_DEVICE, _select_device) @@ -323,8 +366,14 @@ class CecDevice(Entity): def update(self): """Update device status.""" device = self._device - from pycec.const import STATUS_PLAY, STATUS_STOP, STATUS_STILL, \ - POWER_OFF, POWER_ON + from pycec.const import ( + STATUS_PLAY, + STATUS_STOP, + STATUS_STILL, + POWER_OFF, + POWER_ON, + ) + if device.power_status in [POWER_OFF, 3]: self._state = STATE_OFF elif device.status == STATUS_PLAY: @@ -351,12 +400,16 @@ class CecDevice(Entity): """Return the name of the device.""" return ( "%s %s" % (self.vendor_name, self._device.osd_name) - if (self._device.osd_name is not None and - self.vendor_name is not None and self.vendor_name != 'Unknown') + if ( + self._device.osd_name is not None + and self.vendor_name is not None + and self.vendor_name != "Unknown" + ) else "%s %d" % (self._device.type_name, self._logical_address) if self._device.osd_name is None - else "%s %d (%s)" % (self._device.type_name, self._logical_address, - self._device.osd_name)) + else "%s %d (%s)" + % (self._device.type_name, self._logical_address, self._device.osd_name) + ) @property def vendor_id(self): @@ -386,9 +439,13 @@ class CecDevice(Entity): @property def icon(self): """Return the icon for device by its type.""" - return (self._icon if self._icon is not None else - ICONS_BY_TYPE.get(self._device.type) - if self._device.type in ICONS_BY_TYPE else ICON_UNKNOWN) + return ( + self._icon + if self._icon is not None + else ICONS_BY_TYPE.get(self._device.type) + if self._device.type in ICONS_BY_TYPE + else ICON_UNKNOWN + ) @property def device_state_attributes(self): diff --git a/homeassistant/components/hdmi_cec/media_player.py b/homeassistant/components/hdmi_cec/media_player.py index 4468fd9d648..379105430bc 100644 --- a/homeassistant/components/hdmi_cec/media_player.py +++ b/homeassistant/components/hdmi_cec/media_player.py @@ -3,17 +3,30 @@ import logging from homeassistant.components.media_player import MediaPlayerDevice from homeassistant.components.media_player.const import ( - DOMAIN, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP) + DOMAIN, + SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, + SUPPORT_PLAY_MEDIA, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_STOP, + SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, + SUPPORT_VOLUME_MUTE, + SUPPORT_VOLUME_STEP, +) from homeassistant.const import ( - STATE_IDLE, STATE_OFF, STATE_ON, STATE_PAUSED, STATE_PLAYING) + STATE_IDLE, + STATE_OFF, + STATE_ON, + STATE_PAUSED, + STATE_PLAYING, +) from . import ATTR_NEW, CecDevice _LOGGER = logging.getLogger(__name__) -ENTITY_ID_FORMAT = DOMAIN + '.{}' +ENTITY_ID_FORMAT = DOMAIN + ".{}" def setup_platform(hass, config, add_entities, discovery_info=None): @@ -23,9 +36,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): entities = [] for device in discovery_info[ATTR_NEW]: hdmi_device = hass.data.get(device) - entities.append(CecPlayerDevice( - hdmi_device, hdmi_device.logical_address, - )) + entities.append(CecPlayerDevice(hdmi_device, hdmi_device.logical_address)) add_entities(entities, True) @@ -35,33 +46,34 @@ class CecPlayerDevice(CecDevice, MediaPlayerDevice): def __init__(self, device, logical) -> None: """Initialize the HDMI device.""" CecDevice.__init__(self, device, logical) - self.entity_id = "%s.%s_%s" % ( - DOMAIN, 'hdmi', hex(self._logical_address)[2:]) + self.entity_id = "%s.%s_%s" % (DOMAIN, "hdmi", hex(self._logical_address)[2:]) def send_keypress(self, key): """Send keypress to CEC adapter.""" from pycec.commands import KeyPressCommand, KeyReleaseCommand - _LOGGER.debug("Sending keypress %s to device %s", hex(key), - hex(self._logical_address)) - self._device.send_command( - KeyPressCommand(key, dst=self._logical_address)) - self._device.send_command( - KeyReleaseCommand(dst=self._logical_address)) + + _LOGGER.debug( + "Sending keypress %s to device %s", hex(key), hex(self._logical_address) + ) + self._device.send_command(KeyPressCommand(key, dst=self._logical_address)) + self._device.send_command(KeyReleaseCommand(dst=self._logical_address)) def send_playback(self, key): """Send playback status to CEC adapter.""" from pycec.commands import CecCommand - self._device.async_send_command( - CecCommand(key, dst=self._logical_address)) + + self._device.async_send_command(CecCommand(key, dst=self._logical_address)) def mute_volume(self, mute): """Mute volume.""" from pycec.const import KEY_MUTE_TOGGLE + self.send_keypress(KEY_MUTE_TOGGLE) def media_previous_track(self): """Go to previous track.""" from pycec.const import KEY_BACKWARD + self.send_keypress(KEY_BACKWARD) def turn_on(self): @@ -81,6 +93,7 @@ class CecPlayerDevice(CecDevice, MediaPlayerDevice): def media_stop(self): """Stop playback.""" from pycec.const import KEY_STOP + self.send_keypress(KEY_STOP) self._state = STATE_IDLE @@ -91,6 +104,7 @@ class CecPlayerDevice(CecDevice, MediaPlayerDevice): def media_next_track(self): """Skip to next track.""" from pycec.const import KEY_FORWARD + self.send_keypress(KEY_FORWARD) def media_seek(self, position): @@ -104,6 +118,7 @@ class CecPlayerDevice(CecDevice, MediaPlayerDevice): def media_pause(self): """Pause playback.""" from pycec.const import KEY_PAUSE + self.send_keypress(KEY_PAUSE) self._state = STATE_PAUSED @@ -114,18 +129,21 @@ class CecPlayerDevice(CecDevice, MediaPlayerDevice): def media_play(self): """Start playback.""" from pycec.const import KEY_PLAY + self.send_keypress(KEY_PLAY) self._state = STATE_PLAYING def volume_up(self): """Increase volume.""" from pycec.const import KEY_VOLUME_UP + _LOGGER.debug("%s: volume up", self._logical_address) self.send_keypress(KEY_VOLUME_UP) def volume_down(self): """Decrease volume.""" from pycec.const import KEY_VOLUME_DOWN + _LOGGER.debug("%s: volume down", self._logical_address) self.send_keypress(KEY_VOLUME_DOWN) @@ -137,8 +155,14 @@ class CecPlayerDevice(CecDevice, MediaPlayerDevice): def update(self): """Update device status.""" device = self._device - from pycec.const import STATUS_PLAY, STATUS_STOP, STATUS_STILL, \ - POWER_OFF, POWER_ON + from pycec.const import ( + STATUS_PLAY, + STATUS_STOP, + STATUS_STILL, + POWER_OFF, + POWER_ON, + ) + if device.power_status in [POWER_OFF, 3]: self._state = STATE_OFF elif not self.support_pause: @@ -156,16 +180,31 @@ class CecPlayerDevice(CecDevice, MediaPlayerDevice): @property def supported_features(self): """Flag media player features that are supported.""" - from pycec.const import TYPE_RECORDER, TYPE_PLAYBACK, TYPE_TUNER, \ - TYPE_AUDIO + from pycec.const import TYPE_RECORDER, TYPE_PLAYBACK, TYPE_TUNER, TYPE_AUDIO + if self.type_id == TYPE_RECORDER or self.type == TYPE_PLAYBACK: - return (SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PLAY_MEDIA | - SUPPORT_PAUSE | SUPPORT_STOP | SUPPORT_PREVIOUS_TRACK | - SUPPORT_NEXT_TRACK) + return ( + SUPPORT_TURN_ON + | SUPPORT_TURN_OFF + | SUPPORT_PLAY_MEDIA + | SUPPORT_PAUSE + | SUPPORT_STOP + | SUPPORT_PREVIOUS_TRACK + | SUPPORT_NEXT_TRACK + ) if self.type == TYPE_TUNER: - return (SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PLAY_MEDIA | - SUPPORT_PAUSE | SUPPORT_STOP) + return ( + SUPPORT_TURN_ON + | SUPPORT_TURN_OFF + | SUPPORT_PLAY_MEDIA + | SUPPORT_PAUSE + | SUPPORT_STOP + ) if self.type_id == TYPE_AUDIO: - return (SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_VOLUME_STEP | - SUPPORT_VOLUME_MUTE) + return ( + SUPPORT_TURN_ON + | SUPPORT_TURN_OFF + | SUPPORT_VOLUME_STEP + | SUPPORT_VOLUME_MUTE + ) return SUPPORT_TURN_ON | SUPPORT_TURN_OFF diff --git a/homeassistant/components/hdmi_cec/switch.py b/homeassistant/components/hdmi_cec/switch.py index 9fb003f6d6a..53384397cf4 100644 --- a/homeassistant/components/hdmi_cec/switch.py +++ b/homeassistant/components/hdmi_cec/switch.py @@ -8,7 +8,7 @@ from . import ATTR_NEW, CecDevice _LOGGER = logging.getLogger(__name__) -ENTITY_ID_FORMAT = DOMAIN + '.{}' +ENTITY_ID_FORMAT = DOMAIN + ".{}" def setup_platform(hass, config, add_entities, discovery_info=None): @@ -18,9 +18,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): entities = [] for device in discovery_info[ATTR_NEW]: hdmi_device = hass.data.get(device) - entities.append(CecSwitchDevice( - hdmi_device, hdmi_device.logical_address, - )) + entities.append(CecSwitchDevice(hdmi_device, hdmi_device.logical_address)) add_entities(entities, True) @@ -30,8 +28,7 @@ class CecSwitchDevice(CecDevice, SwitchDevice): def __init__(self, device, logical) -> None: """Initialize the HDMI device.""" CecDevice.__init__(self, device, logical) - self.entity_id = "%s.%s_%s" % ( - DOMAIN, 'hdmi', hex(self._logical_address)[2:]) + self.entity_id = "%s.%s_%s" % (DOMAIN, "hdmi", hex(self._logical_address)[2:]) def turn_on(self, **kwargs) -> None: """Turn device on.""" diff --git a/homeassistant/components/heatmiser/climate.py b/homeassistant/components/heatmiser/climate.py index 0b92c377d48..c9bed1e9d34 100644 --- a/homeassistant/components/heatmiser/climate.py +++ b/homeassistant/components/heatmiser/climate.py @@ -4,28 +4,32 @@ import logging import voluptuous as vol from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA -from homeassistant.components.climate.const import ( - SUPPORT_TARGET_TEMPERATURE) +from homeassistant.components.climate.const import SUPPORT_TARGET_TEMPERATURE from homeassistant.const import ( - TEMP_CELSIUS, ATTR_TEMPERATURE, CONF_PORT, CONF_NAME, CONF_ID) + TEMP_CELSIUS, + ATTR_TEMPERATURE, + CONF_PORT, + CONF_NAME, + CONF_ID, +) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -CONF_IPADDRESS = 'ipaddress' -CONF_TSTATS = 'tstats' +CONF_IPADDRESS = "ipaddress" +CONF_TSTATS = "tstats" -TSTATS_SCHEMA = vol.Schema({ - vol.Required(CONF_ID): cv.string, - vol.Required(CONF_NAME): cv.string, -}) +TSTATS_SCHEMA = vol.Schema( + {vol.Required(CONF_ID): cv.string, vol.Required(CONF_NAME): cv.string} +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_IPADDRESS): cv.string, - vol.Required(CONF_PORT): cv.port, - vol.Required(CONF_TSTATS, default={}): - vol.Schema({cv.string: TSTATS_SCHEMA}), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_IPADDRESS): cv.string, + vol.Required(CONF_PORT): cv.port, + vol.Required(CONF_TSTATS, default={}): vol.Schema({cv.string: TSTATS_SCHEMA}), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -39,10 +43,15 @@ def setup_platform(hass, config, add_entities, discovery_info=None): serport = connection.connection(ipaddress, port) serport.open() - add_entities([ - HeatmiserV3Thermostat( - heatmiser, tstat.get(CONF_ID), tstat.get(CONF_NAME), serport) - for tstat in tstats.values()], True) + add_entities( + [ + HeatmiserV3Thermostat( + heatmiser, tstat.get(CONF_ID), tstat.get(CONF_NAME), serport + ) + for tstat in tstats.values() + ], + True, + ) class HeatmiserV3Thermostat(ClimateDevice): @@ -86,17 +95,12 @@ class HeatmiserV3Thermostat(ClimateDevice): def set_temperature(self, **kwargs): """Set new target temperature.""" temperature = kwargs.get(ATTR_TEMPERATURE) - self.heatmiser.hmSendAddress( - self._id, - 18, - temperature, - 1, - self.serport) + self.heatmiser.hmSendAddress(self._id, 18, temperature, 1, self.serport) def update(self): """Get the latest data.""" - self.dcb = self.heatmiser.hmReadAddress(self._id, 'prt', self.serport) - low = self.dcb.get('floortemplow ') - high = self.dcb.get('floortemphigh') + self.dcb = self.heatmiser.hmReadAddress(self._id, "prt", self.serport) + low = self.dcb.get("floortemplow ") + high = self.dcb.get("floortemphigh") self._current_temperature = (high * 256 + low) / 10.0 - self._target_temperature = int(self.dcb.get('roomset')) + self._target_temperature = int(self.dcb.get("roomset")) diff --git a/homeassistant/components/heos/__init__.py b/homeassistant/components/heos/__init__.py index 7a6cb36ab7b..a5450253be0 100644 --- a/homeassistant/components/heos/__init__.py +++ b/homeassistant/components/heos/__init__.py @@ -7,8 +7,7 @@ from typing import Dict from pyheos import CommandError, Heos, const as heos_const import voluptuous as vol -from homeassistant.components.media_player.const import ( - DOMAIN as MEDIA_PLAYER_DOMAIN) +from homeassistant.components.media_player.const import DOMAIN as MEDIA_PLAYER_DOMAIN from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STOP from homeassistant.exceptions import ConfigEntryNotReady @@ -19,14 +18,17 @@ from homeassistant.util import Throttle from . import services from .config_flow import format_title from .const import ( - COMMAND_RETRY_ATTEMPTS, COMMAND_RETRY_DELAY, DATA_CONTROLLER_MANAGER, - DATA_SOURCE_MANAGER, DOMAIN, SIGNAL_HEOS_UPDATED) + COMMAND_RETRY_ATTEMPTS, + COMMAND_RETRY_DELAY, + DATA_CONTROLLER_MANAGER, + DATA_SOURCE_MANAGER, + DOMAIN, + SIGNAL_HEOS_UPDATED, +) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_HOST): cv.string - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + {DOMAIN: vol.Schema({vol.Required(CONF_HOST): cv.string})}, extra=vol.ALLOW_EXTRA +) MIN_UPDATE_SOURCES = timedelta(seconds=1) @@ -43,8 +45,9 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType): # Create new entry based on config hass.async_create_task( hass.config_entries.flow.async_init( - DOMAIN, context={'source': 'import'}, - data={CONF_HOST: host})) + DOMAIN, context={"source": "import"}, data={CONF_HOST: host} + ) + ) else: # Check if host needs to be updated entry = entries[0] @@ -73,6 +76,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): # Disconnect when shutting down async def disconnect_controller(event): await controller.disconnect() + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, disconnect_controller) # Get players and sources @@ -85,12 +89,17 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): _LOGGER.warning( "%s is not logged in to a HEOS account and will be unable " "to retrieve HEOS favorites: Use the 'heos.sign_in' service " - "to sign-in to a HEOS account", host) + "to sign-in to a HEOS account", + host, + ) inputs = await controller.get_input_sources() except (asyncio.TimeoutError, ConnectionError, CommandError) as error: await controller.disconnect() - _LOGGER.debug("Unable to retrieve players and sources: %s", error, - exc_info=isinstance(error, CommandError)) + _LOGGER.debug( + "Unable to retrieve players and sources: %s", + error, + exc_info=isinstance(error, CommandError), + ) raise ConfigEntryNotReady controller_manager = ControllerManager(hass, controller) @@ -102,13 +111,14 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): hass.data[DOMAIN] = { DATA_CONTROLLER_MANAGER: controller_manager, DATA_SOURCE_MANAGER: source_manager, - MEDIA_PLAYER_DOMAIN: players + MEDIA_PLAYER_DOMAIN: players, } services.register(hass, controller) - hass.async_create_task(hass.config_entries.async_forward_entry_setup( - entry, MEDIA_PLAYER_DOMAIN)) + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, MEDIA_PLAYER_DOMAIN) + ) return True @@ -121,7 +131,8 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): services.remove(hass) return await hass.config_entries.async_forward_entry_unload( - entry, MEDIA_PLAYER_DOMAIN) + entry, MEDIA_PLAYER_DOMAIN + ) class ControllerManager: @@ -139,13 +150,20 @@ class ControllerManager: """Subscribe to events of interest.""" self._device_registry, self._entity_registry = await asyncio.gather( self._hass.helpers.device_registry.async_get_registry(), - self._hass.helpers.entity_registry.async_get_registry()) + self._hass.helpers.entity_registry.async_get_registry(), + ) # Handle controller events - self._signals.append(self.controller.dispatcher.connect( - heos_const.SIGNAL_CONTROLLER_EVENT, self._controller_event)) + self._signals.append( + self.controller.dispatcher.connect( + heos_const.SIGNAL_CONTROLLER_EVENT, self._controller_event + ) + ) # Handle connection-related events - self._signals.append(self.controller.dispatcher.connect( - heos_const.SIGNAL_HEOS_EVENT, self._heos_event)) + self._signals.append( + self.controller.dispatcher.connect( + heos_const.SIGNAL_HEOS_EVENT, self._heos_event + ) + ) async def disconnect(self): """Disconnect subscriptions.""" @@ -160,8 +178,7 @@ class ControllerManager: if event == heos_const.EVENT_PLAYERS_CHANGED: self.update_ids(data[heos_const.DATA_MAPPED_IDS]) # Update players - self._hass.helpers.dispatcher.async_dispatcher_send( - SIGNAL_HEOS_UPDATED) + self._hass.helpers.dispatcher.async_dispatcher_send(SIGNAL_HEOS_UPDATED) async def _heos_event(self, event): """Handle connection event.""" @@ -173,38 +190,44 @@ class ControllerManager: except (CommandError, asyncio.TimeoutError, ConnectionError) as ex: _LOGGER.error("Unable to refresh players: %s", ex) # Update players - self._hass.helpers.dispatcher.async_dispatcher_send( - SIGNAL_HEOS_UPDATED) + self._hass.helpers.dispatcher.async_dispatcher_send(SIGNAL_HEOS_UPDATED) def update_ids(self, mapped_ids: Dict[int, int]): """Update the IDs in the device and entity registry.""" # mapped_ids contains the mapped IDs (new:old) for new_id, old_id in mapped_ids.items(): # update device registry - entry = self._device_registry.async_get_device( - {(DOMAIN, old_id)}, set()) + entry = self._device_registry.async_get_device({(DOMAIN, old_id)}, set()) new_identifiers = {(DOMAIN, new_id)} if entry: self._device_registry.async_update_device( - entry.id, new_identifiers=new_identifiers) - _LOGGER.debug("Updated device %s identifiers to %s", - entry.id, new_identifiers) + entry.id, new_identifiers=new_identifiers + ) + _LOGGER.debug( + "Updated device %s identifiers to %s", entry.id, new_identifiers + ) # update entity registry entity_id = self._entity_registry.async_get_entity_id( - MEDIA_PLAYER_DOMAIN, DOMAIN, str(old_id)) + MEDIA_PLAYER_DOMAIN, DOMAIN, str(old_id) + ) if entity_id: self._entity_registry.async_update_entity( - entity_id, new_unique_id=str(new_id)) - _LOGGER.debug("Updated entity %s unique id to %s", - entity_id, new_id) + entity_id, new_unique_id=str(new_id) + ) + _LOGGER.debug("Updated entity %s unique id to %s", entity_id, new_id) class SourceManager: """Class that manages sources for players.""" - def __init__(self, favorites, inputs, *, - retry_delay: int = COMMAND_RETRY_DELAY, - max_retry_attempts: int = COMMAND_RETRY_ATTEMPTS): + def __init__( + self, + favorites, + inputs, + *, + retry_delay: int = COMMAND_RETRY_DELAY, + max_retry_attempts: int = COMMAND_RETRY_ATTEMPTS, + ): """Init input manager.""" self.retry_delay = retry_delay self.max_retry_attempts = max_retry_attempts @@ -215,21 +238,32 @@ class SourceManager: def _build_source_list(self): """Build a single list of inputs from various types.""" source_list = [] - source_list.extend([favorite.name for favorite - in self.favorites.values()]) + source_list.extend([favorite.name for favorite in self.favorites.values()]) source_list.extend([source.name for source in self.inputs]) return source_list async def play_source(self, source: str, player): """Determine type of source and play it.""" - index = next((index for index, favorite in self.favorites.items() - if favorite.name == source), None) + index = next( + ( + index + for index, favorite in self.favorites.items() + if favorite.name == source + ), + None, + ) if index is not None: await player.play_favorite(index) return - input_source = next((input_source for input_source in self.inputs - if input_source.name == source), None) + input_source = next( + ( + input_source + for input_source in self.inputs + if input_source.name == source + ), + None, + ) if input_source is not None: await player.play_input_source(input_source) return @@ -240,13 +274,24 @@ class SourceManager: """Determine current source from now playing media.""" # Match input by input_name:media_id if now_playing_media.source_id == heos_const.MUSIC_SOURCE_AUX_INPUT: - return next((input_source.name for input_source in self.inputs - if input_source.input_name == - now_playing_media.media_id), None) + return next( + ( + input_source.name + for input_source in self.inputs + if input_source.input_name == now_playing_media.media_id + ), + None, + ) # Try matching favorite by name:station or media_id:album_id - return next((source.name for source in self.favorites.values() - if source.name == now_playing_media.station - or source.media_id == now_playing_media.album_id), None) + return next( + ( + source.name + for source in self.favorites.values() + if source.name == now_playing_media.station + or source.media_id == now_playing_media.album_id + ), + None, + ) def connect_update(self, hass, controller): """ @@ -256,6 +301,7 @@ class SourceManager: physical event therefore throttle it. Retrieving sources immediately after the event may fail so retry. """ + @Throttle(MIN_UPDATE_SOURCES) async def get_sources(): retry_attempts = 0 @@ -266,23 +312,29 @@ class SourceManager: favorites = await controller.get_favorites() inputs = await controller.get_input_sources() return favorites, inputs - except (asyncio.TimeoutError, ConnectionError, CommandError) \ - as error: + except (asyncio.TimeoutError, ConnectionError, CommandError) as error: if retry_attempts < self.max_retry_attempts: retry_attempts += 1 - _LOGGER.debug("Error retrieving sources and will " - "retry: %s", error, - exc_info=isinstance(error, CommandError)) + _LOGGER.debug( + "Error retrieving sources and will " "retry: %s", + error, + exc_info=isinstance(error, CommandError), + ) await asyncio.sleep(self.retry_delay) else: - _LOGGER.error("Unable to update sources: %s", error, - exc_info=isinstance(error, CommandError)) + _LOGGER.error( + "Unable to update sources: %s", + error, + exc_info=isinstance(error, CommandError), + ) return async def update_sources(event, data=None): - if event in (heos_const.EVENT_SOURCES_CHANGED, - heos_const.EVENT_USER_CHANGED, - heos_const.EVENT_CONNECTED): + if event in ( + heos_const.EVENT_SOURCES_CHANGED, + heos_const.EVENT_USER_CHANGED, + heos_const.EVENT_CONNECTED, + ): sources = await get_sources() # If throttled, it will return None if sources: @@ -290,10 +342,9 @@ class SourceManager: self.source_list = self._build_source_list() _LOGGER.debug("Sources updated due to changed event") # Let players know to update - hass.helpers.dispatcher.async_dispatcher_send( - SIGNAL_HEOS_UPDATED) + hass.helpers.dispatcher.async_dispatcher_send(SIGNAL_HEOS_UPDATED) controller.dispatcher.connect( - heos_const.SIGNAL_CONTROLLER_EVENT, update_sources) - controller.dispatcher.connect( - heos_const.SIGNAL_HEOS_EVENT, update_sources) + heos_const.SIGNAL_CONTROLLER_EVENT, update_sources + ) + controller.dispatcher.connect(heos_const.SIGNAL_HEOS_EVENT, update_sources) diff --git a/homeassistant/components/heos/config_flow.py b/homeassistant/components/heos/config_flow.py index 8207d40be11..7c7f57a91d7 100644 --- a/homeassistant/components/heos/config_flow.py +++ b/homeassistant/components/heos/config_flow.py @@ -26,29 +26,27 @@ class HeosFlowHandler(config_entries.ConfigFlow): """Handle a discovered Heos device.""" # Store discovered host friendly_name = "{} ({})".format( - discovery_info[CONF_NAME], discovery_info[CONF_HOST]) + discovery_info[CONF_NAME], discovery_info[CONF_HOST] + ) self.hass.data.setdefault(DATA_DISCOVERED_HOSTS, {}) - self.hass.data[DATA_DISCOVERED_HOSTS][friendly_name] \ - = discovery_info[CONF_HOST] + self.hass.data[DATA_DISCOVERED_HOSTS][friendly_name] = discovery_info[CONF_HOST] # Abort if other flows in progress or an entry already exists if self._async_in_progress() or self._async_current_entries(): - return self.async_abort(reason='already_setup') + return self.async_abort(reason="already_setup") # Show selection form - return self.async_show_form(step_id='user') + return self.async_show_form(step_id="user") async def async_step_import(self, user_input=None): """Occurs when an entry is setup through config.""" host = user_input[CONF_HOST] - return self.async_create_entry( - title=format_title(host), - data={CONF_HOST: host}) + return self.async_create_entry(title=format_title(host), data={CONF_HOST: host}) async def async_step_user(self, user_input=None): """Obtain host and validate connection.""" self.hass.data.setdefault(DATA_DISCOVERED_HOSTS, {}) # Only a single entry is needed for all devices if self._async_current_entries(): - return self.async_abort(reason='already_setup') + return self.async_abort(reason="already_setup") # Try connecting to host if provided errors = {} host = None @@ -62,16 +60,18 @@ class HeosFlowHandler(config_entries.ConfigFlow): self.hass.data.pop(DATA_DISCOVERED_HOSTS) return await self.async_step_import({CONF_HOST: host}) except (asyncio.TimeoutError, ConnectionError): - errors[CONF_HOST] = 'connection_failure' + errors[CONF_HOST] = "connection_failure" finally: await heos.disconnect() # Return form - host_type = str if not self.hass.data[DATA_DISCOVERED_HOSTS] \ + host_type = ( + str + if not self.hass.data[DATA_DISCOVERED_HOSTS] else vol.In(list(self.hass.data[DATA_DISCOVERED_HOSTS])) + ) return self.async_show_form( - step_id='user', - data_schema=vol.Schema({ - vol.Required(CONF_HOST, default=host): host_type - }), - errors=errors) + step_id="user", + data_schema=vol.Schema({vol.Required(CONF_HOST, default=host): host_type}), + errors=errors, + ) diff --git a/homeassistant/components/heos/const.py b/homeassistant/components/heos/const.py index d3e3ccb07c3..503df40ccd4 100644 --- a/homeassistant/components/heos/const.py +++ b/homeassistant/components/heos/const.py @@ -7,7 +7,7 @@ COMMAND_RETRY_DELAY = 1 DATA_CONTROLLER_MANAGER = "controller" DATA_SOURCE_MANAGER = "source_manager" DATA_DISCOVERED_HOSTS = "heos_discovered_hosts" -DOMAIN = 'heos' +DOMAIN = "heos" SERVICE_SIGN_IN = "sign_in" SERVICE_SIGN_OUT = "sign_out" SIGNAL_HEOS_UPDATED = "heos_updated" diff --git a/homeassistant/components/heos/media_player.py b/homeassistant/components/heos/media_player.py index ff5c2d707f2..a4094a0c216 100644 --- a/homeassistant/components/heos/media_player.py +++ b/homeassistant/components/heos/media_player.py @@ -9,28 +9,45 @@ from pyheos import CommandError, const as heos_const from homeassistant.components.media_player import MediaPlayerDevice from homeassistant.components.media_player.const import ( - ATTR_MEDIA_ENQUEUE, DOMAIN, MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, - MEDIA_TYPE_URL, SUPPORT_CLEAR_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, - SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, - SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET, SUPPORT_STOP, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP) + ATTR_MEDIA_ENQUEUE, + DOMAIN, + MEDIA_TYPE_MUSIC, + MEDIA_TYPE_PLAYLIST, + MEDIA_TYPE_URL, + SUPPORT_CLEAR_PLAYLIST, + SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, + SUPPORT_PLAY, + SUPPORT_PLAY_MEDIA, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_SELECT_SOURCE, + SUPPORT_SHUFFLE_SET, + SUPPORT_STOP, + SUPPORT_VOLUME_MUTE, + SUPPORT_VOLUME_SET, + SUPPORT_VOLUME_STEP, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_IDLE, STATE_PAUSED, STATE_PLAYING from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util.dt import utcnow -from .const import ( - DATA_SOURCE_MANAGER, DOMAIN as HEOS_DOMAIN, SIGNAL_HEOS_UPDATED) +from .const import DATA_SOURCE_MANAGER, DOMAIN as HEOS_DOMAIN, SIGNAL_HEOS_UPDATED -BASE_SUPPORTED_FEATURES = SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET | \ - SUPPORT_VOLUME_STEP | SUPPORT_CLEAR_PLAYLIST | \ - SUPPORT_SHUFFLE_SET | SUPPORT_SELECT_SOURCE | \ - SUPPORT_PLAY_MEDIA +BASE_SUPPORTED_FEATURES = ( + SUPPORT_VOLUME_MUTE + | SUPPORT_VOLUME_SET + | SUPPORT_VOLUME_STEP + | SUPPORT_CLEAR_PLAYLIST + | SUPPORT_SHUFFLE_SET + | SUPPORT_SELECT_SOURCE + | SUPPORT_PLAY_MEDIA +) PLAY_STATE_TO_STATE = { heos_const.PLAY_STATE_PLAY: STATE_PLAYING, heos_const.PLAY_STATE_STOP: STATE_IDLE, - heos_const.PLAY_STATE_PAUSE: STATE_PAUSED + heos_const.PLAY_STATE_PAUSE: STATE_PAUSED, } CONTROL_TO_SUPPORT = { @@ -38,20 +55,20 @@ CONTROL_TO_SUPPORT = { heos_const.CONTROL_PAUSE: SUPPORT_PAUSE, heos_const.CONTROL_STOP: SUPPORT_STOP, heos_const.CONTROL_PLAY_PREVIOUS: SUPPORT_PREVIOUS_TRACK, - heos_const.CONTROL_PLAY_NEXT: SUPPORT_NEXT_TRACK + heos_const.CONTROL_PLAY_NEXT: SUPPORT_NEXT_TRACK, } _LOGGER = logging.getLogger(__name__) -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Platform uses config entry setup.""" pass -async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities): +async def async_setup_entry( + hass: HomeAssistantType, entry: ConfigEntry, async_add_entities +): """Add media players for a config entry.""" players = hass.data[HEOS_DOMAIN][DOMAIN] devices = [HeosMediaPlayer(player) for player in players.values()] @@ -60,15 +77,22 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry, def log_command_error(command: str): """Return decorator that logs command failure.""" + def decorator(func): @wraps(func) async def wrapper(*args, **kwargs): try: await func(*args, **kwargs) - except (CommandError, asyncio.TimeoutError, ConnectionError, - ValueError) as ex: + except ( + CommandError, + asyncio.TimeoutError, + ConnectionError, + ValueError, + ) as ex: _LOGGER.error("Unable to %s: %s", command, ex) + return wrapper + return decorator @@ -99,12 +123,17 @@ class HeosMediaPlayer(MediaPlayerDevice): """Device added to hass.""" self._source_manager = self.hass.data[HEOS_DOMAIN][DATA_SOURCE_MANAGER] # Update state when attributes of the player change - self._signals.append(self._player.heos.dispatcher.connect( - heos_const.SIGNAL_PLAYER_EVENT, self._player_update)) + self._signals.append( + self._player.heos.dispatcher.connect( + heos_const.SIGNAL_PLAYER_EVENT, self._player_update + ) + ) # Update state when heos changes self._signals.append( self.hass.helpers.dispatcher.async_dispatcher_connect( - SIGNAL_HEOS_UPDATED, self._heos_updated)) + SIGNAL_HEOS_UPDATED, self._heos_updated + ) + ) @log_command_error("clear playlist") async def async_clear_playlist(self): @@ -155,8 +184,10 @@ class HeosMediaPlayer(MediaPlayerDevice): index = int(media_id) except ValueError: # Try finding index by name - index = next((index for index, select in selects.items() - if select == media_id), None) + index = next( + (index for index, select in selects.items() if select == media_id), + None, + ) if index is None: raise ValueError("Invalid quick select '{}'".format(media_id)) await self._player.play_quick_select(index) @@ -167,9 +198,11 @@ class HeosMediaPlayer(MediaPlayerDevice): playlist = next((p for p in playlists if p.name == media_id), None) if not playlist: raise ValueError("Invalid playlist '{}'".format(media_id)) - add_queue_option = heos_const.ADD_QUEUE_ADD_TO_END \ - if kwargs.get(ATTR_MEDIA_ENQUEUE) \ + add_queue_option = ( + heos_const.ADD_QUEUE_ADD_TO_END + if kwargs.get(ATTR_MEDIA_ENQUEUE) else heos_const.ADD_QUEUE_REPLACE_AND_PLAY + ) await self._player.add_to_queue(playlist, add_queue_option) return @@ -179,9 +212,14 @@ class HeosMediaPlayer(MediaPlayerDevice): index = int(media_id) except ValueError: # Try finding index by name - index = next((index for index, favorite - in self._source_manager.favorites.items() - if favorite.name == media_id), None) + index = next( + ( + index + for index, favorite in self._source_manager.favorites.items() + if favorite.name == media_id + ), + None, + ) if index is None: raise ValueError("Invalid favorite '{}'".format(media_id)) await self._player.play_favorite(index) @@ -207,10 +245,8 @@ class HeosMediaPlayer(MediaPlayerDevice): async def async_update(self): """Update supported features of the player.""" controls = self._player.now_playing_media.supported_controls - current_support = [CONTROL_TO_SUPPORT[control] - for control in controls] - self._supported_features = reduce(ior, current_support, - BASE_SUPPORTED_FEATURES) + current_support = [CONTROL_TO_SUPPORT[control] for control in controls] + self._supported_features = reduce(ior, current_support, BASE_SUPPORTED_FEATURES) async def async_will_remove_from_hass(self): """Disconnect the device when removed.""" @@ -227,24 +263,22 @@ class HeosMediaPlayer(MediaPlayerDevice): def device_info(self) -> dict: """Get attributes about the device.""" return { - 'identifiers': { - (HEOS_DOMAIN, self._player.player_id) - }, - 'name': self._player.name, - 'model': self._player.model, - 'manufacturer': 'HEOS', - 'sw_version': self._player.version + "identifiers": {(HEOS_DOMAIN, self._player.player_id)}, + "name": self._player.name, + "model": self._player.model, + "manufacturer": "HEOS", + "sw_version": self._player.version, } @property def device_state_attributes(self) -> dict: """Get additional attribute about the state.""" return { - 'media_album_id': self._player.now_playing_media.album_id, - 'media_queue_id': self._player.now_playing_media.queue_id, - 'media_source_id': self._player.now_playing_media.source_id, - 'media_station': self._player.now_playing_media.station, - 'media_type': self._player.now_playing_media.type + "media_album_id": self._player.now_playing_media.album_id, + "media_queue_id": self._player.now_playing_media.queue_id, + "media_source_id": self._player.now_playing_media.source_id, + "media_station": self._player.now_playing_media.station, + "media_type": self._player.now_playing_media.type, } @property @@ -331,8 +365,7 @@ class HeosMediaPlayer(MediaPlayerDevice): @property def source(self) -> str: """Name of the current input source.""" - return self._source_manager.get_current_source( - self._player.now_playing_media) + return self._source_manager.get_current_source(self._player.now_playing_media) @property def source_list(self) -> Sequence[str]: diff --git a/homeassistant/components/heos/services.py b/homeassistant/components/heos/services.py index 5b998f384dc..8f3521399e2 100644 --- a/homeassistant/components/heos/services.py +++ b/homeassistant/components/heos/services.py @@ -10,14 +10,18 @@ from homeassistant.helpers import config_validation as cv from homeassistant.helpers.typing import HomeAssistantType from .const import ( - ATTR_PASSWORD, ATTR_USERNAME, DOMAIN, SERVICE_SIGN_IN, SERVICE_SIGN_OUT) + ATTR_PASSWORD, + ATTR_USERNAME, + DOMAIN, + SERVICE_SIGN_IN, + SERVICE_SIGN_OUT, +) _LOGGER = logging.getLogger(__name__) -HEOS_SIGN_IN_SCHEMA = vol.Schema({ - vol.Required(ATTR_USERNAME): cv.string, - vol.Required(ATTR_PASSWORD): cv.string -}) +HEOS_SIGN_IN_SCHEMA = vol.Schema( + {vol.Required(ATTR_USERNAME): cv.string, vol.Required(ATTR_PASSWORD): cv.string} +) HEOS_SIGN_OUT_SCHEMA = vol.Schema({}) @@ -25,13 +29,17 @@ HEOS_SIGN_OUT_SCHEMA = vol.Schema({}) def register(hass: HomeAssistantType, controller: Heos): """Register HEOS services.""" hass.services.async_register( - DOMAIN, SERVICE_SIGN_IN, + DOMAIN, + SERVICE_SIGN_IN, functools.partial(_sign_in_handler, controller), - schema=HEOS_SIGN_IN_SCHEMA) + schema=HEOS_SIGN_IN_SCHEMA, + ) hass.services.async_register( - DOMAIN, SERVICE_SIGN_OUT, + DOMAIN, + SERVICE_SIGN_OUT, functools.partial(_sign_out_handler, controller), - schema=HEOS_SIGN_OUT_SCHEMA) + schema=HEOS_SIGN_OUT_SCHEMA, + ) def remove(hass: HomeAssistantType): diff --git a/homeassistant/components/hikvision/binary_sensor.py b/homeassistant/components/hikvision/binary_sensor.py index f15d6739615..a9ab242c2fd 100644 --- a/homeassistant/components/hikvision/binary_sensor.py +++ b/homeassistant/components/hikvision/binary_sensor.py @@ -5,65 +5,77 @@ import voluptuous as vol from homeassistant.helpers.event import track_point_in_utc_time from homeassistant.util.dt import utcnow -from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) +from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA import homeassistant.helpers.config_validation as cv from homeassistant.const import ( - CONF_HOST, CONF_PORT, CONF_NAME, CONF_USERNAME, CONF_PASSWORD, - CONF_SSL, EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START, - ATTR_LAST_TRIP_TIME, CONF_CUSTOMIZE) + CONF_HOST, + CONF_PORT, + CONF_NAME, + CONF_USERNAME, + CONF_PASSWORD, + CONF_SSL, + EVENT_HOMEASSISTANT_STOP, + EVENT_HOMEASSISTANT_START, + ATTR_LAST_TRIP_TIME, + CONF_CUSTOMIZE, +) _LOGGER = logging.getLogger(__name__) -CONF_IGNORED = 'ignored' -CONF_DELAY = 'delay' +CONF_IGNORED = "ignored" +CONF_DELAY = "delay" DEFAULT_PORT = 80 DEFAULT_IGNORED = False DEFAULT_DELAY = 0 -ATTR_DELAY = 'delay' +ATTR_DELAY = "delay" DEVICE_CLASS_MAP = { - 'Motion': 'motion', - 'Line Crossing': 'motion', - 'Field Detection': 'motion', - 'Video Loss': None, - 'Tamper Detection': 'motion', - 'Shelter Alarm': None, - 'Disk Full': None, - 'Disk Error': None, - 'Net Interface Broken': 'connectivity', - 'IP Conflict': 'connectivity', - 'Illegal Access': None, - 'Video Mismatch': None, - 'Bad Video': None, - 'PIR Alarm': 'motion', - 'Face Detection': 'motion', - 'Scene Change Detection': 'motion', - 'I/O': None, - 'Unattended Baggage': 'motion', - 'Attended Baggage': 'motion', - 'Recording Failure': None, - 'Exiting Region': 'motion', - 'Entering Region': 'motion', + "Motion": "motion", + "Line Crossing": "motion", + "Field Detection": "motion", + "Video Loss": None, + "Tamper Detection": "motion", + "Shelter Alarm": None, + "Disk Full": None, + "Disk Error": None, + "Net Interface Broken": "connectivity", + "IP Conflict": "connectivity", + "Illegal Access": None, + "Video Mismatch": None, + "Bad Video": None, + "PIR Alarm": "motion", + "Face Detection": "motion", + "Scene Change Detection": "motion", + "I/O": None, + "Unattended Baggage": "motion", + "Attended Baggage": "motion", + "Recording Failure": None, + "Exiting Region": "motion", + "Entering Region": "motion", } -CUSTOMIZE_SCHEMA = vol.Schema({ - vol.Optional(CONF_IGNORED, default=DEFAULT_IGNORED): cv.boolean, - vol.Optional(CONF_DELAY, default=DEFAULT_DELAY): cv.positive_int - }) +CUSTOMIZE_SCHEMA = vol.Schema( + { + vol.Optional(CONF_IGNORED, default=DEFAULT_IGNORED): cv.boolean, + vol.Optional(CONF_DELAY, default=DEFAULT_DELAY): cv.positive_int, + } +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME): cv.string, - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_SSL, default=False): cv.boolean, - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_CUSTOMIZE, default={}): - vol.Schema({cv.string: CUSTOMIZE_SCHEMA}), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME): cv.string, + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_SSL, default=False): cv.boolean, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_CUSTOMIZE, default={}): vol.Schema( + {cv.string: CUSTOMIZE_SCHEMA} + ), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -77,11 +89,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): customize = config.get(CONF_CUSTOMIZE) if config.get(CONF_SSL): - protocol = 'https' + protocol = "https" else: - protocol = 'http' + protocol = "http" - url = '{}://{}'.format(protocol, host) + url = "{}://{}".format(protocol, host) data = HikvisionData(hass, url, port, name, username, password) @@ -94,21 +106,26 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for sensor, channel_list in data.sensors.items(): for channel in channel_list: # Build sensor name, then parse customize config. - if data.type == 'NVR': - sensor_name = '{}_{}'.format( - sensor.replace(' ', '_'), channel[1]) + if data.type == "NVR": + sensor_name = "{}_{}".format(sensor.replace(" ", "_"), channel[1]) else: - sensor_name = sensor.replace(' ', '_') + sensor_name = sensor.replace(" ", "_") custom = customize.get(sensor_name.lower(), {}) ignore = custom.get(CONF_IGNORED) delay = custom.get(CONF_DELAY) - _LOGGER.debug("Entity: %s - %s, Options - Ignore: %s, Delay: %s", - data.name, sensor_name, ignore, delay) + _LOGGER.debug( + "Entity: %s - %s, Options - Ignore: %s, Delay: %s", + data.name, + sensor_name, + ignore, + delay, + ) if not ignore: - entities.append(HikvisionBinarySensor( - hass, sensor, channel[1], data, delay)) + entities.append( + HikvisionBinarySensor(hass, sensor, channel[1], data, delay) + ) add_entities(entities) @@ -119,6 +136,7 @@ class HikvisionData: def __init__(self, hass, url, port, name, username, password): """Initialize the data object.""" from pyhik.hikvision import HikCamera + self._url = url self._port = port self._name = name @@ -126,8 +144,7 @@ class HikvisionData: self._password = password # Establish camera - self.camdata = HikCamera( - self._url, self._port, self._username, self._password) + self.camdata = HikCamera(self._url, self._port, self._username, self._password) if self._name is None: self._name = self.camdata.get_name @@ -178,12 +195,12 @@ class HikvisionBinarySensor(BinarySensorDevice): self._sensor = sensor self._channel = channel - if self._cam.type == 'NVR': - self._name = '{} {} {}'.format(self._cam.name, sensor, channel) + if self._cam.type == "NVR": + self._name = "{} {} {}".format(self._cam.name, sensor, channel) else: - self._name = '{} {}'.format(self._cam.name, sensor) + self._name = "{} {}".format(self._cam.name, sensor) - self._id = '{}.{}.{}'.format(self._cam.cam_id, sensor, channel) + self._id = "{}.{}.{}".format(self._cam.cam_id, sensor, channel) if delay is None: self._delay = 0 @@ -245,14 +262,15 @@ class HikvisionBinarySensor(BinarySensorDevice): def _update_callback(self, msg): """Update the sensor's state, if needed.""" - _LOGGER.debug('Callback signal from: %s', msg) + _LOGGER.debug("Callback signal from: %s", msg) if self._delay > 0 and not self.is_on: # Set timer to wait until updating the state def _delay_update(now): """Timer callback for sensor update.""" - _LOGGER.debug("%s Called delayed (%ssec) update", - self._name, self._delay) + _LOGGER.debug( + "%s Called delayed (%ssec) update", self._name, self._delay + ) self.schedule_update_ha_state() self._timer = None @@ -261,8 +279,8 @@ class HikvisionBinarySensor(BinarySensorDevice): self._timer = None self._timer = track_point_in_utc_time( - self._hass, _delay_update, - utcnow() + timedelta(seconds=self._delay)) + self._hass, _delay_update, utcnow() + timedelta(seconds=self._delay) + ) elif self._delay > 0 and self.is_on: # For delayed sensors kill any callbacks on true events and update diff --git a/homeassistant/components/hikvisioncam/switch.py b/homeassistant/components/hikvisioncam/switch.py index 373f84cee0e..05bce5f4eac 100644 --- a/homeassistant/components/hikvisioncam/switch.py +++ b/homeassistant/components/hikvisioncam/switch.py @@ -5,8 +5,14 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_NAME, CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT, STATE_OFF, - STATE_ON) + CONF_NAME, + CONF_HOST, + CONF_PASSWORD, + CONF_USERNAME, + CONF_PORT, + STATE_OFF, + STATE_ON, +) from homeassistant.helpers.entity import ToggleEntity import homeassistant.helpers.config_validation as cv @@ -14,18 +20,20 @@ import homeassistant.helpers.config_validation as cv _LOGGING = logging.getLogger(__name__) -DEFAULT_NAME = 'Hikvision Camera Motion Detection' -DEFAULT_PASSWORD = '12345' +DEFAULT_NAME = "Hikvision Camera Motion Detection" +DEFAULT_PASSWORD = "12345" DEFAULT_PORT = 80 -DEFAULT_USERNAME = 'admin' +DEFAULT_USERNAME = "admin" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string, - vol.Optional(CONF_PORT): cv.port, - vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string, + vol.Optional(CONF_PORT): cv.port, + vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -41,8 +49,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): try: hikvision_cam = hikvision.api.CreateDevice( - host, port=port, username=username, password=password, - is_https=False) + host, port=port, username=username, password=password, is_https=False + ) except MissingParamError as param_err: _LOGGING.error("Missing required param: %s", param_err) return False diff --git a/homeassistant/components/hipchat/notify.py b/homeassistant/components/hipchat/notify.py index f12fd1ffa76..03556db386a 100644 --- a/homeassistant/components/hipchat/notify.py +++ b/homeassistant/components/hipchat/notify.py @@ -6,46 +6,57 @@ import voluptuous as vol from homeassistant.const import CONF_HOST, CONF_ROOM, CONF_TOKEN import homeassistant.helpers.config_validation as cv -from homeassistant.components.notify import (ATTR_DATA, ATTR_TARGET, - PLATFORM_SCHEMA, - BaseNotificationService) +from homeassistant.components.notify import ( + ATTR_DATA, + ATTR_TARGET, + PLATFORM_SCHEMA, + BaseNotificationService, +) _LOGGER = logging.getLogger(__name__) -CONF_COLOR = 'color' -CONF_NOTIFY = 'notify' -CONF_FORMAT = 'format' +CONF_COLOR = "color" +CONF_NOTIFY = "notify" +CONF_FORMAT = "format" -DEFAULT_COLOR = 'yellow' -DEFAULT_FORMAT = 'text' -DEFAULT_HOST = 'https://api.hipchat.com/' +DEFAULT_COLOR = "yellow" +DEFAULT_FORMAT = "text" +DEFAULT_HOST = "https://api.hipchat.com/" DEFAULT_NOTIFY = False -VALID_COLORS = {'yellow', 'green', 'red', 'purple', 'gray', 'random'} -VALID_FORMATS = {'text', 'html'} +VALID_COLORS = {"yellow", "green", "red", "purple", "gray", "random"} +VALID_FORMATS = {"text", "html"} -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_ROOM): vol.Coerce(int), - vol.Required(CONF_TOKEN): cv.string, - vol.Optional(CONF_COLOR, default=DEFAULT_COLOR): vol.In(VALID_COLORS), - vol.Optional(CONF_FORMAT, default=DEFAULT_FORMAT): vol.In(VALID_FORMATS), - vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, - vol.Optional(CONF_NOTIFY, default=DEFAULT_NOTIFY): cv.boolean, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_ROOM): vol.Coerce(int), + vol.Required(CONF_TOKEN): cv.string, + vol.Optional(CONF_COLOR, default=DEFAULT_COLOR): vol.In(VALID_COLORS), + vol.Optional(CONF_FORMAT, default=DEFAULT_FORMAT): vol.In(VALID_FORMATS), + vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, + vol.Optional(CONF_NOTIFY, default=DEFAULT_NOTIFY): cv.boolean, + } +) def get_service(hass, config, discovery_info=None): """Get the HipChat notification service.""" return HipchatNotificationService( - config[CONF_TOKEN], config[CONF_ROOM], config[CONF_COLOR], - config[CONF_NOTIFY], config[CONF_FORMAT], config[CONF_HOST]) + config[CONF_TOKEN], + config[CONF_ROOM], + config[CONF_COLOR], + config[CONF_NOTIFY], + config[CONF_FORMAT], + config[CONF_HOST], + ) class HipchatNotificationService(BaseNotificationService): """Implement the notification service for HipChat.""" - def __init__(self, token, default_room, default_color, default_notify, - default_format, host): + def __init__( + self, token, default_room, default_color, default_notify, default_format, host + ): """Initialize the service.""" self._token = token self._default_room = default_room @@ -60,9 +71,11 @@ class HipchatNotificationService(BaseNotificationService): def _get_room(self, room): """Get Room object, creating it if necessary.""" from hipnotify import Room + if room not in self._rooms: self._rooms[room] = Room( - token=self._token, room_id=room, endpoint_url=self._host) + token=self._token, room_id=room, endpoint_url=self._host + ) return self._rooms[room] def send_message(self, message="", **kwargs): @@ -73,19 +86,23 @@ class HipchatNotificationService(BaseNotificationService): if kwargs.get(ATTR_DATA) is not None: data = kwargs.get(ATTR_DATA) - if ((data.get(CONF_COLOR) is not None) - and (data.get(CONF_COLOR) in VALID_COLORS)): + if (data.get(CONF_COLOR) is not None) and ( + data.get(CONF_COLOR) in VALID_COLORS + ): color = data.get(CONF_COLOR) - if ((data.get(CONF_NOTIFY) is not None) - and isinstance(data.get(CONF_NOTIFY), bool)): + if (data.get(CONF_NOTIFY) is not None) and isinstance( + data.get(CONF_NOTIFY), bool + ): notify = data.get(CONF_NOTIFY) - if ((data.get(CONF_FORMAT) is not None) - and (data.get(CONF_FORMAT) in VALID_FORMATS)): + if (data.get(CONF_FORMAT) is not None) and ( + data.get(CONF_FORMAT) in VALID_FORMATS + ): message_format = data.get(CONF_FORMAT) targets = kwargs.get(ATTR_TARGET, [self._default_room]) for target in targets: room = self._get_room(target) - room.notify(msg=message, color=color, notify=notify, - message_format=message_format) + room.notify( + msg=message, color=color, notify=notify, message_format=message_format + ) diff --git a/homeassistant/components/history/__init__.py b/homeassistant/components/history/__init__.py index 2032843e761..3b751b86c73 100644 --- a/homeassistant/components/history/__init__.py +++ b/homeassistant/components/history/__init__.py @@ -8,7 +8,12 @@ import time import voluptuous as vol from homeassistant.const import ( - HTTP_BAD_REQUEST, CONF_DOMAINS, CONF_ENTITIES, CONF_EXCLUDE, CONF_INCLUDE) + HTTP_BAD_REQUEST, + CONF_DOMAINS, + CONF_ENTITIES, + CONF_EXCLUDE, + CONF_INCLUDE, +) import homeassistant.util.dt as dt_util from homeassistant.components import recorder, script from homeassistant.components.http import HomeAssistantView @@ -18,21 +23,30 @@ import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DOMAIN = 'history' -CONF_ORDER = 'use_include_order' +DOMAIN = "history" +CONF_ORDER = "use_include_order" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: recorder.FILTER_SCHEMA.extend({ - vol.Optional(CONF_ORDER, default=False): cv.boolean, - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: recorder.FILTER_SCHEMA.extend( + {vol.Optional(CONF_ORDER, default=False): cv.boolean} + ) + }, + extra=vol.ALLOW_EXTRA, +) -SIGNIFICANT_DOMAINS = ('thermostat', 'climate', 'water_heater') -IGNORE_DOMAINS = ('zone', 'scene',) +SIGNIFICANT_DOMAINS = ("thermostat", "climate", "water_heater") +IGNORE_DOMAINS = ("zone", "scene") -def get_significant_states(hass, start_time, end_time=None, entity_ids=None, - filters=None, include_start_time_state=True): +def get_significant_states( + hass, + start_time, + end_time=None, + entity_ids=None, + filters=None, + include_start_time_state=True, +): """ Return states changes during UTC period start_time - end_time. @@ -45,9 +59,12 @@ def get_significant_states(hass, start_time, end_time=None, entity_ids=None, with session_scope(hass=hass) as session: query = session.query(States).filter( - (States.domain.in_(SIGNIFICANT_DOMAINS) | - (States.last_changed == States.last_updated)) & - (States.last_updated > start_time)) + ( + States.domain.in_(SIGNIFICANT_DOMAINS) + | (States.last_changed == States.last_updated) + ) + & (States.last_updated > start_time) + ) if filters: query = filters.apply(query, entity_ids) @@ -58,29 +75,29 @@ def get_significant_states(hass, start_time, end_time=None, entity_ids=None, query = query.order_by(States.last_updated) states = ( - state for state in execute(query) - if (_is_significant(state) and - not state.attributes.get(ATTR_HIDDEN, False))) + state + for state in execute(query) + if (_is_significant(state) and not state.attributes.get(ATTR_HIDDEN, False)) + ) if _LOGGER.isEnabledFor(logging.DEBUG): elapsed = time.perf_counter() - timer_start - _LOGGER.debug( - 'get_significant_states took %fs', elapsed) + _LOGGER.debug("get_significant_states took %fs", elapsed) return states_to_json( - hass, states, start_time, entity_ids, filters, - include_start_time_state) + hass, states, start_time, entity_ids, filters, include_start_time_state + ) -def state_changes_during_period(hass, start_time, end_time=None, - entity_id=None): +def state_changes_during_period(hass, start_time, end_time=None, entity_id=None): """Return states changes during UTC period start_time - end_time.""" from homeassistant.components.recorder.models import States with session_scope(hass=hass) as session: query = session.query(States).filter( - (States.last_changed == States.last_updated) & - (States.last_updated > start_time)) + (States.last_changed == States.last_updated) + & (States.last_updated > start_time) + ) if end_time is not None: query = query.filter(States.last_updated < end_time) @@ -90,8 +107,7 @@ def state_changes_during_period(hass, start_time, end_time=None, entity_ids = [entity_id] if entity_id is not None else None - states = execute( - query.order_by(States.last_updated)) + states = execute(query.order_by(States.last_updated)) return states_to_json(hass, states, start_time, entity_ids) @@ -104,7 +120,8 @@ def get_last_state_changes(hass, number_of_states, entity_id): with session_scope(hass=hass) as session: query = session.query(States).filter( - (States.last_changed == States.last_updated)) + (States.last_changed == States.last_updated) + ) if entity_id is not None: query = query.filter_by(entity_id=entity_id.lower()) @@ -112,16 +129,15 @@ def get_last_state_changes(hass, number_of_states, entity_id): entity_ids = [entity_id] if entity_id is not None else None states = execute( - query.order_by(States.last_updated.desc()).limit(number_of_states)) + query.order_by(States.last_updated.desc()).limit(number_of_states) + ) - return states_to_json(hass, reversed(states), - start_time, - entity_ids, - include_start_time_state=False) + return states_to_json( + hass, reversed(states), start_time, entity_ids, include_start_time_state=False + ) -def get_states(hass, utc_point_in_time, entity_ids=None, run=None, - filters=None): +def get_states(hass, utc_point_in_time, entity_ids=None, run=None, filters=None): """Return the states at a specific point in time.""" from homeassistant.components.recorder.models import States @@ -138,13 +154,14 @@ def get_states(hass, utc_point_in_time, entity_ids=None, run=None, if entity_ids and len(entity_ids) == 1: # Use an entirely different (and extremely fast) query if we only # have a single entity id - most_recent_state_ids = session.query( - States.state_id.label('max_state_id') - ).filter( - (States.last_updated < utc_point_in_time) & - (States.entity_id.in_(entity_ids)) - ).order_by( - States.last_updated.desc()) + most_recent_state_ids = ( + session.query(States.state_id.label("max_state_id")) + .filter( + (States.last_updated < utc_point_in_time) + & (States.entity_id.in_(entity_ids)) + ) + .order_by(States.last_updated.desc()) + ) most_recent_state_ids = most_recent_state_ids.limit(1) @@ -154,53 +171,59 @@ def get_states(hass, utc_point_in_time, entity_ids=None, run=None, # last recorder run started. most_recent_states_by_date = session.query( - States.entity_id.label('max_entity_id'), - func.max(States.last_updated).label('max_last_updated') + States.entity_id.label("max_entity_id"), + func.max(States.last_updated).label("max_last_updated"), ).filter( - (States.last_updated >= run.start) & - (States.last_updated < utc_point_in_time) + (States.last_updated >= run.start) + & (States.last_updated < utc_point_in_time) ) if entity_ids: - most_recent_states_by_date.filter( - States.entity_id.in_(entity_ids)) + most_recent_states_by_date.filter(States.entity_id.in_(entity_ids)) most_recent_states_by_date = most_recent_states_by_date.group_by( - States.entity_id) + States.entity_id + ) most_recent_states_by_date = most_recent_states_by_date.subquery() most_recent_state_ids = session.query( - func.max(States.state_id).label('max_state_id') - ).join(most_recent_states_by_date, and_( - States.entity_id == most_recent_states_by_date.c.max_entity_id, - States.last_updated == most_recent_states_by_date.c. - max_last_updated)) + func.max(States.state_id).label("max_state_id") + ).join( + most_recent_states_by_date, + and_( + States.entity_id == most_recent_states_by_date.c.max_entity_id, + States.last_updated + == most_recent_states_by_date.c.max_last_updated, + ), + ) - most_recent_state_ids = most_recent_state_ids.group_by( - States.entity_id) + most_recent_state_ids = most_recent_state_ids.group_by(States.entity_id) most_recent_state_ids = most_recent_state_ids.subquery() - query = session.query(States).join( - most_recent_state_ids, - States.state_id == most_recent_state_ids.c.max_state_id - ).filter((~States.domain.in_(IGNORE_DOMAINS))) + query = ( + session.query(States) + .join( + most_recent_state_ids, + States.state_id == most_recent_state_ids.c.max_state_id, + ) + .filter((~States.domain.in_(IGNORE_DOMAINS))) + ) if filters: query = filters.apply(query, entity_ids) - return [state for state in execute(query) - if not state.attributes.get(ATTR_HIDDEN, False)] + return [ + state + for state in execute(query) + if not state.attributes.get(ATTR_HIDDEN, False) + ] def states_to_json( - hass, - states, - start_time, - entity_ids, - filters=None, - include_start_time_state=True): + hass, states, start_time, entity_ids, filters=None, include_start_time_state=True +): """Convert SQL results into JSON friendly data structure. This takes our state list and turns it into a JSON friendly data @@ -225,8 +248,7 @@ def states_to_json( if _LOGGER.isEnabledFor(logging.DEBUG): elapsed = time.perf_counter() - timer_start - _LOGGER.debug( - 'getting %d first datapoints took %fs', len(result), elapsed) + _LOGGER.debug("getting %d first datapoints took %fs", len(result), elapsed) # Append all changes to it for ent_id, group in groupby(states, lambda state: state.entity_id): @@ -256,7 +278,8 @@ async def async_setup(hass, config): hass.http.register_view(HistoryPeriodView(filters, use_include_order)) hass.components.frontend.async_register_built_in_panel( - 'history', 'history', 'hass:poll-box') + "history", "history", "hass:poll-box" + ) return True @@ -264,9 +287,9 @@ async def async_setup(hass, config): class HistoryPeriodView(HomeAssistantView): """Handle history period requests.""" - url = '/api/history/period' - name = 'api:history:view-period' - extra_urls = ['/api/history/period/{datetime}'] + url = "/api/history/period" + name = "api:history:view-period" + extra_urls = ["/api/history/period/{datetime}"] def __init__(self, filters, use_include_order): """Initialize the history period view.""" @@ -280,7 +303,7 @@ class HistoryPeriodView(HomeAssistantView): datetime = dt_util.parse_datetime(datetime) if datetime is None: - return self.json_message('Invalid datetime', HTTP_BAD_REQUEST) + return self.json_message("Invalid datetime", HTTP_BAD_REQUEST) now = dt_util.utcnow() @@ -293,30 +316,35 @@ class HistoryPeriodView(HomeAssistantView): if start_time > now: return self.json([]) - end_time = request.query.get('end_time') + end_time = request.query.get("end_time") if end_time: end_time = dt_util.parse_datetime(end_time) if end_time: end_time = dt_util.as_utc(end_time) else: - return self.json_message('Invalid end_time', HTTP_BAD_REQUEST) + return self.json_message("Invalid end_time", HTTP_BAD_REQUEST) else: end_time = start_time + one_day - entity_ids = request.query.get('filter_entity_id') + entity_ids = request.query.get("filter_entity_id") if entity_ids: - entity_ids = entity_ids.lower().split(',') - include_start_time_state = 'skip_initial_state' not in request.query + entity_ids = entity_ids.lower().split(",") + include_start_time_state = "skip_initial_state" not in request.query - hass = request.app['hass'] + hass = request.app["hass"] result = await hass.async_add_job( - get_significant_states, hass, start_time, end_time, - entity_ids, self.filters, include_start_time_state) + get_significant_states, + hass, + start_time, + end_time, + entity_ids, + self.filters, + include_start_time_state, + ) result = list(result.values()) if _LOGGER.isEnabledFor(logging.DEBUG): elapsed = time.perf_counter() - timer_start - _LOGGER.debug( - 'Extracted %d states in %fs', sum(map(len, result)), elapsed) + _LOGGER.debug("Extracted %d states in %fs", sum(map(len, result)), elapsed) # Optionally reorder the result to respect the ordering given # by any entities explicitly included in the configuration. @@ -378,14 +406,19 @@ class Filters: elif self.excluded_domains and self.included_domains: filter_query = ~States.domain.in_(self.excluded_domains) if self.included_entities: - filter_query &= (States.domain.in_(self.included_domains) | - States.entity_id.in_(self.included_entities)) + filter_query &= States.domain.in_( + self.included_domains + ) | States.entity_id.in_(self.included_entities) else: - filter_query &= (States.domain.in_(self.included_domains) & ~ - States.domain.in_(self.excluded_domains)) + filter_query &= States.domain.in_( + self.included_domains + ) & ~States.domain.in_(self.excluded_domains) # no domain filter just included entities - elif not self.excluded_domains and not self.included_domains and \ - self.included_entities: + elif ( + not self.excluded_domains + and not self.included_domains + and self.included_entities + ): filter_query = States.entity_id.in_(self.included_entities) if filter_query is not None: query = query.filter(filter_query) @@ -401,5 +434,4 @@ def _is_significant(state): Will only test for things that are not filtered out in SQL. """ # scripts that are not cancellable will never change state - return (state.domain != 'script' or - state.attributes.get(script.ATTR_CAN_CANCEL)) + return state.domain != "script" or state.attributes.get(script.ATTR_CAN_CANCEL) diff --git a/homeassistant/components/history_graph/__init__.py b/homeassistant/components/history_graph/__init__.py index 964d47d2502..ad8398c75f5 100644 --- a/homeassistant/components/history_graph/__init__.py +++ b/homeassistant/components/history_graph/__init__.py @@ -10,31 +10,32 @@ from homeassistant.helpers.entity_component import EntityComponent _LOGGER = logging.getLogger(__name__) -DOMAIN = 'history_graph' +DOMAIN = "history_graph" -CONF_HOURS_TO_SHOW = 'hours_to_show' -CONF_REFRESH = 'refresh' +CONF_HOURS_TO_SHOW = "hours_to_show" +CONF_REFRESH = "refresh" ATTR_HOURS_TO_SHOW = CONF_HOURS_TO_SHOW ATTR_REFRESH = CONF_REFRESH -GRAPH_SCHEMA = vol.Schema({ - vol.Required(CONF_ENTITIES): cv.entity_ids, - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_HOURS_TO_SHOW, default=24): vol.Range(min=1), - vol.Optional(CONF_REFRESH, default=0): vol.Range(min=0), -}) +GRAPH_SCHEMA = vol.Schema( + { + vol.Required(CONF_ENTITIES): cv.entity_ids, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_HOURS_TO_SHOW, default=24): vol.Range(min=1), + vol.Optional(CONF_REFRESH, default=0): vol.Range(min=0), + } +) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: cv.schema_with_slug_keys(GRAPH_SCHEMA), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + {DOMAIN: cv.schema_with_slug_keys(GRAPH_SCHEMA)}, extra=vol.ALLOW_EXTRA +) async def async_setup(hass, config): """Load graph configurations.""" - component = EntityComponent( - _LOGGER, DOMAIN, hass) + component = EntityComponent(_LOGGER, DOMAIN, hass) graphs = [] for object_id, cfg in config[DOMAIN].items(): diff --git a/homeassistant/components/history_stats/sensor.py b/homeassistant/components/history_stats/sensor.py index a0a08d4833e..5c59b5f8e97 100644 --- a/homeassistant/components/history_stats/sensor.py +++ b/homeassistant/components/history_stats/sensor.py @@ -11,53 +11,59 @@ import homeassistant.helpers.config_validation as cv import homeassistant.util.dt as dt_util from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_NAME, CONF_ENTITY_ID, CONF_STATE, CONF_TYPE, - EVENT_HOMEASSISTANT_START) + CONF_NAME, + CONF_ENTITY_ID, + CONF_STATE, + CONF_TYPE, + EVENT_HOMEASSISTANT_START, +) from homeassistant.exceptions import TemplateError from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_state_change _LOGGER = logging.getLogger(__name__) -DOMAIN = 'history_stats' -CONF_START = 'start' -CONF_END = 'end' -CONF_DURATION = 'duration' +DOMAIN = "history_stats" +CONF_START = "start" +CONF_END = "end" +CONF_DURATION = "duration" CONF_PERIOD_KEYS = [CONF_START, CONF_END, CONF_DURATION] -CONF_TYPE_TIME = 'time' -CONF_TYPE_RATIO = 'ratio' -CONF_TYPE_COUNT = 'count' +CONF_TYPE_TIME = "time" +CONF_TYPE_RATIO = "ratio" +CONF_TYPE_COUNT = "count" CONF_TYPE_KEYS = [CONF_TYPE_TIME, CONF_TYPE_RATIO, CONF_TYPE_COUNT] -DEFAULT_NAME = 'unnamed statistics' -UNITS = { - CONF_TYPE_TIME: 'h', - CONF_TYPE_RATIO: '%', - CONF_TYPE_COUNT: '' -} -ICON = 'mdi:chart-line' +DEFAULT_NAME = "unnamed statistics" +UNITS = {CONF_TYPE_TIME: "h", CONF_TYPE_RATIO: "%", CONF_TYPE_COUNT: ""} +ICON = "mdi:chart-line" -ATTR_VALUE = 'value' +ATTR_VALUE = "value" def exactly_two_period_keys(conf): """Ensure exactly 2 of CONF_PERIOD_KEYS are provided.""" if sum(param in conf for param in CONF_PERIOD_KEYS) != 2: - raise vol.Invalid('You must provide exactly 2 of the following:' - ' start, end, duration') + raise vol.Invalid( + "You must provide exactly 2 of the following:" " start, end, duration" + ) return conf -PLATFORM_SCHEMA = vol.All(PLATFORM_SCHEMA.extend({ - vol.Required(CONF_ENTITY_ID): cv.entity_id, - vol.Required(CONF_STATE): cv.string, - vol.Optional(CONF_START): cv.template, - vol.Optional(CONF_END): cv.template, - vol.Optional(CONF_DURATION): cv.time_period, - vol.Optional(CONF_TYPE, default=CONF_TYPE_TIME): vol.In(CONF_TYPE_KEYS), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}), exactly_two_period_keys) +PLATFORM_SCHEMA = vol.All( + PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_ENTITY_ID): cv.entity_id, + vol.Required(CONF_STATE): cv.string, + vol.Optional(CONF_START): cv.template, + vol.Optional(CONF_END): cv.template, + vol.Optional(CONF_DURATION): cv.time_period, + vol.Optional(CONF_TYPE, default=CONF_TYPE_TIME): vol.In(CONF_TYPE_KEYS), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + } + ), + exactly_two_period_keys, +) # noinspection PyUnusedLocal @@ -75,8 +81,13 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if template is not None: template.hass = hass - add_entities([HistoryStatsSensor(hass, entity_id, entity_state, start, end, - duration, sensor_type, name)]) + add_entities( + [ + HistoryStatsSensor( + hass, entity_id, entity_state, start, end, duration, sensor_type, name + ) + ] + ) return True @@ -85,8 +96,8 @@ class HistoryStatsSensor(Entity): """Representation of a HistoryStats sensor.""" def __init__( - self, hass, entity_id, entity_state, start, end, duration, - sensor_type, name): + self, hass, entity_id, entity_state, start, end, duration, sensor_type, name + ): """Initialize the HistoryStats sensor.""" self._entity_id = entity_id self._entity_state = entity_state @@ -104,6 +115,7 @@ class HistoryStatsSensor(Entity): @callback def start_refresh(*args): """Register state tracking.""" + @callback def force_refresh(*args): """Force the component to refresh.""" @@ -152,9 +164,7 @@ class HistoryStatsSensor(Entity): return {} hsh = HistoryStatsHelper - return { - ATTR_VALUE: hsh.pretty_duration(self.value), - } + return {ATTR_VALUE: hsh.pretty_duration(self.value)} @property def icon(self): @@ -185,23 +195,25 @@ class HistoryStatsSensor(Entity): now_timestamp = math.floor(dt_util.as_timestamp(now)) # If period has not changed and current time after the period end... - if start_timestamp == p_start_timestamp and \ - end_timestamp == p_end_timestamp and \ - end_timestamp <= now_timestamp: + if ( + start_timestamp == p_start_timestamp + and end_timestamp == p_end_timestamp + and end_timestamp <= now_timestamp + ): # Don't compute anything as the value cannot have changed return # Get history between start and end history_list = history.state_changes_during_period( - self.hass, start, end, str(self._entity_id)) + self.hass, start, end, str(self._entity_id) + ) if self._entity_id not in history_list.keys(): return # Get the first state last_state = history.get_state(self.hass, start, self._entity_id) - last_state = (last_state is not None and - last_state == self._entity_state) + last_state = last_state is not None and last_state == self._entity_state last_time = start_timestamp elapsed = 0 count = 0 @@ -240,16 +252,18 @@ class HistoryStatsSensor(Entity): try: start_rendered = self._start.render() except (TemplateError, TypeError) as ex: - HistoryStatsHelper.handle_template_exception(ex, 'start') + HistoryStatsHelper.handle_template_exception(ex, "start") return start = dt_util.parse_datetime(start_rendered) if start is None: try: - start = dt_util.as_local(dt_util.utc_from_timestamp( - math.floor(float(start_rendered)))) + start = dt_util.as_local( + dt_util.utc_from_timestamp(math.floor(float(start_rendered))) + ) except ValueError: - _LOGGER.error("Parsing error: start must be a datetime" - "or a timestamp") + _LOGGER.error( + "Parsing error: start must be a datetime" "or a timestamp" + ) return # Parse end @@ -257,16 +271,18 @@ class HistoryStatsSensor(Entity): try: end_rendered = self._end.render() except (TemplateError, TypeError) as ex: - HistoryStatsHelper.handle_template_exception(ex, 'end') + HistoryStatsHelper.handle_template_exception(ex, "end") return end = dt_util.parse_datetime(end_rendered) if end is None: try: - end = dt_util.as_local(dt_util.utc_from_timestamp( - math.floor(float(end_rendered)))) + end = dt_util.as_local( + dt_util.utc_from_timestamp(math.floor(float(end_rendered))) + ) except ValueError: - _LOGGER.error("Parsing error: end must be a datetime " - "or a timestamp") + _LOGGER.error( + "Parsing error: end must be a datetime " "or a timestamp" + ) return # Calculate start or end using the duration @@ -296,10 +312,10 @@ class HistoryStatsHelper: hours, seconds = divmod(seconds, 3600) minutes, seconds = divmod(seconds, 60) if days > 0: - return '%dd %dh %dm' % (days, hours, minutes) + return "%dd %dh %dm" % (days, hours, minutes) if hours > 0: - return '%dh %dm' % (hours, minutes) - return '%dm' % minutes + return "%dh %dm" % (hours, minutes) + return "%dm" % minutes @staticmethod def pretty_ratio(value, period): @@ -313,8 +329,7 @@ class HistoryStatsHelper: @staticmethod def handle_template_exception(ex, field): """Log an error nicely if the template cannot be interpreted.""" - if ex.args and ex.args[0].startswith( - "UndefinedError: 'None' has no attribute"): + if ex.args and ex.args[0].startswith("UndefinedError: 'None' has no attribute"): # Common during HA startup - so just a warning _LOGGER.warning(ex) return diff --git a/homeassistant/components/hitron_coda/device_tracker.py b/homeassistant/components/hitron_coda/device_tracker.py index e6f68d704fd..e3e8975c125 100644 --- a/homeassistant/components/hitron_coda/device_tracker.py +++ b/homeassistant/components/hitron_coda/device_tracker.py @@ -7,21 +7,24 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.device_tracker import ( - DOMAIN, PLATFORM_SCHEMA, DeviceScanner) -from homeassistant.const import ( - CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_TYPE + DOMAIN, + PLATFORM_SCHEMA, + DeviceScanner, ) +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_TYPE _LOGGER = logging.getLogger(__name__) DEFAULT_TYPE = "rogers" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_TYPE, default=DEFAULT_TYPE): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_TYPE, default=DEFAULT_TYPE): cv.string, + } +) def get_scanner(_hass, config): @@ -31,7 +34,7 @@ def get_scanner(_hass, config): return scanner if scanner.success_init else None -Device = namedtuple('Device', ['mac', 'name']) +Device = namedtuple("Device", ["mac", "name"]) class HitronCODADeviceScanner(DeviceScanner): @@ -41,16 +44,16 @@ class HitronCODADeviceScanner(DeviceScanner): """Initialize the scanner.""" self.last_results = [] host = config[CONF_HOST] - self._url = 'http://{}/data/getConnectInfo.asp'.format(host) - self._loginurl = 'http://{}/goform/login'.format(host) + self._url = "http://{}/data/getConnectInfo.asp".format(host) + self._loginurl = "http://{}/goform/login".format(host) self._username = config.get(CONF_USERNAME) self._password = config.get(CONF_PASSWORD) if config.get(CONF_TYPE) == "shaw": - self._type = 'pwd' + self._type = "pwd" else: - self._type = 'pws' + self._type = "pws" self._userid = None @@ -65,9 +68,9 @@ class HitronCODADeviceScanner(DeviceScanner): def get_device_name(self, device): """Return the name of the device with the given MAC address.""" - name = next(( - result.name for result in self.last_results - if result.mac == device), None) + name = next( + (result.name for result in self.last_results if result.mac == device), None + ) return name def _login(self): @@ -75,21 +78,16 @@ class HitronCODADeviceScanner(DeviceScanner): _LOGGER.info("Logging in to CODA...") try: - data = [ - ('user', self._username), - (self._type, self._password), - ] + data = [("user", self._username), (self._type, self._password)] res = requests.post(self._loginurl, data=data, timeout=10) except requests.exceptions.Timeout: - _LOGGER.error( - "Connection to the router timed out at URL %s", self._url) + _LOGGER.error("Connection to the router timed out at URL %s", self._url) return False if res.status_code != 200: - _LOGGER.error( - "Connection failed with http code %s", res.status_code) + _LOGGER.error("Connection failed with http code %s", res.status_code) return False try: - self._userid = res.cookies['userid'] + self._userid = res.cookies["userid"] return True except KeyError: _LOGGER.error("Failed to log in to router") @@ -107,16 +105,12 @@ class HitronCODADeviceScanner(DeviceScanner): # doing a request try: - res = requests.get(self._url, timeout=10, cookies={ - 'userid': self._userid - }) + res = requests.get(self._url, timeout=10, cookies={"userid": self._userid}) except requests.exceptions.Timeout: - _LOGGER.error( - "Connection to the router timed out at URL %s", self._url) + _LOGGER.error("Connection to the router timed out at URL %s", self._url) return False if res.status_code != 200: - _LOGGER.error( - "Connection failed with http code %s", res.status_code) + _LOGGER.error("Connection failed with http code %s", res.status_code) return False try: result = res.json() @@ -127,8 +121,8 @@ class HitronCODADeviceScanner(DeviceScanner): # parsing response for info in result: - mac = info['macAddr'] - name = info['hostName'] + mac = info["macAddr"] + name = info["hostName"] # No address = no item :) if mac is None: continue diff --git a/homeassistant/components/hive/__init__.py b/homeassistant/components/hive/__init__.py index 7ad1cc002f9..fc96f2d8c96 100644 --- a/homeassistant/components/hive/__init__.py +++ b/homeassistant/components/hive/__init__.py @@ -4,31 +4,35 @@ import logging from pyhiveapi import Pyhiveapi import voluptuous as vol -from homeassistant.const import ( - CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME) +from homeassistant.const import CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME import homeassistant.helpers.config_validation as cv from homeassistant.helpers.discovery import load_platform _LOGGER = logging.getLogger(__name__) -DOMAIN = 'hive' -DATA_HIVE = 'data_hive' +DOMAIN = "hive" +DATA_HIVE = "data_hive" DEVICETYPES = { - 'binary_sensor': 'device_list_binary_sensor', - 'climate': 'device_list_climate', - 'water_heater': 'device_list_water_heater', - 'light': 'device_list_light', - 'switch': 'device_list_plug', - 'sensor': 'device_list_sensor', + "binary_sensor": "device_list_binary_sensor", + "climate": "device_list_climate", + "water_heater": "device_list_water_heater", + "light": "device_list_light", + "switch": "device_list_plug", + "sensor": "device_list_sensor", } -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Optional(CONF_SCAN_INTERVAL, default=2): cv.positive_int, - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Optional(CONF_SCAN_INTERVAL, default=2): cv.positive_int, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) class HiveSession: @@ -54,8 +58,7 @@ def setup(hass, config): password = config[DOMAIN][CONF_PASSWORD] update_interval = config[DOMAIN][CONF_SCAN_INTERVAL] - devicelist = session.core.initialise_api( - username, password, update_interval) + devicelist = session.core.initialise_api(username, password, update_interval) if devicelist is None: _LOGGER.error("Hive API initialization failed") diff --git a/homeassistant/components/hive/binary_sensor.py b/homeassistant/components/hive/binary_sensor.py index 97900c2852e..80aaaf86463 100644 --- a/homeassistant/components/hive/binary_sensor.py +++ b/homeassistant/components/hive/binary_sensor.py @@ -3,10 +3,7 @@ from homeassistant.components.binary_sensor import BinarySensorDevice from . import DATA_HIVE, DOMAIN -DEVICETYPE_DEVICE_CLASS = { - 'motionsensor': 'motion', - 'contactsensor': 'opening', -} +DEVICETYPE_DEVICE_CLASS = {"motionsensor": "motion", "contactsensor": "opening"} def setup_platform(hass, config, add_entities, discovery_info=None): @@ -29,9 +26,8 @@ class HiveBinarySensorEntity(BinarySensorDevice): self.node_device_type = hivedevice["Hive_DeviceType"] self.session = hivesession self.attributes = {} - self.data_updatesource = '{}.{}'.format(self.device_type, - self.node_id) - self._unique_id = '{}-{}'.format(self.node_id, self.device_type) + self.data_updatesource = "{}.{}".format(self.device_type, self.node_id) + self._unique_id = "{}-{}".format(self.node_id, self.device_type) self.session.entities.append(self) @property @@ -42,16 +38,11 @@ class HiveBinarySensorEntity(BinarySensorDevice): @property def device_info(self): """Return device information.""" - return { - 'identifiers': { - (DOMAIN, self.unique_id) - }, - 'name': self.name - } + return {"identifiers": {(DOMAIN, self.unique_id)}, "name": self.name} def handle_update(self, updatesource): """Handle the new update request.""" - if '{}.{}'.format(self.device_type, self.node_id) not in updatesource: + if "{}.{}".format(self.device_type, self.node_id) not in updatesource: self.schedule_update_ha_state() @property @@ -72,11 +63,9 @@ class HiveBinarySensorEntity(BinarySensorDevice): @property def is_on(self): """Return true if the binary sensor is on.""" - return self.session.sensor.get_state( - self.node_id, self.node_device_type) + return self.session.sensor.get_state(self.node_id, self.node_device_type) def update(self): """Update all Node data from Hive.""" self.session.core.update_data(self.node_id) - self.attributes = self.session.attributes.state_attributes( - self.node_id) + self.attributes = self.session.attributes.state_attributes(self.node_id) diff --git a/homeassistant/components/hive/climate.py b/homeassistant/components/hive/climate.py index dd7e0164367..d4a1c915518 100644 --- a/homeassistant/components/hive/climate.py +++ b/homeassistant/components/hive/climate.py @@ -1,22 +1,28 @@ """Support for the Hive climate devices.""" from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( - HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF, PRESET_BOOST, - SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE, PRESET_NONE) + HVAC_MODE_AUTO, + HVAC_MODE_HEAT, + HVAC_MODE_OFF, + PRESET_BOOST, + SUPPORT_PRESET_MODE, + SUPPORT_TARGET_TEMPERATURE, + PRESET_NONE, +) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS from . import DATA_HIVE, DOMAIN HIVE_TO_HASS_STATE = { - 'SCHEDULE': HVAC_MODE_AUTO, - 'MANUAL': HVAC_MODE_HEAT, - 'OFF': HVAC_MODE_OFF, + "SCHEDULE": HVAC_MODE_AUTO, + "MANUAL": HVAC_MODE_HEAT, + "OFF": HVAC_MODE_OFF, } HASS_TO_HIVE_STATE = { - HVAC_MODE_AUTO: 'SCHEDULE', - HVAC_MODE_HEAT: 'MANUAL', - HVAC_MODE_OFF: 'OFF', + HVAC_MODE_AUTO: "SCHEDULE", + HVAC_MODE_HEAT: "MANUAL", + HVAC_MODE_OFF: "OFF", } SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE @@ -48,9 +54,8 @@ class HiveClimateEntity(ClimateDevice): self.thermostat_node_id = hivedevice["Thermostat_NodeID"] self.session = hivesession self.attributes = {} - self.data_updatesource = '{}.{}'.format( - self.device_type, self.node_id) - self._unique_id = '{}-{}'.format(self.node_id, self.device_type) + self.data_updatesource = "{}.{}".format(self.device_type, self.node_id) + self._unique_id = "{}-{}".format(self.node_id, self.device_type) @property def unique_id(self): @@ -60,12 +65,7 @@ class HiveClimateEntity(ClimateDevice): @property def device_info(self): """Return device information.""" - return { - 'identifiers': { - (DOMAIN, self.unique_id) - }, - 'name': self.name - } + return {"identifiers": {(DOMAIN, self.unique_id)}, "name": self.name} @property def supported_features(self): @@ -74,7 +74,7 @@ class HiveClimateEntity(ClimateDevice): def handle_update(self, updatesource): """Handle the new update request.""" - if '{}.{}'.format(self.device_type, self.node_id) not in updatesource: + if "{}.{}".format(self.device_type, self.node_id) not in updatesource: self.schedule_update_ha_state() @property @@ -82,7 +82,7 @@ class HiveClimateEntity(ClimateDevice): """Return the name of the Climate device.""" friendly_name = "Heating" if self.node_name is not None: - friendly_name = '{} {}'.format(self.node_name, friendly_name) + friendly_name = "{} {}".format(self.node_name, friendly_name) return friendly_name @property @@ -160,8 +160,7 @@ class HiveClimateEntity(ClimateDevice): """Set new target temperature.""" new_temperature = kwargs.get(ATTR_TEMPERATURE) if new_temperature is not None: - self.session.heating.set_target_temperature( - self.node_id, new_temperature) + self.session.heating.set_target_temperature(self.node_id, new_temperature) for entity in self.session.entities: entity.handle_update(self.data_updatesource) @@ -185,4 +184,5 @@ class HiveClimateEntity(ClimateDevice): """Update all Node data from Hive.""" self.session.core.update_data(self.node_id) self.attributes = self.session.attributes.state_attributes( - self.thermostat_node_id) + self.thermostat_node_id + ) diff --git a/homeassistant/components/hive/light.py b/homeassistant/components/hive/light.py index 67331b12b35..5892e304379 100644 --- a/homeassistant/components/hive/light.py +++ b/homeassistant/components/hive/light.py @@ -1,7 +1,13 @@ """Support for the Hive lights.""" from homeassistant.components.light import ( - ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, SUPPORT_COLOR_TEMP, Light) + ATTR_BRIGHTNESS, + ATTR_COLOR_TEMP, + ATTR_HS_COLOR, + SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, + SUPPORT_COLOR_TEMP, + Light, +) import homeassistant.util.color as color_util from . import DATA_HIVE, DOMAIN @@ -27,9 +33,8 @@ class HiveDeviceLight(Light): self.light_device_type = hivedevice["Hive_Light_DeviceType"] self.session = hivesession self.attributes = {} - self.data_updatesource = '{}.{}'.format( - self.device_type, self.node_id) - self._unique_id = '{}-{}'.format(self.node_id, self.device_type) + self.data_updatesource = "{}.{}".format(self.device_type, self.node_id) + self._unique_id = "{}-{}".format(self.node_id, self.device_type) self.session.entities.append(self) @property @@ -40,16 +45,11 @@ class HiveDeviceLight(Light): @property def device_info(self): """Return device information.""" - return { - 'identifiers': { - (DOMAIN, self.unique_id) - }, - 'name': self.name - } + return {"identifiers": {(DOMAIN, self.unique_id)}, "name": self.name} def handle_update(self, updatesource): """Handle the new update request.""" - if '{}.{}'.format(self.device_type, self.node_id) not in updatesource: + if "{}.{}".format(self.device_type, self.node_id) not in updatesource: self.schedule_update_ha_state() @property @@ -70,22 +70,28 @@ class HiveDeviceLight(Light): @property def min_mireds(self): """Return the coldest color_temp that this light supports.""" - if self.light_device_type == "tuneablelight" \ - or self.light_device_type == "colourtuneablelight": + if ( + self.light_device_type == "tuneablelight" + or self.light_device_type == "colourtuneablelight" + ): return self.session.light.get_min_color_temp(self.node_id) @property def max_mireds(self): """Return the warmest color_temp that this light supports.""" - if self.light_device_type == "tuneablelight" \ - or self.light_device_type == "colourtuneablelight": + if ( + self.light_device_type == "tuneablelight" + or self.light_device_type == "colourtuneablelight" + ): return self.session.light.get_max_color_temp(self.node_id) @property def color_temp(self): """Return the CT color value in mireds.""" - if self.light_device_type == "tuneablelight" \ - or self.light_device_type == "colourtuneablelight": + if ( + self.light_device_type == "tuneablelight" + or self.light_device_type == "colourtuneablelight" + ): return self.session.light.get_color_temp(self.node_id) @property @@ -107,7 +113,7 @@ class HiveDeviceLight(Light): new_color = None if ATTR_BRIGHTNESS in kwargs: tmp_new_brightness = kwargs.get(ATTR_BRIGHTNESS) - percentage_brightness = ((tmp_new_brightness / 255) * 100) + percentage_brightness = (tmp_new_brightness / 255) * 100 new_brightness = int(round(percentage_brightness / 5.0) * 5.0) if new_brightness == 0: new_brightness = 5 @@ -120,9 +126,13 @@ class HiveDeviceLight(Light): saturation = int(get_new_color[1]) new_color = (hue, saturation, 100) - self.session.light.turn_on(self.node_id, self.light_device_type, - new_brightness, new_color_temp, - new_color) + self.session.light.turn_on( + self.node_id, + self.light_device_type, + new_brightness, + new_color_temp, + new_color, + ) for entity in self.session.entities: entity.handle_update(self.data_updatesource) @@ -140,15 +150,13 @@ class HiveDeviceLight(Light): if self.light_device_type == "warmwhitelight": supported_features = SUPPORT_BRIGHTNESS elif self.light_device_type == "tuneablelight": - supported_features = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP) + supported_features = SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP elif self.light_device_type == "colourtuneablelight": - supported_features = ( - SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_COLOR) + supported_features = SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_COLOR return supported_features def update(self): """Update all Node data from Hive.""" self.session.core.update_data(self.node_id) - self.attributes = self.session.attributes.state_attributes( - self.node_id) + self.attributes = self.session.attributes.state_attributes(self.node_id) diff --git a/homeassistant/components/hive/sensor.py b/homeassistant/components/hive/sensor.py index b8887d27409..dd3343633d8 100644 --- a/homeassistant/components/hive/sensor.py +++ b/homeassistant/components/hive/sensor.py @@ -5,13 +5,13 @@ from homeassistant.helpers.entity import Entity from . import DATA_HIVE, DOMAIN FRIENDLY_NAMES = { - 'Hub_OnlineStatus': 'Hive Hub Status', - 'Hive_OutsideTemperature': 'Outside Temperature', + "Hub_OnlineStatus": "Hive Hub Status", + "Hive_OutsideTemperature": "Outside Temperature", } DEVICETYPE_ICONS = { - 'Hub_OnlineStatus': 'mdi:switch', - 'Hive_OutsideTemperature': 'mdi:thermometer', + "Hub_OnlineStatus": "mdi:switch", + "Hive_OutsideTemperature": "mdi:thermometer", } @@ -21,8 +21,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return session = hass.data.get(DATA_HIVE) - if (discovery_info["HA_DeviceType"] == "Hub_OnlineStatus" or - discovery_info["HA_DeviceType"] == "Hive_OutsideTemperature"): + if ( + discovery_info["HA_DeviceType"] == "Hub_OnlineStatus" + or discovery_info["HA_DeviceType"] == "Hive_OutsideTemperature" + ): add_entities([HiveSensorEntity(session, discovery_info)]) @@ -35,9 +37,8 @@ class HiveSensorEntity(Entity): self.device_type = hivedevice["HA_DeviceType"] self.node_device_type = hivedevice["Hive_DeviceType"] self.session = hivesession - self.data_updatesource = '{}.{}'.format( - self.device_type, self.node_id) - self._unique_id = '{}-{}'.format(self.node_id, self.device_type) + self.data_updatesource = "{}.{}".format(self.device_type, self.node_id) + self._unique_id = "{}-{}".format(self.node_id, self.device_type) self.session.entities.append(self) @property @@ -48,16 +49,11 @@ class HiveSensorEntity(Entity): @property def device_info(self): """Return device information.""" - return { - 'identifiers': { - (DOMAIN, self.unique_id) - }, - 'name': self.name - } + return {"identifiers": {(DOMAIN, self.unique_id)}, "name": self.name} def handle_update(self, updatesource): """Handle the new update request.""" - if '{}.{}'.format(self.device_type, self.node_id) not in updatesource: + if "{}.{}".format(self.device_type, self.node_id) not in updatesource: self.schedule_update_ha_state() @property diff --git a/homeassistant/components/hive/switch.py b/homeassistant/components/hive/switch.py index ea4094d573c..4644ccaec00 100644 --- a/homeassistant/components/hive/switch.py +++ b/homeassistant/components/hive/switch.py @@ -23,9 +23,8 @@ class HiveDevicePlug(SwitchDevice): self.device_type = hivedevice["HA_DeviceType"] self.session = hivesession self.attributes = {} - self.data_updatesource = '{}.{}'.format( - self.device_type, self.node_id) - self._unique_id = '{}-{}'.format(self.node_id, self.device_type) + self.data_updatesource = "{}.{}".format(self.device_type, self.node_id) + self._unique_id = "{}-{}".format(self.node_id, self.device_type) self.session.entities.append(self) @property @@ -36,16 +35,11 @@ class HiveDevicePlug(SwitchDevice): @property def device_info(self): """Return device information.""" - return { - 'identifiers': { - (DOMAIN, self.unique_id) - }, - 'name': self.name - } + return {"identifiers": {(DOMAIN, self.unique_id)}, "name": self.name} def handle_update(self, updatesource): """Handle the new update request.""" - if '{}.{}'.format(self.device_type, self.node_id) not in updatesource: + if "{}.{}".format(self.device_type, self.node_id) not in updatesource: self.schedule_update_ha_state() @property @@ -83,5 +77,4 @@ class HiveDevicePlug(SwitchDevice): def update(self): """Update all Node data from Hive.""" self.session.core.update_data(self.node_id) - self.attributes = self.session.attributes.state_attributes( - self.node_id) + self.attributes = self.session.attributes.state_attributes(self.node_id) diff --git a/homeassistant/components/hive/water_heater.py b/homeassistant/components/hive/water_heater.py index 8f91e988170..f186d804d34 100644 --- a/homeassistant/components/hive/water_heater.py +++ b/homeassistant/components/hive/water_heater.py @@ -2,23 +2,20 @@ from homeassistant.const import TEMP_CELSIUS from homeassistant.components.water_heater import ( - STATE_ECO, STATE_ON, STATE_OFF, SUPPORT_OPERATION_MODE, WaterHeaterDevice) + STATE_ECO, + STATE_ON, + STATE_OFF, + SUPPORT_OPERATION_MODE, + WaterHeaterDevice, +) from . import DATA_HIVE, DOMAIN -SUPPORT_FLAGS_HEATER = (SUPPORT_OPERATION_MODE) +SUPPORT_FLAGS_HEATER = SUPPORT_OPERATION_MODE -HIVE_TO_HASS_STATE = { - 'SCHEDULE': STATE_ECO, - 'ON': STATE_ON, - 'OFF': STATE_OFF, -} +HIVE_TO_HASS_STATE = {"SCHEDULE": STATE_ECO, "ON": STATE_ON, "OFF": STATE_OFF} -HASS_TO_HIVE_STATE = { - STATE_ECO: 'SCHEDULE', - STATE_ON: 'ON', - STATE_OFF: 'OFF', -} +HASS_TO_HIVE_STATE = {STATE_ECO: "SCHEDULE", STATE_ON: "ON", STATE_OFF: "OFF"} SUPPORT_WATER_HEATER = [STATE_ECO, STATE_ON, STATE_OFF] @@ -45,9 +42,8 @@ class HiveWaterHeater(WaterHeaterDevice): self.node_name = hivedevice["Hive_NodeName"] self.device_type = hivedevice["HA_DeviceType"] self.session = hivesession - self.data_updatesource = '{}.{}'.format( - self.device_type, self.node_id) - self._unique_id = '{}-{}'.format(self.node_id, self.device_type) + self.data_updatesource = "{}.{}".format(self.device_type, self.node_id) + self._unique_id = "{}-{}".format(self.node_id, self.device_type) self._unit_of_measurement = TEMP_CELSIUS @property @@ -58,12 +54,7 @@ class HiveWaterHeater(WaterHeaterDevice): @property def device_info(self): """Return device information.""" - return { - 'identifiers': { - (DOMAIN, self.unique_id) - }, - 'name': self.name - } + return {"identifiers": {(DOMAIN, self.unique_id)}, "name": self.name} @property def supported_features(self): @@ -72,7 +63,7 @@ class HiveWaterHeater(WaterHeaterDevice): def handle_update(self, updatesource): """Handle the new update request.""" - if '{}.{}'.format(self.device_type, self.node_id) not in updatesource: + if "{}.{}".format(self.device_type, self.node_id) not in updatesource: self.schedule_update_ha_state() @property diff --git a/homeassistant/components/hlk_sw16/__init__.py b/homeassistant/components/hlk_sw16/__init__.py index 79de0bd18be..f174b00613b 100644 --- a/homeassistant/components/hlk_sw16/__init__.py +++ b/homeassistant/components/hlk_sw16/__init__.py @@ -4,49 +4,63 @@ import logging import voluptuous as vol from homeassistant.const import ( - CONF_HOST, CONF_PORT, - EVENT_HOMEASSISTANT_STOP, CONF_SWITCHES, CONF_NAME) + CONF_HOST, + CONF_PORT, + EVENT_HOMEASSISTANT_STOP, + CONF_SWITCHES, + CONF_NAME, +) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.dispatcher import ( - async_dispatcher_send, async_dispatcher_connect) + async_dispatcher_send, + async_dispatcher_connect, +) _LOGGER = logging.getLogger(__name__) -DATA_DEVICE_REGISTER = 'hlk_sw16_device_register' +DATA_DEVICE_REGISTER = "hlk_sw16_device_register" DEFAULT_RECONNECT_INTERVAL = 10 CONNECTION_TIMEOUT = 10 DEFAULT_PORT = 8080 -DOMAIN = 'hlk_sw16' +DOMAIN = "hlk_sw16" -SIGNAL_AVAILABILITY = 'hlk_sw16_device_available_{}' +SIGNAL_AVAILABILITY = "hlk_sw16_device_available_{}" -SWITCH_SCHEMA = vol.Schema({ - vol.Optional(CONF_NAME): cv.string, -}) +SWITCH_SCHEMA = vol.Schema({vol.Optional(CONF_NAME): cv.string}) RELAY_ID = vol.All( - vol.Any(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f'), - vol.Coerce(str)) + vol.Any(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "a", "b", "c", "d", "e", "f"), vol.Coerce(str) +) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - cv.string: vol.Schema({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Required(CONF_SWITCHES): vol.Schema({RELAY_ID: SWITCH_SCHEMA}), - }), - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + cv.string: vol.Schema( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Required(CONF_SWITCHES): vol.Schema( + {RELAY_ID: SWITCH_SCHEMA} + ), + } + ) + } + ) + }, + extra=vol.ALLOW_EXTRA, +) async def async_setup(hass, config): """Set up the HLK-SW16 switch.""" # Allow platform to specify function to register new unknown devices from hlk_sw16 import create_hlk_sw16_connection + hass.data[DATA_DEVICE_REGISTER] = {} def add_device(device): @@ -58,20 +72,18 @@ async def async_setup(hass, config): @callback def disconnected(): """Schedule reconnect after connection has been lost.""" - _LOGGER.warning('HLK-SW16 %s disconnected', device) - async_dispatcher_send(hass, SIGNAL_AVAILABILITY.format(device), - False) + _LOGGER.warning("HLK-SW16 %s disconnected", device) + async_dispatcher_send(hass, SIGNAL_AVAILABILITY.format(device), False) @callback def reconnected(): """Schedule reconnect after connection has been lost.""" - _LOGGER.warning('HLK-SW16 %s connected', device) - async_dispatcher_send(hass, SIGNAL_AVAILABILITY.format(device), - True) + _LOGGER.warning("HLK-SW16 %s connected", device) + async_dispatcher_send(hass, SIGNAL_AVAILABILITY.format(device), True) async def connect(): """Set up connection and hook it into HA for reconnect/shutdown.""" - _LOGGER.info('Initiating HLK-SW16 connection to %s', device) + _LOGGER.info("Initiating HLK-SW16 connection to %s", device) client = await create_hlk_sw16_connection( host=host, @@ -80,21 +92,22 @@ async def async_setup(hass, config): reconnect_callback=reconnected, loop=hass.loop, timeout=CONNECTION_TIMEOUT, - reconnect_interval=DEFAULT_RECONNECT_INTERVAL) + reconnect_interval=DEFAULT_RECONNECT_INTERVAL, + ) hass.data[DATA_DEVICE_REGISTER][device] = client # Load platforms hass.async_create_task( - async_load_platform(hass, 'switch', DOMAIN, - (switches, device), - config)) + async_load_platform(hass, "switch", DOMAIN, (switches, device), config) + ) # handle shutdown of HLK-SW16 asyncio transport - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, - lambda x: client.stop()) + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, lambda x: client.stop() + ) - _LOGGER.info('Connected to HLK-SW16 device: %s', device) + _LOGGER.info("Connected to HLK-SW16 device: %s", device) hass.loop.create_task(connect()) @@ -121,8 +134,7 @@ class SW16Device(Entity): @callback def handle_event_callback(self, event): """Propagate changes through ha.""" - _LOGGER.debug("Relay %s new state callback: %r", - self._device_port, event) + _LOGGER.debug("Relay %s new state callback: %r", self._device_port, event) self._is_on = event self.async_schedule_update_ha_state() @@ -148,9 +160,12 @@ class SW16Device(Entity): async def async_added_to_hass(self): """Register update callback.""" - self._client.register_status_callback(self.handle_event_callback, - self._device_port) + self._client.register_status_callback( + self.handle_event_callback, self._device_port + ) self._is_on = await self._client.status(self._device_port) - async_dispatcher_connect(self.hass, - SIGNAL_AVAILABILITY.format(self._device_id), - self._availability_callback) + async_dispatcher_connect( + self.hass, + SIGNAL_AVAILABILITY.format(self._device_id), + self._availability_callback, + ) diff --git a/homeassistant/components/hlk_sw16/switch.py b/homeassistant/components/hlk_sw16/switch.py index b7353f037c1..e9c190678a6 100644 --- a/homeassistant/components/hlk_sw16/switch.py +++ b/homeassistant/components/hlk_sw16/switch.py @@ -22,8 +22,7 @@ def devices_from_config(hass, domain_config): return devices -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the HLK-SW16 platform.""" async_add_entities(devices_from_config(hass, discovery_info)) diff --git a/homeassistant/components/homeassistant/__init__.py b/homeassistant/components/homeassistant/__init__.py index 2bcacb48bd1..2bd0a62cebb 100644 --- a/homeassistant/components/homeassistant/__init__.py +++ b/homeassistant/components/homeassistant/__init__.py @@ -12,24 +12,30 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.service import async_extract_entity_ids from homeassistant.helpers import intent from homeassistant.const import ( - ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, - SERVICE_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_RESTART, - RESTART_EXIT_CODE, ATTR_LATITUDE, ATTR_LONGITUDE) + ATTR_ENTITY_ID, + SERVICE_TURN_ON, + SERVICE_TURN_OFF, + SERVICE_TOGGLE, + SERVICE_HOMEASSISTANT_STOP, + SERVICE_HOMEASSISTANT_RESTART, + RESTART_EXIT_CODE, + ATTR_LATITUDE, + ATTR_LONGITUDE, +) from homeassistant.helpers import config_validation as cv _LOGGER = logging.getLogger(__name__) DOMAIN = ha.DOMAIN -SERVICE_RELOAD_CORE_CONFIG = 'reload_core_config' -SERVICE_CHECK_CONFIG = 'check_config' -SERVICE_UPDATE_ENTITY = 'update_entity' -SERVICE_SET_LOCATION = 'set_location' -SCHEMA_UPDATE_ENTITY = vol.Schema({ - ATTR_ENTITY_ID: cv.entity_ids -}) +SERVICE_RELOAD_CORE_CONFIG = "reload_core_config" +SERVICE_CHECK_CONFIG = "check_config" +SERVICE_UPDATE_ENTITY = "update_entity" +SERVICE_SET_LOCATION = "set_location" +SCHEMA_UPDATE_ENTITY = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids}) async def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]: """Set up general services related to Home Assistant.""" + async def async_handle_turn_service(service): """Handle calls to homeassistant.turn_on/off.""" entity_ids = await async_extract_entity_ids(hass, service) @@ -37,13 +43,14 @@ async def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]: # Generic turn on/off method requires entity id if not entity_ids: _LOGGER.error( - "homeassistant/%s cannot be called without entity_id", - service.service) + "homeassistant/%s cannot be called without entity_id", service.service + ) return # Group entity_ids by domain. groupby requires sorted data. - by_domain = it.groupby(sorted(entity_ids), - lambda item: ha.split_entity_id(item)[0]) + by_domain = it.groupby( + sorted(entity_ids), lambda item: ha.split_entity_id(item)[0] + ) tasks = [] @@ -62,24 +69,30 @@ async def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]: # ent_ids is a generator, convert it to a list. data[ATTR_ENTITY_ID] = list(ent_ids) - tasks.append(hass.services.async_call( - domain, service.service, data, blocking)) + tasks.append( + hass.services.async_call(domain, service.service, data, blocking) + ) await asyncio.wait(tasks) - hass.services.async_register( - ha.DOMAIN, SERVICE_TURN_OFF, async_handle_turn_service) - hass.services.async_register( - ha.DOMAIN, SERVICE_TURN_ON, async_handle_turn_service) - hass.services.async_register( - ha.DOMAIN, SERVICE_TOGGLE, async_handle_turn_service) - hass.helpers.intent.async_register(intent.ServiceIntentHandler( - intent.INTENT_TURN_ON, ha.DOMAIN, SERVICE_TURN_ON, "Turned {} on")) - hass.helpers.intent.async_register(intent.ServiceIntentHandler( - intent.INTENT_TURN_OFF, ha.DOMAIN, SERVICE_TURN_OFF, - "Turned {} off")) - hass.helpers.intent.async_register(intent.ServiceIntentHandler( - intent.INTENT_TOGGLE, ha.DOMAIN, SERVICE_TOGGLE, "Toggled {}")) + hass.services.async_register(ha.DOMAIN, SERVICE_TURN_OFF, async_handle_turn_service) + hass.services.async_register(ha.DOMAIN, SERVICE_TURN_ON, async_handle_turn_service) + hass.services.async_register(ha.DOMAIN, SERVICE_TOGGLE, async_handle_turn_service) + hass.helpers.intent.async_register( + intent.ServiceIntentHandler( + intent.INTENT_TURN_ON, ha.DOMAIN, SERVICE_TURN_ON, "Turned {} on" + ) + ) + hass.helpers.intent.async_register( + intent.ServiceIntentHandler( + intent.INTENT_TURN_OFF, ha.DOMAIN, SERVICE_TURN_OFF, "Turned {} off" + ) + ) + hass.helpers.intent.async_register( + intent.ServiceIntentHandler( + intent.INTENT_TOGGLE, ha.DOMAIN, SERVICE_TOGGLE, "Toggled {}" + ) + ) async def async_handle_core_service(call): """Service handler for handling core services.""" @@ -96,7 +109,9 @@ async def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]: _LOGGER.error(errors) hass.components.persistent_notification.async_create( "Config error. See dev-info panel for details.", - "Config validating", "{0}.check_config".format(ha.DOMAIN)) + "Config validating", + "{0}.check_config".format(ha.DOMAIN), + ) return if call.service == SERVICE_HOMEASSISTANT_RESTART: @@ -104,21 +119,29 @@ async def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]: async def async_handle_update_service(call): """Service handler for updating an entity.""" - tasks = [hass.helpers.entity_component.async_update_entity(entity) - for entity in call.data[ATTR_ENTITY_ID]] + tasks = [ + hass.helpers.entity_component.async_update_entity(entity) + for entity in call.data[ATTR_ENTITY_ID] + ] if tasks: await asyncio.wait(tasks) hass.services.async_register( - ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP, async_handle_core_service) + ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP, async_handle_core_service + ) hass.services.async_register( - ha.DOMAIN, SERVICE_HOMEASSISTANT_RESTART, async_handle_core_service) + ha.DOMAIN, SERVICE_HOMEASSISTANT_RESTART, async_handle_core_service + ) hass.services.async_register( - ha.DOMAIN, SERVICE_CHECK_CONFIG, async_handle_core_service) + ha.DOMAIN, SERVICE_CHECK_CONFIG, async_handle_core_service + ) hass.services.async_register( - ha.DOMAIN, SERVICE_UPDATE_ENTITY, async_handle_update_service, - schema=SCHEMA_UPDATE_ENTITY) + ha.DOMAIN, + SERVICE_UPDATE_ENTITY, + async_handle_update_service, + schema=SCHEMA_UPDATE_ENTITY, + ) async def async_handle_reload_config(call): """Service handler for reloading core config.""" @@ -129,8 +152,7 @@ async def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]: return # auth only processed during startup - await conf_util.async_process_ha_core_config( - hass, conf.get(ha.DOMAIN) or {}) + await conf_util.async_process_ha_core_config(hass, conf.get(ha.DOMAIN) or {}) hass.helpers.service.async_register_admin_service( ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG, async_handle_reload_config @@ -139,15 +161,14 @@ async def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]: async def async_set_location(call): """Service handler to set location.""" await hass.config.async_update( - latitude=call.data[ATTR_LATITUDE], - longitude=call.data[ATTR_LONGITUDE], + latitude=call.data[ATTR_LATITUDE], longitude=call.data[ATTR_LONGITUDE] ) hass.helpers.service.async_register_admin_service( - ha.DOMAIN, SERVICE_SET_LOCATION, async_set_location, vol.Schema({ - ATTR_LATITUDE: cv.latitude, - ATTR_LONGITUDE: cv.longitude, - }) + ha.DOMAIN, + SERVICE_SET_LOCATION, + async_set_location, + vol.Schema({ATTR_LATITUDE: cv.latitude, ATTR_LONGITUDE: cv.longitude}), ) return True diff --git a/homeassistant/components/homeassistant/scene.py b/homeassistant/components/homeassistant/scene.py index 617b5624110..de8a4dc88e7 100644 --- a/homeassistant/components/homeassistant/scene.py +++ b/homeassistant/components/homeassistant/scene.py @@ -4,39 +4,48 @@ from collections import namedtuple import voluptuous as vol from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_STATE, CONF_ENTITIES, CONF_NAME, CONF_PLATFORM, - STATE_OFF, STATE_ON) + ATTR_ENTITY_ID, + ATTR_STATE, + CONF_ENTITIES, + CONF_NAME, + CONF_PLATFORM, + STATE_OFF, + STATE_ON, +) from homeassistant.core import State import homeassistant.helpers.config_validation as cv from homeassistant.helpers.state import HASS_DOMAIN, async_reproduce_state from homeassistant.components.scene import STATES, Scene -PLATFORM_SCHEMA = vol.Schema({ - vol.Required(CONF_PLATFORM): HASS_DOMAIN, - vol.Required(STATES): vol.All( - cv.ensure_list, - [ - { - vol.Required(CONF_NAME): cv.string, - vol.Required(CONF_ENTITIES): { - cv.entity_id: vol.Any(str, bool, dict) - }, - } - ] - ), -}, extra=vol.ALLOW_EXTRA) +PLATFORM_SCHEMA = vol.Schema( + { + vol.Required(CONF_PLATFORM): HASS_DOMAIN, + vol.Required(STATES): vol.All( + cv.ensure_list, + [ + { + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_ENTITIES): { + cv.entity_id: vol.Any(str, bool, dict) + }, + } + ], + ), + }, + extra=vol.ALLOW_EXTRA, +) -SCENECONFIG = namedtuple('SceneConfig', [CONF_NAME, STATES]) +SCENECONFIG = namedtuple("SceneConfig", [CONF_NAME, STATES]) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up home assistant scene entries.""" scene_config = config.get(STATES) - async_add_entities(HomeAssistantScene( - hass, _process_config(scene)) for scene in scene_config) + async_add_entities( + HomeAssistantScene(hass, _process_config(scene)) for scene in scene_config + ) return True @@ -87,11 +96,8 @@ class HomeAssistantScene(Scene): @property def device_state_attributes(self): """Return the scene state attributes.""" - return { - ATTR_ENTITY_ID: list(self.scene_config.states.keys()), - } + return {ATTR_ENTITY_ID: list(self.scene_config.states.keys())} async def async_activate(self): """Activate scene. Try to get entities into requested state.""" - await async_reproduce_state( - self.hass, self.scene_config.states.values(), True) + await async_reproduce_state(self.hass, self.scene_config.states.values(), True) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 90fdde84f3b..d8aafb8e238 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -8,26 +8,57 @@ import voluptuous as vol from homeassistant.components import cover from homeassistant.components.media_player import DEVICE_CLASS_TV from homeassistant.const import ( - ATTR_DEVICE_CLASS, ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, - ATTR_UNIT_OF_MEASUREMENT, CONF_IP_ADDRESS, CONF_NAME, CONF_PORT, - CONF_TYPE, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, - DEVICE_CLASS_TEMPERATURE, EVENT_HOMEASSISTANT_START, - EVENT_HOMEASSISTANT_STOP, TEMP_CELSIUS, TEMP_FAHRENHEIT) + ATTR_DEVICE_CLASS, + ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, + ATTR_UNIT_OF_MEASUREMENT, + CONF_IP_ADDRESS, + CONF_NAME, + CONF_PORT, + CONF_TYPE, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_ILLUMINANCE, + DEVICE_CLASS_TEMPERATURE, + EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entityfilter import FILTER_SCHEMA from homeassistant.util import get_local_ip from homeassistant.util.decorator import Registry from .const import ( - BRIDGE_NAME, CONF_AUTO_START, CONF_ENTITY_CONFIG, CONF_FEATURE_LIST, - CONF_FILTER, CONF_SAFE_MODE, DEFAULT_AUTO_START, DEFAULT_PORT, - DEFAULT_SAFE_MODE, DEVICE_CLASS_CO, DEVICE_CLASS_CO2, DEVICE_CLASS_PM25, - DOMAIN, HOMEKIT_FILE, SERVICE_HOMEKIT_START, - SERVICE_HOMEKIT_RESET_ACCESSORY, TYPE_FAUCET, TYPE_OUTLET, TYPE_SHOWER, - TYPE_SPRINKLER, TYPE_SWITCH, TYPE_VALVE) + BRIDGE_NAME, + CONF_AUTO_START, + CONF_ENTITY_CONFIG, + CONF_FEATURE_LIST, + CONF_FILTER, + CONF_SAFE_MODE, + DEFAULT_AUTO_START, + DEFAULT_PORT, + DEFAULT_SAFE_MODE, + DEVICE_CLASS_CO, + DEVICE_CLASS_CO2, + DEVICE_CLASS_PM25, + DOMAIN, + HOMEKIT_FILE, + SERVICE_HOMEKIT_START, + SERVICE_HOMEKIT_RESET_ACCESSORY, + TYPE_FAUCET, + TYPE_OUTLET, + TYPE_SHOWER, + TYPE_SPRINKLER, + TYPE_SWITCH, + TYPE_VALVE, +) from .util import ( - show_setup_message, validate_entity_config, validate_media_player_features) + show_setup_message, + validate_entity_config, + validate_media_player_features, +) _LOGGER = logging.getLogger(__name__) @@ -41,35 +72,41 @@ STATUS_STOPPED = 2 STATUS_WAIT = 3 SWITCH_TYPES = { - TYPE_FAUCET: 'Valve', - TYPE_OUTLET: 'Outlet', - TYPE_SHOWER: 'Valve', - TYPE_SPRINKLER: 'Valve', - TYPE_SWITCH: 'Switch', - TYPE_VALVE: 'Valve'} + TYPE_FAUCET: "Valve", + TYPE_OUTLET: "Outlet", + TYPE_SHOWER: "Valve", + TYPE_SPRINKLER: "Valve", + TYPE_SWITCH: "Switch", + TYPE_VALVE: "Valve", +} -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.All({ - vol.Optional(CONF_NAME, default=BRIDGE_NAME): - vol.All(cv.string, vol.Length(min=3, max=25)), - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_IP_ADDRESS): - vol.All(ipaddress.ip_address, cv.string), - vol.Optional(CONF_AUTO_START, default=DEFAULT_AUTO_START): cv.boolean, - vol.Optional(CONF_SAFE_MODE, default=DEFAULT_SAFE_MODE): cv.boolean, - vol.Optional(CONF_FILTER, default={}): FILTER_SCHEMA, - vol.Optional(CONF_ENTITY_CONFIG, default={}): validate_entity_config, - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.All( + { + vol.Optional(CONF_NAME, default=BRIDGE_NAME): vol.All( + cv.string, vol.Length(min=3, max=25) + ), + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_IP_ADDRESS): vol.All(ipaddress.ip_address, cv.string), + vol.Optional(CONF_AUTO_START, default=DEFAULT_AUTO_START): cv.boolean, + vol.Optional(CONF_SAFE_MODE, default=DEFAULT_SAFE_MODE): cv.boolean, + vol.Optional(CONF_FILTER, default={}): FILTER_SCHEMA, + vol.Optional(CONF_ENTITY_CONFIG, default={}): validate_entity_config, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) -RESET_ACCESSORY_SERVICE_SCHEMA = vol.Schema({ - vol.Required(ATTR_ENTITY_ID): cv.entity_ids -}) +RESET_ACCESSORY_SERVICE_SCHEMA = vol.Schema( + {vol.Required(ATTR_ENTITY_ID): cv.entity_ids} +) async def async_setup(hass, config): """Set up the HomeKit component.""" - _LOGGER.debug('Begin setup HomeKit') + _LOGGER.debug("Begin setup HomeKit") conf = config[DOMAIN] name = conf[CONF_NAME] @@ -80,24 +117,29 @@ async def async_setup(hass, config): entity_filter = conf[CONF_FILTER] entity_config = conf[CONF_ENTITY_CONFIG] - homekit = HomeKit(hass, name, port, ip_address, entity_filter, - entity_config, safe_mode) + homekit = HomeKit( + hass, name, port, ip_address, entity_filter, entity_config, safe_mode + ) await hass.async_add_executor_job(homekit.setup) def handle_homekit_reset_accessory(service): """Handle start HomeKit service call.""" if homekit.status != STATUS_RUNNING: _LOGGER.warning( - 'HomeKit is not running. Either it is waiting to be ' - 'started or has been stopped.') + "HomeKit is not running. Either it is waiting to be " + "started or has been stopped." + ) return - entity_ids = service.data.get('entity_id') + entity_ids = service.data.get("entity_id") homekit.reset_accessories(entity_ids) - hass.services.async_register(DOMAIN, SERVICE_HOMEKIT_RESET_ACCESSORY, - handle_homekit_reset_accessory, - schema=RESET_ACCESSORY_SERVICE_SCHEMA) + hass.services.async_register( + DOMAIN, + SERVICE_HOMEKIT_RESET_ACCESSORY, + handle_homekit_reset_accessory, + schema=RESET_ACCESSORY_SERVICE_SCHEMA, + ) if auto_start: hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, homekit.start) @@ -107,13 +149,15 @@ async def async_setup(hass, config): """Handle start HomeKit service call.""" if homekit.status != STATUS_READY: _LOGGER.warning( - 'HomeKit is not ready. Either it is already running or has ' - 'been stopped.') + "HomeKit is not ready. Either it is already running or has " + "been stopped." + ) return homekit.start() - hass.services.async_register(DOMAIN, SERVICE_HOMEKIT_START, - handle_homekit_service_start) + hass.services.async_register( + DOMAIN, SERVICE_HOMEKIT_START, handle_homekit_service_start + ) return True @@ -121,85 +165,86 @@ async def async_setup(hass, config): def get_accessory(hass, driver, state, aid, config): """Take state and return an accessory object if supported.""" if not aid: - _LOGGER.warning('The entity "%s" is not supported, since it ' - 'generates an invalid aid, please change it.', - state.entity_id) + _LOGGER.warning( + 'The entity "%s" is not supported, since it ' + "generates an invalid aid, please change it.", + state.entity_id, + ) return None a_type = None name = config.get(CONF_NAME, state.name) - if state.domain == 'alarm_control_panel': - a_type = 'SecuritySystem' + if state.domain == "alarm_control_panel": + a_type = "SecuritySystem" - elif state.domain in ('binary_sensor', 'device_tracker', 'person'): - a_type = 'BinarySensor' + elif state.domain in ("binary_sensor", "device_tracker", "person"): + a_type = "BinarySensor" - elif state.domain == 'climate': - a_type = 'Thermostat' + elif state.domain == "climate": + a_type = "Thermostat" - elif state.domain == 'cover': + elif state.domain == "cover": device_class = state.attributes.get(ATTR_DEVICE_CLASS) features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) - if device_class == 'garage' and \ - features & (cover.SUPPORT_OPEN | cover.SUPPORT_CLOSE): - a_type = 'GarageDoorOpener' + if device_class == "garage" and features & ( + cover.SUPPORT_OPEN | cover.SUPPORT_CLOSE + ): + a_type = "GarageDoorOpener" elif features & cover.SUPPORT_SET_POSITION: - a_type = 'WindowCovering' + a_type = "WindowCovering" elif features & (cover.SUPPORT_OPEN | cover.SUPPORT_CLOSE): - a_type = 'WindowCoveringBasic' + a_type = "WindowCoveringBasic" - elif state.domain == 'fan': - a_type = 'Fan' + elif state.domain == "fan": + a_type = "Fan" - elif state.domain == 'light': - a_type = 'Light' + elif state.domain == "light": + a_type = "Light" - elif state.domain == 'lock': - a_type = 'Lock' + elif state.domain == "lock": + a_type = "Lock" - elif state.domain == 'media_player': + elif state.domain == "media_player": device_class = state.attributes.get(ATTR_DEVICE_CLASS) feature_list = config.get(CONF_FEATURE_LIST) if device_class == DEVICE_CLASS_TV: - a_type = 'TelevisionMediaPlayer' + a_type = "TelevisionMediaPlayer" else: - if feature_list and \ - validate_media_player_features(state, feature_list): - a_type = 'MediaPlayer' + if feature_list and validate_media_player_features(state, feature_list): + a_type = "MediaPlayer" - elif state.domain == 'sensor': + elif state.domain == "sensor": device_class = state.attributes.get(ATTR_DEVICE_CLASS) unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) - if device_class == DEVICE_CLASS_TEMPERATURE or \ - unit in (TEMP_CELSIUS, TEMP_FAHRENHEIT): - a_type = 'TemperatureSensor' - elif device_class == DEVICE_CLASS_HUMIDITY and unit == '%': - a_type = 'HumiditySensor' - elif device_class == DEVICE_CLASS_PM25 \ - or DEVICE_CLASS_PM25 in state.entity_id: - a_type = 'AirQualitySensor' + if device_class == DEVICE_CLASS_TEMPERATURE or unit in ( + TEMP_CELSIUS, + TEMP_FAHRENHEIT, + ): + a_type = "TemperatureSensor" + elif device_class == DEVICE_CLASS_HUMIDITY and unit == "%": + a_type = "HumiditySensor" + elif device_class == DEVICE_CLASS_PM25 or DEVICE_CLASS_PM25 in state.entity_id: + a_type = "AirQualitySensor" elif device_class == DEVICE_CLASS_CO: - a_type = 'CarbonMonoxideSensor' - elif device_class == DEVICE_CLASS_CO2 \ - or DEVICE_CLASS_CO2 in state.entity_id: - a_type = 'CarbonDioxideSensor' - elif device_class == DEVICE_CLASS_ILLUMINANCE or unit in ('lm', 'lx'): - a_type = 'LightSensor' + a_type = "CarbonMonoxideSensor" + elif device_class == DEVICE_CLASS_CO2 or DEVICE_CLASS_CO2 in state.entity_id: + a_type = "CarbonDioxideSensor" + elif device_class == DEVICE_CLASS_ILLUMINANCE or unit in ("lm", "lx"): + a_type = "LightSensor" - elif state.domain == 'switch': + elif state.domain == "switch": switch_type = config.get(CONF_TYPE, TYPE_SWITCH) a_type = SWITCH_TYPES[switch_type] - elif state.domain in ('automation', 'input_boolean', 'remote', 'scene', - 'script'): - a_type = 'Switch' + elif state.domain in ("automation", "input_boolean", "remote", "scene", "script"): + a_type = "Switch" - elif state.domain == 'water_heater': - a_type = 'WaterHeater' + elif state.domain == "water_heater": + a_type = "WaterHeater" if a_type is None: return None @@ -210,17 +255,18 @@ def get_accessory(hass, driver, state, aid, config): def generate_aid(entity_id): """Generate accessory aid with zlib adler32.""" - aid = adler32(entity_id.encode('utf-8')) + aid = adler32(entity_id.encode("utf-8")) if aid in (0, 1): return None return aid -class HomeKit(): +class HomeKit: """Class to handle all actions between HomeKit and Home Assistant.""" - def __init__(self, hass, name, port, ip_address, entity_filter, - entity_config, safe_mode): + def __init__( + self, hass, name, port, ip_address, entity_filter, entity_config, safe_mode + ): """Initialize a HomeKit object.""" self.hass = hass self._name = name @@ -238,16 +284,16 @@ class HomeKit(): """Set up bridge and accessory driver.""" from .accessories import HomeBridge, HomeDriver - self.hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_STOP, self.stop) + self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.stop) ip_addr = self._ip_address or get_local_ip() path = self.hass.config.path(HOMEKIT_FILE) - self.driver = HomeDriver(self.hass, address=ip_addr, - port=self._port, persist_file=path) + self.driver = HomeDriver( + self.hass, address=ip_addr, port=self._port, persist_file=path + ) self.bridge = HomeBridge(self.hass, self.driver, self._name) if self._safe_mode: - _LOGGER.debug('Safe_mode selected') + _LOGGER.debug("Safe_mode selected") self.driver.safe_mode = True def reset_accessories(self, entity_ids): @@ -256,8 +302,9 @@ class HomeKit(): for entity_id in entity_ids: aid = generate_aid(entity_id) if aid not in self.bridge.accessories: - _LOGGER.warning('Could not reset accessory. entity_id ' - 'not found %s', entity_id) + _LOGGER.warning( + "Could not reset accessory. entity_id " "not found %s", entity_id + ) continue acc = self.remove_bridge_accessory(aid) removed.append(acc) @@ -292,9 +339,16 @@ class HomeKit(): # pylint: disable=unused-import from . import ( # noqa F401 - type_covers, type_fans, type_lights, type_locks, - type_media_players, type_security_systems, type_sensors, - type_switches, type_thermostats) + type_covers, + type_fans, + type_lights, + type_locks, + type_media_players, + type_security_systems, + type_sensors, + type_switches, + type_thermostats, + ) for state in self.hass.states.all(): self.add_bridge_accessory(state) @@ -304,10 +358,12 @@ class HomeKit(): show_setup_message(self.hass, self.driver.state.pincode) if len(self.bridge.accessories) > MAX_DEVICES: - _LOGGER.warning('You have exceeded the device limit, which might ' - 'cause issues. Consider using the filter option.') + _LOGGER.warning( + "You have exceeded the device limit, which might " + "cause issues. Consider using the filter option." + ) - _LOGGER.debug('Driver start') + _LOGGER.debug("Driver start") self.hass.add_job(self.driver.start) self.status = STATUS_RUNNING @@ -317,5 +373,5 @@ class HomeKit(): return self.status = STATUS_STOPPED - _LOGGER.debug('Driver stop') + _LOGGER.debug("Driver stop") self.hass.add_job(self.driver.stop) diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index 13dfc90841f..84f0b7894c4 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -9,19 +9,35 @@ from pyhap.accessory_driver import AccessoryDriver from pyhap.const import CATEGORY_OTHER from homeassistant.const import ( - ATTR_BATTERY_CHARGING, ATTR_BATTERY_LEVEL, ATTR_ENTITY_ID, ATTR_SERVICE, - __version__) + ATTR_BATTERY_CHARGING, + ATTR_BATTERY_LEVEL, + ATTR_ENTITY_ID, + ATTR_SERVICE, + __version__, +) from homeassistant.core import callback as ha_callback, split_entity_id from homeassistant.helpers.event import ( - async_track_state_change, track_point_in_utc_time) + async_track_state_change, + track_point_in_utc_time, +) from homeassistant.util import dt as dt_util from .const import ( - ATTR_DISPLAY_NAME, ATTR_VALUE, BRIDGE_MODEL, BRIDGE_SERIAL_NUMBER, - CHAR_BATTERY_LEVEL, CHAR_CHARGING_STATE, CHAR_STATUS_LOW_BATTERY, - CONF_LINKED_BATTERY_SENSOR, CONF_LOW_BATTERY_THRESHOLD, DEBOUNCE_TIMEOUT, - DEFAULT_LOW_BATTERY_THRESHOLD, EVENT_HOMEKIT_CHANGED, MANUFACTURER, - SERV_BATTERY_SERVICE) + ATTR_DISPLAY_NAME, + ATTR_VALUE, + BRIDGE_MODEL, + BRIDGE_SERIAL_NUMBER, + CHAR_BATTERY_LEVEL, + CHAR_CHARGING_STATE, + CHAR_STATUS_LOW_BATTERY, + CONF_LINKED_BATTERY_SENSOR, + CONF_LOW_BATTERY_THRESHOLD, + DEBOUNCE_TIMEOUT, + DEFAULT_LOW_BATTERY_THRESHOLD, + EVENT_HOMEKIT_CHANGED, + MANUFACTURER, + SERV_BATTERY_SERVICE, +) from .util import convert_to_float, dismiss_setup_message, show_setup_message _LOGGER = logging.getLogger(__name__) @@ -29,6 +45,7 @@ _LOGGER = logging.getLogger(__name__) def debounce(func): """Decorate function to debounce callbacks from HomeKit.""" + @ha_callback def call_later_listener(self, *args): """Handle call_later callback.""" @@ -43,11 +60,14 @@ def debounce(func): if debounce_params: debounce_params[0]() # remove listener remove_listener = track_point_in_utc_time( - self.hass, partial(call_later_listener, self), - dt_util.utcnow() + timedelta(seconds=DEBOUNCE_TIMEOUT)) + self.hass, + partial(call_later_listener, self), + dt_util.utcnow() + timedelta(seconds=DEBOUNCE_TIMEOUT), + ) self.debounce[func.__name__] = (remove_listener, *args) - logger.debug('%s: Start %s timeout', self.entity_id, - func.__name__.replace('set_', '')) + logger.debug( + "%s: Start %s timeout", self.entity_id, func.__name__.replace("set_", "") + ) name = getmodule(func).__name__ logger = logging.getLogger(name) @@ -57,14 +77,18 @@ def debounce(func): class HomeAccessory(Accessory): """Adapter class for Accessory.""" - def __init__(self, hass, driver, name, entity_id, aid, config, - category=CATEGORY_OTHER): + def __init__( + self, hass, driver, name, entity_id, aid, config, category=CATEGORY_OTHER + ): """Initialize a Accessory object.""" super().__init__(driver, name, aid=aid) model = split_entity_id(entity_id)[0].replace("_", " ").title() self.set_info_service( - firmware_revision=__version__, manufacturer=MANUFACTURER, - model=model, serial_number=entity_id) + firmware_revision=__version__, + manufacturer=MANUFACTURER, + model=model, + serial_number=entity_id, + ) self.category = category self.config = config or {} self.entity_id = entity_id @@ -72,30 +96,28 @@ class HomeAccessory(Accessory): self.debounce = {} self._support_battery_level = False self._support_battery_charging = True - self.linked_battery_sensor = \ - self.config.get(CONF_LINKED_BATTERY_SENSOR) - self.low_battery_threshold = \ - self.config.get(CONF_LOW_BATTERY_THRESHOLD, - DEFAULT_LOW_BATTERY_THRESHOLD) + self.linked_battery_sensor = self.config.get(CONF_LINKED_BATTERY_SENSOR) + self.low_battery_threshold = self.config.get( + CONF_LOW_BATTERY_THRESHOLD, DEFAULT_LOW_BATTERY_THRESHOLD + ) """Add battery service if available""" - battery_found = self.hass.states.get(self.entity_id).attributes \ - .get(ATTR_BATTERY_LEVEL) + battery_found = self.hass.states.get(self.entity_id).attributes.get( + ATTR_BATTERY_LEVEL + ) if self.linked_battery_sensor: - battery_found = self.hass.states.get( - self.linked_battery_sensor).state + battery_found = self.hass.states.get(self.linked_battery_sensor).state if battery_found is None: return - _LOGGER.debug('%s: Found battery level', self.entity_id) + _LOGGER.debug("%s: Found battery level", self.entity_id) self._support_battery_level = True serv_battery = self.add_preload_service(SERV_BATTERY_SERVICE) - self._char_battery = serv_battery.configure_char( - CHAR_BATTERY_LEVEL, value=0) - self._char_charging = serv_battery.configure_char( - CHAR_CHARGING_STATE, value=2) + self._char_battery = serv_battery.configure_char(CHAR_BATTERY_LEVEL, value=0) + self._char_charging = serv_battery.configure_char(CHAR_CHARGING_STATE, value=2) self._char_low_battery = serv_battery.configure_char( - CHAR_STATUS_LOW_BATTERY, value=0) + CHAR_STATUS_LOW_BATTERY, value=0 + ) async def run(self): """Handle accessory driver started event. @@ -111,22 +133,21 @@ class HomeAccessory(Accessory): """ state = self.hass.states.get(self.entity_id) self.hass.async_add_job(self.update_state_callback, None, None, state) - async_track_state_change( - self.hass, self.entity_id, self.update_state_callback) + async_track_state_change(self.hass, self.entity_id, self.update_state_callback) if self.linked_battery_sensor: battery_state = self.hass.states.get(self.linked_battery_sensor) - self.hass.async_add_job(self.update_linked_battery, None, None, - battery_state) + self.hass.async_add_job( + self.update_linked_battery, None, None, battery_state + ) async_track_state_change( - self.hass, self.linked_battery_sensor, - self.update_linked_battery) + self.hass, self.linked_battery_sensor, self.update_linked_battery + ) @ha_callback - def update_state_callback(self, entity_id=None, old_state=None, - new_state=None): + def update_state_callback(self, entity_id=None, old_state=None, new_state=None): """Handle state change listener callback.""" - _LOGGER.debug('New_state: %s', new_state) + _LOGGER.debug("New_state: %s", new_state) if new_state is None: return if self._support_battery_level and not self.linked_battery_sensor: @@ -134,8 +155,7 @@ class HomeAccessory(Accessory): self.hass.async_add_executor_job(self.update_state, new_state) @ha_callback - def update_linked_battery(self, entity_id=None, old_state=None, - new_state=None): + def update_linked_battery(self, entity_id=None, old_state=None, new_state=None): """Handle linked battery sensor state change listener callback.""" self.hass.async_add_executor_job(self.update_battery, new_state) @@ -144,17 +164,14 @@ class HomeAccessory(Accessory): Only call this function if self._support_battery_level is True. """ - battery_level = convert_to_float( - new_state.attributes.get(ATTR_BATTERY_LEVEL)) + battery_level = convert_to_float(new_state.attributes.get(ATTR_BATTERY_LEVEL)) if self.linked_battery_sensor: battery_level = convert_to_float(new_state.state) if battery_level is None: return self._char_battery.set_value(battery_level) - self._char_low_battery.set_value( - battery_level < self.low_battery_threshold) - _LOGGER.debug('%s: Updated battery level to %d', self.entity_id, - battery_level) + self._char_low_battery.set_value(battery_level < self.low_battery_threshold) + _LOGGER.debug("%s: Updated battery level to %d", self.entity_id, battery_level) if not self._support_battery_charging: return charging = new_state.attributes.get(ATTR_BATTERY_CHARGING) @@ -163,8 +180,7 @@ class HomeAccessory(Accessory): return hk_charging = 1 if charging is True else 0 self._char_charging.set_value(hk_charging) - _LOGGER.debug('%s: Updated battery charging to %d', self.entity_id, - hk_charging) + _LOGGER.debug("%s: Updated battery charging to %d", self.entity_id, hk_charging) def update_state(self, new_state): """Handle state change to update HomeKit value. @@ -175,11 +191,9 @@ class HomeAccessory(Accessory): def call_service(self, domain, service, service_data, value=None): """Fire event and call service for changes from HomeKit.""" - self.hass.add_job( - self.async_call_service, domain, service, service_data, value) + self.hass.add_job(self.async_call_service, domain, service, service_data, value) - async def async_call_service(self, domain, service, service_data, - value=None): + async def async_call_service(self, domain, service, service_data, value=None): """Fire event and call service for changes from HomeKit. This method must be run in the event loop. @@ -188,7 +202,7 @@ class HomeAccessory(Accessory): ATTR_ENTITY_ID: self.entity_id, ATTR_DISPLAY_NAME: self.display_name, ATTR_SERVICE: service, - ATTR_VALUE: value + ATTR_VALUE: value, } self.hass.bus.async_fire(EVENT_HOMEKIT_CHANGED, event_data) @@ -202,8 +216,11 @@ class HomeBridge(Bridge): """Initialize a Bridge object.""" super().__init__(driver, name) self.set_info_service( - firmware_revision=__version__, manufacturer=MANUFACTURER, - model=BRIDGE_MODEL, serial_number=BRIDGE_SERIAL_NUMBER) + firmware_revision=__version__, + manufacturer=MANUFACTURER, + model=BRIDGE_MODEL, + serial_number=BRIDGE_SERIAL_NUMBER, + ) self.hass = hass def setup_message(self): diff --git a/homeassistant/components/homekit/const.py b/homeassistant/components/homekit/const.py index ce0659ddc73..d225225237f 100644 --- a/homeassistant/components/homekit/const.py +++ b/homeassistant/components/homekit/const.py @@ -1,23 +1,23 @@ """Constants used be the HomeKit component.""" # #### Misc #### DEBOUNCE_TIMEOUT = 0.5 -DOMAIN = 'homekit' -HOMEKIT_FILE = '.homekit.state' +DOMAIN = "homekit" +HOMEKIT_FILE = ".homekit.state" HOMEKIT_NOTIFY_ID = 4663548 # #### Attributes #### -ATTR_DISPLAY_NAME = 'display_name' -ATTR_VALUE = 'value' +ATTR_DISPLAY_NAME = "display_name" +ATTR_VALUE = "value" # #### Config #### -CONF_AUTO_START = 'auto_start' -CONF_ENTITY_CONFIG = 'entity_config' -CONF_FEATURE = 'feature' -CONF_FEATURE_LIST = 'feature_list' -CONF_FILTER = 'filter' -CONF_LINKED_BATTERY_SENSOR = 'linked_battery_sensor' -CONF_LOW_BATTERY_THRESHOLD = 'low_battery_threshold' -CONF_SAFE_MODE = 'safe_mode' +CONF_AUTO_START = "auto_start" +CONF_ENTITY_CONFIG = "entity_config" +CONF_FEATURE = "feature" +CONF_FEATURE_LIST = "feature_list" +CONF_FILTER = "filter" +CONF_LINKED_BATTERY_SENSOR = "linked_battery_sensor" +CONF_LOW_BATTERY_THRESHOLD = "low_battery_threshold" +CONF_SAFE_MODE = "safe_mode" # #### Config Defaults #### DEFAULT_AUTO_START = True @@ -26,146 +26,146 @@ DEFAULT_PORT = 51827 DEFAULT_SAFE_MODE = False # #### Features #### -FEATURE_ON_OFF = 'on_off' -FEATURE_PLAY_PAUSE = 'play_pause' -FEATURE_PLAY_STOP = 'play_stop' -FEATURE_TOGGLE_MUTE = 'toggle_mute' +FEATURE_ON_OFF = "on_off" +FEATURE_PLAY_PAUSE = "play_pause" +FEATURE_PLAY_STOP = "play_stop" +FEATURE_TOGGLE_MUTE = "toggle_mute" # #### HomeKit Component Event #### -EVENT_HOMEKIT_CHANGED = 'homekit_state_change' +EVENT_HOMEKIT_CHANGED = "homekit_state_change" # #### HomeKit Component Services #### -SERVICE_HOMEKIT_START = 'start' -SERVICE_HOMEKIT_RESET_ACCESSORY = 'reset_accessory' +SERVICE_HOMEKIT_START = "start" +SERVICE_HOMEKIT_RESET_ACCESSORY = "reset_accessory" # #### String Constants #### -BRIDGE_MODEL = 'Bridge' -BRIDGE_NAME = 'Home Assistant Bridge' -BRIDGE_SERIAL_NUMBER = 'homekit.bridge' -MANUFACTURER = 'Home Assistant' +BRIDGE_MODEL = "Bridge" +BRIDGE_NAME = "Home Assistant Bridge" +BRIDGE_SERIAL_NUMBER = "homekit.bridge" +MANUFACTURER = "Home Assistant" # #### Switch Types #### -TYPE_FAUCET = 'faucet' -TYPE_OUTLET = 'outlet' -TYPE_SHOWER = 'shower' -TYPE_SPRINKLER = 'sprinkler' -TYPE_SWITCH = 'switch' -TYPE_VALVE = 'valve' +TYPE_FAUCET = "faucet" +TYPE_OUTLET = "outlet" +TYPE_SHOWER = "shower" +TYPE_SPRINKLER = "sprinkler" +TYPE_SWITCH = "switch" +TYPE_VALVE = "valve" # #### Services #### -SERV_ACCESSORY_INFO = 'AccessoryInformation' -SERV_AIR_QUALITY_SENSOR = 'AirQualitySensor' -SERV_BATTERY_SERVICE = 'BatteryService' -SERV_CARBON_DIOXIDE_SENSOR = 'CarbonDioxideSensor' -SERV_CARBON_MONOXIDE_SENSOR = 'CarbonMonoxideSensor' -SERV_CONTACT_SENSOR = 'ContactSensor' -SERV_FANV2 = 'Fanv2' -SERV_GARAGE_DOOR_OPENER = 'GarageDoorOpener' -SERV_HUMIDITY_SENSOR = 'HumiditySensor' -SERV_INPUT_SOURCE = 'InputSource' -SERV_LEAK_SENSOR = 'LeakSensor' -SERV_LIGHT_SENSOR = 'LightSensor' -SERV_LIGHTBULB = 'Lightbulb' -SERV_LOCK = 'LockMechanism' -SERV_MOTION_SENSOR = 'MotionSensor' -SERV_OCCUPANCY_SENSOR = 'OccupancySensor' -SERV_OUTLET = 'Outlet' -SERV_SECURITY_SYSTEM = 'SecuritySystem' -SERV_SMOKE_SENSOR = 'SmokeSensor' -SERV_SWITCH = 'Switch' -SERV_TELEVISION = 'Television' -SERV_TELEVISION_SPEAKER = 'TelevisionSpeaker' -SERV_TEMPERATURE_SENSOR = 'TemperatureSensor' -SERV_THERMOSTAT = 'Thermostat' -SERV_VALVE = 'Valve' -SERV_WINDOW_COVERING = 'WindowCovering' +SERV_ACCESSORY_INFO = "AccessoryInformation" +SERV_AIR_QUALITY_SENSOR = "AirQualitySensor" +SERV_BATTERY_SERVICE = "BatteryService" +SERV_CARBON_DIOXIDE_SENSOR = "CarbonDioxideSensor" +SERV_CARBON_MONOXIDE_SENSOR = "CarbonMonoxideSensor" +SERV_CONTACT_SENSOR = "ContactSensor" +SERV_FANV2 = "Fanv2" +SERV_GARAGE_DOOR_OPENER = "GarageDoorOpener" +SERV_HUMIDITY_SENSOR = "HumiditySensor" +SERV_INPUT_SOURCE = "InputSource" +SERV_LEAK_SENSOR = "LeakSensor" +SERV_LIGHT_SENSOR = "LightSensor" +SERV_LIGHTBULB = "Lightbulb" +SERV_LOCK = "LockMechanism" +SERV_MOTION_SENSOR = "MotionSensor" +SERV_OCCUPANCY_SENSOR = "OccupancySensor" +SERV_OUTLET = "Outlet" +SERV_SECURITY_SYSTEM = "SecuritySystem" +SERV_SMOKE_SENSOR = "SmokeSensor" +SERV_SWITCH = "Switch" +SERV_TELEVISION = "Television" +SERV_TELEVISION_SPEAKER = "TelevisionSpeaker" +SERV_TEMPERATURE_SENSOR = "TemperatureSensor" +SERV_THERMOSTAT = "Thermostat" +SERV_VALVE = "Valve" +SERV_WINDOW_COVERING = "WindowCovering" # #### Characteristics #### -CHAR_ACTIVE = 'Active' -CHAR_ACTIVE_IDENTIFIER = 'ActiveIdentifier' -CHAR_AIR_PARTICULATE_DENSITY = 'AirParticulateDensity' -CHAR_AIR_QUALITY = 'AirQuality' -CHAR_BATTERY_LEVEL = 'BatteryLevel' -CHAR_BRIGHTNESS = 'Brightness' -CHAR_CARBON_DIOXIDE_DETECTED = 'CarbonDioxideDetected' -CHAR_CARBON_DIOXIDE_LEVEL = 'CarbonDioxideLevel' -CHAR_CARBON_DIOXIDE_PEAK_LEVEL = 'CarbonDioxidePeakLevel' -CHAR_CARBON_MONOXIDE_DETECTED = 'CarbonMonoxideDetected' -CHAR_CARBON_MONOXIDE_LEVEL = 'CarbonMonoxideLevel' -CHAR_CARBON_MONOXIDE_PEAK_LEVEL = 'CarbonMonoxidePeakLevel' -CHAR_CHARGING_STATE = 'ChargingState' -CHAR_COLOR_TEMPERATURE = 'ColorTemperature' -CHAR_CONFIGURED_NAME = 'ConfiguredName' -CHAR_CONTACT_SENSOR_STATE = 'ContactSensorState' -CHAR_COOLING_THRESHOLD_TEMPERATURE = 'CoolingThresholdTemperature' -CHAR_CURRENT_AMBIENT_LIGHT_LEVEL = 'CurrentAmbientLightLevel' -CHAR_CURRENT_DOOR_STATE = 'CurrentDoorState' -CHAR_CURRENT_HEATING_COOLING = 'CurrentHeatingCoolingState' -CHAR_CURRENT_POSITION = 'CurrentPosition' -CHAR_CURRENT_HUMIDITY = 'CurrentRelativeHumidity' -CHAR_CURRENT_SECURITY_STATE = 'SecuritySystemCurrentState' -CHAR_CURRENT_TEMPERATURE = 'CurrentTemperature' -CHAR_CURRENT_VISIBILITY_STATE = 'CurrentVisibilityState' -CHAR_FIRMWARE_REVISION = 'FirmwareRevision' -CHAR_HEATING_THRESHOLD_TEMPERATURE = 'HeatingThresholdTemperature' -CHAR_HUE = 'Hue' -CHAR_IDENTIFIER = 'Identifier' -CHAR_IN_USE = 'InUse' -CHAR_INPUT_SOURCE_TYPE = 'InputSourceType' -CHAR_IS_CONFIGURED = 'IsConfigured' -CHAR_LEAK_DETECTED = 'LeakDetected' -CHAR_LOCK_CURRENT_STATE = 'LockCurrentState' -CHAR_LOCK_TARGET_STATE = 'LockTargetState' -CHAR_LINK_QUALITY = 'LinkQuality' -CHAR_MANUFACTURER = 'Manufacturer' -CHAR_MODEL = 'Model' -CHAR_MOTION_DETECTED = 'MotionDetected' -CHAR_MUTE = 'Mute' -CHAR_NAME = 'Name' -CHAR_OCCUPANCY_DETECTED = 'OccupancyDetected' -CHAR_ON = 'On' -CHAR_OUTLET_IN_USE = 'OutletInUse' -CHAR_POSITION_STATE = 'PositionState' -CHAR_REMOTE_KEY = 'RemoteKey' -CHAR_ROTATION_DIRECTION = 'RotationDirection' -CHAR_ROTATION_SPEED = 'RotationSpeed' -CHAR_SATURATION = 'Saturation' -CHAR_SERIAL_NUMBER = 'SerialNumber' -CHAR_SLEEP_DISCOVER_MODE = 'SleepDiscoveryMode' -CHAR_SMOKE_DETECTED = 'SmokeDetected' -CHAR_STATUS_LOW_BATTERY = 'StatusLowBattery' -CHAR_SWING_MODE = 'SwingMode' -CHAR_TARGET_DOOR_STATE = 'TargetDoorState' -CHAR_TARGET_HEATING_COOLING = 'TargetHeatingCoolingState' -CHAR_TARGET_POSITION = 'TargetPosition' -CHAR_TARGET_SECURITY_STATE = 'SecuritySystemTargetState' -CHAR_TARGET_TEMPERATURE = 'TargetTemperature' -CHAR_TEMP_DISPLAY_UNITS = 'TemperatureDisplayUnits' -CHAR_VALVE_TYPE = 'ValveType' -CHAR_VOLUME = 'Volume' -CHAR_VOLUME_SELECTOR = 'VolumeSelector' -CHAR_VOLUME_CONTROL_TYPE = 'VolumeControlType' +CHAR_ACTIVE = "Active" +CHAR_ACTIVE_IDENTIFIER = "ActiveIdentifier" +CHAR_AIR_PARTICULATE_DENSITY = "AirParticulateDensity" +CHAR_AIR_QUALITY = "AirQuality" +CHAR_BATTERY_LEVEL = "BatteryLevel" +CHAR_BRIGHTNESS = "Brightness" +CHAR_CARBON_DIOXIDE_DETECTED = "CarbonDioxideDetected" +CHAR_CARBON_DIOXIDE_LEVEL = "CarbonDioxideLevel" +CHAR_CARBON_DIOXIDE_PEAK_LEVEL = "CarbonDioxidePeakLevel" +CHAR_CARBON_MONOXIDE_DETECTED = "CarbonMonoxideDetected" +CHAR_CARBON_MONOXIDE_LEVEL = "CarbonMonoxideLevel" +CHAR_CARBON_MONOXIDE_PEAK_LEVEL = "CarbonMonoxidePeakLevel" +CHAR_CHARGING_STATE = "ChargingState" +CHAR_COLOR_TEMPERATURE = "ColorTemperature" +CHAR_CONFIGURED_NAME = "ConfiguredName" +CHAR_CONTACT_SENSOR_STATE = "ContactSensorState" +CHAR_COOLING_THRESHOLD_TEMPERATURE = "CoolingThresholdTemperature" +CHAR_CURRENT_AMBIENT_LIGHT_LEVEL = "CurrentAmbientLightLevel" +CHAR_CURRENT_DOOR_STATE = "CurrentDoorState" +CHAR_CURRENT_HEATING_COOLING = "CurrentHeatingCoolingState" +CHAR_CURRENT_POSITION = "CurrentPosition" +CHAR_CURRENT_HUMIDITY = "CurrentRelativeHumidity" +CHAR_CURRENT_SECURITY_STATE = "SecuritySystemCurrentState" +CHAR_CURRENT_TEMPERATURE = "CurrentTemperature" +CHAR_CURRENT_VISIBILITY_STATE = "CurrentVisibilityState" +CHAR_FIRMWARE_REVISION = "FirmwareRevision" +CHAR_HEATING_THRESHOLD_TEMPERATURE = "HeatingThresholdTemperature" +CHAR_HUE = "Hue" +CHAR_IDENTIFIER = "Identifier" +CHAR_IN_USE = "InUse" +CHAR_INPUT_SOURCE_TYPE = "InputSourceType" +CHAR_IS_CONFIGURED = "IsConfigured" +CHAR_LEAK_DETECTED = "LeakDetected" +CHAR_LOCK_CURRENT_STATE = "LockCurrentState" +CHAR_LOCK_TARGET_STATE = "LockTargetState" +CHAR_LINK_QUALITY = "LinkQuality" +CHAR_MANUFACTURER = "Manufacturer" +CHAR_MODEL = "Model" +CHAR_MOTION_DETECTED = "MotionDetected" +CHAR_MUTE = "Mute" +CHAR_NAME = "Name" +CHAR_OCCUPANCY_DETECTED = "OccupancyDetected" +CHAR_ON = "On" +CHAR_OUTLET_IN_USE = "OutletInUse" +CHAR_POSITION_STATE = "PositionState" +CHAR_REMOTE_KEY = "RemoteKey" +CHAR_ROTATION_DIRECTION = "RotationDirection" +CHAR_ROTATION_SPEED = "RotationSpeed" +CHAR_SATURATION = "Saturation" +CHAR_SERIAL_NUMBER = "SerialNumber" +CHAR_SLEEP_DISCOVER_MODE = "SleepDiscoveryMode" +CHAR_SMOKE_DETECTED = "SmokeDetected" +CHAR_STATUS_LOW_BATTERY = "StatusLowBattery" +CHAR_SWING_MODE = "SwingMode" +CHAR_TARGET_DOOR_STATE = "TargetDoorState" +CHAR_TARGET_HEATING_COOLING = "TargetHeatingCoolingState" +CHAR_TARGET_POSITION = "TargetPosition" +CHAR_TARGET_SECURITY_STATE = "SecuritySystemTargetState" +CHAR_TARGET_TEMPERATURE = "TargetTemperature" +CHAR_TEMP_DISPLAY_UNITS = "TemperatureDisplayUnits" +CHAR_VALVE_TYPE = "ValveType" +CHAR_VOLUME = "Volume" +CHAR_VOLUME_SELECTOR = "VolumeSelector" +CHAR_VOLUME_CONTROL_TYPE = "VolumeControlType" # #### Properties #### -PROP_MAX_VALUE = 'maxValue' -PROP_MIN_VALUE = 'minValue' -PROP_MIN_STEP = 'minStep' -PROP_CELSIUS = {'minValue': -273, 'maxValue': 999} +PROP_MAX_VALUE = "maxValue" +PROP_MIN_VALUE = "minValue" +PROP_MIN_STEP = "minStep" +PROP_CELSIUS = {"minValue": -273, "maxValue": 999} # #### Device Classes #### -DEVICE_CLASS_CO = 'co' -DEVICE_CLASS_CO2 = 'co2' -DEVICE_CLASS_DOOR = 'door' -DEVICE_CLASS_GARAGE_DOOR = 'garage_door' -DEVICE_CLASS_GAS = 'gas' -DEVICE_CLASS_MOISTURE = 'moisture' -DEVICE_CLASS_MOTION = 'motion' -DEVICE_CLASS_OCCUPANCY = 'occupancy' -DEVICE_CLASS_OPENING = 'opening' -DEVICE_CLASS_PM25 = 'pm25' -DEVICE_CLASS_SMOKE = 'smoke' -DEVICE_CLASS_WINDOW = 'window' +DEVICE_CLASS_CO = "co" +DEVICE_CLASS_CO2 = "co2" +DEVICE_CLASS_DOOR = "door" +DEVICE_CLASS_GARAGE_DOOR = "garage_door" +DEVICE_CLASS_GAS = "gas" +DEVICE_CLASS_MOISTURE = "moisture" +DEVICE_CLASS_MOTION = "motion" +DEVICE_CLASS_OCCUPANCY = "occupancy" +DEVICE_CLASS_OPENING = "opening" +DEVICE_CLASS_PM25 = "pm25" +DEVICE_CLASS_SMOKE = "smoke" +DEVICE_CLASS_WINDOW = "window" # #### Thresholds #### THRESHOLD_CO = 25 diff --git a/homeassistant/components/homekit/type_covers.py b/homeassistant/components/homekit/type_covers.py index 5273480b6ce..3a33207e70e 100644 --- a/homeassistant/components/homekit/type_covers.py +++ b/homeassistant/components/homekit/type_covers.py @@ -4,23 +4,38 @@ import logging from pyhap.const import CATEGORY_GARAGE_DOOR_OPENER, CATEGORY_WINDOW_COVERING from homeassistant.components.cover import ( - ATTR_CURRENT_POSITION, ATTR_POSITION, DOMAIN, SUPPORT_STOP) + ATTR_CURRENT_POSITION, + ATTR_POSITION, + DOMAIN, + SUPPORT_STOP, +) from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, SERVICE_CLOSE_COVER, - SERVICE_OPEN_COVER, SERVICE_SET_COVER_POSITION, SERVICE_STOP_COVER, - STATE_CLOSED, STATE_OPEN) + ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, + SERVICE_CLOSE_COVER, + SERVICE_OPEN_COVER, + SERVICE_SET_COVER_POSITION, + SERVICE_STOP_COVER, + STATE_CLOSED, + STATE_OPEN, +) from . import TYPES from .accessories import HomeAccessory, debounce from .const import ( - CHAR_CURRENT_DOOR_STATE, CHAR_CURRENT_POSITION, CHAR_POSITION_STATE, - CHAR_TARGET_DOOR_STATE, CHAR_TARGET_POSITION, SERV_GARAGE_DOOR_OPENER, - SERV_WINDOW_COVERING) + CHAR_CURRENT_DOOR_STATE, + CHAR_CURRENT_POSITION, + CHAR_POSITION_STATE, + CHAR_TARGET_DOOR_STATE, + CHAR_TARGET_POSITION, + SERV_GARAGE_DOOR_OPENER, + SERV_WINDOW_COVERING, +) _LOGGER = logging.getLogger(__name__) -@TYPES.register('GarageDoorOpener') +@TYPES.register("GarageDoorOpener") class GarageDoorOpener(HomeAccessory): """Generate a Garage Door Opener accessory for a cover entity. @@ -35,13 +50,15 @@ class GarageDoorOpener(HomeAccessory): serv_garage_door = self.add_preload_service(SERV_GARAGE_DOOR_OPENER) self.char_current_state = serv_garage_door.configure_char( - CHAR_CURRENT_DOOR_STATE, value=0) + CHAR_CURRENT_DOOR_STATE, value=0 + ) self.char_target_state = serv_garage_door.configure_char( - CHAR_TARGET_DOOR_STATE, value=0, setter_callback=self.set_state) + CHAR_TARGET_DOOR_STATE, value=0, setter_callback=self.set_state + ) def set_state(self, value): """Change garage state if call came from HomeKit.""" - _LOGGER.debug('%s: Set state to %d', self.entity_id, value) + _LOGGER.debug("%s: Set state to %d", self.entity_id, value) self._flag_state = True params = {ATTR_ENTITY_ID: self.entity_id} @@ -65,7 +82,7 @@ class GarageDoorOpener(HomeAccessory): self._flag_state = False -@TYPES.register('WindowCovering') +@TYPES.register("WindowCovering") class WindowCovering(HomeAccessory): """Generate a Window accessory for a cover entity. @@ -79,14 +96,16 @@ class WindowCovering(HomeAccessory): serv_cover = self.add_preload_service(SERV_WINDOW_COVERING) self.char_current_position = serv_cover.configure_char( - CHAR_CURRENT_POSITION, value=0) + CHAR_CURRENT_POSITION, value=0 + ) self.char_target_position = serv_cover.configure_char( - CHAR_TARGET_POSITION, value=0, setter_callback=self.move_cover) + CHAR_TARGET_POSITION, value=0, setter_callback=self.move_cover + ) @debounce def move_cover(self, value): """Move cover to value if call came from HomeKit.""" - _LOGGER.debug('%s: Set position to %d', self.entity_id, value) + _LOGGER.debug("%s: Set position to %d", self.entity_id, value) self._homekit_target = value params = {ATTR_ENTITY_ID: self.entity_id, ATTR_POSITION: value} @@ -97,13 +116,15 @@ class WindowCovering(HomeAccessory): current_position = new_state.attributes.get(ATTR_CURRENT_POSITION) if isinstance(current_position, int): self.char_current_position.set_value(current_position) - if self._homekit_target is None or \ - abs(current_position - self._homekit_target) < 6: + if ( + self._homekit_target is None + or abs(current_position - self._homekit_target) < 6 + ): self.char_target_position.set_value(current_position) self._homekit_target = None -@TYPES.register('WindowCoveringBasic') +@TYPES.register("WindowCoveringBasic") class WindowCoveringBasic(HomeAccessory): """Generate a Window accessory for a cover entity. @@ -114,22 +135,26 @@ class WindowCoveringBasic(HomeAccessory): def __init__(self, *args): """Initialize a WindowCovering accessory object.""" super().__init__(*args, category=CATEGORY_WINDOW_COVERING) - features = self.hass.states.get(self.entity_id) \ - .attributes.get(ATTR_SUPPORTED_FEATURES) + features = self.hass.states.get(self.entity_id).attributes.get( + ATTR_SUPPORTED_FEATURES + ) self._supports_stop = features & SUPPORT_STOP serv_cover = self.add_preload_service(SERV_WINDOW_COVERING) self.char_current_position = serv_cover.configure_char( - CHAR_CURRENT_POSITION, value=0) + CHAR_CURRENT_POSITION, value=0 + ) self.char_target_position = serv_cover.configure_char( - CHAR_TARGET_POSITION, value=0, setter_callback=self.move_cover) + CHAR_TARGET_POSITION, value=0, setter_callback=self.move_cover + ) self.char_position_state = serv_cover.configure_char( - CHAR_POSITION_STATE, value=2) + CHAR_POSITION_STATE, value=2 + ) @debounce def move_cover(self, value): """Move cover to value if call came from HomeKit.""" - _LOGGER.debug('%s: Set position to %d', self.entity_id, value) + _LOGGER.debug("%s: Set position to %d", self.entity_id, value) if self._supports_stop: if value > 70: diff --git a/homeassistant/components/homekit/type_fans.py b/homeassistant/components/homekit/type_fans.py index d2777a296dc..e3fa6c42c58 100644 --- a/homeassistant/components/homekit/type_fans.py +++ b/homeassistant/components/homekit/type_fans.py @@ -4,25 +4,44 @@ import logging from pyhap.const import CATEGORY_FAN from homeassistant.components.fan import ( - ATTR_DIRECTION, ATTR_OSCILLATING, ATTR_SPEED, ATTR_SPEED_LIST, - DIRECTION_FORWARD, DIRECTION_REVERSE, DOMAIN, SERVICE_OSCILLATE, - SERVICE_SET_DIRECTION, SERVICE_SET_SPEED, SUPPORT_DIRECTION, - SUPPORT_OSCILLATE, SUPPORT_SET_SPEED) + ATTR_DIRECTION, + ATTR_OSCILLATING, + ATTR_SPEED, + ATTR_SPEED_LIST, + DIRECTION_FORWARD, + DIRECTION_REVERSE, + DOMAIN, + SERVICE_OSCILLATE, + SERVICE_SET_DIRECTION, + SERVICE_SET_SPEED, + SUPPORT_DIRECTION, + SUPPORT_OSCILLATE, + SUPPORT_SET_SPEED, +) from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, SERVICE_TURN_OFF, SERVICE_TURN_ON, - STATE_OFF, STATE_ON) + ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, +) from . import TYPES from .accessories import HomeAccessory, debounce from .const import ( - CHAR_ACTIVE, CHAR_ROTATION_DIRECTION, CHAR_ROTATION_SPEED, CHAR_SWING_MODE, - SERV_FANV2) + CHAR_ACTIVE, + CHAR_ROTATION_DIRECTION, + CHAR_ROTATION_SPEED, + CHAR_SWING_MODE, + SERV_FANV2, +) from .util import HomeKitSpeedMapping _LOGGER = logging.getLogger(__name__) -@TYPES.register('Fan') +@TYPES.register("Fan") class Fan(HomeAccessory): """Generate a Fan accessory for a fan entity. @@ -32,27 +51,32 @@ class Fan(HomeAccessory): def __init__(self, *args): """Initialize a new Light accessory object.""" super().__init__(*args, category=CATEGORY_FAN) - self._flag = {CHAR_ACTIVE: False, - CHAR_ROTATION_DIRECTION: False, - CHAR_SWING_MODE: False} + self._flag = { + CHAR_ACTIVE: False, + CHAR_ROTATION_DIRECTION: False, + CHAR_SWING_MODE: False, + } self._state = 0 chars = [] - features = self.hass.states.get(self.entity_id) \ - .attributes.get(ATTR_SUPPORTED_FEATURES) + features = self.hass.states.get(self.entity_id).attributes.get( + ATTR_SUPPORTED_FEATURES + ) if features & SUPPORT_DIRECTION: chars.append(CHAR_ROTATION_DIRECTION) if features & SUPPORT_OSCILLATE: chars.append(CHAR_SWING_MODE) if features & SUPPORT_SET_SPEED: - speed_list = self.hass.states.get(self.entity_id) \ - .attributes.get(ATTR_SPEED_LIST) + speed_list = self.hass.states.get(self.entity_id).attributes.get( + ATTR_SPEED_LIST + ) self.speed_mapping = HomeKitSpeedMapping(speed_list) chars.append(CHAR_ROTATION_SPEED) serv_fan = self.add_preload_service(SERV_FANV2, chars) self.char_active = serv_fan.configure_char( - CHAR_ACTIVE, value=0, setter_callback=self.set_state) + CHAR_ACTIVE, value=0, setter_callback=self.set_state + ) self.char_direction = None self.char_speed = None @@ -60,20 +84,22 @@ class Fan(HomeAccessory): if CHAR_ROTATION_DIRECTION in chars: self.char_direction = serv_fan.configure_char( - CHAR_ROTATION_DIRECTION, value=0, - setter_callback=self.set_direction) + CHAR_ROTATION_DIRECTION, value=0, setter_callback=self.set_direction + ) if CHAR_ROTATION_SPEED in chars: self.char_speed = serv_fan.configure_char( - CHAR_ROTATION_SPEED, value=0, setter_callback=self.set_speed) + CHAR_ROTATION_SPEED, value=0, setter_callback=self.set_speed + ) if CHAR_SWING_MODE in chars: self.char_swing = serv_fan.configure_char( - CHAR_SWING_MODE, value=0, setter_callback=self.set_oscillating) + CHAR_SWING_MODE, value=0, setter_callback=self.set_oscillating + ) def set_state(self, value): """Set state if call came from HomeKit.""" - _LOGGER.debug('%s: Set state to %d', self.entity_id, value) + _LOGGER.debug("%s: Set state to %d", self.entity_id, value) self._flag[CHAR_ACTIVE] = True service = SERVICE_TURN_ON if value == 1 else SERVICE_TURN_OFF params = {ATTR_ENTITY_ID: self.entity_id} @@ -81,7 +107,7 @@ class Fan(HomeAccessory): def set_direction(self, value): """Set state if call came from HomeKit.""" - _LOGGER.debug('%s: Set direction to %d', self.entity_id, value) + _LOGGER.debug("%s: Set direction to %d", self.entity_id, value) self._flag[CHAR_ROTATION_DIRECTION] = True direction = DIRECTION_REVERSE if value == 1 else DIRECTION_FORWARD params = {ATTR_ENTITY_ID: self.entity_id, ATTR_DIRECTION: direction} @@ -89,20 +115,18 @@ class Fan(HomeAccessory): def set_oscillating(self, value): """Set state if call came from HomeKit.""" - _LOGGER.debug('%s: Set oscillating to %d', self.entity_id, value) + _LOGGER.debug("%s: Set oscillating to %d", self.entity_id, value) self._flag[CHAR_SWING_MODE] = True oscillating = value == 1 - params = {ATTR_ENTITY_ID: self.entity_id, - ATTR_OSCILLATING: oscillating} + params = {ATTR_ENTITY_ID: self.entity_id, ATTR_OSCILLATING: oscillating} self.call_service(DOMAIN, SERVICE_OSCILLATE, params, oscillating) @debounce def set_speed(self, value): """Set state if call came from HomeKit.""" - _LOGGER.debug('%s: Set speed to %d', self.entity_id, value) + _LOGGER.debug("%s: Set speed to %d", self.entity_id, value) speed = self.speed_mapping.speed_to_states(value) - params = {ATTR_ENTITY_ID: self.entity_id, - ATTR_SPEED: speed} + params = {ATTR_ENTITY_ID: self.entity_id, ATTR_SPEED: speed} self.call_service(DOMAIN, SERVICE_SET_SPEED, params, speed) def update_state(self, new_state): @@ -111,16 +135,17 @@ class Fan(HomeAccessory): state = new_state.state if state in (STATE_ON, STATE_OFF): self._state = 1 if state == STATE_ON else 0 - if not self._flag[CHAR_ACTIVE] and \ - self.char_active.value != self._state: + if not self._flag[CHAR_ACTIVE] and self.char_active.value != self._state: self.char_active.set_value(self._state) self._flag[CHAR_ACTIVE] = False # Handle Direction if self.char_direction is not None: direction = new_state.attributes.get(ATTR_DIRECTION) - if not self._flag[CHAR_ROTATION_DIRECTION] and \ - direction in (DIRECTION_FORWARD, DIRECTION_REVERSE): + if not self._flag[CHAR_ROTATION_DIRECTION] and direction in ( + DIRECTION_FORWARD, + DIRECTION_REVERSE, + ): hk_direction = 1 if direction == DIRECTION_REVERSE else 0 if self.char_direction.value != hk_direction: self.char_direction.set_value(hk_direction) @@ -130,15 +155,13 @@ class Fan(HomeAccessory): if self.char_speed is not None: speed = new_state.attributes.get(ATTR_SPEED) hk_speed_value = self.speed_mapping.speed_to_homekit(speed) - if hk_speed_value is not None and \ - self.char_speed.value != hk_speed_value: + if hk_speed_value is not None and self.char_speed.value != hk_speed_value: self.char_speed.set_value(hk_speed_value) # Handle Oscillating if self.char_swing is not None: oscillating = new_state.attributes.get(ATTR_OSCILLATING) - if not self._flag[CHAR_SWING_MODE] and \ - oscillating in (True, False): + if not self._flag[CHAR_SWING_MODE] and oscillating in (True, False): hk_oscillating = 1 if oscillating else 0 if self.char_swing.value != hk_oscillating: self.char_swing.set_value(hk_oscillating) diff --git a/homeassistant/components/homekit/type_lights.py b/homeassistant/components/homekit/type_lights.py index f549958f755..fce81d0adf7 100644 --- a/homeassistant/components/homekit/type_lights.py +++ b/homeassistant/components/homekit/type_lights.py @@ -4,25 +4,45 @@ import logging from pyhap.const import CATEGORY_LIGHTBULB from homeassistant.components.light import ( - ATTR_BRIGHTNESS, ATTR_BRIGHTNESS_PCT, ATTR_COLOR_TEMP, ATTR_HS_COLOR, - ATTR_MAX_MIREDS, ATTR_MIN_MIREDS, DOMAIN, SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, SUPPORT_COLOR_TEMP) + ATTR_BRIGHTNESS, + ATTR_BRIGHTNESS_PCT, + ATTR_COLOR_TEMP, + ATTR_HS_COLOR, + ATTR_MAX_MIREDS, + ATTR_MIN_MIREDS, + DOMAIN, + SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, + SUPPORT_COLOR_TEMP, +) from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, SERVICE_TURN_OFF, SERVICE_TURN_ON, - STATE_OFF, STATE_ON) + ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, +) from . import TYPES from .accessories import HomeAccessory, debounce from .const import ( - CHAR_BRIGHTNESS, CHAR_COLOR_TEMPERATURE, CHAR_HUE, CHAR_ON, - CHAR_SATURATION, PROP_MAX_VALUE, PROP_MIN_VALUE, SERV_LIGHTBULB) + CHAR_BRIGHTNESS, + CHAR_COLOR_TEMPERATURE, + CHAR_HUE, + CHAR_ON, + CHAR_SATURATION, + PROP_MAX_VALUE, + PROP_MIN_VALUE, + SERV_LIGHTBULB, +) _LOGGER = logging.getLogger(__name__) -RGB_COLOR = 'rgb_color' +RGB_COLOR = "rgb_color" -@TYPES.register('Light') +@TYPES.register("Light") class Light(HomeAccessory): """Generate a Light accessory for a light entity. @@ -32,14 +52,20 @@ class Light(HomeAccessory): def __init__(self, *args): """Initialize a new Light accessory object.""" super().__init__(*args, category=CATEGORY_LIGHTBULB) - self._flag = {CHAR_ON: False, CHAR_BRIGHTNESS: False, - CHAR_HUE: False, CHAR_SATURATION: False, - CHAR_COLOR_TEMPERATURE: False, RGB_COLOR: False} + self._flag = { + CHAR_ON: False, + CHAR_BRIGHTNESS: False, + CHAR_HUE: False, + CHAR_SATURATION: False, + CHAR_COLOR_TEMPERATURE: False, + RGB_COLOR: False, + } self._state = 0 self.chars = [] - self._features = self.hass.states.get(self.entity_id) \ - .attributes.get(ATTR_SUPPORTED_FEATURES) + self._features = self.hass.states.get(self.entity_id).attributes.get( + ATTR_SUPPORTED_FEATURES + ) if self._features & SUPPORT_BRIGHTNESS: self.chars.append(CHAR_BRIGHTNESS) if self._features & SUPPORT_COLOR_TEMP: @@ -52,34 +78,41 @@ class Light(HomeAccessory): serv_light = self.add_preload_service(SERV_LIGHTBULB, self.chars) self.char_on = serv_light.configure_char( - CHAR_ON, value=self._state, setter_callback=self.set_state) + CHAR_ON, value=self._state, setter_callback=self.set_state + ) if CHAR_BRIGHTNESS in self.chars: self.char_brightness = serv_light.configure_char( - CHAR_BRIGHTNESS, value=0, setter_callback=self.set_brightness) + CHAR_BRIGHTNESS, value=0, setter_callback=self.set_brightness + ) if CHAR_COLOR_TEMPERATURE in self.chars: - min_mireds = self.hass.states.get(self.entity_id) \ - .attributes.get(ATTR_MIN_MIREDS, 153) - max_mireds = self.hass.states.get(self.entity_id) \ - .attributes.get(ATTR_MAX_MIREDS, 500) + min_mireds = self.hass.states.get(self.entity_id).attributes.get( + ATTR_MIN_MIREDS, 153 + ) + max_mireds = self.hass.states.get(self.entity_id).attributes.get( + ATTR_MAX_MIREDS, 500 + ) self.char_color_temperature = serv_light.configure_char( - CHAR_COLOR_TEMPERATURE, value=min_mireds, - properties={PROP_MIN_VALUE: min_mireds, - PROP_MAX_VALUE: max_mireds}, - setter_callback=self.set_color_temperature) + CHAR_COLOR_TEMPERATURE, + value=min_mireds, + properties={PROP_MIN_VALUE: min_mireds, PROP_MAX_VALUE: max_mireds}, + setter_callback=self.set_color_temperature, + ) if CHAR_HUE in self.chars: self.char_hue = serv_light.configure_char( - CHAR_HUE, value=0, setter_callback=self.set_hue) + CHAR_HUE, value=0, setter_callback=self.set_hue + ) if CHAR_SATURATION in self.chars: self.char_saturation = serv_light.configure_char( - CHAR_SATURATION, value=75, setter_callback=self.set_saturation) + CHAR_SATURATION, value=75, setter_callback=self.set_saturation + ) def set_state(self, value): """Set state if call came from HomeKit.""" if self._state == value: return - _LOGGER.debug('%s: Set state to %d', self.entity_id, value) + _LOGGER.debug("%s: Set state to %d", self.entity_id, value) self._flag[CHAR_ON] = True params = {ATTR_ENTITY_ID: self.entity_id} service = SERVICE_TURN_ON if value == 1 else SERVICE_TURN_OFF @@ -88,48 +121,55 @@ class Light(HomeAccessory): @debounce def set_brightness(self, value): """Set brightness if call came from HomeKit.""" - _LOGGER.debug('%s: Set brightness to %d', self.entity_id, value) + _LOGGER.debug("%s: Set brightness to %d", self.entity_id, value) self._flag[CHAR_BRIGHTNESS] = True if value == 0: self.set_state(0) # Turn off light return params = {ATTR_ENTITY_ID: self.entity_id, ATTR_BRIGHTNESS_PCT: value} - self.call_service(DOMAIN, SERVICE_TURN_ON, params, - 'brightness at {}%'.format(value)) + self.call_service( + DOMAIN, SERVICE_TURN_ON, params, "brightness at {}%".format(value) + ) def set_color_temperature(self, value): """Set color temperature if call came from HomeKit.""" - _LOGGER.debug('%s: Set color temp to %s', self.entity_id, value) + _LOGGER.debug("%s: Set color temp to %s", self.entity_id, value) self._flag[CHAR_COLOR_TEMPERATURE] = True params = {ATTR_ENTITY_ID: self.entity_id, ATTR_COLOR_TEMP: value} - self.call_service(DOMAIN, SERVICE_TURN_ON, params, - 'color temperature at {}'.format(value)) + self.call_service( + DOMAIN, SERVICE_TURN_ON, params, "color temperature at {}".format(value) + ) def set_saturation(self, value): """Set saturation if call came from HomeKit.""" - _LOGGER.debug('%s: Set saturation to %d', self.entity_id, value) + _LOGGER.debug("%s: Set saturation to %d", self.entity_id, value) self._flag[CHAR_SATURATION] = True self._saturation = value self.set_color() def set_hue(self, value): """Set hue if call came from HomeKit.""" - _LOGGER.debug('%s: Set hue to %d', self.entity_id, value) + _LOGGER.debug("%s: Set hue to %d", self.entity_id, value) self._flag[CHAR_HUE] = True self._hue = value self.set_color() def set_color(self): """Set color if call came from HomeKit.""" - if self._features & SUPPORT_COLOR and self._flag[CHAR_HUE] and \ - self._flag[CHAR_SATURATION]: + if ( + self._features & SUPPORT_COLOR + and self._flag[CHAR_HUE] + and self._flag[CHAR_SATURATION] + ): color = (self._hue, self._saturation) - _LOGGER.debug('%s: Set hs_color to %s', self.entity_id, color) - self._flag.update({ - CHAR_HUE: False, CHAR_SATURATION: False, RGB_COLOR: True}) + _LOGGER.debug("%s: Set hs_color to %s", self.entity_id, color) + self._flag.update( + {CHAR_HUE: False, CHAR_SATURATION: False, RGB_COLOR: True} + ) params = {ATTR_ENTITY_ID: self.entity_id, ATTR_HS_COLOR: color} - self.call_service(DOMAIN, SERVICE_TURN_ON, params, - 'set color at {}'.format(color)) + self.call_service( + DOMAIN, SERVICE_TURN_ON, params, "set color at {}".format(color) + ) def update_state(self, new_state): """Update light after state change.""" @@ -153,20 +193,23 @@ class Light(HomeAccessory): # Handle color temperature if CHAR_COLOR_TEMPERATURE in self.chars: color_temperature = new_state.attributes.get(ATTR_COLOR_TEMP) - if not self._flag[CHAR_COLOR_TEMPERATURE] \ - and isinstance(color_temperature, int) and \ - self.char_color_temperature.value != color_temperature: + if ( + not self._flag[CHAR_COLOR_TEMPERATURE] + and isinstance(color_temperature, int) + and self.char_color_temperature.value != color_temperature + ): self.char_color_temperature.set_value(color_temperature) self._flag[CHAR_COLOR_TEMPERATURE] = False # Handle Color if CHAR_SATURATION in self.chars and CHAR_HUE in self.chars: - hue, saturation = new_state.attributes.get( - ATTR_HS_COLOR, (None, None)) - if not self._flag[RGB_COLOR] and ( - hue != self._hue or saturation != self._saturation) and \ - isinstance(hue, (int, float)) and \ - isinstance(saturation, (int, float)): + hue, saturation = new_state.attributes.get(ATTR_HS_COLOR, (None, None)) + if ( + not self._flag[RGB_COLOR] + and (hue != self._hue or saturation != self._saturation) + and isinstance(hue, (int, float)) + and isinstance(saturation, (int, float)) + ): self.char_hue.set_value(hue) self.char_saturation.set_value(saturation) self._hue, self._saturation = (hue, saturation) diff --git a/homeassistant/components/homekit/type_locks.py b/homeassistant/components/homekit/type_locks.py index 007d7e1bc93..3a7211ab2ad 100644 --- a/homeassistant/components/homekit/type_locks.py +++ b/homeassistant/components/homekit/type_locks.py @@ -3,8 +3,7 @@ import logging from pyhap.const import CATEGORY_DOOR_LOCK -from homeassistant.components.lock import ( - DOMAIN, STATE_LOCKED, STATE_UNLOCKED) +from homeassistant.components.lock import DOMAIN, STATE_LOCKED, STATE_UNLOCKED from homeassistant.const import ATTR_CODE, ATTR_ENTITY_ID, STATE_UNKNOWN from . import TYPES @@ -22,13 +21,10 @@ HASS_TO_HOMEKIT = { HOMEKIT_TO_HASS = {c: s for s, c in HASS_TO_HOMEKIT.items()} -STATE_TO_SERVICE = { - STATE_LOCKED: 'lock', - STATE_UNLOCKED: 'unlock', -} +STATE_TO_SERVICE = {STATE_LOCKED: "lock", STATE_UNLOCKED: "unlock"} -@TYPES.register('Lock') +@TYPES.register("Lock") class Lock(HomeAccessory): """Generate a Lock accessory for a lock entity. @@ -43,11 +39,13 @@ class Lock(HomeAccessory): serv_lock_mechanism = self.add_preload_service(SERV_LOCK) self.char_current_state = serv_lock_mechanism.configure_char( - CHAR_LOCK_CURRENT_STATE, - value=HASS_TO_HOMEKIT[STATE_UNKNOWN]) + CHAR_LOCK_CURRENT_STATE, value=HASS_TO_HOMEKIT[STATE_UNKNOWN] + ) self.char_target_state = serv_lock_mechanism.configure_char( - CHAR_LOCK_TARGET_STATE, value=HASS_TO_HOMEKIT[STATE_LOCKED], - setter_callback=self.set_state) + CHAR_LOCK_TARGET_STATE, + value=HASS_TO_HOMEKIT[STATE_LOCKED], + setter_callback=self.set_state, + ) def set_state(self, value): """Set lock state to value if call came from HomeKit.""" @@ -68,8 +66,12 @@ class Lock(HomeAccessory): if hass_state in HASS_TO_HOMEKIT: current_lock_state = HASS_TO_HOMEKIT[hass_state] self.char_current_state.set_value(current_lock_state) - _LOGGER.debug("%s: Updated current state to %s (%d)", - self.entity_id, hass_state, current_lock_state) + _LOGGER.debug( + "%s: Updated current state to %s (%d)", + self.entity_id, + hass_state, + current_lock_state, + ) # LockTargetState only supports locked and unlocked if hass_state in (STATE_LOCKED, STATE_UNLOCKED): diff --git a/homeassistant/components/homekit/type_media_players.py b/homeassistant/components/homekit/type_media_players.py index b0c4be35e1b..d9b24782610 100644 --- a/homeassistant/components/homekit/type_media_players.py +++ b/homeassistant/components/homekit/type_media_players.py @@ -4,27 +4,66 @@ import logging from pyhap.const import CATEGORY_SWITCH, CATEGORY_TELEVISION from homeassistant.components.media_player import ( - ATTR_INPUT_SOURCE, ATTR_INPUT_SOURCE_LIST, ATTR_MEDIA_VOLUME_MUTED, - ATTR_MEDIA_VOLUME_LEVEL, SERVICE_SELECT_SOURCE, DOMAIN, SUPPORT_PAUSE, - SUPPORT_PLAY, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP, - SUPPORT_SELECT_SOURCE) + ATTR_INPUT_SOURCE, + ATTR_INPUT_SOURCE_LIST, + ATTR_MEDIA_VOLUME_MUTED, + ATTR_MEDIA_VOLUME_LEVEL, + SERVICE_SELECT_SOURCE, + DOMAIN, + SUPPORT_PAUSE, + SUPPORT_PLAY, + SUPPORT_VOLUME_MUTE, + SUPPORT_VOLUME_SET, + SUPPORT_VOLUME_STEP, + SUPPORT_SELECT_SOURCE, +) from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, SERVICE_MEDIA_PAUSE, - SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_STOP, - SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_VOLUME_MUTE, SERVICE_VOLUME_UP, - SERVICE_VOLUME_DOWN, SERVICE_VOLUME_SET, STATE_OFF, STATE_PLAYING, - STATE_PAUSED, STATE_UNKNOWN) + ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, + SERVICE_MEDIA_PAUSE, + SERVICE_MEDIA_PLAY, + SERVICE_MEDIA_PLAY_PAUSE, + SERVICE_MEDIA_STOP, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + SERVICE_VOLUME_MUTE, + SERVICE_VOLUME_UP, + SERVICE_VOLUME_DOWN, + SERVICE_VOLUME_SET, + STATE_OFF, + STATE_PLAYING, + STATE_PAUSED, + STATE_UNKNOWN, +) from . import TYPES from .accessories import HomeAccessory from .const import ( - CHAR_ACTIVE, CHAR_ACTIVE_IDENTIFIER, CHAR_CONFIGURED_NAME, - CHAR_CURRENT_VISIBILITY_STATE, CHAR_IDENTIFIER, CHAR_INPUT_SOURCE_TYPE, - CHAR_IS_CONFIGURED, CHAR_NAME, CHAR_SLEEP_DISCOVER_MODE, CHAR_MUTE, - CHAR_ON, CHAR_REMOTE_KEY, CHAR_VOLUME_CONTROL_TYPE, CHAR_VOLUME_SELECTOR, - CHAR_VOLUME, CONF_FEATURE_LIST, FEATURE_ON_OFF, FEATURE_PLAY_PAUSE, - FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE, SERV_SWITCH, SERV_TELEVISION, - SERV_TELEVISION_SPEAKER, SERV_INPUT_SOURCE) + CHAR_ACTIVE, + CHAR_ACTIVE_IDENTIFIER, + CHAR_CONFIGURED_NAME, + CHAR_CURRENT_VISIBILITY_STATE, + CHAR_IDENTIFIER, + CHAR_INPUT_SOURCE_TYPE, + CHAR_IS_CONFIGURED, + CHAR_NAME, + CHAR_SLEEP_DISCOVER_MODE, + CHAR_MUTE, + CHAR_ON, + CHAR_REMOTE_KEY, + CHAR_VOLUME_CONTROL_TYPE, + CHAR_VOLUME_SELECTOR, + CHAR_VOLUME, + CONF_FEATURE_LIST, + FEATURE_ON_OFF, + FEATURE_PLAY_PAUSE, + FEATURE_PLAY_STOP, + FEATURE_TOGGLE_MUTE, + SERV_SWITCH, + SERV_TELEVISION, + SERV_TELEVISION_SPEAKER, + SERV_INPUT_SOURCE, +) _LOGGER = logging.getLogger(__name__) @@ -45,24 +84,32 @@ MEDIA_PLAYER_KEYS = { } MODE_FRIENDLY_NAME = { - FEATURE_ON_OFF: 'Power', - FEATURE_PLAY_PAUSE: 'Play/Pause', - FEATURE_PLAY_STOP: 'Play/Stop', - FEATURE_TOGGLE_MUTE: 'Mute', + FEATURE_ON_OFF: "Power", + FEATURE_PLAY_PAUSE: "Play/Pause", + FEATURE_PLAY_STOP: "Play/Stop", + FEATURE_TOGGLE_MUTE: "Mute", } -@TYPES.register('MediaPlayer') +@TYPES.register("MediaPlayer") class MediaPlayer(HomeAccessory): """Generate a Media Player accessory.""" def __init__(self, *args): """Initialize a Switch accessory object.""" super().__init__(*args, category=CATEGORY_SWITCH) - self._flag = {FEATURE_ON_OFF: False, FEATURE_PLAY_PAUSE: False, - FEATURE_PLAY_STOP: False, FEATURE_TOGGLE_MUTE: False} - self.chars = {FEATURE_ON_OFF: None, FEATURE_PLAY_PAUSE: None, - FEATURE_PLAY_STOP: None, FEATURE_TOGGLE_MUTE: None} + self._flag = { + FEATURE_ON_OFF: False, + FEATURE_PLAY_PAUSE: False, + FEATURE_PLAY_STOP: False, + FEATURE_TOGGLE_MUTE: False, + } + self.chars = { + FEATURE_ON_OFF: None, + FEATURE_PLAY_PAUSE: None, + FEATURE_PLAY_STOP: None, + FEATURE_TOGGLE_MUTE: None, + } feature_list = self.config[CONF_FEATURE_LIST] if FEATURE_ON_OFF in feature_list: @@ -70,37 +117,40 @@ class MediaPlayer(HomeAccessory): serv_on_off = self.add_preload_service(SERV_SWITCH, CHAR_NAME) serv_on_off.configure_char(CHAR_NAME, value=name) self.chars[FEATURE_ON_OFF] = serv_on_off.configure_char( - CHAR_ON, value=False, setter_callback=self.set_on_off) + CHAR_ON, value=False, setter_callback=self.set_on_off + ) if FEATURE_PLAY_PAUSE in feature_list: name = self.generate_service_name(FEATURE_PLAY_PAUSE) serv_play_pause = self.add_preload_service(SERV_SWITCH, CHAR_NAME) serv_play_pause.configure_char(CHAR_NAME, value=name) self.chars[FEATURE_PLAY_PAUSE] = serv_play_pause.configure_char( - CHAR_ON, value=False, setter_callback=self.set_play_pause) + CHAR_ON, value=False, setter_callback=self.set_play_pause + ) if FEATURE_PLAY_STOP in feature_list: name = self.generate_service_name(FEATURE_PLAY_STOP) serv_play_stop = self.add_preload_service(SERV_SWITCH, CHAR_NAME) serv_play_stop.configure_char(CHAR_NAME, value=name) self.chars[FEATURE_PLAY_STOP] = serv_play_stop.configure_char( - CHAR_ON, value=False, setter_callback=self.set_play_stop) + CHAR_ON, value=False, setter_callback=self.set_play_stop + ) if FEATURE_TOGGLE_MUTE in feature_list: name = self.generate_service_name(FEATURE_TOGGLE_MUTE) serv_toggle_mute = self.add_preload_service(SERV_SWITCH, CHAR_NAME) serv_toggle_mute.configure_char(CHAR_NAME, value=name) self.chars[FEATURE_TOGGLE_MUTE] = serv_toggle_mute.configure_char( - CHAR_ON, value=False, setter_callback=self.set_toggle_mute) + CHAR_ON, value=False, setter_callback=self.set_toggle_mute + ) def generate_service_name(self, mode): """Generate name for individual service.""" - return '{} {}'.format(self.display_name, MODE_FRIENDLY_NAME[mode]) + return "{} {}".format(self.display_name, MODE_FRIENDLY_NAME[mode]) def set_on_off(self, value): """Move switch state to value if call came from HomeKit.""" - _LOGGER.debug('%s: Set switch state for "on_off" to %s', - self.entity_id, value) + _LOGGER.debug('%s: Set switch state for "on_off" to %s', self.entity_id, value) self._flag[FEATURE_ON_OFF] = True service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF params = {ATTR_ENTITY_ID: self.entity_id} @@ -108,8 +158,9 @@ class MediaPlayer(HomeAccessory): def set_play_pause(self, value): """Move switch state to value if call came from HomeKit.""" - _LOGGER.debug('%s: Set switch state for "play_pause" to %s', - self.entity_id, value) + _LOGGER.debug( + '%s: Set switch state for "play_pause" to %s', self.entity_id, value + ) self._flag[FEATURE_PLAY_PAUSE] = True service = SERVICE_MEDIA_PLAY if value else SERVICE_MEDIA_PAUSE params = {ATTR_ENTITY_ID: self.entity_id} @@ -117,8 +168,9 @@ class MediaPlayer(HomeAccessory): def set_play_stop(self, value): """Move switch state to value if call came from HomeKit.""" - _LOGGER.debug('%s: Set switch state for "play_stop" to %s', - self.entity_id, value) + _LOGGER.debug( + '%s: Set switch state for "play_stop" to %s', self.entity_id, value + ) self._flag[FEATURE_PLAY_STOP] = True service = SERVICE_MEDIA_PLAY if value else SERVICE_MEDIA_STOP params = {ATTR_ENTITY_ID: self.entity_id} @@ -126,11 +178,11 @@ class MediaPlayer(HomeAccessory): def set_toggle_mute(self, value): """Move switch state to value if call came from HomeKit.""" - _LOGGER.debug('%s: Set switch state for "toggle_mute" to %s', - self.entity_id, value) + _LOGGER.debug( + '%s: Set switch state for "toggle_mute" to %s', self.entity_id, value + ) self._flag[FEATURE_TOGGLE_MUTE] = True - params = {ATTR_ENTITY_ID: self.entity_id, - ATTR_MEDIA_VOLUME_MUTED: value} + params = {ATTR_ENTITY_ID: self.entity_id, ATTR_MEDIA_VOLUME_MUTED: value} self.call_service(DOMAIN, SERVICE_VOLUME_MUTE, params) def update_state(self, new_state): @@ -138,39 +190,49 @@ class MediaPlayer(HomeAccessory): current_state = new_state.state if self.chars[FEATURE_ON_OFF]: - hk_state = current_state not in (STATE_OFF, STATE_UNKNOWN, 'None') + hk_state = current_state not in (STATE_OFF, STATE_UNKNOWN, "None") if not self._flag[FEATURE_ON_OFF]: - _LOGGER.debug('%s: Set current state for "on_off" to %s', - self.entity_id, hk_state) + _LOGGER.debug( + '%s: Set current state for "on_off" to %s', self.entity_id, hk_state + ) self.chars[FEATURE_ON_OFF].set_value(hk_state) self._flag[FEATURE_ON_OFF] = False if self.chars[FEATURE_PLAY_PAUSE]: hk_state = current_state == STATE_PLAYING if not self._flag[FEATURE_PLAY_PAUSE]: - _LOGGER.debug('%s: Set current state for "play_pause" to %s', - self.entity_id, hk_state) + _LOGGER.debug( + '%s: Set current state for "play_pause" to %s', + self.entity_id, + hk_state, + ) self.chars[FEATURE_PLAY_PAUSE].set_value(hk_state) self._flag[FEATURE_PLAY_PAUSE] = False if self.chars[FEATURE_PLAY_STOP]: hk_state = current_state == STATE_PLAYING if not self._flag[FEATURE_PLAY_STOP]: - _LOGGER.debug('%s: Set current state for "play_stop" to %s', - self.entity_id, hk_state) + _LOGGER.debug( + '%s: Set current state for "play_stop" to %s', + self.entity_id, + hk_state, + ) self.chars[FEATURE_PLAY_STOP].set_value(hk_state) self._flag[FEATURE_PLAY_STOP] = False if self.chars[FEATURE_TOGGLE_MUTE]: current_state = new_state.attributes.get(ATTR_MEDIA_VOLUME_MUTED) if not self._flag[FEATURE_TOGGLE_MUTE]: - _LOGGER.debug('%s: Set current state for "toggle_mute" to %s', - self.entity_id, current_state) + _LOGGER.debug( + '%s: Set current state for "toggle_mute" to %s', + self.entity_id, + current_state, + ) self.chars[FEATURE_TOGGLE_MUTE].set_value(current_state) self._flag[FEATURE_TOGGLE_MUTE] = False -@TYPES.register('TelevisionMediaPlayer') +@TYPES.register("TelevisionMediaPlayer") class TelevisionMediaPlayer(HomeAccessory): """Generate a Television Media Player accessory.""" @@ -178,8 +240,11 @@ class TelevisionMediaPlayer(HomeAccessory): """Initialize a Switch accessory object.""" super().__init__(*args, category=CATEGORY_TELEVISION) - self._flag = {CHAR_ACTIVE: False, CHAR_ACTIVE_IDENTIFIER: False, - CHAR_MUTE: False} + self._flag = { + CHAR_ACTIVE: False, + CHAR_ACTIVE_IDENTIFIER: False, + CHAR_MUTE: False, + } self.support_select_source = False self.sources = [] @@ -187,15 +252,16 @@ class TelevisionMediaPlayer(HomeAccessory): # Add additional characteristics if volume or input selection supported self.chars_tv = [] self.chars_speaker = [] - features = self.hass.states.get(self.entity_id) \ - .attributes.get(ATTR_SUPPORTED_FEATURES, 0) + features = self.hass.states.get(self.entity_id).attributes.get( + ATTR_SUPPORTED_FEATURES, 0 + ) if features & (SUPPORT_PLAY | SUPPORT_PAUSE): self.chars_tv.append(CHAR_REMOTE_KEY) if features & SUPPORT_VOLUME_MUTE or features & SUPPORT_VOLUME_STEP: - self.chars_speaker.extend((CHAR_NAME, CHAR_ACTIVE, - CHAR_VOLUME_CONTROL_TYPE, - CHAR_VOLUME_SELECTOR)) + self.chars_speaker.extend( + (CHAR_NAME, CHAR_ACTIVE, CHAR_VOLUME_CONTROL_TYPE, CHAR_VOLUME_SELECTOR) + ) if features & SUPPORT_VOLUME_SET: self.chars_speaker.append(CHAR_VOLUME) @@ -207,60 +273,66 @@ class TelevisionMediaPlayer(HomeAccessory): serv_tv.configure_char(CHAR_CONFIGURED_NAME, value=self.display_name) serv_tv.configure_char(CHAR_SLEEP_DISCOVER_MODE, value=True) self.char_active = serv_tv.configure_char( - CHAR_ACTIVE, setter_callback=self.set_on_off) + CHAR_ACTIVE, setter_callback=self.set_on_off + ) if CHAR_REMOTE_KEY in self.chars_tv: self.char_remote_key = serv_tv.configure_char( - CHAR_REMOTE_KEY, setter_callback=self.set_remote_key) + CHAR_REMOTE_KEY, setter_callback=self.set_remote_key + ) if CHAR_VOLUME_SELECTOR in self.chars_speaker: serv_speaker = self.add_preload_service( - SERV_TELEVISION_SPEAKER, self.chars_speaker) + SERV_TELEVISION_SPEAKER, self.chars_speaker + ) serv_tv.add_linked_service(serv_speaker) - name = '{} {}'.format(self.display_name, 'Volume') + name = "{} {}".format(self.display_name, "Volume") serv_speaker.configure_char(CHAR_NAME, value=name) serv_speaker.configure_char(CHAR_ACTIVE, value=1) self.char_mute = serv_speaker.configure_char( - CHAR_MUTE, value=False, setter_callback=self.set_mute) + CHAR_MUTE, value=False, setter_callback=self.set_mute + ) volume_control_type = 1 if CHAR_VOLUME in self.chars_speaker else 2 - serv_speaker.configure_char(CHAR_VOLUME_CONTROL_TYPE, - value=volume_control_type) + serv_speaker.configure_char( + CHAR_VOLUME_CONTROL_TYPE, value=volume_control_type + ) self.char_volume_selector = serv_speaker.configure_char( - CHAR_VOLUME_SELECTOR, setter_callback=self.set_volume_step) + CHAR_VOLUME_SELECTOR, setter_callback=self.set_volume_step + ) if CHAR_VOLUME in self.chars_speaker: self.char_volume = serv_speaker.configure_char( - CHAR_VOLUME, setter_callback=self.set_volume) + CHAR_VOLUME, setter_callback=self.set_volume + ) if self.support_select_source: - self.sources = self.hass.states.get(self.entity_id) \ - .attributes.get(ATTR_INPUT_SOURCE_LIST, []) + self.sources = self.hass.states.get(self.entity_id).attributes.get( + ATTR_INPUT_SOURCE_LIST, [] + ) self.char_input_source = serv_tv.configure_char( - CHAR_ACTIVE_IDENTIFIER, setter_callback=self.set_input_source) + CHAR_ACTIVE_IDENTIFIER, setter_callback=self.set_input_source + ) for index, source in enumerate(self.sources): serv_input = self.add_preload_service( - SERV_INPUT_SOURCE, [CHAR_IDENTIFIER, CHAR_NAME]) + SERV_INPUT_SOURCE, [CHAR_IDENTIFIER, CHAR_NAME] + ) serv_tv.add_linked_service(serv_input) - serv_input.configure_char( - CHAR_CONFIGURED_NAME, value=source) + serv_input.configure_char(CHAR_CONFIGURED_NAME, value=source) serv_input.configure_char(CHAR_NAME, value=source) serv_input.configure_char(CHAR_IDENTIFIER, value=index) serv_input.configure_char(CHAR_IS_CONFIGURED, value=True) input_type = 3 if "hdmi" in source.lower() else 0 - serv_input.configure_char(CHAR_INPUT_SOURCE_TYPE, - value=input_type) - serv_input.configure_char(CHAR_CURRENT_VISIBILITY_STATE, - value=False) - _LOGGER.debug('%s: Added source %s.', self.entity_id, source) + serv_input.configure_char(CHAR_INPUT_SOURCE_TYPE, value=input_type) + serv_input.configure_char(CHAR_CURRENT_VISIBILITY_STATE, value=False) + _LOGGER.debug("%s: Added source %s.", self.entity_id, source) def set_on_off(self, value): """Move switch state to value if call came from HomeKit.""" - _LOGGER.debug('%s: Set switch state for "on_off" to %s', - self.entity_id, value) + _LOGGER.debug('%s: Set switch state for "on_off" to %s', self.entity_id, value) self._flag[CHAR_ACTIVE] = True service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF params = {ATTR_ENTITY_ID: self.entity_id} @@ -268,49 +340,48 @@ class TelevisionMediaPlayer(HomeAccessory): def set_mute(self, value): """Move switch state to value if call came from HomeKit.""" - _LOGGER.debug('%s: Set switch state for "toggle_mute" to %s', - self.entity_id, value) + _LOGGER.debug( + '%s: Set switch state for "toggle_mute" to %s', self.entity_id, value + ) self._flag[CHAR_MUTE] = True - params = {ATTR_ENTITY_ID: self.entity_id, - ATTR_MEDIA_VOLUME_MUTED: value} + params = {ATTR_ENTITY_ID: self.entity_id, ATTR_MEDIA_VOLUME_MUTED: value} self.call_service(DOMAIN, SERVICE_VOLUME_MUTE, params) def set_volume(self, value): """Send volume step value if call came from HomeKit.""" - _LOGGER.debug('%s: Set volume to %s', self.entity_id, value) - params = {ATTR_ENTITY_ID: self.entity_id, - ATTR_MEDIA_VOLUME_LEVEL: value} + _LOGGER.debug("%s: Set volume to %s", self.entity_id, value) + params = {ATTR_ENTITY_ID: self.entity_id, ATTR_MEDIA_VOLUME_LEVEL: value} self.call_service(DOMAIN, SERVICE_VOLUME_SET, params) def set_volume_step(self, value): """Send volume step value if call came from HomeKit.""" - _LOGGER.debug('%s: Step volume by %s', - self.entity_id, value) + _LOGGER.debug("%s: Step volume by %s", self.entity_id, value) service = SERVICE_VOLUME_DOWN if value else SERVICE_VOLUME_UP params = {ATTR_ENTITY_ID: self.entity_id} self.call_service(DOMAIN, service, params) def set_input_source(self, value): """Send input set value if call came from HomeKit.""" - _LOGGER.debug('%s: Set current input to %s', - self.entity_id, value) + _LOGGER.debug("%s: Set current input to %s", self.entity_id, value) source = self.sources[value] self._flag[CHAR_ACTIVE_IDENTIFIER] = True - params = {ATTR_ENTITY_ID: self.entity_id, - ATTR_INPUT_SOURCE: source} + params = {ATTR_ENTITY_ID: self.entity_id, ATTR_INPUT_SOURCE: source} self.call_service(DOMAIN, SERVICE_SELECT_SOURCE, params) def set_remote_key(self, value): """Send remote key value if call came from HomeKit.""" - _LOGGER.debug('%s: Set remote key to %s', self.entity_id, value) + _LOGGER.debug("%s: Set remote key to %s", self.entity_id, value) service = MEDIA_PLAYER_KEYS.get(value) if service: # Handle Play Pause if service == SERVICE_MEDIA_PLAY_PAUSE: state = self.hass.states.get(self.entity_id).state if state in (STATE_PLAYING, STATE_PAUSED): - service = SERVICE_MEDIA_PLAY if state == STATE_PAUSED \ + service = ( + SERVICE_MEDIA_PLAY + if state == STATE_PAUSED else SERVICE_MEDIA_PAUSE + ) params = {ATTR_ENTITY_ID: self.entity_id} self.call_service(DOMAIN, service, params) @@ -321,18 +392,21 @@ class TelevisionMediaPlayer(HomeAccessory): # Power state television hk_state = current_state not in (STATE_OFF, STATE_UNKNOWN) if not self._flag[CHAR_ACTIVE]: - _LOGGER.debug('%s: Set current active state to %s', - self.entity_id, hk_state) + _LOGGER.debug( + "%s: Set current active state to %s", self.entity_id, hk_state + ) self.char_active.set_value(hk_state) self._flag[CHAR_ACTIVE] = False # Set mute state if CHAR_VOLUME_SELECTOR in self.chars_speaker: - current_mute_state = new_state.attributes.get( - ATTR_MEDIA_VOLUME_MUTED) + current_mute_state = new_state.attributes.get(ATTR_MEDIA_VOLUME_MUTED) if not self._flag[CHAR_MUTE]: - _LOGGER.debug('%s: Set current mute state to %s', - self.entity_id, current_mute_state) + _LOGGER.debug( + "%s: Set current mute state to %s", + self.entity_id, + current_mute_state, + ) self.char_mute.set_value(current_mute_state) self._flag[CHAR_MUTE] = False @@ -340,13 +414,16 @@ class TelevisionMediaPlayer(HomeAccessory): if self.support_select_source: source_name = new_state.attributes.get(ATTR_INPUT_SOURCE) if self.sources and not self._flag[CHAR_ACTIVE_IDENTIFIER]: - _LOGGER.debug('%s: Set current input to %s', self.entity_id, - source_name) + _LOGGER.debug( + "%s: Set current input to %s", self.entity_id, source_name + ) if source_name in self.sources: index = self.sources.index(source_name) self.char_input_source.set_value(index) else: - _LOGGER.warning('%s: Sources out of sync. ' - 'Restart HomeAssistant', self.entity_id) + _LOGGER.warning( + "%s: Sources out of sync. " "Restart HomeAssistant", + self.entity_id, + ) self.char_input_source.set_value(0) self._flag[CHAR_ACTIVE_IDENTIFIER] = False diff --git a/homeassistant/components/homekit/type_security_systems.py b/homeassistant/components/homekit/type_security_systems.py index 10befb4af61..345709eb7da 100644 --- a/homeassistant/components/homekit/type_security_systems.py +++ b/homeassistant/components/homekit/type_security_systems.py @@ -5,16 +5,26 @@ from pyhap.const import CATEGORY_ALARM_SYSTEM from homeassistant.components.alarm_control_panel import DOMAIN from homeassistant.const import ( - ATTR_CODE, ATTR_ENTITY_ID, SERVICE_ALARM_ARM_AWAY, SERVICE_ALARM_ARM_HOME, - SERVICE_ALARM_ARM_NIGHT, SERVICE_ALARM_DISARM, STATE_ALARM_ARMED_AWAY, - STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED, - STATE_ALARM_TRIGGERED) + ATTR_CODE, + ATTR_ENTITY_ID, + SERVICE_ALARM_ARM_AWAY, + SERVICE_ALARM_ARM_HOME, + SERVICE_ALARM_ARM_NIGHT, + SERVICE_ALARM_DISARM, + STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_HOME, + STATE_ALARM_ARMED_NIGHT, + STATE_ALARM_DISARMED, + STATE_ALARM_TRIGGERED, +) from . import TYPES from .accessories import HomeAccessory from .const import ( - CHAR_CURRENT_SECURITY_STATE, CHAR_TARGET_SECURITY_STATE, - SERV_SECURITY_SYSTEM) + CHAR_CURRENT_SECURITY_STATE, + CHAR_TARGET_SECURITY_STATE, + SERV_SECURITY_SYSTEM, +) _LOGGER = logging.getLogger(__name__) @@ -36,7 +46,7 @@ STATE_TO_SERVICE = { } -@TYPES.register('SecuritySystem') +@TYPES.register("SecuritySystem") class SecuritySystem(HomeAccessory): """Generate an SecuritySystem accessory for an alarm control panel.""" @@ -48,15 +58,15 @@ class SecuritySystem(HomeAccessory): serv_alarm = self.add_preload_service(SERV_SECURITY_SYSTEM) self.char_current_state = serv_alarm.configure_char( - CHAR_CURRENT_SECURITY_STATE, value=3) + CHAR_CURRENT_SECURITY_STATE, value=3 + ) self.char_target_state = serv_alarm.configure_char( - CHAR_TARGET_SECURITY_STATE, value=3, - setter_callback=self.set_security_state) + CHAR_TARGET_SECURITY_STATE, value=3, setter_callback=self.set_security_state + ) def set_security_state(self, value): """Move security state to value if call came from HomeKit.""" - _LOGGER.debug('%s: Set security state to %d', - self.entity_id, value) + _LOGGER.debug("%s: Set security state to %d", self.entity_id, value) self._flag_state = True hass_value = HOMEKIT_TO_HASS[value] service = STATE_TO_SERVICE[hass_value] @@ -72,11 +82,14 @@ class SecuritySystem(HomeAccessory): if hass_state in HASS_TO_HOMEKIT: current_security_state = HASS_TO_HOMEKIT[hass_state] self.char_current_state.set_value(current_security_state) - _LOGGER.debug('%s: Updated current state to %s (%d)', - self.entity_id, hass_state, current_security_state) + _LOGGER.debug( + "%s: Updated current state to %s (%d)", + self.entity_id, + hass_state, + current_security_state, + ) # SecuritySystemTargetState does not support triggered - if not self._flag_state and \ - hass_state != STATE_ALARM_TRIGGERED: + if not self._flag_state and hass_state != STATE_ALARM_TRIGGERED: self.char_target_state.set_value(current_security_state) self._flag_state = False diff --git a/homeassistant/components/homekit/type_sensors.py b/homeassistant/components/homekit/type_sensors.py index 0d7dd94d014..87c8d5247a5 100644 --- a/homeassistant/components/homekit/type_sensors.py +++ b/homeassistant/components/homekit/type_sensors.py @@ -4,39 +4,66 @@ import logging from pyhap.const import CATEGORY_SENSOR from homeassistant.const import ( - ATTR_DEVICE_CLASS, ATTR_UNIT_OF_MEASUREMENT, STATE_HOME, STATE_ON, - TEMP_CELSIUS) + ATTR_DEVICE_CLASS, + ATTR_UNIT_OF_MEASUREMENT, + STATE_HOME, + STATE_ON, + TEMP_CELSIUS, +) from . import TYPES from .accessories import HomeAccessory from .const import ( - CHAR_AIR_PARTICULATE_DENSITY, CHAR_AIR_QUALITY, - CHAR_CARBON_DIOXIDE_DETECTED, CHAR_CARBON_DIOXIDE_LEVEL, - CHAR_CARBON_DIOXIDE_PEAK_LEVEL, CHAR_CARBON_MONOXIDE_DETECTED, - CHAR_CARBON_MONOXIDE_LEVEL, CHAR_CARBON_MONOXIDE_PEAK_LEVEL, - CHAR_CONTACT_SENSOR_STATE, CHAR_CURRENT_AMBIENT_LIGHT_LEVEL, - CHAR_CURRENT_HUMIDITY, CHAR_CURRENT_TEMPERATURE, CHAR_LEAK_DETECTED, - CHAR_MOTION_DETECTED, CHAR_OCCUPANCY_DETECTED, CHAR_SMOKE_DETECTED, - DEVICE_CLASS_CO2, DEVICE_CLASS_DOOR, DEVICE_CLASS_GARAGE_DOOR, - DEVICE_CLASS_GAS, DEVICE_CLASS_MOISTURE, DEVICE_CLASS_MOTION, - DEVICE_CLASS_OCCUPANCY, DEVICE_CLASS_OPENING, DEVICE_CLASS_SMOKE, - DEVICE_CLASS_WINDOW, PROP_CELSIUS, SERV_AIR_QUALITY_SENSOR, - SERV_CARBON_DIOXIDE_SENSOR, SERV_CARBON_MONOXIDE_SENSOR, - SERV_CONTACT_SENSOR, SERV_HUMIDITY_SENSOR, SERV_LEAK_SENSOR, - SERV_LIGHT_SENSOR, SERV_MOTION_SENSOR, SERV_OCCUPANCY_SENSOR, - SERV_SMOKE_SENSOR, SERV_TEMPERATURE_SENSOR, THRESHOLD_CO, THRESHOLD_CO2) -from .util import ( - convert_to_float, density_to_air_quality, temperature_to_homekit) + CHAR_AIR_PARTICULATE_DENSITY, + CHAR_AIR_QUALITY, + CHAR_CARBON_DIOXIDE_DETECTED, + CHAR_CARBON_DIOXIDE_LEVEL, + CHAR_CARBON_DIOXIDE_PEAK_LEVEL, + CHAR_CARBON_MONOXIDE_DETECTED, + CHAR_CARBON_MONOXIDE_LEVEL, + CHAR_CARBON_MONOXIDE_PEAK_LEVEL, + CHAR_CONTACT_SENSOR_STATE, + CHAR_CURRENT_AMBIENT_LIGHT_LEVEL, + CHAR_CURRENT_HUMIDITY, + CHAR_CURRENT_TEMPERATURE, + CHAR_LEAK_DETECTED, + CHAR_MOTION_DETECTED, + CHAR_OCCUPANCY_DETECTED, + CHAR_SMOKE_DETECTED, + DEVICE_CLASS_CO2, + DEVICE_CLASS_DOOR, + DEVICE_CLASS_GARAGE_DOOR, + DEVICE_CLASS_GAS, + DEVICE_CLASS_MOISTURE, + DEVICE_CLASS_MOTION, + DEVICE_CLASS_OCCUPANCY, + DEVICE_CLASS_OPENING, + DEVICE_CLASS_SMOKE, + DEVICE_CLASS_WINDOW, + PROP_CELSIUS, + SERV_AIR_QUALITY_SENSOR, + SERV_CARBON_DIOXIDE_SENSOR, + SERV_CARBON_MONOXIDE_SENSOR, + SERV_CONTACT_SENSOR, + SERV_HUMIDITY_SENSOR, + SERV_LEAK_SENSOR, + SERV_LIGHT_SENSOR, + SERV_MOTION_SENSOR, + SERV_OCCUPANCY_SENSOR, + SERV_SMOKE_SENSOR, + SERV_TEMPERATURE_SENSOR, + THRESHOLD_CO, + THRESHOLD_CO2, +) +from .util import convert_to_float, density_to_air_quality, temperature_to_homekit _LOGGER = logging.getLogger(__name__) BINARY_SENSOR_SERVICE_MAP = { - DEVICE_CLASS_CO2: (SERV_CARBON_DIOXIDE_SENSOR, - CHAR_CARBON_DIOXIDE_DETECTED), + DEVICE_CLASS_CO2: (SERV_CARBON_DIOXIDE_SENSOR, CHAR_CARBON_DIOXIDE_DETECTED), DEVICE_CLASS_DOOR: (SERV_CONTACT_SENSOR, CHAR_CONTACT_SENSOR_STATE), DEVICE_CLASS_GARAGE_DOOR: (SERV_CONTACT_SENSOR, CHAR_CONTACT_SENSOR_STATE), - DEVICE_CLASS_GAS: (SERV_CARBON_MONOXIDE_SENSOR, - CHAR_CARBON_MONOXIDE_DETECTED), + DEVICE_CLASS_GAS: (SERV_CARBON_MONOXIDE_SENSOR, CHAR_CARBON_MONOXIDE_DETECTED), DEVICE_CLASS_MOISTURE: (SERV_LEAK_SENSOR, CHAR_LEAK_DETECTED), DEVICE_CLASS_MOTION: (SERV_MOTION_SENSOR, CHAR_MOTION_DETECTED), DEVICE_CLASS_OCCUPANCY: (SERV_OCCUPANCY_SENSOR, CHAR_OCCUPANCY_DETECTED), @@ -46,7 +73,7 @@ BINARY_SENSOR_SERVICE_MAP = { } -@TYPES.register('TemperatureSensor') +@TYPES.register("TemperatureSensor") class TemperatureSensor(HomeAccessory): """Generate a TemperatureSensor accessory for a temperature sensor. @@ -58,7 +85,8 @@ class TemperatureSensor(HomeAccessory): super().__init__(*args, category=CATEGORY_SENSOR) serv_temp = self.add_preload_service(SERV_TEMPERATURE_SENSOR) self.char_temp = serv_temp.configure_char( - CHAR_CURRENT_TEMPERATURE, value=0, properties=PROP_CELSIUS) + CHAR_CURRENT_TEMPERATURE, value=0, properties=PROP_CELSIUS + ) def update_state(self, new_state): """Update temperature after state changed.""" @@ -67,11 +95,12 @@ class TemperatureSensor(HomeAccessory): if temperature: temperature = temperature_to_homekit(temperature, unit) self.char_temp.set_value(temperature) - _LOGGER.debug('%s: Current temperature set to %d°C', - self.entity_id, temperature) + _LOGGER.debug( + "%s: Current temperature set to %d°C", self.entity_id, temperature + ) -@TYPES.register('HumiditySensor') +@TYPES.register("HumiditySensor") class HumiditySensor(HomeAccessory): """Generate a HumiditySensor accessory as humidity sensor.""" @@ -80,18 +109,18 @@ class HumiditySensor(HomeAccessory): super().__init__(*args, category=CATEGORY_SENSOR) serv_humidity = self.add_preload_service(SERV_HUMIDITY_SENSOR) self.char_humidity = serv_humidity.configure_char( - CHAR_CURRENT_HUMIDITY, value=0) + CHAR_CURRENT_HUMIDITY, value=0 + ) def update_state(self, new_state): """Update accessory after state change.""" humidity = convert_to_float(new_state.state) if humidity: self.char_humidity.set_value(humidity) - _LOGGER.debug('%s: Percent set to %d%%', - self.entity_id, humidity) + _LOGGER.debug("%s: Percent set to %d%%", self.entity_id, humidity) -@TYPES.register('AirQualitySensor') +@TYPES.register("AirQualitySensor") class AirQualitySensor(HomeAccessory): """Generate a AirQualitySensor accessory as air quality sensor.""" @@ -100,11 +129,12 @@ class AirQualitySensor(HomeAccessory): super().__init__(*args, category=CATEGORY_SENSOR) serv_air_quality = self.add_preload_service( - SERV_AIR_QUALITY_SENSOR, [CHAR_AIR_PARTICULATE_DENSITY]) - self.char_quality = serv_air_quality.configure_char( - CHAR_AIR_QUALITY, value=0) + SERV_AIR_QUALITY_SENSOR, [CHAR_AIR_PARTICULATE_DENSITY] + ) + self.char_quality = serv_air_quality.configure_char(CHAR_AIR_QUALITY, value=0) self.char_density = serv_air_quality.configure_char( - CHAR_AIR_PARTICULATE_DENSITY, value=0) + CHAR_AIR_PARTICULATE_DENSITY, value=0 + ) def update_state(self, new_state): """Update accessory after state change.""" @@ -112,10 +142,10 @@ class AirQualitySensor(HomeAccessory): if density: self.char_density.set_value(density) self.char_quality.set_value(density_to_air_quality(density)) - _LOGGER.debug('%s: Set to %d', self.entity_id, density) + _LOGGER.debug("%s: Set to %d", self.entity_id, density) -@TYPES.register('CarbonMonoxideSensor') +@TYPES.register("CarbonMonoxideSensor") class CarbonMonoxideSensor(HomeAccessory): """Generate a CarbonMonoxidSensor accessory as CO sensor.""" @@ -123,14 +153,17 @@ class CarbonMonoxideSensor(HomeAccessory): """Initialize a CarbonMonoxideSensor accessory object.""" super().__init__(*args, category=CATEGORY_SENSOR) - serv_co = self.add_preload_service(SERV_CARBON_MONOXIDE_SENSOR, [ - CHAR_CARBON_MONOXIDE_LEVEL, CHAR_CARBON_MONOXIDE_PEAK_LEVEL]) - self.char_level = serv_co.configure_char( - CHAR_CARBON_MONOXIDE_LEVEL, value=0) + serv_co = self.add_preload_service( + SERV_CARBON_MONOXIDE_SENSOR, + [CHAR_CARBON_MONOXIDE_LEVEL, CHAR_CARBON_MONOXIDE_PEAK_LEVEL], + ) + self.char_level = serv_co.configure_char(CHAR_CARBON_MONOXIDE_LEVEL, value=0) self.char_peak = serv_co.configure_char( - CHAR_CARBON_MONOXIDE_PEAK_LEVEL, value=0) + CHAR_CARBON_MONOXIDE_PEAK_LEVEL, value=0 + ) self.char_detected = serv_co.configure_char( - CHAR_CARBON_MONOXIDE_DETECTED, value=0) + CHAR_CARBON_MONOXIDE_DETECTED, value=0 + ) def update_state(self, new_state): """Update accessory after state change.""" @@ -140,10 +173,10 @@ class CarbonMonoxideSensor(HomeAccessory): if value > self.char_peak.value: self.char_peak.set_value(value) self.char_detected.set_value(value > THRESHOLD_CO) - _LOGGER.debug('%s: Set to %d', self.entity_id, value) + _LOGGER.debug("%s: Set to %d", self.entity_id, value) -@TYPES.register('CarbonDioxideSensor') +@TYPES.register("CarbonDioxideSensor") class CarbonDioxideSensor(HomeAccessory): """Generate a CarbonDioxideSensor accessory as CO2 sensor.""" @@ -151,14 +184,17 @@ class CarbonDioxideSensor(HomeAccessory): """Initialize a CarbonDioxideSensor accessory object.""" super().__init__(*args, category=CATEGORY_SENSOR) - serv_co2 = self.add_preload_service(SERV_CARBON_DIOXIDE_SENSOR, [ - CHAR_CARBON_DIOXIDE_LEVEL, CHAR_CARBON_DIOXIDE_PEAK_LEVEL]) - self.char_level = serv_co2.configure_char( - CHAR_CARBON_DIOXIDE_LEVEL, value=0) + serv_co2 = self.add_preload_service( + SERV_CARBON_DIOXIDE_SENSOR, + [CHAR_CARBON_DIOXIDE_LEVEL, CHAR_CARBON_DIOXIDE_PEAK_LEVEL], + ) + self.char_level = serv_co2.configure_char(CHAR_CARBON_DIOXIDE_LEVEL, value=0) self.char_peak = serv_co2.configure_char( - CHAR_CARBON_DIOXIDE_PEAK_LEVEL, value=0) + CHAR_CARBON_DIOXIDE_PEAK_LEVEL, value=0 + ) self.char_detected = serv_co2.configure_char( - CHAR_CARBON_DIOXIDE_DETECTED, value=0) + CHAR_CARBON_DIOXIDE_DETECTED, value=0 + ) def update_state(self, new_state): """Update accessory after state change.""" @@ -168,10 +204,10 @@ class CarbonDioxideSensor(HomeAccessory): if value > self.char_peak.value: self.char_peak.set_value(value) self.char_detected.set_value(value > THRESHOLD_CO2) - _LOGGER.debug('%s: Set to %d', self.entity_id, value) + _LOGGER.debug("%s: Set to %d", self.entity_id, value) -@TYPES.register('LightSensor') +@TYPES.register("LightSensor") class LightSensor(HomeAccessory): """Generate a LightSensor accessory as light sensor.""" @@ -181,28 +217,32 @@ class LightSensor(HomeAccessory): serv_light = self.add_preload_service(SERV_LIGHT_SENSOR) self.char_light = serv_light.configure_char( - CHAR_CURRENT_AMBIENT_LIGHT_LEVEL, value=0) + CHAR_CURRENT_AMBIENT_LIGHT_LEVEL, value=0 + ) def update_state(self, new_state): """Update accessory after state change.""" luminance = convert_to_float(new_state.state) if luminance: self.char_light.set_value(luminance) - _LOGGER.debug('%s: Set to %d', self.entity_id, luminance) + _LOGGER.debug("%s: Set to %d", self.entity_id, luminance) -@TYPES.register('BinarySensor') +@TYPES.register("BinarySensor") class BinarySensor(HomeAccessory): """Generate a BinarySensor accessory as binary sensor.""" def __init__(self, *args): """Initialize a BinarySensor accessory object.""" super().__init__(*args, category=CATEGORY_SENSOR) - device_class = self.hass.states.get(self.entity_id).attributes \ - .get(ATTR_DEVICE_CLASS) - service_char = BINARY_SENSOR_SERVICE_MAP[device_class] \ - if device_class in BINARY_SENSOR_SERVICE_MAP \ + device_class = self.hass.states.get(self.entity_id).attributes.get( + ATTR_DEVICE_CLASS + ) + service_char = ( + BINARY_SENSOR_SERVICE_MAP[device_class] + if device_class in BINARY_SENSOR_SERVICE_MAP else BINARY_SENSOR_SERVICE_MAP[DEVICE_CLASS_OCCUPANCY] + ) service = self.add_preload_service(service_char[0]) self.char_detected = service.configure_char(service_char[1], value=0) @@ -212,4 +252,4 @@ class BinarySensor(HomeAccessory): state = new_state.state detected = state in (STATE_ON, STATE_HOME) self.char_detected.set_value(detected) - _LOGGER.debug('%s: Set to %d', self.entity_id, detected) + _LOGGER.debug("%s: Set to %d", self.entity_id, detected) diff --git a/homeassistant/components/homekit/type_switches.py b/homeassistant/components/homekit/type_switches.py index 7629e33a4d7..66d3037b894 100644 --- a/homeassistant/components/homekit/type_switches.py +++ b/homeassistant/components/homekit/type_switches.py @@ -2,22 +2,41 @@ import logging from pyhap.const import ( - CATEGORY_FAUCET, CATEGORY_OUTLET, CATEGORY_SHOWER_HEAD, CATEGORY_SPRINKLER, - CATEGORY_SWITCH) + CATEGORY_FAUCET, + CATEGORY_OUTLET, + CATEGORY_SHOWER_HEAD, + CATEGORY_SPRINKLER, + CATEGORY_SWITCH, +) from homeassistant.components.script import ATTR_CAN_CANCEL from homeassistant.components.switch import DOMAIN from homeassistant.const import ( - ATTR_ENTITY_ID, CONF_TYPE, SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_ON) + ATTR_ENTITY_ID, + CONF_TYPE, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_ON, +) from homeassistant.core import split_entity_id from homeassistant.helpers.event import call_later from . import TYPES from .accessories import HomeAccessory from .const import ( - CHAR_ACTIVE, CHAR_IN_USE, CHAR_ON, CHAR_OUTLET_IN_USE, CHAR_VALVE_TYPE, - SERV_OUTLET, SERV_SWITCH, SERV_VALVE, TYPE_FAUCET, TYPE_SHOWER, - TYPE_SPRINKLER, TYPE_VALVE) + CHAR_ACTIVE, + CHAR_IN_USE, + CHAR_ON, + CHAR_OUTLET_IN_USE, + CHAR_VALVE_TYPE, + SERV_OUTLET, + SERV_SWITCH, + SERV_VALVE, + TYPE_FAUCET, + TYPE_SHOWER, + TYPE_SPRINKLER, + TYPE_VALVE, +) _LOGGER = logging.getLogger(__name__) @@ -29,7 +48,7 @@ VALVE_TYPE = { } -@TYPES.register('Outlet') +@TYPES.register("Outlet") class Outlet(HomeAccessory): """Generate an Outlet accessory.""" @@ -40,14 +59,15 @@ class Outlet(HomeAccessory): serv_outlet = self.add_preload_service(SERV_OUTLET) self.char_on = serv_outlet.configure_char( - CHAR_ON, value=False, setter_callback=self.set_state) + CHAR_ON, value=False, setter_callback=self.set_state + ) self.char_outlet_in_use = serv_outlet.configure_char( - CHAR_OUTLET_IN_USE, value=True) + CHAR_OUTLET_IN_USE, value=True + ) def set_state(self, value): """Move switch state to value if call came from HomeKit.""" - _LOGGER.debug('%s: Set switch state to %s', - self.entity_id, value) + _LOGGER.debug("%s: Set switch state to %s", self.entity_id, value) self._flag_state = True params = {ATTR_ENTITY_ID: self.entity_id} service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF @@ -55,15 +75,14 @@ class Outlet(HomeAccessory): def update_state(self, new_state): """Update switch state after state changed.""" - current_state = (new_state.state == STATE_ON) + current_state = new_state.state == STATE_ON if not self._flag_state: - _LOGGER.debug('%s: Set current state to %s', - self.entity_id, current_state) + _LOGGER.debug("%s: Set current state to %s", self.entity_id, current_state) self.char_on.set_value(current_state) self._flag_state = False -@TYPES.register('Switch') +@TYPES.register("Switch") class Switch(HomeAccessory): """Generate a Switch accessory.""" @@ -73,33 +92,32 @@ class Switch(HomeAccessory): self._domain = split_entity_id(self.entity_id)[0] self._flag_state = False - self.activate_only = self.is_activate( - self.hass.states.get(self.entity_id)) + self.activate_only = self.is_activate(self.hass.states.get(self.entity_id)) serv_switch = self.add_preload_service(SERV_SWITCH) self.char_on = serv_switch.configure_char( - CHAR_ON, value=False, setter_callback=self.set_state) + CHAR_ON, value=False, setter_callback=self.set_state + ) def is_activate(self, state): """Check if entity is activate only.""" can_cancel = state.attributes.get(ATTR_CAN_CANCEL) - if self._domain == 'scene': + if self._domain == "scene": return True - if self._domain == 'script' and not can_cancel: + if self._domain == "script" and not can_cancel: return True return False def reset_switch(self, *args): """Reset switch to emulate activate click.""" - _LOGGER.debug('%s: Reset switch to off', self.entity_id) + _LOGGER.debug("%s: Reset switch to off", self.entity_id) self.char_on.set_value(0) def set_state(self, value): """Move switch state to value if call came from HomeKit.""" - _LOGGER.debug('%s: Set switch state to %s', - self.entity_id, value) + _LOGGER.debug("%s: Set switch state to %s", self.entity_id, value) if self.activate_only and value == 0: - _LOGGER.debug('%s: Ignoring turn_off call', self.entity_id) + _LOGGER.debug("%s: Ignoring turn_off call", self.entity_id) return self._flag_state = True params = {ATTR_ENTITY_ID: self.entity_id} @@ -113,19 +131,19 @@ class Switch(HomeAccessory): """Update switch state after state changed.""" self.activate_only = self.is_activate(new_state) if self.activate_only: - _LOGGER.debug('%s: Ignore state change, entity is activate only', - self.entity_id) + _LOGGER.debug( + "%s: Ignore state change, entity is activate only", self.entity_id + ) return - current_state = (new_state.state == STATE_ON) + current_state = new_state.state == STATE_ON if not self._flag_state: - _LOGGER.debug('%s: Set current state to %s', - self.entity_id, current_state) + _LOGGER.debug("%s: Set current state to %s", self.entity_id, current_state) self.char_on.set_value(current_state) self._flag_state = False -@TYPES.register('Valve') +@TYPES.register("Valve") class Valve(HomeAccessory): """Generate a Valve accessory.""" @@ -138,16 +156,16 @@ class Valve(HomeAccessory): serv_valve = self.add_preload_service(SERV_VALVE) self.char_active = serv_valve.configure_char( - CHAR_ACTIVE, value=False, setter_callback=self.set_state) - self.char_in_use = serv_valve.configure_char( - CHAR_IN_USE, value=False) + CHAR_ACTIVE, value=False, setter_callback=self.set_state + ) + self.char_in_use = serv_valve.configure_char(CHAR_IN_USE, value=False) self.char_valve_type = serv_valve.configure_char( - CHAR_VALVE_TYPE, value=VALVE_TYPE[valve_type][1]) + CHAR_VALVE_TYPE, value=VALVE_TYPE[valve_type][1] + ) def set_state(self, value): """Move value state to value if call came from HomeKit.""" - _LOGGER.debug('%s: Set switch state to %s', - self.entity_id, value) + _LOGGER.debug("%s: Set switch state to %s", self.entity_id, value) self._flag_state = True self.char_in_use.set_value(value) params = {ATTR_ENTITY_ID: self.entity_id} @@ -156,10 +174,9 @@ class Valve(HomeAccessory): def update_state(self, new_state): """Update switch state after state changed.""" - current_state = (new_state.state == STATE_ON) + current_state = new_state.state == STATE_ON if not self._flag_state: - _LOGGER.debug('%s: Set current state to %s', - self.entity_id, current_state) + _LOGGER.debug("%s: Set current state to %s", self.entity_id, current_state) self.char_active.set_value(current_state) self.char_in_use.set_value(current_state) self._flag_state = False diff --git a/homeassistant/components/homekit/type_thermostats.py b/homeassistant/components/homekit/type_thermostats.py index 8032e00db66..e00912d340e 100644 --- a/homeassistant/components/homekit/type_thermostats.py +++ b/homeassistant/components/homekit/type_thermostats.py @@ -4,39 +4,70 @@ import logging from pyhap.const import CATEGORY_THERMOSTAT from homeassistant.components.climate.const import ( - ATTR_CURRENT_TEMPERATURE, ATTR_HVAC_ACTIONS, ATTR_HVAC_MODE, ATTR_MAX_TEMP, - ATTR_MIN_TEMP, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, - ATTR_TARGET_TEMP_STEP, CURRENT_HVAC_COOL, CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, CURRENT_HVAC_OFF, DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, - DOMAIN as DOMAIN_CLIMATE, HVAC_MODE_COOL, HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, + ATTR_CURRENT_TEMPERATURE, + ATTR_HVAC_ACTIONS, + ATTR_HVAC_MODE, + ATTR_MAX_TEMP, + ATTR_MIN_TEMP, + ATTR_TARGET_TEMP_HIGH, + ATTR_TARGET_TEMP_LOW, + ATTR_TARGET_TEMP_STEP, + CURRENT_HVAC_COOL, + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, + CURRENT_HVAC_OFF, + DEFAULT_MAX_TEMP, + DEFAULT_MIN_TEMP, + DOMAIN as DOMAIN_CLIMATE, + HVAC_MODE_COOL, + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_OFF, SERVICE_SET_HVAC_MODE as SERVICE_SET_HVAC_MODE_THERMOSTAT, SERVICE_SET_TEMPERATURE as SERVICE_SET_TEMPERATURE_THERMOSTAT, - SUPPORT_TARGET_TEMPERATURE_RANGE) + SUPPORT_TARGET_TEMPERATURE_RANGE, +) from homeassistant.components.water_heater import ( DOMAIN as DOMAIN_WATER_HEATER, - SERVICE_SET_TEMPERATURE as SERVICE_SET_TEMPERATURE_WATER_HEATER) + SERVICE_SET_TEMPERATURE as SERVICE_SET_TEMPERATURE_WATER_HEATER, +) from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE, TEMP_CELSIUS, - TEMP_FAHRENHEIT) + ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, + ATTR_TEMPERATURE, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, +) from . import TYPES from .accessories import HomeAccessory, debounce from .const import ( - CHAR_COOLING_THRESHOLD_TEMPERATURE, CHAR_CURRENT_HEATING_COOLING, - CHAR_CURRENT_TEMPERATURE, CHAR_HEATING_THRESHOLD_TEMPERATURE, - CHAR_TARGET_HEATING_COOLING, CHAR_TARGET_TEMPERATURE, - CHAR_TEMP_DISPLAY_UNITS, DEFAULT_MAX_TEMP_WATER_HEATER, - DEFAULT_MIN_TEMP_WATER_HEATER, PROP_MAX_VALUE, PROP_MIN_STEP, - PROP_MIN_VALUE, SERV_THERMOSTAT) + CHAR_COOLING_THRESHOLD_TEMPERATURE, + CHAR_CURRENT_HEATING_COOLING, + CHAR_CURRENT_TEMPERATURE, + CHAR_HEATING_THRESHOLD_TEMPERATURE, + CHAR_TARGET_HEATING_COOLING, + CHAR_TARGET_TEMPERATURE, + CHAR_TEMP_DISPLAY_UNITS, + DEFAULT_MAX_TEMP_WATER_HEATER, + DEFAULT_MIN_TEMP_WATER_HEATER, + PROP_MAX_VALUE, + PROP_MIN_STEP, + PROP_MIN_VALUE, + SERV_THERMOSTAT, +) from .util import temperature_to_homekit, temperature_to_states _LOGGER = logging.getLogger(__name__) UNIT_HASS_TO_HOMEKIT = {TEMP_CELSIUS: 0, TEMP_FAHRENHEIT: 1} UNIT_HOMEKIT_TO_HASS = {c: s for s, c in UNIT_HASS_TO_HOMEKIT.items()} -HC_HASS_TO_HOMEKIT = {HVAC_MODE_OFF: 0, HVAC_MODE_HEAT: 1, - HVAC_MODE_COOL: 2, HVAC_MODE_HEAT_COOL: 3} +HC_HASS_TO_HOMEKIT = { + HVAC_MODE_OFF: 0, + HVAC_MODE_HEAT: 1, + HVAC_MODE_COOL: 2, + HVAC_MODE_HEAT_COOL: 3, +} HC_HOMEKIT_TO_HASS = {c: s for s, c in HC_HASS_TO_HOMEKIT.items()} HC_HASS_TO_HOMEKIT_ACTION = { @@ -47,7 +78,7 @@ HC_HASS_TO_HOMEKIT_ACTION = { } -@TYPES.register('Thermostat') +@TYPES.register("Thermostat") class Thermostat(HomeAccessory): """Generate a Thermostat accessory for a climate.""" @@ -60,132 +91,161 @@ class Thermostat(HomeAccessory): self._flag_coolingthresh = False self._flag_heatingthresh = False min_temp, max_temp = self.get_temperature_range() - temp_step = self.hass.states.get(self.entity_id) \ - .attributes.get(ATTR_TARGET_TEMP_STEP, 0.5) + temp_step = self.hass.states.get(self.entity_id).attributes.get( + ATTR_TARGET_TEMP_STEP, 0.5 + ) # Add additional characteristics if auto mode is supported self.chars = [] - features = self.hass.states.get(self.entity_id) \ - .attributes.get(ATTR_SUPPORTED_FEATURES, 0) + features = self.hass.states.get(self.entity_id).attributes.get( + ATTR_SUPPORTED_FEATURES, 0 + ) if features & SUPPORT_TARGET_TEMPERATURE_RANGE: - self.chars.extend((CHAR_COOLING_THRESHOLD_TEMPERATURE, - CHAR_HEATING_THRESHOLD_TEMPERATURE)) + self.chars.extend( + (CHAR_COOLING_THRESHOLD_TEMPERATURE, CHAR_HEATING_THRESHOLD_TEMPERATURE) + ) serv_thermostat = self.add_preload_service(SERV_THERMOSTAT, self.chars) # Current and target mode characteristics self.char_current_heat_cool = serv_thermostat.configure_char( - CHAR_CURRENT_HEATING_COOLING, value=0) + CHAR_CURRENT_HEATING_COOLING, value=0 + ) self.char_target_heat_cool = serv_thermostat.configure_char( - CHAR_TARGET_HEATING_COOLING, value=0, - setter_callback=self.set_heat_cool) + CHAR_TARGET_HEATING_COOLING, value=0, setter_callback=self.set_heat_cool + ) # Current and target temperature characteristics self.char_current_temp = serv_thermostat.configure_char( - CHAR_CURRENT_TEMPERATURE, value=21.0) + CHAR_CURRENT_TEMPERATURE, value=21.0 + ) self.char_target_temp = serv_thermostat.configure_char( - CHAR_TARGET_TEMPERATURE, value=21.0, - properties={PROP_MIN_VALUE: min_temp, - PROP_MAX_VALUE: max_temp, - PROP_MIN_STEP: temp_step}, - setter_callback=self.set_target_temperature) + CHAR_TARGET_TEMPERATURE, + value=21.0, + properties={ + PROP_MIN_VALUE: min_temp, + PROP_MAX_VALUE: max_temp, + PROP_MIN_STEP: temp_step, + }, + setter_callback=self.set_target_temperature, + ) # Display units characteristic self.char_display_units = serv_thermostat.configure_char( - CHAR_TEMP_DISPLAY_UNITS, value=0) + CHAR_TEMP_DISPLAY_UNITS, value=0 + ) # If the device supports it: high and low temperature characteristics self.char_cooling_thresh_temp = None self.char_heating_thresh_temp = None if CHAR_COOLING_THRESHOLD_TEMPERATURE in self.chars: self.char_cooling_thresh_temp = serv_thermostat.configure_char( - CHAR_COOLING_THRESHOLD_TEMPERATURE, value=23.0, - properties={PROP_MIN_VALUE: min_temp, - PROP_MAX_VALUE: max_temp, - PROP_MIN_STEP: temp_step}, - setter_callback=self.set_cooling_threshold) + CHAR_COOLING_THRESHOLD_TEMPERATURE, + value=23.0, + properties={ + PROP_MIN_VALUE: min_temp, + PROP_MAX_VALUE: max_temp, + PROP_MIN_STEP: temp_step, + }, + setter_callback=self.set_cooling_threshold, + ) if CHAR_HEATING_THRESHOLD_TEMPERATURE in self.chars: self.char_heating_thresh_temp = serv_thermostat.configure_char( - CHAR_HEATING_THRESHOLD_TEMPERATURE, value=19.0, - properties={PROP_MIN_VALUE: min_temp, - PROP_MAX_VALUE: max_temp, - PROP_MIN_STEP: temp_step}, - setter_callback=self.set_heating_threshold) + CHAR_HEATING_THRESHOLD_TEMPERATURE, + value=19.0, + properties={ + PROP_MIN_VALUE: min_temp, + PROP_MAX_VALUE: max_temp, + PROP_MIN_STEP: temp_step, + }, + setter_callback=self.set_heating_threshold, + ) def get_temperature_range(self): """Return min and max temperature range.""" - max_temp = self.hass.states.get(self.entity_id) \ - .attributes.get(ATTR_MAX_TEMP) - max_temp = temperature_to_homekit(max_temp, self._unit) if max_temp \ + max_temp = self.hass.states.get(self.entity_id).attributes.get(ATTR_MAX_TEMP) + max_temp = ( + temperature_to_homekit(max_temp, self._unit) + if max_temp else DEFAULT_MAX_TEMP + ) max_temp = round(max_temp * 2) / 2 - min_temp = self.hass.states.get(self.entity_id) \ - .attributes.get(ATTR_MIN_TEMP) - min_temp = temperature_to_homekit(min_temp, self._unit) if min_temp \ + min_temp = self.hass.states.get(self.entity_id).attributes.get(ATTR_MIN_TEMP) + min_temp = ( + temperature_to_homekit(min_temp, self._unit) + if min_temp else DEFAULT_MIN_TEMP + ) min_temp = round(min_temp * 2) / 2 return min_temp, max_temp def set_heat_cool(self, value): """Change operation mode to value if call came from HomeKit.""" - _LOGGER.debug('%s: Set heat-cool to %d', self.entity_id, value) + _LOGGER.debug("%s: Set heat-cool to %d", self.entity_id, value) self._flag_heat_cool = True hass_value = HC_HOMEKIT_TO_HASS[value] - params = { - ATTR_ENTITY_ID: self.entity_id, - ATTR_HVAC_MODE: hass_value - } + params = {ATTR_ENTITY_ID: self.entity_id, ATTR_HVAC_MODE: hass_value} self.call_service( - DOMAIN_CLIMATE, SERVICE_SET_HVAC_MODE_THERMOSTAT, params, - hass_value) + DOMAIN_CLIMATE, SERVICE_SET_HVAC_MODE_THERMOSTAT, params, hass_value + ) @debounce def set_cooling_threshold(self, value): """Set cooling threshold temp to value if call came from HomeKit.""" - _LOGGER.debug('%s: Set cooling threshold temperature to %.1f°C', - self.entity_id, value) + _LOGGER.debug( + "%s: Set cooling threshold temperature to %.1f°C", self.entity_id, value + ) self._flag_coolingthresh = True low = self.char_heating_thresh_temp.value temperature = temperature_to_states(value, self._unit) params = { ATTR_ENTITY_ID: self.entity_id, ATTR_TARGET_TEMP_HIGH: temperature, - ATTR_TARGET_TEMP_LOW: temperature_to_states(low, self._unit)} + ATTR_TARGET_TEMP_LOW: temperature_to_states(low, self._unit), + } self.call_service( - DOMAIN_CLIMATE, SERVICE_SET_TEMPERATURE_THERMOSTAT, - params, 'cooling threshold {}{}'.format(temperature, self._unit)) + DOMAIN_CLIMATE, + SERVICE_SET_TEMPERATURE_THERMOSTAT, + params, + "cooling threshold {}{}".format(temperature, self._unit), + ) @debounce def set_heating_threshold(self, value): """Set heating threshold temp to value if call came from HomeKit.""" - _LOGGER.debug('%s: Set heating threshold temperature to %.1f°C', - self.entity_id, value) + _LOGGER.debug( + "%s: Set heating threshold temperature to %.1f°C", self.entity_id, value + ) self._flag_heatingthresh = True high = self.char_cooling_thresh_temp.value temperature = temperature_to_states(value, self._unit) params = { ATTR_ENTITY_ID: self.entity_id, ATTR_TARGET_TEMP_HIGH: temperature_to_states(high, self._unit), - ATTR_TARGET_TEMP_LOW: temperature} + ATTR_TARGET_TEMP_LOW: temperature, + } self.call_service( - DOMAIN_CLIMATE, SERVICE_SET_TEMPERATURE_THERMOSTAT, - params, 'heating threshold {}{}'.format(temperature, self._unit)) + DOMAIN_CLIMATE, + SERVICE_SET_TEMPERATURE_THERMOSTAT, + params, + "heating threshold {}{}".format(temperature, self._unit), + ) @debounce def set_target_temperature(self, value): """Set target temperature to value if call came from HomeKit.""" - _LOGGER.debug('%s: Set target temperature to %.1f°C', - self.entity_id, value) + _LOGGER.debug("%s: Set target temperature to %.1f°C", self.entity_id, value) self._flag_temperature = True temperature = temperature_to_states(value, self._unit) - params = { - ATTR_ENTITY_ID: self.entity_id, - ATTR_TEMPERATURE: temperature} + params = {ATTR_ENTITY_ID: self.entity_id, ATTR_TEMPERATURE: temperature} self.call_service( - DOMAIN_CLIMATE, SERVICE_SET_TEMPERATURE_THERMOSTAT, - params, '{}{}'.format(temperature, self._unit)) + DOMAIN_CLIMATE, + SERVICE_SET_TEMPERATURE_THERMOSTAT, + params, + "{}{}".format(temperature, self._unit), + ) def update_state(self, new_state): """Update thermostat state after state changed.""" @@ -207,8 +267,7 @@ class Thermostat(HomeAccessory): if self.char_cooling_thresh_temp: cooling_thresh = new_state.attributes.get(ATTR_TARGET_TEMP_HIGH) if isinstance(cooling_thresh, (int, float)): - cooling_thresh = temperature_to_homekit(cooling_thresh, - self._unit) + cooling_thresh = temperature_to_homekit(cooling_thresh, self._unit) if not self._flag_coolingthresh: self.char_cooling_thresh_temp.set_value(cooling_thresh) self._flag_coolingthresh = False @@ -217,8 +276,7 @@ class Thermostat(HomeAccessory): if self.char_heating_thresh_temp: heating_thresh = new_state.attributes.get(ATTR_TARGET_TEMP_LOW) if isinstance(heating_thresh, (int, float)): - heating_thresh = temperature_to_homekit(heating_thresh, - self._unit) + heating_thresh = temperature_to_homekit(heating_thresh, self._unit) if not self._flag_heatingthresh: self.char_heating_thresh_temp.set_value(heating_thresh) self._flag_heatingthresh = False @@ -231,18 +289,18 @@ class Thermostat(HomeAccessory): hvac_mode = new_state.state if hvac_mode and hvac_mode in HC_HASS_TO_HOMEKIT: if not self._flag_heat_cool: - self.char_target_heat_cool.set_value( - HC_HASS_TO_HOMEKIT[hvac_mode]) + self.char_target_heat_cool.set_value(HC_HASS_TO_HOMEKIT[hvac_mode]) self._flag_heat_cool = False # Set current operation mode for supported thermostats hvac_action = new_state.attributes.get(ATTR_HVAC_ACTIONS) if hvac_action: self.char_current_heat_cool.set_value( - HC_HASS_TO_HOMEKIT_ACTION[hvac_action]) + HC_HASS_TO_HOMEKIT_ACTION[hvac_action] + ) -@TYPES.register('WaterHeater') +@TYPES.register("WaterHeater") class WaterHeater(HomeAccessory): """Generate a WaterHeater accessory for a water_heater.""" @@ -257,42 +315,53 @@ class WaterHeater(HomeAccessory): serv_thermostat = self.add_preload_service(SERV_THERMOSTAT) self.char_current_heat_cool = serv_thermostat.configure_char( - CHAR_CURRENT_HEATING_COOLING, value=1) + CHAR_CURRENT_HEATING_COOLING, value=1 + ) self.char_target_heat_cool = serv_thermostat.configure_char( - CHAR_TARGET_HEATING_COOLING, value=1, - setter_callback=self.set_heat_cool) + CHAR_TARGET_HEATING_COOLING, value=1, setter_callback=self.set_heat_cool + ) self.char_current_temp = serv_thermostat.configure_char( - CHAR_CURRENT_TEMPERATURE, value=50.0) + CHAR_CURRENT_TEMPERATURE, value=50.0 + ) self.char_target_temp = serv_thermostat.configure_char( - CHAR_TARGET_TEMPERATURE, value=50.0, - properties={PROP_MIN_VALUE: min_temp, - PROP_MAX_VALUE: max_temp, - PROP_MIN_STEP: 0.5}, - setter_callback=self.set_target_temperature) + CHAR_TARGET_TEMPERATURE, + value=50.0, + properties={ + PROP_MIN_VALUE: min_temp, + PROP_MAX_VALUE: max_temp, + PROP_MIN_STEP: 0.5, + }, + setter_callback=self.set_target_temperature, + ) self.char_display_units = serv_thermostat.configure_char( - CHAR_TEMP_DISPLAY_UNITS, value=0) + CHAR_TEMP_DISPLAY_UNITS, value=0 + ) def get_temperature_range(self): """Return min and max temperature range.""" - max_temp = self.hass.states.get(self.entity_id) \ - .attributes.get(ATTR_MAX_TEMP) - max_temp = temperature_to_homekit(max_temp, self._unit) if max_temp \ + max_temp = self.hass.states.get(self.entity_id).attributes.get(ATTR_MAX_TEMP) + max_temp = ( + temperature_to_homekit(max_temp, self._unit) + if max_temp else DEFAULT_MAX_TEMP_WATER_HEATER + ) max_temp = round(max_temp * 2) / 2 - min_temp = self.hass.states.get(self.entity_id) \ - .attributes.get(ATTR_MIN_TEMP) - min_temp = temperature_to_homekit(min_temp, self._unit) if min_temp \ + min_temp = self.hass.states.get(self.entity_id).attributes.get(ATTR_MIN_TEMP) + min_temp = ( + temperature_to_homekit(min_temp, self._unit) + if min_temp else DEFAULT_MIN_TEMP_WATER_HEATER + ) min_temp = round(min_temp * 2) / 2 return min_temp, max_temp def set_heat_cool(self, value): """Change operation mode to value if call came from HomeKit.""" - _LOGGER.debug('%s: Set heat-cool to %d', self.entity_id, value) + _LOGGER.debug("%s: Set heat-cool to %d", self.entity_id, value) self._flag_heat_cool = True hass_value = HC_HOMEKIT_TO_HASS[value] if hass_value != HVAC_MODE_HEAT: @@ -301,16 +370,16 @@ class WaterHeater(HomeAccessory): @debounce def set_target_temperature(self, value): """Set target temperature to value if call came from HomeKit.""" - _LOGGER.debug('%s: Set target temperature to %.1f°C', - self.entity_id, value) + _LOGGER.debug("%s: Set target temperature to %.1f°C", self.entity_id, value) self._flag_temperature = True temperature = temperature_to_states(value, self._unit) - params = { - ATTR_ENTITY_ID: self.entity_id, - ATTR_TEMPERATURE: temperature} + params = {ATTR_ENTITY_ID: self.entity_id, ATTR_TEMPERATURE: temperature} self.call_service( - DOMAIN_WATER_HEATER, SERVICE_SET_TEMPERATURE_WATER_HEATER, - params, '{}{}'.format(temperature, self._unit)) + DOMAIN_WATER_HEATER, + SERVICE_SET_TEMPERATURE_WATER_HEATER, + params, + "{}{}".format(temperature, self._unit), + ) def update_state(self, new_state): """Update water_heater state after state change.""" diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index b3c90ae6cbe..3b5f3c81436 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -6,54 +6,95 @@ import voluptuous as vol from homeassistant.components import fan, media_player, sensor from homeassistant.const import ( - ATTR_CODE, ATTR_SUPPORTED_FEATURES, CONF_NAME, CONF_TYPE, TEMP_CELSIUS) + ATTR_CODE, + ATTR_SUPPORTED_FEATURES, + CONF_NAME, + CONF_TYPE, + TEMP_CELSIUS, +) from homeassistant.core import split_entity_id import homeassistant.helpers.config_validation as cv import homeassistant.util.temperature as temp_util from .const import ( - CONF_FEATURE, CONF_FEATURE_LIST, CONF_LINKED_BATTERY_SENSOR, - CONF_LOW_BATTERY_THRESHOLD, DEFAULT_LOW_BATTERY_THRESHOLD, FEATURE_ON_OFF, - FEATURE_PLAY_PAUSE, FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE, - HOMEKIT_NOTIFY_ID, TYPE_FAUCET, TYPE_OUTLET, TYPE_SHOWER, TYPE_SPRINKLER, - TYPE_SWITCH, TYPE_VALVE) + CONF_FEATURE, + CONF_FEATURE_LIST, + CONF_LINKED_BATTERY_SENSOR, + CONF_LOW_BATTERY_THRESHOLD, + DEFAULT_LOW_BATTERY_THRESHOLD, + FEATURE_ON_OFF, + FEATURE_PLAY_PAUSE, + FEATURE_PLAY_STOP, + FEATURE_TOGGLE_MUTE, + HOMEKIT_NOTIFY_ID, + TYPE_FAUCET, + TYPE_OUTLET, + TYPE_SHOWER, + TYPE_SPRINKLER, + TYPE_SWITCH, + TYPE_VALVE, +) _LOGGER = logging.getLogger(__name__) -BASIC_INFO_SCHEMA = vol.Schema({ - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_LINKED_BATTERY_SENSOR): cv.entity_domain(sensor.DOMAIN), - vol.Optional(CONF_LOW_BATTERY_THRESHOLD, - default=DEFAULT_LOW_BATTERY_THRESHOLD): cv.positive_int, -}) +BASIC_INFO_SCHEMA = vol.Schema( + { + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_LINKED_BATTERY_SENSOR): cv.entity_domain(sensor.DOMAIN), + vol.Optional( + CONF_LOW_BATTERY_THRESHOLD, default=DEFAULT_LOW_BATTERY_THRESHOLD + ): cv.positive_int, + } +) -FEATURE_SCHEMA = BASIC_INFO_SCHEMA.extend({ - vol.Optional(CONF_FEATURE_LIST, default=None): cv.ensure_list, -}) +FEATURE_SCHEMA = BASIC_INFO_SCHEMA.extend( + {vol.Optional(CONF_FEATURE_LIST, default=None): cv.ensure_list} +) -CODE_SCHEMA = BASIC_INFO_SCHEMA.extend({ - vol.Optional(ATTR_CODE, default=None): vol.Any(None, cv.string), -}) +CODE_SCHEMA = BASIC_INFO_SCHEMA.extend( + {vol.Optional(ATTR_CODE, default=None): vol.Any(None, cv.string)} +) -MEDIA_PLAYER_SCHEMA = vol.Schema({ - vol.Required(CONF_FEATURE): vol.All( - cv.string, vol.In((FEATURE_ON_OFF, FEATURE_PLAY_PAUSE, - FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE))), -}) +MEDIA_PLAYER_SCHEMA = vol.Schema( + { + vol.Required(CONF_FEATURE): vol.All( + cv.string, + vol.In( + ( + FEATURE_ON_OFF, + FEATURE_PLAY_PAUSE, + FEATURE_PLAY_STOP, + FEATURE_TOGGLE_MUTE, + ) + ), + ) + } +) -SWITCH_TYPE_SCHEMA = BASIC_INFO_SCHEMA.extend({ - vol.Optional(CONF_TYPE, default=TYPE_SWITCH): vol.All( - cv.string, vol.In(( - TYPE_FAUCET, TYPE_OUTLET, TYPE_SHOWER, TYPE_SPRINKLER, - TYPE_SWITCH, TYPE_VALVE))), -}) +SWITCH_TYPE_SCHEMA = BASIC_INFO_SCHEMA.extend( + { + vol.Optional(CONF_TYPE, default=TYPE_SWITCH): vol.All( + cv.string, + vol.In( + ( + TYPE_FAUCET, + TYPE_OUTLET, + TYPE_SHOWER, + TYPE_SPRINKLER, + TYPE_SWITCH, + TYPE_VALVE, + ) + ), + ) + } +) def validate_entity_config(values): """Validate config entry for CONF_ENTITY.""" if not isinstance(values, dict): - raise vol.Invalid('expected a dictionary') + raise vol.Invalid("expected a dictionary") entities = {} for entity_id, config in values.items(): @@ -61,10 +102,11 @@ def validate_entity_config(values): domain, _ = split_entity_id(entity) if not isinstance(config, dict): - raise vol.Invalid('The configuration for {} must be ' - ' a dictionary.'.format(entity)) + raise vol.Invalid( + "The configuration for {} must be " " a dictionary.".format(entity) + ) - if domain in ('alarm_control_panel', 'lock'): + if domain in ("alarm_control_panel", "lock"): config = CODE_SCHEMA(config) elif domain == media_player.const.DOMAIN: @@ -74,12 +116,13 @@ def validate_entity_config(values): params = MEDIA_PLAYER_SCHEMA(feature) key = params.pop(CONF_FEATURE) if key in feature_list: - raise vol.Invalid('A feature can be added only once for {}' - .format(entity)) + raise vol.Invalid( + "A feature can be added only once for {}".format(entity) + ) feature_list[key] = params config[CONF_FEATURE_LIST] = feature_list - elif domain == 'switch': + elif domain == "switch": config = SWITCH_TYPE_SCHEMA(config) else: @@ -94,14 +137,13 @@ def validate_media_player_features(state, feature_list): features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) supported_modes = [] - if features & (media_player.const.SUPPORT_TURN_ON | - media_player.const.SUPPORT_TURN_OFF): + if features & ( + media_player.const.SUPPORT_TURN_ON | media_player.const.SUPPORT_TURN_OFF + ): supported_modes.append(FEATURE_ON_OFF) - if features & (media_player.const.SUPPORT_PLAY | - media_player.const.SUPPORT_PAUSE): + if features & (media_player.const.SUPPORT_PLAY | media_player.const.SUPPORT_PAUSE): supported_modes.append(FEATURE_PLAY_PAUSE) - if features & (media_player.const.SUPPORT_PLAY | - media_player.const.SUPPORT_STOP): + if features & (media_player.const.SUPPORT_PLAY | media_player.const.SUPPORT_STOP): supported_modes.append(FEATURE_PLAY_STOP) if features & media_player.const.SUPPORT_VOLUME_MUTE: supported_modes.append(FEATURE_TOGGLE_MUTE) @@ -112,13 +154,12 @@ def validate_media_player_features(state, feature_list): error_list.append(feature) if error_list: - _LOGGER.error('%s does not support features: %s', - state.entity_id, error_list) + _LOGGER.error("%s does not support features: %s", state.entity_id, error_list) return False return True -SpeedRange = namedtuple('SpeedRange', ('start', 'target')) +SpeedRange = namedtuple("SpeedRange", ("start", "target")) SpeedRange.__doc__ += """ Maps Home Assistant speed \ values to percentage based HomeKit speeds. start: Start of the range (inclusive). @@ -133,10 +174,14 @@ class HomeKitSpeedMapping: def __init__(self, speed_list): """Initialize a new SpeedMapping object.""" if speed_list[0] != fan.SPEED_OFF: - _LOGGER.warning("%s does not contain the speed setting " - "%s as its first element. " - "Assuming that %s is equivalent to 'off'.", - speed_list, fan.SPEED_OFF, speed_list[0]) + _LOGGER.warning( + "%s does not contain the speed setting " + "%s as its first element. " + "Assuming that %s is equivalent to 'off'.", + speed_list, + fan.SPEED_OFF, + speed_list[0], + ) self.speed_ranges = OrderedDict() list_size = len(speed_list) for index, speed in enumerate(speed_list): @@ -167,11 +212,14 @@ class HomeKitSpeedMapping: def show_setup_message(hass, pincode): """Display persistent notification with setup information.""" pin = pincode.decode() - _LOGGER.info('Pincode: %s', pin) - message = 'To set up Home Assistant in the Home App, enter the ' \ - 'following code:\n### {}'.format(pin) + _LOGGER.info("Pincode: %s", pin) + message = ( + "To set up Home Assistant in the Home App, enter the " + "following code:\n### {}".format(pin) + ) hass.components.persistent_notification.create( - message, 'HomeKit Setup', HOMEKIT_NOTIFY_ID) + message, "HomeKit Setup", HOMEKIT_NOTIFY_ID + ) def dismiss_setup_message(hass): diff --git a/homeassistant/components/homekit_controller/__init__.py b/homeassistant/components/homekit_controller/__init__.py index f44243e2fc0..fd9c960980c 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -9,10 +9,8 @@ from homeassistant.helpers import device_registry as dr # We need an import from .config_flow, without it .config_flow is never loaded. from .config_flow import HomekitControllerFlowHandler # noqa: F401 from .connection import get_accessory_information, HKDevice -from .const import ( - CONTROLLER, ENTITY_MAP, KNOWN_DEVICES -) -from .const import DOMAIN # noqa: pylint: disable=unused-import +from .const import CONTROLLER, ENTITY_MAP, KNOWN_DEVICES +from .const import DOMAIN # noqa: pylint: disable=unused-import from .storage import EntityMapStorage _LOGGER = logging.getLogger(__name__) @@ -20,7 +18,7 @@ _LOGGER = logging.getLogger(__name__) def escape_characteristic_name(char_name): """Escape any dash or dots in a characteristics name.""" - return char_name.replace('-', '_').replace('.', '_') + return char_name.replace("-", "_").replace(".", "_") class HomeKitEntity(Entity): @@ -29,8 +27,8 @@ class HomeKitEntity(Entity): def __init__(self, accessory, devinfo): """Initialise a generic HomeKit device.""" self._accessory = accessory - self._aid = devinfo['aid'] - self._iid = devinfo['iid'] + self._aid = devinfo["aid"] + self._iid = devinfo["iid"] self._features = 0 self._chars = {} self.setup() @@ -41,20 +39,15 @@ class HomeKitEntity(Entity): """Entity added to hass.""" self._signals.append( self.hass.helpers.dispatcher.async_dispatcher_connect( - self._accessory.signal_state_updated, - self.async_state_changed, + self._accessory.signal_state_updated, self.async_state_changed ) ) - self._accessory.add_pollable_characteristics( - self.pollable_characteristics, - ) + self._accessory.add_pollable_characteristics(self.pollable_characteristics) async def async_will_remove_from_hass(self): """Prepare to be removed from hass.""" - self._accessory.remove_pollable_characteristics( - self._aid, - ) + self._accessory.remove_pollable_characteristics(self._aid) for signal_remove in self._signals: signal_remove() @@ -76,24 +69,22 @@ class HomeKitEntity(Entity): accessories = self._accessory.accessories get_uuid = CharacteristicsTypes.get_uuid - characteristic_types = [ - get_uuid(c) for c in self.get_characteristic_types() - ] + characteristic_types = [get_uuid(c) for c in self.get_characteristic_types()] self.pollable_characteristics = [] self._chars = {} self._char_names = {} for accessory in accessories: - if accessory['aid'] != self._aid: + if accessory["aid"] != self._aid: continue self._accessory_info = get_accessory_information(accessory) - for service in accessory['services']: - if service['iid'] != self._iid: + for service in accessory["services"]: + if service["iid"] != self._iid: continue - for char in service['characteristics']: + for char in service["characteristics"]: try: - uuid = CharacteristicsTypes.get_uuid(char['type']) + uuid = CharacteristicsTypes.get_uuid(char["type"]) except KeyError: # If a KeyError is raised its a non-standard # characteristic. We must ignore it in this case. @@ -108,17 +99,17 @@ class HomeKitEntity(Entity): from homekit.model.characteristics import CharacteristicsTypes # Build up a list of (aid, iid) tuples to poll on update() - self.pollable_characteristics.append((self._aid, char['iid'])) + self.pollable_characteristics.append((self._aid, char["iid"])) # Build a map of ctype -> iid - short_name = CharacteristicsTypes.get_short(char['type']) - self._chars[short_name] = char['iid'] - self._char_names[char['iid']] = short_name + short_name = CharacteristicsTypes.get_short(char["type"]) + self._chars[short_name] = char["iid"] + self._char_names[char["iid"]] = short_name # Callback to allow entity to configure itself based on this # characteristics metadata (valid values, value ranges, features, etc) setup_fn_name = escape_characteristic_name(short_name) - setup_fn = getattr(self, '_setup_{}'.format(setup_fn_name), None) + setup_fn = getattr(self, "_setup_{}".format(setup_fn_name), None) if not setup_fn: return # pylint: disable=not-callable @@ -129,28 +120,28 @@ class HomeKitEntity(Entity): """Collect new data from bridge and update the entity state in hass.""" accessory_state = self._accessory.current_state.get(self._aid, {}) for iid, result in accessory_state.items(): - if 'value' not in result: + if "value" not in result: continue # Callback to update the entity with this characteristic value char_name = escape_characteristic_name(self._char_names[iid]) - update_fn = getattr(self, '_update_{}'.format(char_name), None) + update_fn = getattr(self, "_update_{}".format(char_name), None) if not update_fn: continue # pylint: disable=not-callable - update_fn(result['value']) + update_fn(result["value"]) self.async_write_ha_state() @property def unique_id(self): """Return the ID of this device.""" - serial = self._accessory_info['serial-number'] + serial = self._accessory_info["serial-number"] return "homekit-{}-{}".format(serial, self._iid) @property def name(self): """Return the name of the device if any.""" - return self._accessory_info.get('name') + return self._accessory_info.get("name") @property def available(self) -> bool: @@ -160,24 +151,21 @@ class HomeKitEntity(Entity): @property def device_info(self): """Return the device info.""" - accessory_serial = self._accessory_info['serial-number'] + accessory_serial = self._accessory_info["serial-number"] device_info = { - 'identifiers': { - (DOMAIN, 'serial-number', accessory_serial), - }, - 'name': self._accessory_info['name'], - 'manufacturer': self._accessory_info.get('manufacturer', ''), - 'model': self._accessory_info.get('model', ''), - 'sw_version': self._accessory_info.get('firmware.revision', ''), + "identifiers": {(DOMAIN, "serial-number", accessory_serial)}, + "name": self._accessory_info["name"], + "manufacturer": self._accessory_info.get("manufacturer", ""), + "model": self._accessory_info.get("model", ""), + "sw_version": self._accessory_info.get("firmware.revision", ""), } # Some devices only have a single accessory - we don't add a # via_device otherwise it would be self referential. - bridge_serial = self._accessory.connection_info['serial-number'] + bridge_serial = self._accessory.connection_info["serial-number"] if accessory_serial != bridge_serial: - device_info['via_device'] = ( - DOMAIN, 'serial-number', bridge_serial) + device_info["via_device"] = (DOMAIN, "serial-number", bridge_serial) return device_info @@ -201,13 +189,13 @@ async def async_setup_entry(hass, entry): device_registry.async_get_or_create( config_entry_id=entry.entry_id, identifiers={ - (DOMAIN, 'serial-number', conn_info['serial-number']), - (DOMAIN, 'accessory-id', conn.unique_id), + (DOMAIN, "serial-number", conn_info["serial-number"]), + (DOMAIN, "accessory-id", conn.unique_id), }, name=conn.name, - manufacturer=conn_info.get('manufacturer'), - model=conn_info.get('model'), - sw_version=conn_info.get('firmware.revision'), + manufacturer=conn_info.get("manufacturer"), + model=conn_info.get("model"), + sw_version=conn_info.get("firmware.revision"), ) return True @@ -229,7 +217,7 @@ async def async_setup(hass, config): async def async_unload_entry(hass, entry): """Disconnect from HomeKit devices before unloading entry.""" - hkid = entry.data['AccessoryPairingID'] + hkid = entry.data["AccessoryPairingID"] if hkid in hass.data[KNOWN_DEVICES]: connection = hass.data[KNOWN_DEVICES][hkid] @@ -240,5 +228,5 @@ async def async_unload_entry(hass, entry): async def async_remove_entry(hass, entry): """Cleanup caches before removing config entry.""" - hkid = entry.data['AccessoryPairingID'] + hkid = entry.data["AccessoryPairingID"] hass.data[ENTITY_MAP].async_delete_map(hkid) diff --git a/homeassistant/components/homekit_controller/alarm_control_panel.py b/homeassistant/components/homekit_controller/alarm_control_panel.py index 93279bd626e..38ed064f374 100644 --- a/homeassistant/components/homekit_controller/alarm_control_panel.py +++ b/homeassistant/components/homekit_controller/alarm_control_panel.py @@ -3,12 +3,17 @@ import logging from homeassistant.components.alarm_control_panel import AlarmControlPanel from homeassistant.const import ( - ATTR_BATTERY_LEVEL, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, - STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED) + ATTR_BATTERY_LEVEL, + STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_HOME, + STATE_ALARM_ARMED_NIGHT, + STATE_ALARM_DISARMED, + STATE_ALARM_TRIGGERED, +) from . import KNOWN_DEVICES, HomeKitEntity -ICON = 'mdi:security' +ICON = "mdi:security" _LOGGER = logging.getLogger(__name__) @@ -28,21 +33,20 @@ TARGET_STATE_MAP = { } -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Legacy set up platform.""" pass async def async_setup_entry(hass, config_entry, async_add_entities): """Set up Homekit alarm control panel.""" - hkid = config_entry.data['AccessoryPairingID'] + hkid = config_entry.data["AccessoryPairingID"] conn = hass.data[KNOWN_DEVICES][hkid] def async_add_service(aid, service): - if service['stype'] != 'security-system': + if service["stype"] != "security-system": return False - info = {'aid': aid, 'iid': service['iid']} + info = {"aid": aid, "iid": service["iid"]} async_add_entities([HomeKitAlarmControlPanel(conn, info)], True) return True @@ -62,6 +66,7 @@ class HomeKitAlarmControlPanel(HomeKitEntity, AlarmControlPanel): """Define the homekit characteristics the entity cares about.""" # pylint: disable=import-error from homekit.model.characteristics import CharacteristicsTypes + return [ CharacteristicsTypes.SECURITY_SYSTEM_STATE_CURRENT, CharacteristicsTypes.SECURITY_SYSTEM_STATE_TARGET, @@ -102,9 +107,13 @@ class HomeKitAlarmControlPanel(HomeKitEntity, AlarmControlPanel): async def set_alarm_state(self, state, code=None): """Send state command.""" - characteristics = [{'aid': self._aid, - 'iid': self._chars['security-system-state.target'], - 'value': TARGET_STATE_MAP[state]}] + characteristics = [ + { + "aid": self._aid, + "iid": self._chars["security-system-state.target"], + "value": TARGET_STATE_MAP[state], + } + ] await self._accessory.put_characteristics(characteristics) @property @@ -113,6 +122,4 @@ class HomeKitAlarmControlPanel(HomeKitEntity, AlarmControlPanel): if self._battery_level is None: return None - return { - ATTR_BATTERY_LEVEL: self._battery_level, - } + return {ATTR_BATTERY_LEVEL: self._battery_level} diff --git a/homeassistant/components/homekit_controller/binary_sensor.py b/homeassistant/components/homekit_controller/binary_sensor.py index 74b5bcab724..1e1c8ef5d44 100644 --- a/homeassistant/components/homekit_controller/binary_sensor.py +++ b/homeassistant/components/homekit_controller/binary_sensor.py @@ -20,9 +20,7 @@ class HomeKitMotionSensor(HomeKitEntity, BinarySensorDevice): def get_characteristic_types(self): """Define the homekit characteristics the entity is tracking.""" - return [ - CharacteristicsTypes.MOTION_DETECTED, - ] + return [CharacteristicsTypes.MOTION_DETECTED] def _update_motion_detected(self, value): self._on = value @@ -30,7 +28,7 @@ class HomeKitMotionSensor(HomeKitEntity, BinarySensorDevice): @property def device_class(self): """Define this binary_sensor as a motion sensor.""" - return 'motion' + return "motion" @property def is_on(self): @@ -48,9 +46,7 @@ class HomeKitContactSensor(HomeKitEntity, BinarySensorDevice): def get_characteristic_types(self): """Define the homekit characteristics the entity is tracking.""" - return [ - CharacteristicsTypes.CONTACT_STATE, - ] + return [CharacteristicsTypes.CONTACT_STATE] def _update_contact_state(self, value): self._state = value @@ -61,28 +57,24 @@ class HomeKitContactSensor(HomeKitEntity, BinarySensorDevice): return self._state == 1 -ENTITY_TYPES = { - 'motion': HomeKitMotionSensor, - 'contact': HomeKitContactSensor, -} +ENTITY_TYPES = {"motion": HomeKitMotionSensor, "contact": HomeKitContactSensor} -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Legacy set up platform.""" pass async def async_setup_entry(hass, config_entry, async_add_entities): """Set up Homekit lighting.""" - hkid = config_entry.data['AccessoryPairingID'] + hkid = config_entry.data["AccessoryPairingID"] conn = hass.data[KNOWN_DEVICES][hkid] def async_add_service(aid, service): - entity_class = ENTITY_TYPES.get(service['stype']) + entity_class = ENTITY_TYPES.get(service["stype"]) if not entity_class: return False - info = {'aid': aid, 'iid': service['iid']} + info = {"aid": aid, "iid": service["iid"]} async_add_entities([entity_class(conn, info)], True) return True diff --git a/homeassistant/components/homekit_controller/climate.py b/homeassistant/components/homekit_controller/climate.py index d57c3a97971..d295f607d71 100644 --- a/homeassistant/components/homekit_controller/climate.py +++ b/homeassistant/components/homekit_controller/climate.py @@ -2,12 +2,21 @@ import logging from homeassistant.components.climate import ( - ClimateDevice, DEFAULT_MIN_HUMIDITY, DEFAULT_MAX_HUMIDITY, + ClimateDevice, + DEFAULT_MIN_HUMIDITY, + DEFAULT_MAX_HUMIDITY, ) from homeassistant.components.climate.const import ( - HVAC_MODE_HEAT_COOL, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF, - CURRENT_HVAC_OFF, CURRENT_HVAC_HEAT, CURRENT_HVAC_COOL, - SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_HUMIDITY) + HVAC_MODE_HEAT_COOL, + HVAC_MODE_COOL, + HVAC_MODE_HEAT, + HVAC_MODE_OFF, + CURRENT_HVAC_OFF, + CURRENT_HVAC_HEAT, + CURRENT_HVAC_COOL, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_TARGET_HUMIDITY, +) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS from . import KNOWN_DEVICES, HomeKitEntity @@ -34,21 +43,20 @@ CURRENT_MODE_HOMEKIT_TO_HASS = { } -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Legacy set up platform.""" pass async def async_setup_entry(hass, config_entry, async_add_entities): """Set up Homekit climate.""" - hkid = config_entry.data['AccessoryPairingID'] + hkid = config_entry.data["AccessoryPairingID"] conn = hass.data[KNOWN_DEVICES][hkid] def async_add_service(aid, service): - if service['stype'] != 'thermostat': + if service["stype"] != "thermostat": return False - info = {'aid': aid, 'iid': service['iid']} + info = {"aid": aid, "iid": service["iid"]} async_add_entities([HomeKitClimateDevice(conn, info)], True) return True @@ -78,6 +86,7 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice): """Define the homekit characteristics the entity cares about.""" # pylint: disable=import-error from homekit.model.characteristics import CharacteristicsTypes + return [ CharacteristicsTypes.HEATING_COOLING_CURRENT, CharacteristicsTypes.HEATING_COOLING_TARGET, @@ -88,45 +97,42 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice): ] def _setup_heating_cooling_target(self, characteristic): - if 'valid-values' in characteristic: + if "valid-values" in characteristic: valid_values = [ - val for val in DEFAULT_VALID_MODES - if val in characteristic['valid-values'] + val + for val in DEFAULT_VALID_MODES + if val in characteristic["valid-values"] ] else: valid_values = DEFAULT_VALID_MODES - if 'minValue' in characteristic: + if "minValue" in characteristic: valid_values = [ - val for val in valid_values - if val >= characteristic['minValue'] + val for val in valid_values if val >= characteristic["minValue"] ] - if 'maxValue' in characteristic: + if "maxValue" in characteristic: valid_values = [ - val for val in valid_values - if val <= characteristic['maxValue'] + val for val in valid_values if val <= characteristic["maxValue"] ] - self._valid_modes = [ - MODE_HOMEKIT_TO_HASS[mode] for mode in valid_values - ] + self._valid_modes = [MODE_HOMEKIT_TO_HASS[mode] for mode in valid_values] def _setup_temperature_target(self, characteristic): self._features |= SUPPORT_TARGET_TEMPERATURE - if 'minValue' in characteristic: - self._min_target_temp = characteristic['minValue'] + if "minValue" in characteristic: + self._min_target_temp = characteristic["minValue"] - if 'maxValue' in characteristic: - self._max_target_temp = characteristic['maxValue'] + if "maxValue" in characteristic: + self._max_target_temp = characteristic["maxValue"] def _setup_relative_humidity_target(self, characteristic): self._features |= SUPPORT_TARGET_HUMIDITY - if 'minValue' in characteristic: - self._min_target_humidity = characteristic['minValue'] + if "minValue" in characteristic: + self._min_target_humidity = characteristic["minValue"] - if 'maxValue' in characteristic: - self._max_target_humidity = characteristic['maxValue'] + if "maxValue" in characteristic: + self._max_target_humidity = characteristic["maxValue"] def _update_heating_cooling_current(self, value): # This characteristic describes the current mode of a device, @@ -157,23 +163,31 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice): """Set new target temperature.""" temp = kwargs.get(ATTR_TEMPERATURE) - characteristics = [{'aid': self._aid, - 'iid': self._chars['temperature.target'], - 'value': temp}] + characteristics = [ + {"aid": self._aid, "iid": self._chars["temperature.target"], "value": temp} + ] await self._accessory.put_characteristics(characteristics) async def async_set_humidity(self, humidity): """Set new target humidity.""" - characteristics = [{'aid': self._aid, - 'iid': self._chars['relative-humidity.target'], - 'value': humidity}] + characteristics = [ + { + "aid": self._aid, + "iid": self._chars["relative-humidity.target"], + "value": humidity, + } + ] await self._accessory.put_characteristics(characteristics) async def async_set_hvac_mode(self, hvac_mode): """Set new target operation mode.""" - characteristics = [{'aid': self._aid, - 'iid': self._chars['heating-cooling.target'], - 'value': MODE_HASS_TO_HOMEKIT[hvac_mode]}] + characteristics = [ + { + "aid": self._aid, + "iid": self._chars["heating-cooling.target"], + "value": MODE_HASS_TO_HOMEKIT[hvac_mode], + } + ] await self._accessory.put_characteristics(characteristics) @property diff --git a/homeassistant/components/homekit_controller/config_flow.py b/homeassistant/components/homekit_controller/config_flow.py index 9ddb144ec9a..00214282123 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -12,11 +12,9 @@ from .const import DOMAIN, KNOWN_DEVICES from .connection import get_bridge_information, get_accessory_name -HOMEKIT_IGNORE = [ - 'Home Assistant Bridge', -] -HOMEKIT_DIR = '.homekit' -PAIRING_FILE = 'pairing.json' +HOMEKIT_IGNORE = ["Home Assistant Bridge"] +HOMEKIT_DIR = ".homekit" +PAIRING_FILE = "pairing.json" _LOGGER = logging.getLogger(__name__) @@ -36,7 +34,7 @@ def load_old_pairings(hass): # Find any pairings created in HA <= 0.84 if os.path.exists(data_dir): for device in os.listdir(data_dir): - if not device.startswith('hk-'): + if not device.startswith("hk-"): continue alias = device[3:] if alias in old_pairings: @@ -51,7 +49,7 @@ def load_old_pairings(hass): def find_existing_host(hass, serial): """Return a set of the configured hosts.""" for entry in hass.config_entries.async_entries(DOMAIN): - if entry.data['AccessoryPairingID'] == serial: + if entry.data["AccessoryPairingID"] == serial: return entry @@ -77,34 +75,30 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow): errors = {} if user_input is not None: - key = user_input['device'] - self.hkid = self.devices[key]['id'] - self.model = self.devices[key]['md'] + key = user_input["device"] + self.hkid = self.devices[key]["id"] + self.model = self.devices[key]["md"] return await self.async_step_pair() - all_hosts = await self.hass.async_add_executor_job( - self.controller.discover, 5 - ) + all_hosts = await self.hass.async_add_executor_job(self.controller.discover, 5) self.devices = {} for host in all_hosts: - status_flags = int(host['sf']) + status_flags = int(host["sf"]) paired = not status_flags & 0x01 if paired: continue - self.devices[host['name']] = host + self.devices[host["name"]] = host if not self.devices: - return self.async_abort( - reason='no_devices' - ) + return self.async_abort(reason="no_devices") return self.async_show_form( - step_id='user', + step_id="user", errors=errors, - data_schema=vol.Schema({ - vol.Required('device'): vol.In(self.devices.keys()), - }) + data_schema=vol.Schema( + {vol.Required("device"): vol.In(self.devices.keys())} + ), ) async def async_step_zeroconf(self, discovery_info): @@ -116,41 +110,38 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow): # homekit_python has code to do this, but not in a form we can # easily use, so do the bare minimum ourselves here instead. properties = { - key.lower(): value - for (key, value) in discovery_info['properties'].items() + key.lower(): value for (key, value) in discovery_info["properties"].items() } # The hkid is a unique random number that looks like a pairing code. # It changes if a device is factory reset. - hkid = properties['id'] - model = properties['md'] - name = discovery_info['name'].replace('._hap._tcp.local.', '') - status_flags = int(properties['sf']) + hkid = properties["id"] + model = properties["md"] + name = discovery_info["name"].replace("._hap._tcp.local.", "") + status_flags = int(properties["sf"]) paired = not status_flags & 0x01 _LOGGER.debug("Discovered device %s (%s - %s)", name, model, hkid) # pylint: disable=unsupported-assignment-operation - self.context['hkid'] = hkid - self.context['title_placeholders'] = { - 'name': name, - } + self.context["hkid"] = hkid + self.context["title_placeholders"] = {"name": name} # If multiple HomekitControllerFlowHandler end up getting created # for the same accessory dont let duplicates hang around active_flows = self._async_in_progress() - if any(hkid == flow['context']['hkid'] for flow in active_flows): - return self.async_abort(reason='already_in_progress') + if any(hkid == flow["context"]["hkid"] for flow in active_flows): + return self.async_abort(reason="already_in_progress") # The configuration number increases every time the characteristic map # needs updating. Some devices use a slightly off-spec name so handle # both cases. try: - config_num = int(properties['c#']) + config_num = int(properties["c#"]) except KeyError: _LOGGER.warning( - "HomeKit device %s: c# not exposed, in violation of spec", - hkid) + "HomeKit device %s: c# not exposed, in violation of spec", hkid + ) config_num = None if paired: @@ -161,32 +152,31 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow): conn = self.hass.data[KNOWN_DEVICES][hkid] if conn.config_num != config_num: _LOGGER.debug( - "HomeKit info %s: c# incremented, refreshing entities", - hkid) + "HomeKit info %s: c# incremented, refreshing entities", hkid + ) self.hass.async_create_task( - conn.async_refresh_entity_map(config_num)) - return self.async_abort(reason='already_configured') + conn.async_refresh_entity_map(config_num) + ) + return self.async_abort(reason="already_configured") old_pairings = await self.hass.async_add_executor_job( - load_old_pairings, - self.hass + load_old_pairings, self.hass ) if hkid in old_pairings: return await self.async_import_legacy_pairing( - properties, - old_pairings[hkid] + properties, old_pairings[hkid] ) # Device is paired but not to us - ignore it _LOGGER.debug("HomeKit device %s ignored as already paired", hkid) - return self.async_abort(reason='already_paired') + return self.async_abort(reason="already_paired") # Devices in HOMEKIT_IGNORE have native local integrations - users # should be encouraged to use native integration and not confused # by alternative HK API. if model in HOMEKIT_IGNORE: - return self.async_abort(reason='ignored_model') + return self.async_abort(reason="ignored_model") # Device isn't paired with us or anyone else. # But we have a 'complete' config entry for it - that is probably @@ -207,18 +197,26 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow): """Migrate a legacy pairing to config entries.""" from homekit.controller.ip_implementation import IpPairing - hkid = discovery_props['id'] + hkid = discovery_props["id"] existing = find_existing_host(self.hass, hkid) if existing: _LOGGER.info( - ("Legacy configuration for homekit accessory %s" - "not loaded as already migrated"), hkid) - return self.async_abort(reason='already_configured') + ( + "Legacy configuration for homekit accessory %s" + "not loaded as already migrated" + ), + hkid, + ) + return self.async_abort(reason="already_configured") _LOGGER.info( - ("Legacy configuration %s for homekit" - "accessory migrated to config entries"), hkid) + ( + "Legacy configuration %s for homekit" + "accessory migrated to config entries" + ), + hkid, + ) pairing = IpPairing(pairing_data) @@ -247,40 +245,35 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow): errors = {} if pair_info: - code = pair_info['pairing_code'] + code = pair_info["pairing_code"] try: - await self.hass.async_add_executor_job( - self.finish_pairing, code - ) + await self.hass.async_add_executor_job(self.finish_pairing, code) pairing = self.controller.pairings.get(self.hkid) if pairing: - return await self._entry_from_accessory( - pairing) + return await self._entry_from_accessory(pairing) - errors['pairing_code'] = 'unable_to_pair' + errors["pairing_code"] = "unable_to_pair" except homekit.AuthenticationError: # PairSetup M4 - SRP proof failed # PairSetup M6 - Ed25519 signature verification failed # PairVerify M4 - Decryption failed # PairVerify M4 - Device not recognised # PairVerify M4 - Ed25519 signature verification failed - errors['pairing_code'] = 'authentication_error' + errors["pairing_code"] = "authentication_error" except homekit.UnknownError: # An error occured on the device whilst performing this # operation. - errors['pairing_code'] = 'unknown_error' + errors["pairing_code"] = "unknown_error" except homekit.MaxPeersError: # The device can't pair with any more accessories. - errors['pairing_code'] = 'max_peers_error' + errors["pairing_code"] = "max_peers_error" except homekit.AccessoryNotFoundError: # Can no longer find the device on the network - return self.async_abort(reason='accessory_not_found_error') + return self.async_abort(reason="accessory_not_found_error") except Exception: # pylint: disable=broad-except - _LOGGER.exception( - "Pairing attempt failed with an unhandled exception" - ) - errors['pairing_code'] = 'pairing_failed' + _LOGGER.exception("Pairing attempt failed with an unhandled exception") + errors["pairing_code"] = "pairing_failed" start_pairing = self.controller.start_pairing try: @@ -290,32 +283,30 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow): except homekit.BusyError: # Already performing a pair setup operation with a different # controller - errors['pairing_code'] = 'busy_error' + errors["pairing_code"] = "busy_error" except homekit.MaxTriesError: # The accessory has received more than 100 unsuccessful auth # attempts. - errors['pairing_code'] = 'max_tries_error' + errors["pairing_code"] = "max_tries_error" except homekit.UnavailableError: # The accessory is already paired - cannot try to pair again. - return self.async_abort(reason='already_paired') + return self.async_abort(reason="already_paired") except homekit.AccessoryNotFoundError: # Can no longer find the device on the network - return self.async_abort(reason='accessory_not_found_error') + return self.async_abort(reason="accessory_not_found_error") except Exception: # pylint: disable=broad-except - _LOGGER.exception( - "Pairing attempt failed with an unhandled exception" - ) - errors['pairing_code'] = 'pairing_failed' + _LOGGER.exception("Pairing attempt failed with an unhandled exception") + errors["pairing_code"] = "pairing_failed" return self._async_step_pair_show_form(errors) def _async_step_pair_show_form(self, errors=None): return self.async_show_form( - step_id='pair', + step_id="pair", errors=errors or {}, - data_schema=vol.Schema({ - vol.Required('pairing_code'): vol.All(str, vol.Strip), - }) + data_schema=vol.Schema( + {vol.Required("pairing_code"): vol.All(str, vol.Strip)} + ), ) async def _entry_from_accessory(self, pairing): @@ -330,7 +321,7 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow): # available. Otherwise request a fresh copy from the API. # This removes the 'accessories' key from pairing_data at # the same time. - accessories = pairing_data.pop('accessories', None) + accessories = pairing_data.pop("accessories", None) if not accessories: accessories = await self.hass.async_add_executor_job( pairing.list_accessories_and_characteristics @@ -339,7 +330,4 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow): bridge_info = get_bridge_information(accessories) name = get_accessory_name(bridge_info) - return self.async_create_entry( - title=name, - data=pairing_data, - ) + return self.async_create_entry(title=name, data=pairing_data) diff --git a/homeassistant/components/homekit_controller/connection.py b/homeassistant/components/homekit_controller/connection.py index d46699eb6b5..c4c00cb384b 100644 --- a/homeassistant/components/homekit_controller/connection.py +++ b/homeassistant/components/homekit_controller/connection.py @@ -21,34 +21,34 @@ def get_accessory_information(accessory): from homekit.model.characteristics import CharacteristicsTypes result = {} - for service in accessory['services']: - stype = service['type'].upper() - if ServicesTypes.get_short(stype) != 'accessory-information': + for service in accessory["services"]: + stype = service["type"].upper() + if ServicesTypes.get_short(stype) != "accessory-information": continue - for characteristic in service['characteristics']: - ctype = CharacteristicsTypes.get_short(characteristic['type']) - if 'value' in characteristic: - result[ctype] = characteristic['value'] + for characteristic in service["characteristics"]: + ctype = CharacteristicsTypes.get_short(characteristic["type"]) + if "value" in characteristic: + result[ctype] = characteristic["value"] return result def get_bridge_information(accessories): """Return the accessory info for the bridge.""" for accessory in accessories: - if accessory['aid'] == 1: + if accessory["aid"] == 1: return get_accessory_information(accessory) return get_accessory_information(accessories[0]) def get_accessory_name(accessory_info): """Return the name field of an accessory.""" - for field in ('name', 'model', 'manufacturer'): + for field in ("name", "model", "manufacturer"): if field in accessory_info: return accessory_info[field] return None -class HKDevice(): +class HKDevice: """HomeKit device.""" def __init__(self, hass, config_entry, pairing_data): @@ -87,11 +87,7 @@ class HKDevice(): self.available = True - self.signal_state_updated = '_'.join(( - DOMAIN, - self.unique_id, - 'state_updated', - )) + self.signal_state_updated = "_".join((DOMAIN, self.unique_id, "state_updated")) # Current values of all characteristics homekit_controller is tracking. # Key is a (accessory_id, characteristic_id) tuple. @@ -110,16 +106,13 @@ class HKDevice(): def remove_pollable_characteristics(self, accessory_id): """Remove all pollable characteristics by accessory id.""" self.pollable_characteristics = [ - char for char in self.pollable_characteristics - if char[0] != accessory_id + char for char in self.pollable_characteristics if char[0] != accessory_id ] def async_set_unavailable(self): """Mark state of all entities on this connection as unavailable.""" self.available = False - self.hass.helpers.dispatcher.async_dispatcher_send( - self.signal_state_updated, - ) + self.hass.helpers.dispatcher.async_dispatcher_send(self.signal_state_updated) async def async_setup(self): """Prepare to use a paired HomeKit device in homeassistant.""" @@ -127,19 +120,17 @@ class HKDevice(): if not cache: if await self.async_refresh_entity_map(self.config_num): self._polling_interval_remover = async_track_time_interval( - self.hass, - self.async_update, - DEFAULT_SCAN_INTERVAL + self.hass, self.async_update, DEFAULT_SCAN_INTERVAL ) return True return False - self.accessories = cache['accessories'] - self.config_num = cache['config_num'] + self.accessories = cache["accessories"] + self.config_num = cache["config_num"] # Ensure the Pairing object has access to the latest version of the # entity map. - self.pairing.pairing_data['accessories'] = self.accessories + self.pairing.pairing_data["accessories"] = self.accessories self.async_load_platforms() @@ -148,9 +139,7 @@ class HKDevice(): await self.async_update() self._polling_interval_remover = async_track_time_interval( - self.hass, - self.async_update, - DEFAULT_SCAN_INTERVAL + self.hass, self.async_update, DEFAULT_SCAN_INTERVAL ) return True @@ -164,8 +153,7 @@ class HKDevice(): for platform in self.platforms: unloads.append( self.hass.config_entries.async_forward_entry_unload( - self.config_entry, - platform + self.config_entry, platform ) ) @@ -189,16 +177,14 @@ class HKDevice(): return self.hass.data[ENTITY_MAP].async_create_or_update_map( - self.unique_id, - config_num, - self.accessories, + self.unique_id, config_num, self.accessories ) self.config_num = config_num # For BLE, the Pairing instance relies on the entity map to map # aid/iid to GATT characteristics. So push it to there as well. - self.pairing.pairing_data['accessories'] = self.accessories + self.pairing.pairing_data["accessories"] = self.accessories self.async_load_platforms() @@ -222,11 +208,11 @@ class HKDevice(): from homekit.model.services import ServicesTypes for accessory in self.accessories: - aid = accessory['aid'] - for service in accessory['services']: - iid = service['iid'] - stype = ServicesTypes.get_short(service['type'].upper()) - service['stype'] = stype + aid = accessory["aid"] + for service in accessory["services"]: + iid = service["iid"] + stype = ServicesTypes.get_short(service["type"].upper()) + service["stype"] = stype if (aid, iid) in self.entities: # Don't add the same entity again @@ -242,8 +228,8 @@ class HKDevice(): from homekit.model.services import ServicesTypes for accessory in self.accessories: - for service in accessory['services']: - stype = ServicesTypes.get_short(service['type'].upper()) + for service in accessory["services"]: + stype = ServicesTypes.get_short(service["type"].upper()) if stype not in HOMEKIT_ACCESSORY_DISPATCH: continue @@ -253,8 +239,7 @@ class HKDevice(): self.hass.async_create_task( self.hass.config_entries.async_forward_entry_setup( - self.config_entry, - platform, + self.config_entry, platform ) ) self.platforms.add(platform) @@ -263,20 +248,20 @@ class HKDevice(): """Poll state of all entities attached to this bridge/accessory.""" # pylint: disable=import-error from homekit.exceptions import ( - AccessoryDisconnectedError, AccessoryNotFoundError, - EncryptionError) + AccessoryDisconnectedError, + AccessoryNotFoundError, + EncryptionError, + ) if not self.pollable_characteristics: - _LOGGER.debug( - "HomeKit connection not polling any characteristics." - ) + _LOGGER.debug("HomeKit connection not polling any characteristics.") return _LOGGER.debug("Starting HomeKit controller update") try: new_values_dict = await self.get_characteristics( - self.pollable_characteristics, + self.pollable_characteristics ) except AccessoryNotFoundError: # Not only did the connection fail, but also the accessory is not @@ -294,9 +279,7 @@ class HKDevice(): accessory = self.current_state.setdefault(aid, {}) accessory[cid] = value - self.hass.helpers.dispatcher.async_dispatcher_send( - self.signal_state_updated, - ) + self.hass.helpers.dispatcher.async_dispatcher_send(self.signal_state_updated) _LOGGER.debug("Finished HomeKit controller update") @@ -304,9 +287,7 @@ class HKDevice(): """Read latest state from homekit accessory.""" async with self.pairing_lock: chars = await self.hass.async_add_executor_job( - self.pairing.get_characteristics, - *args, - **kwargs, + self.pairing.get_characteristics, *args, **kwargs ) return chars @@ -314,16 +295,11 @@ class HKDevice(): """Control a HomeKit device state from Home Assistant.""" chars = [] for row in characteristics: - chars.append(( - row['aid'], - row['iid'], - row['value'], - )) + chars.append((row["aid"], row["iid"], row["value"])) async with self.pairing_lock: await self.hass.async_add_executor_job( - self.pairing.put_characteristics, - chars + self.pairing.put_characteristics, chars ) @property @@ -333,7 +309,7 @@ class HKDevice(): This id is random and will change if a device undergoes a hard reset. """ - return self.pairing_data['AccessoryPairingID'] + return self.pairing_data["AccessoryPairingID"] @property def connection_info(self): diff --git a/homeassistant/components/homekit_controller/const.py b/homeassistant/components/homekit_controller/const.py index de37e6bace8..924d27218a2 100644 --- a/homeassistant/components/homekit_controller/const.py +++ b/homeassistant/components/homekit_controller/const.py @@ -1,27 +1,27 @@ """Constants for the homekit_controller component.""" -DOMAIN = 'homekit_controller' +DOMAIN = "homekit_controller" KNOWN_DEVICES = "{}-devices".format(DOMAIN) CONTROLLER = "{}-controller".format(DOMAIN) -ENTITY_MAP = '{}-entity-map'.format(DOMAIN) +ENTITY_MAP = "{}-entity-map".format(DOMAIN) -HOMEKIT_DIR = '.homekit' -PAIRING_FILE = 'pairing.json' +HOMEKIT_DIR = ".homekit" +PAIRING_FILE = "pairing.json" # Mapping from Homekit type to component. HOMEKIT_ACCESSORY_DISPATCH = { - 'lightbulb': 'light', - 'outlet': 'switch', - 'switch': 'switch', - 'thermostat': 'climate', - 'security-system': 'alarm_control_panel', - 'garage-door-opener': 'cover', - 'window': 'cover', - 'window-covering': 'cover', - 'lock-mechanism': 'lock', - 'contact': 'binary_sensor', - 'motion': 'binary_sensor', - 'humidity': 'sensor', - 'light': 'sensor', - 'temperature': 'sensor' + "lightbulb": "light", + "outlet": "switch", + "switch": "switch", + "thermostat": "climate", + "security-system": "alarm_control_panel", + "garage-door-opener": "cover", + "window": "cover", + "window-covering": "cover", + "lock-mechanism": "lock", + "contact": "binary_sensor", + "motion": "binary_sensor", + "humidity": "sensor", + "light": "sensor", + "temperature": "sensor", } diff --git a/homeassistant/components/homekit_controller/cover.py b/homeassistant/components/homekit_controller/cover.py index 7f3761d33a4..c15e0c092ac 100644 --- a/homeassistant/components/homekit_controller/cover.py +++ b/homeassistant/components/homekit_controller/cover.py @@ -2,15 +2,22 @@ import logging from homeassistant.components.cover import ( - ATTR_POSITION, ATTR_TILT_POSITION, SUPPORT_CLOSE, SUPPORT_CLOSE_TILT, - SUPPORT_OPEN, SUPPORT_OPEN_TILT, SUPPORT_SET_POSITION, SUPPORT_STOP, - SUPPORT_SET_TILT_POSITION, CoverDevice) -from homeassistant.const import ( - STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_OPENING) + ATTR_POSITION, + ATTR_TILT_POSITION, + SUPPORT_CLOSE, + SUPPORT_CLOSE_TILT, + SUPPORT_OPEN, + SUPPORT_OPEN_TILT, + SUPPORT_SET_POSITION, + SUPPORT_STOP, + SUPPORT_SET_TILT_POSITION, + CoverDevice, +) +from homeassistant.const import STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_OPENING from . import KNOWN_DEVICES, HomeKitEntity -STATE_STOPPED = 'stopped' +STATE_STOPPED = "stopped" _LOGGER = logging.getLogger(__name__) @@ -19,40 +26,31 @@ CURRENT_GARAGE_STATE_MAP = { 1: STATE_CLOSED, 2: STATE_OPENING, 3: STATE_CLOSING, - 4: STATE_STOPPED + 4: STATE_STOPPED, } -TARGET_GARAGE_STATE_MAP = { - STATE_OPEN: 0, - STATE_CLOSED: 1, - STATE_STOPPED: 2 -} +TARGET_GARAGE_STATE_MAP = {STATE_OPEN: 0, STATE_CLOSED: 1, STATE_STOPPED: 2} -CURRENT_WINDOW_STATE_MAP = { - 0: STATE_OPENING, - 1: STATE_CLOSING, - 2: STATE_STOPPED -} +CURRENT_WINDOW_STATE_MAP = {0: STATE_OPENING, 1: STATE_CLOSING, 2: STATE_STOPPED} -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Legacy set up platform.""" pass async def async_setup_entry(hass, config_entry, async_add_entities): """Set up Homekit covers.""" - hkid = config_entry.data['AccessoryPairingID'] + hkid = config_entry.data["AccessoryPairingID"] conn = hass.data[KNOWN_DEVICES][hkid] def async_add_service(aid, service): - info = {'aid': aid, 'iid': service['iid']} - if service['stype'] == 'garage-door-opener': + info = {"aid": aid, "iid": service["iid"]} + if service["stype"] == "garage-door-opener": async_add_entities([HomeKitGarageDoorCover(conn, info)], True) return True - if service['stype'] in ('window-covering', 'window'): + if service["stype"] in ("window-covering", "window"): async_add_entities([HomeKitWindowCover(conn, info)], True) return True @@ -74,12 +72,13 @@ class HomeKitGarageDoorCover(HomeKitEntity, CoverDevice): @property def device_class(self): """Define this cover as a garage door.""" - return 'garage' + return "garage" def get_characteristic_types(self): """Define the homekit characteristics the entity cares about.""" # pylint: disable=import-error from homekit.model.characteristics import CharacteristicsTypes + return [ CharacteristicsTypes.DOOR_STATE_CURRENT, CharacteristicsTypes.DOOR_STATE_TARGET, @@ -122,9 +121,13 @@ class HomeKitGarageDoorCover(HomeKitEntity, CoverDevice): async def set_door_state(self, state): """Send state command.""" - characteristics = [{'aid': self._aid, - 'iid': self._chars['door-state.target'], - 'value': TARGET_GARAGE_STATE_MAP[state]}] + characteristics = [ + { + "aid": self._aid, + "iid": self._chars["door-state.target"], + "value": TARGET_GARAGE_STATE_MAP[state], + } + ] await self._accessory.put_characteristics(characteristics) @property @@ -133,9 +136,7 @@ class HomeKitGarageDoorCover(HomeKitEntity, CoverDevice): if self._obstruction_detected is None: return None - return { - 'obstruction-detected': self._obstruction_detected, - } + return {"obstruction-detected": self._obstruction_detected} class HomeKitWindowCover(HomeKitEntity, CoverDevice): @@ -149,13 +150,13 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice): self._tilt_position = None self._obstruction_detected = None self.lock_state = None - self._features = ( - SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION) + self._features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION def get_characteristic_types(self): """Define the homekit characteristics the entity cares about.""" # pylint: disable=import-error from homekit.model.characteristics import CharacteristicsTypes + return [ CharacteristicsTypes.POSITION_STATE, CharacteristicsTypes.POSITION_CURRENT, @@ -173,13 +174,13 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice): def _setup_vertical_tilt_current(self, char): self._features |= ( - SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | - SUPPORT_SET_TILT_POSITION) + SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | SUPPORT_SET_TILT_POSITION + ) def _setup_horizontal_tilt_current(self, char): self._features |= ( - SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | - SUPPORT_SET_TILT_POSITION) + SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | SUPPORT_SET_TILT_POSITION + ) def _update_position_state(self, value): self._state = CURRENT_WINDOW_STATE_MAP[value] @@ -223,9 +224,9 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice): async def async_stop_cover(self, **kwargs): """Send hold command.""" - characteristics = [{'aid': self._aid, - 'iid': self._chars['position.hold'], - 'value': 1}] + characteristics = [ + {"aid": self._aid, "iid": self._chars["position.hold"], "value": 1} + ] await self._accessory.put_characteristics(characteristics) async def async_open_cover(self, **kwargs): @@ -239,9 +240,9 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice): async def async_set_cover_position(self, **kwargs): """Send position command.""" position = kwargs[ATTR_POSITION] - characteristics = [{'aid': self._aid, - 'iid': self._chars['position.target'], - 'value': position}] + characteristics = [ + {"aid": self._aid, "iid": self._chars["position.target"], "value": position} + ] await self._accessory.put_characteristics(characteristics) @property @@ -252,16 +253,23 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice): async def async_set_cover_tilt_position(self, **kwargs): """Move the cover tilt to a specific position.""" tilt_position = kwargs[ATTR_TILT_POSITION] - if 'vertical-tilt.target' in self._chars: - characteristics = [{'aid': self._aid, - 'iid': self._chars['vertical-tilt.target'], - 'value': tilt_position}] + if "vertical-tilt.target" in self._chars: + characteristics = [ + { + "aid": self._aid, + "iid": self._chars["vertical-tilt.target"], + "value": tilt_position, + } + ] await self._accessory.put_characteristics(characteristics) - elif 'horizontal-tilt.target' in self._chars: - characteristics = [{'aid': self._aid, - 'iid': - self._chars['horizontal-tilt.target'], - 'value': tilt_position}] + elif "horizontal-tilt.target" in self._chars: + characteristics = [ + { + "aid": self._aid, + "iid": self._chars["horizontal-tilt.target"], + "value": tilt_position, + } + ] await self._accessory.put_characteristics(characteristics) @property @@ -269,7 +277,6 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice): """Return the optional state attributes.""" state_attributes = {} if self._obstruction_detected is not None: - state_attributes['obstruction-detected'] = \ - self._obstruction_detected + state_attributes["obstruction-detected"] = self._obstruction_detected return state_attributes diff --git a/homeassistant/components/homekit_controller/light.py b/homeassistant/components/homekit_controller/light.py index 248412c91a3..534a8c7cd18 100644 --- a/homeassistant/components/homekit_controller/light.py +++ b/homeassistant/components/homekit_controller/light.py @@ -2,29 +2,34 @@ import logging from homeassistant.components.light import ( - ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, SUPPORT_COLOR_TEMP, Light) + ATTR_BRIGHTNESS, + ATTR_COLOR_TEMP, + ATTR_HS_COLOR, + SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, + SUPPORT_COLOR_TEMP, + Light, +) from . import KNOWN_DEVICES, HomeKitEntity _LOGGER = logging.getLogger(__name__) -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Legacy set up platform.""" pass async def async_setup_entry(hass, config_entry, async_add_entities): """Set up Homekit lightbulb.""" - hkid = config_entry.data['AccessoryPairingID'] + hkid = config_entry.data["AccessoryPairingID"] conn = hass.data[KNOWN_DEVICES][hkid] def async_add_service(aid, service): - if service['stype'] != 'lightbulb': + if service["stype"] != "lightbulb": return False - info = {'aid': aid, 'iid': service['iid']} + info = {"aid": aid, "iid": service["iid"]} async_add_entities([HomeKitLight(conn, info)], True) return True @@ -47,6 +52,7 @@ class HomeKitLight(HomeKitEntity, Light): """Define the homekit characteristics the entity cares about.""" # pylint: disable=import-error from homekit.model.characteristics import CharacteristicsTypes + return [ CharacteristicsTypes.ON, CharacteristicsTypes.BRIGHTNESS, @@ -115,29 +121,39 @@ class HomeKitLight(HomeKitEntity, Light): characteristics = [] if hs_color is not None: - characteristics.append({'aid': self._aid, - 'iid': self._chars['hue'], - 'value': hs_color[0]}) - characteristics.append({'aid': self._aid, - 'iid': self._chars['saturation'], - 'value': hs_color[1]}) + characteristics.append( + {"aid": self._aid, "iid": self._chars["hue"], "value": hs_color[0]} + ) + characteristics.append( + { + "aid": self._aid, + "iid": self._chars["saturation"], + "value": hs_color[1], + } + ) if brightness is not None: - characteristics.append({'aid': self._aid, - 'iid': self._chars['brightness'], - 'value': int(brightness * 100 / 255)}) + characteristics.append( + { + "aid": self._aid, + "iid": self._chars["brightness"], + "value": int(brightness * 100 / 255), + } + ) if temperature is not None: - characteristics.append({'aid': self._aid, - 'iid': self._chars['color-temperature'], - 'value': int(temperature)}) - characteristics.append({'aid': self._aid, - 'iid': self._chars['on'], - 'value': True}) + characteristics.append( + { + "aid": self._aid, + "iid": self._chars["color-temperature"], + "value": int(temperature), + } + ) + characteristics.append( + {"aid": self._aid, "iid": self._chars["on"], "value": True} + ) await self._accessory.put_characteristics(characteristics) async def async_turn_off(self, **kwargs): """Turn the specified light off.""" - characteristics = [{'aid': self._aid, - 'iid': self._chars['on'], - 'value': False}] + characteristics = [{"aid": self._aid, "iid": self._chars["on"], "value": False}] await self._accessory.put_characteristics(characteristics) diff --git a/homeassistant/components/homekit_controller/lock.py b/homeassistant/components/homekit_controller/lock.py index 1449f265245..4ca118acee6 100644 --- a/homeassistant/components/homekit_controller/lock.py +++ b/homeassistant/components/homekit_controller/lock.py @@ -2,43 +2,33 @@ import logging from homeassistant.components.lock import LockDevice -from homeassistant.const import ( - ATTR_BATTERY_LEVEL, STATE_LOCKED, STATE_UNLOCKED) +from homeassistant.const import ATTR_BATTERY_LEVEL, STATE_LOCKED, STATE_UNLOCKED from . import KNOWN_DEVICES, HomeKitEntity _LOGGER = logging.getLogger(__name__) -STATE_JAMMED = 'jammed' +STATE_JAMMED = "jammed" -CURRENT_STATE_MAP = { - 0: STATE_UNLOCKED, - 1: STATE_LOCKED, - 2: STATE_JAMMED, - 3: None, -} +CURRENT_STATE_MAP = {0: STATE_UNLOCKED, 1: STATE_LOCKED, 2: STATE_JAMMED, 3: None} -TARGET_STATE_MAP = { - STATE_UNLOCKED: 0, - STATE_LOCKED: 1, -} +TARGET_STATE_MAP = {STATE_UNLOCKED: 0, STATE_LOCKED: 1} -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Legacy set up platform.""" pass async def async_setup_entry(hass, config_entry, async_add_entities): """Set up Homekit lock.""" - hkid = config_entry.data['AccessoryPairingID'] + hkid = config_entry.data["AccessoryPairingID"] conn = hass.data[KNOWN_DEVICES][hkid] def async_add_service(aid, service): - if service['stype'] != 'lock-mechanism': + if service["stype"] != "lock-mechanism": return False - info = {'aid': aid, 'iid': service['iid']} + info = {"aid": aid, "iid": service["iid"]} async_add_entities([HomeKitLock(conn, info)], True) return True @@ -58,6 +48,7 @@ class HomeKitLock(HomeKitEntity, LockDevice): """Define the homekit characteristics the entity cares about.""" # pylint: disable=import-error from homekit.model.characteristics import CharacteristicsTypes + return [ CharacteristicsTypes.LOCK_MECHANISM_CURRENT_STATE, CharacteristicsTypes.LOCK_MECHANISM_TARGET_STATE, @@ -85,9 +76,13 @@ class HomeKitLock(HomeKitEntity, LockDevice): async def _set_lock_state(self, state): """Send state command.""" - characteristics = [{'aid': self._aid, - 'iid': self._chars['lock-mechanism.target-state'], - 'value': TARGET_STATE_MAP[state]}] + characteristics = [ + { + "aid": self._aid, + "iid": self._chars["lock-mechanism.target-state"], + "value": TARGET_STATE_MAP[state], + } + ] await self._accessory.put_characteristics(characteristics) @property @@ -96,6 +91,4 @@ class HomeKitLock(HomeKitEntity, LockDevice): if self._battery_level is None: return None - return { - ATTR_BATTERY_LEVEL: self._battery_level, - } + return {ATTR_BATTERY_LEVEL: self._battery_level} diff --git a/homeassistant/components/homekit_controller/sensor.py b/homeassistant/components/homekit_controller/sensor.py index 9ffa6c6b597..16109f08c10 100644 --- a/homeassistant/components/homekit_controller/sensor.py +++ b/homeassistant/components/homekit_controller/sensor.py @@ -3,7 +3,7 @@ from homeassistant.const import TEMP_CELSIUS from . import KNOWN_DEVICES, HomeKitEntity -HUMIDITY_ICON = 'mdi:water-percent' +HUMIDITY_ICON = "mdi:water-percent" TEMP_C_ICON = "mdi:thermometer" BRIGHTNESS_ICON = "mdi:brightness-6" @@ -11,29 +11,28 @@ UNIT_PERCENT = "%" UNIT_LUX = "lux" -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Legacy set up platform.""" pass async def async_setup_entry(hass, config_entry, async_add_entities): """Set up Homekit covers.""" - hkid = config_entry.data['AccessoryPairingID'] + hkid = config_entry.data["AccessoryPairingID"] conn = hass.data[KNOWN_DEVICES][hkid] def async_add_service(aid, service): - devtype = service['stype'] - info = {'aid': aid, 'iid': service['iid']} - if devtype == 'humidity': + devtype = service["stype"] + info = {"aid": aid, "iid": service["iid"]} + if devtype == "humidity": async_add_entities([HomeKitHumiditySensor(conn, info)], True) return True - if devtype == 'temperature': + if devtype == "temperature": async_add_entities([HomeKitTemperatureSensor(conn, info)], True) return True - if devtype == 'light': + if devtype == "light": async_add_entities([HomeKitLightSensor(conn, info)], True) return True @@ -55,9 +54,7 @@ class HomeKitHumiditySensor(HomeKitEntity): # pylint: disable=import-error from homekit.model.characteristics import CharacteristicsTypes - return [ - CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT - ] + return [CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT] @property def name(self): @@ -96,9 +93,7 @@ class HomeKitTemperatureSensor(HomeKitEntity): # pylint: disable=import-error from homekit.model.characteristics import CharacteristicsTypes - return [ - CharacteristicsTypes.TEMPERATURE_CURRENT - ] + return [CharacteristicsTypes.TEMPERATURE_CURRENT] @property def name(self): @@ -137,9 +132,7 @@ class HomeKitLightSensor(HomeKitEntity): # pylint: disable=import-error from homekit.model.characteristics import CharacteristicsTypes - return [ - CharacteristicsTypes.LIGHT_LEVEL_CURRENT - ] + return [CharacteristicsTypes.LIGHT_LEVEL_CURRENT] @property def name(self): diff --git a/homeassistant/components/homekit_controller/storage.py b/homeassistant/components/homekit_controller/storage.py index 4a7c0a8057b..ec5a2e7cc43 100644 --- a/homeassistant/components/homekit_controller/storage.py +++ b/homeassistant/components/homekit_controller/storage.py @@ -5,7 +5,7 @@ from homeassistant.core import callback from .const import DOMAIN -ENTITY_MAP_STORAGE_KEY = '{}-entity-map'.format(DOMAIN) +ENTITY_MAP_STORAGE_KEY = "{}-entity-map".format(DOMAIN) ENTITY_MAP_STORAGE_VERSION = 1 ENTITY_MAP_SAVE_DELAY = 10 @@ -29,11 +29,7 @@ class EntityMapStorage: def __init__(self, hass): """Create a new entity map store.""" self.hass = hass - self.store = Store( - hass, - ENTITY_MAP_STORAGE_VERSION, - ENTITY_MAP_STORAGE_KEY - ) + self.store = Store(hass, ENTITY_MAP_STORAGE_VERSION, ENTITY_MAP_STORAGE_KEY) self.storage_data = {} async def async_initialize(self): @@ -43,7 +39,7 @@ class EntityMapStorage: # There is no cached data about HomeKit devices yet return - self.storage_data = raw_storage.get('pairings', {}) + self.storage_data = raw_storage.get("pairings", {}) def get_map(self, homekit_id): """Get a pairing cache item.""" @@ -51,10 +47,7 @@ class EntityMapStorage: def async_create_or_update_map(self, homekit_id, config_num, accessories): """Create a new pairing cache.""" - data = { - 'config_num': config_num, - 'accessories': accessories, - } + data = {"config_num": config_num, "accessories": accessories} self.storage_data[homekit_id] = data self._async_schedule_save() return data @@ -75,6 +68,4 @@ class EntityMapStorage: @callback def _data_to_save(self): """Return data of entity map to store in a file.""" - return { - 'pairings': self.storage_data, - } + return {"pairings": self.storage_data} diff --git a/homeassistant/components/homekit_controller/switch.py b/homeassistant/components/homekit_controller/switch.py index 670ddd4db5b..7fdc6a7082f 100644 --- a/homeassistant/components/homekit_controller/switch.py +++ b/homeassistant/components/homekit_controller/switch.py @@ -10,21 +10,20 @@ OUTLET_IN_USE = "outlet_in_use" _LOGGER = logging.getLogger(__name__) -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Legacy set up platform.""" pass async def async_setup_entry(hass, config_entry, async_add_entities): """Set up Homekit lock.""" - hkid = config_entry.data['AccessoryPairingID'] + hkid = config_entry.data["AccessoryPairingID"] conn = hass.data[KNOWN_DEVICES][hkid] def async_add_service(aid, service): - if service['stype'] not in ('switch', 'outlet'): + if service["stype"] not in ("switch", "outlet"): return False - info = {'aid': aid, 'iid': service['iid']} + info = {"aid": aid, "iid": service["iid"]} async_add_entities([HomeKitSwitch(conn, info)], True) return True @@ -44,10 +43,8 @@ class HomeKitSwitch(HomeKitEntity, SwitchDevice): """Define the homekit characteristics the entity cares about.""" # pylint: disable=import-error from homekit.model.characteristics import CharacteristicsTypes - return [ - CharacteristicsTypes.ON, - CharacteristicsTypes.OUTLET_IN_USE, - ] + + return [CharacteristicsTypes.ON, CharacteristicsTypes.OUTLET_IN_USE] def _update_on(self, value): self._on = value @@ -63,22 +60,16 @@ class HomeKitSwitch(HomeKitEntity, SwitchDevice): async def async_turn_on(self, **kwargs): """Turn the specified switch on.""" self._on = True - characteristics = [{'aid': self._aid, - 'iid': self._chars['on'], - 'value': True}] + characteristics = [{"aid": self._aid, "iid": self._chars["on"], "value": True}] await self._accessory.put_characteristics(characteristics) async def async_turn_off(self, **kwargs): """Turn the specified switch off.""" - characteristics = [{'aid': self._aid, - 'iid': self._chars['on'], - 'value': False}] + characteristics = [{"aid": self._aid, "iid": self._chars["on"], "value": False}] await self._accessory.put_characteristics(characteristics) @property def device_state_attributes(self): """Return the optional state attributes.""" if self._outlet_in_use is not None: - return { - OUTLET_IN_USE: self._outlet_in_use, - } + return {OUTLET_IN_USE: self._outlet_in_use} diff --git a/homeassistant/components/homematic/__init__.py b/homeassistant/components/homematic/__init__.py index 013f1eab679..0ab47247edc 100644 --- a/homeassistant/components/homematic/__init__.py +++ b/homeassistant/components/homematic/__init__.py @@ -6,259 +6,360 @@ import logging import voluptuous as vol from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_NAME, CONF_HOST, CONF_HOSTS, CONF_PASSWORD, - CONF_PLATFORM, CONF_SSL, CONF_USERNAME, CONF_VERIFY_SSL, - EVENT_HOMEASSISTANT_STOP, STATE_UNKNOWN) + ATTR_ENTITY_ID, + ATTR_NAME, + CONF_HOST, + CONF_HOSTS, + CONF_PASSWORD, + CONF_PLATFORM, + CONF_SSL, + CONF_USERNAME, + CONF_VERIFY_SSL, + EVENT_HOMEASSISTANT_STOP, + STATE_UNKNOWN, +) from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -DOMAIN = 'homematic' +DOMAIN = "homematic" SCAN_INTERVAL_HUB = timedelta(seconds=300) SCAN_INTERVAL_VARIABLES = timedelta(seconds=30) -DISCOVER_SWITCHES = 'homematic.switch' -DISCOVER_LIGHTS = 'homematic.light' -DISCOVER_SENSORS = 'homematic.sensor' -DISCOVER_BINARY_SENSORS = 'homematic.binary_sensor' -DISCOVER_COVER = 'homematic.cover' -DISCOVER_CLIMATE = 'homematic.climate' -DISCOVER_LOCKS = 'homematic.locks' -DISCOVER_BATTERY = 'homematic.battery' +DISCOVER_SWITCHES = "homematic.switch" +DISCOVER_LIGHTS = "homematic.light" +DISCOVER_SENSORS = "homematic.sensor" +DISCOVER_BINARY_SENSORS = "homematic.binary_sensor" +DISCOVER_COVER = "homematic.cover" +DISCOVER_CLIMATE = "homematic.climate" +DISCOVER_LOCKS = "homematic.locks" +DISCOVER_BATTERY = "homematic.battery" -ATTR_DISCOVER_DEVICES = 'devices' -ATTR_PARAM = 'param' -ATTR_CHANNEL = 'channel' -ATTR_ADDRESS = 'address' -ATTR_VALUE = 'value' -ATTR_VALUE_TYPE = 'value_type' -ATTR_INTERFACE = 'interface' -ATTR_ERRORCODE = 'error' -ATTR_MESSAGE = 'message' -ATTR_MODE = 'mode' -ATTR_TIME = 'time' -ATTR_UNIQUE_ID = 'unique_id' -ATTR_PARAMSET_KEY = 'paramset_key' -ATTR_PARAMSET = 'paramset' -ATTR_DISCOVERY_TYPE = 'discovery_type' -ATTR_LOW_BAT = 'LOW_BAT' -ATTR_LOWBAT = 'LOWBAT' +ATTR_DISCOVER_DEVICES = "devices" +ATTR_PARAM = "param" +ATTR_CHANNEL = "channel" +ATTR_ADDRESS = "address" +ATTR_VALUE = "value" +ATTR_VALUE_TYPE = "value_type" +ATTR_INTERFACE = "interface" +ATTR_ERRORCODE = "error" +ATTR_MESSAGE = "message" +ATTR_MODE = "mode" +ATTR_TIME = "time" +ATTR_UNIQUE_ID = "unique_id" +ATTR_PARAMSET_KEY = "paramset_key" +ATTR_PARAMSET = "paramset" +ATTR_DISCOVERY_TYPE = "discovery_type" +ATTR_LOW_BAT = "LOW_BAT" +ATTR_LOWBAT = "LOWBAT" -EVENT_KEYPRESS = 'homematic.keypress' -EVENT_IMPULSE = 'homematic.impulse' -EVENT_ERROR = 'homematic.error' +EVENT_KEYPRESS = "homematic.keypress" +EVENT_IMPULSE = "homematic.impulse" +EVENT_ERROR = "homematic.error" -SERVICE_VIRTUALKEY = 'virtualkey' -SERVICE_RECONNECT = 'reconnect' -SERVICE_SET_VARIABLE_VALUE = 'set_variable_value' -SERVICE_SET_DEVICE_VALUE = 'set_device_value' -SERVICE_SET_INSTALL_MODE = 'set_install_mode' -SERVICE_PUT_PARAMSET = 'put_paramset' +SERVICE_VIRTUALKEY = "virtualkey" +SERVICE_RECONNECT = "reconnect" +SERVICE_SET_VARIABLE_VALUE = "set_variable_value" +SERVICE_SET_DEVICE_VALUE = "set_device_value" +SERVICE_SET_INSTALL_MODE = "set_install_mode" +SERVICE_PUT_PARAMSET = "put_paramset" HM_DEVICE_TYPES = { DISCOVER_SWITCHES: [ - 'Switch', 'SwitchPowermeter', 'IOSwitch', 'IPSwitch', 'RFSiren', - 'IPSwitchPowermeter', 'HMWIOSwitch', 'Rain', 'EcoLogic', - 'IPKeySwitchPowermeter', 'IPGarage', 'IPKeySwitch', 'IPMultiIO'], - DISCOVER_LIGHTS: ['Dimmer', 'KeyDimmer', 'IPKeyDimmer', 'IPDimmer', - 'ColorEffectLight'], + "Switch", + "SwitchPowermeter", + "IOSwitch", + "IPSwitch", + "RFSiren", + "IPSwitchPowermeter", + "HMWIOSwitch", + "Rain", + "EcoLogic", + "IPKeySwitchPowermeter", + "IPGarage", + "IPKeySwitch", + "IPMultiIO", + ], + DISCOVER_LIGHTS: [ + "Dimmer", + "KeyDimmer", + "IPKeyDimmer", + "IPDimmer", + "ColorEffectLight", + ], DISCOVER_SENSORS: [ - 'SwitchPowermeter', 'Motion', 'MotionV2', 'RemoteMotion', 'MotionIP', - 'ThermostatWall', 'AreaThermostat', 'RotaryHandleSensor', - 'WaterSensor', 'PowermeterGas', 'LuxSensor', 'WeatherSensor', - 'WeatherStation', 'ThermostatWall2', 'TemperatureDiffSensor', - 'TemperatureSensor', 'CO2Sensor', 'IPSwitchPowermeter', 'HMWIOSwitch', - 'FillingLevel', 'ValveDrive', 'EcoLogic', 'IPThermostatWall', - 'IPSmoke', 'RFSiren', 'PresenceIP', 'IPAreaThermostat', - 'IPWeatherSensor', 'RotaryHandleSensorIP', 'IPPassageSensor', - 'IPKeySwitchPowermeter', 'IPThermostatWall230V', 'IPWeatherSensorPlus', - 'IPWeatherSensorBasic', 'IPBrightnessSensor', 'IPGarage', - 'UniversalSensor', 'MotionIPV2', 'IPMultiIO', 'IPThermostatWall2'], + "SwitchPowermeter", + "Motion", + "MotionV2", + "RemoteMotion", + "MotionIP", + "ThermostatWall", + "AreaThermostat", + "RotaryHandleSensor", + "WaterSensor", + "PowermeterGas", + "LuxSensor", + "WeatherSensor", + "WeatherStation", + "ThermostatWall2", + "TemperatureDiffSensor", + "TemperatureSensor", + "CO2Sensor", + "IPSwitchPowermeter", + "HMWIOSwitch", + "FillingLevel", + "ValveDrive", + "EcoLogic", + "IPThermostatWall", + "IPSmoke", + "RFSiren", + "PresenceIP", + "IPAreaThermostat", + "IPWeatherSensor", + "RotaryHandleSensorIP", + "IPPassageSensor", + "IPKeySwitchPowermeter", + "IPThermostatWall230V", + "IPWeatherSensorPlus", + "IPWeatherSensorBasic", + "IPBrightnessSensor", + "IPGarage", + "UniversalSensor", + "MotionIPV2", + "IPMultiIO", + "IPThermostatWall2", + ], DISCOVER_CLIMATE: [ - 'Thermostat', 'ThermostatWall', 'MAXThermostat', 'ThermostatWall2', - 'MAXWallThermostat', 'IPThermostat', 'IPThermostatWall', - 'ThermostatGroup', 'IPThermostatWall230V', 'IPThermostatWall2'], + "Thermostat", + "ThermostatWall", + "MAXThermostat", + "ThermostatWall2", + "MAXWallThermostat", + "IPThermostat", + "IPThermostatWall", + "ThermostatGroup", + "IPThermostatWall230V", + "IPThermostatWall2", + ], DISCOVER_BINARY_SENSORS: [ - 'ShutterContact', 'Smoke', 'SmokeV2', 'Motion', 'MotionV2', - 'MotionIP', 'RemoteMotion', 'WeatherSensor', 'TiltSensor', - 'IPShutterContact', 'HMWIOSwitch', 'MaxShutterContact', 'Rain', - 'WiredSensor', 'PresenceIP', 'IPWeatherSensor', 'IPPassageSensor', - 'SmartwareMotion', 'IPWeatherSensorPlus', 'MotionIPV2', 'WaterIP', - 'IPMultiIO', 'TiltIP', 'IPShutterContactSabotage'], - DISCOVER_COVER: ['Blind', 'KeyBlind', 'IPKeyBlind', 'IPKeyBlindTilt'], - DISCOVER_LOCKS: ['KeyMatic'] + "ShutterContact", + "Smoke", + "SmokeV2", + "Motion", + "MotionV2", + "MotionIP", + "RemoteMotion", + "WeatherSensor", + "TiltSensor", + "IPShutterContact", + "HMWIOSwitch", + "MaxShutterContact", + "Rain", + "WiredSensor", + "PresenceIP", + "IPWeatherSensor", + "IPPassageSensor", + "SmartwareMotion", + "IPWeatherSensorPlus", + "MotionIPV2", + "WaterIP", + "IPMultiIO", + "TiltIP", + "IPShutterContactSabotage", + ], + DISCOVER_COVER: ["Blind", "KeyBlind", "IPKeyBlind", "IPKeyBlindTilt"], + DISCOVER_LOCKS: ["KeyMatic"], } -HM_IGNORE_DISCOVERY_NODE = [ - 'ACTUAL_TEMPERATURE', - 'ACTUAL_HUMIDITY' -] +HM_IGNORE_DISCOVERY_NODE = ["ACTUAL_TEMPERATURE", "ACTUAL_HUMIDITY"] HM_IGNORE_DISCOVERY_NODE_EXCEPTIONS = { - 'ACTUAL_TEMPERATURE': [ - 'IPAreaThermostat', 'IPWeatherSensor', - 'IPWeatherSensorPlus', 'IPWeatherSensorBasic', - 'IPThermostatWall', 'IPThermostatWall2'], + "ACTUAL_TEMPERATURE": [ + "IPAreaThermostat", + "IPWeatherSensor", + "IPWeatherSensorPlus", + "IPWeatherSensorBasic", + "IPThermostatWall", + "IPThermostatWall2", + ] } HM_ATTRIBUTE_SUPPORT = { - 'LOWBAT': ['battery', {0: 'High', 1: 'Low'}], - 'LOW_BAT': ['battery', {0: 'High', 1: 'Low'}], - 'ERROR': ['error', {0: 'No'}], - 'ERROR_SABOTAGE': ['sabotage', {0: 'No', 1: 'Yes'}], - 'SABOTAGE': ['sabotage', {0: 'No', 1: 'Yes'}], - 'RSSI_PEER': ['rssi_peer', {}], - 'RSSI_DEVICE': ['rssi_device', {}], - 'VALVE_STATE': ['valve', {}], - 'LEVEL': ['level', {}], - 'BATTERY_STATE': ['battery', {}], - 'CONTROL_MODE': ['mode', { - 0: 'Auto', - 1: 'Manual', - 2: 'Away', - 3: 'Boost', - 4: 'Comfort', - 5: 'Lowering' - }], - 'POWER': ['power', {}], - 'CURRENT': ['current', {}], - 'VOLTAGE': ['voltage', {}], - 'OPERATING_VOLTAGE': ['voltage', {}], - 'WORKING': ['working', {0: 'No', 1: 'Yes'}], - 'STATE_UNCERTAIN': ['state_uncertain', {}] + "LOWBAT": ["battery", {0: "High", 1: "Low"}], + "LOW_BAT": ["battery", {0: "High", 1: "Low"}], + "ERROR": ["error", {0: "No"}], + "ERROR_SABOTAGE": ["sabotage", {0: "No", 1: "Yes"}], + "SABOTAGE": ["sabotage", {0: "No", 1: "Yes"}], + "RSSI_PEER": ["rssi_peer", {}], + "RSSI_DEVICE": ["rssi_device", {}], + "VALVE_STATE": ["valve", {}], + "LEVEL": ["level", {}], + "BATTERY_STATE": ["battery", {}], + "CONTROL_MODE": [ + "mode", + {0: "Auto", 1: "Manual", 2: "Away", 3: "Boost", 4: "Comfort", 5: "Lowering"}, + ], + "POWER": ["power", {}], + "CURRENT": ["current", {}], + "VOLTAGE": ["voltage", {}], + "OPERATING_VOLTAGE": ["voltage", {}], + "WORKING": ["working", {0: "No", 1: "Yes"}], + "STATE_UNCERTAIN": ["state_uncertain", {}], } HM_PRESS_EVENTS = [ - 'PRESS_SHORT', - 'PRESS_LONG', - 'PRESS_CONT', - 'PRESS_LONG_RELEASE', - 'PRESS', + "PRESS_SHORT", + "PRESS_LONG", + "PRESS_CONT", + "PRESS_LONG_RELEASE", + "PRESS", ] -HM_IMPULSE_EVENTS = [ - 'SEQUENCE_OK', -] +HM_IMPULSE_EVENTS = ["SEQUENCE_OK"] -CONF_RESOLVENAMES_OPTIONS = [ - 'metadata', - 'json', - 'xml', - False -] +CONF_RESOLVENAMES_OPTIONS = ["metadata", "json", "xml", False] -DATA_HOMEMATIC = 'homematic' -DATA_STORE = 'homematic_store' -DATA_CONF = 'homematic_conf' +DATA_HOMEMATIC = "homematic" +DATA_STORE = "homematic_store" +DATA_CONF = "homematic_conf" -CONF_INTERFACES = 'interfaces' -CONF_LOCAL_IP = 'local_ip' -CONF_LOCAL_PORT = 'local_port' -CONF_PORT = 'port' -CONF_PATH = 'path' -CONF_CALLBACK_IP = 'callback_ip' -CONF_CALLBACK_PORT = 'callback_port' -CONF_RESOLVENAMES = 'resolvenames' -CONF_JSONPORT = 'jsonport' -CONF_VARIABLES = 'variables' -CONF_DEVICES = 'devices' -CONF_PRIMARY = 'primary' +CONF_INTERFACES = "interfaces" +CONF_LOCAL_IP = "local_ip" +CONF_LOCAL_PORT = "local_port" +CONF_PORT = "port" +CONF_PATH = "path" +CONF_CALLBACK_IP = "callback_ip" +CONF_CALLBACK_PORT = "callback_port" +CONF_RESOLVENAMES = "resolvenames" +CONF_JSONPORT = "jsonport" +CONF_VARIABLES = "variables" +CONF_DEVICES = "devices" +CONF_PRIMARY = "primary" -DEFAULT_LOCAL_IP = '0.0.0.0' +DEFAULT_LOCAL_IP = "0.0.0.0" DEFAULT_LOCAL_PORT = 0 DEFAULT_RESOLVENAMES = False DEFAULT_JSONPORT = 80 DEFAULT_PORT = 2001 -DEFAULT_PATH = '' -DEFAULT_USERNAME = 'Admin' -DEFAULT_PASSWORD = '' +DEFAULT_PATH = "" +DEFAULT_USERNAME = "Admin" +DEFAULT_PASSWORD = "" DEFAULT_SSL = False DEFAULT_VERIFY_SSL = False DEFAULT_CHANNEL = 1 -DEVICE_SCHEMA = vol.Schema({ - vol.Required(CONF_PLATFORM): 'homematic', - vol.Required(ATTR_NAME): cv.string, - vol.Required(ATTR_ADDRESS): cv.string, - vol.Required(ATTR_INTERFACE): cv.string, - vol.Optional(ATTR_CHANNEL, default=DEFAULT_CHANNEL): vol.Coerce(int), - vol.Optional(ATTR_PARAM): cv.string, - vol.Optional(ATTR_UNIQUE_ID): cv.string, -}) +DEVICE_SCHEMA = vol.Schema( + { + vol.Required(CONF_PLATFORM): "homematic", + vol.Required(ATTR_NAME): cv.string, + vol.Required(ATTR_ADDRESS): cv.string, + vol.Required(ATTR_INTERFACE): cv.string, + vol.Optional(ATTR_CHANNEL, default=DEFAULT_CHANNEL): vol.Coerce(int), + vol.Optional(ATTR_PARAM): cv.string, + vol.Optional(ATTR_UNIQUE_ID): cv.string, + } +) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Optional(CONF_INTERFACES, default={}): {cv.match_all: { - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_PATH, default=DEFAULT_PATH): cv.string, - vol.Optional(CONF_RESOLVENAMES, default=DEFAULT_RESOLVENAMES): - vol.In(CONF_RESOLVENAMES_OPTIONS), - vol.Optional(CONF_JSONPORT, default=DEFAULT_JSONPORT): cv.port, - vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, - vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string, - vol.Optional(CONF_CALLBACK_IP): cv.string, - vol.Optional(CONF_CALLBACK_PORT): cv.port, - vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean, - vol.Optional( - CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean, - }}, - vol.Optional(CONF_HOSTS, default={}): {cv.match_all: { - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, - vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string, - }}, - vol.Optional(CONF_LOCAL_IP, default=DEFAULT_LOCAL_IP): cv.string, - vol.Optional(CONF_LOCAL_PORT): cv.port, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Optional(CONF_INTERFACES, default={}): { + cv.match_all: { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_PATH, default=DEFAULT_PATH): cv.string, + vol.Optional( + CONF_RESOLVENAMES, default=DEFAULT_RESOLVENAMES + ): vol.In(CONF_RESOLVENAMES_OPTIONS), + vol.Optional(CONF_JSONPORT, default=DEFAULT_JSONPORT): cv.port, + vol.Optional( + CONF_USERNAME, default=DEFAULT_USERNAME + ): cv.string, + vol.Optional( + CONF_PASSWORD, default=DEFAULT_PASSWORD + ): cv.string, + vol.Optional(CONF_CALLBACK_IP): cv.string, + vol.Optional(CONF_CALLBACK_PORT): cv.port, + vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean, + vol.Optional( + CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL + ): cv.boolean, + } + }, + vol.Optional(CONF_HOSTS, default={}): { + cv.match_all: { + vol.Required(CONF_HOST): cv.string, + vol.Optional( + CONF_USERNAME, default=DEFAULT_USERNAME + ): cv.string, + vol.Optional( + CONF_PASSWORD, default=DEFAULT_PASSWORD + ): cv.string, + } + }, + vol.Optional(CONF_LOCAL_IP, default=DEFAULT_LOCAL_IP): cv.string, + vol.Optional(CONF_LOCAL_PORT): cv.port, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) -SCHEMA_SERVICE_VIRTUALKEY = vol.Schema({ - vol.Required(ATTR_ADDRESS): vol.All(cv.string, vol.Upper), - vol.Required(ATTR_CHANNEL): vol.Coerce(int), - vol.Required(ATTR_PARAM): cv.string, - vol.Optional(ATTR_INTERFACE): cv.string, -}) +SCHEMA_SERVICE_VIRTUALKEY = vol.Schema( + { + vol.Required(ATTR_ADDRESS): vol.All(cv.string, vol.Upper), + vol.Required(ATTR_CHANNEL): vol.Coerce(int), + vol.Required(ATTR_PARAM): cv.string, + vol.Optional(ATTR_INTERFACE): cv.string, + } +) -SCHEMA_SERVICE_SET_VARIABLE_VALUE = vol.Schema({ - vol.Required(ATTR_NAME): cv.string, - vol.Required(ATTR_VALUE): cv.match_all, - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, -}) +SCHEMA_SERVICE_SET_VARIABLE_VALUE = vol.Schema( + { + vol.Required(ATTR_NAME): cv.string, + vol.Required(ATTR_VALUE): cv.match_all, + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + } +) -SCHEMA_SERVICE_SET_DEVICE_VALUE = vol.Schema({ - vol.Required(ATTR_ADDRESS): vol.All(cv.string, vol.Upper), - vol.Required(ATTR_CHANNEL): vol.Coerce(int), - vol.Required(ATTR_PARAM): vol.All(cv.string, vol.Upper), - vol.Required(ATTR_VALUE): cv.match_all, - vol.Optional(ATTR_VALUE_TYPE): vol.In([ - 'boolean', 'dateTime.iso8601', - 'double', 'int', 'string' - ]), - vol.Optional(ATTR_INTERFACE): cv.string, -}) +SCHEMA_SERVICE_SET_DEVICE_VALUE = vol.Schema( + { + vol.Required(ATTR_ADDRESS): vol.All(cv.string, vol.Upper), + vol.Required(ATTR_CHANNEL): vol.Coerce(int), + vol.Required(ATTR_PARAM): vol.All(cv.string, vol.Upper), + vol.Required(ATTR_VALUE): cv.match_all, + vol.Optional(ATTR_VALUE_TYPE): vol.In( + ["boolean", "dateTime.iso8601", "double", "int", "string"] + ), + vol.Optional(ATTR_INTERFACE): cv.string, + } +) SCHEMA_SERVICE_RECONNECT = vol.Schema({}) -SCHEMA_SERVICE_SET_INSTALL_MODE = vol.Schema({ - vol.Required(ATTR_INTERFACE): cv.string, - vol.Optional(ATTR_TIME, default=60): cv.positive_int, - vol.Optional(ATTR_MODE, default=1): - vol.All(vol.Coerce(int), vol.In([1, 2])), - vol.Optional(ATTR_ADDRESS): vol.All(cv.string, vol.Upper), -}) +SCHEMA_SERVICE_SET_INSTALL_MODE = vol.Schema( + { + vol.Required(ATTR_INTERFACE): cv.string, + vol.Optional(ATTR_TIME, default=60): cv.positive_int, + vol.Optional(ATTR_MODE, default=1): vol.All(vol.Coerce(int), vol.In([1, 2])), + vol.Optional(ATTR_ADDRESS): vol.All(cv.string, vol.Upper), + } +) -SCHEMA_SERVICE_PUT_PARAMSET = vol.Schema({ - vol.Required(ATTR_INTERFACE): cv.string, - vol.Required(ATTR_ADDRESS): vol.All(cv.string, vol.Upper), - vol.Required(ATTR_PARAMSET_KEY): vol.All(cv.string, vol.Upper), - vol.Required(ATTR_PARAMSET): dict, -}) +SCHEMA_SERVICE_PUT_PARAMSET = vol.Schema( + { + vol.Required(ATTR_INTERFACE): cv.string, + vol.Required(ATTR_ADDRESS): vol.All(cv.string, vol.Upper), + vol.Required(ATTR_PARAMSET_KEY): vol.All(cv.string, vol.Upper), + vol.Required(ATTR_PARAMSET): dict, + } +) def setup(hass, config): @@ -272,27 +373,27 @@ def setup(hass, config): # Create hosts-dictionary for pyhomematic for rname, rconfig in conf[CONF_INTERFACES].items(): remotes[rname] = { - 'ip': rconfig.get(CONF_HOST), - 'port': rconfig.get(CONF_PORT), - 'path': rconfig.get(CONF_PATH), - 'resolvenames': rconfig.get(CONF_RESOLVENAMES), - 'jsonport': rconfig.get(CONF_JSONPORT), - 'username': rconfig.get(CONF_USERNAME), - 'password': rconfig.get(CONF_PASSWORD), - 'callbackip': rconfig.get(CONF_CALLBACK_IP), - 'callbackport': rconfig.get(CONF_CALLBACK_PORT), - 'ssl': rconfig.get(CONF_SSL), - 'verify_ssl': rconfig.get(CONF_VERIFY_SSL), - 'connect': True, + "ip": rconfig.get(CONF_HOST), + "port": rconfig.get(CONF_PORT), + "path": rconfig.get(CONF_PATH), + "resolvenames": rconfig.get(CONF_RESOLVENAMES), + "jsonport": rconfig.get(CONF_JSONPORT), + "username": rconfig.get(CONF_USERNAME), + "password": rconfig.get(CONF_PASSWORD), + "callbackip": rconfig.get(CONF_CALLBACK_IP), + "callbackport": rconfig.get(CONF_CALLBACK_PORT), + "ssl": rconfig.get(CONF_SSL), + "verify_ssl": rconfig.get(CONF_VERIFY_SSL), + "connect": True, } for sname, sconfig in conf[CONF_HOSTS].items(): remotes[sname] = { - 'ip': sconfig.get(CONF_HOST), - 'port': DEFAULT_PORT, - 'username': sconfig.get(CONF_USERNAME), - 'password': sconfig.get(CONF_PASSWORD), - 'connect': False, + "ip": sconfig.get(CONF_HOST), + "port": DEFAULT_PORT, + "username": sconfig.get(CONF_USERNAME), + "password": sconfig.get(CONF_PASSWORD), + "connect": False, } # Create server thread @@ -302,15 +403,14 @@ def setup(hass, config): localport=config[DOMAIN].get(CONF_LOCAL_PORT, DEFAULT_LOCAL_PORT), remotes=remotes, systemcallback=bound_system_callback, - interface_id='homeassistant' + interface_id="homeassistant", ) # Start server thread, connect to hosts, initialize to receive events homematic.start() # Stops server when HASS is shutting down - hass.bus.listen_once( - EVENT_HOMEASSISTANT_STOP, hass.data[DATA_HOMEMATIC].stop) + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, hass.data[DATA_HOMEMATIC].stop) # Init homematic hubs entity_hubs = [] @@ -336,16 +436,18 @@ def setup(hass, config): # Channel doesn't exist for device if channel not in hmdevice.ACTIONNODE[param]: - _LOGGER.error("%i is not a channel in hm device %s", - channel, address) + _LOGGER.error("%i is not a channel in hm device %s", channel, address) return # Call parameter hmdevice.actionNodeData(param, True, channel) hass.services.register( - DOMAIN, SERVICE_VIRTUALKEY, _hm_service_virtualkey, - schema=SCHEMA_SERVICE_VIRTUALKEY) + DOMAIN, + SERVICE_VIRTUALKEY, + _hm_service_virtualkey, + schema=SCHEMA_SERVICE_VIRTUALKEY, + ) def _service_handle_value(service): """Service to call setValue method for HomeMatic system variable.""" @@ -354,8 +456,9 @@ def setup(hass, config): value = service.data[ATTR_VALUE] if entity_ids: - entities = [entity for entity in entity_hubs if - entity.entity_id in entity_ids] + entities = [ + entity for entity in entity_hubs if entity.entity_id in entity_ids + ] else: entities = entity_hubs @@ -367,16 +470,22 @@ def setup(hass, config): hub.hm_set_variable(name, value) hass.services.register( - DOMAIN, SERVICE_SET_VARIABLE_VALUE, _service_handle_value, - schema=SCHEMA_SERVICE_SET_VARIABLE_VALUE) + DOMAIN, + SERVICE_SET_VARIABLE_VALUE, + _service_handle_value, + schema=SCHEMA_SERVICE_SET_VARIABLE_VALUE, + ) def _service_handle_reconnect(service): """Service to reconnect all HomeMatic hubs.""" homematic.reconnect() hass.services.register( - DOMAIN, SERVICE_RECONNECT, _service_handle_reconnect, - schema=SCHEMA_SERVICE_RECONNECT) + DOMAIN, + SERVICE_RECONNECT, + _service_handle_reconnect, + schema=SCHEMA_SERVICE_RECONNECT, + ) def _service_handle_device(service): """Service to call setValue method for HomeMatic devices.""" @@ -389,14 +498,14 @@ def setup(hass, config): # Convert value into correct XML-RPC Type. # https://docs.python.org/3/library/xmlrpc.client.html#xmlrpc.client.ServerProxy if value_type: - if value_type == 'int': + if value_type == "int": value = int(value) - elif value_type == 'double': + elif value_type == "double": value = float(value) - elif value_type == 'boolean': + elif value_type == "boolean": value = bool(value) - elif value_type == 'dateTime.iso8601': - value = datetime.strptime(value, '%Y%m%dT%H:%M:%S') + elif value_type == "dateTime.iso8601": + value = datetime.strptime(value, "%Y%m%dT%H:%M:%S") else: # Default is 'string' value = str(value) @@ -410,8 +519,11 @@ def setup(hass, config): hmdevice.setValue(param, value, channel) hass.services.register( - DOMAIN, SERVICE_SET_DEVICE_VALUE, _service_handle_device, - schema=SCHEMA_SERVICE_SET_DEVICE_VALUE) + DOMAIN, + SERVICE_SET_DEVICE_VALUE, + _service_handle_device, + schema=SCHEMA_SERVICE_SET_DEVICE_VALUE, + ) def _service_handle_install_mode(service): """Service to set interface into install mode.""" @@ -423,8 +535,11 @@ def setup(hass, config): homematic.setInstallMode(interface, t=time, mode=mode, address=address) hass.services.register( - DOMAIN, SERVICE_SET_INSTALL_MODE, _service_handle_install_mode, - schema=SCHEMA_SERVICE_SET_INSTALL_MODE) + DOMAIN, + SERVICE_SET_INSTALL_MODE, + _service_handle_install_mode, + schema=SCHEMA_SERVICE_SET_INSTALL_MODE, + ) def _service_put_paramset(service): """Service to call the putParamset method on a HomeMatic connection.""" @@ -438,13 +553,19 @@ def setup(hass, config): _LOGGER.debug( "Calling putParamset: %s, %s, %s, %s", - interface, address, paramset_key, paramset + interface, + address, + paramset_key, + paramset, ) homematic.putParamset(interface, address, paramset_key, paramset) hass.services.register( - DOMAIN, SERVICE_PUT_PARAMSET, _service_put_paramset, - schema=SCHEMA_SERVICE_PUT_PARAMSET) + DOMAIN, + SERVICE_PUT_PARAMSET, + _service_put_paramset, + schema=SCHEMA_SERVICE_PUT_PARAMSET, + ) return True @@ -452,17 +573,17 @@ def setup(hass, config): def _system_callback_handler(hass, config, src, *args): """System callback handler.""" # New devices available at hub - if src == 'newDevices': + if src == "newDevices": (interface_id, dev_descriptions) = args - interface = interface_id.split('-')[-1] + interface = interface_id.split("-")[-1] # Device support active? - if not hass.data[DATA_CONF][interface]['connect']: + if not hass.data[DATA_CONF][interface]["connect"]: return addresses = [] for dev in dev_descriptions: - address = dev['ADDRESS'].split(':')[0] + address = dev["ADDRESS"].split(":")[0] if address not in hass.data[DATA_STORE]: hass.data[DATA_STORE].add(address) addresses.append(address) @@ -474,40 +595,42 @@ def _system_callback_handler(hass, config, src, *args): hmdevice = hass.data[DATA_HOMEMATIC].devices[interface].get(dev) if hmdevice.EVENTNODE: - hmdevice.setEventCallback( - callback=bound_event_callback, bequeath=True) + hmdevice.setEventCallback(callback=bound_event_callback, bequeath=True) # Create HASS entities if addresses: for component_name, discovery_type in ( - ('switch', DISCOVER_SWITCHES), - ('light', DISCOVER_LIGHTS), - ('cover', DISCOVER_COVER), - ('binary_sensor', DISCOVER_BINARY_SENSORS), - ('sensor', DISCOVER_SENSORS), - ('climate', DISCOVER_CLIMATE), - ('lock', DISCOVER_LOCKS), - ('binary_sensor', DISCOVER_BATTERY)): + ("switch", DISCOVER_SWITCHES), + ("light", DISCOVER_LIGHTS), + ("cover", DISCOVER_COVER), + ("binary_sensor", DISCOVER_BINARY_SENSORS), + ("sensor", DISCOVER_SENSORS), + ("climate", DISCOVER_CLIMATE), + ("lock", DISCOVER_LOCKS), + ("binary_sensor", DISCOVER_BATTERY), + ): # Get all devices of a specific type - found_devices = _get_devices( - hass, discovery_type, addresses, interface) + found_devices = _get_devices(hass, discovery_type, addresses, interface) # When devices of this type are found # they are setup in HASS and a discovery event is fired if found_devices: - discovery.load_platform(hass, component_name, DOMAIN, { - ATTR_DISCOVER_DEVICES: found_devices, - ATTR_DISCOVERY_TYPE: discovery_type, - }, config) + discovery.load_platform( + hass, + component_name, + DOMAIN, + { + ATTR_DISCOVER_DEVICES: found_devices, + ATTR_DISCOVERY_TYPE: discovery_type, + }, + config, + ) # Homegear error message - elif src == 'error': + elif src == "error": _LOGGER.error("Error: %s", args) (interface_id, errorcode, message) = args - hass.bus.fire(EVENT_ERROR, { - ATTR_ERRORCODE: errorcode, - ATTR_MESSAGE: message - }) + hass.bus.fire(EVENT_ERROR, {ATTR_ERRORCODE: errorcode, ATTR_MESSAGE: message}) def _get_devices(hass, discovery_type, keys, interface): @@ -520,8 +643,10 @@ def _get_devices(hass, discovery_type, keys, interface): metadata = {} # Class not supported by discovery type - if discovery_type != DISCOVER_BATTERY and \ - class_name not in HM_DEVICE_TYPES[discovery_type]: + if ( + discovery_type != DISCOVER_BATTERY + and class_name not in HM_DEVICE_TYPES[discovery_type] + ): continue # Load metadata needed to generate a parameter list @@ -531,11 +656,9 @@ def _get_devices(hass, discovery_type, keys, interface): metadata.update(device.BINARYNODE) elif discovery_type == DISCOVER_BATTERY: if ATTR_LOWBAT in device.ATTRIBUTENODE: - metadata.update( - {ATTR_LOWBAT: device.ATTRIBUTENODE[ATTR_LOWBAT]}) + metadata.update({ATTR_LOWBAT: device.ATTRIBUTENODE[ATTR_LOWBAT]}) elif ATTR_LOW_BAT in device.ATTRIBUTENODE: - metadata.update( - {ATTR_LOW_BAT: device.ATTRIBUTENODE[ATTR_LOW_BAT]}) + metadata.update({ATTR_LOW_BAT: device.ATTRIBUTENODE[ATTR_LOW_BAT]}) else: continue else: @@ -543,21 +666,22 @@ def _get_devices(hass, discovery_type, keys, interface): # Generate options for 1...n elements with 1...n parameters for param, channels in metadata.items(): - if param in HM_IGNORE_DISCOVERY_NODE and class_name not in \ - HM_IGNORE_DISCOVERY_NODE_EXCEPTIONS.get(param, []): + if ( + param in HM_IGNORE_DISCOVERY_NODE + and class_name not in HM_IGNORE_DISCOVERY_NODE_EXCEPTIONS.get(param, []) + ): continue # Add devices - _LOGGER.debug("%s: Handling %s: %s: %s", - discovery_type, key, param, channels) + _LOGGER.debug( + "%s: Handling %s: %s: %s", discovery_type, key, param, channels + ) for channel in channels: name = _create_ha_id( - name=device.NAME, channel=channel, param=param, - count=len(channels) + name=device.NAME, channel=channel, param=param, count=len(channels) ) unique_id = _create_ha_id( - name=key, channel=channel, param=param, - count=len(channels) + name=key, channel=channel, param=param, count=len(channels) ) device_dict = { CONF_PLATFORM: "homematic", @@ -565,7 +689,7 @@ def _get_devices(hass, discovery_type, keys, interface): ATTR_INTERFACE: interface, ATTR_NAME: name, ATTR_CHANNEL: channel, - ATTR_UNIQUE_ID: unique_id + ATTR_UNIQUE_ID: unique_id, } if param is not None: device_dict[ATTR_PARAM] = param @@ -575,8 +699,7 @@ def _get_devices(hass, discovery_type, keys, interface): DEVICE_SCHEMA(device_dict) device_arr.append(device_dict) except vol.MultipleInvalid as err: - _LOGGER.error("Invalid device config: %s", - str(err)) + _LOGGER.error("Invalid device config: %s", str(err)) return device_arr @@ -613,24 +736,19 @@ def _hm_event_handler(hass, interface, device, caller, attribute, value): if attribute not in hmdevice.EVENTNODE: return - _LOGGER.debug("Event %s for %s channel %i", attribute, - hmdevice.NAME, channel) + _LOGGER.debug("Event %s for %s channel %i", attribute, hmdevice.NAME, channel) # Keypress event if attribute in HM_PRESS_EVENTS: - hass.bus.fire(EVENT_KEYPRESS, { - ATTR_NAME: hmdevice.NAME, - ATTR_PARAM: attribute, - ATTR_CHANNEL: channel - }) + hass.bus.fire( + EVENT_KEYPRESS, + {ATTR_NAME: hmdevice.NAME, ATTR_PARAM: attribute, ATTR_CHANNEL: channel}, + ) return # Impulse event if attribute in HM_IMPULSE_EVENTS: - hass.bus.fire(EVENT_IMPULSE, { - ATTR_NAME: hmdevice.NAME, - ATTR_CHANNEL: channel - }) + hass.bus.fire(EVENT_IMPULSE, {ATTR_NAME: hmdevice.NAME, ATTR_CHANNEL: channel}) return _LOGGER.warning("Event is unknown and not forwarded") @@ -640,8 +758,8 @@ def _device_from_servicecall(hass, service): """Extract HomeMatic device from service call.""" address = service.data.get(ATTR_ADDRESS) interface = service.data.get(ATTR_INTERFACE) - if address == 'BIDCOS-RF': - address = 'BidCoS-RF' + if address == "BIDCOS-RF": + address = "BidCoS-RF" if interface: return hass.data[DATA_HOMEMATIC].devices[interface].get(address) @@ -664,12 +782,12 @@ class HMHub(Entity): self._state = None # Load data - self.hass.helpers.event.track_time_interval( - self._update_hub, SCAN_INTERVAL_HUB) + self.hass.helpers.event.track_time_interval(self._update_hub, SCAN_INTERVAL_HUB) self.hass.add_job(self._update_hub, None) self.hass.helpers.event.track_time_interval( - self._update_variables, SCAN_INTERVAL_VARIABLES) + self._update_variables, SCAN_INTERVAL_VARIABLES + ) self.hass.add_job(self._update_variables, None) @property @@ -799,8 +917,8 @@ class HMDevice(Entity): attr[data[0]] = value # Static attributes - attr['id'] = self._hmdevice.ADDRESS - attr['interface'] = self._interface + attr["id"] = self._hmdevice.ADDRESS + attr["interface"] = self._interface return attr @@ -811,8 +929,7 @@ class HMDevice(Entity): # Initialize self._homematic = self.hass.data[DATA_HOMEMATIC] - self._hmdevice = \ - self._homematic.devices[self._interface][self._address] + self._hmdevice = self._homematic.devices[self._interface][self._address] self._connected = True try: @@ -825,13 +942,11 @@ class HMDevice(Entity): self._available = not self._hmdevice.UNREACH except Exception as err: # pylint: disable=broad-except self._connected = False - _LOGGER.error("Exception while linking %s: %s", - self._address, str(err)) + _LOGGER.error("Exception while linking %s: %s", self._address, str(err)) def _hm_event_callback(self, device, caller, attribute, value): """Handle all pyhomematic device events.""" - _LOGGER.debug("%s received event '%s' value: %s", self._name, - attribute, value) + _LOGGER.debug("%s received event '%s' value: %s", self._name, attribute, value) has_changed = False # Is data needed for this instance? @@ -855,10 +970,14 @@ class HMDevice(Entity): channels_to_sub = set() # Push data to channels_to_sub from hmdevice metadata - for metadata in (self._hmdevice.SENSORNODE, self._hmdevice.BINARYNODE, - self._hmdevice.ATTRIBUTENODE, - self._hmdevice.WRITENODE, self._hmdevice.EVENTNODE, - self._hmdevice.ACTIONNODE): + for metadata in ( + self._hmdevice.SENSORNODE, + self._hmdevice.BINARYNODE, + self._hmdevice.ATTRIBUTENODE, + self._hmdevice.WRITENODE, + self._hmdevice.EVENTNODE, + self._hmdevice.ACTIONNODE, + ): for node, channels in metadata.items(): # Data is needed for this instance if node in self._data: @@ -872,16 +991,14 @@ class HMDevice(Entity): try: channels_to_sub.add(int(channel)) except (ValueError, TypeError): - _LOGGER.error("Invalid channel in metadata from %s", - self._name) + _LOGGER.error("Invalid channel in metadata from %s", self._name) # Set callbacks for channel in channels_to_sub: - _LOGGER.debug( - "Subscribe channel %d from %s", channel, self._name) + _LOGGER.debug("Subscribe channel %d from %s", channel, self._name) self._hmdevice.setEventCallback( - callback=self._hm_event_callback, bequeath=False, - channel=channel) + callback=self._hm_event_callback, bequeath=False, channel=channel + ) def _load_data_from_hm(self): """Load first value from pyhomematic.""" @@ -890,11 +1007,11 @@ class HMDevice(Entity): # Read data from pyhomematic for metadata, funct in ( - (self._hmdevice.ATTRIBUTENODE, - self._hmdevice.getAttributeData), - (self._hmdevice.WRITENODE, self._hmdevice.getWriteData), - (self._hmdevice.SENSORNODE, self._hmdevice.getSensorData), - (self._hmdevice.BINARYNODE, self._hmdevice.getBinaryData)): + (self._hmdevice.ATTRIBUTENODE, self._hmdevice.getAttributeData), + (self._hmdevice.WRITENODE, self._hmdevice.getWriteData), + (self._hmdevice.SENSORNODE, self._hmdevice.getSensorData), + (self._hmdevice.BINARYNODE, self._hmdevice.getBinaryData), + ): for node in metadata: if metadata[node] and node in self._data: self._data[node] = funct(name=node, channel=self._channel) diff --git a/homeassistant/components/homematic/binary_sensor.py b/homeassistant/components/homematic/binary_sensor.py index 8a1db6a8a7b..064b6f3e009 100644 --- a/homeassistant/components/homematic/binary_sensor.py +++ b/homeassistant/components/homematic/binary_sensor.py @@ -2,8 +2,7 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.homematic import ( - ATTR_DISCOVERY_TYPE, DISCOVER_BATTERY) +from homeassistant.components.homematic import ATTR_DISCOVERY_TYPE, DISCOVER_BATTERY from homeassistant.const import DEVICE_CLASS_BATTERY from . import ATTR_DISCOVER_DEVICES, HMDevice @@ -11,19 +10,19 @@ from . import ATTR_DISCOVER_DEVICES, HMDevice _LOGGER = logging.getLogger(__name__) SENSOR_TYPES_CLASS = { - 'IPShutterContact': 'opening', - 'IPShutterContactSabotage': 'opening', - 'MaxShutterContact': 'opening', - 'Motion': 'motion', - 'MotionV2': 'motion', - 'PresenceIP': 'motion', - 'Remote': None, - 'RemoteMotion': None, - 'ShutterContact': 'opening', - 'Smoke': 'smoke', - 'SmokeV2': 'smoke', - 'TiltSensor': None, - 'WeatherSensor': None, + "IPShutterContact": "opening", + "IPShutterContactSabotage": "opening", + "MaxShutterContact": "opening", + "Motion": "motion", + "MotionV2": "motion", + "PresenceIP": "motion", + "Remote": None, + "RemoteMotion": None, + "ShutterContact": "opening", + "Smoke": "smoke", + "SmokeV2": "smoke", + "TiltSensor": None, + "WeatherSensor": None, } @@ -56,8 +55,8 @@ class HMBinarySensor(HMDevice, BinarySensorDevice): def device_class(self): """Return the class of this sensor from DEVICE_CLASSES.""" # If state is MOTION (Only RemoteMotion working) - if self._state == 'MOTION': - return 'motion' + if self._state == "MOTION": + return "motion" return SENSOR_TYPES_CLASS.get(self._hmdevice.__class__.__name__, None) def _init_data_struct(self): diff --git a/homeassistant/components/homematic/climate.py b/homeassistant/components/homematic/climate.py index 584b2dd761a..008055b649a 100644 --- a/homeassistant/components/homematic/climate.py +++ b/homeassistant/components/homematic/climate.py @@ -3,24 +3,24 @@ import logging from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( - HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF, PRESET_BOOST, - PRESET_COMFORT, PRESET_ECO, SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE) + HVAC_MODE_AUTO, + HVAC_MODE_HEAT, + HVAC_MODE_OFF, + PRESET_BOOST, + PRESET_COMFORT, + PRESET_ECO, + SUPPORT_PRESET_MODE, + SUPPORT_TARGET_TEMPERATURE, +) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS from . import ATTR_DISCOVER_DEVICES, HM_ATTRIBUTE_SUPPORT, HMDevice _LOGGER = logging.getLogger(__name__) -HM_TEMP_MAP = [ - 'ACTUAL_TEMPERATURE', - 'TEMPERATURE', -] +HM_TEMP_MAP = ["ACTUAL_TEMPERATURE", "TEMPERATURE"] -HM_HUMI_MAP = [ - 'ACTUAL_HUMIDITY', - 'HUMIDITY', -] +HM_HUMI_MAP = ["ACTUAL_HUMIDITY", "HUMIDITY"] HM_PRESET_MAP = { "BOOST_MODE": PRESET_BOOST, @@ -28,8 +28,8 @@ HM_PRESET_MAP = { "LOWERING_MODE": PRESET_ECO, } -HM_CONTROL_MODE = 'CONTROL_MODE' -HMIP_CONTROL_MODE = 'SET_POINT_MODE' +HM_CONTROL_MODE = "CONTROL_MODE" +HMIP_CONTROL_MODE = "SET_POINT_MODE" SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE @@ -91,8 +91,8 @@ class HMThermostat(HMDevice, ClimateDevice): @property def preset_mode(self): """Return the current preset mode, e.g., home, away, temp.""" - if self._data.get('BOOST_MODE', False): - return 'boost' + if self._data.get("BOOST_MODE", False): + return "boost" # Get the name of the mode mode = HM_ATTRIBUTE_SUPPORT[HM_CONTROL_MODE][1][self._hm_controll_mode] @@ -178,15 +178,17 @@ class HMThermostat(HMDevice, ClimateDevice): if HMIP_CONTROL_MODE in self._data: return self._data[HMIP_CONTROL_MODE] # Homematic - return self._data['CONTROL_MODE'] + return self._data["CONTROL_MODE"] def _init_data_struct(self): """Generate a data dict (self._data) from the Homematic metadata.""" self._state = next(iter(self._hmdevice.WRITENODE.keys())) self._data[self._state] = None - if HM_CONTROL_MODE in self._hmdevice.ATTRIBUTENODE or \ - HMIP_CONTROL_MODE in self._hmdevice.ATTRIBUTENODE: + if ( + HM_CONTROL_MODE in self._hmdevice.ATTRIBUTENODE + or HMIP_CONTROL_MODE in self._hmdevice.ATTRIBUTENODE + ): self._data[HM_CONTROL_MODE] = None for node in self._hmdevice.SENSORNODE.keys(): diff --git a/homeassistant/components/homematic/cover.py b/homeassistant/components/homematic/cover.py index 28e66f39a50..893b3ce8921 100644 --- a/homeassistant/components/homematic/cover.py +++ b/homeassistant/components/homematic/cover.py @@ -2,7 +2,10 @@ import logging from homeassistant.components.cover import ( - ATTR_POSITION, ATTR_TILT_POSITION, CoverDevice) + ATTR_POSITION, + ATTR_TILT_POSITION, + CoverDevice, +) from homeassistant.const import STATE_UNKNOWN from . import ATTR_DISCOVER_DEVICES, HMDevice @@ -67,8 +70,7 @@ class HMCover(HMDevice, CoverDevice): self._state = "LEVEL" self._data.update({self._state: STATE_UNKNOWN}) if "LEVEL_2" in self._hmdevice.WRITENODE: - self._data.update( - {'LEVEL_2': STATE_UNKNOWN}) + self._data.update({"LEVEL_2": STATE_UNKNOWN}) @property def current_cover_tilt_position(self): @@ -76,10 +78,10 @@ class HMCover(HMDevice, CoverDevice): None is unknown, 0 is closed, 100 is fully open. """ - if 'LEVEL_2' not in self._data: + if "LEVEL_2" not in self._data: return None - return int(self._data.get('LEVEL_2', 0) * 100) + return int(self._data.get("LEVEL_2", 0) * 100) def set_cover_tilt_position(self, **kwargs): """Move the cover tilt to a specific position.""" diff --git a/homeassistant/components/homematic/light.py b/homeassistant/components/homematic/light.py index f9bc785d3f4..971a8a9cac0 100644 --- a/homeassistant/components/homematic/light.py +++ b/homeassistant/components/homematic/light.py @@ -2,8 +2,15 @@ import logging from homeassistant.components.light import ( - ATTR_BRIGHTNESS, ATTR_EFFECT, ATTR_HS_COLOR, ATTR_TRANSITION, - SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_EFFECT, Light) + ATTR_BRIGHTNESS, + ATTR_EFFECT, + ATTR_HS_COLOR, + ATTR_TRANSITION, + SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, + SUPPORT_EFFECT, + Light, +) from . import ATTR_DISCOVER_DEVICES, HMDevice @@ -32,7 +39,7 @@ class HMLight(HMDevice, Light): def brightness(self): """Return the brightness of this light between 0..255.""" # Is dimmer? - if self._state == 'LEVEL': + if self._state == "LEVEL": return int(self._hm_get_state() * 255) return None @@ -47,7 +54,7 @@ class HMLight(HMDevice, Light): @property def supported_features(self): """Flag supported features.""" - if 'COLOR' in self._hmdevice.WRITENODE: + if "COLOR" in self._hmdevice.WRITENODE: return SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_EFFECT return SUPPORT_BRIGHTNESS @@ -57,7 +64,7 @@ class HMLight(HMDevice, Light): if not self.supported_features & SUPPORT_COLOR: return None hue, sat = self._hmdevice.get_hs_color() - return hue*360.0, sat*100.0 + return hue * 360.0, sat * 100.0 @property def effect_list(self): @@ -76,7 +83,7 @@ class HMLight(HMDevice, Light): def turn_on(self, **kwargs): """Turn the light on and/or change color or color effect settings.""" if ATTR_TRANSITION in kwargs: - self._hmdevice.setValue('RAMP_TIME', kwargs[ATTR_TRANSITION]) + self._hmdevice.setValue("RAMP_TIME", kwargs[ATTR_TRANSITION]) if ATTR_BRIGHTNESS in kwargs and self._state == "LEVEL": percent_bright = float(kwargs[ATTR_BRIGHTNESS]) / 255 @@ -86,8 +93,9 @@ class HMLight(HMDevice, Light): if ATTR_HS_COLOR in kwargs: self._hmdevice.set_hs_color( - hue=kwargs[ATTR_HS_COLOR][0]/360.0, - saturation=kwargs[ATTR_HS_COLOR][1]/100.0) + hue=kwargs[ATTR_HS_COLOR][0] / 360.0, + saturation=kwargs[ATTR_HS_COLOR][1] / 100.0, + ) if ATTR_EFFECT in kwargs: self._hmdevice.set_effect(kwargs[ATTR_EFFECT]) diff --git a/homeassistant/components/homematic/notify.py b/homeassistant/components/homematic/notify.py index 74ea7095b41..9fd94b9832c 100644 --- a/homeassistant/components/homematic/notify.py +++ b/homeassistant/components/homematic/notify.py @@ -4,22 +4,33 @@ import logging import voluptuous as vol from homeassistant.components.notify import ( - ATTR_DATA, PLATFORM_SCHEMA, BaseNotificationService) + ATTR_DATA, + PLATFORM_SCHEMA, + BaseNotificationService, +) import homeassistant.helpers.config_validation as cv import homeassistant.helpers.template as template_helper from . import ( - ATTR_ADDRESS, ATTR_CHANNEL, ATTR_INTERFACE, ATTR_PARAM, ATTR_VALUE, DOMAIN, - SERVICE_SET_DEVICE_VALUE) + ATTR_ADDRESS, + ATTR_CHANNEL, + ATTR_INTERFACE, + ATTR_PARAM, + ATTR_VALUE, + DOMAIN, + SERVICE_SET_DEVICE_VALUE, +) _LOGGER = logging.getLogger(__name__) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(ATTR_ADDRESS): vol.All(cv.string, vol.Upper), - vol.Required(ATTR_CHANNEL): vol.Coerce(int), - vol.Required(ATTR_PARAM): vol.All(cv.string, vol.Upper), - vol.Required(ATTR_VALUE): cv.match_all, - vol.Optional(ATTR_INTERFACE): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(ATTR_ADDRESS): vol.All(cv.string, vol.Upper), + vol.Required(ATTR_CHANNEL): vol.Coerce(int), + vol.Required(ATTR_PARAM): vol.All(cv.string, vol.Upper), + vol.Required(ATTR_VALUE): cv.match_all, + vol.Optional(ATTR_INTERFACE): cv.string, + } +) def get_service(hass, config, discovery_info=None): @@ -28,7 +39,7 @@ def get_service(hass, config, discovery_info=None): ATTR_ADDRESS: config[ATTR_ADDRESS], ATTR_CHANNEL: config[ATTR_CHANNEL], ATTR_PARAM: config[ATTR_PARAM], - ATTR_VALUE: config[ATTR_VALUE] + ATTR_VALUE: config[ATTR_VALUE], } if ATTR_INTERFACE in config: data[ATTR_INTERFACE] = config[ATTR_INTERFACE] diff --git a/homeassistant/components/homematic/sensor.py b/homeassistant/components/homematic/sensor.py index fca8c746a49..a3fc9f7e0fa 100644 --- a/homeassistant/components/homematic/sensor.py +++ b/homeassistant/components/homematic/sensor.py @@ -8,56 +8,59 @@ from . import ATTR_DISCOVER_DEVICES, HMDevice _LOGGER = logging.getLogger(__name__) HM_STATE_HA_CAST = { - 'RotaryHandleSensor': {0: 'closed', 1: 'tilted', 2: 'open'}, - 'RotaryHandleSensorIP': {0: 'closed', 1: 'tilted', 2: 'open'}, - 'WaterSensor': {0: 'dry', 1: 'wet', 2: 'water'}, - 'CO2Sensor': {0: 'normal', 1: 'added', 2: 'strong'}, - 'IPSmoke': {0: 'off', 1: 'primary', 2: 'intrusion', 3: 'secondary'}, - 'RFSiren': { - 0: 'disarmed', 1: 'extsens_armed', 2: 'allsens_armed', - 3: 'alarm_blocked'}, + "RotaryHandleSensor": {0: "closed", 1: "tilted", 2: "open"}, + "RotaryHandleSensorIP": {0: "closed", 1: "tilted", 2: "open"}, + "WaterSensor": {0: "dry", 1: "wet", 2: "water"}, + "CO2Sensor": {0: "normal", 1: "added", 2: "strong"}, + "IPSmoke": {0: "off", 1: "primary", 2: "intrusion", 3: "secondary"}, + "RFSiren": { + 0: "disarmed", + 1: "extsens_armed", + 2: "allsens_armed", + 3: "alarm_blocked", + }, } HM_UNIT_HA_CAST = { - 'HUMIDITY': '%', - 'TEMPERATURE': '°C', - 'ACTUAL_TEMPERATURE': '°C', - 'BRIGHTNESS': '#', - 'POWER': POWER_WATT, - 'CURRENT': 'mA', - 'VOLTAGE': 'V', - 'ENERGY_COUNTER': ENERGY_WATT_HOUR, - 'GAS_POWER': 'm3', - 'GAS_ENERGY_COUNTER': 'm3', - 'LUX': 'lx', - 'ILLUMINATION': 'lx', - 'CURRENT_ILLUMINATION': 'lx', - 'AVERAGE_ILLUMINATION': 'lx', - 'LOWEST_ILLUMINATION': 'lx', - 'HIGHEST_ILLUMINATION': 'lx', - 'RAIN_COUNTER': 'mm', - 'WIND_SPEED': 'km/h', - 'WIND_DIRECTION': '°', - 'WIND_DIRECTION_RANGE': '°', - 'SUNSHINEDURATION': '#', - 'AIR_PRESSURE': 'hPa', - 'FREQUENCY': 'Hz', - 'VALUE': '#', + "HUMIDITY": "%", + "TEMPERATURE": "°C", + "ACTUAL_TEMPERATURE": "°C", + "BRIGHTNESS": "#", + "POWER": POWER_WATT, + "CURRENT": "mA", + "VOLTAGE": "V", + "ENERGY_COUNTER": ENERGY_WATT_HOUR, + "GAS_POWER": "m3", + "GAS_ENERGY_COUNTER": "m3", + "LUX": "lx", + "ILLUMINATION": "lx", + "CURRENT_ILLUMINATION": "lx", + "AVERAGE_ILLUMINATION": "lx", + "LOWEST_ILLUMINATION": "lx", + "HIGHEST_ILLUMINATION": "lx", + "RAIN_COUNTER": "mm", + "WIND_SPEED": "km/h", + "WIND_DIRECTION": "°", + "WIND_DIRECTION_RANGE": "°", + "SUNSHINEDURATION": "#", + "AIR_PRESSURE": "hPa", + "FREQUENCY": "Hz", + "VALUE": "#", } HM_ICON_HA_CAST = { - 'WIND_SPEED': 'mdi:weather-windy', - 'HUMIDITY': 'mdi:water-percent', - 'TEMPERATURE': 'mdi:thermometer', - 'ACTUAL_TEMPERATURE': 'mdi:thermometer', - 'LUX': 'mdi:weather-sunny', - 'CURRENT_ILLUMINATION': 'mdi:weather-sunny', - 'AVERAGE_ILLUMINATION': 'mdi:weather-sunny', - 'LOWEST_ILLUMINATION': 'mdi:weather-sunny', - 'HIGHEST_ILLUMINATION': 'mdi:weather-sunny', - 'BRIGHTNESS': 'mdi:invert-colors', - 'POWER': 'mdi:flash-red-eye', - 'CURRENT': 'mdi:flash-red-eye', + "WIND_SPEED": "mdi:weather-windy", + "HUMIDITY": "mdi:water-percent", + "TEMPERATURE": "mdi:thermometer", + "ACTUAL_TEMPERATURE": "mdi:thermometer", + "LUX": "mdi:weather-sunny", + "CURRENT_ILLUMINATION": "mdi:weather-sunny", + "AVERAGE_ILLUMINATION": "mdi:weather-sunny", + "LOWEST_ILLUMINATION": "mdi:weather-sunny", + "HIGHEST_ILLUMINATION": "mdi:weather-sunny", + "BRIGHTNESS": "mdi:invert-colors", + "POWER": "mdi:flash-red-eye", + "CURRENT": "mdi:flash-red-eye", } diff --git a/homeassistant/components/homematicip_cloud/__init__.py b/homeassistant/components/homematicip_cloud/__init__.py index f73ce5a9d21..f2d84095b19 100644 --- a/homeassistant/components/homematicip_cloud/__init__.py +++ b/homeassistant/components/homematicip_cloud/__init__.py @@ -13,61 +13,78 @@ from homeassistant.helpers.typing import ConfigType from .config_flow import configured_haps from .const import ( - CONF_ACCESSPOINT, CONF_AUTHTOKEN, DOMAIN, HMIPC_AUTHTOKEN, HMIPC_HAPID, - HMIPC_NAME) + CONF_ACCESSPOINT, + CONF_AUTHTOKEN, + DOMAIN, + HMIPC_AUTHTOKEN, + HMIPC_HAPID, + HMIPC_NAME, +) from .device import HomematicipGenericDevice # noqa: F401 from .hap import HomematicipAuth, HomematicipHAP # noqa: F401 _LOGGER = logging.getLogger(__name__) -ATTR_DURATION = 'duration' -ATTR_ENDTIME = 'endtime' -ATTR_TEMPERATURE = 'temperature' -ATTR_ACCESSPOINT_ID = 'accesspoint_id' +ATTR_DURATION = "duration" +ATTR_ENDTIME = "endtime" +ATTR_TEMPERATURE = "temperature" +ATTR_ACCESSPOINT_ID = "accesspoint_id" -SERVICE_ACTIVATE_ECO_MODE_WITH_DURATION = 'activate_eco_mode_with_duration' -SERVICE_ACTIVATE_ECO_MODE_WITH_PERIOD = 'activate_eco_mode_with_period' -SERVICE_ACTIVATE_VACATION = 'activate_vacation' -SERVICE_DEACTIVATE_ECO_MODE = 'deactivate_eco_mode' -SERVICE_DEACTIVATE_VACATION = 'deactivate_vacation' +SERVICE_ACTIVATE_ECO_MODE_WITH_DURATION = "activate_eco_mode_with_duration" +SERVICE_ACTIVATE_ECO_MODE_WITH_PERIOD = "activate_eco_mode_with_period" +SERVICE_ACTIVATE_VACATION = "activate_vacation" +SERVICE_DEACTIVATE_ECO_MODE = "deactivate_eco_mode" +SERVICE_DEACTIVATE_VACATION = "deactivate_vacation" -CONFIG_SCHEMA = vol.Schema({ - vol.Optional(DOMAIN, default=[]): vol.All(cv.ensure_list, [vol.Schema({ - vol.Optional(CONF_NAME, default=''): vol.Any(cv.string), - vol.Required(CONF_ACCESSPOINT): cv.string, - vol.Required(CONF_AUTHTOKEN): cv.string, - })]), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + vol.Optional(DOMAIN, default=[]): vol.All( + cv.ensure_list, + [ + vol.Schema( + { + vol.Optional(CONF_NAME, default=""): vol.Any(cv.string), + vol.Required(CONF_ACCESSPOINT): cv.string, + vol.Required(CONF_AUTHTOKEN): cv.string, + } + ) + ], + ) + }, + extra=vol.ALLOW_EXTRA, +) -SCHEMA_ACTIVATE_ECO_MODE_WITH_DURATION = vol.Schema({ - vol.Required(ATTR_DURATION): cv.positive_int, - vol.Optional(ATTR_ACCESSPOINT_ID): - vol.All(str, vol.Length(min=24, max=24)), -}) +SCHEMA_ACTIVATE_ECO_MODE_WITH_DURATION = vol.Schema( + { + vol.Required(ATTR_DURATION): cv.positive_int, + vol.Optional(ATTR_ACCESSPOINT_ID): vol.All(str, vol.Length(min=24, max=24)), + } +) -SCHEMA_ACTIVATE_ECO_MODE_WITH_PERIOD = vol.Schema({ - vol.Required(ATTR_ENDTIME): cv.datetime, - vol.Optional(ATTR_ACCESSPOINT_ID): - vol.All(str, vol.Length(min=24, max=24)), -}) +SCHEMA_ACTIVATE_ECO_MODE_WITH_PERIOD = vol.Schema( + { + vol.Required(ATTR_ENDTIME): cv.datetime, + vol.Optional(ATTR_ACCESSPOINT_ID): vol.All(str, vol.Length(min=24, max=24)), + } +) -SCHEMA_ACTIVATE_VACATION = vol.Schema({ - vol.Required(ATTR_ENDTIME): cv.datetime, - vol.Required(ATTR_TEMPERATURE, default=18.0): - vol.All(vol.Coerce(float), vol.Range(min=0, max=55)), - vol.Optional(ATTR_ACCESSPOINT_ID): - vol.All(str, vol.Length(min=24, max=24)), -}) +SCHEMA_ACTIVATE_VACATION = vol.Schema( + { + vol.Required(ATTR_ENDTIME): cv.datetime, + vol.Required(ATTR_TEMPERATURE, default=18.0): vol.All( + vol.Coerce(float), vol.Range(min=0, max=55) + ), + vol.Optional(ATTR_ACCESSPOINT_ID): vol.All(str, vol.Length(min=24, max=24)), + } +) -SCHEMA_DEACTIVATE_ECO_MODE = vol.Schema({ - vol.Optional(ATTR_ACCESSPOINT_ID): - vol.All(str, vol.Length(min=24, max=24)), -}) +SCHEMA_DEACTIVATE_ECO_MODE = vol.Schema( + {vol.Optional(ATTR_ACCESSPOINT_ID): vol.All(str, vol.Length(min=24, max=24))} +) -SCHEMA_DEACTIVATE_VACATION = vol.Schema({ - vol.Optional(ATTR_ACCESSPOINT_ID): - vol.All(str, vol.Length(min=24, max=24)), -}) +SCHEMA_DEACTIVATE_VACATION = vol.Schema( + {vol.Optional(ATTR_ACCESSPOINT_ID): vol.All(str, vol.Length(min=24, max=24))} +) async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: @@ -78,14 +95,17 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: for conf in accesspoints: if conf[CONF_ACCESSPOINT] not in configured_haps(hass): - hass.async_add_job(hass.config_entries.flow.async_init( - DOMAIN, context={'source': config_entries.SOURCE_IMPORT}, - data={ - HMIPC_HAPID: conf[CONF_ACCESSPOINT], - HMIPC_AUTHTOKEN: conf[CONF_AUTHTOKEN], - HMIPC_NAME: conf[CONF_NAME], - } - )) + hass.async_add_job( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={ + HMIPC_HAPID: conf[CONF_ACCESSPOINT], + HMIPC_AUTHTOKEN: conf[CONF_AUTHTOKEN], + HMIPC_NAME: conf[CONF_NAME], + }, + ) + ) async def _async_activate_eco_mode_with_duration(service): """Service to activate eco mode with duration.""" @@ -102,9 +122,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: await home.activate_absence_with_duration(duration) hass.services.async_register( - DOMAIN, SERVICE_ACTIVATE_ECO_MODE_WITH_DURATION, + DOMAIN, + SERVICE_ACTIVATE_ECO_MODE_WITH_DURATION, _async_activate_eco_mode_with_duration, - schema=SCHEMA_ACTIVATE_ECO_MODE_WITH_DURATION) + schema=SCHEMA_ACTIVATE_ECO_MODE_WITH_DURATION, + ) async def _async_activate_eco_mode_with_period(service): """Service to activate eco mode with period.""" @@ -121,9 +143,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: await home.activate_absence_with_period(endtime) hass.services.async_register( - DOMAIN, SERVICE_ACTIVATE_ECO_MODE_WITH_PERIOD, + DOMAIN, + SERVICE_ACTIVATE_ECO_MODE_WITH_PERIOD, _async_activate_eco_mode_with_period, - schema=SCHEMA_ACTIVATE_ECO_MODE_WITH_PERIOD) + schema=SCHEMA_ACTIVATE_ECO_MODE_WITH_PERIOD, + ) async def _async_activate_vacation(service): """Service to activate vacation.""" @@ -141,8 +165,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: await home.activate_vacation(endtime, temperature) hass.services.async_register( - DOMAIN, SERVICE_ACTIVATE_VACATION, _async_activate_vacation, - schema=SCHEMA_ACTIVATE_VACATION) + DOMAIN, + SERVICE_ACTIVATE_VACATION, + _async_activate_vacation, + schema=SCHEMA_ACTIVATE_VACATION, + ) async def _async_deactivate_eco_mode(service): """Service to deactivate eco mode.""" @@ -158,8 +185,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: await home.deactivate_absence() hass.services.async_register( - DOMAIN, SERVICE_DEACTIVATE_ECO_MODE, _async_deactivate_eco_mode, - schema=SCHEMA_DEACTIVATE_ECO_MODE) + DOMAIN, + SERVICE_DEACTIVATE_ECO_MODE, + _async_deactivate_eco_mode, + schema=SCHEMA_DEACTIVATE_ECO_MODE, + ) async def _async_deactivate_vacation(service): """Service to deactivate vacation.""" @@ -175,8 +205,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: await home.deactivate_vacation() hass.services.async_register( - DOMAIN, SERVICE_DEACTIVATE_VACATION, _async_deactivate_vacation, - schema=SCHEMA_DEACTIVATE_VACATION) + DOMAIN, + SERVICE_DEACTIVATE_VACATION, + _async_deactivate_vacation, + schema=SCHEMA_DEACTIVATE_VACATION, + ) def _get_home(hapid: str): """Return a HmIP home.""" @@ -191,7 +224,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up an access point from a config entry.""" hap = HomematicipHAP(hass, entry) - hapid = entry.data[HMIPC_HAPID].replace('-', '').upper() + hapid = entry.data[HMIPC_HAPID].replace("-", "").upper() hass.data[DOMAIN][hapid] = hap if not await hap.async_setup(): @@ -201,12 +234,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: device_registry = await dr.async_get_registry(hass) home = hap.home # Add the HAP name from configuration if set. - hapname = home.label \ - if not home.name else "{} {}".format(home.label, home.name) + hapname = home.label if not home.name else "{} {}".format(home.label, home.name) device_registry.async_get_or_create( config_entry_id=home.id, identifiers={(DOMAIN, home.id)}, - manufacturer='eQ-3', + manufacturer="eQ-3", name=hapname, model=home.modelType, sw_version=home.currentAPVersion, diff --git a/homeassistant/components/homematicip_cloud/alarm_control_panel.py b/homeassistant/components/homematicip_cloud/alarm_control_panel.py index ccd19f26d68..38097afc1b6 100644 --- a/homeassistant/components/homematicip_cloud/alarm_control_panel.py +++ b/homeassistant/components/homematicip_cloud/alarm_control_panel.py @@ -8,25 +8,28 @@ from homematicip.base.enums import WindowState from homeassistant.components.alarm_control_panel import AlarmControlPanel from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( - STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, - STATE_ALARM_TRIGGERED) + STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_HOME, + STATE_ALARM_DISARMED, + STATE_ALARM_TRIGGERED, +) from homeassistant.core import HomeAssistant from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID _LOGGER = logging.getLogger(__name__) -CONST_ALARM_CONTROL_PANEL_NAME = 'HmIP Alarm Control Panel' +CONST_ALARM_CONTROL_PANEL_NAME = "HmIP Alarm Control Panel" -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the HomematicIP Cloud alarm control devices.""" pass -async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, - async_add_entities) -> None: +async def async_setup_entry( + hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities +) -> None: """Set up the HomematicIP alrm control panel from a config entry.""" home = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]].home devices = [] @@ -51,7 +54,7 @@ class HomematicipAlarmControlPanel(AlarmControlPanel): self.alarm_state = STATE_ALARM_DISARMED for security_zone in security_zones: - if security_zone.label == 'INTERNAL': + if security_zone.label == "INTERNAL": self._internal_alarm_zone = security_zone else: self._external_alarm_zone = security_zone @@ -62,8 +65,7 @@ class HomematicipAlarmControlPanel(AlarmControlPanel): activation_state = self._home.get_security_zones_activation() # check arm_away if activation_state == (True, True): - if self._internal_alarm_zone_state or \ - self._external_alarm_zone_state: + if self._internal_alarm_zone_state or self._external_alarm_zone_state: return STATE_ALARM_TRIGGERED return STATE_ALARM_ARMED_AWAY # check arm_home @@ -102,8 +104,7 @@ class HomematicipAlarmControlPanel(AlarmControlPanel): def _async_device_changed(self, *args, **kwargs): """Handle device state changes.""" - _LOGGER.debug("Event %s (%s)", self.name, - CONST_ALARM_CONTROL_PANEL_NAME) + _LOGGER.debug("Event %s (%s)", self.name, CONST_ALARM_CONTROL_PANEL_NAME) self.async_schedule_update_ha_state() @property @@ -122,8 +123,10 @@ class HomematicipAlarmControlPanel(AlarmControlPanel): @property def available(self) -> bool: """Device available.""" - return not self._internal_alarm_zone.unreach or \ - not self._external_alarm_zone.unreach + return ( + not self._internal_alarm_zone.unreach + or not self._external_alarm_zone.unreach + ) @property def unique_id(self) -> str: @@ -133,11 +136,13 @@ class HomematicipAlarmControlPanel(AlarmControlPanel): def _get_zone_alarm_state(security_zone) -> bool: if security_zone.active: - if (security_zone.sabotage or - security_zone.motionDetected or - security_zone.presenceDetected or - security_zone.windowState == WindowState.OPEN or - security_zone.windowState == WindowState.TILTED): + if ( + security_zone.sabotage + or security_zone.motionDetected + or security_zone.presenceDetected + or security_zone.windowState == WindowState.OPEN + or security_zone.windowState == WindowState.TILTED + ): return True return False diff --git a/homeassistant/components/homematicip_cloud/binary_sensor.py b/homeassistant/components/homematicip_cloud/binary_sensor.py index 696b00883ae..9580b803596 100644 --- a/homeassistant/components/homematicip_cloud/binary_sensor.py +++ b/homeassistant/components/homematicip_cloud/binary_sensor.py @@ -2,20 +2,36 @@ import logging from homematicip.aio.device import ( - AsyncDevice, AsyncFullFlushContactInterface, AsyncMotionDetectorIndoor, - AsyncMotionDetectorOutdoor, AsyncMotionDetectorPushButton, - AsyncPresenceDetectorIndoor, AsyncRotaryHandleSensor, AsyncShutterContact, - AsyncSmokeDetector, AsyncWaterSensor, AsyncWeatherSensor, - AsyncWeatherSensorPlus, AsyncWeatherSensorPro) + AsyncDevice, + AsyncFullFlushContactInterface, + AsyncMotionDetectorIndoor, + AsyncMotionDetectorOutdoor, + AsyncMotionDetectorPushButton, + AsyncPresenceDetectorIndoor, + AsyncRotaryHandleSensor, + AsyncShutterContact, + AsyncSmokeDetector, + AsyncWaterSensor, + AsyncWeatherSensor, + AsyncWeatherSensorPlus, + AsyncWeatherSensorPro, +) from homematicip.aio.group import AsyncSecurityGroup, AsyncSecurityZoneGroup from homematicip.aio.home import AsyncHome from homematicip.base.enums import SmokeDetectorAlarmType, WindowState from homeassistant.components.binary_sensor import ( - DEVICE_CLASS_BATTERY, DEVICE_CLASS_DOOR, DEVICE_CLASS_LIGHT, - DEVICE_CLASS_MOISTURE, DEVICE_CLASS_MOTION, DEVICE_CLASS_OPENING, - DEVICE_CLASS_PRESENCE, DEVICE_CLASS_SAFETY, DEVICE_CLASS_SMOKE, - BinarySensorDevice) + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_DOOR, + DEVICE_CLASS_LIGHT, + DEVICE_CLASS_MOISTURE, + DEVICE_CLASS_MOTION, + DEVICE_CLASS_OPENING, + DEVICE_CLASS_PRESENCE, + DEVICE_CLASS_SAFETY, + DEVICE_CLASS_SMOKE, + BinarySensorDevice, +) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -24,25 +40,25 @@ from .device import ATTR_GROUP_MEMBER_UNREACHABLE _LOGGER = logging.getLogger(__name__) -ATTR_LOW_BATTERY = 'low_battery' -ATTR_MOTIONDETECTED = 'motion detected' -ATTR_PRESENCEDETECTED = 'presence detected' -ATTR_POWERMAINSFAILURE = 'power mains failure' -ATTR_WINDOWSTATE = 'window state' -ATTR_MOISTUREDETECTED = 'moisture detected' -ATTR_WATERLEVELDETECTED = 'water level detected' -ATTR_SMOKEDETECTORALARM = 'smoke detector alarm' -ATTR_TODAY_SUNSHINE_DURATION = 'today_sunshine_duration_in_minutes' +ATTR_LOW_BATTERY = "low_battery" +ATTR_MOTIONDETECTED = "motion detected" +ATTR_PRESENCEDETECTED = "presence detected" +ATTR_POWERMAINSFAILURE = "power mains failure" +ATTR_WINDOWSTATE = "window state" +ATTR_MOISTUREDETECTED = "moisture detected" +ATTR_WATERLEVELDETECTED = "water level detected" +ATTR_SMOKEDETECTORALARM = "smoke detector alarm" +ATTR_TODAY_SUNSHINE_DURATION = "today_sunshine_duration_in_minutes" -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the HomematicIP Cloud binary sensor devices.""" pass -async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, - async_add_entities) -> None: +async def async_setup_entry( + hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities +) -> None: """Set up the HomematicIP Cloud binary sensor from a config entry.""" home = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]].home devices = [] @@ -51,9 +67,14 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, devices.append(HomematicipContactInterface(home, device)) if isinstance(device, (AsyncShutterContact, AsyncRotaryHandleSensor)): devices.append(HomematicipShutterContact(home, device)) - if isinstance(device, (AsyncMotionDetectorIndoor, - AsyncMotionDetectorOutdoor, - AsyncMotionDetectorPushButton)): + if isinstance( + device, + ( + AsyncMotionDetectorIndoor, + AsyncMotionDetectorOutdoor, + AsyncMotionDetectorPushButton, + ), + ): devices.append(HomematicipMotionDetector(home, device)) if isinstance(device, AsyncPresenceDetectorIndoor): devices.append(HomematicipPresenceDetector(home, device)) @@ -61,11 +82,11 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, devices.append(HomematicipSmokeDetector(home, device)) if isinstance(device, AsyncWaterSensor): devices.append(HomematicipWaterDetector(home, device)) - if isinstance(device, (AsyncWeatherSensorPlus, - AsyncWeatherSensorPro)): + if isinstance(device, (AsyncWeatherSensorPlus, AsyncWeatherSensorPro)): devices.append(HomematicipRainSensor(home, device)) - if isinstance(device, (AsyncWeatherSensor, AsyncWeatherSensorPlus, - AsyncWeatherSensorPro)): + if isinstance( + device, (AsyncWeatherSensor, AsyncWeatherSensorPlus, AsyncWeatherSensorPro) + ): devices.append(HomematicipStormSensor(home, device)) devices.append(HomematicipSunshineSensor(home, device)) if isinstance(device, AsyncDevice) and device.lowBat is not None: @@ -81,8 +102,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities(devices) -class HomematicipContactInterface(HomematicipGenericDevice, - BinarySensorDevice): +class HomematicipContactInterface(HomematicipGenericDevice, BinarySensorDevice): """Representation of a HomematicIP Cloud contact interface.""" @property @@ -93,7 +113,7 @@ class HomematicipContactInterface(HomematicipGenericDevice, @property def is_on(self) -> bool: """Return true if the contact interface is on/open.""" - if hasattr(self._device, 'sabotage') and self._device.sabotage: + if hasattr(self._device, "sabotage") and self._device.sabotage: return True if self._device.windowState is None: return None @@ -111,7 +131,7 @@ class HomematicipShutterContact(HomematicipGenericDevice, BinarySensorDevice): @property def is_on(self) -> bool: """Return true if the shutter contact is on/open.""" - if hasattr(self._device, 'sabotage') and self._device.sabotage: + if hasattr(self._device, "sabotage") and self._device.sabotage: return True if self._device.windowState is None: return None @@ -129,13 +149,12 @@ class HomematicipMotionDetector(HomematicipGenericDevice, BinarySensorDevice): @property def is_on(self) -> bool: """Return true if motion is detected.""" - if hasattr(self._device, 'sabotage') and self._device.sabotage: + if hasattr(self._device, "sabotage") and self._device.sabotage: return True return self._device.motionDetected -class HomematicipPresenceDetector(HomematicipGenericDevice, - BinarySensorDevice): +class HomematicipPresenceDetector(HomematicipGenericDevice, BinarySensorDevice): """Representation of a HomematicIP Cloud presence detector.""" @property @@ -146,7 +165,7 @@ class HomematicipPresenceDetector(HomematicipGenericDevice, @property def is_on(self) -> bool: """Return true if presence is detected.""" - if hasattr(self._device, 'sabotage') and self._device.sabotage: + if hasattr(self._device, "sabotage") and self._device.sabotage: return True return self._device.presenceDetected @@ -162,8 +181,7 @@ class HomematicipSmokeDetector(HomematicipGenericDevice, BinarySensorDevice): @property def is_on(self) -> bool: """Return true if smoke is detected.""" - return (self._device.smokeDetectorAlarmType - != SmokeDetectorAlarmType.IDLE_OFF) + return self._device.smokeDetectorAlarmType != SmokeDetectorAlarmType.IDLE_OFF class HomematicipWaterDetector(HomematicipGenericDevice, BinarySensorDevice): @@ -190,7 +208,7 @@ class HomematicipStormSensor(HomematicipGenericDevice, BinarySensorDevice): @property def icon(self) -> str: """Return the icon.""" - return 'mdi:weather-windy' if self.is_on else 'mdi:pinwheel-outline' + return "mdi:weather-windy" if self.is_on else "mdi:pinwheel-outline" @property def is_on(self) -> bool: @@ -221,7 +239,7 @@ class HomematicipSunshineSensor(HomematicipGenericDevice, BinarySensorDevice): def __init__(self, home: AsyncHome, device) -> None: """Initialize sunshine sensor.""" - super().__init__(home, device, 'Sunshine') + super().__init__(home, device, "Sunshine") @property def device_class(self) -> str: @@ -237,10 +255,11 @@ class HomematicipSunshineSensor(HomematicipGenericDevice, BinarySensorDevice): def device_state_attributes(self): """Return the state attributes of the illuminance sensor.""" attr = super().device_state_attributes - if hasattr(self._device, 'todaySunshineDuration') and \ - self._device.todaySunshineDuration: - attr[ATTR_TODAY_SUNSHINE_DURATION] = \ - self._device.todaySunshineDuration + if ( + hasattr(self._device, "todaySunshineDuration") + and self._device.todaySunshineDuration + ): + attr[ATTR_TODAY_SUNSHINE_DURATION] = self._device.todaySunshineDuration return attr @@ -249,7 +268,7 @@ class HomematicipBatterySensor(HomematicipGenericDevice, BinarySensorDevice): def __init__(self, home: AsyncHome, device) -> None: """Initialize battery sensor.""" - super().__init__(home, device, 'Battery') + super().__init__(home, device, "Battery") @property def device_class(self) -> str: @@ -262,14 +281,12 @@ class HomematicipBatterySensor(HomematicipGenericDevice, BinarySensorDevice): return self._device.lowBat -class HomematicipSecurityZoneSensorGroup(HomematicipGenericDevice, - BinarySensorDevice): +class HomematicipSecurityZoneSensorGroup(HomematicipGenericDevice, BinarySensorDevice): """Representation of a HomematicIP Cloud security zone group.""" - def __init__(self, home: AsyncHome, device, - post: str = 'SecurityZone') -> None: + def __init__(self, home: AsyncHome, device, post: str = "SecurityZone") -> None: """Initialize security zone group.""" - device.modelType = 'HmIP-{}'.format(post) + device.modelType = "HmIP-{}".format(post) super().__init__(home, device, post) @property @@ -294,8 +311,10 @@ class HomematicipSecurityZoneSensorGroup(HomematicipGenericDevice, if self._device.presenceDetected: attr[ATTR_PRESENCEDETECTED] = True - if self._device.windowState is not None and \ - self._device.windowState != WindowState.CLOSED: + if ( + self._device.windowState is not None + and self._device.windowState != WindowState.CLOSED + ): attr[ATTR_WINDOWSTATE] = str(self._device.windowState) if self._device.unreach: attr[ATTR_GROUP_MEMBER_UNREACHABLE] = True @@ -304,25 +323,30 @@ class HomematicipSecurityZoneSensorGroup(HomematicipGenericDevice, @property def is_on(self) -> bool: """Return true if security issue detected.""" - if self._device.motionDetected or \ - self._device.presenceDetected or \ - self._device.unreach or \ - self._device.sabotage: + if ( + self._device.motionDetected + or self._device.presenceDetected + or self._device.unreach + or self._device.sabotage + ): return True - if self._device.windowState is not None and \ - self._device.windowState != WindowState.CLOSED: + if ( + self._device.windowState is not None + and self._device.windowState != WindowState.CLOSED + ): return True return False -class HomematicipSecuritySensorGroup(HomematicipSecurityZoneSensorGroup, - BinarySensorDevice): +class HomematicipSecuritySensorGroup( + HomematicipSecurityZoneSensorGroup, BinarySensorDevice +): """Representation of a HomematicIP security group.""" def __init__(self, home: AsyncHome, device) -> None: """Initialize security group.""" - super().__init__(home, device, 'Sensors') + super().__init__(home, device, "Sensors") @property def device_state_attributes(self): @@ -337,11 +361,11 @@ class HomematicipSecuritySensorGroup(HomematicipSecurityZoneSensorGroup, attr[ATTR_WATERLEVELDETECTED] = True if self._device.lowBat: attr[ATTR_LOW_BATTERY] = True - if self._device.smokeDetectorAlarmType is not None and \ - self._device.smokeDetectorAlarmType != \ - SmokeDetectorAlarmType.IDLE_OFF: - attr[ATTR_SMOKEDETECTORALARM] = \ - str(self._device.smokeDetectorAlarmType) + if ( + self._device.smokeDetectorAlarmType is not None + and self._device.smokeDetectorAlarmType != SmokeDetectorAlarmType.IDLE_OFF + ): + attr[ATTR_SMOKEDETECTORALARM] = str(self._device.smokeDetectorAlarmType) return attr @@ -349,14 +373,17 @@ class HomematicipSecuritySensorGroup(HomematicipSecurityZoneSensorGroup, def is_on(self) -> bool: """Return true if safety issue detected.""" parent_is_on = super().is_on - if parent_is_on or \ - self._device.powerMainsFailure or \ - self._device.moistureDetected or \ - self._device.waterlevelDetected or \ - self._device.lowBat: + if ( + parent_is_on + or self._device.powerMainsFailure + or self._device.moistureDetected + or self._device.waterlevelDetected + or self._device.lowBat + ): return True - if self._device.smokeDetectorAlarmType is not None and \ - self._device.smokeDetectorAlarmType != \ - SmokeDetectorAlarmType.IDLE_OFF: + if ( + self._device.smokeDetectorAlarmType is not None + and self._device.smokeDetectorAlarmType != SmokeDetectorAlarmType.IDLE_OFF + ): return True return False diff --git a/homeassistant/components/homematicip_cloud/climate.py b/homeassistant/components/homematicip_cloud/climate.py index 44f0c148a41..53e7403ce56 100644 --- a/homeassistant/components/homematicip_cloud/climate.py +++ b/homeassistant/components/homematicip_cloud/climate.py @@ -2,15 +2,20 @@ import logging from typing import Awaitable -from homematicip.aio.device import ( - AsyncHeatingThermostat, AsyncHeatingThermostatCompact) +from homematicip.aio.device import AsyncHeatingThermostat, AsyncHeatingThermostatCompact from homematicip.aio.group import AsyncHeatingGroup from homematicip.aio.home import AsyncHome from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( - HVAC_MODE_AUTO, HVAC_MODE_HEAT, PRESET_BOOST, PRESET_ECO, - SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE, PRESET_NONE) + HVAC_MODE_AUTO, + HVAC_MODE_HEAT, + PRESET_BOOST, + PRESET_ECO, + SUPPORT_PRESET_MODE, + SUPPORT_TARGET_TEMPERATURE, + PRESET_NONE, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS from homeassistant.core import HomeAssistant @@ -19,19 +24,19 @@ from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice _LOGGER = logging.getLogger(__name__) -HMIP_AUTOMATIC_CM = 'AUTOMATIC' -HMIP_MANUAL_CM = 'MANUAL' -HMIP_ECO_CM = 'ECO' +HMIP_AUTOMATIC_CM = "AUTOMATIC" +HMIP_MANUAL_CM = "MANUAL" +HMIP_ECO_CM = "ECO" -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the HomematicIP Cloud climate devices.""" pass -async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, - async_add_entities) -> None: +async def async_setup_entry( + hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities +) -> None: """Set up the HomematicIP climate from a config entry.""" home = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]].home devices = [] @@ -48,7 +53,7 @@ class HomematicipHeatingGroup(HomematicipGenericDevice, ClimateDevice): def __init__(self, home: AsyncHome, device) -> None: """Initialize heating group.""" - device.modelType = 'Group-Heating' + device.modelType = "Group-Heating" self._simple_heating = None if device.actualTemperature is None: self._simple_heating = _get_first_heating_thermostat(device) @@ -158,7 +163,6 @@ class HomematicipHeatingGroup(HomematicipGenericDevice, ClimateDevice): def _get_first_heating_thermostat(heating_group: AsyncHeatingGroup): """Return the first HeatingThermostat from a HeatingGroup.""" for device in heating_group.devices: - if isinstance(device, (AsyncHeatingThermostat, - AsyncHeatingThermostatCompact)): + if isinstance(device, (AsyncHeatingThermostat, AsyncHeatingThermostatCompact)): return device return None diff --git a/homeassistant/components/homematicip_cloud/config_flow.py b/homeassistant/components/homematicip_cloud/config_flow.py index 696425df5b5..a94ea7b53f1 100644 --- a/homeassistant/components/homematicip_cloud/config_flow.py +++ b/homeassistant/components/homematicip_cloud/config_flow.py @@ -7,16 +7,23 @@ from homeassistant import config_entries from homeassistant.core import HomeAssistant, callback from .const import ( - _LOGGER, DOMAIN as HMIPC_DOMAIN, HMIPC_AUTHTOKEN, HMIPC_HAPID, HMIPC_NAME, - HMIPC_PIN) + _LOGGER, + DOMAIN as HMIPC_DOMAIN, + HMIPC_AUTHTOKEN, + HMIPC_HAPID, + HMIPC_NAME, + HMIPC_PIN, +) from .hap import HomematicipAuth @callback def configured_haps(hass: HomeAssistant) -> Set[str]: """Return a set of the configured access points.""" - return set(entry.data[HMIPC_HAPID] for entry - in hass.config_entries.async_entries(HMIPC_DOMAIN)) + return set( + entry.data[HMIPC_HAPID] + for entry in hass.config_entries.async_entries(HMIPC_DOMAIN) + ) @config_entries.HANDLERS.register(HMIPC_DOMAIN) @@ -39,10 +46,9 @@ class HomematicipCloudFlowHandler(config_entries.ConfigFlow): errors = {} if user_input is not None: - user_input[HMIPC_HAPID] = \ - user_input[HMIPC_HAPID].replace('-', '').upper() + user_input[HMIPC_HAPID] = user_input[HMIPC_HAPID].replace("-", "").upper() if user_input[HMIPC_HAPID] in configured_haps(self.hass): - return self.async_abort(reason='already_configured') + return self.async_abort(reason="already_configured") self.auth = HomematicipAuth(self.hass, user_input) connected = await self.auth.async_setup() @@ -51,13 +57,15 @@ class HomematicipCloudFlowHandler(config_entries.ConfigFlow): return await self.async_step_link() return self.async_show_form( - step_id='init', - data_schema=vol.Schema({ - vol.Required(HMIPC_HAPID): str, - vol.Optional(HMIPC_NAME): str, - vol.Optional(HMIPC_PIN): str, - }), - errors=errors + step_id="init", + data_schema=vol.Schema( + { + vol.Required(HMIPC_HAPID): str, + vol.Optional(HMIPC_NAME): str, + vol.Optional(HMIPC_PIN): str, + } + ), + errors=errors, ) async def async_step_link(self, user_input=None): @@ -74,12 +82,13 @@ class HomematicipCloudFlowHandler(config_entries.ConfigFlow): data={ HMIPC_HAPID: self.auth.config.get(HMIPC_HAPID), HMIPC_AUTHTOKEN: authtoken, - HMIPC_NAME: self.auth.config.get(HMIPC_NAME) - }) - return self.async_abort(reason='connection_aborted') - errors['base'] = 'press_the_button' + HMIPC_NAME: self.auth.config.get(HMIPC_NAME), + }, + ) + return self.async_abort(reason="connection_aborted") + errors["base"] = "press_the_button" - return self.async_show_form(step_id='link', errors=errors) + return self.async_show_form(step_id="link", errors=errors) async def async_step_import(self, import_info): """Import a new access point as a config entry.""" @@ -87,17 +96,13 @@ class HomematicipCloudFlowHandler(config_entries.ConfigFlow): authtoken = import_info[HMIPC_AUTHTOKEN] name = import_info[HMIPC_NAME] - hapid = hapid.replace('-', '').upper() + hapid = hapid.replace("-", "").upper() if hapid in configured_haps(self.hass): - return self.async_abort(reason='already_configured') + return self.async_abort(reason="already_configured") _LOGGER.info("Imported authentication for %s", hapid) return self.async_create_entry( title=hapid, - data={ - HMIPC_AUTHTOKEN: authtoken, - HMIPC_HAPID: hapid, - HMIPC_NAME: name, - } + data={HMIPC_AUTHTOKEN: authtoken, HMIPC_HAPID: hapid, HMIPC_NAME: name}, ) diff --git a/homeassistant/components/homematicip_cloud/const.py b/homeassistant/components/homematicip_cloud/const.py index c9a5df601e4..5c48de975f9 100644 --- a/homeassistant/components/homematicip_cloud/const.py +++ b/homeassistant/components/homematicip_cloud/const.py @@ -1,25 +1,25 @@ """Constants for the HomematicIP Cloud component.""" import logging -_LOGGER = logging.getLogger('.') +_LOGGER = logging.getLogger(".") -DOMAIN = 'homematicip_cloud' +DOMAIN = "homematicip_cloud" COMPONENTS = [ - 'alarm_control_panel', - 'binary_sensor', - 'climate', - 'cover', - 'light', - 'sensor', - 'switch', - 'weather', + "alarm_control_panel", + "binary_sensor", + "climate", + "cover", + "light", + "sensor", + "switch", + "weather", ] -CONF_ACCESSPOINT = 'accesspoint' -CONF_AUTHTOKEN = 'authtoken' +CONF_ACCESSPOINT = "accesspoint" +CONF_AUTHTOKEN = "authtoken" -HMIPC_NAME = 'name' -HMIPC_HAPID = 'hapid' -HMIPC_AUTHTOKEN = 'authtoken' -HMIPC_PIN = 'pin' +HMIPC_NAME = "name" +HMIPC_HAPID = "hapid" +HMIPC_AUTHTOKEN = "authtoken" +HMIPC_PIN = "pin" diff --git a/homeassistant/components/homematicip_cloud/cover.py b/homeassistant/components/homematicip_cloud/cover.py index ca102b626c7..9252c4322d9 100644 --- a/homeassistant/components/homematicip_cloud/cover.py +++ b/homeassistant/components/homematicip_cloud/cover.py @@ -5,7 +5,10 @@ from typing import Optional from homematicip.aio.device import AsyncFullFlushBlind, AsyncFullFlushShutter from homeassistant.components.cover import ( - ATTR_POSITION, ATTR_TILT_POSITION, CoverDevice) + ATTR_POSITION, + ATTR_TILT_POSITION, + CoverDevice, +) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -19,14 +22,14 @@ HMIP_SLATS_OPEN = 0 HMIP_SLATS_CLOSED = 1 -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the HomematicIP Cloud cover devices.""" pass -async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, - async_add_entities) -> None: +async def async_setup_entry( + hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities +) -> None: """Set up the HomematicIP cover from a config entry.""" home = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]].home devices = [] diff --git a/homeassistant/components/homematicip_cloud/device.py b/homeassistant/components/homematicip_cloud/device.py index 3cd84791c67..021c264f63f 100644 --- a/homeassistant/components/homematicip_cloud/device.py +++ b/homeassistant/components/homematicip_cloud/device.py @@ -10,20 +10,19 @@ from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -ATTR_MODEL_TYPE = 'model_type' +ATTR_MODEL_TYPE = "model_type" # RSSI HAP -> Device -ATTR_RSSI_DEVICE = 'rssi_device' +ATTR_RSSI_DEVICE = "rssi_device" # RSSI Device -> HAP -ATTR_RSSI_PEER = 'rssi_peer' -ATTR_SABOTAGE = 'sabotage' -ATTR_GROUP_MEMBER_UNREACHABLE = 'group_member_unreachable' +ATTR_RSSI_PEER = "rssi_peer" +ATTR_SABOTAGE = "sabotage" +ATTR_GROUP_MEMBER_UNREACHABLE = "group_member_unreachable" class HomematicipGenericDevice(Entity): """Representation of an HomematicIP generic device.""" - def __init__(self, home: AsyncHome, device, - post: Optional[str] = None) -> None: + def __init__(self, home: AsyncHome, device, post: Optional[str] = None) -> None: """Initialize the generic device.""" self._home = home self._device = device @@ -36,16 +35,15 @@ class HomematicipGenericDevice(Entity): # Only physical devices should be HA devices. if isinstance(self._device, AsyncDevice): return { - 'identifiers': { + "identifiers": { # Serial numbers of Homematic IP device (homematicip_cloud.DOMAIN, self._device.id) }, - 'name': self._device.label, - 'manufacturer': self._device.oem, - 'model': self._device.modelType, - 'sw_version': self._device.firmwareVersion, - 'via_device': ( - homematicip_cloud.DOMAIN, self._device.homeId), + "name": self._device.label, + "manufacturer": self._device.oem, + "model": self._device.modelType, + "sw_version": self._device.firmwareVersion, + "via_device": (homematicip_cloud.DOMAIN, self._device.homeId), } return None @@ -62,9 +60,9 @@ class HomematicipGenericDevice(Entity): def name(self) -> str: """Return the name of the generic device.""" name = self._device.label - if self._home.name is not None and self._home.name != '': + if self._home.name is not None and self._home.name != "": name = "{} {}".format(self._home.name, name) - if self.post is not None and self.post != '': + if self.post is not None and self.post != "": name = "{} {}".format(name, self.post) return name @@ -86,22 +84,20 @@ class HomematicipGenericDevice(Entity): @property def icon(self) -> Optional[str]: """Return the icon.""" - if hasattr(self._device, 'lowBat') and self._device.lowBat: - return 'mdi:battery-outline' - if hasattr(self._device, 'sabotage') and self._device.sabotage: - return 'mdi:alert' + if hasattr(self._device, "lowBat") and self._device.lowBat: + return "mdi:battery-outline" + if hasattr(self._device, "sabotage") and self._device.sabotage: + return "mdi:alert" return None @property def device_state_attributes(self): """Return the state attributes of the generic device.""" attr = {ATTR_MODEL_TYPE: self._device.modelType} - if hasattr(self._device, 'sabotage') and self._device.sabotage: + if hasattr(self._device, "sabotage") and self._device.sabotage: attr[ATTR_SABOTAGE] = self._device.sabotage - if hasattr(self._device, 'rssiDeviceValue') and \ - self._device.rssiDeviceValue: + if hasattr(self._device, "rssiDeviceValue") and self._device.rssiDeviceValue: attr[ATTR_RSSI_DEVICE] = self._device.rssiDeviceValue - if hasattr(self._device, 'rssiPeerValue') and \ - self._device.rssiPeerValue: + if hasattr(self._device, "rssiPeerValue") and self._device.rssiPeerValue: attr[ATTR_RSSI_PEER] = self._device.rssiPeerValue return attr diff --git a/homeassistant/components/homematicip_cloud/hap.py b/homeassistant/components/homematicip_cloud/hap.py index 8bbbb8f41b6..7418aa94d89 100644 --- a/homeassistant/components/homematicip_cloud/hap.py +++ b/homeassistant/components/homematicip_cloud/hap.py @@ -11,8 +11,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession -from .const import ( - COMPONENTS, HMIPC_AUTHTOKEN, HMIPC_HAPID, HMIPC_NAME, HMIPC_PIN) +from .const import COMPONENTS, HMIPC_AUTHTOKEN, HMIPC_HAPID, HMIPC_NAME, HMIPC_PIN from .errors import HmipcConnectionError _LOGGER = logging.getLogger(__name__) @@ -31,9 +30,7 @@ class HomematicipAuth: """Connect to HomematicIP for registration.""" try: self.auth = await self.get_auth( - self.hass, - self.config.get(HMIPC_HAPID), - self.config.get(HMIPC_PIN) + self.hass, self.config.get(HMIPC_HAPID), self.config.get(HMIPC_PIN) ) return True except HmipcConnectionError: @@ -62,7 +59,7 @@ class HomematicipAuth: await auth.init(hapid) if pin: auth.pin = pin - await auth.connectionRequest('HomeAssistant') + await auth.connectionRequest("HomeAssistant") except HmipConnectionError: return False return auth @@ -89,18 +86,21 @@ class HomematicipHAP: self.hass, self.config_entry.data.get(HMIPC_HAPID), self.config_entry.data.get(HMIPC_AUTHTOKEN), - self.config_entry.data.get(HMIPC_NAME) + self.config_entry.data.get(HMIPC_NAME), ) except HmipcConnectionError: raise ConfigEntryNotReady - _LOGGER.info("Connected to HomematicIP with HAP %s", - self.config_entry.data.get(HMIPC_HAPID)) + _LOGGER.info( + "Connected to HomematicIP with HAP %s", + self.config_entry.data.get(HMIPC_HAPID), + ) for component in COMPONENTS: self.hass.async_create_task( self.hass.config_entries.async_forward_entry_setup( - self.config_entry, component) + self.config_entry, component + ) ) return True @@ -116,8 +116,7 @@ class HomematicipHAP: are set to unavailable. """ if not self.home.connected: - _LOGGER.error( - "HMIP access point has lost connection with the cloud") + _LOGGER.error("HMIP access point has lost connection with the cloud") self._accesspoint_connected = False self.set_all_to_unavailable() elif not self._accesspoint_connected: @@ -141,8 +140,7 @@ class HomematicipHAP: except HmipConnectionError: # Somehow connection could not recover. Will disconnect and # so reconnect loop is taking over. - _LOGGER.error( - "Updating state after HMIP access point reconnect failed") + _LOGGER.error("Updating state after HMIP access point reconnect failed") self.hass.async_create_task(self.home.disable_events()) def set_all_to_unavailable(self): @@ -168,10 +166,12 @@ class HomematicipHAP: tries = 0 await hmip_events except HmipConnectionError: - _LOGGER.error("Error connecting to HomematicIP with HAP %s. " - "Retrying in %d seconds", - self.config_entry.data.get(HMIPC_HAPID), - retry_delay) + _LOGGER.error( + "Error connecting to HomematicIP with HAP %s. " + "Retrying in %d seconds", + self.config_entry.data.get(HMIPC_HAPID), + retry_delay, + ) if self._ws_close_requested: break @@ -179,8 +179,9 @@ class HomematicipHAP: tries += 1 try: - self._retry_task = self.hass.async_create_task(asyncio.sleep( - retry_delay)) + self._retry_task = self.hass.async_create_task( + asyncio.sleep(retry_delay) + ) await self._retry_task except asyncio.CancelledError: break @@ -194,17 +195,19 @@ class HomematicipHAP: _LOGGER.info("Closed connection to HomematicIP cloud server") for component in COMPONENTS: await self.hass.config_entries.async_forward_entry_unload( - self.config_entry, component) + self.config_entry, component + ) return True - async def get_hap(self, hass: HomeAssistant, hapid: str, authtoken: str, - name: str) -> AsyncHome: + async def get_hap( + self, hass: HomeAssistant, hapid: str, authtoken: str, name: str + ) -> AsyncHome: """Create a HomematicIP access point object.""" home = AsyncHome(hass.loop, async_get_clientsession(hass)) home.name = name - home.label = 'Access Point' - home.modelType = 'HmIP-HAP' + home.label = "Access Point" + home.modelType = "HmIP-HAP" home.set_auth_token(authtoken) try: diff --git a/homeassistant/components/homematicip_cloud/light.py b/homeassistant/components/homematicip_cloud/light.py index 7cfbae95a33..c034b19bb3a 100644 --- a/homeassistant/components/homematicip_cloud/light.py +++ b/homeassistant/components/homematicip_cloud/light.py @@ -2,16 +2,25 @@ import logging from homematicip.aio.device import ( - AsyncBrandDimmer, AsyncBrandSwitchMeasuring, - AsyncBrandSwitchNotificationLight, AsyncDimmer, AsyncFullFlushDimmer, - AsyncPluggableDimmer) + AsyncBrandDimmer, + AsyncBrandSwitchMeasuring, + AsyncBrandSwitchNotificationLight, + AsyncDimmer, + AsyncFullFlushDimmer, + AsyncPluggableDimmer, +) from homematicip.aio.home import AsyncHome from homematicip.base.enums import RGBColorState from homematicip.base.functionalChannels import NotificationLightChannel from homeassistant.components.light import ( - ATTR_BRIGHTNESS, ATTR_COLOR_NAME, ATTR_HS_COLOR, SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, Light) + ATTR_BRIGHTNESS, + ATTR_COLOR_NAME, + ATTR_HS_COLOR, + SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, + Light, +) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -19,18 +28,18 @@ from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice _LOGGER = logging.getLogger(__name__) -ATTR_ENERGY_COUNTER = 'energy_counter_kwh' -ATTR_POWER_CONSUMPTION = 'power_consumption' +ATTR_ENERGY_COUNTER = "energy_counter_kwh" +ATTR_POWER_CONSUMPTION = "power_consumption" -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Old way of setting up HomematicIP Cloud lights.""" pass -async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, - async_add_entities) -> None: +async def async_setup_entry( + hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities +) -> None: """Set up the HomematicIP Cloud lights from a config entry.""" home = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]].home devices = [] @@ -39,13 +48,18 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, devices.append(HomematicipLightMeasuring(home, device)) elif isinstance(device, AsyncBrandSwitchNotificationLight): devices.append(HomematicipLight(home, device)) - devices.append(HomematicipNotificationLight( - home, device, device.topLightChannelIndex)) - devices.append(HomematicipNotificationLight( - home, device, device.bottomLightChannelIndex)) - elif isinstance(device, - (AsyncDimmer, AsyncPluggableDimmer, - AsyncBrandDimmer, AsyncFullFlushDimmer)): + devices.append( + HomematicipNotificationLight(home, device, device.topLightChannelIndex) + ) + devices.append( + HomematicipNotificationLight( + home, device, device.bottomLightChannelIndex + ) + ) + elif isinstance( + device, + (AsyncDimmer, AsyncPluggableDimmer, AsyncBrandDimmer, AsyncFullFlushDimmer), + ): devices.append(HomematicipDimmer(home, device)) if devices: @@ -81,8 +95,9 @@ class HomematicipLightMeasuring(HomematicipLight): """Return the state attributes of the generic device.""" attr = super().device_state_attributes if self._device.currentPowerConsumption > 0.05: - attr[ATTR_POWER_CONSUMPTION] = \ - round(self._device.currentPowerConsumption, 2) + attr[ATTR_POWER_CONSUMPTION] = round( + self._device.currentPowerConsumption, 2 + ) attr[ATTR_ENERGY_COUNTER] = round(self._device.energyCounter, 2) return attr @@ -97,14 +112,13 @@ class HomematicipDimmer(HomematicipGenericDevice, Light): @property def is_on(self) -> bool: """Return true if device is on.""" - return self._device.dimLevel is not None and \ - self._device.dimLevel > 0.0 + return self._device.dimLevel is not None and self._device.dimLevel > 0.0 @property def brightness(self) -> int: """Return the brightness of this light between 0..255.""" if self._device.dimLevel: - return int(self._device.dimLevel*255) + return int(self._device.dimLevel * 255) return 0 @property @@ -115,7 +129,7 @@ class HomematicipDimmer(HomematicipGenericDevice, Light): async def async_turn_on(self, **kwargs): """Turn the light on.""" if ATTR_BRIGHTNESS in kwargs: - await self._device.set_dim_level(kwargs[ATTR_BRIGHTNESS]/255.0) + await self._device.set_dim_level(kwargs[ATTR_BRIGHTNESS] / 255.0) else: await self._device.set_dim_level(1) @@ -131,9 +145,9 @@ class HomematicipNotificationLight(HomematicipGenericDevice, Light): """Initialize the dimmer light device.""" self.channel = channel if self.channel == 2: - super().__init__(home, device, 'Top') + super().__init__(home, device, "Top") else: - super().__init__(home, device, 'Bottom') + super().__init__(home, device, "Bottom") self._color_switcher = { RGBColorState.WHITE: [0.0, 0.0], @@ -142,7 +156,7 @@ class HomematicipNotificationLight(HomematicipGenericDevice, Light): RGBColorState.GREEN: [120.0, 100.0], RGBColorState.TURQUOISE: [180.0, 100.0], RGBColorState.BLUE: [240.0, 100.0], - RGBColorState.PURPLE: [300.0, 100.0] + RGBColorState.PURPLE: [300.0, 100.0], } @property @@ -152,8 +166,10 @@ class HomematicipNotificationLight(HomematicipGenericDevice, Light): @property def is_on(self) -> bool: """Return true if device is on.""" - return self._func_channel.dimLevel is not None and \ - self._func_channel.dimLevel > 0.0 + return ( + self._func_channel.dimLevel is not None + and self._func_channel.dimLevel > 0.0 + ) @property def brightness(self) -> int: @@ -179,7 +195,7 @@ class HomematicipNotificationLight(HomematicipGenericDevice, Light): @property def name(self) -> str: """Return the name of the generic device.""" - return "{} {}".format(super().name, 'Notification') + return "{} {}".format(super().name, "Notification") @property def supported_features(self) -> int: @@ -189,9 +205,7 @@ class HomematicipNotificationLight(HomematicipGenericDevice, Light): @property def unique_id(self) -> str: """Return a unique ID.""" - return "{}_{}_{}".format(self.__class__.__name__, - self.post, - self._device.id) + return "{}_{}_{}".format(self.__class__.__name__, self.post, self._device.id) async def async_turn_on(self, **kwargs): """Turn the light on.""" @@ -212,17 +226,12 @@ class HomematicipNotificationLight(HomematicipGenericDevice, Light): brightness = max(10, brightness) dim_level = brightness / 255.0 - await self._device.set_rgb_dim_level( - self.channel, - simple_rgb_color, - dim_level) + await self._device.set_rgb_dim_level(self.channel, simple_rgb_color, dim_level) async def async_turn_off(self, **kwargs): """Turn the light off.""" simple_rgb_color = self._func_channel.simpleRGBColorState - await self._device.set_rgb_dim_level( - self.channel, - simple_rgb_color, 0.0) + await self._device.set_rgb_dim_level(self.channel, simple_rgb_color, 0.0) def _convert_color(color) -> RGBColorState: diff --git a/homeassistant/components/homematicip_cloud/sensor.py b/homeassistant/components/homematicip_cloud/sensor.py index b3e23bde2be..b3cbf4627d4 100644 --- a/homeassistant/components/homematicip_cloud/sensor.py +++ b/homeassistant/components/homematicip_cloud/sensor.py @@ -2,74 +2,102 @@ import logging from homematicip.aio.device import ( - AsyncBrandSwitchMeasuring, AsyncFullFlushSwitchMeasuring, - AsyncHeatingThermostat, AsyncHeatingThermostatCompact, AsyncLightSensor, - AsyncMotionDetectorIndoor, AsyncMotionDetectorOutdoor, - AsyncMotionDetectorPushButton, AsyncPlugableSwitchMeasuring, - AsyncPresenceDetectorIndoor, AsyncTemperatureHumiditySensorDisplay, + AsyncBrandSwitchMeasuring, + AsyncFullFlushSwitchMeasuring, + AsyncHeatingThermostat, + AsyncHeatingThermostatCompact, + AsyncLightSensor, + AsyncMotionDetectorIndoor, + AsyncMotionDetectorOutdoor, + AsyncMotionDetectorPushButton, + AsyncPlugableSwitchMeasuring, + AsyncPresenceDetectorIndoor, + AsyncTemperatureHumiditySensorDisplay, AsyncTemperatureHumiditySensorOutdoor, - AsyncTemperatureHumiditySensorWithoutDisplay, AsyncWeatherSensor, - AsyncWeatherSensorPlus, AsyncWeatherSensorPro) + AsyncTemperatureHumiditySensorWithoutDisplay, + AsyncWeatherSensor, + AsyncWeatherSensorPlus, + AsyncWeatherSensorPro, +) from homematicip.aio.home import AsyncHome from homematicip.base.enums import ValveState from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( - DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_POWER, - DEVICE_CLASS_TEMPERATURE, POWER_WATT, TEMP_CELSIUS) + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_ILLUMINANCE, + DEVICE_CLASS_POWER, + DEVICE_CLASS_TEMPERATURE, + POWER_WATT, + TEMP_CELSIUS, +) from homeassistant.core import HomeAssistant from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice _LOGGER = logging.getLogger(__name__) -ATTR_TEMPERATURE_OFFSET = 'temperature_offset' -ATTR_WIND_DIRECTION = 'wind_direction' -ATTR_WIND_DIRECTION_VARIATION = 'wind_direction_variation_in_degree' +ATTR_TEMPERATURE_OFFSET = "temperature_offset" +ATTR_WIND_DIRECTION = "wind_direction" +ATTR_WIND_DIRECTION_VARIATION = "wind_direction_variation_in_degree" -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the HomematicIP Cloud sensors devices.""" pass -async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, - async_add_entities) -> None: +async def async_setup_entry( + hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities +) -> None: """Set up the HomematicIP Cloud sensors from a config entry.""" home = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]].home devices = [HomematicipAccesspointStatus(home)] for device in home.devices: - if isinstance(device, (AsyncHeatingThermostat, - AsyncHeatingThermostatCompact)): + if isinstance(device, (AsyncHeatingThermostat, AsyncHeatingThermostatCompact)): devices.append(HomematicipHeatingThermostat(home, device)) devices.append(HomematicipTemperatureSensor(home, device)) - if isinstance(device, (AsyncTemperatureHumiditySensorDisplay, - AsyncTemperatureHumiditySensorWithoutDisplay, - AsyncTemperatureHumiditySensorOutdoor, - AsyncWeatherSensor, - AsyncWeatherSensorPlus, - AsyncWeatherSensorPro)): + if isinstance( + device, + ( + AsyncTemperatureHumiditySensorDisplay, + AsyncTemperatureHumiditySensorWithoutDisplay, + AsyncTemperatureHumiditySensorOutdoor, + AsyncWeatherSensor, + AsyncWeatherSensorPlus, + AsyncWeatherSensorPro, + ), + ): devices.append(HomematicipTemperatureSensor(home, device)) devices.append(HomematicipHumiditySensor(home, device)) - if isinstance(device, (AsyncLightSensor, AsyncMotionDetectorIndoor, - AsyncMotionDetectorOutdoor, - AsyncMotionDetectorPushButton, - AsyncPresenceDetectorIndoor, - AsyncWeatherSensor, - AsyncWeatherSensorPlus, - AsyncWeatherSensorPro)): + if isinstance( + device, + ( + AsyncLightSensor, + AsyncMotionDetectorIndoor, + AsyncMotionDetectorOutdoor, + AsyncMotionDetectorPushButton, + AsyncPresenceDetectorIndoor, + AsyncWeatherSensor, + AsyncWeatherSensorPlus, + AsyncWeatherSensorPro, + ), + ): devices.append(HomematicipIlluminanceSensor(home, device)) - if isinstance(device, (AsyncPlugableSwitchMeasuring, - AsyncBrandSwitchMeasuring, - AsyncFullFlushSwitchMeasuring)): + if isinstance( + device, + ( + AsyncPlugableSwitchMeasuring, + AsyncBrandSwitchMeasuring, + AsyncFullFlushSwitchMeasuring, + ), + ): devices.append(HomematicipPowerSensor(home, device)) - if isinstance(device, (AsyncWeatherSensor, - AsyncWeatherSensorPlus, - AsyncWeatherSensorPro)): + if isinstance( + device, (AsyncWeatherSensor, AsyncWeatherSensorPlus, AsyncWeatherSensorPro) + ): devices.append(HomematicipWindspeedSensor(home, device)) - if isinstance(device, (AsyncWeatherSensorPlus, - AsyncWeatherSensorPro)): + if isinstance(device, (AsyncWeatherSensorPlus, AsyncWeatherSensorPro)): devices.append(HomematicipTodayRainSensor(home, device)) if devices: @@ -88,7 +116,7 @@ class HomematicipAccesspointStatus(HomematicipGenericDevice): """Return device specific attributes.""" # Adds a sensor to the existing HAP device return { - 'identifiers': { + "identifiers": { # Serial numbers of Homematic IP device (HMIPC_DOMAIN, self._device.id) } @@ -97,7 +125,7 @@ class HomematicipAccesspointStatus(HomematicipGenericDevice): @property def icon(self) -> str: """Return the icon of the access point device.""" - return 'mdi:access-point-network' + return "mdi:access-point-network" @property def state(self) -> float: @@ -112,7 +140,7 @@ class HomematicipAccesspointStatus(HomematicipGenericDevice): @property def unit_of_measurement(self) -> str: """Return the unit this state is expressed in.""" - return '%' + return "%" class HomematicipHeatingThermostat(HomematicipGenericDevice): @@ -120,7 +148,7 @@ class HomematicipHeatingThermostat(HomematicipGenericDevice): def __init__(self, home: AsyncHome, device) -> None: """Initialize heating thermostat device.""" - super().__init__(home, device, 'Heating') + super().__init__(home, device, "Heating") @property def icon(self) -> str: @@ -128,20 +156,20 @@ class HomematicipHeatingThermostat(HomematicipGenericDevice): if super().icon: return super().icon if self._device.valveState != ValveState.ADAPTION_DONE: - return 'mdi:alert' - return 'mdi:radiator' + return "mdi:alert" + return "mdi:radiator" @property def state(self) -> int: """Return the state of the radiator valve.""" if self._device.valveState != ValveState.ADAPTION_DONE: return self._device.valveState - return round(self._device.valvePosition*100) + return round(self._device.valvePosition * 100) @property def unit_of_measurement(self) -> str: """Return the unit this state is expressed in.""" - return '%' + return "%" class HomematicipHumiditySensor(HomematicipGenericDevice): @@ -149,7 +177,7 @@ class HomematicipHumiditySensor(HomematicipGenericDevice): def __init__(self, home: AsyncHome, device) -> None: """Initialize the thermometer device.""" - super().__init__(home, device, 'Humidity') + super().__init__(home, device, "Humidity") @property def device_class(self) -> str: @@ -164,7 +192,7 @@ class HomematicipHumiditySensor(HomematicipGenericDevice): @property def unit_of_measurement(self) -> str: """Return the unit this state is expressed in.""" - return '%' + return "%" class HomematicipTemperatureSensor(HomematicipGenericDevice): @@ -172,7 +200,7 @@ class HomematicipTemperatureSensor(HomematicipGenericDevice): def __init__(self, home: AsyncHome, device) -> None: """Initialize the thermometer device.""" - super().__init__(home, device, 'Temperature') + super().__init__(home, device, "Temperature") @property def device_class(self) -> str: @@ -182,7 +210,7 @@ class HomematicipTemperatureSensor(HomematicipGenericDevice): @property def state(self) -> float: """Return the state.""" - if hasattr(self._device, 'valveActualTemperature'): + if hasattr(self._device, "valveActualTemperature"): return self._device.valveActualTemperature return self._device.actualTemperature @@ -196,8 +224,10 @@ class HomematicipTemperatureSensor(HomematicipGenericDevice): def device_state_attributes(self): """Return the state attributes of the windspeed sensor.""" attr = super().device_state_attributes - if hasattr(self._device, 'temperatureOffset') and \ - self._device.temperatureOffset: + if ( + hasattr(self._device, "temperatureOffset") + and self._device.temperatureOffset + ): attr[ATTR_TEMPERATURE_OFFSET] = self._device.temperatureOffset return attr @@ -207,7 +237,7 @@ class HomematicipIlluminanceSensor(HomematicipGenericDevice): def __init__(self, home: AsyncHome, device) -> None: """Initialize the device.""" - super().__init__(home, device, 'Illuminance') + super().__init__(home, device, "Illuminance") @property def device_class(self) -> str: @@ -217,7 +247,7 @@ class HomematicipIlluminanceSensor(HomematicipGenericDevice): @property def state(self) -> float: """Return the state.""" - if hasattr(self._device, 'averageIllumination'): + if hasattr(self._device, "averageIllumination"): return self._device.averageIllumination return self._device.illumination @@ -225,7 +255,7 @@ class HomematicipIlluminanceSensor(HomematicipGenericDevice): @property def unit_of_measurement(self) -> str: """Return the unit this state is expressed in.""" - return 'lx' + return "lx" class HomematicipPowerSensor(HomematicipGenericDevice): @@ -233,7 +263,7 @@ class HomematicipPowerSensor(HomematicipGenericDevice): def __init__(self, home: AsyncHome, device) -> None: """Initialize the device.""" - super().__init__(home, device, 'Power') + super().__init__(home, device, "Power") @property def device_class(self) -> str: @@ -256,7 +286,7 @@ class HomematicipWindspeedSensor(HomematicipGenericDevice): def __init__(self, home: AsyncHome, device) -> None: """Initialize the device.""" - super().__init__(home, device, 'Windspeed') + super().__init__(home, device, "Windspeed") @property def state(self) -> float: @@ -266,20 +296,19 @@ class HomematicipWindspeedSensor(HomematicipGenericDevice): @property def unit_of_measurement(self) -> str: """Return the unit this state is expressed in.""" - return 'km/h' + return "km/h" @property def device_state_attributes(self): """Return the state attributes of the wind speed sensor.""" attr = super().device_state_attributes - if hasattr(self._device, 'windDirection') and \ - self._device.windDirection: - attr[ATTR_WIND_DIRECTION] = \ - _get_wind_direction(self._device.windDirection) - if hasattr(self._device, 'windDirectionVariation') and \ - self._device.windDirectionVariation: - attr[ATTR_WIND_DIRECTION_VARIATION] = \ - self._device.windDirectionVariation + if hasattr(self._device, "windDirection") and self._device.windDirection: + attr[ATTR_WIND_DIRECTION] = _get_wind_direction(self._device.windDirection) + if ( + hasattr(self._device, "windDirectionVariation") + and self._device.windDirectionVariation + ): + attr[ATTR_WIND_DIRECTION_VARIATION] = self._device.windDirectionVariation return attr @@ -288,7 +317,7 @@ class HomematicipTodayRainSensor(HomematicipGenericDevice): def __init__(self, home: AsyncHome, device) -> None: """Initialize the device.""" - super().__init__(home, device, 'Today Rain') + super().__init__(home, device, "Today Rain") @property def state(self) -> float: @@ -298,39 +327,39 @@ class HomematicipTodayRainSensor(HomematicipGenericDevice): @property def unit_of_measurement(self) -> str: """Return the unit this state is expressed in.""" - return 'mm' + return "mm" def _get_wind_direction(wind_direction_degree: float) -> str: """Convert wind direction degree to named direction.""" if 11.25 <= wind_direction_degree < 33.75: - return 'NNE' + return "NNE" if 33.75 <= wind_direction_degree < 56.25: - return 'NE' + return "NE" if 56.25 <= wind_direction_degree < 78.75: - return 'ENE' + return "ENE" if 78.75 <= wind_direction_degree < 101.25: - return 'E' + return "E" if 101.25 <= wind_direction_degree < 123.75: - return 'ESE' + return "ESE" if 123.75 <= wind_direction_degree < 146.25: - return 'SE' + return "SE" if 146.25 <= wind_direction_degree < 168.75: - return 'SSE' + return "SSE" if 168.75 <= wind_direction_degree < 191.25: - return 'S' + return "S" if 191.25 <= wind_direction_degree < 213.75: - return 'SSW' + return "SSW" if 213.75 <= wind_direction_degree < 236.25: - return 'SW' + return "SW" if 236.25 <= wind_direction_degree < 258.75: - return 'WSW' + return "WSW" if 258.75 <= wind_direction_degree < 281.25: - return 'W' + return "W" if 281.25 <= wind_direction_degree < 303.75: - return 'WNW' + return "WNW" if 303.75 <= wind_direction_degree < 326.25: - return 'NW' + return "NW" if 326.25 <= wind_direction_degree < 348.75: - return 'NNW' - return 'N' + return "NNW" + return "N" diff --git a/homeassistant/components/homematicip_cloud/switch.py b/homeassistant/components/homematicip_cloud/switch.py index 777fc0c35fd..a9535736d0f 100644 --- a/homeassistant/components/homematicip_cloud/switch.py +++ b/homeassistant/components/homematicip_cloud/switch.py @@ -2,10 +2,15 @@ import logging from homematicip.aio.device import ( - AsyncBrandSwitchMeasuring, AsyncFullFlushSwitchMeasuring, AsyncMultiIOBox, - AsyncOpenCollector8Module, AsyncPlugableSwitch, - AsyncPlugableSwitchMeasuring, AsyncPrintedCircuitBoardSwitch2, - AsyncPrintedCircuitBoardSwitchBattery) + AsyncBrandSwitchMeasuring, + AsyncFullFlushSwitchMeasuring, + AsyncMultiIOBox, + AsyncOpenCollector8Module, + AsyncPlugableSwitch, + AsyncPlugableSwitchMeasuring, + AsyncPrintedCircuitBoardSwitch2, + AsyncPrintedCircuitBoardSwitchBattery, +) from homematicip.aio.group import AsyncSwitchingGroup from homematicip.aio.home import AsyncHome @@ -19,14 +24,14 @@ from .device import ATTR_GROUP_MEMBER_UNREACHABLE _LOGGER = logging.getLogger(__name__) -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the HomematicIP Cloud switch devices.""" pass -async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, - async_add_entities) -> None: +async def async_setup_entry( + hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities +) -> None: """Set up the HomematicIP switch from a config entry.""" home = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]].home devices = [] @@ -36,11 +41,13 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, # This device is implemented in the light platform and will # not be added in the switch platform pass - elif isinstance(device, (AsyncPlugableSwitchMeasuring, - AsyncFullFlushSwitchMeasuring)): + elif isinstance( + device, (AsyncPlugableSwitchMeasuring, AsyncFullFlushSwitchMeasuring) + ): devices.append(HomematicipSwitchMeasuring(home, device)) - elif isinstance(device, (AsyncPlugableSwitch, - AsyncPrintedCircuitBoardSwitchBattery)): + elif isinstance( + device, (AsyncPlugableSwitch, AsyncPrintedCircuitBoardSwitchBattery) + ): devices.append(HomematicipSwitch(home, device)) elif isinstance(device, AsyncOpenCollector8Module): for channel in range(1, 9): @@ -54,8 +61,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, for group in home.groups: if isinstance(group, AsyncSwitchingGroup): - devices.append( - HomematicipGroupSwitch(home, group)) + devices.append(HomematicipGroupSwitch(home, group)) if devices: async_add_entities(devices) @@ -85,9 +91,9 @@ class HomematicipSwitch(HomematicipGenericDevice, SwitchDevice): class HomematicipGroupSwitch(HomematicipGenericDevice, SwitchDevice): """representation of a HomematicIP switching group.""" - def __init__(self, home: AsyncHome, device, post: str = 'Group') -> None: + def __init__(self, home: AsyncHome, device, post: str = "Group") -> None: """Initialize switching group.""" - device.modelType = 'HmIP-{}'.format(post) + device.modelType = "HmIP-{}".format(post) super().__init__(home, device, post) @property @@ -143,13 +149,12 @@ class HomematicipMultiSwitch(HomematicipGenericDevice, SwitchDevice): def __init__(self, home: AsyncHome, device, channel: int): """Initialize the multi switch device.""" self.channel = channel - super().__init__(home, device, 'Channel{}'.format(channel)) + super().__init__(home, device, "Channel{}".format(channel)) @property def unique_id(self) -> str: """Return a unique ID.""" - return "{}_{}_{}".format(self.__class__.__name__, - self.post, self._device.id) + return "{}_{}_{}".format(self.__class__.__name__, self.post, self._device.id) @property def is_on(self) -> bool: diff --git a/homeassistant/components/homematicip_cloud/weather.py b/homeassistant/components/homematicip_cloud/weather.py index b97948b2d9f..463e1bfb741 100644 --- a/homeassistant/components/homematicip_cloud/weather.py +++ b/homeassistant/components/homematicip_cloud/weather.py @@ -1,9 +1,11 @@ - """Support for HomematicIP Cloud weather devices.""" import logging from homematicip.aio.device import ( - AsyncWeatherSensor, AsyncWeatherSensorPlus, AsyncWeatherSensorPro) + AsyncWeatherSensor, + AsyncWeatherSensorPlus, + AsyncWeatherSensorPro, +) from homematicip.aio.home import AsyncHome from homeassistant.components.weather import WeatherEntity @@ -16,14 +18,14 @@ from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice _LOGGER = logging.getLogger(__name__) -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the HomematicIP Cloud weather sensor.""" pass -async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, - async_add_entities) -> None: +async def async_setup_entry( + hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities +) -> None: """Set up the HomematicIP weather sensor from a config entry.""" home = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]].home devices = [] @@ -78,12 +80,12 @@ class HomematicipWeatherSensor(HomematicipGenericDevice, WeatherEntity): def condition(self) -> str: """Return the current condition.""" if hasattr(self._device, "raining") and self._device.raining: - return 'rainy' + return "rainy" if self._device.storm: - return 'windy' + return "windy" if self._device.sunshine: - return 'sunny' - return '' + return "sunny" + return "" class HomematicipWeatherSensorPro(HomematicipWeatherSensor): diff --git a/homeassistant/components/homeworks/__init__.py b/homeassistant/components/homeworks/__init__.py index b722a5a4a2d..dcc2ce5dde6 100644 --- a/homeassistant/components/homeworks/__init__.py +++ b/homeassistant/components/homeworks/__init__.py @@ -4,52 +4,63 @@ import logging import voluptuous as vol from homeassistant.const import ( - CONF_HOST, CONF_ID, CONF_NAME, CONF_PORT, EVENT_HOMEASSISTANT_STOP) + CONF_HOST, + CONF_ID, + CONF_NAME, + CONF_PORT, + EVENT_HOMEASSISTANT_STOP, +) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.discovery import load_platform -from homeassistant.helpers.dispatcher import ( - async_dispatcher_connect, dispatcher_send) +from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send from homeassistant.util import slugify _LOGGER = logging.getLogger(__name__) -DOMAIN = 'homeworks' +DOMAIN = "homeworks" -HOMEWORKS_CONTROLLER = 'homeworks' -ENTITY_SIGNAL = 'homeworks_entity_{}' -EVENT_BUTTON_PRESS = 'homeworks_button_press' -EVENT_BUTTON_RELEASE = 'homeworks_button_release' +HOMEWORKS_CONTROLLER = "homeworks" +ENTITY_SIGNAL = "homeworks_entity_{}" +EVENT_BUTTON_PRESS = "homeworks_button_press" +EVENT_BUTTON_RELEASE = "homeworks_button_release" -CONF_DIMMERS = 'dimmers' -CONF_KEYPADS = 'keypads' -CONF_ADDR = 'addr' -CONF_RATE = 'rate' +CONF_DIMMERS = "dimmers" +CONF_KEYPADS = "keypads" +CONF_ADDR = "addr" +CONF_RATE = "rate" -FADE_RATE = 1. +FADE_RATE = 1.0 CV_FADE_RATE = vol.All(vol.Coerce(float), vol.Range(min=0, max=20)) -DIMMER_SCHEMA = vol.Schema({ - vol.Required(CONF_ADDR): cv.string, - vol.Required(CONF_NAME): cv.string, - vol.Optional(CONF_RATE, default=FADE_RATE): CV_FADE_RATE, -}) +DIMMER_SCHEMA = vol.Schema( + { + vol.Required(CONF_ADDR): cv.string, + vol.Required(CONF_NAME): cv.string, + vol.Optional(CONF_RATE, default=FADE_RATE): CV_FADE_RATE, + } +) -KEYPAD_SCHEMA = vol.Schema({ - vol.Required(CONF_ADDR): cv.string, - vol.Required(CONF_NAME): cv.string, -}) +KEYPAD_SCHEMA = vol.Schema( + {vol.Required(CONF_ADDR): cv.string, vol.Required(CONF_NAME): cv.string} +) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_PORT): cv.port, - vol.Required(CONF_DIMMERS): vol.All(cv.ensure_list, [DIMMER_SCHEMA]), - vol.Optional(CONF_KEYPADS, default=[]): - vol.All(cv.ensure_list, [KEYPAD_SCHEMA]), - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PORT): cv.port, + vol.Required(CONF_DIMMERS): vol.All(cv.ensure_list, [DIMMER_SCHEMA]), + vol.Optional(CONF_KEYPADS, default=[]): vol.All( + cv.ensure_list, [KEYPAD_SCHEMA] + ), + } + ) + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass, base_config): @@ -58,7 +69,7 @@ def setup(hass, base_config): def hw_callback(msg_type, values): """Dispatch state changes.""" - _LOGGER.debug('callback: %s, %s', msg_type, values) + _LOGGER.debug("callback: %s, %s", msg_type, values) addr = values[0] signal = ENTITY_SIGNAL.format(addr) dispatcher_send(hass, signal, msg_type, values) @@ -73,7 +84,7 @@ def setup(hass, base_config): hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, cleanup) dimmers = config[CONF_DIMMERS] - load_platform(hass, 'light', DOMAIN, {CONF_DIMMERS: dimmers}, base_config) + load_platform(hass, "light", DOMAIN, {CONF_DIMMERS: dimmers}, base_config) for key_config in config[CONF_KEYPADS]: addr = key_config[CONF_ADDR] @@ -83,7 +94,7 @@ def setup(hass, base_config): return True -class HomeworksDevice(): +class HomeworksDevice: """Base class of a Homeworks device.""" def __init__(self, controller, addr, name): @@ -95,7 +106,7 @@ class HomeworksDevice(): @property def unique_id(self): """Return a unique identifier.""" - return 'homeworks.{}'.format(self._addr) + return "homeworks.{}".format(self._addr) @property def name(self): @@ -122,19 +133,18 @@ class HomeworksKeypadEvent: self._name = name self._id = slugify(self._name) signal = ENTITY_SIGNAL.format(self._addr) - async_dispatcher_connect( - self._hass, signal, self._update_callback) + async_dispatcher_connect(self._hass, signal, self._update_callback) @callback def _update_callback(self, msg_type, values): """Fire events if button is pressed or released.""" - from pyhomeworks.pyhomeworks import ( - HW_BUTTON_PRESSED, HW_BUTTON_RELEASED) + from pyhomeworks.pyhomeworks import HW_BUTTON_PRESSED, HW_BUTTON_RELEASED + if msg_type == HW_BUTTON_PRESSED: event = EVENT_BUTTON_PRESS elif msg_type == HW_BUTTON_RELEASED: event = EVENT_BUTTON_RELEASE else: return - data = {CONF_ID: self._id, CONF_NAME: self._name, 'button': values[1]} + data = {CONF_ID: self._id, CONF_NAME: self._name, "button": values[1]} self._hass.bus.async_fire(event, data) diff --git a/homeassistant/components/homeworks/light.py b/homeassistant/components/homeworks/light.py index 710be7c0077..d1854b4dbf3 100644 --- a/homeassistant/components/homeworks/light.py +++ b/homeassistant/components/homeworks/light.py @@ -1,15 +1,19 @@ """Support for Lutron Homeworks lights.""" import logging -from homeassistant.components.light import ( - ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light) +from homeassistant.components.light import ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light from homeassistant.const import CONF_NAME from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from . import ( - CONF_ADDR, CONF_DIMMERS, CONF_RATE, ENTITY_SIGNAL, HOMEWORKS_CONTROLLER, - HomeworksDevice) + CONF_ADDR, + CONF_DIMMERS, + CONF_RATE, + ENTITY_SIGNAL, + HOMEWORKS_CONTROLLER, + HomeworksDevice, +) _LOGGER = logging.getLogger(__name__) @@ -22,8 +26,9 @@ def setup_platform(hass, config, add_entities, discover_info=None): controller = hass.data[HOMEWORKS_CONTROLLER] devs = [] for dimmer in discover_info[CONF_DIMMERS]: - dev = HomeworksLight(controller, dimmer[CONF_ADDR], - dimmer[CONF_NAME], dimmer[CONF_RATE]) + dev = HomeworksLight( + controller, dimmer[CONF_ADDR], dimmer[CONF_NAME], dimmer[CONF_RATE] + ) devs.append(dev) add_entities(devs, True) @@ -41,9 +46,8 @@ class HomeworksLight(HomeworksDevice, Light): async def async_added_to_hass(self): """Call when entity is added to hass.""" signal = ENTITY_SIGNAL.format(self._addr) - _LOGGER.debug('connecting %s', signal) - async_dispatcher_connect( - self.hass, signal, self._update_callback) + _LOGGER.debug("connecting %s", signal) + async_dispatcher_connect(self.hass, signal, self._update_callback) self._controller.request_dimmer_level(self._addr) @property @@ -73,13 +77,13 @@ class HomeworksLight(HomeworksDevice, Light): def _set_brightness(self, level): """Send the brightness level to the device.""" self._controller.fade_dim( - float((level*100.)/255.), self._rate, - 0, self._addr) + float((level * 100.0) / 255.0), self._rate, 0, self._addr + ) @property def device_state_attributes(self): """Supported attributes.""" - return {'homeworks_address': self._addr} + return {"homeworks_address": self._addr} @property def is_on(self): @@ -92,7 +96,7 @@ class HomeworksLight(HomeworksDevice, Light): from pyhomeworks.pyhomeworks import HW_LIGHT_CHANGED if msg_type == HW_LIGHT_CHANGED: - self._level = int((values[1] * 255.)/100.) + self._level = int((values[1] * 255.0) / 100.0) if self._level != 0: self._prev_level = self._level self.async_schedule_update_ha_state() diff --git a/homeassistant/components/honeywell/climate.py b/homeassistant/components/honeywell/climate.py index 52a554bc731..fc6144cfe46 100644 --- a/homeassistant/components/honeywell/climate.py +++ b/homeassistant/components/honeywell/climate.py @@ -9,71 +9,93 @@ import somecomfort from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA from homeassistant.components.climate.const import ( - ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, - FAN_AUTO, FAN_DIFFUSE, FAN_ON, - SUPPORT_AUX_HEAT, SUPPORT_FAN_MODE, SUPPORT_PRESET_MODE, - SUPPORT_TARGET_HUMIDITY, SUPPORT_TARGET_TEMPERATURE, + ATTR_TARGET_TEMP_HIGH, + ATTR_TARGET_TEMP_LOW, + FAN_AUTO, + FAN_DIFFUSE, + FAN_ON, + SUPPORT_AUX_HEAT, + SUPPORT_FAN_MODE, + SUPPORT_PRESET_MODE, + SUPPORT_TARGET_HUMIDITY, + SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_RANGE, - CURRENT_HVAC_COOL, CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, CURRENT_HVAC_FAN, - HVAC_MODE_OFF, HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_HEAT_COOL, - PRESET_AWAY, PRESET_NONE, + CURRENT_HVAC_COOL, + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, + CURRENT_HVAC_FAN, + HVAC_MODE_OFF, + HVAC_MODE_HEAT, + HVAC_MODE_COOL, + HVAC_MODE_HEAT_COOL, + PRESET_AWAY, + PRESET_NONE, ) from homeassistant.const import ( - CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS, TEMP_FAHRENHEIT, - ATTR_TEMPERATURE, CONF_REGION) + CONF_PASSWORD, + CONF_USERNAME, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, + ATTR_TEMPERATURE, + CONF_REGION, +) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -ATTR_FAN_ACTION = 'fan_action' +ATTR_FAN_ACTION = "fan_action" -CONF_COOL_AWAY_TEMPERATURE = 'away_cool_temperature' -CONF_HEAT_AWAY_TEMPERATURE = 'away_heat_temperature' +CONF_COOL_AWAY_TEMPERATURE = "away_cool_temperature" +CONF_HEAT_AWAY_TEMPERATURE = "away_heat_temperature" DEFAULT_COOL_AWAY_TEMPERATURE = 88 DEFAULT_HEAT_AWAY_TEMPERATURE = 61 -DEFAULT_REGION = 'eu' -REGIONS = ['eu', 'us'] +DEFAULT_REGION = "eu" +REGIONS = ["eu", "us"] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_COOL_AWAY_TEMPERATURE, - default=DEFAULT_COOL_AWAY_TEMPERATURE): vol.Coerce(int), - vol.Optional(CONF_HEAT_AWAY_TEMPERATURE, - default=DEFAULT_HEAT_AWAY_TEMPERATURE): vol.Coerce(int), - vol.Optional(CONF_REGION, default=DEFAULT_REGION): vol.In(REGIONS), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional( + CONF_COOL_AWAY_TEMPERATURE, default=DEFAULT_COOL_AWAY_TEMPERATURE + ): vol.Coerce(int), + vol.Optional( + CONF_HEAT_AWAY_TEMPERATURE, default=DEFAULT_HEAT_AWAY_TEMPERATURE + ): vol.Coerce(int), + vol.Optional(CONF_REGION, default=DEFAULT_REGION): vol.In(REGIONS), + } +) HVAC_MODE_TO_HW_MODE = { - 'SwitchOffAllowed': {HVAC_MODE_OFF: 'off'}, - 'SwitchAutoAllowed': {HVAC_MODE_HEAT_COOL: 'auto'}, - 'SwitchCoolAllowed': {HVAC_MODE_COOL: 'cool'}, - 'SwitchHeatAllowed': {HVAC_MODE_HEAT: 'heat'}, + "SwitchOffAllowed": {HVAC_MODE_OFF: "off"}, + "SwitchAutoAllowed": {HVAC_MODE_HEAT_COOL: "auto"}, + "SwitchCoolAllowed": {HVAC_MODE_COOL: "cool"}, + "SwitchHeatAllowed": {HVAC_MODE_HEAT: "heat"}, } HW_MODE_TO_HVAC_MODE = { - 'off': HVAC_MODE_OFF, - 'emheat': HVAC_MODE_HEAT, - 'heat': HVAC_MODE_HEAT, - 'cool': HVAC_MODE_COOL, - 'auto': HVAC_MODE_HEAT_COOL, + "off": HVAC_MODE_OFF, + "emheat": HVAC_MODE_HEAT, + "heat": HVAC_MODE_HEAT, + "cool": HVAC_MODE_COOL, + "auto": HVAC_MODE_HEAT_COOL, } HW_MODE_TO_HA_HVAC_ACTION = { - 'off': CURRENT_HVAC_IDLE, - 'fan': CURRENT_HVAC_FAN, - 'heat': CURRENT_HVAC_HEAT, - 'cool': CURRENT_HVAC_COOL, + "off": CURRENT_HVAC_IDLE, + "fan": CURRENT_HVAC_FAN, + "heat": CURRENT_HVAC_HEAT, + "cool": CURRENT_HVAC_COOL, } FAN_MODE_TO_HW = { - 'fanModeOnAllowed': {FAN_ON: 'on'}, - 'fanModeAutoAllowed': {FAN_AUTO: 'auto'}, - 'fanModeCirculateAllowed': {FAN_DIFFUSE: 'circulate'}, + "fanModeOnAllowed": {FAN_ON: "on"}, + "fanModeAutoAllowed": {FAN_AUTO: "auto"}, + "fanModeCirculateAllowed": {FAN_DIFFUSE: "circulate"}, } HW_FAN_MODE_TO_HA = { - 'on': FAN_ON, - 'auto': FAN_AUTO, - 'circulate': FAN_DIFFUSE, - 'follow schedule': FAN_AUTO, + "on": FAN_ON, + "auto": FAN_AUTO, + "circulate": FAN_DIFFUSE, + "follow schedule": FAN_AUTO, } @@ -82,42 +104,53 @@ def setup_platform(hass, config, add_entities, discovery_info=None): username = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) - if config.get(CONF_REGION) == 'us': + if config.get(CONF_REGION) == "us": try: client = somecomfort.SomeComfort(username, password) except somecomfort.AuthError: _LOGGER.error("Failed to login to honeywell account %s", username) return except somecomfort.SomeComfortError: - _LOGGER.error("Failed to initialize the Honeywell client: " - "Check your configuration (username, password), " - "or maybe you have exceeded the API rate limit?") + _LOGGER.error( + "Failed to initialize the Honeywell client: " + "Check your configuration (username, password), " + "or maybe you have exceeded the API rate limit?" + ) return - dev_id = config.get('thermostat') - loc_id = config.get('location') + dev_id = config.get("thermostat") + loc_id = config.get("location") cool_away_temp = config.get(CONF_COOL_AWAY_TEMPERATURE) heat_away_temp = config.get(CONF_HEAT_AWAY_TEMPERATURE) - add_entities([HoneywellUSThermostat(client, device, cool_away_temp, - heat_away_temp, username, password) - for location in client.locations_by_id.values() - for device in location.devices_by_id.values() - if ((not loc_id or location.locationid == loc_id) and - (not dev_id or device.deviceid == dev_id))]) + add_entities( + [ + HoneywellUSThermostat( + client, device, cool_away_temp, heat_away_temp, username, password + ) + for location in client.locations_by_id.values() + for device in location.devices_by_id.values() + if ( + (not loc_id or location.locationid == loc_id) + and (not dev_id or device.deviceid == dev_id) + ) + ] + ) return _LOGGER.warning( "The honeywell component has been deprecated for EU (i.e. non-US) " "systems. For EU-based systems, use the evohome component, " - "see: https://home-assistant.io/components/evohome") + "see: https://home-assistant.io/components/evohome" + ) class HoneywellUSThermostat(ClimateDevice): """Representation of a Honeywell US Thermostat.""" - def __init__(self, client, device, cool_away_temp, - heat_away_temp, username, password): + def __init__( + self, client, device, cool_away_temp, heat_away_temp, username, password + ): """Initialize the thermostat.""" self._client = client self._device = device @@ -127,30 +160,33 @@ class HoneywellUSThermostat(ClimateDevice): self._username = username self._password = password - _LOGGER.debug("latestData = %s ", device._data) # noqa; pylint: disable=protected-access + _LOGGER.debug( + "latestData = %s ", device._data + ) # noqa; pylint: disable=protected-access # not all honeywell HVACs support all modes - mappings = [v for k, v in HVAC_MODE_TO_HW_MODE.items() - if device.raw_ui_data[k]] + mappings = [v for k, v in HVAC_MODE_TO_HW_MODE.items() if device.raw_ui_data[k]] self._hvac_mode_map = {k: v for d in mappings for k, v in d.items()} - self._supported_features = \ - SUPPORT_PRESET_MODE | \ - SUPPORT_TARGET_TEMPERATURE | \ - SUPPORT_TARGET_TEMPERATURE_RANGE + self._supported_features = ( + SUPPORT_PRESET_MODE + | SUPPORT_TARGET_TEMPERATURE + | SUPPORT_TARGET_TEMPERATURE_RANGE + ) - if device._data['canControlHumidification']: # noqa; pylint: disable=protected-access + if device._data[ + "canControlHumidification" + ]: # noqa; pylint: disable=protected-access self._supported_features |= SUPPORT_TARGET_HUMIDITY - if device.raw_ui_data['SwitchEmergencyHeatAllowed']: + if device.raw_ui_data["SwitchEmergencyHeatAllowed"]: self._supported_features |= SUPPORT_AUX_HEAT - if not device._data['hasFan']: # pylint: disable=protected-access + if not device._data["hasFan"]: # pylint: disable=protected-access return # not all honeywell fans support all modes - mappings = [v for k, v in FAN_MODE_TO_HW.items() - if device.raw_fan_data[k]] + mappings = [v for k, v in FAN_MODE_TO_HW.items() if device.raw_fan_data[k]] self._fan_mode_map = {k: v for d in mappings for k, v in d.items()} self._supported_features |= SUPPORT_FAN_MODE @@ -164,10 +200,9 @@ class HoneywellUSThermostat(ClimateDevice): def device_state_attributes(self) -> Dict[str, Any]: """Return the device specific state attributes.""" data = {} - data[ATTR_FAN_ACTION] = \ - 'running' if self._device.fan_running else 'idle' + data[ATTR_FAN_ACTION] = "running" if self._device.fan_running else "idle" if self._device.raw_dr_data: - data['dr_phase'] = self._device.raw_dr_data.get('Phase') + data["dr_phase"] = self._device.raw_dr_data.get("Phase") return data @property @@ -179,25 +214,24 @@ class HoneywellUSThermostat(ClimateDevice): def min_temp(self) -> float: """Return the minimum temperature.""" if self.hvac_mode in [HVAC_MODE_COOL, HVAC_MODE_HEAT_COOL]: - return self._device.raw_ui_data['CoolLowerSetptLimit'] + return self._device.raw_ui_data["CoolLowerSetptLimit"] if self.hvac_mode == HVAC_MODE_HEAT: - return self._device.raw_ui_data['HeatLowerSetptLimit'] + return self._device.raw_ui_data["HeatLowerSetptLimit"] return None @property def max_temp(self) -> float: """Return the maximum temperature.""" if self.hvac_mode == HVAC_MODE_COOL: - return self._device.raw_ui_data['CoolUpperSetptLimit'] + return self._device.raw_ui_data["CoolUpperSetptLimit"] if self.hvac_mode in [HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL]: - return self._device.raw_ui_data['HeatUpperSetptLimit'] + return self._device.raw_ui_data["HeatUpperSetptLimit"] return None @property def temperature_unit(self) -> str: """Return the unit of measurement.""" - return (TEMP_CELSIUS if self._device.temperature_unit == 'C' - else TEMP_FAHRENHEIT) + return TEMP_CELSIUS if self._device.temperature_unit == "C" else TEMP_FAHRENHEIT @property def current_humidity(self) -> Optional[int]: @@ -262,7 +296,7 @@ class HoneywellUSThermostat(ClimateDevice): @property def is_aux_heat(self) -> Optional[str]: """Return true if aux heater.""" - return self._device.system_mode == 'emheat' + return self._device.system_mode == "emheat" @property def fan_mode(self) -> Optional[str]: @@ -285,19 +319,17 @@ class HoneywellUSThermostat(ClimateDevice): # Set hold if this is not the case if getattr(self._device, "hold_{}".format(mode)) is False: # Get next period key - next_period_key = '{}NextPeriod'.format(mode.capitalize()) + next_period_key = "{}NextPeriod".format(mode.capitalize()) # Get next period raw value next_period = self._device.raw_ui_data.get(next_period_key) # Get next period time hour, minute = divmod(next_period * 15, 60) # Set hold time - setattr(self._device, - "hold_{}".format(mode), - datetime.time(hour, minute)) + setattr( + self._device, "hold_{}".format(mode), datetime.time(hour, minute) + ) # Set temperature - setattr(self._device, - "setpoint_{}".format(mode), - temperature) + setattr(self._device, "setpoint_{}".format(mode), temperature) except somecomfort.SomeComfortError: _LOGGER.error("Temperature %.1f out of range", temperature) @@ -337,21 +369,23 @@ class HoneywellUSThermostat(ClimateDevice): # Get current mode mode = self._device.system_mode except somecomfort.SomeComfortError: - _LOGGER.error('Can not get system mode') + _LOGGER.error("Can not get system mode") return try: # Set permanent hold - setattr(self._device, - "hold_{}".format(mode), - True) + setattr(self._device, "hold_{}".format(mode), True) # Set temperature - setattr(self._device, - "setpoint_{}".format(mode), - getattr(self, "_{}_away_temp".format(mode))) + setattr( + self._device, + "setpoint_{}".format(mode), + getattr(self, "_{}_away_temp".format(mode)), + ) except somecomfort.SomeComfortError: - _LOGGER.error('Temperature %.1f out of range', - getattr(self, "_{}_away_temp".format(mode))) + _LOGGER.error( + "Temperature %.1f out of range", + getattr(self, "_{}_away_temp".format(mode)), + ) def _turn_away_mode_off(self) -> None: """Turn away off.""" @@ -361,7 +395,7 @@ class HoneywellUSThermostat(ClimateDevice): self._device.hold_cool = False self._device.hold_heat = False except somecomfort.SomeComfortError: - _LOGGER.error('Can not stop hold mode') + _LOGGER.error("Can not stop hold mode") def set_preset_mode(self, preset_mode: str) -> None: """Set new preset mode.""" @@ -372,7 +406,7 @@ class HoneywellUSThermostat(ClimateDevice): def turn_aux_heat_on(self) -> None: """Turn auxiliary heater on.""" - self._device.system_mode = 'emheat' + self._device.system_mode = "emheat" def turn_aux_heat_off(self) -> None: """Turn auxiliary heater off.""" @@ -388,21 +422,20 @@ class HoneywellUSThermostat(ClimateDevice): will succeed, is to recreate a new somecomfort client. """ try: - self._client = somecomfort.SomeComfort( - self._username, self._password) + self._client = somecomfort.SomeComfort(self._username, self._password) except somecomfort.AuthError: - _LOGGER.error("Failed to login to honeywell account %s", - self._username) + _LOGGER.error("Failed to login to honeywell account %s", self._username) return False except somecomfort.SomeComfortError as ex: - _LOGGER.error("Failed to initialize honeywell client: %s", - str(ex)) + _LOGGER.error("Failed to initialize honeywell client: %s", str(ex)) return False - devices = [device - for location in self._client.locations_by_id.values() - for device in location.devices_by_id.values() - if device.name == self._device.name] + devices = [ + device + for location in self._client.locations_by_id.values() + for device in location.devices_by_id.values() + if device.name == self._device.name + ] if len(devices) != 1: _LOGGER.error("Failed to find device %s", self._device.name) @@ -418,14 +451,18 @@ class HoneywellUSThermostat(ClimateDevice): try: self._device.refresh() break - except (somecomfort.client.APIRateLimited, OSError, - requests.exceptions.ReadTimeout) as exp: + except ( + somecomfort.client.APIRateLimited, + OSError, + requests.exceptions.ReadTimeout, + ) as exp: retries -= 1 if retries == 0: raise exp if not self._retry(): raise exp - _LOGGER.error( - "SomeComfort update failed, Retrying - Error: %s", exp) + _LOGGER.error("SomeComfort update failed, Retrying - Error: %s", exp) - _LOGGER.debug("latestData = %s ", self._device._data) # noqa; pylint: disable=protected-access + _LOGGER.debug( + "latestData = %s ", self._device._data + ) # noqa; pylint: disable=protected-access diff --git a/homeassistant/components/hook/switch.py b/homeassistant/components/hook/switch.py index abe2040b091..d26f35e2dfc 100644 --- a/homeassistant/components/hook/switch.py +++ b/homeassistant/components/hook/switch.py @@ -6,28 +6,35 @@ import voluptuous as vol import async_timeout import aiohttp -from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) +from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, CONF_TOKEN from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -HOOK_ENDPOINT = 'https://api.gethook.io/v1/' +HOOK_ENDPOINT = "https://api.gethook.io/v1/" TIMEOUT = 10 -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Exclusive(CONF_PASSWORD, 'hook_secret', msg='hook: provide ' + - 'username/password OR token'): cv.string, - vol.Exclusive(CONF_TOKEN, 'hook_secret', msg='hook: provide ' + - 'username/password OR token'): cv.string, - vol.Inclusive(CONF_USERNAME, 'hook_auth'): cv.string, - vol.Inclusive(CONF_PASSWORD, 'hook_auth'): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Exclusive( + CONF_PASSWORD, + "hook_secret", + msg="hook: provide " + "username/password OR token", + ): cv.string, + vol.Exclusive( + CONF_TOKEN, + "hook_secret", + msg="hook: provide " + "username/password OR token", + ): cv.string, + vol.Inclusive(CONF_USERNAME, "hook_auth"): cv.string, + vol.Inclusive(CONF_PASSWORD, "hook_auth"): cv.string, + } +) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up Hook by getting the access token and list of actions.""" username = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) @@ -38,10 +45,9 @@ async def async_setup_platform(hass, config, async_add_entities, try: with async_timeout.timeout(TIMEOUT): response = await websession.post( - '{}{}'.format(HOOK_ENDPOINT, 'user/login'), - data={ - 'username': username, - 'password': password}) + "{}{}".format(HOOK_ENDPOINT, "user/login"), + data={"username": username, "password": password}, + ) # The Hook API returns JSON but calls it 'text/html'. Setting # content_type=None disables aiohttp's content-type validation. data = await response.json(content_type=None) @@ -50,7 +56,7 @@ async def async_setup_platform(hass, config, async_add_entities, return False try: - token = data['data']['token'] + token = data["data"]["token"] except KeyError: _LOGGER.error("No token. Check username and password") return False @@ -58,21 +64,18 @@ async def async_setup_platform(hass, config, async_add_entities, try: with async_timeout.timeout(TIMEOUT): response = await websession.get( - '{}{}'.format(HOOK_ENDPOINT, 'device'), - params={"token": token}) + "{}{}".format(HOOK_ENDPOINT, "device"), params={"token": token} + ) data = await response.json(content_type=None) except (asyncio.TimeoutError, aiohttp.ClientError) as error: _LOGGER.error("Failed getting devices: %s", error) return False async_add_entities( - HookSmartHome( - hass, - token, - d['device_id'], - d['device_name']) - for lst in data['data'] - for d in lst) + HookSmartHome(hass, token, d["device_id"], d["device_name"]) + for lst in data["data"] + for d in lst + ) class HookSmartHome(SwitchDevice): @@ -85,8 +88,7 @@ class HookSmartHome(SwitchDevice): self._state = False self._id = device_id self._name = device_name - _LOGGER.debug( - "Creating Hook object: ID: %s Name: %s", self._id, self._name) + _LOGGER.debug("Creating Hook object: ID: %s Name: %s", self._id, self._name) @property def name(self): @@ -104,8 +106,7 @@ class HookSmartHome(SwitchDevice): _LOGGER.debug("Sending: %s", url) websession = async_get_clientsession(self.hass) with async_timeout.timeout(TIMEOUT): - response = await websession.get( - url, params={"token": self._token}) + response = await websession.get(url, params={"token": self._token}) data = await response.json(content_type=None) except (asyncio.TimeoutError, aiohttp.ClientError) as error: @@ -113,21 +114,19 @@ class HookSmartHome(SwitchDevice): return False _LOGGER.debug("Got: %s", data) - return data['return_value'] == '1' + return data["return_value"] == "1" async def async_turn_on(self, **kwargs): """Turn the device on asynchronously.""" _LOGGER.debug("Turning on: %s", self._name) - url = '{}{}{}{}'.format( - HOOK_ENDPOINT, 'device/trigger/', self._id, '/On') + url = "{}{}{}{}".format(HOOK_ENDPOINT, "device/trigger/", self._id, "/On") success = await self._send(url) self._state = success async def async_turn_off(self, **kwargs): """Turn the device off asynchronously.""" _LOGGER.debug("Turning off: %s", self._name) - url = '{}{}{}{}'.format( - HOOK_ENDPOINT, 'device/trigger/', self._id, '/Off') + url = "{}{}{}{}".format(HOOK_ENDPOINT, "device/trigger/", self._id, "/Off") success = await self._send(url) # If it wasn't successful, keep state as true self._state = not success diff --git a/homeassistant/components/horizon/media_player.py b/homeassistant/components/horizon/media_player.py index ab72b051f1b..8bed30e88f3 100644 --- a/homeassistant/components/horizon/media_player.py +++ b/homeassistant/components/horizon/media_player.py @@ -5,34 +5,53 @@ import logging import voluptuous as vol from homeassistant import util -from homeassistant.components.media_player import ( - MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA from homeassistant.components.media_player.const import ( - MEDIA_TYPE_CHANNEL, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, - SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_OFF, - SUPPORT_TURN_ON) + MEDIA_TYPE_CHANNEL, + SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, + SUPPORT_PLAY, + SUPPORT_PLAY_MEDIA, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, +) from homeassistant.const import ( - CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_PAUSED, STATE_PLAYING) + CONF_HOST, + CONF_NAME, + CONF_PORT, + STATE_OFF, + STATE_PAUSED, + STATE_PLAYING, +) from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'Horizon' +DEFAULT_NAME = "Horizon" DEFAULT_PORT = 5900 MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1) MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) -SUPPORT_HORIZON = SUPPORT_NEXT_TRACK | SUPPORT_PAUSE | SUPPORT_PLAY | \ - SUPPORT_PLAY_MEDIA | SUPPORT_PREVIOUS_TRACK | SUPPORT_TURN_ON | \ - SUPPORT_TURN_OFF +SUPPORT_HORIZON = ( + SUPPORT_NEXT_TRACK + | SUPPORT_PAUSE + | SUPPORT_PLAY + | SUPPORT_PLAY_MEDIA + | SUPPORT_PREVIOUS_TRACK + | SUPPORT_TURN_ON + | SUPPORT_TURN_OFF +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -142,8 +161,11 @@ class HorizonDevice(MediaPlayerDevice): except ValueError: _LOGGER.error("Invalid channel: %s", media_id) else: - _LOGGER.error("Invalid media type %s. Supported type: %s", - media_type, MEDIA_TYPE_CHANNEL) + _LOGGER.error( + "Invalid media type %s. Supported type: %s", + media_type, + MEDIA_TYPE_CHANNEL, + ) def _select_channel(self, channel): """Select a channel (taken from einder library, thx).""" @@ -163,8 +185,9 @@ class HorizonDevice(MediaPlayerDevice): elif channel: self._client.select_channel(channel) except OSError as msg: - _LOGGER.error("%s disconnected: %s. Trying to reconnect...", - self._name, msg) + _LOGGER.error( + "%s disconnected: %s. Trying to reconnect...", self._name, msg + ) # for reconnect, first gracefully disconnect self._client.disconnect() @@ -173,8 +196,7 @@ class HorizonDevice(MediaPlayerDevice): self._client.connect() self._client.authorize() except AuthenticationError as msg: - _LOGGER.error("Authentication to %s failed: %s", self._name, - msg) + _LOGGER.error("Authentication to %s failed: %s", self._name, msg) return except OSError as msg: # occurs if horizon box is offline diff --git a/homeassistant/components/hp_ilo/sensor.py b/homeassistant/components/hp_ilo/sensor.py index 46fde885613..1ad70c06397 100644 --- a/homeassistant/components/hp_ilo/sensor.py +++ b/homeassistant/components/hp_ilo/sensor.py @@ -6,9 +6,16 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_HOST, CONF_MONITORED_VARIABLES, CONF_NAME, CONF_PASSWORD, CONF_PORT, - CONF_SENSOR_TYPE, CONF_UNIT_OF_MEASUREMENT, CONF_USERNAME, - CONF_VALUE_TEMPLATE) + CONF_HOST, + CONF_MONITORED_VARIABLES, + CONF_NAME, + CONF_PASSWORD, + CONF_PORT, + CONF_SENSOR_TYPE, + CONF_UNIT_OF_MEASUREMENT, + CONF_USERNAME, + CONF_VALUE_TEMPLATE, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle @@ -21,35 +28,43 @@ DEFAULT_PORT = 443 MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300) SENSOR_TYPES = { - 'server_name': ['Server Name', 'get_server_name'], - 'server_fqdn': ['Server FQDN', 'get_server_fqdn'], - 'server_host_data': ['Server Host Data', 'get_host_data'], - 'server_oa_info': ['Server Onboard Administrator Info', 'get_oa_info'], - 'server_power_status': ['Server Power state', 'get_host_power_status'], - 'server_power_readings': ['Server Power readings', 'get_power_readings'], - 'server_power_on_time': ['Server Power On time', - 'get_server_power_on_time'], - 'server_asset_tag': ['Server Asset Tag', 'get_asset_tag'], - 'server_uid_status': ['Server UID light', 'get_uid_status'], - 'server_health': ['Server Health', 'get_embedded_health'], - 'network_settings': ['Network Settings', 'get_network_settings'] + "server_name": ["Server Name", "get_server_name"], + "server_fqdn": ["Server FQDN", "get_server_fqdn"], + "server_host_data": ["Server Host Data", "get_host_data"], + "server_oa_info": ["Server Onboard Administrator Info", "get_oa_info"], + "server_power_status": ["Server Power state", "get_host_power_status"], + "server_power_readings": ["Server Power readings", "get_power_readings"], + "server_power_on_time": ["Server Power On time", "get_server_power_on_time"], + "server_asset_tag": ["Server Asset Tag", "get_asset_tag"], + "server_uid_status": ["Server UID light", "get_uid_status"], + "server_health": ["Server Health", "get_embedded_health"], + "network_settings": ["Network Settings", "get_network_settings"], } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_MONITORED_VARIABLES, default=[]): - vol.All(cv.ensure_list, [vol.Schema({ - vol.Required(CONF_NAME): cv.string, - vol.Required(CONF_SENSOR_TYPE): - vol.All(cv.string, vol.In(SENSOR_TYPES)), - vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, - vol.Optional(CONF_VALUE_TEMPLATE): cv.template - })]), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_MONITORED_VARIABLES, default=[]): vol.All( + cv.ensure_list, + [ + vol.Schema( + { + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_SENSOR_TYPE): vol.All( + cv.string, vol.In(SENSOR_TYPES) + ), + vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, + } + ) + ], + ), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -74,12 +89,13 @@ def setup_platform(hass, config, add_entities, discovery_info=None): new_device = HpIloSensor( hass=hass, hp_ilo_data=hp_ilo_data, - sensor_name='{} {}'.format( - config.get(CONF_NAME), monitored_variable[CONF_NAME]), + sensor_name="{} {}".format( + config.get(CONF_NAME), monitored_variable[CONF_NAME] + ), sensor_type=monitored_variable[CONF_SENSOR_TYPE], sensor_value_template=monitored_variable.get(CONF_VALUE_TEMPLATE), - unit_of_measurement=monitored_variable.get( - CONF_UNIT_OF_MEASUREMENT)) + unit_of_measurement=monitored_variable.get(CONF_UNIT_OF_MEASUREMENT), + ) devices.append(new_device) add_entities(devices, True) @@ -88,8 +104,15 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class HpIloSensor(Entity): """Representation of a HP iLO sensor.""" - def __init__(self, hass, hp_ilo_data, sensor_type, sensor_name, - sensor_value_template, unit_of_measurement): + def __init__( + self, + hass, + hp_ilo_data, + sensor_type, + sensor_name, + sensor_value_template, + unit_of_measurement, + ): """Initialize the HP iLO sensor.""" self._hass = hass self._name = sensor_name @@ -161,8 +184,14 @@ class HpIloData: try: self.data = hpilo.Ilo( - hostname=self._host, login=self._login, - password=self._password, port=self._port) - except (hpilo.IloError, hpilo.IloCommunicationError, - hpilo.IloLoginFailed) as error: + hostname=self._host, + login=self._login, + password=self._password, + port=self._port, + ) + except ( + hpilo.IloError, + hpilo.IloCommunicationError, + hpilo.IloLoginFailed, + ) as error: raise ValueError("Unable to init HP ILO, {}".format(error)) diff --git a/homeassistant/components/html5/notify.py b/homeassistant/components/html5/notify.py index 8e4d902ad73..18882968cf9 100644 --- a/homeassistant/components/html5/notify.py +++ b/homeassistant/components/html5/notify.py @@ -16,27 +16,37 @@ from homeassistant.components import websocket_api from homeassistant.components.frontend import add_manifest_json_key from homeassistant.components.http import HomeAssistantView from homeassistant.const import ( - HTTP_BAD_REQUEST, HTTP_INTERNAL_SERVER_ERROR, HTTP_UNAUTHORIZED, URL_ROOT) + HTTP_BAD_REQUEST, + HTTP_INTERNAL_SERVER_ERROR, + HTTP_UNAUTHORIZED, + URL_ROOT, +) from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv from homeassistant.util import ensure_unique_string from homeassistant.util.json import load_json, save_json from homeassistant.components.notify import ( - ATTR_DATA, ATTR_TARGET, ATTR_TITLE, ATTR_TITLE_DEFAULT, DOMAIN, - PLATFORM_SCHEMA, BaseNotificationService) + ATTR_DATA, + ATTR_TARGET, + ATTR_TITLE, + ATTR_TITLE_DEFAULT, + DOMAIN, + PLATFORM_SCHEMA, + BaseNotificationService, +) _LOGGER = logging.getLogger(__name__) -REGISTRATIONS_FILE = 'html5_push_registrations.conf' +REGISTRATIONS_FILE = "html5_push_registrations.conf" -SERVICE_DISMISS = 'html5_dismiss' +SERVICE_DISMISS = "html5_dismiss" -ATTR_GCM_SENDER_ID = 'gcm_sender_id' -ATTR_GCM_API_KEY = 'gcm_api_key' -ATTR_VAPID_PUB_KEY = 'vapid_pub_key' -ATTR_VAPID_PRV_KEY = 'vapid_prv_key' -ATTR_VAPID_EMAIL = 'vapid_email' +ATTR_GCM_SENDER_ID = "gcm_sender_id" +ATTR_GCM_API_KEY = "gcm_api_key" +ATTR_VAPID_PUB_KEY = "vapid_pub_key" +ATTR_VAPID_PRV_KEY = "vapid_prv_key" +ATTR_VAPID_EMAIL = "vapid_email" def gcm_api_deprecated(value): @@ -46,92 +56,114 @@ def gcm_api_deprecated(value): "Configuring html5_push_notifications via the GCM api" " has been deprecated and will stop working after April 11," " 2019. Use the VAPID configuration instead. For instructions," - " see https://www.home-assistant.io/components/notify.html5/") + " see https://www.home-assistant.io/components/notify.html5/" + ) return value -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(ATTR_GCM_SENDER_ID): - vol.All(cv.string, gcm_api_deprecated), - vol.Optional(ATTR_GCM_API_KEY): cv.string, - vol.Optional(ATTR_VAPID_PUB_KEY): cv.string, - vol.Optional(ATTR_VAPID_PRV_KEY): cv.string, - vol.Optional(ATTR_VAPID_EMAIL): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(ATTR_GCM_SENDER_ID): vol.All(cv.string, gcm_api_deprecated), + vol.Optional(ATTR_GCM_API_KEY): cv.string, + vol.Optional(ATTR_VAPID_PUB_KEY): cv.string, + vol.Optional(ATTR_VAPID_PRV_KEY): cv.string, + vol.Optional(ATTR_VAPID_EMAIL): cv.string, + } +) -ATTR_SUBSCRIPTION = 'subscription' -ATTR_BROWSER = 'browser' -ATTR_NAME = 'name' +ATTR_SUBSCRIPTION = "subscription" +ATTR_BROWSER = "browser" +ATTR_NAME = "name" -ATTR_ENDPOINT = 'endpoint' -ATTR_KEYS = 'keys' -ATTR_AUTH = 'auth' -ATTR_P256DH = 'p256dh' -ATTR_EXPIRATIONTIME = 'expirationTime' +ATTR_ENDPOINT = "endpoint" +ATTR_KEYS = "keys" +ATTR_AUTH = "auth" +ATTR_P256DH = "p256dh" +ATTR_EXPIRATIONTIME = "expirationTime" -ATTR_TAG = 'tag' -ATTR_ACTION = 'action' -ATTR_ACTIONS = 'actions' -ATTR_TYPE = 'type' -ATTR_URL = 'url' -ATTR_DISMISS = 'dismiss' -ATTR_PRIORITY = 'priority' -DEFAULT_PRIORITY = 'normal' -ATTR_TTL = 'ttl' +ATTR_TAG = "tag" +ATTR_ACTION = "action" +ATTR_ACTIONS = "actions" +ATTR_TYPE = "type" +ATTR_URL = "url" +ATTR_DISMISS = "dismiss" +ATTR_PRIORITY = "priority" +DEFAULT_PRIORITY = "normal" +ATTR_TTL = "ttl" DEFAULT_TTL = 86400 -ATTR_JWT = 'jwt' +ATTR_JWT = "jwt" -WS_TYPE_APPKEY = 'notify/html5/appkey' -SCHEMA_WS_APPKEY = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_APPKEY -}) +WS_TYPE_APPKEY = "notify/html5/appkey" +SCHEMA_WS_APPKEY = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {vol.Required("type"): WS_TYPE_APPKEY} +) # The number of days after the moment a notification is sent that a JWT # is valid. JWT_VALID_DAYS = 7 KEYS_SCHEMA = vol.All( - dict, vol.Schema({ - vol.Required(ATTR_AUTH): cv.string, - vol.Required(ATTR_P256DH): cv.string, - }) + dict, + vol.Schema( + {vol.Required(ATTR_AUTH): cv.string, vol.Required(ATTR_P256DH): cv.string} + ), ) SUBSCRIPTION_SCHEMA = vol.All( - dict, vol.Schema({ - # pylint: disable=no-value-for-parameter - vol.Required(ATTR_ENDPOINT): vol.Url(), - vol.Required(ATTR_KEYS): KEYS_SCHEMA, - vol.Optional(ATTR_EXPIRATIONTIME): vol.Any(None, cv.positive_int), - }) + dict, + vol.Schema( + { + # pylint: disable=no-value-for-parameter + vol.Required(ATTR_ENDPOINT): vol.Url(), + vol.Required(ATTR_KEYS): KEYS_SCHEMA, + vol.Optional(ATTR_EXPIRATIONTIME): vol.Any(None, cv.positive_int), + } + ), ) -DISMISS_SERVICE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_TARGET): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(ATTR_DATA): dict, -}) +DISMISS_SERVICE_SCHEMA = vol.Schema( + { + vol.Optional(ATTR_TARGET): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(ATTR_DATA): dict, + } +) -REGISTER_SCHEMA = vol.Schema({ - vol.Required(ATTR_SUBSCRIPTION): SUBSCRIPTION_SCHEMA, - vol.Required(ATTR_BROWSER): vol.In(['chrome', 'firefox']), - vol.Optional(ATTR_NAME): cv.string -}) +REGISTER_SCHEMA = vol.Schema( + { + vol.Required(ATTR_SUBSCRIPTION): SUBSCRIPTION_SCHEMA, + vol.Required(ATTR_BROWSER): vol.In(["chrome", "firefox"]), + vol.Optional(ATTR_NAME): cv.string, + } +) -CALLBACK_EVENT_PAYLOAD_SCHEMA = vol.Schema({ - vol.Required(ATTR_TAG): cv.string, - vol.Required(ATTR_TYPE): vol.In(['received', 'clicked', 'closed']), - vol.Required(ATTR_TARGET): cv.string, - vol.Optional(ATTR_ACTION): cv.string, - vol.Optional(ATTR_DATA): dict, -}) +CALLBACK_EVENT_PAYLOAD_SCHEMA = vol.Schema( + { + vol.Required(ATTR_TAG): cv.string, + vol.Required(ATTR_TYPE): vol.In(["received", "clicked", "closed"]), + vol.Required(ATTR_TARGET): cv.string, + vol.Optional(ATTR_ACTION): cv.string, + vol.Optional(ATTR_DATA): dict, + } +) -NOTIFY_CALLBACK_EVENT = 'html5_notification' +NOTIFY_CALLBACK_EVENT = "html5_notification" # Badge and timestamp are Chrome specific (not in official spec) HTML5_SHOWNOTIFICATION_PARAMETERS = ( - 'actions', 'badge', 'body', 'dir', 'icon', 'image', 'lang', - 'renotify', 'requireInteraction', 'tag', 'timestamp', 'vibrate') + "actions", + "badge", + "body", + "dir", + "icon", + "image", + "lang", + "renotify", + "requireInteraction", + "tag", + "timestamp", + "vibrate", +) def get_service(hass, config, discovery_info=None): @@ -148,27 +180,24 @@ def get_service(hass, config, discovery_info=None): vapid_email = config.get(ATTR_VAPID_EMAIL) def websocket_appkey(hass, connection, msg): - connection.send_message( - websocket_api.result_message(msg['id'], vapid_pub_key)) + connection.send_message(websocket_api.result_message(msg["id"], vapid_pub_key)) hass.components.websocket_api.async_register_command( WS_TYPE_APPKEY, websocket_appkey, SCHEMA_WS_APPKEY ) - hass.http.register_view( - HTML5PushRegistrationView(registrations, json_path)) + hass.http.register_view(HTML5PushRegistrationView(registrations, json_path)) hass.http.register_view(HTML5PushCallbackView(registrations)) gcm_api_key = config.get(ATTR_GCM_API_KEY) gcm_sender_id = config.get(ATTR_GCM_SENDER_ID) if gcm_sender_id is not None: - add_manifest_json_key( - ATTR_GCM_SENDER_ID, config.get(ATTR_GCM_SENDER_ID)) + add_manifest_json_key(ATTR_GCM_SENDER_ID, config.get(ATTR_GCM_SENDER_ID)) return HTML5NotificationService( - hass, gcm_api_key, vapid_prv_key, vapid_email, registrations, - json_path) + hass, gcm_api_key, vapid_prv_key, vapid_email, registrations, json_path + ) def _load_config(filename): @@ -183,8 +212,8 @@ def _load_config(filename): class HTML5PushRegistrationView(HomeAssistantView): """Accepts push registrations from a browser.""" - url = '/api/notify.html5' - name = 'api:notify.html5' + url = "/api/notify.html5" + name = "api:notify.html5" def __init__(self, registrations, json_path): """Init HTML5PushRegistrationView.""" @@ -196,12 +225,11 @@ class HTML5PushRegistrationView(HomeAssistantView): try: data = await request.json() except ValueError: - return self.json_message('Invalid JSON', HTTP_BAD_REQUEST) + return self.json_message("Invalid JSON", HTTP_BAD_REQUEST) try: data = REGISTER_SCHEMA(data) except vol.Invalid as ex: - return self.json_message( - humanize_error(data, ex), HTTP_BAD_REQUEST) + return self.json_message(humanize_error(data, ex), HTTP_BAD_REQUEST) devname = data.get(ATTR_NAME) data.pop(ATTR_NAME, None) @@ -212,12 +240,10 @@ class HTML5PushRegistrationView(HomeAssistantView): self.registrations[name] = data try: - hass = request.app['hass'] + hass = request.app["hass"] - await hass.async_add_job(save_json, self.json_path, - self.registrations) - return self.json_message( - 'Push notification subscriber registered.') + await hass.async_add_job(save_json, self.json_path, self.registrations) + return self.json_message("Push notification subscriber registered.") except HomeAssistantError: if previous_registration is not None: self.registrations[name] = previous_registration @@ -225,7 +251,8 @@ class HTML5PushRegistrationView(HomeAssistantView): self.registrations.pop(name) return self.json_message( - 'Error saving registration.', HTTP_INTERNAL_SERVER_ERROR) + "Error saving registration.", HTTP_INTERNAL_SERVER_ERROR + ) def find_registration_name(self, data, suggested=None): """Find a registration name matching data or generate a unique one.""" @@ -234,15 +261,14 @@ class HTML5PushRegistrationView(HomeAssistantView): subscription = registration.get(ATTR_SUBSCRIPTION) if subscription.get(ATTR_ENDPOINT) == endpoint: return key - return ensure_unique_string(suggested or 'unnamed device', - self.registrations) + return ensure_unique_string(suggested or "unnamed device", self.registrations) async def delete(self, request): """Delete a registration.""" try: data = await request.json() except ValueError: - return self.json_message('Invalid JSON', HTTP_BAD_REQUEST) + return self.json_message("Invalid JSON", HTTP_BAD_REQUEST) subscription = data.get(ATTR_SUBSCRIPTION) @@ -255,29 +281,29 @@ class HTML5PushRegistrationView(HomeAssistantView): if not found: # If not found, unregistering was already done. Return 200 - return self.json_message('Registration not found.') + return self.json_message("Registration not found.") reg = self.registrations.pop(found) try: - hass = request.app['hass'] + hass = request.app["hass"] - await hass.async_add_job(save_json, self.json_path, - self.registrations) + await hass.async_add_job(save_json, self.json_path, self.registrations) except HomeAssistantError: self.registrations[found] = reg return self.json_message( - 'Error saving registration.', HTTP_INTERNAL_SERVER_ERROR) + "Error saving registration.", HTTP_INTERNAL_SERVER_ERROR + ) - return self.json_message('Push notification subscriber unregistered.') + return self.json_message("Push notification subscriber unregistered.") class HTML5PushCallbackView(HomeAssistantView): """Accepts push registrations from a browser.""" requires_auth = False - url = '/api/notify.html5/callback' - name = 'api:notify.html5/callback' + url = "/api/notify.html5/callback" + name = "api:notify.html5/callback" def __init__(self, registrations): """Init HTML5PushCallbackView.""" @@ -301,36 +327,40 @@ class HTML5PushCallbackView(HomeAssistantView): except jwt.exceptions.DecodeError: pass - return self.json_message('No target found in JWT', - status_code=HTTP_UNAUTHORIZED) + return self.json_message( + "No target found in JWT", status_code=HTTP_UNAUTHORIZED + ) # The following is based on code from Auth0 # https://auth0.com/docs/quickstart/backend/python def check_authorization_header(self, request): """Check the authorization header.""" import jwt + auth = request.headers.get(AUTHORIZATION, None) if not auth: - return self.json_message('Authorization header is expected', - status_code=HTTP_UNAUTHORIZED) + return self.json_message( + "Authorization header is expected", status_code=HTTP_UNAUTHORIZED + ) parts = auth.split() - if parts[0].lower() != 'bearer': - return self.json_message('Authorization header must ' - 'start with Bearer', - status_code=HTTP_UNAUTHORIZED) + if parts[0].lower() != "bearer": + return self.json_message( + "Authorization header must " "start with Bearer", + status_code=HTTP_UNAUTHORIZED, + ) if len(parts) != 2: - return self.json_message('Authorization header must ' - 'be Bearer token', - status_code=HTTP_UNAUTHORIZED) + return self.json_message( + "Authorization header must " "be Bearer token", + status_code=HTTP_UNAUTHORIZED, + ) token = parts[1] try: payload = self.decode_jwt(token) except jwt.exceptions.InvalidTokenError: - return self.json_message('token is invalid', - status_code=HTTP_UNAUTHORIZED) + return self.json_message("token is invalid", status_code=HTTP_UNAUTHORIZED) return payload async def post(self, request): @@ -342,7 +372,7 @@ class HTML5PushCallbackView(HomeAssistantView): try: data = await request.json() except ValueError: - return self.json_message('Invalid JSON', HTTP_BAD_REQUEST) + return self.json_message("Invalid JSON", HTTP_BAD_REQUEST) event_payload = { ATTR_TAG: data.get(ATTR_TAG), @@ -359,20 +389,20 @@ class HTML5PushCallbackView(HomeAssistantView): try: event_payload = CALLBACK_EVENT_PAYLOAD_SCHEMA(event_payload) except vol.Invalid as ex: - _LOGGER.warning("Callback event payload is not valid: %s", - humanize_error(event_payload, ex)) + _LOGGER.warning( + "Callback event payload is not valid: %s", + humanize_error(event_payload, ex), + ) - event_name = '{}.{}'.format(NOTIFY_CALLBACK_EVENT, - event_payload[ATTR_TYPE]) - request.app['hass'].bus.fire(event_name, event_payload) - return self.json({'status': 'ok', 'event': event_payload[ATTR_TYPE]}) + event_name = "{}.{}".format(NOTIFY_CALLBACK_EVENT, event_payload[ATTR_TYPE]) + request.app["hass"].bus.fire(event_name, event_payload) + return self.json({"status": "ok", "event": event_payload[ATTR_TYPE]}) class HTML5NotificationService(BaseNotificationService): """Implement the notification service for HTML5.""" - def __init__(self, hass, gcm_key, vapid_prv, vapid_email, registrations, - json_path): + def __init__(self, hass, gcm_key, vapid_prv, vapid_email, registrations, json_path): """Initialize the service.""" self._gcm_key = gcm_key self._vapid_prv = vapid_prv @@ -394,8 +424,11 @@ class HTML5NotificationService(BaseNotificationService): await self.async_dismiss(**kwargs) hass.services.async_register( - DOMAIN, SERVICE_DISMISS, async_dismiss_message, - schema=DISMISS_SERVICE_SCHEMA) + DOMAIN, + SERVICE_DISMISS, + async_dismiss_message, + schema=DISMISS_SERVICE_SCHEMA, + ) @property def targets(self): @@ -409,11 +442,7 @@ class HTML5NotificationService(BaseNotificationService): """Dismisses a notification.""" data = kwargs.get(ATTR_DATA) tag = data.get(ATTR_TAG) if data else "" - payload = { - ATTR_TAG: tag, - ATTR_DISMISS: True, - ATTR_DATA: {} - } + payload = {ATTR_TAG: tag, ATTR_DISMISS: True, ATTR_DATA: {}} self._push_message(payload, **kwargs) @@ -422,19 +451,18 @@ class HTML5NotificationService(BaseNotificationService): This method must be run in the event loop. """ - await self.hass.async_add_executor_job( - partial(self.dismiss, **kwargs)) + await self.hass.async_add_executor_job(partial(self.dismiss, **kwargs)) def send_message(self, message="", **kwargs): """Send a message to a user.""" tag = str(uuid.uuid4()) payload = { - 'badge': '/static/images/notification-badge.png', - 'body': message, + "badge": "/static/images/notification-badge.png", + "body": message, ATTR_DATA: {}, - 'icon': '/static/icons/favicon-192x192.png', + "icon": "/static/icons/favicon-192x192.png", ATTR_TAG: tag, - ATTR_TITLE: kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT) + ATTR_TITLE: kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT), } data = kwargs.get(ATTR_DATA) @@ -453,8 +481,10 @@ class HTML5NotificationService(BaseNotificationService): payload[ATTR_DATA] = data_tmp - if (payload[ATTR_DATA].get(ATTR_URL) is None and - payload.get(ATTR_ACTIONS) is None): + if ( + payload[ATTR_DATA].get(ATTR_URL) is None + and payload.get(ATTR_ACTIONS) is None + ): payload[ATTR_DATA][ATTR_URL] = URL_ROOT self._push_message(payload, **kwargs) @@ -466,9 +496,9 @@ class HTML5NotificationService(BaseNotificationService): timestamp = int(time.time()) ttl = int(kwargs.get(ATTR_TTL, DEFAULT_TTL)) priority = kwargs.get(ATTR_PRIORITY, DEFAULT_PRIORITY) - if priority not in ['normal', 'high']: + if priority not in ["normal", "high"]: priority = DEFAULT_PRIORITY - payload['timestamp'] = (timestamp*1000) # Javascript ms since epoch + payload["timestamp"] = timestamp * 1000 # Javascript ms since epoch targets = kwargs.get(ATTR_TARGET) if not targets: @@ -479,42 +509,39 @@ class HTML5NotificationService(BaseNotificationService): try: info = REGISTER_SCHEMA(info) except vol.Invalid: - _LOGGER.error("%s is not a valid HTML5 push notification" - " target", target) + _LOGGER.error( + "%s is not a valid HTML5 push notification" " target", target + ) continue payload[ATTR_DATA][ATTR_JWT] = add_jwt( - timestamp, target, payload[ATTR_TAG], - info[ATTR_SUBSCRIPTION][ATTR_KEYS][ATTR_AUTH]) + timestamp, + target, + payload[ATTR_TAG], + info[ATTR_SUBSCRIPTION][ATTR_KEYS][ATTR_AUTH], + ) webpusher = WebPusher(info[ATTR_SUBSCRIPTION]) if self._vapid_prv and self._vapid_email: vapid_headers = create_vapid_headers( - self._vapid_email, info[ATTR_SUBSCRIPTION], - self._vapid_prv) - vapid_headers.update({ - 'urgency': priority, - 'priority': priority - }) + self._vapid_email, info[ATTR_SUBSCRIPTION], self._vapid_prv + ) + vapid_headers.update({"urgency": priority, "priority": priority}) response = webpusher.send( - data=json.dumps(payload), - headers=vapid_headers, - ttl=ttl + data=json.dumps(payload), headers=vapid_headers, ttl=ttl ) else: # Only pass the gcm key if we're actually using GCM # If we don't, notifications break on FireFox - gcm_key = self._gcm_key \ - if 'googleapis.com' \ - in info[ATTR_SUBSCRIPTION][ATTR_ENDPOINT] \ + gcm_key = ( + self._gcm_key + if "googleapis.com" in info[ATTR_SUBSCRIPTION][ATTR_ENDPOINT] else None - response = webpusher.send( - json.dumps(payload), gcm_key=gcm_key, ttl=ttl ) + response = webpusher.send(json.dumps(payload), gcm_key=gcm_key, ttl=ttl) if response.status_code == 410: _LOGGER.info("Notification channel has expired") reg = self.registrations.pop(target) - if not save_json(self.registrations_json_path, - self.registrations): + if not save_json(self.registrations_json_path, self.registrations): self.registrations[target] = reg _LOGGER.error("Error saving registration") else: @@ -524,23 +551,27 @@ class HTML5NotificationService(BaseNotificationService): def add_jwt(timestamp, target, tag, jwt_secret): """Create JWT json to put into payload.""" import jwt - jwt_exp = (datetime.fromtimestamp(timestamp) + - timedelta(days=JWT_VALID_DAYS)) - jwt_claims = {'exp': jwt_exp, 'nbf': timestamp, - 'iat': timestamp, ATTR_TARGET: target, - ATTR_TAG: tag} - return jwt.encode(jwt_claims, jwt_secret).decode('utf-8') + + jwt_exp = datetime.fromtimestamp(timestamp) + timedelta(days=JWT_VALID_DAYS) + jwt_claims = { + "exp": jwt_exp, + "nbf": timestamp, + "iat": timestamp, + ATTR_TARGET: target, + ATTR_TAG: tag, + } + return jwt.encode(jwt_claims, jwt_secret).decode("utf-8") def create_vapid_headers(vapid_email, subscription_info, vapid_private_key): """Create encrypted headers to send to WebPusher.""" from py_vapid import Vapid - if (vapid_email and vapid_private_key and - ATTR_ENDPOINT in subscription_info): + + if vapid_email and vapid_private_key and ATTR_ENDPOINT in subscription_info: url = urlparse(subscription_info.get(ATTR_ENDPOINT)) vapid_claims = { - 'sub': 'mailto:{}'.format(vapid_email), - 'aud': "{}://{}".format(url.scheme, url.netloc) + "sub": "mailto:{}".format(vapid_email), + "aud": "{}://{}".format(url.scheme, url.netloc), } vapid = Vapid.from_string(private_key=vapid_private_key) return vapid.sign(vapid_claims) diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index 7790837fff1..84c7d15a580 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -10,7 +10,10 @@ from aiohttp.web_exceptions import HTTPMovedPermanently import voluptuous as vol from homeassistant.const import ( - EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, SERVER_PORT) + EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP, + SERVER_PORT, +) import homeassistant.helpers.config_validation as cv import homeassistant.util as hass_util from homeassistant.util import ssl as ssl_util @@ -18,41 +21,36 @@ from homeassistant.util.logging import HideSensitiveDataFilter from .auth import setup_auth from .ban import setup_bans -from .const import ( # noqa - KEY_AUTHENTICATED, - KEY_HASS, - KEY_HASS_USER, - KEY_REAL_IP, -) +from .const import KEY_AUTHENTICATED, KEY_HASS, KEY_HASS_USER, KEY_REAL_IP # noqa from .cors import setup_cors from .real_ip import setup_real_ip from .static import CACHE_HEADERS, CachingStaticResource from .view import HomeAssistantView # noqa -DOMAIN = 'http' +DOMAIN = "http" -CONF_API_PASSWORD = 'api_password' -CONF_SERVER_HOST = 'server_host' -CONF_SERVER_PORT = 'server_port' -CONF_BASE_URL = 'base_url' -CONF_SSL_CERTIFICATE = 'ssl_certificate' -CONF_SSL_PEER_CERTIFICATE = 'ssl_peer_certificate' -CONF_SSL_KEY = 'ssl_key' -CONF_CORS_ORIGINS = 'cors_allowed_origins' -CONF_USE_X_FORWARDED_FOR = 'use_x_forwarded_for' -CONF_TRUSTED_PROXIES = 'trusted_proxies' -CONF_TRUSTED_NETWORKS = 'trusted_networks' -CONF_LOGIN_ATTEMPTS_THRESHOLD = 'login_attempts_threshold' -CONF_IP_BAN_ENABLED = 'ip_ban_enabled' -CONF_SSL_PROFILE = 'ssl_profile' +CONF_API_PASSWORD = "api_password" +CONF_SERVER_HOST = "server_host" +CONF_SERVER_PORT = "server_port" +CONF_BASE_URL = "base_url" +CONF_SSL_CERTIFICATE = "ssl_certificate" +CONF_SSL_PEER_CERTIFICATE = "ssl_peer_certificate" +CONF_SSL_KEY = "ssl_key" +CONF_CORS_ORIGINS = "cors_allowed_origins" +CONF_USE_X_FORWARDED_FOR = "use_x_forwarded_for" +CONF_TRUSTED_PROXIES = "trusted_proxies" +CONF_TRUSTED_NETWORKS = "trusted_networks" +CONF_LOGIN_ATTEMPTS_THRESHOLD = "login_attempts_threshold" +CONF_IP_BAN_ENABLED = "ip_ban_enabled" +CONF_SSL_PROFILE = "ssl_profile" -SSL_MODERN = 'modern' -SSL_INTERMEDIATE = 'intermediate' +SSL_MODERN = "modern" +SSL_INTERMEDIATE = "intermediate" _LOGGER = logging.getLogger(__name__) -DEFAULT_SERVER_HOST = '0.0.0.0' -DEFAULT_DEVELOPMENT = '0' +DEFAULT_SERVER_HOST = "0.0.0.0" +DEFAULT_DEVELOPMENT = "0" NO_LOGIN_ATTEMPT_THRESHOLD = -1 @@ -65,7 +63,8 @@ def trusted_networks_deprecated(value): "Configuring trusted_networks via the http integration has been" " deprecated. Use the trusted networks auth provider instead." " For instructions, see https://www.home-assistant.io/docs/" - "authentication/providers/#trusted-networks") + "authentication/providers/#trusted-networks" + ) return value @@ -78,49 +77,54 @@ def api_password_deprecated(value): "Configuring api_password via the http integration has been" " deprecated. Use the legacy api password auth provider instead." " For instructions, see https://www.home-assistant.io/docs/" - "authentication/providers/#legacy-api-password") + "authentication/providers/#legacy-api-password" + ) return value -HTTP_SCHEMA = vol.Schema({ - vol.Optional(CONF_API_PASSWORD): - vol.All(cv.string, api_password_deprecated), - vol.Optional(CONF_SERVER_HOST, default=DEFAULT_SERVER_HOST): cv.string, - vol.Optional(CONF_SERVER_PORT, default=SERVER_PORT): cv.port, - vol.Optional(CONF_BASE_URL): cv.string, - vol.Optional(CONF_SSL_CERTIFICATE): cv.isfile, - vol.Optional(CONF_SSL_PEER_CERTIFICATE): cv.isfile, - vol.Optional(CONF_SSL_KEY): cv.isfile, - vol.Optional(CONF_CORS_ORIGINS, default=[]): - vol.All(cv.ensure_list, [cv.string]), - vol.Inclusive(CONF_USE_X_FORWARDED_FOR, 'proxy'): cv.boolean, - vol.Inclusive(CONF_TRUSTED_PROXIES, 'proxy'): - vol.All(cv.ensure_list, [ip_network]), - vol.Optional(CONF_TRUSTED_NETWORKS, default=[]): - vol.All(cv.ensure_list, [ip_network], trusted_networks_deprecated), - vol.Optional(CONF_LOGIN_ATTEMPTS_THRESHOLD, - default=NO_LOGIN_ATTEMPT_THRESHOLD): - vol.Any(cv.positive_int, NO_LOGIN_ATTEMPT_THRESHOLD), - vol.Optional(CONF_IP_BAN_ENABLED, default=True): cv.boolean, - vol.Optional(CONF_SSL_PROFILE, default=SSL_MODERN): - vol.In([SSL_INTERMEDIATE, SSL_MODERN]), -}) +HTTP_SCHEMA = vol.Schema( + { + vol.Optional(CONF_API_PASSWORD): vol.All(cv.string, api_password_deprecated), + vol.Optional(CONF_SERVER_HOST, default=DEFAULT_SERVER_HOST): cv.string, + vol.Optional(CONF_SERVER_PORT, default=SERVER_PORT): cv.port, + vol.Optional(CONF_BASE_URL): cv.string, + vol.Optional(CONF_SSL_CERTIFICATE): cv.isfile, + vol.Optional(CONF_SSL_PEER_CERTIFICATE): cv.isfile, + vol.Optional(CONF_SSL_KEY): cv.isfile, + vol.Optional(CONF_CORS_ORIGINS, default=[]): vol.All( + cv.ensure_list, [cv.string] + ), + vol.Inclusive(CONF_USE_X_FORWARDED_FOR, "proxy"): cv.boolean, + vol.Inclusive(CONF_TRUSTED_PROXIES, "proxy"): vol.All( + cv.ensure_list, [ip_network] + ), + vol.Optional(CONF_TRUSTED_NETWORKS, default=[]): vol.All( + cv.ensure_list, [ip_network], trusted_networks_deprecated + ), + vol.Optional( + CONF_LOGIN_ATTEMPTS_THRESHOLD, default=NO_LOGIN_ATTEMPT_THRESHOLD + ): vol.Any(cv.positive_int, NO_LOGIN_ATTEMPT_THRESHOLD), + vol.Optional(CONF_IP_BAN_ENABLED, default=True): cv.boolean, + vol.Optional(CONF_SSL_PROFILE, default=SSL_MODERN): vol.In( + [SSL_INTERMEDIATE, SSL_MODERN] + ), + } +) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: HTTP_SCHEMA, -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema({DOMAIN: HTTP_SCHEMA}, extra=vol.ALLOW_EXTRA) class ApiConfig: """Configuration settings for API server.""" - def __init__(self, host: str, port: Optional[int] = SERVER_PORT, - use_ssl: bool = False) -> None: + def __init__( + self, host: str, port: Optional[int] = SERVER_PORT, use_ssl: bool = False + ) -> None: """Initialize a new API config object.""" self.host = host self.port = port - host = host.rstrip('/') + host = host.rstrip("/") if host.startswith(("http://", "https://")): self.base_url = host elif use_ssl: @@ -129,7 +133,7 @@ class ApiConfig: self.base_url = "http://{}".format(host) if port is not None: - self.base_url += ':{}'.format(port) + self.base_url += ":{}".format(port) async def async_setup(hass, config): @@ -153,8 +157,9 @@ async def async_setup(hass, config): ssl_profile = conf[CONF_SSL_PROFILE] if api_password is not None: - logging.getLogger('aiohttp.access').addFilter( - HideSensitiveDataFilter(api_password)) + logging.getLogger("aiohttp.access").addFilter( + HideSensitiveDataFilter(api_password) + ) server = HomeAssistantHTTP( hass, @@ -203,11 +208,21 @@ async def async_setup(hass, config): class HomeAssistantHTTP: """HTTP server for Home Assistant.""" - def __init__(self, hass, - ssl_certificate, ssl_peer_certificate, - ssl_key, server_host, server_port, cors_origins, - use_x_forwarded_for, trusted_proxies, - login_threshold, is_ban_enabled, ssl_profile): + def __init__( + self, + hass, + ssl_certificate, + ssl_peer_certificate, + ssl_key, + server_host, + server_port, + cors_origins, + use_x_forwarded_for, + trusted_proxies, + login_threshold, + is_ban_enabled, + ssl_profile, + ): """Initialize the HTTP Home Assistant server.""" app = self.app = web.Application(middlewares=[]) app[KEY_HASS] = hass @@ -246,13 +261,13 @@ class HomeAssistantHTTP: # Instantiate the view, if needed view = view() - if not hasattr(view, 'url'): + if not hasattr(view, "url"): class_name = view.__class__.__name__ raise AttributeError( '{0} missing required attribute "url"'.format(class_name) ) - if not hasattr(view, 'name'): + if not hasattr(view, "name"): class_name = view.__class__.__name__ raise AttributeError( '{0} missing required attribute "name"'.format(class_name) @@ -269,11 +284,12 @@ class HomeAssistantHTTP: for the redirect, otherwise it has to be a string with placeholders in rule syntax. """ + async def redirect(request): """Redirect to location.""" raise HTTPMovedPermanently(redirect_to) - self.app.router.add_route('GET', url, redirect) + self.app.router.add_route("GET", url, redirect) def register_static_path(self, url_path, path, cache_headers=True): """Register a folder or file to serve as a static path.""" @@ -286,15 +302,18 @@ class HomeAssistantHTTP: return if cache_headers: + async def serve_file(request): """Serve file from disk.""" return web.FileResponse(path, headers=CACHE_HEADERS) + else: + async def serve_file(request): """Serve file from disk.""" return web.FileResponse(path) - self.app.router.add_route('GET', url_path, serve_file) + self.app.router.add_route("GET", url_path, serve_file) async def start(self): """Start the aiohttp server.""" @@ -305,18 +324,21 @@ class HomeAssistantHTTP: else: context = ssl_util.server_context_modern() await self.hass.async_add_executor_job( - context.load_cert_chain, self.ssl_certificate, - self.ssl_key) + context.load_cert_chain, self.ssl_certificate, self.ssl_key + ) except OSError as error: - _LOGGER.error("Could not read SSL certificate from %s: %s", - self.ssl_certificate, error) + _LOGGER.error( + "Could not read SSL certificate from %s: %s", + self.ssl_certificate, + error, + ) return if self.ssl_peer_certificate: context.verify_mode = ssl.CERT_REQUIRED await self.hass.async_add_executor_job( - context.load_verify_locations, - self.ssl_peer_certificate) + context.load_verify_locations, self.ssl_peer_certificate + ) else: context = None @@ -330,13 +352,15 @@ class HomeAssistantHTTP: self.runner = web.AppRunner(self.app) await self.runner.setup() - self.site = web.TCPSite(self.runner, self.server_host, - self.server_port, ssl_context=context) + self.site = web.TCPSite( + self.runner, self.server_host, self.server_port, ssl_context=context + ) try: await self.site.start() except OSError as error: - _LOGGER.error("Failed to create HTTP server at port %d: %s", - self.server_port, error) + _LOGGER.error( + "Failed to create HTTP server at port %d: %s", self.server_port, error + ) async def stop(self): """Stop the aiohttp server.""" diff --git a/homeassistant/components/http/auth.py b/homeassistant/components/http/auth.py index 0d8e327e086..c65cb6a2e94 100644 --- a/homeassistant/components/http/auth.py +++ b/homeassistant/components/http/auth.py @@ -12,17 +12,13 @@ from homeassistant.const import HTTP_HEADER_HA_AUTH from homeassistant.core import callback from homeassistant.util import dt as dt_util -from .const import ( - KEY_AUTHENTICATED, - KEY_HASS_USER, - KEY_REAL_IP, -) +from .const import KEY_AUTHENTICATED, KEY_HASS_USER, KEY_REAL_IP _LOGGER = logging.getLogger(__name__) -DATA_API_PASSWORD = 'api_password' -DATA_SIGN_SECRET = 'http.auth.sign_secret' -SIGN_QUERY_PARAM = 'authSig' +DATA_API_PASSWORD = "api_password" +DATA_SIGN_SECRET = "http.auth.sign_secret" +SIGN_QUERY_PARAM = "authSig" @callback @@ -34,12 +30,20 @@ def async_sign_path(hass, refresh_token_id, path, expiration): secret = hass.data[DATA_SIGN_SECRET] = generate_secret() now = dt_util.utcnow() - return "{}?{}={}".format(path, SIGN_QUERY_PARAM, jwt.encode({ - 'iss': refresh_token_id, - 'path': path, - 'iat': now, - 'exp': now + expiration, - }, secret, algorithm='HS256').decode()) + return "{}?{}={}".format( + path, + SIGN_QUERY_PARAM, + jwt.encode( + { + "iss": refresh_token_id, + "path": path, + "iat": now, + "exp": now + expiration, + }, + secret, + algorithm="HS256", + ).decode(), + ) @callback @@ -53,7 +57,7 @@ def setup_auth(hass, app): trusted_networks = [] for prv in hass.auth.auth_providers: - if prv.type == 'trusted_networks': + if prv.type == "trusted_networks": trusted_networks += prv.trusted_networks async def async_validate_auth_header(request): @@ -63,42 +67,41 @@ def setup_auth(hass, app): Basic auth_type is legacy code, should be removed with api_password. """ try: - auth_type, auth_val = \ - request.headers.get(hdrs.AUTHORIZATION).split(' ', 1) + auth_type, auth_val = request.headers.get(hdrs.AUTHORIZATION).split(" ", 1) except ValueError: # If no space in authorization header return False - if auth_type == 'Bearer': - refresh_token = await hass.auth.async_validate_access_token( - auth_val) + if auth_type == "Bearer": + refresh_token = await hass.auth.async_validate_access_token(auth_val) if refresh_token is None: return False request[KEY_HASS_USER] = refresh_token.user return True - if auth_type == 'Basic' and support_legacy: - decoded = base64.b64decode(auth_val).decode('utf-8') + if auth_type == "Basic" and support_legacy: + decoded = base64.b64decode(auth_val).decode("utf-8") try: - username, password = decoded.split(':', 1) + username, password = decoded.split(":", 1) except ValueError: # If no ':' in decoded return False - if username != 'homeassistant': + if username != "homeassistant": return False - user = await legacy_api_password.async_validate_password( - hass, password) + user = await legacy_api_password.async_validate_password(hass, password) if user is None: return False request[KEY_HASS_USER] = user _LOGGER.info( - 'Basic auth with api_password is going to deprecate,' - ' please use a bearer token to access %s from %s', - request.path, request[KEY_REAL_IP]) + "Basic auth with api_password is going to deprecate," + " please use a bearer token to access %s from %s", + request.path, + request[KEY_REAL_IP], + ) old_auth_warning.add(request.path) return True @@ -118,18 +121,15 @@ def setup_auth(hass, app): try: claims = jwt.decode( - signature, - secret, - algorithms=['HS256'], - options={'verify_iss': False} + signature, secret, algorithms=["HS256"], options={"verify_iss": False} ) except jwt.InvalidTokenError: return False - if claims['path'] != request.path: + if claims["path"] != request.path: return False - refresh_token = await hass.auth.async_get_refresh_token(claims['iss']) + refresh_token = await hass.auth.async_get_refresh_token(claims["iss"]) if refresh_token is None: return False @@ -141,8 +141,7 @@ def setup_auth(hass, app): """Test if request is from a trusted ip.""" ip_addr = request[KEY_REAL_IP] - if not any(ip_addr in trusted_network - for trusted_network in trusted_networks): + if not any(ip_addr in trusted_network for trusted_network in trusted_networks): return False user = await hass.auth.async_get_owner() @@ -154,8 +153,7 @@ def setup_auth(hass, app): async def async_validate_legacy_api_password(request, password): """Validate api_password.""" - user = await legacy_api_password.async_validate_password( - hass, password) + user = await legacy_api_password.async_validate_password(hass, password) if user is None: return False @@ -167,49 +165,63 @@ def setup_auth(hass, app): """Authenticate as middleware.""" authenticated = False - if (HTTP_HEADER_HA_AUTH in request.headers or - DATA_API_PASSWORD in request.query): + if HTTP_HEADER_HA_AUTH in request.headers or DATA_API_PASSWORD in request.query: if request.path not in old_auth_warning: _LOGGER.log( logging.INFO if support_legacy else logging.WARNING, - 'api_password is going to deprecate. You need to use a' - ' bearer token to access %s from %s', - request.path, request[KEY_REAL_IP]) + "api_password is going to deprecate. You need to use a" + " bearer token to access %s from %s", + request.path, + request[KEY_REAL_IP], + ) old_auth_warning.add(request.path) - if (hdrs.AUTHORIZATION in request.headers and - await async_validate_auth_header(request)): + if hdrs.AUTHORIZATION in request.headers and await async_validate_auth_header( + request + ): # it included both use_auth and api_password Basic auth authenticated = True # We first start with a string check to avoid parsing query params # for every request. - elif (request.method == "GET" and SIGN_QUERY_PARAM in request.query and - await async_validate_signed_request(request)): + elif ( + request.method == "GET" + and SIGN_QUERY_PARAM in request.query + and await async_validate_signed_request(request) + ): authenticated = True - elif (trusted_networks and - await async_validate_trusted_networks(request)): + elif trusted_networks and await async_validate_trusted_networks(request): if request.path not in old_auth_warning: # When removing this, don't forget to remove the print logic # in http/view.py - request['deprecate_warning_message'] = \ - 'Access from trusted networks without auth token is ' \ - 'going to be removed in Home Assistant 0.96. Configure ' \ - 'the trusted networks auth provider or use long-lived ' \ - 'access tokens to access {} from {}'.format( - request.path, request[KEY_REAL_IP]) + request["deprecate_warning_message"] = ( + "Access from trusted networks without auth token is " + "going to be removed in Home Assistant 0.96. Configure " + "the trusted networks auth provider or use long-lived " + "access tokens to access {} from {}".format( + request.path, request[KEY_REAL_IP] + ) + ) old_auth_warning.add(request.path) authenticated = True - elif (support_legacy and HTTP_HEADER_HA_AUTH in request.headers and - await async_validate_legacy_api_password( - request, request.headers[HTTP_HEADER_HA_AUTH])): + elif ( + support_legacy + and HTTP_HEADER_HA_AUTH in request.headers + and await async_validate_legacy_api_password( + request, request.headers[HTTP_HEADER_HA_AUTH] + ) + ): authenticated = True - elif (support_legacy and DATA_API_PASSWORD in request.query and - await async_validate_legacy_api_password( - request, request.query[DATA_API_PASSWORD])): + elif ( + support_legacy + and DATA_API_PASSWORD in request.query + and await async_validate_legacy_api_password( + request, request.query[DATA_API_PASSWORD] + ) + ): authenticated = True request[KEY_AUTHENTICATED] = authenticated diff --git a/homeassistant/components/http/ban.py b/homeassistant/components/http/ban.py index 1cb610e71a6..db8d2ade959 100644 --- a/homeassistant/components/http/ban.py +++ b/homeassistant/components/http/ban.py @@ -18,19 +18,19 @@ from .const import KEY_REAL_IP _LOGGER = logging.getLogger(__name__) -KEY_BANNED_IPS = 'ha_banned_ips' -KEY_FAILED_LOGIN_ATTEMPTS = 'ha_failed_login_attempts' -KEY_LOGIN_THRESHOLD = 'ha_login_threshold' +KEY_BANNED_IPS = "ha_banned_ips" +KEY_FAILED_LOGIN_ATTEMPTS = "ha_failed_login_attempts" +KEY_LOGIN_THRESHOLD = "ha_login_threshold" -NOTIFICATION_ID_BAN = 'ip-ban' -NOTIFICATION_ID_LOGIN = 'http-login' +NOTIFICATION_ID_BAN = "ip-ban" +NOTIFICATION_ID_LOGIN = "http-login" -IP_BANS_FILE = 'ip_bans.yaml' -ATTR_BANNED_AT = 'banned_at' +IP_BANS_FILE = "ip_bans.yaml" +ATTR_BANNED_AT = "banned_at" -SCHEMA_IP_BAN_ENTRY = vol.Schema({ - vol.Optional('banned_at'): vol.Any(None, cv.datetime) -}) +SCHEMA_IP_BAN_ENTRY = vol.Schema( + {vol.Optional("banned_at"): vol.Any(None, cv.datetime)} +) @callback @@ -43,7 +43,8 @@ def setup_bans(hass, app, login_threshold): async def ban_startup(app): """Initialize bans when app starts up.""" app[KEY_BANNED_IPS] = await async_load_ip_bans_config( - hass, hass.config.path(IP_BANS_FILE)) + hass, hass.config.path(IP_BANS_FILE) + ) app.on_startup.append(ban_startup) @@ -57,8 +58,9 @@ async def ban_middleware(request, handler): # Verify if IP is not banned ip_address_ = request[KEY_REAL_IP] - is_banned = any(ip_ban.ip_address == ip_address_ - for ip_ban in request.app[KEY_BANNED_IPS]) + is_banned = any( + ip_ban.ip_address == ip_address_ for ip_ban in request.app[KEY_BANNED_IPS] + ) if is_banned: raise HTTPForbidden() @@ -72,12 +74,14 @@ async def ban_middleware(request, handler): def log_invalid_auth(func): """Decorate function to handle invalid auth or failed login attempts.""" + async def handle_req(view, request, *args, **kwargs): """Try to log failed login attempts if response status >= 400.""" resp = await func(view, request, *args, **kwargs) if resp.status >= 400: await process_wrong_login(request) return resp + return handle_req @@ -89,35 +93,40 @@ async def process_wrong_login(request): """ remote_addr = request[KEY_REAL_IP] - msg = ('Login attempt or request with invalid authentication ' - 'from {}'.format(remote_addr)) + msg = "Login attempt or request with invalid authentication " "from {}".format( + remote_addr + ) _LOGGER.warning(msg) - hass = request.app['hass'] + hass = request.app["hass"] hass.components.persistent_notification.async_create( - msg, 'Login attempt failed', NOTIFICATION_ID_LOGIN) + msg, "Login attempt failed", NOTIFICATION_ID_LOGIN + ) # Check if ban middleware is loaded - if (KEY_BANNED_IPS not in request.app or - request.app[KEY_LOGIN_THRESHOLD] < 1): + if KEY_BANNED_IPS not in request.app or request.app[KEY_LOGIN_THRESHOLD] < 1: return request.app[KEY_FAILED_LOGIN_ATTEMPTS][remote_addr] += 1 - if (request.app[KEY_FAILED_LOGIN_ATTEMPTS][remote_addr] >= - request.app[KEY_LOGIN_THRESHOLD]): + if ( + request.app[KEY_FAILED_LOGIN_ATTEMPTS][remote_addr] + >= request.app[KEY_LOGIN_THRESHOLD] + ): new_ban = IpBan(remote_addr) request.app[KEY_BANNED_IPS].append(new_ban) await hass.async_add_job( - update_ip_bans_config, hass.config.path(IP_BANS_FILE), new_ban) + update_ip_bans_config, hass.config.path(IP_BANS_FILE), new_ban + ) - _LOGGER.warning( - "Banned IP %s for too many login attempts", remote_addr) + _LOGGER.warning("Banned IP %s for too many login attempts", remote_addr) hass.components.persistent_notification.async_create( - 'Too many login attempts from {}'.format(remote_addr), - 'Banning IP address', NOTIFICATION_ID_BAN) + "Too many login attempts from {}".format(remote_addr), + "Banning IP address", + NOTIFICATION_ID_BAN, + ) async def process_success_login(request): @@ -130,14 +139,16 @@ async def process_success_login(request): remote_addr = request[KEY_REAL_IP] # Check if ban middleware is loaded - if (KEY_BANNED_IPS not in request.app or - request.app[KEY_LOGIN_THRESHOLD] < 1): + if KEY_BANNED_IPS not in request.app or request.app[KEY_LOGIN_THRESHOLD] < 1: return - if remote_addr in request.app[KEY_FAILED_LOGIN_ATTEMPTS] and \ - request.app[KEY_FAILED_LOGIN_ATTEMPTS][remote_addr] > 0: - _LOGGER.debug('Login success, reset failed login attempts counter' - ' from %s', remote_addr) + if ( + remote_addr in request.app[KEY_FAILED_LOGIN_ATTEMPTS] + and request.app[KEY_FAILED_LOGIN_ATTEMPTS][remote_addr] > 0 + ): + _LOGGER.debug( + "Login success, reset failed login attempts counter" " from %s", remote_addr + ) request.app[KEY_FAILED_LOGIN_ATTEMPTS].pop(remote_addr) @@ -159,13 +170,13 @@ async def async_load_ip_bans_config(hass: HomeAssistant, path: str): except FileNotFoundError: return ip_list except HomeAssistantError as err: - _LOGGER.error('Unable to load %s: %s', path, str(err)) + _LOGGER.error("Unable to load %s: %s", path, str(err)) return ip_list for ip_ban, ip_info in list_.items(): try: ip_info = SCHEMA_IP_BAN_ENTRY(ip_info) - ip_list.append(IpBan(ip_ban, ip_info['banned_at'])) + ip_list.append(IpBan(ip_ban, ip_info["banned_at"])) except vol.Invalid as err: _LOGGER.error("Failed to load IP ban %s: %s", ip_info, err) continue @@ -175,9 +186,11 @@ async def async_load_ip_bans_config(hass: HomeAssistant, path: str): def update_ip_bans_config(path: str, ip_ban: IpBan): """Update config file with new banned IP address.""" - with open(path, 'a') as out: - ip_ = {str(ip_ban.ip_address): { - ATTR_BANNED_AT: ip_ban.banned_at.strftime("%Y-%m-%dT%H:%M:%S") - }} - out.write('\n') + with open(path, "a") as out: + ip_ = { + str(ip_ban.ip_address): { + ATTR_BANNED_AT: ip_ban.banned_at.strftime("%Y-%m-%dT%H:%M:%S") + } + } + out.write("\n") out.write(dump(ip_)) diff --git a/homeassistant/components/http/const.py b/homeassistant/components/http/const.py index f26220e63d1..9392e790d62 100644 --- a/homeassistant/components/http/const.py +++ b/homeassistant/components/http/const.py @@ -1,5 +1,5 @@ """HTTP specific constants.""" -KEY_AUTHENTICATED = 'ha_authenticated' -KEY_HASS = 'hass' -KEY_HASS_USER = 'hass_user' -KEY_REAL_IP = 'ha_real_ip' +KEY_AUTHENTICATED = "ha_authenticated" +KEY_HASS = "hass" +KEY_HASS_USER = "hass_user" +KEY_REAL_IP = "ha_real_ip" diff --git a/homeassistant/components/http/cors.py b/homeassistant/components/http/cors.py index 880cc47ac0d..5c24ecbebed 100644 --- a/homeassistant/components/http/cors.py +++ b/homeassistant/components/http/cors.py @@ -2,13 +2,17 @@ from aiohttp.web_urldispatcher import Resource, ResourceRoute, StaticResource from aiohttp.hdrs import ACCEPT, CONTENT_TYPE, ORIGIN, AUTHORIZATION -from homeassistant.const import ( - HTTP_HEADER_HA_AUTH, HTTP_HEADER_X_REQUESTED_WITH) +from homeassistant.const import HTTP_HEADER_HA_AUTH, HTTP_HEADER_X_REQUESTED_WITH from homeassistant.core import callback ALLOWED_CORS_HEADERS = [ - ORIGIN, ACCEPT, HTTP_HEADER_X_REQUESTED_WITH, CONTENT_TYPE, - HTTP_HEADER_HA_AUTH, AUTHORIZATION] + ORIGIN, + ACCEPT, + HTTP_HEADER_X_REQUESTED_WITH, + CONTENT_TYPE, + HTTP_HEADER_HA_AUTH, + AUTHORIZATION, +] VALID_CORS_TYPES = (Resource, ResourceRoute, StaticResource) @@ -17,18 +21,21 @@ def setup_cors(app, origins): """Set up CORS.""" import aiohttp_cors - cors = aiohttp_cors.setup(app, defaults={ - host: aiohttp_cors.ResourceOptions( - allow_headers=ALLOWED_CORS_HEADERS, - allow_methods='*', - ) for host in origins - }) + cors = aiohttp_cors.setup( + app, + defaults={ + host: aiohttp_cors.ResourceOptions( + allow_headers=ALLOWED_CORS_HEADERS, allow_methods="*" + ) + for host in origins + }, + ) cors_added = set() def _allow_cors(route, config=None): """Allow CORS on a route.""" - if hasattr(route, 'resource'): + if hasattr(route, "resource"): path = route.resource else: path = route @@ -44,12 +51,14 @@ def setup_cors(app, origins): cors.add(route, config) cors_added.add(path) - app['allow_cors'] = lambda route: _allow_cors(route, { - '*': aiohttp_cors.ResourceOptions( - allow_headers=ALLOWED_CORS_HEADERS, - allow_methods='*', - ) - }) + app["allow_cors"] = lambda route: _allow_cors( + route, + { + "*": aiohttp_cors.ResourceOptions( + allow_headers=ALLOWED_CORS_HEADERS, allow_methods="*" + ) + }, + ) if not origins: return diff --git a/homeassistant/components/http/data_validator.py b/homeassistant/components/http/data_validator.py index 98686e5cabd..8d6ac0b1ceb 100644 --- a/homeassistant/components/http/data_validator.py +++ b/homeassistant/components/http/data_validator.py @@ -23,6 +23,7 @@ class RequestDataValidator: def __call__(self, method): """Decorate a function.""" + @wraps(method) async def wrapper(view, request, *args, **kwargs): """Wrap a request handler with data validation.""" @@ -30,18 +31,18 @@ class RequestDataValidator: try: data = await request.json() except ValueError: - if not self._allow_empty or \ - (await request.content.read()) != b'': - _LOGGER.error('Invalid JSON received.') - return view.json_message('Invalid JSON.', 400) + if not self._allow_empty or (await request.content.read()) != b"": + _LOGGER.error("Invalid JSON received.") + return view.json_message("Invalid JSON.", 400) data = {} try: - kwargs['data'] = self._schema(data) + kwargs["data"] = self._schema(data) except vol.Invalid as err: - _LOGGER.error('Data does not match schema: %s', err) + _LOGGER.error("Data does not match schema: %s", err) return view.json_message( - 'Message format incorrect: {}'.format(err), 400) + "Message format incorrect: {}".format(err), 400 + ) result = await method(view, request, *args, **kwargs) return result diff --git a/homeassistant/components/http/real_ip.py b/homeassistant/components/http/real_ip.py index 9bbf30bd9d1..c38e5d0b592 100644 --- a/homeassistant/components/http/real_ip.py +++ b/homeassistant/components/http/real_ip.py @@ -12,21 +12,25 @@ from .const import KEY_REAL_IP @callback def setup_real_ip(app, use_x_forwarded_for, trusted_proxies): """Create IP Ban middleware for the app.""" + @middleware async def real_ip_middleware(request, handler): """Real IP middleware.""" - connected_ip = ip_address( - request.transport.get_extra_info('peername')[0]) + connected_ip = ip_address(request.transport.get_extra_info("peername")[0]) request[KEY_REAL_IP] = connected_ip # Only use the XFF header if enabled, present, and from a trusted proxy try: - if (use_x_forwarded_for and - X_FORWARDED_FOR in request.headers and - any(connected_ip in trusted_proxy - for trusted_proxy in trusted_proxies)): + if ( + use_x_forwarded_for + and X_FORWARDED_FOR in request.headers + and any( + connected_ip in trusted_proxy for trusted_proxy in trusted_proxies + ) + ): request[KEY_REAL_IP] = ip_address( - request.headers.get(X_FORWARDED_FOR).split(', ')[-1]) + request.headers.get(X_FORWARDED_FOR).split(", ")[-1] + ) except ValueError: pass diff --git a/homeassistant/components/http/static.py b/homeassistant/components/http/static.py index 4fac9bf1ae9..f78ce81d884 100644 --- a/homeassistant/components/http/static.py +++ b/homeassistant/components/http/static.py @@ -16,7 +16,7 @@ class CachingStaticResource(StaticResource): """Static Resource handler that will add cache headers.""" async def _handle(self, request): - rel_url = request.match_info['filename'] + rel_url = request.match_info["filename"] try: filename = Path(rel_url) if filename.anchor: @@ -40,5 +40,6 @@ class CachingStaticResource(StaticResource): return await super()._handle(request) if filepath.is_file(): return FileResponse( - filepath, chunk_size=self._chunk_size, headers=CACHE_HEADERS) + filepath, chunk_size=self._chunk_size, headers=CACHE_HEADERS + ) raise HTTPNotFound diff --git a/homeassistant/components/http/view.py b/homeassistant/components/http/view.py index ea9ca6ac31f..35e74b7c2c0 100644 --- a/homeassistant/components/http/view.py +++ b/homeassistant/components/http/view.py @@ -5,7 +5,10 @@ import logging from aiohttp import web from aiohttp.web_exceptions import ( - HTTPBadRequest, HTTPInternalServerError, HTTPUnauthorized) + HTTPBadRequest, + HTTPInternalServerError, + HTTPUnauthorized, +) import voluptuous as vol from homeassistant import exceptions @@ -31,7 +34,7 @@ class HomeAssistantView: # pylint: disable=no-self-use def context(self, request): """Generate a context from a request.""" - user = request.get('hass_user') + user = request.get("hass_user") if user is None: return Context() @@ -42,32 +45,33 @@ class HomeAssistantView: try: msg = json.dumps( result, sort_keys=True, cls=JSONEncoder, allow_nan=False - ).encode('UTF-8') + ).encode("UTF-8") except (ValueError, TypeError) as err: - _LOGGER.error('Unable to serialize to JSON: %s\n%s', err, result) + _LOGGER.error("Unable to serialize to JSON: %s\n%s", err, result) raise HTTPInternalServerError response = web.Response( - body=msg, content_type=CONTENT_TYPE_JSON, status=status_code, - headers=headers) + body=msg, + content_type=CONTENT_TYPE_JSON, + status=status_code, + headers=headers, + ) response.enable_compression() return response - def json_message(self, message, status_code=200, message_code=None, - headers=None): + def json_message(self, message, status_code=200, message_code=None, headers=None): """Return a JSON message response.""" - data = {'message': message} + data = {"message": message} if message_code is not None: - data['code'] = message_code + data["code"] = message_code return self.json(data, status_code, headers=headers) def register(self, app, router): """Register the view with a router.""" - assert self.url is not None, 'No url set for view' + assert self.url is not None, "No url set for view" urls = [self.url] + self.extra_urls routes = [] - for method in ('get', 'post', 'delete', 'put', 'patch', 'head', - 'options'): + for method in ("get", "post", "delete", "put", "patch", "head", "options"): handler = getattr(self, method, None) if not handler: @@ -82,13 +86,14 @@ class HomeAssistantView: return for route in routes: - app['allow_cors'](route) + app["allow_cors"](route) def request_handler_factory(view, handler): """Wrap the handler classes.""" - assert asyncio.iscoroutinefunction(handler) or is_callback(handler), \ - "Handler should be a coroutine or a callback." + assert asyncio.iscoroutinefunction(handler) or is_callback( + handler + ), "Handler should be a coroutine or a callback." async def handle(request): """Handle incoming request.""" @@ -99,14 +104,18 @@ def request_handler_factory(view, handler): if view.requires_auth: if authenticated: - if 'deprecate_warning_message' in request: - _LOGGER.warning(request['deprecate_warning_message']) + if "deprecate_warning_message" in request: + _LOGGER.warning(request["deprecate_warning_message"]) await process_success_login(request) else: raise HTTPUnauthorized() - _LOGGER.debug('Serving %s to %s (auth: %s)', - request.path, request.get(KEY_REAL_IP), authenticated) + _LOGGER.debug( + "Serving %s to %s (auth: %s)", + request.path, + request.get(KEY_REAL_IP), + authenticated, + ) try: result = handler(request, **request.match_info) @@ -130,12 +139,13 @@ def request_handler_factory(view, handler): result, status_code = result if isinstance(result, str): - result = result.encode('utf-8') + result = result.encode("utf-8") elif result is None: - result = b'' + result = b"" elif not isinstance(result, bytes): - assert False, ('Result should be None, string, bytes or Response. ' - 'Got: {}').format(result) + assert False, ( + "Result should be None, string, bytes or Response. " "Got: {}" + ).format(result) return web.Response(body=result, status=status_code) diff --git a/homeassistant/components/htu21d/sensor.py b/homeassistant/components/htu21d/sensor.py index 01c2b0399b9..c2223720eb5 100644 --- a/homeassistant/components/htu21d/sensor.py +++ b/homeassistant/components/htu21d/sensor.py @@ -14,24 +14,25 @@ from homeassistant.util.temperature import celsius_to_fahrenheit _LOGGER = logging.getLogger(__name__) -CONF_I2C_BUS = 'i2c_bus' +CONF_I2C_BUS = "i2c_bus" DEFAULT_I2C_BUS = 1 MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=5) -DEFAULT_NAME = 'HTU21D Sensor' +DEFAULT_NAME = "HTU21D Sensor" -SENSOR_TEMPERATURE = 'temperature' -SENSOR_HUMIDITY = 'humidity' +SENSOR_TEMPERATURE = "temperature" +SENSOR_HUMIDITY = "humidity" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_I2C_BUS, default=DEFAULT_I2C_BUS): vol.Coerce(int), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_I2C_BUS, default=DEFAULT_I2C_BUS): vol.Coerce(int), + } +) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the HTU21D sensor.""" import smbus # pylint: disable=import-error from i2csense.htu21d import HTU21D # pylint: disable=import-error @@ -41,17 +42,17 @@ async def async_setup_platform(hass, config, async_add_entities, temp_unit = hass.config.units.temperature_unit bus = smbus.SMBus(config.get(CONF_I2C_BUS)) - sensor = await hass.async_add_job( - partial(HTU21D, bus, logger=_LOGGER) - ) + sensor = await hass.async_add_job(partial(HTU21D, bus, logger=_LOGGER)) if not sensor.sample_ok: _LOGGER.error("HTU21D sensor not detected in bus %s", bus_number) return False sensor_handler = await hass.async_add_job(HTU21DHandler, sensor) - dev = [HTU21DSensor(sensor_handler, name, SENSOR_TEMPERATURE, temp_unit), - HTU21DSensor(sensor_handler, name, SENSOR_HUMIDITY, '%')] + dev = [ + HTU21DSensor(sensor_handler, name, SENSOR_TEMPERATURE, temp_unit), + HTU21DSensor(sensor_handler, name, SENSOR_HUMIDITY, "%"), + ] async_add_entities(dev) @@ -75,7 +76,7 @@ class HTU21DSensor(Entity): def __init__(self, htu21d_client, name, variable, unit): """Initialize the sensor.""" - self._name = '{}_{}'.format(name, variable) + self._name = "{}_{}".format(name, variable) self._variable = variable self._unit_of_measurement = unit self._client = htu21d_client diff --git a/homeassistant/components/huawei_lte/__init__.py b/homeassistant/components/huawei_lte/__init__.py index ca2b7e6805c..c68231ba0e4 100644 --- a/homeassistant/components/huawei_lte/__init__.py +++ b/homeassistant/components/huawei_lte/__init__.py @@ -12,7 +12,10 @@ from huawei_lte_api.Client import Client from huawei_lte_api.exceptions import ResponseErrorNotSupportedException from homeassistant.const import ( - CONF_URL, CONF_USERNAME, CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP, + CONF_URL, + CONF_USERNAME, + CONF_PASSWORD, + EVENT_HOMEASSISTANT_STOP, ) from homeassistant.helpers import config_validation as cv from homeassistant.util import Throttle @@ -21,20 +24,30 @@ _LOGGER = logging.getLogger(__name__) # dicttoxml (used by huawei-lte-api) has uselessly verbose INFO level. # https://github.com/quandyfactory/dicttoxml/issues/60 -logging.getLogger('dicttoxml').setLevel(logging.WARNING) +logging.getLogger("dicttoxml").setLevel(logging.WARNING) MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=10) -DOMAIN = 'huawei_lte' -DATA_KEY = 'huawei_lte' +DOMAIN = "huawei_lte" +DATA_KEY = "huawei_lte" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.All(cv.ensure_list, [vol.Schema({ - vol.Required(CONF_URL): cv.url, - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - })]) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.All( + cv.ensure_list, + [ + vol.Schema( + { + vol.Required(CONF_URL): cv.url, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + } + ) + ], + ) + }, + extra=vol.ALLOW_EXTRA, +) @attr.s @@ -91,16 +104,16 @@ class RouterData: try: setattr(self, path, func()) except ResponseErrorNotSupportedException as ex: - _LOGGER.warning( - "%s not supported by device", path, exc_info=ex) + _LOGGER.warning("%s not supported by device", path, exc_info=ex) self._subscriptions.discard(path) finally: _LOGGER.debug("%s=%s", path, getattr(self, path)) get_data("device_information", self.client.device.information) get_data("device_signal", self.client.device.signal) - get_data("monitoring_traffic_statistics", - self.client.monitoring.traffic_statistics) + get_data( + "monitoring_traffic_statistics", self.client.monitoring.traffic_statistics + ) get_data("wlan_host_list", self.client.wlan.host_list) @@ -135,11 +148,7 @@ def _setup_lte(hass, lte_config) -> None: username = lte_config[CONF_USERNAME] password = lte_config[CONF_PASSWORD] - connection = AuthorizedConnection( - url, - username=username, - password=password, - ) + connection = AuthorizedConnection(url, username=username, password=password) client = Client(connection) data = RouterData(client) diff --git a/homeassistant/components/huawei_lte/device_tracker.py b/homeassistant/components/huawei_lte/device_tracker.py index 552bfb90703..878a819aaae 100644 --- a/homeassistant/components/huawei_lte/device_tracker.py +++ b/homeassistant/components/huawei_lte/device_tracker.py @@ -5,15 +5,11 @@ import attr import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.device_tracker import ( - PLATFORM_SCHEMA, DeviceScanner, -) +from homeassistant.components.device_tracker import PLATFORM_SCHEMA, DeviceScanner from homeassistant.const import CONF_URL from . import DATA_KEY, RouterData -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_URL): cv.url, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({vol.Optional(CONF_URL): cv.url}) HOSTS_PATH = "wlan_host_list.Hosts" diff --git a/homeassistant/components/huawei_lte/notify.py b/homeassistant/components/huawei_lte/notify.py index 2222c1333dd..31804f722c6 100644 --- a/homeassistant/components/huawei_lte/notify.py +++ b/homeassistant/components/huawei_lte/notify.py @@ -5,7 +5,10 @@ import voluptuous as vol import attr from homeassistant.components.notify import ( - BaseNotificationService, ATTR_TARGET, PLATFORM_SCHEMA) + BaseNotificationService, + ATTR_TARGET, + PLATFORM_SCHEMA, +) from homeassistant.const import CONF_RECIPIENT, CONF_URL import homeassistant.helpers.config_validation as cv @@ -13,10 +16,12 @@ from . import DATA_KEY _LOGGER = logging.getLogger(__name__) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_URL): cv.url, - vol.Required(CONF_RECIPIENT): vol.All(cv.ensure_list, [cv.string]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_URL): cv.url, + vol.Required(CONF_RECIPIENT): vol.All(cv.ensure_list, [cv.string]), + } +) async def async_get_service(hass, config, discovery_info=None): @@ -45,8 +50,7 @@ class HuaweiLteSmsNotificationService(BaseNotificationService): return try: - resp = data.client.sms.send_sms( - phone_numbers=targets, message=message) + resp = data.client.sms.send_sms(phone_numbers=targets, message=message) _LOGGER.debug("Sent to %s: %s", targets, resp) except ResponseErrorException as ex: _LOGGER.error("Could not send to %s: %s", targets, ex) diff --git a/homeassistant/components/huawei_lte/sensor.py b/homeassistant/components/huawei_lte/sensor.py index 4686ab16854..02ccff82c52 100644 --- a/homeassistant/components/huawei_lte/sensor.py +++ b/homeassistant/components/huawei_lte/sensor.py @@ -6,11 +6,11 @@ from typing import Optional import attr import voluptuous as vol -from homeassistant.const import ( - CONF_URL, CONF_MONITORED_CONDITIONS, STATE_UNKNOWN, -) +from homeassistant.const import CONF_URL, CONF_MONITORED_CONDITIONS, STATE_UNKNOWN from homeassistant.components.sensor import ( - PLATFORM_SCHEMA, DEVICE_CLASS_SIGNAL_STRENGTH) + PLATFORM_SCHEMA, + DEVICE_CLASS_SIGNAL_STRENGTH, +) from homeassistant.helpers.entity import Entity import homeassistant.helpers.config_validation as cv @@ -18,8 +18,8 @@ from . import DATA_KEY, RouterData _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME_TEMPLATE = 'Huawei {} {}' -DEFAULT_DEVICE_NAME = 'LTE' +DEFAULT_NAME_TEMPLATE = "Huawei {} {}" +DEFAULT_DEVICE_NAME = "LTE" DEFAULT_SENSORS = [ "device_information.WanIPAddress", @@ -30,88 +30,78 @@ DEFAULT_SENSORS = [ ] SENSOR_META = { - "device_information.SoftwareVersion": dict( - name="Software version", - ), - "device_information.WanIPAddress": dict( - name="WAN IP address", - icon="mdi:ip", - ), - "device_information.WanIPv6Address": dict( - name="WAN IPv6 address", - icon="mdi:ip", - ), - "device_signal.band": dict( - name="Band", - ), - "device_signal.cell_id": dict( - name="Cell ID", - ), - "device_signal.lac": dict( - name="LAC", - ), + "device_information.SoftwareVersion": dict(name="Software version"), + "device_information.WanIPAddress": dict(name="WAN IP address", icon="mdi:ip"), + "device_information.WanIPv6Address": dict(name="WAN IPv6 address", icon="mdi:ip"), + "device_signal.band": dict(name="Band"), + "device_signal.cell_id": dict(name="Cell ID"), + "device_signal.lac": dict(name="LAC"), "device_signal.mode": dict( name="Mode", - formatter=lambda x: ({ - '0': '2G', - '2': '3G', - '7': '4G', - }.get(x, 'Unknown'), None), - ), - "device_signal.pci": dict( - name="PCI", + formatter=lambda x: ({"0": "2G", "2": "3G", "7": "4G"}.get(x, "Unknown"), None), ), + "device_signal.pci": dict(name="PCI"), "device_signal.rsrq": dict( name="RSRQ", device_class=DEVICE_CLASS_SIGNAL_STRENGTH, # http://www.lte-anbieter.info/technik/rsrq.php - icon=lambda x: - (x is None or x < -11) and "mdi:signal-cellular-outline" - or x < -8 and "mdi:signal-cellular-1" - or x < -5 and "mdi:signal-cellular-2" - or "mdi:signal-cellular-3" + icon=lambda x: (x is None or x < -11) + and "mdi:signal-cellular-outline" + or x < -8 + and "mdi:signal-cellular-1" + or x < -5 + and "mdi:signal-cellular-2" + or "mdi:signal-cellular-3", ), "device_signal.rsrp": dict( name="RSRP", device_class=DEVICE_CLASS_SIGNAL_STRENGTH, # http://www.lte-anbieter.info/technik/rsrp.php - icon=lambda x: - (x is None or x < -110) and "mdi:signal-cellular-outline" - or x < -95 and "mdi:signal-cellular-1" - or x < -80 and "mdi:signal-cellular-2" - or "mdi:signal-cellular-3" + icon=lambda x: (x is None or x < -110) + and "mdi:signal-cellular-outline" + or x < -95 + and "mdi:signal-cellular-1" + or x < -80 + and "mdi:signal-cellular-2" + or "mdi:signal-cellular-3", ), "device_signal.rssi": dict( name="RSSI", device_class=DEVICE_CLASS_SIGNAL_STRENGTH, # https://eyesaas.com/wi-fi-signal-strength/ - icon=lambda x: - (x is None or x < -80) and "mdi:signal-cellular-outline" - or x < -70 and "mdi:signal-cellular-1" - or x < -60 and "mdi:signal-cellular-2" - or "mdi:signal-cellular-3" + icon=lambda x: (x is None or x < -80) + and "mdi:signal-cellular-outline" + or x < -70 + and "mdi:signal-cellular-1" + or x < -60 + and "mdi:signal-cellular-2" + or "mdi:signal-cellular-3", ), "device_signal.sinr": dict( name="SINR", device_class=DEVICE_CLASS_SIGNAL_STRENGTH, # http://www.lte-anbieter.info/technik/sinr.php - icon=lambda x: - (x is None or x < 0) and "mdi:signal-cellular-outline" - or x < 5 and "mdi:signal-cellular-1" - or x < 10 and "mdi:signal-cellular-2" - or "mdi:signal-cellular-3" + icon=lambda x: (x is None or x < 0) + and "mdi:signal-cellular-outline" + or x < 5 + and "mdi:signal-cellular-1" + or x < 10 + and "mdi:signal-cellular-2" + or "mdi:signal-cellular-3", ), } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_URL): cv.url, - vol.Optional( - CONF_MONITORED_CONDITIONS, default=DEFAULT_SENSORS): cv.ensure_list, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_URL): cv.url, + vol.Optional( + CONF_MONITORED_CONDITIONS, default=DEFAULT_SENSORS + ): cv.ensure_list, + } +) -def setup_platform( - hass, config, add_entities, discovery_info): +def setup_platform(hass, config, add_entities, discovery_info): """Set up Huawei LTE sensor devices.""" data = hass.data[DATA_KEY].get_data(config) sensors = [] @@ -129,8 +119,7 @@ def format_default(value): unit = None if value is not None: # Clean up value and infer unit, e.g. -71dBm, 15 dB - match = re.match( - r"(?P.+?)\s*(?P[a-zA-Z]+)\s*$", str(value)) + match = re.match(r"(?P.+?)\s*(?P[a-zA-Z]+)\s*$", str(value)) if match: try: value = float(match.group("value")) @@ -155,8 +144,7 @@ class HuaweiLteSensor(Entity): def unique_id(self) -> str: """Return unique ID for sensor.""" return "{}_{}".format( - self.data["device_information.SerialNumber"], - ".".join(self.path), + self.data["device_information.SerialNumber"], ".".join(self.path) ) @property @@ -167,8 +155,7 @@ class HuaweiLteSensor(Entity): except KeyError: dname = None vname = self.meta.get("name", self.path) - return DEFAULT_NAME_TEMPLATE.format( - dname or DEFAULT_DEVICE_NAME, vname) + return DEFAULT_NAME_TEMPLATE.format(dname or DEFAULT_DEVICE_NAME, vname) @property def state(self): diff --git a/homeassistant/components/huawei_router/device_tracker.py b/homeassistant/components/huawei_router/device_tracker.py index 88e2a57a579..08b7c9ec859 100644 --- a/homeassistant/components/huawei_router/device_tracker.py +++ b/homeassistant/components/huawei_router/device_tracker.py @@ -9,16 +9,21 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.device_tracker import ( - DOMAIN, PLATFORM_SCHEMA, DeviceScanner) + DOMAIN, + PLATFORM_SCHEMA, + DeviceScanner, +) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME _LOGGER = logging.getLogger(__name__) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_USERNAME): cv.string -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string, + } +) def get_scanner(hass, config): @@ -28,14 +33,14 @@ def get_scanner(hass, config): return scanner -Device = namedtuple('Device', ['name', 'ip', 'mac', 'state']) +Device = namedtuple("Device", ["name", "ip", "mac", "state"]) class HuaweiDeviceScanner(DeviceScanner): """This class queries a router running HUAWEI firmware.""" - ARRAY_REGEX = re.compile(r'var UserDevinfo = new Array\((.*)null\);') - DEVICE_REGEX = re.compile(r'new USERDevice\((.*?)\),') + ARRAY_REGEX = re.compile(r"var UserDevinfo = new Array\((.*)null\);") + DEVICE_REGEX = re.compile(r"new USERDevice\((.*?)\),") DEVICE_ATTR_REGEX = re.compile( '"(?P.*?)","(?P.*?)",' '"(?P.*?)","(?P.*?)",' @@ -43,14 +48,15 @@ class HuaweiDeviceScanner(DeviceScanner): '"(?P.*?)","(?P.*?)",' '"(?P