Add support for Hisense AEH-W4A1 wifi module (AC remote control) (#28641)
* First commit * First working release, but there's a lot to do * Added support for preset_modes * Refined logic * Added translations for config_flow * Updated translations * modified: homeassistant/components/hisense_aehw4a1/climate.py * modified: climate.py * Updated library to latest version * Small changes * Null states when AC off * Minor fixes * Latest updates for TOX * First commit * First working release, but there's a lot to do * new file: requirements_test_all.txt * Added support for preset_modes * Refined logic * Added translations for config_flow * Updated translations * modified: homeassistant/components/hisense_aehw4a1/climate.py * modified: climate.py * Updated library to latest version * Small changes * Null states when AC off * Minor fixes * Latest updates for TOX * new file: requirements_test_all.txt * Fighting with tox * vs Tox round 2 * Isort and updated requirements_test_all.txt * Fighting with lint * Implemented available state * Changed exception type after Travis-ci pylint fails * Support entry in configuration.yaml * Removed commented code * Switched to async * Minor changes * Updated library and fixed pylint errors * Code optimization * Implemented static ip addresses in configuration.yaml * Reverted to existing constant * Corrected pylint wrong-import-order * Recovery from nuke event (messing all while rebase) * Resolved Ci error * Changes for PR * Corrected temp scale for frontend * Added test for config entry from configuration.yaml * Updated dependency * Check on manual config * Imported custom exceptions and modified import config * Optimized * Change based on PR revision * Added logging for failure event on manual config * Tests added but to be corrected * Edited tests * Tests updated to ensure no I/O * Working on tests * Cheanges based on revision for PR * Setting librey exception as direct side_effect in test * Final changes for PR * Redundand on command solved * Improved AC logic
This commit is contained in:
parent
35000848ed
commit
d796053d9f
13 changed files with 671 additions and 0 deletions
|
@ -288,6 +288,7 @@ omit =
|
||||||
homeassistant/components/heatmiser/climate.py
|
homeassistant/components/heatmiser/climate.py
|
||||||
homeassistant/components/hikvision/binary_sensor.py
|
homeassistant/components/hikvision/binary_sensor.py
|
||||||
homeassistant/components/hikvisioncam/switch.py
|
homeassistant/components/hikvisioncam/switch.py
|
||||||
|
homeassistant/components/hisense_aehw4a1/*
|
||||||
homeassistant/components/hitron_coda/device_tracker.py
|
homeassistant/components/hitron_coda/device_tracker.py
|
||||||
homeassistant/components/hive/*
|
homeassistant/components/hive/*
|
||||||
homeassistant/components/hlk_sw16/*
|
homeassistant/components/hlk_sw16/*
|
||||||
|
|
|
@ -128,6 +128,7 @@ homeassistant/components/heos/* @andrewsayre
|
||||||
homeassistant/components/here_travel_time/* @eifinger
|
homeassistant/components/here_travel_time/* @eifinger
|
||||||
homeassistant/components/hikvision/* @mezz64
|
homeassistant/components/hikvision/* @mezz64
|
||||||
homeassistant/components/hikvisioncam/* @fbradyirl
|
homeassistant/components/hikvisioncam/* @fbradyirl
|
||||||
|
homeassistant/components/hisense_aehw4a1/* @bannhead
|
||||||
homeassistant/components/history/* @home-assistant/core
|
homeassistant/components/history/* @home-assistant/core
|
||||||
homeassistant/components/history_graph/* @andrey-git
|
homeassistant/components/history_graph/* @andrey-git
|
||||||
homeassistant/components/hive/* @Rendili @KJonline
|
homeassistant/components/hive/* @Rendili @KJonline
|
||||||
|
|
81
homeassistant/components/hisense_aehw4a1/__init__.py
Normal file
81
homeassistant/components/hisense_aehw4a1/__init__.py
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
"""The Hisense AEH-W4A1 integration."""
|
||||||
|
import ipaddress
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from pyaehw4a1.aehw4a1 import AehW4a1
|
||||||
|
import pyaehw4a1.exceptions
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN
|
||||||
|
from homeassistant.const import CONF_IP_ADDRESS
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def coerce_ip(value):
|
||||||
|
"""Validate that provided value is a valid IP address."""
|
||||||
|
if not value:
|
||||||
|
raise vol.Invalid("Must define an IP address")
|
||||||
|
try:
|
||||||
|
ipaddress.IPv4Network(value)
|
||||||
|
except ValueError:
|
||||||
|
raise vol.Invalid("Not a valid IP address")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
DOMAIN: {
|
||||||
|
CLIMATE_DOMAIN: vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Optional(CONF_IP_ADDRESS, default=[]): vol.All(
|
||||||
|
cv.ensure_list, [vol.All(cv.string, coerce_ip)]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
extra=vol.ALLOW_EXTRA,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup(hass, config):
|
||||||
|
"""Set up the Hisense AEH-W4A1 integration."""
|
||||||
|
conf = config.get(DOMAIN)
|
||||||
|
hass.data[DOMAIN] = {}
|
||||||
|
|
||||||
|
if conf is not None:
|
||||||
|
devices = conf[CONF_IP_ADDRESS][:]
|
||||||
|
for device in devices:
|
||||||
|
try:
|
||||||
|
await AehW4a1(device).check()
|
||||||
|
except pyaehw4a1.exceptions.ConnectionError:
|
||||||
|
conf[CONF_IP_ADDRESS].remove(device)
|
||||||
|
_LOGGER.warning("Hisense AEH-W4A1 at %s not found", device)
|
||||||
|
if conf[CONF_IP_ADDRESS]:
|
||||||
|
hass.data[DOMAIN] = conf
|
||||||
|
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 a config entry for Hisense AEH-W4A1."""
|
||||||
|
hass.async_create_task(
|
||||||
|
hass.config_entries.async_forward_entry_setup(entry, CLIMATE_DOMAIN)
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass, entry):
|
||||||
|
"""Unload a config entry."""
|
||||||
|
return await hass.config_entries.async_forward_entry_unload(entry, CLIMATE_DOMAIN)
|
438
homeassistant/components/hisense_aehw4a1/climate.py
Normal file
438
homeassistant/components/hisense_aehw4a1/climate.py
Normal file
|
@ -0,0 +1,438 @@
|
||||||
|
"""Pyaehw4a1 platform to control of Hisense AEH-W4A1 Climate Devices."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from pyaehw4a1.aehw4a1 import AehW4a1
|
||||||
|
import pyaehw4a1.exceptions
|
||||||
|
|
||||||
|
from homeassistant.components.climate import ClimateDevice
|
||||||
|
from homeassistant.components.climate.const import (
|
||||||
|
FAN_AUTO,
|
||||||
|
FAN_HIGH,
|
||||||
|
FAN_LOW,
|
||||||
|
FAN_MEDIUM,
|
||||||
|
HVAC_MODE_COOL,
|
||||||
|
HVAC_MODE_DRY,
|
||||||
|
HVAC_MODE_FAN_ONLY,
|
||||||
|
HVAC_MODE_HEAT,
|
||||||
|
HVAC_MODE_OFF,
|
||||||
|
PRESET_BOOST,
|
||||||
|
PRESET_ECO,
|
||||||
|
PRESET_NONE,
|
||||||
|
PRESET_SLEEP,
|
||||||
|
SUPPORT_FAN_MODE,
|
||||||
|
SUPPORT_PRESET_MODE,
|
||||||
|
SUPPORT_SWING_MODE,
|
||||||
|
SUPPORT_TARGET_TEMPERATURE,
|
||||||
|
SWING_BOTH,
|
||||||
|
SWING_HORIZONTAL,
|
||||||
|
SWING_OFF,
|
||||||
|
SWING_VERTICAL,
|
||||||
|
)
|
||||||
|
from homeassistant.const import (
|
||||||
|
ATTR_TEMPERATURE,
|
||||||
|
PRECISION_WHOLE,
|
||||||
|
TEMP_CELSIUS,
|
||||||
|
TEMP_FAHRENHEIT,
|
||||||
|
)
|
||||||
|
|
||||||
|
from . import CONF_IP_ADDRESS, DOMAIN
|
||||||
|
|
||||||
|
SUPPORT_FLAGS = (
|
||||||
|
SUPPORT_TARGET_TEMPERATURE
|
||||||
|
| SUPPORT_FAN_MODE
|
||||||
|
| SUPPORT_SWING_MODE
|
||||||
|
| SUPPORT_PRESET_MODE
|
||||||
|
)
|
||||||
|
|
||||||
|
MIN_TEMP_C = 16
|
||||||
|
MAX_TEMP_C = 32
|
||||||
|
|
||||||
|
MIN_TEMP_F = 61
|
||||||
|
MAX_TEMP_F = 90
|
||||||
|
|
||||||
|
HVAC_MODES = [
|
||||||
|
HVAC_MODE_OFF,
|
||||||
|
HVAC_MODE_HEAT,
|
||||||
|
HVAC_MODE_COOL,
|
||||||
|
HVAC_MODE_DRY,
|
||||||
|
HVAC_MODE_FAN_ONLY,
|
||||||
|
]
|
||||||
|
|
||||||
|
FAN_MODES = [
|
||||||
|
"mute",
|
||||||
|
FAN_LOW,
|
||||||
|
FAN_MEDIUM,
|
||||||
|
FAN_HIGH,
|
||||||
|
FAN_AUTO,
|
||||||
|
]
|
||||||
|
|
||||||
|
SWING_MODES = [
|
||||||
|
SWING_OFF,
|
||||||
|
SWING_VERTICAL,
|
||||||
|
SWING_HORIZONTAL,
|
||||||
|
SWING_BOTH,
|
||||||
|
]
|
||||||
|
|
||||||
|
PRESET_MODES = [
|
||||||
|
PRESET_NONE,
|
||||||
|
PRESET_ECO,
|
||||||
|
PRESET_BOOST,
|
||||||
|
PRESET_SLEEP,
|
||||||
|
"sleep_2",
|
||||||
|
"sleep_3",
|
||||||
|
"sleep_4",
|
||||||
|
]
|
||||||
|
|
||||||
|
AC_TO_HA_STATE = {
|
||||||
|
"0001": HVAC_MODE_HEAT,
|
||||||
|
"0010": HVAC_MODE_COOL,
|
||||||
|
"0011": HVAC_MODE_DRY,
|
||||||
|
"0000": HVAC_MODE_FAN_ONLY,
|
||||||
|
}
|
||||||
|
|
||||||
|
HA_STATE_TO_AC = {
|
||||||
|
HVAC_MODE_OFF: "off",
|
||||||
|
HVAC_MODE_HEAT: "mode_heat",
|
||||||
|
HVAC_MODE_COOL: "mode_cool",
|
||||||
|
HVAC_MODE_DRY: "mode_dry",
|
||||||
|
HVAC_MODE_FAN_ONLY: "mode_fan",
|
||||||
|
}
|
||||||
|
|
||||||
|
AC_TO_HA_FAN_MODES = {
|
||||||
|
"00000000": FAN_AUTO, # fan value for heat mode
|
||||||
|
"00000001": FAN_AUTO,
|
||||||
|
"00000010": "mute",
|
||||||
|
"00000100": FAN_LOW,
|
||||||
|
"00000110": FAN_MEDIUM,
|
||||||
|
"00001000": FAN_HIGH,
|
||||||
|
}
|
||||||
|
|
||||||
|
HA_FAN_MODES_TO_AC = {
|
||||||
|
"mute": "speed_mute",
|
||||||
|
FAN_LOW: "speed_low",
|
||||||
|
FAN_MEDIUM: "speed_med",
|
||||||
|
FAN_HIGH: "speed_max",
|
||||||
|
FAN_AUTO: "speed_auto",
|
||||||
|
}
|
||||||
|
|
||||||
|
AC_TO_HA_SWING = {
|
||||||
|
"00": SWING_OFF,
|
||||||
|
"10": SWING_VERTICAL,
|
||||||
|
"01": SWING_HORIZONTAL,
|
||||||
|
"11": SWING_BOTH,
|
||||||
|
}
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _build_entity(device):
|
||||||
|
_LOGGER.debug("Found device at %s", device)
|
||||||
|
return ClimateAehW4a1(device)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
|
"""Set up the AEH-W4A1 climate platform."""
|
||||||
|
# Priority 1: manual config
|
||||||
|
if hass.data[DOMAIN].get(CONF_IP_ADDRESS):
|
||||||
|
devices = hass.data[DOMAIN][CONF_IP_ADDRESS]
|
||||||
|
else:
|
||||||
|
# Priority 2: scanned interfaces
|
||||||
|
devices = await AehW4a1().discovery()
|
||||||
|
|
||||||
|
entities = [_build_entity(device) for device in devices]
|
||||||
|
async_add_entities(entities, True)
|
||||||
|
|
||||||
|
|
||||||
|
class ClimateAehW4a1(ClimateDevice):
|
||||||
|
"""Representation of a Hisense AEH-W4A1 module for climate device."""
|
||||||
|
|
||||||
|
def __init__(self, device):
|
||||||
|
"""Initialize the climate device."""
|
||||||
|
self._unique_id = device
|
||||||
|
self._device = AehW4a1(device)
|
||||||
|
self._hvac_modes = HVAC_MODES
|
||||||
|
self._fan_modes = FAN_MODES
|
||||||
|
self._swing_modes = SWING_MODES
|
||||||
|
self._preset_modes = PRESET_MODES
|
||||||
|
self._available = None
|
||||||
|
self._on = None
|
||||||
|
self._temperature_unit = None
|
||||||
|
self._current_temperature = None
|
||||||
|
self._target_temperature = None
|
||||||
|
self._hvac_mode = None
|
||||||
|
self._fan_mode = None
|
||||||
|
self._swing_mode = None
|
||||||
|
self._preset_mode = None
|
||||||
|
self._previous_state = None
|
||||||
|
|
||||||
|
async def async_update(self):
|
||||||
|
"""Pull state from AEH-W4A1."""
|
||||||
|
try:
|
||||||
|
status = await self._device.command("status_102_0")
|
||||||
|
except pyaehw4a1.exceptions.ConnectionError as library_error:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Unexpected error of %s: %s", self._unique_id, library_error
|
||||||
|
)
|
||||||
|
self._available = False
|
||||||
|
return
|
||||||
|
|
||||||
|
self._available = True
|
||||||
|
|
||||||
|
self._on = status["run_status"]
|
||||||
|
|
||||||
|
if status["temperature_Fahrenheit"] == "0":
|
||||||
|
self._temperature_unit = TEMP_CELSIUS
|
||||||
|
else:
|
||||||
|
self._temperature_unit = TEMP_FAHRENHEIT
|
||||||
|
|
||||||
|
self._current_temperature = int(status["indoor_temperature_status"], 2)
|
||||||
|
|
||||||
|
if self._on == "1":
|
||||||
|
device_mode = status["mode_status"]
|
||||||
|
self._hvac_mode = AC_TO_HA_STATE[device_mode]
|
||||||
|
|
||||||
|
fan_mode = status["wind_status"]
|
||||||
|
self._fan_mode = AC_TO_HA_FAN_MODES[fan_mode]
|
||||||
|
|
||||||
|
swing_mode = f'{status["up_down"]}{status["left_right"]}'
|
||||||
|
self._swing_mode = AC_TO_HA_SWING[swing_mode]
|
||||||
|
|
||||||
|
if self._hvac_mode in (HVAC_MODE_COOL, HVAC_MODE_HEAT):
|
||||||
|
self._target_temperature = int(status["indoor_temperature_setting"], 2)
|
||||||
|
else:
|
||||||
|
self._target_temperature = None
|
||||||
|
|
||||||
|
if status["efficient"] == "1":
|
||||||
|
self._preset_mode = PRESET_BOOST
|
||||||
|
elif status["low_electricity"] == "1":
|
||||||
|
self._preset_mode = PRESET_ECO
|
||||||
|
elif status["sleep_status"] == "0000001":
|
||||||
|
self._preset_mode = PRESET_SLEEP
|
||||||
|
elif status["sleep_status"] == "0000010":
|
||||||
|
self._preset_mode = "sleep_2"
|
||||||
|
elif status["sleep_status"] == "0000011":
|
||||||
|
self._preset_mode = "sleep_3"
|
||||||
|
elif status["sleep_status"] == "0000100":
|
||||||
|
self._preset_mode = "sleep_4"
|
||||||
|
else:
|
||||||
|
self._preset_mode = PRESET_NONE
|
||||||
|
else:
|
||||||
|
self._hvac_mode = HVAC_MODE_OFF
|
||||||
|
self._fan_mode = None
|
||||||
|
self._swing_mode = None
|
||||||
|
self._target_temperature = None
|
||||||
|
self._preset_mode = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self):
|
||||||
|
"""Return True if entity is available."""
|
||||||
|
return self._available
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name of the climate device."""
|
||||||
|
return self._unique_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def temperature_unit(self):
|
||||||
|
"""Return the unit of measurement."""
|
||||||
|
return self._temperature_unit
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_temperature(self):
|
||||||
|
"""Return the current temperature."""
|
||||||
|
return self._current_temperature
|
||||||
|
|
||||||
|
@property
|
||||||
|
def target_temperature(self):
|
||||||
|
"""Return the temperature we are trying to reach."""
|
||||||
|
return self._target_temperature
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_mode(self):
|
||||||
|
"""Return hvac target hvac state."""
|
||||||
|
return self._hvac_mode
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_modes(self):
|
||||||
|
"""Return the list of available operation modes."""
|
||||||
|
return self._hvac_modes
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fan_mode(self):
|
||||||
|
"""Return the fan setting."""
|
||||||
|
return self._fan_mode
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fan_modes(self):
|
||||||
|
"""Return the list of available fan modes."""
|
||||||
|
return self._fan_modes
|
||||||
|
|
||||||
|
@property
|
||||||
|
def preset_mode(self):
|
||||||
|
"""Return the preset mode if on."""
|
||||||
|
return self._preset_mode
|
||||||
|
|
||||||
|
@property
|
||||||
|
def preset_modes(self):
|
||||||
|
"""Return the list of available preset modes."""
|
||||||
|
return self._preset_modes
|
||||||
|
|
||||||
|
@property
|
||||||
|
def swing_mode(self):
|
||||||
|
"""Return swing operation."""
|
||||||
|
return self._swing_mode
|
||||||
|
|
||||||
|
@property
|
||||||
|
def swing_modes(self):
|
||||||
|
"""Return the list of available fan modes."""
|
||||||
|
return self._swing_modes
|
||||||
|
|
||||||
|
@property
|
||||||
|
def min_temp(self):
|
||||||
|
"""Return the minimum temperature."""
|
||||||
|
if self._temperature_unit == TEMP_CELSIUS:
|
||||||
|
return MIN_TEMP_C
|
||||||
|
return MIN_TEMP_F
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_temp(self):
|
||||||
|
"""Return the maximum temperature."""
|
||||||
|
if self._temperature_unit == TEMP_CELSIUS:
|
||||||
|
return MAX_TEMP_C
|
||||||
|
return MAX_TEMP_F
|
||||||
|
|
||||||
|
@property
|
||||||
|
def precision(self):
|
||||||
|
"""Return the precision of the system."""
|
||||||
|
return PRECISION_WHOLE
|
||||||
|
|
||||||
|
@property
|
||||||
|
def target_temperature_step(self):
|
||||||
|
"""Return the supported step of target temperature."""
|
||||||
|
return 1
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supported_features(self):
|
||||||
|
"""Return the list of supported features."""
|
||||||
|
return SUPPORT_FLAGS
|
||||||
|
|
||||||
|
async def async_set_temperature(self, **kwargs):
|
||||||
|
"""Set new target temperatures."""
|
||||||
|
if self._on != "1":
|
||||||
|
_LOGGER.warning(
|
||||||
|
"AC at %s is off, could not set temperature", self._unique_id
|
||||||
|
)
|
||||||
|
return
|
||||||
|
temp = kwargs.get(ATTR_TEMPERATURE)
|
||||||
|
if temp is not None:
|
||||||
|
_LOGGER.debug("Setting temp of %s to %s", self._unique_id, temp)
|
||||||
|
if self._preset_mode != PRESET_NONE:
|
||||||
|
await self.async_set_preset_mode(PRESET_NONE)
|
||||||
|
if self._temperature_unit == TEMP_CELSIUS:
|
||||||
|
await self._device.command(f"temp_{int(temp)}_C")
|
||||||
|
else:
|
||||||
|
await self._device.command(f"temp_{int(temp)}_F")
|
||||||
|
|
||||||
|
async def async_set_fan_mode(self, fan_mode):
|
||||||
|
"""Set new fan mode."""
|
||||||
|
if self._on != "1":
|
||||||
|
_LOGGER.warning("AC at %s is off, could not set fan mode", self._unique_id)
|
||||||
|
return
|
||||||
|
if self._hvac_mode in (HVAC_MODE_COOL, HVAC_MODE_FAN_ONLY) and (
|
||||||
|
self._hvac_mode != HVAC_MODE_FAN_ONLY or fan_mode != FAN_AUTO
|
||||||
|
):
|
||||||
|
_LOGGER.debug("Setting fan mode of %s to %s", self._unique_id, fan_mode)
|
||||||
|
await self._device.command(HA_FAN_MODES_TO_AC[fan_mode])
|
||||||
|
|
||||||
|
async def async_set_swing_mode(self, swing_mode):
|
||||||
|
"""Set new target swing operation."""
|
||||||
|
if self._on != "1":
|
||||||
|
_LOGGER.warning(
|
||||||
|
"AC at %s is off, could not set swing mode", self._unique_id
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
_LOGGER.debug("Setting swing mode of %s to %s", self._unique_id, swing_mode)
|
||||||
|
swing_act = self._swing_mode
|
||||||
|
|
||||||
|
if swing_mode == SWING_OFF and swing_act != SWING_OFF:
|
||||||
|
if swing_act in (SWING_HORIZONTAL, SWING_BOTH):
|
||||||
|
await self._device.command("hor_dir")
|
||||||
|
if swing_act in (SWING_VERTICAL, SWING_BOTH):
|
||||||
|
await self._device.command("vert_dir")
|
||||||
|
|
||||||
|
if swing_mode == SWING_BOTH and swing_act != SWING_BOTH:
|
||||||
|
if swing_act in (SWING_OFF, SWING_HORIZONTAL):
|
||||||
|
await self._device.command("vert_swing")
|
||||||
|
if swing_act in (SWING_OFF, SWING_VERTICAL):
|
||||||
|
await self._device.command("hor_swing")
|
||||||
|
|
||||||
|
if swing_mode == SWING_VERTICAL and swing_act != SWING_VERTICAL:
|
||||||
|
if swing_act in (SWING_OFF, SWING_HORIZONTAL):
|
||||||
|
await self._device.command("vert_swing")
|
||||||
|
if swing_act in (SWING_BOTH, SWING_HORIZONTAL):
|
||||||
|
await self._device.command("hor_dir")
|
||||||
|
|
||||||
|
if swing_mode == SWING_HORIZONTAL and swing_act != SWING_HORIZONTAL:
|
||||||
|
if swing_act in (SWING_BOTH, SWING_VERTICAL):
|
||||||
|
await self._device.command("vert_dir")
|
||||||
|
if swing_act in (SWING_OFF, SWING_VERTICAL):
|
||||||
|
await self._device.command("hor_swing")
|
||||||
|
|
||||||
|
async def async_set_preset_mode(self, preset_mode):
|
||||||
|
"""Set new preset mode."""
|
||||||
|
if self._on != "1":
|
||||||
|
if preset_mode == PRESET_NONE:
|
||||||
|
return
|
||||||
|
await self.async_turn_on()
|
||||||
|
|
||||||
|
_LOGGER.debug("Setting preset mode of %s to %s", self._unique_id, preset_mode)
|
||||||
|
|
||||||
|
if preset_mode == PRESET_ECO:
|
||||||
|
await self._device.command("energysave_on")
|
||||||
|
self._previous_state = preset_mode
|
||||||
|
elif preset_mode == PRESET_BOOST:
|
||||||
|
await self._device.command("turbo_on")
|
||||||
|
self._previous_state = preset_mode
|
||||||
|
elif preset_mode == PRESET_SLEEP:
|
||||||
|
await self._device.command("sleep_1")
|
||||||
|
self._previous_state = self._hvac_mode
|
||||||
|
elif preset_mode == "sleep_2":
|
||||||
|
await self._device.command("sleep_2")
|
||||||
|
self._previous_state = self._hvac_mode
|
||||||
|
elif preset_mode == "sleep_3":
|
||||||
|
await self._device.command("sleep_3")
|
||||||
|
self._previous_state = self._hvac_mode
|
||||||
|
elif preset_mode == "sleep_4":
|
||||||
|
await self._device.command("sleep_4")
|
||||||
|
self._previous_state = self._hvac_mode
|
||||||
|
elif self._previous_state is not None:
|
||||||
|
if self._previous_state == PRESET_ECO:
|
||||||
|
await self._device.command("energysave_off")
|
||||||
|
elif self._previous_state == PRESET_BOOST:
|
||||||
|
await self._device.command("turbo_off")
|
||||||
|
elif self._previous_state in HA_STATE_TO_AC:
|
||||||
|
await self._device.command(HA_STATE_TO_AC[self._previous_state])
|
||||||
|
self._previous_state = None
|
||||||
|
|
||||||
|
async def async_set_hvac_mode(self, hvac_mode):
|
||||||
|
"""Set new operation mode."""
|
||||||
|
_LOGGER.debug("Setting operation mode of %s to %s", self._unique_id, hvac_mode)
|
||||||
|
if hvac_mode == HVAC_MODE_OFF:
|
||||||
|
await self.async_turn_off()
|
||||||
|
else:
|
||||||
|
await self._device.command(HA_STATE_TO_AC[hvac_mode])
|
||||||
|
if self._on != "1":
|
||||||
|
await self.async_turn_on()
|
||||||
|
|
||||||
|
async def async_turn_on(self):
|
||||||
|
"""Turn on."""
|
||||||
|
_LOGGER.debug("Turning %s on", self._unique_id)
|
||||||
|
await self._device.command("on")
|
||||||
|
|
||||||
|
async def async_turn_off(self):
|
||||||
|
"""Turn off."""
|
||||||
|
_LOGGER.debug("Turning %s off", self._unique_id)
|
||||||
|
await self._device.command("off")
|
22
homeassistant/components/hisense_aehw4a1/config_flow.py
Normal file
22
homeassistant/components/hisense_aehw4a1/config_flow.py
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
"""Config flow for Hisense AEH-W4A1 integration."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from pyaehw4a1.aehw4a1 import AehW4a1
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.helpers import config_entry_flow
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def _async_has_devices(hass):
|
||||||
|
"""Return if there are devices that can be discovered."""
|
||||||
|
aehw4a1_ip_addresses = await AehW4a1().discovery()
|
||||||
|
return len(aehw4a1_ip_addresses) > 0
|
||||||
|
|
||||||
|
|
||||||
|
config_entry_flow.register_discovery_flow(
|
||||||
|
DOMAIN, "Hisense AEH-W4A1", _async_has_devices, config_entries.CONN_CLASS_LOCAL_POLL
|
||||||
|
)
|
3
homeassistant/components/hisense_aehw4a1/const.py
Normal file
3
homeassistant/components/hisense_aehw4a1/const.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
"""Constants for the Hisense AEH-W4A1 integration."""
|
||||||
|
|
||||||
|
DOMAIN = "hisense_aehw4a1"
|
13
homeassistant/components/hisense_aehw4a1/manifest.json
Normal file
13
homeassistant/components/hisense_aehw4a1/manifest.json
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"domain": "hisense_aehw4a1",
|
||||||
|
"name": "Hisense AEH-W4A1",
|
||||||
|
"config_flow": true,
|
||||||
|
"documentation": "https://www.home-assistant.io/integrations/hisense_aehw4a1",
|
||||||
|
"requirements": [
|
||||||
|
"pyaehw4a1==0.3.1"
|
||||||
|
],
|
||||||
|
"dependencies": [],
|
||||||
|
"codeowners": [
|
||||||
|
"@bannhead"
|
||||||
|
]
|
||||||
|
}
|
15
homeassistant/components/hisense_aehw4a1/strings.json
Normal file
15
homeassistant/components/hisense_aehw4a1/strings.json
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"title": "Hisense AEH-W4A1",
|
||||||
|
"step": {
|
||||||
|
"confirm": {
|
||||||
|
"title": "Hisense AEH-W4A1",
|
||||||
|
"description": "Do you want to set up Hisense AEH-W4A1?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"single_instance_allowed": "Only a single configuration of Hisense AEH-W4A1 is possible.",
|
||||||
|
"no_devices_found": "No Hisense AEH-W4A1 devices found on the network."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,6 +28,7 @@ FLOWS = [
|
||||||
"gpslogger",
|
"gpslogger",
|
||||||
"hangouts",
|
"hangouts",
|
||||||
"heos",
|
"heos",
|
||||||
|
"hisense_aehw4a1",
|
||||||
"homekit_controller",
|
"homekit_controller",
|
||||||
"homematicip_cloud",
|
"homematicip_cloud",
|
||||||
"huawei_lte",
|
"huawei_lte",
|
||||||
|
|
|
@ -1089,6 +1089,9 @@ py_nextbusnext==0.1.4
|
||||||
# homeassistant.components.ads
|
# homeassistant.components.ads
|
||||||
pyads==3.0.7
|
pyads==3.0.7
|
||||||
|
|
||||||
|
# homeassistant.components.hisense_aehw4a1
|
||||||
|
pyaehw4a1==0.3.1
|
||||||
|
|
||||||
# homeassistant.components.aftership
|
# homeassistant.components.aftership
|
||||||
pyaftership==0.1.2
|
pyaftership==0.1.2
|
||||||
|
|
||||||
|
|
|
@ -369,6 +369,9 @@ pyRFXtrx==0.23
|
||||||
# homeassistant.components.nextbus
|
# homeassistant.components.nextbus
|
||||||
py_nextbusnext==0.1.4
|
py_nextbusnext==0.1.4
|
||||||
|
|
||||||
|
# homeassistant.components.hisense_aehw4a1
|
||||||
|
pyaehw4a1==0.3.1
|
||||||
|
|
||||||
# homeassistant.components.almond
|
# homeassistant.components.almond
|
||||||
pyalmond==0.0.2
|
pyalmond==0.0.2
|
||||||
|
|
||||||
|
|
1
tests/components/hisense_aehw4a1/__init__.py
Normal file
1
tests/components/hisense_aehw4a1/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
"""Tests for the hisense_aehw4a1 component."""
|
89
tests/components/hisense_aehw4a1/test_init.py
Normal file
89
tests/components/hisense_aehw4a1/test_init.py
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
"""Tests for the Hisense AEH-W4A1 init file."""
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from pyaehw4a1 import exceptions
|
||||||
|
|
||||||
|
from homeassistant import config_entries, data_entry_flow
|
||||||
|
from homeassistant.components import hisense_aehw4a1
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
from tests.common import mock_coro
|
||||||
|
|
||||||
|
|
||||||
|
async def test_creating_entry_sets_up_climate_discovery(hass):
|
||||||
|
"""Test setting up Hisense AEH-W4A1 loads the climate component."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.hisense_aehw4a1.config_flow.AehW4a1.discovery",
|
||||||
|
return_value=mock_coro(["1.2.3.4"]),
|
||||||
|
):
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.hisense_aehw4a1.climate.async_setup_entry",
|
||||||
|
return_value=mock_coro(True),
|
||||||
|
) as mock_setup:
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
hisense_aehw4a1.DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Confirmation form
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {}
|
||||||
|
)
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(mock_setup.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_configuring_hisense_w4a1_create_entry(hass):
|
||||||
|
"""Test that specifying config will create an entry."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.hisense_aehw4a1.config_flow.AehW4a1.check",
|
||||||
|
return_value=mock_coro(True),
|
||||||
|
):
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.hisense_aehw4a1.async_setup_entry",
|
||||||
|
return_value=mock_coro(True),
|
||||||
|
) as mock_setup:
|
||||||
|
await async_setup_component(
|
||||||
|
hass,
|
||||||
|
hisense_aehw4a1.DOMAIN,
|
||||||
|
{"hisense_aehw4a1": {"ip_address": ["1.2.3.4"]}},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(mock_setup.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_configuring_hisense_w4a1_not_creates_entry_for_device_not_found(hass):
|
||||||
|
"""Test that specifying config will not create an entry."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.hisense_aehw4a1.config_flow.AehW4a1.check",
|
||||||
|
side_effect=exceptions.ConnectionError,
|
||||||
|
):
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.hisense_aehw4a1.async_setup_entry",
|
||||||
|
return_value=mock_coro(True),
|
||||||
|
) as mock_setup:
|
||||||
|
await async_setup_component(
|
||||||
|
hass,
|
||||||
|
hisense_aehw4a1.DOMAIN,
|
||||||
|
{"hisense_aehw4a1": {"ip_address": ["1.2.3.4"]}},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(mock_setup.mock_calls) == 0
|
||||||
|
|
||||||
|
|
||||||
|
async def test_configuring_hisense_w4a1_not_creates_entry_for_empty_import(hass):
|
||||||
|
"""Test that specifying config will not create an entry."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.hisense_aehw4a1.async_setup_entry",
|
||||||
|
return_value=mock_coro(True),
|
||||||
|
) as mock_setup:
|
||||||
|
await async_setup_component(hass, hisense_aehw4a1.DOMAIN, {})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(mock_setup.mock_calls) == 0
|
Loading…
Add table
Add a link
Reference in a new issue