Skip to content
Open
Show file tree
Hide file tree
Changes from all 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