mirror of
https://github.com/home-assistant/core.git
synced 2025-07-08 13:57:10 +00:00
Add multi device support back to honeywell (#54003)
* Add multi device support back to honeywell * Fix device reference in honeywell climate * Use deviceid for unique_id * Add test for multiple thermostats * Reduce recursive jobs * Remove old filter Co-authored-by: J. Nick Koston <nick@koston.org> Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
09b872d51f
commit
2aed7b94c5
@ -41,7 +41,7 @@ async def async_setup_entry(hass, config):
|
|||||||
_LOGGER.debug("No devices found")
|
_LOGGER.debug("No devices found")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
data = HoneywellService(hass, client, username, password, devices[0])
|
data = HoneywellData(hass, client, username, password, devices)
|
||||||
await data.update()
|
await data.update()
|
||||||
hass.data.setdefault(DOMAIN, {})
|
hass.data.setdefault(DOMAIN, {})
|
||||||
hass.data[DOMAIN][config.entry_id] = data
|
hass.data[DOMAIN][config.entry_id] = data
|
||||||
@ -65,16 +65,16 @@ def get_somecomfort_client(username, password):
|
|||||||
) from ex
|
) from ex
|
||||||
|
|
||||||
|
|
||||||
class HoneywellService:
|
class HoneywellData:
|
||||||
"""Get the latest data and update."""
|
"""Get the latest data and update."""
|
||||||
|
|
||||||
def __init__(self, hass, client, username, password, device):
|
def __init__(self, hass, client, username, password, devices):
|
||||||
"""Initialize the data object."""
|
"""Initialize the data object."""
|
||||||
self._hass = hass
|
self._hass = hass
|
||||||
self._client = client
|
self._client = client
|
||||||
self._username = username
|
self._username = username
|
||||||
self._password = password
|
self._password = password
|
||||||
self.device = device
|
self.devices = devices
|
||||||
|
|
||||||
async def _retry(self) -> bool:
|
async def _retry(self) -> bool:
|
||||||
"""Recreate a new somecomfort client.
|
"""Recreate a new somecomfort client.
|
||||||
@ -93,23 +93,27 @@ class HoneywellService:
|
|||||||
device
|
device
|
||||||
for location in self._client.locations_by_id.values()
|
for location in self._client.locations_by_id.values()
|
||||||
for device in location.devices_by_id.values()
|
for device in location.devices_by_id.values()
|
||||||
if device.name == self.device.name
|
|
||||||
]
|
]
|
||||||
|
|
||||||
if len(devices) != 1:
|
if len(devices) == 0:
|
||||||
_LOGGER.error("Failed to find device %s", self.device.name)
|
_LOGGER.error("Failed to find any devices")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.device = devices[0]
|
self.devices = devices
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _refresh_devices(self):
|
||||||
|
"""Refresh each enabled device."""
|
||||||
|
for device in self.devices:
|
||||||
|
device.refresh()
|
||||||
|
|
||||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||||
async def update(self) -> None:
|
async def update(self) -> None:
|
||||||
"""Update the state."""
|
"""Update the state."""
|
||||||
retries = 3
|
retries = 3
|
||||||
while retries > 0:
|
while retries > 0:
|
||||||
try:
|
try:
|
||||||
await self._hass.async_add_executor_job(self.device.refresh)
|
await self._hass.async_add_executor_job(self._refresh_devices)
|
||||||
break
|
break
|
||||||
except (
|
except (
|
||||||
somecomfort.client.APIRateLimited,
|
somecomfort.client.APIRateLimited,
|
||||||
@ -126,7 +130,3 @@ class HoneywellService:
|
|||||||
raise exp
|
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 # pylint: disable=protected-access
|
|
||||||
)
|
|
||||||
|
@ -41,7 +41,6 @@ from homeassistant.const import (
|
|||||||
TEMP_FAHRENHEIT,
|
TEMP_FAHRENHEIT,
|
||||||
)
|
)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
import homeassistant.helpers.device_registry as dr
|
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
_LOGGER,
|
_LOGGER,
|
||||||
@ -116,7 +115,12 @@ async def async_setup_entry(hass, config, async_add_entities, discovery_info=Non
|
|||||||
|
|
||||||
data = hass.data[DOMAIN][config.entry_id]
|
data = hass.data[DOMAIN][config.entry_id]
|
||||||
|
|
||||||
async_add_entities([HoneywellUSThermostat(data, cool_away_temp, heat_away_temp)])
|
async_add_entities(
|
||||||
|
[
|
||||||
|
HoneywellUSThermostat(data, device, cool_away_temp, heat_away_temp)
|
||||||
|
for device in data.devices
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(hass, config, add_entities, discovery_info=None):
|
async def async_setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
@ -142,25 +146,24 @@ async def async_setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
class HoneywellUSThermostat(ClimateEntity):
|
class HoneywellUSThermostat(ClimateEntity):
|
||||||
"""Representation of a Honeywell US Thermostat."""
|
"""Representation of a Honeywell US Thermostat."""
|
||||||
|
|
||||||
def __init__(self, data, cool_away_temp, heat_away_temp):
|
def __init__(self, data, device, cool_away_temp, heat_away_temp):
|
||||||
"""Initialize the thermostat."""
|
"""Initialize the thermostat."""
|
||||||
self._data = data
|
self._data = data
|
||||||
|
self._device = device
|
||||||
self._cool_away_temp = cool_away_temp
|
self._cool_away_temp = cool_away_temp
|
||||||
self._heat_away_temp = heat_away_temp
|
self._heat_away_temp = heat_away_temp
|
||||||
self._away = False
|
self._away = False
|
||||||
|
|
||||||
self._attr_unique_id = dr.format_mac(data.device.mac_address)
|
self._attr_unique_id = device.deviceid
|
||||||
self._attr_name = data.device.name
|
self._attr_name = device.name
|
||||||
self._attr_temperature_unit = (
|
self._attr_temperature_unit = (
|
||||||
TEMP_CELSIUS if data.device.temperature_unit == "C" else TEMP_FAHRENHEIT
|
TEMP_CELSIUS if device.temperature_unit == "C" else TEMP_FAHRENHEIT
|
||||||
)
|
)
|
||||||
self._attr_preset_modes = [PRESET_NONE, PRESET_AWAY]
|
self._attr_preset_modes = [PRESET_NONE, PRESET_AWAY]
|
||||||
self._attr_is_aux_heat = data.device.system_mode == "emheat"
|
self._attr_is_aux_heat = device.system_mode == "emheat"
|
||||||
|
|
||||||
# not all honeywell HVACs support all modes
|
# not all honeywell HVACs support all modes
|
||||||
mappings = [
|
mappings = [v for k, v in HVAC_MODE_TO_HW_MODE.items() if device.raw_ui_data[k]]
|
||||||
v for k, v in HVAC_MODE_TO_HW_MODE.items() if data.device.raw_ui_data[k]
|
|
||||||
]
|
|
||||||
self._hvac_mode_map = {k: v for d in mappings for k, v in d.items()}
|
self._hvac_mode_map = {k: v for d in mappings for k, v in d.items()}
|
||||||
self._attr_hvac_modes = list(self._hvac_mode_map)
|
self._attr_hvac_modes = list(self._hvac_mode_map)
|
||||||
|
|
||||||
@ -170,28 +173,23 @@ class HoneywellUSThermostat(ClimateEntity):
|
|||||||
| SUPPORT_TARGET_TEMPERATURE_RANGE
|
| SUPPORT_TARGET_TEMPERATURE_RANGE
|
||||||
)
|
)
|
||||||
|
|
||||||
if data.device._data["canControlHumidification"]:
|
if device._data["canControlHumidification"]:
|
||||||
self._attr_supported_features |= SUPPORT_TARGET_HUMIDITY
|
self._attr_supported_features |= SUPPORT_TARGET_HUMIDITY
|
||||||
|
|
||||||
if data.device.raw_ui_data["SwitchEmergencyHeatAllowed"]:
|
if device.raw_ui_data["SwitchEmergencyHeatAllowed"]:
|
||||||
self._attr_supported_features |= SUPPORT_AUX_HEAT
|
self._attr_supported_features |= SUPPORT_AUX_HEAT
|
||||||
|
|
||||||
if not data.device._data["hasFan"]:
|
if not device._data["hasFan"]:
|
||||||
return
|
return
|
||||||
|
|
||||||
# not all honeywell fans support all modes
|
# not all honeywell fans support all modes
|
||||||
mappings = [v for k, v in FAN_MODE_TO_HW.items() if data.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._fan_mode_map = {k: v for d in mappings for k, v in d.items()}
|
||||||
|
|
||||||
self._attr_fan_modes = list(self._fan_mode_map)
|
self._attr_fan_modes = list(self._fan_mode_map)
|
||||||
|
|
||||||
self._attr_supported_features |= SUPPORT_FAN_MODE
|
self._attr_supported_features |= SUPPORT_FAN_MODE
|
||||||
|
|
||||||
@property
|
|
||||||
def _device(self):
|
|
||||||
"""Shortcut to access the device."""
|
|
||||||
return self._data.device
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def extra_state_attributes(self) -> dict[str, Any]:
|
def extra_state_attributes(self) -> dict[str, Any]:
|
||||||
"""Return the device specific state attributes."""
|
"""Return the device specific state attributes."""
|
||||||
|
@ -31,7 +31,7 @@ def config_entry(config_data):
|
|||||||
def device():
|
def device():
|
||||||
"""Mock a somecomfort.Device."""
|
"""Mock a somecomfort.Device."""
|
||||||
mock_device = create_autospec(somecomfort.Device, instance=True)
|
mock_device = create_autospec(somecomfort.Device, instance=True)
|
||||||
mock_device.deviceid.return_value = "device1"
|
mock_device.deviceid = 1234567
|
||||||
mock_device._data = {
|
mock_device._data = {
|
||||||
"canControlHumidification": False,
|
"canControlHumidification": False,
|
||||||
"hasFan": False,
|
"hasFan": False,
|
||||||
@ -43,6 +43,22 @@ def device():
|
|||||||
return mock_device
|
return mock_device
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def another_device():
|
||||||
|
"""Mock a somecomfort.Device."""
|
||||||
|
mock_device = create_autospec(somecomfort.Device, instance=True)
|
||||||
|
mock_device.deviceid = 7654321
|
||||||
|
mock_device._data = {
|
||||||
|
"canControlHumidification": False,
|
||||||
|
"hasFan": False,
|
||||||
|
}
|
||||||
|
mock_device.system_mode = "off"
|
||||||
|
mock_device.name = "device2"
|
||||||
|
mock_device.current_temperature = 20
|
||||||
|
mock_device.mac_address = "macaddress1"
|
||||||
|
return mock_device
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def location(device):
|
def location(device):
|
||||||
"""Mock a somecomfort.Location."""
|
"""Mock a somecomfort.Location."""
|
||||||
|
@ -1,8 +1,27 @@
|
|||||||
"""Test honeywell setup process."""
|
"""Test honeywell setup process."""
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
async def test_setup_entry(hass, config_entry):
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
async def test_setup_entry(hass: HomeAssistant, config_entry: MockConfigEntry):
|
||||||
"""Initialize the config entry."""
|
"""Initialize the config entry."""
|
||||||
config_entry.add_to_hass(hass)
|
config_entry.add_to_hass(hass)
|
||||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
assert config_entry.state is ConfigEntryState.LOADED
|
||||||
|
assert hass.states.async_entity_ids_count() == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_setup_multiple_thermostats(
|
||||||
|
hass: HomeAssistant, config_entry: MockConfigEntry, location, another_device
|
||||||
|
) -> None:
|
||||||
|
"""Test that the config form is shown."""
|
||||||
|
location.devices_by_id[another_device.deviceid] = another_device
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert config_entry.state is ConfigEntryState.LOADED
|
||||||
|
assert hass.states.async_entity_ids_count() == 2
|
||||||
|
Loading…
x
Reference in New Issue
Block a user