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:
RDFurman 2021-08-23 16:54:55 -06:00 committed by GitHub
parent 09b872d51f
commit 2aed7b94c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 67 additions and 34 deletions

View File

@ -41,7 +41,7 @@ async def async_setup_entry(hass, config):
_LOGGER.debug("No devices found")
return False
data = HoneywellService(hass, client, username, password, devices[0])
data = HoneywellData(hass, client, username, password, devices)
await data.update()
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][config.entry_id] = data
@ -65,16 +65,16 @@ def get_somecomfort_client(username, password):
) from ex
class HoneywellService:
class HoneywellData:
"""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."""
self._hass = hass
self._client = client
self._username = username
self._password = password
self.device = device
self.devices = devices
async def _retry(self) -> bool:
"""Recreate a new somecomfort client.
@ -93,23 +93,27 @@ class HoneywellService:
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)
if len(devices) == 0:
_LOGGER.error("Failed to find any devices")
return False
self.device = devices[0]
self.devices = devices
return True
def _refresh_devices(self):
"""Refresh each enabled device."""
for device in self.devices:
device.refresh()
@Throttle(MIN_TIME_BETWEEN_UPDATES)
async def update(self) -> None:
"""Update the state."""
retries = 3
while retries > 0:
try:
await self._hass.async_add_executor_job(self.device.refresh)
await self._hass.async_add_executor_job(self._refresh_devices)
break
except (
somecomfort.client.APIRateLimited,
@ -126,7 +130,3 @@ class HoneywellService:
raise exp
_LOGGER.error("SomeComfort update failed, Retrying - Error: %s", exp)
_LOGGER.debug(
"latestData = %s ", self.device._data # pylint: disable=protected-access
)

View File

@ -41,7 +41,6 @@ from homeassistant.const import (
TEMP_FAHRENHEIT,
)
import homeassistant.helpers.config_validation as cv
import homeassistant.helpers.device_registry as dr
from .const import (
_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]
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):
@ -142,25 +146,24 @@ async def async_setup_platform(hass, config, add_entities, discovery_info=None):
class HoneywellUSThermostat(ClimateEntity):
"""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."""
self._data = data
self._device = device
self._cool_away_temp = cool_away_temp
self._heat_away_temp = heat_away_temp
self._away = False
self._attr_unique_id = dr.format_mac(data.device.mac_address)
self._attr_name = data.device.name
self._attr_unique_id = device.deviceid
self._attr_name = device.name
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_is_aux_heat = data.device.system_mode == "emheat"
self._attr_is_aux_heat = device.system_mode == "emheat"
# not all honeywell HVACs support all modes
mappings = [
v for k, v in HVAC_MODE_TO_HW_MODE.items() if data.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._attr_hvac_modes = list(self._hvac_mode_map)
@ -170,28 +173,23 @@ class HoneywellUSThermostat(ClimateEntity):
| SUPPORT_TARGET_TEMPERATURE_RANGE
)
if data.device._data["canControlHumidification"]:
if device._data["canControlHumidification"]:
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
if not data.device._data["hasFan"]:
if not device._data["hasFan"]:
return
# 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._attr_fan_modes = list(self._fan_mode_map)
self._attr_supported_features |= SUPPORT_FAN_MODE
@property
def _device(self):
"""Shortcut to access the device."""
return self._data.device
@property
def extra_state_attributes(self) -> dict[str, Any]:
"""Return the device specific state attributes."""

View File

@ -31,7 +31,7 @@ def config_entry(config_data):
def device():
"""Mock a somecomfort.Device."""
mock_device = create_autospec(somecomfort.Device, instance=True)
mock_device.deviceid.return_value = "device1"
mock_device.deviceid = 1234567
mock_device._data = {
"canControlHumidification": False,
"hasFan": False,
@ -43,6 +43,22 @@ def 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
def location(device):
"""Mock a somecomfort.Location."""

View File

@ -1,8 +1,27 @@
"""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."""
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() == 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