Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion homeassistant/components/gardena_bluetooth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from bleak.backends.device import BLEDevice
from gardena_bluetooth.client import CachedConnection, Client
from gardena_bluetooth.const import DeviceConfiguration, DeviceInformation
from gardena_bluetooth.const import AquaContour, DeviceConfiguration, DeviceInformation
from gardena_bluetooth.exceptions import (
CharacteristicNoAccess,
CharacteristicNotFound,
Expand Down Expand Up @@ -35,6 +35,7 @@
Platform.BINARY_SENSOR,
Platform.BUTTON,
Platform.NUMBER,
Platform.SELECT,
Platform.SENSOR,
Platform.SWITCH,
Platform.VALVE,
Expand Down Expand Up @@ -90,8 +91,10 @@ async def async_setup_entry(

name = entry.title
name = await client.read_char(DeviceConfiguration.custom_device_name, name)
name = await client.read_char(AquaContour.custom_device_name, name)

await _update_timestamp(client, DeviceConfiguration.unix_timestamp)
await _update_timestamp(client, AquaContour.unix_timestamp)

except (TimeoutError, CommunicationFailure, DeviceUnavailable) as exception:
await client.disconnect()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from dataclasses import dataclass, field

from gardena_bluetooth.const import Sensor, Valve
from gardena_bluetooth.const import AquaContour, Sensor, Valve
from gardena_bluetooth.parse import CharacteristicBool

from homeassistant.components.binary_sensor import (
Expand Down Expand Up @@ -47,6 +47,13 @@ def context(self) -> set[str]:
entity_category=EntityCategory.DIAGNOSTIC,
char=Sensor.connected_state,
),
GardenaBluetoothBinarySensorEntityDescription(
key=AquaContour.frost_warning.unique_id,
translation_key="frost_warning",
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
char=AquaContour.frost_warning,
),
)


Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/gardena_bluetooth/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def _is_supported(discovery_info: BluetoothServiceInfo):
ProductType.WATER_COMPUTER,
ProductType.AUTOMATS,
ProductType.PRESSURE_TANKS,
ProductType.AQUA_CONTOURS,
):
_LOGGER.debug("Unsupported device: %s", manufacturer_data)
return False
Expand Down Expand Up @@ -70,6 +71,7 @@ def __init__(self) -> None:

async def async_read_data(self):
"""Try to connect to device and extract information."""
assert self.address
client = Client(get_connection(self.hass, self.address))
try:
model = await client.read_char(DeviceInformation.model_number)
Expand Down
32 changes: 28 additions & 4 deletions homeassistant/components/gardena_bluetooth/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from dataclasses import dataclass, field

from gardena_bluetooth.const import DeviceConfiguration, Sensor, Valve
from gardena_bluetooth.const import DeviceConfiguration, Sensor, Spray, Valve
from gardena_bluetooth.parse import (
Characteristic,
CharacteristicInt,
Expand All @@ -18,7 +18,7 @@
NumberEntityDescription,
NumberMode,
)
from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfTime
from homeassistant.const import DEGREE, PERCENTAGE, EntityCategory, UnitOfTime
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

Expand All @@ -34,6 +34,7 @@ class GardenaBluetoothNumberEntityDescription(NumberEntityDescription):
default_factory=lambda: CharacteristicInt("")
)
connected_state: Characteristic | None = None
scale: float = 1.0

@property
def context(self) -> set[str]:
Expand Down Expand Up @@ -104,6 +105,27 @@ def context(self) -> set[str]:
char=Sensor.threshold,
connected_state=Sensor.connected_state,
),
GardenaBluetoothNumberEntityDescription(
key="spray_sector",
translation_key="spray_sector",
native_unit_of_measurement=DEGREE,
mode=NumberMode.BOX,
native_min_value=0.0,
native_max_value=359.0,
native_step=1.0,
char=Spray.sector,
),
GardenaBluetoothNumberEntityDescription(
key="spray_distance",
translation_key="spray_distance",
native_unit_of_measurement=PERCENTAGE,
mode=NumberMode.SLIDER,
native_min_value=0.0,
native_max_value=100.0,
native_step=0.1,
char=Spray.distance,
scale=10.0,
),
)


Expand Down Expand Up @@ -134,7 +156,7 @@ def _handle_coordinator_update(self) -> None:
if data is None:
self._attr_native_value = None
else:
self._attr_native_value = float(data)
self._attr_native_value = float(data) / self.entity_description.scale

if char := self.entity_description.connected_state:
self._attr_available = bool(self.coordinator.get_cached(char))
Expand All @@ -145,7 +167,9 @@ def _handle_coordinator_update(self) -> None:

async def async_set_native_value(self, value: float) -> None:
"""Set new value."""
await self.coordinator.write(self.entity_description.char, int(value))
await self.coordinator.write(
self.entity_description.char, int(value * self.entity_description.scale)
)
self.async_write_ha_state()


Expand Down
113 changes: 113 additions & 0 deletions homeassistant/components/gardena_bluetooth/select.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
"""Support for select entities."""

from __future__ import annotations

from dataclasses import dataclass, field
from enum import IntEnum

from gardena_bluetooth.const import (
AquaContour,
AquaContourPosition,
AquaContourWatering,
)
from gardena_bluetooth.parse import CharacteristicInt

from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from .coordinator import GardenaBluetoothConfigEntry
from .entity import GardenaBluetoothDescriptorEntity


def _enum_to_int(enum: type[IntEnum]) -> dict[str, int]:
return {member.name.lower(): member.value for member in enum}


def _reverse_dict(value: dict[str, int]) -> dict[int, str]:
return {value: key for key, value in value.items()}


@dataclass(frozen=True, kw_only=True)
class GardenaBluetoothSelectEntityDescription(SelectEntityDescription):
"""Description of entity."""

key: str = field(init=False)
char: CharacteristicInt
option_to_number: dict[str, int]
number_to_option: dict[int, str] = field(init=False)

def __post_init__(self):
"""Initialize calculated fields."""
object.__setattr__(self, "key", self.char.unique_id)
object.__setattr__(self, "options", list(self.option_to_number.keys()))
object.__setattr__(
self, "number_to_option", _reverse_dict(self.option_to_number)
)

@property
def context(self) -> set[str]:
"""Context needed for update coordinator."""
return {self.char.uuid}


DESCRIPTIONS = (
GardenaBluetoothSelectEntityDescription(
translation_key="watering_active",
char=AquaContourWatering.watering_active,
option_to_number=_enum_to_int(AquaContourWatering.watering_active.enum),
),
GardenaBluetoothSelectEntityDescription(
translation_key="operation_mode",
char=AquaContour.operation_mode,
option_to_number=_enum_to_int(AquaContour.operation_mode.enum),
),
GardenaBluetoothSelectEntityDescription(
translation_key="active_position",
char=AquaContourPosition.active_position,
option_to_number={
"position_1": 1,
"position_2": 2,
"position_3": 3,
"position_4": 4,
"position_5": 5,
},
),
)


async def async_setup_entry(
hass: HomeAssistant,
entry: GardenaBluetoothConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up select based on a config entry."""
coordinator = entry.runtime_data
entities = [
GardenaBluetoothSelectEntity(coordinator, description, description.context)
for description in DESCRIPTIONS
if description.char.unique_id in coordinator.characteristics
]
async_add_entities(entities)


class GardenaBluetoothSelectEntity(GardenaBluetoothDescriptorEntity, SelectEntity):
"""Representation of a select entity."""

entity_description: GardenaBluetoothSelectEntityDescription

@property
def current_option(self) -> str | None:
"""Return the selected entity option to represent the entity state."""
char = self.entity_description.char
value = self.coordinator.get_cached(char)
if value is None:
return None
return self.entity_description.number_to_option.get(value)

async def async_select_option(self, option: str) -> None:
"""Change the selected option."""
char = self.entity_description.char
value = self.entity_description.option_to_number[option]
await self.coordinator.write(char, value)
self.async_write_ha_state()
Loading
Loading