271 lines
11 KiB
Diff
271 lines
11 KiB
Diff
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)."
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+}
|