diff --git a/.HA_VERSION b/.HA_VERSION index f3ca2e0..3f77693 100644 --- a/.HA_VERSION +++ b/.HA_VERSION @@ -1 +1 @@ -2025.11.2 \ No newline at end of file +2026.7.1 \ No newline at end of file diff --git a/automations.yaml b/automations.yaml index d471f26..9168bfc 100644 --- a/automations.yaml +++ b/automations.yaml @@ -1,35 +1,3 @@ -- id: '1652892911485' - alias: Dunstabzug an - description: '' - trigger: - - type: opened - platform: device - device_id: 925011b73a5e493591e69fb225b7cfd7 - entity_id: binary_sensor.openclose_17 - domain: binary_sensor - condition: [] - action: - - type: turn_on - device_id: 501d9510266fae1bef92308c34718b38 - entity_id: switch.on_off_plug_in_unit_27 - domain: switch - mode: single -- id: '1652892963309' - alias: Dunstabzug aus - description: '' - trigger: - - type: not_opened - platform: device - device_id: 925011b73a5e493591e69fb225b7cfd7 - entity_id: binary_sensor.openclose_17 - domain: binary_sensor - condition: [] - action: - - type: turn_off - device_id: 501d9510266fae1bef92308c34718b38 - entity_id: switch.on_off_plug_in_unit_27 - domain: switch - mode: single - id: '1679990307886' alias: ring_30min_Wohnen_new_snapshot description: '' @@ -89,19 +57,19 @@ - id: '1702998213164' alias: Bodensensor_Batterie_schwach description: '' - trigger: + triggers: - type: battery_level - platform: device device_id: c98f516ed093e6f4027ef95659173f06 entity_id: fcad955888a5219fe0bed96490dd2f74 domain: sensor below: 10 - condition: [] - action: - - service: notify.mobile_app_phone_1 + trigger: device + conditions: [] + actions: + - action: notify.mobile_app_nothing3apro + metadata: {} data: - message: Battery Blink Carport is low - title: Attention + message: Bodensensor-Batterie schwach!!! mode: single - id: '1703424483609' alias: OpenWindow_HWR @@ -138,33 +106,32 @@ below: 10 conditions: [] actions: - - action: notify.mobile_app_m2102j20sg - data: {} - - data: - message: Battery Ring Haustür is low - title: Attention - action: notify.hass_synchat + - action: notify.mobile_app_nothing3apro + metadata: {} + data: + message: Batterie Haustür schwach! Bitte aufladen. + - action: notify.mobile_app_a142 + metadata: {} + data: + message: Batterie Haustür schwach! Bitte aufladen. mode: single - id: '1704809998635' alias: info_battery_garten description: '' - trigger: + triggers: - type: battery_level - platform: device device_id: 9e8fab1237714c677c6d747fe81e5639 entity_id: eb89c5a51befafbb6ab287111ff3f0d9 domain: sensor below: 5 - condition: [] - action: - - service: notify.mobile_app_phone_1 - data: - message: Battery Ring Garten is low - title: Attention - - service: notify.hass_synchat - data: - message: Battery Ring Garten is low - title: Attention + trigger: device + conditions: [] + actions: + - action: notify.mobile_app_a142 + data: {} + - action: notify.mobile_app_nothing3apro + metadata: {} + data: {} mode: single - id: '1707337272134' alias: window_reminder @@ -248,13 +215,13 @@ data: message: Fenster {{ trigger.to_state.attributes.friendly_name }} geöffnet! Bitte schließen. - action: notify.mobile_app_m2102j20sg - - metadata: {} + action: notify.mobile_app_lenovo_tb_j606f + - action: notify.mobile_app_a142 + metadata: {} data: message: Fenster {{ trigger.to_state.attributes.friendly_name }} geöffnet! Bitte schließen. - action: notify.mobile_app_lenovo_tb_j606f - - action: notify.mobile_app_a142 + - action: notify.mobile_app_nothing3apro metadata: {} data: message: Fenster {{ trigger.to_state.attributes.friendly_name }} geöffnet! Bitte @@ -315,9 +282,9 @@ path: panhans/advanced_heating_control.yaml input: input_trvs: + - climate.t_kuche - climate.t_esszimmer - climate.t_wohnzimmer - - climate.t_kuche input_temperature_sensor: sensor.temperature_38 input_temperature_eco_static: 16 input_min_instead_of_off: true @@ -333,7 +300,6 @@ - binary_sensor.fk_wohnzimmer - binary_sensor.openclose_17 input_liming_protection: true - input_mode_outside_temperature: sensor.sensor1_soil_temperature input_mode_outside_temperature_threshold: 18 input_generic_calibration_offset: 7 input_frost_protection_temp: 7 @@ -348,6 +314,7 @@ seconds: 30 input_temperature_comfort: input_number.comforttemp input_temperature_eco: input_number.ecotemp + input_window_open_temperature: 5 - id: '1736353137059' alias: "\U0001F525 Advanced Heating Control Oben" description: '' @@ -356,7 +323,6 @@ input: input_trvs: - climate.t_buro - - climate.t_gaste1 input_temperature_sensor: sensor.office_room_temperature input_temperature_comfort_static: 23 input_min_instead_of_off: true @@ -369,82 +335,6 @@ - binary_sensor.fk_buro input_liming_protection: true input_frost_protection_temp: 7 -- id: '1749638886388' - alias: gäste-wc_licht - description: Schaltet bei Bewegung das Licht an - triggers: - - type: motion - device_id: 6e9d01f467927d61f45768160a892e45 - entity_id: fb26eefedea1e06a200b9f9ec17a7f3d - domain: binary_sensor - trigger: device - conditions: [] - actions: - - type: turn_on - device_id: 8d6bbf2015aa51bbf1e064cac4aef609 - entity_id: b7f728e28b4c342d1921a47409822ed9 - domain: light - brightness_pct: 50 - mode: single -- id: '1749638989528' - alias: gäste-wc_licht_aus - description: '' - triggers: - - type: no_motion - device_id: 6e9d01f467927d61f45768160a892e45 - entity_id: fb26eefedea1e06a200b9f9ec17a7f3d - domain: binary_sensor - trigger: device - for: - hours: 0 - minutes: 2 - seconds: 10 - conditions: [] - actions: - - type: turn_off - device_id: 8d6bbf2015aa51bbf1e064cac4aef609 - entity_id: b7f728e28b4c342d1921a47409822ed9 - domain: light - mode: single -- id: '1758515986313' - alias: Licht Eingang an - description: Schaltet bei Bewegung das Licht an - triggers: - - type: motion - device_id: ab5a4270111ddad1ee8bb693143416bd - entity_id: d67ad6df4a99dd98ac122f72dbd9cfa0 - domain: binary_sensor - trigger: device - conditions: - - condition: sun - before: sunrise - after: sunset - actions: - - type: turn_on - device_id: 294f5f31177faceb618c0ffd1e24ba7d - entity_id: f0e686c16165f9db196292b778fee428 - domain: switch - mode: single -- id: '1758516096352' - alias: eingang_licht_aus - description: '' - triggers: - - type: no_motion - device_id: ab5a4270111ddad1ee8bb693143416bd - entity_id: d67ad6df4a99dd98ac122f72dbd9cfa0 - domain: binary_sensor - trigger: device - for: - hours: 0 - minutes: 1 - seconds: 0 - conditions: [] - actions: - - type: turn_off - device_id: 294f5f31177faceb618c0ffd1e24ba7d - entity_id: f0e686c16165f9db196292b778fee428 - domain: switch - mode: single - id: '1762262741326' alias: remove_orphan_deconz description: '' @@ -507,8 +397,40 @@ alias: Eingang_Licht description: '' triggers: + - trigger: state + entity_id: + - event.haustur_motion + attribute: event_type + to: + - motion + conditions: + - condition: sun + before: sunrise + after: sunset + actions: + - if: + - condition: state + entity_id: event.haustur_motion + state: + - motion + attribute: event_type + then: + - action: switch.turn_on + target: + entity_id: switch.on_off_plug_in_unit_33 + data: {} + else: + - action: switch.turn_off + target: + entity_id: switch.on_off_plug_in_unit_33 + data: {} + mode: single +- id: '1764618229369' + alias: Dunstabzug_OnOff + description: '' + triggers: - entity_id: - - binary_sensor.presence_74 + - binary_sensor.openclose_17 from: - 'off' to: @@ -516,34 +438,26 @@ id: 'on' trigger: state - entity_id: - - binary_sensor.presence_74 + - binary_sensor.openclose_17 from: - 'on' to: - 'off' id: 'off' - for: - hours: 0 - minutes: 3 - seconds: 0 trigger: state - conditions: - - condition: sun - before: sunrise - after: sunset + conditions: [] actions: - if: - condition: trigger id: 'on' then: - - data: - brightness_pct: 50 - action: light.turn_on + - action: switch.turn_on target: - entity_id: light.p_eingang - else: - - action: light.turn_off + entity_id: switch.on_off_plug_in_unit_27 data: {} + else: + - action: switch.turn_off target: - entity_id: light.p_eingang + entity_id: switch.on_off_plug_in_unit_27 + data: {} mode: single diff --git a/configuration.yaml b/configuration.yaml index 9d6cbe7..045a7d0 100644 --- a/configuration.yaml +++ b/configuration.yaml @@ -2,7 +2,7 @@ default_config: homeassistant: - internal_url: "http://192.168.0.40:8123" + internal_url: "http://192.168.0.87:8123" external_url: "https://ha.solusar.de" country: DE @@ -22,14 +22,22 @@ homeassistant: - type: homeassistant customize: - sensor.lepus3_060_zahlerstand_total: + sensor.lepus3_060_lepus_zaehlerstand_total: device_class: energy unit_of_measurement: "Wh" state_class: total_increasing - sensor.lepus3_060_wirkenergie_total: + sensor.lepus3_060_lepus_wirkenergie_total: device_class: energy unit_of_measurement: "Wh" state_class: total_increasing + sensor.lepus3_060_lepus_e_in: + device_class: energy + unit_of_measurement: "kWh" + state_class: total_increasing + sensor.lepus3_060_lepus_e_out: + device_class: energy + unit_of_measurement: "kWh" + state_class: total_increasing allowlist_external_dirs: - /tmp @@ -45,14 +53,33 @@ tts: http: use_x_forwarded_for: true trusted_proxies: - - 192.168.0.28 # Add the IP address of the proxy server + - 192.168.0.27 # Add the IP address of the proxy server - 192.168.0.0/24 # trusted network + - 172.0.0.0/8 + ip_ban_enabled: true + login_attempts_threshold: 3 -notify: - - platform: synology_chat - name: hass_synchat - verify_ssl: false - resource: https://realm.solusar.de:5001/webapi/entry.cgi?api=SYNO.Chat.External&method=incoming&version=2&token=%22okeKTH4iySRNb8g34muKKnh6MwVZ79TkYu6W57oQst1KOcVbD1cMOY9DMv57vZyp%22 +logger: + default: warning +# logs: +# pydeconz: debug +# homeassistant.components.deconz: debug + +#notify: +# - platform: synology_chat +# name: hass_synchat +# verify_ssl: false +# resource: https://realm.solusar.de:5001/webapi/entry.cgi?api=SYNO.Chat.External&method=incoming&version=2&token=%22okeKTH4iySRNb8g34muKKnh6MwVZ79TkYu6W57oQst1KOcVbD1cMOY9DMv57vZyp%22 + +#emulated_hue: +# listen_port: 80 +# host_ip: 192.168.0.87 +# expose_by_default: true +# exposed_domains: +# - light +# - smartplug +# entities: + # includes automation: !include automations.yaml @@ -61,3 +88,4 @@ scene: !include scenes.yaml sensor: !include sensors.yaml rest: !include rest.yaml template: !include template.yaml + diff --git a/custom_components/better_thermostat/__init__.py b/custom_components/better_thermostat/__init__.py index 970cbf2..ca18468 100644 --- a/custom_components/better_thermostat/__init__.py +++ b/custom_components/better_thermostat/__init__.py @@ -1,25 +1,32 @@ """The better_thermostat component.""" -import logging from asyncio import Lock -from homeassistant.const import Platform -from homeassistant.core import HomeAssistant +import logging + from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_NAME, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import issue_registry as ir from homeassistant.helpers.typing import ConfigType import voluptuous as vol from .utils.const import ( CONF_CALIBRATION_MODE, CONF_HEATER, + CONF_HUMIDITY, CONF_NO_SYSTEM_MODE_OFF, + CONF_OUTDOOR_SENSOR, + CONF_SENSOR, + CONF_SENSOR_WINDOW, CONF_WINDOW_TIMEOUT, CONF_WINDOW_TIMEOUT_AFTER, CalibrationMode, ) +from .utils.helpers import get_device_model _LOGGER = logging.getLogger(__name__) DOMAIN = "better_thermostat" -PLATFORMS = [Platform.CLIMATE] +PLATFORMS = [Platform.CLIMATE, Platform.SENSOR, Platform.NUMBER, Platform.SWITCH] CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA) config_entry_update_listener_lock = Lock() @@ -36,7 +43,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up entry.""" hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = {} - await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) + try: + # Setup climate platform first to ensure entity is available for other platforms + await hass.config_entries.async_forward_entry_setups(entry, [Platform.CLIMATE]) + # Setup other platforms that depend on climate entity + await hass.config_entries.async_forward_entry_setups( + entry, [Platform.SENSOR, Platform.NUMBER, Platform.SWITCH] + ) + except Exception: + _LOGGER.exception( + "better_thermostat: Fehler beim Laden der Plattformen für Entry %s", + entry.entry_id, + ) + return False entry.async_on_unload(entry.add_update_listener(config_entry_update_listener)) return True @@ -55,7 +74,43 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload_ok +async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: + """Remove repair-registry issues created by this Better Thermostat instance. + + Issues are scoped by ``device_name`` or by individual ``entity_id`` and + persist in HA's issue registry until explicitly deleted, so they have to + be cleaned up here to avoid stale warnings after a config entry is gone. + """ + device_name = entry.data.get(CONF_NAME, entry.title) + + for issue_id in ( + f"invalid_external_temperature_{device_name}", + f"invalid_window_state_{device_name}", + f"degraded_mode_{device_name}", + ): + ir.async_delete_issue(hass, DOMAIN, issue_id) + + entity_ids: list[str] = [] + for trv in entry.data.get(CONF_HEATER) or []: + trv_id = trv.get("trv") + if trv_id: + entity_ids.append(trv_id) + for conf_key in ( + CONF_SENSOR, + CONF_HUMIDITY, + CONF_SENSOR_WINDOW, + CONF_OUTDOOR_SENSOR, + ): + eid = entry.data.get(conf_key) + if eid: + entity_ids.append(eid) + + for eid in entity_ids: + ir.async_delete_issue(hass, DOMAIN, f"missing_entity_{eid}") + + async def async_reload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> None: + """Reload the config entry.""" await async_unload_entry(hass, config_entry) await async_setup_entry(hass, config_entry) @@ -63,21 +118,17 @@ async def async_reload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> async def async_migrate_entry(hass, config_entry: ConfigEntry): """Migrate old entry.""" _LOGGER.debug("Migrating from version %s", config_entry.version) + + new = {**config_entry.data} + if config_entry.version == 1: - new = {**config_entry.data} for trv in new[CONF_HEATER]: trv["advanced"].update({CalibrationMode.AGGRESIVE_CALIBRATION: False}) - config_entry.version = 2 - hass.config_entries.async_update_entry(config_entry, data=new) if config_entry.version == 2: - new = {**config_entry.data} new[CONF_WINDOW_TIMEOUT] = 0 - config_entry.version = 3 - hass.config_entries.async_update_entry(config_entry, data=new) if config_entry.version == 3: - new = {**config_entry.data} for trv in new[CONF_HEATER]: if ( CalibrationMode.AGGRESIVE_CALIBRATION in trv["advanced"] @@ -87,22 +138,43 @@ async def async_migrate_entry(hass, config_entry: ConfigEntry): {CONF_CALIBRATION_MODE: CalibrationMode.AGGRESIVE_CALIBRATION} ) else: - trv["advanced"].update({CONF_CALIBRATION_MODE: CalibrationMode.DEFAULT}) - config_entry.version = 4 - hass.config_entries.async_update_entry(config_entry, data=new) + trv["advanced"].update( + {CONF_CALIBRATION_MODE: CalibrationMode.MPC_CALIBRATION} + ) if config_entry.version == 4: - new = {**config_entry.data} for trv in new[CONF_HEATER]: trv["advanced"].update({CONF_NO_SYSTEM_MODE_OFF: False}) - config_entry.version = 5 - hass.config_entries.async_update_entry(config_entry, data=new) if config_entry.version == 5: - new = {**config_entry.data} new[CONF_WINDOW_TIMEOUT_AFTER] = new[CONF_WINDOW_TIMEOUT] - config_entry.version = 6 - hass.config_entries.async_update_entry(config_entry, data=new) + + if config_entry.version < 18: + # Make sure all TRVs fetch the get_device_model method to update their model info, which is used for device-specific quirks again. + migration_context = type( + "MigrationContext", + (), + {"hass": hass, "device_name": config_entry.title, "model": None}, + )() + heaters = new.get(CONF_HEATER, []) + for trv in heaters: + entity_id = trv.get("entity_id") + if entity_id: + trv["model"] = await get_device_model(migration_context, entity_id) + _LOGGER.debug( + "Migration to version 1.8: TRV %s model updated to %s", + entity_id, + trv["model"], + ) + new[CONF_HEATER] = heaters + + _LOGGER.debug( + "Migration to version 1.8: Updated TRV model information for all TRVs in config entry %s", + config_entry.entry_id, + ) + # update the new config entry with the updated TRV model information + + hass.config_entries.async_update_entry(config_entry, data=new, version=18) _LOGGER.info("Migration to version %s successful", config_entry.version) diff --git a/custom_components/better_thermostat/adapters/deconz.py b/custom_components/better_thermostat/adapters/deconz.py index 3bcb762..038253d 100644 --- a/custom_components/better_thermostat/adapters/deconz.py +++ b/custom_components/better_thermostat/adapters/deconz.py @@ -1,7 +1,16 @@ +"""Adapter for deCONZ devices. + +This module implements the minimal adapter interface required by the +Better Thermostat integration for deCONZ-controlled TRV devices. +""" + import logging + +from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN + from .generic import ( - set_temperature as generic_set_temperature, set_hvac_mode as generic_set_hvac_mode, + set_temperature as generic_set_temperature, ) _LOGGER = logging.getLogger(__name__) @@ -9,13 +18,21 @@ _LOGGER = logging.getLogger(__name__) async def get_info(self, entity_id): """Get info from TRV.""" - _offset = self.hass.states.get(entity_id).attributes.get("offset", None) + state = self.hass.states.get(entity_id) + if state is None: + return {"support_offset": False, "support_valve": False} + _offset = state.attributes.get("offset", None) if _offset is None: return {"support_offset": False, "support_valve": False} return {"support_offset": True, "support_valve": False} async def init(self, entity_id): + """Initialize adapter for an entity. + + This adapter does not require any special initialization, so the + function returns None. + """ return None @@ -31,12 +48,23 @@ async def set_hvac_mode(self, entity_id, hvac_mode): async def get_current_offset(self, entity_id): """Get current offset.""" - return float(str(self.hass.states.get(entity_id).attributes.get("offset", 0))) + state = self.hass.states.get(entity_id) + if state is None or state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN): + return 0.0 + try: + return float(str(state.attributes.get("offset", 0))) + except (ValueError, TypeError): + _LOGGER.warning( + "better_thermostat %s: Could not convert calibration offset '%s' to float, using 0", + self.device_name, + state.attributes.get("offset"), + ) + return 0.0 async def get_offset_step(self, entity_id): """Get offset step.""" - return float(1.0) + return 1.0 async def get_min_offset(self, entity_id): diff --git a/custom_components/better_thermostat/adapters/generic.py b/custom_components/better_thermostat/adapters/generic.py index 190c844..ebf180d 100644 --- a/custom_components/better_thermostat/adapters/generic.py +++ b/custom_components/better_thermostat/adapters/generic.py @@ -1,9 +1,18 @@ +"""Generic adapter helpers used by multiple TRV integrations. + +This module implements the generic, default behaviour for TRV adapters +used by Better Thermostat when a device-specific adapter does not exist. +""" + import asyncio import logging from homeassistant.components.number.const import SERVICE_SET_VALUE -from ..utils.helpers import find_local_calibration_entity -from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN +from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN, UnitOfTemperature +from homeassistant.util.unit_conversion import TemperatureConverter + +from ..utils.helpers import find_local_calibration_entity, normalize_hvac_mode +from .base import wait_for_calibration_entity_or_timeout _LOGGER = logging.getLogger(__name__) @@ -19,61 +28,72 @@ async def get_info(self, entity_id): async def init(self, entity_id): + """Initialize generic adapter for an entity. + + Finds and registers a local calibration entity (if configured) and waits + for it to appear before returning. Returns None after initialization. + """ if ( self.real_trvs[entity_id]["local_temperature_calibration_entity"] is None and self.real_trvs[entity_id]["calibration"] != 1 ): - self.real_trvs[entity_id]["local_temperature_calibration_entity"] = ( - await find_local_calibration_entity(self, entity_id) - ) + self.real_trvs[entity_id][ + "local_temperature_calibration_entity" + ] = await find_local_calibration_entity(self, entity_id) _LOGGER.debug( "better_thermostat %s: uses local calibration entity %s", self.device_name, self.real_trvs[entity_id]["local_temperature_calibration_entity"], ) - # Wait for the entity to be available - _ready = True - while _ready: - if self.hass.states.get( - self.real_trvs[entity_id]["local_temperature_calibration_entity"] - ).state in (STATE_UNAVAILABLE, STATE_UNKNOWN, None): - _LOGGER.info( - "better_thermostat %s: waiting for TRV/climate entity with id '%s' to become fully available...", - self.device_name, - self.real_trvs[entity_id]["local_temperature_calibration_entity"], - ) - await asyncio.sleep(5) - continue - _ready = False - return - - return None + if ( + self.real_trvs[entity_id]["local_temperature_calibration_entity"] + is not None + ): + await wait_for_calibration_entity_or_timeout( + self, + entity_id, + self.real_trvs[entity_id]["local_temperature_calibration_entity"], + ) + else: + _LOGGER.warning( + "better_thermostat %s: no local calibration entity found for '%s', skipping calibration init", + self.device_name, + entity_id, + ) async def get_current_offset(self, entity_id): """Get current offset.""" if self.real_trvs[entity_id]["local_temperature_calibration_entity"] is not None: - return float( - str( - self.hass.states.get( - self.real_trvs[entity_id]["local_temperature_calibration_entity"] - ).state - ) + state = self.hass.states.get( + self.real_trvs[entity_id]["local_temperature_calibration_entity"] ) + if state is None or state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN): + return 0.0 + try: + # For SELECT entities, remove the 'k' suffix if present (e.g., "1.5k" -> "1.5") + state_str = str(state.state).replace("k", "") + return float(state_str) + except (ValueError, TypeError): + _LOGGER.warning( + "better_thermostat %s: Could not convert calibration offset '%s' to float, using 0", + self.device_name, + state.state, + ) + return 0.0 else: - return None + return 0.0 async def get_offset_step(self, entity_id): """Get offset step.""" if self.real_trvs[entity_id]["local_temperature_calibration_entity"] is not None: - return float( - str( - self.hass.states.get( - self.real_trvs[entity_id]["local_temperature_calibration_entity"] - ).attributes.get("step", 1) - ) + state = self.hass.states.get( + self.real_trvs[entity_id]["local_temperature_calibration_entity"] ) + if state is None: + return None + return float(str(state.attributes.get("step", 1))) else: return None @@ -81,13 +101,26 @@ async def get_offset_step(self, entity_id): async def get_min_offset(self, entity_id): """Get min offset.""" if self.real_trvs[entity_id]["local_temperature_calibration_entity"] is not None: - return float( - str( - self.hass.states.get( - self.real_trvs[entity_id]["local_temperature_calibration_entity"] - ).attributes.get("min", -10) - ) + state = self.hass.states.get( + self.real_trvs[entity_id]["local_temperature_calibration_entity"] ) + if state is None: + return -6.0 + + # For SELECT entities, infer from options + if state.domain == "select": + options = state.attributes.get("options", []) + if options: + try: + # Extract numeric values from options (remove 'k' suffix) + values = [float(opt.replace("k", "")) for opt in options] + return min(values) + except (ValueError, TypeError): + return -6.0 + return -6.0 + + # For NUMBER entities, use the min attribute + return float(str(state.attributes.get("min", -10))) else: return -6 @@ -95,19 +128,37 @@ async def get_min_offset(self, entity_id): async def get_max_offset(self, entity_id): """Get max offset.""" if self.real_trvs[entity_id]["local_temperature_calibration_entity"] is not None: - return float( - str( - self.hass.states.get( - self.real_trvs[entity_id]["local_temperature_calibration_entity"] - ).attributes.get("max", 10) - ) + state = self.hass.states.get( + self.real_trvs[entity_id]["local_temperature_calibration_entity"] ) + if state is None: + return 6.0 + + # For SELECT entities, infer from options + if state.domain == "select": + options = state.attributes.get("options", []) + if options: + try: + # Extract numeric values from options (remove 'k' suffix) + values = [float(opt.replace("k", "")) for opt in options] + return max(values) + except (ValueError, TypeError): + return 6.0 + return 6.0 + + # For NUMBER entities, use the max attribute + return float(str(state.attributes.get("max", 10))) else: return 6 async def set_temperature(self, entity_id, temperature): """Set new target temperature.""" + if self.hass.config.units.temperature_unit == UnitOfTemperature.FAHRENHEIT: + temperature = TemperatureConverter.convert( + temperature, UnitOfTemperature.CELSIUS, UnitOfTemperature.FAHRENHEIT + ) + temperature = round(temperature, 1) await self.hass.services.async_call( "climate", "set_temperature", @@ -119,17 +170,36 @@ async def set_temperature(self, entity_id, temperature): async def set_hvac_mode(self, entity_id, hvac_mode): """Set new target hvac mode.""" - _LOGGER.debug("better_thermostat %s: set_hvac_mode %s", self.device_name, hvac_mode) + + hvac_mode_norm = normalize_hvac_mode(hvac_mode) + _LOGGER.debug( + "better_thermostat %s: set_hvac_mode %s -> %s", + self.device_name, + hvac_mode, + hvac_mode_norm, + ) try: await self.hass.services.async_call( "climate", "set_hvac_mode", - {"entity_id": entity_id, "hvac_mode": hvac_mode}, + {"entity_id": entity_id, "hvac_mode": hvac_mode_norm}, blocking=True, context=self.context, ) except TypeError: - _LOGGER.debug("TypeError in set_hvac_mode") + _LOGGER.debug( + "TypeError in set_hvac_mode (entity=%s, hvac_mode=%s)", + entity_id, + hvac_mode_norm, + ) + except Exception as exc: + _LOGGER.exception( + "better_thermostat %s: Exception in set_hvac_mode for %s with %s: %s", + self.device_name, + entity_id, + hvac_mode_norm, + exc, + ) async def set_offset(self, entity_id, offset): @@ -138,23 +208,69 @@ async def set_offset(self, entity_id, offset): max_calibration = await get_max_offset(self, entity_id) min_calibration = await get_min_offset(self, entity_id) - if offset >= max_calibration: - offset = max_calibration - if offset <= min_calibration: - offset = min_calibration + offset = min(max_calibration, offset) + offset = max(min_calibration, offset) - await self.hass.services.async_call( - "number", - SERVICE_SET_VALUE, - { - "entity_id": self.real_trvs[entity_id][ - "local_temperature_calibration_entity" - ], - "value": offset, - }, - blocking=True, - context=self.context, + calibration_entity = self.real_trvs[entity_id][ + "local_temperature_calibration_entity" + ] + entity_state = self.hass.states.get(calibration_entity) + + # Derive domain safely - from entity_state if available, otherwise from entity_id + domain = ( + entity_state.domain if entity_state else calibration_entity.split(".", 1)[0] ) + + # Check if it's a SELECT entity or NUMBER entity + if domain == "select": + # For SELECT entities, format with 'k' suffix (e.g., "1.5k") + option_value = f"{offset:.1f}k" + + # Get available options (handle None entity_state gracefully) + options = [] + if entity_state: + options = entity_state.attributes.get("options", []) + + # Validate and snap to closest matching option if needed + if options: + if option_value not in options: + try: + # Parse all options and find the closest match + parsed_options = {} + for opt in options: + try: + parsed_options[opt] = float(str(opt).replace("k", "")) + except (ValueError, TypeError): + continue + + if parsed_options: + # Find option with minimum distance to target offset + closest_option = min( + parsed_options, + key=lambda opt: abs(parsed_options[opt] - offset), + ) + option_value = closest_option + except (ValueError, TypeError): + # If parsing fails, keep original option_value and hope for the best + pass + + await self.hass.services.async_call( + "select", + "select_option", + {"entity_id": calibration_entity, "option": option_value}, + blocking=True, + context=self.context, + ) + else: + # For NUMBER entities, use the original set_value service + await self.hass.services.async_call( + "number", + SERVICE_SET_VALUE, + {"entity_id": calibration_entity, "value": offset}, + blocking=True, + context=self.context, + ) + self.real_trvs[entity_id]["last_calibration"] = offset if ( self.real_trvs[entity_id]["last_hvac_mode"] is not None diff --git a/custom_components/better_thermostat/adapters/mqtt.py b/custom_components/better_thermostat/adapters/mqtt.py index 0b59479..7475388 100644 --- a/custom_components/better_thermostat/adapters/mqtt.py +++ b/custom_components/better_thermostat/adapters/mqtt.py @@ -1,14 +1,21 @@ +"""MQTT adapter for TRV devices. + +This module implements MQTT-specific behaviour for TRV devices used by +the Better Thermostat integration. +""" + import asyncio import logging from homeassistant.components.number.const import SERVICE_SET_VALUE +from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN from ..utils.helpers import find_local_calibration_entity, find_valve_entity +from .base import wait_for_calibration_entity_or_timeout from .generic import ( set_hvac_mode as generic_set_hvac_mode, set_temperature as generic_set_temperature, ) -from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN _LOGGER = logging.getLogger(__name__) @@ -21,48 +28,55 @@ async def get_info(self, entity_id): if offset is not None: support_offset = True valve = await find_valve_entity(self, entity_id) - if valve is not None: - support_valve = True + if valve is not None and valve.get("entity_id"): + support_valve = bool(valve.get("writable", False)) return {"support_offset": support_offset, "support_valve": support_valve} async def init(self, entity_id): + """Initialize the MQTT adapter for a TRV entity. + + Performs early discovery of the valve position and the local + calibration entity when available. + """ + # Try to discover valve position entity early + try: + from ..utils.helpers import find_valve_entity as _find_valve + + valve = await _find_valve(self, entity_id) + if valve is not None: + self.real_trvs[entity_id]["valve_position_entity"] = valve.get("entity_id") + self.real_trvs[entity_id]["valve_position_writable"] = bool( + valve.get("writable", False) + ) + except Exception: + pass + if ( self.real_trvs[entity_id]["local_temperature_calibration_entity"] is None and self.real_trvs[entity_id]["calibration"] != 1 ): - self.real_trvs[entity_id]["local_temperature_calibration_entity"] = ( - await find_local_calibration_entity(self, entity_id) - ) + self.real_trvs[entity_id][ + "local_temperature_calibration_entity" + ] = await find_local_calibration_entity(self, entity_id) _LOGGER.debug( "better_thermostat %s: uses local calibration entity %s", self.device_name, self.real_trvs[entity_id]["local_temperature_calibration_entity"], ) - # Wait for the entity to be available - _ready = True - while _ready: - if self.hass.states.get( - self.real_trvs[entity_id]["local_temperature_calibration_entity"] - ).state in (STATE_UNAVAILABLE, STATE_UNKNOWN, None): - _LOGGER.info( - "better_thermostat %s: waiting for TRV/climate entity with id '%s' to become fully available...", - self.device_name, - self.real_trvs[entity_id]["local_temperature_calibration_entity"], - ) - await asyncio.sleep(5) - continue - _ready = False - return - - _has_preset = self.hass.states.get(entity_id).attributes.get( - "preset_modes", None + await wait_for_calibration_entity_or_timeout( + self, + entity_id, + self.real_trvs[entity_id]["local_temperature_calibration_entity"], ) + + state = self.hass.states.get(entity_id) + _has_preset = state.attributes.get("preset_modes", None) if state else None if _has_preset is not None: await self.hass.services.async_call( "climate", "set_preset_mode", - {"entity_id": entity_id, "preset_mode": "manual"}, + {"entity_id": entity_id, "preset_mode": "none"}, blocking=True, context=self.context, ) @@ -81,46 +95,50 @@ async def set_hvac_mode(self, entity_id, hvac_mode): async def get_current_offset(self, entity_id): """Get current offset.""" - return float( - str( - self.hass.states.get( - self.real_trvs[entity_id]["local_temperature_calibration_entity"] - ).state - ) + state = self.hass.states.get( + self.real_trvs[entity_id]["local_temperature_calibration_entity"] ) + if state is None or state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN): + return 0.0 + try: + return float(str(state.state)) + except (ValueError, TypeError): + _LOGGER.warning( + "better_thermostat %s: Could not convert calibration offset '%s' to float, using 0", + self.device_name, + state.state, + ) + return 0.0 async def get_offset_step(self, entity_id): """Get offset step.""" - return float( - str( - self.hass.states.get( - self.real_trvs[entity_id]["local_temperature_calibration_entity"] - ).attributes.get("step", 1) - ) + state = self.hass.states.get( + self.real_trvs[entity_id]["local_temperature_calibration_entity"] ) + if state is None: + return 1.0 + return float(str(state.attributes.get("step", 1))) async def get_min_offset(self, entity_id): """Get min offset.""" - return float( - str( - self.hass.states.get( - self.real_trvs[entity_id]["local_temperature_calibration_entity"] - ).attributes.get("min", -10) - ) + state = self.hass.states.get( + self.real_trvs[entity_id]["local_temperature_calibration_entity"] ) + if state is None: + return -10.0 + return float(str(state.attributes.get("min", -10))) async def get_max_offset(self, entity_id): """Get max offset.""" - return float( - str( - self.hass.states.get( - self.real_trvs[entity_id]["local_temperature_calibration_entity"] - ).attributes.get("max", 10) - ) + state = self.hass.states.get( + self.real_trvs[entity_id]["local_temperature_calibration_entity"] ) + if state is None: + return 10.0 + return float(str(state.attributes.get("max", 10))) async def set_offset(self, entity_id, offset): @@ -128,10 +146,8 @@ async def set_offset(self, entity_id, offset): max_calibration = await get_max_offset(self, entity_id) min_calibration = await get_min_offset(self, entity_id) - if offset >= max_calibration: - offset = max_calibration - if offset <= min_calibration: - offset = min_calibration + offset = min(max_calibration, offset) + offset = max(min_calibration, offset) await self.hass.services.async_call( "number", @@ -159,8 +175,30 @@ async def set_offset(self, entity_id, offset): async def set_valve(self, entity_id, valve): """Set new target valve.""" _LOGGER.debug( - f"better_thermostat {self.device_name}: TO TRV {entity_id} set_valve: {valve}" + "better_thermostat %s: TO TRV %s set_valve: %s", + self.device_name, + entity_id, + valve, ) + if self.real_trvs.get(entity_id, {}).get("valve_position_writable") is False: + _LOGGER.debug( + "better_thermostat %s: valve entity for %s is read-only, skip adapter write", + self.device_name, + entity_id, + ) + return + + # get min max from entity attributes + valve_entity = self.hass.states.get( + self.real_trvs[entity_id]["valve_position_entity"] + ) + if valve_entity is not None: + min_valve = float(str(valve_entity.attributes.get("min", 0))) + max_valve = float(str(valve_entity.attributes.get("max", 100))) + valve = min_valve + (valve / 100.0) * (max_valve - min_valve) + step = float(str(valve_entity.attributes.get("step", 1))) + valve = round(valve / step) * step + await self.hass.services.async_call( "number", SERVICE_SET_VALUE, diff --git a/custom_components/better_thermostat/adapters/tado.py b/custom_components/better_thermostat/adapters/tado.py index 9eb70d2..a12c266 100644 --- a/custom_components/better_thermostat/adapters/tado.py +++ b/custom_components/better_thermostat/adapters/tado.py @@ -1,7 +1,16 @@ +"""Tado adapter helpers for Better Thermostat. + +This module implements the thin adapter that maps Better Thermostat actions +onto the Tado climate services (offsets and modes). +""" + import logging + +from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN + from .generic import ( - set_temperature as generic_set_temperature, set_hvac_mode as generic_set_hvac_mode, + set_temperature as generic_set_temperature, ) _LOGGER = logging.getLogger(__name__) @@ -13,6 +22,10 @@ async def get_info(self, entity_id): async def init(self, entity_id): + """Perform per-entity initialization for the Tado adapter. + + Currently, no initialization is required and the function returns None. + """ return None @@ -28,14 +41,23 @@ async def set_hvac_mode(self, entity_id, hvac_mode): async def get_current_offset(self, entity_id): """Get current offset.""" - return float( - str(self.hass.states.get(entity_id).attributes.get("offset_celsius", 0)) - ) + state = self.hass.states.get(entity_id) + if state is None or state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN): + return 0.0 + try: + return float(str(state.attributes.get("offset_celsius", 0))) + except (ValueError, TypeError): + _LOGGER.warning( + "better_thermostat %s: Could not convert calibration offset '%s' to float, using 0", + self.device_name, + state.attributes.get("offset_celsius"), + ) + return 0.0 async def get_offset_step(self, entity_id): """Get offset step.""" - return float(0.01) + return 0.01 async def get_min_offset(self, entity_id): @@ -50,10 +72,8 @@ async def get_max_offset(self, entity_id): async def set_offset(self, entity_id, offset): """Set new target offset.""" - if offset >= 10: - offset = 10 - if offset <= -10: - offset = -10 + offset = min(10, offset) + offset = max(-10, offset) await self.hass.services.async_call( "tado", "set_climate_temperature_offset", diff --git a/custom_components/better_thermostat/climate.py b/custom_components/better_thermostat/climate.py index f43631b..57a7b53 100644 --- a/custom_components/better_thermostat/climate.py +++ b/custom_components/better_thermostat/climate.py @@ -1,45 +1,55 @@ -"""Better Thermostat""" +"""Better Thermostat.""" +from __future__ import annotations + +from abc import ABC import asyncio +from collections import deque +from datetime import datetime, timedelta +from functools import cached_property import json import logging -from abc import ABC -from datetime import datetime, timedelta from random import randint -from statistics import mean +from time import monotonic +from typing import Any # Home Assistant imports -from homeassistant.components.climate import ( - ATTR_HVAC_MODE, - ATTR_TARGET_TEMP_HIGH, - ATTR_TARGET_TEMP_LOW, - ClimateEntity, - PRESET_NONE, -) +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( + ATTR_HVAC_MODE, ATTR_MAX_TEMP, ATTR_MIN_TEMP, + ATTR_TARGET_TEMP_HIGH, + ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_STEP, + PRESET_NONE, ClimateEntityFeature, HVACAction, HVACMode, ) -from homeassistant.components.group.util import reduce_attribute from homeassistant.const import ( ATTR_TEMPERATURE, CONF_NAME, - EVENT_HOMEASSISTANT_START, STATE_UNAVAILABLE, STATE_UNKNOWN, + UnitOfTemperature, ) -from homeassistant.core import Context, CoreState, ServiceCall, callback +from homeassistant.core import CALLBACK_TYPE, Context, ServiceCall, State, callback from homeassistant.helpers import entity_platform +from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers.dispatcher import dispatcher_send from homeassistant.helpers.event import ( + async_call_later, async_track_state_change_event, async_track_time_change, async_track_time_interval, ) from homeassistant.helpers.restore_state import RestoreEntity +from homeassistant.helpers.start import async_at_started + +# preferred for HA time handling (UTC aware) +from homeassistant.util import dt as dt_util +from homeassistant.util.unit_conversion import TemperatureConverter # Local imports from .adapters.delegate import ( @@ -49,29 +59,42 @@ from .adapters.delegate import ( get_offset_step, init, load_adapter, + set_hvac_mode as adapter_set_hvac_mode, + set_temperature as adapter_set_temperature, ) from .events.cooler import trigger_cooler_change from .events.temperature import trigger_temperature_change from .events.trv import trigger_trv_change from .events.window import trigger_window_change, window_queue -from .model_fixes.model_quirks import load_model_quirks +from .model_fixes.model_quirks import inital_tweak, load_model_quirks +from .utils.calibration.pid import ( + export_pid_states as pid_export_states, + format_bucket, + reset_pid_state as pid_reset_state, + resolve_unique_id, + round_to_bucket, +) from .utils.const import ( ATTR_STATE_BATTERIES, ATTR_STATE_CALL_FOR_HEAT, ATTR_STATE_ERRORS, + ATTR_STATE_HEAT_LOSS, ATTR_STATE_HEATING_POWER, ATTR_STATE_HUMIDIY, ATTR_STATE_LAST_CHANGE, ATTR_STATE_MAIN_MODE, + ATTR_STATE_OFF_TEMPERATURE, + ATTR_STATE_PRESET_TEMPERATURE, ATTR_STATE_SAVED_TEMPERATURE, ATTR_STATE_WINDOW_OPEN, - BETTERTHERMOSTAT_SET_TEMPERATURE_SCHEMA, + BETTERTHERMOSTAT_RESET_PID_SCHEMA, CONF_COOLER, CONF_HEATER, CONF_HUMIDITY, CONF_MODEL, CONF_OFF_TEMPERATURE, CONF_OUTDOOR_SENSOR, + CONF_PRESETS, CONF_SENSOR, CONF_SENSOR_WINDOW, CONF_TARGET_TEMP_STEP, @@ -80,23 +103,74 @@ from .utils.const import ( CONF_WINDOW_TIMEOUT, CONF_WINDOW_TIMEOUT_AFTER, SERVICE_RESET_HEATING_POWER, - SERVICE_RESTORE_SAVED_TARGET_TEMPERATURE, - SERVICE_SET_TEMP_TARGET_TEMPERATURE, + SERVICE_RESET_PID_LEARNINGS, SUPPORT_FLAGS, VERSION, + CalibrationMode, + CalibrationType, ) from .utils.controlling import control_queue, control_trv -from .utils.helpers import convert_to_float, find_battery_entity, get_hvac_bt_mode -from .utils.watcher import check_all_entities +from .utils.helpers import ( + attr_to_celsius, + convert_to_float, + convert_to_float_celsius, + find_battery_entity, + get_device_model, + get_hvac_bt_mode, + is_reasonable_temperature, + normalize_hvac_mode, + state_temperature_unit, +) +from .utils.hvac_action import ( + ToleranceHysteresis, + TrvSnapshot, + compute_hvac_action, + should_heat_with_tolerance, +) +from .utils.preset_manager import PresetManager +from .utils.restore import ( + clamp_heat_loss, + clamp_heating_power, + mean_trv_target, + restore_target_temperature, +) +from .utils.state_manager import StateManager +from .utils.telemetry import ( + collect_balance_attrs, + collect_cycle_telemetry, + collect_pid_debug_attrs, +) +from .utils.thermal_learning import ( + HeatingCycle, + HeatingPowerTracker, + HeatLossTracker, + LossCycle, + LossStats, +) +from .utils.valve_maintenance import ( + build_trv_snapshots, + collect_maintenance_trvs, + compute_initial_maintenance, + compute_next_maintenance, + run_valve_maintenance, +) +from .utils.watcher import ( + STARTUP_DEGRADED_GRACE_PERIOD, + await_optional_sensors, + check_and_update_degraded_mode, + check_critical_entities, + is_entity_available, +) from .utils.weather import check_ambient_air_temperature, check_weather - _LOGGER = logging.getLogger(__name__) DOMAIN = "better_thermostat" +# Default temperature when no sensor data is available (last resort fallback) +DEFAULT_FALLBACK_TEMPERATURE = 20.0 -class ContinueLoop(Exception): - pass +# Signal für dynamische Entity-Updates +SIGNAL_BT_CONFIG_CHANGED = "bt_config_changed_{}" @callback @@ -126,62 +200,57 @@ def async_set_temperature_service_validate(service_call: ServiceCall) -> Service async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Better Thermostat platform.""" - platform = entity_platform.async_get_current_platform() + _LOGGER.debug("better_thermostat: async_setup_platform called (deprecated no-op)") - # Register service validators - platform.async_register_service_validator( - "set_temperature", async_set_temperature_service_validate + +async def async_setup_entry(hass, entry, async_add_entities): + """Set up Better Thermostat climate entity for a config entry.""" + _LOGGER.debug( + "better_thermostat %s: async_setup_entry start (entry_id=%s)", + entry.data.get(CONF_NAME), + entry.entry_id, ) - -async def async_setup_entry(hass, entry, async_add_devices): - """Setup sensor platform.""" - - async def async_service_handler(entity, call): - """Handle the service calls.""" - if call.service == SERVICE_RESTORE_SAVED_TARGET_TEMPERATURE: - await entity.restore_temp_temperature() - elif call.service == SERVICE_SET_TEMP_TARGET_TEMPERATURE: - await entity.set_temp_temperature(call.data[ATTR_TEMPERATURE]) - elif call.service == SERVICE_RESET_HEATING_POWER: - await entity.reset_heating_power() - platform = entity_platform.async_get_current_platform() - platform.async_register_entity_service( - SERVICE_SET_TEMP_TARGET_TEMPERATURE, - BETTERTHERMOSTAT_SET_TEMPERATURE_SCHEMA, - "set_temp_temperature", - ) - platform.async_register_entity_service( - SERVICE_RESTORE_SAVED_TARGET_TEMPERATURE, {}, "restore_temp_temperature" - ) + # Register entity services (validator done manually inside method) platform.async_register_entity_service( SERVICE_RESET_HEATING_POWER, {}, "reset_heating_power" ) + platform.async_register_entity_service( + "run_valve_maintenance", {}, "run_valve_maintenance_service" + ) + platform.async_register_entity_service( + SERVICE_RESET_PID_LEARNINGS, + BETTERTHERMOSTAT_RESET_PID_SCHEMA, + "reset_pid_learnings_service", + ) - async_add_devices( - [ - BetterThermostat( - entry.data.get(CONF_NAME), - entry.data.get(CONF_HEATER), - entry.data.get(CONF_SENSOR), - entry.data.get(CONF_HUMIDITY, None), - entry.data.get(CONF_SENSOR_WINDOW, None), - entry.data.get(CONF_WINDOW_TIMEOUT, None), - entry.data.get(CONF_WINDOW_TIMEOUT_AFTER, None), - entry.data.get(CONF_WEATHER, None), - entry.data.get(CONF_OUTDOOR_SENSOR, None), - entry.data.get(CONF_OFF_TEMPERATURE, None), - entry.data.get(CONF_TOLERANCE, 0.0), - entry.data.get(CONF_TARGET_TEMP_STEP, "0.0"), - entry.data.get(CONF_MODEL, None), - entry.data.get(CONF_COOLER, None), - hass.config.units.temperature_unit, - entry.entry_id, - device_class="better_thermostat", - state_class="better_thermostat_state", - ) - ] + bt_entity = BetterThermostat( + entry.data.get(CONF_NAME), + entry.data.get(CONF_HEATER), + entry.data.get(CONF_SENSOR), + entry.data.get(CONF_HUMIDITY, None), + entry.data.get(CONF_SENSOR_WINDOW, None), + entry.data.get(CONF_WINDOW_TIMEOUT, None), + entry.data.get(CONF_WINDOW_TIMEOUT_AFTER, None), + entry.data.get(CONF_WEATHER, None), + entry.data.get(CONF_OUTDOOR_SENSOR, None), + entry.data.get(CONF_OFF_TEMPERATURE, None), + entry.data.get(CONF_TOLERANCE, 0.0), + entry.data.get(CONF_TARGET_TEMP_STEP, "0.0"), + entry.data.get(CONF_MODEL, None), + entry.data.get(CONF_COOLER, None), + entry.data.get(CONF_PRESETS, None), + hass.config.units.temperature_unit, + entry.entry_id, + device_class="better_thermostat", + state_class="better_thermostat_state", + ) + hass.data[DOMAIN][entry.entry_id]["climate"] = bt_entity + async_add_entities([bt_entity]) + _LOGGER.debug( + "better_thermostat %s: async_setup_entry finished creating entity", + entry.data.get(CONF_NAME), ) @@ -192,48 +261,83 @@ class BetterThermostat(ClimateEntity, RestoreEntity, ABC): _attr_name = None _enable_turn_on_off_backwards_compatibility = False - async def set_temp_temperature(self, temperature): - """Set temporary target temperature.""" - if self._saved_temperature is None: - self._saved_temperature = self.bt_target_temp - self.bt_target_temp = convert_to_float( - temperature, self.device_name, "service.set_temp_temperature()" - ) - self.async_write_ha_state() - await self.control_queue_task.put(self) - else: - self.bt_target_temp = convert_to_float( - temperature, self.device_name, "service.set_temp_temperature()" - ) - self.async_write_ha_state() - await self.control_queue_task.put(self) - - async def restore_temp_temperature(self): - """Restore the previously saved target temperature.""" - if self._saved_temperature is not None: - self.bt_target_temp = convert_to_float( - self._saved_temperature, - self.device_name, - "service.restore_temp_temperature()", - ) - self._saved_temperature = None - self.async_write_ha_state() - await self.control_queue_task.put(self) + # ECO mode removed; set_eco_mode service and logic deleted. async def reset_heating_power(self): """Reset heating power to default value.""" - self.heating_power = 0.01 + self._heating_tracker.reset_power() self.async_write_ha_state() + # ------------------------------------------------------------------ + # Thermal tracker properties + # Used by: extra_state_attributes, helpers.py, sensor.py, + # _restore_state, _hydrate_thermal_from_state, + # _sync_controllers_to_state + # TODO: Eliminate most of these by accessing trackers directly. + # - heating_power_normalized, last_heating_power_stats, heating_cycles, + # last_heat_loss_stats, loss_cycles: only read by extra_state_attributes + # - heat_loss_rate: only used within climate.py + # - heating_power + heat_loss_rate: keep until sensor.py generic + # attribute mapping (_climate_attr) is refactored + # ------------------------------------------------------------------ + @property - def device_info(self): - return { - "identifiers": {(DOMAIN, self.unique_id)}, - "name": self.device_name, - "manufacturer": "Better Thermostat", - "model": self.model, - "sw_version": VERSION, - } + def heating_power(self) -> float: + """Return the current heating power in °C/min.""" + return self._heating_tracker.heating_power + + @heating_power.setter + def heating_power(self, value: float) -> None: + self._heating_tracker.heating_power = value + + @property + def heating_power_normalized(self) -> float | None: + """Return the normalized heating power.""" + return self._heating_tracker.normalized_power + + @heating_power_normalized.setter + def heating_power_normalized(self, value: float | None) -> None: + self._heating_tracker.normalized_power = value + + @property + def last_heating_power_stats(self) -> deque: + """Return recent heating power statistics.""" + return self._heating_tracker.stats + + @property + def heating_cycles(self) -> deque[HeatingCycle]: + """Return recorded heating cycles.""" + return self._heating_tracker.cycles + + @property + def heat_loss_rate(self) -> float: + """Return the current heat loss rate in °C/min.""" + return self._loss_tracker.heat_loss_rate + + @heat_loss_rate.setter + def heat_loss_rate(self, value: float) -> None: + self._loss_tracker.heat_loss_rate = value + + @property + def last_heat_loss_stats(self) -> deque[LossStats]: + """Return recent heat loss statistics.""" + return self._loss_tracker.stats + + @property + def loss_cycles(self) -> deque[LossCycle]: + """Return recorded heat loss cycles.""" + return self._loss_tracker.cycles + + @cached_property + def device_info(self) -> DeviceInfo: + """Return device info.""" + return DeviceInfo( + identifiers={(DOMAIN, self.unique_id)}, + name=self.device_name, + manufacturer="Better Thermostat", + model=self.model, + sw_version=VERSION, + ) def __init__( self, @@ -251,6 +355,7 @@ class BetterThermostat(ClimateEntity, RestoreEntity, ABC): target_temp_step, model, cooler_entity_id, + enabled_presets, unit, unique_id, device_class, @@ -268,46 +373,114 @@ class BetterThermostat(ClimateEntity, RestoreEntity, ABC): self.entity_ids = [] self.all_trvs = heater_entity_id self.sensor_entity_id = sensor_entity_id - self.humidity_entity_id = humidity_sensor_entity_id + self.humidity_sensor_entity_id = humidity_sensor_entity_id self.cooler_entity_id = cooler_entity_id self.window_id = window_id or None self.window_delay = window_delay or 0 self.window_delay_after = window_delay_after or 0 self.weather_entity = weather_entity or None self.outdoor_sensor = outdoor_sensor or None - self.off_temperature = float(off_temperature) or None - self.tolerance = float(tolerance) or 0.0 + # Robust off temperature parsing: preserve 0.0 and ignore invalid strings + self.off_temperature = None + if off_temperature not in (None, "", "None"): # allow numeric 0 + try: + parsed_off = float(off_temperature) + # Accept any float (including 0.0); reject extreme nonsense + if -100.0 < parsed_off < 150.0: + if unit == UnitOfTemperature.FAHRENHEIT: + parsed_off = TemperatureConverter.convert( + parsed_off, + UnitOfTemperature.FAHRENHEIT, + UnitOfTemperature.CELSIUS, + ) + self.off_temperature = parsed_off + else: + _LOGGER.warning( + "better_thermostat %s: off_temperature %.2f outside plausible range, ignoring", + self.device_name, + parsed_off, + ) + except (TypeError, ValueError): + _LOGGER.warning( + "better_thermostat %s: invalid off_temperature '%s', ignoring", + self.device_name, + off_temperature, + ) + + # Robust tolerance parsing & sanitizing + try: + self.tolerance = float(tolerance) if tolerance is not None else 0.0 + if unit == UnitOfTemperature.FAHRENHEIT: + self.tolerance = self.tolerance * 5.0 / 9.0 + except (TypeError, ValueError): + _LOGGER.warning( + "better_thermostat %s: invalid tolerance '%s', falling back to 0.0", + self.device_name, + tolerance, + ) + self.tolerance = 0.0 + if self.tolerance < 0: + _LOGGER.warning( + "better_thermostat %s: negative tolerance '%s' adjusted to 0.0", + self.device_name, + self.tolerance, + ) + self.tolerance = 0.0 + if self.tolerance > 10: + _LOGGER.warning( + "better_thermostat %s: unusually high tolerance '%s' (>10) may cause sluggish response", + self.device_name, + self.tolerance, + ) self._unique_id = unique_id self._unit = unit self._device_class = device_class self._state_class = state_class self._hvac_list = [HVACMode.HEAT, HVACMode.OFF] - self._preset_mode = PRESET_NONE self.map_on_hvac_mode = HVACMode.HEAT - self.next_valve_maintenance = datetime.now() + timedelta( + self.next_valve_maintenance = dt_util.now() + timedelta( hours=randint(1, 24 * 5) ) self.cur_temp = None - self._current_humidity = 0 + self._current_humidity: float | None = 0.0 self.window_open = None - self.bt_target_temp_step = float(target_temp_step) or 0.0 - self.bt_min_temp = 0 - self.bt_max_temp = 30 + self.bt_target_temp_step = ( + float(target_temp_step) + if target_temp_step and target_temp_step != "0.0" + else None + ) + if ( + self.bt_target_temp_step is not None + and unit == UnitOfTemperature.FAHRENHEIT + ): + self.bt_target_temp_step = round(self.bt_target_temp_step * 5.0 / 9.0, 4) + self.bt_min_temp: float | None = 0.0 + self.bt_max_temp: float | None = 30.0 self.bt_target_temp = 5.0 self.bt_target_cooltemp = None self._support_flags = SUPPORT_FLAGS | ClimateEntityFeature.PRESET_MODE - self.bt_hvac_mode = None + self.bt_hvac_mode: HVACMode | None = None + # Track min/max encountered target temps (initialize to default span) + self.min_target_temp = 18.0 + self.max_target_temp = 21.0 self.closed_window_triggered = False self.call_for_heat = True self.ignore_states = False self.last_dampening_timestamp = None self.version = VERSION - self.last_change = datetime.now() - timedelta(hours=2) - self.last_external_sensor_change = datetime.now() - timedelta(hours=2) - self.last_internal_sensor_change = datetime.now() - timedelta(hours=2) + self.last_change = dt_util.now() - timedelta(hours=2) + self.last_external_sensor_change = dt_util.now() - timedelta(hours=2) + self.last_internal_sensor_change = dt_util.now() - timedelta(hours=2) self._temp_lock = asyncio.Lock() + self.bt_update_lock = False self.startup_running = True self._saved_temperature = None + if enabled_presets is not None: + self.preset_mgr = PresetManager(enabled_presets=enabled_presets) + else: + self.preset_mgr = PresetManager() + # Config entry id (same as unique id passed in) used for durable persistence beyond RestoreEntity + self._config_entry_id = self._unique_id self.last_avg_outdoor_temp = None self.last_main_hvac_mode = None self.last_window_state = None @@ -316,23 +489,77 @@ class BetterThermostat(ClimateEntity, RestoreEntity, ABC): self.context = None self.attr_hvac_action = None self.old_attr_hvac_action = None + self._hysteresis = ToleranceHysteresis() self.heating_start_temp = None self.heating_start_timestamp = None self.heating_end_temp = None self.heating_end_timestamp = None + # Thermal learning trackers (state machines for heating power / heat loss) + # Must be initialised before property-based assignments below. + self._heating_tracker = HeatingPowerTracker() + self._loss_tracker = HeatLossTracker() + # Heat loss tracking (idle cooling rate) + self.loss_start_temp = None + self.loss_start_timestamp = None + self.loss_end_temp = None + self.loss_end_timestamp = None + self.heat_loss_rate = 0.01 + self._loss_last_action = None + self._tolerance_last_action = HVACAction.IDLE + self._tolerance_hold_active = False self._async_unsub_state_changed = None self.all_entities = [] self.devices_states = {} self.devices_errors = [] - self.control_queue_task = asyncio.Queue(maxsize=1) + # Degraded mode: thermostat continues operating with some sensors unavailable + self.degraded_mode = False + self.unavailable_sensors = [] + # Startup grace period suppresses the degraded-mode WARNING and the HA + # repair issue while slow integrations finish initializing. + self._degraded_grace_until: datetime | None = None + self._degraded_warning_emitted: bool = False + self.control_queue_task: asyncio.Queue[BetterThermostat] = asyncio.Queue( + maxsize=1 + ) if self.window_id is not None: - self.window_queue_task = asyncio.Queue(maxsize=1) - asyncio.create_task(control_queue(self)) - if self.window_id is not None: - asyncio.create_task(window_queue(self)) - self.heating_power = 0.01 - self.last_heating_power_stats = [] + self.window_queue_task: asyncio.Queue[BetterThermostat] = asyncio.Queue( + maxsize=1 + ) + self._control_task = None + self._window_task = None self.is_removed = False + # Valve maintenance control + self.in_maintenance = False + # If control actions are requested during valve maintenance, defer them and + # trigger one control cycle once maintenance finishes. + self._control_needed_after_maintenance = False + # Balance / Hydraulic: temperature trend (K/min) + self.temp_slope = None + self._slope_last_temp = None + self._slope_last_ts = None + # External temperature filter (anti-jitter for controllers like MPC) + # 900s = 15min, 1800s = 30min + self.external_temp_ema_tau_s = 300.0 + self.external_temp_ema = None + self._external_temp_ema_ts = None + self.cur_temp_filtered = None + # Unified state persistence (replaces per-controller stores) + self.state_mgr: StateManager | None = None + self._save_cancel: CALLBACK_TYPE | None = None + + self.last_known_external_temp = None + self._slope_periodic_last_ts = None + + # Anti-flicker state + self.flicker_unignore_cancel = None + self.flicker_candidate = None + self.plateau_timer_cancel = None + self.last_change_direction = 0 + self.prev_stable_temp = None + self.accum_delta = 0.0 + self.accum_dir = 0 + self.pending_temp = None + self.pending_since = None async def async_added_to_hass(self): """Run when entity about to be added. @@ -343,7 +570,16 @@ class BetterThermostat(ClimateEntity, RestoreEntity, ABC): """ if isinstance(self.all_trvs, str): return _LOGGER.error( - "You updated from version before 1.0.0-Beta36 of the Better Thermostat integration, you need to remove the BT devices (integration) and add it again." + "You updated from version before 1.0.0-Beta36 of the Better Thermostat integration, " + "you need to remove the BT devices (integration) and add it again." + ) + + self._control_task = self.hass.async_create_background_task( + control_queue(self), name=f"bt_control_queue_{self.device_name}" + ) + if self.window_id is not None: + self._window_task = self.hass.async_create_background_task( + window_queue(self), name=f"bt_window_queue_{self.device_name}" ) if self.cooler_entity_id is not None: @@ -357,22 +593,76 @@ class BetterThermostat(ClimateEntity, RestoreEntity, ABC): for trv in self.all_trvs: _calibration = 1 - if trv["advanced"]["calibration"] == "local_calibration_based": + _advanced = trv.get("advanced", {}) + _calibration_type = _advanced.get("calibration") + if _calibration_type == CalibrationType.TARGET_TEMP_BASED: _calibration = 0 - if trv["advanced"]["calibration"] == "hybrid_calibration": + if _calibration_type == CalibrationType.DIRECT_VALVE_BASED: _calibration = 2 + if _calibration_type == CalibrationType.LOCAL_BASED: + _calibration = 3 _adapter = await load_adapter(self, trv["integration"], trv["trv"]) - _model_quirks = await load_model_quirks(self, trv["model"], trv["trv"]) + # Resolve/refresh model dynamically at startup to ensure correct quirks + resolved_model = trv.get("model") + try: + # prefers state model_id when present + detected_model = await get_device_model(self, trv["trv"]) + if ( + isinstance(detected_model, str) + and detected_model + and detected_model != resolved_model + ): + _LOGGER.info( + "better_thermostat %s: detected model '%s' for %s (was '%s' in config), using detected model", + self.device_name, + detected_model, + trv["trv"], + resolved_model, + ) + resolved_model = detected_model + except (AttributeError, TypeError) as e: + _LOGGER.debug( + "better_thermostat %s: get_device_model(%s) failed: %s", + self.device_name, + trv.get("trv"), + e, + ) + _LOGGER.debug( + "better_thermostat %s: loading model quirks: model='%s' trv='%s'", + self.device_name, + resolved_model, + trv.get("trv"), + ) + _model_quirks = await load_model_quirks(self, resolved_model, trv["trv"]) + try: + mod_name = getattr(_model_quirks, "__name__", str(_model_quirks)) + _LOGGER.debug( + "better_thermostat %s: loaded model quirks module '%s' for model '%s' (trv %s)", + self.device_name, + mod_name, + resolved_model, + trv.get("trv"), + ) + except (AttributeError, TypeError) as e: + _LOGGER.debug( + "better_thermostat %s: could not determine quirks module name for model '%s' (trv %s): %s", + self.device_name, + resolved_model, + trv.get("trv"), + e, + ) self.real_trvs[trv["trv"]] = { "calibration": _calibration, "integration": trv["integration"], "adapter": _adapter, "model_quirks": _model_quirks, - "model": trv["model"], - "advanced": trv["advanced"], + "model": resolved_model, + "advanced": _advanced, "ignore_trv_states": False, "valve_position": None, "valve_position_entity": None, + "valve_position_writable": None, + "valve_max_opening": 100.0, "max_temp": None, "min_temp": None, "target_temp_step": None, @@ -381,8 +671,9 @@ class BetterThermostat(ClimateEntity, RestoreEntity, ABC): "hvac_modes": None, "hvac_mode": None, "local_temperature_calibration_entity": None, - "local_calibration_min": None, - "local_calibration_max": None, + "local_calibration_min": -7, + "local_calibration_max": 7, + "local_calibration_step": 0.5, "calibration_received": True, "target_temp_received": True, "system_mode_received": True, @@ -395,6 +686,20 @@ class BetterThermostat(ClimateEntity, RestoreEntity, ABC): def on_remove(): self.is_removed = True + # Cancel any pending debounced save so it doesn't fire after + # the entity is gone. flush() below will save immediately. + if self._save_cancel is not None: + self._save_cancel() + self._save_cancel = None + if self.state_mgr is not None: + try: + self._sync_controllers_to_state() + self.hass.async_create_background_task( + self.state_mgr.flush(), + name=f"bt_state_flush_{self.device_name}", + ) + except RuntimeError: + pass self.async_on_remove(on_remove) @@ -404,6 +709,27 @@ class BetterThermostat(ClimateEntity, RestoreEntity, ABC): "better_thermostat %s: Waiting for entity to be ready...", self.device_name ) + # Unified state persistence + try: + from .utils.migrate_v0_stores import migrate_v0_stores + + self.state_mgr = StateManager(self.hass, self._config_entry_id) + await self.state_mgr.load() + await migrate_v0_stores( + self.hass, + self.state_mgr, + entity_prefix=f"{self._unique_id}:", + config_entry_id=self._config_entry_id, + ) + self._hydrate_controllers_from_state() + self._hydrate_thermal_from_state() + except (FileNotFoundError, PermissionError, RuntimeError) as e: + _LOGGER.debug( + "better_thermostat %s: state storage init/load failed: %s", + self.device_name, + e, + ) + @callback def _async_startup(*_): """Init on startup. @@ -414,18 +740,21 @@ class BetterThermostat(ClimateEntity, RestoreEntity, ABC): All parameters are piped. """ self.context = Context() - loop = asyncio.get_event_loop() - loop.create_task(self.startup()) + self.hass.async_create_background_task( + self.startup(), name=f"better_thermostat_startup_{self.device_name}" + ) - if self.hass.state == CoreState.running: - _async_startup() - else: - self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, _async_startup) + # Run after Home Assistant has finished starting (CoreState.running), + # so dependent integrations like ZHA / MQTT have published their + # entities and are ready to accept service calls. Runs immediately + # if BT is added after HA is already up. + self.async_on_remove(async_at_started(self.hass, _async_startup)) async def _trigger_check_weather(self, event=None): - _check = await check_all_entities(self) + _check = await check_critical_entities(self) if _check is False: return + await check_and_update_degraded_mode(self) await check_weather(self) if self._last_call_for_heat != self.call_for_heat: self._last_call_for_heat = self.call_for_heat @@ -435,9 +764,16 @@ class BetterThermostat(ClimateEntity, RestoreEntity, ABC): await self.control_queue_task.put(self) async def _trigger_time(self, event=None): - _check = await check_all_entities(self) + _check = await check_critical_entities(self) if _check is False: return + await check_and_update_degraded_mode(self) + if getattr(self, "in_maintenance", False): + _LOGGER.debug( + "better_thermostat %s: periodic tick skipped (valve maintenance running)", + self.device_name, + ) + return _LOGGER.debug( "better_thermostat %s: get last avg outdoor temps...", self.device_name ) @@ -446,33 +782,137 @@ class BetterThermostat(ClimateEntity, RestoreEntity, ABC): if event is not None: await self.control_queue_task.put(self) - async def _trigger_temperature_change(self, event): - _check = await check_all_entities(self) + async def _trigger_outdoor_change(self, event=None): + """Re-evaluate the outdoor-temperature threshold on sensor changes. + + The threshold is otherwise only refreshed at startup and the daily + tick. Re-running the ambient check when the outdoor sensor changes + lets heating react promptly. Control is only re-queued when + ``call_for_heat`` actually flips, so frequent outdoor readings that + stay on the same side of the threshold do not spam the queue. + """ + _check = await check_critical_entities(self) if _check is False: return + await check_and_update_degraded_mode(self) + if getattr(self, "in_maintenance", False): + return + await check_ambient_air_temperature(self) + if self._last_call_for_heat != self.call_for_heat: + self._last_call_for_heat = self.call_for_heat + self.async_write_ha_state() + if event is not None: + await self.control_queue_task.put(self) + + async def _trigger_temperature_change(self, event): + _check = await check_critical_entities(self) + if _check is False: + return + await check_and_update_degraded_mode(self) self.async_set_context(event.context) if (event.data.get("new_state")) is None: return - self.hass.async_create_task(trigger_temperature_change(self, event)) + self.hass.async_create_background_task( + trigger_temperature_change(self, event), + name=f"bt_trigger_temp_change_{self.device_name}", + ) + + async def _external_temperature_keepalive(self, event=None): + """Re-send the external temperature regularly to the TRVs. + + Many devices expect an update at least every ~30 minutes. + """ + try: + cur = self.cur_temp + if cur is None: + _LOGGER.debug( + "better_thermostat %s: external_temperature keepalive skipped (cur_temp is None)", + self.device_name, + ) + return + + # Verwende die bekannten TRV-Entity-IDs (Keys in real_trvs) + trv_ids = list(self.real_trvs.keys()) + # Fallback (sollte i.d.R. nicht benötigt werden) + if not trv_ids and hasattr(self, "entity_ids"): + trv_ids = list(self.entity_ids or []) + if not trv_ids: + _LOGGER.debug( + "better_thermostat %s: external_temperature keepalive: no TRVs found", + self.device_name, + ) + return + else: + _LOGGER.debug( + "better_thermostat %s: external_temperature keepalive: %d TRV(s) found", + self.device_name, + len(trv_ids), + ) + + for trv_id in trv_ids: + try: + quirks = ( + self.real_trvs.get(trv_id, {}).get("model_quirks") + if hasattr(self, "real_trvs") + else None + ) + if quirks and hasattr(quirks, "maybe_set_external_temperature"): + ok = await quirks.maybe_set_external_temperature( + self, trv_id, cur + ) + _LOGGER.debug( + "better_thermostat %s: external_temperature keepalive sent to %s (ok=%s, value=%s)", + self.device_name, + trv_id, + ok, + cur, + ) + else: + _LOGGER.debug( + "better_thermostat %s: no quirks with maybe_set_external_temperature for %s", + self.device_name, + trv_id, + ) + except (OSError, RuntimeError, AttributeError, TypeError): + _LOGGER.debug( + "better_thermostat %s: external_temperature keepalive write failed for %s (non critical)", + self.device_name, + trv_id, + ) + except (OSError, RuntimeError, AttributeError, TypeError): + _LOGGER.debug( + "better_thermostat %s: external_temperature keepalive encountered an error", + self.device_name, + ) async def _trigger_humidity_change(self, event): - _check = await check_all_entities(self) + _check = await check_critical_entities(self) if _check is False: return + await check_and_update_degraded_mode(self) self.async_set_context(event.context) if (event.data.get("new_state")) is None: return - self._current_humidity = convert_to_float( - str(self.hass.states.get(self.humidity_entity_id).state), - self.device_name, - "humidity_update", - ) + # Only update humidity if sensor is available + if is_entity_available(self.hass, self.humidity_sensor_entity_id): + humidity_state = self.hass.states.get(self.humidity_sensor_entity_id) + if humidity_state is not None: + self._current_humidity = convert_to_float( + str(humidity_state.state), self.device_name, "humidity_update" + ) self.async_write_ha_state() async def _trigger_trv_change(self, event): - _check = await check_all_entities(self) + _check = await check_critical_entities(self) if _check is False: return + await check_and_update_degraded_mode(self) + if getattr(self, "in_maintenance", False): + _LOGGER.debug( + "better_thermostat %s: TRV change skipped (valve maintenance running)", + self.device_name, + ) + return self.async_set_context(event.context) if self._async_unsub_state_changed is None: return @@ -480,35 +920,54 @@ class BetterThermostat(ClimateEntity, RestoreEntity, ABC): if (event.data.get("new_state")) is None: return - self.hass.async_create_task(trigger_trv_change(self, event)) + self.hass.async_create_background_task( + trigger_trv_change(self, event), + name=f"bt_trigger_trv_change_{self.device_name}", + ) async def _trigger_window_change(self, event): - _check = await check_all_entities(self) + _check = await check_critical_entities(self) if _check is False: return + await check_and_update_degraded_mode(self) self.async_set_context(event.context) if (event.data.get("new_state")) is None: return - self.hass.async_create_task(trigger_window_change(self, event)) + # Only process window changes if window sensor is available + if is_entity_available(self.hass, self.window_id): + self.hass.async_create_background_task( + trigger_window_change(self, event), + name=f"bt_trigger_window_change_{self.device_name}", + ) - async def _tigger_cooler_change(self, event): - _check = await check_all_entities(self) + async def _trigger_cooler_change(self, event): + _check = await check_critical_entities(self) if _check is False: return + await check_and_update_degraded_mode(self) self.async_set_context(event.context) if (event.data.get("new_state")) is None: return - self.hass.async_create_task(trigger_cooler_change(self, event)) + self.hass.async_create_background_task( + trigger_cooler_change(self, event), + name=f"bt_trigger_cooler_change_{self.device_name}", + ) - async def startup(self): - """Run when entity about to be added. + def _set_trv_calibration_defaults(self, trv): + """Set default calibration values for TRV.""" + if self.real_trvs[trv].get("last_calibration") is None: + self.real_trvs[trv]["last_calibration"] = 0 + if self.real_trvs[trv].get("local_calibration_min") is None: + self.real_trvs[trv]["local_calibration_min"] = -7 + if self.real_trvs[trv].get("local_calibration_max") is None: + self.real_trvs[trv]["local_calibration_max"] = 7 + if self.real_trvs[trv].get("local_calibration_step") is None: + self.real_trvs[trv]["local_calibration_step"] = 0.5 - Returns - ------- - None - """ + async def startup(self) -> None: + """Orchestrate entity startup.""" while self.startup_running: _LOGGER.info( "better_thermostat %s: Starting version %s. Waiting for entity to be ready...", @@ -517,152 +976,271 @@ class BetterThermostat(ClimateEntity, RestoreEntity, ABC): ) sensor_state = self.hass.states.get(self.sensor_entity_id) - if sensor_state is not None: - if sensor_state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN, None): - _LOGGER.info( - "better_thermostat %s: waiting for sensor entity with id '%s' to become fully available...", - self.device_name, - self.sensor_entity_id, - ) - await asyncio.sleep(10) - continue - - try: - for trv in self.real_trvs.keys(): - trv_state = self.hass.states.get(trv) - if trv_state is None: - _LOGGER.info( - "better_thermostat %s: waiting for TRV/climate entity with id '%s' to become fully available...", - self.device_name, - trv, - ) - await asyncio.sleep(10) - raise ContinueLoop - if trv_state is not None: - if trv_state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN, None): - _LOGGER.info( - "better_thermostat %s: waiting for TRV/climate entity with id '%s' to become fully available...", - self.device_name, - trv, - ) - await asyncio.sleep(10) - raise ContinueLoop - except ContinueLoop: + if not self._check_entities_ready(sensor_state): + await asyncio.sleep(20) continue - if self.window_id is not None: - if self.hass.states.get(self.window_id).state in ( - STATE_UNAVAILABLE, - STATE_UNKNOWN, - None, - ): - _LOGGER.info( - "better_thermostat %s: waiting for window sensor entity with id '%s' to become fully available...", - self.device_name, - self.window_id, - ) - await asyncio.sleep(10) - continue + states = self._collect_trv_states() + self._resolve_temperature_range(states) + self._initialize_sensors(sensor_state) + await check_and_update_degraded_mode(self) + await self._restore_state(states) + self._validate_hvac_mode(states) + await self._initialize_trvs() + await self._finalize_startup() + break - if self.cooler_entity_id is not None: - if self.hass.states.get(self.cooler_entity_id).state in ( - STATE_UNAVAILABLE, - STATE_UNKNOWN, - None, - ): - _LOGGER.info( - "better_thermostat %s: waiting for cooler entity with id '%s' to become fully available...", - self.device_name, - self.cooler_entity_id, - ) - await asyncio.sleep(10) - continue + def _check_entities_ready(self, sensor_state: State | None) -> bool: + """Check whether sensor and all TRVs are available. - if self.humidity_entity_id is not None: - humidity_state = self.hass.states.get(self.humidity_entity_id) - if humidity_state is None or humidity_state.state in ( - STATE_UNAVAILABLE, - STATE_UNKNOWN, - None, - ): - _LOGGER.info( - "better_thermostat %s: waiting for humidity sensor entity with id '%s' to become fully available...", - self.device_name, - self.humidity_entity_id, - ) - await asyncio.sleep(10) - continue - - if self.outdoor_sensor is not None: - if self.hass.states.get(self.outdoor_sensor).state in ( - STATE_UNAVAILABLE, - STATE_UNKNOWN, - None, - ): - _LOGGER.info( - "better_thermostat %s: waiting for outdoor sensor entity with id '%s' to become fully available...", - self.device_name, - self.outdoor_sensor, - ) - await asyncio.sleep(10) - continue - - if self.weather_entity is not None: - if self.hass.states.get(self.weather_entity).state in ( - STATE_UNAVAILABLE, - STATE_UNKNOWN, - None, - ): - _LOGGER.info( - "better_thermostat %s: waiting for weather entity with id '%s' to become fully available...", - self.device_name, - self.weather_entity, - ) - await asyncio.sleep(10) - continue - - states = [ - state - for entity_id in self.real_trvs - if (state := self.hass.states.get(entity_id)) is not None - ] - - self.bt_min_temp = reduce_attribute(states, ATTR_MIN_TEMP, reduce=max) - self.bt_max_temp = reduce_attribute(states, ATTR_MAX_TEMP, reduce=min) - - if self.bt_target_temp_step == 0.0: - self.bt_target_temp_step = reduce_attribute( - states, ATTR_TARGET_TEMP_STEP, reduce=max - ) - - self.all_entities.append(self.sensor_entity_id) - - self.cur_temp = convert_to_float( - str(sensor_state.state), self.device_name, "startup()" + Returns True when every entity is ready, False otherwise. + """ + if sensor_state is None or sensor_state.state in ( + STATE_UNAVAILABLE, + STATE_UNKNOWN, + None, + ): + _LOGGER.info( + "better_thermostat %s: waiting for sensor entity with id '%s' to become fully available...", + self.device_name, + self.sensor_entity_id, ) - if self.humidity_entity_id is not None: - self.all_entities.append(self.humidity_entity_id) - self._current_humidity = convert_to_float( - str(self.hass.states.get(self.humidity_entity_id).state), + return False + + for trv in self.real_trvs: + trv_state = self.hass.states.get(trv) + if trv_state is None or trv_state.state in ( + STATE_UNAVAILABLE, + STATE_UNKNOWN, + None, + ): + _LOGGER.info( + "better_thermostat %s: waiting for TRV/climate entity with id '%s' to become fully available...", self.device_name, - "startup()", + trv, + ) + return False + return True + + def _collect_trv_states(self) -> list[State]: + """Collect current State objects for all TRVs and optional cooler.""" + states = [ + state + for entity_id in self.real_trvs + if (state := self.hass.states.get(entity_id)) is not None + ] + + # Include cooler entity in min/max calculation to ensure BT's + # temperature range is compatible with all controlled devices + if self.cooler_entity_id is not None: + cooler_state = self.hass.states.get(self.cooler_entity_id) + if cooler_state is not None and cooler_state.state not in ( + STATE_UNAVAILABLE, + STATE_UNKNOWN, + None, + ): + states.append(cooler_state) + + return states + + def _resolve_temperature_range(self, states: list[State]) -> None: + """Derive min/max/step temperature from TRV states.""" + # Convert each child's min/max to Celsius before reducing, because + # children may report in Fahrenheit while BT works internally in °C. + min_temps: list[float] = [] + max_temps: list[float] = [] + steps: list[float] = [] + for s in states: + _unit = state_temperature_unit( + s.attributes, self.hass.config.units.temperature_unit + ) + _raw_min = s.attributes.get(ATTR_MIN_TEMP) + if _raw_min is not None: + _c = convert_to_float_celsius( + str(_raw_min), + self.device_name, + "_resolve_temperature_range(min)", + unit_of_measurement=_unit, + ) + if _c is not None: + min_temps.append(_c) + _raw_max = s.attributes.get(ATTR_MAX_TEMP) + if _raw_max is not None: + _c = convert_to_float_celsius( + str(_raw_max), + self.device_name, + "_resolve_temperature_range(max)", + unit_of_measurement=_unit, + ) + if _c is not None: + max_temps.append(_c) + _raw_step = s.attributes.get(ATTR_TARGET_TEMP_STEP) + if _raw_step is not None: + _sf = convert_to_float( + str(_raw_step), self.device_name, "_resolve_temperature_range(step)" + ) + if _sf is not None: + # Convert step as a temperature delta if child uses °F + if _unit == UnitOfTemperature.FAHRENHEIT: + _sf = round(_sf * 5.0 / 9.0, 4) + steps.append(_sf) + self.bt_min_temp = max(min_temps) if min_temps else None + self.bt_max_temp = min(max_temps) if max_temps else None + + if ( + self.bt_min_temp is not None + and self.bt_max_temp is not None + and self.bt_min_temp > self.bt_max_temp + ): + _LOGGER.warning( + "better_thermostat %s: min temp (%.1f°) > max temp (%.1f°). " + "This indicates non-overlapping temperature ranges between " + "heater and cooler entities. Please check your configuration.", + self.device_name, + self.bt_min_temp, + self.bt_max_temp, + ) + + if self.bt_target_temp_step is None: + self.bt_target_temp_step = max(steps) if steps else None + + def _initialize_sensors(self, sensor_state: State | None) -> None: + """Set up room temperature, humidity, cooler and window sensors.""" + self.all_entities.append(self.sensor_entity_id) + + # Handle room temperature sensor with TRV fallback + room_candidate: float | None = None + if sensor_state is not None and sensor_state.state not in ( + STATE_UNAVAILABLE, + STATE_UNKNOWN, + None, + ): + room_candidate = convert_to_float_celsius( + str(sensor_state.state), + self.device_name, + "startup()", + unit_of_measurement=sensor_state.attributes.get("unit_of_measurement"), + ) + if not is_reasonable_temperature(room_candidate): + _LOGGER.warning( + "better_thermostat %s: Room temperature sensor '%s' reports " + "implausible value %s; falling back to TRV internal temperature.", + self.device_name, + self.sensor_entity_id, + room_candidate, + ) + room_candidate = None + + if room_candidate is not None: + self.cur_temp = room_candidate + else: + if sensor_state is None or sensor_state.state in ( + STATE_UNAVAILABLE, + STATE_UNKNOWN, + None, + ): + _LOGGER.warning( + "better_thermostat %s: Room temperature sensor '%s' unavailable. " + "Falling back to TRV internal temperature.", + self.device_name, + self.sensor_entity_id, + ) + self.cur_temp = None + for trv_id in self.real_trvs: + trv_state = self.hass.states.get(trv_id) + if trv_state is None: + continue + trv_temp = trv_state.attributes.get("current_temperature") + if trv_temp is None: + continue + candidate = attr_to_celsius( + self, + trv_state, + "current_temperature", + None, + "startup() TRV fallback", + ) + if not is_reasonable_temperature(candidate): + _LOGGER.warning( + "better_thermostat %s: TRV '%s' reports implausible " + "current_temperature %s; trying next TRV.", + self.device_name, + trv_id, + candidate, + ) + continue + self.cur_temp = candidate + _LOGGER.info( + "better_thermostat %s: Using TRV '%s' temperature: %.1f°C", + self.device_name, + trv_id, + candidate, + ) + break + if self.cur_temp is None: + self.cur_temp = DEFAULT_FALLBACK_TEMPERATURE + _LOGGER.warning( + "better_thermostat %s: No temperature available, using default %.1f°C", + self.device_name, + DEFAULT_FALLBACK_TEMPERATURE, ) - if self.cooler_entity_id is not None: - self.bt_target_cooltemp = convert_to_float( - str( - self.hass.states.get(self.cooler_entity_id).attributes.get( - "temperature" - ) - ), + # Initialize EMA with current temperature at startup + if self.cur_temp is not None: + self.last_known_external_temp = self.cur_temp + try: + from .events.temperature import _update_external_temp_ema + + _update_external_temp_ema(self, float(self.cur_temp)) + _LOGGER.debug( + "better_thermostat %s: initialized external_temp_ema at startup with %.2f", self.device_name, - "startup()", + self.cur_temp, + ) + except (ValueError, TypeError, ImportError) as e: + _LOGGER.warning( + "better_thermostat %s: failed to initialize external_temp_ema at startup: %s", + self.device_name, + e, ) - if self.window_id is not None: - self.all_entities.append(self.window_id) - window = self.hass.states.get(self.window_id) + if self.humidity_sensor_entity_id is not None: + self.all_entities.append(self.humidity_sensor_entity_id) + _hum_state = self.hass.states.get(self.humidity_sensor_entity_id) + if _hum_state is not None and _hum_state.state not in ( + STATE_UNAVAILABLE, + STATE_UNKNOWN, + None, + ): + self._current_humidity = ( + convert_to_float( + str(_hum_state.state), self.device_name, "startup()" + ) + or 0.0 + ) + # else: already logged warning above, _current_humidity stays None + if self.cooler_entity_id is not None: + _cooler_state = self.hass.states.get(self.cooler_entity_id) + if _cooler_state is not None and _cooler_state.state not in ( + STATE_UNAVAILABLE, + STATE_UNKNOWN, + None, + ): + self.bt_target_cooltemp = attr_to_celsius( + self, _cooler_state, "temperature", None, "startup()" + ) + # else: already logged warning above + + if self.window_id is not None: + self.all_entities.append(self.window_id) + window = self.hass.states.get(self.window_id) + + if window is not None and window.state not in ( + STATE_UNAVAILABLE, + STATE_UNKNOWN, + None, + ): check = window.state if check in ("on", "open", "true"): self.window_open = True @@ -674,395 +1252,950 @@ class BetterThermostat(ClimateEntity, RestoreEntity, ABC): "Open" if self.window_open else "Closed", ) else: + # At startup, unavailable/unknown usually means the sensor + # has not joined HA yet, so heating continues normally + # (assume closed). At runtime the same states mean a live + # sensor was lost and count as open (see events/window.py). self.window_open = False + _LOGGER.debug( + "better_thermostat %s: window sensor unavailable, assuming closed", + self.device_name, + ) + else: + self.window_open = False - # Check If we have an old state - old_state = await self.async_get_last_state() - if old_state is not None: - # If we have no initial temperature, restore - # If we have a previously saved temperature - if old_state.attributes.get(ATTR_TEMPERATURE) is None: - self.bt_target_temp = reduce_attribute( - states, ATTR_TEMPERATURE, reduce=lambda *data: mean(data) - ) + async def _restore_state(self, states: list[State]) -> None: + """Restore previous state from HA state machine or fall back to defaults.""" + _LOGGER.debug( + "better_thermostat %s: calling async_get_last_state", self.device_name + ) + old_state = await self.async_get_last_state() + _LOGGER.debug( + "better_thermostat %s: async_get_last_state returned", self.device_name + ) + if old_state is not None: + _LOGGER.debug("better_thermostat %s: restoring state...", self.device_name) + # Restore external_temp_ema if available (overwrites startup init) + if "external_temp_ema" in old_state.attributes: + try: + _restored_ema = float(old_state.attributes["external_temp_ema"]) + self.external_temp_ema = _restored_ema + self.cur_temp_filtered = round(_restored_ema, 2) + # Reset timestamp to now so the next delta is calculated from restart time + self._external_temp_ema_ts = monotonic() _LOGGER.debug( - "better_thermostat %s: Undefined target temperature, falling back to %s", + "better_thermostat %s: restored external_temp_ema from state: %.2f", self.device_name, - self.bt_target_temp, - ) - else: - _oldtarget_temperature = float( - old_state.attributes.get(ATTR_TEMPERATURE) - ) - # if the saved temperature is lower than the min_temp, set it to min_temp - if _oldtarget_temperature < self.bt_min_temp: - _LOGGER.warning( - "better_thermostat %s: Saved target temperature %s is lower than min_temp %s, setting to min_temp", - self.device_name, - _oldtarget_temperature, - self.bt_min_temp, - ) - _oldtarget_temperature = self.bt_min_temp - # if the saved temperature is higher than the max_temp, set it to max_temp - elif _oldtarget_temperature > self.bt_max_temp: - _LOGGER.warning( - "better_thermostat %s: Saved target temperature %s is higher than max_temp %s, setting to max_temp", - self.device_name, - _oldtarget_temperature, - self.bt_min_temp, - ) - _oldtarget_temperature = self.bt_max_temp - self.bt_target_temp = convert_to_float( - str(_oldtarget_temperature), self.device_name, "startup()" - ) - if old_state.state not in (STATE_UNAVAILABLE, STATE_UNKNOWN, None): - self.bt_hvac_mode = old_state.state - if old_state.attributes.get(ATTR_STATE_CALL_FOR_HEAT, None) is not None: - self.call_for_heat = old_state.attributes.get( - ATTR_STATE_CALL_FOR_HEAT - ) - if ( - old_state.attributes.get(ATTR_STATE_SAVED_TEMPERATURE, None) - is not None - ): - self._saved_temperature = convert_to_float( - str( - old_state.attributes.get(ATTR_STATE_SAVED_TEMPERATURE, None) - ), - self.device_name, - "startup()", - ) - if old_state.attributes.get(ATTR_STATE_HUMIDIY, None) is not None: - self._current_humidity = old_state.attributes.get( - ATTR_STATE_HUMIDIY - ) - if old_state.attributes.get(ATTR_STATE_MAIN_MODE, None) is not None: - self.last_main_hvac_mode = old_state.attributes.get( - ATTR_STATE_MAIN_MODE - ) - if old_state.attributes.get(ATTR_STATE_HEATING_POWER, None) is not None: - self.heating_power = float( - old_state.attributes.get(ATTR_STATE_HEATING_POWER) + _restored_ema, ) + except (ValueError, TypeError): + pass - else: - # No previous state, try and restore defaults - if self.bt_target_temp is None or not isinstance( - self.bt_target_temp, float - ): - _LOGGER.info( - "better_thermostat %s: No previously saved temperature found on startup, get it from the TRV", - self.device_name, - ) - self.bt_target_temp = reduce_attribute( - states, ATTR_TEMPERATURE, reduce=lambda *data: mean(data) - ) - - # if hvac mode could not be restored, turn heat off - if self.bt_hvac_mode in (STATE_UNAVAILABLE, STATE_UNKNOWN, None): - current_hvac_modes = [ - x.state for x in states if x.state != HVACMode.OFF - ] - # return the most common hvac mode (what the thermostat is set to do) except OFF - if current_hvac_modes: - _temp_bt_hvac_mode = max( - set(current_hvac_modes), key=current_hvac_modes.count - ) - if _temp_bt_hvac_mode is not HVACMode.OFF: - self.bt_hvac_mode = HVACMode.HEAT - else: - self.bt_hvac_mode = HVACMode.OFF + # Restore temp_slope if available + if "temp_slope_K_min" in old_state.attributes: + try: + _restored_slope = float(old_state.attributes["temp_slope_K_min"]) + self.temp_slope = _restored_slope _LOGGER.debug( - "better_thermostat %s: No previously hvac mode found on startup, turn bt to trv mode %s", + "better_thermostat %s: restored temp_slope from state: %.4f", self.device_name, - self.bt_hvac_mode, + _restored_slope, ) - # return off if all are off - elif all(x.state == HVACMode.OFF for x in states): - self.bt_hvac_mode = HVACMode.OFF - _LOGGER.debug( - "better_thermostat %s: No previously hvac mode found on startup, turn bt to trv mode %s", - self.device_name, - self.bt_hvac_mode, - ) - else: - _LOGGER.warning( - "better_thermostat %s: No previously hvac mode found on startup, turn heat off", - self.device_name, - ) - self.bt_hvac_mode = HVACMode.OFF + except (ValueError, TypeError): + pass _LOGGER.debug( - "better_thermostat %s: Startup config, BT hvac mode is %s, Target temp %s", + "better_thermostat %s: restoring target temperature...", + self.device_name, + ) + # Clamp the saved target, or fall back to the TRV mean. + _restored_target = restore_target_temperature( + old_state.attributes.get(ATTR_TEMPERATURE), + states, + self.bt_min_temp, + self.bt_max_temp, + self.device_name, + self.hass.config.units.temperature_unit, + ) + if _restored_target is not None: + self.bt_target_temp = _restored_target + _LOGGER.debug( + "better_thermostat %s: target temperature restored", self.device_name + ) + + _LOGGER.debug( + "better_thermostat %s: restoring preset mode...", self.device_name + ) + # Restore preset mode if present + _old_preset = old_state.attributes.get("preset_mode") + if ( + isinstance(_old_preset, str) + and _old_preset in self.preset_mgr.available_modes + ): + self.preset_mgr.mode = _old_preset + else: + self.preset_mgr.mode = PRESET_NONE + + _LOGGER.debug( + "better_thermostat %s: applying restored preset temperature...", + self.device_name, + ) + # If we restored a preset (not NONE) and we have a stored temperature for it, + # ensure target temp matches (unless the restored target was already equal). + if self.preset_mgr.mode is not None and self.preset_mgr.mode != PRESET_NONE: + preset_temp = self.preset_mgr.get_temperature(self.preset_mgr.mode) + # Only override if different to avoid masking manual restore logic + if ( + isinstance(preset_temp, (int, float)) + and preset_temp is not None + and self.bt_target_temp != preset_temp + ): + _LOGGER.debug( + "better_thermostat %s: Applying restored preset %s temperature %s after startup", + self.device_name, + self.preset_mgr.mode, + preset_temp, + ) + self.bt_target_temp = preset_temp + _LOGGER.debug( + "better_thermostat %s: restored preset temperature applied", + self.device_name, + ) + + _LOGGER.debug( + "better_thermostat %s: restoring other attributes...", self.device_name + ) + if old_state.state not in (STATE_UNAVAILABLE, STATE_UNKNOWN, None): + try: + self.bt_hvac_mode = HVACMode(old_state.state) + except ValueError: + _LOGGER.warning( + "better_thermostat %s: restored an unrecognised hvac mode %s; " + "leaving it for validation", + self.device_name, + old_state.state, + ) + if old_state.attributes.get(ATTR_STATE_CALL_FOR_HEAT, None) is not None: + self.call_for_heat = bool( + old_state.attributes.get(ATTR_STATE_CALL_FOR_HEAT) + ) + if old_state.attributes.get(ATTR_STATE_SAVED_TEMPERATURE, None) is not None: + self._saved_temperature = convert_to_float( + str(old_state.attributes.get(ATTR_STATE_SAVED_TEMPERATURE, None)), + self.device_name, + "startup()", + ) + if old_state.attributes.get(ATTR_STATE_HUMIDIY, None) is not None: + self._current_humidity = float(old_state.attributes[ATTR_STATE_HUMIDIY]) + if old_state.attributes.get(ATTR_STATE_MAIN_MODE, None) is not None: + self.last_main_hvac_mode = str( + old_state.attributes[ATTR_STATE_MAIN_MODE] + ) + if old_state.attributes.get(ATTR_STATE_HEATING_POWER, None) is not None: + self.heating_power = clamp_heating_power( + old_state.attributes.get(ATTR_STATE_HEATING_POWER), self.device_name + ) + + # Restore heat loss if available + if old_state.attributes.get(ATTR_STATE_HEAT_LOSS, None) is not None: + _restored_loss = clamp_heat_loss( + old_state.attributes.get(ATTR_STATE_HEAT_LOSS) + ) + if _restored_loss is not None: + self.heat_loss_rate = _restored_loss + if ( + old_state.attributes.get(ATTR_STATE_PRESET_TEMPERATURE, None) + is not None + ): + self.preset_mgr.saved_temperature = convert_to_float( + str(old_state.attributes.get(ATTR_STATE_PRESET_TEMPERATURE, None)), + self.device_name, + "startup()", + ) + # Restore preset mode + if old_state.attributes.get("preset_mode", None) is not None: + restored_preset: str = str(old_state.attributes["preset_mode"]) + if restored_preset in self.preset_modes: + self.preset_mgr.mode = restored_preset + _LOGGER.debug( + "better_thermostat %s: Restored preset mode: %s", + self.device_name, + restored_preset, + ) + _LOGGER.debug( + "better_thermostat %s: state restoration completed", self.device_name + ) + + # ECO mode state / saved ECO temperature not restored; Eco preset is supported via PRESET_ECO. + + else: + # No previous state, try and restore defaults + _LOGGER.debug( + "better_thermostat %s: no previous state, restoring defaults...", + self.device_name, + ) + if self.bt_target_temp is None or not isinstance( + self.bt_target_temp, float + ): + _LOGGER.info( + "better_thermostat %s: No previously saved temperature found on startup, get it from the TRV", + self.device_name, + ) + _restored_target = mean_trv_target( + states, + self.device_name, + system_unit=self.hass.config.units.temperature_unit, + ) + if _restored_target is not None: + self.bt_target_temp = _restored_target + _LOGGER.debug("better_thermostat %s: defaults restored", self.device_name) + + def _validate_hvac_mode(self, states: list[State]) -> None: + """Validate and fix HVAC mode after state restoration.""" + # if hvac mode could not be restored, turn heat off + _LOGGER.debug("better_thermostat %s: checking hvac mode...", self.device_name) + if self.bt_hvac_mode in (STATE_UNAVAILABLE, STATE_UNKNOWN, None): + # OFF is filtered out, so a non-empty list means at least one child + # is running -> adopt HEAT; otherwise (all OFF or none) stay OFF. + current_hvac_modes = [x.state for x in states if x.state != HVACMode.OFF] + if current_hvac_modes: + self.bt_hvac_mode = HVACMode.HEAT + else: + self.bt_hvac_mode = HVACMode.OFF + _LOGGER.debug( + "better_thermostat %s: No previously hvac mode found on startup, turn bt to trv mode %s", self.device_name, self.bt_hvac_mode, - self.bt_target_temp, ) - if self.last_main_hvac_mode is None: - self.last_main_hvac_mode = self.bt_hvac_mode + _LOGGER.debug( + "better_thermostat %s: Startup config, BT hvac mode is %s, Target temp %s", + self.device_name, + self.bt_hvac_mode, + self.bt_target_temp, + ) - if self.humidity_entity_id is not None: - self._current_humidity = convert_to_float( - str(self.hass.states.get(self.humidity_entity_id).state), + if self.last_main_hvac_mode is None: + self.last_main_hvac_mode = self.bt_hvac_mode + + _LOGGER.debug( + "better_thermostat %s: checking humidity sensor...", self.device_name + ) + if self.humidity_sensor_entity_id is not None: + _hum_state = self.hass.states.get(self.humidity_sensor_entity_id) + if _hum_state is None: + _LOGGER.warning( + "better_thermostat %s: Humidity sensor %s not found or not ready", self.device_name, - "startup()", + self.humidity_sensor_entity_id, ) - else: self._current_humidity = 0 - - self.last_window_state = self.window_open - if self.bt_hvac_mode not in ( - HVACMode.OFF, - HVACMode.HEAT_COOL, - HVACMode.HEAT, - ): - self.bt_hvac_mode = HVACMode.HEAT - - self.async_write_ha_state() - - for trv in self.real_trvs.keys(): - self.all_entities.append(trv) - await init(self, trv) - if self.real_trvs[trv]["calibration"] != 1: - self.real_trvs[trv]["last_calibration"] = await get_current_offset( - self, trv + else: + self._current_humidity = ( + convert_to_float( + str(_hum_state.state), self.device_name, "startup()" ) - self.real_trvs[trv]["local_calibration_min"] = await get_min_offset( - self, trv - ) - self.real_trvs[trv]["local_calibration_max"] = await get_max_offset( - self, trv - ) - self.real_trvs[trv]["local_calibration_step"] = ( - await get_offset_step(self, trv) - ) - else: - self.real_trvs[trv]["last_calibration"] = 0 - - self.real_trvs[trv]["valve_position"] = convert_to_float( - str( - self.hass.states.get(trv).attributes.get("valve_position", None) - ), - self.device_name, - "startup", + or 0.0 ) - self.real_trvs[trv]["max_temp"] = convert_to_float( - str(self.hass.states.get(trv).attributes.get("max_temp", 30)), - self.device_name, - "startup", + else: + self._current_humidity = 0.0 + + self.last_window_state = self.window_open + if self.bt_hvac_mode not in (HVACMode.OFF, HVACMode.HEAT_COOL, HVACMode.HEAT): + self.bt_hvac_mode = HVACMode.HEAT + + _LOGGER.debug( + "better_thermostat %s: writing initial state...", self.device_name + ) + self.async_write_ha_state() + + async def _initialize_trvs(self) -> None: + """Initialize each TRV: init, tweak, calibration offsets, attributes, control.""" + for trv, trv_data in self.real_trvs.items(): + self.all_entities.append(trv) + _LOGGER.debug( + "better_thermostat %s: initializing TRV %s", self.device_name, trv + ) + try: + await asyncio.wait_for(init(self, trv), timeout=30) + _LOGGER.debug( + "better_thermostat %s: TRV %s initialized", self.device_name, trv ) - self.real_trvs[trv]["min_temp"] = convert_to_float( - str(self.hass.states.get(trv).attributes.get("min_temp", 5)), + except TimeoutError: + _LOGGER.error( + "better_thermostat %s: Timeout initializing TRV %s", self.device_name, - "startup", + trv, ) - self.real_trvs[trv]["target_temp_step"] = convert_to_float( - str( - self.hass.states.get(trv).attributes.get( - "target_temp_step", 0.5 + except Exception as exc: + _LOGGER.error( + "better_thermostat %s: Error initializing TRV %s: %s", + self.device_name, + trv, + exc, + ) + + try: + await inital_tweak(self, trv) + except Exception as exc: + _LOGGER.error( + "better_thermostat %s: Error running initial tweak for TRV %s: %s", + self.device_name, + trv, + exc, + ) + + if trv_data["calibration"] != 1: + _LOGGER.debug( + "better_thermostat %s: getting offsets for TRV %s", + self.device_name, + trv, + ) + + try: + async with asyncio.timeout(10): + trv_data["last_calibration"] = await get_current_offset( + self, trv ) - ), + trv_data["local_calibration_min"] = await get_min_offset( + self, trv + ) + trv_data["local_calibration_max"] = await get_max_offset( + self, trv + ) + trv_data["local_calibration_step"] = await get_offset_step( + self, trv + ) + # Ensure None values are replaced with sensible defaults + self._set_trv_calibration_defaults(trv) + _LOGGER.debug( + "better_thermostat %s: offsets for TRV %s retrieved", + self.device_name, + trv, + ) + except TimeoutError: + _LOGGER.error( + "better_thermostat %s: Timeout getting offsets for TRV %s", + self.device_name, + trv, + ) + self._set_trv_calibration_defaults(trv) + except Exception as exc: + _LOGGER.error( + "better_thermostat %s: Error getting offsets for TRV %s: %s", + self.device_name, + trv, + exc, + ) + self._set_trv_calibration_defaults(trv) + else: + trv_data["last_calibration"] = 0 + trv_data["local_calibration_min"] = -7 + trv_data["local_calibration_max"] = 7 + trv_data["local_calibration_step"] = 0.5 + + _s = self.hass.states.get(trv) + _attrs = _s.attributes if _s else {} + _LOGGER.debug( + "better_thermostat %s: reading TRV %s attributes...", + self.device_name, + trv, + ) + trv_data["valve_position"] = convert_to_float( + str(_attrs.get("valve_position", None)), self.device_name, "startup" + ) + trv_data["max_temp"] = attr_to_celsius(self, _s, "max_temp", 30, "startup") + trv_data["min_temp"] = attr_to_celsius(self, _s, "min_temp", 5, "startup") + # Prefer configured step over device-reported step + cfg_step = ( + self.bt_target_temp_step + if self.bt_target_temp_step and self.bt_target_temp_step > 0.0 + else None + ) + if cfg_step is not None: + trv_data["target_temp_step"] = cfg_step + else: + trv_data["target_temp_step"] = convert_to_float( + str(_attrs.get("target_temp_step", 0.5)), self.device_name, "startup", ) - self.real_trvs[trv]["temperature"] = convert_to_float( - str(self.hass.states.get(trv).attributes.get("temperature", 5)), - self.device_name, - "startup", - ) - self.real_trvs[trv]["hvac_modes"] = self.hass.states.get( - trv - ).attributes.get("hvac_modes", None) - self.real_trvs[trv]["hvac_mode"] = self.hass.states.get(trv).state - self.real_trvs[trv]["last_hvac_mode"] = self.hass.states.get(trv).state - self.real_trvs[trv]["last_temperature"] = convert_to_float( - str(self.hass.states.get(trv).attributes.get("temperature")), + trv_data["temperature"] = attr_to_celsius( + self, _s, "temperature", 5, "startup" + ) + trv_data["hvac_modes"] = _attrs.get("hvac_modes", None) + trv_data["hvac_mode"] = _s.state if _s else None + trv_data["last_hvac_mode"] = _s.state if _s else None + trv_data["last_temperature"] = attr_to_celsius( + self, _s, "temperature", None, "startup()" + ) + # The 5.0 °C fallback for a missing reading must not pass the + # unit conversion (a literal "5" read as °F becomes about + # -15 °C), and a real reading of 0.0 is a reading. + _raw_current_temp = _attrs.get("current_temperature") + _current_temp = ( + convert_to_float_celsius( + str(_raw_current_temp), self.device_name, "startup()", - ) - self.real_trvs[trv]["current_temperature"] = convert_to_float( - str( - self.hass.states.get(trv).attributes.get("current_temperature") - or 5 + unit_of_measurement=state_temperature_unit( + _attrs, self.hass.config.units.temperature_unit ), + ) + if _raw_current_temp is not None + else 5.0 + ) + # Marker / garbage readings (for example AVM's 126.5 / 127 °C) + # must not seed the cache and feed the first control cycle. + if _current_temp is not None and not is_reasonable_temperature( + _current_temp + ): + _LOGGER.warning( + "better_thermostat %s: TRV %s reports implausible " + "current_temperature %s at startup; ignoring", self.device_name, - "startup()", + trv, + _current_temp, ) - await control_trv(self, trv) - - await self._trigger_time(None) - await self._trigger_check_weather(None) - self.startup_running = False - self._available = True - self.async_write_ha_state() - # - await asyncio.sleep(5) - - # try to find battery entities for all related entities - for entity in self.all_entities: - if entity is not None: - battery_id = await find_battery_entity(self, entity) - if battery_id is not None: - self.devices_states[entity] = { - "battery_id": battery_id, - "battery": None, - } - - if self.is_removed: - return - - # update_hvac_action(self) - # Add listener - if self.outdoor_sensor is not None: - self.all_entities.append(self.outdoor_sensor) - self.async_on_remove( - async_track_time_change(self.hass, self._trigger_time, 5, 0, 0) + _current_temp = None + trv_data["current_temperature"] = _current_temp + _LOGGER.debug( + "better_thermostat %s: controlling TRV %s...", self.device_name, trv + ) + try: + await asyncio.wait_for(control_trv(self, trv), timeout=10) + _LOGGER.debug( + "better_thermostat %s: TRV %s controlled", self.device_name, trv + ) + except TimeoutError: + _LOGGER.error( + "better_thermostat %s: Timeout controlling TRV %s", + self.device_name, + trv, + ) + except Exception as exc: + _LOGGER.error( + "better_thermostat %s: Error controlling TRV %s: %s", + self.device_name, + trv, + exc, ) - await check_all_entities(self) + async def _finalize_startup(self) -> None: + """Run post-init tasks: triggers, listeners, periodic jobs.""" + _LOGGER.debug("better_thermostat %s: triggering time...", self.device_name) + await self._trigger_time(None) + _LOGGER.debug( + "better_thermostat %s: triggering check weather...", self.device_name + ) + await self._trigger_check_weather(None) + _LOGGER.debug("better_thermostat %s: startup finishing...", self.device_name) + self.startup_running = False + self._available = True + self.async_write_ha_state() + _LOGGER.debug("better_thermostat %s: sleeping 15s...", self.device_name) + await asyncio.sleep(15) + _LOGGER.debug( + "better_thermostat %s: finding battery entities...", self.device_name + ) + + # try to find battery entities for all related entities + for entity in self.all_entities: + if entity is not None: + battery_id = await find_battery_entity(self, entity) + if battery_id is not None: + self.devices_states[entity] = { + "battery_id": battery_id, + "battery": None, + } + + if self.is_removed: + return + + # Add listener + if self.outdoor_sensor is not None: + self.all_entities.append(self.outdoor_sensor) + self.async_on_remove( + async_track_time_change(self.hass, self._trigger_time, 5, 0, 0) + ) + + _LOGGER.debug( + "better_thermostat %s: checking critical entities...", self.device_name + ) + await check_critical_entities(self) + + # Wait for optional sensors with increasing retry delays before + # entering degraded mode (see await_optional_sensors for details). + # During the startup grace window, a transition into degraded mode is + # logged at DEBUG and the HA repair issue is deferred — slow cloud + # integrations get time to come online before the user sees a warning. + self._degraded_grace_until = dt_util.now() + STARTUP_DEGRADED_GRACE_PERIOD + await await_optional_sensors(self) + await check_and_update_degraded_mode(self) + + async def _post_grace_degraded_recheck() -> None: + remaining = ( + (self._degraded_grace_until - dt_util.now()).total_seconds() + if self._degraded_grace_until + else 0.0 + ) + if remaining > 0: + await asyncio.sleep(remaining) if self.is_removed: return + await check_and_update_degraded_mode(self) + self.hass.async_create_background_task( + _post_grace_degraded_recheck(), + name=f"bt_post_grace_degraded_{self.device_name}", + ) + + if self.is_removed: + return + + _LOGGER.debug( + "better_thermostat %s: registering periodic tasks...", self.device_name + ) + self.async_on_remove( + async_track_time_interval( + self.hass, self._trigger_check_weather, timedelta(hours=1) + ) + ) + + # Periodischer 5-Minuten-Tick: nur aktivieren, wenn Balance konfiguriert ist + balance_modes = {"heuristic", "pid"} + active_balance_modes = set() + active_calibration_modes = set() + try: + for trv_info in self.real_trvs.values(): + advanced = trv_info.get("advanced", {}) or {} + + raw_balance = advanced.get("balance_mode", "") + balance_value = getattr(raw_balance, "value", raw_balance) + if isinstance(balance_value, str): + balance_mode = balance_value.lower() + if balance_mode in balance_modes: + active_balance_modes.add(balance_mode) + + raw_calibration = advanced.get("calibration_mode", "") + calibration_value = getattr(raw_calibration, "value", raw_calibration) + if isinstance(calibration_value, str): + calibration_mode = calibration_value.lower() + if calibration_mode in ( + CalibrationMode.DEFAULT.value, + CalibrationMode.MPC_CALIBRATION.value, + CalibrationMode.TPI_CALIBRATION.value, + CalibrationMode.PID_CALIBRATION.value, + ): + active_calibration_modes.add(calibration_mode) + except Exception: + active_balance_modes = set() + active_calibration_modes = set() + + if active_balance_modes or active_calibration_modes: self.async_on_remove( async_track_time_interval( - self.hass, self._trigger_check_weather, timedelta(hours=1) + self.hass, self._trigger_time, timedelta(minutes=5) ) ) + _LOGGER.debug( + "better_thermostat %s: 5min periodic tick enabled (balance_modes=%s calibration_modes=%s)", + self.device_name, + sorted(active_balance_modes), + sorted(active_calibration_modes), + ) + else: + _LOGGER.debug( + "better_thermostat %s: 5min periodic tick skipped (no supported balance/calibration mode)", + self.device_name, + ) + # Ventilwartung: separaten Tick nur aktivieren, wenn mindestens ein TRV sie eingeschaltet hat + try: + maint_trvs = collect_maintenance_trvs(self.real_trvs) + except Exception: + maint_trvs = [] + + if maint_trvs: + self.next_valve_maintenance = compute_initial_maintenance( + self.real_trvs, maint_trvs + ) + self.async_on_remove( + async_track_time_interval( + self.hass, self._maintenance_tick, timedelta(minutes=5) + ) + ) + _LOGGER.debug( + "better_thermostat %s: valve maintenance tick enabled (5min), first run at %s", + self.device_name, + self.next_valve_maintenance, + ) + else: + _LOGGER.debug( + "better_thermostat %s: valve maintenance tick skipped (no TRV enabled)", + self.device_name, + ) + + self.async_on_remove( + async_track_state_change_event( + self.hass, [self.sensor_entity_id], self._trigger_temperature_change + ) + ) + if self.humidity_sensor_entity_id is not None: self.async_on_remove( async_track_state_change_event( - self.hass, [self.sensor_entity_id], self._trigger_temperature_change + self.hass, + [self.humidity_sensor_entity_id], + self._trigger_humidity_change, ) ) - if self.humidity_entity_id is not None: - self.async_on_remove( - async_track_state_change_event( - self.hass, - [self.humidity_entity_id], - self._trigger_humidity_change, + if self._async_unsub_state_changed is None: + self._async_unsub_state_changed = async_track_state_change_event( + self.hass, self.entity_ids, self._trigger_trv_change + ) + self.async_on_remove(self._async_unsub_state_changed) + if self.window_id is not None: + self.async_on_remove( + async_track_state_change_event( + self.hass, [self.window_id], self._trigger_window_change + ) + ) + if self.cooler_entity_id is not None: + self.async_on_remove( + async_track_state_change_event( + self.hass, [self.cooler_entity_id], self._trigger_cooler_change + ) + ) + if self.outdoor_sensor is not None: + self.async_on_remove( + async_track_state_change_event( + self.hass, [self.outdoor_sensor], self._trigger_outdoor_change + ) + ) + # Sende initial sofort einen Keepalive, damit TRVs nicht bis zum ersten 30min-Tick warten müssen + try: + _LOGGER.debug( + "better_thermostat %s: creating keepalive task...", self.device_name + ) + self.hass.async_create_background_task( + self._external_temperature_keepalive(), + name=f"bt_ext_temp_keepalive_{self.device_name}", + ) + except Exception as exc: + _LOGGER.error( + "better_thermostat %s: Failed to create external temperature keepalive task: %s", + self.device_name, + exc, + ) + # Start periodic EMA update (every minute) + _LOGGER.debug("better_thermostat %s: starting EMA timer...", self.device_name) + self.async_on_remove( + async_track_time_interval( + self.hass, self._async_update_ema_periodic, timedelta(minutes=1) + ) + ) + _LOGGER.info("better_thermostat %s: startup completed.", self.device_name) + self.async_write_ha_state() + await self.async_update_ha_state(force_refresh=True) + + async def _maintenance_tick(self, event=None): + """Periodic maintenance tick: runs valve exercise when due and enabled.""" + # quick availability check - only critical entities needed for maintenance + try: + ok = await check_critical_entities(self) + if ok is False: + return + await check_and_update_degraded_mode(self) + except Exception: + _LOGGER.debug( + "better_thermostat %s: maintenance availability check failed; " + "skipping this tick", + self.device_name, + ) + return + + # Skip if already running or not due + now = dt_util.now() + if self.in_maintenance: + return + try: + if self.next_valve_maintenance and now < self.next_valve_maintenance: + return + except TypeError: + # next_valve_maintenance is not comparable to now; fall through and + # let this tick re-evaluate the schedule. + pass + + # Skip when device is OFF or window open + if self.window_open: + # postpone by an hour to avoid hammering + self.next_valve_maintenance = now + timedelta(hours=1) + _LOGGER.debug( + "better_thermostat %s: valve maintenance postponed (window open)", + self.device_name, + ) + return + if HVACMode.OFF in (self.hvac_mode, self.bt_hvac_mode): + self.next_valve_maintenance = now + timedelta(hours=1) + _LOGGER.debug( + "better_thermostat %s: valve maintenance postponed (HVAC OFF)", + self.device_name, + ) + return + + # Check if any TRV actually has maintenance enabled + try: + trvs_to_service = collect_maintenance_trvs(self.real_trvs) + except Exception: + _LOGGER.debug( + "better_thermostat %s: could not collect maintenance TRVs", + self.device_name, + ) + trvs_to_service = [] + + if not trvs_to_service: + # no enabled TRVs => schedule far in the future to avoid frequent wakeups + self.next_valve_maintenance = now + timedelta(days=7) + return + + # Run maintenance asynchronously (don't block the tick) + self.hass.async_create_background_task( + self._run_valve_maintenance(trvs_to_service), + name=f"bt_valve_maintenance_{self.device_name}", + ) + + async def _run_valve_maintenance(self, trvs: list[str]) -> None: + """Perform valve exercise: open fully, then close, restore state, and reschedule. + + Manages state flags (ignore_states, in_maintenance, + ignore_trv_states, control_queue) around the pure maintenance + logic in ``utils.valve_maintenance``. + """ + if self.in_maintenance: + return + self.in_maintenance = True + # Suppress control loop briefly to prevent interference during maintenance + self.ignore_states = True + + try: + # Set per-TRV guard + for trv_id in trvs: + try: + self.real_trvs[trv_id]["ignore_trv_states"] = True + except (KeyError, TypeError): + pass + + # Build snapshots (skips TRVs with state=None) + infos = build_trv_snapshots( + self.real_trvs, trvs, self.hass.states.get, self.device_name + ) + serviced_ids = {info.entity_id for info in infos} + + # Release guard for TRVs that were skipped (state=None) + for trv_id in trvs: + if trv_id not in serviced_ids: + try: + self.real_trvs[trv_id]["ignore_trv_states"] = False + except (KeyError, TypeError): + pass + + # Bind adapter callbacks to self + from .adapters.delegate import set_valve as _delegate_set_valve + + async def _set_valve(entity_id: str, pct: int) -> bool: + try: + ok = await _delegate_set_valve(self, entity_id, int(pct)) + return bool(ok) + except Exception: + _LOGGER.debug( + "better_thermostat %s: maintenance valve set failed for %s", + self.device_name, + entity_id, ) + return False + + async def _set_temp(entity_id: str, temp: float) -> None: + await adapter_set_temperature(self, entity_id, temp) + + async def _set_mode(entity_id: str, mode: str) -> None: + await adapter_set_hvac_mode(self, entity_id, mode) + + # Run 2× open/close + restore + await run_valve_maintenance( + infos, + set_valve_fn=_set_valve, + set_temperature_fn=_set_temp, + set_hvac_mode_fn=_set_mode, + device_name=self.device_name, + ) + + # Release per-TRV guard for serviced TRVs + for trv_id in serviced_ids: + try: + self.real_trvs[trv_id]["ignore_trv_states"] = False + except (KeyError, TypeError): + pass + + # Schedule next run + self.next_valve_maintenance = compute_next_maintenance(self.real_trvs, trvs) + _LOGGER.info( + "better_thermostat %s: next valve maintenance at %s", + self.device_name, + self.next_valve_maintenance, + ) + finally: + self._control_needed_after_maintenance = False + # Always release ignore_states after maintenance. + # If we restore a previous True here, the control_queue loop can get + # stuck sleeping forever and never consume queued control actions. + self.ignore_states = False + self.in_maintenance = False + + # Trigger one control cycle after maintenance so BT immediately + # resumes with the latest window/temp/target states. + if self.bt_hvac_mode != HVACMode.OFF: + try: + self.control_queue_task.put_nowait(self) + except Exception: + # Queue full or not ready; periodic tick will eventually catch up. + pass + + # -- Unified state persistence helpers ------------------------------------ + + def _hydrate_controllers_from_state(self) -> None: + """Seed the module-level controller caches from persisted state.""" + if self.state_mgr is None: + return + self.state_mgr.hydrate_controllers(f"{self._unique_id}:") + + def _hydrate_thermal_from_state(self) -> None: + """Apply persisted, clamped thermal stats to entity attributes.""" + if self.state_mgr is None: + return + heating_power, heat_loss_rate = self.state_mgr.clamped_thermal() + if heating_power is not None: + self.heating_power = heating_power + if heat_loss_rate is not None: + self.heat_loss_rate = heat_loss_rate + + def _sync_controllers_to_state(self) -> None: + """Push current controller caches and thermal stats into the StateManager.""" + if self.state_mgr is None: + return + self.state_mgr.sync_controllers( + f"{self._unique_id}:", + getattr(self, "heating_power", None), + getattr(self, "heat_loss_rate", None), + ) + + @callback + def schedule_save_state(self, delay_s: float = 15.0) -> None: + """Schedule a debounced persist of unified state. + + Uses ``async_call_later`` so the timer is cancelable and + HA-idiomatic. Repeated calls within *delay_s* reset the timer + so that the save always happens *delay_s* after the **last** + trigger (true debounce). + """ + state_mgr = self.state_mgr + if state_mgr is None: + return + + # Cancel any previously scheduled save (resets the timer). + if self._save_cancel is not None: + self._save_cancel() + self._save_cancel = None + + async def _do_save(_now: object) -> None: + self._save_cancel = None + try: + self._sync_controllers_to_state() + await state_mgr.save_if_dirty() + except Exception: + _LOGGER.exception( + "better_thermostat %s: failed to persist state", self.device_name ) - if self._async_unsub_state_changed is None: - self._async_unsub_state_changed = async_track_state_change_event( - self.hass, self.entity_ids, self._trigger_trv_change - ) - self.async_on_remove(self._async_unsub_state_changed) - if self.window_id is not None: - self.async_on_remove( - async_track_state_change_event( - self.hass, [self.window_id], self._trigger_window_change - ) - ) - if self.cooler_entity_id is not None: - self.async_on_remove( - async_track_state_change_event( - self.hass, [self.cooler_entity_id], self._tigger_cooler_change - ) - ) - _LOGGER.info("better_thermostat %s: startup completed.", self.device_name) - self.async_write_ha_state() - await self.async_update_ha_state(force_refresh=True) - break + + self._save_cancel = async_call_later(self.hass, delay_s, _do_save) async def calculate_heating_power(self): + """Learn effective heating power (°C/min) from completed heating cycles. + + Delegates to :class:`HeatingPowerTracker` and handles HA side-effects. + """ + if self.cur_temp is None: + return + + # Lazy init of target range bounds if not hasattr(self, "min_target_temp"): self.min_target_temp = self.bt_target_temp or 18.0 if not hasattr(self, "max_target_temp"): self.max_target_temp = self.bt_target_temp or 21.0 - if ( - self.heating_start_temp is not None - and self.heating_end_temp is not None - and self.cur_temp < self.heating_end_temp - ): - _temp_diff = self.heating_end_temp - self.heating_start_temp - _time_diff_minutes = round( - (self.heating_end_timestamp - self.heating_start_timestamp).seconds - / 60.0, - 1, - ) - if _time_diff_minutes > 1: - temp_range = max(self.max_target_temp - self.min_target_temp, 0.1) - weight_factor = max( - 0.5, - min( - 1.5, - 0.5 + (self.bt_target_temp - self.min_target_temp) / temp_range, + current_action = self._compute_hvac_action() + outdoor_temp = self._get_outdoor_temp() + + result = self._heating_tracker.update( + self.cur_temp, + current_action, + dt_util.utcnow(), + target_temp=self.bt_target_temp, + outdoor_temp=outdoor_temp, + ) + + if result.action_changed: + self.old_attr_hvac_action = result.current_action + self.attr_hvac_action = result.current_action + + if result.cycle_result is not None or result.action_changed: + if result.cycle_result and result.cycle_result.power_changed: + self.schedule_save_state() + self.async_write_ha_state() + + async def calculate_heat_loss(self): + """Learn effective heat loss (°C/min) during idle cooling periods. + + Delegates to :class:`HeatLossTracker` and handles HA side-effects. + """ + if self.cur_temp is None: + return + + current_action = self._compute_hvac_action() + + result = self._loss_tracker.update( + self.cur_temp, + current_action, + dt_util.utcnow(), + window_open=bool(self.window_open), + ) + + if result.cycle_result is not None: + self.async_write_ha_state() + if result.cycle_result.loss_changed: + self.schedule_save_state() + + def _get_outdoor_temp(self) -> float | None: + """Resolve outdoor temperature from sensor entity, if configured.""" + if self.outdoor_sensor is None: + return None + try: + outdoor_state = self.hass.states.get(self.outdoor_sensor) + if outdoor_state is not None: + return convert_to_float_celsius( + str(outdoor_state.state), + self.device_name, + "calculate_heating_power.outdoor", + unit_of_measurement=outdoor_state.attributes.get( + "unit_of_measurement" ), ) - - _degrees_time = round(_temp_diff / _time_diff_minutes, 4) - self.heating_power = round( - ( - self.heating_power * (1 - 0.1 * weight_factor) - + _degrees_time * 0.1 * weight_factor - ), - 4, - ) - - if len(self.last_heating_power_stats) >= 10: - self.last_heating_power_stats = self.last_heating_power_stats[ - len(self.last_heating_power_stats) - 9 : - ] - self.last_heating_power_stats.append( - { - "temp_diff": round(_temp_diff, 1), - "time": _time_diff_minutes, - "degrees_time": round(_degrees_time, 4), - "weight_factor": weight_factor, - "heating_power": round(self.heating_power, 4), - } - ) - - _LOGGER.debug( - f"better_thermostat {self.device_name}: calculate_heating_power / " - f"temp_diff: {round(_temp_diff, 1)} - time: {_time_diff_minutes} - " - f"degrees_time: {round(_degrees_time, 4)} - weight_factor: {weight_factor} - " - f"heating_power: {round(self.heating_power, 4)} - " - f"heating_power_stats: {self.last_heating_power_stats}" - ) - - # reset for the next heating start - self.heating_start_temp = None - self.heating_end_temp = None - - if self.heating_end_temp: - self.min_target_temp = min(self.min_target_temp, self.bt_target_temp) - self.max_target_temp = max(self.max_target_temp, self.bt_target_temp) - - # heating starts - if ( - self.attr_hvac_action == HVACAction.HEATING - and self.old_attr_hvac_action != HVACAction.HEATING - ): - self.heating_start_temp = self.cur_temp - self.heating_start_timestamp = datetime.now() - # heating stops - elif ( - self.attr_hvac_action != HVACAction.HEATING - and self.old_attr_hvac_action == HVACAction.HEATING - and self.heating_start_temp is not None - and self.heating_end_temp is None - ): - self.heating_end_temp = self.cur_temp - self.heating_end_timestamp = datetime.now() - # check if the temp is still rising, after heating stopped - elif ( - self.attr_hvac_action != HVACAction.HEATING - and self.heating_start_temp is not None - and self.heating_end_temp is not None - and self.cur_temp > self.heating_end_temp - ): - self.heating_end_temp = self.cur_temp - - if self.attr_hvac_action != self.old_attr_hvac_action: - self.old_attr_hvac_action = self.attr_hvac_action - self.async_write_ha_state() + except Exception: + pass + return None @property - def extra_state_attributes(self) -> dict[str, any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the device specific state attributes. Returns @@ -1075,15 +2208,50 @@ class BetterThermostat(ClimateEntity, RestoreEntity, ABC): ATTR_STATE_CALL_FOR_HEAT: self.call_for_heat, ATTR_STATE_LAST_CHANGE: self.last_change.isoformat(), ATTR_STATE_SAVED_TEMPERATURE: self._saved_temperature, - ATTR_STATE_HUMIDIY: self._current_humidity, + ATTR_STATE_PRESET_TEMPERATURE: self.preset_mgr.saved_temperature, ATTR_STATE_MAIN_MODE: self.last_main_hvac_mode, + ATTR_STATE_OFF_TEMPERATURE: self.off_temperature, CONF_TOLERANCE: self.tolerance, CONF_TARGET_TEMP_STEP: self.bt_target_temp_step, ATTR_STATE_HEATING_POWER: self.heating_power, + ATTR_STATE_HEAT_LOSS: getattr(self, "heat_loss_rate", None), ATTR_STATE_ERRORS: json.dumps(self.devices_errors), ATTR_STATE_BATTERIES: json.dumps(self.devices_states), + "external_temp_ema": self.cur_temp_filtered, + # Degraded mode: thermostat running with some sensors unavailable + "degraded_mode": self.degraded_mode, + "unavailable_sensors": self.unavailable_sensors, + # ECO mode attribute removed: eco preset supported via PRESET_ECO } + # Optional: next scheduled valve maintenance (ISO8601) + try: + if ( + hasattr(self, "next_valve_maintenance") + and self.next_valve_maintenance is not None + ): + dev_specific["next_valve_maintenance"] = ( + self.next_valve_maintenance.isoformat() + ) + except Exception: + pass + + # Optional: summarize last valve method per TRV (adapter vs override) + try: + methods = {} + for trv_id, info in (self.real_trvs or {}).items(): + m = info.get("last_valve_method") + if m: + methods[trv_id] = m + if methods: + dev_specific["valve_method"] = methods + except Exception: + pass + + dev_specific.update(collect_cycle_telemetry(self)) + dev_specific.update(collect_balance_attrs(self)) + dev_specific.update(collect_pid_debug_attrs(self)) + return dev_specific @property @@ -1147,7 +2315,7 @@ class BetterThermostat(ClimateEntity, RestoreEntity, ABC): @property def temperature_unit(self) -> str: """Return the unit of measurement.""" - return self._unit + return UnitOfTemperature.CELSIUS @property def current_temperature(self) -> float | None: @@ -1162,7 +2330,30 @@ class BetterThermostat(ClimateEntity, RestoreEntity, ABC): @property def hvac_mode(self) -> HVACMode | None: """Return current operation.""" - return get_hvac_bt_mode(self, self.bt_hvac_mode) + # Fallback if None + if self.bt_hvac_mode is None: + return HVACMode.OFF + mapped = get_hvac_bt_mode(self, self.bt_hvac_mode) + if isinstance(mapped, HVACMode): + result = mapped + else: + try: + result = HVACMode(mapped) + except Exception: + try: + result = HVACMode[mapped.upper()] + except Exception: + return HVACMode.OFF + + # Ensure result is in available modes list + if result not in self._hvac_list: + # HEAT should map to map_on_hvac_mode (HEAT_COOL when cooler exists) + if result == HVACMode.HEAT and self.map_on_hvac_mode in self._hvac_list: + return self.map_on_hvac_mode + # Fallback to OFF if mode still invalid + return HVACMode.OFF + + return result @property def hvac_modes(self) -> list[HVACMode]: @@ -1171,25 +2362,93 @@ class BetterThermostat(ClimateEntity, RestoreEntity, ABC): @property def hvac_action(self): - """Return the current HVAC action""" - if self.bt_target_temp is not None and self.cur_temp is not None: - if self.hvac_mode == HVACMode.OFF: - self.attr_hvac_action = HVACAction.OFF - elif ( - self.bt_target_temp > self.cur_temp + self.tolerance - and self.window_open is False - ): - self.attr_hvac_action = HVACAction.HEATING - elif ( - self.bt_target_temp > self.cur_temp - and self.window_open is False - and self.bt_hvac_mode is not HVACMode.OFF - ): - self.attr_hvac_action = HVACAction.HEATING - else: - self.attr_hvac_action = HVACAction.IDLE + """Return the current HVAC action. + + Every control cycle commits a fresh action; the one computation + here bridges the gap until the first commit and is cached so + repeated state reads do not rebuild it. + """ + if self.attr_hvac_action is None: + self.attr_hvac_action = self._compute_hvac_action_pure().action return self.attr_hvac_action + def _should_heat_with_tolerance( + self, previous_action: HVACAction | None, tol: float + ) -> bool: + """Apply hysteresis so heating restarts only below target - tolerance.""" + if self.bt_target_temp is None or self.cur_temp is None: + return False + return should_heat_with_tolerance( + self.cur_temp, self.bt_target_temp, tol, previous_action + ) + + def _build_trv_snapshots(self) -> list[TrvSnapshot]: + """Build TrvSnapshot list from real_trvs with hass state fallback.""" + snapshots: list[TrvSnapshot] = [] + for trv_id, info in (self.real_trvs or {}).items(): + if not isinstance(info, dict): + continue + + # Resolve hvac_action: cached first, hass state fallback + action_val = info.get("hvac_action") + action_str = str(action_val).lower() if action_val is not None else "" + if not action_str: + try: + trv_state = self.hass.states.get(trv_id) + action_raw = None + if trv_state is not None: + action_raw = trv_state.attributes.get("hvac_action") + if action_raw is None: + action_raw = trv_state.attributes.get("action") + action_str = ( + str(action_raw).lower() if action_raw is not None else "" + ) + if action_str: + try: + info["hvac_action"] = action_str + except Exception: + pass + except Exception: + action_str = "" + + snapshots.append( + TrvSnapshot( + trv_id=trv_id, + ignore_trv_states=bool(info.get("ignore_trv_states")), + hvac_action=action_str or None, + valve_position=info.get("valve_position"), + last_valve_percent=info.get("last_valve_percent"), + ) + ) + return snapshots + + def _compute_hvac_action(self): + """Return the current HVAC action enum value.""" + result = self._compute_hvac_action_pure() + self._commit_hvac_action(result) + return result.action + + def _compute_hvac_action_pure(self): + """Compute current HVAC action.""" + return compute_hvac_action( + hysteresis=self._hysteresis, + cur_temp=self.cur_temp, + target_temp=self.bt_target_temp, + cool_target=self.bt_target_cooltemp, + hvac_mode=self.hvac_mode, + bt_hvac_mode=self.bt_hvac_mode, + window_open=self.window_open, + tolerance=self.tolerance or 0.0, + ignore_states=self.ignore_states, + trv_snapshots=self._build_trv_snapshots(), + device_name=self.device_name, + ) + + def _commit_hvac_action(self, result) -> None: + """Apply computed hysteresis state.""" + self._hysteresis.last_action = result.new_last_action + self._hysteresis.hold_active = result.new_hold_active + @property def target_temperature(self) -> float | None: """Return the temperature we try to reach. @@ -1199,7 +2458,9 @@ class BetterThermostat(ClimateEntity, RestoreEntity, ABC): float Target temperature. """ - if None in (self.bt_max_temp, self.bt_min_temp, self.bt_target_temp): + if self.bt_target_temp is None: + return None + if self.bt_min_temp is None or self.bt_max_temp is None: return self.bt_target_temp # if target temp is below minimum, return minimum if self.bt_target_temp < self.bt_min_temp: @@ -1211,53 +2472,98 @@ class BetterThermostat(ClimateEntity, RestoreEntity, ABC): @property def target_temperature_low(self) -> float | None: + """Return the low target temperature.""" if self.cooler_entity_id is None: return None return self.bt_target_temp @property def target_temperature_high(self) -> float | None: + """Return the high target temperature.""" if self.cooler_entity_id is None: return None return self.bt_target_cooltemp - async def async_set_hvac_mode(self, hvac_mode: str) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode | str) -> None: """Set hvac mode. Returns ------- None """ - if hvac_mode in (HVACMode.HEAT, HVACMode.HEAT_COOL, HVACMode.OFF): - self.bt_hvac_mode = get_hvac_bt_mode(self, hvac_mode) + + hvac_mode_norm = normalize_hvac_mode(hvac_mode) + if hvac_mode_norm in (HVACMode.HEAT, HVACMode.HEAT_COOL, HVACMode.OFF): + self.bt_hvac_mode = HVACMode(get_hvac_bt_mode(self, hvac_mode_norm)) else: _LOGGER.error( "better_thermostat %s: Unsupported hvac_mode %s", self.device_name, - hvac_mode, + hvac_mode_norm, ) self.async_write_ha_state() + # During valve maintenance we must not block on the control queue (maxsize=1) + # and must not override maintenance valve exercise. + if getattr(self, "in_maintenance", False): + self._control_needed_after_maintenance = True + return + await self.control_queue_task.put(self) + def _enforce_cool_above_heat(self) -> None: + """Keep the cooling target strictly above the heating target. + + In HEAT_COOL mode the two setpoints must not cross. If the cool target is + at or below the heat target, bump it up by one temperature step. + """ + if ( + self.hvac_mode != HVACMode.HEAT_COOL + or self.bt_target_cooltemp is None + or self.bt_target_temp is None + or self.bt_target_cooltemp > self.bt_target_temp + ): + return + step = self.bt_target_temp_step or 0.5 + adjusted = self.bt_target_temp + step + _LOGGER.warning( + "better_thermostat %s: cooling target %.2f adjusted to %.2f to stay above heating target %.2f", + self.device_name, + self.bt_target_cooltemp, + adjusted, + self.bt_target_temp, + ) + self.bt_target_cooltemp = adjusted + async def async_set_temperature(self, **kwargs) -> None: """Set new target temperature.""" - if self.bt_hvac_mode == HVACMode.OFF: - return + _LOGGER.debug( + "better_thermostat %s: async_set_temperature kwargs=%s, current preset=%s, hvac_mode=%s", + self.device_name, + kwargs, + self.preset_mgr.mode, + self.bt_hvac_mode, + ) _new_setpoint = None _new_setpointlow = None _new_setpointhigh = None if ATTR_HVAC_MODE in kwargs: - hvac_mode = str(kwargs.get(ATTR_HVAC_MODE, None)) - if hvac_mode in (HVACMode.HEAT, HVACMode.HEAT_COOL, HVACMode.OFF): - self.bt_hvac_mode = hvac_mode + hvac_mode_val = kwargs.get(ATTR_HVAC_MODE, None) + hvac_mode_norm = ( + normalize_hvac_mode(hvac_mode_val) + if hvac_mode_val is not None + else None + ) + if hvac_mode_norm in (HVACMode.HEAT, HVACMode.HEAT_COOL, HVACMode.OFF): + self.bt_hvac_mode = hvac_mode_norm else: _LOGGER.error( "better_thermostat %s: Unsupported hvac_mode %s", self.device_name, - hvac_mode, + hvac_mode_norm, ) + if ATTR_TEMPERATURE in kwargs: _new_setpoint = convert_to_float( str(kwargs.get(ATTR_TEMPERATURE, None)), @@ -1279,9 +2585,14 @@ class BetterThermostat(ClimateEntity, RestoreEntity, ABC): "controlling.settarget_temperature_high()", ) - if _new_setpoint is None and _new_setpointlow is None: + if ( + _new_setpoint is None + and _new_setpointlow is None + and _new_setpointhigh is None + ): _LOGGER.debug( - f"better_thermostat {self.device_name}: received a new setpoint from HA, but temperature attribute was not set, ignoring" + "better_thermostat %s: received a new setpoint from HA, but temperature attribute was not set, ignoring", + self.device_name, ) return @@ -1295,10 +2606,60 @@ class BetterThermostat(ClimateEntity, RestoreEntity, ABC): self.max_temp, max(self.min_temp, _new_setpointhigh) ) - self.bt_target_temp = _new_setpoint or _new_setpointlow + # Preserve explicit 0.0 values (avoid Python truthiness bug) + if _new_setpoint is not None: + self.bt_target_temp = _new_setpoint + elif _new_setpointlow is not None: + self.bt_target_temp = _new_setpointlow + if _new_setpointhigh is not None: self.bt_target_cooltemp = _new_setpointhigh + # Enforce ordering: cool target should be above heat target in HEAT_COOL. + self._enforce_cool_above_heat() + + # If a specific preset (Comfort, Eco, …) is active and the user manually + # changes the target temperature to a value that does not match the + # preset's stored temperature, deactivate the preset (return to + # PRESET_NONE) while keeping the new manual temperature. The preset's + # own Number entity also funnels through this method, but it first + # updates the preset's stored temperature so the values match and the + # preset stays active. + if ( + (_new_setpoint is not None or _new_setpointlow is not None) + and self.bt_target_temp is not None + and self.preset_mgr.mode != PRESET_NONE + ): + applied = float(self.bt_target_temp) + preset_stored = self.preset_mgr.get_temperature(self.preset_mgr.mode) + if preset_stored is None or abs(applied - float(preset_stored)) > 1e-3: + old_preset = self.preset_mgr.mode + self.preset_mgr.deactivate() + _LOGGER.debug( + "better_thermostat %s: Deactivated preset %s due to manual target temperature change to %s", + self.device_name, + old_preset, + applied, + ) + + # If the user manually changes the temperature while in PRESET_NONE (Manual), + # record it as the stored manual temperature. Specific presets (Comfort, Eco, + # etc.) are managed via separate Number entities and must NOT be overwritten + # by manual setpoint changes. + if ( + _new_setpoint is not None or _new_setpointlow is not None + ) and self.bt_target_temp is not None: + applied = float(self.bt_target_temp) + old_value = self.preset_mgr.record_manual_change(applied) + if old_value is not None: + _LOGGER.debug( + "better_thermostat %s: Updated stored preset temperature for %s from %s to %s due to manual change", + self.device_name, + self.preset_mgr.mode, + old_value, + applied, + ) + _LOGGER.debug( "better_thermostat %s: HA set target temperature to %s & %s", self.device_name, @@ -1307,14 +2668,57 @@ class BetterThermostat(ClimateEntity, RestoreEntity, ABC): ) self.async_write_ha_state() - await self.control_queue_task.put(self) + # Only trigger control queue if thermostat is not OFF + # When OFF, we still save the temperature but don't send it to the physical device + if self.bt_hvac_mode != HVACMode.OFF: + # During valve maintenance we must not block on the control queue + # (Queue maxsize=1) and must not override maintenance. + if getattr(self, "in_maintenance", False): + self._control_needed_after_maintenance = True + return + await self.control_queue_task.put(self) async def async_turn_off(self) -> None: + """Turn the entity off.""" await self.async_set_hvac_mode(HVACMode.OFF) async def async_turn_on(self) -> None: + """Turn the entity on.""" await self.async_set_hvac_mode(HVACMode.HEAT) + def _signal_config_change(self) -> None: + """Signal a configuration change to trigger entity cleanup/recreation.""" + signal_key = f"bt_config_changed_{self._config_entry_id}" + dispatcher_send(self.hass, signal_key, {"entry_id": self._config_entry_id}) + _LOGGER.debug( + "better_thermostat %s: Signaled configuration change", self.device_name + ) + + async def run_valve_maintenance_service(self) -> None: + """Entity service: run valve maintenance immediately (ignores schedule).""" + try: + if self.in_maintenance: + _LOGGER.debug( + "better_thermostat %s: valve maintenance already running", + self.device_name, + ) + return + trvs_to_service = collect_maintenance_trvs(self.real_trvs) + if not trvs_to_service: + _LOGGER.debug( + "better_thermostat %s: valve maintenance requested, but no TRV has it enabled", + self.device_name, + ) + return + # force immediate run + self.next_valve_maintenance = dt_util.now() + await self._run_valve_maintenance(trvs_to_service) + except Exception: + _LOGGER.debug( + "better_thermostat %s: valve maintenance service encountered an error", + self.device_name, + ) + @property def min_temp(self): """Return the minimum temperature. @@ -1345,21 +2749,6 @@ class BetterThermostat(ClimateEntity, RestoreEntity, ABC): # Get default temp from super class return super().max_temp - @property - def _is_device_active(self): - """Get the current state of the device for HA. - - Returns - ------- - string - State of the device. - """ - if self.bt_hvac_mode == HVACMode.OFF: - return False - if self.window_open: - return False - return True - @property def supported_features(self): """Return the list of supported features. @@ -1385,20 +2774,297 @@ class BetterThermostat(ClimateEntity, RestoreEntity, ABC): @property def preset_mode(self): - return self._preset_mode + """Return the current preset mode.""" + return self.preset_mgr.mode + async def async_set_preset_mode(self, preset_mode: str) -> None: + """Set new preset mode (HA async API). + + NOTE: + Home Assistant calls `async_set_preset_mode` directly when present. + A coroutine named `set_preset_mode` (without the `async_` prefix) + would be assumed synchronous and executed via `run_in_executor`, + raising "set_preset_mode cannot be used with run_in_executor". + """ + self.bt_update_lock = True + try: + old_preset = self.preset_mgr.mode + new_temp = self.preset_mgr.activate( + preset_mode, self.bt_target_temp, self.min_temp, self.max_temp + ) + + if new_temp is None and preset_mode not in self.preset_mgr.available_modes: + _LOGGER.warning( + "better_thermostat %s: Unsupported preset mode %s", + self.device_name, + preset_mode, + ) + return + + if new_temp is not None: + self.bt_target_temp = new_temp + + _LOGGER.debug( + "better_thermostat %s: After preset change %s -> %s, bt_target_temp=%s, bt_hvac_mode=%s", + self.device_name, + old_preset, + preset_mode, + self.bt_target_temp, + self.bt_hvac_mode, + ) + + self.async_write_ha_state() + if ( + hasattr(self, "control_queue_task") + and self.control_queue_task is not None + ): + await self.control_queue_task.put(self) + finally: + self.bt_update_lock = False + + # Backwards compatibility: If anything external still tries to call the old + # (incorrect) async method name, provide a thin wrapper. This is intentionally + # NOT async so HA will not pick it up as the implementation again. + # type: ignore[override] # Backward compatibility wrapper def set_preset_mode(self, preset_mode: str) -> None: - self._preset_mode = preset_mode + """Backward compatible wrapper. + + This wrapper schedules the new async method on the event loop. It should + only be hit by external/custom code; HA core will prefer async_set_preset_mode. + """ + if self.hass is None: + return + # Schedule without waiting; state updates will propagate asynchronously. + self.hass.async_create_background_task( + self.async_set_preset_mode(preset_mode), + name=f"bt_set_preset_{self.device_name}", + ) @property def preset_modes(self): - return [ - PRESET_NONE, - # PRESET_AWAY, - # PRESET_ECO, - # PRESET_COMFORT, - # PRESET_BOOST, - # PRESET_SLEEP, - # PRESET_ACTIVITY, - # PRESET_HOME, - ] + """Return the available preset modes.""" + return self.preset_mgr.available_modes + + async def reset_pid_learnings_service( + self, + apply_pid_defaults: bool = False, + defaults_kp: float | None = None, + defaults_ki: float | None = None, + defaults_kd: float | None = None, + ) -> None: + """Entity service: reset learned PID state for this entity. + + - Clears all cached PIDState entries for this entity (all TRVs/buckets) + - Schedules persistence saves for the map + """ + try: + prefix = f"{self._unique_id}:" + # Collect keys to reset from balance module + current = pid_export_states(prefix=prefix) or {} + count = 0 + for key in list(current.keys()): + try: + pid_reset_state(key) + count += 1 + except Exception: + _LOGGER.debug( + "better_thermostat %s: could not reset PID state %s", + self.device_name, + key, + ) + _LOGGER.info( + "better_thermostat %s: reset %d PID learning state entries (prefix=%s)", + self.device_name, + count, + prefix, + ) + # Schedule persistence of cleared PID states + try: + self.schedule_save_state() + except Exception: + _LOGGER.debug( + "better_thermostat %s: could not schedule state save after PID reset", + self.device_name, + ) + + # Optionally seed PID defaults for the CURRENT target bucket(s) + if apply_pid_defaults: + try: + from .utils.calibration.pid import PIDParams, seed_pid_gains + + # Use provided overrides or PIDParams defaults + _defs = PIDParams() + kp = float(defaults_kp) if defaults_kp is not None else _defs.kp + ki = float(defaults_ki) if defaults_ki is not None else _defs.ki + kd = float(defaults_kd) if defaults_kd is not None else _defs.kd + + # Build current bucket tag based on current heat target + def _bucket(temp): + try: + return format_bucket(round_to_bucket(temp)) + except (TypeError, ValueError): + return None + + # Build list of candidate buckets: current and ±0.5°C neighbors + bucket_tag = _bucket(self.bt_target_temp) + buckets: list[str] = [] + try: + if isinstance(self.bt_target_temp, (int, float)): + base = round_to_bucket(self.bt_target_temp) + buckets = [ + format_bucket(base), + format_bucket(base + 0.5), + format_bucket(base - 0.5), + ] + elif bucket_tag: + buckets = [bucket_tag] + except (TypeError, ValueError): + if bucket_tag: + buckets = [bucket_tag] + uid = resolve_unique_id(self) + seeded = 0 + for trv_id in self.real_trvs: + for b in buckets or []: + key = f"{uid}:{trv_id}:{b}" + try: + if seed_pid_gains(key, kp=kp, ki=ki, kd=kd): + seeded += 1 + except Exception: + _LOGGER.debug( + "better_thermostat %s: could not seed PID gains for %s", + self.device_name, + key, + ) + if seeded > 0: + _LOGGER.info( + "better_thermostat %s: applied PID defaults (kp=%.3f ki=%.3f kd=%.3f) to %d bucket state(s) across %d TRV(s)", + self.device_name, + kp, + ki, + kd, + seeded, + len(list(self.real_trvs.keys()) or []), + ) + try: + self.schedule_save_state() + except Exception: + _LOGGER.debug( + "better_thermostat %s: could not schedule state save " + "after seeding PID defaults", + self.device_name, + ) + # Kick the control loop so the new gains are used promptly + try: + await self.control_queue_task.put(self) + except Exception: + _LOGGER.debug( + "better_thermostat %s: could not queue control cycle " + "after seeding PID defaults", + self.device_name, + ) + else: + _LOGGER.debug( + "better_thermostat %s: apply_pid_defaults did not seed any bucket (bt_target_temp=%s, buckets=%s)", + self.device_name, + self.bt_target_temp, + buckets, + ) + except Exception as e: + _LOGGER.debug( + "better_thermostat %s: apply_pid_defaults failed: %s", + self.device_name, + e, + ) + except Exception as e: + _LOGGER.debug( + "better_thermostat %s: reset_pid_learnings_service error: %s", + self.device_name, + e, + ) + + async def _async_update_ema_periodic(self, now=None): + """Periodically update the EMA filter to ensure it converges even if sensor is silent.""" + # Skip if startup is still running to avoid race conditions or confusing logs + if self.startup_running: + return + + from .events.temperature import _update_external_temp_ema + + _LOGGER.debug( + "better_thermostat %s: _async_update_ema_periodic triggered", + self.device_name, + ) + + last_raw = self.last_known_external_temp + if last_raw is not None: + try: + _LOGGER.debug( + "better_thermostat %s: updating EMA with last_raw=%s", + self.device_name, + last_raw, + ) + + # Calculate slope from EMA change + old_ema = self.external_temp_ema + old_ts = self._slope_periodic_last_ts + now_ts = monotonic() + + new_ema = _update_external_temp_ema(self, float(last_raw)) + + if old_ema is not None and old_ts is not None: + dt_min = (now_ts - old_ts) / 60.0 + if dt_min > 0.1: # Avoid division by zero or tiny steps + delta_T = new_ema - old_ema + slope = delta_T / dt_min + self.temp_slope = slope + _LOGGER.debug( + "better_thermostat %s: periodic slope calc: old_ema=%.3f new_ema=%.3f dt=%.2fmin -> slope=%.4f K/min", + self.device_name, + old_ema, + new_ema, + dt_min, + slope, + ) + + self._slope_periodic_last_ts = now_ts + + _LOGGER.debug( + "better_thermostat %s: periodic EMA result=%.3f", + self.device_name, + new_ema, + ) + # If the sensor entity is listening to state changes, we should trigger an update + # But we don't want to spam the state machine if nothing changed significantly? + # The sensor entity reads `cur_temp_filtered` from `self`. + # We can just write state if we want the sensor to update. + # But `async_write_ha_state` updates the climate entity state. + # The sensor listens to the climate entity. + # So we should call `async_write_ha_state` if we want the sensor to see the new EMA. + self.async_write_ha_state() + except Exception as e: + _LOGGER.error( + "better_thermostat %s: error in _async_update_ema_periodic: %s", + self.device_name, + e, + ) + else: + _LOGGER.debug( + "better_thermostat %s: _async_update_ema_periodic skipped (no last_known_external_temp)", + self.device_name, + ) + + async def async_will_remove_from_hass(self): + """Run when entity will be removed from hass.""" + if self._control_task: + self._control_task.cancel() + try: + await self._control_task + except asyncio.CancelledError: + pass + if self._window_task: + self._window_task.cancel() + try: + await self._window_task + except asyncio.CancelledError: + pass + await super().async_will_remove_from_hass() diff --git a/custom_components/better_thermostat/config_flow.py b/custom_components/better_thermostat/config_flow.py index a60ae51..6ac49cd 100644 --- a/custom_components/better_thermostat/config_flow.py +++ b/custom_components/better_thermostat/config_flow.py @@ -1,24 +1,35 @@ -import logging -import voluptuous as vol +"""Config flow for Better Thermostat.""" from collections import OrderedDict +from collections.abc import Iterable, Mapping +import copy +import logging +from typing import Any + from homeassistant import config_entries +from homeassistant.components.climate.const import ( + PRESET_ACTIVITY, + PRESET_AWAY, + PRESET_BOOST, + PRESET_COMFORT, + PRESET_ECO, + PRESET_HOME, + PRESET_SLEEP, + HVACMode, +) from homeassistant.const import CONF_NAME from homeassistant.core import callback -from homeassistant.helpers import selector -from homeassistant.components.climate.const import HVACMode -from homeassistant.helpers import config_validation as cv - +from homeassistant.helpers import config_validation as cv, selector +from homeassistant.helpers.dispatcher import dispatcher_send +import voluptuous as vol +from . import DOMAIN # pylint: disable=unused-import from .adapters.delegate import load_adapter - -from .utils.helpers import get_device_model, get_trv_intigration - from .utils.const import ( - CONF_COOLER, - CONF_PROTECT_OVERHEATING, CONF_CALIBRATION, + CONF_CALIBRATION_MODE, CONF_CHILD_LOCK, + CONF_COOLER, CONF_HEAT_AUTO_SWAPPED, CONF_HEATER, CONF_HOMEMATICIP, @@ -27,55 +38,35 @@ from .utils.const import ( CONF_NO_SYSTEM_MODE_OFF, CONF_OFF_TEMPERATURE, CONF_OUTDOOR_SENSOR, + CONF_PRESETS, + CONF_PROTECT_OVERHEATING, CONF_SENSOR, CONF_SENSOR_WINDOW, + CONF_TARGET_TEMP_STEP, + CONF_TOLERANCE, CONF_VALVE_MAINTENANCE, CONF_WEATHER, CONF_WINDOW_TIMEOUT, CONF_WINDOW_TIMEOUT_AFTER, - CONF_CALIBRATION_MODE, - CONF_TOLERANCE, - CONF_TARGET_TEMP_STEP, CalibrationMode, CalibrationType, ) - -from . import DOMAIN # pylint:disable=unused-import +from .utils.helpers import get_device_model, get_trv_intigration _LOGGER = logging.getLogger(__name__) -CALIBRATION_TYPE_SELECTOR = selector.SelectSelector( - selector.SelectSelectorConfig( - options=[ - selector.SelectOptionDict( - value=CalibrationType.TARGET_TEMP_BASED, - label="Target Temperature Based", - ) - ], - mode=selector.SelectSelectorMode.DROPDOWN, - ) +CONFIG_WALKTHROUGH_URL = ( + "https://better-thermostat.org/setup/configuration-walkthrough/" ) -CALIBRATION_TYPE_ALL_SELECTOR = selector.SelectSelector( - selector.SelectSelectorConfig( - options=[ - selector.SelectOptionDict( - value=CalibrationType.TARGET_TEMP_BASED, - label="Target Temperature Based", - ), - selector.SelectOptionDict( - value=CalibrationType.LOCAL_BASED, label="Offset Based" - ), - selector.SelectOptionDict(value=CalibrationType.HYBRID, label="Hybrid"), - ], - mode=selector.SelectSelectorMode.DROPDOWN, - ) -) TEMP_STEP_SELECTOR = selector.SelectSelector( selector.SelectSelectorConfig( options=[ - selector.SelectOptionDict(value="0.0", label="Auto"), + selector.SelectOptionDict( + value="0.0", label="Auto" + ), # Keep for backwards compatibility + selector.SelectOptionDict(value="", label="Auto (New)"), selector.SelectOptionDict(value="0.1", label="0.1 °C"), selector.SelectOptionDict(value="0.2", label="0.2 °C"), selector.SelectOptionDict(value="0.25", label="0.25 °C"), @@ -86,15 +77,28 @@ TEMP_STEP_SELECTOR = selector.SelectSelector( ) ) + CALIBRATION_MODE_SELECTOR = selector.SelectSelector( selector.SelectSelectorConfig( options=[ - selector.SelectOptionDict(value=CalibrationMode.DEFAULT, label="Normal"), + selector.SelectOptionDict( + value=CalibrationMode.HEATING_POWER_CALIBRATION, label="(AI) Time Based" + ), + selector.SelectOptionDict( + value=CalibrationMode.DEFAULT, + label="External Sensor Offset Only (Default)", + ), + selector.SelectOptionDict( + value=CalibrationMode.MPC_CALIBRATION, label="MPC Predictive (Beta)" + ), selector.SelectOptionDict( value=CalibrationMode.AGGRESIVE_CALIBRATION, label="Agressive" ), selector.SelectOptionDict( - value=CalibrationMode.HEATING_POWER_CALIBRATION, label="AI Time Based" + value=CalibrationMode.TPI_CALIBRATION, label="TPI Controller" + ), + selector.SelectOptionDict( + value=CalibrationMode.PID_CALIBRATION, label="PID Controller" ), selector.SelectOptionDict( value=CalibrationMode.NO_CALIBRATION, label="No Calibration" @@ -105,19 +109,562 @@ CALIBRATION_MODE_SELECTOR = selector.SelectSelector( ) +PRESET_SELECTOR = selector.SelectSelector( + selector.SelectSelectorConfig( + options=[ + selector.SelectOptionDict(value=PRESET_ECO, label="Eco"), + selector.SelectOptionDict(value=PRESET_AWAY, label="Away"), + selector.SelectOptionDict(value=PRESET_BOOST, label="Boost"), + selector.SelectOptionDict(value=PRESET_COMFORT, label="Comfort"), + selector.SelectOptionDict(value=PRESET_HOME, label="Home"), + selector.SelectOptionDict(value=PRESET_SLEEP, label="Sleep"), + selector.SelectOptionDict(value=PRESET_ACTIVITY, label="Activity"), + ], + mode=selector.SelectSelectorMode.DROPDOWN, + multiple=True, + translation_key="presets", + ) +) + + +_USER_FIELD_DEFAULTS: dict[str, Any] = { + CONF_OFF_TEMPERATURE: 20, + CONF_TOLERANCE: 0.0, + CONF_TARGET_TEMP_STEP: "0.0", +} + + +def _as_bool(value: bool | str | int | None, default: bool = False) -> bool: + if isinstance(value, bool): + return value + if value is None: + return default + if isinstance(value, str): + lowered = value.strip().lower() + if lowered in {"true", "yes", "1", "on"}: + return True + if lowered in {"false", "no", "0", "off"}: + return False + return bool(value) + + +async def _load_adapter_info( + flow: config_entries.ConfigFlow | config_entries.OptionsFlow, + integration: str | None, + trv_id: str | None, + *, + existing_adapter: Any | None = None, +) -> tuple[Any | None, dict[str, Any]]: + adapter = existing_adapter + info: dict[str, Any] = {} + + if integration and trv_id: + if adapter is None: + try: + adapter = await load_adapter(flow, integration, trv_id) + except ( + RuntimeError, + ValueError, + TypeError, + ): # pragma: no cover - defensive + _LOGGER.debug("load_adapter failed", exc_info=True) + + if adapter is not None and hasattr(adapter, "get_info"): + try: + # type: ignore[attr-defined] + info = await adapter.get_info(flow, trv_id) + except (RuntimeError, ValueError, TypeError, AttributeError): + _LOGGER.debug("adapter get_info failed", exc_info=True) + + return adapter, info + + +def _default_calibration_from_info(info: dict[str, Any]) -> str: + if info.get("support_offset", False): + return "local_calibration_based" + if info.get("support_valve", False): + return "direct_valve_based" + return "target_temp_based" + + +def _trv_supports_auto( + flow: config_entries.ConfigFlow | config_entries.OptionsFlow, trv_id: str | None +) -> bool: + if not trv_id: + return False + trv_state = flow.hass.states.get(trv_id) + if not trv_state or not hasattr(trv_state, "attributes"): + return False + hvac_modes = trv_state.attributes.get("hvac_modes") or [] + return HVACMode.AUTO in hvac_modes + + +def _build_advanced_fields( + *, + sources: Iterable[dict[str, Any] | None], + default_calibration: str, + homematic: bool, + has_auto: bool, + support_valve: bool = False, + support_offset: bool = False, +) -> OrderedDict: + # Migrate old balance_mode to calibration_mode + sources_list = list(sources) + for source in sources_list: + if isinstance(source, dict): + balance_mode = source.get("balance_mode") + if balance_mode == "pid": + # Migrate PID from balance_mode to calibration_mode + source["calibration_mode"] = CalibrationMode.PID_CALIBRATION.value + # Remove old balance_mode + source.pop("balance_mode", None) + elif balance_mode in ("heuristic", "none"): + # For other balance modes, set calibration_mode to default if not set + if "calibration_mode" not in source: + source["calibration_mode"] = CalibrationMode.MPC_CALIBRATION.value + # Remove old balance_mode + source.pop("balance_mode", None) + + sources = sources_list + + def get_value(key: str, fallback: Any) -> Any: + """Get value from first source dict that contains the key.""" + for source in sources: + if isinstance(source, dict) and key in source: + return source[key] + return fallback + + def get_bool(key: str, fallback: bool) -> bool: + """Get boolean value from sources, converting string representations.""" + return _as_bool(get_value(key, fallback), fallback) + + # Build fields directly in the final desired order without post-reordering + # Compute values used below + calib_default = get_value(CONF_CALIBRATION, default_calibration) + + options = [] + if support_valve: + options.append( + selector.SelectOptionDict( + value=CalibrationType.DIRECT_VALVE_BASED, label="Direct Valve Based" + ) + ) + + options.append( + selector.SelectOptionDict( + value=CalibrationType.TARGET_TEMP_BASED, label="Target Temperature Based" + ) + ) + + if support_offset: + options.append( + selector.SelectOptionDict( + value=CalibrationType.LOCAL_BASED, label="Offset Based" + ) + ) + + calib_selector = selector.SelectSelector( + selector.SelectSelectorConfig( + options=options, mode=selector.SelectSelectorMode.DROPDOWN + ) + ) + ordered: OrderedDict = OrderedDict() + + # 1) Calibration + protection flags + ordered[vol.Required(CONF_CALIBRATION, default=calib_default)] = calib_selector + ordered[ + vol.Required( + CONF_CALIBRATION_MODE, + default=get_value( + CONF_CALIBRATION_MODE, CalibrationMode.HEATING_POWER_CALIBRATION + ), + ) + ] = CALIBRATION_MODE_SELECTOR + + ordered[ + vol.Optional( + CONF_PROTECT_OVERHEATING, default=get_bool(CONF_PROTECT_OVERHEATING, True) + ) + ] = bool + ordered[ + vol.Optional( + CONF_NO_SYSTEM_MODE_OFF, default=get_bool(CONF_NO_SYSTEM_MODE_OFF, False) + ) + ] = bool + ordered[ + vol.Optional( + CONF_HEAT_AUTO_SWAPPED, default=get_bool(CONF_HEAT_AUTO_SWAPPED, False) + ) + ] = bool + ordered[ + vol.Optional( + CONF_VALVE_MAINTENANCE, default=get_bool(CONF_VALVE_MAINTENANCE, False) + ) + ] = bool + ordered[vol.Optional(CONF_CHILD_LOCK, default=get_bool(CONF_CHILD_LOCK, False))] = ( + bool + ) + ordered[ + vol.Optional(CONF_HOMEMATICIP, default=get_bool(CONF_HOMEMATICIP, homematic)) + ] = bool + + return ordered + + +def _normalize_advanced_submission( + data: dict[str, Any], *, default_calibration: str, homematic: bool, has_auto: bool +) -> dict[str, Any]: + normalized: dict[str, Any] = dict(data) + normalized[CONF_CALIBRATION] = normalized.get(CONF_CALIBRATION, default_calibration) + normalized[CONF_CALIBRATION_MODE] = normalized.get( + CONF_CALIBRATION_MODE, CalibrationMode.HEATING_POWER_CALIBRATION + ) + normalized[CONF_PROTECT_OVERHEATING] = _as_bool( + normalized.get(CONF_PROTECT_OVERHEATING), False + ) + normalized[CONF_NO_SYSTEM_MODE_OFF] = _as_bool( + normalized.get(CONF_NO_SYSTEM_MODE_OFF), False + ) + normalized[CONF_HEAT_AUTO_SWAPPED] = _as_bool( + normalized.get(CONF_HEAT_AUTO_SWAPPED), False + ) + normalized[CONF_VALVE_MAINTENANCE] = _as_bool( + normalized.get(CONF_VALVE_MAINTENANCE), False + ) + normalized[CONF_CHILD_LOCK] = _as_bool(normalized.get(CONF_CHILD_LOCK), False) + normalized[CONF_HOMEMATICIP] = _as_bool(normalized.get(CONF_HOMEMATICIP), homematic) + + _LOGGER.debug("Normalized advanced submission: %s", normalized) + + return normalized + + +def _duration_dict_to_seconds(duration: int | float | dict[str, int] | None) -> int: + if duration is None: + return 0 + if isinstance(duration, (int, float)): + try: + return max(int(duration), 0) + except (TypeError, ValueError): + return 0 + if isinstance(duration, dict): + try: + return int(cv.time_period_dict(duration).total_seconds()) or 0 + except (vol.Invalid, TypeError, ValueError): + return 0 + return 0 + + +def _seconds_to_duration_dict(value: int | float | str | None) -> dict[str, int]: + try: + total = int(value or 0) + except (TypeError, ValueError): + total = 0 + total = max(total, 0) + hours, remainder = divmod(total, 3600) + minutes, seconds = divmod(remainder, 60) + return {"hours": int(hours), "minutes": int(minutes), "seconds": int(seconds)} + + +def _build_user_fields( + *, mode: str, current: Mapping[str, Any], user_input: dict[str, Any] | None = None +) -> OrderedDict: + user_input = user_input or {} + is_create = mode == "create" + fields: OrderedDict = OrderedDict() + + def resolve(key: str, fallback: Any = None) -> Any: + """Resolve field value from user input, current config, or defaults.""" + if key in user_input: + return user_input[key] + if key in current and current[key] is not None: + return current[key] + if fallback is None and key in _USER_FIELD_DEFAULTS: + return _USER_FIELD_DEFAULTS[key] + return fallback + + def add_field( + key: str, field_type: Any, *, required: bool = False, default: Any = None + ) -> None: + """Add a field to the form schema with appropriate validation.""" + description = None + use_default = default is not None + + if isinstance(field_type, selector.EntitySelector): + if default not in (None, [], ""): + description = {"suggested_value": default} + use_default = False + + if required: + if use_default: + fields[vol.Required(key, default=default)] = field_type + else: + fields[ + ( + vol.Required(key, description=description) + if description + else vol.Required(key) + ) + ] = field_type + elif use_default: + fields[vol.Optional(key, default=default)] = field_type + else: + fields[ + ( + vol.Optional(key, description=description) + if description + else vol.Optional(key) + ) + ] = field_type + + def add_entity_selector( + key: str, + *, + domain: str | list[str], + device_class: str | None = None, + multiple: bool = False, + required: bool = False, + ) -> None: + """Add an entity selector field with domain and device class filtering.""" + if device_class is not None: + selector_config = selector.EntitySelectorConfig( + domain=domain, multiple=multiple, device_class=device_class + ) + else: + selector_config = selector.EntitySelectorConfig( + domain=domain, multiple=multiple + ) + default = resolve(key) + if key == CONF_HEATER and isinstance(default, list): + default = [ + item.get("trv") + for item in default + if isinstance(item, dict) and item.get("trv") + ] + if key == CONF_HEATER and not default: + default = None + add_field( + key, + selector.EntitySelector(selector_config), + required=required, + default=default, + ) + + add_field(CONF_NAME, str, default=resolve(CONF_NAME, "")) + + add_entity_selector(CONF_HEATER, domain="climate", multiple=True, required=True) + add_entity_selector(CONF_COOLER, domain="climate", multiple=False) + + add_entity_selector( + CONF_SENSOR, + domain=["sensor", "number", "input_number"], + device_class="temperature", + required=is_create, + ) + add_entity_selector( + CONF_HUMIDITY, + domain=["sensor", "number", "input_number"], + device_class="humidity", + ) + add_entity_selector( + CONF_OUTDOOR_SENSOR, + domain=["sensor", "input_number", "number"], + device_class="temperature", + ) + add_entity_selector( + CONF_SENSOR_WINDOW, domain=["group", "sensor", "input_boolean", "binary_sensor"] + ) + add_entity_selector(CONF_WEATHER, domain="weather") + + for key in (CONF_WINDOW_TIMEOUT, CONF_WINDOW_TIMEOUT_AFTER): + if key in user_input and user_input[key] is not None: + duration_default = user_input[key] + else: + stored = resolve(key, 0 if not is_create else None) + if isinstance(stored, dict): + duration_default = stored + elif stored is not None: + duration_default = _seconds_to_duration_dict(stored) + else: + duration_default = None + add_field(key, selector.DurationSelector(), default=duration_default) + + off_temp_default = resolve( + CONF_OFF_TEMPERATURE, _USER_FIELD_DEFAULTS[CONF_OFF_TEMPERATURE] + ) + try: + off_temp_default = int(off_temp_default) + except (TypeError, ValueError): + off_temp_default = _USER_FIELD_DEFAULTS[CONF_OFF_TEMPERATURE] + add_field(CONF_OFF_TEMPERATURE, int, default=off_temp_default) + + add_field( + CONF_PRESETS, PRESET_SELECTOR, default=resolve(CONF_PRESETS, [PRESET_ECO]) + ) + + tolerance_default = resolve(CONF_TOLERANCE, _USER_FIELD_DEFAULTS[CONF_TOLERANCE]) + try: + tolerance_default = float(tolerance_default) + except (TypeError, ValueError): + tolerance_default = _USER_FIELD_DEFAULTS[CONF_TOLERANCE] + add_field( + CONF_TOLERANCE, + vol.All(vol.Coerce(float), vol.Range(min=0)), + default=tolerance_default, + ) + + target_step_default = resolve( + CONF_TARGET_TEMP_STEP, _USER_FIELD_DEFAULTS[CONF_TARGET_TEMP_STEP] + ) + if target_step_default is not None: + target_step_default = str(target_step_default) + add_field(CONF_TARGET_TEMP_STEP, TEMP_STEP_SELECTOR, default=target_step_default) + + return fields + + +def _normalize_user_submission( + user_input: dict[str, Any], *, mode: str, base: Mapping[str, Any] | None = None +) -> dict[str, Any]: + if base: + if not isinstance(base, dict): + base_dict = dict(base) + else: + base_dict = base + base_copy = copy.deepcopy(base_dict) + else: + base_copy = {} + normalized: dict[str, Any] = base_copy + + normalized[CONF_NAME] = user_input.get(CONF_NAME, normalized.get(CONF_NAME, "")) + + heaters_value = user_input.get(CONF_HEATER, normalized.get(CONF_HEATER, [])) + if isinstance(heaters_value, list): + heaters_list = heaters_value + elif heaters_value is None: + heaters_list = [] + else: + heaters_list = [heaters_value] + if heaters_list and isinstance(heaters_list[0], dict): + heaters_list = [ + item.get("trv") + for item in heaters_list + if isinstance(item, dict) and item.get("trv") + ] + normalized[CONF_HEATER] = list(heaters_list) + normalized[CONF_COOLER] = user_input.get(CONF_COOLER, normalized.get(CONF_COOLER)) + + optional_keys = ( + CONF_SENSOR, + CONF_SENSOR_WINDOW, + CONF_HUMIDITY, + CONF_OUTDOOR_SENSOR, + CONF_WEATHER, + ) + for key in optional_keys: + if key in user_input: + value = user_input.get(key) + if value == "" or value is None: + normalized[key] = None + else: + normalized[key] = value + else: + normalized[key] = None + + for key in (CONF_WINDOW_TIMEOUT, CONF_WINDOW_TIMEOUT_AFTER): + if key in user_input: + normalized[key] = _duration_dict_to_seconds(user_input.get(key)) + elif mode == "create" and key not in normalized: + normalized[key] = 0 + + off_temp = user_input.get( + CONF_OFF_TEMPERATURE, + normalized.get( + CONF_OFF_TEMPERATURE, _USER_FIELD_DEFAULTS[CONF_OFF_TEMPERATURE] + ), + ) + if off_temp is None: + normalized[CONF_OFF_TEMPERATURE] = _USER_FIELD_DEFAULTS[CONF_OFF_TEMPERATURE] + else: + try: + normalized[CONF_OFF_TEMPERATURE] = int(off_temp) + except (TypeError, ValueError): + normalized[CONF_OFF_TEMPERATURE] = _USER_FIELD_DEFAULTS[ + CONF_OFF_TEMPERATURE + ] + + if CONF_PRESETS in user_input: + normalized[CONF_PRESETS] = user_input[CONF_PRESETS] + elif mode == "create" and CONF_PRESETS not in normalized: + normalized[CONF_PRESETS] = [] + + tolerance = user_input.get( + CONF_TOLERANCE, + normalized.get(CONF_TOLERANCE, _USER_FIELD_DEFAULTS[CONF_TOLERANCE]), + ) + if tolerance is None: + normalized[CONF_TOLERANCE] = _USER_FIELD_DEFAULTS[CONF_TOLERANCE] + else: + try: + normalized[CONF_TOLERANCE] = float(tolerance) + except (TypeError, ValueError): + normalized[CONF_TOLERANCE] = _USER_FIELD_DEFAULTS[CONF_TOLERANCE] + + target_step = user_input.get( + CONF_TARGET_TEMP_STEP, + normalized.get( + CONF_TARGET_TEMP_STEP, _USER_FIELD_DEFAULTS[CONF_TARGET_TEMP_STEP] + ), + ) + if target_step in (None, ""): + target_step = _USER_FIELD_DEFAULTS[CONF_TARGET_TEMP_STEP] + normalized[CONF_TARGET_TEMP_STEP] = str(target_step) + + return normalized + + +async def _prepare_advanced_context( + flow: config_entries.ConfigFlow | config_entries.OptionsFlow, + trv_config: dict[str, Any] | None, +) -> dict[str, Any]: + trv_config = trv_config or {} + integration = trv_config.get("integration") + trv_id = trv_config.get("trv") + adapter, info = await _load_adapter_info( + flow, integration, trv_id, existing_adapter=trv_config.get("adapter") + ) + default_calibration = _default_calibration_from_info(info) + homematic = bool(integration and "homematic" in integration.lower()) + has_auto = _trv_supports_auto(flow, trv_id) + + return { + "adapter": adapter, + "info": info, + "default_calibration": default_calibration, + "homematic": homematic, + "has_auto": has_auto, + "integration": integration, + "trv_id": trv_id, + } + + class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): - VERSION = 6 + """Config flow for Better Thermostat.""" + + VERSION = 18 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL def __init__(self): """Initialize the config flow.""" self.device_name = "" - self.data = None + self.data: dict[str, Any] | None = None self.model = None self.heater_entity_id = None - self.trv_bundle = [] + self.trv_bundle: list[dict[str, Any]] = [] self.integration = None self.i = 0 + self._active_trv_config: dict[str, Any] | None = None super().__init__() @staticmethod @@ -127,11 +674,26 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -> config_entries.OptionsFlow: """Get the options flow for this handler.""" return OptionsFlowHandler(config_entry) - """Get the options flow for this handler.""" + + # Added to satisfy abstract base in newer HA versions + # type: ignore[override] + def is_matching(self, other_flow: config_entries.ConfigFlow) -> bool: + """Return True if this flow matches an existing config flow (reconfigure).""" + if ( + getattr(self, "unique_id", None) + and getattr(other_flow, "unique_id", None) == self.unique_id + ): + return True + return False async def async_step_confirm(self, user_input=None, confirm_type=None): """Handle user-confirmation of discovered node.""" errors = {} + if not self.data: + errors["base"] = "no_data" + return self.async_show_form(step_id="confirm", errors=errors) + + # attach current trv bundle self.data[CONF_HEATER] = self.trv_bundle if user_input is not None: if self.data is not None: @@ -140,249 +702,156 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): await self.async_set_unique_id( f"{self.data['name']}_{unique_trv_string}" ) + _LOGGER.debug( + "Creating entry with heater bundle: %s", self.data.get(CONF_HEATER) + ) self._abort_if_unique_id_configured() return self.async_create_entry(title=self.data["name"], data=self.data) if confirm_type is not None: errors["base"] = confirm_type - _trvs = ",".join([x["trv"] for x in self.data[CONF_HEATER]]) + data = self.data or {} + _trv_list = data.get(CONF_HEATER) or [] + _trvs = ",".join([x.get("trv", "?") for x in _trv_list]) return self.async_show_form( step_id="confirm", errors=errors, - description_placeholders={"name": self.data[CONF_NAME], "trv": _trvs}, + description_placeholders={"name": data.get(CONF_NAME, ""), "trv": _trvs}, ) async def async_step_advanced(self, user_input=None, _trv_config=None): - """Handle options flow.""" + """Handle the advanced step of the config flow.""" + trv_cfg = _trv_config if isinstance(_trv_config, dict) else None + if trv_cfg is None: + trv_cfg = self._active_trv_config + if trv_cfg is None: + _LOGGER.debug( + "ConfigFlow advanced step missing TRV context; returning to confirm" + ) + return await self.async_step_confirm() + + self._active_trv_config = trv_cfg + ctx = await _prepare_advanced_context(self, trv_cfg) + existing_adv = trv_cfg.get("advanced") if isinstance(trv_cfg, dict) else None + _LOGGER.debug( + "ConfigFlow advanced step called (index=%s, trv=%s) with user_input=%s", + self.i, + ctx.get("trv_id"), + user_input, + ) + if user_input is not None: - self.trv_bundle[self.i]["advanced"] = user_input + advanced_data = _normalize_advanced_submission( + user_input, + default_calibration=ctx["default_calibration"], + homematic=ctx["homematic"], + has_auto=ctx["has_auto"], + ) + _LOGGER.debug( + "ConfigFlow advanced step storing data for %s (index %s): %s", + trv_cfg.get("trv"), + self.i, + advanced_data, + ) + self.trv_bundle[self.i]["advanced"] = advanced_data self.trv_bundle[self.i]["adapter"] = None self.i += 1 + self._active_trv_config = None if len(self.trv_bundle) > self.i: + _LOGGER.debug( + "ConfigFlow advanced step moving to next TRV index=%s", self.i + ) return await self.async_step_advanced(None, self.trv_bundle[self.i]) _has_off_mode = True for trv in self.trv_bundle: - if HVACMode.OFF not in self.hass.states.get( - trv.get("trv") - ).attributes.get("hvac_modes"): + entity_id = trv.get("trv") + state_obj = self.hass.states.get(entity_id) if entity_id else None + hvac_modes: list[str] = [] + if state_obj and hasattr(state_obj, "attributes"): + hvac_modes = state_obj.attributes.get("hvac_modes", []) or [] + if HVACMode.OFF not in hvac_modes: _has_off_mode = False - if _has_off_mode is False: + if not _has_off_mode: return await self.async_step_confirm(None, "no_off_mode") return await self.async_step_confirm() user_input = user_input or {} - homematic = False - if _trv_config.get("integration").find("homematic") != -1: - homematic = True - - fields = OrderedDict() - - _default_calibration = "target_temp_based" - _adapter = _trv_config.get("adapter", None) - if _adapter is not None: - _info = await _adapter.get_info(self, _trv_config.get("trv")) - - if _info.get("support_offset", False): - _default_calibration = "local_calibration_based" - - if _default_calibration == "local_calibration_based": - fields[ - vol.Required( - CONF_CALIBRATION, - default=user_input.get(CONF_CALIBRATION, _default_calibration), - ) - ] = CALIBRATION_TYPE_ALL_SELECTOR - else: - fields[ - vol.Required( - CONF_CALIBRATION, - default=user_input.get(CONF_CALIBRATION, _default_calibration), - ) - ] = CALIBRATION_TYPE_SELECTOR - - fields[ - vol.Required( - CONF_CALIBRATION_MODE, - default=user_input.get( - CONF_CALIBRATION_MODE, CalibrationMode.HEATING_POWER_CALIBRATION - ), - ) - ] = CALIBRATION_MODE_SELECTOR - - fields[ - vol.Optional( - CONF_PROTECT_OVERHEATING, - default=user_input.get(CONF_PROTECT_OVERHEATING, False), - ) - ] = bool - - fields[ - vol.Optional( - CONF_NO_SYSTEM_MODE_OFF, - default=user_input.get(CONF_NO_SYSTEM_MODE_OFF, False), - ) - ] = bool - - fields[ - vol.Optional( - CONF_HEAT_AUTO_SWAPPED, - default=user_input.get(CONF_HEAT_AUTO_SWAPPED, False), - ) - ] = bool - - if _info.get("support_valve", False): - fields[ - vol.Optional( - CONF_VALVE_MAINTENANCE, - default=user_input.get(CONF_VALVE_MAINTENANCE, False), - ) - ] = bool - - fields[ - vol.Optional( - CONF_CHILD_LOCK, default=user_input.get(CONF_CHILD_LOCK, False) - ) - ] = bool - fields[ - vol.Optional( - CONF_HOMEMATICIP, default=user_input.get(CONF_HOMEMATICIP, homematic) - ) - ] = bool + info = ctx.get("info", {}) + fields = _build_advanced_fields( + sources=(user_input, existing_adv), + default_calibration=ctx["default_calibration"], + homematic=ctx["homematic"], + has_auto=ctx["has_auto"], + support_valve=info.get("support_valve", False), + support_offset=info.get("support_offset", False), + ) + _LOGGER.debug( + "ConfigFlow advanced step showing form for trv=%s with defaults=%s", + ctx.get("trv_id"), + existing_adv, + ) return self.async_show_form( step_id="advanced", data_schema=vol.Schema(fields), last_step=False, - description_placeholders={"trv": _trv_config.get("trv")}, + description_placeholders={ + "trv": ctx.get("trv_id") or "-", + "docs_url": CONFIG_WALKTHROUGH_URL, + }, ) async def async_step_user(self, user_input=None): - errors = {} + """Handle the initial step.""" + errors: dict[str, str] = {} + current = self.data or {} if user_input is not None: - if self.data is None: - self.data = user_input - self.heater_entity_id = self.data[CONF_HEATER] - if self.data[CONF_NAME] == "": + _LOGGER.debug("ConfigFlow user step received input: %s", user_input) + try: + normalized = _normalize_user_submission( + user_input, mode="create", base=current + ) + except Exception as err: + _LOGGER.exception("ConfigFlow user step normalization failed: %s", err) + raise + self.data = normalized + _LOGGER.debug("ConfigFlow user step normalized data: %s", normalized) + if not normalized.get(CONF_NAME): errors["base"] = "no_name" - if CONF_SENSOR_WINDOW not in self.data: - self.data[CONF_SENSOR_WINDOW] = None - if CONF_HUMIDITY not in self.data: - self.data[CONF_HUMIDITY] = None - if CONF_OUTDOOR_SENSOR not in self.data: - self.data[CONF_OUTDOOR_SENSOR] = None - if CONF_WEATHER not in self.data: - self.data[CONF_WEATHER] = None - if CONF_COOLER not in self.data: - self.data[CONF_COOLER] = None - - if CONF_WINDOW_TIMEOUT in self.data: - self.data[CONF_WINDOW_TIMEOUT] = ( - int( - cv.time_period_dict( - user_input.get(CONF_WINDOW_TIMEOUT, None) - ).total_seconds() - ) - or 0 - ) - else: - self.data[CONF_WINDOW_TIMEOUT] = 0 - - if CONF_WINDOW_TIMEOUT_AFTER in self.data: - self.data[CONF_WINDOW_TIMEOUT_AFTER] = ( - int( - cv.time_period_dict( - user_input.get(CONF_WINDOW_TIMEOUT_AFTER, None) - ).total_seconds() - ) - or 0 - ) - else: - self.data[CONF_WINDOW_TIMEOUT_AFTER] = 0 + heaters = normalized.get(CONF_HEATER) or [] if "base" not in errors: + self.heater_entity_id = list(heaters) + self.trv_bundle = [] for trv in self.heater_entity_id: - _intigration = await get_trv_intigration(self, trv) + integration = await get_trv_intigration(self, trv) self.trv_bundle.append( { "trv": trv, - "integration": _intigration, + "integration": integration, "model": await get_device_model(self, trv), - "adapter": await load_adapter(self, _intigration, trv), + "adapter": await load_adapter(self, integration, trv), } ) + _LOGGER.debug( + "ConfigFlow user step built trv bundle: %s", self.trv_bundle + ) self.data[CONF_MODEL] = "/".join([x["model"] for x in self.trv_bundle]) return await self.async_step_advanced(None, self.trv_bundle[0]) - user_input = user_input or {} + fields = _build_user_fields( + mode="create", current=self.data or {}, user_input=user_input + ) return self.async_show_form( step_id="user", - data_schema=vol.Schema( - { - vol.Optional(CONF_NAME, default=user_input.get(CONF_NAME, "")): str, - vol.Required(CONF_HEATER): selector.EntitySelector( - selector.EntitySelectorConfig(domain="climate", multiple=True) - ), - vol.Optional(CONF_COOLER): selector.EntitySelector( - selector.EntitySelectorConfig(domain="climate", multiple=False) - ), - vol.Required(CONF_SENSOR): selector.EntitySelector( - selector.EntitySelectorConfig( - domain=["sensor", "number", "input_number"], - device_class="temperature", - multiple=False, - ) - ), - vol.Optional(CONF_HUMIDITY): selector.EntitySelector( - selector.EntitySelectorConfig( - domain=["sensor", "number", "input_number"], - device_class="humidity", - multiple=False, - ) - ), - vol.Optional(CONF_OUTDOOR_SENSOR): selector.EntitySelector( - selector.EntitySelectorConfig( - domain=["sensor", "input_number", "number"], - device_class="temperature", - multiple=False, - ) - ), - vol.Optional(CONF_SENSOR_WINDOW): selector.EntitySelector( - selector.EntitySelectorConfig( - domain=[ - "group", - "sensor", - "input_boolean", - "binary_sensor", - ], - multiple=False, - ) - ), - vol.Optional(CONF_WEATHER): selector.EntitySelector( - selector.EntitySelectorConfig(domain="weather", multiple=False) - ), - vol.Optional(CONF_WINDOW_TIMEOUT): selector.DurationSelector(), - vol.Optional( - CONF_WINDOW_TIMEOUT_AFTER - ): selector.DurationSelector(), - vol.Optional( - CONF_OFF_TEMPERATURE, - default=user_input.get(CONF_OFF_TEMPERATURE, 20), - ): int, - vol.Optional( - CONF_TOLERANCE, default=user_input.get(CONF_TOLERANCE, 0.0) - ): vol.All(vol.Coerce(float), vol.Range(min=0)), - vol.Optional( - CONF_TARGET_TEMP_STEP, - default=str(user_input.get(CONF_TARGET_TEMP_STEP, "0.0")), - ): TEMP_STEP_SELECTOR, - } - ), + data_schema=vol.Schema(fields), errors=errors, last_step=False, + description_placeholders={"docs_url": CONFIG_WALKTHROUGH_URL}, ) @@ -392,13 +861,19 @@ class OptionsFlowHandler(config_entries.OptionsFlow): def __init__(self, config_entry: config_entries.ConfigEntry) -> None: """Initialize options flow.""" self.i = 0 - self.trv_bundle = [] + # Dynamic config structures use Any as they store heterogeneous data + self.trv_bundle: list[dict[str, Any]] = [] self.device_name = "" self._last_step = False - self.updated_config = {} + self.updated_config: dict[str, Any] = {} + self._active_trv_config: dict[str, Any] | None = None + # Do not set `self.config_entry` directly; store in a private attribute + # to avoid deprecated behavior. The framework will set `config_entry` on + # the options flow object as needed. + self._config_entry = config_entry super().__init__() - async def async_step_init(self, user_input=None): + async def async_step_init(self, _user_input=None): """Manage the options.""" return await self.async_step_user() @@ -406,8 +881,39 @@ class OptionsFlowHandler(config_entries.OptionsFlow): self, user_input=None, _trv_config=None, _update_config=None ): """Manage the advanced options.""" + trv_cfg = _trv_config if isinstance(_trv_config, dict) else None + if trv_cfg is None: + trv_cfg = self._active_trv_config + if trv_cfg is None: + _LOGGER.debug( + "OptionsFlow advanced step missing TRV context; aborting to init" + ) + return await self.async_step_init() + + self._active_trv_config = trv_cfg + ctx = await _prepare_advanced_context(self, trv_cfg) + existing_adv = trv_cfg.get("advanced") if isinstance(trv_cfg, dict) else None + _LOGGER.debug( + "OptionsFlow advanced step called (index=%s, trv=%s) with user_input=%s", + self.i, + ctx.get("trv_id"), + user_input, + ) + if user_input is not None: - self.trv_bundle[self.i]["advanced"] = user_input + advanced_data = _normalize_advanced_submission( + user_input, + default_calibration=ctx["default_calibration"], + homematic=ctx["homematic"], + has_auto=ctx["has_auto"], + ) + _LOGGER.debug( + "OptionsFlow advanced step storing data for %s (index %s): %s", + trv_cfg.get("trv"), + self.i, + advanced_data, + ) + self.trv_bundle[self.i]["advanced"] = advanced_data self.trv_bundle[self.i]["adapter"] = None self.i += 1 @@ -415,308 +921,172 @@ class OptionsFlowHandler(config_entries.OptionsFlow): self._last_step = True if len(self.trv_bundle) > self.i: + self._active_trv_config = None return await self.async_step_advanced( None, self.trv_bundle[self.i], _update_config ) self.updated_config[CONF_HEATER] = self.trv_bundle _LOGGER.debug("Updated config: %s", self.updated_config) - self.hass.config_entries.async_update_entry( - self.config_entry, data=self.updated_config + _LOGGER.debug( + "OptionsFlow writing heater bundle: %s", + self.updated_config.get(CONF_HEATER), ) + + # Check for calibration mode changes to trigger entity cleanup + await self._check_calibration_changes() + + self.hass.config_entries.async_update_entry( + self._config_entry, data=self.updated_config + ) + self._active_trv_config = None return self.async_create_entry( title=self.updated_config["name"], data=self.updated_config ) user_input = user_input or {} - homematic = False - if _trv_config.get("integration").find("homematic") != -1: - homematic = True - - fields = OrderedDict() - - _default_calibration = "target_temp_based" - self.device_name = user_input.get(CONF_NAME, "-") - - _adapter = await load_adapter( - self, _trv_config.get("integration"), _trv_config.get("trv") + info = ctx.get("info", {}) + fields = _build_advanced_fields( + sources=(user_input, existing_adv), + default_calibration=ctx["default_calibration"], + homematic=ctx["homematic"], + has_auto=ctx["has_auto"], + support_valve=info.get("support_valve", False), + support_offset=info.get("support_offset", False), ) - if _adapter is not None: - _info = await _adapter.get_info(self, _trv_config.get("trv")) - - if _info.get("support_offset", False): - _default_calibration = "local_calibration_based" - - if _default_calibration == "local_calibration_based": - fields[ - vol.Required( - CONF_CALIBRATION, - default=user_input.get( - CONF_CALIBRATION, - _trv_config["advanced"].get( - CONF_CALIBRATION, _default_calibration - ), - ), - ) - ] = CALIBRATION_TYPE_ALL_SELECTOR - else: - fields[ - vol.Required( - CONF_CALIBRATION, - default=user_input.get( - CONF_CALIBRATION, - _trv_config["advanced"].get( - CONF_CALIBRATION, _default_calibration - ), - ), - ) - ] = CALIBRATION_TYPE_SELECTOR - - fields[ - vol.Required( - CONF_CALIBRATION_MODE, - default=_trv_config["advanced"].get( - CONF_CALIBRATION_MODE, CalibrationMode.HEATING_POWER_CALIBRATION - ), - ) - ] = CALIBRATION_MODE_SELECTOR - - fields[ - vol.Optional( - CONF_PROTECT_OVERHEATING, - default=_trv_config["advanced"].get(CONF_PROTECT_OVERHEATING, False), - ) - ] = bool - - fields[ - vol.Optional( - CONF_NO_SYSTEM_MODE_OFF, - default=_trv_config["advanced"].get(CONF_NO_SYSTEM_MODE_OFF, False), - ) - ] = bool - - has_auto = False - trv = self.hass.states.get(_trv_config.get("trv")) - if HVACMode.AUTO in trv.attributes.get("hvac_modes"): - has_auto = True - - fields[ - vol.Optional( - CONF_HEAT_AUTO_SWAPPED, - default=_trv_config["advanced"].get(CONF_HEAT_AUTO_SWAPPED, has_auto), - ) - ] = bool - - if _info.get("support_valve", False): - fields[ - vol.Optional( - CONF_VALVE_MAINTENANCE, - default=_trv_config["advanced"].get(CONF_VALVE_MAINTENANCE, False), - ) - ] = bool - - fields[ - vol.Optional( - CONF_CHILD_LOCK, - default=_trv_config["advanced"].get(CONF_CHILD_LOCK, False), - ) - ] = bool - fields[ - vol.Optional( - CONF_HOMEMATICIP, - default=_trv_config["advanced"].get(CONF_HOMEMATICIP, homematic), - ) - ] = bool + _LOGGER.debug( + "OptionsFlow advanced step showing form for trv=%s with defaults=%s", + ctx.get("trv_id"), + existing_adv, + ) + self.device_name = user_input.get(CONF_NAME, "-") return self.async_show_form( step_id="advanced", data_schema=vol.Schema(fields), last_step=self._last_step, - description_placeholders={"trv": _trv_config.get("trv")}, + description_placeholders={ + "trv": ctx.get("trv_id") or "-", + "docs_url": CONFIG_WALKTHROUGH_URL, + }, ) async def async_step_user(self, user_input=None): + """Handle the user step.""" if user_input is not None: - current_config = self.config_entry.data - self.updated_config = dict(current_config) - self.updated_config[CONF_SENSOR] = user_input.get(CONF_SENSOR, None) - self.updated_config[CONF_SENSOR_WINDOW] = user_input.get( - CONF_SENSOR_WINDOW, None - ) - self.updated_config[CONF_HUMIDITY] = user_input.get(CONF_HUMIDITY, None) - self.updated_config[CONF_OUTDOOR_SENSOR] = user_input.get( - CONF_OUTDOOR_SENSOR, None - ) - self.updated_config[CONF_WEATHER] = user_input.get(CONF_WEATHER, None) - - if CONF_WINDOW_TIMEOUT in self.updated_config: - self.updated_config[CONF_WINDOW_TIMEOUT] = ( - int( - cv.time_period_dict( - user_input.get(CONF_WINDOW_TIMEOUT, None) - ).total_seconds() - ) - or 0 + _LOGGER.debug("OptionsFlow user step received input: %s", user_input) + try: + normalized = _normalize_user_submission( + user_input, mode="update", base=self._config_entry.data ) - else: - self.updated_config[CONF_WINDOW_TIMEOUT] = 0 + except Exception as err: + _LOGGER.exception("OptionsFlow user step normalization failed: %s", err) + raise + _LOGGER.debug("OptionsFlow user step normalized data: %s", normalized) + self.updated_config = normalized + self.trv_bundle = [] - if CONF_WINDOW_TIMEOUT_AFTER in self.updated_config: - self.updated_config[CONF_WINDOW_TIMEOUT_AFTER] = ( - int( - cv.time_period_dict( - user_input.get(CONF_WINDOW_TIMEOUT_AFTER, None) - ).total_seconds() + # Get the list of heaters from the normalized input + heaters = normalized.get(CONF_HEATER, []) + + # Create a map of existing TRV configs by TRV ID + existing_trvs = { + trv.get("trv"): trv + for trv in self._config_entry.data.get(CONF_HEATER, []) + if isinstance(trv, dict) and trv.get("trv") + } + + for heater_item in heaters: + if isinstance(heater_item, dict): + trv_id = heater_item.get("trv") + else: + trv_id = heater_item + + if not trv_id: + continue + + if trv_id in existing_trvs: + # Use existing config for this TRV + trv_copy = copy.deepcopy(existing_trvs[trv_id]) + trv_copy["adapter"] = None + self.trv_bundle.append(trv_copy) + else: + # This is a new TRV added during edit + integration = await get_trv_intigration(self, trv_id) + self.trv_bundle.append( + { + "trv": trv_id, + "integration": integration, + "model": await get_device_model(self, trv_id), + "adapter": await load_adapter(self, integration, trv_id), + } ) - or 0 - ) - else: - self.updated_config[CONF_WINDOW_TIMEOUT_AFTER] = 0 - self.updated_config[CONF_OFF_TEMPERATURE] = user_input.get( - CONF_OFF_TEMPERATURE - ) - - self.updated_config[CONF_TOLERANCE] = float( - user_input.get(CONF_TOLERANCE, 0.0) - ) - self.updated_config[CONF_TARGET_TEMP_STEP] = float( - user_input.get(CONF_TARGET_TEMP_STEP, "0.0") - ) - - for trv in self.updated_config[CONF_HEATER]: - trv["adapter"] = None - self.trv_bundle.append(trv) + _LOGGER.debug("OptionsFlow user step built trv bundle: %s", self.trv_bundle) return await self.async_step_advanced( None, self.trv_bundle[0], self.updated_config ) - fields = OrderedDict() - - fields[ - vol.Optional( - CONF_SENSOR, - description={ - "suggested_value": self.config_entry.data.get(CONF_SENSOR, "") - }, - ) - ] = selector.EntitySelector( - selector.EntitySelectorConfig( - domain=["sensor", "number", "input_number"], - device_class="temperature", - multiple=False, - ) + fields = _build_user_fields( + mode="update", current=self._config_entry.data, user_input=user_input ) - fields[ - vol.Optional( - CONF_HUMIDITY, - description={ - "suggested_value": self.config_entry.data.get(CONF_HUMIDITY, "") - }, - ) - ] = selector.EntitySelector( - selector.EntitySelectorConfig( - domain=["sensor", "number", "input_number"], - device_class="humidity", - multiple=False, - ) - ) - - fields[ - vol.Optional( - CONF_SENSOR_WINDOW, - description={ - "suggested_value": self.config_entry.data.get( - CONF_SENSOR_WINDOW, "" - ) - }, - ) - ] = selector.EntitySelector( - selector.EntitySelectorConfig( - domain=["group", "sensor", "input_boolean", "binary_sensor"], - multiple=False, - ) - ) - - fields[ - vol.Optional( - CONF_OUTDOOR_SENSOR, - description={ - "suggested_value": self.config_entry.data.get( - CONF_OUTDOOR_SENSOR, "" - ) - }, - ) - ] = selector.EntitySelector( - selector.EntitySelectorConfig( - domain=["sensor", "input_number", "number"], - device_class="temperature", - multiple=False, - ) - ) - - fields[ - vol.Optional( - CONF_WEATHER, - description={ - "suggested_value": self.config_entry.data.get(CONF_WEATHER, "") - }, - ) - ] = selector.EntitySelector( - selector.EntitySelectorConfig(domain="weather", multiple=False) - ) - - _timeout = self.config_entry.data.get(CONF_WINDOW_TIMEOUT, 0) - _timeout = str(cv.time_period_seconds(_timeout)) - _timeout = { - "hours": int(_timeout.split(":", maxsplit=1)[0]), - "minutes": int(_timeout.split(":")[1]), - "seconds": int(_timeout.split(":")[2]), - } - fields[ - vol.Optional( - CONF_WINDOW_TIMEOUT, - default=_timeout, - description={"suggested_value": _timeout}, - ) - ] = selector.DurationSelector() - - _timeout = self.config_entry.data.get(CONF_WINDOW_TIMEOUT_AFTER, 0) - _timeout = str(cv.time_period_seconds(_timeout)) - _timeout = { - "hours": int(_timeout.split(":", maxsplit=1)[0]), - "minutes": int(_timeout.split(":")[1]), - "seconds": int(_timeout.split(":")[2]), - } - fields[ - vol.Optional( - CONF_WINDOW_TIMEOUT_AFTER, - default=_timeout, - description={"suggested_value": _timeout}, - ) - ] = selector.DurationSelector() - - fields[ - vol.Optional( - CONF_OFF_TEMPERATURE, - default=self.config_entry.data.get(CONF_OFF_TEMPERATURE, 5), - ) - ] = int - - fields[ - vol.Optional( - CONF_TOLERANCE, default=self.config_entry.data.get(CONF_TOLERANCE, 0.0) - ) - ] = vol.All(vol.Coerce(float), vol.Range(min=0)) - fields[ - vol.Optional( - CONF_TARGET_TEMP_STEP, - default=str(self.config_entry.data.get(CONF_TARGET_TEMP_STEP, 0.0)), - ) - ] = TEMP_STEP_SELECTOR - return self.async_show_form( - step_id="user", data_schema=vol.Schema(fields), last_step=False + step_id="user", + data_schema=vol.Schema(fields), + last_step=False, + description_placeholders={"docs_url": CONFIG_WALKTHROUGH_URL}, ) + + async def _check_calibration_changes(self) -> None: + """Check for calibration mode changes and signal for entity cleanup.""" + old_config = self._config_entry.data + new_config = self.updated_config + + # Get active calibration algorithms from both configs + old_algorithms = self._get_active_algorithms(old_config) + new_algorithms = self._get_active_algorithms(new_config) + + if old_algorithms != new_algorithms: + algorithms_added = new_algorithms - old_algorithms + algorithms_removed = old_algorithms - new_algorithms + + _LOGGER.info( + "Better Thermostat %s: Calibration algorithms changed. Added: %s, Removed: %s", + self.updated_config.get(CONF_NAME, "unknown"), + [ + alg.value if hasattr(alg, "value") else str(alg) + for alg in algorithms_added + ], + [ + alg.value if hasattr(alg, "value") else str(alg) + for alg in algorithms_removed + ], + ) + + # Signal configuration change for dynamic entity management + signal_key = f"bt_config_changed_{self._config_entry.entry_id}" + dispatcher_send( + self.hass, signal_key, {"entry_id": self._config_entry.entry_id} + ) + + def _get_active_algorithms(self, config: Mapping[str, Any]) -> set: + """Get set of calibration algorithms currently in use by any TRV.""" + if not config or CONF_HEATER not in config: + return set() + + active_algorithms = set() + for trv in config.get(CONF_HEATER, []): + advanced = trv.get("advanced", {}) + calibration_mode = advanced.get(CONF_CALIBRATION_MODE) + if calibration_mode: + # Konvertiere String zu Enum falls nötig + if isinstance(calibration_mode, str): + try: + calibration_mode = CalibrationMode(calibration_mode) + except ValueError: + continue + active_algorithms.add(calibration_mode) + + return active_algorithms diff --git a/custom_components/better_thermostat/device_trigger.py b/custom_components/better_thermostat/device_trigger.py index 4f9523d..1216ed7 100644 --- a/custom_components/better_thermostat/device_trigger.py +++ b/custom_components/better_thermostat/device_trigger.py @@ -1,19 +1,37 @@ +"""Device trigger helpers for Better Thermostat. + +This module implements the device trigger helpers and validators used by +Home Assistant's device trigger integration for Better Thermostat climate +devices. + +Purpose-specific triggers (HA 2025.12+): + - heating_active hvac_action becomes "heating" + - heating_stopped hvac_action leaves "heating" + - humidity_high humidity exceeds a configurable threshold (default 60 %) + - window_opened window_open attribute becomes True + - window_closed window_open attribute becomes False + - battery_low minimum TRV battery level drops below threshold (default 20 %) + - device_error at least one device error is present + - target_temp_reached current temperature reaches the target temperature + +Classic triggers (kept for backwards compatibility): + - hvac_mode_changed + - current_temperature_changed + - current_humidity_changed +""" + from __future__ import annotations -import voluptuous as vol -from homeassistant.core import CALLBACK_TYPE, HomeAssistant -from homeassistant.helpers import config_validation as cv, entity_registry - +from homeassistant.components.climate.const import HVAC_MODES +from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA from homeassistant.components.homeassistant.triggers import ( numeric_state as numeric_state_trigger, state as state_trigger, ) -from . import DOMAIN -from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo -from homeassistant.helpers.typing import ConfigType -from homeassistant.components.climate.const import HVAC_MODES +from homeassistant.components.homeassistant.triggers.state import CONF_FROM, CONF_TO from homeassistant.const import ( CONF_ABOVE, + CONF_ATTRIBUTE, CONF_BELOW, CONF_DEVICE_ID, CONF_DOMAIN, @@ -23,46 +41,128 @@ from homeassistant.const import ( CONF_TYPE, PERCENTAGE, ) +from homeassistant.core import CALLBACK_TYPE, HomeAssistant +from homeassistant.helpers import config_validation as cv, entity_registry +from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo +from homeassistant.helpers.typing import ConfigType +import voluptuous as vol + +from . import DOMAIN + +# --------------------------------------------------------------------------- +# All supported trigger types +# --------------------------------------------------------------------------- + +# Purpose-specific (new in HA 2025.12) +_PURPOSE_TRIGGER_TYPES = { + "heating_active", + "heating_stopped", + "humidity_high", + "window_opened", + "window_closed", + "battery_low", + "device_error", + "target_temp_reached", +} + +# Legacy triggers (kept for backwards compatibility) +_CLASSIC_TRIGGER_TYPES = { + "hvac_mode_changed", + "current_temperature_changed", + "current_humidity_changed", +} + +TRIGGER_TYPES = _PURPOSE_TRIGGER_TYPES | _CLASSIC_TRIGGER_TYPES + +# --------------------------------------------------------------------------- +# Static TRIGGER_SCHEMA required by HA 2025.12 device automation framework. +# Extra fields are validated dynamically via async_get_trigger_capabilities. +# --------------------------------------------------------------------------- +TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend( + { + vol.Required(CONF_TYPE): vol.In(TRIGGER_TYPES), + # Fields used by classic triggers + vol.Optional(CONF_TO): vol.Any(str, [str]), + # Fields used by numeric triggers + vol.Optional(CONF_ABOVE): vol.Coerce(float), + vol.Optional(CONF_BELOW): vol.Coerce(float), + # Shared optional field + vol.Optional(CONF_FOR): cv.positive_time_period_dict, + } +) + +# --------------------------------------------------------------------------- +# Default threshold values +# --------------------------------------------------------------------------- +DEFAULT_HUMIDITY_THRESHOLD = 60.0 # % +DEFAULT_BATTERY_THRESHOLD = 20.0 # % +# Temperature delta at which "target reached" fires (current - target >= value) +TARGET_REACHED_DELTA = 0.0 # °C / °F async def async_get_triggers( hass: HomeAssistant, device_id: str -) -> list[dict[str, str]]: +) -> list[dict[str, str | dict[str, bool]]]: """List device triggers for Better Thermostat devices.""" registry = entity_registry.async_get(hass) - triggers = [] + triggers: list[dict[str, str | dict[str, bool]]] = [] - # Get all the integrations entities for this device for entry in entity_registry.async_entries_for_device(registry, device_id): if entry.domain != DOMAIN: continue - state = hass.states.get(entry.entity_id) - if not state: + if not hass.states.get(entry.entity_id): continue - base_trigger = { + base = { CONF_PLATFORM: "device", CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, } - # Add standard climate triggers + # ------------------------------------------------------------------ + # Purpose-specific triggers (primary – shown first in the UI) + # ------------------------------------------------------------------ + primary_types = [ + "heating_active", + "heating_stopped", + "window_opened", + "window_closed", + "target_temp_reached", + "device_error", + ] + for trigger_type in primary_types: + triggers.append( + {**base, CONF_TYPE: trigger_type, "metadata": {"secondary": False}} + ) + + # ------------------------------------------------------------------ + # Purpose-specific triggers (secondary – sensor / diagnostic info) + # ------------------------------------------------------------------ + secondary_types = ["humidity_high", "battery_low"] + for trigger_type in secondary_types: + triggers.append( + {**base, CONF_TYPE: trigger_type, "metadata": {"secondary": True}} + ) + + # ------------------------------------------------------------------ + # Classic / legacy triggers + # ------------------------------------------------------------------ triggers.extend( [ { - **base_trigger, + **base, CONF_TYPE: "hvac_mode_changed", - "metadata": {"secondary": False}, + "metadata": {"secondary": True}, }, { - **base_trigger, + **base, CONF_TYPE: "current_temperature_changed", - "metadata": {"secondary": False}, + "metadata": {"secondary": True}, }, { - **base_trigger, + **base, CONF_TYPE: "current_humidity_changed", "metadata": {"secondary": True}, }, @@ -78,15 +178,211 @@ async def async_attach_trigger( action: TriggerActionType, trigger_info: TriggerInfo, ) -> CALLBACK_TYPE: - """Attach a trigger.""" - if (trigger_type := config[CONF_TYPE]) == "hvac_mode_changed": + """Attach a trigger and return an unsubscribe callback.""" + trigger_type: str = config[CONF_TYPE] + entity_id: str = config[CONF_ENTITY_ID] + + # ------------------------------------------------------------------ + # Helpers + # ------------------------------------------------------------------ + def _build_state( + attribute: str, to: str | None = None, from_: str | None = None + ) -> dict: + cfg: dict = { + state_trigger.CONF_PLATFORM: "state", + state_trigger.CONF_ENTITY_ID: entity_id, + CONF_ATTRIBUTE: attribute, + } + if to is not None: + cfg[CONF_TO] = to + if from_ is not None: + cfg[CONF_FROM] = from_ + if CONF_FOR in config: + cfg[CONF_FOR] = config[CONF_FOR] + return cfg + + def _build_numeric(template: str) -> dict: + cfg: dict = { + numeric_state_trigger.CONF_PLATFORM: "numeric_state", + numeric_state_trigger.CONF_ENTITY_ID: entity_id, + numeric_state_trigger.CONF_VALUE_TEMPLATE: template, + } + if CONF_ABOVE in config: + cfg[CONF_ABOVE] = config[CONF_ABOVE] + if CONF_BELOW in config: + cfg[CONF_BELOW] = config[CONF_BELOW] + if CONF_FOR in config: + cfg[CONF_FOR] = config[CONF_FOR] + return cfg + + # ------------------------------------------------------------------ + # Purpose-specific trigger: heating_active + # Fires when hvac_action changes TO "heating". + # ------------------------------------------------------------------ + if trigger_type == "heating_active": + state_config = _build_state("hvac_action", to="heating") + state_config = await state_trigger.async_validate_trigger_config( + hass, state_config + ) + return await state_trigger.async_attach_trigger( + hass, state_config, action, trigger_info, platform_type="device" + ) + + # ------------------------------------------------------------------ + # Purpose-specific trigger: heating_stopped + # Fires when hvac_action changes FROM "heating" to anything else. + # ------------------------------------------------------------------ + if trigger_type == "heating_stopped": + state_config = _build_state("hvac_action", from_="heating") + state_config = await state_trigger.async_validate_trigger_config( + hass, state_config + ) + return await state_trigger.async_attach_trigger( + hass, state_config, action, trigger_info, platform_type="device" + ) + + # ------------------------------------------------------------------ + # Purpose-specific trigger: window_opened + # Fires when window_open attribute becomes truthy (True). + # Uses a numeric template to avoid bool→string comparison issues. + # ------------------------------------------------------------------ + if trigger_type == "window_opened": + numeric_config = { + numeric_state_trigger.CONF_PLATFORM: "numeric_state", + numeric_state_trigger.CONF_ENTITY_ID: entity_id, + numeric_state_trigger.CONF_VALUE_TEMPLATE: ( + "{{ 1 if state.attributes.get('window_open') else 0 }}" + ), + CONF_ABOVE: 0.5, + } + if CONF_FOR in config: + numeric_config[CONF_FOR] = config[CONF_FOR] + numeric_config = await numeric_state_trigger.async_validate_trigger_config( + hass, numeric_config + ) + return await numeric_state_trigger.async_attach_trigger( + hass, numeric_config, action, trigger_info, platform_type="device" + ) + + # ------------------------------------------------------------------ + # Purpose-specific trigger: window_closed + # Fires when window_open attribute becomes falsy (False / None). + # ------------------------------------------------------------------ + if trigger_type == "window_closed": + numeric_config = { + numeric_state_trigger.CONF_PLATFORM: "numeric_state", + numeric_state_trigger.CONF_ENTITY_ID: entity_id, + numeric_state_trigger.CONF_VALUE_TEMPLATE: ( + "{{ 1 if state.attributes.get('window_open') else 0 }}" + ), + CONF_BELOW: 0.5, + } + if CONF_FOR in config: + numeric_config[CONF_FOR] = config[CONF_FOR] + numeric_config = await numeric_state_trigger.async_validate_trigger_config( + hass, numeric_config + ) + return await numeric_state_trigger.async_attach_trigger( + hass, numeric_config, action, trigger_info, platform_type="device" + ) + + # ------------------------------------------------------------------ + # Purpose-specific trigger: humidity_high + # Fires when the BT humidity attribute exceeds the threshold. + # Threshold is configurable (CONF_ABOVE); default is DEFAULT_HUMIDITY_THRESHOLD. + # ------------------------------------------------------------------ + if trigger_type == "humidity_high": + numeric_config = _build_numeric( + "{{ state.attributes.get('humidity', 0) | float(0) }}" + ) + if CONF_ABOVE not in numeric_config: + numeric_config[CONF_ABOVE] = DEFAULT_HUMIDITY_THRESHOLD + numeric_config = await numeric_state_trigger.async_validate_trigger_config( + hass, numeric_config + ) + return await numeric_state_trigger.async_attach_trigger( + hass, numeric_config, action, trigger_info, platform_type="device" + ) + + # ------------------------------------------------------------------ + # Purpose-specific trigger: battery_low + # Fires when the minimum TRV battery level drops below the threshold. + # Threshold is configurable (CONF_BELOW); default is DEFAULT_BATTERY_THRESHOLD. + # Template extracts the minimum 'battery' value from the batteries JSON dict. + # ------------------------------------------------------------------ + if trigger_type == "battery_low": + battery_template = ( + "{%- set bat = state.attributes.get('batteries', '{}') | from_json -%}" + "{%- set levels = bat.values() | map(attribute='battery') | reject('none') | list -%}" + "{{ (levels | min) if levels else 101 }}" + ) + numeric_config = _build_numeric(battery_template) + if CONF_BELOW not in numeric_config: + numeric_config[CONF_BELOW] = DEFAULT_BATTERY_THRESHOLD + numeric_config = await numeric_state_trigger.async_validate_trigger_config( + hass, numeric_config + ) + return await numeric_state_trigger.async_attach_trigger( + hass, numeric_config, action, trigger_info, platform_type="device" + ) + + # ------------------------------------------------------------------ + # Purpose-specific trigger: device_error + # Fires when the errors attribute contains at least one entry. + # ------------------------------------------------------------------ + if trigger_type == "device_error": + error_template = ( + "{{ (state.attributes.get('errors', '[]') | from_json | length) }}" + ) + numeric_config = { + numeric_state_trigger.CONF_PLATFORM: "numeric_state", + numeric_state_trigger.CONF_ENTITY_ID: entity_id, + numeric_state_trigger.CONF_VALUE_TEMPLATE: error_template, + CONF_ABOVE: 0, + } + if CONF_FOR in config: + numeric_config[CONF_FOR] = config[CONF_FOR] + numeric_config = await numeric_state_trigger.async_validate_trigger_config( + hass, numeric_config + ) + return await numeric_state_trigger.async_attach_trigger( + hass, numeric_config, action, trigger_info, platform_type="device" + ) + + # ------------------------------------------------------------------ + # Purpose-specific trigger: target_temp_reached + # Fires when current_temperature >= target_temperature. + # The template computes (current - target); triggers when value >= TARGET_REACHED_DELTA. + # ------------------------------------------------------------------ + if trigger_type == "target_temp_reached": + reached_template = ( + "{{ (state.attributes.get('current_temperature', 0) | float(0))" + " - (state.attributes.get('temperature', 0) | float(0)) }}" + ) + numeric_config = { + numeric_state_trigger.CONF_PLATFORM: "numeric_state", + numeric_state_trigger.CONF_ENTITY_ID: entity_id, + numeric_state_trigger.CONF_VALUE_TEMPLATE: reached_template, + CONF_ABOVE: TARGET_REACHED_DELTA, + } + if CONF_FOR in config: + numeric_config[CONF_FOR] = config[CONF_FOR] + numeric_config = await numeric_state_trigger.async_validate_trigger_config( + hass, numeric_config + ) + return await numeric_state_trigger.async_attach_trigger( + hass, numeric_config, action, trigger_info, platform_type="device" + ) + + # ------------------------------------------------------------------ + # Classic trigger: hvac_mode_changed + # ------------------------------------------------------------------ + if trigger_type == "hvac_mode_changed": state_config = { state_trigger.CONF_PLATFORM: "state", - state_trigger.CONF_ENTITY_ID: config[CONF_ENTITY_ID], - state_trigger.CONF_TO: config[state_trigger.CONF_TO], - state_trigger.CONF_FROM: [ - mode for mode in HVAC_MODES if mode != config[state_trigger.CONF_TO] - ], + state_trigger.CONF_ENTITY_ID: entity_id, + CONF_TO: config[CONF_TO], + CONF_FROM: [mode for mode in HVAC_MODES if mode != config[CONF_TO]], } if CONF_FOR in config: state_config[CONF_FOR] = config[CONF_FOR] @@ -97,53 +393,97 @@ async def async_attach_trigger( hass, state_config, action, trigger_info, platform_type="device" ) - numeric_state_config = { - numeric_state_trigger.CONF_PLATFORM: "numeric_state", - numeric_state_trigger.CONF_ENTITY_ID: config[CONF_ENTITY_ID], - } - + # ------------------------------------------------------------------ + # Classic triggers: current_temperature_changed / current_humidity_changed + # ------------------------------------------------------------------ if trigger_type == "current_temperature_changed": - numeric_state_config[numeric_state_trigger.CONF_VALUE_TEMPLATE] = ( - "{{ state.attributes.current_temperature }}" - ) + template = "{{ state.attributes.current_temperature }}" else: - numeric_state_config[numeric_state_trigger.CONF_VALUE_TEMPLATE] = ( - "{{ state.attributes.current_humidity }}" - ) + template = "{{ state.attributes.current_humidity }}" - if CONF_ABOVE in config: - numeric_state_config[CONF_ABOVE] = config[CONF_ABOVE] - if CONF_BELOW in config: - numeric_state_config[CONF_BELOW] = config[CONF_BELOW] - if CONF_FOR in config: - numeric_state_config[CONF_FOR] = config[CONF_FOR] - - numeric_state_config = await numeric_state_trigger.async_validate_trigger_config( - hass, numeric_state_config + numeric_config = _build_numeric(template) + numeric_config = await numeric_state_trigger.async_validate_trigger_config( + hass, numeric_config ) return await numeric_state_trigger.async_attach_trigger( - hass, numeric_state_config, action, trigger_info, platform_type="device" + hass, numeric_config, action, trigger_info, platform_type="device" ) async def async_get_trigger_capabilities( hass: HomeAssistant, config: ConfigType ) -> dict[str, vol.Schema]: - """List trigger capabilities.""" + """List trigger capabilities (extra fields shown in the automation editor).""" trigger_type = config[CONF_TYPE] - if trigger_type == "hvac_mode_changed": + # ------------------------------------------------------------------ + # Triggers with a "for" duration option only + # ------------------------------------------------------------------ + if trigger_type in { + "heating_active", + "heating_stopped", + "window_opened", + "window_closed", + "device_error", + "target_temp_reached", + }: + return { + "extra_fields": vol.Schema( + {vol.Optional(CONF_FOR): cv.positive_time_period_dict} + ) + } + + # ------------------------------------------------------------------ + # humidity_high: configurable threshold + duration + # ------------------------------------------------------------------ + if trigger_type == "humidity_high": return { "extra_fields": vol.Schema( { - vol.Required(state_trigger.CONF_TO): vol.In(HVAC_MODES), + vol.Optional( + CONF_ABOVE, + description={"suffix": PERCENTAGE}, + default=DEFAULT_HUMIDITY_THRESHOLD, + ): vol.Coerce(float), vol.Optional(CONF_FOR): cv.positive_time_period_dict, } ) } - # Temperature and humidity triggers use the same schema - if trigger_type in ["current_temperature_changed", "current_humidity_changed"]: + # ------------------------------------------------------------------ + # battery_low: configurable threshold + duration + # ------------------------------------------------------------------ + if trigger_type == "battery_low": + return { + "extra_fields": vol.Schema( + { + vol.Optional( + CONF_BELOW, + description={"suffix": PERCENTAGE}, + default=DEFAULT_BATTERY_THRESHOLD, + ): vol.Coerce(float), + vol.Optional(CONF_FOR): cv.positive_time_period_dict, + } + ) + } + + # ------------------------------------------------------------------ + # Classic trigger: hvac_mode_changed + # ------------------------------------------------------------------ + if trigger_type == "hvac_mode_changed": + return { + "extra_fields": vol.Schema( + { + vol.Required(CONF_TO): vol.In(HVAC_MODES), + vol.Optional(CONF_FOR): cv.positive_time_period_dict, + } + ) + } + + # ------------------------------------------------------------------ + # Classic triggers: temperature / humidity value thresholds + # ------------------------------------------------------------------ + if trigger_type in {"current_temperature_changed", "current_humidity_changed"}: unit = ( hass.config.units.temperature_unit if trigger_type == "current_temperature_changed" diff --git a/custom_components/better_thermostat/diagnostics.py b/custom_components/better_thermostat/diagnostics.py index 672913f..885ee78 100644 --- a/custom_components/better_thermostat/diagnostics.py +++ b/custom_components/better_thermostat/diagnostics.py @@ -3,7 +3,7 @@ from __future__ import annotations from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, State from .utils.const import CONF_HEATER, CONF_SENSOR, CONF_SENSOR_WINDOW @@ -31,7 +31,7 @@ async def async_get_config_entry_diagnostics( } external_temperature = hass.states.get(config_entry.data[CONF_SENSOR]) - window = "-" + window: str | State | None = "-" window_entity_id = config_entry.data.get(CONF_SENSOR_WINDOW, False) if window_entity_id: try: diff --git a/custom_components/better_thermostat/events/cooler.py b/custom_components/better_thermostat/events/cooler.py index dfb2f8b..eb19bef 100644 --- a/custom_components/better_thermostat/events/cooler.py +++ b/custom_components/better_thermostat/events/cooler.py @@ -1,16 +1,16 @@ -import asyncio +"""Cooler event handlers for the Better Thermostat integration. + +Contains the event handler that reacts to changes in the configured cooler +entity and updates the integration state accordingly. +""" + import logging -from homeassistant.components.climate.const import ( - ATTR_HVAC_ACTION, - HVACAction, - HVACMode, -) + +from homeassistant.components.climate.const import HVACMode from homeassistant.core import State, callback -from homeassistant.components.group.util import find_state_attributes from custom_components.better_thermostat.utils.helpers import convert_to_float - _LOGGER = logging.getLogger(__name__) @@ -21,21 +21,33 @@ async def trigger_cooler_change(self, event): return if self.control_queue_task is None: return - asyncio.create_task(update_hvac_action(self)) _main_change = False old_state = event.data.get("old_state") new_state = event.data.get("new_state") entity_id = event.data.get("entity_id") - if None in (new_state, old_state, new_state.attributes): + if new_state is None or old_state is None: _LOGGER.debug( - f"better_thermostat {self.device_name}: Cooler {entity_id} update contained not all necessary data for processing, skipping" + "better_thermostat %s: Cooler %s update contained not all " + "necessary data for processing, skipping", + self.device_name, + entity_id, ) return if not isinstance(new_state, State) or not isinstance(old_state, State): _LOGGER.debug( - f"better_thermostat {self.device_name}: Cooler {entity_id} update contained not a State, skipping" + "better_thermostat %s: Cooler %s update contained not a State, skipping", + self.device_name, + entity_id, + ) + return + + if new_state.attributes is None: + _LOGGER.debug( + "better_thermostat %s: Cooler %s update had no attributes, skipping", + self.device_name, + entity_id, ) return # set context HACK TO FIND OUT IF AN EVENT WAS SEND BY BT @@ -45,37 +57,43 @@ async def trigger_cooler_change(self, event): return _LOGGER.debug( - f"better_thermostat {self.device_name}: Cooler {entity_id} update received" + "better_thermostat %s: Cooler %s update received", self.device_name, entity_id ) - _main_key = "temperature" - if "temperature" not in old_state.attributes: - _main_key = "target_temp_high" + def _get_cooling_setpoint(state): + """Extract cooling setpoint from state, checking both attribute keys.""" + for key in ("temperature", "target_temp_high"): + if key in state.attributes: + return convert_to_float( + str(state.attributes[key]), + self.device_name, + "trigger_cooler_change()", + ) + return None - _old_cooling_setpoint = convert_to_float( - str(old_state.attributes.get(_main_key, None)), - self.device_name, - "trigger_cooler_change()", - ) - _new_cooling_setpoint = convert_to_float( - str(new_state.attributes.get(_main_key, None)), - self.device_name, - "trigger_cooler_change()", - ) + _old_cooling_setpoint = _get_cooling_setpoint(old_state) + _new_cooling_setpoint = _get_cooling_setpoint(new_state) if ( _new_cooling_setpoint is not None and _old_cooling_setpoint is not None - and self.bt_hvac_mode is not HVACMode.OFF + and self.bt_hvac_mode != HVACMode.OFF ): _LOGGER.debug( - f"better_thermostat {self.device_name}: trigger_cooler_change / _old_cooling_setpoint: {_old_cooling_setpoint} - _new_cooling_setpoint: {_new_cooling_setpoint}" + "better_thermostat %s: trigger_cooler_change / " + "_old_cooling_setpoint: %s - _new_cooling_setpoint: %s", + self.device_name, + _old_cooling_setpoint, + _new_cooling_setpoint, ) if ( _new_cooling_setpoint < self.bt_min_temp or self.bt_max_temp < _new_cooling_setpoint ): _LOGGER.warning( - f"better_thermostat {self.device_name}: New Cooler {entity_id} setpoint outside of range, overwriting it" + "better_thermostat %s: New Cooler %s setpoint outside of range, " + "overwriting it", + self.device_name, + entity_id, ) if _new_cooling_setpoint < self.bt_min_temp: @@ -85,7 +103,10 @@ async def trigger_cooler_change(self, event): self.bt_target_cooltemp = _new_cooling_setpoint if self.bt_target_temp >= self.bt_target_cooltemp: - self.bt_target_temp = self.bt_target_cooltemp - self.bt_target_temp_step + self.bt_target_temp = max( + self.bt_target_cooltemp - max(self.bt_target_temp_step or 0.5, 0.5), + self.bt_min_temp, + ) _main_change = True if _main_change is True: @@ -93,37 +114,3 @@ async def trigger_cooler_change(self, event): return await self.control_queue_task.put(self) self.async_write_ha_state() return - - -async def update_hvac_action(self): - """Update the hvac action.""" - if self.startup_running or self.control_queue_task is None: - return - - hvac_action = None - states = [ - state - for entity_id in [self.cooler_entity_id] - if (state := self.hass.states.get(entity_id)) is not None - ] - - hvac_actions = list(find_state_attributes(states, ATTR_HVAC_ACTION)) - - if not hvac_actions: - self.attr_hvac_action = None - elif all(a == HVACAction.OFF for a in hvac_actions): - hvac_action = HVACAction.OFF - elif self.bt_target_cooltemp < self.cur_temp and self.window_open is False: - hvac_action = HVACAction.COOLING - elif ( - self.bt_target_cooltemp < self.cur_temp - and self.attr_hvac_action == HVACAction.COOLING - and self.window_open is False - ): - hvac_action = HVACAction.COOLING - else: - hvac_action = HVACAction.IDLE - - if self.hvac_action != hvac_action: - self.attr_hvac_action = hvac_action - await self.async_update_ha_state(force_refresh=True) diff --git a/custom_components/better_thermostat/events/temperature.py b/custom_components/better_thermostat/events/temperature.py index 6ea067b..fe2b07d 100644 --- a/custom_components/better_thermostat/events/temperature.py +++ b/custom_components/better_thermostat/events/temperature.py @@ -1,16 +1,165 @@ -import logging +"""External temperature event handlers for Better Thermostat. -from custom_components.better_thermostat.utils.const import CONF_HOMEMATICIP -from ..utils.helpers import convert_to_float -from datetime import datetime -from homeassistant.helpers import issue_registry as ir +This module includes logic to handle external temperature updates and apply +debounce, anti-flicker, accumulation, and plateau acceptance heuristics used +to make robust decisions about whether the external temperature should be +propagated to the target devices. +""" + +import logging +import math +from time import monotonic from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN from homeassistant.core import callback +from homeassistant.helpers import issue_registry as ir +from homeassistant.helpers.event import async_call_later +from homeassistant.util import dt as dt_util + +from custom_components.better_thermostat.utils.const import CONF_HOMEMATICIP +from custom_components.better_thermostat.utils.helpers import ( + convert_to_float_celsius, + is_reasonable_temperature, +) _LOGGER = logging.getLogger(__name__) +# is ignored for this time window (seconds). +FLICKER_REVERT_WINDOW = 45 # can optionally be made configurable later +# Accept sub-threshold changes if the new value stays stable for this window (seconds) +PLATEAU_ACCEPT_WINDOW = 120 + + +def _update_external_temp_ema(self, temp_q: float) -> float: + """Update and return EMA-filtered external temperature. + + Uses a time-based EMA so varying sensor update intervals behave sensibly. + + Tunables (optional attributes on `self`): + - `external_temp_ema_tau_s` (float): time constant in seconds (e.g. 900=15min, 1800=30min) + """ + + tau_s = float(self.external_temp_ema_tau_s or 300.0) + if tau_s <= 0: + tau_s = 300.0 + + now_m = monotonic() + prev_ts = self._external_temp_ema_ts + prev_ema = self.external_temp_ema + + if prev_ts is None or prev_ema is None: + ema = float(temp_q) + else: + dt_s = max(0.0, float(now_m) - float(prev_ts)) + # alpha = 1 - exp(-dt/tau) + alpha = 1.0 - math.exp(-dt_s / tau_s) if dt_s > 0 else 0.0 + ema = float(prev_ema) + alpha * (float(temp_q) - float(prev_ema)) + + _LOGGER.debug( + "better_thermostat %s: EMA calc: prev=%.3f input=%.3f dt=%.1fs alpha=%.4f -> new=%.3f", + self.device_name, + float(prev_ema), + float(temp_q), + dt_s, + alpha, + ema, + ) + + self._external_temp_ema_ts = now_m + self.external_temp_ema = ema + # Expose a generic name so consumers don't need to know EMA vs SMA + self.cur_temp_filtered = round(float(ema), 2) + return float(ema) + + +async def _apply_temperature_update(self, new_temp): + """Apply the new external temperature and trigger updates.""" + _LOGGER.debug( + "better_thermostat %s: _apply_temperature_update called with %.2f", + self.device_name, + new_temp, + ) + _cur_q = None if self.cur_temp is None else round(self.cur_temp, 2) + new_temp_q = round(new_temp, 2) + + # Remember previous value as stable pre-measure before updating + if _cur_q is not None and _cur_q != new_temp_q: + self.prev_stable_temp = _cur_q + # Richtung merken (nur bei echter Änderung) + if _cur_q is not None: + if new_temp_q > _cur_q: + self.last_change_direction = 1 + elif new_temp_q < _cur_q: + self.last_change_direction = -1 + self.cur_temp = new_temp_q + self.last_known_external_temp = new_temp_q + # Update EMA (useful if called from timer after delay) + try: + _update_external_temp_ema(self, float(new_temp_q)) + except Exception as exc: + _LOGGER.debug( + "better_thermostat %s: EMA update failed (non-critical): %s", + self.device_name, + exc, + ) + _ema = self.external_temp_ema + self.last_external_sensor_change = dt_util.now() + # Reset accumulation & pending after accept + self.accum_delta = 0.0 + self.accum_dir = 0 + self.pending_temp = None + self.pending_since = None + # Cancel any pending plateau timer + if getattr(self, "plateau_timer_cancel", None) is not None: + self.plateau_timer_cancel() + self.plateau_timer_cancel = None + self.async_write_ha_state() + if _ema is not None: + _LOGGER.debug( + "better_thermostat %s: external_temperature filtered (ema_tau_s=%s) raw=%.2f ema=%.2f", + self.device_name, + self.external_temp_ema_tau_s, + float(new_temp_q), + float(_ema), + ) + # Schreibe den von BT verwendeten Wert (self.cur_temp) ins TRV + try: + trv_ids = list(self.real_trvs.keys()) + if not trv_ids and hasattr(self, "entity_ids"): + trv_ids = list(self.entity_ids or []) + for trv_id in trv_ids: + quirks = ( + self.real_trvs.get(trv_id, {}).get("model_quirks") + if hasattr(self, "real_trvs") + else None + ) + if quirks and hasattr(quirks, "maybe_set_external_temperature"): + await quirks.maybe_set_external_temperature(self, trv_id, self.cur_temp) + else: + _LOGGER.debug( + "better_thermostat %s: no quirks with maybe_set_external_temperature for %s", + self.device_name, + trv_id, + ) + except (AttributeError, KeyError, TypeError, ValueError, RuntimeError): + _LOGGER.debug( + "better_thermostat %s: external_temperature write to TRV failed (non critical)", + self.device_name, + ) + # Enqueue control action (skip during valve maintenance to avoid overwriting exercise). + # Still mark that a control cycle is needed after maintenance so we immediately + # resume with the latest temperature. + if self.control_queue_task is not None: + if getattr(self, "in_maintenance", False): + self._control_needed_after_maintenance = True + else: + await self.control_queue_task.put(self) + _LOGGER.debug( + "better_thermostat %s: _apply_temperature_update finished", self.device_name + ) + + @callback async def trigger_temperature_change(self, event): """Handle temperature changes. @@ -33,49 +182,379 @@ async def trigger_temperature_change(self, event): if new_state is None or new_state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN, None): return - _incoming_temperature = convert_to_float( - str(new_state.state), self.device_name, "external_temperature" + _incoming_temperature = convert_to_float_celsius( + str(new_state.state), + self.device_name, + "external_temperature", + unit_of_measurement=new_state.attributes.get("unit_of_measurement"), + ) + # Quantisiere auf 2 Dezimalstellen, um FP-Artefakte zu vermeiden + _incoming_temperature_q = ( + None if _incoming_temperature is None else round(_incoming_temperature, 2) ) - _time_diff = 60 + # Ensure timestamp exists (first run guard) + if self.last_external_sensor_change is None: + # Setze einen alten Zeitpunkt, damit erste Änderung akzeptiert wird + self.last_external_sensor_change = dt_util.now() + + # Basis-Debounce (Sekunden) für normale Geräte; durch Anti-Flicker können wir hier auf 5s runter + # gesetzt werden. HomematicIP erhält unten weiterhin ein höheres Intervall (600s). + _time_diff = 5 + # Signifikanz-Schwelle: 0.11°C (um 0.1°C Rauschen zu filtern). + # Wir ignorieren die Toleranz-Einstellung hier, um auch bei größerer Regel-Toleranz + # präzise Sensor-Updates zu erhalten. + _sig_threshold = 0.11 try: for trv in self.all_trvs: if trv["advanced"][CONF_HOMEMATICIP]: _time_diff = 600 - except KeyError: + except (KeyError, TypeError): pass - if _incoming_temperature is None or _incoming_temperature < -50: - # raise a ha repair notication + if not is_reasonable_temperature(_incoming_temperature_q): + # raise a ha repair notification _LOGGER.error( - "better_thermostat %s: external_temperature is not a valid number: %s", + "better_thermostat %s: external_temperature %s is outside the " + "plausible range; ignoring (raw state: %s)", self.device_name, + _incoming_temperature_q, new_state.state, ) + # Minimal kompatibler Aufruf (Parameter-Namen angepasst an aktuelle HA API) ir.async_create_issue( hass=self.hass, - issue_id=f"missing_entity_{self.device_name}", - issue_title=f"better_thermostat {self.device_name} has invalid external_temperature value", - issue_severity="error", - issue_description=f"better_thermostat {self.device_name} has invalid external_temperature: {new_state.state}", - issue_category="config", - issue_suggested_action="Please check the external_temperature sensor", + domain="better_thermostat", + issue_id=f"invalid_external_temperature_{self.device_name}", + is_fixable=False, + severity=ir.IssueSeverity.ERROR, + translation_key="invalid_external_temperature", + translation_placeholders={ + "name": self.device_name, + "value": str(new_state.state), + }, ) return + _now = dt_util.now() + try: + _age = (_now - self.last_external_sensor_change).total_seconds() + except (TypeError, AttributeError): # defensiv, sollte nicht auftreten + _age = 999999 + # Gerundete Vergleichswerte + _cur_q = None if self.cur_temp is None else round(self.cur_temp, 2) + _diff = None if _cur_q is None else abs(_incoming_temperature_q - _cur_q) + # Quantisierte Differenz zur robusten Schwellenprüfung (vermeidet 0.099999-Fehler) + _diff_q = None if _diff is None else round(_diff, 2) + _sig_threshold_q = round(_sig_threshold, 2) + _is_significant = _cur_q is None or ( + _diff_q is not None and _diff_q >= _sig_threshold_q + ) + _interval_ok = _age > _time_diff + + # Anti-Flicker: Wenn der neue Wert exakt dem vorherigen stabilen Wert entspricht + # (also ein schneller Rücksprung) UND wir kürzlich erst umgestellt haben, + # dann ignorieren wir diesen Rücksprung bis das Fenster abläuft. if ( - _incoming_temperature != self.cur_temp - and (datetime.now() - self.last_external_sensor_change).total_seconds() - > _time_diff + False # Anti-flicker disabled + and _cur_q is not None + and self.prev_stable_temp is not None + and _incoming_temperature_q == round(self.prev_stable_temp, 2) + and _incoming_temperature_q != _cur_q + and _age < FLICKER_REVERT_WINDOW ): - _LOGGER.debug( - "better_thermostat %s: external_temperature changed from %s to %s", - self.device_name, - self.cur_temp, - _incoming_temperature, + # Plane eine Übernahme nach Ablauf des Revert-Fensters, falls der Sensorwert stabil bleibt + try: + remaining = max(0.0, float(FLICKER_REVERT_WINDOW) - float(_age)) + except (ValueError, TypeError): + remaining = float(FLICKER_REVERT_WINDOW) + # Merke Kandidatenwert und cancel ggf. vorherige Planung + cancel_cb = self.flicker_unignore_cancel + if callable(cancel_cb): + cancel_cb() + self.flicker_unignore_cancel = None + self.flicker_candidate = _incoming_temperature_q + + def _deadline_cb(_now): # executed by HA loop, schedule async body + async def _apply_if_stable(): + try: + # Prüfe aktuellen Sensor-Status + sensor_id = self.sensor_entity_id + state = self.hass.states.get(sensor_id) if sensor_id else None + if state is None or state.state in ( + STATE_UNAVAILABLE, + STATE_UNKNOWN, + None, + ): + return + _val = convert_to_float_celsius( + str(state.state), + self.device_name, + "external_temperature", + unit_of_measurement=state.attributes.get("unit_of_measurement"), + ) + _val_q = None if _val is None else round(_val, 2) + cand = self.flicker_candidate + # Übernehme nur, wenn Kandidatwert unverändert und ungleich cur_temp ist + if _val_q is not None and cand is not None and _val_q == cand: + if _val_q != self.cur_temp: + _LOGGER.debug( + "better_thermostat %s: external_temperature flicker revert auto-accepted after %ss (value=%.2f)", + self.device_name, + FLICKER_REVERT_WINDOW, + _val_q, + ) + # Akzeptiere Wert wie im normalen Pfad + _prev = self.cur_temp + if _prev is not None and _prev != _val_q: + self.prev_stable_temp = _prev + if _val_q > _prev: + self.last_change_direction = 1 + elif _val_q < _prev: + self.last_change_direction = -1 + self.cur_temp = _val_q + try: + _update_external_temp_ema(self, float(_val_q)) + except Exception: + _LOGGER.debug( + "better_thermostat %s: external_temperature EMA update failed (non critical)", + self.device_name, + ) + self.last_external_sensor_change = dt_util.now() + # Reset Anti-Flicker-Akkumulatoren + self.accum_delta = 0.0 + self.accum_dir = 0 + self.accum_since = dt_util.now() + self.pending_temp = None + self.pending_since = None + self.async_write_ha_state() + # Schreibe TRV-External-Temp über Quirks, falls vorhanden + try: + trv_ids = list(self.real_trvs.keys()) + if not trv_ids and hasattr(self, "entity_ids"): + trv_ids = list(self.entity_ids or []) + for trv_id in trv_ids: + quirks = ( + self.real_trvs.get(trv_id, {}).get( + "model_quirks" + ) + if hasattr(self, "real_trvs") + else None + ) + if quirks and hasattr( + quirks, "maybe_set_external_temperature" + ): + await quirks.maybe_set_external_temperature( + self, trv_id, self.cur_temp + ) + except ( + AttributeError, + KeyError, + TypeError, + ValueError, + RuntimeError, + ): + _LOGGER.debug( + "better_thermostat %s: external_temperature write to TRV failed (non critical)", + self.device_name, + ) + if self.control_queue_task is not None: + await self.control_queue_task.put(self) + finally: + # Aufräumen + self.flicker_unignore_cancel = None + self.flicker_candidate = None + + # schedule the async part + self.hass.loop.create_task(_apply_if_stable()) + + self.flicker_unignore_cancel = async_call_later( + self.hass, remaining + 0.1, _deadline_cb + ) + _LOGGER.debug( + "better_thermostat %s: external_temperature flicker revert ignored (current=%.2f revert=%.2f age=%.1fs < %ss)", + self.device_name, + _cur_q, + _incoming_temperature_q, + _age, + FLICKER_REVERT_WINDOW, + ) + return + + # Richtungswechsel-Schutz: kleine Gegenbewegungen (<= Schwellwert) innerhalb des Flicker-Fensters ignorieren, + # um Ping-Pong um genau 0.10°C zu vermeiden. + _dir_now = 0 + if _cur_q is not None: + if _incoming_temperature_q > _cur_q: + _dir_now = 1 + elif _incoming_temperature_q < _cur_q: + _dir_now = -1 + _last_dir = self.last_change_direction + _block_flip_small = ( + False # Anti-flicker disabled # noqa: PLR1714 + and _dir_now != 0 + and _last_dir != 0 + and _dir_now != _last_dir + and _diff_q is not None + and _diff_q <= _sig_threshold_q + and _age < FLICKER_REVERT_WINDOW + ) + + if _block_flip_small: + _LOGGER.debug( + "better_thermostat %s: external_temperature opposite-direction change ignored " + "(current=%.2f new=%.2f diff=%.2f age=%.1fs <= %ss threshold=%.2f)", + self.device_name, + (_cur_q if _cur_q is not None else float("nan")), + _incoming_temperature_q, + (_diff if _diff is not None else float("nan")), + _age, + FLICKER_REVERT_WINDOW, + _sig_threshold_q, + ) + return + + # Slope calculation (simple delta per minute) + # Disabled in favor of periodic EMA-based slope calculation in climate.py + # try: + # now_m = monotonic() + # _last_ts = getattr(self, "_slope_last_ts", None) + # if _last_ts is not None and _cur_q is not None: + # dt_min = max(1e-6, (now_m - _last_ts) / 60.0) + # dT = _incoming_temperature_q - _cur_q # K + # inst_slope = dT / dt_min # K/min + # # light smoothing + # if getattr(self, "temp_slope", None) is None: + # self.temp_slope = inst_slope + # else: + # self.temp_slope = 0.7 * self.temp_slope + 0.3 * inst_slope + # setattr(self, "_slope_last_ts", now_m) + # except (AttributeError, TypeError, ZeroDivisionError): + # pass + + # Accumulation of small changes in the same direction + _accept_reason = None + if _cur_q is not None: + _signed_delta = round(_incoming_temperature_q - _cur_q, 2) + if _signed_delta != 0: + # set direction from sign + _acc_dir_now = 1 if _signed_delta > 0 else -1 + if self.accum_dir in (0, _acc_dir_now): + self.accum_delta = round(self.accum_delta + _signed_delta, 2) + self.accum_dir = _acc_dir_now if self.accum_dir == 0 else self.accum_dir + else: + # direction flipped: reset accumulation to current delta + self.accum_delta = _signed_delta + self.accum_dir = _acc_dir_now + # Plateau tracking + if self.pending_temp != _incoming_temperature_q: + self.pending_temp = _incoming_temperature_q + self.pending_since = dt_util.now() + # Cancel existing timer if pending value changes + if getattr(self, "plateau_timer_cancel", None) is not None: + self.plateau_timer_cancel() + self.plateau_timer_cancel = None + # no change (value back to current): reset pending/timer + elif self.pending_temp is not None: + self.pending_temp = None + self.pending_since = None + if getattr(self, "plateau_timer_cancel", None) is not None: + self.plateau_timer_cancel() + self.plateau_timer_cancel = None + + _accum_ok = ( + _cur_q is not None + and abs(self.accum_delta) >= _sig_threshold_q + and _interval_ok + ) + + # Plateau acceptance: sub-threshold change persisted long enough + _plateau_ok = False + if ( + not _is_significant + and _cur_q is not None + and self.pending_temp is not None + and self.pending_temp != _cur_q + and self.pending_since is not None + ): + _plateau_age = (dt_util.now() - self.pending_since).total_seconds() + _plateau_ok = _plateau_age >= PLATEAU_ACCEPT_WINDOW and _interval_ok + + # Schedule timer if not already scheduled + if not _plateau_ok and getattr(self, "plateau_timer_cancel", None) is None: + remaining = max(0.1, PLATEAU_ACCEPT_WINDOW - _plateau_age) + + async def _plateau_cb(_now): + self.plateau_timer_cancel = None + # Re-check debounce interval so HomematicIP 600s is respected + _cb_age = ( + (dt_util.now() - self.last_external_sensor_change).total_seconds() + if self.last_external_sensor_change is not None + else 999999 + ) + _cb_interval_ok = _cb_age > _time_diff + if self.pending_temp is not None and _cb_interval_ok: + _LOGGER.debug( + "better_thermostat %s: external_temperature plateau auto-accepted (value=%.2f)", + self.device_name, + self.pending_temp, + ) + await _apply_temperature_update(self, self.pending_temp) + + self.plateau_timer_cancel = async_call_later( + self.hass, remaining, _plateau_cb + ) + + if _cur_q is None: + # First reading ever — always accept regardless of interval + _accept_reason = "first_reading" + elif _is_significant and _interval_ok: + _accept_reason = "significant" + elif _accum_ok: + _accept_reason = "accumulated" + elif _plateau_ok: + _accept_reason = "plateau" + + if _accept_reason is not None: + # Verarbeite sofort, wenn Intervall abgelaufen ODER Änderung sehr groß + _LOGGER.debug( + "better_thermostat %s: external_temperature update accepted (old=%.2f new=%.2f diff=%.2f " + "age=%.1fs threshold=%.2f interval=%ss reason=%s accum=%.2f dir=%s)", + self.device_name, + (_cur_q if _cur_q is not None else float("nan")), + _incoming_temperature_q, + (_diff_q if _diff_q is not None else float("nan")), + _age, + _sig_threshold_q, + _time_diff, + _accept_reason, + (self.accum_delta if _cur_q is not None else 0.0), + ("+" if self.accum_dir > 0 else ("-" if self.accum_dir < 0 else "0")), + ) + await _apply_temperature_update(self, _incoming_temperature_q) + else: + _LOGGER.debug( + "better_thermostat %s: external_temperature ignored (old=%.2f new=%.2f diff=%s " + "age=%.1fs sig=%s interval_ok=%s threshold=%.2f accum=%.2f dir=%s pending=%s pending_age=%ss)", + self.device_name, + (_cur_q if _cur_q is not None else float("nan")), + _incoming_temperature_q, + (f"{_diff_q:.2f}" if _diff_q is not None else "None"), + _age, + _is_significant, + _interval_ok, + _sig_threshold_q, + (self.accum_delta if _cur_q is not None else 0.0), + ("+" if self.accum_dir > 0 else ("-" if self.accum_dir < 0 else "0")), + ( + f"{self.pending_temp:.2f}" + if isinstance(self.pending_temp, (int, float)) + else None + ), + ( + f"{(dt_util.now() - self.pending_since).total_seconds():.1f}" + if self.pending_since is not None + else None + ), ) - self.cur_temp = _incoming_temperature - self.last_external_sensor_change = datetime.now() - self.async_write_ha_state() - await self.control_queue_task.put(self) diff --git a/custom_components/better_thermostat/events/trv.py b/custom_components/better_thermostat/events/trv.py index ffb7e18..0474747 100644 --- a/custom_components/better_thermostat/events/trv.py +++ b/custom_components/better_thermostat/events/trv.py @@ -1,30 +1,37 @@ -import asyncio -from datetime import datetime +"""TRV event handlers and helpers for better_thermostat. + +This module contains the various Home Assistant TRV event handlers and +helper functions used by the Better Thermostat integration to read and +convert thermostat states and prepare outbound payloads. +""" + import logging -from custom_components.better_thermostat.utils.const import CONF_HOMEMATICIP -from homeassistant.components.climate.const import ( - HVACMode, - ATTR_HVAC_ACTION, - HVACAction, -) +from homeassistant.components.climate.const import HVACMode +from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN from homeassistant.core import State, callback -from homeassistant.components.group.util import find_state_attributes -from custom_components.better_thermostat.utils.helpers import ( - convert_to_float, - mode_remap, -) +from homeassistant.util import dt as dt_util + from custom_components.better_thermostat.adapters.delegate import get_current_offset - -from custom_components.better_thermostat.utils.const import ( - CalibrationType, - CalibrationMode, -) - from custom_components.better_thermostat.calibration import ( calculate_calibration_local, calculate_calibration_setpoint, ) +from custom_components.better_thermostat.model_fixes.model_quirks import ( + load_model_quirks, +) +from custom_components.better_thermostat.utils.const import ( + CONF_HOMEMATICIP, + CalibrationMode, + CalibrationType, +) +from custom_components.better_thermostat.utils.helpers import ( + attr_to_celsius, + convert_to_float, + get_device_model, + is_reasonable_temperature, + mode_remap, +) _LOGGER = logging.getLogger(__name__) @@ -38,21 +45,26 @@ async def trigger_trv_change(self, event): return if self.bt_target_temp is None or self.cur_temp is None or self.tolerance is None: return - asyncio.create_task(update_hvac_action(self)) + if self.bt_update_lock: + return _main_change = False old_state = event.data.get("old_state") new_state = event.data.get("new_state") entity_id = event.data.get("entity_id") - if None in (new_state, old_state, new_state.attributes): + if new_state is None or old_state is None or new_state.attributes is None: _LOGGER.debug( - f"better_thermostat {self.device_name}: TRV {entity_id} update contained not all necessary data for processing, skipping" + "better_thermostat %s: TRV %s update contained not all necessary data for processing, skipping", + self.device_name, + entity_id, ) return if not isinstance(new_state, State) or not isinstance(old_state, State): _LOGGER.debug( - f"better_thermostat {self.device_name}: TRV {entity_id} update contained not a State, skipping" + "better_thermostat %s: TRV %s update contained not a State, skipping", + self.device_name, + entity_id, ) return # set context HACK TO FIND OUT IF AN EVENT WAS SEND BY BT @@ -64,13 +76,79 @@ async def trigger_trv_change(self, event): # _LOGGER.debug(f"better_thermostat {self.device_name}: TRV {entity_id} update received") _org_trv_state = self.hass.states.get(entity_id) + if _org_trv_state is None: + _LOGGER.debug( + "better_thermostat %s: TRV %s state not found in registry, skipping", + self.device_name, + entity_id, + ) + return + + if _org_trv_state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN): + # The device is gone; its last internal temperature must not + # keep feeding the calibration as if it were live. + if self.real_trvs[entity_id].get("current_temperature") is not None: + _LOGGER.debug( + "better_thermostat %s: TRV %s became %s; invalidating its " + "internal temperature", + self.device_name, + entity_id, + _org_trv_state.state, + ) + self.real_trvs[entity_id]["current_temperature"] = None + # The next valid reading is the first live data after the + # outage and must not be dropped by the debounce below. + self.real_trvs[entity_id]["accept_next_internal_temp"] = True + return + child_lock = self.real_trvs[entity_id]["advanced"].get("child_lock") - _new_current_temp = convert_to_float( - str(_org_trv_state.attributes.get("current_temperature", None)), - self.device_name, - "TRV_current_temp", + # Dynamische Modell-Erkennung: nur einmalig (z. B. beim Start) – nicht bei jedem Event + try: + prev_model = self.real_trvs.get(entity_id, {}).get("model") + if not prev_model: + if _org_trv_state is not None and isinstance( + _org_trv_state.attributes, dict + ): + # Nur prüfen, wenn Hinweise vorhanden sind + if ( + "model_id" in _org_trv_state.attributes + or "device" in _org_trv_state.attributes + ): + detected = await get_device_model(self, entity_id) + if isinstance(detected, str) and detected: + _LOGGER.info( + "better_thermostat %s: TRV %s model detected: %s; " + "loading quirks", + self.device_name, + entity_id, + detected, + ) + quirks = await load_model_quirks(self, detected, entity_id) + self.real_trvs[entity_id]["model"] = detected + self.real_trvs[entity_id]["model_quirks"] = quirks + except Exception as e: + _LOGGER.debug( + "better_thermostat %s: dynamic model detection failed for %s: %s", + self.device_name, + entity_id, + e, + ) + + _new_current_temp = attr_to_celsius( + self, _org_trv_state, "current_temperature", None, "TRV_current_temp" ) + if _new_current_temp is not None and not is_reasonable_temperature( + _new_current_temp + ): + _LOGGER.warning( + "better_thermostat %s: TRV %s reports implausible current_temperature " + "%s; ignoring", + self.device_name, + entity_id, + _new_current_temp, + ) + _new_current_temp = None _time_diff = 5 try: @@ -83,7 +161,8 @@ async def trigger_trv_change(self, event): _new_current_temp is not None and self.real_trvs[entity_id]["current_temperature"] != _new_current_temp and ( - (datetime.now() - self.last_internal_sensor_change).total_seconds() + self.real_trvs[entity_id].pop("accept_next_internal_temp", False) + or (dt_util.now() - self.last_internal_sensor_change).total_seconds() > _time_diff or ( self.real_trvs[entity_id]["calibration_received"] is False @@ -94,22 +173,28 @@ async def trigger_trv_change(self, event): _old_temp = self.real_trvs[entity_id]["current_temperature"] self.real_trvs[entity_id]["current_temperature"] = _new_current_temp _LOGGER.debug( - f"better_thermostat {self.device_name}: TRV {entity_id} sends new internal temperature from {_old_temp} to {_new_current_temp}" + "better_thermostat %s: TRV %s sends new internal temperature from %s to %s", + self.device_name, + entity_id, + _old_temp, + _new_current_temp, ) - self.last_internal_sensor_change = datetime.now() + self.last_internal_sensor_change = dt_util.now() _main_change = True - # TODO: async def in controlling? + # async def in controlling? (left as note) if self.real_trvs[entity_id]["calibration_received"] is False: self.real_trvs[entity_id]["calibration_received"] = True _LOGGER.debug( - f"better_thermostat {self.device_name}: calibration accepted by TRV {entity_id}" + "better_thermostat %s: calibration accepted by TRV %s", + self.device_name, + entity_id, ) _main_change = False if self.real_trvs[entity_id]["calibration"] == 0: - self.real_trvs[entity_id]["last_calibration"] = ( - await get_current_offset(self, entity_id) - ) + self.real_trvs[entity_id][ + "last_calibration" + ] = await get_current_offset(self, entity_id) if self.ignore_states: return @@ -118,10 +203,41 @@ async def trigger_trv_change(self, event): mapped_state = convert_inbound_states(self, entity_id, _org_trv_state) except TypeError: _LOGGER.debug( - f"better_thermostat {self.device_name}: remapping TRV {entity_id} state failed, skipping" + "better_thermostat %s: remapping TRV %s state failed, skipping", + self.device_name, + entity_id, ) return + # hvac_action bedingungslos in den Cache schreiben (immer aktuell halten) + try: + hvac_action_attr = _org_trv_state.attributes.get("hvac_action") + if hvac_action_attr is None: + hvac_action_attr = _org_trv_state.attributes.get("action") + if hvac_action_attr is not None: + val = str(hvac_action_attr).strip().lower() + prev = self.real_trvs[entity_id].get("hvac_action") + self.real_trvs[entity_id]["hvac_action"] = val + if prev != val: + _main_change = True + _LOGGER.debug( + "better_thermostat %s: TRV %s hvac_action changed: %s -> %s", + self.device_name, + entity_id, + prev, + val, + ) + + # valve_position aktualisieren + val_pos = _org_trv_state.attributes.get("valve_position") + if val_pos is not None: + self.real_trvs[entity_id]["valve_position"] = convert_to_float( + str(val_pos), self.device_name, "trv_event" + ) + + except Exception: + pass + if mapped_state in (HVACMode.OFF, HVACMode.HEAT, HVACMode.HEAT_COOL): if ( self.real_trvs[entity_id]["hvac_mode"] != _org_trv_state.state @@ -129,7 +245,12 @@ async def trigger_trv_change(self, event): ): _old = self.real_trvs[entity_id]["hvac_mode"] _LOGGER.debug( - f"better_thermostat {self.device_name}: TRV {entity_id} decoded TRV mode changed from {_old} to {_org_trv_state.state} - converted {new_state.state}" + "better_thermostat %s: TRV %s decoded TRV mode changed from %s to %s - converted %s", + self.device_name, + entity_id, + _old, + _org_trv_state.state, + new_state.state, ) self.real_trvs[entity_id]["hvac_mode"] = _org_trv_state.state _main_change = True @@ -140,34 +261,41 @@ async def trigger_trv_change(self, event): ): self.bt_hvac_mode = mapped_state + # Hinweis: Kein Caching von hvac_action mehr – BT liest direkt vom TRV-State in climate.py + _main_key = "temperature" if "temperature" not in old_state.attributes: _main_key = "target_temp_low" - _old_heating_setpoint = convert_to_float( - str(old_state.attributes.get(_main_key, None)), - self.device_name, - "trigger_trv_change()", + _old_heating_setpoint = attr_to_celsius( + self, old_state, _main_key, None, "trigger_trv_change()" ) - _new_heating_setpoint = convert_to_float( - str(new_state.attributes.get(_main_key, None)), - self.device_name, - "trigger_trv_change()", + _new_heating_setpoint = attr_to_celsius( + self, new_state, _main_key, None, "trigger_trv_change()" + ) + _is_no_off_device = self.real_trvs[entity_id]["advanced"].get( + "no_off_system_mode", False ) if ( _new_heating_setpoint is not None and _old_heating_setpoint is not None - and self.bt_hvac_mode is not HVACMode.OFF + and (self.bt_hvac_mode != HVACMode.OFF or _is_no_off_device) ): _LOGGER.debug( - f"better_thermostat {self.device_name}: trigger_trv_change test / _old_heating_setpoint: {_old_heating_setpoint} - _new_heating_setpoint: {_new_heating_setpoint} - _last_temperature: {self.real_trvs[entity_id]['last_temperature']}" + "better_thermostat %s: trigger_trv_change / _old_heating_setpoint: %s - _new_heating_setpoint: %s - _last_temperature: %s", + self.device_name, + _old_heating_setpoint, + _new_heating_setpoint, + self.real_trvs[entity_id]["last_temperature"], ) if ( _new_heating_setpoint < self.bt_min_temp or self.bt_max_temp < _new_heating_setpoint ): _LOGGER.warning( - f"better_thermostat {self.device_name}: New TRV {entity_id} setpoint outside of range, overwriting it" + "better_thermostat %s: New TRV %s setpoint outside of range, overwriting it", + self.device_name, + entity_id, ) if _new_heating_setpoint < self.bt_min_temp: @@ -175,35 +303,97 @@ async def trigger_trv_change(self, event): else: _new_heating_setpoint = self.bt_max_temp - if ( - self.bt_target_temp != _new_heating_setpoint - and _old_heating_setpoint != _new_heating_setpoint - and self.real_trvs[entity_id]["last_temperature"] != _new_heating_setpoint + # Step-aware echo detection: changes strictly smaller than the device + # step are treated as device-side rounding echoes of a BT-written + # value, not as user input. User input on a TRV display moves the + # setpoint by at least one step. + _step_raw = ( + self.real_trvs[entity_id].get("target_temp_step") + or self.bt_target_temp_step + or 0.5 + ) + try: + _step = float(_step_raw) + except (TypeError, ValueError): + _step = 0.5 + if _step <= 0: + _step = 0.5 + # Compare only against values BT itself wrote. ``_old_heating_setpoint`` + # is the TRV's previously published state and is not necessarily a + # BT-written value, so it does not belong in the echo-suppression set. + _bt_known_values = ( + self.bt_target_temp, + self.real_trvs[entity_id]["last_temperature"], + ) + _is_echo = any( + v is not None and abs(_new_heating_setpoint - v) < _step + for v in _bt_known_values + ) + _accept_user_setpoint = ( + not _is_echo and not child_lock and self.real_trvs[entity_id]["target_temp_received"] is True and self.real_trvs[entity_id]["system_mode_received"] is True - and self.real_trvs[entity_id]["hvac_mode"] is not HVACMode.OFF + and self.real_trvs[entity_id]["hvac_mode"] != HVACMode.OFF and self.window_open is False - ): + and not self.real_trvs[entity_id].get("ignore_trv_states", False) + ) + if _accept_user_setpoint: _LOGGER.debug( - f"better_thermostat {self.device_name}: TRV {entity_id} decoded TRV target temp changed from {self.bt_target_temp} to {_new_heating_setpoint}" + "better_thermostat %s: TRV %s decoded TRV target temp changed from %s to %s", + self.device_name, + entity_id, + self.bt_target_temp, + _new_heating_setpoint, ) self.bt_target_temp = _new_heating_setpoint if self.cooler_entity_id is not None: - if self.bt_target_temp <= self.bt_target_cooltemp: - self.bt_target_cooltemp = ( - self.bt_target_temp - self.bt_target_temp_step - ) if self.bt_target_temp >= self.bt_target_cooltemp: - self.bt_target_cooltemp = ( - self.bt_target_temp - self.bt_target_temp_step + self.bt_target_cooltemp = self.bt_target_temp + ( + self.bt_target_temp_step or 0.5 ) _main_change = True + elif _new_heating_setpoint != _old_heating_setpoint: + # A setpoint change arrived from the TRV but was not adopted as user + # intent. Record which guard suppressed it so intermittent "change + # ignored" / "device not syncing" reports can be diagnosed from a + # debug log instead of guesswork. + _LOGGER.debug( + "better_thermostat %s: TRV %s setpoint change %s -> %s NOT adopted " + "(echo=%s child_lock=%s target_temp_received=%s system_mode_received=%s " + "hvac_mode=%s window_open=%s ignore_trv_states=%s bt_target_temp=%s " + "last_temperature=%s step=%s)", + self.device_name, + entity_id, + _old_heating_setpoint, + _new_heating_setpoint, + _is_echo, + child_lock, + self.real_trvs[entity_id]["target_temp_received"], + self.real_trvs[entity_id]["system_mode_received"], + self.real_trvs[entity_id]["hvac_mode"], + self.window_open, + self.real_trvs[entity_id].get("ignore_trv_states", False), + self.bt_target_temp, + self.real_trvs[entity_id]["last_temperature"], + _step, + ) if self.real_trvs[entity_id]["advanced"].get("no_off_system_mode", False): if _new_heating_setpoint == self.real_trvs[entity_id]["min_temp"]: - self.bt_hvac_mode = HVACMode.OFF + # Only set OFF if window is NOT open - min_temp during window + # open was set by BT, not by user turning off heating + if not self.window_open: + if self.bt_hvac_mode != HVACMode.OFF: + _LOGGER.debug( + "better_thermostat %s: TRV %s reported min_temp %s on a " + "no_off_system_mode device -> interpreting as heating OFF", + self.device_name, + entity_id, + _new_heating_setpoint, + ) + self.bt_hvac_mode = HVACMode.OFF else: self.bt_hvac_mode = HVACMode.HEAT _main_change = True @@ -211,76 +401,21 @@ async def trigger_trv_change(self, event): if _main_change is True: self.async_write_ha_state() return await self.control_queue_task.put(self) + self.async_write_ha_state() return -async def update_hvac_action(self): - self.bt_target_temp = 5.0 if self.bt_target_temp is None else self.bt_target_temp - self.cur_temp = 5.0 if self.cur_temp is None else self.cur_temp - self.tolerance = 0.0 if self.tolerance is None else self.tolerance - """Update the hvac action.""" - if self.startup_running or self.control_queue_task is None: - return +def convert_inbound_states(self, entity_id, state: State) -> str | None: + """Convert HVAC mode in a thermostat state from Home Assistant. - hvac_action = None - - # i don't know why this is here just for hometicip / wtom - 2023-08-23 - # for trv in self.all_trvs: - # if trv["advanced"][CONF_HOMEMATICIP]: - # entity_id = trv["trv"] - # state = self.hass.states.get(entity_id) - # if state is None: - # continue - - # if state.attributes.get(ATTR_HVAC_ACTION) == HVACAction.HEATING: - # hvac_action = HVACAction.HEATING - # break - # elif state.attributes.get(ATTR_HVAC_ACTION) == HVACAction.IDLE: - # hvac_action = HVACAction.IDLE - - # return the most common action if it is not off - states = [ - state - for entity_id in self.real_trvs - if (state := self.hass.states.get(entity_id)) is not None - ] - - hvac_actions = list(find_state_attributes(states, ATTR_HVAC_ACTION)) - - if not hvac_actions: - self.attr_hvac_action = None - # return action off if all are off - elif all(a == HVACAction.OFF for a in hvac_actions): - hvac_action = HVACAction.OFF - # else check if is heating - elif ( - self.bt_target_temp > self.cur_temp + self.tolerance - and self.window_open is False - ): - hvac_action = HVACAction.HEATING - elif ( - self.bt_target_temp > self.cur_temp - and self.attr_hvac_action == HVACAction.HEATING - and self.window_open is False - ): - hvac_action = HVACAction.HEATING - else: - hvac_action = HVACAction.IDLE - - if self.hvac_action != hvac_action: - self.attr_hvac_action = hvac_action - await self.async_update_ha_state(force_refresh=True) - - -def convert_inbound_states(self, entity_id, state: State) -> str: - """Convert hvac mode in a thermostat state from HA Parameters ---------- self : - self instance of better_thermostat + self instance of better_thermostat state : State - Inbound thermostat state, which will be modified + Inbound thermostat state, which will be modified + Returns ------- Modified state @@ -300,27 +435,13 @@ def convert_inbound_states(self, entity_id, state: State) -> str: def convert_outbound_states(self, entity_id, hvac_mode) -> dict | None: - """Creates the new outbound thermostat state. - Parameters - ---------- - self : - self instance of better_thermostat - hvac_mode : - the HA mode to convert to - Returns - ------- - dict - A dictionary containing the new outbound thermostat state containing the following keys: - temperature: float - local_temperature: float - local_temperature_calibration: float - system_mode: string - None - In case of an error. - """ + """Convert outbound states for TRV control. + Returns the payload for setting the TRV state. + """ _new_local_calibration = None _new_heating_setpoint = None + _new_valve_position = None try: _calibration_type = self.real_trvs[entity_id]["advanced"].get("calibration") @@ -333,57 +454,91 @@ def convert_outbound_states(self, entity_id, hvac_mode) -> dict | None: "better_thermostat %s: no calibration type found in device config, talking to the TRV using fallback mode", self.device_name, ) + # Fallback: keine lokale Kalibrierung durchführen, nur Solltemperatur setzen _new_heating_setpoint = self.bt_target_temp - _new_local_calibration = calculate_calibration_local(self, entity_id) + _new_local_calibration = None - if _new_local_calibration is None: - return None + elif _calibration_type == CalibrationType.LOCAL_BASED: + _new_local_calibration = calculate_calibration_local(self, entity_id) + _new_heating_setpoint = self.bt_target_temp + + elif _calibration_type in ( + CalibrationType.TARGET_TEMP_BASED, + CalibrationType.DIRECT_VALVE_BASED, + ): + if _calibration_mode == CalibrationMode.NO_CALIBRATION: + _new_heating_setpoint = self.bt_target_temp + else: + _new_heating_setpoint = calculate_calibration_setpoint(self, entity_id) + _new_local_calibration = None else: - if _calibration_type == CalibrationType.LOCAL_BASED: - _new_local_calibration = calculate_calibration_local(self, entity_id) + # Unknown calibration type - use fallback + _LOGGER.warning( + "better_thermostat %s: unknown calibration type %s, using fallback mode", + self.device_name, + _calibration_type, + ) + _new_heating_setpoint = self.bt_target_temp + _new_local_calibration = None - _new_heating_setpoint = self.bt_target_temp + # System mode handling - applies to ALL calibration modes including fallback + _system_modes = self.real_trvs[entity_id]["hvac_modes"] + _has_system_mode = _system_modes is not None - elif _calibration_type == CalibrationType.TARGET_TEMP_BASED: - if _calibration_mode == CalibrationMode.NO_CALIBRATION: - _new_heating_setpoint = self.bt_target_temp - else: - _new_heating_setpoint = calculate_calibration_setpoint( - self, entity_id - ) + # Normalize without forcing to str to avoid values like "HVACMode.HEAT" + _orig_mode = hvac_mode + hvac_mode = mode_remap(self, entity_id, hvac_mode, False) + _LOGGER.debug( + "better_thermostat %s: convert_outbound_states(%s) system_mode in=%s out=%s", + self.device_name, + entity_id, + _orig_mode, + hvac_mode, + ) - _system_modes = self.real_trvs[entity_id]["hvac_modes"] - _has_system_mode = _system_modes is not None + if not _has_system_mode: + _LOGGER.debug( + "better_thermostat %s: device config expects no system mode, while the device has one. Device system mode will be ignored", + self.device_name, + ) + if hvac_mode == HVACMode.OFF: + _new_heating_setpoint = self.real_trvs[entity_id]["min_temp"] + hvac_mode = None + _LOGGER.debug( + "better_thermostat %s: convert_outbound_states(%s) suppressing system_mode for no-off device", + self.device_name, + entity_id, + ) + if hvac_mode == HVACMode.OFF and ( + (_system_modes is not None and HVACMode.OFF not in _system_modes) + or self.real_trvs[entity_id]["advanced"].get("no_off_system_mode") + ): + _min_temp = self.real_trvs[entity_id]["min_temp"] + _LOGGER.debug( + "better_thermostat %s: sending %s°C to the TRV because this device has no system mode off and heater should be off", + self.device_name, + _min_temp, + ) + _new_heating_setpoint = _min_temp + hvac_mode = None - # Handling different devices with or without system mode reported or contained in the device config - - hvac_mode = mode_remap(self, entity_id, str(hvac_mode), False) - - if not _has_system_mode: - _LOGGER.debug( - f"better_thermostat {self.device_name}: device config expects no system mode, while the device has one. Device system mode will be ignored" - ) - if hvac_mode == HVACMode.OFF: - _new_heating_setpoint = self.real_trvs[entity_id]["min_temp"] - hvac_mode = None - if hvac_mode == HVACMode.OFF and ( - HVACMode.OFF not in _system_modes - or self.real_trvs[entity_id]["advanced"].get("no_off_system_mode") - ): - _min_temp = self.real_trvs[entity_id]["min_temp"] - _LOGGER.debug( - f"better_thermostat {self.device_name}: sending {_min_temp}°C to the TRV because this device has no system mode off and heater should be off" - ) - _new_heating_setpoint = _min_temp - hvac_mode = None - - return { + # Build payload; include calibration only if present + _payload = { "temperature": _new_heating_setpoint, "local_temperature": self.real_trvs[entity_id]["current_temperature"], "system_mode": hvac_mode, - "local_temperature_calibration": _new_local_calibration, } + if _new_local_calibration is not None: + _payload["local_temperature_calibration"] = _new_local_calibration + if _new_valve_position is not None: + _payload["valve_position"] = _new_valve_position + return _payload except Exception as e: - _LOGGER.error(e) + _LOGGER.exception( + "better_thermostat %s: exception in convert_outbound_states for %s: %s", + self.device_name, + entity_id, + e, + ) return None diff --git a/custom_components/better_thermostat/events/window.py b/custom_components/better_thermostat/events/window.py index 7c0ab20..2b4df4c 100644 --- a/custom_components/better_thermostat/events/window.py +++ b/custom_components/better_thermostat/events/window.py @@ -1,11 +1,17 @@ +"""Window event handling and debounce queue helper. + +These helpers respond to window sensor events and implement debouncing and +delayed handling so that HVAC behavior uses window-open information reliably. +""" + import asyncio import logging -from custom_components.better_thermostat import DOMAIN from homeassistant.core import callback -from homeassistant.const import STATE_OFF from homeassistant.helpers import issue_registry as ir +from custom_components.better_thermostat import DOMAIN + _LOGGER = logging.getLogger(__name__) @@ -34,45 +40,63 @@ async def trigger_window_change(self, event) -> None: old_window_open = self.window_open - if new_state in ("on", "unknown", "unavailable"): + if new_state in ("on", "true", "open", "unknown", "unavailable"): new_window_open = True if new_state == "unknown": _LOGGER.warning( "better_thermostat %s: Window sensor state is unknown, assuming window is open", self.device_name, ) + elif new_state == "unavailable": + _LOGGER.info( + "better_thermostat %s: Window sensor is unavailable, assuming window is open", + self.device_name, + ) # window was opened, disable heating power calculation for this period - self.heating_start_temp = None + self._heating_tracker.start_temp = None self.async_write_ha_state() - elif new_state == "off": + elif new_state in ("off", "false", "closed"): new_window_open = False else: _LOGGER.error( - f"better_thermostat {self.device_name}: New window sensor state '{new_state}' not recognized" + "better_thermostat %s: New window sensor state '%s' not recognized", + self.device_name, + new_state, ) ir.async_create_issue( hass=self.hass, domain=DOMAIN, - issue_id=f"missing_entity_{self.device_name}", - issue_title=f"better_thermostat {self.device_name} has invalid window sensor state", - issue_severity="error", - issue_description=f"better_thermostat {self.device_name} has invalid window sensor state: {new_state}", - issue_category="config", - issue_suggested_action="Please check the window sensor", + issue_id=f"invalid_window_state_{self.device_name}", + is_fixable=False, + is_persistent=False, + learn_more_url="https://better-thermostat.org/qanda/window_sensor", + severity=ir.IssueSeverity.ERROR, + translation_key="invalid_window_state", + translation_placeholders={ + "name": str(self.device_name), + "state": str(new_state), + }, ) return # make sure to skip events which do not change the saved window state: if new_window_open == old_window_open: _LOGGER.debug( - f"better_thermostat {self.device_name}: Window state did not change, skipping event" + "better_thermostat %s: Window state did not change, skipping event", + self.device_name, ) return await self.window_queue_task.put(new_window_open) async def window_queue(self): + """Process queued window-open events. + + This coroutine dequeues window state changes, applies configured wait + delays and triggers the control queue when the window remains in the + expected state after the delay. + """ try: while True: window_event_to_process = await self.window_queue_task.get() @@ -80,37 +104,65 @@ async def window_queue(self): if window_event_to_process is not None: if window_event_to_process: _LOGGER.debug( - f"better_thermostat {self.device_name}: Window opened, waiting {self.window_delay} seconds before continuing" + "better_thermostat %s: Window opened, " + "waiting %s seconds before continuing", + self.device_name, + self.window_delay, ) await asyncio.sleep(self.window_delay) else: _LOGGER.debug( - f"better_thermostat {self.device_name}: Window closed, waiting {self.window_delay_after} seconds before continuing" + "better_thermostat %s: Window closed, " + "waiting %s seconds before continuing", + self.device_name, + self.window_delay_after, ) await asyncio.sleep(self.window_delay_after) + window_state = self.hass.states.get(self.window_id) + if window_state is None: + _LOGGER.debug( + "better_thermostat %s: Window sensor %s vanished " + "during the debounce delay; skipping event", + self.device_name, + self.window_id, + ) + continue # remap off on to true false current_window_state = True - if self.hass.states.get(self.window_id).state == STATE_OFF: + if window_state.state in ("off", "false", "closed"): current_window_state = False # make sure the current state is the suggested change state to prevent a false positive: if current_window_state == window_event_to_process: self.window_open = window_event_to_process self.async_write_ha_state() - if not self.control_queue_task.empty(): - empty_queue(self.control_queue_task) - await self.control_queue_task.put(self) + if getattr(self, "in_maintenance", False): + # Keep state up to date during maintenance, but defer control + # until maintenance ends. + self._control_needed_after_maintenance = True + else: + if not self.control_queue_task.empty(): + empty_queue(self.control_queue_task) + await self.control_queue_task.put(self) except asyncio.CancelledError: + _LOGGER.debug( + "better_thermostat %s: Window queue processing cancelled", + self.device_name, + ) raise finally: self.window_queue_task.task_done() except asyncio.CancelledError: _LOGGER.debug( - f"better_thermostat {self.device_name}: Window queue task cancelled" + "better_thermostat %s: Window queue task cancelled", self.device_name ) raise def empty_queue(q: asyncio.Queue): + """Empty out a Queue of pending items. + + Consumes all pending items from the queue and marks them as done. + """ for _ in range(q.qsize()): q.get_nowait() q.task_done() diff --git a/custom_components/better_thermostat/manifest.json b/custom_components/better_thermostat/manifest.json index b445357..55d72f6 100644 --- a/custom_components/better_thermostat/manifest.json +++ b/custom_components/better_thermostat/manifest.json @@ -17,5 +17,5 @@ "iot_class": "local_push", "issue_tracker": "https://github.com/KartoffelToby/better_thermostat/issues", "requirements": [], - "version": "1.7.0" + "version": "1.8.2" } diff --git a/custom_components/better_thermostat/model_fixes/BHT-002-GCLZB.py b/custom_components/better_thermostat/model_fixes/BHT-002-GCLZB.py index f6b52a3..e5e4179 100644 --- a/custom_components/better_thermostat/model_fixes/BHT-002-GCLZB.py +++ b/custom_components/better_thermostat/model_fixes/BHT-002-GCLZB.py @@ -1,15 +1,19 @@ -""" -This model fix is due to any floating point number being set to +/- 1 million by Zigbee2MQTT for the local_calibration +"""Zigbee2MQTT local calibration value fix. + +This model fix addresses an issue where Zigbee2MQTT reports incorrect +floating-point values (e.g., +/- 1e6) for the local calibration. The +helper functions below normalize values to sensible integer offsets. """ import math def fix_local_calibration(self, entity_id, offset): - """ - If still heating, round UP the offset + """Sanitize and normalize a reported calibration offset. - This creates a lower "fake" thermostat temperature, making it heat the room + Rounds to the nearest integer (towards ceiling if the room is heating) + to recover from the erroneous float values produced by some Zigbee + integrations. """ if self.cur_temp < self.bt_target_temp: offset = math.ceil(offset) @@ -20,12 +24,15 @@ def fix_local_calibration(self, entity_id, offset): def fix_target_temperature_calibration(self, entity_id, temperature): + """No-op target-temperature calibration fix for this model.""" return temperature async def override_set_hvac_mode(self, entity_id, hvac_mode): + """No override on system mode for this model.""" return False async def override_set_temperature(self, entity_id, temperature): + """No temperature override for this model.""" return False diff --git a/custom_components/better_thermostat/model_fixes/SEA801-Zigbee_SEA802-Zigbee.py b/custom_components/better_thermostat/model_fixes/SEA801-Zigbee_SEA802-Zigbee.py index 6deb978..aae9135 100644 --- a/custom_components/better_thermostat/model_fixes/SEA801-Zigbee_SEA802-Zigbee.py +++ b/custom_components/better_thermostat/model_fixes/SEA801-Zigbee_SEA802-Zigbee.py @@ -1,29 +1,79 @@ +"""Model quirks for SEA801/SEA802 Zigbee thermostats. + +Includes device-specific offsets and behavior adaptations required for certain +SEA801/SEA802 based devices. +""" + +import logging + +from custom_components.better_thermostat.utils.helpers import ( + entity_uses_mpc_calibration, +) + +_LOGGER = logging.getLogger(__name__) + + def fix_local_calibration(self, entity_id, offset): - # device SEA802 fix - if (self.cur_temp - self.bt_target_temp) < -0.2: + """Adjust the local calibration offset for SEA801/SEA802 devices. + + The function applies small adjustments based on the external and target + temperatures to avoid incorrect temperature behavior. + """ + if entity_uses_mpc_calibration(self, entity_id): + return offset + _cur_external_temp = self.cur_temp + _target_temp = self.bt_target_temp + + if (_cur_external_temp + 0.1) >= _target_temp: + offset = round(offset + 0.5, 1) + elif (_cur_external_temp + 0.5) >= _target_temp: offset -= 2.5 return offset def fix_target_temperature_calibration(self, entity_id, temperature): - # device SEA802 fix - _cur_trv_temp = float( - self.hass.states.get(entity_id).attributes["current_temperature"] - ) + """Adjust the setpoint temperature for SEA801/SEA802 devices. + + Ensures a minimum distance between the current TRV temperature and the + target temperature to avoid short-cycling and oscillation. + """ + _state = self.hass.states.get(entity_id) + _cur_trv_temp = None + if _state is not None: + _cur_trv_temp = _state.attributes.get("current_temperature") if _cur_trv_temp is None: return temperature + + if entity_uses_mpc_calibration(self, entity_id): + return temperature + + _cur_trv_temp = float(_cur_trv_temp) if ( round(temperature, 1) > round(_cur_trv_temp, 1) - and temperature - _cur_trv_temp < 2.5 + and temperature - _cur_trv_temp < 1.5 ): - temperature += 2.5 + # Statt die gewünschte Temperatur pauschal um 1.5°C zu erhöhen, + # setze sie auf mindestens (aktuelle TRV-Temp + 1.5°C). + # So ist der Mindestabstand garantiert, ohne unnötig zu überschießen. + temperature = round(_cur_trv_temp + 1.5, 1) + return temperature async def override_set_hvac_mode(self, entity_id, hvac_mode): + """No HVAC mode override for SEA801/SEA802 devices. + + Return False to indicate no custom handling and let the adapter handle + normal behavior. + """ return False async def override_set_temperature(self, entity_id, temperature): + """No set_temperature override for SEA801/SEA802 devices. + + Return False to indicate the adapter should use the default set_temperature + implementation. + """ return False diff --git a/custom_components/better_thermostat/model_fixes/SPZB0001.py b/custom_components/better_thermostat/model_fixes/SPZB0001.py index 19d5d61..fd8b25d 100644 --- a/custom_components/better_thermostat/model_fixes/SPZB0001.py +++ b/custom_components/better_thermostat/model_fixes/SPZB0001.py @@ -1,4 +1,20 @@ +"""Model fixes for SPZB0001 devices. + +Device-specific quirks for SPZB0001 thermostats handled by the +Better Thermostat integration. +""" + +import logging + +from homeassistant.helpers import entity_registry as er + +from ..utils.const import CalibrationType + +_LOGGER = logging.getLogger(__name__) + + def fix_local_calibration(self, entity_id, offset): + """Clamp local calibration to safe bounds for SPZB0001 devices.""" if offset > 5: offset = 5 elif offset < -5: @@ -6,13 +22,80 @@ def fix_local_calibration(self, entity_id, offset): return offset +async def check_operation_mode(self, entity_id, goal: str = "1"): + """Return a possibly adjusted valve calibration for SPZB0001. + + Currently a no-op. + """ + + entity_registry = er.async_get(self.hass) + reg_entity = entity_registry.async_get(entity_id) + if reg_entity is None: + _LOGGER.debug( + "better_thermostat %s: SPZB0001 check_operation_mode: no registry entity for %s", + self.device_name, + entity_id, + ) + return False + device_id = reg_entity.device_id + target_entity = None + for ent in entity_registry.entities.values(): + if ent.device_id != device_id or ent.domain != "select": + continue + en = (ent.entity_id or "").lower() + uid = (ent.unique_id or "").lower() + name = (getattr(ent, "original_name", None) or "").lower() + if "_trv_mode" in en or "_trv_mode" in uid or "Trv mode" in name: + target_entity = ent.entity_id + if target_entity is None: + _LOGGER.debug( + "better_thermostat %s: SPZB0001 check_operation_mode: no target entity for %s", + self.device_name, + entity_id, + ) + return False + val = self.hass.states.get(target_entity) + if val is None: + return False + if val.state != goal: + _LOGGER.debug( + "better_thermostat %s: SPZB0001 check_operation_mode: setting target entity %s to %s from %s", + self.device_name, + target_entity, + goal, + val.state, + ) + await self.hass.services.async_call( + "select", "select_option", {"entity_id": target_entity, "option": goal} + ) + + return True + + +async def inital_tweak(self, entity_id): + """Run initial tweaks for the device.""" + _calibration_type = self.real_trvs[entity_id]["advanced"].get( + "calibration", CalibrationType.TARGET_TEMP_BASED + ) + if _calibration_type == CalibrationType.DIRECT_VALVE_BASED: + await check_operation_mode(self, entity_id, goal="1") + else: + await check_operation_mode(self, entity_id, goal="2") + + def fix_target_temperature_calibration(self, entity_id, temperature): + """Return a possibly adjusted target temperature for SPZB0001. + + Currently a no-op. + """ return temperature async def override_set_hvac_mode(self, entity_id, hvac_mode): + """Do not override HVAC mode for SPZB0001 devices.""" return False async def override_set_temperature(self, entity_id, temperature): + """Do not override temperature sets for SPZB0001 devices.""" return False diff --git a/custom_components/better_thermostat/model_fixes/TS0601.py b/custom_components/better_thermostat/model_fixes/TS0601.py index e43128a..e8fcef9 100644 --- a/custom_components/better_thermostat/model_fixes/TS0601.py +++ b/custom_components/better_thermostat/model_fixes/TS0601.py @@ -1,4 +1,15 @@ +"""Model quirks for generic TS0601 Zigbee thermostats. + +Contains model-specific handling for known quirks in TS0601-based devices. +""" + + def fix_local_calibration(self, entity_id, offset): + """Normalize local calibration offset for TS0601 devices. + + This function performs model-specific rounding/adjustment to avoid + spurious values that would lead to incorrect behavior. + """ _cur_external_temp = self.cur_temp _target_temp = self.bt_target_temp @@ -11,6 +22,11 @@ def fix_local_calibration(self, entity_id, offset): def fix_target_temperature_calibration(self, entity_id, temperature): + """Adjust target temperature calibration for TS0601 devices. + + Ensures a minimum distance between the current TRV internal temperature + and the requested setpoint to avoid oscillation. + """ _cur_trv_temp = float( self.hass.states.get(entity_id).attributes["current_temperature"] ) @@ -26,8 +42,10 @@ def fix_target_temperature_calibration(self, entity_id, temperature): async def override_set_hvac_mode(self, entity_id, hvac_mode): + """No special HVAC mode override for TS0601 devices.""" return False async def override_set_temperature(self, entity_id, temperature): + """No special set_temperature override for TS0601 devices.""" return False diff --git a/custom_components/better_thermostat/model_fixes/TS0601_thermostat.py b/custom_components/better_thermostat/model_fixes/TS0601_thermostat.py index e43128a..1fe4cc0 100644 --- a/custom_components/better_thermostat/model_fixes/TS0601_thermostat.py +++ b/custom_components/better_thermostat/model_fixes/TS0601_thermostat.py @@ -1,4 +1,12 @@ +"""Model quirks for TS0601 thermostat devices. + +These helpers fix or adapt device-reported values for TS0601 thermostat +devices used by the Better Thermostat integration. +""" + + def fix_local_calibration(self, entity_id, offset): + """Normalize a local calibration offset for TS0601 thermostat devices.""" _cur_external_temp = self.cur_temp _target_temp = self.bt_target_temp @@ -11,6 +19,7 @@ def fix_local_calibration(self, entity_id, offset): def fix_target_temperature_calibration(self, entity_id, temperature): + """Adjust the target temperature for TS0601 thermostat devices.""" _cur_trv_temp = float( self.hass.states.get(entity_id).attributes["current_temperature"] ) @@ -26,8 +35,10 @@ def fix_target_temperature_calibration(self, entity_id, temperature): async def override_set_hvac_mode(self, entity_id, hvac_mode): + """No special override for HVAC mode on TS0601 thermostats.""" return False async def override_set_temperature(self, entity_id, temperature): + """No special override for target temperature on TS0601 thermostats.""" return False diff --git a/custom_components/better_thermostat/model_fixes/TV02-Zigbee.py b/custom_components/better_thermostat/model_fixes/TV02-Zigbee.py index 46e8240..e2b8cfe 100644 --- a/custom_components/better_thermostat/model_fixes/TV02-Zigbee.py +++ b/custom_components/better_thermostat/model_fixes/TV02-Zigbee.py @@ -1,28 +1,34 @@ -# Quirks for TV02-Zigbee +"""Quirks for TV02-Zigbee thermostats.""" + import logging + from homeassistant.components.climate.const import HVACMode _LOGGER = logging.getLogger(__name__) def fix_local_calibration(self, entity_id, offset): + """Return local calibration offset unchanged for TRV02 devices.""" return offset def fix_target_temperature_calibration(self, entity_id, temperature): + """Return target temperature unchanged for TRV02 devices.""" return temperature async def override_set_hvac_mode(self, entity_id, hvac_mode): - """Enable specific device quirks while setting hvac mode + """Enable device quirks while setting HVAC mode. + Parameters ---------- self : - self instance of better_thermostat + self instance of better_thermostat entity_id : - Entity id of the TRV. + Entity id of the TRV. hvac_mode: - HVAC mode to be set. + HVAC mode to be set. + Returns ------- None @@ -37,7 +43,9 @@ async def override_set_hvac_mode(self, entity_id, hvac_mode): model = self.real_trvs[entity_id]["model"] if model == "TV02-Zigbee" and hvac_mode != HVACMode.OFF: _LOGGER.debug( - f"better_thermostat {self.device_name}: TRV {entity_id} device quirk hvac trv02-zigbee active" + "better_thermostat %s: TRV %s device quirk hvac trv02-zigbee active", + self.device_name, + entity_id, ) await self.hass.services.async_call( "climate", @@ -50,15 +58,17 @@ async def override_set_hvac_mode(self, entity_id, hvac_mode): async def override_set_temperature(self, entity_id, temperature): - """Enable specific device quirks while setting temperature + """Enable device quirks while setting temperature. + Parameters ---------- self : - self instance of better_thermostat + self instance of better_thermostat entity_id : - Entity id of the TRV. + Entity id of the TRV. temperature: - Temperature to be set. + Temperature to be set. + Returns ------- None @@ -66,7 +76,9 @@ async def override_set_temperature(self, entity_id, temperature): model = self.real_trvs[entity_id]["model"] if model == "TV02-Zigbee": _LOGGER.debug( - f"better_thermostat {self.device_name}: TRV {entity_id} device quirk trv02-zigbee active" + "better_thermostat %s: TRV %s device quirk trv02-zigbee active", + self.device_name, + entity_id, ) await self.hass.services.async_call( "climate", diff --git a/custom_components/better_thermostat/model_fixes/default.py b/custom_components/better_thermostat/model_fixes/default.py index f3f2718..33d0efa 100644 --- a/custom_components/better_thermostat/model_fixes/default.py +++ b/custom_components/better_thermostat/model_fixes/default.py @@ -1,14 +1,195 @@ +"""Default model quirks passthrough for unknown devices. + +These helpers implement safe no-op defaults for devices that do not +require specific quirks. +""" + +import logging + +from homeassistant.components.lock import LockState +from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.helpers import entity_registry as er + +_LOGGER = logging.getLogger(__name__) + +VALVE_MAINTENANCE_INTERVAL_HOURS = 168 # Default: 7 days + + def fix_local_calibration(self, entity_id, offset): + """Return the given local calibration offset unchanged.""" return offset +def fix_valve_calibration(self, entity_id, valve): + """Return the given valve calibration unchanged.""" + return valve + + def fix_target_temperature_calibration(self, entity_id, temperature): + """Return the given target temperature unchanged.""" return temperature async def override_set_hvac_mode(self, entity_id, hvac_mode): + """Do not override HVAC mode by default.""" return False async def override_set_temperature(self, entity_id, temperature): + """Do not override set temperature by default.""" return False + + +async def override_set_valve(self, entity_id, percent: int): + """Do not override valve by default.""" + return False + + +async def inital_tweak(self, entity_id): + """Run initial tweaks for the device.""" + entity_registry = er.async_get(self.hass) + reg_entity = entity_registry.async_get(entity_id) + + if reg_entity is not None and reg_entity.device_id is not None: + device_id = reg_entity.device_id + + def find_entity(domains, keywords): + for ent in entity_registry.entities.values(): + if ent.device_id != device_id or ent.domain not in domains: + continue + name = (getattr(ent, "original_name", "") or "").lower() + uid = (ent.unique_id or "").lower() + eid = (ent.entity_id or "").lower() + + if ( + any(k in name for k in keywords) + or any(k in uid for k in keywords) + or any(k in eid for k in keywords) + ): + return ent.entity_id + return None + + # 1. Local calibration -> 0 + cal_entity = find_entity( + ["number"], + ["local_temperature_calibration", "local_calibration", "calibration"], + ) + if cal_entity: + try: + _LOGGER.debug( + "better_thermostat %s: Resetting local calibration for %s to 0", + self.device_name, + cal_entity, + ) + await self.hass.services.async_call( + "number", "set_value", {"entity_id": cal_entity, "value": 0} + ) + except Exception as e: + _LOGGER.warning( + "better_thermostat %s: Failed to reset calibration for %s: %s", + self.device_name, + cal_entity, + e, + ) + + # 2. Child lock sync setting + child_lock_setting = self.real_trvs[entity_id]["advanced"].get("child_lock") + if child_lock_setting is not None: + # Look for switch (Z2M) or lock + cl_entity = find_entity( + ["switch", "lock"], ["child_lock", "child lock", "lock"] + ) + if cl_entity: + target_state = STATE_ON if child_lock_setting else STATE_OFF + domain = cl_entity.split(".")[0] + + try: + if domain == "switch": + cur = self.hass.states.get(cl_entity) + if cur and cur.state != target_state: + _LOGGER.debug( + "better_thermostat %s: Setting child lock (switch) for %s to %s", + self.device_name, + cl_entity, + target_state, + ) + service = "turn_on" if child_lock_setting else "turn_off" + await self.hass.services.async_call( + "switch", service, {"entity_id": cl_entity} + ) + elif domain == "lock": + target_lock = ( + LockState.LOCKED + if child_lock_setting + else LockState.UNLOCKED + ) + cur = self.hass.states.get(cl_entity) + if cur and cur.state != target_lock.value: + _LOGGER.debug( + "better_thermostat %s: Setting child lock (lock) for %s to %s", + self.device_name, + cl_entity, + target_lock, + ) + service = "lock" if child_lock_setting else "unlock" + await self.hass.services.async_call( + "lock", service, {"entity_id": cl_entity} + ) + except Exception as e: + _LOGGER.warning( + "better_thermostat %s: Failed to set child lock for %s: %s", + self.device_name, + cl_entity, + e, + ) + + # 3. Away / Window detection -> Off + # Window detection disable the interal trv window detection, its handled by better_thermostat + win_entity = find_entity( + ["switch"], + ["window_detection", "window_open", "window open", "open_window"], + ) + if win_entity: + try: + cur = self.hass.states.get(win_entity) + if cur and cur.state != STATE_OFF: + _LOGGER.debug( + "better_thermostat %s: Disabling window detection for %s", + self.device_name, + win_entity, + ) + await self.hass.services.async_call( + "switch", "turn_off", {"entity_id": win_entity} + ) + except Exception as e: + _LOGGER.warning( + "better_thermostat %s: Failed to disable window detection for %s: %s", + self.device_name, + win_entity, + e, + ) + + # Away mode -> Off + # Disable the away mode on the device if available + away_entity = find_entity( + ["switch"], ["away_mode", "away mode", "holiday_mode", "holiday"] + ) + if away_entity: + try: + cur = self.hass.states.get(away_entity) + if cur and cur.state != STATE_OFF: + _LOGGER.debug( + "better_thermostat %s: Disabling away mode for %s", + self.device_name, + away_entity, + ) + await self.hass.services.async_call( + "switch", "turn_off", {"entity_id": away_entity} + ) + except Exception as e: + _LOGGER.warning( + "better_thermostat %s: Failed to disable away mode for %s: %s", + self.device_name, + away_entity, + e, + ) diff --git a/custom_components/better_thermostat/services.yaml b/custom_components/better_thermostat/services.yaml index d55dd13..b8f360e 100644 --- a/custom_components/better_thermostat/services.yaml +++ b/custom_components/better_thermostat/services.yaml @@ -1,14 +1,6 @@ -save_current_target_temperature: - name: Save target temperature - description: Save the current target temperature for later use. - target: - entity: - domain: climate - integration: better_thermostat - -restore_saved_target_temperature: - name: Restore target temperature - description: Restore the previously saved target temperature. +run_valve_maintenance: + name: Run valve maintenance now + description: Run the valve maintenance cycle immediately for this Better Thermostat entity. target: entity: domain: climate @@ -22,21 +14,51 @@ reset_heating_power: domain: climate integration: better_thermostat -set_temp_target_temperature: - name: Set temporary (ECO) target temperature - description: Set a temporary target temperature and save the current temperature. +reset_pid_learnings: + name: Reset PID learnings + description: Clear all learned PID state (and optionally learned open caps) for this entity. fields: - temperature: - name: Temperature - description: New target temperature to set - required: true + include_open_caps: + name: Also clear learned open caps + description: If enabled, also clears learned min/max open percentages per TRV/target bucket. + required: false + default: false + selector: + boolean: {} + apply_pid_defaults: + name: Apply PID defaults after reset + description: If enabled, seeds the current target bucket(s) with PID default gains (kp/ki/kd). You can override defaults below. + required: false + default: false + selector: + boolean: {} + defaults_kp: + name: Default kp + description: Proportional gain to seed after reset (falls back to BalanceParams.kp if not provided). + required: false selector: number: min: 0 - max: 35 - step: 0.5 - mode: box - unit_of_measurement: °C + max: 1000 + step: 0.1 + defaults_ki: + name: Default ki + description: Integral gain to seed after reset (falls back to BalanceParams.ki if not provided). + required: false + selector: + number: + min: 0 + max: 1 + step: 0.001 + defaults_kd: + name: Default kd + description: Derivative gain to seed after reset (falls back to BalanceParams.kd if not provided). + required: false + selector: + number: + min: 0 + max: 10000 + step: 10 target: entity: domain: climate diff --git a/custom_components/better_thermostat/strings.json b/custom_components/better_thermostat/strings.json index 2593d5d..79e7927 100644 --- a/custom_components/better_thermostat/strings.json +++ b/custom_components/better_thermostat/strings.json @@ -1,128 +1,238 @@ { - "config": { - "step": { - "user": { - "description": "Setup your Better Thermostat to integrate with Home Assistant\n**If you need more info: https://better-thermostat.org/configuration#first-step**", - "data": { - "name": "Name", - "thermostat": "The real thermostat", - "cooler": "The cooling device (optional)", - "temperature_sensor": "Temperature sensor", - "humidity_sensor": "Humidity sensor", - "window_sensors": "Window sensor", - "off_temperature": "The outdoor temperature when the thermostat should turn off", - "tolerance": "Tolerance, to prevent the thermostat from turning on and off too often", - "window_off_delay": "Delay before the thermostat should turn off when the window is opened", - "window_off_delay_after": "Delay before the thermostat should turn on when the window is closed", - "outdoor_sensor": "Outdoor temperature sensor", - "weather": "Weather entity to get the outdoor temperature" - } - }, - "advanced": { - "description": "Advanced configuration {trv}\n***Info about calibration types: https://better-thermostat.org/configuration#second-step***", - "data": { - "protect_overheating": "Overheating protection?", - "heat_auto_swapped": "If 'auto' means 'heat' for your TRV and you want to swap it", - "child_lock": "Ignore all inputs on the TRV like a child lock", - "homematicip": "If you use HomematicIP, you should enable this to slow down the requests to prevent the duty cycle", - "valve_maintenance": "If your thermostat has no own maintenance mode, you can use this one", - "calibration": "Calibration type", - "calibration_mode": "Calibration mode", - "no_off_system_mode": "If your TRV doesn't support the 'off' mode, enable this to use target temperature 5°C instead" - }, - "data_description": { - "protect_overheating": "Some TRVs don't close the valve completely when the temperature is reached. Or the radiator has a lot of rest heat. This can cause overheating. This option can prevent this.", - "calibration_mode": "How the calibration is calculated\n***Normal***: In this mode, the TRV internal temperature sensor is fixed by the external temperature sensor.\n***Aggresive***: In this mode, the TRV internal temperature sensor is fixed by the external temperature sensor but set much lower/higher to get a quicker boost.\n***AI Time Based***: In this mode, the TRV internal temperature sensor is fixed by the external temperature sensor, but the value is calculated by a custom algorithm to improve the TRV internal algorithm.", - "calibration": "How the calibration is applied on the TRV\n***Target Temperature Based***: Apply the calibration to the target temperature.\n***Offset Based***: Apply the calibration to the temperature offset." - } - }, - "confirm": { - "title": "Confirm adding a Better Thermostat", - "description": "You are about to add `{name}` to Home Assistant.\nWith {trv} as the real Thermostat" - } - }, - "error": { - "failed": "Something went wrong.", - "no_name": "Please enter a name.", - "no_off_mode": "Your device is special and has no off mode :(\nBetter Thermostat will use the minimal target temp instead.", - "no_outside_temp": "You have no outside temperature sensor. Better Thermostat will use the weather entity instead." - }, - "abort": { - "single_instance_allowed": "Only a single Better Thermostat is allowed for each real thermostat.", - "no_devices_found": "No thermostat entity found, make sure you have a climate entity in your Home Assistant" - } + "title": "Better Thermostat", + "config": { + "step": { + "user": { + "description": "Setup your Better Thermostat to integrate with Home Assistant\n\n**Detailed information:** {docs_url}", + "data": { + "name": "Name", + "thermostat": "The real thermostat", + "cooler": "The cooling device (optional)", + "temperature_sensor": "Temperature sensor", + "humidity_sensor": "Humidity sensor", + "window_sensors": "Window sensor", + "off_temperature": "The outdoor temperature when the thermostat should turn off", + "tolerance": "Tolerance, to prevent the thermostat from turning on and off too often", + "window_off_delay": "Delay before the thermostat should turn off when the window is opened", + "window_off_delay_after": "Delay before the thermostat should turn on when the window is closed", + "outdoor_sensor": "Outdoor temperature sensor", + "weather": "Weather entity to get the outdoor temperature", + "target_temp_step": "Target temperature step", + "presets": "Enabled Presets" }, - "options": { - "step": { - "user": { - "description": "Update your Better Thermostat settings", - "data": { - "name": "Name", - "thermostat": "The real thermostat", - "temperature_sensor": "Temperature Sensor", - "humidity_sensor": "Humidity sensor", - "window_sensors": "Window Sensor", - "off_temperature": "The outdoor temperature when the thermostat should turn off", - "tolerance": "Tolerance, to prevent the thermostat from turning on and off too often", - "window_off_delay": "Delay before the thermostat should turn off when the window is opened", - "window_off_delay_after": "Delay before the thermostat should turn on when the window is closed", - "outdoor_sensor": "Outdoor temperature sensor", - "weather": "Weather entity to get the outdoor temperature", - "valve_maintenance": "If your thermostat has no own maintenance mode, you can use this one", - "calibration": "The sort of calibration https://better-thermostat.org/configuration#second-step", - "heat_auto_swapped": "If the auto means heat for your TRV and you want to swap it", - "child_lock": "Ignore all inputs on the TRV like a child lock", - "homematicip": "If you use HomematicIP, you should enable this to slow down the requests to prevent the duty cycle" - } - }, - "advanced": { - "description": "Advanced configuration {trv}\n***Info about calibration types: https://better-thermostat.org/configuration#second-step***", - "data": { - "protect_overheating": "Overheating protection?", - "heat_auto_swapped": "If the auto means heat for your TRV and you want to swap it", - "child_lock": "Ignore all inputs on the TRV like a child lock", - "homematicip": "If you use HomematicIP, you should enable this to slow down the requests to prevent the duty cycle", - "valve_maintenance": "If your thermostat has no own maintenance mode, you can use this one", - "calibration": "Calibration type", - "calibration_mode": "Calibration mode", - "no_off_system_mode": "If your TRV doesn't support the 'off' mode, enable this to use target temperature 5°C instead" - }, - "data_description": { - "protect_overheating": "Some TRVs don't close the valve completely when the temperature is reached. Or the radiator has a lot of rest heat. This can cause overheating. This option can prevent this.", - "calibration_mode": "How the calibration is calculated\n***Normal***: In this mode, the TRV internal temperature sensor is fixed by the external temperature sensor.\n***Aggresive***: In this mode, the TRV internal temperature sensor is fixed by the external temperature sensor but set much lower/higher to get a quicker boost.\n***AI Time Based***: In this mode, the TRV internal temperature sensor is fixed by the external temperature sensor, but the value is calculated by a custom algorithm to improve the TRV internal algorithm.", - "calibration": "How the calibration is applied on the TRV\n***Target Temperature Based***: Apply the calibration to the target temperature.\n***Offset Based***: Apply the calibration to the temperature offset." } - } - } - }, - "issues": { - "missing_entity": { - "title": "BT: {name} - related entity is missing", - "fix_flow": { - "step": { - "confirm": { - "title": "The related entity {entity} is missing", - "description": "The reason for this is that the entity ({entity}) is not available in your Home Assistant.\n\nYou can fix this by checking if the battery of the device is full or reconnecting it to HA. Make sure that the entity is back in HA before you continue." - } - } - } - } - }, - "services": { - "save_current_target_temperature": { - "name": "Save current Temperature", - "description": "Save the current target temperature for later restore." - }, - "restore_saved_target_temperature": { - "name": "Restore temperature", - "description": "Restore the saved target temperature." - }, - "reset_heating_power": { - "name": "Reset heating power", - "description": "Reset heating power to default value." - }, - "set_temp_target_temperature": { - "name": "Set eco temperature", - "description": "Set the target temperature to a temporary like night mode, and save the old one." - } + "data_description": { + "presets": "Select the presets you want to be available for this thermostat." } + }, + "advanced": { + "description": "Advanced configuration {trv}\n\n**Detailed information:** {docs_url}", + "data": { + "protect_overheating": "Overheating protection?", + "heat_auto_swapped": "If 'auto' means 'heat' for your TRV and you want to swap it", + "child_lock": "Ignore all inputs on the TRV like a child lock", + "homematicip": "If you use HomematicIP, you should enable this to slow down the requests to prevent the duty cycle", + "valve_maintenance": "If your thermostat has no own maintenance mode, you can use this one", + "calibration": "Calibration type", + "calibration_mode": "Calibration mode", + "no_off_system_mode": "If your TRV doesn't support the 'off' mode, enable this to use target temperature 5°C instead" + }, + "data_description": { + "protect_overheating": "Some TRVs don't close the valve completely when the temperature is reached. Or the radiator has a lot of rest heat. This can cause overheating. This option can prevent this.", + "calibration_mode": "How the calibration is calculated\n***Normal***: Uses your external temperature sensor to correct the TRV's internal sensor. Simple and reliable for most setups.\n***Aggressive***: Similar to Normal, but pushes the TRV harder to reach temperature faster. Good if your room heats slowly.\n***AI Time Based***: Learns your room's heating patterns over time and adjusts automatically. Balances comfort and efficiency.\n***MPC Predictive***: Advanced algorithm that predicts temperature changes and optimizes valve position. Best for stable, efficient heating with minimal overshooting. Learns your room's thermal behavior.\n***PID Controller***: Classic control method that responds to temperature errors. Good for systems with varying heating power or external disturbances.\n***TPI Controller***: Calculates heating duty cycles based on temperature difference. Simple and effective for consistent heating patterns.", + "calibration": "How the calibration is applied on the TRV\n***Target Temperature Based***: Apply the calibration to the target temperature.\n***Offset Based***: Apply the calibration to the temperature offset." + } + }, + "confirm": { + "title": "Confirm adding a Better Thermostat", + "description": "You are about to add `{name}` to Home Assistant.\nWith {trv} as the real Thermostat" + } + }, + "error": { + "failed": "Something went wrong.", + "no_name": "Please enter a name.", + "no_off_mode": "Your device is special and has no off mode :(\nBetter Thermostat will use the minimal target temp instead.", + "no_outside_temp": "You have no outside temperature sensor. Better Thermostat will use the weather entity instead." + }, + "abort": { + "single_instance_allowed": "Only a single Better Thermostat is allowed for each real thermostat.", + "no_devices_found": "No thermostat entity found, make sure you have a climate entity in your Home Assistant" + } + }, + "options": { + "step": { + "user": { + "description": "Update your Better Thermostat settings\n\n**Detailed information:** {docs_url}", + "data": { + "name": "Name", + "thermostat": "The real thermostat", + "temperature_sensor": "Temperature Sensor", + "humidity_sensor": "Humidity sensor", + "window_sensors": "Window Sensor", + "off_temperature": "The outdoor temperature when the thermostat should turn off", + "tolerance": "Tolerance, to prevent the thermostat from turning on and off too often", + "window_off_delay": "Delay before the thermostat should turn off when the window is opened", + "window_off_delay_after": "Delay before the thermostat should turn on when the window is closed", + "outdoor_sensor": "Outdoor temperature sensor", + "weather": "Weather entity to get the outdoor temperature", + "target_temp_step": "Target temperature step", + "presets": "Enabled Presets", + "valve_maintenance": "If your thermostat has no own maintenance mode, you can use this one", + "calibration": "The sort of calibration", + "heat_auto_swapped": "If 'auto' means 'heat' for your TRV and you want to swap it", + "child_lock": "Ignore all inputs on the TRV like a child lock", + "homematicip": "If you use HomematicIP, you should enable this to slow down the requests to prevent the duty cycle" + }, + "data_description": { + "presets": "Select the presets you want to be available for this thermostat." + } + }, + "advanced": { + "description": "Advanced configuration {trv}\n\n**Detailed information:** {docs_url}", + "data": { + "protect_overheating": "Overheating protection?", + "heat_auto_swapped": "If 'auto' means 'heat' for your TRV and you want to swap it", + "child_lock": "Ignore all inputs on the TRV like a child lock", + "homematicip": "If you use HomematicIP, you should enable this to slow down the requests to prevent the duty cycle", + "valve_maintenance": "If your thermostat has no own maintenance mode, you can use this one", + "calibration": "Calibration type", + "calibration_mode": "Calibration mode", + "no_off_system_mode": "If your TRV doesn't support the 'off' mode, enable this to use target temperature 5°C instead" + }, + "data_description": { + "protect_overheating": "Some TRVs don't close the valve completely when the temperature is reached. Or the radiator has a lot of rest heat. This can cause overheating. This option can prevent this.", + "calibration_mode": "How the calibration is calculated\n***Normal***: Uses your external temperature sensor to correct the TRV's internal sensor. Simple and reliable for most setups.\n***Aggressive***: Similar to Normal, but pushes the TRV harder to reach temperature faster. Good if your room heats slowly.\n***AI Time Based***: Learns your room's heating patterns over time and adjusts automatically. Balances comfort and efficiency.\n***MPC Predictive***: Advanced algorithm that predicts temperature changes and optimizes valve position. Best for stable, efficient heating with minimal overshooting. Learns your room's thermal behavior.\n***PID Controller***: Classic control method that responds to temperature errors. Good for systems with varying heating power or external disturbances.\n***TPI Controller***: Calculates heating duty cycles based on temperature difference. Simple and effective for consistent heating patterns.", + "calibration": "How the calibration is applied on the TRV\n***Target Temperature Based***: Apply the calibration to the target temperature.\n***Offset Based***: Apply the calibration to the temperature offset." + } + } + } + }, + "issues": { + "missing_entity": { + "title": "BT: {name} - related entity is missing", + "fix_flow": { + "step": { + "confirm": { + "title": "The related entity {entity} is missing", + "description": "The reason for this is that the entity ({entity}) is not available in your Home Assistant.\n\nYou can fix this by checking if the battery of the device is full or reconnecting it to HA. Make sure that the entity is back in HA before you continue." + } + } + } + }, + "degraded_mode": { + "title": "BT: {name} - sensor error: {sensors} running in degraded mode", + "description": "Better Thermostat is running in degraded mode because some sensors are unavailable: {sensors}\n\nThe thermostat will continue to operate with reduced functionality. For example:\n- If the room temperature sensor is unavailable, the TRV's internal sensor will be used instead.\n- If window sensors are unavailable, windows are assumed to be closed.\n- If outdoor/weather sensors are unavailable, heating will always be enabled.\n\nCheck if the affected devices have battery issues or connectivity problems." + }, + "invalid_window_state": { + "title": "BT: {name} - invalid window sensor state", + "description": "The window sensor for Better Thermostat {name} reported an unrecognized state: {state}.\n\nExpected states are: 'on', 'off', 'true', 'false', 'open', 'closed'.\n\nPlease check your window sensor configuration." + } + }, + "services": { + "run_valve_maintenance": { + "name": "Run valve maintenance now", + "description": "Run the valve maintenance cycle immediately for this Better Thermostat entity." + }, + "reset_heating_power": { + "name": "Reset heating power", + "description": "Reset heating power to default value." + }, + "reset_pid_learnings": { + "name": "Reset PID learnings", + "description": "Clear all learned PID state (and optionally learned open caps) for this entity.", + "fields": { + "include_open_caps": { + "name": "Also clear learned open caps", + "description": "If enabled, also clears learned min/max open percentages per TRV/target bucket." + }, + "apply_pid_defaults": { + "name": "Apply PID defaults after reset", + "description": "If enabled, seeds the current target bucket(s) with PID default gains (kp/ki/kd). You can override defaults below." + }, + "defaults_kp": { + "name": "Default kp", + "description": "Proportional gain to seed after reset (falls back to BalanceParams.kp if not provided)." + }, + "defaults_ki": { + "name": "Default ki", + "description": "Integral gain to seed after reset (falls back to BalanceParams.ki if not provided)." + }, + "defaults_kd": { + "name": "Default kd", + "description": "Derivative gain to seed after reset (falls back to BalanceParams.kd if not provided)." + } + } + } + }, + "selector": { + "presets": { + "options": { + "eco": "Eco", + "away": "Away", + "boost": "Boost", + "comfort": "Comfort", + "home": "Home", + "sleep": "Sleep", + "activity": "Activity" + } + } + }, + "entity": { + "number": { + "pid_kp": { + "name": "{trv_name} PID Kp (Proportional)" + }, + "pid_ki": { + "name": "{trv_name} PID Ki (Integral)" + }, + "pid_kd": { + "name": "{trv_name} PID Kd (Derivative)" + }, + "pid_kp_no_trv": { + "name": "PID Kp (Proportional)" + }, + "pid_ki_no_trv": { + "name": "PID Ki (Integral)" + }, + "pid_kd_no_trv": { + "name": "PID Kd (Derivative)" + } + }, + "switch": { + "pid_auto_tune": { + "name": "{trv_name} PID Auto Tune" + }, + "pid_auto_tune_no_trv": { + "name": "PID Auto Tune" + }, + "child_lock": { + "name": "{trv_name} Child Lock" + }, + "child_lock_no_trv": { + "name": "Child Lock" + } + } + }, + "device_automation": { + "trigger_type": { + "heating_active": "Thermostat switches to heating", + "heating_stopped": "Thermostat stops heating", + "humidity_high": "Humidity exceeds threshold", + "window_opened": "Window opened", + "window_closed": "Window closed", + "battery_low": "Battery low", + "device_error": "Device error detected", + "target_temp_reached": "Target temperature reached", + "hvac_mode_changed": "HVAC mode changed", + "current_temperature_changed": "Current temperature changed", + "current_humidity_changed": "Current humidity changed" + }, + "condition_type": { + "is_hvac_mode": "Thermostat is set to a specific HVAC mode", + "is_hvac_action": "Thermostat is performing a specific HVAC action" + }, + "action_type": { + "set_hvac_mode": "Set HVAC mode", + "set_temperature": "Set target temperature" + } + } } diff --git a/custom_components/better_thermostat/translations/da.json b/custom_components/better_thermostat/translations/da.json index 1f9c577..0507486 100644 --- a/custom_components/better_thermostat/translations/da.json +++ b/custom_components/better_thermostat/translations/da.json @@ -3,7 +3,7 @@ "config": { "step": { "user": { - "description": "Konfigurer din Better Thermostat til at integrere med Home Assistant\n**Hvis du har brug for mere info: https://better-thermostat.org/configuration#first-step**", + "description": "Konfigurer din Better Thermostat til at integrere med Home Assistant\n\n**Detaljerede oplysninger:** {docs_url}", "data": { "name": "Navn", "thermostat": "Den rigtige termostat", @@ -16,11 +16,16 @@ "window_off_delay": "Forsinkelse, før termostaten slukker, når vinduet er åbent, og tændt, når vinduet er lukket", "window_off_delay_after": "Forsinkelse, før termostaten tænder, når vinduet er lukket", "outdoor_sensor": "Hvis du har en udeføler, kan du bruge den til at få udetemperaturen", - "weather": "Din vejrentitet for at få udendørstemperaturen" + "weather": "Din vejrentitet for at få udendørstemperaturen", + "target_temp_step": "Trin for måltemperatur", + "presets": "Aktiverede forudindstillinger" + }, + "data_description": { + "presets": "Vælg de forudindstillinger, der skal være tilgængelige for denne termostat." } }, "advanced": { - "description": "Avanceret konfiguration {trv}\n***Info om kalibreringstyper: https://better-thermostat.org/configuration#second-step*** ", + "description": "Avanceret konfiguration {trv}\n\n**Detaljerede oplysninger:** {docs_url}", "data": { "protect_overheating": "Overophedningsbeskyttelse?", "heat_auto_swapped": "Hvis auto betyder varme for din TRV og du vil bytte det", @@ -56,7 +61,7 @@ "options": { "step": { "user": { - "description": "Opdater dine Better Thermostat indstillinger", + "description": "Opdater dine Better Thermostat indstillinger\n\n**Detaljerede oplysninger:** {docs_url}", "data": { "name": "Navn", "thermostat": "Den rigtige termostat", @@ -68,16 +73,21 @@ "window_off_delay": "Forsinkelse, før termostaten slukker, når vinduet er åbent, og tændt, når vinduet er lukket", "window_off_delay_after": "Forsinkelse, før termostaten tænder, når vinduet er lukket", "outdoor_sensor": "Har du en udendørsføler, kan du bruge den til at få udetemperaturen", - "valve_maintenance": "Hvis din termostat ikke har nogen egen vedligeholdelsestilstand, kan du bruge denne", - "calibration": "Den slags kalibrering https://better-thermostat.org/configuration#second-step", "weather": "Din vejrentitet for at få udendørstemperaturen", + "target_temp_step": "Trin for måltemperatur", + "presets": "Aktiverede forudindstillinger", + "valve_maintenance": "Hvis din termostat ikke har nogen egen vedligeholdelsestilstand, kan du bruge denne", + "calibration": "Den slags kalibrering", "heat_auto_swapped": "Hvis auto betyder varme til din TRV, og du ønsker at bytte den", "child_lock": "Ignorer alle input på TRV som en børnesikring", - "homaticip": "Hvis du bruger HomematicIP, bør du aktivere dette for at bremse anmodningerne for at forhindre arbejdscyklus" + "homematicip": "Hvis du bruger HomematicIP, bør du aktivere dette for at bremse anmodningerne for at forhindre arbejdscyklus" + }, + "data_description": { + "presets": "Vælg de forudindstillinger, der skal være tilgængelige for denne termostat." } }, "advanced": { - "description": "Avanceret konfiguration {trv}\n***Info om kalibreringstyper: https://better-thermostat.org/configuration#second-step*** ", + "description": "Avanceret konfiguration {trv}\n\n**Detaljerede oplysninger:** {docs_url}", "data": { "protect_overheating": "Overophedningsbeskyttelse?", "heat_auto_swapped": "Hvis auto betyder varme for din TRV og du vil bytte det", @@ -107,24 +117,122 @@ } } } + }, + "degraded_mode": { + "title": "BT: {name} - sensorfejl: {sensors} kører i nedsat tilstand", + "description": "Better Thermostat kører i nedsat tilstand, fordi nogle sensorer ikke er tilgængelige: {sensors}\n\nTermostaten vil fortsætte med at fungere med reduceret funktionalitet. For eksempel:\n- Hvis rumtemperatursensoren ikke er tilgængelig, bruges TRV'ens interne sensor i stedet.\n- Hvis vinduesensorer ikke er tilgængelige, antages vinduer at være lukkede.\n- Hvis udendørs-/vejrsensorer ikke er tilgængelige, vil opvarmning altid være aktiveret.\n\nKontroller, om de berørte enheder har batteriproblemer eller forbindelsesproblemer." + }, + "invalid_window_state": { + "title": "BT: {name} - ugyldig vinduesensortilstand", + "description": "Vinduesensoren for Better Thermostat {name} rapporterede en ukendt tilstand: {state}.\n\nForventede tilstande er: 'on', 'off', 'true', 'false', 'open', 'closed'.\n\nKontroller din vinduesensorkonfiguration." } }, "services": { - "save_current_target_temperature": { - "name": "Gem nuværende temperatur", - "description": "Gem den nuværende måltemperatur til senere gendannelse." - }, - "restore_saved_target_temperature": { - "name": "Gendan temperatur", - "description": "Gendan den gemte måltemperatur." + "run_valve_maintenance": { + "name": "Kør ventilvedligeholdelse nu", + "description": "Kører ventilvedligeholdelsescyklussen for denne Better Thermostat-enhed med det samme." }, "reset_heating_power": { "name": "Nulstil opvarmningskraft", "description": "Nulstil opvarmningskraft til standardværdi." }, - "set_temp_target_temperature": { - "name": "Indstil eco-temperatur", - "description": "Indstil måltemperaturen til en midlertidig som nattilstand, og gem den gamle." + "reset_pid_learnings": { + "name": "Nulstil PID-læring", + "description": "Rydder al lært PID-tilstand (og eventuelt lærte åbningsgrænser) for denne enhed.", + "fields": { + "include_open_caps": { + "name": "Ryd også lærte åbningsgrænser", + "description": "Hvis aktiveret, ryddes også de lærte minimums-/maksimumsåbningsprocenter pr. TRV/målinterval." + }, + "apply_pid_defaults": { + "name": "Anvend PID-standardværdier efter nulstilling", + "description": "Hvis aktiveret, forudfyldes de aktuelle målintervaller med PID-standardforstærkninger (kp/ki/kd). Du kan tilsidesætte standardværdierne nedenfor." + }, + "defaults_kp": { + "name": "Standard-kp", + "description": "Proportionalforstærkning, der sættes efter nulstilling (falder tilbage til BalanceParams.kp, hvis ikke angivet)." + }, + "defaults_ki": { + "name": "Standard-ki", + "description": "Integralforstærkning, der sættes efter nulstilling (falder tilbage til BalanceParams.ki, hvis ikke angivet)." + }, + "defaults_kd": { + "name": "Standard-kd", + "description": "Differentialforstærkning, der sættes efter nulstilling (falder tilbage til BalanceParams.kd, hvis ikke angivet)." + } + } + } + }, + "selector": { + "presets": { + "options": { + "eco": "Eco", + "away": "Ude", + "boost": "Boost", + "comfort": "Komfort", + "home": "Hjemme", + "sleep": "Søvn", + "activity": "Aktivitet" + } + } + }, + "entity": { + "number": { + "pid_kp": { + "name": "{trv_name} PID Kp (proportional)" + }, + "pid_ki": { + "name": "{trv_name} PID Ki (integral)" + }, + "pid_kd": { + "name": "{trv_name} PID Kd (differentiel)" + }, + "pid_kp_no_trv": { + "name": "PID Kp (proportional)" + }, + "pid_ki_no_trv": { + "name": "PID Ki (integral)" + }, + "pid_kd_no_trv": { + "name": "PID Kd (differentiel)" + } + }, + "switch": { + "pid_auto_tune": { + "name": "{trv_name} PID-autotuning" + }, + "pid_auto_tune_no_trv": { + "name": "PID-autotuning" + }, + "child_lock": { + "name": "{trv_name} Børnesikring" + }, + "child_lock_no_trv": { + "name": "Børnesikring" + } + } + }, + "device_automation": { + "trigger_type": { + "heating_active": "Termostat skifter til opvarmning", + "heating_stopped": "Termostat stopper opvarmning", + "humidity_high": "Luftfugtighed overstiger grænseværdi", + "window_opened": "Vindue åbnet", + "window_closed": "Vindue lukket", + "battery_low": "Batteri lavt", + "device_error": "Enhedsfejl registreret", + "target_temp_reached": "Måltemperatur nået", + "hvac_mode_changed": "HVAC-tilstand ændret", + "current_temperature_changed": "Aktuel temperatur ændret", + "current_humidity_changed": "Aktuel luftfugtighed ændret" + }, + "condition_type": { + "is_hvac_mode": "Termostaten er indstillet til en bestemt HVAC-tilstand", + "is_hvac_action": "Termostaten udfører en bestemt HVAC-handling" + }, + "action_type": { + "set_hvac_mode": "Indstil HVAC-tilstand", + "set_temperature": "Indstil måltemperatur" } } } diff --git a/custom_components/better_thermostat/translations/de.json b/custom_components/better_thermostat/translations/de.json index 5829603..85f8553 100644 --- a/custom_components/better_thermostat/translations/de.json +++ b/custom_components/better_thermostat/translations/de.json @@ -3,7 +3,7 @@ "config": { "step": { "user": { - "description": "Einrichtung von Better Thermostat mit Home Assistant\n**Für mehr Informationen: https://better-thermostat.org/configuration#first-step**", + "description": "Einrichtung von Better Thermostat mit Home Assistant\n\n**Detaillierte Infos:** {docs_url}", "data": { "name": "Name", "thermostat": "Das reale Thermostat", @@ -16,11 +16,16 @@ "window_off_delay": "Wartezeit, bevor das Thermostat bei geöffnetem Fenster abgeschaltet wird.", "window_off_delay_after": "Wartezeit, bevor das Thermostat bei geschlossenem Fenster angeschaltet wird.", "outdoor_sensor": "Wenn ein Außentemperatursensor vorhanden ist, kann dieser anstelle der Wetter-Entität genutzt werden.", - "weather": "Die Wetter-Entität für die Außentemperatur." + "weather": "Die Wetter-Entität für die Außentemperatur.", + "target_temp_step": "Solltemperatur-Schritt", + "presets": "Aktivierte Presets" + }, + "data_description": { + "presets": "Wähle die Presets aus, die für dieses Thermostat verfügbar sein sollen." } }, "advanced": { - "description": "Einstellungen für {trv}\n\n***Infos über die Kalibrierungstypen: https://better-thermostat.org/configuration#second-step*** ", + "description": "Einstellungen für {trv}\n\n**Detaillierte Infos:** {docs_url}", "data": { "protect_overheating": "Überhitzung verhindern?", "heat_auto_swapped": "Tauscht die Modi auto und heat, falls diese bei dem realen Thermostat vertauscht sind.", @@ -33,7 +38,7 @@ }, "data_description": { "protect_overheating": "Manche TRVs schließen auch nach Erreichen der Temperatur das Ventil nicht vollständig. Dies kann zu Überhitzungen führen. Ebenso, falls der Radiator viel Restwärme abstrahlt. Diese Option kann dies verhindern.", - "calibration_mode": "Wie die Kalibrierung berechnet wird\n***Normal***: In diesem Modus wird die interne TRV-Temperatur an die des externen Sensors angeglichen.\n\n***Aggresive***: In diesem Modus wird die interne TRV-Temperatur an die des externen Sensors angeglichen - allerdings mit größeren Werten. Dies ist hilfreich, wenn ein Raum schnell aufgeheizt werden soll oder das TRV träge ist.\n\n***AI Time Based***: In diesem Modus wird ein eigener Algorithmus genutzt, der anhand des externen Temperatursensors die Kalibrierung berechnet. Dieser Modus versucht den TRV internen Algorithmus zu optimieren.", + "calibration_mode": "Wie die Kalibrierung berechnet wird\n***Normal***: In diesem Modus wird die interne TRV-Temperatur an die des externen Sensors angeglichen.\n\n***Aggressive***: In diesem Modus wird die interne TRV-Temperatur an die des externen Sensors angeglichen - allerdings mit größeren Werten. Dies ist hilfreich, wenn ein Raum schnell aufgeheizt werden soll oder das TRV träge ist.\n\n***AI Time Based***: In diesem Modus wird ein eigener Algorithmus genutzt, der anhand des externen Temperatursensors die Kalibrierung berechnet. Dieser Modus versucht den TRV internen Algorithmus zu optimieren.\n\n***MPC Predictive (Beta)***: Nutzt den MPC-Prädiktivregler, um eine Ventilöffnung vorherzusagen und wandelt sie bei fehlender Ventilsteuerung in Temperatur- oder Offset-Anpassungen um.\n\n***PID Controller***: Verwendet einen PID-Regler, um die Ventilposition dynamisch basierend auf Temperaturfehler, proportionalem Anteil, integraler Akkumulation und derivativem Vorhersage anzupassen.\n\n***TPI Controller***: Verwendet einen Zeit-Proportional-Integral-Regler für stabile und präzise Temperatursteuerung über die Zeit.", "calibration": "Wie die Kalibrierung auf das TRV angewendet werden soll.\n\n***Target Temperature Based***: Kalibriert das TRV über die Zieltemperatur.\n\n***Offset Based***: Kalibriert das TRV über eine Offset Funktion im TRV selbst. (Empfohlen)" } }, @@ -43,10 +48,10 @@ } }, "error": { - "no_outside_temp": "Es kann keine Außentemperatur geladen werden.", "failed": "Ups, hier stimmt was nicht.", "no_name": "Du musst einen Namen vergeben.", - "no_off_mode": "Dein Gerät ist ein Sonderfall, es hat keinen OFF Modus :(\nBetter Thermostat wird stattdessen das TRV auf den Minimalwert setzen." + "no_off_mode": "Dein Gerät ist ein Sonderfall, es hat keinen OFF Modus :(\nBetter Thermostat wird stattdessen das TRV auf den Minimalwert setzen.", + "no_outside_temp": "Es kann keine Außentemperatur geladen werden." }, "abort": { "single_instance_allowed": "Es ist nur ein einzelnes BT je realem Thermostat erlaubt.", @@ -56,7 +61,7 @@ "options": { "step": { "user": { - "description": "Aktualisiere die Better Thermostat Einstellungen", + "description": "Aktualisiere die Better Thermostat Einstellungen\n\n**Detaillierte Infos:** {docs_url}", "data": { "name": "Name", "thermostat": "Das reale Thermostat", @@ -69,15 +74,20 @@ "window_off_delay_after": "Wartezeit, bevor das Thermostat bei geschlossenem Fenster angeschaltet wird.", "outdoor_sensor": "Wenn ein Außentemperatursensor vorhanden ist, kann dieser anstelle der Wetter-Entität genutzt werden.", "weather": "Die Wetter-Entität für die Außentemperatur.", + "target_temp_step": "Solltemperatur-Schritt", + "presets": "Aktivierte Presets", "valve_maintenance": "Wenn Ihr Thermostat keinen eigenen Wartungsmodus hat, können Sie diesen verwenden.", - "child_lock": "Ignorieren Sie alle Eingaben am TRV wie eine Kindersicherung.", - "homematicip": "Wenn Sie HomematicIP verwenden, sollten Sie dies aktivieren, um die Anfragen zu verlangsamen und den Duty Cycle zu verhindern.", + "calibration": "Die Art der Kalibrierung", "heat_auto_swapped": "Wenn das Auto Wärme für Ihr TRV bedeutet und Sie es austauschen möchten.", - "calibration": "Die Art der Kalibrierung https://better-thermostat.org/configuration#second-step" + "child_lock": "Ignorieren Sie alle Eingaben am TRV wie eine Kindersicherung.", + "homematicip": "Wenn Sie HomematicIP verwenden, sollten Sie dies aktivieren, um die Anfragen zu verlangsamen und den Duty Cycle zu verhindern." + }, + "data_description": { + "presets": "Wähle die Presets aus, die für dieses Thermostat verfügbar sein sollen." } }, "advanced": { - "description": "Aktualisiere die Einstellungen für {trv}\n\n***Infos über die Kalibrierungstypen: https://better-thermostat.org/configuration#second-step*** ", + "description": "Aktualisiere die Einstellungen für {trv}\n\n**Detaillierte Infos:** {docs_url}", "data": { "protect_overheating": "Überhitzung verhindern?", "heat_auto_swapped": "Tauscht die Modi auto und heat, falls diese bei dem realen Thermostat vertauscht sind.", @@ -90,7 +100,7 @@ }, "data_description": { "protect_overheating": "Manche TRVs schließen auch nach Erreichen der Temperatur das Ventil nicht vollständig. Dies kann zu Überhitzungen führen. Ebenso falls der Radiator viel Restwärme abstrahlt. Diese Option kann dies verhindern.", - "calibration_mode": "Wie die Kalibrierung berechnet wird\n***Normal***: In diesem Modus wird die interne TRV-Temperatur an die des externen Sensors angeglichen.\n\n***Aggresive***: In diesem Modus wird die interne TRV-Temperatur an die des externen Sensors angeglichen - allerdings mit größeren Werten. Dies ist hilfreich, wenn ein Raum schnell aufgeheizt werden soll oder das TRV träge ist.\n\n***AI Time Based***: In diesem Modus wird ein eigener Algorithmus genutzt, der anhand des externen Temperatursensors die Kalibrierung berechnet. Dieser Modus versucht den TRV internen Algorithmus zu optimieren.", + "calibration_mode": "Wie die Kalibrierung berechnet wird\n***Normal***: In diesem Modus wird die interne TRV-Temperatur an die des externen Sensors angeglichen.\n\n***Aggressive***: In diesem Modus wird die interne TRV-Temperatur an die des externen Sensors angeglichen - allerdings mit größeren Werten. Dies ist hilfreich, wenn ein Raum schnell aufgeheizt werden soll oder das TRV träge ist.\n\n***AI Time Based***: In diesem Modus wird ein eigener Algorithmus genutzt, der anhand des externen Temperatursensors die Kalibrierung berechnet. Dieser Modus versucht den TRV internen Algorithmus zu optimieren.\n\n***MPC Predictive (Beta)***: Nutzt den MPC-Prädiktivregler, um eine Ventilöffnung vorherzusagen und wandelt sie bei fehlender Ventilsteuerung in Temperatur- oder Offset-Anpassungen um.\n\n***PID Controller***: Verwendet einen PID-Regler, um die Ventilposition dynamisch basierend auf Temperaturfehler, proportionalem Anteil, integraler Akkumulation und derivativem Vorhersage anzupassen.\n\n***TPI Controller***: Verwendet einen Zeit-Proportional-Integral-Regler für stabile und präzise Temperatursteuerung über die Zeit.", "calibration": "Wie die Kalibrierung auf das TRV angewendet werden soll.\n\n***Target Temperature Based***: Kalibriert das TRV über die Zieltemperatur.\n\n***Offset Based***: Kalibriert das TRV über eine Offset Funktion im TRV selbst. (Empfohlen)" } } @@ -107,24 +117,122 @@ } } } + }, + "degraded_mode": { + "title": "BT: {name} - Sensorfehler: {sensors} läuft im eingeschränkten Modus", + "description": "Better Thermostat läuft im eingeschränkten Modus, da einige Sensoren nicht verfügbar sind: {sensors}\n\nDer Thermostat arbeitet mit reduzierter Funktionalität weiter. Zum Beispiel:\n- Wenn der Raumtemperatursensor nicht verfügbar ist, wird stattdessen der interne Sensor des TRV verwendet.\n- Wenn Fenstersensoren nicht verfügbar sind, wird angenommen, dass die Fenster geschlossen sind.\n- Wenn Außen-/Wettersensoren nicht verfügbar sind, wird die Heizung immer aktiviert sein.\n\nÜberprüfen Sie, ob die betroffenen Geräte Batterieprobleme oder Verbindungsprobleme haben." + }, + "invalid_window_state": { + "title": "BT: {name} - ungültiger Fenstersensor-Status", + "description": "Der Fenstersensor für Better Thermostat {name} hat einen unbekannten Status gemeldet: {state}.\n\nErwartete Status sind: 'on', 'off', 'true', 'false', 'open', 'closed'.\n\nBitte überprüfen Sie Ihre Fenstersensor-Konfiguration." } }, "services": { - "save_current_target_temperature": { - "name": "Speichere die aktuelle Zieltemperatur", - "description": "Speichert die aktuelle Zieltemperatur um sie zu einem späteren Zeitpunkt wiederherzustellen." - }, - "restore_saved_target_temperature": { - "name": "Stelle die Zieltemperatur wieder her", - "description": "Stellt die Zieltemperatur aus dem letzten gespeicherten Wert wieder her." + "run_valve_maintenance": { + "name": "Ventilwartung jetzt ausführen", + "description": "Führt den Ventilwartungszyklus für diese Better-Thermostat-Entität sofort aus." }, "reset_heating_power": { "name": "Setze 'Heating Power' zurück", "description": "Stellt die Heating Power auf den Startwert zurück." }, - "set_temp_target_temperature": { - "name": "Setze die Temperatur für ECO", - "description": "Speichert eine ECO Temperatur, die für den ECO Modus genutzt wird." + "reset_pid_learnings": { + "name": "PID-Lernwerte zurücksetzen", + "description": "Löscht alle gelernten PID-Werte (und optional gelernte Öffnungsgrenzen) für diese Entität.", + "fields": { + "include_open_caps": { + "name": "Auch gelernte Öffnungsgrenzen löschen", + "description": "Wenn aktiviert, werden auch die gelernten minimalen/maximalen Öffnungsprozente je TRV/Ziel-Bereich gelöscht." + }, + "apply_pid_defaults": { + "name": "PID-Standardwerte nach dem Zurücksetzen anwenden", + "description": "Wenn aktiviert, wird der aktuelle Ziel-Bereich mit den PID-Standardverstärkungen (kp/ki/kd) vorbelegt. Die Standardwerte können unten überschrieben werden." + }, + "defaults_kp": { + "name": "Standard-kp", + "description": "Proportionalverstärkung, die nach dem Zurücksetzen gesetzt wird (fällt auf BalanceParams.kp zurück, wenn nicht angegeben)." + }, + "defaults_ki": { + "name": "Standard-ki", + "description": "Integralverstärkung, die nach dem Zurücksetzen gesetzt wird (fällt auf BalanceParams.ki zurück, wenn nicht angegeben)." + }, + "defaults_kd": { + "name": "Standard-kd", + "description": "Differenzialverstärkung, die nach dem Zurücksetzen gesetzt wird (fällt auf BalanceParams.kd zurück, wenn nicht angegeben)." + } + } + } + }, + "selector": { + "presets": { + "options": { + "eco": "Eco", + "away": "Abwesend", + "boost": "Boost", + "comfort": "Komfort", + "home": "Zuhause", + "sleep": "Schlafen", + "activity": "Aktivität" + } + } + }, + "entity": { + "number": { + "pid_kp": { + "name": "{trv_name} PID Kp (Proportional)" + }, + "pid_ki": { + "name": "{trv_name} PID Ki (Integral)" + }, + "pid_kd": { + "name": "{trv_name} PID Kd (Differential)" + }, + "pid_kp_no_trv": { + "name": "PID Kp (Proportional)" + }, + "pid_ki_no_trv": { + "name": "PID Ki (Integral)" + }, + "pid_kd_no_trv": { + "name": "PID Kd (Differential)" + } + }, + "switch": { + "pid_auto_tune": { + "name": "{trv_name} PID Auto-Tuning" + }, + "pid_auto_tune_no_trv": { + "name": "PID Auto-Tuning" + }, + "child_lock": { + "name": "{trv_name} Kindersicherung" + }, + "child_lock_no_trv": { + "name": "Kindersicherung" + } + } + }, + "device_automation": { + "trigger_type": { + "heating_active": "Thermostat wechselt in Heizmodus", + "heating_stopped": "Thermostat stoppt Heizung", + "humidity_high": "Luftfeuchtigkeit überschreitet Schwellenwert", + "window_opened": "Fenster geöffnet", + "window_closed": "Fenster geschlossen", + "battery_low": "Batterie schwach", + "device_error": "Gerätefehler erkannt", + "target_temp_reached": "Zieltemperatur erreicht", + "hvac_mode_changed": "HVAC-Modus geändert", + "current_temperature_changed": "Aktuelle Temperatur geändert", + "current_humidity_changed": "Aktuelle Luftfeuchtigkeit geändert" + }, + "condition_type": { + "is_hvac_mode": "Thermostat ist auf einen bestimmten HVAC-Modus eingestellt", + "is_hvac_action": "Thermostat führt eine bestimmte HVAC-Aktion aus" + }, + "action_type": { + "set_hvac_mode": "HVAC-Modus setzen", + "set_temperature": "Zieltemperatur setzen" } } } diff --git a/custom_components/better_thermostat/translations/en.json b/custom_components/better_thermostat/translations/en.json index 2d9fceb..79e7927 100644 --- a/custom_components/better_thermostat/translations/en.json +++ b/custom_components/better_thermostat/translations/en.json @@ -3,7 +3,7 @@ "config": { "step": { "user": { - "description": "Setup your Better Thermostat to integrate with Home Assistant\n**If you need more info: https://better-thermostat.org/configuration#first-step**", + "description": "Setup your Better Thermostat to integrate with Home Assistant\n\n**Detailed information:** {docs_url}", "data": { "name": "Name", "thermostat": "The real thermostat", @@ -11,30 +11,35 @@ "temperature_sensor": "Temperature sensor", "humidity_sensor": "Humidity sensor", "window_sensors": "Window sensor", - "off_temperature": "The outdoor temperature when the thermostat turns off", - "tolerance": "Tolerance, to prevent the thermostat from turning on and off too often.", - "window_off_delay": "Delay before the thermostat turns off when the window is opened", - "window_off_delay_after": "Delay before the thermostat turns on when the window is closed", - "outdoor_sensor": "If you have an outdoor sensor, you can use it to get the outdoor temperature", - "weather": "Your weather entity to get the outdoor temperature" + "off_temperature": "The outdoor temperature when the thermostat should turn off", + "tolerance": "Tolerance, to prevent the thermostat from turning on and off too often", + "window_off_delay": "Delay before the thermostat should turn off when the window is opened", + "window_off_delay_after": "Delay before the thermostat should turn on when the window is closed", + "outdoor_sensor": "Outdoor temperature sensor", + "weather": "Weather entity to get the outdoor temperature", + "target_temp_step": "Target temperature step", + "presets": "Enabled Presets" + }, + "data_description": { + "presets": "Select the presets you want to be available for this thermostat." } }, "advanced": { - "description": "Advanced configuration {trv}\n***Info about calibration types: https://better-thermostat.org/configuration#second-step***", + "description": "Advanced configuration {trv}\n\n**Detailed information:** {docs_url}", "data": { "protect_overheating": "Overheating protection?", - "heat_auto_swapped": "If the auto means heat for your TRV and you want to swap it", + "heat_auto_swapped": "If 'auto' means 'heat' for your TRV and you want to swap it", "child_lock": "Ignore all inputs on the TRV like a child lock", "homematicip": "If you use HomematicIP, you should enable this to slow down the requests to prevent the duty cycle", "valve_maintenance": "If your thermostat has no own maintenance mode, you can use this one", - "calibration": "Calibration Type", + "calibration": "Calibration type", "calibration_mode": "Calibration mode", - "no_off_system_mode": "If your TRV can't handle the off mode, you can enable this to use target temperature 5°C instead" + "no_off_system_mode": "If your TRV doesn't support the 'off' mode, enable this to use target temperature 5°C instead" }, "data_description": { "protect_overheating": "Some TRVs don't close the valve completely when the temperature is reached. Or the radiator has a lot of rest heat. This can cause overheating. This option can prevent this.", - "calibration_mode": "The kind how the calibration should be calculated\n***Normal***: In this mode, the TRV internal temperature sensor is fixed by the external temperature sensor.\n***Aggresive***: In this mode, the TRV internal temperature sensor is fixed by the external temperature sensor but set much lower/higher to get a quicker boost.\n***AI Time Based***: In this mode, the TRV internal temperature sensor is fixed by the external temperature sensor, but a custom algorithm calculates the value to improve the TRV internal algorithm.", - "calibration": "How the calibration should be applied on the TRV (Target temp or offset)\n***Target Temperature Based***: Apply the calibration to the target temperature.\n***Offset Based***: Apply the calibration to the offset." + "calibration_mode": "How the calibration is calculated\n***Normal***: Uses your external temperature sensor to correct the TRV's internal sensor. Simple and reliable for most setups.\n***Aggressive***: Similar to Normal, but pushes the TRV harder to reach temperature faster. Good if your room heats slowly.\n***AI Time Based***: Learns your room's heating patterns over time and adjusts automatically. Balances comfort and efficiency.\n***MPC Predictive***: Advanced algorithm that predicts temperature changes and optimizes valve position. Best for stable, efficient heating with minimal overshooting. Learns your room's thermal behavior.\n***PID Controller***: Classic control method that responds to temperature errors. Good for systems with varying heating power or external disturbances.\n***TPI Controller***: Calculates heating duty cycles based on temperature difference. Simple and effective for consistent heating patterns.", + "calibration": "How the calibration is applied on the TRV\n***Target Temperature Based***: Apply the calibration to the target temperature.\n***Offset Based***: Apply the calibration to the temperature offset." } }, "confirm": { @@ -45,53 +50,58 @@ "error": { "failed": "Something went wrong.", "no_name": "Please enter a name.", - "no_off_mode": "Your device is very special and has no off mode :(\nBetter Thermostat will use the minimal target temp instead.", + "no_off_mode": "Your device is special and has no off mode :(\nBetter Thermostat will use the minimal target temp instead.", "no_outside_temp": "You have no outside temperature sensor. Better Thermostat will use the weather entity instead." }, "abort": { - "single_instance_allowed": "Only a single Thermostat for each real is allowed.", - "no_devices_found": "No thermostat entity found, make sure you have a climate entity in your home assistant" + "single_instance_allowed": "Only a single Better Thermostat is allowed for each real thermostat.", + "no_devices_found": "No thermostat entity found, make sure you have a climate entity in your Home Assistant" } }, "options": { "step": { "user": { - "description": "Update your Better Thermostat settings", + "description": "Update your Better Thermostat settings\n\n**Detailed information:** {docs_url}", "data": { "name": "Name", "thermostat": "The real thermostat", "temperature_sensor": "Temperature Sensor", "humidity_sensor": "Humidity sensor", "window_sensors": "Window Sensor", - "off_temperature": "The outdoor temperature when the thermostat turns off", - "tolerance": "Tolerance, to prevent the thermostat from turning on and off too often.", - "window_off_delay": "Delay before the thermostat turns off when the window is opened", - "window_off_delay_after": "Delay before the thermostat turns on when the window is closed", - "outdoor_sensor": "If you have an outdoor sensor, you can use it to get the outdoor temperature", + "off_temperature": "The outdoor temperature when the thermostat should turn off", + "tolerance": "Tolerance, to prevent the thermostat from turning on and off too often", + "window_off_delay": "Delay before the thermostat should turn off when the window is opened", + "window_off_delay_after": "Delay before the thermostat should turn on when the window is closed", + "outdoor_sensor": "Outdoor temperature sensor", + "weather": "Weather entity to get the outdoor temperature", + "target_temp_step": "Target temperature step", + "presets": "Enabled Presets", "valve_maintenance": "If your thermostat has no own maintenance mode, you can use this one", - "calibration": "The sort of calibration https://better-thermostat.org/configuration#second-step", - "weather": "Your weather entity to get the outdoor temperature", - "heat_auto_swapped": "If the auto means heat for your TRV and you want to swap it", + "calibration": "The sort of calibration", + "heat_auto_swapped": "If 'auto' means 'heat' for your TRV and you want to swap it", "child_lock": "Ignore all inputs on the TRV like a child lock", "homematicip": "If you use HomematicIP, you should enable this to slow down the requests to prevent the duty cycle" + }, + "data_description": { + "presets": "Select the presets you want to be available for this thermostat." } }, "advanced": { - "description": "Advanced configuration {trv}\n***Info about calibration types: https://better-thermostat.org/configuration#second-step***", + "description": "Advanced configuration {trv}\n\n**Detailed information:** {docs_url}", "data": { "protect_overheating": "Overheating protection?", - "heat_auto_swapped": "If the auto means heat for your TRV and you want to swap it", + "heat_auto_swapped": "If 'auto' means 'heat' for your TRV and you want to swap it", "child_lock": "Ignore all inputs on the TRV like a child lock", "homematicip": "If you use HomematicIP, you should enable this to slow down the requests to prevent the duty cycle", "valve_maintenance": "If your thermostat has no own maintenance mode, you can use this one", - "calibration": "The sort of calibration you want to use", + "calibration": "Calibration type", "calibration_mode": "Calibration mode", - "no_off_system_mode": "If your TRV can't handle the off mode, you can enable this to use target temperature 5°C instead" + "no_off_system_mode": "If your TRV doesn't support the 'off' mode, enable this to use target temperature 5°C instead" }, "data_description": { "protect_overheating": "Some TRVs don't close the valve completely when the temperature is reached. Or the radiator has a lot of rest heat. This can cause overheating. This option can prevent this.", - "calibration_mode": "The kind how the calibration should be calculated\n***Normal***: In this mode, the TRV internal temperature sensor is fixed by the external temperature sensor.\n***Aggresive***: In this mode, the TRV internal temperature sensor is fixed by the external temperature sensor but set much lower/higher to get a quicker boost.\n***AI Time Based***: In this mode the TRV internal temperature sensor is fixed by the external temperature sensor, but a custom algorithm calculates the value to improve the TRV internal algorithm.", - "calibration": "How the calibration should be applied on the TRV (Target temp or offset)\n***Target Temperature Based***: Apply the calibration to the target temperature.\n***Offset Based***: Apply the calibration to the offset." + "calibration_mode": "How the calibration is calculated\n***Normal***: Uses your external temperature sensor to correct the TRV's internal sensor. Simple and reliable for most setups.\n***Aggressive***: Similar to Normal, but pushes the TRV harder to reach temperature faster. Good if your room heats slowly.\n***AI Time Based***: Learns your room's heating patterns over time and adjusts automatically. Balances comfort and efficiency.\n***MPC Predictive***: Advanced algorithm that predicts temperature changes and optimizes valve position. Best for stable, efficient heating with minimal overshooting. Learns your room's thermal behavior.\n***PID Controller***: Classic control method that responds to temperature errors. Good for systems with varying heating power or external disturbances.\n***TPI Controller***: Calculates heating duty cycles based on temperature difference. Simple and effective for consistent heating patterns.", + "calibration": "How the calibration is applied on the TRV\n***Target Temperature Based***: Apply the calibration to the target temperature.\n***Offset Based***: Apply the calibration to the temperature offset." } } } @@ -107,24 +117,122 @@ } } } + }, + "degraded_mode": { + "title": "BT: {name} - sensor error: {sensors} running in degraded mode", + "description": "Better Thermostat is running in degraded mode because some sensors are unavailable: {sensors}\n\nThe thermostat will continue to operate with reduced functionality. For example:\n- If the room temperature sensor is unavailable, the TRV's internal sensor will be used instead.\n- If window sensors are unavailable, windows are assumed to be closed.\n- If outdoor/weather sensors are unavailable, heating will always be enabled.\n\nCheck if the affected devices have battery issues or connectivity problems." + }, + "invalid_window_state": { + "title": "BT: {name} - invalid window sensor state", + "description": "The window sensor for Better Thermostat {name} reported an unrecognized state: {state}.\n\nExpected states are: 'on', 'off', 'true', 'false', 'open', 'closed'.\n\nPlease check your window sensor configuration." } }, "services": { - "save_current_target_temperature": { - "name": "Save current Temperature", - "description": "Save the current target temperature for later restore." - }, - "restore_saved_target_temperature": { - "name": "Restore temperature", - "description": "Restore the saved target temperature." + "run_valve_maintenance": { + "name": "Run valve maintenance now", + "description": "Run the valve maintenance cycle immediately for this Better Thermostat entity." }, "reset_heating_power": { "name": "Reset heating power", "description": "Reset heating power to default value." }, - "set_temp_target_temperature": { - "name": "Set eco temperature", - "description": "Set the target temperature to a temporary like night mode, and save the old one." + "reset_pid_learnings": { + "name": "Reset PID learnings", + "description": "Clear all learned PID state (and optionally learned open caps) for this entity.", + "fields": { + "include_open_caps": { + "name": "Also clear learned open caps", + "description": "If enabled, also clears learned min/max open percentages per TRV/target bucket." + }, + "apply_pid_defaults": { + "name": "Apply PID defaults after reset", + "description": "If enabled, seeds the current target bucket(s) with PID default gains (kp/ki/kd). You can override defaults below." + }, + "defaults_kp": { + "name": "Default kp", + "description": "Proportional gain to seed after reset (falls back to BalanceParams.kp if not provided)." + }, + "defaults_ki": { + "name": "Default ki", + "description": "Integral gain to seed after reset (falls back to BalanceParams.ki if not provided)." + }, + "defaults_kd": { + "name": "Default kd", + "description": "Derivative gain to seed after reset (falls back to BalanceParams.kd if not provided)." + } + } + } + }, + "selector": { + "presets": { + "options": { + "eco": "Eco", + "away": "Away", + "boost": "Boost", + "comfort": "Comfort", + "home": "Home", + "sleep": "Sleep", + "activity": "Activity" + } + } + }, + "entity": { + "number": { + "pid_kp": { + "name": "{trv_name} PID Kp (Proportional)" + }, + "pid_ki": { + "name": "{trv_name} PID Ki (Integral)" + }, + "pid_kd": { + "name": "{trv_name} PID Kd (Derivative)" + }, + "pid_kp_no_trv": { + "name": "PID Kp (Proportional)" + }, + "pid_ki_no_trv": { + "name": "PID Ki (Integral)" + }, + "pid_kd_no_trv": { + "name": "PID Kd (Derivative)" + } + }, + "switch": { + "pid_auto_tune": { + "name": "{trv_name} PID Auto Tune" + }, + "pid_auto_tune_no_trv": { + "name": "PID Auto Tune" + }, + "child_lock": { + "name": "{trv_name} Child Lock" + }, + "child_lock_no_trv": { + "name": "Child Lock" + } + } + }, + "device_automation": { + "trigger_type": { + "heating_active": "Thermostat switches to heating", + "heating_stopped": "Thermostat stops heating", + "humidity_high": "Humidity exceeds threshold", + "window_opened": "Window opened", + "window_closed": "Window closed", + "battery_low": "Battery low", + "device_error": "Device error detected", + "target_temp_reached": "Target temperature reached", + "hvac_mode_changed": "HVAC mode changed", + "current_temperature_changed": "Current temperature changed", + "current_humidity_changed": "Current humidity changed" + }, + "condition_type": { + "is_hvac_mode": "Thermostat is set to a specific HVAC mode", + "is_hvac_action": "Thermostat is performing a specific HVAC action" + }, + "action_type": { + "set_hvac_mode": "Set HVAC mode", + "set_temperature": "Set target temperature" } } } diff --git a/custom_components/better_thermostat/translations/fr.json b/custom_components/better_thermostat/translations/fr.json index e7b19dd..1b069db 100644 --- a/custom_components/better_thermostat/translations/fr.json +++ b/custom_components/better_thermostat/translations/fr.json @@ -3,7 +3,7 @@ "config": { "step": { "user": { - "description": "Configurez Better Thermostat pour l'intégrer à Home Assistant\n**Si vous avez besoin de plus d'informations : https://better-thermostat.org/configuration#first-step**", + "description": "Configurez Better Thermostat pour l'intégrer à Home Assistant\n\n**Infos détaillées :** {docs_url}", "data": { "name": "Nom", "thermostat": "Le vrai thermostat", @@ -16,11 +16,16 @@ "window_off_delay": "Délai avant que le thermostat ne s'éteigne lorsque la fenêtre est ouverte", "window_off_delay_after": "Délai avant que le thermostat ne s'allume lorsque la fenêtre est fermée", "outdoor_sensor": "Si vous avez un capteur extérieur, vous pouvez l'utiliser pour obtenir la température extérieure", - "weather": "Votre entité météo pour obtenir la température extérieure" + "weather": "Votre entité météo pour obtenir la température extérieure", + "target_temp_step": "Pas de la température cible", + "presets": "Préréglages activés" + }, + "data_description": { + "presets": "Sélectionnez les préréglages qui doivent être disponibles pour ce thermostat." } }, "advanced": { - "description": "Configuration avancée {trv}\n***Informations sur les types de calibrage : https://better-thermostat.org/configuration#second-step***", + "description": "Configuration avancée {trv}\n\n**Infos détaillées :** {docs_url}", "data": { "protect_overheating": "Protection contre la surchauffe ?", "heat_auto_swapped": "Si le mode automatique signifie chauffage pour votre TRV et que vous voulez le permuter", @@ -33,7 +38,7 @@ }, "data_description": { "protect_overheating": "Certains TRVs ne ferment pas complètement la vanne lorsque la température est atteinte. Ou le radiateur a beaucoup de chaleur résiduelle. Cela peut provoquer une surchauffe. Cette option peut prévenir cela.", - "calibration_mode": "Le type de calcul du calibrage\n***Normal*** : Dans ce mode, le capteur de température interne du TRV est fixé par le capteur de température externe.\n***Aggressif*** : Dans ce mode, le capteur de température interne du TRV est fixé par le capteur de température externe mais réglé beaucoup plus bas/haut pour obtenir un boost plus rapide.\n***AI Time Based*** : Dans ce mode, le capteur de température interne du TRV est fixé par le capteur de température externe, mais la valeur est calculée par un algorithme personnalisé pour améliorer l'algorithme interne du TRV.", + "calibration_mode": "Le type de calcul du calibrage\n***Normal*** : Dans ce mode, le capteur de température interne du TRV est fixé par le capteur de température externe.\n***Aggressif*** : Dans ce mode, le capteur de température interne du TRV est fixé par le capteur de température externe mais réglé beaucoup plus bas/haut pour obtenir un boost plus rapide.\n***AI Time Based*** : Dans ce mode, le capteur de température interne du TRV est fixé par le capteur de température externe, mais la valeur est calculée par un algorithme personnalisé pour améliorer l'algorithme interne du TRV.\n***MPC Predictive (Beta)*** : Utilise le contrôleur prédictif MPC pour dériver une ouverture de vanne et la convertir en décalages ou points de consigne lorsque le contrôle direct de la vanne n'est pas disponible.\n***PID Controller*** : Utilise un contrôleur PID pour ajuster dynamiquement la position de la vanne en fonction de l'erreur de température, du terme proportionnel, de l'accumulation intégrale et de la prédiction dérivée.\n***TPI Controller*** : Utilise un contrôleur Proportionnel Intégral Temporel pour un contrôle de température stable et précis dans le temps.", "calibration": "Comment le calibrage doit être appliqué sur le TRV (Température cible ou décalage)\n***Basé sur la température cible*** : Appliquer le calibrage à la température cible.\n***Basé sur le décalage*** : Appliquer le calibrage au décalage." } }, @@ -56,7 +61,7 @@ "options": { "step": { "user": { - "description": "Mettez à jour les paramètres de Better Thermostat", + "description": "Mettez à jour les paramètres de Better Thermostat\n\n**Infos détaillées :** {docs_url}", "data": { "name": "Nom", "thermostat": "Le vrai thermostat", @@ -68,16 +73,21 @@ "window_off_delay": "Délai avant que le thermostat ne s'éteigne lorsque la fenêtre est ouverte", "window_off_delay_after": "Délai avant que le thermostat ne s'allume lorsque la fenêtre est fermée", "outdoor_sensor": "Si vous avez un capteur extérieur, vous pouvez l'utiliser pour obtenir la température extérieure", - "valve_maintenance": "Si votre thermostat n'a pas de mode maintenance propre, vous pouvez utiliser celui-ci", - "calibration": "Le type de calibrage https://better-thermostat.org/configuration#second-step", "weather": "Votre entité météo pour obtenir la température extérieure", + "target_temp_step": "Pas de la température cible", + "presets": "Préréglages activés", + "valve_maintenance": "Si votre thermostat n'a pas de mode maintenance propre, vous pouvez utiliser celui-ci", + "calibration": "Le type de calibrage", "heat_auto_swapped": "Si le mode automatique signifie chauffage pour votre TRV et que vous voulez le permuter", "child_lock": "Ignorer toutes les entrées sur le TRV comme une sécurité enfant", "homematicip": "Si vous utilisez HomematicIP, vous devriez l'activer pour ralentir les demandes et éviter le cycle de fonctionnement excessif" + }, + "data_description": { + "presets": "Sélectionnez les préréglages qui doivent être disponibles pour ce thermostat." } }, "advanced": { - "description": "Configuration avancée {trv}\n***Informations sur les types de calibrage : https://better-thermostat.org/configuration#second-step***", + "description": "Configuration avancée {trv}\n\n**Infos détaillées :** {docs_url}", "data": { "protect_overheating": "Protection contre la surchauffe ?", "heat_auto_swapped": "Si le mode automatique signifie chauffage pour votre TRV et que vous voulez le permuter", @@ -90,7 +100,7 @@ }, "data_description": { "protect_overheating": "Certains TRVs ne ferment pas complètement la vanne lorsque la température est atteinte. Ou le radiateur a beaucoup de chaleur résiduelle. Cela peut provoquer une surchauffe. Cette option peut prévenir cela.", - "calibration_mode": "Le type de calcul du calibrage\n***Normal*** : Dans ce mode, le capteur de température interne du TRV est fixé par le capteur de température externe.\n***Aggressif*** : Dans ce mode, le capteur de température interne du TRV est fixé par le capteur de température externe mais réglé beaucoup plus bas/haut pour obtenir un boost plus rapide.\n***AI Time Based*** : Dans ce mode, le capteur de température interne du TRV est fixé par le capteur de température externe, mais la valeur est calculée par un algorithme personnalisé pour améliorer l'algorithme interne du TRV.", + "calibration_mode": "Le type de calcul du calibrage\n***Normal*** : Dans ce mode, le capteur de température interne du TRV est fixé par le capteur de température externe.\n***Aggressif*** : Dans ce mode, le capteur de température interne du TRV est fixé par le capteur de température externe mais réglé beaucoup plus bas/haut pour obtenir un boost plus rapide.\n***AI Time Based*** : Dans ce mode, le capteur de température interne du TRV est fixé par le capteur de température externe, mais la valeur est calculée par un algorithme personnalisé pour améliorer l'algorithme interne du TRV.\n***MPC Predictive (Beta)*** : Utilise le contrôleur prédictif MPC pour dériver une ouverture de vanne et la convertir en décalages ou points de consigne lorsque le contrôle direct de la vanne n'est pas disponible.\n***PID Controller*** : Utilise un contrôleur PID pour ajuster dynamiquement la position de la vanne en fonction de l'erreur de température, du terme proportionnel, de l'accumulation intégrale et de la prédiction dérivée.\n***TPI Controller*** : Utilise un contrôleur Proportionnel Intégral Temporel pour un contrôle de température stable et précis dans le temps.", "calibration": "Comment le calibrage doit être appliqué sur le TRV (Température cible ou décalage)\n***Basé sur la température cible*** : Appliquer le calibrage à la température cible.\n***Basé sur le décalage*** : Appliquer le calibrage au décalage." } } @@ -107,24 +117,122 @@ } } } + }, + "degraded_mode": { + "title": "BT: {name} - erreur de capteur : {sensors} fonctionne en mode dégradé", + "description": "Better Thermostat fonctionne en mode dégradé car certains capteurs ne sont pas disponibles : {sensors}\n\nLe thermostat continuera à fonctionner avec des fonctionnalités réduites. Par exemple :\n- Si le capteur de température ambiante n'est pas disponible, le capteur interne du TRV sera utilisé à la place.\n- Si les capteurs de fenêtre ne sont pas disponibles, les fenêtres sont considérées comme fermées.\n- Si les capteurs extérieurs/météo ne sont pas disponibles, le chauffage sera toujours activé.\n\nVérifiez si les appareils concernés ont des problèmes de batterie ou de connectivité." + }, + "invalid_window_state": { + "title": "BT: {name} - état du capteur de fenêtre invalide", + "description": "Le capteur de fenêtre pour Better Thermostat {name} a signalé un état non reconnu : {state}.\n\nLes états attendus sont : 'on', 'off', 'true', 'false', 'open', 'closed'.\n\nVeuillez vérifier la configuration de votre capteur de fenêtre." } }, "services": { - "save_current_target_temperature": { - "name": "Enregistrer la température actuelle", - "description": "Enregistrez la température cible actuelle pour une restauration ultérieure." - }, - "restore_saved_target_temperature": { - "name": "Restaurer la température", - "description": "Restaurez la température cible enregistrée." + "run_valve_maintenance": { + "name": "Lancer la maintenance de la vanne maintenant", + "description": "Exécute immédiatement le cycle de maintenance de la vanne pour cette entité Better Thermostat." }, "reset_heating_power": { "name": "Réinitialiser la puissance de chauffage", "description": "Réinitialisez la puissance de chauffage à la valeur par défaut." }, - "set_temp_target_temperature": { - "name": "Définir la température éco", - "description": "Définissez la température cible sur une valeur temporaire comme le mode nuit, et enregistrez l'ancienne." + "reset_pid_learnings": { + "name": "Réinitialiser l'apprentissage PID", + "description": "Efface tout l'état PID appris (et éventuellement les limites d'ouverture apprises) pour cette entité.", + "fields": { + "include_open_caps": { + "name": "Effacer aussi les limites d'ouverture apprises", + "description": "Si activé, efface également les pourcentages d'ouverture min/max appris par TRV/plage de consigne." + }, + "apply_pid_defaults": { + "name": "Appliquer les valeurs PID par défaut après la réinitialisation", + "description": "Si activé, prérenseigne les plages de consigne actuelles avec les gains PID par défaut (kp/ki/kd). Vous pouvez remplacer les valeurs par défaut ci-dessous." + }, + "defaults_kp": { + "name": "kp par défaut", + "description": "Gain proportionnel appliqué après la réinitialisation (utilise BalanceParams.kp si non renseigné)." + }, + "defaults_ki": { + "name": "ki par défaut", + "description": "Gain intégral appliqué après la réinitialisation (utilise BalanceParams.ki si non renseigné)." + }, + "defaults_kd": { + "name": "kd par défaut", + "description": "Gain dérivé appliqué après la réinitialisation (utilise BalanceParams.kd si non renseigné)." + } + } + } + }, + "selector": { + "presets": { + "options": { + "eco": "Éco", + "away": "Absent", + "boost": "Boost", + "comfort": "Confort", + "home": "Maison", + "sleep": "Sommeil", + "activity": "Activité" + } + } + }, + "entity": { + "number": { + "pid_kp": { + "name": "{trv_name} PID Kp (proportionnel)" + }, + "pid_ki": { + "name": "{trv_name} PID Ki (intégral)" + }, + "pid_kd": { + "name": "{trv_name} PID Kd (dérivé)" + }, + "pid_kp_no_trv": { + "name": "PID Kp (proportionnel)" + }, + "pid_ki_no_trv": { + "name": "PID Ki (intégral)" + }, + "pid_kd_no_trv": { + "name": "PID Kd (dérivé)" + } + }, + "switch": { + "pid_auto_tune": { + "name": "{trv_name} Réglage automatique PID" + }, + "pid_auto_tune_no_trv": { + "name": "Réglage automatique PID" + }, + "child_lock": { + "name": "{trv_name} Sécurité enfant" + }, + "child_lock_no_trv": { + "name": "Sécurité enfant" + } + } + }, + "device_automation": { + "trigger_type": { + "heating_active": "Le thermostat passe en mode chauffage", + "heating_stopped": "Le thermostat arrête le chauffage", + "humidity_high": "L'humidité dépasse le seuil", + "window_opened": "Fenêtre ouverte", + "window_closed": "Fenêtre fermée", + "battery_low": "Batterie faible", + "device_error": "Erreur de périphérique détectée", + "target_temp_reached": "Température cible atteinte", + "hvac_mode_changed": "Mode CVC modifié", + "current_temperature_changed": "Température actuelle modifiée", + "current_humidity_changed": "Humidité actuelle modifiée" + }, + "condition_type": { + "is_hvac_mode": "Le thermostat est réglé sur un mode HVAC spécifique", + "is_hvac_action": "Le thermostat exécute une action HVAC spécifique" + }, + "action_type": { + "set_hvac_mode": "Définir le mode HVAC", + "set_temperature": "Définir la température cible" } } -} \ No newline at end of file +} diff --git a/custom_components/better_thermostat/translations/pl.json b/custom_components/better_thermostat/translations/pl.json index 4f1a59a..0e57db7 100644 --- a/custom_components/better_thermostat/translations/pl.json +++ b/custom_components/better_thermostat/translations/pl.json @@ -3,30 +3,38 @@ "config": { "step": { "user": { - "description": "Skonfiguruj swój Better Thermostat do integracji z Home Assistant\n**Więcej informacji znajdziesz na: https://better-thermostat.org/configuration#first-step**", + "description": "Skonfiguruj swój Better Thermostat do integracji z Home Assistant\n\n**Szczegółowe informacje:** {docs_url}", "data": { "name": "Nazwa", "thermostat": "Twój termostat", + "cooler": "Urządzenie chłodzące (opcjonalnie)", "temperature_sensor": "Sensor temperatury", "humidity_sensor": "Sensor wilgotności", "window_sensors": "Sensor okna", "off_temperature": "Temperatura zewnętrzna, przy której termostat ma się wyłączyć", + "tolerance": "Tolerancja, aby zapobiec zbyt częstemu włączaniu i wyłączaniu termostatu", "window_off_delay": "Opóźnienie wyłączenia termostatu, kiedy otworzysz okno lub je zamkniesz (w sekundach)", + "window_off_delay_after": "Opóźnienie włączenia termostatu po zamknięciu okna", "outdoor_sensor": "Jeśli masz czujnik zewnętrzny, możesz go użyć, aby uzyskać temperaturę zewnętrzną", - "weather": "Twoja jednostka pogodowa, aby uzyskać temperaturę zewnętrzną" + "weather": "Twoja jednostka pogodowa, aby uzyskać temperaturę zewnętrzną", + "target_temp_step": "Krok temperatury docelowej", + "presets": "Włączone ustawienia predefiniowane" + }, + "data_description": { + "presets": "Wybierz ustawienia predefiniowane, które mają być dostępne dla tego termostatu." } }, "advanced": { - "description": "Zaawansowana konfiguracja {trv}\n***Informacja o typach kalibracji: https://better-thermostat.org/configuration#second-step***", + "description": "Zaawansowana konfiguracja {trv}\n\n**Szczegółowe informacje:** {docs_url}", "data": { + "protect_overheating": "Zabezpieczenie przed przegrzaniem?", "heat_auto_swapped": "Jeżeli tryb auto oznacza grzanie dla Twojego TRV i chcesz to zmienić", "child_lock": "Ignoruj wszystkie wejścia w TRV jak np. Blokada dziecięca", "homematicip": "Jeżeli używasz HomematicIP, powinieneś włączyć tę opcję, żeby spowolnić żądania cyklu pracy", "valve_maintenance": "Jeżeli Twój termostat nie ma trybu konserwacji, możesz użyć tej opcji.", "calibration": "Rodzaj kalibracji, której chcesz użyć", - "no_off_system_mode": "Jeśli Twój TRV nie obsługuje trybu wyłączenia, możesz go włączyć, aby zamiast tego używać temperatury docelowej 5°C", "calibration_mode": "Tryb kalibracji", - "protect_overheating": "Zabezpieczenie przed przegrzaniem?" + "no_off_system_mode": "Jeśli Twój TRV nie obsługuje trybu wyłączenia, możesz go włączyć, aby zamiast tego używać temperatury docelowej 5°C" }, "data_description": { "protect_overheating": "Niektóre TRV całkowicie zamykają zawór po osiągnięciu temperatury. Lub grzejnik ma dużo ciepła resztkowego. Może to spowodować przegrzanie. Ta opcja może temu zapobiec.", @@ -40,10 +48,10 @@ } }, "error": { - "no_outside_temp": "Nie masz czujnika temperatury zewnętrznej. Better Thermostat zamiast tego użyje jednostki pogodowej.", "failed": "coś poszło nie tak.", "no_name": "Proszę podać nazwę.", - "no_off_mode": "Twoje urządzenie jest inne i nie ma trybu wyłączenia :(\nTo będzie działać, ale musisz stworzyć automatyzację, aby ustawić funkcje pod swoje urządzenie" + "no_off_mode": "Twoje urządzenie jest inne i nie ma trybu wyłączenia :(\nTo będzie działać, ale musisz stworzyć automatyzację, aby ustawić funkcje pod swoje urządzenie", + "no_outside_temp": "Nie masz czujnika temperatury zewnętrznej. Better Thermostat zamiast tego użyje jednostki pogodowej." }, "abort": { "single_instance_allowed": "Dozwolony jest tylko jeden termostat dla jednego urządzenia.", @@ -53,33 +61,40 @@ "options": { "step": { "user": { - "description": "Zaktualizuj ustawienia Better Thermostat", + "description": "Zaktualizuj ustawienia Better Thermostat\n\n**Szczegółowe informacje:** {docs_url}", "data": { + "name": "Nazwa", + "thermostat": "Prawdziwy termostat", "temperature_sensor": "Sensor temperatury", "humidity_sensor": "Sensor wilgotności", "window_sensors": "Sensor okna", "off_temperature": "Temperatura zewnętrzna, przy której termostat ma się wyłączyć", + "tolerance": "Tolerancja, aby zapobiec zbyt częstemu włączaniu i wyłączaniu termostatu", "window_off_delay": "Opóźnienie wyłączenia termostatu, kiedy otworzysz okno lub je zamkniesz (w sekundach)", + "window_off_delay_after": "Opóźnienie włączenia termostatu po zamknięciu okna", "outdoor_sensor": "Jeśli masz czujnik zewnętrzny, możesz go użyć, aby uzyskać temperaturę zewnętrzną", - "valve_maintenance": "Jeżeli Twój termostat nie ma własnego trybu konserwacji, możesz użyć tej opcji.", - "calibration": "Rodzaje kalibracji https://better-thermostat.org/configuration#second-step", "weather": "Twoja jednostka pogodowa, aby uzyskać temperaturę zewnętrzną", + "target_temp_step": "Krok temperatury docelowej", + "presets": "Włączone ustawienia predefiniowane", + "valve_maintenance": "Jeżeli Twój termostat nie ma własnego trybu konserwacji, możesz użyć tej opcji.", + "calibration": "Rodzaje kalibracji", "heat_auto_swapped": "Jeżeli tryb auto oznacza grzanie dla Twojego TRV i chcesz to zmienić", "child_lock": "Ignoruj wszystkie wejścia w TRV jak np. Blokada dziecięca", - "homematicip": "Jeżeli używasz HomematicIP, powinienieś włączyć tę opcję żeby spowolnić żądania", - "name": "Nazwa", - "thermostat": "Prawdziwy termostat" + "homematicip": "Jeżeli używasz HomematicIP, powinienieś włączyć tę opcję żeby spowolnić żądania" + }, + "data_description": { + "presets": "Wybierz ustawienia predefiniowane, które mają być dostępne dla tego termostatu." } }, "advanced": { - "description": "Zaawansowana konfiguracja {trv}\n***Informacja o typach kalibracji: https://better-thermostat.org/configuration#second-step***", + "description": "Zaawansowana konfiguracja {trv}\n\n**Szczegółowe informacje:** {docs_url}", "data": { + "protect_overheating": "Zabezpieczenie przed przegrzaniem?", "heat_auto_swapped": "Jeżeli tryb auto oznacza grzanie dla Twojego TRV i chcesz to zmienić", "child_lock": "Ignoruj wszystkie wejścia w TRV jak np. Blokada dziecięca", "homematicip": "Jeżeli używasz HomematicIP, powinieneś włączyć tę opcję, żeby spowolnić żądania spowolnienia cyklu pracy", "valve_maintenance": "Jeżeli Twój termostat nie ma trybu konserwacji, możesz użyć tej opcji.", "calibration": "Rodzaj kalibracji, której chcesz użyć", - "protect_overheating": "Zabezpieczenie przed przegrzaniem?", "calibration_mode": "Tryb kalibracji", "no_off_system_mode": "Jeśli Twój TRV nie obsługuje trybu wyłączenia, możesz go włączyć, aby zamiast tego używać temperatury docelowej 5°C" }, @@ -102,6 +117,122 @@ } } } + }, + "degraded_mode": { + "title": "BT: {name} - błąd czujnika: {sensors} działa w trybie ograniczonym", + "description": "Better Thermostat działa w trybie ograniczonym, ponieważ niektóre czujniki są niedostępne: {sensors}\n\nTermostat będzie nadal działać z ograniczoną funkcjonalnością. Na przykład:\n- Jeśli czujnik temperatury pokojowej jest niedostępny, zostanie użyty wewnętrzny czujnik TRV.\n- Jeśli czujniki okien są niedostępne, okna są uznawane za zamknięte.\n- Jeśli czujniki zewnętrzne/pogodowe są niedostępne, ogrzewanie będzie zawsze włączone.\n\nSprawdź, czy dotknięte urządzenia mają problemy z baterią lub łącznością." + }, + "invalid_window_state": { + "title": "BT: {name} - nieprawidłowy stan czujnika okna", + "description": "Czujnik okna dla Better Thermostat {name} zgłosił nierozpoznany stan: {state}.\n\nOczekiwane stany to: 'on', 'off', 'true', 'false', 'open', 'closed'.\n\nSprawdź konfigurację czujnika okna." + } + }, + "services": { + "run_valve_maintenance": { + "name": "Uruchom teraz konserwację zaworu", + "description": "Natychmiast uruchamia cykl konserwacji zaworu dla tej encji Better Thermostat." + }, + "reset_heating_power": { + "name": "Zresetuj moc grzewczą", + "description": "Resetuje moc grzewczą do wartości domyślnej." + }, + "reset_pid_learnings": { + "name": "Zresetuj nauczone wartości PID", + "description": "Czyści cały nauczony stan PID (i opcjonalnie nauczone limity otwarcia) dla tej encji.", + "fields": { + "include_open_caps": { + "name": "Wyczyść także nauczone limity otwarcia", + "description": "Jeśli włączone, czyści również nauczone minimalne/maksymalne procenty otwarcia dla każdego TRV/przedziału docelowego." + }, + "apply_pid_defaults": { + "name": "Zastosuj domyślne wartości PID po resecie", + "description": "Jeśli włączone, bieżące przedziały docelowe zostaną wstępnie wypełnione domyślnymi wzmocnieniami PID (kp/ki/kd). Wartości domyślne można nadpisać poniżej." + }, + "defaults_kp": { + "name": "Domyślne kp", + "description": "Wzmocnienie proporcjonalne ustawiane po resecie (jeśli nie podano, używane jest BalanceParams.kp)." + }, + "defaults_ki": { + "name": "Domyślne ki", + "description": "Wzmocnienie całkujące ustawiane po resecie (jeśli nie podano, używane jest BalanceParams.ki)." + }, + "defaults_kd": { + "name": "Domyślne kd", + "description": "Wzmocnienie różniczkujące ustawiane po resecie (jeśli nie podano, używane jest BalanceParams.kd)." + } + } + } + }, + "selector": { + "presets": { + "options": { + "eco": "Eko", + "away": "Poza domem", + "boost": "Boost", + "comfort": "Komfort", + "home": "W domu", + "sleep": "Sen", + "activity": "Aktywność" + } + } + }, + "entity": { + "number": { + "pid_kp": { + "name": "{trv_name} PID Kp (proporcjonalny)" + }, + "pid_ki": { + "name": "{trv_name} PID Ki (całkujący)" + }, + "pid_kd": { + "name": "{trv_name} PID Kd (różniczkujący)" + }, + "pid_kp_no_trv": { + "name": "PID Kp (proporcjonalny)" + }, + "pid_ki_no_trv": { + "name": "PID Ki (całkujący)" + }, + "pid_kd_no_trv": { + "name": "PID Kd (różniczkujący)" + } + }, + "switch": { + "pid_auto_tune": { + "name": "{trv_name} Automatyczne strojenie PID" + }, + "pid_auto_tune_no_trv": { + "name": "Automatyczne strojenie PID" + }, + "child_lock": { + "name": "{trv_name} Blokada rodzicielska" + }, + "child_lock_no_trv": { + "name": "Blokada rodzicielska" + } + } + }, + "device_automation": { + "trigger_type": { + "heating_active": "Termostat przełącza się na ogrzewanie", + "heating_stopped": "Termostat zatrzymuje ogrzewanie", + "humidity_high": "Wilgotność przekracza próg", + "window_opened": "Okno otwarte", + "window_closed": "Okno zamknięte", + "battery_low": "Niski poziom baterii", + "device_error": "Wykryto błąd urządzenia", + "target_temp_reached": "Docelowa temperatura osiągnięta", + "hvac_mode_changed": "Tryb HVAC zmieniony", + "current_temperature_changed": "Aktualna temperatura zmieniona", + "current_humidity_changed": "Aktualna wilgotność zmieniona" + }, + "condition_type": { + "is_hvac_mode": "Termostat jest ustawiony na określony tryb HVAC", + "is_hvac_action": "Termostat wykonuje określoną akcję HVAC" + }, + "action_type": { + "set_hvac_mode": "Ustaw tryb HVAC", + "set_temperature": "Ustaw temperaturę docelową" } } } diff --git a/custom_components/better_thermostat/translations/sk.json b/custom_components/better_thermostat/translations/sk.json index d079e43..1828970 100644 --- a/custom_components/better_thermostat/translations/sk.json +++ b/custom_components/better_thermostat/translations/sk.json @@ -3,7 +3,7 @@ "config": { "step": { "user": { - "description": "Nastavenie lepšieho termostatu na integráciu s aplikáciou Home Assistant\n**Ak potrebujete viac informácií: https://better-thermostat.org/configuration#first-step**", + "description": "Nastavenie lepšieho termostatu na integráciu s aplikáciou Home Assistant\n\n**Podrobné informácie:** {docs_url}", "data": { "name": "Názov", "thermostat": "Skutočný termostat", @@ -16,11 +16,16 @@ "window_off_delay": "Oneskorenie pred vypnutím termostatu pri otvorení okna", "window_off_delay_after": "Oneskorenie pred zapnutím termostatu pri zatvorenom okne", "outdoor_sensor": "Ak máte vonkajší snímač, môžete pomocou neho zistiť vonkajšiu teplotu", - "weather": "Váš meteorologický subjekt na zistenie vonkajšej teploty" + "weather": "Váš meteorologický subjekt na zistenie vonkajšej teploty", + "target_temp_step": "Krok cieľovej teploty", + "presets": "Povolené predvoľby" + }, + "data_description": { + "presets": "Vyberte predvoľby, ktoré chcete mať k dispozícii pre tento termostat." } }, "advanced": { - "description": "Rozšírená konfigurácia {trv}\n***Informácie o druhoch kalibrácie: https://better-thermostat.org/configuration#second-step***", + "description": "Rozšírená konfigurácia {trv}\n\n**Podrobné informácie:** {docs_url}", "data": { "protect_overheating": "Ochrana proti prehriatiu?", "heat_auto_swapped": "Ak auto znamená teplo pre váš TRV a chcete ho vymeniť", @@ -32,8 +37,8 @@ "no_off_system_mode": "Ak váš TRV nedokáže pracovať v režime vypnutia, môžete namiesto toho povoliť použitie cieľovej teploty 5 °C." }, "data_description": { - "protect_overheating": "Niektoré TRV nezatvárajú ventil úplne, keď sa dosiahne požadovaná teplota. Alebo je v radiátore veľa zvyškového tepla. To môže spôsobiť prehriatie. Táto možnosť tomu môže zabrániť.", - "calibration_mode": "Spôsob, ako sa má kalibrácia vypočítať\n***Normal***: V tomto režime je vnútorný snímač teploty TRV fixovaný vonkajším snímačom teploty.\n***Agresívny***: V tomto režime je vnútorný teplotný snímač TRV fixovaný vonkajším teplotným snímačom, ale je nastavený oveľa nižšie/vyššie, aby sa dosiahol rýchlejší nárast.\n***AI na základe času***: V tomto režime je vnútorný teplotný snímač TRV fixovaný vonkajším teplotným snímačom, ale hodnota sa vypočíta pomocou vlastného algoritmu na zlepšenie vnútorného algoritmu TRV.", + "protect_overheating": "Niektoré termostatické ventily úplne neuzatvoria ventil po dosiahnutí požadovanej teploty. Alebo má radiátor veľa zvyškového tepla. To môže spôsobiť prehriatie. Táto možnosť tomu môže zabrániť.", + "calibration_mode": "Druh, akým sa má kalibrácia vypočítať\n***Normálny***: V tomto režime je vnútorný snímač teploty TRV fixovaný externým snímačom teploty.\n***Agresívny***: V tomto režime je vnútorný snímač teploty TRV fixovaný externým snímačom teploty, ale nastavený oveľa nižšie/vyššie, aby sa dosiahlo rýchlejšie zvýšenie teploty.\n***Časovo založený na AI***: V tomto režime je vnútorný snímač teploty TRV fixovaný externým snímačom teploty, ale vlastný algoritmus vypočíta hodnotu na zlepšenie vnútorného algoritmu TRV.\n***Prediktívny MPC (Beta)***: Používa prediktívny regulátor MPC na odvodenie otvorenia ventilu a prevedie ho na posuny alebo požadované hodnoty, keď priame riadenie ventilu nie je k dispozícii.\n***PID regulátor***: Používa PID regulátor na dynamické nastavenie polohy ventilu na základe teplotnej chyby, proporcionálneho člena, integrálnej akumulácie a derivačnej predikcie.\n***TPI regulátor***: Používa časovo proporcionálny integračný regulátor pre stabilnú a presnú reguláciu teploty v priebehu času.", "calibration": "Ako sa má kalibrácia použiť na TRV (cieľová teplota alebo posun)\n***Na základe cieľovej teploty***: Použite kalibráciu na cieľovú teplotu.\n***Na základe ofsetu***: Použite kalibráciu na offset." } }, @@ -56,7 +61,7 @@ "options": { "step": { "user": { - "description": "Aktualizujte Better Thermostat nastavenia", + "description": "Aktualizujte Better Thermostat nastavenia\n\n**Podrobné informácie:** {docs_url}", "data": { "name": "Názov", "thermostat": "Reálny thermostat", @@ -68,16 +73,21 @@ "window_off_delay": "Oneskorenie pred vypnutím termostatu pri otvorení okna", "window_off_delay_after": "Oneskorenie pred zapnutím termostatu pri zatvorenom okne", "outdoor_sensor": "Ak máte vonkajší snímač, môžete pomocou neho zistiť vonkajšiu teplotu", - "valve_maintenance": "Ak váš termostat nemá vlastný režim údržby, môžete použiť tento režim", - "calibration": "Druh kalibrácie https://better-thermostat.org/configuration#second-step", "weather": "Váš meteorologický subjekt na zistenie vonkajšej teploty", + "target_temp_step": "Krok cieľovej teploty", + "presets": "Enabled Predvoľby", + "valve_maintenance": "Ak váš termostat nemá vlastný režim údržby, môžete použiť tento režim", + "calibration": "Druh kalibrácie", "heat_auto_swapped": "Ak auto znamená teplo pre váš TRV a chcete ho vymeniť", "child_lock": "Ignorovať všetky vstupy na TRV ako detský zámok", "homematicip": "Ak používate HomematicIP, mali by ste túto funkciu povoliť, aby ste spomalili požiadavky a zabránili tak pracovnému cyklu" + }, + "data_description": { + "presets": "Vyberte predvoľby, ktoré chcete mať k dispozícii pre tento termostat." } }, "advanced": { - "description": "Rozšírená konfigurácia {trv}\n***Informácie o druhoch kalibrácie: https://better-thermostat.org/configuration#second-step***", + "description": "Rozšírená konfigurácia {trv}\n\n**Podrobné informácie:** {docs_url}", "data": { "protect_overheating": "Ochrana proti prehriatiu?", "heat_auto_swapped": "Ak auto znamená teplo pre váš TRV a chcete ho vymeniť", @@ -90,7 +100,7 @@ }, "data_description": { "protect_overheating": "Niektoré TRV nezatvárajú ventil úplne, keď sa dosiahne požadovaná teplota. Alebo je v radiátore veľa zvyškového tepla. To môže spôsobiť prehriatie. Táto možnosť tomu môže zabrániť.", - "calibration_mode": "Druh, ako sa má kalibrácia vypočítať\n***Normálna***: V tomto režime je vnútorný snímač teploty TRV fixovaný vonkajším snímačom teploty.\n***Aggresívna***: V tomto režime je vnútorný teplotný snímač TRV fixovaný vonkajším teplotným snímačom, ale je nastavený oveľa nižšie/vyššie, aby sa dosiahol rýchlejší nárast.\n***AI na základe času***: V tomto režime je vnútorný teplotný snímač TRV fixovaný vonkajším teplotným snímačom, ale hodnota sa vypočíta pomocou vlastného algoritmu na zlepšenie vnútorného algoritmu TRV.", + "calibration_mode": "Druh výpočtu kalibrácie\n***Normálny***: V tomto režime je vnútorný snímač teploty TRV fixovaný externým snímačom teploty.\n***Agresívny***: V tomto režime je vnútorný snímač teploty TRV fixovaný externým snímačom teploty, ale nastavený oveľa nižšie/vyššie, aby sa dosiahlo rýchlejšie zvýšenie teploty.\n***Časovo riadený AI***: V tomto režime je vnútorný snímač teploty TRV fixovaný externým snímačom teploty, ale vlastný algoritmus vypočíta hodnotu na zlepšenie vnútorného algoritmu TRV.\n***Prediktívny MPC (Beta)***: Používa algoritmus hydraulického vyváženia MPC na odvodenie otvorenia ventilu a prevádza ho na posuny alebo požadované hodnoty, keď priame riadenie ventilu nie je k dispozícii.\n***PID regulátor***: Používa PID regulátor na dynamické nastavenie polohy ventilu na základe teplotnej chyby, proporcionálneho člena, integrálnej akumulácie a derivačnej predikcie.\n***TPI regulátor***: Používa časovo proporcionálny integračný regulátor pre stabilnú a presnú reguláciu teploty v priebehu času.", "calibration": "Ako sa má kalibrácia použiť na TRV (cieľová teplota alebo ofset)\n***Na základe cieľovej teploty***: Použite kalibráciu na cieľovú teplotu.\n***Na základe ofsetu***: Použite kalibráciu na offset." } } @@ -107,24 +117,122 @@ } } } + }, + "degraded_mode": { + "title": "BT: {name} - chyba snímača: {sensors} beží v obmedzenom režime", + "description": "Better Thermostat beží v obmedzenom režime, pretože niektoré snímače nie sú dostupné: {sensors}\n\nTermostat bude naďalej fungovať s obmedzenou funkcionalitou. Napríklad:\n- Ak snímač izbovej teploty nie je dostupný, použije sa interný snímač TRV.\n- Ak snímače okien nie sú dostupné, okná sa považujú za zatvorené.\n- Ak vonkajšie/meteorologické snímače nie sú dostupné, kúrenie bude vždy zapnuté.\n\nSkontrolujte, či postihnuté zariadenia nemajú problémy s batériou alebo pripojením." + }, + "invalid_window_state": { + "title": "BT: {name} - neplatný stav snímača okna", + "description": "Snímač okna pre Better Thermostat {name} nahlásil nerozpoznaný stav: {state}.\n\nOčakávané stavy sú: 'on', 'off', 'true', 'false', 'open', 'closed'.\n\nSkontrolujte konfiguráciu snímača okna." } }, "services": { - "save_current_target_temperature": { - "name": "Uložiť aktuálnu teplotu", - "description": "Uloženie aktuálnej cieľovej teploty na neskoršie obnovenie." - }, - "restore_saved_target_temperature": { - "name": "Obnoviť teploty", - "description": "Obnovenie uloženej cieľovej teploty." + "run_valve_maintenance": { + "name": "Spustiť údržbu ventilu teraz", + "description": "Okamžite spustí cyklus údržby ventilu pre túto entitu Better Thermostat." }, "reset_heating_power": { "name": "Reset vykurovacieho výkonu", "description": "Obnovenie výkonu vykurovania na predvolenú hodnotu." }, - "set_temp_target_temperature": { - "name": "Nastavenie eko teploty", - "description": "Nastavte cieľovú teplotu na dočasný, napríklad nočný režim, a uložte starú teplotu." + "reset_pid_learnings": { + "name": "Vynulovať naučené hodnoty PID", + "description": "Vymaže všetok naučený stav PID (a voliteľne naučené limity otvorenia) pre túto entitu.", + "fields": { + "include_open_caps": { + "name": "Vymazať aj naučené limity otvorenia", + "description": "Ak je povolené, vymažú sa aj naučené minimálne/maximálne percentá otvorenia pre každý TRV/cieľové pásmo." + }, + "apply_pid_defaults": { + "name": "Po vynulovaní použiť predvolené hodnoty PID", + "description": "Ak je povolené, aktuálne cieľové pásma sa predvyplnia predvolenými zosilneniami PID (kp/ki/kd). Predvolené hodnoty môžete prepísať nižšie." + }, + "defaults_kp": { + "name": "Predvolené kp", + "description": "Proporcionálne zosilnenie nastavené po vynulovaní (ak nie je zadané, použije sa BalanceParams.kp)." + }, + "defaults_ki": { + "name": "Predvolené ki", + "description": "Integračné zosilnenie nastavené po vynulovaní (ak nie je zadané, použije sa BalanceParams.ki)." + }, + "defaults_kd": { + "name": "Predvolené kd", + "description": "Derivačné zosilnenie nastavené po vynulovaní (ak nie je zadané, použije sa BalanceParams.kd)." + } + } + } + }, + "selector": { + "presets": { + "options": { + "eco": "Eko", + "away": "Preč", + "boost": "Boost", + "comfort": "Komfort", + "home": "Doma", + "sleep": "Spánok", + "activity": "Aktivita" + } + } + }, + "entity": { + "number": { + "pid_kp": { + "name": "{trv_name} PID Kp (Proporcionálne)" + }, + "pid_ki": { + "name": "{trv_name} PID Ki (Integrálne)" + }, + "pid_kd": { + "name": "{trv_name} PID Kd (Derivačné)" + }, + "pid_kp_no_trv": { + "name": "PID Kp (Proporcionálne)" + }, + "pid_ki_no_trv": { + "name": "PID Ki (Integračné)" + }, + "pid_kd_no_trv": { + "name": "PID Kd (Derivačné)" + } + }, + "switch": { + "pid_auto_tune": { + "name": "{trv_name} PID Auto Tune" + }, + "pid_auto_tune_no_trv": { + "name": "PID Auto Tune" + }, + "child_lock": { + "name": "{trv_name} Detská poistka" + }, + "child_lock_no_trv": { + "name": "Detská poistka" + } + } + }, + "device_automation": { + "trigger_type": { + "heating_active": "Termostat prechádza do režimu kúrenia", + "heating_stopped": "Termostat zastavuje kúrenie", + "humidity_high": "Vlhkosť prekračuje prah", + "window_opened": "Okno otvorené", + "window_closed": "Okno zatvorené", + "battery_low": "Nízka batéria", + "device_error": "Zistená chyba zariadenia", + "target_temp_reached": "Cieľová teplota dosiahnutá", + "hvac_mode_changed": "Režim HVAC zmenený", + "current_temperature_changed": "Aktuálna teplota zmenená", + "current_humidity_changed": "Aktuálna vlhkosť zmenená" + }, + "condition_type": { + "is_hvac_mode": "Termostat je nastavený na konkrétny režim HVAC", + "is_hvac_action": "Termostat vykonáva konkrétnu akciu HVAC" + }, + "action_type": { + "set_hvac_mode": "Nastaviť režim HVAC", + "set_temperature": "Nastaviť cieľovú teplotu" } } -} \ No newline at end of file +} diff --git a/custom_components/better_thermostat/utils/controlling.py b/custom_components/better_thermostat/utils/controlling.py index 693f6e4..63a1919 100644 --- a/custom_components/better_thermostat/utils/controlling.py +++ b/custom_components/better_thermostat/utils/controlling.py @@ -1,208 +1,513 @@ +"""Controlling module for Better Thermostat.""" + import asyncio import logging -from homeassistant.components.climate.const import HVACMode - -from custom_components.better_thermostat.model_fixes.model_quirks import ( - override_set_hvac_mode, - override_set_temperature, -) +from homeassistant.components.climate.const import PRESET_BOOST, HVACMode +from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN, UnitOfTemperature +from homeassistant.util.unit_conversion import TemperatureConverter from custom_components.better_thermostat.adapters.delegate import ( - set_offset, get_current_offset, - set_temperature, set_hvac_mode, + set_offset, + set_temperature, + set_valve, ) - -from custom_components.better_thermostat.events.trv import ( - convert_outbound_states, - update_hvac_action, +from custom_components.better_thermostat.events.trv import convert_outbound_states +from custom_components.better_thermostat.model_fixes.model_quirks import ( + override_set_hvac_mode, +) +from custom_components.better_thermostat.utils.const import ( + CalibrationMode, + CalibrationType, +) +from custom_components.better_thermostat.utils.helpers import ( + attr_to_celsius, + convert_to_float, ) - -from custom_components.better_thermostat.utils.helpers import convert_to_float - -from custom_components.better_thermostat.utils.const import CalibrationMode - _LOGGER = logging.getLogger(__name__) -class TaskManager: - def __init__(self): - self.tasks = set() +def _is_boost_heating_active(self) -> bool: + """Check if boost mode is active and heating is needed. - def create_task(self, coro): - task = asyncio.create_task(coro) + Returns True when boost preset is active and current temperature + is below target temperature. + """ + return ( + self.preset_mode == PRESET_BOOST + and self.cur_temp is not None + and self.bt_target_temp is not None + and self.cur_temp < self.bt_target_temp + ) + + +def _get_valve_control( + self, heater_entity_id: str, calibration_mode, calibration_type +) -> tuple[dict | None, str | None]: + """Determine valve control settings based on boost mode or calibration. + + Returns a tuple of (valve_settings_dict, source_name). + valve_settings_dict contains 'valve_percent' and 'apply_valve' keys. + Returns (None, None) if no valve control should be applied. + """ + # Forcing the valve on a non-direct-valve TRV bypasses the calibration chain + # and leaves the valve stuck open after boost ends. + if ( + _is_boost_heating_active(self) + and calibration_type == CalibrationType.DIRECT_VALVE_BASED + ): + max_opening = (self.real_trvs.get(heater_entity_id) or {}).get( + "valve_max_opening", 100 + ) + if isinstance(max_opening, (int, float)): + target_pct = max(0, min(100, int(round(float(max_opening))))) + else: + target_pct = 100 + return {"valve_percent": target_pct, "apply_valve": True}, "boost_mode" + + # Check calibration-based valve control + if calibration_type != CalibrationType.DIRECT_VALVE_BASED: + return None, None + + # Try calibration balance from various calibration modes + cal_bal = self.real_trvs[heater_entity_id].get("calibration_balance") + if ( + isinstance(cal_bal, dict) + and cal_bal.get("apply_valve") + and cal_bal.get("valve_percent") is not None + ): + source_map = { + CalibrationMode.MPC_CALIBRATION: "mpc_calibration", + CalibrationMode.TPI_CALIBRATION: "tpi_calibration", + CalibrationMode.PID_CALIBRATION: "pid_calibration", + CalibrationMode.HEATING_POWER_CALIBRATION: "heating_power_calibration", + } + source = source_map.get(calibration_mode) + if source: + return cal_bal, source + + # Fallback to raw balance + raw_balance = self.real_trvs[heater_entity_id].get("balance") + if ( + isinstance(raw_balance, dict) + and raw_balance.get("apply_valve") + and raw_balance.get("valve_percent") is not None + ): + return raw_balance, "balance" + + return None, None + + +class TaskManager: + """Manages background asyncio tasks with automatic cleanup. + + Tracks created tasks and automatically removes them from the set when they complete. + """ + + def __init__(self, hass=None): + """Initialize the task manager with an empty task set.""" + self.tasks = set() + self.hass = hass + + def create_task(self, coro, name=None): + """Create and track an asyncio task with automatic cleanup on completion. + + Parameters + ---------- + coro : Coroutine + The coroutine to execute as a task + name : str, optional + A descriptive name for the background task + + Returns + ------- + asyncio.Task + The created task + """ + if self.hass is not None: + task = self.hass.async_create_background_task( + coro, name=name or "bt_task_manager_task" + ) + else: + task = asyncio.create_task(coro) self.tasks.add(task) task.add_done_callback(self.tasks.discard) return task async def control_queue(self): - """The accutal control loop. - Parameters - ---------- - self : - instance of better_thermostat + """Process control commands from the queue and coordinate TRV control. - Returns - ------- - None - """ - if not hasattr(self, "task_manager"): - self.task_manager = TaskManager() + This async task runs continuously, processing control requests from the + control_queue_task queue. It calculates heating power once per cycle, + then controls all TRVs in parallel using asyncio.gather(). Cooler control + is executed separately if a cooler entity is configured. - while True: - if self.ignore_states or self.startup_running: - await asyncio.sleep(1) - continue - else: - controls_to_process = await self.control_queue_task.get() - if controls_to_process is not None: - self.ignore_states = True - result = True - for trv in self.real_trvs.keys(): - try: - _temp = await control_trv(self, trv) - if _temp is False: - result = False - except Exception: - _LOGGER.exception( - "better_thermostat %s: ERROR controlling: %s", - self.device_name, - trv, - ) - result = False - - # Retry task if some TRVs failed. Discard the task if the queue is full - # to avoid blocking and therefore deadlocking this function. - if result is False: - try: - self.control_queue_task.put_nowait(self) - except asyncio.QueueFull: - _LOGGER.debug( - "better_thermostat %s: control queue is full, discarding task" - ) - - self.control_queue_task.task_done() - self.ignore_states = False - - -async def control_trv(self, heater_entity_id=None): - """This is the main controller for the real TRV + The queue pauses during maintenance mode or when ignore_states is True. Parameters ---------- - self : - instance of better_thermostat + self : BetterThermostat + The Better Thermostat climate entity instance Returns ------- None + This function runs indefinitely in an asyncio task """ if not hasattr(self, "task_manager"): - self.task_manager = TaskManager() + self.task_manager = TaskManager(hass=self.hass) + + try: + while True: + if getattr(self, "in_maintenance", False): + await asyncio.sleep(1) + continue + + if self.ignore_states or self.startup_running: + await asyncio.sleep(1) + continue + else: + controls_to_process = await self.control_queue_task.get() + if controls_to_process is not None: + self.ignore_states = True + + # Calculate heating power once per cycle + try: + await self.calculate_heating_power() + except Exception: + _LOGGER.exception( + "better_thermostat %s: ERROR calculating heating power", + self.device_name, + ) + + # Calculate heat loss once per cycle (idle cooling) + try: + await self.calculate_heat_loss() + except Exception: + _LOGGER.exception( + "better_thermostat %s: ERROR calculating heat loss", + self.device_name, + ) + + # Handle cooler logic once per cycle + if self.cooler_entity_id is not None: + try: + await control_cooler(self) + except Exception: + _LOGGER.exception( + "better_thermostat %s: ERROR controlling cooler", + self.device_name, + ) + + # Create tasks for all TRVs to run in parallel + tasks = [] + for trv in self.real_trvs.keys(): + tasks.append(control_trv(self, trv)) + + # Run all TRV controls in parallel + results = await asyncio.gather(*tasks, return_exceptions=True) + + result = True + for i, res in enumerate(results): + if isinstance(res, Exception): + trv_id = list(self.real_trvs.keys())[i] + _LOGGER.error( + "better_thermostat %s: ERROR controlling TRV %s: %s", + self.device_name, + trv_id, + res, + ) + result = False + elif res is False: + result = False + + # Retry task if some TRVs failed. Discard the task if the queue is full + # to avoid blocking and therefore deadlocking this function. + if result is False: + try: + self.control_queue_task.put_nowait(self) + except asyncio.QueueFull: + _LOGGER.debug( + "better_thermostat %s: control queue is full, discarding task", + self.device_name, + ) + + self.control_queue_task.task_done() + if not getattr(self, "in_maintenance", False): + self.ignore_states = False + except asyncio.CancelledError: + _LOGGER.debug( + "better_thermostat %s: control_queue task cancelled, cleaning up", + self.device_name, + ) + raise + finally: + # Ensure ignore_states is reset on any exit unless maintenance wants it suppressed. + if not getattr(self, "in_maintenance", False): + self.ignore_states = False + + +async def control_cooler(self): + """Control the cooler entity based on current temperature and cooling setpoint. + + Activates cooling when current temperature exceeds target cooling temperature + minus tolerance and is above heating target. Deactivates cooling when + temperature drops below cooling target minus tolerance or when BT HVAC mode is OFF. + """ + # Get current cooler state to avoid sending redundant commands + cooler_state = self.hass.states.get(self.cooler_entity_id) + if cooler_state is None or cooler_state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN): + _LOGGER.debug( + "better_thermostat %s: cooler %s unavailable, skipping", + self.device_name, + self.cooler_entity_id, + ) + return + + current_hvac_mode = cooler_state.state + current_temp = cooler_state.attributes.get("temperature") + + # Determine desired state based on current conditions + desired_temp = self.bt_target_cooltemp + + if any( + v is None + for v in ( + self.cur_temp, + self.bt_target_cooltemp, + self.tolerance, + self.bt_target_temp, + ) + ): + _LOGGER.debug( + "better_thermostat %s: cooler %s one or more required values are None " + "(cur_temp=%s, bt_target_cooltemp=%s, tolerance=%s, bt_target_temp=%s), " + "defaulting to OFF", + self.device_name, + self.cooler_entity_id, + self.cur_temp, + self.bt_target_cooltemp, + self.tolerance, + self.bt_target_temp, + ) + desired_mode = HVACMode.OFF + elif self.bt_hvac_mode == HVACMode.OFF: + desired_mode = HVACMode.OFF + elif ( + self.cur_temp >= self.bt_target_cooltemp - self.tolerance + and self.cur_temp > self.bt_target_temp + ): + desired_mode = HVACMode.COOL + else: + desired_mode = HVACMode.OFF + + # Only send temperature command if it differs from current + if desired_temp is None: + _LOGGER.debug( + "better_thermostat %s: cooler %s desired temperature is None, " + "skipping set_temperature", + self.device_name, + self.cooler_entity_id, + ) + elif current_temp is None or current_temp != desired_temp: + if current_temp is None: + _LOGGER.debug( + "better_thermostat %s: cooler %s current temperature is unknown, " + "sending set_temperature command anyway", + self.device_name, + self.cooler_entity_id, + ) + else: + _LOGGER.debug( + "better_thermostat %s: TO COOLER set_temperature: %s from: %s to: %s", + self.device_name, + self.cooler_entity_id, + current_temp, + desired_temp, + ) + _temp_to_set = desired_temp + if self.hass.config.units.temperature_unit == UnitOfTemperature.FAHRENHEIT: + _temp_to_set = TemperatureConverter.convert( + desired_temp, UnitOfTemperature.CELSIUS, UnitOfTemperature.FAHRENHEIT + ) + _temp_to_set = round(_temp_to_set, 1) + await self.hass.services.async_call( + "climate", + "set_temperature", + {"entity_id": self.cooler_entity_id, "temperature": _temp_to_set}, + blocking=True, + context=self.context, + ) + + # Only send hvac_mode command if it differs from current + if current_hvac_mode != desired_mode: + _LOGGER.debug( + "better_thermostat %s: TO COOLER set_hvac_mode: %s from: %s to: %s", + self.device_name, + self.cooler_entity_id, + current_hvac_mode, + desired_mode, + ) + await self.hass.services.async_call( + "climate", + "set_hvac_mode", + {"entity_id": self.cooler_entity_id, "hvac_mode": desired_mode}, + blocking=True, + context=self.context, + ) + + +async def control_trv(self, heater_entity_id=None): + """Control a single TRV by setting temperature, HVAC mode, calibration, and valve position. + + All operations are executed within self._temp_lock to ensure atomic execution when + multiple TRVs are controlled in parallel. Unavailable TRVs are skipped without + executing any control operations. + + Parameters + ---------- + self : BetterThermostat + The Better Thermostat climate entity instance + heater_entity_id : str, optional + Entity ID of the TRV to control. If None or not found, returns False. + + Returns + ------- + bool + True if control succeeded or TRV was skipped (unavailable) + False if TRV not found in real_trvs or state conversion failed + """ + # Guard against missing or invalid heater_entity_id + if not heater_entity_id or heater_entity_id not in self.real_trvs: + return False + + if not hasattr(self, "task_manager"): + self.task_manager = TaskManager(hass=self.hass) async with self._temp_lock: self.real_trvs[heater_entity_id]["ignore_trv_states"] = True - await update_hvac_action(self) - await self.calculate_heating_power() + try: + # Preserve old action for change detection if attributes exist + if hasattr(self, "attr_hvac_action"): + self.old_attr_hvac_action = getattr(self, "attr_hvac_action", None) + # Recompute current hvac action (uses internal climate logic) + if hasattr(self, "_compute_hvac_action_pure"): + result = self._compute_hvac_action_pure() + self._commit_hvac_action(result) + self.attr_hvac_action = result.action + except Exception: + _LOGGER.debug( + "better_thermostat %s: hvac action recompute failed (non critical)", + getattr(self, "device_name", "unknown"), + ) _trv = self.hass.states.get(heater_entity_id) - _current_set_temperature = convert_to_float( - str(_trv.attributes.get("temperature", None)), - self.device_name, - "controlling()", + + # Check if TRV is available before attempting to control it + if _trv is None or ( + _trv.state in (STATE_UNAVAILABLE, STATE_UNKNOWN) + and not _is_boost_heating_active(self) + ): + _LOGGER.debug( + "better_thermostat %s: TRV %s is unavailable, skipping control. " + "Control will resume when TRV becomes available.", + self.device_name, + heater_entity_id, + ) + self.real_trvs[heater_entity_id]["ignore_trv_states"] = False + return True + + _current_set_temperature = attr_to_celsius( + self, _trv, "temperature", None, "controlling()" ) _remapped_states = convert_outbound_states( self, heater_entity_id, self.bt_hvac_mode ) if not isinstance(_remapped_states, dict): - _LOGGER.debug( - f"better_thermostat {self.device_name}: ERROR {heater_entity_id} {_remapped_states}" + _LOGGER.warning( + "better_thermostat %s: convert_outbound_states returned %r for %s " + "(expected dict) — skipping control cycle", + self.device_name, + _remapped_states, + heater_entity_id, ) - await asyncio.sleep(10) - self.ignore_states = False + await asyncio.sleep(2) self.real_trvs[heater_entity_id]["ignore_trv_states"] = False return False _temperature = _remapped_states.get("temperature", None) _calibration = _remapped_states.get("local_temperature_calibration", None) _calibration_mode = self.real_trvs[heater_entity_id]["advanced"].get( - "calibration_mode", CalibrationMode.DEFAULT + "calibration_mode", CalibrationMode.MPC_CALIBRATION ) + _calibration_type = self.real_trvs[heater_entity_id]["advanced"].get( + "calibration", CalibrationType.TARGET_TEMP_BASED + ) + # Pair the forced 100 % valve with a max-temp setpoint so the TRV + # firmware does not fight the valve command. + if ( + _is_boost_heating_active(self) + and _calibration_type == CalibrationType.DIRECT_VALVE_BASED + ): + _temperature = self.real_trvs[heater_entity_id]["max_temp"] + + # Optional: set valve position if supported (e.g., MQTT/Z2M) + try: + valve_settings, _source = _get_valve_control( + self, heater_entity_id, _calibration_mode, _calibration_type + ) + if valve_settings is not None: + target_pct = int(round(valve_settings.get("valve_percent", 0))) + _LOGGER.debug( + "better_thermostat %s: TO TRV set_valve: %s to: %s%% (source=%s)", + self.device_name, + heater_entity_id, + target_pct, + _source, + ) + ok = await set_valve(self, heater_entity_id, target_pct) + if not ok: + _LOGGER.debug( + "better_thermostat %s: delegate.set_valve returned False (target=%s%%, entity=%s, source=%s)", + self.device_name, + target_pct, + heater_entity_id, + _source, + ) + elif _calibration_type != CalibrationType.DIRECT_VALVE_BASED: + pass # non-valve TRV: no valve control expected + except Exception: + _LOGGER.debug( + "better_thermostat %s: set_valve not applied for %s (unsupported or failed)", + self.device_name, + heater_entity_id, + ) _new_hvac_mode = handle_window_open(self, _remapped_states) - # New cooler section - if self.cooler_entity_id is not None: - if ( - self.cur_temp >= self.bt_target_cooltemp - self.tolerance - and _new_hvac_mode is not HVACMode.OFF - and self.cur_temp > self.bt_target_temp - ): - await self.hass.services.async_call( - "climate", - "set_temperature", - { - "entity_id": self.cooler_entity_id, - "temperature": self.bt_target_cooltemp, - }, - blocking=True, - context=self.context, - ) - await self.hass.services.async_call( - "climate", - "set_hvac_mode", - {"entity_id": self.cooler_entity_id, "hvac_mode": HVACMode.COOL}, - blocking=True, - context=self.context, - ) - elif ( - self.cur_temp <= (self.bt_target_cooltemp - self.tolerance) - and _new_hvac_mode is not HVACMode.OFF - ): - await self.hass.services.async_call( - "climate", - "set_temperature", - { - "entity_id": self.cooler_entity_id, - "temperature": self.bt_target_cooltemp, - }, - blocking=True, - context=self.context, - ) - await self.hass.services.async_call( - "climate", - "set_hvac_mode", - {"entity_id": self.cooler_entity_id, "hvac_mode": HVACMode.OFF}, - blocking=True, - context=self.context, - ) - else: - await self.hass.services.async_call( - "climate", - "set_temperature", - { - "entity_id": self.cooler_entity_id, - "temperature": self.bt_target_cooltemp, - }, - blocking=True, - context=self.context, - ) - await self.hass.services.async_call( - "climate", - "set_hvac_mode", - {"entity_id": self.cooler_entity_id, "hvac_mode": HVACMode.OFF}, - blocking=True, - context=self.context, - ) - - # if we don't need ot heat, we force HVACMode to be off + # if we don't need to heat, we force HVACMode to be off if self.call_for_heat is False: _new_hvac_mode = HVACMode.OFF + # Safety override: if boost mode was active but we forced OFF (window/no-heat), + # ensure valve is reset to 0% to prevent overheating. Only direct-valve + # calibration types accept valve commands; LOCAL_BASED and + # TARGET_TEMP_BASED control via offset / setpoint instead. + if ( + _is_boost_heating_active(self) + and _new_hvac_mode == HVACMode.OFF + and _calibration_type == CalibrationType.DIRECT_VALVE_BASED + ): + _LOGGER.debug( + "better_thermostat %s: Boost safety override - resetting valve to 0%% because HVAC mode is OFF", + self.device_name, + ) + await set_valve(self, heater_entity_id, 0) + # Manage TRVs with no HVACMode.OFF _no_off_system_mode = ( HVACMode.OFF not in self.real_trvs[heater_entity_id]["hvac_modes"] @@ -215,7 +520,9 @@ async def control_trv(self, heater_entity_id=None): if _no_off_system_mode is True and _new_hvac_mode == HVACMode.OFF: _min_temp = self.real_trvs[heater_entity_id]["min_temp"] _LOGGER.debug( - f"better_thermostat {self.device_name}: sending {_min_temp}°C to the TRV because this device has no system mode off and heater should be off" + "better_thermostat %s: sending %s°C to the TRV because this device has no system mode off and heater should be off", + self.device_name, + _min_temp, ) _temperature = _min_temp @@ -229,7 +536,11 @@ async def control_trv(self, heater_entity_id=None): ) ): _LOGGER.debug( - f"better_thermostat {self.device_name}: TO TRV set_hvac_mode: {heater_entity_id} from: {_trv.state} to: {_new_hvac_mode}" + "better_thermostat %s: TO TRV set_hvac_mode: %s from: %s to: %s", + self.device_name, + heater_entity_id, + _trv.state, + _new_hvac_mode, ) self.real_trvs[heater_entity_id]["last_hvac_mode"] = _new_hvac_mode _tvr_has_quirk = await override_set_hvac_mode( @@ -239,7 +550,10 @@ async def control_trv(self, heater_entity_id=None): await set_hvac_mode(self, heater_entity_id, _new_hvac_mode) if self.real_trvs[heater_entity_id]["system_mode_received"] is True: self.real_trvs[heater_entity_id]["system_mode_received"] = False - self.task_manager.create_task(check_system_mode(self, heater_entity_id)) + self.task_manager.create_task( + check_system_mode(self, heater_entity_id), + name=f"bt_check_system_mode_{heater_entity_id}", + ) # set new calibration offset if ( @@ -255,8 +569,6 @@ async def control_trv(self, heater_entity_id=None): self.device_name, heater_entity_id, ) - # this should not be before, set_hvac_mode (because if it fails, the new hvac mode will never be sent) - self.ignore_states = False self.real_trvs[heater_entity_id]["ignore_trv_states"] = False return True @@ -270,11 +582,31 @@ async def control_trv(self, heater_entity_id=None): "last_calibration", _current_calibration ) + # If current calibration already matches target, reset calibration_received + # to avoid it getting stuck at False when the state event was suppressed. + if ( + self.real_trvs[heater_entity_id]["calibration_received"] is False + and _current_calibration is not None + and abs(float(_current_calibration) - float(_calibration)) < 0.5 + ): + _LOGGER.debug( + "better_thermostat %s: TRV %s calibration already at target (%s), " + "resetting calibration_received flag", + self.device_name, + heater_entity_id, + _calibration, + ) + self.real_trvs[heater_entity_id]["calibration_received"] = True + if self.real_trvs[heater_entity_id][ "calibration_received" ] is True and float(_old_calibration) != float(_calibration): _LOGGER.debug( - f"better_thermostat {self.device_name}: TO TRV set_local_temperature_calibration: {heater_entity_id} from: {_old_calibration} to: {_calibration}" + "better_thermostat %s: TO TRV set_local_temperature_calibration: %s from: %s to: %s", + self.device_name, + heater_entity_id, + _old_calibration, + _calibration, ) await set_offset(self, heater_entity_id, _calibration) self.real_trvs[heater_entity_id]["calibration_received"] = False @@ -286,60 +618,76 @@ async def control_trv(self, heater_entity_id=None): if _temperature != _current_set_temperature: old = self.real_trvs[heater_entity_id].get("last_temperature", "?") _LOGGER.debug( - f"better_thermostat {self.device_name}: TO TRV set_temperature: {heater_entity_id} from: {old} to: {_temperature}" + "better_thermostat %s: TO TRV set_temperature: %s from: %s to: %s", + self.device_name, + heater_entity_id, + old, + _temperature, ) self.real_trvs[heater_entity_id]["last_temperature"] = _temperature - _tvr_has_quirk = await override_set_temperature( - self, heater_entity_id, _temperature - ) - if _tvr_has_quirk is False: - await set_temperature(self, heater_entity_id, _temperature) + await set_temperature(self, heater_entity_id, _temperature) if self.real_trvs[heater_entity_id]["target_temp_received"] is True: self.real_trvs[heater_entity_id]["target_temp_received"] = False self.task_manager.create_task( - check_target_temperature(self, heater_entity_id) + check_target_temperature(self, heater_entity_id), + name=f"bt_check_target_temp_{heater_entity_id}", ) - await asyncio.sleep(3) - self.real_trvs[heater_entity_id]["ignore_trv_states"] = False - return True + # Let TRV state updates propagate before accepting new state events + await asyncio.sleep(3) + self.real_trvs[heater_entity_id]["ignore_trv_states"] = False + return True def handle_window_open(self, _remapped_states): - """handle window open""" - _converted_hvac_mode = _remapped_states.get("system_mode", None) - _hvac_mode_send = _converted_hvac_mode + """Override HVAC mode to OFF when window is open. - if self.window_open is True and self.last_window_state is False: - # if the window is open or the sensor is not available, we're done - self.last_main_hvac_mode = _hvac_mode_send - _hvac_mode_send = HVACMode.OFF - self.last_window_state = True - _LOGGER.debug( - f"better_thermostat {self.device_name}: control_trv: window is open or status of window is unknown, setting window open" - ) - elif self.window_open is False and self.last_window_state is True: - _hvac_mode_send = self.last_main_hvac_mode - self.last_window_state = False - _LOGGER.debug( - f"better_thermostat {self.device_name}: control_trv: window is closed, setting window closed restoring mode: {_hvac_mode_send}" - ) + Parameters + ---------- + self : BetterThermostat + The Better Thermostat climate entity instance + _remapped_states : dict + Dictionary containing remapped TRV states including system_mode - # Force off on window open - if self.window_open is True: - _hvac_mode_send = HVACMode.OFF - - return _hvac_mode_send + Returns + ------- + HVACMode + HVACMode.OFF if window is open, otherwise the remapped system_mode + """ + if self.window_open: + return HVACMode.OFF + return _remapped_states.get("system_mode", None) async def check_system_mode(self, heater_entity_id=None): - """check system mode""" + """Wait for TRV to confirm HVAC mode change, timeout after 6 minutes. + + Polls the TRV state every second until hvac_mode matches last_hvac_mode + or timeout is reached. Sets system_mode_received flag when complete. + + Parameters + ---------- + self : BetterThermostat + The Better Thermostat climate entity instance + heater_entity_id : str, optional + Entity ID of the TRV to check + + Returns + ------- + bool + Always returns True + """ _timeout = 0 _real_trv = self.real_trvs[heater_entity_id] while _real_trv["hvac_mode"] != _real_trv["last_hvac_mode"]: if _timeout > 360: - _LOGGER.debug( - f"better_thermostat {self.device_name}: {heater_entity_id} the real TRV did not respond to the system mode change" + _LOGGER.warning( + "better_thermostat %s: TRV %s did not confirm the system mode change " + "after 360s (wrote=%s, last reported=%s); giving up and assuming applied", + self.device_name, + heater_entity_id, + _real_trv["last_hvac_mode"], + _real_trv["hvac_mode"], ) _timeout = 0 break @@ -351,22 +699,44 @@ async def check_system_mode(self, heater_entity_id=None): async def check_target_temperature(self, heater_entity_id=None): - """Check if target temperature is reached.""" + """Wait for TRV to confirm target temperature change, timeout after 6 minutes. + + Polls the TRV temperature attribute every second until it matches + last_temperature or timeout is reached. Sets target_temp_received flag when complete. + + Parameters + ---------- + self : BetterThermostat + The Better Thermostat climate entity instance + heater_entity_id : str, optional + Entity ID of the TRV to check + + Returns + ------- + bool + Always returns True + """ _timeout = 0 _real_trv = self.real_trvs[heater_entity_id] while True: - _current_set_temperature = convert_to_float( - str( - self.hass.states.get(heater_entity_id).attributes.get( - "temperature", None - ) - ), - self.device_name, - "check_target_temperature()", + _trv_state = self.hass.states.get(heater_entity_id) + if _trv_state is None or _trv_state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN): + _LOGGER.debug( + "better_thermostat %s: %s became unavailable during check_target_temperature", + self.device_name, + heater_entity_id, + ) + break + _current_set_temperature = attr_to_celsius( + self, _trv_state, "temperature", None, "check_target_temperature()" ) if _timeout == 0: _LOGGER.debug( - f"better_thermostat {self.device_name}: {heater_entity_id} / check_target_temp / _last: {_real_trv['last_temperature']} - _current: {_current_set_temperature}" + "better_thermostat %s: %s / check_target_temp / _last: %s - _current: %s", + self.device_name, + heater_entity_id, + _real_trv["last_temperature"], + _current_set_temperature, ) if ( _current_set_temperature is None @@ -375,8 +745,13 @@ async def check_target_temperature(self, heater_entity_id=None): _timeout = 0 break if _timeout > 360: - _LOGGER.debug( - f"better_thermostat {self.device_name}: {heater_entity_id} the real TRV did not respond to the target temperature change" + _LOGGER.warning( + "better_thermostat %s: TRV %s did not confirm the target temperature " + "after 360s (wrote=%s, last reported=%s); giving up and assuming applied", + self.device_name, + heater_entity_id, + _real_trv["last_temperature"], + _current_set_temperature, ) _timeout = 0 break diff --git a/custom_components/better_thermostat/utils/helpers.py b/custom_components/better_thermostat/utils/helpers.py index 68ade82..5873c14 100644 --- a/custom_components/better_thermostat/utils/helpers.py +++ b/custom_components/better_thermostat/utils/helpers.py @@ -1,22 +1,99 @@ """Helper functions for the Better Thermostat component.""" -import re +from collections.abc import Callable, Mapping +from datetime import datetime import logging import math -from datetime import datetime -from enum import Enum -from homeassistant.helpers import device_registry as dr, entity_registry as er -from homeassistant.helpers.entity_registry import async_entries_for_config_entry +import re +from typing import Any from homeassistant.components.climate.const import HVACMode +from homeassistant.const import UnitOfTemperature +from homeassistant.core import State +from homeassistant.helpers import device_registry as dr, entity_registry as er +from homeassistant.helpers.entity_registry import async_entries_for_config_entry +from homeassistant.util import dt as dt_util +from homeassistant.util.unit_conversion import TemperatureConverter -from custom_components.better_thermostat.utils.const import CONF_HEAT_AUTO_SWAPPED - +from custom_components.better_thermostat.utils.const import ( + CONF_HEAT_AUTO_SWAPPED, + MAX_HEATING_POWER, + MAX_REASONABLE_TEMPERATURE, + MIN_HEATING_POWER, + MIN_REASONABLE_TEMPERATURE, + VALVE_MIN_BASE, + VALVE_MIN_OPENING_LARGE_DIFF, + VALVE_MIN_PROPORTIONAL_SLOPE, + VALVE_MIN_SMALL_DIFF_THRESHOLD, + VALVE_MIN_THRESHOLD_TEMP_DIFF, + CalibrationMode, +) _LOGGER = logging.getLogger(__name__) +def normalize_calibration_mode( + mode: CalibrationMode | str | None, +) -> CalibrationMode | str | None: + """Normalize a calibration_mode field from TRV advanced data.""" + + # Backwards compatibility: older configs stored numeric calibration modes + # (e.g. 0 for DEFAULT). Only map known values. + if isinstance(mode, (int, float)): + try: + numeric = int(mode) + except (TypeError, ValueError): + numeric = None + if numeric == 0: + return CalibrationMode.DEFAULT + return None + + if isinstance(mode, CalibrationMode): + return mode + if isinstance(mode, str): + value = mode.strip().lower() + try: + return CalibrationMode(value) + except ValueError: + return value + return None + + +def is_calibration_mode( + mode: CalibrationMode | str | None, expected: CalibrationMode +) -> bool: + """Return True if ``mode`` is the expected CalibrationMode.""" + + normalized = normalize_calibration_mode(mode) + if isinstance(normalized, CalibrationMode): + return normalized == expected + if isinstance(normalized, str): + return normalized == expected.value + return False + + +def entity_uses_calibration_mode(bt, entity_id: str, expected: CalibrationMode) -> bool: + """Check if the given TRV has ``expected`` calibration mode configured.""" + + try: + advanced = (bt.real_trvs.get(entity_id, {}) or {}).get("advanced", {}) or {} + except AttributeError: + return False + mode = advanced.get("calibration_mode") + return is_calibration_mode(mode, expected) + + +def entity_uses_mpc_calibration(bt, entity_id: str) -> bool: + """Check if entity uses MPC calibration mode.""" + return entity_uses_calibration_mode(bt, entity_id, CalibrationMode.MPC_CALIBRATION) + + def get_hvac_bt_mode(self, mode: str) -> str: + """Return the main HVAC mode mapping for the Better Thermostat. + + The function handles simple mapping from HVACMode.HEAT to configured + internal modes used by the integration. + """ if mode == HVACMode.HEAT: mode = self.map_on_hvac_mode elif mode == HVACMode.HEAT_COOL: @@ -24,6 +101,42 @@ def get_hvac_bt_mode(self, mode: str) -> str: return mode +def normalize_hvac_mode(value: HVACMode | str) -> HVACMode | str: + """Normalize a hvac_mode value to a proper HVACMode enum when possible. + + Accepts + ------- + value : HVACMode | str + - HVACMode enum: returned as-is + - Strings like 'heat', 'off', 'heat_cool', 'auto', 'dry', 'fan_only' + - Strings like 'HVACMode.HEAT' (will be converted to HVACMode.HEAT) + + Returns + ------- + HVACMode | str + HVACMode if recognized, otherwise the lowercased string without prefix. + """ + if isinstance(value, HVACMode): + return value + if isinstance(value, str): + raw = value.strip() + # Strip enum-like prefix if present + if raw.lower().startswith("hvacmode."): + raw = raw.split(".", 1)[1] + key = raw.lower() + mapping = { + "off": HVACMode.OFF, + "heat": HVACMode.HEAT, + "cool": HVACMode.COOL, + "heat_cool": HVACMode.HEAT_COOL, + "auto": HVACMode.AUTO, + "dry": HVACMode.DRY, + "fan_only": HVACMode.FAN_ONLY, + } + return mapping.get(key, key) + return value + + def mode_remap(self, entity_id, hvac_mode: str, inbound: bool = False) -> str: """Remap HVAC mode to correct mode if nessesary. @@ -60,32 +173,81 @@ def mode_remap(self, entity_id, hvac_mode: str, inbound: bool = False) -> str: return HVACMode.HEAT_COOL if inbound and hvac_mode == HVACMode.HEAT_COOL: return HVACMode.HEAT + if HVACMode.HEAT_COOL not in trv_modes and HVACMode.HEAT in trv_modes: + # entity only supports HEAT, but not HEAT_COOL - need to translate + if not inbound and hvac_mode == HVACMode.HEAT_COOL: + return HVACMode.HEAT + if inbound and hvac_mode == HVACMode.HEAT: + return HVACMode.HEAT_COOL if hvac_mode != HVACMode.AUTO: return hvac_mode _LOGGER.error( - f"better_thermostat {self.device_name}: {entity_id} HVAC mode { - hvac_mode} is not supported by this device, is it possible that you forgot to set the heat auto swapped option?" + "better_thermostat %s: %s HVAC mode %s is not supported by this device, " + "is it possible that you forgot to set the heat auto swapped option?", + self.device_name, + entity_id, + hvac_mode, ) return HVACMode.OFF def heating_power_valve_position(self, entity_id): + """Compute an expected valve position from the heating power. + + Given the global `heating_power` estimate and the target/current + temperature, a heuristic mapping to valve opening percentage is + returned (between 0.0 and 1.0). + """ _temp_diff = float(float(self.bt_target_temp) - float(self.cur_temp)) + # Guard against negative temp_diff (room warmer than target) + # This can occur in TRV override edge case when temperature rises + # above target but TRV still reports heating (delayed update) + if _temp_diff <= 0: + _LOGGER.debug( + f"better_thermostat {self.device_name}: {entity_id} " + f"cur_temp >= target_temp ({self.cur_temp} >= {self.bt_target_temp}), " + f"setting valve to 0%" + ) + return 0.0 + + # Ensure heating_power is bounded to realistic values + # This protects against incorrectly learned high values + heating_power = max( + MIN_HEATING_POWER, min(MAX_HEATING_POWER, float(self.heating_power)) + ) + + # Original formula with improved robustness a = 0.019 b = 0.946 - valve_pos = a * (_temp_diff / self.heating_power) ** b + valve_pos = a * (_temp_diff / heating_power) ** b - if valve_pos < 0.0: - valve_pos = 0.0 - if valve_pos > 1.0: - valve_pos = 1.0 + # Apply minimum valve position when heating is actively needed + # If temp_diff > threshold, ensure minimum valve opening + # This prevents the system from getting stuck with too-low valve positions + if _temp_diff > VALVE_MIN_THRESHOLD_TEMP_DIFF: + valve_pos = max(VALVE_MIN_OPENING_LARGE_DIFF, valve_pos) + elif _temp_diff >= VALVE_MIN_SMALL_DIFF_THRESHOLD: + # For smaller differences, use a proportional minimum + min_valve = ( + VALVE_MIN_BASE + + (_temp_diff - VALVE_MIN_SMALL_DIFF_THRESHOLD) + * VALVE_MIN_PROPORTIONAL_SLOPE + ) + valve_pos = max(min_valve, valve_pos) + + # Bound to valid range + valve_pos = max(0.0, min(1.0, valve_pos)) _LOGGER.debug( - f"better_thermostat {self.device_name}: {entity_id} / heating_power_valve_position - temp diff: {round( - _temp_diff, 1)} - heating power: {round(self.heating_power, 4)} - expected valve position: {round(valve_pos * 100)}%" + "better_thermostat %s: %s / heating_power_valve_position - temp diff: %s - heating power: %s (bounded) - expected valve position: %s%%", + self.device_name, + entity_id, + round(_temp_diff, 1), + round(heating_power, 4), + round(valve_pos * 100), ) return valve_pos @@ -118,14 +280,28 @@ def heating_power_valve_position(self, entity_id): # | 0.5 | 1.0000 | +def is_reasonable_temperature(value: float | None) -> bool: + """Return ``True`` iff ``value`` is a plausible indoor temperature in °C. + + Rejects ``None`` and any value outside ``MIN_REASONABLE_TEMPERATURE`` .. + ``MAX_REASONABLE_TEMPERATURE``. Out-of-range values are typically + marker / garbage readings produced by upstream integrations (for + example, AVM Fritz!DECT exposes 126.5 / 127 °C when the thermostat is + in OFF / ON mode). + """ + if value is None: + return False + return MIN_REASONABLE_TEMPERATURE <= value <= MAX_REASONABLE_TEMPERATURE + + def convert_to_float( - value: str | float, instance_name: str, context: str + value: str | int | float | None, instance_name: str, context: str ) -> float | None: """Convert value to float or print error message. Parameters ---------- - value : str, int, float + value : str | int | float | None the value to convert to float instance_name : str the name of the instance thermostat @@ -134,38 +310,170 @@ def convert_to_float( Returns ------- - float - the converted value - None - If error occurred and cannot convert the value. + float | None + the converted value, or None if conversion failed """ if value is None or value == "None": return None try: - return round_by_step(float(value), 0.1) + # Use 0.01 step (2 decimal places) to preserve sensor precision. + # Rounding to 0.1 caused issues where 19.97 became 20.0, leading to + # incorrect HVAC action decisions (see issues #1792, #1789, #1785). + return round_by_step(float(value), 0.01) except (ValueError, TypeError, AttributeError, KeyError): _LOGGER.debug( - f"better thermostat {instance_name}: Could not convert '{ - value}' to float in {context}" + "better thermostat %s: Could not convert '%s' to float in %s", + instance_name, + value, + context, ) return None -class rounding(Enum): - # rounding functions that avoid errors due to using floats +def convert_to_float_celsius( + value: str | int | float | None, + instance_name: str, + context: str, + unit_of_measurement: str | None = None, +) -> float | None: + """Convert value to float and ensure it is in Celsius. + If *unit_of_measurement* indicates Fahrenheit the value is converted to + Celsius after the initial float conversion. + + Parameters + ---------- + value : str | int | float | None + the value to convert + instance_name : str + thermostat instance name (for logging) + context : str + calling function context (for logging) + unit_of_measurement : str | None + the unit of the incoming value (e.g. ``UnitOfTemperature.FAHRENHEIT``) + + Returns + ------- + float | None + the value in Celsius, or None if conversion failed + """ + result = convert_to_float(value, instance_name, context) + if result is not None and unit_of_measurement == UnitOfTemperature.FAHRENHEIT: + result = TemperatureConverter.convert( + result, UnitOfTemperature.FAHRENHEIT, UnitOfTemperature.CELSIUS + ) + result = round(result, 2) + return result + + +def state_temperature_unit( + attributes: Mapping[str, object] | None, system_unit: str | None +) -> str | None: + """Resolve the temperature unit of a state's attributes. + + ``climate`` entities report their temperatures in the Home Assistant system + unit and do not expose a ``temperature_unit`` / ``unit_of_measurement`` + attribute. ``system_unit`` (``hass.config.units.temperature_unit``) is used + as the fallback so the values are interpreted in the right unit. Sensors + that carry an explicit ``unit_of_measurement`` keep it. + + Parameters + ---------- + attributes : Mapping[str, object] | None + the state attributes to inspect, or None when the state is missing + system_unit : str | None + the configured system temperature unit, used as fallback + + Returns + ------- + str | None + the resolved temperature unit, or ``system_unit`` when no explicit + unit attribute is present + """ + if not attributes: + return system_unit + for attr in ("temperature_unit", "unit_of_measurement"): + unit = attributes.get(attr) + if isinstance(unit, str): + return unit + return system_unit + + +def attr_to_celsius( + self, + state: State | None, + key: str, + default: str | int | float | None = None, + context: str = "", +) -> float | None: + """Read a temperature attribute from a foreign state and return it in °C. + + The single inbound boundary for foreign temperatures: it resolves the source + unit via :func:`state_temperature_unit` (system-unit fallback, since + ``climate`` entities expose no unit attribute) and converts to the Celsius + Better Thermostat works in internally. + + Parameters + ---------- + self : + the Better Thermostat instance, supplying ``hass`` and ``device_name`` + state : State | None + the source state to read from, or None when it is unavailable + key : str + the attribute name holding the temperature (e.g. ``"temperature"``) + default : str | int | float | None + value used when the attribute is missing + context : str + calling context, forwarded for logging + + Returns + ------- + float | None + the temperature in Celsius, or None if conversion failed + + See Also + -------- + convert_to_float_celsius : performs the unit conversion + state_temperature_unit : resolves the source unit + """ + attributes = state.attributes if state is not None else {} + return convert_to_float_celsius( + str(attributes.get(key, default)), + self.device_name, + context, + unit_of_measurement=state_temperature_unit( + attributes, self.hass.config.units.temperature_unit + ), + ) + + +class rounding: + """Rounding helpers for stable step-based rounding. + + Provides minor offsets to avoid floating point rounding artifacts when + converting values to integer steps. + """ + + @staticmethod def up(x: float) -> float: + """Round up with a tiny epsilon to avoid FP artifacts.""" return math.ceil(x - 0.0001) + @staticmethod def down(x: float) -> float: + """Round down with a tiny epsilon to avoid FP artifacts.""" return math.floor(x + 0.0001) + @staticmethod def nearest(x: float) -> float: + """Round to nearest step with a small epsilon to avoid up-rounding.""" return round(x - 0.0001) def round_by_step( - value: float | None, step: float | None, f_rounding: rounding = rounding.nearest + value: float | None, + step: float | None, + f_rounding: Callable[[float], float] = rounding.nearest, ) -> float | None: """Round the value based on the allowed decimal 'step' size. @@ -175,6 +483,8 @@ def round_by_step( the value to round step : float size of one step + f_rounding : callable + rounding function (default: rounding.nearest) Returns ------- @@ -184,6 +494,9 @@ def round_by_step( if value is None or step is None: return None + # Use default rounding function if none provided + if f_rounding is None: + f_rounding = rounding.nearest # convert to integer number of steps for rounding, then convert back to decimal return f_rounding(value / step) * step @@ -205,7 +518,7 @@ def check_float(potential_float): try: float(potential_float) return True - except ValueError: + except (ValueError, TypeError): return False @@ -225,7 +538,7 @@ def convert_time(time_string): If the time string is not a valid time. """ try: - _current_time = datetime.now() + _current_time = dt_util.now() _get_hours_minutes = datetime.strptime(time_string, "%H:%M") return _current_time.replace( hour=_get_hours_minutes.hour, @@ -238,49 +551,183 @@ def convert_time(time_string): async def find_valve_entity(self, entity_id): - """Find the local calibration entity for the TRV. + """Locate a per-TRV valve position helper entity, if available. - This is a hacky way to find the local calibration entity for the TRV. It is not possible to find the entity - automatically, because the entity_id is not the same as the friendly_name. The friendly_name is the same for all - thermostats of the same brand, but the entity_id is different. - - Parameters - ---------- - self : - self instance of better_thermostat - - Returns - ------- - str - the entity_id of the local calibration entity - None - if no local calibration entity was found + Returns a mapping with the entity_id, whether it appears writable, and the + detection reason. ``None`` if no related entity could be found. """ entity_registry = er.async_get(self.hass) reg_entity = entity_registry.async_get(entity_id) if reg_entity is None: return None - entity_entries = async_entries_for_config_entry( - entity_registry, reg_entity.config_entry_id - ) + + # Some integrations (notably certain Zigbee stacks) may expose valve helpers + # under a different Home Assistant device_id than the climate entity. + # To support these, also match candidates by shared device identifiers. + dev_reg = None + base_identifiers: set[tuple[str, str]] = set() + try: + dev_reg = dr.async_get(self.hass) + device_id = getattr(reg_entity, "device_id", None) + base_device = dev_reg.async_get(device_id) if device_id is not None else None + base_identifiers = set(getattr(base_device, "identifiers", set()) or set()) + except Exception: + dev_reg = None + base_identifiers = set() + + config_entry_id = reg_entity.config_entry_id + if config_entry_id is None: + return None + entity_entries = async_entries_for_config_entry(entity_registry, config_entry_id) + preferred_domains = {"number", "input_number"} + readonly_candidate: dict[str, Any] | None = None + + def _device_matches(candidate) -> bool: + # Strong match: same device + if getattr(candidate, "device_id", None) == getattr( + reg_entity, "device_id", None + ): + return True + # Fallback: match by shared identifiers if device registry is available + if dev_reg is None or not base_identifiers: + return False + cand_device_id = getattr(candidate, "device_id", None) + if not cand_device_id: + return False + try: + cand_device = dev_reg.async_get(cand_device_id) + except Exception: + return False + cand_identifiers = set(getattr(cand_device, "identifiers", set()) or set()) + return bool(base_identifiers.intersection(cand_identifiers)) + + # Known translation_key values used by TRV integrations for valve-related entities. + # These are stable, language-independent identifiers set by the integration. + _VALVE_TRANSLATION_KEYS: dict[str, str] = { + "valve_position": "valve_position", + "valve_opening_degree": "valve_opening_degree", + "valve_closing_degree": "valve_closing_degree", + "pi_heating_demand": "pi_heating_demand", + "heating_demand": "pi_heating_demand", + # Shelly BLU TRV uses this translation_key + "valve": "valve_position", + } + + def _classify_by_translation_key(entity) -> str | None: + """Classify entity by its translation_key (stable, language-independent).""" + tk = getattr(entity, "translation_key", None) + if tk and tk in _VALVE_TRANSLATION_KEYS: + return _VALVE_TRANSLATION_KEYS[tk] + return None + + def _classify(uid: str, ent_id: str, original_name: str) -> str | None: + """Classify by string matching (fallback for integrations without translation_key).""" + descriptor = f"{uid} {ent_id} {original_name}".lower() + # Sonoff TRVZB (and some others) expose explicit valve degree entities + if "valve_opening_degree" in descriptor: + return "valve_opening_degree" + if "valve_closing_degree" in descriptor: + return "valve_closing_degree" + + # Existing patterns + if "pi_heating_demand" in descriptor: + return "pi_heating_demand" + if "valve_position" in descriptor: + return "valve_position" + + # Generic fallbacks: try to catch "valve ... position/opening/degree" + if "valve" in descriptor and ( + "position" in descriptor + or "opening" in descriptor + or "degree" in descriptor + ): + return "valve_generic" + + if descriptor.endswith("_position") or descriptor.endswith(" position"): + return "position" + return None + + def _score(reason: str, writable: bool, domain: str) -> tuple[int, int, int]: + # Higher is better. + reason_score = { + "valve_opening_degree": 100, + "valve_closing_degree": 95, + "valve_position": 90, + "pi_heating_demand": 80, + "valve_generic": 60, + "position": 50, + }.get(reason, 0) + writable_score = 10 if writable else 0 + domain_score = 1 if domain in preferred_domains else 0 + return (reason_score, writable_score, domain_score) + + best: dict[str, Any] | None = None + best_score: tuple[int, int, int] = (-1, -1, -1) + for entity in entity_entries: - uid = entity.unique_id - # Make sure we use the correct device entities - if entity.device_id == reg_entity.device_id: - if "_valve_position" in uid or "_position" in uid: - _LOGGER.debug( - f"better thermostat: Found valve position entity { - entity.entity_id} for {entity_id}" - ) - return entity.entity_id + uid = entity.unique_id or "" + if not _device_matches(entity): + continue + + # Prefer translation_key (stable, language-independent) over string matching + reason = _classify_by_translation_key(entity) + if reason is None: + reason = _classify( + uid, + entity.entity_id or "", + getattr(entity, "original_name", None) or "", + ) + if reason is None: + continue + domain = (entity.entity_id or "").split(".", 1)[0] + writable = domain in preferred_domains + info = { + "entity_id": entity.entity_id, + "writable": writable, + "reason": reason, + "domain": domain, + } + + score = _score(reason, writable, domain) + if best is None or score > best_score: + best = info + best_score = score + if not writable and readonly_candidate is None: + readonly_candidate = info + + if best is not None and best.get("writable"): + _LOGGER.debug( + "better thermostat: Found writable valve helper %s for %s (reason=%s)", + best.get("entity_id"), + entity_id, + best.get("reason"), + ) + return best + + if readonly_candidate is not None: + _LOGGER.debug( + "better thermostat: Found read-only valve helper %s for %s (reason=%s)", + readonly_candidate.get("entity_id"), + entity_id, + readonly_candidate.get("reason"), + ) + return readonly_candidate _LOGGER.debug( - f"better thermostat: Could not find valve position entity for {entity_id}" + "better thermostat: Could not find valve position entity for %s", entity_id ) return None -async def find_battery_entity(self, entity_id): +async def find_battery_entity(self, entity_id, _visited=None): + """Find the battery entity related to the given entity's device. + + Returns the `entity_id` of the battery sensor attached to the same device + as `entity_id`, or None if none found. + + For groups, returns the battery entity with the lowest battery level + among all group members. + """ entity_registry = er.async_get(self.hass) entity_info = entity_registry.entities.get(entity_id) @@ -290,6 +737,17 @@ async def find_battery_entity(self, entity_id): device_id = entity_info.device_id + # Groups and virtual entities have no device_id + # Check if this is a group and resolve member batteries + if device_id is None: + state = self.hass.states.get(entity_id) + if state and "entity_id" in state.attributes: + # It's a group - find battery with lowest level among members + return await _find_lowest_battery_in_group( + self, state.attributes["entity_id"], _visited + ) + return None + for entity in entity_registry.entities.values(): if entity.device_id == device_id and ( entity.device_class == "battery" @@ -300,12 +758,75 @@ async def find_battery_entity(self, entity_id): return None +async def _find_lowest_battery_in_group(self, member_ids, visited=None): + """Find the battery entity with the lowest level among group members. + + Parameters + ---------- + self : BetterThermostat instance + member_ids : list of entity_id strings + visited : set of already visited entity_ids to prevent infinite recursion + + Returns + ------- + entity_id of the battery with lowest level, or None if no batteries found + """ + if visited is None: + visited = set() + + lowest_battery_id = None + lowest_battery_level = None + + for member_id in member_ids: + # Skip already visited entities to prevent infinite recursion + if member_id in visited: + continue + visited.add(member_id) + + battery_id = await find_battery_entity(self, member_id, visited) + if battery_id is None: + continue + + battery_state = self.hass.states.get(battery_id) + if battery_state is None: + continue + + try: + level = float(battery_state.state) + except (ValueError, TypeError): + _LOGGER.debug( + "better_thermostat: non-numeric battery state '%s' for %s", + battery_state.state, + battery_id, + ) + continue + + if lowest_battery_level is None or level < lowest_battery_level: + lowest_battery_level = level + lowest_battery_id = battery_id + + return lowest_battery_id + + +# Known translation_key values used by TRV integrations for calibration entities. +# These are stable, language-independent identifiers set by the integration. +_CALIBRATION_TRANSLATION_KEYS: set[str] = { + "local_temperature_calibration", + "temperature_calibration", + "temperature_offset", + "calibration_temperature", + "local_temperature_offset", + # eq3btsmart uses "offset" as translation_key for its temperature offset + "offset", +} + + async def find_local_calibration_entity(self, entity_id): """Find the local calibration entity for the TRV. - This is a hacky way to find the local calibration entity for the TRV. It is not possible to find the entity - automatically, because the entity_id is not the same as the friendly_name. The friendly_name is the same for all - thermostats of the same brand, but the entity_id is different. + Uses the entity registry's ``translation_key`` and ``original_name`` + for a stable, language-independent lookup. Falls back to the legacy + unique_id / entity_id string matching for older integrations. Parameters ---------- @@ -323,24 +844,52 @@ async def find_local_calibration_entity(self, entity_id): reg_entity = entity_registry.async_get(entity_id) if reg_entity is None: return None - entity_entries = async_entries_for_config_entry( - entity_registry, reg_entity.config_entry_id - ) + config_entry_id = reg_entity.config_entry_id + if config_entry_id is None: + return None + entity_entries = async_entries_for_config_entry(entity_registry, config_entry_id) + calibration_entity = None + # First pass: match by translation_key (preferred, stable approach) for entity in entity_entries: - uid = entity.unique_id + " " + entity.entity_id - # Make sure we use the correct device entities - if entity.device_id == reg_entity.device_id: - if "temperature_calibration" in uid or "temperature_offset" in uid: - _LOGGER.debug( - f"better thermostat: Found local calibration entity { - entity.entity_id} for {entity_id}" - ) - return entity.entity_id + if entity.device_id != reg_entity.device_id: + continue + tk = getattr(entity, "translation_key", None) + if tk and tk in _CALIBRATION_TRANSLATION_KEYS: + _LOGGER.debug( + "better thermostat: Found local calibration entity %s for %s (translation_key=%s)", + entity.entity_id, + entity_id, + tk, + ) + calibration_entity = entity.entity_id + break - _LOGGER.debug( - f"better thermostat: Could not find local calibration entity for {entity_id}" - ) - return None + # Second pass: fallback to string matching on unique_id / entity_id / original_name + if calibration_entity is None: + for entity in entity_entries: + if entity.device_id != reg_entity.device_id: + continue + descriptor = f"{entity.unique_id} {entity.entity_id} {getattr(entity, 'original_name', '') or ''}".lower() + if ( + "temperature_calibration" in descriptor + or "temperature_offset" in descriptor + or "temperatur_offset" in descriptor + or "local_temperature" in descriptor + ): + _LOGGER.debug( + "better thermostat: Found local calibration entity %s for %s (string match)", + entity.entity_id, + entity_id, + ) + calibration_entity = entity.entity_id + + if calibration_entity is None: + _LOGGER.debug( + "better thermostat: Could not find local calibration entity for %s", + entity_id, + ) + + return calibration_entity async def get_trv_intigration(self, entity_id): @@ -358,6 +907,8 @@ async def get_trv_intigration(self, entity_id): """ entity_reg = er.async_get(self.hass) entry = entity_reg.async_get(entity_id) + if entry is None: + return "generic_thermostat" try: return entry.platform except AttributeError: @@ -372,7 +923,7 @@ def get_max_value(obj, value, default): _temp = obj[key].get(value, 0) if _temp is not None: _raw.append(_temp) - return max(_raw, key=lambda x: float(x)) + return max(_raw, key=float) except (KeyError, ValueError): return default @@ -385,61 +936,81 @@ def get_min_value(obj, value, default): _temp = obj[key].get(value, 999) if _temp is not None: _raw.append(_temp) - return min(_raw, key=lambda x: float(x)) + return min(_raw, key=float) except (KeyError, ValueError): return default -async def get_device_model(self, entity_id): - """Fetches the device model from HA. - Parameters - ---------- - self : - self instance of better_thermostat - Returns - ------- - string - the name of the thermostat model +async def get_device_model(self, entity_id: str) -> str: + """Determine the device model from the Device Registry entry. + + Priority: model_id > model (before parens) > model > config > "generic" """ - if self.model is None: + selected: str | None = None + source: str = "none" + + try: + entity_reg = er.async_get(self.hass) + entry = entity_reg.async_get(entity_id) + dev_reg = dr.async_get(self.hass) + device = None try: - entity_reg = er.async_get(self.hass) - entry = entity_reg.async_get(entity_id) - dev_reg = dr.async_get(self.hass) - device = dev_reg.async_get(entry.device_id) - _LOGGER.debug(f"better_thermostat {self.device_name}: found device:") - _LOGGER.debug(device) - try: - # Z2M reports the device name as a long string with the actual model name in braces, we need to extract it - matches = re.findall(r"\((.+?)\)", device.model) - return matches[-1] - except IndexError: - # Other climate integrations might report the model name plainly, need more infos on this - return device.model - except ( - RuntimeError, - ValueError, - AttributeError, - KeyError, - TypeError, - NameError, - IndexError, - ): - try: - return ( - self.hass.states.get(entity_id) - .attributes.get("device") - .get("model", "generic") - ) - except ( - RuntimeError, - ValueError, - AttributeError, - KeyError, - TypeError, - NameError, - IndexError, - ): - return "generic" - else: - return self.model + dev_id = getattr(entry, "device_id", None) + if isinstance(dev_id, str) and dev_id: + device = dev_reg.async_get(dev_id) + except Exception: + device = None + # Selection exclusively via Device-Registry + try: + _LOGGER.debug( + "better_thermostat %s: device registry -> manufacturer=%s model=%s model_id=%s name=%s identifiers=%s", + self.device_name, + getattr(device, "manufacturer", None), + getattr(device, "model", None), + getattr(device, "model_id", None), + getattr(device, "name", None), + list(getattr(device, "identifiers", []) or []), + ) + except Exception: + pass + + dev_model_id = getattr(device, "model_id", None) + if isinstance(dev_model_id, str) and len(dev_model_id.strip()) >= 2: + selected = dev_model_id.strip() + source = "devreg.model_id" + else: + model_str = getattr(device, "model", None) + _LOGGER.debug( + "better_thermostat %s: device.model raw='%s'", + self.device_name, + model_str, + ) + if isinstance(model_str, str) and model_str.strip(): + # Extract model before parentheses: "MODEL (Desc)" -> "MODEL" + model_clean: str = re.sub(r"\s*\(.*\)\s*$", "", model_str).strip() + if len(model_clean) >= 2: + selected = model_clean + source = "devreg.model(before_parens)" + elif len(model_str.strip()) >= 2: + selected = model_str.strip() + source = "devreg.model" + except Exception: + # swallow registry access issues and continue to fallback + pass + + # Final fallback: configured model, then generic + if not selected and isinstance(self.model, str) and len(self.model.strip()) >= 2: + selected = self.model.strip() + source = "config.model" + if not selected: + selected = "generic" + source = "default" + + _LOGGER.debug( + "better_thermostat %s: get_device_model(%s) selected='%s' via %s", + self.device_name, + entity_id, + selected, + source, + ) + return selected diff --git a/custom_components/better_thermostat/utils/watcher.py b/custom_components/better_thermostat/utils/watcher.py index cc75c47..15a9858 100644 --- a/custom_components/better_thermostat/utils/watcher.py +++ b/custom_components/better_thermostat/utils/watcher.py @@ -1,41 +1,100 @@ +"""Watcher helpers to verify the presence and state of configured entities. + +This module contains utility functions to verify entities, check batteries, +and raise Home Assistant issues if an entity is missing or unavailable. + +Supports degraded mode operation where optional sensors (window, humidity, +outdoor, weather) can be unavailable without blocking thermostat operation. +""" + from __future__ import annotations -from homeassistant.helpers import issue_registry as ir -from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN + +from datetime import timedelta import logging +from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN +from homeassistant.helpers import issue_registry as ir +from homeassistant.util import dt as dt_util + DOMAIN = "better_thermostat" _LOGGER = logging.getLogger(__name__) +# Window after startup during which a transition into degraded mode is logged +# at DEBUG and does not raise a Home Assistant repair. Slow integrations +# (cloud weather, Ecowitt, etc.) often need several minutes to publish their +# first state. +STARTUP_DEGRADED_GRACE_PERIOD = timedelta(minutes=5) + +# States considered unavailable +UNAVAILABLE_STATES = ( + STATE_UNAVAILABLE, + STATE_UNKNOWN, + None, + "missing", + "unknown", + "unavail", + "unavailable", +) + + +def is_entity_available(hass, entity) -> bool: + """Check if an entity is available without side effects. + + Parameters + ---------- + hass : + Home Assistant instance + entity : str + Entity ID to check + + Returns + ------- + bool + True if entity exists and is in a valid state + """ + if entity is None: + return False + entity_states = hass.states.get(entity) + if entity_states is None: + return False + return entity_states.state not in UNAVAILABLE_STATES + async def check_entity(self, entity) -> bool: + """Check if a specific entity is present and available. + + Returns True if the entity is available and known to Home Assistant, + otherwise raises an issue and returns False. + """ if entity is None: return False entity_states = self.hass.states.get(entity) if entity_states is None: return False state = entity_states.state - if state in ( - STATE_UNAVAILABLE, - STATE_UNKNOWN, - None, - "missing", - "unknown", - "unavail", - "unavailable", - ): + if state in UNAVAILABLE_STATES: _LOGGER.debug( - f"better_thermostat {self.device_name}: {entity} is unavailable. with state {state}" + "better_thermostat %s: %s is unavailable. with state %s", + self.device_name, + entity, + state, ) return False if entity in self.devices_errors: self.devices_errors.remove(entity) self.async_write_ha_state() ir.async_delete_issue(self.hass, DOMAIN, f"missing_entity_{entity}") - self.hass.async_create_task(get_battery_status(self, entity)) + self.hass.async_create_background_task( + get_battery_status(self, entity), name=f"bt_battery_status_{entity}" + ) return True async def get_battery_status(self, entity): + """Read a battery entity for a device and update internal state. + + Uses the provided mapping stored in `self.devices_states`. + """ if entity in self.devices_states: battery_id = self.devices_states[entity].get("battery_id") if battery_id is not None: @@ -51,6 +110,10 @@ async def get_battery_status(self, entity): async def check_all_entities(self) -> bool: + """Verify all configured entities and report missing ones as issues. + + Returns True if all entities are available. + """ entities = self.all_entities for entity in entities: if not await check_entity(self, entity): @@ -73,3 +136,269 @@ async def check_all_entities(self) -> bool: ) return False return True + + +def get_optional_sensors(self) -> list: + """Return list of optional sensor entity IDs. + + Optional sensors are those that can be unavailable without + blocking thermostat operation (degraded mode). + + Returns + ------- + list + List of optional sensor entity IDs + """ + optional = [] + if getattr(self, "window_id", None): + optional.append(self.window_id) + if getattr(self, "humidity_sensor_entity_id", None): + optional.append(self.humidity_sensor_entity_id) + if getattr(self, "outdoor_sensor", None): + optional.append(self.outdoor_sensor) + if getattr(self, "weather_entity", None): + optional.append(self.weather_entity) + return optional + + +def get_critical_entities(self) -> list: + """Return list of critical entity IDs. + + Critical entities are TRVs - without them the thermostat cannot function. + The room temperature sensor is semi-critical (can fall back to TRV temp). + + Returns + ------- + list + List of critical entity IDs (TRVs) + """ + critical = [] + if hasattr(self, "real_trvs") and self.real_trvs: + critical.extend(list(self.real_trvs.keys())) + return critical + + +async def check_critical_entities(self) -> bool: + """Check only critical entities (TRVs). + + Returns True if all TRVs are available. Does not block on optional sensors. + + Returns + ------- + bool + True if all critical entities are available + """ + critical = get_critical_entities(self) + for entity in critical: + if not is_entity_available(self.hass, entity): + _LOGGER.warning( + "better_thermostat %s: Critical entity %s is unavailable", + self.device_name, + entity, + ) + if entity not in self.devices_errors: + self.devices_errors.append(entity) + self.async_write_ha_state() + ir.async_create_issue( + hass=self.hass, + domain=DOMAIN, + issue_id=f"missing_entity_{entity}", + is_fixable=True, + is_persistent=False, + learn_more_url="https://better-thermostat.org/qanda/missing_entity", + severity=ir.IssueSeverity.ERROR, + translation_key="missing_entity", + translation_placeholders={ + "entity": str(entity), + "name": str(self.device_name), + }, + ) + return False + else: + # Clear error if entity is now available + if entity in self.devices_errors: + self.devices_errors.remove(entity) + ir.async_delete_issue(self.hass, DOMAIN, f"missing_entity_{entity}") + # Update battery status for available entities + self.hass.async_create_background_task( + get_battery_status(self, entity), name=f"bt_battery_status_{entity}" + ) + return True + + +# Default delays for the optional-sensor startup retry loop. +# Short initial interval catches fast local sensors (Zigbee), longer +# intervals give cloud / weather integrations time. Total ≈ 60 s. +DEFAULT_OPTIONAL_SENSOR_DELAYS: tuple[int, ...] = (3, 5, 10, 15, 25) + + +async def await_optional_sensors( + self, + delays: tuple[int, ...] | list[int] = DEFAULT_OPTIONAL_SENSOR_DELAYS, + _sleep=None, +) -> list[str]: + """Wait for optional sensors to become available with increasing delays. + + After a reboot, optional sensors (outdoor, weather, window, humidity) + frequently need a few seconds to initialise. This helper retries with + increasing intervals so that ``check_and_update_degraded_mode`` is not + called while sensors are still starting up. + + Parameters + ---------- + self : + BetterThermostat instance (must expose ``.hass`` and + ``.device_name``). + delays : + Sequence of sleep durations in seconds between retries. + Defaults to ``DEFAULT_OPTIONAL_SENSOR_DELAYS`` (3/5/10/15/25 s). + _sleep : + Injectable sleep coroutine for testing. Defaults to + ``asyncio.sleep``. + + Returns + ------- + list[str] + Entity IDs of optional sensors that are still unavailable after + all retries have been exhausted (empty if all came online). + """ + import asyncio + + if _sleep is None: + _sleep = asyncio.sleep + + elapsed = 0 + pending: list[str] = [] + + for idx, delay in enumerate(delays): + pending = [ + eid + for eid in get_optional_sensors(self) + if not is_entity_available(self.hass, eid) + ] + if not pending: + _LOGGER.debug( + "better_thermostat %s: all optional sensors available (after %d s)", + self.device_name, + elapsed, + ) + return [] + _LOGGER.debug( + "better_thermostat %s: waiting for optional sensors " + "(attempt %d/%d, next check in %d s, pending: %s)", + self.device_name, + idx + 1, + len(delays), + delay, + ", ".join(pending), + ) + await _sleep(delay) + elapsed += delay + + # Final check after the last sleep + pending = [ + eid + for eid in get_optional_sensors(self) + if not is_entity_available(self.hass, eid) + ] + if not pending: + _LOGGER.debug( + "better_thermostat %s: all optional sensors available (after %d s)", + self.device_name, + elapsed, + ) + return pending + + +async def check_and_update_degraded_mode(self) -> bool: + """Check optional sensors and update degraded mode status. + + Sets self.degraded_mode to True if any optional sensor is unavailable. + Updates self.unavailable_sensors with list of unavailable optional sensors. + + Returns + ------- + bool + True if operating in degraded mode (some optional sensors unavailable) + """ + optional = get_optional_sensors(self) + unavailable = [] + + for entity in optional: + if not is_entity_available(self.hass, entity): + unavailable.append(entity) + _LOGGER.debug( + "better_thermostat %s: Optional sensor %s is unavailable (degraded mode)", + self.device_name, + entity, + ) + else: + # Update battery status for available optional sensors + self.hass.async_create_background_task( + get_battery_status(self, entity), name=f"bt_battery_status_{entity}" + ) + + # Check room temperature sensor - special case with TRV fallback + sensor_available = is_entity_available(self.hass, self.sensor_entity_id) + if not sensor_available: + unavailable.append(self.sensor_entity_id) + _LOGGER.warning( + "better_thermostat %s: Room temperature sensor %s unavailable, " + "falling back to TRV internal temperature", + self.device_name, + self.sensor_entity_id, + ) + else: + # Update battery status for room temperature sensor + self.hass.async_create_background_task( + get_battery_status(self, self.sensor_entity_id), + name=f"bt_battery_status_{self.sensor_entity_id}", + ) + + # Update instance state + old_degraded = getattr(self, "degraded_mode", False) + self.degraded_mode = len(unavailable) > 0 + self.unavailable_sensors = unavailable + + grace_until = getattr(self, "_degraded_grace_until", None) + in_grace = grace_until is not None and dt_util.now() < grace_until + has_warned = getattr(self, "_degraded_warning_emitted", False) + + if self.degraded_mode and not has_warned and not in_grace: + _LOGGER.warning( + "better_thermostat %s: Entering degraded mode. Unavailable sensors: %s", + self.device_name, + ", ".join(unavailable), + ) + ir.async_create_issue( + hass=self.hass, + domain=DOMAIN, + issue_id=f"degraded_mode_{self.device_name}", + is_fixable=False, + is_persistent=False, + learn_more_url="https://better-thermostat.org/qanda/degraded_mode", + severity=ir.IssueSeverity.WARNING, + translation_key="degraded_mode", + translation_placeholders={ + "name": str(self.device_name), + "sensors": ", ".join(unavailable), + }, + ) + self._degraded_warning_emitted = True + elif self.degraded_mode and in_grace and not old_degraded: + _LOGGER.debug( + "better_thermostat %s: degraded mode during startup grace period " + "(unavailable: %s); waiting for sensors before warning", + self.device_name, + ", ".join(unavailable), + ) + elif not self.degraded_mode and has_warned: + _LOGGER.info( + "better_thermostat %s: Exiting degraded mode. All sensors available.", + self.device_name, + ) + ir.async_delete_issue(self.hass, DOMAIN, f"degraded_mode_{self.device_name}") + self._degraded_warning_emitted = False + + self.async_write_ha_state() + return self.degraded_mode diff --git a/custom_components/better_thermostat/utils/weather.py b/custom_components/better_thermostat/utils/weather.py index 7699206..50ff6fa 100644 --- a/custom_components/better_thermostat/utils/weather.py +++ b/custom_components/better_thermostat/utils/weather.py @@ -1,24 +1,31 @@ +"""Weather utils.""" + from collections import deque -import logging -from datetime import timedelta, datetime -import homeassistant.util.dt as dt_util -from homeassistant.components.recorder import get_instance, history from contextlib import suppress +from datetime import datetime, timedelta +import logging + +from homeassistant.components.recorder import history +from homeassistant.components.weather import ( + DOMAIN as WEATHER_DOMAIN, + WeatherEntityFeature, +) +from homeassistant.exceptions import HomeAssistantError, ServiceNotSupported + +# get_instance location can differ between HA versions; prefer helpers API. +from homeassistant.helpers.recorder import get_instance +import homeassistant.util.dt as dt_util # from datetime import datetime, timedelta - # import homeassistant.util.dt as dt_util # from homeassistant.components.recorder.history import state_changes_during_period - -from .helpers import convert_to_float -from statistics import median - +from .helpers import convert_to_float_celsius _LOGGER = logging.getLogger(__name__) async def check_weather(self) -> bool: - """check weather predictions or ambient air temperature if available + """Check weather predictions or ambient air temperature if available. Parameters ---------- @@ -31,22 +38,41 @@ async def check_weather(self) -> bool: true if call_for_heat was changed """ old_call_for_heat = self.call_for_heat - _call_for_heat_weather = False + _call_for_heat_weather: bool | None = None _call_for_heat_outdoor = False self.call_for_heat = True if self.weather_entity is not None: _call_for_heat_weather = await check_weather_prediction(self) - self.call_for_heat = _call_for_heat_weather + if isinstance( + _call_for_heat_weather, bool + ): # Only apply if we got a valid response + self.call_for_heat = _call_for_heat_weather if self.outdoor_sensor is not None: if None in (self.last_avg_outdoor_temp, self.off_temperature): # TODO: add condition if heating period (oct-mar) then set it to true? - _LOGGER.warning( - "better_thermostat %s: no outdoor sensor data found. fallback to heat", - self.device_name, + # Check if sensor is currently unavailable (expected during startup) + _outdoor_state = self.hass.states.get(self.outdoor_sensor) + _sensor_unavailable = _outdoor_state is None or _outdoor_state.state in ( + "unavailable", + "unknown", + None, ) + + if _sensor_unavailable: + # Sensor not ready yet - expected during startup, just debug + _LOGGER.debug( + "better_thermostat %s: outdoor sensor not yet available, fallback to heat", + self.device_name, + ) + else: + # Sensor is available but we have no cached data - unexpected, warn + _LOGGER.warning( + "better_thermostat %s: outdoor sensor available but no data cached, fallback to heat", + self.device_name, + ) _call_for_heat_outdoor = True else: _call_for_heat_outdoor = self.last_avg_outdoor_temp < self.off_temperature @@ -63,79 +89,138 @@ async def check_weather(self) -> bool: return False -async def check_weather_prediction(self) -> bool: - """Checks configured weather entity for next two days of temperature predictions. +async def check_weather_prediction(self) -> bool | None: + """Check configured weather entity over roughly the next two days. + + The forecast horizon is normalised to about two days regardless of the + entity's forecast granularity (daily, twice-daily or hourly), and the + sampled temperatures are averaged. Returns ------- bool - True if the maximum forcast temperature is lower than the off temperature + True if the average forecast temperature (or the current + temperature) is below the off temperature, i.e. heat is required None if not successful """ if self.weather_entity is None: _LOGGER.warning( - f"better_thermostat {self.device_name}: weather entity not available." + "better_thermostat %s: weather entity not available.", self.device_name ) - return None + return False if self.off_temperature is None or not isinstance(self.off_temperature, float): _LOGGER.warning( - f"better_thermostat {self.device_name}: off_temperature not set or not a float." + "better_thermostat %s: off_temperature not set or not a float.", + self.device_name, ) - return None + return False try: + state = self.hass.states.get(self.weather_entity) + features = state.attributes.get("supported_features", 0) if state else 0 + + if features & WeatherEntityFeature.FORECAST_DAILY: + ftype = "daily" + elif features & WeatherEntityFeature.FORECAST_TWICE_DAILY: + ftype = "twice_daily" + elif features & WeatherEntityFeature.FORECAST_HOURLY: + ftype = "hourly" + else: + _LOGGER.warning( + "better_thermostat %s: weather entity '%s' does not advertise any forecast support.", + self.device_name, + self.weather_entity, + ) + return None + + # Sample roughly the next two days regardless of forecast granularity. + _forecast_samples = {"daily": 2, "twice_daily": 4, "hourly": 48}[ftype] + forecasts = await self.hass.services.async_call( - "weather", + WEATHER_DOMAIN, "get_forecasts", - {"type": "daily", "entity_id": self.weather_entity}, + {"type": ftype, "entity_id": [self.weather_entity]}, blocking=True, return_response=True, ) - forecast = forecasts.get(self.weather_entity).get("forecast") - if len(forecast) > 0: - cur_outside_temp = convert_to_float( - str( - self.hass.states.get(self.weather_entity).attributes.get( - "temperature" - ) + forecast_container = ( + forecasts.get(self.weather_entity) if isinstance(forecasts, dict) else None + ) + forecast = ( + forecast_container.get("forecast") + if isinstance(forecast_container, dict) + else None + ) + if isinstance(forecast, list) and len(forecast) > 0: + # current outside temp from entity state (may be None) + cur_state = self.hass.states.get(self.weather_entity) + cur_outside_temp = convert_to_float_celsius( + ( + str(cur_state.attributes.get("temperature")) + if cur_state and cur_state.attributes + else "" ), self.device_name, "check_weather_prediction()", + unit_of_measurement=( + cur_state.attributes.get("temperature_unit") + if cur_state and cur_state.attributes + else None + ), ) - max_forecast_temp = int( - round( - ( - convert_to_float( - str(forecast[0]["temperature"]), - self.device_name, - "check_weather_prediction()", - ) - + convert_to_float( - str(forecast[1]["temperature"]), - self.device_name, - "check_weather_prediction()", - ) + # average the sampled forecast temps over the two-day horizon + _entity_temp_unit = ( + cur_state.attributes.get("temperature_unit") + if cur_state and cur_state.attributes + else None + ) + temps = [] + for i in range(min(_forecast_samples, len(forecast))): + temps.append( + convert_to_float_celsius( + ( + str(forecast[i].get("temperature")) + if isinstance(forecast[i], dict) + else "" + ), + self.device_name, + "check_weather_prediction()", + unit_of_measurement=( + forecast[i].get("temperature_unit", _entity_temp_unit) + if isinstance(forecast[i], dict) + else _entity_temp_unit + ), ) - / 2 ) + valid_temps: list[float] = [t for t in temps if isinstance(t, (int, float))] + avg_forecast_temp = None + if valid_temps: + avg_forecast_temp = sum(valid_temps) / float(len(valid_temps)) + + cond_cur = ( + isinstance(cur_outside_temp, (int, float)) + and cur_outside_temp < self.off_temperature ) - return ( - cur_outside_temp < self.off_temperature - or max_forecast_temp < self.off_temperature + cond_fc = ( + isinstance(avg_forecast_temp, (int, float)) + and avg_forecast_temp < self.off_temperature ) + return bool(cond_cur or cond_fc) else: raise TypeError - except TypeError: + except (TypeError, ServiceNotSupported, HomeAssistantError): _LOGGER.warning( - f"better_thermostat {self.device_name}: no weather entity data found." + "better_thermostat %s: no weather entity data found.", self.device_name ) + # Return None (no opinion) on a transient failure so check_weather keeps + # the current call_for_heat decision while weather data is unavailable. return None async def check_ambient_air_temperature(self): - """Gets the history for two days and evaluates the necessary for heating. + """Get the history for two days and evaluates the necessary for heating. Returns ------- @@ -149,14 +234,29 @@ async def check_ambient_air_temperature(self): if self.off_temperature is None or not isinstance(self.off_temperature, float): _LOGGER.warning( - f"better_thermostat {self.device_name}: off_temperature not set or not a float." + "better_thermostat %s: off_temperature not set or not a float.", + self.device_name, ) return None - self.last_avg_outdoor_temp = convert_to_float( - self.hass.states.get(self.outdoor_sensor).state, + # Check if outdoor sensor is available + outdoor_state = self.hass.states.get(self.outdoor_sensor) + if outdoor_state is None or outdoor_state.state in ("unavailable", "unknown", None): + _LOGGER.debug( + "better_thermostat %s: outdoor sensor %s unavailable, skipping ambient check", + self.device_name, + self.outdoor_sensor, + ) + # Keep last known value or default to heating enabled + if self.last_avg_outdoor_temp is None: + self.call_for_heat = True + return None + + self.last_avg_outdoor_temp = convert_to_float_celsius( + outdoor_state.state, self.device_name, "check_ambient_air_temperature()", + unit_of_measurement=outdoor_state.attributes.get("unit_of_measurement"), ) if "recorder" in self.hass.config.components: _temp_history = DailyHistory(2) @@ -179,29 +279,48 @@ async def check_ambient_air_temperature(self): dt_util.utcnow(), lower_entity_id, ) - - for item in history_list.get(lower_entity_id): + items = [] + try: + items = history_list.get(lower_entity_id) or [] + except (AttributeError, KeyError, TypeError): + items = [] + for item in items: # filter out all None, NaN, "unknown" and "unavailable" states. # only keep real values with suppress(ValueError): if item.state not in ("unknown", "unavailable"): _temp_history.add_measurement( - convert_to_float( + convert_to_float_celsius( item.state, self.device_name, "check_ambient_air_temperature()", + unit_of_measurement=( + getattr(item, "attributes", {}).get( + "unit_of_measurement" + ) + or outdoor_state.attributes.get("unit_of_measurement") + ), ), datetime.fromtimestamp(item.last_updated.timestamp()), ) avg_temp = _temp_history.min + if avg_temp is None: + # No usable recorder history (e.g. a freshly created helper or a + # sensor the recorder does not retain). Fall back to the current + # reading so the outdoor threshold still applies instead of + # defaulting to "heat". + avg_temp = self.last_avg_outdoor_temp _LOGGER.debug("Initializing from database completed") else: avg_temp = self.last_avg_outdoor_temp _LOGGER.debug( - f"better_thermostat {self.device_name}: avg outdoor temp: {avg_temp}, threshold is {self.off_temperature}" + "better_thermostat %s: avg outdoor temp: %s, threshold is %s", + self.device_name, + avg_temp, + self.off_temperature, ) if avg_temp is not None: @@ -213,20 +332,29 @@ async def check_ambient_air_temperature(self): class DailyHistory: - """Stores one measurement per day for a maximum number of days. - At the moment only the maximum value per day is kept. + """Store one measurement per day for a maximum number of days. + + Compute an average outside temperature that better reflects the last days: + - Track all readings per day and compute the per-day mean + - Then compute the overall mean across the kept days + + Note: Attribute name `min` is kept for backward compatibility with callers, + but it now contains the multi-day mean (float) instead of a median of minima. """ def __init__(self, max_length): """Create new DailyHistory with a maximum length of the history.""" self.max_length = max_length - self._days = None - self._max_dict = {} + self._days = None # deque[date] + # Track per-day aggregate to compute means + self._sum_dict = {} + self._count_dict = {} + # Back-compat field: will store the resulting multi-day mean self.min = None def add_measurement(self, value, timestamp=None): - """Add a new measurement for a certain day.""" - day = (timestamp or datetime.now()).date() + """Add a new measurement for a certain day (value: float).""" + day = (timestamp or dt_util.now()).date() if not isinstance(value, (int, float)): return if self._days is None: @@ -235,22 +363,41 @@ class DailyHistory: else: current_day = self._days[-1] if day == current_day: - self._max_dict[day] = min(value, self._max_dict[day]) + # Accumulate for the same day + self._sum_dict[day] = self._sum_dict.get(day, 0.0) + float(value) + self._count_dict[day] = self._count_dict.get(day, 0) + 1 elif day > current_day: self._add_day(day, value) else: - _LOGGER.warning("Received old measurement, not storing it") + _LOGGER.debug( + "DailyHistory: received out-of-order measurement, skipping" + ) - self.min = median(self._max_dict.values()) + # Compute per-day means and then the overall mean across days + day_means = [] + if self._days: + for d in self._days: + cnt = self._count_dict.get(d, 0) + if cnt > 0: + day_means.append(self._sum_dict.get(d, 0.0) / float(cnt)) + if day_means: + self.min = sum(day_means) / float(len(day_means)) def _add_day(self, day, value): """Add a new day to the history. + Deletes the oldest day, if the queue becomes too long. """ + if self._days is None: + self._days = deque() if len(self._days) == self.max_length: oldest = self._days.popleft() - del self._max_dict[oldest] + # Clean up aggregates of the removed day + self._sum_dict.pop(oldest, None) + self._count_dict.pop(oldest, None) self._days.append(day) if not isinstance(value, (int, float)): return - self._max_dict[day] = value + # Initialize aggregates for the new day with the first value + self._sum_dict[day] = float(value) + self._count_dict[day] = 1 diff --git a/custom_components/gardena_smart_system/__init__.py b/custom_components/gardena_smart_system/__init__.py index 34a0d4f..2bd394b 100644 --- a/custom_components/gardena_smart_system/__init__.py +++ b/custom_components/gardena_smart_system/__init__.py @@ -1,109 +1,111 @@ """Support for Gardena Smart System devices.""" -import asyncio import logging -from gardena.exceptions.authentication_exception import AuthenticationException -from gardena.smart_system import SmartSystem, get_ssl_context from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ( - CONF_CLIENT_ID, - CONF_CLIENT_SECRET, - EVENT_HOMEASSISTANT_STOP, -) -from homeassistant.core import HomeAssistant -from oauthlib.oauth2.rfc6749.errors import ( - AccessDeniedError, - InvalidClientError, - MissingTokenError, -) +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant, ServiceCall +from homeassistant.helpers.device_registry import DeviceEntry -from .const import ( - DOMAIN, - GARDENA_LOCATION, - GARDENA_SYSTEM, -) +from .const import DOMAIN +from .coordinator import GardenaSmartSystemCoordinator +from .services import GardenaServiceManager _LOGGER = logging.getLogger(__name__) -PLATFORMS = ["lawn_mower", "sensor", "switch", "binary_sensor", "button"] +PLATFORMS = [ + Platform.LAWN_MOWER, + Platform.NUMBER, + Platform.SENSOR, + Platform.BINARY_SENSOR, + Platform.SWITCH, + Platform.VALVE, + Platform.BUTTON, +] -# Create SSL context outside of event loop -_SSL_CONTEXT = get_ssl_context() -async def async_setup(hass: HomeAssistant, config: dict): +async def async_setup(hass: HomeAssistant, config: dict) -> bool: """Set up the Gardena Smart System integration.""" + _LOGGER.info("Setting up Gardena Smart System integration") + + # Initialize service manager once per Home Assistant instance if DOMAIN not in hass.data: hass.data[DOMAIN] = {} + service_manager = GardenaServiceManager(hass) + hass.data[DOMAIN]["service_manager"] = service_manager + _LOGGER.info("Gardena Smart System services initialized") + + async def _async_reload_service(call: ServiceCall) -> None: + """Reload all config entries for this integration.""" + for entry in hass.config_entries.async_entries(DOMAIN): + await hass.config_entries.async_reload(entry.entry_id) + + hass.services.async_register(DOMAIN, "reload", _async_reload_service) + return True + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: - _LOGGER.debug("Setting up Gardena Smart System component") - - gardena_system = GardenaSmartSystem( - hass, - client_id=entry.data[CONF_CLIENT_ID], - client_secret=entry.data[CONF_CLIENT_SECRET], + """Set up Gardena Smart System from a config entry.""" + _LOGGER.info("Setting up Gardena Smart System component") + + # Create client + from .gardena_client import GardenaSmartSystemClient + + client = GardenaSmartSystemClient( + client_id=entry.data["client_id"], + client_secret=entry.data["client_secret"], + dev_mode=True, # Enable dev mode to bypass SSL issues on macOS ) - while True: - try: - await gardena_system.start() - break # If connection is successful, return True - except ConnectionError: - await asyncio.sleep(60) # Wait for 60 seconds before trying to reconnect - except AccessDeniedError as ex: - _LOGGER.error('Got Access Denied Error when setting up Gardena Smart System: %s', ex) - return False - except InvalidClientError as ex: - _LOGGER.error('Got Invalid Client Error when setting up Gardena Smart System: %s', ex) - return False - except MissingTokenError as ex: - _LOGGER.error('Got Missing Token Error when setting up Gardena Smart System: %s', ex) - return False - - hass.data[DOMAIN][GARDENA_SYSTEM] = gardena_system - - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, lambda event: hass.async_create_task(gardena_system.stop())) - + + # Create coordinator + coordinator = GardenaSmartSystemCoordinator( + hass, + client=client, + ) + + # Store coordinator in hass data + hass.data[DOMAIN][entry.entry_id] = coordinator + + # Start the coordinator + await coordinator.async_config_entry_first_refresh() + + # Forward the setup to the platforms await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) - - _LOGGER.debug("Gardena Smart System component setup finished") + + _LOGGER.info("Gardena Smart System component setup finished") return True -class GardenaSmartSystem: - """A Gardena Smart System wrapper class.""" +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + _LOGGER.info("Unloading Gardena Smart System component") - def __init__(self, hass, client_id, client_secret): - """Initialize the Gardena Smart System.""" - self._hass = hass - self.smart_system = SmartSystem( - client_id=client_id, - client_secret=client_secret, - ssl_context=_SSL_CONTEXT # Use the pre-created SSL context - ) + # Unload platforms + unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) - async def start(self): - try: - _LOGGER.debug("Starting GardenaSmartSystem") - await self.smart_system.authenticate() - await self.smart_system.update_locations() + if unload_ok: + # Remove coordinator from hass data + coordinator = hass.data[DOMAIN].pop(entry.entry_id) + await coordinator.async_shutdown() - if len(self.smart_system.locations) < 1: - _LOGGER.error("No locations found") - raise Exception("No locations found") + return unload_ok - # currently gardena supports only one location and gateway, so we can take the first - location = list(self.smart_system.locations.values())[0] - _LOGGER.debug(f"Using location: {location.name} ({location.id})") - await self.smart_system.update_devices(location) - self._hass.data[DOMAIN][GARDENA_LOCATION] = location - _LOGGER.debug("Starting GardenaSmartSystem websocket") - asyncio.create_task(self.smart_system.start_ws(self._hass.data[DOMAIN][GARDENA_LOCATION])) - _LOGGER.debug("Websocket thread launched !") - except AuthenticationException as ex: - _LOGGER.error( - f"Authentication failed : {ex.message}. You may need to check your token or create a new app in the gardena api and use the new token.") - async def stop(self): - _LOGGER.debug("Stopping GardenaSmartSystem") - await self.smart_system.quit() +async def async_remove_config_entry_device( + hass: HomeAssistant, + config_entry: ConfigEntry, + device_entry: DeviceEntry, +) -> bool: + """Allow removal of devices no longer reported by the Gardena API.""" + coordinator = hass.data[DOMAIN].get(config_entry.entry_id) + if not coordinator: + return True + + known_device_ids: set[str] = set() + for location in coordinator.locations.values(): + for device_id in location.devices: + known_device_ids.add(device_id) + + return not device_entry.identifiers.intersection( + (DOMAIN, device_id) for device_id in known_device_ids + ) diff --git a/custom_components/gardena_smart_system/binary_sensor.py b/custom_components/gardena_smart_system/binary_sensor.py index 48e9aa8..fafac79 100644 --- a/custom_components/gardena_smart_system/binary_sensor.py +++ b/custom_components/gardena_smart_system/binary_sensor.py @@ -1,61 +1,91 @@ -"""Support for Gardena Smart System websocket connection status.""" +"""Support for Gardena Smart System binary sensors.""" +from __future__ import annotations + +import logging +from typing import Any + from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, BinarySensorEntity, ) - -from custom_components.gardena_smart_system import GARDENA_SYSTEM +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DOMAIN +from .coordinator import GardenaSmartSystemCoordinator +from .entities import GardenaOnlineEntity, GardenaEntity + +_LOGGER = logging.getLogger(__name__) -async def async_setup_entry(hass, config_entry, async_add_entities): - """Perform the setup for Gardena websocket connection status.""" - async_add_entities( - [SmartSystemWebsocketStatus(hass.data[DOMAIN][GARDENA_SYSTEM].smart_system)], - True, - ) +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up Gardena Smart System binary sensors.""" + coordinator: GardenaSmartSystemCoordinator = hass.data[DOMAIN][entry.entry_id] + + entities = [] + + for location in coordinator.locations.values(): + for device in location.devices.values(): + entities.append(GardenaOnlineBinarySensor(coordinator, device)) + + entities.append(GardenaWebSocketConnectedSensor(coordinator, entry.entry_id)) + + async_add_entities(entities) -class SmartSystemWebsocketStatus(BinarySensorEntity): - """Representation of Gardena Smart System websocket connection status.""" +class GardenaOnlineBinarySensor(GardenaOnlineEntity, BinarySensorEntity): + """Representation of a Gardena device online status sensor.""" - def __init__(self, smart_system) -> None: - """Initialize the binary sensor.""" - super().__init__() - self._unique_id = "smart_gardena_websocket_status" - self._name = "Gardena Smart System connection" - self._smart_system = smart_system + def __init__(self, coordinator: GardenaSmartSystemCoordinator, device) -> None: + """Initialize the online status sensor.""" + super().__init__(coordinator, device) + self._attr_name = f"{device.name} Online" - async def async_added_to_hass(self): - """Subscribe to events.""" - self._smart_system.add_ws_status_callback(self.update_callback) + +class GardenaWebSocketConnectedSensor(GardenaEntity, BinarySensorEntity): + """Binary sensor indicating whether the Gardena WebSocket is connected.""" + + _attr_device_class = BinarySensorDeviceClass.CONNECTIVITY + + def __init__(self, coordinator: GardenaSmartSystemCoordinator, entry_id: str) -> None: + """Initialize the WebSocket connectivity sensor.""" + from .models import GardenaDevice + dummy_device = GardenaDevice( + id=f"websocket_status_{entry_id}", + name="WebSocket Status", + model_type="WebSocket Client", + serial="websocket", + services={}, + location_id="" + ) + super().__init__(coordinator, dummy_device, "WEBSOCKET") + self._attr_name = "Gardena WebSocket Connected" + self._attr_unique_id = f"gardena_websocket_connected_{entry_id}" @property - def name(self): - """Return the name of the device.""" - return self._name - - @property - def unique_id(self) -> str: - """Return a unique ID.""" - return self._unique_id + def available(self) -> bool: + """Return True if entity is available.""" + return True @property def is_on(self) -> bool: - """Return the status of the sensor.""" - return self._smart_system.is_ws_connected - - @property - def should_poll(self) -> bool: - """No polling needed for a sensor.""" + """Return True if WebSocket is connected.""" + if self.coordinator.websocket_client: + return self.coordinator.websocket_client.is_connected return False - def update_callback(self, status): - """Call update for Home Assistant when the device is updated.""" - self.schedule_update_ha_state(True) - @property - def device_class(self): - """Return the class of this device, from component DEVICE_CLASSES.""" - return BinarySensorDeviceClass.CONNECTIVITY + def extra_state_attributes(self) -> dict[str, Any]: + """Return entity specific state attributes.""" + attrs = super().extra_state_attributes + if self.coordinator.websocket_client: + attrs.update({ + "reconnect_attempts": self.coordinator.websocket_client.reconnect_attempts, + }) + return attrs + diff --git a/custom_components/gardena_smart_system/config_flow.py b/custom_components/gardena_smart_system/config_flow.py index 2a3d26a..9ed722c 100644 --- a/custom_components/gardena_smart_system/config_flow.py +++ b/custom_components/gardena_smart_system/config_flow.py @@ -1,122 +1,102 @@ -"""Config flow for Gardena integration.""" +"""Config flow for Gardena Smart System integration.""" +from __future__ import annotations + import logging -from collections import OrderedDict +from typing import Any -import homeassistant.helpers.config_validation as cv import voluptuous as vol -from gardena.smart_system import SmartSystem -from homeassistant import config_entries -from homeassistant.const import ( - CONF_CLIENT_ID, - CONF_CLIENT_SECRET, - CONF_ID, -) -from homeassistant.core import callback -from .const import ( - DOMAIN, - CONF_MOWER_DURATION, - CONF_SMART_IRRIGATION_DURATION, - CONF_SMART_WATERING_DURATION, - DEFAULT_MOWER_DURATION, - DEFAULT_SMART_IRRIGATION_DURATION, - DEFAULT_SMART_WATERING_DURATION, -) +from homeassistant import config_entries +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET +from homeassistant.data_entry_flow import FlowResult + +from .const import DOMAIN +from .credential_validation import async_validate_gardena_credentials _LOGGER = logging.getLogger(__name__) -DEFAULT_OPTIONS = { - CONF_MOWER_DURATION: DEFAULT_MOWER_DURATION, - CONF_SMART_IRRIGATION_DURATION: DEFAULT_SMART_IRRIGATION_DURATION, - CONF_SMART_WATERING_DURATION: DEFAULT_SMART_WATERING_DURATION, -} - -class GardenaSmartSystemConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): - """Handle a config flow for Gardena.""" +class GardenaSmartSystemConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Gardena Smart System.""" VERSION = 1 - CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_PUSH - async def _show_setup_form(self, errors=None): - """Show the setup form to the user.""" - errors = {} - - fields = OrderedDict() - fields[vol.Required(CONF_CLIENT_ID)] = str - fields[vol.Required(CONF_CLIENT_SECRET)] = str - - return self.async_show_form( - step_id="user", data_schema=vol.Schema(fields), errors=errors + def _credentials_schema(self) -> vol.Schema: + """Schema for API credential input.""" + return vol.Schema( + { + vol.Required(CONF_CLIENT_ID): str, + vol.Required(CONF_CLIENT_SECRET): str, + } ) - async def async_step_user(self, user_input=None): - """Handle the initial step.""" - if user_input is None: - return await self._show_setup_form() - - errors = {} - # try: - # await try_connection( - # user_input[CONF_CLIENT_ID], - # user_input[CONF_CLIENT_SECRET]) - # except Exception: # pylint: disable=broad-except - # _LOGGER.exception("Unexpected exception") - # errors["base"] = "unknown" - # return await self._show_setup_form(errors) - - unique_id = user_input[CONF_CLIENT_ID] - - await self.async_set_unique_id(unique_id) - self._abort_if_unique_id_configured() - - return self.async_create_entry( - title="", + async def _apply_credential_updates( + self, + entry: config_entries.ConfigEntry, + user_input: dict[str, Any], + ) -> FlowResult: + """Persist credentials; reload only when the config entry is enabled.""" + self.hass.config_entries.async_update_entry( + entry, data={ - CONF_ID: unique_id, CONF_CLIENT_ID: user_input[CONF_CLIENT_ID], - CONF_CLIENT_SECRET: user_input[CONF_CLIENT_SECRET] - }) + CONF_CLIENT_SECRET: user_input[CONF_CLIENT_SECRET], + }, + ) + if entry.disabled_by is None: + await self.hass.config_entries.async_reload(entry.entry_id) + else: + _LOGGER.info( + "Credentials updated for disabled config entry %s; " + "enable the integration to connect", + entry.entry_id, + ) + return self.async_abort(reason="reconfigure_successful") - @staticmethod - @callback - def async_get_options_flow(config_entry): - return GardenaSmartSystemOptionsFlowHandler(config_entry) + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle the initial step.""" + errors: dict[str, str] = {} - -class GardenaSmartSystemOptionsFlowHandler(config_entries.OptionsFlow): - async def async_step_init(self, user_input=None): - """Manage the options.""" - return await self.async_step_user() - - async def async_step_user(self, user_input=None): - """Handle a flow initialized by the user.""" - errors = {} if user_input is not None: - # TODO: Validate options (min, max values) - return self.async_create_entry(title="", data=user_input) + errors = await async_validate_gardena_credentials(user_input) + if not errors: + return self.async_create_entry( + title="Gardena Smart System", + data=user_input, + ) - fields = OrderedDict() - fields[vol.Optional( - CONF_MOWER_DURATION, - default=self.config_entry.options.get( - CONF_MOWER_DURATION, DEFAULT_MOWER_DURATION))] = cv.positive_int - fields[vol.Optional( - CONF_SMART_IRRIGATION_DURATION, - default=self.config_entry.options.get( - CONF_SMART_IRRIGATION_DURATION, DEFAULT_SMART_IRRIGATION_DURATION))] = cv.positive_int - fields[vol.Optional( - CONF_SMART_WATERING_DURATION, - default=self.config_entry.options.get( - CONF_SMART_WATERING_DURATION, DEFAULT_SMART_WATERING_DURATION))] = cv.positive_int + return self.async_show_form( + step_id="user", + data_schema=self._credentials_schema(), + errors=errors, + ) - return self.async_show_form(step_id="user", data_schema=vol.Schema(fields), errors=errors) + async def async_step_reconfigure( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Allow updating API credentials without removing the config entry.""" + reconfigure_entry = self._get_reconfigure_entry() + errors: dict[str, str] = {} + if user_input is not None: + errors = await async_validate_gardena_credentials(user_input) + if not errors: + return await self._apply_credential_updates( + reconfigure_entry, user_input + ) -async def try_connection(client_id, client_secret): - _LOGGER.debug("Trying to connect to Gardena during setup") - smart_system = SmartSystem(client_id=client_id, client_secret=client_secret) - await smart_system.authenticate() - await smart_system.update_locations() - await smart_system.quit() - _LOGGER.debug("Successfully connected to Gardena during setup") + return self.async_show_form( + step_id="reconfigure", + data_schema=self.add_suggested_values_to_schema( + self._credentials_schema(), + { + CONF_CLIENT_ID: reconfigure_entry.data.get(CONF_CLIENT_ID, ""), + CONF_CLIENT_SECRET: reconfigure_entry.data.get( + CONF_CLIENT_SECRET, "" + ), + }, + ), + errors=errors, + ) diff --git a/custom_components/gardena_smart_system/const.py b/custom_components/gardena_smart_system/const.py index ec2b232..719f0ff 100644 --- a/custom_components/gardena_smart_system/const.py +++ b/custom_components/gardena_smart_system/const.py @@ -1,24 +1,131 @@ -DOMAIN = "gardena_smart_system" -GARDENA_SYSTEM = "gardena_system" -GARDENA_LOCATION = "gardena_location" +"""Constants for the Gardena Smart System integration.""" +from __future__ import annotations -CONF_MOWER_DURATION = "mower_duration" -CONF_SMART_IRRIGATION_DURATION = "smart_irrigation_control_duration" -CONF_SMART_WATERING_DURATION = "smart_watering_duration" +from typing import Final -DEFAULT_MOWER_DURATION = 60 -DEFAULT_SMART_IRRIGATION_DURATION = 30 -DEFAULT_SMART_WATERING_DURATION = 30 +from homeassistant.components.lawn_mower import LawnMowerActivity -ATTR_NAME = "name" -ATTR_ACTIVITY = "activity" -ATTR_BATTERY_STATE = "battery_state" -ATTR_RF_LINK_LEVEL = "rf_link_level" -ATTR_RF_LINK_STATE = "rf_link_state" -ATTR_SERIAL = "serial" -ATTR_OPERATING_HOURS = "operating_hours" -ATTR_LAST_ERROR = "last_error" -ATTR_ERROR = "error" -ATTR_STATE = "state" -ATTR_STINT_START = "stint_start" -ATTR_STINT_END = "stint_end" +# Domain +DOMAIN: Final = "gardena_smart_system" + +# Configuration keys +CONF_CLIENT_ID: Final = "client_id" +CONF_CLIENT_SECRET: Final = "client_secret" +# API constants +API_BASE_URL: Final = "https://api.smart.gardena.dev/v2" +API_TIMEOUT: Final = 30 + +# Device types +DEVICE_TYPE_MOWER: Final = "MOWER" +DEVICE_TYPE_VALVE: Final = "VALVE" +DEVICE_TYPE_POWER_SOCKET: Final = "POWER_SOCKET" +DEVICE_TYPE_SENSOR: Final = "SENSOR" + +# Service types +SERVICE_TYPE_COMMON: Final = "COMMON" +SERVICE_TYPE_MOWER: Final = "MOWER" +SERVICE_TYPE_VALVE: Final = "VALVE" +SERVICE_TYPE_POWER_SOCKET: Final = "POWER_SOCKET" +SERVICE_TYPE_SENSOR: Final = "SENSOR" + +# Mower states +MOWER_STATE_OK: Final = "OK" +MOWER_STATE_WARNING: Final = "WARNING" +MOWER_STATE_ERROR: Final = "ERROR" +MOWER_STATE_UNAVAILABLE: Final = "UNAVAILABLE" + +# Gardena service states that represent an actual error condition. The lawn +# mower entity only reports LawnMowerActivity.ERROR when the service state is +# one of these — otherwise an unmapped/NONE activity (e.g. the mower stopped in +# the garden out of battery) falls back to PAUSED instead of contradicting the +# mower_error sensor, which reports "no error". See #375. +MOWER_ERROR_STATES: Final = frozenset({MOWER_STATE_ERROR, MOWER_STATE_WARNING}) + +# Mower activities +MOWER_ACTIVITY_PAUSED: Final = "PAUSED" +MOWER_ACTIVITY_PAUSED_IN_CS: Final = "PAUSED_IN_CS" +MOWER_ACTIVITY_CUTTING: Final = "OK_CUTTING" +MOWER_ACTIVITY_CUTTING_TIMER_OVERRIDDEN: Final = "OK_CUTTING_TIMER_OVERRIDDEN" +MOWER_ACTIVITY_SEARCHING: Final = "OK_SEARCHING" +MOWER_ACTIVITY_LEAVING: Final = "OK_LEAVING" +MOWER_ACTIVITY_CHARGING: Final = "OK_CHARGING" +MOWER_ACTIVITY_PARKED: Final = "PARKED_TIMER" +MOWER_ACTIVITY_PARKED_TIMER: Final = "PARKED_TIMER" +MOWER_ACTIVITY_PARKED_PARK_SELECTED: Final = "PARKED_PARK_SELECTED" +MOWER_ACTIVITY_PARKED_AUTOTIMER: Final = "PARKED_AUTOTIMER" +MOWER_ACTIVITY_PARKED_FROST: Final = "PARKED_FROST" +MOWER_ACTIVITY_PARKED_NO_LIGHT: Final = "PARKED_NO_LIGHT" +MOWER_ACTIVITY_PARKED_MOWING_COMPLETED: Final = "PARKED_MOWING_COMPLETED" +MOWER_ACTIVITY_PARKED_RAIN: Final = "PARKED_RAIN" +MOWER_ACTIVITY_PARKED_DAILY_LIMIT_REACHED: Final = "PARKED_DAILY_LIMIT_REACHED" +MOWER_ACTIVITY_STOPPED_IN_GARDEN: Final = "STOPPED_IN_GARDEN" +MOWER_ACTIVITY_INITIATE_NEXT_ACTION: Final = "INITIATE_NEXT_ACTION" +MOWER_ACTIVITY_SEARCHING_FOR_SATELLITES: Final = "SEARCHING_FOR_SATELLITES" +MOWER_ACTIVITY_NONE: Final = "NONE" + +# Mower activity mapping to Home Assistant +MOWER_ACTIVITY_MAP: Final = { + MOWER_ACTIVITY_PAUSED: LawnMowerActivity.PAUSED, + MOWER_ACTIVITY_PAUSED_IN_CS: LawnMowerActivity.PAUSED, + MOWER_ACTIVITY_CUTTING: LawnMowerActivity.MOWING, + MOWER_ACTIVITY_CUTTING_TIMER_OVERRIDDEN: LawnMowerActivity.MOWING, + MOWER_ACTIVITY_SEARCHING: LawnMowerActivity.RETURNING, + MOWER_ACTIVITY_LEAVING: LawnMowerActivity.MOWING, + MOWER_ACTIVITY_CHARGING: LawnMowerActivity.DOCKED, + MOWER_ACTIVITY_PARKED: LawnMowerActivity.DOCKED, + MOWER_ACTIVITY_PARKED_TIMER: LawnMowerActivity.DOCKED, + MOWER_ACTIVITY_PARKED_PARK_SELECTED: LawnMowerActivity.DOCKED, + MOWER_ACTIVITY_PARKED_AUTOTIMER: LawnMowerActivity.DOCKED, + MOWER_ACTIVITY_PARKED_FROST: LawnMowerActivity.DOCKED, + MOWER_ACTIVITY_PARKED_NO_LIGHT: LawnMowerActivity.DOCKED, + MOWER_ACTIVITY_PARKED_MOWING_COMPLETED: LawnMowerActivity.DOCKED, + MOWER_ACTIVITY_PARKED_RAIN: LawnMowerActivity.DOCKED, + MOWER_ACTIVITY_PARKED_DAILY_LIMIT_REACHED: LawnMowerActivity.DOCKED, + MOWER_ACTIVITY_STOPPED_IN_GARDEN: LawnMowerActivity.DOCKED, + MOWER_ACTIVITY_INITIATE_NEXT_ACTION: LawnMowerActivity.MOWING, + MOWER_ACTIVITY_SEARCHING_FOR_SATELLITES: LawnMowerActivity.DOCKED, + # NONE is intentionally not mapped: the entity decides between ERROR and + # PAUSED based on the Gardena service state instead of assuming an error. + # See GardenaLawnMower.activity and MOWER_ERROR_STATES (#375). +} + +# Mower informational codes — operational states that are NOT errors. +# The mower_error sensor returns "no_message" when last_error_code is in this set +# so that user automations trigger only on real actionable errors. +MOWER_INFORMATIONAL_CODES: Final = frozenset({ + "no_message", # Already "no error" + "uninitialised", # Normal boot state + "parked_daily_limit_reached", # Daily schedule limit reached — normal operation + "outside_working_area", # Mower returned to base outside its zone — normal + "off_disabled", # Disabled manually by user + "off_hatch_open", # Hatch open for maintenance + "off_hatch_closed", # Hatch closed — normal state + "wait_updating", # Firmware update in progress + "wait_power_up", # Booting up + "wait_stop_pressed", # Stop button held — user-initiated maintenance + "wait_for_safety_pin", # Waiting for safety pin — user-initiated maintenance + "guide_calibration_accomplished", # Calibration completed successfully + "connection_changed", # Network state change — informational + "connection_not_changed", # Network state change — informational +}) + +# WebSocket configuration +WEBSOCKET_RECONNECT_DELAY: Final = 5 # seconds +# Number of fast (exponential-backoff) reconnection attempts before falling back +# to a slow, quota-friendly retry cadence. The client never permanently gives up: +# after this many attempts it keeps retrying at WEBSOCKET_SLOW_RECONNECT_INTERVAL +# so stale data eventually recovers without a Home Assistant restart (#378). +WEBSOCKET_MAX_RECONNECT_ATTEMPTS: Final = 10 +# Slow reconnection cadence (seconds) used once the fast attempts are exhausted. +# One attempt/hour is ~168 POST /v2/websocket requests/week, well within the +# 700 requests/week Gardena API quota. +WEBSOCKET_SLOW_RECONNECT_INTERVAL: Final = 3600 + +# Valve duration configuration +CONF_VALVE_DURATIONS: Final = "valve_durations" +DEFAULT_VALVE_DURATION_SECONDS: Final = 3600 + +# Attribute names +ATTR_BATTERY_STATE: Final = "battery_state" +ATTR_RF_LINK_LEVEL: Final = "rf_link_level" +ATTR_RF_LINK_STATE: Final = "rf_link_state" \ No newline at end of file diff --git a/custom_components/gardena_smart_system/manifest.json b/custom_components/gardena_smart_system/manifest.json index cb9f61e..b288637 100644 --- a/custom_components/gardena_smart_system/manifest.json +++ b/custom_components/gardena_smart_system/manifest.json @@ -1,11 +1,13 @@ { "domain": "gardena_smart_system", "name": "Gardena Smart System integration", - "version": "1.2.1", + "version": "3.1.3", "config_flow": true, "documentation": "https://github.com/py-smart-gardena/hass-gardena-smart-system", "issue_tracker": "https://github.com/py-smart-gardena/hass-gardena-smart-system/issues", "dependencies": [], "codeowners": ["@py-smart-gardena"], - "requirements": ["py-smart-gardena==1.3.12", "oauthlib==3.2.2"] + "requirements": ["aiohttp>=3.7.0", "websockets>=10.0"], + "iot_class": "cloud_polling", + "integration_type": "device" } diff --git a/custom_components/gardena_smart_system/sensor.py b/custom_components/gardena_smart_system/sensor.py index c306de5..3d2f9a1 100644 --- a/custom_components/gardena_smart_system/sensor.py +++ b/custom_components/gardena_smart_system/sensor.py @@ -1,147 +1,540 @@ """Support for Gardena Smart System sensors.""" +from __future__ import annotations + import logging +from datetime import datetime, timedelta, timezone +from typing import Any -from homeassistant.components.sensor import SensorDeviceClass, UnitOfTemperature - -from homeassistant.helpers.entity import Entity -from homeassistant.const import ( - ATTR_BATTERY_LEVEL, - PERCENTAGE, -) +from homeassistant.components.sensor import SensorEntity, SensorDeviceClass, SensorStateClass +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import PERCENTAGE, UnitOfTemperature, UnitOfTime +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import DeviceInfo, EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ( DOMAIN, ATTR_BATTERY_STATE, ATTR_RF_LINK_LEVEL, ATTR_RF_LINK_STATE, - GARDENA_LOCATION, + MOWER_INFORMATIONAL_CODES, ) - +from .coordinator import GardenaSmartSystemCoordinator +from .entities import GardenaEntity _LOGGER = logging.getLogger(__name__) -SOIL_SENSOR_TYPES = { - "soil_temperature": [ - UnitOfTemperature.CELSIUS, - "mdi:thermometer", - SensorDeviceClass.TEMPERATURE, - ], - "soil_humidity": ["%", "mdi:water-percent", SensorDeviceClass.HUMIDITY], - ATTR_BATTERY_LEVEL: [PERCENTAGE, "mdi:battery", SensorDeviceClass.BATTERY], -} -SENSOR_TYPES = { - **{ - "ambient_temperature": [ - UnitOfTemperature.CELSIUS, - "mdi:thermometer", - SensorDeviceClass.TEMPERATURE, - ], - "light_intensity": ["lx", None, SensorDeviceClass.ILLUMINANCE], - }, - **SOIL_SENSOR_TYPES, -} +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up Gardena Smart System sensors.""" + coordinator: GardenaSmartSystemCoordinator = hass.data[DOMAIN][entry.entry_id] - -async def async_setup_entry(hass, config_entry, async_add_entities): - """Perform the setup for Gardena sensor devices.""" + # Create sensor entities for each device entities = [] - for sensor in hass.data[DOMAIN][GARDENA_LOCATION].find_device_by_type("SENSOR"): - for sensor_type in SENSOR_TYPES: - entities.append(GardenaSensor(sensor, sensor_type)) + + for location in coordinator.locations.values(): + for device in location.devices.values(): + _LOGGER.debug(f"Checking device {device.name} ({device.id}) - Services: {list(device.services.keys())}") + + # Add battery and RF link sensors for devices that have COMMON service + if "COMMON" in device.services: + common_services = device.services["COMMON"] + _LOGGER.debug(f"Found {len(common_services)} common services for device: {device.name} ({device.id})") + for common_service in common_services: + # Only create battery sensor if device has a battery. + # Include when battery_level is present even if battery_state is not yet + # populated (soil sensors can return null battery_state at initial load). + has_battery = ( + common_service.battery_state not in [None, "NO_BATTERY"] + or common_service.battery_level is not None + ) + if has_battery: + _LOGGER.debug(f"Creating battery sensor for device with battery: {device.name} (battery_state: {common_service.battery_state})") + entities.append(GardenaBatterySensor(coordinator, device, common_service)) + else: + _LOGGER.debug(f"Skipping battery sensor for device without battery: {device.name} (battery_state: {common_service.battery_state})") - for sensor in hass.data[DOMAIN][GARDENA_LOCATION].find_device_by_type( - "SOIL_SENSOR" - ): - for sensor_type in SOIL_SENSOR_TYPES: - entities.append(GardenaSensor(sensor, sensor_type)) + # RF Link Quality sensor + if common_service.rf_link_level is not None: + entities.append(GardenaRFLinkLevelSensor(coordinator, device, common_service)) + + # Add mower sensors + if "MOWER" in device.services: + mower_services = device.services["MOWER"] + for mower_service in mower_services: + entities.append(GardenaMowerErrorSensor(coordinator, device, mower_service)) + if mower_service.operating_hours is not None: + entities.append(GardenaMowerOperatingHoursSensor(coordinator, device, mower_service)) - for mower in hass.data[DOMAIN][GARDENA_LOCATION].find_device_by_type("MOWER"): - # Add battery sensor for mower - entities.append(GardenaSensor(mower, ATTR_BATTERY_LEVEL)) + # Add watering end time sensors for valves + if "VALVE" in device.services: + valve_services = device.services["VALVE"] + for valve_service in valve_services: + entities.append(GardenaValveRemainingTimeSensor(coordinator, device, valve_service)) - for water_control in hass.data[DOMAIN][GARDENA_LOCATION].find_device_by_type( - "WATER_CONTROL" - ): - # Add battery sensor for water control - entities.append(GardenaSensor(water_control, ATTR_BATTERY_LEVEL)) - _LOGGER.debug("Adding sensor as sensor %s", entities) - async_add_entities(entities, True) + # Add sensor entities if available + if "SENSOR" in device.services: + sensor_services = device.services["SENSOR"] + _LOGGER.debug(f"Found {len(sensor_services)} sensor services for device: {device.name} ({device.id})") + for sensor_service in sensor_services: + _LOGGER.debug(f"Creating sensor entities for service: {sensor_service.id}") + + # Check if this is a soil sensor (has soil_humidity or soil_temperature) + is_soil_sensor = (sensor_service.soil_humidity is not None or + sensor_service.soil_temperature is not None) + + # Create temperature sensors + if sensor_service.soil_temperature is not None: + entities.append(GardenaTemperatureSensor(coordinator, device, sensor_service, "soil_temperature", is_soil_sensor)) + if sensor_service.ambient_temperature is not None: + entities.append(GardenaTemperatureSensor(coordinator, device, sensor_service, "ambient_temperature", is_soil_sensor)) + + # Create humidity sensor (only for soil sensors) + if sensor_service.soil_humidity is not None: + entities.append(GardenaHumiditySensor(coordinator, device, sensor_service)) + + # Create light sensor + if sensor_service.light_intensity is not None: + entities.append(GardenaLightSensor(coordinator, device, sensor_service)) + + # Add API usage diagnostic sensor (one per config entry) + entities.append(GardenaAPIUsageSensor(coordinator, entry.entry_id)) + + _LOGGER.debug(f"Created {len(entities)} sensor entities") + async_add_entities(entities) -class GardenaSensor(Entity): - """Representation of a Gardena Sensor.""" +class GardenaBatterySensor(GardenaEntity, SensorEntity): + """Representation of a Gardena battery sensor.""" - def __init__(self, device, sensor_type): - """Initialize the Gardena Sensor.""" - self._sensor_type = sensor_type - self._name = f"{device.name} {sensor_type.replace('_', ' ')}" - self._unique_id = f"{device.serial}-{sensor_type}" - self._device = device + _attr_state_class = SensorStateClass.MEASUREMENT - async def async_added_to_hass(self): - """Subscribe to sensor events.""" - self._device.add_callback(self.update_callback) + def __init__(self, coordinator: GardenaSmartSystemCoordinator, device, common_service) -> None: + """Initialize the battery sensor.""" + super().__init__(coordinator, device, "COMMON") + self._common_service = common_service + self._device_id = device.id + self._attr_name = f"{device.name} Battery Level" + self._attr_unique_id = f"{device.id}_{common_service.id}_battery_level" + self._attr_native_unit_of_measurement = PERCENTAGE + self._attr_device_class = SensorDeviceClass.BATTERY + self._attr_icon = "mdi:battery" - @property - def should_poll(self) -> bool: - """No polling needed for a sensor.""" - return False - - def update_callback(self, device): - """Call update for Home Assistant when the device is updated.""" - self.schedule_update_ha_state(True) - - @property - def name(self): - """Return the name of the device.""" - return self._name - - @property - def unique_id(self) -> str: - """Return a unique ID.""" - return self._unique_id - - @property - def icon(self): - """Return the icon to use in the frontend.""" - return SENSOR_TYPES[self._sensor_type][1] - - @property - def unit_of_measurement(self): - """Return the unit of measurement of this entity, if any.""" - return SENSOR_TYPES[self._sensor_type][0] - - @property - def device_class(self): - """Return the device class of this entity.""" - if self._sensor_type in SENSOR_TYPES: - return SENSOR_TYPES[self._sensor_type][2] + def _get_current_common_service(self): + """Get current common service from coordinator (fresh data).""" + device = self.coordinator.get_device_by_id(self._device_id) + if device and "COMMON" in device.services: + for service in device.services["COMMON"]: + if service.id == self._common_service.id: + return service return None @property - def state(self): - """Return the state of the sensor.""" - return getattr(self._device, self._sensor_type) + def native_value(self) -> int | None: + """Return the battery level.""" + current_service = self._get_current_common_service() + return current_service.battery_level if current_service else None @property - def extra_state_attributes(self): - """Return the state attributes of the sensor.""" - return { - ATTR_BATTERY_LEVEL: self._device.battery_level, - ATTR_BATTERY_STATE: self._device.battery_state, - ATTR_RF_LINK_LEVEL: self._device.rf_link_level, - ATTR_RF_LINK_STATE: self._device.rf_link_state, - } + def extra_state_attributes(self) -> dict[str, Any]: + """Return entity specific state attributes.""" + attrs = super().extra_state_attributes + current_service = self._get_current_common_service() + if current_service: + attrs.update({ + ATTR_BATTERY_STATE: current_service.battery_state, + ATTR_RF_LINK_LEVEL: current_service.rf_link_level, + ATTR_RF_LINK_STATE: current_service.rf_link_state, + }) + return attrs + + +class GardenaMowerErrorSensor(GardenaEntity, SensorEntity): + """Representation of a Gardena mower error code sensor.""" + + _attr_translation_key = "mower_error" + _attr_has_entity_name = True + _attr_device_class = SensorDeviceClass.ENUM + + def __init__(self, coordinator: GardenaSmartSystemCoordinator, device, mower_service) -> None: + """Initialize the mower error sensor.""" + super().__init__(coordinator, device, "MOWER") + self._mower_service = mower_service + self._device_id = device.id + self._attr_name = None + self._attr_unique_id = f"{device.id}_{mower_service.id}_last_error_code" + self._attr_icon = "mdi:alert-circle-outline" + self._attr_options = [ + "uninitialised", "no_message", "outside_working_area", "no_loop_signal", + "no_charging_station_signal", "wrong_loop_signal", + "loop_sensor_problem_front", "loop_sensor_problem_rear", + "trapped", "upside_down", "low_battery", "empty_battery", "no_drive", + "lifted", "stuck_in_charging_station", "charging_station_blocked", + "collision_sensor_problem_rear", "collision_sensor_problem_front", + "wheel_motor_blocked_right", "wheel_motor_blocked_left", + "wheel_drive_problem_right", "wheel_drive_problem_left", + "cutting_system_blocked", "invalid_sub_device_combination", + "settings_restored", "charging_system_problem", "tilt_sensor_problem", + "mower_tilted", "wheel_motor_overloaded_right", + "wheel_motor_overloaded_left", "charging_current_too_high", + "electronic_problem", "cutting_height_blocked", "cutting_height_problem", + "temporary_problem", "guide_1_not_found", "guide_2_not_found", + "guide_3_not_found", "gps_tracker_module_error", "weak_gps_signal", + "guide_calibration_failed", "temporary_battery_problem", + "battery_problem", "alarm_mower_switched_off", "alarm_mower_stopped", + "alarm_mower_lifted", "alarm_mower_tilted", "com_board_not_available", + "slipped", "invalid_battery_combination", "safety_function_faulty", + "invalid_system_conf", "lift_sensor_defect", "mobile_loop_defect", + "left_loop_sensor", "right_loop_sensor", "wrong_pin", "temporary_lift", + "cutting_drive", "steep_slope", "stop_button_fail", + "angle_cutting_means_off", "slave_mcu_lost", "cutting_overload", + "cutting_height_range", "cutting_height_drift", "cutting_height_limited", + "cutting_height_drive", "cutting_height_current", + "cutting_height_direction", "mower_to_cs_com", "ultrasonic_error", + "high_low_bat_temp_a", "high_low_bat_temp_b", + "too_low_voltage_bat_a", "too_low_voltage_bat_b", + "alarm_motion", "alarm_geofence", + "rr_wheel_blocked", "rl_wheel_blocked", + "rr_wheel_drive", "rl_wheel_drive", + "rear_right_wheel_overloaded", "rear_left_wheel_overloaded", + "angular_sensor_defect", "no_power_in_cs", "switch_cord_sensor_defect", + "map_not_valid", "no_position", "no_rs_communication", + "folding_sensor_activated", + "ultrasonic_sensor_1_defect", "ultrasonic_sensor_2_defect", + "ultrasonic_sensor_3_defect", "ultrasonic_sensor_4_defect", + "cutting_drive_motor_1_defect", "cutting_drive_motor_2_defect", + "cutting_drive_motor_3_defect", + "collision_sensor_defect", "docking_sensor_defect", + "folding_cutting_deck_sensor_defect", "loop_sensor_defect", + "collision_sensor_error", "no_confirmed_position", + "major_cutting_disk_imbalance", "complex_working_area", + "invalid_sw_configuration", "radar_error", "work_area_tampered", + "destination_not_reachable", "wait_stop_pressed", "wait_for_safety_pin", + "destination_not_reachable_warning", "battery_near_end_of_life", + "edgemotor_blocked", "no_correction_data", "invalid_correction_data", + "wait_updating", "wait_power_up", "off_disabled", "off_hatch_open", + "off_hatch_closed", "parked_daily_limit_reached", + "vision_system_malfunction", "poor_vision_system_performance", + "vision_processing_failed", + "loop_sensor_problem_left", "loop_sensor_problem_right", + "wrong_pin_code", "lost_wheel_brush", "accessory_power_anomaly", + "loop_wire_broken", "battery_fet_error", "imbalanced_cutting_disc", + "cutting_motor_problem", "limited_cutting_height_range", + "cutting_motor_drive_defect", "memory_circuit_problem", + "stop_button_problem", "difficult_finding_home", + "guide_calibration_accomplished", "too_many_batteries", + "alarm_mower_in_motion", "alarm_outside_geofence", + "connection_changed", "connection_not_changed", + "unknown", + ] + + def _get_current_mower_service(self): + """Get current mower service from coordinator (fresh data).""" + device = self.coordinator.get_device_by_id(self._device_id) + if device and "MOWER" in device.services: + for service in device.services["MOWER"]: + if service.id == self._mower_service.id: + return service + return None @property - def device_info(self): - return { - "identifiers": { - # Serial numbers are unique identifiers within a specific domain - (DOMAIN, self._device.serial) - }, - "name": self._device.name, - "manufacturer": "Gardena", - "model": self._device.model_type, + def native_value(self) -> str | None: + """Return the last error code, or 'no_message' for informational states.""" + current_service = self._get_current_mower_service() + if not current_service: + return None + error_code = current_service.last_error_code + if error_code: + code = error_code.lower() + if code in MOWER_INFORMATIONAL_CODES: + return "no_message" + return code + return "no_message" + + @property + def icon(self) -> str: + """Return icon based on error state.""" + value = self.native_value + if value and value != "no_message": + return "mdi:alert-circle" + return "mdi:check-circle-outline" + + +class GardenaMowerOperatingHoursSensor(GardenaEntity, SensorEntity): + """Representation of a Gardena mower operating hours sensor.""" + + _attr_state_class = SensorStateClass.TOTAL_INCREASING + _attr_device_class = SensorDeviceClass.DURATION + + def __init__(self, coordinator: GardenaSmartSystemCoordinator, device, mower_service) -> None: + """Initialize the operating hours sensor.""" + super().__init__(coordinator, device, "MOWER") + self._mower_service = mower_service + self._device_id = device.id + self._attr_name = f"{device.name} Operating Hours" + self._attr_unique_id = f"{device.id}_{mower_service.id}_operating_hours" + self._attr_native_unit_of_measurement = UnitOfTime.HOURS + self._attr_icon = "mdi:clock-outline" + + def _get_current_mower_service(self): + """Get current mower service from coordinator (fresh data).""" + device = self.coordinator.get_device_by_id(self._device_id) + if device and "MOWER" in device.services: + for service in device.services["MOWER"]: + if service.id == self._mower_service.id: + return service + return None + + @property + def native_value(self) -> int | None: + """Return the operating hours.""" + current_service = self._get_current_mower_service() + return current_service.operating_hours if current_service else None + + +class GardenaRFLinkLevelSensor(GardenaEntity, SensorEntity): + """Representation of a Gardena RF link quality sensor.""" + + _attr_state_class = SensorStateClass.MEASUREMENT + + def __init__(self, coordinator: GardenaSmartSystemCoordinator, device, common_service) -> None: + """Initialize the RF link level sensor.""" + super().__init__(coordinator, device, "COMMON") + self._common_service = common_service + self._device_id = device.id + self._attr_name = f"{device.name} RF Link Quality" + self._attr_unique_id = f"{device.id}_{common_service.id}_rf_link_level" + self._attr_native_unit_of_measurement = PERCENTAGE + self._attr_icon = "mdi:signal" + + def _get_current_common_service(self): + """Get current common service from coordinator (fresh data).""" + device = self.coordinator.get_device_by_id(self._device_id) + if device and "COMMON" in device.services: + for service in device.services["COMMON"]: + if service.id == self._common_service.id: + return service + return None + + @property + def native_value(self) -> int | None: + """Return the RF link level.""" + current_service = self._get_current_common_service() + return current_service.rf_link_level if current_service else None + + @property + def extra_state_attributes(self) -> dict[str, Any]: + """Return entity specific state attributes.""" + attrs = super().extra_state_attributes + current_service = self._get_current_common_service() + if current_service: + attrs[ATTR_RF_LINK_STATE] = current_service.rf_link_state + return attrs + + +class GardenaTemperatureSensor(GardenaEntity, SensorEntity): + """Representation of a Gardena temperature sensor.""" + + _attr_state_class = SensorStateClass.MEASUREMENT + + def __init__(self, coordinator: GardenaSmartSystemCoordinator, device, sensor_service, temp_attr: str, is_soil_sensor: bool = False) -> None: + """Initialize the temperature sensor.""" + super().__init__(coordinator, device, "SENSOR") + self._sensor_service = sensor_service + self._device_id = device.id + self._temp_attr = temp_attr + + if temp_attr == "soil_temperature": + self._attr_name = f"{device.name} Soil Temperature" + self._attr_unique_id = f"{device.id}_{sensor_service.id}_soil_temperature" + else: + self._attr_name = f"{device.name} Ambient Temperature" + self._attr_unique_id = f"{device.id}_{sensor_service.id}_ambient_temperature" + + # Add soil sensor indicator to name if it's a soil sensor + if is_soil_sensor and temp_attr == "soil_temperature": + self._attr_name = f"{device.name} Soil Temperature (Soil Sensor)" + + self._attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS + self._attr_device_class = SensorDeviceClass.TEMPERATURE + self._attr_icon = "mdi:thermometer" + + def _get_current_sensor_service(self): + """Get current sensor service from coordinator (fresh data).""" + device = self.coordinator.get_device_by_id(self._device_id) + if device and "SENSOR" in device.services: + for service in device.services["SENSOR"]: + if service.id == self._sensor_service.id: + return service + return None + + @property + def native_value(self) -> int | None: + """Return the temperature value.""" + current_service = self._get_current_sensor_service() + if not current_service: + return None + + if self._temp_attr == "soil_temperature": + return current_service.soil_temperature + else: + return current_service.ambient_temperature + + +class GardenaHumiditySensor(GardenaEntity, SensorEntity): + """Representation of a Gardena humidity sensor.""" + + _attr_state_class = SensorStateClass.MEASUREMENT + + def __init__(self, coordinator: GardenaSmartSystemCoordinator, device, sensor_service) -> None: + """Initialize the humidity sensor.""" + super().__init__(coordinator, device, "SENSOR") + self._sensor_service = sensor_service + self._device_id = device.id + self._attr_name = f"{device.name} Soil Humidity" + self._attr_unique_id = f"{device.id}_{sensor_service.id}_soil_humidity" + self._attr_native_unit_of_measurement = PERCENTAGE + self._attr_device_class = SensorDeviceClass.MOISTURE + self._attr_icon = "mdi:water-percent" + + def _get_current_sensor_service(self): + """Get current sensor service from coordinator (fresh data).""" + device = self.coordinator.get_device_by_id(self._device_id) + if device and "SENSOR" in device.services: + for service in device.services["SENSOR"]: + if service.id == self._sensor_service.id: + return service + return None + + @property + def native_value(self) -> int | None: + """Return the humidity value.""" + current_service = self._get_current_sensor_service() + return current_service.soil_humidity if current_service else None + + +class GardenaLightSensor(GardenaEntity, SensorEntity): + """Representation of a Gardena light sensor.""" + + _attr_state_class = SensorStateClass.MEASUREMENT + + def __init__(self, coordinator: GardenaSmartSystemCoordinator, device, sensor_service) -> None: + """Initialize the light sensor.""" + super().__init__(coordinator, device, "SENSOR") + self._sensor_service = sensor_service + self._device_id = device.id + self._attr_name = f"{device.name} Light Intensity" + self._attr_unique_id = f"{device.id}_{sensor_service.id}_light_intensity" + self._attr_native_unit_of_measurement = "lx" + self._attr_device_class = SensorDeviceClass.ILLUMINANCE + self._attr_icon = "mdi:white-balance-sunny" + + def _get_current_sensor_service(self): + """Get current sensor service from coordinator (fresh data).""" + device = self.coordinator.get_device_by_id(self._device_id) + if device and "SENSOR" in device.services: + for service in device.services["SENSOR"]: + if service.id == self._sensor_service.id: + return service + return None + + @property + def native_value(self) -> int | None: + """Return the light intensity value.""" + current_service = self._get_current_sensor_service() + return current_service.light_intensity if current_service else None + + +class GardenaValveRemainingTimeSensor(GardenaEntity, SensorEntity): + """Sensor that shows the watering end time for a valve.""" + + _attr_device_class = SensorDeviceClass.TIMESTAMP + + def __init__(self, coordinator: GardenaSmartSystemCoordinator, device, valve_service) -> None: + """Initialize the valve remaining time sensor.""" + super().__init__(coordinator, device, "VALVE") + self._valve_service = valve_service + self._device_id = device.id + valve_name = valve_service.name or device.name + self._attr_name = f"{valve_name} Watering End" + self._attr_unique_id = f"{device.id}_{valve_service.id}_watering_end" + self._attr_icon = "mdi:timer-sand" + + def _get_current_valve_service(self): + """Get current valve service from coordinator (fresh data).""" + device = self.coordinator.get_device_by_id(self._device_id) + if device and "VALVE" in device.services: + for service in device.services["VALVE"]: + if service.id == self._valve_service.id: + return service + return None + + @property + def native_value(self) -> datetime | None: + """Return the watering end timestamp.""" + current_service = self._get_current_valve_service() + if not current_service: + return None + + if current_service.activity in ("MANUAL_WATERING", "SCHEDULED_WATERING"): + if current_service.duration and current_service.duration_timestamp: + try: + start = datetime.fromisoformat( + current_service.duration_timestamp.replace("Z", "+00:00") + ) + return start + timedelta(seconds=current_service.duration) + except (ValueError, TypeError): + return None + return None + + +class GardenaAPIUsageSensor(SensorEntity): + """Sensor tracking Gardena API usage against the 700 requests/week quota.""" + + _attr_has_entity_name = True + _attr_translation_key = "api_requests_week" + _attr_icon = "mdi:api" + _attr_state_class = SensorStateClass.TOTAL + _attr_entity_category = EntityCategory.DIAGNOSTIC + + def __init__(self, coordinator: GardenaSmartSystemCoordinator, entry_id: str) -> None: + """Initialize the API usage sensor.""" + self.coordinator = coordinator + self._entry_id = entry_id + self._attr_unique_id = f"gardena_api_usage_{entry_id}" + self._attr_name = "API Requests (Week)" + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, f"websocket_status_{entry_id}")}, + ) + + @property + def available(self) -> bool: + """Always available.""" + return True + + @property + def native_value(self) -> int: + """Return the number of API requests this week.""" + return self.coordinator.client.api_tracker.requests_this_week + + @property + def extra_state_attributes(self) -> dict[str, Any]: + """Return diagnostic attributes with request breakdown.""" + tracker = self.coordinator.client.api_tracker + attrs: dict[str, Any] = { + "requests_today": tracker.requests_today, + "requests_this_week": tracker.requests_this_week, + "quota_weekly": 700, + "requests_by_endpoint": tracker.requests_by_endpoint(), + "recent_requests": tracker.recent_requests, } + return attrs + + async def async_update(self) -> None: + """No-op: value is computed on read from the shared tracker.""" + + diff --git a/custom_components/gardena_smart_system/strings.json b/custom_components/gardena_smart_system/strings.json index 8c0355a..326f738 100644 --- a/custom_components/gardena_smart_system/strings.json +++ b/custom_components/gardena_smart_system/strings.json @@ -1,22 +1,71 @@ { + "entity": { + "sensor": { + "api_requests_week": { + "name": "API Requests (Week)" + }, + "mower_error": { + "name": "Error code", + "state": { + "no_message": "No error", + "outside_working_area": "Outside working area", + "no_loop_signal": "No loop signal", + "wrong_loop_signal": "Wrong loop signal", + "loop_sensor_problem_front": "Front loop sensor defect", + "loop_sensor_problem_rear": "Rear loop sensor defect", + "trapped": "Trapped", + "upside_down": "Upside down", + "low_battery": "Low battery", + "empty_battery": "Empty battery", + "no_drive": "No drive", + "lifted": "Lifted", + "stuck_in_charging_station": "Stuck in charging station", + "charging_station_blocked": "Charging station blocked", + "collision_sensor_problem_rear": "Rear collision sensor defect", + "collision_sensor_problem_front": "Front collision sensor defect", + "wheel_motor_blocked_right": "Right wheel motor blocked", + "wheel_motor_blocked_left": "Left wheel motor blocked", + "cutting_system_blocked": "Cutting system blocked", + "steep_slope": "Too steep slope", + "parked_daily_limit_reached": "Daily limit reached", + "unknown": "Unknown error" + } + } + } + }, "config": { "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_account%]" + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", + "reconfigure_successful": "API credentials updated. The integration was reloaded." }, "error": { "cannot_connect": "Failed to connect, please try again.", - "invalid_auth": "Invalid authentication.", + "invalid_auth": "Invalid authentication credentials. Please check your Client ID and Client Secret.", + "insufficient_permissions": "Insufficient permissions. Please check your API key and permissions.", + "no_locations": "No locations found. Please set up your Gardena smart Gateway in the official Gardena app first, then retry.", + "server_error": "Server error occurred. Please try again later.", + "auth_error": "Authentication error occurred. Please check your credentials.", + "unknown_error": "An unexpected error occurred. Please try again.", "too_many_requests": "Too many requests, retry later.", + "api_error": "Failed to connect to the Gardena API. Please verify your credentials and try again.", "unknown": "Unexpected error." }, "step": { "user": { - "description": "Please enter your credentials.", + "description": "Please enter your Gardena Smart System API credentials. You can obtain these from the Gardena Developer Portal.", "data": { "client_id": "Application Key / Client ID", "client_secret": "Application secret / Client secret" }, "title": "Gardena Smart System" + }, + "reconfigure": { + "description": "Update your Husqvarna developer application credentials. Home Assistant obtains short-lived access tokens automatically — you do not enter a Bearer token manually. Existing entities are kept.", + "data": { + "client_id": "Application Key / Client ID", + "client_secret": "Application secret / Client secret" + }, + "title": "Update Gardena API credentials" } } }, @@ -42,6 +91,32 @@ "description": "The duration in seconds." } } + }, + "start_automatic": { + "name": "Start automatic", + "description": "Start the mower with automatic schedule." + }, + "park_until_next_task": { + "name": "Park until next task", + "description": "Park the mower until the next scheduled task." + }, + "park_until_further_notice": { + "name": "Park until further notice", + "description": "Park the mower until further notice." + }, + "reconnect_websocket": { + "name": "Reconnect WebSocket", + "description": "Force WebSocket reconnection for the Gardena Smart System integration." + }, + "websocket_diagnostics": { + "name": "WebSocket Diagnostics", + "description": "Get WebSocket connection diagnostics and status information.", + "fields": { + "detailed": { + "name": "Detailed", + "description": "Include detailed connection information." + } + } } } } \ No newline at end of file diff --git a/custom_components/gardena_smart_system/switch.py b/custom_components/gardena_smart_system/switch.py index 6390cde..785ca83 100644 --- a/custom_components/gardena_smart_system/switch.py +++ b/custom_components/gardena_smart_system/switch.py @@ -1,381 +1,94 @@ -"""Support for Gardena switch (Power control, water control, smart irrigation control).""" -import asyncio +"""Support for Gardena Smart System switches.""" +from __future__ import annotations + import logging +from typing import Any -from homeassistant.core import callback from homeassistant.components.switch import SwitchEntity -from homeassistant.const import ATTR_BATTERY_LEVEL - -from .const import ( - ATTR_ACTIVITY, - ATTR_BATTERY_STATE, - ATTR_LAST_ERROR, - ATTR_RF_LINK_LEVEL, - ATTR_RF_LINK_STATE, - ATTR_SERIAL, - CONF_SMART_IRRIGATION_DURATION, - CONF_SMART_WATERING_DURATION, - DEFAULT_SMART_IRRIGATION_DURATION, - DEFAULT_SMART_WATERING_DURATION, - DOMAIN, - GARDENA_LOCATION, -) -from .sensor import GardenaSensor +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from .const import DOMAIN +from .coordinator import GardenaSmartSystemCoordinator +from .entities import GardenaDeviceEntity _LOGGER = logging.getLogger(__name__) -async def async_setup_entry(hass, config_entry, async_add_entities): - """Set up the switches platform.""" +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up Gardena Smart System switches.""" + coordinator: GardenaSmartSystemCoordinator = hass.data[DOMAIN][entry.entry_id] + # Create switch entities for each device entities = [] - for water_control in hass.data[DOMAIN][GARDENA_LOCATION].find_device_by_type("WATER_CONTROL"): - entities.append(GardenaSmartWaterControl(water_control, config_entry.options)) + + for location in coordinator.locations.values(): + for device in location.devices.values(): + _LOGGER.debug(f"Checking device {device.name} ({device.id}) - Services: {list(device.services.keys())}") + # Add power socket switches if available + if "POWER_SOCKET" in device.services: + power_services = device.services["POWER_SOCKET"] + _LOGGER.debug(f"Found {len(power_services)} power socket services for device: {device.name} ({device.id})") + for power_service in power_services: + _LOGGER.debug(f"Creating power socket switch for service: {power_service.id}") + entities.append(GardenaPowerSocketSwitch(coordinator, device, power_service)) + else: + _LOGGER.debug(f"Device {device.name} ({device.id}) has no POWER_SOCKET service") - for power_switch in hass.data[DOMAIN][GARDENA_LOCATION].find_device_by_type("POWER_SOCKET"): - entities.append(GardenaPowerSocket(power_switch)) - - for smart_irrigation in hass.data[DOMAIN][GARDENA_LOCATION].find_device_by_type("SMART_IRRIGATION_CONTROL"): - for valve in smart_irrigation.valves.values(): - entities.append(GardenaSmartIrrigationControl( - smart_irrigation, valve['id'], config_entry.options)) - - _LOGGER.debug( - "Adding water control, power socket and smart irrigation control as switch: %s", - entities) - async_add_entities(entities, True) + _LOGGER.debug(f"Created {len(entities)} power socket switch entities") + async_add_entities(entities) -class GardenaSmartWaterControl(SwitchEntity): - """Representation of a Gardena Smart Water Control.""" +class GardenaPowerSocketSwitch(GardenaDeviceEntity, SwitchEntity): + """Representation of a Gardena power socket switch.""" - def __init__(self, wc, options): - """Initialize the Gardena Smart Water Control.""" - self._device = wc - self._options = options - self._name = f"{self._device.name}" - self._unique_id = f"{self._device.serial}-valve" - self._state = None - self._error_message = "" - - async def async_added_to_hass(self): - """Subscribe to events.""" - self._device.add_callback(self.update_callback) + def __init__(self, coordinator: GardenaSmartSystemCoordinator, device, power_service) -> None: + """Initialize the power socket switch.""" + super().__init__(coordinator, device, "POWER_SOCKET") + self._attr_name = f"{device.name} Power Socket" + self._power_service = power_service + self._device_id = device.id @property - def should_poll(self) -> bool: - """No polling needed for a water valve.""" + def is_on(self) -> bool: + """Return true if the switch is on.""" + device = self.coordinator.get_device_by_id(self._device_id) + service = device.services.get("POWER_SOCKET") + if service and service[0].activity: + return service[0].activity in ["FOREVER_ON", "TIME_LIMITED_ON", "SCHEDULED_ON"] return False - def update_callback(self, device): - """Call update for Home Assistant when the device is updated.""" - self.schedule_update_ha_state(True) + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn the switch on.""" + if self._power_service: + command_data = { + "data": { + "id": "turn_on", + "type": "POWER_SOCKET_CONTROL", + "attributes": { + "command": "START_OVERRIDE", + }, + } + } + await self.coordinator.client.send_command(self._power_service.id, command_data) + await self.coordinator.async_request_refresh() - async def async_update(self): - """Update the states of Gardena devices.""" - _LOGGER.debug("Running Gardena update") - # Managing state - state = self._device.valve_state - _LOGGER.debug("Water control has state %s", state) - if state in ["WARNING", "ERROR", "UNAVAILABLE"]: - _LOGGER.debug("Water control has an error") - self._state = False - self._error_message = self._device.last_error_code - else: - _LOGGER.debug("Getting water control state") - activity = self._device.valve_activity - self._error_message = "" - _LOGGER.debug("Water control has activity %s", activity) - if activity == "CLOSED": - self._state = False - elif activity in ["MANUAL_WATERING", "SCHEDULED_WATERING"]: - self._state = True - else: - _LOGGER.debug("Water control has none activity") - - @property - def name(self): - """Return the name of the device.""" - return self._name - - @property - def unique_id(self) -> str: - """Return a unique ID.""" - return self._unique_id - - @property - def is_on(self): - """Return true if it is on.""" - return self._state - - @property - def available(self): - """Return True if the device is available.""" - return self._device.valve_state != "UNAVAILABLE" - - def error(self): - """Return the error message.""" - return self._error_message - - @property - def extra_state_attributes(self): - """Return the state attributes of the water valve.""" - return { - ATTR_ACTIVITY: self._device.valve_activity, - ATTR_BATTERY_LEVEL: self._device.battery_level, - ATTR_BATTERY_STATE: self._device.battery_state, - ATTR_RF_LINK_LEVEL: self._device.rf_link_level, - ATTR_RF_LINK_STATE: self._device.rf_link_state, - ATTR_LAST_ERROR: self._error_message, - } - - @property - def option_smart_watering_duration(self) -> int: - return self._options.get( - CONF_SMART_WATERING_DURATION, DEFAULT_SMART_WATERING_DURATION - ) - - def turn_on(self, **kwargs): - """Start watering.""" - duration = self.option_smart_watering_duration * 60 - return asyncio.run_coroutine_threadsafe( - self._device.start_seconds_to_override(duration), self.hass.loop - ).result() - - def turn_off(self, **kwargs): - """Stop watering.""" - return asyncio.run_coroutine_threadsafe( - self._device.stop_until_next_task(), self.hass.loop - ).result() - - @property - def device_info(self): - return { - "identifiers": { - # Serial numbers are unique identifiers within a specific domain - (DOMAIN, self._device.serial) - }, - "name": self._device.name, - "manufacturer": "Gardena", - "model": self._device.model_type, - } - - -class GardenaPowerSocket(SwitchEntity): - """Representation of a Gardena Power Socket.""" - - def __init__(self, ps): - """Initialize the Gardena Power Socket.""" - self._device = ps - self._name = f"{self._device.name}" - self._unique_id = f"{self._device.serial}" - self._state = None - self._error_message = "" - - async def async_added_to_hass(self): - """Subscribe to events.""" - self._device.add_callback(self.update_callback) - - @property - def should_poll(self) -> bool: - """No polling needed for a power socket.""" - return False - - def update_callback(self, device): - """Call update for Home Assistant when the device is updated.""" - self.schedule_update_ha_state(True) - - async def async_update(self): - """Update the states of Gardena devices.""" - _LOGGER.debug("Running Gardena update") - # Managing state - state = self._device.state - _LOGGER.debug("Power socket has state %s", state) - if state in ["WARNING", "ERROR", "UNAVAILABLE"]: - _LOGGER.debug("Power socket has an error") - self._state = False - self._error_message = self._device.last_error_code - else: - _LOGGER.debug("Getting Power socket state") - activity = self._device.activity - self._error_message = "" - _LOGGER.debug("Power socket has activity %s", activity) - if activity == "OFF": - self._state = False - elif activity in ["FOREVER_ON", "TIME_LIMITED_ON", "SCHEDULED_ON"]: - self._state = True - else: - _LOGGER.debug("Power socket has none activity") - - @property - def name(self): - """Return the name of the device.""" - return self._name - - @property - def unique_id(self) -> str: - """Return a unique ID.""" - return self._unique_id - - @property - def is_on(self): - """Return true if it is on.""" - return self._state - - @property - def available(self): - """Return True if the device is available.""" - return self._device.state != "UNAVAILABLE" - - def error(self): - """Return the error message.""" - return self._error_message - - @property - def extra_state_attributes(self): - """Return the state attributes of the power switch.""" - return { - ATTR_ACTIVITY: self._device.activity, - ATTR_RF_LINK_LEVEL: self._device.rf_link_level, - ATTR_RF_LINK_STATE: self._device.rf_link_state, - ATTR_LAST_ERROR: self._error_message, - } - - def turn_on(self, **kwargs): - """Start watering.""" - return asyncio.run_coroutine_threadsafe( - self._device.start_override(), self.hass.loop - ).result() - - def turn_off(self, **kwargs): - """Stop watering.""" - return asyncio.run_coroutine_threadsafe( - self._device.stop_until_next_task(), self.hass.loop - ).result() - - @property - def device_info(self): - return { - "identifiers": { - # Serial numbers are unique identifiers within a specific domain - (DOMAIN, self._device.serial) - }, - "name": self._device.name, - "manufacturer": "Gardena", - "model": self._device.model_type, - } - - -class GardenaSmartIrrigationControl(SwitchEntity): - """Representation of a Gardena Smart Irrigation Control.""" - - def __init__(self, sic, valve_id, options): - """Initialize the Gardena Smart Irrigation Control.""" - self._device = sic - self._valve_id = valve_id - self._options = options - self._name = f"{self._device.name} - {self._device.valves[self._valve_id]['name']}" - self._unique_id = f"{self._device.serial}-{self._valve_id}" - self._state = None - self._error_message = "" - - async def async_added_to_hass(self): - """Subscribe to events.""" - self._device.add_callback(self.update_callback) - - @property - def should_poll(self) -> bool: - """No polling needed for a smart irrigation control.""" - return False - - def update_callback(self, device): - """Call update for Home Assistant when the device is updated.""" - self.schedule_update_ha_state(True) - - async def async_update(self): - """Update the states of Gardena devices.""" - _LOGGER.debug("Running Gardena update") - # Managing state - valve = self._device.valves[self._valve_id] - _LOGGER.debug("Valve has state: %s", valve["state"]) - if valve["state"] in ["WARNING", "ERROR", "UNAVAILABLE"]: - _LOGGER.debug("Valve has an error") - self._state = False - self._error_message = valve["last_error_code"] - else: - _LOGGER.debug("Getting Valve state") - activity = valve["activity"] - self._error_message = "" - _LOGGER.debug("Valve has activity: %s", activity) - if activity == "CLOSED": - self._state = False - elif activity in ["MANUAL_WATERING", "SCHEDULED_WATERING"]: - self._state = True - else: - _LOGGER.debug("Valve has unknown activity") - - @property - def name(self): - """Return the name of the device.""" - return self._name - - @property - def unique_id(self) -> str: - """Return a unique ID.""" - return self._unique_id - - @property - def is_on(self): - """Return true if it is on.""" - return self._state - - @property - def available(self): - """Return True if the device is available.""" - return self._device.valves[self._valve_id]["state"] != "UNAVAILABLE" - - def error(self): - """Return the error message.""" - return self._error_message - - @property - def extra_state_attributes(self): - """Return the state attributes of the smart irrigation control.""" - return { - ATTR_ACTIVITY: self._device.valves[self._valve_id]["activity"], - ATTR_RF_LINK_LEVEL: self._device.rf_link_level, - ATTR_RF_LINK_STATE: self._device.rf_link_state, - ATTR_LAST_ERROR: self._error_message, - } - - @property - def option_smart_irrigation_duration(self) -> int: - return self._options.get( - CONF_SMART_IRRIGATION_DURATION, DEFAULT_SMART_IRRIGATION_DURATION - ) - - def turn_on(self, **kwargs): - """Start watering.""" - duration = self.option_smart_irrigation_duration * 60 - return asyncio.run_coroutine_threadsafe( - self._device.start_seconds_to_override(duration, self._valve_id), self.hass.loop - ).result() - - def turn_off(self, **kwargs): - """Stop watering.""" - return asyncio.run_coroutine_threadsafe( - self._device.stop_until_next_task(self._valve_id), self.hass.loop - ).result() - - @property - def device_info(self): - return { - "identifiers": { - # Serial numbers are unique identifiers within a specific domain - (DOMAIN, self._device.serial) - }, - "name": self._device.name, - "manufacturer": "Gardena", - "model": self._device.model_type, - } + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn the switch off.""" + if self._power_service: + command_data = { + "data": { + "id": "turn_off", + "type": "POWER_SOCKET_CONTROL", + "attributes": { + "command": "STOP_UNTIL_NEXT_TASK", + }, + } + } + await self.coordinator.client.send_command(self._power_service.id, command_data) + await self.coordinator.async_request_refresh() diff --git a/custom_components/gardena_smart_system/translations/de.json b/custom_components/gardena_smart_system/translations/de.json index 6c2a53e..470d63e 100644 --- a/custom_components/gardena_smart_system/translations/de.json +++ b/custom_components/gardena_smart_system/translations/de.json @@ -1,23 +1,196 @@ { + "entity": { + "sensor": { + "api_requests_week": { + "name": "API-Anfragen (Woche)" + }, + "mower_error": { + "name": "Fehlercode", + "state": { + "uninitialised": "Nicht initialisiert", + "no_message": "Kein Fehler", + "outside_working_area": "Außerhalb des Mähbereichs", + "no_loop_signal": "Kein Schleifensignal", + "no_charging_station_signal": "Kein Ladestationssignal", + "wrong_loop_signal": "Falsches Schleifensignal", + "loop_sensor_problem_front": "Vorderer Schleifensensor", + "loop_sensor_problem_rear": "Hinterer Schleifensensor", + "trapped": "Mäher hängt fest", + "upside_down": "Mäher kopfüber", + "low_battery": "Niedriger Batteriestand", + "empty_battery": "Akku leer", + "no_drive": "Kein Antrieb", + "lifted": "Angehoben", + "stuck_in_charging_station": "Eingeklemmt in Ladestation", + "charging_station_blocked": "Ladestation blockiert", + "collision_sensor_problem_rear": "Störung hinterer Kollisionssensor", + "collision_sensor_problem_front": "Störung vorderer Kollisionssensor", + "wheel_motor_blocked_right": "Radmotor rechts blockiert", + "wheel_motor_blocked_left": "Radmotor links blockiert", + "wheel_drive_problem_right": "Rechtes Antriebsrad blockiert", + "wheel_drive_problem_left": "Linkes Antriebsrad blockiert", + "cutting_system_blocked": "Schneidsystem blockiert", + "invalid_sub_device_combination": "Fehlerhafte Verbindung", + "settings_restored": "Standardeinstellungen", + "charging_system_problem": "Fehler des Ladesystems", + "tilt_sensor_problem": "Kippsensorproblem", + "mower_tilted": "Mäher geneigt", + "wheel_motor_overloaded_right": "Rechter Radmotor überlastet", + "wheel_motor_overloaded_left": "Linker Radmotor überlastet", + "charging_current_too_high": "Ladestrom zu hoch", + "electronic_problem": "Elektronisches Problem", + "cutting_height_blocked": "Schnitthöhenregler blockiert", + "cutting_height_problem": "Problem mit Schnitthöhe", + "temporary_problem": "Vorübergehendes Problem", + "guide_1_not_found": "Leitkabel nicht gefunden", + "guide_2_not_found": "Leitkabel 2 nicht gefunden", + "guide_3_not_found": "Leitkabel 3 nicht gefunden", + "gps_tracker_module_error": "GPS Modul Fehler", + "weak_gps_signal": "Ungenaue Positionsdaten", + "guide_calibration_failed": "Kalibration des Leitkabels fehlgeschlagen", + "temporary_battery_problem": "Kurzzeitiges Batterieproblem", + "battery_problem": "Akku unzulässig", + "alarm_mower_switched_off": "Alarm! Mäher ausgeschalten", + "alarm_mower_stopped": "Alarm! Mäher gestoppt", + "alarm_mower_lifted": "Alarm! Mäher angehoben", + "alarm_mower_tilted": "Alarm! Mäher gekippt", + "com_board_not_available": "Kommunikationsboard nicht erreichbar", + "slipped": "Räder rutschen ab", + "invalid_battery_combination": "Ungültige Batteriekombination", + "safety_function_faulty": "Sicherheitsfunktion", + "invalid_system_conf": "Fehlerhafte Systemkonfiguration", + "lift_sensor_defect": "Hebesensor Fehler", + "mobile_loop_defect": "ZoneProtect Akku leer", + "left_loop_sensor": "Linker Schleifensensor defekt", + "right_loop_sensor": "Rechter Schleifensensor defekt", + "wrong_pin": "Falscher PIN-Code", + "temporary_lift": "Mäher angehoben", + "cutting_drive": "Schneideantrieb defekt", + "steep_slope": "Steigung zu steil", + "stop_button_fail": "Problem mit der Stop-Taste", + "angle_cutting_means_off": "Schneidwerk am Hang abgeschaltet", + "slave_mcu_lost": "Verbindungsproblem mit MCU", + "cutting_overload": "Schneidwerk überlastet", + "cutting_height_range": "Schnitthöhenbereich Fehler", + "cutting_height_drift": "Schnitthöhe Drift", + "cutting_height_limited": "Schnitthöhe begrenzt", + "cutting_height_drive": "Schnitthöhenantrieb Problem", + "cutting_height_current": "Schnitthöhe Strom zu hoch", + "cutting_height_direction": "Schnitthöhe falsche Richtung", + "mower_to_cs_com": "Kommunikation mit Ladestation fehlgeschlagen", + "ultrasonic_error": "Ultraschallsensor Fehler", + "high_low_bat_temp_a": "Akkutemperatur außerhalb der Grenzwerte", + "high_low_bat_temp_b": "Batterie B Temperatur außerhalb des Bereichs", + "too_low_voltage_bat_a": "Batterie A Spannung zu niedrig", + "too_low_voltage_bat_b": "Batterie B Spannung zu niedrig", + "alarm_motion": "Alarm: Bewegung erkannt", + "alarm_geofence": "Alarm: Außerhalb des Geofence", + "rr_wheel_blocked": "Hinteres rechtes Rad blockiert", + "rl_wheel_blocked": "Hinteres linkes Rad blockiert", + "rr_wheel_drive": "Hinterer rechter Radantrieb Problem", + "rl_wheel_drive": "Hinterer linker Radantrieb Problem", + "rear_right_wheel_overloaded": "Hinteres rechtes Rad überlastet", + "rear_left_wheel_overloaded": "Hinteres linkes Rad überlastet", + "angular_sensor_defect": "Winkelsensor defekt", + "no_power_in_cs": "Ladestation nicht mit Strom versorgt", + "switch_cord_sensor_defect": "Schaltkabelsensor defekt", + "map_not_valid": "Karte ungültig", + "no_position": "Position zu ungenau", + "no_rs_communication": "Referenzstation Kommunikation verloren", + "folding_sensor_activated": "Klappsensor aktiviert", + "ultrasonic_sensor_1_defect": "Ultraschallsensor 1 defekt", + "ultrasonic_sensor_2_defect": "Ultraschallsensor 2 defekt", + "ultrasonic_sensor_3_defect": "Ultraschallsensor 3 defekt", + "ultrasonic_sensor_4_defect": "Ultraschallsensor 4 defekt", + "cutting_drive_motor_1_defect": "Schneidemotor 1 defekt", + "cutting_drive_motor_2_defect": "Schneidemotor 2 defekt", + "cutting_drive_motor_3_defect": "Schneidemotor 3 defekt", + "collision_sensor_defect": "Kollisionssensor Fehler", + "docking_sensor_defect": "Der Andocksensor ist defekt", + "folding_cutting_deck_sensor_defect": "Klapp-Mähdeck-Sensor defekt", + "loop_sensor_defect": "Schleifensensor Fehler", + "collision_sensor_error": "Kollision", + "no_confirmed_position": "Keine bestätigte Position", + "major_cutting_disk_imbalance": "Starke Unwucht der Schneidscheibe", + "complex_working_area": "Arbeitsbereich zu komplex", + "invalid_sw_configuration": "Ungültige Software-Konfiguration", + "radar_error": "Radar Fehler", + "work_area_tampered": "Arbeitsbereich manipuliert", + "destination_not_reachable": "Zielort nicht erreichbar", + "wait_stop_pressed": "Sicherheitsstopp", + "wait_for_safety_pin": "Mäher gesperrt", + "destination_not_reachable_warning": "Ziel blockiert", + "battery_near_end_of_life": "Akku muss ausgetauscht werden", + "edgemotor_blocked": "Kantenschneidscheibe blockiert", + "no_correction_data": "Positionsdaten unzureichend", + "invalid_correction_data": "Fehlerhafte Korrekturdaten", + "wait_updating": "Firmware wird aktualisiert", + "wait_power_up": "Wird hochgefahren", + "off_disabled": "Über Hauptschalter deaktiviert", + "off_hatch_open": "Klappe offen", + "off_hatch_closed": "Klappe geschlossen", + "parked_daily_limit_reached": "Tageslimit erreicht", + "vision_system_malfunction": "Objekterkennung fehlgeschlagen", + "poor_vision_system_performance": "Kameragehäuse verschmutzt", + "vision_processing_failed": "Bildverarbeitung fehlgeschlagen", + "loop_sensor_problem_left": "Problem Schleifensensor, links", + "loop_sensor_problem_right": "Problem Schleifensensor, rechts", + "wrong_pin_code": "Falscher PIN-Code", + "lost_wheel_brush": "Radbürste verloren", + "accessory_power_anomaly": "Zubehör Stromversorgung Anomalie", + "loop_wire_broken": "Begrenzungsdraht gebrochen", + "battery_fet_error": "Akku beschädigt", + "imbalanced_cutting_disc": "Unwucht der Schneidscheibe", + "cutting_motor_problem": "Schnittmotor gestört", + "limited_cutting_height_range": "Schnitthöhe begrenzt", + "cutting_motor_drive_defect": "Schneidsystem blockiert", + "memory_circuit_problem": "Elektronisches Problem", + "stop_button_problem": "Stop-Taste gestört", + "difficult_finding_home": "Problem LS finden", + "guide_calibration_accomplished": "Kalibration des Leitkabels beendet", + "too_many_batteries": "Zu viele Akkus", + "alarm_mower_in_motion": "Alarm! Mäher wurde bewegt", + "alarm_outside_geofence": "Alarm! Mäher außerhalb Begrenzungszauns", + "connection_changed": "Verbindung geändert", + "connection_not_changed": "Verbindung nicht geändert", + "unknown": "Unbekannter Fehler" + } + } + } + }, "config": { "abort": { - "already_configured": "Der Standort ist bereits konfiguriert" + "already_configured": "Der Standort ist bereits konfiguriert", + "reconfigure_successful": "API-Zugangsdaten aktualisiert. Die Integration wurde neu geladen." }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut.", - "invalid_auth": "Ungültige Authentifizierung.", - "too_many_requests": "Zu viele Anfragen, versuchen Sie es später erneut.", + "cannot_connect": "Verbindung fehlgeschlagen, bitte erneut versuchen.", + "invalid_auth": "Ungültige Anmeldedaten. Bitte überprüfen Sie Ihre Client-ID und Ihr Client-Secret.", + "insufficient_permissions": "Unzureichende Berechtigungen. Bitte überprüfen Sie Ihren API-Schlüssel und die Berechtigungen.", + "no_locations": "Keine Standorte gefunden. Bitte richten Sie zuerst Ihr Gardena Smart Gateway in der offiziellen Gardena-App ein.", + "server_error": "Serverfehler aufgetreten. Bitte später erneut versuchen.", + "auth_error": "Authentifizierungsfehler aufgetreten. Bitte überprüfen Sie Ihre Anmeldedaten.", + "unknown_error": "Ein unerwarteter Fehler ist aufgetreten. Bitte erneut versuchen.", + "too_many_requests": "Zu viele Anfragen, bitte später erneut versuchen.", + "api_error": "Verbindung zur Gardena-API fehlgeschlagen. Bitte überprüfen Sie Ihre Anmeldedaten.", "unknown": "Unerwarteter Fehler." }, "step": { "user": { - "description": "Bitte geben Sie Ihre Anmeldeinformationen ein.", + "description": "Bitte geben Sie Ihre Gardena Smart System API-Anmeldedaten ein. Sie erhalten diese über das Gardena Developer Portal.", "data": { - "email": "E-Mail", - "password": "Passwort", - "client_id": "Application Key / Client ID" + "client_id": "Application Key / Client ID", + "client_secret": "Application Secret / Client Secret" }, "title": "Gardena Smart System" + }, + "reconfigure": { + "description": "Neue Zugangsdaten der Husqvarna-Developer-App eintragen. Den kurzlebigen Access Token (Bearer) holt Home Assistant automatisch — hier kein manuelles Token nötig. Bestehende Entities bleiben erhalten.", + "data": { + "client_id": "Application Key / Client ID", + "client_secret": "Application Secret / Client Secret" + }, + "title": "Gardena API-Zugangsdaten ändern" } } }, @@ -27,10 +200,48 @@ "data": { "mower_duration": "Mäherdauer (Minuten)", "smart_irrigation_control_duration": "Smart Irrigation Control Dauer (Minuten)", - "smart_watering_duration": "Smarte Bewässerungs Dauer (Minuten)" + "smart_watering_duration": "Smarte Bewässerungsdauer (Minuten)" }, "title": "Gardena Smart System - Optionen" } } + }, + "services": { + "start_override": { + "name": "Überschreibung starten", + "description": "Starten Sie den Mäher sofort für eine bestimmte Dauer und überschreiben Sie den Zeitplan.", + "fields": { + "duration": { + "name": "Dauer", + "description": "Die Dauer in Sekunden." + } + } + }, + "start_automatic": { + "name": "Automatisch starten", + "description": "Starten Sie den Mäher mit dem automatischen Zeitplan." + }, + "park_until_next_task": { + "name": "Bis zur nächsten Aufgabe parken", + "description": "Parken Sie den Mäher bis zur nächsten geplanten Aufgabe." + }, + "park_until_further_notice": { + "name": "Bis auf Weiteres parken", + "description": "Parken Sie den Mäher bis auf Weiteres." + }, + "reconnect_websocket": { + "name": "WebSocket neu verbinden", + "description": "WebSocket-Verbindung für die Gardena Smart System-Integration erzwingen." + }, + "websocket_diagnostics": { + "name": "WebSocket-Diagnose", + "description": "WebSocket-Verbindungsdiagnose und Statusinformationen abrufen.", + "fields": { + "detailed": { + "name": "Detailliert", + "description": "Detaillierte Verbindungsinformationen einbeziehen." + } + } + } } } diff --git a/custom_components/gardena_smart_system/translations/en.json b/custom_components/gardena_smart_system/translations/en.json index f2e3704..2891180 100644 --- a/custom_components/gardena_smart_system/translations/en.json +++ b/custom_components/gardena_smart_system/translations/en.json @@ -1,23 +1,196 @@ { + "entity": { + "sensor": { + "api_requests_week": { + "name": "API Requests (Week)" + }, + "mower_error": { + "name": "Error code", + "state": { + "uninitialised": "Uninitialised", + "no_message": "No error", + "outside_working_area": "Outside working area", + "no_loop_signal": "No loop signal", + "no_charging_station_signal": "No charging station signal", + "wrong_loop_signal": "Wrong loop signal", + "loop_sensor_problem_front": "Front loop sensor defect", + "loop_sensor_problem_rear": "Rear loop sensor defect", + "trapped": "Trapped", + "upside_down": "Upside down", + "low_battery": "Low battery", + "empty_battery": "Empty battery", + "no_drive": "No drive", + "lifted": "Lifted", + "stuck_in_charging_station": "Stuck in charging station", + "charging_station_blocked": "Charging station blocked", + "collision_sensor_problem_rear": "Rear collision sensor defect", + "collision_sensor_problem_front": "Front collision sensor defect", + "wheel_motor_blocked_right": "Right wheel motor blocked", + "wheel_motor_blocked_left": "Left wheel motor blocked", + "wheel_drive_problem_right": "Right wheel drive problem", + "wheel_drive_problem_left": "Left wheel drive problem", + "cutting_system_blocked": "Cutting system blocked", + "invalid_sub_device_combination": "Invalid sub-device combination", + "settings_restored": "Settings restored to default", + "charging_system_problem": "Charging system problem", + "tilt_sensor_problem": "Tilt sensor problem", + "mower_tilted": "Mower tilted", + "wheel_motor_overloaded_right": "Right wheel motor overloaded", + "wheel_motor_overloaded_left": "Left wheel motor overloaded", + "charging_current_too_high": "Charging current too high", + "electronic_problem": "Electronic problem", + "cutting_height_blocked": "Cutting height blocked", + "cutting_height_problem": "Cutting height problem", + "temporary_problem": "Temporary problem", + "guide_1_not_found": "Guide 1 not found", + "guide_2_not_found": "Guide 2 not found", + "guide_3_not_found": "Guide 3 not found", + "gps_tracker_module_error": "GPS tracker module error", + "weak_gps_signal": "Weak GPS signal", + "guide_calibration_failed": "Guide calibration failed", + "temporary_battery_problem": "Temporary battery problem", + "battery_problem": "Battery problem", + "alarm_mower_switched_off": "Alarm: mower switched off", + "alarm_mower_stopped": "Alarm: mower stopped", + "alarm_mower_lifted": "Alarm: mower lifted", + "alarm_mower_tilted": "Alarm: mower tilted", + "com_board_not_available": "Communication board not available", + "slipped": "Slipped", + "invalid_battery_combination": "Invalid battery combination", + "safety_function_faulty": "Safety function faulty", + "invalid_system_conf": "Invalid system configuration", + "lift_sensor_defect": "Lift sensor defect", + "mobile_loop_defect": "Mobile loop defect", + "left_loop_sensor": "Left loop sensor defect", + "right_loop_sensor": "Right loop sensor defect", + "wrong_pin": "Wrong PIN", + "temporary_lift": "Temporarily lifted", + "cutting_drive": "Cutting drive defect", + "steep_slope": "Too steep slope", + "stop_button_fail": "Stop button failure", + "angle_cutting_means_off": "Cutting means turned off on slope", + "slave_mcu_lost": "Connection lost with secondary MCU", + "cutting_overload": "Cutting overload", + "cutting_height_range": "Cutting height range error", + "cutting_height_drift": "Cutting height drift", + "cutting_height_limited": "Cutting height limited", + "cutting_height_drive": "Cutting height drive problem", + "cutting_height_current": "Cutting height current too high", + "cutting_height_direction": "Cutting height wrong direction", + "mower_to_cs_com": "Communication with charging station failed", + "ultrasonic_error": "Ultrasonic sensor error", + "high_low_bat_temp_a": "Battery A temperature out of range", + "high_low_bat_temp_b": "Battery B temperature out of range", + "too_low_voltage_bat_a": "Battery A voltage too low", + "too_low_voltage_bat_b": "Battery B voltage too low", + "alarm_motion": "Alarm: motion detected", + "alarm_geofence": "Alarm: outside geofence", + "rr_wheel_blocked": "Rear right wheel blocked", + "rl_wheel_blocked": "Rear left wheel blocked", + "rr_wheel_drive": "Rear right wheel drive problem", + "rl_wheel_drive": "Rear left wheel drive problem", + "rear_right_wheel_overloaded": "Rear right wheel overloaded", + "rear_left_wheel_overloaded": "Rear left wheel overloaded", + "angular_sensor_defect": "Angular sensor defect", + "no_power_in_cs": "No power in charging station", + "switch_cord_sensor_defect": "Switch cord sensor defect", + "map_not_valid": "Map not valid", + "no_position": "No position available", + "no_rs_communication": "Reference station communication lost", + "folding_sensor_activated": "Folding sensor activated", + "ultrasonic_sensor_1_defect": "Ultrasonic sensor 1 defect", + "ultrasonic_sensor_2_defect": "Ultrasonic sensor 2 defect", + "ultrasonic_sensor_3_defect": "Ultrasonic sensor 3 defect", + "ultrasonic_sensor_4_defect": "Ultrasonic sensor 4 defect", + "cutting_drive_motor_1_defect": "Cutting drive motor 1 defect", + "cutting_drive_motor_2_defect": "Cutting drive motor 2 defect", + "cutting_drive_motor_3_defect": "Cutting drive motor 3 defect", + "collision_sensor_defect": "Collision sensor defect", + "docking_sensor_defect": "Docking sensor defect", + "folding_cutting_deck_sensor_defect": "Folding cutting deck sensor defect", + "loop_sensor_defect": "Loop sensor defect", + "collision_sensor_error": "Collision sensor error", + "no_confirmed_position": "No confirmed position", + "major_cutting_disk_imbalance": "Major cutting disk imbalance", + "complex_working_area": "Working area too complex", + "invalid_sw_configuration": "Invalid software configuration", + "radar_error": "Radar error", + "work_area_tampered": "Work area tampered", + "destination_not_reachable": "Destination not reachable", + "wait_stop_pressed": "Stop button pressed", + "wait_for_safety_pin": "Waiting for safety PIN", + "destination_not_reachable_warning": "Destination not reachable (warning)", + "battery_near_end_of_life": "Battery near end of life", + "edgemotor_blocked": "Edge motor blocked", + "no_correction_data": "No correction data available", + "invalid_correction_data": "Invalid correction data", + "wait_updating": "Updating firmware", + "wait_power_up": "Powering up", + "off_disabled": "Disabled on main switch", + "off_hatch_open": "Hatch open", + "off_hatch_closed": "Hatch closed", + "parked_daily_limit_reached": "Daily limit reached", + "vision_system_malfunction": "Vision system malfunction", + "poor_vision_system_performance": "Poor vision system performance", + "vision_processing_failed": "Vision processing failed", + "loop_sensor_problem_left": "Left loop sensor problem", + "loop_sensor_problem_right": "Right loop sensor problem", + "wrong_pin_code": "Wrong PIN code", + "lost_wheel_brush": "Wheel brush lost", + "accessory_power_anomaly": "Accessory power anomaly", + "loop_wire_broken": "Loop wire broken", + "battery_fet_error": "Battery FET error", + "imbalanced_cutting_disc": "Imbalanced cutting disc", + "cutting_motor_problem": "Cutting motor problem", + "limited_cutting_height_range": "Limited cutting height range", + "cutting_motor_drive_defect": "Cutting motor drive defect", + "memory_circuit_problem": "Memory circuit problem", + "stop_button_problem": "Stop button problem", + "difficult_finding_home": "Difficult finding home", + "guide_calibration_accomplished": "Guide calibration accomplished", + "too_many_batteries": "Too many batteries", + "alarm_mower_in_motion": "Alarm: mower in motion", + "alarm_outside_geofence": "Alarm: outside geofence", + "connection_changed": "Connection changed", + "connection_not_changed": "Connection not changed", + "unknown": "Unknown error" + } + } + } + }, "config": { "abort": { - "already_configured": "Location is already configured" + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", + "reconfigure_successful": "API credentials updated. The integration was reloaded." }, "error": { "cannot_connect": "Failed to connect, please try again.", - "invalid_auth": "Invalid authentication.", + "invalid_auth": "Invalid authentication credentials. Please check your Client ID and Client Secret.", + "insufficient_permissions": "Insufficient permissions. Please check your API key and permissions.", + "no_locations": "No locations found. Please set up your Gardena smart Gateway in the official Gardena app first, then retry.", + "server_error": "Server error occurred. Please try again later.", + "auth_error": "Authentication error occurred. Please check your credentials.", + "unknown_error": "An unexpected error occurred. Please try again.", "too_many_requests": "Too many requests, retry later.", + "api_error": "Failed to connect to the Gardena API. Please verify your credentials and try again.", "unknown": "Unexpected error." }, "step": { "user": { - "description": "Please enter your credentials.", + "description": "Please enter your Gardena Smart System API credentials. You can obtain these from the Gardena Developer Portal.", "data": { - "email": "E-mail", - "password": "Password", - "client_id": "Application Key / Client ID" + "client_id": "Application Key / Client ID", + "client_secret": "Application secret / Client secret" }, "title": "Gardena Smart System" + }, + "reconfigure": { + "description": "Update your Husqvarna developer application credentials. Home Assistant obtains short-lived access tokens automatically — you do not enter a Bearer token manually. Existing entities are kept.", + "data": { + "client_id": "Application Key / Client ID", + "client_secret": "Application secret / Client secret" + }, + "title": "Update Gardena API credentials" } } }, @@ -43,6 +216,32 @@ "description": "The duration in seconds." } } + }, + "start_automatic": { + "name": "Start automatic", + "description": "Start the mower with automatic schedule." + }, + "park_until_next_task": { + "name": "Park until next task", + "description": "Park the mower until the next scheduled task." + }, + "park_until_further_notice": { + "name": "Park until further notice", + "description": "Park the mower until further notice." + }, + "reconnect_websocket": { + "name": "Reconnect WebSocket", + "description": "Force WebSocket reconnection for the Gardena Smart System integration." + }, + "websocket_diagnostics": { + "name": "WebSocket Diagnostics", + "description": "Get WebSocket connection diagnostics and status information.", + "fields": { + "detailed": { + "name": "Detailed", + "description": "Include detailed connection information." + } + } } } } diff --git a/custom_components/gardena_smart_system/translations/fr.json b/custom_components/gardena_smart_system/translations/fr.json index add1314..a6c652a 100644 --- a/custom_components/gardena_smart_system/translations/fr.json +++ b/custom_components/gardena_smart_system/translations/fr.json @@ -1,21 +1,60 @@ { + "entity": { + "sensor": { + "api_requests_week": { + "name": "Requêtes API (Semaine)" + }, + "mower_error": { + "name": "Code d'erreur", + "state": { + "no_message": "Aucune erreur", + "outside_working_area": "Hors de la zone de travail", + "no_loop_signal": "Pas de signal de boucle", + "wrong_loop_signal": "Mauvais signal de boucle", + "loop_sensor_problem_front": "Capteur de boucle avant défectueux", + "loop_sensor_problem_rear": "Capteur de boucle arrière défectueux", + "trapped": "Bloquée", + "upside_down": "Retournée", + "low_battery": "Batterie faible", + "empty_battery": "Batterie vide", + "no_drive": "Pas de traction", + "lifted": "Soulevée", + "stuck_in_charging_station": "Bloquée dans la station de charge", + "charging_station_blocked": "Station de charge bloquée", + "collision_sensor_problem_rear": "Capteur de collision arrière défectueux", + "collision_sensor_problem_front": "Capteur de collision avant défectueux", + "wheel_motor_blocked_right": "Moteur de roue droite bloqué", + "wheel_motor_blocked_left": "Moteur de roue gauche bloqué", + "cutting_system_blocked": "Système de coupe bloqué", + "steep_slope": "Pente trop raide", + "parked_daily_limit_reached": "Limite quotidienne atteinte", + "unknown": "Erreur inconnue" + } + } + } + }, "config": { "abort": { - "already_configured": "La localisation est déjà configurée" + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]" }, "error": { "cannot_connect": "Impossible de se connecter, veuillez ré-essayer.", - "invalid_auth": "Authentification invalide.", - "too_many_requests": "Trop de requêtes, veuillez ré-essayer plus tard.", - "unknown": "Erreur innatendue." + "invalid_auth": "Identifiants d'authentification invalides. Veuillez vérifier votre Client ID et Client Secret.", + "insufficient_permissions": "Permissions insuffisantes. Veuillez vérifier votre clé API et vos permissions.", + "no_locations": "Aucun emplacement trouvé. Veuillez d'abord configurer votre passerelle Gardena smart dans l'application officielle Gardena, puis réessayez.", + "server_error": "Erreur serveur survenue. Veuillez ré-essayer plus tard.", + "auth_error": "Erreur d'authentification survenue. Veuillez vérifier vos identifiants.", + "unknown_error": "Une erreur inattendue s'est produite. Veuillez ré-essayer.", + "too_many_requests": "Trop de requêtes, ré-essayez plus tard.", + "api_error": "Impossible de se connecter à l'API Gardena. Veuillez vérifier vos identifiants et réessayer.", + "unknown": "Erreur inattendue." }, "step": { "user": { - "description": "Veuillez renseigner vos identifiants.", + "description": "Veuillez saisir vos identifiants API Gardena Smart System. Vous pouvez les obtenir depuis le Portail Développeur Gardena.", "data": { - "email": "E-mail", - "password": "Mot de passe", - "client_id": "Application Key / Client ID" + "client_id": "Clé d'application / Client ID", + "client_secret": "Secret d'application / Client secret" }, "title": "Gardena Smart System" } @@ -32,5 +71,43 @@ "title": "Gardena Smart System - Options" } } + }, + "services": { + "start_override": { + "name": "Démarrer la substitution", + "description": "Démarrer la tondeuse immédiatement pour une durée spécifique et remplacer le planning.", + "fields": { + "duration": { + "name": "Durée", + "description": "La durée en secondes." + } + } + }, + "start_automatic": { + "name": "Démarrer automatique", + "description": "Démarrer la tondeuse avec le planning automatique." + }, + "park_until_next_task": { + "name": "Ranger jusqu'à la prochaine tâche", + "description": "Ranger la tondeuse jusqu'à la prochaine tâche programmée." + }, + "park_until_further_notice": { + "name": "Ranger jusqu'à nouvel ordre", + "description": "Ranger la tondeuse jusqu'à nouvel ordre." + }, + "reconnect_websocket": { + "name": "Reconnecter WebSocket", + "description": "Forcer la reconnexion WebSocket pour l'intégration Gardena Smart System." + }, + "websocket_diagnostics": { + "name": "Diagnostics WebSocket", + "description": "Obtenir les diagnostics et le statut de la connexion WebSocket.", + "fields": { + "detailed": { + "name": "Détaillé", + "description": "Inclure les informations détaillées de connexion." + } + } + } } } diff --git a/custom_components/gardena_smart_system/translations/nb.json b/custom_components/gardena_smart_system/translations/nb.json index 1ecc43b..e4fe84d 100644 --- a/custom_components/gardena_smart_system/translations/nb.json +++ b/custom_components/gardena_smart_system/translations/nb.json @@ -32,5 +32,34 @@ "title": "Gardena Smart System - Alternativer" } } + }, + "services": { + "start_override": { + "name": "Start overstyring", + "description": "Start klipperen umiddelbart for en bestemt varighet og overstyr timeplanen.", + "icon": "mdi:mower-on", + "fields": { + "duration": { + "name": "Varighet", + "description": "Varigheten i sekunder." + } + } + }, + "start_automatic": { + "name": "Start automatisk", + "description": "Start klipperen med den automatiske timeplanen." + }, + "park_until_next_task": { + "name": "Parker til neste oppgave", + "description": "Parker klipperen til neste planlagte oppgave." + }, + "park_until_further_notice": { + "name": "Parker til videre", + "description": "Parker klipperen til videre." + }, + "reconnect_websocket": { + "name": "Koble til WebSocket på nytt", + "description": "Tving WebSocket-tilkobling for Gardena Smart System-integrasjonen." + } } } diff --git a/custom_components/gardena_smart_system/translations/nl.json b/custom_components/gardena_smart_system/translations/nl.json index fdd8387..d276c9c 100644 --- a/custom_components/gardena_smart_system/translations/nl.json +++ b/custom_components/gardena_smart_system/translations/nl.json @@ -32,5 +32,34 @@ "title": "Gardena Smart System - Opties" } } + }, + "services": { + "start_override": { + "name": "Start overschrijving", + "description": "Start de maaier onmiddellijk voor een specifieke duur en overschrijf het schema.", + "icon": "mdi:mower-on", + "fields": { + "duration": { + "name": "Duur", + "description": "De duur in seconden." + } + } + }, + "start_automatic": { + "name": "Start automatisch", + "description": "Start de maaier met het automatische schema." + }, + "park_until_next_task": { + "name": "Parkeren tot volgende taak", + "description": "Parkeer de maaier tot de volgende geplande taak." + }, + "park_until_further_notice": { + "name": "Parkeren tot nader order", + "description": "Parkeer de maaier tot nader order." + }, + "reconnect_websocket": { + "name": "WebSocket opnieuw verbinden", + "description": "WebSocket-verbinding forceren voor de Gardena Smart System-integratie." + } } } diff --git a/custom_components/gardena_smart_system/translations/vacuum.da.json b/custom_components/gardena_smart_system/translations/vacuum.da.json deleted file mode 100644 index ab3bd8d..0000000 --- a/custom_components/gardena_smart_system/translations/vacuum.da.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "device_automation": { - "action_type": { - "clean": "Lad {entity_name} klippe", - "dock": "Lad {entity_name} vende tilbage til oplader" - }, - "condition_type": { - "is_cleaning": "{entity_name} klipper", - "is_docked": "{entity_name} er i oplader" - }, - "trigger_type": { - "cleaning": "{entity_name} begyndte at klippe", - "docked": "{entity_name} er i dock" - } - }, - "state": { - "_": { - "cleaning": "Klipper", - "docked": "I oplader", - "error": "Fejl", - "idle": "[%key:common::state::idle%]", - "off": "[%key:common::state::off%]", - "on": "[%key:common::state::on%]", - "paused": "Sat p\u00e5 pause", - "returning": "Vender tilbage til oplader" - } - }, - "title": "Pl\u00e6neklipper" -} diff --git a/custom_components/gardena_smart_system/translations/vacuum.de.json b/custom_components/gardena_smart_system/translations/vacuum.de.json deleted file mode 100644 index dc0a390..0000000 --- a/custom_components/gardena_smart_system/translations/vacuum.de.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "title": "Mäher", - "device_automation": { - "condition_type": { - "is_docked": "{entity_name} ist angedockt", - "is_cleaning": "{entity_name} mäht" - }, - "trigger_type": { - "cleaning": "{entity_name} hat begonnen zu mähen", - "docked": "{entity_name} hat angedockt" - }, - "action_type": { - "clean": "Lasse {entity_name} mähen", - "dock": "Lasse {entity_name} zum Dock zurückkehren" - } - }, - "state": { - "_": { - "cleaning": "Mäht", - "docked": "Angedockt", - "error": "Fehler", - "idle": "Inaktiv", - "off": "Aus", - "on": "An", - "paused": "Pausiert", - "returning": "Zurück zum Dock" - } - } - } diff --git a/custom_components/gardena_smart_system/translations/vacuum.en.json b/custom_components/gardena_smart_system/translations/vacuum.en.json deleted file mode 100644 index f41f145..0000000 --- a/custom_components/gardena_smart_system/translations/vacuum.en.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "title": "Mower", - "device_automation": { - "condition_type": { - "is_docked": "{entity_name} is docked", - "is_cleaning": "{entity_name} is cutting" - }, - "trigger_type": { - "cleaning": "{entity_name} started cutting", - "docked": "{entity_name} docked" - }, - "action_type": { - "clean": "Let {entity_name} cut", - "dock": "Let {entity_name} return to the dock" - } - }, - "state": { - "_": { - "cleaning": "Cutting", - "docked": "Docked", - "error": "Error", - "idle": "[%key:common::state::idle%]", - "off": "[%key:common::state::off%]", - "on": "[%key:common::state::on%]", - "paused": "[%key:common::state::paused%]", - "returning": "Returning to dock" - } - } - } diff --git a/custom_components/gardena_smart_system/translations/vacuum.nl.json b/custom_components/gardena_smart_system/translations/vacuum.nl.json deleted file mode 100644 index 3dc067d..0000000 --- a/custom_components/gardena_smart_system/translations/vacuum.nl.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "title": "Maaier", - "device_automation": { - "condition_type": { - "is_docked": "{entity_name} is geparkeerd", - "is_cleaning": "{entity_name} is aan het maaien" - }, - "trigger_type": { - "cleaning": "{entity_name} is gestart met maaien", - "docked": "{entity_name} parkeert" - }, - "action_type": { - "clean": "Laat {entity_name} maaien", - "dock": "Laat {entity_name} parkeren" - } - }, - "state": { - "_": { - "cleaning": "Maaien", - "docked": "Geparkeerd", - "error": "Fout", - "idle": "[%key:common::state::idle%]", - "off": "[%key:common::state::off%]", - "on": "[%key:common::state::on%]", - "paused": "[%key:common::state::paused%]", - "returning": "Terug naar laadstation aan het gaan" - } - } - } diff --git a/custom_components/localtuya/config_flow.py b/custom_components/localtuya/config_flow.py index 5c87e25..2573bc4 100644 --- a/custom_components/localtuya/config_flow.py +++ b/custom_components/localtuya/config_flow.py @@ -393,7 +393,7 @@ class LocalTuyaOptionsFlowHandler(config_entries.OptionsFlow): def __init__(self, config_entry): """Initialize localtuya options flow.""" - self.config_entry = config_entry + self._config_entry = config_entry # self.dps_strings = config_entry.data.get(CONF_DPS_STRINGS, gen_dps_strings()) # self.entities = config_entry.data[CONF_ENTITIES] self.selected_device = None diff --git a/custom_components/omnik_inverter/__init__.py b/custom_components/omnik_inverter/__init__.py index aeeeb7c..d48ff52 100644 --- a/custom_components/omnik_inverter/__init__.py +++ b/custom_components/omnik_inverter/__init__.py @@ -1,17 +1,21 @@ """Omnik Inverter platform configuration.""" + from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant -from .const import CONFIGFLOW_VERSION, DOMAIN, LOGGER +from .const import CONFIGFLOW_VERSION, LOGGER from .coordinator import OmnikInverterDataUpdateCoordinator +type OmnikInverterConfigEntry = ConfigEntry[OmnikInverterDataUpdateCoordinator] + PLATFORMS = [Platform.SENSOR, Platform.BINARY_SENSOR] -async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: - """ - Set up OmnikInverter as config entry. +async def async_setup_entry( + hass: HomeAssistant, entry: OmnikInverterConfigEntry +) -> bool: + """Set up OmnikInverter as config entry. Args: hass: The HomeAssistant instance. @@ -19,21 +23,21 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: Returns: Return true after setting up. - """ - hass.data.setdefault(DOMAIN, {}) + """ coordinator = OmnikInverterDataUpdateCoordinator(hass, entry) await coordinator.async_config_entry_first_refresh() - hass.data[DOMAIN][entry.entry_id] = coordinator + entry.runtime_data = coordinator await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True -async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: - """ - Unload a config entry. +async def async_unload_entry( + hass: HomeAssistant, entry: OmnikInverterConfigEntry +) -> bool: + """Unload a config entry. Args: hass: The HomeAssistant instance. @@ -41,29 +45,31 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: Returns: Return true if unload was successful, false otherwise. + """ - if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): - hass.data[DOMAIN].pop(entry.entry_id) - return unload_ok + return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) async def async_migrate_entry( - hass: HomeAssistant, config_entry: ConfigEntry # pylint: disable=unused-argument -): - """ - Migrate an old entry. + _hass: HomeAssistant, + config_entry: ConfigEntry, +) -> bool: + """Migrate an old entry. Args: - hass: The HomeAssistant instance. + _hass: The HomeAssistant instance. config_entry: The ConfigEntry containing the user input. Returns: Return false, not possible. + """ if config_entry.version <= 2: LOGGER.warning( - "Impossible to migrate config version from version %s to version %s.\r\nPlease consider to delete and re-add the integration.", # noqa: E501 + "Impossible to migrate config version from version %s to version %s." + "\r\nPlease consider to delete and re-add the integration.", config_entry.version, CONFIGFLOW_VERSION, ) return False + return False diff --git a/custom_components/omnik_inverter/__pycache__/__init__.cpython-311.pyc b/custom_components/omnik_inverter/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 7305bbe..0000000 Binary files a/custom_components/omnik_inverter/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/custom_components/omnik_inverter/__pycache__/binary_sensor.cpython-311.pyc b/custom_components/omnik_inverter/__pycache__/binary_sensor.cpython-311.pyc deleted file mode 100644 index d0b032e..0000000 Binary files a/custom_components/omnik_inverter/__pycache__/binary_sensor.cpython-311.pyc and /dev/null differ diff --git a/custom_components/omnik_inverter/__pycache__/config_flow.cpython-311.pyc b/custom_components/omnik_inverter/__pycache__/config_flow.cpython-311.pyc deleted file mode 100644 index 1d3a7f9..0000000 Binary files a/custom_components/omnik_inverter/__pycache__/config_flow.cpython-311.pyc and /dev/null differ diff --git a/custom_components/omnik_inverter/__pycache__/const.cpython-311.pyc b/custom_components/omnik_inverter/__pycache__/const.cpython-311.pyc deleted file mode 100644 index 59c2c8b..0000000 Binary files a/custom_components/omnik_inverter/__pycache__/const.cpython-311.pyc and /dev/null differ diff --git a/custom_components/omnik_inverter/__pycache__/coordinator.cpython-311.pyc b/custom_components/omnik_inverter/__pycache__/coordinator.cpython-311.pyc deleted file mode 100644 index 0a86663..0000000 Binary files a/custom_components/omnik_inverter/__pycache__/coordinator.cpython-311.pyc and /dev/null differ diff --git a/custom_components/omnik_inverter/__pycache__/diagnostics.cpython-311.pyc b/custom_components/omnik_inverter/__pycache__/diagnostics.cpython-311.pyc deleted file mode 100644 index b881dc0..0000000 Binary files a/custom_components/omnik_inverter/__pycache__/diagnostics.cpython-311.pyc and /dev/null differ diff --git a/custom_components/omnik_inverter/__pycache__/models.cpython-311.pyc b/custom_components/omnik_inverter/__pycache__/models.cpython-311.pyc deleted file mode 100644 index 6cf5b4c..0000000 Binary files a/custom_components/omnik_inverter/__pycache__/models.cpython-311.pyc and /dev/null differ diff --git a/custom_components/omnik_inverter/__pycache__/sensor.cpython-311.pyc b/custom_components/omnik_inverter/__pycache__/sensor.cpython-311.pyc deleted file mode 100644 index 92db620..0000000 Binary files a/custom_components/omnik_inverter/__pycache__/sensor.cpython-311.pyc and /dev/null differ diff --git a/custom_components/omnik_inverter/binary_sensor.py b/custom_components/omnik_inverter/binary_sensor.py index f063f4c..d639008 100644 --- a/custom_components/omnik_inverter/binary_sensor.py +++ b/custom_components/omnik_inverter/binary_sensor.py @@ -1,23 +1,27 @@ """Support for Omnik Inverter binary sensors.""" + from __future__ import annotations -from typing import Any +from typing import TYPE_CHECKING, Any -from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, BinarySensorEntity, BinarySensorEntityDescription, ) -from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import EntityCategory -from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.util import slugify -from .const import DOMAIN, SERVICE_DEVICE -from .coordinator import OmnikInverterDataUpdateCoordinator +from .const import SERVICE_DEVICE from .models import OmnikInverterEntity +if TYPE_CHECKING: + from homeassistant.core import HomeAssistant + from homeassistant.helpers.entity_platform import AddEntitiesCallback + + from . import OmnikInverterConfigEntry + from .coordinator import OmnikInverterDataUpdateCoordinator + BINARY_SENSORS: tuple[BinarySensorEntityDescription, ...] = ( BinarySensorEntityDescription( key="online", @@ -29,19 +33,19 @@ BINARY_SENSORS: tuple[BinarySensorEntityDescription, ...] = ( async def async_setup_entry( - hass: HomeAssistant, - entry: ConfigEntry, + _hass: HomeAssistant, + entry: OmnikInverterConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """ - Load all Omnik Inverter binary sensors. + """Load all Omnik Inverter binary sensors. Args: - hass: The HomeAssistant instance. + _hass: The HomeAssistant instance. entry: The ConfigEntry containing the user input. async_add_entities: The callback to provide the created entities to. + """ - coordinator = hass.data[DOMAIN][entry.entry_id] + coordinator = entry.runtime_data async_add_entities( OmnikInverterBinarySensor( @@ -58,7 +62,7 @@ class OmnikInverterBinarySensor(OmnikInverterEntity, BinarySensorEntity): """Defines an Omnik Inverter Binary Sensor.""" entity_description: BinarySensorEntityDescription - _options: dict[Any] + _options: dict[str, Any] def __init__( # pylint: disable=too-many-arguments self, @@ -66,28 +70,28 @@ class OmnikInverterBinarySensor(OmnikInverterEntity, BinarySensorEntity): name: str, description: BinarySensorEntityDescription, service: str, - ): - """ - Initialise the entity. + ) -> None: + """Initialise the entity. Args: coordinator: The data coordinator updating the models. name: The identifier for this entity. description: The entity description for the binary sensor. service: The service to create the binary sensor for. + """ super().__init__(coordinator=coordinator, name=name, service=service) self.entity_description = description - self.entity_id = f"{BINARY_SENSOR_DOMAIN}.{self._name}_{self.entity_description.key}" # noqa: E501 - self._attr_unique_id = f"{self._name}_{service}_{self.entity_description.key}" + self._attr_unique_id = slugify( + f"{self._name}_{service}_{self.entity_description.key}" + ) self._attr_name = self.entity_description.name @property def is_on(self) -> bool: - """ - Return True if the service is on. + """Return True if the service is on. Returns: True if the device is on. diff --git a/custom_components/omnik_inverter/config_flow.py b/custom_components/omnik_inverter/config_flow.py index 5349a94..df5d1e7 100644 --- a/custom_components/omnik_inverter/config_flow.py +++ b/custom_components/omnik_inverter/config_flow.py @@ -1,11 +1,17 @@ """Config flow for Omnik Inverter integration.""" + from __future__ import annotations import socket from typing import Any import voluptuous as vol -from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import ( CONF_HOST, CONF_NAME, @@ -14,12 +20,12 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.selector import ( TextSelector, TextSelectorConfig, TextSelectorType, ) + from omnikinverter import OmnikInverter, OmnikInverterError from .const import ( @@ -34,9 +40,12 @@ from .const import ( ) +class InvalidHostError(Exception): + """Exception raised when the host is invalid.""" + + async def validate_input(user_input: dict[str, Any]) -> str | None: - """ - Validate the given user input. + """Validate the given user input. Args: user_input: The user input. @@ -45,47 +54,48 @@ async def validate_input(user_input: dict[str, Any]) -> str | None: The host name of the inverter Raises: - Exception: If the host could not be validated. + InvalidHostError: If the host could not be validated. + """ host = user_input[CONF_HOST] try: return socket.gethostbyname(host) except socket.gaierror as exc: - raise Exception( # pylint: disable=broad-exception-raised - "invalid_host" - ) from exc + msg = "invalid_host" + raise InvalidHostError(msg) from exc -class OmnikInverterFlowHandler(ConfigFlow, domain=DOMAIN): +class OmnikInverterFlowHandler(ConfigFlow, domain=DOMAIN): # type: ignore[call-arg] """Config flow for Omnik Inverter.""" VERSION = CONFIGFLOW_VERSION - def __init__(self): + def __init__(self) -> None: """Initialize with empty source type.""" - self.source_type = None + self.source_type: str | None = None @staticmethod @callback def async_get_options_flow( - config_entry: ConfigEntry, + _config_entry: ConfigEntry, ) -> OmnikInverterOptionsFlowHandler: - """ - Get the options flow for this handler. + """Get the options flow for this handler. Args: - config_entry: The ConfigEntry instance. + _config_entry: The ConfigEntry instance. Returns: The created config flow. + """ - return OmnikInverterOptionsFlowHandler(config_entry) + return OmnikInverterOptionsFlowHandler() async def async_step_user( - self, user_input=None, errors: dict[str, str] | None = None - ) -> FlowResult: - """ - Handle a flow initialized by the user. + self, + user_input: dict[str, Any] | None = None, + errors: dict[str, str] | None = None, + ) -> ConfigFlowResult: + """Handle a flow initialized by the user. Args: user_input: The input received from the user or none. @@ -93,6 +103,7 @@ class OmnikInverterFlowHandler(ConfigFlow, domain=DOMAIN): Returns: The created config entry or a form to re-enter the user input with errors. + """ errors = {} if user_input is not None: @@ -113,15 +124,15 @@ class OmnikInverterFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_setup( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: - """ - Handle setup flow for the JS and JSON route. + ) -> ConfigFlowResult: + """Handle setup flow for the JS and JSON route. Args: user_input: The input received from the user or none. Returns: The created config entry or a form to re-enter the user input with errors. + """ errors = {} @@ -130,13 +141,13 @@ class OmnikInverterFlowHandler(ConfigFlow, domain=DOMAIN): await validate_input(user_input) async with OmnikInverter( host=user_input[CONF_HOST], - source_type=self.source_type, + source_type=self.source_type, # type: ignore[arg-type] ) as client: await client.inverter() except OmnikInverterError: LOGGER.exception("Failed to connect to the Omnik") errors["base"] = "cannot_connect" - except Exception as error: # pylint: disable=broad-exception-caught + except InvalidHostError as error: errors["base"] = str(error) else: return self.async_create_entry( @@ -162,15 +173,15 @@ class OmnikInverterFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_setup_html( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: - """ - Handle setup flow for the HTML`route. + ) -> ConfigFlowResult: + """Handle setup flow for the HTML route. Args: user_input: The input received from the user or none. Returns: The created config entry or a form to re-enter the user input with errors. + """ errors = {} @@ -179,7 +190,7 @@ class OmnikInverterFlowHandler(ConfigFlow, domain=DOMAIN): await validate_input(user_input) async with OmnikInverter( host=user_input[CONF_HOST], - source_type=self.source_type, + source_type=self.source_type, # type: ignore[arg-type] username=user_input[CONF_USERNAME], password=user_input[CONF_PASSWORD], ) as client: @@ -187,7 +198,7 @@ class OmnikInverterFlowHandler(ConfigFlow, domain=DOMAIN): except OmnikInverterError: LOGGER.exception("Failed to connect to the Omnik") errors["base"] = "cannot_connect" - except Exception as error: # pylint: disable=broad-exception-caught + except InvalidHostError as error: errors["base"] = str(error) else: return self.async_create_entry( @@ -217,15 +228,15 @@ class OmnikInverterFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_setup_tcp( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: - """ - Handle setup flow for the TCP route. + ) -> ConfigFlowResult: + """Handle setup flow for the TCP route. Args: user_input: The input received from the user or none. Returns: The created config entry or a form to re-enter the user input with errors. + """ errors = {} @@ -234,14 +245,14 @@ class OmnikInverterFlowHandler(ConfigFlow, domain=DOMAIN): await validate_input(user_input) async with OmnikInverter( host=user_input[CONF_HOST], - source_type=self.source_type, + source_type=self.source_type, # type: ignore[arg-type] serial_number=user_input[CONF_SERIAL], ) as client: await client.inverter() except OmnikInverterError: LOGGER.exception("Failed to connect to the Omnik") errors["base"] = "cannot_connect" - except Exception as error: # pylint: disable=broad-except + except InvalidHostError as error: errors["base"] = str(error) else: return self.async_create_entry( @@ -271,27 +282,17 @@ class OmnikInverterFlowHandler(ConfigFlow, domain=DOMAIN): class OmnikInverterOptionsFlowHandler(OptionsFlow): """Handle options.""" - def __init__(self, config_entry: ConfigEntry) -> None: - """ - Initialize options flow. - - Args: - config_entry: The ConfigEntry instance. - """ - self.config_entry = config_entry - self.source_type = config_entry.data[CONF_SOURCE_TYPE] - async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: - """ - Handle a flow initialized by the user. + ) -> ConfigFlowResult: + """Handle a flow initialized by the user. Args: user_input: The input received from the user or none. Returns: The created config entry. + """ errors = {} @@ -301,10 +302,12 @@ class OmnikInverterOptionsFlowHandler(OptionsFlow): except OmnikInverterError: LOGGER.exception("Failed to connect to the Omnik") errors["base"] = "cannot_connect" - except Exception as error: # pylint: disable=broad-except + except InvalidHostError as error: errors["base"] = str(error) else: - updated_config = {CONF_SOURCE_TYPE: self.source_type} + updated_config = { + CONF_SOURCE_TYPE: self.config_entry.data[CONF_SOURCE_TYPE] + } for key in (CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_SERIAL): if key in user_input: updated_config[key] = user_input[key] @@ -320,7 +323,7 @@ class OmnikInverterOptionsFlowHandler(OptionsFlow): options[key] = user_input[key] return self.async_create_entry(title="", data=options) - fields = { + fields: dict[Any, Any] = { vol.Optional( CONF_NAME, default=self.config_entry.title, @@ -331,7 +334,7 @@ class OmnikInverterOptionsFlowHandler(OptionsFlow): ): str, } - if self.source_type == "html": + if self.config_entry.data[CONF_SOURCE_TYPE] == "html": fields[ vol.Required( CONF_USERNAME, default=self.config_entry.data.get(CONF_USERNAME) @@ -342,7 +345,7 @@ class OmnikInverterOptionsFlowHandler(OptionsFlow): CONF_PASSWORD, default=self.config_entry.data.get(CONF_PASSWORD) ) ] = TextSelector(TextSelectorConfig(type=TextSelectorType.PASSWORD)) - elif self.source_type == "tcp": + elif self.config_entry.data[CONF_SOURCE_TYPE] == "tcp": fields[ vol.Required( CONF_SERIAL, default=self.config_entry.data.get(CONF_SERIAL) diff --git a/custom_components/omnik_inverter/const.py b/custom_components/omnik_inverter/const.py index 8f3643a..0899175 100644 --- a/custom_components/omnik_inverter/const.py +++ b/custom_components/omnik_inverter/const.py @@ -1,4 +1,5 @@ """Constants for the Omnik Inverter integration.""" + from __future__ import annotations import logging diff --git a/custom_components/omnik_inverter/coordinator.py b/custom_components/omnik_inverter/coordinator.py index a3c248a..6e07c3d 100644 --- a/custom_components/omnik_inverter/coordinator.py +++ b/custom_components/omnik_inverter/coordinator.py @@ -1,4 +1,5 @@ """Omnik Inverter platform configuration.""" + import logging from datetime import timedelta from typing import TypedDict @@ -8,6 +9,7 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + from omnikinverter import Device, Inverter, OmnikInverter from omnikinverter.exceptions import OmnikInverterAuthError, OmnikInverterError @@ -37,12 +39,12 @@ class OmnikInverterDataUpdateCoordinator(DataUpdateCoordinator): config_entry: ConfigEntry def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: - """ - Class to manage fetching Omnik Inverter data. + """Class to manage fetching Omnik Inverter data. Args: hass: The HomeAssistant instance. entry: The ConfigEntry containing the user input. + """ super().__init__( hass, @@ -73,8 +75,7 @@ class OmnikInverterDataUpdateCoordinator(DataUpdateCoordinator): ) async def _async_update_data(self) -> OmnikInverterData: - """ - Fetch data from the omnik inverter. + """Fetch data from the omnik inverter. This is the place to pre-process the data to lookup tables so entities can quickly look up their data. @@ -86,17 +87,17 @@ class OmnikInverterDataUpdateCoordinator(DataUpdateCoordinator): Raises: ConfigEntryAuthFailed: An invalid config was ued. UpdateFailed: An error occurred when updating the data. + """ try: data: OmnikInverterData = { SERVICE_INVERTER: await self.omnikinverter.inverter(), SERVICE_DEVICE: await self.omnikinverter.device(), } - return data except OmnikInverterAuthError as error: _LOGGER.exception("Failed to authenticate with the Omnik") raise ConfigEntryAuthFailed from error - except OmnikInverterError as error: _LOGGER.exception("Failed to connect to the Omnik") raise UpdateFailed(error) from error + return data diff --git a/custom_components/omnik_inverter/diagnostics.py b/custom_components/omnik_inverter/diagnostics.py index d5f2985..286ed79 100644 --- a/custom_components/omnik_inverter/diagnostics.py +++ b/custom_components/omnik_inverter/diagnostics.py @@ -1,25 +1,27 @@ """Diagnostics support for Omnik Inverter integration.""" + from __future__ import annotations from dataclasses import asdict -from typing import Any +from typing import TYPE_CHECKING, Any from homeassistant.components.diagnostics import async_redact_data -from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, CONF_IP_ADDRESS -from homeassistant.core import HomeAssistant -from . import OmnikInverterDataUpdateCoordinator -from .const import CONF_SERIAL, DOMAIN, SERVICE_DEVICE, SERVICE_INVERTER +from .const import CONF_SERIAL, SERVICE_DEVICE, SERVICE_INVERTER + +if TYPE_CHECKING: + from homeassistant.core import HomeAssistant + + from . import OmnikInverterConfigEntry TO_REDACT = {CONF_HOST, CONF_IP_ADDRESS, CONF_SERIAL} async def async_get_config_entry_diagnostics( - hass: HomeAssistant, entry: ConfigEntry + _hass: HomeAssistant, entry: OmnikInverterConfigEntry ) -> dict[str, Any]: - """ - Return diagnostics for a config entry. + """Return diagnostics for a config entry. Args: hass: The HomeAssistant instance. @@ -27,8 +29,9 @@ async def async_get_config_entry_diagnostics( Returns: The created diagnostics object. + """ - coordinator: OmnikInverterDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + coordinator = entry.runtime_data return { "entry": { diff --git a/custom_components/omnik_inverter/manifest.json b/custom_components/omnik_inverter/manifest.json index 4bcc72c..20b4ed1 100644 --- a/custom_components/omnik_inverter/manifest.json +++ b/custom_components/omnik_inverter/manifest.json @@ -12,5 +12,5 @@ "requirements": [ "omnikinverter==1.0.0" ], - "version": "2.6.4" + "version": "3.0.0" } diff --git a/custom_components/omnik_inverter/models.py b/custom_components/omnik_inverter/models.py index cb17ebc..97ca4e6 100644 --- a/custom_components/omnik_inverter/models.py +++ b/custom_components/omnik_inverter/models.py @@ -1,20 +1,19 @@ """Support for Omnik Inverter entities.""" + from __future__ import annotations from dataclasses import dataclass from homeassistant.components.sensor import SensorEntityDescription from homeassistant.helpers.device_registry import DeviceEntryType -from homeassistant.helpers.entity import DeviceInfo, Entity +from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN, MANUFACTURER, SERVICE_DEVICE, SERVICE_INVERTER from .coordinator import OmnikInverterDataUpdateCoordinator -class OmnikInverterEntity( - CoordinatorEntity[OmnikInverterDataUpdateCoordinator], Entity -): +class OmnikInverterEntity(CoordinatorEntity[OmnikInverterDataUpdateCoordinator]): """Defines an Omnik Inverter Entity.""" _name: str @@ -28,13 +27,13 @@ class OmnikInverterEntity( name: str, service: str, ) -> None: - """ - Initialise the entity. + """Initialise the entity. Args: coordinator: The data coordinator updating the models. name: The identifier for this entity. service: The service (Device or Inverter)/ + """ super().__init__(coordinator) self._name = name @@ -44,21 +43,21 @@ class OmnikInverterEntity( @property def device_info(self) -> DeviceInfo: - """ - Return information to link this entity with the correct device. + """Return information to link this entity with the correct device. Returns: The device identifiers to make sure the entity is attached to the correct device. + """ return DeviceInfo( identifiers={(DOMAIN, f"{self.entry_id}_{self.service}")}, - name=self.service.title(), + name=f"{self._name} {self.service.title()}", manufacturer=MANUFACTURER, entry_type=DeviceEntryType.SERVICE, model=self.coordinator.data[SERVICE_INVERTER].model, sw_version=self.coordinator.data[self.service].firmware, - configuration_url=f"http://{ self.coordinator.data[SERVICE_DEVICE].ip_address}", # noqa: E501 + configuration_url=f"http://{self.coordinator.data[SERVICE_DEVICE].ip_address}", ) diff --git a/custom_components/omnik_inverter/sensor.py b/custom_components/omnik_inverter/sensor.py index 31718b9..b9af241 100644 --- a/custom_components/omnik_inverter/sensor.py +++ b/custom_components/omnik_inverter/sensor.py @@ -1,17 +1,16 @@ """Support for Omnik Inverter sensors.""" + from __future__ import annotations import dataclasses -from typing import Any, Literal +from typing import TYPE_CHECKING, Any, Literal -from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, SensorEntityDescription, SensorStateClass, ) -from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( PERCENTAGE, UnitOfElectricCurrent, @@ -22,14 +21,21 @@ from homeassistant.const import ( UnitOfTemperature, UnitOfTime, ) -from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import EntityCategory -from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.util import slugify -from .const import DOMAIN, SERVICE_DEVICE, SERVICE_INVERTER -from .coordinator import OmnikInverterDataUpdateCoordinator +from .const import SERVICE_DEVICE, SERVICE_INVERTER from .models import OmnikInverterEntity, RangedSensorEntityDescription +if TYPE_CHECKING: + from collections.abc import Iterator + + from homeassistant.core import HomeAssistant + from homeassistant.helpers.entity_platform import AddEntitiesCallback + + from . import OmnikInverterConfigEntry + from .coordinator import OmnikInverterDataUpdateCoordinator + SENSORS: dict[Literal["inverter", "device"], tuple[SensorEntityDescription, ...]] = { SERVICE_DEVICE: ( SensorEntityDescription( @@ -162,23 +168,28 @@ SENSORS: dict[Literal["inverter", "device"], tuple[SensorEntityDescription, ...] async def async_setup_entry( - hass: HomeAssistant, - entry: ConfigEntry, + _hass: HomeAssistant, + entry: OmnikInverterConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """ - Load all Omnik Inverter sensors. + """Load all Omnik Inverter sensors. Args: - hass: The HomeAssistant instance. + _hass: The HomeAssistant instance. entry: The ConfigEntry containing the user input. async_add_entities: The callback to provide the created entities to. + """ - coordinator = hass.data[DOMAIN][entry.entry_id] + coordinator = entry.runtime_data options = entry.options - def create_sensor_entities(description, service): - if isinstance(description, RangedSensorEntityDescription): + def create_sensor_entities( + description: SensorEntityDescription, service: str + ) -> Iterator[OmnikInverterSensor]: + if ( + isinstance(description, RangedSensorEntityDescription) + and description.size is not None + ): for i in description.size: yield OmnikInverterRangedSensor( coordinator=coordinator, @@ -211,7 +222,7 @@ class OmnikInverterSensor(OmnikInverterEntity, SensorEntity): """Defines an Omnik Inverter Sensor.""" entity_description: SensorEntityDescription - _options: dict[Any] + _options: dict[str, Any] def __init__( # pylint: disable=too-many-arguments self, @@ -219,10 +230,9 @@ class OmnikInverterSensor(OmnikInverterEntity, SensorEntity): name: str, description: SensorEntityDescription, service: str, - options: dict[Any], - ): - """ - Initialise the entity. + options: dict[str, Any], + ) -> None: + """Initialise the entity. Args: coordinator: The data coordinator updating the models. @@ -230,27 +240,25 @@ class OmnikInverterSensor(OmnikInverterEntity, SensorEntity): description: The entity description for the sensor. service: The service to create the sensor for. options: The options provided by the user. + """ super().__init__(coordinator=coordinator, name=name, service=service) self.entity_description = description self._options = options - self.entity_id = ( - f"{SENSOR_DOMAIN}.{self._name}_{self.entity_description.key}" # noqa: E501 - ) - self._attr_unique_id = ( + self._attr_unique_id = slugify( f"{self.entry_id}_{service}_{self.entity_description.key}" ) - self._attr_name = self.entity_description.name + self._attr_name = f"{name} {self.entity_description.name}" @property def native_value(self) -> Any | None: - """ - Return the state of the sensor. + """Return the state of the sensor. Returns: The current state value of the sensor. + """ value = getattr( self.coordinator.data[self.service], self.entity_description.key @@ -268,17 +276,16 @@ class OmnikInverterRangedSensor(OmnikInverterSensor): _index: int _data_key: str - def __init__( # pylint: disable=too-many-arguments + def __init__( # noqa: PLR0913 # pylint: disable=too-many-arguments self, coordinator: OmnikInverterDataUpdateCoordinator, index: int, name: str, description: RangedSensorEntityDescription, service: str, - options: dict[Any], - ): - """ - Initialise the entity. + options: dict[str, Any], + ) -> None: + """Initialise the entity. Args: coordinator: The data coordinator updating the models. @@ -287,10 +294,14 @@ class OmnikInverterRangedSensor(OmnikInverterSensor): description: The entity description for the sensor. service: The service to create the sensor for. options: The options provided by the user. + """ self._index = index + if description.data_key is None: + msg = "data_key is required for RangedSensorEntityDescription" + raise TypeError(msg) self._data_key = description.data_key - description = dataclasses.replace( + description = dataclasses.replace( # type: ignore[call-arg] description, key=description.key.format(index + 1), name=description.name.format(index + 1), @@ -306,11 +317,11 @@ class OmnikInverterRangedSensor(OmnikInverterSensor): @property def native_value(self) -> Any | None: - """ - Return the state of the sensor. + """Return the state of the sensor. Returns: The current state value of the sensor. + """ value = getattr(self.coordinator.data[self.service], self._data_key) diff --git a/rest.yaml b/rest.yaml index 144ccb2..e69de29 100644 --- a/rest.yaml +++ b/rest.yaml @@ -1,52 +0,0 @@ - resource: https://api.awekas.at/current.php?key=rAOAW%2BhN93NdB9innyKqE%2Bd%2FIofSmhyZvoeVdiIUPcdzWOh7WSBdgdrBBBboyIKd51lUOh%2BCEQHMlgPoLlPpgA%3D%3D&lng=de - scan_interval: 120 - sensor: - - name: "Wetterstation" - json_attributes_path: "$.current" - value_template: "OK" - json_attributes: - - temperature - - windchill - - dewpoint - - humidity - - airpress_rel - - tendency - - precipitation - - rainrate - - itsraining - - windspeed - - gustspeed - - winddirection - - indoortemperature - - indoorhumidity - - wetbulbtemperature - - - name: "Wetterstation Vortag" - json_attributes_path: "$.day" - value_template: "OK" - json_attributes: - - temp_min - - temp_max - - dewpoint_min - - dewpoint_max - - hum_min - - hum_max - - airp_rel_min - - airp_rel_max - - windspeed_min - - windspeed_max - - winddir_max - - gustspeed_min - - gustspeed_max - - rainrate_max - - precipitation_24h - - solar_max - - gustspeed_max - - rainrate_max - - precipitation_24h - - - name: "Niederschlag letzte Stunde" - json_attributes_path: "$.1h" - value_template: "OK" - json_attributes: - - precipitation_1h diff --git a/sensors.yaml b/sensors.yaml index 2c0a3bf..0d4e75b 100644 --- a/sensors.yaml +++ b/sensors.yaml @@ -13,3 +13,29 @@ # attribute_templates: # state_class: total_increasing + - platform: snmp + name: "HPE Switch Uptime" + host: 192.168.0.4 + community: realm_local + baseoid: 1.3.6.1.2.1.1.3.0 + value_template: "{{ (value | float / 6000) | round(1) }}" + unit_of_measurement: "Minuten" + + - platform: snmp + name: "HPE Switch Port 1 Status" + host: 192.168.0.4 + community: realm_local + baseoid: 1.3.6.1.2.1.2.2.1.7.1 + value_template: > + {% if value == '1' %} + Connected + {% else %} + Disconnected + {% endif %} + + - platform: snmp + name: "HPE Switch Port 1 Traffic In" + host: 192.168.0.4 + community: realm_local + baseoid: 1.3.6.1.2.1.2.2.1.10.1 + unit_of_measurement: "Bytes" diff --git a/www/community/lovelace-mushroom/mushroom.js b/www/community/lovelace-mushroom/mushroom.js index 6434e47..23ed580 100644 --- a/www/community/lovelace-mushroom/mushroom.js +++ b/www/community/lovelace-mushroom/mushroom.js @@ -1,37 +1,37 @@ -var e,t,n,i,o,r,a,l,s,c,u,d,h,p,f,m,v,g,_,y,b,k,w,x,E,C,A,S,T,I,O,M,z,P,L,N,j,$,B,R,H,D,U,V,F,G,Y,K,W,q,X,Z,J,Q,ee,te,ne,ie,oe,re,ae,le,se,ce,ue,de,he,pe,fe,me,ve,ge,_e,ye,be,ke,we,xe,Ee,Ce,Ae,Se,Te,Ie,Oe,Me,ze,Pe,Le,Ne,je,$e,Be,Re,He,De,Ue,Ve,Fe,Ge,Ye,Ke,We,qe,Xe,Ze,Je,Qe,et,tt,nt,it,ot,rt,at,lt,st,ct,ut,dt,ht,pt,ft,mt,vt,gt,_t,yt,bt,kt,wt,xt,Et,Ct,At,St,Tt,It,Ot,Mt,zt,Pt,Lt,Nt,jt,$t,Bt,Rt,Ht,Dt,Ut,Vt,Ft,Gt,Yt,Kt,Wt,qt,Xt,Zt,Jt,Qt,en,tn,nn,on,rn,an,ln,sn,cn,un,dn,hn,pn,fn,mn,vn,gn,_n,yn,bn,kn,wn,xn,En,Cn,An,Sn,Tn,In,On,Mn,zn,Pn,Ln,Nn,jn,$n,Bn,Rn,Hn,Dn,Un,Vn,Fn,Gn,Yn,Kn,Wn,qn,Xn,Zn,Jn,Qn,ei,ti,ni,ii,oi,ri,ai,li,si,ci,ui,di,hi,pi,fi,mi,vi,gi,_i,yi,bi,ki,wi,xi,Ei,Ci,Ai,Si,Ti,Ii,Oi,Mi,zi,Pi,Li,Ni,ji,$i,Bi,Ri,Hi,Di,Ui,Vi,Fi,Gi,Yi,Ki,Wi,qi,Xi,Zi,Ji,Qi,eo,to,no,io,oo,ro,ao,lo,so,co,uo,ho,po,fo,mo,vo,go,_o,yo,bo,ko,wo,xo,Eo,Co,Ao,So,To,Io,Oo,Mo,zo,Po,Lo,No,jo,$o,Bo,Ro,Ho,Do,Uo,Vo,Fo,Go,Yo,Ko,Wo,qo,Xo,Zo,Jo,Qo,er,tr,nr,ir,or,rr,ar,lr,sr,cr,ur,dr,hr,pr,fr,mr,vr,gr,_r,yr,br,kr,wr,xr,Er,Cr,Ar,Sr,Tr,Ir,Or,Mr,zr=["message","explanation"];function Pr(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function Lr(e){return Yr(e)||Vr(e)||aa(e)||Gr()}function Nr(e){if(null!=e){var t=e["function"==typeof Symbol&&Symbol.iterator||"@@iterator"],n=0;if(t)return t.call(e);if("function"==typeof e.next)return e;if(!isNaN(e.length))return{next:function(){return e&&n>=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}}}throw new TypeError(ha(e)+" is not iterable")}var jr=Kr().m(es);function $r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);t&&(i=i.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,i)}return n}function Br(e){for(var t=1;t3?(o=f===i)&&(s=r[(l=r[4])?5:(l=3,3)],r[4]=r[5]=e):r[0]<=p&&((o=n<2&&pi||i>f)&&(r[4]=n,r[5]=i,h.n=f,l=0))}if(o||n>1)return a;throw d=!0,i}return function(o,u,f){if(c>1)throw TypeError("Generator is already running");for(d&&1===u&&p(u,f),l=u,s=f;(t=l<2?e:s)||!d;){r||(l?l<3?(l>1&&(h.n=-1),p(l,s)):h.n=s:h.v=s);try{if(c=2,r){if(l||(o="next"),t=r[o]){if(!(t=t.call(r,s)))throw TypeError("iterator result is not an object");if(!t.done)return t;s=t.value,l<2&&(l=0)}else 1===l&&(t=r.return)&&t.call(r),l<2&&(s=TypeError("The iterator does not provide a '"+o+"' method"),l=1);r=e}else if((t=(d=h.n<0)?s:n.call(i,h))!==a)break}catch(t){r=e,l=1,s=t}finally{c=1}}return{value:t,done:d}}}(n,o,r),!0),c}var a={};function l(){}function s(){}function c(){}t=Object.getPrototypeOf;var u=[][i]?t(t([][i]())):(Wr(t={},i,(function(){return this})),t),d=c.prototype=l.prototype=Object.create(u);function h(e){return Object.setPrototypeOf?Object.setPrototypeOf(e,c):(e.__proto__=c,Wr(e,o,"GeneratorFunction")),e.prototype=Object.create(d),e}return s.prototype=c,Wr(d,"constructor",c),Wr(c,"constructor",s),s.displayName="GeneratorFunction",Wr(c,o,"GeneratorFunction"),Wr(d),Wr(d,o,"Generator"),Wr(d,i,(function(){return this})),Wr(d,"toString",(function(){return"[object Generator]"})),(Kr=function(){return{w:r,m:h}})()}function Wr(e,t,n,i){var o=Object.defineProperty;try{o({},"",{})}catch(e){o=0}Wr=function(e,t,n,i){function r(t,n){Wr(e,t,(function(e){return this._invoke(t,n,e)}))}t?o?o(e,t,{value:n,enumerable:!i,configurable:!i,writable:!i}):e[t]=n:(r("next",0),r("throw",1),r("return",2))},Wr(e,t,n,i)}function qr(e,t,n,i,o,r,a){try{var l=e[r](a),s=l.value}catch(e){return void n(e)}l.done?t(s):Promise.resolve(s).then(i,o)}function Xr(e){return function(){var t=this,n=arguments;return new Promise((function(i,o){var r=e.apply(t,n);function a(e){qr(r,i,o,a,l,"next",e)}function l(e){qr(r,i,o,a,l,"throw",e)}a(void 0)}))}}function Zr(e,t,n){return t=oa(t),Jr(e,na()?Reflect.construct(t,n||[],oa(e).constructor):t.apply(e,n))}function Jr(e,t){if(t&&("object"==ha(t)||"function"==typeof t))return t;if(void 0!==t)throw new TypeError("Derived constructors may only return object or undefined");return Qr(e)}function Qr(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function ea(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),Object.defineProperty(e,"prototype",{writable:!1}),t&&ia(e,t)}function ta(e){var t="function"==typeof Map?new Map:void 0;return ta=function(e){if(null===e||!function(e){try{return-1!==Function.toString.call(e).indexOf("[native code]")}catch(t){return"function"==typeof e}}(e))return e;if("function"!=typeof e)throw new TypeError("Super expression must either be null or a function");if(void 0!==t){if(t.has(e))return t.get(e);t.set(e,n)}function n(){return function(e,t,n){if(na())return Reflect.construct.apply(null,arguments);var i=[null];i.push.apply(i,t);var o=new(e.bind.apply(e,i));return n&&ia(o,n.prototype),o}(e,arguments,oa(this).constructor)}return n.prototype=Object.create(e.prototype,{constructor:{value:n,enumerable:!1,writable:!0,configurable:!0}}),ia(n,e)},ta(e)}function na(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){})))}catch(e){}return(na=function(){return!!e})()}function ia(e,t){return ia=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e},ia(e,t)}function oa(e){return oa=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(e){return e.__proto__||Object.getPrototypeOf(e)},oa(e)}function ra(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(!n){if(Array.isArray(e)||(n=aa(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var i=0,o=function(){};return{s:o,n:function(){return i>=e.length?{done:!0}:{done:!1,value:e[i++]}},e:function(e){throw e},f:o}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var r,a=!0,l=!1;return{s:function(){n=n.call(e)},n:function(){var e=n.next();return a=e.done,e},e:function(e){l=!0,r=e},f:function(){try{a||null==n.return||n.return()}finally{if(l)throw r}}}}function aa(e,t){if(e){if("string"==typeof e)return la(e,t);var n={}.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?la(e,t):void 0}}function la(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,i=Array(t);n=0;l--)(o=e[l])&&(a=(r<3?o(a):r>3?o(t,n,a):o(t,n))||a);return r>3&&a&&Object.defineProperty(t,n,a),a}function _a(e){var t="function"==typeof Symbol&&Symbol.iterator,n=t&&e[t],i=0;if(n)return n.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&i>=e.length&&(e=void 0),{value:e&&e[i++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function ya(e,t,n){if(n||2===arguments.length)for(var i,o=0,r=t.length;o=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}}}throw new TypeError(mr(t)+" is not iterable")}var Ri=Zi().m(ds);function Ui(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(t);e&&(o=o.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,o)}return n}function Vi(t){for(var e=1;e3?(i=f===o)&&(l=r[(s=r[4])?5:(s=3,3)],r[4]=r[5]=t):r[0]<=p&&((i=n<2&&po||o>f)&&(r[4]=n,r[5]=o,d.n=f,s=0))}if(i||n>1)return a;throw h=!0,o}return function(i,u,f){if(c>1)throw TypeError("Generator is already running");for(h&&1===u&&p(u,f),s=u,l=f;(e=s<2?t:l)||!h;){r||(s?s<3?(s>1&&(d.n=-1),p(s,l)):d.n=l:d.v=l);try{if(c=2,r){if(s||(i="next"),e=r[i]){if(!(e=e.call(r,l)))throw TypeError("iterator result is not an object");if(!e.done)return e;l=e.value,s<2&&(s=0)}else 1===s&&(e=r.return)&&e.call(r),s<2&&(l=TypeError("The iterator does not provide a '"+i+"' method"),s=1);r=t}else if((e=(h=d.n<0)?l:n.call(o,d))!==a)break}catch(e){r=t,s=1,l=e}finally{c=1}}return{value:e,done:h}}}(n,i,r),!0),c}var a={};function s(){}function l(){}function c(){}e=Object.getPrototypeOf;var u=[][o]?e(e([][o]())):(Ji(e={},o,(function(){return this})),e),h=c.prototype=s.prototype=Object.create(u);function d(t){return Object.setPrototypeOf?Object.setPrototypeOf(t,c):(t.__proto__=c,Ji(t,i,"GeneratorFunction")),t.prototype=Object.create(h),t}return l.prototype=c,Ji(h,"constructor",c),Ji(c,"constructor",l),l.displayName="GeneratorFunction",Ji(c,i,"GeneratorFunction"),Ji(h),Ji(h,i,"Generator"),Ji(h,o,(function(){return this})),Ji(h,"toString",(function(){return"[object Generator]"})),(Zi=function(){return{w:r,m:d}})()}function Ji(t,e,n,o){var i=Object.defineProperty;try{i({},"",{})}catch(t){i=0}Ji=function(t,e,n,o){function r(e,n){Ji(t,e,(function(t){return this._invoke(e,n,t)}))}e?i?i(t,e,{value:n,enumerable:!o,configurable:!o,writable:!o}):t[e]=n:(r("next",0),r("throw",1),r("return",2))},Ji(t,e,n,o)}function Qi(t,e,n,o,i,r,a){try{var s=t[r](a),l=s.value}catch(t){return void n(t)}s.done?e(l):Promise.resolve(l).then(o,i)}function tr(t){return function(){var e=this,n=arguments;return new Promise((function(o,i){var r=t.apply(e,n);function a(t){Qi(r,o,i,a,s,"next",t)}function s(t){Qi(r,o,i,a,s,"throw",t)}a(void 0)}))}}function er(t,e,n){return e=sr(e),function(t,e){if(e&&("object"==mr(e)||"function"==typeof e))return e;if(void 0!==e)throw new TypeError("Derived constructors may only return object or undefined");return nr(t)}(t,rr()?Reflect.construct(e,n||[],sr(t).constructor):e.apply(t,n))}function nr(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}function or(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function");t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),Object.defineProperty(t,"prototype",{writable:!1}),e&&ar(t,e)}function ir(t){var e="function"==typeof Map?new Map:void 0;return ir=function(t){if(null===t||!function(t){try{return-1!==Function.toString.call(t).indexOf("[native code]")}catch(e){return"function"==typeof t}}(t))return t;if("function"!=typeof t)throw new TypeError("Super expression must either be null or a function");if(void 0!==e){if(e.has(t))return e.get(t);e.set(t,n)}function n(){return function(t,e,n){if(rr())return Reflect.construct.apply(null,arguments);var o=[null];o.push.apply(o,e);var i=new(t.bind.apply(t,o));return n&&ar(i,n.prototype),i}(t,arguments,sr(this).constructor)}return n.prototype=Object.create(t.prototype,{constructor:{value:n,enumerable:!1,writable:!0,configurable:!0}}),ar(n,t)},ir(t)}function rr(){try{var t=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){})))}catch(t){}return(rr=function(){return!!t})()}function ar(t,e){return ar=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,e){return t.__proto__=e,t},ar(t,e)}function sr(t){return sr=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(t){return t.__proto__||Object.getPrototypeOf(t)},sr(t)}function lr(t,e){var n="undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(!n){if(Array.isArray(t)||(n=cr(t))||e&&t&&"number"==typeof t.length){n&&(t=n);var o=0,i=function(){};return{s:i,n:function(){return o>=t.length?{done:!0}:{done:!1,value:t[o++]}},e:function(t){throw t},f:i}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var r,a=!0,s=!1;return{s:function(){n=n.call(t)},n:function(){var t=n.next();return a=t.done,t},e:function(t){s=!0,r=t},f:function(){try{a||null==n.return||n.return()}finally{if(s)throw r}}}}function cr(t,e){if(t){if("string"==typeof t)return ur(t,e);var n={}.toString.call(t).slice(8,-1);return"Object"===n&&t.constructor&&(n=t.constructor.name),"Map"===n||"Set"===n?Array.from(t):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?ur(t,e):void 0}}function ur(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,o=Array(e);n=0;s--)(i=t[s])&&(a=(r<3?i(a):r>3?i(e,n,a):i(e,n))||a);return r>3&&a&&Object.defineProperty(e,n,a),a}function kr(t,e,n){if(n||2===arguments.length)for(var o,i=0,r=e.length;i1?t-1:0),i=1;i0&&(this._$Ep=t)}},{key:"createRenderRoot",value:function(){var e,t=null!==(e=this.shadowRoot)&&void 0!==e?e:this.attachShadow(this.constructor.shadowRootOptions);return function(e,t){if(ka)e.adoptedStyleSheets=t.map((function(e){return e instanceof CSSStyleSheet?e:e.styleSheet}));else{var n,i=ra(t);try{for(i.s();!(n=i.n()).done;){var o=n.value,r=document.createElement("style"),a=ba.litNonce;void 0!==a&&r.setAttribute("nonce",a),r.textContent=o.cssText,e.appendChild(r)}}catch(e){i.e(e)}finally{i.f()}}}(t,this.constructor.elementStyles),t}},{key:"connectedCallback",value:function(){var e,t;null!==(e=this.renderRoot)&&void 0!==e||(this.renderRoot=this.createRenderRoot()),this.enableUpdating(!0),null===(t=this._$EO)||void 0===t||t.forEach((function(e){var t;return null===(t=e.hostConnected)||void 0===t?void 0:t.call(e)}))}},{key:"enableUpdating",value:function(e){}},{key:"disconnectedCallback",value:function(){var e;null===(e=this._$EO)||void 0===e||e.forEach((function(e){var t;return null===(t=e.hostDisconnected)||void 0===t?void 0:t.call(e)}))}},{key:"attributeChangedCallback",value:function(e,t,n){this._$AK(e,n)}},{key:"_$ET",value:function(e,t){var n=this.constructor.elementProperties.get(e),i=this.constructor._$Eu(e,n);if(void 0!==i&&!0===n.reflect){var o,r=(void 0!==(null===(o=n.converter)||void 0===o?void 0:o.toAttribute)?n.converter:Ra).toAttribute(t,n.type);this._$Em=e,null==r?this.removeAttribute(i):this.setAttribute(i,r),this._$Em=null}}},{key:"_$AK",value:function(e,t){var n=this.constructor,i=n._$Eh.get(e);if(void 0!==i&&this._$Em!==i){var o,r,a,l=n.getPropertyOptions(i),s="function"==typeof l.converter?{fromAttribute:l.converter}:void 0!==(null===(o=l.converter)||void 0===o?void 0:o.fromAttribute)?l.converter:Ra;this._$Em=i;var c=s.fromAttribute(t,l.type);this[i]=null!==(r=null!=c?c:null===(a=this._$Ej)||void 0===a?void 0:a.get(i))&&void 0!==r?r:c,this._$Em=null}}},{key:"requestUpdate",value:function(e,t,n){if(void 0!==e){var i,o,r=this.constructor,a=this[e];if(null!=n||(n=r.getPropertyOptions(e)),!((null!==(i=n.hasChanged)&&void 0!==i?i:Ha)(a,t)||n.useDefault&&n.reflect&&a===(null===(o=this._$Ej)||void 0===o?void 0:o.get(e))&&!this.hasAttribute(r._$Eu(e,n))))return;this.C(e,t,n)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}},{key:"C",value:function(e,t,n,i){var o,r,a,l=n.useDefault,s=n.reflect,c=n.wrapped;l&&!(null!==(o=this._$Ej)&&void 0!==o?o:this._$Ej=new Map).has(e)&&(this._$Ej.set(e,null!==(r=null!=i?i:t)&&void 0!==r?r:this[e]),!0!==c||void 0!==i)||(this._$AL.has(e)||(this.hasUpdated||l||(t=void 0),this._$AL.set(e,t)),!0===s&&this._$Em!==e&&(null!==(a=this._$Eq)&&void 0!==a?a:this._$Eq=new Set).add(e))}},{key:"_$EP",value:(n=Xr(Kr().m((function e(){var t,n;return Kr().w((function(e){for(;;)switch(e.p=e.n){case 0:return this.isUpdatePending=!0,e.p=1,e.n=2,this._$ES;case 2:e.n=4;break;case 3:e.p=3,n=e.v,Promise.reject(n);case 4:if(null==(t=this.scheduleUpdate())){e.n=5;break}return e.n=5,t;case 5:return e.a(2,!this.isUpdatePending)}}),e,this,[[1,3]])}))),function(){return n.apply(this,arguments)})},{key:"scheduleUpdate",value:function(){return this.performUpdate()}},{key:"performUpdate",value:function(){if(this.isUpdatePending){if(!this.hasUpdated){var e;if(null!==(e=this.renderRoot)&&void 0!==e||(this.renderRoot=this.createRenderRoot()),this._$Ep){var t,n=ra(this._$Ep);try{for(n.s();!(t=n.n()).done;){var i=Fr(t.value,2),o=i[0],r=i[1];this[o]=r}}catch(e){n.e(e)}finally{n.f()}this._$Ep=void 0}var a=this.constructor.elementProperties;if(a.size>0){var l,s=ra(a);try{for(s.s();!(l=s.n()).done;){var c=Fr(l.value,2),u=c[0],d=c[1],h=d.wrapped,p=this[u];!0!==h||this._$AL.has(u)||void 0===p||this.C(u,void 0,d,p)}}catch(e){s.e(e)}finally{s.f()}}}var f=!1,m=this._$AL;try{var v;(f=this.shouldUpdate(m))?(this.willUpdate(m),null!==(v=this._$EO)&&void 0!==v&&v.forEach((function(e){var t;return null===(t=e.hostUpdate)||void 0===t?void 0:t.call(e)})),this.update(m)):this._$EM()}catch(m){throw f=!1,this._$EM(),m}f&&this._$AE(m)}}},{key:"willUpdate",value:function(e){}},{key:"_$AE",value:function(e){var t;null!==(t=this._$EO)&&void 0!==t&&t.forEach((function(e){var t;return null===(t=e.hostUpdated)||void 0===t?void 0:t.call(e)})),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(e)),this.updated(e)}},{key:"_$EM",value:function(){this._$AL=new Map,this.isUpdatePending=!1}},{key:"updateComplete",get:function(){return this.getUpdateComplete()}},{key:"getUpdateComplete",value:function(){return this._$ES}},{key:"shouldUpdate",value:function(e){return!0}},{key:"update",value:function(e){var t=this;this._$Eq&&(this._$Eq=this._$Eq.forEach((function(e){return t._$ET(e,t[e])}))),this._$EM()}},{key:"updated",value:function(e){}},{key:"firstUpdated",value:function(e){}}],[{key:"addInitializer",value:function(e){var t;this._$Ei(),(null!==(t=this.l)&&void 0!==t?t:this.l=[]).push(e)}},{key:"observedAttributes",get:function(){return this.finalize(),this._$Eh&&Ur(this._$Eh.keys())}},{key:"createProperty",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:Da;if(t.state&&(t.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(e)&&((t=Object.create(t)).wrapped=!0),this.elementProperties.set(e,t),!t.noAccessor){var n=Symbol(),i=this.getPropertyDescriptor(e,n,t);void 0!==i&&Ia(this.prototype,e,i)}}},{key:"getPropertyDescriptor",value:function(e,t,n){var i,o=null!==(i=Oa(this.prototype,e))&&void 0!==i?i:{get:function(){return this[t]},set:function(e){this[t]=e}},r=o.get,a=o.set;return{get:r,set:function(t){var i=null==r?void 0:r.call(this);null!=a&&a.call(this,t),this.requestUpdate(e,i,n)},configurable:!0,enumerable:!0}}},{key:"getPropertyOptions",value:function(e){var t;return null!==(t=this.elementProperties.get(e))&&void 0!==t?t:Da}},{key:"_$Ei",value:function(){if(!this.hasOwnProperty(Ba("elementProperties"))){var e=Pa(this);e.finalize(),void 0!==e.l&&(this.l=Ur(e.l)),this.elementProperties=new Map(e.elementProperties)}}},{key:"finalize",value:function(){if(!this.hasOwnProperty(Ba("finalized"))){if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(Ba("properties"))){var e,t=this.properties,n=ra([].concat(Ur(Ma(t)),Ur(za(t))));try{for(n.s();!(e=n.n()).done;){var i=e.value;this.createProperty(i,t[i])}}catch(e){n.e(e)}finally{n.f()}}var o=this[Symbol.metadata];if(null!==o){var r=litPropertyMetadata.get(o);if(void 0!==r){var a,l=ra(r);try{for(l.s();!(a=l.n()).done;){var s=Fr(a.value,2),c=s[0],u=s[1];this.elementProperties.set(c,u)}}catch(e){l.e(e)}finally{l.f()}}}this._$Eh=new Map;var d,h=ra(this.elementProperties);try{for(h.s();!(d=h.n()).done;){var p=Fr(d.value,2),f=p[0],m=p[1],v=this._$Eu(f,m);void 0!==v&&this._$Eh.set(v,f)}}catch(e){h.e(e)}finally{h.f()}this.elementStyles=this.finalizeStyles(this.styles)}}},{key:"finalizeStyles",value:function(e){var t=[];if(Array.isArray(e)){var n,i=ra(new Set(e.flat(1/0).reverse()));try{for(i.s();!(n=i.n()).done;){var o=n.value;t.unshift(Sa(o))}}catch(e){i.e(e)}finally{i.f()}}else void 0!==e&&t.push(Sa(e));return t}},{key:"_$Eu",value:function(e,t){var n=t.attribute;return!1===n?void 0:"string"==typeof n?n:"string"==typeof e?e.toLowerCase():void 0}}]);var n}();Ua.elementStyles=[],Ua.shadowRootOptions={mode:"open"},Ua[Ba("elementProperties")]=new Map,Ua[Ba("finalized")]=new Map,null!=$a&&$a({ReactiveElement:Ua}),(null!==(n=La.reactiveElementVersions)&&void 0!==n?n:La.reactiveElementVersions=[]).push("2.1.1"); +var wr=globalThis,Cr=wr.ShadowRoot&&(void 0===wr.ShadyCSS||wr.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,Er=Symbol(),xr=new WeakMap,Ar=function(){return pr((function t(e,n,o){if(hr(this,t),this._$cssResult$=!0,o!==Er)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=e,this.t=n}),[{key:"styleSheet",get:function(){var t=this.o,e=this.t;if(Cr&&void 0===t){var n=void 0!==e&&1===e.length;n&&(t=xr.get(e)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),n&&xr.set(e,t))}return t}},{key:"toString",value:function(){return this.cssText}}])}(),Sr=function(t){return new Ar("string"==typeof t?t:t+"",void 0,Er)},Tr=function(t){for(var e=arguments.length,n=new Array(e>1?e-1:0),o=1;o0&&(this._$Ep=e)}},{key:"createRenderRoot",value:function(){var t,e=null!==(t=this.shadowRoot)&&void 0!==t?t:this.attachShadow(this.constructor.shadowRootOptions);return function(t,e){if(Cr)t.adoptedStyleSheets=e.map((function(t){return t instanceof CSSStyleSheet?t:t.styleSheet}));else{var n,o=lr(e);try{for(o.s();!(n=o.n()).done;){var i=n.value,r=document.createElement("style"),a=wr.litNonce;void 0!==a&&r.setAttribute("nonce",a),r.textContent=i.cssText,t.appendChild(r)}}catch(t){o.e(t)}finally{o.f()}}}(e,this.constructor.elementStyles),e}},{key:"connectedCallback",value:function(){var t,e;null!==(t=this.renderRoot)&&void 0!==t||(this.renderRoot=this.createRenderRoot()),this.enableUpdating(!0),null===(e=this._$EO)||void 0===e||e.forEach((function(t){var e;return null===(e=t.hostConnected)||void 0===e?void 0:e.call(t)}))}},{key:"enableUpdating",value:function(t){}},{key:"disconnectedCallback",value:function(){var t;null===(t=this._$EO)||void 0===t||t.forEach((function(t){var e;return null===(e=t.hostDisconnected)||void 0===e?void 0:e.call(t)}))}},{key:"attributeChangedCallback",value:function(t,e,n){this._$AK(t,n)}},{key:"_$ET",value:function(t,e){var n=this.constructor.elementProperties.get(t),o=this.constructor._$Eu(t,n);if(void 0!==o&&!0===n.reflect){var i,r=(void 0!==(null===(i=n.converter)||void 0===i?void 0:i.toAttribute)?n.converter:Ur).toAttribute(e,n.type);this._$Em=t,null==r?this.removeAttribute(o):this.setAttribute(o,r),this._$Em=null}}},{key:"_$AK",value:function(t,e){var n=this.constructor,o=n._$Eh.get(t);if(void 0!==o&&this._$Em!==o){var i,r,a,s=n.getPropertyOptions(o),l="function"==typeof s.converter?{fromAttribute:s.converter}:void 0!==(null===(i=s.converter)||void 0===i?void 0:i.fromAttribute)?s.converter:Ur;this._$Em=o;var c=l.fromAttribute(e,s.type);this[o]=null!==(r=null!=c?c:null===(a=this._$Ej)||void 0===a?void 0:a.get(o))&&void 0!==r?r:c,this._$Em=null}}},{key:"requestUpdate",value:function(t,e,n){if(void 0!==t){var o,i,r=this.constructor,a=this[t];if(null!=n||(n=r.getPropertyOptions(t)),!((null!==(o=n.hasChanged)&&void 0!==o?o:Vr)(a,e)||n.useDefault&&n.reflect&&a===(null===(i=this._$Ej)||void 0===i?void 0:i.get(t))&&!this.hasAttribute(r._$Eu(t,n))))return;this.C(t,e,n)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}},{key:"C",value:function(t,e,n,o){var i,r,a,s=n.useDefault,l=n.reflect,c=n.wrapped;s&&!(null!==(i=this._$Ej)&&void 0!==i?i:this._$Ej=new Map).has(t)&&(this._$Ej.set(t,null!==(r=null!=o?o:e)&&void 0!==r?r:this[t]),!0!==c||void 0!==o)||(this._$AL.has(t)||(this.hasUpdated||s||(e=void 0),this._$AL.set(t,e)),!0===l&&this._$Em!==t&&(null!==(a=this._$Eq)&&void 0!==a?a:this._$Eq=new Set).add(t))}},{key:"_$EP",value:(n=tr(Zi().m((function t(){var e,n;return Zi().w((function(t){for(;;)switch(t.p=t.n){case 0:return this.isUpdatePending=!0,t.p=1,t.n=2,this._$ES;case 2:t.n=4;break;case 3:t.p=3,n=t.v,Promise.reject(n);case 4:if(null==(e=this.scheduleUpdate())){t.n=5;break}return t.n=5,e;case 5:return t.a(2,!this.isUpdatePending)}}),t,this,[[1,3]])}))),function(){return n.apply(this,arguments)})},{key:"scheduleUpdate",value:function(){return this.performUpdate()}},{key:"performUpdate",value:function(){if(this.isUpdatePending){if(!this.hasUpdated){var t;if(null!==(t=this.renderRoot)&&void 0!==t||(this.renderRoot=this.createRenderRoot()),this._$Ep){var e,n=lr(this._$Ep);try{for(n.s();!(e=n.n()).done;){var o=qi(e.value,2),i=o[0],r=o[1];this[i]=r}}catch(t){n.e(t)}finally{n.f()}this._$Ep=void 0}var a=this.constructor.elementProperties;if(a.size>0){var s,l=lr(a);try{for(l.s();!(s=l.n()).done;){var c=qi(s.value,2),u=c[0],h=c[1],d=h.wrapped,p=this[u];!0!==d||this._$AL.has(u)||void 0===p||this.C(u,void 0,h,p)}}catch(t){l.e(t)}finally{l.f()}}}var f=!1,m=this._$AL;try{var v;(f=this.shouldUpdate(m))?(this.willUpdate(m),null!==(v=this._$EO)&&void 0!==v&&v.forEach((function(t){var e;return null===(e=t.hostUpdate)||void 0===e?void 0:e.call(t)})),this.update(m)):this._$EM()}catch(m){throw f=!1,this._$EM(),m}f&&this._$AE(m)}}},{key:"willUpdate",value:function(t){}},{key:"_$AE",value:function(t){var e;null!==(e=this._$EO)&&void 0!==e&&e.forEach((function(t){var e;return null===(e=t.hostUpdated)||void 0===e?void 0:e.call(t)})),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}},{key:"_$EM",value:function(){this._$AL=new Map,this.isUpdatePending=!1}},{key:"updateComplete",get:function(){return this.getUpdateComplete()}},{key:"getUpdateComplete",value:function(){return this._$ES}},{key:"shouldUpdate",value:function(t){return!0}},{key:"update",value:function(t){var e=this;this._$Eq&&(this._$Eq=this._$Eq.forEach((function(t){return e._$ET(t,e[t])}))),this._$EM()}},{key:"updated",value:function(t){}},{key:"firstUpdated",value:function(t){}}],[{key:"addInitializer",value:function(t){var e;this._$Ei(),(null!==(e=this.l)&&void 0!==e?e:this.l=[]).push(t)}},{key:"observedAttributes",get:function(){return this.finalize(),this._$Eh&&Ki(this._$Eh.keys())}},{key:"createProperty",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:Fr;if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(t,e),!e.noAccessor){var n=Symbol(),o=this.getPropertyDescriptor(t,n,e);void 0!==o&&Or(this.prototype,t,o)}}},{key:"getPropertyDescriptor",value:function(t,e,n){var o,i=null!==(o=Ir(this.prototype,t))&&void 0!==o?o:{get:function(){return this[e]},set:function(t){this[e]=t}},r=i.get,a=i.set;return{get:r,set:function(e){var o=null==r?void 0:r.call(this);null!=a&&a.call(this,e),this.requestUpdate(t,o,n)},configurable:!0,enumerable:!0}}},{key:"getPropertyOptions",value:function(t){var e;return null!==(e=this.elementProperties.get(t))&&void 0!==e?e:Fr}},{key:"_$Ei",value:function(){if(!this.hasOwnProperty(Rr("elementProperties"))){var t=Nr(this);t.finalize(),void 0!==t.l&&(this.l=Ki(t.l)),this.elementProperties=new Map(t.elementProperties)}}},{key:"finalize",value:function(){if(!this.hasOwnProperty(Rr("finalized"))){if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(Rr("properties"))){var t,e=this.properties,n=lr([].concat(Ki(jr(e)),Ki(Pr(e))));try{for(n.s();!(t=n.n()).done;){var o=t.value;this.createProperty(o,e[o])}}catch(t){n.e(t)}finally{n.f()}}var i=this[Symbol.metadata];if(null!==i){var r=litPropertyMetadata.get(i);if(void 0!==r){var a,s=lr(r);try{for(s.s();!(a=s.n()).done;){var l=qi(a.value,2),c=l[0],u=l[1];this.elementProperties.set(c,u)}}catch(t){s.e(t)}finally{s.f()}}}this._$Eh=new Map;var h,d=lr(this.elementProperties);try{for(d.s();!(h=d.n()).done;){var p=qi(h.value,2),f=p[0],m=p[1],v=this._$Eu(f,m);void 0!==v&&this._$Eh.set(v,f)}}catch(t){d.e(t)}finally{d.f()}this.elementStyles=this.finalizeStyles(this.styles)}}},{key:"finalizeStyles",value:function(t){var e=[];if(Array.isArray(t)){var n,o=lr(new Set(t.flat(1/0).reverse()));try{for(o.s();!(n=o.n()).done;){var i=n.value;e.unshift(Mr(i))}}catch(t){o.e(t)}finally{o.f()}}else void 0!==t&&e.push(Mr(t));return e}},{key:"_$Eu",value:function(t,e){var n=e.attribute;return!1===n?void 0:"string"==typeof n?n:"string"==typeof t?t.toLowerCase():void 0}}]);var n}();$r.elementStyles=[],$r.shadowRootOptions={mode:"open"},$r[Rr("elementProperties")]=new Map,$r[Rr("finalized")]=new Map,null!=Dr&&Dr({ReactiveElement:$r}),(null!==(n=Br.reactiveElementVersions)&&void 0!==n?n:Br.reactiveElementVersions=[]).push("2.1.1"); /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause */ -var Va=globalThis,Fa=Va.trustedTypes,Ga=Fa?Fa.createPolicy("lit-html",{createHTML:function(e){return e}}):void 0,Ya="$lit$",Ka="lit$".concat(Math.random().toFixed(9).slice(2),"$"),Wa="?"+Ka,qa="<".concat(Wa,">"),Xa=document,Za=function(){return Xa.createComment("")},Ja=function(e){return null===e||"object"!=ha(e)&&"function"!=typeof e},Qa=Array.isArray,el="[ \t\n\f\r]",tl=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,nl=/-->/g,il=/>/g,ol=RegExp(">|".concat(el,"(?:([^\\s\"'>=/]+)(").concat(el,"*=").concat(el,"*(?:[^ \t\n\f\r\"'`<>=]|(\"|')|))|$)"),"g"),rl=/'/g,al=/"/g,ll=/^(?:script|style|textarea|title)$/i,sl=function(e){return function(t){for(var n=arguments.length,i=new Array(n>1?n-1:0),o=1;o":3===t?"":"",a=tl,l=0;l"===u[0]?(a=null!=n?n:tl,d=-1):void 0===u[1]?d=-2:(d=a.lastIndex-u[2].length,c=u[1],a=void 0===u[3]?ol:'"'===u[3]?al:rl):a===al||a===rl?a=ol:a===nl||a===il?a=tl:(a=ol,n=void 0);var p=a===ol&&e[l+1].startsWith("/>")?" ":"";r+=a===tl?s+qa:d>=0?(o.push(c),s.slice(0,d)+Ya+s.slice(d)+Ka+p):s+Ka+(-2===d?l:p)}return[ml(e,r+(e[i]||"")+(2===t?"":3===t?"":"")),o]}(o,r),d=Fr(u,2),h=d[0],p=d[1];if(this.el=e.createElement(h,n),fl.currentNode=this.el.content,2===r||3===r){var f=this.el.content.firstChild;f.replaceWith.apply(f,Ur(f.childNodes))}for(;null!==(i=fl.nextNode())&&c.length0){i.textContent=Fa?Fa.emptyScript:"";for(var x=0;x2&&void 0!==arguments[2]?arguments[2]:e,s=arguments.length>3?arguments[3]:void 0;if(t===dl)return t;var c=void 0!==s?null===(n=l._$Co)||void 0===n?void 0:n[s]:l._$Cl,u=Ja(t)?void 0:t._$litDirective$;return(null===(i=c)||void 0===i?void 0:i.constructor)!==u&&(null!==(o=c)&&void 0!==o&&null!==(r=o._$AO)&&void 0!==r&&r.call(o,!1),void 0===u?c=void 0:(c=new u(e))._$AT(e,l,s),void 0!==s?(null!==(a=l._$Co)&&void 0!==a?a:l._$Co=[])[s]=c:l._$Cl=c),void 0!==c&&(t=gl(e,c._$AS(e,t.values),c,s)),t}var _l=function(){return ua((function e(t,n){sa(this,e),this._$AV=[],this._$AN=void 0,this._$AD=t,this._$AM=n}),[{key:"parentNode",get:function(){return this._$AM.parentNode}},{key:"_$AU",get:function(){return this._$AM._$AU}},{key:"u",value:function(e){var t,n=this._$AD,i=n.el.content,o=n.parts,r=(null!==(t=null==e?void 0:e.creationScope)&&void 0!==t?t:Xa).importNode(i,!0);fl.currentNode=r;for(var a=fl.nextNode(),l=0,s=0,c=o[0];void 0!==c;){var u;if(l===c.index){var d=void 0;2===c.type?d=new yl(a,a.nextSibling,this,e):1===c.type?d=new c.ctor(a,c.name,c.strings,this,e):6===c.type&&(d=new El(a,this,e)),this._$AV.push(d),c=o[++s]}l!==(null===(u=c)||void 0===u?void 0:u.index)&&(a=fl.nextNode(),l++)}return fl.currentNode=Xa,r}},{key:"p",value:function(e){var t,n=0,i=ra(this._$AV);try{for(i.s();!(t=i.n()).done;){var o=t.value;void 0!==o&&(void 0!==o.strings?(o._$AI(e,o,n),n+=o.strings.length-2):o._$AI(e[n])),n++}}catch(e){i.e(e)}finally{i.f()}}}])}(),yl=function(){function e(t,n,i,o){var r;sa(this,e),this.type=2,this._$AH=hl,this._$AN=void 0,this._$AA=t,this._$AB=n,this._$AM=i,this.options=o,this._$Cv=null===(r=null==o?void 0:o.isConnected)||void 0===r||r}return ua(e,[{key:"_$AU",get:function(){var e,t;return null!==(e=null===(t=this._$AM)||void 0===t?void 0:t._$AU)&&void 0!==e?e:this._$Cv}},{key:"parentNode",get:function(){var e,t=this._$AA.parentNode,n=this._$AM;return void 0!==n&&11===(null===(e=t)||void 0===e?void 0:e.nodeType)&&(t=n.parentNode),t}},{key:"startNode",get:function(){return this._$AA}},{key:"endNode",get:function(){return this._$AB}},{key:"_$AI",value:function(e){e=gl(this,e,arguments.length>1&&void 0!==arguments[1]?arguments[1]:this),Ja(e)?e===hl||null==e||""===e?(this._$AH!==hl&&this._$AR(),this._$AH=hl):e!==this._$AH&&e!==dl&&this._(e):void 0!==e._$litType$?this.$(e):void 0!==e.nodeType?this.T(e):function(e){return Qa(e)||"function"==typeof(null==e?void 0:e[Symbol.iterator])}(e)?this.k(e):this._(e)}},{key:"O",value:function(e){return this._$AA.parentNode.insertBefore(e,this._$AB)}},{key:"T",value:function(e){this._$AH!==e&&(this._$AR(),this._$AH=this.O(e))}},{key:"_",value:function(e){this._$AH!==hl&&Ja(this._$AH)?this._$AA.nextSibling.data=e:this.T(Xa.createTextNode(e)),this._$AH=e}},{key:"$",value:function(e){var t,n=e.values,i=e._$litType$,o="number"==typeof i?this._$AC(e):(void 0===i.el&&(i.el=vl.createElement(ml(i.h,i.h[0]),this.options)),i);if((null===(t=this._$AH)||void 0===t?void 0:t._$AD)===o)this._$AH.p(n);else{var r=new _l(o,this),a=r.u(this.options);r.p(n),this.T(a),this._$AH=r}}},{key:"_$AC",value:function(e){var t=pl.get(e.strings);return void 0===t&&pl.set(e.strings,t=new vl(e)),t}},{key:"k",value:function(t){Qa(this._$AH)||(this._$AH=[],this._$AR());var n,i,o=this._$AH,r=0,a=ra(t);try{for(a.s();!(i=a.n()).done;){var l=i.value;r===o.length?o.push(n=new e(this.O(Za()),this.O(Za()),this,this.options)):n=o[r],n._$AI(l),r++}}catch(e){a.e(e)}finally{a.f()}r0&&void 0!==arguments[0]?arguments[0]:this._$AA.nextSibling,t=arguments.length>1?arguments[1]:void 0;for(null===(n=this._$AP)||void 0===n||n.call(this,!1,!0,t);e!==this._$AB;){var n,i=e.nextSibling;e.remove(),e=i}}},{key:"setConnected",value:function(e){var t;void 0===this._$AM&&(this._$Cv=e,null===(t=this._$AP)||void 0===t||t.call(this,e))}}])}(),bl=function(){return ua((function e(t,n,i,o,r){sa(this,e),this.type=1,this._$AH=hl,this._$AN=void 0,this.element=t,this.name=n,this._$AM=o,this.options=r,i.length>2||""!==i[0]||""!==i[1]?(this._$AH=Array(i.length-1).fill(new String),this.strings=i):this._$AH=hl}),[{key:"tagName",get:function(){return this.element.tagName}},{key:"_$AU",get:function(){return this._$AM._$AU}},{key:"_$AI",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this,n=arguments.length>2?arguments[2]:void 0,i=arguments.length>3?arguments[3]:void 0,o=this.strings,r=!1;if(void 0===o)e=gl(this,e,t,0),(r=!Ja(e)||e!==this._$AH&&e!==dl)&&(this._$AH=e);else{var a,l,s=e;for(e=o[0],a=0;a1&&void 0!==arguments[1]?arguments[1]:this,0))&&void 0!==t?t:hl)!==dl){var n=this._$AH,i=e===hl&&n!==hl||e.capture!==n.capture||e.once!==n.once||e.passive!==n.passive,o=e!==hl&&(n===hl||i);i&&this.element.removeEventListener(this.name,this,n),o&&this.element.addEventListener(this.name,this,e),this._$AH=e}}},{key:"handleEvent",value:function(e){var t,n;"function"==typeof this._$AH?this._$AH.call(null!==(t=null===(n=this.options)||void 0===n?void 0:n.host)&&void 0!==t?t:this.element,e):this._$AH.handleEvent(e)}}])}(),El=function(){return ua((function e(t,n,i){sa(this,e),this.element=t,this.type=6,this._$AN=void 0,this._$AM=n,this.options=i}),[{key:"_$AU",get:function(){return this._$AM._$AU}},{key:"_$AI",value:function(e){gl(this,e)}}])}(),Cl=Va.litHtmlPolyfillSupport;null!=Cl&&Cl(vl,yl),(null!==(i=Va.litHtmlVersions)&&void 0!==i?i:Va.litHtmlVersions=[]).push("3.3.1");var Al=globalThis,Sl=function(e){function t(){var e;return sa(this,t),(e=Zr(this,t,arguments)).renderOptions={host:Qr(e)},e._$Do=void 0,e}return ea(t,Ua),ua(t,[{key:"createRenderRoot",value:function(){var e,n,i=Hr(t,"createRenderRoot",this,3)([]);return null!==(n=(e=this.renderOptions).renderBefore)&&void 0!==n||(e.renderBefore=i.firstChild),i}},{key:"update",value:function(e){var n=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),Hr(t,"update",this,3)([e]),this._$Do=function(e,t,n){var i,o=null!==(i=null==n?void 0:n.renderBefore)&&void 0!==i?i:t,r=o._$litPart$;if(void 0===r){var a,l=null!==(a=null==n?void 0:n.renderBefore)&&void 0!==a?a:null;o._$litPart$=r=new yl(t.insertBefore(Za(),l),l,void 0,null!=n?n:{})}return r._$AI(e),r}(n,this.renderRoot,this.renderOptions)}},{key:"connectedCallback",value:function(){var e;Hr(t,"connectedCallback",this,3)([]),null===(e=this._$Do)||void 0===e||e.setConnected(!0)}},{key:"disconnectedCallback",value:function(){var e;Hr(t,"disconnectedCallback",this,3)([]),null===(e=this._$Do)||void 0===e||e.setConnected(!1)}},{key:"render",value:function(){return dl}}])}(); +var Gr=globalThis,Kr=Gr.trustedTypes,Yr=Kr?Kr.createPolicy("lit-html",{createHTML:function(t){return t}}):void 0,qr="$lit$",Wr="lit$".concat(Math.random().toFixed(9).slice(2),"$"),Xr="?"+Wr,Zr="<".concat(Xr,">"),Jr=document,Qr=function(){return Jr.createComment("")},ta=function(t){return null===t||"object"!=mr(t)&&"function"!=typeof t},ea=Array.isArray,na="[ \t\n\f\r]",oa=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,ia=/-->/g,ra=/>/g,aa=RegExp(">|".concat(na,"(?:([^\\s\"'>=/]+)(").concat(na,"*=").concat(na,"*(?:[^ \t\n\f\r\"'`<>=]|(\"|')|))|$)"),"g"),sa=/'/g,la=/"/g,ca=/^(?:script|style|textarea|title)$/i,ua=function(t){return function(e){for(var n=arguments.length,o=new Array(n>1?n-1:0),i=1;i":3===e?"":"",a=oa,s=0;s"===u[0]?(a=null!=n?n:oa,h=-1):void 0===u[1]?h=-2:(h=a.lastIndex-u[2].length,c=u[1],a=void 0===u[3]?aa:'"'===u[3]?la:sa):a===la||a===sa?a=aa:a===ia||a===ra?a=oa:(a=aa,n=void 0);var p=a===aa&&t[s+1].startsWith("/>")?" ":"";r+=a===oa?l+Zr:h>=0?(i.push(c),l.slice(0,h)+qr+l.slice(h)+Wr+p):l+Wr+(-2===h?s:p)}return[ga(t,r+(t[o]||"")+(2===e?"":3===e?"":"")),i]},ya=function(){return pr((function t(e,n){var o,i=e.strings,r=e._$litType$;hr(this,t),this.parts=[];var a=0,s=0,l=i.length-1,c=this.parts,u=qi(_a(i,r),2),h=u[0],d=u[1];if(this.el=t.createElement(h,n),va.currentNode=this.el.content,2===r||3===r){var p=this.el.content.firstChild;p.replaceWith.apply(p,Ki(p.childNodes))}for(;null!==(o=va.nextNode())&&c.length0){o.textContent=Kr?Kr.emptyScript:"";for(var w=0;w2&&void 0!==arguments[2]?arguments[2]:t,l=arguments.length>3?arguments[3]:void 0;if(e===pa)return e;var c=void 0!==l?null===(n=s._$Co)||void 0===n?void 0:n[l]:s._$Cl,u=ta(e)?void 0:e._$litDirective$;return(null===(o=c)||void 0===o?void 0:o.constructor)!==u&&(null!==(i=c)&&void 0!==i&&null!==(r=i._$AO)&&void 0!==r&&r.call(i,!1),void 0===u?c=void 0:(c=new u(t))._$AT(t,s,l),void 0!==l?(null!==(a=s._$Co)&&void 0!==a?a:s._$Co=[])[l]=c:s._$Cl=c),void 0!==c&&(e=ba(t,c._$AS(t,e.values),c,l)),e}var ka=function(){return pr((function t(e,n){hr(this,t),this._$AV=[],this._$AN=void 0,this._$AD=e,this._$AM=n}),[{key:"parentNode",get:function(){return this._$AM.parentNode}},{key:"_$AU",get:function(){return this._$AM._$AU}},{key:"u",value:function(t){var e,n=this._$AD,o=n.el.content,i=n.parts,r=(null!==(e=null==t?void 0:t.creationScope)&&void 0!==e?e:Jr).importNode(o,!0);va.currentNode=r;for(var a=va.nextNode(),s=0,l=0,c=i[0];void 0!==c;){var u;if(s===c.index){var h=void 0;2===c.type?h=new wa(a,a.nextSibling,this,t):1===c.type?h=new c.ctor(a,c.name,c.strings,this,t):6===c.type&&(h=new Sa(a,this,t)),this._$AV.push(h),c=i[++l]}s!==(null===(u=c)||void 0===u?void 0:u.index)&&(a=va.nextNode(),s++)}return va.currentNode=Jr,r}},{key:"p",value:function(t){var e,n=0,o=lr(this._$AV);try{for(o.s();!(e=o.n()).done;){var i=e.value;void 0!==i&&(void 0!==i.strings?(i._$AI(t,i,n),n+=i.strings.length-2):i._$AI(t[n])),n++}}catch(t){o.e(t)}finally{o.f()}}}])}(),wa=function(){function t(e,n,o,i){var r;hr(this,t),this.type=2,this._$AH=fa,this._$AN=void 0,this._$AA=e,this._$AB=n,this._$AM=o,this.options=i,this._$Cv=null===(r=null==i?void 0:i.isConnected)||void 0===r||r}return pr(t,[{key:"_$AU",get:function(){var t,e;return null!==(t=null===(e=this._$AM)||void 0===e?void 0:e._$AU)&&void 0!==t?t:this._$Cv}},{key:"parentNode",get:function(){var t,e=this._$AA.parentNode,n=this._$AM;return void 0!==n&&11===(null===(t=e)||void 0===t?void 0:t.nodeType)&&(e=n.parentNode),e}},{key:"startNode",get:function(){return this._$AA}},{key:"endNode",get:function(){return this._$AB}},{key:"_$AI",value:function(t){t=ba(this,t,arguments.length>1&&void 0!==arguments[1]?arguments[1]:this),ta(t)?t===fa||null==t||""===t?(this._$AH!==fa&&this._$AR(),this._$AH=fa):t!==this._$AH&&t!==pa&&this._(t):void 0!==t._$litType$?this.$(t):void 0!==t.nodeType?this.T(t):function(t){return ea(t)||"function"==typeof(null==t?void 0:t[Symbol.iterator])}(t)?this.k(t):this._(t)}},{key:"O",value:function(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}},{key:"T",value:function(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t))}},{key:"_",value:function(t){this._$AH!==fa&&ta(this._$AH)?this._$AA.nextSibling.data=t:this.T(Jr.createTextNode(t)),this._$AH=t}},{key:"$",value:function(t){var e,n=t.values,o=t._$litType$,i="number"==typeof o?this._$AC(t):(void 0===o.el&&(o.el=ya.createElement(ga(o.h,o.h[0]),this.options)),o);if((null===(e=this._$AH)||void 0===e?void 0:e._$AD)===i)this._$AH.p(n);else{var r=new ka(i,this),a=r.u(this.options);r.p(n),this.T(a),this._$AH=r}}},{key:"_$AC",value:function(t){var e=ma.get(t.strings);return void 0===e&&ma.set(t.strings,e=new ya(t)),e}},{key:"k",value:function(e){ea(this._$AH)||(this._$AH=[],this._$AR());var n,o,i=this._$AH,r=0,a=lr(e);try{for(a.s();!(o=a.n()).done;){var s=o.value;r===i.length?i.push(n=new t(this.O(Qr()),this.O(Qr()),this,this.options)):n=i[r],n._$AI(s),r++}}catch(t){a.e(t)}finally{a.f()}r0&&void 0!==arguments[0]?arguments[0]:this._$AA.nextSibling,e=arguments.length>1?arguments[1]:void 0;for(null===(n=this._$AP)||void 0===n||n.call(this,!1,!0,e);t!==this._$AB;){var n,o=t.nextSibling;t.remove(),t=o}}},{key:"setConnected",value:function(t){var e;void 0===this._$AM&&(this._$Cv=t,null===(e=this._$AP)||void 0===e||e.call(this,t))}}])}(),Ca=function(){return pr((function t(e,n,o,i,r){hr(this,t),this.type=1,this._$AH=fa,this._$AN=void 0,this.element=e,this.name=n,this._$AM=i,this.options=r,o.length>2||""!==o[0]||""!==o[1]?(this._$AH=Array(o.length-1).fill(new String),this.strings=o):this._$AH=fa}),[{key:"tagName",get:function(){return this.element.tagName}},{key:"_$AU",get:function(){return this._$AM._$AU}},{key:"_$AI",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this,n=arguments.length>2?arguments[2]:void 0,o=arguments.length>3?arguments[3]:void 0,i=this.strings,r=!1;if(void 0===i)t=ba(this,t,e,0),(r=!ta(t)||t!==this._$AH&&t!==pa)&&(this._$AH=t);else{var a,s,l=t;for(t=i[0],a=0;a1&&void 0!==arguments[1]?arguments[1]:this,0))&&void 0!==e?e:fa)!==pa){var n=this._$AH,o=t===fa&&n!==fa||t.capture!==n.capture||t.once!==n.once||t.passive!==n.passive,i=t!==fa&&(n===fa||o);o&&this.element.removeEventListener(this.name,this,n),i&&this.element.addEventListener(this.name,this,t),this._$AH=t}}},{key:"handleEvent",value:function(t){var e,n;"function"==typeof this._$AH?this._$AH.call(null!==(e=null===(n=this.options)||void 0===n?void 0:n.host)&&void 0!==e?e:this.element,t):this._$AH.handleEvent(t)}}])}(),Sa=function(){return pr((function t(e,n,o){hr(this,t),this.element=e,this.type=6,this._$AN=void 0,this._$AM=n,this.options=o}),[{key:"_$AU",get:function(){return this._$AM._$AU}},{key:"_$AI",value:function(t){ba(this,t)}}])}(),Ta=Gr.litHtmlPolyfillSupport;null!=Ta&&Ta(ya,wa),(null!==(o=Gr.litHtmlVersions)&&void 0!==o?o:Gr.litHtmlVersions=[]).push("3.3.1");var Ma=globalThis,za=function(t){function e(){var t;return hr(this,e),(t=er(this,e,arguments)).renderOptions={host:nr(t)},t._$Do=void 0,t}return or(e,$r),pr(e,[{key:"createRenderRoot",value:function(){var t,n,o=$i(e,"createRenderRoot",this,3)([]);return null!==(n=(t=this.renderOptions).renderBefore)&&void 0!==n||(t.renderBefore=o.firstChild),o}},{key:"update",value:function(t){var n=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),$i(e,"update",this,3)([t]),this._$Do=function(t,e,n){var o,i=null!==(o=null==n?void 0:n.renderBefore)&&void 0!==o?o:e,r=i._$litPart$;if(void 0===r){var a,s=null!==(a=null==n?void 0:n.renderBefore)&&void 0!==a?a:null;i._$litPart$=r=new wa(e.insertBefore(Qr(),s),s,void 0,null!=n?n:{})}return r._$AI(t),r}(n,this.renderRoot,this.renderOptions)}},{key:"connectedCallback",value:function(){var t;$i(e,"connectedCallback",this,3)([]),null===(t=this._$Do)||void 0===t||t.setConnected(!0)}},{key:"disconnectedCallback",value:function(){var t;$i(e,"disconnectedCallback",this,3)([]),null===(t=this._$Do)||void 0===t||t.setConnected(!1)}},{key:"render",value:function(){return pa}}])}(); /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause - */Sl._$litElement$=!0,Sl.finalized=!0,null===(o=Al.litElementHydrateSupport)||void 0===o||o.call(Al,{LitElement:Sl});var Tl=Al.litElementPolyfillSupport;null==Tl||Tl({LitElement:Sl}),(null!==(r=Al.litElementVersions)&&void 0!==r?r:Al.litElementVersions=[]).push("4.2.1"); + */za._$litElement$=!0,za.finalized=!0,null===(i=Ma.litElementHydrateSupport)||void 0===i||i.call(Ma,{LitElement:za});var Oa=Ma.litElementPolyfillSupport;null==Oa||Oa({LitElement:za}),(null!==(r=Ma.litElementVersions)&&void 0!==r?r:Ma.litElementVersions=[]).push("4.2.1"); /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause */ -var Il=function(e){return function(t,n){void 0!==n?n.addInitializer((function(){customElements.define(e,t)})):customElements.define(e,t)}},Ol={attribute:!0,type:String,converter:Ra,reflect:!1,hasChanged:Ha},Ml=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:Ol,t=arguments.length>1?arguments[1]:void 0,n=arguments.length>2?arguments[2]:void 0,i=n.kind,o=n.metadata,r=globalThis.litPropertyMetadata.get(o);if(void 0===r&&globalThis.litPropertyMetadata.set(o,r=new Map),"setter"===i&&((e=Object.create(e)).wrapped=!0),r.set(n.name,e),"accessor"===i){var a=n.name;return{set:function(n){var i=t.get.call(this);t.set.call(this,n),this.requestUpdate(a,i,e)},init:function(t){return void 0!==t&&this.C(a,void 0,e,t),t}}}if("setter"===i){var l=n.name;return function(n){var i=this[l];t.call(this,n),this.requestUpdate(l,i,e)}}throw Error("Unsupported decorator location: "+i)}; +var Ia=function(t){return function(e,n){void 0!==n?n.addInitializer((function(){customElements.define(t,e)})):customElements.define(t,e)}},ja={attribute:!0,type:String,converter:Ur,reflect:!1,hasChanged:Vr},Pa=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:ja,e=arguments.length>1?arguments[1]:void 0,n=arguments.length>2?arguments[2]:void 0,o=n.kind,i=n.metadata,r=globalThis.litPropertyMetadata.get(i);if(void 0===r&&globalThis.litPropertyMetadata.set(i,r=new Map),"setter"===o&&((t=Object.create(t)).wrapped=!0),r.set(n.name,t),"accessor"===o){var a=n.name;return{set:function(n){var o=e.get.call(this);e.set.call(this,n),this.requestUpdate(a,o,t)},init:function(e){return void 0!==e&&this.C(a,void 0,t,e),e}}}if("setter"===o){var s=n.name;return function(n){var o=this[s];e.call(this,n),this.requestUpdate(s,o,t)}}throw Error("Unsupported decorator location: "+o)}; /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause - */function zl(e){return function(t,n){return"object"==ha(n)?Ml(e,t,n):function(e,t,n){var i=t.hasOwnProperty(n);return t.constructor.createProperty(n,e),i?Object.getOwnPropertyDescriptor(t,n):void 0}(e,t,n)}} + */function Na(t){return function(e,n){return"object"==mr(n)?Pa(t,e,n):function(t,e,n){var o=e.hasOwnProperty(n);return e.constructor.createProperty(n,t),o?Object.getOwnPropertyDescriptor(e,n):void 0}(t,e,n)}} /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause - */function Pl(e){return zl(Br(Br({},e),{},{state:!0,attribute:!1}))} + */function Ba(t){return Na(Vi(Vi({},t),{},{state:!0,attribute:!1}))} /** * @license * Copyright 2017 Google LLC @@ -42,351 +42,37 @@ var Il=function(e){return function(t,n){void 0!==n?n.addInitializer((function(){ * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause */ -function Ll(e,t){return function(t,n,i){return function(e,t,n){return n.configurable=!0,n.enumerable=!0,Reflect.decorate&&"object"!=ha(t)&&Object.defineProperty(e,t,n),n}(t,n,{get:function(){return function(t){var n,i;return null!==(n=null===(i=t.renderRoot)||void 0===i?void 0:i.querySelector(e))&&void 0!==n?n:null}(this)}})}}var Nl,jl,$l,Bl,Rl,Hl=new Set(["fan","input_boolean","light","switch","group","automation","humidifier","valve"]),Dl=function(e,t,n,i){i=i||{},n=null==n?{}:n;var o=new Event(t,{bubbles:void 0===i.bubbles||i.bubbles,cancelable:Boolean(i.cancelable),composed:void 0===i.composed||i.composed});return o.detail=n,e.dispatchEvent(o),o},Ul=function(e){return e.substr(0,e.indexOf("."))},Vl=function(e,t){return Fl(e.attributes,t)},Fl=function(e,t){return 0!=(e.supported_features&t)};!function(e){e.language="language",e.system="system",e.comma_decimal="comma_decimal",e.decimal_comma="decimal_comma",e.space_comma="space_comma",e.none="none"}(Nl||(Nl={})),function(e){e.language="language",e.system="system",e.am_pm="12",e.twenty_four="24"}(jl||(jl={})),function(e){e.local="local",e.server="server"}($l||($l={})),function(e){e.language="language",e.system="system",e.DMY="DMY",e.MDY="MDY",e.YMD="YMD"}(Bl||(Bl={})),function(e){e.language="language",e.monday="monday",e.tuesday="tuesday",e.wednesday="wednesday",e.thursday="thursday",e.friday="friday",e.saturday="saturday",e.sunday="sunday"}(Rl||(Rl={}));var Gl=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:2;return Math.round(e*Math.pow(10,t))/Math.pow(10,t)},Yl=function(e,t,n){var i=t?function(e){switch(e.number_format){case Nl.comma_decimal:return["en-US","en"];case Nl.decimal_comma:return["de","es","it"];case Nl.space_comma:return["fr","sv","cs"];case Nl.system:return;default:return e.language}}(t):void 0;if(Number.isNaN=Number.isNaN||function e(t){return"number"==typeof t&&e(t)},(null==t?void 0:t.number_format)!==Nl.none&&!Number.isNaN(Number(e))&&Intl)try{return new Intl.NumberFormat(i,Kl(e,n)).format(Number(e))}catch(t){return console.error(t),new Intl.NumberFormat(void 0,Kl(e,n)).format(Number(e))}return"string"==typeof e?e:"".concat(Gl(e,null==n?void 0:n.maximumFractionDigits).toString()).concat("currency"===(null==n?void 0:n.style)?" ".concat(n.currency):"")},Kl=function(e,t){var n=Object.assign({maximumFractionDigits:2},t);if("string"!=typeof e)return n;if(!t||void 0===t.minimumFractionDigits&&void 0===t.maximumFractionDigits){var i=e.indexOf(".")>-1?e.split(".")[1].length:0;n.minimumFractionDigits=i,n.maximumFractionDigits=i}return n},Wl=function(e){function t(e,n){var i,o;sa(this,t);var r=e.message,a=e.explanation,l=function(e,t){if(null==e)return{};var n,i,o=function(e,t){if(null==e)return{};var n={};for(var i in e)if({}.hasOwnProperty.call(e,i)){if(-1!==t.indexOf(i))continue;n[i]=e[i]}return n}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(i=0;i2&&void 0!==arguments[2]?arguments[2]:{};return Kr().m((function i(){var o,r,a,l,s,c,u,d,h,p,f,m,v,g,_,y,b,k,w,x,E,C,A,S,T,I,O,M,z,P;return Kr().w((function(i){for(;;)switch(i.p=i.n){case 0:o=n.path,r=void 0===o?[]:o,a=n.branch,l=void 0===a?[e]:a,s=n.coerce,c=void 0!==s&&s,u=n.mask,h={path:r,branch:l,mask:d=void 0!==u&&u},c&&(e=t.coercer(e,h)),p="valid",f=ra(t.validator(e,h)),i.p=1,f.s();case 2:if((m=f.n()).done){i.n=4;break}return(v=m.value).explanation=n.message,p="not_valid",i.n=3,[v,void 0];case 3:i.n=2;break;case 4:i.n=6;break;case 5:i.p=5,O=i.v,f.e(O);case 6:return i.p=6,f.f(),i.f(6);case 7:g=ra(t.entries(e,h)),i.p=8,g.s();case 9:if((_=g.n()).done){i.n=19;break}y=Fr(_.value,3),b=y[0],k=y[1],w=y[2],x=ts(k,w,{path:void 0===b?r:[].concat(Ur(r),[b]),branch:void 0===b?l:[].concat(Ur(l),[k]),coerce:c,mask:d,message:n.message}),E=ra(x),i.p=10,E.s();case 11:if((C=E.n()).done){i.n=15;break}if(!(A=C.value)[0]){i.n=13;break}return p=null!=A[0].refinement?"not_refined":"not_valid",i.n=12,[A[0],void 0];case 12:i.n=14;break;case 13:c&&(k=A[1],void 0===b?e=k:e instanceof Map?e.set(b,k):e instanceof Set?e.add(k):Xl(e)&&(void 0!==k||b in e)&&(e[b]=k));case 14:i.n=11;break;case 15:i.n=17;break;case 16:i.p=16,M=i.v,E.e(M);case 17:return i.p=17,E.f(),i.f(17);case 18:i.n=9;break;case 19:i.n=21;break;case 20:i.p=20,z=i.v,g.e(z);case 21:return i.p=21,g.f(),i.f(21);case 22:if("not_valid"===p){i.n=29;break}S=ra(t.refiner(e,h)),i.p=23,S.s();case 24:if((T=S.n()).done){i.n=26;break}return(I=T.value).explanation=n.message,p="not_refined",i.n=25,[I,void 0];case 25:i.n=24;break;case 26:i.n=28;break;case 27:i.p=27,P=i.v,S.e(P);case 28:return i.p=28,S.f(),i.f(28);case 29:if("valid"!==p){i.n=30;break}return i.n=30,[void 0,e];case 30:return i.a(2)}}),i,null,[[23,27,28,29],[10,16,17,18],[8,20,21,22],[1,5,6,7]])}))()}var ns=function(){return ua((function e(t){var n=this;sa(this,e);var i=t.type,o=t.schema,r=t.validator,a=t.refiner,l=t.coercer,s=void 0===l?function(e){return e}:l,c=t.entries,u=void 0===c?Kr().m((function e(){return Kr().w((function(e){for(;;)if(0===e.n)return e.a(2)}),e)})):c;this.type=i,this.schema=o,this.entries=u,this.coercer=s,this.validator=r?function(e,t){return es(r(e,t),t,n,e)}:function(){return[]},this.refiner=a?function(e,t){return es(a(e,t),t,n,e)}:function(){return[]}}),[{key:"assert",value:function(e,t){return is(e,this,t)}},{key:"create",value:function(e,t){return function(e,t,n){var i=os(e,t,{coerce:!0,message:n});if(i[0])throw i[0];return i[1]}(e,this,t)}},{key:"is",value:function(e){return function(e,t){var n=os(e,t);return!n[0]}(e,this)}},{key:"mask",value:function(e,t){return function(e,t,n){var i=os(e,t,{coerce:!0,mask:!0,message:n});if(i[0])throw i[0];return i[1]}(e,this,t)}},{key:"validate",value:function(e){return os(e,this,arguments.length>1&&void 0!==arguments[1]?arguments[1]:{})}}])}();function is(e,t,n){var i=os(e,t,{message:n});if(i[0])throw i[0]}function os(e,t){var n=ts(e,t,arguments.length>2&&void 0!==arguments[2]?arguments[2]:{}),i=function(e){var t=e.next(),n=t.done,i=t.value;return n?void 0:i}(n);return i[0]?[new Wl(i[0],Kr().m((function e(){var t,i,o,r;return Kr().w((function(e){for(;;)switch(e.p=e.n){case 0:t=ra(n),e.p=1,t.s();case 2:if((i=t.n()).done){e.n=4;break}if(!(o=i.value)[0]){e.n=3;break}return e.n=3,o[0];case 3:e.n=2;break;case 4:e.n=6;break;case 5:e.p=5,r=e.v,t.e(r);case 6:return e.p=6,t.f(),e.f(6);case 7:return e.a(2)}}),e,null,[[1,5,6,7]])}))),void 0]:[void 0,i[1]]}function rs(){for(var e=arguments.length,t=new Array(e),n=0;n2&&void 0!==arguments[2]&&arguments[2],o=function(){for(var o=arguments.length,r=new Array(o),a=0;at||Number(r)===t&&Number(a)>=n||void 0!==i};function cc(e,t){var n=t&&t.cache?t.cache:yc,i=t&&t.serializer?t.serializer:gc;return(t&&t.strategy?t.strategy:pc)(e,{cache:n,serializer:i})}function uc(e,t,n,i){var o,r=null==(o=i)||"number"==typeof o||"boolean"==typeof o?i:n(i),a=t.get(r);return void 0===a&&(a=e.call(this,i),t.set(r,a)),a}function dc(e,t,n){var i=Array.prototype.slice.call(arguments,3),o=n(i),r=t.get(o);return void 0===r&&(r=e.apply(this,i),t.set(o,r)),r}function hc(e,t,n,i,o){return n.bind(t,e,i,o)}function pc(e,t){return hc(e,this,1===e.length?uc:dc,t.cache.create(),t.serializer)}var fc,mc,vc,gc=function(){return JSON.stringify(arguments)},_c=function(){function e(){this.cache=Object.create(null)}return e.prototype.get=function(e){return this.cache[e]},e.prototype.set=function(e,t){this.cache[e]=t},e}(),yc={create:function(){return new _c}},bc={variadic:function(e,t){return hc(e,this,dc,t.cache.create(),t.serializer)}};function kc(e){return e.type===mc.literal}function wc(e){return e.type===mc.argument}function xc(e){return e.type===mc.number}function Ec(e){return e.type===mc.date}function Cc(e){return e.type===mc.time}function Ac(e){return e.type===mc.select}function Sc(e){return e.type===mc.plural}function Tc(e){return e.type===mc.pound}function Ic(e){return e.type===mc.tag}function Oc(e){return!(!e||"object"!==ha(e)||e.type!==vc.number)}function Mc(e){return!(!e||"object"!==ha(e)||e.type!==vc.dateTime)}!function(e){e[e.EXPECT_ARGUMENT_CLOSING_BRACE=1]="EXPECT_ARGUMENT_CLOSING_BRACE",e[e.EMPTY_ARGUMENT=2]="EMPTY_ARGUMENT",e[e.MALFORMED_ARGUMENT=3]="MALFORMED_ARGUMENT",e[e.EXPECT_ARGUMENT_TYPE=4]="EXPECT_ARGUMENT_TYPE",e[e.INVALID_ARGUMENT_TYPE=5]="INVALID_ARGUMENT_TYPE",e[e.EXPECT_ARGUMENT_STYLE=6]="EXPECT_ARGUMENT_STYLE",e[e.INVALID_NUMBER_SKELETON=7]="INVALID_NUMBER_SKELETON",e[e.INVALID_DATE_TIME_SKELETON=8]="INVALID_DATE_TIME_SKELETON",e[e.EXPECT_NUMBER_SKELETON=9]="EXPECT_NUMBER_SKELETON",e[e.EXPECT_DATE_TIME_SKELETON=10]="EXPECT_DATE_TIME_SKELETON",e[e.UNCLOSED_QUOTE_IN_ARGUMENT_STYLE=11]="UNCLOSED_QUOTE_IN_ARGUMENT_STYLE",e[e.EXPECT_SELECT_ARGUMENT_OPTIONS=12]="EXPECT_SELECT_ARGUMENT_OPTIONS",e[e.EXPECT_PLURAL_ARGUMENT_OFFSET_VALUE=13]="EXPECT_PLURAL_ARGUMENT_OFFSET_VALUE",e[e.INVALID_PLURAL_ARGUMENT_OFFSET_VALUE=14]="INVALID_PLURAL_ARGUMENT_OFFSET_VALUE",e[e.EXPECT_SELECT_ARGUMENT_SELECTOR=15]="EXPECT_SELECT_ARGUMENT_SELECTOR",e[e.EXPECT_PLURAL_ARGUMENT_SELECTOR=16]="EXPECT_PLURAL_ARGUMENT_SELECTOR",e[e.EXPECT_SELECT_ARGUMENT_SELECTOR_FRAGMENT=17]="EXPECT_SELECT_ARGUMENT_SELECTOR_FRAGMENT",e[e.EXPECT_PLURAL_ARGUMENT_SELECTOR_FRAGMENT=18]="EXPECT_PLURAL_ARGUMENT_SELECTOR_FRAGMENT",e[e.INVALID_PLURAL_ARGUMENT_SELECTOR=19]="INVALID_PLURAL_ARGUMENT_SELECTOR",e[e.DUPLICATE_PLURAL_ARGUMENT_SELECTOR=20]="DUPLICATE_PLURAL_ARGUMENT_SELECTOR",e[e.DUPLICATE_SELECT_ARGUMENT_SELECTOR=21]="DUPLICATE_SELECT_ARGUMENT_SELECTOR",e[e.MISSING_OTHER_CLAUSE=22]="MISSING_OTHER_CLAUSE",e[e.INVALID_TAG=23]="INVALID_TAG",e[e.INVALID_TAG_NAME=25]="INVALID_TAG_NAME",e[e.UNMATCHED_CLOSING_TAG=26]="UNMATCHED_CLOSING_TAG",e[e.UNCLOSED_TAG=27]="UNCLOSED_TAG"}(fc||(fc={})),function(e){e[e.literal=0]="literal",e[e.argument=1]="argument",e[e.number=2]="number",e[e.date=3]="date",e[e.time=4]="time",e[e.select=5]="select",e[e.plural=6]="plural",e[e.pound=7]="pound",e[e.tag=8]="tag"}(mc||(mc={})),function(e){e[e.number=0]="number",e[e.dateTime=1]="dateTime"}(vc||(vc={}));var zc=/[ \xA0\u1680\u2000-\u200A\u202F\u205F\u3000]/,Pc=/(?:[Eec]{1,6}|G{1,5}|[Qq]{1,5}|(?:[yYur]+|U{1,5})|[ML]{1,5}|d{1,2}|D{1,3}|F{1}|[abB]{1,5}|[hkHK]{1,2}|w{1,2}|W{1}|m{1,2}|s{1,2}|[zZOvVxX]{1,4})(?=([^']*'[^']*')*[^']*$)/g;function Lc(e){var t={};return e.replace(Pc,(function(e){var n=e.length;switch(e[0]){case"G":t.era=4===n?"long":5===n?"narrow":"short";break;case"y":t.year=2===n?"2-digit":"numeric";break;case"Y":case"u":case"U":case"r":throw new RangeError("`Y/u/U/r` (year) patterns are not supported, use `y` instead");case"q":case"Q":throw new RangeError("`q/Q` (quarter) patterns are not supported");case"M":case"L":t.month=["numeric","2-digit","short","long","narrow"][n-1];break;case"w":case"W":throw new RangeError("`w/W` (week) patterns are not supported");case"d":t.day=["numeric","2-digit"][n-1];break;case"D":case"F":case"g":throw new RangeError("`D/F/g` (day) patterns are not supported, use `d` instead");case"E":t.weekday=4===n?"long":5===n?"narrow":"short";break;case"e":if(n<4)throw new RangeError("`e..eee` (weekday) patterns are not supported");t.weekday=["short","long","narrow","short"][n-4];break;case"c":if(n<4)throw new RangeError("`c..ccc` (weekday) patterns are not supported");t.weekday=["short","long","narrow","short"][n-4];break;case"a":t.hour12=!0;break;case"b":case"B":throw new RangeError("`b/B` (period) patterns are not supported, use `a` instead");case"h":t.hourCycle="h12",t.hour=["numeric","2-digit"][n-1];break;case"H":t.hourCycle="h23",t.hour=["numeric","2-digit"][n-1];break;case"K":t.hourCycle="h11",t.hour=["numeric","2-digit"][n-1];break;case"k":t.hourCycle="h24",t.hour=["numeric","2-digit"][n-1];break;case"j":case"J":case"C":throw new RangeError("`j/J/C` (hour) patterns are not supported, use `h/H/K/k` instead");case"m":t.minute=["numeric","2-digit"][n-1];break;case"s":t.second=["numeric","2-digit"][n-1];break;case"S":case"A":throw new RangeError("`S/A` (second) patterns are not supported, use `s` instead");case"z":t.timeZoneName=n<4?"short":"long";break;case"Z":case"O":case"v":case"V":case"X":case"x":throw new RangeError("`Z/O/v/V/X/x` (timeZone) patterns are not supported, use `z` instead")}return""})),t}var Nc=/[\t-\r \x85\u200E\u200F\u2028\u2029]/i;var jc=/^\.(?:(0+)(\*)?|(#+)|(0+)(#+))$/g,$c=/^(@+)?(\+|#+)?[rs]?$/g,Bc=/(\*)(0+)|(#+)(0+)|(0+)/g,Rc=/^(0+)$/;function Hc(e){var t={};return"r"===e[e.length-1]?t.roundingPriority="morePrecision":"s"===e[e.length-1]&&(t.roundingPriority="lessPrecision"),e.replace($c,(function(e,n,i){return"string"!=typeof i?(t.minimumSignificantDigits=n.length,t.maximumSignificantDigits=n.length):"+"===i?t.minimumSignificantDigits=n.length:"#"===n[0]?t.maximumSignificantDigits=n.length:(t.minimumSignificantDigits=n.length,t.maximumSignificantDigits=n.length+("string"==typeof i?i.length:0)),""})),t}function Dc(e){switch(e){case"sign-auto":return{signDisplay:"auto"};case"sign-accounting":case"()":return{currencySign:"accounting"};case"sign-always":case"+!":return{signDisplay:"always"};case"sign-accounting-always":case"()!":return{signDisplay:"always",currencySign:"accounting"};case"sign-except-zero":case"+?":return{signDisplay:"exceptZero"};case"sign-accounting-except-zero":case"()?":return{signDisplay:"exceptZero",currencySign:"accounting"};case"sign-never":case"+_":return{signDisplay:"never"}}}function Uc(e){var t;if("E"===e[0]&&"E"===e[1]?(t={notation:"engineering"},e=e.slice(2)):"E"===e[0]&&(t={notation:"scientific"},e=e.slice(1)),t){var n=e.slice(0,2);if("+!"===n?(t.signDisplay="always",e=e.slice(2)):"+?"===n&&(t.signDisplay="exceptZero",e=e.slice(2)),!Rc.test(e))throw new Error("Malformed concise eng/scientific notation");t.minimumIntegerDigits=e.length}return t}function Vc(e){var t=Dc(e);return t||{}}function Fc(e){for(var t={},n=0,i=e;n1)throw new RangeError("integer-width stems only accept a single optional option");o.options[0].replace(Bc,(function(e,n,i,o,r,a){if(n)t.minimumIntegerDigits=i.length;else{if(o&&r)throw new Error("We currently do not support maximum integer digits");if(a)throw new Error("We currently do not support exact integer digits")}return""}));continue}if(Rc.test(o.stem))t.minimumIntegerDigits=o.stem.length;else if(jc.test(o.stem)){if(o.options.length>1)throw new RangeError("Fraction-precision stems only accept a single optional option");o.stem.replace(jc,(function(e,n,i,o,r,a){return"*"===i?t.minimumFractionDigits=n.length:o&&"#"===o[0]?t.maximumFractionDigits=o.length:r&&a?(t.minimumFractionDigits=r.length,t.maximumFractionDigits=r.length+a.length):(t.minimumFractionDigits=n.length,t.maximumFractionDigits=n.length),""}));var r=o.options[0];"w"===r?t=va(va({},t),{trailingZeroDisplay:"stripIfInteger"}):r&&(t=va(va({},t),Hc(r)))}else if($c.test(o.stem))t=va(va({},t),Hc(o.stem));else{var a=Dc(o.stem);a&&(t=va(va({},t),a));var l=Uc(o.stem);l&&(t=va(va({},t),l))}}return t}var Gc,Yc={"001":["H","h"],419:["h","H","hB","hb"],AC:["H","h","hb","hB"],AD:["H","hB"],AE:["h","hB","hb","H"],AF:["H","hb","hB","h"],AG:["h","hb","H","hB"],AI:["H","h","hb","hB"],AL:["h","H","hB"],AM:["H","hB"],AO:["H","hB"],AR:["h","H","hB","hb"],AS:["h","H"],AT:["H","hB"],AU:["h","hb","H","hB"],AW:["H","hB"],AX:["H"],AZ:["H","hB","h"],BA:["H","hB","h"],BB:["h","hb","H","hB"],BD:["h","hB","H"],BE:["H","hB"],BF:["H","hB"],BG:["H","hB","h"],BH:["h","hB","hb","H"],BI:["H","h"],BJ:["H","hB"],BL:["H","hB"],BM:["h","hb","H","hB"],BN:["hb","hB","h","H"],BO:["h","H","hB","hb"],BQ:["H"],BR:["H","hB"],BS:["h","hb","H","hB"],BT:["h","H"],BW:["H","h","hb","hB"],BY:["H","h"],BZ:["H","h","hb","hB"],CA:["h","hb","H","hB"],CC:["H","h","hb","hB"],CD:["hB","H"],CF:["H","h","hB"],CG:["H","hB"],CH:["H","hB","h"],CI:["H","hB"],CK:["H","h","hb","hB"],CL:["h","H","hB","hb"],CM:["H","h","hB"],CN:["H","hB","hb","h"],CO:["h","H","hB","hb"],CP:["H"],CR:["h","H","hB","hb"],CU:["h","H","hB","hb"],CV:["H","hB"],CW:["H","hB"],CX:["H","h","hb","hB"],CY:["h","H","hb","hB"],CZ:["H"],DE:["H","hB"],DG:["H","h","hb","hB"],DJ:["h","H"],DK:["H"],DM:["h","hb","H","hB"],DO:["h","H","hB","hb"],DZ:["h","hB","hb","H"],EA:["H","h","hB","hb"],EC:["h","H","hB","hb"],EE:["H","hB"],EG:["h","hB","hb","H"],EH:["h","hB","hb","H"],ER:["h","H"],ES:["H","hB","h","hb"],ET:["hB","hb","h","H"],FI:["H"],FJ:["h","hb","H","hB"],FK:["H","h","hb","hB"],FM:["h","hb","H","hB"],FO:["H","h"],FR:["H","hB"],GA:["H","hB"],GB:["H","h","hb","hB"],GD:["h","hb","H","hB"],GE:["H","hB","h"],GF:["H","hB"],GG:["H","h","hb","hB"],GH:["h","H"],GI:["H","h","hb","hB"],GL:["H","h"],GM:["h","hb","H","hB"],GN:["H","hB"],GP:["H","hB"],GQ:["H","hB","h","hb"],GR:["h","H","hb","hB"],GT:["h","H","hB","hb"],GU:["h","hb","H","hB"],GW:["H","hB"],GY:["h","hb","H","hB"],HK:["h","hB","hb","H"],HN:["h","H","hB","hb"],HR:["H","hB"],HU:["H","h"],IC:["H","h","hB","hb"],ID:["H"],IE:["H","h","hb","hB"],IL:["H","hB"],IM:["H","h","hb","hB"],IN:["h","H"],IO:["H","h","hb","hB"],IQ:["h","hB","hb","H"],IR:["hB","H"],IS:["H"],IT:["H","hB"],JE:["H","h","hb","hB"],JM:["h","hb","H","hB"],JO:["h","hB","hb","H"],JP:["H","K","h"],KE:["hB","hb","H","h"],KG:["H","h","hB","hb"],KH:["hB","h","H","hb"],KI:["h","hb","H","hB"],KM:["H","h","hB","hb"],KN:["h","hb","H","hB"],KP:["h","H","hB","hb"],KR:["h","H","hB","hb"],KW:["h","hB","hb","H"],KY:["h","hb","H","hB"],KZ:["H","hB"],LA:["H","hb","hB","h"],LB:["h","hB","hb","H"],LC:["h","hb","H","hB"],LI:["H","hB","h"],LK:["H","h","hB","hb"],LR:["h","hb","H","hB"],LS:["h","H"],LT:["H","h","hb","hB"],LU:["H","h","hB"],LV:["H","hB","hb","h"],LY:["h","hB","hb","H"],MA:["H","h","hB","hb"],MC:["H","hB"],MD:["H","hB"],ME:["H","hB","h"],MF:["H","hB"],MG:["H","h"],MH:["h","hb","H","hB"],MK:["H","h","hb","hB"],ML:["H"],MM:["hB","hb","H","h"],MN:["H","h","hb","hB"],MO:["h","hB","hb","H"],MP:["h","hb","H","hB"],MQ:["H","hB"],MR:["h","hB","hb","H"],MS:["H","h","hb","hB"],MT:["H","h"],MU:["H","h"],MV:["H","h"],MW:["h","hb","H","hB"],MX:["h","H","hB","hb"],MY:["hb","hB","h","H"],MZ:["H","hB"],NA:["h","H","hB","hb"],NC:["H","hB"],NE:["H"],NF:["H","h","hb","hB"],NG:["H","h","hb","hB"],NI:["h","H","hB","hb"],NL:["H","hB"],NO:["H","h"],NP:["H","h","hB"],NR:["H","h","hb","hB"],NU:["H","h","hb","hB"],NZ:["h","hb","H","hB"],OM:["h","hB","hb","H"],PA:["h","H","hB","hb"],PE:["h","H","hB","hb"],PF:["H","h","hB"],PG:["h","H"],PH:["h","hB","hb","H"],PK:["h","hB","H"],PL:["H","h"],PM:["H","hB"],PN:["H","h","hb","hB"],PR:["h","H","hB","hb"],PS:["h","hB","hb","H"],PT:["H","hB"],PW:["h","H"],PY:["h","H","hB","hb"],QA:["h","hB","hb","H"],RE:["H","hB"],RO:["H","hB"],RS:["H","hB","h"],RU:["H"],RW:["H","h"],SA:["h","hB","hb","H"],SB:["h","hb","H","hB"],SC:["H","h","hB"],SD:["h","hB","hb","H"],SE:["H"],SG:["h","hb","H","hB"],SH:["H","h","hb","hB"],SI:["H","hB"],SJ:["H"],SK:["H"],SL:["h","hb","H","hB"],SM:["H","h","hB"],SN:["H","h","hB"],SO:["h","H"],SR:["H","hB"],SS:["h","hb","H","hB"],ST:["H","hB"],SV:["h","H","hB","hb"],SX:["H","h","hb","hB"],SY:["h","hB","hb","H"],SZ:["h","hb","H","hB"],TA:["H","h","hb","hB"],TC:["h","hb","H","hB"],TD:["h","H","hB"],TF:["H","h","hB"],TG:["H","hB"],TH:["H","h"],TJ:["H","h"],TL:["H","hB","hb","h"],TM:["H","h"],TN:["h","hB","hb","H"],TO:["h","H"],TR:["H","hB"],TT:["h","hb","H","hB"],TW:["hB","hb","h","H"],TZ:["hB","hb","H","h"],UA:["H","hB","h"],UG:["hB","hb","H","h"],UM:["h","hb","H","hB"],US:["h","hb","H","hB"],UY:["h","H","hB","hb"],UZ:["H","hB","h"],VA:["H","h","hB"],VC:["h","hb","H","hB"],VE:["h","H","hB","hb"],VG:["h","hb","H","hB"],VI:["h","hb","H","hB"],VN:["H","h"],VU:["h","H"],WF:["H","hB"],WS:["h","H"],XK:["H","hB","h"],YE:["h","hB","hb","H"],YT:["H","hB"],ZA:["H","h","hb","hB"],ZM:["h","hb","H","hB"],ZW:["H","h"],"af-ZA":["H","h","hB","hb"],"ar-001":["h","hB","hb","H"],"ca-ES":["H","h","hB"],"en-001":["h","hb","H","hB"],"en-HK":["h","hb","H","hB"],"en-IL":["H","h","hb","hB"],"en-MY":["h","hb","H","hB"],"es-BR":["H","h","hB","hb"],"es-ES":["H","h","hB","hb"],"es-GQ":["H","h","hB","hb"],"fr-CA":["H","h","hB"],"gl-ES":["H","h","hB"],"gu-IN":["hB","hb","h","H"],"hi-IN":["hB","h","H"],"it-CH":["H","h","hB"],"it-IT":["H","h","hB"],"kn-IN":["hB","h","H"],"ml-IN":["hB","h","H"],"mr-IN":["hB","hb","h","H"],"pa-IN":["hB","hb","h","H"],"ta-IN":["hB","h","hb","H"],"te-IN":["hB","h","H"],"zu-ZA":["H","hB","hb","h"]};function Kc(e){var t=e.hourCycle;if(void 0===t&&e.hourCycles&&e.hourCycles.length&&(t=e.hourCycles[0]),t)switch(t){case"h24":return"k";case"h23":return"H";case"h12":return"h";case"h11":return"K";default:throw new Error("Invalid hourCycle")}var n,i=e.language;return"root"!==i&&(n=e.maximize().region),(Yc[n||""]||Yc[i||""]||Yc["".concat(i,"-001")]||Yc["001"])[0]}var Wc=new RegExp("^".concat(zc.source,"*")),qc=new RegExp("".concat(zc.source,"*$"));function Xc(e,t){return{start:e,end:t}}var Zc=!!String.prototype.startsWith&&"_a".startsWith("a",1),Jc=!!String.fromCodePoint,Qc=!!Object.fromEntries,eu=!!String.prototype.codePointAt,tu=!!String.prototype.trimStart,nu=!!String.prototype.trimEnd,iu=!!Number.isSafeInteger?Number.isSafeInteger:function(e){return"number"==typeof e&&isFinite(e)&&Math.floor(e)===e&&Math.abs(e)<=9007199254740991},ou=!0;try{ou="a"===(null===(Gc=hu("([^\\p{White_Space}\\p{Pattern_Syntax}]*)","yu").exec("a"))||void 0===Gc?void 0:Gc[0])}catch(qA){ou=!1}var ru,au=Zc?function(e,t,n){return e.startsWith(t,n)}:function(e,t,n){return e.slice(n,n+t.length)===t},lu=Jc?String.fromCodePoint:function(){for(var e=[],t=0;tr;){if((n=e[r++])>1114111)throw RangeError(n+" is not a valid code point");i+=n<65536?String.fromCharCode(n):String.fromCharCode(55296+((n-=65536)>>10),n%1024+56320)}return i},su=Qc?Object.fromEntries:function(e){for(var t={},n=0,i=e;n=n)){var i,o=e.charCodeAt(t);return o<55296||o>56319||t+1===n||(i=e.charCodeAt(t+1))<56320||i>57343?o:i-56320+(o-55296<<10)+65536}},uu=tu?function(e){return e.trimStart()}:function(e){return e.replace(Wc,"")},du=nu?function(e){return e.trimEnd()}:function(e){return e.replace(qc,"")};function hu(e,t){return new RegExp(e,t)}if(ou){var pu=hu("([^\\p{White_Space}\\p{Pattern_Syntax}]*)","yu");ru=function(e,t){var n;return pu.lastIndex=t,null!==(n=pu.exec(e)[1])&&void 0!==n?n:""}}else ru=function(e,t){for(var n=[];;){var i=cu(e,t);if(void 0===i||_u(i)||yu(i))break;n.push(i),t+=i>=65536?2:1}return lu.apply(void 0,n)};var fu,mu=function(){function e(e,t){void 0===t&&(t={}),this.message=e,this.position={offset:0,line:1,column:1},this.ignoreTag=!!t.ignoreTag,this.locale=t.locale,this.requiresOtherClause=!!t.requiresOtherClause,this.shouldParseSkeletons=!!t.shouldParseSkeletons}return e.prototype.parse=function(){if(0!==this.offset())throw Error("parser can only be used once");return this.parseMessage(0,"",!1)},e.prototype.parseMessage=function(e,t,n){for(var i=[];!this.isEOF();){var o=this.char();if(123===o){if((r=this.parseArgument(e,n)).err)return r;i.push(r.val)}else{if(125===o&&e>0)break;if(35!==o||"plural"!==t&&"selectordinal"!==t){if(60===o&&!this.ignoreTag&&47===this.peek()){if(n)break;return this.error(fc.UNMATCHED_CLOSING_TAG,Xc(this.clonePosition(),this.clonePosition()))}if(60===o&&!this.ignoreTag&&vu(this.peek()||0)){if((r=this.parseTag(e,t)).err)return r;i.push(r.val)}else{var r;if((r=this.parseLiteral(e,t)).err)return r;i.push(r.val)}}else{var a=this.clonePosition();this.bump(),i.push({type:mc.pound,location:Xc(a,this.clonePosition())})}}}return{val:i,err:null}},e.prototype.parseTag=function(e,t){var n=this.clonePosition();this.bump();var i=this.parseTagName();if(this.bumpSpace(),this.bumpIf("/>"))return{val:{type:mc.literal,value:"<".concat(i,"/>"),location:Xc(n,this.clonePosition())},err:null};if(this.bumpIf(">")){var o=this.parseMessage(e+1,t,!0);if(o.err)return o;var r=o.val,a=this.clonePosition();if(this.bumpIf("")?{val:{type:mc.tag,value:i,children:r,location:Xc(n,this.clonePosition())},err:null}:this.error(fc.INVALID_TAG,Xc(a,this.clonePosition())))}return this.error(fc.UNCLOSED_TAG,Xc(n,this.clonePosition()))}return this.error(fc.INVALID_TAG,Xc(n,this.clonePosition()))},e.prototype.parseTagName=function(){var e=this.offset();for(this.bump();!this.isEOF()&&gu(this.char());)this.bump();return this.message.slice(e,this.offset())},e.prototype.parseLiteral=function(e,t){for(var n=this.clonePosition(),i="";;){var o=this.tryParseQuote(t);if(o)i+=o;else{var r=this.tryParseUnquoted(e,t);if(r)i+=r;else{var a=this.tryParseLeftAngleBracket();if(!a)break;i+=a}}}var l=Xc(n,this.clonePosition());return{val:{type:mc.literal,value:i,location:l},err:null}},e.prototype.tryParseLeftAngleBracket=function(){return this.isEOF()||60!==this.char()||!this.ignoreTag&&(vu(e=this.peek()||0)||47===e)?null:(this.bump(),"<");var e},e.prototype.tryParseQuote=function(e){if(this.isEOF()||39!==this.char())return null;switch(this.peek()){case 39:return this.bump(),this.bump(),"'";case 123:case 60:case 62:case 125:break;case 35:if("plural"===e||"selectordinal"===e)break;return null;default:return null}this.bump();var t=[this.char()];for(this.bump();!this.isEOF();){var n=this.char();if(39===n){if(39!==this.peek()){this.bump();break}t.push(39),this.bump()}else t.push(n);this.bump()}return lu.apply(void 0,t)},e.prototype.tryParseUnquoted=function(e,t){if(this.isEOF())return null;var n=this.char();return 60===n||123===n||35===n&&("plural"===t||"selectordinal"===t)||125===n&&e>0?null:(this.bump(),lu(n))},e.prototype.parseArgument=function(e,t){var n=this.clonePosition();if(this.bump(),this.bumpSpace(),this.isEOF())return this.error(fc.EXPECT_ARGUMENT_CLOSING_BRACE,Xc(n,this.clonePosition()));if(125===this.char())return this.bump(),this.error(fc.EMPTY_ARGUMENT,Xc(n,this.clonePosition()));var i=this.parseIdentifierIfPossible().value;if(!i)return this.error(fc.MALFORMED_ARGUMENT,Xc(n,this.clonePosition()));if(this.bumpSpace(),this.isEOF())return this.error(fc.EXPECT_ARGUMENT_CLOSING_BRACE,Xc(n,this.clonePosition()));switch(this.char()){case 125:return this.bump(),{val:{type:mc.argument,value:i,location:Xc(n,this.clonePosition())},err:null};case 44:return this.bump(),this.bumpSpace(),this.isEOF()?this.error(fc.EXPECT_ARGUMENT_CLOSING_BRACE,Xc(n,this.clonePosition())):this.parseArgumentOptions(e,t,i,n);default:return this.error(fc.MALFORMED_ARGUMENT,Xc(n,this.clonePosition()))}},e.prototype.parseIdentifierIfPossible=function(){var e=this.clonePosition(),t=this.offset(),n=ru(this.message,t),i=t+n.length;return this.bumpTo(i),{value:n,location:Xc(e,this.clonePosition())}},e.prototype.parseArgumentOptions=function(e,t,n,i){var o,r=this.clonePosition(),a=this.parseIdentifierIfPossible().value,l=this.clonePosition();switch(a){case"":return this.error(fc.EXPECT_ARGUMENT_TYPE,Xc(r,l));case"number":case"date":case"time":this.bumpSpace();var s=null;if(this.bumpIf(",")){this.bumpSpace();var c=this.clonePosition();if((g=this.parseSimpleArgStyleIfPossible()).err)return g;if(0===(p=du(g.val)).length)return this.error(fc.EXPECT_ARGUMENT_STYLE,Xc(this.clonePosition(),this.clonePosition()));s={style:p,styleLocation:Xc(c,this.clonePosition())}}if((_=this.tryParseArgumentClose(i)).err)return _;var u=Xc(i,this.clonePosition());if(s&&au(null==s?void 0:s.style,"::",0)){var d=uu(s.style.slice(2));if("number"===a)return(g=this.parseNumberSkeletonFromString(d,s.styleLocation)).err?g:{val:{type:mc.number,value:n,location:u,style:g.val},err:null};if(0===d.length)return this.error(fc.EXPECT_DATE_TIME_SKELETON,u);var h=d;this.locale&&(h=function(e,t){for(var n="",i=0;i>1),s=Kc(t);for("H"!=s&&"k"!=s||(l=0);l-- >0;)n+="a";for(;a-- >0;)n=s+n}else n+="J"===o?"H":o}return n}(d,this.locale));var p={type:vc.dateTime,pattern:h,location:s.styleLocation,parsedOptions:this.shouldParseSkeletons?Lc(h):{}};return{val:{type:"date"===a?mc.date:mc.time,value:n,location:u,style:p},err:null}}return{val:{type:"number"===a?mc.number:"date"===a?mc.date:mc.time,value:n,location:u,style:null!==(o=null==s?void 0:s.style)&&void 0!==o?o:null},err:null};case"plural":case"selectordinal":case"select":var f=this.clonePosition();if(this.bumpSpace(),!this.bumpIf(","))return this.error(fc.EXPECT_SELECT_ARGUMENT_OPTIONS,Xc(f,va({},f)));this.bumpSpace();var m=this.parseIdentifierIfPossible(),v=0;if("select"!==a&&"offset"===m.value){if(!this.bumpIf(":"))return this.error(fc.EXPECT_PLURAL_ARGUMENT_OFFSET_VALUE,Xc(this.clonePosition(),this.clonePosition()));var g;if(this.bumpSpace(),(g=this.tryParseDecimalInteger(fc.EXPECT_PLURAL_ARGUMENT_OFFSET_VALUE,fc.INVALID_PLURAL_ARGUMENT_OFFSET_VALUE)).err)return g;this.bumpSpace(),m=this.parseIdentifierIfPossible(),v=g.val}var _,y=this.tryParsePluralOrSelectOptions(e,a,t,m);if(y.err)return y;if((_=this.tryParseArgumentClose(i)).err)return _;var b=Xc(i,this.clonePosition());return"select"===a?{val:{type:mc.select,value:n,options:su(y.val),location:b},err:null}:{val:{type:mc.plural,value:n,options:su(y.val),offset:v,pluralType:"plural"===a?"cardinal":"ordinal",location:b},err:null};default:return this.error(fc.INVALID_ARGUMENT_TYPE,Xc(r,l))}},e.prototype.tryParseArgumentClose=function(e){return this.isEOF()||125!==this.char()?this.error(fc.EXPECT_ARGUMENT_CLOSING_BRACE,Xc(e,this.clonePosition())):(this.bump(),{val:!0,err:null})},e.prototype.parseSimpleArgStyleIfPossible=function(){for(var e=0,t=this.clonePosition();!this.isEOF();){switch(this.char()){case 39:this.bump();var n=this.clonePosition();if(!this.bumpUntil("'"))return this.error(fc.UNCLOSED_QUOTE_IN_ARGUMENT_STYLE,Xc(n,this.clonePosition()));this.bump();break;case 123:e+=1,this.bump();break;case 125:if(!(e>0))return{val:this.message.slice(t.offset,this.offset()),err:null};e-=1;break;default:this.bump()}}return{val:this.message.slice(t.offset,this.offset()),err:null}},e.prototype.parseNumberSkeletonFromString=function(e,t){var n=[];try{n=function(e){if(0===e.length)throw new Error("Number skeleton cannot be empty");for(var t=e.split(Nc).filter((function(e){return e.length>0})),n=[],i=0,o=t;i=48&&a<=57))break;o=!0,r=10*r+(a-48),this.bump()}var l=Xc(i,this.clonePosition());return o?iu(r*=n)?{val:r,err:null}:this.error(t,l):this.error(e,l)},e.prototype.offset=function(){return this.position.offset},e.prototype.isEOF=function(){return this.offset()===this.message.length},e.prototype.clonePosition=function(){return{offset:this.position.offset,line:this.position.line,column:this.position.column}},e.prototype.char=function(){var e=this.position.offset;if(e>=this.message.length)throw Error("out of bound");var t=cu(this.message,e);if(void 0===t)throw Error("Offset ".concat(e," is at invalid UTF-16 code unit boundary"));return t},e.prototype.error=function(e,t){return{val:null,err:{kind:e,message:this.message,location:t}}},e.prototype.bump=function(){if(!this.isEOF()){var e=this.char();10===e?(this.position.line+=1,this.position.column=1,this.position.offset+=1):(this.position.column+=1,this.position.offset+=e<65536?1:2)}},e.prototype.bumpIf=function(e){if(au(this.message,e,this.offset())){for(var t=0;t=0?(this.bumpTo(n),!0):(this.bumpTo(this.message.length),!1)},e.prototype.bumpTo=function(e){if(this.offset()>e)throw Error("targetOffset ".concat(e," must be greater than or equal to the current offset ").concat(this.offset()));for(e=Math.min(e,this.message.length);;){var t=this.offset();if(t===e)break;if(t>e)throw Error("targetOffset ".concat(e," is at invalid UTF-16 code unit boundary"));if(this.bump(),this.isEOF())break}},e.prototype.bumpSpace=function(){for(;!this.isEOF()&&_u(this.char());)this.bump()},e.prototype.peek=function(){if(this.isEOF())return null;var e=this.char(),t=this.offset(),n=this.message.charCodeAt(t+(e>=65536?2:1));return null!=n?n:null},e}();function vu(e){return e>=97&&e<=122||e>=65&&e<=90}function gu(e){return 45===e||46===e||e>=48&&e<=57||95===e||e>=97&&e<=122||e>=65&&e<=90||183==e||e>=192&&e<=214||e>=216&&e<=246||e>=248&&e<=893||e>=895&&e<=8191||e>=8204&&e<=8205||e>=8255&&e<=8256||e>=8304&&e<=8591||e>=11264&&e<=12271||e>=12289&&e<=55295||e>=63744&&e<=64975||e>=65008&&e<=65533||e>=65536&&e<=983039}function _u(e){return e>=9&&e<=13||32===e||133===e||e>=8206&&e<=8207||8232===e||8233===e}function yu(e){return e>=33&&e<=35||36===e||e>=37&&e<=39||40===e||41===e||42===e||43===e||44===e||45===e||e>=46&&e<=47||e>=58&&e<=59||e>=60&&e<=62||e>=63&&e<=64||91===e||92===e||93===e||94===e||96===e||123===e||124===e||125===e||126===e||161===e||e>=162&&e<=165||166===e||167===e||169===e||171===e||172===e||174===e||176===e||177===e||182===e||187===e||191===e||215===e||247===e||e>=8208&&e<=8213||e>=8214&&e<=8215||8216===e||8217===e||8218===e||e>=8219&&e<=8220||8221===e||8222===e||8223===e||e>=8224&&e<=8231||e>=8240&&e<=8248||8249===e||8250===e||e>=8251&&e<=8254||e>=8257&&e<=8259||8260===e||8261===e||8262===e||e>=8263&&e<=8273||8274===e||8275===e||e>=8277&&e<=8286||e>=8592&&e<=8596||e>=8597&&e<=8601||e>=8602&&e<=8603||e>=8604&&e<=8607||8608===e||e>=8609&&e<=8610||8611===e||e>=8612&&e<=8613||8614===e||e>=8615&&e<=8621||8622===e||e>=8623&&e<=8653||e>=8654&&e<=8655||e>=8656&&e<=8657||8658===e||8659===e||8660===e||e>=8661&&e<=8691||e>=8692&&e<=8959||e>=8960&&e<=8967||8968===e||8969===e||8970===e||8971===e||e>=8972&&e<=8991||e>=8992&&e<=8993||e>=8994&&e<=9e3||9001===e||9002===e||e>=9003&&e<=9083||9084===e||e>=9085&&e<=9114||e>=9115&&e<=9139||e>=9140&&e<=9179||e>=9180&&e<=9185||e>=9186&&e<=9254||e>=9255&&e<=9279||e>=9280&&e<=9290||e>=9291&&e<=9311||e>=9472&&e<=9654||9655===e||e>=9656&&e<=9664||9665===e||e>=9666&&e<=9719||e>=9720&&e<=9727||e>=9728&&e<=9838||9839===e||e>=9840&&e<=10087||10088===e||10089===e||10090===e||10091===e||10092===e||10093===e||10094===e||10095===e||10096===e||10097===e||10098===e||10099===e||10100===e||10101===e||e>=10132&&e<=10175||e>=10176&&e<=10180||10181===e||10182===e||e>=10183&&e<=10213||10214===e||10215===e||10216===e||10217===e||10218===e||10219===e||10220===e||10221===e||10222===e||10223===e||e>=10224&&e<=10239||e>=10240&&e<=10495||e>=10496&&e<=10626||10627===e||10628===e||10629===e||10630===e||10631===e||10632===e||10633===e||10634===e||10635===e||10636===e||10637===e||10638===e||10639===e||10640===e||10641===e||10642===e||10643===e||10644===e||10645===e||10646===e||10647===e||10648===e||e>=10649&&e<=10711||10712===e||10713===e||10714===e||10715===e||e>=10716&&e<=10747||10748===e||10749===e||e>=10750&&e<=11007||e>=11008&&e<=11055||e>=11056&&e<=11076||e>=11077&&e<=11078||e>=11079&&e<=11084||e>=11085&&e<=11123||e>=11124&&e<=11125||e>=11126&&e<=11157||11158===e||e>=11159&&e<=11263||e>=11776&&e<=11777||11778===e||11779===e||11780===e||11781===e||e>=11782&&e<=11784||11785===e||11786===e||11787===e||11788===e||11789===e||e>=11790&&e<=11798||11799===e||e>=11800&&e<=11801||11802===e||11803===e||11804===e||11805===e||e>=11806&&e<=11807||11808===e||11809===e||11810===e||11811===e||11812===e||11813===e||11814===e||11815===e||11816===e||11817===e||e>=11818&&e<=11822||11823===e||e>=11824&&e<=11833||e>=11834&&e<=11835||e>=11836&&e<=11839||11840===e||11841===e||11842===e||e>=11843&&e<=11855||e>=11856&&e<=11857||11858===e||e>=11859&&e<=11903||e>=12289&&e<=12291||12296===e||12297===e||12298===e||12299===e||12300===e||12301===e||12302===e||12303===e||12304===e||12305===e||e>=12306&&e<=12307||12308===e||12309===e||12310===e||12311===e||12312===e||12313===e||12314===e||12315===e||12316===e||12317===e||e>=12318&&e<=12319||12320===e||12336===e||64830===e||64831===e||e>=65093&&e<=65094}function bu(e){e.forEach((function(e){if(delete e.location,Ac(e)||Sc(e))for(var t in e.options)delete e.options[t].location,bu(e.options[t].value);else xc(e)&&Oc(e.style)||(Ec(e)||Cc(e))&&Mc(e.style)?delete e.style.location:Ic(e)&&bu(e.children)}))}function ku(e,t){void 0===t&&(t={}),t=va({shouldParseSkeletons:!0,requiresOtherClause:!0},t);var n=new mu(e,t).parse();if(n.err){var i=SyntaxError(fc[n.err.kind]);throw i.location=n.err.location,i.originalMessage=n.err.message,i}return(null==t?void 0:t.captureLocation)||bu(n.val),n.val}!function(e){e.MISSING_VALUE="MISSING_VALUE",e.INVALID_VALUE="INVALID_VALUE",e.MISSING_INTL_API="MISSING_INTL_API"}(fu||(fu={}));var wu,xu=function(e){function t(t,n,i){var o=e.call(this,t)||this;return o.code=n,o.originalMessage=i,o}return ma(t,e),t.prototype.toString=function(){return"[formatjs Error: ".concat(this.code,"] ").concat(this.message)},t}(Error),Eu=function(e){function t(t,n,i,o){return e.call(this,'Invalid values for "'.concat(t,'": "').concat(n,'". Options are "').concat(Object.keys(i).join('", "'),'"'),fu.INVALID_VALUE,o)||this}return ma(t,e),t}(xu),Cu=function(e){function t(t,n,i){return e.call(this,'Value for "'.concat(t,'" must be of type ').concat(n),fu.INVALID_VALUE,i)||this}return ma(t,e),t}(xu),Au=function(e){function t(t,n){return e.call(this,'The intl string context variable "'.concat(t,'" was not provided to the string "').concat(n,'"'),fu.MISSING_VALUE,n)||this}return ma(t,e),t}(xu);function Su(e){return"function"==typeof e}function Tu(e,t,n,i,o,r,a){if(1===e.length&&kc(e[0]))return[{type:wu.literal,value:e[0].value}];for(var l=[],s=0,c=e;s0?new Intl.Locale(t[0]):new Intl.Locale("string"==typeof e?e:e[0])}},e.__parse=ku,e.formats={number:{integer:{maximumFractionDigits:0},currency:{style:"currency"},percent:{style:"percent"}},date:{short:{month:"numeric",day:"numeric",year:"2-digit"},medium:{month:"short",day:"numeric",year:"numeric"},long:{month:"long",day:"numeric",year:"numeric"},full:{weekday:"long",month:"long",day:"numeric",year:"numeric"}},time:{short:{hour:"numeric",minute:"numeric"},medium:{hour:"numeric",minute:"numeric",second:"numeric"},long:{hour:"numeric",minute:"numeric",second:"numeric",timeZoneName:"short"},full:{hour:"numeric",minute:"numeric",second:"numeric",timeZoneName:"short"}}},e}(),zu={not_found:"لم يتم العثور على الكيان"},Pu={card:{chips:{alignment:"محاذاة"},climate:{hvac_modes:"أوضاع HVAC",show_temperature_control:"التحكم في درجة الحرارة؟"},cover:{show_buttons_control:"أزرار التحكم؟",show_position_control:"التحكم في الموقع؟",show_tilt_position_control:"التحكم في الإمالة؟"},empty:{no_config_options:"لا تحتوي هذه البطاقة على خيارات التكوين."},fan:{show_direction_control:"التحكم بالإتجاه؟",show_oscillate_control:"التحكم في التذبذب؟",show_percentage_control:"التحكم في النسبة المئوية؟"},generic:{collapsible_controls:"تصغير عناصر التحكم عند الإيقاف",color:"اللون",content_info:"المحتوى",fill_container:"ملئ الحاوية",icon_animation:"تحريك الرمز عندما يكون نشطًا؟",icon_color:"لون الأيقونة",icon_type:"نوع الأيقونة",layout:"التخطيط",primary_info:"المعلومات الأساسية",secondary_info:"المعلومات الفرعية",use_entity_picture:"استخدم صورة الكيان؟"},humidifier:{show_target_humidity_control:"التحكم في الرطوبة؟?"},light:{incompatible_controls:"قد لا يتم عرض بعض عناصر التحكم إذا كان الضوء الخاص بك لا يدعم الميزة.",show_brightness_control:"التحكم في السطوع؟",show_color_control:"التحكم في اللون؟",show_color_temp_control:"التحكم في درجة حرارة اللون؟",use_light_color:"استخدم لون فاتح"},lock:{lock:"مقفل",open:"مفتوح",unlock:"إلغاء قفل"},"media-player":{media_controls:"التحكم في الوسائط",media_controls_list:{next:"التالي",on_off:"تشغيل/إيقاف",play_pause_stop:"تشغيل/إيقاف مؤقت/إيقاف",previous:"السابق",repeat:"وضع التكرار",shuffle:"خلط"},show_volume_level:"إظهار مستوى الصوت",use_media_artwork:"استخدم صورة الوسائط",use_media_info:"استخدم معلومات الوسائط",volume_controls:"التحكم في الصوت",volume_controls_list:{volume_buttons:"أزرار الصوت",volume_mute:"كتم",volume_set:"مستوى الصوت"}},number:{display_mode:"وضع العرض",display_mode_list:{buttons:"الأزرار",default:"الافتراضي(سحب)",slider:"سحب"}},template:{badge_color:"لون الشارة",badge_icon:"أيقونة الشارة",content:"المحتوى",entity_extra:"تستخدم في القوالب والإجراءات",label:"التسمية",multiline_secondary:"متعدد الأسطر الثانوية؟",picture:"صورة (ستحل محل الأيقونة)",primary:"المعلومات الأساسية",secondary:"المعلومات الثانوية"},title:{subtitle:"العنوان الفرعي",subtitle_tap_action:"إجراء النقر على العنوان الفرعي",title:"العنوان",title_tap_action:"إجراء النقر على العنوان"},update:{show_buttons_control:"أزرار التحكم؟"},vacuum:{commands:"الاوامر",commands_list:{on_off:"تشغيل/إيقاف"}},weather:{show_conditions:"الأحوال الجوية؟",show_temperature:"الطقس؟"}},chip:{"chip-picker":{add:"أضف رقاقة",chips:"رقاقات",clear:"مسح",edit:"تعديل",select:"اختر الرقاقة",types:{action:"إجراء","alarm-control-panel":"تنبيه",back:"رجوع",conditional:"مشروط",entity:"الكيان",light:"مظيء",menu:"القائمة",quickbar:"تبويب سريع",spacer:"مساحة",template:"قالب",weather:"الطقس"}},conditional:{chip:"رقاقة"},sub_element_editor:{title:"محرر الرقاقة"}},form:{alignment_picker:{values:{center:"توسيط",default:"المحاذاة الافتراضية",end:"نهاية",justify:"مساواة",start:"بداية"}},color_picker:{values:{default:"اللون الإفتراضي"}},icon_type_picker:{values:{default:"النوع افتراضي","entity-picture":"صورة الكيان",icon:"أيقونة",none:"لا شئ"}},info_picker:{values:{default:"المعلومات الافتراضية","last-changed":"آخر تغيير","last-updated":"آخر تحديث",name:"الإسم",none:"لا شئ",state:"الحالة"}},layout_picker:{values:{default:"تخطيط افتراضي",horizontal:"تخطيط أفقي",vertical:"تخطيط رأسي"}}}},Lu={card:zu,editor:Pu},Nu=Object.freeze({__proto__:null,card:zu,default:Lu,editor:Pu}),ju={card:{chips:{alignment:"Подравняване"},climate:{hvac_modes:"HVAC Режими",show_temperature_control:"Контрол на температурата?"},cover:{show_buttons_control:"Контролни бутони?",show_position_control:"Контрол на позицията?",show_tilt_position_control:"Контрол на наклона?"},fan:{show_oscillate_control:"Контрол на трептенето?",show_percentage_control:"Процентов контрол?"},generic:{collapsible_controls:"Свий контролите при изключен",content_info:"Съдържание",fill_container:"Изпълване на контейнера",icon_animation:"Анимирай иконата при активен?",icon_color:"Цвят на икона",icon_type:"Тип на икона",layout:"Оформление",primary_info:"Първостепенна информация",secondary_info:"Второстепенна информация",use_entity_picture:"Използвай снимката на обекта?"},humidifier:{show_target_humidity_control:"Контрол на влажността?"},light:{incompatible_controls:"Някои опции могат да бъдат скрити при условие че осветителното тяло не поддържа фунцията.",show_brightness_control:"Контрол на яркостта?",show_color_control:"Контрол на цвета?",show_color_temp_control:"Контрол на температурата?",use_light_color:"Използвай цвета на светлината"},lock:{lock:"Заключен",open:"Отворен",unlock:"Отключен"},"media-player":{media_controls:"Контрол на Медиата",media_controls_list:{next:"Следващ",on_off:"Вкл./Изкл.",play_pause_stop:"Пусни/пауза/стоп",previous:"Предишен",repeat:"Повтаряне",shuffle:"Разбъркано"},show_volume_level:"Покажи контрола за звук",use_media_artwork:"Използвай визуалните детайли от медията",use_media_info:"Използвай информация от медията",volume_controls:"Контрол на звука",volume_controls_list:{volume_buttons:"Бутони за звук",volume_mute:"Заглуши",volume_set:"Ниво на звука"}},template:{badge_color:"Цвят на значка",badge_icon:"Икона на значка",content:"Съдържание",entity_extra:"Използван в шаблони и действия",multiline_secondary:"Много-редова второстепенна информация?",picture:"Картина (ще замени иконата)",primary:"Първостепенна информация",secondary:"Второстепенна информация"},title:{subtitle:"Подзаглавие",title:"Заглавие"},update:{show_buttons_control:"Контролни бутони?"},vacuum:{commands:"Конади",commands_list:{on_off:"Вкл./Изкл."}},weather:{show_conditions:"Условия?",show_temperature:"Температура?"}},chip:{"chip-picker":{add:"Добави чип",chips:"Чипове",clear:"Изчисти",edit:"Редактирай",select:"Избери чип",types:{action:"Действия","alarm-control-panel":"Аларма",back:"Назад",conditional:"Условни",entity:"Обект",light:"Осветление",menu:"Меню",template:"Шаблон",weather:"Време"}},conditional:{chip:"Чип"},sub_element_editor:{title:"Чип редактор"}},form:{alignment_picker:{values:{center:"Център",default:"Основно подравняване",end:"Край",justify:"Подравнен",start:"Старт"}},color_picker:{values:{default:"Основен цвят"}},icon_type_picker:{values:{default:"Основен тип","entity-picture":"Картина на обекта",icon:"Икона",none:"Липсва"}},info_picker:{values:{default:"Основна информация","last-changed":"Последно Променен","last-updated":"Последно Актуализиран",name:"Име",none:"Липсва",state:"Състояние"}},layout_picker:{values:{default:"Основно оформление",horizontal:"Хоризонтално оформление",vertical:"Вертикално оформление"}}}},$u={editor:ju},Bu=Object.freeze({__proto__:null,default:$u,editor:ju}),Ru={not_found:"No s'ha trobat l'entitat"},Hu={card:{chips:{alignment:"Alineació"},climate:{hvac_modes:"Modes HVAC",show_temperature_control:"Control de temperatura?"},cover:{show_buttons_control:"Botons de control?",show_position_control:"Control de posició?",show_tilt_position_control:"Control d'inclinació?"},fan:{show_oscillate_control:"Control d'oscil·lació?",show_percentage_control:"Control de percentatge?"},generic:{collapsible_controls:"Amaga els controls en desactivar",color:"Color",content_info:"Contingut",fill_container:"Emplena el contenidor",icon_animation:"Animar icona en activar?",icon_color:"Color d'icona",icon_type:"Tipus d'icona",layout:"Distribució",primary_info:"Informació primaria",secondary_info:"Informació secundaria",use_entity_picture:"Fer servir la imatge de l'entitat?"},humidifier:{show_target_humidity_control:"Control d'humitat?"},light:{incompatible_controls:"Alguns controls no es mostraran si l'entitat no suporta eixa funció.",show_brightness_control:"Control de brillantor?",show_color_control:"Control de color?",show_color_temp_control:"Control de la temperatura del color?",use_light_color:"Fes servir el color del llum"},lock:{lock:"Bloqueja",open:"Obri",unlock:"Desbloqueja"},"media-player":{media_controls:"Controls multimèdia",media_controls_list:{next:"Pista següent",on_off:"Engegar/Apagar",play_pause_stop:"Reproduïr/Pausar/Detindre",previous:"Pista anterior",repeat:"Mode de repetició",shuffle:"Mesclar"},show_volume_level:"Mostra el nivell de volum",use_media_artwork:"Fes servir l'art multimèdia",use_media_info:"Empra la informació multimèdia",volume_controls:"Controls de volum",volume_controls_list:{volume_buttons:"Botons de volum",volume_mute:"Silenci",volume_set:"Nivell de volum"}},number:{display_mode:"Mode de visualització",display_mode_list:{buttons:"Botons",default:"Per defecte (lliscant)",slider:"Lliscant"}},template:{badge_color:"Color de la insígnia",badge_icon:"Icona de la insígnia",content:"Contingut",entity_extra:"Utilitzats en plantilles i accions",label:"Etiqueta",multiline_secondary:"Secundaria en varies línies?",picture:"Imatge (reemplaçarà la icona)",primary:"Informació primaria",secondary:"Informació secundaria"},title:{subtitle:"Subtítol",subtitle_tap_action:"Acció en tocar el subtítol",title:"Títol",title_tap_action:"Acció en tocar el títol"},update:{show_buttons_control:"Botons de control?"},vacuum:{commands:"Comandaments",commands_list:{on_off:"Engegar/Apagar"}},weather:{show_conditions:"Condicions?",show_temperature:"Temperatura?"}},chip:{"chip-picker":{add:"Afegir xip",chips:"Xips",clear:"Buidar",edit:"Editar",select:"Seleccionar chip",types:{action:"Acció","alarm-control-panel":"Alarma",back:"Tornar",conditional:"Condicional",entity:"Entitat",light:"Llum",menu:"Menú",spacer:"Espai",template:"Plantilla",weather:"Oratge"}},conditional:{chip:"Xip"},sub_element_editor:{title:"Editor de xips"}},form:{alignment_picker:{values:{center:"Centre",default:"Alineació per defecte",end:"Final",justify:"Justifica",start:"Inici"}},color_picker:{values:{default:"Color per defecte"}},icon_type_picker:{values:{default:"Tipus per defecte","entity-picture":"Entitat d'imatge",icon:"Icona",none:"Cap"}},info_picker:{values:{default:"Informació per defecte","last-changed":"Últim Canvi","last-updated":"Última Actualització",name:"Nom",none:"Cap",state:"Estat"}},layout_picker:{values:{default:"Distribució per defecte",horizontal:"Distribució horitzontal",vertical:"Distribució vertical"}}}},Du={card:Ru,editor:Hu},Uu=Object.freeze({__proto__:null,card:Ru,default:Du,editor:Hu}),Vu={not_found:"Entita nebyla nalezena"},Fu={card:{chips:{alignment:"Zarovnání"},climate:{hvac_modes:"Režimy HVAC",show_temperature_control:"Ovládání teploty?"},cover:{show_buttons_control:"Zobrazit ovládací tlačítka?",show_position_control:"Zobrazit ovládání polohy?",show_tilt_position_control:"Zobrazit ovládání náklonu?"},fan:{show_oscillate_control:"Ovládání oscilaceM",show_percentage_control:"Ovládání v procentech?"},generic:{collapsible_controls:"Pokud je vypnuto, skrýt ovládací prvky",content_info:"Obsah",fill_container:"Vyplnit prostor",icon_animation:"Pokud je aktivní, animovat ikonu?",icon_color:"Barva ikony",icon_type:"Typ ikony",layout:"Rozložení",primary_info:"Primární informace",secondary_info:"Sekundární informace",use_entity_picture:"Použít ikonu entity?"},humidifier:{show_target_humidity_control:"Ovládání vlhkosti?"},light:{incompatible_controls:"Některé ovládací prvky se nemusí zobrazit, pokud vaše světlo tuto funkci nepodporuje.",show_brightness_control:"Ovládání jasu?",show_color_control:"Ovládání barvy světla?",show_color_temp_control:"Ovládání teploty světla?",use_light_color:"Ikona podle barvy světla?"},lock:{lock:"Zamčeno",open:"Otevřeno",unlock:"Odemčeno"},"media-player":{media_controls:"Ovládání médií",media_controls_list:{next:"Další stopa",on_off:"Zapnout/Vypnout",play_pause_stop:"Přehrát/Pauza/Zastavit",previous:"Předchozí stopa",repeat:"Režim opakování",shuffle:"Zamíchat"},show_volume_level:"Zobrazit úroveň hlasitosti",use_media_artwork:"Použít artwork z média",use_media_info:"Použít informace z média",volume_controls:"Ovládání hlasitosti",volume_controls_list:{volume_buttons:"Tlačítka hlasitosti",volume_mute:"Ztlumit",volume_set:"Úroveň hlasitosti"}},number:{display_mode:"Režim zobrazení",display_mode_list:{buttons:"Tlačítka",default:"Výchozí (posuvník)",slider:"Posuvník"}},template:{badge_color:"Barva odznaku",badge_icon:"Ikona odznaku",content:"Obsah",entity_extra:"Použito v šablonách a akcích",multiline_secondary:"Víceřádková sekundární informace?",picture:"Obrázek (nahradí ikonu)",primary:"Primární informace",secondary:"Sekundární informace"},title:{subtitle:"Popis",subtitle_tap_action:"Akce při klepnutí na popis",title:"Nadpis",title_tap_action:"Akce při klepnutí na nadpis"},update:{show_buttons_control:"Zobrazit ovládací tlačítka?"},vacuum:{commands:"Příkazy",commands_list:{on_off:"Zapnout/Vypnout"}},weather:{show_conditions:"Zobrazit podmínky?",show_temperature:"Zobrazit teplotu?"}},chip:{"chip-picker":{add:"Přidat tlačítko",chips:"Tlačítka",clear:"Vymazat",edit:"Upravit",select:"Vybrat tlačítko",types:{action:"Akce","alarm-control-panel":"Alarm",back:"Zpět",conditional:"Podmínka",entity:"Entita",light:"Světlo",menu:"Menu",spacer:"Mezera",template:"Šablona",weather:"Počasí"}},conditional:{chip:"Tlačítko"},sub_element_editor:{title:"Editor tlačítek"}},form:{alignment_picker:{values:{center:"Na střed",default:"Výchozí zarovnání",end:"Na konec",justify:"Do bloku",start:"Na začátek"}},color_picker:{values:{default:"Výchozí barva"}},icon_type_picker:{values:{default:"Výchozí typ","entity-picture":"Ikona entity",icon:"Ikona",none:"Nic"}},info_picker:{values:{default:"Výchozí informace","last-changed":"Poslední změna","last-updated":"Poslední aktualizace",name:"Název",none:"Nic",state:"Stav"}},layout_picker:{values:{default:"Výchozí rozložení",horizontal:"Vodorovné rozložení",vertical:"Svislé rozložení"}}}},Gu={card:Vu,editor:Fu},Yu=Object.freeze({__proto__:null,card:Vu,default:Gu,editor:Fu}),Ku={not_found:"Enhed ikke fundet"},Wu={card:{chips:{alignment:"Justering"},climate:{hvac_modes:"HVAC-tilstande",show_temperature_control:"Temperaturkontrol?"},cover:{show_buttons_control:"Betjeningsknapper?",show_position_control:"Positionskontrol?",show_tilt_position_control:"Tiltkontrol?"},fan:{show_oscillate_control:"Oscillationskontrol?",show_percentage_control:"Procentkontrol?"},generic:{collapsible_controls:"Skjul kontroller når slukket",color:"Farve",content_info:"Indhold",fill_container:"Fyld container",icon_animation:"Animér ikon når aktiv?",icon_color:"Ikon farve",icon_type:"Ikon type",layout:"Layout",primary_info:"Primær information",secondary_info:"Sekundær information",use_entity_picture:"Brug enhedsbillede?"},humidifier:{show_target_humidity_control:"Luftfugtighedskontrol?"},light:{incompatible_controls:"Nogle kontroller vises muligvis ikke, hvis dit lys ikke understøtter funktionen.",show_brightness_control:"Lysstyrkekontrol?",show_color_control:"Farvekontrol?",show_color_temp_control:"Temperaturfarvekontrol?",use_light_color:"Brug lysfarve"},lock:{lock:"Lås",open:"Åben",unlock:"Lås op"},"media-player":{media_controls:"Mediekontrol",media_controls_list:{next:"Næste nummer",on_off:"Tænd/Sluk",play_pause_stop:"Afspil/Pause/Stop",previous:"Forrige nummer",repeat:"Gentagelsestilstand",shuffle:"Bland"},show_volume_level:"Vis lydstyrke",use_media_artwork:"Brug mediebilleder",use_media_info:"Brug medieinformation",volume_controls:"Lydstyrkekontrol",volume_controls_list:{volume_buttons:"Lydstyrkeknapper",volume_mute:"Lydløs",volume_set:"Lydstyrke"}},number:{display_mode:"Visningstilstand",display_mode_list:{buttons:"Knapper",default:"Standard (slider)",slider:"Slider"}},template:{badge_color:"Badge farve",badge_icon:"Badge ikon",content:"Indhold",entity_extra:"Anvendes i skabeloner og handlinger",label:"Label",multiline_secondary:"Multi-linje sekundær?",picture:"Billede (erstatter ikonet)",primary:"Primær information",secondary:"Sekundær information"},title:{subtitle:"Undertitel",subtitle_tap_action:"Undertitel tryk handling",title:"Titel",title_tap_action:"Title tryk handling"},update:{show_buttons_control:"Betjeningsknapper?"},vacuum:{commands:"Kommandoer",commands_list:{on_off:"Slå til/fra"}},weather:{show_conditions:"Vejrforhold?",show_temperature:"Temperatur?"}},chip:{"chip-picker":{add:"Tilføj chip",chips:"Chips",clear:"Nulstil",edit:"Rediger",select:"Vælg chip",types:{action:"Handling","alarm-control-panel":"Alarm",back:"Tilbage",conditional:"Betinget",entity:"Enhed",light:"Lys",menu:"Menu",spacer:"Afstand",template:"Skabelon",weather:"Vejr"}},conditional:{chip:"Chip"},sub_element_editor:{title:"Chip-editor"}},form:{alignment_picker:{values:{center:"Centrer",default:"Standard justering",end:"Slut",justify:"Lige margener",start:"Start"}},color_picker:{values:{default:"Standardfarve"}},icon_type_picker:{values:{default:"Standard type","entity-picture":"Enhedsbillede",icon:"Ikon",none:"Ingen"}},info_picker:{values:{default:"Standard information","last-changed":"Sidst ændret","last-updated":"Sidst opdateret",name:"Navn",none:"Ingen",state:"Status"}},layout_picker:{values:{default:"Standard layout",horizontal:"Horisontal layout",vertical:"Vertikal layout"}}}},qu={card:Ku,editor:Wu},Xu=Object.freeze({__proto__:null,card:Ku,default:qu,editor:Wu}),Zu={not_found:"Entität nicht gefunden"},Ju={card:{chips:{alignment:"Ausrichtung"},climate:{hvac_modes:"HVAC-Modi",show_temperature_control:"Temperatursteuerung?"},cover:{show_buttons_control:"Schaltflächensteuerung?",show_position_control:"Positionssteuerung?",show_tilt_position_control:"Winkelsteuerung?"},empty:{no_config_options:"Diese Karte hat keine Optionen."},fan:{show_direction_control:"Richtungssteuerung?",show_oscillate_control:"Oszillationssteuerung?",show_percentage_control:"Prozentuale Kontrolle?"},generic:{collapsible_controls:"Schieberegler einklappen, wenn aus",color:"Farbe",content_info:"Inhalt",fill_container:"Container ausfüllen",icon_animation:"Icon animieren, wenn aktiv?",icon_color:"Icon-Farbe",icon_type:"Icon-Typ",layout:"Layout",primary_info:"Primäre Information",secondary_info:"Sekundäre Information",use_entity_picture:"Entitätsbild verwenden?"},humidifier:{show_target_humidity_control:"Luftfeuchtigkeitssteuerung?"},light:{incompatible_controls:"Einige Steuerelemente werden möglicherweise nicht angezeigt, wenn Ihr Licht diese Funktion nicht unterstützt.",show_brightness_control:"Helligkeitsregelung?",show_color_control:"Farbsteuerung?",show_color_temp_control:"Farbtemperatursteuerung?",use_light_color:"Farbsteuerung verwenden"},lock:{lock:"Verriegeln",open:"Öffnen",unlock:"Entriegeln"},"media-player":{media_controls:"Mediensteuerung",media_controls_list:{next:"Nächster Titel",on_off:"Ein/Aus",play_pause_stop:"Play/Pause/Stop",previous:"Vorheriger Titel",repeat:"Wiederholen",shuffle:"Zufällige Wiedergabe"},show_volume_level:"Lautstärke-Level anzeigen",use_media_artwork:"Mediengrafik verwenden",use_media_info:"Medieninfos verwenden",volume_controls:"Lautstärkesteuerung",volume_controls_list:{volume_buttons:"Lautstärke-Buttons",volume_mute:"Stumm",volume_set:"Lautstärke-Level"}},number:{display_mode:"Anzeigemodus",display_mode_list:{buttons:"Buttons",default:"Standard (Schieberegler)",slider:"Schieberegler"}},template:{badge_color:"Badge-Farbe",badge_icon:"Badge-Icon",content:"Inhalt",entity_extra:"Wird in Vorlagen und Aktionen verwendet",label:"Beschriftung",multiline_secondary:"Mehrzeilig sekundär?",picture:"Bild (ersetzt das Icon)",primary:"Primäre Information",secondary:"Sekundäre Information"},title:{subtitle:"Untertitel",subtitle_tap_action:"Untertitel Tipp-Aktion",title:"Titel",title_tap_action:"Titel Tipp-Aktion"},update:{show_buttons_control:"Schaltflächensteuerung?"},vacuum:{commands:"Befehle",commands_list:{on_off:"An/Ausschalten"}},weather:{show_conditions:"Bedingungen?",show_temperature:"Temperatur?"}},chip:{"chip-picker":{add:"Chip hinzufügen",chips:"Chips",clear:"Löschen",edit:"Editieren",select:"Chip auswählen",types:{action:"Aktion","alarm-control-panel":"Alarm",back:"Zurück",conditional:"Bedingung",entity:"Entität",light:"Licht",menu:"Menü",quickbar:"Quickbar",spacer:"Abstand",template:"Vorlage",weather:"Wetter"}},conditional:{chip:"Chip"},sub_element_editor:{title:"Chip Editor"}},form:{alignment_picker:{values:{center:"Mitte",default:"Standard",end:"Ende",justify:"Ausrichten",start:"Anfang"}},color_picker:{values:{default:"Standardfarbe"}},icon_type_picker:{values:{default:"Standard-Typ","entity-picture":"Entitätsbild",icon:"Icon",none:"Keines"}},info_picker:{values:{default:"Standard-Information","last-changed":"Letzte Änderung","last-updated":"Letzte Aktualisierung",name:"Name",none:"Keine",state:"Zustand"}},layout_picker:{values:{default:"Standard-Layout",horizontal:"Horizontales Layout",vertical:"Vertikales Layout"}}}},Qu={card:Zu,editor:Ju},ed=Object.freeze({__proto__:null,card:Zu,default:Qu,editor:Ju}),td={card:{chips:{alignment:"Ευθυγράμμιση"},cover:{show_buttons_control:"Έλεγχος κουμπιών;",show_position_control:"Έλεγχος θέσης;"},fan:{show_oscillate_control:"Έλεγχος ταλάντωσης;",show_percentage_control:"Έλεγχος ποσοστού;"},generic:{content_info:"Περιεχόμενο",icon_animation:"Κίνηση εικονιδίου όταν είναι ενεργό;",icon_color:"Χρώμα εικονιδίου",layout:"Διάταξη",primary_info:"Πρωτεύουσες πληροφορίες",secondary_info:"Δευτερεύουσες πληροφορίες",use_entity_picture:"Χρήση εικόνας οντότητας;"},light:{incompatible_controls:"Ορισμένα στοιχεία ελέγχου ενδέχεται να μην εμφανίζονται εάν το φωτιστικό σας δεν υποστηρίζει τη λειτουργία.",show_brightness_control:"Έλεγχος φωτεινότητας;",show_color_control:"Έλεγχος χρώματος;",show_color_temp_control:"Έλεγχος χρώματος θερμοκρασίας;",use_light_color:"Χρήση χρώματος φωτος"},"media-player":{media_controls:"Έλεγχος πολυμέσων",media_controls_list:{next:"Επόμενο κομμάτι",on_off:"Ενεργοποίηση/απενεργοποίηση",play_pause_stop:"Αναπαραγωγή/παύση/διακοπή",previous:"Προηγούμενο κομμάτι",repeat:"Λειτουργία επανάληψης",shuffle:"Τυχαία σειρά"},use_media_artwork:"Χρήση έργων τέχνης πολυμέσων",use_media_info:"Χρήση πληροφοριών πολυμέσων",volume_controls:"Χειριστήρια έντασης ήχου",volume_controls_list:{volume_buttons:"Κουμπιά έντασης ήχου",volume_mute:"Σίγαση",volume_set:"Επίπεδο έντασης ήχου"}},template:{content:"Περιεχόμενο",entity_extra:"Χρησιμοποιείται σε πρότυπα και ενέργειες",multiline_secondary:"Δευτερεύουσες πολλαπλών γραμμών;",primary:"Πρωτεύουσες πληροφορίες",secondary:"Δευτερεύουσες πληροφορίες"},title:{subtitle:"Υπότιτλος",title:"Τίτλος"},update:{show_buttons_control:"Έλεγχος κουμπιών;"},vacuum:{commands:"Εντολές"},weather:{show_conditions:"Συνθήκες;",show_temperature:"Θερμοκρασία;"}},chip:{"chip-picker":{add:"Προσθήκη chip",chips:"Chips",clear:"Καθαρισμός",edit:"Επεξεργασία",select:"Επιλογή chip",types:{action:"Ενέργεια","alarm-control-panel":"Συναγερμός",back:"Πίσω",conditional:"Υπό προϋποθέσεις",entity:"Οντότητα",light:"Φως",menu:"Μενού",template:"Πρότυπο",weather:"Καιρός"}},conditional:{chip:"Chip"},sub_element_editor:{title:"Επεξεργαστής Chip"}},form:{alignment_picker:{values:{center:"Στοίχιση στο κέντρο",default:"Προεπιλεγμένη στοίχιση",end:"Στοίχιση δεξιά",justify:"Πλήρης στοίχιση",start:"Στοίχιση αριστερά"}},color_picker:{values:{default:"Προεπιλεγμένο χρώμα"}},info_picker:{values:{default:"Προεπιλεγμένες πληροφορίες","last-changed":"Τελευταία αλλαγή","last-updated":"Τελευταία ενημέρωση",name:"Όνομα",none:"Τίποτα",state:"Κατάσταση"}},layout_picker:{values:{default:"Προεπιλεγμένη διάταξη",horizontal:"Οριζόντια διάταξη",vertical:"Κάθετη διάταξη"}}}},nd={editor:td},id=Object.freeze({__proto__:null,default:nd,editor:td}),od={not_found:"Entity not found"},rd={section:{context:"Context",content:"Content",features:"Features",interactions:"Interactions",layout:"Layout",badge:"Badge"},card:{chips:{alignment:"Alignment"},climate:{hvac_modes:"HVAC Modes",show_temperature_control:"Temperature control?"},cover:{show_buttons_control:"Control buttons?",show_position_control:"Position control?",show_tilt_position_control:"Tilt control?"},empty:{no_config_options:"This card has no config options."},fan:{show_direction_control:"Direction control?",show_oscillate_control:"Oscillate control?",show_percentage_control:"Percentage control?"},generic:{entity:"Entity",area:"Area",color:"Color",content_info:"Content",fill_container:"Fill container",icon_animation:"Animate icon when active?",icon_color:"Icon color",icon_type:"Icon type",layout:"Layout",primary_info:"Primary information",secondary_info:"Secondary information",use_entity_picture:"Use entity picture?",collapsible_controls:"Collapse controls when off",picture:"Picture",picture_helper:"If set, it will replace the icon."},humidifier:{show_target_humidity_control:"Humidity control?"},light:{incompatible_controls:"Some controls may not be displayed if your light does not support the feature.",show_brightness_control:"Brightness control?",show_color_control:"Color control?",show_color_temp_control:"Color temperature control?",use_light_color:"Use light color"},lock:{lock:"Lock",open:"Open",unlock:"Unlock"},"media-player":{media_controls:"Media controls",media_controls_list:{next:"Next track",on_off:"Turn on/off",play_pause_stop:"Play/pause/stop",previous:"Previous track",repeat:"Repeat mode",shuffle:"Shuffle"},show_volume_level:"Show volume level",use_media_artwork:"Use media artwork",use_media_info:"Use media info",volume_controls:"Volume controls",volume_controls_list:{volume_buttons:"Volume buttons",volume_mute:"Mute",volume_set:"Volume level"}},number:{display_mode:"Display Mode",display_mode_list:{buttons:"Buttons",default:"Default (slider)",slider:"Slider"}},template:{area_helper:"Used in templates and features",area:"Area",badge_color:"Badge color",badge_icon:"Badge icon",badge_text_helper:"If set, it will replace the icon.",badge_text:"Badge text",badge:"Badge",content:"Content",entity_helper:"Used in templates, interactions and features",entity_helper_legacy:"Used in templates and interactions",label:"Label",layout:"Layout",multiline_secondary_helper:"The card may be taller to fit the text and will not always align with the grid system.",multiline_secondary:"Allow multiline secondary information",primary:"Primary information",secondary:"Secondary information"},title:{subtitle:"Subtitle",subtitle_tap_action:"Subtitle tap action",title:"Title",title_tap_action:"Title tap action"},update:{show_buttons_control:"Control buttons?"},vacuum:{commands:"Commands",commands_list:{on_off:"Turn on/off"}},weather:{show_conditions:"Conditions?",show_temperature:"Temperature?"}},badge:{template:{label:"Label",content:"Content",entity_helper:"Used in templates and interactions",area_helper:"Used in templates"}},chip:{"chip-picker":{add:"Add chip",chips:"Chips",clear:"Clear",edit:"Edit",select:"Select chip",types:{action:"Action","alarm-control-panel":"Alarm",back:"Back",conditional:"Conditional",entity:"Entity",light:"Light",menu:"Menu",quickbar:"Quickbar",spacer:"Spacer",template:"Template",weather:"Weather"}},conditional:{chip:"Chip"},sub_element_editor:{title:"Chip editor"}},form:{alignment_picker:{values:{center:"Center",default:"Default alignment",end:"End",justify:"Justify",start:"Start"}},color_picker:{values:{default:"Default color"}},icon_type_picker:{values:{default:"Default type","entity-picture":"Entity picture",icon:"Icon",none:"None"}},info_picker:{values:{default:"Default information","last-changed":"Last Changed","last-updated":"Last Updated",name:"Name",none:"None",state:"State"}},layout_picker:{values:{default:"Default layout",horizontal:"Horizontal layout",vertical:"Vertical layout"}}}},ad={title:"Card updated",description:"Your card’s configuration has been migrated to the new version. You can find more information about the changes in {link}.",post:"the GitHub post",revert:"Revert",ok:"Ok"},ld={card:od,editor:rd,migration:ad},sd=Object.freeze({__proto__:null,card:od,default:ld,editor:rd,migration:ad}),cd={not_found:"Entidad no encontrada"},ud={card:{chips:{alignment:"Alineación"},climate:{hvac_modes:"Modos de climatización",show_temperature_control:"¿Control de temperatura?"},cover:{show_buttons_control:"¿Botones de control?",show_position_control:"¿Control de posición?",show_tilt_position_control:"¿Control de inclinación?"},empty:{no_config_options:"Esta carta no tiene opciones de config."},fan:{show_direction_control:"¿Control de dirección?",show_oscillate_control:"¿Controlar oscilación?",show_percentage_control:"¿Controlar porcentaje?"},generic:{collapsible_controls:"Contraer controles cuando está apagado",color:"Color",content_info:"Contenido",fill_container:"Rellenar",icon_animation:"¿Icono animado cuando está activo?",icon_color:"Color de icono",icon_type:"Tipo de icono",layout:"Diseño",primary_info:"Información primaria",secondary_info:"Información secundaria",use_entity_picture:"¿Usar imagen de entidad?"},humidifier:{show_target_humidity_control:"¿Controlar humedad?"},light:{incompatible_controls:"Es posible que algunos controles no se muestren si la luz no es compatible con esta función.",show_brightness_control:"¿Controlar brillo?",show_color_control:"¿Controlar color?",show_color_temp_control:"¿Controlar temperatura del color?",use_light_color:"Usar color de la luz"},lock:{lock:"Bloquear",open:"Abrir",unlock:"Desbloquear"},"media-player":{media_controls:"Controles multimedia",media_controls_list:{next:"Pista siguiente",on_off:"Activar/desactivar",play_pause_stop:"Reproducir/pausa/parar",previous:"Pista anterior",repeat:"Modo de repetición",shuffle:"Aleatoria"},show_volume_level:"Mostrar nivel de volumen",use_media_artwork:"Usar ilustraciones multimedia",use_media_info:"Usar información multimedia",volume_controls:"Controles de volumen",volume_controls_list:{volume_buttons:"Botones de volumen",volume_mute:"Silenciar",volume_set:"Nivel de volumen"}},number:{display_mode:"Modo de visualización",display_mode_list:{buttons:"Botones",default:"Por defecto (deslizante)",slider:"Control deslizante"}},template:{badge_color:"Color del distintivo",badge_icon:"Icono del distintivo",content:"Contenido",entity_extra:"Utilizado en plantillas y acciones",label:"Etiqueta",multiline_secondary:"¿Secundaria multilínea?",picture:"Imagen (sustituirá al icono)",primary:"Información primaria",secondary:"Información secundaria"},title:{subtitle:"Subtítulo",subtitle_tap_action:"Acción al tocar el subtítulo",title:"Título",title_tap_action:"Acción al tocar el título"},update:{show_buttons_control:"¿Botones de control?"},vacuum:{commands:"Comandos",commands_list:{on_off:"Activar/desactivar"}},weather:{show_conditions:"¿Condiciones?",show_temperature:"¿Temperatura?"}},chip:{"chip-picker":{add:"Añadir chip",chips:"Chips",clear:"Limpiar",edit:"Editar",select:"Seleccionar chip",types:{action:"Acción","alarm-control-panel":"Alarma",back:"Volver",conditional:"Condicional",entity:"Entidad",light:"Luz",menu:"Menú",spacer:"Espaciador",template:"Plantilla",weather:"Clima"}},conditional:{chip:"Chip"},sub_element_editor:{title:"Editor de chip"}},form:{alignment_picker:{values:{center:"Centrado",default:"Alineación predeterminada",end:"Final",justify:"Justificado",start:"Inicio"}},color_picker:{values:{default:"Color predeterminado"}},icon_type_picker:{values:{default:"Por defecto","entity-picture":"Imagen de entidad",icon:"Icono",none:"Ninguno"}},info_picker:{values:{default:"Información predeterminada","last-changed":"Último cambio","last-updated":"Última actualización",name:"Nombre",none:"Ninguno",state:"Estado"}},layout_picker:{values:{default:"Diseño predeterminado",horizontal:"Diseño horizontal",vertical:"Diseño vertical"}}}},dd={card:cd,editor:ud},hd=Object.freeze({__proto__:null,card:cd,default:dd,editor:ud}),pd={not_found:"Entiteettiä ei löytynyt"},fd={card:{chips:{alignment:"Asettelu"},climate:{hvac_modes:"HVAC-tilat",show_temperature_control:"Lämpötilan säätö?"},cover:{show_buttons_control:"Toimintopainikkeet?",show_position_control:"Sijainnin hallinta?",show_tilt_position_control:"Kallistuksen säätö?"},fan:{show_oscillate_control:"Oskillaation säätö?",show_percentage_control:"Prosentuaalinen säätö?"},generic:{collapsible_controls:"Supista säätimet ollessa pois-tilassa",color:"Väri",content_info:"Sisältö",fill_container:"Täytä alue",icon_animation:"Animoi kuvake, kun aktiivinen?",icon_color:"Ikonin väri",icon_type:"Kuvakkeen tyyppi",layout:"Asettelu",primary_info:"Ensisijaiset tiedot",secondary_info:"Toissijaiset tiedot",use_entity_picture:"Käytä kohteen kuvaa?"},humidifier:{show_target_humidity_control:"Kosteudenhallinta?"},light:{incompatible_controls:"Jotkin toiminnot eivät näy, jos valaisimesi ei tue niitä.",show_brightness_control:"Kirkkauden säätö?",show_color_control:"Värin säätö?",show_color_temp_control:"Värilämpötilan säätö?",use_light_color:"Käytä valaisimen väriä"},lock:{lock:"Lukitse",open:"Avaa",unlock:"Poista lukitus"},"media-player":{media_controls:"Toiminnot",media_controls_list:{next:"Seuraava kappale",on_off:"Päälle/pois",play_pause_stop:"Toista/keskeytä/pysäytä",previous:"Edellinen kappale",repeat:"Jatkuva toisto",shuffle:"Sekoita"},show_volume_level:"Näytä äänenvoimakkuuden hallinta",use_media_artwork:"Käytä median kuvituksia",use_media_info:"Käytä median tietoja",volume_controls:"Äänenvoimakkuuden hallinta",volume_controls_list:{volume_buttons:"Äänenvoimakkuuspainikkeet",volume_mute:"Mykistä",volume_set:"Äänenvoimakkuus"}},number:{display_mode:"Näyttötila",display_mode_list:{buttons:"Painikkeet",default:"Oletus (liukusäädin)",slider:"Liukusäädin"}},template:{badge_color:"Merkin väri",badge_icon:"Merkin kuvake",content:"Sisältö",entity_extra:"Käytetään malleissa ja toiminnoissa",label:"Nimiö",multiline_secondary:"Monirivinen toissijainen tieto?",picture:"Kuva (korvaa kuvakkeen)",primary:"Ensisijaiset tiedot",secondary:"Toissijaiset tiedot"},title:{subtitle:"Tekstitys",subtitle_tap_action:"Alaotsikon napautustoiminto",title:"Otsikko",title_tap_action:"Otsikkonapautustoiminto"},update:{show_buttons_control:"Toimintopainikkeet?"},vacuum:{commands:"Komennot",commands_list:{on_off:"Kytke päälle/pois"}},weather:{show_conditions:"Ehdot?",show_temperature:"Lämpötila?"}},chip:{"chip-picker":{add:"Lisää merkki",chips:"Merkit",clear:"Tyhjennä",edit:"Muokkaa",select:"Valitse merkki",types:{action:"Toiminto","alarm-control-panel":"Hälytys",back:"Takaisin",conditional:"Ehdollinen",entity:"Kohde",light:"Valaisin",menu:"Valikko",spacer:"Välikappale",template:"Malli",weather:"Sää"}},conditional:{chip:"Merkki"},sub_element_editor:{title:"Merkkieditori"}},form:{alignment_picker:{values:{center:"Keskitä",default:"Keskitys",end:"Loppu",justify:"Sovita",start:"Alku"}},color_picker:{values:{default:"Oletusväri"}},icon_type_picker:{values:{default:"Oletustyyppi","entity-picture":"Kohteen kuva",icon:"Kuvake",none:"Ei mitään"}},info_picker:{values:{default:"Oletustiedot","last-changed":"Viimeksi muuttunut","last-updated":"Viimeksi päivittynyt",name:"Nimi",none:"Ei mitään",state:"Tila"}},layout_picker:{values:{default:"Oletusasettelu",horizontal:"Vaakasuuntainen",vertical:"Pystysuuntainen"}}}},md={card:pd,editor:fd},vd=Object.freeze({__proto__:null,card:pd,default:md,editor:fd}),gd={not_found:"Entité inconnue"},_d={badge:{template:{area_helper:"Utilisée dans les modèles",content:"Contenu",entity_helper:"Utilisée dans les modèles et les interactions",label:"Libellé"}},card:{chips:{alignment:"Alignement"},climate:{hvac_modes:"Modes du thermostat",show_temperature_control:"Contrôle de la température ?"},cover:{show_buttons_control:"Contrôle avec boutons ?",show_position_control:"Contrôle de la position ?",show_tilt_position_control:"Contrôle de l'inclinaison ?"},empty:{no_config_options:"Cette carte n'a pas de paramètres."},fan:{show_direction_control:"Contrôle de la direction ?",show_oscillate_control:"Contrôle de l'oscillation ?",show_percentage_control:"Contrôle de la vitesse ?"},generic:{area:"Pièce",collapsible_controls:"Reduire les contrôles quand éteint",color:"Couleur",content_info:"Contenu",entity:"Entité",fill_container:"Remplir le conteneur",icon_animation:"Animation de l'icône ?",icon_color:"Couleur de l'icône",icon_type:"Type d'icône",layout:"Disposition",picture:"Image",picture_helper:"Si définie, elle remplacera l'icône.",primary_info:"Information principale",secondary_info:"Information secondaire",use_entity_picture:"Utiliser l'image de l'entité ?"},humidifier:{show_target_humidity_control:"Contrôle d'humidité ?"},light:{incompatible_controls:"Certains contrôles peuvent ne pas être affichés si votre lumière ne supporte pas la fonctionnalité.",show_brightness_control:"Contrôle de luminosité ?",show_color_control:"Contrôle de la couleur ?",show_color_temp_control:"Contrôle de la température ?",use_light_color:"Utiliser la couleur de la lumière"},lock:{lock:"Verrouiller",open:"Ouvrir",unlock:"Déverrouiller"},"media-player":{media_controls:"Contrôles du media",media_controls_list:{next:"Suivant",on_off:"Allumer/Éteindre",play_pause_stop:"Lecture/pause/stop",previous:"Précédent",repeat:"Mode de répétition",shuffle:"Lecture aléatoire"},show_volume_level:"Afficher le niveau de volume",use_media_artwork:"Utiliser l'illustration du media",use_media_info:"Utiliser les informations du media",volume_controls:"Contrôles du volume",volume_controls_list:{volume_buttons:"Bouton de volume",volume_mute:"Muet",volume_set:"Niveau de volume"}},number:{display_mode:"Mode d'affichage",display_mode_list:{buttons:"Boutons",default:"Par défaut (Curseur)",slider:"Curseur"}},template:{area:"Pièce",area_helper:"Utilisée dans les modèles et les fonctionnalités",badge:"Badge",badge_color:"Couleur du badge",badge_icon:"Icône du badge",badge_text:"Texte du badge",badge_text_helper:"Si définie, elle remplacera l'icône.",content:"Contenu",entity_extra:"Utilisée pour les modèles et les actions",entity_helper:"Utilisée dans les modèles, les interactions et les fonctionnalités",entity_helper_legacy:"Utilisé dans les modèles et les interactions",label:"Libellé",layout:"Disposition",multiline_secondary:"Autoriser les informations secondaires sur plusieurs lignes",multiline_secondary_helper:"La carte peut être plus haute pour s'adapter au texte et ne s'alignera pas toujours avec le système de grille.",picture:"Image (remplacera l'icône)",primary:"Information principale",secondary:"Information secondaire"},title:{subtitle:"Sous-titre",subtitle_tap_action:"Appui sur le sous-titre",title:"Titre",title_tap_action:"Appui sur le titre"},update:{show_buttons_control:"Contrôle avec boutons ?"},vacuum:{commands:"Commandes",commands_list:{on_off:"Allumer/Éteindre"}},weather:{show_conditions:"Conditions ?",show_conditons:"Conditions ?",show_temperature:"Température ?"}},chip:{"chip-picker":{add:'Ajouter une "chip"',chips:'"Chips"',clear:"Effacer",edit:"Modifier",select:'Sélectionner une "chip"',types:{action:"Action","alarm-control-panel":"Alarme",back:"Retour",conditional:"Conditionnel",entity:"Entité",light:"Lumière",menu:"Menu",quickbar:"Barre d'accès rapide",spacer:"Espacement",template:"Modèle",weather:"Météo"}},conditional:{chip:"Chip"},sub_element_editor:{title:'Éditeur de "chip"'}},form:{alignment_picker:{values:{center:"Centré",default:"Alignement par défaut",end:"Fin",justify:"Justifié",start:"Début"}},color_picker:{values:{default:"Couleur par défaut"}},icon_type_picker:{values:{default:"Type par défaut","entity-picture":"Image de l'entité",icon:"Icône",none:"Aucune"}},info_picker:{values:{default:"Information par défaut","last-changed":"Dernière modification","last-updated":"Dernière mise à jour",name:"Nom",none:"Aucune",state:"État"}},layout_picker:{values:{default:"Disposition par défault",horizontal:"Disposition horizontale",vertical:"Disposition verticale"}}},section:{badge:"Badge",content:"Contenu",context:"Contexte",features:"Fonctionnalités",interactions:"Interactions",layout:"Disposition"}},yd={description:"La configuration de votre carte a été migrée vers la nouvelle version. Vous pouvez trouver plus d’informations sur les changements dans {link}.",ok:"Ok",post:"l'article sur Github",revert:"Revenir en arrière",title:"Carte mise à jour"},bd={card:gd,editor:_d,migration:yd},kd=Object.freeze({__proto__:null,card:gd,default:bd,editor:_d,migration:yd}),wd={not_found:"היישות לא נמצאה"},xd={card:{chips:{alignment:"יישור"},climate:{hvac_modes:"מצבי שואב אבק",show_temperature_control:"בקרת טמפרטורה?"},cover:{show_buttons_control:"הצג כפתורי שליטה?",show_position_control:"הצג פקדי מיקום?",show_tilt_position_control:"שליטה בהטייה?"},empty:{no_config_options:"לכרטיסיה זו אין אפשרויות להגדרה."},fan:{show_direction_control:"שליטה בכיוון?",show_oscillate_control:"שליטה בהתנדנדות?",show_percentage_control:"שליטה באחוז?"},generic:{collapsible_controls:"הסתר שליטה כשאר מכובה",color:"צבע",content_info:"תוכן",fill_container:"מלא גבולות",icon_animation:"הנפש צלמית אם פעיל?",icon_color:"צבע אייקון",icon_type:"סוג צלמית",layout:"סידור",primary_info:"מידע ראשי",secondary_info:"מידע מישני",use_entity_picture:"השתמש בתמונת הישות?"},humidifier:{show_target_humidity_control:"הצג פקדי לחות?"},light:{incompatible_controls:"יתכן וחלק מהכפתורים לא יופיעו אם התאורה אינה תומכת בתכונה.",show_brightness_control:"שליטה בבהירות?",show_color_control:"הצג פקד צבע?",show_color_temp_control:"הצג פקד גוון תאורה?",use_light_color:"השתמש בצבע האור"},lock:{lock:"נעל",open:"פתח",unlock:"בטל נעילה"},"media-player":{media_controls:"שליטה במדיה",media_controls_list:{next:"רצועה הבאה",on_off:"הדלק/כבה",play_pause_stop:"נגן/השהה/הפסק",previous:"רצועה קודמת",repeat:"חזרה",shuffle:"ערבב"},show_volume_level:"הצג שליטת ווליום",use_media_artwork:"השתמש באומנות מדיה",use_media_info:"השתמש במידע מדיה",volume_controls:"שליטה בווליום",volume_controls_list:{volume_buttons:"כפתורי ווליום",volume_mute:"השתק",volume_set:"רמת ווליום"}},number:{display_mode:"הגדרת מצב תצוגה",display_mode_list:{buttons:"לחצנים",default:"ברירת מחדל (סרגל גלילה)",slider:"סרגל גלילה"}},template:{badge_color:"צבע תג",badge_icon:"צלמית תג",content:"תוכן",entity_extra:"משמש בתבניות ופעולות",label:"תווית",multiline_secondary:"מידע משני בשורות?",picture:"תמונה (תחליף את הצלמית)",primary:"מידע ראשי",secondary:"מידע מישני"},title:{subtitle:"כתובית",subtitle_tap_action:"פעולה בלחיצה על כותרת משנה",title:"כותרת",title_tap_action:"פעולה בלחיצה על הכותרת"},update:{show_buttons_control:"הצג כפתורי שליטה?"},vacuum:{commands:"פקודות",commands_list:{on_off:"כיבוי/הדלקה"},icon_animation:"הנפשת אייקון"},weather:{show_conditions:"הצג תנאים?",show_temperature:"הצג טמפרטורה?"}},chip:{"chip-picker":{add:"הוסף שבב",chips:"שבבים",clear:"נקה",edit:"ערוך",select:"בחר שבב",types:{action:"פעולה","alarm-control-panel":"אזעקה",back:"חזור",conditional:"מותנה",entity:"ישות",light:"אור",menu:"תפריט",spacer:"מרווח",template:"תבנית",weather:"מזג אוויר"}},conditional:{chip:"שבב"},sub_element_editor:{title:"עורך שבב"}},form:{alignment_picker:{values:{center:"אמצע",default:"יישור ברירת מחדל",end:"סוף",justify:"מוצדק",start:"התחלה"}},color_picker:{values:{default:"צבע ברירת מחדל"}},icon_type_picker:{values:{default:"סוג ברירת מחדל","entity-picture":"תמונת יישות",icon:"צלמית",none:"ריק"}},info_picker:{values:{default:"מידע ברירת מחדל","last-changed":"שונה לאחרונה","last-updated":"עודכן לאחרונה",name:"שם",none:"ריק",state:"מצב"}},layout_picker:{values:{default:"סידור ברירת מחדל",horizontal:"סידור מאוזן",vertical:"סידור מאונך"}}}},Ed={card:wd,editor:xd},Cd=Object.freeze({__proto__:null,card:wd,default:Ed,editor:xd}),Ad={not_found:"Entitás nem található"},Sd={card:{chips:{alignment:"Rendezés"},climate:{hvac_modes:"HVAC mód",show_temperature_control:"Hőmérséklet vezérlő"},cover:{show_buttons_control:"Vezérlő gombok",show_position_control:"Pozíció vezérlő",show_tilt_position_control:"Dőlésszög szabályzó"},fan:{show_oscillate_control:"Oszcilláció vezérlő",show_percentage_control:"Százalékos vezérlő"},generic:{collapsible_controls:"Vezérlők összezárása kikapcsolt állapotban",content_info:"Tartalom",fill_container:"Tároló kitöltése",icon_animation:"Ikon animálása aktív állapotban",icon_color:"Ikon szín",icon_type:"Ikon típus",layout:"Elrendezés",primary_info:"Elsődleges információ",secondary_info:"Másodlagos információ",use_entity_picture:"Entitás kép használata"},humidifier:{show_target_humidity_control:"Páratartalom vezérlő"},light:{incompatible_controls:"Azok a vezérlők nem lesznek megjelenítve, amelyeket a fényforrás nem támogat.",show_brightness_control:"Fényerő vezérlő",show_color_control:"Szín vezérlő",show_color_temp_control:"Színhőmérséklet vezérlő",use_light_color:"Fény szín használata"},lock:{lock:"Zár",open:"Nyitva",unlock:"Nyit"},"media-player":{media_controls:"Média vezérlők",media_controls_list:{next:"Következő szám",on_off:"Ki/bekapcsolás",play_pause_stop:"Lejátszás/szünet/állj",previous:"Előző szám",repeat:"Ismétlés módja",shuffle:"Véletlen lejátszás"},show_volume_level:"Hangerő mutatása",use_media_artwork:"Média borító használata",use_media_info:"Média infó használata",volume_controls:"Hangerő vezérlők",volume_controls_list:{volume_buttons:"Hangerő gombok",volume_mute:"Némítás",volume_set:"Hangerő szint"}},number:{display_mode:"Megjelenítési mód",display_mode_list:{buttons:"Gombok",default:"Alepértelmezett (csúszka)",slider:"Csúszka"}},template:{badge_color:"Jelvény szín",badge_icon:"Jelvény ikon",content:"Tartalom",entity_extra:"Műveletek és sablonok használatakor",multiline_secondary:"Másodlagost több sorba?",picture:"Kép (lecseréli az ikont)",primary:"Elsődleges információ",secondary:"Másodlagos információ"},title:{subtitle:"Alcím",subtitle_tap_action:"Alcímre koppintáskor",title:"Fejléc",title_tap_action:"Fejlécre koppintáskor"},update:{show_buttons_control:"Vezérlő gombok"},vacuum:{commands:"Utasítások",commands_list:{on_off:"Ki/Bekapcsolás"}},weather:{show_conditions:"Állapotok",show_temperature:"Hőmérséklet"}},chip:{"chip-picker":{add:"Chip hozzáadása",chips:"Chip-ek",clear:"Ürítés",edit:"Szerkesztés",select:"Chip kiválasztása",types:{action:"Művelet","alarm-control-panel":"Riasztó",back:"Vissza",conditional:"Feltételes",entity:"Entitás",light:"Fényforrás",menu:"Menü",spacer:"Térköz",template:"Sablon",weather:"Időjárás"}},conditional:{chip:"Chip"},sub_element_editor:{title:"Chip szerkesztő"}},form:{alignment_picker:{values:{center:"Közepe",default:"Alapértelmezett rendezés",end:"Vége",justify:"Sorkizárt",start:"Kezdete"}},color_picker:{values:{default:"Alapértelmezett szín"}},icon_type_picker:{values:{default:"Alapértelmezett típus","entity-picture":"Entitás kép",icon:"Ikon",none:"Egyik sem"}},info_picker:{values:{default:"Alepértelmezett információ","last-changed":"Utoljára módosítva","last-updated":"Utoljára frissítve",name:"Név",none:"Egyik sem",state:"Állapot"}},layout_picker:{values:{default:"Alapértelmezet elrendezés",horizontal:"Vízszintes elrendezés",vertical:"Függőleges elrendezés"}}}},Td={card:Ad,editor:Sd},Id=Object.freeze({__proto__:null,card:Ad,default:Td,editor:Sd}),Od={not_found:"Entitas tidak ditemukan"},Md={card:{chips:{alignment:"Perataan"},climate:{hvac_modes:"Mode HVAC",show_temperature_control:"Kontrol suhu?"},cover:{show_buttons_control:"Tombol kontrol?",show_position_control:"Kontrol posisi?",show_tilt_position_control:"Kontrol kemiringan?"},fan:{show_oscillate_control:"Kontrol osilasi?",show_percentage_control:"Kontrol persentase?"},generic:{collapsible_controls:"Sembunyikan kontrol saat mati",color:"Warna",content_info:"Konten",fill_container:"Isi kontainer",icon_animation:"Animasikan ikon saat aktif?",icon_color:"Warna ikon",icon_type:"Tipe ikon",layout:"Tata letak",primary_info:"Informasi primer",secondary_info:"Informasi sekunder",use_entity_picture:"Gunakan gambar entitas?"},humidifier:{show_target_humidity_control:"Kontrol kelembapan?"},light:{incompatible_controls:"Beberapa kontrol mungkin tidak ditampilkan jika lampu Anda tidak mendukung fitur tersebut.",show_brightness_control:"Kontrol kecerahan?",show_color_control:"Kontrol warna?",show_color_temp_control:"Kontrol suhu warna?",use_light_color:"Gunakan warna lampu"},lock:{lock:"Kunci",open:"Buka",unlock:"Buka kunci"},"media-player":{media_controls:"Kontrol media",media_controls_list:{next:"Lagu berikutnya",on_off:"Nyalakan/Matikan",play_pause_stop:"Putar/jeda/stop",previous:"Lagu sebelumnya",repeat:"Mode pengulangan",shuffle:"Acak"},show_volume_level:"Tampilkan level volume",use_media_artwork:"Gunakan gambar seni media",use_media_info:"Gunakan info media",volume_controls:"Kontrol volume",volume_controls_list:{volume_buttons:"Tombol volume",volume_mute:"Bisukan",volume_set:"Level volume"}},number:{display_mode:"Mode Tampilan",display_mode_list:{buttons:"Tombol",default:"Bawaan (geser)",slider:"Geser"}},template:{badge_color:"Warna lencana",badge_icon:"Ikon lencana",content:"Konten",entity_extra:"Digunakan dalam templat dan tindakan",label:"Label",multiline_secondary:"Info sekunder multibaris?",picture:"Gambar (akan menggantikan ikon)",primary:"Informasi primer",secondary:"Informasi sekunder"},title:{subtitle:"Subjudul",subtitle_tap_action:"Tindakan ketuk subjudul",title:"Judul",title_tap_action:"Tindakan ketuk judul"},update:{show_buttons_control:"Tombol kontrol?"},vacuum:{commands:"Perintah",commands_list:{on_off:"Nyalakan/Matikan"}},weather:{show_conditions:"Kondisi?",show_temperature:"Suhu?"}},chip:{"chip-picker":{add:"Tambah cip",chips:"Cip",clear:"Hapus",edit:"Edit",select:"Pilih cip",types:{action:"Tindakan","alarm-control-panel":"Alarm",back:"Kembali",conditional:"Kondisional",entity:"Entitas",light:"Lampu",menu:"Menu",spacer:"Pemisah",template:"Templat",weather:"Cuaca"}},conditional:{chip:"Cip"},sub_element_editor:{title:"Editor cip"}},form:{alignment_picker:{values:{center:"Tengah",default:"Perataan bawaan",end:"Akhir",justify:"Rata kanan-kiri",start:"Awal"}},color_picker:{values:{default:"Warna bawaan"}},icon_type_picker:{values:{default:"Tipe bawaan","entity-picture":"Gambar entitas",icon:"Ikon",none:"Tidak ada"}},info_picker:{values:{default:"Informasi bawaan","last-changed":"Terakhir Diubah","last-updated":"Terakhir Diperbarui",name:"Nama",none:"Tidak ada",state:"Status"}},layout_picker:{values:{default:"Tata letak bawaan",horizontal:"Tata letak horizontal",vertical:"Tata letak vertikal"}}}},zd={card:Od,editor:Md},Pd=Object.freeze({__proto__:null,card:Od,default:zd,editor:Md}),Ld={not_found:"Entità non trovata"},Nd={card:{chips:{alignment:"Allineamento"},climate:{hvac_modes:"Modalità del termostato",show_temperature_control:"Controllo della temperatura?"},cover:{show_buttons_control:"Pulsanti di controllo",show_position_control:"Controllo percentuale apertura",show_tilt_position_control:"Controllo percentuale inclinazione"},fan:{show_oscillate_control:"Controllo oscillazione",show_percentage_control:"Controllo potenza"},generic:{collapsible_controls:"Nascondi i controlli quando spento",color:"Colore",content_info:"Contenuto",fill_container:"Riempi il contenitore",icon_animation:"Anima l'icona quando attiva",icon_color:"Colore dell'icona",icon_type:"Tipo icona",layout:"Disposizione",primary_info:"Informazione primaria",secondary_info:"Informazione secondaria",use_entity_picture:"Usa l'immagine dell'entità"},humidifier:{show_target_humidity_control:"Controllo umidità"},light:{incompatible_controls:"Alcuni controlli potrebbero non essere mostrati se la tua luce non li supporta.",show_brightness_control:"Controllo luminosità",show_color_control:"Controllo colore",show_color_temp_control:"Controllo temperatura",use_light_color:"Usa il colore della luce"},lock:{lock:"Blocca",open:"Aperto",unlock:"Sblocca"},"media-player":{media_controls:"Controlli media",media_controls_list:{next:"Traccia successiva",on_off:"Accendi/Spegni",play_pause_stop:"Play/Pausa/Stop",previous:"Traccia precedente",repeat:"Ciclo continuo",shuffle:"Riproduzione casuale"},show_volume_level:"Mostra volume",use_media_artwork:"Usa la copertina della sorgente",use_media_info:"Mostra le informazioni della sorgente",volume_controls:"Controlli del Volume",volume_controls_list:{volume_buttons:"Bottoni del volume",volume_mute:"Silenzia",volume_set:"Livello del volume"}},number:{display_mode:"Modalità di visualizzazione",display_mode_list:{buttons:"Pulsanti",default:"Predefinito (cursore)",slider:"Cursore"}},template:{badge_color:"Colore del badge",badge_icon:"Icona del badge",content:"Contenuto",entity_extra:"Usato in templates ed azioni",label:"Etichetta",multiline_secondary:"Abilita frasi multilinea",picture:"Immagine (sostituirà l'icona)",primary:"Informazione primaria",secondary:"Informazione secondaria"},title:{subtitle:"Sottotitolo",subtitle_tap_action:"Azione di tap sul sottotitolo",title:"Titolo",title_tap_action:"Azione di tap sul titolo"},update:{show_buttons_control:"Pulsanti di controllo"},vacuum:{commands:"Comandi",commands_list:{on_off:"Accendi/Spegni"}},weather:{show_conditions:"Condizioni",show_temperature:"Temperatura"}},chip:{"chip-picker":{add:"Aggiungi chip",chips:"Chips",clear:"Rimuovi",edit:"Modifica",select:"Seleziona chip",types:{action:"Azione","alarm-control-panel":"Allarme",back:"Pulsante indietro",conditional:"Condizione",entity:"Entità",light:"Luce",menu:"Menù",spacer:"Distanziere",template:"Modello",weather:"Meteo"}},conditional:{chip:"Chip"},sub_element_editor:{title:"Editor di chip"}},form:{alignment_picker:{values:{center:"Centro",default:"Allineamento predefinito",end:"Fine",justify:"Giustificato",start:"Inizio"}},color_picker:{values:{default:"Colore predefinito"}},icon_type_picker:{values:{default:"Tipo predefinito","entity-picture":"Immagine dell'entità",icon:"Icona",none:"Nessuna"}},info_picker:{values:{default:"Informazione predefinita","last-changed":"Ultimo cambiamento","last-updated":"Ultimo aggiornamento",name:"Nome",none:"Nessuno",state:"Stato"}},layout_picker:{values:{default:"Disposizione predefinita",horizontal:"Disposizione orizzontale",vertical:"Disposizione verticale"}}}},jd={card:Ld,editor:Nd},$d=Object.freeze({__proto__:null,card:Ld,default:jd,editor:Nd}),Bd={card:{chips:{alignment:"정렬"},climate:{hvac_modes:"HVAC 모드",show_temperature_control:"온도 조절 표시"},cover:{show_buttons_control:"컨트롤 버튼 표시",show_position_control:"위치 컨트롤 표시",show_tilt_position_control:"기울기 컨트롤 표시"},fan:{show_oscillate_control:"오실레이트 컨트롤",show_percentage_control:"퍼센트 컨트롤"},generic:{collapsible_controls:"꺼져있을 때 컨트롤 접기",content_info:"내용 정보",fill_container:"콘테이너 채우기",icon_animation:"활성화 시 아이콘 애니메이션 사용",icon_color:"아이콘 색",icon_type:"아이콘 타입",layout:"레이아웃",primary_info:"기본 정보",secondary_info:"보조 정보",use_entity_picture:"엔티티 사진 사용"},humidifier:{show_target_humidity_control:"습도 조절 표시"},light:{incompatible_controls:"조명이 기능을 지원하지 않는 경우 일부 컨트롤이 표시되지 않을 수 있습니다.",show_brightness_control:"밝기 컨트롤 표시",show_color_control:"색 컨트롤 표시",show_color_temp_control:"색 온도 컨트롤 표시",use_light_color:"조명 색 사용"},lock:{lock:"잠금",open:"열기",unlock:"잠금 해제"},"media-player":{media_controls:"미디어 컨트롤",media_controls_list:{next:"다음 트랙",on_off:"켜기/끄기",play_pause_stop:"재생/일시 정지/정지",previous:"이전 트랙",repeat:"반복 모드",shuffle:"섞기"},show_volume_level:"볼륨 레벨 표시",use_media_artwork:"미디어 아트워크 사용",use_media_info:"미디어 정보 사용",volume_controls:"볼륨 컨트롤",volume_controls_list:{volume_buttons:"볼륨 버튼",volume_mute:"음소거",volume_set:"볼륨 레벨"}},template:{badge_color:"뱃지 색",badge_icon:"뱃지 아이콘",content:"내용",entity_extra:"템플릿 및 작업에 사용",multiline_secondary:"Multiline secondary?",picture:"그림 (아이콘 대체)",primary:"기본 정보",secondary:"보조 정보"},title:{subtitle:"부제목",subtitle_tap_action:"부제목 탭 액션",title:"제목",title_tap_action:"제목 탭 액션"},update:{show_buttons_control:"컨트롤 버튼 표시"},vacuum:{commands:"명령어",commands_list:{on_off:"켜기/끄기"}},weather:{show_conditions:"조건 표시",show_temperature:"온도 표시"}},chip:{"chip-picker":{add:"칩 추가",chips:"칩",clear:"클리어",edit:"수정",select:"칩 선택",types:{action:"액션","alarm-control-panel":"알람",back:"이전",conditional:"Conditional",entity:"엔티티",light:"조명",menu:"메뉴",template:"템플릿",weather:"날씨"}},conditional:{chip:"칩"},sub_element_editor:{title:"칩 에디터"}},form:{alignment_picker:{values:{center:"중앙",default:"기본 정렬",end:"끝",justify:"행 정렬",start:"시작"}},color_picker:{values:{default:"기본 색"}},icon_type_picker:{values:{default:"기본 타입","entity-picture":"엔티티 사진",icon:"아이콘",none:"없음"}},info_picker:{values:{default:"기본 정보","last-changed":"마지막 변경","last-updated":"마지막 업데이트",name:"이름",none:"없음",state:"상태"}},layout_picker:{values:{default:"기본 레이아웃",horizontal:"수평 레이아웃",vertical:"수직 레이아웃"}}}},Rd={editor:Bd},Hd=Object.freeze({__proto__:null,default:Rd,editor:Bd}),Dd={not_found:"Enhet ikke funnet"},Ud={card:{chips:{alignment:"Justering"},climate:{hvac_modes:"HVAC-moduser",show_temperature_control:"Temperaturkontroll?"},cover:{show_buttons_control:"Kontrollere med knapper?",show_position_control:"Posisjonskontroll?",show_tilt_position_control:"Vippe kontroll?"},fan:{show_oscillate_control:"Oscillerende kontroll?",show_percentage_control:"Prosentvis kontroll?"},generic:{collapsible_controls:"Skjul kontroller når av",color:"Farge",content_info:"Innhold",fill_container:"Fyll beholder",icon_animation:"Animer ikon når aktivt?",icon_color:"Ikon farge",icon_type:"Ikontype",layout:"Oppsett",primary_info:"Primærinformasjon",secondary_info:"Sekundærinformasjon",use_entity_picture:"Bruk enhetsbilde?"},humidifier:{show_target_humidity_control:"Fuktighetskontroll?"},light:{incompatible_controls:"Noen kontroller vises kanskje ikke hvis lyset ditt ikke støtter denne funksjonen.",show_brightness_control:"Lysstyrkekontroll?",show_color_control:"Fargekontroll?",show_color_temp_control:"Temperatur fargekontroll?",use_light_color:"Bruk lys farge"},lock:{lock:"Lås",open:"Åpne",unlock:"Lås opp"},"media-player":{media_controls:"Media kontroller",media_controls_list:{next:"Neste spor",on_off:"Slå på/av",play_pause_stop:"Spill/pause/stopp",previous:"Forrige spor",repeat:"Gjenta",shuffle:"Bland"},show_volume_level:"Vis volumnivå",use_media_artwork:"Bruk mediabilde",use_media_info:"Bruk mediainformasjon",volume_controls:"Volumkontroller",volume_controls_list:{volume_buttons:"Volumknapper",volume_mute:"Demp",volume_set:"Volumnivå"}},number:{display_mode:"Visningsmodus",display_mode_list:{buttons:"Knapper",default:"Standard (skyveknapp)",slider:"Skyveknapp"}},template:{badge_color:"Badge farge",badge_icon:"Badge ikon",content:"Innhold",entity_extra:"Brukes i maler og handlinger",label:"Etikett",multiline_secondary:"Multilinje sekundær?",picture:"Bilde (erstatter ikonet)",primary:"Primærinformasjon",secondary:"Sekundærinformasjon"},title:{subtitle:"Undertekst",subtitle_tap_action:"Undertekst tap action",title:"Tittel",title_tap_action:"Tittel tap action"},update:{show_buttons_control:"Kontroller knapper?"},vacuum:{commands:"Kommandoer",commands_list:{on_off:"Slå på/av"}},weather:{show_conditions:"Forhold?",show_temperature:"Temperatur?"}},chip:{"chip-picker":{add:"Legg til chip",chips:"Chips",clear:"Klare",edit:"Endre",select:"Velg chip",types:{action:"Handling","alarm-control-panel":"Alarm",back:"Tilbake",conditional:"Betinget",entity:"Entitet",light:"Lys",menu:"Meny",spacer:"Mellomrom",template:"Mal",weather:"Vær"}},conditional:{chip:"Chip"},sub_element_editor:{title:"Chip redaktør"}},form:{alignment_picker:{values:{center:"Senter",default:"Standard justering",end:"Slutt",justify:"Blokkjuster",start:"Start"}},color_picker:{values:{default:"Standard farge"}},icon_type_picker:{values:{default:"Standard type","entity-picture":"Enhetsbilde",icon:"Ikon",none:"Ingen"}},info_picker:{values:{default:"Standard informasjon","last-changed":"Sist endret","last-updated":"Sist oppdatert",name:"Navn",none:"Ingen",state:"Tilstand"}},layout_picker:{values:{default:"Standardoppsett",horizontal:"Horisontalt oppsett",vertical:"Vertikalt oppsett"}}}},Vd={card:Dd,editor:Ud},Fd=Object.freeze({__proto__:null,card:Dd,default:Vd,editor:Ud}),Gd={not_found:"Entiteit niet gevonden"},Yd={card:{chips:{alignment:"Uitlijning"},climate:{hvac_modes:"HVAC-Modi",show_temperature_control:"Temperatuur bediening?"},cover:{show_buttons_control:"Bedieningsknoppen?",show_position_control:"Positie bediening?",show_tilt_position_control:"Kantel bediening?"},empty:{no_config_options:"Deze kaart heeft geen configuratie opties."},fan:{show_direction_control:"Richting bediening?",show_oscillate_control:"Oscillatie bediening?",show_percentage_control:"Bediening middels percentage?"},generic:{collapsible_controls:"Bedieningselementen verbergen wanneer uitgeschakeld",color:"Kleur",content_info:"Inhoud",fill_container:"Vul container",icon_animation:"Icoon animeren indien actief?",icon_color:"Icoon kleur",icon_type:"Icoon type",layout:"Lay-out",primary_info:"Primaire informatie",secondary_info:"Secundaire informatie",use_entity_picture:"Gebruik afbeelding van entiteit?"},humidifier:{show_target_humidity_control:"Vochtigheid bediening?"},light:{incompatible_controls:"Sommige bedieningselementen worden mogelijk niet weergegeven als uw lamp deze functie niet ondersteunt.",show_brightness_control:"Helderheidsbediening?",show_color_control:"Kleur bediening?",show_color_temp_control:"Kleurtemperatuur bediening?",use_light_color:"Gebruik licht kleur"},lock:{lock:"Vergrendel",open:"Open",unlock:"Ontgrendel"},"media-player":{media_controls:"Mediabediening",media_controls_list:{next:"Volgende nummer",on_off:"Zet aan/uit",play_pause_stop:"Speel/pauze/stop",previous:"Vorige nummer",repeat:"Herhalen",shuffle:"Willekeurig afspelen"},show_volume_level:"Toon volumeniveau",use_media_artwork:"Gebruik media omslag",use_media_info:"Gebruik media informatie",volume_controls:"Volumebediening",volume_controls_list:{volume_buttons:"Volume knoppen",volume_mute:"Dempen",volume_set:"Volumeniveau"}},number:{display_mode:"Weergave Modus",display_mode_list:{buttons:"Knoppen",default:"Standaard (schuifbalk)",slider:"Schuifbalk"}},template:{badge_color:"Badge kleur",badge_icon:"Badge icoon",content:"Inhoud",entity_extra:"Gebruikt in sjablonen en acties",label:"Label",multiline_secondary:"Secundaire informatie op meerdere regels tonen?",picture:"Afbeelding (zal het icoon vervangen)",primary:"Primaire informatie",secondary:"Secundaire informatie"},title:{subtitle:"Ondertitel",subtitle_tap_action:"Ondertitel tik actie",title:"Titel",title_tap_action:"Titel tik actie"},update:{show_buttons_control:"Bedieningsknoppen?"},vacuum:{commands:"Commando's",commands_list:{on_off:"Zet aan/uit"}},weather:{show_conditions:"Weersomstandigheden?",show_temperature:"Temperatuur?"}},chip:{"chip-picker":{add:"Toevoegen chip",chips:"Chips",clear:"Maak leeg",edit:"Bewerk",select:"Selecteer chip",types:{action:"Actie","alarm-control-panel":"Alarm",back:"Terug",conditional:"Voorwaardelijk",entity:"Entiteit",light:"Licht",menu:"Menu",spacer:"Afstandhouder",template:"Sjabloon",weather:"Weer"}},conditional:{chip:"Chip"},sub_element_editor:{title:"Chip-editor"}},form:{alignment_picker:{values:{center:"Midden",default:"Standaard uitlijning",end:"Einde",justify:"Uitlijnen",start:"Begin"}},color_picker:{values:{default:"Standaard kleur"}},icon_type_picker:{values:{default:"Standaard icoon type","entity-picture":"Entiteit afbeelding",icon:"Icoon",none:"Geen"}},info_picker:{values:{default:"Standaard informatie","last-changed":"Laatst gewijzigd","last-updated":"Laatst bijgewerkt",name:"Naam",none:"Geen",state:"Staat"}},layout_picker:{values:{default:"Standaard lay-out",horizontal:"Horizontale lay-out",vertical:"Verticale lay-out"}}}},Kd={card:Gd,editor:Yd},Wd=Object.freeze({__proto__:null,card:Gd,default:Kd,editor:Yd}),qd={not_found:"Nie znaleziono encji"},Xd={card:{chips:{alignment:"Wyrównanie"},climate:{hvac_modes:"Tryby urządzenia",show_temperature_control:"Sterowanie temperaturą?"},cover:{show_buttons_control:"Przyciski sterujące?",show_position_control:"Sterowanie położeniem?",show_tilt_position_control:"Sterowanie poziomem otwarcia?"},fan:{show_direction_control:"Kontrola kierunku?",show_oscillate_control:"Sterowanie oscylacją?",show_percentage_control:"Sterowanie procentowe?"},generic:{collapsible_controls:"Zwiń sterowanie, jeśli wyłączone",color:"Kolor",content_info:"Zawartość",fill_container:"Wypełnij zawartością",icon_animation:"Animować, gdy aktywny?",icon_color:"Kolor ikony",icon_type:"Typ ikony",layout:"Układ",primary_info:"Informacje główne",secondary_info:"Informacje drugorzędne",use_entity_picture:"Użyć obrazu encji?"},humidifier:{show_target_humidity_control:"Sterowanie wilgotnością?"},light:{incompatible_controls:"Niektóre funkcje są niewidoczne, jeśli światło ich nie obsługuje.",show_brightness_control:"Sterowanie jasnością?",show_color_control:"Sterowanie kolorami?",show_color_temp_control:"Sterowanie temperaturą światła?",use_light_color:"Użyj koloru światła"},lock:{lock:"Zablokuj",open:"Otwórz",unlock:"Odblokuj"},"media-player":{media_controls:"Sterowanie multimediami",media_controls_list:{next:"Następne nagranie",on_off:"Włącz/wyłącz",play_pause_stop:"Odtwórz/Pauza/Zatrzymaj",previous:"Poprzednie nagranie",repeat:"Powtarzanie",shuffle:"Losowo"},show_volume_level:"Wyświetl poziom głośności",use_media_artwork:"Użyj okładek multimediów",use_media_info:"Użyj informacji o multimediach",volume_controls:"Sterowanie głośnością",volume_controls_list:{volume_buttons:"Przyciski głośności",volume_mute:"Wycisz",volume_set:"Poziom głośności"}},number:{display_mode:"Sposób wyświetlania",display_mode_list:{buttons:"Przyciski",default:"Domyślnie (suwak)",slider:"Suwak"}},template:{badge_color:"Kolor odznaki",badge_icon:"Ikona odznaki",content:"Zawartość",entity_extra:"Używane w szablonach i akcjach",label:"Etykieta",multiline_secondary:"Drugorzędne wielowierszowe?",picture:"Obraz (zamiast ikony)",primary:"Informacje główne",secondary:"Informacje drugorzędne"},title:{subtitle:"Podtytuł",subtitle_tap_action:"Akcja na podtytule",title:"Tytuł",title_tap_action:"Akcja na tytule"},update:{show_buttons_control:"Przyciski sterujące?"},vacuum:{commands:"Polecenia",commands_list:{on_off:"Włącz/Wyłącz"}},weather:{show_conditions:"Warunki?",show_temperature:"Temperatura?"}},chip:{"chip-picker":{add:"Dodaj czip",chips:"Czipy",clear:"Wyczyść",edit:"Edytuj",select:"Wybierz czip",types:{action:"Akcja","alarm-control-panel":"Alarm",back:"Wstecz",conditional:"Warunkowy",entity:"Encja",light:"Światło",menu:"Menu",spacer:"Odstęp",template:"Szablon",weather:"Pogoda"}},conditional:{chip:"Czip"},sub_element_editor:{title:"Edytor czipów"}},form:{alignment_picker:{values:{center:"Wyśrodkowanie",default:"Wyrównanie domyślne",end:"Wyrównanie do prawej",justify:"Justowanie",start:"Wyrównanie do lewej"}},color_picker:{values:{default:"Domyślny kolor"}},icon_type_picker:{values:{default:"Domyślny typ","entity-picture":"Obraz encji",icon:"Ikona",none:"Brak"}},info_picker:{values:{default:"Domyślne informacje","last-changed":"Ostatnia zmiana","last-updated":"Ostatnia aktualizacja",name:"Nazwa",none:"Brak",state:"Stan"}},layout_picker:{values:{default:"Układ domyślny",horizontal:"Układ poziomy",vertical:"Układ pionowy"}}}},Zd={card:qd,editor:Xd},Jd=Object.freeze({__proto__:null,card:qd,default:Zd,editor:Xd}),Qd={not_found:"Entidade não encontrada"},eh={card:{chips:{alignment:"Alinhamento"},climate:{hvac_modes:"Modos do HVAC",show_temperature_control:"Controle de temperatura?"},cover:{show_buttons_control:"Botões de controle?",show_position_control:"Controle de posição?",show_tilt_position_control:"Controle de inclinação?"},empty:{no_config_options:"Esse card não possui opções de configuração."},fan:{show_direction_control:"Controle de direção?",show_oscillate_control:"Controle de oscilação?",show_percentage_control:"Controle de porcentagem?"},generic:{collapsible_controls:"Recolher controles quando desligado",color:"Cor",content_info:"Conteúdo",fill_container:"Preencher espaço",icon_animation:"Animar ícone quando ativo?",icon_color:"Cor do ícone",icon_type:"Tipo do ícone",layout:"Layout",primary_info:"Informação primária",secondary_info:"Informação secundária",use_entity_picture:"Usar imagem da entidade?"},humidifier:{show_target_humidity_control:"Controle de umidade?"},light:{incompatible_controls:"Alguns controles podem não ser exibidos se sua luz não suportar o recurso.",show_brightness_control:"Controle de brilho?",show_color_control:"Controle de cor?",show_color_temp_control:"Controle de temperatura de cor?",use_light_color:"Usar cor da luz"},lock:{lock:"Bloquear",open:"Abrir",unlock:"Desbloquear"},"media-player":{media_controls:"Controles de mídia",media_controls_list:{next:"Próxima faixa",on_off:"Ligar/Desligar",play_pause_stop:"Reproduzir/pausar/parar",previous:"Faixa anterior",repeat:"Modo repetição",shuffle:"Embaralhar"},show_volume_level:"Mostrar nível de volume",use_media_artwork:"Usar arte da mídia",use_media_info:"Usar informação da mídia",volume_controls:"Controles de volume",volume_controls_list:{volume_buttons:"Botões de volume",volume_mute:"Mudo",volume_set:"Nível de volume"}},number:{display_mode:"Modo de exibição",display_mode_list:{buttons:"Botões",default:"Padrão (deslizante)",slider:"Deslizante"}},template:{badge_color:"Cor do badge",badge_icon:"Ícone do badge",content:"Conteúdo",entity_extra:"Usado em modelos e ações",label:"Label",multiline_secondary:"Multilinha secundária?",picture:"Imagem (irá substituir o ícone)",primary:"Informação primária",secondary:"Informação secundária"},title:{subtitle:"Legenda",subtitle_tap_action:"Ação de toque na legenda",title:"Título",title_tap_action:"Ação de toque no título"},update:{show_buttons_control:"Botões de controle?"},vacuum:{commands:"Comandos",commands_list:{on_off:"Ligar/Desligar"}},weather:{show_conditions:"Condições?",show_temperature:"Temperatura?"}},chip:{"chip-picker":{add:"Adicionar chip",chips:"Chips",clear:"Limpar",edit:"Editar",select:"Selecionar chip",types:{action:"Ação","alarm-control-panel":"Alarme",back:"Voltar",conditional:"Condicional",entity:"Entidade",light:"Luz",menu:"Menu",quickbar:"Barra rápida",spacer:"Espaçador",template:"Template",weather:"Clima"}},conditional:{chip:"Chip"},sub_element_editor:{title:"Editor de chip"}},form:{alignment_picker:{values:{center:"Centro",default:"Alinhamento padrão",end:"Fim",justify:"Justificado",start:"Início"}},color_picker:{values:{default:"Cor padrão"}},icon_type_picker:{values:{default:"Tipo padrão","entity-picture":"Imagem da entidade",icon:"Ícone",none:"Nenhum"}},info_picker:{values:{default:"Informação padrão","last-changed":"Última alteração","last-updated":"Última atualização",name:"Nome",none:"Nenhum",state:"Estado"}},layout_picker:{values:{default:"Layout padrão",horizontal:"Layout horizontal",vertical:"Layout vertical"}}}},th={card:Qd,editor:eh},nh=Object.freeze({__proto__:null,card:Qd,default:th,editor:eh}),ih={not_found:"Entidade não encontrada"},oh={card:{chips:{alignment:"Alinhamento"},climate:{hvac_modes:"Modos HVAC",show_temperature_control:"Controlo de temperatura?"},cover:{show_buttons_control:"Botões de controlo?",show_position_control:"Controlo de posição?",show_tilt_position_control:"Controlo de inclinação?"},fan:{show_oscillate_control:"Controlo de oscilação?",show_percentage_control:"Controlo de percentagem?"},generic:{collapsible_controls:"Colapsar controlos quando desligado",color:"Cor",content_info:"Conteúdo",fill_container:"Preencher contentor",icon_animation:"Animar ícone quando ativo?",icon_color:"Cor do ícone",icon_type:"Tipo de ícone",layout:"Layout",primary_info:"Informação principal",secondary_info:"Informação secundária",use_entity_picture:"Usar imagem da entidade?"},humidifier:{show_target_humidity_control:"Controlo de humidade?"},light:{incompatible_controls:"Alguns controlos podem não ser exibidos se a luz não suportar a funcionalidade.",show_brightness_control:"Controlo de brilho?",show_color_control:"Controlo de cor?",show_color_temp_control:"Controlo de temperatura da cor?",use_light_color:"Usar cor da luz"},lock:{lock:"Trancar",open:"Aberto",unlock:"Destrancar"},"media-player":{media_controls:"Controlos de media",media_controls_list:{next:"Próxima faixa",on_off:"Ligar/Desligar",play_pause_stop:"Tocar/pausa/stop",previous:"Faixa anterior",repeat:"Modo repetir",shuffle:"Baralhar"},show_volume_level:"Mostrar nível do volume",use_media_artwork:"Usar arte do media",use_media_info:"Usar informação do media",volume_controls:"Controlos de volume",volume_controls_list:{volume_buttons:"Botões de volume",volume_mute:"Calar",volume_set:"Nível do volume"}},number:{display_mode:"Modo de exibição",display_mode_list:{buttons:"Botões",default:"Por defeito (slider)",slider:"Deslizador"}},template:{badge_color:"Cor do crachá",badge_icon:"Icóne do crachá",content:"Conteúdo",entity_extra:"Usado em modelos e ações",label:"Rótulo",multiline_secondary:"Secundária multilinha?",picture:"Imagem (irá substituir o ícone)",primary:"Informação principal",secondary:"Informação secundária"},title:{subtitle:"Subtítulo",subtitle_tap_action:"Ação ao tocar no subtítulo",title:"Título",title_tap_action:"Ação ao tocar no título"},update:{show_buttons_control:"Botões de controlo?"},vacuum:{commands:"Comandos",commands_list:{on_off:"Ligar/Desligar"}},weather:{show_conditions:"Condições?",show_temperature:"Temperatura?"}},chip:{"chip-picker":{add:"Adicionar ficha",chips:"Fichas",clear:"Limpar",edit:"Editar",select:"Selecionar ficha",types:{action:"Ação","alarm-control-panel":"Alarme",back:"Voltar",conditional:"Condicional",entity:"Entidade",light:"Iluminação",menu:"Menu",spacer:"Espaçador",template:"Modelo",weather:"Clima"}},conditional:{chip:"Ficha"},sub_element_editor:{title:"Editor de fichas"}},form:{alignment_picker:{values:{center:"Centrado",default:"Alinhamento predefinido",end:"Fim",justify:"Justificado",start:"Início"}},color_picker:{values:{default:"Cor padrão"}},icon_type_picker:{values:{default:"Tipo predefinido","entity-picture":"Entidade de imagem",icon:"Ícone",none:"Nenhum"}},info_picker:{values:{default:"Informações padrão","last-changed":"Última alteração","last-updated":"Última atualização",name:"Nome",none:"Nenhum",state:"Estado"}},layout_picker:{values:{default:"Layout padrão",horizontal:"Layout horizontal",vertical:"Layout vertical"}}}},rh={card:ih,editor:oh},ah=Object.freeze({__proto__:null,card:ih,default:rh,editor:oh}),lh={card:{chips:{alignment:"Aliniere"},climate:{hvac_modes:"Moduri HVAC",show_temperature_control:"Comenzi temperatură?"},cover:{show_buttons_control:"Comenzi pentru control?",show_position_control:"Comandă pentru poziție?",show_tilt_position_control:"Comandă pentru înclinare?"},fan:{icon_animation:"Animare pictograma la activare?",show_oscillate_control:"Comandă oscilație?",show_percentage_control:"Comandă procent?"},generic:{collapsible_controls:"Restrângere la dezactivare",content_info:"Conținut",fill_container:"Umplere container",icon_color:"Culoare pictogramă",icon_type:"Tip pictogramă",layout:"Aranjare",primary_info:"Informație principală",secondary_info:"Informație secundară",use_entity_picture:"Imagine?"},humidifier:{show_target_humidity_control:"Comenzi umiditate?"},light:{incompatible_controls:"Unele comenzi ar putea să nu fie afișate dacă lumina nu suportă această caracteristică.",show_brightness_control:"Comandă pentru strălucire?",show_color_control:"Comandă pentru culoare?",show_color_temp_control:"Comandă pentru temperatură de culoare?",use_light_color:"Folosește culoarea luminii"},lock:{lock:"Încuie",open:"Deschide",unlock:"Descuie"},"media-player":{media_controls:"Comenzi media",media_controls_list:{next:"Pista următoare",on_off:"Pornit/Oprit",play_pause_stop:"Redare/Pauză/Stop",previous:"Pista anterioară",repeat:"Mod repetare",shuffle:"Amestecare"},show_volume_level:"Nivel volum",use_media_artwork:"Grafică media",use_media_info:"Informații media",volume_controls:"Comenzi volum",volume_controls_list:{volume_buttons:"Comenzi volum",volume_mute:"Dezactivare sunet",volume_set:"Nivel volum"}},template:{badge_color:"Culoare insignă",badge_icon:"Pictogramă insignă",content:"Conținut",entity_extra:"Folosită în șabloane și acțiuni",multiline_secondary:"Informație secundară pe mai multe linii?",picture:"Imagine (inlocuiește pictograma)",primary:"Informație principală",secondary:"Informație secundară"},title:{subtitle:"Subtitlu",title:"Titlu"},update:{show_buttons_control:"Comenzi control?"},vacuum:{commands:"Comenzi"},weather:{show_conditions:"Condiții?",show_temperature:"Temperatură?"}},chip:{"chip-picker":{add:"Adaugă jeton",chips:"Jetoane",clear:"Șterge",edit:"Modifică",select:"Alege jeton",types:{action:"Acțiune","alarm-control-panel":"Alarmă",back:"Înapoi",conditional:"Condițional",entity:"Entitate",light:"Lumină",menu:"Meniu",template:"Șablon",weather:"Vreme"}},conditional:{chip:"Jeton"},sub_element_editor:{title:"Editor jeton"}},form:{alignment_picker:{values:{center:"Centrat",default:"Aliniere implicită",end:"Dreapta",justify:"Umplere",start:"Stânga"}},color_picker:{values:{default:"Culoare implicită"}},icon_type_picker:{values:{default:"Tip implicit","entity-picture":"Imagine",icon:"Pictogramă",none:"Niciuna"}},info_picker:{values:{default:"Informație implicită","last-changed":"Ultima modificare","last-updated":"Ultima actulizare",name:"Nume",none:"Niciuna",state:"Stare"}},layout_picker:{values:{default:"Aranjare implicită",horizontal:"Orizontală",vertical:"Verticală"}}}},sh={editor:lh},ch=Object.freeze({__proto__:null,default:sh,editor:lh}),uh={not_found:"Сущность не найдена"},dh={card:{chips:{alignment:"Выравнивание"},climate:{hvac_modes:"Режимы работы",show_temperature_control:"Управлять целевой температурой?"},cover:{show_buttons_control:"Добавить кнопки управления?",show_position_control:"Управлять позицией?",show_tilt_position_control:"Управлять наклоном?"},empty:{no_config_options:"Эта карточка не имеет опций конфигурации."},fan:{icon_animation:"Анимировать иконку когда включено?",show_direction_control:"Направление?",show_oscillate_control:"Oscillate control?",show_percentage_control:"Управлять процентами?"},generic:{collapsible_controls:"Сворачивать элементы управления при выключении",color:"Цвет",content_info:"Содержимое",fill_container:"Заполнение",icon_animation:"Анимировать иконку, когда активна?",icon_color:"Цвет иконки",icon_type:"Тип иконки",layout:"Расположение",primary_info:"Основная информация",secondary_info:"Второстепенная информация",use_entity_picture:"Использовать изображение объекта?"},humidifier:{show_target_humidity_control:"Управлять целевым уровенем влажности?"},light:{incompatible_controls:"Некоторые элементы управления могут не отображаться, если ваш светильник не поддерживает эти функции.",show_brightness_control:"Управлять яркостью?",show_color_control:"Управлять цветом?",show_color_temp_control:"Управлять цветовой температурой?",use_light_color:"Использовать текущий цвет света"},lock:{lock:"Закрыто",open:"Открыто",unlock:"Разблокировано"},"media-player":{media_controls:"Управление медиа-устройством",media_controls_list:{next:"Следующий трек",on_off:"Включение/выключение",play_pause_stop:"Воспроизведение/пауза/остановка",previous:"Предыдущий трек",repeat:"Режим повтора",shuffle:"Перемешивание"},show_volume_level:"Показать уровень громкости",use_media_artwork:"Использовать обложку с медиа-устройства",use_media_info:"Использовать информацию с медиа-устройства",volume_controls:"Регулятор громкости",volume_controls_list:{volume_buttons:"Кнопки громкости",volume_mute:"Без звука",volume_set:"Уровень громкости"}},number:{display_mode:"Режим отображения",display_mode_list:{buttons:"Кнопки",default:"Стандартно (слайдер)",slider:"Слайдер"}},template:{badge_color:"Цвет значка",badge_icon:"Иконка значка",content:"Содержимое",entity_extra:"Используется в шаблонах и действиях",label:"Ярлык",multiline_secondary:"Многострочная Второстепенная информация?",picture:"Изображение (заменить иконку)",primary:"Основная информация",secondary:"Второстепенная информация"},title:{subtitle:"Подзаголовок",subtitle_tap_action:"Действие при нажатии на подзаголовок",title:"Заголовок",title_tap_action:"Действие при нажатии на заголовок"},update:{show_buttons_control:"Кнопки управления?"},vacuum:{commands:"Команды",commands_list:{on_off:"Включить/выключить"}},weather:{show_conditions:"Условия?",show_temperature:"Температура?"}},chip:{"chip-picker":{add:"Добавить мини-карточку",chips:"Мини-карточки",clear:"Очистить",edit:"Изменить",select:"Выбрать мини-карточку",types:{action:"Действие","alarm-control-panel":"Тревога",back:"Назад",conditional:"Условия",entity:"Объект",light:"Освещение",menu:"Меню",quickbar:"Панель быстрого доступа",spacer:"Пробел",template:"Шаблон",weather:"Погода"}},conditional:{chip:"Мини-карточка"},sub_element_editor:{title:"Редактор мини-карточек"}},form:{alignment_picker:{values:{center:"По центру",default:"Выравнивание по умолчанию",end:"К концу",justify:"На всю ширину",start:"К началу"}},color_picker:{values:{default:"Цвет по умолчанию"}},icon_type_picker:{values:{default:"По умолчанию","entity-picture":"Изображение",icon:"Иконка",none:"Нет"}},info_picker:{values:{default:"По умолчанию","last-changed":"Последнее изменение","last-updated":"Последнее обновление",name:"Имя",none:"Нет",state:"Статус"}},layout_picker:{values:{default:"Расположение по умолчанию",horizontal:"Горизонтальное расположение",vertical:"Вертикальное расположение"}}}},hh={card:uh,editor:dh},ph=Object.freeze({__proto__:null,card:uh,default:hh,editor:dh}),fh={not_found:"Entita nenájdená"},mh={badge:{template:{area_helper:"Používa sa v šablónach",content:"Obsah",entity_helper:"Používa sa v šablónach a interakciách",label:"Nápis"}},card:{chips:{alignment:"Zarovnanie"},climate:{hvac_modes:"HVAC mód",show_temperature_control:"Ovládanie teploty?"},cover:{show_buttons_control:"Zobraziť ovládacie tlačidlá?",show_position_control:"Ovládanie pozície?",show_tilt_position_control:"Ovládanie natočenia?"},empty:{no_config_options:"Táto karta nemá žiadne možnosti konfigurácie."},fan:{show_direction_control:"Ovládanie smeru?",show_oscillate_control:"Ovládanie oscilácie?",show_percentage_control:"Ovládanie rýchlosti v percentách?"},generic:{area:"Oblasť",collapsible_controls:"Skryť ovládanie v stave VYP",color:"Farba",content_info:"Obsah",entity:"Entita",fill_container:"Vyplniť priestor",icon_animation:"Animovaná ikona v stave ZAP?",icon_color:"Farba ikony",icon_type:"Typ ikony",layout:"Rozloženie",picture:"Obrázok",picture_helper:"Ak je nastavené, nahradí ikonu.",primary_info:"Základné info",secondary_info:"Doplnkové info",use_entity_picture:"Použiť obrázok entity?"},humidifier:{show_target_humidity_control:"Ovládanie vlhkosti?"},light:{incompatible_controls:"Niektoré ovládacie prvky sa nemusia zobraziť, pokiaľ ich svetlo nepodporuje.",show_brightness_control:"Ovládanie jasu?",show_color_control:"Ovládanie farby?",show_color_temp_control:"Ovládanie teploty farby?",use_light_color:"Použiť farbu svetla"},lock:{lock:"Zamknuté",open:"Otvorené",unlock:"Odomknuté"},"media-player":{media_controls:"Ovládanie média",media_controls_list:{next:"Ďalšia",on_off:"Zap / Vyp",play_pause_stop:"Spustiť/pauza/stop",previous:"Predchádzajúca",repeat:"Opakovať",shuffle:"Premiešať"},show_volume_level:"Zobraziť úroveň hlasitosti",use_media_artwork:"Použiť obrázok z média",use_media_info:"Použiť info o médiu",volume_controls:"Ovládanie hlasitosti",volume_controls_list:{volume_buttons:"Tlačidlá hlasitosti",volume_mute:"Stlmiť",volume_set:"Úroveň hlasitosti"}},number:{display_mode:"Režim zobrazenia",display_mode_list:{buttons:"Tlačidlá",default:"Predvolené (posúvač)",slider:"Posúvač"}},template:{area:"Oblasť",area_helper:"Používa sa v šablónach a funkciách",badge:"Odznak",badge_color:"Farba odznaku",badge_icon:"Ikona odznaku",badge_text:"Text odznaku",badge_text_helper:"Ak je nastavené, nahradí ikonu.",content:"Obsah",entity_extra:"Použitá v šablónach a akciách",entity_helper:"Používa sa v šablónach, interakciách a funkciách",entity_helper_legacy:"Používa sa v šablónach a interakciách",label:"Štítok",layout:"Rozloženie",multiline_secondary:"Povoliť viacriadkové doplnkové informácie",multiline_secondary_helper:"Karta môže byť vyššia, aby sa do nej vošiel text, a nemusí byť vždy zarovnaná s mriežkovým systémom.",picture:"Obrázok (nahrádza ikonu)",primary:"Základné info",secondary:"Doplnkové info"},title:{subtitle:"Podnadpis",subtitle_tap_action:"Akcia klepnutia na titulky",title:"Nadpis",title_tap_action:"Akcia klepnutia na názov"},update:{show_buttons_control:"Zobraziť ovládacie tlačidlá?"},vacuum:{commands:"Príkazy",commands_list:{on_off:"Zapnúť/Vypnúť"}},weather:{show_conditions:"Zobraziť podmienky?",show_temperature:"Zobraziť teplotu?"}},chip:{"chip-picker":{add:"Pridať štítok",chips:"Štítky",clear:"Vymazať",edit:"Editovať",select:"Vybrať štítok",types:{action:"Akcia","alarm-control-panel":"Alarm",back:"Späť",conditional:"Podmienené",entity:"Entita",light:"Svetlo",menu:"Menu",quickbar:"Rýchla lišta",spacer:"Medzera",template:"Šablóna",weather:"Počasie"}},conditional:{chip:"Čip"},sub_element_editor:{title:"Editor štítkov"}},form:{alignment_picker:{values:{center:"Stred",default:"Predvolené zarovnanie",end:"Koniec",justify:"Vyplniť",start:"Začiatok"}},color_picker:{values:{default:"Predvolená farba"}},icon_type_picker:{values:{default:"Predvolený typ","entity-picture":"Obrázok entity",icon:"Ikona",none:"Žiadny"}},info_picker:{values:{default:"Predvolené informácie","last-changed":"Posledná zmena","last-updated":"Posledná aktualizácia",name:"Názov",none:"Žiadna",state:"Stav"}},layout_picker:{values:{default:"Predvolené rozloženie",horizontal:"Vodorovné rozloženie",vertical:"Zvislé rozloženie"}}},section:{badge:"Odznak",content:"Obsah",context:"Kontext",features:"Funkcie",interactions:"Interakcie",layout:"Rozloženie"}},vh={description:"Nastavenie vašej karty bolo prenesené do novej verzie. Viac informácií o zmenách nájdete na {link}.",ok:"Ok",post:"príspevku na GitHub",revert:"Vrátiť späť",title:"Karta aktualizovaná"},gh={card:fh,editor:mh,migration:vh},_h=Object.freeze({__proto__:null,card:fh,default:gh,editor:mh,migration:vh}),yh={not_found:"Entiteta ni najdena"},bh={card:{chips:{alignment:"Poravnava"},climate:{hvac_modes:"HVAC načini",show_temperature_control:"Nadzor temperature?"},cover:{show_buttons_control:"Gumbi za upravljanje?",show_position_control:"Nadzor položaja?",show_tilt_position_control:"Nadzor nagiba?"},fan:{show_oscillate_control:"Kontrola nihanja?",show_percentage_control:"Kontrola v odstotkih?"},generic:{collapsible_controls:"Strni kontrolnike, ko so izklopljeni",content_info:"Vsebina",fill_container:"Zapolnitev prostora",icon_animation:"Animacija ikone, ko je aktivna?",icon_color:"Barva ikone",icon_type:"Vrsta ikone",layout:"Postavitev",primary_info:"Primarna informacija",secondary_info:"Sekundarna informacija",use_entity_picture:"Uporabi sliko entitete?"},humidifier:{show_target_humidity_control:"Nadzor vlažnosti?"},light:{incompatible_controls:"Nekateri kontrolniki morda ne bodo prikazani, če vaša luč ne podpira te funkcije.",show_brightness_control:"Nadzor svetlosti?",show_color_control:"Nadzor barv?",show_color_temp_control:"Nadzor temperature barve?",use_light_color:"Uporabi svetlo barvo"},lock:{lock:"Zaklepanje",open:"Odprto",unlock:"Odkleni"},"media-player":{media_controls:"Nadzor medijev",media_controls_list:{next:"Naslednja skladba",on_off:"Vklop/izklop",play_pause_stop:"Predvajaj/pavza/ustavi",previous:"Prejšnja skladba",repeat:"Ponavljajoči način",shuffle:"Naključno"},show_volume_level:"Pokaži raven glasnosti",use_media_artwork:"Uporabite medijsko umetniško delo",use_media_info:"Uporabite informacije o medijih",volume_controls:"Kontrole glasnosti",volume_controls_list:{volume_buttons:"Gumbi za glasnost",volume_mute:"Tiho",volume_set:"Raven glasnosti"}},number:{display_mode:"Način prikaza",display_mode_list:{buttons:"Gumbi",default:"Privzeto (drsnik)",slider:"Drsnik"}},template:{badge_color:"Barva značke",badge_icon:"Ikona značke",content:"Vsebina",entity_extra:"Uporablja se v predlogah in dejanjih",multiline_secondary:"Večvrstični sekundarni?",picture:"Slika (nadomestila bo ikono)",primary:"Primarna informacija",secondary:"Sekundarna informacija"},title:{subtitle:"Podnaslov",subtitle_tap_action:"Dejanje dotika podnapisov",title:"Naziv",title_tap_action:"Dejanje dotika naslova"},update:{show_buttons_control:"Gumbi za upravljanje?"},vacuum:{commands:"Ukazi",commands_list:{on_off:"Vklop/izklop"}},weather:{show_conditions:"Pogoji?",show_temperature:"Temperatura?"}},chip:{"chip-picker":{add:"Dodaj čip",chips:"Čipi",clear:"Pobriši",edit:"Uredi",select:"Izbira čipa",types:{action:"Dejanje","alarm-control-panel":"Alarm",back:"Nazaj",conditional:"Pogojno",entity:"Entiteta",light:"Svetloba",menu:"Meni",spacer:"Distančnik",template:"Predloga",weather:"Vreme"}},conditional:{chip:"Ćiš"},sub_element_editor:{title:"Urejevalnik čipov"}},form:{alignment_picker:{values:{center:"Center",default:"Privzeta poravnava",end:"Konec",justify:"Poravnava",start:"Pričetek"}},color_picker:{values:{default:"Privzeta barva"}},icon_type_picker:{values:{default:"Privzeta vrsta","entity-picture":"Slika entitete",icon:"Ikona",none:"Brez"}},info_picker:{values:{default:"Privzete informacije","last-changed":"Zadnja sprememba","last-updated":"Zadnja posodobitev",name:"Naziv",none:"Brez",state:"Stanje"}},layout_picker:{values:{default:"Privzeta postavitev",horizontal:"Horizontalna postavitev",vertical:"Vertikalna postavitev"}}}},kh={card:yh,editor:bh},wh={not_found:"Enheten hittades inte"},xh={card:{chips:{alignment:"Justering"},climate:{hvac_modes:"HVAC-lägen",show_temperature_control:"Temperaturkontroll?"},cover:{show_buttons_control:"Visa kontrollknappar?",show_position_control:"Visa positionskontroll?",show_tilt_position_control:"Visa lutningskontroll?"},empty:{no_config_options:"Detta kort har inga konfigurationsalternativ."},fan:{show_direction_control:"Riktningskontroll?",show_oscillate_control:"Kontroll för oscillera?",show_percentage_control:"Procentuell kontroll?"},generic:{collapsible_controls:"Dölj kontroller när enheten är av",color:"Färg",content_info:"Innehåll",fill_container:"Fyll container",icon_animation:"Animera ikonen när enheten är på?",icon_color:"Ikonens färg",icon_type:"Ikontyp",layout:"Layout",primary_info:"Primär information",secondary_info:"Sekundär information",use_entity_picture:"Använd enhetens bild?"},humidifier:{show_target_humidity_control:"Fuktkontroll?"},light:{incompatible_controls:"Kontroller som inte stöds av enheten kommer inte visas.",show_brightness_control:"Styr ljushet?",show_color_control:"Styr färg?",show_color_temp_control:"Färgtemperaturkontroll?",use_light_color:"Styr ljusets färg"},lock:{lock:"Lås",open:"Öppna",unlock:"Lås upp"},"media-player":{media_controls:"Mediakontroller",media_controls_list:{next:"Nästa spår",on_off:"Slå på/av",play_pause_stop:"Spela/pausa/stoppa",previous:"Föregående spår",repeat:"Upprepa",shuffle:"Blanda"},show_volume_level:"Volymkontroll",use_media_artwork:"Visa mediaomslag",use_media_info:"Använd media information",volume_controls:"Volymkontroller",volume_controls_list:{volume_buttons:"Volymknappar",volume_mute:"Ljud av",volume_set:"Volymnivå"}},number:{display_mode:"Visningsläge",display_mode_list:{buttons:"Knappar",default:"Standard (skjutreglage)",slider:"Skjutreglage"}},template:{badge_color:"Färg på märke",badge_icon:"Märke ikon",content:"Innehåll",entity_extra:"Används i mallar och åtgärder",label:"Etikett",multiline_secondary:"Sekundär med flera rader?",picture:"Bild (ersätter ikonen)",primary:"Primär information",secondary:"Sekundär information"},title:{subtitle:"Underrubrik",subtitle_tap_action:"Subtitle tap action",title:"Rubrik",title_tap_action:"Titel tryck åtgärd"},update:{show_buttons_control:"Visa kontrollknappar?"},vacuum:{commands:"Kommandon",commands_list:{on_off:"Slå av/på"}},weather:{show_conditions:"Förhållanden?",show_temperature:"Temperatur?"}},chip:{"chip-picker":{add:"Lägg till chip",chips:"Chips",clear:"Rensa",edit:"Redigera",select:"Välj chip",types:{action:"Åtgärd","alarm-control-panel":"Alarm",back:"Bakåt",conditional:"Villkorad",entity:"Enhet",light:"Ljus",menu:"Meny",quickbar:"Snabbfält",spacer:"Avståndshållare",template:"Mall",weather:"Väder"}},conditional:{chip:"Chip"},sub_element_editor:{title:"Chipredigerare"}},form:{alignment_picker:{values:{center:"Centrerad",default:"Standard (början)",end:"Slutet",justify:"Anpassa",start:"Starta"}},color_picker:{values:{default:"Standardfärg"}},icon_type_picker:{values:{default:"Standard typ","entity-picture":"Enhetsbild",icon:"Ikon",none:"Ingen"}},info_picker:{values:{default:"Förvald information","last-changed":"Sist ändrad","last-updated":"Sist uppdaterad",name:"Namn",none:"Ingen",state:"Status"}},layout_picker:{values:{default:"Standard",horizontal:"Horisontell",vertical:"Vertikal"}}}},Eh={card:wh,editor:xh},Ch={not_found:"Varlık bulunamadı"},Ah={card:{chips:{alignment:"Hizalama"},climate:{hvac_modes:"HVAC Modları",show_temperature_control:"Sıcaklık kontrolü?"},cover:{show_buttons_control:"Düğme kontrolleri?",show_position_control:"Pozisyon kontrolü?",show_tilt_position_control:"Eğim kontrolü?"},empty:{no_config_options:"Bu kartın yapılandırma seçeneği yok."},fan:{show_direction_control:"Yön kontrolü?",show_oscillate_control:"Salınım kontrolü?",show_percentage_control:"Yüzde kontrolü?"},generic:{collapsible_controls:"Kapalıyken kontrolleri daralt",color:"Renk",content_info:"İçerik",fill_container:"Alanı doldur",icon_animation:"Aktif olduğunda simgeyi hareket ettir?",icon_color:"Simge renki",icon_type:"İkon tipi",layout:"Düzen",primary_info:"Birinci bilgi",secondary_info:"İkinci bilgi",use_entity_picture:"Varlık resmi kullanılsın?"},humidifier:{show_target_humidity_control:"Nem kontrolü?"},light:{incompatible_controls:"Kullandığınız lamba bu özellikleri desteklemiyorsa bazı kontroller görüntülenemeyebilir.",show_brightness_control:"Parlaklık kontrolü?",show_color_control:"Renk kontrolü?",show_color_temp_control:"Renk ısısı kontrolü?",use_light_color:"Işık rengini kullan"},lock:{lock:"Kilitle",open:"Aç",unlock:"Kilidi aç"},"media-player":{media_controls:"Medya kontrolleri",media_controls_list:{next:"Sonraki parça",on_off:"Aç/Kapat",play_pause_stop:"Oynat/duraklat/durdur",previous:"Önceki parça",repeat:"Tekrarlama modu",shuffle:"Karışık çal"},show_volume_level:"Ses seviyesini göster",use_media_artwork:"Medya resimlerini kullan",use_media_info:"Medya bilgilerini kullan",volume_controls:"Ses seviyesi kontrolleri",volume_controls_list:{volume_buttons:"Ses butonları",volume_mute:"Sessize al",volume_set:"Ses seviyesi"}},number:{display_mode:"Görüntüleme Modu",display_mode_list:{buttons:"Butonlar",default:"Varsayılan (kayan)",slider:"Kayan"}},template:{badge_color:"Rozet rengi",badge_icon:"Rozet simgesi",content:"İçerik",entity_extra:"Şablonlarda ve eylemlerde kullanılsın",label:"Etiket",multiline_secondary:"İkinci bilgi çok satır olsun?",picture:"Resim (ikonun yerine geçecek)",primary:"Birinci bilgi",secondary:"İkinci bilgi"},title:{subtitle:"Altbaşlık",subtitle_tap_action:"Dokunma eylemi alt başlığı",title:"Başlık",title_tap_action:"Dokunma eylemi başlığı"},update:{show_buttons_control:"Düğme kontrolü?"},vacuum:{commands:"Komutlar",commands_list:{on_off:"Aç/Kapat"}},weather:{show_conditions:"Hava koşulu?",show_temperature:"Sıcaklık?"}},chip:{"chip-picker":{add:"Chip ekle",chips:"Chipler",clear:"Temizle",edit:"Düzenle",select:"Chip seç",types:{action:"Eylem","alarm-control-panel":"Alarm",back:"Geri",conditional:"Koşullu",entity:"Varlık",light:"Işık",menu:"Menü",spacer:"Boşluk",template:"Şablon",weather:"Hava Durumu"}},conditional:{chip:"Chip"},sub_element_editor:{title:"Chip düzenleyici"}},form:{alignment_picker:{values:{center:"Ortala",default:"Varsayılan hizalama",end:"Sağa yasla",justify:"İki yana yasla",start:"Sola yasla"}},color_picker:{values:{default:"Varsayılan renk"}},icon_type_picker:{values:{default:"Varsayılan tip","entity-picture":"Varlık resmi",icon:"Simge",none:"Hiçbiri"}},info_picker:{values:{default:"Varsayılan bilgi","last-changed":"Son Değişim","last-updated":"Son Güncelleme",name:"İsim",none:"None",state:"Durum"}},layout_picker:{values:{default:"Varsayılan düzen",horizontal:"Yatay düzen",vertical:"Dikey düzen"}}}},Sh={card:Ch,editor:Ah},Th={not_found:"Сутність не знайдено"},Ih={card:{chips:{alignment:"Вирівнювання"},climate:{hvac_modes:"Режими",show_temperature_control:"Керування температурою?"},cover:{show_buttons_control:"Кнопки керування?",show_position_control:"Керування позицією?",show_tilt_position_control:"Керування нахилом?"},fan:{show_oscillate_control:"Керування повротом?",show_percentage_control:"Керування швидкістю?"},generic:{collapsible_controls:"Приховувати елементи керування коли вимкнено?",content_info:"Вміст",fill_container:"Заповнити контейнер",icon_animation:"Анімувати іконку при активації?",icon_color:"Колір іконки",icon_type:"Тип іконки",layout:"Розташування",primary_info:"Головна інформація",secondary_info:"Додаткова інформація",use_entity_picture:"Використовувати зображення сутності?"},humidifier:{show_target_humidity_control:"Керування вологістю?"},light:{incompatible_controls:"Деякі елементи керування можуть не відображатись якщо ваш пристрій не підтримує цю функцію.",show_brightness_control:"Контроль яскравості?",show_color_control:"Керування кольором світла?",show_color_temp_control:"Керування температурою світла?",use_light_color:"Використовувати колір світла"},lock:{lock:"Зачинити",open:"Відкрити",unlock:"Відчинити"},"media-player":{media_controls:"Керування медіа",media_controls_list:{next:"Наступний трек",on_off:"Увімкнути/Вимкнути",play_pause_stop:"Відтворити/пауза/стоп",previous:"Попередній трек",repeat:"Режим повторення",shuffle:"Перемішати"},show_volume_level:"Показати рівень гучності",use_media_artwork:"Використовувати зображення медіа",use_media_info:"Використовувати інформацію медіа",volume_controls:"Елементи керування гучністю",volume_controls_list:{volume_buttons:"Кнопки гучності",volume_mute:"Вимк. звук",volume_set:"Рівень гучності"}},number:{display_mode:"Відображати режим",display_mode_list:{buttons:"Кнопки",default:"За замовчуванням (повзунок)",slider:"Повзунок"}},template:{badge_color:"Колір значка",badge_icon:"Іконка значка",content:"Вміст",entity_extra:"Використовується в шаблонах та діях",multiline_secondary:"Багаторядкова додаткова інформація?",picture:"Зображення (замінить іконку)",primary:"Головна інформація",secondary:"Додаткова інформація"},title:{subtitle:"Підзаголовок",subtitle_tap_action:"Дія при дотику до підзаголовку",title:"Заголовок",title_tap_action:"Дія при дотику до заголовку"},update:{show_buttons_control:"Кнопки керування?"},vacuum:{commands:"Команди",commands_list:{on_off:"Увімкнути/Вимкнути"}},weather:{show_conditions:"Умови?",show_temperature:"Температура?"}},chip:{"chip-picker":{add:"Додати міні-картку",chips:"Міні-картки",clear:"Очистити",edit:"Редагувати",select:"Обрати міні-картку",types:{action:"Дія","alarm-control-panel":"Сигналізація",back:"Назад",conditional:"Умовна",entity:"Сутність",light:"Світло",menu:"Меню",spacer:"Порожнє місце",template:"Вручну",weather:"Погода"}},conditional:{chip:"Міні-картка"},sub_element_editor:{title:"Редактор міні-карток"}},form:{alignment_picker:{values:{center:"По центру",default:"Вирівнювання за замовчуванням",end:"В кінці",justify:"Вирівняти",start:"На початку"}},color_picker:{values:{default:"Колір за замовчуванням"}},icon_type_picker:{values:{default:"За замовчуванням","entity-picture":"Зображення сутності",icon:"Іконка",none:"Нічого"}},info_picker:{values:{default:"Інформація за замовчуванням","last-changed":"Востаннє змінено","last-updated":"Востаннє оновлено",name:"Назва",none:"Нічого",state:"Стан"}},layout_picker:{values:{default:"Розташування за замовчуванням",horizontal:"Горизонтальне розташування",vertical:"Вертикальне розташування"}}}},Oh={card:Th,editor:Ih},Mh={not_found:"Không tìm thấy thực thể"},zh={card:{chips:{alignment:"Căn chỉnh"},climate:{hvac_modes:"Chế độ điều hòa",show_temperature_control:"Điều khiển nhiệt độ?"},cover:{show_buttons_control:"Điều khiển nút bấm?",show_position_control:"Điều khiển vị trí?",show_tilt_position_control:"Điều khiển độ nghiêng?"},fan:{show_oscillate_control:"Điều khiển xoay?",show_percentage_control:"Điều khiển dạng phần trăm?"},generic:{collapsible_controls:"Thu nhỏ điều kiển khi tắt",content_info:"Nội dung",fill_container:"Làm đầy ô chứa",icon_animation:"Biểu tượng chuyển động khi kích hoạt?",icon_color:"Màu biểu tượng",icon_type:"Kiểu biểu tượng",layout:"Bố cục",primary_info:"Thông tin chính",secondary_info:"Thông tin phụ",use_entity_picture:"Dùng ảnh của thực thể?"},humidifier:{show_target_humidity_control:"Điều khiển độ ẩm?"},light:{incompatible_controls:"Một số điều khiển sẽ không được hiển thị nếu đèn của bạn không hỗ trợ tính năng đó.",show_brightness_control:"Điều khiển độ sáng?",show_color_control:"Điều khiển màu sắc?",show_color_temp_control:"Điều khiển nhiệt độ màu?",use_light_color:"Dùng màu đèn"},lock:{lock:"Khóa",open:"Mở",unlock:"Mở khóa"},"media-player":{media_controls:"Điều khiển đa phương tiện",media_controls_list:{next:"Bài tiếp theo",on_off:"Bật/tắt",play_pause_stop:"Phát/tạm dừng/dừng",previous:"Bài trước",repeat:"Chế độ lặp lại",shuffle:"Xáo trộn"},show_volume_level:"Hiện mức âm lượng",use_media_artwork:"Dùng ảnh đa phương tiện",use_media_info:"Dùng thông tin đa phương tiện",volume_controls:"Điều khiển âm lượng",volume_controls_list:{volume_buttons:"Nút âm lượng",volume_mute:"Im lặng",volume_set:"Mức âm lượng"}},number:{display_mode:"Chế độ hiển thị",display_mode_list:{buttons:"Nút",default:"Mặc định (thanh trượt)",slider:"Thanh trượt"}},template:{badge_color:"Màu huy hiệu",badge_icon:"Biểu tượng huy hiệu",content:"Nội dung",entity_extra:"Được sử dụng trong bản mẫu và hành động",multiline_secondary:"Nhiều dòng thông tin phụ?",picture:"Ảnh (thay cho biểu tượng)",primary:"Thông tin chính",secondary:"Thông tin phụ"},title:{subtitle:"Phụ đề",subtitle_tap_action:"Hành động khi nhấp phụ đề",title:"Tiêu đề",title_tap_action:"Hành động khi nhấp tiêu đề"},update:{show_buttons_control:"Điều khiển nút bấm?"},vacuum:{commands:"Mệnh lệnh",commands_list:{on_off:"Bật/tắt"}},weather:{show_conditions:"Điều kiện?",show_temperature:"Nhiệt độ?"}},chip:{"chip-picker":{add:"Thêm phỉnh",chips:"Phỉnh",clear:"Tẩy trống",edit:"Chỉnh sửa",select:"Chọn phỉnh",types:{action:"Hành động","alarm-control-panel":"Báo động",back:"Quay về",conditional:"Điều kiện",entity:"Thực thể",light:"Đèn",menu:"Trình đơn",spacer:"Ngăn cách",template:"Mẫu",weather:"Thời tiết"}},conditional:{chip:"Phỉnh"},sub_element_editor:{title:"Trình soạn phỉnh"}},form:{alignment_picker:{values:{center:"Căn giữa",default:"Căn chỉnh mặc định",end:"Căn cuối",justify:"Căn hai bên",start:"Căn đầu"}},color_picker:{values:{default:"Màu mặc định"}},icon_type_picker:{values:{default:"Kiểu mặc định","entity-picture":"Ảnh thực thể",icon:"Biểu tượng",none:"Không có"}},info_picker:{values:{default:"Thông tin mặc định","last-changed":"Lần thay đổi cuối","last-updated":"Lần cập nhật cuối",name:"Tên",none:"Không có",state:"Trạng thái"}},layout_picker:{values:{default:"Bố cục mặc định",horizontal:"Bố cục ngang",vertical:"Bố cục dọc"}}}},Ph={card:Mh,editor:zh},Lh={not_found:"未找到实体"},Nh={card:{chips:{alignment:"对齐"},climate:{hvac_modes:"空调模式",show_temperature_control:"温度控制?"},cover:{show_buttons_control:"按钮控制?",show_position_control:"位置控制?",show_tilt_position_control:"角度控制?"},empty:{no_config_options:"这个卡片没有可配置的选项。"},fan:{show_direction_control:"方向控制?",show_oscillate_control:"摆动控制?",show_percentage_control:"百分比控制?"},generic:{collapsible_controls:"关闭时隐藏控制器",color:"颜色",content_info:"内容",fill_container:"填满容器",icon_animation:"激活时使用动态图标?",icon_color:"图标颜色",icon_type:"图标类型",layout:"布局",primary_info:"首要信息",secondary_info:"次要信息",use_entity_picture:"使用实体图片?"},humidifier:{show_target_humidity_control:"湿度控制?"},light:{incompatible_controls:"设备不支持的控制器将不会显示。",show_brightness_control:"亮度控制?",show_color_control:"颜色控制?",show_color_temp_control:"色温控制?",use_light_color:"使用灯光颜色"},lock:{lock:"锁定",open:"打开",unlock:"解锁"},"media-player":{media_controls:"媒体控制",media_controls_list:{next:"下一曲",on_off:"开启/关闭",play_pause_stop:"播放/暂停/停止",previous:"上一曲",repeat:"循环模式",shuffle:"随机"},show_volume_level:"显示音量大小",use_media_artwork:"使用媒体插图",use_media_info:"使用媒体信息",volume_controls:"音量控制",volume_controls_list:{volume_buttons:"音量按钮",volume_mute:"静音",volume_set:"音量等级"}},number:{display_mode:"显示模式",display_mode_list:{buttons:"按钮",default:"默认 (滑块)",slider:"滑块"}},template:{badge_color:"徽标颜色",badge_icon:"徽标图标",content:"内容",entity_extra:"用于模板和动作",label:"标签",multiline_secondary:"多行次要信息?",picture:"图片 (将会替代图标)",primary:"首要信息",secondary:"次要信息"},title:{subtitle:"子标题",subtitle_tap_action:"子标题点击动作",title:"标题",title_tap_action:"标题点击动作"},update:{show_buttons_control:"控制按钮?"},vacuum:{commands:"命令",commands_list:{on_off:"开/关"}},weather:{show_conditions:"条件?",show_temperature:"温度?"}},chip:{"chip-picker":{add:"添加 chip",chips:"小卡片",clear:"清除",edit:"编辑",select:"选择 chip",types:{action:"动作","alarm-control-panel":"警戒控制台",back:"返回",conditional:"条件显示",entity:"实体",light:"灯光",menu:"菜单",quickbar:"快捷栏",spacer:"占位符",template:"模板",weather:"天气"}},conditional:{chip:"小卡片"},sub_element_editor:{title:"Chip 编辑"}},form:{alignment_picker:{values:{center:"居中对齐",default:"默认",end:"右对齐",justify:"两端对齐",start:"左对齐"}},color_picker:{values:{default:"默认颜色"}},icon_type_picker:{values:{default:"默认类型","entity-picture":"实体图片",icon:"图标",none:"无"}},info_picker:{values:{default:"默认信息","last-changed":"变更时间","last-updated":"更新时间",name:"名称",none:"无",state:"状态"}},layout_picker:{values:{default:"默认布局",horizontal:"水平布局",vertical:"垂直布局"}}}},jh={card:Lh,editor:Nh},$h={not_found:"未找到實體"},Bh={card:{chips:{alignment:"對齊"},climate:{hvac_modes:"空調模式",show_temperature_control:"溫度控制?"},cover:{show_buttons_control:"按鈕控制?",show_position_control:"位置控制?",show_tilt_position_control:"角度控制?"},fan:{show_oscillate_control:"擺頭控制?",show_percentage_control:"百分比控制?"},generic:{collapsible_controls:"關閉時隱藏控制項",color:"顏色",content_info:"內容",fill_container:"填滿容器",icon_animation:"啟動時使用動態圖示?",icon_color:"圖示顏色",icon_type:"圖示樣式",layout:"佈局",primary_info:"主要訊息",secondary_info:"次要訊息",use_entity_picture:"使用實體圖片?"},humidifier:{show_target_humidity_control:"溼度控制?"},light:{incompatible_controls:"不會顯示裝置不支援的控制。",show_brightness_control:"亮度控制?",show_color_control:"色彩控制?",show_color_temp_control:"色溫控制?",use_light_color:"使用燈光顏色"},lock:{lock:"上鎖",open:"打開",unlock:"解鎖"},"media-player":{media_controls:"媒體控制",media_controls_list:{next:"下一首",on_off:"開啟、關閉",play_pause_stop:"播放、暫停、停止",previous:"上一首",repeat:"重複播放",shuffle:"隨機播放"},show_volume_level:"顯示音量大小",use_media_artwork:"使用媒體插圖",use_media_info:"使用媒體資訊",volume_controls:"音量控制",volume_controls_list:{volume_buttons:"音量按鈕",volume_mute:"靜音",volume_set:"音量等級"}},number:{display_mode:"顯示模式",display_mode_list:{buttons:"按鈕",default:"預設 (滑桿)",slider:"滑桿"}},template:{badge_color:"角標顏色",badge_icon:"角標圖示",content:"內容",entity_extra:"用於模板與動作",label:"標籤",multiline_secondary:"多行次要訊息?",picture:"圖片 (將會取代圖示)",primary:"主要訊息",secondary:"次要訊息"},title:{subtitle:"副標題",subtitle_tap_action:"副標題點擊動作",title:"標題",title_tap_action:"標題點擊動作"},update:{show_buttons_control:"按鈕控制?"},vacuum:{commands:"指令",commands_list:{on_off:"開啟、關閉"}},weather:{show_conditions:"狀況?",show_temperature:"溫度?"}},chip:{"chip-picker":{add:"新增小卡片",chips:"小卡片",clear:"清除",edit:"編輯",select:"選擇小卡片",types:{action:"動作","alarm-control-panel":"警報器控制",back:"返回",conditional:"條件",entity:"實體",light:"燈光",menu:"選單",spacer:"佔位符",template:"模板",weather:"天氣"}},conditional:{chip:"小卡片"},sub_element_editor:{title:"小卡片編輯器"}},form:{alignment_picker:{values:{center:"居中對齊",default:"預設對齊",end:"居右對齊",justify:"兩端對齊",start:"居左對齊"}},color_picker:{values:{default:"預設顏色"}},icon_type_picker:{values:{default:"預設樣式","entity-picture":"實體圖片",icon:"圖示",none:"無"}},info_picker:{values:{default:"預設訊息","last-changed":"最近變動時間","last-updated":"最近更新時間",name:"名稱",none:"無",state:"狀態"}},layout_picker:{values:{default:"預設佈局",horizontal:"水平佈局",vertical:"垂直佈局"}}}},Rh={card:$h,editor:Bh},Hh={ar:Nu,bg:Bu,ca:Uu,cs:Yu,da:Xu,de:ed,el:id,en:sd,es:hd,fi:vd,fr:kd,he:Cd,hu:Id,id:Pd,it:$d,"ko-KR":Hd,nb:Fd,nl:Wd,pl:Jd,"pt-BR":nh,"pt-PT":ah,ro:ch,ru:ph,sl:Object.freeze({__proto__:null,card:yh,default:kh,editor:bh}),sk:_h,sv:Object.freeze({__proto__:null,card:wh,default:Eh,editor:xh}),tr:Object.freeze({__proto__:null,card:Ch,default:Sh,editor:Ah}),uk:Object.freeze({__proto__:null,card:Th,default:Oh,editor:Ih}),vi:Object.freeze({__proto__:null,card:Mh,default:Ph,editor:zh}),"zh-Hans":Object.freeze({__proto__:null,card:Lh,default:jh,editor:Nh}),"zh-Hant":Object.freeze({__proto__:null,card:$h,default:Rh,editor:Bh})};function Dh(e,t){try{return e.split(".").reduce((function(e,t){return e[t]}),Hh[t])}catch(e){return}}function Uh(e){return function(t){var n,i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},o=null!==(n=null==e?void 0:e.locale.language)&&void 0!==n?n:"en",r=Dh(t,o);if(r||(r=Dh(t,"en")),!r)return t;try{return new Mu(r,o).format(i)}catch(e){return console.error('Error formatting message for key "'.concat(t,'" with lang "').concat(o,'":'),e),r}}} -/** - * @license - * Copyright 2020 Google Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */var Vh="Unknown",Fh="Backspace",Gh="Enter",Yh="Spacebar",Kh="PageUp",Wh="PageDown",qh="End",Xh="Home",Zh="ArrowLeft",Jh="ArrowUp",Qh="ArrowRight",ep="ArrowDown",tp="Delete",np="Escape",ip="Tab",op=new Set;op.add(Fh),op.add(Gh),op.add(Yh),op.add(Kh),op.add(Wh),op.add(qh),op.add(Xh),op.add(Zh),op.add(Jh),op.add(Qh),op.add(ep),op.add(tp),op.add(np),op.add(ip);var rp=8,ap=13,lp=32,sp=33,cp=34,up=35,dp=36,hp=37,pp=38,fp=39,mp=40,vp=46,gp=27,_p=9,yp=new Map;yp.set(rp,Fh),yp.set(ap,Gh),yp.set(lp,Yh),yp.set(sp,Kh),yp.set(cp,Wh),yp.set(up,qh),yp.set(dp,Xh),yp.set(hp,Zh),yp.set(pp,Jh),yp.set(fp,Qh),yp.set(mp,ep),yp.set(vp,tp),yp.set(gp,np),yp.set(_p,ip);var bp=new Set;function kp(e){var t=e.key;if(op.has(t))return t;var n=yp.get(e.keyCode);return n||Vh} -/** - * @license - * Copyright 2020 Google Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */bp.add(Kh),bp.add(Wh),bp.add(qh),bp.add(Xh),bp.add(Zh),bp.add(Jh),bp.add(Qh),bp.add(ep);var wp={UNKNOWN:"Unknown",BACKSPACE:"Backspace",ENTER:"Enter",SPACEBAR:"Spacebar",PAGE_UP:"PageUp",PAGE_DOWN:"PageDown",END:"End",HOME:"Home",ARROW_LEFT:"ArrowLeft",ARROW_UP:"ArrowUp",ARROW_RIGHT:"ArrowRight",ARROW_DOWN:"ArrowDown",DELETE:"Delete",ESCAPE:"Escape",TAB:"Tab"},xp=new Set;xp.add(wp.BACKSPACE),xp.add(wp.ENTER),xp.add(wp.SPACEBAR),xp.add(wp.PAGE_UP),xp.add(wp.PAGE_DOWN),xp.add(wp.END),xp.add(wp.HOME),xp.add(wp.ARROW_LEFT),xp.add(wp.ARROW_UP),xp.add(wp.ARROW_RIGHT),xp.add(wp.ARROW_DOWN),xp.add(wp.DELETE),xp.add(wp.ESCAPE),xp.add(wp.TAB);var Ep=8,Cp=13,Ap=32,Sp=33,Tp=34,Ip=35,Op=36,Mp=37,zp=38,Pp=39,Lp=40,Np=46,jp=27,$p=9,Bp=new Map;Bp.set(Ep,wp.BACKSPACE),Bp.set(Cp,wp.ENTER),Bp.set(Ap,wp.SPACEBAR),Bp.set(Sp,wp.PAGE_UP),Bp.set(Tp,wp.PAGE_DOWN),Bp.set(Ip,wp.END),Bp.set(Op,wp.HOME),Bp.set(Mp,wp.ARROW_LEFT),Bp.set(zp,wp.ARROW_UP),Bp.set(Pp,wp.ARROW_RIGHT),Bp.set(Lp,wp.ARROW_DOWN),Bp.set(Np,wp.DELETE),Bp.set(jp,wp.ESCAPE),Bp.set($p,wp.TAB);var Rp,Hp,Dp=new Set;function Up(e){var t=e.key;if(xp.has(t))return t;var n=Bp.get(e.keyCode);return n||wp.UNKNOWN} -/** - * @license - * Copyright 2018 Google Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */Dp.add(wp.PAGE_UP),Dp.add(wp.PAGE_DOWN),Dp.add(wp.END),Dp.add(wp.HOME),Dp.add(wp.ARROW_LEFT),Dp.add(wp.ARROW_UP),Dp.add(wp.ARROW_RIGHT),Dp.add(wp.ARROW_DOWN);var Vp="mdc-list-item--activated",Fp="mdc-list-item",Gp="mdc-list-item--disabled",Yp="mdc-list-item--selected",Kp="mdc-list-item__text",Wp="mdc-list-item__primary-text",qp="mdc-list";(Rp={})[""+Vp]="mdc-list-item--activated",Rp[""+Fp]="mdc-list-item",Rp[""+Gp]="mdc-list-item--disabled",Rp[""+Yp]="mdc-list-item--selected",Rp[""+Wp]="mdc-list-item__primary-text",Rp[""+qp]="mdc-list";var Xp=((Hp={})[""+Vp]="mdc-deprecated-list-item--activated",Hp[""+Fp]="mdc-deprecated-list-item",Hp[""+Gp]="mdc-deprecated-list-item--disabled",Hp[""+Yp]="mdc-deprecated-list-item--selected",Hp[""+Kp]="mdc-deprecated-list-item__text",Hp[""+Wp]="mdc-deprecated-list-item__primary-text",Hp[""+qp]="mdc-deprecated-list",Hp);Xp[Fp],Xp[Fp],Xp[Fp],Xp[Fp],Xp[Fp],Xp[Fp];var Zp={TYPEAHEAD_BUFFER_CLEAR_TIMEOUT_MS:300},Jp=["input","button","textarea","select"],Qp=function(e){var t=e.target;if(t){var n=(""+t.tagName).toLowerCase();-1===Jp.indexOf(n)&&e.preventDefault()}}; -/** - * @license - * Copyright 2020 Google Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */function ef(e,t){for(var n=new Map,i=0;it&&!n(r[l].index)){s=l;break}if(-1!==s)return i.sortedIndexCursor=s,r[i.sortedIndexCursor].index;return-1}(r,a,s,t):function(e,t,n){var i=n.typeaheadBuffer[0],o=e.get(i);if(!o)return-1;var r=o[n.sortedIndexCursor];if(0===r.text.lastIndexOf(n.typeaheadBuffer,0)&&!t(r.index))return r.index;var a=(n.sortedIndexCursor+1)%o.length,l=-1;for(;a!==n.sortedIndexCursor;){var s=o[a],c=0===s.text.lastIndexOf(n.typeaheadBuffer,0),u=!t(s.index);if(c&&u){l=a;break}a=(a+1)%o.length}if(-1!==l)return n.sortedIndexCursor=l,o[n.sortedIndexCursor].index;return-1}(r,s,t),-1===n||l||o(n),n}function nf(e){return e.typeaheadBuffer.length>0} +function La(t,e){return function(e,n,o){return function(t,e,n){return n.configurable=!0,n.enumerable=!0,Reflect.decorate&&"object"!=mr(e)&&Object.defineProperty(t,e,n),n}(e,n,{get:function(){return function(e){var n,o;return null!==(n=null===(o=e.renderRoot)||void 0===o?void 0:o.querySelector(t))&&void 0!==n?n:null}(this)}})}} /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause - */ -var of;null===(of=window.HTMLSlotElement)||void 0===of||of.prototype.assignedElements; -/** - * @license - * Copyright 2019 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - */ -var rf,af,lf=window,sf=lf.ShadowRoot&&(void 0===lf.ShadyCSS||lf.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,cf=Symbol(),uf=new WeakMap,df=function(){return ua((function e(t,n,i){if(sa(this,e),this._$cssResult$=!0,i!==cf)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=n}),[{key:"styleSheet",get:function(){var e=this.o,t=this.t;if(sf&&void 0===e){var n=void 0!==t&&1===t.length;n&&(e=uf.get(t)),void 0===e&&((this.o=e=new CSSStyleSheet).replaceSync(this.cssText),n&&uf.set(t,e))}return e}},{key:"toString",value:function(){return this.cssText}}])}(),hf=sf?function(e){return e}:function(e){return e instanceof CSSStyleSheet?function(e){var t,n="",i=ra(e.cssRules);try{for(i.s();!(t=i.n()).done;){n+=t.value.cssText}}catch(e){i.e(e)}finally{i.f()}return function(e){return new df("string"==typeof e?e:e+"",void 0,cf)}(n)}(e):e},pf=window,ff=pf.trustedTypes,mf=ff?ff.emptyScript:"",vf=pf.reactiveElementPolyfillSupport,gf={toAttribute:function(e,t){switch(t){case Boolean:e=e?mf:null;break;case Object:case Array:e=null==e?e:JSON.stringify(e)}return e},fromAttribute:function(e,t){var n=e;switch(t){case Boolean:n=null!==e;break;case Number:n=null===e?null:Number(e);break;case Object:case Array:try{n=JSON.parse(e)}catch(e){n=null}}return n}},_f=function(e,t){return t!==e&&(t==t||e==e)},yf={attribute:!0,type:String,converter:gf,reflect:!1,hasChanged:_f},bf="finalized",kf=function(e){function t(){var e;return sa(this,t),(e=Zr(this,t))._$Ei=new Map,e.isUpdatePending=!1,e.hasUpdated=!1,e._$El=null,e._$Eu(),e}return ea(t,ta(HTMLElement)),ua(t,[{key:"_$Eu",value:function(){var e,t=this;this._$E_=new Promise((function(e){return t.enableUpdating=e})),this._$AL=new Map,this._$Eg(),this.requestUpdate(),null===(e=this.constructor.h)||void 0===e||e.forEach((function(e){return e(t)}))}},{key:"addController",value:function(e){var t,n;(null!==(t=this._$ES)&&void 0!==t?t:this._$ES=[]).push(e),void 0!==this.renderRoot&&this.isConnected&&(null===(n=e.hostConnected)||void 0===n||n.call(e))}},{key:"removeController",value:function(e){var t;null===(t=this._$ES)||void 0===t||t.splice(this._$ES.indexOf(e)>>>0,1)}},{key:"_$Eg",value:function(){var e=this;this.constructor.elementProperties.forEach((function(t,n){e.hasOwnProperty(n)&&(e._$Ei.set(n,e[n]),delete e[n])}))}},{key:"createRenderRoot",value:function(){var e,t=null!==(e=this.shadowRoot)&&void 0!==e?e:this.attachShadow(this.constructor.shadowRootOptions);return function(e,t){sf?e.adoptedStyleSheets=t.map((function(e){return e instanceof CSSStyleSheet?e:e.styleSheet})):t.forEach((function(t){var n=document.createElement("style"),i=lf.litNonce;void 0!==i&&n.setAttribute("nonce",i),n.textContent=t.cssText,e.appendChild(n)}))}(t,this.constructor.elementStyles),t}},{key:"connectedCallback",value:function(){var e;void 0===this.renderRoot&&(this.renderRoot=this.createRenderRoot()),this.enableUpdating(!0),null===(e=this._$ES)||void 0===e||e.forEach((function(e){var t;return null===(t=e.hostConnected)||void 0===t?void 0:t.call(e)}))}},{key:"enableUpdating",value:function(e){}},{key:"disconnectedCallback",value:function(){var e;null===(e=this._$ES)||void 0===e||e.forEach((function(e){var t;return null===(t=e.hostDisconnected)||void 0===t?void 0:t.call(e)}))}},{key:"attributeChangedCallback",value:function(e,t,n){this._$AK(e,n)}},{key:"_$EO",value:function(e,t){var n,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:yf,o=this.constructor._$Ep(e,i);if(void 0!==o&&!0===i.reflect){var r=(void 0!==(null===(n=i.converter)||void 0===n?void 0:n.toAttribute)?i.converter:gf).toAttribute(t,i.type);this._$El=e,null==r?this.removeAttribute(o):this.setAttribute(o,r),this._$El=null}}},{key:"_$AK",value:function(e,t){var n,i=this.constructor,o=i._$Ev.get(e);if(void 0!==o&&this._$El!==o){var r=i.getPropertyOptions(o),a="function"==typeof r.converter?{fromAttribute:r.converter}:void 0!==(null===(n=r.converter)||void 0===n?void 0:n.fromAttribute)?r.converter:gf;this._$El=o,this[o]=a.fromAttribute(t,r.type),this._$El=null}}},{key:"requestUpdate",value:function(e,t,n){var i=!0;void 0!==e&&(((n=n||this.constructor.getPropertyOptions(e)).hasChanged||_f)(this[e],t)?(this._$AL.has(e)||this._$AL.set(e,t),!0===n.reflect&&this._$El!==e&&(void 0===this._$EC&&(this._$EC=new Map),this._$EC.set(e,n))):i=!1),!this.isUpdatePending&&i&&(this._$E_=this._$Ej())}},{key:"_$Ej",value:(n=Xr(Kr().m((function e(){var t,n;return Kr().w((function(e){for(;;)switch(e.p=e.n){case 0:return this.isUpdatePending=!0,e.p=1,e.n=2,this._$E_;case 2:e.n=4;break;case 3:e.p=3,n=e.v,Promise.reject(n);case 4:if(null==(t=this.scheduleUpdate())){e.n=5;break}return e.n=5,t;case 5:return e.a(2,!this.isUpdatePending)}}),e,this,[[1,3]])}))),function(){return n.apply(this,arguments)})},{key:"scheduleUpdate",value:function(){return this.performUpdate()}},{key:"performUpdate",value:function(){var e,t=this;if(this.isUpdatePending){this.hasUpdated,this._$Ei&&(this._$Ei.forEach((function(e,n){return t[n]=e})),this._$Ei=void 0);var n=!1,i=this._$AL;try{(n=this.shouldUpdate(i))?(this.willUpdate(i),null===(e=this._$ES)||void 0===e||e.forEach((function(e){var t;return null===(t=e.hostUpdate)||void 0===t?void 0:t.call(e)})),this.update(i)):this._$Ek()}catch(e){throw n=!1,this._$Ek(),e}n&&this._$AE(i)}}},{key:"willUpdate",value:function(e){}},{key:"_$AE",value:function(e){var t;null===(t=this._$ES)||void 0===t||t.forEach((function(e){var t;return null===(t=e.hostUpdated)||void 0===t?void 0:t.call(e)})),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(e)),this.updated(e)}},{key:"_$Ek",value:function(){this._$AL=new Map,this.isUpdatePending=!1}},{key:"updateComplete",get:function(){return this.getUpdateComplete()}},{key:"getUpdateComplete",value:function(){return this._$E_}},{key:"shouldUpdate",value:function(e){return!0}},{key:"update",value:function(e){var t=this;void 0!==this._$EC&&(this._$EC.forEach((function(e,n){return t._$EO(n,t[n],e)})),this._$EC=void 0),this._$Ek()}},{key:"updated",value:function(e){}},{key:"firstUpdated",value:function(e){}}],[{key:"addInitializer",value:function(e){var t;this.finalize(),(null!==(t=this.h)&&void 0!==t?t:this.h=[]).push(e)}},{key:"observedAttributes",get:function(){var e=this;this.finalize();var t=[];return this.elementProperties.forEach((function(n,i){var o=e._$Ep(i,n);void 0!==o&&(e._$Ev.set(o,i),t.push(o))})),t}},{key:"createProperty",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:yf;if(t.state&&(t.attribute=!1),this.finalize(),this.elementProperties.set(e,t),!t.noAccessor&&!this.prototype.hasOwnProperty(e)){var n="symbol"==ha(e)?Symbol():"__"+e,i=this.getPropertyDescriptor(e,n,t);void 0!==i&&Object.defineProperty(this.prototype,e,i)}}},{key:"getPropertyDescriptor",value:function(e,t,n){return{get:function(){return this[t]},set:function(i){var o=this[e];this[t]=i,this.requestUpdate(e,o,n)},configurable:!0,enumerable:!0}}},{key:"getPropertyOptions",value:function(e){return this.elementProperties.get(e)||yf}},{key:"finalize",value:function(){if(this.hasOwnProperty(bf))return!1;this[bf]=!0;var e=Object.getPrototypeOf(this);if(e.finalize(),void 0!==e.h&&(this.h=Ur(e.h)),this.elementProperties=new Map(e.elementProperties),this._$Ev=new Map,this.hasOwnProperty("properties")){var t,n=this.properties,i=ra([].concat(Ur(Object.getOwnPropertyNames(n)),Ur(Object.getOwnPropertySymbols(n))));try{for(i.s();!(t=i.n()).done;){var o=t.value;this.createProperty(o,n[o])}}catch(e){i.e(e)}finally{i.f()}}return this.elementStyles=this.finalizeStyles(this.styles),!0}},{key:"finalizeStyles",value:function(e){var t=[];if(Array.isArray(e)){var n,i=ra(new Set(e.flat(1/0).reverse()));try{for(i.s();!(n=i.n()).done;){var o=n.value;t.unshift(hf(o))}}catch(e){i.e(e)}finally{i.f()}}else void 0!==e&&t.push(hf(e));return t}},{key:"_$Ep",value:function(e,t){var n=t.attribute;return!1===n?void 0:"string"==typeof n?n:"string"==typeof e?e.toLowerCase():void 0}}]);var n}();kf[bf]=!0,kf.elementProperties=new Map,kf.elementStyles=[],kf.shadowRootOptions={mode:"open"},null==vf||vf({ReactiveElement:kf}),(null!==(rf=pf.reactiveElementVersions)&&void 0!==rf?rf:pf.reactiveElementVersions=[]).push("1.6.3");var wf=window,xf=wf.trustedTypes,Ef=xf?xf.createPolicy("lit-html",{createHTML:function(e){return e}}):void 0,Cf="$lit$",Af="lit$".concat((Math.random()+"").slice(9),"$"),Sf="?"+Af,Tf="<".concat(Sf,">"),If=document,Of=function(){return If.createComment("")},Mf=function(e){return null===e||"object"!=ha(e)&&"function"!=typeof e},zf=Array.isArray,Pf="[ \t\n\f\r]",Lf=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,Nf=/-->/g,jf=/>/g,$f=RegExp(">|".concat(Pf,"(?:([^\\s\"'>=/]+)(").concat(Pf,"*=").concat(Pf,"*(?:[^ \t\n\f\r\"'`<>=]|(\"|')|))|$)"),"g"),Bf=/'/g,Rf=/"/g,Hf=/^(?:script|style|textarea|title)$/i,Df=Symbol.for("lit-noChange"),Uf=Symbol.for("lit-nothing"),Vf=new WeakMap,Ff=If.createTreeWalker(If,129,null,!1);function Gf(e,t){if(!Array.isArray(e)||!e.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==Ef?Ef.createHTML(t):t}var Yf=function(){return ua((function e(t,n){var i,o=t.strings,r=t._$litType$;sa(this,e),this.parts=[];var a=0,l=0,s=o.length-1,c=this.parts,u=function(e,t){for(var n,i=e.length-1,o=[],r=2===t?"":"",a=Lf,l=0;l"===u[0]?(a=null!=n?n:Lf,d=-1):void 0===u[1]?d=-2:(d=a.lastIndex-u[2].length,c=u[1],a=void 0===u[3]?$f:'"'===u[3]?Rf:Bf):a===Rf||a===Bf?a=$f:a===Nf||a===jf?a=Lf:(a=$f,n=void 0);var p=a===$f&&e[l+1].startsWith("/>")?" ":"";r+=a===Lf?s+Tf:d>=0?(o.push(c),s.slice(0,d)+Cf+s.slice(d)+Af+p):s+Af+(-2===d?(o.push(void 0),l):p)}return[Gf(e,r+(e[i]||"")+(2===t?"":"")),o]}(o,r),d=Fr(u,2),h=d[0],p=d[1];if(this.el=e.createElement(h,n),Ff.currentNode=this.el.content,2===r){var f=this.el.content,m=f.firstChild;m.remove(),f.append.apply(f,Ur(m.childNodes))}for(;null!==(i=Ff.nextNode())&&c.length0){i.textContent=xf?xf.emptyScript:"";for(var T=0;T2&&void 0!==arguments[2]?arguments[2]:e,l=arguments.length>3?arguments[3]:void 0;if(t===Df)return t;var s=void 0!==l?null===(n=a._$Co)||void 0===n?void 0:n[l]:a._$Cl,c=Mf(t)?void 0:t._$litDirective$;return(null==s?void 0:s.constructor)!==c&&(null===(i=null==s?void 0:s._$AO)||void 0===i||i.call(s,!1),void 0===c?s=void 0:(s=new c(e))._$AT(e,a,l),void 0!==l?(null!==(o=(r=a)._$Co)&&void 0!==o?o:r._$Co=[])[l]=s:a._$Cl=s),void 0!==s&&(t=Kf(e,s._$AS(e,t.values),s,l)),t}var Wf=function(){return ua((function e(t,n){sa(this,e),this._$AV=[],this._$AN=void 0,this._$AD=t,this._$AM=n}),[{key:"parentNode",get:function(){return this._$AM.parentNode}},{key:"_$AU",get:function(){return this._$AM._$AU}},{key:"u",value:function(e){var t,n=this._$AD,i=n.el.content,o=n.parts,r=(null!==(t=null==e?void 0:e.creationScope)&&void 0!==t?t:If).importNode(i,!0);Ff.currentNode=r;for(var a=Ff.nextNode(),l=0,s=0,c=o[0];void 0!==c;){if(l===c.index){var u=void 0;2===c.type?u=new qf(a,a.nextSibling,this,e):1===c.type?u=new c.ctor(a,c.name,c.strings,this,e):6===c.type&&(u=new tm(a,this,e)),this._$AV.push(u),c=o[++s]}l!==(null==c?void 0:c.index)&&(a=Ff.nextNode(),l++)}return Ff.currentNode=If,r}},{key:"v",value:function(e){var t,n=0,i=ra(this._$AV);try{for(i.s();!(t=i.n()).done;){var o=t.value;void 0!==o&&(void 0!==o.strings?(o._$AI(e,o,n),n+=o.strings.length-2):o._$AI(e[n])),n++}}catch(e){i.e(e)}finally{i.f()}}}])}(),qf=function(){function e(t,n,i,o){var r;sa(this,e),this.type=2,this._$AH=Uf,this._$AN=void 0,this._$AA=t,this._$AB=n,this._$AM=i,this.options=o,this._$Cp=null===(r=null==o?void 0:o.isConnected)||void 0===r||r}return ua(e,[{key:"_$AU",get:function(){var e,t;return null!==(t=null===(e=this._$AM)||void 0===e?void 0:e._$AU)&&void 0!==t?t:this._$Cp}},{key:"parentNode",get:function(){var e=this._$AA.parentNode,t=this._$AM;return void 0!==t&&11===(null==e?void 0:e.nodeType)&&(e=t.parentNode),e}},{key:"startNode",get:function(){return this._$AA}},{key:"endNode",get:function(){return this._$AB}},{key:"_$AI",value:function(e){e=Kf(this,e,arguments.length>1&&void 0!==arguments[1]?arguments[1]:this),Mf(e)?e===Uf||null==e||""===e?(this._$AH!==Uf&&this._$AR(),this._$AH=Uf):e!==this._$AH&&e!==Df&&this._(e):void 0!==e._$litType$?this.g(e):void 0!==e.nodeType?this.$(e):function(e){return zf(e)||"function"==typeof(null==e?void 0:e[Symbol.iterator])}(e)?this.T(e):this._(e)}},{key:"k",value:function(e){return this._$AA.parentNode.insertBefore(e,this._$AB)}},{key:"$",value:function(e){this._$AH!==e&&(this._$AR(),this._$AH=this.k(e))}},{key:"_",value:function(e){this._$AH!==Uf&&Mf(this._$AH)?this._$AA.nextSibling.data=e:this.$(If.createTextNode(e)),this._$AH=e}},{key:"g",value:function(e){var t,n=e.values,i=e._$litType$,o="number"==typeof i?this._$AC(e):(void 0===i.el&&(i.el=Yf.createElement(Gf(i.h,i.h[0]),this.options)),i);if((null===(t=this._$AH)||void 0===t?void 0:t._$AD)===o)this._$AH.v(n);else{var r=new Wf(o,this),a=r.u(this.options);r.v(n),this.$(a),this._$AH=r}}},{key:"_$AC",value:function(e){var t=Vf.get(e.strings);return void 0===t&&Vf.set(e.strings,t=new Yf(e)),t}},{key:"T",value:function(t){zf(this._$AH)||(this._$AH=[],this._$AR());var n,i,o=this._$AH,r=0,a=ra(t);try{for(a.s();!(i=a.n()).done;){var l=i.value;r===o.length?o.push(n=new e(this.k(Of()),this.k(Of()),this,this.options)):n=o[r],n._$AI(l),r++}}catch(e){a.e(e)}finally{a.f()}r0&&void 0!==arguments[0]?arguments[0]:this._$AA.nextSibling,n=arguments.length>1?arguments[1]:void 0;for(null===(e=this._$AP)||void 0===e||e.call(this,!1,!0,n);t&&t!==this._$AB;){var i=t.nextSibling;t.remove(),t=i}}},{key:"setConnected",value:function(e){var t;void 0===this._$AM&&(this._$Cp=e,null===(t=this._$AP)||void 0===t||t.call(this,e))}}])}(),Xf=function(){return ua((function e(t,n,i,o,r){sa(this,e),this.type=1,this._$AH=Uf,this._$AN=void 0,this.element=t,this.name=n,this._$AM=o,this.options=r,i.length>2||""!==i[0]||""!==i[1]?(this._$AH=Array(i.length-1).fill(new String),this.strings=i):this._$AH=Uf}),[{key:"tagName",get:function(){return this.element.tagName}},{key:"_$AU",get:function(){return this._$AM._$AU}},{key:"_$AI",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this,n=arguments.length>2?arguments[2]:void 0,i=arguments.length>3?arguments[3]:void 0,o=this.strings,r=!1;if(void 0===o)e=Kf(this,e,t,0),(r=!Mf(e)||e!==this._$AH&&e!==Df)&&(this._$AH=e);else{var a,l,s=e;for(e=o[0],a=0;a1&&void 0!==arguments[1]?arguments[1]:this,0))&&void 0!==t?t:Uf)!==Df){var n=this._$AH,i=e===Uf&&n!==Uf||e.capture!==n.capture||e.once!==n.once||e.passive!==n.passive,o=e!==Uf&&(n===Uf||i);i&&this.element.removeEventListener(this.name,this,n),o&&this.element.addEventListener(this.name,this,e),this._$AH=e}}},{key:"handleEvent",value:function(e){var t,n;"function"==typeof this._$AH?this._$AH.call(null!==(n=null===(t=this.options)||void 0===t?void 0:t.host)&&void 0!==n?n:this.element,e):this._$AH.handleEvent(e)}}])}(),tm=function(){return ua((function e(t,n,i){sa(this,e),this.element=t,this.type=6,this._$AN=void 0,this._$AM=n,this.options=i}),[{key:"_$AU",get:function(){return this._$AM._$AU}},{key:"_$AI",value:function(e){Kf(this,e)}}])}(),nm=wf.litHtmlPolyfillSupport;null==nm||nm(Yf,qf),(null!==(af=wf.litHtmlVersions)&&void 0!==af?af:wf.litHtmlVersions=[]).push("2.8.0");var im,om,rm=function(e){function t(){var e;return sa(this,t),(e=Zr(this,t,arguments)).renderOptions={host:Qr(e)},e._$Do=void 0,e}return ea(t,kf),ua(t,[{key:"createRenderRoot",value:function(){var e,n,i=Hr(t,"createRenderRoot",this,3)([]);return null!==(e=(n=this.renderOptions).renderBefore)&&void 0!==e||(n.renderBefore=i.firstChild),i}},{key:"update",value:function(e){var n=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),Hr(t,"update",this,3)([e]),this._$Do=function(e,t,n){var i,o,r=null!==(i=null==n?void 0:n.renderBefore)&&void 0!==i?i:t,a=r._$litPart$;if(void 0===a){var l=null!==(o=null==n?void 0:n.renderBefore)&&void 0!==o?o:null;r._$litPart$=a=new qf(t.insertBefore(Of(),l),l,void 0,null!=n?n:{})}return a._$AI(e),a}(n,this.renderRoot,this.renderOptions)}},{key:"connectedCallback",value:function(){var e;Hr(t,"connectedCallback",this,3)([]),null===(e=this._$Do)||void 0===e||e.setConnected(!0)}},{key:"disconnectedCallback",value:function(){var e;Hr(t,"disconnectedCallback",this,3)([]),null===(e=this._$Do)||void 0===e||e.setConnected(!1)}},{key:"render",value:function(){return Df}}])}(); -/** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - */rm.finalized=!0,rm._$litElement$=!0,null===(im=globalThis.litElementHydrateSupport)||void 0===im||im.call(globalThis,{LitElement:rm});var am=globalThis.litElementPolyfillSupport;null==am||am({LitElement:rm}),(null!==(om=globalThis.litElementVersions)&&void 0!==om?om:globalThis.litElementVersions=[]).push("3.3.3");function lm(e){return{addClass:function(t){e.classList.add(t)},removeClass:function(t){e.classList.remove(t)},hasClass:function(t){return e.classList.contains(t)}}}var sm=function(){},cm={get passive(){return!1}};document.addEventListener("x",sm,cm),document.removeEventListener("x",sm); -/** - * @license - * Copyright 2018 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */var um,dm,hm=function(e){function t(){return sa(this,t),Zr(this,t,arguments)}return ea(t,rm),ua(t,[{key:"click",value:function(){if(this.mdcRoot)return this.mdcRoot.focus(),void this.mdcRoot.click();Hr(t,"click",this,3)([])}},{key:"createFoundation",value:function(){void 0!==this.mdcFoundation&&this.mdcFoundation.destroy(),this.mdcFoundationClass&&(this.mdcFoundation=new this.mdcFoundationClass(this.createAdapter()),this.mdcFoundation.init())}},{key:"firstUpdated",value:function(){this.createFoundation()}}])}(),pm=null!==(dm=null===(um=window.ShadyDOM)||void 0===um?void 0:um.inUse)&&void 0!==dm&&dm,fm=function(e){function t(){var e;return sa(this,t),(e=Zr(this,t,arguments)).disabled=!1,e.containingForm=null,e.formDataListener=function(t){e.disabled||e.setFormData(t.formData)},e}return ea(t,hm),ua(t,[{key:"findFormElement",value:function(){if(!this.shadowRoot||pm)return null;for(var e=this.getRootNode().querySelectorAll("form"),t=0,n=Array.from(e);t=this.adapter.getMenuItemCount()||(e===vv.UNSET_INDEX?this.adapter.setSelectedText(""):this.adapter.setSelectedText(this.adapter.getMenuItemTextAtIndex(e).trim()),this.adapter.setSelectedIndex(e),t&&this.adapter.closeMenu(),n||this.lastSelectedIndex===e||this.handleChange(),this.lastSelectedIndex=e)},t.prototype.setValue=function(e,t){void 0===t&&(t=!1);var n=this.adapter.getMenuItemValues().indexOf(e);this.setSelectedIndex(n,!1,t)},t.prototype.getValue=function(){var e=this.adapter.getSelectedIndex(),t=this.adapter.getMenuItemValues();return e!==vv.UNSET_INDEX?t[e]:""},t.prototype.getDisabled=function(){return this.disabled},t.prototype.setDisabled=function(e){this.disabled=e,this.disabled?(this.adapter.addClass(fv.DISABLED),this.adapter.closeMenu()):this.adapter.removeClass(fv.DISABLED),this.leadingIcon&&this.leadingIcon.setDisabled(this.disabled),this.disabled?this.adapter.removeSelectAnchorAttr("tabindex"):this.adapter.setSelectAnchorAttr("tabindex","0"),this.adapter.setSelectAnchorAttr("aria-disabled",this.disabled.toString())},t.prototype.openMenu=function(){this.adapter.addClass(fv.ACTIVATED),this.adapter.openMenu(),this.isMenuOpen=!0,this.adapter.setSelectAnchorAttr("aria-expanded","true")},t.prototype.setHelperTextContent=function(e){this.helperText&&this.helperText.setContent(e)},t.prototype.layout=function(){if(this.adapter.hasLabel()){var e=this.getValue().length>0,t=this.adapter.hasClass(fv.FOCUSED),n=e||t,i=this.adapter.hasClass(fv.REQUIRED);this.notchOutline(n),this.adapter.floatLabel(n),this.adapter.setLabelRequired(i)}},t.prototype.layoutOptions=function(){var e=this.adapter.getMenuItemValues().indexOf(this.getValue());this.setSelectedIndex(e,!1,!0)},t.prototype.handleMenuOpened=function(){if(0!==this.adapter.getMenuItemValues().length){var e=this.getSelectedIndex(),t=e>=0?e:0;this.adapter.focusMenuItemAtIndex(t)}},t.prototype.handleMenuClosing=function(){this.adapter.setSelectAnchorAttr("aria-expanded","false")},t.prototype.handleMenuClosed=function(){this.adapter.removeClass(fv.ACTIVATED),this.isMenuOpen=!1,this.adapter.isSelectAnchorFocused()||this.blur()},t.prototype.handleChange=function(){this.layout(),this.adapter.notifyChange(this.getValue()),this.adapter.hasClass(fv.REQUIRED)&&this.useDefaultValidation&&this.setValid(this.isValid())},t.prototype.handleMenuItemAction=function(e){this.setSelectedIndex(e,!0)},t.prototype.handleFocus=function(){this.adapter.addClass(fv.FOCUSED),this.layout(),this.adapter.activateBottomLine()},t.prototype.handleBlur=function(){this.isMenuOpen||this.blur()},t.prototype.handleClick=function(e){this.disabled||this.recentlyClicked||(this.setClickDebounceTimeout(),this.isMenuOpen?this.adapter.closeMenu():(this.adapter.setRippleCenter(e),this.openMenu()))},t.prototype.handleKeydown=function(e){if(!this.isMenuOpen&&this.adapter.hasClass(fv.FOCUSED)){var t=dv(e)===Pm,n=dv(e)===Lm,i=dv(e)===Hm,o=dv(e)===Um;if(!(e.ctrlKey||e.metaKey)&&(!n&&e.key&&1===e.key.length||n&&this.adapter.isTypeaheadInProgress())){var r=n?" ":e.key,a=this.adapter.typeaheadMatchItem(r,this.getSelectedIndex());return a>=0&&this.setSelectedIndex(a),void e.preventDefault()}(t||n||i||o)&&(this.openMenu(),e.preventDefault())}},t.prototype.notchOutline=function(e){if(this.adapter.hasOutline()){var t=this.adapter.hasClass(fv.FOCUSED);if(e){var n=vv.LABEL_SCALE,i=this.adapter.getLabelWidth()*n;this.adapter.notchOutline(i)}else t||this.adapter.closeOutline()}},t.prototype.setLeadingIconAriaLabel=function(e){this.leadingIcon&&this.leadingIcon.setAriaLabel(e)},t.prototype.setLeadingIconContent=function(e){this.leadingIcon&&this.leadingIcon.setContent(e)},t.prototype.getUseDefaultValidation=function(){return this.useDefaultValidation},t.prototype.setUseDefaultValidation=function(e){this.useDefaultValidation=e},t.prototype.setValid=function(e){this.useDefaultValidation||(this.customValidity=e),this.adapter.setSelectAnchorAttr("aria-invalid",(!e).toString()),e?(this.adapter.removeClass(fv.INVALID),this.adapter.removeMenuClass(fv.MENU_INVALID)):(this.adapter.addClass(fv.INVALID),this.adapter.addMenuClass(fv.MENU_INVALID)),this.syncHelperTextValidity(e)},t.prototype.isValid=function(){return this.useDefaultValidation&&this.adapter.hasClass(fv.REQUIRED)&&!this.adapter.hasClass(fv.DISABLED)?this.getSelectedIndex()!==vv.UNSET_INDEX&&(0!==this.getSelectedIndex()||Boolean(this.getValue())):this.customValidity},t.prototype.setRequired=function(e){e?this.adapter.addClass(fv.REQUIRED):this.adapter.removeClass(fv.REQUIRED),this.adapter.setSelectAnchorAttr("aria-required",e.toString()),this.adapter.setLabelRequired(e)},t.prototype.getRequired=function(){return"true"===this.adapter.getSelectAnchorAttr("aria-required")},t.prototype.init=function(){var e=this.adapter.getAnchorElement();e&&(this.adapter.setMenuAnchorElement(e),this.adapter.setMenuAnchorCorner(cv.BOTTOM_START)),this.adapter.setMenuWrapFocus(!1),this.setDisabled(this.adapter.hasClass(fv.DISABLED)),this.syncHelperTextValidity(!this.adapter.hasClass(fv.INVALID)),this.layout(),this.layoutOptions()},t.prototype.blur=function(){this.adapter.removeClass(fv.FOCUSED),this.layout(),this.adapter.deactivateBottomLine(),this.adapter.hasClass(fv.REQUIRED)&&this.useDefaultValidation&&this.setValid(this.isValid())},t.prototype.syncHelperTextValidity=function(e){if(this.helperText){this.helperText.setValidity(e);var t=this.helperText.isVisible(),n=this.helperText.getId();t&&n?this.adapter.setSelectAnchorAttr(mv.ARIA_DESCRIBEDBY,n):this.adapter.removeSelectAnchorAttr(mv.ARIA_DESCRIBEDBY)}},t.prototype.setClickDebounceTimeout=function(){var e=this;clearTimeout(this.clickDebounceTimeout),this.clickDebounceTimeout=setTimeout((function(){e.recentlyClicked=!1}),vv.CLICK_DEBOUNCE_TIMEOUT_MS),this.recentlyClicked=!0},t}(Om),_v=window,yv=_v.ShadowRoot&&(void 0===_v.ShadyCSS||_v.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,bv=Symbol(),kv=new WeakMap,wv=function(){return ua((function e(t,n,i){if(sa(this,e),this._$cssResult$=!0,i!==bv)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=n}),[{key:"styleSheet",get:function(){var e=this.o,t=this.t;if(yv&&void 0===e){var n=void 0!==t&&1===t.length;n&&(e=kv.get(t)),void 0===e&&((this.o=e=new CSSStyleSheet).replaceSync(this.cssText),n&&kv.set(t,e))}return e}},{key:"toString",value:function(){return this.cssText}}])}(),xv=yv?function(e){return e}:function(e){return e instanceof CSSStyleSheet?function(e){var t,n="",i=ra(e.cssRules);try{for(i.s();!(t=i.n()).done;){n+=t.value.cssText}}catch(e){i.e(e)}finally{i.f()}return function(e){return new wv("string"==typeof e?e:e+"",void 0,bv)}(n)}(e):e},Ev=window,Cv=Ev.trustedTypes,Av=Cv?Cv.emptyScript:"",Sv=Ev.reactiveElementPolyfillSupport,Tv={toAttribute:function(e,t){switch(t){case Boolean:e=e?Av:null;break;case Object:case Array:e=null==e?e:JSON.stringify(e)}return e},fromAttribute:function(e,t){var n=e;switch(t){case Boolean:n=null!==e;break;case Number:n=null===e?null:Number(e);break;case Object:case Array:try{n=JSON.parse(e)}catch(e){n=null}}return n}},Iv=function(e,t){return t!==e&&(t==t||e==e)},Ov={attribute:!0,type:String,converter:Tv,reflect:!1,hasChanged:Iv},Mv="finalized",zv=function(e){function t(){var e;return sa(this,t),(e=Zr(this,t))._$Ei=new Map,e.isUpdatePending=!1,e.hasUpdated=!1,e._$El=null,e._$Eu(),e}return ea(t,ta(HTMLElement)),ua(t,[{key:"_$Eu",value:function(){var e,t=this;this._$E_=new Promise((function(e){return t.enableUpdating=e})),this._$AL=new Map,this._$Eg(),this.requestUpdate(),null===(e=this.constructor.h)||void 0===e||e.forEach((function(e){return e(t)}))}},{key:"addController",value:function(e){var t,n;(null!==(t=this._$ES)&&void 0!==t?t:this._$ES=[]).push(e),void 0!==this.renderRoot&&this.isConnected&&(null===(n=e.hostConnected)||void 0===n||n.call(e))}},{key:"removeController",value:function(e){var t;null===(t=this._$ES)||void 0===t||t.splice(this._$ES.indexOf(e)>>>0,1)}},{key:"_$Eg",value:function(){var e=this;this.constructor.elementProperties.forEach((function(t,n){e.hasOwnProperty(n)&&(e._$Ei.set(n,e[n]),delete e[n])}))}},{key:"createRenderRoot",value:function(){var e,t=null!==(e=this.shadowRoot)&&void 0!==e?e:this.attachShadow(this.constructor.shadowRootOptions);return function(e,t){yv?e.adoptedStyleSheets=t.map((function(e){return e instanceof CSSStyleSheet?e:e.styleSheet})):t.forEach((function(t){var n=document.createElement("style"),i=_v.litNonce;void 0!==i&&n.setAttribute("nonce",i),n.textContent=t.cssText,e.appendChild(n)}))}(t,this.constructor.elementStyles),t}},{key:"connectedCallback",value:function(){var e;void 0===this.renderRoot&&(this.renderRoot=this.createRenderRoot()),this.enableUpdating(!0),null===(e=this._$ES)||void 0===e||e.forEach((function(e){var t;return null===(t=e.hostConnected)||void 0===t?void 0:t.call(e)}))}},{key:"enableUpdating",value:function(e){}},{key:"disconnectedCallback",value:function(){var e;null===(e=this._$ES)||void 0===e||e.forEach((function(e){var t;return null===(t=e.hostDisconnected)||void 0===t?void 0:t.call(e)}))}},{key:"attributeChangedCallback",value:function(e,t,n){this._$AK(e,n)}},{key:"_$EO",value:function(e,t){var n,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:Ov,o=this.constructor._$Ep(e,i);if(void 0!==o&&!0===i.reflect){var r=(void 0!==(null===(n=i.converter)||void 0===n?void 0:n.toAttribute)?i.converter:Tv).toAttribute(t,i.type);this._$El=e,null==r?this.removeAttribute(o):this.setAttribute(o,r),this._$El=null}}},{key:"_$AK",value:function(e,t){var n,i=this.constructor,o=i._$Ev.get(e);if(void 0!==o&&this._$El!==o){var r=i.getPropertyOptions(o),a="function"==typeof r.converter?{fromAttribute:r.converter}:void 0!==(null===(n=r.converter)||void 0===n?void 0:n.fromAttribute)?r.converter:Tv;this._$El=o,this[o]=a.fromAttribute(t,r.type),this._$El=null}}},{key:"requestUpdate",value:function(e,t,n){var i=!0;void 0!==e&&(((n=n||this.constructor.getPropertyOptions(e)).hasChanged||Iv)(this[e],t)?(this._$AL.has(e)||this._$AL.set(e,t),!0===n.reflect&&this._$El!==e&&(void 0===this._$EC&&(this._$EC=new Map),this._$EC.set(e,n))):i=!1),!this.isUpdatePending&&i&&(this._$E_=this._$Ej())}},{key:"_$Ej",value:(n=Xr(Kr().m((function e(){var t,n;return Kr().w((function(e){for(;;)switch(e.p=e.n){case 0:return this.isUpdatePending=!0,e.p=1,e.n=2,this._$E_;case 2:e.n=4;break;case 3:e.p=3,n=e.v,Promise.reject(n);case 4:if(null==(t=this.scheduleUpdate())){e.n=5;break}return e.n=5,t;case 5:return e.a(2,!this.isUpdatePending)}}),e,this,[[1,3]])}))),function(){return n.apply(this,arguments)})},{key:"scheduleUpdate",value:function(){return this.performUpdate()}},{key:"performUpdate",value:function(){var e,t=this;if(this.isUpdatePending){this.hasUpdated,this._$Ei&&(this._$Ei.forEach((function(e,n){return t[n]=e})),this._$Ei=void 0);var n=!1,i=this._$AL;try{(n=this.shouldUpdate(i))?(this.willUpdate(i),null===(e=this._$ES)||void 0===e||e.forEach((function(e){var t;return null===(t=e.hostUpdate)||void 0===t?void 0:t.call(e)})),this.update(i)):this._$Ek()}catch(e){throw n=!1,this._$Ek(),e}n&&this._$AE(i)}}},{key:"willUpdate",value:function(e){}},{key:"_$AE",value:function(e){var t;null===(t=this._$ES)||void 0===t||t.forEach((function(e){var t;return null===(t=e.hostUpdated)||void 0===t?void 0:t.call(e)})),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(e)),this.updated(e)}},{key:"_$Ek",value:function(){this._$AL=new Map,this.isUpdatePending=!1}},{key:"updateComplete",get:function(){return this.getUpdateComplete()}},{key:"getUpdateComplete",value:function(){return this._$E_}},{key:"shouldUpdate",value:function(e){return!0}},{key:"update",value:function(e){var t=this;void 0!==this._$EC&&(this._$EC.forEach((function(e,n){return t._$EO(n,t[n],e)})),this._$EC=void 0),this._$Ek()}},{key:"updated",value:function(e){}},{key:"firstUpdated",value:function(e){}}],[{key:"addInitializer",value:function(e){var t;this.finalize(),(null!==(t=this.h)&&void 0!==t?t:this.h=[]).push(e)}},{key:"observedAttributes",get:function(){var e=this;this.finalize();var t=[];return this.elementProperties.forEach((function(n,i){var o=e._$Ep(i,n);void 0!==o&&(e._$Ev.set(o,i),t.push(o))})),t}},{key:"createProperty",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:Ov;if(t.state&&(t.attribute=!1),this.finalize(),this.elementProperties.set(e,t),!t.noAccessor&&!this.prototype.hasOwnProperty(e)){var n="symbol"==ha(e)?Symbol():"__"+e,i=this.getPropertyDescriptor(e,n,t);void 0!==i&&Object.defineProperty(this.prototype,e,i)}}},{key:"getPropertyDescriptor",value:function(e,t,n){return{get:function(){return this[t]},set:function(i){var o=this[e];this[t]=i,this.requestUpdate(e,o,n)},configurable:!0,enumerable:!0}}},{key:"getPropertyOptions",value:function(e){return this.elementProperties.get(e)||Ov}},{key:"finalize",value:function(){if(this.hasOwnProperty(Mv))return!1;this[Mv]=!0;var e=Object.getPrototypeOf(this);if(e.finalize(),void 0!==e.h&&(this.h=Ur(e.h)),this.elementProperties=new Map(e.elementProperties),this._$Ev=new Map,this.hasOwnProperty("properties")){var t,n=this.properties,i=ra([].concat(Ur(Object.getOwnPropertyNames(n)),Ur(Object.getOwnPropertySymbols(n))));try{for(i.s();!(t=i.n()).done;){var o=t.value;this.createProperty(o,n[o])}}catch(e){i.e(e)}finally{i.f()}}return this.elementStyles=this.finalizeStyles(this.styles),!0}},{key:"finalizeStyles",value:function(e){var t=[];if(Array.isArray(e)){var n,i=ra(new Set(e.flat(1/0).reverse()));try{for(i.s();!(n=i.n()).done;){var o=n.value;t.unshift(xv(o))}}catch(e){i.e(e)}finally{i.f()}}else void 0!==e&&t.push(xv(e));return t}},{key:"_$Ep",value:function(e,t){var n=t.attribute;return!1===n?void 0:"string"==typeof n?n:"string"==typeof e?e.toLowerCase():void 0}}]);var n}();zv[Mv]=!0,zv.elementProperties=new Map,zv.elementStyles=[],zv.shadowRootOptions={mode:"open"},null==Sv||Sv({ReactiveElement:zv}),(null!==(hv=Ev.reactiveElementVersions)&&void 0!==hv?hv:Ev.reactiveElementVersions=[]).push("1.6.3");var Pv=window,Lv=Pv.trustedTypes,Nv=Lv?Lv.createPolicy("lit-html",{createHTML:function(e){return e}}):void 0,jv="$lit$",$v="lit$".concat((Math.random()+"").slice(9),"$"),Bv="?"+$v,Rv="<".concat(Bv,">"),Hv=document,Dv=function(){return Hv.createComment("")},Uv=function(e){return null===e||"object"!=ha(e)&&"function"!=typeof e},Vv=Array.isArray,Fv="[ \t\n\f\r]",Gv=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,Yv=/-->/g,Kv=/>/g,Wv=RegExp(">|".concat(Fv,"(?:([^\\s\"'>=/]+)(").concat(Fv,"*=").concat(Fv,"*(?:[^ \t\n\f\r\"'`<>=]|(\"|')|))|$)"),"g"),qv=/'/g,Xv=/"/g,Zv=/^(?:script|style|textarea|title)$/i,Jv=function(e){return function(t){for(var n=arguments.length,i=new Array(n>1?n-1:0),o=1;o":"",a=Gv,l=0;l"===u[0]?(a=null!=n?n:Gv,d=-1):void 0===u[1]?d=-2:(d=a.lastIndex-u[2].length,c=u[1],a=void 0===u[3]?Wv:'"'===u[3]?Xv:qv):a===Xv||a===qv?a=Wv:a===Yv||a===Kv?a=Gv:(a=Wv,n=void 0);var p=a===Wv&&e[l+1].startsWith("/>")?" ":"";r+=a===Gv?s+Rv:d>=0?(o.push(c),s.slice(0,d)+jv+s.slice(d)+$v+p):s+$v+(-2===d?(o.push(void 0),l):p)}return[ig(e,r+(e[i]||"")+(2===t?"":"")),o]}(o,r),d=Fr(u,2),h=d[0],p=d[1];if(this.el=e.createElement(h,n),ng.currentNode=this.el.content,2===r){var f=this.el.content,m=f.firstChild;m.remove(),f.append.apply(f,Ur(m.childNodes))}for(;null!==(i=ng.nextNode())&&c.length0){i.textContent=Lv?Lv.emptyScript:"";for(var T=0;T2&&void 0!==arguments[2]?arguments[2]:e,l=arguments.length>3?arguments[3]:void 0;if(t===Qv)return t;var s=void 0!==l?null===(n=a._$Co)||void 0===n?void 0:n[l]:a._$Cl,c=Uv(t)?void 0:t._$litDirective$;return(null==s?void 0:s.constructor)!==c&&(null===(i=null==s?void 0:s._$AO)||void 0===i||i.call(s,!1),void 0===c?s=void 0:(s=new c(e))._$AT(e,a,l),void 0!==l?(null!==(o=(r=a)._$Co)&&void 0!==o?o:r._$Co=[])[l]=s:a._$Cl=s),void 0!==s&&(t=rg(e,s._$AS(e,t.values),s,l)),t}var ag=function(){return ua((function e(t,n){sa(this,e),this._$AV=[],this._$AN=void 0,this._$AD=t,this._$AM=n}),[{key:"parentNode",get:function(){return this._$AM.parentNode}},{key:"_$AU",get:function(){return this._$AM._$AU}},{key:"u",value:function(e){var t,n=this._$AD,i=n.el.content,o=n.parts,r=(null!==(t=null==e?void 0:e.creationScope)&&void 0!==t?t:Hv).importNode(i,!0);ng.currentNode=r;for(var a=ng.nextNode(),l=0,s=0,c=o[0];void 0!==c;){if(l===c.index){var u=void 0;2===c.type?u=new lg(a,a.nextSibling,this,e):1===c.type?u=new c.ctor(a,c.name,c.strings,this,e):6===c.type&&(u=new pg(a,this,e)),this._$AV.push(u),c=o[++s]}l!==(null==c?void 0:c.index)&&(a=ng.nextNode(),l++)}return ng.currentNode=Hv,r}},{key:"v",value:function(e){var t,n=0,i=ra(this._$AV);try{for(i.s();!(t=i.n()).done;){var o=t.value;void 0!==o&&(void 0!==o.strings?(o._$AI(e,o,n),n+=o.strings.length-2):o._$AI(e[n])),n++}}catch(e){i.e(e)}finally{i.f()}}}])}(),lg=function(){function e(t,n,i,o){var r;sa(this,e),this.type=2,this._$AH=eg,this._$AN=void 0,this._$AA=t,this._$AB=n,this._$AM=i,this.options=o,this._$Cp=null===(r=null==o?void 0:o.isConnected)||void 0===r||r}return ua(e,[{key:"_$AU",get:function(){var e,t;return null!==(t=null===(e=this._$AM)||void 0===e?void 0:e._$AU)&&void 0!==t?t:this._$Cp}},{key:"parentNode",get:function(){var e=this._$AA.parentNode,t=this._$AM;return void 0!==t&&11===(null==e?void 0:e.nodeType)&&(e=t.parentNode),e}},{key:"startNode",get:function(){return this._$AA}},{key:"endNode",get:function(){return this._$AB}},{key:"_$AI",value:function(e){e=rg(this,e,arguments.length>1&&void 0!==arguments[1]?arguments[1]:this),Uv(e)?e===eg||null==e||""===e?(this._$AH!==eg&&this._$AR(),this._$AH=eg):e!==this._$AH&&e!==Qv&&this._(e):void 0!==e._$litType$?this.g(e):void 0!==e.nodeType?this.$(e):function(e){return Vv(e)||"function"==typeof(null==e?void 0:e[Symbol.iterator])}(e)?this.T(e):this._(e)}},{key:"k",value:function(e){return this._$AA.parentNode.insertBefore(e,this._$AB)}},{key:"$",value:function(e){this._$AH!==e&&(this._$AR(),this._$AH=this.k(e))}},{key:"_",value:function(e){this._$AH!==eg&&Uv(this._$AH)?this._$AA.nextSibling.data=e:this.$(Hv.createTextNode(e)),this._$AH=e}},{key:"g",value:function(e){var t,n=e.values,i=e._$litType$,o="number"==typeof i?this._$AC(e):(void 0===i.el&&(i.el=og.createElement(ig(i.h,i.h[0]),this.options)),i);if((null===(t=this._$AH)||void 0===t?void 0:t._$AD)===o)this._$AH.v(n);else{var r=new ag(o,this),a=r.u(this.options);r.v(n),this.$(a),this._$AH=r}}},{key:"_$AC",value:function(e){var t=tg.get(e.strings);return void 0===t&&tg.set(e.strings,t=new og(e)),t}},{key:"T",value:function(t){Vv(this._$AH)||(this._$AH=[],this._$AR());var n,i,o=this._$AH,r=0,a=ra(t);try{for(a.s();!(i=a.n()).done;){var l=i.value;r===o.length?o.push(n=new e(this.k(Dv()),this.k(Dv()),this,this.options)):n=o[r],n._$AI(l),r++}}catch(e){a.e(e)}finally{a.f()}r0&&void 0!==arguments[0]?arguments[0]:this._$AA.nextSibling,n=arguments.length>1?arguments[1]:void 0;for(null===(e=this._$AP)||void 0===e||e.call(this,!1,!0,n);t&&t!==this._$AB;){var i=t.nextSibling;t.remove(),t=i}}},{key:"setConnected",value:function(e){var t;void 0===this._$AM&&(this._$Cp=e,null===(t=this._$AP)||void 0===t||t.call(this,e))}}])}(),sg=function(){return ua((function e(t,n,i,o,r){sa(this,e),this.type=1,this._$AH=eg,this._$AN=void 0,this.element=t,this.name=n,this._$AM=o,this.options=r,i.length>2||""!==i[0]||""!==i[1]?(this._$AH=Array(i.length-1).fill(new String),this.strings=i):this._$AH=eg}),[{key:"tagName",get:function(){return this.element.tagName}},{key:"_$AU",get:function(){return this._$AM._$AU}},{key:"_$AI",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this,n=arguments.length>2?arguments[2]:void 0,i=arguments.length>3?arguments[3]:void 0,o=this.strings,r=!1;if(void 0===o)e=rg(this,e,t,0),(r=!Uv(e)||e!==this._$AH&&e!==Qv)&&(this._$AH=e);else{var a,l,s=e;for(e=o[0],a=0;a1&&void 0!==arguments[1]?arguments[1]:this,0))&&void 0!==t?t:eg)!==Qv){var n=this._$AH,i=e===eg&&n!==eg||e.capture!==n.capture||e.once!==n.once||e.passive!==n.passive,o=e!==eg&&(n===eg||i);i&&this.element.removeEventListener(this.name,this,n),o&&this.element.addEventListener(this.name,this,e),this._$AH=e}}},{key:"handleEvent",value:function(e){var t,n;"function"==typeof this._$AH?this._$AH.call(null!==(n=null===(t=this.options)||void 0===t?void 0:t.host)&&void 0!==n?n:this.element,e):this._$AH.handleEvent(e)}}])}(),pg=function(){return ua((function e(t,n,i){sa(this,e),this.element=t,this.type=6,this._$AN=void 0,this._$AM=n,this.options=i}),[{key:"_$AU",get:function(){return this._$AM._$AU}},{key:"_$AI",value:function(e){rg(this,e)}}])}(),fg=Pv.litHtmlPolyfillSupport;null==fg||fg(og,lg),(null!==(pv=Pv.litHtmlVersions)&&void 0!==pv?pv:Pv.litHtmlVersions=[]).push("2.8.0");var mg,vg,gg=function(e){function t(){var e;return sa(this,t),(e=Zr(this,t,arguments)).renderOptions={host:Qr(e)},e._$Do=void 0,e}return ea(t,zv),ua(t,[{key:"createRenderRoot",value:function(){var e,n,i=Hr(t,"createRenderRoot",this,3)([]);return null!==(e=(n=this.renderOptions).renderBefore)&&void 0!==e||(n.renderBefore=i.firstChild),i}},{key:"update",value:function(e){var n=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),Hr(t,"update",this,3)([e]),this._$Do=function(e,t,n){var i,o,r=null!==(i=null==n?void 0:n.renderBefore)&&void 0!==i?i:t,a=r._$litPart$;if(void 0===a){var l=null!==(o=null==n?void 0:n.renderBefore)&&void 0!==o?o:null;r._$litPart$=a=new lg(t.insertBefore(Dv(),l),l,void 0,null!=n?n:{})}return a._$AI(e),a}(n,this.renderRoot,this.renderOptions)}},{key:"connectedCallback",value:function(){var e;Hr(t,"connectedCallback",this,3)([]),null===(e=this._$Do)||void 0===e||e.setConnected(!0)}},{key:"disconnectedCallback",value:function(){var e;Hr(t,"disconnectedCallback",this,3)([]),null===(e=this._$Do)||void 0===e||e.setConnected(!1)}},{key:"render",value:function(){return Qv}}])}(); -/** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - */gg.finalized=!0,gg._$litElement$=!0,null===(mg=globalThis.litElementHydrateSupport)||void 0===mg||mg.call(globalThis,{LitElement:gg});var _g=globalThis.litElementPolyfillSupport;null==_g||_g({LitElement:gg}),(null!==(vg=globalThis.litElementVersions)&&void 0!==vg?vg:globalThis.litElementVersions=[]).push("3.3.3"); -/** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - */ -var yg=function(e,t){return"method"===t.kind&&t.descriptor&&!("value"in t.descriptor)?Br(Br({},t),{},{finisher:function(n){n.createProperty(t.key,e)}}):{kind:"field",key:Symbol(),placement:"own",descriptor:{},originalKey:t.key,initializer:function(){"function"==typeof t.initializer&&(this[t.key]=t.initializer.call(this))},finisher:function(n){n.createProperty(t.key,e)}}},bg=function(e,t,n){t.constructor.createProperty(n,e)};function kg(e){return function(t,n){return void 0!==n?bg(e,t,n):yg(e,t)}} -/** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - */function wg(e){return kg(Br(Br({},e),{},{state:!0}))} -/** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - */var xg,Eg=function(e){var t=e.finisher,n=e.descriptor;return function(e,i){var o;if(void 0===i){var r=null!==(o=e.originalKey)&&void 0!==o?o:e.key,a=null!=n?{kind:"method",placement:"prototype",key:r,descriptor:n(e.key)}:Br(Br({},e),{},{key:r});return null!=t&&(a.finisher=function(e){t(e,r)}),a}var l=e.constructor;void 0!==n&&Object.defineProperty(e,i,n(i)),null==t||t(l,i)}}; -/** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - */ -/** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - */ -function Cg(e,t){return Eg({descriptor:function(t){var n={get:function(){var t,n;return null!==(n=null===(t=this.renderRoot)||void 0===t?void 0:t.querySelector(e))&&void 0!==n?n:null},enumerable:!0,configurable:!0};return n}})} -/** - * @license - * Copyright 2021 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - */null===(xg=window.HTMLSlotElement)||void 0===xg||xg.prototype.assignedElements; -/** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - */ -var Ag=1,Sg=function(){return ua((function e(t){sa(this,e)}),[{key:"_$AU",get:function(){return this._$AM._$AU}},{key:"_$AT",value:function(e,t,n){this._$Ct=e,this._$AM=t,this._$Ci=n}},{key:"_$AS",value:function(e,t){return this.update(e,t)}},{key:"update",value:function(e,t){return this.render.apply(this,Ur(t))}}])}(),Tg=function(e){return function(){for(var t=arguments.length,n=new Array(t),i=0;i2)throw Error("`classMap()` can only be used in the `class` attribute and must be the only part in the attribute.");return n}return ea(t,Sg),ua(t,[{key:"render",value:function(e){return" "+Object.keys(e).filter((function(t){return e[t]})).join(" ")+" "}},{key:"update",value:function(e,t){var n,i,o=this,r=Fr(t,1)[0];if(void 0===this.it){for(var a in this.it=new Set,void 0!==e.strings&&(this.nt=new Set(e.strings.join(" ").split(/\s/).filter((function(e){return""!==e})))),r)r[a]&&!(null===(n=this.nt)||void 0===n?void 0:n.has(a))&&this.it.add(a);return this.render(r)}var l=e.element.classList;for(var s in this.it.forEach((function(e){e in r||(l.remove(e),o.it.delete(e))})),r){var c=!!r[s];c===this.it.has(s)||(null===(i=this.nt)||void 0===i?void 0:i.has(s))||(c?(l.add(s),this.it.add(s)):(l.remove(s),this.it.delete(s)))}return Qv}}])}()),Ig=function(e){return null!=e?e:eg},Og=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t={};for(var n in e)t[n]=e[n];return Object.assign({badInput:!1,customError:!1,patternMismatch:!1,rangeOverflow:!1,rangeUnderflow:!1,stepMismatch:!1,tooLong:!1,tooShort:!1,typeMismatch:!1,valid:!0,valueMissing:!1},t)},Mg=function(e){function t(){var e;return sa(this,t),(e=Zr(this,t,arguments)).mdcFoundationClass=gv,e.disabled=!1,e.outlined=!1,e.label="",e.outlineOpen=!1,e.outlineWidth=0,e.value="",e.name="",e.selectedText="",e.icon="",e.menuOpen=!1,e.helper="",e.validateOnInitialRender=!1,e.validationMessage="",e.required=!1,e.naturalMenuWidth=!1,e.isUiValid=!0,e.fixedMenuPosition=!1,e.typeaheadState={bufferClearTimeout:0,currentFirstChar:"",sortedIndexCursor:0,typeaheadBuffer:""},e.sortedIndexByFirstChar=new Map,e.menuElement_=null,e.listeners=[],e.onBodyClickBound=function(){},e._menuUpdateComplete=null,e.valueSetDirectly=!1,e.validityTransform=null,e._validity=Og(),e}return ea(t,fm),ua(t,[{key:"items",get:function(){return this.menuElement_||(this.menuElement_=this.menuElement),this.menuElement_?this.menuElement_.items:[]}},{key:"selected",get:function(){var e=this.menuElement;return e?e.selected:null}},{key:"index",get:function(){var e=this.menuElement;return e?e.index:-1}},{key:"shouldRenderHelperText",get:function(){return!!this.helper||!!this.validationMessage}},{key:"validity",get:function(){return this._checkValidity(this.value),this._validity}},{key:"render",value:function(){var e={"mdc-select--disabled":this.disabled,"mdc-select--no-label":!this.label,"mdc-select--filled":!this.outlined,"mdc-select--outlined":this.outlined,"mdc-select--with-leading-icon":!!this.icon,"mdc-select--required":this.required,"mdc-select--invalid":!this.isUiValid},t=this.label?"label":void 0,n=this.shouldRenderHelperText?"helper-text":void 0;return Jv(l||(l=Pr(['\n \n \n \x3c!-- @ts-ignore --\x3e\n
\n ','\n \n \n \n \n \n \n \n \n \n ',"\n
\n ","\n \n ",""])),Tg(e),this.name,this.value,this.disabled,this.required,this.menuOpen,!this.isUiValid,Ig(t),this.required,Ig(n),this.onClick,this.onFocus,this.onBlur,this.onKeydown,this.renderRipple(),this.outlined?this.renderOutline():this.renderLabel(),this.renderLeadingIcon(),this.selectedText,this.renderLineRipple(),this.renderMenu(),this.renderHelperText())}},{key:"renderMenu",value:function(){var e=this.getMenuClasses();return Jv(s||(s=Pr(['\n \n '])))}},{key:"renderOutline",value:function(){return this.outlined?Jv(d||(d=Pr(["\n \n ',"\n "])),this.outlineWidth,this.outlineOpen,this.renderLabel()):eg}},{key:"renderLabel",value:function(){return this.label?Jv(h||(h=Pr(["\n ',"\n "])),wm(this.label),this.label):eg}},{key:"renderLeadingIcon",value:function(){return this.icon?Jv(p||(p=Pr(['
',"
"])),this.icon):eg}},{key:"renderLineRipple",value:function(){return this.outlined?eg:Jv(f||(f=Pr(["\n \n "])),Im())}},{key:"renderHelperText",value:function(){if(!this.shouldRenderHelperText)return eg;var e=this.validationMessage&&!this.isUiValid,t={"mdc-select-helper-text--validation-msg":e};return Jv(m||(m=Pr(['\n ',"

"])),Tg(t),e?this.validationMessage:this.helper)}},{key:"createAdapter",value:function(){var e,t=this;return Object.assign(Object.assign({},lm(this.mdcRoot)),{activateBottomLine:function(){t.lineRippleElement&&t.lineRippleElement.lineRippleFoundation.activate()},deactivateBottomLine:function(){t.lineRippleElement&&t.lineRippleElement.lineRippleFoundation.deactivate()},hasLabel:function(){return!!t.label},floatLabel:function(e){t.labelElement&&t.labelElement.floatingLabelFoundation.float(e)},getLabelWidth:function(){return t.labelElement?t.labelElement.floatingLabelFoundation.getWidth():0},setLabelRequired:function(e){t.labelElement&&t.labelElement.floatingLabelFoundation.setRequired(e)},hasOutline:function(){return t.outlined},notchOutline:function(e){t.outlineElement&&!t.outlineOpen&&(t.outlineWidth=e,t.outlineOpen=!0)},closeOutline:function(){t.outlineElement&&(t.outlineOpen=!1)},setRippleCenter:function(e){t.lineRippleElement&&t.lineRippleElement.lineRippleFoundation.setRippleCenter(e)},notifyChange:(e=Xr(Kr().m((function e(n){var i;return Kr().w((function(e){for(;;)switch(e.n){case 0:if(t.valueSetDirectly||n!==t.value){e.n=1;break}return e.a(2);case 1:return t.valueSetDirectly=!1,t.value=n,e.n=2,t.updateComplete;case 2:i=new Event("change",{bubbles:!0}),t.dispatchEvent(i);case 3:return e.a(2)}}),e)}))),function(t){return e.apply(this,arguments)}),setSelectedText:function(e){return t.selectedText=e},isSelectAnchorFocused:function(){var e=t.anchorElement;return!!e&&e.getRootNode().activeElement===e},getSelectAnchorAttr:function(e){var n=t.anchorElement;return n?n.getAttribute(e):null},setSelectAnchorAttr:function(e,n){var i=t.anchorElement;i&&i.setAttribute(e,n)},removeSelectAnchorAttr:function(e){var n=t.anchorElement;n&&n.removeAttribute(e)},openMenu:function(){t.menuOpen=!0},closeMenu:function(){t.menuOpen=!1},addMenuClass:function(){},removeMenuClass:function(){},getAnchorElement:function(){return t.anchorElement},setMenuAnchorElement:function(){},setMenuAnchorCorner:function(){var e=t.menuElement;e&&(e.corner="BOTTOM_START")},setMenuWrapFocus:function(e){var n=t.menuElement;n&&(n.wrapFocus=e)},focusMenuItemAtIndex:function(e){var n=t.menuElement;if(n){var i=n.items[e];i&&i.focus()}},getMenuItemCount:function(){var e=t.menuElement;return e?e.items.length:0},getMenuItemValues:function(){var e=t.menuElement;return e?e.items.map((function(e){return e.value})):[]},getMenuItemTextAtIndex:function(e){var n=t.menuElement;if(!n)return"";var i=n.items[e];return i?i.text:""},getSelectedIndex:function(){return t.index},setSelectedIndex:function(){},isTypeaheadInProgress:function(){return nf(t.typeaheadState)},typeaheadMatchItem:function(e,n){if(!t.menuElement)return-1;var i={focusItemAtIndex:function(e){t.menuElement.focusItemAtIndex(e)},focusedItemIndex:n||t.menuElement.getFocusedItemIndex(),nextChar:e,sortedIndexByFirstChar:t.sortedIndexByFirstChar,skipFocus:!1,isItemAtIndexDisabled:function(e){return t.items[e].disabled}},o=tf(i,t.typeaheadState);return-1!==o&&t.select(o),o}})}},{key:"checkValidity",value:function(){var e=this._checkValidity(this.value);if(!e){var t=new Event("invalid",{bubbles:!1,cancelable:!0});this.dispatchEvent(t)}return e}},{key:"reportValidity",value:function(){var e=this.checkValidity();return this.isUiValid=e,e}},{key:"_checkValidity",value:function(e){var t=this.formElement.validity,n=Og(t);if(this.validityTransform){var i=this.validityTransform(e,n);n=Object.assign(Object.assign({},n),i)}return this._validity=n,this._validity.valid}},{key:"setCustomValidity",value:function(e){this.validationMessage=e,this.formElement.setCustomValidity(e)}},{key:"getUpdateComplete",value:(a=Xr(Kr().m((function e(){var n;return Kr().w((function(e){for(;;)switch(e.n){case 0:return e.n=1,this._menuUpdateComplete;case 1:return e.n=2,Hr(t,"getUpdateComplete",this,3)([]);case 2:return n=e.v,e.a(2,n)}}),e,this)}))),function(){return a.apply(this,arguments)})},{key:"firstUpdated",value:(r=Xr(Kr().m((function e(){var n,i,o=this;return Kr().w((function(e){for(;;)switch(e.n){case 0:if(!(n=this.menuElement)){e.n=1;break}return this._menuUpdateComplete=n.updateComplete,e.n=1,this._menuUpdateComplete;case 1:if(Hr(t,"firstUpdated",this,3)([]),this.mdcFoundation.isValid=function(){return!0},this.mdcFoundation.setValid=function(){},this.mdcFoundation.setDisabled(this.disabled),this.validateOnInitialRender&&this.reportValidity(),this.selected){e.n=5;break}if(this.items.length||!this.slotElement||!this.slotElement.assignedNodes({flatten:!0}).length){e.n=3;break}return e.n=2,new Promise((function(e){return requestAnimationFrame(e)}));case 2:return e.n=3,this.layout();case 3:if(i=this.items.length&&""===this.items[0].value,this.value||!i){e.n=4;break}return this.select(0),e.a(2);case 4:this.selectByValue(this.value);case 5:this.sortedIndexByFirstChar=ef(this.items.length,(function(e){return o.items[e].text}));case 6:return e.a(2)}}),e,this)}))),function(){return r.apply(this,arguments)})},{key:"onItemsUpdated",value:function(){var e=this;this.sortedIndexByFirstChar=ef(this.items.length,(function(t){return e.items[t].text}))}},{key:"select",value:function(e){var t=this.menuElement;t&&t.select(e)}},{key:"selectByValue",value:function(e){for(var t=-1,n=0;n0,o=n&&this.index0&&void 0!==a[0])||a[0],this.mdcFoundation&&this.mdcFoundation.layout(),e.n=1,this.updateComplete;case 1:if((n=this.menuElement)&&n.layout(t),i=this.labelElement){e.n=2;break}return this.outlineOpen=!1,e.a(2);case 2:if(o=!!this.label&&!!this.value,i.floatingLabelFoundation.float(o),this.outlined){e.n=3;break}return e.a(2);case 3:return this.outlineOpen=o,e.n=4,this.updateComplete;case 4:r=i.floatingLabelFoundation.getWidth(),this.outlineOpen&&(this.outlineWidth=r);case 5:return e.a(2)}}),e,this)}))),function(){return i.apply(this,arguments)})},{key:"layoutOptions",value:(n=Xr(Kr().m((function e(){return Kr().w((function(e){for(;;)switch(e.n){case 0:if(this.mdcFoundation){e.n=1;break}return e.a(2);case 1:this.mdcFoundation.layoutOptions();case 2:return e.a(2)}}),e,this)}))),function(){return n.apply(this,arguments)})}]);var n,i,o,r,a}();ga([Cg(".mdc-select")],Mg.prototype,"mdcRoot",void 0),ga([Cg(".formElement")],Mg.prototype,"formElement",void 0),ga([Cg("slot")],Mg.prototype,"slotElement",void 0),ga([Cg("select")],Mg.prototype,"nativeSelectElement",void 0),ga([Cg("input")],Mg.prototype,"nativeInputElement",void 0),ga([Cg(".mdc-line-ripple")],Mg.prototype,"lineRippleElement",void 0),ga([Cg(".mdc-floating-label")],Mg.prototype,"labelElement",void 0),ga([Cg("mwc-notched-outline")],Mg.prototype,"outlineElement",void 0),ga([Cg(".mdc-menu")],Mg.prototype,"menuElement",void 0),ga([Cg(".mdc-select__anchor")],Mg.prototype,"anchorElement",void 0),ga([kg({type:Boolean,attribute:"disabled",reflect:!0}),mm((function(e){this.mdcFoundation&&this.mdcFoundation.setDisabled(e)}))],Mg.prototype,"disabled",void 0),ga([kg({type:Boolean}),mm((function(e,t){void 0!==t&&this.outlined!==t&&this.layout(!1)}))],Mg.prototype,"outlined",void 0),ga([kg({type:String}),mm((function(e,t){void 0!==t&&this.label!==t&&this.layout(!1)}))],Mg.prototype,"label",void 0),ga([wg()],Mg.prototype,"outlineOpen",void 0),ga([wg()],Mg.prototype,"outlineWidth",void 0),ga([kg({type:String}),mm((function(e){if(this.mdcFoundation){var t=null===this.selected&&!!e,n=this.selected&&this.selected.value!==e;(t||n)&&this.selectByValue(e),this.reportValidity()}}))],Mg.prototype,"value",void 0),ga([kg()],Mg.prototype,"name",void 0),ga([wg()],Mg.prototype,"selectedText",void 0),ga([kg({type:String})],Mg.prototype,"icon",void 0),ga([wg()],Mg.prototype,"menuOpen",void 0),ga([kg({type:String})],Mg.prototype,"helper",void 0),ga([kg({type:Boolean})],Mg.prototype,"validateOnInitialRender",void 0),ga([kg({type:String})],Mg.prototype,"validationMessage",void 0),ga([kg({type:Boolean})],Mg.prototype,"required",void 0),ga([kg({type:Boolean})],Mg.prototype,"naturalMenuWidth",void 0),ga([wg()],Mg.prototype,"isUiValid",void 0),ga([kg({type:Boolean})],Mg.prototype,"fixedMenuPosition",void 0),ga([function(e){return Eg({finisher:function(t,n){Object.assign(t.prototype[n],e)}})}({capture:!0})],Mg.prototype,"handleTypeahead",null); -/** - * @license - * Copyright 2021 Google LLC - * SPDX-LIcense-Identifier: Apache-2.0 - */ -var zg=function(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),i=1;i']))):hl}},{key:"connectedCallback",value:function(){Hr(t,"connectedCallback",this,3)([]),window.addEventListener("translations-updated",this._translationsUpdated)}},{key:"disconnectedCallback",value:function(){Hr(t,"disconnectedCallback",this,3)([]),window.removeEventListener("translations-updated",this._translationsUpdated)}}])}();Pg.styles=[zg,Aa(_||(_=Pr(["\n .mdc-select__anchor {\n height: var(--select-height, 56px) !important;\n }\n "])))],ga([zl({type:Boolean})],Pg.prototype,"icon",void 0),Pg=ga([Il("mushroom-select")],Pg);var Lg=["default","start","center","end","justify"],Ng={default:"mdi:format-align-left",start:"mdi:format-align-left",center:"mdi:format-align-center",end:"mdi:format-align-right",justify:"mdi:format-align-justify"},jg=function(e){function t(){var e;return sa(this,t),(e=Zr(this,t,arguments)).label="",e.configValue="",e}return ea(t,Sl),ua(t,[{key:"_selectChanged",value:function(e){var t=e.target.value;t&&this.dispatchEvent(new CustomEvent("value-changed",{detail:{value:"default"!==t?t:""}}))}},{key:"render",value:function(){var e=Uh(this.hass),t=this.value||"default";return cl(y||(y=Pr(["\n \n ','\n =e.length)return!1;var t=e[s_];if(r_.test(t))return!0;if("-"===t){if(e.length-s_<2)return!1;var n=e[s_+1];return!("-"!==n&&!r_.test(n))}return!1}var d_={deg:1,rad:180/Math.PI,grad:.9,turn:360};function h_(e){var t="";if("-"!==e[s_]&&"+"!==e[s_]||(t+=e[s_++]),t+=p_(e),"."===e[s_]&&/\d/.test(e[s_+1])&&(t+=e[s_++]+p_(e)),"e"!==e[s_]&&"E"!==e[s_]||("-"!==e[s_+1]&&"+"!==e[s_+1]||!/\d/.test(e[s_+2])?/\d/.test(e[s_+1])&&(t+=e[s_++]+p_(e)):t+=e[s_++]+e[s_++]+p_(e)),u_(e)){var n=f_(e);return"deg"===n||"rad"===n||"turn"===n||"grad"===n?{type:l_.Hue,value:t*d_[n]}:void 0}return"%"===e[s_]?(s_++,{type:l_.Percentage,value:+t}):{type:l_.Number,value:+t}}function p_(e){for(var t="";/\d/.test(e[s_]);)t+=e[s_++];return t}function f_(e){for(var t="";s_4)){if(4===i.length){if(i[3].type!==l_.Alpha)return;i[3]=i[3].value}return 3===i.length&&i.push({type:l_.None,value:void 0}),i.every((function(e){return e.type!==l_.Alpha}))?i:void 0}}var __=function(e){if("string"==typeof e){for(var t=function(){var e,t=(arguments.length>0&&void 0!==arguments[0]?arguments[0]:"").trim(),n=[];for(s_=0;s_=1?t.length-1:Math.max(Math.floor(n),0),o=t[i];return void 0===o?void 0:y_(o[0],o[1],n-i)}}),k_=function(e){var t=!1,n=e.map((function(e){return void 0!==e?(t=!0,e):1}));return t?n:e},w_={mode:"rgb",channels:["r","g","b","alpha"],parse:[function(e,t){if(t&&("rgb"===t[0]||"rgba"===t[0])){var n={mode:"rgb"},i=Fr(t,5),o=i[1],r=i[2],a=i[3],l=i[4];if(o.type!==l_.Hue&&r.type!==l_.Hue&&a.type!==l_.Hue)return o.type!==l_.None&&(n.r=o.type===l_.Number?o.value/255:o.value/100),r.type!==l_.None&&(n.g=r.type===l_.Number?r.value/255:r.value/100),a.type!==l_.None&&(n.b=a.type===l_.Number?a.value/255:a.value/100),l.type!==l_.None&&(n.alpha=Math.min(1,Math.max(0,l.type===l_.Number?l.value:l.value/100))),n}},function(e){var t;return(t=e.match(Vg))?Dg(parseInt(t[1],16),t[1].length):void 0},function(e){var t,n={mode:"rgb"};if(t=e.match(qg))void 0!==t[1]&&(n.r=t[1]/255),void 0!==t[2]&&(n.g=t[2]/255),void 0!==t[3]&&(n.b=t[3]/255);else{if(!(t=e.match(Xg)))return;void 0!==t[1]&&(n.r=t[1]/100),void 0!==t[2]&&(n.g=t[2]/100),void 0!==t[3]&&(n.b=t[3]/100)}return void 0!==t[4]?n.alpha=Math.max(0,Math.min(1,t[4]/100)):void 0!==t[5]&&(n.alpha=Math.max(0,Math.min(1,+t[5]))),n},function(e){return Dg(Ug[e.toLowerCase()],6)},function(e){return"transparent"===e?{mode:"rgb",r:0,g:0,b:0,alpha:0}:void 0},"srgb"],serialize:"srgb",interpolate:{r:b_,g:b_,b:b_,alpha:{use:b_,fixup:k_}},gamut:!0,white:{r:1,g:1,b:1},black:{r:0,g:0,b:0}},x_=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0;return Math.pow(Math.abs(e),563/256)*Math.sign(e)},E_=function(e){var t=x_(e.r),n=x_(e.g),i=x_(e.b),o={mode:"xyz65",x:.5766690429101305*t+.1855582379065463*n+.1882286462349947*i,y:.297344975250536*t+.6273635662554661*n+.0752914584939979*i,z:.0270313613864123*t+.0706888525358272*n+.9913375368376386*i};return void 0!==e.alpha&&(o.alpha=e.alpha),o},C_=function(e){return Math.pow(Math.abs(e),256/563)*Math.sign(e)},A_=function(e){var t=e.x,n=e.y,i=e.z,o=e.alpha;void 0===t&&(t=0),void 0===n&&(n=0),void 0===i&&(i=0);var r={mode:"a98",r:C_(2.0415879038107465*t-.5650069742788597*n-.3447313507783297*i),g:C_(-.9692436362808798*t+1.8759675015077206*n+.0415550574071756*i),b:C_(.0134442806320312*t-.1183623922310184*n+1.0151749943912058*i)};return void 0!==o&&(r.alpha=o),r},S_=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,t=Math.abs(e);return t<=.04045?e/12.92:(Math.sign(e)||1)*Math.pow((t+.055)/1.055,2.4)},T_=function(e){var t=e.r,n=e.g,i=e.b,o=e.alpha,r={mode:"lrgb",r:S_(t),g:S_(n),b:S_(i)};return void 0!==o&&(r.alpha=o),r},I_=function(e){var t=T_(e),n=t.r,i=t.g,o=t.b,r=t.alpha,a={mode:"xyz65",x:.4123907992659593*n+.357584339383878*i+.1804807884018343*o,y:.2126390058715102*n+.715168678767756*i+.0721923153607337*o,z:.0193308187155918*n+.119194779794626*i+.9505321522496607*o};return void 0!==r&&(a.alpha=r),a},O_=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,t=Math.abs(e);return t>.0031308?(Math.sign(e)||1)*(1.055*Math.pow(t,1/2.4)-.055):12.92*e},M_=function(e){var t=e.r,n=e.g,i=e.b,o=e.alpha,r={mode:arguments.length>1&&void 0!==arguments[1]?arguments[1]:"rgb",r:O_(t),g:O_(n),b:O_(i)};return void 0!==o&&(r.alpha=o),r},z_=function(e){var t=e.x,n=e.y,i=e.z,o=e.alpha;void 0===t&&(t=0),void 0===n&&(n=0),void 0===i&&(i=0);var r=M_({r:3.2409699419045226*t-1.537383177570094*n-.4986107602930034*i,g:-.9692436362808796*t+1.8759675015077204*n+.0415550574071756*i,b:.0556300796969936*t-.2039769588889765*n+1.0569715142428784*i});return void 0!==o&&(r.alpha=o),r},P_=Br(Br({},w_),{},{mode:"a98",parse:["a98-rgb"],serialize:"a98-rgb",fromMode:{rgb:function(e){return A_(I_(e))},xyz65:A_},toMode:{rgb:function(e){return z_(E_(e))},xyz65:E_}}),L_=function(e){return(e%=360)<0?e+360:e},N_=function(e){return function(e,t){return e.map((function(n,i,o){if(void 0===n)return n;var r=L_(n);return 0===i||void 0===e[i-1]?r:t(r-L_(o[i-1]))})).reduce((function(e,t){return e.length&&void 0!==t&&void 0!==e[e.length-1]?(e.push(t+e[e.length-1]),e):(e.push(t),e)}),[])}(e,(function(e){return Math.abs(e)<=180?e:e-360*Math.sign(e)}))},j_=[-.14861,1.78277,-.29227,-.90649,1.97294,0],$_=Math.PI/180,B_=180/Math.PI,R_=j_[3]*j_[4],H_=j_[1]*j_[4],D_=j_[1]*j_[2]-j_[0]*j_[3],U_=function(e,t){if(void 0===e.h||void 0===t.h||!e.s||!t.s)return 0;var n=L_(e.h),i=L_(t.h),o=Math.sin((i-n+360)/2*Math.PI/180);return 2*Math.sqrt(e.s*t.s)*o},V_=function(e,t){if(void 0===e.h||void 0===t.h||!e.c||!t.c)return 0;var n=L_(e.h),i=L_(t.h),o=Math.sin((i-n+360)/2*Math.PI/180);return 2*Math.sqrt(e.c*t.c)*o},F_=function(e){var t=e.reduce((function(e,t){if(void 0!==t){var n=t*Math.PI/180;e.sin+=Math.sin(n),e.cos+=Math.cos(n)}return e}),{sin:0,cos:0}),n=180*Math.atan2(t.sin,t.cos)/Math.PI;return n<0?360+n:n},G_={mode:"cubehelix",channels:["h","s","l","alpha"],parse:["--cubehelix"],serialize:"--cubehelix",ranges:{h:[0,360],s:[0,4.614],l:[0,1]},fromMode:{rgb:function(e){var t=e.r,n=e.g,i=e.b,o=e.alpha;void 0===t&&(t=0),void 0===n&&(n=0),void 0===i&&(i=0);var r=(D_*i+t*R_-n*H_)/(D_+R_-H_),a=i-r,l=(j_[4]*(n-r)-j_[2]*a)/j_[3],s={mode:"cubehelix",l:r,s:0===r||1===r?void 0:Math.sqrt(a*a+l*l)/(j_[4]*r*(1-r))};return s.s&&(s.h=Math.atan2(l,a)*B_-120),void 0!==o&&(s.alpha=o),s}},toMode:{rgb:function(e){var t=e.h,n=e.s,i=e.l,o=e.alpha,r={mode:"rgb"};t=(void 0===t?0:t+120)*$_,void 0===i&&(i=0);var a=void 0===n?0:n*i*(1-i),l=Math.cos(t),s=Math.sin(t);return r.r=i+a*(j_[0]*l+j_[1]*s),r.g=i+a*(j_[2]*l+j_[3]*s),r.b=i+a*(j_[4]*l+j_[5]*s),void 0!==o&&(r.alpha=o),r}},interpolate:{h:{use:b_,fixup:N_},s:b_,l:b_,alpha:{use:b_,fixup:k_}},difference:{h:U_},average:{h:F_}},Y_=function(e){var t=e.l,n=e.a,i=e.b,o=e.alpha,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"lch";void 0===n&&(n=0),void 0===i&&(i=0);var a=Math.sqrt(n*n+i*i),l={mode:r,l:t,c:a};return a&&(l.h=L_(180*Math.atan2(i,n)/Math.PI)),void 0!==o&&(l.alpha=o),l},K_=function(e){var t=e.l,n=e.c,i=e.h,o=e.alpha;void 0===i&&(i=0);var r={mode:arguments.length>1&&void 0!==arguments[1]?arguments[1]:"lab",l:t,a:n?n*Math.cos(i/180*Math.PI):0,b:n?n*Math.sin(i/180*Math.PI):0};return void 0!==o&&(r.alpha=o),r},W_=Math.pow(29,3)/Math.pow(3,3),q_=Math.pow(6,3)/Math.pow(29,3),X_=.3457/.3585,Z_=1,J_=.2958/.3585,Q_=.3127/.329,ey=1,ty=.3583/.329,ny=function(e){return Math.pow(e,3)>q_?Math.pow(e,3):(116*e-16)/W_},iy=function(e){var t=e.l,n=e.a,i=e.b,o=e.alpha;void 0===t&&(t=0),void 0===n&&(n=0),void 0===i&&(i=0);var r=(t+16)/116,a=r-i/200,l={mode:"xyz65",x:ny(n/500+r)*Q_,y:ny(r)*ey,z:ny(a)*ty};return void 0!==o&&(l.alpha=o),l},oy=function(e){return z_(iy(e))},ry=function(e){return e>q_?Math.cbrt(e):(W_*e+16)/116},ay=function(e){var t=e.x,n=e.y,i=e.z,o=e.alpha;void 0===t&&(t=0),void 0===n&&(n=0),void 0===i&&(i=0);var r=ry(t/Q_),a=ry(n/ey),l={mode:"lab65",l:116*a-16,a:500*(r-a),b:200*(a-ry(i/ty))};return void 0!==o&&(l.alpha=o),l},ly=function(e){var t=ay(I_(e));return e.r===e.b&&e.b===e.g&&(t.a=t.b=0),t},sy=26/180*Math.PI,cy=Math.cos(sy),uy=Math.sin(sy),dy=100/Math.log(1.39),hy=function(e){var t=e.l,n=e.c,i=e.h,o=e.alpha;void 0===t&&(t=0),void 0===n&&(n=0),void 0===i&&(i=0);var r={mode:"lab65",l:(Math.exp(1*t/dy)-1)/.0039},a=(Math.exp(.0435*n*1*1)-1)/.075,l=a*Math.cos(i/180*Math.PI-sy),s=a*Math.sin(i/180*Math.PI-sy);return r.a=l*cy-s/.83*uy,r.b=l*uy+s/.83*cy,void 0!==o&&(r.alpha=o),r},py=function(e){var t=e.l,n=e.a,i=e.b,o=e.alpha;void 0===t&&(t=0),void 0===n&&(n=0),void 0===i&&(i=0);var r=n*cy+i*uy,a=.83*(i*cy-n*uy),l=Math.sqrt(r*r+a*a),s={mode:"dlch",l:dy/1*Math.log(1+.0039*t),c:Math.log(1+.075*l)/.0435};return s.c&&(s.h=L_((Math.atan2(a,r)+sy)/Math.PI*180)),void 0!==o&&(s.alpha=o),s},fy=function(e){return hy(Y_(e,"dlch"))},my=function(e){return K_(py(e),"dlab")},vy={mode:"dlab",parse:["--din99o-lab"],serialize:"--din99o-lab",toMode:{lab65:fy,rgb:function(e){return oy(fy(e))}},fromMode:{lab65:my,rgb:function(e){return my(ly(e))}},channels:["l","a","b","alpha"],ranges:{l:[0,100],a:[-40.09,45.501],b:[-40.469,44.344]},interpolate:{l:b_,a:b_,b:b_,alpha:{use:b_,fixup:k_}}},gy={mode:"dlch",parse:["--din99o-lch"],serialize:"--din99o-lch",toMode:{lab65:hy,dlab:function(e){return K_(e,"dlab")},rgb:function(e){return oy(hy(e))}},fromMode:{lab65:py,dlab:function(e){return Y_(e,"dlch")},rgb:function(e){return py(ly(e))}},channels:["l","c","h","alpha"],ranges:{l:[0,100],c:[0,51.484],h:[0,360]},interpolate:{l:b_,c:b_,h:{use:b_,fixup:N_},alpha:{use:b_,fixup:k_}},difference:{h:V_},average:{h:F_}};var _y={mode:"hsi",toMode:{rgb:function(e){var t=e.h,n=e.s,i=e.i,o=e.alpha;t=L_(void 0!==t?t:0),void 0===n&&(n=0),void 0===i&&(i=0);var r,a=Math.abs(t/60%2-1);switch(Math.floor(t/60)){case 0:r={r:i*(1+n*(3/(2-a)-1)),g:i*(1+n*(3*(1-a)/(2-a)-1)),b:i*(1-n)};break;case 1:r={r:i*(1+n*(3*(1-a)/(2-a)-1)),g:i*(1+n*(3/(2-a)-1)),b:i*(1-n)};break;case 2:r={r:i*(1-n),g:i*(1+n*(3/(2-a)-1)),b:i*(1+n*(3*(1-a)/(2-a)-1))};break;case 3:r={r:i*(1-n),g:i*(1+n*(3*(1-a)/(2-a)-1)),b:i*(1+n*(3/(2-a)-1))};break;case 4:r={r:i*(1+n*(3*(1-a)/(2-a)-1)),g:i*(1-n),b:i*(1+n*(3/(2-a)-1))};break;case 5:r={r:i*(1+n*(3/(2-a)-1)),g:i*(1-n),b:i*(1+n*(3*(1-a)/(2-a)-1))};break;default:r={r:i*(1-n),g:i*(1-n),b:i*(1-n)}}return r.mode="rgb",void 0!==o&&(r.alpha=o),r}},parse:["--hsi"],serialize:"--hsi",fromMode:{rgb:function(e){var t=e.r,n=e.g,i=e.b,o=e.alpha;void 0===t&&(t=0),void 0===n&&(n=0),void 0===i&&(i=0);var r=Math.max(t,n,i),a=Math.min(t,n,i),l={mode:"hsi",s:t+n+i===0?0:1-3*a/(t+n+i),i:(t+n+i)/3};return r-a!=0&&(l.h=60*(r===t?(n-i)/(r-a)+6*(n1){var o=n+i;n/=o,i/=o}return ky({h:t,s:1===i?1:1-n/(1-i),v:1-i,alpha:e.alpha})}},fromMode:{rgb:function(e){var t=wy(e);if(void 0!==t){var n=void 0!==t.s?t.s:0,i=void 0!==t.v?t.v:0,o={mode:"hwb",w:(1-n)*i,b:1-i};return void 0!==t.h&&(o.h=t.h),void 0!==t.alpha&&(o.alpha=t.alpha),o}}},channels:["h","w","b","alpha"],ranges:{h:[0,360]},gamut:"rgb",parse:[function(e,t){if(t&&"hwb"===t[0]){var n={mode:"hwb"},i=Fr(t,5),o=i[1],r=i[2],a=i[3],l=i[4];if(o.type!==l_.None){if(o.type===l_.Percentage)return;n.h=o.value}if(r.type!==l_.None){if(r.type===l_.Hue)return;n.w=r.value/100}if(a.type!==l_.None){if(a.type===l_.Hue)return;n.b=a.value/100}return l.type!==l_.None&&(n.alpha=Math.min(1,Math.max(0,l.type===l_.Number?l.value:l.value/100))),n}}],serialize:function(e){return"hwb(".concat(void 0!==e.h?e.h:"none"," ").concat(void 0!==e.w?100*e.w+"%":"none"," ").concat(void 0!==e.b?100*e.b+"%":"none").concat(e.alpha<1?" / ".concat(e.alpha):"",")")},interpolate:{h:{use:b_,fixup:N_},w:b_,b:b_,alpha:{use:b_,fixup:k_}},difference:{h:function(e,t){if(void 0===e.h||void 0===t.h)return 0;var n=L_(e.h),i=L_(t.h);return Math.abs(i-n)>180?n-(i-360*Math.sign(i-n)):i-n}},average:{h:F_}},Cy=.1593017578125,Ay=78.84375,Sy=.8359375,Ty=18.8515625,Iy=18.6875;function Oy(e){if(e<0)return 0;var t=Math.pow(e,1/Ay);return 1e4*Math.pow(Math.max(0,t-Sy)/(Ty-Iy*t),1/Cy)}function My(e){if(e<0)return 0;var t=Math.pow(e/1e4,Cy);return Math.pow((Sy+Ty*t)/(1+Iy*t),Ay)}var zy=function(e){return Math.max(e/203,0)},Py=function(e){var t=e.i,n=e.t,i=e.p,o=e.alpha;void 0===t&&(t=0),void 0===n&&(n=0),void 0===i&&(i=0);var r=Oy(t+.008609037037932761*n+.11102962500302593*i),a=Oy(t-.00860903703793275*n-.11102962500302599*i),l=Oy(t+.5600313357106791*n-.32062717498731885*i),s={mode:"xyz65",x:zy(2.070152218389422*r-1.3263473389671556*a+.2066510476294051*l),y:zy(.3647385209748074*r+.680566024947227*a-.0453045459220346*l),z:zy(-.049747207535812*r-.0492609666966138*a+1.1880659249923042*l)};return void 0!==o&&(s.alpha=o),s},Ly=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0;return Math.max(203*e,0)},Ny=function(e){var t=e.x,n=e.y,i=e.z,o=e.alpha,r=Ly(t),a=Ly(n),l=Ly(i),s=My(.3592832590121217*r+.6976051147779502*a-.0358915932320289*l),c=My(-.1920808463704995*r+1.1004767970374323*a+.0753748658519118*l),u=My(.0070797844607477*r+.0748396662186366*a+.8433265453898765*l),d={mode:"itp",i:.5*s+.5*c,t:1.61376953125*s-3.323486328125*c+1.709716796875*u,p:4.378173828125*s-4.24560546875*c-.132568359375*u};return void 0!==o&&(d.alpha=o),d},jy={mode:"itp",channels:["i","t","p","alpha"],parse:["--ictcp"],serialize:"--ictcp",toMode:{xyz65:Py,rgb:function(e){return z_(Py(e))}},fromMode:{xyz65:Ny,rgb:function(e){return Ny(I_(e))}},ranges:{i:[0,.581],t:[-.369,.272],p:[-.164,.331]},interpolate:{i:b_,t:b_,p:b_,alpha:{use:b_,fixup:k_}}},$y=function(e){if(e<0)return 0;var t=Math.pow(e/1e4,Cy);return Math.pow((Sy+Ty*t)/(1+Iy*t),134.03437499999998)},By=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0;return Math.max(203*e,0)},Ry=function(e){var t=e.x,n=e.y,i=e.z,o=e.alpha;t=By(t),n=By(n);var r=1.15*t-.15*(i=By(i)),a=.66*n+.34*t,l=$y(.41478972*r+.579999*a+.014648*i),s=$y(-.20151*r+1.120649*a+.0531008*i),c=$y(-.0166008*r+.2648*a+.6684799*i),u=(l+s)/2,d={mode:"jab",j:.44*u/(1-.56*u)-16295499532821565e-27,a:3.524*l-4.066708*s+.542708*c,b:.199076*l+1.096799*s-1.295875*c};return void 0!==o&&(d.alpha=o),d},Hy=16295499532821565e-27,Dy=function(e){if(e<0)return 0;var t=Math.pow(e,.007460772656268216);return 1e4*Math.pow((Sy-t)/(Iy*t-Ty),1/Cy)},Uy=function(e){return e/203},Vy=function(e){var t=e.j,n=e.a,i=e.b,o=e.alpha;void 0===t&&(t=0),void 0===n&&(n=0),void 0===i&&(i=0);var r=(t+Hy)/(.44+.56*(t+Hy)),a=Dy(r+.13860504*n+.058047316*i),l=Dy(r-.13860504*n-.058047316*i),s=Dy(r-.096019242*n-.8118919*i),c={mode:"xyz65",x:Uy(1.661373024652174*a-.914523081304348*l+.23136208173913045*s),y:Uy(-.3250758611844533*a+1.571847026732543*l-.21825383453227928*s),z:Uy(-.090982811*a-.31272829*l+1.5227666*s)};return void 0!==o&&(c.alpha=o),c},Fy=function(e){var t=Ry(I_(e));return e.r===e.b&&e.b===e.g&&(t.a=t.b=0),t},Gy=function(e){return z_(Vy(e))},Yy={mode:"jab",channels:["j","a","b","alpha"],parse:["--jzazbz"],serialize:"--jzazbz",fromMode:{rgb:Fy,xyz65:Ry},toMode:{rgb:Gy,xyz65:Vy},ranges:{j:[0,.222],a:[-.109,.129],b:[-.185,.134]},interpolate:{j:b_,a:b_,b:b_,alpha:{use:b_,fixup:k_}}},Ky=function(e){var t=e.j,n=e.a,i=e.b,o=e.alpha;void 0===n&&(n=0),void 0===i&&(i=0);var r=Math.sqrt(n*n+i*i),a={mode:"jch",j:t,c:r};return r&&(a.h=L_(180*Math.atan2(i,n)/Math.PI)),void 0!==o&&(a.alpha=o),a},Wy=function(e){var t=e.j,n=e.c,i=e.h,o=e.alpha;void 0===i&&(i=0);var r={mode:"jab",j:t,a:n?n*Math.cos(i/180*Math.PI):0,b:n?n*Math.sin(i/180*Math.PI):0};return void 0!==o&&(r.alpha=o),r},qy={mode:"jch",parse:["--jzczhz"],serialize:"--jzczhz",toMode:{jab:Wy,rgb:function(e){return Gy(Wy(e))}},fromMode:{rgb:function(e){return Ky(Fy(e))},jab:Ky},channels:["j","c","h","alpha"],ranges:{j:[0,.221],c:[0,.19],h:[0,360]},interpolate:{h:{use:b_,fixup:N_},c:b_,j:b_,alpha:{use:b_,fixup:k_}},difference:{h:V_},average:{h:F_}},Xy=Math.pow(29,3)/Math.pow(3,3),Zy=Math.pow(6,3)/Math.pow(29,3),Jy=function(e){return Math.pow(e,3)>Zy?Math.pow(e,3):(116*e-16)/Xy},Qy=function(e){var t=e.l,n=e.a,i=e.b,o=e.alpha;void 0===t&&(t=0),void 0===n&&(n=0),void 0===i&&(i=0);var r=(t+16)/116,a=r-i/200,l={mode:"xyz50",x:Jy(n/500+r)*X_,y:Jy(r)*Z_,z:Jy(a)*J_};return void 0!==o&&(l.alpha=o),l},eb=function(e){var t=e.x,n=e.y,i=e.z,o=e.alpha;void 0===t&&(t=0),void 0===n&&(n=0),void 0===i&&(i=0);var r=M_({r:3.1341359569958707*t-1.6173863321612538*n-.4906619460083532*i,g:-.978795502912089*t+1.916254567259524*n+.03344273116131949*i,b:.07195537988411677*t-.2289768264158322*n+1.405386058324125*i});return void 0!==o&&(r.alpha=o),r},tb=function(e){return eb(Qy(e))},nb=function(e){var t=T_(e),n=t.r,i=t.g,o=t.b,r=t.alpha,a={mode:"xyz50",x:.436065742824811*n+.3851514688337912*i+.14307845442264197*o,y:.22249319175623702*n+.7168870538238823*i+.06061979053616537*o,z:.013923904500943465*n+.09708128566574634*i+.7140993584005155*o};return void 0!==r&&(a.alpha=r),a},ib=function(e){return e>Zy?Math.cbrt(e):(Xy*e+16)/116},ob=function(e){var t=e.x,n=e.y,i=e.z,o=e.alpha;void 0===t&&(t=0),void 0===n&&(n=0),void 0===i&&(i=0);var r=ib(t/X_),a=ib(n/Z_),l={mode:"lab",l:116*a-16,a:500*(r-a),b:200*(a-ib(i/J_))};return void 0!==o&&(l.alpha=o),l},rb=function(e){var t=ob(nb(e));return e.r===e.b&&e.b===e.g&&(t.a=t.b=0),t};var ab={mode:"lab",toMode:{xyz50:Qy,rgb:tb},fromMode:{xyz50:ob,rgb:rb},channels:["l","a","b","alpha"],ranges:{l:[0,100],a:[-125,125],b:[-125,125]},parse:[function(e,t){if(t&&"lab"===t[0]){var n={mode:"lab"},i=Fr(t,5),o=i[1],r=i[2],a=i[3],l=i[4];if(o.type!==l_.Hue&&r.type!==l_.Hue&&a.type!==l_.Hue)return o.type!==l_.None&&(n.l=Math.min(Math.max(0,o.value),100)),r.type!==l_.None&&(n.a=r.type===l_.Number?r.value:125*r.value/100),a.type!==l_.None&&(n.b=a.type===l_.Number?a.value:125*a.value/100),l.type!==l_.None&&(n.alpha=Math.min(1,Math.max(0,l.type===l_.Number?l.value:l.value/100))),n}}],serialize:function(e){return"lab(".concat(void 0!==e.l?e.l:"none"," ").concat(void 0!==e.a?e.a:"none"," ").concat(void 0!==e.b?e.b:"none").concat(e.alpha<1?" / ".concat(e.alpha):"",")")},interpolate:{l:b_,a:b_,b:b_,alpha:{use:b_,fixup:k_}}},lb=Br(Br({},ab),{},{mode:"lab65",parse:["--lab-d65"],serialize:"--lab-d65",toMode:{xyz65:iy,rgb:oy},fromMode:{xyz65:ay,rgb:ly},ranges:{l:[0,100],a:[-125,125],b:[-125,125]}});var sb={mode:"lch",toMode:{lab:K_,rgb:function(e){return tb(K_(e))}},fromMode:{rgb:function(e){return Y_(rb(e))},lab:Y_},channels:["l","c","h","alpha"],ranges:{l:[0,100],c:[0,150],h:[0,360]},parse:[function(e,t){if(t&&"lch"===t[0]){var n={mode:"lch"},i=Fr(t,5),o=i[1],r=i[2],a=i[3],l=i[4];if(o.type!==l_.None){if(o.type===l_.Hue)return;n.l=Math.min(Math.max(0,o.value),100)}if(r.type!==l_.None&&(n.c=Math.max(0,r.type===l_.Number?r.value:150*r.value/100)),a.type!==l_.None){if(a.type===l_.Percentage)return;n.h=a.value}return l.type!==l_.None&&(n.alpha=Math.min(1,Math.max(0,l.type===l_.Number?l.value:l.value/100))),n}}],serialize:function(e){return"lch(".concat(void 0!==e.l?e.l:"none"," ").concat(void 0!==e.c?e.c:"none"," ").concat(void 0!==e.h?e.h:"none").concat(e.alpha<1?" / ".concat(e.alpha):"",")")},interpolate:{h:{use:b_,fixup:N_},c:b_,l:b_,alpha:{use:b_,fixup:k_}},difference:{h:V_},average:{h:F_}},cb=Br(Br({},sb),{},{mode:"lch65",parse:["--lch-d65"],serialize:"--lch-d65",toMode:{lab65:function(e){return K_(e,"lab65")},rgb:function(e){return oy(K_(e,"lab65"))}},fromMode:{rgb:function(e){return Y_(ly(e),"lch65")},lab65:function(e){return Y_(e,"lch65")}},ranges:{l:[0,100],c:[0,150],h:[0,360]}}),ub=function(e){var t=e.l,n=e.u,i=e.v,o=e.alpha;void 0===n&&(n=0),void 0===i&&(i=0);var r=Math.sqrt(n*n+i*i),a={mode:"lchuv",l:t,c:r};return r&&(a.h=L_(180*Math.atan2(i,n)/Math.PI)),void 0!==o&&(a.alpha=o),a},db=function(e){var t=e.l,n=e.c,i=e.h,o=e.alpha;void 0===i&&(i=0);var r={mode:"luv",l:t,u:n?n*Math.cos(i/180*Math.PI):0,v:n?n*Math.sin(i/180*Math.PI):0};return void 0!==o&&(r.alpha=o),r},hb=function(e,t,n){return 4*e/(e+15*t+3*n)},pb=function(e,t,n){return 9*t/(e+15*t+3*n)},fb=hb(X_,Z_,J_),mb=pb(X_,Z_,J_),vb=function(e){var t=e.x,n=e.y,i=e.z,o=e.alpha;void 0===t&&(t=0),void 0===n&&(n=0),void 0===i&&(i=0);var r,a=(r=n/Z_)<=Zy?Xy*r:116*Math.cbrt(r)-16,l=hb(t,n,i),s=pb(t,n,i);isFinite(l)&&isFinite(s)?(l=13*a*(l-fb),s=13*a*(s-mb)):a=l=s=0;var c={mode:"luv",l:a,u:l,v:s};return void 0!==o&&(c.alpha=o),c},gb=function(e,t,n){return 4*e/(e+15*t+3*n)}(X_,Z_,J_),_b=function(e,t,n){return 9*t/(e+15*t+3*n)}(X_,Z_,J_),yb=function(e){var t=e.l,n=e.u,i=e.v,o=e.alpha;if(void 0===t&&(t=0),0===t)return{mode:"xyz50",x:0,y:0,z:0};void 0===n&&(n=0),void 0===i&&(i=0);var r=n/(13*t)+gb,a=i/(13*t)+_b,l=Z_*(t<=8?t/Xy:Math.pow((t+16)/116,3)),s={mode:"xyz50",x:l*(9*r)/(4*a),y:l,z:l*(12-3*r-20*a)/(4*a)};return void 0!==o&&(s.alpha=o),s},bb={mode:"lchuv",toMode:{luv:db,rgb:function(e){return eb(yb(db(e)))}},fromMode:{rgb:function(e){return ub(vb(nb(e)))},luv:ub},channels:["l","c","h","alpha"],parse:["--lchuv"],serialize:"--lchuv",ranges:{l:[0,100],c:[0,176.956],h:[0,360]},interpolate:{h:{use:b_,fixup:N_},c:b_,l:b_,alpha:{use:b_,fixup:k_}},difference:{h:V_},average:{h:F_}},kb=Br(Br({},w_),{},{mode:"lrgb",toMode:{rgb:M_},fromMode:{rgb:T_},parse:["srgb-linear"],serialize:"srgb-linear"}),wb={mode:"luv",toMode:{xyz50:yb,rgb:function(e){return eb(yb(e))}},fromMode:{xyz50:vb,rgb:function(e){return vb(nb(e))}},channels:["l","u","v","alpha"],parse:["--luv"],serialize:"--luv",ranges:{l:[0,100],u:[-84.936,175.042],v:[-125.882,87.243]},interpolate:{l:b_,u:b_,v:b_,alpha:{use:b_,fixup:k_}}},xb=function(e){var t=e.r,n=e.g,i=e.b,o=e.alpha;void 0===t&&(t=0),void 0===n&&(n=0),void 0===i&&(i=0);var r=Math.cbrt(.412221469470763*t+.5363325372617348*n+.0514459932675022*i),a=Math.cbrt(.2119034958178252*t+.6806995506452344*n+.1073969535369406*i),l=Math.cbrt(.0883024591900564*t+.2817188391361215*n+.6299787016738222*i),s={mode:"oklab",l:.210454268309314*r+.7936177747023054*a-.0040720430116193*l,a:1.9779985324311684*r-2.42859224204858*a+.450593709617411*l,b:.0259040424655478*r+.7827717124575296*a-.8086757549230774*l};return void 0!==o&&(s.alpha=o),s},Eb=function(e){var t=xb(T_(e));return e.r===e.b&&e.b===e.g&&(t.a=t.b=0),t},Cb=function(e){var t=e.l,n=e.a,i=e.b,o=e.alpha;void 0===t&&(t=0),void 0===n&&(n=0),void 0===i&&(i=0);var r=Math.pow(t+.3963377773761749*n+.2158037573099136*i,3),a=Math.pow(t-.1055613458156586*n-.0638541728258133*i,3),l=Math.pow(t-.0894841775298119*n-1.2914855480194092*i,3),s={mode:"lrgb",r:4.076741636075957*r-3.3077115392580616*a+.2309699031821044*l,g:-1.2684379732850317*r+2.6097573492876887*a-.3413193760026573*l,b:-.0041960761386756*r-.7034186179359362*a+1.7076146940746117*l};return void 0!==o&&(s.alpha=o),s},Ab=function(e){return M_(Cb(e))};function Sb(e){var t=.206,n=1.206/1.03;return.5*(n*e-t+Math.sqrt((n*e-t)*(n*e-t)+.12*n*e))}function Tb(e){return(e*e+.206*e)/(1.170873786407767*(e+.03))}function Ib(e,t){var n=function(e,t){var n,i,o,r,a,l,s,c;-1.88170328*e-.80936493*t>1?(n=1.19086277,i=1.76576728,o=.59662641,r=.75515197,a=.56771245,l=4.0767416621,s=-3.3077115913,c=.2309699292):1.81444104*e-1.19445276*t>1?(n=.73956515,i=-.45954404,o=.08285427,r=.1254107,a=.14503204,l=-1.2684380046,s=2.6097574011,c=-.3413193965):(n=1.35733652,i=-.00915799,o=-1.1513021,r=-.50559606,a=.00692167,l=-.0041960863,s=-.7034186147,c=1.707614701);var u=n+i*e+o*t+r*e*e+a*e*t,d=.3963377774*e+.2158037573*t,h=-.1055613458*e-.0638541728*t,p=-.0894841775*e-1.291485548*t,f=1+u*d,m=1+u*h,v=1+u*p,g=l*(f*f*f)+s*(m*m*m)+c*(v*v*v),_=l*(3*d*f*f)+s*(3*h*m*m)+c*(3*p*v*v);return u-g*_/(_*_-.5*g*(l*(6*d*d*f)+s*(6*h*h*m)+c*(6*p*p*v)))}(e,t),i=Cb({l:1,a:n*e,b:n*t}),o=Math.cbrt(1/Math.max(i.r,i.g,i.b));return[o,o*n]}function Ob(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;n||(n=Ib(e,t));var i=n[0],o=n[1];return[o/i,o/(1-i)]}function Mb(e,t,n){var i=Ib(t,n),o=function(e,t,n,i,o){var r,a=arguments.length>5&&void 0!==arguments[5]?arguments[5]:null;if(a||(a=Ib(e,t)),(n-o)*a[1]-(a[0]-o)*i<=0)r=a[1]*o/(i*a[0]+a[1]*(o-n));else{var l=n-o,s=.3963377774*e+.2158037573*t,c=-.1055613458*e-.0638541728*t,u=-.0894841775*e-1.291485548*t,d=l+i*s,h=l+i*c,p=l+i*u,f=o*(1-(r=a[1]*(o-1)/(i*(a[0]-1)+a[1]*(o-n))))+r*n,m=r*i,v=f+m*s,g=f+m*c,_=f+m*u,y=v*v*v,b=g*g*g,k=_*_*_,w=3*d*v*v,x=3*h*g*g,E=3*p*_*_,C=6*d*d*v,A=6*h*h*g,S=6*p*p*_,T=4.0767416621*y-3.3077115913*b+.2309699292*k-1,I=4.0767416621*w-3.3077115913*x+.2309699292*E,O=I/(I*I-.5*T*(4.0767416621*C-3.3077115913*A+.2309699292*S)),M=-T*O,z=-1.2684380046*y+2.6097574011*b-.3413193965*k-1,P=-1.2684380046*w+2.6097574011*x-.3413193965*E,L=P/(P*P-.5*z*(-1.2684380046*C+2.6097574011*A-.3413193965*S)),N=-z*L,j=-.0041960863*y-.7034186147*b+1.707614701*k-1,$=-.0041960863*w-.7034186147*x+1.707614701*E,B=$/($*$-.5*j*(-.0041960863*C-.7034186147*A+1.707614701*S)),R=-j*B;M=O>=0?M:1e6,N=L>=0?N:1e6,R=B>=0?R:1e6,r+=Math.min(M,Math.min(N,R))}return r}(t,n,e,1,e,i),r=Ob(t,n,i),a=e*(.11516993+1/(7.4477897+4.1590124*n+t*(1.75198401*n-2.19557347+t*(-2.13704948-10.02301043*n+t*(5.38770819*n-4.24894561+4.69891013*t))))),l=(1-e)*(.11239642+1/(1.6132032-.68124379*n+t*(.40370612+.90148123*n+t*(.6122399*n-.27087943+t*(.00299215-.45399568*n-.14661872*t))))),s=.9*(o/Math.min(e*r[0],(1-e)*r[1]))*Math.sqrt(Math.sqrt(1/(1/(a*a*a*a)+1/(l*l*l*l))));return a=.4*e,l=.8*(1-e),[Math.sqrt(1/(1/(a*a)+1/(l*l))),s,o]}function zb(e){var t=void 0!==e.l?e.l:0,n=void 0!==e.a?e.a:0,i=void 0!==e.b?e.b:0,o={mode:"okhsl",l:Sb(t)};void 0!==e.alpha&&(o.alpha=e.alpha);var r=Math.sqrt(n*n+i*i);if(!r)return o.s=0,o;var a,l=Fr(Mb(t,n/r,i/r),3),s=l[0],c=l[1],u=l[2];if(r=1/512?Math.sign(e)*Math.pow(t,1/1.8):16*e},Fb=function(e){var t=e.x,n=e.y,i=e.z,o=e.alpha;void 0===t&&(t=0),void 0===n&&(n=0),void 0===i&&(i=0);var r={mode:"prophoto",r:Vb(1.3457868816471585*t-.2555720873797946*n-.0511018649755453*i),g:Vb(-.5446307051249019*t+1.5082477428451466*n+.0205274474364214*i),b:Vb(0*t+0*n+1.2119675456389452*i)};return void 0!==o&&(r.alpha=o),r},Gb=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,t=Math.abs(e);return t>=16/512?Math.sign(e)*Math.pow(t,1.8):e/16},Yb=function(e){var t=Gb(e.r),n=Gb(e.g),i=Gb(e.b),o={mode:"xyz50",x:.7977666449006423*t+.1351812974005331*n+.0313477341283922*i,y:.2880748288194013*t+.7118352342418731*n+899369387256e-16*i,z:0*t+0*n+.8251046025104602*i};return void 0!==e.alpha&&(o.alpha=e.alpha),o},Kb=Br(Br({},w_),{},{mode:"prophoto",parse:["prophoto-rgb"],serialize:"prophoto-rgb",fromMode:{xyz50:Fb,rgb:function(e){return Fb(nb(e))}},toMode:{xyz50:Yb,rgb:function(e){return eb(Yb(e))}}}),Wb=1.09929682680944,qb=function(e){var t=Math.abs(e);return t>.018053968510807?(Math.sign(e)||1)*(Wb*Math.pow(t,.45)-(Wb-1)):4.5*e},Xb=function(e){var t=e.x,n=e.y,i=e.z,o=e.alpha;void 0===t&&(t=0),void 0===n&&(n=0),void 0===i&&(i=0);var r={mode:"rec2020",r:qb(1.7166511879712683*t-.3556707837763925*n-.2533662813736599*i),g:qb(-.6666843518324893*t+1.6164812366349395*n+.0157685458139111*i),b:qb(.0176398574453108*t-.0427706132578085*n+.9421031212354739*i)};return void 0!==o&&(r.alpha=o),r},Zb=1.09929682680944,Jb=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,t=Math.abs(e);return t<.08124285829863151?e/4.5:(Math.sign(e)||1)*Math.pow((t+Zb-1)/Zb,1/.45)},Qb=function(e){var t=Jb(e.r),n=Jb(e.g),i=Jb(e.b),o={mode:"xyz65",x:.6369580483012911*t+.1446169035862083*n+.1688809751641721*i,y:.262700212011267*t+.6779980715188708*n+.059301716469862*i,z:0*t+.0280726930490874*n+1.0609850577107909*i};return void 0!==e.alpha&&(o.alpha=e.alpha),o},ek=Br(Br({},w_),{},{mode:"rec2020",fromMode:{xyz65:Xb,rgb:function(e){return Xb(I_(e))}},toMode:{xyz65:Qb,rgb:function(e){return z_(Qb(e))}},parse:["rec2020"],serialize:"rec2020"}),tk=.0037930732552754493,nk=Math.cbrt(tk),ik=function(e){return Math.cbrt(e)-nk},ok=function(e){return Math.pow(e+nk,3)},rk={mode:"xyb",channels:["x","y","b","alpha"],parse:["--xyb"],serialize:"--xyb",toMode:{rgb:function(e){var t=e.x,n=e.y,i=e.b,o=e.alpha;void 0===t&&(t=0),void 0===n&&(n=0),void 0===i&&(i=0);var r=ok(t+n)-tk,a=ok(n-t)-tk,l=ok(i+n)-tk,s=M_({r:11.031566904639861*r-9.866943908131562*a-.16462299650829934*l,g:-3.2541473810744237*r+4.418770377582723*a-.16462299650829934*l,b:-3.6588512867136815*r+2.7129230459360922*a+1.9459282407775895*l});return void 0!==o&&(s.alpha=o),s}},fromMode:{rgb:function(e){var t=T_(e),n=t.r,i=t.g,o=t.b,r=t.alpha,a=ik(.3*n+.622*i+.078*o+tk),l=ik(.23*n+.692*i+.078*o+tk),s={mode:"xyb",x:(a-l)/2,y:(a+l)/2,b:ik(.2434226892454782*n+.2047674442449682*i+.5518098665095535*o+tk)-(a+l)/2};return void 0!==r&&(s.alpha=r),s}},ranges:{x:[-.0154,.0281],y:[0,.8453],b:[-.2778,.388]},interpolate:{x:b_,y:b_,b:b_,alpha:{use:b_,fixup:k_}}},ak={mode:"xyz50",parse:["xyz-d50"],serialize:"xyz-d50",toMode:{rgb:eb,lab:ob},fromMode:{rgb:nb,lab:Qy},channels:["x","y","z","alpha"],ranges:{x:[0,.964],y:[0,.999],z:[0,.825]},interpolate:{x:b_,y:b_,z:b_,alpha:{use:b_,fixup:k_}}},lk={mode:"xyz65",toMode:{rgb:z_,xyz50:function(e){var t=e.x,n=e.y,i=e.z,o=e.alpha;void 0===t&&(t=0),void 0===n&&(n=0),void 0===i&&(i=0);var r={mode:"xyz50",x:1.0479298208405488*t+.0229467933410191*n-.0501922295431356*i,y:.0296278156881593*t+.990434484573249*n-.0170738250293851*i,z:-.0092430581525912*t+.0150551448965779*n+.7518742899580008*i};return void 0!==o&&(r.alpha=o),r}},fromMode:{rgb:I_,xyz50:function(e){var t=e.x,n=e.y,i=e.z,o=e.alpha;void 0===t&&(t=0),void 0===n&&(n=0),void 0===i&&(i=0);var r={mode:"xyz65",x:.9554734527042182*t-.0230985368742614*n+.0632593086610217*i,y:-.0283697069632081*t+1.0099954580058226*n+.021041398966943*i,z:.0123140016883199*t-.0205076964334779*n+1.3303659366080753*i};return void 0!==o&&(r.alpha=o),r}},ranges:{x:[0,.95],y:[0,1],z:[0,1.088]},channels:["x","y","z","alpha"],parse:["xyz","xyz-d65"],serialize:"xyz-d65",interpolate:{x:b_,y:b_,z:b_,alpha:{use:b_,fixup:k_}}},sk={mode:"yiq",toMode:{rgb:function(e){var t=e.y,n=e.i,i=e.q,o=e.alpha;void 0===t&&(t=0),void 0===n&&(n=0),void 0===i&&(i=0);var r={mode:"rgb",r:t+.95608445*n+.6208885*i,g:t-.27137664*n-.6486059*i,b:t-1.10561724*n+1.70250126*i};return void 0!==o&&(r.alpha=o),r}},fromMode:{rgb:function(e){var t=e.r,n=e.g,i=e.b,o=e.alpha;void 0===t&&(t=0),void 0===n&&(n=0),void 0===i&&(i=0);var r={mode:"yiq",y:.29889531*t+.58662247*n+.11448223*i,i:.59597799*t-.2741761*n-.32180189*i,q:.21147017*t-.52261711*n+.31114694*i};return void 0!==o&&(r.alpha=o),r}},channels:["y","i","q","alpha"],parse:["--yiq"],serialize:"--yiq",ranges:{i:[-.595,.595],q:[-.522,.522]},interpolate:{y:b_,i:b_,q:b_,alpha:{use:b_,fixup:k_}}};i_(P_),i_(G_),i_(vy),i_(gy),i_(_y),i_(by);var ck=i_(xy);i_(Ey),i_(jy),i_(Yy),i_(qy),i_(ab),i_(lb);var uk=i_(sb);i_(cb),i_(bb),i_(kb),i_(wb),i_(Lb),i_($b),i_(Bb),i_(Rb),i_(Ub),i_(Kb),i_(ek);var dk=i_(w_);i_(rk),i_(ak),i_(lk),i_(sk);var hk=["primary","accent","red","pink","purple","deep-purple","indigo","blue","light-blue","cyan","teal","green","light-green","lime","yellow","amber","orange","deep-orange","brown","light-grey","grey","dark-grey","blue-grey","black","white","disabled"];function pk(e){if("primary"===e||"accent"===e)return"var(--rgb-".concat(e,"-color)");if(hk.includes(e))return"var(--rgb-".concat(e,")");if(e.startsWith("#"))try{var t=dk(e);if(t){var n=t.r,i=t.g,o=t.b;return"".concat(Math.round(255*n),", ").concat(Math.round(255*i),", ").concat(Math.round(255*o))}return""}catch(e){return""}return e}var fk=Aa(x||(x=Pr(["\n --default-red: 244, 67, 54;\n --default-pink: 233, 30, 99;\n --default-purple: 146, 107, 199;\n --default-deep-purple: 110, 65, 171;\n --default-indigo: 63, 81, 181;\n --default-blue: 33, 150, 243;\n --default-light-blue: 3, 169, 244;\n --default-cyan: 0, 188, 212;\n --default-teal: 0, 150, 136;\n --default-green: 76, 175, 80;\n --default-light-green: 139, 195, 74;\n --default-lime: 205, 220, 57;\n --default-yellow: 255, 235, 59;\n --default-amber: 255, 193, 7;\n --default-orange: 255, 152, 0;\n --default-deep-orange: 255, 111, 34;\n --default-brown: 121, 85, 72;\n --default-light-grey: 189, 189, 189;\n --default-grey: 158, 158, 158;\n --default-dark-grey: 96, 96, 96;\n --default-blue-grey: 96, 125, 139;\n --default-black: 0, 0, 0;\n --default-white: 255, 255, 255;\n --default-disabled: 189, 189, 189;\n"]))),mk=Aa(E||(E=Pr(["\n --default-disabled: 111, 111, 111;\n"]))),vk=function(e){function t(){var e;return sa(this,t),(e=Zr(this,t,arguments)).label="",e.configValue="",e}return ea(t,Sl),ua(t,[{key:"_selectChanged",value:function(e){var t=e.target.value;t&&this.dispatchEvent(new CustomEvent("value-changed",{detail:{value:"default"!==t?t:""}}))}},{key:"render",value:function(){var e=this,t=Uh(this.hass);return cl(C||(C=Pr(["\n ','\n \n ',"\n \n ","\n \n "])),Boolean(this.value),this.label,this.configValue,this._selectChanged,(function(e){return e.stopPropagation()}),this.value||"default",this.renderColorCircle(this.value||"grey"),t("editor.form.color_picker.values.default"),hk.map((function(t){return cl(A||(A=Pr(["\n \n ','\n ',"\n \n "])),t,function(e){return e.split("-").map((function(e){return function(e){return e.charAt(0).toUpperCase()+e.slice(1)}(e)})).join(" ")}(t),e.renderColorCircle(t))})))}},{key:"renderColorCircle",value:function(e){return cl(S||(S=Pr(['\n \n \n ',"\n \n ","\n \n "])),this.label,this.configValue,this._selectChanged,(function(e){return e.stopPropagation()}),this.value||"default",e("editor.form.icon_type_picker.values.default"),bk.map((function(t){return cl(L||(L=Pr(["\n \n ","\n \n "])),t,e("editor.form.icon_type_picker.values.".concat(t))||function(e){return e.charAt(0).toUpperCase()+e.slice(1)}(t))})))}}],[{key:"styles",get:function(){return Aa(N||(N=Pr(["\n mushroom-select {\n width: 100%;\n }\n "])))}}])}();ga([zl()],xk.prototype,"label",void 0),ga([zl()],xk.prototype,"value",void 0),ga([zl()],xk.prototype,"configValue",void 0),ga([zl()],xk.prototype,"hass",void 0),xk=ga([Il("mushroom-icon-type-picker")],xk);var Ek=function(e){function t(){return sa(this,t),Zr(this,t,arguments)}return ea(t,Sl),ua(t,[{key:"render",value:function(){return cl(j||(j=Pr(["\n \n "])),this.hass,this.label,this.value,this._valueChanged)}},{key:"_valueChanged",value:function(e){Dl(this,"value-changed",{value:e.detail.value||void 0})}}])}();ga([zl()],Ek.prototype,"hass",void 0),ga([zl()],Ek.prototype,"selector",void 0),ga([zl()],Ek.prototype,"value",void 0),ga([zl()],Ek.prototype,"label",void 0),Ek=ga([Il("ha-selector-mush_icon_type")],Ek);var Ck=function(e){function t(){var e;return sa(this,t),(e=Zr(this,t,arguments)).label="",e.configValue="",e}return ea(t,Sl),ua(t,[{key:"_selectChanged",value:function(e){var t=e.target.value;t&&this.dispatchEvent(new CustomEvent("value-changed",{detail:{value:"default"!==t?t:""}}))}},{key:"render",value:function(){var e,t=Uh(this.hass);return cl($||($=Pr(["\n \n ',"\n \n ","\n \n "])),this.label,this.configValue,this._selectChanged,(function(e){return e.stopPropagation()}),this.value||"default",t("editor.form.info_picker.values.default"),(null!==(e=this.infos)&&void 0!==e?e:yk).map((function(e){return cl(B||(B=Pr(["\n \n ","\n \n "])),e,t("editor.form.info_picker.values.".concat(e))||function(e){return e.charAt(0).toUpperCase()+e.slice(1)}(e))})))}}],[{key:"styles",get:function(){return Aa(R||(R=Pr(["\n mushroom-select {\n width: 100%;\n }\n "])))}}])}();ga([zl()],Ck.prototype,"label",void 0),ga([zl()],Ck.prototype,"value",void 0),ga([zl()],Ck.prototype,"configValue",void 0),ga([zl()],Ck.prototype,"infos",void 0),ga([zl()],Ck.prototype,"hass",void 0),Ck=ga([Il("mushroom-info-picker")],Ck);var Ak=function(e){function t(){return sa(this,t),Zr(this,t,arguments)}return ea(t,Sl),ua(t,[{key:"render",value:function(){return cl(H||(H=Pr(["\n \n "])),this.hass,this.selector.mush_info.infos,this.label,this.value,this._valueChanged)}},{key:"_valueChanged",value:function(e){Dl(this,"value-changed",{value:e.detail.value||void 0})}}])}();ga([zl()],Ak.prototype,"hass",void 0),ga([zl()],Ak.prototype,"selector",void 0),ga([zl()],Ak.prototype,"value",void 0),ga([zl()],Ak.prototype,"label",void 0),Ak=ga([Il("ha-selector-mush_info")],Ak);var Sk=["default","horizontal","vertical"],Tk={default:"mdi:card-text-outline",vertical:"mdi:focus-field-vertical",horizontal:"mdi:focus-field-horizontal"},Ik=function(e){function t(){var e;return sa(this,t),(e=Zr(this,t,arguments)).label="",e.configValue="",e}return ea(t,Sl),ua(t,[{key:"_selectChanged",value:function(e){var t=e.target.value;t&&this.dispatchEvent(new CustomEvent("value-changed",{detail:{value:"default"!==t?t:""}}))}},{key:"render",value:function(){var e=Uh(this.hass),t=this.value||"default";return cl(D||(D=Pr(["\n \n ','\n \n \n ',"\n ","\n \n "])),null!==(e=this.primary)&&void 0!==e?e:"",this.secondary?cl(le||(le=Pr(['',""])),this.multiline_secondary?" multiline_secondary":"",this.secondary):hl)}}],[{key:"styles",get:function(){return Aa(se||(se=Pr(["\n .container {\n min-width: 0;\n flex: 1;\n display: flex;\n flex-direction: column;\n }\n .primary {\n font-weight: var(--card-primary-font-weight);\n font-size: var(--card-primary-font-size);\n line-height: var(--card-primary-line-height);\n color: var(--card-primary-color);\n letter-spacing: var(--card-primary-letter-spacing);\n text-overflow: ellipsis;\n overflow: hidden;\n white-space: nowrap;\n }\n .secondary {\n font-weight: var(--card-secondary-font-weight);\n font-size: var(--card-secondary-font-size);\n line-height: var(--card-secondary-line-height);\n color: var(--card-secondary-color);\n letter-spacing: var(--card-secondary-letter-spacing);\n text-overflow: ellipsis;\n overflow: hidden;\n white-space: nowrap;\n }\n .multiline_secondary {\n white-space: pre-wrap;\n }\n "])))}}])}();ga([zl({attribute:!1})],Vk.prototype,"primary",void 0),ga([zl({attribute:!1})],Vk.prototype,"secondary",void 0),ga([zl({type:Boolean})],Vk.prototype,"multiline_secondary",void 0),Vk=ga([Il("mushroom-state-info")],Vk);var Fk=function(e){function t(){return sa(this,t),Zr(this,t,arguments)}return ea(t,Sl),ua(t,[{key:"render",value:function(){var e,t,n,i;return cl(ce||(ce=Pr(["\n \n ","\n ","\n \n "])),Mk({container:!0,vertical:"vertical"===(null===(e=this.appearance)||void 0===e?void 0:e.layout)}),"none"!==(null===(t=this.appearance)||void 0===t?void 0:t.icon_type)?cl(ue||(ue=Pr(['\n
\n \n \n
\n ']))):hl,"none"!==(null===(n=this.appearance)||void 0===n?void 0:n.primary_info)||"none"!==(null===(i=this.appearance)||void 0===i?void 0:i.secondary_info)?cl(de||(de=Pr(['\n
\n \n
\n ']))):hl)}}],[{key:"styles",get:function(){return Aa(he||(he=Pr(['\n :host {\n display: block;\n height: 100%;\n }\n .container {\n height: 100%;\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n padding: var(--spacing);\n gap: var(--spacing);\n }\n .icon {\n position: relative;\n }\n .icon ::slotted(*[slot="badge"]) {\n position: absolute;\n top: -3px;\n right: -3px;\n }\n :host([rtl]) .icon ::slotted(*[slot="badge"]) {\n right: initial;\n left: -3px;\n }\n .info {\n min-width: 0;\n width: 100%;\n display: flex;\n flex-direction: column;\n }\n .container.vertical {\n flex-direction: column;\n }\n .container.vertical .info {\n text-align: center;\n }\n '])))}}])}();function Gk(e){var t,n,i,o,r;return{layout:null!==(t=e.layout)&&void 0!==t?t:Yk(e),fill_container:null!==(n=e.fill_container)&&void 0!==n&&n,primary_info:null!==(i=e.primary_info)&&void 0!==i?i:Wk(e),secondary_info:null!==(o=e.secondary_info)&&void 0!==o?o:qk(e),icon_type:null!==(r=e.icon_type)&&void 0!==r?r:Kk(e)}}function Yk(e){return e.vertical?"vertical":"default"}function Kk(e){return e.hide_icon?"none":e.use_entity_picture||e.use_media_artwork?"entity-picture":"icon"}function Wk(e){return e.hide_name?"none":"name"}function qk(e){return e.hide_state?"none":"state"}ga([zl()],Fk.prototype,"appearance",void 0),Fk=ga([Il("mushroom-state-item")],Fk);var Xk=function(e){function t(){var e;return sa(this,t),(e=Zr(this,t,arguments)).picture_url="",e}return ea(t,Sl),ua(t,[{key:"render",value:function(){return cl(pe||(pe=Pr(["\n
\n \n \n \n \n \n ']))):hl}},{key:"renderStateInfo",value:function(e,t,n,i){var o=this.hass.formatEntityState(e),r=null!=i?i:o,a=kk(t.primary_info,n,r,e,this.hass),l=kk(t.secondary_info,n,r,e,this.hass);return cl(we||(we=Pr(['\n =0}iw({type:rw,name:"Mushroom Alarm Control Panel Card",description:"Card for alarm control panel"});var dw=function(e){function t(){return sa(this,t),Zr(this,t,arguments)}return ea(t,tw),ua(t,[{key:"hasControls",get:function(){var e,t;return Boolean(null===(t=null===(e=this._config)||void 0===e?void 0:e.states)||void 0===t?void 0:t.length)}},{key:"_onTap",value:function(e,t){e.stopPropagation(),Lk(this,this.hass,this._stateObj,t)}},{key:"_handleAction",value:function(e){Xs(this,this.hass,this._config,e.detail.action)}},{key:"render",value:function(){var e=this;if(!this.hass||!this._config||!this._config.entity)return hl;var t=this._stateObj;if(!t)return this.renderNotFound(this._config);var n=this._config.name||t.attributes.friendly_name||"",i=this._config.icon,o=Gk(this._config),r=wk(t,o.icon_type),a=this._config.states&&this._config.states.length>0?function(e){return"disarmed"===e.state}(t)?this._config.states.map((function(e){return{mode:e}})):[{mode:"disarmed"}]:[],l=function(e){return As!==e.state}(t),s=bs(this.hass);return cl(Ee||(Ee=Pr(["\n \n \n \n ","\n ","\n ",";\n \n ","\n \n \n "])),Mk({"fill-container":o.fill_container}),o,s,s,o,this._handleAction,qs({hasHold:Zs(this._config.hold_action),hasDoubleClick:Zs(this._config.double_tap_action)}),r?this.renderPicture(r):this.renderIcon(t,i),this.renderBadge(t),this.renderStateInfo(t,o,n),a.length>0?cl(Ce||(Ce=Pr(['\n
\n \n \n
\n ']))))}}],[{key:"styles",get:function(){return[Dk,Aa(ze||(ze=Pr(["\n :host {\n --icon-color: var(--primary-text-color);\n --text-color: var(--primary-text-color);\n }\n ha-card {\n box-sizing: border-box;\n height: var(--chip-height);\n min-width: var(--chip-height);\n font-size: var(--chip-height);\n width: auto;\n border-radius: var(--chip-border-radius);\n display: flex;\n flex-direction: row;\n align-items: center;\n background: var(--chip-background);\n border-width: var(--chip-border-width);\n border-color: var(--chip-border-color);\n box-shadow: var(--chip-box-shadow);\n box-sizing: content-box;\n }\n .avatar {\n --avatar-size: calc(\n var(--chip-height) - 2 * var(--chip-avatar-padding)\n );\n border-radius: var(--chip-avatar-border-radius);\n height: var(--avatar-size);\n width: var(--avatar-size);\n margin-left: var(--chip-avatar-padding);\n box-sizing: border-box;\n object-fit: cover;\n }\n :host([rtl]) .avatar {\n margin-left: initial;\n margin-right: var(--chip-avatar-padding);\n }\n .content {\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: center;\n height: 100%;\n padding: var(--chip-padding);\n line-height: 0;\n }\n ::slotted(ha-icon),\n ::slotted(ha-state-icon) {\n display: flex;\n line-height: 0;\n --mdc-icon-size: var(--chip-icon-size);\n color: var(--icon-color);\n }\n ::slotted(svg) {\n width: var(--chip-icon-size);\n height: var(--chip-icon-size);\n display: flex;\n }\n ::slotted(span) {\n font-weight: var(--chip-font-weight);\n font-size: var(--chip-font-size);\n line-height: 1;\n color: var(--text-color);\n }\n ::slotted(*:not(:last-child)) {\n margin-right: 0.15em;\n }\n :host([rtl]) ::slotted(*:not(:last-child)) {\n margin-right: initial;\n margin-left: 0.15em;\n }\n "])))]}}])}();ga([zl()],hw.prototype,"icon",void 0),ga([zl()],hw.prototype,"label",void 0),ga([zl()],hw.prototype,"avatar",void 0),ga([zl()],hw.prototype,"avatarOnly",void 0),hw=ga([Il("mushroom-chip")],hw);var pw=function(e){try{var t=fw(e.type);if(customElements.get(t)){var n=document.createElement(t,e);return n.setConfig(e),n}var i=document.createElement(t);return customElements.whenDefined(t).then((function(){try{customElements.upgrade(i),i.setConfig(e)}catch(e){}})),i}catch(e){return void console.error(e)}};function fw(e){return"".concat(ow,"-").concat(e,"-chip")}function mw(e){return"".concat(ow,"-").concat(e,"-chip-editor")}var vw=function(e){function t(){return sa(this,t),Zr(this,t,arguments)}return ea(t,Sl),ua(t,[{key:"setConfig",value:function(e){this._config=e}},{key:"_handleAction",value:function(e){Xs(this,this.hass,this._config,e.detail.action)}},{key:"render",value:function(){var e;if(!this.hass||!this._config||!this._config.entity)return hl;var t=this._config.entity,n=this.hass.states[t];if(!n)return hl;var i=this._config.name||n.attributes.friendly_name||"",o=this._config.icon,r=this._config.icon_color,a=this._config.use_entity_picture?Ps(n):void 0,l=this.hass.formatEntityState(n),s=Os(n),c=kk(null!==(e=this._config.content_info)&&void 0!==e?e:"state",i,l,n,this.hass),u=bs(this.hass);return cl(Pe||(Pe=Pr(["\n \n ","\n ","\n \n "])),u,this._handleAction,qs({hasHold:Zs(this._config.hold_action),hasDoubleClick:Zs(this._config.double_tap_action)}),a?this.hass.hassUrl(a):void 0,a&&!c,a?hl:this.renderIcon(n,o,r,s),c?cl(Le||(Le=Pr(["",""])),c):hl)}},{key:"renderIcon",value:function(e,t,n,i){var o={};if(n){var r=pk(n);o["--color"]="rgb(".concat(r,")")}return cl(Ne||(Ne=Pr(["\n \n "])),this.hass,e,t,Hg(o),Mk({active:i}))}}],[{key:"getConfigElement",value:(i=Xr(Kr().m((function e(){return Kr().w((function(e){for(;;)switch(e.n){case 0:return e.n=1,Promise.resolve().then((function(){return GC}));case 1:return e.a(2,document.createElement(mw("entity")))}}),e)}))),function(){return i.apply(this,arguments)})},{key:"getStubConfig",value:(n=Xr(Kr().m((function e(t){var n;return Kr().w((function(e){for(;;)if(0===e.n)return n=Object.keys(t.states),e.a(2,{type:"entity",entity:n[0]})}),e)}))),function(e){return n.apply(this,arguments)})},{key:"styles",get:function(){return Aa(je||(je=Pr(["\n mushroom-chip {\n cursor: pointer;\n }\n ha-state-icon.active {\n color: var(--color);\n }\n "])))}}]);var n,i}();ga([zl({attribute:!1})],vw.prototype,"hass",void 0),ga([Pl()],vw.prototype,"_config",void 0),vw=ga([Il(fw("entity"))],vw);var gw=new Set(["partlycloudy","cloudy","fog","windy","windy-variant","hail","rainy","snowy","snowy-rainy","pouring","lightning","lightning-rainy"]),_w=new Set(["hail","rainy","pouring"]),yw=new Set(["windy","windy-variant"]),bw=new Set(["snowy","snowy-rainy"]),kw=new Set(["lightning","lightning-rainy"]),ww=Aa($e||($e=Pr(["\n .rain {\n fill: var(--weather-icon-rain-color, #30b3ff);\n }\n .sun {\n fill: var(--weather-icon-sun-color, #fdd93c);\n }\n .moon {\n fill: var(--weather-icon-moon-color, #fcf497);\n }\n .cloud-back {\n fill: var(--weather-icon-cloud-back-color, #d4d4d4);\n }\n .cloud-front {\n fill: var(--weather-icon-cloud-front-color, #f9f9f9);\n }\n"]))),xw=function(e,t){return ul(Be||(Be=Pr(['\n \n ',"\n ","\n ","\n ","\n ","\n ","\n ","\n ","\n ","\n "])),"sunny"===e?ul(Re||(Re=Pr(['\n \n ']))):"","clear-night"===e?ul(He||(He=Pr(['\n \n ']))):"","partlycloudy"===e&&t?ul(De||(De=Pr(['\n \n ']))):"partlycloudy"===e?ul(Ue||(Ue=Pr(['\n \n ']))):"",gw.has(e)?ul(Ve||(Ve=Pr(['\n \n \n ']))):"",_w.has(e)?ul(Fe||(Fe=Pr(['\n \n \n \n \n ']))):"","pouring"===e?ul(Ge||(Ge=Pr(['\n \n \n ']))):"",yw.has(e)?ul(Ye||(Ye=Pr(['\n \n \n ']))):"",bw.has(e)?ul(Ke||(Ke=Pr(['\n \n \n \n ']))):"",kw.has(e)?ul(We||(We=Pr(['\n \n ']))):"")},Ew=function(e){function t(){return sa(this,t),Zr(this,t,arguments)}return ea(t,Sl),ua(t,[{key:"setConfig",value:function(e){this._config=e}},{key:"_handleAction",value:function(e){Xs(this,this.hass,this._config,e.detail.action)}},{key:"render",value:function(){if(!this.hass||!this._config||!this._config.entity)return hl;var e=this._config.entity,t=this.hass.states[e];if(!t)return hl;var n=xw(t.state,!0),i=[];if(this._config.show_conditions){var o=this.hass.formatEntityState(t);i.push(o)}if(this._config.show_temperature){var r=this.hass.formatEntityAttributeValue(t,"temperature");i.push(r)}var a=bs(this.hass);return cl(qe||(qe=Pr(["\n \n ","\n ","\n \n "])),a,this._handleAction,qs({hasHold:Zs(this._config.hold_action),hasDoubleClick:Zs(this._config.double_tap_action)}),n,i.length>0?cl(Xe||(Xe=Pr(["",""])),i.join(" ⸱ ")):hl)}}],[{key:"getConfigElement",value:(i=Xr(Kr().m((function e(){return Kr().w((function(e){for(;;)switch(e.n){case 0:return e.n=1,Promise.resolve().then((function(){return ZC}));case 1:return e.a(2,document.createElement(mw("weather")))}}),e)}))),function(){return i.apply(this,arguments)})},{key:"getStubConfig",value:(n=Xr(Kr().m((function e(t){var n,i;return Kr().w((function(e){for(;;)if(0===e.n)return n=Object.keys(t.states),i=n.filter((function(e){return"weather"===e.split(".")[0]})),e.a(2,{type:"weather",entity:i[0]})}),e)}))),function(e){return n.apply(this,arguments)})},{key:"styles",get:function(){return[ww,Aa(Ze||(Ze=Pr(["\n mushroom-chip {\n cursor: pointer;\n }\n "])))]}}]);var n,i}();ga([zl({attribute:!1})],Ew.prototype,"hass",void 0),ga([Pl()],Ew.prototype,"_config",void 0),Ew=ga([Il(fw("weather"))],Ew);var Cw="mdi:arrow-left",Aw=function(e){function t(){return sa(this,t),Zr(this,t,arguments)}return ea(t,Sl),ua(t,[{key:"setConfig",value:function(e){this._config=e}},{key:"_handleAction",value:function(){window.history.back()}},{key:"render",value:function(){if(!this.hass||!this._config)return hl;var e=this._config.icon||Cw,t=bs(this.hass);return cl(Je||(Je=Pr(["\n \n \n \n "])),t,this._handleAction,qs(),this.hass,e)}}],[{key:"getConfigElement",value:(i=Xr(Kr().m((function e(){return Kr().w((function(e){for(;;)switch(e.n){case 0:return e.n=1,Promise.resolve().then((function(){return eA}));case 1:return e.a(2,document.createElement(mw("back")))}}),e)}))),function(){return i.apply(this,arguments)})},{key:"getStubConfig",value:(n=Xr(Kr().m((function e(t){return Kr().w((function(e){for(;;)if(0===e.n)return e.a(2,{type:"back"})}),e)}))),function(e){return n.apply(this,arguments)})},{key:"styles",get:function(){return Aa(Qe||(Qe=Pr(["\n mushroom-chip {\n cursor: pointer;\n }\n "])))}}]);var n,i}();ga([zl({attribute:!1})],Aw.prototype,"hass",void 0),ga([Pl()],Aw.prototype,"_config",void 0),Aw=ga([Il(fw("back"))],Aw);var Sw="mdi:flash",Tw=function(e){function t(){return sa(this,t),Zr(this,t,arguments)}return ea(t,Sl),ua(t,[{key:"setConfig",value:function(e){this._config=e}},{key:"_handleAction",value:function(e){Xs(this,this.hass,this._config,e.detail.action)}},{key:"render",value:function(){if(!this.hass||!this._config)return hl;var e=this._config.icon||Sw,t=this._config.icon_color,n={};if(t){var i=pk(t);n["--color"]="rgb(".concat(i,")")}var o=bs(this.hass);return cl(et||(et=Pr(["\n \n \n \n "])),o,this._handleAction,qs({hasHold:Zs(this._config.hold_action),hasDoubleClick:Zs(this._config.double_tap_action)}),this.hass,e,Hg(n))}}],[{key:"getConfigElement",value:(i=Xr(Kr().m((function e(){return Kr().w((function(e){for(;;)switch(e.n){case 0:return e.n=1,Promise.resolve().then((function(){return oA}));case 1:return e.a(2,document.createElement(mw("action")))}}),e)}))),function(){return i.apply(this,arguments)})},{key:"getStubConfig",value:(n=Xr(Kr().m((function e(t){return Kr().w((function(e){for(;;)if(0===e.n)return e.a(2,{type:"action"})}),e)}))),function(e){return n.apply(this,arguments)})},{key:"styles",get:function(){return Aa(tt||(tt=Pr(["\n mushroom-chip {\n cursor: pointer;\n }\n ha-state-icon {\n color: var(--color);\n }\n "])))}}]);var n,i}();ga([zl({attribute:!1})],Tw.prototype,"hass",void 0),ga([Pl()],Tw.prototype,"_config",void 0),Tw=ga([Il(fw("action"))],Tw);var Iw="mdi:menu",Ow=function(e){function t(){return sa(this,t),Zr(this,t,arguments)}return ea(t,Sl),ua(t,[{key:"setConfig",value:function(e){this._config=e}},{key:"_handleAction",value:function(){Dl(this,"hass-toggle-menu")}},{key:"render",value:function(){if(!this.hass||!this._config)return hl;var e=this._config.icon||Iw,t=bs(this.hass);return cl(nt||(nt=Pr(["\n \n \n \n "])),t,this._handleAction,qs(),this.hass,e)}}],[{key:"getConfigElement",value:(i=Xr(Kr().m((function e(){return Kr().w((function(e){for(;;)switch(e.n){case 0:return e.n=1,Promise.resolve().then((function(){return lA}));case 1:return e.a(2,document.createElement(mw("menu")))}}),e)}))),function(){return i.apply(this,arguments)})},{key:"getStubConfig",value:(n=Xr(Kr().m((function e(t){return Kr().w((function(e){for(;;)if(0===e.n)return e.a(2,{type:"menu"})}),e)}))),function(e){return n.apply(this,arguments)})},{key:"styles",get:function(){return Aa(it||(it=Pr(["\n mushroom-chip {\n cursor: pointer;\n }\n "])))}}]);var n,i}();ga([zl({attribute:!1})],Ow.prototype,"hass",void 0),ga([Pl()],Ow.prototype,"_config",void 0),Ow=ga([Il(fw("menu"))],Ow);var Mw="mdi:magnify",zw=function(e){function t(){return sa(this,t),Zr(this,t,arguments)}return ea(t,Sl),ua(t,[{key:"setConfig",value:function(e){this._config=e}},{key:"_handleAction",value:function(){if(this.hass&&this._config){var e;switch(this._config.mode||"entity"){case"command":e="c";break;case"device":e="d";break;case"entity":e="e"}var t=new KeyboardEvent("keydown",{bubbles:!0,composed:!0,key:e});this.dispatchEvent(t)}}},{key:"render",value:function(){if(!this.hass||!this._config)return hl;var e=this._config.icon||Mw,t=bs(this.hass);return cl(ot||(ot=Pr(["\n \n \n \n "])),t,this._handleAction,qs(),this.hass,e)}}],[{key:"getConfigElement",value:(i=Xr(Kr().m((function e(){return Kr().w((function(e){for(;;)switch(e.n){case 0:return e.n=1,Promise.resolve().then((function(){return uA}));case 1:return e.a(2,document.createElement(mw("quickbar")))}}),e)}))),function(){return i.apply(this,arguments)})},{key:"getStubConfig",value:(n=Xr(Kr().m((function e(t){return Kr().w((function(e){for(;;)if(0===e.n)return e.a(2,{type:"quickbar"})}),e)}))),function(e){return n.apply(this,arguments)})},{key:"styles",get:function(){return Aa(rt||(rt=Pr(["\n mushroom-chip {\n cursor: pointer;\n }\n "])))}}]);var n,i}();function Pw(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}function Lw(e){throw new Error('Could not dynamically require "'+e+'". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.')}ga([zl({attribute:!1})],zw.prototype,"hass",void 0),ga([Pl()],zw.prototype,"_config",void 0),zw=ga([Il(fw("quickbar"))],zw);var Nw,jw={exports:{}};var $w=(Nw||(Nw=1,jw.exports=function e(t,n,i){function o(a,l){if(!n[a]){if(!t[a]){if(!l&&Lw)return Lw(a);if(r)return r(a,!0);throw new Error("Cannot find module '"+a+"'")}l=n[a]={exports:{}},t[a][0].call(l.exports,(function(e){return o(t[a][1][e]||e)}),l,l.exports,e,t,n,i)}return n[a].exports}for(var r=Lw,a=0;a>16),s((65280&i)>>8),s(255&i);return 2==o?s(255&(i=c(e.charAt(n))<<2|c(e.charAt(n+1))>>4)):1==o&&(s((i=c(e.charAt(n))<<10|c(e.charAt(n+1))<<4|c(e.charAt(n+2))>>2)>>8&255),s(255&i)),r},e.fromByteArray=function(e){var t,n,i,o,r=e.length%3,a="";function l(e){return"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(e)}for(t=0,i=e.length-r;t>18&63)+l(o>>12&63)+l(o>>6&63)+l(63&o);switch(r){case 1:a=(a+=l((n=e[e.length-1])>>2))+l(n<<4&63)+"==";break;case 2:a=(a=(a+=l((n=(e[e.length-2]<<8)+e[e.length-1])>>10))+l(n>>4&63))+l(n<<2&63)+"="}return a}}(void 0===n?this.base64js={}:n)}).call(this,e("lYpoI2"),"undefined"!=typeof self?self:"undefined"!=typeof window?window:{},e("buffer").Buffer,arguments[3],arguments[4],arguments[5],arguments[6],"/node_modules/gulp-browserify/node_modules/base64-js/lib/b64.js","/node_modules/gulp-browserify/node_modules/base64-js/lib")},{buffer:3,lYpoI2:11}],3:[function(e,t,n){(function(t,i,o,r,a,l,s,c,u){var d=e("base64-js"),h=e("ieee754");function o(e,t,n){if(!(this instanceof o))return new o(e,t,n);var i,r,a,l,s=ha(e);if("base64"===t&&"string"==s)for(e=(l=e).trim?l.trim():l.replace(/^\s+|\s+$/g,"");e.length%4!=0;)e+="=";if("number"==s)i=O(e);else if("string"==s)i=o.byteLength(e,t);else{if("object"!=s)throw new Error("First argument needs to be a number, array or string.");i=O(e.length)}if(o._useTypedArrays?r=o._augment(new Uint8Array(i)):((r=this).length=i,r._isBuffer=!0),o._useTypedArrays&&"number"==typeof e.byteLength)r._set(e);else if(M(l=e)||o.isBuffer(l)||l&&"object"==ha(l)&&"number"==typeof l.length)for(a=0;a>8,n%=256,i.push(n),i.push(t);return i}(t),e,n,i)}function m(e,t,n){var i="";n=Math.min(e.length,n);for(var o=t;o>>0)):(t+1>>0),o}function _(e,t,n,i){if(i||(H("boolean"==typeof n,"missing or invalid endian"),H(null!=t,"missing offset"),H(t+1>>8*(i?r:1-r)}function x(e,t,n,i,o){if(o||(H(null!=t,"missing value"),H("boolean"==typeof i,"missing or invalid endian"),H(null!=n,"missing offset"),H(n+3>>8*(i?r:3-r)&255}function E(e,t,n,i,o){o||(H(null!=t,"missing value"),H("boolean"==typeof i,"missing or invalid endian"),H(null!=n,"missing offset"),H(n+1this.length&&(i=this.length);var r=(i=e.length-t=this.length))return this[e]},o.prototype.readUInt16LE=function(e,t){return v(this,e,!0,t)},o.prototype.readUInt16BE=function(e,t){return v(this,e,!1,t)},o.prototype.readUInt32LE=function(e,t){return g(this,e,!0,t)},o.prototype.readUInt32BE=function(e,t){return g(this,e,!1,t)},o.prototype.readInt8=function(e,t){if(t||(H(null!=e,"missing offset"),H(e=this.length))return 128&this[e]?-1*(255-this[e]+1):this[e]},o.prototype.readInt16LE=function(e,t){return _(this,e,!0,t)},o.prototype.readInt16BE=function(e,t){return _(this,e,!1,t)},o.prototype.readInt32LE=function(e,t){return y(this,e,!0,t)},o.prototype.readInt32BE=function(e,t){return y(this,e,!1,t)},o.prototype.readFloatLE=function(e,t){return b(this,e,!0,t)},o.prototype.readFloatBE=function(e,t){return b(this,e,!1,t)},o.prototype.readDoubleLE=function(e,t){return k(this,e,!0,t)},o.prototype.readDoubleBE=function(e,t){return k(this,e,!1,t)},o.prototype.writeUInt8=function(e,t,n){n||(H(null!=e,"missing value"),H(null!=t,"missing offset"),H(t=this.length||(this[t]=e)},o.prototype.writeUInt16LE=function(e,t,n){w(this,e,t,!0,n)},o.prototype.writeUInt16BE=function(e,t,n){w(this,e,t,!1,n)},o.prototype.writeUInt32LE=function(e,t,n){x(this,e,t,!0,n)},o.prototype.writeUInt32BE=function(e,t,n){x(this,e,t,!1,n)},o.prototype.writeInt8=function(e,t,n){n||(H(null!=e,"missing value"),H(null!=t,"missing offset"),H(t=this.length||(0<=e?this.writeUInt8(e,t,n):this.writeUInt8(255+e+1,t,n))},o.prototype.writeInt16LE=function(e,t,n){E(this,e,t,!0,n)},o.prototype.writeInt16BE=function(e,t,n){E(this,e,t,!1,n)},o.prototype.writeInt32LE=function(e,t,n){C(this,e,t,!0,n)},o.prototype.writeInt32BE=function(e,t,n){C(this,e,t,!1,n)},o.prototype.writeFloatLE=function(e,t,n){A(this,e,t,!0,n)},o.prototype.writeFloatBE=function(e,t,n){A(this,e,t,!1,n)},o.prototype.writeDoubleLE=function(e,t,n){S(this,e,t,!0,n)},o.prototype.writeDoubleBE=function(e,t,n){S(this,e,t,!1,n)},o.prototype.fill=function(e,t,n){if(t=t||0,n=n||this.length,H("number"==typeof(e="string"==typeof(e=e||0)?e.charCodeAt(0):e)&&!isNaN(e),"value is not a number"),H(t<=n,"end < start"),n!==t&&0!==this.length){H(0<=t&&t"},o.prototype.toArrayBuffer=function(){if("undefined"==typeof Uint8Array)throw new Error("Buffer.toArrayBuffer not supported in this browser");if(o._useTypedArrays)return new o(this).buffer;for(var e=new Uint8Array(this.length),t=0,n=e.length;t=t.length||o>=e.length);o++)t[o+n]=e[o];return o}function j(e){try{return decodeURIComponent(e)}catch(e){return String.fromCharCode(65533)}}function $(e,t){H("number"==typeof e,"cannot write a non-number as a number"),H(0<=e,"specified a negative value for writing an unsigned value"),H(e<=t,"value is larger than maximum value for type"),H(Math.floor(e)===e,"value has a fractional component")}function B(e,t,n){H("number"==typeof e,"cannot write a non-number as a number"),H(e<=t,"value larger than maximum allowed value"),H(n<=e,"value smaller than minimum allowed value"),H(Math.floor(e)===e,"value has a fractional component")}function R(e,t,n){H("number"==typeof e,"cannot write a non-number as a number"),H(e<=t,"value larger than maximum allowed value"),H(n<=e,"value smaller than minimum allowed value")}function H(e,t){if(!e)throw new Error(t||"Failed assertion")}o._augment=function(e){return e._isBuffer=!0,e._get=e.get,e._set=e.set,e.get=T.get,e.set=T.set,e.write=T.write,e.toString=T.toString,e.toLocaleString=T.toString,e.toJSON=T.toJSON,e.copy=T.copy,e.slice=T.slice,e.readUInt8=T.readUInt8,e.readUInt16LE=T.readUInt16LE,e.readUInt16BE=T.readUInt16BE,e.readUInt32LE=T.readUInt32LE,e.readUInt32BE=T.readUInt32BE,e.readInt8=T.readInt8,e.readInt16LE=T.readInt16LE,e.readInt16BE=T.readInt16BE,e.readInt32LE=T.readInt32LE,e.readInt32BE=T.readInt32BE,e.readFloatLE=T.readFloatLE,e.readFloatBE=T.readFloatBE,e.readDoubleLE=T.readDoubleLE,e.readDoubleBE=T.readDoubleBE,e.writeUInt8=T.writeUInt8,e.writeUInt16LE=T.writeUInt16LE,e.writeUInt16BE=T.writeUInt16BE,e.writeUInt32LE=T.writeUInt32LE,e.writeUInt32BE=T.writeUInt32BE,e.writeInt8=T.writeInt8,e.writeInt16LE=T.writeInt16LE,e.writeInt16BE=T.writeInt16BE,e.writeInt32LE=T.writeInt32LE,e.writeInt32BE=T.writeInt32BE,e.writeFloatLE=T.writeFloatLE,e.writeFloatBE=T.writeFloatBE,e.writeDoubleLE=T.writeDoubleLE,e.writeDoubleBE=T.writeDoubleBE,e.fill=T.fill,e.inspect=T.inspect,e.toArrayBuffer=T.toArrayBuffer,e}}).call(this,e("lYpoI2"),"undefined"!=typeof self?self:"undefined"!=typeof window?window:{},e("buffer").Buffer,arguments[3],arguments[4],arguments[5],arguments[6],"/node_modules/gulp-browserify/node_modules/buffer/index.js","/node_modules/gulp-browserify/node_modules/buffer")},{"base64-js":2,buffer:3,ieee754:10,lYpoI2:11}],4:[function(e,t,n){(function(n,i,o,r,a,l,s,c,u){o=e("buffer").Buffer;var d=4,h=new o(d);h.fill(0),t.exports={hash:function(e,t,n,i){for(var r=t(function(e,t){e.length%d!=0&&(n=e.length+(d-e.length%d),e=o.concat([e,h],n));for(var n,i=[],r=t?e.readInt32BE:e.readInt32LE,a=0;am?t=e(t):t.length>5]|=128<>>9<<4)]=t;for(var n=1732584193,i=-271733879,o=-1732584194,r=271733878,a=0;a>>32-o,n)}function f(e,t,n,i,o,r,a){return p(t&n|~t&i,e,t,o,r,a)}function m(e,t,n,i,o,r,a){return p(t&i|n&~i,e,t,o,r,a)}function v(e,t,n,i,o,r,a){return p(t^n^i,e,t,o,r,a)}function g(e,t,n,i,o,r,a){return p(n^(t|~i),e,t,o,r,a)}function _(e,t){var n=(65535&e)+(65535&t);return(e>>16)+(t>>16)+(n>>16)<<16|65535&n}t.exports=function(e){return d.hash(e,h,16)}}).call(this,e("lYpoI2"),"undefined"!=typeof self?self:"undefined"!=typeof window?window:{},e("buffer").Buffer,arguments[3],arguments[4],arguments[5],arguments[6],"/node_modules/gulp-browserify/node_modules/crypto-browserify/md5.js","/node_modules/gulp-browserify/node_modules/crypto-browserify")},{"./helpers":4,buffer:3,lYpoI2:11}],7:[function(e,t,n){(function(e,n,i,o,r,a,l,s,c){t.exports=function(e){for(var t,n=new Array(e),i=0;i>>((3&i)<<3)&255;return n}}).call(this,e("lYpoI2"),"undefined"!=typeof self?self:"undefined"!=typeof window?window:{},e("buffer").Buffer,arguments[3],arguments[4],arguments[5],arguments[6],"/node_modules/gulp-browserify/node_modules/crypto-browserify/rng.js","/node_modules/gulp-browserify/node_modules/crypto-browserify")},{buffer:3,lYpoI2:11}],8:[function(e,t,n){(function(n,i,o,r,a,l,s,c,u){var d=e("./helpers");function h(e,t){e[t>>5]|=128<<24-t%32,e[15+(t+64>>9<<4)]=t;for(var n,i,o,r=Array(80),a=1732584193,l=-271733879,s=-1732584194,c=271733878,u=-1009589776,d=0;d>16)+(t>>16)+(n>>16)<<16|65535&n}function f(e,t){return e<>>32-t}t.exports=function(e){return d.hash(e,h,20,!0)}}).call(this,e("lYpoI2"),"undefined"!=typeof self?self:"undefined"!=typeof window?window:{},e("buffer").Buffer,arguments[3],arguments[4],arguments[5],arguments[6],"/node_modules/gulp-browserify/node_modules/crypto-browserify/sha.js","/node_modules/gulp-browserify/node_modules/crypto-browserify")},{"./helpers":4,buffer:3,lYpoI2:11}],9:[function(e,t,n){(function(n,i,o,r,a,l,s,c,u){function d(e,t){var n=(65535&e)+(65535&t);return(e>>16)+(t>>16)+(n>>16)<<16|65535&n}function h(e,t){var n,i=new Array(1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298),o=new Array(1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225),r=new Array(64);e[t>>5]|=128<<24-t%32,e[15+(t+64>>9<<4)]=t;for(var a,l,s=0;s>>t|e<<32-t},m=function(e,t){return e>>>t};t.exports=function(e){return p.hash(e,h,32,!0)}}).call(this,e("lYpoI2"),"undefined"!=typeof self?self:"undefined"!=typeof window?window:{},e("buffer").Buffer,arguments[3],arguments[4],arguments[5],arguments[6],"/node_modules/gulp-browserify/node_modules/crypto-browserify/sha256.js","/node_modules/gulp-browserify/node_modules/crypto-browserify")},{"./helpers":4,buffer:3,lYpoI2:11}],10:[function(e,t,n){(function(e,t,i,o,r,a,l,s,c){n.read=function(e,t,n,i,o){var r,a,l=8*o-i-1,s=(1<>1,u=-7,d=n?o-1:0,h=n?-1:1;for(o=e[t+d],d+=h,r=o&(1<<-u)-1,o>>=-u,u+=l;0>=-u,u+=i;0>1,d=23===o?Math.pow(2,-24)-Math.pow(2,-77):0,h=i?0:r-1,p=i?1:-1;for(r=t<0||0===t&&1/t<0?1:0,t=Math.abs(t),isNaN(t)||t===1/0?(l=isNaN(t)?1:0,a=c):(a=Math.floor(Math.log(t)/Math.LN2),t*(i=Math.pow(2,-a))<1&&(a--,i*=2),2<=(t+=1<=a+u?d/i:d*Math.pow(2,1-u))*i&&(a++,i/=2),c<=a+u?(l=0,a=c):1<=a+u?(l=(t*i-1)*Math.pow(2,o),a+=u):(l=t*Math.pow(2,u-1)*Math.pow(2,o),a=0));8<=o;e[n+h]=255&l,h+=p,l/=256,o-=8);for(a=a<\n ","\n ","\n \n "])),o,this._handleAction,qs({hasHold:Zs(this._config.hold_action),hasDoubleClick:Zs(this._config.double_tap_action)}),i?this.hass.hassUrl(i):void 0,i&&!n||!1,i?hl:r||(e?this.renderIcon(e,t):hl),n?this.renderContent(n):hl)}},{key:"renderIcon",value:function(e,t){var n={};if(t){var i=pk(t);n["--color"]="rgb(".concat(i,")")}return cl(lt||(lt=Pr([""])),this.hass,e,Hg(n))}},{key:"renderContent",value:function(e){return cl(st||(st=Pr(["",""])),e)}},{key:"updated",value:function(e){Hr(t,"updated",this,3)([e]),this._config&&this.hass&&this._tryConnect()}},{key:"_tryConnect",value:(l=Xr(Kr().m((function e(){var t=this;return Kr().w((function(e){for(;;)switch(e.n){case 0:Vw.forEach((function(e){t._tryConnectKey(e)}));case 1:return e.a(2)}}),e)}))),function(){return l.apply(this,arguments)})},{key:"_tryConnectKey",value:(a=Xr(Kr().m((function e(t){var n,i,o,r,a=this;return Kr().w((function(e){for(;;)switch(e.p=e.n){case 0:if(void 0===this._unsubRenderTemplates.get(t)&&this.hass&&this._config&&this.isTemplate(t)){e.n=1;break}return e.a(2);case 1:return e.p=1,o=Fs(this.hass.connection,(function(e){a._templateResults=Object.assign(Object.assign({},a._templateResults),Rr({},t,e))}),{template:null!==(n=this._config[t])&&void 0!==n?n:"",entity_ids:this._config.entity_id,variables:{config:this._config,user:this.hass.user.name,entity:this._config.entity},strict:!0}),this._unsubRenderTemplates.set(t,o),e.n=2,o;case 2:e.n=4;break;case 3:e.p=3,e.v,r={result:null!==(i=this._config[t])&&void 0!==i?i:"",listeners:{all:!1,domains:[],entities:[],time:!1}},this._templateResults=Object.assign(Object.assign({},this._templateResults),Rr({},t,r)),this._unsubRenderTemplates.delete(t);case 4:return e.a(2)}}),e,this,[[1,3]])}))),function(e){return a.apply(this,arguments)})},{key:"_tryDisconnect",value:(r=Xr(Kr().m((function e(){var t=this;return Kr().w((function(e){for(;;)switch(e.n){case 0:Vw.forEach((function(e){t._tryDisconnectKey(e)}));case 1:return e.a(2)}}),e)}))),function(){return r.apply(this,arguments)})},{key:"_tryDisconnectKey",value:(o=Xr(Kr().m((function e(t){var n,i;return Kr().w((function(e){for(;;)switch(e.p=e.n){case 0:if(n=this._unsubRenderTemplates.get(t)){e.n=1;break}return e.a(2);case 1:return e.p=1,e.n=2,n;case 2:(0,e.v)(),this._unsubRenderTemplates.delete(t),e.n=5;break;case 3:if(e.p=3,"not_found"!==(i=e.v).code&&"template_error"!==i.code){e.n=4;break}e.n=5;break;case 4:throw i;case 5:return e.a(2)}}),e,this,[[1,3]])}))),function(e){return o.apply(this,arguments)})}],[{key:"getConfigElement",value:(i=Xr(Kr().m((function e(){return Kr().w((function(e){for(;;)switch(e.n){case 0:return e.n=1,Promise.resolve().then((function(){return bA}));case 1:return e.a(2,document.createElement(mw("template")))}}),e)}))),function(){return i.apply(this,arguments)})},{key:"getStubConfig",value:(n=Xr(Kr().m((function e(t){return Kr().w((function(e){for(;;)if(0===e.n)return e.a(2,{type:"template"})}),e)}))),function(e){return n.apply(this,arguments)})},{key:"styles",get:function(){return Aa(ct||(ct=Pr(["\n mushroom-chip {\n cursor: pointer;\n }\n ha-state-icon {\n color: var(--color);\n }\n ","\n "])),ww)}}]);var n,i,o,r,a,l}();ga([zl({attribute:!1})],Fw.prototype,"hass",void 0),ga([Pl()],Fw.prototype,"_config",void 0),ga([Pl()],Fw.prototype,"_templateResults",void 0),ga([Pl()],Fw.prototype,"_unsubRenderTemplates",void 0),Fw=ga([Il(fw("template"))],Fw);var Gw=function(){var e,t,n;customElements.get("ha-form")&&customElements.get("hui-card-features-editor")||null===(e=customElements.get("hui-tile-card"))||void 0===e||e.getConfigElement(),customElements.get("ha-entity-picker")||null===(t=customElements.get("hui-entities-card"))||void 0===t||t.getConfigElement(),customElements.get("ha-card-conditions-editor")||null===(n=customElements.get("hui-conditional-card"))||void 0===n||n.getConfigElement()},Yw=function(){var e=Xr(Kr().m((function e(t){var n;return Kr().w((function(e){for(;;)switch(e.n){case 0:if(!(n=customElements.get(t))){e.n=1;break}return e.a(2,n);case 1:return e.n=2,customElements.whenDefined(t);case 2:return e.a(2,customElements.get(t))}}),e)})));return function(t){return e.apply(this,arguments)}}(),Kw=fw("conditional"),Ww=function(){var e=Xr(Kr().m((function e(){var t,n;return Kr().w((function(e){for(;;)switch(e.n){case 0:if(!customElements.get(Kw)){e.n=1;break}return e.a(2);case 1:if(customElements.get("hui-conditional-base")){e.n=3;break}return e.n=2,window.loadCardHelpers();case 2:e.v.createCardElement({type:"conditional",card:{type:"button"},conditions:[]});case 3:return e.n=4,Yw("hui-conditional-base");case 4:t=e.v,n=function(e){function t(){return sa(this,t),Zr(this,t,arguments)}return ea(t,e),ua(t,[{key:"setConfig",value:function(e){if(this.validateConfig(e),!e.chip)throw new Error("No chip configured");this._element=pw(e.chip)}}],[{key:"getConfigElement",value:(i=Xr(Kr().m((function e(){return Kr().w((function(e){for(;;)switch(e.n){case 0:return e.n=1,Promise.resolve().then((function(){return DO}));case 1:return e.a(2,document.createElement(mw("conditional")))}}),e)}))),function(){return i.apply(this,arguments)})},{key:"getStubConfig",value:(n=Xr(Kr().m((function e(){return Kr().w((function(e){for(;;)if(0===e.n)return e.a(2,{type:"conditional",conditions:[]})}),e)}))),function(){return n.apply(this,arguments)})}]);var n,i}(t),customElements.get(Kw)||customElements.define(Kw,n);case 5:return e.a(2)}}),e)})));return function(){return e.apply(this,arguments)}}();function qw(e){return null!=e.attributes.rgb_color?e.attributes.rgb_color:void 0}function Xw(e){var t={mode:"rgb",r:e[0]/255,g:e[1]/255,b:e[2]/255},n=uk(t);return((null==n?void 0:n.l)||0)>96}function Zw(e){var t={mode:"rgb",r:e[0]/255,g:e[1]/255,b:e[2]/255},n=uk(t);return((null==n?void 0:n.l)||0)>97}function Jw(e){return function(e){var t;return(null===(t=e.attributes.supported_color_modes)||void 0===t?void 0:t.some((function(e){return Ls.includes(e)})))||!1}(e)}function Qw(e){return function(e){var t;return(null===(t=e.attributes.supported_color_modes)||void 0===t?void 0:t.some((function(e){return Ns.includes(e)})))||!1}(e)}var ex=function(e){function t(){return sa(this,t),Zr(this,t,arguments)}return ea(t,Sl),ua(t,[{key:"setConfig",value:function(e){this._config=Object.assign({tap_action:{action:"toggle"},hold_action:{action:"more-info"}},e)}},{key:"_handleAction",value:function(e){Xs(this,this.hass,this._config,e.detail.action)}},{key:"render",value:function(){var e,t;if(!this.hass||!this._config||!this._config.entity)return hl;var n=this._config.entity,i=this.hass.states[n];if(!i)return hl;var o=this._config.name||i.attributes.friendly_name||"",r=this._config.icon,a=this.hass.formatEntityState(i),l=Os(i),s=qw(i),c={};if(s&&(null===(e=this._config)||void 0===e?void 0:e.use_light_color)){var u=s.join(",");c["--color"]="rgb(".concat(u,")"),Zw(s)&&(c["--color"]="rgba(var(--rgb-primary-text-color), 0.2)")}var d=kk(null!==(t=this._config.content_info)&&void 0!==t?t:"state",o,a,i,this.hass),h=bs(this.hass);return cl(ut||(ut=Pr(["\n \n \n ","\n \n "])),h,this._handleAction,qs({hasHold:Zs(this._config.hold_action),hasDoubleClick:Zs(this._config.double_tap_action)}),this.hass,i,r,Hg(c),Mk({active:l}),d?cl(dt||(dt=Pr(["",""])),d):hl)}}],[{key:"getConfigElement",value:(i=Xr(Kr().m((function e(){return Kr().w((function(e){for(;;)switch(e.n){case 0:return e.n=1,Promise.resolve().then((function(){return qO}));case 1:return e.a(2,document.createElement(mw("light")))}}),e)}))),function(){return i.apply(this,arguments)})},{key:"getStubConfig",value:(n=Xr(Kr().m((function e(t){var n,i;return Kr().w((function(e){for(;;)if(0===e.n)return n=Object.keys(t.states),i=n.filter((function(e){return"light"===e.split(".")[0]})),e.a(2,{type:"light",entity:i[0]})}),e)}))),function(e){return n.apply(this,arguments)})},{key:"styles",get:function(){return Aa(ht||(ht=Pr(["\n :host {\n --color: rgb(var(--rgb-state-light));\n }\n mushroom-chip {\n cursor: pointer;\n }\n ha-state-icon.active {\n color: var(--color);\n }\n "])))}}]);var n,i}();ga([zl({attribute:!1})],ex.prototype,"hass",void 0),ga([Pl()],ex.prototype,"_config",void 0),ex=ga([Il(fw("light"))],ex);var tx=function(e){function t(){return sa(this,t),Zr(this,t,arguments)}return ea(t,Sl),ua(t,[{key:"setConfig",value:function(e){this._config=e}},{key:"_handleAction",value:function(e){Xs(this,this.hass,this._config,e.detail.action)}},{key:"render",value:function(){var e;if(!this.hass||!this._config||!this._config.entity)return hl;var t=this._config.entity,n=this.hass.states[t];if(!n)return hl;var i=this._config.name||n.attributes.friendly_name||"",o=this._config.icon,r=cw(n.state),a=uw(n.state),l=this.hass.formatEntityState(n),s={};if(r){var c=pk(r);s["--color"]="rgb(".concat(c,")")}var u=kk(null!==(e=this._config.content_info)&&void 0!==e?e:"state",i,l,n,this.hass),d=bs(this.hass);return cl(pt||(pt=Pr(["\n \n \n ","\n \n "])),d,this._handleAction,qs({hasHold:Zs(this._config.hold_action),hasDoubleClick:Zs(this._config.double_tap_action)}),this.hass,n,o,Hg(s),Mk({pulse:a}),u?cl(ft||(ft=Pr(["",""])),u):hl)}}],[{key:"getConfigElement",value:(i=Xr(Kr().m((function e(){return Kr().w((function(e){for(;;)switch(e.n){case 0:return e.n=1,Promise.resolve().then((function(){return QO}));case 1:return e.a(2,document.createElement(mw("alarm-control-panel")))}}),e)}))),function(){return i.apply(this,arguments)})},{key:"getStubConfig",value:(n=Xr(Kr().m((function e(t){var n,i;return Kr().w((function(e){for(;;)if(0===e.n)return n=Object.keys(t.states),i=n.filter((function(e){return lw.includes(e.split(".")[0])})),e.a(2,{type:"alarm-control-panel",entity:i[0]})}),e)}))),function(e){return n.apply(this,arguments)})},{key:"styles",get:function(){return Aa(mt||(mt=Pr(["\n mushroom-chip {\n cursor: pointer;\n }\n ha-state-icon {\n color: var(--color);\n }\n ha-state-icon.pulse {\n animation: 1s ease 0s infinite normal none running pulse;\n }\n ","\n "])),Hk.pulse)}}]);var n,i}();ga([zl({attribute:!1})],tx.prototype,"hass",void 0),ga([Pl()],tx.prototype,"_config",void 0),tx=ga([Il(fw("alarm-control-panel"))],tx);var nx=function(e){function t(){return sa(this,t),Zr(this,t,arguments)}return ea(t,Sl),ua(t,[{key:"setConfig",value:function(){}}],[{key:"styles",get:function(){return Aa(vt||(vt=Pr(["\n :host {\n flex-grow: 1;\n }\n "])))}}])}();nx=ga([Il(fw("spacer"))],nx);var ix="".concat(ow,"-chips-card"),ox="".concat(ix,"-editor");iw({type:ix,name:"Mushroom Chips Card",description:"Card with chips to display informations"});var rx=function(e){function t(){return sa(this,t),Zr(this,t,arguments)}return ea(t,Sl),ua(t,[{key:"hass",set:function(e){var t,n=Qk(this._hass),i=Qk(e);n!==i&&this.toggleAttribute("dark-mode",i),this._hass=e,null===(t=this.shadowRoot)||void 0===t||t.querySelectorAll("div > *").forEach((function(t){t.hass=e}))}},{key:"getCardSize",value:function(){return 1}},{key:"setConfig",value:function(e){this._config=e}},{key:"render",value:function(){var e=this;if(!this._config||!this._hass)return hl;var t="";this._config.alignment&&(t="align-".concat(this._config.alignment));var n=bs(this._hass);return cl(gt||(gt=Pr(['\n \n
\n \n \n \n \n \n \n \n
\n '])),this._decrementValue,this.disabled,Mk({value:!0,pending:this.pending,disabled:this.disabled}),e,this._incrementValue,this.disabled)}}],[{key:"styles",get:function(){return Aa(xt||(xt=Pr(["\n :host {\n --text-color: var(--primary-text-color);\n --text-color-disabled: rgb(var(--rgb-disabled));\n --icon-color: var(--primary-text-color);\n --icon-color-disabled: rgb(var(--rgb-disabled));\n --bg-color: rgba(var(--rgb-primary-text-color), 0.05);\n --bg-color-disabled: rgba(var(--rgb-disabled), 0.2);\n height: var(--control-height);\n width: calc(var(--control-height) * var(--control-button-ratio) * 3);\n flex: none;\n }\n .container {\n box-sizing: border-box;\n width: 100%;\n height: 100%;\n padding: 6px;\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: center;\n border-radius: var(--control-border-radius);\n border: none;\n background-color: var(--bg-color);\n transition: background-color 280ms ease-in-out;\n height: var(--control-height);\n overflow: hidden;\n }\n .button {\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: center;\n padding: 4px;\n border: none;\n background: none;\n cursor: pointer;\n border-radius: var(--control-border-radius);\n line-height: 0;\n height: 100%;\n }\n .minus {\n padding-right: 0;\n }\n .plus {\n padding-left: 0;\n }\n .button:disabled {\n cursor: not-allowed;\n }\n .button ha-icon {\n font-size: var(--control-height);\n --mdc-icon-size: var(--control-icon-size);\n color: var(--icon-color);\n pointer-events: none;\n }\n .button:disabled ha-icon {\n color: var(--icon-color-disabled);\n }\n .value {\n text-align: center;\n flex-grow: 1;\n flex-shrink: 0;\n flex-basis: 20px;\n font-weight: bold;\n color: var(--text-color);\n }\n .value.disabled {\n color: var(--text-color-disabled);\n }\n .value.pending {\n opacity: 0.5;\n }\n "])))}}])}();ga([zl({attribute:!1})],mx.prototype,"locale",void 0),ga([zl({type:Boolean})],mx.prototype,"disabled",void 0),ga([zl({attribute:!1,type:Number,reflect:!0})],mx.prototype,"value",void 0),ga([zl({type:Number})],mx.prototype,"step",void 0),ga([zl({type:Number})],mx.prototype,"min",void 0),ga([zl({type:Number})],mx.prototype,"max",void 0),ga([zl({attribute:!1})],mx.prototype,"formatOptions",void 0),ga([Pl()],mx.prototype,"pending",void 0),ga([Ll("#container")],mx.prototype,"container",void 0),mx=ga([Il("mushroom-input-number")],mx);var vx=function(e){function t(){var e;return sa(this,t),(e=Zr(this,t,arguments)).fill=!1,e}return ea(t,Sl),ua(t,[{key:"_stepSize",get:function(){return this.entity.attributes.target_temp_step?this.entity.attributes.target_temp_step:"°F"===this.hass.config.unit_system.temperature?1:.5}},{key:"onValueChange",value:function(e){var t=e.detail.value;this.hass.callService("climate","set_temperature",{entity_id:this.entity.entity_id,temperature:t})}},{key:"onLowValueChange",value:function(e){var t=e.detail.value;this.hass.callService("climate","set_temperature",{entity_id:this.entity.entity_id,target_temp_low:t,target_temp_high:this.entity.attributes.target_temp_high})}},{key:"onHighValueChange",value:function(e){var t=e.detail.value;this.hass.callService("climate","set_temperature",{entity_id:this.entity.entity_id,target_temp_low:this.entity.attributes.target_temp_low,target_temp_high:t})}},{key:"render",value:function(){var e=bs(this.hass),t=Ms(this.entity),n=1===this._stepSize?{maximumFractionDigits:0}:{minimumFractionDigits:1,maximumFractionDigits:1},i=function(e){return{"--bg-color":"rgba(var(--rgb-state-climate-".concat(e,"), 0.05)"),"--icon-color":"rgb(var(--rgb-state-climate-".concat(e,"))"),"--text-color":"rgb(var(--rgb-state-climate-".concat(e,"))")}};return cl(Et||(Et=Pr(["\n \n ","\n ","\n \n "])),this.fill,e,null!=this.entity.attributes.temperature?cl(Ct||(Ct=Pr(["\n \n "])),this.hass.locale,this.entity.attributes.temperature,this._stepSize,this.entity.attributes.min_temp,this.entity.attributes.max_temp,!t,n,this.onValueChange):hl,null!=this.entity.attributes.target_temp_low&&null!=this.entity.attributes.target_temp_high?cl(At||(At=Pr(["\n \n "])),Hg(i("heat")),this.hass.locale,this.entity.attributes.target_temp_low,this._stepSize,this.entity.attributes.min_temp,this.entity.attributes.max_temp,!t,n,this.onLowValueChange,Hg(i("cool")),this.hass.locale,this.entity.attributes.target_temp_high,this._stepSize,this.entity.attributes.min_temp,this.entity.attributes.max_temp,!t,n,this.onHighValueChange):hl)}}])}();ga([zl({attribute:!1})],vx.prototype,"hass",void 0),ga([zl({attribute:!1})],vx.prototype,"entity",void 0),ga([zl()],vx.prototype,"fill",void 0),vx=ga([Il("mushroom-climate-temperature-control")],vx);var gx={temperature_control:"mdi:thermometer",hvac_mode_control:"mdi:thermostat"};iw({type:ax,name:"Mushroom Climate Card",description:"Card for climate entity"});var _x=function(e){function t(){return sa(this,t),Zr(this,t,arguments)}return ea(t,tw),ua(t,[{key:"_controls",get:function(){if(!this._config||!this._stateObj)return[];var e,t=this._stateObj,n=[];return(null!=(e=t).attributes.temperature||null!=e.attributes.target_temp_low&&null!=e.attributes.target_temp_high)&&this._config.show_temperature_control&&n.push("temperature_control"),function(e,t){return(e.attributes.hvac_modes||[]).some((function(e){return(null!=t?t:[]).includes(e)}))}(t,this._config.hvac_modes)&&n.push("hvac_mode_control"),n}},{key:"hasControls",get:function(){return this._controls.length>0}},{key:"_onControlTap",value:function(e,t){t.stopPropagation(),this._activeControl=e}},{key:"setConfig",value:function(e){Hr(t,"setConfig",this,3)([Object.assign({tap_action:{action:"toggle"},hold_action:{action:"more-info"}},e)]),this.updateActiveControl()}},{key:"updated",value:function(e){Hr(t,"updated",this,3)([e]),this.hass&&e.has("hass")&&this.updateActiveControl()}},{key:"updateActiveControl",value:function(){var e=!!this._activeControl&&this._controls.includes(this._activeControl);this._activeControl=e?this._activeControl:this._controls[0]}},{key:"_handleAction",value:function(e){Xs(this,this.hass,this._config,e.detail.action)}},{key:"render",value:function(){if(!this.hass||!this._config||!this._config.entity)return hl;var e=this._stateObj;if(!e)return this.renderNotFound(this._config);var t=this._config.name||e.attributes.friendly_name||"",n=this._config.icon,i=Gk(this._config),o=wk(e,i.icon_type),r=this.hass.formatEntityState(e);if(null!==e.attributes.current_temperature){var a=this.hass.formatEntityAttributeValue(e,"current_temperature");r+=" ⸱ ".concat(a)}var l=bs(this.hass),s=(!this._config.collapsible_controls||Os(e))&&this._controls.length;return cl(St||(St=Pr(["\n \n \n \n ","\n ","\n ",";\n \n ","\n \n
\n "])),Mk({"fill-container":i.fill_container}),i,l,l,i,this._handleAction,qs({hasHold:Zs(this._config.hold_action),hasDoubleClick:Zs(this._config.double_tap_action)}),o?this.renderPicture(o):this.renderIcon(e,n),this.renderBadge(e),this.renderStateInfo(e,i,t,r),s?cl(Tt||(Tt=Pr(['\n
\n \n \n '])),!Ms(this.entity),this._onStopTap):void 0,Vl(this.entity,2)?cl(Rt||(Rt=Pr(["\n \n \n \n "])),!Ms(this.entity)||this.closedDisabled,this._onCloseTap,function(e){switch(e.attributes.device_class){case"awning":case"curtain":case"door":case"gate":return"mdi:arrow-collapse-horizontal";default:return"mdi:arrow-down"}}(this.entity)):void 0)}}])}();ga([zl({attribute:!1})],wx.prototype,"hass",void 0),ga([zl({attribute:!1})],wx.prototype,"entity",void 0),ga([zl()],wx.prototype,"fill",void 0),wx=ga([Il("mushroom-cover-buttons-control")],wx);var xx,Ex,Cx={exports:{}}; + */var Ha,Da,Ra,Ua,Va,Fa=1,$a=function(t){return function(){for(var e=arguments.length,n=new Array(e),o=0;o2)throw Error("`classMap()` can only be used in the `class` attribute and must be the only part in the attribute.");return o}return or(e,Ga),pr(e,[{key:"render",value:function(t){return" "+Object.keys(t).filter((function(e){return t[e]})).join(" ")+" "}},{key:"update",value:function(t,e){var n=qi(e,1)[0];if(void 0===this.st){for(var o in this.st=new Set,void 0!==t.strings&&(this.nt=new Set(t.strings.join(" ").split(/\s/).filter((function(t){return""!==t})))),n){var i;n[o]&&(null===(i=this.nt)||void 0===i||!i.has(o))&&this.st.add(o)}return this.render(n)}var r,a=t.element.classList,s=lr(this.st);try{for(s.s();!(r=s.n()).done;){var l=r.value;l in n||(a.remove(l),this.st.delete(l))}}catch(t){s.e(t)}finally{s.f()}for(var c in n){var u,h=!!n[c];h===this.st.has(c)||(null===(u=this.nt)||void 0===u?void 0:u.has(c))||(h?(a.add(c),this.st.add(c)):(a.remove(c),this.st.delete(c)))}return pa}}])}()),Ya="important",qa=" !"+Ya,Wa=$a(function(t){function e(t){var n,o;if(hr(this,e),o=er(this,e,[t]),t.type!==Fa||"style"!==t.name||(null===(n=t.strings)||void 0===n?void 0:n.length)>2)throw Error("The `styleMap` directive must be used in the `style` attribute and must be the only part in the attribute.");return o}return or(e,Ga),pr(e,[{key:"render",value:function(t){return Object.keys(t).reduce((function(e,n){var o=t[n];return null==o?e:e+"".concat(n=n.includes("-")?n:n.replace(/(?:^(webkit|moz|ms|o)|)(?=[A-Z])/g,"-$&").toLowerCase(),":").concat(o,";")}),"")}},{key:"update",value:function(t,e){var n=qi(e,1)[0],o=t.element.style;if(void 0===this.ft)return this.ft=new Set(Object.keys(n)),this.render(n);var i,r=lr(this.ft);try{for(r.s();!(i=r.n()).done;){var a=i.value;null==n[a]&&(this.ft.delete(a),a.includes("-")?o.removeProperty(a):o[a]=null)}}catch(t){r.e(t)}finally{r.f()}for(var s in n){var l=n[s];if(null!=l){this.ft.add(s);var c="string"==typeof l&&l.endsWith(qa);s.includes("-")||c?o.setProperty(s,c?l.slice(0,-11):l,c?Ya:""):o[s]=l}}return pa}}])}()),Xa=new Set(["fan","input_boolean","light","switch","group","automation","humidifier","valve"]),Za=function(t,e,n,o){o=o||{},n=null==n?{}:n;var i=new Event(e,{bubbles:void 0===o.bubbles||o.bubbles,cancelable:Boolean(o.cancelable),composed:void 0===o.composed||o.composed});return i.detail=n,t.dispatchEvent(i),i},Ja=function(t){return t.substr(0,t.indexOf("."))},Qa=function(t){return Ja(t.entity_id)},ts=function(t,e){return es(t.attributes,e)},es=function(t,e){return 0!=(t.supported_features&e)},ns=function(t,e,n){return Math.min(Math.max(t,e),n)};!function(t){t.language="language",t.system="system",t.comma_decimal="comma_decimal",t.decimal_comma="decimal_comma",t.space_comma="space_comma",t.none="none"}(Ha||(Ha={})),function(t){t.language="language",t.system="system",t.am_pm="12",t.twenty_four="24"}(Da||(Da={})),function(t){t.local="local",t.server="server"}(Ra||(Ra={})),function(t){t.language="language",t.system="system",t.DMY="DMY",t.MDY="MDY",t.YMD="YMD"}(Ua||(Ua={})),function(t){t.language="language",t.monday="monday",t.tuesday="tuesday",t.wednesday="wednesday",t.thursday="thursday",t.friday="friday",t.saturday="saturday",t.sunday="sunday"}(Va||(Va={}));var os=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:2;return Math.round(t*Math.pow(10,e))/Math.pow(10,e)},is=function(t,e,n){var o=e?function(t){switch(t.number_format){case Ha.comma_decimal:return["en-US","en"];case Ha.decimal_comma:return["de","es","it"];case Ha.space_comma:return["fr","sv","cs"];case Ha.system:return;default:return t.language}}(e):void 0;if(Number.isNaN=Number.isNaN||function t(e){return"number"==typeof e&&t(e)},(null==e?void 0:e.number_format)!==Ha.none&&!Number.isNaN(Number(t))&&Intl)try{return new Intl.NumberFormat(o,rs(t,n)).format(Number(t))}catch(e){return console.error(e),new Intl.NumberFormat(void 0,rs(t,n)).format(Number(t))}return"string"==typeof t?t:"".concat(os(t,null==n?void 0:n.maximumFractionDigits).toString()).concat("currency"===(null==n?void 0:n.style)?" ".concat(n.currency):"")},rs=function(t,e){var n=Object.assign({maximumFractionDigits:2},e);if("string"!=typeof t)return n;if(!e||void 0===e.minimumFractionDigits&&void 0===e.maximumFractionDigits){var o=t.indexOf(".")>-1?t.split(".")[1].length:0;n.minimumFractionDigits=o,n.maximumFractionDigits=o}return n},as=function(t){function e(t,n){var o,i;hr(this,e);var r=t.message,a=t.explanation,s=function(t,e){if(null==t)return{};var n,o,i=function(t,e){if(null==t)return{};var n={};for(var o in t)if({}.hasOwnProperty.call(t,o)){if(-1!==e.indexOf(o))continue;n[o]=t[o]}return n}(t,e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(t);for(o=0;o2&&void 0!==arguments[2]?arguments[2]:{};return Zi().m((function o(){var i,r,a,s,l,c,u,h,d,p,f,m,v,g,_,y,b,k,w,C,E,x,A,S,T,M,z,O,I,j;return Zi().w((function(o){for(;;)switch(o.p=o.n){case 0:i=n.path,r=void 0===i?[]:i,a=n.branch,s=void 0===a?[t]:a,l=n.coerce,c=void 0!==l&&l,u=n.mask,d={path:r,branch:s,mask:h=void 0!==u&&u},c&&(t=e.coercer(t,d)),p="valid",f=lr(e.validator(t,d)),o.p=1,f.s();case 2:if((m=f.n()).done){o.n=4;break}return(v=m.value).explanation=n.message,p="not_valid",o.n=3,[v,void 0];case 3:o.n=2;break;case 4:o.n=6;break;case 5:o.p=5,z=o.v,f.e(z);case 6:return o.p=6,f.f(),o.f(6);case 7:g=lr(e.entries(t,d)),o.p=8,g.s();case 9:if((_=g.n()).done){o.n=19;break}y=qi(_.value,3),b=y[0],k=y[1],w=y[2],C=ps(k,w,{path:void 0===b?r:[].concat(Ki(r),[b]),branch:void 0===b?s:[].concat(Ki(s),[k]),coerce:c,mask:h,message:n.message}),E=lr(C),o.p=10,E.s();case 11:if((x=E.n()).done){o.n=15;break}if(!(A=x.value)[0]){o.n=13;break}return p=null!=A[0].refinement?"not_refined":"not_valid",o.n=12,[A[0],void 0];case 12:o.n=14;break;case 13:c&&(k=A[1],void 0===b?t=k:t instanceof Map?t.set(b,k):t instanceof Set?t.add(k):ls(t)&&(void 0!==k||b in t)&&(t[b]=k));case 14:o.n=11;break;case 15:o.n=17;break;case 16:o.p=16,O=o.v,E.e(O);case 17:return o.p=17,E.f(),o.f(17);case 18:o.n=9;break;case 19:o.n=21;break;case 20:o.p=20,I=o.v,g.e(I);case 21:return o.p=21,g.f(),o.f(21);case 22:if("not_valid"===p){o.n=29;break}S=lr(e.refiner(t,d)),o.p=23,S.s();case 24:if((T=S.n()).done){o.n=26;break}return(M=T.value).explanation=n.message,p="not_refined",o.n=25,[M,void 0];case 25:o.n=24;break;case 26:o.n=28;break;case 27:o.p=27,j=o.v,S.e(j);case 28:return o.p=28,S.f(),o.f(28);case 29:if("valid"!==p){o.n=30;break}return o.n=30,[void 0,t];case 30:return o.a(2)}}),o,null,[[23,27,28,29],[10,16,17,18],[8,20,21,22],[1,5,6,7]])}))()}var fs=function(){return pr((function t(e){var n=this;hr(this,t);var o=e.type,i=e.schema,r=e.validator,a=e.refiner,s=e.coercer,l=void 0===s?function(t){return t}:s,c=e.entries,u=void 0===c?Zi().m((function t(){return Zi().w((function(t){for(;;)if(0===t.n)return t.a(2)}),t)})):c;this.type=o,this.schema=i,this.entries=u,this.coercer=l,this.validator=r?function(t,e){return ds(r(t,e),e,n,t)}:function(){return[]},this.refiner=a?function(t,e){return ds(a(t,e),e,n,t)}:function(){return[]}}),[{key:"assert",value:function(t,e){return ms(t,this,e)}},{key:"create",value:function(t,e){return function(t,e,n){var o=vs(t,e,{coerce:!0,message:n});if(o[0])throw o[0];return o[1]}(t,this,e)}},{key:"is",value:function(t){return function(t,e){var n=vs(t,e);return!n[0]}(t,this)}},{key:"mask",value:function(t,e){return function(t,e,n){var o=vs(t,e,{coerce:!0,mask:!0,message:n});if(o[0])throw o[0];return o[1]}(t,this,e)}},{key:"validate",value:function(t){return vs(t,this,arguments.length>1&&void 0!==arguments[1]?arguments[1]:{})}}])}();function ms(t,e,n){var o=vs(t,e,{message:n});if(o[0])throw o[0]}function vs(t,e){var n=ps(t,e,arguments.length>2&&void 0!==arguments[2]?arguments[2]:{}),o=function(t){var e=t.next(),n=e.done,o=e.value;return n?void 0:o}(n);return o[0]?[new as(o[0],Zi().m((function t(){var e,o,i,r;return Zi().w((function(t){for(;;)switch(t.p=t.n){case 0:e=lr(n),t.p=1,e.s();case 2:if((o=e.n()).done){t.n=4;break}if(!(i=o.value)[0]){t.n=3;break}return t.n=3,i[0];case 3:t.n=2;break;case 4:t.n=6;break;case 5:t.p=5,r=t.v,e.e(r);case 6:return t.p=6,e.f(),t.f(6);case 7:return t.a(2)}}),t,null,[[1,5,6,7]])}))),void 0]:[void 0,o[1]]}function gs(){for(var t=arguments.length,e=new Array(t),n=0;n0||navigator.msMaxTouchPoints>0,nl=function(t){function e(){var t;return hr(this,e),(t=er(this,e,arguments)).holdTime=500,t.held=!1,t.cancelled=!1,t}return or(e,ir(HTMLElement)),pr(e,[{key:"connectedCallback",value:function(){var t=this;Object.assign(this.style,{position:"fixed",width:el?"100px":"50px",height:el?"100px":"50px",transform:"translate(-50%, -50%) scale(0)",pointerEvents:"none",zIndex:"999",background:"var(--primary-color)",display:null,opacity:"0.2",borderRadius:"50%",transition:"transform 180ms ease-in-out"}),["touchcancel","mouseout","mouseup","touchmove","mousewheel","wheel","scroll"].forEach((function(e){document.addEventListener(e,(function(){t.cancelled=!0,t.timer&&(t._stopAnimation(),clearTimeout(t.timer),t.timer=void 0)}),{passive:!0})}))}},{key:"bind",value:function(t){var e=this,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};t.actionHandler&&js(n,t.actionHandler.options)||(t.actionHandler?(t.removeEventListener("touchstart",t.actionHandler.start),t.removeEventListener("touchend",t.actionHandler.end),t.removeEventListener("touchcancel",t.actionHandler.end),t.removeEventListener("mousedown",t.actionHandler.start),t.removeEventListener("click",t.actionHandler.end),t.removeEventListener("keydown",t.actionHandler.handleKeyDown)):t.addEventListener("contextmenu",(function(t){var e=t||window.event;return e.preventDefault&&e.preventDefault(),e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0,e.returnValue=!1,!1})),t.actionHandler={options:n},n.disabled||(t.actionHandler.start=function(t){var o,i;e.cancelled=!1,t.touches?(o=t.touches[0].clientX,i=t.touches[0].clientY):(o=t.clientX,i=t.clientY),n.hasHold&&(e.held=!1,e.timer=window.setTimeout((function(){e._startAnimation(o,i),e.held=!0}),e.holdTime))},t.actionHandler.end=function(t){if(!("touchcancel"===t.type||"touchend"===t.type&&e.cancelled)){var o=t.target;t.cancelable&&t.preventDefault(),n.hasHold&&(clearTimeout(e.timer),e._stopAnimation(),e.timer=void 0),n.hasHold&&e.held?Za(o,"action",{action:"hold"}):n.hasDoubleClick?"click"===t.type&&t.detail<2||!e.dblClickTimeout?e.dblClickTimeout=window.setTimeout((function(){e.dblClickTimeout=void 0,!1!==n.hasTap&&Za(o,"action",{action:"tap"})}),250):(clearTimeout(e.dblClickTimeout),e.dblClickTimeout=void 0,Za(o,"action",{action:"double_tap"})):!1!==n.hasTap&&Za(o,"action",{action:"tap"})}},t.actionHandler.handleKeyDown=function(t){["Enter"," "].includes(t.key)&&t.currentTarget.actionHandler.end(t)},t.addEventListener("touchstart",t.actionHandler.start,{passive:!0}),t.addEventListener("touchend",t.actionHandler.end),t.addEventListener("touchcancel",t.actionHandler.end),t.addEventListener("mousedown",t.actionHandler.start,{passive:!0}),t.addEventListener("click",t.actionHandler.end),t.addEventListener("keydown",t.actionHandler.handleKeyDown)))}},{key:"_startAnimation",value:function(t,e){Object.assign(this.style,{left:"".concat(t,"px"),top:"".concat(e,"px"),transform:"translate(-50%, -50%) scale(1)"})}},{key:"_stopAnimation",value:function(){Object.assign(this.style,{left:null,top:null,transform:"translate(-50%, -50%) scale(0)"})}}])}(),ol=function(t,e){var n=function(){var t=document.body;if(t.querySelector("action-handler"))return t.querySelector("action-handler");customElements.get("action-handler")||customElements.define("action-handler",nl);var e=document.createElement("action-handler");return t.appendChild(e),e}();n&&n.bind(t,e)},il=$a(function(t){function e(){return hr(this,e),er(this,e,arguments)}return or(e,Ga),pr(e,[{key:"update",value:function(t,e){var n=qi(e,1)[0];return ol(t.element,n),pa}},{key:"render",value:function(t){}}])}()),rl=function(){var t=tr(Zi().m((function t(e,n,o,i){return Zi().w((function(t){for(;;)switch(t.n){case 0:Za(e,"hass-action",{config:o,action:i});case 1:return t.a(2)}}),t)})));return function(e,n,o,i){return t.apply(this,arguments)}}();function al(t){return void 0!==t&&"none"!==t.action}var sl=As({user:Ts()}),ll=zs([ws(),As({text:Ss(Ts()),excemptions:Ss(ks(sl))})]),cl=As({action:Es("url"),url_path:Ts(),confirmation:Ss(ll)}),ul=As({action:Cs(["call-service","perform-action"]),service:Ss(Ts()),perform_action:Ss(Ts()),service_data:Ss(As()),data:Ss(As()),target:Ss(As({entity_id:Ss(zs([Ts(),ks(Ts())])),device_id:Ss(zs([Ts(),ks(Ts())])),area_id:Ss(zs([Ts(),ks(Ts())])),floor_id:Ss(zs([Ts(),ks(Ts())])),label_id:Ss(zs([Ts(),ks(Ts())]))})),confirmation:Ss(ll)}),hl=As({action:Es("navigate"),navigation_path:Ts(),confirmation:Ss(ll)}),dl=Ms({action:Es("assist"),pipeline_id:Ss(Ts()),start_listening:Ss(ws())}),pl=Ms({action:Es("fire-dom-event")}),fl=As({action:Cs(["none","toggle","more-info","call-service","perform-action","url","navigate","assist"]),confirmation:Ss(ll)}),ml=ys((function(t){if(t&&"object"===mr(t)&&"action"in t)switch(t.action){case"call-service":case"perform-action":return ul;case"fire-dom-event":return pl;case"navigate":return hl;case"url":return cl;case"assist":return dl}return fl})),vl=Tr(a||(a=Li(['\n #sortable a:nth-of-type(2n) paper-icon-item {\n animation-name: keyframes1;\n animation-iteration-count: infinite;\n transform-origin: 50% 10%;\n animation-delay: -0.75s;\n animation-duration: 0.25s;\n }\n\n #sortable a:nth-of-type(2n-1) paper-icon-item {\n animation-name: keyframes2;\n animation-iteration-count: infinite;\n animation-direction: alternate;\n transform-origin: 30% 5%;\n animation-delay: -0.5s;\n animation-duration: 0.33s;\n }\n\n #sortable a {\n height: 48px;\n display: flex;\n }\n\n #sortable {\n outline: none;\n display: block !important;\n }\n\n .hidden-panel {\n display: flex !important;\n }\n\n .sortable-fallback {\n display: none;\n }\n\n .sortable-ghost {\n opacity: 0.4;\n }\n\n .sortable-fallback {\n opacity: 0;\n }\n\n @keyframes keyframes1 {\n 0% {\n transform: rotate(-1deg);\n animation-timing-function: ease-in;\n }\n\n 50% {\n transform: rotate(1.5deg);\n animation-timing-function: ease-out;\n }\n }\n\n @keyframes keyframes2 {\n 0% {\n transform: rotate(1deg);\n animation-timing-function: ease-in;\n }\n\n 50% {\n transform: rotate(-1.5deg);\n animation-timing-function: ease-out;\n }\n }\n\n .show-panel,\n .hide-panel {\n display: none;\n position: absolute;\n top: 0;\n right: 4px;\n --mdc-icon-button-size: 40px;\n }\n\n :host([rtl]) .show-panel {\n right: initial;\n left: 4px;\n }\n\n .hide-panel {\n top: 4px;\n right: 8px;\n }\n\n :host([rtl]) .hide-panel {\n right: initial;\n left: 8px;\n }\n\n :host([expanded]) .hide-panel {\n display: block;\n }\n\n :host([expanded]) .show-panel {\n display: inline-flex;\n }\n\n paper-icon-item.hidden-panel,\n paper-icon-item.hidden-panel span,\n paper-icon-item.hidden-panel ha-icon[slot="item-icon"] {\n color: var(--secondary-text-color);\n cursor: pointer;\n }\n']))),gl=function(t,e,n,o){var i=qi(t.split(".",3),3),r=i[0],a=i[1];i[2];return Number(r)>e||Number(r)===e&&Number(a)>=n||void 0!==o},_l=function(t,e){return t.callWS({type:"config/entity_registry/get",entity_id:e})};Ws((function(t){var e,n={},o=lr(t);try{for(o.s();!(e=o.n()).done;){var i=e.value;n[i.entity_id]=i}}catch(t){o.e(t)}finally{o.f()}return n})),Ws((function(t){var e,n={},o=lr(t);try{for(o.s();!(e=o.n()).done;){var i=e.value;n[i.id]=i}}catch(t){o.e(t)}finally{o.f()}return n}));var yl={armed_home:{feature:1,service:"alarm_arm_home",icon:"mdi:home"},armed_away:{feature:2,service:"alarm_arm_away",icon:"mdi:lock"},armed_night:{feature:4,service:"alarm_arm_night",icon:"mdi:moon-waning-crescent"},armed_vacation:{feature:32,service:"alarm_arm_vacation",icon:"mdi:airplane"},armed_custom_bypass:{feature:16,service:"alarm_arm_custom_bypass",icon:"mdi:shield"},disarmed:{service:"alarm_disarm",icon:"mdi:shield-off"}},bl=function(){var t=tr(Zi().m((function t(e,n,o,i){var r,a,s,l,c,u,h,d;return Zi().w((function(t){for(;;)switch(t.n){case 0:if(s=yl[i].service,!("disarmed"!==i&&o.attributes.code_arm_required||"disarmed"===i&&o.attributes.code_format)){t.n=5;break}return t.n=1,_l(n,o.entity_id).catch((function(){}));case 1:if(c=t.v,null===(a=null===(r=null==c?void 0:c.options)||void 0===r?void 0:r.alarm_control_panel)||void 0===a?void 0:a.default_code){t.n=5;break}return u="disarmed"===i,t.n=2,window.loadCardHelpers();case 2:return h=t.v,t.n=3,h.showEnterCodeDialog(e,{codeFormat:o.attributes.code_format,title:n.localize("ui.card.alarm_control_panel.".concat(u?"disarm":"arm")),submitText:n.localize("ui.card.alarm_control_panel.".concat(u?"disarm":"arm"))});case 3:if(null!=(d=t.v)){t.n=4;break}throw new Error("Code dialog closed");case 4:l=d;case 5:return t.n=6,n.callService("alarm_control_panel",s,{entity_id:o.entity_id,code:l});case 6:return t.a(2)}}),t)})));return function(e,n,o,i){return t.apply(this,arguments)}}(),kl=function(t){function e(){var t;return hr(this,e),(t=er(this,e,arguments)).icon="",t}return or(e,za),pr(e,[{key:"render",value:function(){return ha(s||(s=Li(['\n
\n \n ',"\n ","\n
\n "])),null!==(t=this.primary)&&void 0!==t?t:"",this.secondary?ha(C||(C=Li(['',""])),this.multiline_secondary?" multiline_secondary":"",this.secondary):fa)}}],[{key:"styles",get:function(){return Tr(E||(E=Li(["\n .container {\n min-width: 0;\n flex: 1;\n display: flex;\n flex-direction: column;\n }\n .primary {\n font-weight: var(--card-primary-font-weight);\n font-size: var(--card-primary-font-size);\n line-height: var(--card-primary-line-height);\n color: var(--card-primary-color);\n letter-spacing: var(--card-primary-letter-spacing);\n text-overflow: ellipsis;\n overflow: hidden;\n white-space: nowrap;\n }\n .secondary {\n font-weight: var(--card-secondary-font-weight);\n font-size: var(--card-secondary-font-size);\n line-height: var(--card-secondary-line-height);\n color: var(--card-secondary-color);\n letter-spacing: var(--card-secondary-letter-spacing);\n text-overflow: ellipsis;\n overflow: hidden;\n white-space: nowrap;\n }\n .multiline_secondary {\n white-space: pre-wrap;\n }\n "])))}}])}();br([Na({attribute:!1})],Ml.prototype,"primary",void 0),br([Na({attribute:!1})],Ml.prototype,"secondary",void 0),br([Na({type:Boolean})],Ml.prototype,"multiline_secondary",void 0),Ml=br([Ia("mushroom-state-info")],Ml);var zl=function(t){function e(){return hr(this,e),er(this,e,arguments)}return or(e,za),pr(e,[{key:"render",value:function(){var t,e,n,o;return ha(x||(x=Li(["\n \n ","\n ","\n
\n "])),Ka({container:!0,vertical:"vertical"===(null===(t=this.appearance)||void 0===t?void 0:t.layout)}),"none"!==(null===(e=this.appearance)||void 0===e?void 0:e.icon_type)?ha(A||(A=Li(['\n
\n \n \n
\n ']))):fa,"none"!==(null===(n=this.appearance)||void 0===n?void 0:n.primary_info)||"none"!==(null===(o=this.appearance)||void 0===o?void 0:o.secondary_info)?ha(S||(S=Li(['\n
\n \n
\n ']))):fa)}}],[{key:"styles",get:function(){return Tr(T||(T=Li(['\n :host {\n display: block;\n height: 100%;\n }\n .container {\n height: 100%;\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n padding: var(--spacing);\n gap: var(--spacing);\n }\n .icon {\n position: relative;\n }\n .icon ::slotted(*[slot="badge"]) {\n position: absolute;\n top: -3px;\n right: -3px;\n }\n :host([rtl]) .icon ::slotted(*[slot="badge"]) {\n right: initial;\n left: -3px;\n }\n .info {\n min-width: 0;\n width: 100%;\n display: flex;\n flex-direction: column;\n }\n .container.vertical {\n flex-direction: column;\n }\n .container.vertical .info {\n text-align: center;\n }\n '])))}}])}();function Ol(t){var e,n;return{layout:null!==(e=t.layout)&&void 0!==e?e:Il(t),fill_container:null!==(n=t.fill_container)&&void 0!==n&&n,primary_info:t.primary_info||Pl(t),secondary_info:t.secondary_info||Nl(t),icon_type:t.icon_type||jl(t)}}function Il(t){return t.vertical?"vertical":"default"}function jl(t){return t.hide_icon?"none":t.use_entity_picture||t.use_media_artwork?"entity-picture":"icon"}function Pl(t){return t.hide_name?"none":"name"}function Nl(t){return t.hide_state?"none":"state"}function Bl(t,e){var n=e&&e.cache?e.cache:Kl,o=e&&e.serializer?e.serializer:$l;return(e&&e.strategy?e.strategy:Rl)(t,{cache:n,serializer:o})}function Ll(t,e,n,o){var i,r=null==(i=o)||"number"==typeof i||"boolean"==typeof i?o:n(o),a=e.get(r);return void 0===a&&(a=t.call(this,o),e.set(r,a)),a}function Hl(t,e,n){var o=Array.prototype.slice.call(arguments,3),i=n(o),r=e.get(i);return void 0===r&&(r=t.apply(this,o),e.set(i,r)),r}function Dl(t,e,n,o,i){return n.bind(e,t,o,i)}function Rl(t,e){return Dl(t,this,1===t.length?Ll:Hl,e.cache.create(),e.serializer)}br([Na()],zl.prototype,"appearance",void 0),zl=br([Ia("mushroom-state-item")],zl);var Ul,Vl,Fl,$l=function(){return JSON.stringify(arguments)},Gl=function(){function t(){this.cache=Object.create(null)}return t.prototype.get=function(t){return this.cache[t]},t.prototype.set=function(t,e){this.cache[t]=e},t}(),Kl={create:function(){return new Gl}},Yl={variadic:function(t,e){return Dl(t,this,Hl,e.cache.create(),e.serializer)}};function ql(t){return t.type===Vl.literal}function Wl(t){return t.type===Vl.argument}function Xl(t){return t.type===Vl.number}function Zl(t){return t.type===Vl.date}function Jl(t){return t.type===Vl.time}function Ql(t){return t.type===Vl.select}function tc(t){return t.type===Vl.plural}function ec(t){return t.type===Vl.pound}function nc(t){return t.type===Vl.tag}function oc(t){return!(!t||"object"!==mr(t)||t.type!==Fl.number)}function ic(t){return!(!t||"object"!==mr(t)||t.type!==Fl.dateTime)}!function(t){t[t.EXPECT_ARGUMENT_CLOSING_BRACE=1]="EXPECT_ARGUMENT_CLOSING_BRACE",t[t.EMPTY_ARGUMENT=2]="EMPTY_ARGUMENT",t[t.MALFORMED_ARGUMENT=3]="MALFORMED_ARGUMENT",t[t.EXPECT_ARGUMENT_TYPE=4]="EXPECT_ARGUMENT_TYPE",t[t.INVALID_ARGUMENT_TYPE=5]="INVALID_ARGUMENT_TYPE",t[t.EXPECT_ARGUMENT_STYLE=6]="EXPECT_ARGUMENT_STYLE",t[t.INVALID_NUMBER_SKELETON=7]="INVALID_NUMBER_SKELETON",t[t.INVALID_DATE_TIME_SKELETON=8]="INVALID_DATE_TIME_SKELETON",t[t.EXPECT_NUMBER_SKELETON=9]="EXPECT_NUMBER_SKELETON",t[t.EXPECT_DATE_TIME_SKELETON=10]="EXPECT_DATE_TIME_SKELETON",t[t.UNCLOSED_QUOTE_IN_ARGUMENT_STYLE=11]="UNCLOSED_QUOTE_IN_ARGUMENT_STYLE",t[t.EXPECT_SELECT_ARGUMENT_OPTIONS=12]="EXPECT_SELECT_ARGUMENT_OPTIONS",t[t.EXPECT_PLURAL_ARGUMENT_OFFSET_VALUE=13]="EXPECT_PLURAL_ARGUMENT_OFFSET_VALUE",t[t.INVALID_PLURAL_ARGUMENT_OFFSET_VALUE=14]="INVALID_PLURAL_ARGUMENT_OFFSET_VALUE",t[t.EXPECT_SELECT_ARGUMENT_SELECTOR=15]="EXPECT_SELECT_ARGUMENT_SELECTOR",t[t.EXPECT_PLURAL_ARGUMENT_SELECTOR=16]="EXPECT_PLURAL_ARGUMENT_SELECTOR",t[t.EXPECT_SELECT_ARGUMENT_SELECTOR_FRAGMENT=17]="EXPECT_SELECT_ARGUMENT_SELECTOR_FRAGMENT",t[t.EXPECT_PLURAL_ARGUMENT_SELECTOR_FRAGMENT=18]="EXPECT_PLURAL_ARGUMENT_SELECTOR_FRAGMENT",t[t.INVALID_PLURAL_ARGUMENT_SELECTOR=19]="INVALID_PLURAL_ARGUMENT_SELECTOR",t[t.DUPLICATE_PLURAL_ARGUMENT_SELECTOR=20]="DUPLICATE_PLURAL_ARGUMENT_SELECTOR",t[t.DUPLICATE_SELECT_ARGUMENT_SELECTOR=21]="DUPLICATE_SELECT_ARGUMENT_SELECTOR",t[t.MISSING_OTHER_CLAUSE=22]="MISSING_OTHER_CLAUSE",t[t.INVALID_TAG=23]="INVALID_TAG",t[t.INVALID_TAG_NAME=25]="INVALID_TAG_NAME",t[t.UNMATCHED_CLOSING_TAG=26]="UNMATCHED_CLOSING_TAG",t[t.UNCLOSED_TAG=27]="UNCLOSED_TAG"}(Ul||(Ul={})),function(t){t[t.literal=0]="literal",t[t.argument=1]="argument",t[t.number=2]="number",t[t.date=3]="date",t[t.time=4]="time",t[t.select=5]="select",t[t.plural=6]="plural",t[t.pound=7]="pound",t[t.tag=8]="tag"}(Vl||(Vl={})),function(t){t[t.number=0]="number",t[t.dateTime=1]="dateTime"}(Fl||(Fl={}));var rc=/[ \xA0\u1680\u2000-\u200A\u202F\u205F\u3000]/,ac=/(?:[Eec]{1,6}|G{1,5}|[Qq]{1,5}|(?:[yYur]+|U{1,5})|[ML]{1,5}|d{1,2}|D{1,3}|F{1}|[abB]{1,5}|[hkHK]{1,2}|w{1,2}|W{1}|m{1,2}|s{1,2}|[zZOvVxX]{1,4})(?=([^']*'[^']*')*[^']*$)/g;function sc(t){var e={};return t.replace(ac,(function(t){var n=t.length;switch(t[0]){case"G":e.era=4===n?"long":5===n?"narrow":"short";break;case"y":e.year=2===n?"2-digit":"numeric";break;case"Y":case"u":case"U":case"r":throw new RangeError("`Y/u/U/r` (year) patterns are not supported, use `y` instead");case"q":case"Q":throw new RangeError("`q/Q` (quarter) patterns are not supported");case"M":case"L":e.month=["numeric","2-digit","short","long","narrow"][n-1];break;case"w":case"W":throw new RangeError("`w/W` (week) patterns are not supported");case"d":e.day=["numeric","2-digit"][n-1];break;case"D":case"F":case"g":throw new RangeError("`D/F/g` (day) patterns are not supported, use `d` instead");case"E":e.weekday=4===n?"long":5===n?"narrow":"short";break;case"e":if(n<4)throw new RangeError("`e..eee` (weekday) patterns are not supported");e.weekday=["short","long","narrow","short"][n-4];break;case"c":if(n<4)throw new RangeError("`c..ccc` (weekday) patterns are not supported");e.weekday=["short","long","narrow","short"][n-4];break;case"a":e.hour12=!0;break;case"b":case"B":throw new RangeError("`b/B` (period) patterns are not supported, use `a` instead");case"h":e.hourCycle="h12",e.hour=["numeric","2-digit"][n-1];break;case"H":e.hourCycle="h23",e.hour=["numeric","2-digit"][n-1];break;case"K":e.hourCycle="h11",e.hour=["numeric","2-digit"][n-1];break;case"k":e.hourCycle="h24",e.hour=["numeric","2-digit"][n-1];break;case"j":case"J":case"C":throw new RangeError("`j/J/C` (hour) patterns are not supported, use `h/H/K/k` instead");case"m":e.minute=["numeric","2-digit"][n-1];break;case"s":e.second=["numeric","2-digit"][n-1];break;case"S":case"A":throw new RangeError("`S/A` (second) patterns are not supported, use `s` instead");case"z":e.timeZoneName=n<4?"short":"long";break;case"Z":case"O":case"v":case"V":case"X":case"x":throw new RangeError("`Z/O/v/V/X/x` (timeZone) patterns are not supported, use `z` instead")}return""})),e}var lc=/[\t-\r \x85\u200E\u200F\u2028\u2029]/i;var cc=/^\.(?:(0+)(\*)?|(#+)|(0+)(#+))$/g,uc=/^(@+)?(\+|#+)?[rs]?$/g,hc=/(\*)(0+)|(#+)(0+)|(0+)/g,dc=/^(0+)$/;function pc(t){var e={};return"r"===t[t.length-1]?e.roundingPriority="morePrecision":"s"===t[t.length-1]&&(e.roundingPriority="lessPrecision"),t.replace(uc,(function(t,n,o){return"string"!=typeof o?(e.minimumSignificantDigits=n.length,e.maximumSignificantDigits=n.length):"+"===o?e.minimumSignificantDigits=n.length:"#"===n[0]?e.maximumSignificantDigits=n.length:(e.minimumSignificantDigits=n.length,e.maximumSignificantDigits=n.length+("string"==typeof o?o.length:0)),""})),e}function fc(t){switch(t){case"sign-auto":return{signDisplay:"auto"};case"sign-accounting":case"()":return{currencySign:"accounting"};case"sign-always":case"+!":return{signDisplay:"always"};case"sign-accounting-always":case"()!":return{signDisplay:"always",currencySign:"accounting"};case"sign-except-zero":case"+?":return{signDisplay:"exceptZero"};case"sign-accounting-except-zero":case"()?":return{signDisplay:"exceptZero",currencySign:"accounting"};case"sign-never":case"+_":return{signDisplay:"never"}}}function mc(t){var e;if("E"===t[0]&&"E"===t[1]?(e={notation:"engineering"},t=t.slice(2)):"E"===t[0]&&(e={notation:"scientific"},t=t.slice(1)),e){var n=t.slice(0,2);if("+!"===n?(e.signDisplay="always",t=t.slice(2)):"+?"===n&&(e.signDisplay="exceptZero",t=t.slice(2)),!dc.test(t))throw new Error("Malformed concise eng/scientific notation");e.minimumIntegerDigits=t.length}return e}function vc(t){var e=fc(t);return e||{}}function gc(t){for(var e={},n=0,o=t;n1)throw new RangeError("integer-width stems only accept a single optional option");i.options[0].replace(hc,(function(t,n,o,i,r,a){if(n)e.minimumIntegerDigits=o.length;else{if(i&&r)throw new Error("We currently do not support maximum integer digits");if(a)throw new Error("We currently do not support exact integer digits")}return""}));continue}if(dc.test(i.stem))e.minimumIntegerDigits=i.stem.length;else if(cc.test(i.stem)){if(i.options.length>1)throw new RangeError("Fraction-precision stems only accept a single optional option");i.stem.replace(cc,(function(t,n,o,i,r,a){return"*"===o?e.minimumFractionDigits=n.length:i&&"#"===i[0]?e.maximumFractionDigits=i.length:r&&a?(e.minimumFractionDigits=r.length,e.maximumFractionDigits=r.length+a.length):(e.minimumFractionDigits=n.length,e.maximumFractionDigits=n.length),""}));var r=i.options[0];"w"===r?e=yr(yr({},e),{trailingZeroDisplay:"stripIfInteger"}):r&&(e=yr(yr({},e),pc(r)))}else if(uc.test(i.stem))e=yr(yr({},e),pc(i.stem));else{var a=fc(i.stem);a&&(e=yr(yr({},e),a));var s=mc(i.stem);s&&(e=yr(yr({},e),s))}}return e}var _c,yc={"001":["H","h"],419:["h","H","hB","hb"],AC:["H","h","hb","hB"],AD:["H","hB"],AE:["h","hB","hb","H"],AF:["H","hb","hB","h"],AG:["h","hb","H","hB"],AI:["H","h","hb","hB"],AL:["h","H","hB"],AM:["H","hB"],AO:["H","hB"],AR:["h","H","hB","hb"],AS:["h","H"],AT:["H","hB"],AU:["h","hb","H","hB"],AW:["H","hB"],AX:["H"],AZ:["H","hB","h"],BA:["H","hB","h"],BB:["h","hb","H","hB"],BD:["h","hB","H"],BE:["H","hB"],BF:["H","hB"],BG:["H","hB","h"],BH:["h","hB","hb","H"],BI:["H","h"],BJ:["H","hB"],BL:["H","hB"],BM:["h","hb","H","hB"],BN:["hb","hB","h","H"],BO:["h","H","hB","hb"],BQ:["H"],BR:["H","hB"],BS:["h","hb","H","hB"],BT:["h","H"],BW:["H","h","hb","hB"],BY:["H","h"],BZ:["H","h","hb","hB"],CA:["h","hb","H","hB"],CC:["H","h","hb","hB"],CD:["hB","H"],CF:["H","h","hB"],CG:["H","hB"],CH:["H","hB","h"],CI:["H","hB"],CK:["H","h","hb","hB"],CL:["h","H","hB","hb"],CM:["H","h","hB"],CN:["H","hB","hb","h"],CO:["h","H","hB","hb"],CP:["H"],CR:["h","H","hB","hb"],CU:["h","H","hB","hb"],CV:["H","hB"],CW:["H","hB"],CX:["H","h","hb","hB"],CY:["h","H","hb","hB"],CZ:["H"],DE:["H","hB"],DG:["H","h","hb","hB"],DJ:["h","H"],DK:["H"],DM:["h","hb","H","hB"],DO:["h","H","hB","hb"],DZ:["h","hB","hb","H"],EA:["H","h","hB","hb"],EC:["h","H","hB","hb"],EE:["H","hB"],EG:["h","hB","hb","H"],EH:["h","hB","hb","H"],ER:["h","H"],ES:["H","hB","h","hb"],ET:["hB","hb","h","H"],FI:["H"],FJ:["h","hb","H","hB"],FK:["H","h","hb","hB"],FM:["h","hb","H","hB"],FO:["H","h"],FR:["H","hB"],GA:["H","hB"],GB:["H","h","hb","hB"],GD:["h","hb","H","hB"],GE:["H","hB","h"],GF:["H","hB"],GG:["H","h","hb","hB"],GH:["h","H"],GI:["H","h","hb","hB"],GL:["H","h"],GM:["h","hb","H","hB"],GN:["H","hB"],GP:["H","hB"],GQ:["H","hB","h","hb"],GR:["h","H","hb","hB"],GT:["h","H","hB","hb"],GU:["h","hb","H","hB"],GW:["H","hB"],GY:["h","hb","H","hB"],HK:["h","hB","hb","H"],HN:["h","H","hB","hb"],HR:["H","hB"],HU:["H","h"],IC:["H","h","hB","hb"],ID:["H"],IE:["H","h","hb","hB"],IL:["H","hB"],IM:["H","h","hb","hB"],IN:["h","H"],IO:["H","h","hb","hB"],IQ:["h","hB","hb","H"],IR:["hB","H"],IS:["H"],IT:["H","hB"],JE:["H","h","hb","hB"],JM:["h","hb","H","hB"],JO:["h","hB","hb","H"],JP:["H","K","h"],KE:["hB","hb","H","h"],KG:["H","h","hB","hb"],KH:["hB","h","H","hb"],KI:["h","hb","H","hB"],KM:["H","h","hB","hb"],KN:["h","hb","H","hB"],KP:["h","H","hB","hb"],KR:["h","H","hB","hb"],KW:["h","hB","hb","H"],KY:["h","hb","H","hB"],KZ:["H","hB"],LA:["H","hb","hB","h"],LB:["h","hB","hb","H"],LC:["h","hb","H","hB"],LI:["H","hB","h"],LK:["H","h","hB","hb"],LR:["h","hb","H","hB"],LS:["h","H"],LT:["H","h","hb","hB"],LU:["H","h","hB"],LV:["H","hB","hb","h"],LY:["h","hB","hb","H"],MA:["H","h","hB","hb"],MC:["H","hB"],MD:["H","hB"],ME:["H","hB","h"],MF:["H","hB"],MG:["H","h"],MH:["h","hb","H","hB"],MK:["H","h","hb","hB"],ML:["H"],MM:["hB","hb","H","h"],MN:["H","h","hb","hB"],MO:["h","hB","hb","H"],MP:["h","hb","H","hB"],MQ:["H","hB"],MR:["h","hB","hb","H"],MS:["H","h","hb","hB"],MT:["H","h"],MU:["H","h"],MV:["H","h"],MW:["h","hb","H","hB"],MX:["h","H","hB","hb"],MY:["hb","hB","h","H"],MZ:["H","hB"],NA:["h","H","hB","hb"],NC:["H","hB"],NE:["H"],NF:["H","h","hb","hB"],NG:["H","h","hb","hB"],NI:["h","H","hB","hb"],NL:["H","hB"],NO:["H","h"],NP:["H","h","hB"],NR:["H","h","hb","hB"],NU:["H","h","hb","hB"],NZ:["h","hb","H","hB"],OM:["h","hB","hb","H"],PA:["h","H","hB","hb"],PE:["h","H","hB","hb"],PF:["H","h","hB"],PG:["h","H"],PH:["h","hB","hb","H"],PK:["h","hB","H"],PL:["H","h"],PM:["H","hB"],PN:["H","h","hb","hB"],PR:["h","H","hB","hb"],PS:["h","hB","hb","H"],PT:["H","hB"],PW:["h","H"],PY:["h","H","hB","hb"],QA:["h","hB","hb","H"],RE:["H","hB"],RO:["H","hB"],RS:["H","hB","h"],RU:["H"],RW:["H","h"],SA:["h","hB","hb","H"],SB:["h","hb","H","hB"],SC:["H","h","hB"],SD:["h","hB","hb","H"],SE:["H"],SG:["h","hb","H","hB"],SH:["H","h","hb","hB"],SI:["H","hB"],SJ:["H"],SK:["H"],SL:["h","hb","H","hB"],SM:["H","h","hB"],SN:["H","h","hB"],SO:["h","H"],SR:["H","hB"],SS:["h","hb","H","hB"],ST:["H","hB"],SV:["h","H","hB","hb"],SX:["H","h","hb","hB"],SY:["h","hB","hb","H"],SZ:["h","hb","H","hB"],TA:["H","h","hb","hB"],TC:["h","hb","H","hB"],TD:["h","H","hB"],TF:["H","h","hB"],TG:["H","hB"],TH:["H","h"],TJ:["H","h"],TL:["H","hB","hb","h"],TM:["H","h"],TN:["h","hB","hb","H"],TO:["h","H"],TR:["H","hB"],TT:["h","hb","H","hB"],TW:["hB","hb","h","H"],TZ:["hB","hb","H","h"],UA:["H","hB","h"],UG:["hB","hb","H","h"],UM:["h","hb","H","hB"],US:["h","hb","H","hB"],UY:["h","H","hB","hb"],UZ:["H","hB","h"],VA:["H","h","hB"],VC:["h","hb","H","hB"],VE:["h","H","hB","hb"],VG:["h","hb","H","hB"],VI:["h","hb","H","hB"],VN:["H","h"],VU:["h","H"],WF:["H","hB"],WS:["h","H"],XK:["H","hB","h"],YE:["h","hB","hb","H"],YT:["H","hB"],ZA:["H","h","hb","hB"],ZM:["h","hb","H","hB"],ZW:["H","h"],"af-ZA":["H","h","hB","hb"],"ar-001":["h","hB","hb","H"],"ca-ES":["H","h","hB"],"en-001":["h","hb","H","hB"],"en-HK":["h","hb","H","hB"],"en-IL":["H","h","hb","hB"],"en-MY":["h","hb","H","hB"],"es-BR":["H","h","hB","hb"],"es-ES":["H","h","hB","hb"],"es-GQ":["H","h","hB","hb"],"fr-CA":["H","h","hB"],"gl-ES":["H","h","hB"],"gu-IN":["hB","hb","h","H"],"hi-IN":["hB","h","H"],"it-CH":["H","h","hB"],"it-IT":["H","h","hB"],"kn-IN":["hB","h","H"],"ml-IN":["hB","h","H"],"mr-IN":["hB","hb","h","H"],"pa-IN":["hB","hb","h","H"],"ta-IN":["hB","h","hb","H"],"te-IN":["hB","h","H"],"zu-ZA":["H","hB","hb","h"]};function bc(t){var e=t.hourCycle;if(void 0===e&&t.hourCycles&&t.hourCycles.length&&(e=t.hourCycles[0]),e)switch(e){case"h24":return"k";case"h23":return"H";case"h12":return"h";case"h11":return"K";default:throw new Error("Invalid hourCycle")}var n,o=t.language;return"root"!==o&&(n=t.maximize().region),(yc[n||""]||yc[o||""]||yc["".concat(o,"-001")]||yc["001"])[0]}var kc=new RegExp("^".concat(rc.source,"*")),wc=new RegExp("".concat(rc.source,"*$"));function Cc(t,e){return{start:t,end:e}}var Ec=!!String.prototype.startsWith&&"_a".startsWith("a",1),xc=!!String.fromCodePoint,Ac=!!Object.fromEntries,Sc=!!String.prototype.codePointAt,Tc=!!String.prototype.trimStart,Mc=!!String.prototype.trimEnd,zc=!!Number.isSafeInteger?Number.isSafeInteger:function(t){return"number"==typeof t&&isFinite(t)&&Math.floor(t)===t&&Math.abs(t)<=9007199254740991},Oc=!0;try{Oc="a"===(null===(_c=Dc("([^\\p{White_Space}\\p{Pattern_Syntax}]*)","yu").exec("a"))||void 0===_c?void 0:_c[0])}catch(ra){Oc=!1}var Ic,jc=Ec?function(t,e,n){return t.startsWith(e,n)}:function(t,e,n){return t.slice(n,n+e.length)===e},Pc=xc?String.fromCodePoint:function(){for(var t=[],e=0;er;){if((n=t[r++])>1114111)throw RangeError(n+" is not a valid code point");o+=n<65536?String.fromCharCode(n):String.fromCharCode(55296+((n-=65536)>>10),n%1024+56320)}return o},Nc=Ac?Object.fromEntries:function(t){for(var e={},n=0,o=t;n=n)){var o,i=t.charCodeAt(e);return i<55296||i>56319||e+1===n||(o=t.charCodeAt(e+1))<56320||o>57343?i:o-56320+(i-55296<<10)+65536}},Lc=Tc?function(t){return t.trimStart()}:function(t){return t.replace(kc,"")},Hc=Mc?function(t){return t.trimEnd()}:function(t){return t.replace(wc,"")};function Dc(t,e){return new RegExp(t,e)}if(Oc){var Rc=Dc("([^\\p{White_Space}\\p{Pattern_Syntax}]*)","yu");Ic=function(t,e){var n;return Rc.lastIndex=e,null!==(n=Rc.exec(t)[1])&&void 0!==n?n:""}}else Ic=function(t,e){for(var n=[];;){var o=Bc(t,e);if(void 0===o||Gc(o)||Kc(o))break;n.push(o),e+=o>=65536?2:1}return Pc.apply(void 0,n)};var Uc,Vc=function(){function t(t,e){void 0===e&&(e={}),this.message=t,this.position={offset:0,line:1,column:1},this.ignoreTag=!!e.ignoreTag,this.locale=e.locale,this.requiresOtherClause=!!e.requiresOtherClause,this.shouldParseSkeletons=!!e.shouldParseSkeletons}return t.prototype.parse=function(){if(0!==this.offset())throw Error("parser can only be used once");return this.parseMessage(0,"",!1)},t.prototype.parseMessage=function(t,e,n){for(var o=[];!this.isEOF();){var i=this.char();if(123===i){if((r=this.parseArgument(t,n)).err)return r;o.push(r.val)}else{if(125===i&&t>0)break;if(35!==i||"plural"!==e&&"selectordinal"!==e){if(60===i&&!this.ignoreTag&&47===this.peek()){if(n)break;return this.error(Ul.UNMATCHED_CLOSING_TAG,Cc(this.clonePosition(),this.clonePosition()))}if(60===i&&!this.ignoreTag&&Fc(this.peek()||0)){if((r=this.parseTag(t,e)).err)return r;o.push(r.val)}else{var r;if((r=this.parseLiteral(t,e)).err)return r;o.push(r.val)}}else{var a=this.clonePosition();this.bump(),o.push({type:Vl.pound,location:Cc(a,this.clonePosition())})}}}return{val:o,err:null}},t.prototype.parseTag=function(t,e){var n=this.clonePosition();this.bump();var o=this.parseTagName();if(this.bumpSpace(),this.bumpIf("/>"))return{val:{type:Vl.literal,value:"<".concat(o,"/>"),location:Cc(n,this.clonePosition())},err:null};if(this.bumpIf(">")){var i=this.parseMessage(t+1,e,!0);if(i.err)return i;var r=i.val,a=this.clonePosition();if(this.bumpIf("")?{val:{type:Vl.tag,value:o,children:r,location:Cc(n,this.clonePosition())},err:null}:this.error(Ul.INVALID_TAG,Cc(a,this.clonePosition())))}return this.error(Ul.UNCLOSED_TAG,Cc(n,this.clonePosition()))}return this.error(Ul.INVALID_TAG,Cc(n,this.clonePosition()))},t.prototype.parseTagName=function(){var t=this.offset();for(this.bump();!this.isEOF()&&$c(this.char());)this.bump();return this.message.slice(t,this.offset())},t.prototype.parseLiteral=function(t,e){for(var n=this.clonePosition(),o="";;){var i=this.tryParseQuote(e);if(i)o+=i;else{var r=this.tryParseUnquoted(t,e);if(r)o+=r;else{var a=this.tryParseLeftAngleBracket();if(!a)break;o+=a}}}var s=Cc(n,this.clonePosition());return{val:{type:Vl.literal,value:o,location:s},err:null}},t.prototype.tryParseLeftAngleBracket=function(){return this.isEOF()||60!==this.char()||!this.ignoreTag&&(Fc(t=this.peek()||0)||47===t)?null:(this.bump(),"<");var t},t.prototype.tryParseQuote=function(t){if(this.isEOF()||39!==this.char())return null;switch(this.peek()){case 39:return this.bump(),this.bump(),"'";case 123:case 60:case 62:case 125:break;case 35:if("plural"===t||"selectordinal"===t)break;return null;default:return null}this.bump();var e=[this.char()];for(this.bump();!this.isEOF();){var n=this.char();if(39===n){if(39!==this.peek()){this.bump();break}e.push(39),this.bump()}else e.push(n);this.bump()}return Pc.apply(void 0,e)},t.prototype.tryParseUnquoted=function(t,e){if(this.isEOF())return null;var n=this.char();return 60===n||123===n||35===n&&("plural"===e||"selectordinal"===e)||125===n&&t>0?null:(this.bump(),Pc(n))},t.prototype.parseArgument=function(t,e){var n=this.clonePosition();if(this.bump(),this.bumpSpace(),this.isEOF())return this.error(Ul.EXPECT_ARGUMENT_CLOSING_BRACE,Cc(n,this.clonePosition()));if(125===this.char())return this.bump(),this.error(Ul.EMPTY_ARGUMENT,Cc(n,this.clonePosition()));var o=this.parseIdentifierIfPossible().value;if(!o)return this.error(Ul.MALFORMED_ARGUMENT,Cc(n,this.clonePosition()));if(this.bumpSpace(),this.isEOF())return this.error(Ul.EXPECT_ARGUMENT_CLOSING_BRACE,Cc(n,this.clonePosition()));switch(this.char()){case 125:return this.bump(),{val:{type:Vl.argument,value:o,location:Cc(n,this.clonePosition())},err:null};case 44:return this.bump(),this.bumpSpace(),this.isEOF()?this.error(Ul.EXPECT_ARGUMENT_CLOSING_BRACE,Cc(n,this.clonePosition())):this.parseArgumentOptions(t,e,o,n);default:return this.error(Ul.MALFORMED_ARGUMENT,Cc(n,this.clonePosition()))}},t.prototype.parseIdentifierIfPossible=function(){var t=this.clonePosition(),e=this.offset(),n=Ic(this.message,e),o=e+n.length;return this.bumpTo(o),{value:n,location:Cc(t,this.clonePosition())}},t.prototype.parseArgumentOptions=function(t,e,n,o){var i,r=this.clonePosition(),a=this.parseIdentifierIfPossible().value,s=this.clonePosition();switch(a){case"":return this.error(Ul.EXPECT_ARGUMENT_TYPE,Cc(r,s));case"number":case"date":case"time":this.bumpSpace();var l=null;if(this.bumpIf(",")){this.bumpSpace();var c=this.clonePosition();if((g=this.parseSimpleArgStyleIfPossible()).err)return g;if(0===(p=Hc(g.val)).length)return this.error(Ul.EXPECT_ARGUMENT_STYLE,Cc(this.clonePosition(),this.clonePosition()));l={style:p,styleLocation:Cc(c,this.clonePosition())}}if((_=this.tryParseArgumentClose(o)).err)return _;var u=Cc(o,this.clonePosition());if(l&&jc(null==l?void 0:l.style,"::",0)){var h=Lc(l.style.slice(2));if("number"===a)return(g=this.parseNumberSkeletonFromString(h,l.styleLocation)).err?g:{val:{type:Vl.number,value:n,location:u,style:g.val},err:null};if(0===h.length)return this.error(Ul.EXPECT_DATE_TIME_SKELETON,u);var d=h;this.locale&&(d=function(t,e){for(var n="",o=0;o>1),l=bc(e);for("H"!=l&&"k"!=l||(s=0);s-- >0;)n+="a";for(;a-- >0;)n=l+n}else n+="J"===i?"H":i}return n}(h,this.locale));var p={type:Fl.dateTime,pattern:d,location:l.styleLocation,parsedOptions:this.shouldParseSkeletons?sc(d):{}};return{val:{type:"date"===a?Vl.date:Vl.time,value:n,location:u,style:p},err:null}}return{val:{type:"number"===a?Vl.number:"date"===a?Vl.date:Vl.time,value:n,location:u,style:null!==(i=null==l?void 0:l.style)&&void 0!==i?i:null},err:null};case"plural":case"selectordinal":case"select":var f=this.clonePosition();if(this.bumpSpace(),!this.bumpIf(","))return this.error(Ul.EXPECT_SELECT_ARGUMENT_OPTIONS,Cc(f,yr({},f)));this.bumpSpace();var m=this.parseIdentifierIfPossible(),v=0;if("select"!==a&&"offset"===m.value){if(!this.bumpIf(":"))return this.error(Ul.EXPECT_PLURAL_ARGUMENT_OFFSET_VALUE,Cc(this.clonePosition(),this.clonePosition()));var g;if(this.bumpSpace(),(g=this.tryParseDecimalInteger(Ul.EXPECT_PLURAL_ARGUMENT_OFFSET_VALUE,Ul.INVALID_PLURAL_ARGUMENT_OFFSET_VALUE)).err)return g;this.bumpSpace(),m=this.parseIdentifierIfPossible(),v=g.val}var _,y=this.tryParsePluralOrSelectOptions(t,a,e,m);if(y.err)return y;if((_=this.tryParseArgumentClose(o)).err)return _;var b=Cc(o,this.clonePosition());return"select"===a?{val:{type:Vl.select,value:n,options:Nc(y.val),location:b},err:null}:{val:{type:Vl.plural,value:n,options:Nc(y.val),offset:v,pluralType:"plural"===a?"cardinal":"ordinal",location:b},err:null};default:return this.error(Ul.INVALID_ARGUMENT_TYPE,Cc(r,s))}},t.prototype.tryParseArgumentClose=function(t){return this.isEOF()||125!==this.char()?this.error(Ul.EXPECT_ARGUMENT_CLOSING_BRACE,Cc(t,this.clonePosition())):(this.bump(),{val:!0,err:null})},t.prototype.parseSimpleArgStyleIfPossible=function(){for(var t=0,e=this.clonePosition();!this.isEOF();){switch(this.char()){case 39:this.bump();var n=this.clonePosition();if(!this.bumpUntil("'"))return this.error(Ul.UNCLOSED_QUOTE_IN_ARGUMENT_STYLE,Cc(n,this.clonePosition()));this.bump();break;case 123:t+=1,this.bump();break;case 125:if(!(t>0))return{val:this.message.slice(e.offset,this.offset()),err:null};t-=1;break;default:this.bump()}}return{val:this.message.slice(e.offset,this.offset()),err:null}},t.prototype.parseNumberSkeletonFromString=function(t,e){var n=[];try{n=function(t){if(0===t.length)throw new Error("Number skeleton cannot be empty");for(var e=t.split(lc).filter((function(t){return t.length>0})),n=[],o=0,i=e;o=48&&a<=57))break;i=!0,r=10*r+(a-48),this.bump()}var s=Cc(o,this.clonePosition());return i?zc(r*=n)?{val:r,err:null}:this.error(e,s):this.error(t,s)},t.prototype.offset=function(){return this.position.offset},t.prototype.isEOF=function(){return this.offset()===this.message.length},t.prototype.clonePosition=function(){return{offset:this.position.offset,line:this.position.line,column:this.position.column}},t.prototype.char=function(){var t=this.position.offset;if(t>=this.message.length)throw Error("out of bound");var e=Bc(this.message,t);if(void 0===e)throw Error("Offset ".concat(t," is at invalid UTF-16 code unit boundary"));return e},t.prototype.error=function(t,e){return{val:null,err:{kind:t,message:this.message,location:e}}},t.prototype.bump=function(){if(!this.isEOF()){var t=this.char();10===t?(this.position.line+=1,this.position.column=1,this.position.offset+=1):(this.position.column+=1,this.position.offset+=t<65536?1:2)}},t.prototype.bumpIf=function(t){if(jc(this.message,t,this.offset())){for(var e=0;e=0?(this.bumpTo(n),!0):(this.bumpTo(this.message.length),!1)},t.prototype.bumpTo=function(t){if(this.offset()>t)throw Error("targetOffset ".concat(t," must be greater than or equal to the current offset ").concat(this.offset()));for(t=Math.min(t,this.message.length);;){var e=this.offset();if(e===t)break;if(e>t)throw Error("targetOffset ".concat(t," is at invalid UTF-16 code unit boundary"));if(this.bump(),this.isEOF())break}},t.prototype.bumpSpace=function(){for(;!this.isEOF()&&Gc(this.char());)this.bump()},t.prototype.peek=function(){if(this.isEOF())return null;var t=this.char(),e=this.offset(),n=this.message.charCodeAt(e+(t>=65536?2:1));return null!=n?n:null},t}();function Fc(t){return t>=97&&t<=122||t>=65&&t<=90}function $c(t){return 45===t||46===t||t>=48&&t<=57||95===t||t>=97&&t<=122||t>=65&&t<=90||183==t||t>=192&&t<=214||t>=216&&t<=246||t>=248&&t<=893||t>=895&&t<=8191||t>=8204&&t<=8205||t>=8255&&t<=8256||t>=8304&&t<=8591||t>=11264&&t<=12271||t>=12289&&t<=55295||t>=63744&&t<=64975||t>=65008&&t<=65533||t>=65536&&t<=983039}function Gc(t){return t>=9&&t<=13||32===t||133===t||t>=8206&&t<=8207||8232===t||8233===t}function Kc(t){return t>=33&&t<=35||36===t||t>=37&&t<=39||40===t||41===t||42===t||43===t||44===t||45===t||t>=46&&t<=47||t>=58&&t<=59||t>=60&&t<=62||t>=63&&t<=64||91===t||92===t||93===t||94===t||96===t||123===t||124===t||125===t||126===t||161===t||t>=162&&t<=165||166===t||167===t||169===t||171===t||172===t||174===t||176===t||177===t||182===t||187===t||191===t||215===t||247===t||t>=8208&&t<=8213||t>=8214&&t<=8215||8216===t||8217===t||8218===t||t>=8219&&t<=8220||8221===t||8222===t||8223===t||t>=8224&&t<=8231||t>=8240&&t<=8248||8249===t||8250===t||t>=8251&&t<=8254||t>=8257&&t<=8259||8260===t||8261===t||8262===t||t>=8263&&t<=8273||8274===t||8275===t||t>=8277&&t<=8286||t>=8592&&t<=8596||t>=8597&&t<=8601||t>=8602&&t<=8603||t>=8604&&t<=8607||8608===t||t>=8609&&t<=8610||8611===t||t>=8612&&t<=8613||8614===t||t>=8615&&t<=8621||8622===t||t>=8623&&t<=8653||t>=8654&&t<=8655||t>=8656&&t<=8657||8658===t||8659===t||8660===t||t>=8661&&t<=8691||t>=8692&&t<=8959||t>=8960&&t<=8967||8968===t||8969===t||8970===t||8971===t||t>=8972&&t<=8991||t>=8992&&t<=8993||t>=8994&&t<=9e3||9001===t||9002===t||t>=9003&&t<=9083||9084===t||t>=9085&&t<=9114||t>=9115&&t<=9139||t>=9140&&t<=9179||t>=9180&&t<=9185||t>=9186&&t<=9254||t>=9255&&t<=9279||t>=9280&&t<=9290||t>=9291&&t<=9311||t>=9472&&t<=9654||9655===t||t>=9656&&t<=9664||9665===t||t>=9666&&t<=9719||t>=9720&&t<=9727||t>=9728&&t<=9838||9839===t||t>=9840&&t<=10087||10088===t||10089===t||10090===t||10091===t||10092===t||10093===t||10094===t||10095===t||10096===t||10097===t||10098===t||10099===t||10100===t||10101===t||t>=10132&&t<=10175||t>=10176&&t<=10180||10181===t||10182===t||t>=10183&&t<=10213||10214===t||10215===t||10216===t||10217===t||10218===t||10219===t||10220===t||10221===t||10222===t||10223===t||t>=10224&&t<=10239||t>=10240&&t<=10495||t>=10496&&t<=10626||10627===t||10628===t||10629===t||10630===t||10631===t||10632===t||10633===t||10634===t||10635===t||10636===t||10637===t||10638===t||10639===t||10640===t||10641===t||10642===t||10643===t||10644===t||10645===t||10646===t||10647===t||10648===t||t>=10649&&t<=10711||10712===t||10713===t||10714===t||10715===t||t>=10716&&t<=10747||10748===t||10749===t||t>=10750&&t<=11007||t>=11008&&t<=11055||t>=11056&&t<=11076||t>=11077&&t<=11078||t>=11079&&t<=11084||t>=11085&&t<=11123||t>=11124&&t<=11125||t>=11126&&t<=11157||11158===t||t>=11159&&t<=11263||t>=11776&&t<=11777||11778===t||11779===t||11780===t||11781===t||t>=11782&&t<=11784||11785===t||11786===t||11787===t||11788===t||11789===t||t>=11790&&t<=11798||11799===t||t>=11800&&t<=11801||11802===t||11803===t||11804===t||11805===t||t>=11806&&t<=11807||11808===t||11809===t||11810===t||11811===t||11812===t||11813===t||11814===t||11815===t||11816===t||11817===t||t>=11818&&t<=11822||11823===t||t>=11824&&t<=11833||t>=11834&&t<=11835||t>=11836&&t<=11839||11840===t||11841===t||11842===t||t>=11843&&t<=11855||t>=11856&&t<=11857||11858===t||t>=11859&&t<=11903||t>=12289&&t<=12291||12296===t||12297===t||12298===t||12299===t||12300===t||12301===t||12302===t||12303===t||12304===t||12305===t||t>=12306&&t<=12307||12308===t||12309===t||12310===t||12311===t||12312===t||12313===t||12314===t||12315===t||12316===t||12317===t||t>=12318&&t<=12319||12320===t||12336===t||64830===t||64831===t||t>=65093&&t<=65094}function Yc(t){t.forEach((function(t){if(delete t.location,Ql(t)||tc(t))for(var e in t.options)delete t.options[e].location,Yc(t.options[e].value);else Xl(t)&&oc(t.style)||(Zl(t)||Jl(t))&&ic(t.style)?delete t.style.location:nc(t)&&Yc(t.children)}))}function qc(t,e){void 0===e&&(e={}),e=yr({shouldParseSkeletons:!0,requiresOtherClause:!0},e);var n=new Vc(t,e).parse();if(n.err){var o=SyntaxError(Ul[n.err.kind]);throw o.location=n.err.location,o.originalMessage=n.err.message,o}return(null==e?void 0:e.captureLocation)||Yc(n.val),n.val}!function(t){t.MISSING_VALUE="MISSING_VALUE",t.INVALID_VALUE="INVALID_VALUE",t.MISSING_INTL_API="MISSING_INTL_API"}(Uc||(Uc={}));var Wc,Xc=function(t){function e(e,n,o){var i=t.call(this,e)||this;return i.code=n,i.originalMessage=o,i}return _r(e,t),e.prototype.toString=function(){return"[formatjs Error: ".concat(this.code,"] ").concat(this.message)},e}(Error),Zc=function(t){function e(e,n,o,i){return t.call(this,'Invalid values for "'.concat(e,'": "').concat(n,'". Options are "').concat(Object.keys(o).join('", "'),'"'),Uc.INVALID_VALUE,i)||this}return _r(e,t),e}(Xc),Jc=function(t){function e(e,n,o){return t.call(this,'Value for "'.concat(e,'" must be of type ').concat(n),Uc.INVALID_VALUE,o)||this}return _r(e,t),e}(Xc),Qc=function(t){function e(e,n){return t.call(this,'The intl string context variable "'.concat(e,'" was not provided to the string "').concat(n,'"'),Uc.MISSING_VALUE,n)||this}return _r(e,t),e}(Xc);function tu(t){return"function"==typeof t}function eu(t,e,n,o,i,r,a){if(1===t.length&&ql(t[0]))return[{type:Wc.literal,value:t[0].value}];for(var s=[],l=0,c=t;l0?new Intl.Locale(e[0]):new Intl.Locale("string"==typeof t?t:t[0])}},t.__parse=qc,t.formats={number:{integer:{maximumFractionDigits:0},currency:{style:"currency"},percent:{style:"percent"}},date:{short:{month:"numeric",day:"numeric",year:"2-digit"},medium:{month:"short",day:"numeric",year:"numeric"},long:{month:"long",day:"numeric",year:"numeric"},full:{weekday:"long",month:"long",day:"numeric",year:"numeric"}},time:{short:{hour:"numeric",minute:"numeric"},medium:{hour:"numeric",minute:"numeric",second:"numeric"},long:{hour:"numeric",minute:"numeric",second:"numeric",timeZoneName:"short"},full:{hour:"numeric",minute:"numeric",second:"numeric",timeZoneName:"short"}}},t}(),ru={not_found:"لم يتم العثور على الكيان"},au={card:{chips:{alignment:"محاذاة"},climate:{hvac_modes:"أوضاع HVAC",show_temperature_control:"التحكم في درجة الحرارة؟"},cover:{show_buttons_control:"أزرار التحكم؟",show_position_control:"التحكم في الموقع؟",show_tilt_position_control:"التحكم في الإمالة؟"},empty:{no_config_options:"لا تحتوي هذه البطاقة على خيارات التكوين."},fan:{show_direction_control:"التحكم بالإتجاه؟",show_oscillate_control:"التحكم في التذبذب؟",show_percentage_control:"التحكم في النسبة المئوية؟"},generic:{collapsible_controls:"تصغير عناصر التحكم عند الإيقاف",color:"اللون",content_info:"المحتوى",fill_container:"ملئ الحاوية",icon_animation:"تحريك الرمز عندما يكون نشطًا؟",icon_color:"لون الأيقونة",icon_type:"نوع الأيقونة",layout:"التخطيط",primary_info:"المعلومات الأساسية",secondary_info:"المعلومات الفرعية",use_entity_picture:"استخدم صورة الكيان؟"},humidifier:{show_target_humidity_control:"التحكم في الرطوبة؟?"},light:{incompatible_controls:"قد لا يتم عرض بعض عناصر التحكم إذا كان الضوء الخاص بك لا يدعم الميزة.",show_brightness_control:"التحكم في السطوع؟",show_color_control:"التحكم في اللون؟",show_color_temp_control:"التحكم في درجة حرارة اللون؟",use_light_color:"استخدم لون فاتح"},lock:{lock:"مقفل",open:"مفتوح",unlock:"إلغاء قفل"},"media-player":{media_controls:"التحكم في الوسائط",media_controls_list:{next:"التالي",on_off:"تشغيل/إيقاف",play_pause_stop:"تشغيل/إيقاف مؤقت/إيقاف",previous:"السابق",repeat:"وضع التكرار",shuffle:"خلط"},show_volume_level:"إظهار مستوى الصوت",use_media_artwork:"استخدم صورة الوسائط",use_media_info:"استخدم معلومات الوسائط",volume_controls:"التحكم في الصوت",volume_controls_list:{volume_buttons:"أزرار الصوت",volume_mute:"كتم",volume_set:"مستوى الصوت"}},number:{display_mode:"وضع العرض",display_mode_list:{buttons:"الأزرار",default:"الافتراضي(سحب)",slider:"سحب"}},template:{badge_color:"لون الشارة",badge_icon:"أيقونة الشارة",content:"المحتوى",entity_extra:"تستخدم في القوالب والإجراءات",label:"التسمية",multiline_secondary:"متعدد الأسطر الثانوية؟",picture:"صورة (ستحل محل الأيقونة)",primary:"المعلومات الأساسية",secondary:"المعلومات الثانوية"},title:{subtitle:"العنوان الفرعي",subtitle_tap_action:"إجراء النقر على العنوان الفرعي",title:"العنوان",title_tap_action:"إجراء النقر على العنوان"},update:{show_buttons_control:"أزرار التحكم؟"},vacuum:{commands:"الاوامر",commands_list:{on_off:"تشغيل/إيقاف"}},weather:{show_conditions:"الأحوال الجوية؟",show_temperature:"الطقس؟"}},chip:{"chip-picker":{add:"أضف رقاقة",chips:"رقاقات",clear:"مسح",edit:"تعديل",select:"اختر الرقاقة",types:{action:"إجراء","alarm-control-panel":"تنبيه",back:"رجوع",conditional:"مشروط",entity:"الكيان",light:"مظيء",menu:"القائمة",quickbar:"تبويب سريع",spacer:"مساحة",template:"قالب",weather:"الطقس"}},conditional:{chip:"رقاقة"},sub_element_editor:{title:"محرر الرقاقة"}},form:{alignment_picker:{values:{center:"توسيط",default:"المحاذاة الافتراضية",end:"نهاية",justify:"مساواة",start:"بداية"}},color_picker:{values:{default:"اللون الإفتراضي"}},icon_type_picker:{values:{default:"النوع افتراضي","entity-picture":"صورة الكيان",icon:"أيقونة",none:"لا شئ"}},info_picker:{values:{default:"المعلومات الافتراضية","last-changed":"آخر تغيير","last-updated":"آخر تحديث",name:"الإسم",none:"لا شئ",state:"الحالة"}},layout_picker:{values:{default:"تخطيط افتراضي",horizontal:"تخطيط أفقي",vertical:"تخطيط رأسي"}}}},su={card:ru,editor:au},lu=Object.freeze({__proto__:null,card:ru,default:su,editor:au}),cu={card:{chips:{alignment:"Подравняване"},climate:{hvac_modes:"HVAC Режими",show_temperature_control:"Контрол на температурата?"},cover:{show_buttons_control:"Контролни бутони?",show_position_control:"Контрол на позицията?",show_tilt_position_control:"Контрол на наклона?"},fan:{show_oscillate_control:"Контрол на трептенето?",show_percentage_control:"Процентов контрол?"},generic:{collapsible_controls:"Свий контролите при изключен",content_info:"Съдържание",fill_container:"Изпълване на контейнера",icon_animation:"Анимирай иконата при активен?",icon_color:"Цвят на икона",icon_type:"Тип на икона",layout:"Оформление",primary_info:"Първостепенна информация",secondary_info:"Второстепенна информация",use_entity_picture:"Използвай снимката на обекта?"},humidifier:{show_target_humidity_control:"Контрол на влажността?"},light:{incompatible_controls:"Някои опции могат да бъдат скрити при условие че осветителното тяло не поддържа фунцията.",show_brightness_control:"Контрол на яркостта?",show_color_control:"Контрол на цвета?",show_color_temp_control:"Контрол на температурата?",use_light_color:"Използвай цвета на светлината"},lock:{lock:"Заключен",open:"Отворен",unlock:"Отключен"},"media-player":{media_controls:"Контрол на Медиата",media_controls_list:{next:"Следващ",on_off:"Вкл./Изкл.",play_pause_stop:"Пусни/пауза/стоп",previous:"Предишен",repeat:"Повтаряне",shuffle:"Разбъркано"},show_volume_level:"Покажи контрола за звук",use_media_artwork:"Използвай визуалните детайли от медията",use_media_info:"Използвай информация от медията",volume_controls:"Контрол на звука",volume_controls_list:{volume_buttons:"Бутони за звук",volume_mute:"Заглуши",volume_set:"Ниво на звука"}},template:{badge_color:"Цвят на значка",badge_icon:"Икона на значка",content:"Съдържание",entity_extra:"Използван в шаблони и действия",multiline_secondary:"Много-редова второстепенна информация?",picture:"Картина (ще замени иконата)",primary:"Първостепенна информация",secondary:"Второстепенна информация"},title:{subtitle:"Подзаглавие",title:"Заглавие"},update:{show_buttons_control:"Контролни бутони?"},vacuum:{commands:"Конади",commands_list:{on_off:"Вкл./Изкл."}},weather:{show_conditions:"Условия?",show_temperature:"Температура?"}},chip:{"chip-picker":{add:"Добави чип",chips:"Чипове",clear:"Изчисти",edit:"Редактирай",select:"Избери чип",types:{action:"Действия","alarm-control-panel":"Аларма",back:"Назад",conditional:"Условни",entity:"Обект",light:"Осветление",menu:"Меню",template:"Шаблон",weather:"Време"}},conditional:{chip:"Чип"},sub_element_editor:{title:"Чип редактор"}},form:{alignment_picker:{values:{center:"Център",default:"Основно подравняване",end:"Край",justify:"Подравнен",start:"Старт"}},color_picker:{values:{default:"Основен цвят"}},icon_type_picker:{values:{default:"Основен тип","entity-picture":"Картина на обекта",icon:"Икона",none:"Липсва"}},info_picker:{values:{default:"Основна информация","last-changed":"Последно Променен","last-updated":"Последно Актуализиран",name:"Име",none:"Липсва",state:"Състояние"}},layout_picker:{values:{default:"Основно оформление",horizontal:"Хоризонтално оформление",vertical:"Вертикално оформление"}}}},uu={editor:cu},hu=Object.freeze({__proto__:null,default:uu,editor:cu}),du={not_found:"No s'ha trobat l'entitat"},pu={card:{chips:{alignment:"Alineació"},climate:{hvac_modes:"Modes HVAC",show_temperature_control:"Control de temperatura?"},cover:{show_buttons_control:"Botons de control?",show_position_control:"Control de posició?",show_tilt_position_control:"Control d'inclinació?"},fan:{show_oscillate_control:"Control d'oscil·lació?",show_percentage_control:"Control de percentatge?"},generic:{collapsible_controls:"Amaga els controls en desactivar",color:"Color",content_info:"Contingut",fill_container:"Emplena el contenidor",icon_animation:"Animar icona en activar?",icon_color:"Color d'icona",icon_type:"Tipus d'icona",layout:"Distribució",primary_info:"Informació primaria",secondary_info:"Informació secundaria",use_entity_picture:"Fer servir la imatge de l'entitat?"},humidifier:{show_target_humidity_control:"Control d'humitat?"},light:{incompatible_controls:"Alguns controls no es mostraran si l'entitat no suporta eixa funció.",show_brightness_control:"Control de brillantor?",show_color_control:"Control de color?",show_color_temp_control:"Control de la temperatura del color?",use_light_color:"Fes servir el color del llum"},lock:{lock:"Bloqueja",open:"Obri",unlock:"Desbloqueja"},"media-player":{media_controls:"Controls multimèdia",media_controls_list:{next:"Pista següent",on_off:"Engegar/Apagar",play_pause_stop:"Reproduïr/Pausar/Detindre",previous:"Pista anterior",repeat:"Mode de repetició",shuffle:"Mesclar"},show_volume_level:"Mostra el nivell de volum",use_media_artwork:"Fes servir l'art multimèdia",use_media_info:"Empra la informació multimèdia",volume_controls:"Controls de volum",volume_controls_list:{volume_buttons:"Botons de volum",volume_mute:"Silenci",volume_set:"Nivell de volum"}},number:{display_mode:"Mode de visualització",display_mode_list:{buttons:"Botons",default:"Per defecte (lliscant)",slider:"Lliscant"}},template:{badge_color:"Color de la insígnia",badge_icon:"Icona de la insígnia",content:"Contingut",entity_extra:"Utilitzats en plantilles i accions",label:"Etiqueta",multiline_secondary:"Secundaria en varies línies?",picture:"Imatge (reemplaçarà la icona)",primary:"Informació primaria",secondary:"Informació secundaria"},title:{subtitle:"Subtítol",subtitle_tap_action:"Acció en tocar el subtítol",title:"Títol",title_tap_action:"Acció en tocar el títol"},update:{show_buttons_control:"Botons de control?"},vacuum:{commands:"Comandaments",commands_list:{on_off:"Engegar/Apagar"}},weather:{show_conditions:"Condicions?",show_temperature:"Temperatura?"}},chip:{"chip-picker":{add:"Afegir xip",chips:"Xips",clear:"Buidar",edit:"Editar",select:"Seleccionar chip",types:{action:"Acció","alarm-control-panel":"Alarma",back:"Tornar",conditional:"Condicional",entity:"Entitat",light:"Llum",menu:"Menú",spacer:"Espai",template:"Plantilla",weather:"Oratge"}},conditional:{chip:"Xip"},sub_element_editor:{title:"Editor de xips"}},form:{alignment_picker:{values:{center:"Centre",default:"Alineació per defecte",end:"Final",justify:"Justifica",start:"Inici"}},color_picker:{values:{default:"Color per defecte"}},icon_type_picker:{values:{default:"Tipus per defecte","entity-picture":"Entitat d'imatge",icon:"Icona",none:"Cap"}},info_picker:{values:{default:"Informació per defecte","last-changed":"Últim Canvi","last-updated":"Última Actualització",name:"Nom",none:"Cap",state:"Estat"}},layout_picker:{values:{default:"Distribució per defecte",horizontal:"Distribució horitzontal",vertical:"Distribució vertical"}}}},fu={card:du,editor:pu},mu=Object.freeze({__proto__:null,card:du,default:fu,editor:pu}),vu={not_found:"Entita nebyla nalezena"},gu={card:{chips:{alignment:"Zarovnání"},climate:{hvac_modes:"Režimy HVAC",show_temperature_control:"Ovládání teploty?"},cover:{show_buttons_control:"Zobrazit ovládací tlačítka?",show_position_control:"Zobrazit ovládání polohy?",show_tilt_position_control:"Zobrazit ovládání náklonu?"},fan:{show_oscillate_control:"Ovládání oscilaceM",show_percentage_control:"Ovládání v procentech?"},generic:{collapsible_controls:"Pokud je vypnuto, skrýt ovládací prvky",content_info:"Obsah",fill_container:"Vyplnit prostor",icon_animation:"Pokud je aktivní, animovat ikonu?",icon_color:"Barva ikony",icon_type:"Typ ikony",layout:"Rozložení",primary_info:"Primární informace",secondary_info:"Sekundární informace",use_entity_picture:"Použít ikonu entity?"},humidifier:{show_target_humidity_control:"Ovládání vlhkosti?"},light:{incompatible_controls:"Některé ovládací prvky se nemusí zobrazit, pokud vaše světlo tuto funkci nepodporuje.",show_brightness_control:"Ovládání jasu?",show_color_control:"Ovládání barvy světla?",show_color_temp_control:"Ovládání teploty světla?",use_light_color:"Ikona podle barvy světla?"},lock:{lock:"Zamčeno",open:"Otevřeno",unlock:"Odemčeno"},"media-player":{media_controls:"Ovládání médií",media_controls_list:{next:"Další stopa",on_off:"Zapnout/Vypnout",play_pause_stop:"Přehrát/Pauza/Zastavit",previous:"Předchozí stopa",repeat:"Režim opakování",shuffle:"Zamíchat"},show_volume_level:"Zobrazit úroveň hlasitosti",use_media_artwork:"Použít artwork z média",use_media_info:"Použít informace z média",volume_controls:"Ovládání hlasitosti",volume_controls_list:{volume_buttons:"Tlačítka hlasitosti",volume_mute:"Ztlumit",volume_set:"Úroveň hlasitosti"}},number:{display_mode:"Režim zobrazení",display_mode_list:{buttons:"Tlačítka",default:"Výchozí (posuvník)",slider:"Posuvník"}},template:{badge_color:"Barva odznaku",badge_icon:"Ikona odznaku",content:"Obsah",entity_extra:"Použito v šablonách a akcích",multiline_secondary:"Víceřádková sekundární informace?",picture:"Obrázek (nahradí ikonu)",primary:"Primární informace",secondary:"Sekundární informace"},title:{subtitle:"Popis",subtitle_tap_action:"Akce při klepnutí na popis",title:"Nadpis",title_tap_action:"Akce při klepnutí na nadpis"},update:{show_buttons_control:"Zobrazit ovládací tlačítka?"},vacuum:{commands:"Příkazy",commands_list:{on_off:"Zapnout/Vypnout"}},weather:{show_conditions:"Zobrazit podmínky?",show_temperature:"Zobrazit teplotu?"}},chip:{"chip-picker":{add:"Přidat tlačítko",chips:"Tlačítka",clear:"Vymazat",edit:"Upravit",select:"Vybrat tlačítko",types:{action:"Akce","alarm-control-panel":"Alarm",back:"Zpět",conditional:"Podmínka",entity:"Entita",light:"Světlo",menu:"Menu",spacer:"Mezera",template:"Šablona",weather:"Počasí"}},conditional:{chip:"Tlačítko"},sub_element_editor:{title:"Editor tlačítek"}},form:{alignment_picker:{values:{center:"Na střed",default:"Výchozí zarovnání",end:"Na konec",justify:"Do bloku",start:"Na začátek"}},color_picker:{values:{default:"Výchozí barva"}},icon_type_picker:{values:{default:"Výchozí typ","entity-picture":"Ikona entity",icon:"Ikona",none:"Nic"}},info_picker:{values:{default:"Výchozí informace","last-changed":"Poslední změna","last-updated":"Poslední aktualizace",name:"Název",none:"Nic",state:"Stav"}},layout_picker:{values:{default:"Výchozí rozložení",horizontal:"Vodorovné rozložení",vertical:"Svislé rozložení"}}}},_u={card:vu,editor:gu},yu=Object.freeze({__proto__:null,card:vu,default:_u,editor:gu}),bu={not_found:"Enhed ikke fundet"},ku={card:{chips:{alignment:"Justering"},climate:{hvac_modes:"HVAC-tilstande",show_temperature_control:"Temperaturkontrol?"},cover:{show_buttons_control:"Betjeningsknapper?",show_position_control:"Positionskontrol?",show_tilt_position_control:"Tiltkontrol?"},fan:{show_oscillate_control:"Oscillationskontrol?",show_percentage_control:"Procentkontrol?"},generic:{collapsible_controls:"Skjul kontroller når slukket",color:"Farve",content_info:"Indhold",fill_container:"Fyld container",icon_animation:"Animér ikon når aktiv?",icon_color:"Ikon farve",icon_type:"Ikon type",layout:"Layout",primary_info:"Primær information",secondary_info:"Sekundær information",use_entity_picture:"Brug enhedsbillede?"},humidifier:{show_target_humidity_control:"Luftfugtighedskontrol?"},light:{incompatible_controls:"Nogle kontroller vises muligvis ikke, hvis dit lys ikke understøtter funktionen.",show_brightness_control:"Lysstyrkekontrol?",show_color_control:"Farvekontrol?",show_color_temp_control:"Temperaturfarvekontrol?",use_light_color:"Brug lysfarve"},lock:{lock:"Lås",open:"Åben",unlock:"Lås op"},"media-player":{media_controls:"Mediekontrol",media_controls_list:{next:"Næste nummer",on_off:"Tænd/Sluk",play_pause_stop:"Afspil/Pause/Stop",previous:"Forrige nummer",repeat:"Gentagelsestilstand",shuffle:"Bland"},show_volume_level:"Vis lydstyrke",use_media_artwork:"Brug mediebilleder",use_media_info:"Brug medieinformation",volume_controls:"Lydstyrkekontrol",volume_controls_list:{volume_buttons:"Lydstyrkeknapper",volume_mute:"Lydløs",volume_set:"Lydstyrke"}},number:{display_mode:"Visningstilstand",display_mode_list:{buttons:"Knapper",default:"Standard (slider)",slider:"Slider"}},template:{badge_color:"Badge farve",badge_icon:"Badge ikon",content:"Indhold",entity_extra:"Anvendes i skabeloner og handlinger",label:"Label",multiline_secondary:"Multi-linje sekundær?",picture:"Billede (erstatter ikonet)",primary:"Primær information",secondary:"Sekundær information"},title:{subtitle:"Undertitel",subtitle_tap_action:"Undertitel tryk handling",title:"Titel",title_tap_action:"Title tryk handling"},update:{show_buttons_control:"Betjeningsknapper?"},vacuum:{commands:"Kommandoer",commands_list:{on_off:"Slå til/fra"}},weather:{show_conditions:"Vejrforhold?",show_temperature:"Temperatur?"}},chip:{"chip-picker":{add:"Tilføj chip",chips:"Chips",clear:"Nulstil",edit:"Rediger",select:"Vælg chip",types:{action:"Handling","alarm-control-panel":"Alarm",back:"Tilbage",conditional:"Betinget",entity:"Enhed",light:"Lys",menu:"Menu",spacer:"Afstand",template:"Skabelon",weather:"Vejr"}},conditional:{chip:"Chip"},sub_element_editor:{title:"Chip-editor"}},form:{alignment_picker:{values:{center:"Centrer",default:"Standard justering",end:"Slut",justify:"Lige margener",start:"Start"}},color_picker:{values:{default:"Standardfarve"}},icon_type_picker:{values:{default:"Standard type","entity-picture":"Enhedsbillede",icon:"Ikon",none:"Ingen"}},info_picker:{values:{default:"Standard information","last-changed":"Sidst ændret","last-updated":"Sidst opdateret",name:"Navn",none:"Ingen",state:"Status"}},layout_picker:{values:{default:"Standard layout",horizontal:"Horisontal layout",vertical:"Vertikal layout"}}}},wu={card:bu,editor:ku},Cu=Object.freeze({__proto__:null,card:bu,default:wu,editor:ku}),Eu={not_found:"Entität nicht gefunden"},xu={card:{chips:{alignment:"Ausrichtung"},climate:{hvac_modes:"HVAC-Modi",show_temperature_control:"Temperatursteuerung?"},cover:{show_buttons_control:"Schaltflächensteuerung?",show_position_control:"Positionssteuerung?",show_tilt_position_control:"Winkelsteuerung?"},empty:{no_config_options:"Diese Karte hat keine Optionen."},fan:{show_direction_control:"Richtungssteuerung?",show_oscillate_control:"Oszillationssteuerung?",show_percentage_control:"Prozentuale Kontrolle?"},generic:{collapsible_controls:"Schieberegler einklappen, wenn aus",color:"Farbe",content_info:"Inhalt",fill_container:"Container ausfüllen",icon_animation:"Icon animieren, wenn aktiv?",icon_color:"Icon-Farbe",icon_type:"Icon-Typ",layout:"Layout",primary_info:"Primäre Information",secondary_info:"Sekundäre Information",use_entity_picture:"Entitätsbild verwenden?"},humidifier:{show_target_humidity_control:"Luftfeuchtigkeitssteuerung?"},light:{incompatible_controls:"Einige Steuerelemente werden möglicherweise nicht angezeigt, wenn Ihr Licht diese Funktion nicht unterstützt.",show_brightness_control:"Helligkeitsregelung?",show_color_control:"Farbsteuerung?",show_color_temp_control:"Farbtemperatursteuerung?",use_light_color:"Farbsteuerung verwenden"},lock:{lock:"Verriegeln",open:"Öffnen",unlock:"Entriegeln"},"media-player":{media_controls:"Mediensteuerung",media_controls_list:{next:"Nächster Titel",on_off:"Ein/Aus",play_pause_stop:"Play/Pause/Stop",previous:"Vorheriger Titel",repeat:"Wiederholen",shuffle:"Zufällige Wiedergabe"},show_volume_level:"Lautstärke-Level anzeigen",use_media_artwork:"Mediengrafik verwenden",use_media_info:"Medieninfos verwenden",volume_controls:"Lautstärkesteuerung",volume_controls_list:{volume_buttons:"Lautstärke-Buttons",volume_mute:"Stumm",volume_set:"Lautstärke-Level"}},number:{display_mode:"Anzeigemodus",display_mode_list:{buttons:"Buttons",default:"Standard (Schieberegler)",slider:"Schieberegler"}},template:{badge_color:"Badge-Farbe",badge_icon:"Badge-Icon",content:"Inhalt",entity_extra:"Wird in Vorlagen und Aktionen verwendet",label:"Beschriftung",multiline_secondary:"Mehrzeilig sekundär?",picture:"Bild (ersetzt das Icon)",primary:"Primäre Information",secondary:"Sekundäre Information"},title:{subtitle:"Untertitel",subtitle_tap_action:"Untertitel Tipp-Aktion",title:"Titel",title_tap_action:"Titel Tipp-Aktion"},update:{show_buttons_control:"Schaltflächensteuerung?"},vacuum:{commands:"Befehle",commands_list:{on_off:"An/Ausschalten"}},weather:{show_conditions:"Bedingungen?",show_temperature:"Temperatur?"}},chip:{"chip-picker":{add:"Chip hinzufügen",chips:"Chips",clear:"Löschen",edit:"Editieren",select:"Chip auswählen",types:{action:"Aktion","alarm-control-panel":"Alarm",back:"Zurück",conditional:"Bedingung",entity:"Entität",light:"Licht",menu:"Menü",quickbar:"Quickbar",spacer:"Abstand",template:"Vorlage",weather:"Wetter"}},conditional:{chip:"Chip"},sub_element_editor:{title:"Chip Editor"}},form:{alignment_picker:{values:{center:"Mitte",default:"Standard",end:"Ende",justify:"Ausrichten",start:"Anfang"}},color_picker:{values:{default:"Standardfarbe"}},icon_type_picker:{values:{default:"Standard-Typ","entity-picture":"Entitätsbild",icon:"Icon",none:"Keines"}},info_picker:{values:{default:"Standard-Information","last-changed":"Letzte Änderung","last-updated":"Letzte Aktualisierung",name:"Name",none:"Keine",state:"Zustand"}},layout_picker:{values:{default:"Standard-Layout",horizontal:"Horizontales Layout",vertical:"Vertikales Layout"}}}},Au={card:Eu,editor:xu},Su=Object.freeze({__proto__:null,card:Eu,default:Au,editor:xu}),Tu={card:{chips:{alignment:"Ευθυγράμμιση"},cover:{show_buttons_control:"Έλεγχος κουμπιών;",show_position_control:"Έλεγχος θέσης;"},fan:{show_oscillate_control:"Έλεγχος ταλάντωσης;",show_percentage_control:"Έλεγχος ποσοστού;"},generic:{content_info:"Περιεχόμενο",icon_animation:"Κίνηση εικονιδίου όταν είναι ενεργό;",icon_color:"Χρώμα εικονιδίου",layout:"Διάταξη",primary_info:"Πρωτεύουσες πληροφορίες",secondary_info:"Δευτερεύουσες πληροφορίες",use_entity_picture:"Χρήση εικόνας οντότητας;"},light:{incompatible_controls:"Ορισμένα στοιχεία ελέγχου ενδέχεται να μην εμφανίζονται εάν το φωτιστικό σας δεν υποστηρίζει τη λειτουργία.",show_brightness_control:"Έλεγχος φωτεινότητας;",show_color_control:"Έλεγχος χρώματος;",show_color_temp_control:"Έλεγχος χρώματος θερμοκρασίας;",use_light_color:"Χρήση χρώματος φωτος"},"media-player":{media_controls:"Έλεγχος πολυμέσων",media_controls_list:{next:"Επόμενο κομμάτι",on_off:"Ενεργοποίηση/απενεργοποίηση",play_pause_stop:"Αναπαραγωγή/παύση/διακοπή",previous:"Προηγούμενο κομμάτι",repeat:"Λειτουργία επανάληψης",shuffle:"Τυχαία σειρά"},use_media_artwork:"Χρήση έργων τέχνης πολυμέσων",use_media_info:"Χρήση πληροφοριών πολυμέσων",volume_controls:"Χειριστήρια έντασης ήχου",volume_controls_list:{volume_buttons:"Κουμπιά έντασης ήχου",volume_mute:"Σίγαση",volume_set:"Επίπεδο έντασης ήχου"}},template:{content:"Περιεχόμενο",entity_extra:"Χρησιμοποιείται σε πρότυπα και ενέργειες",multiline_secondary:"Δευτερεύουσες πολλαπλών γραμμών;",primary:"Πρωτεύουσες πληροφορίες",secondary:"Δευτερεύουσες πληροφορίες"},title:{subtitle:"Υπότιτλος",title:"Τίτλος"},update:{show_buttons_control:"Έλεγχος κουμπιών;"},vacuum:{commands:"Εντολές"},weather:{show_conditions:"Συνθήκες;",show_temperature:"Θερμοκρασία;"}},chip:{"chip-picker":{add:"Προσθήκη chip",chips:"Chips",clear:"Καθαρισμός",edit:"Επεξεργασία",select:"Επιλογή chip",types:{action:"Ενέργεια","alarm-control-panel":"Συναγερμός",back:"Πίσω",conditional:"Υπό προϋποθέσεις",entity:"Οντότητα",light:"Φως",menu:"Μενού",template:"Πρότυπο",weather:"Καιρός"}},conditional:{chip:"Chip"},sub_element_editor:{title:"Επεξεργαστής Chip"}},form:{alignment_picker:{values:{center:"Στοίχιση στο κέντρο",default:"Προεπιλεγμένη στοίχιση",end:"Στοίχιση δεξιά",justify:"Πλήρης στοίχιση",start:"Στοίχιση αριστερά"}},color_picker:{values:{default:"Προεπιλεγμένο χρώμα"}},info_picker:{values:{default:"Προεπιλεγμένες πληροφορίες","last-changed":"Τελευταία αλλαγή","last-updated":"Τελευταία ενημέρωση",name:"Όνομα",none:"Τίποτα",state:"Κατάσταση"}},layout_picker:{values:{default:"Προεπιλεγμένη διάταξη",horizontal:"Οριζόντια διάταξη",vertical:"Κάθετη διάταξη"}}}},Mu={editor:Tu},zu=Object.freeze({__proto__:null,default:Mu,editor:Tu}),Ou={not_found:"Entity not found"},Iu={section:{context:"Context",content:"Content",features:"Features",interactions:"Interactions",layout:"Layout",badge:"Badge"},card:{chips:{alignment:"Alignment"},climate:{hvac_modes:"HVAC Modes",show_temperature_control:"Temperature control?"},cover:{show_buttons_control:"Control buttons?",show_position_control:"Position control?",show_tilt_position_control:"Tilt control?"},empty:{no_config_options:"This card has no config options."},fan:{show_direction_control:"Direction control?",show_oscillate_control:"Oscillate control?",show_percentage_control:"Percentage control?"},generic:{entity:"Entity",area:"Area",color:"Color",content_info:"Content",fill_container:"Fill container",icon_animation:"Animate icon when active?",icon_color:"Icon color",icon_type:"Icon type",layout:"Layout",primary_info:"Primary information",secondary_info:"Secondary information",use_entity_picture:"Use entity picture?",collapsible_controls:"Collapse controls when off",picture:"Picture",picture_helper:"If set, it will replace the icon."},humidifier:{show_target_humidity_control:"Humidity control?"},light:{incompatible_controls:"Some controls may not be displayed if your light does not support the feature.",show_brightness_control:"Brightness control?",show_color_control:"Color control?",show_color_temp_control:"Color temperature control?",use_light_color:"Use light color"},lock:{lock:"Lock",open:"Open",unlock:"Unlock"},"media-player":{media_controls:"Media controls",media_controls_list:{next:"Next track",on_off:"Turn on/off",play_pause_stop:"Play/pause/stop",previous:"Previous track",repeat:"Repeat mode",shuffle:"Shuffle"},show_volume_level:"Show volume level",use_media_artwork:"Use media artwork",use_media_info:"Use media info",volume_controls:"Volume controls",volume_controls_list:{volume_buttons:"Volume buttons",volume_mute:"Mute",volume_set:"Volume level"}},number:{display_mode:"Display Mode",display_mode_list:{buttons:"Buttons",default:"Default (slider)",slider:"Slider"}},template:{area_helper:"Used in templates and features",area:"Area",badge_color:"Badge color",badge_icon:"Badge icon",badge_text_helper:"If set, it will replace the icon.",badge_text:"Badge text",badge:"Badge",content:"Content",entity_helper:"Used in templates, interactions and features",entity_helper_legacy:"Used in templates and interactions",label:"Label",layout:"Layout",multiline_secondary_helper:"The card may be taller to fit the text and will not always align with the grid system.",multiline_secondary:"Allow multiline secondary information",primary:"Primary information",secondary:"Secondary information"},title:{alignment:"Alignment",subtitle:"Subtitle",subtitle_tap_action:"Subtitle tap action",title:"Title",title_tap_action:"Title tap action"},update:{show_buttons_control:"Control buttons?"},vacuum:{commands:"Commands",commands_list:{on_off:"Turn on/off"}},weather:{show_conditions:"Conditions?",show_temperature:"Temperature?"}},badge:{template:{label:"Label",content:"Content",entity_helper:"Used in templates and interactions",area_helper:"Used in templates"}},chip:{"chip-picker":{add:"Add chip",chips:"Chips",clear:"Clear",edit:"Edit",select:"Select chip",types:{action:"Action","alarm-control-panel":"Alarm",back:"Back",conditional:"Conditional",entity:"Entity",light:"Light",menu:"Menu",quickbar:"Quickbar",spacer:"Spacer",template:"Template",weather:"Weather"}},conditional:{chip:"Chip"},sub_element_editor:{title:"Chip editor"}},form:{alignment_picker:{values:{center:"Center",default:"Default alignment",end:"End",justify:"Justify",start:"Start"}},color_picker:{values:{default:"Default color"}},icon_type_picker:{values:{default:"Default type","entity-picture":"Entity picture",icon:"Icon",none:"None"}},info_picker:{values:{default:"Default information","last-changed":"Last Changed","last-updated":"Last Updated",name:"Name",none:"None",state:"State"}},layout_picker:{values:{default:"Default layout",horizontal:"Horizontal layout",vertical:"Vertical layout"}}}},ju={title:"Card updated",description:"Your card’s configuration has been migrated to the new version. You can find more information about the changes in {link}.",post:"the GitHub post",revert:"Revert",ok:"Ok"},Pu={card:Ou,editor:Iu,migration:ju},Nu=Object.freeze({__proto__:null,card:Ou,default:Pu,editor:Iu,migration:ju}),Bu={not_found:"Entidad no encontrada"},Lu={card:{chips:{alignment:"Alineación"},climate:{hvac_modes:"Modos de climatización",show_temperature_control:"¿Control de temperatura?"},cover:{show_buttons_control:"¿Botones de control?",show_position_control:"¿Control de posición?",show_tilt_position_control:"¿Control de inclinación?"},empty:{no_config_options:"Esta carta no tiene opciones de config."},fan:{show_direction_control:"¿Control de dirección?",show_oscillate_control:"¿Controlar oscilación?",show_percentage_control:"¿Controlar porcentaje?"},generic:{collapsible_controls:"Contraer controles cuando está apagado",color:"Color",content_info:"Contenido",fill_container:"Rellenar",icon_animation:"¿Icono animado cuando está activo?",icon_color:"Color de icono",icon_type:"Tipo de icono",layout:"Diseño",primary_info:"Información primaria",secondary_info:"Información secundaria",use_entity_picture:"¿Usar imagen de entidad?"},humidifier:{show_target_humidity_control:"¿Controlar humedad?"},light:{incompatible_controls:"Es posible que algunos controles no se muestren si la luz no es compatible con esta función.",show_brightness_control:"¿Controlar brillo?",show_color_control:"¿Controlar color?",show_color_temp_control:"¿Controlar temperatura del color?",use_light_color:"Usar color de la luz"},lock:{lock:"Bloquear",open:"Abrir",unlock:"Desbloquear"},"media-player":{media_controls:"Controles multimedia",media_controls_list:{next:"Pista siguiente",on_off:"Activar/desactivar",play_pause_stop:"Reproducir/pausa/parar",previous:"Pista anterior",repeat:"Modo de repetición",shuffle:"Aleatoria"},show_volume_level:"Mostrar nivel de volumen",use_media_artwork:"Usar ilustraciones multimedia",use_media_info:"Usar información multimedia",volume_controls:"Controles de volumen",volume_controls_list:{volume_buttons:"Botones de volumen",volume_mute:"Silenciar",volume_set:"Nivel de volumen"}},number:{display_mode:"Modo de visualización",display_mode_list:{buttons:"Botones",default:"Por defecto (deslizante)",slider:"Control deslizante"}},template:{badge_color:"Color del distintivo",badge_icon:"Icono del distintivo",content:"Contenido",entity_extra:"Utilizado en plantillas y acciones",label:"Etiqueta",multiline_secondary:"¿Secundaria multilínea?",picture:"Imagen (sustituirá al icono)",primary:"Información primaria",secondary:"Información secundaria"},title:{subtitle:"Subtítulo",subtitle_tap_action:"Acción al tocar el subtítulo",title:"Título",title_tap_action:"Acción al tocar el título"},update:{show_buttons_control:"¿Botones de control?"},vacuum:{commands:"Comandos",commands_list:{on_off:"Activar/desactivar"}},weather:{show_conditions:"¿Condiciones?",show_temperature:"¿Temperatura?"}},chip:{"chip-picker":{add:"Añadir chip",chips:"Chips",clear:"Limpiar",edit:"Editar",select:"Seleccionar chip",types:{action:"Acción","alarm-control-panel":"Alarma",back:"Volver",conditional:"Condicional",entity:"Entidad",light:"Luz",menu:"Menú",spacer:"Espaciador",template:"Plantilla",weather:"Clima"}},conditional:{chip:"Chip"},sub_element_editor:{title:"Editor de chip"}},form:{alignment_picker:{values:{center:"Centrado",default:"Alineación predeterminada",end:"Final",justify:"Justificado",start:"Inicio"}},color_picker:{values:{default:"Color predeterminado"}},icon_type_picker:{values:{default:"Por defecto","entity-picture":"Imagen de entidad",icon:"Icono",none:"Ninguno"}},info_picker:{values:{default:"Información predeterminada","last-changed":"Último cambio","last-updated":"Última actualización",name:"Nombre",none:"Ninguno",state:"Estado"}},layout_picker:{values:{default:"Diseño predeterminado",horizontal:"Diseño horizontal",vertical:"Diseño vertical"}}}},Hu={card:Bu,editor:Lu},Du=Object.freeze({__proto__:null,card:Bu,default:Hu,editor:Lu}),Ru={not_found:"Entiteettiä ei löytynyt"},Uu={card:{chips:{alignment:"Asettelu"},climate:{hvac_modes:"HVAC-tilat",show_temperature_control:"Lämpötilan säätö?"},cover:{show_buttons_control:"Toimintopainikkeet?",show_position_control:"Sijainnin hallinta?",show_tilt_position_control:"Kallistuksen säätö?"},fan:{show_oscillate_control:"Oskillaation säätö?",show_percentage_control:"Prosentuaalinen säätö?"},generic:{collapsible_controls:"Supista säätimet ollessa pois-tilassa",color:"Väri",content_info:"Sisältö",fill_container:"Täytä alue",icon_animation:"Animoi kuvake, kun aktiivinen?",icon_color:"Ikonin väri",icon_type:"Kuvakkeen tyyppi",layout:"Asettelu",primary_info:"Ensisijaiset tiedot",secondary_info:"Toissijaiset tiedot",use_entity_picture:"Käytä kohteen kuvaa?"},humidifier:{show_target_humidity_control:"Kosteudenhallinta?"},light:{incompatible_controls:"Jotkin toiminnot eivät näy, jos valaisimesi ei tue niitä.",show_brightness_control:"Kirkkauden säätö?",show_color_control:"Värin säätö?",show_color_temp_control:"Värilämpötilan säätö?",use_light_color:"Käytä valaisimen väriä"},lock:{lock:"Lukitse",open:"Avaa",unlock:"Poista lukitus"},"media-player":{media_controls:"Toiminnot",media_controls_list:{next:"Seuraava kappale",on_off:"Päälle/pois",play_pause_stop:"Toista/keskeytä/pysäytä",previous:"Edellinen kappale",repeat:"Jatkuva toisto",shuffle:"Sekoita"},show_volume_level:"Näytä äänenvoimakkuuden hallinta",use_media_artwork:"Käytä median kuvituksia",use_media_info:"Käytä median tietoja",volume_controls:"Äänenvoimakkuuden hallinta",volume_controls_list:{volume_buttons:"Äänenvoimakkuuspainikkeet",volume_mute:"Mykistä",volume_set:"Äänenvoimakkuus"}},number:{display_mode:"Näyttötila",display_mode_list:{buttons:"Painikkeet",default:"Oletus (liukusäädin)",slider:"Liukusäädin"}},template:{badge_color:"Merkin väri",badge_icon:"Merkin kuvake",content:"Sisältö",entity_extra:"Käytetään malleissa ja toiminnoissa",label:"Nimiö",multiline_secondary:"Monirivinen toissijainen tieto?",picture:"Kuva (korvaa kuvakkeen)",primary:"Ensisijaiset tiedot",secondary:"Toissijaiset tiedot"},title:{subtitle:"Tekstitys",subtitle_tap_action:"Alaotsikon napautustoiminto",title:"Otsikko",title_tap_action:"Otsikkonapautustoiminto"},update:{show_buttons_control:"Toimintopainikkeet?"},vacuum:{commands:"Komennot",commands_list:{on_off:"Kytke päälle/pois"}},weather:{show_conditions:"Ehdot?",show_temperature:"Lämpötila?"}},chip:{"chip-picker":{add:"Lisää merkki",chips:"Merkit",clear:"Tyhjennä",edit:"Muokkaa",select:"Valitse merkki",types:{action:"Toiminto","alarm-control-panel":"Hälytys",back:"Takaisin",conditional:"Ehdollinen",entity:"Kohde",light:"Valaisin",menu:"Valikko",spacer:"Välikappale",template:"Malli",weather:"Sää"}},conditional:{chip:"Merkki"},sub_element_editor:{title:"Merkkieditori"}},form:{alignment_picker:{values:{center:"Keskitä",default:"Keskitys",end:"Loppu",justify:"Sovita",start:"Alku"}},color_picker:{values:{default:"Oletusväri"}},icon_type_picker:{values:{default:"Oletustyyppi","entity-picture":"Kohteen kuva",icon:"Kuvake",none:"Ei mitään"}},info_picker:{values:{default:"Oletustiedot","last-changed":"Viimeksi muuttunut","last-updated":"Viimeksi päivittynyt",name:"Nimi",none:"Ei mitään",state:"Tila"}},layout_picker:{values:{default:"Oletusasettelu",horizontal:"Vaakasuuntainen",vertical:"Pystysuuntainen"}}}},Vu={card:Ru,editor:Uu},Fu=Object.freeze({__proto__:null,card:Ru,default:Vu,editor:Uu}),$u={not_found:"Entité inconnue"},Gu={badge:{template:{area_helper:"Utilisée dans les modèles",content:"Contenu",entity_helper:"Utilisée dans les modèles et les interactions",label:"Libellé"}},card:{chips:{alignment:"Alignement"},climate:{hvac_modes:"Modes du thermostat",show_temperature_control:"Contrôle de la température ?"},cover:{show_buttons_control:"Contrôle avec boutons ?",show_position_control:"Contrôle de la position ?",show_tilt_position_control:"Contrôle de l'inclinaison ?"},empty:{no_config_options:"Cette carte n'a pas de paramètres."},fan:{show_direction_control:"Contrôle de la direction ?",show_oscillate_control:"Contrôle de l'oscillation ?",show_percentage_control:"Contrôle de la vitesse ?"},generic:{area:"Pièce",collapsible_controls:"Reduire les contrôles quand éteint",color:"Couleur",content_info:"Contenu",entity:"Entité",fill_container:"Remplir le conteneur",icon_animation:"Animation de l'icône ?",icon_color:"Couleur de l'icône",icon_type:"Type d'icône",layout:"Disposition",picture:"Image",picture_helper:"Si définie, elle remplacera l'icône.",primary_info:"Information principale",secondary_info:"Information secondaire",use_entity_picture:"Utiliser l'image de l'entité ?"},humidifier:{show_target_humidity_control:"Contrôle d'humidité ?"},light:{incompatible_controls:"Certains contrôles peuvent ne pas être affichés si votre lumière ne supporte pas la fonctionnalité.",show_brightness_control:"Contrôle de luminosité ?",show_color_control:"Contrôle de la couleur ?",show_color_temp_control:"Contrôle de la température ?",use_light_color:"Utiliser la couleur de la lumière"},lock:{lock:"Verrouiller",open:"Ouvrir",unlock:"Déverrouiller"},"media-player":{media_controls:"Contrôles du media",media_controls_list:{next:"Suivant",on_off:"Allumer/Éteindre",play_pause_stop:"Lecture/pause/stop",previous:"Précédent",repeat:"Mode de répétition",shuffle:"Lecture aléatoire"},show_volume_level:"Afficher le niveau de volume",use_media_artwork:"Utiliser l'illustration du media",use_media_info:"Utiliser les informations du media",volume_controls:"Contrôles du volume",volume_controls_list:{volume_buttons:"Bouton de volume",volume_mute:"Muet",volume_set:"Niveau de volume"}},number:{display_mode:"Mode d'affichage",display_mode_list:{buttons:"Boutons",default:"Par défaut (Curseur)",slider:"Curseur"}},template:{area:"Pièce",area_helper:"Utilisée dans les modèles et les fonctionnalités",badge:"Badge",badge_color:"Couleur du badge",badge_icon:"Icône du badge",badge_text:"Texte du badge",badge_text_helper:"Si définie, elle remplacera l'icône.",content:"Contenu",entity_extra:"Utilisée pour les modèles et les actions",entity_helper:"Utilisée dans les modèles, les interactions et les fonctionnalités",entity_helper_legacy:"Utilisé dans les modèles et les interactions",label:"Libellé",layout:"Disposition",multiline_secondary:"Autoriser les informations secondaires sur plusieurs lignes",multiline_secondary_helper:"La carte peut être plus haute pour s'adapter au texte et ne s'alignera pas toujours avec le système de grille.",picture:"Image (remplacera l'icône)",primary:"Information principale",secondary:"Information secondaire"},title:{subtitle:"Sous-titre",subtitle_tap_action:"Appui sur le sous-titre",title:"Titre",title_tap_action:"Appui sur le titre"},update:{show_buttons_control:"Contrôle avec boutons ?"},vacuum:{commands:"Commandes",commands_list:{on_off:"Allumer/Éteindre"}},weather:{show_conditions:"Conditions ?",show_conditons:"Conditions ?",show_temperature:"Température ?"}},chip:{"chip-picker":{add:'Ajouter une "chip"',chips:'"Chips"',clear:"Effacer",edit:"Modifier",select:'Sélectionner une "chip"',types:{action:"Action","alarm-control-panel":"Alarme",back:"Retour",conditional:"Conditionnel",entity:"Entité",light:"Lumière",menu:"Menu",quickbar:"Barre d'accès rapide",spacer:"Espacement",template:"Modèle",weather:"Météo"}},conditional:{chip:"Chip"},sub_element_editor:{title:'Éditeur de "chip"'}},form:{alignment_picker:{values:{center:"Centré",default:"Alignement par défaut",end:"Fin",justify:"Justifié",start:"Début"}},color_picker:{values:{default:"Couleur par défaut"}},icon_type_picker:{values:{default:"Type par défaut","entity-picture":"Image de l'entité",icon:"Icône",none:"Aucune"}},info_picker:{values:{default:"Information par défaut","last-changed":"Dernière modification","last-updated":"Dernière mise à jour",name:"Nom",none:"Aucune",state:"État"}},layout_picker:{values:{default:"Disposition par défault",horizontal:"Disposition horizontale",vertical:"Disposition verticale"}}},section:{badge:"Badge",content:"Contenu",context:"Contexte",features:"Fonctionnalités",interactions:"Interactions",layout:"Disposition"}},Ku={description:"La configuration de votre carte a été migrée vers la nouvelle version. Vous pouvez trouver plus d’informations sur les changements dans {link}.",ok:"Ok",post:"l'article sur Github",revert:"Revenir en arrière",title:"Carte mise à jour"},Yu={card:$u,editor:Gu,migration:Ku},qu=Object.freeze({__proto__:null,card:$u,default:Yu,editor:Gu,migration:Ku}),Wu={not_found:"היישות לא נמצאה"},Xu={card:{chips:{alignment:"יישור"},climate:{hvac_modes:"מצבי שואב אבק",show_temperature_control:"בקרת טמפרטורה?"},cover:{show_buttons_control:"הצג כפתורי שליטה?",show_position_control:"הצג פקדי מיקום?",show_tilt_position_control:"שליטה בהטייה?"},empty:{no_config_options:"לכרטיסיה זו אין אפשרויות להגדרה."},fan:{show_direction_control:"שליטה בכיוון?",show_oscillate_control:"שליטה בהתנדנדות?",show_percentage_control:"שליטה באחוז?"},generic:{collapsible_controls:"הסתר שליטה כשאר מכובה",color:"צבע",content_info:"תוכן",fill_container:"מלא גבולות",icon_animation:"הנפש צלמית אם פעיל?",icon_color:"צבע אייקון",icon_type:"סוג צלמית",layout:"סידור",primary_info:"מידע ראשי",secondary_info:"מידע מישני",use_entity_picture:"השתמש בתמונת הישות?"},humidifier:{show_target_humidity_control:"הצג פקדי לחות?"},light:{incompatible_controls:"יתכן וחלק מהכפתורים לא יופיעו אם התאורה אינה תומכת בתכונה.",show_brightness_control:"שליטה בבהירות?",show_color_control:"הצג פקד צבע?",show_color_temp_control:"הצג פקד גוון תאורה?",use_light_color:"השתמש בצבע האור"},lock:{lock:"נעל",open:"פתח",unlock:"בטל נעילה"},"media-player":{media_controls:"שליטה במדיה",media_controls_list:{next:"רצועה הבאה",on_off:"הדלק/כבה",play_pause_stop:"נגן/השהה/הפסק",previous:"רצועה קודמת",repeat:"חזרה",shuffle:"ערבב"},show_volume_level:"הצג שליטת ווליום",use_media_artwork:"השתמש באומנות מדיה",use_media_info:"השתמש במידע מדיה",volume_controls:"שליטה בווליום",volume_controls_list:{volume_buttons:"כפתורי ווליום",volume_mute:"השתק",volume_set:"רמת ווליום"}},number:{display_mode:"הגדרת מצב תצוגה",display_mode_list:{buttons:"לחצנים",default:"ברירת מחדל (סרגל גלילה)",slider:"סרגל גלילה"}},template:{badge_color:"צבע תג",badge_icon:"צלמית תג",content:"תוכן",entity_extra:"משמש בתבניות ופעולות",label:"תווית",multiline_secondary:"מידע משני בשורות?",picture:"תמונה (תחליף את הצלמית)",primary:"מידע ראשי",secondary:"מידע מישני"},title:{subtitle:"כתובית",subtitle_tap_action:"פעולה בלחיצה על כותרת משנה",title:"כותרת",title_tap_action:"פעולה בלחיצה על הכותרת"},update:{show_buttons_control:"הצג כפתורי שליטה?"},vacuum:{commands:"פקודות",commands_list:{on_off:"כיבוי/הדלקה"},icon_animation:"הנפשת אייקון"},weather:{show_conditions:"הצג תנאים?",show_temperature:"הצג טמפרטורה?"}},chip:{"chip-picker":{add:"הוסף שבב",chips:"שבבים",clear:"נקה",edit:"ערוך",select:"בחר שבב",types:{action:"פעולה","alarm-control-panel":"אזעקה",back:"חזור",conditional:"מותנה",entity:"ישות",light:"אור",menu:"תפריט",spacer:"מרווח",template:"תבנית",weather:"מזג אוויר"}},conditional:{chip:"שבב"},sub_element_editor:{title:"עורך שבב"}},form:{alignment_picker:{values:{center:"אמצע",default:"יישור ברירת מחדל",end:"סוף",justify:"מוצדק",start:"התחלה"}},color_picker:{values:{default:"צבע ברירת מחדל"}},icon_type_picker:{values:{default:"סוג ברירת מחדל","entity-picture":"תמונת יישות",icon:"צלמית",none:"ריק"}},info_picker:{values:{default:"מידע ברירת מחדל","last-changed":"שונה לאחרונה","last-updated":"עודכן לאחרונה",name:"שם",none:"ריק",state:"מצב"}},layout_picker:{values:{default:"סידור ברירת מחדל",horizontal:"סידור מאוזן",vertical:"סידור מאונך"}}}},Zu={card:Wu,editor:Xu},Ju=Object.freeze({__proto__:null,card:Wu,default:Zu,editor:Xu}),Qu={not_found:"Entitás nem található"},th={card:{chips:{alignment:"Rendezés"},climate:{hvac_modes:"HVAC mód",show_temperature_control:"Hőmérséklet vezérlő"},cover:{show_buttons_control:"Vezérlő gombok",show_position_control:"Pozíció vezérlő",show_tilt_position_control:"Dőlésszög szabályzó"},fan:{show_oscillate_control:"Oszcilláció vezérlő",show_percentage_control:"Százalékos vezérlő"},generic:{collapsible_controls:"Vezérlők összezárása kikapcsolt állapotban",content_info:"Tartalom",fill_container:"Tároló kitöltése",icon_animation:"Ikon animálása aktív állapotban",icon_color:"Ikon szín",icon_type:"Ikon típus",layout:"Elrendezés",primary_info:"Elsődleges információ",secondary_info:"Másodlagos információ",use_entity_picture:"Entitás kép használata"},humidifier:{show_target_humidity_control:"Páratartalom vezérlő"},light:{incompatible_controls:"Azok a vezérlők nem lesznek megjelenítve, amelyeket a fényforrás nem támogat.",show_brightness_control:"Fényerő vezérlő",show_color_control:"Szín vezérlő",show_color_temp_control:"Színhőmérséklet vezérlő",use_light_color:"Fény szín használata"},lock:{lock:"Zár",open:"Nyitva",unlock:"Nyit"},"media-player":{media_controls:"Média vezérlők",media_controls_list:{next:"Következő szám",on_off:"Ki/bekapcsolás",play_pause_stop:"Lejátszás/szünet/állj",previous:"Előző szám",repeat:"Ismétlés módja",shuffle:"Véletlen lejátszás"},show_volume_level:"Hangerő mutatása",use_media_artwork:"Média borító használata",use_media_info:"Média infó használata",volume_controls:"Hangerő vezérlők",volume_controls_list:{volume_buttons:"Hangerő gombok",volume_mute:"Némítás",volume_set:"Hangerő szint"}},number:{display_mode:"Megjelenítési mód",display_mode_list:{buttons:"Gombok",default:"Alepértelmezett (csúszka)",slider:"Csúszka"}},template:{badge_color:"Jelvény szín",badge_icon:"Jelvény ikon",content:"Tartalom",entity_extra:"Műveletek és sablonok használatakor",multiline_secondary:"Másodlagost több sorba?",picture:"Kép (lecseréli az ikont)",primary:"Elsődleges információ",secondary:"Másodlagos információ"},title:{subtitle:"Alcím",subtitle_tap_action:"Alcímre koppintáskor",title:"Fejléc",title_tap_action:"Fejlécre koppintáskor"},update:{show_buttons_control:"Vezérlő gombok"},vacuum:{commands:"Utasítások",commands_list:{on_off:"Ki/Bekapcsolás"}},weather:{show_conditions:"Állapotok",show_temperature:"Hőmérséklet"}},chip:{"chip-picker":{add:"Chip hozzáadása",chips:"Chip-ek",clear:"Ürítés",edit:"Szerkesztés",select:"Chip kiválasztása",types:{action:"Művelet","alarm-control-panel":"Riasztó",back:"Vissza",conditional:"Feltételes",entity:"Entitás",light:"Fényforrás",menu:"Menü",spacer:"Térköz",template:"Sablon",weather:"Időjárás"}},conditional:{chip:"Chip"},sub_element_editor:{title:"Chip szerkesztő"}},form:{alignment_picker:{values:{center:"Közepe",default:"Alapértelmezett rendezés",end:"Vége",justify:"Sorkizárt",start:"Kezdete"}},color_picker:{values:{default:"Alapértelmezett szín"}},icon_type_picker:{values:{default:"Alapértelmezett típus","entity-picture":"Entitás kép",icon:"Ikon",none:"Egyik sem"}},info_picker:{values:{default:"Alepértelmezett információ","last-changed":"Utoljára módosítva","last-updated":"Utoljára frissítve",name:"Név",none:"Egyik sem",state:"Állapot"}},layout_picker:{values:{default:"Alapértelmezet elrendezés",horizontal:"Vízszintes elrendezés",vertical:"Függőleges elrendezés"}}}},eh={card:Qu,editor:th},nh=Object.freeze({__proto__:null,card:Qu,default:eh,editor:th}),oh={not_found:"Entitas tidak ditemukan"},ih={card:{chips:{alignment:"Perataan"},climate:{hvac_modes:"Mode HVAC",show_temperature_control:"Kontrol suhu?"},cover:{show_buttons_control:"Tombol kontrol?",show_position_control:"Kontrol posisi?",show_tilt_position_control:"Kontrol kemiringan?"},fan:{show_oscillate_control:"Kontrol osilasi?",show_percentage_control:"Kontrol persentase?"},generic:{collapsible_controls:"Sembunyikan kontrol saat mati",color:"Warna",content_info:"Konten",fill_container:"Isi kontainer",icon_animation:"Animasikan ikon saat aktif?",icon_color:"Warna ikon",icon_type:"Tipe ikon",layout:"Tata letak",primary_info:"Informasi primer",secondary_info:"Informasi sekunder",use_entity_picture:"Gunakan gambar entitas?"},humidifier:{show_target_humidity_control:"Kontrol kelembapan?"},light:{incompatible_controls:"Beberapa kontrol mungkin tidak ditampilkan jika lampu Anda tidak mendukung fitur tersebut.",show_brightness_control:"Kontrol kecerahan?",show_color_control:"Kontrol warna?",show_color_temp_control:"Kontrol suhu warna?",use_light_color:"Gunakan warna lampu"},lock:{lock:"Kunci",open:"Buka",unlock:"Buka kunci"},"media-player":{media_controls:"Kontrol media",media_controls_list:{next:"Lagu berikutnya",on_off:"Nyalakan/Matikan",play_pause_stop:"Putar/jeda/stop",previous:"Lagu sebelumnya",repeat:"Mode pengulangan",shuffle:"Acak"},show_volume_level:"Tampilkan level volume",use_media_artwork:"Gunakan gambar seni media",use_media_info:"Gunakan info media",volume_controls:"Kontrol volume",volume_controls_list:{volume_buttons:"Tombol volume",volume_mute:"Bisukan",volume_set:"Level volume"}},number:{display_mode:"Mode Tampilan",display_mode_list:{buttons:"Tombol",default:"Bawaan (geser)",slider:"Geser"}},template:{badge_color:"Warna lencana",badge_icon:"Ikon lencana",content:"Konten",entity_extra:"Digunakan dalam templat dan tindakan",label:"Label",multiline_secondary:"Info sekunder multibaris?",picture:"Gambar (akan menggantikan ikon)",primary:"Informasi primer",secondary:"Informasi sekunder"},title:{subtitle:"Subjudul",subtitle_tap_action:"Tindakan ketuk subjudul",title:"Judul",title_tap_action:"Tindakan ketuk judul"},update:{show_buttons_control:"Tombol kontrol?"},vacuum:{commands:"Perintah",commands_list:{on_off:"Nyalakan/Matikan"}},weather:{show_conditions:"Kondisi?",show_temperature:"Suhu?"}},chip:{"chip-picker":{add:"Tambah cip",chips:"Cip",clear:"Hapus",edit:"Edit",select:"Pilih cip",types:{action:"Tindakan","alarm-control-panel":"Alarm",back:"Kembali",conditional:"Kondisional",entity:"Entitas",light:"Lampu",menu:"Menu",spacer:"Pemisah",template:"Templat",weather:"Cuaca"}},conditional:{chip:"Cip"},sub_element_editor:{title:"Editor cip"}},form:{alignment_picker:{values:{center:"Tengah",default:"Perataan bawaan",end:"Akhir",justify:"Rata kanan-kiri",start:"Awal"}},color_picker:{values:{default:"Warna bawaan"}},icon_type_picker:{values:{default:"Tipe bawaan","entity-picture":"Gambar entitas",icon:"Ikon",none:"Tidak ada"}},info_picker:{values:{default:"Informasi bawaan","last-changed":"Terakhir Diubah","last-updated":"Terakhir Diperbarui",name:"Nama",none:"Tidak ada",state:"Status"}},layout_picker:{values:{default:"Tata letak bawaan",horizontal:"Tata letak horizontal",vertical:"Tata letak vertikal"}}}},rh={card:oh,editor:ih},ah=Object.freeze({__proto__:null,card:oh,default:rh,editor:ih}),sh={not_found:"Entità non trovata"},lh={card:{chips:{alignment:"Allineamento"},climate:{hvac_modes:"Modalità del termostato",show_temperature_control:"Controllo della temperatura?"},cover:{show_buttons_control:"Pulsanti di controllo",show_position_control:"Controllo percentuale apertura",show_tilt_position_control:"Controllo percentuale inclinazione"},fan:{show_oscillate_control:"Controllo oscillazione",show_percentage_control:"Controllo potenza"},generic:{collapsible_controls:"Nascondi i controlli quando spento",color:"Colore",content_info:"Contenuto",fill_container:"Riempi il contenitore",icon_animation:"Anima l'icona quando attiva",icon_color:"Colore dell'icona",icon_type:"Tipo icona",layout:"Disposizione",primary_info:"Informazione primaria",secondary_info:"Informazione secondaria",use_entity_picture:"Usa l'immagine dell'entità"},humidifier:{show_target_humidity_control:"Controllo umidità"},light:{incompatible_controls:"Alcuni controlli potrebbero non essere mostrati se la tua luce non li supporta.",show_brightness_control:"Controllo luminosità",show_color_control:"Controllo colore",show_color_temp_control:"Controllo temperatura",use_light_color:"Usa il colore della luce"},lock:{lock:"Blocca",open:"Aperto",unlock:"Sblocca"},"media-player":{media_controls:"Controlli media",media_controls_list:{next:"Traccia successiva",on_off:"Accendi/Spegni",play_pause_stop:"Play/Pausa/Stop",previous:"Traccia precedente",repeat:"Ciclo continuo",shuffle:"Riproduzione casuale"},show_volume_level:"Mostra volume",use_media_artwork:"Usa la copertina della sorgente",use_media_info:"Mostra le informazioni della sorgente",volume_controls:"Controlli del Volume",volume_controls_list:{volume_buttons:"Bottoni del volume",volume_mute:"Silenzia",volume_set:"Livello del volume"}},number:{display_mode:"Modalità di visualizzazione",display_mode_list:{buttons:"Pulsanti",default:"Predefinito (cursore)",slider:"Cursore"}},template:{badge_color:"Colore del badge",badge_icon:"Icona del badge",content:"Contenuto",entity_extra:"Usato in templates ed azioni",label:"Etichetta",multiline_secondary:"Abilita frasi multilinea",picture:"Immagine (sostituirà l'icona)",primary:"Informazione primaria",secondary:"Informazione secondaria"},title:{subtitle:"Sottotitolo",subtitle_tap_action:"Azione di tap sul sottotitolo",title:"Titolo",title_tap_action:"Azione di tap sul titolo"},update:{show_buttons_control:"Pulsanti di controllo"},vacuum:{commands:"Comandi",commands_list:{on_off:"Accendi/Spegni"}},weather:{show_conditions:"Condizioni",show_temperature:"Temperatura"}},chip:{"chip-picker":{add:"Aggiungi chip",chips:"Chips",clear:"Rimuovi",edit:"Modifica",select:"Seleziona chip",types:{action:"Azione","alarm-control-panel":"Allarme",back:"Pulsante indietro",conditional:"Condizione",entity:"Entità",light:"Luce",menu:"Menù",spacer:"Distanziere",template:"Modello",weather:"Meteo"}},conditional:{chip:"Chip"},sub_element_editor:{title:"Editor di chip"}},form:{alignment_picker:{values:{center:"Centro",default:"Allineamento predefinito",end:"Fine",justify:"Giustificato",start:"Inizio"}},color_picker:{values:{default:"Colore predefinito"}},icon_type_picker:{values:{default:"Tipo predefinito","entity-picture":"Immagine dell'entità",icon:"Icona",none:"Nessuna"}},info_picker:{values:{default:"Informazione predefinita","last-changed":"Ultimo cambiamento","last-updated":"Ultimo aggiornamento",name:"Nome",none:"Nessuno",state:"Stato"}},layout_picker:{values:{default:"Disposizione predefinita",horizontal:"Disposizione orizzontale",vertical:"Disposizione verticale"}}}},ch={card:sh,editor:lh},uh=Object.freeze({__proto__:null,card:sh,default:ch,editor:lh}),hh={card:{chips:{alignment:"정렬"},climate:{hvac_modes:"HVAC 모드",show_temperature_control:"온도 조절 표시"},cover:{show_buttons_control:"컨트롤 버튼 표시",show_position_control:"위치 컨트롤 표시",show_tilt_position_control:"기울기 컨트롤 표시"},fan:{show_oscillate_control:"오실레이트 컨트롤",show_percentage_control:"퍼센트 컨트롤"},generic:{collapsible_controls:"꺼져있을 때 컨트롤 접기",content_info:"내용 정보",fill_container:"콘테이너 채우기",icon_animation:"활성화 시 아이콘 애니메이션 사용",icon_color:"아이콘 색",icon_type:"아이콘 타입",layout:"레이아웃",primary_info:"기본 정보",secondary_info:"보조 정보",use_entity_picture:"엔티티 사진 사용"},humidifier:{show_target_humidity_control:"습도 조절 표시"},light:{incompatible_controls:"조명이 기능을 지원하지 않는 경우 일부 컨트롤이 표시되지 않을 수 있습니다.",show_brightness_control:"밝기 컨트롤 표시",show_color_control:"색 컨트롤 표시",show_color_temp_control:"색 온도 컨트롤 표시",use_light_color:"조명 색 사용"},lock:{lock:"잠금",open:"열기",unlock:"잠금 해제"},"media-player":{media_controls:"미디어 컨트롤",media_controls_list:{next:"다음 트랙",on_off:"켜기/끄기",play_pause_stop:"재생/일시 정지/정지",previous:"이전 트랙",repeat:"반복 모드",shuffle:"섞기"},show_volume_level:"볼륨 레벨 표시",use_media_artwork:"미디어 아트워크 사용",use_media_info:"미디어 정보 사용",volume_controls:"볼륨 컨트롤",volume_controls_list:{volume_buttons:"볼륨 버튼",volume_mute:"음소거",volume_set:"볼륨 레벨"}},template:{badge_color:"뱃지 색",badge_icon:"뱃지 아이콘",content:"내용",entity_extra:"템플릿 및 작업에 사용",multiline_secondary:"Multiline secondary?",picture:"그림 (아이콘 대체)",primary:"기본 정보",secondary:"보조 정보"},title:{subtitle:"부제목",subtitle_tap_action:"부제목 탭 액션",title:"제목",title_tap_action:"제목 탭 액션"},update:{show_buttons_control:"컨트롤 버튼 표시"},vacuum:{commands:"명령어",commands_list:{on_off:"켜기/끄기"}},weather:{show_conditions:"조건 표시",show_temperature:"온도 표시"}},chip:{"chip-picker":{add:"칩 추가",chips:"칩",clear:"클리어",edit:"수정",select:"칩 선택",types:{action:"액션","alarm-control-panel":"알람",back:"이전",conditional:"Conditional",entity:"엔티티",light:"조명",menu:"메뉴",template:"템플릿",weather:"날씨"}},conditional:{chip:"칩"},sub_element_editor:{title:"칩 에디터"}},form:{alignment_picker:{values:{center:"중앙",default:"기본 정렬",end:"끝",justify:"행 정렬",start:"시작"}},color_picker:{values:{default:"기본 색"}},icon_type_picker:{values:{default:"기본 타입","entity-picture":"엔티티 사진",icon:"아이콘",none:"없음"}},info_picker:{values:{default:"기본 정보","last-changed":"마지막 변경","last-updated":"마지막 업데이트",name:"이름",none:"없음",state:"상태"}},layout_picker:{values:{default:"기본 레이아웃",horizontal:"수평 레이아웃",vertical:"수직 레이아웃"}}}},dh={editor:hh},ph=Object.freeze({__proto__:null,default:dh,editor:hh}),fh={not_found:"Enhet ikke funnet"},mh={card:{chips:{alignment:"Justering"},climate:{hvac_modes:"HVAC-moduser",show_temperature_control:"Temperaturkontroll?"},cover:{show_buttons_control:"Kontrollere med knapper?",show_position_control:"Posisjonskontroll?",show_tilt_position_control:"Vippe kontroll?"},fan:{show_oscillate_control:"Oscillerende kontroll?",show_percentage_control:"Prosentvis kontroll?"},generic:{collapsible_controls:"Skjul kontroller når av",color:"Farge",content_info:"Innhold",fill_container:"Fyll beholder",icon_animation:"Animer ikon når aktivt?",icon_color:"Ikon farge",icon_type:"Ikontype",layout:"Oppsett",primary_info:"Primærinformasjon",secondary_info:"Sekundærinformasjon",use_entity_picture:"Bruk enhetsbilde?"},humidifier:{show_target_humidity_control:"Fuktighetskontroll?"},light:{incompatible_controls:"Noen kontroller vises kanskje ikke hvis lyset ditt ikke støtter denne funksjonen.",show_brightness_control:"Lysstyrkekontroll?",show_color_control:"Fargekontroll?",show_color_temp_control:"Temperatur fargekontroll?",use_light_color:"Bruk lys farge"},lock:{lock:"Lås",open:"Åpne",unlock:"Lås opp"},"media-player":{media_controls:"Media kontroller",media_controls_list:{next:"Neste spor",on_off:"Slå på/av",play_pause_stop:"Spill/pause/stopp",previous:"Forrige spor",repeat:"Gjenta",shuffle:"Bland"},show_volume_level:"Vis volumnivå",use_media_artwork:"Bruk mediabilde",use_media_info:"Bruk mediainformasjon",volume_controls:"Volumkontroller",volume_controls_list:{volume_buttons:"Volumknapper",volume_mute:"Demp",volume_set:"Volumnivå"}},number:{display_mode:"Visningsmodus",display_mode_list:{buttons:"Knapper",default:"Standard (skyveknapp)",slider:"Skyveknapp"}},template:{badge_color:"Badge farge",badge_icon:"Badge ikon",content:"Innhold",entity_extra:"Brukes i maler og handlinger",label:"Etikett",multiline_secondary:"Multilinje sekundær?",picture:"Bilde (erstatter ikonet)",primary:"Primærinformasjon",secondary:"Sekundærinformasjon"},title:{subtitle:"Undertekst",subtitle_tap_action:"Undertekst tap action",title:"Tittel",title_tap_action:"Tittel tap action"},update:{show_buttons_control:"Kontroller knapper?"},vacuum:{commands:"Kommandoer",commands_list:{on_off:"Slå på/av"}},weather:{show_conditions:"Forhold?",show_temperature:"Temperatur?"}},chip:{"chip-picker":{add:"Legg til chip",chips:"Chips",clear:"Klare",edit:"Endre",select:"Velg chip",types:{action:"Handling","alarm-control-panel":"Alarm",back:"Tilbake",conditional:"Betinget",entity:"Entitet",light:"Lys",menu:"Meny",spacer:"Mellomrom",template:"Mal",weather:"Vær"}},conditional:{chip:"Chip"},sub_element_editor:{title:"Chip redaktør"}},form:{alignment_picker:{values:{center:"Senter",default:"Standard justering",end:"Slutt",justify:"Blokkjuster",start:"Start"}},color_picker:{values:{default:"Standard farge"}},icon_type_picker:{values:{default:"Standard type","entity-picture":"Enhetsbilde",icon:"Ikon",none:"Ingen"}},info_picker:{values:{default:"Standard informasjon","last-changed":"Sist endret","last-updated":"Sist oppdatert",name:"Navn",none:"Ingen",state:"Tilstand"}},layout_picker:{values:{default:"Standardoppsett",horizontal:"Horisontalt oppsett",vertical:"Vertikalt oppsett"}}}},vh={card:fh,editor:mh},gh=Object.freeze({__proto__:null,card:fh,default:vh,editor:mh}),_h={not_found:"Entiteit niet gevonden"},yh={card:{chips:{alignment:"Uitlijning"},climate:{hvac_modes:"HVAC-Modi",show_temperature_control:"Temperatuur bediening?"},cover:{show_buttons_control:"Bedieningsknoppen?",show_position_control:"Positie bediening?",show_tilt_position_control:"Kantel bediening?"},empty:{no_config_options:"Deze kaart heeft geen configuratie opties."},fan:{show_direction_control:"Richting bediening?",show_oscillate_control:"Oscillatie bediening?",show_percentage_control:"Bediening middels percentage?"},generic:{collapsible_controls:"Bedieningselementen verbergen wanneer uitgeschakeld",color:"Kleur",content_info:"Inhoud",fill_container:"Vul container",icon_animation:"Icoon animeren indien actief?",icon_color:"Icoon kleur",icon_type:"Icoon type",layout:"Lay-out",primary_info:"Primaire informatie",secondary_info:"Secundaire informatie",use_entity_picture:"Gebruik afbeelding van entiteit?"},humidifier:{show_target_humidity_control:"Vochtigheid bediening?"},light:{incompatible_controls:"Sommige bedieningselementen worden mogelijk niet weergegeven als uw lamp deze functie niet ondersteunt.",show_brightness_control:"Helderheidsbediening?",show_color_control:"Kleur bediening?",show_color_temp_control:"Kleurtemperatuur bediening?",use_light_color:"Gebruik licht kleur"},lock:{lock:"Vergrendel",open:"Open",unlock:"Ontgrendel"},"media-player":{media_controls:"Mediabediening",media_controls_list:{next:"Volgende nummer",on_off:"Zet aan/uit",play_pause_stop:"Speel/pauze/stop",previous:"Vorige nummer",repeat:"Herhalen",shuffle:"Willekeurig afspelen"},show_volume_level:"Toon volumeniveau",use_media_artwork:"Gebruik media omslag",use_media_info:"Gebruik media informatie",volume_controls:"Volumebediening",volume_controls_list:{volume_buttons:"Volume knoppen",volume_mute:"Dempen",volume_set:"Volumeniveau"}},number:{display_mode:"Weergave Modus",display_mode_list:{buttons:"Knoppen",default:"Standaard (schuifbalk)",slider:"Schuifbalk"}},template:{badge_color:"Badge kleur",badge_icon:"Badge icoon",content:"Inhoud",entity_extra:"Gebruikt in sjablonen en acties",label:"Label",multiline_secondary:"Secundaire informatie op meerdere regels tonen?",picture:"Afbeelding (zal het icoon vervangen)",primary:"Primaire informatie",secondary:"Secundaire informatie"},title:{subtitle:"Ondertitel",subtitle_tap_action:"Ondertitel tik actie",title:"Titel",title_tap_action:"Titel tik actie"},update:{show_buttons_control:"Bedieningsknoppen?"},vacuum:{commands:"Commando's",commands_list:{on_off:"Zet aan/uit"}},weather:{show_conditions:"Weersomstandigheden?",show_temperature:"Temperatuur?"}},chip:{"chip-picker":{add:"Toevoegen chip",chips:"Chips",clear:"Maak leeg",edit:"Bewerk",select:"Selecteer chip",types:{action:"Actie","alarm-control-panel":"Alarm",back:"Terug",conditional:"Voorwaardelijk",entity:"Entiteit",light:"Licht",menu:"Menu",spacer:"Afstandhouder",template:"Sjabloon",weather:"Weer"}},conditional:{chip:"Chip"},sub_element_editor:{title:"Chip-editor"}},form:{alignment_picker:{values:{center:"Midden",default:"Standaard uitlijning",end:"Einde",justify:"Uitlijnen",start:"Begin"}},color_picker:{values:{default:"Standaard kleur"}},icon_type_picker:{values:{default:"Standaard icoon type","entity-picture":"Entiteit afbeelding",icon:"Icoon",none:"Geen"}},info_picker:{values:{default:"Standaard informatie","last-changed":"Laatst gewijzigd","last-updated":"Laatst bijgewerkt",name:"Naam",none:"Geen",state:"Staat"}},layout_picker:{values:{default:"Standaard lay-out",horizontal:"Horizontale lay-out",vertical:"Verticale lay-out"}}}},bh={card:_h,editor:yh},kh=Object.freeze({__proto__:null,card:_h,default:bh,editor:yh}),wh={not_found:"Nie znaleziono encji"},Ch={card:{chips:{alignment:"Wyrównanie"},climate:{hvac_modes:"Tryby urządzenia",show_temperature_control:"Sterowanie temperaturą?"},cover:{show_buttons_control:"Przyciski sterujące?",show_position_control:"Sterowanie położeniem?",show_tilt_position_control:"Sterowanie poziomem otwarcia?"},fan:{show_direction_control:"Kontrola kierunku?",show_oscillate_control:"Sterowanie oscylacją?",show_percentage_control:"Sterowanie procentowe?"},generic:{collapsible_controls:"Zwiń sterowanie, jeśli wyłączone",color:"Kolor",content_info:"Zawartość",fill_container:"Wypełnij zawartością",icon_animation:"Animować, gdy aktywny?",icon_color:"Kolor ikony",icon_type:"Typ ikony",layout:"Układ",primary_info:"Informacje główne",secondary_info:"Informacje drugorzędne",use_entity_picture:"Użyć obrazu encji?"},humidifier:{show_target_humidity_control:"Sterowanie wilgotnością?"},light:{incompatible_controls:"Niektóre funkcje są niewidoczne, jeśli światło ich nie obsługuje.",show_brightness_control:"Sterowanie jasnością?",show_color_control:"Sterowanie kolorami?",show_color_temp_control:"Sterowanie temperaturą światła?",use_light_color:"Użyj koloru światła"},lock:{lock:"Zablokuj",open:"Otwórz",unlock:"Odblokuj"},"media-player":{media_controls:"Sterowanie multimediami",media_controls_list:{next:"Następne nagranie",on_off:"Włącz/wyłącz",play_pause_stop:"Odtwórz/Pauza/Zatrzymaj",previous:"Poprzednie nagranie",repeat:"Powtarzanie",shuffle:"Losowo"},show_volume_level:"Wyświetl poziom głośności",use_media_artwork:"Użyj okładek multimediów",use_media_info:"Użyj informacji o multimediach",volume_controls:"Sterowanie głośnością",volume_controls_list:{volume_buttons:"Przyciski głośności",volume_mute:"Wycisz",volume_set:"Poziom głośności"}},number:{display_mode:"Sposób wyświetlania",display_mode_list:{buttons:"Przyciski",default:"Domyślnie (suwak)",slider:"Suwak"}},template:{badge_color:"Kolor odznaki",badge_icon:"Ikona odznaki",content:"Zawartość",entity_extra:"Używane w szablonach i akcjach",label:"Etykieta",multiline_secondary:"Drugorzędne wielowierszowe?",picture:"Obraz (zamiast ikony)",primary:"Informacje główne",secondary:"Informacje drugorzędne"},title:{subtitle:"Podtytuł",subtitle_tap_action:"Akcja na podtytule",title:"Tytuł",title_tap_action:"Akcja na tytule"},update:{show_buttons_control:"Przyciski sterujące?"},vacuum:{commands:"Polecenia",commands_list:{on_off:"Włącz/Wyłącz"}},weather:{show_conditions:"Warunki?",show_temperature:"Temperatura?"}},chip:{"chip-picker":{add:"Dodaj czip",chips:"Czipy",clear:"Wyczyść",edit:"Edytuj",select:"Wybierz czip",types:{action:"Akcja","alarm-control-panel":"Alarm",back:"Wstecz",conditional:"Warunkowy",entity:"Encja",light:"Światło",menu:"Menu",spacer:"Odstęp",template:"Szablon",weather:"Pogoda"}},conditional:{chip:"Czip"},sub_element_editor:{title:"Edytor czipów"}},form:{alignment_picker:{values:{center:"Wyśrodkowanie",default:"Wyrównanie domyślne",end:"Wyrównanie do prawej",justify:"Justowanie",start:"Wyrównanie do lewej"}},color_picker:{values:{default:"Domyślny kolor"}},icon_type_picker:{values:{default:"Domyślny typ","entity-picture":"Obraz encji",icon:"Ikona",none:"Brak"}},info_picker:{values:{default:"Domyślne informacje","last-changed":"Ostatnia zmiana","last-updated":"Ostatnia aktualizacja",name:"Nazwa",none:"Brak",state:"Stan"}},layout_picker:{values:{default:"Układ domyślny",horizontal:"Układ poziomy",vertical:"Układ pionowy"}}}},Eh={card:wh,editor:Ch},xh=Object.freeze({__proto__:null,card:wh,default:Eh,editor:Ch}),Ah={not_found:"Entidade não encontrada"},Sh={card:{chips:{alignment:"Alinhamento"},climate:{hvac_modes:"Modos do HVAC",show_temperature_control:"Controle de temperatura?"},cover:{show_buttons_control:"Botões de controle?",show_position_control:"Controle de posição?",show_tilt_position_control:"Controle de inclinação?"},empty:{no_config_options:"Esse card não possui opções de configuração."},fan:{show_direction_control:"Controle de direção?",show_oscillate_control:"Controle de oscilação?",show_percentage_control:"Controle de porcentagem?"},generic:{collapsible_controls:"Recolher controles quando desligado",color:"Cor",content_info:"Conteúdo",fill_container:"Preencher espaço",icon_animation:"Animar ícone quando ativo?",icon_color:"Cor do ícone",icon_type:"Tipo do ícone",layout:"Layout",primary_info:"Informação primária",secondary_info:"Informação secundária",use_entity_picture:"Usar imagem da entidade?"},humidifier:{show_target_humidity_control:"Controle de umidade?"},light:{incompatible_controls:"Alguns controles podem não ser exibidos se sua luz não suportar o recurso.",show_brightness_control:"Controle de brilho?",show_color_control:"Controle de cor?",show_color_temp_control:"Controle de temperatura de cor?",use_light_color:"Usar cor da luz"},lock:{lock:"Bloquear",open:"Abrir",unlock:"Desbloquear"},"media-player":{media_controls:"Controles de mídia",media_controls_list:{next:"Próxima faixa",on_off:"Ligar/Desligar",play_pause_stop:"Reproduzir/pausar/parar",previous:"Faixa anterior",repeat:"Modo repetição",shuffle:"Embaralhar"},show_volume_level:"Mostrar nível de volume",use_media_artwork:"Usar arte da mídia",use_media_info:"Usar informação da mídia",volume_controls:"Controles de volume",volume_controls_list:{volume_buttons:"Botões de volume",volume_mute:"Mudo",volume_set:"Nível de volume"}},number:{display_mode:"Modo de exibição",display_mode_list:{buttons:"Botões",default:"Padrão (deslizante)",slider:"Deslizante"}},template:{badge_color:"Cor do badge",badge_icon:"Ícone do badge",content:"Conteúdo",entity_extra:"Usado em modelos e ações",label:"Label",multiline_secondary:"Multilinha secundária?",picture:"Imagem (irá substituir o ícone)",primary:"Informação primária",secondary:"Informação secundária"},title:{subtitle:"Legenda",subtitle_tap_action:"Ação de toque na legenda",title:"Título",title_tap_action:"Ação de toque no título"},update:{show_buttons_control:"Botões de controle?"},vacuum:{commands:"Comandos",commands_list:{on_off:"Ligar/Desligar"}},weather:{show_conditions:"Condições?",show_temperature:"Temperatura?"}},chip:{"chip-picker":{add:"Adicionar chip",chips:"Chips",clear:"Limpar",edit:"Editar",select:"Selecionar chip",types:{action:"Ação","alarm-control-panel":"Alarme",back:"Voltar",conditional:"Condicional",entity:"Entidade",light:"Luz",menu:"Menu",quickbar:"Barra rápida",spacer:"Espaçador",template:"Template",weather:"Clima"}},conditional:{chip:"Chip"},sub_element_editor:{title:"Editor de chip"}},form:{alignment_picker:{values:{center:"Centro",default:"Alinhamento padrão",end:"Fim",justify:"Justificado",start:"Início"}},color_picker:{values:{default:"Cor padrão"}},icon_type_picker:{values:{default:"Tipo padrão","entity-picture":"Imagem da entidade",icon:"Ícone",none:"Nenhum"}},info_picker:{values:{default:"Informação padrão","last-changed":"Última alteração","last-updated":"Última atualização",name:"Nome",none:"Nenhum",state:"Estado"}},layout_picker:{values:{default:"Layout padrão",horizontal:"Layout horizontal",vertical:"Layout vertical"}}}},Th={card:Ah,editor:Sh},Mh=Object.freeze({__proto__:null,card:Ah,default:Th,editor:Sh}),zh={not_found:"Entidade não encontrada"},Oh={card:{chips:{alignment:"Alinhamento"},climate:{hvac_modes:"Modos HVAC",show_temperature_control:"Controlo de temperatura?"},cover:{show_buttons_control:"Botões de controlo?",show_position_control:"Controlo de posição?",show_tilt_position_control:"Controlo de inclinação?"},fan:{show_oscillate_control:"Controlo de oscilação?",show_percentage_control:"Controlo de percentagem?"},generic:{collapsible_controls:"Colapsar controlos quando desligado",color:"Cor",content_info:"Conteúdo",fill_container:"Preencher contentor",icon_animation:"Animar ícone quando ativo?",icon_color:"Cor do ícone",icon_type:"Tipo de ícone",layout:"Layout",primary_info:"Informação principal",secondary_info:"Informação secundária",use_entity_picture:"Usar imagem da entidade?"},humidifier:{show_target_humidity_control:"Controlo de humidade?"},light:{incompatible_controls:"Alguns controlos podem não ser exibidos se a luz não suportar a funcionalidade.",show_brightness_control:"Controlo de brilho?",show_color_control:"Controlo de cor?",show_color_temp_control:"Controlo de temperatura da cor?",use_light_color:"Usar cor da luz"},lock:{lock:"Trancar",open:"Aberto",unlock:"Destrancar"},"media-player":{media_controls:"Controlos de media",media_controls_list:{next:"Próxima faixa",on_off:"Ligar/Desligar",play_pause_stop:"Tocar/pausa/stop",previous:"Faixa anterior",repeat:"Modo repetir",shuffle:"Baralhar"},show_volume_level:"Mostrar nível do volume",use_media_artwork:"Usar arte do media",use_media_info:"Usar informação do media",volume_controls:"Controlos de volume",volume_controls_list:{volume_buttons:"Botões de volume",volume_mute:"Calar",volume_set:"Nível do volume"}},number:{display_mode:"Modo de exibição",display_mode_list:{buttons:"Botões",default:"Por defeito (slider)",slider:"Deslizador"}},template:{badge_color:"Cor do crachá",badge_icon:"Icóne do crachá",content:"Conteúdo",entity_extra:"Usado em modelos e ações",label:"Rótulo",multiline_secondary:"Secundária multilinha?",picture:"Imagem (irá substituir o ícone)",primary:"Informação principal",secondary:"Informação secundária"},title:{subtitle:"Subtítulo",subtitle_tap_action:"Ação ao tocar no subtítulo",title:"Título",title_tap_action:"Ação ao tocar no título"},update:{show_buttons_control:"Botões de controlo?"},vacuum:{commands:"Comandos",commands_list:{on_off:"Ligar/Desligar"}},weather:{show_conditions:"Condições?",show_temperature:"Temperatura?"}},chip:{"chip-picker":{add:"Adicionar ficha",chips:"Fichas",clear:"Limpar",edit:"Editar",select:"Selecionar ficha",types:{action:"Ação","alarm-control-panel":"Alarme",back:"Voltar",conditional:"Condicional",entity:"Entidade",light:"Iluminação",menu:"Menu",spacer:"Espaçador",template:"Modelo",weather:"Clima"}},conditional:{chip:"Ficha"},sub_element_editor:{title:"Editor de fichas"}},form:{alignment_picker:{values:{center:"Centrado",default:"Alinhamento predefinido",end:"Fim",justify:"Justificado",start:"Início"}},color_picker:{values:{default:"Cor padrão"}},icon_type_picker:{values:{default:"Tipo predefinido","entity-picture":"Entidade de imagem",icon:"Ícone",none:"Nenhum"}},info_picker:{values:{default:"Informações padrão","last-changed":"Última alteração","last-updated":"Última atualização",name:"Nome",none:"Nenhum",state:"Estado"}},layout_picker:{values:{default:"Layout padrão",horizontal:"Layout horizontal",vertical:"Layout vertical"}}}},Ih={card:zh,editor:Oh},jh=Object.freeze({__proto__:null,card:zh,default:Ih,editor:Oh}),Ph={card:{chips:{alignment:"Aliniere"},climate:{hvac_modes:"Moduri HVAC",show_temperature_control:"Comenzi temperatură?"},cover:{show_buttons_control:"Comenzi pentru control?",show_position_control:"Comandă pentru poziție?",show_tilt_position_control:"Comandă pentru înclinare?"},fan:{icon_animation:"Animare pictograma la activare?",show_oscillate_control:"Comandă oscilație?",show_percentage_control:"Comandă procent?"},generic:{collapsible_controls:"Restrângere la dezactivare",content_info:"Conținut",fill_container:"Umplere container",icon_color:"Culoare pictogramă",icon_type:"Tip pictogramă",layout:"Aranjare",primary_info:"Informație principală",secondary_info:"Informație secundară",use_entity_picture:"Imagine?"},humidifier:{show_target_humidity_control:"Comenzi umiditate?"},light:{incompatible_controls:"Unele comenzi ar putea să nu fie afișate dacă lumina nu suportă această caracteristică.",show_brightness_control:"Comandă pentru strălucire?",show_color_control:"Comandă pentru culoare?",show_color_temp_control:"Comandă pentru temperatură de culoare?",use_light_color:"Folosește culoarea luminii"},lock:{lock:"Încuie",open:"Deschide",unlock:"Descuie"},"media-player":{media_controls:"Comenzi media",media_controls_list:{next:"Pista următoare",on_off:"Pornit/Oprit",play_pause_stop:"Redare/Pauză/Stop",previous:"Pista anterioară",repeat:"Mod repetare",shuffle:"Amestecare"},show_volume_level:"Nivel volum",use_media_artwork:"Grafică media",use_media_info:"Informații media",volume_controls:"Comenzi volum",volume_controls_list:{volume_buttons:"Comenzi volum",volume_mute:"Dezactivare sunet",volume_set:"Nivel volum"}},template:{badge_color:"Culoare insignă",badge_icon:"Pictogramă insignă",content:"Conținut",entity_extra:"Folosită în șabloane și acțiuni",multiline_secondary:"Informație secundară pe mai multe linii?",picture:"Imagine (inlocuiește pictograma)",primary:"Informație principală",secondary:"Informație secundară"},title:{subtitle:"Subtitlu",title:"Titlu"},update:{show_buttons_control:"Comenzi control?"},vacuum:{commands:"Comenzi"},weather:{show_conditions:"Condiții?",show_temperature:"Temperatură?"}},chip:{"chip-picker":{add:"Adaugă jeton",chips:"Jetoane",clear:"Șterge",edit:"Modifică",select:"Alege jeton",types:{action:"Acțiune","alarm-control-panel":"Alarmă",back:"Înapoi",conditional:"Condițional",entity:"Entitate",light:"Lumină",menu:"Meniu",template:"Șablon",weather:"Vreme"}},conditional:{chip:"Jeton"},sub_element_editor:{title:"Editor jeton"}},form:{alignment_picker:{values:{center:"Centrat",default:"Aliniere implicită",end:"Dreapta",justify:"Umplere",start:"Stânga"}},color_picker:{values:{default:"Culoare implicită"}},icon_type_picker:{values:{default:"Tip implicit","entity-picture":"Imagine",icon:"Pictogramă",none:"Niciuna"}},info_picker:{values:{default:"Informație implicită","last-changed":"Ultima modificare","last-updated":"Ultima actulizare",name:"Nume",none:"Niciuna",state:"Stare"}},layout_picker:{values:{default:"Aranjare implicită",horizontal:"Orizontală",vertical:"Verticală"}}}},Nh={editor:Ph},Bh=Object.freeze({__proto__:null,default:Nh,editor:Ph}),Lh={not_found:"Сущность не найдена"},Hh={card:{chips:{alignment:"Выравнивание"},climate:{hvac_modes:"Режимы работы",show_temperature_control:"Управлять целевой температурой?"},cover:{show_buttons_control:"Добавить кнопки управления?",show_position_control:"Управлять позицией?",show_tilt_position_control:"Управлять наклоном?"},empty:{no_config_options:"Эта карточка не имеет опций конфигурации."},fan:{icon_animation:"Анимировать иконку когда включено?",show_direction_control:"Направление?",show_oscillate_control:"Oscillate control?",show_percentage_control:"Управлять процентами?"},generic:{collapsible_controls:"Сворачивать элементы управления при выключении",color:"Цвет",content_info:"Содержимое",fill_container:"Заполнение",icon_animation:"Анимировать иконку, когда активна?",icon_color:"Цвет иконки",icon_type:"Тип иконки",layout:"Расположение",primary_info:"Основная информация",secondary_info:"Второстепенная информация",use_entity_picture:"Использовать изображение объекта?"},humidifier:{show_target_humidity_control:"Управлять целевым уровенем влажности?"},light:{incompatible_controls:"Некоторые элементы управления могут не отображаться, если ваш светильник не поддерживает эти функции.",show_brightness_control:"Управлять яркостью?",show_color_control:"Управлять цветом?",show_color_temp_control:"Управлять цветовой температурой?",use_light_color:"Использовать текущий цвет света"},lock:{lock:"Закрыто",open:"Открыто",unlock:"Разблокировано"},"media-player":{media_controls:"Управление медиа-устройством",media_controls_list:{next:"Следующий трек",on_off:"Включение/выключение",play_pause_stop:"Воспроизведение/пауза/остановка",previous:"Предыдущий трек",repeat:"Режим повтора",shuffle:"Перемешивание"},show_volume_level:"Показать уровень громкости",use_media_artwork:"Использовать обложку с медиа-устройства",use_media_info:"Использовать информацию с медиа-устройства",volume_controls:"Регулятор громкости",volume_controls_list:{volume_buttons:"Кнопки громкости",volume_mute:"Без звука",volume_set:"Уровень громкости"}},number:{display_mode:"Режим отображения",display_mode_list:{buttons:"Кнопки",default:"Стандартно (слайдер)",slider:"Слайдер"}},template:{badge_color:"Цвет значка",badge_icon:"Иконка значка",content:"Содержимое",entity_extra:"Используется в шаблонах и действиях",label:"Ярлык",multiline_secondary:"Многострочная Второстепенная информация?",picture:"Изображение (заменить иконку)",primary:"Основная информация",secondary:"Второстепенная информация"},title:{subtitle:"Подзаголовок",subtitle_tap_action:"Действие при нажатии на подзаголовок",title:"Заголовок",title_tap_action:"Действие при нажатии на заголовок"},update:{show_buttons_control:"Кнопки управления?"},vacuum:{commands:"Команды",commands_list:{on_off:"Включить/выключить"}},weather:{show_conditions:"Условия?",show_temperature:"Температура?"}},chip:{"chip-picker":{add:"Добавить мини-карточку",chips:"Мини-карточки",clear:"Очистить",edit:"Изменить",select:"Выбрать мини-карточку",types:{action:"Действие","alarm-control-panel":"Тревога",back:"Назад",conditional:"Условия",entity:"Объект",light:"Освещение",menu:"Меню",quickbar:"Панель быстрого доступа",spacer:"Пробел",template:"Шаблон",weather:"Погода"}},conditional:{chip:"Мини-карточка"},sub_element_editor:{title:"Редактор мини-карточек"}},form:{alignment_picker:{values:{center:"По центру",default:"Выравнивание по умолчанию",end:"К концу",justify:"На всю ширину",start:"К началу"}},color_picker:{values:{default:"Цвет по умолчанию"}},icon_type_picker:{values:{default:"По умолчанию","entity-picture":"Изображение",icon:"Иконка",none:"Нет"}},info_picker:{values:{default:"По умолчанию","last-changed":"Последнее изменение","last-updated":"Последнее обновление",name:"Имя",none:"Нет",state:"Статус"}},layout_picker:{values:{default:"Расположение по умолчанию",horizontal:"Горизонтальное расположение",vertical:"Вертикальное расположение"}}}},Dh={card:Lh,editor:Hh},Rh=Object.freeze({__proto__:null,card:Lh,default:Dh,editor:Hh}),Uh={not_found:"Entita nenájdená"},Vh={badge:{template:{area_helper:"Používa sa v šablónach",content:"Obsah",entity_helper:"Používa sa v šablónach a interakciách",label:"Nápis"}},card:{chips:{alignment:"Zarovnanie"},climate:{hvac_modes:"HVAC mód",show_temperature_control:"Ovládanie teploty?"},cover:{show_buttons_control:"Zobraziť ovládacie tlačidlá?",show_position_control:"Ovládanie pozície?",show_tilt_position_control:"Ovládanie natočenia?"},empty:{no_config_options:"Táto karta nemá žiadne možnosti konfigurácie."},fan:{show_direction_control:"Ovládanie smeru?",show_oscillate_control:"Ovládanie oscilácie?",show_percentage_control:"Ovládanie rýchlosti v percentách?"},generic:{area:"Oblasť",collapsible_controls:"Skryť ovládanie v stave VYP",color:"Farba",content_info:"Obsah",entity:"Entita",fill_container:"Vyplniť priestor",icon_animation:"Animovaná ikona v stave ZAP?",icon_color:"Farba ikony",icon_type:"Typ ikony",layout:"Rozloženie",picture:"Obrázok",picture_helper:"Ak je nastavené, nahradí ikonu.",primary_info:"Základné info",secondary_info:"Doplnkové info",use_entity_picture:"Použiť obrázok entity?"},humidifier:{show_target_humidity_control:"Ovládanie vlhkosti?"},light:{incompatible_controls:"Niektoré ovládacie prvky sa nemusia zobraziť, pokiaľ ich svetlo nepodporuje.",show_brightness_control:"Ovládanie jasu?",show_color_control:"Ovládanie farby?",show_color_temp_control:"Ovládanie teploty farby?",use_light_color:"Použiť farbu svetla"},lock:{lock:"Zamknuté",open:"Otvorené",unlock:"Odomknuté"},"media-player":{media_controls:"Ovládanie média",media_controls_list:{next:"Ďalšia",on_off:"Zap / Vyp",play_pause_stop:"Spustiť/pauza/stop",previous:"Predchádzajúca",repeat:"Opakovať",shuffle:"Premiešať"},show_volume_level:"Zobraziť úroveň hlasitosti",use_media_artwork:"Použiť obrázok z média",use_media_info:"Použiť info o médiu",volume_controls:"Ovládanie hlasitosti",volume_controls_list:{volume_buttons:"Tlačidlá hlasitosti",volume_mute:"Stlmiť",volume_set:"Úroveň hlasitosti"}},number:{display_mode:"Režim zobrazenia",display_mode_list:{buttons:"Tlačidlá",default:"Predvolené (posúvač)",slider:"Posúvač"}},template:{area:"Oblasť",area_helper:"Používa sa v šablónach a funkciách",badge:"Odznak",badge_color:"Farba odznaku",badge_icon:"Ikona odznaku",badge_text:"Text odznaku",badge_text_helper:"Ak je nastavené, nahradí ikonu.",content:"Obsah",entity_extra:"Použitá v šablónach a akciách",entity_helper:"Používa sa v šablónach, interakciách a funkciách",entity_helper_legacy:"Používa sa v šablónach a interakciách",label:"Štítok",layout:"Rozloženie",multiline_secondary:"Povoliť viacriadkové doplnkové informácie",multiline_secondary_helper:"Karta môže byť vyššia, aby sa do nej vošiel text, a nemusí byť vždy zarovnaná s mriežkovým systémom.",picture:"Obrázok (nahrádza ikonu)",primary:"Základné info",secondary:"Doplnkové info"},title:{subtitle:"Podnadpis",subtitle_tap_action:"Akcia klepnutia na titulky",title:"Nadpis",title_tap_action:"Akcia klepnutia na názov"},update:{show_buttons_control:"Zobraziť ovládacie tlačidlá?"},vacuum:{commands:"Príkazy",commands_list:{on_off:"Zapnúť/Vypnúť"}},weather:{show_conditions:"Zobraziť podmienky?",show_temperature:"Zobraziť teplotu?"}},chip:{"chip-picker":{add:"Pridať štítok",chips:"Štítky",clear:"Vymazať",edit:"Editovať",select:"Vybrať štítok",types:{action:"Akcia","alarm-control-panel":"Alarm",back:"Späť",conditional:"Podmienené",entity:"Entita",light:"Svetlo",menu:"Menu",quickbar:"Rýchla lišta",spacer:"Medzera",template:"Šablóna",weather:"Počasie"}},conditional:{chip:"Čip"},sub_element_editor:{title:"Editor štítkov"}},form:{alignment_picker:{values:{center:"Stred",default:"Predvolené zarovnanie",end:"Koniec",justify:"Vyplniť",start:"Začiatok"}},color_picker:{values:{default:"Predvolená farba"}},icon_type_picker:{values:{default:"Predvolený typ","entity-picture":"Obrázok entity",icon:"Ikona",none:"Žiadny"}},info_picker:{values:{default:"Predvolené informácie","last-changed":"Posledná zmena","last-updated":"Posledná aktualizácia",name:"Názov",none:"Žiadna",state:"Stav"}},layout_picker:{values:{default:"Predvolené rozloženie",horizontal:"Vodorovné rozloženie",vertical:"Zvislé rozloženie"}}},section:{badge:"Odznak",content:"Obsah",context:"Kontext",features:"Funkcie",interactions:"Interakcie",layout:"Rozloženie"}},Fh={description:"Nastavenie vašej karty bolo prenesené do novej verzie. Viac informácií o zmenách nájdete na {link}.",ok:"Ok",post:"príspevku na GitHub",revert:"Vrátiť späť",title:"Karta aktualizovaná"},$h={card:Uh,editor:Vh,migration:Fh},Gh=Object.freeze({__proto__:null,card:Uh,default:$h,editor:Vh,migration:Fh}),Kh={not_found:"Entiteta ni najdena"},Yh={card:{chips:{alignment:"Poravnava"},climate:{hvac_modes:"HVAC načini",show_temperature_control:"Nadzor temperature?"},cover:{show_buttons_control:"Gumbi za upravljanje?",show_position_control:"Nadzor položaja?",show_tilt_position_control:"Nadzor nagiba?"},fan:{show_oscillate_control:"Kontrola nihanja?",show_percentage_control:"Kontrola v odstotkih?"},generic:{collapsible_controls:"Strni kontrolnike, ko so izklopljeni",content_info:"Vsebina",fill_container:"Zapolnitev prostora",icon_animation:"Animacija ikone, ko je aktivna?",icon_color:"Barva ikone",icon_type:"Vrsta ikone",layout:"Postavitev",primary_info:"Primarna informacija",secondary_info:"Sekundarna informacija",use_entity_picture:"Uporabi sliko entitete?"},humidifier:{show_target_humidity_control:"Nadzor vlažnosti?"},light:{incompatible_controls:"Nekateri kontrolniki morda ne bodo prikazani, če vaša luč ne podpira te funkcije.",show_brightness_control:"Nadzor svetlosti?",show_color_control:"Nadzor barv?",show_color_temp_control:"Nadzor temperature barve?",use_light_color:"Uporabi svetlo barvo"},lock:{lock:"Zaklepanje",open:"Odprto",unlock:"Odkleni"},"media-player":{media_controls:"Nadzor medijev",media_controls_list:{next:"Naslednja skladba",on_off:"Vklop/izklop",play_pause_stop:"Predvajaj/pavza/ustavi",previous:"Prejšnja skladba",repeat:"Ponavljajoči način",shuffle:"Naključno"},show_volume_level:"Pokaži raven glasnosti",use_media_artwork:"Uporabite medijsko umetniško delo",use_media_info:"Uporabite informacije o medijih",volume_controls:"Kontrole glasnosti",volume_controls_list:{volume_buttons:"Gumbi za glasnost",volume_mute:"Tiho",volume_set:"Raven glasnosti"}},number:{display_mode:"Način prikaza",display_mode_list:{buttons:"Gumbi",default:"Privzeto (drsnik)",slider:"Drsnik"}},template:{badge_color:"Barva značke",badge_icon:"Ikona značke",content:"Vsebina",entity_extra:"Uporablja se v predlogah in dejanjih",multiline_secondary:"Večvrstični sekundarni?",picture:"Slika (nadomestila bo ikono)",primary:"Primarna informacija",secondary:"Sekundarna informacija"},title:{subtitle:"Podnaslov",subtitle_tap_action:"Dejanje dotika podnapisov",title:"Naziv",title_tap_action:"Dejanje dotika naslova"},update:{show_buttons_control:"Gumbi za upravljanje?"},vacuum:{commands:"Ukazi",commands_list:{on_off:"Vklop/izklop"}},weather:{show_conditions:"Pogoji?",show_temperature:"Temperatura?"}},chip:{"chip-picker":{add:"Dodaj čip",chips:"Čipi",clear:"Pobriši",edit:"Uredi",select:"Izbira čipa",types:{action:"Dejanje","alarm-control-panel":"Alarm",back:"Nazaj",conditional:"Pogojno",entity:"Entiteta",light:"Svetloba",menu:"Meni",spacer:"Distančnik",template:"Predloga",weather:"Vreme"}},conditional:{chip:"Ćiš"},sub_element_editor:{title:"Urejevalnik čipov"}},form:{alignment_picker:{values:{center:"Center",default:"Privzeta poravnava",end:"Konec",justify:"Poravnava",start:"Pričetek"}},color_picker:{values:{default:"Privzeta barva"}},icon_type_picker:{values:{default:"Privzeta vrsta","entity-picture":"Slika entitete",icon:"Ikona",none:"Brez"}},info_picker:{values:{default:"Privzete informacije","last-changed":"Zadnja sprememba","last-updated":"Zadnja posodobitev",name:"Naziv",none:"Brez",state:"Stanje"}},layout_picker:{values:{default:"Privzeta postavitev",horizontal:"Horizontalna postavitev",vertical:"Vertikalna postavitev"}}}},qh={card:Kh,editor:Yh},Wh={not_found:"Enheten hittades inte"},Xh={card:{chips:{alignment:"Justering"},climate:{hvac_modes:"HVAC-lägen",show_temperature_control:"Temperaturkontroll?"},cover:{show_buttons_control:"Visa kontrollknappar?",show_position_control:"Visa positionskontroll?",show_tilt_position_control:"Visa lutningskontroll?"},empty:{no_config_options:"Detta kort har inga konfigurationsalternativ."},fan:{show_direction_control:"Riktningskontroll?",show_oscillate_control:"Kontroll för oscillera?",show_percentage_control:"Procentuell kontroll?"},generic:{collapsible_controls:"Dölj kontroller när enheten är av",color:"Färg",content_info:"Innehåll",fill_container:"Fyll container",icon_animation:"Animera ikonen när enheten är på?",icon_color:"Ikonens färg",icon_type:"Ikontyp",layout:"Layout",primary_info:"Primär information",secondary_info:"Sekundär information",use_entity_picture:"Använd enhetens bild?"},humidifier:{show_target_humidity_control:"Fuktkontroll?"},light:{incompatible_controls:"Kontroller som inte stöds av enheten kommer inte visas.",show_brightness_control:"Styr ljushet?",show_color_control:"Styr färg?",show_color_temp_control:"Färgtemperaturkontroll?",use_light_color:"Styr ljusets färg"},lock:{lock:"Lås",open:"Öppna",unlock:"Lås upp"},"media-player":{media_controls:"Mediakontroller",media_controls_list:{next:"Nästa spår",on_off:"Slå på/av",play_pause_stop:"Spela/pausa/stoppa",previous:"Föregående spår",repeat:"Upprepa",shuffle:"Blanda"},show_volume_level:"Volymkontroll",use_media_artwork:"Visa mediaomslag",use_media_info:"Använd media information",volume_controls:"Volymkontroller",volume_controls_list:{volume_buttons:"Volymknappar",volume_mute:"Ljud av",volume_set:"Volymnivå"}},number:{display_mode:"Visningsläge",display_mode_list:{buttons:"Knappar",default:"Standard (skjutreglage)",slider:"Skjutreglage"}},template:{badge_color:"Färg på märke",badge_icon:"Märke ikon",content:"Innehåll",entity_extra:"Används i mallar och åtgärder",label:"Etikett",multiline_secondary:"Sekundär med flera rader?",picture:"Bild (ersätter ikonen)",primary:"Primär information",secondary:"Sekundär information"},title:{subtitle:"Underrubrik",subtitle_tap_action:"Subtitle tap action",title:"Rubrik",title_tap_action:"Titel tryck åtgärd"},update:{show_buttons_control:"Visa kontrollknappar?"},vacuum:{commands:"Kommandon",commands_list:{on_off:"Slå av/på"}},weather:{show_conditions:"Förhållanden?",show_temperature:"Temperatur?"}},chip:{"chip-picker":{add:"Lägg till chip",chips:"Chips",clear:"Rensa",edit:"Redigera",select:"Välj chip",types:{action:"Åtgärd","alarm-control-panel":"Alarm",back:"Bakåt",conditional:"Villkorad",entity:"Enhet",light:"Ljus",menu:"Meny",quickbar:"Snabbfält",spacer:"Avståndshållare",template:"Mall",weather:"Väder"}},conditional:{chip:"Chip"},sub_element_editor:{title:"Chipredigerare"}},form:{alignment_picker:{values:{center:"Centrerad",default:"Standard (början)",end:"Slutet",justify:"Anpassa",start:"Starta"}},color_picker:{values:{default:"Standardfärg"}},icon_type_picker:{values:{default:"Standard typ","entity-picture":"Enhetsbild",icon:"Ikon",none:"Ingen"}},info_picker:{values:{default:"Förvald information","last-changed":"Sist ändrad","last-updated":"Sist uppdaterad",name:"Namn",none:"Ingen",state:"Status"}},layout_picker:{values:{default:"Standard",horizontal:"Horisontell",vertical:"Vertikal"}}}},Zh={card:Wh,editor:Xh},Jh={not_found:"Varlık bulunamadı"},Qh={card:{chips:{alignment:"Hizalama"},climate:{hvac_modes:"HVAC Modları",show_temperature_control:"Sıcaklık kontrolü?"},cover:{show_buttons_control:"Düğme kontrolleri?",show_position_control:"Pozisyon kontrolü?",show_tilt_position_control:"Eğim kontrolü?"},empty:{no_config_options:"Bu kartın yapılandırma seçeneği yok."},fan:{show_direction_control:"Yön kontrolü?",show_oscillate_control:"Salınım kontrolü?",show_percentage_control:"Yüzde kontrolü?"},generic:{collapsible_controls:"Kapalıyken kontrolleri daralt",color:"Renk",content_info:"İçerik",fill_container:"Alanı doldur",icon_animation:"Aktif olduğunda simgeyi hareket ettir?",icon_color:"Simge renki",icon_type:"İkon tipi",layout:"Düzen",primary_info:"Birinci bilgi",secondary_info:"İkinci bilgi",use_entity_picture:"Varlık resmi kullanılsın?"},humidifier:{show_target_humidity_control:"Nem kontrolü?"},light:{incompatible_controls:"Kullandığınız lamba bu özellikleri desteklemiyorsa bazı kontroller görüntülenemeyebilir.",show_brightness_control:"Parlaklık kontrolü?",show_color_control:"Renk kontrolü?",show_color_temp_control:"Renk ısısı kontrolü?",use_light_color:"Işık rengini kullan"},lock:{lock:"Kilitle",open:"Aç",unlock:"Kilidi aç"},"media-player":{media_controls:"Medya kontrolleri",media_controls_list:{next:"Sonraki parça",on_off:"Aç/Kapat",play_pause_stop:"Oynat/duraklat/durdur",previous:"Önceki parça",repeat:"Tekrarlama modu",shuffle:"Karışık çal"},show_volume_level:"Ses seviyesini göster",use_media_artwork:"Medya resimlerini kullan",use_media_info:"Medya bilgilerini kullan",volume_controls:"Ses seviyesi kontrolleri",volume_controls_list:{volume_buttons:"Ses butonları",volume_mute:"Sessize al",volume_set:"Ses seviyesi"}},number:{display_mode:"Görüntüleme Modu",display_mode_list:{buttons:"Butonlar",default:"Varsayılan (kayan)",slider:"Kayan"}},template:{badge_color:"Rozet rengi",badge_icon:"Rozet simgesi",content:"İçerik",entity_extra:"Şablonlarda ve eylemlerde kullanılsın",label:"Etiket",multiline_secondary:"İkinci bilgi çok satır olsun?",picture:"Resim (ikonun yerine geçecek)",primary:"Birinci bilgi",secondary:"İkinci bilgi"},title:{subtitle:"Altbaşlık",subtitle_tap_action:"Dokunma eylemi alt başlığı",title:"Başlık",title_tap_action:"Dokunma eylemi başlığı"},update:{show_buttons_control:"Düğme kontrolü?"},vacuum:{commands:"Komutlar",commands_list:{on_off:"Aç/Kapat"}},weather:{show_conditions:"Hava koşulu?",show_temperature:"Sıcaklık?"}},chip:{"chip-picker":{add:"Chip ekle",chips:"Chipler",clear:"Temizle",edit:"Düzenle",select:"Chip seç",types:{action:"Eylem","alarm-control-panel":"Alarm",back:"Geri",conditional:"Koşullu",entity:"Varlık",light:"Işık",menu:"Menü",spacer:"Boşluk",template:"Şablon",weather:"Hava Durumu"}},conditional:{chip:"Chip"},sub_element_editor:{title:"Chip düzenleyici"}},form:{alignment_picker:{values:{center:"Ortala",default:"Varsayılan hizalama",end:"Sağa yasla",justify:"İki yana yasla",start:"Sola yasla"}},color_picker:{values:{default:"Varsayılan renk"}},icon_type_picker:{values:{default:"Varsayılan tip","entity-picture":"Varlık resmi",icon:"Simge",none:"Hiçbiri"}},info_picker:{values:{default:"Varsayılan bilgi","last-changed":"Son Değişim","last-updated":"Son Güncelleme",name:"İsim",none:"None",state:"Durum"}},layout_picker:{values:{default:"Varsayılan düzen",horizontal:"Yatay düzen",vertical:"Dikey düzen"}}}},td={card:Jh,editor:Qh},ed={not_found:"Сутність не знайдено"},nd={card:{chips:{alignment:"Вирівнювання"},climate:{hvac_modes:"Режими",show_temperature_control:"Керування температурою?"},cover:{show_buttons_control:"Кнопки керування?",show_position_control:"Керування позицією?",show_tilt_position_control:"Керування нахилом?"},fan:{show_oscillate_control:"Керування повротом?",show_percentage_control:"Керування швидкістю?"},generic:{collapsible_controls:"Приховувати елементи керування коли вимкнено?",content_info:"Вміст",fill_container:"Заповнити контейнер",icon_animation:"Анімувати іконку при активації?",icon_color:"Колір іконки",icon_type:"Тип іконки",layout:"Розташування",primary_info:"Головна інформація",secondary_info:"Додаткова інформація",use_entity_picture:"Використовувати зображення сутності?"},humidifier:{show_target_humidity_control:"Керування вологістю?"},light:{incompatible_controls:"Деякі елементи керування можуть не відображатись якщо ваш пристрій не підтримує цю функцію.",show_brightness_control:"Контроль яскравості?",show_color_control:"Керування кольором світла?",show_color_temp_control:"Керування температурою світла?",use_light_color:"Використовувати колір світла"},lock:{lock:"Зачинити",open:"Відкрити",unlock:"Відчинити"},"media-player":{media_controls:"Керування медіа",media_controls_list:{next:"Наступний трек",on_off:"Увімкнути/Вимкнути",play_pause_stop:"Відтворити/пауза/стоп",previous:"Попередній трек",repeat:"Режим повторення",shuffle:"Перемішати"},show_volume_level:"Показати рівень гучності",use_media_artwork:"Використовувати зображення медіа",use_media_info:"Використовувати інформацію медіа",volume_controls:"Елементи керування гучністю",volume_controls_list:{volume_buttons:"Кнопки гучності",volume_mute:"Вимк. звук",volume_set:"Рівень гучності"}},number:{display_mode:"Відображати режим",display_mode_list:{buttons:"Кнопки",default:"За замовчуванням (повзунок)",slider:"Повзунок"}},template:{badge_color:"Колір значка",badge_icon:"Іконка значка",content:"Вміст",entity_extra:"Використовується в шаблонах та діях",multiline_secondary:"Багаторядкова додаткова інформація?",picture:"Зображення (замінить іконку)",primary:"Головна інформація",secondary:"Додаткова інформація"},title:{subtitle:"Підзаголовок",subtitle_tap_action:"Дія при дотику до підзаголовку",title:"Заголовок",title_tap_action:"Дія при дотику до заголовку"},update:{show_buttons_control:"Кнопки керування?"},vacuum:{commands:"Команди",commands_list:{on_off:"Увімкнути/Вимкнути"}},weather:{show_conditions:"Умови?",show_temperature:"Температура?"}},chip:{"chip-picker":{add:"Додати міні-картку",chips:"Міні-картки",clear:"Очистити",edit:"Редагувати",select:"Обрати міні-картку",types:{action:"Дія","alarm-control-panel":"Сигналізація",back:"Назад",conditional:"Умовна",entity:"Сутність",light:"Світло",menu:"Меню",spacer:"Порожнє місце",template:"Вручну",weather:"Погода"}},conditional:{chip:"Міні-картка"},sub_element_editor:{title:"Редактор міні-карток"}},form:{alignment_picker:{values:{center:"По центру",default:"Вирівнювання за замовчуванням",end:"В кінці",justify:"Вирівняти",start:"На початку"}},color_picker:{values:{default:"Колір за замовчуванням"}},icon_type_picker:{values:{default:"За замовчуванням","entity-picture":"Зображення сутності",icon:"Іконка",none:"Нічого"}},info_picker:{values:{default:"Інформація за замовчуванням","last-changed":"Востаннє змінено","last-updated":"Востаннє оновлено",name:"Назва",none:"Нічого",state:"Стан"}},layout_picker:{values:{default:"Розташування за замовчуванням",horizontal:"Горизонтальне розташування",vertical:"Вертикальне розташування"}}}},od={card:ed,editor:nd},id={not_found:"Không tìm thấy thực thể"},rd={section:{context:"Ngữ cảnh",content:"Nội dung",features:"Tính năng",interactions:"Tương tác",layout:"Bố cục",badge:"Huy hiệu"},card:{chips:{alignment:"Căn chỉnh"},climate:{hvac_modes:"Chế độ điều hòa",show_temperature_control:"Điều khiển nhiệt độ?"},cover:{show_buttons_control:"Điều khiển nút bấm?",show_position_control:"Điều khiển vị trí?",show_tilt_position_control:"Điều khiển độ nghiêng?"},empty:{no_config_options:"Thẻ này không có tùy chọn cấu hình."},fan:{show_direction_control:"Điều khiển hướng?",show_oscillate_control:"Điều khiển xoay?",show_percentage_control:"Điều khiển phần trăm?"},generic:{entity:"Thực thể",area:"Khu vực",color:"Màu",content_info:"Nội dung",fill_container:"Làm đầy ô chứa",icon_animation:"Biểu tượng chuyển động khi kích hoạt?",icon_color:"Màu biểu tượng",icon_type:"Kiểu biểu tượng",layout:"Bố cục",primary_info:"Thông tin chính",secondary_info:"Thông tin phụ",use_entity_picture:"Dùng ảnh của thực thể?",collapsible_controls:"Thu nhỏ điều kiển khi tắt",picture:"Hình ảnh",picture_helper:"Nếu đặt, nó sẽ thay cho biểu tượng."},humidifier:{show_target_humidity_control:"Điều khiển độ ẩm?"},light:{incompatible_controls:"Một số điều khiển sẽ không được hiển thị nếu đèn của bạn không hỗ trợ tính năng đó.",show_brightness_control:"Điều khiển độ sáng?",show_color_control:"Điều khiển màu sắc?",show_color_temp_control:"Điều khiển nhiệt độ màu?",use_light_color:"Dùng màu đèn"},lock:{lock:"Khóa",open:"Mở",unlock:"Mở khóa"},"media-player":{media_controls:"Điều khiển đa phương tiện",media_controls_list:{next:"Bài tiếp theo",on_off:"Bật/tắt",play_pause_stop:"Phát/tạm dừng/dừng",previous:"Bài trước",repeat:"Chế độ lặp lại",shuffle:"Xáo trộn"},show_volume_level:"Hiện mức âm lượng",use_media_artwork:"Dùng ảnh đa phương tiện",use_media_info:"Dùng thông tin đa phương tiện",volume_controls:"Điều khiển âm lượng",volume_controls_list:{volume_buttons:"Nút âm lượng",volume_mute:"Im lặng",volume_set:"Mức âm lượng"}},number:{display_mode:"Chế độ hiển thị",display_mode_list:{buttons:"Nút",default:"Mặc định (thanh trượt)",slider:"Thanh trượt"}},template:{area_helper:"Dùng trong bản mẫu và tính năng",area:"Khu vực",badge_color:"Màu huy hiệu",badge_icon:"Biểu tượng huy hiệu",badge_text_helper:"Nếu đặt, nó sẽ thay thế biểu tượng.",badge_text:"Chữ trong huy hiệu",badge:"Huy hiệu",content:"Nội dung",entity_helper:"Dùng trong bản mẫu, tương tác và tính năng",entity_helper_legacy:"Dùng trong bản mẫu và tương tác",label:"Nhãn",layout:"Bố cục",multiline_secondary_helper:"Thẻ có thể được kéo cao lên để vừa với văn bản và không phải lúc nào cũng vừa vặn với hệ thống lưới.",multiline_secondary:"Cho phép nhiều dòng thông tin phụ",primary:"Thông tin chính",secondary:"Thông tin phụ"},title:{subtitle:"Phụ đề",subtitle_tap_action:"Hành động khi nhấp phụ đề",title:"Tiêu đề",title_tap_action:"Hành động khi nhấp tiêu đề"},update:{show_buttons_control:"Điều khiển nút bấm?"},vacuum:{commands:"Mệnh lệnh",commands_list:{on_off:"Bật/tắt"}},weather:{show_conditions:"Điều kiện?",show_temperature:"Nhiệt độ?"}},badge:{template:{label:"Nhãn",content:"Nội dung",entity_helper:"Dùng trong bản mẫu và tương tác",area_helper:"Dùng trong bản mẫu"}},chip:{"chip-picker":{add:"Thêm phỉnh",chips:"Phỉnh",clear:"Tẩy trống",edit:"Chỉnh sửa",select:"Chọn phỉnh",types:{action:"Hành động","alarm-control-panel":"Báo động",back:"Quay về",conditional:"Điều kiện",entity:"Thực thể",light:"Đèn",menu:"Trình đơn",quickbar:"Thanh nhanh",spacer:"Ngăn cách",template:"Mẫu",weather:"Thời tiết"}},conditional:{chip:"Phỉnh"},sub_element_editor:{title:"Trình soạn phỉnh"}},form:{alignment_picker:{values:{center:"Căn giữa",default:"Căn chỉnh mặc định",end:"Căn cuối",justify:"Căn hai bên",start:"Căn đầu"}},color_picker:{values:{default:"Màu mặc định"}},icon_type_picker:{values:{default:"Kiểu mặc định","entity-picture":"Ảnh thực thể",icon:"Biểu tượng",none:"Không có"}},info_picker:{values:{default:"Thông tin mặc định","last-changed":"Lần thay đổi cuối","last-updated":"Lần cập nhật cuối",name:"Tên",none:"Không có",state:"Trạng thái"}},layout_picker:{values:{default:"Bố cục mặc định",horizontal:"Bố cục ngang",vertical:"Bố cục dọc"}}}},ad={title:"Thẻ đã cập nhật",description:"Cấu hình thẻ của bạn đã được nhập thành phiên bản mới. Bạn có thể tìm thêm thông tin về thay đổi tại {link}.",post:"bài trên GitHub",revert:"Đảo ngược",ok:"Ok"},sd={card:id,editor:rd,migration:ad},ld={not_found:"未找到实体"},cd={card:{chips:{alignment:"对齐"},climate:{hvac_modes:"空调模式",show_temperature_control:"温度控制?"},cover:{show_buttons_control:"按钮控制?",show_position_control:"位置控制?",show_tilt_position_control:"角度控制?"},empty:{no_config_options:"这个卡片没有可配置的选项。"},fan:{show_direction_control:"方向控制?",show_oscillate_control:"摆动控制?",show_percentage_control:"百分比控制?"},generic:{collapsible_controls:"关闭时隐藏控制器",color:"颜色",content_info:"内容",fill_container:"填满容器",icon_animation:"激活时使用动态图标?",icon_color:"图标颜色",icon_type:"图标类型",layout:"布局",primary_info:"首要信息",secondary_info:"次要信息",use_entity_picture:"使用实体图片?"},humidifier:{show_target_humidity_control:"湿度控制?"},light:{incompatible_controls:"设备不支持的控制器将不会显示。",show_brightness_control:"亮度控制?",show_color_control:"颜色控制?",show_color_temp_control:"色温控制?",use_light_color:"使用灯光颜色"},lock:{lock:"锁定",open:"打开",unlock:"解锁"},"media-player":{media_controls:"媒体控制",media_controls_list:{next:"下一曲",on_off:"开启/关闭",play_pause_stop:"播放/暂停/停止",previous:"上一曲",repeat:"循环模式",shuffle:"随机"},show_volume_level:"显示音量大小",use_media_artwork:"使用媒体插图",use_media_info:"使用媒体信息",volume_controls:"音量控制",volume_controls_list:{volume_buttons:"音量按钮",volume_mute:"静音",volume_set:"音量等级"}},number:{display_mode:"显示模式",display_mode_list:{buttons:"按钮",default:"默认 (滑块)",slider:"滑块"}},template:{badge_color:"徽标颜色",badge_icon:"徽标图标",content:"内容",entity_extra:"用于模板和动作",label:"标签",multiline_secondary:"多行次要信息?",picture:"图片 (将会替代图标)",primary:"首要信息",secondary:"次要信息"},title:{subtitle:"子标题",subtitle_tap_action:"子标题点击动作",title:"标题",title_tap_action:"标题点击动作"},update:{show_buttons_control:"控制按钮?"},vacuum:{commands:"命令",commands_list:{on_off:"开/关"}},weather:{show_conditions:"条件?",show_temperature:"温度?"}},chip:{"chip-picker":{add:"添加 chip",chips:"小卡片",clear:"清除",edit:"编辑",select:"选择 chip",types:{action:"动作","alarm-control-panel":"警戒控制台",back:"返回",conditional:"条件显示",entity:"实体",light:"灯光",menu:"菜单",quickbar:"快捷栏",spacer:"占位符",template:"模板",weather:"天气"}},conditional:{chip:"小卡片"},sub_element_editor:{title:"Chip 编辑"}},form:{alignment_picker:{values:{center:"居中对齐",default:"默认",end:"右对齐",justify:"两端对齐",start:"左对齐"}},color_picker:{values:{default:"默认颜色"}},icon_type_picker:{values:{default:"默认类型","entity-picture":"实体图片",icon:"图标",none:"无"}},info_picker:{values:{default:"默认信息","last-changed":"变更时间","last-updated":"更新时间",name:"名称",none:"无",state:"状态"}},layout_picker:{values:{default:"默认布局",horizontal:"水平布局",vertical:"垂直布局"}}}},ud={card:ld,editor:cd},hd={not_found:"未找到實體"},dd={card:{chips:{alignment:"對齊"},climate:{hvac_modes:"空調模式",show_temperature_control:"溫度控制?"},cover:{show_buttons_control:"按鈕控制?",show_position_control:"位置控制?",show_tilt_position_control:"角度控制?"},fan:{show_oscillate_control:"擺頭控制?",show_percentage_control:"百分比控制?"},generic:{collapsible_controls:"關閉時隱藏控制項",color:"顏色",content_info:"內容",fill_container:"填滿容器",icon_animation:"啟動時使用動態圖示?",icon_color:"圖示顏色",icon_type:"圖示樣式",layout:"佈局",primary_info:"主要訊息",secondary_info:"次要訊息",use_entity_picture:"使用實體圖片?"},humidifier:{show_target_humidity_control:"溼度控制?"},light:{incompatible_controls:"不會顯示裝置不支援的控制。",show_brightness_control:"亮度控制?",show_color_control:"色彩控制?",show_color_temp_control:"色溫控制?",use_light_color:"使用燈光顏色"},lock:{lock:"上鎖",open:"打開",unlock:"解鎖"},"media-player":{media_controls:"媒體控制",media_controls_list:{next:"下一首",on_off:"開啟、關閉",play_pause_stop:"播放、暫停、停止",previous:"上一首",repeat:"重複播放",shuffle:"隨機播放"},show_volume_level:"顯示音量大小",use_media_artwork:"使用媒體插圖",use_media_info:"使用媒體資訊",volume_controls:"音量控制",volume_controls_list:{volume_buttons:"音量按鈕",volume_mute:"靜音",volume_set:"音量等級"}},number:{display_mode:"顯示模式",display_mode_list:{buttons:"按鈕",default:"預設 (滑桿)",slider:"滑桿"}},template:{badge_color:"角標顏色",badge_icon:"角標圖示",content:"內容",entity_extra:"用於模板與動作",label:"標籤",multiline_secondary:"多行次要訊息?",picture:"圖片 (將會取代圖示)",primary:"主要訊息",secondary:"次要訊息"},title:{subtitle:"副標題",subtitle_tap_action:"副標題點擊動作",title:"標題",title_tap_action:"標題點擊動作"},update:{show_buttons_control:"按鈕控制?"},vacuum:{commands:"指令",commands_list:{on_off:"開啟、關閉"}},weather:{show_conditions:"狀況?",show_temperature:"溫度?"}},chip:{"chip-picker":{add:"新增小卡片",chips:"小卡片",clear:"清除",edit:"編輯",select:"選擇小卡片",types:{action:"動作","alarm-control-panel":"警報器控制",back:"返回",conditional:"條件",entity:"實體",light:"燈光",menu:"選單",spacer:"佔位符",template:"模板",weather:"天氣"}},conditional:{chip:"小卡片"},sub_element_editor:{title:"小卡片編輯器"}},form:{alignment_picker:{values:{center:"居中對齊",default:"預設對齊",end:"居右對齊",justify:"兩端對齊",start:"居左對齊"}},color_picker:{values:{default:"預設顏色"}},icon_type_picker:{values:{default:"預設樣式","entity-picture":"實體圖片",icon:"圖示",none:"無"}},info_picker:{values:{default:"預設訊息","last-changed":"最近變動時間","last-updated":"最近更新時間",name:"名稱",none:"無",state:"狀態"}},layout_picker:{values:{default:"預設佈局",horizontal:"水平佈局",vertical:"垂直佈局"}}}},pd={card:hd,editor:dd},fd={ar:lu,bg:hu,ca:mu,cs:yu,da:Cu,de:Su,el:zu,en:Nu,es:Du,fi:Fu,fr:qu,he:Ju,hu:nh,id:ah,it:uh,"ko-KR":ph,nb:gh,nl:kh,pl:xh,"pt-BR":Mh,"pt-PT":jh,ro:Bh,ru:Rh,sl:Object.freeze({__proto__:null,card:Kh,default:qh,editor:Yh}),sk:Gh,sv:Object.freeze({__proto__:null,card:Wh,default:Zh,editor:Xh}),tr:Object.freeze({__proto__:null,card:Jh,default:td,editor:Qh}),uk:Object.freeze({__proto__:null,card:ed,default:od,editor:nd}),vi:Object.freeze({__proto__:null,card:id,default:sd,editor:rd,migration:ad}),"zh-Hans":Object.freeze({__proto__:null,card:ld,default:ud,editor:cd}),"zh-Hant":Object.freeze({__proto__:null,card:hd,default:pd,editor:dd})};function md(t,e){try{return t.split(".").reduce((function(t,e){return t[e]}),fd[e])}catch(t){return}}function vd(t){return function(e){var n,o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},i=null!==(n=null==t?void 0:t.locale.language)&&void 0!==n?n:"en",r=md(e,i);if(r||(r=md(e,"en")),!r)return e;try{return new iu(r,i).format(o)}catch(t){return console.error('Error formatting message for key "'.concat(e,'" with lang "').concat(i,'":'),t),r}}}var gd=function(t){function e(){var t;return hr(this,e),(t=er(this,e,arguments)).picture_url="",t}return or(e,za),pr(e,[{key:"render",value:function(){return ha(M||(M=Li(["\n
=t.length)return!1;var e=t[Dd];if(Bd.test(e))return!0;if("-"===e){if(t.length-Dd<2)return!1;var n=t[Dd+1];return!("-"!==n&&!Bd.test(n))}return!1}var Vd={deg:1,rad:180/Math.PI,grad:.9,turn:360};function Fd(t){var e="";if("-"!==t[Dd]&&"+"!==t[Dd]||(e+=t[Dd++]),e+=$d(t),"."===t[Dd]&&/\d/.test(t[Dd+1])&&(e+=t[Dd++]+$d(t)),"e"!==t[Dd]&&"E"!==t[Dd]||("-"!==t[Dd+1]&&"+"!==t[Dd+1]||!/\d/.test(t[Dd+2])?/\d/.test(t[Dd+1])&&(e+=t[Dd++]+$d(t)):e+=t[Dd++]+t[Dd++]+$d(t)),Ud(t)){var n=Gd(t);return"deg"===n||"rad"===n||"turn"===n||"grad"===n?{type:Hd.Hue,value:e*Vd[n]}:void 0}return"%"===t[Dd]?(Dd++,{type:Hd.Percentage,value:+e}):{type:Hd.Number,value:+e}}function $d(t){for(var e="";/\d/.test(t[Dd]);)e+=t[Dd++];return e}function Gd(t){for(var e="";Dd4)){if(4===o.length){if(o[3].type!==Hd.Alpha)return;o[3]=o[3].value}return 3===o.length&&o.push({type:Hd.None,value:void 0}),o.every((function(t){return t.type!==Hd.Alpha}))?o:void 0}}var Wd=function(t){if("string"==typeof t){for(var e=function(){var t,e=(arguments.length>0&&void 0!==arguments[0]?arguments[0]:"").trim(),n=[];for(Dd=0;Dd=1?e.length-1:Math.max(Math.floor(n),0),i=e[o];return void 0===i?void 0:Xd(i[0],i[1],n-o)}}),Jd=function(t){var e=!1,n=t.map((function(t){return void 0!==t?(e=!0,t):1}));return e?n:t},Qd={mode:"rgb",channels:["r","g","b","alpha"],parse:[function(t,e){if(e&&("rgb"===e[0]||"rgba"===e[0])){var n={mode:"rgb"},o=qi(e,5),i=o[1],r=o[2],a=o[3],s=o[4];if(i.type!==Hd.Hue&&r.type!==Hd.Hue&&a.type!==Hd.Hue)return i.type!==Hd.None&&(n.r=i.type===Hd.Number?i.value/255:i.value/100),r.type!==Hd.None&&(n.g=r.type===Hd.Number?r.value/255:r.value/100),a.type!==Hd.None&&(n.b=a.type===Hd.Number?a.value/255:a.value/100),s.type!==Hd.None&&(n.alpha=Math.min(1,Math.max(0,s.type===Hd.Number?s.value:s.value/100))),n}},function(t){var e;return(e=t.match(bd))?_d(parseInt(e[1],16),e[1].length):void 0},function(t){var e,n={mode:"rgb"};if(e=t.match(Ad))void 0!==e[1]&&(n.r=e[1]/255),void 0!==e[2]&&(n.g=e[2]/255),void 0!==e[3]&&(n.b=e[3]/255);else{if(!(e=t.match(Sd)))return;void 0!==e[1]&&(n.r=e[1]/100),void 0!==e[2]&&(n.g=e[2]/100),void 0!==e[3]&&(n.b=e[3]/100)}return void 0!==e[4]?n.alpha=Math.max(0,Math.min(1,e[4]/100)):void 0!==e[5]&&(n.alpha=Math.max(0,Math.min(1,+e[5]))),n},function(t){return _d(yd[t.toLowerCase()],6)},function(t){return"transparent"===t?{mode:"rgb",r:0,g:0,b:0,alpha:0}:void 0},"srgb"],serialize:"srgb",interpolate:{r:Zd,g:Zd,b:Zd,alpha:{use:Zd,fixup:Jd}},gamut:!0,white:{r:1,g:1,b:1},black:{r:0,g:0,b:0}},tp=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0;return Math.pow(Math.abs(t),563/256)*Math.sign(t)},ep=function(t){var e=tp(t.r),n=tp(t.g),o=tp(t.b),i={mode:"xyz65",x:.5766690429101305*e+.1855582379065463*n+.1882286462349947*o,y:.297344975250536*e+.6273635662554661*n+.0752914584939979*o,z:.0270313613864123*e+.0706888525358272*n+.9913375368376386*o};return void 0!==t.alpha&&(i.alpha=t.alpha),i},np=function(t){return Math.pow(Math.abs(t),256/563)*Math.sign(t)},op=function(t){var e=t.x,n=t.y,o=t.z,i=t.alpha;void 0===e&&(e=0),void 0===n&&(n=0),void 0===o&&(o=0);var r={mode:"a98",r:np(2.0415879038107465*e-.5650069742788597*n-.3447313507783297*o),g:np(-.9692436362808798*e+1.8759675015077206*n+.0415550574071756*o),b:np(.0134442806320312*e-.1183623922310184*n+1.0151749943912058*o)};return void 0!==i&&(r.alpha=i),r},ip=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,e=Math.abs(t);return e<=.04045?t/12.92:(Math.sign(t)||1)*Math.pow((e+.055)/1.055,2.4)},rp=function(t){var e=t.r,n=t.g,o=t.b,i=t.alpha,r={mode:"lrgb",r:ip(e),g:ip(n),b:ip(o)};return void 0!==i&&(r.alpha=i),r},ap=function(t){var e=rp(t),n=e.r,o=e.g,i=e.b,r=e.alpha,a={mode:"xyz65",x:.4123907992659593*n+.357584339383878*o+.1804807884018343*i,y:.2126390058715102*n+.715168678767756*o+.0721923153607337*i,z:.0193308187155918*n+.119194779794626*o+.9505321522496607*i};return void 0!==r&&(a.alpha=r),a},sp=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,e=Math.abs(t);return e>.0031308?(Math.sign(t)||1)*(1.055*Math.pow(e,1/2.4)-.055):12.92*t},lp=function(t){var e=t.r,n=t.g,o=t.b,i=t.alpha,r={mode:arguments.length>1&&void 0!==arguments[1]?arguments[1]:"rgb",r:sp(e),g:sp(n),b:sp(o)};return void 0!==i&&(r.alpha=i),r},cp=function(t){var e=t.x,n=t.y,o=t.z,i=t.alpha;void 0===e&&(e=0),void 0===n&&(n=0),void 0===o&&(o=0);var r=lp({r:3.2409699419045226*e-1.537383177570094*n-.4986107602930034*o,g:-.9692436362808796*e+1.8759675015077204*n+.0415550574071756*o,b:.0556300796969936*e-.2039769588889765*n+1.0569715142428784*o});return void 0!==i&&(r.alpha=i),r},up=Vi(Vi({},Qd),{},{mode:"a98",parse:["a98-rgb"],serialize:"a98-rgb",fromMode:{rgb:function(t){return op(ap(t))},xyz65:op},toMode:{rgb:function(t){return cp(ep(t))},xyz65:ep}}),hp=function(t){return(t%=360)<0?t+360:t},dp=function(t){return function(t,e){return t.map((function(n,o,i){if(void 0===n)return n;var r=hp(n);return 0===o||void 0===t[o-1]?r:e(r-hp(i[o-1]))})).reduce((function(t,e){return t.length&&void 0!==e&&void 0!==t[t.length-1]?(t.push(e+t[t.length-1]),t):(t.push(e),t)}),[])}(t,(function(t){return Math.abs(t)<=180?t:t-360*Math.sign(t)}))},pp=[-.14861,1.78277,-.29227,-.90649,1.97294,0],fp=Math.PI/180,mp=180/Math.PI,vp=pp[3]*pp[4],gp=pp[1]*pp[4],_p=pp[1]*pp[2]-pp[0]*pp[3],yp=function(t,e){if(void 0===t.h||void 0===e.h||!t.s||!e.s)return 0;var n=hp(t.h),o=hp(e.h),i=Math.sin((o-n+360)/2*Math.PI/180);return 2*Math.sqrt(t.s*e.s)*i},bp=function(t,e){if(void 0===t.h||void 0===e.h||!t.c||!e.c)return 0;var n=hp(t.h),o=hp(e.h),i=Math.sin((o-n+360)/2*Math.PI/180);return 2*Math.sqrt(t.c*e.c)*i},kp=function(t){var e=t.reduce((function(t,e){if(void 0!==e){var n=e*Math.PI/180;t.sin+=Math.sin(n),t.cos+=Math.cos(n)}return t}),{sin:0,cos:0}),n=180*Math.atan2(e.sin,e.cos)/Math.PI;return n<0?360+n:n},wp={mode:"cubehelix",channels:["h","s","l","alpha"],parse:["--cubehelix"],serialize:"--cubehelix",ranges:{h:[0,360],s:[0,4.614],l:[0,1]},fromMode:{rgb:function(t){var e=t.r,n=t.g,o=t.b,i=t.alpha;void 0===e&&(e=0),void 0===n&&(n=0),void 0===o&&(o=0);var r=(_p*o+e*vp-n*gp)/(_p+vp-gp),a=o-r,s=(pp[4]*(n-r)-pp[2]*a)/pp[3],l={mode:"cubehelix",l:r,s:0===r||1===r?void 0:Math.sqrt(a*a+s*s)/(pp[4]*r*(1-r))};return l.s&&(l.h=Math.atan2(s,a)*mp-120),void 0!==i&&(l.alpha=i),l}},toMode:{rgb:function(t){var e=t.h,n=t.s,o=t.l,i=t.alpha,r={mode:"rgb"};e=(void 0===e?0:e+120)*fp,void 0===o&&(o=0);var a=void 0===n?0:n*o*(1-o),s=Math.cos(e),l=Math.sin(e);return r.r=o+a*(pp[0]*s+pp[1]*l),r.g=o+a*(pp[2]*s+pp[3]*l),r.b=o+a*(pp[4]*s+pp[5]*l),void 0!==i&&(r.alpha=i),r}},interpolate:{h:{use:Zd,fixup:dp},s:Zd,l:Zd,alpha:{use:Zd,fixup:Jd}},difference:{h:yp},average:{h:kp}},Cp=function(t){var e=t.l,n=t.a,o=t.b,i=t.alpha,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"lch";void 0===n&&(n=0),void 0===o&&(o=0);var a=Math.sqrt(n*n+o*o),s={mode:r,l:e,c:a};return a&&(s.h=hp(180*Math.atan2(o,n)/Math.PI)),void 0!==i&&(s.alpha=i),s},Ep=function(t){var e=t.l,n=t.c,o=t.h,i=t.alpha;void 0===o&&(o=0);var r={mode:arguments.length>1&&void 0!==arguments[1]?arguments[1]:"lab",l:e,a:n?n*Math.cos(o/180*Math.PI):0,b:n?n*Math.sin(o/180*Math.PI):0};return void 0!==i&&(r.alpha=i),r},xp=Math.pow(29,3)/Math.pow(3,3),Ap=Math.pow(6,3)/Math.pow(29,3),Sp=.3457/.3585,Tp=1,Mp=.2958/.3585,zp=.3127/.329,Op=1,Ip=.3583/.329,jp=function(t){return Math.pow(t,3)>Ap?Math.pow(t,3):(116*t-16)/xp},Pp=function(t){var e=t.l,n=t.a,o=t.b,i=t.alpha;void 0===e&&(e=0),void 0===n&&(n=0),void 0===o&&(o=0);var r=(e+16)/116,a=r-o/200,s={mode:"xyz65",x:jp(n/500+r)*zp,y:jp(r)*Op,z:jp(a)*Ip};return void 0!==i&&(s.alpha=i),s},Np=function(t){return cp(Pp(t))},Bp=function(t){return t>Ap?Math.cbrt(t):(xp*t+16)/116},Lp=function(t){var e=t.x,n=t.y,o=t.z,i=t.alpha;void 0===e&&(e=0),void 0===n&&(n=0),void 0===o&&(o=0);var r=Bp(e/zp),a=Bp(n/Op),s={mode:"lab65",l:116*a-16,a:500*(r-a),b:200*(a-Bp(o/Ip))};return void 0!==i&&(s.alpha=i),s},Hp=function(t){var e=Lp(ap(t));return t.r===t.b&&t.b===t.g&&(e.a=e.b=0),e},Dp=26/180*Math.PI,Rp=Math.cos(Dp),Up=Math.sin(Dp),Vp=100/Math.log(1.39),Fp=function(t){var e=t.l,n=t.c,o=t.h,i=t.alpha;void 0===e&&(e=0),void 0===n&&(n=0),void 0===o&&(o=0);var r={mode:"lab65",l:(Math.exp(1*e/Vp)-1)/.0039},a=(Math.exp(.0435*n*1*1)-1)/.075,s=a*Math.cos(o/180*Math.PI-Dp),l=a*Math.sin(o/180*Math.PI-Dp);return r.a=s*Rp-l/.83*Up,r.b=s*Up+l/.83*Rp,void 0!==i&&(r.alpha=i),r},$p=function(t){var e=t.l,n=t.a,o=t.b,i=t.alpha;void 0===e&&(e=0),void 0===n&&(n=0),void 0===o&&(o=0);var r=n*Rp+o*Up,a=.83*(o*Rp-n*Up),s=Math.sqrt(r*r+a*a),l={mode:"dlch",l:Vp/1*Math.log(1+.0039*e),c:Math.log(1+.075*s)/.0435};return l.c&&(l.h=hp((Math.atan2(a,r)+Dp)/Math.PI*180)),void 0!==i&&(l.alpha=i),l},Gp=function(t){return Fp(Cp(t,"dlch"))},Kp=function(t){return Ep($p(t),"dlab")},Yp={mode:"dlab",parse:["--din99o-lab"],serialize:"--din99o-lab",toMode:{lab65:Gp,rgb:function(t){return Np(Gp(t))}},fromMode:{lab65:Kp,rgb:function(t){return Kp(Hp(t))}},channels:["l","a","b","alpha"],ranges:{l:[0,100],a:[-40.09,45.501],b:[-40.469,44.344]},interpolate:{l:Zd,a:Zd,b:Zd,alpha:{use:Zd,fixup:Jd}}},qp={mode:"dlch",parse:["--din99o-lch"],serialize:"--din99o-lch",toMode:{lab65:Fp,dlab:function(t){return Ep(t,"dlab")},rgb:function(t){return Np(Fp(t))}},fromMode:{lab65:$p,dlab:function(t){return Cp(t,"dlch")},rgb:function(t){return $p(Hp(t))}},channels:["l","c","h","alpha"],ranges:{l:[0,100],c:[0,51.484],h:[0,360]},interpolate:{l:Zd,c:Zd,h:{use:Zd,fixup:dp},alpha:{use:Zd,fixup:Jd}},difference:{h:bp},average:{h:kp}};var Wp={mode:"hsi",toMode:{rgb:function(t){var e=t.h,n=t.s,o=t.i,i=t.alpha;e=hp(void 0!==e?e:0),void 0===n&&(n=0),void 0===o&&(o=0);var r,a=Math.abs(e/60%2-1);switch(Math.floor(e/60)){case 0:r={r:o*(1+n*(3/(2-a)-1)),g:o*(1+n*(3*(1-a)/(2-a)-1)),b:o*(1-n)};break;case 1:r={r:o*(1+n*(3*(1-a)/(2-a)-1)),g:o*(1+n*(3/(2-a)-1)),b:o*(1-n)};break;case 2:r={r:o*(1-n),g:o*(1+n*(3/(2-a)-1)),b:o*(1+n*(3*(1-a)/(2-a)-1))};break;case 3:r={r:o*(1-n),g:o*(1+n*(3*(1-a)/(2-a)-1)),b:o*(1+n*(3/(2-a)-1))};break;case 4:r={r:o*(1+n*(3*(1-a)/(2-a)-1)),g:o*(1-n),b:o*(1+n*(3/(2-a)-1))};break;case 5:r={r:o*(1+n*(3/(2-a)-1)),g:o*(1-n),b:o*(1+n*(3*(1-a)/(2-a)-1))};break;default:r={r:o*(1-n),g:o*(1-n),b:o*(1-n)}}return r.mode="rgb",void 0!==i&&(r.alpha=i),r}},parse:["--hsi"],serialize:"--hsi",fromMode:{rgb:function(t){var e=t.r,n=t.g,o=t.b,i=t.alpha;void 0===e&&(e=0),void 0===n&&(n=0),void 0===o&&(o=0);var r=Math.max(e,n,o),a=Math.min(e,n,o),s={mode:"hsi",s:e+n+o===0?0:1-3*a/(e+n+o),i:(e+n+o)/3};return r-a!=0&&(s.h=60*(r===e?(n-o)/(r-a)+6*(n1){var i=n+o;n/=i,o/=i}return Jp({h:e,s:1===o?1:1-n/(1-o),v:1-o,alpha:t.alpha})}},fromMode:{rgb:function(t){var e=Qp(t);if(void 0!==e){var n=void 0!==e.s?e.s:0,o=void 0!==e.v?e.v:0,i={mode:"hwb",w:(1-n)*o,b:1-o};return void 0!==e.h&&(i.h=e.h),void 0!==e.alpha&&(i.alpha=e.alpha),i}}},channels:["h","w","b","alpha"],ranges:{h:[0,360]},gamut:"rgb",parse:[function(t,e){if(e&&"hwb"===e[0]){var n={mode:"hwb"},o=qi(e,5),i=o[1],r=o[2],a=o[3],s=o[4];if(i.type!==Hd.None){if(i.type===Hd.Percentage)return;n.h=i.value}if(r.type!==Hd.None){if(r.type===Hd.Hue)return;n.w=r.value/100}if(a.type!==Hd.None){if(a.type===Hd.Hue)return;n.b=a.value/100}return s.type!==Hd.None&&(n.alpha=Math.min(1,Math.max(0,s.type===Hd.Number?s.value:s.value/100))),n}}],serialize:function(t){return"hwb(".concat(void 0!==t.h?t.h:"none"," ").concat(void 0!==t.w?100*t.w+"%":"none"," ").concat(void 0!==t.b?100*t.b+"%":"none").concat(t.alpha<1?" / ".concat(t.alpha):"",")")},interpolate:{h:{use:Zd,fixup:dp},w:Zd,b:Zd,alpha:{use:Zd,fixup:Jd}},difference:{h:function(t,e){if(void 0===t.h||void 0===e.h)return 0;var n=hp(t.h),o=hp(e.h);return Math.abs(o-n)>180?n-(o-360*Math.sign(o-n)):o-n}},average:{h:kp}},nf=.1593017578125,of=78.84375,rf=.8359375,af=18.8515625,sf=18.6875;function lf(t){if(t<0)return 0;var e=Math.pow(t,1/of);return 1e4*Math.pow(Math.max(0,e-rf)/(af-sf*e),1/nf)}function cf(t){if(t<0)return 0;var e=Math.pow(t/1e4,nf);return Math.pow((rf+af*e)/(1+sf*e),of)}var uf=function(t){return Math.max(t/203,0)},hf=function(t){var e=t.i,n=t.t,o=t.p,i=t.alpha;void 0===e&&(e=0),void 0===n&&(n=0),void 0===o&&(o=0);var r=lf(e+.008609037037932761*n+.11102962500302593*o),a=lf(e-.00860903703793275*n-.11102962500302599*o),s=lf(e+.5600313357106791*n-.32062717498731885*o),l={mode:"xyz65",x:uf(2.070152218389422*r-1.3263473389671556*a+.2066510476294051*s),y:uf(.3647385209748074*r+.680566024947227*a-.0453045459220346*s),z:uf(-.049747207535812*r-.0492609666966138*a+1.1880659249923042*s)};return void 0!==i&&(l.alpha=i),l},df=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0;return Math.max(203*t,0)},pf=function(t){var e=t.x,n=t.y,o=t.z,i=t.alpha,r=df(e),a=df(n),s=df(o),l=cf(.3592832590121217*r+.6976051147779502*a-.0358915932320289*s),c=cf(-.1920808463704995*r+1.1004767970374323*a+.0753748658519118*s),u=cf(.0070797844607477*r+.0748396662186366*a+.8433265453898765*s),h={mode:"itp",i:.5*l+.5*c,t:1.61376953125*l-3.323486328125*c+1.709716796875*u,p:4.378173828125*l-4.24560546875*c-.132568359375*u};return void 0!==i&&(h.alpha=i),h},ff={mode:"itp",channels:["i","t","p","alpha"],parse:["--ictcp"],serialize:"--ictcp",toMode:{xyz65:hf,rgb:function(t){return cp(hf(t))}},fromMode:{xyz65:pf,rgb:function(t){return pf(ap(t))}},ranges:{i:[0,.581],t:[-.369,.272],p:[-.164,.331]},interpolate:{i:Zd,t:Zd,p:Zd,alpha:{use:Zd,fixup:Jd}}},mf=function(t){if(t<0)return 0;var e=Math.pow(t/1e4,nf);return Math.pow((rf+af*e)/(1+sf*e),134.03437499999998)},vf=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0;return Math.max(203*t,0)},gf=function(t){var e=t.x,n=t.y,o=t.z,i=t.alpha;e=vf(e),n=vf(n);var r=1.15*e-.15*(o=vf(o)),a=.66*n+.34*e,s=mf(.41478972*r+.579999*a+.014648*o),l=mf(-.20151*r+1.120649*a+.0531008*o),c=mf(-.0166008*r+.2648*a+.6684799*o),u=(s+l)/2,h={mode:"jab",j:.44*u/(1-.56*u)-16295499532821565e-27,a:3.524*s-4.066708*l+.542708*c,b:.199076*s+1.096799*l-1.295875*c};return void 0!==i&&(h.alpha=i),h},_f=16295499532821565e-27,yf=function(t){if(t<0)return 0;var e=Math.pow(t,.007460772656268216);return 1e4*Math.pow((rf-e)/(sf*e-af),1/nf)},bf=function(t){return t/203},kf=function(t){var e=t.j,n=t.a,o=t.b,i=t.alpha;void 0===e&&(e=0),void 0===n&&(n=0),void 0===o&&(o=0);var r=(e+_f)/(.44+.56*(e+_f)),a=yf(r+.13860504*n+.058047316*o),s=yf(r-.13860504*n-.058047316*o),l=yf(r-.096019242*n-.8118919*o),c={mode:"xyz65",x:bf(1.661373024652174*a-.914523081304348*s+.23136208173913045*l),y:bf(-.3250758611844533*a+1.571847026732543*s-.21825383453227928*l),z:bf(-.090982811*a-.31272829*s+1.5227666*l)};return void 0!==i&&(c.alpha=i),c},wf=function(t){var e=gf(ap(t));return t.r===t.b&&t.b===t.g&&(e.a=e.b=0),e},Cf=function(t){return cp(kf(t))},Ef={mode:"jab",channels:["j","a","b","alpha"],parse:["--jzazbz"],serialize:"--jzazbz",fromMode:{rgb:wf,xyz65:gf},toMode:{rgb:Cf,xyz65:kf},ranges:{j:[0,.222],a:[-.109,.129],b:[-.185,.134]},interpolate:{j:Zd,a:Zd,b:Zd,alpha:{use:Zd,fixup:Jd}}},xf=function(t){var e=t.j,n=t.a,o=t.b,i=t.alpha;void 0===n&&(n=0),void 0===o&&(o=0);var r=Math.sqrt(n*n+o*o),a={mode:"jch",j:e,c:r};return r&&(a.h=hp(180*Math.atan2(o,n)/Math.PI)),void 0!==i&&(a.alpha=i),a},Af=function(t){var e=t.j,n=t.c,o=t.h,i=t.alpha;void 0===o&&(o=0);var r={mode:"jab",j:e,a:n?n*Math.cos(o/180*Math.PI):0,b:n?n*Math.sin(o/180*Math.PI):0};return void 0!==i&&(r.alpha=i),r},Sf={mode:"jch",parse:["--jzczhz"],serialize:"--jzczhz",toMode:{jab:Af,rgb:function(t){return Cf(Af(t))}},fromMode:{rgb:function(t){return xf(wf(t))},jab:xf},channels:["j","c","h","alpha"],ranges:{j:[0,.221],c:[0,.19],h:[0,360]},interpolate:{h:{use:Zd,fixup:dp},c:Zd,j:Zd,alpha:{use:Zd,fixup:Jd}},difference:{h:bp},average:{h:kp}},Tf=Math.pow(29,3)/Math.pow(3,3),Mf=Math.pow(6,3)/Math.pow(29,3),zf=function(t){return Math.pow(t,3)>Mf?Math.pow(t,3):(116*t-16)/Tf},Of=function(t){var e=t.l,n=t.a,o=t.b,i=t.alpha;void 0===e&&(e=0),void 0===n&&(n=0),void 0===o&&(o=0);var r=(e+16)/116,a=r-o/200,s={mode:"xyz50",x:zf(n/500+r)*Sp,y:zf(r)*Tp,z:zf(a)*Mp};return void 0!==i&&(s.alpha=i),s},If=function(t){var e=t.x,n=t.y,o=t.z,i=t.alpha;void 0===e&&(e=0),void 0===n&&(n=0),void 0===o&&(o=0);var r=lp({r:3.1341359569958707*e-1.6173863321612538*n-.4906619460083532*o,g:-.978795502912089*e+1.916254567259524*n+.03344273116131949*o,b:.07195537988411677*e-.2289768264158322*n+1.405386058324125*o});return void 0!==i&&(r.alpha=i),r},jf=function(t){return If(Of(t))},Pf=function(t){var e=rp(t),n=e.r,o=e.g,i=e.b,r=e.alpha,a={mode:"xyz50",x:.436065742824811*n+.3851514688337912*o+.14307845442264197*i,y:.22249319175623702*n+.7168870538238823*o+.06061979053616537*i,z:.013923904500943465*n+.09708128566574634*o+.7140993584005155*i};return void 0!==r&&(a.alpha=r),a},Nf=function(t){return t>Mf?Math.cbrt(t):(Tf*t+16)/116},Bf=function(t){var e=t.x,n=t.y,o=t.z,i=t.alpha;void 0===e&&(e=0),void 0===n&&(n=0),void 0===o&&(o=0);var r=Nf(e/Sp),a=Nf(n/Tp),s={mode:"lab",l:116*a-16,a:500*(r-a),b:200*(a-Nf(o/Mp))};return void 0!==i&&(s.alpha=i),s},Lf=function(t){var e=Bf(Pf(t));return t.r===t.b&&t.b===t.g&&(e.a=e.b=0),e};var Hf={mode:"lab",toMode:{xyz50:Of,rgb:jf},fromMode:{xyz50:Bf,rgb:Lf},channels:["l","a","b","alpha"],ranges:{l:[0,100],a:[-125,125],b:[-125,125]},parse:[function(t,e){if(e&&"lab"===e[0]){var n={mode:"lab"},o=qi(e,5),i=o[1],r=o[2],a=o[3],s=o[4];if(i.type!==Hd.Hue&&r.type!==Hd.Hue&&a.type!==Hd.Hue)return i.type!==Hd.None&&(n.l=Math.min(Math.max(0,i.value),100)),r.type!==Hd.None&&(n.a=r.type===Hd.Number?r.value:125*r.value/100),a.type!==Hd.None&&(n.b=a.type===Hd.Number?a.value:125*a.value/100),s.type!==Hd.None&&(n.alpha=Math.min(1,Math.max(0,s.type===Hd.Number?s.value:s.value/100))),n}}],serialize:function(t){return"lab(".concat(void 0!==t.l?t.l:"none"," ").concat(void 0!==t.a?t.a:"none"," ").concat(void 0!==t.b?t.b:"none").concat(t.alpha<1?" / ".concat(t.alpha):"",")")},interpolate:{l:Zd,a:Zd,b:Zd,alpha:{use:Zd,fixup:Jd}}},Df=Vi(Vi({},Hf),{},{mode:"lab65",parse:["--lab-d65"],serialize:"--lab-d65",toMode:{xyz65:Pp,rgb:Np},fromMode:{xyz65:Lp,rgb:Hp},ranges:{l:[0,100],a:[-125,125],b:[-125,125]}});var Rf={mode:"lch",toMode:{lab:Ep,rgb:function(t){return jf(Ep(t))}},fromMode:{rgb:function(t){return Cp(Lf(t))},lab:Cp},channels:["l","c","h","alpha"],ranges:{l:[0,100],c:[0,150],h:[0,360]},parse:[function(t,e){if(e&&"lch"===e[0]){var n={mode:"lch"},o=qi(e,5),i=o[1],r=o[2],a=o[3],s=o[4];if(i.type!==Hd.None){if(i.type===Hd.Hue)return;n.l=Math.min(Math.max(0,i.value),100)}if(r.type!==Hd.None&&(n.c=Math.max(0,r.type===Hd.Number?r.value:150*r.value/100)),a.type!==Hd.None){if(a.type===Hd.Percentage)return;n.h=a.value}return s.type!==Hd.None&&(n.alpha=Math.min(1,Math.max(0,s.type===Hd.Number?s.value:s.value/100))),n}}],serialize:function(t){return"lch(".concat(void 0!==t.l?t.l:"none"," ").concat(void 0!==t.c?t.c:"none"," ").concat(void 0!==t.h?t.h:"none").concat(t.alpha<1?" / ".concat(t.alpha):"",")")},interpolate:{h:{use:Zd,fixup:dp},c:Zd,l:Zd,alpha:{use:Zd,fixup:Jd}},difference:{h:bp},average:{h:kp}},Uf=Vi(Vi({},Rf),{},{mode:"lch65",parse:["--lch-d65"],serialize:"--lch-d65",toMode:{lab65:function(t){return Ep(t,"lab65")},rgb:function(t){return Np(Ep(t,"lab65"))}},fromMode:{rgb:function(t){return Cp(Hp(t),"lch65")},lab65:function(t){return Cp(t,"lch65")}},ranges:{l:[0,100],c:[0,150],h:[0,360]}}),Vf=function(t){var e=t.l,n=t.u,o=t.v,i=t.alpha;void 0===n&&(n=0),void 0===o&&(o=0);var r=Math.sqrt(n*n+o*o),a={mode:"lchuv",l:e,c:r};return r&&(a.h=hp(180*Math.atan2(o,n)/Math.PI)),void 0!==i&&(a.alpha=i),a},Ff=function(t){var e=t.l,n=t.c,o=t.h,i=t.alpha;void 0===o&&(o=0);var r={mode:"luv",l:e,u:n?n*Math.cos(o/180*Math.PI):0,v:n?n*Math.sin(o/180*Math.PI):0};return void 0!==i&&(r.alpha=i),r},$f=function(t,e,n){return 4*t/(t+15*e+3*n)},Gf=function(t,e,n){return 9*e/(t+15*e+3*n)},Kf=$f(Sp,Tp,Mp),Yf=Gf(Sp,Tp,Mp),qf=function(t){var e=t.x,n=t.y,o=t.z,i=t.alpha;void 0===e&&(e=0),void 0===n&&(n=0),void 0===o&&(o=0);var r,a=(r=n/Tp)<=Mf?Tf*r:116*Math.cbrt(r)-16,s=$f(e,n,o),l=Gf(e,n,o);isFinite(s)&&isFinite(l)?(s=13*a*(s-Kf),l=13*a*(l-Yf)):a=s=l=0;var c={mode:"luv",l:a,u:s,v:l};return void 0!==i&&(c.alpha=i),c},Wf=function(t,e,n){return 4*t/(t+15*e+3*n)}(Sp,Tp,Mp),Xf=function(t,e,n){return 9*e/(t+15*e+3*n)}(Sp,Tp,Mp),Zf=function(t){var e=t.l,n=t.u,o=t.v,i=t.alpha;if(void 0===e&&(e=0),0===e)return{mode:"xyz50",x:0,y:0,z:0};void 0===n&&(n=0),void 0===o&&(o=0);var r=n/(13*e)+Wf,a=o/(13*e)+Xf,s=Tp*(e<=8?e/Tf:Math.pow((e+16)/116,3)),l={mode:"xyz50",x:s*(9*r)/(4*a),y:s,z:s*(12-3*r-20*a)/(4*a)};return void 0!==i&&(l.alpha=i),l},Jf={mode:"lchuv",toMode:{luv:Ff,rgb:function(t){return If(Zf(Ff(t)))}},fromMode:{rgb:function(t){return Vf(qf(Pf(t)))},luv:Vf},channels:["l","c","h","alpha"],parse:["--lchuv"],serialize:"--lchuv",ranges:{l:[0,100],c:[0,176.956],h:[0,360]},interpolate:{h:{use:Zd,fixup:dp},c:Zd,l:Zd,alpha:{use:Zd,fixup:Jd}},difference:{h:bp},average:{h:kp}},Qf=Vi(Vi({},Qd),{},{mode:"lrgb",toMode:{rgb:lp},fromMode:{rgb:rp},parse:["srgb-linear"],serialize:"srgb-linear"}),tm={mode:"luv",toMode:{xyz50:Zf,rgb:function(t){return If(Zf(t))}},fromMode:{xyz50:qf,rgb:function(t){return qf(Pf(t))}},channels:["l","u","v","alpha"],parse:["--luv"],serialize:"--luv",ranges:{l:[0,100],u:[-84.936,175.042],v:[-125.882,87.243]},interpolate:{l:Zd,u:Zd,v:Zd,alpha:{use:Zd,fixup:Jd}}},em=function(t){var e=t.r,n=t.g,o=t.b,i=t.alpha;void 0===e&&(e=0),void 0===n&&(n=0),void 0===o&&(o=0);var r=Math.cbrt(.412221469470763*e+.5363325372617348*n+.0514459932675022*o),a=Math.cbrt(.2119034958178252*e+.6806995506452344*n+.1073969535369406*o),s=Math.cbrt(.0883024591900564*e+.2817188391361215*n+.6299787016738222*o),l={mode:"oklab",l:.210454268309314*r+.7936177747023054*a-.0040720430116193*s,a:1.9779985324311684*r-2.42859224204858*a+.450593709617411*s,b:.0259040424655478*r+.7827717124575296*a-.8086757549230774*s};return void 0!==i&&(l.alpha=i),l},nm=function(t){var e=em(rp(t));return t.r===t.b&&t.b===t.g&&(e.a=e.b=0),e},om=function(t){var e=t.l,n=t.a,o=t.b,i=t.alpha;void 0===e&&(e=0),void 0===n&&(n=0),void 0===o&&(o=0);var r=Math.pow(e+.3963377773761749*n+.2158037573099136*o,3),a=Math.pow(e-.1055613458156586*n-.0638541728258133*o,3),s=Math.pow(e-.0894841775298119*n-1.2914855480194092*o,3),l={mode:"lrgb",r:4.076741636075957*r-3.3077115392580616*a+.2309699031821044*s,g:-1.2684379732850317*r+2.6097573492876887*a-.3413193760026573*s,b:-.0041960761386756*r-.7034186179359362*a+1.7076146940746117*s};return void 0!==i&&(l.alpha=i),l},im=function(t){return lp(om(t))};function rm(t){var e=.206,n=1.206/1.03;return.5*(n*t-e+Math.sqrt((n*t-e)*(n*t-e)+.12*n*t))}function am(t){return(t*t+.206*t)/(1.170873786407767*(t+.03))}function sm(t,e){var n=function(t,e){var n,o,i,r,a,s,l,c;-1.88170328*t-.80936493*e>1?(n=1.19086277,o=1.76576728,i=.59662641,r=.75515197,a=.56771245,s=4.0767416621,l=-3.3077115913,c=.2309699292):1.81444104*t-1.19445276*e>1?(n=.73956515,o=-.45954404,i=.08285427,r=.1254107,a=.14503204,s=-1.2684380046,l=2.6097574011,c=-.3413193965):(n=1.35733652,o=-.00915799,i=-1.1513021,r=-.50559606,a=.00692167,s=-.0041960863,l=-.7034186147,c=1.707614701);var u=n+o*t+i*e+r*t*t+a*t*e,h=.3963377774*t+.2158037573*e,d=-.1055613458*t-.0638541728*e,p=-.0894841775*t-1.291485548*e,f=1+u*h,m=1+u*d,v=1+u*p,g=s*(f*f*f)+l*(m*m*m)+c*(v*v*v),_=s*(3*h*f*f)+l*(3*d*m*m)+c*(3*p*v*v);return u-g*_/(_*_-.5*g*(s*(6*h*h*f)+l*(6*d*d*m)+c*(6*p*p*v)))}(t,e),o=om({l:1,a:n*t,b:n*e}),i=Math.cbrt(1/Math.max(o.r,o.g,o.b));return[i,i*n]}function lm(t,e){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;n||(n=sm(t,e));var o=n[0],i=n[1];return[i/o,i/(1-o)]}function cm(t,e,n){var o=sm(e,n),i=function(t,e,n,o,i){var r,a=arguments.length>5&&void 0!==arguments[5]?arguments[5]:null;if(a||(a=sm(t,e)),(n-i)*a[1]-(a[0]-i)*o<=0)r=a[1]*i/(o*a[0]+a[1]*(i-n));else{var s=n-i,l=.3963377774*t+.2158037573*e,c=-.1055613458*t-.0638541728*e,u=-.0894841775*t-1.291485548*e,h=s+o*l,d=s+o*c,p=s+o*u,f=i*(1-(r=a[1]*(i-1)/(o*(a[0]-1)+a[1]*(i-n))))+r*n,m=r*o,v=f+m*l,g=f+m*c,_=f+m*u,y=v*v*v,b=g*g*g,k=_*_*_,w=3*h*v*v,C=3*d*g*g,E=3*p*_*_,x=6*h*h*v,A=6*d*d*g,S=6*p*p*_,T=4.0767416621*y-3.3077115913*b+.2309699292*k-1,M=4.0767416621*w-3.3077115913*C+.2309699292*E,z=M/(M*M-.5*T*(4.0767416621*x-3.3077115913*A+.2309699292*S)),O=-T*z,I=-1.2684380046*y+2.6097574011*b-.3413193965*k-1,j=-1.2684380046*w+2.6097574011*C-.3413193965*E,P=j/(j*j-.5*I*(-1.2684380046*x+2.6097574011*A-.3413193965*S)),N=-I*P,B=-.0041960863*y-.7034186147*b+1.707614701*k-1,L=-.0041960863*w-.7034186147*C+1.707614701*E,H=L/(L*L-.5*B*(-.0041960863*x-.7034186147*A+1.707614701*S)),D=-B*H;O=z>=0?O:1e6,N=P>=0?N:1e6,D=H>=0?D:1e6,r+=Math.min(O,Math.min(N,D))}return r}(e,n,t,1,t,o),r=lm(e,n,o),a=t*(.11516993+1/(7.4477897+4.1590124*n+e*(1.75198401*n-2.19557347+e*(-2.13704948-10.02301043*n+e*(5.38770819*n-4.24894561+4.69891013*e))))),s=(1-t)*(.11239642+1/(1.6132032-.68124379*n+e*(.40370612+.90148123*n+e*(.6122399*n-.27087943+e*(.00299215-.45399568*n-.14661872*e))))),l=.9*(i/Math.min(t*r[0],(1-t)*r[1]))*Math.sqrt(Math.sqrt(1/(1/(a*a*a*a)+1/(s*s*s*s))));return a=.4*t,s=.8*(1-t),[Math.sqrt(1/(1/(a*a)+1/(s*s))),l,i]}function um(t){var e=void 0!==t.l?t.l:0,n=void 0!==t.a?t.a:0,o=void 0!==t.b?t.b:0,i={mode:"okhsl",l:rm(e)};void 0!==t.alpha&&(i.alpha=t.alpha);var r=Math.sqrt(n*n+o*o);if(!r)return i.s=0,i;var a,s=qi(cm(e,n/r,o/r),3),l=s[0],c=s[1],u=s[2];if(r=1/512?Math.sign(t)*Math.pow(e,1/1.8):16*t},wm=function(t){var e=t.x,n=t.y,o=t.z,i=t.alpha;void 0===e&&(e=0),void 0===n&&(n=0),void 0===o&&(o=0);var r={mode:"prophoto",r:km(1.3457868816471585*e-.2555720873797946*n-.0511018649755453*o),g:km(-.5446307051249019*e+1.5082477428451466*n+.0205274474364214*o),b:km(0*e+0*n+1.2119675456389452*o)};return void 0!==i&&(r.alpha=i),r},Cm=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,e=Math.abs(t);return e>=16/512?Math.sign(t)*Math.pow(e,1.8):t/16},Em=function(t){var e=Cm(t.r),n=Cm(t.g),o=Cm(t.b),i={mode:"xyz50",x:.7977666449006423*e+.1351812974005331*n+.0313477341283922*o,y:.2880748288194013*e+.7118352342418731*n+899369387256e-16*o,z:0*e+0*n+.8251046025104602*o};return void 0!==t.alpha&&(i.alpha=t.alpha),i},xm=Vi(Vi({},Qd),{},{mode:"prophoto",parse:["prophoto-rgb"],serialize:"prophoto-rgb",fromMode:{xyz50:wm,rgb:function(t){return wm(Pf(t))}},toMode:{xyz50:Em,rgb:function(t){return If(Em(t))}}}),Am=1.09929682680944,Sm=function(t){var e=Math.abs(t);return e>.018053968510807?(Math.sign(t)||1)*(Am*Math.pow(e,.45)-(Am-1)):4.5*t},Tm=function(t){var e=t.x,n=t.y,o=t.z,i=t.alpha;void 0===e&&(e=0),void 0===n&&(n=0),void 0===o&&(o=0);var r={mode:"rec2020",r:Sm(1.7166511879712683*e-.3556707837763925*n-.2533662813736599*o),g:Sm(-.6666843518324893*e+1.6164812366349395*n+.0157685458139111*o),b:Sm(.0176398574453108*e-.0427706132578085*n+.9421031212354739*o)};return void 0!==i&&(r.alpha=i),r},Mm=1.09929682680944,zm=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,e=Math.abs(t);return e<.08124285829863151?t/4.5:(Math.sign(t)||1)*Math.pow((e+Mm-1)/Mm,1/.45)},Om=function(t){var e=zm(t.r),n=zm(t.g),o=zm(t.b),i={mode:"xyz65",x:.6369580483012911*e+.1446169035862083*n+.1688809751641721*o,y:.262700212011267*e+.6779980715188708*n+.059301716469862*o,z:0*e+.0280726930490874*n+1.0609850577107909*o};return void 0!==t.alpha&&(i.alpha=t.alpha),i},Im=Vi(Vi({},Qd),{},{mode:"rec2020",fromMode:{xyz65:Tm,rgb:function(t){return Tm(ap(t))}},toMode:{xyz65:Om,rgb:function(t){return cp(Om(t))}},parse:["rec2020"],serialize:"rec2020"}),jm=.0037930732552754493,Pm=Math.cbrt(jm),Nm=function(t){return Math.cbrt(t)-Pm},Bm=function(t){return Math.pow(t+Pm,3)},Lm={mode:"xyb",channels:["x","y","b","alpha"],parse:["--xyb"],serialize:"--xyb",toMode:{rgb:function(t){var e=t.x,n=t.y,o=t.b,i=t.alpha;void 0===e&&(e=0),void 0===n&&(n=0),void 0===o&&(o=0);var r=Bm(e+n)-jm,a=Bm(n-e)-jm,s=Bm(o+n)-jm,l=lp({r:11.031566904639861*r-9.866943908131562*a-.16462299650829934*s,g:-3.2541473810744237*r+4.418770377582723*a-.16462299650829934*s,b:-3.6588512867136815*r+2.7129230459360922*a+1.9459282407775895*s});return void 0!==i&&(l.alpha=i),l}},fromMode:{rgb:function(t){var e=rp(t),n=e.r,o=e.g,i=e.b,r=e.alpha,a=Nm(.3*n+.622*o+.078*i+jm),s=Nm(.23*n+.692*o+.078*i+jm),l={mode:"xyb",x:(a-s)/2,y:(a+s)/2,b:Nm(.2434226892454782*n+.2047674442449682*o+.5518098665095535*i+jm)-(a+s)/2};return void 0!==r&&(l.alpha=r),l}},ranges:{x:[-.0154,.0281],y:[0,.8453],b:[-.2778,.388]},interpolate:{x:Zd,y:Zd,b:Zd,alpha:{use:Zd,fixup:Jd}}},Hm={mode:"xyz50",parse:["xyz-d50"],serialize:"xyz-d50",toMode:{rgb:If,lab:Bf},fromMode:{rgb:Pf,lab:Of},channels:["x","y","z","alpha"],ranges:{x:[0,.964],y:[0,.999],z:[0,.825]},interpolate:{x:Zd,y:Zd,z:Zd,alpha:{use:Zd,fixup:Jd}}},Dm={mode:"xyz65",toMode:{rgb:cp,xyz50:function(t){var e=t.x,n=t.y,o=t.z,i=t.alpha;void 0===e&&(e=0),void 0===n&&(n=0),void 0===o&&(o=0);var r={mode:"xyz50",x:1.0479298208405488*e+.0229467933410191*n-.0501922295431356*o,y:.0296278156881593*e+.990434484573249*n-.0170738250293851*o,z:-.0092430581525912*e+.0150551448965779*n+.7518742899580008*o};return void 0!==i&&(r.alpha=i),r}},fromMode:{rgb:ap,xyz50:function(t){var e=t.x,n=t.y,o=t.z,i=t.alpha;void 0===e&&(e=0),void 0===n&&(n=0),void 0===o&&(o=0);var r={mode:"xyz65",x:.9554734527042182*e-.0230985368742614*n+.0632593086610217*o,y:-.0283697069632081*e+1.0099954580058226*n+.021041398966943*o,z:.0123140016883199*e-.0205076964334779*n+1.3303659366080753*o};return void 0!==i&&(r.alpha=i),r}},ranges:{x:[0,.95],y:[0,1],z:[0,1.088]},channels:["x","y","z","alpha"],parse:["xyz","xyz-d65"],serialize:"xyz-d65",interpolate:{x:Zd,y:Zd,z:Zd,alpha:{use:Zd,fixup:Jd}}},Rm={mode:"yiq",toMode:{rgb:function(t){var e=t.y,n=t.i,o=t.q,i=t.alpha;void 0===e&&(e=0),void 0===n&&(n=0),void 0===o&&(o=0);var r={mode:"rgb",r:e+.95608445*n+.6208885*o,g:e-.27137664*n-.6486059*o,b:e-1.10561724*n+1.70250126*o};return void 0!==i&&(r.alpha=i),r}},fromMode:{rgb:function(t){var e=t.r,n=t.g,o=t.b,i=t.alpha;void 0===e&&(e=0),void 0===n&&(n=0),void 0===o&&(o=0);var r={mode:"yiq",y:.29889531*e+.58662247*n+.11448223*o,i:.59597799*e-.2741761*n-.32180189*o,q:.21147017*e-.52261711*n+.31114694*o};return void 0!==i&&(r.alpha=i),r}},channels:["y","i","q","alpha"],parse:["--yiq"],serialize:"--yiq",ranges:{i:[-.595,.595],q:[-.522,.522]},interpolate:{y:Zd,i:Zd,q:Zd,alpha:{use:Zd,fixup:Jd}}};Pd(up),Pd(wp),Pd(Yp),Pd(qp),Pd(Wp),Pd(Zp);var Um=Pd(tf);Pd(ef),Pd(ff),Pd(Ef),Pd(Sf),Pd(Hf),Pd(Df);var Vm=Pd(Rf);Pd(Uf),Pd(Jf),Pd(Qf),Pd(tm),Pd(dm),Pd(mm),Pd(vm),Pd(gm),Pd(bm),Pd(xm),Pd(Im);var Fm=Pd(Qd);Pd(Lm),Pd(Hm),Pd(Dm),Pd(Rm);var $m=["primary","accent","red","pink","purple","deep-purple","indigo","blue","light-blue","cyan","teal","green","light-green","lime","yellow","amber","orange","deep-orange","brown","light-grey","grey","dark-grey","blue-grey","black","white","disabled"];function Gm(t){if("primary"===t||"accent"===t)return"var(--rgb-".concat(t,"-color)");if($m.includes(t))return"var(--rgb-".concat(t,")");if(t.startsWith("#"))try{var e=Fm(t);if(e){var n=e.r,o=e.g,i=e.b;return"".concat(Math.round(255*n),", ").concat(Math.round(255*o),", ").concat(Math.round(255*i))}return""}catch(t){return""}return t}var Km=Tr(O||(O=Li(["\n --default-red: 244, 67, 54;\n --default-pink: 233, 30, 99;\n --default-purple: 146, 107, 199;\n --default-deep-purple: 110, 65, 171;\n --default-indigo: 63, 81, 181;\n --default-blue: 33, 150, 243;\n --default-light-blue: 3, 169, 244;\n --default-cyan: 0, 188, 212;\n --default-teal: 0, 150, 136;\n --default-green: 76, 175, 80;\n --default-light-green: 139, 195, 74;\n --default-lime: 205, 220, 57;\n --default-yellow: 255, 235, 59;\n --default-amber: 255, 193, 7;\n --default-orange: 255, 152, 0;\n --default-deep-orange: 255, 111, 34;\n --default-brown: 121, 85, 72;\n --default-light-grey: 189, 189, 189;\n --default-grey: 158, 158, 158;\n --default-dark-grey: 96, 96, 96;\n --default-blue-grey: 96, 125, 139;\n --default-black: 0, 0, 0;\n --default-white: 255, 255, 255;\n --default-disabled: 189, 189, 189;\n"]))),Ym=Tr(I||(I=Li(["\n --default-disabled: 111, 111, 111;\n"]))),qm=Tr(j||(j=Li(['\n --spacing: var(--mush-spacing, 10px);\n\n /* Title */\n --title-padding: var(--mush-title-padding, 24px 12px 8px);\n --title-spacing: var(--mush-title-spacing, 8px);\n --title-font-size: var(--mush-title-font-size, 24px);\n --title-font-weight: var(--mush-title-font-weight, normal);\n --title-line-height: var(--mush-title-line-height, 32px);\n --title-color: var(--mush-title-color, var(--primary-text-color));\n --title-letter-spacing: var(--mush-title-letter-spacing, -0.288px);\n --subtitle-font-size: var(--mush-subtitle-font-size, 16px);\n --subtitle-font-weight: var(--mush-subtitle-font-weight, normal);\n --subtitle-line-height: var(--mush-subtitle-line-height, 24px);\n --subtitle-color: var(--mush-subtitle-color, var(--secondary-text-color));\n --subtitle-letter-spacing: var(--mush-subtitle-letter-spacing, 0px);\n\n /* Card */\n --card-primary-font-size: var(--mush-card-primary-font-size, 14px);\n --card-secondary-font-size: var(--mush-card-secondary-font-size, 12px);\n --card-primary-font-weight: var(--mush-card-primary-font-weight, 500);\n --card-secondary-font-weight: var(--mush-card-secondary-font-weight, 400);\n --card-primary-line-height: var(--mush-card-primary-line-height, 20px);\n --card-secondary-line-height: var(--mush-card-secondary-line-height, 16px);\n --card-primary-color: var(\n --mush-card-primary-color,\n var(--primary-text-color)\n );\n --card-secondary-color: var(\n --mush-card-secondary-color,\n var(--primary-text-color)\n );\n --card-primary-letter-spacing: var(--mush-card-primary-letter-spacing, 0.1px);\n --card-secondary-letter-spacing: var(\n --mush-card-secondary-letter-spacing,\n 0.4px\n );\n\n /* Chips */\n --chip-spacing: var(--mush-chip-spacing, 8px);\n --chip-padding: var(--mush-chip-padding, 0 0.25em);\n --chip-height: var(--mush-chip-height, 36px);\n --chip-border-radius: var(--mush-chip-border-radius, 19px);\n --chip-border-width: var(\n --mush-chip-border-width,\n var(--ha-card-border-width, 1px)\n );\n --chip-border-color: var(\n --mush-chip-border-color,\n var(--ha-card-border-color, var(--divider-color))\n );\n --chip-box-shadow: var(\n --mush-chip-box-shadow,\n var(--ha-card-box-shadow, "none")\n );\n --chip-font-size: var(--mush-chip-font-size, 0.3em);\n --chip-font-weight: var(--mush-chip-font-weight, bold);\n --chip-icon-size: var(--mush-chip-icon-size, 0.5em);\n --chip-avatar-padding: var(--mush-chip-avatar-padding, 0.1em);\n --chip-avatar-border-radius: var(--mush-chip-avatar-border-radius, 50%);\n --chip-background: var(\n --mush-chip-background,\n var(--ha-card-background, var(--card-background-color, white))\n );\n /* Controls */\n --control-border-radius: var(--mush-control-border-radius, 12px);\n --control-height: var(--mush-control-height, 42px);\n --control-button-ratio: var(--mush-control-button-ratio, 1);\n --control-icon-size: var(--mush-control-icon-size, 0.5em);\n --control-spacing: var(--mush-control-spacing, 12px);\n\n /* Slider */\n --slider-threshold: var(--mush-slider-threshold);\n\n /* Input Number */\n --input-number-debounce: var(--mush-input-number-debounce);\n\n /* Layout */\n --layout-align: var(--mush-layout-align, center);\n\n /* Badge */\n --badge-size: var(--mush-badge-size, 16px);\n --badge-icon-size: var(--mush-badge-icon-size, 0.75em);\n --badge-border-radius: var(--mush-badge-border-radius, 50%);\n\n /* Icon */\n --icon-border-radius: var(--mush-icon-border-radius, 50%);\n --icon-size: var(--mush-icon-size, 36px);\n --icon-symbol-size: var(--mush-icon-symbol-size, 0.667em);\n']))),Wm=Tr(P||(P=Li(["\n /* RGB */\n /* Standard colors */\n --rgb-red: var(--mush-rgb-red, var(--default-red));\n --rgb-pink: var(--mush-rgb-pink, var(--default-pink));\n --rgb-purple: var(--mush-rgb-purple, var(--default-purple));\n --rgb-deep-purple: var(--mush-rgb-deep-purple, var(--default-deep-purple));\n --rgb-indigo: var(--mush-rgb-indigo, var(--default-indigo));\n --rgb-blue: var(--mush-rgb-blue, var(--default-blue));\n --rgb-light-blue: var(--mush-rgb-light-blue, var(--default-light-blue));\n --rgb-cyan: var(--mush-rgb-cyan, var(--default-cyan));\n --rgb-teal: var(--mush-rgb-teal, var(--default-teal));\n --rgb-green: var(--mush-rgb-green, var(--default-green));\n --rgb-light-green: var(--mush-rgb-light-green, var(--default-light-green));\n --rgb-lime: var(--mush-rgb-lime, var(--default-lime));\n --rgb-yellow: var(--mush-rgb-yellow, var(--default-yellow));\n --rgb-amber: var(--mush-rgb-amber, var(--default-amber));\n --rgb-orange: var(--mush-rgb-orange, var(--default-orange));\n --rgb-deep-orange: var(--mush-rgb-deep-orange, var(--default-deep-orange));\n --rgb-brown: var(--mush-rgb-brown, var(--default-brown));\n --rgb-light-grey: var(--mush-rgb-light-grey, var(--default-light-grey));\n --rgb-grey: var(--mush-rgb-grey, var(--default-grey));\n --rgb-dark-grey: var(--mush-rgb-dark-grey, var(--default-dark-grey));\n --rgb-blue-grey: var(--mush-rgb-blue-grey, var(--default-blue-grey));\n --rgb-black: var(--mush-rgb-black, var(--default-black));\n --rgb-white: var(--mush-rgb-white, var(--default-white));\n --rgb-disabled: var(--mush-rgb-disabled, var(--default-disabled));\n\n /* Action colors */\n --rgb-info: var(--mush-rgb-info, var(--rgb-blue));\n --rgb-success: var(--mush-rgb-success, var(--rgb-green));\n --rgb-warning: var(--mush-rgb-warning, var(--rgb-orange));\n --rgb-danger: var(--mush-rgb-danger, var(--rgb-red));\n\n /* State colors */\n --rgb-state-vacuum: var(--mush-rgb-state-vacuum, var(--rgb-teal));\n --rgb-state-fan: var(--mush-rgb-state-fan, var(--rgb-green));\n --rgb-state-light: var(--mush-rgb-state-light, var(--rgb-orange));\n --rgb-state-entity: var(--mush-rgb-state-entity, var(--rgb-blue));\n --rgb-state-media-player: var(\n --mush-rgb-state-media-player,\n var(--rgb-indigo)\n );\n --rgb-state-lock: var(--mush-rgb-state-lock, var(--rgb-blue));\n --rgb-state-number: var(--mush-rgb-state-number, var(--rgb-blue));\n --rgb-state-humidifier: var(--mush-rgb-state-humidifier, var(--rgb-purple));\n\n /* State alarm colors */\n --rgb-state-alarm-disarmed: var(\n --mush-rgb-state-alarm-disarmed,\n var(--rgb-info)\n );\n --rgb-state-alarm-armed: var(\n --mush-rgb-state-alarm-armed,\n var(--rgb-success)\n );\n --rgb-state-alarm-triggered: var(\n --mush-rgb-state-alarm-triggered,\n var(--rgb-danger)\n );\n\n /* State person colors */\n --rgb-state-person-home: var(\n --mush-rgb-state-person-home,\n var(--rgb-success)\n );\n --rgb-state-person-not-home: var(\n --mush-rgb-state-person-not-home,\n var(--rgb-danger)\n );\n --rgb-state-person-zone: var(--mush-rgb-state-person-zone, var(--rgb-info));\n --rgb-state-person-unknown: var(\n --mush-rgb-state-person-unknown,\n var(--rgb-grey)\n );\n\n /* State update colors */\n --rgb-state-update-on: var(--mush-rgb-state-update-on, var(--rgb-orange));\n --rgb-state-update-off: var(--mush-rgb-update-off, var(--rgb-green));\n --rgb-state-update-installing: var(\n --mush-rgb-update-installing,\n var(--rgb-blue)\n );\n\n /* State lock colors */\n --rgb-state-lock-locked: var(--mush-rgb-state-lock-locked, var(--rgb-green));\n --rgb-state-lock-unlocked: var(\n --mush-rgb-state-lock-unlocked,\n var(--rgb-red)\n );\n --rgb-state-lock-pending: var(\n --mush-rgb-state-lock-pending,\n var(--rgb-orange)\n );\n\n /* State cover colors */\n --rgb-state-cover-open: var(--mush-rgb-state-cover-open, var(--rgb-blue));\n --rgb-state-cover-closed: var(\n --mush-rgb-state-cover-closed,\n var(--rgb-disabled)\n );\n\n /* State climate colors */\n --rgb-state-climate-auto: var(\n --mush-rgb-state-climate-auto,\n var(--rgb-green)\n );\n --rgb-state-climate-cool: var(--mush-rgb-state-climate-cool, var(--rgb-blue));\n --rgb-state-climate-dry: var(--mush-rgb-state-climate-dry, var(--rgb-orange));\n --rgb-state-climate-fan-only: var(\n --mush-rgb-state-climate-fan-only,\n var(--rgb-teal)\n );\n --rgb-state-climate-heat: var(\n --mush-rgb-state-climate-heat,\n var(--rgb-deep-orange)\n );\n --rgb-state-climate-heat-cool: var(\n --mush-rgb-state-climate-heat-cool,\n var(--rgb-green)\n );\n --rgb-state-climate-idle: var(\n --mush-rgb-state-climate-idle,\n var(--rgb-disabled)\n );\n --rgb-state-climate-off: var(\n --mush-rgb-state-climate-off,\n var(--rgb-disabled)\n );\n"])));function Xm(t){return!!t&&t.themes.darkMode}var Zm=function(t){function e(){return hr(this,e),er(this,e,arguments)}return or(e,za),pr(e,[{key:"updated",value:function(t){if($i(e,"updated",this,3)([t]),t.has("hass")&&this.hass){var n=Xm(t.get("hass")),o=Xm(this.hass);n!==o&&this.toggleAttribute("dark-mode",o)}}}],[{key:"styles",get:function(){return[Sl,Tr(N||(N=Li(["\n :host {\n ","\n }\n :host([dark-mode]) {\n ","\n }\n :host {\n ","\n ","\n }\n "])),Km,Ym,Wm,qm)]}}])}();br([Na({attribute:!1})],Zm.prototype,"hass",void 0);var Jm=["button","input_button","scene"],Qm=["name","state","last-changed","last-updated","none"],tv=["icon","entity-picture","none"];function ev(t,e,n,o,i){switch(t){case"name":return e;case"state":var r=o.entity_id.split(".")[0];return"timestamp"!==o.attributes.device_class&&!Jm.includes(r)||!Us(o)||function(t){return t.state===Ls}(o)?n:ha(B||(B=Li(["\n \n "])),i,o.state);case"last-changed":return ha(L||(L=Li(["\n \n "])),i,o.last_changed);case"last-updated":return ha(H||(H=Li(["\n \n "])),i,o.last_updated);case"none":return}}function nv(t,e){return"entity-picture"===e?Fs(t):void 0}var ov=function(t){function e(){return hr(this,e),er(this,e,arguments)}return or(e,Zm),pr(e,[{key:"_stateObj",get:function(){if(this._config&&this.hass&&this._config.entity){var t=this._config.entity;return this.hass.states[t]}}},{key:"hasControls",get:function(){return!1}},{key:"setConfig",value:function(t){this._config=Object.assign({tap_action:{action:"more-info"},hold_action:{action:"more-info"}},t)}},{key:"getCardSize",value:function(){var t,e=1;if(!this._config)return e;var n=Ol(this._config);return"vertical"===n.layout&&(e+=1),"horizontal"===(null==n?void 0:n.layout)||!this.hasControls||"collapsible_controls"in this._config&&(null===(t=this._config)||void 0===t?void 0:t.collapsible_controls)||(e+=1),e}},{key:"getLayoutOptions",value:function(){if(!this._config)return{grid_columns:2,grid_rows:1};var t={grid_columns:2,grid_rows:0},e=Ol(this._config),n="collapsible_controls"in this._config&&Boolean(this._config.collapsible_controls),o="none"!==e.primary_info||"none"!==e.secondary_info,i="none"!==e.icon_type,r=this._stateObj&&Rs(this._stateObj),a=this.hasControls&&(!n||r);return"vertical"===e.layout&&(i&&(t.grid_rows+=1),o&&(t.grid_rows+=1),a&&(t.grid_rows+=1)),"horizontal"===e.layout&&(t.grid_rows=1,t.grid_columns=4),"default"===e.layout&&((o||i)&&(t.grid_rows+=1),a&&(t.grid_rows+=1)),a||o||(t.grid_columns=1,t.grid_rows=1),t.grid_rows=Math.max(t.grid_rows,1),t}},{key:"getGridOptions",value:function(){if(!this._config)return{columns:6,rows:1};var t={min_rows:1,min_columns:4,columns:6,rows:0},e=Ol(this._config),n="collapsible_controls"in this._config&&Boolean(this._config.collapsible_controls),o="none"!==e.primary_info||"none"!==e.secondary_info,i="none"!==e.icon_type,r=this._stateObj&&Rs(this._stateObj),a=this.hasControls&&(!n||r);return"vertical"===e.layout&&(i&&(t.rows+=1),o&&(t.rows+=1),a&&(t.rows+=1),t.min_columns=2),"horizontal"===e.layout&&(t.rows=1,t.columns=12),"default"===e.layout&&((o||i)&&(t.rows+=1),a&&(t.rows+=1)),a||o||(t.columns=3,t.rows=1,t.min_columns=2),t.rows=Math.max(t.rows,1),t.min_rows=t.rows,t}},{key:"renderPicture",value:function(t){return ha(D||(D=Li(['\n \n \n \n \n \n \n ']))):fa}},{key:"renderStateInfo",value:function(t,e,n,o){var i=this.hass.formatEntityState(t),r=null!=o?o:i,a=ev(e.primary_info,n,r,t,this.hass),s=ev(e.secondary_info,n,r,t,this.hass);return ha(F||(F=Li(['\n =0}rv({type:sv,name:"Mushroom Alarm Control Panel Card",description:"Card for alarm control panel"});var pv=function(t){function e(){return hr(this,e),er(this,e,arguments)}return or(e,ov),pr(e,[{key:"hasControls",get:function(){var t,e;return Boolean(null===(e=null===(t=this._config)||void 0===t?void 0:t.states)||void 0===e?void 0:e.length)}},{key:"_onTap",value:function(t,e){t.stopPropagation(),bl(this,this.hass,this._stateObj,e)}},{key:"_handleAction",value:function(t){rl(this,this.hass,this._config,t.detail.action)}},{key:"render",value:function(){var t=this;if(!this.hass||!this._config||!this._config.entity)return fa;var e=this._stateObj;if(!e)return this.renderNotFound(this._config);var n=this._config.name||e.attributes.friendly_name||"",o=this._config.icon,i=Ol(this._config),r=nv(e,i.icon_type),a=this._config.states&&this._config.states.length>0?function(t){return"disarmed"===t.state}(e)?this._config.states.map((function(t){return{mode:t}})):[{mode:"disarmed"}]:[],s=function(t){return Bs!==t.state}(e),l=Is(this.hass);return ha(G||(G=Li(["\n \n \n \n ","\n ","\n ",";\n \n ","\n \n \n "])),Ka({"fill-container":i.fill_container}),i,l,l,i,this._handleAction,il({hasHold:al(this._config.hold_action),hasDoubleClick:al(this._config.double_tap_action)}),r?this.renderPicture(r):this.renderIcon(e,o),this.renderBadge(e),this.renderStateInfo(e,i,n),a.length>0?ha(K||(K=Li(['\n
\n \n \n
\n ']))))}}],[{key:"styles",get:function(){return[Sl,Tr(Q||(Q=Li(["\n :host {\n --icon-color: var(--primary-text-color);\n --text-color: var(--primary-text-color);\n }\n ha-card {\n box-sizing: border-box;\n height: var(--chip-height);\n min-width: var(--chip-height);\n font-size: var(--chip-height);\n width: auto;\n border-radius: var(--chip-border-radius);\n display: flex;\n flex-direction: row;\n align-items: center;\n background: var(--chip-background);\n border-width: var(--chip-border-width);\n border-color: var(--chip-border-color);\n box-shadow: var(--chip-box-shadow);\n box-sizing: content-box;\n }\n .avatar {\n --avatar-size: calc(\n var(--chip-height) - 2 * var(--chip-avatar-padding)\n );\n border-radius: var(--chip-avatar-border-radius);\n height: var(--avatar-size);\n width: var(--avatar-size);\n margin-left: var(--chip-avatar-padding);\n box-sizing: border-box;\n object-fit: cover;\n }\n :host([rtl]) .avatar {\n margin-left: initial;\n margin-right: var(--chip-avatar-padding);\n }\n .content {\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: center;\n height: 100%;\n padding: var(--chip-padding);\n line-height: 0;\n }\n ::slotted(ha-icon),\n ::slotted(ha-state-icon) {\n display: flex;\n line-height: 0;\n --mdc-icon-size: var(--chip-icon-size);\n color: var(--icon-color);\n }\n ::slotted(svg) {\n width: var(--chip-icon-size);\n height: var(--chip-icon-size);\n display: flex;\n }\n ::slotted(span) {\n font-weight: var(--chip-font-weight);\n font-size: var(--chip-font-size);\n line-height: 1;\n color: var(--text-color);\n }\n ::slotted(*:not(:last-child)) {\n margin-right: 0.15em;\n }\n :host([rtl]) ::slotted(*:not(:last-child)) {\n margin-right: initial;\n margin-left: 0.15em;\n }\n "])))]}}])}();br([Na()],fv.prototype,"icon",void 0),br([Na()],fv.prototype,"label",void 0),br([Na()],fv.prototype,"avatar",void 0),br([Na()],fv.prototype,"avatarOnly",void 0),fv=br([Ia("mushroom-chip")],fv);var mv=function(t){try{var e=vv(t.type);if(customElements.get(e)){var n=document.createElement(e,t);return n.setConfig(t),n}var o=document.createElement(e);return customElements.whenDefined(e).then((function(){try{customElements.upgrade(o),o.setConfig(t)}catch(t){}})),o}catch(t){return void console.error(t)}};function vv(t){return"".concat(av,"-").concat(t,"-chip")}function gv(t){return"".concat(av,"-").concat(t,"-chip-editor")}var _v=function(t){function e(){return hr(this,e),er(this,e,arguments)}return or(e,za),pr(e,[{key:"setConfig",value:function(t){this._config=t}},{key:"_handleAction",value:function(t){rl(this,this.hass,this._config,t.detail.action)}},{key:"render",value:function(){var t;if(!this.hass||!this._config||!this._config.entity)return fa;var e=this._config.entity,n=this.hass.states[e];if(!n)return fa;var o=this._config.name||n.attributes.friendly_name||"",i=this._config.icon,r=this._config.icon_color,a=this._config.use_entity_picture?Fs(n):void 0,s=this.hass.formatEntityState(n),l=Rs(n),c=ev(null!==(t=this._config.content_info)&&void 0!==t?t:"state",o,s,n,this.hass),u=Is(this.hass);return ha(tt||(tt=Li(["\n \n ","\n ","\n \n "])),u,this._handleAction,il({hasHold:al(this._config.hold_action),hasDoubleClick:al(this._config.double_tap_action)}),a?this.hass.hassUrl(a):void 0,a&&!c,a?fa:this.renderIcon(n,i,r,l),c?ha(et||(et=Li(["",""])),c):fa)}},{key:"renderIcon",value:function(t,e,n,o){var i={};if(n){var r=Gm(n);i["--color"]="rgb(".concat(r,")")}return ha(nt||(nt=Li(["\n \n "])),this.hass,t,e,Wa(i),Ka({active:o}))}}],[{key:"getConfigElement",value:(o=tr(Zi().m((function t(){return Zi().w((function(t){for(;;)switch(t.n){case 0:return t.n=1,Promise.resolve().then((function(){return ab}));case 1:return t.a(2,document.createElement(gv("entity")))}}),t)}))),function(){return o.apply(this,arguments)})},{key:"getStubConfig",value:(n=tr(Zi().m((function t(e){var n;return Zi().w((function(t){for(;;)if(0===t.n)return n=Object.keys(e.states),t.a(2,{type:"entity",entity:n[0]})}),t)}))),function(t){return n.apply(this,arguments)})},{key:"styles",get:function(){return Tr(ot||(ot=Li(["\n mushroom-chip {\n cursor: pointer;\n }\n ha-state-icon.active {\n color: var(--color);\n }\n "])))}}]);var n,o}();br([Na({attribute:!1})],_v.prototype,"hass",void 0),br([Ba()],_v.prototype,"_config",void 0),_v=br([Ia(vv("entity"))],_v);var yv=new Set(["partlycloudy","cloudy","fog","windy","windy-variant","hail","rainy","snowy","snowy-rainy","pouring","lightning","lightning-rainy"]),bv=new Set(["hail","rainy","pouring"]),kv=new Set(["windy","windy-variant"]),wv=new Set(["snowy","snowy-rainy"]),Cv=new Set(["lightning","lightning-rainy"]),Ev=Tr(it||(it=Li(["\n .rain {\n fill: var(--weather-icon-rain-color, #30b3ff);\n }\n .sun {\n fill: var(--weather-icon-sun-color, #fdd93c);\n }\n .moon {\n fill: var(--weather-icon-moon-color, #fcf497);\n }\n .cloud-back {\n fill: var(--weather-icon-cloud-back-color, #d4d4d4);\n }\n .cloud-front {\n fill: var(--weather-icon-cloud-front-color, #f9f9f9);\n }\n"]))),xv=function(t,e){return da(rt||(rt=Li(['\n \n ',"\n ","\n ","\n ","\n ","\n ","\n ","\n ","\n ","\n "])),"sunny"===t?da(at||(at=Li(['\n \n ']))):"","clear-night"===t?da(st||(st=Li(['\n \n ']))):"","partlycloudy"===t&&e?da(lt||(lt=Li(['\n \n ']))):"partlycloudy"===t?da(ct||(ct=Li(['\n \n ']))):"",yv.has(t)?da(ut||(ut=Li(['\n \n \n ']))):"",bv.has(t)?da(ht||(ht=Li(['\n \n \n \n \n ']))):"","pouring"===t?da(dt||(dt=Li(['\n \n \n ']))):"",kv.has(t)?da(pt||(pt=Li(['\n \n \n ']))):"",wv.has(t)?da(ft||(ft=Li(['\n \n \n \n ']))):"",Cv.has(t)?da(mt||(mt=Li(['\n \n ']))):"")},Av=function(t){function e(){return hr(this,e),er(this,e,arguments)}return or(e,za),pr(e,[{key:"setConfig",value:function(t){this._config=t}},{key:"_handleAction",value:function(t){rl(this,this.hass,this._config,t.detail.action)}},{key:"render",value:function(){if(!this.hass||!this._config||!this._config.entity)return fa;var t=this._config.entity,e=this.hass.states[t];if(!e)return fa;var n=xv(e.state,!0),o=[];if(this._config.show_conditions){var i=this.hass.formatEntityState(e);o.push(i)}if(this._config.show_temperature){var r=this.hass.formatEntityAttributeValue(e,"temperature");o.push(r)}var a=Is(this.hass);return ha(vt||(vt=Li(["\n \n ","\n ","\n \n "])),a,this._handleAction,il({hasHold:al(this._config.hold_action),hasDoubleClick:al(this._config.double_tap_action)}),n,o.length>0?ha(gt||(gt=Li(["",""])),o.join(" ⸱ ")):fa)}}],[{key:"getConfigElement",value:(o=tr(Zi().m((function t(){return Zi().w((function(t){for(;;)switch(t.n){case 0:return t.n=1,Promise.resolve().then((function(){return db}));case 1:return t.a(2,document.createElement(gv("weather")))}}),t)}))),function(){return o.apply(this,arguments)})},{key:"getStubConfig",value:(n=tr(Zi().m((function t(e){var n,o;return Zi().w((function(t){for(;;)if(0===t.n)return n=Object.keys(e.states),o=n.filter((function(t){return"weather"===t.split(".")[0]})),t.a(2,{type:"weather",entity:o[0]})}),t)}))),function(t){return n.apply(this,arguments)})},{key:"styles",get:function(){return[Ev,Tr(_t||(_t=Li(["\n mushroom-chip {\n cursor: pointer;\n }\n "])))]}}]);var n,o}();br([Na({attribute:!1})],Av.prototype,"hass",void 0),br([Ba()],Av.prototype,"_config",void 0),Av=br([Ia(vv("weather"))],Av);var Sv="mdi:arrow-left",Tv=function(t){function e(){return hr(this,e),er(this,e,arguments)}return or(e,za),pr(e,[{key:"setConfig",value:function(t){this._config=t}},{key:"_handleAction",value:function(){window.history.back()}},{key:"render",value:function(){if(!this.hass||!this._config)return fa;var t=this._config.icon||Sv,e=Is(this.hass);return ha(yt||(yt=Li(["\n \n \n \n "])),e,this._handleAction,il(),this.hass,t)}}],[{key:"getConfigElement",value:(o=tr(Zi().m((function t(){return Zi().w((function(t){for(;;)switch(t.n){case 0:return t.n=1,Promise.resolve().then((function(){return mb}));case 1:return t.a(2,document.createElement(gv("back")))}}),t)}))),function(){return o.apply(this,arguments)})},{key:"getStubConfig",value:(n=tr(Zi().m((function t(e){return Zi().w((function(t){for(;;)if(0===t.n)return t.a(2,{type:"back"})}),t)}))),function(t){return n.apply(this,arguments)})},{key:"styles",get:function(){return Tr(bt||(bt=Li(["\n mushroom-chip {\n cursor: pointer;\n }\n "])))}}]);var n,o}();br([Na({attribute:!1})],Tv.prototype,"hass",void 0),br([Ba()],Tv.prototype,"_config",void 0),Tv=br([Ia(vv("back"))],Tv);var Mv="mdi:flash",zv=function(t){function e(){return hr(this,e),er(this,e,arguments)}return or(e,za),pr(e,[{key:"setConfig",value:function(t){this._config=t}},{key:"_handleAction",value:function(t){rl(this,this.hass,this._config,t.detail.action)}},{key:"render",value:function(){if(!this.hass||!this._config)return fa;var t=this._config.icon||Mv,e=this._config.icon_color,n={};if(e){var o=Gm(e);n["--color"]="rgb(".concat(o,")")}var i=Is(this.hass);return ha(kt||(kt=Li(["\n \n \n \n "])),i,this._handleAction,il({hasHold:al(this._config.hold_action),hasDoubleClick:al(this._config.double_tap_action)}),this.hass,t,Wa(n))}}],[{key:"getConfigElement",value:(o=tr(Zi().m((function t(){return Zi().w((function(t){for(;;)switch(t.n){case 0:return t.n=1,Promise.resolve().then((function(){return yb}));case 1:return t.a(2,document.createElement(gv("action")))}}),t)}))),function(){return o.apply(this,arguments)})},{key:"getStubConfig",value:(n=tr(Zi().m((function t(e){return Zi().w((function(t){for(;;)if(0===t.n)return t.a(2,{type:"action"})}),t)}))),function(t){return n.apply(this,arguments)})},{key:"styles",get:function(){return Tr(wt||(wt=Li(["\n mushroom-chip {\n cursor: pointer;\n }\n ha-state-icon {\n color: var(--color);\n }\n "])))}}]);var n,o}();br([Na({attribute:!1})],zv.prototype,"hass",void 0),br([Ba()],zv.prototype,"_config",void 0),zv=br([Ia(vv("action"))],zv);var Ov="mdi:menu",Iv=function(t){function e(){return hr(this,e),er(this,e,arguments)}return or(e,za),pr(e,[{key:"setConfig",value:function(t){this._config=t}},{key:"_handleAction",value:function(){Za(this,"hass-toggle-menu")}},{key:"render",value:function(){if(!this.hass||!this._config)return fa;var t=this._config.icon||Ov,e=Is(this.hass);return ha(Ct||(Ct=Li(["\n \n \n \n "])),e,this._handleAction,il(),this.hass,t)}}],[{key:"getConfigElement",value:(o=tr(Zi().m((function t(){return Zi().w((function(t){for(;;)switch(t.n){case 0:return t.n=1,Promise.resolve().then((function(){return wb}));case 1:return t.a(2,document.createElement(gv("menu")))}}),t)}))),function(){return o.apply(this,arguments)})},{key:"getStubConfig",value:(n=tr(Zi().m((function t(e){return Zi().w((function(t){for(;;)if(0===t.n)return t.a(2,{type:"menu"})}),t)}))),function(t){return n.apply(this,arguments)})},{key:"styles",get:function(){return Tr(Et||(Et=Li(["\n mushroom-chip {\n cursor: pointer;\n }\n "])))}}]);var n,o}();br([Na({attribute:!1})],Iv.prototype,"hass",void 0),br([Ba()],Iv.prototype,"_config",void 0),Iv=br([Ia(vv("menu"))],Iv);var jv="mdi:magnify",Pv=function(t){function e(){return hr(this,e),er(this,e,arguments)}return or(e,za),pr(e,[{key:"setConfig",value:function(t){this._config=t}},{key:"_handleAction",value:function(){if(this.hass&&this._config){var t;switch(this._config.mode||"entity"){case"command":t="c";break;case"device":t="d";break;case"entity":t="e"}var e=new KeyboardEvent("keydown",{bubbles:!0,composed:!0,key:t});this.dispatchEvent(e)}}},{key:"render",value:function(){if(!this.hass||!this._config)return fa;var t=this._config.icon||jv,e=Is(this.hass);return ha(xt||(xt=Li(["\n \n \n \n "])),e,this._handleAction,il(),this.hass,t)}}],[{key:"getConfigElement",value:(o=tr(Zi().m((function t(){return Zi().w((function(t){for(;;)switch(t.n){case 0:return t.n=1,Promise.resolve().then((function(){return xb}));case 1:return t.a(2,document.createElement(gv("quickbar")))}}),t)}))),function(){return o.apply(this,arguments)})},{key:"getStubConfig",value:(n=tr(Zi().m((function t(e){return Zi().w((function(t){for(;;)if(0===t.n)return t.a(2,{type:"quickbar"})}),t)}))),function(t){return n.apply(this,arguments)})},{key:"styles",get:function(){return Tr(At||(At=Li(["\n mushroom-chip {\n cursor: pointer;\n }\n "])))}}]);var n,o}();function Nv(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}function Bv(t){throw new Error('Could not dynamically require "'+t+'". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.')}br([Na({attribute:!1})],Pv.prototype,"hass",void 0),br([Ba()],Pv.prototype,"_config",void 0),Pv=br([Ia(vv("quickbar"))],Pv);var Lv,Hv={exports:{}};var Dv=(Lv||(Lv=1,Hv.exports=function t(e,n,o){function i(a,s){if(!n[a]){if(!e[a]){if(!s&&Bv)return Bv(a);if(r)return r(a,!0);throw new Error("Cannot find module '"+a+"'")}s=n[a]={exports:{}},e[a][0].call(s.exports,(function(t){return i(e[a][1][t]||t)}),s,s.exports,t,e,n,o)}return n[a].exports}for(var r=Bv,a=0;a>16),l((65280&o)>>8),l(255&o);return 2==i?l(255&(o=c(t.charAt(n))<<2|c(t.charAt(n+1))>>4)):1==i&&(l((o=c(t.charAt(n))<<10|c(t.charAt(n+1))<<4|c(t.charAt(n+2))>>2)>>8&255),l(255&o)),r},t.fromByteArray=function(t){var e,n,o,i,r=t.length%3,a="";function s(t){return"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(t)}for(e=0,o=t.length-r;e>18&63)+s(i>>12&63)+s(i>>6&63)+s(63&i);switch(r){case 1:a=(a+=s((n=t[t.length-1])>>2))+s(n<<4&63)+"==";break;case 2:a=(a=(a+=s((n=(t[t.length-2]<<8)+t[t.length-1])>>10))+s(n>>4&63))+s(n<<2&63)+"="}return a}}(void 0===n?this.base64js={}:n)}).call(this,t("lYpoI2"),"undefined"!=typeof self?self:"undefined"!=typeof window?window:{},t("buffer").Buffer,arguments[3],arguments[4],arguments[5],arguments[6],"/node_modules/gulp-browserify/node_modules/base64-js/lib/b64.js","/node_modules/gulp-browserify/node_modules/base64-js/lib")},{buffer:3,lYpoI2:11}],3:[function(t,e,n){(function(e,o,i,r,a,s,l,c,u){var h=t("base64-js"),d=t("ieee754");function i(t,e,n){if(!(this instanceof i))return new i(t,e,n);var o,r,a,s,l=mr(t);if("base64"===e&&"string"==l)for(t=(s=t).trim?s.trim():s.replace(/^\s+|\s+$/g,"");t.length%4!=0;)t+="=";if("number"==l)o=z(t);else if("string"==l)o=i.byteLength(t,e);else{if("object"!=l)throw new Error("First argument needs to be a number, array or string.");o=z(t.length)}if(i._useTypedArrays?r=i._augment(new Uint8Array(o)):((r=this).length=o,r._isBuffer=!0),i._useTypedArrays&&"number"==typeof t.byteLength)r._set(t);else if(O(s=t)||i.isBuffer(s)||s&&"object"==mr(s)&&"number"==typeof s.length)for(a=0;a>8,n%=256,o.push(n),o.push(e);return o}(e),t,n,o)}function m(t,e,n){var o="";n=Math.min(t.length,n);for(var i=e;i>>0)):(e+1>>0),i}function _(t,e,n,o){if(o||(R("boolean"==typeof n,"missing or invalid endian"),R(null!=e,"missing offset"),R(e+1>>8*(o?r:1-r)}function C(t,e,n,o,i){if(i||(R(null!=e,"missing value"),R("boolean"==typeof o,"missing or invalid endian"),R(null!=n,"missing offset"),R(n+3>>8*(o?r:3-r)&255}function E(t,e,n,o,i){i||(R(null!=e,"missing value"),R("boolean"==typeof o,"missing or invalid endian"),R(null!=n,"missing offset"),R(n+1this.length&&(o=this.length);var r=(o=t.length-e=this.length))return this[t]},i.prototype.readUInt16LE=function(t,e){return v(this,t,!0,e)},i.prototype.readUInt16BE=function(t,e){return v(this,t,!1,e)},i.prototype.readUInt32LE=function(t,e){return g(this,t,!0,e)},i.prototype.readUInt32BE=function(t,e){return g(this,t,!1,e)},i.prototype.readInt8=function(t,e){if(e||(R(null!=t,"missing offset"),R(t=this.length))return 128&this[t]?-1*(255-this[t]+1):this[t]},i.prototype.readInt16LE=function(t,e){return _(this,t,!0,e)},i.prototype.readInt16BE=function(t,e){return _(this,t,!1,e)},i.prototype.readInt32LE=function(t,e){return y(this,t,!0,e)},i.prototype.readInt32BE=function(t,e){return y(this,t,!1,e)},i.prototype.readFloatLE=function(t,e){return b(this,t,!0,e)},i.prototype.readFloatBE=function(t,e){return b(this,t,!1,e)},i.prototype.readDoubleLE=function(t,e){return k(this,t,!0,e)},i.prototype.readDoubleBE=function(t,e){return k(this,t,!1,e)},i.prototype.writeUInt8=function(t,e,n){n||(R(null!=t,"missing value"),R(null!=e,"missing offset"),R(e=this.length||(this[e]=t)},i.prototype.writeUInt16LE=function(t,e,n){w(this,t,e,!0,n)},i.prototype.writeUInt16BE=function(t,e,n){w(this,t,e,!1,n)},i.prototype.writeUInt32LE=function(t,e,n){C(this,t,e,!0,n)},i.prototype.writeUInt32BE=function(t,e,n){C(this,t,e,!1,n)},i.prototype.writeInt8=function(t,e,n){n||(R(null!=t,"missing value"),R(null!=e,"missing offset"),R(e=this.length||(0<=t?this.writeUInt8(t,e,n):this.writeUInt8(255+t+1,e,n))},i.prototype.writeInt16LE=function(t,e,n){E(this,t,e,!0,n)},i.prototype.writeInt16BE=function(t,e,n){E(this,t,e,!1,n)},i.prototype.writeInt32LE=function(t,e,n){x(this,t,e,!0,n)},i.prototype.writeInt32BE=function(t,e,n){x(this,t,e,!1,n)},i.prototype.writeFloatLE=function(t,e,n){A(this,t,e,!0,n)},i.prototype.writeFloatBE=function(t,e,n){A(this,t,e,!1,n)},i.prototype.writeDoubleLE=function(t,e,n){S(this,t,e,!0,n)},i.prototype.writeDoubleBE=function(t,e,n){S(this,t,e,!1,n)},i.prototype.fill=function(t,e,n){if(e=e||0,n=n||this.length,R("number"==typeof(t="string"==typeof(t=t||0)?t.charCodeAt(0):t)&&!isNaN(t),"value is not a number"),R(e<=n,"end < start"),n!==e&&0!==this.length){R(0<=e&&e"},i.prototype.toArrayBuffer=function(){if("undefined"==typeof Uint8Array)throw new Error("Buffer.toArrayBuffer not supported in this browser");if(i._useTypedArrays)return new i(this).buffer;for(var t=new Uint8Array(this.length),e=0,n=t.length;e=e.length||i>=t.length);i++)e[i+n]=t[i];return i}function B(t){try{return decodeURIComponent(t)}catch(t){return String.fromCharCode(65533)}}function L(t,e){R("number"==typeof t,"cannot write a non-number as a number"),R(0<=t,"specified a negative value for writing an unsigned value"),R(t<=e,"value is larger than maximum value for type"),R(Math.floor(t)===t,"value has a fractional component")}function H(t,e,n){R("number"==typeof t,"cannot write a non-number as a number"),R(t<=e,"value larger than maximum allowed value"),R(n<=t,"value smaller than minimum allowed value"),R(Math.floor(t)===t,"value has a fractional component")}function D(t,e,n){R("number"==typeof t,"cannot write a non-number as a number"),R(t<=e,"value larger than maximum allowed value"),R(n<=t,"value smaller than minimum allowed value")}function R(t,e){if(!t)throw new Error(e||"Failed assertion")}i._augment=function(t){return t._isBuffer=!0,t._get=t.get,t._set=t.set,t.get=T.get,t.set=T.set,t.write=T.write,t.toString=T.toString,t.toLocaleString=T.toString,t.toJSON=T.toJSON,t.copy=T.copy,t.slice=T.slice,t.readUInt8=T.readUInt8,t.readUInt16LE=T.readUInt16LE,t.readUInt16BE=T.readUInt16BE,t.readUInt32LE=T.readUInt32LE,t.readUInt32BE=T.readUInt32BE,t.readInt8=T.readInt8,t.readInt16LE=T.readInt16LE,t.readInt16BE=T.readInt16BE,t.readInt32LE=T.readInt32LE,t.readInt32BE=T.readInt32BE,t.readFloatLE=T.readFloatLE,t.readFloatBE=T.readFloatBE,t.readDoubleLE=T.readDoubleLE,t.readDoubleBE=T.readDoubleBE,t.writeUInt8=T.writeUInt8,t.writeUInt16LE=T.writeUInt16LE,t.writeUInt16BE=T.writeUInt16BE,t.writeUInt32LE=T.writeUInt32LE,t.writeUInt32BE=T.writeUInt32BE,t.writeInt8=T.writeInt8,t.writeInt16LE=T.writeInt16LE,t.writeInt16BE=T.writeInt16BE,t.writeInt32LE=T.writeInt32LE,t.writeInt32BE=T.writeInt32BE,t.writeFloatLE=T.writeFloatLE,t.writeFloatBE=T.writeFloatBE,t.writeDoubleLE=T.writeDoubleLE,t.writeDoubleBE=T.writeDoubleBE,t.fill=T.fill,t.inspect=T.inspect,t.toArrayBuffer=T.toArrayBuffer,t}}).call(this,t("lYpoI2"),"undefined"!=typeof self?self:"undefined"!=typeof window?window:{},t("buffer").Buffer,arguments[3],arguments[4],arguments[5],arguments[6],"/node_modules/gulp-browserify/node_modules/buffer/index.js","/node_modules/gulp-browserify/node_modules/buffer")},{"base64-js":2,buffer:3,ieee754:10,lYpoI2:11}],4:[function(t,e,n){(function(n,o,i,r,a,s,l,c,u){i=t("buffer").Buffer;var h=4,d=new i(h);d.fill(0),e.exports={hash:function(t,e,n,o){for(var r=e(function(t,e){t.length%h!=0&&(n=t.length+(h-t.length%h),t=i.concat([t,d],n));for(var n,o=[],r=e?t.readInt32BE:t.readInt32LE,a=0;am?e=t(e):e.length>5]|=128<>>9<<4)]=e;for(var n=1732584193,o=-271733879,i=-1732584194,r=271733878,a=0;a>>32-i,n)}function f(t,e,n,o,i,r,a){return p(e&n|~e&o,t,e,i,r,a)}function m(t,e,n,o,i,r,a){return p(e&o|n&~o,t,e,i,r,a)}function v(t,e,n,o,i,r,a){return p(e^n^o,t,e,i,r,a)}function g(t,e,n,o,i,r,a){return p(n^(e|~o),t,e,i,r,a)}function _(t,e){var n=(65535&t)+(65535&e);return(t>>16)+(e>>16)+(n>>16)<<16|65535&n}e.exports=function(t){return h.hash(t,d,16)}}).call(this,t("lYpoI2"),"undefined"!=typeof self?self:"undefined"!=typeof window?window:{},t("buffer").Buffer,arguments[3],arguments[4],arguments[5],arguments[6],"/node_modules/gulp-browserify/node_modules/crypto-browserify/md5.js","/node_modules/gulp-browserify/node_modules/crypto-browserify")},{"./helpers":4,buffer:3,lYpoI2:11}],7:[function(t,e,n){(function(t,n,o,i,r,a,s,l,c){e.exports=function(t){for(var e,n=new Array(t),o=0;o>>((3&o)<<3)&255;return n}}).call(this,t("lYpoI2"),"undefined"!=typeof self?self:"undefined"!=typeof window?window:{},t("buffer").Buffer,arguments[3],arguments[4],arguments[5],arguments[6],"/node_modules/gulp-browserify/node_modules/crypto-browserify/rng.js","/node_modules/gulp-browserify/node_modules/crypto-browserify")},{buffer:3,lYpoI2:11}],8:[function(t,e,n){(function(n,o,i,r,a,s,l,c,u){var h=t("./helpers");function d(t,e){t[e>>5]|=128<<24-e%32,t[15+(e+64>>9<<4)]=e;for(var n,o,i,r=Array(80),a=1732584193,s=-271733879,l=-1732584194,c=271733878,u=-1009589776,h=0;h>16)+(e>>16)+(n>>16)<<16|65535&n}function f(t,e){return t<>>32-e}e.exports=function(t){return h.hash(t,d,20,!0)}}).call(this,t("lYpoI2"),"undefined"!=typeof self?self:"undefined"!=typeof window?window:{},t("buffer").Buffer,arguments[3],arguments[4],arguments[5],arguments[6],"/node_modules/gulp-browserify/node_modules/crypto-browserify/sha.js","/node_modules/gulp-browserify/node_modules/crypto-browserify")},{"./helpers":4,buffer:3,lYpoI2:11}],9:[function(t,e,n){(function(n,o,i,r,a,s,l,c,u){function h(t,e){var n=(65535&t)+(65535&e);return(t>>16)+(e>>16)+(n>>16)<<16|65535&n}function d(t,e){var n,o=new Array(1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298),i=new Array(1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225),r=new Array(64);t[e>>5]|=128<<24-e%32,t[15+(e+64>>9<<4)]=e;for(var a,s,l=0;l>>e|t<<32-e},m=function(t,e){return t>>>e};e.exports=function(t){return p.hash(t,d,32,!0)}}).call(this,t("lYpoI2"),"undefined"!=typeof self?self:"undefined"!=typeof window?window:{},t("buffer").Buffer,arguments[3],arguments[4],arguments[5],arguments[6],"/node_modules/gulp-browserify/node_modules/crypto-browserify/sha256.js","/node_modules/gulp-browserify/node_modules/crypto-browserify")},{"./helpers":4,buffer:3,lYpoI2:11}],10:[function(t,e,n){(function(t,e,o,i,r,a,s,l,c){n.read=function(t,e,n,o,i){var r,a,s=8*i-o-1,l=(1<>1,u=-7,h=n?i-1:0,d=n?-1:1;for(i=t[e+h],h+=d,r=i&(1<<-u)-1,i>>=-u,u+=s;0>=-u,u+=o;0>1,h=23===i?Math.pow(2,-24)-Math.pow(2,-77):0,d=o?0:r-1,p=o?1:-1;for(r=e<0||0===e&&1/e<0?1:0,e=Math.abs(e),isNaN(e)||e===1/0?(s=isNaN(e)?1:0,a=c):(a=Math.floor(Math.log(e)/Math.LN2),e*(o=Math.pow(2,-a))<1&&(a--,o*=2),2<=(e+=1<=a+u?h/o:h*Math.pow(2,1-u))*o&&(a++,o/=2),c<=a+u?(s=0,a=c):1<=a+u?(s=(e*o-1)*Math.pow(2,i),a+=u):(s=e*Math.pow(2,u-1)*Math.pow(2,i),a=0));8<=i;t[n+d]=255&s,d+=p,s/=256,i-=8);for(a=a<\n ","\n ","\n \n "])),i,this._handleAction,il({hasHold:al(this._config.hold_action),hasDoubleClick:al(this._config.double_tap_action)}),o?this.hass.hassUrl(o):void 0,o&&!n||!1,o?fa:r||(t?this.renderIcon(t,e):fa),n?this.renderContent(n):fa)}},{key:"renderIcon",value:function(t,e){var n={};if(e){var o=Gm(e);n["--color"]="rgb(".concat(o,")")}return ha(Tt||(Tt=Li([""])),this.hass,t,Wa(n))}},{key:"renderContent",value:function(t){return ha(Mt||(Mt=Li(["",""])),t)}},{key:"updated",value:function(t){$i(e,"updated",this,3)([t]),this._config&&this.hass&&this._tryConnect()}},{key:"_tryConnect",value:(s=tr(Zi().m((function t(){var e=this;return Zi().w((function(t){for(;;)switch(t.n){case 0:Gv.forEach((function(t){e._tryConnectKey(t)}));case 1:return t.a(2)}}),t)}))),function(){return s.apply(this,arguments)})},{key:"_tryConnectKey",value:(a=tr(Zi().m((function t(e){var n,o,i,r,a=this;return Zi().w((function(t){for(;;)switch(t.p=t.n){case 0:if(void 0===this._unsubRenderTemplates.get(e)&&this.hass&&this._config&&this.isTemplate(e)){t.n=1;break}return t.a(2);case 1:return t.p=1,i=tl(this.hass.connection,(function(t){a._templateResults=Object.assign(Object.assign({},a._templateResults),Fi({},e,t))}),{template:null!==(n=this._config[e])&&void 0!==n?n:"",entity_ids:this._config.entity_id,variables:{config:this._config,user:this.hass.user.name,entity:this._config.entity},strict:!0}),this._unsubRenderTemplates.set(e,i),t.n=2,i;case 2:t.n=4;break;case 3:t.p=3,t.v,r={result:null!==(o=this._config[e])&&void 0!==o?o:"",listeners:{all:!1,domains:[],entities:[],time:!1}},this._templateResults=Object.assign(Object.assign({},this._templateResults),Fi({},e,r)),this._unsubRenderTemplates.delete(e);case 4:return t.a(2)}}),t,this,[[1,3]])}))),function(t){return a.apply(this,arguments)})},{key:"_tryDisconnect",value:(r=tr(Zi().m((function t(){var e=this;return Zi().w((function(t){for(;;)switch(t.n){case 0:Gv.forEach((function(t){e._tryDisconnectKey(t)}));case 1:return t.a(2)}}),t)}))),function(){return r.apply(this,arguments)})},{key:"_tryDisconnectKey",value:(i=tr(Zi().m((function t(e){var n,o;return Zi().w((function(t){for(;;)switch(t.p=t.n){case 0:if(n=this._unsubRenderTemplates.get(e)){t.n=1;break}return t.a(2);case 1:return t.p=1,t.n=2,n;case 2:(0,t.v)(),this._unsubRenderTemplates.delete(e),t.n=5;break;case 3:if(t.p=3,"not_found"!==(o=t.v).code&&"template_error"!==o.code){t.n=4;break}t.n=5;break;case 4:throw o;case 5:return t.a(2)}}),t,this,[[1,3]])}))),function(t){return i.apply(this,arguments)})}],[{key:"getConfigElement",value:(o=tr(Zi().m((function t(){return Zi().w((function(t){for(;;)switch(t.n){case 0:return t.n=1,Promise.resolve().then((function(){return Ib}));case 1:return t.a(2,document.createElement(gv("template")))}}),t)}))),function(){return o.apply(this,arguments)})},{key:"getStubConfig",value:(n=tr(Zi().m((function t(e){return Zi().w((function(t){for(;;)if(0===t.n)return t.a(2,{type:"template"})}),t)}))),function(t){return n.apply(this,arguments)})},{key:"styles",get:function(){return Tr(zt||(zt=Li(["\n mushroom-chip {\n cursor: pointer;\n }\n ha-state-icon {\n color: var(--color);\n }\n ","\n "])),Ev)}}]);var n,o,i,r,a,s}();br([Na({attribute:!1})],Kv.prototype,"hass",void 0),br([Ba()],Kv.prototype,"_config",void 0),br([Ba()],Kv.prototype,"_templateResults",void 0),br([Ba()],Kv.prototype,"_unsubRenderTemplates",void 0),Kv=br([Ia(vv("template"))],Kv);var Yv=function(){var t,e,n;customElements.get("ha-form")&&customElements.get("hui-card-features-editor")||null===(t=customElements.get("hui-tile-card"))||void 0===t||t.getConfigElement(),customElements.get("ha-entity-picker")||null===(e=customElements.get("hui-entities-card"))||void 0===e||e.getConfigElement(),customElements.get("ha-card-conditions-editor")||null===(n=customElements.get("hui-conditional-card"))||void 0===n||n.getConfigElement()},qv=function(){var t=tr(Zi().m((function t(e){var n;return Zi().w((function(t){for(;;)switch(t.n){case 0:if(!(n=customElements.get(e))){t.n=1;break}return t.a(2,n);case 1:return t.n=2,customElements.whenDefined(e);case 2:return t.a(2,customElements.get(e))}}),t)})));return function(e){return t.apply(this,arguments)}}(),Wv=vv("conditional"),Xv=function(){var t=tr(Zi().m((function t(){var e,n;return Zi().w((function(t){for(;;)switch(t.n){case 0:if(!customElements.get(Wv)){t.n=1;break}return t.a(2);case 1:if(customElements.get("hui-conditional-base")){t.n=3;break}return t.n=2,window.loadCardHelpers();case 2:t.v.createCardElement({type:"conditional",card:{type:"button"},conditions:[]});case 3:return t.n=4,qv("hui-conditional-base");case 4:e=t.v,n=function(t){function e(){return hr(this,e),er(this,e,arguments)}return or(e,t),pr(e,[{key:"setConfig",value:function(t){if(this.validateConfig(t),!t.chip)throw new Error("No chip configured");this._element=mv(t.chip)}}],[{key:"getConfigElement",value:(o=tr(Zi().m((function t(){return Zi().w((function(t){for(;;)switch(t.n){case 0:return t.n=1,Promise.resolve().then((function(){return kC}));case 1:return t.a(2,document.createElement(gv("conditional")))}}),t)}))),function(){return o.apply(this,arguments)})},{key:"getStubConfig",value:(n=tr(Zi().m((function t(){return Zi().w((function(t){for(;;)if(0===t.n)return t.a(2,{type:"conditional",conditions:[]})}),t)}))),function(){return n.apply(this,arguments)})}]);var n,o}(e),customElements.get(Wv)||customElements.define(Wv,n);case 5:return t.a(2)}}),t)})));return function(){return t.apply(this,arguments)}}();function Zv(t){return null!=t.attributes.rgb_color?t.attributes.rgb_color:void 0}function Jv(t){var e={mode:"rgb",r:t[0]/255,g:t[1]/255,b:t[2]/255},n=Vm(e);return((null==n?void 0:n.l)||0)>96}function Qv(t){var e={mode:"rgb",r:t[0]/255,g:t[1]/255,b:t[2]/255},n=Vm(e);return((null==n?void 0:n.l)||0)>97}function tg(t){return function(t){var e;return(null===(e=t.attributes.supported_color_modes)||void 0===e?void 0:e.some((function(t){return $s.includes(t)})))||!1}(t)}function eg(t){return function(t){var e;return(null===(e=t.attributes.supported_color_modes)||void 0===e?void 0:e.some((function(t){return Gs.includes(t)})))||!1}(t)}var ng=function(t){function e(){return hr(this,e),er(this,e,arguments)}return or(e,za),pr(e,[{key:"setConfig",value:function(t){this._config=Object.assign({tap_action:{action:"toggle"},hold_action:{action:"more-info"}},t)}},{key:"_handleAction",value:function(t){rl(this,this.hass,this._config,t.detail.action)}},{key:"render",value:function(){var t,e;if(!this.hass||!this._config||!this._config.entity)return fa;var n=this._config.entity,o=this.hass.states[n];if(!o)return fa;var i=this._config.name||o.attributes.friendly_name||"",r=this._config.icon,a=this.hass.formatEntityState(o),s=Rs(o),l=Zv(o),c={};if(l&&(null===(t=this._config)||void 0===t?void 0:t.use_light_color)){var u=l.join(",");c["--color"]="rgb(".concat(u,")"),Qv(l)&&(c["--color"]="rgba(var(--rgb-primary-text-color), 0.2)")}var h=ev(null!==(e=this._config.content_info)&&void 0!==e?e:"state",i,a,o,this.hass),d=Is(this.hass);return ha(Ot||(Ot=Li(["\n \n \n ","\n \n "])),d,this._handleAction,il({hasHold:al(this._config.hold_action),hasDoubleClick:al(this._config.double_tap_action)}),this.hass,o,r,Wa(c),Ka({active:s}),h?ha(It||(It=Li(["",""])),h):fa)}}],[{key:"getConfigElement",value:(o=tr(Zi().m((function t(){return Zi().w((function(t){for(;;)switch(t.n){case 0:return t.n=1,Promise.resolve().then((function(){return MC}));case 1:return t.a(2,document.createElement(gv("light")))}}),t)}))),function(){return o.apply(this,arguments)})},{key:"getStubConfig",value:(n=tr(Zi().m((function t(e){var n,o;return Zi().w((function(t){for(;;)if(0===t.n)return n=Object.keys(e.states),o=n.filter((function(t){return"light"===t.split(".")[0]})),t.a(2,{type:"light",entity:o[0]})}),t)}))),function(t){return n.apply(this,arguments)})},{key:"styles",get:function(){return Tr(jt||(jt=Li(["\n :host {\n --color: rgb(var(--rgb-state-light));\n }\n mushroom-chip {\n cursor: pointer;\n }\n ha-state-icon.active {\n color: var(--color);\n }\n "])))}}]);var n,o}();br([Na({attribute:!1})],ng.prototype,"hass",void 0),br([Ba()],ng.prototype,"_config",void 0),ng=br([Ia(vv("light"))],ng);var og=function(t){function e(){return hr(this,e),er(this,e,arguments)}return or(e,za),pr(e,[{key:"setConfig",value:function(t){this._config=t}},{key:"_handleAction",value:function(t){rl(this,this.hass,this._config,t.detail.action)}},{key:"render",value:function(){var t;if(!this.hass||!this._config||!this._config.entity)return fa;var e=this._config.entity,n=this.hass.states[e];if(!n)return fa;var o=this._config.name||n.attributes.friendly_name||"",i=this._config.icon,r=hv(n.state),a=dv(n.state),s=this.hass.formatEntityState(n),l={};if(r){var c=Gm(r);l["--color"]="rgb(".concat(c,")")}var u=ev(null!==(t=this._config.content_info)&&void 0!==t?t:"state",o,s,n,this.hass),h=Is(this.hass);return ha(Pt||(Pt=Li(["\n \n \n ","\n \n "])),h,this._handleAction,il({hasHold:al(this._config.hold_action),hasDoubleClick:al(this._config.double_tap_action)}),this.hass,n,i,Wa(l),Ka({pulse:a}),u?ha(Nt||(Nt=Li(["",""])),u):fa)}}],[{key:"getConfigElement",value:(o=tr(Zi().m((function t(){return Zi().w((function(t){for(;;)switch(t.n){case 0:return t.n=1,Promise.resolve().then((function(){return jC}));case 1:return t.a(2,document.createElement(gv("alarm-control-panel")))}}),t)}))),function(){return o.apply(this,arguments)})},{key:"getStubConfig",value:(n=tr(Zi().m((function t(e){var n,o;return Zi().w((function(t){for(;;)if(0===t.n)return n=Object.keys(e.states),o=n.filter((function(t){return cv.includes(t.split(".")[0])})),t.a(2,{type:"alarm-control-panel",entity:o[0]})}),t)}))),function(t){return n.apply(this,arguments)})},{key:"styles",get:function(){return Tr(Bt||(Bt=Li(["\n mushroom-chip {\n cursor: pointer;\n }\n ha-state-icon {\n color: var(--color);\n }\n ha-state-icon.pulse {\n animation: 1s ease 0s infinite normal none running pulse;\n }\n ","\n "])),Al.pulse)}}]);var n,o}();br([Na({attribute:!1})],og.prototype,"hass",void 0),br([Ba()],og.prototype,"_config",void 0),og=br([Ia(vv("alarm-control-panel"))],og);var ig=function(t){function e(){return hr(this,e),er(this,e,arguments)}return or(e,za),pr(e,[{key:"setConfig",value:function(){}}],[{key:"styles",get:function(){return Tr(Lt||(Lt=Li(["\n :host {\n flex-grow: 1;\n }\n "])))}}])}();ig=br([Ia(vv("spacer"))],ig);var rg="".concat(av,"-chips-card"),ag="".concat(rg,"-editor");rv({type:rg,name:"Mushroom Chips Card",description:"Card with chips to display informations"});var sg=function(t){function e(){return hr(this,e),er(this,e,arguments)}return or(e,za),pr(e,[{key:"hass",set:function(t){var e,n=Xm(this._hass),o=Xm(t);n!==o&&this.toggleAttribute("dark-mode",o),this._hass=t,null===(e=this.shadowRoot)||void 0===e||e.querySelectorAll("div > *").forEach((function(e){e.hass=t}))}},{key:"getCardSize",value:function(){return 1}},{key:"setConfig",value:function(t){this._config=t}},{key:"render",value:function(){var t=this;if(!this._config||!this._hass)return fa;var e="";this._config.alignment&&(e="align-".concat(this._config.alignment));var n=Is(this._hass);return ha(Ht||(Ht=Li(['\n \n
\n \n \n \n \n \n \n \n
\n '])),this._decrementValue,this.disabled,Ka({value:!0,pending:this.pending,disabled:this.disabled}),t,this._incrementValue,this.disabled)}}],[{key:"styles",get:function(){return Tr($t||($t=Li(["\n :host {\n --text-color: var(--primary-text-color);\n --text-color-disabled: rgb(var(--rgb-disabled));\n --icon-color: var(--primary-text-color);\n --icon-color-disabled: rgb(var(--rgb-disabled));\n --bg-color: rgba(var(--rgb-primary-text-color), 0.05);\n --bg-color-disabled: rgba(var(--rgb-disabled), 0.2);\n height: var(--control-height);\n width: calc(var(--control-height) * var(--control-button-ratio) * 3);\n flex: none;\n }\n .container {\n box-sizing: border-box;\n width: 100%;\n height: 100%;\n padding: 6px;\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: center;\n border-radius: var(--control-border-radius);\n border: none;\n background-color: var(--bg-color);\n transition: background-color 280ms ease-in-out;\n height: var(--control-height);\n overflow: hidden;\n }\n .button {\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: center;\n padding: 4px;\n border: none;\n background: none;\n cursor: pointer;\n border-radius: var(--control-border-radius);\n line-height: 0;\n height: 100%;\n }\n .minus {\n padding-right: 0;\n }\n .plus {\n padding-left: 0;\n }\n .button:disabled {\n cursor: not-allowed;\n }\n .button ha-icon {\n font-size: var(--control-height);\n --mdc-icon-size: var(--control-icon-size);\n color: var(--icon-color);\n pointer-events: none;\n }\n .button:disabled ha-icon {\n color: var(--icon-color-disabled);\n }\n .value {\n text-align: center;\n flex-grow: 1;\n flex-shrink: 0;\n flex-basis: 20px;\n font-weight: bold;\n color: var(--text-color);\n }\n .value.disabled {\n color: var(--text-color-disabled);\n }\n .value.pending {\n opacity: 0.5;\n }\n "])))}}])}();br([Na({attribute:!1})],gg.prototype,"locale",void 0),br([Na({type:Boolean})],gg.prototype,"disabled",void 0),br([Na({attribute:!1,type:Number,reflect:!0})],gg.prototype,"value",void 0),br([Na({type:Number})],gg.prototype,"step",void 0),br([Na({type:Number})],gg.prototype,"min",void 0),br([Na({type:Number})],gg.prototype,"max",void 0),br([Na({attribute:!1})],gg.prototype,"formatOptions",void 0),br([Ba()],gg.prototype,"pending",void 0),br([La("#container")],gg.prototype,"container",void 0),gg=br([Ia("mushroom-input-number")],gg);var _g=function(t){function e(){var t;return hr(this,e),(t=er(this,e,arguments)).fill=!1,t}return or(e,za),pr(e,[{key:"_stepSize",get:function(){return this.entity.attributes.target_temp_step?this.entity.attributes.target_temp_step:"°F"===this.hass.config.unit_system.temperature?1:.5}},{key:"_supportsTarget",value:function(){return"climate"===Qa(this.entity)&&ts(this.entity,1)}},{key:"_supportsTargetRange",value:function(){return"climate"===Qa(this.entity)&&ts(this.entity,2)}},{key:"onValueChange",value:function(t){var e=t.detail.value;this.hass.callService("climate","set_temperature",{entity_id:this.entity.entity_id,temperature:e})}},{key:"onLowValueChange",value:function(t){var e=t.detail.value;this.hass.callService("climate","set_temperature",{entity_id:this.entity.entity_id,target_temp_low:e,target_temp_high:this.entity.attributes.target_temp_high})}},{key:"onHighValueChange",value:function(t){var e=t.detail.value;this.hass.callService("climate","set_temperature",{entity_id:this.entity.entity_id,target_temp_low:this.entity.attributes.target_temp_low,target_temp_high:e})}},{key:"render",value:function(){var t=Is(this.hass),e=Us(this.entity),n=1===this._stepSize?{maximumFractionDigits:0}:{minimumFractionDigits:1,maximumFractionDigits:1},o=function(t){return{"--bg-color":"rgba(var(--rgb-state-climate-".concat(t,"), 0.05)"),"--icon-color":"rgb(var(--rgb-state-climate-".concat(t,"))"),"--text-color":"rgb(var(--rgb-state-climate-".concat(t,"))")}},i=this._supportsTarget(),r=this._supportsTargetRange(),a=null!=this.entity.attributes.temperature,s=null!=this.entity.attributes.target_temp_low&&null!=this.entity.attributes.target_temp_high,l=i&&a,c=!l&&r&&s;return ha(Gt||(Gt=Li(["\n \n ","\n ","\n \n "])),this.fill,t,l?ha(Kt||(Kt=Li(["\n \n "])),this.hass.locale,this.entity.attributes.temperature,this._stepSize,this.entity.attributes.min_temp,this.entity.attributes.max_temp,!e,n,this.onValueChange):fa,c?ha(Yt||(Yt=Li(["\n \n "])),Wa(o("heat")),this.hass.locale,this.entity.attributes.target_temp_low,this._stepSize,this.entity.attributes.min_temp,this.entity.attributes.max_temp,!e,n,this.onLowValueChange,Wa(o("cool")),this.hass.locale,this.entity.attributes.target_temp_high,this._stepSize,this.entity.attributes.min_temp,this.entity.attributes.max_temp,!e,n,this.onHighValueChange):fa)}}])}();br([Na({attribute:!1})],_g.prototype,"hass",void 0),br([Na({attribute:!1})],_g.prototype,"entity",void 0),br([Na()],_g.prototype,"fill",void 0),_g=br([Ia("mushroom-climate-temperature-control")],_g);var yg={temperature_control:"mdi:thermometer",hvac_mode_control:"mdi:thermostat"};rv({type:lg,name:"Mushroom Climate Card",description:"Card for climate entity"});var bg=function(t){function e(){return hr(this,e),er(this,e,arguments)}return or(e,ov),pr(e,[{key:"_controls",get:function(){if(!this._config||!this._stateObj)return[];var t,e=this._stateObj,n=[];return(null!=(t=e).attributes.temperature||null!=t.attributes.target_temp_low&&null!=t.attributes.target_temp_high)&&this._config.show_temperature_control&&n.push("temperature_control"),function(t,e){return(t.attributes.hvac_modes||[]).some((function(t){return(null!=e?e:[]).includes(t)}))}(e,this._config.hvac_modes)&&n.push("hvac_mode_control"),n}},{key:"hasControls",get:function(){return this._controls.length>0}},{key:"_onControlTap",value:function(t,e){e.stopPropagation(),this._activeControl=t}},{key:"setConfig",value:function(t){$i(e,"setConfig",this,3)([Object.assign({tap_action:{action:"toggle"},hold_action:{action:"more-info"}},t)]),this.updateActiveControl()}},{key:"updated",value:function(t){$i(e,"updated",this,3)([t]),this.hass&&t.has("hass")&&this.updateActiveControl()}},{key:"updateActiveControl",value:function(){var t=!!this._activeControl&&this._controls.includes(this._activeControl);this._activeControl=t?this._activeControl:this._controls[0]}},{key:"_handleAction",value:function(t){rl(this,this.hass,this._config,t.detail.action)}},{key:"render",value:function(){if(!this.hass||!this._config||!this._config.entity)return fa;var t=this._stateObj;if(!t)return this.renderNotFound(this._config);var e=this._config.name||t.attributes.friendly_name||"",n=this._config.icon,o=Ol(this._config),i=nv(t,o.icon_type),r=this.hass.formatEntityState(t);if(null!==t.attributes.current_temperature){var a=this.hass.formatEntityAttributeValue(t,"current_temperature");r+=" ⸱ ".concat(a)}var s=Is(this.hass),l=(!this._config.collapsible_controls||Rs(t))&&this._controls.length;return ha(qt||(qt=Li(["\n \n \n \n ","\n ","\n ",";\n \n ","\n \n
\n "])),Ka({"fill-container":o.fill_container}),o,s,s,o,this._handleAction,il({hasHold:al(this._config.hold_action),hasDoubleClick:al(this._config.double_tap_action)}),i?this.renderPicture(i):this.renderIcon(t,n),this.renderBadge(t),this.renderStateInfo(t,o,e,r),l?ha(Wt||(Wt=Li(['\n
\n \n \n '])),!Us(this.entity),this._onStopTap):void 0,ts(this.entity,2)?ha(ae||(ae=Li(["\n \n \n \n "])),!Us(this.entity)||this.closedDisabled,this._onCloseTap,function(t){switch(t.attributes.device_class){case"awning":case"curtain":case"door":case"gate":return"mdi:arrow-collapse-horizontal";default:return"mdi:arrow-down"}}(this.entity)):void 0)}}])}();br([Na({attribute:!1})],Eg.prototype,"hass",void 0),br([Na({attribute:!1})],Eg.prototype,"entity",void 0),br([Na()],Eg.prototype,"fill",void 0),Eg=br([Ia("mushroom-cover-buttons-control")],Eg);var xg,Ag,Sg={exports:{}}; /*! Hammer.JS - v2.0.7 - 2016-04-22 * http://hammerjs.github.io/ * * Copyright (c) 2016 Jorik Tangelder; - * Licensed under the MIT license */xx||(xx=1,Ex=Cx,function(e,t,n,i){var o,r=["","webkit","Moz","MS","ms","o"],a=t.createElement("div"),l="function",s=Math.round,c=Math.abs,u=Date.now;function d(e,t,n){return setTimeout(_(e,n),t)}function h(e,t,n){return!!Array.isArray(e)&&(p(e,n[t],n),!0)}function p(e,t,n){var o;if(e)if(e.forEach)e.forEach(t,n);else if(e.length!==i)for(o=0;o\s*\(/gm,"{anonymous}()@"):"Unknown Stack Trace",r=e.console&&(e.console.warn||e.console.log);return r&&r.call(e.console,o,i),t.apply(this,arguments)}}o="function"!=typeof Object.assign?function(e){if(e===i||null===e)throw new TypeError("Cannot convert undefined or null to object");for(var t=Object(e),n=1;n-1}function C(e){return e.trim().split(/\s+/g)}function A(e,t,n){if(e.indexOf&&!n)return e.indexOf(t);for(var i=0;in[t]})),i}function I(e,t){for(var n,o,a=t[0].toUpperCase()+t.slice(1),l=0;l1&&!n.firstMultiple?n.firstMultiple=Q(t):1===r&&(n.firstMultiple=!1);var a=n.firstInput,l=n.firstMultiple,s=l?l.center:a.center,d=t.center=ee(o);t.timeStamp=u(),t.deltaTime=t.timeStamp-a.timeStamp,t.angle=oe(s,d),t.distance=ie(s,d),function(e,t){var n=t.center,i=e.offsetDelta||{},o=e.prevDelta||{},r=e.prevInput||{};t.eventType!==B&&r.eventType!==R||(o=e.prevDelta={x:r.deltaX||0,y:r.deltaY||0},i=e.offsetDelta={x:n.x,y:n.y}),t.deltaX=o.x+(n.x-i.x),t.deltaY=o.y+(n.y-i.y)}(n,t),t.offsetDirection=ne(t.deltaX,t.deltaY);var h,p,f=te(t.deltaTime,t.deltaX,t.deltaY);t.overallVelocityX=f.x,t.overallVelocityY=f.y,t.overallVelocity=c(f.x)>c(f.y)?f.x:f.y,t.scale=l?(h=l.pointers,ie((p=o)[0],p[1],X)/ie(h[0],h[1],X)):1,t.rotation=l?function(e,t){return oe(t[1],t[0],X)+oe(e[1],e[0],X)}(l.pointers,o):0,t.maxPointers=n.prevInput?t.pointers.length>n.prevInput.maxPointers?t.pointers.length:n.prevInput.maxPointers:t.pointers.length,function(e,t){var n,o,r,a,l=e.lastInterval||t,s=t.timeStamp-l.timeStamp;if(t.eventType!=H&&(s>$||l.velocity===i)){var u=t.deltaX-l.deltaX,d=t.deltaY-l.deltaY,h=te(s,u,d);o=h.x,r=h.y,n=c(h.x)>c(h.y)?h.x:h.y,a=ne(u,d),e.lastInterval=t}else n=l.velocity,o=l.velocityX,r=l.velocityY,a=l.direction;t.velocity=n,t.velocityX=o,t.velocityY=r,t.direction=a}(n,t);var m=e.element;x(t.srcEvent.target,m)&&(m=t.srcEvent.target),t.target=m}(e,n),e.emit("hammer.input",n),e.recognize(n),e.session.prevInput=n}function Q(e){for(var t=[],n=0;n=c(t)?e<0?U:V:t<0?F:G}function ie(e,t,n){n||(n=q);var i=t[n[0]]-e[n[0]],o=t[n[1]]-e[n[1]];return Math.sqrt(i*i+o*o)}function oe(e,t,n){n||(n=q);var i=t[n[0]]-e[n[0]],o=t[n[1]]-e[n[1]];return 180*Math.atan2(o,i)/Math.PI}Z.prototype={handler:function(){},init:function(){this.evEl&&k(this.element,this.evEl,this.domHandler),this.evTarget&&k(this.target,this.evTarget,this.domHandler),this.evWin&&k(M(this.element),this.evWin,this.domHandler)},destroy:function(){this.evEl&&w(this.element,this.evEl,this.domHandler),this.evTarget&&w(this.target,this.evTarget,this.domHandler),this.evWin&&w(M(this.element),this.evWin,this.domHandler)}};var re={mousedown:B,mousemove:2,mouseup:R},ae="mousedown",le="mousemove mouseup";function se(){this.evEl=ae,this.evWin=le,this.pressed=!1,Z.apply(this,arguments)}g(se,Z,{handler:function(e){var t=re[e.type];t&B&&0===e.button&&(this.pressed=!0),2&t&&1!==e.which&&(t=R),this.pressed&&(t&R&&(this.pressed=!1),this.callback(this.manager,t,{pointers:[e],changedPointers:[e],pointerType:j,srcEvent:e}))}});var ce={pointerdown:B,pointermove:2,pointerup:R,pointercancel:H,pointerout:H},ue={2:N,3:"pen",4:j,5:"kinect"},de="pointerdown",he="pointermove pointerup pointercancel";function pe(){this.evEl=de,this.evWin=he,Z.apply(this,arguments),this.store=this.manager.session.pointerEvents=[]}e.MSPointerEvent&&!e.PointerEvent&&(de="MSPointerDown",he="MSPointerMove MSPointerUp MSPointerCancel"),g(pe,Z,{handler:function(e){var t=this.store,n=!1,i=e.type.toLowerCase().replace("ms",""),o=ce[i],r=ue[e.pointerType]||e.pointerType,a=r==N,l=A(t,e.pointerId,"pointerId");o&B&&(0===e.button||a)?l<0&&(t.push(e),l=t.length-1):o&(R|H)&&(n=!0),l<0||(t[l]=e,this.callback(this.manager,o,{pointers:t,changedPointers:[e],pointerType:r,srcEvent:e}),n&&t.splice(l,1))}});var fe={touchstart:B,touchmove:2,touchend:R,touchcancel:H};function me(){this.evTarget="touchstart",this.evWin="touchstart touchmove touchend touchcancel",this.started=!1,Z.apply(this,arguments)}function ve(e,t){var n=S(e.touches),i=S(e.changedTouches);return t&(R|H)&&(n=T(n.concat(i),"identifier")),[n,i]}g(me,Z,{handler:function(e){var t=fe[e.type];if(t===B&&(this.started=!0),this.started){var n=ve.call(this,e,t);t&(R|H)&&n[0].length-n[1].length==0&&(this.started=!1),this.callback(this.manager,t,{pointers:n[0],changedPointers:n[1],pointerType:N,srcEvent:e})}}});var ge={touchstart:B,touchmove:2,touchend:R,touchcancel:H},_e="touchstart touchmove touchend touchcancel";function ye(){this.evTarget=_e,this.targetIds={},Z.apply(this,arguments)}function be(e,t){var n=S(e.touches),i=this.targetIds;if(t&(2|B)&&1===n.length)return i[n[0].identifier]=!0,[n,n];var o,r,a=S(e.changedTouches),l=[],s=this.target;if(r=n.filter((function(e){return x(e.target,s)})),t===B)for(o=0;o-1&&i.splice(e,1)}),ke)}}function Ce(e){for(var t=e.srcEvent.clientX,n=e.srcEvent.clientY,i=0;i-1&&this.requireFail.splice(t,1),this},hasRequireFailures:function(){return this.requireFail.length>0},canRecognizeWith:function(e){return!!this.simultaneous[e.id]},emit:function(e){var t=this,n=this.state;function i(n){t.manager.emit(n,e)}n<8&&i(t.options.event+Re(n)),i(t.options.event),e.additionalEvent&&i(e.additionalEvent),n>=8&&i(t.options.event+Re(n))},tryEmit:function(e){if(this.canEmit())return this.emit(e);this.state=$e},canEmit:function(){for(var e=0;et.threshold&&o&t.direction},attrTest:function(e){return Ue.prototype.attrTest.call(this,e)&&(2&this.state||!(2&this.state)&&this.directionTest(e))},emit:function(e){this.pX=e.deltaX,this.pY=e.deltaY;var t=He(e.direction);t&&(e.additionalEvent=this.options.event+t),this._super.emit.call(this,e)}}),g(Fe,Ue,{defaults:{event:"pinch",threshold:0,pointers:2},getTouchAction:function(){return[Me]},attrTest:function(e){return this._super.attrTest.call(this,e)&&(Math.abs(e.scale-1)>this.options.threshold||2&this.state)},emit:function(e){if(1!==e.scale){var t=e.scale<1?"in":"out";e.additionalEvent=this.options.event+t}this._super.emit.call(this,e)}}),g(Ge,Be,{defaults:{event:"press",pointers:1,time:251,threshold:9},getTouchAction:function(){return[Ie]},process:function(e){var t=this.options,n=e.pointers.length===t.pointers,i=e.distancet.time;if(this._input=e,!i||!n||e.eventType&(R|H)&&!o)this.reset();else if(e.eventType&B)this.reset(),this._timer=d((function(){this.state=8,this.tryEmit()}),t.time,this);else if(e.eventType&R)return 8;return $e},reset:function(){clearTimeout(this._timer)},emit:function(e){8===this.state&&(e&&e.eventType&R?this.manager.emit(this.options.event+"up",e):(this._input.timeStamp=u(),this.manager.emit(this.options.event,this._input)))}}),g(Ye,Ue,{defaults:{event:"rotate",threshold:0,pointers:2},getTouchAction:function(){return[Me]},attrTest:function(e){return this._super.attrTest.call(this,e)&&(Math.abs(e.rotation)>this.options.threshold||2&this.state)}}),g(Ke,Ue,{defaults:{event:"swipe",threshold:10,velocity:.3,direction:Y|K,pointers:1},getTouchAction:function(){return Ve.prototype.getTouchAction.call(this)},attrTest:function(e){var t,n=this.options.direction;return n&(Y|K)?t=e.overallVelocity:n&Y?t=e.overallVelocityX:n&K&&(t=e.overallVelocityY),this._super.attrTest.call(this,e)&&n&e.offsetDirection&&e.distance>this.options.threshold&&e.maxPointers==this.options.pointers&&c(t)>this.options.velocity&&e.eventType&R},emit:function(e){var t=He(e.offsetDirection);t&&this.manager.emit(this.options.event+t,e),this.manager.emit(this.options.event,e)}}),g(We,Be,{defaults:{event:"tap",pointers:1,taps:1,interval:300,time:250,threshold:9,posThreshold:10},getTouchAction:function(){return[Oe]},process:function(e){var t=this.options,n=e.pointers.length===t.pointers,i=e.distance\n
\n ',"\n ","\n
\n
\n "])),Mk({container:!0,inactive:this.inactive||this.disabled,controlled:this.controlled}),Hg({"--value":"".concat(this.valueToPercentage(null!==(e=this.value)&&void 0!==e?e:0))}),this.showActive?cl(Dt||(Dt=Pr(['
']))):hl,this.showIndicator?cl(Ut||(Ut=Pr(['
']))):hl)}}],[{key:"styles",get:function(){return Aa(Vt||(Vt=Pr(['\n :host {\n --main-color: rgba(var(--rgb-secondary-text-color), 1);\n --bg-gradient: none;\n --bg-color: rgba(var(--rgb-secondary-text-color), 0.2);\n --main-color-inactive: rgb(var(--rgb-disabled));\n --bg-color-inactive: rgba(var(--rgb-disabled), 0.2);\n }\n .container {\n display: flex;\n flex-direction: row;\n height: var(--control-height);\n }\n .slider {\n position: relative;\n height: 100%;\n width: 100%;\n border-radius: var(--control-border-radius);\n transform: translateZ(0);\n overflow: hidden;\n cursor: pointer;\n }\n .slider * {\n pointer-events: none;\n }\n .slider .slider-track-background {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n width: 100%;\n background-color: var(--bg-color);\n background-image: var(--gradient);\n }\n .slider .slider-track-active {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n width: 100%;\n transform: scale3d(var(--value, 0), 1, 1);\n transform-origin: left;\n background-color: var(--main-color);\n transition: transform 180ms ease-in-out;\n }\n .slider .slider-track-indicator {\n position: absolute;\n top: 0;\n bottom: 0;\n left: calc(var(--value, 0) * (100% - 10px));\n width: 10px;\n border-radius: 3px;\n background-color: white;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);\n transition: left 180ms ease-in-out;\n }\n .slider .slider-track-indicator:after {\n display: block;\n content: "";\n background-color: var(--main-color);\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n right: 0;\n margin: auto;\n height: 20px;\n width: 2px;\n border-radius: 1px;\n }\n .inactive .slider .slider-track-background {\n background-color: var(--bg-color-inactive);\n background-image: none;\n }\n .inactive .slider .slider-track-indicator:after {\n background-color: var(--main-color-inactive);\n }\n .inactive .slider .slider-track-active {\n background-color: var(--main-color-inactive);\n }\n .controlled .slider .slider-track-active {\n transition: none;\n }\n .controlled .slider .slider-track-indicator {\n transition: none;\n }\n '])))}}])}();function Tx(e){return null!=e.attributes.current_position?Math.round(e.attributes.current_position):void 0}function Ix(e){var t=e.state;return"open"===t||"opening"===t?"var(--rgb-state-cover-open)":"closed"===t||"closing"===t?"var(--rgb-state-cover-closed)":"var(--rgb-disabled)"}ga([zl({type:Boolean})],Sx.prototype,"disabled",void 0),ga([zl({type:Boolean})],Sx.prototype,"inactive",void 0),ga([zl({type:Boolean,attribute:"show-active"})],Sx.prototype,"showActive",void 0),ga([zl({type:Boolean,attribute:"show-indicator"})],Sx.prototype,"showIndicator",void 0),ga([zl({attribute:!1,type:Number,reflect:!0})],Sx.prototype,"value",void 0),ga([zl({type:Number})],Sx.prototype,"step",void 0),ga([zl({type:Number})],Sx.prototype,"min",void 0),ga([zl({type:Number})],Sx.prototype,"max",void 0),ga([Pl()],Sx.prototype,"controlled",void 0),ga([Ll("#slider")],Sx.prototype,"slider",void 0),Sx=ga([Il("mushroom-slider")],Sx);var Ox=function(e){function t(){return sa(this,t),Zr(this,t,arguments)}return ea(t,Sl),ua(t,[{key:"onChange",value:function(e){var t=e.detail.value;this.hass.callService("cover","set_cover_position",{entity_id:this.entity.entity_id,position:t})}},{key:"onCurrentChange",value:function(e){var t=e.detail.value;this.dispatchEvent(new CustomEvent("current-change",{detail:{value:t}}))}},{key:"render",value:function(){var e=Tx(this.entity);return cl(Ft||(Ft=Pr(["\n \n "])),e,!Ms(this.entity),!0,this.onChange,this.onCurrentChange)}}],[{key:"styles",get:function(){return Aa(Gt||(Gt=Pr(["\n mushroom-slider {\n --main-color: var(--slider-color);\n --bg-color: var(--slider-bg-color);\n }\n "])))}}])}();ga([zl({attribute:!1})],Ox.prototype,"hass",void 0),ga([zl({attribute:!1})],Ox.prototype,"entity",void 0),Ox=ga([Il("mushroom-cover-position-control")],Ox);var Mx=function(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:24,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:.2,n=[],i=0;i\n "])),t,!Ms(this.entity),!0,this.onChange,this.onCurrentChange)}}],[{key:"styles",get:function(){var e=Mx.map((function(e){var t=Fr(e,2),n=t[0],i=t[1];return"".concat(i," ").concat(100*n,"%")})).join(", ");return Aa(Kt||(Kt=Pr(["\n mushroom-slider {\n --main-color: var(--slider-color);\n --bg-color: var(--slider-bg-color);\n --gradient: -webkit-linear-gradient(right, ",");\n }\n "])),Ca(e))}}])}();ga([zl({attribute:!1})],zx.prototype,"hass",void 0),ga([zl({attribute:!1})],zx.prototype,"entity",void 0),zx=ga([Il("mushroom-cover-tilt-position-control")],zx);var Px={buttons_control:"mdi:gesture-tap-button",position_control:"mdi:gesture-swipe-horizontal",tilt_position_control:"mdi:rotate-right"};iw({type:yx,name:"Mushroom Cover Card",description:"Card for cover entity"});var Lx=function(e){function t(){return sa(this,t),Zr(this,t,arguments)}return ea(t,tw),ua(t,[{key:"hasControls",get:function(){return this._controls.length>0}},{key:"_nextControl",get:function(){var e;if(this._activeControl)return null!==(e=this._controls[this._controls.indexOf(this._activeControl)+1])&&void 0!==e?e:this._controls[0]}},{key:"_onNextControlTap",value:function(e){e.stopPropagation(),this._activeControl=this._nextControl}},{key:"getCardSize",value:function(){return 1}},{key:"setConfig",value:function(e){Hr(t,"setConfig",this,3)([Object.assign({tap_action:{action:"toggle"},hold_action:{action:"more-info"}},e)]),this.updateActiveControl(),this.updatePosition()}},{key:"_controls",get:function(){if(!this._config||!this._stateObj)return[];var e=[];return this._config.show_buttons_control&&e.push("buttons_control"),this._config.show_position_control&&e.push("position_control"),this._config.show_tilt_position_control&&e.push("tilt_position_control"),e}},{key:"updateActiveControl",value:function(){var e=!!this._activeControl&&this._controls.includes(this._activeControl);this._activeControl=e?this._activeControl:this._controls[0]}},{key:"updated",value:function(e){Hr(t,"updated",this,3)([e]),this.hass&&e.has("hass")&&(this.updatePosition(),this.updateActiveControl())}},{key:"updatePosition",value:function(){this.position=void 0;var e=this._stateObj;e&&(this.position=Tx(e))}},{key:"onCurrentPositionChange",value:function(e){null!=e.detail.value&&(this.position=e.detail.value)}},{key:"_handleAction",value:function(e){Xs(this,this.hass,this._config,e.detail.action)}},{key:"render",value:function(){if(!this.hass||!this._config||!this._config.entity)return hl;var e=this._stateObj;if(!e)return this.renderNotFound(this._config);var t=this._config.name||e.attributes.friendly_name||"",n=this._config.icon,i=Gk(this._config),o=wk(e,i.icon_type),r=this.hass.formatEntityState(e);if(this.position){var a=this.hass.formatEntityAttributeValue(e,"current_position",this.position);r+=" ⸱ ".concat(a)}var l=bs(this.hass);return cl(Wt||(Wt=Pr(["\n \n \n \n ","\n ","\n ",";\n \n ","\n \n \n "])),Mk({"fill-container":i.fill_container}),i,l,l,i,this._handleAction,qs({hasHold:Zs(this._config.hold_action),hasDoubleClick:Zs(this._config.double_tap_action)}),o?this.renderPicture(o):this.renderIcon(e,n),this.renderBadge(e),this.renderStateInfo(e,i,t,r),this._controls.length>0?cl(qt||(qt=Pr(['\n
\n \n \n ']))):hl}}],[{key:"getConfigElement",value:(n=Xr(Kr().m((function e(){return Kr().w((function(e){for(;;)switch(e.n){case 0:return e.n=1,Promise.resolve().then((function(){return zM}));case 1:return e.a(2,document.createElement(jx))}}),e)}))),function(){return n.apply(this,arguments)})},{key:"styles",get:function(){return[Hr(t,"styles",this),Aa(on||(on=Pr(["\n :host {\n display: block;\n height: 100%;\n }\n\n ha-card {\n background: none;\n height: 100%;\n min-height: 56px;\n display: flex;\n justify-content: center;\n align-items: center;\n --mdc-icon-size: 40px;\n --icon-primary-color: var(--divider-color, rgba(0, 0, 0, 0.12));\n }\n "])))]}}]);var n}();ga([zl({type:Boolean})],$x.prototype,"preview",void 0),$x=ga([Il(Nx)],$x);var Bx="".concat(ow,"-entity-card"),Rx="".concat(Bx,"-editor");iw({type:Bx,name:"Mushroom Entity Card",description:"Card for all entities"});var Hx=function(e){function t(){return sa(this,t),Zr(this,t,arguments)}return ea(t,tw),ua(t,[{key:"_handleAction",value:function(e){Xs(this,this.hass,this._config,e.detail.action)}},{key:"render",value:function(){if(!this._config||!this.hass||!this._config.entity)return hl;var e=this._stateObj;if(!e)return this.renderNotFound(this._config);var t=this._config.name||e.attributes.friendly_name||"",n=this._config.icon,i=Gk(this._config),o=wk(e,i.icon_type),r=bs(this.hass);return cl(rn||(rn=Pr(["\n \n \n \n ","\n ","\n ",";\n \n \n \n "])),Mk({"fill-container":i.fill_container}),i,r,r,i,this._handleAction,qs({hasHold:Zs(this._config.hold_action),hasDoubleClick:Zs(this._config.double_tap_action)}),o?this.renderPicture(o):this.renderIcon(e,n),this.renderBadge(e),this.renderStateInfo(e,i,t))}},{key:"renderIcon",value:function(e,t){var n,i=Os(e),o={},r=null===(n=this._config)||void 0===n?void 0:n.icon_color;if(r){var a=pk(r);o["--icon-color"]="rgb(".concat(a,")"),o["--shape-color"]="rgba(".concat(a,", 0.2)")}return cl(an||(an=Pr(['\n ',"
"])),u):e?this.renderIcon(e,t):hl,(e||a)&&n?this.renderBadgeIcon(n,i):void 0,o,r,l)}},{key:"renderPicture",value:function(e){return cl(In||(In=Pr(['\n \n \n
\n \n '])),"volume_down",!Ms(this.entity)||zs(this.entity),this.handleClick):void 0,s?cl(ri||(ri=Pr(["\n \n '])),"volume_up",!Ms(this.entity)||zs(this.entity),this.handleClick):void 0)}}],[{key:"styles",get:function(){return Aa(ai||(ai=Pr(["\n mushroom-slider {\n flex: 1;\n --main-color: rgb(var(--rgb-state-media-player));\n --bg-color: rgba(var(--rgb-state-media-player), 0.2);\n }\n "])))}}])}();ga([zl({attribute:!1})],OE.prototype,"hass",void 0),ga([zl({attribute:!1})],OE.prototype,"entity",void 0),ga([zl({type:Boolean})],OE.prototype,"fill",void 0),ga([zl({attribute:!1})],OE.prototype,"controls",void 0),OE=ga([Il("mushroom-media-player-volume-control")],OE);var ME={media_control:"mdi:play-pause",volume_control:"mdi:volume-high"};iw({type:EE,name:"Mushroom Media Card",description:"Card for media player entity"});var zE=function(e){function t(){return sa(this,t),Zr(this,t,arguments)}return ea(t,tw),ua(t,[{key:"hasControls",get:function(){var e,t,n,i;return Boolean(null===(t=null===(e=this._config)||void 0===e?void 0:e.media_controls)||void 0===t?void 0:t.length)||Boolean(null===(i=null===(n=this._config)||void 0===n?void 0:n.volume_controls)||void 0===i?void 0:i.length)}},{key:"_controls",get:function(){if(!this._config||!this._stateObj)return[];var e=this._stateObj,t=[];return function(e,t){return SE(e,null!=t?t:[]).length>0}(e,this._config.media_controls)&&t.push("media_control"),function(e,t){return(null==t?void 0:t.includes("volume_buttons"))&&Vl(e,1024)||(null==t?void 0:t.includes("volume_mute"))&&Vl(e,8)||(null==t?void 0:t.includes("volume_set"))&&Vl(e,4)}(e,this._config.volume_controls)&&t.push("volume_control"),t}},{key:"_onControlTap",value:function(e,t){t.stopPropagation(),this._activeControl=e}},{key:"setConfig",value:function(e){Hr(t,"setConfig",this,3)([e]),this.updateActiveControl(),this.updateVolume()}},{key:"updated",value:function(e){Hr(t,"updated",this,3)([e]),this.hass&&e.has("hass")&&(this.updateActiveControl(),this.updateVolume())}},{key:"updateVolume",value:function(){this.volume=void 0;var e=this._stateObj;e&&(this.volume=e.attributes.volume_level)}},{key:"onCurrentVolumeChange",value:function(e){null!=e.detail.value&&(this.volume=e.detail.value/100)}},{key:"updateActiveControl",value:function(){var e=!!this._activeControl&&this._controls.includes(this._activeControl);this._activeControl=e?this._activeControl:this._controls[0]}},{key:"_handleAction",value:function(e){Xs(this,this.hass,this._config,e.detail.action)}},{key:"render",value:function(){if(!this._config||!this.hass||!this._config.entity)return hl;var e=this._stateObj;if(!e)return this.renderNotFound(this._config);var t,n,i,o=function(e,t){var n,i=e.icon;if(![As,Ss,Ts].includes(t.state)&&e.use_media_info)switch(null===(n=t.attributes.app_name)||void 0===n?void 0:n.toLowerCase()){case"spotify":return"mdi:spotify";case"google podcasts":return"mdi:google-podcast";case"plex":return"mdi:plex";case"soundcloud":return"mdi:soundcloud";case"youtube":return"mdi:youtube";case"oto music":return"mdi:music-circle";case"netflix":return"mdi:netflix";default:return}return i}(this._config,e),r=(t=this._config,n=e,i=t.name||n.attributes.friendly_name||"",![As,Ss,Ts].includes(n.state)&&t.use_media_info&&n.attributes.media_title&&(i=n.attributes.media_title),i),a=Gk(this._config),l=wk(e,a.icon_type),s=function(e,t,n){var i=n.formatEntityState(t);return![As,Ss,Ts].includes(t.state)&&e.use_media_info&&function(e){var t;switch(e.attributes.media_content_type){case"music":case"image":t=e.attributes.media_artist;break;case"playlist":t=e.attributes.media_playlist;break;case"tvshow":t=e.attributes.media_series_title,e.attributes.media_season&&(t+=" S"+e.attributes.media_season,e.attributes.media_episode&&(t+="E"+e.attributes.media_episode));break;default:t=e.attributes.app_name||""}return t}(t)||i}(this._config,e,this.hass);if(null!=this.volume&&this._config.show_volume_level){var c=this.hass.formatEntityAttributeValue(e,"volume_level",this.volume);s+=" ⸱ ".concat(c)}var u=bs(this.hass),d=(!this._config.collapsible_controls||Os(e))&&this._controls.length;return cl(li||(li=Pr(["\n \n \n \n ","\n ","\n ",";\n \n ","\n \n \n "])),Mk({"fill-container":a.fill_container}),a,u,u,a,this._handleAction,qs({hasHold:Zs(this._config.hold_action),hasDoubleClick:Zs(this._config.double_tap_action)}),l?this.renderPicture(l):this.renderIcon(e,o),this.renderBadge(e),this.renderStateInfo(e,a,r,s),d?cl(si||(si=Pr(['\n
\n
\n
\n \n
\n ',"\n ","\n
\n
\n "])),Ka({container:!0,inactive:this.inactive||this.disabled,controlled:this.controlled}),Wa({"--value":"".concat(this.valueToPercentage(null!==(t=this.value)&&void 0!==t?t:0))}),this.showActive?ha(le||(le=Li(['
']))):fa,this.showIndicator?ha(ce||(ce=Li(['
']))):fa)}}],[{key:"styles",get:function(){return Tr(ue||(ue=Li(['\n :host {\n --main-color: rgba(var(--rgb-secondary-text-color), 1);\n --bg-gradient: none;\n --bg-color: rgba(var(--rgb-secondary-text-color), 0.2);\n --main-color-inactive: rgb(var(--rgb-disabled));\n --bg-color-inactive: rgba(var(--rgb-disabled), 0.2);\n }\n .container {\n display: flex;\n flex-direction: row;\n height: var(--control-height);\n }\n .slider {\n position: relative;\n height: 100%;\n width: 100%;\n border-radius: var(--control-border-radius);\n transform: translateZ(0);\n overflow: hidden;\n cursor: pointer;\n }\n .slider * {\n pointer-events: none;\n }\n .slider .slider-track-background {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n width: 100%;\n background-color: var(--bg-color);\n background-image: var(--gradient);\n }\n .slider .slider-track-active {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n width: 100%;\n transform: scale3d(var(--value, 0), 1, 1);\n transform-origin: left;\n background-color: var(--main-color);\n transition: transform 180ms ease-in-out;\n }\n .slider .slider-track-indicator {\n position: absolute;\n top: 0;\n bottom: 0;\n left: calc(var(--value, 0) * (100% - 10px));\n width: 10px;\n border-radius: 3px;\n background-color: white;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);\n transition: left 180ms ease-in-out;\n }\n .slider .slider-track-indicator:after {\n display: block;\n content: "";\n background-color: var(--main-color);\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n right: 0;\n margin: auto;\n height: 20px;\n width: 2px;\n border-radius: 1px;\n }\n .inactive .slider .slider-track-background {\n background-color: var(--bg-color-inactive);\n background-image: none;\n }\n .inactive .slider .slider-track-indicator:after {\n background-color: var(--main-color-inactive);\n }\n .inactive .slider .slider-track-active {\n background-color: var(--main-color-inactive);\n }\n .controlled .slider .slider-track-active {\n transition: none;\n }\n .controlled .slider .slider-track-indicator {\n transition: none;\n }\n '])))}}])}();function zg(t){return null!=t.attributes.current_position?Math.round(t.attributes.current_position):void 0}function Og(t){var e=t.state;return"open"===e||"opening"===e?"var(--rgb-state-cover-open)":"closed"===e||"closing"===e?"var(--rgb-state-cover-closed)":"var(--rgb-disabled)"}br([Na({type:Boolean})],Mg.prototype,"disabled",void 0),br([Na({type:Boolean})],Mg.prototype,"inactive",void 0),br([Na({type:Boolean,attribute:"show-active"})],Mg.prototype,"showActive",void 0),br([Na({type:Boolean,attribute:"show-indicator"})],Mg.prototype,"showIndicator",void 0),br([Na({attribute:!1,type:Number,reflect:!0})],Mg.prototype,"value",void 0),br([Na({type:Number})],Mg.prototype,"step",void 0),br([Na({type:Number})],Mg.prototype,"min",void 0),br([Na({type:Number})],Mg.prototype,"max",void 0),br([Ba()],Mg.prototype,"controlled",void 0),br([La("#slider")],Mg.prototype,"slider",void 0),Mg=br([Ia("mushroom-slider")],Mg);var Ig=function(t){function e(){return hr(this,e),er(this,e,arguments)}return or(e,za),pr(e,[{key:"onChange",value:function(t){var e=t.detail.value;this.hass.callService("cover","set_cover_position",{entity_id:this.entity.entity_id,position:e})}},{key:"onCurrentChange",value:function(t){var e=t.detail.value;this.dispatchEvent(new CustomEvent("current-change",{detail:{value:e}}))}},{key:"render",value:function(){var t=zg(this.entity);return ha(he||(he=Li(["\n \n "])),t,!Us(this.entity),!0,this.onChange,this.onCurrentChange)}}],[{key:"styles",get:function(){return Tr(de||(de=Li(["\n mushroom-slider {\n --main-color: var(--slider-color);\n --bg-color: var(--slider-bg-color);\n }\n "])))}}])}();br([Na({attribute:!1})],Ig.prototype,"hass",void 0),br([Na({attribute:!1})],Ig.prototype,"entity",void 0),Ig=br([Ia("mushroom-cover-position-control")],Ig);var jg=function(){for(var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:24,e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:.2,n=[],o=0;o\n "])),e,!Us(this.entity),!0,this.onChange,this.onCurrentChange)}}],[{key:"styles",get:function(){var t=jg.map((function(t){var e=qi(t,2),n=e[0],o=e[1];return"".concat(o," ").concat(100*n,"%")})).join(", ");return Tr(fe||(fe=Li(["\n mushroom-slider {\n --main-color: var(--slider-color);\n --bg-color: var(--slider-bg-color);\n --gradient: -webkit-linear-gradient(right, ",");\n }\n "])),Sr(t))}}])}();br([Na({attribute:!1})],Pg.prototype,"hass",void 0),br([Na({attribute:!1})],Pg.prototype,"entity",void 0),Pg=br([Ia("mushroom-cover-tilt-position-control")],Pg);var Ng={buttons_control:"mdi:gesture-tap-button",position_control:"mdi:gesture-swipe-horizontal",tilt_position_control:"mdi:rotate-right"};rv({type:kg,name:"Mushroom Cover Card",description:"Card for cover entity"});var Bg=function(t){function e(){return hr(this,e),er(this,e,arguments)}return or(e,ov),pr(e,[{key:"hasControls",get:function(){return this._controls.length>0}},{key:"_nextControl",get:function(){var t;if(this._activeControl)return null!==(t=this._controls[this._controls.indexOf(this._activeControl)+1])&&void 0!==t?t:this._controls[0]}},{key:"_onNextControlTap",value:function(t){t.stopPropagation(),this._activeControl=this._nextControl}},{key:"getCardSize",value:function(){return 1}},{key:"setConfig",value:function(t){$i(e,"setConfig",this,3)([Object.assign({tap_action:{action:"toggle"},hold_action:{action:"more-info"}},t)]),this.updateActiveControl(),this.updatePosition()}},{key:"_controls",get:function(){if(!this._config||!this._stateObj)return[];var t=[];return this._config.show_buttons_control&&t.push("buttons_control"),this._config.show_position_control&&t.push("position_control"),this._config.show_tilt_position_control&&t.push("tilt_position_control"),t}},{key:"updateActiveControl",value:function(){var t=!!this._activeControl&&this._controls.includes(this._activeControl);this._activeControl=t?this._activeControl:this._controls[0]}},{key:"updated",value:function(t){$i(e,"updated",this,3)([t]),this.hass&&t.has("hass")&&(this.updatePosition(),this.updateActiveControl())}},{key:"updatePosition",value:function(){this.position=void 0;var t=this._stateObj;t&&(this.position=zg(t))}},{key:"onCurrentPositionChange",value:function(t){null!=t.detail.value&&(this.position=t.detail.value)}},{key:"_handleAction",value:function(t){rl(this,this.hass,this._config,t.detail.action)}},{key:"render",value:function(){if(!this.hass||!this._config||!this._config.entity)return fa;var t=this._stateObj;if(!t)return this.renderNotFound(this._config);var e=this._config.name||t.attributes.friendly_name||"",n=this._config.icon,o=Ol(this._config),i=nv(t,o.icon_type),r=this.hass.formatEntityState(t);if(this.position){var a=this.hass.formatEntityAttributeValue(t,"current_position",this.position);r+=" ⸱ ".concat(a)}var s=Is(this.hass);return ha(me||(me=Li(["\n \n \n \n ","\n ","\n ",";\n \n ","\n \n \n "])),Ka({"fill-container":o.fill_container}),o,s,s,o,this._handleAction,il({hasHold:al(this._config.hold_action),hasDoubleClick:al(this._config.double_tap_action)}),i?this.renderPicture(i):this.renderIcon(t,n),this.renderBadge(t),this.renderStateInfo(t,o,e,r),this._controls.length>0?ha(ve||(ve=Li(['\n
\n \n \n ']))):fa}}],[{key:"getConfigElement",value:(n=tr(Zi().m((function t(){return Zi().w((function(t){for(;;)switch(t.n){case 0:return t.n=1,Promise.resolve().then((function(){return fE}));case 1:return t.a(2,document.createElement(Hg))}}),t)}))),function(){return n.apply(this,arguments)})},{key:"styles",get:function(){return[$i(e,"styles",this),Tr(Ee||(Ee=Li(["\n :host {\n display: block;\n height: 100%;\n }\n\n ha-card {\n background: none;\n height: 100%;\n min-height: 56px;\n display: flex;\n justify-content: center;\n align-items: center;\n --mdc-icon-size: 40px;\n --icon-primary-color: var(--divider-color, rgba(0, 0, 0, 0.12));\n }\n "])))]}}]);var n}();br([Na({type:Boolean})],Dg.prototype,"preview",void 0),Dg=br([Ia(Lg)],Dg);var Rg="".concat(av,"-entity-card"),Ug="".concat(Rg,"-editor");rv({type:Rg,name:"Mushroom Entity Card",description:"Card for all entities"});var Vg=function(t){function e(){return hr(this,e),er(this,e,arguments)}return or(e,ov),pr(e,[{key:"_handleAction",value:function(t){rl(this,this.hass,this._config,t.detail.action)}},{key:"render",value:function(){if(!this._config||!this.hass||!this._config.entity)return fa;var t=this._stateObj;if(!t)return this.renderNotFound(this._config);var e=this._config.name||t.attributes.friendly_name||"",n=this._config.icon,o=Ol(this._config),i=nv(t,o.icon_type),r=Is(this.hass);return ha(xe||(xe=Li(["\n \n \n \n ","\n ","\n ",";\n \n \n \n "])),Ka({"fill-container":o.fill_container}),o,r,r,o,this._handleAction,il({hasHold:al(this._config.hold_action),hasDoubleClick:al(this._config.double_tap_action)}),i?this.renderPicture(i):this.renderIcon(t,n),this.renderBadge(t),this.renderStateInfo(t,o,e))}},{key:"renderIcon",value:function(t,e){var n,o=Rs(t),i={},r=null===(n=this._config)||void 0===n?void 0:n.icon_color;if(r){var a=Gm(r);i["--icon-color"]="rgb(".concat(a,")"),i["--shape-color"]="rgba(".concat(a,", 0.2)")}return ha(Ae||(Ae=Li(['\n ',"
"])),u):t?this.renderIcon(t,e):fa,(t||a)&&n?this.renderBadgeIcon(n,o):void 0,i,r,s)}},{key:"renderPicture",value:function(t){return ha(We||(We=Li(['\n \n \n
\n \n '])),"volume_down",!Us(this.entity)||Vs(this.entity),this.handleClick):void 0,l?ha(An||(An=Li(["\n \n '])),"volume_up",!Us(this.entity)||Vs(this.entity),this.handleClick):void 0)}}],[{key:"styles",get:function(){return Tr(Sn||(Sn=Li(["\n mushroom-slider {\n flex: 1;\n --main-color: rgb(var(--rgb-state-media-player));\n --bg-color: rgba(var(--rgb-state-media-player), 0.2);\n }\n "])))}}])}();br([Na({attribute:!1})],H_.prototype,"hass",void 0),br([Na({attribute:!1})],H_.prototype,"entity",void 0),br([Na({type:Boolean})],H_.prototype,"fill",void 0),br([Na({attribute:!1})],H_.prototype,"controls",void 0),H_=br([Ia("mushroom-media-player-volume-control")],H_);var D_={media_control:"mdi:play-pause",volume_control:"mdi:volume-high"};rv({type:I_,name:"Mushroom Media Card",description:"Card for media player entity"});var R_=function(t){function e(){return hr(this,e),er(this,e,arguments)}return or(e,ov),pr(e,[{key:"hasControls",get:function(){var t,e,n,o;return Boolean(null===(e=null===(t=this._config)||void 0===t?void 0:t.media_controls)||void 0===e?void 0:e.length)||Boolean(null===(o=null===(n=this._config)||void 0===n?void 0:n.volume_controls)||void 0===o?void 0:o.length)}},{key:"_controls",get:function(){if(!this._config||!this._stateObj)return[];var t=this._stateObj,e=[];return function(t,e){return N_(t,null!=e?e:[]).length>0}(t,this._config.media_controls)&&e.push("media_control"),function(t,e){return(null==e?void 0:e.includes("volume_buttons"))&&ts(t,1024)||(null==e?void 0:e.includes("volume_mute"))&&ts(t,8)||(null==e?void 0:e.includes("volume_set"))&&ts(t,4)}(t,this._config.volume_controls)&&e.push("volume_control"),e}},{key:"_onControlTap",value:function(t,e){e.stopPropagation(),this._activeControl=t}},{key:"setConfig",value:function(t){$i(e,"setConfig",this,3)([t]),this.updateActiveControl(),this.updateVolume()}},{key:"updated",value:function(t){$i(e,"updated",this,3)([t]),this.hass&&t.has("hass")&&(this.updateActiveControl(),this.updateVolume())}},{key:"updateVolume",value:function(){this.volume=void 0;var t=this._stateObj;t&&(this.volume=t.attributes.volume_level)}},{key:"onCurrentVolumeChange",value:function(t){null!=t.detail.value&&(this.volume=t.detail.value/100)}},{key:"updateActiveControl",value:function(){var t=!!this._activeControl&&this._controls.includes(this._activeControl);this._activeControl=t?this._activeControl:this._controls[0]}},{key:"_handleAction",value:function(t){rl(this,this.hass,this._config,t.detail.action)}},{key:"render",value:function(){if(!this._config||!this.hass||!this._config.entity)return fa;var t=this._stateObj;if(!t)return this.renderNotFound(this._config);var e,n,o,i=function(t,e){var n,o=t.icon;if(![Bs,Ls,Hs].includes(e.state)&&t.use_media_info)switch(null===(n=e.attributes.app_name)||void 0===n?void 0:n.toLowerCase()){case"spotify":return"mdi:spotify";case"google podcasts":return"mdi:google-podcast";case"plex":return"mdi:plex";case"soundcloud":return"mdi:soundcloud";case"youtube":return"mdi:youtube";case"oto music":return"mdi:music-circle";case"netflix":return"mdi:netflix";default:return}return o}(this._config,t),r=(e=this._config,n=t,o=e.name||n.attributes.friendly_name||"",![Bs,Ls,Hs].includes(n.state)&&e.use_media_info&&n.attributes.media_title&&(o=n.attributes.media_title),o),a=Ol(this._config),s=nv(t,a.icon_type),l=function(t,e,n){var o=n.formatEntityState(e);return![Bs,Ls,Hs].includes(e.state)&&t.use_media_info&&function(t){var e;switch(t.attributes.media_content_type){case"music":case"image":e=t.attributes.media_artist;break;case"playlist":e=t.attributes.media_playlist;break;case"tvshow":e=t.attributes.media_series_title,t.attributes.media_season&&(e+=" S"+t.attributes.media_season,t.attributes.media_episode&&(e+="E"+t.attributes.media_episode));break;default:e=t.attributes.app_name||""}return e}(e)||o}(this._config,t,this.hass);if(null!=this.volume&&this._config.show_volume_level){var c=this.hass.formatEntityAttributeValue(t,"volume_level",this.volume);l+=" ⸱ ".concat(c)}var u=Is(this.hass),h=(!this._config.collapsible_controls||Rs(t))&&this._controls.length;return ha(Tn||(Tn=Li(["\n \n \n \n ","\n ","\n ",";\n \n ","\n \n \n "])),Ka({"fill-container":a.fill_container}),a,u,u,a,this._handleAction,il({hasHold:al(this._config.hold_action),hasDoubleClick:al(this._config.double_tap_action)}),s?this.renderPicture(s):this.renderIcon(t,i),this.renderBadge(t),this.renderStateInfo(t,a,r,l),h?ha(Mn||(Mn=Li(['\n
\n
\n
\n \n \n
\n
\n ',"\n ","\n
\n \n "])),Hg(d),this._handleAction,qs({disabled:!this._hasCardAction,hasHold:Zs(this._config.hold_action),hasDoubleClick:Zs(this._config.double_tap_action)}),WE(this._hasCardAction?"button":void 0),WE(this._hasCardAction?"0":void 0),!this._hasCardAction,g,e||r||i||o?cl(Oi||(Oi=Pr(['
\n ',"\n ","\n
"])),_,e||r?cl(Mi||(Mi=Pr(["\n \n ","\n ","\n \n "])),WE(this._hasIconAction?"button":void 0),WE(this._hasIconAction?"0":void 0),this._handleIconAction,qs({disabled:!this._hasIconAction,hasHold:Zs(this._config.icon_hold_action),hasDoubleClick:Zs(this._config.icon_double_tap_action)}),this._hasIconAction,r?this.hass.hassUrl(r):void 0,u?"weather":"",u?cl(zi||(zi=Pr(['
',"
"])),u):cl(Pi||(Pi=Pr(['','\n \n

',"","

\n
\n "])),WE(i?"button":void 0),WE(i?"0":void 0),Mk({actionable:i}),this._handleTitleAction,qs(),e,this.renderArrow()):hl,t?cl(Fi||(Fi=Pr(["\n ',"","\n
\n "])),WE(o?"button":void 0),WE(o?"0":void 0),Mk({actionable:o}),this._handleSubtitleAction,qs(),t,this.renderArrow()):hl)}},{key:"renderArrow",value:function(){var e=bs(this.hass);return cl(Gi||(Gi=Pr(["
"])),e?"mdi:chevron-left":"mdi:chevron-right")}},{key:"updated",value:function(e){Hr(t,"updated",this,3)([e]),this._config&&this.hass&&this._tryConnect()}},{key:"_tryConnect",value:(l=Xr(Kr().m((function e(){var t=this;return Kr().w((function(e){for(;;)switch(e.n){case 0:cC.forEach((function(e){t._tryConnectKey(e)}));case 1:return e.a(2)}}),e)}))),function(){return l.apply(this,arguments)})},{key:"_tryConnectKey",value:(a=Xr(Kr().m((function e(t){var n,i,o,r,a=this;return Kr().w((function(e){for(;;)switch(e.p=e.n){case 0:if(void 0===this._unsubRenderTemplates.get(t)&&this.hass&&this._config&&this.isTemplate(t)){e.n=1;break}return e.a(2);case 1:return e.p=1,o=Fs(this.hass.connection,(function(e){a._templateResults=Object.assign(Object.assign({},a._templateResults),Rr({},t,e))}),{template:null!==(n=this._config[t])&&void 0!==n?n:"",entity_ids:this._config.entity_id,variables:{config:this._config,user:this.hass.user.name},strict:!0}),this._unsubRenderTemplates.set(t,o),e.n=2,o;case 2:e.n=4;break;case 3:e.p=3,e.v,r={result:null!==(i=this._config[t])&&void 0!==i?i:"",listeners:{all:!1,domains:[],entities:[],time:!1}},this._templateResults=Object.assign(Object.assign({},this._templateResults),Rr({},t,r)),this._unsubRenderTemplates.delete(t);case 4:return e.a(2)}}),e,this,[[1,3]])}))),function(e){return a.apply(this,arguments)})},{key:"_tryDisconnect",value:(r=Xr(Kr().m((function e(){var t=this;return Kr().w((function(e){for(;;)switch(e.n){case 0:cC.forEach((function(e){t._tryDisconnectKey(e)}));case 1:return e.a(2)}}),e)}))),function(){return r.apply(this,arguments)})},{key:"_tryDisconnectKey",value:(o=Xr(Kr().m((function e(t){var n,i;return Kr().w((function(e){for(;;)switch(e.p=e.n){case 0:if(n=this._unsubRenderTemplates.get(t)){e.n=1;break}return e.a(2);case 1:return e.p=1,e.n=2,n;case 2:(0,e.v)(),this._unsubRenderTemplates.delete(t),e.n=5;break;case 3:if(e.p=3,"not_found"!==(i=e.v).code&&"template_error"!==i.code){e.n=4;break}e.n=5;break;case 4:throw i;case 5:return e.a(2)}}),e,this,[[1,3]])}))),function(e){return o.apply(this,arguments)})}],[{key:"getConfigElement",value:(i=Xr(Kr().m((function e(){return Kr().w((function(e){for(;;)switch(e.n){case 0:return e.n=1,Promise.resolve().then((function(){return Oz}));case 1:return e.a(2,document.createElement(lC))}}),e)}))),function(){return i.apply(this,arguments)})},{key:"getStubConfig",value:(n=Xr(Kr().m((function e(t){return Kr().w((function(e){for(;;)if(0===e.n)return e.a(2,{type:"custom:".concat(aC),title:"Hello, {{ user }} !"})}),e)}))),function(e){return n.apply(this,arguments)})},{key:"styles",get:function(){return[Hr(t,"styles",this),nw,Aa(Yi||(Yi=Pr(["\n .header {\n display: block;\n padding: var(--title-padding);\n background: none;\n border: none;\n box-shadow: none;\n text-align: var(--card-text-align, inherit);\n }\n .header div * {\n margin: 0;\n white-space: pre-wrap;\n }\n .header div:not(:last-of-type) {\n margin-bottom: var(--title-spacing);\n }\n .actionable {\n cursor: pointer;\n }\n .header ha-icon {\n display: none;\n }\n .actionable ha-icon {\n display: inline-block;\n margin-left: 4px;\n transition: transform 180ms ease-in-out;\n }\n .actionable:hover ha-icon {\n transform: translateX(4px);\n }\n [rtl] .actionable ha-icon {\n margin-left: initial;\n margin-right: 4px;\n }\n [rtl] .actionable:hover ha-icon {\n transform: translateX(-4px);\n }\n .title {\n color: var(--title-color);\n font-size: var(--title-font-size);\n font-weight: var(--title-font-weight);\n line-height: var(--title-line-height);\n letter-spacing: var(--title-letter-spacing);\n --mdc-icon-size: var(--title-font-size);\n }\n .subtitle {\n color: var(--subtitle-color);\n font-size: var(--subtitle-font-size);\n font-weight: var(--subtitle-font-weight);\n line-height: var(--subtitle-line-height);\n letter-spacing: var(--subtitle-letter-spacing);\n --mdc-icon-size: var(--subtitle-font-size);\n }\n .align-start {\n text-align: start;\n }\n .align-end {\n text-align: end;\n }\n .align-center {\n text-align: center;\n }\n .align-justify {\n text-align: justify;\n }\n "])))]}}]);var n,i,o,r,a,l}();ga([Pl()],uC.prototype,"_config",void 0),ga([Pl()],uC.prototype,"_templateResults",void 0),ga([Pl()],uC.prototype,"_unsubRenderTemplates",void 0),uC=ga([Il(aC)],uC);var dC="".concat(ow,"-update-card"),hC="".concat(dC,"-editor"),pC=["update"],fC={on:"var(--rgb-state-update-on)",off:"var(--rgb-state-update-off)",installing:"var(--rgb-state-update-installing)"},mC=function(e){function t(){var e;return sa(this,t),(e=Zr(this,t,arguments)).fill=!1,e}return ea(t,Sl),ua(t,[{key:"_handleInstall",value:function(){this.hass.callService("update","install",{entity_id:this.entity.entity_id})}},{key:"_handleSkip",value:function(e){e.stopPropagation(),this.hass.callService("update","skip",{entity_id:this.entity.entity_id})}},{key:"installDisabled",get:function(){if(!Ms(this.entity))return!0;var e=this.entity.attributes.latest_version&&this.entity.attributes.skipped_version===this.entity.attributes.latest_version;return!Os(this.entity)&&!e||Ds(this.entity)}},{key:"skipDisabled",get:function(){return!Ms(this.entity)||(this.entity.attributes.latest_version&&this.entity.attributes.skipped_version===this.entity.attributes.latest_version||!Os(this.entity)||Ds(this.entity))}},{key:"render",value:function(){var e=bs(this.hass);return cl(Ki||(Ki=Pr(["\n \n \n \n \n \n \n \n '])),this.fill,e,this.skipDisabled,this._handleSkip,this.installDisabled,this._handleInstall)}}])}();ga([zl({attribute:!1})],mC.prototype,"hass",void 0),ga([zl({attribute:!1})],mC.prototype,"entity",void 0),ga([zl({type:Boolean})],mC.prototype,"fill",void 0),mC=ga([Il("mushroom-update-buttons-control")],mC),iw({type:dC,name:"Mushroom Update Card",description:"Card for update entity"});var vC=function(e){function t(){return sa(this,t),Zr(this,t,arguments)}return ea(t,tw),ua(t,[{key:"hasControls",get:function(){return!(!this._stateObj||!this._config)&&(Boolean(this._config.show_buttons_control)&&Vl(this._stateObj,1))}},{key:"_handleAction",value:function(e){Xs(this,this.hass,this._config,e.detail.action)}},{key:"render",value:function(){if(!this._config||!this.hass||!this._config.entity)return hl;var e=this._stateObj;if(!e)return this.renderNotFound(this._config);var t=this._config.name||e.attributes.friendly_name||"",n=this._config.icon,i=Gk(this._config),o=wk(e,i.icon_type),r=bs(this.hass),a=(!this._config.collapsible_controls||Os(e))&&this._config.show_buttons_control&&Vl(e,1);return cl(Wi||(Wi=Pr(["\n \n \n \n ","\n ","\n ",";\n \n ","\n \n \n "])),Mk({"fill-container":i.fill_container}),i,r,r,i,this._handleAction,qs({hasHold:Zs(this._config.hold_action),hasDoubleClick:Zs(this._config.double_tap_action)}),o?this.renderPicture(o):this.renderIcon(e,n),this.renderBadge(e),this.renderStateInfo(e,i,t),a?cl(qi||(qi=Pr(['\n \n "])),void 0!==(null===(e=this._config.chip)||void 0===e?void 0:e.type)?cl(Wo||(Wo=Pr(['\n
\n \n ',"\n \n ",'\n
\n \n
\n \n \n \n ','\n
\n \n ',"\n \n
\n ","\n "])),this.hass.localize("ui.common.back"),this._goBack,e("editor.chip.sub_element_editor.title"),!this._guiModeAvailable,this._toggleMode,this.hass.localize(this._guiMode?"ui.panel.lovelace.editor.edit_card.show_code_editor":"ui.panel.lovelace.editor.edit_card.show_visual_editor"),"chip"===this.config.type?cl(ir||(ir=Pr(['\n \n \n \n
\n
\n ',"\n ","\n
\n \n "])),Wa(h),this._handleAction,il({disabled:!this._hasCardAction,hasHold:al(this._config.hold_action),hasDoubleClick:al(this._config.double_tap_action)}),ny(this._hasCardAction?"button":void 0),ny(this._hasCardAction?"0":void 0),!this._hasCardAction,g,t||r||o||i?ha(Jn||(Jn=Li(['
\n ',"\n ","\n
"])),_,t||r?ha(Qn||(Qn=Li(["\n \n ","\n ","\n \n "])),ny(this._hasIconAction?"button":void 0),ny(this._hasIconAction?"0":void 0),this._handleIconAction,b?k:void 0,b?void 0:il(k),this._hasIconAction,r?this.hass.hassUrl(r):void 0,u?"weather":"",r?fa:u?ha(to||(to=Li(['
',"
"])),u):ha(eo||(eo=Li(['\n ','\n \n

',"","

\n
\n "])),ny(o?"button":void 0),ny(o?"0":void 0),Ka({actionable:o}),this._handleTitleAction,il(),t,this.renderArrow()):fa,e?ha(uo||(uo=Li(["\n ',"","\n
\n "])),ny(i?"button":void 0),ny(i?"0":void 0),Ka({actionable:i}),this._handleSubtitleAction,il(),e,this.renderArrow()):fa)}},{key:"renderArrow",value:function(){var t=Is(this.hass);return ha(ho||(ho=Li(["
"])),t?"mdi:chevron-left":"mdi:chevron-right")}},{key:"updated",value:function(t){$i(e,"updated",this,3)([t]),this._config&&this.hass&&this._tryConnect()}},{key:"_tryConnect",value:(s=tr(Zi().m((function t(){var e=this;return Zi().w((function(t){for(;;)switch(t.n){case 0:_y.forEach((function(t){e._tryConnectKey(t)}));case 1:return t.a(2)}}),t)}))),function(){return s.apply(this,arguments)})},{key:"_tryConnectKey",value:(a=tr(Zi().m((function t(e){var n,o,i,r,a=this;return Zi().w((function(t){for(;;)switch(t.p=t.n){case 0:if(void 0===this._unsubRenderTemplates.get(e)&&this.hass&&this._config&&this.isTemplate(e)){t.n=1;break}return t.a(2);case 1:return t.p=1,i=tl(this.hass.connection,(function(t){a._templateResults=Object.assign(Object.assign({},a._templateResults),Fi({},e,t))}),{template:null!==(n=this._config[e])&&void 0!==n?n:"",entity_ids:this._config.entity_id,variables:{config:this._config,user:this.hass.user.name},strict:!0}),this._unsubRenderTemplates.set(e,i),t.n=2,i;case 2:t.n=4;break;case 3:t.p=3,t.v,r={result:null!==(o=this._config[e])&&void 0!==o?o:"",listeners:{all:!1,domains:[],entities:[],time:!1}},this._templateResults=Object.assign(Object.assign({},this._templateResults),Fi({},e,r)),this._unsubRenderTemplates.delete(e);case 4:return t.a(2)}}),t,this,[[1,3]])}))),function(t){return a.apply(this,arguments)})},{key:"_tryDisconnect",value:(r=tr(Zi().m((function t(){var e=this;return Zi().w((function(t){for(;;)switch(t.n){case 0:_y.forEach((function(t){e._tryDisconnectKey(t)}));case 1:return t.a(2)}}),t)}))),function(){return r.apply(this,arguments)})},{key:"_tryDisconnectKey",value:(i=tr(Zi().m((function t(e){var n,o;return Zi().w((function(t){for(;;)switch(t.p=t.n){case 0:if(n=this._unsubRenderTemplates.get(e)){t.n=1;break}return t.a(2);case 1:return t.p=1,t.n=2,n;case 2:(0,t.v)(),this._unsubRenderTemplates.delete(e),t.n=5;break;case 3:if(t.p=3,"not_found"!==(o=t.v).code&&"template_error"!==o.code){t.n=4;break}t.n=5;break;case 4:throw o;case 5:return t.a(2)}}),t,this,[[1,3]])}))),function(t){return i.apply(this,arguments)})}],[{key:"getConfigElement",value:(o=tr(Zi().m((function t(){return Zi().w((function(t){for(;;)switch(t.n){case 0:return t.n=1,Promise.resolve().then((function(){return dx}));case 1:return t.a(2,document.createElement(vy))}}),t)}))),function(){return o.apply(this,arguments)})},{key:"getStubConfig",value:(n=tr(Zi().m((function t(e){return Zi().w((function(t){for(;;)if(0===t.n)return t.a(2,{type:"custom:".concat(my),title:"Hello, {{ user }} !"})}),t)}))),function(t){return n.apply(this,arguments)})},{key:"styles",get:function(){return[$i(e,"styles",this),iv,Tr(po||(po=Li(["\n .header {\n display: block;\n padding: var(--title-padding);\n background: none;\n border: none;\n box-shadow: none;\n text-align: var(--card-text-align, inherit);\n }\n .header div * {\n margin: 0;\n white-space: pre-wrap;\n }\n .header div:not(:last-of-type) {\n margin-bottom: var(--title-spacing);\n }\n .actionable {\n cursor: pointer;\n }\n .header ha-icon {\n display: none;\n }\n .actionable ha-icon {\n display: inline-block;\n margin-left: 4px;\n transition: transform 180ms ease-in-out;\n }\n .actionable:hover ha-icon {\n transform: translateX(4px);\n }\n [rtl] .actionable ha-icon {\n margin-left: initial;\n margin-right: 4px;\n }\n [rtl] .actionable:hover ha-icon {\n transform: translateX(-4px);\n }\n .title {\n color: var(--title-color);\n font-size: var(--title-font-size);\n font-weight: var(--title-font-weight);\n line-height: var(--title-line-height);\n letter-spacing: var(--title-letter-spacing);\n --mdc-icon-size: var(--title-font-size);\n }\n .subtitle {\n color: var(--subtitle-color);\n font-size: var(--subtitle-font-size);\n font-weight: var(--subtitle-font-weight);\n line-height: var(--subtitle-line-height);\n letter-spacing: var(--subtitle-letter-spacing);\n --mdc-icon-size: var(--subtitle-font-size);\n }\n .align-start {\n text-align: start;\n }\n .align-end {\n text-align: end;\n }\n .align-center {\n text-align: center;\n }\n .align-justify {\n text-align: justify;\n }\n "])))]}}]);var n,o,i,r,a,s}();br([Ba()],yy.prototype,"_config",void 0),br([Ba()],yy.prototype,"_templateResults",void 0),br([Ba()],yy.prototype,"_unsubRenderTemplates",void 0),yy=br([Ia(my)],yy);var by="".concat(av,"-update-card"),ky="".concat(by,"-editor"),wy=["update"],Cy={on:"var(--rgb-state-update-on)",off:"var(--rgb-state-update-off)",installing:"var(--rgb-state-update-installing)"},Ey=function(t){function e(){var t;return hr(this,e),(t=er(this,e,arguments)).fill=!1,t}return or(e,za),pr(e,[{key:"_handleInstall",value:function(){this.hass.callService("update","install",{entity_id:this.entity.entity_id})}},{key:"_handleSkip",value:function(t){t.stopPropagation(),this.hass.callService("update","skip",{entity_id:this.entity.entity_id})}},{key:"installDisabled",get:function(){if(!Us(this.entity))return!0;var t=this.entity.attributes.latest_version&&this.entity.attributes.skipped_version===this.entity.attributes.latest_version;return!Rs(this.entity)&&!t||Zs(this.entity)}},{key:"skipDisabled",get:function(){return!Us(this.entity)||(this.entity.attributes.latest_version&&this.entity.attributes.skipped_version===this.entity.attributes.latest_version||!Rs(this.entity)||Zs(this.entity))}},{key:"render",value:function(){var t=Is(this.hass);return ha(fo||(fo=Li(["\n \n
\n \n \n \n \n \n '])),this.fill,t,this.skipDisabled,this._handleSkip,this.installDisabled,this._handleInstall)}}])}();br([Na({attribute:!1})],Ey.prototype,"hass",void 0),br([Na({attribute:!1})],Ey.prototype,"entity",void 0),br([Na({type:Boolean})],Ey.prototype,"fill",void 0),Ey=br([Ia("mushroom-update-buttons-control")],Ey),rv({type:by,name:"Mushroom Update Card",description:"Card for update entity"});var xy=function(t){function e(){return hr(this,e),er(this,e,arguments)}return or(e,ov),pr(e,[{key:"hasControls",get:function(){return!(!this._stateObj||!this._config)&&(Boolean(this._config.show_buttons_control)&&ts(this._stateObj,1))}},{key:"_handleAction",value:function(t){rl(this,this.hass,this._config,t.detail.action)}},{key:"render",value:function(){if(!this._config||!this.hass||!this._config.entity)return fa;var t=this._stateObj;if(!t)return this.renderNotFound(this._config);var e=this._config.name||t.attributes.friendly_name||"",n=this._config.icon,o=Ol(this._config),i=nv(t,o.icon_type),r=Is(this.hass),a=(!this._config.collapsible_controls||Rs(t))&&this._config.show_buttons_control&&ts(t,1);return ha(mo||(mo=Li(["\n \n \n \n ","\n ","\n ",";\n \n ","\n \n \n "])),Ka({"fill-container":o.fill_container}),o,r,r,o,this._handleAction,il({hasHold:al(this._config.hold_action),hasDoubleClick:al(this._config.double_tap_action)}),i?this.renderPicture(i):this.renderIcon(t,n),this.renderBadge(t),this.renderStateInfo(t,o,e),a?ha(vo||(vo=Li(['\n \n "])),void 0!==(null===(t=this._config.chip)||void 0===t?void 0:t.type)?ha(Qo||(Qo=Li(['\n
\n \n ',"\n \n ",'\n
\n \n
\n \n \n \n ','\n
\n \n ',"\n \n \n ","\n "])),this.hass.localize("ui.common.back"),this._goBack,t("editor.chip.sub_element_editor.title"),!this._guiModeAvailable,this._toggleMode,this.hass.localize(this._guiMode?"ui.panel.lovelace.editor.edit_card.show_code_editor":"ui.panel.lovelace.editor.edit_card.show_visual_editor"),"chip"===this.config.type?ha(si||(si=Li(['\n \n
\n ',"\n
\n \n ","\n \n "])),this.label||"".concat(t("editor.chip.chip-picker.chips")," (").concat(this.hass.localize("ui.panel.lovelace.editor.card.config.required"),")"),iM([this.chips,this._renderEmptySortable],(function(){return e._renderEmptySortable?"":e.chips.map((function(n,i){return cl(ar||(ar=Pr(['\n
\n
\n \n
\n ',"\n ","\n \n \n \n
\n '])),cl(lr||(lr=Pr(['\n
\n
\n ','\n \n ',"\n \n
\n
\n "])),e._renderChipLabel(n),e._renderChipSecondary(n)),oM.has(n.type)?hl:cl(sr||(sr=Pr(["\n \n \n \n '])),t("editor.chip.chip-picker.edit"),i,e._editChip),t("editor.chip.chip-picker.clear"),i,e._removeChip)}))})),t("editor.chip.chip-picker.add"),this._addChips,(function(e){return e.stopPropagation()}),RO.map((function(e){return cl(cr||(cr=Pr(["\n \n ","\n \n "])),e,t("editor.chip.chip-picker.types.".concat(e)))})))}},{key:"updated",value:function(e){var n;Hr(t,"updated",this,3)([e]);var i=e.has("_attached"),o=e.has("chips");if(o||i)return i&&!this._attached?(null===(n=this._sortable)||void 0===n||n.destroy(),void(this._sortable=void 0)):void(this._sortable||!this.chips?o&&this._handleChipsChanged():this._createSortable())}},{key:"_handleChipsChanged",value:(o=Xr(Kr().m((function e(){var t;return Kr().w((function(e){for(;;)switch(e.n){case 0:return this._renderEmptySortable=!0,e.n=1,this.updateComplete;case 1:for(t=this.shadowRoot.querySelector(".chips");t.lastElementChild;)t.removeChild(t.lastElementChild);this._renderEmptySortable=!1;case 2:return e.a(2)}}),e,this)}))),function(){return o.apply(this,arguments)})},{key:"_createSortable",value:(i=Xr(Kr().m((function e(){var t,n=this;return Kr().w((function(e){for(;;)switch(e.n){case 0:if(tM){e.n=2;break}return e.n=1,Promise.resolve().then((function(){return JL}));case 1:t=e.v,(tM=t.Sortable).mount(t.OnSpill),tM.mount(t.AutoScroll());case 2:this._sortable=new tM(this.shadowRoot.querySelector(".chips"),{animation:150,fallbackClass:"sortable-fallback",handle:".handle",onEnd:function(){var e=Xr(Kr().m((function e(t){return Kr().w((function(e){for(;;)if(0===e.n)return e.a(2,n._chipMoved(t))}),e)})));return function(t){return e.apply(this,arguments)}}()});case 3:return e.a(2)}}),e,this)}))),function(){return i.apply(this,arguments)})},{key:"_addChips",value:(n=Xr(Kr().m((function e(t){var n,i,o,r,a;return Kr().w((function(e){for(;;)switch(e.n){case 0:if(n=t.target,""!==(i=n.value)){e.n=1;break}return e.a(2);case 1:if("conditional"!==i){e.n=2;break}return e.n=2,Ww();case 2:if(!(r=BO(i))||!r.getStubConfig){e.n=4;break}return e.n=3,r.getStubConfig(this.hass);case 3:o=e.v,e.n=5;break;case 4:o={type:i};case 5:a=this.chips.concat(o),n.value="",Dl(this,"chips-changed",{chips:a});case 6:return e.a(2)}}),e,this)}))),function(e){return n.apply(this,arguments)})},{key:"_chipMoved",value:function(e){if(e.oldIndex!==e.newIndex){var t=this.chips.concat();t.splice(e.newIndex,0,t.splice(e.oldIndex,1)[0]),Dl(this,"chips-changed",{chips:t})}}},{key:"_removeChip",value:function(e){var t=e.currentTarget.index,n=this.chips.concat();n.splice(t,1),Dl(this,"chips-changed",{chips:n})}},{key:"_editChip",value:function(e){var t=e.currentTarget.index;Dl(this,"edit-detail-element",{subElementConfig:{index:t,type:"chip",elementConfig:this.chips[t]}})}},{key:"_renderChipLabel",value:function(e){return Uh(this.hass)("editor.chip.chip-picker.types.".concat(e.type))}},{key:"_renderChipSecondary",value:function(e){var t,n,i=Uh(this.hass);if("entity"in e&&e.entity)return"".concat(null!==(n=null!==(t=this.getEntityName(e.entity))&&void 0!==t?t:e.entity)&&void 0!==n?n:"");if("chip"in e&&e.chip){var o=i("editor.chip.chip-picker.types.".concat(e.chip.type));return this._renderChipSecondary(e.chip)?"".concat(this._renderChipSecondary(e.chip)," (via ").concat(o,")"):o}return""}},{key:"getEntityName",value:function(e){if(this.hass){var t=this.hass.states[e];if(t)return t.attributes.friendly_name}}}],[{key:"styles",get:function(){return[Hr(t,"styles",this),lc,Aa(ur||(ur=Pr(["\n .chip {\n display: flex;\n align-items: center;\n }\n\n ha-icon {\n display: flex;\n }\n\n mushroom-select {\n width: 100%;\n }\n\n .chip .handle {\n padding-right: 8px;\n cursor: move;\n }\n\n .chip .handle > * {\n pointer-events: none;\n }\n\n .special-row {\n height: 60px;\n font-size: 16px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n flex-grow: 1;\n }\n\n .special-row div {\n display: flex;\n flex-direction: column;\n }\n\n .remove-icon,\n .edit-icon {\n --mdc-icon-button-size: 36px;\n color: var(--secondary-text-color);\n }\n\n .secondary {\n font-size: 12px;\n color: var(--secondary-text-color);\n }\n "])))]}}]);var n,i,o}();ga([zl({attribute:!1})],rM.prototype,"chips",void 0),ga([zl()],rM.prototype,"label",void 0),ga([Pl()],rM.prototype,"_attached",void 0),ga([Pl()],rM.prototype,"_renderEmptySortable",void 0),rM=ga([Il("mushroom-chips-card-chips-editor")],rM);var aM=fs({type:hs("action"),icon:ms(vs()),icon_color:ms(vs()),tap_action:ms(ac),hold_action:ms(ac),double_tap_action:ms(ac)}),lM=fs({type:hs("back"),icon:ms(vs()),icon_color:ms(vs())}),sM=fs({type:hs("entity"),entity:ms(vs()),name:ms(vs()),content_info:ms(vs()),icon:ms(vs()),icon_color:ms(vs()),use_entity_picture:ms(us()),tap_action:ms(ac),hold_action:ms(ac),double_tap_action:ms(ac)}),cM=fs({type:hs("menu"),icon:ms(vs()),icon_color:ms(vs())}),uM=fs({type:hs("quickbar"),icon:ms(vs()),mode:ms(ds(["command","device","entity"]))}),dM=fs({type:hs("weather"),entity:ms(vs()),tap_action:ms(ac),hold_action:ms(ac),double_tap_action:ms(ac),show_temperature:ms(us()),show_conditions:ms(us())}),hM=fs({type:hs("conditional"),chip:ms(ss()),conditions:ms(cs(ss()))}),pM=fs({type:hs("light"),entity:ms(vs()),name:ms(vs()),content_info:ms(vs()),icon:ms(vs()),use_light_color:ms(us()),tap_action:ms(ac),hold_action:ms(ac),double_tap_action:ms(ac)}),fM=fs({type:hs("template"),entity:ms(vs()),tap_action:ms(ac),hold_action:ms(ac),double_tap_action:ms(ac),content:ms(vs()),icon:ms(vs()),icon_color:ms(vs()),picture:ms(vs()),entity_id:ms(_s([vs(),cs(vs())]))}),mM=fs({type:hs("spacer")}),vM=ls((function(e){if(e&&"object"===ha(e)&&"type"in e)switch(e.type){case"action":return aM;case"back":return lM;case"entity":return sM;case"menu":return cM;case"quickbar":return uM;case"weather":return dM;case"conditional":return hM;case"light":return pM;case"template":return fM;case"spacer":return mM}return fs()})),gM=rs(QE,fs({chips:cs(vM),alignment:ms(vs())})),_M=function(e){function t(){return sa(this,t),Zr(this,t,arguments)}return ea(t,ew),ua(t,[{key:"connectedCallback",value:function(){Hr(t,"connectedCallback",this,3)([]),Gw()}},{key:"setConfig",value:function(e){is(e,gM),this._config=e}},{key:"_title",get:function(){return this._config.title||""}},{key:"_theme",get:function(){return this._config.theme||""}},{key:"render",value:function(){if(!this.hass||!this._config)return hl;if(this._subElementEditorConfig)return cl(dr||(dr=Pr(["\n \n \n "])),this.hass,this._subElementEditorConfig,this._goBack,this._handleSubElementChanged);var e=Uh(this.hass);return cl(hr||(hr=Pr(['\n
\n \n \n \n

\n ','\n

\n
\n \n
\n \n ',"\n "])),n("migration.post"))}),this._revertToLegacy,n("migration.revert"),this._done,n("migration.ok")):hl,this.hass,i,t,this._computeLabel,this._computeHelper,this._valueChanged,this.hass.localize("ui.panel.lovelace.editor.card.generic.features"),this.hass,i,o,this._computeLabel,this._computeHelper,this._valueChanged,this.hass,r,null!==(e=this._config.features)&&void 0!==e?e:[],this._featuresChanged,this._editDetailElement)}},{key:"_featuresChanged",value:function(e){if(e.stopPropagation(),this._config&&this.hass){var t=e.detail.features,n=Object.assign(Object.assign({},this._config),{features:t});0===t.length&&delete n.features,Dl(this,"config-changed",{config:n})}}},{key:"_editDetailElement",value:function(e){var t=this,n=e.detail.subElementConfig.index,i=this._config.features[n],o=this._featureContext(this._config);Dl(this,"edit-sub-element",{config:i,saveConfig:function(e){return t._updateFeature(n,e)},context:o,type:"feature"})}},{key:"_updateFeature",value:function(e,t){var n=this._config.features.concat();n[e]=t;var i=Object.assign(Object.assign({},this._config),{features:n});Dl(this,"config-changed",{config:i})}},{key:"_valueChanged",value:function(e){if(e.stopPropagation(),this._config&&this.hass){var t=e.detail.value,n=Object.assign({features:this._config.features},t);n.content_layout&&(n.vertical="vertical"===n.content_layout,delete n.content_layout),n.vertical||delete n.vertical,Dl(this,"config-changed",{config:n})}}}],[{key:"styles",get:function(){return[Aa(Sr||(Sr=Pr(['\n ha-form {\n display: block;\n margin-bottom: 24px;\n }\n .features-form {\n margin-bottom: 8px;\n }\n ha-expansion-panel {\n display: block;\n --expansion-panel-content-padding: 0;\n border-radius: 6px;\n --ha-card-border-radius: 6px;\n }\n ha-expansion-panel .content {\n padding: 12px;\n }\n ha-expansion-panel > *[slot="header"] {\n margin: 0;\n font-size: inherit;\n font-weight: inherit;\n }\n ha-expansion-panel ha-icon {\n color: var(--secondary-text-color);\n }\n ha-alert {\n margin-bottom: 16px;\n display: block;\n }\n ha-alert a {\n color: var(--primary-color);\n }\n ha-alert .actions {\n display: flex;\n width: 100%;\n flex: 1;\n align-items: flex-end;\n flex-direction: row;\n justify-content: flex-end;\n gap: 8px;\n margin-top: 8px;\n border-radius: 8px;\n }\n '])))]}}])}();ga([zl({attribute:!1})],xz.prototype,"hass",void 0),ga([Pl()],xz.prototype,"_config",void 0),ga([Pl()],xz.prototype,"_legacyConfig",void 0),xz=ga([Il("mushroom-template-card-editor")],xz);var Ez=Object.freeze({__proto__:null,get MushroomTemplateCardEditor(){return xz},TEMPLATE_CARD_HELPERS:wz,TEMPLATE_CARD_LABELS:bz,TILE_LABELS:kz}),Cz=rs(QE,fs({title:ms(vs()),subtitle:ms(vs()),alignment:ms(vs()),title_tap_action:ms(ac),subtitle_tap_action:ms(ac)})),Az=["navigate","url","perform-action","none"],Sz=["title","subtitle","title_tap_action","subtitle_tap_action"],Tz=[{name:"title",selector:{template:{}}},{name:"subtitle",selector:{template:{}}},{name:"alignment",selector:{mush_alignment:{}}},{name:"title_tap_action",selector:{ui_action:{actions:Az}}},{name:"subtitle_tap_action",selector:{ui_action:{actions:Az}}}],Iz=function(e){function t(){var e;return sa(this,t),(e=Zr(this,t,arguments))._computeLabel=function(t){var n=Uh(e.hass);return Sz.includes(t.name)?n("editor.card.title.".concat(t.name)):e.hass.localize("ui.panel.lovelace.editor.card.generic.".concat(t.name))},e}return ea(t,ew),ua(t,[{key:"connectedCallback",value:function(){Hr(t,"connectedCallback",this,3)([]),Gw()}},{key:"setConfig",value:function(e){is(e,Cz),this._config=e}},{key:"render",value:function(){return this.hass&&this._config?cl(Tr||(Tr=Pr(["\n \n "])),this.hass,this._config,Tz,this._computeLabel,this._valueChanged):hl}},{key:"_valueChanged",value:function(e){Dl(this,"config-changed",{config:e.detail.value})}}])}();ga([Pl()],Iz.prototype,"_config",void 0),Iz=ga([Il(lC)],Iz);var Oz=Object.freeze({__proto__:null,get TitleCardEditor(){return Iz}}),Mz=rs(QE,rs(jC,zC,OC),fs({show_buttons_control:ms(us()),collapsible_controls:ms(us())})),zz=["show_buttons_control"],Pz=["more-info","navigate","url","perform-action","assist","none"],Lz=Rs((function(){return[{name:"entity",selector:{entity:{domain:pC}}},{name:"name",selector:{text:{}}},{name:"icon",selector:{icon:{}},context:{icon_entity:"entity"}}].concat(PC,[{type:"grid",name:"",schema:[{name:"show_buttons_control",selector:{boolean:{}}},{name:"collapsible_controls",selector:{boolean:{}}}]}],Ur(MC(Pz)))})),Nz=function(e){function t(){var e;return sa(this,t),(e=Zr(this,t,arguments))._computeLabel=function(t){var n=Uh(e.hass);return LC.includes(t.name)?n("editor.card.generic.".concat(t.name)):zz.includes(t.name)?n("editor.card.update.".concat(t.name)):e.hass.localize("ui.panel.lovelace.editor.card.generic.".concat(t.name))},e}return ea(t,ew),ua(t,[{key:"connectedCallback",value:function(){Hr(t,"connectedCallback",this,3)([]),Gw()}},{key:"setConfig",value:function(e){is(e,Mz),this._config=e}},{key:"render",value:function(){if(!this.hass||!this._config)return hl;var e=Lz();return cl(Ir||(Ir=Pr(["\n \n "])),this.hass,this._config,e,this._computeLabel,this._valueChanged)}},{key:"_valueChanged",value:function(e){Dl(this,"config-changed",{config:e.detail.value})}}])}();ga([Pl()],Nz.prototype,"_config",void 0),Nz=ga([Il(hC)],Nz);var jz=Object.freeze({__proto__:null,get UpdateCardEditor(){return Nz}}),$z=["on_off","start_pause","stop","locate","clean_spot","return_home"],Bz=rs(QE,rs(jC,zC,OC),fs({icon_animation:ms(us()),commands:ms(cs(vs()))})),Rz=["commands"],Hz=Rs((function(e,t){return[{name:"entity",selector:{entity:{domain:yC}}},{name:"name",selector:{text:{}}},{type:"grid",name:"",schema:[{name:"icon",selector:{icon:{}},context:{icon_entity:"entity"}},{name:"icon_animation",selector:{boolean:{}}}]}].concat(PC,[{name:"commands",selector:{select:{mode:"list",multiple:!0,options:$z.map((function(n){return{value:n,label:"on_off"===n?t("editor.card.vacuum.commands_list.".concat(n)):e("ui.dialogs.more_info_control.vacuum.".concat(n))}}))}}}],Ur(MC()))})),Dz=function(e){function t(){var e;return sa(this,t),(e=Zr(this,t,arguments))._computeLabel=function(t){var n=Uh(e.hass);return LC.includes(t.name)?n("editor.card.generic.".concat(t.name)):Rz.includes(t.name)?n("editor.card.vacuum.".concat(t.name)):e.hass.localize("ui.panel.lovelace.editor.card.generic.".concat(t.name))},e}return ea(t,ew),ua(t,[{key:"connectedCallback",value:function(){Hr(t,"connectedCallback",this,3)([]),Gw()}},{key:"setConfig",value:function(e){is(e,Bz),this._config=e}},{key:"render",value:function(){if(!this.hass||!this._config)return hl;var e=Uh(this.hass),t=Hz(this.hass.localize,e);return cl(Or||(Or=Pr(["\n \n "])),this.hass,this._config,t,this._computeLabel,this._valueChanged)}},{key:"_valueChanged",value:function(e){Dl(this,"config-changed",{config:e.detail.value})}}])}();ga([Pl()],Dz.prototype,"_config",void 0),Dz=ga([Il(_C)],Dz);var Uz=Object.freeze({__proto__:null,get VacuumCardEditor(){return Dz}}),Vz=rs(fs({type:vs(),visibility:ss()}),OC,fs({entity:ms(vs()),area:ms(vs()),icon:ms(vs()),color:ms(vs()),label:ms(vs()),content:ms(vs()),picture:ms(vs()),entity_id:ms(_s([vs(),cs(vs())]))})),Fz=["label","content"],Gz=[{name:"context",flatten:!0,type:"expandable",icon:"mdi:shape",schema:[{name:"entity",selector:{entity:{}}},{name:"area",selector:{area:{}}}]},{name:"content",flatten:!0,type:"expandable",icon:"mdi:text-short",schema:[{name:"label",selector:{template:{}}},{name:"content",selector:{template:{}}},{name:"color",selector:{template:{}}},{name:"icon",selector:{template:{}}},{name:"picture",selector:{template:{}}}]},{name:"interactions",type:"expandable",flatten:!0,icon:"mdi:gesture-tap",schema:[{name:"tap_action",selector:{ui_action:{default_action:"none"}}},{name:"",type:"optional_actions",flatten:!0,schema:["hold_action","double_tap_action"].map((function(e){return{name:e,selector:{ui_action:{default_action:"none"}}}}))}]}],Yz=function(e){function t(){var e;return sa(this,t),(e=Zr(this,t,arguments))._computeLabel=function(t){var n=Uh(e.hass);return"expandable"===t.type?n("editor.section.".concat(t.name)):LC.includes(t.name)?n("editor.card.generic.".concat(t.name)):Fz.includes(t.name)?n("editor.card.template.".concat(t.name)):e.hass.localize("ui.panel.lovelace.editor.card.generic.".concat(t.name))},e._computeHelper=function(t){if("expandable"!==t.type){var n=Uh(e.hass);return NC.includes(t.name)?n("editor.card.generic.".concat(t.name,"_helper")):Fz.includes(t.name)?n("editor.card.template.".concat(t.name,"_helper")):void 0}},e}return ea(t,Sl),ua(t,[{key:"connectedCallback",value:function(){Hr(t,"connectedCallback",this,3)([]),Gw()}},{key:"setConfig",value:function(e){is(e,Vz),this._config=e}},{key:"render",value:function(){return this.hass&&this._config?cl(Mr||(Mr=Pr(["\n \n "])),this.hass,this._config,Gz,this._computeLabel,this._computeHelper,this._valueChanged):hl}},{key:"_valueChanged",value:function(e){Dl(this,"config-changed",{config:e.detail.value})}}])}();ga([zl({attribute:!1})],Yz.prototype,"hass",void 0),ga([Pl()],Yz.prototype,"_config",void 0),Yz=ga([Il("mushroom-template-badge-editor")],Yz);var Kz=Object.freeze({__proto__:null,get MushroomTemplateBadgeEditor(){return Yz},TEMPLATE_BADGE_LABELS:Fz}); +var NC,BC={},LC=$a(function(t){function e(){var t;return hr(this,e),(t=er(this,e,arguments)).ot=BC,t}return or(e,Ga),pr(e,[{key:"render",value:function(t,e){return e()}},{key:"update",value:function(t,e){var n=this,o=qi(e,2),i=o[0],r=o[1];if(Array.isArray(i)){if(Array.isArray(this.ot)&&this.ot.length===i.length&&i.every((function(t,e){return t===n.ot[e]})))return pa}else if(this.ot===i)return pa;return this.ot=Array.isArray(i)?Array.from(i):i,this.render(i,r)}}])}()),HC={},DC=$a(function(t){function e(){var t;return hr(this,e),(t=er(this,e,arguments)).key=fa,t}return or(e,Ga),pr(e,[{key:"render",value:function(t,e){return this.key=t,e}},{key:"update",value:function(t,e){var n=qi(e,2),o=n[0],i=n[1];return o!==this.key&&(function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:HC;t._$AH=e}(t),this.key=o),i}}])}()),RC=new Set(["spacer"]),UC=function(t){function e(){var t;return hr(this,e),(t=er(this,e,arguments))._attached=!1,t._renderEmptySortable=!1,t._selectorKey=0,t}return or(e,Zm),pr(e,[{key:"connectedCallback",value:function(){$i(e,"connectedCallback",this,3)([]),this._attached=!0}},{key:"disconnectedCallback",value:function(){$i(e,"disconnectedCallback",this,3)([]),this._attached=!1}},{key:"render",value:function(){var t=this;if(!this.chips||!this.hass)return fa;var e=vd(this.hass);return ha(ci||(ci=Li(["\n

\n ",'\n

\n
\n ',"\n
\n ","\n "])),this.label||"".concat(e("editor.chip.chip-picker.chips")," (").concat(this.hass.localize("ui.panel.lovelace.editor.card.config.required"),")"),LC([this.chips,this._renderEmptySortable],(function(){return t._renderEmptySortable?"":t.chips.map((function(n,o){return ha(ui||(ui=Li(['\n
\n
\n \n
\n ',"\n ","\n \n \n \n
\n '])),ha(hi||(hi=Li(['\n
\n
\n ','\n \n ',"\n \n
\n
\n "])),t._renderChipLabel(n),t._renderChipSecondary(n)),RC.has(n.type)?fa:ha(di||(di=Li(["\n \n \n \n '])),e("editor.chip.chip-picker.edit"),o,t._editChip),e("editor.chip.chip-picker.clear"),o,t._removeChip)}))})),DC(this._selectorKey,ha(pi||(pi=Li([""])),this.hass,e("editor.chip.chip-picker.add"),"",{select:{options:yC.map((function(t){return{value:t,label:e("editor.chip.chip-picker.types.".concat(t))}})),mode:"dropdown"}},this._addChips)))}},{key:"updated",value:function(t){var n;$i(e,"updated",this,3)([t]);var o=t.has("_attached"),i=t.has("chips");if(i||o)return o&&!this._attached?(null===(n=this._sortable)||void 0===n||n.destroy(),void(this._sortable=void 0)):void(this._sortable||!this.chips?i&&this._handleChipsChanged():this._createSortable())}},{key:"_handleChipsChanged",value:(i=tr(Zi().m((function t(){var e;return Zi().w((function(t){for(;;)switch(t.n){case 0:return this._renderEmptySortable=!0,t.n=1,this.updateComplete;case 1:for(e=this.shadowRoot.querySelector(".chips");e.lastElementChild;)e.removeChild(e.lastElementChild);this._renderEmptySortable=!1;case 2:return t.a(2)}}),t,this)}))),function(){return i.apply(this,arguments)})},{key:"_createSortable",value:(o=tr(Zi().m((function t(){var e,n=this;return Zi().w((function(t){for(;;)switch(t.n){case 0:if(NC){t.n=2;break}return t.n=1,Promise.resolve().then((function(){return PS}));case 1:e=t.v,(NC=e.Sortable).mount(e.OnSpill),NC.mount(e.AutoScroll());case 2:this._sortable=new NC(this.shadowRoot.querySelector(".chips"),{animation:150,fallbackClass:"sortable-fallback",handle:".handle",onEnd:function(){var t=tr(Zi().m((function t(e){return Zi().w((function(t){for(;;)if(0===t.n)return t.a(2,n._chipMoved(e))}),t)})));return function(e){return t.apply(this,arguments)}}()});case 3:return t.a(2)}}),t,this)}))),function(){return o.apply(this,arguments)})},{key:"_addChips",value:(n=tr(Zi().m((function t(e){var n,o,i,r,a;return Zi().w((function(t){for(;;)switch(t.n){case 0:if(""!==(o=null!==(n=e.detail.value)&&void 0!==n?n:"")){t.n=1;break}return t.a(2);case 1:if("conditional"!==o){t.n=2;break}return t.n=2,Xv();case 2:if(!(r=_C(o))||!r.getStubConfig){t.n=4;break}return t.n=3,r.getStubConfig(this.hass);case 3:i=t.v,t.n=5;break;case 4:i={type:o};case 5:a=this.chips.concat(i),this._selectorKey++,Za(this,"chips-changed",{chips:a});case 6:return t.a(2)}}),t,this)}))),function(t){return n.apply(this,arguments)})},{key:"_chipMoved",value:function(t){if(t.oldIndex!==t.newIndex){var e=this.chips.concat();e.splice(t.newIndex,0,e.splice(t.oldIndex,1)[0]),Za(this,"chips-changed",{chips:e})}}},{key:"_removeChip",value:function(t){var e=t.currentTarget.index,n=this.chips.concat();n.splice(e,1),Za(this,"chips-changed",{chips:n})}},{key:"_editChip",value:function(t){var e=t.currentTarget.index;Za(this,"edit-detail-element",{subElementConfig:{index:e,type:"chip",elementConfig:this.chips[e]}})}},{key:"_renderChipLabel",value:function(t){return vd(this.hass)("editor.chip.chip-picker.types.".concat(t.type))}},{key:"_renderChipSecondary",value:function(t){var e,n,o=vd(this.hass);if("entity"in t&&t.entity)return"".concat(null!==(n=null!==(e=this.getEntityName(t.entity))&&void 0!==e?e:t.entity)&&void 0!==n?n:"");if("chip"in t&&t.chip){var i=o("editor.chip.chip-picker.types.".concat(t.chip.type));return this._renderChipSecondary(t.chip)?"".concat(this._renderChipSecondary(t.chip)," (via ").concat(i,")"):i}return""}},{key:"getEntityName",value:function(t){if(this.hass){var e=this.hass.states[t];if(e)return e.attributes.friendly_name}}}],[{key:"styles",get:function(){return[$i(e,"styles",this),vl,Tr(fi||(fi=Li(["\n .chip {\n display: flex;\n align-items: center;\n }\n\n ha-icon {\n display: flex;\n }\n\n ha-select {\n width: 100%;\n }\n\n .chip .handle {\n padding-right: 8px;\n cursor: move;\n }\n\n .chip .handle > * {\n pointer-events: none;\n }\n\n .special-row {\n height: 60px;\n font-size: 16px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n flex-grow: 1;\n }\n\n .special-row div {\n display: flex;\n flex-direction: column;\n }\n\n .remove-icon,\n .edit-icon {\n --mdc-icon-button-size: 36px;\n color: var(--secondary-text-color);\n }\n\n .secondary {\n font-size: 12px;\n color: var(--secondary-text-color);\n }\n "])))]}}]);var n,o,i}(); +/** + * @license + * Copyright 2020 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */br([Na({attribute:!1})],UC.prototype,"chips",void 0),br([Na()],UC.prototype,"label",void 0),br([Ba()],UC.prototype,"_attached",void 0),br([Ba()],UC.prototype,"_renderEmptySortable",void 0),br([Ba()],UC.prototype,"_selectorKey",void 0),UC=br([Ia("mushroom-chips-card-chips-editor")],UC);var VC=As({type:Es("action"),icon:Ss(Ts()),icon_color:Ss(Ts()),tap_action:Ss(ml),hold_action:Ss(ml),double_tap_action:Ss(ml)}),FC=As({type:Es("back"),icon:Ss(Ts()),icon_color:Ss(Ts())}),$C=As({type:Es("entity"),entity:Ss(Ts()),name:Ss(Ts()),content_info:Ss(Ts()),icon:Ss(Ts()),icon_color:Ss(Ts()),use_entity_picture:Ss(ws()),tap_action:Ss(ml),hold_action:Ss(ml),double_tap_action:Ss(ml)}),GC=As({type:Es("menu"),icon:Ss(Ts()),icon_color:Ss(Ts())}),KC=As({type:Es("quickbar"),icon:Ss(Ts()),mode:Ss(Cs(["command","device","entity"]))}),YC=As({type:Es("weather"),entity:Ss(Ts()),tap_action:Ss(ml),hold_action:Ss(ml),double_tap_action:Ss(ml),show_temperature:Ss(ws()),show_conditions:Ss(ws())}),qC=As({type:Es("conditional"),chip:Ss(bs()),conditions:Ss(ks(bs()))}),WC=As({type:Es("light"),entity:Ss(Ts()),name:Ss(Ts()),content_info:Ss(Ts()),icon:Ss(Ts()),use_light_color:Ss(ws()),tap_action:Ss(ml),hold_action:Ss(ml),double_tap_action:Ss(ml)}),XC=As({type:Es("template"),entity:Ss(Ts()),tap_action:Ss(ml),hold_action:Ss(ml),double_tap_action:Ss(ml),content:Ss(Ts()),icon:Ss(Ts()),icon_color:Ss(Ts()),picture:Ss(Ts()),entity_id:Ss(zs([Ts(),ks(Ts())]))}),ZC=As({type:Es("spacer")}),JC=ys((function(t){if(t&&"object"===mr(t)&&"type"in t)switch(t.type){case"action":return VC;case"back":return FC;case"entity":return $C;case"menu":return GC;case"quickbar":return KC;case"weather":return YC;case"conditional":return qC;case"light":return WC;case"template":return XC;case"spacer":return ZC}return As()})),QC=gs(ly,As({chips:ks(JC),alignment:Ss(Ts())})),tE=function(t){function e(){return hr(this,e),er(this,e,arguments)}return or(e,Zm),pr(e,[{key:"connectedCallback",value:function(){$i(e,"connectedCallback",this,3)([]),Yv()}},{key:"setConfig",value:function(t){ms(t,QC),this._config=t}},{key:"_title",get:function(){return this._config.title||""}},{key:"_theme",get:function(){return this._config.theme||""}},{key:"render",value:function(){var t;if(!this.hass||!this._config)return fa;if(this._subElementEditorConfig)return ha(mi||(mi=Li(["\n \n \n "])),this.hass,this._subElementEditorConfig,this._goBack,this._handleSubElementChanged);var e=vd(this.hass);return ha(vi||(vi=Li(['\n
\n \n \n \n

\n ','\n

\n
\n \n
\n \n ',"\n "])),n("migration.post"))}),this._revertToLegacy,n("migration.revert"),this._done,n("migration.ok")):fa,this.hass,o,e,this._computeLabel,this._computeHelper,this._valueChanged,this.hass.localize("ui.panel.lovelace.editor.card.generic.features"),this.hass,o,i,this._computeLabel,this._computeHelper,this._valueChanged,this.hass,r,null!==(t=this._config.features)&&void 0!==t?t:[],this._featuresChanged,this._editDetailElement)}},{key:"_featuresChanged",value:function(t){if(t.stopPropagation(),this._config&&this.hass){var e=t.detail.features,n=Object.assign(Object.assign({},this._config),{features:e});0===e.length&&delete n.features,Za(this,"config-changed",{config:n})}}},{key:"_editDetailElement",value:function(t){var e=this,n=t.detail.subElementConfig.index,o=this._config.features[n],i=this._featureContext(this._config);Za(this,"edit-sub-element",{config:o,saveConfig:function(t){return e._updateFeature(n,t)},context:i,type:"feature"})}},{key:"_updateFeature",value:function(t,e){var n=this._config.features.concat();n[t]=e;var o=Object.assign(Object.assign({},this._config),{features:n});Za(this,"config-changed",{config:o})}},{key:"_valueChanged",value:function(t){if(t.stopPropagation(),this._config&&this.hass){var e=t.detail.value,n=Object.assign({features:this._config.features},e);n.content_layout&&(n.vertical="vertical"===n.content_layout,delete n.content_layout),n.vertical||delete n.vertical,Za(this,"config-changed",{config:n})}}}],[{key:"styles",get:function(){return[Tr(Oi||(Oi=Li(['\n ha-form {\n display: block;\n margin-bottom: 24px;\n }\n .features-form {\n margin-bottom: 8px;\n }\n ha-expansion-panel {\n display: block;\n --expansion-panel-content-padding: 0;\n border-radius: 6px;\n --ha-card-border-radius: 6px;\n }\n ha-expansion-panel .content {\n padding: 12px;\n }\n ha-expansion-panel > *[slot="header"] {\n margin: 0;\n font-size: inherit;\n font-weight: inherit;\n }\n ha-expansion-panel ha-icon {\n color: var(--secondary-text-color);\n }\n ha-alert {\n margin-bottom: 16px;\n display: block;\n }\n ha-alert a {\n color: var(--primary-color);\n }\n ha-alert .actions {\n display: flex;\n width: 100%;\n flex: 1;\n align-items: flex-end;\n flex-direction: row;\n justify-content: flex-end;\n gap: 8px;\n margin-top: 8px;\n border-radius: 8px;\n }\n '])))]}}])}();br([Na({attribute:!1})],rx.prototype,"hass",void 0),br([Ba()],rx.prototype,"_config",void 0),br([Ba()],rx.prototype,"_legacyConfig",void 0),rx=br([Ia("mushroom-template-card-editor")],rx);var ax=Object.freeze({__proto__:null,get MushroomTemplateCardEditor(){return rx},TEMPLATE_CARD_HELPERS:ix,TEMPLATE_CARD_LABELS:nx,TILE_LABELS:ox}),sx=gs(ly,As({title:Ss(Ts()),subtitle:Ss(Ts()),alignment:Ss(Ts()),title_tap_action:Ss(ml),subtitle_tap_action:Ss(ml)})),lx=["navigate","url","perform-action","none"],cx=["title","subtitle","alignment","title_tap_action","subtitle_tap_action"],ux=Ws((function(t){return[{name:"title",selector:{template:{}}},{name:"subtitle",selector:{template:{}}},{name:"alignment",selector:{select:{options:Ky(t),mode:"dropdown"}}},{name:"title_tap_action",selector:{ui_action:{actions:lx}}},{name:"subtitle_tap_action",selector:{ui_action:{actions:lx}}}]})),hx=function(t){function e(){var t;return hr(this,e),(t=er(this,e,arguments))._computeLabel=function(e){var n=vd(t.hass);return cx.includes(e.name)?n("editor.card.title.".concat(e.name)):t.hass.localize("ui.panel.lovelace.editor.card.generic.".concat(e.name))},t}return or(e,Zm),pr(e,[{key:"connectedCallback",value:function(){$i(e,"connectedCallback",this,3)([]),Yv()}},{key:"setConfig",value:function(t){ms(t,sx),this._config=t}},{key:"render",value:function(){if(!this.hass||!this._config)return fa;var t=vd(this.hass),e=ux(t);return ha(Ii||(Ii=Li(["\n \n "])),this.hass,this._config,e,this._computeLabel,this._valueChanged)}},{key:"_valueChanged",value:function(t){Za(this,"config-changed",{config:t.detail.value})}}])}();br([Ba()],hx.prototype,"_config",void 0),hx=br([Ia(vy)],hx);var dx=Object.freeze({__proto__:null,get TitleCardEditor(){return hx}}),px=gs(ly,gs(Zy,Vy,Dy),As({show_buttons_control:Ss(ws()),collapsible_controls:Ss(ws())})),fx=["show_buttons_control"],mx=["more-info","navigate","url","perform-action","assist","none"],vx=Ws((function(t){return[{name:"entity",selector:{entity:{domain:wy}}},{name:"name",selector:{text:{}}},{name:"icon",selector:{icon:{}},context:{icon_entity:"entity"}}].concat(Ki(qy(t)),[{type:"grid",name:"",schema:[{name:"show_buttons_control",selector:{boolean:{}}},{name:"collapsible_controls",selector:{boolean:{}}}]}],Ki(Ry(mx)))})),gx=function(t){function e(){var t;return hr(this,e),(t=er(this,e,arguments))._computeLabel=function(e){var n=vd(t.hass);return Wy.includes(e.name)?n("editor.card.generic.".concat(e.name)):fx.includes(e.name)?n("editor.card.update.".concat(e.name)):t.hass.localize("ui.panel.lovelace.editor.card.generic.".concat(e.name))},t}return or(e,Zm),pr(e,[{key:"connectedCallback",value:function(){$i(e,"connectedCallback",this,3)([]),Yv()}},{key:"setConfig",value:function(t){ms(t,px),this._config=t}},{key:"render",value:function(){if(!this.hass||!this._config)return fa;var t=vd(this.hass),e=vx(t);return ha(ji||(ji=Li(["\n \n "])),this.hass,this._config,e,this._computeLabel,this._valueChanged)}},{key:"_valueChanged",value:function(t){Za(this,"config-changed",{config:t.detail.value})}}])}();br([Ba()],gx.prototype,"_config",void 0),gx=br([Ia(ky)],gx);var _x=Object.freeze({__proto__:null,get UpdateCardEditor(){return gx}}),yx=["on_off","start_pause","stop","locate","clean_spot","return_home"],bx=gs(ly,gs(Zy,Vy,Dy),As({icon_animation:Ss(ws()),commands:Ss(ks(Ts()))})),kx=["commands"],wx=Ws((function(t,e){return[{name:"entity",selector:{entity:{domain:Ty}}},{name:"name",selector:{text:{}}},{type:"grid",name:"",schema:[{name:"icon",selector:{icon:{}},context:{icon_entity:"entity"}},{name:"icon_animation",selector:{boolean:{}}}]}].concat(Ki(qy(e)),[{name:"commands",selector:{select:{mode:"list",multiple:!0,options:yx.map((function(n){return{value:n,label:"on_off"===n?e("editor.card.vacuum.commands_list.".concat(n)):t("ui.dialogs.more_info_control.vacuum.".concat(n))}}))}}}],Ki(Ry()))})),Cx=function(t){function e(){var t;return hr(this,e),(t=er(this,e,arguments))._computeLabel=function(e){var n=vd(t.hass);return Wy.includes(e.name)?n("editor.card.generic.".concat(e.name)):kx.includes(e.name)?n("editor.card.vacuum.".concat(e.name)):t.hass.localize("ui.panel.lovelace.editor.card.generic.".concat(e.name))},t}return or(e,Zm),pr(e,[{key:"connectedCallback",value:function(){$i(e,"connectedCallback",this,3)([]),Yv()}},{key:"setConfig",value:function(t){ms(t,bx),this._config=t}},{key:"render",value:function(){if(!this.hass||!this._config)return fa;var t=vd(this.hass),e=wx(this.hass.localize,t);return ha(Pi||(Pi=Li(["\n \n "])),this.hass,this._config,e,this._computeLabel,this._valueChanged)}},{key:"_valueChanged",value:function(t){Za(this,"config-changed",{config:t.detail.value})}}])}();br([Ba()],Cx.prototype,"_config",void 0),Cx=br([Ia(Sy)],Cx);var Ex=Object.freeze({__proto__:null,get VacuumCardEditor(){return Cx}}),xx=gs(As({type:Ts(),visibility:bs()}),Dy,As({entity:Ss(Ts()),area:Ss(Ts()),icon:Ss(Ts()),color:Ss(Ts()),label:Ss(Ts()),content:Ss(Ts()),picture:Ss(Ts()),entity_id:Ss(zs([Ts(),ks(Ts())]))})),Ax=["label","content"],Sx=[{name:"context",flatten:!0,type:"expandable",icon:"mdi:shape",schema:[{name:"entity",selector:{entity:{}}},{name:"area",selector:{area:{}}}]},{name:"content",flatten:!0,type:"expandable",icon:"mdi:text-short",schema:[{name:"label",selector:{template:{}}},{name:"content",selector:{template:{}}},{name:"color",selector:{template:{}}},{name:"icon",selector:{template:{}}},{name:"picture",selector:{template:{}}}]},{name:"interactions",type:"expandable",flatten:!0,icon:"mdi:gesture-tap",schema:[{name:"tap_action",selector:{ui_action:{default_action:"none"}}},{name:"",type:"optional_actions",flatten:!0,schema:["hold_action","double_tap_action"].map((function(t){return{name:t,selector:{ui_action:{default_action:"none"}}}}))}]}],Tx=function(t){function e(){var t;return hr(this,e),(t=er(this,e,arguments))._computeLabel=function(e){var n=vd(t.hass);return"expandable"===e.type?n("editor.section.".concat(e.name)):Wy.includes(e.name)?n("editor.card.generic.".concat(e.name)):Ax.includes(e.name)?n("editor.card.template.".concat(e.name)):t.hass.localize("ui.panel.lovelace.editor.card.generic.".concat(e.name))},t._computeHelper=function(e){if("expandable"!==e.type){var n=vd(t.hass);return Xy.includes(e.name)?n("editor.card.generic.".concat(e.name,"_helper")):Ax.includes(e.name)?n("editor.card.template.".concat(e.name,"_helper")):void 0}},t}return or(e,za),pr(e,[{key:"connectedCallback",value:function(){$i(e,"connectedCallback",this,3)([]),Yv()}},{key:"setConfig",value:function(t){ms(t,xx),this._config=t}},{key:"render",value:function(){return this.hass&&this._config?ha(Ni||(Ni=Li(["\n \n "])),this.hass,this._config,Sx,this._computeLabel,this._computeHelper,this._valueChanged):fa}},{key:"_valueChanged",value:function(t){Za(this,"config-changed",{config:t.detail.value})}}])}();br([Na({attribute:!1})],Tx.prototype,"hass",void 0),br([Ba()],Tx.prototype,"_config",void 0),Tx=br([Ia("mushroom-template-badge-editor")],Tx);var Mx=Object.freeze({__proto__:null,get MushroomTemplateBadgeEditor(){return Tx},TEMPLATE_BADGE_LABELS:Ax}); /**! * Sortable 1.15.6 * @author RubaXa * @author owenm * @license MIT - */function Wz(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);t&&(i=i.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,i)}return n}function qz(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function eP(e){if("undefined"!=typeof window&&window.navigator)return!!navigator.userAgent.match(e)}var tP=eP(/(?:Trident.*rv[ :]?11\.|msie|iemobile|Windows Phone)/i),nP=eP(/Edge/i),iP=eP(/firefox/i),oP=eP(/safari/i)&&!eP(/chrome/i)&&!eP(/android/i),rP=eP(/iP(ad|od|hone)/i),aP=eP(/chrome/i)&&eP(/android/i),lP={capture:!1,passive:!1};function sP(e,t,n){e.addEventListener(t,n,!tP&&lP)}function cP(e,t,n){e.removeEventListener(t,n,!tP&&lP)}function uP(e,t){if(t){if(">"===t[0]&&(t=t.substring(1)),e)try{if(e.matches)return e.matches(t);if(e.msMatchesSelector)return e.msMatchesSelector(t);if(e.webkitMatchesSelector)return e.webkitMatchesSelector(t)}catch(e){return!1}return!1}}function dP(e){return e.host&&e!==document&&e.host.nodeType?e.host:e.parentNode}function hP(e,t,n,i){if(e){n=n||document;do{if(null!=t&&(">"===t[0]?e.parentNode===n&&uP(e,t):uP(e,t))||i&&e===n)return e;if(e===n)break}while(e=dP(e))}return null}var pP,fP=/\s+/g;function mP(e,t,n){if(e&&t)if(e.classList)e.classList[n?"add":"remove"](t);else{var i=(" "+e.className+" ").replace(fP," ").replace(" "+t+" "," ");e.className=(i+(n?" "+t:"")).replace(fP," ")}}function vP(e,t,n){var i=e&&e.style;if(i){if(void 0===n)return document.defaultView&&document.defaultView.getComputedStyle?n=document.defaultView.getComputedStyle(e,""):e.currentStyle&&(n=e.currentStyle),void 0===t?n:n[t];t in i||-1!==t.indexOf("webkit")||(t="-webkit-"+t),i[t]=n+("string"==typeof n?"":"px")}}function gP(e,t){var n="";if("string"==typeof e)n=e;else do{var i=vP(e,"transform");i&&"none"!==i&&(n=i+" "+n)}while(!t&&(e=e.parentNode));var o=window.DOMMatrix||window.WebKitCSSMatrix||window.CSSMatrix||window.MSCSSMatrix;return o&&new o(n)}function _P(e,t,n){if(e){var i=e.getElementsByTagName(t),o=0,r=i.length;if(n)for(;o=bP(i)[n]))return i;if(i===yP())break;i=AP(i,!1)}return!1}function wP(e,t,n,i){for(var o=0,r=0,a=e.children;r2&&void 0!==arguments[2]?arguments[2]:{},i=n.evt,o=Qz(n,$P);jP.pluginEvent.bind(OL)(e,t,qz({dragEl:HP,parentEl:DP,ghostEl:UP,rootEl:VP,nextEl:FP,lastDownEl:GP,cloneEl:YP,cloneHidden:KP,dragStarted:aL,putSortable:QP,activeSortable:OL.active,originalEvent:i,oldIndex:WP,oldDraggableIndex:XP,newIndex:qP,newDraggableIndex:ZP,hideGhostForTarget:AL,unhideGhostForTarget:SL,cloneNowHidden:function(){KP=!0},cloneNowShown:function(){KP=!1},dispatchSortableEvent:function(e){RP({sortable:t,name:e,originalEvent:i})}},o))};function RP(e){!function(e){var t=e.sortable,n=e.rootEl,i=e.name,o=e.targetEl,r=e.cloneEl,a=e.toEl,l=e.fromEl,s=e.oldIndex,c=e.newIndex,u=e.oldDraggableIndex,d=e.newDraggableIndex,h=e.originalEvent,p=e.putSortable,f=e.extraEventProperties;if(t=t||n&&n[zP]){var m,v=t.options,g="on"+i.charAt(0).toUpperCase()+i.substr(1);!window.CustomEvent||tP||nP?(m=document.createEvent("Event")).initEvent(i,!0,!0):m=new CustomEvent(i,{bubbles:!0,cancelable:!0}),m.to=a||n,m.from=l||n,m.item=o||n,m.clone=r,m.oldIndex=s,m.newIndex=c,m.oldDraggableIndex=u,m.newDraggableIndex=d,m.originalEvent=h,m.pullMode=p?p.lastPutMode:void 0;var _=qz(qz({},f),jP.getEventProperties(i,t));for(var y in _)m[y]=_[y];n&&n.dispatchEvent(m),v[g]&&v[g].call(t,m)}}(qz({putSortable:QP,cloneEl:YP,targetEl:HP,rootEl:VP,oldIndex:WP,oldDraggableIndex:XP,newIndex:qP,newDraggableIndex:ZP},e))}var HP,DP,UP,VP,FP,GP,YP,KP,WP,qP,XP,ZP,JP,QP,eL,tL,nL,iL,oL,rL,aL,lL,sL,cL,uL,dL=!1,hL=!1,pL=[],fL=!1,mL=!1,vL=[],gL=!1,_L=[],yL="undefined"!=typeof document,bL=rP,kL=nP||tP?"cssFloat":"float",wL=yL&&!aP&&!rP&&"draggable"in document.createElement("div"),xL=function(){if(yL){if(tP)return!1;var e=document.createElement("x");return e.style.cssText="pointer-events:auto","auto"===e.style.pointerEvents}}(),EL=function(e,t){var n=vP(e),i=parseInt(n.width)-parseInt(n.paddingLeft)-parseInt(n.paddingRight)-parseInt(n.borderLeftWidth)-parseInt(n.borderRightWidth),o=wP(e,0,t),r=wP(e,1,t),a=o&&vP(o),l=r&&vP(r),s=a&&parseInt(a.marginLeft)+parseInt(a.marginRight)+bP(o).width,c=l&&parseInt(l.marginLeft)+parseInt(l.marginRight)+bP(r).width;if("flex"===n.display)return"column"===n.flexDirection||"column-reverse"===n.flexDirection?"vertical":"horizontal";if("grid"===n.display)return n.gridTemplateColumns.split(" ").length<=1?"vertical":"horizontal";if(o&&a.float&&"none"!==a.float){var u="left"===a.float?"left":"right";return!r||"both"!==l.clear&&l.clear!==u?"horizontal":"vertical"}return o&&("block"===a.display||"flex"===a.display||"table"===a.display||"grid"===a.display||s>=i&&"none"===n[kL]||r&&"none"===n[kL]&&s+c>i)?"vertical":"horizontal"},CL=function(e){function t(e,n){return function(i,o,r,a){var l=i.options.group.name&&o.options.group.name&&i.options.group.name===o.options.group.name;if(null==e&&(n||l))return!0;if(null==e||!1===e)return!1;if(n&&"clone"===e)return e;if("function"==typeof e)return t(e(i,o,r,a),n)(i,o,r,a);var s=(n?i:o).options.group.name;return!0===e||"string"==typeof e&&e===s||e.join&&e.indexOf(s)>-1}}var n={},i=e.group;i&&"object"==Xz(i)||(i={name:i}),n.name=i.name,n.checkPull=t(i.pull,!0),n.checkPut=t(i.put),n.revertClone=i.revertClone,e.group=n},AL=function(){!xL&&UP&&vP(UP,"display","none")},SL=function(){!xL&&UP&&vP(UP,"display","")};yL&&!aP&&document.addEventListener("click",(function(e){if(hL)return e.preventDefault(),e.stopPropagation&&e.stopPropagation(),e.stopImmediatePropagation&&e.stopImmediatePropagation(),hL=!1,!1}),!0);var TL=function(e){if(HP){var t=function(e,t){var n;return pL.some((function(i){var o=i[zP].options.emptyInsertThreshold;if(o&&!xP(i)){var r=bP(i),a=e>=r.left-o&&e<=r.right+o,l=t>=r.top-o&&t<=r.bottom+o;return a&&l?n=i:void 0}})),n}((e=e.touches?e.touches[0]:e).clientX,e.clientY);if(t){var n={};for(var i in e)e.hasOwnProperty(i)&&(n[i]=e[i]);n.target=n.rootEl=t,n.preventDefault=void 0,n.stopPropagation=void 0,t[zP]._onDragOver(n)}}},IL=function(e){HP&&HP.parentNode[zP]._isOutsideThisEl(e.target)};function OL(e,t){if(!e||!e.nodeType||1!==e.nodeType)throw"Sortable: `el` must be an HTMLElement, not ".concat({}.toString.call(e));this.el=e,this.options=t=Jz({},t),e[zP]=this;var n={group:null,sort:!0,disabled:!1,store:null,handle:null,draggable:/^[uo]l$/i.test(e.nodeName)?">li":">*",swapThreshold:1,invertSwap:!1,invertedSwapThreshold:null,removeCloneOnHide:!0,direction:function(){return EL(e,this.options)},ghostClass:"sortable-ghost",chosenClass:"sortable-chosen",dragClass:"sortable-drag",ignore:"a, img",filter:null,preventOnFilter:!0,animation:0,easing:null,setData:function(e,t){e.setData("Text",t.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0,delayOnTouchOnly:!1,touchStartThreshold:(Number.parseInt?Number:window).parseInt(window.devicePixelRatio,10)||1,forceFallback:!1,fallbackClass:"sortable-fallback",fallbackOnBody:!1,fallbackTolerance:0,fallbackOffset:{x:0,y:0},supportPointer:!1!==OL.supportPointer&&"PointerEvent"in window&&(!oP||rP),emptyInsertThreshold:5};for(var i in jP.initializePlugins(this,e,n),n)!(i in t)&&(t[i]=n[i]);for(var o in CL(t),this)"_"===o.charAt(0)&&"function"==typeof this[o]&&(this[o]=this[o].bind(this));this.nativeDraggable=!t.forceFallback&&wL,this.nativeDraggable&&(this.options.touchStartThreshold=1),t.supportPointer?sP(e,"pointerdown",this._onTapStart):(sP(e,"mousedown",this._onTapStart),sP(e,"touchstart",this._onTapStart)),this.nativeDraggable&&(sP(e,"dragover",this),sP(e,"dragenter",this)),pL.push(this.el),t.store&&t.store.get&&this.sort(t.store.get(this)||[]),Jz(this,PP())}function ML(e,t,n,i,o,r,a,l){var s,c,u=e[zP],d=u.options.onMove;return!window.CustomEvent||tP||nP?(s=document.createEvent("Event")).initEvent("move",!0,!0):s=new CustomEvent("move",{bubbles:!0,cancelable:!0}),s.to=t,s.from=e,s.dragged=n,s.draggedRect=i,s.related=o||t,s.relatedRect=r||bP(t),s.willInsertAfter=l,s.originalEvent=a,e.dispatchEvent(s),d&&(c=d.call(u,s,a)),c}function zL(e){e.draggable=!1}function PL(){gL=!1}function LL(e){for(var t=e.tagName+e.className+e.src+e.href+e.textContent,n=t.length,i=0;n--;)i+=t.charCodeAt(n);return i.toString(36)}function NL(e){return setTimeout(e,0)}function jL(e){return clearTimeout(e)}OL.prototype={constructor:OL,_isOutsideThisEl:function(e){this.el.contains(e)||e===this.el||(lL=null)},_getDirection:function(e,t){return"function"==typeof this.options.direction?this.options.direction.call(this,e,t,HP):this.options.direction},_onTapStart:function(e){if(e.cancelable){var t=this,n=this.el,i=this.options,o=i.preventOnFilter,r=e.type,a=e.touches&&e.touches[0]||e.pointerType&&"touch"===e.pointerType&&e,l=(a||e).target,s=e.target.shadowRoot&&(e.path&&e.path[0]||e.composedPath&&e.composedPath()[0])||l,c=i.filter;if(function(e){_L.length=0;var t=e.getElementsByTagName("input"),n=t.length;for(;n--;){var i=t[n];i.checked&&_L.push(i)}}(n),!HP&&!(/mousedown|pointerdown/.test(r)&&0!==e.button||i.disabled)&&!s.isContentEditable&&(this.nativeDraggable||!oP||!l||"SELECT"!==l.tagName.toUpperCase())&&!((l=hP(l,i.draggable,n,!1))&&l.animated||GP===l)){if(WP=EP(l),XP=EP(l,i.draggable),"function"==typeof c){if(c.call(this,e,l,this))return RP({sortable:t,rootEl:s,name:"filter",targetEl:l,toEl:n,fromEl:n}),BP("filter",t,{evt:e}),void(o&&e.preventDefault())}else if(c&&(c=c.split(",").some((function(i){if(i=hP(s,i.trim(),n,!1))return RP({sortable:t,rootEl:i,name:"filter",targetEl:l,fromEl:n,toEl:n}),BP("filter",t,{evt:e}),!0}))))return void(o&&e.preventDefault());i.handle&&!hP(s,i.handle,n,!1)||this._prepareDragStart(e,a,l)}}},_prepareDragStart:function(e,t,n){var i,o=this,r=o.el,a=o.options,l=r.ownerDocument;if(n&&!HP&&n.parentNode===r){var s=bP(n);if(VP=r,DP=(HP=n).parentNode,FP=HP.nextSibling,GP=n,JP=a.group,OL.dragged=HP,eL={target:HP,clientX:(t||e).clientX,clientY:(t||e).clientY},oL=eL.clientX-s.left,rL=eL.clientY-s.top,this._lastX=(t||e).clientX,this._lastY=(t||e).clientY,HP.style["will-change"]="all",i=function(){BP("delayEnded",o,{evt:e}),OL.eventCanceled?o._onDrop():(o._disableDelayedDragEvents(),!iP&&o.nativeDraggable&&(HP.draggable=!0),o._triggerDragStart(e,t),RP({sortable:o,name:"choose",originalEvent:e}),mP(HP,a.chosenClass,!0))},a.ignore.split(",").forEach((function(e){_P(HP,e.trim(),zL)})),sP(l,"dragover",TL),sP(l,"mousemove",TL),sP(l,"touchmove",TL),a.supportPointer?(sP(l,"pointerup",o._onDrop),!this.nativeDraggable&&sP(l,"pointercancel",o._onDrop)):(sP(l,"mouseup",o._onDrop),sP(l,"touchend",o._onDrop),sP(l,"touchcancel",o._onDrop)),iP&&this.nativeDraggable&&(this.options.touchStartThreshold=4,HP.draggable=!0),BP("delayStart",this,{evt:e}),!a.delay||a.delayOnTouchOnly&&!t||this.nativeDraggable&&(nP||tP))i();else{if(OL.eventCanceled)return void this._onDrop();a.supportPointer?(sP(l,"pointerup",o._disableDelayedDrag),sP(l,"pointercancel",o._disableDelayedDrag)):(sP(l,"mouseup",o._disableDelayedDrag),sP(l,"touchend",o._disableDelayedDrag),sP(l,"touchcancel",o._disableDelayedDrag)),sP(l,"mousemove",o._delayedDragTouchMoveHandler),sP(l,"touchmove",o._delayedDragTouchMoveHandler),a.supportPointer&&sP(l,"pointermove",o._delayedDragTouchMoveHandler),o._dragStartTimer=setTimeout(i,a.delay)}}},_delayedDragTouchMoveHandler:function(e){var t=e.touches?e.touches[0]:e;Math.max(Math.abs(t.clientX-this._lastX),Math.abs(t.clientY-this._lastY))>=Math.floor(this.options.touchStartThreshold/(this.nativeDraggable&&window.devicePixelRatio||1))&&this._disableDelayedDrag()},_disableDelayedDrag:function(){HP&&zL(HP),clearTimeout(this._dragStartTimer),this._disableDelayedDragEvents()},_disableDelayedDragEvents:function(){var e=this.el.ownerDocument;cP(e,"mouseup",this._disableDelayedDrag),cP(e,"touchend",this._disableDelayedDrag),cP(e,"touchcancel",this._disableDelayedDrag),cP(e,"pointerup",this._disableDelayedDrag),cP(e,"pointercancel",this._disableDelayedDrag),cP(e,"mousemove",this._delayedDragTouchMoveHandler),cP(e,"touchmove",this._delayedDragTouchMoveHandler),cP(e,"pointermove",this._delayedDragTouchMoveHandler)},_triggerDragStart:function(e,t){t=t||"touch"==e.pointerType&&e,!this.nativeDraggable||t?this.options.supportPointer?sP(document,"pointermove",this._onTouchMove):sP(document,t?"touchmove":"mousemove",this._onTouchMove):(sP(HP,"dragend",this),sP(VP,"dragstart",this._onDragStart));try{document.selection?NL((function(){document.selection.empty()})):window.getSelection().removeAllRanges()}catch(e){}},_dragStarted:function(e,t){if(dL=!1,VP&&HP){BP("dragStarted",this,{evt:t}),this.nativeDraggable&&sP(document,"dragover",IL);var n=this.options;!e&&mP(HP,n.dragClass,!1),mP(HP,n.ghostClass,!0),OL.active=this,e&&this._appendGhost(),RP({sortable:this,name:"start",originalEvent:t})}else this._nulling()},_emulateDragOver:function(){if(tL){this._lastX=tL.clientX,this._lastY=tL.clientY,AL();for(var e=document.elementFromPoint(tL.clientX,tL.clientY),t=e;e&&e.shadowRoot&&(e=e.shadowRoot.elementFromPoint(tL.clientX,tL.clientY))!==t;)t=e;if(HP.parentNode[zP]._isOutsideThisEl(e),t)do{if(t[zP]){if(t[zP]._onDragOver({clientX:tL.clientX,clientY:tL.clientY,target:e,rootEl:t})&&!this.options.dragoverBubble)break}e=t}while(t=dP(t));SL()}},_onTouchMove:function(e){if(eL){var t=this.options,n=t.fallbackTolerance,i=t.fallbackOffset,o=e.touches?e.touches[0]:e,r=UP&&gP(UP,!0),a=UP&&r&&r.a,l=UP&&r&&r.d,s=bL&&uL&&CP(uL),c=(o.clientX-eL.clientX+i.x)/(a||1)+(s?s[0]-vL[0]:0)/(a||1),u=(o.clientY-eL.clientY+i.y)/(l||1)+(s?s[1]-vL[1]:0)/(l||1);if(!OL.active&&!dL){if(n&&Math.max(Math.abs(o.clientX-this._lastX),Math.abs(o.clientY-this._lastY))o.right+r||e.clientY>i.bottom&&e.clientX>i.left:e.clientY>o.bottom+r||e.clientX>i.right&&e.clientY>i.top}(e,o,this)&&!m.animated){if(m===HP)return M(!1);if(m&&r===e.target&&(a=m),a&&(n=bP(a)),!1!==ML(VP,r,HP,t,a,n,e,!!a))return O(),m&&m.nextSibling?r.insertBefore(HP,m.nextSibling):r.appendChild(HP),DP=r,z(),M(!0)}else if(m&&function(e,t,n){var i=bP(wP(n.el,0,n.options,!0)),o=MP(n.el,n.options,UP),r=10;return t?e.clientXu+c*r/2:sd-cL)return-sL}else if(s>u+c*(1-o)/2&&sd-c*r/2))return s>u+c/2?1:-1;return 0}(e,a,n,o,k?1:l.swapThreshold,null==l.invertedSwapThreshold?l.swapThreshold:l.invertedSwapThreshold,mL,lL===a),0!==g){var C=EP(HP);do{C-=g,y=DP.children[C]}while(y&&("none"===vP(y,"display")||y===UP))}if(0===g||y===a)return M(!1);lL=a,sL=g;var A=a.nextElementSibling,S=!1,T=ML(VP,r,HP,t,a,n,e,S=1===g);if(!1!==T)return 1!==T&&-1!==T||(S=1===T),gL=!0,setTimeout(PL,30),O(),S&&!A?r.appendChild(HP):a.parentNode.insertBefore(HP,S?A:a),x&&IP(x,0,E-x.scrollTop),DP=HP.parentNode,void 0===_||mL||(cL=Math.abs(_-bP(a)[w])),z(),M(!0)}if(r.contains(HP))return M(!1)}return!1}function I(l,s){BP(l,p,qz({evt:e,isOwner:u,axis:o?"vertical":"horizontal",revert:i,dragRect:t,targetRect:n,canSort:d,fromSortable:h,target:a,completed:M,onMove:function(n,i){return ML(VP,r,HP,t,n,bP(n),e,i)},changed:z},s))}function O(){I("dragOverAnimationCapture"),p.captureAnimationState(),p!==h&&h.captureAnimationState()}function M(t){return I("dragOverCompleted",{insertion:t}),t&&(u?c._hideClone():c._showClone(p),p!==h&&(mP(HP,QP?QP.options.ghostClass:c.options.ghostClass,!1),mP(HP,l.ghostClass,!0)),QP!==p&&p!==OL.active?QP=p:p===OL.active&&QP&&(QP=null),h===p&&(p._ignoreWhileAnimating=a),p.animateAll((function(){I("dragOverAnimationComplete"),p._ignoreWhileAnimating=null})),p!==h&&(h.animateAll(),h._ignoreWhileAnimating=null)),(a===HP&&!HP.animated||a===r&&!a.animated)&&(lL=null),l.dragoverBubble||e.rootEl||a===document||(HP.parentNode[zP]._isOutsideThisEl(e.target),!t&&TL(e)),!l.dragoverBubble&&e.stopPropagation&&e.stopPropagation(),f=!0}function z(){qP=EP(HP),ZP=EP(HP,l.draggable),RP({sortable:p,name:"change",toEl:r,newIndex:qP,newDraggableIndex:ZP,originalEvent:e})}},_ignoreWhileAnimating:null,_offMoveEvents:function(){cP(document,"mousemove",this._onTouchMove),cP(document,"touchmove",this._onTouchMove),cP(document,"pointermove",this._onTouchMove),cP(document,"dragover",TL),cP(document,"mousemove",TL),cP(document,"touchmove",TL)},_offUpEvents:function(){var e=this.el.ownerDocument;cP(e,"mouseup",this._onDrop),cP(e,"touchend",this._onDrop),cP(e,"pointerup",this._onDrop),cP(e,"pointercancel",this._onDrop),cP(e,"touchcancel",this._onDrop),cP(document,"selectstart",this)},_onDrop:function(e){var t=this.el,n=this.options;qP=EP(HP),ZP=EP(HP,n.draggable),BP("drop",this,{evt:e}),DP=HP&&HP.parentNode,qP=EP(HP),ZP=EP(HP,n.draggable),OL.eventCanceled||(dL=!1,mL=!1,fL=!1,clearInterval(this._loopId),clearTimeout(this._dragStartTimer),jL(this.cloneId),jL(this._dragStartId),this.nativeDraggable&&(cP(document,"drop",this),cP(t,"dragstart",this._onDragStart)),this._offMoveEvents(),this._offUpEvents(),oP&&vP(document.body,"user-select",""),vP(HP,"transform",""),e&&(aL&&(e.cancelable&&e.preventDefault(),!n.dropBubble&&e.stopPropagation()),UP&&UP.parentNode&&UP.parentNode.removeChild(UP),(VP===DP||QP&&"clone"!==QP.lastPutMode)&&YP&&YP.parentNode&&YP.parentNode.removeChild(YP),HP&&(this.nativeDraggable&&cP(HP,"dragend",this),zL(HP),HP.style["will-change"]="",aL&&!dL&&mP(HP,QP?QP.options.ghostClass:this.options.ghostClass,!1),mP(HP,this.options.chosenClass,!1),RP({sortable:this,name:"unchoose",toEl:DP,newIndex:null,newDraggableIndex:null,originalEvent:e}),VP!==DP?(qP>=0&&(RP({rootEl:DP,name:"add",toEl:DP,fromEl:VP,originalEvent:e}),RP({sortable:this,name:"remove",toEl:DP,originalEvent:e}),RP({rootEl:DP,name:"sort",toEl:DP,fromEl:VP,originalEvent:e}),RP({sortable:this,name:"sort",toEl:DP,originalEvent:e})),QP&&QP.save()):qP!==WP&&qP>=0&&(RP({sortable:this,name:"update",toEl:DP,originalEvent:e}),RP({sortable:this,name:"sort",toEl:DP,originalEvent:e})),OL.active&&(null!=qP&&-1!==qP||(qP=WP,ZP=XP),RP({sortable:this,name:"end",toEl:DP,originalEvent:e}),this.save())))),this._nulling()},_nulling:function(){BP("nulling",this),VP=HP=DP=UP=FP=YP=GP=KP=eL=tL=aL=qP=ZP=WP=XP=lL=sL=QP=JP=OL.dragged=OL.ghost=OL.clone=OL.active=null,_L.forEach((function(e){e.checked=!0})),_L.length=nL=iL=0},handleEvent:function(e){switch(e.type){case"drop":case"dragend":this._onDrop(e);break;case"dragenter":case"dragover":HP&&(this._onDragOver(e),function(e){e.dataTransfer&&(e.dataTransfer.dropEffect="move");e.cancelable&&e.preventDefault()}(e));break;case"selectstart":e.preventDefault()}},toArray:function(){for(var e,t=[],n=this.el.children,i=0,o=n.length,r=this.options;i=0||(i[n]=t[n]);return i}(t,e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(t);for(o=0;o=0||Object.prototype.propertyIsEnumerable.call(t,n)&&(i[n]=t[n])}return i}function Bx(t){if("undefined"!=typeof window&&window.navigator)return!!navigator.userAgent.match(t)}var Lx=Bx(/(?:Trident.*rv[ :]?11\.|msie|iemobile|Windows Phone)/i),Hx=Bx(/Edge/i),Dx=Bx(/firefox/i),Rx=Bx(/safari/i)&&!Bx(/chrome/i)&&!Bx(/android/i),Ux=Bx(/iP(ad|od|hone)/i),Vx=Bx(/chrome/i)&&Bx(/android/i),Fx={capture:!1,passive:!1};function $x(t,e,n){t.addEventListener(e,n,!Lx&&Fx)}function Gx(t,e,n){t.removeEventListener(e,n,!Lx&&Fx)}function Kx(t,e){if(e){if(">"===e[0]&&(e=e.substring(1)),t)try{if(t.matches)return t.matches(e);if(t.msMatchesSelector)return t.msMatchesSelector(e);if(t.webkitMatchesSelector)return t.webkitMatchesSelector(e)}catch(t){return!1}return!1}}function Yx(t){return t.host&&t!==document&&t.host.nodeType?t.host:t.parentNode}function qx(t,e,n,o){if(t){n=n||document;do{if(null!=e&&(">"===e[0]?t.parentNode===n&&Kx(t,e):Kx(t,e))||o&&t===n)return t;if(t===n)break}while(t=Yx(t))}return null}var Wx,Xx=/\s+/g;function Zx(t,e,n){if(t&&e)if(t.classList)t.classList[n?"add":"remove"](e);else{var o=(" "+t.className+" ").replace(Xx," ").replace(" "+e+" "," ");t.className=(o+(n?" "+e:"")).replace(Xx," ")}}function Jx(t,e,n){var o=t&&t.style;if(o){if(void 0===n)return document.defaultView&&document.defaultView.getComputedStyle?n=document.defaultView.getComputedStyle(t,""):t.currentStyle&&(n=t.currentStyle),void 0===e?n:n[e];e in o||-1!==e.indexOf("webkit")||(e="-webkit-"+e),o[e]=n+("string"==typeof n?"":"px")}}function Qx(t,e){var n="";if("string"==typeof t)n=t;else do{var o=Jx(t,"transform");o&&"none"!==o&&(n=o+" "+n)}while(!e&&(t=t.parentNode));var i=window.DOMMatrix||window.WebKitCSSMatrix||window.CSSMatrix||window.MSCSSMatrix;return i&&new i(n)}function tA(t,e,n){if(t){var o=t.getElementsByTagName(e),i=0,r=o.length;if(n)for(;i=nA(o)[n]))return o;if(o===eA())break;o=lA(o,!1)}return!1}function iA(t,e,n,o){for(var i=0,r=0,a=t.children;r2&&void 0!==arguments[2]?arguments[2]:{},o=n.evt,i=Nx(n,yA);_A.pluginEvent.bind(dS)(t,e,Ox({dragEl:wA,parentEl:CA,ghostEl:EA,rootEl:xA,nextEl:AA,lastDownEl:SA,cloneEl:TA,cloneHidden:MA,dragStarted:VA,putSortable:NA,activeSortable:dS.active,originalEvent:o,oldIndex:zA,oldDraggableIndex:IA,newIndex:OA,newDraggableIndex:jA,hideGhostForTarget:lS,unhideGhostForTarget:cS,cloneNowHidden:function(){MA=!0},cloneNowShown:function(){MA=!1},dispatchSortableEvent:function(t){kA({sortable:e,name:t,originalEvent:o})}},i))};function kA(t){!function(t){var e=t.sortable,n=t.rootEl,o=t.name,i=t.targetEl,r=t.cloneEl,a=t.toEl,s=t.fromEl,l=t.oldIndex,c=t.newIndex,u=t.oldDraggableIndex,h=t.newDraggableIndex,d=t.originalEvent,p=t.putSortable,f=t.extraEventProperties;if(e=e||n&&n[fA]){var m,v=e.options,g="on"+o.charAt(0).toUpperCase()+o.substr(1);!window.CustomEvent||Lx||Hx?(m=document.createEvent("Event")).initEvent(o,!0,!0):m=new CustomEvent(o,{bubbles:!0,cancelable:!0}),m.to=a||n,m.from=s||n,m.item=i||n,m.clone=r,m.oldIndex=l,m.newIndex=c,m.oldDraggableIndex=u,m.newDraggableIndex=h,m.originalEvent=d,m.pullMode=p?p.lastPutMode:void 0;var _=Ox(Ox({},f),_A.getEventProperties(o,e));for(var y in _)m[y]=_[y];n&&n.dispatchEvent(m),v[g]&&v[g].call(e,m)}}(Ox({putSortable:NA,cloneEl:TA,targetEl:wA,rootEl:xA,oldIndex:zA,oldDraggableIndex:IA,newIndex:OA,newDraggableIndex:jA},t))}var wA,CA,EA,xA,AA,SA,TA,MA,zA,OA,IA,jA,PA,NA,BA,LA,HA,DA,RA,UA,VA,FA,$A,GA,KA,YA=!1,qA=!1,WA=[],XA=!1,ZA=!1,JA=[],QA=!1,tS=[],eS="undefined"!=typeof document,nS=Ux,oS=Hx||Lx?"cssFloat":"float",iS=eS&&!Vx&&!Ux&&"draggable"in document.createElement("div"),rS=function(){if(eS){if(Lx)return!1;var t=document.createElement("x");return t.style.cssText="pointer-events:auto","auto"===t.style.pointerEvents}}(),aS=function(t,e){var n=Jx(t),o=parseInt(n.width)-parseInt(n.paddingLeft)-parseInt(n.paddingRight)-parseInt(n.borderLeftWidth)-parseInt(n.borderRightWidth),i=iA(t,0,e),r=iA(t,1,e),a=i&&Jx(i),s=r&&Jx(r),l=a&&parseInt(a.marginLeft)+parseInt(a.marginRight)+nA(i).width,c=s&&parseInt(s.marginLeft)+parseInt(s.marginRight)+nA(r).width;if("flex"===n.display)return"column"===n.flexDirection||"column-reverse"===n.flexDirection?"vertical":"horizontal";if("grid"===n.display)return n.gridTemplateColumns.split(" ").length<=1?"vertical":"horizontal";if(i&&a.float&&"none"!==a.float){var u="left"===a.float?"left":"right";return!r||"both"!==s.clear&&s.clear!==u?"horizontal":"vertical"}return i&&("block"===a.display||"flex"===a.display||"table"===a.display||"grid"===a.display||l>=o&&"none"===n[oS]||r&&"none"===n[oS]&&l+c>o)?"vertical":"horizontal"},sS=function(t){function e(t,n){return function(o,i,r,a){var s=o.options.group.name&&i.options.group.name&&o.options.group.name===i.options.group.name;if(null==t&&(n||s))return!0;if(null==t||!1===t)return!1;if(n&&"clone"===t)return t;if("function"==typeof t)return e(t(o,i,r,a),n)(o,i,r,a);var l=(n?o:i).options.group.name;return!0===t||"string"==typeof t&&t===l||t.join&&t.indexOf(l)>-1}}var n={},o=t.group;o&&"object"==Ix(o)||(o={name:o}),n.name=o.name,n.checkPull=e(o.pull,!0),n.checkPut=e(o.put),n.revertClone=o.revertClone,t.group=n},lS=function(){!rS&&EA&&Jx(EA,"display","none")},cS=function(){!rS&&EA&&Jx(EA,"display","")};eS&&!Vx&&document.addEventListener("click",(function(t){if(qA)return t.preventDefault(),t.stopPropagation&&t.stopPropagation(),t.stopImmediatePropagation&&t.stopImmediatePropagation(),qA=!1,!1}),!0);var uS=function(t){if(wA){var e=function(t,e){var n;return WA.some((function(o){var i=o[fA].options.emptyInsertThreshold;if(i&&!rA(o)){var r=nA(o),a=t>=r.left-i&&t<=r.right+i,s=e>=r.top-i&&e<=r.bottom+i;return a&&s?n=o:void 0}})),n}((t=t.touches?t.touches[0]:t).clientX,t.clientY);if(e){var n={};for(var o in t)t.hasOwnProperty(o)&&(n[o]=t[o]);n.target=n.rootEl=e,n.preventDefault=void 0,n.stopPropagation=void 0,e[fA]._onDragOver(n)}}},hS=function(t){wA&&wA.parentNode[fA]._isOutsideThisEl(t.target)};function dS(t,e){if(!t||!t.nodeType||1!==t.nodeType)throw"Sortable: `el` must be an HTMLElement, not ".concat({}.toString.call(t));this.el=t,this.options=e=Px({},e),t[fA]=this;var n={group:null,sort:!0,disabled:!1,store:null,handle:null,draggable:/^[uo]l$/i.test(t.nodeName)?">li":">*",swapThreshold:1,invertSwap:!1,invertedSwapThreshold:null,removeCloneOnHide:!0,direction:function(){return aS(t,this.options)},ghostClass:"sortable-ghost",chosenClass:"sortable-chosen",dragClass:"sortable-drag",ignore:"a, img",filter:null,preventOnFilter:!0,animation:0,easing:null,setData:function(t,e){t.setData("Text",e.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0,delayOnTouchOnly:!1,touchStartThreshold:(Number.parseInt?Number:window).parseInt(window.devicePixelRatio,10)||1,forceFallback:!1,fallbackClass:"sortable-fallback",fallbackOnBody:!1,fallbackTolerance:0,fallbackOffset:{x:0,y:0},supportPointer:!1!==dS.supportPointer&&"PointerEvent"in window&&(!Rx||Ux),emptyInsertThreshold:5};for(var o in _A.initializePlugins(this,t,n),n)!(o in e)&&(e[o]=n[o]);for(var i in sS(e),this)"_"===i.charAt(0)&&"function"==typeof this[i]&&(this[i]=this[i].bind(this));this.nativeDraggable=!e.forceFallback&&iS,this.nativeDraggable&&(this.options.touchStartThreshold=1),e.supportPointer?$x(t,"pointerdown",this._onTapStart):($x(t,"mousedown",this._onTapStart),$x(t,"touchstart",this._onTapStart)),this.nativeDraggable&&($x(t,"dragover",this),$x(t,"dragenter",this)),WA.push(this.el),e.store&&e.store.get&&this.sort(e.store.get(this)||[]),Px(this,mA())}function pS(t,e,n,o,i,r,a,s){var l,c,u=t[fA],h=u.options.onMove;return!window.CustomEvent||Lx||Hx?(l=document.createEvent("Event")).initEvent("move",!0,!0):l=new CustomEvent("move",{bubbles:!0,cancelable:!0}),l.to=e,l.from=t,l.dragged=n,l.draggedRect=o,l.related=i||e,l.relatedRect=r||nA(e),l.willInsertAfter=s,l.originalEvent=a,t.dispatchEvent(l),h&&(c=h.call(u,l,a)),c}function fS(t){t.draggable=!1}function mS(){QA=!1}function vS(t){for(var e=t.tagName+t.className+t.src+t.href+t.textContent,n=e.length,o=0;n--;)o+=e.charCodeAt(n);return o.toString(36)}function gS(t){return setTimeout(t,0)}function _S(t){return clearTimeout(t)}dS.prototype={constructor:dS,_isOutsideThisEl:function(t){this.el.contains(t)||t===this.el||(FA=null)},_getDirection:function(t,e){return"function"==typeof this.options.direction?this.options.direction.call(this,t,e,wA):this.options.direction},_onTapStart:function(t){if(t.cancelable){var e=this,n=this.el,o=this.options,i=o.preventOnFilter,r=t.type,a=t.touches&&t.touches[0]||t.pointerType&&"touch"===t.pointerType&&t,s=(a||t).target,l=t.target.shadowRoot&&(t.path&&t.path[0]||t.composedPath&&t.composedPath()[0])||s,c=o.filter;if(function(t){tS.length=0;var e=t.getElementsByTagName("input"),n=e.length;for(;n--;){var o=e[n];o.checked&&tS.push(o)}}(n),!wA&&!(/mousedown|pointerdown/.test(r)&&0!==t.button||o.disabled)&&!l.isContentEditable&&(this.nativeDraggable||!Rx||!s||"SELECT"!==s.tagName.toUpperCase())&&!((s=qx(s,o.draggable,n,!1))&&s.animated||SA===s)){if(zA=aA(s),IA=aA(s,o.draggable),"function"==typeof c){if(c.call(this,t,s,this))return kA({sortable:e,rootEl:l,name:"filter",targetEl:s,toEl:n,fromEl:n}),bA("filter",e,{evt:t}),void(i&&t.preventDefault())}else if(c&&(c=c.split(",").some((function(o){if(o=qx(l,o.trim(),n,!1))return kA({sortable:e,rootEl:o,name:"filter",targetEl:s,fromEl:n,toEl:n}),bA("filter",e,{evt:t}),!0}))))return void(i&&t.preventDefault());o.handle&&!qx(l,o.handle,n,!1)||this._prepareDragStart(t,a,s)}}},_prepareDragStart:function(t,e,n){var o,i=this,r=i.el,a=i.options,s=r.ownerDocument;if(n&&!wA&&n.parentNode===r){var l=nA(n);if(xA=r,CA=(wA=n).parentNode,AA=wA.nextSibling,SA=n,PA=a.group,dS.dragged=wA,BA={target:wA,clientX:(e||t).clientX,clientY:(e||t).clientY},RA=BA.clientX-l.left,UA=BA.clientY-l.top,this._lastX=(e||t).clientX,this._lastY=(e||t).clientY,wA.style["will-change"]="all",o=function(){bA("delayEnded",i,{evt:t}),dS.eventCanceled?i._onDrop():(i._disableDelayedDragEvents(),!Dx&&i.nativeDraggable&&(wA.draggable=!0),i._triggerDragStart(t,e),kA({sortable:i,name:"choose",originalEvent:t}),Zx(wA,a.chosenClass,!0))},a.ignore.split(",").forEach((function(t){tA(wA,t.trim(),fS)})),$x(s,"dragover",uS),$x(s,"mousemove",uS),$x(s,"touchmove",uS),a.supportPointer?($x(s,"pointerup",i._onDrop),!this.nativeDraggable&&$x(s,"pointercancel",i._onDrop)):($x(s,"mouseup",i._onDrop),$x(s,"touchend",i._onDrop),$x(s,"touchcancel",i._onDrop)),Dx&&this.nativeDraggable&&(this.options.touchStartThreshold=4,wA.draggable=!0),bA("delayStart",this,{evt:t}),!a.delay||a.delayOnTouchOnly&&!e||this.nativeDraggable&&(Hx||Lx))o();else{if(dS.eventCanceled)return void this._onDrop();a.supportPointer?($x(s,"pointerup",i._disableDelayedDrag),$x(s,"pointercancel",i._disableDelayedDrag)):($x(s,"mouseup",i._disableDelayedDrag),$x(s,"touchend",i._disableDelayedDrag),$x(s,"touchcancel",i._disableDelayedDrag)),$x(s,"mousemove",i._delayedDragTouchMoveHandler),$x(s,"touchmove",i._delayedDragTouchMoveHandler),a.supportPointer&&$x(s,"pointermove",i._delayedDragTouchMoveHandler),i._dragStartTimer=setTimeout(o,a.delay)}}},_delayedDragTouchMoveHandler:function(t){var e=t.touches?t.touches[0]:t;Math.max(Math.abs(e.clientX-this._lastX),Math.abs(e.clientY-this._lastY))>=Math.floor(this.options.touchStartThreshold/(this.nativeDraggable&&window.devicePixelRatio||1))&&this._disableDelayedDrag()},_disableDelayedDrag:function(){wA&&fS(wA),clearTimeout(this._dragStartTimer),this._disableDelayedDragEvents()},_disableDelayedDragEvents:function(){var t=this.el.ownerDocument;Gx(t,"mouseup",this._disableDelayedDrag),Gx(t,"touchend",this._disableDelayedDrag),Gx(t,"touchcancel",this._disableDelayedDrag),Gx(t,"pointerup",this._disableDelayedDrag),Gx(t,"pointercancel",this._disableDelayedDrag),Gx(t,"mousemove",this._delayedDragTouchMoveHandler),Gx(t,"touchmove",this._delayedDragTouchMoveHandler),Gx(t,"pointermove",this._delayedDragTouchMoveHandler)},_triggerDragStart:function(t,e){e=e||"touch"==t.pointerType&&t,!this.nativeDraggable||e?this.options.supportPointer?$x(document,"pointermove",this._onTouchMove):$x(document,e?"touchmove":"mousemove",this._onTouchMove):($x(wA,"dragend",this),$x(xA,"dragstart",this._onDragStart));try{document.selection?gS((function(){document.selection.empty()})):window.getSelection().removeAllRanges()}catch(t){}},_dragStarted:function(t,e){if(YA=!1,xA&&wA){bA("dragStarted",this,{evt:e}),this.nativeDraggable&&$x(document,"dragover",hS);var n=this.options;!t&&Zx(wA,n.dragClass,!1),Zx(wA,n.ghostClass,!0),dS.active=this,t&&this._appendGhost(),kA({sortable:this,name:"start",originalEvent:e})}else this._nulling()},_emulateDragOver:function(){if(LA){this._lastX=LA.clientX,this._lastY=LA.clientY,lS();for(var t=document.elementFromPoint(LA.clientX,LA.clientY),e=t;t&&t.shadowRoot&&(t=t.shadowRoot.elementFromPoint(LA.clientX,LA.clientY))!==e;)e=t;if(wA.parentNode[fA]._isOutsideThisEl(t),e)do{if(e[fA]){if(e[fA]._onDragOver({clientX:LA.clientX,clientY:LA.clientY,target:t,rootEl:e})&&!this.options.dragoverBubble)break}t=e}while(e=Yx(e));cS()}},_onTouchMove:function(t){if(BA){var e=this.options,n=e.fallbackTolerance,o=e.fallbackOffset,i=t.touches?t.touches[0]:t,r=EA&&Qx(EA,!0),a=EA&&r&&r.a,s=EA&&r&&r.d,l=nS&&KA&&sA(KA),c=(i.clientX-BA.clientX+o.x)/(a||1)+(l?l[0]-JA[0]:0)/(a||1),u=(i.clientY-BA.clientY+o.y)/(s||1)+(l?l[1]-JA[1]:0)/(s||1);if(!dS.active&&!YA){if(n&&Math.max(Math.abs(i.clientX-this._lastX),Math.abs(i.clientY-this._lastY))i.right+r||t.clientY>o.bottom&&t.clientX>o.left:t.clientY>i.bottom+r||t.clientX>o.right&&t.clientY>o.top}(t,i,this)&&!m.animated){if(m===wA)return O(!1);if(m&&r===t.target&&(a=m),a&&(n=nA(a)),!1!==pS(xA,r,wA,e,a,n,t,!!a))return z(),m&&m.nextSibling?r.insertBefore(wA,m.nextSibling):r.appendChild(wA),CA=r,I(),O(!0)}else if(m&&function(t,e,n){var o=nA(iA(n.el,0,n.options,!0)),i=pA(n.el,n.options,EA),r=10;return e?t.clientXu+c*r/2:lh-GA)return-$A}else if(l>u+c*(1-i)/2&&lh-c*r/2))return l>u+c/2?1:-1;return 0}(t,a,n,i,k?1:s.swapThreshold,null==s.invertedSwapThreshold?s.swapThreshold:s.invertedSwapThreshold,ZA,FA===a),0!==g){var x=aA(wA);do{x-=g,y=CA.children[x]}while(y&&("none"===Jx(y,"display")||y===EA))}if(0===g||y===a)return O(!1);FA=a,$A=g;var A=a.nextElementSibling,S=!1,T=pS(xA,r,wA,e,a,n,t,S=1===g);if(!1!==T)return 1!==T&&-1!==T||(S=1===T),QA=!0,setTimeout(mS,30),z(),S&&!A?r.appendChild(wA):a.parentNode.insertBefore(wA,S?A:a),C&&hA(C,0,E-C.scrollTop),CA=wA.parentNode,void 0===_||ZA||(GA=Math.abs(_-nA(a)[w])),I(),O(!0)}if(r.contains(wA))return O(!1)}return!1}function M(s,l){bA(s,p,Ox({evt:t,isOwner:u,axis:i?"vertical":"horizontal",revert:o,dragRect:e,targetRect:n,canSort:h,fromSortable:d,target:a,completed:O,onMove:function(n,o){return pS(xA,r,wA,e,n,nA(n),t,o)},changed:I},l))}function z(){M("dragOverAnimationCapture"),p.captureAnimationState(),p!==d&&d.captureAnimationState()}function O(e){return M("dragOverCompleted",{insertion:e}),e&&(u?c._hideClone():c._showClone(p),p!==d&&(Zx(wA,NA?NA.options.ghostClass:c.options.ghostClass,!1),Zx(wA,s.ghostClass,!0)),NA!==p&&p!==dS.active?NA=p:p===dS.active&&NA&&(NA=null),d===p&&(p._ignoreWhileAnimating=a),p.animateAll((function(){M("dragOverAnimationComplete"),p._ignoreWhileAnimating=null})),p!==d&&(d.animateAll(),d._ignoreWhileAnimating=null)),(a===wA&&!wA.animated||a===r&&!a.animated)&&(FA=null),s.dragoverBubble||t.rootEl||a===document||(wA.parentNode[fA]._isOutsideThisEl(t.target),!e&&uS(t)),!s.dragoverBubble&&t.stopPropagation&&t.stopPropagation(),f=!0}function I(){OA=aA(wA),jA=aA(wA,s.draggable),kA({sortable:p,name:"change",toEl:r,newIndex:OA,newDraggableIndex:jA,originalEvent:t})}},_ignoreWhileAnimating:null,_offMoveEvents:function(){Gx(document,"mousemove",this._onTouchMove),Gx(document,"touchmove",this._onTouchMove),Gx(document,"pointermove",this._onTouchMove),Gx(document,"dragover",uS),Gx(document,"mousemove",uS),Gx(document,"touchmove",uS)},_offUpEvents:function(){var t=this.el.ownerDocument;Gx(t,"mouseup",this._onDrop),Gx(t,"touchend",this._onDrop),Gx(t,"pointerup",this._onDrop),Gx(t,"pointercancel",this._onDrop),Gx(t,"touchcancel",this._onDrop),Gx(document,"selectstart",this)},_onDrop:function(t){var e=this.el,n=this.options;OA=aA(wA),jA=aA(wA,n.draggable),bA("drop",this,{evt:t}),CA=wA&&wA.parentNode,OA=aA(wA),jA=aA(wA,n.draggable),dS.eventCanceled||(YA=!1,ZA=!1,XA=!1,clearInterval(this._loopId),clearTimeout(this._dragStartTimer),_S(this.cloneId),_S(this._dragStartId),this.nativeDraggable&&(Gx(document,"drop",this),Gx(e,"dragstart",this._onDragStart)),this._offMoveEvents(),this._offUpEvents(),Rx&&Jx(document.body,"user-select",""),Jx(wA,"transform",""),t&&(VA&&(t.cancelable&&t.preventDefault(),!n.dropBubble&&t.stopPropagation()),EA&&EA.parentNode&&EA.parentNode.removeChild(EA),(xA===CA||NA&&"clone"!==NA.lastPutMode)&&TA&&TA.parentNode&&TA.parentNode.removeChild(TA),wA&&(this.nativeDraggable&&Gx(wA,"dragend",this),fS(wA),wA.style["will-change"]="",VA&&!YA&&Zx(wA,NA?NA.options.ghostClass:this.options.ghostClass,!1),Zx(wA,this.options.chosenClass,!1),kA({sortable:this,name:"unchoose",toEl:CA,newIndex:null,newDraggableIndex:null,originalEvent:t}),xA!==CA?(OA>=0&&(kA({rootEl:CA,name:"add",toEl:CA,fromEl:xA,originalEvent:t}),kA({sortable:this,name:"remove",toEl:CA,originalEvent:t}),kA({rootEl:CA,name:"sort",toEl:CA,fromEl:xA,originalEvent:t}),kA({sortable:this,name:"sort",toEl:CA,originalEvent:t})),NA&&NA.save()):OA!==zA&&OA>=0&&(kA({sortable:this,name:"update",toEl:CA,originalEvent:t}),kA({sortable:this,name:"sort",toEl:CA,originalEvent:t})),dS.active&&(null!=OA&&-1!==OA||(OA=zA,jA=IA),kA({sortable:this,name:"end",toEl:CA,originalEvent:t}),this.save())))),this._nulling()},_nulling:function(){bA("nulling",this),xA=wA=CA=EA=AA=TA=SA=MA=BA=LA=VA=OA=jA=zA=IA=FA=$A=NA=PA=dS.dragged=dS.ghost=dS.clone=dS.active=null,tS.forEach((function(t){t.checked=!0})),tS.length=HA=DA=0},handleEvent:function(t){switch(t.type){case"drop":case"dragend":this._onDrop(t);break;case"dragenter":case"dragover":wA&&(this._onDragOver(t),function(t){t.dataTransfer&&(t.dataTransfer.dropEffect="move");t.cancelable&&t.preventDefault()}(t));break;case"selectstart":t.preventDefault()}},toArray:function(){for(var t,e=[],n=this.el.children,o=0,i=n.length,r=this.options;o