From b386063e794955bcf3db12bfa91b418a6a1ee441 Mon Sep 17 00:00:00 2001 From: Marc Billow Date: Thu, 9 Jul 2026 14:12:58 -0500 Subject: [PATCH] fix(fridge): translate enum select/sensor values in Python, not strings.json hassfest rejects translation keys containing uppercase letters, but the raw Samsung enum values for flex zone, beverage zone, ice type, and ice making status are uppercase (e.g. CV_TTYPE_RF9000A_FREEZE). Follow the existing dryer-course pattern instead: map raw device values to human-readable names in Python via a new SelectDesc.option_names field (mirrored read/write in select.py) and a value_fn for the sensor, and drop the now-unused translation_key entries from strings.json/en.json. --- .../registry/capabilities/fridge.py | 41 +++++++++++++++---- .../localthings/registry/entities.py | 1 + custom_components/localthings/select.py | 18 ++++++-- custom_components/localthings/strings.json | 30 -------------- .../localthings/translations/en.json | 30 -------------- 5 files changed, 49 insertions(+), 71 deletions(-) diff --git a/custom_components/localthings/registry/capabilities/fridge.py b/custom_components/localthings/registry/capabilities/fridge.py index 4de4582..fe79b0b 100644 --- a/custom_components/localthings/registry/capabilities/fridge.py +++ b/custom_components/localthings/registry/capabilities/fridge.py @@ -20,7 +20,32 @@ from ..entities import ( ) # Beverage zone flex modes. -_BZONE_MODES = ('SP_TTYPE_BEER_DRINKS', 'SP_TTYPE_WINE_DESSERT') +_BZONE_MODE_NAMES = { + 'SP_TTYPE_BEER_DRINKS': 'Beverage', + 'SP_TTYPE_WINE_DESSERT': 'Wine and Dessert', +} +_BZONE_MODES = tuple(_BZONE_MODE_NAMES) + +# Flex zone (convertible drawer) modes. +_FLEX_ZONE_MODE_NAMES = { + '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', +} + +# Icemaker ball-production rate options (whiskey icemaker models). +_ICE_TYPE_NAMES = { + 'WHISKEY_ICEBALL_3': '3 Balls/Day', + 'WHISKEY_ICEBALL_6': '6 Balls/Day', + 'WHISKEY_ICEBALL_9': '9 Balls/Day', +} + +_ICE_MAKING_STATUS_NAMES = { + 'ICESTATUS_STOP': 'Idle', + 'ICESTATUS_RUN': 'Making ice', +} def _int(v): @@ -102,17 +127,17 @@ ICEMAKER_GENERIC = Capability( entities=( SensorDesc(key='making_status', field='x.com.samsung.da.iceMaker.iceMakingStatus', - name=None, icon='mdi:cube-outline', - translation_key='ice_making_status'), + name='Making status', icon='mdi:cube-outline', + value_fn=lambda v: _ICE_MAKING_STATUS_NAMES.get(v, v)), SwitchDesc(key='enabled', field='x.com.samsung.da.iceMaker.state', name=None, 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', - translation_key='ice_type', + name='Ice type', icon='mdi:cube-outline', entity_category='config', options_field='x.com.samsung.da.iceType.supported', + option_names=_ICE_TYPE_NAMES, exists_fn=lambda rep: bool(rep.get('x.com.samsung.da.iceType.supported')), write_fn=_icemaker_write('x.com.samsung.da.iceType.desired')), ), @@ -468,9 +493,9 @@ BEVERAGE_ZONE = Capability( entities=( SelectDesc(key='beverage_zone_mode', field='roomDesiredMode', name='Beverage zone mode', icon='mdi:glass-wine', - translation_key='beverage_zone_mode', entity_category='config', - options=_BZONE_MODES, write_fn=_bzone_write), + options=_BZONE_MODES, option_names=_BZONE_MODE_NAMES, + write_fn=_bzone_write), ), ) @@ -496,9 +521,9 @@ FLEX_ZONE = Capability( SelectDesc(key='flex_zone_mode', field='x.com.samsung.da.modes', name='Flex zone mode', icon='mdi:thermostat', - translation_key='flex_zone_mode', entity_category='config', options_field='x.com.samsung.da.supportedOptions', + option_names=_FLEX_ZONE_MODE_NAMES, exists_fn=lambda rep: bool( rep.get('x.com.samsung.da.supportedOptions')), value_fn=lambda modes: next( diff --git a/custom_components/localthings/registry/entities.py b/custom_components/localthings/registry/entities.py index f606742..564c216 100644 --- a/custom_components/localthings/registry/entities.py +++ b/custom_components/localthings/registry/entities.py @@ -46,6 +46,7 @@ class BinarySensorDesc(SamsungEntityDescription): class SelectDesc(SamsungEntityDescription): options: Any = () # tuple[str,...] | Callable[[dict], list[str]] options_field: Optional[str] = None # resource field that contains the live options list + option_names: Optional[dict] = None # raw device value -> human-readable name write_fn: WriteFn = None diff --git a/custom_components/localthings/select.py b/custom_components/localthings/select.py index 8ffe47a..aa2829a 100644 --- a/custom_components/localthings/select.py +++ b/custom_components/localthings/select.py @@ -39,12 +39,24 @@ class LocalThingsSelect(LocalThingsEntity, SelectEntity): desc: SelectDesc = self._bound.desc if desc.options_field: rep = self.coordinator.last_resources.get(self._bound.href) or {} - return list(rep.get(desc.options_field) or []) - return self._attr_options + raw = list(rep.get(desc.options_field) or []) + else: + raw = self._attr_options + if desc.option_names: + return [desc.option_names.get(o, o) for o in raw] + return raw @property def current_option(self): - return (self.coordinator.data or {}).get(self._state_key) + desc: SelectDesc = self._bound.desc + raw = (self.coordinator.data or {}).get(self._state_key) + if desc.option_names: + return desc.option_names.get(raw, raw) + return raw async def async_select_option(self, option: str) -> None: + desc: SelectDesc = self._bound.desc + if desc.option_names: + reverse = {v: k for k, v in desc.option_names.items()} + option = reverse.get(option, option) await self.coordinator.async_send_command(self._bound, option) diff --git a/custom_components/localthings/strings.json b/custom_components/localthings/strings.json index 0eb9952..b09c50c 100644 --- a/custom_components/localthings/strings.json +++ b/custom_components/localthings/strings.json @@ -1,13 +1,5 @@ { "entity": { - "sensor": { - "ice_making_status": { - "state": { - "ICESTATUS_STOP": "Idle", - "ICESTATUS_RUN": "Making ice" - } - } - }, "select": { "door_alert": { "state": { @@ -23,28 +15,6 @@ "66": "Medium", "100": "High" } - }, - "ice_type": { - "state": { - "WHISKEY_ICEBALL_3": "3 Balls/Day", - "WHISKEY_ICEBALL_6": "6 Balls/Day", - "WHISKEY_ICEBALL_9": "9 Balls/Day" - } - }, - "beverage_zone_mode": { - "state": { - "SP_TTYPE_BEER_DRINKS": "Beverage", - "SP_TTYPE_WINE_DESSERT": "Wine and Dessert" - } - }, - "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" - } } } }, diff --git a/custom_components/localthings/translations/en.json b/custom_components/localthings/translations/en.json index 0eb9952..b09c50c 100644 --- a/custom_components/localthings/translations/en.json +++ b/custom_components/localthings/translations/en.json @@ -1,13 +1,5 @@ { "entity": { - "sensor": { - "ice_making_status": { - "state": { - "ICESTATUS_STOP": "Idle", - "ICESTATUS_RUN": "Making ice" - } - } - }, "select": { "door_alert": { "state": { @@ -23,28 +15,6 @@ "66": "Medium", "100": "High" } - }, - "ice_type": { - "state": { - "WHISKEY_ICEBALL_3": "3 Balls/Day", - "WHISKEY_ICEBALL_6": "6 Balls/Day", - "WHISKEY_ICEBALL_9": "9 Balls/Day" - } - }, - "beverage_zone_mode": { - "state": { - "SP_TTYPE_BEER_DRINKS": "Beverage", - "SP_TTYPE_WINE_DESSERT": "Wine and Dessert" - } - }, - "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" - } } } },