Skip to content

Commit 0b4a7db

Browse files
committed
Merge branch 'main' of https://github.com/iluvdata/liebherr
2 parents c03e8b5 + 097cc18 commit 0b4a7db

18 files changed

Lines changed: 282 additions & 94 deletions

File tree

README.md

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ This is an *unofficial* custom integration for Home Assistant that allows you to
1414

1515
### HACS (Recommended)
1616

17-
> [!Warning]
18-
> If you are upgrading from the `bhuebschen/liebherr` version, it's recommended to remove this first to avoid orphaned devices/entities.
1917

2018
[![Open your Home Assistant instance and open a repository inside the Home Assistant Community Store.](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?category=integration&owner=iluvdata&repository=liebherr)
2119

@@ -41,6 +39,7 @@ or search for the Liebherr integration in HACS
4139

4240
1. Enter your Liebherr HomeAPI API key. (see [here](https://developer.liebherr.com/apis/smartdevice-homeapi/), how to get the key)
4341
2. Complete the setup process.
42+
3. (Optional): Configure the polling interval and the entity type for Presentation Light (see below).
4443

4544
## Usage
4645
Once the integration is configured, your Liebherr devices will appear as entities in Home Assistant. You can:
@@ -51,14 +50,17 @@ Once the integration is configured, your Liebherr devices will appear as entitie
5150
Controls will map to the following domains:
5251
| Liebherr Control | Homeassistant Domain |
5352
| -----------------| ---------------------|
54-
| Ice Maker, BioFreshPlus | Select |
55-
| Presentation Light | Light |
53+
| Auto Door | Cover |
54+
|Ice Maker, BioFreshPlus | Select |
55+
| Presentation Light | Light or Number*|
5656
| SuperCool, SuperFreeze, PartyMode, NightMode | Switch|
5757
| HydroBreeze | Fan |
5858
| Temperature | Climate |
5959
| `image_url` (Device) | Image |
6060
| Bottle Timer | Not available |
6161

62+
*\* In version ≥ 2025.12.5 the domain/type of control/entity created can be selected in the integration options.*
63+
6264
### Discover New Appliances
6365

6466
Currently appliances added to your Liebherr account will not be automatically discovered. Once an appliance is connected to your Liebherr account (and accessible in the SmartHome app) manually reload the integration from the integration screen:
@@ -71,26 +73,21 @@ and click on "Reload" on the configuration menu:
7173

7274
## Update Interval
7375

74-
### Version 2025.10.4
75-
76-
Given rate limits imposed by Liebherr in the [SmartDevice Home API](https://developer.liebherr.com/apis/smartdevice-homeapi/#advice-for-implementation) the integration can only make a request to the API every 30s. The interval between poll updates depends on the number of devices (since controls have to be requested separately for each device) and is determined by:
77-
78-
$$
79-
polling\ interval = number\ of\ devices\ ×\ 30\ seconds
80-
$$
81-
#### Example
82-
If you have 4 Liebherr devices associated with your account the update interval will be $4 × 30$ seconds $= 2$ minutes. Over this two minute period each device will be updated *sequentially* at 30 second intervals:
76+
Given rate limits imposed by Liebherr in the beta [SmartDevice Home API](https://developer.liebherr.com/apis/smartdevice-homeapi/#advice-for-implementation) the integration can only make a request to the device control API more often than every 30s.
8377

84-
> Device 1 > 30 seconds > Device 2 > 30 seconds > Device 3 > 30 Seconds > Device 4 > 30 seconds > Device 1 > ...
78+
> [!NOTE] Last Updated Sensor
79+
> A diagnostic sensor will be created for each device showing the last timestamp of the most recent poll but is disabled by default (as it will quickly fill up your database with state changes).
8580
86-
### Version 2025.12.0
81+
### Version 2025.12.0
8782

8883
This version will calculate the polling interval based on the number of devices/appliances associated with your Liebherr account. Essentially the goal is to poll each device's controls every 30 seconds and is calculated thusly:
8984

9085
```math
9186
poll\ interval=\frac{30\ seconds}{number\ of\ devices}
9287
```
93-
With a minimun poll interval of 30 seconds.
88+
With a minimun poll interval of 30 seconds.
89+
90+
The polling interval can be adjusted manual (within some present limits) by changing the integration options.
9491

9592
## Troubleshooting
9693
- Ensure your Liebherr api key is correct.
@@ -102,8 +99,6 @@ With a minimun poll interval of 30 seconds.
10299
## Acknowledgements
103100
This is a rewrite of the great [custom intergration](https://github.com/bhuebschen/liebherr) orginally maintained by @bhuebschen from a fork created by @skatsavos. The original intergration stopped working in Oct 2025 and the orginal maintainer did not appear to be maintaining the project.
104101

105-
> [!Warning]
106-
> This is nearly a complete rewrite to the orginal integration. As such there is not a suitable upgrade path. Start by removing the prior liebherr entry and HACS respository from your Homeassistant **before** proceeding with installation.
107102

108103
> [!Warning]
109104
> This was tested on a Liebherr Device lacking:
@@ -112,15 +107,7 @@ This is a rewrite of the great [custom intergration](https://github.com/bhuebsch
112107
>> - BioFreshPlus (reported to be working)
113108
>> - HydroBreeze (reported to be working)
114109
>
115-
> If you encounter an issue with these features please submit an issue [here](https://github.com/iluvdata/liebherr/issues).
116-
117-
### Significant Changes from [bhuebschen/liebherr v0.1.1](https://github.com/bhuebschen/liebherr)
118-
- Data is now pulled from the API using a single "coordinated" pull set on a configurable interval per https://github.com/bhuebschen/liebherr/issues/44#issuecomment-3442421338 and https://developer.liebherr.com/apis/smartdevice-homeapi/ set to a default interval of 30s.
119-
- Added support for all the support features in the API such as IceMaker Control, BioFreshPlus, AutoDoor, Presentation Light (wine fridges) and HydroBreeze (see Caution above).
120-
- Translation support has been greatly expanded but please note I was not able to update the many translations (please feel free to contribute)!
121-
- Device/appliance list will only be queried upon setup. If you add a new device to your Liebherr account you with have to "Reload" the integration in Homeassitant (or restart Homeassistant).
122-
- The integration was modernized to align better with Homeassistant's development standards https://developers.home-assistant.io/docs/development_index and remove the use of deprecated functions.
123-
110+
> If you encounter an issue with these features please submit an issue.
124111
125112
## Support
126113
If you encounter any issues or have feature requests, please open an issue on the [GitHub Issues page](https://github.com/iluvdata/liebherr/issues).

custom_components/liebherr/__init__.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Liebherr HomeAPI for HomeAssistant."""
22

33
import logging
4+
from typing import Any
45

56
from pyliebherr.exception import LiebherrAuthException
67

@@ -9,6 +10,7 @@
910
from homeassistant.core import HomeAssistant
1011
from homeassistant.exceptions import ConfigEntryAuthFailed
1112

13+
from .const import CONF_PRESENTATION_LIGHT_AS_NUMBER
1214
from .coordinator import LiebherrConfigEntry, LiebherrCoordinator
1315

1416
_LOGGER = logging.getLogger(__name__)
@@ -18,7 +20,6 @@
1820
Platform.COVER,
1921
Platform.FAN,
2022
Platform.IMAGE,
21-
Platform.LIGHT,
2223
Platform.SENSOR,
2324
Platform.SELECT,
2425
Platform.SWITCH,
@@ -40,31 +41,33 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: LiebherrConfigEnt
4041

4142
config_entry.runtime_data = coordinator
4243

44+
if config_entry.options.get(CONF_PRESENTATION_LIGHT_AS_NUMBER, False):
45+
PLATFORMS.add(Platform.NUMBER)
46+
else:
47+
# Add Light to platforms
48+
PLATFORMS.add(Platform.LIGHT)
49+
4350
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
4451

4552
return True
4653

4754

4855
async def async_migrate_entry(hass: HomeAssistant, config_entry: LiebherrConfigEntry):
4956
"""Migrate old entry to newer version."""
50-
_LOGGER.debug(
51-
"Migrating configuration from version %s.%s",
52-
config_entry.version,
53-
config_entry.minor_version,
54-
)
5557

5658
if config_entry.version == 1:
5759
if config_entry.minor_version < 2:
58-
# No longer using options for interval
60+
# No longer using options for interval, since reverted
5961
hass.config_entries.async_update_entry(
6062
config_entry, options={}, minor_version=2, version=1
6163
)
62-
63-
_LOGGER.debug(
64-
"Migration to configuration version %s.%s successful",
65-
config_entry.version,
66-
config_entry.minor_version,
67-
)
64+
if config_entry.minor_version < 3:
65+
# Add presentation_light_as_select option defaulting to False
66+
options: dict[str, Any] = config_entry.options.copy()
67+
options[CONF_PRESENTATION_LIGHT_AS_NUMBER] = False
68+
hass.config_entries.async_update_entry(
69+
config_entry, options=options, minor_version=3, version=1
70+
)
6871

6972
return True
7073

custom_components/liebherr/climate.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,4 +132,5 @@ def _handle_coordinator_update(self) -> None:
132132
self._attr_min_temp = control.min
133133
if control.max:
134134
self._attr_max_temp = control.max
135-
self.async_write_ha_state()
135+
self.async_write_ha_state()
136+
return

custom_components/liebherr/config_flow.py

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
)
1717
from homeassistant.const import CONF_API_KEY
1818
from homeassistant.core import callback
19+
from homeassistant.data_entry_flow import section
1920
from homeassistant.helpers.selector import (
21+
BooleanSelector,
2022
NumberSelector,
2123
NumberSelectorConfig,
2224
NumberSelectorMode,
@@ -26,13 +28,44 @@
2628
from . import _LOGGER
2729
from .const import (
2830
CONF_POLL_INTERVAL,
31+
CONF_PRESENTATION_LIGHT_AS_NUMBER,
2932
DEFAULT_UPDATE_INTERVAL,
3033
DOMAIN,
3134
MAX_UPDATE_INTERVAL,
3235
MIN_UPDATE_INTERVAL,
3336
)
3437
from .coordinator import LiebherrConfigEntry
3538

39+
POLLING_SECTION: str = "polling_options"
40+
LIGHT_SECTION: str = "presentation_light_options"
41+
42+
OPTIONS_SCHEMA: vol.Schema = vol.Schema(
43+
{
44+
vol.Required(POLLING_SECTION): section(
45+
vol.Schema(
46+
{
47+
vol.Required(CONF_POLL_INTERVAL): NumberSelector(
48+
NumberSelectorConfig(
49+
min=MIN_UPDATE_INTERVAL,
50+
max=MAX_UPDATE_INTERVAL, # 5 minutes
51+
step=1,
52+
unit_of_measurement="s",
53+
mode=NumberSelectorMode.BOX,
54+
)
55+
)
56+
}
57+
)
58+
),
59+
vol.Required(LIGHT_SECTION): section(
60+
vol.Schema(
61+
{
62+
vol.Required(CONF_PRESENTATION_LIGHT_AS_NUMBER): BooleanSelector(),
63+
}
64+
)
65+
),
66+
}
67+
)
68+
3669

3770
def async_calculate_poll_interval(number_of_devices: int) -> int:
3871
"""Calculate poll interval based on number of devices."""
@@ -57,33 +90,32 @@ async def async_step_init(
5790
if user_input is not None:
5891
return self.async_create_entry(
5992
data={
60-
CONF_POLL_INTERVAL: user_input[CONF_POLL_INTERVAL],
93+
CONF_POLL_INTERVAL: user_input[POLLING_SECTION][CONF_POLL_INTERVAL],
94+
CONF_PRESENTATION_LIGHT_AS_NUMBER: user_input[LIGHT_SECTION][
95+
CONF_PRESENTATION_LIGHT_AS_NUMBER
96+
],
6197
}
6298
)
6399

64-
data_schema: vol.Schema = vol.Schema(
65-
{
66-
vol.Required(CONF_POLL_INTERVAL): NumberSelector(
67-
NumberSelectorConfig(
68-
min=MIN_UPDATE_INTERVAL,
69-
max=MAX_UPDATE_INTERVAL, # 5 minutes
70-
step=1,
71-
unit_of_measurement="s",
72-
mode=NumberSelectorMode.BOX,
73-
)
74-
),
75-
}
76-
)
77100
suggested_values = {
78-
CONF_POLL_INTERVAL: self.config_entry.options.get(
79-
CONF_POLL_INTERVAL,
80-
async_calculate_poll_interval(len(self.config_entry.runtime_data.data)),
81-
),
101+
POLLING_SECTION: {
102+
CONF_POLL_INTERVAL: self.config_entry.options.get(
103+
CONF_POLL_INTERVAL,
104+
async_calculate_poll_interval(
105+
len(self.config_entry.runtime_data.data)
106+
),
107+
)
108+
},
109+
LIGHT_SECTION: {
110+
CONF_PRESENTATION_LIGHT_AS_NUMBER: self.config_entry.options.get(
111+
CONF_PRESENTATION_LIGHT_AS_NUMBER, False
112+
),
113+
},
82114
}
83115
return self.async_show_form(
84116
step_id="init",
85117
data_schema=self.add_suggested_values_to_schema(
86-
data_schema, suggested_values
118+
OPTIONS_SCHEMA, suggested_values
87119
),
88120
description_placeholders={
89121
"min_int": str(MIN_UPDATE_INTERVAL),
@@ -103,7 +135,7 @@ class LiebherrConfigFlow(ConfigFlow, domain=DOMAIN):
103135

104136
VERSION = 1
105137

106-
MINOR_VERSION = 2
138+
MINOR_VERSION = 3
107139

108140
async def async_step_user(
109141
self, user_input: dict[str, str] | None = None

custom_components/liebherr/const.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
MAX_UPDATE_INTERVAL: Final[int] = 300 # in seconds, 5 minutes
1111

1212
CONF_POLL_INTERVAL: Final[str] = "poll_interval"
13+
CONF_PRESENTATION_LIGHT_AS_NUMBER: Final[str] = "presentation_light_as_number"
1314

1415
BRIGHTNESS_SCALE: Final[dict[str, tuple[int, int]]] = {"WPgbi 7472-20": (1, 5)}
1516
DEFAULT_BRIGHTNESS_SCALE: Final[tuple[int, int]] = (1, 4)

custom_components/liebherr/cover.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,8 @@ def _handle_coordinator_update(self) -> None:
117117
self._last_state = (
118118
STATE_OPENING if self._attr_is_opening else STATE_CLOSING
119119
)
120-
break
121-
self.async_write_ha_state()
120+
self.async_write_ha_state()
121+
return
122122

123123
async def async_open_cover(self, **kwargs):
124124
"""Send command to open the cover."""

custom_components/liebherr/diagnostics.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,6 @@ async def async_get_config_entry_diagnostics(
2323

2424
return {
2525
"entry_data": async_redact_data(entry.data, TO_REDACT),
26+
"options": async_redact_data(entry.options, TO_REDACT),
2627
"data": async_redact_data(devices, TO_REDACT),
2728
}

custom_components/liebherr/fan.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ def _handle_coordinator_update(self) -> None:
9494
self._attr_percentage = ordered_list_item_to_percentage(
9595
list(HydroBreezeControlRequest.HydroBreezeMode), mode
9696
)
97-
self.async_write_ha_state()
97+
self.async_write_ha_state()
98+
return
9899

99100
async def _async_set_mode(
100101
self, mode: HydroBreezeControlRequest.HydroBreezeMode

custom_components/liebherr/light.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ def _handle_coordinator_update(self) -> None:
6262
self._attr_is_on = True
6363
else:
6464
self._attr_is_on = False
65-
self.async_write_ha_state()
65+
self.async_write_ha_state()
66+
return
6667

6768
async def async_turn_on(
6869
self,

custom_components/liebherr/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@
1212
"loggers": ["pyliebherr"],
1313
"requirements": ["pyliebherr==2025.11.4", "Pillow>=11.0.0"],
1414
"single_config_entry": true,
15-
"version": "2025.12.4"
15+
"version": "2026.1.1"
1616
}

0 commit comments

Comments
 (0)