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
6 changes: 3 additions & 3 deletions homeassistant/components/light/zwave.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@

def get_device(node, values, node_config, **kwargs):
"""Create Z-Wave entity device."""
name = '{}.{}'.format(DOMAIN, zwave.object_id(values.primary))
refresh = node_config.get(zwave.CONF_REFRESH_VALUE)
delay = node_config.get(zwave.CONF_REFRESH_DELAY)
_LOGGER.debug("name=%s node_config=%s CONF_REFRESH_VALUE=%s"
" CONF_REFRESH_DELAY=%s", name, node_config, refresh, delay)
_LOGGER.debug("node=%d value=%d node_config=%s CONF_REFRESH_VALUE=%s"
" CONF_REFRESH_DELAY=%s", node.node_id,
values.primary.value_id, node_config, refresh, delay)

if node.has_command_class(zwave.const.COMMAND_CLASS_SWITCH_COLOR):
return ZwaveColorLight(values, refresh, delay)
Expand Down
69 changes: 39 additions & 30 deletions homeassistant/components/zwave/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from homeassistant.core import CoreState
from homeassistant.loader import get_platform
from homeassistant.helpers import discovery
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.const import (
ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
Expand Down Expand Up @@ -55,6 +56,7 @@
CONF_DEVICE_CONFIG_GLOB = 'device_config_glob'
CONF_DEVICE_CONFIG_DOMAIN = 'device_config_domain'
CONF_NETWORK_KEY = 'network_key'
CONF_NEW_ENTITY_IDS = 'new_entity_ids'

ATTR_POWER = 'power_consumption'

Expand Down Expand Up @@ -149,6 +151,7 @@
cv.positive_int,
vol.Optional(CONF_USB_STICK_PATH, default=DEFAULT_CONF_USB_STICK_PATH):
cv.string,
vol.Optional(CONF_NEW_ENTITY_IDS, default=False): cv.boolean,
}),
}, extra=vol.ALLOW_EXTRA)

Expand All @@ -162,7 +165,7 @@ def _obj_to_dict(obj):

def _value_name(value):
"""Return the name of the value."""
return '{} {}'.format(node_name(value.node), value.label)
return '{} {}'.format(node_name(value.node), value.label).strip()


def _node_object_id(node):
Expand Down Expand Up @@ -250,6 +253,13 @@ def setup(hass, config):
config[DOMAIN][CONF_DEVICE_CONFIG],
config[DOMAIN][CONF_DEVICE_CONFIG_DOMAIN],
config[DOMAIN][CONF_DEVICE_CONFIG_GLOB])
new_entity_ids = config[DOMAIN][CONF_NEW_ENTITY_IDS]
if not new_entity_ids:
_LOGGER.warning(
"ZWave entity_ids will soon be changing. To opt in to new "
"entity_ids now, set `new_entity_ids: true` under zwave in your "
"configuration.yaml. See the following blog post for details: "
"https://home-assistant.io/blog/2017/06/15/zwave-entity-ids/")

# Setup options
options = ZWaveOption(
Expand Down Expand Up @@ -311,30 +321,20 @@ def value_added(node, value):

def node_added(node):
"""Handle a new node on the network."""
entity = ZWaveNodeEntity(node, network)
node_config = device_config.get(entity.entity_id)
entity = ZWaveNodeEntity(node, network, new_entity_ids)
name = node_name(node)
if new_entity_ids:
generated_id = generate_entity_id(DOMAIN + '.{}', name, [])
else:
generated_id = entity.entity_id
node_config = device_config.get(generated_id)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the ID list in line 327 is empty it will just slugify it without collision prevention.
So in case of collision both device will have the same ID here pulling the same config.

Also, since rename_value acts by value_id which is not exposed to the UI in any way, does user using the UI rename card has a way to determine which of the two identical entities they are renaming?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So for the first point, unfortunately the ID post collision detection can't be used to ignore a device, since that ID is only available after the entity is added to hass. Colliding entity_ids aren't really meant to be used long-term anyway, so after they are renamed it won't be an issue.

For the second point, what do you think about exposing the value ID in the device state attributes, then including it in the rename card drop-down?

if node_config.get(CONF_IGNORED):
_LOGGER.info(
"Ignoring node entity %s due to device settings",
entity.entity_id)
generated_id)
return
component.add_entities([entity])

def scene_activated(node, scene_id):
"""Handle an activated scene on any node in the network."""
hass.bus.fire(const.EVENT_SCENE_ACTIVATED, {
ATTR_ENTITY_ID: _node_object_id(node),
const.ATTR_OBJECT_ID: _node_object_id(node),
const.ATTR_SCENE_ID: scene_id
})

def node_event_activated(node, value):
"""Handle a nodeevent on any node in the network."""
hass.bus.fire(const.EVENT_NODE_EVENT, {
const.ATTR_OBJECT_ID: _node_object_id(node),
const.ATTR_BASIC_LEVEL: value
})

def network_ready():
"""Handle the query of all awake nodes."""
_LOGGER.info("Zwave network is ready for use. All awake nodes "
Expand All @@ -352,10 +352,6 @@ def network_complete():
value_added, ZWaveNetwork.SIGNAL_VALUE_ADDED, weak=False)
dispatcher.connect(
node_added, ZWaveNetwork.SIGNAL_NODE_ADDED, weak=False)
dispatcher.connect(
scene_activated, ZWaveNetwork.SIGNAL_SCENE_EVENT, weak=False)
dispatcher.connect(
node_event_activated, ZWaveNetwork.SIGNAL_NODE_EVENT, weak=False)
dispatcher.connect(
network_ready, ZWaveNetwork.SIGNAL_AWAKE_NODES_QUERIED, weak=False)
dispatcher.connect(
Expand Down Expand Up @@ -757,18 +753,22 @@ def _check_entity_ready(self):
self.primary)
if workaround_component and workaround_component != component:
if workaround_component == workaround.WORKAROUND_IGNORE:
_LOGGER.info("Ignoring device %s due to workaround.",
"{}.{}".format(
component, object_id(self.primary)))
_LOGGER.info("Ignoring Node %d Value %d due to workaround.",
self.primary.node.node_id, self.primary.value_id)
# No entity will be created for this value
self._workaround_ignore = True
return
_LOGGER.debug("Using %s instead of %s",
workaround_component, component)
component = workaround_component

name = "{}.{}".format(component, object_id(self.primary))
node_config = self._device_config.get(name)
value_name = _value_name(self.primary)
if self._zwave_config[DOMAIN][CONF_NEW_ENTITY_IDS]:
generated_id = generate_entity_id(
component + '.{}', value_name, [])
else:
generated_id = "{}.{}".format(component, object_id(self.primary))
node_config = self._device_config.get(generated_id)

# Configure node
_LOGGER.debug("Adding Node_id=%s Generic_command_class=%s, "
Expand All @@ -781,7 +781,7 @@ def _check_entity_ready(self):

if node_config.get(CONF_IGNORED):
_LOGGER.info(
"Ignoring entity %s due to device settings", name)
"Ignoring entity %s due to device settings", generated_id)
# No entity will be created for this value
self._workaround_ignore = True
return
Expand All @@ -802,6 +802,12 @@ def _check_entity_ready(self):
self._workaround_ignore = True
return

device.old_entity_id = "{}.{}".format(
component, object_id(self.primary))
device.new_entity_id = "{}.{}".format(component, slugify(device.name))
if not self._zwave_config[DOMAIN][CONF_NEW_ENTITY_IDS]:
device.entity_id = device.old_entity_id

self._entity = device

dict_id = id(self)
Expand All @@ -828,7 +834,6 @@ def __init__(self, values, domain):
self.values = values
self.node = values.primary.node
self.values.primary.set_change_verified(False)
self.entity_id = "{}.{}".format(domain, object_id(values.primary))

self._name = _value_name(self.values.primary)
self._unique_id = "ZWAVE-{}-{}".format(self.node.node_id,
Expand Down Expand Up @@ -895,6 +900,10 @@ def device_state_attributes(self):
"""Return the device specific state attributes."""
attrs = {
const.ATTR_NODE_ID: self.node_id,
const.ATTR_VALUE_INDEX: self.values.primary.index,
const.ATTR_VALUE_INSTANCE: self.values.primary.instance,
'old_entity_id': self.old_entity_id,
'new_entity_id': self.new_entity_id,
}

if self.power_consumption is not None:
Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/zwave/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ def get(self, request, node_id):

values_data[entity_values.primary.value_id] = {
'label': entity_values.primary.label,
'index': entity_values.primary.index,
'instance': entity_values.primary.instance,
}
return self.json(values_data)

Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/zwave/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
ATTR_CONFIG_PARAMETER = "parameter"
ATTR_CONFIG_SIZE = "size"
ATTR_CONFIG_VALUE = "value"
ATTR_VALUE_INDEX = "value_index"
ATTR_VALUE_INSTANCE = "value_instance"
NETWORK_READY_WAIT_SECS = 30

DISCOVERY_DEVICE = 'device'
Expand Down
53 changes: 49 additions & 4 deletions homeassistant/components/zwave/node_entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
import logging

from homeassistant.core import callback
from homeassistant.const import ATTR_BATTERY_LEVEL, ATTR_WAKEUP
from homeassistant.const import ATTR_BATTERY_LEVEL, ATTR_WAKEUP, ATTR_ENTITY_ID
from homeassistant.helpers.entity import Entity
from homeassistant.util import slugify

from .const import ATTR_NODE_ID, DOMAIN, COMMAND_CLASS_WAKE_UP
from .const import (
ATTR_NODE_ID, COMMAND_CLASS_WAKE_UP, ATTR_SCENE_ID, ATTR_BASIC_LEVEL,
EVENT_NODE_EVENT, EVENT_SCENE_ACTIVATED, DOMAIN)
from .util import node_name

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -38,6 +40,8 @@ class ZWaveBaseEntity(Entity):
def __init__(self):
"""Initialize the base Z-Wave class."""
self._update_scheduled = False
self.old_entity_id = None
self.new_entity_id = None

def maybe_schedule_update(self):
"""Maybe schedule state update.
Expand Down Expand Up @@ -72,7 +76,7 @@ def sub_status(status, stage):
class ZWaveNodeEntity(ZWaveBaseEntity):
"""Representation of a Z-Wave node."""

def __init__(self, node, network):
def __init__(self, node, network, new_entity_ids):
"""Initialize node."""
# pylint: disable=import-error
super().__init__()
Expand All @@ -84,8 +88,11 @@ def __init__(self, node, network):
self._name = node_name(self.node)
self._product_name = node.product_name
self._manufacturer_name = node.manufacturer_name
self.entity_id = "{}.{}_{}".format(
self.old_entity_id = "{}.{}_{}".format(
DOMAIN, slugify(self._name), self.node_id)
self.new_entity_id = "{}.{}".format(DOMAIN, slugify(self._name))
if not new_entity_ids:
self.entity_id = self.old_entity_id
self._attributes = {}
self.wakeup_interval = None
self.location = None
Expand All @@ -95,6 +102,10 @@ def __init__(self, node, network):
dispatcher.connect(self.network_node_changed, ZWaveNetwork.SIGNAL_NODE)
dispatcher.connect(
self.network_node_changed, ZWaveNetwork.SIGNAL_NOTIFICATION)
dispatcher.connect(
self.network_node_event, ZWaveNetwork.SIGNAL_NODE_EVENT)
dispatcher.connect(
self.network_scene_activated, ZWaveNetwork.SIGNAL_SCENE_EVENT)

def network_node_changed(self, node=None, args=None):
"""Handle a changed node on the network."""
Expand Down Expand Up @@ -134,6 +145,38 @@ def node_changed(self):

self.maybe_schedule_update()

def network_node_event(self, node, value):
"""Handle a node activated event on the network."""
if node.node_id == self.node.node_id:
self.node_event(value)

def node_event(self, value):
"""Handle a node activated event for this node."""
if self.hass is None:
return

self.hass.bus.fire(EVENT_NODE_EVENT, {
ATTR_ENTITY_ID: self.entity_id,
ATTR_NODE_ID: self.node.node_id,
ATTR_BASIC_LEVEL: value
})

def network_scene_activated(self, node, scene_id):
"""Handle a scene activated event on the network."""
if node.node_id == self.node.node_id:
self.scene_activated(scene_id)

def scene_activated(self, scene_id):
"""Handle an activated scene for this node."""
if self.hass is None:
return

self.hass.bus.fire(EVENT_SCENE_ACTIVATED, {
ATTR_ENTITY_ID: self.entity_id,
ATTR_NODE_ID: self.node.node_id,
ATTR_SCENE_ID: scene_id
})

@property
def state(self):
"""Return the state."""
Expand Down Expand Up @@ -169,6 +212,8 @@ def device_state_attributes(self):
ATTR_NODE_NAME: self._name,
ATTR_MANUFACTURER_NAME: self._manufacturer_name,
ATTR_PRODUCT_NAME: self._product_name,
'old_entity_id': self.old_entity_id,
'new_entity_id': self.new_entity_id,
}
attrs.update(self._attributes)
if self.battery_level is not None:
Expand Down
5 changes: 4 additions & 1 deletion tests/components/zwave/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ def test_get_values(hass, test_client):
ZWaveNodeValueView().register(app.router)

node = MockNode(node_id=1)
value = MockValue(value_id=123456, node=node, label='Test Label')
value = MockValue(value_id=123456, node=node, label='Test Label',
instance=1, index=2)
values = MockEntityValues(primary=value)
node2 = MockNode(node_id=2)
value2 = MockValue(value_id=234567, node=node2, label='Test Label 2')
Expand All @@ -33,6 +34,8 @@ def test_get_values(hass, test_client):
assert result == {
'123456': {
'label': 'Test Label',
'instance': 1,
'index': 2,
}
}

Expand Down
Loading