From 7db4eeaf7fec064b8d03acef44746bd0d1f1932c Mon Sep 17 00:00:00 2001 From: Andrew Sayre <6730289+andrewsayre@users.noreply.github.com> Date: Sun, 30 Jun 2019 22:29:21 -0400 Subject: [PATCH] Move SmartThings imports to top (#24878) * Move imports to top * use lib constants * Add missing three_axis mapping --- .../components/smartthings/__init__.py | 9 +- .../components/smartthings/binary_sensor.py | 38 +-- .../components/smartthings/climate.py | 10 +- .../components/smartthings/config_flow.py | 5 +- homeassistant/components/smartthings/cover.py | 8 +- homeassistant/components/smartthings/fan.py | 4 +- homeassistant/components/smartthings/light.py | 6 +- homeassistant/components/smartthings/lock.py | 5 +- .../components/smartthings/sensor.py | 243 +++++++++--------- .../components/smartthings/smartapp.py | 20 +- .../components/smartthings/switch.py | 6 +- tests/components/smartthings/conftest.py | 13 +- 12 files changed, 176 insertions(+), 191 deletions(-) diff --git a/homeassistant/components/smartthings/__init__.py b/homeassistant/components/smartthings/__init__.py index f2f1021ff66..aaeb5578a3a 100644 --- a/homeassistant/components/smartthings/__init__.py +++ b/homeassistant/components/smartthings/__init__.py @@ -6,6 +6,8 @@ from typing import Iterable from aiohttp.client_exceptions import ( ClientConnectionError, ClientResponseError) +from pysmartapp.event import EVENT_TYPE_DEVICE +from pysmartthings import Attribute, Capability, SmartThings from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_ACCESS_TOKEN @@ -60,8 +62,6 @@ async def async_migrate_entry(hass: HomeAssistantType, entry: ConfigEntry): async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): """Initialize config entry which represents an installed SmartApp.""" - from pysmartthings import SmartThings - if not validate_webhook_requirements(hass): _LOGGER.warning("The 'base_url' of the 'http' component must be " "configured and start with 'https://'") @@ -179,8 +179,6 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): async def async_remove_entry( hass: HomeAssistantType, entry: ConfigEntry) -> None: """Perform clean-up when entry is being removed.""" - from pysmartthings import SmartThings - api = SmartThings(async_get_clientsession(hass), entry.data[CONF_ACCESS_TOKEN]) @@ -301,9 +299,6 @@ class DeviceBroker: async def _event_handler(self, req, resp, app): """Broker for incoming events.""" - from pysmartapp.event import EVENT_TYPE_DEVICE - from pysmartthings import Capability, Attribute - # Do not process events received from a different installed app # under the same parent SmartApp (valid use-scenario) if req.installed_app_id != self._installed_app_id: diff --git a/homeassistant/components/smartthings/binary_sensor.py b/homeassistant/components/smartthings/binary_sensor.py index 39ff2999e3a..9a8533d398d 100644 --- a/homeassistant/components/smartthings/binary_sensor.py +++ b/homeassistant/components/smartthings/binary_sensor.py @@ -1,32 +1,34 @@ """Support for binary sensors through the SmartThings cloud API.""" from typing import Optional, Sequence +from pysmartthings import Attribute, Capability + from homeassistant.components.binary_sensor import BinarySensorDevice from . import SmartThingsEntity from .const import DATA_BROKERS, DOMAIN CAPABILITY_TO_ATTRIB = { - 'accelerationSensor': 'acceleration', - 'contactSensor': 'contact', - 'filterStatus': 'filterStatus', - 'motionSensor': 'motion', - 'presenceSensor': 'presence', - 'soundSensor': 'sound', - 'tamperAlert': 'tamper', - 'valve': 'valve', - 'waterSensor': 'water', + Capability.acceleration_sensor: Attribute.acceleration, + Capability.contact_sensor: Attribute.contact, + Capability.filter_status: Attribute.filter_status, + Capability.motion_sensor: Attribute.motion, + Capability.presence_sensor: Attribute.presence, + Capability.sound_sensor: Attribute.sound, + Capability.tamper_alert: Attribute.tamper, + Capability.valve: Attribute.valve, + Capability.water_sensor: Attribute.water, } ATTRIB_TO_CLASS = { - 'acceleration': 'moving', - 'contact': 'opening', - 'filterStatus': 'problem', - 'motion': 'motion', - 'presence': 'presence', - 'sound': 'sound', - 'tamper': 'problem', - 'valve': 'opening', - 'water': 'moisture', + Attribute.acceleration: 'moving', + Attribute.contact: 'opening', + Attribute.filter_status: 'problem', + Attribute.motion: 'motion', + Attribute.presence: 'presence', + Attribute.sound: 'sound', + Attribute.tamper: 'problem', + Attribute.valve: 'opening', + Attribute.water: 'moisture', } diff --git a/homeassistant/components/smartthings/climate.py b/homeassistant/components/smartthings/climate.py index f872e14bc77..c1897e8566b 100644 --- a/homeassistant/components/smartthings/climate.py +++ b/homeassistant/components/smartthings/climate.py @@ -3,6 +3,8 @@ import asyncio import logging from typing import Iterable, Optional, Sequence +from pysmartthings import Attribute, Capability + from homeassistant.components.climate import ( DOMAIN as CLIMATE_DOMAIN, ClimateDevice) from homeassistant.components.climate.const import ( @@ -69,8 +71,6 @@ async def async_setup_platform( async def async_setup_entry(hass, config_entry, async_add_entities): """Add climate entities for a config entry.""" - from pysmartthings import Capability - ac_capabilities = [ Capability.air_conditioner_mode, Capability.air_conditioner_fan_mode, @@ -93,8 +93,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: """Return all capabilities supported if minimum required are present.""" - from pysmartthings import Capability - supported = [ Capability.air_conditioner_mode, Capability.demand_response_load_control, @@ -145,8 +143,6 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice): self._operations = None def _determine_features(self): - from pysmartthings import Capability - flags = SUPPORT_OPERATION_MODE \ | SUPPORT_TARGET_TEMPERATURE \ | SUPPORT_TARGET_TEMPERATURE_LOW \ @@ -301,7 +297,6 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice): @property def temperature_unit(self): """Return the unit of measurement.""" - from pysmartthings import Attribute return UNIT_MAP.get( self._device.status.attributes[Attribute.temperature].unit) @@ -440,6 +435,5 @@ class SmartThingsAirConditioner(SmartThingsEntity, ClimateDevice): @property def temperature_unit(self): """Return the unit of measurement.""" - from pysmartthings import Attribute return UNIT_MAP.get( self._device.status.attributes[Attribute.temperature].unit) diff --git a/homeassistant/components/smartthings/config_flow.py b/homeassistant/components/smartthings/config_flow.py index da9b7c8854e..dc36f754084 100644 --- a/homeassistant/components/smartthings/config_flow.py +++ b/homeassistant/components/smartthings/config_flow.py @@ -2,6 +2,7 @@ import logging from aiohttp import ClientResponseError +from pysmartthings import APIResponseError, AppOAuth, SmartThings import voluptuous as vol from homeassistant import config_entries @@ -54,8 +55,6 @@ class SmartThingsFlowHandler(config_entries.ConfigFlow): async def async_step_user(self, user_input=None): """Get access token and validate it.""" - from pysmartthings import APIResponseError, AppOAuth, SmartThings - errors = {} if user_input is None or CONF_ACCESS_TOKEN not in user_input: return self._show_step_user(errors) @@ -182,8 +181,6 @@ class SmartThingsFlowHandler(config_entries.ConfigFlow): Launched when the user completes the flow or when the SmartApp is installed into an additional location. """ - from pysmartthings import SmartThings - if not self.api: # Launched from the SmartApp install event handler self.api = SmartThings( diff --git a/homeassistant/components/smartthings/cover.py b/homeassistant/components/smartthings/cover.py index 47116ad3dd6..e2e662be598 100644 --- a/homeassistant/components/smartthings/cover.py +++ b/homeassistant/components/smartthings/cover.py @@ -1,6 +1,8 @@ """Support for covers through the SmartThings cloud API.""" from typing import Optional, Sequence +from pysmartthings import Attribute, Capability + from homeassistant.components.cover import ( ATTR_POSITION, DEVICE_CLASS_DOOR, DEVICE_CLASS_GARAGE, DEVICE_CLASS_SHADE, DOMAIN as COVER_DOMAIN, STATE_CLOSED, STATE_CLOSING, STATE_OPEN, @@ -37,8 +39,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: """Return all capabilities supported if minimum required are present.""" - from pysmartthings import Capability - min_required = [ Capability.door_control, Capability.garage_door_control, @@ -58,8 +58,6 @@ class SmartThingsCover(SmartThingsEntity, CoverDevice): def __init__(self, device): """Initialize the cover class.""" - from pysmartthings import Capability - super().__init__(device) self._device_class = None self._state = None @@ -93,8 +91,6 @@ class SmartThingsCover(SmartThingsEntity, CoverDevice): async def async_update(self): """Update the attrs of the cover.""" - from pysmartthings import Attribute, Capability - value = None if Capability.door_control in self._device.capabilities: self._device_class = DEVICE_CLASS_DOOR diff --git a/homeassistant/components/smartthings/fan.py b/homeassistant/components/smartthings/fan.py index befcb3fcb78..843a32ef8c5 100644 --- a/homeassistant/components/smartthings/fan.py +++ b/homeassistant/components/smartthings/fan.py @@ -1,6 +1,8 @@ """Support for fans through the SmartThings cloud API.""" from typing import Optional, Sequence +from pysmartthings import Capability + from homeassistant.components.fan import ( SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, SPEED_OFF, SUPPORT_SET_SPEED, FanEntity) @@ -34,8 +36,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: """Return all capabilities supported if minimum required are present.""" - from pysmartthings import Capability - supported = [Capability.switch, Capability.fan_speed] # Must have switch and fan_speed if all(capability in capabilities for capability in supported): diff --git a/homeassistant/components/smartthings/light.py b/homeassistant/components/smartthings/light.py index 6e609b4b53c..0584e0006b6 100644 --- a/homeassistant/components/smartthings/light.py +++ b/homeassistant/components/smartthings/light.py @@ -2,6 +2,8 @@ import asyncio from typing import Optional, Sequence +from pysmartthings import Capability + from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, ATTR_TRANSITION, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_TRANSITION, @@ -28,8 +30,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: """Return all capabilities supported if minimum required are present.""" - from pysmartthings import Capability - supported = [ Capability.switch, Capability.switch_level, @@ -69,8 +69,6 @@ class SmartThingsLight(SmartThingsEntity, Light): def _determine_features(self): """Get features supported by the device.""" - from pysmartthings.device import Capability - features = 0 # Brightness and transition if Capability.switch_level in self._device.capabilities: diff --git a/homeassistant/components/smartthings/lock.py b/homeassistant/components/smartthings/lock.py index ca2e45114d9..9184120c874 100644 --- a/homeassistant/components/smartthings/lock.py +++ b/homeassistant/components/smartthings/lock.py @@ -1,6 +1,8 @@ """Support for locks through the SmartThings cloud API.""" from typing import Optional, Sequence +from pysmartthings import Attribute, Capability + from homeassistant.components.lock import LockDevice from . import SmartThingsEntity @@ -33,8 +35,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: """Return all capabilities supported if minimum required are present.""" - from pysmartthings import Capability - if Capability.lock in capabilities: return [Capability.lock] return None @@ -61,7 +61,6 @@ class SmartThingsLock(SmartThingsEntity, LockDevice): @property def device_state_attributes(self): """Return device specific state attributes.""" - from pysmartthings import Attribute state_attrs = {} status = self._device.status.attributes[Attribute.lock] if status.value: diff --git a/homeassistant/components/smartthings/sensor.py b/homeassistant/components/smartthings/sensor.py index 4abb3e20c3e..b27956e2cea 100644 --- a/homeassistant/components/smartthings/sensor.py +++ b/homeassistant/components/smartthings/sensor.py @@ -2,10 +2,12 @@ from collections import namedtuple from typing import Optional, Sequence +from pysmartthings import Attribute, Capability + from homeassistant.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, - DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TIMESTAMP, MASS_KILOGRAMS, - ENERGY_KILO_WATT_HOUR, POWER_WATT, TEMP_CELSIUS, TEMP_FAHRENHEIT) + DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TIMESTAMP, ENERGY_KILO_WATT_HOUR, + MASS_KILOGRAMS, POWER_WATT, TEMP_CELSIUS, TEMP_FAHRENHEIT) from . import SmartThingsEntity from .const import DATA_BROKERS, DOMAIN @@ -13,132 +15,139 @@ from .const import DATA_BROKERS, DOMAIN Map = namedtuple("map", "attribute name default_unit device_class") CAPABILITY_TO_SENSORS = { - 'activityLightingMode': [ - Map('lightingMode', "Activity Lighting Mode", None, None)], - 'airConditionerMode': [ - Map('airConditionerMode', "Air Conditioner Mode", None, None)], - 'airQualitySensor': [ - Map('airQuality', "Air Quality", 'CAQI', None)], - 'alarm': [ - Map('alarm', "Alarm", None, None)], - 'audioVolume': [ - Map('volume', "Volume", "%", None)], - 'battery': [ - Map('battery', "Battery", "%", DEVICE_CLASS_BATTERY)], - 'bodyMassIndexMeasurement': [ - Map('bmiMeasurement', "Body Mass Index", "kg/m^2", None)], - 'bodyWeightMeasurement': [ - Map('bodyWeightMeasurement', "Body Weight", MASS_KILOGRAMS, None)], - 'carbonDioxideMeasurement': [ - Map('carbonDioxide', "Carbon Dioxide Measurement", "ppm", None)], - 'carbonMonoxideDetector': [ - Map('carbonMonoxide', "Carbon Monoxide Detector", None, None)], - 'carbonMonoxideMeasurement': [ - Map('carbonMonoxideLevel', "Carbon Monoxide Measurement", "ppm", + Capability.activity_lighting_mode: [ + Map(Attribute.lighting_mode, "Activity Lighting Mode", None, None)], + Capability.air_conditioner_mode: [ + Map(Attribute.air_conditioner_mode, "Air Conditioner Mode", None, None)], - 'dishwasherOperatingState': [ - Map('machineState', "Dishwasher Machine State", None, None), - Map('dishwasherJobState', "Dishwasher Job State", None, None), - Map('completionTime', "Dishwasher Completion Time", None, + Capability.air_quality_sensor: [ + Map(Attribute.air_quality, "Air Quality", 'CAQI', None)], + Capability.alarm: [ + Map(Attribute.alarm, "Alarm", None, None)], + Capability.audio_volume: [ + Map(Attribute.volume, "Volume", "%", None)], + Capability.battery: [ + Map(Attribute.battery, "Battery", "%", DEVICE_CLASS_BATTERY)], + Capability.body_mass_index_measurement: [ + Map(Attribute.bmi_measurement, "Body Mass Index", "kg/m^2", None)], + Capability.body_weight_measurement: [ + Map(Attribute.body_weight_measurement, "Body Weight", MASS_KILOGRAMS, + None)], + Capability.carbon_dioxide_measurement: [ + Map(Attribute.carbon_dioxide, "Carbon Dioxide Measurement", "ppm", + None)], + Capability.carbon_monoxide_detector: [ + Map(Attribute.carbon_monoxide, "Carbon Monoxide Detector", None, + None)], + Capability.carbon_monoxide_measurement: [ + Map(Attribute.carbon_monoxide_level, "Carbon Monoxide Measurement", + "ppm", None)], + Capability.dishwasher_operating_state: [ + Map(Attribute.machine_state, "Dishwasher Machine State", None, None), + Map(Attribute.dishwasher_job_state, "Dishwasher Job State", None, + None), + Map(Attribute.completion_time, "Dishwasher Completion Time", None, DEVICE_CLASS_TIMESTAMP)], - 'dryerMode': [ - Map('dryerMode', "Dryer Mode", None, None)], - 'dryerOperatingState': [ - Map('machineState', "Dryer Machine State", None, None), - Map('dryerJobState', "Dryer Job State", None, None), - Map('completionTime', "Dryer Completion Time", None, + Capability.dryer_mode: [ + Map(Attribute.dryer_mode, "Dryer Mode", None, None)], + Capability.dryer_operating_state: [ + Map(Attribute.machine_state, "Dryer Machine State", None, None), + Map(Attribute.dryer_job_state, "Dryer Job State", None, None), + Map(Attribute.completion_time, "Dryer Completion Time", None, DEVICE_CLASS_TIMESTAMP)], - 'dustSensor': [ - Map('fineDustLevel', "Fine Dust Level", None, None), - Map('dustLevel', "Dust Level", None, None)], - 'energyMeter': [ - Map('energy', "Energy Meter", ENERGY_KILO_WATT_HOUR, None)], - 'equivalentCarbonDioxideMeasurement': [ - Map('equivalentCarbonDioxideMeasurement', + Capability.dust_sensor: [ + Map(Attribute.fine_dust_level, "Fine Dust Level", None, None), + Map(Attribute.dust_level, "Dust Level", None, None)], + Capability.energy_meter: [ + Map(Attribute.energy, "Energy Meter", ENERGY_KILO_WATT_HOUR, None)], + Capability.equivalent_carbon_dioxide_measurement: [ + Map(Attribute.equivalent_carbon_dioxide_measurement, 'Equivalent Carbon Dioxide Measurement', 'ppm', None)], - 'formaldehydeMeasurement': [ - Map('formaldehydeLevel', 'Formaldehyde Measurement', 'ppm', None)], - 'illuminanceMeasurement': [ - Map('illuminance', "Illuminance", 'lux', DEVICE_CLASS_ILLUMINANCE)], - 'infraredLevel': [ - Map('infraredLevel', "Infrared Level", '%', None)], - 'lock': [ - Map('lock', "Lock", None, None)], - 'mediaInputSource': [ - Map('inputSource', "Media Input Source", None, None)], - 'mediaPlaybackRepeat': [ - Map('playbackRepeatMode', "Media Playback Repeat", None, None)], - 'mediaPlaybackShuffle': [ - Map('playbackShuffle', "Media Playback Shuffle", None, None)], - 'mediaPlayback': [ - Map('playbackStatus', "Media Playback Status", None, None)], - 'odorSensor': [ - Map('odorLevel', "Odor Sensor", None, None)], - 'ovenMode': [ - Map('ovenMode', "Oven Mode", None, None)], - 'ovenOperatingState': [ - Map('machineState', "Oven Machine State", None, None), - Map('ovenJobState', "Oven Job State", None, None), - Map('completionTime', "Oven Completion Time", None, None)], - 'ovenSetpoint': [ - Map('ovenSetpoint', "Oven Set Point", None, None)], - 'powerMeter': [ - Map('power', "Power Meter", POWER_WATT, None)], - 'powerSource': [ - Map('powerSource', "Power Source", None, None)], - 'refrigerationSetpoint': [ - Map('refrigerationSetpoint', "Refrigeration Setpoint", None, + Capability.formaldehyde_measurement: [ + Map(Attribute.formaldehyde_level, 'Formaldehyde Measurement', 'ppm', + None)], + Capability.illuminance_measurement: [ + Map(Attribute.illuminance, "Illuminance", 'lux', + DEVICE_CLASS_ILLUMINANCE)], + Capability.infrared_level: [ + Map(Attribute.infrared_level, "Infrared Level", '%', None)], + Capability.media_input_source: [ + Map(Attribute.input_source, "Media Input Source", None, None)], + Capability.media_playback_repeat: [ + Map(Attribute.playback_repeat_mode, "Media Playback Repeat", None, + None)], + Capability.media_playback_shuffle: [ + Map(Attribute.playback_shuffle, "Media Playback Shuffle", None, None)], + Capability.media_playback: [ + Map(Attribute.playback_status, "Media Playback Status", None, None)], + Capability.odor_sensor: [ + Map(Attribute.odor_level, "Odor Sensor", None, None)], + Capability.oven_mode: [ + Map(Attribute.oven_mode, "Oven Mode", None, None)], + Capability.oven_operating_state: [ + Map(Attribute.machine_state, "Oven Machine State", None, None), + Map(Attribute.oven_job_state, "Oven Job State", None, None), + Map(Attribute.completion_time, "Oven Completion Time", None, None)], + Capability.oven_setpoint: [ + Map(Attribute.oven_setpoint, "Oven Set Point", None, None)], + Capability.power_meter: [ + Map(Attribute.power, "Power Meter", POWER_WATT, None)], + Capability.power_source: [ + Map(Attribute.power_source, "Power Source", None, None)], + Capability.refrigeration_setpoint: [ + Map(Attribute.refrigeration_setpoint, "Refrigeration Setpoint", None, DEVICE_CLASS_TEMPERATURE)], - 'relativeHumidityMeasurement': [ - Map('humidity', "Relative Humidity Measurement", '%', + Capability.relative_humidity_measurement: [ + Map(Attribute.humidity, "Relative Humidity Measurement", '%', DEVICE_CLASS_HUMIDITY)], - 'robotCleanerCleaningMode': [ - Map('robotCleanerCleaningMode', "Robot Cleaner Cleaning Mode", + Capability.robot_cleaner_cleaning_mode: [ + Map(Attribute.robot_cleaner_cleaning_mode, + "Robot Cleaner Cleaning Mode", None, None)], + Capability.robot_cleaner_movement: [ + Map(Attribute.robot_cleaner_movement, "Robot Cleaner Movement", None, + None)], + Capability.robot_cleaner_turbo_mode: [ + Map(Attribute.robot_cleaner_turbo_mode, "Robot Cleaner Turbo Mode", None, None)], - 'robotCleanerMovement': [ - Map('robotCleanerMovement', "Robot Cleaner Movement", None, None)], - 'robotCleanerTurboMode': [ - Map('robotCleanerTurboMode', "Robot Cleaner Turbo Mode", None, None)], - 'signalStrength': [ - Map('lqi', "LQI Signal Strength", None, None), - Map('rssi', "RSSI Signal Strength", None, None)], - 'smokeDetector': [ - Map('smoke', "Smoke Detector", None, None)], - 'temperatureMeasurement': [ - Map('temperature', "Temperature Measurement", None, + Capability.signal_strength: [ + Map(Attribute.lqi, "LQI Signal Strength", None, None), + Map(Attribute.rssi, "RSSI Signal Strength", None, None)], + Capability.smoke_detector: [ + Map(Attribute.smoke, "Smoke Detector", None, None)], + Capability.temperature_measurement: [ + Map(Attribute.temperature, "Temperature Measurement", None, DEVICE_CLASS_TEMPERATURE)], - 'thermostatCoolingSetpoint': [ - Map('coolingSetpoint', "Thermostat Cooling Setpoint", None, + Capability.thermostat_cooling_setpoint: [ + Map(Attribute.cooling_setpoint, "Thermostat Cooling Setpoint", None, DEVICE_CLASS_TEMPERATURE)], - 'thermostatFanMode': [ - Map('thermostatFanMode', "Thermostat Fan Mode", None, None)], - 'thermostatHeatingSetpoint': [ - Map('heatingSetpoint', "Thermostat Heating Setpoint", None, + Capability.thermostat_fan_mode: [ + Map(Attribute.thermostat_fan_mode, "Thermostat Fan Mode", None, None)], + Capability.thermostat_heating_setpoint: [ + Map(Attribute.heating_setpoint, "Thermostat Heating Setpoint", None, DEVICE_CLASS_TEMPERATURE)], - 'thermostatMode': [ - Map('thermostatMode', "Thermostat Mode", None, None)], - 'thermostatOperatingState': [ - Map('thermostatOperatingState', "Thermostat Operating State", + Capability.thermostat_mode: [ + Map(Attribute.thermostat_mode, "Thermostat Mode", None, None)], + Capability.thermostat_operating_state: [ + Map(Attribute.thermostat_operating_state, "Thermostat Operating State", None, None)], - 'thermostatSetpoint': [ - Map('thermostatSetpoint', "Thermostat Setpoint", None, + Capability.thermostat_setpoint: [ + Map(Attribute.thermostat_setpoint, "Thermostat Setpoint", None, DEVICE_CLASS_TEMPERATURE)], - 'threeAxis': [ - Map('threeAxis', "Three Axis", None, None)], - 'tvChannel': [ - Map('tvChannel', "Tv Channel", None, None)], - 'tvocMeasurement': [ - Map('tvocLevel', "Tvoc Measurement", 'ppm', None)], - 'ultravioletIndex': [ - Map('ultravioletIndex', "Ultraviolet Index", None, None)], - 'voltageMeasurement': [ - Map('voltage', "Voltage Measurement", 'V', None)], - 'washerMode': [ - Map('washerMode', "Washer Mode", None, None)], - 'washerOperatingState': [ - Map('machineState', "Washer Machine State", None, None), - Map('washerJobState', "Washer Job State", None, None), - Map('completionTime', "Washer Completion Time", None, + Capability.three_axis: [], + Capability.tv_channel: [ + Map(Attribute.tv_channel, "Tv Channel", None, None)], + Capability.tvoc_measurement: [ + Map(Attribute.tvoc_level, "Tvoc Measurement", 'ppm', None)], + Capability.ultraviolet_index: [ + Map(Attribute.ultraviolet_index, "Ultraviolet Index", None, None)], + Capability.voltage_measurement: [ + Map(Attribute.voltage, "Voltage Measurement", 'V', None)], + Capability.washer_mode: [ + Map(Attribute.washer_mode, "Washer Mode", None, None)], + Capability.washer_operating_state: [ + Map(Attribute.machine_state, "Washer Machine State", None, None), + Map(Attribute.washer_job_state, "Washer Job State", None, None), + Map(Attribute.completion_time, "Washer Completion Time", None, DEVICE_CLASS_TIMESTAMP)] } @@ -158,7 +167,6 @@ async def async_setup_platform( async def async_setup_entry(hass, config_entry, async_add_entities): """Add binary sensors for a config entry.""" - from pysmartthings import Capability broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] sensors = [] for device in broker.devices.values(): @@ -245,7 +253,6 @@ class SmartThingsThreeAxisSensor(SmartThingsEntity): @property def state(self): """Return the state of the sensor.""" - from pysmartthings import Attribute three_axis = self._device.status.attributes[Attribute.three_axis].value try: return three_axis[self._index] diff --git a/homeassistant/components/smartthings/smartapp.py b/homeassistant/components/smartthings/smartapp.py index 68999914d71..02494ae002c 100644 --- a/homeassistant/components/smartthings/smartapp.py +++ b/homeassistant/components/smartthings/smartapp.py @@ -6,6 +6,12 @@ from urllib.parse import urlparse from uuid import uuid4 from aiohttp import web +from pysmartapp import Dispatcher, SmartAppManager +from pysmartapp.const import SETTINGS_APP_ID +from pysmartthings import ( + APP_TYPE_WEBHOOK, CAPABILITIES, CLASSIFICATION_AUTOMATION, App, AppOAuth, + AppSettings, InstalledAppStatus, SmartThings, SourceType, Subscription, + SubscriptionEntity) from homeassistant.components import cloud, webhook from homeassistant.const import CONF_WEBHOOK_ID @@ -43,8 +49,6 @@ async def validate_installed_app(api, installed_app_id: str): Query the API for the installed SmartApp and validate that it is tied to the specified app_id and is in an authorized state. """ - from pysmartthings import InstalledAppStatus - installed_app = await api.installed_app(installed_app_id) if installed_app.installed_app_status != InstalledAppStatus.AUTHORIZED: raise RuntimeWarning("Installed SmartApp instance '{}' ({}) is not " @@ -77,8 +81,6 @@ def get_webhook_url(hass: HomeAssistantType) -> str: def _get_app_template(hass: HomeAssistantType): - from pysmartthings import APP_TYPE_WEBHOOK, CLASSIFICATION_AUTOMATION - endpoint = "at " + hass.config.api.base_url cloudhook_url = hass.data[DOMAIN][CONF_CLOUDHOOK_URL] if cloudhook_url is not None: @@ -98,9 +100,6 @@ def _get_app_template(hass: HomeAssistantType): async def create_app(hass: HomeAssistantType, api): """Create a SmartApp for this instance of hass.""" - from pysmartthings import App, AppOAuth, AppSettings - from pysmartapp.const import SETTINGS_APP_ID - # Create app from template attributes template = _get_app_template(hass) app = App() @@ -170,8 +169,6 @@ async def setup_smartapp_endpoint(hass: HomeAssistantType): SmartApps are an extension point within the SmartThings ecosystem and is used to receive push updates (i.e. device updates) from the cloud. """ - from pysmartapp import Dispatcher, SmartAppManager - data = hass.data.get(DOMAIN) if data: # already setup @@ -264,11 +261,6 @@ async def smartapp_sync_subscriptions( hass: HomeAssistantType, auth_token: str, location_id: str, installed_app_id: str, devices): """Synchronize subscriptions of an installed up.""" - from pysmartthings import ( - CAPABILITIES, SmartThings, SourceType, Subscription, - SubscriptionEntity - ) - api = SmartThings(async_get_clientsession(hass), auth_token) tasks = [] diff --git a/homeassistant/components/smartthings/switch.py b/homeassistant/components/smartthings/switch.py index 2149a87250e..4ebce73b6a2 100644 --- a/homeassistant/components/smartthings/switch.py +++ b/homeassistant/components/smartthings/switch.py @@ -1,6 +1,8 @@ """Support for switches through the SmartThings cloud API.""" from typing import Optional, Sequence +from pysmartthings import Attribute, Capability + from homeassistant.components.switch import SwitchDevice from . import SmartThingsEntity @@ -23,8 +25,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: """Return all capabilities supported if minimum required are present.""" - from pysmartthings import Capability - # Must be able to be turned on/off. if Capability.switch in capabilities: return [Capability.switch, @@ -53,13 +53,11 @@ class SmartThingsSwitch(SmartThingsEntity, SwitchDevice): @property def current_power_w(self): """Return the current power usage in W.""" - from pysmartthings import Attribute return self._device.status.attributes[Attribute.power].value @property def today_energy_kwh(self): """Return the today total energy usage in kWh.""" - from pysmartthings import Attribute return self._device.status.attributes[Attribute.energy].value @property diff --git a/tests/components/smartthings/conftest.py b/tests/components/smartthings/conftest.py index 3f346c9df0d..27299a1efd6 100644 --- a/tests/components/smartthings/conftest.py +++ b/tests/components/smartthings/conftest.py @@ -5,7 +5,8 @@ from uuid import uuid4 from pysmartthings import ( CLASSIFICATION_AUTOMATION, AppEntity, AppOAuthClient, AppSettings, - DeviceEntity, InstalledApp, Location, SceneEntity, Subscription) + DeviceEntity, InstalledApp, Location, SceneEntity, SmartThings, + Subscription) from pysmartthings.api import Api import pytest @@ -23,6 +24,8 @@ from homeassistant.setup import async_setup_component from tests.common import mock_coro +COMPONENT_PREFIX = "homeassistant.components.smartthings." + async def setup_platform(hass, platform: str, *, devices=None, scenes=None): @@ -163,8 +166,12 @@ def smartthings_mock_fixture(locations): return_value=next(location for location in locations if location.location_id == location_id)) - with patch("pysmartthings.SmartThings", autospec=True) as mock: - mock.return_value.location.side_effect = _location + smartthings_mock = Mock(SmartThings) + smartthings_mock.location.side_effect = _location + mock = Mock(return_value=smartthings_mock) + with patch(COMPONENT_PREFIX + "SmartThings", new=mock), \ + patch(COMPONENT_PREFIX + "config_flow.SmartThings", new=mock), \ + patch(COMPONENT_PREFIX + "smartapp.SmartThings", new=mock): yield mock