Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 46 additions & 18 deletions homeassistant/components/zwave_js/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
from zwave_js_server.client import Client as ZwaveClient
from zwave_js_server.exceptions import BaseZwaveJSServerError, InvalidServerVersion
from zwave_js_server.model.node import Node as ZwaveNode
from zwave_js_server.model.notification import Notification
from zwave_js_server.model.notification import (
EntryControlNotification,
NotificationNotification,
)
from zwave_js_server.model.value import ValueNotification

from homeassistant.config_entries import ConfigEntry
Expand All @@ -29,7 +32,12 @@
from .const import (
ATTR_COMMAND_CLASS,
ATTR_COMMAND_CLASS_NAME,
ATTR_DATA_TYPE,
ATTR_ENDPOINT,
ATTR_EVENT,
ATTR_EVENT_DATA,
ATTR_EVENT_LABEL,
ATTR_EVENT_TYPE,
ATTR_HOME_ID,
ATTR_LABEL,
ATTR_NODE_ID,
Expand All @@ -51,7 +59,8 @@
EVENT_DEVICE_ADDED_TO_REGISTRY,
LOGGER,
PLATFORMS,
ZWAVE_JS_EVENT,
ZWAVE_JS_NOTIFICATION_EVENT,
ZWAVE_JS_VALUE_NOTIFICATION_EVENT,
)
from .discovery import async_discover_values
from .helpers import get_device_id
Expand Down Expand Up @@ -102,7 +111,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await async_ensure_addon_running(hass, entry)

client = ZwaveClient(entry.data[CONF_URL], async_get_clientsession(hass))
dev_reg = await device_registry.async_get_registry(hass)
dev_reg = device_registry.async_get(hass)
ent_reg = entity_registry.async_get(hass)

@callback
Expand Down Expand Up @@ -169,9 +178,8 @@ def async_on_value_notification(notification: ValueNotification) -> None:
if notification.metadata.states:
value = notification.metadata.states.get(str(value), value)
hass.bus.async_fire(
ZWAVE_JS_EVENT,
ZWAVE_JS_VALUE_NOTIFICATION_EVENT,
{
ATTR_TYPE: "value_notification",
ATTR_DOMAIN: DOMAIN,
ATTR_NODE_ID: notification.node.node_id,
ATTR_HOME_ID: client.driver.controller.home_id,
Expand All @@ -190,21 +198,41 @@ def async_on_value_notification(notification: ValueNotification) -> None:
)

@callback
def async_on_notification(notification: Notification) -> None:
def async_on_notification(
notification: EntryControlNotification | NotificationNotification,
) -> None:
"""Relay stateless notification events from Z-Wave nodes to hass."""
device = dev_reg.async_get_device({get_device_id(client, notification.node)})
hass.bus.async_fire(
ZWAVE_JS_EVENT,
{
ATTR_TYPE: "notification",
ATTR_DOMAIN: DOMAIN,
ATTR_NODE_ID: notification.node.node_id,
ATTR_HOME_ID: client.driver.controller.home_id,
ATTR_DEVICE_ID: device.id, # type: ignore
ATTR_LABEL: notification.notification_label,
ATTR_PARAMETERS: notification.parameters,
},
)
event_data = {
ATTR_DOMAIN: DOMAIN,
ATTR_NODE_ID: notification.node.node_id,
ATTR_HOME_ID: client.driver.controller.home_id,
ATTR_DEVICE_ID: device.id, # type: ignore
ATTR_COMMAND_CLASS: notification.command_class,
}

if isinstance(notification, EntryControlNotification):
event_data.update(
{
ATTR_COMMAND_CLASS_NAME: "Entry Control",
ATTR_EVENT_TYPE: notification.event_type,
ATTR_DATA_TYPE: notification.data_type,
ATTR_EVENT_DATA: notification.event_data,
}
)
else:
event_data.update(
{
ATTR_COMMAND_CLASS_NAME: "Notification",
ATTR_LABEL: notification.label,
ATTR_TYPE: notification.type_,
ATTR_EVENT: notification.event,
ATTR_EVENT_LABEL: notification.event_label,
ATTR_PARAMETERS: notification.parameters,
}
)

hass.bus.async_fire(ZWAVE_JS_NOTIFICATION_EVENT, event_data)

entry_hass_data: dict = hass.data[DOMAIN].setdefault(entry.entry_id, {})
# connect and throw error if connection failed
Expand Down
20 changes: 11 additions & 9 deletions homeassistant/components/zwave_js/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@
ENABLED = "enabled"
FORCE_CONSOLE = "force_console"

# constants for setting config parameters
VALUE_ID = "value_id"
STATUS = "status"


@callback
def async_register_api(hass: HomeAssistant) -> None:
Expand Down Expand Up @@ -321,7 +325,7 @@ async def websocket_set_config_parameter(
client = hass.data[DOMAIN][entry_id][DATA_CLIENT]
node = client.driver.controller.nodes[node_id]
try:
result = await async_set_config_parameter(
zwave_value, cmd_status = await async_set_config_parameter(
node, value, property_, property_key=property_key
)
except (InvalidNewValue, NotFoundError, NotImplementedError, SetValueFailed) as err:
Expand All @@ -340,7 +344,10 @@ async def websocket_set_config_parameter(

connection.send_result(
msg[ID],
str(result),
{
VALUE_ID: zwave_value.value_id,
STATUS: cmd_status,
},
)


Expand Down Expand Up @@ -395,11 +402,6 @@ def websocket_get_config_parameters(
)


def convert_log_level_to_enum(value: str) -> LogLevel:
"""Convert log level string to LogLevel enum."""
return LogLevel[value.upper()]


def filename_is_present_if_logging_to_file(obj: dict) -> dict:
"""Validate that filename is provided if log_to_file is True."""
if obj.get(LOG_TO_FILE, False) and FILENAME not in obj:
Expand All @@ -420,8 +422,8 @@ def filename_is_present_if_logging_to_file(obj: dict) -> dict:
vol.Optional(LEVEL): vol.All(
cv.string,
vol.Lower,
vol.In([log_level.name.lower() for log_level in LogLevel]),
lambda val: LogLevel[val.upper()],
vol.In([log_level.value for log_level in LogLevel]),
lambda val: LogLevel(val), # pylint: disable=unnecessary-lambda
),
vol.Optional(LOG_TO_FILE): cv.boolean,
vol.Optional(FILENAME): cv.string,
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/zwave_js/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ def __init__(
self._setpoint_values[enum] = self.get_zwave_value(
THERMOSTAT_SETPOINT_PROPERTY,
command_class=CommandClass.THERMOSTAT_SETPOINT,
value_property_key=enum.value.key,
value_property_key=enum.value,
add_to_watched_value_ids=True,
)
# Use the first found setpoint value to always determine the temperature unit
Expand Down
8 changes: 7 additions & 1 deletion homeassistant/components/zwave_js/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
LOGGER = logging.getLogger(__package__)

# constants for events
ZWAVE_JS_EVENT = f"{DOMAIN}_event"
ZWAVE_JS_VALUE_NOTIFICATION_EVENT = f"{DOMAIN}_value_notification"
ZWAVE_JS_NOTIFICATION_EVENT = f"{DOMAIN}_notification"
ATTR_NODE_ID = "node_id"
ATTR_HOME_ID = "home_id"
ATTR_ENDPOINT = "endpoint"
Expand All @@ -43,6 +44,11 @@
ATTR_PROPERTY = "property"
ATTR_PROPERTY_KEY = "property_key"
ATTR_PARAMETERS = "parameters"
ATTR_EVENT = "event"
ATTR_EVENT_LABEL = "event_label"
ATTR_EVENT_TYPE = "event_type"
ATTR_EVENT_DATA = "event_data"
ATTR_DATA_TYPE = "data_type"

# service constants
SERVICE_SET_CONFIG_PARAMETER = "set_config_parameter"
Expand Down
13 changes: 6 additions & 7 deletions homeassistant/components/zwave_js/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,12 +247,11 @@ async def _async_set_colors(self, colors: dict[ColorComponent, int]) -> None:

async def _async_set_color(self, color: ColorComponent, new_value: int) -> None:
"""Set defined color to given value."""
property_key = color.value
# actually set the new color value
target_zwave_value = self.get_zwave_value(
"targetColor",
CommandClass.SWITCH_COLOR,
value_property_key=property_key.key,
value_property_key=color.value,
)
if target_zwave_value is None:
# guard for unsupported color
Expand Down Expand Up @@ -315,27 +314,27 @@ def _calculate_color_values(self) -> None:
red_val = self.get_zwave_value(
"currentColor",
CommandClass.SWITCH_COLOR,
value_property_key=ColorComponent.RED.value.key,
value_property_key=ColorComponent.RED.value,
)
green_val = self.get_zwave_value(
"currentColor",
CommandClass.SWITCH_COLOR,
value_property_key=ColorComponent.GREEN.value.key,
value_property_key=ColorComponent.GREEN.value,
)
blue_val = self.get_zwave_value(
"currentColor",
CommandClass.SWITCH_COLOR,
value_property_key=ColorComponent.BLUE.value.key,
value_property_key=ColorComponent.BLUE.value,
)
ww_val = self.get_zwave_value(
"currentColor",
CommandClass.SWITCH_COLOR,
value_property_key=ColorComponent.WARM_WHITE.value.key,
value_property_key=ColorComponent.WARM_WHITE.value,
)
cw_val = self.get_zwave_value(
"currentColor",
CommandClass.SWITCH_COLOR,
value_property_key=ColorComponent.COLD_WHITE.value.key,
value_property_key=ColorComponent.COLD_WHITE.value,
)
# prefer the (new) combined color property
# https://github.com/zwave-js/node-zwave-js/pull/1782
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/zwave_js/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "Z-Wave JS",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/zwave_js",
"requirements": ["zwave-js-server-python==0.22.0"],
"requirements": ["zwave-js-server-python==0.23.0"],
"codeowners": ["@home-assistant/z-wave"],
"dependencies": ["http", "websocket_api"]
}
20 changes: 9 additions & 11 deletions homeassistant/components/zwave_js/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import logging

import voluptuous as vol
from zwave_js_server.const import CommandStatus
from zwave_js_server.model.node import Node as ZwaveNode
from zwave_js_server.util.node import async_set_config_parameter

Expand Down Expand Up @@ -104,26 +105,23 @@ async def async_set_config_parameter(self, service: ServiceCall) -> None:
new_value = service.data[const.ATTR_CONFIG_VALUE]

for node in nodes:
zwave_value = await async_set_config_parameter(
zwave_value, cmd_status = await async_set_config_parameter(
node,
new_value,
property_or_property_name,
property_key=property_key,
)

if zwave_value:
_LOGGER.info(
"Set configuration parameter %s on Node %s with value %s",
zwave_value,
node,
new_value,
)
if cmd_status == CommandStatus.ACCEPTED:
msg = "Set configuration parameter %s on Node %s with value %s"
else:
raise ValueError(
f"Unable to set configuration parameter on Node {node} with "
f"value {new_value}"
msg = (
"Added command to queue to set configuration parameter %s on Node "
"%s with value %s. Parameter will be set when the device wakes up"
)

_LOGGER.info(msg, zwave_value, node, new_value)

async def async_poll_value(self, service: ServiceCall) -> None:
"""Poll value on a node."""
for entity_id in service.data[ATTR_ENTITY_ID]:
Expand Down
2 changes: 1 addition & 1 deletion requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2399,4 +2399,4 @@ zigpy==0.33.0
zm-py==0.5.2

# homeassistant.components.zwave_js
zwave-js-server-python==0.22.0
zwave-js-server-python==0.23.0
2 changes: 1 addition & 1 deletion requirements_test_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1242,4 +1242,4 @@ zigpy-znp==0.4.0
zigpy==0.33.0

# homeassistant.components.zwave_js
zwave-js-server-python==0.22.0
zwave-js-server-python==0.23.0
6 changes: 3 additions & 3 deletions tests/components/zwave_js/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ async def test_update_log_config(hass, client, integration, hass_ws_client):
assert len(client.async_send_command.call_args_list) == 1
args = client.async_send_command.call_args[0][0]
assert args["command"] == "update_log_config"
assert args["config"] == {"level": 0}
assert args["config"] == {"level": "error"}

client.async_send_command.reset_mock()

Expand Down Expand Up @@ -428,7 +428,7 @@ async def test_update_log_config(hass, client, integration, hass_ws_client):
args = client.async_send_command.call_args[0][0]
assert args["command"] == "update_log_config"
assert args["config"] == {
"level": 0,
"level": "error",
"logToFile": True,
"filename": "/test",
"forceConsole": True,
Expand Down Expand Up @@ -490,7 +490,7 @@ async def test_get_log_config(hass, client, integration, hass_ws_client):
"success": True,
"config": {
"enabled": True,
"level": 0,
"level": "error",
"logToFile": False,
"filename": "/test.txt",
"forceConsole": False,
Expand Down
Loading