laundry: guided setup left download cycles unselectable, and cleared the course
A program is only offerable once it has both a name and the Download course code that goes in the Course_ token. Guided setup collected names and never asked about the course, so a user could walk all nine programs, watch every name save, and end up with nothing in the cycle list. Worse, it actively cleared the course. _apply_cloud_course_names read download_course out of the submitted form; the guided name form has no such field, so it passed None and apply_cloud_courses stored that. Naming a program therefore removed every previously-named program from the list. Traced on the fixture: 87 -> name one -> None -> nothing offerable. Two changes. apply_cloud_courses now defaults download_course to "leave it alone" rather than None, so silence can't be mistaken for a clear, and the guided path forwards the field only when its form actually carried it. And the first guided name form now asks for the course, prefilled from what was just observed, dropping the field once confirmed. Asking there rather than up front is deliberate: it is the first moment there is evidence to prefill, since the user has just loaded a program and the course showing alongside it is the Download one. That makes the walk stand on its own, which is the whole point of offering it as the primary path. The bulk form's course dropdown now shares the guided one's builder. Translations for the guided-setup strings are in for cs/de/es/it/ko/nl, matching the vocabulary the earlier pass established. The new field on the name form reuses each locale's existing label from the bulk form rather than adding an untranslated string.
This commit is contained in:
10 files changed
+219
-103
No files matched your search
@@ -1051,8 +1051,13 @@ class LocalThingsOptionsFlow(config_entries.OptionsFlow):
|
||||
existing = (coord.cloud_courses.snapshot()["slots"].get(slot) or {}).get("name", "")
|
||||
|
||||
if user_input is not None:
|
||||
name = str(user_input.get("name", "")).strip()
|
||||
errors = self._apply_cloud_course_names(coord, [slot], {f"name_{slot}": name})
|
||||
# Rebuilt into the shared validator's shape. `download_course`
|
||||
# is forwarded only when this form actually carried it, so its
|
||||
# absence still means "not asked about" rather than "clear it".
|
||||
payload: dict[str, Any] = {f"name_{slot}": str(user_input.get("name", "")).strip()}
|
||||
if "download_course" in user_input:
|
||||
payload["download_course"] = user_input["download_course"]
|
||||
errors = self._apply_cloud_course_names(coord, [slot], payload)
|
||||
if errors:
|
||||
return self._cloud_name_form(coord, slot, existing, errors=errors)
|
||||
# Straight back to waiting: the appliance is still sitting on this
|
||||
@@ -1069,20 +1074,60 @@ class LocalThingsOptionsFlow(config_entries.OptionsFlow):
|
||||
"""One text field, with the copy switched on whether this program is
|
||||
already set up. Re-selecting one is not an error -- it is how someone
|
||||
checks their work -- so it gets an edit form rather than a rejection,
|
||||
and the counter deliberately does not move."""
|
||||
and the counter deliberately does not move.
|
||||
|
||||
The Download course joins the form the first time round, and only
|
||||
until it is confirmed. Guided setup would otherwise finish having
|
||||
collected names but no course, and a program needs both before it can
|
||||
be offered -- so the whole walk would produce nothing selectable. It
|
||||
is asked here rather than up front because this is the first moment
|
||||
there is evidence to prefill: the user has just loaded a program, so
|
||||
the course showing alongside it is the Download one.
|
||||
"""
|
||||
placeholders = self._cloud_progress_placeholders(coord)
|
||||
placeholders["slot"] = slot
|
||||
placeholders["remaining"] = (
|
||||
coord.resource("/operational/state/vs/0").get("x.com.samsung.da.remainingTime") or "--"
|
||||
)
|
||||
fields: dict[Any, Any] = {vol.Optional("name", default=existing): _TEXT}
|
||||
if not coord.cloud_courses.snapshot()["download_course"]:
|
||||
fields[
|
||||
vol.Optional("download_course", description={"suggested_value": self._cloud_course})
|
||||
] = self._cloud_course_selector(coord)
|
||||
return self.async_show_form(
|
||||
step_id="cloud_name",
|
||||
data_schema=vol.Schema({vol.Optional("name", default=existing): _TEXT}),
|
||||
data_schema=vol.Schema(fields),
|
||||
errors=errors or {},
|
||||
description_placeholders=placeholders,
|
||||
last_step=False,
|
||||
)
|
||||
|
||||
@property
|
||||
def _cloud_course(self) -> str | None:
|
||||
coord = self._coordinator()
|
||||
if coord is None:
|
||||
return None
|
||||
candidates = coord.cloud_courses.download_candidates()
|
||||
return candidates[0] if candidates else None
|
||||
|
||||
def _cloud_course_selector(self, coord):
|
||||
"""The appliance's own course codes, observed candidates first.
|
||||
|
||||
custom_value stays off deliberately: whatever lands here becomes the
|
||||
Course_ token of a real write, and a typed-in code the appliance
|
||||
doesn't offer would start something nobody chose.
|
||||
"""
|
||||
available = cycle_options(coord.canonical_resources(MAIN))
|
||||
candidates = [c for c in coord.cloud_courses.download_candidates() if c in available]
|
||||
ordered = candidates + [c for c in available if c not in candidates]
|
||||
return SelectSelector(
|
||||
SelectSelectorConfig(
|
||||
options=ordered,
|
||||
custom_value=False,
|
||||
mode=SelectSelectorMode.DROPDOWN,
|
||||
)
|
||||
)
|
||||
|
||||
async def async_step_cloud_timeout(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
@@ -1167,6 +1212,15 @@ class LocalThingsOptionsFlow(config_entries.OptionsFlow):
|
||||
return {"base": "cloud_course_name_duplicate"}
|
||||
taken.add(name.casefold())
|
||||
|
||||
# Absent means "this form didn't ask" -- the guided name form drops
|
||||
# the field once the course is confirmed -- which must leave the
|
||||
# stored value alone rather than clearing it. A program is only
|
||||
# offerable when both a name and the course are set, so clearing it
|
||||
# here would make naming things remove them from the cycle list.
|
||||
if "download_course" not in user_input:
|
||||
coord.apply_cloud_courses(names)
|
||||
return {}
|
||||
|
||||
# Belt and braces over the selector's own custom_value=False: this
|
||||
# value becomes the Course_ token of a real write, so it is checked
|
||||
# against the appliance's own course list here too, where the store
|
||||
@@ -1203,24 +1257,9 @@ class LocalThingsOptionsFlow(config_entries.OptionsFlow):
|
||||
_TEXT
|
||||
)
|
||||
|
||||
# Course codes this device actually offers, so the Download course
|
||||
# can only ever be set to one of them. Auto-detected candidates come
|
||||
# first -- see CloudCourses.download_candidates. custom_value stays
|
||||
# off deliberately: whatever lands here becomes the Course_ token of a
|
||||
# real write, and a typed-in code the appliance doesn't offer would
|
||||
# start something nobody chose.
|
||||
available = cycle_options(coord.canonical_resources(MAIN))
|
||||
candidates = [c for c in store.download_candidates() if c in available]
|
||||
ordered = candidates + [c for c in available if c not in candidates]
|
||||
suggested = record["download_course"] or (candidates[0] if candidates else None)
|
||||
suggested = record["download_course"] or self._cloud_course
|
||||
fields[vol.Optional("download_course", description={"suggested_value": suggested})] = (
|
||||
SelectSelector(
|
||||
SelectSelectorConfig(
|
||||
options=ordered,
|
||||
custom_value=False,
|
||||
mode=SelectSelectorMode.DROPDOWN,
|
||||
)
|
||||
)
|
||||
self._cloud_course_selector(coord)
|
||||
)
|
||||
|
||||
pending = [s for s in advertised if s not in slots]
|
||||
|
||||
@@ -10,7 +10,7 @@ import threading
|
||||
import time
|
||||
import zlib
|
||||
from datetime import timedelta
|
||||
from typing import Any
|
||||
from typing import Any, cast
|
||||
|
||||
import cbor2
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
@@ -75,6 +75,10 @@ from .registry.subdevices import (
|
||||
normalize_seed_batch,
|
||||
)
|
||||
|
||||
# Sentinel for apply_cloud_courses: "leave this field as it is",
|
||||
# distinct from None which means "clear it".
|
||||
_KEEP = object()
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
_SEED_PATH = ["device", "0"]
|
||||
@@ -536,17 +540,24 @@ class LocalThingsCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
self._observe.apply(cloudcourse.COURSE_HREF, rep, source="poll")
|
||||
return cloudcourse.loaded_slot(rep)
|
||||
|
||||
def apply_cloud_courses(self, names: dict[str, str], download_course: str | None) -> None:
|
||||
def apply_cloud_courses(self, names: dict[str, str], download_course: object = _KEEP) -> None:
|
||||
"""The one mutation path for the cloud-program store (issue #342).
|
||||
|
||||
Takes the whole submission at once so a nine-program naming pass is
|
||||
one config-entry write rather than nine, and so persistence, the
|
||||
canonical-view invalidation and the Repairs refresh can't be done for
|
||||
one half of a change and skipped for the other.
|
||||
|
||||
`download_course` defaults to "leave it alone" rather than None.
|
||||
Guided setup submits one name at a time and says nothing about the
|
||||
course; with None as the default that silently cleared a course the
|
||||
user had already confirmed, and since a program is only offerable
|
||||
once both are set, naming things made them disappear.
|
||||
"""
|
||||
for slot, name in names.items():
|
||||
self._cloud.set_name(slot, name)
|
||||
self._cloud.set_download_course(download_course)
|
||||
if download_course is not _KEEP:
|
||||
self._cloud.set_download_course(cast("str | None", download_course))
|
||||
self._persist_cloud_courses()
|
||||
|
||||
@callback
|
||||
|
||||
@@ -1454,29 +1454,30 @@
|
||||
}
|
||||
},
|
||||
"cloud_courses": {
|
||||
"title": "Download cycles",
|
||||
"description": "Guided setup walks the appliance with you: select a downloaded cycle on the appliance and name it as it's found, one at a time.\n\nEdit names shows everything found so far at once — use it to rename or correct something later.",
|
||||
"title": "Stažené cykly",
|
||||
"description": "Průvodce nastavením vás provede zařízením: na zařízení vyberte stažený cyklus a jakmile je nalezen, pojmenujte ho – jeden po druhém.\n\nÚprava názvů zobrazí vše dosud nalezené najednou – použijte ji později k přejmenování nebo opravě.",
|
||||
"menu_options": {
|
||||
"cloud_guided": "Guided setup",
|
||||
"cloud_manual": "Edit names"
|
||||
"cloud_guided": "Průvodce nastavením",
|
||||
"cloud_manual": "Upravit názvy"
|
||||
}
|
||||
},
|
||||
"cloud_wait": {
|
||||
"title": "Download cycles"
|
||||
"title": "Stažené cykly"
|
||||
},
|
||||
"cloud_name": {
|
||||
"title": "Name this cycle",
|
||||
"description": "The appliance switched to a downloaded cycle (slot {slot}) and reports {remaining} remaining.\n\nGive it the name you want to see in Home Assistant, then select the next one on the appliance. Leave it blank to keep this cycle out of the list.\n\nNamed so far ({named} of {total}): {named_list}\n\nNames must be unique. You can close this dialog whenever you like — names are saved as you go.",
|
||||
"title": "Pojmenujte tento cyklus",
|
||||
"description": "Zařízení přepnulo na stažený cyklus (pozice {slot}) a hlásí zbývající čas {remaining}.\n\nZadejte název, který chcete vidět v Home Assistant, a poté na zařízení vyberte další cyklus. Necháte-li pole prázdné, tento cyklus zůstane mimo seznam.\n\nDosud pojmenováno ({named} z {total}): {named_list}\n\nNázvy musí být jedinečné. Toto okno můžete kdykoli zavřít – názvy se ukládají průběžně.",
|
||||
"data": {
|
||||
"name": "Name"
|
||||
"name": "Název",
|
||||
"download_course": "Kód programu „Stažený program“"
|
||||
}
|
||||
},
|
||||
"cloud_timeout": {
|
||||
"title": "No cycle selected",
|
||||
"description": "Nothing was selected on the appliance. Make sure it's set to the Download cycle, then step through the downloaded programs.\n\nNamed so far ({named} of {total}): {named_list}\n\nEverything named so far is already saved.",
|
||||
"title": "Nebyl vybrán žádný cyklus",
|
||||
"description": "Na zařízení nebylo nic vybráno. Ujistěte se, že je nastaveno na Stažený program, a poté postupně procházejte stažené programy.\n\nDosud pojmenováno ({named} z {total}): {named_list}\n\nVše dosud pojmenované je již uloženo.",
|
||||
"menu_options": {
|
||||
"cloud_guided": "Wait again",
|
||||
"cloud_finish": "Finish"
|
||||
"cloud_guided": "Počkat znovu",
|
||||
"cloud_finish": "Dokončit"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1490,7 +1491,7 @@
|
||||
"not_loaded": "Toto zařízení ještě není připojeno. Zkuste to znovu, až se načte."
|
||||
},
|
||||
"progress": {
|
||||
"cloud_wait": "Select a downloaded cycle on the appliance now.\n\nNamed so far ({named} of {total}): {named_list}\n\nYou can close this dialog at any time — names are saved as you go."
|
||||
"cloud_wait": "Nyní na zařízení vyberte stažený cyklus.\n\nDosud pojmenováno ({named} z {total}): {named_list}\n\nToto okno můžete kdykoli zavřít – názvy se ukládají průběžně."
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
|
||||
@@ -1454,29 +1454,30 @@
|
||||
}
|
||||
},
|
||||
"cloud_courses": {
|
||||
"title": "Download cycles",
|
||||
"description": "Guided setup walks the appliance with you: select a downloaded cycle on the appliance and name it as it's found, one at a time.\n\nEdit names shows everything found so far at once — use it to rename or correct something later.",
|
||||
"title": "Download-Programme",
|
||||
"description": "Die geführte Einrichtung begleitet Sie am Gerät: Wählen Sie am Gerät ein heruntergeladenes Programm aus und benennen Sie es, sobald es gefunden wird – eines nach dem anderen.\n\nNamen bearbeiten zeigt alles bisher Gefundene auf einmal an – nutzen Sie es später, um etwas umzubenennen oder zu korrigieren.",
|
||||
"menu_options": {
|
||||
"cloud_guided": "Guided setup",
|
||||
"cloud_manual": "Edit names"
|
||||
"cloud_guided": "Geführte Einrichtung",
|
||||
"cloud_manual": "Namen bearbeiten"
|
||||
}
|
||||
},
|
||||
"cloud_wait": {
|
||||
"title": "Download cycles"
|
||||
"title": "Download-Programme"
|
||||
},
|
||||
"cloud_name": {
|
||||
"title": "Name this cycle",
|
||||
"description": "The appliance switched to a downloaded cycle (slot {slot}) and reports {remaining} remaining.\n\nGive it the name you want to see in Home Assistant, then select the next one on the appliance. Leave it blank to keep this cycle out of the list.\n\nNamed so far ({named} of {total}): {named_list}\n\nNames must be unique. You can close this dialog whenever you like — names are saved as you go.",
|
||||
"title": "Dieses Programm benennen",
|
||||
"description": "Das Gerät ist zu einem heruntergeladenen Programm gewechselt (Platz {slot}) und meldet eine verbleibende Zeit von {remaining}.\n\nGeben Sie den Namen ein, den Sie in Home Assistant sehen möchten, und wählen Sie dann am Gerät das nächste aus. Lassen Sie das Feld leer, um dieses Programm von der Liste auszuschließen.\n\nBisher benannt ({named} von {total}): {named_list}\n\nNamen müssen eindeutig sein. Sie können dieses Fenster jederzeit schließen – die Namen werden fortlaufend gespeichert.",
|
||||
"data": {
|
||||
"name": "Name"
|
||||
"name": "Name",
|
||||
"download_course": "Programmcode des Download-Programms"
|
||||
}
|
||||
},
|
||||
"cloud_timeout": {
|
||||
"title": "No cycle selected",
|
||||
"description": "Nothing was selected on the appliance. Make sure it's set to the Download cycle, then step through the downloaded programs.\n\nNamed so far ({named} of {total}): {named_list}\n\nEverything named so far is already saved.",
|
||||
"title": "Kein Programm ausgewählt",
|
||||
"description": "Am Gerät wurde nichts ausgewählt. Stellen Sie sicher, dass es auf das Download-Programm eingestellt ist, und gehen Sie dann die heruntergeladenen Programme durch.\n\nBisher benannt ({named} von {total}): {named_list}\n\nAlles bisher Benannte ist bereits gespeichert.",
|
||||
"menu_options": {
|
||||
"cloud_guided": "Wait again",
|
||||
"cloud_finish": "Finish"
|
||||
"cloud_guided": "Erneut warten",
|
||||
"cloud_finish": "Fertig"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1490,7 +1491,7 @@
|
||||
"not_loaded": "Dieses Gerät ist noch nicht verbunden. Versuchen Sie es erneut, sobald es geladen wurde."
|
||||
},
|
||||
"progress": {
|
||||
"cloud_wait": "Select a downloaded cycle on the appliance now.\n\nNamed so far ({named} of {total}): {named_list}\n\nYou can close this dialog at any time — names are saved as you go."
|
||||
"cloud_wait": "Wählen Sie jetzt ein heruntergeladenes Programm am Gerät aus.\n\nBisher benannt ({named} von {total}): {named_list}\n\nSie können dieses Fenster jederzeit schließen – die Namen werden fortlaufend gespeichert."
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
|
||||
@@ -1468,7 +1468,8 @@
|
||||
"title": "Name this cycle",
|
||||
"description": "The appliance switched to a downloaded cycle (slot {slot}) and reports {remaining} remaining.\n\nGive it the name you want to see in Home Assistant, then select the next one on the appliance. Leave it blank to keep this cycle out of the list.\n\nNamed so far ({named} of {total}): {named_list}\n\nNames must be unique. You can close this dialog whenever you like — names are saved as you go.",
|
||||
"data": {
|
||||
"name": "Name"
|
||||
"name": "Name",
|
||||
"download_course": "Download cycle course code"
|
||||
}
|
||||
},
|
||||
"cloud_timeout": {
|
||||
|
||||
@@ -101,29 +101,30 @@
|
||||
}
|
||||
},
|
||||
"cloud_courses": {
|
||||
"title": "Download cycles",
|
||||
"description": "Guided setup walks the appliance with you: select a downloaded cycle on the appliance and name it as it's found, one at a time.\n\nEdit names shows everything found so far at once — use it to rename or correct something later.",
|
||||
"title": "Ciclos descargados",
|
||||
"description": "La configuración guiada te acompaña con el dispositivo: selecciona un ciclo descargado en el dispositivo y ponle nombre en cuanto se detecte, uno por uno.\n\nEditar nombres muestra todo lo encontrado hasta ahora de una vez — úsalo más adelante para renombrar o corregir algo.",
|
||||
"menu_options": {
|
||||
"cloud_guided": "Guided setup",
|
||||
"cloud_manual": "Edit names"
|
||||
"cloud_guided": "Configuración guiada",
|
||||
"cloud_manual": "Editar nombres"
|
||||
}
|
||||
},
|
||||
"cloud_wait": {
|
||||
"title": "Download cycles"
|
||||
"title": "Ciclos descargados"
|
||||
},
|
||||
"cloud_name": {
|
||||
"title": "Name this cycle",
|
||||
"description": "The appliance switched to a downloaded cycle (slot {slot}) and reports {remaining} remaining.\n\nGive it the name you want to see in Home Assistant, then select the next one on the appliance. Leave it blank to keep this cycle out of the list.\n\nNamed so far ({named} of {total}): {named_list}\n\nNames must be unique. You can close this dialog whenever you like — names are saved as you go.",
|
||||
"title": "Nombra este ciclo",
|
||||
"description": "El dispositivo cambió a un ciclo descargado (ranura {slot}) e informa de {remaining} restantes.\n\nDale el nombre que quieras ver en Home Assistant y luego selecciona el siguiente en el dispositivo. Déjalo en blanco para mantener este ciclo fuera de la lista.\n\nNombrados hasta ahora ({named} de {total}): {named_list}\n\nLos nombres deben ser únicos. Puedes cerrar este cuadro de diálogo cuando quieras — los nombres se guardan sobre la marcha.",
|
||||
"data": {
|
||||
"name": "Name"
|
||||
"name": "Nombre",
|
||||
"download_course": "Código de programa de Descarga de Programas"
|
||||
}
|
||||
},
|
||||
"cloud_timeout": {
|
||||
"title": "No cycle selected",
|
||||
"description": "Nothing was selected on the appliance. Make sure it's set to the Download cycle, then step through the downloaded programs.\n\nNamed so far ({named} of {total}): {named_list}\n\nEverything named so far is already saved.",
|
||||
"title": "No se seleccionó ningún ciclo",
|
||||
"description": "No se seleccionó nada en el dispositivo. Asegúrate de que esté puesto en Descarga de Programas y luego ve pasando por los programas descargados.\n\nNombrados hasta ahora ({named} de {total}): {named_list}\n\nTodo lo nombrado hasta ahora ya está guardado.",
|
||||
"menu_options": {
|
||||
"cloud_guided": "Wait again",
|
||||
"cloud_finish": "Finish"
|
||||
"cloud_guided": "Esperar de nuevo",
|
||||
"cloud_finish": "Finalizar"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -137,7 +138,7 @@
|
||||
"not_loaded": "Este dispositivo aún no está conectado. Inténtalo de nuevo cuando se haya cargado."
|
||||
},
|
||||
"progress": {
|
||||
"cloud_wait": "Select a downloaded cycle on the appliance now.\n\nNamed so far ({named} of {total}): {named_list}\n\nYou can close this dialog at any time — names are saved as you go."
|
||||
"cloud_wait": "Selecciona ahora un ciclo descargado en el dispositivo.\n\nNombrados hasta ahora ({named} de {total}): {named_list}\n\nPuedes cerrar este cuadro de diálogo en cualquier momento — los nombres se guardan sobre la marcha."
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
|
||||
@@ -1454,29 +1454,30 @@
|
||||
}
|
||||
},
|
||||
"cloud_courses": {
|
||||
"title": "Download cycles",
|
||||
"description": "Guided setup walks the appliance with you: select a downloaded cycle on the appliance and name it as it's found, one at a time.\n\nEdit names shows everything found so far at once — use it to rename or correct something later.",
|
||||
"title": "Cicli scaricati",
|
||||
"description": "La configurazione guidata accompagna l'utente al dispositivo: selezionare un ciclo scaricato sul dispositivo e assegnargli un nome non appena viene rilevato, uno alla volta.\n\nModifica nomi mostra tutto ciò che è stato trovato finora in una sola volta — usarla in seguito per rinominare o correggere qualcosa.",
|
||||
"menu_options": {
|
||||
"cloud_guided": "Guided setup",
|
||||
"cloud_manual": "Edit names"
|
||||
"cloud_guided": "Configurazione guidata",
|
||||
"cloud_manual": "Modifica nomi"
|
||||
}
|
||||
},
|
||||
"cloud_wait": {
|
||||
"title": "Download cycles"
|
||||
"title": "Cicli scaricati"
|
||||
},
|
||||
"cloud_name": {
|
||||
"title": "Name this cycle",
|
||||
"description": "The appliance switched to a downloaded cycle (slot {slot}) and reports {remaining} remaining.\n\nGive it the name you want to see in Home Assistant, then select the next one on the appliance. Leave it blank to keep this cycle out of the list.\n\nNamed so far ({named} of {total}): {named_list}\n\nNames must be unique. You can close this dialog whenever you like — names are saved as you go.",
|
||||
"title": "Assegna un nome a questo ciclo",
|
||||
"description": "Il dispositivo è passato a un ciclo scaricato (slot {slot}) e segnala {remaining} rimanenti.\n\nAssegnargli il nome che si desidera vedere in Home Assistant, quindi selezionare il successivo sul dispositivo. Lasciare vuoto per escludere questo ciclo dall'elenco.\n\nAssegnati finora ({named} di {total}): {named_list}\n\nI nomi devono essere univoci. È possibile chiudere questa finestra in qualsiasi momento — i nomi vengono salvati man mano.",
|
||||
"data": {
|
||||
"name": "Name"
|
||||
"name": "Nome",
|
||||
"download_course": "Codice programma del ciclo Scaricato"
|
||||
}
|
||||
},
|
||||
"cloud_timeout": {
|
||||
"title": "No cycle selected",
|
||||
"description": "Nothing was selected on the appliance. Make sure it's set to the Download cycle, then step through the downloaded programs.\n\nNamed so far ({named} of {total}): {named_list}\n\nEverything named so far is already saved.",
|
||||
"title": "Nessun ciclo selezionato",
|
||||
"description": "Non è stato selezionato nulla sul dispositivo. Assicurarsi che sia impostato sul ciclo Scaricato, quindi scorrere i programmi scaricati.\n\nAssegnati finora ({named} di {total}): {named_list}\n\nTutto ciò che è stato assegnato finora è già salvato.",
|
||||
"menu_options": {
|
||||
"cloud_guided": "Wait again",
|
||||
"cloud_finish": "Finish"
|
||||
"cloud_guided": "Attendi di nuovo",
|
||||
"cloud_finish": "Fine"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1490,7 +1491,7 @@
|
||||
"not_loaded": "Questo dispositivo non è ancora connesso. Riprova dopo il caricamento."
|
||||
},
|
||||
"progress": {
|
||||
"cloud_wait": "Select a downloaded cycle on the appliance now.\n\nNamed so far ({named} of {total}): {named_list}\n\nYou can close this dialog at any time — names are saved as you go."
|
||||
"cloud_wait": "Selezionare ora un ciclo scaricato sul dispositivo.\n\nAssegnati finora ({named} di {total}): {named_list}\n\nÈ possibile chiudere questa finestra in qualsiasi momento — i nomi vengono salvati man mano."
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
|
||||
@@ -1454,29 +1454,30 @@
|
||||
}
|
||||
},
|
||||
"cloud_courses": {
|
||||
"title": "Download cycles",
|
||||
"description": "Guided setup walks the appliance with you: select a downloaded cycle on the appliance and name it as it's found, one at a time.\n\nEdit names shows everything found so far at once — use it to rename or correct something later.",
|
||||
"title": "다운로드 코스",
|
||||
"description": "안내 설정은 기기와 함께 진행됩니다. 기기에서 다운로드한 코스를 선택하고, 코스가 확인될 때마다 하나씩 이름을 지정하세요.\n\n이름 편집은 지금까지 찾은 모든 코스를 한 번에 보여줍니다. 나중에 이름을 바꾸거나 수정할 때 사용하세요.",
|
||||
"menu_options": {
|
||||
"cloud_guided": "Guided setup",
|
||||
"cloud_manual": "Edit names"
|
||||
"cloud_guided": "안내 설정",
|
||||
"cloud_manual": "이름 편집"
|
||||
}
|
||||
},
|
||||
"cloud_wait": {
|
||||
"title": "Download cycles"
|
||||
"title": "다운로드 코스"
|
||||
},
|
||||
"cloud_name": {
|
||||
"title": "Name this cycle",
|
||||
"description": "The appliance switched to a downloaded cycle (slot {slot}) and reports {remaining} remaining.\n\nGive it the name you want to see in Home Assistant, then select the next one on the appliance. Leave it blank to keep this cycle out of the list.\n\nNamed so far ({named} of {total}): {named_list}\n\nNames must be unique. You can close this dialog whenever you like — names are saved as you go.",
|
||||
"title": "이 코스 이름 지정",
|
||||
"description": "기기가 다운로드한 코스(슬롯 {slot})로 전환되었으며 {remaining} 남았다고 표시됩니다.\n\nHome Assistant에서 보고 싶은 이름을 입력한 다음, 기기에서 다음 코스를 선택하세요. 비워 두면 이 코스는 목록에서 제외됩니다.\n\n지금까지 이름 지정됨 ({named}/{total}개): {named_list}\n\n이름은 서로 달라야 합니다. 이 창은 언제든지 닫을 수 있습니다. 이름은 진행하는 대로 저장됩니다.",
|
||||
"data": {
|
||||
"name": "Name"
|
||||
"name": "이름",
|
||||
"download_course": "다운로드 코스 코드"
|
||||
}
|
||||
},
|
||||
"cloud_timeout": {
|
||||
"title": "No cycle selected",
|
||||
"description": "Nothing was selected on the appliance. Make sure it's set to the Download cycle, then step through the downloaded programs.\n\nNamed so far ({named} of {total}): {named_list}\n\nEverything named so far is already saved.",
|
||||
"title": "선택된 코스 없음",
|
||||
"description": "기기에서 아무것도 선택되지 않았습니다. 다운로드 코스로 설정되어 있는지 확인한 다음, 다운로드된 프로그램을 하나씩 실행해 보세요.\n\n지금까지 이름 지정됨 ({named}/{total}개): {named_list}\n\n지금까지 이름을 지정한 항목은 이미 저장되었습니다.",
|
||||
"menu_options": {
|
||||
"cloud_guided": "Wait again",
|
||||
"cloud_finish": "Finish"
|
||||
"cloud_guided": "다시 대기",
|
||||
"cloud_finish": "완료"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1490,7 +1491,7 @@
|
||||
"not_loaded": "이 기기는 아직 연결되지 않았습니다. 기기를 불러온 후 다시 시도하세요."
|
||||
},
|
||||
"progress": {
|
||||
"cloud_wait": "Select a downloaded cycle on the appliance now.\n\nNamed so far ({named} of {total}): {named_list}\n\nYou can close this dialog at any time — names are saved as you go."
|
||||
"cloud_wait": "지금 기기에서 다운로드한 코스를 선택하세요.\n\n지금까지 이름 지정됨 ({named}/{total}개): {named_list}\n\n이 창은 언제든지 닫을 수 있습니다. 이름은 진행하는 대로 저장됩니다."
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
|
||||
@@ -1454,29 +1454,30 @@
|
||||
}
|
||||
},
|
||||
"cloud_courses": {
|
||||
"title": "Download cycles",
|
||||
"description": "Guided setup walks the appliance with you: select a downloaded cycle on the appliance and name it as it's found, one at a time.\n\nEdit names shows everything found so far at once — use it to rename or correct something later.",
|
||||
"title": "Gedownloade programma's",
|
||||
"description": "Begeleide installatie loodst je stap voor stap langs het apparaat: selecteer op het apparaat een gedownload programma en geef het een naam zodra het gevonden is, één voor één.\n\nNamen bewerken toont in één keer alles wat tot nu toe is gevonden — gebruik dit later om iets te hernoemen of te corrigeren.",
|
||||
"menu_options": {
|
||||
"cloud_guided": "Guided setup",
|
||||
"cloud_manual": "Edit names"
|
||||
"cloud_guided": "Begeleide installatie",
|
||||
"cloud_manual": "Namen bewerken"
|
||||
}
|
||||
},
|
||||
"cloud_wait": {
|
||||
"title": "Download cycles"
|
||||
"title": "Gedownloade programma's"
|
||||
},
|
||||
"cloud_name": {
|
||||
"title": "Name this cycle",
|
||||
"description": "The appliance switched to a downloaded cycle (slot {slot}) and reports {remaining} remaining.\n\nGive it the name you want to see in Home Assistant, then select the next one on the appliance. Leave it blank to keep this cycle out of the list.\n\nNamed so far ({named} of {total}): {named_list}\n\nNames must be unique. You can close this dialog whenever you like — names are saved as you go.",
|
||||
"title": "Geef dit programma een naam",
|
||||
"description": "Het apparaat is overgeschakeld naar een gedownload programma (slot {slot}) en meldt nog {remaining} resterend.\n\nGeef het de naam die je in Home Assistant wilt zien en selecteer daarna het volgende op het apparaat. Laat het veld leeg om dit programma buiten de lijst te houden.\n\nTot nu toe benoemd ({named} van {total}): {named_list}\n\nNamen moeten uniek zijn. Je kunt dit venster op elk moment sluiten — namen worden onderweg opgeslagen.",
|
||||
"data": {
|
||||
"name": "Name"
|
||||
"name": "Naam",
|
||||
"download_course": "Programmacode van \"Gedownload\""
|
||||
}
|
||||
},
|
||||
"cloud_timeout": {
|
||||
"title": "No cycle selected",
|
||||
"description": "Nothing was selected on the appliance. Make sure it's set to the Download cycle, then step through the downloaded programs.\n\nNamed so far ({named} of {total}): {named_list}\n\nEverything named so far is already saved.",
|
||||
"title": "Geen programma geselecteerd",
|
||||
"description": "Er is niets geselecteerd op het apparaat. Zorg dat het is ingesteld op het programma \"Gedownload\" en doorloop dan de gedownloade programma's.\n\nTot nu toe benoemd ({named} van {total}): {named_list}\n\nAlles wat tot nu toe benoemd is, is al opgeslagen.",
|
||||
"menu_options": {
|
||||
"cloud_guided": "Wait again",
|
||||
"cloud_finish": "Finish"
|
||||
"cloud_guided": "Opnieuw wachten",
|
||||
"cloud_finish": "Voltooien"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1490,7 +1491,7 @@
|
||||
"not_loaded": "Dit apparaat is nog niet verbonden. Probeer het opnieuw zodra het is geladen."
|
||||
},
|
||||
"progress": {
|
||||
"cloud_wait": "Select a downloaded cycle on the appliance now.\n\nNamed so far ({named} of {total}): {named_list}\n\nYou can close this dialog at any time — names are saved as you go."
|
||||
"cloud_wait": "Selecteer nu een gedownload programma op het apparaat.\n\nTot nu toe benoemd ({named} van {total}): {named_list}\n\nJe kunt dit venster op elk moment sluiten — namen worden onderweg opgeslagen."
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
|
||||
@@ -622,3 +622,62 @@ async def test_the_name_form_shows_the_other_names_while_typing(hass: HomeAssist
|
||||
|
||||
form = await handler.async_step_cloud_name()
|
||||
assert form["description_placeholders"]["named_list"] == "Jeans"
|
||||
|
||||
|
||||
async def test_guided_naming_never_clears_a_confirmed_download_course(hass: HomeAssistant):
|
||||
"""A program needs both a name and the Download course before it can be
|
||||
offered. The guided name form says nothing about the course, and passing
|
||||
that silence through as None wiped a confirmed one -- so naming a program
|
||||
removed every already-named program from the cycle list."""
|
||||
coordinator = await _coordinator(hass)
|
||||
coordinator.apply_cloud_courses({"6B": "Jeans"}, "87")
|
||||
await _flush(hass)
|
||||
assert "cloud:6B" in _cycle_options(coordinator)
|
||||
|
||||
handler = await _options_handler(hass, coordinator)
|
||||
await handler.async_step_cloud_guided()
|
||||
_stub_probe(coordinator, ["55"])
|
||||
await _run_wait(handler)
|
||||
await handler.async_step_cloud_name({"name": "Sports"})
|
||||
await _flush(hass)
|
||||
|
||||
assert coordinator.cloud_courses.snapshot()["download_course"] == "87"
|
||||
assert {"cloud:55", "cloud:6B"} <= set(_cycle_options(coordinator))
|
||||
|
||||
|
||||
async def test_guided_setup_alone_produces_a_selectable_cycle(hass: HomeAssistant):
|
||||
"""The walk has to stand on its own: someone who only ever uses guided
|
||||
setup must end up with something in the cycle list. So the first name
|
||||
form also asks for the Download course, prefilled from what was just
|
||||
observed, and drops the field once it is confirmed."""
|
||||
coordinator = await _coordinator(hass)
|
||||
handler = await _options_handler(hass, coordinator)
|
||||
await handler.async_step_cloud_guided()
|
||||
_stub_probe(coordinator, ["55"])
|
||||
await _run_wait(handler)
|
||||
|
||||
first = await handler.async_step_cloud_name()
|
||||
assert "download_course" in str(first["data_schema"].schema)
|
||||
# Prefilled from the observation the fixture already carries.
|
||||
assert handler._cloud_course == "87"
|
||||
|
||||
await handler.async_step_cloud_name({"name": "Sports", "download_course": "87"})
|
||||
await _flush(hass)
|
||||
assert "cloud:55" in _cycle_options(coordinator)
|
||||
|
||||
# Confirmed now, so the next program is asked for its name only.
|
||||
handler._cloud_slot = "6B"
|
||||
second = await handler.async_step_cloud_name()
|
||||
assert "download_course" not in str(second["data_schema"].schema)
|
||||
|
||||
|
||||
async def test_guided_rejects_a_download_course_the_device_does_not_offer(hass: HomeAssistant):
|
||||
coordinator = await _coordinator(hass)
|
||||
handler = await _options_handler(hass, coordinator)
|
||||
await handler.async_step_cloud_guided()
|
||||
_stub_probe(coordinator, ["55"])
|
||||
await _run_wait(handler)
|
||||
|
||||
result = await handler.async_step_cloud_name({"name": "Sports", "download_course": "FF"})
|
||||
assert result["errors"] == {"base": "cloud_course_unknown_course"}
|
||||
assert coordinator.cloud_courses.snapshot()["download_course"] is None
|
||||
Reference in new issue
Block a user