Add ozw garage door barrier support (#37316)
This commit is contained in:
parent
4679e670f1
commit
f6df85f8ce
6 changed files with 223 additions and 1 deletions
|
@ -1,8 +1,11 @@
|
||||||
"""Support for Z-Wave cover devices."""
|
"""Support for Z-Wave cover devices."""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from openzwavemqtt.const import CommandClass
|
||||||
|
|
||||||
from homeassistant.components.cover import (
|
from homeassistant.components.cover import (
|
||||||
ATTR_POSITION,
|
ATTR_POSITION,
|
||||||
|
DEVICE_CLASS_GARAGE,
|
||||||
DOMAIN as COVER_DOMAIN,
|
DOMAIN as COVER_DOMAIN,
|
||||||
SUPPORT_CLOSE,
|
SUPPORT_CLOSE,
|
||||||
SUPPORT_OPEN,
|
SUPPORT_OPEN,
|
||||||
|
@ -19,6 +22,8 @@ _LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
SUPPORTED_FEATURES_POSITION = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION
|
SUPPORTED_FEATURES_POSITION = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION
|
||||||
|
SUPPORT_GARAGE = SUPPORT_OPEN | SUPPORT_CLOSE
|
||||||
|
VALUE_SELECTED_ID = "Selected_id"
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
|
@ -27,8 +32,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
@callback
|
@callback
|
||||||
def async_add_cover(values):
|
def async_add_cover(values):
|
||||||
"""Add Z-Wave Cover."""
|
"""Add Z-Wave Cover."""
|
||||||
|
if values.primary.command_class == CommandClass.BARRIER_OPERATOR:
|
||||||
|
cover = ZwaveGarageDoorBarrier(values)
|
||||||
|
else:
|
||||||
|
cover = ZWaveCoverEntity(values)
|
||||||
|
|
||||||
async_add_entities([ZWaveCoverEntity(values)])
|
async_add_entities([cover])
|
||||||
|
|
||||||
hass.data[DOMAIN][config_entry.entry_id][DATA_UNSUBSCRIBE].append(
|
hass.data[DOMAIN][config_entry.entry_id][DATA_UNSUBSCRIBE].append(
|
||||||
async_dispatcher_connect(hass, f"{DOMAIN}_new_{COVER_DOMAIN}", async_add_cover)
|
async_dispatcher_connect(hass, f"{DOMAIN}_new_{COVER_DOMAIN}", async_add_cover)
|
||||||
|
@ -74,3 +83,40 @@ class ZWaveCoverEntity(ZWaveDeviceEntity, CoverEntity):
|
||||||
async def async_close_cover(self, **kwargs):
|
async def async_close_cover(self, **kwargs):
|
||||||
"""Close cover."""
|
"""Close cover."""
|
||||||
self.values.primary.send_value(0)
|
self.values.primary.send_value(0)
|
||||||
|
|
||||||
|
|
||||||
|
class ZwaveGarageDoorBarrier(ZWaveDeviceEntity, CoverEntity):
|
||||||
|
"""Representation of a barrier operator Zwave garage door device."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supported_features(self):
|
||||||
|
"""Flag supported features."""
|
||||||
|
return SUPPORT_GARAGE
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_class(self):
|
||||||
|
"""Return the class of this device, from component DEVICE_CLASSES."""
|
||||||
|
return DEVICE_CLASS_GARAGE
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_opening(self):
|
||||||
|
"""Return true if cover is in an opening state."""
|
||||||
|
return self.values.primary.value[VALUE_SELECTED_ID] == 3
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_closing(self):
|
||||||
|
"""Return true if cover is in a closing state."""
|
||||||
|
return self.values.primary.value[VALUE_SELECTED_ID] == 1
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_closed(self):
|
||||||
|
"""Return the current position of Zwave garage door."""
|
||||||
|
return self.values.primary.value[VALUE_SELECTED_ID] == 0
|
||||||
|
|
||||||
|
def close_cover(self, **kwargs):
|
||||||
|
"""Close the garage door."""
|
||||||
|
self.values.primary.send_value(0)
|
||||||
|
|
||||||
|
def open_cover(self, **kwargs):
|
||||||
|
"""Open the garage door."""
|
||||||
|
self.values.primary.send_value(4)
|
||||||
|
|
|
@ -160,6 +160,19 @@ DISCOVERY_SCHEMAS = (
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{ # Garage Door Barrier
|
||||||
|
const.DISC_COMPONENT: "cover",
|
||||||
|
const.DISC_GENERIC_DEVICE_CLASS: (const_ozw.GENERIC_TYPE_ENTRY_CONTROL,),
|
||||||
|
const.DISC_SPECIFIC_DEVICE_CLASS: (
|
||||||
|
const_ozw.SPECIFIC_TYPE_SECURE_BARRIER_ADDON,
|
||||||
|
),
|
||||||
|
const.DISC_VALUES: {
|
||||||
|
const.DISC_PRIMARY: {
|
||||||
|
const.DISC_COMMAND_CLASS: CommandClass.BARRIER_OPERATOR,
|
||||||
|
const.DISC_INDEX: ValueIndex.BARRIER_OPERATOR_LABEL,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{ # Fan
|
{ # Fan
|
||||||
const.DISC_COMPONENT: "fan",
|
const.DISC_COMPONENT: "fan",
|
||||||
const.DISC_GENERIC_DEVICE_CLASS: const_ozw.GENERIC_TYPE_SWITCH_MULTILEVEL,
|
const.DISC_GENERIC_DEVICE_CLASS: const_ozw.GENERIC_TYPE_SWITCH_MULTILEVEL,
|
||||||
|
|
|
@ -33,6 +33,12 @@ def cover_data_fixture():
|
||||||
return load_fixture("ozw/cover_network_dump.csv")
|
return load_fixture("ozw/cover_network_dump.csv")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="cover_gdo_data", scope="session")
|
||||||
|
def cover_gdo_data_fixture():
|
||||||
|
"""Load cover_gdo MQTT data and return it."""
|
||||||
|
return load_fixture("ozw/cover_gdo_network_dump.csv")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="climate_data", scope="session")
|
@pytest.fixture(name="climate_data", scope="session")
|
||||||
def climate_data_fixture():
|
def climate_data_fixture():
|
||||||
"""Load climate MQTT data and return it."""
|
"""Load climate MQTT data and return it."""
|
||||||
|
@ -136,6 +142,17 @@ async def cover_msg_fixture(hass):
|
||||||
return message
|
return message
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="cover_gdo_msg")
|
||||||
|
async def cover_gdo_msg_fixture(hass):
|
||||||
|
"""Return a mock MQTT msg with a cover barrier state change message."""
|
||||||
|
sensor_json = json.loads(
|
||||||
|
await hass.async_add_executor_job(load_fixture, "ozw/cover_gdo.json")
|
||||||
|
)
|
||||||
|
message = MQTTMessage(topic=sensor_json["topic"], payload=sensor_json["payload"])
|
||||||
|
message.encode()
|
||||||
|
return message
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="climate_msg")
|
@pytest.fixture(name="climate_msg")
|
||||||
async def climate_msg_fixture(hass):
|
async def climate_msg_fixture(hass):
|
||||||
"""Return a mock MQTT msg with a climate mode change message."""
|
"""Return a mock MQTT msg with a climate mode change message."""
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
"""Test Z-Wave Covers."""
|
"""Test Z-Wave Covers."""
|
||||||
from homeassistant.components.cover import ATTR_CURRENT_POSITION
|
from homeassistant.components.cover import ATTR_CURRENT_POSITION
|
||||||
|
from homeassistant.components.ozw.cover import VALUE_SELECTED_ID
|
||||||
|
|
||||||
from .common import setup_ozw
|
from .common import setup_ozw
|
||||||
|
|
||||||
|
VALUE_ID = "Value"
|
||||||
|
|
||||||
|
|
||||||
async def test_cover(hass, cover_data, sent_messages, cover_msg):
|
async def test_cover(hass, cover_data, sent_messages, cover_msg):
|
||||||
"""Test setting up config entry."""
|
"""Test setting up config entry."""
|
||||||
|
@ -84,3 +87,47 @@ async def test_cover(hass, cover_data, sent_messages, cover_msg):
|
||||||
msg = sent_messages[4]
|
msg = sent_messages[4]
|
||||||
assert msg["topic"] == "OpenZWave/1/command/setvalue/"
|
assert msg["topic"] == "OpenZWave/1/command/setvalue/"
|
||||||
assert msg["payload"] == {"Value": 0, "ValueIDKey": 625573905}
|
assert msg["payload"] == {"Value": 0, "ValueIDKey": 625573905}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_barrier(hass, cover_gdo_data, sent_messages, cover_gdo_msg):
|
||||||
|
"""Test setting up config entry."""
|
||||||
|
receive_message = await setup_ozw(hass, fixture=cover_gdo_data)
|
||||||
|
# Test loaded
|
||||||
|
state = hass.states.get("cover.gd00z_4_barrier_state")
|
||||||
|
assert state is not None
|
||||||
|
assert state.state == "closed"
|
||||||
|
|
||||||
|
# Test opening
|
||||||
|
await hass.services.async_call(
|
||||||
|
"cover",
|
||||||
|
"open_cover",
|
||||||
|
{"entity_id": "cover.gd00z_4_barrier_state"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert len(sent_messages) == 1
|
||||||
|
msg = sent_messages[0]
|
||||||
|
assert msg["topic"] == "OpenZWave/1/command/setvalue/"
|
||||||
|
assert msg["payload"] == {"Value": 4, "ValueIDKey": 281475083239444}
|
||||||
|
|
||||||
|
# Feedback on state
|
||||||
|
cover_gdo_msg.decode()
|
||||||
|
cover_gdo_msg.payload[VALUE_ID][VALUE_SELECTED_ID] = 4
|
||||||
|
cover_gdo_msg.encode()
|
||||||
|
receive_message(cover_gdo_msg)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("cover.gd00z_4_barrier_state")
|
||||||
|
assert state is not None
|
||||||
|
assert state.state == "open"
|
||||||
|
|
||||||
|
# Test closing
|
||||||
|
await hass.services.async_call(
|
||||||
|
"cover",
|
||||||
|
"close_cover",
|
||||||
|
{"entity_id": "cover.gd00z_4_barrier_state"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert len(sent_messages) == 2
|
||||||
|
msg = sent_messages[1]
|
||||||
|
assert msg["topic"] == "OpenZWave/1/command/setvalue/"
|
||||||
|
assert msg["payload"] == {"Value": 0, "ValueIDKey": 281475083239444}
|
||||||
|
|
54
tests/fixtures/ozw/cover_gdo.json
vendored
Normal file
54
tests/fixtures/ozw/cover_gdo.json
vendored
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
{
|
||||||
|
"topic": "OpenZWave/1/node/6/instance/1/commandclass/102/value/281475083239444/",
|
||||||
|
"payload": {
|
||||||
|
"Label": "Barrier State",
|
||||||
|
"Value": {
|
||||||
|
"List": [
|
||||||
|
{
|
||||||
|
"Value": 0,
|
||||||
|
"Label": "Closed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Value": 1,
|
||||||
|
"Label": "Closing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Value": 2,
|
||||||
|
"Label": "Stopped"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Value": 3,
|
||||||
|
"Label": "Opening"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Value": 4,
|
||||||
|
"Label": "Opened"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Value": 5,
|
||||||
|
"Label": "Unknown"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Selected": "Closed",
|
||||||
|
"Selected_id": 0
|
||||||
|
},
|
||||||
|
"Units": "",
|
||||||
|
"ValueSet": true,
|
||||||
|
"ValuePolled": false,
|
||||||
|
"ChangeVerified": false,
|
||||||
|
"Min": 0,
|
||||||
|
"Max": 0,
|
||||||
|
"Type": "List",
|
||||||
|
"Instance": 1,
|
||||||
|
"CommandClass": "COMMAND_CLASS_BARRIER_OPERATOR",
|
||||||
|
"Index": 1,
|
||||||
|
"Node": 6,
|
||||||
|
"Genre": "User",
|
||||||
|
"Help": "The Current State of the Barrier",
|
||||||
|
"ValueIDKey": 281475083239444,
|
||||||
|
"ReadOnly": false,
|
||||||
|
"WriteOnly": false,
|
||||||
|
"Event": "valueChanged",
|
||||||
|
"TimeStamp": 1593634453
|
||||||
|
}
|
||||||
|
}
|
45
tests/fixtures/ozw/cover_gdo_network_dump.csv
vendored
Normal file
45
tests/fixtures/ozw/cover_gdo_network_dump.csv
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue