forked from home-assistant/core
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Ecowitt integration (home-assistant#77441)
* Add ecowitt integration * add tests * use total * use total * test coverage * Update homeassistant/components/ecowitt/__init__.py Co-authored-by: Paulus Schoutsen <[email protected]> * Update homeassistant/components/ecowitt/binary_sensor.py Co-authored-by: Paulus Schoutsen <[email protected]> * Update homeassistant/components/ecowitt/entity.py Co-authored-by: Paulus Schoutsen <[email protected]> * Update homeassistant/components/ecowitt/diagnostics.py Co-authored-by: Paulus Schoutsen <[email protected]> * add to async_on_unload * remove attr_name / unload callback * support unload platforms * using replace * address mapping * update type * mark final * Apply suggestions from code review Co-authored-by: Martin Hjelmare <[email protected]> * Fix bracket * Fix another bracket * Address comment * Add strings * update tests * Update homeassistant/components/ecowitt/strings.json Co-authored-by: Martin Hjelmare <[email protected]> * update text * Update homeassistant/components/ecowitt/strings.json Co-authored-by: Martin Hjelmare <[email protected]> Co-authored-by: Paulus Schoutsen <[email protected]> Co-authored-by: Martin Hjelmare <[email protected]>
- Loading branch information
1 parent
ee6ffb1
commit 105bb3e
Showing
17 changed files
with
705 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
"""The Ecowitt Weather Station Component.""" | ||
from __future__ import annotations | ||
|
||
from aioecowitt import EcoWittListener | ||
|
||
from homeassistant.config_entries import ConfigEntry | ||
from homeassistant.const import CONF_PORT, EVENT_HOMEASSISTANT_STOP, Platform | ||
from homeassistant.core import Event, HomeAssistant | ||
|
||
from .const import CONF_PATH, DOMAIN | ||
|
||
PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.SENSOR] | ||
|
||
|
||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: | ||
"""Set up the Ecowitt component from UI.""" | ||
hass.data.setdefault(DOMAIN, {}) | ||
|
||
ecowitt = hass.data[DOMAIN][entry.entry_id] = EcoWittListener( | ||
port=entry.data[CONF_PORT], path=entry.data[CONF_PATH] | ||
) | ||
|
||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) | ||
|
||
await ecowitt.start() | ||
|
||
# Close on shutdown | ||
async def _stop_ecowitt(_: Event): | ||
"""Stop the Ecowitt listener.""" | ||
await ecowitt.stop() | ||
|
||
entry.async_on_unload( | ||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _stop_ecowitt) | ||
) | ||
|
||
return True | ||
|
||
|
||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: | ||
"""Unload a config entry.""" | ||
ecowitt = hass.data[DOMAIN][entry.entry_id] | ||
await ecowitt.stop() | ||
|
||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): | ||
hass.data[DOMAIN].pop(entry.entry_id) | ||
|
||
return unload_ok |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
"""Support for Ecowitt Weather Stations.""" | ||
import dataclasses | ||
from typing import Final | ||
|
||
from aioecowitt import EcoWittListener, EcoWittSensor, EcoWittSensorTypes | ||
|
||
from homeassistant.components.binary_sensor import ( | ||
BinarySensorDeviceClass, | ||
BinarySensorEntity, | ||
BinarySensorEntityDescription, | ||
) | ||
from homeassistant.config_entries import ConfigEntry | ||
from homeassistant.core import HomeAssistant | ||
from homeassistant.helpers.entity_platform import AddEntitiesCallback | ||
|
||
from .const import DOMAIN | ||
from .entity import EcowittEntity | ||
|
||
ECOWITT_BINARYSENSORS_MAPPING: Final = { | ||
EcoWittSensorTypes.LEAK: BinarySensorEntityDescription( | ||
key="LEAK", device_class=BinarySensorDeviceClass.MOISTURE | ||
), | ||
EcoWittSensorTypes.BATTERY_BINARY: BinarySensorEntityDescription( | ||
key="BATTERY", device_class=BinarySensorDeviceClass.BATTERY | ||
), | ||
} | ||
|
||
|
||
async def async_setup_entry( | ||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback | ||
) -> None: | ||
"""Add sensors if new.""" | ||
ecowitt: EcoWittListener = hass.data[DOMAIN][entry.entry_id] | ||
|
||
def _new_sensor(sensor: EcoWittSensor) -> None: | ||
"""Add new sensor.""" | ||
if sensor.stype not in ECOWITT_BINARYSENSORS_MAPPING: | ||
return | ||
mapping = ECOWITT_BINARYSENSORS_MAPPING[sensor.stype] | ||
|
||
# Setup sensor description | ||
description = dataclasses.replace( | ||
mapping, | ||
key=sensor.key, | ||
name=sensor.name, | ||
) | ||
|
||
async_add_entities([EcowittBinarySensorEntity(sensor, description)]) | ||
|
||
ecowitt.new_sensor_cb.append(_new_sensor) | ||
entry.async_on_unload(lambda: ecowitt.new_sensor_cb.remove(_new_sensor)) | ||
|
||
# Add all sensors that are already known | ||
for sensor in ecowitt.sensors.values(): | ||
_new_sensor(sensor) | ||
|
||
|
||
class EcowittBinarySensorEntity(EcowittEntity, BinarySensorEntity): | ||
"""Representation of a Ecowitt BinarySensor.""" | ||
|
||
def __init__( | ||
self, sensor: EcoWittSensor, description: BinarySensorEntityDescription | ||
) -> None: | ||
"""Initialize the sensor.""" | ||
super().__init__(sensor) | ||
self.entity_description = description | ||
|
||
@property | ||
def is_on(self) -> bool: | ||
"""Return true if the binary sensor is on.""" | ||
return self.ecowitt.value > 0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
"""Config flow for ecowitt.""" | ||
from __future__ import annotations | ||
|
||
import logging | ||
import secrets | ||
from typing import Any | ||
|
||
from aioecowitt import EcoWittListener | ||
import voluptuous as vol | ||
|
||
from homeassistant import config_entries | ||
from homeassistant.const import CONF_PORT | ||
from homeassistant.core import HomeAssistant | ||
from homeassistant.data_entry_flow import FlowResult | ||
from homeassistant.exceptions import HomeAssistantError | ||
import homeassistant.helpers.config_validation as cv | ||
|
||
from .const import CONF_PATH, DEFAULT_PORT, DOMAIN | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
STEP_USER_DATA_SCHEMA = vol.Schema( | ||
{ | ||
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, | ||
vol.Optional(CONF_PATH, default=f"/{secrets.token_urlsafe(16)}"): cv.string, | ||
} | ||
) | ||
|
||
|
||
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, str]: | ||
"""Validate user input.""" | ||
# Check if the port is in use | ||
try: | ||
listener = EcoWittListener(port=data[CONF_PORT]) | ||
await listener.start() | ||
await listener.stop() | ||
except OSError: | ||
raise InvalidPort from None | ||
|
||
return {"title": f"Ecowitt on port {data[CONF_PORT]}"} | ||
|
||
|
||
class EcowittConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): | ||
"""Config flow for the Ecowitt.""" | ||
|
||
VERSION = 1 | ||
|
||
async def async_step_user( | ||
self, user_input: dict[str, Any] | None = None | ||
) -> FlowResult: | ||
"""Handle the initial step.""" | ||
if user_input is None: | ||
return self.async_show_form( | ||
step_id="user", data_schema=STEP_USER_DATA_SCHEMA | ||
) | ||
|
||
errors = {} | ||
|
||
# Check if the port is in use by another config entry | ||
self._async_abort_entries_match({CONF_PORT: user_input[CONF_PORT]}) | ||
|
||
try: | ||
info = await validate_input(self.hass, user_input) | ||
except InvalidPort: | ||
errors["base"] = "invalid_port" | ||
except Exception: # pylint: disable=broad-except | ||
_LOGGER.exception("Unexpected exception") | ||
errors["base"] = "unknown" | ||
else: | ||
return self.async_create_entry(title=info["title"], data=user_input) | ||
|
||
return self.async_show_form( | ||
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors | ||
) | ||
|
||
|
||
class InvalidPort(HomeAssistantError): | ||
"""Error to indicate there port is not usable.""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
"""Constants used by ecowitt component.""" | ||
|
||
DOMAIN = "ecowitt" | ||
|
||
DEFAULT_PORT = 49199 | ||
|
||
CONF_PATH = "path" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
"""Provides diagnostics for EcoWitt.""" | ||
from __future__ import annotations | ||
|
||
from typing import Any | ||
|
||
from aioecowitt import EcoWittListener | ||
|
||
from homeassistant.config_entries import ConfigEntry | ||
from homeassistant.core import HomeAssistant | ||
from homeassistant.helpers.device_registry import DeviceEntry | ||
|
||
from .const import DOMAIN | ||
|
||
|
||
async def async_get_device_diagnostics( | ||
hass: HomeAssistant, entry: ConfigEntry, device: DeviceEntry | ||
) -> dict[str, Any]: | ||
"""Return diagnostics for a device entry.""" | ||
ecowitt: EcoWittListener = hass.data[DOMAIN][entry.entry_id] | ||
station_id = next(item[1] for item in device.identifiers if item[0] == DOMAIN) | ||
|
||
station = ecowitt.stations[station_id] | ||
|
||
data = { | ||
"device": { | ||
"name": station.station, | ||
"model": station.model, | ||
"frequency": station.frequency, | ||
"version": station.version, | ||
}, | ||
"raw": ecowitt.last_values[station_id], | ||
"sensors": { | ||
sensor.key: sensor.value | ||
for sensor in station.sensors | ||
if sensor.station.key == station_id | ||
}, | ||
} | ||
|
||
return data |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
"""The Ecowitt Weather Station Entity.""" | ||
from __future__ import annotations | ||
|
||
import time | ||
|
||
from aioecowitt import EcoWittSensor | ||
|
||
from homeassistant.helpers.entity import DeviceInfo, Entity | ||
|
||
from .const import DOMAIN | ||
|
||
|
||
class EcowittEntity(Entity): | ||
"""Base class for Ecowitt Weather Station.""" | ||
|
||
_attr_has_entity_name = True | ||
_attr_should_poll = False | ||
|
||
def __init__(self, sensor: EcoWittSensor) -> None: | ||
"""Construct the entity.""" | ||
self.ecowitt: EcoWittSensor = sensor | ||
|
||
self._attr_unique_id = f"{sensor.station.key}-{sensor.key}" | ||
self._attr_device_info = DeviceInfo( | ||
identifiers={ | ||
(DOMAIN, sensor.station.key), | ||
}, | ||
name=sensor.station.station, | ||
model=sensor.station.model, | ||
sw_version=sensor.station.version, | ||
) | ||
|
||
async def async_added_to_hass(self): | ||
"""Install listener for updates later.""" | ||
|
||
def _update_state(): | ||
"""Update the state on callback.""" | ||
self.async_write_ha_state() | ||
|
||
self.ecowitt.update_cb.append(_update_state) | ||
self.async_on_remove(lambda: self.ecowitt.update_cb.remove(_update_state)) | ||
|
||
@property | ||
def available(self) -> bool: | ||
"""Return whether the state is based on actual reading from device.""" | ||
return (self.ecowitt.last_update_m + 5 * 60) > time.monotonic() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"domain": "ecowitt", | ||
"name": "Ecowitt Weather Station", | ||
"config_flow": true, | ||
"documentation": "https://www.home-assistant.io/integrations/ecowitt", | ||
"requirements": ["aioecowitt==2022.08.3"], | ||
"codeowners": ["@pvizeli"], | ||
"iot_class": "local_push" | ||
} |
Oops, something went wrong.