Repository: home-assistant/core
Pull request: Add proper support for zwave_js Indicator CC
## Breaking change
On/Off properties for the Indicator CC are no longer discovered as entities. They were previously discovered as sensor entities which were not useful in cases where you wanted to change these values, and you would need to change the value for all three On/Off properties simultaneously in order for them to work which wasn't supported with our existing discovery model.

## Proposed change
Resolves https://github.com/home-assistant/core/issues/90049

Note that I couldn't find real test data for all of the cases so I created a fake device to test them. We can replace these with real devices if/when we get proper fixtures for them.


## Type of change
<!--
  What type of change does your PR introduce to Home Assistant?
  NOTE: Please, check only 1! box!
  If your PR requires multiple boxes to be checked, you'll most likely need to
  split it into multiple PRs. This makes things easier and faster to code review.
-->

- [ ] Dependency upgrade
- [ ] Bugfix (non-breaking change which fixes an issue)
- [ ] New integration (thank you!)
- [x] New feature (which adds functionality to an existing integration)
- [ ] Deprecation (breaking change to happen in the future)
- [ ] Breaking change (fix/feature causing existing functionality to break)
- [ ] Code quality improvements to existing code or addition of tests

## Additional information
<!--
  Details are important, and help maintainers processing your PR.
  Please be sure to fill out additional details, if applicable.
-->

- This PR fixes or closes issue: fixes #
- This PR is related to issue: 
- Link to documentation pull request: 

## Checklist
<!--
  Put an `x` in the boxes that apply. You can also fill these out after
  creating the PR. If you're unsure about any of them, don't hesitate to ask.
  We're here to help! This is simply a reminder of what we are going to look
  for before merging your code.
-->

- [ ] The code change is tested and works locally.
- [ ] Local tests pass. **Your PR cannot be merged unless tests pass**
- [ ] There is no commented out code in this PR.
- [ ] I have followed the [development checklist][dev-checklist]
- [ ] I have followed the [perfect PR recommendations][perfect-pr]
- [ ] The code has been formatted using Black (`black --fast homeassistant tests`)
- [ ] Tests have been added to verify that the new code works.

If user exposed functionality or configuration variables are added/changed:

- [ ] Documentation added/updated for [www.home-assistant.io][docs-repository]

If the code communicates with devices, web services, or third-party tools:

- [ ] The [manifest file][manifest-docs] has all fields filled out correctly.  
      Updated and included derived files by running: `python3 -m script.hassfest`.
- [ ] New or updated dependencies have been added to `requirements_all.txt`.  
      Updated by running `python3 -m script.gen_requirements_all`.
- [ ] For the updated dependencies - a link to the changelog, or at minimum a diff between library versions is added to the PR description.
- [ ] Untested files have been added to `.coveragerc`.

<!--
  This project is very active and we have a high turnover of pull requests.

  Unfortunately, the number of incoming pull requests is higher than what our
  reviewers can review and merge so there is a long backlog of pull requests
  waiting for review. You can help here!
  
  By reviewing another pull request, you will help raise the code quality of
  that pull request and the final review will be faster. This way the general
  pace of pull request reviews will go up and your wait time will go down.
  
  When picking a pull request to review, try to choose one that hasn't yet
  been reviewed.

  Thanks for helping out!
-->

To help with the load of incoming pull requests:

- [ ] I have reviewed two other [open pull requests][prs] in this repository.

[prs]: https://github.com/home-assistant/core/pulls?q=is%3Aopen+is%3Apr+-author%3A%40me+-draft%3Atrue+-label%3Awaiting-for-upstream+sort%3Acreated-desc+review%3Anone+-status%3Afailure

<!--
  Thank you for contributing <3

  Below, some useful links you could explore:
-->
[dev-checklist]: https://developers.home-assistant.io/docs/en/development_checklist.html
[manifest-docs]: https://developers.home-assistant.io/docs/en/creating_integration_manifest.html
[quality-scale]: https://developers.home-assistant.io/docs/en/next/integration_quality_scale_index.html
[docs-repository]: https://github.com/home-assistant/home-assistant.io
[perfect-pr]: https://developers.home-assistant.io/docs/review-process/#creating-the-perfect-pr


Problem statement:
Z-Wave JS: Necessary changes to Indicator CC values
### The problem

As discussed "offline", the values exposed for Indicator CC need to be changed/updated. Here's an overview of what needs to be supported:

**Indicator CC V1** only knows a single value, which needs to be a writable number entity:
```jsonc
{
  "commandClass": 135,
  "commandClassName": "Indicator",
  "property": "value",
  "propertyName": "identify",
  "metadata": {
    "type": "number",
    "readable": true,
    "writeable": true,
    "min": 0,
    "max": 255,
    "label": "Indicator value",
    "ccSpecific": { "indicatorId": 0 }
  }
}
```

**Indicator CC V2+** knows multiple values, numbers or booleans, writable or readonly

All values have the same format of the value ID:
```jsonc
{
  "commandClass": 135,
  "commandClassName": "Indicator",
  "property": /* some number */,
  "propertyKey": /* some number */,
  "propertyName": /* Some indicator name, e.g. "Button 1 indication" */,
  "propertyKeyName": /* Some indicator property name, e.g. "Multilevel" */,
  "metadata": { ... }
}
```

The metadata depends on the indicator definition:

**writable number**: -> should be a number entity
```jsonc
{
  // ... 
  "metadata": {
    "type": "number",
    "readable": true,
    "writeable": true,
    "min": 0, // min can be different
    "max": 255, // max can also be different
    "label": /* combination of propertyName and propertyKeyName */,
    "ccSpecific": {
      "indicatorId": /* some number, equals "property" from the value ID */,
      "propertyId": /* some number, equals "propertyKey" from the value ID */
    }
  }
}
```

**readonly number**: does not currently exist, but future spec versions may add them. -> should be a numeric sensor
The only difference is the `writeable` field from the metadata
```jsonc
{
  // ... 
  "metadata": {
    "type": "number",
    "readable": true,
    "writeable": false, // <-- HERE
    "min": 0, // min can be different
    "max": 255, // max can also be different
    "label": /* combination of propertyName and propertyKeyName */,
    "ccSpecific": {
      "indicatorId": /* some number, equals "property" from the value ID */,
      "propertyId": /* some number, equals "propertyKey" from the value ID */
    }
  }
}
```


**writable boolean**: -> should be a switch/toggle
```jsonc
{
  // ... 
  "metadata": {
    "type": "boolean",
    "readable": true,
    "writeable": true,
    "label": /* combination of propertyName and propertyKeyName */,
    "ccSpecific": {
      "indicatorId": /* some number, equals "property" from the value ID */,
      "propertyId": /* some number, equals "propertyKey" from the value ID */
    }
  }
}
```


**readonly boolean**: -> should be a binary sensor
The only difference is the `writeable` field from the metadata
```jsonc
{
  // ... 
  "metadata": {
    "type": "boolean",
    "readable": true,
    "writeable": false, // <-- HERE
    "label": /* combination of propertyName and propertyKeyName */,
    "ccSpecific": {
      "indicatorId": /* some number, equals "property" from the value ID */,
      "propertyId": /* some number, equals "propertyKey" from the value ID */
    }
  }
}
```

### What version of Home Assistant Core has the issue?

irrelevant

### What was the last working version of Home Assistant Core?

_No response_

### What type of installation are you running?

Home Assistant OS

### Integration causing the issue

Z-Wave JS

### Link to integration documentation on our website

_No response_

### Diagnostics information

_No response_

### Example YAML snippet

_No response_

### Anything in the logs that might be useful for us?

_No response_

### Additional information

_No response_

Hints from issue discussion:
Hey there @home-assistant/z-wave, mind taking a look at this issue as it has been labeled with an integration (`zwave_js`) you are listed as a [code owner](https://github.com/home-assistant/core/blob/dev/CODEOWNERS#L1408) for? Thanks!

<details>
<summary>Code owner commands</summary>


Code owners of `zwave_js` can trigger bot actions by commenting:

- `@home-assistant close` Closes the issue.
- `@home-assistant rename Awesome new title` Renames the issue.
- `@home-assistant reopen` Reopen the issue.
- `@home-assistant unassign zwave_js` Removes the current integration label and assignees on the issue, add the integration domain after the command.

</details>


<sub><sup>(message by CodeOwnersMention)</sup></sub>

---

[zwave_js documentation](https://www.home-assistant.io/integrations/zwave_js)
[zwave_js source](https://github.com/home-assistant/core/tree/dev/homeassistant/components/zwave_js)
<sub><sup>(message by IssueLinks)</sup></sub>
do you have devices that have indicators that support the various V2 options? This is easy enough to resolve but ideally we would have test data to run tests against as well
No unfortunately not. The above should be the corresponding values  which would end up in the HA dump.

If that doesn't help, I have one with writable numeric indicators, maybe that can be used to fake the others by changing the value metadata.
yeah let's start with that I guess and we can go from there. Send me the device diagnostics dump for that one and I'll make modifications as needed
Here's a V3.

[zwave_js-8fdd61f58349c9a6e25e5a0b938ad68e-Z-Wave Plus 700 Series Dimmer Switch-8c9395ac91cf2c7bb43ddfed782243f3.json.txt](https://github.com/home-assistant/core/files/11057978/zwave_js-8fdd61f58349c9a6e25e5a0b938ad68e-Z-Wave.Plus.700.Series.Dimmer.Switch-8c9395ac91cf2c7bb43ddfed782243f3.json.txt)

from that dump, there's also this type of indicator which I guess should be a button:
```
{
      "endpoint": 0,
      "commandClass": 135,
      "commandClassName": "Indicator",
      "property": "identify",
      "propertyName": "identify",
      "ccVersion": 3,
      "metadata": {
        "type": "boolean",
        "readable": false,
        "writeable": true,
        "label": "Identify"
      }
    }
```
from that dump, there's also this type of indicator which I guess should be a button:
```
{
      "endpoint": 0,
      "commandClass": 135,
      "commandClassName": "Indicator",
      "property": "identify",
      "propertyName": "identify",
      "ccVersion": 3,
      "metadata": {
        "type": "boolean",
        "readable": false,
        "writeable": true,
        "label": "Identify"
      }
    }
```
> from that dump, there's also this type of indicator which I guess should be a button:

Yes. It's actually a certification requirement to present this functionality.
Yep, we talked about putting it on the device page below the ping button

Commit message: add comment so I don't forget

Patch to review:
diff --git a/homeassistant/components/zwave_js/button.py b/homeassistant/components/zwave_js/button.py
index 2db82d38d62d13..118f42a2d94bd8 100644
--- a/homeassistant/components/zwave_js/button.py
+++ b/homeassistant/components/zwave_js/button.py
@@ -5,14 +5,16 @@
 from zwave_js_server.model.driver import Driver
 from zwave_js_server.model.node import Node as ZwaveNode
 
-from homeassistant.components.button import ButtonEntity
+from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, ButtonEntity
 from homeassistant.config_entries import ConfigEntry
 from homeassistant.const import EntityCategory
 from homeassistant.core import HomeAssistant, callback
 from homeassistant.helpers.dispatcher import async_dispatcher_connect
 from homeassistant.helpers.entity_platform import AddEntitiesCallback
 
+from . import ZwaveDiscoveryInfo
 from .const import DATA_CLIENT, DOMAIN, LOGGER
+from .entity import ZWaveBaseEntity
 from .helpers import get_device_info, get_valueless_base_unique_id
 
 PARALLEL_UPDATES = 0
@@ -26,6 +28,13 @@ async def async_setup_entry(
     """Set up Z-Wave button from config entry."""
     client: ZwaveClient = hass.data[DOMAIN][config_entry.entry_id][DATA_CLIENT]
 
+    @callback
+    def async_add_button(info: ZwaveDiscoveryInfo) -> None:
+        """Add button entity."""
+        driver = client.driver
+        assert driver is not None  # Driver is ready before platforms are loaded.
+        async_add_entities([ZwaveBooleanNodeButton(config_entry, driver, info)])
+
     @callback
     def async_add_ping_button_entity(node: ZwaveNode) -> None:
         """Add ping button entity."""
@@ -41,6 +50,31 @@ def async_add_ping_button_entity(node: ZwaveNode) -> None:
         )
     )
 
+    config_entry.async_on_unload(
+        async_dispatcher_connect(
+            hass,
+            f"{DOMAIN}_{config_entry.entry_id}_add_{BUTTON_DOMAIN}",
+            async_add_button,
+        )
+    )
+
+
+class ZwaveBooleanNodeButton(ZWaveBaseEntity, ButtonEntity):
+    """Representation of a ZWave button entity for a boolean value."""
+
+    def __init__(
+        self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo
+    ) -> None:
+        """Initialize entity."""
+        super().__init__(config_entry, driver, info)
+        self._attr_name = self.generate_name(include_value_name=True)
+
+    async def async_press(self) -> None:
+        """Press the button."""
+        await self.info.primary_value.node.async_set_value(
+            self.info.primary_value, True
+        )
+
 
 class ZWaveNodePingButton(ButtonEntity):
     """Representation of a ping button entity."""
@@ -77,6 +111,9 @@ async def async_added_to_hass(self) -> None:
             )
         )
 
+        # we don't listen for `remove_entity_on_ready_node` signal because this entity
+        # is created when the node is added which occurs before ready. It only needs to
+        # be removed if the node is removed from the network.
         self.async_on_remove(
             async_dispatcher_connect(
                 self.hass,
diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py
index 36295a64558930..0d1a14c978eaa4 100644
--- a/homeassistant/components/zwave_js/discovery.py
+++ b/homeassistant/components/zwave_js/discovery.py
@@ -44,7 +44,7 @@
 from zwave_js_server.model.value import Value as ZwaveValue
 
 from homeassistant.backports.enum import StrEnum
-from homeassistant.const import Platform
+from homeassistant.const import EntityCategory, Platform
 from homeassistant.core import callback
 from homeassistant.helpers.device_registry import DeviceEntry
 
@@ -108,7 +108,8 @@ class ZwaveDiscoveryInfo:
     node: ZwaveNode
     # the value object itself for primary value
     primary_value: ZwaveValue
-    # bool to specify whether state is assumed and events should be fired on value update
+    # bool to specify whether state is assumed and events should be fired on value
+    # update
     assumed_state: bool
     # the home assistant platform for which an entity should be created
     platform: Platform
@@ -122,6 +123,8 @@ class ZwaveDiscoveryInfo:
     platform_data_template: BaseDiscoverySchemaDataTemplate | None = None
     # bool to specify whether entity should be enabled by default
     entity_registry_enabled_default: bool = True
+    # the entity category for the discovered entity
+    entity_category: EntityCategory | None = None
 
 
 @dataclass
@@ -143,10 +146,16 @@ class ZWaveValueDiscoverySchema(DataclassMustHaveAtLeastOne):
     property_name: set[str] | None = None
     # [optional] the value's property key must match ANY of these values
     property_key: set[str | int | None] | None = None
+    # [optional] the value's property key must NOT match ANY of these values
+    not_property_key: set[str | int | None] | None = None
     # [optional] the value's property key name must match ANY of these values
     property_key_name: set[str | None] | None = None
     # [optional] the value's metadata_type must match ANY of these values
     type: set[str] | None = None
+    # [optional] the value's metadata_readable must match this value
+    readable: bool | None = None
+    # [optional] the value's metadata_writeable must match this value
+    writeable: bool | None = None
 
 
 @dataclass
@@ -196,6 +205,8 @@ class ZWaveDiscoverySchema:
     assumed_state: bool = False
     # [optional] bool to specify whether entity should be enabled by default
     entity_registry_enabled_default: bool = True
+    # [optional] the entity category for the discovered entity
+    entity_category: EntityCategory | None = None
 
 
 def get_config_parameter_discovery_schema(
@@ -694,23 +705,26 @@ def get_config_parameter_discovery_schema(
         ),
         allow_multi=True,
     ),
-    # generic text sensors
+    # binary sensor for Indicator CC
     ZWaveDiscoverySchema(
-        platform=Platform.SENSOR,
-        hint="string_sensor",
+        platform=Platform.BINARY_SENSOR,
+        hint="boolean",
         primary_value=ZWaveValueDiscoverySchema(
-            command_class={CommandClass.SENSOR_ALARM},
-            type={ValueType.STRING},
+            command_class={CommandClass.INDICATOR},
+            type={ValueType.BOOLEAN},
+            readable=True,
+            writeable=False,
         ),
+        entity_category=EntityCategory.DIAGNOSTIC,
     ),
+    # generic text sensors
     ZWaveDiscoverySchema(
         platform=Platform.SENSOR,
         hint="string_sensor",
         primary_value=ZWaveValueDiscoverySchema(
-            command_class={CommandClass.INDICATOR},
+            command_class={CommandClass.SENSOR_ALARM},
             type={ValueType.STRING},
         ),
-        entity_registry_enabled_default=False,
     ),
     # generic numeric sensors
     ZWaveDiscoverySchema(
@@ -732,9 +746,11 @@ def get_config_parameter_discovery_schema(
         primary_value=ZWaveValueDiscoverySchema(
             command_class={CommandClass.INDICATOR},
             type={ValueType.NUMBER},
+            readable=True,
+            writeable=False,
         ),
         data_template=NumericSensorDataTemplate(),
-        entity_registry_enabled_default=False,
+        entity_category=EntityCategory.DIAGNOSTIC,
     ),
     # Meter sensors for Meter CC
     ZWaveDiscoverySchema(
@@ -767,9 +783,7 @@ def get_config_parameter_discovery_schema(
         platform=Platform.NUMBER,
         hint="Basic",
         primary_value=ZWaveValueDiscoverySchema(
-            command_class={
-                CommandClass.BASIC,
-            },
+            command_class={CommandClass.BASIC},
             type={ValueType.NUMBER},
             property={CURRENT_VALUE_PROPERTY},
         ),
@@ -782,14 +796,48 @@ def get_config_parameter_discovery_schema(
                 property={TARGET_VALUE_PROPERTY},
             )
         ],
-        data_template=NumericSensorDataTemplate(),
         entity_registry_enabled_default=False,
     ),
+    # number for Indicator CC (exclude property keys 3-5)
+    ZWaveDiscoverySchema(
+        platform=Platform.NUMBER,
+        primary_value=ZWaveValueDiscoverySchema(
+            command_class={CommandClass.INDICATOR},
+            type={ValueType.NUMBER},
+            not_property_key={3, 4, 5},
+            readable=True,
+            writeable=True,
+        ),
+        entity_category=EntityCategory.DIAGNOSTIC,
+    ),
+    # button for Indicator CC
+    ZWaveDiscoverySchema(
+        platform=Platform.BUTTON,
+        primary_value=ZWaveValueDiscoverySchema(
+            command_class={CommandClass.INDICATOR},
+            type={ValueType.BOOLEAN},
+            readable=False,
+            writeable=True,
+        ),
+        entity_category=EntityCategory.DIAGNOSTIC,
+    ),
     # binary switches
     ZWaveDiscoverySchema(
         platform=Platform.SWITCH,
         primary_value=SWITCH_BINARY_CURRENT_VALUE_SCHEMA,
     ),
+    # switch for Indicator CC
+    ZWaveDiscoverySchema(
+        platform=Platform.SWITCH,
+        hint="indicator",
+        primary_value=ZWaveValueDiscoverySchema(
+            command_class={CommandClass.INDICATOR},
+            type={ValueType.BOOLEAN},
+            readable=True,
+            writeable=True,
+        ),
+        entity_category=EntityCategory.DIAGNOSTIC,
+    ),
     # binary switch
     # barrier operator signaling states
     ZWaveDiscoverySchema(
@@ -1028,6 +1076,7 @@ def async_discover_single_value(
             platform_data=resolved_data,
             additional_value_ids_to_watch=additional_value_ids_to_watch,
             entity_registry_enabled_default=schema.entity_registry_enabled_default,
+            entity_category=schema.entity_category,
         )
 
         if not schema.allow_multi:
@@ -1063,6 +1112,12 @@ def check_value(value: ZwaveValue, schema: ZWaveValueDiscoverySchema) -> bool:
         and value.property_key not in schema.property_key
     ):
         return False
+    # check property_key against not_property_key set
+    if (
+        schema.not_property_key is not None
+        and value.property_key in schema.not_property_key
+    ):
+        return False
     # check property_key_name
     if (
         schema.property_key_name is not None
@@ -1072,6 +1127,12 @@ def check_value(value: ZwaveValue, schema: ZWaveValueDiscoverySchema) -> bool:
     # check metadata_type
     if schema.type is not None and value.metadata.type not in schema.type:
         return False
+    # check metadata_readable
+    if schema.readable is not None and value.metadata.readable != schema.readable:
+        return False
+    # check metadata_writeable
+    if schema.writeable is not None and value.metadata.writeable != schema.writeable:
+        return False
     return True
 
 
diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py
index 2a57257ffbfbb8..f15b94f3920932 100644
--- a/homeassistant/components/zwave_js/entity.py
+++ b/homeassistant/components/zwave_js/entity.py
@@ -44,10 +44,12 @@ def __init__(
         # Entity class attributes
         self._attr_name = self.generate_name()
         self._attr_unique_id = get_unique_id(driver, self.info.primary_value.value_id)
-        self._attr_entity_registry_enabled_default = (
-            self.info.entity_registry_enabled_default
-        )
-        self._attr_assumed_state = self.info.assumed_state
+        if self.info.entity_registry_enabled_default is False:
+            self._attr_entity_registry_enabled_default = False
+        if self.info.entity_category is not None:
+            self._attr_entity_category = self.info.entity_category
+        if self.info.assumed_state:
+            self._attr_assumed_state = True
         # device is precreated in main handler
         self._attr_device_info = DeviceInfo(
             identifiers={get_device_id(driver, self.info.node)},
@@ -97,6 +99,17 @@ async def async_added_to_hass(self) -> None:
         self.async_on_remove(
             self.info.node.on(EVENT_VALUE_REMOVED, self._value_removed)
         )
+        self.async_on_remove(
+            async_dispatcher_connect(
+                self.hass,
+                (
+                    f"{DOMAIN}_"
+                    f"{get_valueless_base_unique_id(self.driver, self.info.node)}_"
+                    "remove_entity"
+                ),
+                self.async_remove,
+            )
+        )
         self.async_on_remove(
             async_dispatcher_connect(
                 self.hass,
diff --git a/homeassistant/components/zwave_js/number.py b/homeassistant/components/zwave_js/number.py
index a6a219fa7c63e5..52302c64b33b1a 100644
--- a/homeassistant/components/zwave_js/number.py
+++ b/homeassistant/components/zwave_js/number.py
@@ -66,7 +66,9 @@ def __init__(
             self._target_value = self.get_zwave_value(TARGET_VALUE_PROPERTY)
 
         # Entity class attributes
-        self._attr_name = self.generate_name(alternate_value_name=info.platform_hint)
+        self._attr_name = self.generate_name(
+            include_value_name=True, alternate_value_name=info.platform_hint
+        )
 
     @property
     def native_min_value(self) -> float:
diff --git a/homeassistant/components/zwave_js/sensor.py b/homeassistant/components/zwave_js/sensor.py
index 7a0532f00eb44d..66b00424a56c4b 100644
--- a/homeassistant/components/zwave_js/sensor.py
+++ b/homeassistant/components/zwave_js/sensor.py
@@ -637,6 +637,9 @@ async def async_added_to_hass(self) -> None:
                 self.async_poll_value,
             )
         )
+        # we don't listen for `remove_entity_on_ready_node` signal because this entity
+        # is created when the node is added which occurs before ready. It only needs to
+        # be removed if the node is removed from the network.
         self.async_on_remove(
             async_dispatcher_connect(
                 self.hass,
diff --git a/homeassistant/components/zwave_js/switch.py b/homeassistant/components/zwave_js/switch.py
index 450a0cfd31d97e..24e7486d8bd345 100644
--- a/homeassistant/components/zwave_js/switch.py
+++ b/homeassistant/components/zwave_js/switch.py
@@ -41,6 +41,8 @@ def async_add_switch(info: ZwaveDiscoveryInfo) -> None:
             entities.append(
                 ZWaveBarrierEventSignalingSwitch(config_entry, driver, info)
             )
+        elif info.platform_hint == "indicator":
+            entities.append(ZWaveIndicatorSwitch(config_entry, driver, info))
         else:
             entities.append(ZWaveSwitch(config_entry, driver, info))
 
@@ -85,6 +87,18 @@ async def async_turn_off(self, **kwargs: Any) -> None:
             await self.info.node.async_set_value(self._target_value, False)
 
 
+class ZWaveIndicatorSwitch(ZWaveSwitch):
+    """Represenstation of a Z-Wave Indicator CC switch."""
+
+    def __init__(
+        self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo
+    ) -> None:
+        """Initialize the switch."""
+        super().__init__(config_entry, driver, info)
+        self._target_value = self.info.primary_value
+        self._attr_name = self.generate_name(include_value_name=True)
+
+
 class ZWaveBarrierEventSignalingSwitch(ZWaveBaseEntity, SwitchEntity):
     """Switch is used to turn on/off a barrier device's event signaling subsystem."""
 
diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py
index 32082a0bb85861..d071d9379157d1 100644
--- a/tests/components/zwave_js/conftest.py
+++ b/tests/components/zwave_js/conftest.py
@@ -11,6 +11,8 @@
 from zwave_js_server.model.node import Node
 from zwave_js_server.version import VersionInfo
 
+from homeassistant.core import HomeAssistant
+
 from tests.common import MockConfigEntry, load_fixture
 
 # Add-on fixtures
@@ -109,7 +111,7 @@ def mock_addon_options(addon_info):
 def set_addon_options_side_effect_fixture(addon_options):
     """Return the set add-on options side effect."""
 
-    async def set_addon_options(hass, slug, options):
+    async def set_addon_options(hass: HomeAssistant, slug, options):
         """Mock set add-on options."""
         addon_options.update(options["options"])
 
@@ -130,7 +132,7 @@ def mock_set_addon_options(set_addon_options_side_effect):
 def install_addon_side_effect_fixture(addon_store_info, addon_info):
     """Return the install add-on side effect."""
 
-    async def install_addon(hass, slug):
+    async def install_addon(hass: HomeAssistant, slug):
         """Mock install add-on."""
         addon_store_info.return_value = {
             "available": True,
@@ -168,7 +170,7 @@ def mock_update_addon():
 def start_addon_side_effect_fixture(addon_store_info, addon_info):
     """Return the start add-on options side effect."""
 
-    async def start_addon(hass, slug):
+    async def start_addon(hass: HomeAssistant, slug):
         """Mock start add-on."""
         addon_store_info.return_value = {
             "available": True,
@@ -365,7 +367,7 @@ def climate_adc_t3000_state_fixture():
 
 @pytest.fixture(name="climate_danfoss_lc_13_state", scope="session")
 def climate_danfoss_lc_13_state_fixture():
-    """Load the climate Danfoss (LC-13) electronic radiator thermostat node state fixture data."""
+    """Load Danfoss (LC-13) electronic radiator thermostat node state fixture data."""
     return json.loads(load_fixture("zwave_js/climate_danfoss_lc_13_state.json"))
 
 
@@ -522,7 +524,7 @@ def climate_radio_thermostat_ct101_multiple_temp_units_state_fixture():
     scope="session",
 )
 def climate_radio_thermostat_ct100_mode_and_setpoint_on_different_endpoints_state_fixture():
-    """Load the climate device with mode and setpoint on different endpoints node state fixture data."""
+    """Load climate device w/ mode+setpoint on diff endpoints node state fixture data."""
     return json.loads(
         load_fixture(
             "zwave_js/climate_radio_thermostat_ct100_mode_and_setpoint_on_different_endpoints_state.json"
@@ -604,6 +606,18 @@ def lock_home_connect_620_state_fixture():
     return json.loads(load_fixture("zwave_js/lock_home_connect_620_state.json"))
 
 
+@pytest.fixture(name="switch_zooz_zen72_state", scope="session")
+def switch_zooz_zen72_state_fixture():
+    """Load the Zooz Zen72 switch node state fixture data."""
+    return json.loads(load_fixture("zwave_js/switch_zooz_zen72_state.json"))
+
+
+@pytest.fixture(name="indicator_test_state", scope="session")
+def indicator_test_state_fixture():
+    """Load the indicator CC test node state fixture data."""
+    return json.loads(load_fixture("zwave_js/indicator_test_state.json"))
+
+
 # model fixtures
 
 
@@ -612,7 +626,6 @@ def mock_client_fixture(
     controller_state, controller_node_state, version_state, log_config_state
 ):
     """Mock a client."""
-
     with patch(
         "homeassistant.components.zwave_js.ZwaveClient", autospec=True
     ) as client_class:
@@ -722,7 +735,7 @@ def climate_radio_thermostat_ct100_plus_fixture(
 def climate_radio_thermostat_ct100_plus_different_endpoints_fixture(
     client, climate_radio_thermostat_ct100_plus_different_endpoints_state
 ):
-    """Mock a climate radio thermostat ct100 plus node with values on different endpoints."""
+    """Mock climate radio thermostat ct100 plus node w/ values on diff endpoints."""
     node = Node(
         client,
         copy.deepcopy(climate_radio_thermostat_ct100_plus_different_endpoints_state),
@@ -773,7 +786,7 @@ def climate_adc_t3000_missing_mode_fixture(client, climate_adc_t3000_state):
 
 @pytest.fixture(name="climate_adc_t3000_missing_fan_mode_states")
 def climate_adc_t3000_missing_fan_mode_states_fixture(client, climate_adc_t3000_state):
-    """Mock a climate ADC-T3000 node with missing 'states' metadata on Thermostat Fan Mode."""
+    """Mock ADC-T3000 node w/ missing 'states' metadata on Thermostat Fan Mode."""
     data = copy.deepcopy(climate_adc_t3000_state)
     data["name"] = f"{data['name']} missing fan mode states"
     for value in data["values"]:
@@ -872,7 +885,7 @@ def nortek_thermostat_removed_event_fixture(client):
 
 
 @pytest.fixture(name="integration")
-async def integration_fixture(hass, client):
+async def integration_fixture(hass: HomeAssistant, client):
     """Set up the zwave_js integration."""
     entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"})
     entry.add_to_hass(hass)
@@ -1146,3 +1159,19 @@ def lock_home_connect_620_fixture(client, lock_home_connect_620_state):
     node = Node(client, copy.deepcopy(lock_home_connect_620_state))
     client.driver.controller.nodes[node.node_id] = node
     return node
+
+
+@pytest.fixture(name="switch_zooz_zen72")
+def switch_zooz_zen72_fixture(client, switch_zooz_zen72_state):
+    """Mock a Zooz Zen72 switch node."""
+    node = Node(client, copy.deepcopy(switch_zooz_zen72_state))
+    client.driver.controller.nodes[node.node_id] = node
+    return node
+
+
+@pytest.fixture(name="indicator_test")
+def indicator_test_fixture(client, indicator_test_state):
+    """Mock a indicator CC test node."""
+    node = Node(client, copy.deepcopy(indicator_test_state))
+    client.driver.controller.nodes[node.node_id] = node
+    return node
diff --git a/tests/components/zwave_js/fixtures/indicator_test_state.json b/tests/components/zwave_js/fixtures/indicator_test_state.json
new file mode 100644
index 00000000000000..9e823b79109f9d
--- /dev/null
+++ b/tests/components/zwave_js/fixtures/indicator_test_state.json
@@ -0,0 +1,190 @@
+{
+  "nodeId": 43,
+  "index": 0,
+  "installerIcon": 1536,
+  "userIcon": 1540,
+  "status": 4,
+  "ready": true,
+  "isListening": true,
+  "isRouting": true,
+  "isSecure": true,
+  "manufacturerId": 634,
+  "productId": 40962,
+  "productType": 28672,
+  "firmwareVersion": "10.0.1",
+  "zwavePlusVersion": 2,
+  "location": "**REDACTED**",
+  "deviceConfig": {
+    "filename": "/usr/src/app/store/.config-db/devices/0x027a/zen72.json",
+    "isEmbedded": true,
+    "manufacturer": "Fake",
+    "manufacturerId": 634,
+    "label": "Device",
+    "description": "This is a fake device",
+    "devices": [
+      {
+        "productType": 28672,
+        "productId": 40962
+      }
+    ],
+    "firmwareVersion": {
+      "min": "0.0",
+      "max": "255.255"
+    },
+    "associations": {},
+    "paramInformation": {
+      "_map": {}
+    },
+    "compat": {
+      "skipConfigurationNameQuery": true,
+      "skipConfigurationInfoQuery": true
+    },
+    "metadata": {
+      "inclusion": "1. Initiate inclusion (pairing) in the app (or web interface).\n2. TAP UP 3 TIMES QUICKLY if using traditional Z-Wave inclusion.\n3. The LED indicator will blink blue to signal communication and turn green for 3 seconds if inclusion is successful or turn red for 3 seconds if the pairing attempt fails",
+      "exclusion": "1. Bring your Z-Wave gateway (hub) close to the switch if possible\n2. Put the Z-Wave hub into exclusion mode (not sure how to do that? ask@getzooz.com) \n3. Tap the lower paddle on the switch 3 times quickly (the LED indicator will start blinking blue)\n4. Your hub will confirm exclusion, the LED indicator on the switch will turn green for 3 seconds, and the device will disappear from your controller's device list",
+      "reset": "If your primary controller is missing or inoperable, you may need to reset the device to factory settings. To reset the switch, press and hold the lower paddle for 10 seconds until the LED indicator starts blinking. Release paddle, and immediately after, tap the lower paddle 5 times to complete the reset. The LED indicator will flash blue 3 times and turn red for 3 seconds to confirm successful reset",
+      "manual": "https://products.z-wavealliance.org/ProductManual/File?folder=&filename=product_documents/4108/zooz-700-series-z-wave-dimmer-zen72-manual.pdf"
+    }
+  },
+  "label": "Device",
+  "interviewAttempts": 0,
+  "endpoints": [
+    {
+      "nodeId": 43,
+      "index": 0,
+      "installerIcon": 1536,
+      "userIcon": 1540,
+      "deviceClass": {
+        "basic": {
+          "key": 4,
+          "label": "Routing Slave"
+        },
+        "generic": {
+          "key": 17,
+          "label": "Multilevel Switch"
+        },
+        "specific": {
+          "key": 1,
+          "label": "Multilevel Power Switch"
+        },
+        "mandatorySupportedCCs": [32, 38, 39],
+        "mandatoryControlledCCs": []
+      },
+      "commandClasses": [
+        {
+          "id": 135,
+          "name": "Indicator",
+          "version": 3,
+          "isSecure": true
+        }
+      ]
+    }
+  ],
+  "values": [
+    {
+      "endpoint": 0,
+      "commandClass": 135,
+      "commandClassName": "Indicator",
+      "property": "Test",
+      "propertyKey": "Sensor",
+      "propertyName": "Test",
+      "propertyKeyName": "Sensor",
+      "ccVersion": 3,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": false,
+        "description": "Tests non-writeable Indicator CC number gets created as a sensor entity",
+        "label": "Sensor",
+        "ccSpecific": {
+          "indicatorId": 80,
+          "propertyId": 4
+        }
+      },
+      "value": 0
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 135,
+      "commandClassName": "Indicator",
+      "property": "Test",
+      "propertyKey": "Switch",
+      "propertyName": "Test",
+      "propertyKeyName": "Switch",
+      "ccVersion": 3,
+      "metadata": {
+        "type": "boolean",
+        "readable": true,
+        "writeable": true,
+        "description": "Tests writeable Indicator CC boolean gets created as a switch entity",
+        "label": "Switch",
+        "ccSpecific": {
+          "indicatorId": 80,
+          "propertyId": 5
+        }
+      },
+      "value": false
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 135,
+      "commandClassName": "Indicator",
+      "property": "Test",
+      "propertyKey": "Binary Sensor",
+      "propertyName": "Test",
+      "propertyKeyName": "Binary Sensor",
+      "ccVersion": 3,
+      "metadata": {
+        "type": "boolean",
+        "readable": true,
+        "writeable": false,
+        "description": "Tests non-writeable Indicator CC boolean gets created as a binary sensor entity",
+        "label": "Binary Sensor",
+        "ccSpecific": {
+          "indicatorId": 80,
+          "propertyId": 5
+        }
+      },
+      "value": false
+    }
+  ],
+  "isFrequentListening": false,
+  "maxDataRate": 100000,
+  "supportedDataRates": [40000, 100000],
+  "protocolVersion": 3,
+  "supportsBeaming": true,
+  "supportsSecurity": false,
+  "nodeType": 1,
+  "zwavePlusNodeType": 0,
+  "zwavePlusRoleType": 5,
+  "deviceClass": {
+    "basic": {
+      "key": 4,
+      "label": "Routing Slave"
+    },
+    "generic": {
+      "key": 17,
+      "label": "Multilevel Switch"
+    },
+    "specific": {
+      "key": 1,
+      "label": "Multilevel Power Switch"
+    },
+    "mandatorySupportedCCs": [32, 38, 39],
+    "mandatoryControlledCCs": []
+  },
+  "interviewStage": "Complete",
+  "deviceDatabaseUrl": "https://devices.zwave-js.io/?jumpTo=0x027a:0x7000:0xa002:10.0.1",
+  "statistics": {
+    "commandsTX": 64,
+    "commandsRX": 88,
+    "commandsDroppedRX": 0,
+    "commandsDroppedTX": 0,
+    "timeoutResponse": 0,
+    "rtt": 74.5,
+    "rssi": -60
+  },
+  "highestSecurityClass": 1,
+  "isControllerNode": false,
+  "keepAwake": false
+}
diff --git a/tests/components/zwave_js/fixtures/switch_zooz_zen72_state.json b/tests/components/zwave_js/fixtures/switch_zooz_zen72_state.json
new file mode 100644
index 00000000000000..04b3a336618e65
--- /dev/null
+++ b/tests/components/zwave_js/fixtures/switch_zooz_zen72_state.json
@@ -0,0 +1,1299 @@
+{
+  "nodeId": 43,
+  "index": 0,
+  "installerIcon": 1536,
+  "userIcon": 1540,
+  "status": 4,
+  "ready": true,
+  "isListening": true,
+  "isRouting": true,
+  "isSecure": true,
+  "manufacturerId": 634,
+  "productId": 40962,
+  "productType": 28672,
+  "firmwareVersion": "10.0.1",
+  "zwavePlusVersion": 2,
+  "location": "**REDACTED**",
+  "deviceConfig": {
+    "filename": "/usr/src/app/store/.config-db/devices/0x027a/zen72.json",
+    "isEmbedded": true,
+    "manufacturer": "Zooz",
+    "manufacturerId": 634,
+    "label": "ZEN72",
+    "description": "Z-Wave Plus 700 Series Dimmer Switch",
+    "devices": [
+      {
+        "productType": 28672,
+        "productId": 40962
+      }
+    ],
+    "firmwareVersion": {
+      "min": "0.0",
+      "max": "255.255"
+    },
+    "associations": {},
+    "paramInformation": {
+      "_map": {}
+    },
+    "compat": {
+      "skipConfigurationNameQuery": true,
+      "skipConfigurationInfoQuery": true
+    },
+    "metadata": {
+      "inclusion": "1. Initiate inclusion (pairing) in the app (or web interface).\n2. TAP UP 3 TIMES QUICKLY if using traditional Z-Wave inclusion.\n3. The LED indicator will blink blue to signal communication and turn green for 3 seconds if inclusion is successful or turn red for 3 seconds if the pairing attempt fails",
+      "exclusion": "1. Bring your Z-Wave gateway (hub) close to the switch if possible\n2. Put the Z-Wave hub into exclusion mode (not sure how to do that? ask@getzooz.com) \n3. Tap the lower paddle on the switch 3 times quickly (the LED indicator will start blinking blue)\n4. Your hub will confirm exclusion, the LED indicator on the switch will turn green for 3 seconds, and the device will disappear from your controller's device list",
+      "reset": "If your primary controller is missing or inoperable, you may need to reset the device to factory settings. To reset the switch, press and hold the lower paddle for 10 seconds until the LED indicator starts blinking. Release paddle, and immediately after, tap the lower paddle 5 times to complete the reset. The LED indicator will flash blue 3 times and turn red for 3 seconds to confirm successful reset",
+      "manual": "https://products.z-wavealliance.org/ProductManual/File?folder=&filename=product_documents/4108/zooz-700-series-z-wave-dimmer-zen72-manual.pdf"
+    }
+  },
+  "label": "ZEN72",
+  "interviewAttempts": 0,
+  "endpoints": [
+    {
+      "nodeId": 43,
+      "index": 0,
+      "installerIcon": 1536,
+      "userIcon": 1540,
+      "deviceClass": {
+        "basic": {
+          "key": 4,
+          "label": "Routing Slave"
+        },
+        "generic": {
+          "key": 17,
+          "label": "Multilevel Switch"
+        },
+        "specific": {
+          "key": 1,
+          "label": "Multilevel Power Switch"
+        },
+        "mandatorySupportedCCs": [32, 38, 39],
+        "mandatoryControlledCCs": []
+      },
+      "commandClasses": [
+        {
+          "id": 38,
+          "name": "Multilevel Switch",
+          "version": 4,
+          "isSecure": true
+        },
+        {
+          "id": 159,
+          "name": "Security 2",
+          "version": 1,
+          "isSecure": true
+        },
+        {
+          "id": 94,
+          "name": "Z-Wave Plus Info",
+          "version": 2,
+          "isSecure": false
+        },
+        {
+          "id": 85,
+          "name": "Transport Service",
+          "version": 2,
+          "isSecure": false
+        },
+        {
+          "id": 108,
+          "name": "Supervision",
+          "version": 1,
+          "isSecure": false
+        },
+        {
+          "id": 134,
+          "name": "Version",
+          "version": 3,
+          "isSecure": true
+        },
+        {
+          "id": 112,
+          "name": "Configuration",
+          "version": 4,
+          "isSecure": true
+        },
+        {
+          "id": 91,
+          "name": "Central Scene",
+          "version": 3,
+          "isSecure": true
+        },
+        {
+          "id": 133,
+          "name": "Association",
+          "version": 3,
+          "isSecure": true
+        },
+        {
+          "id": 142,
+          "name": "Multi Channel Association",
+          "version": 4,
+          "isSecure": true
+        },
+        {
+          "id": 89,
+          "name": "Association Group Information",
+          "version": 3,
+          "isSecure": true
+        },
+        {
+          "id": 114,
+          "name": "Manufacturer Specific",
+          "version": 2,
+          "isSecure": true
+        },
+        {
+          "id": 90,
+          "name": "Device Reset Locally",
+          "version": 1,
+          "isSecure": true
+        },
+        {
+          "id": 135,
+          "name": "Indicator",
+          "version": 3,
+          "isSecure": true
+        },
+        {
+          "id": 115,
+          "name": "Powerlevel",
+          "version": 1,
+          "isSecure": true
+        },
+        {
+          "id": 122,
+          "name": "Firmware Update Meta Data",
+          "version": 5,
+          "isSecure": true
+        }
+      ]
+    }
+  ],
+  "values": [
+    {
+      "endpoint": 0,
+      "commandClass": 38,
+      "commandClassName": "Multilevel Switch",
+      "property": "targetValue",
+      "propertyName": "targetValue",
+      "ccVersion": 4,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": true,
+        "label": "Target value",
+        "valueChangeOptions": ["transitionDuration"],
+        "min": 0,
+        "max": 99
+      },
+      "value": 29
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 38,
+      "commandClassName": "Multilevel Switch",
+      "property": "duration",
+      "propertyName": "duration",
+      "ccVersion": 4,
+      "metadata": {
+        "type": "duration",
+        "readable": true,
+        "writeable": false,
+        "label": "Remaining duration"
+      },
+      "value": {
+        "value": 3,
+        "unit": "seconds"
+      }
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 38,
+      "commandClassName": "Multilevel Switch",
+      "property": "currentValue",
+      "propertyName": "currentValue",
+      "ccVersion": 4,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": false,
+        "label": "Current value",
+        "min": 0,
+        "max": 99
+      },
+      "value": 99
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 38,
+      "commandClassName": "Multilevel Switch",
+      "property": "Up",
+      "propertyName": "Up",
+      "ccVersion": 4,
+      "metadata": {
+        "type": "boolean",
+        "readable": false,
+        "writeable": true,
+        "label": "Perform a level change (Up)",
+        "ccSpecific": {
+          "switchType": 2
+        },
+        "valueChangeOptions": ["transitionDuration"]
+      }
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 38,
+      "commandClassName": "Multilevel Switch",
+      "property": "Down",
+      "propertyName": "Down",
+      "ccVersion": 4,
+      "metadata": {
+        "type": "boolean",
+        "readable": false,
+        "writeable": true,
+        "label": "Perform a level change (Down)",
+        "ccSpecific": {
+          "switchType": 2
+        },
+        "valueChangeOptions": ["transitionDuration"]
+      }
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 38,
+      "commandClassName": "Multilevel Switch",
+      "property": "restorePrevious",
+      "propertyName": "restorePrevious",
+      "ccVersion": 4,
+      "metadata": {
+        "type": "boolean",
+        "readable": false,
+        "writeable": true,
+        "label": "Restore previous value"
+      }
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 91,
+      "commandClassName": "Central Scene",
+      "property": "scene",
+      "propertyKey": "001",
+      "propertyName": "scene",
+      "propertyKeyName": "001",
+      "ccVersion": 3,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": false,
+        "label": "Scene 001",
+        "min": 0,
+        "max": 255,
+        "states": {
+          "0": "KeyPressed",
+          "1": "KeyReleased",
+          "2": "KeyHeldDown",
+          "3": "KeyPressed2x",
+          "4": "KeyPressed3x",
+          "5": "KeyPressed4x",
+          "6": "KeyPressed5x"
+        }
+      }
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 91,
+      "commandClassName": "Central Scene",
+      "property": "scene",
+      "propertyKey": "002",
+      "propertyName": "scene",
+      "propertyKeyName": "002",
+      "ccVersion": 3,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": false,
+        "label": "Scene 002",
+        "min": 0,
+        "max": 255,
+        "states": {
+          "0": "KeyPressed",
+          "1": "KeyReleased",
+          "2": "KeyHeldDown",
+          "3": "KeyPressed2x",
+          "4": "KeyPressed3x",
+          "5": "KeyPressed4x",
+          "6": "KeyPressed5x"
+        }
+      }
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 91,
+      "commandClassName": "Central Scene",
+      "property": "slowRefresh",
+      "propertyName": "slowRefresh",
+      "ccVersion": 3,
+      "metadata": {
+        "type": "boolean",
+        "readable": true,
+        "writeable": true,
+        "description": "When this is true, KeyHeldDown notifications are sent every 55s. When this is false, the notifications are sent every 200ms.",
+        "label": "Send held down notifications at a slow rate"
+      }
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 112,
+      "commandClassName": "Configuration",
+      "property": 1,
+      "propertyName": "Inverted Orientation",
+      "ccVersion": 4,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": true,
+        "label": "Inverted Orientation",
+        "default": 0,
+        "min": 0,
+        "max": 2,
+        "states": {
+          "0": "Disable",
+          "1": "Enable",
+          "2": "Momentary mode"
+        },
+        "valueSize": 1,
+        "format": 1,
+        "allowManualEntry": false,
+        "isFromConfig": true
+      },
+      "value": 0
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 112,
+      "commandClassName": "Configuration",
+      "property": 2,
+      "propertyName": "LED Indicator",
+      "ccVersion": 4,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": true,
+        "label": "LED Indicator",
+        "default": 0,
+        "min": 0,
+        "max": 3,
+        "states": {
+          "0": "On when load is off",
+          "1": "On when load is on",
+          "2": "Always off",
+          "3": "Always on"
+        },
+        "valueSize": 1,
+        "format": 0,
+        "allowManualEntry": false,
+        "isFromConfig": true
+      },
+      "value": 0
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 112,
+      "commandClassName": "Configuration",
+      "property": 23,
+      "propertyName": "LED Indicator Color",
+      "ccVersion": 4,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": true,
+        "label": "LED Indicator Color",
+        "default": 1,
+        "min": 0,
+        "max": 3,
+        "states": {
+          "0": "White",
+          "1": "Blue",
+          "2": "Green",
+          "3": "Red"
+        },
+        "valueSize": 1,
+        "format": 1,
+        "allowManualEntry": false,
+        "isFromConfig": true
+      },
+      "value": 2
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 112,
+      "commandClassName": "Configuration",
+      "property": 24,
+      "propertyName": "LED Indicator Brightness",
+      "ccVersion": 4,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": true,
+        "label": "LED Indicator Brightness",
+        "default": 1,
+        "min": 0,
+        "max": 2,
+        "states": {
+          "0": "Bright (100%)",
+          "1": "Medium (60%)",
+          "2": "Low (30%)"
+        },
+        "valueSize": 1,
+        "format": 1,
+        "allowManualEntry": false,
+        "isFromConfig": true
+      },
+      "value": 0
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 112,
+      "commandClassName": "Configuration",
+      "property": 3,
+      "propertyName": "Auto Turn-Off Timer",
+      "ccVersion": 4,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": true,
+        "label": "Auto Turn-Off Timer",
+        "default": 0,
+        "min": 0,
+        "max": 65535,
+        "states": {
+          "0": "Disable"
+        },
+        "unit": "minutes",
+        "valueSize": 4,
+        "format": 1,
+        "allowManualEntry": true,
+        "isFromConfig": true
+      },
+      "value": 0
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 112,
+      "commandClassName": "Configuration",
+      "property": 5,
+      "propertyName": "Auto Turn-On Timer",
+      "ccVersion": 4,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": true,
+        "label": "Auto Turn-On Timer",
+        "default": 0,
+        "min": 0,
+        "max": 65535,
+        "states": {
+          "0": "Disable"
+        },
+        "unit": "minutes",
+        "valueSize": 4,
+        "format": 1,
+        "allowManualEntry": true,
+        "isFromConfig": true
+      },
+      "value": 0
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 112,
+      "commandClassName": "Configuration",
+      "property": 8,
+      "propertyName": "State After Power Failure",
+      "ccVersion": 4,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": true,
+        "label": "State After Power Failure",
+        "default": 2,
+        "min": 0,
+        "max": 2,
+        "states": {
+          "0": "Always off",
+          "1": "Always on",
+          "2": "Previous state"
+        },
+        "valueSize": 1,
+        "format": 0,
+        "allowManualEntry": false,
+        "isFromConfig": true
+      },
+      "value": 2
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 112,
+      "commandClassName": "Configuration",
+      "property": 9,
+      "propertyName": "Ramp Rate Off/On (Manual Control)",
+      "ccVersion": 4,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": true,
+        "label": "Ramp Rate Off/On (Manual Control)",
+        "default": 1,
+        "min": 0,
+        "max": 99,
+        "states": {
+          "0": "Instant on/off"
+        },
+        "unit": "seconds",
+        "valueSize": 1,
+        "format": 1,
+        "allowManualEntry": true,
+        "isFromConfig": true
+      },
+      "value": 1
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 112,
+      "commandClassName": "Configuration",
+      "property": 10,
+      "propertyName": "Minimum Brightness",
+      "ccVersion": 4,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": true,
+        "label": "Minimum Brightness",
+        "default": 1,
+        "min": 1,
+        "max": 99,
+        "unit": "%",
+        "valueSize": 1,
+        "format": 1,
+        "allowManualEntry": true,
+        "isFromConfig": true
+      },
+      "value": 10
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 112,
+      "commandClassName": "Configuration",
+      "property": 11,
+      "propertyName": "Maximum Brightness",
+      "ccVersion": 4,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": true,
+        "label": "Maximum Brightness",
+        "default": 99,
+        "min": 1,
+        "max": 99,
+        "unit": "%",
+        "valueSize": 1,
+        "format": 1,
+        "allowManualEntry": true,
+        "isFromConfig": true
+      },
+      "value": 99
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 112,
+      "commandClassName": "Configuration",
+      "property": 12,
+      "propertyName": "Double-Tap Upper Paddle Behavior",
+      "ccVersion": 4,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": true,
+        "label": "Double-Tap Upper Paddle Behavior",
+        "default": 0,
+        "min": 0,
+        "max": 3,
+        "states": {
+          "0": "Full brightness",
+          "1": "Configured custom brightness (parameter 18)",
+          "2": "Configured maximum brightness",
+          "3": "Double tap disabled"
+        },
+        "valueSize": 1,
+        "format": 1,
+        "allowManualEntry": false,
+        "isFromConfig": true
+      },
+      "value": 0
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 112,
+      "commandClassName": "Configuration",
+      "property": 25,
+      "propertyName": "Single-Tap Upper Paddle Behavior",
+      "ccVersion": 4,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": true,
+        "label": "Single-Tap Upper Paddle Behavior",
+        "default": 0,
+        "min": 0,
+        "max": 3,
+        "states": {
+          "0": "Previous brightness",
+          "1": "Configured custom brightness (parameter 18)",
+          "2": "Configured maximum brightness",
+          "3": "Full brightness"
+        },
+        "valueSize": 1,
+        "format": 1,
+        "allowManualEntry": false,
+        "isFromConfig": true
+      },
+      "value": 1
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 112,
+      "commandClassName": "Configuration",
+      "property": 13,
+      "propertyName": "Enable/Disable Scene Control",
+      "ccVersion": 4,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": true,
+        "description": "Enable or disable scene control functionality for quick double tap triggers.",
+        "label": "Enable/Disable Scene Control",
+        "default": 0,
+        "min": 0,
+        "max": 1,
+        "states": {
+          "0": "Disable",
+          "1": "Enable"
+        },
+        "valueSize": 1,
+        "format": 1,
+        "allowManualEntry": false,
+        "isFromConfig": true
+      },
+      "value": 0
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 112,
+      "commandClassName": "Configuration",
+      "property": 15,
+      "propertyName": "Smart Switch Mode",
+      "ccVersion": 4,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": true,
+        "label": "Smart Switch Mode",
+        "default": 1,
+        "min": 0,
+        "max": 2,
+        "states": {
+          "0": "Local control disabled",
+          "1": "Local and Z-Wave control enabled",
+          "2": "Local and Z-Wave control disabled"
+        },
+        "valueSize": 1,
+        "format": 1,
+        "allowManualEntry": false,
+        "isFromConfig": true
+      },
+      "value": 1
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 112,
+      "commandClassName": "Configuration",
+      "property": 19,
+      "propertyName": "3-Way Switch Type",
+      "ccVersion": 4,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": true,
+        "label": "3-Way Switch Type",
+        "default": 0,
+        "min": 0,
+        "max": 3,
+        "states": {
+          "0": "Toggle switch",
+          "1": "Toggle switch (with dimming control)",
+          "2": "Momentary switch (with dimming control)",
+          "3": "Momentary switch (with dimming control, but reduce after double click)"
+        },
+        "valueSize": 1,
+        "format": 0,
+        "allowManualEntry": false,
+        "isFromConfig": true
+      },
+      "value": 0
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 112,
+      "commandClassName": "Configuration",
+      "property": 20,
+      "propertyName": "Smart Bulb Mode: Dimming Reporting Behavior",
+      "ccVersion": 4,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": true,
+        "description": "Reporting behavior for local control and Z-Wave disabled / enabled, respectively. Final level for Z-Wave control actions is always sent as a Multilevel Switch report, regardless of setting.",
+        "label": "Smart Bulb Mode: Dimming Reporting Behavior",
+        "default": 0,
+        "min": 0,
+        "max": 1,
+        "states": {
+          "0": "Each brightness level / Final level",
+          "1": "Final brightness level only for local dimming, regardless of control setting"
+        },
+        "valueSize": 1,
+        "format": 0,
+        "allowManualEntry": false,
+        "isFromConfig": true
+      },
+      "value": 1
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 112,
+      "commandClassName": "Configuration",
+      "property": 21,
+      "propertyName": "Smart Bulb Mode: Reporting Behavior",
+      "ccVersion": 4,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": true,
+        "label": "Smart Bulb Mode: Reporting Behavior",
+        "default": 0,
+        "min": 0,
+        "max": 1,
+        "states": {
+          "0": "Switch always reports status and changes LED state",
+          "1": "No status or LED state change when physical (and Z-Wave) control disabled"
+        },
+        "valueSize": 1,
+        "format": 1,
+        "allowManualEntry": false,
+        "isFromConfig": true
+      },
+      "value": 0
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 112,
+      "commandClassName": "Configuration",
+      "property": 16,
+      "propertyName": "Local Dimming Speed",
+      "ccVersion": 4,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": true,
+        "description": "The time it takes to get from 0% to 100% brightness when pressing and holding the paddle.",
+        "label": "Local Dimming Speed",
+        "default": 5,
+        "min": 1,
+        "max": 99,
+        "unit": "seconds",
+        "valueSize": 1,
+        "format": 1,
+        "allowManualEntry": true,
+        "isFromConfig": true
+      },
+      "value": 3
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 112,
+      "commandClassName": "Configuration",
+      "property": 18,
+      "propertyName": "Custom Brightness Level",
+      "ccVersion": 4,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": true,
+        "label": "Custom Brightness Level",
+        "default": 0,
+        "min": 0,
+        "max": 99,
+        "states": {
+          "0": "Previous brightness"
+        },
+        "valueSize": 1,
+        "format": 1,
+        "allowManualEntry": true,
+        "isFromConfig": true
+      },
+      "value": 50
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 112,
+      "commandClassName": "Configuration",
+      "property": 22,
+      "propertyName": "Night Light Mode",
+      "ccVersion": 4,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": true,
+        "description": "Set the brightness level the dimmer will turn on to when off and when lower paddle is held down for one second.",
+        "label": "Night Light Mode",
+        "default": 20,
+        "min": 0,
+        "max": 99,
+        "states": {
+          "0": "Disable"
+        },
+        "valueSize": 1,
+        "format": 0,
+        "allowManualEntry": true,
+        "isFromConfig": true
+      },
+      "value": 25
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 112,
+      "commandClassName": "Configuration",
+      "property": 26,
+      "propertyName": "Local Programming",
+      "ccVersion": 4,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": true,
+        "description": "Controls programming (eg. including/excluding) device through physical inputs, allowing the according input sequence to be used for scene control instead. Factory reset remains available.",
+        "label": "Local Programming",
+        "default": 1,
+        "min": 0,
+        "max": 1,
+        "states": {
+          "0": "Enable",
+          "1": "Disable"
+        },
+        "valueSize": 1,
+        "format": 1,
+        "allowManualEntry": false,
+        "isFromConfig": true
+      },
+      "value": 0
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 114,
+      "commandClassName": "Manufacturer Specific",
+      "property": "manufacturerId",
+      "propertyName": "manufacturerId",
+      "ccVersion": 2,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": false,
+        "label": "Manufacturer ID",
+        "min": 0,
+        "max": 65535
+      },
+      "value": 634
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 114,
+      "commandClassName": "Manufacturer Specific",
+      "property": "productType",
+      "propertyName": "productType",
+      "ccVersion": 2,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": false,
+        "label": "Product type",
+        "min": 0,
+        "max": 65535
+      },
+      "value": 28672
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 114,
+      "commandClassName": "Manufacturer Specific",
+      "property": "productId",
+      "propertyName": "productId",
+      "ccVersion": 2,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": false,
+        "label": "Product ID",
+        "min": 0,
+        "max": 65535
+      },
+      "value": 40962
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 134,
+      "commandClassName": "Version",
+      "property": "libraryType",
+      "propertyName": "libraryType",
+      "ccVersion": 3,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": false,
+        "label": "Library type",
+        "states": {
+          "0": "Unknown",
+          "1": "Static Controller",
+          "2": "Controller",
+          "3": "Enhanced Slave",
+          "4": "Slave",
+          "5": "Installer",
+          "6": "Routing Slave",
+          "7": "Bridge Controller",
+          "8": "Device under Test",
+          "9": "N/A",
+          "10": "AV Remote",
+          "11": "AV Device"
+        }
+      },
+      "value": 3
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 134,
+      "commandClassName": "Version",
+      "property": "protocolVersion",
+      "propertyName": "protocolVersion",
+      "ccVersion": 3,
+      "metadata": {
+        "type": "string",
+        "readable": true,
+        "writeable": false,
+        "label": "Z-Wave protocol version"
+      },
+      "value": "7.13"
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 134,
+      "commandClassName": "Version",
+      "property": "firmwareVersions",
+      "propertyName": "firmwareVersions",
+      "ccVersion": 3,
+      "metadata": {
+        "type": "string[]",
+        "readable": true,
+        "writeable": false,
+        "label": "Z-Wave chip firmware versions"
+      },
+      "value": ["10.0"]
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 134,
+      "commandClassName": "Version",
+      "property": "hardwareVersion",
+      "propertyName": "hardwareVersion",
+      "ccVersion": 3,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": false,
+        "label": "Z-Wave chip hardware version"
+      },
+      "value": 1
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 134,
+      "commandClassName": "Version",
+      "property": "sdkVersion",
+      "propertyName": "sdkVersion",
+      "ccVersion": 3,
+      "metadata": {
+        "type": "string",
+        "readable": true,
+        "writeable": false,
+        "label": "SDK version"
+      },
+      "value": "7.13.9"
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 134,
+      "commandClassName": "Version",
+      "property": "applicationFrameworkAPIVersion",
+      "propertyName": "applicationFrameworkAPIVersion",
+      "ccVersion": 3,
+      "metadata": {
+        "type": "string",
+        "readable": true,
+        "writeable": false,
+        "label": "Z-Wave application framework API version"
+      },
+      "value": "10.13.9"
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 134,
+      "commandClassName": "Version",
+      "property": "applicationFrameworkBuildNumber",
+      "propertyName": "applicationFrameworkBuildNumber",
+      "ccVersion": 3,
+      "metadata": {
+        "type": "string",
+        "readable": true,
+        "writeable": false,
+        "label": "Z-Wave application framework API build number"
+      },
+      "value": 405
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 134,
+      "commandClassName": "Version",
+      "property": "hostInterfaceVersion",
+      "propertyName": "hostInterfaceVersion",
+      "ccVersion": 3,
+      "metadata": {
+        "type": "string",
+        "readable": true,
+        "writeable": false,
+        "label": "Serial API version"
+      },
+      "value": "unused"
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 134,
+      "commandClassName": "Version",
+      "property": "hostInterfaceBuildNumber",
+      "propertyName": "hostInterfaceBuildNumber",
+      "ccVersion": 3,
+      "metadata": {
+        "type": "string",
+        "readable": true,
+        "writeable": false,
+        "label": "Serial API build number"
+      },
+      "value": 0
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 134,
+      "commandClassName": "Version",
+      "property": "zWaveProtocolVersion",
+      "propertyName": "zWaveProtocolVersion",
+      "ccVersion": 3,
+      "metadata": {
+        "type": "string",
+        "readable": true,
+        "writeable": false,
+        "label": "Z-Wave protocol version"
+      },
+      "value": "7.13.9"
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 134,
+      "commandClassName": "Version",
+      "property": "zWaveProtocolBuildNumber",
+      "propertyName": "zWaveProtocolBuildNumber",
+      "ccVersion": 3,
+      "metadata": {
+        "type": "string",
+        "readable": true,
+        "writeable": false,
+        "label": "Z-Wave protocol build number"
+      },
+      "value": 405
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 134,
+      "commandClassName": "Version",
+      "property": "applicationVersion",
+      "propertyName": "applicationVersion",
+      "ccVersion": 3,
+      "metadata": {
+        "type": "string",
+        "readable": true,
+        "writeable": false,
+        "label": "Application version"
+      },
+      "value": "10.0.1"
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 134,
+      "commandClassName": "Version",
+      "property": "applicationBuildNumber",
+      "propertyName": "applicationBuildNumber",
+      "ccVersion": 3,
+      "metadata": {
+        "type": "string",
+        "readable": true,
+        "writeable": false,
+        "label": "Application build number"
+      },
+      "value": 43707
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 135,
+      "commandClassName": "Indicator",
+      "property": 80,
+      "propertyKey": 3,
+      "propertyName": "Node Identify",
+      "propertyKeyName": "On/Off Period: Duration",
+      "ccVersion": 3,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": true,
+        "description": "Sets the duration of an on/off period in 1/10th seconds. Must be set together with \"On/Off Cycle Count\"",
+        "label": "Node Identify - On/Off Period: Duration",
+        "ccSpecific": {
+          "indicatorId": 80,
+          "propertyId": 3
+        }
+      },
+      "value": 0
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 135,
+      "commandClassName": "Indicator",
+      "property": 80,
+      "propertyKey": 4,
+      "propertyName": "Node Identify",
+      "propertyKeyName": "On/Off Cycle Count",
+      "ccVersion": 3,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": true,
+        "description": "Sets the number of on/off periods. 0xff means infinite. Must be set together with \"On/Off Period duration\"",
+        "label": "Node Identify - On/Off Cycle Count",
+        "ccSpecific": {
+          "indicatorId": 80,
+          "propertyId": 4
+        }
+      },
+      "value": 0
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 135,
+      "commandClassName": "Indicator",
+      "property": 80,
+      "propertyKey": 5,
+      "propertyName": "Node Identify",
+      "propertyKeyName": "On/Off Period: On time",
+      "ccVersion": 3,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": true,
+        "description": "This property is used to set the length of the On time during an On/Off period. It allows asymmetric On/Off periods. The value 0x00 MUST represent symmetric On/Off period (On time equal to Off time)",
+        "label": "Node Identify - On/Off Period: On time",
+        "ccSpecific": {
+          "indicatorId": 80,
+          "propertyId": 5
+        }
+      },
+      "value": 0
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 135,
+      "commandClassName": "Indicator",
+      "property": "value",
+      "propertyName": "value",
+      "ccVersion": 3,
+      "metadata": {
+        "type": "number",
+        "readable": true,
+        "writeable": true,
+        "label": "Indicator value",
+        "ccSpecific": {
+          "indicatorId": 0
+        },
+        "min": 0,
+        "max": 255
+      }
+    },
+    {
+      "endpoint": 0,
+      "commandClass": 135,
+      "commandClassName": "Indicator",
+      "property": "identify",
+      "propertyName": "identify",
+      "ccVersion": 3,
+      "metadata": {
+        "type": "boolean",
+        "readable": false,
+        "writeable": true,
+        "label": "Identify"
+      }
+    }
+  ],
+  "isFrequentListening": false,
+  "maxDataRate": 100000,
+  "supportedDataRates": [40000, 100000],
+  "protocolVersion": 3,
+  "supportsBeaming": true,
+  "supportsSecurity": false,
+  "nodeType": 1,
+  "zwavePlusNodeType": 0,
+  "zwavePlusRoleType": 5,
+  "deviceClass": {
+    "basic": {
+      "key": 4,
+      "label": "Routing Slave"
+    },
+    "generic": {
+      "key": 17,
+      "label": "Multilevel Switch"
+    },
+    "specific": {
+      "key": 1,
+      "label": "Multilevel Power Switch"
+    },
+    "mandatorySupportedCCs": [32, 38, 39],
+    "mandatoryControlledCCs": []
+  },
+  "interviewStage": "Complete",
+  "deviceDatabaseUrl": "https://devices.zwave-js.io/?jumpTo=0x027a:0x7000:0xa002:10.0.1",
+  "statistics": {
+    "commandsTX": 64,
+    "commandsRX": 88,
+    "commandsDroppedRX": 0,
+    "commandsDroppedTX": 0,
+    "timeoutResponse": 0,
+    "rtt": 74.5,
+    "rssi": -60
+  },
+  "highestSecurityClass": 1,
+  "isControllerNode": false,
+  "keepAwake": false
+}
diff --git a/tests/components/zwave_js/test_api.py b/tests/components/zwave_js/test_api.py
index f8a7a68f1399e8..b3dd4e34ee876c 100644
--- a/tests/components/zwave_js/test_api.py
+++ b/tests/components/zwave_js/test_api.py
@@ -85,6 +85,8 @@
 from tests.common import MockUser
 from tests.typing import ClientSessionGenerator, WebSocketGenerator
 
+CONTROLLER_PATCH_PREFIX = "zwave_js_server.model.controller.Controller"
+
 
 def get_device(hass: HomeAssistant, node):
     """Get device ID for a node."""
@@ -131,7 +133,7 @@ async def test_network_status(
 
     # Try API call with entry ID
     with patch(
-        "zwave_js_server.model.controller.Controller.async_get_state",
+        f"{CONTROLLER_PATCH_PREFIX}.async_get_state",
         return_value=controller_state["controller"],
     ):
         await ws_client.send_json(
@@ -155,7 +157,7 @@ async def test_network_status(
     )
     assert device
     with patch(
-        "zwave_js_server.model.controller.Controller.async_get_state",
+        f"{CONTROLLER_PATCH_PREFIX}.async_get_state",
         return_value=controller_state["controller"],
     ):
         await ws_client.send_json(
@@ -410,7 +412,8 @@ async def test_node_metadata(
         "controller being inoperable or otherwise unavailable.)"
     )
     assert result["manual"] == (
-        "https://products.z-wavealliance.org/ProductManual/File?folder=&filename=MarketCertificationFiles/2479/ZP3111-5_R2_20170316.pdf"
+        "https://products.z-wavealliance.org/ProductManual/File?folder=&filename="
+        "MarketCertificationFiles/2479/ZP3111-5_R2_20170316.pdf"
     )
     assert not result["wakeup"]
     assert (
@@ -885,7 +888,7 @@ async def test_add_node(
 
     # Test FailedZWaveCommand is caught
     with patch(
-        "zwave_js_server.model.controller.Controller.async_begin_inclusion",
+        f"{CONTROLLER_PATCH_PREFIX}.async_begin_inclusion",
         side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
     ):
         await ws_client.send_json(
@@ -1155,7 +1158,7 @@ async def test_provision_smart_start_node(
 
     # Test FailedZWaveCommand is caught
     with patch(
-        "zwave_js_server.model.controller.Controller.async_provision_smart_start_node",
+        f"{CONTROLLER_PATCH_PREFIX}.async_provision_smart_start_node",
         side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
     ):
         await ws_client.send_json(
@@ -1163,7 +1166,9 @@ async def test_provision_smart_start_node(
                 ID: 7,
                 TYPE: "zwave_js/provision_smart_start_node",
                 ENTRY_ID: entry.entry_id,
-                QR_CODE_STRING: "90testtesttesttesttesttesttesttesttesttesttesttesttest",
+                QR_CODE_STRING: (
+                    "90testtesttesttesttesttesttesttesttesttesttesttesttest"
+                ),
             }
         )
         msg = await ws_client.receive_json()
@@ -1259,7 +1264,7 @@ async def test_unprovision_smart_start_node(
 
     # Test FailedZWaveCommand is caught
     with patch(
-        "zwave_js_server.model.controller.Controller.async_unprovision_smart_start_node",
+        f"{CONTROLLER_PATCH_PREFIX}.async_unprovision_smart_start_node",
         side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
     ):
         await ws_client.send_json(
@@ -1332,7 +1337,7 @@ async def test_get_provisioning_entries(
 
     # Test FailedZWaveCommand is caught
     with patch(
-        "zwave_js_server.model.controller.Controller.async_get_provisioning_entries",
+        f"{CONTROLLER_PATCH_PREFIX}.async_get_provisioning_entries",
         side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
     ):
         await ws_client.send_json(
@@ -1432,7 +1437,9 @@ async def test_parse_qr_code_string(
                 ID: 6,
                 TYPE: "zwave_js/parse_qr_code_string",
                 ENTRY_ID: entry.entry_id,
-                QR_CODE_STRING: "90testtesttesttesttesttesttesttesttesttesttesttesttest",
+                QR_CODE_STRING: (
+                    "90testtesttesttesttesttesttesttesttesttesttesttesttest"
+                ),
             }
         )
         msg = await ws_client.receive_json()
@@ -1497,7 +1504,9 @@ async def test_try_parse_dsk_from_qr_code_string(
                 ID: 6,
                 TYPE: "zwave_js/try_parse_dsk_from_qr_code_string",
                 ENTRY_ID: entry.entry_id,
-                QR_CODE_STRING: "90testtesttesttesttesttesttesttesttesttesttesttesttest",
+                QR_CODE_STRING: (
+                    "90testtesttesttesttesttesttesttesttesttesttesttesttest"
+                ),
             }
         )
         msg = await ws_client.receive_json()
@@ -1572,7 +1581,7 @@ async def test_cancel_inclusion_exclusion(
 
     # Test FailedZWaveCommand is caught
     with patch(
-        "zwave_js_server.model.controller.Controller.async_stop_inclusion",
+        f"{CONTROLLER_PATCH_PREFIX}.async_stop_inclusion",
         side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
     ):
         await ws_client.send_json(
@@ -1590,7 +1599,7 @@ async def test_cancel_inclusion_exclusion(
 
     # Test FailedZWaveCommand is caught
     with patch(
-        "zwave_js_server.model.controller.Controller.async_stop_exclusion",
+        f"{CONTROLLER_PATCH_PREFIX}.async_stop_exclusion",
         side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
     ):
         await ws_client.send_json(
@@ -1709,7 +1718,7 @@ async def test_remove_node(
 
     # Test FailedZWaveCommand is caught
     with patch(
-        "zwave_js_server.model.controller.Controller.async_begin_exclusion",
+        f"{CONTROLLER_PATCH_PREFIX}.async_begin_exclusion",
         side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
     ):
         await ws_client.send_json(
@@ -2054,7 +2063,7 @@ async def test_replace_failed_node(
 
     # Test FailedZWaveCommand is caught
     with patch(
-        "zwave_js_server.model.controller.Controller.async_replace_failed_node",
+        f"{CONTROLLER_PATCH_PREFIX}.async_replace_failed_node",
         side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
     ):
         await ws_client.send_json(
@@ -2105,7 +2114,7 @@ async def test_remove_failed_node(
 
     # Test FailedZWaveCommand is caught
     with patch(
-        "zwave_js_server.model.controller.Controller.async_remove_failed_node",
+        f"{CONTROLLER_PATCH_PREFIX}.async_remove_failed_node",
         side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
     ):
         await ws_client.send_json(
@@ -2200,7 +2209,7 @@ async def test_begin_healing_network(
 
     # Test FailedZWaveCommand is caught
     with patch(
-        "zwave_js_server.model.controller.Controller.async_begin_healing_network",
+        f"{CONTROLLER_PATCH_PREFIX}.async_begin_healing_network",
         side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
     ):
         await ws_client.send_json(
@@ -2342,7 +2351,7 @@ async def test_stop_healing_network(
 
     # Test FailedZWaveCommand is caught
     with patch(
-        "zwave_js_server.model.controller.Controller.async_stop_healing_network",
+        f"{CONTROLLER_PATCH_PREFIX}.async_stop_healing_network",
         side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
     ):
         await ws_client.send_json(
@@ -2403,7 +2412,7 @@ async def test_heal_node(
 
     # Test FailedZWaveCommand is caught
     with patch(
-        "zwave_js_server.model.controller.Controller.async_heal_node",
+        f"{CONTROLLER_PATCH_PREFIX}.async_heal_node",
         side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
     ):
         await ws_client.send_json(
@@ -3279,7 +3288,7 @@ async def test_subscribe_log_updates(
 async def test_update_log_config(
     hass: HomeAssistant, client, integration, hass_ws_client: WebSocketGenerator
 ) -> None:
-    """Test that the update_log_config WS API call works and that schema validation works."""
+    """Test that update_log_config WS API call and schema validation works."""
     entry = integration
     ws_client = await hass_ws_client(hass)
 
@@ -3841,7 +3850,7 @@ async def test_subscribe_firmware_update_status_initial_value(
     integration,
     hass_ws_client: WebSocketGenerator,
 ) -> None:
-    """Test subscribe_firmware_update_status websocket command with in progress update."""
+    """Test subscribe_firmware_update_status WS command with in progress update."""
     ws_client = await hass_ws_client(hass)
     device = get_device(hass, multisensor_6)
 
@@ -4160,7 +4169,7 @@ async def test_is_any_ota_firmware_update_in_progress(
 
     # Test FailedZWaveCommand is caught
     with patch(
-        "zwave_js_server.model.controller.Controller.async_is_any_ota_firmware_update_in_progress",
+        f"{CONTROLLER_PATCH_PREFIX}.async_is_any_ota_firmware_update_in_progress",
         side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
     ):
         await ws_client.send_json(
diff --git a/tests/components/zwave_js/test_diagnostics.py b/tests/components/zwave_js/test_diagnostics.py
index 773b799cd6f284..3f5fd6850ee7b9 100644
--- a/tests/components/zwave_js/test_diagnostics.py
+++ b/tests/components/zwave_js/test_diagnostics.py
@@ -118,7 +118,7 @@ async def test_device_diagnostics_missing_primary_value(
     integration,
     hass_client: ClientSessionGenerator,
 ) -> None:
-    """Test that the device diagnostics handles an entity with a missing primary value."""
+    """Test that device diagnostics handles an entity with a missing primary value."""
     dev_reg = async_get_dev_reg(hass)
     device = dev_reg.async_get_device({get_device_id(client.driver, multisensor_6)})
     assert device
diff --git a/tests/components/zwave_js/test_discovery.py b/tests/components/zwave_js/test_discovery.py
index 1840e4d798004c..ed636bf1289f82 100644
--- a/tests/components/zwave_js/test_discovery.py
+++ b/tests/components/zwave_js/test_discovery.py
@@ -1,6 +1,19 @@
-"""Test discovery of entities for device-specific schemas for the Z-Wave JS integration."""
+"""Test entity discovery for device-specific schemas for the Z-Wave JS integration."""
 import pytest
 
+from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
+from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS
+from homeassistant.components.number import (
+    ATTR_VALUE,
+    DOMAIN as NUMBER_DOMAIN,
+    SERVICE_SET_VALUE,
+)
+from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
+from homeassistant.components.switch import (
+    DOMAIN as SWITCH_DOMAIN,
+    SERVICE_TURN_OFF,
+    SERVICE_TURN_ON,
+)
 from homeassistant.components.zwave_js.discovery import (
     FirmwareVersionRange,
     ZWaveDiscoverySchema,
@@ -9,6 +22,7 @@
 from homeassistant.components.zwave_js.discovery_data_template import (
     DynamicCurrentTempClimateDataTemplate,
 )
+from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_UNKNOWN, EntityCategory
 from homeassistant.core import HomeAssistant
 from homeassistant.helpers import entity_registry as er
 
@@ -141,3 +155,141 @@ async def test_merten_507801_disabled_enitites(
         )
         assert updated_entry != entry
         assert updated_entry.disabled is False
+
+
+async def test_zooz_zen72(
+    hass: HomeAssistant, client, switch_zooz_zen72, integration
+) -> None:
+    """Test that Zooz ZEN72 Indicators are discovered as number entities."""
+    ent_reg = er.async_get(hass)
+    assert len(hass.states.async_entity_ids(NUMBER_DOMAIN)) == 1
+    assert len(hass.states.async_entity_ids(BUTTON_DOMAIN)) == 2  # includes ping
+    entity_id = "number.z_wave_plus_700_series_dimmer_switch_indicator_value"
+    entry = ent_reg.async_get(entity_id)
+    assert entry
+    assert entry.entity_category == EntityCategory.DIAGNOSTIC
+    state = hass.states.get(entity_id)
+    assert state
+    assert state.state == STATE_UNKNOWN
+    await hass.services.async_call(
+        NUMBER_DOMAIN,
+        SERVICE_SET_VALUE,
+        {
+            ATTR_ENTITY_ID: entity_id,
+            ATTR_VALUE: 5,
+        },
+        blocking=True,
+    )
+    assert len(client.async_send_command.call_args_list) == 1
+    args = client.async_send_command.call_args[0][0]
+    assert args["command"] == "node.set_value"
+    assert args["nodeId"] == switch_zooz_zen72.node_id
+    assert args["valueId"] == {
+        "commandClass": 135,
+        "endpoint": 0,
+        "property": "value",
+    }
+    assert args["value"] == 5
+
+    client.async_send_command.reset_mock()
+
+    entity_id = "button.z_wave_plus_700_series_dimmer_switch_identify"
+    entry = ent_reg.async_get(entity_id)
+    assert entry
+    assert entry.entity_category == EntityCategory.DIAGNOSTIC
+    await hass.services.async_call(
+        BUTTON_DOMAIN,
+        SERVICE_PRESS,
+        {ATTR_ENTITY_ID: entity_id},
+        blocking=True,
+    )
+    assert len(client.async_send_command.call_args_list) == 1
+    args = client.async_send_command.call_args[0][0]
+    assert args["command"] == "node.set_value"
+    assert args["nodeId"] == switch_zooz_zen72.node_id
+    assert args["valueId"] == {
+        "commandClass": 135,
+        "endpoint": 0,
+        "property": "identify",
+    }
+    assert args["value"] is True
+
+
+async def test_indicator_test(
+    hass: HomeAssistant, client, indicator_test, integration
+) -> None:
+    """Test that Indicators are discovered properly.
+
+    This test covers indicators that we don't already have device fixtures for.
+    """
+    ent_reg = er.async_get(hass)
+    assert len(hass.states.async_entity_ids(NUMBER_DOMAIN)) == 0
+    assert len(hass.states.async_entity_ids(BUTTON_DOMAIN)) == 1  # only ping
+    assert len(hass.states.async_entity_ids(BINARY_SENSOR_DOMAIN)) == 1
+    assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 2  # include node status
+    assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 1
+
+    entity_id = "binary_sensor.this_is_a_fake_device_binary_sensor"
+    entry = ent_reg.async_get(entity_id)
+    assert entry
+    assert entry.entity_category == EntityCategory.DIAGNOSTIC
+    state = hass.states.get(entity_id)
+    assert state
+    assert state.state == STATE_OFF
+
+    client.async_send_command.reset_mock()
+
+    entity_id = "sensor.this_is_a_fake_device_sensor"
+    entry = ent_reg.async_get(entity_id)
+    assert entry
+    assert entry.entity_category == EntityCategory.DIAGNOSTIC
+    state = hass.states.get(entity_id)
+    assert state
+    assert state.state == "0.0"
+
+    client.async_send_command.reset_mock()
+
+    entity_id = "switch.this_is_a_fake_device_switch"
+    entry = ent_reg.async_get(entity_id)
+    assert entry
+    assert entry.entity_category == EntityCategory.DIAGNOSTIC
+    state = hass.states.get(entity_id)
+    assert state
+    assert state.state == STATE_OFF
+    await hass.services.async_call(
+        SWITCH_DOMAIN,
+        SERVICE_TURN_ON,
+        {ATTR_ENTITY_ID: entity_id},
+        blocking=True,
+    )
+    assert len(client.async_send_command.call_args_list) == 1
+    args = client.async_send_command.call_args[0][0]
+    assert args["command"] == "node.set_value"
+    assert args["nodeId"] == indicator_test.node_id
+    assert args["valueId"] == {
+        "commandClass": 135,
+        "endpoint": 0,
+        "property": "Test",
+        "propertyKey": "Switch",
+    }
+    assert args["value"] is True
+
+    client.async_send_command.reset_mock()
+
+    await hass.services.async_call(
+        SWITCH_DOMAIN,
+        SERVICE_TURN_OFF,
+        {ATTR_ENTITY_ID: entity_id},
+        blocking=True,
+    )
+    assert len(client.async_send_command.call_args_list) == 1
+    args = client.async_send_command.call_args[0][0]
+    assert args["command"] == "node.set_value"
+    assert args["nodeId"] == indicator_test.node_id
+    assert args["valueId"] == {
+        "commandClass": 135,
+        "endpoint": 0,
+        "property": "Test",
+        "propertyKey": "Switch",
+    }
+    assert args["value"] is False
diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py
index 62513c3ad87791..7d7d9b45d068b8 100644
--- a/tests/components/zwave_js/test_init.py
+++ b/tests/components/zwave_js/test_init.py
@@ -101,7 +101,7 @@ async def test_disabled_statistics(hass: HomeAssistant, client) -> None:
 
 
 async def test_noop_statistics(hass: HomeAssistant, client) -> None:
-    """Test that we don't make any statistics calls if user hasn't provided preference."""
+    """Test that we don't make statistics calls if user hasn't set preference."""
     entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"})
     entry.add_to_hass(hass)
 
@@ -1332,7 +1332,7 @@ async def test_node_model_change(
 async def test_disabled_node_status_entity_on_node_replaced(
     hass: HomeAssistant, zp3111_state, zp3111, client, integration
 ) -> None:
-    """Test that when a node replacement event is received the node status sensor is removed."""
+    """Test when node replacement event is received, node status sensor is removed."""
     node_status_entity = "sensor.4_in_1_sensor_node_status"
     state = hass.states.get(node_status_entity)
     assert state
diff --git a/tests/components/zwave_js/test_sensor.py b/tests/components/zwave_js/test_sensor.py
index e41158a5d1d396..f0b2f54d4bd1a2 100644
--- a/tests/components/zwave_js/test_sensor.py
+++ b/tests/components/zwave_js/test_sensor.py
@@ -44,7 +44,6 @@
     ENERGY_SENSOR,
     HUMIDITY_SENSOR,
     ID_LOCK_CONFIG_PARAMETER_SENSOR,
-    INDICATOR_SENSOR,
     METER_ENERGY_SENSOR,
     NOTIFICATION_MOTION_SENSOR,
     POWER_SENSOR,
@@ -166,18 +165,6 @@ async def test_disabled_notification_sensor(
     assert state.attributes["value"] == 8
 
 
-async def test_disabled_indcator_sensor(
-    hass: HomeAssistant, climate_radio_thermostat_ct100_plus, integration
-) -> None:
-    """Test sensor is created from Indicator CC and is disabled."""
-    ent_reg = er.async_get(hass)
-    entity_entry = ent_reg.async_get(INDICATOR_SENSOR)
-
-    assert entity_entry
-    assert entity_entry.disabled
-    assert entity_entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION
-
-
 async def test_config_parameter_sensor(
     hass: HomeAssistant, lock_id_lock_as_id150, integration
 ) -> None:


Code review comments:
diff_hunk: @@ -782,14 +796,48 @@ def get_config_parameter_discovery_schema(
                 property={TARGET_VALUE_PROPERTY},
             )
         ],
-        data_template=NumericSensorDataTemplate(),
         entity_registry_enabled_default=False,
     ),
+    # number for Indicator CC (exclude property keys 3-5)
+    ZWaveDiscoverySchema(
+        platform=Platform.NUMBER,
+        primary_value=ZWaveValueDiscoverySchema(
+            command_class={CommandClass.INDICATOR},
+            type={ValueType.NUMBER},
+            not_property_key={3, 4, 5},
+            readable=True,
+            writeable=True,
+        ),
+        entity_category=EntityCategory.DIAGNOSTIC,
+    ),
+    # button for Indicator CC
+    ZWaveDiscoverySchema(
+        platform=Platform.BUTTON,
+        primary_value=ZWaveValueDiscoverySchema(
+            command_class={CommandClass.INDICATOR},
+            type={ValueType.BOOLEAN},
+            readable=False,
+            writeable=True,
+        ),
+        entity_category=EntityCategory.DIAGNOSTIC,
path: homeassistant/components/zwave_js/discovery.py
text: @user1:
```suggestion
        entity_category=EntityCategory.CONFIG,
```

diff_hunk: @@ -782,14 +796,48 @@ def get_config_parameter_discovery_schema(
                 property={TARGET_VALUE_PROPERTY},
             )
         ],
-        data_template=NumericSensorDataTemplate(),
         entity_registry_enabled_default=False,
     ),
+    # number for Indicator CC (exclude property keys 3-5)
+    ZWaveDiscoverySchema(
+        platform=Platform.NUMBER,
+        primary_value=ZWaveValueDiscoverySchema(
+            command_class={CommandClass.INDICATOR},
+            type={ValueType.NUMBER},
+            not_property_key={3, 4, 5},
+            readable=True,
+            writeable=True,
+        ),
+        entity_category=EntityCategory.DIAGNOSTIC,
path: homeassistant/components/zwave_js/discovery.py
text: @user1:
```suggestion
        entity_category=EntityCategory.CONFIG,
```

diff_hunk: @@ -782,14 +796,48 @@ def get_config_parameter_discovery_schema(
                 property={TARGET_VALUE_PROPERTY},
             )
         ],
-        data_template=NumericSensorDataTemplate(),
         entity_registry_enabled_default=False,
     ),
+    # number for Indicator CC (exclude property keys 3-5)
+    ZWaveDiscoverySchema(
+        platform=Platform.NUMBER,
+        primary_value=ZWaveValueDiscoverySchema(
+            command_class={CommandClass.INDICATOR},
+            type={ValueType.NUMBER},
+            not_property_key={3, 4, 5},
+            readable=True,
+            writeable=True,
+        ),
+        entity_category=EntityCategory.DIAGNOSTIC,
+    ),
+    # button for Indicator CC
+    ZWaveDiscoverySchema(
+        platform=Platform.BUTTON,
+        primary_value=ZWaveValueDiscoverySchema(
+            command_class={CommandClass.INDICATOR},
+            type={ValueType.BOOLEAN},
+            readable=False,
+            writeable=True,
+        ),
+        entity_category=EntityCategory.DIAGNOSTIC,
+    ),
     # binary switches
     ZWaveDiscoverySchema(
         platform=Platform.SWITCH,
         primary_value=SWITCH_BINARY_CURRENT_VALUE_SCHEMA,
     ),
+    # switch for Indicator CC
+    ZWaveDiscoverySchema(
+        platform=Platform.SWITCH,
+        hint="indicator",
+        primary_value=ZWaveValueDiscoverySchema(
+            command_class={CommandClass.INDICATOR},
+            type={ValueType.BOOLEAN},
+            readable=True,
+            writeable=True,
+        ),
+        entity_category=EntityCategory.DIAGNOSTIC,
path: homeassistant/components/zwave_js/discovery.py
text: @user1:
```suggestion
        entity_category=EntityCategory.CONFIG,
```