mirror of
https://github.com/home-assistant/core.git
synced 2025-04-24 09:17:53 +00:00
Add zwave to ozw migration (#39081)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com> Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
parent
982c42e746
commit
8b72324ae6
@ -36,6 +36,7 @@ from .const import (
|
||||
DATA_UNSUBSCRIBE,
|
||||
DOMAIN,
|
||||
MANAGER,
|
||||
NODES_VALUES,
|
||||
PLATFORMS,
|
||||
TOPIC_OPENZWAVE,
|
||||
)
|
||||
@ -68,7 +69,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
ozw_data[DATA_UNSUBSCRIBE] = []
|
||||
|
||||
data_nodes = {}
|
||||
data_values = {}
|
||||
hass.data[DOMAIN][NODES_VALUES] = data_values = {}
|
||||
removed_nodes = []
|
||||
manager_options = {"topic_prefix": f"{TOPIC_OPENZWAVE}/"}
|
||||
|
||||
|
@ -37,6 +37,15 @@ class DomainConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
self.integration_created_addon = False
|
||||
self.install_task = None
|
||||
|
||||
async def async_step_import(self, data):
|
||||
"""Handle imported data.
|
||||
|
||||
This step will be used when importing data during zwave to ozw migration.
|
||||
"""
|
||||
self.network_key = data.get(CONF_NETWORK_KEY)
|
||||
self.usb_path = data.get(CONF_USB_PATH)
|
||||
return await self.async_step_user()
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle the initial step."""
|
||||
if self._async_current_entries():
|
||||
@ -163,13 +172,15 @@ class DomainConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
else:
|
||||
return self._async_create_entry_from_vars()
|
||||
|
||||
self.usb_path = self.addon_config.get(CONF_ADDON_DEVICE, "")
|
||||
self.network_key = self.addon_config.get(CONF_ADDON_NETWORK_KEY, "")
|
||||
usb_path = self.addon_config.get(CONF_ADDON_DEVICE, self.usb_path or "")
|
||||
network_key = self.addon_config.get(
|
||||
CONF_ADDON_NETWORK_KEY, self.network_key or ""
|
||||
)
|
||||
|
||||
data_schema = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_USB_PATH, default=self.usb_path): str,
|
||||
vol.Optional(CONF_NETWORK_KEY, default=self.network_key): str,
|
||||
vol.Required(CONF_USB_PATH, default=usb_path): str,
|
||||
vol.Optional(CONF_NETWORK_KEY, default=network_key): str,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -25,6 +25,7 @@ PLATFORMS = [
|
||||
SWITCH_DOMAIN,
|
||||
]
|
||||
MANAGER = "manager"
|
||||
NODES_VALUES = "nodes_values"
|
||||
|
||||
# MQTT Topics
|
||||
TOPIC_OPENZWAVE = "OpenZWave"
|
||||
@ -40,6 +41,9 @@ ATTR_SCENE_LABEL = "scene_label"
|
||||
ATTR_SCENE_VALUE_ID = "scene_value_id"
|
||||
ATTR_SCENE_VALUE_LABEL = "scene_value_label"
|
||||
|
||||
# Config entry data and options
|
||||
MIGRATED = "migrated"
|
||||
|
||||
# Service specific
|
||||
SERVICE_ADD_NODE = "add_node"
|
||||
SERVICE_REMOVE_NODE = "remove_node"
|
||||
|
@ -7,7 +7,8 @@
|
||||
"python-openzwave-mqtt[mqtt-client]==1.4.0"
|
||||
],
|
||||
"after_dependencies": [
|
||||
"mqtt"
|
||||
"mqtt",
|
||||
"zwave"
|
||||
],
|
||||
"codeowners": [
|
||||
"@cgarwood",
|
||||
|
171
homeassistant/components/ozw/migration.py
Normal file
171
homeassistant/components/ozw/migration.py
Normal file
@ -0,0 +1,171 @@
|
||||
"""Provide tools for migrating from the zwave integration."""
|
||||
from homeassistant.helpers.device_registry import (
|
||||
async_get_registry as async_get_device_registry,
|
||||
)
|
||||
from homeassistant.helpers.entity_registry import (
|
||||
async_entries_for_config_entry,
|
||||
async_get_registry as async_get_entity_registry,
|
||||
)
|
||||
|
||||
from .const import DOMAIN, MIGRATED, NODES_VALUES
|
||||
from .entity import create_device_id, create_value_id
|
||||
|
||||
# The following dicts map labels between OpenZWave 1.4 and 1.6.
|
||||
METER_CC_LABELS = {
|
||||
"Energy": "Electric - kWh",
|
||||
"Power": "Electric - W",
|
||||
"Count": "Electric - Pulses",
|
||||
"Voltage": "Electric - V",
|
||||
"Current": "Electric - A",
|
||||
"Power Factor": "Electric - PF",
|
||||
}
|
||||
|
||||
NOTIFICATION_CC_LABELS = {
|
||||
"General": "Start",
|
||||
"Smoke": "Smoke Alarm",
|
||||
"Carbon Monoxide": "Carbon Monoxide",
|
||||
"Carbon Dioxide": "Carbon Dioxide",
|
||||
"Heat": "Heat",
|
||||
"Flood": "Water",
|
||||
"Access Control": "Access Control",
|
||||
"Burglar": "Home Security",
|
||||
"Power Management": "Power Management",
|
||||
"System": "System",
|
||||
"Emergency": "Emergency",
|
||||
"Clock": "Clock",
|
||||
"Appliance": "Appliance",
|
||||
"HomeHealth": "Home Health",
|
||||
}
|
||||
|
||||
CC_ID_LABELS = {
|
||||
50: METER_CC_LABELS,
|
||||
113: NOTIFICATION_CC_LABELS,
|
||||
}
|
||||
|
||||
|
||||
async def async_get_migration_data(hass):
|
||||
"""Return dict with ozw side migration info."""
|
||||
data = {}
|
||||
nodes_values = hass.data[DOMAIN][NODES_VALUES]
|
||||
ozw_config_entries = hass.config_entries.async_entries(DOMAIN)
|
||||
config_entry = ozw_config_entries[0] # ozw only has a single config entry
|
||||
ent_reg = await async_get_entity_registry(hass)
|
||||
entity_entries = async_entries_for_config_entry(ent_reg, config_entry.entry_id)
|
||||
unique_entries = {entry.unique_id: entry for entry in entity_entries}
|
||||
dev_reg = await async_get_device_registry(hass)
|
||||
|
||||
for node_id, node_values in nodes_values.items():
|
||||
for entity_values in node_values:
|
||||
unique_id = create_value_id(entity_values.primary)
|
||||
if unique_id not in unique_entries:
|
||||
continue
|
||||
node = entity_values.primary.node
|
||||
device_identifier = (
|
||||
DOMAIN,
|
||||
create_device_id(node, entity_values.primary.instance),
|
||||
)
|
||||
device_entry = dev_reg.async_get_device({device_identifier}, set())
|
||||
data[unique_id] = {
|
||||
"node_id": node_id,
|
||||
"node_instance": entity_values.primary.instance,
|
||||
"device_id": device_entry.id,
|
||||
"command_class": entity_values.primary.command_class.value,
|
||||
"command_class_label": entity_values.primary.label,
|
||||
"value_index": entity_values.primary.index,
|
||||
"unique_id": unique_id,
|
||||
"entity_entry": unique_entries[unique_id],
|
||||
}
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def map_node_values(zwave_data, ozw_data):
|
||||
"""Map zwave node values onto ozw node values."""
|
||||
migration_map = {"device_entries": {}, "entity_entries": {}}
|
||||
|
||||
for zwave_entry in zwave_data.values():
|
||||
node_id = zwave_entry["node_id"]
|
||||
node_instance = zwave_entry["node_instance"]
|
||||
cc_id = zwave_entry["command_class"]
|
||||
zwave_cc_label = zwave_entry["command_class_label"]
|
||||
|
||||
if cc_id in CC_ID_LABELS:
|
||||
labels = CC_ID_LABELS[cc_id]
|
||||
ozw_cc_label = labels.get(zwave_cc_label, zwave_cc_label)
|
||||
|
||||
ozw_entry = next(
|
||||
(
|
||||
entry
|
||||
for entry in ozw_data.values()
|
||||
if entry["node_id"] == node_id
|
||||
and entry["node_instance"] == node_instance
|
||||
and entry["command_class"] == cc_id
|
||||
and entry["command_class_label"] == ozw_cc_label
|
||||
),
|
||||
None,
|
||||
)
|
||||
else:
|
||||
value_index = zwave_entry["value_index"]
|
||||
|
||||
ozw_entry = next(
|
||||
(
|
||||
entry
|
||||
for entry in ozw_data.values()
|
||||
if entry["node_id"] == node_id
|
||||
and entry["node_instance"] == node_instance
|
||||
and entry["command_class"] == cc_id
|
||||
and entry["value_index"] == value_index
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
if ozw_entry is None:
|
||||
continue
|
||||
|
||||
# Save the zwave_entry under the ozw entity_id to create the map.
|
||||
# Check that the mapped entities have the same domain.
|
||||
if zwave_entry["entity_entry"].domain == ozw_entry["entity_entry"].domain:
|
||||
migration_map["entity_entries"][
|
||||
ozw_entry["entity_entry"].entity_id
|
||||
] = zwave_entry
|
||||
migration_map["device_entries"][ozw_entry["device_id"]] = zwave_entry[
|
||||
"device_id"
|
||||
]
|
||||
|
||||
return migration_map
|
||||
|
||||
|
||||
async def async_migrate(hass, migration_map):
|
||||
"""Perform zwave to ozw migration."""
|
||||
dev_reg = await async_get_device_registry(hass)
|
||||
for ozw_device_id, zwave_device_id in migration_map["device_entries"].items():
|
||||
zwave_device_entry = dev_reg.async_get(zwave_device_id)
|
||||
dev_reg.async_update_device(
|
||||
ozw_device_id,
|
||||
area_id=zwave_device_entry.area_id,
|
||||
name_by_user=zwave_device_entry.name_by_user,
|
||||
)
|
||||
|
||||
ent_reg = await async_get_entity_registry(hass)
|
||||
for zwave_entry in migration_map["entity_entries"].values():
|
||||
zwave_entity_id = zwave_entry["entity_entry"].entity_id
|
||||
ent_reg.async_remove(zwave_entity_id)
|
||||
|
||||
for ozw_entity_id, zwave_entry in migration_map["entity_entries"].items():
|
||||
entity_entry = zwave_entry["entity_entry"]
|
||||
ent_reg.async_update_entity(
|
||||
ozw_entity_id,
|
||||
new_entity_id=entity_entry.entity_id,
|
||||
name=entity_entry.name,
|
||||
icon=entity_entry.icon,
|
||||
)
|
||||
|
||||
zwave_config_entry = hass.config_entries.async_entries("zwave")[0]
|
||||
await hass.config_entries.async_remove(zwave_config_entry.entry_id)
|
||||
|
||||
ozw_config_entry = hass.config_entries.async_entries("ozw")[0]
|
||||
updates = {
|
||||
**ozw_config_entry.data,
|
||||
MIGRATED: True,
|
||||
}
|
||||
hass.config_entries.async_update_entry(ozw_config_entry, data=updates)
|
@ -1,4 +1,6 @@
|
||||
"""Web socket API for OpenZWave."""
|
||||
import logging
|
||||
|
||||
from openzwavemqtt.const import (
|
||||
ATTR_CODE_SLOT,
|
||||
ATTR_LABEL,
|
||||
@ -23,7 +25,11 @@ from homeassistant.helpers import config_validation as cv
|
||||
|
||||
from .const import ATTR_CONFIG_PARAMETER, ATTR_CONFIG_VALUE, DOMAIN, MANAGER
|
||||
from .lock import ATTR_USERCODE
|
||||
from .migration import async_get_migration_data, async_migrate, map_node_values
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DRY_RUN = "dry_run"
|
||||
TYPE = "type"
|
||||
ID = "id"
|
||||
OZW_INSTANCE = "ozw_instance"
|
||||
@ -52,6 +58,7 @@ ATTR_NEIGHBORS = "neighbors"
|
||||
@callback
|
||||
def async_register_api(hass):
|
||||
"""Register all of our api endpoints."""
|
||||
websocket_api.async_register_command(hass, websocket_migrate_zwave)
|
||||
websocket_api.async_register_command(hass, websocket_get_instances)
|
||||
websocket_api.async_register_command(hass, websocket_get_nodes)
|
||||
websocket_api.async_register_command(hass, websocket_network_status)
|
||||
@ -161,6 +168,63 @@ def _get_config_params(node, *args):
|
||||
return config_params
|
||||
|
||||
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.async_response
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required(TYPE): "ozw/migrate_zwave",
|
||||
vol.Optional(DRY_RUN, default=True): bool,
|
||||
}
|
||||
)
|
||||
async def websocket_migrate_zwave(hass, connection, msg):
|
||||
"""Migrate the zwave integration device and entity data to ozw integration."""
|
||||
if "zwave" not in hass.config.components:
|
||||
_LOGGER.error("Can not migrate, zwave integration is not loaded")
|
||||
connection.send_message(
|
||||
websocket_api.error_message(
|
||||
msg["id"], "zwave_not_loaded", "Integration zwave is not loaded"
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
zwave = hass.components.zwave
|
||||
zwave_data = await zwave.async_get_ozw_migration_data(hass)
|
||||
_LOGGER.debug("Migration zwave data: %s", zwave_data)
|
||||
|
||||
ozw_data = await async_get_migration_data(hass)
|
||||
_LOGGER.debug("Migration ozw data: %s", ozw_data)
|
||||
|
||||
can_migrate = map_node_values(zwave_data, ozw_data)
|
||||
|
||||
zwave_entity_ids = [
|
||||
entry["entity_entry"].entity_id for entry in zwave_data.values()
|
||||
]
|
||||
ozw_entity_ids = [entry["entity_entry"].entity_id for entry in ozw_data.values()]
|
||||
migration_device_map = {
|
||||
zwave_device_id: ozw_device_id
|
||||
for ozw_device_id, zwave_device_id in can_migrate["device_entries"].items()
|
||||
}
|
||||
migration_entity_map = {
|
||||
zwave_entry["entity_entry"].entity_id: ozw_entity_id
|
||||
for ozw_entity_id, zwave_entry in can_migrate["entity_entries"].items()
|
||||
}
|
||||
_LOGGER.debug("Migration entity map: %s", migration_entity_map)
|
||||
|
||||
if not msg[DRY_RUN]:
|
||||
await async_migrate(hass, can_migrate)
|
||||
|
||||
connection.send_result(
|
||||
msg[ID],
|
||||
{
|
||||
"migration_device_map": migration_device_map,
|
||||
"zwave_entity_ids": zwave_entity_ids,
|
||||
"ozw_entity_ids": ozw_entity_ids,
|
||||
"migration_entity_map": migration_entity_map,
|
||||
"migrated": not msg[DRY_RUN],
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@websocket_api.websocket_command({vol.Required(TYPE): "ozw/get_instances"})
|
||||
def websocket_get_instances(hass, connection, msg):
|
||||
"""Get a list of OZW instances."""
|
||||
|
@ -28,6 +28,7 @@ from homeassistant.helpers.entity import generate_entity_id
|
||||
from homeassistant.helpers.entity_component import DEFAULT_SCAN_INTERVAL
|
||||
from homeassistant.helpers.entity_platform import EntityPlatform
|
||||
from homeassistant.helpers.entity_registry import (
|
||||
async_entries_for_config_entry,
|
||||
async_get_registry as async_get_entity_registry,
|
||||
)
|
||||
from homeassistant.helpers.entity_values import EntityValues
|
||||
@ -81,6 +82,8 @@ CONF_DEVICE_CONFIG = "device_config"
|
||||
CONF_DEVICE_CONFIG_GLOB = "device_config_glob"
|
||||
CONF_DEVICE_CONFIG_DOMAIN = "device_config_domain"
|
||||
|
||||
DATA_ZWAVE_CONFIG_YAML_PRESENT = "zwave_config_yaml_present"
|
||||
|
||||
DEFAULT_CONF_IGNORED = False
|
||||
DEFAULT_CONF_INVERT_OPENCLOSE_BUTTONS = False
|
||||
DEFAULT_CONF_INVERT_PERCENT = False
|
||||
@ -250,6 +253,64 @@ CONFIG_SCHEMA = vol.Schema(
|
||||
)
|
||||
|
||||
|
||||
async def async_get_ozw_migration_data(hass):
|
||||
"""Return dict with info for migration to ozw integration."""
|
||||
data_to_migrate = {}
|
||||
|
||||
zwave_config_entries = hass.config_entries.async_entries(DOMAIN)
|
||||
if not zwave_config_entries:
|
||||
_LOGGER.error("Config entry not set up")
|
||||
return data_to_migrate
|
||||
|
||||
if hass.data.get(DATA_ZWAVE_CONFIG_YAML_PRESENT):
|
||||
_LOGGER.warning(
|
||||
"Remove %s from configuration.yaml "
|
||||
"to avoid setting up this integration on restart "
|
||||
"after completing migration to ozw",
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
config_entry = zwave_config_entries[0] # zwave only has a single config entry
|
||||
ent_reg = await async_get_entity_registry(hass)
|
||||
entity_entries = async_entries_for_config_entry(ent_reg, config_entry.entry_id)
|
||||
unique_entries = {entry.unique_id: entry for entry in entity_entries}
|
||||
dev_reg = await async_get_device_registry(hass)
|
||||
|
||||
for entity_values in hass.data[DATA_ENTITY_VALUES]:
|
||||
node = entity_values.primary.node
|
||||
unique_id = compute_value_unique_id(node, entity_values.primary)
|
||||
if unique_id not in unique_entries:
|
||||
continue
|
||||
device_identifier, _ = node_device_id_and_name(
|
||||
node, entity_values.primary.instance
|
||||
)
|
||||
device_entry = dev_reg.async_get_device({device_identifier}, set())
|
||||
data_to_migrate[unique_id] = {
|
||||
"node_id": node.node_id,
|
||||
"node_instance": entity_values.primary.instance,
|
||||
"device_id": device_entry.id,
|
||||
"command_class": entity_values.primary.command_class,
|
||||
"command_class_label": entity_values.primary.label,
|
||||
"value_index": entity_values.primary.index,
|
||||
"unique_id": unique_id,
|
||||
"entity_entry": unique_entries[unique_id],
|
||||
}
|
||||
|
||||
return data_to_migrate
|
||||
|
||||
|
||||
@callback
|
||||
def async_is_ozw_migrated(hass):
|
||||
"""Return True if migration to ozw is done."""
|
||||
ozw_config_entries = hass.config_entries.async_entries("ozw")
|
||||
if not ozw_config_entries:
|
||||
return False
|
||||
|
||||
ozw_config_entry = ozw_config_entries[0] # only one ozw entry is allowed
|
||||
migrated = bool(ozw_config_entry.data.get("migrated"))
|
||||
return migrated
|
||||
|
||||
|
||||
def _obj_to_dict(obj):
|
||||
"""Convert an object into a hash for debug."""
|
||||
return {
|
||||
@ -312,6 +373,7 @@ async def async_setup(hass, config):
|
||||
|
||||
conf = config[DOMAIN]
|
||||
hass.data[DATA_ZWAVE_CONFIG] = conf
|
||||
hass.data[DATA_ZWAVE_CONFIG_YAML_PRESENT] = True
|
||||
|
||||
if not hass.config_entries.async_entries(DOMAIN):
|
||||
hass.async_create_task(
|
||||
@ -343,6 +405,12 @@ async def async_setup_entry(hass, config_entry):
|
||||
# pylint: enable=import-error
|
||||
from pydispatch import dispatcher
|
||||
|
||||
if async_is_ozw_migrated(hass):
|
||||
_LOGGER.error(
|
||||
"Migration to ozw has been done. Please remove the zwave integration"
|
||||
)
|
||||
return False
|
||||
|
||||
# Merge config entry and yaml config
|
||||
config = config_entry.data
|
||||
if DATA_ZWAVE_CONFIG in hass.data:
|
||||
|
@ -4,5 +4,6 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/zwave",
|
||||
"requirements": ["homeassistant-pyozw==0.1.10", "pydispatcher==2.0.5"],
|
||||
"after_dependencies": ["ozw"],
|
||||
"codeowners": ["@home-assistant/z-wave"]
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import websocket_api
|
||||
from homeassistant.components.ozw.const import DOMAIN as OZW_DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_IMPORT
|
||||
from homeassistant.core import callback
|
||||
|
||||
from .const import (
|
||||
@ -56,9 +58,32 @@ def websocket_get_migration_config(hass, connection, msg):
|
||||
)
|
||||
|
||||
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.async_response
|
||||
@websocket_api.websocket_command({vol.Required(TYPE): "zwave/start_ozw_config_flow"})
|
||||
async def websocket_start_ozw_config_flow(hass, connection, msg):
|
||||
"""Start the ozw integration config flow (for migration wizard).
|
||||
|
||||
Return data with the flow id of the started ozw config flow.
|
||||
"""
|
||||
config = hass.data[DATA_ZWAVE_CONFIG]
|
||||
data = {
|
||||
"usb_path": config[CONF_USB_STICK_PATH],
|
||||
"network_key": config[CONF_NETWORK_KEY],
|
||||
}
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
OZW_DOMAIN, context={"source": SOURCE_IMPORT}, data=data
|
||||
)
|
||||
connection.send_result(
|
||||
msg[ID],
|
||||
{"flow_id": result["flow_id"]},
|
||||
)
|
||||
|
||||
|
||||
@callback
|
||||
def async_load_websocket_api(hass):
|
||||
"""Set up the web socket API."""
|
||||
websocket_api.async_register_command(hass, websocket_network_status)
|
||||
websocket_api.async_register_command(hass, websocket_get_config)
|
||||
websocket_api.async_register_command(hass, websocket_get_migration_config)
|
||||
websocket_api.async_register_command(hass, websocket_start_ozw_config_flow)
|
||||
|
@ -16,6 +16,12 @@ def generic_data_fixture():
|
||||
return load_fixture("ozw/generic_network_dump.csv")
|
||||
|
||||
|
||||
@pytest.fixture(name="migration_data", scope="session")
|
||||
def migration_data_fixture():
|
||||
"""Load migration MQTT data and return it."""
|
||||
return load_fixture("ozw/migration_fixture.csv")
|
||||
|
||||
|
||||
@pytest.fixture(name="fan_data", scope="session")
|
||||
def fan_data_fixture():
|
||||
"""Load fan MQTT data and return it."""
|
||||
|
@ -535,3 +535,52 @@ async def test_discovery_addon_not_installed(
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "start_addon"
|
||||
|
||||
|
||||
async def test_import_addon_installed(
|
||||
hass, supervisor, addon_installed, addon_options, set_addon_options, start_addon
|
||||
):
|
||||
"""Test add-on already installed but not running on Supervisor."""
|
||||
hass.config.components.add("mqtt")
|
||||
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data={"usb_path": "/test/imported", "network_key": "imported123"},
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "on_supervisor"
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], {"use_addon": True}
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "start_addon"
|
||||
|
||||
# the default input should be the imported data
|
||||
default_input = result["data_schema"]({})
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.ozw.async_setup", return_value=True
|
||||
) as mock_setup, patch(
|
||||
"homeassistant.components.ozw.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], default_input
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["title"] == TITLE
|
||||
assert result["data"] == {
|
||||
"usb_path": "/test/imported",
|
||||
"network_key": "imported123",
|
||||
"use_addon": True,
|
||||
"integration_created_addon": False,
|
||||
}
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
292
tests/components/ozw/test_migration.py
Normal file
292
tests/components/ozw/test_migration.py
Normal file
@ -0,0 +1,292 @@
|
||||
"""Test zwave to ozw migration."""
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.ozw.websocket_api import ID, TYPE
|
||||
from homeassistant.helpers.device_registry import (
|
||||
DeviceEntry,
|
||||
async_get_registry as async_get_device_registry,
|
||||
)
|
||||
from homeassistant.helpers.entity_registry import (
|
||||
RegistryEntry,
|
||||
async_get_registry as async_get_entity_registry,
|
||||
)
|
||||
|
||||
from .common import setup_ozw
|
||||
|
||||
from tests.common import MockConfigEntry, mock_device_registry, mock_registry
|
||||
|
||||
ZWAVE_SOURCE_NODE_DEVICE_ID = "zwave_source_node_device_id"
|
||||
ZWAVE_SOURCE_NODE_DEVICE_NAME = "Z-Wave Source Node Device"
|
||||
ZWAVE_SOURCE_NODE_DEVICE_AREA = "Z-Wave Source Node Area"
|
||||
ZWAVE_SOURCE_ENTITY = "sensor.zwave_source_node"
|
||||
ZWAVE_SOURCE_NODE_UNIQUE_ID = "10-4321"
|
||||
ZWAVE_BATTERY_DEVICE_ID = "zwave_battery_device_id"
|
||||
ZWAVE_BATTERY_DEVICE_NAME = "Z-Wave Battery Device"
|
||||
ZWAVE_BATTERY_DEVICE_AREA = "Z-Wave Battery Area"
|
||||
ZWAVE_BATTERY_ENTITY = "sensor.zwave_battery_level"
|
||||
ZWAVE_BATTERY_UNIQUE_ID = "36-1234"
|
||||
ZWAVE_BATTERY_NAME = "Z-Wave Battery Level"
|
||||
ZWAVE_BATTERY_ICON = "mdi:zwave-test-battery"
|
||||
ZWAVE_POWER_DEVICE_ID = "zwave_power_device_id"
|
||||
ZWAVE_POWER_DEVICE_NAME = "Z-Wave Power Device"
|
||||
ZWAVE_POWER_DEVICE_AREA = "Z-Wave Power Area"
|
||||
ZWAVE_POWER_ENTITY = "binary_sensor.zwave_power"
|
||||
ZWAVE_POWER_UNIQUE_ID = "32-5678"
|
||||
ZWAVE_POWER_NAME = "Z-Wave Power"
|
||||
ZWAVE_POWER_ICON = "mdi:zwave-test-power"
|
||||
|
||||
|
||||
@pytest.fixture(name="zwave_migration_data")
|
||||
def zwave_migration_data_fixture(hass):
|
||||
"""Return mock zwave migration data."""
|
||||
zwave_source_node_device = DeviceEntry(
|
||||
id=ZWAVE_SOURCE_NODE_DEVICE_ID,
|
||||
name_by_user=ZWAVE_SOURCE_NODE_DEVICE_NAME,
|
||||
area_id=ZWAVE_SOURCE_NODE_DEVICE_AREA,
|
||||
)
|
||||
zwave_source_node_entry = RegistryEntry(
|
||||
entity_id=ZWAVE_SOURCE_ENTITY,
|
||||
unique_id=ZWAVE_SOURCE_NODE_UNIQUE_ID,
|
||||
platform="zwave",
|
||||
name="Z-Wave Source Node",
|
||||
)
|
||||
zwave_battery_device = DeviceEntry(
|
||||
id=ZWAVE_BATTERY_DEVICE_ID,
|
||||
name_by_user=ZWAVE_BATTERY_DEVICE_NAME,
|
||||
area_id=ZWAVE_BATTERY_DEVICE_AREA,
|
||||
)
|
||||
zwave_battery_entry = RegistryEntry(
|
||||
entity_id=ZWAVE_BATTERY_ENTITY,
|
||||
unique_id=ZWAVE_BATTERY_UNIQUE_ID,
|
||||
platform="zwave",
|
||||
name=ZWAVE_BATTERY_NAME,
|
||||
icon=ZWAVE_BATTERY_ICON,
|
||||
)
|
||||
zwave_power_device = DeviceEntry(
|
||||
id=ZWAVE_POWER_DEVICE_ID,
|
||||
name_by_user=ZWAVE_POWER_DEVICE_NAME,
|
||||
area_id=ZWAVE_POWER_DEVICE_AREA,
|
||||
)
|
||||
zwave_power_entry = RegistryEntry(
|
||||
entity_id=ZWAVE_POWER_ENTITY,
|
||||
unique_id=ZWAVE_POWER_UNIQUE_ID,
|
||||
platform="zwave",
|
||||
name=ZWAVE_POWER_NAME,
|
||||
icon=ZWAVE_POWER_ICON,
|
||||
)
|
||||
zwave_migration_data = {
|
||||
ZWAVE_SOURCE_NODE_UNIQUE_ID: {
|
||||
"node_id": 10,
|
||||
"node_instance": 1,
|
||||
"device_id": zwave_source_node_device.id,
|
||||
"command_class": 113,
|
||||
"command_class_label": "SourceNodeId",
|
||||
"value_index": 2,
|
||||
"unique_id": ZWAVE_SOURCE_NODE_UNIQUE_ID,
|
||||
"entity_entry": zwave_source_node_entry,
|
||||
},
|
||||
ZWAVE_BATTERY_UNIQUE_ID: {
|
||||
"node_id": 36,
|
||||
"node_instance": 1,
|
||||
"device_id": zwave_battery_device.id,
|
||||
"command_class": 128,
|
||||
"command_class_label": "Battery Level",
|
||||
"value_index": 0,
|
||||
"unique_id": ZWAVE_BATTERY_UNIQUE_ID,
|
||||
"entity_entry": zwave_battery_entry,
|
||||
},
|
||||
ZWAVE_POWER_UNIQUE_ID: {
|
||||
"node_id": 32,
|
||||
"node_instance": 1,
|
||||
"device_id": zwave_power_device.id,
|
||||
"command_class": 50,
|
||||
"command_class_label": "Power",
|
||||
"value_index": 8,
|
||||
"unique_id": ZWAVE_POWER_UNIQUE_ID,
|
||||
"entity_entry": zwave_power_entry,
|
||||
},
|
||||
}
|
||||
|
||||
mock_device_registry(
|
||||
hass,
|
||||
{
|
||||
zwave_source_node_device.id: zwave_source_node_device,
|
||||
zwave_battery_device.id: zwave_battery_device,
|
||||
zwave_power_device.id: zwave_power_device,
|
||||
},
|
||||
)
|
||||
mock_registry(
|
||||
hass,
|
||||
{
|
||||
ZWAVE_SOURCE_ENTITY: zwave_source_node_entry,
|
||||
ZWAVE_BATTERY_ENTITY: zwave_battery_entry,
|
||||
ZWAVE_POWER_ENTITY: zwave_power_entry,
|
||||
},
|
||||
)
|
||||
|
||||
return zwave_migration_data
|
||||
|
||||
|
||||
@pytest.fixture(name="zwave_integration")
|
||||
def zwave_integration_fixture(hass, zwave_migration_data):
|
||||
"""Mock the zwave integration."""
|
||||
hass.config.components.add("zwave")
|
||||
zwave_config_entry = MockConfigEntry(domain="zwave", data={"usb_path": "/dev/test"})
|
||||
zwave_config_entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"homeassistant.components.zwave.async_get_ozw_migration_data",
|
||||
return_value=zwave_migration_data,
|
||||
):
|
||||
yield zwave_config_entry
|
||||
|
||||
|
||||
async def test_migrate_zwave(hass, migration_data, hass_ws_client, zwave_integration):
|
||||
"""Test the zwave to ozw migration websocket api."""
|
||||
await setup_ozw(hass, fixture=migration_data)
|
||||
client = await hass_ws_client(hass)
|
||||
|
||||
assert hass.config_entries.async_entries("zwave")
|
||||
|
||||
await client.send_json({ID: 5, TYPE: "ozw/migrate_zwave", "dry_run": False})
|
||||
msg = await client.receive_json()
|
||||
result = msg["result"]
|
||||
|
||||
migration_entity_map = {
|
||||
ZWAVE_BATTERY_ENTITY: "sensor.water_sensor_6_battery_level",
|
||||
}
|
||||
|
||||
assert result["zwave_entity_ids"] == [
|
||||
ZWAVE_SOURCE_ENTITY,
|
||||
ZWAVE_BATTERY_ENTITY,
|
||||
ZWAVE_POWER_ENTITY,
|
||||
]
|
||||
assert result["ozw_entity_ids"] == [
|
||||
"sensor.smart_plug_electric_w",
|
||||
"sensor.water_sensor_6_battery_level",
|
||||
]
|
||||
assert result["migration_entity_map"] == migration_entity_map
|
||||
assert result["migrated"] is True
|
||||
|
||||
dev_reg = await async_get_device_registry(hass)
|
||||
ent_reg = await async_get_entity_registry(hass)
|
||||
|
||||
# check the device registry migration
|
||||
|
||||
# check that the migrated entries have correct attributes
|
||||
battery_entry = dev_reg.async_get_device(
|
||||
identifiers={("ozw", "1.36.1")}, connections=set()
|
||||
)
|
||||
assert battery_entry.name_by_user == ZWAVE_BATTERY_DEVICE_NAME
|
||||
assert battery_entry.area_id == ZWAVE_BATTERY_DEVICE_AREA
|
||||
power_entry = dev_reg.async_get_device(
|
||||
identifiers={("ozw", "1.32.1")}, connections=set()
|
||||
)
|
||||
assert power_entry.name_by_user == ZWAVE_POWER_DEVICE_NAME
|
||||
assert power_entry.area_id == ZWAVE_POWER_DEVICE_AREA
|
||||
|
||||
migration_device_map = {
|
||||
ZWAVE_BATTERY_DEVICE_ID: battery_entry.id,
|
||||
ZWAVE_POWER_DEVICE_ID: power_entry.id,
|
||||
}
|
||||
|
||||
assert result["migration_device_map"] == migration_device_map
|
||||
|
||||
# check the entity registry migration
|
||||
|
||||
# this should have been migrated and no longer present under that id
|
||||
assert not ent_reg.async_is_registered("sensor.water_sensor_6_battery_level")
|
||||
|
||||
# these should not have been migrated and is still in the registry
|
||||
assert ent_reg.async_is_registered(ZWAVE_SOURCE_ENTITY)
|
||||
source_entry = ent_reg.async_get(ZWAVE_SOURCE_ENTITY)
|
||||
assert source_entry.unique_id == ZWAVE_SOURCE_NODE_UNIQUE_ID
|
||||
assert ent_reg.async_is_registered(ZWAVE_POWER_ENTITY)
|
||||
source_entry = ent_reg.async_get(ZWAVE_POWER_ENTITY)
|
||||
assert source_entry.unique_id == ZWAVE_POWER_UNIQUE_ID
|
||||
assert ent_reg.async_is_registered("sensor.smart_plug_electric_w")
|
||||
|
||||
# this is the new entity_id of the ozw entity
|
||||
assert ent_reg.async_is_registered(ZWAVE_BATTERY_ENTITY)
|
||||
|
||||
# check that the migrated entries have correct attributes
|
||||
battery_entry = ent_reg.async_get(ZWAVE_BATTERY_ENTITY)
|
||||
assert battery_entry.unique_id == "1-36-610271249"
|
||||
assert battery_entry.name == ZWAVE_BATTERY_NAME
|
||||
assert battery_entry.icon == ZWAVE_BATTERY_ICON
|
||||
|
||||
# check that the zwave config entry has been removed
|
||||
assert not hass.config_entries.async_entries("zwave")
|
||||
|
||||
# Check that the zwave integration fails entry setup after migration
|
||||
zwave_config_entry = MockConfigEntry(domain="zwave")
|
||||
zwave_config_entry.add_to_hass(hass)
|
||||
assert not await hass.config_entries.async_setup(zwave_config_entry.entry_id)
|
||||
|
||||
|
||||
async def test_migrate_zwave_dry_run(
|
||||
hass, migration_data, hass_ws_client, zwave_integration
|
||||
):
|
||||
"""Test the zwave to ozw migration websocket api dry run."""
|
||||
await setup_ozw(hass, fixture=migration_data)
|
||||
client = await hass_ws_client(hass)
|
||||
|
||||
await client.send_json({ID: 5, TYPE: "ozw/migrate_zwave"})
|
||||
msg = await client.receive_json()
|
||||
result = msg["result"]
|
||||
|
||||
migration_entity_map = {
|
||||
ZWAVE_BATTERY_ENTITY: "sensor.water_sensor_6_battery_level",
|
||||
}
|
||||
|
||||
assert result["zwave_entity_ids"] == [
|
||||
ZWAVE_SOURCE_ENTITY,
|
||||
ZWAVE_BATTERY_ENTITY,
|
||||
ZWAVE_POWER_ENTITY,
|
||||
]
|
||||
assert result["ozw_entity_ids"] == [
|
||||
"sensor.smart_plug_electric_w",
|
||||
"sensor.water_sensor_6_battery_level",
|
||||
]
|
||||
assert result["migration_entity_map"] == migration_entity_map
|
||||
assert result["migrated"] is False
|
||||
|
||||
ent_reg = await async_get_entity_registry(hass)
|
||||
|
||||
# no real migration should have been done
|
||||
assert ent_reg.async_is_registered("sensor.water_sensor_6_battery_level")
|
||||
assert ent_reg.async_is_registered("sensor.smart_plug_electric_w")
|
||||
|
||||
assert ent_reg.async_is_registered(ZWAVE_SOURCE_ENTITY)
|
||||
source_entry = ent_reg.async_get(ZWAVE_SOURCE_ENTITY)
|
||||
assert source_entry.unique_id == ZWAVE_SOURCE_NODE_UNIQUE_ID
|
||||
|
||||
assert ent_reg.async_is_registered(ZWAVE_BATTERY_ENTITY)
|
||||
battery_entry = ent_reg.async_get(ZWAVE_BATTERY_ENTITY)
|
||||
assert battery_entry.unique_id == ZWAVE_BATTERY_UNIQUE_ID
|
||||
|
||||
assert ent_reg.async_is_registered(ZWAVE_POWER_ENTITY)
|
||||
power_entry = ent_reg.async_get(ZWAVE_POWER_ENTITY)
|
||||
assert power_entry.unique_id == ZWAVE_POWER_UNIQUE_ID
|
||||
|
||||
# check that the zwave config entry has not been removed
|
||||
assert hass.config_entries.async_entries("zwave")
|
||||
|
||||
# Check that the zwave integration can be setup after dry run
|
||||
zwave_config_entry = zwave_integration
|
||||
with patch("openzwave.option.ZWaveOption"), patch("openzwave.network.ZWaveNetwork"):
|
||||
assert await hass.config_entries.async_setup(zwave_config_entry.entry_id)
|
||||
|
||||
|
||||
async def test_migrate_zwave_not_setup(hass, migration_data, hass_ws_client):
|
||||
"""Test the zwave to ozw migration websocket without zwave setup."""
|
||||
await setup_ozw(hass, fixture=migration_data)
|
||||
client = await hass_ws_client(hass)
|
||||
|
||||
await client.send_json({ID: 5, TYPE: "ozw/migrate_zwave"})
|
||||
msg = await client.receive_json()
|
||||
|
||||
assert not msg["success"]
|
||||
assert msg["error"]["code"] == "zwave_not_loaded"
|
||||
assert msg["error"]["message"] == "Integration zwave is not loaded"
|
@ -1,4 +1,6 @@
|
||||
"""Test Z-Wave Websocket API."""
|
||||
from unittest.mock import call, patch
|
||||
|
||||
from homeassistant.bootstrap import async_setup_component
|
||||
from homeassistant.components.zwave.const import (
|
||||
CONF_AUTOHEAL,
|
||||
@ -8,6 +10,8 @@ from homeassistant.components.zwave.const import (
|
||||
)
|
||||
from homeassistant.components.zwave.websocket_api import ID, TYPE
|
||||
|
||||
NETWORK_KEY = "0xTE, 0xST, 0xTE, 0xST, 0xTE, 0xST, 0xTE, 0xST, 0xTE, 0xST, 0xTE, 0xST, 0xTE, 0xST, 0xTE, 0xST"
|
||||
|
||||
|
||||
async def test_zwave_ws_api(hass, mock_openzwave, hass_ws_client):
|
||||
"""Test Z-Wave websocket API."""
|
||||
@ -20,7 +24,7 @@ async def test_zwave_ws_api(hass, mock_openzwave, hass_ws_client):
|
||||
CONF_AUTOHEAL: False,
|
||||
CONF_USB_STICK_PATH: "/dev/zwave",
|
||||
CONF_POLLING_INTERVAL: 6000,
|
||||
CONF_NETWORK_KEY: "0xTE, 0xST, 0xTE, 0xST, 0xTE, 0xST, 0xTE, 0xST, 0xTE, 0xST, 0xTE, 0xST, 0xTE, 0xST, 0xTE, 0xST",
|
||||
CONF_NETWORK_KEY: NETWORK_KEY,
|
||||
}
|
||||
},
|
||||
)
|
||||
@ -38,12 +42,47 @@ async def test_zwave_ws_api(hass, mock_openzwave, hass_ws_client):
|
||||
assert not result[CONF_AUTOHEAL]
|
||||
assert result[CONF_POLLING_INTERVAL] == 6000
|
||||
|
||||
|
||||
async def test_zwave_ozw_migration_api(hass, mock_openzwave, hass_ws_client):
|
||||
"""Test Z-Wave to OpenZWave websocket migration API."""
|
||||
|
||||
await async_setup_component(
|
||||
hass,
|
||||
"zwave",
|
||||
{
|
||||
"zwave": {
|
||||
CONF_AUTOHEAL: False,
|
||||
CONF_USB_STICK_PATH: "/dev/zwave",
|
||||
CONF_POLLING_INTERVAL: 6000,
|
||||
CONF_NETWORK_KEY: NETWORK_KEY,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
client = await hass_ws_client(hass)
|
||||
|
||||
await client.send_json({ID: 6, TYPE: "zwave/get_migration_config"})
|
||||
msg = await client.receive_json()
|
||||
result = msg["result"]
|
||||
|
||||
assert result[CONF_USB_STICK_PATH] == "/dev/zwave"
|
||||
assert (
|
||||
result[CONF_NETWORK_KEY]
|
||||
== "0xTE, 0xST, 0xTE, 0xST, 0xTE, 0xST, 0xTE, 0xST, 0xTE, 0xST, 0xTE, 0xST, 0xTE, 0xST, 0xTE, 0xST"
|
||||
assert result[CONF_NETWORK_KEY] == NETWORK_KEY
|
||||
|
||||
with patch(
|
||||
"homeassistant.config_entries.ConfigEntriesFlowManager.async_init"
|
||||
) as async_init:
|
||||
|
||||
async_init.return_value = {"flow_id": "mock_flow_id"}
|
||||
await client.send_json({ID: 7, TYPE: "zwave/start_ozw_config_flow"})
|
||||
msg = await client.receive_json()
|
||||
|
||||
result = msg["result"]
|
||||
|
||||
assert result["flow_id"] == "mock_flow_id"
|
||||
assert async_init.call_args == call(
|
||||
"ozw",
|
||||
context={"source": "import"},
|
||||
data={"usb_path": "/dev/zwave", "network_key": NETWORK_KEY},
|
||||
)
|
||||
|
9
tests/fixtures/ozw/migration_fixture.csv
vendored
Normal file
9
tests/fixtures/ozw/migration_fixture.csv
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
OpenZWave/1/status/,{ "OpenZWave_Version": "1.6.1008", "OZWDeamon_Version": "0.1", "QTOpenZWave_Version": "1.0.0", "QT_Version": "5.12.5", "Status": "driverAllNodesQueried", "TimeStamp": 1579566933, "ManufacturerSpecificDBReady": true, "homeID": 3245146787, "getControllerNodeId": 1, "getSUCNodeId": 1, "isPrimaryController": true, "isBridgeController": false, "hasExtendedTXStatistics": true, "getControllerLibraryVersion": "Z-Wave 3.95", "getControllerLibraryType": "Static Controller", "getControllerPath": "/dev/zwave"}
|
||||
OpenZWave/1/node/32/,{ "NodeID": 32, "NodeQueryStage": "Complete", "isListening": true, "isFlirs": false, "isBeaming": true, "isRouting": true, "isSecurityv1": false, "isZWavePlus": true, "isNIFRecieved": true, "isAwake": true, "isFailed": false, "MetaData": { "OZWInfoURL": "http://www.openzwave.com/device-database/0208:0005:0101", "ZWAProductURL": "", "ProductPic": "images/hank/hkzw-so01-smartplug.png", "Description": "fixture description", "WakeupHelp": "", "ProductSupportURL": "", "Frequency": "", "Name": "Smart Plug", "ProductPicBase64": "iVBORggg==" }, "Event": "nodeQueriesComplete", "TimeStamp": 1579566933, "NodeManufacturerName": "HANK Electronics Ltd", "NodeProductName": "HKZW-SO01 Smart Plug", "NodeBasicString": "Routing Slave", "NodeBasic": 4, "NodeGenericString": "Binary Switch", "NodeGeneric": 16, "NodeSpecificString": "Binary Power Switch", "NodeSpecific": 1, "NodeManufacturerID": "0x0208", "NodeProductType": "0x0101", "NodeProductID": "0x0005", "NodeBaudRate": 100000, "NodeVersion": 4, "NodeGroups": 1, "NodeName": "", "NodeLocation": "", "NodeDeviceTypeString": "On/Off Power Switch", "NodeDeviceType": 1792, "NodeRole": 5, "NodeRoleString": "Always On Slave", "NodePlusType": 0, "NodePlusTypeString": "Z-Wave+ node", "Neighbors": [ 1, 33, 36, 37, 39 ]}
|
||||
OpenZWave/1/node/32/instance/1/,{ "Instance": 1, "TimeStamp": 1579566891}
|
||||
OpenZWave/1/node/32/instance/1/commandclass/50/,{ "Instance": 1, "CommandClassId": 50, "CommandClass": "COMMAND_CLASS_METER", "TimeStamp": 1579566891}
|
||||
OpenZWave/1/node/32/instance/1/commandclass/50/value/562950495305746/,{ "Label": "Electric - W", "Value": 0.0, "Units": "W", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_METER", "Index": 2, "Node": 32, "Genre": "User", "Help": "", "ValueIDKey": 562950495305746, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891}
|
||||
OpenZWave/1/node/36/,{ "NodeID": 36, "NodeQueryStage": "CacheLoad", "isListening": false, "isFlirs": false, "isBeaming": true, "isRouting": true, "isSecurityv1": false, "isZWavePlus": false, "isNIFRecieved": true, "isAwake": true, "isFailed": false, "MetaData": { "OZWInfoURL": "http://www.openzwave.com/device-database/0086:007A:0102", "ZWAProductURL": "", "ProductPic": "images/aeotec/zw122.png", "Description": "fixture description", "WakeupHelp": "Pressing the Action Button once will trigger sending the Wake up notification command. If press and hold the Z-Wave button for 3 seconds, the Water Sensor will wake up for 10 minutes.", "ProductSupportURL": "", "Frequency": "", "Name": "Water Sensor 6", "ProductPicBase64": "kSuQmCC" }, "Event": "nodeNaming", "TimeStamp": 1579566891, "NodeManufacturerName": "AEON Labs", "NodeProductName": "ZW122 Water Sensor 6", "NodeBasicString": "Routing Slave", "NodeBasic": 4, "NodeGenericString": "Notification Sensor", "NodeGeneric": 7, "NodeSpecificString": "Notification Sensor", "NodeSpecific": 1, "NodeManufacturerID": "0x0086", "NodeProductType": "0x0102", "NodeProductID": "0x007a", "NodeBaudRate": 100000, "NodeVersion": 4, "NodeGroups": 4}
|
||||
OpenZWave/1/node/36/instance/1/,{ "Instance": 1, "TimeStamp": 1579566891}
|
||||
OpenZWave/1/node/36/instance/1/commandclass/128/,{ "Instance": 1, "CommandClassId": 128, "CommandClass": "COMMAND_CLASS_BATTERY", "TimeStamp": 1579566891}
|
||||
OpenZWave/1/node/36/instance/1/commandclass/128/value/610271249/,{ "Label": "Battery Level", "Value": 100, "Units": "%", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_BATTERY", "Index": 0, "Node": 36, "Genre": "User", "Help": "Current Battery Level", "ValueIDKey": 610271249, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891}
|
Can't render this file because it contains an unexpected character in line 1 and column 26.
|
Loading…
x
Reference in New Issue
Block a user