Add ozw garage door barrier support (#37316)

This commit is contained in:
Chris 2020-07-02 12:29:09 -07:00 committed by GitHub
parent 4679e670f1
commit f6df85f8ce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 223 additions and 1 deletions

View file

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

View file

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

View file

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

View file

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

File diff suppressed because one or more lines are too long