Add zwave_mqtt sensor platform (#35135)

* Add sensor platform

* Fix signal name

* Add sensor discovery schema

* Add test for disabled entities

* Add test for enabling advanced sensor

* Add additional fake sensors and tests for device classes

* More device class tests

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
Charles Garwood 2020-05-04 22:07:29 -04:00 committed by GitHub
parent a3071571ac
commit de636cce6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 288 additions and 1 deletions

View File

@ -1,9 +1,10 @@
"""Constants for the zwave_mqtt integration.""" """Constants for the zwave_mqtt integration."""
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
DOMAIN = "zwave_mqtt" DOMAIN = "zwave_mqtt"
DATA_UNSUBSCRIBE = "unsubscribe" DATA_UNSUBSCRIBE = "unsubscribe"
PLATFORMS = [SWITCH_DOMAIN] PLATFORMS = [SENSOR_DOMAIN, SWITCH_DOMAIN]
# MQTT Topics # MQTT Topics
TOPIC_OPENZWAVE = "OpenZWave" TOPIC_OPENZWAVE = "OpenZWave"

View File

@ -5,6 +5,30 @@ from openzwavemqtt.const import CommandClass, ValueGenre, ValueType
from . import const from . import const
DISCOVERY_SCHEMAS = ( 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 { # Switch platform
const.DISC_COMPONENT: "switch", const.DISC_COMPONENT: "switch",
const.DISC_GENERIC_DEVICE_CLASS: ( const.DISC_GENERIC_DEVICE_CLASS: (

View File

@ -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

View File

@ -38,3 +38,14 @@ async def switch_msg_fixture(hass):
message = MQTTMessage(topic=switch_json["topic"], payload=switch_json["payload"]) message = MQTTMessage(topic=switch_json["topic"], payload=switch_json["payload"])
message.encode() message.encode()
return message 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

View File

@ -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"

View File

@ -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/,{ "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/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/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/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/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/,{ "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} 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}

Can't render this file because it contains an unexpected character in line 1 and column 26.

38
tests/fixtures/zwave_mqtt/sensor.json vendored Normal file
View File

@ -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
}
}