mirror of
https://github.com/home-assistant/core.git
synced 2025-07-18 18:57:06 +00:00
Add error handling for all zwave_js service calls (#93846)
* Add error handling for all service calls * Switch siren to use internal function * Remove failing checks * Revert change to poll service, add comments, and add additional error handling * Add error handling for ping and refresh + review comment + add tests * Add test for statistics entity refresh
This commit is contained in:
parent
927b59fe5a
commit
bd8c88f51b
@ -77,7 +77,7 @@ class ZwaveBooleanNodeButton(ZWaveBaseEntity, ButtonEntity):
|
|||||||
|
|
||||||
async def async_press(self) -> None:
|
async def async_press(self) -> None:
|
||||||
"""Press the button."""
|
"""Press the button."""
|
||||||
await self.info.node.async_set_value(self.info.primary_value, True)
|
await self._async_set_value(self.info.primary_value, True)
|
||||||
|
|
||||||
|
|
||||||
class ZWaveNodePingButton(ButtonEntity):
|
class ZWaveNodePingButton(ButtonEntity):
|
||||||
@ -100,6 +100,9 @@ class ZWaveNodePingButton(ButtonEntity):
|
|||||||
|
|
||||||
async def async_poll_value(self, _: bool) -> None:
|
async def async_poll_value(self, _: bool) -> None:
|
||||||
"""Poll a value."""
|
"""Poll a value."""
|
||||||
|
# We log an error instead of raising an exception because this service call occurs
|
||||||
|
# in a separate task since it is called via the dispatcher and we don't want to
|
||||||
|
# raise the exception in that separate task because it is confusing to the user.
|
||||||
LOGGER.error(
|
LOGGER.error(
|
||||||
"There is no value to refresh for this entity so the zwave_js.refresh_value"
|
"There is no value to refresh for this entity so the zwave_js.refresh_value"
|
||||||
" service won't work for it"
|
" service won't work for it"
|
||||||
|
@ -437,7 +437,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity):
|
|||||||
except StopIteration:
|
except StopIteration:
|
||||||
raise ValueError(f"Received an invalid fan mode: {fan_mode}") from None
|
raise ValueError(f"Received an invalid fan mode: {fan_mode}") from None
|
||||||
|
|
||||||
await self.info.node.async_set_value(self._fan_mode, new_state)
|
await self._async_set_value(self._fan_mode, new_state)
|
||||||
|
|
||||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
@ -451,7 +451,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity):
|
|||||||
)
|
)
|
||||||
target_temp: float | None = kwargs.get(ATTR_TEMPERATURE)
|
target_temp: float | None = kwargs.get(ATTR_TEMPERATURE)
|
||||||
if target_temp is not None:
|
if target_temp is not None:
|
||||||
await self.info.node.async_set_value(setpoint, target_temp)
|
await self._async_set_value(setpoint, target_temp)
|
||||||
elif len(self._current_mode_setpoint_enums) == 2:
|
elif len(self._current_mode_setpoint_enums) == 2:
|
||||||
setpoint_low: ZwaveValue = self._setpoint_value_or_raise(
|
setpoint_low: ZwaveValue = self._setpoint_value_or_raise(
|
||||||
self._current_mode_setpoint_enums[0]
|
self._current_mode_setpoint_enums[0]
|
||||||
@ -462,9 +462,9 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity):
|
|||||||
target_temp_low: float | None = kwargs.get(ATTR_TARGET_TEMP_LOW)
|
target_temp_low: float | None = kwargs.get(ATTR_TARGET_TEMP_LOW)
|
||||||
target_temp_high: float | None = kwargs.get(ATTR_TARGET_TEMP_HIGH)
|
target_temp_high: float | None = kwargs.get(ATTR_TARGET_TEMP_HIGH)
|
||||||
if target_temp_low is not None:
|
if target_temp_low is not None:
|
||||||
await self.info.node.async_set_value(setpoint_low, target_temp_low)
|
await self._async_set_value(setpoint_low, target_temp_low)
|
||||||
if target_temp_high is not None:
|
if target_temp_high is not None:
|
||||||
await self.info.node.async_set_value(setpoint_high, target_temp_high)
|
await self._async_set_value(setpoint_high, target_temp_high)
|
||||||
|
|
||||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||||
"""Set new target hvac mode."""
|
"""Set new target hvac mode."""
|
||||||
@ -475,7 +475,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity):
|
|||||||
# Thermostat(valve) has no support for setting a mode, so we make it a no-op
|
# Thermostat(valve) has no support for setting a mode, so we make it a no-op
|
||||||
return
|
return
|
||||||
|
|
||||||
await self.info.node.async_set_value(self._current_mode, hvac_mode_id)
|
await self._async_set_value(self._current_mode, hvac_mode_id)
|
||||||
|
|
||||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||||
"""Set new target preset mode."""
|
"""Set new target preset mode."""
|
||||||
@ -487,7 +487,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity):
|
|||||||
preset_mode_value = self._hvac_presets.get(preset_mode)
|
preset_mode_value = self._hvac_presets.get(preset_mode)
|
||||||
if preset_mode_value is None:
|
if preset_mode_value is None:
|
||||||
raise ValueError(f"Received an invalid preset mode: {preset_mode}")
|
raise ValueError(f"Received an invalid preset mode: {preset_mode}")
|
||||||
await self.info.node.async_set_value(self._current_mode, preset_mode_value)
|
await self._async_set_value(self._current_mode, preset_mode_value)
|
||||||
|
|
||||||
|
|
||||||
class DynamicCurrentTempClimate(ZWaveClimate):
|
class DynamicCurrentTempClimate(ZWaveClimate):
|
||||||
|
@ -163,7 +163,7 @@ class CoverPositionMixin(ZWaveBaseEntity, CoverEntity):
|
|||||||
async def async_set_cover_position(self, **kwargs: Any) -> None:
|
async def async_set_cover_position(self, **kwargs: Any) -> None:
|
||||||
"""Move the cover to a specific position."""
|
"""Move the cover to a specific position."""
|
||||||
assert self._target_position_value
|
assert self._target_position_value
|
||||||
await self.info.node.async_set_value(
|
await self._async_set_value(
|
||||||
self._target_position_value,
|
self._target_position_value,
|
||||||
self.percent_to_zwave_position(kwargs[ATTR_POSITION]),
|
self.percent_to_zwave_position(kwargs[ATTR_POSITION]),
|
||||||
)
|
)
|
||||||
@ -171,14 +171,14 @@ class CoverPositionMixin(ZWaveBaseEntity, CoverEntity):
|
|||||||
async def async_open_cover(self, **kwargs: Any) -> None:
|
async def async_open_cover(self, **kwargs: Any) -> None:
|
||||||
"""Open the cover."""
|
"""Open the cover."""
|
||||||
assert self._target_position_value
|
assert self._target_position_value
|
||||||
await self.info.node.async_set_value(
|
await self._async_set_value(
|
||||||
self._target_position_value, self._fully_open_position
|
self._target_position_value, self._fully_open_position
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_close_cover(self, **kwargs: Any) -> None:
|
async def async_close_cover(self, **kwargs: Any) -> None:
|
||||||
"""Close cover."""
|
"""Close cover."""
|
||||||
assert self._target_position_value
|
assert self._target_position_value
|
||||||
await self.info.node.async_set_value(
|
await self._async_set_value(
|
||||||
self._target_position_value, self._fully_closed_position
|
self._target_position_value, self._fully_closed_position
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -186,7 +186,7 @@ class CoverPositionMixin(ZWaveBaseEntity, CoverEntity):
|
|||||||
"""Stop cover."""
|
"""Stop cover."""
|
||||||
assert self._stop_position_value
|
assert self._stop_position_value
|
||||||
# Stop the cover, will stop regardless of the actual direction of travel.
|
# Stop the cover, will stop regardless of the actual direction of travel.
|
||||||
await self.info.node.async_set_value(self._stop_position_value, False)
|
await self._async_set_value(self._stop_position_value, False)
|
||||||
|
|
||||||
|
|
||||||
class CoverTiltMixin(ZWaveBaseEntity, CoverEntity):
|
class CoverTiltMixin(ZWaveBaseEntity, CoverEntity):
|
||||||
@ -259,7 +259,7 @@ class CoverTiltMixin(ZWaveBaseEntity, CoverEntity):
|
|||||||
async def async_set_cover_tilt_position(self, **kwargs: Any) -> None:
|
async def async_set_cover_tilt_position(self, **kwargs: Any) -> None:
|
||||||
"""Move the cover tilt to a specific position."""
|
"""Move the cover tilt to a specific position."""
|
||||||
assert self._target_tilt_value
|
assert self._target_tilt_value
|
||||||
await self.info.node.async_set_value(
|
await self._async_set_value(
|
||||||
self._target_tilt_value,
|
self._target_tilt_value,
|
||||||
self.percent_to_zwave_tilt(kwargs[ATTR_TILT_POSITION]),
|
self.percent_to_zwave_tilt(kwargs[ATTR_TILT_POSITION]),
|
||||||
)
|
)
|
||||||
@ -267,22 +267,18 @@ class CoverTiltMixin(ZWaveBaseEntity, CoverEntity):
|
|||||||
async def async_open_cover_tilt(self, **kwargs: Any) -> None:
|
async def async_open_cover_tilt(self, **kwargs: Any) -> None:
|
||||||
"""Open the cover tilt."""
|
"""Open the cover tilt."""
|
||||||
assert self._target_tilt_value
|
assert self._target_tilt_value
|
||||||
await self.info.node.async_set_value(
|
await self._async_set_value(self._target_tilt_value, self._fully_open_tilt)
|
||||||
self._target_tilt_value, self._fully_open_tilt
|
|
||||||
)
|
|
||||||
|
|
||||||
async def async_close_cover_tilt(self, **kwargs: Any) -> None:
|
async def async_close_cover_tilt(self, **kwargs: Any) -> None:
|
||||||
"""Close the cover tilt."""
|
"""Close the cover tilt."""
|
||||||
assert self._target_tilt_value
|
assert self._target_tilt_value
|
||||||
await self.info.node.async_set_value(
|
await self._async_set_value(self._target_tilt_value, self._fully_closed_tilt)
|
||||||
self._target_tilt_value, self._fully_closed_tilt
|
|
||||||
)
|
|
||||||
|
|
||||||
async def async_stop_cover_tilt(self, **kwargs: Any) -> None:
|
async def async_stop_cover_tilt(self, **kwargs: Any) -> None:
|
||||||
"""Stop the cover tilt."""
|
"""Stop the cover tilt."""
|
||||||
assert self._stop_tilt_value
|
assert self._stop_tilt_value
|
||||||
# Stop the tilt, will stop regardless of the actual direction of travel.
|
# Stop the tilt, will stop regardless of the actual direction of travel.
|
||||||
await self.info.node.async_set_value(self._stop_tilt_value, False)
|
await self._async_set_value(self._stop_tilt_value, False)
|
||||||
|
|
||||||
|
|
||||||
class ZWaveMultilevelSwitchCover(CoverPositionMixin):
|
class ZWaveMultilevelSwitchCover(CoverPositionMixin):
|
||||||
@ -455,8 +451,8 @@ class ZwaveMotorizedBarrier(ZWaveBaseEntity, CoverEntity):
|
|||||||
|
|
||||||
async def async_open_cover(self, **kwargs: Any) -> None:
|
async def async_open_cover(self, **kwargs: Any) -> None:
|
||||||
"""Open the garage door."""
|
"""Open the garage door."""
|
||||||
await self.info.node.async_set_value(self._target_state, BarrierState.OPEN)
|
await self._async_set_value(self._target_state, BarrierState.OPEN)
|
||||||
|
|
||||||
async def async_close_cover(self, **kwargs: Any) -> None:
|
async def async_close_cover(self, **kwargs: Any) -> None:
|
||||||
"""Close the garage door."""
|
"""Close the garage door."""
|
||||||
await self.info.node.async_set_value(self._target_state, BarrierState.CLOSED)
|
await self._async_set_value(self._target_state, BarrierState.CLOSED)
|
||||||
|
@ -67,12 +67,20 @@ class ZWaveBaseEntity(Entity):
|
|||||||
To be overridden by platforms needing this event.
|
To be overridden by platforms needing this event.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
async def _async_poll_value(self, value_or_id: str | ZwaveValue) -> None:
|
||||||
|
"""Poll a value."""
|
||||||
|
# We log an error instead of raising an exception because this service call occurs
|
||||||
|
# in a separate task and we don't want to raise the exception in that separate task
|
||||||
|
# because it is confusing to the user.
|
||||||
|
try:
|
||||||
|
await self.info.node.async_poll_value(value_or_id)
|
||||||
|
except BaseZwaveJSServerError as err:
|
||||||
|
LOGGER.error("Error while refreshing value %s: %s", value_or_id, err)
|
||||||
|
|
||||||
async def async_poll_value(self, refresh_all_values: bool) -> None:
|
async def async_poll_value(self, refresh_all_values: bool) -> None:
|
||||||
"""Poll a value."""
|
"""Poll a value."""
|
||||||
if not refresh_all_values:
|
if not refresh_all_values:
|
||||||
self.hass.async_create_task(
|
self.hass.async_create_task(self._async_poll_value(self.info.primary_value))
|
||||||
self.info.node.async_poll_value(self.info.primary_value)
|
|
||||||
)
|
|
||||||
LOGGER.info(
|
LOGGER.info(
|
||||||
(
|
(
|
||||||
"Refreshing primary value %s for %s, "
|
"Refreshing primary value %s for %s, "
|
||||||
@ -84,7 +92,7 @@ class ZWaveBaseEntity(Entity):
|
|||||||
return
|
return
|
||||||
|
|
||||||
for value_id in self.watched_value_ids:
|
for value_id in self.watched_value_ids:
|
||||||
self.hass.async_create_task(self.info.node.async_poll_value(value_id))
|
self.hass.async_create_task(self._async_poll_value(value_id))
|
||||||
|
|
||||||
LOGGER.info(
|
LOGGER.info(
|
||||||
(
|
(
|
||||||
|
@ -100,7 +100,7 @@ class ZwaveFan(ZWaveBaseEntity, FanEntity):
|
|||||||
percentage_to_ranged_value(DEFAULT_SPEED_RANGE, percentage)
|
percentage_to_ranged_value(DEFAULT_SPEED_RANGE, percentage)
|
||||||
)
|
)
|
||||||
|
|
||||||
await self.info.node.async_set_value(self._target_value, zwave_speed)
|
await self._async_set_value(self._target_value, zwave_speed)
|
||||||
|
|
||||||
async def async_turn_on(
|
async def async_turn_on(
|
||||||
self,
|
self,
|
||||||
@ -122,15 +122,13 @@ class ZwaveFan(ZWaveBaseEntity, FanEntity):
|
|||||||
# when setting to a previous value to avoid waiting for the value to be
|
# when setting to a previous value to avoid waiting for the value to be
|
||||||
# updated from the device which is typically delayed and causes a confusing
|
# updated from the device which is typically delayed and causes a confusing
|
||||||
# UX.
|
# UX.
|
||||||
await self.info.node.async_set_value(
|
await self._async_set_value(self._target_value, SET_TO_PREVIOUS_VALUE)
|
||||||
self._target_value, SET_TO_PREVIOUS_VALUE
|
|
||||||
)
|
|
||||||
self._use_optimistic_state = True
|
self._use_optimistic_state = True
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
"""Turn the device off."""
|
"""Turn the device off."""
|
||||||
await self.info.node.async_set_value(self._target_value, 0)
|
await self._async_set_value(self._target_value, 0)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool | None:
|
def is_on(self) -> bool | None:
|
||||||
@ -174,13 +172,13 @@ class ValueMappingZwaveFan(ZwaveFan):
|
|||||||
async def async_set_percentage(self, percentage: int) -> None:
|
async def async_set_percentage(self, percentage: int) -> None:
|
||||||
"""Set the speed percentage of the fan."""
|
"""Set the speed percentage of the fan."""
|
||||||
zwave_speed = self.percentage_to_zwave_speed(percentage)
|
zwave_speed = self.percentage_to_zwave_speed(percentage)
|
||||||
await self.info.node.async_set_value(self._target_value, zwave_speed)
|
await self._async_set_value(self._target_value, zwave_speed)
|
||||||
|
|
||||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||||
"""Set new preset mode."""
|
"""Set new preset mode."""
|
||||||
for zwave_value, mapped_preset_mode in self.fan_value_mapping.presets.items():
|
for zwave_value, mapped_preset_mode in self.fan_value_mapping.presets.items():
|
||||||
if preset_mode == mapped_preset_mode:
|
if preset_mode == mapped_preset_mode:
|
||||||
await self.info.node.async_set_value(self._target_value, zwave_value)
|
await self._async_set_value(self._target_value, zwave_value)
|
||||||
return
|
return
|
||||||
|
|
||||||
raise NotValidPresetModeError(
|
raise NotValidPresetModeError(
|
||||||
@ -342,13 +340,13 @@ class ZwaveThermostatFan(ZWaveBaseEntity, FanEntity):
|
|||||||
"""Turn the device on."""
|
"""Turn the device on."""
|
||||||
if not self._fan_off:
|
if not self._fan_off:
|
||||||
raise HomeAssistantError("Unhandled action turn_on")
|
raise HomeAssistantError("Unhandled action turn_on")
|
||||||
await self.info.node.async_set_value(self._fan_off, False)
|
await self._async_set_value(self._fan_off, False)
|
||||||
|
|
||||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
"""Turn the device off."""
|
"""Turn the device off."""
|
||||||
if not self._fan_off:
|
if not self._fan_off:
|
||||||
raise HomeAssistantError("Unhandled action turn_off")
|
raise HomeAssistantError("Unhandled action turn_off")
|
||||||
await self.info.node.async_set_value(self._fan_off, True)
|
await self._async_set_value(self._fan_off, True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool | None:
|
def is_on(self) -> bool | None:
|
||||||
@ -377,7 +375,7 @@ class ZwaveThermostatFan(ZWaveBaseEntity, FanEntity):
|
|||||||
except StopIteration:
|
except StopIteration:
|
||||||
raise ValueError(f"Received an invalid fan mode: {preset_mode}") from None
|
raise ValueError(f"Received an invalid fan mode: {preset_mode}") from None
|
||||||
|
|
||||||
await self.info.node.async_set_value(self._fan_mode, new_state)
|
await self._async_set_value(self._fan_mode, new_state)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def preset_modes(self) -> list[str] | None:
|
def preset_modes(self) -> list[str] | None:
|
||||||
|
@ -175,7 +175,7 @@ class ZWaveHumidifier(ZWaveBaseEntity, HumidifierEntity):
|
|||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
await self.info.node.async_set_value(self._current_mode, new_mode)
|
await self._async_set_value(self._current_mode, new_mode)
|
||||||
|
|
||||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
"""Turn off device."""
|
"""Turn off device."""
|
||||||
@ -192,7 +192,7 @@ class ZWaveHumidifier(ZWaveBaseEntity, HumidifierEntity):
|
|||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
await self.info.node.async_set_value(self._current_mode, new_mode)
|
await self._async_set_value(self._current_mode, new_mode)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_humidity(self) -> int | None:
|
def target_humidity(self) -> int | None:
|
||||||
@ -204,7 +204,7 @@ class ZWaveHumidifier(ZWaveBaseEntity, HumidifierEntity):
|
|||||||
async def async_set_humidity(self, humidity: int) -> None:
|
async def async_set_humidity(self, humidity: int) -> None:
|
||||||
"""Set new target humidity."""
|
"""Set new target humidity."""
|
||||||
if self._setpoint:
|
if self._setpoint:
|
||||||
await self.info.node.async_set_value(self._setpoint, humidity)
|
await self._async_set_value(self._setpoint, humidity)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def min_humidity(self) -> int:
|
def min_humidity(self) -> int:
|
||||||
|
@ -324,9 +324,7 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity):
|
|||||||
color_name = MULTI_COLOR_MAP[color]
|
color_name = MULTI_COLOR_MAP[color]
|
||||||
colors_dict[color_name] = value
|
colors_dict[color_name] = value
|
||||||
# set updated color object
|
# set updated color object
|
||||||
await self.info.node.async_set_value(
|
await self._async_set_value(combined_color_val, colors_dict, zwave_transition)
|
||||||
combined_color_val, colors_dict, zwave_transition
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _async_set_brightness(
|
async def _async_set_brightness(
|
||||||
self, brightness: int | None, transition: float | None = None
|
self, brightness: int | None, transition: float | None = None
|
||||||
@ -350,7 +348,7 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity):
|
|||||||
zwave_transition = {TRANSITION_DURATION_OPTION: "default"}
|
zwave_transition = {TRANSITION_DURATION_OPTION: "default"}
|
||||||
|
|
||||||
# setting a value requires setting targetValue
|
# setting a value requires setting targetValue
|
||||||
await self.info.node.async_set_value(
|
await self._async_set_value(
|
||||||
self._target_brightness, zwave_brightness, zwave_transition
|
self._target_brightness, zwave_brightness, zwave_transition
|
||||||
)
|
)
|
||||||
# We do an optimistic state update when setting to a previous value
|
# We do an optimistic state update when setting to a previous value
|
||||||
|
@ -13,12 +13,14 @@ from zwave_js_server.const.command_class.lock import (
|
|||||||
LOCK_CMD_CLASS_TO_PROPERTY_MAP,
|
LOCK_CMD_CLASS_TO_PROPERTY_MAP,
|
||||||
DoorLockMode,
|
DoorLockMode,
|
||||||
)
|
)
|
||||||
|
from zwave_js_server.exceptions import BaseZwaveJSServerError
|
||||||
from zwave_js_server.util.lock import clear_usercode, set_usercode
|
from zwave_js_server.util.lock import clear_usercode, set_usercode
|
||||||
|
|
||||||
from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN, LockEntity
|
from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN, LockEntity
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED
|
from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import config_validation as cv, entity_platform
|
from homeassistant.helpers import config_validation as cv, entity_platform
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
@ -114,7 +116,7 @@ class ZWaveLock(ZWaveBaseEntity, LockEntity):
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
if target_value is not None:
|
if target_value is not None:
|
||||||
await self.info.node.async_set_value(
|
await self._async_set_value(
|
||||||
target_value,
|
target_value,
|
||||||
STATE_TO_ZWAVE_MAP[self.info.primary_value.command_class][target_state],
|
STATE_TO_ZWAVE_MAP[self.info.primary_value.command_class][target_state],
|
||||||
)
|
)
|
||||||
@ -129,10 +131,20 @@ class ZWaveLock(ZWaveBaseEntity, LockEntity):
|
|||||||
|
|
||||||
async def async_set_lock_usercode(self, code_slot: int, usercode: str) -> None:
|
async def async_set_lock_usercode(self, code_slot: int, usercode: str) -> None:
|
||||||
"""Set the usercode to index X on the lock."""
|
"""Set the usercode to index X on the lock."""
|
||||||
|
try:
|
||||||
await set_usercode(self.info.node, code_slot, usercode)
|
await set_usercode(self.info.node, code_slot, usercode)
|
||||||
|
except BaseZwaveJSServerError as err:
|
||||||
|
raise HomeAssistantError(
|
||||||
|
f"Unable to set lock usercode on code_slot {code_slot}: {err}"
|
||||||
|
) from err
|
||||||
LOGGER.debug("User code at slot %s set", code_slot)
|
LOGGER.debug("User code at slot %s set", code_slot)
|
||||||
|
|
||||||
async def async_clear_lock_usercode(self, code_slot: int) -> None:
|
async def async_clear_lock_usercode(self, code_slot: int) -> None:
|
||||||
"""Clear the usercode at index X on the lock."""
|
"""Clear the usercode at index X on the lock."""
|
||||||
|
try:
|
||||||
await clear_usercode(self.info.node, code_slot)
|
await clear_usercode(self.info.node, code_slot)
|
||||||
|
except BaseZwaveJSServerError as err:
|
||||||
|
raise HomeAssistantError(
|
||||||
|
f"Unable to clear lock usercode on code_slot {code_slot}: {err}"
|
||||||
|
) from err
|
||||||
LOGGER.debug("User code at slot %s cleared", code_slot)
|
LOGGER.debug("User code at slot %s cleared", code_slot)
|
||||||
|
@ -164,6 +164,6 @@ class ZwaveVolumeNumberEntity(ZWaveBaseEntity, NumberEntity):
|
|||||||
|
|
||||||
async def async_set_native_value(self, value: float) -> None:
|
async def async_set_native_value(self, value: float) -> None:
|
||||||
"""Set new value."""
|
"""Set new value."""
|
||||||
await self.info.node.async_set_value(
|
await self._async_set_value(
|
||||||
self.info.primary_value, round(value * self.correction_factor)
|
self.info.primary_value, round(value * self.correction_factor)
|
||||||
)
|
)
|
||||||
|
@ -92,7 +92,7 @@ class ZwaveSelectEntity(ZWaveBaseEntity, SelectEntity):
|
|||||||
for key, val in self.info.primary_value.metadata.states.items()
|
for key, val in self.info.primary_value.metadata.states.items()
|
||||||
if val == option
|
if val == option
|
||||||
)
|
)
|
||||||
await self.info.node.async_set_value(self.info.primary_value, int(key))
|
await self._async_set_value(self.info.primary_value, int(key))
|
||||||
|
|
||||||
|
|
||||||
class ZWaveConfigParameterSelectEntity(ZwaveSelectEntity):
|
class ZWaveConfigParameterSelectEntity(ZwaveSelectEntity):
|
||||||
@ -162,7 +162,7 @@ class ZwaveDefaultToneSelectEntity(ZWaveBaseEntity, SelectEntity):
|
|||||||
for key, val in self._tones_value.metadata.states.items()
|
for key, val in self._tones_value.metadata.states.items()
|
||||||
if val == option
|
if val == option
|
||||||
)
|
)
|
||||||
await self.info.node.async_set_value(self.info.primary_value, int(key))
|
await self._async_set_value(self.info.primary_value, int(key))
|
||||||
|
|
||||||
|
|
||||||
class ZwaveMultilevelSwitchSelectEntity(ZWaveBaseEntity, SelectEntity):
|
class ZwaveMultilevelSwitchSelectEntity(ZWaveBaseEntity, SelectEntity):
|
||||||
@ -197,4 +197,4 @@ class ZwaveMultilevelSwitchSelectEntity(ZWaveBaseEntity, SelectEntity):
|
|||||||
"""Change the selected option."""
|
"""Change the selected option."""
|
||||||
assert self._target_value is not None
|
assert self._target_value is not None
|
||||||
key = next(key for key, val in self._lookup_map.items() if val == option)
|
key = next(key for key, val in self._lookup_map.items() if val == option)
|
||||||
await self.info.node.async_set_value(self._target_value, int(key))
|
await self._async_set_value(self._target_value, int(key))
|
||||||
|
@ -11,6 +11,7 @@ from zwave_js_server.const.command_class.meter import (
|
|||||||
RESET_METER_OPTION_TARGET_VALUE,
|
RESET_METER_OPTION_TARGET_VALUE,
|
||||||
RESET_METER_OPTION_TYPE,
|
RESET_METER_OPTION_TYPE,
|
||||||
)
|
)
|
||||||
|
from zwave_js_server.exceptions import BaseZwaveJSServerError
|
||||||
from zwave_js_server.model.controller import Controller
|
from zwave_js_server.model.controller import Controller
|
||||||
from zwave_js_server.model.controller.statistics import ControllerStatisticsDataType
|
from zwave_js_server.model.controller.statistics import ControllerStatisticsDataType
|
||||||
from zwave_js_server.model.driver import Driver
|
from zwave_js_server.model.driver import Driver
|
||||||
@ -43,6 +44,7 @@ from homeassistant.const import (
|
|||||||
UnitOfTime,
|
UnitOfTime,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import entity_platform
|
from homeassistant.helpers import entity_platform
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
@ -671,9 +673,15 @@ class ZWaveMeterSensor(ZWaveNumericSensor):
|
|||||||
if value is not None:
|
if value is not None:
|
||||||
options[RESET_METER_OPTION_TARGET_VALUE] = value
|
options[RESET_METER_OPTION_TARGET_VALUE] = value
|
||||||
args = [options] if options else []
|
args = [options] if options else []
|
||||||
|
try:
|
||||||
await node.endpoints[endpoint].async_invoke_cc_api(
|
await node.endpoints[endpoint].async_invoke_cc_api(
|
||||||
CommandClass.METER, "reset", *args, wait_for_result=False
|
CommandClass.METER, "reset", *args, wait_for_result=False
|
||||||
)
|
)
|
||||||
|
except BaseZwaveJSServerError as err:
|
||||||
|
LOGGER.error(
|
||||||
|
"Failed to reset meters on node %s endpoint %s: %s", node, endpoint, err
|
||||||
|
)
|
||||||
|
raise HomeAssistantError from err
|
||||||
LOGGER.debug(
|
LOGGER.debug(
|
||||||
"Meters on node %s endpoint %s reset with the following options: %s",
|
"Meters on node %s endpoint %s reset with the following options: %s",
|
||||||
node,
|
node,
|
||||||
@ -802,6 +810,9 @@ class ZWaveNodeStatusSensor(SensorEntity):
|
|||||||
|
|
||||||
async def async_poll_value(self, _: bool) -> None:
|
async def async_poll_value(self, _: bool) -> None:
|
||||||
"""Poll a value."""
|
"""Poll a value."""
|
||||||
|
# We log an error instead of raising an exception because this service call occurs
|
||||||
|
# in a separate task since it is called via the dispatcher and we don't want to
|
||||||
|
# raise the exception in that separate task because it is confusing to the user.
|
||||||
LOGGER.error(
|
LOGGER.error(
|
||||||
"There is no value to refresh for this entity so the zwave_js.refresh_value"
|
"There is no value to refresh for this entity so the zwave_js.refresh_value"
|
||||||
" service won't work for it"
|
" service won't work for it"
|
||||||
@ -878,7 +889,10 @@ class ZWaveStatisticsSensor(SensorEntity):
|
|||||||
|
|
||||||
async def async_poll_value(self, _: bool) -> None:
|
async def async_poll_value(self, _: bool) -> None:
|
||||||
"""Poll a value."""
|
"""Poll a value."""
|
||||||
raise ValueError(
|
# We log an error instead of raising an exception because this service call occurs
|
||||||
|
# in a separate task since it is called via the dispatcher and we don't want to
|
||||||
|
# raise the exception in that separate task because it is confusing to the user.
|
||||||
|
LOGGER.error(
|
||||||
"There is no value to refresh for this entity so the zwave_js.refresh_value"
|
"There is no value to refresh for this entity so the zwave_js.refresh_value"
|
||||||
" service won't work for it"
|
" service won't work for it"
|
||||||
)
|
)
|
||||||
|
@ -633,8 +633,11 @@ class ZWaveServices:
|
|||||||
"calls will still work for now but the service will be removed in a "
|
"calls will still work for now but the service will be removed in a "
|
||||||
"future release"
|
"future release"
|
||||||
)
|
)
|
||||||
nodes: set[ZwaveNode] = service.data[const.ATTR_NODES]
|
nodes: list[ZwaveNode] = list(service.data[const.ATTR_NODES])
|
||||||
await asyncio.gather(*(node.async_ping() for node in nodes))
|
results = await asyncio.gather(
|
||||||
|
*(node.async_ping() for node in nodes), return_exceptions=True
|
||||||
|
)
|
||||||
|
raise_exceptions_from_results(nodes, results)
|
||||||
|
|
||||||
async def async_invoke_cc_api(self, service: ServiceCall) -> None:
|
async def async_invoke_cc_api(self, service: ServiceCall) -> None:
|
||||||
"""Invoke a command class API."""
|
"""Invoke a command class API."""
|
||||||
|
@ -79,14 +79,6 @@ class ZwaveSirenEntity(ZWaveBaseEntity, SirenEntity):
|
|||||||
return None
|
return None
|
||||||
return bool(self.info.primary_value.value)
|
return bool(self.info.primary_value.value)
|
||||||
|
|
||||||
async def async_set_value(
|
|
||||||
self, new_value: int, options: dict[str, Any] | None = None
|
|
||||||
) -> None:
|
|
||||||
"""Set a value on a siren node."""
|
|
||||||
await self.info.node.async_set_value(
|
|
||||||
self.info.primary_value, new_value, options=options
|
|
||||||
)
|
|
||||||
|
|
||||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
"""Turn the device on."""
|
"""Turn the device on."""
|
||||||
tone_id: int | None = kwargs.get(ATTR_TONE)
|
tone_id: int | None = kwargs.get(ATTR_TONE)
|
||||||
@ -95,11 +87,13 @@ class ZwaveSirenEntity(ZWaveBaseEntity, SirenEntity):
|
|||||||
options["volume"] = round(volume * 100)
|
options["volume"] = round(volume * 100)
|
||||||
# Play the default tone if a tone isn't provided
|
# Play the default tone if a tone isn't provided
|
||||||
if tone_id is None:
|
if tone_id is None:
|
||||||
await self.async_set_value(ToneID.DEFAULT, options)
|
await self._async_set_value(
|
||||||
|
self.info.primary_value, ToneID.DEFAULT, options
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
await self.async_set_value(tone_id, options)
|
await self._async_set_value(self.info.primary_value, tone_id, options)
|
||||||
|
|
||||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
"""Turn the device off."""
|
"""Turn the device off."""
|
||||||
await self.async_set_value(ToneID.OFF)
|
await self._async_set_value(self.info.primary_value, ToneID.OFF)
|
||||||
|
@ -291,6 +291,9 @@ class ZWaveNodeFirmwareUpdate(UpdateEntity):
|
|||||||
|
|
||||||
async def async_poll_value(self, _: bool) -> None:
|
async def async_poll_value(self, _: bool) -> None:
|
||||||
"""Poll a value."""
|
"""Poll a value."""
|
||||||
|
# We log an error instead of raising an exception because this service call occurs
|
||||||
|
# in a separate task since it is called via the dispatcher and we don't want to
|
||||||
|
# raise the exception in that separate task because it is confusing to the user.
|
||||||
LOGGER.error(
|
LOGGER.error(
|
||||||
"There is no value to refresh for this entity so the zwave_js.refresh_value"
|
"There is no value to refresh for this entity so the zwave_js.refresh_value"
|
||||||
" service won't work for it"
|
" service won't work for it"
|
||||||
|
@ -5,6 +5,7 @@ from zwave_js_server.const.command_class.thermostat import (
|
|||||||
THERMOSTAT_OPERATING_STATE_PROPERTY,
|
THERMOSTAT_OPERATING_STATE_PROPERTY,
|
||||||
)
|
)
|
||||||
from zwave_js_server.event import Event
|
from zwave_js_server.event import Event
|
||||||
|
from zwave_js_server.exceptions import FailedZWaveCommand
|
||||||
from zwave_js_server.model.node import Node
|
from zwave_js_server.model.node import Node
|
||||||
|
|
||||||
from homeassistant.components.climate import (
|
from homeassistant.components.climate import (
|
||||||
@ -30,6 +31,7 @@ from homeassistant.components.climate import (
|
|||||||
HVACMode,
|
HVACMode,
|
||||||
)
|
)
|
||||||
from homeassistant.components.zwave_js.climate import ATTR_FAN_STATE
|
from homeassistant.components.zwave_js.climate import ATTR_FAN_STATE
|
||||||
|
from homeassistant.components.zwave_js.const import DOMAIN, SERVICE_REFRESH_VALUE
|
||||||
from homeassistant.components.zwave_js.helpers import ZwaveValueMatcher
|
from homeassistant.components.zwave_js.helpers import ZwaveValueMatcher
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
@ -49,7 +51,11 @@ from .common import (
|
|||||||
|
|
||||||
|
|
||||||
async def test_thermostat_v2(
|
async def test_thermostat_v2(
|
||||||
hass: HomeAssistant, client, climate_radio_thermostat_ct100_plus, integration
|
hass: HomeAssistant,
|
||||||
|
client,
|
||||||
|
climate_radio_thermostat_ct100_plus,
|
||||||
|
integration,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test a thermostat v2 command class entity."""
|
"""Test a thermostat v2 command class entity."""
|
||||||
node = climate_radio_thermostat_ct100_plus
|
node = climate_radio_thermostat_ct100_plus
|
||||||
@ -280,6 +286,20 @@ async def test_thermostat_v2(
|
|||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Refresh value should log an error when there is an issue
|
||||||
|
client.async_send_command.reset_mock()
|
||||||
|
client.async_send_command.side_effect = FailedZWaveCommand("test", 1, "test")
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_REFRESH_VALUE,
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: CLIMATE_RADIO_THERMOSTAT_ENTITY,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "Error while refreshing value" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
async def test_thermostat_different_endpoints(
|
async def test_thermostat_different_endpoints(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Test the Z-Wave JS lock platform."""
|
"""Test the Z-Wave JS lock platform."""
|
||||||
|
import pytest
|
||||||
from zwave_js_server.const import CommandClass
|
from zwave_js_server.const import CommandClass
|
||||||
from zwave_js_server.const.command_class.lock import (
|
from zwave_js_server.const.command_class.lock import (
|
||||||
ATTR_CODE_SLOT,
|
ATTR_CODE_SLOT,
|
||||||
@ -6,6 +7,7 @@ from zwave_js_server.const.command_class.lock import (
|
|||||||
CURRENT_MODE_PROPERTY,
|
CURRENT_MODE_PROPERTY,
|
||||||
)
|
)
|
||||||
from zwave_js_server.event import Event
|
from zwave_js_server.event import Event
|
||||||
|
from zwave_js_server.exceptions import FailedZWaveCommand
|
||||||
from zwave_js_server.model.node import Node, NodeStatus
|
from zwave_js_server.model.node import Node, NodeStatus
|
||||||
|
|
||||||
from homeassistant.components.lock import (
|
from homeassistant.components.lock import (
|
||||||
@ -27,6 +29,7 @@ from homeassistant.const import (
|
|||||||
STATE_UNLOCKED,
|
STATE_UNLOCKED,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
|
||||||
from .common import SCHLAGE_BE469_LOCK_ENTITY, replace_value_of_zwave_value
|
from .common import SCHLAGE_BE469_LOCK_ENTITY, replace_value_of_zwave_value
|
||||||
|
|
||||||
@ -153,6 +156,33 @@ async def test_door_lock(
|
|||||||
}
|
}
|
||||||
assert args["value"] == 0
|
assert args["value"] == 0
|
||||||
|
|
||||||
|
client.async_send_command.reset_mock()
|
||||||
|
|
||||||
|
client.async_send_command.side_effect = FailedZWaveCommand("test", 1, "test")
|
||||||
|
# Test set usercode service error handling
|
||||||
|
with pytest.raises(HomeAssistantError):
|
||||||
|
await hass.services.async_call(
|
||||||
|
ZWAVE_JS_DOMAIN,
|
||||||
|
SERVICE_SET_LOCK_USERCODE,
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: SCHLAGE_BE469_LOCK_ENTITY,
|
||||||
|
ATTR_CODE_SLOT: 1,
|
||||||
|
ATTR_USERCODE: "1234",
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test clear usercode service error handling
|
||||||
|
with pytest.raises(HomeAssistantError):
|
||||||
|
await hass.services.async_call(
|
||||||
|
ZWAVE_JS_DOMAIN,
|
||||||
|
SERVICE_CLEAR_LOCK_USERCODE,
|
||||||
|
{ATTR_ENTITY_ID: SCHLAGE_BE469_LOCK_ENTITY, ATTR_CODE_SLOT: 1},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
client.async_send_command.reset_mock()
|
||||||
|
|
||||||
event = Event(
|
event = Event(
|
||||||
type="dead",
|
type="dead",
|
||||||
data={
|
data={
|
||||||
|
@ -4,6 +4,7 @@ import copy
|
|||||||
import pytest
|
import pytest
|
||||||
from zwave_js_server.const.command_class.meter import MeterType
|
from zwave_js_server.const.command_class.meter import MeterType
|
||||||
from zwave_js_server.event import Event
|
from zwave_js_server.event import Event
|
||||||
|
from zwave_js_server.exceptions import FailedZWaveCommand
|
||||||
from zwave_js_server.model.node import Node
|
from zwave_js_server.model.node import Node
|
||||||
|
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
@ -39,6 +40,7 @@ from homeassistant.const import (
|
|||||||
UnitOfTime,
|
UnitOfTime,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
from .common import (
|
from .common import (
|
||||||
@ -420,6 +422,18 @@ async def test_reset_meter(
|
|||||||
|
|
||||||
client.async_send_command_no_wait.reset_mock()
|
client.async_send_command_no_wait.reset_mock()
|
||||||
|
|
||||||
|
client.async_send_command_no_wait.side_effect = FailedZWaveCommand(
|
||||||
|
"test", 1, "test"
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(HomeAssistantError):
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_RESET_METER,
|
||||||
|
{ATTR_ENTITY_ID: METER_ENERGY_SENSOR},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_meter_attributes(
|
async def test_meter_attributes(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@ -609,7 +623,7 @@ NODE_STATISTICS_SUFFIXES_UNKNOWN = {
|
|||||||
|
|
||||||
|
|
||||||
async def test_statistics_sensors(
|
async def test_statistics_sensors(
|
||||||
hass: HomeAssistant, zp3111, client, integration
|
hass: HomeAssistant, zp3111, client, integration, caplog: pytest.LogCaptureFixture
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test statistics sensors."""
|
"""Test statistics sensors."""
|
||||||
ent_reg = er.async_get(hass)
|
ent_reg = er.async_get(hass)
|
||||||
@ -730,10 +744,27 @@ async def test_statistics_sensors(
|
|||||||
(NODE_STATISTICS_ENTITY_PREFIX, NODE_STATISTICS_SUFFIXES_UNKNOWN),
|
(NODE_STATISTICS_ENTITY_PREFIX, NODE_STATISTICS_SUFFIXES_UNKNOWN),
|
||||||
):
|
):
|
||||||
for suffix_key, val in suffixes.items():
|
for suffix_key, val in suffixes.items():
|
||||||
state = hass.states.get(f"{prefix}{suffix_key}")
|
entity_id = f"{prefix}{suffix_key}"
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
assert state
|
assert state
|
||||||
assert state.state == str(val)
|
assert state.state == str(val)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_REFRESH_VALUE,
|
||||||
|
{ATTR_ENTITY_ID: entity_id},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert caplog.text.count("There is no value to refresh for this entity") == len(
|
||||||
|
[
|
||||||
|
*CONTROLLER_STATISTICS_SUFFIXES,
|
||||||
|
*CONTROLLER_STATISTICS_SUFFIXES_UNKNOWN,
|
||||||
|
*NODE_STATISTICS_SUFFIXES,
|
||||||
|
*NODE_STATISTICS_SUFFIXES_UNKNOWN,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
ENERGY_PRODUCTION_ENTITY_MAP = {
|
ENERGY_PRODUCTION_ENTITY_MAP = {
|
||||||
"energy_production_power": {
|
"energy_production_power": {
|
||||||
|
@ -1539,6 +1539,18 @@ async def test_ping(
|
|||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
client.async_send_command.reset_mock()
|
||||||
|
client.async_send_command.side_effect = FailedZWaveCommand("test", 1, "test")
|
||||||
|
with pytest.raises(HomeAssistantError):
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_PING,
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: CLIMATE_RADIO_THERMOSTAT_ENTITY,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_invoke_cc_api(
|
async def test_invoke_cc_api(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user