Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add support for Aeotec ZWA021 TRV (Eurotronics Spirit Z clones) #1048

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ venv
.idea
custom_components/hacs
x_*
.haconfig
.haconfig
106 changes: 106 additions & 0 deletions custom_components/better_thermostat/adapters/zwave_js.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import asyncio
import logging

from homeassistant.components.number.const import DOMAIN, SERVICE_SET_VALUE
from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN

from ..utils.helpers import find_valve_entity
from .generic import (
set_hvac_mode as generic_set_hvac_mode,
set_temperature as generic_set_temperature,
)

_LOGGER = logging.getLogger(__name__)


async def get_info(self, entity_id):
"""Get info from TRV."""
support_valve = False
valve = await find_valve_entity(self, entity_id)
if valve is not None:
support_valve = True
return {"support_offset": False, "support_valve": support_valve}


async def init(self, entity_id):
if self.real_trvs[entity_id]["valve_position_entity"] is None:
self.real_trvs[entity_id]["valve_position_entity"] = await find_valve_entity(
self, entity_id
)
_LOGGER.debug(
"better_thermostat %s: uses valve position entity %s",
self.name,
self.real_trvs[entity_id]["valve_position_entity"],
)
# Wait for the entity to be available
_ready = False
while not _ready:
if self.hass.states.get(
self.real_trvs[entity_id]["valve_position_entity"]
).state in (
STATE_UNAVAILABLE,
STATE_UNKNOWN,
None,
):
_LOGGER.info(
"better_thermostat %s: waiting for TRV/climate entity with id '%s' to become fully available...",
self.name,
self.real_trvs[entity_id]["valve_position_entity"],
)
await asyncio.sleep(5)
continue
_ready = True
return


async def get_current_offset(self, entity_id):
"""Get current offset."""
return None


async def get_offset_steps(self, entity_id):
"""Get offset steps."""
return None


async def get_min_offset(self, entity_id):
"""Get min offset."""
return -6


async def get_max_offset(self, entity_id):
"""Get max offset."""
return 6


async def set_temperature(self, entity_id, temperature):
"""Set new target temperature."""
return await generic_set_temperature(self, entity_id, temperature)


async def set_hvac_mode(self, entity_id, hvac_mode):
"""Set new target hvac mode."""
return await generic_set_hvac_mode(self, entity_id, hvac_mode)


async def set_offset(self, entity_id, offset):
"""Set new target offset."""
return # Not supported


async def set_valve(self, entity_id, valve):
"""Set new target valve."""
_LOGGER.debug(
f"better_thermostat {self.name}: TO TRV {entity_id} set_valve: {valve}"
)
value = min(round(valve * 100), 99)
return await self.hass.services.async_call(
DOMAIN,
SERVICE_SET_VALUE,
{
"entity_id": self.real_trvs[entity_id]["valve_position_entity"],
"value": value,
},
blocking=True,
context=self.context,
)
47 changes: 47 additions & 0 deletions custom_components/better_thermostat/model_fixes/ZWA021.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import logging
from homeassistant.components.climate.const import HVACMode
from homeassistant.components.zwave_js.const import SERVICE_SET_VALUE, DOMAIN

_LOGGER = logging.getLogger(__name__)


def fix_local_calibration(self, entity_id, offset):
return offset


def fix_target_temperature_calibration(self, entity_id, temperature):
return temperature


async def override_set_hvac_mode(self, entity_id, hvac_mode):
model = self.real_trvs[entity_id]["model"]
_LOGGER.debug(f"Setting {entity_id} model {model} hvac mode to {hvac_mode}")
if model == "ZWA021" and hvac_mode != HVACMode.OFF:
_LOGGER.debug(
f"better_thermostat {self.name}: TRV {entity_id} device quirk hvac ZWA021 active"
)
await self.hass.services.async_call(
DOMAIN,
SERVICE_SET_VALUE,
{
"entity_id": entity_id,
"command_class": "64",
"property": "mode",
"value": "31",
},
blocking=True,
context=self.context,
)
else:
await self.hass.services.async_call(
"climate",
"set_hvac_mode",
{"entity_id": entity_id, "hvac_mode": hvac_mode},
blocking=True,
context=self.context,
)
return True


async def override_set_temperature(self, entity_id, temperature):
return False
12 changes: 10 additions & 2 deletions custom_components/better_thermostat/utils/controlling.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@
get_current_offset,
set_temperature,
set_hvac_mode,
set_valve,
)

from .helpers import convert_to_float, heating_power_valve_position
from custom_components.better_thermostat.events.trv import (
convert_outbound_states,
update_hvac_action,
)

from custom_components.better_thermostat.utils.helpers import convert_to_float

from custom_components.better_thermostat.utils.const import CalibrationMode


Expand Down Expand Up @@ -300,6 +300,14 @@ async def control_trv(self, heater_entity_id=None):
check_target_temperature(self, heater_entity_id)
)

# set new valve position
if (
self.real_trvs[heater_entity_id]["valve_position_entity"] is not None
and _new_hvac_mode != HVACMode.OFF
):
_valve_position = heating_power_valve_position(self, heater_entity_id)
await set_valve(self, heater_entity_id, _valve_position)

await asyncio.sleep(3)
self.real_trvs[heater_entity_id]["ignore_trv_states"] = False
return True
Expand Down
31 changes: 17 additions & 14 deletions custom_components/better_thermostat/utils/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
from datetime import datetime
from enum import Enum
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.entity_registry import async_entries_for_config_entry
from homeassistant.helpers.entity_registry import (
async_entries_for_config_entry,
async_entries_for_device,
)

from homeassistant.components.climate.const import HVACMode

Expand Down Expand Up @@ -202,7 +205,7 @@ def convert_time(time_string):
async def find_valve_entity(self, entity_id):
"""Find the local calibration entity for the TRV.

This is a hacky way to find the local calibration entity for the TRV. It is not possible to find the entity
This is a hacky way to find the valve position entity for the TRV. It is not possible to find the entity
automatically, because the entity_id is not the same as the friendly_name. The friendly_name is the same for all
thermostats of the same brand, but the entity_id is different.

Expand All @@ -214,26 +217,26 @@ async def find_valve_entity(self, entity_id):
Returns
-------
str
the entity_id of the local calibration entity
the entity_id of the valve position entity
None
if no local calibration entity was found
if no valve position entity was found
"""
entity_registry = er.async_get(self.hass)
reg_entity = entity_registry.async_get(entity_id)
if reg_entity is None:
return None
entity_entries = async_entries_for_config_entry(
entity_registry, reg_entity.config_entry_id
)
entity_entries = async_entries_for_device(entity_registry, reg_entity.device_id)
for entity in entity_entries:
uid = entity.unique_id
# Make sure we use the correct device entities
if entity.device_id == reg_entity.device_id:
if "_valve_position" in uid or "_position" in uid:
_LOGGER.debug(
f"better thermostat: Found valve position entity {entity.entity_id} for {entity_id}"
)
return entity.entity_id
if (
"_valve_position" in uid
or "_position" in uid
or entity.original_name == "Valve control"
):
_LOGGER.debug(
f"better thermostat: Found valve position entity {entity.entity_id} for {entity_id}"
)
return entity.entity_id

_LOGGER.debug(
f"better thermostat: Could not find valve position entity for {entity_id}"
Expand Down
Loading