Options flow patch (apply on top of lock-cycle-transitions.patch)

This commit is contained in:
2026-07-22 09:57:34 +03:00
parent 0e3e56c4db
commit c2dfdd450a
+270
View File
@@ -0,0 +1,270 @@
diff --git a/custom_components/tuya_local_ble/__init__.py b/custom_components/tuya_local_ble/__init__.py
index d000ccd..d1a060b 100644
--- a/custom_components/tuya_local_ble/__init__.py
+++ b/custom_components/tuya_local_ble/__init__.py
@@ -89,7 +89,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
- #entry.async_on_unload(entry.add_update_listener(_async_update_listener))
+ entry.async_on_unload(entry.add_update_listener(_async_update_listener))
async def _async_stop(event: Event) -> None:
"""Close the connection."""
@@ -100,6 +100,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
)
return True
+async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
+ """Reload the entry when options change."""
+ await hass.config_entries.async_reload(entry.entry_id)
+
+
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
diff --git a/custom_components/tuya_local_ble/config_flow.py b/custom_components/tuya_local_ble/config_flow.py
index a89eff8..d93947a 100644
--- a/custom_components/tuya_local_ble/config_flow.py
+++ b/custom_components/tuya_local_ble/config_flow.py
@@ -8,8 +8,10 @@ from typing import Any
import voluptuous as vol
from homeassistant.config_entries import (
+ ConfigEntry,
ConfigFlow,
- ConfigFlowResult
+ ConfigFlowResult,
+ OptionsFlow,
)
from homeassistant.components.bluetooth import (
BluetoothServiceInfoBleak,
@@ -18,22 +20,45 @@ from homeassistant.components.bluetooth import (
from homeassistant.const import CONF_ADDRESS
from homeassistant.core import callback
#from homeassistant.data_entry_flow import FlowResult
+from homeassistant.helpers.selector import (
+ NumberSelector,
+ NumberSelectorConfig,
+ NumberSelectorMode,
+ TextSelector,
+ TextSelectorConfig,
+)
from .tuya_ble import SERVICE_UUID, TuyaBLEDeviceCredentials
from .const import (
DOMAIN,
+ CONF_BLE_UNLOCK_CHECK,
+ CONF_LOCK_CYCLE_SECONDS,
+ CONF_UNLOCK_CYCLE_SECONDS,
)
from .devices import TuyaBLEData, get_device_readable_name
from .keyman import HASSTuyaBLEDeviceManager
_LOGGER = logging.getLogger(__name__)
+OPTIONS_KEYS = (
+ CONF_LOCK_CYCLE_SECONDS,
+ CONF_UNLOCK_CYCLE_SECONDS,
+ CONF_BLE_UNLOCK_CHECK,
+)
+
+
class TuyaBLEConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Tuya BLE."""
VERSION = 1
+ @staticmethod
+ @callback
+ def async_get_options_flow(config_entry: ConfigEntry) -> TuyaBLEOptionsFlow:
+ """Get the options flow for this handler."""
+ return TuyaBLEOptionsFlow()
+
def __init__(self) -> None:
"""Initialize the config flow."""
super().__init__()
@@ -132,3 +157,61 @@ class TuyaBLEConfigFlow(ConfigFlow, domain=DOMAIN):
),
errors=errors,
)
+
+
+class TuyaBLEOptionsFlow(OptionsFlow):
+ """Handle options for a Tuya BLE device.
+
+ Values set here override the matching fields in devices.json; an empty
+ field falls back to devices.json (or the built-in default).
+ """
+
+ async def async_step_init(
+ self, user_input: dict[str, Any] | None = None
+ ) -> ConfigFlowResult:
+ """Manage the device options."""
+ if user_input is not None:
+ options = dict(self.config_entry.options)
+ for key in OPTIONS_KEYS:
+ value = user_input.get(key)
+ if value in (None, ""):
+ options.pop(key, None)
+ else:
+ options[key] = value
+ return self.async_create_entry(title="", data=options)
+
+ options = self.config_entry.options
+ cycle_selector = NumberSelector(
+ NumberSelectorConfig(
+ min=1,
+ max=120,
+ step=0.1,
+ unit_of_measurement="s",
+ mode=NumberSelectorMode.BOX,
+ )
+ )
+ return self.async_show_form(
+ step_id="init",
+ data_schema=vol.Schema(
+ {
+ vol.Optional(
+ CONF_LOCK_CYCLE_SECONDS,
+ description={
+ "suggested_value": options.get(CONF_LOCK_CYCLE_SECONDS)
+ },
+ ): cycle_selector,
+ vol.Optional(
+ CONF_UNLOCK_CYCLE_SECONDS,
+ description={
+ "suggested_value": options.get(CONF_UNLOCK_CYCLE_SECONDS)
+ },
+ ): cycle_selector,
+ vol.Optional(
+ CONF_BLE_UNLOCK_CHECK,
+ description={
+ "suggested_value": options.get(CONF_BLE_UNLOCK_CHECK)
+ },
+ ): TextSelector(TextSelectorConfig()),
+ },
+ ),
+ )
diff --git a/custom_components/tuya_local_ble/keyman.py b/custom_components/tuya_local_ble/keyman.py
index ea2d4ef..02e4e73 100644
--- a/custom_components/tuya_local_ble/keyman.py
+++ b/custom_components/tuya_local_ble/keyman.py
@@ -84,6 +84,15 @@ class HASSTuyaBLEDeviceManager(AbstaractTuyaBLEDeviceManager):
credentials = self._devicedata.get(address)
if credentials:
+ # Options set via the integration UI override devices.json values.
+ for key in (
+ CONF_BLE_UNLOCK_CHECK,
+ CONF_LOCK_CYCLE_SECONDS,
+ CONF_UNLOCK_CYCLE_SECONDS,
+ ):
+ value = self._data.get(key)
+ if value not in (None, ""):
+ credentials = {**credentials, key: value}
result = TuyaBLEDeviceCredentials(
credentials.get(CONF_UUID, ""),
credentials.get(CONF_LOCAL_KEY, ""),
diff --git a/custom_components/tuya_local_ble/strings.json b/custom_components/tuya_local_ble/strings.json
index 76f3fb2..ef92e87 100644
--- a/custom_components/tuya_local_ble/strings.json
+++ b/custom_components/tuya_local_ble/strings.json
@@ -210,5 +210,23 @@
"name": "Program: position[/time];..."
}
}
+ },
+ "options": {
+ "step": {
+ "init": {
+ "title": "Tuya Local BLE options",
+ "description": "These values override the matching fields in config/tuya_local_ble/devices.json. Leave a field empty to fall back to devices.json (or the built-in default). If the device stops working after a change, clear the fields here or fix devices.json directly.",
+ "data": {
+ "lock_cycle_seconds": "Lock cycle time (s)",
+ "unlock_cycle_seconds": "Unlock cycle time (s)",
+ "ble_unlock_check": "ble_unlock_check (base64)"
+ },
+ "data_description": {
+ "lock_cycle_seconds": "How long the motor runs when locking, including the pause at the end stop. New commands are rejected while the cycle is running.",
+ "unlock_cycle_seconds": "Same as above, for unlocking.",
+ "ble_unlock_check": "Raw Tuya status value used to authorize remote unlock. Refresh it from the Tuya IoT cloud (Get Status Reporting Log) if unlock starts returning error code 1."
+ }
+ }
+ }
}
}
diff --git a/custom_components/tuya_local_ble/translations/en.json b/custom_components/tuya_local_ble/translations/en.json
index 019f00e..68d63af 100644
--- a/custom_components/tuya_local_ble/translations/en.json
+++ b/custom_components/tuya_local_ble/translations/en.json
@@ -209,6 +209,23 @@
"name": "Program: position[/time];..."
}
}
+ },
+ "options": {
+ "step": {
+ "init": {
+ "title": "Tuya Local BLE options",
+ "description": "These values override the matching fields in config/tuya_local_ble/devices.json. Leave a field empty to fall back to devices.json (or the built-in default). If the device stops working after a change, clear the fields here or fix devices.json directly.",
+ "data": {
+ "lock_cycle_seconds": "Lock cycle time (s)",
+ "unlock_cycle_seconds": "Unlock cycle time (s)",
+ "ble_unlock_check": "ble_unlock_check (base64)"
+ },
+ "data_description": {
+ "lock_cycle_seconds": "How long the motor runs when locking, including the pause at the end stop. New commands are rejected while the cycle is running.",
+ "unlock_cycle_seconds": "Same as above, for unlocking.",
+ "ble_unlock_check": "Raw Tuya status value used to authorize remote unlock. Refresh it from the Tuya IoT cloud (Get Status Reporting Log) if unlock starts returning error code 1."
+ }
+ }
}
}
-
\ No newline at end of file
+}
diff --git a/custom_components/tuya_local_ble/translations/ru.json b/custom_components/tuya_local_ble/translations/ru.json
new file mode 100644
index 0000000..e976df9
--- /dev/null
+++ b/custom_components/tuya_local_ble/translations/ru.json
@@ -0,0 +1,37 @@
+{
+ "config": {
+ "abort": {
+ "no_unconfigured_devices": "Ненастроенные устройства не найдены."
+ },
+ "error": {
+ "device_not_registered": "Устройство не зарегистрировано, проверьте конфигурацию."
+ },
+ "flow_title": "{name}",
+ "step": {
+ "device": {
+ "data": {
+ "address": "Устройство Tuya BLE"
+ },
+ "description": "Выберите устройство Tuya BLE для настройки. Устройство должно быть зарегистрировано в облаке через мобильное приложение."
+ }
+ }
+ },
+ "options": {
+ "step": {
+ "init": {
+ "title": "Настройки Tuya Local BLE",
+ "description": "Эти значения перекрывают одноимённые поля в config/tuya_local_ble/devices.json. Пустое поле — используется значение из devices.json (или встроенное по умолчанию). Если после изменения устройство перестало работать — очистите поля здесь или поправьте devices.json напрямую.",
+ "data": {
+ "lock_cycle_seconds": "Время цикла закрытия (с)",
+ "unlock_cycle_seconds": "Время цикла открытия (с)",
+ "ble_unlock_check": "ble_unlock_check (base64)"
+ },
+ "data_description": {
+ "lock_cycle_seconds": "Сколько работает мотор при закрытии, включая паузу после упора. Пока идёт цикл, повторные команды отклоняются.",
+ "unlock_cycle_seconds": "То же самое, но для открытия.",
+ "ble_unlock_check": "Сырое значение статуса Tuya для авторизации удалённого открытия. Если открытие начало возвращать error code 1 — обновите значение из облака Tuya (Get Status Reporting Log)."
+ }
+ }
+ }
+ }
+}