Add binary_sensor platform to onewire integration (#42832)

* Add binary_sensor platform to onewire integration

* Keep the same name

* Rework tests

* Rework tests
This commit is contained in:
epenet 2020-11-08 03:42:20 +01:00 committed by GitHub
parent 1626c236dc
commit 2b2d7558de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 388 additions and 35 deletions

View File

@ -0,0 +1,132 @@
"""Support for 1-Wire binary sensors."""
import os
from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.const import CONF_TYPE
from .const import CONF_TYPE_OWSERVER, DOMAIN, SENSOR_TYPE_SENSED
from .onewire_entities import OneWireProxy
from .onewirehub import OneWireHub
DEVICE_BINARY_SENSORS = {
# Family : { path, sensor_type }
"12": [
{
"path": "sensed.A",
"name": "Sensed A",
"type": SENSOR_TYPE_SENSED,
"default_disabled": True,
},
{
"path": "sensed.B",
"name": "Sensed B",
"type": SENSOR_TYPE_SENSED,
"default_disabled": True,
},
],
"29": [
{
"path": "sensed.0",
"name": "Sensed 0",
"type": SENSOR_TYPE_SENSED,
"default_disabled": True,
},
{
"path": "sensed.1",
"name": "Sensed 1",
"type": SENSOR_TYPE_SENSED,
"default_disabled": True,
},
{
"path": "sensed.2",
"name": "Sensed 2",
"type": SENSOR_TYPE_SENSED,
"default_disabled": True,
},
{
"path": "sensed.3",
"name": "Sensed 3",
"type": SENSOR_TYPE_SENSED,
"default_disabled": True,
},
{
"path": "sensed.4",
"name": "Sensed 4",
"type": SENSOR_TYPE_SENSED,
"default_disabled": True,
},
{
"path": "sensed.5",
"name": "Sensed 5",
"type": SENSOR_TYPE_SENSED,
"default_disabled": True,
},
{
"path": "sensed.6",
"name": "Sensed 6",
"type": SENSOR_TYPE_SENSED,
"default_disabled": True,
},
{
"path": "sensed.7",
"name": "Sensed 7",
"type": SENSOR_TYPE_SENSED,
"default_disabled": True,
},
],
}
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up 1-Wire platform."""
# Only OWServer implementation works with binary sensors
if config_entry.data[CONF_TYPE] == CONF_TYPE_OWSERVER:
onewirehub = hass.data[DOMAIN][config_entry.unique_id]
entities = await hass.async_add_executor_job(get_entities, onewirehub)
async_add_entities(entities, True)
def get_entities(onewirehub: OneWireHub):
"""Get a list of entities."""
entities = []
for device in onewirehub.devices:
family = device["family"]
device_type = device["type"]
sensor_id = os.path.split(os.path.split(device["path"])[0])[1]
if family not in DEVICE_BINARY_SENSORS:
continue
device_info = {
"identifiers": {(DOMAIN, sensor_id)},
"manufacturer": "Maxim Integrated",
"model": device_type,
"name": sensor_id,
}
for device_sensor in DEVICE_BINARY_SENSORS[family]:
device_file = os.path.join(
os.path.split(device["path"])[0], device_sensor["path"]
)
entities.append(
OneWireBinarySensor(
sensor_id,
device_file,
device_sensor["type"],
device_sensor["name"],
device_info,
device_sensor.get("default_disabled", False),
onewirehub.owproxy,
)
)
return entities
class OneWireBinarySensor(BinarySensorEntity, OneWireProxy):
"""Implementation of a 1-Wire binary sensor."""
@property
def is_on(self):
"""Return true if sensor is on."""
return self._state

View File

@ -1,4 +1,5 @@
"""Constants for 1-Wire component."""
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.const import (
DEVICE_CLASS_CURRENT,
@ -36,6 +37,7 @@ SENSOR_TYPE_HUMIDITY = "humidity"
SENSOR_TYPE_ILLUMINANCE = "illuminance"
SENSOR_TYPE_MOISTURE = "moisture"
SENSOR_TYPE_PRESSURE = "pressure"
SENSOR_TYPE_SENSED = "sensed"
SENSOR_TYPE_TEMPERATURE = "temperature"
SENSOR_TYPE_VOLTAGE = "voltage"
SENSOR_TYPE_WETNESS = "wetness"
@ -51,8 +53,10 @@ SENSOR_TYPES = {
SENSOR_TYPE_COUNT: ["count", None],
SENSOR_TYPE_VOLTAGE: [VOLT, DEVICE_CLASS_VOLTAGE],
SENSOR_TYPE_CURRENT: [ELECTRICAL_CURRENT_AMPERE, DEVICE_CLASS_CURRENT],
SENSOR_TYPE_SENSED: [None, None],
}
SUPPORTED_PLATFORMS = [
BINARY_SENSOR_DOMAIN,
SENSOR_DOMAIN,
]

View File

@ -7,7 +7,7 @@ from pyownet import protocol
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import StateType
from .const import SENSOR_TYPES
from .const import SENSOR_TYPE_COUNT, SENSOR_TYPE_SENSED, SENSOR_TYPES
_LOGGER = logging.getLogger(__name__)
@ -86,12 +86,12 @@ class OneWireProxy(OneWire):
sensor_type: str,
sensor_name: str,
device_info: Dict[str, Any],
disable_startup: bool,
default_disabled: bool,
owproxy: protocol._Proxy,
):
"""Initialize the sensor."""
super().__init__(
name, device_file, sensor_type, sensor_name, device_info, disable_startup
name, device_file, sensor_type, sensor_name, device_info, default_disabled
)
self._owproxy = owproxy
@ -107,8 +107,10 @@ class OneWireProxy(OneWire):
except protocol.Error as exc:
_LOGGER.error("Owserver failure in read(), got: %s", exc)
else:
if "count" in self._unit_of_measurement:
if self._sensor_type == SENSOR_TYPE_COUNT:
value = int(self._value_raw)
elif self._sensor_type == SENSOR_TYPE_SENSED:
value = int(self._value_raw) == 1
else:
value = round(self._value_raw, 1)

View File

@ -2,6 +2,7 @@
from homeassistant.components.onewire.const import (
CONF_MOUNT_DIR,
CONF_NAMES,
CONF_TYPE_OWSERVER,
CONF_TYPE_SYSBUS,
DEFAULT_SYSBUS_MOUNT_DIR,
@ -63,3 +64,29 @@ async def setup_onewire_owserver_integration(hass):
await hass.async_block_till_done()
return config_entry
async def setup_onewire_patched_owserver_integration(hass):
"""Create the 1-Wire integration."""
config_entry = MockConfigEntry(
domain=DOMAIN,
source="user",
data={
CONF_TYPE: CONF_TYPE_OWSERVER,
CONF_HOST: "1.2.3.4",
CONF_PORT: "1234",
CONF_NAMES: {
"10.111111111111": "My DS18B20",
},
},
unique_id=f"{CONF_TYPE_OWSERVER}:1.2.3.4:1234",
connection_class=CONN_CLASS_LOCAL_POLL,
options={},
entry_id="2",
)
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
return config_entry

View File

@ -0,0 +1,86 @@
"""Tests for 1-Wire devices connected on OWServer."""
import copy
from pyownet.protocol import Error as ProtocolError
import pytest
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
from homeassistant.components.onewire.binary_sensor import DEVICE_BINARY_SENSORS
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.setup import async_setup_component
from . import setup_onewire_patched_owserver_integration
from tests.async_mock import patch
from tests.common import mock_registry
MOCK_DEVICE_SENSORS = {
"12.111111111111": {
"inject_reads": [
b"DS2406", # read device type
],
BINARY_SENSOR_DOMAIN: [
{
"entity_id": "binary_sensor.12_111111111111_sensed_a",
"injected_value": b" 1",
"result": STATE_ON,
},
{
"entity_id": "binary_sensor.12_111111111111_sensed_b",
"injected_value": b" 0",
"result": STATE_OFF,
},
],
},
}
@pytest.mark.parametrize("device_id", MOCK_DEVICE_SENSORS.keys())
@patch("homeassistant.components.onewire.onewirehub.protocol.proxy")
async def test_owserver_binary_sensor(owproxy, hass, device_id):
"""Test for 1-Wire binary sensor.
This test forces all entities to be enabled.
"""
await async_setup_component(hass, "persistent_notification", {})
entity_registry = mock_registry(hass)
mock_device_sensor = MOCK_DEVICE_SENSORS[device_id]
device_family = device_id[0:2]
dir_return_value = [f"/{device_id}/"]
read_side_effect = [device_family.encode()]
if "inject_reads" in mock_device_sensor:
read_side_effect += mock_device_sensor["inject_reads"]
expected_sensors = mock_device_sensor[BINARY_SENSOR_DOMAIN]
for expected_sensor in expected_sensors:
read_side_effect.append(expected_sensor["injected_value"])
# Ensure enough read side effect
read_side_effect.extend([ProtocolError("Missing injected value")] * 10)
owproxy.return_value.dir.return_value = dir_return_value
owproxy.return_value.read.side_effect = read_side_effect
# Force enable binary sensors
patch_device_binary_sensors = copy.deepcopy(DEVICE_BINARY_SENSORS)
for item in patch_device_binary_sensors[device_family]:
item["default_disabled"] = False
with patch(
"homeassistant.components.onewire.SUPPORTED_PLATFORMS", [BINARY_SENSOR_DOMAIN]
), patch.dict(
"homeassistant.components.onewire.binary_sensor.DEVICE_BINARY_SENSORS",
patch_device_binary_sensors,
):
await setup_onewire_patched_owserver_integration(hass)
await hass.async_block_till_done()
assert len(entity_registry.entities) == len(expected_sensors)
for expected_sensor in expected_sensors:
entity_id = expected_sensor["entity_id"]
registry_entry = entity_registry.entities.get(entity_id)
assert registry_entry is not None
state = hass.states.get(entity_id)
assert state.state == expected_sensor["result"]

View File

@ -2,10 +2,11 @@
from pyownet.protocol import Error as ProtocolError
import pytest
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
from homeassistant.components.onewire.const import (
DEFAULT_OWSERVER_PORT,
DOMAIN,
PRESSURE_CBAR,
SUPPORTED_PLATFORMS,
)
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.const import (
@ -19,31 +20,24 @@ from homeassistant.const import (
LIGHT_LUX,
PERCENTAGE,
PRESSURE_MBAR,
STATE_OFF,
STATE_ON,
TEMP_CELSIUS,
VOLT,
)
from homeassistant.setup import async_setup_component
from . import setup_onewire_patched_owserver_integration
from tests.async_mock import patch
from tests.common import mock_device_registry, mock_registry
MOCK_CONFIG = {
SENSOR_DOMAIN: {
"platform": DOMAIN,
"host": "localhost",
"port": DEFAULT_OWSERVER_PORT,
"names": {
"10.111111111111": "My DS18B20",
},
}
}
MOCK_DEVICE_SENSORS = {
"00.111111111111": {
"inject_reads": [
b"", # read device type
],
"sensors": [],
SENSOR_DOMAIN: [],
},
"10.111111111111": {
"inject_reads": [
@ -55,7 +49,7 @@ MOCK_DEVICE_SENSORS = {
"model": "DS18S20",
"name": "10.111111111111",
},
"sensors": [
SENSOR_DOMAIN: [
{
"entity_id": "sensor.my_ds18b20_temperature",
"unique_id": "/10.111111111111/temperature",
@ -76,7 +70,27 @@ MOCK_DEVICE_SENSORS = {
"model": "DS2406",
"name": "12.111111111111",
},
"sensors": [
BINARY_SENSOR_DOMAIN: [
{
"entity_id": "binary_sensor.12_111111111111_sensed_a",
"unique_id": "/12.111111111111/sensed.A",
"injected_value": b" 1",
"result": STATE_ON,
"unit": None,
"class": None,
"disabled": True,
},
{
"entity_id": "binary_sensor.12_111111111111_sensed_b",
"unique_id": "/12.111111111111/sensed.B",
"injected_value": b" 0",
"result": STATE_OFF,
"unit": None,
"class": None,
"disabled": True,
},
],
SENSOR_DOMAIN: [
{
"entity_id": "sensor.12_111111111111_temperature",
"unique_id": "/12.111111111111/TAI8570/temperature",
@ -107,7 +121,7 @@ MOCK_DEVICE_SENSORS = {
"model": "DS2423",
"name": "1D.111111111111",
},
"sensors": [
SENSOR_DOMAIN: [
{
"entity_id": "sensor.1d_111111111111_counter_a",
"unique_id": "/1D.111111111111/counter.A",
@ -136,7 +150,7 @@ MOCK_DEVICE_SENSORS = {
"model": "DS1822",
"name": "22.111111111111",
},
"sensors": [
SENSOR_DOMAIN: [
{
"entity_id": "sensor.22_111111111111_temperature",
"unique_id": "/22.111111111111/temperature",
@ -157,7 +171,7 @@ MOCK_DEVICE_SENSORS = {
"model": "DS2438",
"name": "26.111111111111",
},
"sensors": [
SENSOR_DOMAIN: [
{
"entity_id": "sensor.26_111111111111_temperature",
"unique_id": "/26.111111111111/temperature",
@ -268,7 +282,7 @@ MOCK_DEVICE_SENSORS = {
"model": "DS18B20",
"name": "28.111111111111",
},
"sensors": [
SENSOR_DOMAIN: [
{
"entity_id": "sensor.28_111111111111_temperature",
"unique_id": "/28.111111111111/temperature",
@ -279,6 +293,91 @@ MOCK_DEVICE_SENSORS = {
},
],
},
"29.111111111111": {
"inject_reads": [
b"DS2408", # read device type
],
"device_info": {
"identifiers": {(DOMAIN, "29.111111111111")},
"manufacturer": "Maxim Integrated",
"model": "DS2408",
"name": "29.111111111111",
},
BINARY_SENSOR_DOMAIN: [
{
"entity_id": "binary_sensor.29_111111111111_sensed_0",
"unique_id": "/29.111111111111/sensed.0",
"injected_value": b" 1",
"result": STATE_ON,
"unit": None,
"class": None,
"disabled": True,
},
{
"entity_id": "binary_sensor.29_111111111111_sensed_1",
"unique_id": "/29.111111111111/sensed.1",
"injected_value": b" 0",
"result": STATE_OFF,
"unit": None,
"class": None,
"disabled": True,
},
{
"entity_id": "binary_sensor.29_111111111111_sensed_2",
"unique_id": "/29.111111111111/sensed.2",
"injected_value": b" 0",
"result": STATE_OFF,
"unit": None,
"class": None,
"disabled": True,
},
{
"entity_id": "binary_sensor.29_111111111111_sensed_3",
"unique_id": "/29.111111111111/sensed.3",
"injected_value": b" 0",
"result": STATE_OFF,
"unit": None,
"class": None,
"disabled": True,
},
{
"entity_id": "binary_sensor.29_111111111111_sensed_4",
"unique_id": "/29.111111111111/sensed.4",
"injected_value": b" 0",
"result": STATE_OFF,
"unit": None,
"class": None,
"disabled": True,
},
{
"entity_id": "binary_sensor.29_111111111111_sensed_5",
"unique_id": "/29.111111111111/sensed.5",
"injected_value": b" 0",
"result": STATE_OFF,
"unit": None,
"class": None,
"disabled": True,
},
{
"entity_id": "binary_sensor.29_111111111111_sensed_6",
"unique_id": "/29.111111111111/sensed.6",
"injected_value": b" 0",
"result": STATE_OFF,
"unit": None,
"class": None,
"disabled": True,
},
{
"entity_id": "binary_sensor.29_111111111111_sensed_7",
"unique_id": "/29.111111111111/sensed.7",
"injected_value": b" 0",
"result": STATE_OFF,
"unit": None,
"class": None,
"disabled": True,
},
],
},
"3B.111111111111": {
"inject_reads": [
b"DS1825", # read device type
@ -289,7 +388,7 @@ MOCK_DEVICE_SENSORS = {
"model": "DS1825",
"name": "3B.111111111111",
},
"sensors": [
SENSOR_DOMAIN: [
{
"entity_id": "sensor.3b_111111111111_temperature",
"unique_id": "/3B.111111111111/temperature",
@ -310,7 +409,7 @@ MOCK_DEVICE_SENSORS = {
"model": "DS28EA00",
"name": "42.111111111111",
},
"sensors": [
SENSOR_DOMAIN: [
{
"entity_id": "sensor.42_111111111111_temperature",
"unique_id": "/42.111111111111/temperature",
@ -331,7 +430,7 @@ MOCK_DEVICE_SENSORS = {
"model": "HobbyBoards_EF",
"name": "EF.111111111111",
},
"sensors": [
SENSOR_DOMAIN: [
{
"entity_id": "sensor.ef_111111111111_humidity",
"unique_id": "/EF.111111111111/humidity/humidity_corrected",
@ -372,7 +471,7 @@ MOCK_DEVICE_SENSORS = {
"model": "HB_MOISTURE_METER",
"name": "EF.111111111112",
},
"sensors": [
SENSOR_DOMAIN: [
{
"entity_id": "sensor.ef_111111111112_wetness_0",
"unique_id": "/EF.111111111112/moisture/sensor.0",
@ -411,7 +510,9 @@ MOCK_DEVICE_SENSORS = {
@pytest.mark.parametrize("device_id", MOCK_DEVICE_SENSORS.keys())
async def test_owserver_setup_valid_device(hass, device_id):
@pytest.mark.parametrize("platform", SUPPORTED_PLATFORMS)
@patch("homeassistant.components.onewire.onewirehub.protocol.proxy")
async def test_owserver_setup_valid_device(owproxy, hass, device_id, platform):
"""Test for 1-Wire device.
As they would be on a clean setup: all binary-sensors and switches disabled.
@ -422,23 +523,23 @@ async def test_owserver_setup_valid_device(hass, device_id):
mock_device_sensor = MOCK_DEVICE_SENSORS[device_id]
device_family = device_id[0:2]
dir_return_value = [f"/{device_id}/"]
read_side_effect = [device_id[0:2].encode()]
read_side_effect = [device_family.encode()]
if "inject_reads" in mock_device_sensor:
read_side_effect += mock_device_sensor["inject_reads"]
expected_sensors = mock_device_sensor["sensors"]
expected_sensors = mock_device_sensor.get(platform, [])
for expected_sensor in expected_sensors:
read_side_effect.append(expected_sensor["injected_value"])
# Ensure enough read side effect
read_side_effect.extend([ProtocolError("Missing injected value")] * 10)
owproxy.return_value.dir.return_value = dir_return_value
owproxy.return_value.read.side_effect = read_side_effect
with patch("homeassistant.components.onewire.onewirehub.protocol.proxy") as owproxy:
owproxy.return_value.dir.return_value = dir_return_value
owproxy.return_value.read.side_effect = read_side_effect
assert await async_setup_component(hass, SENSOR_DOMAIN, MOCK_CONFIG)
with patch("homeassistant.components.onewire.SUPPORTED_PLATFORMS", [platform]):
await setup_onewire_patched_owserver_integration(hass)
await hass.async_block_till_done()
assert len(entity_registry.entities) == len(expected_sensors)

View File

@ -79,6 +79,7 @@ MOCK_DEVICE_SENSORS = {
},
],
},
"29-111111111111": {"sensors": []},
"3B-111111111111": {
"device_info": {
"identifiers": {(DOMAIN, "3B-111111111111")},