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
17 changes: 17 additions & 0 deletions custom_components/petkit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from homeassistant.loader import async_get_loaded_integration

from .const import (
ADVANCED_POLLING_SECTION,
BT_SECTION,
CONF_BLE_RELAY_ENABLED,
CONF_DELETE_AFTER,
Expand All @@ -32,7 +33,10 @@
CONF_MEDIA_EV_TYPE,
CONF_MEDIA_PATH,
CONF_SCAN_INTERVAL_BLUETOOTH,
CONF_SCAN_INTERVAL_DEFAULT,
CONF_SCAN_INTERVAL_MEDIA,
CONF_SCAN_INTERVAL_SLOW,
CONF_SMART_POLLING_BOOST_DURATION,
COORDINATOR,
COORDINATOR_BLUETOOTH,
COORDINATOR_MEDIA,
Expand All @@ -46,10 +50,12 @@
DEFAULT_SCAN_INTERVAL,
DEFAULT_SCAN_INTERVAL_BLUETOOTH,
DEFAULT_SCAN_INTERVAL_MEDIA,
DEFAULT_SMART_POLLING_BOOST_DURATION,
DOMAIN,
LOGGER,
MEDIA_SECTION,
NOTIFICATION_SECTION,
SCAN_INTERVAL_SLOW,
)
from .coordinator import (
PetkitBluetoothUpdateCoordinator,
Expand Down Expand Up @@ -385,6 +391,17 @@ async def async_migrate_entry(hass: HomeAssistant, entry: PetkitConfigEntry) ->
new_options[NOTIFICATION_SECTION] = section
hass.config_entries.async_update_entry(entry, options=new_options, version=8)

if entry.version < 9:
new_options = dict(entry.options)
polling_section = dict(new_options.get(ADVANCED_POLLING_SECTION, {}))
polling_section.setdefault(CONF_SCAN_INTERVAL_SLOW, SCAN_INTERVAL_SLOW)
polling_section.setdefault(CONF_SCAN_INTERVAL_DEFAULT, DEFAULT_SCAN_INTERVAL)
polling_section.setdefault(
CONF_SMART_POLLING_BOOST_DURATION, DEFAULT_SMART_POLLING_BOOST_DURATION
)
new_options[ADVANCED_POLLING_SECTION] = polling_section
hass.config_entries.async_update_entry(entry, options=new_options, version=9)

return True


Expand Down
2 changes: 1 addition & 1 deletion custom_components/petkit/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ def is_on(self) -> bool | None:
and value
and self.coordinator.fast_poll_tic < 1
):
self.coordinator.enable_smart_polling(3)
self.coordinator.enable_smart_polling()

return value
return None
2 changes: 1 addition & 1 deletion custom_components/petkit/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ def available(self) -> bool:
async def async_press(self) -> None:
"""Handle the button press."""
LOGGER.debug("Button pressed: %s", self.entity_description.key)
self.coordinator.enable_smart_polling(3)
self.coordinator.enable_smart_polling()
await self.entity_description.action(
self.coordinator.config_entry.runtime_data.client, self.device
)
Expand Down
47 changes: 46 additions & 1 deletion custom_components/petkit/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from homeassistant.helpers.selector import BooleanSelector, BooleanSelectorConfig

from .const import (
ADVANCED_POLLING_SECTION,
ADVANCED_SECTION,
ALL_TIMEZONES_LST,
BT_SECTION,
Expand All @@ -47,7 +48,10 @@
CONF_MEDIA_EV_TYPE,
CONF_MEDIA_PATH,
CONF_SCAN_INTERVAL_BLUETOOTH,
CONF_SCAN_INTERVAL_DEFAULT,
CONF_SCAN_INTERVAL_MEDIA,
CONF_SCAN_INTERVAL_SLOW,
CONF_SMART_POLLING_BOOST_DURATION,
COUNTRY_TO_CODE_DICT,
DEFAULT_BLUETOOTH_RELAY,
DEFAULT_DELETE_AFTER,
Expand All @@ -56,13 +60,16 @@
DEFAULT_ENABLED_NOTIFICATIONS,
DEFAULT_EVENTS,
DEFAULT_MEDIA_PATH,
DEFAULT_SCAN_INTERVAL,
DEFAULT_SCAN_INTERVAL_BLUETOOTH,
DEFAULT_SCAN_INTERVAL_MEDIA,
DEFAULT_SMART_POLLING_BOOST_DURATION,
DOMAIN,
LOGGER,
MEDIA_SECTION,
NOTIFICATION_CATEGORIES,
NOTIFICATION_SECTION,
SCAN_INTERVAL_SLOW,
)


Expand Down Expand Up @@ -188,6 +195,37 @@ async def async_step_init(
),
{"collapsed": False},
),
vol.Required(ADVANCED_POLLING_SECTION): section(
vol.Schema(
{
vol.Required(
CONF_SCAN_INTERVAL_SLOW,
default=self.config_entry.options.get(
ADVANCED_POLLING_SECTION, {}
).get(CONF_SCAN_INTERVAL_SLOW, SCAN_INTERVAL_SLOW),
): vol.All(int, vol.Range(min=5, max=600)),
vol.Required(
CONF_SCAN_INTERVAL_DEFAULT,
default=self.config_entry.options.get(
ADVANCED_POLLING_SECTION, {}
).get(
CONF_SCAN_INTERVAL_DEFAULT,
DEFAULT_SCAN_INTERVAL,
),
): vol.All(int, vol.Range(min=5, max=600)),
vol.Required(
CONF_SMART_POLLING_BOOST_DURATION,
default=self.config_entry.options.get(
ADVANCED_POLLING_SECTION, {}
).get(
CONF_SMART_POLLING_BOOST_DURATION,
DEFAULT_SMART_POLLING_BOOST_DURATION,
),
): vol.All(int, vol.Range(min=5, max=300)),
}
),
{"collapsed": True},
),
}
),
)
Expand All @@ -196,7 +234,7 @@ async def async_step_init(
class PetkitFlowHandler(ConfigFlow, domain=DOMAIN):
"""Config flow for Petkit Smart Devices."""

VERSION = 8
VERSION = 9

@staticmethod
@callback
Expand Down Expand Up @@ -313,6 +351,13 @@ async def async_step_user(
DEFAULT_ENABLED_NOTIFICATIONS
),
},
ADVANCED_POLLING_SECTION: {
CONF_SCAN_INTERVAL_SLOW: SCAN_INTERVAL_SLOW,
CONF_SCAN_INTERVAL_DEFAULT: DEFAULT_SCAN_INTERVAL,
CONF_SMART_POLLING_BOOST_DURATION: (
DEFAULT_SMART_POLLING_BOOST_DURATION
),
},
},
)

Expand Down
9 changes: 9 additions & 0 deletions custom_components/petkit/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@

ADVANCED_SECTION = "advanced_options"

ADVANCED_POLLING_SECTION = "advanced_polling_options"
CONF_SCAN_INTERVAL_SLOW = "scan_interval_slow"
CONF_SCAN_INTERVAL_DEFAULT = "scan_interval_default"
CONF_SMART_POLLING_BOOST_DURATION = "smart_polling_boost_duration"

BT_SECTION = "bluetooth_options"
CONF_BLE_RELAY_ENABLED = "ble_relay_enabled"
CONF_SCAN_INTERVAL_BLUETOOTH = "scan_interval_bluetooth"
Expand Down Expand Up @@ -82,6 +87,10 @@
SCAN_INTERVAL_FAST = 5
SCAN_INTERVAL_SLOW = 190

# Default smart polling boost duration in seconds. The previous behaviour was
# 3 ticks at SCAN_INTERVAL_FAST (5 sec), so the equivalent default is 15 sec.
DEFAULT_SMART_POLLING_BOOST_DURATION = 15

# Messages constants
NO_ERROR = "No error"

Expand Down
46 changes: 42 additions & 4 deletions custom_components/petkit/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,25 @@
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .const import (
ADVANCED_POLLING_SECTION,
BT_SECTION,
CONF_BLE_RELAY_ENABLED,
CONF_DELETE_AFTER,
CONF_MEDIA_DL_IMAGE,
CONF_MEDIA_DL_VIDEO,
CONF_MEDIA_EV_TYPE,
CONF_MEDIA_PATH,
CONF_SCAN_INTERVAL_DEFAULT,
CONF_SCAN_INTERVAL_SLOW,
CONF_SMART_POLLING_BOOST_DURATION,
DEFAULT_BLUETOOTH_RELAY,
DEFAULT_DELETE_AFTER,
DEFAULT_DL_IMAGE,
DEFAULT_DL_VIDEO,
DEFAULT_EVENTS,
DEFAULT_MEDIA_PATH,
DEFAULT_SCAN_INTERVAL,
DEFAULT_SMART_POLLING_BOOST_DURATION,
DOMAIN,
LOGGER,
MEDIA_SECTION,
Expand All @@ -73,21 +78,52 @@ def __init__(self, hass, logger, name, update_interval, config_entry):
self.current_devices = set()
self.fast_poll_tic = 0
self.mqtt_connected = False
self._reload_polling_options()

def _reload_polling_options(self) -> None:
"""Read polling intervals from the config entry options.

Falls back to historical defaults when the section is missing so
existing entries (and tests that build entries without options)
keep working without an explicit migration step.
"""
polling_options = self.config_entry.options.get(ADVANCED_POLLING_SECTION, {})
self.scan_interval_slow = polling_options.get(
CONF_SCAN_INTERVAL_SLOW, SCAN_INTERVAL_SLOW
)
self.scan_interval_default = polling_options.get(
CONF_SCAN_INTERVAL_DEFAULT, DEFAULT_SCAN_INTERVAL
)
self.smart_polling_boost_duration = polling_options.get(
CONF_SMART_POLLING_BOOST_DURATION, DEFAULT_SMART_POLLING_BOOST_DURATION
)

def enable_smart_polling(self, nb_tic: int) -> None:
"""Enable smart polling."""
def enable_smart_polling(self, boost_duration: int | None = None) -> None:
"""Enable smart polling for the configured boost duration.

Args:
boost_duration: Override duration in seconds. When ``None`` (the
default), the coordinator uses the user-configured value from
``advanced_polling_options.smart_polling_boost_duration``.

"""
if self.fast_poll_tic > 0:
LOGGER.debug(
"Fast poll tic already enabled for %s tics", self.fast_poll_tic
)
return

if boost_duration is None:
boost_duration = self.smart_polling_boost_duration

nb_tic = max(1, boost_duration // SCAN_INTERVAL_FAST)
self.update_interval = timedelta(seconds=SCAN_INTERVAL_FAST)
self.fast_poll_tic = nb_tic
LOGGER.debug(
"Fast poll tic enabled for %s tics (at %ssec interval)",
"Fast poll tic enabled for %s tics (at %ssec interval, ~%ss total)",
nb_tic,
SCAN_INTERVAL_FAST,
nb_tic * SCAN_INTERVAL_FAST,
)

async def _update_smart_polling(self) -> None:
Expand All @@ -97,7 +133,9 @@ async def _update_smart_polling(self) -> None:
LOGGER.debug("Fast poll tic remaining: %s", self.fast_poll_tic)
else:
base_interval = (
SCAN_INTERVAL_SLOW if self.mqtt_connected else DEFAULT_SCAN_INTERVAL
self.scan_interval_slow
if self.mqtt_connected
else self.scan_interval_default
)

if self.update_interval != timedelta(seconds=base_interval):
Expand Down
14 changes: 14 additions & 0 deletions custom_components/petkit/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,20 @@
},
"description": "Control which PetKit device events raise a Home Assistant persistent notification independently from the Petkit app settings.",
"name": "Notifications"
},
"advanced_polling_options": {
"data": {
"scan_interval_slow": "Refresh interval — MQTT connected (seconds)",
"scan_interval_default": "Refresh interval — MQTT disconnected (seconds)",
"smart_polling_boost_duration": "Smart polling boost duration (seconds)"
},
"data_description": {
"scan_interval_slow": "How often to poll the PetKit cloud while the IoT MQTT listener is connected. The default (190 s) keeps cloud load low because MQTT is expected to push prompt updates between polls. Lower it (e.g. 5–30 s) when you need near-real-time entity updates and accept the extra REST traffic.",
"scan_interval_default": "Fallback poll interval used when the IoT MQTT listener is disconnected. Defaults to 60 s so entities recover quickly if the MQTT channel drops.",
"smart_polling_boost_duration": "When an event-triggered entity (e.g. toilet occupied) flips on, the coordinator briefly polls every 5 s to capture the follow-up state changes. This setting controls how long that fast burst lasts. Default is 15 s (≈3 fast ticks)."
},
"description": "Tune how aggressively the coordinator polls the PetKit REST API. Lower intervals improve responsiveness at the cost of additional API requests; raise them to reduce cloud usage. Changes take effect immediately after saving.",
"name": "Advanced polling"
}
},
"title": "Petkit integration configuration"
Expand Down
24 changes: 24 additions & 0 deletions custom_components/petkit/translations/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,30 @@
},
"description": "Эти настройки применимы, если используются устройства, которые оснащены камерой и могут записывать фото и видео",
"name": "Настройки фото и видео"
},
"notifications_options": {
"data": {
"enabled_notifications": "Включённые уведомления"
},
"data_description": {
"enabled_notifications": "Выберите, какие события PetKit показывать как постоянные уведомления Home Assistant. Снятие галочки отключает категорию только в Home Assistant — настройки уведомлений в приложении PetKit это не затрагивает."
},
"description": "Управление тем, какие события устройств PetKit вызывают постоянное уведомление в Home Assistant — независимо от настроек самого приложения PetKit.",
"name": "Уведомления"
},
"advanced_polling_options": {
"data": {
"scan_interval_slow": "Интервал опроса при подключённом MQTT (секунды)",
"scan_interval_default": "Интервал опроса при отключённом MQTT (секунды)",
"smart_polling_boost_duration": "Длительность ускоренного опроса (секунды)"
},
"data_description": {
"scan_interval_slow": "Как часто опрашивать облако PetKit, пока MQTT-слушатель подключён. По умолчанию 190 с — нагрузка на облако минимальна, потому что MQTT должен передавать обновления между опросами. Уменьшите (например, до 5–30 с), если нужны почти мгновенные обновления — это даст дополнительный REST-трафик.",
"scan_interval_default": "Запасной интервал опроса, когда MQTT-слушатель отключён. По умолчанию 60 с — состояния быстро восстанавливаются при потере MQTT.",
"smart_polling_boost_duration": "Когда срабатывает событийный сенсор (например, «лоток занят»), координатор кратковременно опрашивает каждые 5 с, чтобы не пропустить последующие изменения. Эта настройка задаёт длительность такого ускоренного опроса. По умолчанию 15 с (≈3 быстрых опроса)."
},
"description": "Тонкая настройка того, как агрессивно координатор опрашивает REST API PetKit. Меньшие интервалы — быстрее обновления, но больше запросов к облаку; большие — наоборот. Изменения применяются сразу после сохранения.",
"name": "Расширенные настройки опроса"
}
},
"title": "Настройки интеграции PetKit"
Expand Down