diff --git a/CODEOWNERS b/CODEOWNERS index a7d1d346c5f..52f13748303 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -235,6 +235,7 @@ homeassistant/components/obihai/* @dshokouhi homeassistant/components/ohmconnect/* @robbiet480 homeassistant/components/ombi/* @larssont homeassistant/components/onboarding/* @home-assistant/core +homeassistant/components/onewire/* @garbled1 homeassistant/components/opentherm_gw/* @mvn23 homeassistant/components/openuv/* @bachya homeassistant/components/openweathermap/* @fabaff diff --git a/homeassistant/components/onewire/manifest.json b/homeassistant/components/onewire/manifest.json index 2d8c6c71071..6687a10e3d7 100644 --- a/homeassistant/components/onewire/manifest.json +++ b/homeassistant/components/onewire/manifest.json @@ -2,7 +2,11 @@ "domain": "onewire", "name": "Onewire", "documentation": "https://www.home-assistant.io/integrations/onewire", - "requirements": [], + "requirements": [ + "pyownet==0.10.0.post1" + ], "dependencies": [], - "codeowners": [] + "codeowners": [ + "@garbled1" + ] } diff --git a/homeassistant/components/onewire/sensor.py b/homeassistant/components/onewire/sensor.py index 6e90178c5d3..6405cb05adc 100644 --- a/homeassistant/components/onewire/sensor.py +++ b/homeassistant/components/onewire/sensor.py @@ -4,10 +4,11 @@ import logging import os import time +from pyownet import protocol import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import TEMP_CELSIUS +from homeassistant.const import CONF_HOST, CONF_PORT, TEMP_CELSIUS import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -30,33 +31,122 @@ DEVICE_SENSORS = { "28": {"temperature": "temperature"}, "3B": {"temperature": "temperature"}, "42": {"temperature": "temperature"}, + "1D": {"counter_a": "counter.A", "counter_b": "counter.B"}, + "EF": {"HobbyBoard": "special"}, +} + +# EF sensors are usually hobbyboards specialized sensors. +# These can only be read by OWFS. Currently this driver only supports them +# via owserver (network protocol) + +HOBBYBOARD_EF = { + "HobbyBoards_EF": { + "humidity": "humidity/humidity_corrected", + "humidity_raw": "humidity/humidity_raw", + "temperature": "humidity/temperature", + }, + "HB_MOISTURE_METER": { + "moisture_0": "moisture/sensor.0", + "moisture_1": "moisture/sensor.1", + "moisture_2": "moisture/sensor.2", + "moisture_3": "moisture/sensor.3", + }, } SENSOR_TYPES = { "temperature": ["temperature", TEMP_CELSIUS], "humidity": ["humidity", "%"], + "humidity_raw": ["humidity", "%"], "pressure": ["pressure", "mb"], "illuminance": ["illuminance", "lux"], + "wetness_0": ["wetness", "%"], + "wetness_1": ["wetness", "%"], + "wetness_2": ["wetness", "%"], + "wetness_3": ["wetness", "%"], + "moisture_0": ["moisture", "cb"], + "moisture_1": ["moisture", "cb"], + "moisture_2": ["moisture", "cb"], + "moisture_3": ["moisture", "cb"], + "counter_a": ["counter", "count"], + "counter_b": ["counter", "count"], + "HobbyBoard": ["none", "none"], } PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Optional(CONF_NAMES): {cv.string: cv.string}, vol.Optional(CONF_MOUNT_DIR, default=DEFAULT_MOUNT_DIR): cv.string, + vol.Optional(CONF_HOST): cv.string, + vol.Optional(CONF_PORT, default=4304): cv.port, } ) +def hb_info_from_type(dev_type="std"): + """Return the proper info array for the device type.""" + if "std" in dev_type: + return DEVICE_SENSORS + if "HobbyBoard" in dev_type: + return HOBBYBOARD_EF + + def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the one wire Sensors.""" - base_dir = config.get(CONF_MOUNT_DIR) + base_dir = config[CONF_MOUNT_DIR] + owport = config[CONF_PORT] + owhost = config.get(CONF_HOST) devs = [] device_names = {} if "names" in config: if isinstance(config["names"], dict): device_names = config["names"] - if base_dir == DEFAULT_MOUNT_DIR: + # We have an owserver on a remote(or local) host/port + if owhost: + try: + owproxy = protocol.proxy(host=owhost, port=owport) + devices = owproxy.dir() + except protocol.Error as exc: + _LOGGER.error( + "Cannot connect to owserver on %s:%d, got: %s", owhost, owport, exc + ) + devices = [] + for device in devices: + _LOGGER.debug("found device: %s", device) + family = owproxy.read(f"{device}family").decode() + dev_type = "std" + if "EF" in family: + dev_type = "HobbyBoard" + family = owproxy.read(f"{device}type").decode() + + if family not in hb_info_from_type(dev_type): + _LOGGER.warning( + "Ignoring unknown family (%s) of sensor found for device: %s", + family, + device, + ) + continue + for sensor_key, sensor_value in hb_info_from_type(dev_type)[family].items(): + if "moisture" in sensor_key: + s_id = sensor_key.split("_")[1] + is_leaf = int( + owproxy.read(f"{device}moisture/is_leaf.{s_id}").decode() + ) + if is_leaf: + sensor_key = f"wetness_{id}" + sensor_id = os.path.split(os.path.split(device)[0])[1] + device_file = os.path.join(os.path.split(device)[0], sensor_value) + devs.append( + OneWireProxy( + device_names.get(sensor_id, sensor_id), + device_file, + sensor_key, + owproxy, + ) + ) + + # We have a raw GPIO ow sensor on a Pi + elif base_dir == DEFAULT_MOUNT_DIR: for device_family in DEVICE_SENSORS: for device_folder in glob(os.path.join(base_dir, device_family + "[.-]*")): sensor_id = os.path.split(device_folder)[1] @@ -68,10 +158,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None): "temperature", ) ) + + # We have an owfs mounted else: for family_file_path in glob(os.path.join(base_dir, "*", "family")): with open(family_file_path, "r") as family_file: family = family_file.read() + if "EF" in family: + continue if family in DEVICE_SENSORS: for sensor_key, sensor_value in DEVICE_SENSORS[family].items(): sensor_id = os.path.split(os.path.split(family_file_path)[0])[1] @@ -121,6 +215,8 @@ class OneWire(Entity): @property def state(self): """Return the state of the sensor.""" + if "count" in self._unit_of_measurement: + return int(self._state) return self._state @property @@ -129,6 +225,34 @@ class OneWire(Entity): return self._unit_of_measurement +class OneWireProxy(OneWire): + """Implementation of a One wire Sensor through owserver.""" + + def __init__(self, name, device_file, sensor_type, owproxy): + """Initialize the onewire sensor via owserver.""" + super().__init__(name, device_file, sensor_type) + self._owproxy = owproxy + + def _read_value_ownet(self): + """Read a value from the owserver.""" + if self._owproxy: + return self._owproxy.read(self._device_file).decode().lstrip() + return None + + def update(self): + """Get the latest data from the device.""" + value = None + value_read = False + try: + value_read = self._read_value_ownet() + except protocol.Error as exc: + _LOGGER.error("Owserver failure in read(), got: %s", exc) + if value_read: + value = round(float(value_read), 1) + + self._state = value + + class OneWireDirect(OneWire): """Implementation of an One wire Sensor directly connected to RPI GPIO.""" diff --git a/requirements_all.txt b/requirements_all.txt index 49a9fa49e2e..5a95d3f28b8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1429,6 +1429,9 @@ pyowlet==1.0.3 # homeassistant.components.openweathermap pyowm==2.10.0 +# homeassistant.components.onewire +pyownet==0.10.0.post1 + # homeassistant.components.elv pypca==0.0.7