From 489d85d862789523788bcc539a2701400052ae26 Mon Sep 17 00:00:00 2001 From: Tim Rightnour <6556271+garbled1@users.noreply.github.com> Date: Tue, 7 Dec 2021 05:59:43 -0700 Subject: [PATCH] Add Onewire diagnostic and config switches and binary_sensors (#59309) * Onewire: Add diagnostic and config switches and binary_sensors This commit adds diagnostic and config switches and binary_sensors to the HobbyBoards devices. With these, the user will be able to configure those devices, without having to run owwrite/owread commands outside of HA. * Address review from @epenet * Add HB_HUB to DEVICE_SUPPORT_OWSERVER * Device class and entity category enums * Fixup merge breakage * Remove duplicate lines --- .../components/onewire/binary_sensor.py | 37 ++++- homeassistant/components/onewire/const.py | 3 +- homeassistant/components/onewire/switch.py | 58 ++++++- tests/components/onewire/const.py | 150 ++++++++++++++++++ 4 files changed, 241 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/onewire/binary_sensor.py b/homeassistant/components/onewire/binary_sensor.py index 7f569b150c2..945ec2344d4 100644 --- a/homeassistant/components/onewire/binary_sensor.py +++ b/homeassistant/components/onewire/binary_sensor.py @@ -6,6 +6,7 @@ import os from typing import TYPE_CHECKING from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, BinarySensorEntity, BinarySensorEntityDescription, ) @@ -13,10 +14,12 @@ from homeassistant.components.onewire.model import OWServerDeviceDescription from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_TYPE from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ( CONF_TYPE_OWSERVER, + DEVICE_KEYS_0_3, DEVICE_KEYS_0_7, DEVICE_KEYS_A_B, DOMAIN, @@ -61,8 +64,33 @@ DEVICE_BINARY_SENSORS: dict[str, tuple[OneWireBinarySensorEntityDescription, ... ) for id in DEVICE_KEYS_A_B ), + "EF": (), # "HobbyBoard": special } +# EF sensors are usually hobbyboards specialized sensors. +HOBBYBOARD_EF: dict[str, tuple[OneWireBinarySensorEntityDescription, ...]] = { + "HB_HUB": tuple( + OneWireBinarySensorEntityDescription( + key=f"hub/short.{id}", + entity_registry_enabled_default=False, + name=f"Hub Short on Branch {id}", + read_mode=READ_MODE_BOOL, + entity_category=EntityCategory.DIAGNOSTIC, + device_class=BinarySensorDeviceClass.PROBLEM, + ) + for id in DEVICE_KEYS_0_3 + ), +} + + +def get_sensor_types( + device_sub_type: str, +) -> dict[str, tuple[OneWireBinarySensorEntityDescription, ...]]: + """Return the proper info array for the device type.""" + if "HobbyBoard" in device_sub_type: + return HOBBYBOARD_EF + return DEVICE_BINARY_SENSORS + async def async_setup_entry( hass: HomeAssistant, @@ -89,11 +117,16 @@ def get_entities(onewirehub: OneWireHub) -> list[BinarySensorEntity]: assert isinstance(device, OWServerDeviceDescription) family = device.family device_id = device.id + device_type = device.type device_info = device.device_info + device_sub_type = "std" + if "EF" in family: + device_sub_type = "HobbyBoard" + family = device_type - if family not in DEVICE_BINARY_SENSORS: + if family not in get_sensor_types(device_sub_type): continue - for description in DEVICE_BINARY_SENSORS[family]: + for description in get_sensor_types(device_sub_type)[family]: device_file = os.path.join(os.path.split(device.path)[0], description.key) name = f"{device_id} {description.name}" entities.append( diff --git a/homeassistant/components/onewire/const.py b/homeassistant/components/onewire/const.py index 91f931e2517..fc13ad9c3b0 100644 --- a/homeassistant/components/onewire/const.py +++ b/homeassistant/components/onewire/const.py @@ -15,6 +15,7 @@ DEFAULT_SYSBUS_MOUNT_DIR = "/sys/bus/w1/devices/" DOMAIN = "onewire" +DEVICE_KEYS_0_3 = range(4) DEVICE_KEYS_0_7 = range(8) DEVICE_KEYS_A_B = ("A", "B") @@ -32,7 +33,7 @@ DEVICE_SUPPORT_OWSERVER = { "3B": (), "42": (), "7E": ("EDS0066", "EDS0068"), - "EF": ("HB_MOISTURE_METER", "HobbyBoards_EF"), + "EF": ("HB_HUB", "HB_MOISTURE_METER", "HobbyBoards_EF"), } DEVICE_SUPPORT_SYSBUS = ["10", "22", "28", "3B", "42"] diff --git a/homeassistant/components/onewire/switch.py b/homeassistant/components/onewire/switch.py index 49f1ece51ca..a7dc1335a46 100644 --- a/homeassistant/components/onewire/switch.py +++ b/homeassistant/components/onewire/switch.py @@ -2,7 +2,6 @@ from __future__ import annotations from dataclasses import dataclass -import logging import os from typing import TYPE_CHECKING, Any @@ -16,6 +15,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ( CONF_TYPE_OWSERVER, + DEVICE_KEYS_0_3, DEVICE_KEYS_0_7, DEVICE_KEYS_A_B, DOMAIN, @@ -97,9 +97,54 @@ DEVICE_SWITCHES: dict[str, tuple[OneWireEntityDescription, ...]] = { ) for id in DEVICE_KEYS_A_B ), + "EF": (), # "HobbyBoard": special } -LOGGER = logging.getLogger(__name__) +# EF sensors are usually hobbyboards specialized sensors. + +HOBBYBOARD_EF: dict[str, tuple[OneWireEntityDescription, ...]] = { + "HB_HUB": tuple( + OneWireSwitchEntityDescription( + key=f"hub/branch.{id}", + entity_registry_enabled_default=False, + name=f"Hub Branch {id} Enable", + read_mode=READ_MODE_BOOL, + entity_category=EntityCategory.CONFIG, + ) + for id in DEVICE_KEYS_0_3 + ), + "HB_MOISTURE_METER": tuple( + [ + OneWireSwitchEntityDescription( + key=f"moisture/is_leaf.{id}", + entity_registry_enabled_default=False, + name=f"Leaf Sensor {id} Enable", + read_mode=READ_MODE_BOOL, + entity_category=EntityCategory.CONFIG, + ) + for id in DEVICE_KEYS_0_3 + ] + + [ + OneWireSwitchEntityDescription( + key=f"moisture/is_moisture.{id}", + entity_registry_enabled_default=False, + name=f"Moisture Sensor {id} Enable", + read_mode=READ_MODE_BOOL, + entity_category=EntityCategory.CONFIG, + ) + for id in DEVICE_KEYS_0_3 + ] + ), +} + + +def get_sensor_types( + device_sub_type: str, +) -> dict[str, tuple[OneWireEntityDescription, ...]]: + """Return the proper info array for the device type.""" + if "HobbyBoard" in device_sub_type: + return HOBBYBOARD_EF + return DEVICE_SWITCHES async def async_setup_entry( @@ -127,12 +172,17 @@ def get_entities(onewirehub: OneWireHub) -> list[SwitchEntity]: if TYPE_CHECKING: assert isinstance(device, OWServerDeviceDescription) family = device.family + device_type = device.type device_id = device.id device_info = device.device_info + device_sub_type = "std" + if "EF" in family: + device_sub_type = "HobbyBoard" + family = device_type - if family not in DEVICE_SWITCHES: + if family not in get_sensor_types(device_sub_type): continue - for description in DEVICE_SWITCHES[family]: + for description in get_sensor_types(device_sub_type)[family]: device_file = os.path.join(os.path.split(device.path)[0], description.key) name = f"{device_id} {description.name}" entities.append( diff --git a/tests/components/onewire/const.py b/tests/components/onewire/const.py index 2153e153961..3eb0ef51742 100644 --- a/tests/components/onewire/const.py +++ b/tests/components/onewire/const.py @@ -2,6 +2,7 @@ from pi1wire import InvalidCRCException, UnsupportResponseException from pyownet.protocol import Error as ProtocolError +from homeassistant.components.binary_sensor import BinarySensorDeviceClass from homeassistant.components.onewire.const import ( DOMAIN, MANUFACTURER_EDS, @@ -796,6 +797,155 @@ MOCK_OWPROXY_DEVICES = { ATTR_UNIT_OF_MEASUREMENT: PRESSURE_CBAR, }, ], + Platform.SWITCH: [ + { + ATTR_DEFAULT_DISABLED: True, + ATTR_ENTITY_CATEGORY: EntityCategory.CONFIG, + ATTR_ENTITY_ID: "switch.ef_111111111112_leaf_sensor_0_enable", + ATTR_INJECT_READS: b"1", + ATTR_STATE: STATE_ON, + ATTR_UNIQUE_ID: "/EF.111111111112/moisture/is_leaf.0", + }, + { + ATTR_DEFAULT_DISABLED: True, + ATTR_ENTITY_CATEGORY: EntityCategory.CONFIG, + ATTR_ENTITY_ID: "switch.ef_111111111112_leaf_sensor_1_enable", + ATTR_INJECT_READS: b"1", + ATTR_STATE: STATE_ON, + ATTR_UNIQUE_ID: "/EF.111111111112/moisture/is_leaf.1", + }, + { + ATTR_DEFAULT_DISABLED: True, + ATTR_ENTITY_CATEGORY: EntityCategory.CONFIG, + ATTR_ENTITY_ID: "switch.ef_111111111112_leaf_sensor_2_enable", + ATTR_INJECT_READS: b"0", + ATTR_STATE: STATE_OFF, + ATTR_UNIQUE_ID: "/EF.111111111112/moisture/is_leaf.2", + }, + { + ATTR_DEFAULT_DISABLED: True, + ATTR_ENTITY_CATEGORY: EntityCategory.CONFIG, + ATTR_ENTITY_ID: "switch.ef_111111111112_leaf_sensor_3_enable", + ATTR_INJECT_READS: b"0", + ATTR_STATE: STATE_OFF, + ATTR_UNIQUE_ID: "/EF.111111111112/moisture/is_leaf.3", + }, + { + ATTR_DEFAULT_DISABLED: True, + ATTR_ENTITY_CATEGORY: EntityCategory.CONFIG, + ATTR_ENTITY_ID: "switch.ef_111111111112_moisture_sensor_0_enable", + ATTR_INJECT_READS: b"1", + ATTR_STATE: STATE_ON, + ATTR_UNIQUE_ID: "/EF.111111111112/moisture/is_moisture.0", + }, + { + ATTR_DEFAULT_DISABLED: True, + ATTR_ENTITY_CATEGORY: EntityCategory.CONFIG, + ATTR_ENTITY_ID: "switch.ef_111111111112_moisture_sensor_1_enable", + ATTR_INJECT_READS: b"1", + ATTR_STATE: STATE_ON, + ATTR_UNIQUE_ID: "/EF.111111111112/moisture/is_moisture.1", + }, + { + ATTR_DEFAULT_DISABLED: True, + ATTR_ENTITY_CATEGORY: EntityCategory.CONFIG, + ATTR_ENTITY_ID: "switch.ef_111111111112_moisture_sensor_2_enable", + ATTR_INJECT_READS: b"0", + ATTR_STATE: STATE_OFF, + ATTR_UNIQUE_ID: "/EF.111111111112/moisture/is_moisture.2", + }, + { + ATTR_DEFAULT_DISABLED: True, + ATTR_ENTITY_CATEGORY: EntityCategory.CONFIG, + ATTR_ENTITY_ID: "switch.ef_111111111112_moisture_sensor_3_enable", + ATTR_INJECT_READS: b"0", + ATTR_STATE: STATE_OFF, + ATTR_UNIQUE_ID: "/EF.111111111112/moisture/is_moisture.3", + }, + ], + }, + "EF.111111111113": { + ATTR_INJECT_READS: [ + b"HB_HUB", # read type + ], + ATTR_DEVICE_INFO: { + ATTR_IDENTIFIERS: {(DOMAIN, "EF.111111111113")}, + ATTR_MANUFACTURER: MANUFACTURER_HOBBYBOARDS, + ATTR_MODEL: "HB_HUB", + ATTR_NAME: "EF.111111111113", + }, + Platform.BINARY_SENSOR: [ + { + ATTR_DEFAULT_DISABLED: True, + ATTR_DEVICE_CLASS: BinarySensorDeviceClass.PROBLEM, + ATTR_ENTITY_CATEGORY: EntityCategory.DIAGNOSTIC, + ATTR_ENTITY_ID: "binary_sensor.ef_111111111113_hub_short_on_branch_0", + ATTR_INJECT_READS: b"1", + ATTR_STATE: STATE_ON, + ATTR_UNIQUE_ID: "/EF.111111111113/hub/short.0", + }, + { + ATTR_DEFAULT_DISABLED: True, + ATTR_DEVICE_CLASS: BinarySensorDeviceClass.PROBLEM, + ATTR_ENTITY_CATEGORY: EntityCategory.DIAGNOSTIC, + ATTR_ENTITY_ID: "binary_sensor.ef_111111111113_hub_short_on_branch_1", + ATTR_INJECT_READS: b"0", + ATTR_STATE: STATE_OFF, + ATTR_UNIQUE_ID: "/EF.111111111113/hub/short.1", + }, + { + ATTR_DEFAULT_DISABLED: True, + ATTR_DEVICE_CLASS: BinarySensorDeviceClass.PROBLEM, + ATTR_ENTITY_CATEGORY: EntityCategory.DIAGNOSTIC, + ATTR_ENTITY_ID: "binary_sensor.ef_111111111113_hub_short_on_branch_2", + ATTR_INJECT_READS: b"1", + ATTR_STATE: STATE_ON, + ATTR_UNIQUE_ID: "/EF.111111111113/hub/short.2", + }, + { + ATTR_DEFAULT_DISABLED: True, + ATTR_DEVICE_CLASS: BinarySensorDeviceClass.PROBLEM, + ATTR_ENTITY_CATEGORY: EntityCategory.DIAGNOSTIC, + ATTR_ENTITY_ID: "binary_sensor.ef_111111111113_hub_short_on_branch_3", + ATTR_INJECT_READS: b"0", + ATTR_STATE: STATE_OFF, + ATTR_UNIQUE_ID: "/EF.111111111113/hub/short.3", + }, + ], + Platform.SWITCH: [ + { + ATTR_DEFAULT_DISABLED: True, + ATTR_ENTITY_CATEGORY: EntityCategory.CONFIG, + ATTR_ENTITY_ID: "switch.ef_111111111113_hub_branch_0_enable", + ATTR_INJECT_READS: b"1", + ATTR_STATE: STATE_ON, + ATTR_UNIQUE_ID: "/EF.111111111113/hub/branch.0", + }, + { + ATTR_DEFAULT_DISABLED: True, + ATTR_ENTITY_CATEGORY: EntityCategory.CONFIG, + ATTR_ENTITY_ID: "switch.ef_111111111113_hub_branch_1_enable", + ATTR_INJECT_READS: b"0", + ATTR_STATE: STATE_OFF, + ATTR_UNIQUE_ID: "/EF.111111111113/hub/branch.1", + }, + { + ATTR_DEFAULT_DISABLED: True, + ATTR_ENTITY_CATEGORY: EntityCategory.CONFIG, + ATTR_ENTITY_ID: "switch.ef_111111111113_hub_branch_2_enable", + ATTR_INJECT_READS: b"1", + ATTR_STATE: STATE_ON, + ATTR_UNIQUE_ID: "/EF.111111111113/hub/branch.2", + }, + { + ATTR_DEFAULT_DISABLED: True, + ATTR_ENTITY_CATEGORY: EntityCategory.CONFIG, + ATTR_ENTITY_ID: "switch.ef_111111111113_hub_branch_3_enable", + ATTR_INJECT_READS: b"0", + ATTR_STATE: STATE_OFF, + ATTR_UNIQUE_ID: "/EF.111111111113/hub/branch.3", + }, + ], }, "7E.111111111111": { ATTR_INJECT_READS: [