Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion homeassistant/components/zwave_mqtt/const.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"""Constants for the zwave_mqtt integration."""
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN

DOMAIN = "zwave_mqtt"
DATA_UNSUBSCRIBE = "unsubscribe"
PLATFORMS = [SWITCH_DOMAIN]
PLATFORMS = [SENSOR_DOMAIN, SWITCH_DOMAIN]

# MQTT Topics
TOPIC_OPENZWAVE = "OpenZWave"
Expand Down
24 changes: 24 additions & 0 deletions homeassistant/components/zwave_mqtt/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,30 @@
from . import const

DISCOVERY_SCHEMAS = (
{ # All other text/numeric sensors
const.DISC_COMPONENT: "sensor",
const.DISC_VALUES: {
const.DISC_PRIMARY: {
const.DISC_COMMAND_CLASS: (
CommandClass.SENSOR_MULTILEVEL,
CommandClass.METER,
CommandClass.ALARM,
CommandClass.SENSOR_ALARM,
CommandClass.INDICATOR,
CommandClass.BATTERY,
CommandClass.NOTIFICATION,
CommandClass.BASIC,
),
const.DISC_TYPE: (
ValueType.DECIMAL,
ValueType.INT,
ValueType.STRING,
ValueType.BYTE,
ValueType.LIST,
),
}
},
},
{ # Switch platform
const.DISC_COMPONENT: "switch",
const.DISC_GENERIC_DEVICE_CLASS: (
Expand Down
131 changes: 131 additions & 0 deletions homeassistant/components/zwave_mqtt/sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
"""Representation of Z-Wave sensors."""

import logging

from openzwavemqtt.const import CommandClass

from homeassistant.components.sensor import (
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_ILLUMINANCE,
DEVICE_CLASS_POWER,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
DOMAIN as SENSOR_DOMAIN,
)
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect

from .const import DATA_UNSUBSCRIBE, DOMAIN
from .entity import ZWaveDeviceEntity

_LOGGER = logging.getLogger(__name__)


async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Z-Wave sensor from config entry."""

@callback
def async_add_sensor(value):
"""Add Z-Wave Sensor."""
# Basic Sensor types
if isinstance(value.primary.value, (float, int)):
sensor = ZWaveNumericSensor(value)

elif isinstance(value.primary.value, dict):
sensor = ZWaveListSensor(value)

else:
_LOGGER.warning("Sensor not implemented for value %s", value.primary.label)
return

async_add_entities([sensor])

hass.data[DOMAIN][config_entry.entry_id][DATA_UNSUBSCRIBE].append(
async_dispatcher_connect(
hass, f"{DOMAIN}_new_{SENSOR_DOMAIN}", async_add_sensor
)
)


class ZwaveSensorBase(ZWaveDeviceEntity):
"""Basic Representation of a Z-Wave sensor."""

@property
def device_class(self):
"""Return the device class of the sensor."""
if self.values.primary.command_class == CommandClass.BATTERY:
return DEVICE_CLASS_BATTERY
if self.values.primary.command_class == CommandClass.METER:
return DEVICE_CLASS_POWER
if "Temperature" in self.values.primary.label:
return DEVICE_CLASS_TEMPERATURE
if "Illuminance" in self.values.primary.label:
return DEVICE_CLASS_ILLUMINANCE
if "Humidity" in self.values.primary.label:
return DEVICE_CLASS_HUMIDITY
if "Power" in self.values.primary.label:
return DEVICE_CLASS_POWER
if "Energy" in self.values.primary.label:
return DEVICE_CLASS_POWER
if "Electric" in self.values.primary.label:
return DEVICE_CLASS_POWER
if "Pressure" in self.values.primary.label:
return DEVICE_CLASS_PRESSURE
return None

@property
def entity_registry_enabled_default(self) -> bool:
"""Return if the entity should be enabled when first added to the entity registry."""
# We hide some of the more advanced sensors by default to not overwhelm users
if self.values.primary.command_class in [
CommandClass.BASIC,
CommandClass.INDICATOR,
CommandClass.NOTIFICATION,
]:
return False
return True


class ZWaveNumericSensor(ZwaveSensorBase):
"""Representation of a Z-Wave sensor."""

@property
def state(self):
"""Return state of the sensor."""
return round(self.values.primary.value, 2)

@property
def unit_of_measurement(self):
"""Return unit of measurement the value is expressed in."""
if self.values.primary.units == "C":
return TEMP_CELSIUS
if self.values.primary.units == "F":
return TEMP_FAHRENHEIT

return self.values.primary.units


class ZWaveListSensor(ZwaveSensorBase):
"""Representation of a Z-Wave list sensor."""

@property
def state(self):
"""Return the state of the sensor."""
# We use the id as value for backwards compatibility
return self.values.primary.value["Selected_id"]

@property
def device_state_attributes(self):
"""Return the device specific state attributes."""
attributes = super().device_state_attributes
# add the value's label as property
attributes["label"] = self.values.primary.value["Selected"]
return attributes

@property
def entity_registry_enabled_default(self) -> bool:
"""Return if the entity should be enabled when first added to the entity registry."""
# these sensors are only here for backwards compatibility, disable them by default
return False
11 changes: 11 additions & 0 deletions tests/components/zwave_mqtt/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,14 @@ async def switch_msg_fixture(hass):
message = MQTTMessage(topic=switch_json["topic"], payload=switch_json["payload"])
message.encode()
return message


@pytest.fixture(name="sensor_msg")
async def sensor_msg_fixture(hass):
"""Return a mock MQTT msg with a sensor change message."""
sensor_json = json.loads(
await hass.async_add_executor_job(load_fixture, "zwave_mqtt/sensor.json")
)
message = MQTTMessage(topic=sensor_json["topic"], payload=sensor_json["payload"])
message.encode()
return message
76 changes: 76 additions & 0 deletions tests/components/zwave_mqtt/test_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""Test Z-Wave Sensors."""
from homeassistant.components.sensor import (
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_POWER,
DEVICE_CLASS_PRESSURE,
DOMAIN as SENSOR_DOMAIN,
)
from homeassistant.components.zwave_mqtt.const import DOMAIN
from homeassistant.const import ATTR_DEVICE_CLASS

from .common import setup_zwave


async def test_sensor(hass, generic_data):
"""Test setting up config entry."""
await setup_zwave(hass, fixture=generic_data)

# Test standard sensor
state = hass.states.get("sensor.smart_plug_electric_v")
assert state is not None
assert state.state == "123.9"
assert state.attributes["unit_of_measurement"] == "V"

# Test device classes
state = hass.states.get("sensor.trisensor_relative_humidity")
assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_HUMIDITY
state = hass.states.get("sensor.trisensor_pressure")
assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_PRESSURE
state = hass.states.get("sensor.trisensor_fake_power")
assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_POWER
state = hass.states.get("sensor.trisensor_fake_energy")
assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_POWER
state = hass.states.get("sensor.trisensor_fake_electric")
assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_POWER

# Test ZWaveListSensor disabled by default
registry = await hass.helpers.entity_registry.async_get_registry()
entity_id = "sensor.water_sensor_6_instance_1_water"
state = hass.states.get(entity_id)
assert state is None

entry = registry.async_get(entity_id)
assert entry
assert entry.disabled
assert entry.disabled_by == "integration"

# Test enabling entity
updated_entry = registry.async_update_entity(
entry.entity_id, **{"disabled_by": None}
)
assert updated_entry != entry
assert updated_entry.disabled is False


async def test_sensor_enabled(hass, generic_data, sensor_msg):
"""Test enabling an advanced sensor."""

registry = await hass.helpers.entity_registry.async_get_registry()

entry = registry.async_get_or_create(
SENSOR_DOMAIN,
DOMAIN,
"1-36-1407375493578772",
suggested_object_id="water_sensor_6_instance_1_water",
disabled_by=None,
)
assert entry.disabled is False

receive_msg = await setup_zwave(hass, fixture=generic_data)
receive_msg(sensor_msg)
await hass.async_block_till_done()

state = hass.states.get(entry.entity_id)
assert state is not None
assert state.state == "0"
assert state.attributes["label"] == "Clear"
6 changes: 6 additions & 0 deletions tests/fixtures/zwave_mqtt/generic_network_dump.csv
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,13 @@ OpenZWave/1/node/37/instance/1/commandclass/48/value/625737744/,{ "Label": "S
OpenZWave/1/node/37/instance/1/commandclass/49/,{ "Instance": 1, "CommandClassId": 49, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "TimeStamp": 1579566891}
OpenZWave/1/node/37/instance/1/commandclass/49/value/281475602464786/,{ "Label": "Air Temperature", "Value": 20.700000762939454, "Units": "C", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "Index": 1, "Node": 37, "Genre": "User", "Help": "Air Temperature Sensor Value", "ValueIDKey": 281475602464786, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891}
OpenZWave/1/node/37/instance/1/commandclass/49/value/844425555886098/,{ "Label": "Illuminance", "Value": 0.0, "Units": "Lux", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "Index": 3, "Node": 37, "Genre": "User", "Help": "Luminance Sensor Value", "ValueIDKey": 844425555886098, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891}
OpenZWave/1/node/37/instance/1/commandclass/49/value/1234567890/,{ "Label": "Relative Humidity", "Value": 56.7, "Units": "%", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "Index": 3, "Node": 37, "Genre": "User", "Help": "Humidity Sensor Value", "ValueIDKey": 1234567890, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891}
OpenZWave/1/node/37/instance/1/commandclass/49/value/12345678901/,{ "Label": "Pressure", "Value": 123, "Units": "inHg", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "Index": 8, "Node": 37, "Genre": "User", "Help": "Pressure Sensor Value", "ValueIDKey": 12345678901, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891}
OpenZWave/1/node/37/instance/1/commandclass/49/value/72057594672070676/,{ "Label": "Air Temperature Units", "Value": { "List": [ { "Value": 0, "Label": "Celsius" }, { "Value": 1, "Label": "Fahrenheit" } ], "Selected": "Celsius" }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "Index": 256, "Node": 37, "Genre": "System", "Help": "Air Temperature Sensor Available Units", "ValueIDKey": 72057594672070676, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891}
OpenZWave/1/node/37/instance/1/commandclass/49/value/12345678902/,{ "Label": "Fake Power", "Value": 123, "Units": "W", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "Index": 90, "Node": 37, "Genre": "User", "Help": "Power Sensor Value", "ValueIDKey": 12345678902, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891}
OpenZWave/1/node/37/instance/1/commandclass/49/value/12345678903/,{ "Label": "Fake Energy", "Value": 456, "Units": "W", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "Index": 91, "Node": 37, "Genre": "User", "Help": "Energy Sensor Value", "ValueIDKey": 12345678903, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891}
OpenZWave/1/node/37/instance/1/commandclass/49/value/12345678904/,{ "Label": "Fake Electric", "Value": 789, "Units": "W", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "Index": 92, "Node": 37, "Genre": "User", "Help": "Electric Sensor Value", "ValueIDKey": 12345678904, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891}
OpenZWave/1/node/37/instance/1/commandclass/49/value/12345678905/,{ "Label": "Fake String", "Value": "fake", "Units": "", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "Index": 8, "Node": 37, "Genre": "User", "Help": "Fake String Sensor Value", "ValueIDKey": 12345678901, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891}
OpenZWave/1/node/37/instance/1/commandclass/49/value/72620544625491988/,{ "Label": "Illuminance Units", "Value": { "List": [ { "Value": 1, "Label": "Lux" } ], "Selected": "Lux" }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "Index": 258, "Node": 37, "Genre": "System", "Help": "Luminance Sensor Available Units", "ValueIDKey": 72620544625491988, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891}
OpenZWave/1/node/37/instance/1/commandclass/94/,{ "Instance": 1, "CommandClassId": 94, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "TimeStamp": 1579566891}
OpenZWave/1/node/37/instance/1/commandclass/94/value/634880017/,{ "Label": "ZWave+ Version", "Value": 1, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 0, "Node": 37, "Genre": "System", "Help": "ZWave+ Version Supported on the Device", "ValueIDKey": 634880017, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891}
Expand Down
38 changes: 38 additions & 0 deletions tests/fixtures/zwave_mqtt/sensor.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"topic": "OpenZWave/1/node/36/instance/1/commandclass/113/value/1407375493578772/",
"payload": {
"Label": "Instance 1: Water",
"Value": {
"List": [
{
"Value": 0,
"Label": "Clear"
},
{
"Value": 2,
"Label": "Water Leak at Unknown Location"
}
],
"Selected": "Clear",
"Selected_id": 0
},
"Units": "",
"Min": 0,
"Max": 0,
"Type": "List",
"Instance": 1,
"CommandClass": "COMMAND_CLASS_NOTIFICATION",
"Index": 5,
"Node": 36,
"Genre": "User",
"Help": "Water Alerts",
"ValueIDKey": 1407375493578772,
"ReadOnly": false,
"WriteOnly": false,
"ValueSet": false,
"ValuePolled": false,
"ChangeVerified": false,
"Event": "valueAdded",
"TimeStamp": 1579566891
}
}