Add zwave to zwave_js migration (#56159)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
parent
d5c3d234ec
commit
50fffe48f8
20 changed files with 1219 additions and 687 deletions
|
@ -35,15 +35,6 @@ 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():
|
||||
|
|
|
@ -1,171 +0,0 @@
|
|||
"""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)
|
|
@ -25,7 +25,6 @@ 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__)
|
||||
|
||||
|
@ -58,7 +57,6 @@ 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)
|
||||
|
@ -168,63 +166,6 @@ 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."""
|
||||
|
|
|
@ -29,7 +29,6 @@ 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
|
||||
|
@ -56,11 +55,18 @@ from .const import (
|
|||
DOMAIN,
|
||||
)
|
||||
from .discovery_schemas import DISCOVERY_SCHEMAS
|
||||
from .migration import ( # noqa: F401 pylint: disable=unused-import
|
||||
async_add_migration_entity_value,
|
||||
async_get_migration_data,
|
||||
async_is_ozw_migrated,
|
||||
async_is_zwave_js_migrated,
|
||||
)
|
||||
from .node_entity import ZWaveBaseEntity, ZWaveNodeEntity
|
||||
from .util import (
|
||||
check_has_unique_id,
|
||||
check_node_schema,
|
||||
check_value_schema,
|
||||
compute_value_unique_id,
|
||||
is_node_parsed,
|
||||
node_device_id_and_name,
|
||||
node_name,
|
||||
|
@ -253,64 +259,6 @@ 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 {
|
||||
|
@ -404,9 +352,22 @@ async def async_setup_entry(hass, config_entry): # noqa: C901
|
|||
# pylint: enable=import-error
|
||||
from pydispatch import dispatcher
|
||||
|
||||
if async_is_ozw_migrated(hass):
|
||||
if async_is_ozw_migrated(hass) or async_is_zwave_js_migrated(hass):
|
||||
|
||||
if hass.data.get(DATA_ZWAVE_CONFIG_YAML_PRESENT):
|
||||
config_yaml_message = (
|
||||
", and remove %s from configuration.yaml "
|
||||
"to avoid setting up this integration on restart ",
|
||||
DOMAIN,
|
||||
)
|
||||
else:
|
||||
config_yaml_message = ""
|
||||
|
||||
_LOGGER.error(
|
||||
"Migration to ozw has been done. Please remove the zwave integration"
|
||||
"Migration away from legacy Z-Wave has been done. "
|
||||
"Please remove the %s integration%s",
|
||||
DOMAIN,
|
||||
config_yaml_message,
|
||||
)
|
||||
return False
|
||||
|
||||
|
@ -1307,6 +1268,9 @@ class ZWaveDeviceEntity(ZWaveBaseEntity):
|
|||
self.refresh_from_network,
|
||||
)
|
||||
|
||||
# Add legacy Z-Wave migration data.
|
||||
await async_add_migration_entity_value(self.hass, self.entity_id, self.values)
|
||||
|
||||
def _update_attributes(self):
|
||||
"""Update the node attributes. May only be used inside callback."""
|
||||
self.node_id = self.node.node_id
|
||||
|
@ -1386,8 +1350,3 @@ class ZWaveDeviceEntity(ZWaveBaseEntity):
|
|||
) or self.node.is_ready:
|
||||
return compute_value_unique_id(self.node, self.values.primary)
|
||||
return None
|
||||
|
||||
|
||||
def compute_value_unique_id(node, value):
|
||||
"""Compute unique_id a value would get if it were to get one."""
|
||||
return f"{node.node_id}-{value.object_id}"
|
||||
|
|
|
@ -4,7 +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"],
|
||||
"iot_class": "local_push"
|
||||
}
|
||||
|
|
167
homeassistant/components/zwave/migration.py
Normal file
167
homeassistant/components/zwave/migration.py
Normal file
|
@ -0,0 +1,167 @@
|
|||
"""Handle migration from legacy Z-Wave to OpenZWave and Z-Wave JS."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, TypedDict, cast
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.device_registry import async_get as async_get_device_registry
|
||||
from homeassistant.helpers.entity_registry import async_get as async_get_entity_registry
|
||||
from homeassistant.helpers.singleton import singleton
|
||||
from homeassistant.helpers.storage import Store
|
||||
|
||||
from .const import DOMAIN
|
||||
from .util import node_device_id_and_name
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import ZWaveDeviceEntityValues
|
||||
|
||||
LEGACY_ZWAVE_MIGRATION = f"{DOMAIN}_legacy_zwave_migration"
|
||||
STORAGE_WRITE_DELAY = 30
|
||||
STORAGE_KEY = f"{DOMAIN}.legacy_zwave_migration"
|
||||
STORAGE_VERSION = 1
|
||||
|
||||
|
||||
class ZWaveMigrationData(TypedDict):
|
||||
"""Represent the Z-Wave migration data dict."""
|
||||
|
||||
node_id: int
|
||||
node_instance: int
|
||||
command_class: int
|
||||
command_class_label: str
|
||||
value_index: int
|
||||
device_id: str
|
||||
domain: str
|
||||
entity_id: str
|
||||
unique_id: str
|
||||
unit_of_measurement: str | None
|
||||
|
||||
|
||||
@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
|
||||
|
||||
|
||||
@callback
|
||||
def async_is_zwave_js_migrated(hass):
|
||||
"""Return True if migration to Z-Wave JS is done."""
|
||||
zwave_js_config_entries = hass.config_entries.async_entries("zwave_js")
|
||||
if not zwave_js_config_entries:
|
||||
return False
|
||||
|
||||
migrated = any(
|
||||
config_entry.data.get("migrated") for config_entry in zwave_js_config_entries
|
||||
)
|
||||
return migrated
|
||||
|
||||
|
||||
async def async_add_migration_entity_value(
|
||||
hass: HomeAssistant,
|
||||
entity_id: str,
|
||||
entity_values: ZWaveDeviceEntityValues,
|
||||
) -> None:
|
||||
"""Add Z-Wave entity value for legacy Z-Wave migration."""
|
||||
migration_handler: LegacyZWaveMigration = await get_legacy_zwave_migration(hass)
|
||||
migration_handler.add_entity_value(entity_id, entity_values)
|
||||
|
||||
|
||||
async def async_get_migration_data(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry
|
||||
) -> dict[str, ZWaveMigrationData]:
|
||||
"""Return Z-Wave migration data."""
|
||||
migration_handler: LegacyZWaveMigration = await get_legacy_zwave_migration(hass)
|
||||
return await migration_handler.get_data(config_entry)
|
||||
|
||||
|
||||
@singleton(LEGACY_ZWAVE_MIGRATION)
|
||||
async def get_legacy_zwave_migration(hass: HomeAssistant) -> LegacyZWaveMigration:
|
||||
"""Return legacy Z-Wave migration handler."""
|
||||
migration_handler = LegacyZWaveMigration(hass)
|
||||
await migration_handler.load_data()
|
||||
return migration_handler
|
||||
|
||||
|
||||
class LegacyZWaveMigration:
|
||||
"""Handle the migration from zwave to ozw and zwave_js."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant) -> None:
|
||||
"""Set up migration instance."""
|
||||
self._hass = hass
|
||||
self._store = Store(hass, STORAGE_VERSION, STORAGE_KEY)
|
||||
self._data: dict[str, dict[str, ZWaveMigrationData]] = {}
|
||||
|
||||
async def load_data(self) -> None:
|
||||
"""Load Z-Wave migration data."""
|
||||
stored = cast(dict, await self._store.async_load())
|
||||
if stored:
|
||||
self._data = stored
|
||||
|
||||
@callback
|
||||
def save_data(
|
||||
self, config_entry_id: str, entity_id: str, data: ZWaveMigrationData
|
||||
) -> None:
|
||||
"""Save Z-Wave migration data."""
|
||||
if config_entry_id not in self._data:
|
||||
self._data[config_entry_id] = {}
|
||||
self._data[config_entry_id][entity_id] = data
|
||||
self._store.async_delay_save(self._data_to_save, STORAGE_WRITE_DELAY)
|
||||
|
||||
@callback
|
||||
def _data_to_save(self) -> dict[str, dict[str, ZWaveMigrationData]]:
|
||||
"""Return data to save."""
|
||||
return self._data
|
||||
|
||||
@callback
|
||||
def add_entity_value(
|
||||
self,
|
||||
entity_id: str,
|
||||
entity_values: ZWaveDeviceEntityValues,
|
||||
) -> None:
|
||||
"""Add info for one entity and Z-Wave value."""
|
||||
ent_reg = async_get_entity_registry(self._hass)
|
||||
dev_reg = async_get_device_registry(self._hass)
|
||||
|
||||
node = entity_values.primary.node
|
||||
entity_entry = ent_reg.async_get(entity_id)
|
||||
assert entity_entry
|
||||
device_identifier, _ = node_device_id_and_name(
|
||||
node, entity_values.primary.instance
|
||||
)
|
||||
device_entry = dev_reg.async_get_device({device_identifier}, set())
|
||||
assert device_entry
|
||||
|
||||
# Normalize unit of measurement.
|
||||
if unit := entity_entry.unit_of_measurement:
|
||||
unit = unit.lower()
|
||||
if unit == "":
|
||||
unit = None
|
||||
|
||||
data: ZWaveMigrationData = {
|
||||
"node_id": node.node_id,
|
||||
"node_instance": entity_values.primary.instance,
|
||||
"command_class": entity_values.primary.command_class,
|
||||
"command_class_label": entity_values.primary.label,
|
||||
"value_index": entity_values.primary.index,
|
||||
"device_id": device_entry.id,
|
||||
"domain": entity_entry.domain,
|
||||
"entity_id": entity_id,
|
||||
"unique_id": entity_entry.unique_id,
|
||||
"unit_of_measurement": unit,
|
||||
}
|
||||
|
||||
self.save_data(entity_entry.config_entry_id, entity_id, data)
|
||||
|
||||
async def get_data(
|
||||
self, config_entry: ConfigEntry
|
||||
) -> dict[str, ZWaveMigrationData]:
|
||||
"""Return Z-Wave migration data."""
|
||||
await self.load_data()
|
||||
data = self._data.get(config_entry.entry_id)
|
||||
return data or {}
|
|
@ -88,6 +88,11 @@ def check_value_schema(value, schema):
|
|||
return True
|
||||
|
||||
|
||||
def compute_value_unique_id(node, value):
|
||||
"""Compute unique_id a value would get if it were to get one."""
|
||||
return f"{node.node_id}-{value.object_id}"
|
||||
|
||||
|
||||
def node_name(node):
|
||||
"""Return the name of the node."""
|
||||
if is_node_parsed(node):
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
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
|
||||
|
||||
|
@ -59,12 +58,14 @@ def websocket_get_migration_config(hass, connection, msg):
|
|||
|
||||
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.websocket_command(
|
||||
{vol.Required(TYPE): "zwave/start_zwave_js_config_flow"}
|
||||
)
|
||||
@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).
|
||||
async def websocket_start_zwave_js_config_flow(hass, connection, msg):
|
||||
"""Start the Z-Wave JS integration config flow (for migration wizard).
|
||||
|
||||
Return data with the flow id of the started ozw config flow.
|
||||
Return data with the flow id of the started Z-Wave JS config flow.
|
||||
"""
|
||||
config = hass.data[DATA_ZWAVE_CONFIG]
|
||||
data = {
|
||||
|
@ -72,7 +73,7 @@ async def websocket_start_ozw_config_flow(hass, connection, msg):
|
|||
"network_key": config[CONF_NETWORK_KEY],
|
||||
}
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
OZW_DOMAIN, context={"source": SOURCE_IMPORT}, data=data
|
||||
"zwave_js", context={"source": SOURCE_IMPORT}, data=data
|
||||
)
|
||||
connection.send_result(
|
||||
msg[ID],
|
||||
|
@ -86,4 +87,4 @@ def async_load_websocket_api(hass):
|
|||
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)
|
||||
websocket_api.async_register_command(hass, websocket_start_zwave_js_config_flow)
|
||||
|
|
|
@ -58,8 +58,15 @@ from .const import (
|
|||
DATA_CLIENT,
|
||||
DOMAIN,
|
||||
EVENT_DEVICE_ADDED_TO_REGISTRY,
|
||||
LOGGER,
|
||||
)
|
||||
from .helpers import async_enable_statistics, update_data_collection_preference
|
||||
from .migrate import (
|
||||
ZWaveMigrationData,
|
||||
async_get_migration_data,
|
||||
async_map_legacy_zwave_values,
|
||||
async_migrate_legacy_zwave,
|
||||
)
|
||||
|
||||
DATA_UNSUBSCRIBE = "unsubs"
|
||||
|
||||
|
@ -96,6 +103,9 @@ OPTED_IN = "opted_in"
|
|||
SECURITY_CLASSES = "security_classes"
|
||||
CLIENT_SIDE_AUTH = "client_side_auth"
|
||||
|
||||
# constants for migration
|
||||
DRY_RUN = "dry_run"
|
||||
|
||||
|
||||
def async_get_entry(orig_func: Callable) -> Callable:
|
||||
"""Decorate async function to get entry."""
|
||||
|
@ -218,6 +228,8 @@ def async_register_api(hass: HomeAssistant) -> None:
|
|||
hass, websocket_subscribe_controller_statistics
|
||||
)
|
||||
websocket_api.async_register_command(hass, websocket_subscribe_node_statistics)
|
||||
websocket_api.async_register_command(hass, websocket_node_ready)
|
||||
websocket_api.async_register_command(hass, websocket_migrate_zwave)
|
||||
hass.http.register_view(DumpView())
|
||||
hass.http.register_view(FirmwareUploadView())
|
||||
|
||||
|
@ -272,6 +284,42 @@ async def websocket_network_status(
|
|||
)
|
||||
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required(TYPE): "zwave_js/node_ready",
|
||||
vol.Required(ENTRY_ID): str,
|
||||
vol.Required(NODE_ID): int,
|
||||
}
|
||||
)
|
||||
@websocket_api.async_response
|
||||
@async_get_node
|
||||
async def websocket_node_ready(
|
||||
hass: HomeAssistant,
|
||||
connection: ActiveConnection,
|
||||
msg: dict,
|
||||
node: Node,
|
||||
) -> None:
|
||||
"""Subscribe to the node ready event of a Z-Wave JS node."""
|
||||
|
||||
@callback
|
||||
def forward_event(event: dict) -> None:
|
||||
"""Forward the event."""
|
||||
connection.send_message(
|
||||
websocket_api.event_message(msg[ID], {"event": event["event"]})
|
||||
)
|
||||
|
||||
@callback
|
||||
def async_cleanup() -> None:
|
||||
"""Remove signal listeners."""
|
||||
for unsub in unsubs:
|
||||
unsub()
|
||||
|
||||
connection.subscriptions[msg["id"]] = async_cleanup
|
||||
msg[DATA_UNSUBSCRIBE] = unsubs = [node.on("ready", forward_event)]
|
||||
|
||||
connection.send_result(msg[ID])
|
||||
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required(TYPE): "zwave_js/node_status",
|
||||
|
@ -1743,3 +1791,72 @@ async def websocket_subscribe_node_statistics(
|
|||
connection.subscriptions[msg["id"]] = async_cleanup
|
||||
|
||||
connection.send_result(msg[ID], _get_node_statistics_dict(node.statistics))
|
||||
|
||||
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required(TYPE): "zwave_js/migrate_zwave",
|
||||
vol.Required(ENTRY_ID): str,
|
||||
vol.Optional(DRY_RUN, default=True): bool,
|
||||
}
|
||||
)
|
||||
@websocket_api.async_response
|
||||
@async_get_entry
|
||||
async def websocket_migrate_zwave(
|
||||
hass: HomeAssistant,
|
||||
connection: ActiveConnection,
|
||||
msg: dict,
|
||||
entry: ConfigEntry,
|
||||
client: Client,
|
||||
) -> None:
|
||||
"""Migrate Z-Wave device and entity data to Z-Wave JS integration."""
|
||||
if "zwave" not in hass.config.components:
|
||||
connection.send_message(
|
||||
websocket_api.error_message(
|
||||
msg["id"], "zwave_not_loaded", "Integration zwave is not loaded"
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
zwave = hass.components.zwave
|
||||
zwave_config_entries = hass.config_entries.async_entries("zwave")
|
||||
zwave_config_entry = zwave_config_entries[0] # zwave only has a single config entry
|
||||
zwave_data: dict[str, ZWaveMigrationData] = await zwave.async_get_migration_data(
|
||||
hass, zwave_config_entry
|
||||
)
|
||||
LOGGER.debug("Migration zwave data: %s", zwave_data)
|
||||
|
||||
zwave_js_config_entry = entry
|
||||
zwave_js_data = await async_get_migration_data(hass, zwave_js_config_entry)
|
||||
LOGGER.debug("Migration zwave_js data: %s", zwave_js_data)
|
||||
|
||||
migration_map = async_map_legacy_zwave_values(zwave_data, zwave_js_data)
|
||||
|
||||
zwave_entity_ids = [entry["entity_id"] for entry in zwave_data.values()]
|
||||
zwave_js_entity_ids = [entry["entity_id"] for entry in zwave_js_data.values()]
|
||||
migration_device_map = {
|
||||
zwave_device_id: zwave_js_device_id
|
||||
for zwave_js_device_id, zwave_device_id in migration_map.device_entries.items()
|
||||
}
|
||||
migration_entity_map = {
|
||||
zwave_entry["entity_id"]: zwave_js_entity_id
|
||||
for zwave_js_entity_id, zwave_entry in migration_map.entity_entries.items()
|
||||
}
|
||||
LOGGER.debug("Migration entity map: %s", migration_entity_map)
|
||||
|
||||
if not msg[DRY_RUN]:
|
||||
await async_migrate_legacy_zwave(
|
||||
hass, zwave_config_entry, zwave_js_config_entry, migration_map
|
||||
)
|
||||
|
||||
connection.send_result(
|
||||
msg[ID],
|
||||
{
|
||||
"migration_device_map": migration_device_map,
|
||||
"zwave_entity_ids": zwave_entity_ids,
|
||||
"zwave_js_entity_ids": zwave_js_entity_ids,
|
||||
"migration_entity_map": migration_entity_map,
|
||||
"migrated": not msg[DRY_RUN],
|
||||
},
|
||||
)
|
||||
|
|
|
@ -302,6 +302,16 @@ class ConfigFlow(BaseZwaveJSFlow, config_entries.ConfigFlow, domain=DOMAIN):
|
|||
"""Return the options flow."""
|
||||
return OptionsFlowHandler(config_entry)
|
||||
|
||||
async def async_step_import(self, data: dict[str, Any]) -> FlowResult:
|
||||
"""Handle imported data.
|
||||
|
||||
This step will be used when importing data
|
||||
during Z-Wave to Z-Wave JS 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: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
|
|
|
@ -15,6 +15,7 @@ from homeassistant.helpers.entity import Entity
|
|||
from .const import DOMAIN
|
||||
from .discovery import ZwaveDiscoveryInfo
|
||||
from .helpers import get_device_id, get_unique_id
|
||||
from .migrate import async_add_migration_entity_value
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -109,6 +110,11 @@ class ZWaveBaseEntity(Entity):
|
|||
)
|
||||
)
|
||||
|
||||
# Add legacy Z-Wave migration data.
|
||||
await async_add_migration_entity_value(
|
||||
self.hass, self.config_entry, self.entity_id, self.info
|
||||
)
|
||||
|
||||
def generate_name(
|
||||
self,
|
||||
include_value_name: bool = False,
|
||||
|
|
|
@ -1,27 +1,355 @@
|
|||
"""Functions used to migrate unique IDs for Z-Wave JS entities."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, field
|
||||
import logging
|
||||
from typing import TypedDict, cast
|
||||
|
||||
from zwave_js_server.client import Client as ZwaveClient
|
||||
from zwave_js_server.model.value import Value as ZwaveValue
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import STATE_UNAVAILABLE
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.device_registry import DeviceEntry
|
||||
from homeassistant.helpers.device_registry import (
|
||||
DeviceEntry,
|
||||
async_get as async_get_device_registry,
|
||||
)
|
||||
from homeassistant.helpers.entity_registry import (
|
||||
EntityRegistry,
|
||||
RegistryEntry,
|
||||
async_entries_for_device,
|
||||
async_get as async_get_entity_registry,
|
||||
)
|
||||
from homeassistant.helpers.singleton import singleton
|
||||
from homeassistant.helpers.storage import Store
|
||||
|
||||
from .const import DOMAIN
|
||||
from .discovery import ZwaveDiscoveryInfo
|
||||
from .helpers import get_unique_id
|
||||
from .helpers import get_device_id, get_unique_id
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
LEGACY_ZWAVE_MIGRATION = f"{DOMAIN}_legacy_zwave_migration"
|
||||
MIGRATED = "migrated"
|
||||
STORAGE_WRITE_DELAY = 30
|
||||
STORAGE_KEY = f"{DOMAIN}.legacy_zwave_migration"
|
||||
STORAGE_VERSION = 1
|
||||
|
||||
NOTIFICATION_CC_LABEL_TO_PROPERTY_NAME = {
|
||||
"Smoke": "Smoke Alarm",
|
||||
"Carbon Monoxide": "CO Alarm",
|
||||
"Carbon Dioxide": "CO2 Alarm",
|
||||
"Heat": "Heat Alarm",
|
||||
"Flood": "Water Alarm",
|
||||
"Access Control": "Access Control",
|
||||
"Burglar": "Home Security",
|
||||
"Power Management": "Power Management",
|
||||
"System": "System",
|
||||
"Emergency": "Siren",
|
||||
"Clock": "Clock",
|
||||
"Appliance": "Appliance",
|
||||
"HomeHealth": "Home Health",
|
||||
}
|
||||
|
||||
SENSOR_MULTILEVEL_CC_LABEL_TO_PROPERTY_NAME = {
|
||||
"Temperature": "Air temperature",
|
||||
"General": "General purpose",
|
||||
"Luminance": "Illuminance",
|
||||
"Power": "Power",
|
||||
"Relative Humidity": "Humidity",
|
||||
"Velocity": "Velocity",
|
||||
"Direction": "Direction",
|
||||
"Atmospheric Pressure": "Atmospheric pressure",
|
||||
"Barometric Pressure": "Barometric pressure",
|
||||
"Solar Radiation": "Solar radiation",
|
||||
"Dew Point": "Dew point",
|
||||
"Rain Rate": "Rain rate",
|
||||
"Tide Level": "Tide level",
|
||||
"Weight": "Weight",
|
||||
"Voltage": "Voltage",
|
||||
"Current": "Current",
|
||||
"CO2 Level": "Carbon dioxide (CO₂) level",
|
||||
"Air Flow": "Air flow",
|
||||
"Tank Capacity": "Tank capacity",
|
||||
"Distance": "Distance",
|
||||
"Angle Position": "Angle position",
|
||||
"Rotation": "Rotation",
|
||||
"Water Temperature": "Water temperature",
|
||||
"Soil Temperature": "Soil temperature",
|
||||
"Seismic Intensity": "Seismic Intensity",
|
||||
"Seismic Magnitude": "Seismic magnitude",
|
||||
"Ultraviolet": "Ultraviolet",
|
||||
"Electrical Resistivity": "Electrical resistivity",
|
||||
"Electrical Conductivity": "Electrical conductivity",
|
||||
"Loudness": "Loudness",
|
||||
"Moisture": "Moisture",
|
||||
}
|
||||
|
||||
CC_ID_LABEL_TO_PROPERTY = {
|
||||
49: SENSOR_MULTILEVEL_CC_LABEL_TO_PROPERTY_NAME,
|
||||
113: NOTIFICATION_CC_LABEL_TO_PROPERTY_NAME,
|
||||
}
|
||||
|
||||
|
||||
class ZWaveMigrationData(TypedDict):
|
||||
"""Represent the Z-Wave migration data dict."""
|
||||
|
||||
node_id: int
|
||||
node_instance: int
|
||||
command_class: int
|
||||
command_class_label: str
|
||||
value_index: int
|
||||
device_id: str
|
||||
domain: str
|
||||
entity_id: str
|
||||
unique_id: str
|
||||
unit_of_measurement: str | None
|
||||
|
||||
|
||||
class ZWaveJSMigrationData(TypedDict):
|
||||
"""Represent the Z-Wave JS migration data dict."""
|
||||
|
||||
node_id: int
|
||||
endpoint_index: int
|
||||
command_class: int
|
||||
value_property_name: str
|
||||
value_property_key_name: str | None
|
||||
value_id: str
|
||||
device_id: str
|
||||
domain: str
|
||||
entity_id: str
|
||||
unique_id: str
|
||||
unit_of_measurement: str | None
|
||||
|
||||
|
||||
@dataclass
|
||||
class LegacyZWaveMappedData:
|
||||
"""Represent the mapped data between Z-Wave and Z-Wave JS."""
|
||||
|
||||
entity_entries: dict[str, ZWaveMigrationData] = field(default_factory=dict)
|
||||
device_entries: dict[str, str] = field(default_factory=dict)
|
||||
|
||||
|
||||
async def async_add_migration_entity_value(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
entity_id: str,
|
||||
discovery_info: ZwaveDiscoveryInfo,
|
||||
) -> None:
|
||||
"""Add Z-Wave JS entity value for legacy Z-Wave migration."""
|
||||
migration_handler: LegacyZWaveMigration = await get_legacy_zwave_migration(hass)
|
||||
migration_handler.add_entity_value(config_entry, entity_id, discovery_info)
|
||||
|
||||
|
||||
async def async_get_migration_data(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry
|
||||
) -> dict[str, ZWaveJSMigrationData]:
|
||||
"""Return Z-Wave JS migration data."""
|
||||
migration_handler: LegacyZWaveMigration = await get_legacy_zwave_migration(hass)
|
||||
return await migration_handler.get_data(config_entry)
|
||||
|
||||
|
||||
@singleton(LEGACY_ZWAVE_MIGRATION)
|
||||
async def get_legacy_zwave_migration(hass: HomeAssistant) -> LegacyZWaveMigration:
|
||||
"""Return legacy Z-Wave migration handler."""
|
||||
migration_handler = LegacyZWaveMigration(hass)
|
||||
await migration_handler.load_data()
|
||||
return migration_handler
|
||||
|
||||
|
||||
class LegacyZWaveMigration:
|
||||
"""Handle the migration from zwave to zwave_js."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant) -> None:
|
||||
"""Set up migration instance."""
|
||||
self._hass = hass
|
||||
self._store = Store(hass, STORAGE_VERSION, STORAGE_KEY)
|
||||
self._data: dict[str, dict[str, ZWaveJSMigrationData]] = {}
|
||||
|
||||
async def load_data(self) -> None:
|
||||
"""Load Z-Wave JS migration data."""
|
||||
stored = cast(dict, await self._store.async_load())
|
||||
if stored:
|
||||
self._data = stored
|
||||
|
||||
@callback
|
||||
def save_data(
|
||||
self, config_entry_id: str, entity_id: str, data: ZWaveJSMigrationData
|
||||
) -> None:
|
||||
"""Save Z-Wave JS migration data."""
|
||||
if config_entry_id not in self._data:
|
||||
self._data[config_entry_id] = {}
|
||||
self._data[config_entry_id][entity_id] = data
|
||||
self._store.async_delay_save(self._data_to_save, STORAGE_WRITE_DELAY)
|
||||
|
||||
@callback
|
||||
def _data_to_save(self) -> dict[str, dict[str, ZWaveJSMigrationData]]:
|
||||
"""Return data to save."""
|
||||
return self._data
|
||||
|
||||
@callback
|
||||
def add_entity_value(
|
||||
self,
|
||||
config_entry: ConfigEntry,
|
||||
entity_id: str,
|
||||
discovery_info: ZwaveDiscoveryInfo,
|
||||
) -> None:
|
||||
"""Add info for one entity and Z-Wave JS value."""
|
||||
ent_reg = async_get_entity_registry(self._hass)
|
||||
dev_reg = async_get_device_registry(self._hass)
|
||||
|
||||
node = discovery_info.node
|
||||
primary_value = discovery_info.primary_value
|
||||
entity_entry = ent_reg.async_get(entity_id)
|
||||
assert entity_entry
|
||||
device_identifier = get_device_id(node.client, node)
|
||||
device_entry = dev_reg.async_get_device({device_identifier}, set())
|
||||
assert device_entry
|
||||
|
||||
# Normalize unit of measurement.
|
||||
if unit := entity_entry.unit_of_measurement:
|
||||
unit = unit.lower()
|
||||
if unit == "":
|
||||
unit = None
|
||||
|
||||
data: ZWaveJSMigrationData = {
|
||||
"node_id": node.node_id,
|
||||
"endpoint_index": node.index,
|
||||
"command_class": primary_value.command_class,
|
||||
"value_property_name": primary_value.property_name,
|
||||
"value_property_key_name": primary_value.property_key_name,
|
||||
"value_id": primary_value.value_id,
|
||||
"device_id": device_entry.id,
|
||||
"domain": entity_entry.domain,
|
||||
"entity_id": entity_id,
|
||||
"unique_id": entity_entry.unique_id,
|
||||
"unit_of_measurement": unit,
|
||||
}
|
||||
|
||||
self.save_data(config_entry.entry_id, entity_id, data)
|
||||
|
||||
async def get_data(
|
||||
self, config_entry: ConfigEntry
|
||||
) -> dict[str, ZWaveJSMigrationData]:
|
||||
"""Return Z-Wave JS migration data for a config entry."""
|
||||
await self.load_data()
|
||||
data = self._data.get(config_entry.entry_id)
|
||||
return data or {}
|
||||
|
||||
|
||||
@callback
|
||||
def async_map_legacy_zwave_values(
|
||||
zwave_data: dict[str, ZWaveMigrationData],
|
||||
zwave_js_data: dict[str, ZWaveJSMigrationData],
|
||||
) -> LegacyZWaveMappedData:
|
||||
"""Map Z-Wave node values onto Z-Wave JS node values."""
|
||||
migration_map = LegacyZWaveMappedData()
|
||||
zwave_proc_data: dict[
|
||||
tuple[int, int, int, str, str | None, str | None],
|
||||
ZWaveMigrationData | None,
|
||||
] = {}
|
||||
zwave_js_proc_data: dict[
|
||||
tuple[int, int, int, str, str | None, str | None],
|
||||
ZWaveJSMigrationData | None,
|
||||
] = {}
|
||||
|
||||
for zwave_item in zwave_data.values():
|
||||
zwave_js_property_name = CC_ID_LABEL_TO_PROPERTY.get(
|
||||
zwave_item["command_class"], {}
|
||||
).get(zwave_item["command_class_label"])
|
||||
item_id = (
|
||||
zwave_item["node_id"],
|
||||
zwave_item["command_class"],
|
||||
zwave_item["node_instance"] - 1,
|
||||
zwave_item["domain"],
|
||||
zwave_item["unit_of_measurement"],
|
||||
zwave_js_property_name,
|
||||
)
|
||||
|
||||
# Filter out duplicates that are not resolvable.
|
||||
if item_id in zwave_proc_data:
|
||||
zwave_proc_data[item_id] = None
|
||||
continue
|
||||
|
||||
zwave_proc_data[item_id] = zwave_item
|
||||
|
||||
for zwave_js_item in zwave_js_data.values():
|
||||
# Only identify with property name if there is a command class label map.
|
||||
if zwave_js_item["command_class"] in CC_ID_LABEL_TO_PROPERTY:
|
||||
zwave_js_property_name = zwave_js_item["value_property_name"]
|
||||
else:
|
||||
zwave_js_property_name = None
|
||||
item_id = (
|
||||
zwave_js_item["node_id"],
|
||||
zwave_js_item["command_class"],
|
||||
zwave_js_item["endpoint_index"],
|
||||
zwave_js_item["domain"],
|
||||
zwave_js_item["unit_of_measurement"],
|
||||
zwave_js_property_name,
|
||||
)
|
||||
|
||||
# Filter out duplicates that are not resolvable.
|
||||
if item_id in zwave_js_proc_data:
|
||||
zwave_js_proc_data[item_id] = None
|
||||
continue
|
||||
|
||||
zwave_js_proc_data[item_id] = zwave_js_item
|
||||
|
||||
for item_id, zwave_entry in zwave_proc_data.items():
|
||||
zwave_js_entry = zwave_js_proc_data.pop(item_id, None)
|
||||
|
||||
if zwave_entry is None or zwave_js_entry is None:
|
||||
continue
|
||||
|
||||
migration_map.entity_entries[zwave_js_entry["entity_id"]] = zwave_entry
|
||||
migration_map.device_entries[zwave_js_entry["device_id"]] = zwave_entry[
|
||||
"device_id"
|
||||
]
|
||||
|
||||
return migration_map
|
||||
|
||||
|
||||
async def async_migrate_legacy_zwave(
|
||||
hass: HomeAssistant,
|
||||
zwave_config_entry: ConfigEntry,
|
||||
zwave_js_config_entry: ConfigEntry,
|
||||
migration_map: LegacyZWaveMappedData,
|
||||
) -> None:
|
||||
"""Perform Z-Wave to Z-Wave JS migration."""
|
||||
dev_reg = async_get_device_registry(hass)
|
||||
for zwave_js_device_id, zwave_device_id in migration_map.device_entries.items():
|
||||
zwave_device_entry = dev_reg.async_get(zwave_device_id)
|
||||
if not zwave_device_entry:
|
||||
continue
|
||||
dev_reg.async_update_device(
|
||||
zwave_js_device_id,
|
||||
area_id=zwave_device_entry.area_id,
|
||||
name_by_user=zwave_device_entry.name_by_user,
|
||||
)
|
||||
|
||||
ent_reg = async_get_entity_registry(hass)
|
||||
for zwave_js_entity_id, zwave_entry in migration_map.entity_entries.items():
|
||||
zwave_entity_id = zwave_entry["entity_id"]
|
||||
entity_entry = ent_reg.async_get(zwave_entity_id)
|
||||
if not entity_entry:
|
||||
continue
|
||||
ent_reg.async_remove(zwave_entity_id)
|
||||
ent_reg.async_update_entity(
|
||||
zwave_js_entity_id,
|
||||
new_entity_id=entity_entry.entity_id,
|
||||
name=entity_entry.name,
|
||||
icon=entity_entry.icon,
|
||||
)
|
||||
|
||||
await hass.config_entries.async_remove(zwave_config_entry.entry_id)
|
||||
|
||||
updates = {
|
||||
**zwave_js_config_entry.data,
|
||||
MIGRATED: True,
|
||||
}
|
||||
hass.config_entries.async_update_entry(zwave_js_config_entry, data=updates)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ValueID:
|
||||
|
|
|
@ -134,14 +134,14 @@ IGNORE_VIOLATIONS = {
|
|||
# Demo
|
||||
("demo", "manual"),
|
||||
("demo", "openalpr_local"),
|
||||
# Migration wizard from zwave to ozw.
|
||||
"ozw",
|
||||
# Migration of settings from zeroconf to network
|
||||
("network", "zeroconf"),
|
||||
# This should become a helper method that integrations can submit data to
|
||||
("websocket_api", "lovelace"),
|
||||
("websocket_api", "shopping_list"),
|
||||
"logbook",
|
||||
# Migration wizard from zwave to zwave_js.
|
||||
"zwave_js",
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -512,49 +512,3 @@ 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_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_entry.mock_calls) == 1
|
||||
|
|
|
@ -1,285 +0,0 @@
|
|||
"""Test zwave to ozw migration."""
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.ozw.websocket_api import ID, TYPE
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
|
||||
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 = dr.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 = er.RegistryEntry(
|
||||
entity_id=ZWAVE_SOURCE_ENTITY,
|
||||
unique_id=ZWAVE_SOURCE_NODE_UNIQUE_ID,
|
||||
platform="zwave",
|
||||
name="Z-Wave Source Node",
|
||||
)
|
||||
zwave_battery_device = dr.DeviceEntry(
|
||||
id=ZWAVE_BATTERY_DEVICE_ID,
|
||||
name_by_user=ZWAVE_BATTERY_DEVICE_NAME,
|
||||
area_id=ZWAVE_BATTERY_DEVICE_AREA,
|
||||
)
|
||||
zwave_battery_entry = er.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 = dr.DeviceEntry(
|
||||
id=ZWAVE_POWER_DEVICE_ID,
|
||||
name_by_user=ZWAVE_POWER_DEVICE_NAME,
|
||||
area_id=ZWAVE_POWER_DEVICE_AREA,
|
||||
)
|
||||
zwave_power_entry = er.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 = dr.async_get(hass)
|
||||
ent_reg = er.async_get(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 = er.async_get(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"
|
|
@ -15,8 +15,7 @@ from homeassistant.components.zwave import (
|
|||
DATA_NETWORK,
|
||||
const,
|
||||
)
|
||||
from homeassistant.components.zwave.binary_sensor import get_device
|
||||
from homeassistant.const import ATTR_ENTITY_ID, ATTR_NAME
|
||||
from homeassistant.const import ATTR_NAME
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
|
@ -1854,38 +1853,6 @@ async def test_remove_association(hass, mock_openzwave, zwave_setup_ready):
|
|||
assert group.remove_association.mock_calls[0][1][1] == 5
|
||||
|
||||
|
||||
async def test_refresh_entity(hass, mock_openzwave, zwave_setup_ready):
|
||||
"""Test zwave refresh_entity service."""
|
||||
node = MockNode()
|
||||
value = MockValue(
|
||||
data=False, node=node, command_class=const.COMMAND_CLASS_SENSOR_BINARY
|
||||
)
|
||||
power_value = MockValue(data=50, node=node, command_class=const.COMMAND_CLASS_METER)
|
||||
values = MockEntityValues(primary=value, power=power_value)
|
||||
device = get_device(node=node, values=values, node_config={})
|
||||
device.hass = hass
|
||||
device.entity_id = "binary_sensor.mock_entity_id"
|
||||
await device.async_added_to_hass()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await hass.services.async_call(
|
||||
"zwave", "refresh_entity", {ATTR_ENTITY_ID: "binary_sensor.mock_entity_id"}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert node.refresh_value.called
|
||||
assert len(node.refresh_value.mock_calls) == 2
|
||||
assert (
|
||||
sorted(
|
||||
[
|
||||
node.refresh_value.mock_calls[0][1][0],
|
||||
node.refresh_value.mock_calls[1][1][0],
|
||||
]
|
||||
)
|
||||
== sorted([value.value_id, power_value.value_id])
|
||||
)
|
||||
|
||||
|
||||
async def test_refresh_node(hass, mock_openzwave, zwave_setup_ready):
|
||||
"""Test zwave refresh_node service."""
|
||||
zwave_network = hass.data[DATA_NETWORK]
|
||||
|
|
|
@ -44,8 +44,8 @@ async def test_zwave_ws_api(hass, mock_openzwave, hass_ws_client):
|
|||
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."""
|
||||
async def test_zwave_zwave_js_migration_api(hass, mock_openzwave, hass_ws_client):
|
||||
"""Test Z-Wave to Z-Wave JS websocket migration API."""
|
||||
|
||||
await async_setup_component(
|
||||
hass,
|
||||
|
@ -76,14 +76,14 @@ async def test_zwave_ozw_migration_api(hass, mock_openzwave, hass_ws_client):
|
|||
) as async_init:
|
||||
|
||||
async_init.return_value = {"flow_id": "mock_flow_id"}
|
||||
await client.send_json({ID: 7, TYPE: "zwave/start_ozw_config_flow"})
|
||||
await client.send_json({ID: 7, TYPE: "zwave/start_zwave_js_config_flow"})
|
||||
msg = await client.receive_json()
|
||||
|
||||
result = msg["result"]
|
||||
|
||||
assert result["flow_id"] == "mock_flow_id"
|
||||
assert async_init.call_args == call(
|
||||
"ozw",
|
||||
"zwave_js",
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data={"usb_path": "/dev/zwave", "network_key": NETWORK_KEY},
|
||||
)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Test the Z-Wave JS Websocket API."""
|
||||
from copy import deepcopy
|
||||
import json
|
||||
from unittest.mock import patch
|
||||
|
||||
|
@ -17,6 +18,7 @@ from zwave_js_server.exceptions import (
|
|||
NotFoundError,
|
||||
SetValueFailed,
|
||||
)
|
||||
from zwave_js_server.model.node import Node
|
||||
from zwave_js_server.model.value import _get_value_id_from_dict, get_value_id
|
||||
|
||||
from homeassistant.components.websocket_api.const import ERR_NOT_FOUND
|
||||
|
@ -78,6 +80,51 @@ async def test_network_status(hass, integration, hass_ws_client):
|
|||
assert msg["error"]["code"] == ERR_NOT_LOADED
|
||||
|
||||
|
||||
async def test_node_ready(
|
||||
hass,
|
||||
multisensor_6_state,
|
||||
client,
|
||||
integration,
|
||||
hass_ws_client,
|
||||
):
|
||||
"""Test the node ready websocket command."""
|
||||
entry = integration
|
||||
ws_client = await hass_ws_client(hass)
|
||||
node_data = deepcopy(multisensor_6_state) # Copy to allow modification in tests.
|
||||
node = Node(client, node_data)
|
||||
node.data["ready"] = False
|
||||
client.driver.controller.nodes[node.node_id] = node
|
||||
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 3,
|
||||
TYPE: "zwave_js/node_ready",
|
||||
ENTRY_ID: entry.entry_id,
|
||||
"node_id": node.node_id,
|
||||
}
|
||||
)
|
||||
|
||||
msg = await ws_client.receive_json()
|
||||
assert msg["success"]
|
||||
|
||||
node.data["ready"] = True
|
||||
event = Event(
|
||||
"ready",
|
||||
{
|
||||
"source": "node",
|
||||
"event": "ready",
|
||||
"nodeId": node.node_id,
|
||||
"nodeState": node.data,
|
||||
},
|
||||
)
|
||||
node.receive_event(event)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
msg = await ws_client.receive_json()
|
||||
|
||||
assert msg["event"]["event"] == "ready"
|
||||
|
||||
|
||||
async def test_node_status(hass, multisensor_6, integration, hass_ws_client):
|
||||
"""Test the node status websocket command."""
|
||||
entry = integration
|
||||
|
|
|
@ -2053,3 +2053,71 @@ async def test_options_addon_not_installed(
|
|||
assert entry.data["integration_created_addon"] is True
|
||||
assert client.connect.call_count == 2
|
||||
assert client.disconnect.call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}])
|
||||
async def test_import_addon_installed(
|
||||
hass,
|
||||
supervisor,
|
||||
addon_installed,
|
||||
addon_options,
|
||||
set_addon_options,
|
||||
start_addon,
|
||||
get_addon_discovery_info,
|
||||
):
|
||||
"""Test import step while add-on already installed on Supervisor."""
|
||||
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"] == "configure_addon"
|
||||
|
||||
# the default input should be the imported data
|
||||
default_input = result["data_schema"]({})
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], default_input
|
||||
)
|
||||
|
||||
assert set_addon_options.call_args == call(
|
||||
hass,
|
||||
"core_zwave_js",
|
||||
{"options": {"device": "/test/imported", "network_key": "imported123"}},
|
||||
)
|
||||
|
||||
assert result["type"] == "progress"
|
||||
assert result["step_id"] == "start_addon"
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.zwave_js.async_setup", return_value=True
|
||||
) as mock_setup, patch(
|
||||
"homeassistant.components.zwave_js.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
await hass.async_block_till_done()
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert start_addon.call_args == call(hass, "core_zwave_js")
|
||||
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["title"] == TITLE
|
||||
assert result["data"] == {
|
||||
"url": "ws://host1:3001",
|
||||
"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
|
||||
|
|
|
@ -1,15 +1,443 @@
|
|||
"""Test the Z-Wave JS migration module."""
|
||||
import copy
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from zwave_js_server.model.node import Node
|
||||
|
||||
from homeassistant.components.zwave_js.api import ENTRY_ID, ID, TYPE
|
||||
from homeassistant.components.zwave_js.const import DOMAIN
|
||||
from homeassistant.components.zwave_js.helpers import get_device_id
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
|
||||
from .common import AIR_TEMPERATURE_SENSOR, NOTIFICATION_MOTION_BINARY_SENSOR
|
||||
|
||||
from tests.common import MockConfigEntry, mock_device_registry, mock_registry
|
||||
|
||||
# Switch device
|
||||
ZWAVE_SWITCH_DEVICE_ID = "zwave_switch_device_id"
|
||||
ZWAVE_SWITCH_DEVICE_NAME = "Z-Wave Switch Device"
|
||||
ZWAVE_SWITCH_DEVICE_AREA = "Z-Wave Switch Area"
|
||||
ZWAVE_SWITCH_ENTITY = "switch.zwave_switch_node"
|
||||
ZWAVE_SWITCH_UNIQUE_ID = "102-6789"
|
||||
ZWAVE_SWITCH_NAME = "Z-Wave Switch"
|
||||
ZWAVE_SWITCH_ICON = "mdi:zwave-test-switch"
|
||||
ZWAVE_POWER_ENTITY = "sensor.zwave_power"
|
||||
ZWAVE_POWER_UNIQUE_ID = "102-5678"
|
||||
ZWAVE_POWER_NAME = "Z-Wave Power"
|
||||
ZWAVE_POWER_ICON = "mdi:zwave-test-power"
|
||||
|
||||
# Multisensor device
|
||||
ZWAVE_MULTISENSOR_DEVICE_ID = "zwave_multisensor_device_id"
|
||||
ZWAVE_MULTISENSOR_DEVICE_NAME = "Z-Wave Multisensor Device"
|
||||
ZWAVE_MULTISENSOR_DEVICE_AREA = "Z-Wave Multisensor Area"
|
||||
ZWAVE_SOURCE_NODE_ENTITY = "sensor.zwave_source_node"
|
||||
ZWAVE_SOURCE_NODE_UNIQUE_ID = "52-4321"
|
||||
ZWAVE_BATTERY_ENTITY = "sensor.zwave_battery_level"
|
||||
ZWAVE_BATTERY_UNIQUE_ID = "52-1234"
|
||||
ZWAVE_BATTERY_NAME = "Z-Wave Battery Level"
|
||||
ZWAVE_BATTERY_ICON = "mdi:zwave-test-battery"
|
||||
ZWAVE_TAMPERING_ENTITY = "sensor.zwave_tampering"
|
||||
ZWAVE_TAMPERING_UNIQUE_ID = "52-3456"
|
||||
ZWAVE_TAMPERING_NAME = "Z-Wave Tampering"
|
||||
ZWAVE_TAMPERING_ICON = "mdi:zwave-test-tampering"
|
||||
|
||||
|
||||
@pytest.fixture(name="zwave_migration_data")
|
||||
def zwave_migration_data_fixture(hass):
|
||||
"""Return mock zwave migration data."""
|
||||
zwave_switch_device = dr.DeviceEntry(
|
||||
id=ZWAVE_SWITCH_DEVICE_ID,
|
||||
name_by_user=ZWAVE_SWITCH_DEVICE_NAME,
|
||||
area_id=ZWAVE_SWITCH_DEVICE_AREA,
|
||||
)
|
||||
zwave_switch_entry = er.RegistryEntry(
|
||||
entity_id=ZWAVE_SWITCH_ENTITY,
|
||||
unique_id=ZWAVE_SWITCH_UNIQUE_ID,
|
||||
platform="zwave",
|
||||
name=ZWAVE_SWITCH_NAME,
|
||||
icon=ZWAVE_SWITCH_ICON,
|
||||
)
|
||||
zwave_multisensor_device = dr.DeviceEntry(
|
||||
id=ZWAVE_MULTISENSOR_DEVICE_ID,
|
||||
name_by_user=ZWAVE_MULTISENSOR_DEVICE_NAME,
|
||||
area_id=ZWAVE_MULTISENSOR_DEVICE_AREA,
|
||||
)
|
||||
zwave_source_node_entry = er.RegistryEntry(
|
||||
entity_id=ZWAVE_SOURCE_NODE_ENTITY,
|
||||
unique_id=ZWAVE_SOURCE_NODE_UNIQUE_ID,
|
||||
platform="zwave",
|
||||
name="Z-Wave Source Node",
|
||||
)
|
||||
zwave_battery_entry = er.RegistryEntry(
|
||||
entity_id=ZWAVE_BATTERY_ENTITY,
|
||||
unique_id=ZWAVE_BATTERY_UNIQUE_ID,
|
||||
platform="zwave",
|
||||
name=ZWAVE_BATTERY_NAME,
|
||||
icon=ZWAVE_BATTERY_ICON,
|
||||
unit_of_measurement="%",
|
||||
)
|
||||
zwave_power_entry = er.RegistryEntry(
|
||||
entity_id=ZWAVE_POWER_ENTITY,
|
||||
unique_id=ZWAVE_POWER_UNIQUE_ID,
|
||||
platform="zwave",
|
||||
name=ZWAVE_POWER_NAME,
|
||||
icon=ZWAVE_POWER_ICON,
|
||||
unit_of_measurement="W",
|
||||
)
|
||||
zwave_tampering_entry = er.RegistryEntry(
|
||||
entity_id=ZWAVE_TAMPERING_ENTITY,
|
||||
unique_id=ZWAVE_TAMPERING_UNIQUE_ID,
|
||||
platform="zwave",
|
||||
name=ZWAVE_TAMPERING_NAME,
|
||||
icon=ZWAVE_TAMPERING_ICON,
|
||||
unit_of_measurement="", # Test empty string unit normalization.
|
||||
)
|
||||
|
||||
zwave_migration_data = {
|
||||
ZWAVE_SWITCH_ENTITY: {
|
||||
"node_id": 102,
|
||||
"node_instance": 1,
|
||||
"command_class": 37,
|
||||
"command_class_label": "",
|
||||
"value_index": 1,
|
||||
"device_id": zwave_switch_device.id,
|
||||
"domain": zwave_switch_entry.domain,
|
||||
"entity_id": zwave_switch_entry.entity_id,
|
||||
"unique_id": ZWAVE_SWITCH_UNIQUE_ID,
|
||||
"unit_of_measurement": zwave_switch_entry.unit_of_measurement,
|
||||
},
|
||||
ZWAVE_POWER_ENTITY: {
|
||||
"node_id": 102,
|
||||
"node_instance": 1,
|
||||
"command_class": 50,
|
||||
"command_class_label": "Power",
|
||||
"value_index": 8,
|
||||
"device_id": zwave_switch_device.id,
|
||||
"domain": zwave_power_entry.domain,
|
||||
"entity_id": zwave_power_entry.entity_id,
|
||||
"unique_id": ZWAVE_POWER_UNIQUE_ID,
|
||||
"unit_of_measurement": zwave_power_entry.unit_of_measurement,
|
||||
},
|
||||
ZWAVE_SOURCE_NODE_ENTITY: {
|
||||
"node_id": 52,
|
||||
"node_instance": 1,
|
||||
"command_class": 113,
|
||||
"command_class_label": "SourceNodeId",
|
||||
"value_index": 1,
|
||||
"device_id": zwave_multisensor_device.id,
|
||||
"domain": zwave_source_node_entry.domain,
|
||||
"entity_id": zwave_source_node_entry.entity_id,
|
||||
"unique_id": ZWAVE_SOURCE_NODE_UNIQUE_ID,
|
||||
"unit_of_measurement": zwave_source_node_entry.unit_of_measurement,
|
||||
},
|
||||
ZWAVE_BATTERY_ENTITY: {
|
||||
"node_id": 52,
|
||||
"node_instance": 1,
|
||||
"command_class": 128,
|
||||
"command_class_label": "Battery Level",
|
||||
"value_index": 0,
|
||||
"device_id": zwave_multisensor_device.id,
|
||||
"domain": zwave_battery_entry.domain,
|
||||
"entity_id": zwave_battery_entry.entity_id,
|
||||
"unique_id": ZWAVE_BATTERY_UNIQUE_ID,
|
||||
"unit_of_measurement": zwave_battery_entry.unit_of_measurement,
|
||||
},
|
||||
ZWAVE_TAMPERING_ENTITY: {
|
||||
"node_id": 52,
|
||||
"node_instance": 1,
|
||||
"command_class": 113,
|
||||
"command_class_label": "Burglar",
|
||||
"value_index": 10,
|
||||
"device_id": zwave_multisensor_device.id,
|
||||
"domain": zwave_tampering_entry.domain,
|
||||
"entity_id": zwave_tampering_entry.entity_id,
|
||||
"unique_id": ZWAVE_TAMPERING_UNIQUE_ID,
|
||||
"unit_of_measurement": zwave_tampering_entry.unit_of_measurement,
|
||||
},
|
||||
}
|
||||
|
||||
mock_device_registry(
|
||||
hass,
|
||||
{
|
||||
zwave_switch_device.id: zwave_switch_device,
|
||||
zwave_multisensor_device.id: zwave_multisensor_device,
|
||||
},
|
||||
)
|
||||
mock_registry(
|
||||
hass,
|
||||
{
|
||||
ZWAVE_SWITCH_ENTITY: zwave_switch_entry,
|
||||
ZWAVE_SOURCE_NODE_ENTITY: zwave_source_node_entry,
|
||||
ZWAVE_BATTERY_ENTITY: zwave_battery_entry,
|
||||
ZWAVE_POWER_ENTITY: zwave_power_entry,
|
||||
ZWAVE_TAMPERING_ENTITY: zwave_tampering_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_migration_data",
|
||||
return_value=zwave_migration_data,
|
||||
):
|
||||
yield zwave_config_entry
|
||||
|
||||
|
||||
async def test_migrate_zwave(
|
||||
hass,
|
||||
zwave_integration,
|
||||
aeon_smart_switch_6,
|
||||
multisensor_6,
|
||||
integration,
|
||||
hass_ws_client,
|
||||
):
|
||||
"""Test the Z-Wave to Z-Wave JS migration websocket api."""
|
||||
entry = integration
|
||||
client = await hass_ws_client(hass)
|
||||
|
||||
assert hass.config_entries.async_entries("zwave")
|
||||
|
||||
await client.send_json(
|
||||
{
|
||||
ID: 5,
|
||||
TYPE: "zwave_js/migrate_zwave",
|
||||
ENTRY_ID: entry.entry_id,
|
||||
"dry_run": False,
|
||||
}
|
||||
)
|
||||
msg = await client.receive_json()
|
||||
result = msg["result"]
|
||||
|
||||
migration_entity_map = {
|
||||
ZWAVE_SWITCH_ENTITY: "switch.smart_switch_6",
|
||||
ZWAVE_BATTERY_ENTITY: "sensor.multisensor_6_battery_level",
|
||||
}
|
||||
|
||||
assert result["zwave_entity_ids"] == [
|
||||
ZWAVE_SWITCH_ENTITY,
|
||||
ZWAVE_POWER_ENTITY,
|
||||
ZWAVE_SOURCE_NODE_ENTITY,
|
||||
ZWAVE_BATTERY_ENTITY,
|
||||
ZWAVE_TAMPERING_ENTITY,
|
||||
]
|
||||
expected_zwave_js_entities = [
|
||||
"switch.smart_switch_6",
|
||||
"sensor.multisensor_6_air_temperature",
|
||||
"sensor.multisensor_6_illuminance",
|
||||
"sensor.multisensor_6_humidity",
|
||||
"sensor.multisensor_6_ultraviolet",
|
||||
"binary_sensor.multisensor_6_home_security_tampering_product_cover_removed",
|
||||
"binary_sensor.multisensor_6_home_security_motion_detection",
|
||||
"sensor.multisensor_6_battery_level",
|
||||
"binary_sensor.multisensor_6_low_battery_level",
|
||||
"light.smart_switch_6",
|
||||
"sensor.smart_switch_6_electric_consumed_kwh",
|
||||
"sensor.smart_switch_6_electric_consumed_w",
|
||||
"sensor.smart_switch_6_electric_consumed_v",
|
||||
"sensor.smart_switch_6_electric_consumed_a",
|
||||
]
|
||||
# Assert that both lists have the same items without checking order
|
||||
assert not set(result["zwave_js_entity_ids"]) ^ set(expected_zwave_js_entities)
|
||||
assert result["migration_entity_map"] == migration_entity_map
|
||||
assert result["migrated"] is True
|
||||
|
||||
dev_reg = dr.async_get(hass)
|
||||
ent_reg = er.async_get(hass)
|
||||
|
||||
# check the device registry migration
|
||||
|
||||
# check that the migrated entries have correct attributes
|
||||
multisensor_device_entry = dev_reg.async_get_device(
|
||||
identifiers={("zwave_js", "3245146787-52")}, connections=set()
|
||||
)
|
||||
assert multisensor_device_entry
|
||||
assert multisensor_device_entry.name_by_user == ZWAVE_MULTISENSOR_DEVICE_NAME
|
||||
assert multisensor_device_entry.area_id == ZWAVE_MULTISENSOR_DEVICE_AREA
|
||||
switch_device_entry = dev_reg.async_get_device(
|
||||
identifiers={("zwave_js", "3245146787-102")}, connections=set()
|
||||
)
|
||||
assert switch_device_entry
|
||||
assert switch_device_entry.name_by_user == ZWAVE_SWITCH_DEVICE_NAME
|
||||
assert switch_device_entry.area_id == ZWAVE_SWITCH_DEVICE_AREA
|
||||
|
||||
migration_device_map = {
|
||||
ZWAVE_SWITCH_DEVICE_ID: switch_device_entry.id,
|
||||
ZWAVE_MULTISENSOR_DEVICE_ID: multisensor_device_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.multisensor_6_battery_level")
|
||||
|
||||
# these should not have been migrated and is still in the registry
|
||||
assert ent_reg.async_is_registered(ZWAVE_SOURCE_NODE_ENTITY)
|
||||
source_entry = ent_reg.async_get(ZWAVE_SOURCE_NODE_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(ZWAVE_TAMPERING_ENTITY)
|
||||
tampering_entry = ent_reg.async_get(ZWAVE_TAMPERING_ENTITY)
|
||||
assert tampering_entry.unique_id == ZWAVE_TAMPERING_UNIQUE_ID
|
||||
assert ent_reg.async_is_registered("sensor.smart_switch_6_electric_consumed_w")
|
||||
|
||||
# this is the new entity_ids of the zwave_js entities
|
||||
assert ent_reg.async_is_registered(ZWAVE_SWITCH_ENTITY)
|
||||
assert ent_reg.async_is_registered(ZWAVE_BATTERY_ENTITY)
|
||||
|
||||
# check that the migrated entries have correct attributes
|
||||
switch_entry = ent_reg.async_get(ZWAVE_SWITCH_ENTITY)
|
||||
assert switch_entry
|
||||
assert switch_entry.unique_id == "3245146787.102-37-0-currentValue"
|
||||
assert switch_entry.name == ZWAVE_SWITCH_NAME
|
||||
assert switch_entry.icon == ZWAVE_SWITCH_ICON
|
||||
battery_entry = ent_reg.async_get(ZWAVE_BATTERY_ENTITY)
|
||||
assert battery_entry
|
||||
assert battery_entry.unique_id == "3245146787.52-128-0-level"
|
||||
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,
|
||||
zwave_integration,
|
||||
aeon_smart_switch_6,
|
||||
multisensor_6,
|
||||
integration,
|
||||
hass_ws_client,
|
||||
):
|
||||
"""Test the zwave to zwave_js migration websocket api dry run."""
|
||||
entry = integration
|
||||
client = await hass_ws_client(hass)
|
||||
|
||||
await client.send_json(
|
||||
{ID: 5, TYPE: "zwave_js/migrate_zwave", ENTRY_ID: entry.entry_id}
|
||||
)
|
||||
msg = await client.receive_json()
|
||||
result = msg["result"]
|
||||
|
||||
migration_entity_map = {
|
||||
ZWAVE_SWITCH_ENTITY: "switch.smart_switch_6",
|
||||
ZWAVE_BATTERY_ENTITY: "sensor.multisensor_6_battery_level",
|
||||
}
|
||||
|
||||
assert result["zwave_entity_ids"] == [
|
||||
ZWAVE_SWITCH_ENTITY,
|
||||
ZWAVE_POWER_ENTITY,
|
||||
ZWAVE_SOURCE_NODE_ENTITY,
|
||||
ZWAVE_BATTERY_ENTITY,
|
||||
ZWAVE_TAMPERING_ENTITY,
|
||||
]
|
||||
expected_zwave_js_entities = [
|
||||
"switch.smart_switch_6",
|
||||
"sensor.multisensor_6_air_temperature",
|
||||
"sensor.multisensor_6_illuminance",
|
||||
"sensor.multisensor_6_humidity",
|
||||
"sensor.multisensor_6_ultraviolet",
|
||||
"binary_sensor.multisensor_6_home_security_tampering_product_cover_removed",
|
||||
"binary_sensor.multisensor_6_home_security_motion_detection",
|
||||
"sensor.multisensor_6_battery_level",
|
||||
"binary_sensor.multisensor_6_low_battery_level",
|
||||
"light.smart_switch_6",
|
||||
"sensor.smart_switch_6_electric_consumed_kwh",
|
||||
"sensor.smart_switch_6_electric_consumed_w",
|
||||
"sensor.smart_switch_6_electric_consumed_v",
|
||||
"sensor.smart_switch_6_electric_consumed_a",
|
||||
]
|
||||
# Assert that both lists have the same items without checking order
|
||||
assert not set(result["zwave_js_entity_ids"]) ^ set(expected_zwave_js_entities)
|
||||
assert result["migration_entity_map"] == migration_entity_map
|
||||
|
||||
dev_reg = dr.async_get(hass)
|
||||
|
||||
multisensor_device_entry = dev_reg.async_get_device(
|
||||
identifiers={("zwave_js", "3245146787-52")}, connections=set()
|
||||
)
|
||||
assert multisensor_device_entry
|
||||
assert multisensor_device_entry.name_by_user is None
|
||||
assert multisensor_device_entry.area_id is None
|
||||
switch_device_entry = dev_reg.async_get_device(
|
||||
identifiers={("zwave_js", "3245146787-102")}, connections=set()
|
||||
)
|
||||
assert switch_device_entry
|
||||
assert switch_device_entry.name_by_user is None
|
||||
assert switch_device_entry.area_id is None
|
||||
|
||||
migration_device_map = {
|
||||
ZWAVE_SWITCH_DEVICE_ID: switch_device_entry.id,
|
||||
ZWAVE_MULTISENSOR_DEVICE_ID: multisensor_device_entry.id,
|
||||
}
|
||||
|
||||
assert result["migration_device_map"] == migration_device_map
|
||||
|
||||
assert result["migrated"] is False
|
||||
|
||||
ent_reg = er.async_get(hass)
|
||||
|
||||
# no real migration should have been done
|
||||
assert ent_reg.async_is_registered("switch.smart_switch_6")
|
||||
assert ent_reg.async_is_registered("sensor.multisensor_6_battery_level")
|
||||
assert ent_reg.async_is_registered("sensor.smart_switch_6_electric_consumed_w")
|
||||
|
||||
assert ent_reg.async_is_registered(ZWAVE_SOURCE_NODE_ENTITY)
|
||||
source_entry = ent_reg.async_get(ZWAVE_SOURCE_NODE_ENTITY)
|
||||
assert source_entry
|
||||
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
|
||||
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
|
||||
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, aeon_smart_switch_6, multisensor_6, integration, hass_ws_client
|
||||
):
|
||||
"""Test the zwave to zwave_js migration websocket without zwave setup."""
|
||||
entry = integration
|
||||
client = await hass_ws_client(hass)
|
||||
|
||||
await client.send_json(
|
||||
{ID: 5, TYPE: "zwave_js/migrate_zwave", ENTRY_ID: entry.entry_id}
|
||||
)
|
||||
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"
|
||||
|
||||
|
||||
async def test_unique_id_migration_dupes(
|
||||
hass, multisensor_6_state, client, integration
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue