-
-
Notifications
You must be signed in to change notification settings - Fork 32.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add DROP integration * Remove all but one platform for first PR * Simplify initialization of hass.data[] structure * Remove unnecessary mnemonic 'DROP_' prefix from DOMAIN constants * Remove unnecessary whitespace * Clarify configuration 'confirm' step description * Remove unnecessary whitespace * Use device class where applicable * Remove unnecessary constructor and change its elements to class variables * Change base entity inheritance to CoordinatorEntity * Make sensor definitions more concise * Rename HA domain from drop to drop_connect * Remove underscores from class and function names * Remove duplicate temperature sensor * Change title capitalization * Refactor using SensorEntityDescription * Remove unnecessary intermediate dict layer * Remove generated translations file * Remove currently unused string values * Use constants in sensor definitions * Replace values with constants * Move translation keys * Remove unnecessary unique ID and config entry references * Clean up DROPEntity initialization * Clean up sensors * Rename vars and functions according to style * Remove redundant self references * Clean up DROPSensor initializer * Add missing state classes * Simplify detection of configured devices * Change entity identifiers to create device linkage * Move device_info to coordinator * Remove unnecessary properties * Correct hub device IDs * Remove redundant attribute * Replace optional UID with assert * Remove redundant attribute * Correct coordinator initialization * Fix mypy error * Move API functionality to 3rd party library * Abstract device to sensor map into a dict * Unsubscribe MQTT on unload * Move entity device information * Make type checking for mypy conditional * Bump dropmqttapi to 1.0.1 * Freeze dataclass to match parent class * Fix race condition in MQTT unsubscribe setup * Ensure unit tests begin with invalid MQTT state * Change unit tests to reflect device firmware * Move MQTT subscription out of the coordinator * Tidy up initializer * Move entirety of MQTT subscription out of the coordinator * Make drop_api a class property * Remove unnecessary type checks * Simplify some unit test asserts * Remove argument matching default * Add entity category to battery and cartridge life sensors
- Loading branch information
Showing
20 changed files
with
1,411 additions
and
0 deletions.
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
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,66 @@ | ||
"""The drop_connect integration.""" | ||
from __future__ import annotations | ||
|
||
import logging | ||
from typing import TYPE_CHECKING | ||
|
||
from homeassistant.components import mqtt | ||
from homeassistant.components.mqtt import ReceiveMessage | ||
from homeassistant.config_entries import ConfigEntry | ||
from homeassistant.const import Platform | ||
from homeassistant.core import HomeAssistant, callback | ||
|
||
from .const import CONF_DATA_TOPIC, CONF_DEVICE_TYPE, DOMAIN | ||
from .coordinator import DROPDeviceDataUpdateCoordinator | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
PLATFORMS: list[Platform] = [Platform.SENSOR] | ||
|
||
|
||
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: | ||
"""Set up DROP from a config entry.""" | ||
|
||
# Make sure MQTT integration is enabled and the client is available. | ||
if not await mqtt.async_wait_for_mqtt_client(hass): | ||
_LOGGER.error("MQTT integration is not available") | ||
return False | ||
|
||
if TYPE_CHECKING: | ||
assert config_entry.unique_id is not None | ||
drop_data_coordinator = DROPDeviceDataUpdateCoordinator( | ||
hass, config_entry.unique_id | ||
) | ||
|
||
@callback | ||
def mqtt_callback(msg: ReceiveMessage) -> None: | ||
"""Pass MQTT payload to DROP API parser.""" | ||
if drop_data_coordinator.drop_api.parse_drop_message( | ||
msg.topic, msg.payload, msg.qos, msg.retain | ||
): | ||
drop_data_coordinator.async_set_updated_data(None) | ||
|
||
config_entry.async_on_unload( | ||
await mqtt.async_subscribe( | ||
hass, config_entry.data[CONF_DATA_TOPIC], mqtt_callback | ||
) | ||
) | ||
_LOGGER.debug( | ||
"Entry %s (%s) subscribed to %s", | ||
config_entry.unique_id, | ||
config_entry.data[CONF_DEVICE_TYPE], | ||
config_entry.data[CONF_DATA_TOPIC], | ||
) | ||
|
||
hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = drop_data_coordinator | ||
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS) | ||
return True | ||
|
||
|
||
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: | ||
"""Unload a config entry.""" | ||
if unload_ok := await hass.config_entries.async_unload_platforms( | ||
config_entry, PLATFORMS | ||
): | ||
hass.data[DOMAIN].pop(config_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,98 @@ | ||
"""Config flow for drop_connect integration.""" | ||
from __future__ import annotations | ||
|
||
import logging | ||
from typing import TYPE_CHECKING, Any | ||
|
||
from dropmqttapi.discovery import DropDiscovery | ||
|
||
from homeassistant import config_entries | ||
from homeassistant.data_entry_flow import FlowResult | ||
from homeassistant.helpers.service_info.mqtt import MqttServiceInfo | ||
|
||
from .const import ( | ||
CONF_COMMAND_TOPIC, | ||
CONF_DATA_TOPIC, | ||
CONF_DEVICE_DESC, | ||
CONF_DEVICE_ID, | ||
CONF_DEVICE_NAME, | ||
CONF_DEVICE_OWNER_ID, | ||
CONF_DEVICE_TYPE, | ||
CONF_HUB_ID, | ||
DISCOVERY_TOPIC, | ||
DOMAIN, | ||
) | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): | ||
"""Handle DROP config flow.""" | ||
|
||
VERSION = 1 | ||
|
||
_drop_discovery: DropDiscovery | None = None | ||
|
||
async def async_step_mqtt(self, discovery_info: MqttServiceInfo) -> FlowResult: | ||
"""Handle a flow initialized by MQTT discovery.""" | ||
|
||
# Abort if the topic does not match our discovery topic or the payload is empty. | ||
if ( | ||
discovery_info.subscribed_topic != DISCOVERY_TOPIC | ||
or not discovery_info.payload | ||
): | ||
return self.async_abort(reason="invalid_discovery_info") | ||
|
||
self._drop_discovery = DropDiscovery(DOMAIN) | ||
if not ( | ||
await self._drop_discovery.parse_discovery( | ||
discovery_info.topic, discovery_info.payload | ||
) | ||
): | ||
return self.async_abort(reason="invalid_discovery_info") | ||
existing_entry = await self.async_set_unique_id( | ||
f"{self._drop_discovery.hub_id}_{self._drop_discovery.device_id}" | ||
) | ||
if existing_entry is not None: | ||
# Note: returning "invalid_discovery_info" here instead of "already_configured" | ||
# allows discovery of additional device types. | ||
return self.async_abort(reason="invalid_discovery_info") | ||
|
||
self.context.update({"title_placeholders": {"name": self._drop_discovery.name}}) | ||
|
||
return await self.async_step_confirm() | ||
|
||
async def async_step_confirm( | ||
self, user_input: dict[str, Any] | None = None | ||
) -> FlowResult: | ||
"""Confirm the setup.""" | ||
if TYPE_CHECKING: | ||
assert self._drop_discovery is not None | ||
if user_input is not None: | ||
device_data = { | ||
CONF_COMMAND_TOPIC: self._drop_discovery.command_topic, | ||
CONF_DATA_TOPIC: self._drop_discovery.data_topic, | ||
CONF_DEVICE_DESC: self._drop_discovery.device_desc, | ||
CONF_DEVICE_ID: self._drop_discovery.device_id, | ||
CONF_DEVICE_NAME: self._drop_discovery.name, | ||
CONF_DEVICE_TYPE: self._drop_discovery.device_type, | ||
CONF_HUB_ID: self._drop_discovery.hub_id, | ||
CONF_DEVICE_OWNER_ID: self._drop_discovery.owner_id, | ||
} | ||
return self.async_create_entry( | ||
title=self._drop_discovery.name, data=device_data | ||
) | ||
|
||
return self.async_show_form( | ||
step_id="confirm", | ||
description_placeholders={ | ||
"device_name": self._drop_discovery.name, | ||
"device_type": self._drop_discovery.device_desc, | ||
}, | ||
) | ||
|
||
async def async_step_user( | ||
self, user_input: dict[str, Any] | None = None | ||
) -> FlowResult: | ||
"""Handle a flow initialized by the user.""" | ||
return self.async_abort(reason="not_supported") |
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,25 @@ | ||
"""Constants for the drop_connect integration.""" | ||
|
||
# Keys for values used in the config_entry data dictionary | ||
CONF_COMMAND_TOPIC = "drop_command_topic" | ||
CONF_DATA_TOPIC = "drop_data_topic" | ||
CONF_DEVICE_DESC = "device_desc" | ||
CONF_DEVICE_ID = "device_id" | ||
CONF_DEVICE_TYPE = "device_type" | ||
CONF_HUB_ID = "drop_hub_id" | ||
CONF_DEVICE_NAME = "name" | ||
CONF_DEVICE_OWNER_ID = "drop_device_owner_id" | ||
|
||
# Values for DROP device types | ||
DEV_FILTER = "filt" | ||
DEV_HUB = "hub" | ||
DEV_LEAK_DETECTOR = "leak" | ||
DEV_PROTECTION_VALVE = "pv" | ||
DEV_PUMP_CONTROLLER = "pc" | ||
DEV_RO_FILTER = "ro" | ||
DEV_SALT_SENSOR = "salt" | ||
DEV_SOFTENER = "soft" | ||
|
||
DISCOVERY_TOPIC = "drop_connect/discovery/#" | ||
|
||
DOMAIN = "drop_connect" |
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,25 @@ | ||
"""DROP device data update coordinator object.""" | ||
from __future__ import annotations | ||
|
||
import logging | ||
|
||
from dropmqttapi.mqttapi import DropAPI | ||
|
||
from homeassistant.config_entries import ConfigEntry | ||
from homeassistant.core import HomeAssistant | ||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator | ||
|
||
from .const import DOMAIN | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
class DROPDeviceDataUpdateCoordinator(DataUpdateCoordinator): | ||
"""DROP device object.""" | ||
|
||
config_entry: ConfigEntry | ||
|
||
def __init__(self, hass: HomeAssistant, unique_id: str) -> None: | ||
"""Initialize the device.""" | ||
super().__init__(hass, _LOGGER, name=f"{DOMAIN}-{unique_id}") | ||
self.drop_api = DropAPI() |
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,53 @@ | ||
"""Base entity class for DROP entities.""" | ||
from __future__ import annotations | ||
|
||
from typing import TYPE_CHECKING | ||
|
||
from homeassistant.helpers.device_registry import DeviceInfo | ||
from homeassistant.helpers.update_coordinator import CoordinatorEntity | ||
|
||
from .const import ( | ||
CONF_DEVICE_DESC, | ||
CONF_DEVICE_NAME, | ||
CONF_DEVICE_OWNER_ID, | ||
CONF_DEVICE_TYPE, | ||
CONF_HUB_ID, | ||
DEV_HUB, | ||
DOMAIN, | ||
) | ||
from .coordinator import DROPDeviceDataUpdateCoordinator | ||
|
||
|
||
class DROPEntity(CoordinatorEntity[DROPDeviceDataUpdateCoordinator]): | ||
"""Representation of a DROP device entity.""" | ||
|
||
_attr_has_entity_name = True | ||
|
||
def __init__( | ||
self, entity_type: str, coordinator: DROPDeviceDataUpdateCoordinator | ||
) -> None: | ||
"""Init DROP entity.""" | ||
super().__init__(coordinator) | ||
if TYPE_CHECKING: | ||
assert coordinator.config_entry.unique_id is not None | ||
unique_id = coordinator.config_entry.unique_id | ||
self._attr_unique_id = f"{unique_id}_{entity_type}" | ||
entry_data = coordinator.config_entry.data | ||
model: str = entry_data[CONF_DEVICE_DESC] | ||
if entry_data[CONF_DEVICE_TYPE] == DEV_HUB: | ||
model = f"Hub {entry_data[CONF_HUB_ID]}" | ||
self._attr_device_info = DeviceInfo( | ||
manufacturer="Chandler Systems, Inc.", | ||
model=model, | ||
name=entry_data[CONF_DEVICE_NAME], | ||
identifiers={(DOMAIN, unique_id)}, | ||
) | ||
if entry_data[CONF_DEVICE_TYPE] != DEV_HUB: | ||
self._attr_device_info.update( | ||
{ | ||
"via_device": ( | ||
DOMAIN, | ||
entry_data[CONF_DEVICE_OWNER_ID], | ||
) | ||
} | ||
) |
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,11 @@ | ||
{ | ||
"domain": "drop_connect", | ||
"name": "DROP", | ||
"codeowners": ["@ChandlerSystems", "@pfrazer"], | ||
"config_flow": true, | ||
"dependencies": ["mqtt"], | ||
"documentation": "https://www.home-assistant.io/integrations/drop_connect", | ||
"iot_class": "local_push", | ||
"mqtt": ["drop_connect/discovery/#"], | ||
"requirements": ["dropmqttapi==1.0.1"] | ||
} |
Oops, something went wrong.