Merge branch 'pr68' into claude/home-assistant-i18n-iwwyvr
This commit is contained in:
@@ -368,22 +368,27 @@ class LocalThingsConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
|
||||
if has_creds:
|
||||
schema = vol.Schema({vol.Required(CONF_HOST): _TEXT})
|
||||
reuse_note = "CA credentials will be reused from the existing device."
|
||||
step_id = "user_reuse"
|
||||
else:
|
||||
schema = vol.Schema({
|
||||
vol.Required(CONF_HOST): _TEXT,
|
||||
vol.Required(CONF_CA_CERT_PEM): _MULTILINE,
|
||||
vol.Required(CONF_CA_KEY_PEM): _MULTILINE,
|
||||
})
|
||||
reuse_note = ""
|
||||
step_id = "user"
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
step_id=step_id,
|
||||
data_schema=schema,
|
||||
errors=errors,
|
||||
description_placeholders={"reuse_note": reuse_note},
|
||||
)
|
||||
|
||||
async def async_step_user_reuse(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle the localized host-only form for additional appliances."""
|
||||
return await self.async_step_user(user_input)
|
||||
|
||||
async def async_step_confirm_unknown_type(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
@@ -391,14 +396,28 @@ class LocalThingsConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
if user_input is not None:
|
||||
return self._create_entry(self._pending_info)
|
||||
|
||||
if not self._pending_info["one_ui_version"]:
|
||||
return await self.async_step_confirm_unknown_type_no_version()
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="confirm_unknown_type",
|
||||
data_schema=vol.Schema({}),
|
||||
description_placeholders={
|
||||
"one_ui_version": self._pending_info["one_ui_version"] or "(none reported)",
|
||||
"one_ui_version": self._pending_info["one_ui_version"],
|
||||
},
|
||||
)
|
||||
|
||||
async def async_step_confirm_unknown_type_no_version(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Confirm an unknown appliance that did not report oneUiVersion."""
|
||||
if user_input is not None:
|
||||
return self._create_entry(self._pending_info)
|
||||
return self.async_show_form(
|
||||
step_id="confirm_unknown_type_no_version",
|
||||
data_schema=vol.Schema({}),
|
||||
)
|
||||
|
||||
|
||||
class LocalThingsOptionsFlow(config_entries.OptionsFlow):
|
||||
"""Per-device options: the remote-control-off write-block override
|
||||
@@ -487,7 +506,7 @@ class LocalThingsOptionsFlow(config_entries.OptionsFlow):
|
||||
"href": href,
|
||||
"current_value": (
|
||||
json.dumps(current, indent=2, ensure_ascii=False)
|
||||
if current else "(no cached value)"
|
||||
if current else "{}"
|
||||
),
|
||||
},
|
||||
)
|
||||
@@ -531,7 +550,7 @@ class LocalThingsOptionsFlow(config_entries.OptionsFlow):
|
||||
"code": f"{code >> 5}.{code & 0x1f:02d} ({code:#04x})",
|
||||
"new_value": (
|
||||
json.dumps(new_rep, indent=2, ensure_ascii=False)
|
||||
if new_rep else "(no value returned)"
|
||||
if new_rep else "{}"
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -38,13 +38,6 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
_SEED_PATH = ['device', '0']
|
||||
|
||||
_REMOTE_CONTROL_DISABLED_MESSAGE = (
|
||||
"Remote control is turned off on this device. Check your appliance's "
|
||||
"manual for how to enable remote control before Home Assistant can "
|
||||
"control it."
|
||||
)
|
||||
|
||||
|
||||
class _NoOpDescriptor:
|
||||
"""StateCache requires a descriptor with an on_observation hook. This
|
||||
integration doesn't use per-capability observation hooks, so this is a
|
||||
@@ -548,12 +541,18 @@ class LocalThingsCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
resources = self._cache.snapshot()
|
||||
bypass_remote_control = self._entry.options.get(CONF_BYPASS_REMOTE_CONTROL, False)
|
||||
if not bypass_remote_control and not remote_control_enabled(resources):
|
||||
raise ServiceValidationError(_REMOTE_CONTROL_DISABLED_MESSAGE)
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="remote_control_disabled",
|
||||
)
|
||||
validate_fn = getattr(desc, 'validate_fn', None)
|
||||
if validate_fn is not None:
|
||||
error = validate_fn(payload, rep, resources)
|
||||
if error:
|
||||
raise ServiceValidationError(error)
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key=error,
|
||||
)
|
||||
result = write_fn(payload, rep, href)
|
||||
if result is None:
|
||||
self._log.warning("write_fn rejected payload %r for %s", payload, href)
|
||||
@@ -672,10 +671,16 @@ class LocalThingsCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
help users pin down device-specific write behavior without a new
|
||||
release."""
|
||||
if not isinstance(body, dict) or not body:
|
||||
raise ServiceValidationError("Debug write payload must be a non-empty object.")
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="debug_payload_empty",
|
||||
)
|
||||
path_segs = [s for s in href.strip('/').split('/') if s]
|
||||
if not path_segs:
|
||||
raise ServiceValidationError("A resource href is required.")
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="resource_href_required",
|
||||
)
|
||||
norm_href = '/' + '/'.join(path_segs)
|
||||
async with self._session_lock:
|
||||
code, new_rep = await self.hass.async_add_executor_job(
|
||||
|
||||
@@ -47,6 +47,19 @@ def _derive_name(state_key: str) -> str:
|
||||
return _snake_to_title(name).strip()
|
||||
|
||||
|
||||
def _instance_display_name(bound: BoundEntity, state_key: str) -> str:
|
||||
"""Return the stable vendor/href instance label used in a name placeholder."""
|
||||
if bound.instance_name:
|
||||
return bound.instance_name
|
||||
source = bound.key_override or state_key
|
||||
suffix = f"_{bound.desc.key}"
|
||||
if source.endswith(suffix):
|
||||
source = source[:-len(suffix)]
|
||||
elif bound.instance and source.endswith(bound.instance):
|
||||
source = source[:-len(bound.instance)] + bound.instance.replace("_", " ")
|
||||
return _derive_name(source)
|
||||
|
||||
|
||||
class LocalThingsEntity(CoordinatorEntity[LocalThingsCoordinator]):
|
||||
"""Base class for all Local Things entities."""
|
||||
|
||||
@@ -57,15 +70,30 @@ class LocalThingsEntity(CoordinatorEntity[LocalThingsCoordinator]):
|
||||
self._bound = bound
|
||||
self._state_key = _key(bound)
|
||||
self._attr_unique_id = f"{DOMAIN}_{coordinator.device_serial}_{self._state_key}"
|
||||
if bound.desc.name is not None:
|
||||
self._attr_name = bound.desc.name
|
||||
elif bound.instance_name:
|
||||
# A device-given instance name (e.g. an ice maker's "Cubed
|
||||
# Ice") takes the place of the href-derived instance label,
|
||||
# keeping the same entity-specific suffix (issue #27).
|
||||
self._attr_name = f"{bound.instance_name} {_derive_name(bound.desc.key)}".strip()
|
||||
else:
|
||||
self._attr_name = _derive_name(self._state_key)
|
||||
if bound.desc.translation_placeholders is not None:
|
||||
self._attr_translation_placeholders = dict(
|
||||
bound.desc.translation_placeholders
|
||||
)
|
||||
elif bound.desc.use_instance_name:
|
||||
self._attr_translation_placeholders = {
|
||||
"instance_name": _instance_display_name(bound, self._state_key)
|
||||
}
|
||||
|
||||
# Do not set _attr_name for translated entities: Home Assistant gives
|
||||
# an explicitly-set name precedence over the translation catalog. The
|
||||
# English descriptor name remains a fallback for descriptions without
|
||||
# a translation key, while existing registry entity IDs stay intact via
|
||||
# their unchanged unique IDs.
|
||||
has_translation = (
|
||||
bound.desc.translation_key is not None or bound.desc.name is not None
|
||||
)
|
||||
if not has_translation:
|
||||
if bound.instance_name:
|
||||
self._attr_name = (
|
||||
f"{bound.instance_name} {_derive_name(bound.desc.key)}".strip()
|
||||
)
|
||||
else:
|
||||
self._attr_name = _derive_name(self._state_key)
|
||||
self._attr_icon = bound.desc.icon
|
||||
raw_cat = bound.desc.entity_category
|
||||
self._attr_entity_category = EntityCategory(raw_cat) if raw_cat else None
|
||||
@@ -88,7 +116,11 @@ class LocalThingsEntity(CoordinatorEntity[LocalThingsCoordinator]):
|
||||
value arrives on a later poll.
|
||||
"""
|
||||
tk = self._bound.desc.translation_key
|
||||
return tk(self.coordinator.last_resources) if callable(tk) else tk
|
||||
if callable(tk):
|
||||
return tk(self.coordinator.last_resources)
|
||||
if tk is not None:
|
||||
return tk
|
||||
return self._bound.desc.key if self._bound.desc.name is not None else None
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
|
||||
@@ -142,7 +142,7 @@ AIR_FILTER = Capability(
|
||||
SensorDesc(key='air_filter_status', field='x.com.samsung.da.filterStatus',
|
||||
name='Filter status', device_class='enum',
|
||||
options=('normal', 'wash', 'replace'),
|
||||
translation_key='air_filter_status',
|
||||
translation_key='filter_status',
|
||||
icon='mdi:air-filter', entity_category='diagnostic',
|
||||
value_fn=lambda v: v.lower() if isinstance(v, str) else v),
|
||||
),
|
||||
|
||||
@@ -280,7 +280,11 @@ WATER_FILTER = Capability(
|
||||
name='Filter usage', unit='%', state_class='measurement',
|
||||
icon='mdi:filter'),
|
||||
SensorDesc(key='filter_status', field='x.com.samsung.da.filterStatus',
|
||||
name='Filter status', icon='mdi:filter-check'),
|
||||
name='Filter status', icon='mdi:filter-check',
|
||||
device_class='enum', options=('normal', 'wash', 'replace'),
|
||||
value_fn=lambda value: (
|
||||
value.lower() if isinstance(value, str) else value
|
||||
)),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -93,6 +93,8 @@ COOKTOP_MODE = Capability(
|
||||
key=f'burner_{slot}_state',
|
||||
field='x.com.samsung.da.options',
|
||||
name=f'Burner {slot} state',
|
||||
translation_key='burner_state',
|
||||
translation_placeholders={'number': str(slot)},
|
||||
icon='mdi:gas-burner',
|
||||
value_fn=lambda options, slot=slot: _option_value(
|
||||
options, f'OperationState{slot}'
|
||||
|
||||
@@ -55,7 +55,8 @@ TEMP_CURRENT_GENERIC = Capability(
|
||||
poll_tier='warm',
|
||||
entities=(
|
||||
SensorDesc(key='temperature', field='temperature',
|
||||
name=None, icon='mdi:thermometer',
|
||||
name=None, translation_key='instance_temperature',
|
||||
use_instance_name=True, icon='mdi:thermometer',
|
||||
device_class='temperature', unit_fn=_temp_unit,
|
||||
state_class='measurement'),
|
||||
),
|
||||
@@ -68,7 +69,8 @@ TEMP_SETPOINT_GENERIC = Capability(
|
||||
poll_tier='warm',
|
||||
entities=(
|
||||
NumberDesc(key='setpoint', field='temperature',
|
||||
name=None, device_class='temperature', unit_fn=_temp_unit,
|
||||
name=None, translation_key='instance_setpoint',
|
||||
use_instance_name=True, device_class='temperature', unit_fn=_temp_unit,
|
||||
native_min=-20.0, native_max=50.0,
|
||||
range_field='range', entity_category='config',
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
@@ -123,17 +125,18 @@ ICEMAKER_GENERIC = Capability(
|
||||
entities=(
|
||||
SensorDesc(key='making_status',
|
||||
field='x.com.samsung.da.iceMaker.iceMakingStatus',
|
||||
name=None, icon='mdi:cube-outline',
|
||||
name=None, use_instance_name=True, icon='mdi:cube-outline',
|
||||
device_class='enum',
|
||||
options=('icestatus_stop', 'icestatus_run'),
|
||||
translation_key='ice_making_status',
|
||||
value_fn=lambda v: v.lower() if isinstance(v, str) else v),
|
||||
SwitchDesc(key='enabled', field='x.com.samsung.da.iceMaker.state',
|
||||
name=None, icon='mdi:cube-outline',
|
||||
name=None, translation_key='instance_enabled',
|
||||
use_instance_name=True, icon='mdi:cube-outline',
|
||||
value_fn=lambda v: v == 'On',
|
||||
write_fn=_icemaker_write('x.com.samsung.da.iceMaker.state')),
|
||||
SelectDesc(key='type', field='x.com.samsung.da.iceType.desired',
|
||||
name=None, icon='mdi:cube-outline',
|
||||
name=None, use_instance_name=True, icon='mdi:cube-outline',
|
||||
translation_key='ice_type',
|
||||
entity_category='config',
|
||||
options_field='x.com.samsung.da.iceType.supported',
|
||||
@@ -387,7 +390,7 @@ CABINET_LIGHT_ENHANCED = Capability(
|
||||
entities=(
|
||||
SelectDesc(key='day_brightness', field='level.brightness.daytime',
|
||||
name='Cabinet brightness', icon='mdi:brightness-5',
|
||||
translation_key='brightness_level',
|
||||
translation_key='day_brightness',
|
||||
entity_category='config',
|
||||
options=_NIGHT_BRIGHTNESS_OPTIONS,
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
@@ -575,7 +578,8 @@ DOOR_GENERIC = Capability(
|
||||
poll_tier='hot',
|
||||
entities=(
|
||||
BinarySensorDesc(key='open', field='openState',
|
||||
name=None, device_class='door',
|
||||
name=None, translation_key='instance_open',
|
||||
use_instance_name=True, device_class='door',
|
||||
value_fn=lambda v: v == 'Open'),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -276,6 +276,12 @@ def _table_id(resources, table_href):
|
||||
return rep.get('x.com.samsung.da.st.courseTable')
|
||||
|
||||
|
||||
_TRANSLATED_COURSE_TABLES = {
|
||||
'dryer_cycle': frozenset({'table_03'}),
|
||||
'washer_cycle': frozenset({'table_02'}),
|
||||
}
|
||||
|
||||
|
||||
def cycle_select(*, translation_key, icon, table_href=None):
|
||||
"""A 'Cycle' select over /course/vs/0, labelled from `translation_key`.
|
||||
|
||||
@@ -287,23 +293,19 @@ def cycle_select(*, translation_key, icon, table_href=None):
|
||||
suffixes translation_key with the device's own course-table id, read
|
||||
from /st/washercourse/vs/0 or /st/dryercourse/vs/0's
|
||||
x.com.samsung.da.st.courseTable (e.g. 'washer_cycle' + 'Table_02' ->
|
||||
'washer_cycle_table_02'). No table id available at all -- the href
|
||||
absent or empty -- gets no translation_key, i.e. the raw course code
|
||||
displayed as-is.
|
||||
'washer_cycle_table_02'). An absent or unrecognized table id gets the
|
||||
name-only ``cycle`` translation key while the raw course code remains
|
||||
visible and writable.
|
||||
|
||||
This matters because course codes are NOT guaranteed consistent across
|
||||
board generations sharing the same /course/vs/0 contract: every code in
|
||||
washer_cycle_table_02 was confirmed against Table_02-reporting devices
|
||||
(DA_WM_TP1/TP2 boards); FlexWash's older DA_WM_A51 board reports
|
||||
Table_00 instead, so the same hex code could mean a different course
|
||||
there for all we've verified. Building the key from whatever table the
|
||||
device actually reports, rather than gating a single hardcoded key on
|
||||
an exact match, means a table we haven't built translations for yet
|
||||
(like Table_00) just falls through Home Assistant's own missing-
|
||||
translation handling to the same raw-code display -- exactly what
|
||||
happens today for any individual code within a table's translations
|
||||
that isn't populated yet -- and adding one later needs new strings.json
|
||||
entries, not a code change here.
|
||||
there for all we've verified. Only tables with a complete catalog are
|
||||
assigned a state translation key. Unknown future tables deliberately use
|
||||
the generic key, avoiding misleading translations and missing-translation
|
||||
errors while keeping the exact vendor code for writes.
|
||||
|
||||
Left at its default for dishwasher, which has no equivalent table-id
|
||||
resource in any dump seen and no evidence its course codes vary by
|
||||
@@ -314,7 +316,10 @@ def cycle_select(*, translation_key, icon, table_href=None):
|
||||
if table_href is not None:
|
||||
def key(resources):
|
||||
table = _table_id(resources, table_href)
|
||||
return f'{translation_key}_{table.lower()}' if table else None
|
||||
table_id = table.lower() if isinstance(table, str) else None
|
||||
if table_id in _TRANSLATED_COURSE_TABLES.get(translation_key, ()):
|
||||
return f'{translation_key}_{table_id}'
|
||||
return 'cycle'
|
||||
|
||||
return SelectDesc(
|
||||
key='cycle', name='Cycle', icon=icon, translation_key=key,
|
||||
|
||||
@@ -82,16 +82,22 @@ def _burner_entities(i):
|
||||
return (
|
||||
SelectDesc(key=f'burner_{i}_power_level', field='burnerList',
|
||||
name=f'Burner {n} power level', icon='mdi:knob',
|
||||
translation_key='range_burner_power_level',
|
||||
translation_placeholders={'number': str(n)},
|
||||
options=_power_level_options,
|
||||
exists_fn=exists,
|
||||
value_fn=_burner_field_fn(i, 'powerLevel'),
|
||||
write_fn=_burner_power_level_write(i)),
|
||||
SensorDesc(key=f'burner_{i}_state', field='burnerList',
|
||||
name=f'Burner {n} state', icon='mdi:stove',
|
||||
translation_key='burner_state',
|
||||
translation_placeholders={'number': str(n)},
|
||||
exists_fn=exists,
|
||||
value_fn=_burner_field_fn(i, 'operationState')),
|
||||
BinarySensorDesc(key=f'burner_{i}_hot_surface', field='burnerList',
|
||||
name=f'Burner {n} hot surface', device_class='heat',
|
||||
translation_key='burner_hot_surface',
|
||||
translation_placeholders={'number': str(n)},
|
||||
exists_fn=exists,
|
||||
value_fn=_burner_hot_surface_fn(i)),
|
||||
)
|
||||
|
||||
@@ -166,6 +166,12 @@ HOOD_FILTER = Capability(
|
||||
name='Filter status',
|
||||
icon='mdi:air-filter',
|
||||
entity_category='diagnostic',
|
||||
device_class='enum',
|
||||
options=('normal', 'wash', 'replace'),
|
||||
translation_key='filter_status',
|
||||
value_fn=lambda value: (
|
||||
value.lower() if isinstance(value, str) else value
|
||||
),
|
||||
),
|
||||
SensorDesc(
|
||||
key='hood_filter_capacity',
|
||||
|
||||
@@ -282,7 +282,7 @@ def _bool_option_switch(key, name, icon, prefix, availability_field):
|
||||
if len(pairs) != len(courses):
|
||||
return None
|
||||
if pairs[courses.index(current)] != 'F0':
|
||||
return f"{name} isn't available on the selected cycle."
|
||||
return f"{key}_unavailable_for_cycle"
|
||||
return None
|
||||
|
||||
return bool_option_switch(
|
||||
@@ -306,7 +306,7 @@ WASHER_COURSE = Capability(
|
||||
exists_fn=lambda rep, resources: _drum_clean_last_cleaned(rep) is not None,
|
||||
rep_fn=_drum_clean_last_cleaned),
|
||||
SelectDesc(key='detergent_quantity', name='Detergent quantity', icon='mdi:cup-water',
|
||||
translation_key='washer_dosing_quantity',
|
||||
translation_key='detergent_quantity',
|
||||
entity_category='config',
|
||||
options=_level_options('DetergentLevelCtrl'),
|
||||
exists_fn=lambda rep, resources: bool(
|
||||
@@ -315,7 +315,7 @@ WASHER_COURSE = Capability(
|
||||
write_fn=_level_write('DetergentLevelCtrl')),
|
||||
SelectDesc(key='detergent_water_hardness', name='Detergent water hardness',
|
||||
icon='mdi:water-opacity',
|
||||
translation_key='washer_detergent_water_hardness',
|
||||
translation_key='detergent_water_hardness',
|
||||
entity_category='config',
|
||||
options=_level_options('DetergentLevel2Ctrl'),
|
||||
exists_fn=lambda rep, resources: bool(
|
||||
@@ -323,7 +323,7 @@ WASHER_COURSE = Capability(
|
||||
rep_fn=_dosing_level('DetergentLevel2Ctrl'),
|
||||
write_fn=_level_write('DetergentLevel2Ctrl')),
|
||||
SelectDesc(key='softener_quantity', name='Softener quantity', icon='mdi:flask-outline',
|
||||
translation_key='washer_dosing_quantity',
|
||||
translation_key='softener_quantity',
|
||||
entity_category='config',
|
||||
options=_level_options('SoftenerLevelCtrl'),
|
||||
exists_fn=lambda rep, resources: bool(
|
||||
@@ -332,7 +332,7 @@ WASHER_COURSE = Capability(
|
||||
write_fn=_level_write('SoftenerLevelCtrl')),
|
||||
SelectDesc(key='softener_concentration', name='Softener concentration',
|
||||
icon='mdi:flask-plus-outline',
|
||||
translation_key='washer_softener_concentration',
|
||||
translation_key='softener_concentration',
|
||||
entity_category='config',
|
||||
options=_level_options('SoftenerLevel2Ctrl'),
|
||||
exists_fn=lambda rep, resources: bool(
|
||||
|
||||
@@ -8,14 +8,14 @@ where a description declares one.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Callable, Optional
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Callable, Mapping, Optional
|
||||
|
||||
WriteFn = Optional[Callable[[Any, dict], "tuple[list[str], dict] | None"]]
|
||||
# (payload, rep, resources) -> a human-readable rejection message, or None to
|
||||
# allow the write. resources is the coordinator's full href->rep snapshot, for
|
||||
# the same cross-resource lookups exists_fn needs (e.g. reading a sibling
|
||||
# href's live option list).
|
||||
# (payload, rep, resources) -> a translation key, or None to allow the
|
||||
# write. resources is the coordinator's full href->rep snapshot, for the same
|
||||
# cross-resource lookups exists_fn needs (e.g. reading a sibling href's live
|
||||
# option list).
|
||||
ValidateFn = Optional[Callable[[Any, dict, dict], "str | None"]]
|
||||
|
||||
|
||||
@@ -34,6 +34,10 @@ class SamsungEntityDescription:
|
||||
# device) -- for a descriptor shared across board generations whose
|
||||
# state-code meaning isn't guaranteed consistent between them; see
|
||||
# laundry.cycle_select's table-id-gated resolver.
|
||||
translation_placeholders: Optional[Mapping[str, str]] = None
|
||||
# Dynamic resources such as fridge compartments and ice makers use a
|
||||
# device-provided or href-derived instance label inside a translated name.
|
||||
use_instance_name: bool = False
|
||||
icon: Optional[str] = None
|
||||
entity_category: Optional[str] = None # 'diagnostic' | 'config' | None
|
||||
enabled_default: bool = True
|
||||
|
||||
@@ -31,6 +31,79 @@ async def async_setup_entry(
|
||||
|
||||
_CAMEL_BOUNDARY_RE = re.compile(r'(?<=[a-z0-9])(?=[A-Z])')
|
||||
|
||||
# Keep this synchronized with entity.select.*.state in strings.json. A select
|
||||
# can have a translation key for its name without translating its dynamic
|
||||
# Samsung options. Only known states are normalized for HA's lowercase lookup;
|
||||
# unknown future vendor values retain a readable label and exact write value.
|
||||
TRANSLATED_SELECT_STATES: dict[str, frozenset[str]] = {
|
||||
'beverage_zone_mode': frozenset({
|
||||
'sp_ttype_beer_drinks', 'sp_ttype_wine_dessert',
|
||||
}),
|
||||
'brightness_level': frozenset({'33', '66', '100'}),
|
||||
'day_brightness': frozenset({'33', '66', '100'}),
|
||||
'dishwasher_cycle': frozenset({
|
||||
'07', '0e', '80', '83', '84', '86', '8d', '8e', '8f', '90',
|
||||
}),
|
||||
'door_alert': frozenset({'1', '2', '3', '4'}),
|
||||
'dryer_cycle_table_03': frozenset({
|
||||
'16', '18', '19', '1a', '1b', '1c', '1d', '1e', '1f', '20',
|
||||
'23', '24', '25', '27',
|
||||
}),
|
||||
'finish_sound': frozenset({'off', 'on'}),
|
||||
'flex_zone_mode': frozenset({
|
||||
'cv_fdr_beverage', 'cv_fdr_deli', 'cv_fdr_meat',
|
||||
'cv_fdr_soft_freezer', 'cv_fdr_wine',
|
||||
'cv_ttype_rf9000a_beverage', 'cv_ttype_rf9000a_freeze',
|
||||
'cv_ttype_rf9000a_fruit_veggies',
|
||||
'cv_ttype_rf9000a_meat_fish', 'cv_ttype_rf9000a_softfreeze',
|
||||
}),
|
||||
'heated_dry': frozenset({'extra_high', 'high', 'low', 'off'}),
|
||||
'ice_type': frozenset({
|
||||
'off', 'whiskey_iceball_3', 'whiskey_iceball_6',
|
||||
'whiskey_iceball_9',
|
||||
}),
|
||||
'led_brightness': frozenset({'high', 'low'}),
|
||||
'led_night_brightness': frozenset({'high', 'low'}),
|
||||
'oven_mode': frozenset({
|
||||
'air_fry', 'bake', 'broil', 'convection', 'convection_bake',
|
||||
'convection_broil', 'frozen_pizza_plus', 'no_operation',
|
||||
'plate_warm', 'slow_cook',
|
||||
}),
|
||||
'pantry_zone_mode': frozenset({'fdr_deli', 'fdr_drinks', 'fdr_wine'}),
|
||||
'range_burner_power_level': frozenset({'0', 'boost', 'simmer'}),
|
||||
'sound_mode': frozenset({'mute', 'tone', 'voice'}),
|
||||
'spin_speed': frozenset({'high', 'low', 'medium', 'no_spin', 'rinse_hold'}),
|
||||
'buzzer_sound': frozenset({'off', 'on'}),
|
||||
'wash_temperature': frozenset({
|
||||
'cold', 'cool', 'extra_hot', 'hot', 'none', 'warm',
|
||||
}),
|
||||
'washer_cycle_table_02': frozenset({
|
||||
'1b', '1c', '1d', '1e', '1f', '20', '21', '22', '23', '24',
|
||||
'25', '26', '27', '28', '29', '2d', '2e', '2f', '30', '32',
|
||||
'33', '36', '37', '38', '39', '66', '8f', '96',
|
||||
}),
|
||||
'detergent_quantity': frozenset({'00', '01', '02', '03'}),
|
||||
'softener_quantity': frozenset({'00', '01', '02', '03'}),
|
||||
'detergent_water_hardness': frozenset({'01', '02', '03'}),
|
||||
'softener_concentration': frozenset({'01', '02', '03'}),
|
||||
'washer_dry_level': frozenset({
|
||||
'30', '60', '90', '120', '180', '240', 'cupboard', 'none',
|
||||
}),
|
||||
'range_hood_lamp_brightness': frozenset({'1', '2'}),
|
||||
}
|
||||
|
||||
|
||||
def _translation_state(value: str, translation_key: str) -> str | None:
|
||||
"""Return a known HA translation state, or None for a vendor fallback."""
|
||||
known = TRANSLATED_SELECT_STATES.get(translation_key)
|
||||
if known is None:
|
||||
return None
|
||||
direct = value.lower().replace(' ', '_')
|
||||
if direct in known:
|
||||
return direct
|
||||
snake = _CAMEL_BOUNDARY_RE.sub('_', value).lower().replace(' ', '_')
|
||||
return snake if snake in known else None
|
||||
|
||||
|
||||
def _display(value, translation_key: Optional[str]):
|
||||
"""Turn a raw device option/state value into what's shown in the UI.
|
||||
@@ -57,7 +130,13 @@ def _display(value, translation_key: Optional[str]):
|
||||
if not isinstance(value, str):
|
||||
return value
|
||||
if translation_key:
|
||||
return value.lower()
|
||||
if translated := _translation_state(value, translation_key):
|
||||
return translated
|
||||
if translation_key not in TRANSLATED_SELECT_STATES:
|
||||
# The entity name is translated, but this select deliberately has
|
||||
# no static state catalog (for example an unknown course table).
|
||||
# Keep its opaque device value untouched.
|
||||
return value
|
||||
if value.islower():
|
||||
return value.replace('_', ' ').title()
|
||||
return _CAMEL_BOUNDARY_RE.sub(' ', value)
|
||||
|
||||
@@ -61,7 +61,6 @@ class LocalThingsConnectionModeSensor(CoordinatorEntity[LocalThingsCoordinator],
|
||||
Disabled by default — it's for troubleshooting, not everyday use."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = 'Connection mode'
|
||||
_attr_translation_key = 'connection_mode'
|
||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||
_attr_entity_registry_enabled_default = False
|
||||
|
||||
@@ -1,187 +1,88 @@
|
||||
{
|
||||
"entity": {
|
||||
"select": {
|
||||
"door_alert": {
|
||||
"state": {
|
||||
"1": "Alarm 1",
|
||||
"2": "Alarm 2",
|
||||
"3": "Alarm 3",
|
||||
"4": "Alarm 4"
|
||||
}
|
||||
"binary_sensor": {
|
||||
"any_burner_active": {
|
||||
"name": "Any burner active"
|
||||
},
|
||||
"brightness_level": {
|
||||
"state": {
|
||||
"33": "Low",
|
||||
"66": "Medium",
|
||||
"100": "High"
|
||||
}
|
||||
"automatic_operation": {
|
||||
"name": "Automatic operation"
|
||||
},
|
||||
"range_hood_lamp_brightness": {
|
||||
"state": {
|
||||
"1": "Level 1",
|
||||
"2": "Level 2"
|
||||
}
|
||||
"burner_hot_surface": {
|
||||
"name": "Burner {number} hot surface"
|
||||
},
|
||||
"ice_type": {
|
||||
"state": {
|
||||
"off": "Off",
|
||||
"whiskey_iceball_3": "3 Balls/Day",
|
||||
"whiskey_iceball_6": "6 Balls/Day",
|
||||
"whiskey_iceball_9": "9 Balls/Day"
|
||||
}
|
||||
"cloud_connected": {
|
||||
"name": "Cloud connected"
|
||||
},
|
||||
"beverage_zone_mode": {
|
||||
"state": {
|
||||
"sp_ttype_beer_drinks": "Beverage",
|
||||
"sp_ttype_wine_dessert": "Wine and Dessert"
|
||||
}
|
||||
"cooktop_safety_shutoff_enabled": {
|
||||
"name": "Hot surface auto-shutoff enabled"
|
||||
},
|
||||
"pantry_zone_mode": {
|
||||
"state": {
|
||||
"fdr_wine": "Wine",
|
||||
"fdr_deli": "Deli",
|
||||
"fdr_drinks": "Drinks"
|
||||
}
|
||||
"current_limit_enabled": {
|
||||
"name": "Current limit enabled"
|
||||
},
|
||||
"flex_zone_mode": {
|
||||
"state": {
|
||||
"cv_ttype_rf9000a_freeze": "Freeze",
|
||||
"cv_ttype_rf9000a_softfreeze": "Soft Freeze",
|
||||
"cv_ttype_rf9000a_meat_fish": "Meat/Fish",
|
||||
"cv_ttype_rf9000a_fruit_veggies": "Fruit & Veggies",
|
||||
"cv_ttype_rf9000a_beverage": "Beverage",
|
||||
"cv_fdr_wine": "Wine",
|
||||
"cv_fdr_deli": "Deli",
|
||||
"cv_fdr_beverage": "Beverage",
|
||||
"cv_fdr_meat": "Meat",
|
||||
"cv_fdr_soft_freezer": "Soft Freezer"
|
||||
}
|
||||
"cycle_active": {
|
||||
"name": "[%key:component::binary_sensor::entity_component::running::name%]"
|
||||
},
|
||||
"dishwasher_cycle": {
|
||||
"state": {
|
||||
"0e": "AI Wash",
|
||||
"07": "Pre blast",
|
||||
"90": "Self clean",
|
||||
"86": "Normal",
|
||||
"83": "Express 60",
|
||||
"84": "Heavy",
|
||||
"8d": "Pots and pans",
|
||||
"80": "Delicate",
|
||||
"8e": "Plastic",
|
||||
"8f": "Baby Care"
|
||||
}
|
||||
"defrost_active": {
|
||||
"name": "Defrost active"
|
||||
},
|
||||
"washer_cycle_table_02": {
|
||||
"state": {
|
||||
"1c": "Eco 40-60",
|
||||
"1d": "Super Speed",
|
||||
"21": "Colours",
|
||||
"1b": "Cotton",
|
||||
"1e": "15' Quick Wash",
|
||||
"29": "Drum Clean+",
|
||||
"24": "Towels",
|
||||
"33": "Bedding",
|
||||
"28": "Drain/Spin",
|
||||
"26": "Delicates",
|
||||
"27": "Rinse+Spin",
|
||||
"22": "Wool",
|
||||
"20": "Hygiene Steam",
|
||||
"23": "Outdoor",
|
||||
"25": "Synthetics",
|
||||
"32": "Shirts",
|
||||
"2f": "Activewear",
|
||||
"2e": "Baby Care",
|
||||
"30": "Cloudy Day",
|
||||
"66": "Denim",
|
||||
"2d": "Silent Wash",
|
||||
"8f": "Intense Cold",
|
||||
"96": "Less Microfiber",
|
||||
"36": "Wash+Dry",
|
||||
"37": "Air Wash",
|
||||
"38": "Cotton Dry",
|
||||
"39": "Synthetics Dry",
|
||||
"1f": "Intense Cold"
|
||||
}
|
||||
"detergent_low": {
|
||||
"name": "Detergent low"
|
||||
},
|
||||
"dryer_cycle_table_03": {
|
||||
"state": {
|
||||
"16": "Cotton",
|
||||
"18": "Synthetics",
|
||||
"19": "Delicates",
|
||||
"1a": "Wool",
|
||||
"1b": "Bedding",
|
||||
"1c": "Shirts",
|
||||
"1d": "Towels",
|
||||
"1e": "Outdoor",
|
||||
"1f": "Mixed Load",
|
||||
"20": "Iron Dry",
|
||||
"23": "Quick Dry 35",
|
||||
"24": "Cool Air",
|
||||
"25": "Warm Air",
|
||||
"27": "Time Dry"
|
||||
}
|
||||
"device_active": {
|
||||
"name": "Device active"
|
||||
},
|
||||
"washer_dosing_quantity": {
|
||||
"state": {
|
||||
"00": "None",
|
||||
"01": "Low",
|
||||
"02": "Medium",
|
||||
"03": "High"
|
||||
}
|
||||
"door_open": {
|
||||
"name": "[%key:component::binary_sensor::entity_component::door::name%]"
|
||||
},
|
||||
"washer_detergent_water_hardness": {
|
||||
"state": {
|
||||
"01": "Soft",
|
||||
"02": "Medium",
|
||||
"03": "Hard"
|
||||
}
|
||||
"firmware_update": {
|
||||
"name": "Firmware update available"
|
||||
},
|
||||
"washer_softener_concentration": {
|
||||
"state": {
|
||||
"01": "1x",
|
||||
"02": "2x",
|
||||
"03": "3x"
|
||||
}
|
||||
"instance_open": {
|
||||
"name": "{instance_name} open"
|
||||
},
|
||||
"washer_dry_level": {
|
||||
"state": {
|
||||
"none": "Off",
|
||||
"cupboard": "Cupboard",
|
||||
"30": "30 min",
|
||||
"60": "1 hr",
|
||||
"90": "1 hr 30",
|
||||
"120": "2 hr",
|
||||
"180": "3 hr",
|
||||
"240": "4 hr"
|
||||
}
|
||||
"paired_hood_connected": {
|
||||
"name": "Paired hood connected"
|
||||
},
|
||||
"paired_hood_light": {
|
||||
"name": "Paired hood light"
|
||||
},
|
||||
"paired_hood_power": {
|
||||
"name": "Paired hood power"
|
||||
},
|
||||
"periodic_air_sensing": {
|
||||
"name": "Periodic air sensing"
|
||||
},
|
||||
"power_state": {
|
||||
"name": "Power state"
|
||||
},
|
||||
"rapid_freezing": {
|
||||
"name": "Rapid freezing"
|
||||
},
|
||||
"rapid_fridge": {
|
||||
"name": "Rapid fridge"
|
||||
},
|
||||
"remote_control": {
|
||||
"name": "Smart Control"
|
||||
},
|
||||
"softener_low": {
|
||||
"name": "Softener low"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"ice_making_status": {
|
||||
"state": {
|
||||
"icestatus_stop": "Idle",
|
||||
"icestatus_run": "Making ice"
|
||||
}
|
||||
"button": {
|
||||
"diagnosis_start": {
|
||||
"name": "Start diagnosis"
|
||||
},
|
||||
"connection_mode": {
|
||||
"state": {
|
||||
"observe": "Push (observe)",
|
||||
"poll": "Polling"
|
||||
}
|
||||
"pause": {
|
||||
"name": "[%key:common::action::pause%]"
|
||||
},
|
||||
"machine_state": {
|
||||
"state": {
|
||||
"idle": "Idle",
|
||||
"active": "Active",
|
||||
"pause": "Paused"
|
||||
}
|
||||
"selfcheck_start": {
|
||||
"name": "Start self-check"
|
||||
},
|
||||
"air_filter_status": {
|
||||
"state": {
|
||||
"normal": "Normal",
|
||||
"wash": "Wash",
|
||||
"replace": "Replace"
|
||||
}
|
||||
"start": {
|
||||
"name": "[%key:common::action::start%]"
|
||||
},
|
||||
"stop": {
|
||||
"name": "[%key:common::action::stop%]"
|
||||
}
|
||||
},
|
||||
"climate": {
|
||||
@@ -189,24 +90,11 @@
|
||||
"state_attributes": {
|
||||
"fan_mode": {
|
||||
"state": {
|
||||
"auto": "Auto",
|
||||
"low": "Low",
|
||||
"medium": "Medium",
|
||||
"high": "High",
|
||||
"turbo": "Turbo"
|
||||
}
|
||||
},
|
||||
"swing_mode": {
|
||||
"state": {
|
||||
"off": "Fixed",
|
||||
"both": "All directions",
|
||||
"vertical": "Up and down"
|
||||
}
|
||||
},
|
||||
"preset_mode": {
|
||||
"state": {
|
||||
"none": "Off",
|
||||
"sleep": "Sleep",
|
||||
"quiet": "Quiet",
|
||||
"smart": "Smart",
|
||||
"speed": "Speed"
|
||||
@@ -214,17 +102,628 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"number": {
|
||||
"cook_time": {
|
||||
"name": "Cook time"
|
||||
},
|
||||
"delay_start_hours": {
|
||||
"name": "Delay start"
|
||||
},
|
||||
"instance_setpoint": {
|
||||
"name": "{instance_name} setpoint"
|
||||
},
|
||||
"oven_setpoint": {
|
||||
"name": "Setpoint"
|
||||
},
|
||||
"sound_volume": {
|
||||
"name": "Sound volume"
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
"ai_energy_level": {
|
||||
"name": "AI Energy Mode level"
|
||||
},
|
||||
"beverage_zone_mode": {
|
||||
"name": "Beverage zone mode",
|
||||
"state": {
|
||||
"sp_ttype_beer_drinks": "Beverage",
|
||||
"sp_ttype_wine_dessert": "Wine and dessert"
|
||||
}
|
||||
},
|
||||
"brightness_level": {
|
||||
"name": "Night brightness",
|
||||
"state": {
|
||||
"33": "[%key:common::state::low%]",
|
||||
"66": "[%key:common::state::medium%]",
|
||||
"100": "[%key:common::state::high%]"
|
||||
}
|
||||
},
|
||||
"day_brightness": {
|
||||
"name": "Cabinet brightness",
|
||||
"state": {
|
||||
"33": "[%key:common::state::low%]",
|
||||
"66": "[%key:common::state::medium%]",
|
||||
"100": "[%key:common::state::high%]"
|
||||
}
|
||||
},
|
||||
"buzzer_sound": {
|
||||
"name": "Buzzer sound",
|
||||
"state": {
|
||||
"off": "[%key:common::state::off%]",
|
||||
"on": "[%key:common::state::on%]"
|
||||
}
|
||||
},
|
||||
"cycle": {
|
||||
"name": "Cycle"
|
||||
},
|
||||
"detergent_quantity": {
|
||||
"name": "Detergent quantity",
|
||||
"state": {
|
||||
"00": "None",
|
||||
"01": "[%key:common::state::low%]",
|
||||
"02": "[%key:common::state::medium%]",
|
||||
"03": "[%key:common::state::high%]"
|
||||
}
|
||||
},
|
||||
"detergent_water_hardness": {
|
||||
"name": "Detergent water hardness",
|
||||
"state": {
|
||||
"01": "Soft",
|
||||
"02": "[%key:common::state::medium%]",
|
||||
"03": "Hard"
|
||||
}
|
||||
},
|
||||
"dishwasher_cycle": {
|
||||
"name": "Cycle",
|
||||
"state": {
|
||||
"80": "Delicate",
|
||||
"83": "Express 60",
|
||||
"84": "Heavy",
|
||||
"86": "Normal",
|
||||
"90": "Self clean",
|
||||
"0e": "AI Wash",
|
||||
"07": "Pre blast",
|
||||
"8d": "Pots and pans",
|
||||
"8e": "Plastic",
|
||||
"8f": "Baby Care"
|
||||
}
|
||||
},
|
||||
"door_alert": {
|
||||
"name": "Door alarm",
|
||||
"state": {
|
||||
"1": "Alarm 1",
|
||||
"2": "Alarm 2",
|
||||
"3": "Alarm 3",
|
||||
"4": "Alarm 4"
|
||||
}
|
||||
},
|
||||
"dryer_cycle_table_03": {
|
||||
"name": "Cycle",
|
||||
"state": {
|
||||
"16": "Cotton",
|
||||
"18": "Synthetics",
|
||||
"19": "Delicates",
|
||||
"20": "Iron dry",
|
||||
"23": "Quick Dry 35",
|
||||
"24": "Cool air",
|
||||
"25": "Warm air",
|
||||
"27": "Time dry",
|
||||
"1a": "Wool",
|
||||
"1b": "Bedding",
|
||||
"1c": "Shirts",
|
||||
"1d": "Towels",
|
||||
"1e": "Outdoor",
|
||||
"1f": "Mixed load"
|
||||
}
|
||||
},
|
||||
"finish_sound": {
|
||||
"name": "Finish sound",
|
||||
"state": {
|
||||
"off": "[%key:common::state::off%]",
|
||||
"on": "[%key:common::state::on%]"
|
||||
}
|
||||
},
|
||||
"flex_zone_mode": {
|
||||
"name": "Flex zone mode",
|
||||
"state": {
|
||||
"cv_ttype_rf9000a_freeze": "Freeze",
|
||||
"cv_ttype_rf9000a_softfreeze": "Soft freeze",
|
||||
"cv_ttype_rf9000a_meat_fish": "Meat/Fish",
|
||||
"cv_ttype_rf9000a_fruit_veggies": "Fruit & vegetables",
|
||||
"cv_ttype_rf9000a_beverage": "Beverage",
|
||||
"cv_fdr_wine": "Wine",
|
||||
"cv_fdr_deli": "Deli",
|
||||
"cv_fdr_beverage": "Beverage",
|
||||
"cv_fdr_meat": "Meat",
|
||||
"cv_fdr_soft_freezer": "Soft freezer"
|
||||
}
|
||||
},
|
||||
"heated_dry": {
|
||||
"name": "Smart Dry",
|
||||
"state": {
|
||||
"off": "[%key:common::state::off%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"high": "[%key:common::state::high%]",
|
||||
"extra_high": "Extra high"
|
||||
}
|
||||
},
|
||||
"ice_type": {
|
||||
"name": "{instance_name} type",
|
||||
"state": {
|
||||
"off": "[%key:common::state::off%]",
|
||||
"whiskey_iceball_3": "3 balls/day",
|
||||
"whiskey_iceball_6": "6 balls/day",
|
||||
"whiskey_iceball_9": "9 balls/day"
|
||||
}
|
||||
},
|
||||
"led_brightness": {
|
||||
"name": "Door LED brightness",
|
||||
"state": {
|
||||
"low": "[%key:common::state::low%]",
|
||||
"high": "[%key:common::state::high%]"
|
||||
}
|
||||
},
|
||||
"led_night_brightness": {
|
||||
"name": "Door LED night brightness",
|
||||
"state": {
|
||||
"low": "[%key:common::state::low%]",
|
||||
"high": "[%key:common::state::high%]"
|
||||
}
|
||||
},
|
||||
"oven_mode": {
|
||||
"name": "Cooking mode",
|
||||
"state": {
|
||||
"no_operation": "No operation",
|
||||
"bake": "Bake",
|
||||
"broil": "Broil",
|
||||
"convection": "Convection",
|
||||
"convection_bake": "Convection bake",
|
||||
"convection_broil": "Convection broil",
|
||||
"frozen_pizza_plus": "Frozen Pizza+",
|
||||
"slow_cook": "Slow cook",
|
||||
"plate_warm": "Plate warm",
|
||||
"air_fry": "Air fry"
|
||||
}
|
||||
},
|
||||
"pantry_zone_mode": {
|
||||
"name": "Pantry zone mode",
|
||||
"state": {
|
||||
"fdr_wine": "Wine",
|
||||
"fdr_deli": "Deli",
|
||||
"fdr_drinks": "Drinks"
|
||||
}
|
||||
},
|
||||
"range_burner_power_level": {
|
||||
"name": "Burner {number} power level",
|
||||
"state": {
|
||||
"0": "[%key:common::state::off%]",
|
||||
"boost": "Boost",
|
||||
"simmer": "Simmer"
|
||||
}
|
||||
},
|
||||
"range_hood_lamp_brightness": {
|
||||
"name": "Lamp brightness",
|
||||
"state": {
|
||||
"1": "Level 1",
|
||||
"2": "Level 2"
|
||||
}
|
||||
},
|
||||
"rinse_cycles": {
|
||||
"name": "Rinse cycles"
|
||||
},
|
||||
"softener_concentration": {
|
||||
"name": "Softener concentration",
|
||||
"state": {
|
||||
"01": "1x",
|
||||
"02": "2x",
|
||||
"03": "3x"
|
||||
}
|
||||
},
|
||||
"softener_quantity": {
|
||||
"name": "Softener quantity",
|
||||
"state": {
|
||||
"00": "None",
|
||||
"01": "[%key:common::state::low%]",
|
||||
"02": "[%key:common::state::medium%]",
|
||||
"03": "[%key:common::state::high%]"
|
||||
}
|
||||
},
|
||||
"sound_mode": {
|
||||
"name": "Sound mode",
|
||||
"state": {
|
||||
"voice": "Voice",
|
||||
"tone": "Tone",
|
||||
"mute": "Mute"
|
||||
}
|
||||
},
|
||||
"spin_speed": {
|
||||
"name": "Spin speed",
|
||||
"state": {
|
||||
"rinse_hold": "Rinse hold",
|
||||
"no_spin": "No spin",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"medium": "[%key:common::state::medium%]",
|
||||
"high": "[%key:common::state::high%]"
|
||||
}
|
||||
},
|
||||
"wash_temperature": {
|
||||
"name": "Wash temperature",
|
||||
"state": {
|
||||
"none": "None",
|
||||
"cold": "Cold",
|
||||
"cool": "Cool",
|
||||
"warm": "Warm",
|
||||
"hot": "Hot",
|
||||
"extra_hot": "Extra hot"
|
||||
}
|
||||
},
|
||||
"washer_cycle_table_02": {
|
||||
"name": "Cycle",
|
||||
"state": {
|
||||
"20": "Hygiene Steam",
|
||||
"21": "Colours",
|
||||
"22": "Wool",
|
||||
"23": "Outdoor",
|
||||
"24": "Towels",
|
||||
"25": "Synthetics",
|
||||
"26": "Delicates",
|
||||
"27": "Rinse+Spin",
|
||||
"28": "Drain/Spin",
|
||||
"29": "Drum Clean+",
|
||||
"30": "Cloudy Day",
|
||||
"32": "Shirts",
|
||||
"33": "Bedding",
|
||||
"36": "Wash+Dry",
|
||||
"37": "Air Wash",
|
||||
"38": "Cotton Dry",
|
||||
"39": "Synthetics Dry",
|
||||
"66": "Denim",
|
||||
"96": "Less Microfiber",
|
||||
"1c": "Eco 40-60",
|
||||
"1d": "Super Speed",
|
||||
"1b": "Cotton",
|
||||
"1e": "15' Quick Wash",
|
||||
"2f": "Activewear",
|
||||
"2e": "Baby Care",
|
||||
"2d": "Silent Wash",
|
||||
"8f": "Intense Cold",
|
||||
"1f": "Intense Cold"
|
||||
}
|
||||
},
|
||||
"washer_dry_level": {
|
||||
"name": "Dry level",
|
||||
"state": {
|
||||
"30": "30 min",
|
||||
"60": "1 hr",
|
||||
"90": "1 hr 30",
|
||||
"120": "2 hr",
|
||||
"180": "3 hr",
|
||||
"240": "4 hr",
|
||||
"none": "[%key:common::state::off%]",
|
||||
"cupboard": "Cupboard"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"air_filter_usage": {
|
||||
"name": "Filter usage"
|
||||
},
|
||||
"air_sensing_state": {
|
||||
"name": "Air sensing state"
|
||||
},
|
||||
"alarm_code": {
|
||||
"name": "Alarm code"
|
||||
},
|
||||
"auto_ventilation_action": {
|
||||
"name": "Auto ventilation action"
|
||||
},
|
||||
"automatic_ventilation_state": {
|
||||
"name": "Automatic ventilation state"
|
||||
},
|
||||
"burner_state": {
|
||||
"name": "Burner {number} state"
|
||||
},
|
||||
"clean_level": {
|
||||
"name": "Clean level"
|
||||
},
|
||||
"connection_mode": {
|
||||
"name": "Connection mode",
|
||||
"state": {
|
||||
"observe": "Push (observe)",
|
||||
"poll": "Polling"
|
||||
}
|
||||
},
|
||||
"cooktop_state": {
|
||||
"name": "Cooktop state"
|
||||
},
|
||||
"current_limit_level": {
|
||||
"name": "Current limit level"
|
||||
},
|
||||
"current_temp_c": {
|
||||
"name": "[%key:component::sensor::entity_component::temperature::name%]"
|
||||
},
|
||||
"diagnosis": {
|
||||
"name": "Diagnosis"
|
||||
},
|
||||
"diagnosis_status": {
|
||||
"name": "Diagnosis status"
|
||||
},
|
||||
"drum_clean_cycles_remaining": {
|
||||
"name": "Drum clean due in"
|
||||
},
|
||||
"drum_clean_last_cleaned": {
|
||||
"name": "Drum last cleaned"
|
||||
},
|
||||
"dry_level": {
|
||||
"name": "Dry level"
|
||||
},
|
||||
"dry_time": {
|
||||
"name": "Dry time"
|
||||
},
|
||||
"dryer_type": {
|
||||
"name": "Dryer type"
|
||||
},
|
||||
"dust": {
|
||||
"name": "Dust"
|
||||
},
|
||||
"energy_kwh": {
|
||||
"name": "[%key:component::sensor::entity_component::energy::name%]"
|
||||
},
|
||||
"energy_last_month_kwh": {
|
||||
"name": "Energy (last month)"
|
||||
},
|
||||
"energy_saved_kwh": {
|
||||
"name": "Energy saved"
|
||||
},
|
||||
"energy_this_month_kwh": {
|
||||
"name": "Energy (this month)"
|
||||
},
|
||||
"fan_direction": {
|
||||
"name": "Fan direction"
|
||||
},
|
||||
"fan_speed_level": {
|
||||
"name": "Fan speed level"
|
||||
},
|
||||
"filter_progress": {
|
||||
"name": "Filter progress"
|
||||
},
|
||||
"filter_usage": {
|
||||
"name": "Filter usage"
|
||||
},
|
||||
"fine_dust": {
|
||||
"name": "Fine dust"
|
||||
},
|
||||
"finish_time": {
|
||||
"name": "Estimated finish"
|
||||
},
|
||||
"freezer_temperature": {
|
||||
"name": "Freezer temperature"
|
||||
},
|
||||
"fridge_temperature": {
|
||||
"name": "Fridge temperature"
|
||||
},
|
||||
"hood_filter_capacity": {
|
||||
"name": "Filter capacity"
|
||||
},
|
||||
"hood_filter_usage": {
|
||||
"name": "Filter usage"
|
||||
},
|
||||
"instance_temperature": {
|
||||
"name": "{instance_name} temperature"
|
||||
},
|
||||
"job_beginning_status": {
|
||||
"name": "Job beginning status"
|
||||
},
|
||||
"last_air_sensing_level": {
|
||||
"name": "Last air sensing level"
|
||||
},
|
||||
"last_air_sensing_time": {
|
||||
"name": "Last air sensing time"
|
||||
},
|
||||
"main_timer_current": {
|
||||
"name": "Timer current value"
|
||||
},
|
||||
"main_timer_state": {
|
||||
"name": "Timer state"
|
||||
},
|
||||
"odor": {
|
||||
"name": "Odor"
|
||||
},
|
||||
"operating_mode": {
|
||||
"name": "Operating mode"
|
||||
},
|
||||
"operation_origin": {
|
||||
"name": "Last operation source"
|
||||
},
|
||||
"operation_time_minutes": {
|
||||
"name": "Operation time"
|
||||
},
|
||||
"oven_state": {
|
||||
"name": "Cavity state"
|
||||
},
|
||||
"paired_hood_fan_speed": {
|
||||
"name": "Paired hood fan speed"
|
||||
},
|
||||
"paired_hood_firmware": {
|
||||
"name": "Paired hood firmware"
|
||||
},
|
||||
"paired_hood_model": {
|
||||
"name": "Paired hood model"
|
||||
},
|
||||
"power_energy_kwh": {
|
||||
"name": "Power energy"
|
||||
},
|
||||
"power_watts": {
|
||||
"name": "[%key:component::sensor::entity_component::power::name%]"
|
||||
},
|
||||
"progress": {
|
||||
"name": "Progress"
|
||||
},
|
||||
"progress_percentage": {
|
||||
"name": "Progress percent"
|
||||
},
|
||||
"selfcheck_error": {
|
||||
"name": "Self-check error"
|
||||
},
|
||||
"selfcheck_result": {
|
||||
"name": "Self-check result"
|
||||
},
|
||||
"selfcheck_status": {
|
||||
"name": "Self-check status"
|
||||
},
|
||||
"super_fine_dust": {
|
||||
"name": "Super fine dust"
|
||||
},
|
||||
"water_liters": {
|
||||
"name": "Water consumption"
|
||||
},
|
||||
"machine_state": {
|
||||
"name": "Machine state",
|
||||
"state": {
|
||||
"idle": "[%key:common::state::idle%]",
|
||||
"active": "[%key:common::state::active%]",
|
||||
"pause": "Paused"
|
||||
}
|
||||
},
|
||||
"filter_status": {
|
||||
"name": "Filter status",
|
||||
"state": {
|
||||
"normal": "[%key:common::state::normal%]",
|
||||
"wash": "Wash",
|
||||
"replace": "Replace"
|
||||
}
|
||||
},
|
||||
"ice_making_status": {
|
||||
"name": "{instance_name} making status",
|
||||
"state": {
|
||||
"icestatus_stop": "[%key:common::state::idle%]",
|
||||
"icestatus_run": "Making ice"
|
||||
}
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"ai_energy_level": {
|
||||
"name": "AI Energy Mode"
|
||||
},
|
||||
"air_purify": {
|
||||
"name": "Air purification"
|
||||
},
|
||||
"auto_clean": {
|
||||
"name": "Auto clean"
|
||||
},
|
||||
"auto_door_opener": {
|
||||
"name": "Auto door opener"
|
||||
},
|
||||
"auto_release_dry": {
|
||||
"name": "Auto Release Dry"
|
||||
},
|
||||
"autofill": {
|
||||
"name": "Autofill pitcher"
|
||||
},
|
||||
"bubble_soak": {
|
||||
"name": "Bubble Soak"
|
||||
},
|
||||
"cabinet_light_dim": {
|
||||
"name": "Brighten gradually"
|
||||
},
|
||||
"cabinet_light_switch": {
|
||||
"name": "Cabinet light"
|
||||
},
|
||||
"child_lock": {
|
||||
"name": "Child lock"
|
||||
},
|
||||
"defrost_delay": {
|
||||
"name": "Defrost delay"
|
||||
},
|
||||
"display_light": {
|
||||
"name": "Display light"
|
||||
},
|
||||
"fast_preheat": {
|
||||
"name": "Fast preheat"
|
||||
},
|
||||
"fridge_sound": {
|
||||
"name": "Sound"
|
||||
},
|
||||
"ice_maker_enabled": {
|
||||
"name": "Ice maker"
|
||||
},
|
||||
"ice_night_mode": {
|
||||
"name": "Nighttime ice quiet mode"
|
||||
},
|
||||
"instance_enabled": {
|
||||
"name": "{instance_name} enabled"
|
||||
},
|
||||
"intensive": {
|
||||
"name": "Intensive"
|
||||
},
|
||||
"lamp": {
|
||||
"name": "Lamp"
|
||||
},
|
||||
"led_night_light": {
|
||||
"name": "Door LED night light"
|
||||
},
|
||||
"mute_once": {
|
||||
"name": "Mute once"
|
||||
},
|
||||
"natural_steam": {
|
||||
"name": "Natural steam"
|
||||
},
|
||||
"power_switch": {
|
||||
"name": "Power"
|
||||
},
|
||||
"pre_wash": {
|
||||
"name": "Pre-wash"
|
||||
},
|
||||
"rapid_freezing": {
|
||||
"name": "Rapid freezing"
|
||||
},
|
||||
"rapid_fridge": {
|
||||
"name": "Rapid fridge"
|
||||
},
|
||||
"sabbath_mode": {
|
||||
"name": "Sabbath mode"
|
||||
},
|
||||
"sanitize": {
|
||||
"name": "Sanitize"
|
||||
},
|
||||
"sound": {
|
||||
"name": "Sound"
|
||||
},
|
||||
"storm_wash": {
|
||||
"name": "Storm Wash+"
|
||||
},
|
||||
"welcome_lighting": {
|
||||
"name": "Welcome lighting"
|
||||
},
|
||||
"wrinkle_prevent": {
|
||||
"name": "Wrinkle prevent"
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
"led_night_end": {
|
||||
"name": "Door LED night end"
|
||||
},
|
||||
"led_night_start": {
|
||||
"name": "Door LED night start"
|
||||
},
|
||||
"night_end": {
|
||||
"name": "Night light end"
|
||||
},
|
||||
"night_start": {
|
||||
"name": "Night light start"
|
||||
}
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Add Samsung Appliance",
|
||||
"description": "Enter the IP address of your Samsung appliance and the AC14K_M CA credentials used to mint device certificates. {reuse_note}",
|
||||
"title": "Add Samsung appliance",
|
||||
"description": "Enter the IP address of your Samsung appliance and the AC14K_M CA credentials used to mint device certificates.",
|
||||
"data": {
|
||||
"host": "IP Address",
|
||||
"ca_cert_pem": "CA Certificate (PEM)",
|
||||
"ca_key_pem": "CA Private Key (PEM)"
|
||||
"host": "IP address",
|
||||
"ca_cert_pem": "CA certificate (PEM)",
|
||||
"ca_key_pem": "CA private key (PEM)"
|
||||
},
|
||||
"data_description": {
|
||||
"host": "Local IP address of your Samsung appliance (e.g. 192.168.1.50).",
|
||||
@@ -232,9 +731,23 @@
|
||||
"ca_key_pem": "The PEM-encoded AC14K_M private key. Paste the full contents including the BEGIN/END PRIVATE KEY header and footer."
|
||||
}
|
||||
},
|
||||
"user_reuse": {
|
||||
"title": "Add Samsung appliance",
|
||||
"description": "Enter the IP address of your Samsung appliance. The CA credentials from an existing LocalThings appliance will be reused.",
|
||||
"data": {
|
||||
"host": "IP address"
|
||||
},
|
||||
"data_description": {
|
||||
"host": "Local IP address of your Samsung appliance (e.g. 192.168.1.50)."
|
||||
}
|
||||
},
|
||||
"confirm_unknown_type": {
|
||||
"title": "Appliance type not recognized",
|
||||
"description": "This appliance reported oneUiVersion \"{one_ui_version}\", which isn't a recognized type. It'll still be added, but only with common capabilities (power, alarms, etc. where present) rather than the full set for its family. You can help add full support afterward by downloading diagnostics for this device (Settings > Devices & Services > this device > the menu > Download diagnostics) and filing them in a new issue. Submit to add it anyway."
|
||||
},
|
||||
"confirm_unknown_type_no_version": {
|
||||
"title": "Appliance type not recognized",
|
||||
"description": "This appliance did not report a oneUiVersion and its type couldn't be recognized. It'll still be added, but only with common capabilities (power, alarms, etc. where present) rather than the full set for its family. You can help add full support afterward by downloading diagnostics for this device (Settings > Devices & Services > this device > the menu > Download diagnostics) and filing them in a new issue. Submit to add it anyway."
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -243,13 +756,13 @@
|
||||
"unknown": "Unexpected error. Check the Home Assistant logs for details."
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "This device is already configured."
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"title": "Local Things Options",
|
||||
"title": "LocalThings options",
|
||||
"menu_options": {
|
||||
"settings": "Remote control write settings",
|
||||
"debug_write": "Debug: write to a resource"
|
||||
@@ -298,5 +811,25 @@
|
||||
"title": "Incomplete capability coverage for {device_name}",
|
||||
"description": "This device is missing full capability coverage. Either its appliance type wasn't recognized, or some of the resources it exposes aren't modeled yet. It'll keep working with whatever is already supported. You can help expand support by going to Settings > Devices & Services > {device_name} > the menu (top right) > Download diagnostics, then filing it with the linked issue template."
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"remote_control_disabled": {
|
||||
"message": "Remote control is turned off on this device. Check your appliance's manual for how to enable remote control before Home Assistant can control it."
|
||||
},
|
||||
"debug_payload_empty": {
|
||||
"message": "Debug write payload must be a non-empty object."
|
||||
},
|
||||
"resource_href_required": {
|
||||
"message": "A resource href is required."
|
||||
},
|
||||
"bubble_soak_unavailable_for_cycle": {
|
||||
"message": "Bubble Soak isn't available on the selected cycle."
|
||||
},
|
||||
"pre_wash_unavailable_for_cycle": {
|
||||
"message": "Pre-wash isn't available on the selected cycle."
|
||||
},
|
||||
"intensive_unavailable_for_cycle": {
|
||||
"message": "Intensive isn't available on the selected cycle."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,187 +1,88 @@
|
||||
{
|
||||
"entity": {
|
||||
"select": {
|
||||
"door_alert": {
|
||||
"state": {
|
||||
"1": "Alarm 1",
|
||||
"2": "Alarm 2",
|
||||
"3": "Alarm 3",
|
||||
"4": "Alarm 4"
|
||||
}
|
||||
"binary_sensor": {
|
||||
"any_burner_active": {
|
||||
"name": "Any burner active"
|
||||
},
|
||||
"brightness_level": {
|
||||
"state": {
|
||||
"33": "Low",
|
||||
"66": "Medium",
|
||||
"100": "High"
|
||||
}
|
||||
"automatic_operation": {
|
||||
"name": "Automatic operation"
|
||||
},
|
||||
"range_hood_lamp_brightness": {
|
||||
"state": {
|
||||
"1": "Level 1",
|
||||
"2": "Level 2"
|
||||
}
|
||||
"burner_hot_surface": {
|
||||
"name": "Burner {number} hot surface"
|
||||
},
|
||||
"ice_type": {
|
||||
"state": {
|
||||
"off": "Off",
|
||||
"whiskey_iceball_3": "3 Balls/Day",
|
||||
"whiskey_iceball_6": "6 Balls/Day",
|
||||
"whiskey_iceball_9": "9 Balls/Day"
|
||||
}
|
||||
"cloud_connected": {
|
||||
"name": "Cloud connected"
|
||||
},
|
||||
"beverage_zone_mode": {
|
||||
"state": {
|
||||
"sp_ttype_beer_drinks": "Beverage",
|
||||
"sp_ttype_wine_dessert": "Wine and Dessert"
|
||||
}
|
||||
"cooktop_safety_shutoff_enabled": {
|
||||
"name": "Hot surface auto-shutoff enabled"
|
||||
},
|
||||
"pantry_zone_mode": {
|
||||
"state": {
|
||||
"fdr_wine": "Wine",
|
||||
"fdr_deli": "Deli",
|
||||
"fdr_drinks": "Drinks"
|
||||
}
|
||||
"current_limit_enabled": {
|
||||
"name": "Current limit enabled"
|
||||
},
|
||||
"flex_zone_mode": {
|
||||
"state": {
|
||||
"cv_ttype_rf9000a_freeze": "Freeze",
|
||||
"cv_ttype_rf9000a_softfreeze": "Soft Freeze",
|
||||
"cv_ttype_rf9000a_meat_fish": "Meat/Fish",
|
||||
"cv_ttype_rf9000a_fruit_veggies": "Fruit & Veggies",
|
||||
"cv_ttype_rf9000a_beverage": "Beverage",
|
||||
"cv_fdr_wine": "Wine",
|
||||
"cv_fdr_deli": "Deli",
|
||||
"cv_fdr_beverage": "Beverage",
|
||||
"cv_fdr_meat": "Meat",
|
||||
"cv_fdr_soft_freezer": "Soft Freezer"
|
||||
}
|
||||
"cycle_active": {
|
||||
"name": "Running"
|
||||
},
|
||||
"dishwasher_cycle": {
|
||||
"state": {
|
||||
"0e": "AI Wash",
|
||||
"07": "Pre blast",
|
||||
"90": "Self clean",
|
||||
"86": "Normal",
|
||||
"83": "Express 60",
|
||||
"84": "Heavy",
|
||||
"8d": "Pots and pans",
|
||||
"80": "Delicate",
|
||||
"8e": "Plastic",
|
||||
"8f": "Baby Care"
|
||||
}
|
||||
"defrost_active": {
|
||||
"name": "Defrost active"
|
||||
},
|
||||
"washer_cycle_table_02": {
|
||||
"state": {
|
||||
"1c": "Eco 40-60",
|
||||
"1d": "Super Speed",
|
||||
"21": "Colours",
|
||||
"1b": "Cotton",
|
||||
"1e": "15' Quick Wash",
|
||||
"29": "Drum Clean+",
|
||||
"24": "Towels",
|
||||
"33": "Bedding",
|
||||
"28": "Drain/Spin",
|
||||
"26": "Delicates",
|
||||
"27": "Rinse+Spin",
|
||||
"22": "Wool",
|
||||
"20": "Hygiene Steam",
|
||||
"23": "Outdoor",
|
||||
"25": "Synthetics",
|
||||
"32": "Shirts",
|
||||
"2f": "Activewear",
|
||||
"2e": "Baby Care",
|
||||
"30": "Cloudy Day",
|
||||
"66": "Denim",
|
||||
"2d": "Silent Wash",
|
||||
"8f": "Intense Cold",
|
||||
"96": "Less Microfiber",
|
||||
"36": "Wash+Dry",
|
||||
"37": "Air Wash",
|
||||
"38": "Cotton Dry",
|
||||
"39": "Synthetics Dry",
|
||||
"1f": "Intense Cold"
|
||||
}
|
||||
"detergent_low": {
|
||||
"name": "Detergent low"
|
||||
},
|
||||
"dryer_cycle_table_03": {
|
||||
"state": {
|
||||
"16": "Cotton",
|
||||
"18": "Synthetics",
|
||||
"19": "Delicates",
|
||||
"1a": "Wool",
|
||||
"1b": "Bedding",
|
||||
"1c": "Shirts",
|
||||
"1d": "Towels",
|
||||
"1e": "Outdoor",
|
||||
"1f": "Mixed Load",
|
||||
"20": "Iron Dry",
|
||||
"23": "Quick Dry 35",
|
||||
"24": "Cool Air",
|
||||
"25": "Warm Air",
|
||||
"27": "Time Dry"
|
||||
}
|
||||
"device_active": {
|
||||
"name": "Device active"
|
||||
},
|
||||
"washer_dosing_quantity": {
|
||||
"state": {
|
||||
"00": "None",
|
||||
"01": "Low",
|
||||
"02": "Medium",
|
||||
"03": "High"
|
||||
}
|
||||
"door_open": {
|
||||
"name": "Door"
|
||||
},
|
||||
"washer_detergent_water_hardness": {
|
||||
"state": {
|
||||
"01": "Soft",
|
||||
"02": "Medium",
|
||||
"03": "Hard"
|
||||
}
|
||||
"firmware_update": {
|
||||
"name": "Firmware update available"
|
||||
},
|
||||
"washer_softener_concentration": {
|
||||
"state": {
|
||||
"01": "1x",
|
||||
"02": "2x",
|
||||
"03": "3x"
|
||||
}
|
||||
"instance_open": {
|
||||
"name": "{instance_name} open"
|
||||
},
|
||||
"washer_dry_level": {
|
||||
"state": {
|
||||
"none": "Off",
|
||||
"cupboard": "Cupboard",
|
||||
"30": "30 min",
|
||||
"60": "1 hr",
|
||||
"90": "1 hr 30",
|
||||
"120": "2 hr",
|
||||
"180": "3 hr",
|
||||
"240": "4 hr"
|
||||
}
|
||||
"paired_hood_connected": {
|
||||
"name": "Paired hood connected"
|
||||
},
|
||||
"paired_hood_light": {
|
||||
"name": "Paired hood light"
|
||||
},
|
||||
"paired_hood_power": {
|
||||
"name": "Paired hood power"
|
||||
},
|
||||
"periodic_air_sensing": {
|
||||
"name": "Periodic air sensing"
|
||||
},
|
||||
"power_state": {
|
||||
"name": "Power state"
|
||||
},
|
||||
"rapid_freezing": {
|
||||
"name": "Rapid freezing"
|
||||
},
|
||||
"rapid_fridge": {
|
||||
"name": "Rapid fridge"
|
||||
},
|
||||
"remote_control": {
|
||||
"name": "Smart Control"
|
||||
},
|
||||
"softener_low": {
|
||||
"name": "Softener low"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"ice_making_status": {
|
||||
"state": {
|
||||
"icestatus_stop": "Idle",
|
||||
"icestatus_run": "Making ice"
|
||||
}
|
||||
"button": {
|
||||
"diagnosis_start": {
|
||||
"name": "Start diagnosis"
|
||||
},
|
||||
"connection_mode": {
|
||||
"state": {
|
||||
"observe": "Push (observe)",
|
||||
"poll": "Polling"
|
||||
}
|
||||
"pause": {
|
||||
"name": "Pause"
|
||||
},
|
||||
"machine_state": {
|
||||
"state": {
|
||||
"idle": "Idle",
|
||||
"active": "Active",
|
||||
"pause": "Paused"
|
||||
}
|
||||
"selfcheck_start": {
|
||||
"name": "Start self-check"
|
||||
},
|
||||
"air_filter_status": {
|
||||
"state": {
|
||||
"normal": "Normal",
|
||||
"wash": "Wash",
|
||||
"replace": "Replace"
|
||||
}
|
||||
"start": {
|
||||
"name": "Start"
|
||||
},
|
||||
"stop": {
|
||||
"name": "Stop"
|
||||
}
|
||||
},
|
||||
"climate": {
|
||||
@@ -189,24 +90,11 @@
|
||||
"state_attributes": {
|
||||
"fan_mode": {
|
||||
"state": {
|
||||
"auto": "Auto",
|
||||
"low": "Low",
|
||||
"medium": "Medium",
|
||||
"high": "High",
|
||||
"turbo": "Turbo"
|
||||
}
|
||||
},
|
||||
"swing_mode": {
|
||||
"state": {
|
||||
"off": "Fixed",
|
||||
"both": "All directions",
|
||||
"vertical": "Up and down"
|
||||
}
|
||||
},
|
||||
"preset_mode": {
|
||||
"state": {
|
||||
"none": "Off",
|
||||
"sleep": "Sleep",
|
||||
"quiet": "Quiet",
|
||||
"smart": "Smart",
|
||||
"speed": "Speed"
|
||||
@@ -214,17 +102,628 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"number": {
|
||||
"cook_time": {
|
||||
"name": "Cook time"
|
||||
},
|
||||
"delay_start_hours": {
|
||||
"name": "Delay start"
|
||||
},
|
||||
"instance_setpoint": {
|
||||
"name": "{instance_name} setpoint"
|
||||
},
|
||||
"oven_setpoint": {
|
||||
"name": "Setpoint"
|
||||
},
|
||||
"sound_volume": {
|
||||
"name": "Sound volume"
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
"ai_energy_level": {
|
||||
"name": "AI Energy Mode level"
|
||||
},
|
||||
"beverage_zone_mode": {
|
||||
"name": "Beverage zone mode",
|
||||
"state": {
|
||||
"sp_ttype_beer_drinks": "Beverage",
|
||||
"sp_ttype_wine_dessert": "Wine and dessert"
|
||||
}
|
||||
},
|
||||
"brightness_level": {
|
||||
"name": "Night brightness",
|
||||
"state": {
|
||||
"33": "Low",
|
||||
"66": "Medium",
|
||||
"100": "High"
|
||||
}
|
||||
},
|
||||
"day_brightness": {
|
||||
"name": "Cabinet brightness",
|
||||
"state": {
|
||||
"33": "Low",
|
||||
"66": "Medium",
|
||||
"100": "High"
|
||||
}
|
||||
},
|
||||
"buzzer_sound": {
|
||||
"name": "Buzzer sound",
|
||||
"state": {
|
||||
"off": "Off",
|
||||
"on": "On"
|
||||
}
|
||||
},
|
||||
"cycle": {
|
||||
"name": "Cycle"
|
||||
},
|
||||
"detergent_quantity": {
|
||||
"name": "Detergent quantity",
|
||||
"state": {
|
||||
"00": "None",
|
||||
"01": "Low",
|
||||
"02": "Medium",
|
||||
"03": "High"
|
||||
}
|
||||
},
|
||||
"detergent_water_hardness": {
|
||||
"name": "Detergent water hardness",
|
||||
"state": {
|
||||
"01": "Soft",
|
||||
"02": "Medium",
|
||||
"03": "Hard"
|
||||
}
|
||||
},
|
||||
"dishwasher_cycle": {
|
||||
"name": "Cycle",
|
||||
"state": {
|
||||
"80": "Delicate",
|
||||
"83": "Express 60",
|
||||
"84": "Heavy",
|
||||
"86": "Normal",
|
||||
"90": "Self clean",
|
||||
"0e": "AI Wash",
|
||||
"07": "Pre blast",
|
||||
"8d": "Pots and pans",
|
||||
"8e": "Plastic",
|
||||
"8f": "Baby Care"
|
||||
}
|
||||
},
|
||||
"door_alert": {
|
||||
"name": "Door alarm",
|
||||
"state": {
|
||||
"1": "Alarm 1",
|
||||
"2": "Alarm 2",
|
||||
"3": "Alarm 3",
|
||||
"4": "Alarm 4"
|
||||
}
|
||||
},
|
||||
"dryer_cycle_table_03": {
|
||||
"name": "Cycle",
|
||||
"state": {
|
||||
"16": "Cotton",
|
||||
"18": "Synthetics",
|
||||
"19": "Delicates",
|
||||
"20": "Iron dry",
|
||||
"23": "Quick Dry 35",
|
||||
"24": "Cool air",
|
||||
"25": "Warm air",
|
||||
"27": "Time dry",
|
||||
"1a": "Wool",
|
||||
"1b": "Bedding",
|
||||
"1c": "Shirts",
|
||||
"1d": "Towels",
|
||||
"1e": "Outdoor",
|
||||
"1f": "Mixed load"
|
||||
}
|
||||
},
|
||||
"finish_sound": {
|
||||
"name": "Finish sound",
|
||||
"state": {
|
||||
"off": "Off",
|
||||
"on": "On"
|
||||
}
|
||||
},
|
||||
"flex_zone_mode": {
|
||||
"name": "Flex zone mode",
|
||||
"state": {
|
||||
"cv_ttype_rf9000a_freeze": "Freeze",
|
||||
"cv_ttype_rf9000a_softfreeze": "Soft freeze",
|
||||
"cv_ttype_rf9000a_meat_fish": "Meat/Fish",
|
||||
"cv_ttype_rf9000a_fruit_veggies": "Fruit & vegetables",
|
||||
"cv_ttype_rf9000a_beverage": "Beverage",
|
||||
"cv_fdr_wine": "Wine",
|
||||
"cv_fdr_deli": "Deli",
|
||||
"cv_fdr_beverage": "Beverage",
|
||||
"cv_fdr_meat": "Meat",
|
||||
"cv_fdr_soft_freezer": "Soft freezer"
|
||||
}
|
||||
},
|
||||
"heated_dry": {
|
||||
"name": "Smart Dry",
|
||||
"state": {
|
||||
"off": "Off",
|
||||
"low": "Low",
|
||||
"high": "High",
|
||||
"extra_high": "Extra high"
|
||||
}
|
||||
},
|
||||
"ice_type": {
|
||||
"name": "{instance_name} type",
|
||||
"state": {
|
||||
"off": "Off",
|
||||
"whiskey_iceball_3": "3 balls/day",
|
||||
"whiskey_iceball_6": "6 balls/day",
|
||||
"whiskey_iceball_9": "9 balls/day"
|
||||
}
|
||||
},
|
||||
"led_brightness": {
|
||||
"name": "Door LED brightness",
|
||||
"state": {
|
||||
"low": "Low",
|
||||
"high": "High"
|
||||
}
|
||||
},
|
||||
"led_night_brightness": {
|
||||
"name": "Door LED night brightness",
|
||||
"state": {
|
||||
"low": "Low",
|
||||
"high": "High"
|
||||
}
|
||||
},
|
||||
"oven_mode": {
|
||||
"name": "Cooking mode",
|
||||
"state": {
|
||||
"no_operation": "No operation",
|
||||
"bake": "Bake",
|
||||
"broil": "Broil",
|
||||
"convection": "Convection",
|
||||
"convection_bake": "Convection bake",
|
||||
"convection_broil": "Convection broil",
|
||||
"frozen_pizza_plus": "Frozen Pizza+",
|
||||
"slow_cook": "Slow cook",
|
||||
"plate_warm": "Plate warm",
|
||||
"air_fry": "Air fry"
|
||||
}
|
||||
},
|
||||
"pantry_zone_mode": {
|
||||
"name": "Pantry zone mode",
|
||||
"state": {
|
||||
"fdr_wine": "Wine",
|
||||
"fdr_deli": "Deli",
|
||||
"fdr_drinks": "Drinks"
|
||||
}
|
||||
},
|
||||
"range_burner_power_level": {
|
||||
"name": "Burner {number} power level",
|
||||
"state": {
|
||||
"0": "Off",
|
||||
"boost": "Boost",
|
||||
"simmer": "Simmer"
|
||||
}
|
||||
},
|
||||
"range_hood_lamp_brightness": {
|
||||
"name": "Lamp brightness",
|
||||
"state": {
|
||||
"1": "Level 1",
|
||||
"2": "Level 2"
|
||||
}
|
||||
},
|
||||
"rinse_cycles": {
|
||||
"name": "Rinse cycles"
|
||||
},
|
||||
"softener_concentration": {
|
||||
"name": "Softener concentration",
|
||||
"state": {
|
||||
"01": "1x",
|
||||
"02": "2x",
|
||||
"03": "3x"
|
||||
}
|
||||
},
|
||||
"softener_quantity": {
|
||||
"name": "Softener quantity",
|
||||
"state": {
|
||||
"00": "None",
|
||||
"01": "Low",
|
||||
"02": "Medium",
|
||||
"03": "High"
|
||||
}
|
||||
},
|
||||
"sound_mode": {
|
||||
"name": "Sound mode",
|
||||
"state": {
|
||||
"voice": "Voice",
|
||||
"tone": "Tone",
|
||||
"mute": "Mute"
|
||||
}
|
||||
},
|
||||
"spin_speed": {
|
||||
"name": "Spin speed",
|
||||
"state": {
|
||||
"rinse_hold": "Rinse hold",
|
||||
"no_spin": "No spin",
|
||||
"low": "Low",
|
||||
"medium": "Medium",
|
||||
"high": "High"
|
||||
}
|
||||
},
|
||||
"wash_temperature": {
|
||||
"name": "Wash temperature",
|
||||
"state": {
|
||||
"none": "None",
|
||||
"cold": "Cold",
|
||||
"cool": "Cool",
|
||||
"warm": "Warm",
|
||||
"hot": "Hot",
|
||||
"extra_hot": "Extra hot"
|
||||
}
|
||||
},
|
||||
"washer_cycle_table_02": {
|
||||
"name": "Cycle",
|
||||
"state": {
|
||||
"20": "Hygiene Steam",
|
||||
"21": "Colours",
|
||||
"22": "Wool",
|
||||
"23": "Outdoor",
|
||||
"24": "Towels",
|
||||
"25": "Synthetics",
|
||||
"26": "Delicates",
|
||||
"27": "Rinse+Spin",
|
||||
"28": "Drain/Spin",
|
||||
"29": "Drum Clean+",
|
||||
"30": "Cloudy Day",
|
||||
"32": "Shirts",
|
||||
"33": "Bedding",
|
||||
"36": "Wash+Dry",
|
||||
"37": "Air Wash",
|
||||
"38": "Cotton Dry",
|
||||
"39": "Synthetics Dry",
|
||||
"66": "Denim",
|
||||
"96": "Less Microfiber",
|
||||
"1c": "Eco 40-60",
|
||||
"1d": "Super Speed",
|
||||
"1b": "Cotton",
|
||||
"1e": "15' Quick Wash",
|
||||
"2f": "Activewear",
|
||||
"2e": "Baby Care",
|
||||
"2d": "Silent Wash",
|
||||
"8f": "Intense Cold",
|
||||
"1f": "Intense Cold"
|
||||
}
|
||||
},
|
||||
"washer_dry_level": {
|
||||
"name": "Dry level",
|
||||
"state": {
|
||||
"30": "30 min",
|
||||
"60": "1 hr",
|
||||
"90": "1 hr 30",
|
||||
"120": "2 hr",
|
||||
"180": "3 hr",
|
||||
"240": "4 hr",
|
||||
"none": "Off",
|
||||
"cupboard": "Cupboard"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"air_filter_usage": {
|
||||
"name": "Filter usage"
|
||||
},
|
||||
"air_sensing_state": {
|
||||
"name": "Air sensing state"
|
||||
},
|
||||
"alarm_code": {
|
||||
"name": "Alarm code"
|
||||
},
|
||||
"auto_ventilation_action": {
|
||||
"name": "Auto ventilation action"
|
||||
},
|
||||
"automatic_ventilation_state": {
|
||||
"name": "Automatic ventilation state"
|
||||
},
|
||||
"burner_state": {
|
||||
"name": "Burner {number} state"
|
||||
},
|
||||
"clean_level": {
|
||||
"name": "Clean level"
|
||||
},
|
||||
"connection_mode": {
|
||||
"name": "Connection mode",
|
||||
"state": {
|
||||
"observe": "Push (observe)",
|
||||
"poll": "Polling"
|
||||
}
|
||||
},
|
||||
"cooktop_state": {
|
||||
"name": "Cooktop state"
|
||||
},
|
||||
"current_limit_level": {
|
||||
"name": "Current limit level"
|
||||
},
|
||||
"current_temp_c": {
|
||||
"name": "Temperature"
|
||||
},
|
||||
"diagnosis": {
|
||||
"name": "Diagnosis"
|
||||
},
|
||||
"diagnosis_status": {
|
||||
"name": "Diagnosis status"
|
||||
},
|
||||
"drum_clean_cycles_remaining": {
|
||||
"name": "Drum clean due in"
|
||||
},
|
||||
"drum_clean_last_cleaned": {
|
||||
"name": "Drum last cleaned"
|
||||
},
|
||||
"dry_level": {
|
||||
"name": "Dry level"
|
||||
},
|
||||
"dry_time": {
|
||||
"name": "Dry time"
|
||||
},
|
||||
"dryer_type": {
|
||||
"name": "Dryer type"
|
||||
},
|
||||
"dust": {
|
||||
"name": "Dust"
|
||||
},
|
||||
"energy_kwh": {
|
||||
"name": "Energy"
|
||||
},
|
||||
"energy_last_month_kwh": {
|
||||
"name": "Energy (last month)"
|
||||
},
|
||||
"energy_saved_kwh": {
|
||||
"name": "Energy saved"
|
||||
},
|
||||
"energy_this_month_kwh": {
|
||||
"name": "Energy (this month)"
|
||||
},
|
||||
"fan_direction": {
|
||||
"name": "Fan direction"
|
||||
},
|
||||
"fan_speed_level": {
|
||||
"name": "Fan speed level"
|
||||
},
|
||||
"filter_progress": {
|
||||
"name": "Filter progress"
|
||||
},
|
||||
"filter_usage": {
|
||||
"name": "Filter usage"
|
||||
},
|
||||
"fine_dust": {
|
||||
"name": "Fine dust"
|
||||
},
|
||||
"finish_time": {
|
||||
"name": "Estimated finish"
|
||||
},
|
||||
"freezer_temperature": {
|
||||
"name": "Freezer temperature"
|
||||
},
|
||||
"fridge_temperature": {
|
||||
"name": "Fridge temperature"
|
||||
},
|
||||
"hood_filter_capacity": {
|
||||
"name": "Filter capacity"
|
||||
},
|
||||
"hood_filter_usage": {
|
||||
"name": "Filter usage"
|
||||
},
|
||||
"instance_temperature": {
|
||||
"name": "{instance_name} temperature"
|
||||
},
|
||||
"job_beginning_status": {
|
||||
"name": "Job beginning status"
|
||||
},
|
||||
"last_air_sensing_level": {
|
||||
"name": "Last air sensing level"
|
||||
},
|
||||
"last_air_sensing_time": {
|
||||
"name": "Last air sensing time"
|
||||
},
|
||||
"main_timer_current": {
|
||||
"name": "Timer current value"
|
||||
},
|
||||
"main_timer_state": {
|
||||
"name": "Timer state"
|
||||
},
|
||||
"odor": {
|
||||
"name": "Odor"
|
||||
},
|
||||
"operating_mode": {
|
||||
"name": "Operating mode"
|
||||
},
|
||||
"operation_origin": {
|
||||
"name": "Last operation source"
|
||||
},
|
||||
"operation_time_minutes": {
|
||||
"name": "Operation time"
|
||||
},
|
||||
"oven_state": {
|
||||
"name": "Cavity state"
|
||||
},
|
||||
"paired_hood_fan_speed": {
|
||||
"name": "Paired hood fan speed"
|
||||
},
|
||||
"paired_hood_firmware": {
|
||||
"name": "Paired hood firmware"
|
||||
},
|
||||
"paired_hood_model": {
|
||||
"name": "Paired hood model"
|
||||
},
|
||||
"power_energy_kwh": {
|
||||
"name": "Power energy"
|
||||
},
|
||||
"power_watts": {
|
||||
"name": "Power"
|
||||
},
|
||||
"progress": {
|
||||
"name": "Progress"
|
||||
},
|
||||
"progress_percentage": {
|
||||
"name": "Progress percent"
|
||||
},
|
||||
"selfcheck_error": {
|
||||
"name": "Self-check error"
|
||||
},
|
||||
"selfcheck_result": {
|
||||
"name": "Self-check result"
|
||||
},
|
||||
"selfcheck_status": {
|
||||
"name": "Self-check status"
|
||||
},
|
||||
"super_fine_dust": {
|
||||
"name": "Super fine dust"
|
||||
},
|
||||
"water_liters": {
|
||||
"name": "Water consumption"
|
||||
},
|
||||
"machine_state": {
|
||||
"name": "Machine state",
|
||||
"state": {
|
||||
"idle": "Idle",
|
||||
"active": "Active",
|
||||
"pause": "Paused"
|
||||
}
|
||||
},
|
||||
"filter_status": {
|
||||
"name": "Filter status",
|
||||
"state": {
|
||||
"normal": "Normal",
|
||||
"wash": "Wash",
|
||||
"replace": "Replace"
|
||||
}
|
||||
},
|
||||
"ice_making_status": {
|
||||
"name": "{instance_name} making status",
|
||||
"state": {
|
||||
"icestatus_stop": "Idle",
|
||||
"icestatus_run": "Making ice"
|
||||
}
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"ai_energy_level": {
|
||||
"name": "AI Energy Mode"
|
||||
},
|
||||
"air_purify": {
|
||||
"name": "Air purification"
|
||||
},
|
||||
"auto_clean": {
|
||||
"name": "Auto clean"
|
||||
},
|
||||
"auto_door_opener": {
|
||||
"name": "Auto door opener"
|
||||
},
|
||||
"auto_release_dry": {
|
||||
"name": "Auto Release Dry"
|
||||
},
|
||||
"autofill": {
|
||||
"name": "Autofill pitcher"
|
||||
},
|
||||
"bubble_soak": {
|
||||
"name": "Bubble Soak"
|
||||
},
|
||||
"cabinet_light_dim": {
|
||||
"name": "Brighten gradually"
|
||||
},
|
||||
"cabinet_light_switch": {
|
||||
"name": "Cabinet light"
|
||||
},
|
||||
"child_lock": {
|
||||
"name": "Child lock"
|
||||
},
|
||||
"defrost_delay": {
|
||||
"name": "Defrost delay"
|
||||
},
|
||||
"display_light": {
|
||||
"name": "Display light"
|
||||
},
|
||||
"fast_preheat": {
|
||||
"name": "Fast preheat"
|
||||
},
|
||||
"fridge_sound": {
|
||||
"name": "Sound"
|
||||
},
|
||||
"ice_maker_enabled": {
|
||||
"name": "Ice maker"
|
||||
},
|
||||
"ice_night_mode": {
|
||||
"name": "Nighttime ice quiet mode"
|
||||
},
|
||||
"instance_enabled": {
|
||||
"name": "{instance_name} enabled"
|
||||
},
|
||||
"intensive": {
|
||||
"name": "Intensive"
|
||||
},
|
||||
"lamp": {
|
||||
"name": "Lamp"
|
||||
},
|
||||
"led_night_light": {
|
||||
"name": "Door LED night light"
|
||||
},
|
||||
"mute_once": {
|
||||
"name": "Mute once"
|
||||
},
|
||||
"natural_steam": {
|
||||
"name": "Natural steam"
|
||||
},
|
||||
"power_switch": {
|
||||
"name": "Power"
|
||||
},
|
||||
"pre_wash": {
|
||||
"name": "Pre-wash"
|
||||
},
|
||||
"rapid_freezing": {
|
||||
"name": "Rapid freezing"
|
||||
},
|
||||
"rapid_fridge": {
|
||||
"name": "Rapid fridge"
|
||||
},
|
||||
"sabbath_mode": {
|
||||
"name": "Sabbath mode"
|
||||
},
|
||||
"sanitize": {
|
||||
"name": "Sanitize"
|
||||
},
|
||||
"sound": {
|
||||
"name": "Sound"
|
||||
},
|
||||
"storm_wash": {
|
||||
"name": "Storm Wash+"
|
||||
},
|
||||
"welcome_lighting": {
|
||||
"name": "Welcome lighting"
|
||||
},
|
||||
"wrinkle_prevent": {
|
||||
"name": "Wrinkle prevent"
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
"led_night_end": {
|
||||
"name": "Door LED night end"
|
||||
},
|
||||
"led_night_start": {
|
||||
"name": "Door LED night start"
|
||||
},
|
||||
"night_end": {
|
||||
"name": "Night light end"
|
||||
},
|
||||
"night_start": {
|
||||
"name": "Night light start"
|
||||
}
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Add Samsung Appliance",
|
||||
"description": "Enter the IP address of your Samsung appliance and the AC14K_M CA credentials used to mint device certificates. {reuse_note}",
|
||||
"title": "Add Samsung appliance",
|
||||
"description": "Enter the IP address of your Samsung appliance and the AC14K_M CA credentials used to mint device certificates.",
|
||||
"data": {
|
||||
"host": "IP Address",
|
||||
"ca_cert_pem": "CA Certificate (PEM)",
|
||||
"ca_key_pem": "CA Private Key (PEM)"
|
||||
"host": "IP address",
|
||||
"ca_cert_pem": "CA certificate (PEM)",
|
||||
"ca_key_pem": "CA private key (PEM)"
|
||||
},
|
||||
"data_description": {
|
||||
"host": "Local IP address of your Samsung appliance (e.g. 192.168.1.50).",
|
||||
@@ -232,9 +731,23 @@
|
||||
"ca_key_pem": "The PEM-encoded AC14K_M private key. Paste the full contents including the BEGIN/END PRIVATE KEY header and footer."
|
||||
}
|
||||
},
|
||||
"user_reuse": {
|
||||
"title": "Add Samsung appliance",
|
||||
"description": "Enter the IP address of your Samsung appliance. The CA credentials from an existing LocalThings appliance will be reused.",
|
||||
"data": {
|
||||
"host": "IP address"
|
||||
},
|
||||
"data_description": {
|
||||
"host": "Local IP address of your Samsung appliance (e.g. 192.168.1.50)."
|
||||
}
|
||||
},
|
||||
"confirm_unknown_type": {
|
||||
"title": "Appliance type not recognized",
|
||||
"description": "This appliance reported oneUiVersion \"{one_ui_version}\", which isn't a recognized type. It'll still be added, but only with common capabilities (power, alarms, etc. where present) rather than the full set for its family. You can help add full support afterward by downloading diagnostics for this device (Settings > Devices & Services > this device > the menu > Download diagnostics) and filing them in a new issue. Submit to add it anyway."
|
||||
},
|
||||
"confirm_unknown_type_no_version": {
|
||||
"title": "Appliance type not recognized",
|
||||
"description": "This appliance did not report a oneUiVersion and its type couldn't be recognized. It'll still be added, but only with common capabilities (power, alarms, etc. where present) rather than the full set for its family. You can help add full support afterward by downloading diagnostics for this device (Settings > Devices & Services > this device > the menu > Download diagnostics) and filing them in a new issue. Submit to add it anyway."
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -243,13 +756,13 @@
|
||||
"unknown": "Unexpected error. Check the Home Assistant logs for details."
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "This device is already configured."
|
||||
"already_configured": "Device is already configured"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"title": "Local Things Options",
|
||||
"title": "LocalThings options",
|
||||
"menu_options": {
|
||||
"settings": "Remote control write settings",
|
||||
"debug_write": "Debug: write to a resource"
|
||||
@@ -298,5 +811,25 @@
|
||||
"title": "Incomplete capability coverage for {device_name}",
|
||||
"description": "This device is missing full capability coverage. Either its appliance type wasn't recognized, or some of the resources it exposes aren't modeled yet. It'll keep working with whatever is already supported. You can help expand support by going to Settings > Devices & Services > {device_name} > the menu (top right) > Download diagnostics, then filing it with the linked issue template."
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"remote_control_disabled": {
|
||||
"message": "Remote control is turned off on this device. Check your appliance's manual for how to enable remote control before Home Assistant can control it."
|
||||
},
|
||||
"debug_payload_empty": {
|
||||
"message": "Debug write payload must be a non-empty object."
|
||||
},
|
||||
"resource_href_required": {
|
||||
"message": "A resource href is required."
|
||||
},
|
||||
"bubble_soak_unavailable_for_cycle": {
|
||||
"message": "Bubble Soak isn't available on the selected cycle."
|
||||
},
|
||||
"pre_wash_unavailable_for_cycle": {
|
||||
"message": "Pre-wash isn't available on the selected cycle."
|
||||
},
|
||||
"intensive_unavailable_for_cycle": {
|
||||
"message": "Intensive isn't available on the selected cycle."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,835 @@
|
||||
{
|
||||
"entity": {
|
||||
"binary_sensor": {
|
||||
"any_burner_active": {
|
||||
"name": "Een brander actief"
|
||||
},
|
||||
"automatic_operation": {
|
||||
"name": "Automatische werking"
|
||||
},
|
||||
"burner_hot_surface": {
|
||||
"name": "Brander {number} heet oppervlak"
|
||||
},
|
||||
"cloud_connected": {
|
||||
"name": "Verbonden met cloud"
|
||||
},
|
||||
"cooktop_safety_shutoff_enabled": {
|
||||
"name": "Automatische uitschakeling heet oppervlak ingeschakeld"
|
||||
},
|
||||
"current_limit_enabled": {
|
||||
"name": "Stroombegrenzing ingeschakeld"
|
||||
},
|
||||
"cycle_active": {
|
||||
"name": "Actief"
|
||||
},
|
||||
"defrost_active": {
|
||||
"name": "Ontdooien actief"
|
||||
},
|
||||
"detergent_low": {
|
||||
"name": "Wasmiddel bijna op"
|
||||
},
|
||||
"device_active": {
|
||||
"name": "Apparaat actief"
|
||||
},
|
||||
"door_open": {
|
||||
"name": "Deur"
|
||||
},
|
||||
"firmware_update": {
|
||||
"name": "Firmware-update beschikbaar"
|
||||
},
|
||||
"instance_open": {
|
||||
"name": "{instance_name} open"
|
||||
},
|
||||
"paired_hood_connected": {
|
||||
"name": "Gekoppelde afzuigkap verbonden"
|
||||
},
|
||||
"paired_hood_light": {
|
||||
"name": "Verlichting gekoppelde afzuigkap"
|
||||
},
|
||||
"paired_hood_power": {
|
||||
"name": "Gekoppelde afzuigkap actief"
|
||||
},
|
||||
"periodic_air_sensing": {
|
||||
"name": "Periodieke luchtmeting"
|
||||
},
|
||||
"power_state": {
|
||||
"name": "Voedingsstatus"
|
||||
},
|
||||
"rapid_freezing": {
|
||||
"name": "Snelvriezen"
|
||||
},
|
||||
"rapid_fridge": {
|
||||
"name": "Snelkoelen"
|
||||
},
|
||||
"remote_control": {
|
||||
"name": "Smart Control"
|
||||
},
|
||||
"softener_low": {
|
||||
"name": "Wasverzachter bijna op"
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"diagnosis_start": {
|
||||
"name": "Diagnose starten"
|
||||
},
|
||||
"pause": {
|
||||
"name": "Pauzeren"
|
||||
},
|
||||
"selfcheck_start": {
|
||||
"name": "Zelfcontrole starten"
|
||||
},
|
||||
"start": {
|
||||
"name": "Starten"
|
||||
},
|
||||
"stop": {
|
||||
"name": "Stoppen"
|
||||
}
|
||||
},
|
||||
"climate": {
|
||||
"airconditioner": {
|
||||
"state_attributes": {
|
||||
"fan_mode": {
|
||||
"state": {
|
||||
"turbo": "Turbo"
|
||||
}
|
||||
},
|
||||
"preset_mode": {
|
||||
"state": {
|
||||
"quiet": "Stil",
|
||||
"smart": "Slim",
|
||||
"speed": "Snel"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"number": {
|
||||
"cook_time": {
|
||||
"name": "Bereidingstijd"
|
||||
},
|
||||
"delay_start_hours": {
|
||||
"name": "Uitgestelde start"
|
||||
},
|
||||
"instance_setpoint": {
|
||||
"name": "Instelpunt {instance_name}"
|
||||
},
|
||||
"oven_setpoint": {
|
||||
"name": "Instelpunt"
|
||||
},
|
||||
"sound_volume": {
|
||||
"name": "Geluidsvolume"
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
"ai_energy_level": {
|
||||
"name": "Niveau AI Energy Mode"
|
||||
},
|
||||
"beverage_zone_mode": {
|
||||
"name": "Modus drankenzone",
|
||||
"state": {
|
||||
"sp_ttype_beer_drinks": "Dranken",
|
||||
"sp_ttype_wine_dessert": "Wijn en dessert"
|
||||
}
|
||||
},
|
||||
"brightness_level": {
|
||||
"name": "Nachthelderheid",
|
||||
"state": {
|
||||
"33": "Laag",
|
||||
"66": "Gemiddeld",
|
||||
"100": "Hoog"
|
||||
}
|
||||
},
|
||||
"day_brightness": {
|
||||
"name": "Helderheid kast",
|
||||
"state": {
|
||||
"33": "Laag",
|
||||
"66": "Gemiddeld",
|
||||
"100": "Hoog"
|
||||
}
|
||||
},
|
||||
"buzzer_sound": {
|
||||
"name": "Zoemergeluid",
|
||||
"state": {
|
||||
"off": "Uit",
|
||||
"on": "Aan"
|
||||
}
|
||||
},
|
||||
"cycle": {
|
||||
"name": "Programma"
|
||||
},
|
||||
"detergent_quantity": {
|
||||
"name": "Hoeveelheid wasmiddel",
|
||||
"state": {
|
||||
"00": "Geen",
|
||||
"01": "Laag",
|
||||
"02": "Gemiddeld",
|
||||
"03": "Hoog"
|
||||
}
|
||||
},
|
||||
"detergent_water_hardness": {
|
||||
"name": "Waterhardheid voor wasmiddel",
|
||||
"state": {
|
||||
"01": "Zacht",
|
||||
"02": "Gemiddeld",
|
||||
"03": "Hard"
|
||||
}
|
||||
},
|
||||
"dishwasher_cycle": {
|
||||
"name": "Programma",
|
||||
"state": {
|
||||
"80": "Delicaat",
|
||||
"83": "Express 60",
|
||||
"84": "Intensief",
|
||||
"86": "Normaal",
|
||||
"90": "Zelfreiniging",
|
||||
"0e": "AI Wash",
|
||||
"07": "Voorspoelen",
|
||||
"8d": "Potten en pannen",
|
||||
"8e": "Kunststof",
|
||||
"8f": "Babyverzorging"
|
||||
}
|
||||
},
|
||||
"door_alert": {
|
||||
"name": "Deuralarm",
|
||||
"state": {
|
||||
"1": "Alarm 1",
|
||||
"2": "Alarm 2",
|
||||
"3": "Alarm 3",
|
||||
"4": "Alarm 4"
|
||||
}
|
||||
},
|
||||
"dryer_cycle_table_03": {
|
||||
"name": "Programma",
|
||||
"state": {
|
||||
"16": "Katoen",
|
||||
"18": "Synthetisch",
|
||||
"19": "Fijne was",
|
||||
"20": "Strijkdroog",
|
||||
"23": "Snel drogen 35'",
|
||||
"24": "Koude lucht",
|
||||
"25": "Warme lucht",
|
||||
"27": "Tijdprogramma",
|
||||
"1a": "Wol",
|
||||
"1b": "Beddengoed",
|
||||
"1c": "Overhemden",
|
||||
"1d": "Handdoeken",
|
||||
"1e": "Outdoor",
|
||||
"1f": "Gemengde was"
|
||||
}
|
||||
},
|
||||
"finish_sound": {
|
||||
"name": "Eindgeluid",
|
||||
"state": {
|
||||
"off": "Uit",
|
||||
"on": "Aan"
|
||||
}
|
||||
},
|
||||
"flex_zone_mode": {
|
||||
"name": "Modus FlexZone",
|
||||
"state": {
|
||||
"cv_ttype_rf9000a_freeze": "Vriezen",
|
||||
"cv_ttype_rf9000a_softfreeze": "Zachtvriezen",
|
||||
"cv_ttype_rf9000a_meat_fish": "Vlees/vis",
|
||||
"cv_ttype_rf9000a_fruit_veggies": "Fruit en groenten",
|
||||
"cv_ttype_rf9000a_beverage": "Dranken",
|
||||
"cv_fdr_wine": "Wijn",
|
||||
"cv_fdr_deli": "Delicatessen",
|
||||
"cv_fdr_beverage": "Dranken",
|
||||
"cv_fdr_meat": "Vlees",
|
||||
"cv_fdr_soft_freezer": "Zachtvriezer"
|
||||
}
|
||||
},
|
||||
"heated_dry": {
|
||||
"name": "Smart Dry",
|
||||
"state": {
|
||||
"off": "Uit",
|
||||
"low": "Laag",
|
||||
"high": "Hoog",
|
||||
"extra_high": "Extra hoog"
|
||||
}
|
||||
},
|
||||
"ice_type": {
|
||||
"name": "Type {instance_name}",
|
||||
"state": {
|
||||
"off": "Uit",
|
||||
"whiskey_iceball_3": "3 ijsballen per dag",
|
||||
"whiskey_iceball_6": "6 ijsballen per dag",
|
||||
"whiskey_iceball_9": "9 ijsballen per dag"
|
||||
}
|
||||
},
|
||||
"led_brightness": {
|
||||
"name": "Helderheid deur-LED",
|
||||
"state": {
|
||||
"low": "Laag",
|
||||
"high": "Hoog"
|
||||
}
|
||||
},
|
||||
"led_night_brightness": {
|
||||
"name": "Nachtelijke helderheid deur-LED",
|
||||
"state": {
|
||||
"low": "Laag",
|
||||
"high": "Hoog"
|
||||
}
|
||||
},
|
||||
"oven_mode": {
|
||||
"name": "Bereidingsmodus",
|
||||
"state": {
|
||||
"no_operation": "Niet actief",
|
||||
"bake": "Bakken",
|
||||
"broil": "Grillen",
|
||||
"convection": "Hetelucht",
|
||||
"convection_bake": "Bakken met hetelucht",
|
||||
"convection_broil": "Grillen met hetelucht",
|
||||
"frozen_pizza_plus": "Frozen Pizza+",
|
||||
"slow_cook": "Langzaam garen",
|
||||
"plate_warm": "Borden warmhouden",
|
||||
"air_fry": "Airfryen"
|
||||
}
|
||||
},
|
||||
"pantry_zone_mode": {
|
||||
"name": "Modus voorraadzone",
|
||||
"state": {
|
||||
"fdr_wine": "Wijn",
|
||||
"fdr_deli": "Delicatessen",
|
||||
"fdr_drinks": "Dranken"
|
||||
}
|
||||
},
|
||||
"range_burner_power_level": {
|
||||
"name": "Vermogensniveau brander {number}",
|
||||
"state": {
|
||||
"0": "Uit",
|
||||
"boost": "Boost",
|
||||
"simmer": "Sudderen"
|
||||
}
|
||||
},
|
||||
"range_hood_lamp_brightness": {
|
||||
"name": "Helderheid lamp",
|
||||
"state": {
|
||||
"1": "Niveau 1",
|
||||
"2": "Niveau 2"
|
||||
}
|
||||
},
|
||||
"rinse_cycles": {
|
||||
"name": "Spoelcycli"
|
||||
},
|
||||
"softener_concentration": {
|
||||
"name": "Concentratie wasverzachter",
|
||||
"state": {
|
||||
"01": "1x",
|
||||
"02": "2x",
|
||||
"03": "3x"
|
||||
}
|
||||
},
|
||||
"softener_quantity": {
|
||||
"name": "Hoeveelheid wasverzachter",
|
||||
"state": {
|
||||
"00": "Geen",
|
||||
"01": "Laag",
|
||||
"02": "Gemiddeld",
|
||||
"03": "Hoog"
|
||||
}
|
||||
},
|
||||
"sound_mode": {
|
||||
"name": "Geluidsmodus",
|
||||
"state": {
|
||||
"voice": "Spraak",
|
||||
"tone": "Toon",
|
||||
"mute": "Dempen"
|
||||
}
|
||||
},
|
||||
"spin_speed": {
|
||||
"name": "Centrifugesnelheid",
|
||||
"state": {
|
||||
"rinse_hold": "Spoelstop",
|
||||
"no_spin": "Niet centrifugeren",
|
||||
"low": "Laag",
|
||||
"medium": "Gemiddeld",
|
||||
"high": "Hoog"
|
||||
}
|
||||
},
|
||||
"wash_temperature": {
|
||||
"name": "Wastemperatuur",
|
||||
"state": {
|
||||
"none": "Geen",
|
||||
"cold": "Koud",
|
||||
"cool": "Koel",
|
||||
"warm": "Warm",
|
||||
"hot": "Heet",
|
||||
"extra_hot": "Extra heet"
|
||||
}
|
||||
},
|
||||
"washer_cycle_table_02": {
|
||||
"name": "Programma",
|
||||
"state": {
|
||||
"20": "Hygiënisch stomen",
|
||||
"21": "Bonte was",
|
||||
"22": "Wol",
|
||||
"23": "Outdoor",
|
||||
"24": "Handdoeken",
|
||||
"25": "Synthetisch",
|
||||
"26": "Fijne was",
|
||||
"27": "Spoelen+centrifugeren",
|
||||
"28": "Afpompen/centrifugeren",
|
||||
"29": "Drum Clean+",
|
||||
"30": "Bewolkte dag",
|
||||
"32": "Overhemden",
|
||||
"33": "Beddengoed",
|
||||
"36": "Wassen+drogen",
|
||||
"37": "Air Wash",
|
||||
"38": "Katoen drogen",
|
||||
"39": "Synthetisch drogen",
|
||||
"66": "Spijkergoed",
|
||||
"96": "Minder microvezels",
|
||||
"1c": "Eco 40-60",
|
||||
"1d": "Super Speed",
|
||||
"1b": "Katoen",
|
||||
"1e": "15' Quick Wash",
|
||||
"2f": "Sportkleding",
|
||||
"2e": "Babyverzorging",
|
||||
"2d": "Stille was",
|
||||
"8f": "Intensief koud",
|
||||
"1f": "Intensief koud"
|
||||
}
|
||||
},
|
||||
"washer_dry_level": {
|
||||
"name": "Droogniveau",
|
||||
"state": {
|
||||
"30": "30 min",
|
||||
"60": "1 uur",
|
||||
"90": "1 uur 30 min",
|
||||
"120": "2 uur",
|
||||
"180": "3 uur",
|
||||
"240": "4 uur",
|
||||
"none": "Uit",
|
||||
"cupboard": "Kastdroog"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"air_filter_usage": {
|
||||
"name": "Filterverbruik"
|
||||
},
|
||||
"air_sensing_state": {
|
||||
"name": "Status luchtmeting"
|
||||
},
|
||||
"alarm_code": {
|
||||
"name": "Alarmcode"
|
||||
},
|
||||
"auto_ventilation_action": {
|
||||
"name": "Actie automatische ventilatie"
|
||||
},
|
||||
"automatic_ventilation_state": {
|
||||
"name": "Status automatische ventilatie"
|
||||
},
|
||||
"burner_state": {
|
||||
"name": "Status brander {number}"
|
||||
},
|
||||
"clean_level": {
|
||||
"name": "Reinigingsniveau"
|
||||
},
|
||||
"connection_mode": {
|
||||
"name": "Verbindingsmodus",
|
||||
"state": {
|
||||
"observe": "Push (observeren)",
|
||||
"poll": "Polling"
|
||||
}
|
||||
},
|
||||
"cooktop_state": {
|
||||
"name": "Status kookplaat"
|
||||
},
|
||||
"current_limit_level": {
|
||||
"name": "Niveau stroombegrenzing"
|
||||
},
|
||||
"current_temp_c": {
|
||||
"name": "Temperatuur"
|
||||
},
|
||||
"diagnosis": {
|
||||
"name": "Diagnose"
|
||||
},
|
||||
"diagnosis_status": {
|
||||
"name": "Diagnosestatus"
|
||||
},
|
||||
"drum_clean_cycles_remaining": {
|
||||
"name": "Trommelreiniging over"
|
||||
},
|
||||
"drum_clean_last_cleaned": {
|
||||
"name": "Trommel laatst gereinigd"
|
||||
},
|
||||
"dry_level": {
|
||||
"name": "Droogniveau"
|
||||
},
|
||||
"dry_time": {
|
||||
"name": "Droogtijd"
|
||||
},
|
||||
"dryer_type": {
|
||||
"name": "Type droger"
|
||||
},
|
||||
"dust": {
|
||||
"name": "Stof"
|
||||
},
|
||||
"energy_kwh": {
|
||||
"name": "Energie"
|
||||
},
|
||||
"energy_last_month_kwh": {
|
||||
"name": "Energie (vorige maand)"
|
||||
},
|
||||
"energy_saved_kwh": {
|
||||
"name": "Bespaarde energie"
|
||||
},
|
||||
"energy_this_month_kwh": {
|
||||
"name": "Energie (deze maand)"
|
||||
},
|
||||
"fan_direction": {
|
||||
"name": "Ventilatorrichting"
|
||||
},
|
||||
"fan_speed_level": {
|
||||
"name": "Ventilatorniveau"
|
||||
},
|
||||
"filter_progress": {
|
||||
"name": "Filterverbruik"
|
||||
},
|
||||
"filter_usage": {
|
||||
"name": "Filtergebruik"
|
||||
},
|
||||
"fine_dust": {
|
||||
"name": "Fijnstof"
|
||||
},
|
||||
"finish_time": {
|
||||
"name": "Geschatte eindtijd"
|
||||
},
|
||||
"freezer_temperature": {
|
||||
"name": "Vriezertemperatuur"
|
||||
},
|
||||
"fridge_temperature": {
|
||||
"name": "Koelkasttemperatuur"
|
||||
},
|
||||
"hood_filter_capacity": {
|
||||
"name": "Filtercapaciteit"
|
||||
},
|
||||
"hood_filter_usage": {
|
||||
"name": "Filterverbruik"
|
||||
},
|
||||
"instance_temperature": {
|
||||
"name": "Temperatuur {instance_name}"
|
||||
},
|
||||
"job_beginning_status": {
|
||||
"name": "Status programmastart"
|
||||
},
|
||||
"last_air_sensing_level": {
|
||||
"name": "Laatste luchtmeetniveau"
|
||||
},
|
||||
"last_air_sensing_time": {
|
||||
"name": "Tijdstip laatste luchtmeting"
|
||||
},
|
||||
"main_timer_current": {
|
||||
"name": "Huidige timerwaarde"
|
||||
},
|
||||
"main_timer_state": {
|
||||
"name": "Timerstatus"
|
||||
},
|
||||
"odor": {
|
||||
"name": "Geur"
|
||||
},
|
||||
"operating_mode": {
|
||||
"name": "Werkingsmodus"
|
||||
},
|
||||
"operation_origin": {
|
||||
"name": "Bron laatste bediening"
|
||||
},
|
||||
"operation_time_minutes": {
|
||||
"name": "Werkingsduur"
|
||||
},
|
||||
"oven_state": {
|
||||
"name": "Status ovenruimte"
|
||||
},
|
||||
"paired_hood_fan_speed": {
|
||||
"name": "Ventilatorsnelheid gekoppelde afzuigkap"
|
||||
},
|
||||
"paired_hood_firmware": {
|
||||
"name": "Firmware gekoppelde afzuigkap"
|
||||
},
|
||||
"paired_hood_model": {
|
||||
"name": "Model gekoppelde afzuigkap"
|
||||
},
|
||||
"power_energy_kwh": {
|
||||
"name": "Energieverbruik"
|
||||
},
|
||||
"power_watts": {
|
||||
"name": "Vermogen"
|
||||
},
|
||||
"progress": {
|
||||
"name": "Voortgang"
|
||||
},
|
||||
"progress_percentage": {
|
||||
"name": "Voortgangspercentage"
|
||||
},
|
||||
"selfcheck_error": {
|
||||
"name": "Fout zelfcontrole"
|
||||
},
|
||||
"selfcheck_result": {
|
||||
"name": "Resultaat zelfcontrole"
|
||||
},
|
||||
"selfcheck_status": {
|
||||
"name": "Status zelfcontrole"
|
||||
},
|
||||
"super_fine_dust": {
|
||||
"name": "Ultrafijnstof"
|
||||
},
|
||||
"water_liters": {
|
||||
"name": "Waterverbruik"
|
||||
},
|
||||
"machine_state": {
|
||||
"name": "Machinestatus",
|
||||
"state": {
|
||||
"idle": "Inactief",
|
||||
"active": "Actief",
|
||||
"pause": "Gepauzeerd"
|
||||
}
|
||||
},
|
||||
"filter_status": {
|
||||
"name": "Filterstatus",
|
||||
"state": {
|
||||
"normal": "Normaal",
|
||||
"wash": "Reinigen",
|
||||
"replace": "Vervangen"
|
||||
}
|
||||
},
|
||||
"ice_making_status": {
|
||||
"name": "{instance_name} ijsmaakstatus",
|
||||
"state": {
|
||||
"icestatus_stop": "Inactief",
|
||||
"icestatus_run": "IJs maken"
|
||||
}
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"ai_energy_level": {
|
||||
"name": "AI Energy Mode"
|
||||
},
|
||||
"air_purify": {
|
||||
"name": "Luchtzuivering"
|
||||
},
|
||||
"auto_clean": {
|
||||
"name": "Automatisch reinigen"
|
||||
},
|
||||
"auto_door_opener": {
|
||||
"name": "Automatische deuropener"
|
||||
},
|
||||
"auto_release_dry": {
|
||||
"name": "Auto Release Dry"
|
||||
},
|
||||
"autofill": {
|
||||
"name": "Automatisch gevulde waterkan"
|
||||
},
|
||||
"bubble_soak": {
|
||||
"name": "Bubble Soak"
|
||||
},
|
||||
"cabinet_light_dim": {
|
||||
"name": "Geleidelijk feller"
|
||||
},
|
||||
"cabinet_light_switch": {
|
||||
"name": "Kastverlichting"
|
||||
},
|
||||
"child_lock": {
|
||||
"name": "Kinderslot"
|
||||
},
|
||||
"defrost_delay": {
|
||||
"name": "Ontdooien uitstellen"
|
||||
},
|
||||
"display_light": {
|
||||
"name": "Displayverlichting"
|
||||
},
|
||||
"fast_preheat": {
|
||||
"name": "Snel voorverwarmen"
|
||||
},
|
||||
"fridge_sound": {
|
||||
"name": "Geluid"
|
||||
},
|
||||
"ice_maker_enabled": {
|
||||
"name": "IJsmaker"
|
||||
},
|
||||
"ice_night_mode": {
|
||||
"name": "Stille nachtmodus ijsmaker"
|
||||
},
|
||||
"instance_enabled": {
|
||||
"name": "{instance_name} ingeschakeld"
|
||||
},
|
||||
"intensive": {
|
||||
"name": "Intensive"
|
||||
},
|
||||
"lamp": {
|
||||
"name": "Lamp"
|
||||
},
|
||||
"led_night_light": {
|
||||
"name": "Nachtverlichting deur-LED"
|
||||
},
|
||||
"mute_once": {
|
||||
"name": "Eenmalig dempen"
|
||||
},
|
||||
"natural_steam": {
|
||||
"name": "Natuurlijke stoom"
|
||||
},
|
||||
"power_switch": {
|
||||
"name": "Voeding"
|
||||
},
|
||||
"pre_wash": {
|
||||
"name": "Voorwas"
|
||||
},
|
||||
"rapid_freezing": {
|
||||
"name": "Snelvriezen"
|
||||
},
|
||||
"rapid_fridge": {
|
||||
"name": "Snelkoelen"
|
||||
},
|
||||
"sabbath_mode": {
|
||||
"name": "Sabbatmodus"
|
||||
},
|
||||
"sanitize": {
|
||||
"name": "Hygiëne"
|
||||
},
|
||||
"sound": {
|
||||
"name": "Geluid"
|
||||
},
|
||||
"storm_wash": {
|
||||
"name": "Storm Wash+"
|
||||
},
|
||||
"welcome_lighting": {
|
||||
"name": "Welkomstverlichting"
|
||||
},
|
||||
"wrinkle_prevent": {
|
||||
"name": "Kreukpreventie"
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
"led_night_end": {
|
||||
"name": "Einde nachtverlichting deur-LED"
|
||||
},
|
||||
"led_night_start": {
|
||||
"name": "Start nachtverlichting deur-LED"
|
||||
},
|
||||
"night_end": {
|
||||
"name": "Einde nachtverlichting"
|
||||
},
|
||||
"night_start": {
|
||||
"name": "Start nachtverlichting"
|
||||
}
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Samsung-apparaat toevoegen",
|
||||
"description": "Voer het IP-adres van je Samsung-apparaat en de AC14K_M-CA-inloggegevens in waarmee apparaatcertificaten worden aangemaakt.",
|
||||
"data": {
|
||||
"host": "IP-adres",
|
||||
"ca_cert_pem": "CA-certificaat (PEM)",
|
||||
"ca_key_pem": "Privésleutel van CA (PEM)"
|
||||
},
|
||||
"data_description": {
|
||||
"host": "Het lokale IP-adres van je Samsung-apparaat (bijvoorbeeld 192.168.1.50).",
|
||||
"ca_cert_pem": "De PEM-gecodeerde AC14K_M-CA-certificaatketen. Plak de volledige inhoud van je fullchain-PEM, inclusief alle blokken BEGIN CERTIFICATE en END CERTIFICATE.",
|
||||
"ca_key_pem": "De PEM-gecodeerde AC14K_M-privésleutel. Plak de volledige inhoud, inclusief de kop- en voettekst BEGIN PRIVATE KEY en END PRIVATE KEY."
|
||||
}
|
||||
},
|
||||
"user_reuse": {
|
||||
"title": "Samsung-apparaat toevoegen",
|
||||
"description": "Voer het IP-adres van je Samsung-apparaat in. De CA-inloggegevens van een bestaand LocalThings-apparaat worden hergebruikt.",
|
||||
"data": {
|
||||
"host": "IP-adres"
|
||||
},
|
||||
"data_description": {
|
||||
"host": "Het lokale IP-adres van je Samsung-apparaat (bijvoorbeeld 192.168.1.50)."
|
||||
}
|
||||
},
|
||||
"confirm_unknown_type": {
|
||||
"title": "Apparaattype niet herkend",
|
||||
"description": "Dit apparaat meldt oneUiVersion \"{one_ui_version}\", wat niet als apparaattype wordt herkend. Het apparaat wordt toch toegevoegd, maar alleen met algemene mogelijkheden (zoals voeding en alarmen, voor zover aanwezig), in plaats van alle mogelijkheden voor deze apparaatfamilie. Je kunt daarna helpen volledige ondersteuning toe te voegen door diagnostische gegevens voor dit apparaat te downloaden (Instellingen > Apparaten & diensten > dit apparaat > het menu > Diagnostische gegevens downloaden) en deze bij een nieuw issue te voegen. Kies Verzenden om het apparaat toch toe te voegen."
|
||||
},
|
||||
"confirm_unknown_type_no_version": {
|
||||
"title": "Apparaattype niet herkend",
|
||||
"description": "Dit apparaat meldt geen oneUiVersion en het apparaattype kon niet worden herkend. Het apparaat wordt toch toegevoegd, maar alleen met algemene mogelijkheden (zoals voeding en alarmen, voor zover aanwezig), in plaats van alle mogelijkheden voor deze apparaatfamilie. Je kunt daarna helpen volledige ondersteuning toe te voegen door diagnostische gegevens voor dit apparaat te downloaden (Instellingen > Apparaten & diensten > dit apparaat > het menu > Diagnostische gegevens downloaden) en deze bij een nieuw issue te voegen. Kies Verzenden om het apparaat toch toe te voegen."
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Kan geen verbinding maken met het apparaat. Controleer of het IP-adres bereikbaar is en de CA-inloggegevens juist zijn.",
|
||||
"invalid_ca": "Het CA-certificaat of de privésleutel kon niet worden geladen. Controleer of de PEM-inhoud juist is en of de sleutel bij het certificaat hoort.",
|
||||
"unknown": "Onverwachte fout. Raadpleeg de Home Assistant-logboeken voor meer informatie."
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "Apparaat is al geconfigureerd"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"title": "LocalThings-opties",
|
||||
"menu_options": {
|
||||
"settings": "Schrijfinstellingen voor afstandsbediening",
|
||||
"debug_write": "Foutopsporing: naar een resource schrijven"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "Schrijfinstellingen voor afstandsbediening",
|
||||
"description": "Sommige apparaten accepteren bepaalde schrijfbewerkingen (bijvoorbeeld de standaarddosering van wasmiddel of wasverzachter op een wasmachine), ook als ze melden dat de afstandsbediening is uitgeschakeld. LocalThings blokkeert standaard elke schrijfbewerking met een duidelijke foutmelding wanneer een apparaat meldt dat de afstandsbediening is uitgeschakeld, in plaats van het apparaat de opdracht stilzwijgend te laten weigeren. Schakel deze optie alleen in als je hebt bevestigd dat schrijfbewerkingen op dit apparaat echt werken wanneer de afstandsbediening is uitgeschakeld. Anders verruil je de duidelijke foutmelding voor een stille mislukking.",
|
||||
"data": {
|
||||
"bypass_remote_control_lock": "Schrijfbewerkingen toestaan wanneer afstandsbediening als uitgeschakeld wordt gemeld"
|
||||
}
|
||||
},
|
||||
"debug_write": {
|
||||
"title": "Foutopsporing: naar een resource schrijven",
|
||||
"description": "Geavanceerd hulpmiddel om apparaatspecifiek schrijfgedrag te onderzoeken. Kies de resource (href) waarnaar je wilt schrijven, of voer een aangepaste resource in die niet in de lijst staat. Hiermee wordt de blokkering bij een uitgeschakelde afstandsbediening omzeild en worden precies de opgegeven velden verzonden. Dit kan de configuratie van je apparaat verstoren, dus gebruik het bewust.",
|
||||
"data": {
|
||||
"href": "Href van resource"
|
||||
}
|
||||
},
|
||||
"debug_edit": {
|
||||
"title": "Schrijfbewerking voor foutopsporing: {href}",
|
||||
"description": "Huidige waarde van `{href}`:\n```\n{current_value}\n```\nVoer hieronder ALLEEN de velden in die je wilt wijzigen. Wat je invoert, wordt ongewijzigd naar het apparaat verzonden (als gedeeltelijke update). Het wordt NIET samengevoegd met de hierboven getoonde velden, dus plak niet de volledige waarde terug. Let op de typen: een numerieke tekenreeks zoals \"1\" moet tussen aanhalingstekens blijven staan. Een 1 zonder aanhalingstekens wordt als integer verzonden, wat sommige apparaten weigeren.",
|
||||
"data": {
|
||||
"payload": "Te schrijven payload"
|
||||
}
|
||||
},
|
||||
"debug_result": {
|
||||
"title": "Resultaat van schrijfbewerking voor foutopsporing",
|
||||
"description": "Het apparaat heeft CoAP-code {code} geretourneerd. Een code in de 2.xx-klasse betekent dat de schrijfbewerking is geaccepteerd; 4.xx/5.xx betekent dat deze is geweigerd. De resource heeft nu de volgende waarde:\n```\n{new_value}\n```\nAls de waarde ongewijzigd is, heeft het apparaat de schrijfbewerking waarschijnlijk genegeerd. Kies wat je hierna wilt doen:",
|
||||
"menu_options": {
|
||||
"debug_write": "Naar een andere resource schrijven",
|
||||
"finish": "Voltooien"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"empty_payload": "Voer ten minste één veld in om te schrijven.",
|
||||
"write_failed": "De schrijfbewerking is mislukt. Raadpleeg de Home Assistant-logboeken voor meer informatie."
|
||||
},
|
||||
"abort": {
|
||||
"not_loaded": "Dit apparaat is nog niet verbonden. Probeer het opnieuw zodra het is geladen."
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"device_gap": {
|
||||
"title": "Onvolledige ondersteuning van mogelijkheden voor {device_name}",
|
||||
"description": "Niet alle mogelijkheden van dit apparaat worden ondersteund. Het apparaattype is niet herkend of sommige beschikbare resources zijn nog niet gemodelleerd. Het apparaat blijft werken met de mogelijkheden die al worden ondersteund. Je kunt helpen de ondersteuning uit te breiden: ga naar Instellingen > Apparaten & diensten > {device_name} > het menu (rechtsboven) > Diagnostische gegevens downloaden en voeg het bestand daarna bij via de gekoppelde issue-template."
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"remote_control_disabled": {
|
||||
"message": "Afstandsbediening is uitgeschakeld op dit apparaat. Raadpleeg de handleiding van je apparaat om afstandsbediening in te schakelen voordat Home Assistant het kan bedienen."
|
||||
},
|
||||
"debug_payload_empty": {
|
||||
"message": "De payload voor de schrijfbewerking voor foutopsporing moet een niet-leeg object zijn."
|
||||
},
|
||||
"resource_href_required": {
|
||||
"message": "Een resource-href is vereist."
|
||||
},
|
||||
"bubble_soak_unavailable_for_cycle": {
|
||||
"message": "Bubble Soak is niet beschikbaar voor het geselecteerde programma."
|
||||
},
|
||||
"pre_wash_unavailable_for_cycle": {
|
||||
"message": "Voorwas is niet beschikbaar voor het geselecteerde programma."
|
||||
},
|
||||
"intensive_unavailable_for_cycle": {
|
||||
"message": "Intensief is niet beschikbaar voor het geselecteerde programma."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@ async def test_form_second_device_reuses_creds(hass: HomeAssistant) -> None:
|
||||
DOMAIN, context={'source': 'user'}
|
||||
)
|
||||
assert result['type'] == FlowResultType.FORM
|
||||
assert result['step_id'] == 'user_reuse'
|
||||
assert CONF_CA_CERT_PEM not in result['data_schema'].schema
|
||||
assert CONF_CA_KEY_PEM not in result['data_schema'].schema
|
||||
|
||||
@@ -219,6 +220,39 @@ async def test_unknown_type_shows_confirmation_step(
|
||||
assert result['data'][CONF_HOST] == MOCK_HOST
|
||||
|
||||
|
||||
async def test_unknown_type_without_version_uses_localized_step(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""No English placeholder sentinel leaks into a translated description."""
|
||||
probe_result = {
|
||||
'port': MOCK_PORT,
|
||||
'serial': MOCK_SERIAL,
|
||||
'leaf_cert_pem': 'leaf cert',
|
||||
'leaf_key_pem': 'leaf key',
|
||||
'one_ui_version': '',
|
||||
'device_type_recognized': False,
|
||||
}
|
||||
with patch(
|
||||
'custom_components.localthings.config_flow._probe_and_validate',
|
||||
return_value=probe_result,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={'source': 'user'}
|
||||
)
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result['flow_id'],
|
||||
{
|
||||
CONF_HOST: MOCK_HOST,
|
||||
CONF_CA_CERT_PEM: MOCK_CA_CERT_PEM,
|
||||
CONF_CA_KEY_PEM: MOCK_CA_KEY_PEM,
|
||||
},
|
||||
)
|
||||
|
||||
assert result['type'] == FlowResultType.FORM
|
||||
assert result['step_id'] == 'confirm_unknown_type_no_version'
|
||||
assert not result.get('description_placeholders')
|
||||
|
||||
|
||||
async def test_duplicate_device_aborted(hass: HomeAssistant, mock_probe) -> None:
|
||||
"""Second add of same serial: flow aborts.
|
||||
|
||||
|
||||
@@ -914,9 +914,12 @@ async def test_send_command_blocked_when_remote_control_disabled(
|
||||
|
||||
with patch.object(fake, 'subscribe'):
|
||||
fake.post = _post
|
||||
with pytest.raises(ServiceValidationError, match="Remote control is turned off"):
|
||||
with pytest.raises(ServiceValidationError) as exc_info:
|
||||
await coordinator.async_send_command(bound, 5)
|
||||
|
||||
assert exc_info.value.translation_domain == DOMAIN
|
||||
assert exc_info.value.translation_key == 'remote_control_disabled'
|
||||
|
||||
assert posted is False
|
||||
|
||||
|
||||
@@ -977,8 +980,9 @@ async def test_send_command_remote_control_check_precedes_validate_fn(
|
||||
desc = SwitchDesc(key='test', field='value', write_fn=_write_fn, validate_fn=_validate_fn)
|
||||
bound = BoundEntity(href='/test/vs/0', capability=coordinator.bound[0].capability, desc=desc)
|
||||
|
||||
with pytest.raises(ServiceValidationError, match="Remote control is turned off"):
|
||||
with pytest.raises(ServiceValidationError) as exc_info:
|
||||
await coordinator.async_send_command(bound, 'On')
|
||||
assert exc_info.value.translation_key == 'remote_control_disabled'
|
||||
|
||||
|
||||
async def test_send_command_bypasses_remote_control_when_option_enabled(
|
||||
|
||||
@@ -69,7 +69,7 @@ def test_course_bound_to_shared_course_vs_0():
|
||||
assert callable(desc.translation_key)
|
||||
table_03 = {'/st/dryercourse/vs/0': {'x.com.samsung.da.st.courseTable': 'Table_03'}}
|
||||
assert desc.translation_key(table_03) == 'dryer_cycle_table_03'
|
||||
assert desc.translation_key({}) is None
|
||||
assert desc.translation_key({}) == 'cycle'
|
||||
assert desc.options is laundry.cycle_options
|
||||
rep = {'x.com.samsung.da.options': ['Course_16', 'GMT_02']}
|
||||
assert desc.rep_fn(rep) == '16'
|
||||
|
||||
+18
-16
@@ -1,7 +1,4 @@
|
||||
"""Tests for LocalThingsEntity's display-name derivation
|
||||
(custom_components/localthings/entity.py) -- the explicit-name,
|
||||
device-given-instance-name, and href-derived fallbacks.
|
||||
"""
|
||||
"""Tests for translated entity naming and dynamic instance placeholders."""
|
||||
from custom_components.localthings.entity import LocalThingsEntity
|
||||
from custom_components.localthings.registry.capability import Capability
|
||||
from custom_components.localthings.registry.discovery import BoundEntity
|
||||
@@ -23,23 +20,28 @@ def _make_entity(desc, href='/x/vs/0', key_override=None, instance='', instance_
|
||||
return LocalThingsEntity(_FakeCoordinator(), bound)
|
||||
|
||||
|
||||
def test_explicit_name_wins_over_everything():
|
||||
desc = BinarySensorDesc(key='enabled', name='Explicit Name')
|
||||
def test_explicit_fallback_name_uses_translation_instead_of_attr_name():
|
||||
"""An _attr_name would take precedence over HA's translation catalog."""
|
||||
desc = BinarySensorDesc(key='enabled', name='Explicit name')
|
||||
entity = _make_entity(desc, instance_name='Cubed Ice')
|
||||
assert entity._attr_name == 'Explicit Name'
|
||||
assert entity.translation_key == 'enabled'
|
||||
assert not hasattr(entity, '_attr_name')
|
||||
|
||||
|
||||
def test_instance_name_prefixes_the_derived_suffix():
|
||||
"""Issue #27: an ice maker's device-given name ("Cubed Ice") replaces
|
||||
the href-derived instance label ("Icemaker One") as the name prefix,
|
||||
keeping the same entity-specific suffix."""
|
||||
desc = BinarySensorDesc(key='enabled')
|
||||
def test_device_instance_name_becomes_translation_placeholder():
|
||||
desc = BinarySensorDesc(
|
||||
key='enabled', translation_key='instance_enabled', use_instance_name=True
|
||||
)
|
||||
entity = _make_entity(desc, key_override='icemaker_one_enabled',
|
||||
instance_name='Cubed Ice')
|
||||
assert entity._attr_name == 'Cubed Ice Enabled'
|
||||
assert entity.translation_key == 'instance_enabled'
|
||||
assert entity.translation_placeholders == {'instance_name': 'Cubed Ice'}
|
||||
assert not hasattr(entity, '_attr_name')
|
||||
|
||||
|
||||
def test_no_instance_name_falls_back_to_derived_state_key():
|
||||
desc = BinarySensorDesc(key='enabled')
|
||||
def test_href_instance_name_becomes_translation_placeholder():
|
||||
desc = BinarySensorDesc(
|
||||
key='enabled', translation_key='instance_enabled', use_instance_name=True
|
||||
)
|
||||
entity = _make_entity(desc, key_override='icemaker_one_enabled')
|
||||
assert entity._attr_name == 'Icemaker One Enabled'
|
||||
assert entity.translation_placeholders == {'instance_name': 'Icemaker One'}
|
||||
|
||||
@@ -173,11 +173,9 @@ class TestCycleSelectTableGating:
|
||||
codes aren't guaranteed consistent across board generations sharing
|
||||
the same /course/vs/0 contract; FlexWash's older board reports a
|
||||
different course table than every device the shipped translations
|
||||
were confirmed against). The resolved key is built from whatever table
|
||||
the device actually reports -- a table with no strings.json entries
|
||||
yet just falls through Home Assistant's own missing-translation
|
||||
handling to raw-code display, the same as any individual untranslated
|
||||
code within an existing table."""
|
||||
were confirmed against). The resolved key is built from a reported
|
||||
table id. Devices without one use the generic translated Cycle entity
|
||||
name while preserving raw option codes as a safe fallback."""
|
||||
|
||||
def _desc(self):
|
||||
return laundry.cycle_select(
|
||||
@@ -198,20 +196,17 @@ class TestCycleSelectTableGating:
|
||||
assert callable(desc.translation_key)
|
||||
assert desc.translation_key(resources) == 'washer_cycle_table_02'
|
||||
|
||||
def test_resolved_key_reflects_an_unbuilt_table_too(self):
|
||||
"""No gating against a hardcoded 'known good' table -- a table we
|
||||
haven't shipped translations for yet still gets a key built for
|
||||
it, just one strings.json has nothing under (raw-code display)."""
|
||||
def test_untranslated_table_uses_generic_cycle_key(self):
|
||||
"""An unknown table does not claim another board's state labels."""
|
||||
desc = self._desc()
|
||||
resources = {'/st/washercourse/vs/0': {'x.com.samsung.da.st.courseTable': 'Table_00'}}
|
||||
assert desc.translation_key(resources) == 'washer_cycle_table_00'
|
||||
assert desc.translation_key(resources) == 'cycle'
|
||||
|
||||
def test_resolves_to_none_when_table_id_is_unknown(self):
|
||||
"""No href, or an empty rep, gets no translation_key at all --
|
||||
there's nothing to build a key from."""
|
||||
def test_resolves_to_generic_cycle_when_table_id_is_unknown(self):
|
||||
"""An absent table id still gets a translated generic entity name."""
|
||||
desc = self._desc()
|
||||
assert desc.translation_key({}) is None
|
||||
assert desc.translation_key({'/st/washercourse/vs/0': {}}) is None
|
||||
assert desc.translation_key({}) == 'cycle'
|
||||
assert desc.translation_key({'/st/washercourse/vs/0': {}}) == 'cycle'
|
||||
|
||||
|
||||
class TestBuzzerSound:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
from custom_components.localthings.select import _display
|
||||
|
||||
_UNTRANSLATED = None
|
||||
_TRANSLATED = 'door_alert'
|
||||
_TRANSLATED = 'ice_type'
|
||||
|
||||
|
||||
def test_display_titlecases_a_fully_lowercase_device_native_token():
|
||||
@@ -34,5 +34,14 @@ def test_display_lowercases_for_translation_key_lookup():
|
||||
assert _display('Whiskey_IceBall_3', _TRANSLATED) == 'whiskey_iceball_3'
|
||||
|
||||
|
||||
def test_unknown_translated_vendor_value_keeps_readable_fallback():
|
||||
"""A firmware-added value must remain readable instead of being mangled."""
|
||||
assert _display('FutureVendorMode', _TRANSLATED) == 'Future Vendor Mode'
|
||||
|
||||
|
||||
def test_known_camel_case_state_uses_snake_case_translation_key():
|
||||
assert _display('ExtraHigh', 'heated_dry') == 'extra_high'
|
||||
|
||||
|
||||
def test_display_passes_through_non_string_values():
|
||||
assert _display(None, _UNTRANSLATED) is None
|
||||
|
||||
@@ -49,7 +49,7 @@ def test_callable_options_receives_full_resource_snapshot():
|
||||
'/other/vs/0': {'codes': ['1C', '1D']},
|
||||
}
|
||||
entity = _make_select(desc, '/x/vs/0', resources)
|
||||
assert entity.options == ['1c', '1d']
|
||||
assert entity.options == ['1C', '1D']
|
||||
assert calls == [resources]
|
||||
|
||||
|
||||
@@ -74,3 +74,29 @@ def test_callable_translation_key_reresolves_live_not_once_at_construction():
|
||||
|
||||
resources['key'] = 'washer_cycle_table_02'
|
||||
assert entity.translation_key == 'washer_cycle_table_02'
|
||||
|
||||
|
||||
async def test_unknown_vendor_option_round_trips_to_exact_raw_value():
|
||||
"""Readable fallback labels must still write the exact Samsung token."""
|
||||
class _WritableCoordinator(_FakeCoordinator):
|
||||
data = {'mode': 'FutureVendorMode'}
|
||||
|
||||
def __init__(self, last_resources):
|
||||
super().__init__(last_resources)
|
||||
self.writes = []
|
||||
|
||||
async def async_send_command(self, bound, value):
|
||||
self.writes.append(value)
|
||||
|
||||
desc = SelectDesc(
|
||||
key='mode', name='Mode', translation_key='door_alert',
|
||||
options=('Known', 'FutureVendorMode'), write_fn=lambda *args: None,
|
||||
)
|
||||
capability = Capability(href='/x/vs/0', entities=(desc,))
|
||||
bound = BoundEntity(href='/x/vs/0', capability=capability, desc=desc)
|
||||
coordinator = _WritableCoordinator({})
|
||||
entity = LocalThingsSelect(coordinator, bound)
|
||||
|
||||
assert entity.options[-1] == 'Future Vendor Mode'
|
||||
await entity.async_select_option('Future Vendor Mode')
|
||||
assert coordinator.writes == ['FutureVendorMode']
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
"""Translation architecture and catalog synchronization tests."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
from string import Formatter
|
||||
|
||||
from custom_components.localthings.registry.capability import Capability
|
||||
from custom_components.localthings.registry.entities import PLATFORM_OF
|
||||
from custom_components.localthings.select import TRANSLATED_SELECT_STATES
|
||||
|
||||
|
||||
INTEGRATION = (
|
||||
Path(__file__).parents[1] / "custom_components" / "localthings"
|
||||
)
|
||||
|
||||
|
||||
def _load(name: str) -> dict:
|
||||
return json.loads((INTEGRATION / name).read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def _topology(value):
|
||||
if isinstance(value, dict):
|
||||
return {key: _topology(child) for key, child in value.items()}
|
||||
if isinstance(value, list):
|
||||
return [_topology(child) for child in value]
|
||||
return None
|
||||
|
||||
|
||||
def _placeholders(value: str) -> set[str]:
|
||||
return {
|
||||
field_name
|
||||
for _, field_name, _, _ in Formatter().parse(value)
|
||||
if field_name is not None
|
||||
}
|
||||
|
||||
|
||||
def _walk_strings(value, path=()):
|
||||
if isinstance(value, dict):
|
||||
for key, child in value.items():
|
||||
yield from _walk_strings(child, (*path, key))
|
||||
elif isinstance(value, list):
|
||||
for index, child in enumerate(value):
|
||||
yield from _walk_strings(child, (*path, str(index)))
|
||||
elif isinstance(value, str):
|
||||
yield path, value
|
||||
|
||||
|
||||
def _all_descriptions():
|
||||
capabilities_dir = INTEGRATION / "registry" / "capabilities"
|
||||
seen: set[int] = set()
|
||||
for module_path in capabilities_dir.glob("*.py"):
|
||||
if module_path.stem == "__init__":
|
||||
continue
|
||||
module = importlib.import_module(
|
||||
f"custom_components.localthings.registry.capabilities.{module_path.stem}"
|
||||
)
|
||||
|
||||
def visit(value):
|
||||
if isinstance(value, Capability):
|
||||
if id(value) in seen:
|
||||
return
|
||||
seen.add(id(value))
|
||||
yield from value.entities
|
||||
elif isinstance(value, (tuple, list, set)):
|
||||
for child in value:
|
||||
yield from visit(child)
|
||||
|
||||
for value in vars(module).values():
|
||||
yield from visit(value)
|
||||
|
||||
|
||||
def test_source_and_english_translation_topology_and_placeholders_match():
|
||||
source = _load("strings.json")
|
||||
english = _load("translations/en.json")
|
||||
assert _topology(source) == _topology(english)
|
||||
|
||||
english_strings = dict(_walk_strings(english))
|
||||
for path, value in _walk_strings(source):
|
||||
assert _placeholders(value) == _placeholders(english_strings[path]), path
|
||||
|
||||
|
||||
def test_runtime_translation_catalogs_have_no_unresolved_references():
|
||||
for language in ("en", "nl"):
|
||||
unresolved = [
|
||||
(path, value)
|
||||
for path, value in _walk_strings(
|
||||
_load(f"translations/{language}.json")
|
||||
)
|
||||
if "[%key:" in value
|
||||
]
|
||||
assert unresolved == []
|
||||
|
||||
|
||||
def test_english_and_dutch_catalog_topology_and_placeholders_match():
|
||||
english = _load("translations/en.json")
|
||||
dutch = _load("translations/nl.json")
|
||||
assert _topology(english) == _topology(dutch)
|
||||
|
||||
dutch_strings = dict(_walk_strings(dutch))
|
||||
for path, value in _walk_strings(english):
|
||||
assert _placeholders(value) == _placeholders(dutch_strings[path]), path
|
||||
|
||||
|
||||
def test_every_translatable_descriptor_has_an_entity_catalog_entry():
|
||||
entity_strings = _load("strings.json")["entity"]
|
||||
missing = []
|
||||
for desc in _all_descriptions():
|
||||
translation_key = desc.translation_key
|
||||
if callable(translation_key):
|
||||
# Runtime table resolvers use the generic name-only fallback plus
|
||||
# the static tables currently documented by this integration.
|
||||
continue
|
||||
if translation_key is None and desc.name is not None:
|
||||
translation_key = desc.key
|
||||
if translation_key is None:
|
||||
continue # Main fan entity: device name + HA fan translations.
|
||||
platform = PLATFORM_OF[type(desc)]
|
||||
if translation_key not in entity_strings.get(platform, {}):
|
||||
missing.append((platform, desc.key, translation_key))
|
||||
assert missing == []
|
||||
|
||||
select_strings = entity_strings["select"]
|
||||
for key in ("cycle", "washer_cycle_table_02", "dryer_cycle_table_03"):
|
||||
assert key in select_strings
|
||||
|
||||
|
||||
def test_select_state_normalization_is_synchronized_with_catalog():
|
||||
select_strings = _load("strings.json")["entity"]["select"]
|
||||
translated = {
|
||||
key: frozenset(value["state"])
|
||||
for key, value in select_strings.items()
|
||||
if "state" in value
|
||||
}
|
||||
assert TRANSLATED_SELECT_STATES == translated
|
||||
|
||||
|
||||
def test_all_entity_state_translation_keys_are_lowercase():
|
||||
entity_strings = _load("strings.json")["entity"]
|
||||
for platform in entity_strings.values():
|
||||
for translation in platform.values():
|
||||
for state_key in translation.get("state", {}):
|
||||
assert state_key == state_key.lower()
|
||||
@@ -64,16 +64,15 @@ class TestWasherCourse:
|
||||
"""Table-scoped (issue: course codes aren't guaranteed consistent
|
||||
across board generations sharing /course/vs/0 -- FlexWash's older
|
||||
board reports Table_00, not the Table_02 every washer_cycle_table_02
|
||||
name was confirmed against) -- see laundry.cycle_select. The key is
|
||||
built from whatever table the device reports, not gated against a
|
||||
hardcoded 'known good' value."""
|
||||
name was confirmed against) -- see laundry.cycle_select. Only a
|
||||
verified table gets table-specific state translations."""
|
||||
desc = next(e for e in washer.WASHER_COURSE.entities if e.key == 'cycle')
|
||||
assert callable(desc.translation_key)
|
||||
table_02 = {'/st/washercourse/vs/0': {'x.com.samsung.da.st.courseTable': 'Table_02'}}
|
||||
assert desc.translation_key(table_02) == 'washer_cycle_table_02'
|
||||
table_00 = {'/st/washercourse/vs/0': {'x.com.samsung.da.st.courseTable': 'Table_00'}}
|
||||
assert desc.translation_key(table_00) == 'washer_cycle_table_00'
|
||||
assert desc.translation_key({}) is None
|
||||
assert desc.translation_key(table_00) == 'cycle'
|
||||
assert desc.translation_key({}) == 'cycle'
|
||||
|
||||
def test_reads_raw_course_code_from_options_array(self):
|
||||
"""rep_fn returns the raw device code; display names come from
|
||||
@@ -206,14 +205,11 @@ class TestDetergentSoftenerDosing:
|
||||
assert self._desc('detergent_quantity').rep_fn(rep) == '7'
|
||||
|
||||
def test_translation_keys(self):
|
||||
"""detergent_quantity and softener_quantity share one translation_key
|
||||
(same 00-03 -> None/Low/Medium/High vocabulary on both dispensers,
|
||||
same shape as fridge.py's shared 'brightness_level' key); hardness
|
||||
and concentration each have their own since their labels differ."""
|
||||
assert self._desc('detergent_quantity').translation_key == 'washer_dosing_quantity'
|
||||
assert self._desc('softener_quantity').translation_key == 'washer_dosing_quantity'
|
||||
assert self._desc('detergent_water_hardness').translation_key == 'washer_detergent_water_hardness'
|
||||
assert self._desc('softener_concentration').translation_key == 'washer_softener_concentration'
|
||||
"""Each entity gets its own translated name and state vocabulary."""
|
||||
assert self._desc('detergent_quantity').translation_key == 'detergent_quantity'
|
||||
assert self._desc('softener_quantity').translation_key == 'softener_quantity'
|
||||
assert self._desc('detergent_water_hardness').translation_key == 'detergent_water_hardness'
|
||||
assert self._desc('softener_concentration').translation_key == 'softener_concentration'
|
||||
|
||||
def test_quantity_and_hardness_options_decode_supported_list(self):
|
||||
assert self._desc('detergent_quantity').options(_DOSING_RESOURCES) == ['00', '01', '02', '03']
|
||||
@@ -359,8 +355,10 @@ class TestWashOptionToggleValidation:
|
||||
|
||||
def test_rejected_on_an_unsupported_course(self):
|
||||
rep = {'x.com.samsung.da.options': ['Course_1C', _BUBBLE_SOAK_SET]}
|
||||
msg = self._desc('bubble_soak').validate_fn('On', rep, _EDIT_COURSE_RESOURCES)
|
||||
assert msg == "Bubble soak isn't available on the selected cycle."
|
||||
translation_key = self._desc('bubble_soak').validate_fn(
|
||||
'On', rep, _EDIT_COURSE_RESOURCES
|
||||
)
|
||||
assert translation_key == 'bubble_soak_unavailable_for_cycle'
|
||||
|
||||
def test_pre_wash_and_intensive_use_their_own_availableset_field(self):
|
||||
rep = {'x.com.samsung.da.options': ['Course_30', _PRE_WASH_AVAILABLE_SET]}
|
||||
|
||||
Reference in New Issue
Block a user