text: """Support for Z-Wave."""
  import asyncio
  import copy
  from importlib import import_module
  import logging
  from pprint import pprint
  
  import voluptuous as vol
  
  from homeassistant import config_entries
  from homeassistant.const import (
      ATTR_ENTITY_ID,
      EVENT_HOMEASSISTANT_START,
      EVENT_HOMEASSISTANT_STOP,
  )
  from homeassistant.core import CoreState, callback
  from homeassistant.helpers import discovery
  import homeassistant.helpers.config_validation as cv
  from homeassistant.helpers.device_registry import (
      async_get_registry as async_get_device_registry,
  )
  from homeassistant.helpers.dispatcher import (
      async_dispatcher_connect,
      async_dispatcher_send,
  )
  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_get_registry as async_get_entity_registry,
  )
  from homeassistant.helpers.entity_values import EntityValues
  from homeassistant.helpers.event import async_track_time_change
  from homeassistant.util import convert
  import homeassistant.util.dt as dt_util
  
  from . import config_flow  # noqa: F401 pylint: disable=unused-import
  from . import const, websocket_api as wsapi, workaround
  from .const import (
      CONF_AUTOHEAL,
      CONF_CONFIG_PATH,
      CONF_DEBUG,
      CONF_NETWORK_KEY,
      CONF_POLLING_INTERVAL,
      CONF_USB_STICK_PATH,
      DATA_DEVICES,
      DATA_ENTITY_VALUES,
      DATA_NETWORK,
      DATA_ZWAVE_CONFIG,
      DEFAULT_CONF_AUTOHEAL,
      DEFAULT_CONF_USB_STICK_PATH,
      DEFAULT_DEBUG,
      DEFAULT_POLLING_INTERVAL,
      DOMAIN,
  )
  from .discovery_schemas import DISCOVERY_SCHEMAS
  from .node_entity import ZWaveBaseEntity, ZWaveNodeEntity
  from .util import (
      check_has_unique_id,
      check_node_schema,
      check_value_schema,
      is_node_parsed,
      node_device_id_and_name,
      node_name,
  )
  
  _LOGGER = logging.getLogger(__name__)
  
  CLASS_ID = "class_id"
  
  ATTR_POWER = "power_consumption"
  
  CONF_POLLING_INTENSITY = "polling_intensity"
  CONF_IGNORED = "ignored"
  CONF_INVERT_OPENCLOSE_BUTTONS = "invert_openclose_buttons"
  CONF_INVERT_PERCENT = "invert_percent"
  CONF_REFRESH_VALUE = "refresh_value"
  CONF_REFRESH_DELAY = "delay"
  CONF_DEVICE_CONFIG = "device_config"
  CONF_DEVICE_CONFIG_GLOB = "device_config_glob"
  CONF_DEVICE_CONFIG_DOMAIN = "device_config_domain"
  
  DEFAULT_CONF_IGNORED = False
  DEFAULT_CONF_INVERT_OPENCLOSE_BUTTONS = False
  DEFAULT_CONF_INVERT_PERCENT = False
  DEFAULT_CONF_REFRESH_VALUE = False
  DEFAULT_CONF_REFRESH_DELAY = 5
  
  SUPPORTED_PLATFORMS = [
      "binary_sensor",
      "climate",
      "cover",
      "fan",
      "lock",
      "light",
      "sensor",
      "switch",
  ]
  
  RENAME_NODE_SCHEMA = vol.Schema(
      {
          vol.Required(const.ATTR_NODE_ID): vol.Coerce(int),
          vol.Required(const.ATTR_NAME): cv.string,
          vol.Optional(const.ATTR_UPDATE_IDS, default=False): cv.boolean,
      }
  )
  
  RENAME_VALUE_SCHEMA = vol.Schema(
      {
          vol.Required(const.ATTR_NODE_ID): vol.Coerce(int),
          vol.Required(const.ATTR_VALUE_ID): vol.Coerce(int),
          vol.Required(const.ATTR_NAME): cv.string,
          vol.Optional(const.ATTR_UPDATE_IDS, default=False): cv.boolean,
      }
  )
  
  SET_CONFIG_PARAMETER_SCHEMA = vol.Schema(
      {
          vol.Required(const.ATTR_NODE_ID): vol.Coerce(int),
          vol.Required(const.ATTR_CONFIG_PARAMETER): vol.Coerce(int),
          vol.Required(const.ATTR_CONFIG_VALUE): vol.Any(vol.Coerce(int), cv.string),
          vol.Optional(const.ATTR_CONFIG_SIZE, default=2): vol.Coerce(int),
      }
  )
  
  SET_NODE_VALUE_SCHEMA = vol.Schema(
      {
          vol.Required(const.ATTR_NODE_ID): vol.Coerce(int),
          vol.Required(const.ATTR_VALUE_ID): vol.Any(vol.Coerce(int), cv.string),
          vol.Required(const.ATTR_CONFIG_VALUE): vol.Any(vol.Coerce(int), cv.string),
      }
  )
  
  REFRESH_NODE_VALUE_SCHEMA = vol.Schema(
      {
          vol.Required(const.ATTR_NODE_ID): vol.Coerce(int),
          vol.Required(const.ATTR_VALUE_ID): vol.Coerce(int),
      }
  )
  
  SET_POLL_INTENSITY_SCHEMA = vol.Schema(
      {
          vol.Required(const.ATTR_NODE_ID): vol.Coerce(int),
          vol.Required(const.ATTR_VALUE_ID): vol.Coerce(int),
          vol.Required(const.ATTR_POLL_INTENSITY): vol.Coerce(int),
      }
  )
  
  PRINT_CONFIG_PARAMETER_SCHEMA = vol.Schema(
      {
          vol.Required(const.ATTR_NODE_ID): vol.Coerce(int),
          vol.Required(const.ATTR_CONFIG_PARAMETER): vol.Coerce(int),
      }
  )
  
  NODE_SERVICE_SCHEMA = vol.Schema({vol.Required(const.ATTR_NODE_ID): vol.Coerce(int)})
  
  REFRESH_ENTITY_SCHEMA = vol.Schema({vol.Required(ATTR_ENTITY_ID): cv.entity_id})
  
  RESET_NODE_METERS_SCHEMA = vol.Schema(
      {
          vol.Required(const.ATTR_NODE_ID): vol.Coerce(int),
          vol.Optional(const.ATTR_INSTANCE, default=1): vol.Coerce(int),
      }
  )
  
  CHANGE_ASSOCIATION_SCHEMA = vol.Schema(
      {
          vol.Required(const.ATTR_ASSOCIATION): cv.string,
          vol.Required(const.ATTR_NODE_ID): vol.Coerce(int),
          vol.Required(const.ATTR_TARGET_NODE_ID): vol.Coerce(int),
          vol.Required(const.ATTR_GROUP): vol.Coerce(int),
          vol.Optional(const.ATTR_INSTANCE, default=0x00): vol.Coerce(int),
      }
  )
  
  SET_WAKEUP_SCHEMA = vol.Schema(
      {
          vol.Required(const.ATTR_NODE_ID): vol.Coerce(int),
          vol.Required(const.ATTR_CONFIG_VALUE): vol.All(
              vol.Coerce(int), cv.positive_int
          ),
      }
  )
  
  HEAL_NODE_SCHEMA = vol.Schema(
      {
          vol.Required(const.ATTR_NODE_ID): vol.Coerce(int),
          vol.Optional(const.ATTR_RETURN_ROUTES, default=False): cv.boolean,
      }
  )
  
  TEST_NODE_SCHEMA = vol.Schema(
      {
          vol.Required(const.ATTR_NODE_ID): vol.Coerce(int),
          vol.Optional(const.ATTR_MESSAGES, default=1): cv.positive_int,
      }
  )
  
  
  DEVICE_CONFIG_SCHEMA_ENTRY = vol.Schema(
      {
          vol.Optional(CONF_POLLING_INTENSITY): cv.positive_int,
          vol.Optional(CONF_IGNORED, default=DEFAULT_CONF_IGNORED): cv.boolean,
          vol.Optional(
              CONF_INVERT_OPENCLOSE_BUTTONS, default=DEFAULT_CONF_INVERT_OPENCLOSE_BUTTONS
          ): cv.boolean,
          vol.Optional(
              CONF_INVERT_PERCENT, default=DEFAULT_CONF_INVERT_PERCENT
          ): cv.boolean,
          vol.Optional(
              CONF_REFRESH_VALUE, default=DEFAULT_CONF_REFRESH_VALUE
          ): cv.boolean,
          vol.Optional(
              CONF_REFRESH_DELAY, default=DEFAULT_CONF_REFRESH_DELAY
          ): cv.positive_int,
      }
  )
  
  SIGNAL_REFRESH_ENTITY_FORMAT = "zwave_refresh_entity_{}"
  
  CONFIG_SCHEMA = vol.Schema(
      {
          DOMAIN: vol.Schema(
              {
                  vol.Optional(CONF_AUTOHEAL, default=DEFAULT_CONF_AUTOHEAL): cv.boolean,
                  vol.Optional(CONF_CONFIG_PATH): cv.string,
                  vol.Optional(CONF_NETWORK_KEY): vol.All(
                      cv.string, vol.Match(r"(0x\w\w,\s?){15}0x\w\w")
                  ),
                  vol.Optional(CONF_DEVICE_CONFIG, default={}): vol.Schema(
                      {cv.entity_id: DEVICE_CONFIG_SCHEMA_ENTRY}
                  ),
                  vol.Optional(CONF_DEVICE_CONFIG_GLOB, default={}): vol.Schema(
                      {cv.string: DEVICE_CONFIG_SCHEMA_ENTRY}
                  ),
                  vol.Optional(CONF_DEVICE_CONFIG_DOMAIN, default={}): vol.Schema(
                      {cv.string: DEVICE_CONFIG_SCHEMA_ENTRY}
                  ),
                  vol.Optional(CONF_DEBUG, default=DEFAULT_DEBUG): cv.boolean,
                  vol.Optional(
                      CONF_POLLING_INTERVAL, default=DEFAULT_POLLING_INTERVAL
                  ): cv.positive_int,
                  vol.Optional(CONF_USB_STICK_PATH): cv.string,
              }
          )
      },
      extra=vol.ALLOW_EXTRA,
  )
  
  
  def _obj_to_dict(obj):
      """Convert an object into a hash for debug."""
      return {
          key: getattr(obj, key)
          for key in dir(obj)
          if key[0] != "_" and not callable(getattr(obj, key))
      }
  
  
  def _value_name(value):
      """Return the name of the value."""
      return "{} {}".format(node_name(value.node), value.label).strip()
  
  
  def nice_print_node(node):
      """Print a nice formatted node to the output (debug method)."""
      node_dict = _obj_to_dict(node)
      node_dict["values"] = {
          value_id: _obj_to_dict(value) for value_id, value in node.values.items()
      }
  
      _LOGGER.info("FOUND NODE %s \n%s", node.product_name, node_dict)
  
  
  def get_config_value(node, value_index, tries=5):
      """Return the current configuration value for a specific index."""
      try:
          for value in node.values.values():
              if (
                  value.command_class == const.COMMAND_CLASS_CONFIGURATION
                  and value.index == value_index
              ):
                  return value.data
      except RuntimeError:
          # If we get a runtime error the dict has changed while
          # we was looking for a value, just do it again
          return (
              None if tries <= 0 else get_config_value(node, value_index, tries=tries - 1)
          )
      return None
  
  
  async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
      """Set up the Z-Wave platform (generic part)."""
      if discovery_info is None or DATA_NETWORK not in hass.data:
          return False
  
      device = hass.data[DATA_DEVICES].get(discovery_info[const.DISCOVERY_DEVICE], None)
      if device is None:
          return False
  
      async_add_entities([device])
      return True
  
  
  async def async_setup(hass, config):
      """Set up Z-Wave components."""
      if DOMAIN not in config:
          return True
  
      conf = config[DOMAIN]
      hass.data[DATA_ZWAVE_CONFIG] = conf
  
      if not hass.config_entries.async_entries(DOMAIN):
          hass.async_create_task(
              hass.config_entries.flow.async_init(
                  DOMAIN,
                  context={"source": config_entries.SOURCE_IMPORT},
                  data={
                      CONF_USB_STICK_PATH: conf.get(
                          CONF_USB_STICK_PATH, DEFAULT_CONF_USB_STICK_PATH
                      ),
                      CONF_NETWORK_KEY: conf.get(CONF_NETWORK_KEY),
                  },
              )
          )
  
      return True
  
  
  async def async_setup_entry(hass, config_entry):
      """Set up Z-Wave from a config entry.
  
      Will automatically load components to support devices found on the network.
      """
      from pydispatch import dispatcher
  
      # pylint: disable=import-error
      from openzwave.option import ZWaveOption
      from openzwave.network import ZWaveNetwork
      from openzwave.group import ZWaveGroup
  
      # Merge config entry and yaml config
      config = config_entry.data
      if DATA_ZWAVE_CONFIG in hass.data:
          config = {**config, **hass.data[DATA_ZWAVE_CONFIG]}
  
      # Update hass.data with merged config so we can access it elsewhere
      hass.data[DATA_ZWAVE_CONFIG] = config
  
      # Load configuration
      use_debug = config.get(CONF_DEBUG, DEFAULT_DEBUG)
      autoheal = config.get(CONF_AUTOHEAL, DEFAULT_CONF_AUTOHEAL)
      device_config = EntityValues(
          config.get(CONF_DEVICE_CONFIG),
          config.get(CONF_DEVICE_CONFIG_DOMAIN),
          config.get(CONF_DEVICE_CONFIG_GLOB),
      )
  
      usb_path = config[CONF_USB_STICK_PATH]
  
      _LOGGER.info("Z-Wave USB path is %s", usb_path)
  
      # Setup options
      options = ZWaveOption(
          usb_path,
          user_path=hass.config.config_dir,
          config_path=config.get(CONF_CONFIG_PATH),
      )
  
      options.set_console_output(use_debug)
  
      if config.get(CONF_NETWORK_KEY):
          options.addOption("NetworkKey", config[CONF_NETWORK_KEY])
  
      await hass.async_add_executor_job(options.lock)
      network = hass.data[DATA_NETWORK] = ZWaveNetwork(options, autostart=False)
      hass.data[DATA_DEVICES] = {}
      hass.data[DATA_ENTITY_VALUES] = []
  
      registry = await async_get_entity_registry(hass)
  
      wsapi.async_load_websocket_api(hass)
  
      if use_debug:  # pragma: no cover
  
          def log_all(signal, value=None):
              """Log all the signals."""
              print("")
              print("SIGNAL *****", signal)
              if value and signal in (
                  ZWaveNetwork.SIGNAL_VALUE_CHANGED,
                  ZWaveNetwork.SIGNAL_VALUE_ADDED,
                  ZWaveNetwork.SIGNAL_SCENE_EVENT,
                  ZWaveNetwork.SIGNAL_NODE_EVENT,
                  ZWaveNetwork.SIGNAL_AWAKE_NODES_QUERIED,
                  ZWaveNetwork.SIGNAL_ALL_NODES_QUERIED,
                  ZWaveNetwork.SIGNAL_ALL_NODES_QUERIED_SOME_DEAD,
              ):
                  pprint(_obj_to_dict(value))
  
              print("")
  
          dispatcher.connect(log_all, weak=False)
  
      def value_added(node, value):
          """Handle new added value to a node on the network."""
          # Check if this value should be tracked by an existing entity
          for values in hass.data[DATA_ENTITY_VALUES]:
              values.check_value(value)
  
          for schema in DISCOVERY_SCHEMAS:
              if not check_node_schema(node, schema):
                  continue
              if not check_value_schema(
                  value, schema[const.DISC_VALUES][const.DISC_PRIMARY]
              ):
                  continue
  
              values = ZWaveDeviceEntityValues(
                  hass, schema, value, config, device_config, registry
              )
  
              # We create a new list and update the reference here so that
              # the list can be safely iterated over in the main thread
              new_values = hass.data[DATA_ENTITY_VALUES] + [values]
              hass.data[DATA_ENTITY_VALUES] = new_values
  
      platform = EntityPlatform(
          hass=hass,
          logger=_LOGGER,
          domain=DOMAIN,
          platform_name=DOMAIN,
          platform=None,
          scan_interval=DEFAULT_SCAN_INTERVAL,
          entity_namespace=None,
      )
      platform.config_entry = config_entry
  
      def node_added(node):
          """Handle a new node on the network."""
          entity = ZWaveNodeEntity(node, network)
  
          async def _add_node_to_component():
              if hass.data[DATA_DEVICES].get(entity.unique_id):
                  return
  
              name = node_name(node)
              generated_id = generate_entity_id(DOMAIN + ".{}", name, [])
              node_config = device_config.get(generated_id)
              if node_config.get(CONF_IGNORED):
                  _LOGGER.info(
                      "Ignoring node entity %s due to device settings", generated_id
                  )
                  return
  
              hass.data[DATA_DEVICES][entity.unique_id] = entity
              await platform.async_add_entities([entity])
  
          if entity.unique_id:
              hass.async_add_job(_add_node_to_component())
              return
  
          @callback
          def _on_ready(sec):
              _LOGGER.info("Z-Wave node %d ready after %d seconds", entity.node_id, sec)
              hass.async_add_job(_add_node_to_component)
  
          @callback
          def _on_timeout(sec):
              _LOGGER.warning(
                  "Z-Wave node %d not ready after %d seconds, continuing anyway",
                  entity.node_id,
                  sec,
              )
              hass.async_add_job(_add_node_to_component)
  
          hass.add_job(check_has_unique_id, entity, _on_ready, _on_timeout)
  
      def node_removed(node):
          node_id = node.node_id
          node_key = f"node-{node_id}"
          for key in list(hass.data[DATA_DEVICES]):
              if key is None:
                  continue
              if not key.startswith(f"{node_id}-"):
                  continue
  
              entity = hass.data[DATA_DEVICES][key]
              _LOGGER.debug(
                  "Removing Entity - value: %s - entity_id: %s", key, entity.entity_id
              )
              hass.add_job(entity.node_removed())
              del hass.data[DATA_DEVICES][key]
  
          entity = hass.data[DATA_DEVICES][node_key]
          hass.add_job(entity.node_removed())
          del hass.data[DATA_DEVICES][node_key]
  
          hass.add_job(_remove_device(node))
  
      async def _remove_device(node):
          dev_reg = await async_get_device_registry(hass)
          identifier, name = node_device_id_and_name(node)
          device = dev_reg.async_get_device(identifiers={identifier}, connections=set())
          if device is not None:
              _LOGGER.debug("Removing Device - %s - %s", device.id, name)
              dev_reg.async_remove_device(device.id)
  
      def network_ready():
          """Handle the query of all awake nodes."""
          _LOGGER.info(
              "Z-Wave network is ready for use. All awake nodes "
              "have been queried. Sleeping nodes will be "
              "queried when they awake."
          )
          hass.bus.fire(const.EVENT_NETWORK_READY)
  
      def network_complete():
          """Handle the querying of all nodes on network."""
          _LOGGER.info(
              "Z-Wave network is complete. All nodes on the network have been queried"
          )
          hass.bus.fire(const.EVENT_NETWORK_COMPLETE)
  
      def network_complete_some_dead():
          """Handle the querying of all nodes on network."""
          _LOGGER.info(
              "Z-Wave network is complete. All nodes on the network "
              "have been queried, but some nodes are marked dead"
          )
          hass.bus.fire(const.EVENT_NETWORK_COMPLETE_SOME_DEAD)
  
      dispatcher.connect(value_added, ZWaveNetwork.SIGNAL_VALUE_ADDED, weak=False)
      dispatcher.connect(node_added, ZWaveNetwork.SIGNAL_NODE_ADDED, weak=False)
      dispatcher.connect(node_removed, ZWaveNetwork.SIGNAL_NODE_REMOVED, weak=False)
      dispatcher.connect(
          network_ready, ZWaveNetwork.SIGNAL_AWAKE_NODES_QUERIED, weak=False
      )
      dispatcher.connect(
          network_complete, ZWaveNetwork.SIGNAL_ALL_NODES_QUERIED, weak=False
      )
      dispatcher.connect(
          network_complete_some_dead,
          ZWaveNetwork.SIGNAL_ALL_NODES_QUERIED_SOME_DEAD,
          weak=False,
      )
  
      def add_node(service):
          """Switch into inclusion mode."""
          _LOGGER.info("Z-Wave add_node have been initialized")
          network.controller.add_node()
  
      def add_node_secure(service):
          """Switch into secure inclusion mode."""
          _LOGGER.info("Z-Wave add_node_secure have been initialized")
          network.controller.add_node(True)
  
      def remove_node(service):
          """Switch into exclusion mode."""
          _LOGGER.info("Z-Wave remove_node have been initialized")
          network.controller.remove_node()
  
      def cancel_command(service):
          """Cancel a running controller command."""
          _LOGGER.info("Cancel running Z-Wave command")
          network.controller.cancel_command()
  
      def heal_network(service):
          """Heal the network."""
          _LOGGER.info("Z-Wave heal running")
          network.heal()
  
      def soft_reset(service):
          """Soft reset the controller."""
          _LOGGER.info("Z-Wave soft_reset have been initialized")
          network.controller.soft_reset()
  
      def test_network(service):
          """Test the network by sending commands to all the nodes."""
          _LOGGER.info("Z-Wave test_network have been initialized")
          network.test()
  
      def stop_network(_service_or_event):
          """Stop Z-Wave network."""
          _LOGGER.info("Stopping Z-Wave network")
          network.stop()
          if hass.state == CoreState.running:
              hass.bus.fire(const.EVENT_NETWORK_STOP)
  
      async def rename_node(service):
          """Rename a node."""
          node_id = service.data.get(const.ATTR_NODE_ID)
          node = network.nodes[node_id]
          name = service.data.get(const.ATTR_NAME)
          node.name = name
          _LOGGER.info("Renamed Z-Wave node %d to %s", node_id, name)
          update_ids = service.data.get(const.ATTR_UPDATE_IDS)
          # We want to rename the device, the node entity,
          # and all the contained entities
          node_key = f"node-{node_id}"
          entity = hass.data[DATA_DEVICES][node_key]
          await entity.node_renamed(update_ids)
          for key in list(hass.data[DATA_DEVICES]):
              if not key.startswith(f"{node_id}-"):
                  continue
              entity = hass.data[DATA_DEVICES][key]
              await entity.value_renamed(update_ids)
  
      async def rename_value(service):
          """Rename a node value."""
          node_id = service.data.get(const.ATTR_NODE_ID)
          value_id = service.data.get(const.ATTR_VALUE_ID)
          node = network.nodes[node_id]
          value = node.values[value_id]
          name = service.data.get(const.ATTR_NAME)
          value.label = name
          _LOGGER.info(
              "Renamed Z-Wave value (Node %d Value %d) to %s", node_id, value_id, name
          )
          update_ids = service.data.get(const.ATTR_UPDATE_IDS)
          value_key = f"{node_id}-{value_id}"
          entity = hass.data[DATA_DEVICES][value_key]
          await entity.value_renamed(update_ids)
  
      def set_poll_intensity(service):
          """Set the polling intensity of a node value."""
          node_id = service.data.get(const.ATTR_NODE_ID)
          value_id = service.data.get(const.ATTR_VALUE_ID)
          node = network.nodes[node_id]
          value = node.values[value_id]
          intensity = service.data.get(const.ATTR_POLL_INTENSITY)
          if intensity == 0:
              if value.disable_poll():
                  _LOGGER.info("Polling disabled (Node %d Value %d)", node_id, value_id)
                  return
              _LOGGER.info(
                  "Polling disabled failed (Node %d Value %d)", node_id, value_id
              )
          else:
              if value.enable_poll(intensity):
                  _LOGGER.info(
                      "Set polling intensity (Node %d Value %d) to %s",
                      node_id,
                      value_id,
                      intensity,
                  )
                  return
              _LOGGER.info(
                  "Set polling intensity failed (Node %d Value %d)", node_id, value_id
              )
  
      def remove_failed_node(service):
          """Remove failed node."""
          node_id = service.data.get(const.ATTR_NODE_ID)
          _LOGGER.info("Trying to remove zwave node %d", node_id)
          network.controller.remove_failed_node(node_id)
  
      def replace_failed_node(service):
          """Replace failed node."""
          node_id = service.data.get(const.ATTR_NODE_ID)
          _LOGGER.info("Trying to replace zwave node %d", node_id)
          network.controller.replace_failed_node(node_id)
  
      def set_config_parameter(service):
          """Set a config parameter to a node."""
          node_id = service.data.get(const.ATTR_NODE_ID)
          node = network.nodes[node_id]
          param = service.data.get(const.ATTR_CONFIG_PARAMETER)
          selection = service.data.get(const.ATTR_CONFIG_VALUE)
          size = service.data.get(const.ATTR_CONFIG_SIZE)
          for value in node.get_values(
              class_id=const.COMMAND_CLASS_CONFIGURATION
          ).values():
              if value.index != param:
                  continue
              if value.type == const.TYPE_BOOL:
                  value.data = int(selection == "True")
                  _LOGGER.info(
                      "Setting configuration parameter %s on Node %s with bool selection %s",
                      param,
                      node_id,
                      str(selection),
                  )
                  return
              if value.type == const.TYPE_LIST:
                  value.data = str(selection)
                  _LOGGER.info(
                      "Setting configuration parameter %s on Node %s with list selection %s",
                      param,
                      node_id,
                      str(selection),
                  )
                  return
              if value.type == const.TYPE_BUTTON:
                  network.manager.pressButton(value.value_id)
                  network.manager.releaseButton(value.value_id)
                  _LOGGER.info(
                      "Setting configuration parameter %s on Node %s "
                      "with button selection %s",
                      param,
                      node_id,
                      selection,
                  )
                  return
              value.data = int(selection)
              _LOGGER.info(
                  "Setting configuration parameter %s on Node %s with selection %s",
                  param,
                  node_id,
                  selection,
              )
              return
          node.set_config_param(param, selection, size)
          _LOGGER.info(
              "Setting unknown configuration parameter %s on Node %s with selection %s",
              param,
              node_id,
              selection,
          )
  
      def refresh_node_value(service):
          """Refresh the specified value from a node."""
          node_id = service.data.get(const.ATTR_NODE_ID)
          value_id = service.data.get(const.ATTR_VALUE_ID)
          node = network.nodes[node_id]
          node.values[value_id].refresh()
          _LOGGER.info("Node %s value %s refreshed", node_id, value_id)
  
      def set_node_value(service):
          """Set the specified value on a node."""
          node_id = service.data.get(const.ATTR_NODE_ID)
          value_id = service.data.get(const.ATTR_VALUE_ID)
          value = service.data.get(const.ATTR_CONFIG_VALUE)
          node = network.nodes[node_id]
          node.values[value_id].data = value
          _LOGGER.info("Node %s value %s set to %s", node_id, value_id, value)
  
      def print_config_parameter(service):
          """Print a config parameter from a node."""
          node_id = service.data.get(const.ATTR_NODE_ID)
          node = network.nodes[node_id]
          param = service.data.get(const.ATTR_CONFIG_PARAMETER)
          _LOGGER.info(
              "Config parameter %s on Node %s: %s",
              param,
              node_id,
              get_config_value(node, param),
          )
  
      def print_node(service):
          """Print all information about z-wave node."""
          node_id = service.data.get(const.ATTR_NODE_ID)
          node = network.nodes[node_id]
          nice_print_node(node)
  
      def set_wakeup(service):
          """Set wake-up interval of a node."""
          node_id = service.data.get(const.ATTR_NODE_ID)
          node = network.nodes[node_id]
          value = service.data.get(const.ATTR_CONFIG_VALUE)
          if node.can_wake_up():
              for value_id in node.get_values(class_id=const.COMMAND_CLASS_WAKE_UP):
                  node.values[value_id].data = value
                  _LOGGER.info("Node %s wake-up set to %d", node_id, value)
          else:
              _LOGGER.info("Node %s is not wakeable", node_id)
  
      def change_association(service):
          """Change an association in the zwave network."""
          association_type = service.data.get(const.ATTR_ASSOCIATION)
          node_id = service.data.get(const.ATTR_NODE_ID)
          target_node_id = service.data.get(const.ATTR_TARGET_NODE_ID)
          group = service.data.get(const.ATTR_GROUP)
          instance = service.data.get(const.ATTR_INSTANCE)
  
          node = ZWaveGroup(group, network, node_id)
          if association_type == "add":
              node.add_association(target_node_id, instance)
              _LOGGER.info(
                  "Adding association for node:%s in group:%s "
                  "target node:%s, instance=%s",
                  node_id,
                  group,
                  target_node_id,
                  instance,
              )
          if association_type == "remove":
              node.remove_association(target_node_id, instance)
              _LOGGER.info(
                  "Removing association for node:%s in group:%s "
                  "target node:%s, instance=%s",
                  node_id,
                  group,
                  target_node_id,
                  instance,
              )
  
      async def async_refresh_entity(service):
          """Refresh values that specific entity depends on."""
          entity_id = service.data.get(ATTR_ENTITY_ID)
          async_dispatcher_send(hass, SIGNAL_REFRESH_ENTITY_FORMAT.format(entity_id))
  
      def refresh_node(service):
          """Refresh all node info."""
          node_id = service.data.get(const.ATTR_NODE_ID)
          node = network.nodes[node_id]
          node.refresh_info()
  
      def reset_node_meters(service):
          """Reset meter counters of a node."""
          node_id = service.data.get(const.ATTR_NODE_ID)
          instance = service.data.get(const.ATTR_INSTANCE)
          node = network.nodes[node_id]
          for value in node.get_values(class_id=const.COMMAND_CLASS_METER).values():
              if value.index != const.INDEX_METER_RESET:
                  continue
              if value.instance != instance:
                  continue
              network.manager.pressButton(value.value_id)
              network.manager.releaseButton(value.value_id)
              _LOGGER.info(
                  "Resetting meters on node %s instance %s....", node_id, instance
              )
              return
          _LOGGER.info(
              "Node %s on instance %s does not have resettable meters.",
              node_id,
              instance,
          )
  
      def heal_node(service):
          """Heal a node on the network."""
          node_id = service.data.get(const.ATTR_NODE_ID)
          update_return_routes = service.data.get(const.ATTR_RETURN_ROUTES)
          node = network.nodes[node_id]
          _LOGGER.info("Z-Wave node heal running for node %s", node_id)
          node.heal(update_return_routes)
  
      def test_node(service):
          """Send test messages to a node on the network."""
          node_id = service.data.get(const.ATTR_NODE_ID)
          messages = service.data.get(const.ATTR_MESSAGES)
          node = network.nodes[node_id]
          _LOGGER.info("Sending %s test-messages to node %s.", messages, node_id)
          node.test(messages)
  
      def start_zwave(_service_or_event):
          """Startup Z-Wave network."""
          _LOGGER.info("Starting Z-Wave network...")
          network.start()
          hass.bus.fire(const.EVENT_NETWORK_START)
  
          async def _check_awaked():
              """Wait for Z-wave awaked state (or timeout) and finalize start."""
              _LOGGER.debug("network state: %d %s", network.state, network.state_str)
  
              start_time = dt_util.utcnow()
              while True:
                  waited = int((dt_util.utcnow() - start_time).total_seconds())
  
                  if network.state >= network.STATE_AWAKED:
                      # Need to be in STATE_AWAKED before talking to nodes.
                      _LOGGER.info("Z-Wave ready after %d seconds", waited)
                      break
  
                  if waited >= const.NETWORK_READY_WAIT_SECS:
                      # Wait up to NETWORK_READY_WAIT_SECS seconds for the Z-Wave
                      # network to be ready.
                      _LOGGER.warning(
                          "Z-Wave not ready after %d seconds, continuing anyway", waited
                      )
                      _LOGGER.info(
                          "final network state: %d %s", network.state, network.state_str
                      )
                      break
  
                  await asyncio.sleep(1)
  
              hass.async_add_job(_finalize_start)
  
          hass.add_job(_check_awaked)
  
      def _finalize_start():
          """Perform final initializations after Z-Wave network is awaked."""
          polling_interval = convert(config.get(CONF_POLLING_INTERVAL), int)
          if polling_interval is not None:
              network.set_poll_interval(polling_interval, False)
  
          poll_interval = network.get_poll_interval()
          _LOGGER.info("Z-Wave polling interval set to %d ms", poll_interval)
  
          hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_network)
  
          # Register node services for Z-Wave network
          hass.services.register(DOMAIN, const.SERVICE_ADD_NODE, add_node)
          hass.services.register(DOMAIN, const.SERVICE_ADD_NODE_SECURE, add_node_secure)
          hass.services.register(DOMAIN, const.SERVICE_REMOVE_NODE, remove_node)
          hass.services.register(DOMAIN, const.SERVICE_CANCEL_COMMAND, cancel_command)
          hass.services.register(DOMAIN, const.SERVICE_HEAL_NETWORK, heal_network)
          hass.services.register(DOMAIN, const.SERVICE_SOFT_RESET, soft_reset)
          hass.services.register(DOMAIN, const.SERVICE_TEST_NETWORK, test_network)
          hass.services.register(DOMAIN, const.SERVICE_STOP_NETWORK, stop_network)
          hass.services.register(
              DOMAIN, const.SERVICE_RENAME_NODE, rename_node, schema=RENAME_NODE_SCHEMA
          )
          hass.services.register(
              DOMAIN, const.SERVICE_RENAME_VALUE, rename_value, schema=RENAME_VALUE_SCHEMA
          )
          hass.services.register(
              DOMAIN,
              const.SERVICE_SET_CONFIG_PARAMETER,
              set_config_parameter,
              schema=SET_CONFIG_PARAMETER_SCHEMA,
          )
          hass.services.register(
              DOMAIN,
              const.SERVICE_SET_NODE_VALUE,
              set_node_value,
              schema=SET_NODE_VALUE_SCHEMA,
          )
          hass.services.register(
              DOMAIN,
              const.SERVICE_REFRESH_NODE_VALUE,
              refresh_node_value,
              schema=REFRESH_NODE_VALUE_SCHEMA,
          )
          hass.services.register(
              DOMAIN,
              const.SERVICE_PRINT_CONFIG_PARAMETER,
              print_config_parameter,
              schema=PRINT_CONFIG_PARAMETER_SCHEMA,
          )
          hass.services.register(
              DOMAIN,
              const.SERVICE_REMOVE_FAILED_NODE,
              remove_failed_node,
              schema=NODE_SERVICE_SCHEMA,
          )
          hass.services.register(
              DOMAIN,
              const.SERVICE_REPLACE_FAILED_NODE,
              replace_failed_node,
              schema=NODE_SERVICE_SCHEMA,
          )
  
          hass.services.register(
              DOMAIN,
              const.SERVICE_CHANGE_ASSOCIATION,
              change_association,
              schema=CHANGE_ASSOCIATION_SCHEMA,
          )
          hass.services.register(
              DOMAIN, const.SERVICE_SET_WAKEUP, set_wakeup, schema=SET_WAKEUP_SCHEMA
          )
          hass.services.register(
              DOMAIN, const.SERVICE_PRINT_NODE, print_node, schema=NODE_SERVICE_SCHEMA
          )
          hass.services.register(
              DOMAIN,
              const.SERVICE_REFRESH_ENTITY,
              async_refresh_entity,
              schema=REFRESH_ENTITY_SCHEMA,
          )
          hass.services.register(
              DOMAIN, const.SERVICE_REFRESH_NODE, refresh_node, schema=NODE_SERVICE_SCHEMA
          )
          hass.services.register(
              DOMAIN,
              const.SERVICE_RESET_NODE_METERS,
              reset_node_meters,
              schema=RESET_NODE_METERS_SCHEMA,
          )
          hass.services.register(
              DOMAIN,
              const.SERVICE_SET_POLL_INTENSITY,
              set_poll_intensity,
              schema=SET_POLL_INTENSITY_SCHEMA,
          )
          hass.services.register(
              DOMAIN, const.SERVICE_HEAL_NODE, heal_node, schema=HEAL_NODE_SCHEMA
          )
          hass.services.register(
              DOMAIN, const.SERVICE_TEST_NODE, test_node, schema=TEST_NODE_SCHEMA
          )
  
      # Setup autoheal
      if autoheal:
          _LOGGER.info("Z-Wave network autoheal is enabled")
          async_track_time_change(hass, heal_network, hour=0, minute=0, second=0)
  
      hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, start_zwave)
  
      hass.services.async_register(DOMAIN, const.SERVICE_START_NETWORK, start_zwave)
  
      for entry_component in SUPPORTED_PLATFORMS:
          hass.async_create_task(
              hass.config_entries.async_forward_entry_setup(config_entry, entry_component)
          )
  
      return True
  
  
  class ZWaveDeviceEntityValues:
      """Manages entity access to the underlying zwave value objects."""
  
      def __init__(
          self, hass, schema, primary_value, zwave_config, device_config, registry
      ):
          """Initialize the values object with the passed entity schema."""
          self._hass = hass
          self._zwave_config = zwave_config
          self._device_config = device_config
          self._schema = copy.deepcopy(schema)
          self._values = {}
          self._entity = None
          self._workaround_ignore = False
          self._registry = registry
  
          for name in self._schema[const.DISC_VALUES].keys():
              self._values[name] = None
              self._schema[const.DISC_VALUES][name][const.DISC_INSTANCE] = [
                  primary_value.instance
              ]
  
          self._values[const.DISC_PRIMARY] = primary_value
          self._node = primary_value.node
          self._schema[const.DISC_NODE_ID] = [self._node.node_id]
  
          # Check values that have already been discovered for node
          for value in self._node.values.values():
              self.check_value(value)
  
          self._check_entity_ready()
  
      def __getattr__(self, name):
          """Get the specified value for this entity."""
          return self._values[name]
  
      def __iter__(self):
          """Allow iteration over all values."""
          return iter(self._values.values())
  
      def check_value(self, value):
          """Check if the new value matches a missing value for this entity.
  
          If a match is found, it is added to the values mapping.
          """
          if not check_node_schema(value.node, self._schema):
              return
          for name in self._values:
              if self._values[name] is not None:
                  continue
              if not check_value_schema(value, self._schema[const.DISC_VALUES][name]):
                  continue
              self._values[name] = value
              if self._entity:
                  self._entity.value_added()
                  self._entity.value_changed()
  
              self._check_entity_ready()
  
      def _check_entity_ready(self):
          """Check if all required values are discovered and create entity."""
          if self._workaround_ignore:
              return
          if self._entity is not None:
              return
  
          for name in self._schema[const.DISC_VALUES]:
              if self._values[name] is None and not self._schema[const.DISC_VALUES][
                  name
              ].get(const.DISC_OPTIONAL):
                  return
  
          component = self._schema[const.DISC_COMPONENT]
  
          workaround_component = workaround.get_device_component_mapping(self.primary)
          if workaround_component and workaround_component != component:
              if workaround_component == workaround.WORKAROUND_IGNORE:
                  _LOGGER.info(
                      "Ignoring Node %d Value %d due to workaround.",
                      self.primary.node.node_id,
                      self.primary.value_id,
                  )
                  # No entity will be created for this value
                  self._workaround_ignore = True
                  return
              _LOGGER.debug("Using %s instead of %s", workaround_component, component)
              component = workaround_component
  
          entity_id = self._registry.async_get_entity_id(
              component, DOMAIN, compute_value_unique_id(self._node, self.primary)
          )
          if entity_id is None:
              value_name = _value_name(self.primary)
              entity_id = generate_entity_id(component + ".{}", value_name, [])
          node_config = self._device_config.get(entity_id)
  
          # Configure node
          _LOGGER.debug(
              "Adding Node_id=%s Generic_command_class=%s, "
              "Specific_command_class=%s, "
              "Command_class=%s, Value type=%s, "
              "Genre=%s as %s",
              self._node.node_id,
              self._node.generic,
              self._node.specific,
              self.primary.command_class,
              self.primary.type,
              self.primary.genre,
              component,
          )
  
          if node_config.get(CONF_IGNORED):
              _LOGGER.info("Ignoring entity %s due to device settings", entity_id)
              # No entity will be created for this value
              self._workaround_ignore = True
              return
  
          polling_intensity = convert(node_config.get(CONF_POLLING_INTENSITY), int)
          if polling_intensity:
              self.primary.enable_poll(polling_intensity)
  
          platform = import_module(f".{component}", __name__)
  
          device = platform.get_device(
              node=self._node, values=self, node_config=node_config, hass=self._hass
          )
          if device is None:
              # No entity will be created for this value
              self._workaround_ignore = True
              return
  
          self._entity = device
  
          @callback
          def _on_ready(sec):
              _LOGGER.info(
                  "Z-Wave entity %s (node_id: %d) ready after %d seconds",
                  device.name,
                  self._node.node_id,
                  sec,
              )
              self._hass.async_add_job(discover_device, component, device)
  
          @callback
          def _on_timeout(sec):
              _LOGGER.warning(
                  "Z-Wave entity %s (node_id: %d) not ready after %d seconds, "
                  "continuing anyway",
                  device.name,
                  self._node.node_id,
                  sec,
              )
              self._hass.async_add_job(discover_device, component, device)
  
          async def discover_device(component, device):
              """Put device in a dictionary and call discovery on it."""
              if self._hass.data[DATA_DEVICES].get(device.unique_id):
                  return
  
              self._hass.data[DATA_DEVICES][device.unique_id] = device
              if component in SUPPORTED_PLATFORMS:
                  async_dispatcher_send(self._hass, f"zwave_new_{component}", device)
              else:
                  await discovery.async_load_platform(
                      self._hass,
                      component,
                      DOMAIN,
                      {const.DISCOVERY_DEVICE: device.unique_id},
                      self._zwave_config,
                  )
  
          if device.unique_id:
              self._hass.add_job(discover_device, component, device)
          else:
              self._hass.add_job(check_has_unique_id, device, _on_ready, _on_timeout)
  
  
  class ZWaveDeviceEntity(ZWaveBaseEntity):
      """Representation of a Z-Wave node entity."""
  
      def __init__(self, values, domain):
          """Initialize the z-Wave device."""
          # pylint: disable=import-error
          super().__init__()
          from openzwave.network import ZWaveNetwork
          from pydispatch import dispatcher
  
          self.values = values
          self.node = values.primary.node
          self.values.primary.set_change_verified(False)
  
          self._name = _value_name(self.values.primary)
          self._unique_id = self._compute_unique_id()
          self._update_attributes()
  
          dispatcher.connect(
              self.network_value_changed, ZWaveNetwork.SIGNAL_VALUE_CHANGED
          )
  
      def network_value_changed(self, value):
          """Handle a value change on the network."""
          if value.value_id in [v.value_id for v in self.values if v]:
              return self.value_changed()
  
      def value_added(self):
          """Handle a new value of this entity."""
          pass
  
      def value_changed(self):
          """Handle a changed value for this entity's node."""
          self._update_attributes()
          self.update_properties()
          self.maybe_schedule_update()
  
      async def value_renamed(self, update_ids=False):
          """Rename the node and update any IDs."""
          self._name = _value_name(self.values.primary)
          if update_ids:
              # Update entity ID.
              ent_reg = await async_get_entity_registry(self.hass)
              new_entity_id = ent_reg.async_generate_entity_id(
                  self.platform.domain,
                  self._name,
                  self.platform.entities.keys() - {self.entity_id},
              )
              if new_entity_id != self.entity_id:
                  # Don't change the name attribute, it will be None unless
                  # customised and if it's been customised, keep the
                  # customisation.
                  ent_reg.async_update_entity(self.entity_id, new_entity_id=new_entity_id)
                  return
          # else for the above two ifs, update if not using update_entity
          self.async_write_ha_state()
  
      async def async_added_to_hass(self):
          """Add device to dict."""
          async_dispatcher_connect(
              self.hass,
              SIGNAL_REFRESH_ENTITY_FORMAT.format(self.entity_id),
              self.refresh_from_network,
          )
  
      def _update_attributes(self):
          """Update the node attributes. May only be used inside callback."""
          self.node_id = self.node.node_id
          self._name = _value_name(self.values.primary)
          if not self._unique_id:
              self._unique_id = self._compute_unique_id()
              if self._unique_id:
                  self.try_remove_and_add()
  
          if self.values.power:
              self.power_consumption = round(
                  self.values.power.data, self.values.power.precision
              )
          else:
              self.power_consumption = None
  
      def update_properties(self):
          """Update on data changes for node values."""
          pass
  
      @property
      def should_poll(self):
          """No polling needed."""
          return False
  
      @property
      def unique_id(self):
          """Return a unique ID."""
          return self._unique_id
  
      @property
      def device_info(self):
          """Return device information."""
          identifier, name = node_device_id_and_name(
              self.node, self.values.primary.instance
          )
          info = {
              "name": name,
              "identifiers": {identifier},
              "manufacturer": self.node.manufacturer_name,
              "model": self.node.product_name,
          }
          if self.values.primary.instance > 1:
              info["via_device"] = (DOMAIN, self.node_id)
          elif self.node_id > 1:
              info["via_device"] = (DOMAIN, 1)
          return info
  
      @property
      def name(self):
          """Return the name of the device."""
          return self._name
  
      @property
      def device_state_attributes(self):
          """Return the device specific state attributes."""
          attrs = {
              const.ATTR_NODE_ID: self.node_id,
              const.ATTR_VALUE_INDEX: self.values.primary.index,
              const.ATTR_VALUE_INSTANCE: self.values.primary.instance,
              const.ATTR_VALUE_ID: str(self.values.primary.value_id),
          }
  
          if self.power_consumption is not None:
              attrs[ATTR_POWER] = self.power_consumption
  
          return attrs
  
      def refresh_from_network(self):
          """Refresh all dependent values from zwave network."""
          for value in self.values:
              if value is not None:
                  self.node.refresh_value(value.value_id)
  
      def _compute_unique_id(self):
          if (
              is_node_parsed(self.node) and self.values.primary.label != "Unknown"
          ) 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}"
  