From 809b99a6227537f2d24c14a2f713113ab79eb96f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=82ngelo=20Mois=C3=A9s=20Alves?= Date: Sun, 4 Oct 2026 22:22:51 -0300 Subject: [PATCH 1/7] fix(panel): SAIR on a forced PIN change no longer opens the menu Finding 9 of docs/analysis/PLANO_REVISAO_EXTERNA.md. An account whose PIN must change (a stricter policy, or the admin's factory 1234) is identified and sent straight to Novo PIN. SAIR there opened the Settings menu, with the session's every bit and the PIN unchanged; the change was asked again only at the next identification. showPinEntry( ) takes a `forced` flag, and the two forced calls in panelIdentify( ) set it. pinCancel( ) sends a forced PIN_FOR_OWN to the dashboard, where Settings asks for the account again; the voluntary change, from the menu, still goes back to the menu. _pinForced has no initializer: only pinCancel( ) reads it, on the PIN screen, and showPinEntry( ), the one way onto that screen, writes it first. Zeroing it cost the constructor 16 B. Tested on the rig with pico_w_test, each image started from the S1 bench dump. Tightening the policy from 4/3/0 to 5/3/0 marks every account holding a PIN. pmgr identified with its old 5-digit PIN (SEC_PIN_OK, 308), Novo PIN opened on the ordered keypad, and SAIR then gave: - main e5438e7: UI mode 5, the Settings menu (screenshot); - this branch: UI mode 0, the dashboard; CFG then opened the account picker (mode 26). The factory-PIN path is the same call with the same flag; it was not run on the rig. The panel's screens have no native suite. Manual chapter 8 says what SAIR does now; MANUAL.pt-BR.html regenerated. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01BniVDEDSHGQgcoqmCRn5um --- docs/MANUAL.pt-BR.html | 2 +- docs/_manual/cap08-contas.md | 2 +- src/AppManager_Panel.cpp | 4 ++-- src/DisplayManager.h | 10 +++++++++- src/DisplayManager_Users.cpp | 12 +++++++++--- 5 files changed, 22 insertions(+), 8 deletions(-) diff --git a/docs/MANUAL.pt-BR.html b/docs/MANUAL.pt-BR.html index 339e9254..39ebc5d5 100644 --- a/docs/MANUAL.pt-BR.html +++ b/docs/MANUAL.pt-BR.html @@ -4251,7 +4251,7 @@

PIN errado e bloqueios

O PIN de fábrica

A conta admin sai de fábrica com o PIN 1234. Na primeira identificação com ele, o painel abre direto a tela Novo PIN, e o log registra o código 302 com o texto PIN padrão detectado; forçando troca. A troca fica pendente até o admin escolher um PIN diferente de 1234; escolher 1234 de novo não a desfaz.

Da mesma forma, quando a política de PIN fica mais exigente, toda conta com PIN é marcada para trocá-lo, e o painel abre Novo PIN na próxima identificação de cada uma (A política de PIN).

-

Nos dois casos, a conta já está identificada quando a tela Novo PIN abre. SAIR leva ao menu sem trocar, e a tela volta na identificação seguinte.

+

Nos dois casos, a conta já está identificada quando a tela Novo PIN abre, mas o menu só abre depois da troca. SAIR volta à tela inicial sem trocar, e a tela volta na identificação seguinte. Até a v2.11.0, SAIR levava ao menu sem trocar, inclusive com o 1234 de fábrica.

O próprio PIN

Qualquer conta identificada troca o próprio PIN pelo item Alterar Senha do menu (o 5º, Change Password).

    diff --git a/docs/_manual/cap08-contas.md b/docs/_manual/cap08-contas.md index 66e60c70..a6a2b5f4 100644 --- a/docs/_manual/cap08-contas.md +++ b/docs/_manual/cap08-contas.md @@ -335,7 +335,7 @@ A conta `admin` sai de fábrica com o PIN `1234`. Na primeira identificação co Da mesma forma, quando a política de PIN fica mais exigente, toda conta com PIN é marcada para trocá-lo, e o painel abre **Novo PIN** na próxima identificação de cada uma ([A política de PIN](#cap-08-politica)). -Nos dois casos, a conta já está identificada quando a tela **Novo PIN** abre. **SAIR** leva ao menu sem trocar, e a tela volta na identificação seguinte. +Nos dois casos, a conta já está identificada quando a tela **Novo PIN** abre, mas o menu só abre depois da troca. **SAIR** volta à tela inicial sem trocar, e a tela volta na identificação seguinte. Até a v2.11.0, **SAIR** levava ao menu sem trocar, inclusive com o `1234` de fábrica. ### O próprio PIN {#cap-08-proprio-pin} diff --git a/src/AppManager_Panel.cpp b/src/AppManager_Panel.cpp index 993c9987..076360a9 100644 --- a/src/AppManager_Panel.cpp +++ b/src/AppManager_Panel.cpp @@ -156,13 +156,13 @@ void AppManager::panelIdentify( ) { * the first thing it does is choose a PIN that complies. The admin's factory * "1234" arrives here the same way. */ if (_storageMgr->pinMustChange(slot)) { - _displayMgr->showPinEntry(DisplayManager::PIN_FOR_OWN); + _displayMgr->showPinEntry(DisplayManager::PIN_FOR_OWN, -1, true); LOG_CODE(LOG_WARN, "SEC", SEC_UNAUTHORIZED, (int)slot, TRL("PIN does not meet the policy; forcing change.")); return; } if (slot == 0 && _storageMgr->mustChangePin( )) { - _displayMgr->showPinEntry(DisplayManager::PIN_FOR_OWN); + _displayMgr->showPinEntry(DisplayManager::PIN_FOR_OWN, -1, true); LOG_CODE(LOG_WARN, "SEC", SEC_UNAUTHORIZED, 0, TRL("Default PIN detected; forcing change.")); return; diff --git a/src/DisplayManager.h b/src/DisplayManager.h index 244d7117..1eb6875e 100644 --- a/src/DisplayManager.h +++ b/src/DisplayManager.h @@ -564,7 +564,10 @@ class DisplayManager { PIN_FOR_USER, /**< an admin sets another account's PIN */ PIN_FOR_NEW_ACCOUNT /**< last step of creating an account */ }; - void showPinEntry(uint8_t purpose, int8_t targetUser = -1); + /** `forced`: PIN_FOR_OWN because the account's PIN must change (policy + * or factory PIN), so the session may not go on without it: SAIR leaves + * to the dashboard, and Settings asks for the PIN again from there. */ + void showPinEntry(uint8_t purpose, int8_t targetUser = -1, bool forced = false); /** Forces repaint of the keypad after another screen (the license) * covered it. State (typed digits, lockout) is untouched. */ /** Core 0's verdict on the PIN of the last EVT_AUTH_PIN. Runs the lockout @@ -1185,6 +1188,11 @@ class DisplayManager { uint8_t _pinPhase = 0; /**< 0 typing, 1 confirming (new PIN) */ uint8_t _pinPurpose = 0; /**< PinPurpose */ int8_t _pinTarget = -1; /**< PIN_FOR_USER: whose */ + /** PIN_FOR_OWN that the session must not skip. No initializer: only + * pinCancel( ) reads it, on the PIN screen, and the one way onto that + * screen, showPinEntry( ), writes it first. Zeroing it cost the + * constructor 16 B (pico_w_release, 2026-10-04). */ + bool _pinForced; LangKey _pinMsg = TR_KEYS_COUNT;/**< transient line under the dots */ /* What each key on screen carries. Under KB_SET2/KB_SET3 it is a DEAL: * the alphabet plus decoys over cards of S slots, re-dealt on every entry, diff --git a/src/DisplayManager_Users.cpp b/src/DisplayManager_Users.cpp index feaccf5f..d4e3f007 100644 --- a/src/DisplayManager_Users.cpp +++ b/src/DisplayManager_Users.cpp @@ -190,11 +190,12 @@ uint8_t DisplayManager::pinKeyFace(int key, char* out, size_t cap) const { return (uint8_t)n; } -void DisplayManager::showPinEntry(uint8_t purpose, int8_t targetUser) { +void DisplayManager::showPinEntry(uint8_t purpose, int8_t targetUser, bool forced) { mutex_enter_blocking(&_stateMutex); _uiMode = MODE_AUTH; _pinPurpose = purpose; _pinTarget = targetUser; + _pinForced = forced; _pinLen = 0; _pinBuf[0] = '\0'; memset(_pinTaps, 0, sizeof(_pinTaps)); _pinPhase = 0; _pinFirst[0] = '\0'; @@ -293,11 +294,16 @@ void DisplayManager::setPanelSession(int8_t user, uint16_t perms) { mutex_exit(&_stateMutex); } -/* Cancel from the keypad: back to wherever this PIN was asked from. */ +/* Cancel from the keypad: back to wherever this PIN was asked from. A change + * the account was sent to by force was asked from nowhere: the menu used to + * open behind it, with the session's every bit and the PIN still the one the + * policy or the factory left (finding 9 of docs/analysis/PLANO_REVISAO_EXTERNA.md). + * It leaves to the dashboard, and Settings asks for the PIN again from there, + * which sends the account back to the change. */ void DisplayManager::pinCancel( ) { clearEnteredPin( ); switch (_pinPurpose) { - case PIN_FOR_OWN: showSettingsMain( ); break; + case PIN_FOR_OWN: if (_pinForced) forceDashboard( ); else showSettingsMain( ); break; case PIN_FOR_USER: showUserEdit(_pinTarget, false); break; case PIN_FOR_NEW_ACCOUNT: showUserEdit(-1, true); break; default: forceDashboard( ); break; From 1d8c298c8eb0c7a212c9e9e6fe4e783deb6fa507 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=82ngelo=20Mois=C3=A9s=20Alves?= Date: Sun, 4 Oct 2026 22:22:54 -0300 Subject: [PATCH 2/7] fix(panel): an account grants at the panel only the bits it holds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Finding 8 of docs/analysis/PLANO_REVISAO_EXTERNA.md. At the panel, an account with Users (PERM_USER_MGR) gave Limits, Block and Maintenance to any account, a new one or its own, whatever it held itself. The web has refused that since V-09 (commitGrantAllowed). panelGrantAllowed(before, after, caller), in SessionCheck.h, is the rule: a change of an account's panel bits may only ADD bits the caller holds. Taking one away is allowed, and so is leaving one the caller lacks where it was. panelAllowed( ) gains an overload that asks it, used by EVT_USER_ADD (from 0) and EVT_USER_PERMS. A refusal is the one panelAllowed( ) already gives: the error tone, APP_UI_PERM_DENIED (458) in the log, "Sem permissão", and nothing written. An overload, not two default arguments: those cost each of the other call sites 4 B, 56 B in all on pico_w_release. Tests first: test_panel_grant_adds_only_bits_the_caller_holds, in the native suite, which passes 284 of 284, under ASan and UBSan too. On the rig, pmgr holding Users alone (0x0100), from its Users menu: - a new account with L and M: main created it with 0x1400; this branch refuses it; - ptgt, 0x0, plus L: main 0x0400; this branch refuses, and it stays 0x0; - pkeep, 0x0800, minus B (a bit pmgr lacks): 0x0 on both; - pmgr itself plus M: main 0x1100; this branch refuses, 0x0100; - the log: main 450 once and 453 three times; this branch 458 three times and 453 once. Manual chapter 8 says who grants what; MANUAL.pt-BR.html regenerated. The READMEs count the new case. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01BniVDEDSHGQgcoqmCRn5um --- README.es-ES.md | 4 ++-- README.md | 4 ++-- README.pt-BR.md | 4 ++-- docs/MANUAL.pt-BR.html | 5 ++-- docs/_manual/cap08-contas.md | 6 +++-- src/AppManager.h | 5 ++++ src/AppManager_Panel.cpp | 38 +++++++++++++++++++++++------- src/SessionCheck.h | 18 ++++++++++++++ test/test_validators/test_main.cpp | 28 ++++++++++++++++++++++ 9 files changed, 94 insertions(+), 18 deletions(-) diff --git a/README.es-ES.md b/README.es-ES.md index 449d1107..2696bf25 100644 --- a/README.es-ES.md +++ b/README.es-ES.md @@ -50,7 +50,7 @@ Los tres comparten el mismo núcleo: | **Release actual** | **v2.11.0** (04/10/2026); el [changelog](CHANGELOG.md) dice qué cambió cada versión. SIMUT salió de beta con la v2.7.0, sobre mediciones: un soak de 8,18 h sin ningún reinicio y 6 de 6 actualizaciones por el aire sin perder nada. | | **Imágenes publicadas** | Cuatro imágenes, cada una en `.uf2` y `.bin`. Para la Pico W: `release` (panel táctil TFT), `alpha` (LCD 16×2 con consola Bluetooth) y `air` (registrador a batería sin pantalla). Para la Pico 2 W (RP2350): `releasetwo`, la release, cuyo `.uf2` lleva su tabla de particiones y cuyo `.bin` arranca a prueba. Junto a ellas van los packs de idioma pt-BR y es-ES y un manifiesto de OTA. Una imagen con otro conjunto de funciones sale del [configurador de build](https://angelointj.github.io/simut/configurador/), y el CI la compila desde `main`; el configurador todavía no compila para la Pico 2 W. | | **Madurez** |
    • `release`: **estable**.
    • `alpha`: publicado y probado en el banco, con su LCD 16×2 incluido desde el 26/09/2026.
    • `air`: **experimental**. Su único soak largo falló: un sueño en el ciclo 119 nunca despertó (F28). Hoy lo mitiga un watchdog a lo largo del despertar; la causa raíz no está confirmada.
    • `releasetwo`: **nueva** en la v2.11.0, probada en el banco en una Pico 2 W (RP2350 A2). Se actualiza por el aire en el otro slot, a prueba, y vuelve sola cuando la actualización no se confirma.
    | -| **Pruebas** | Cada pull request ejecuta 632 casos de test en el host en 9 suites, 60 s de fuzzing y análisis estático, y compila las siete imágenes de firmware con la caché fría. El comportamiento en hardware real se verifica en un banco — ver [Verificación en hardware](docs/VERIFICATION.es-ES.md). | +| **Pruebas** | Cada pull request ejecuta 633 casos de test en el host en 9 suites, 60 s de fuzzing y análisis estático, y compila las siete imágenes de firmware con la caché fría. El comportamiento en hardware real se verifica en un banco — ver [Verificación en hardware](docs/VERIFICATION.es-ES.md). | **Limitaciones conocidas.** Cada una está documentada donde aplica. - **Actualización.** La actualización por el aire reformatea el sistema de archivos: @@ -303,7 +303,7 @@ El dispositivo expone una API REST en `http:///api/`: ### Tests en el host ```bash -pio test -e native # validadores, cursor de telemetría, etiquetas, parsers, paquetes de idioma, las pantallas Licencia y de actualización, el menú de Ajustes, la verificación de contraseña, la sesión web (283 casos) +pio test -e native # validadores, cursor de telemetría, etiquetas, parsers, paquetes de idioma, las pantallas Licencia y de actualización, el menú de Ajustes, la verificación de contraseña, la sesión web (284 casos) pio test -e native_history_v5 # códec del histórico V5 (62) pio test -e native_cli # parser de la CLI (33) pio test -e native_logpolicy # persistencia de log por transición, franjas de la autopsia, lectura del táctil (57) diff --git a/README.md b/README.md index 746ec071..0ac7b7af 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ They share one core: | **Current release** | **v2.11.0** (2026-10-04); the [changelog](CHANGELOG.md) says what each version changed. SIMUT left beta with v2.7.0, on measurements: an 8.18 h soak with 0 reboots, and 6 of 6 over-the-air updates with nothing lost. | | **Published images** | Four images, each as `.uf2` and `.bin`. For the Pico W: `release` (TFT touch panel), `alpha` (16×2 LCD with a Bluetooth console) and `air` (headless battery logger). For the Pico 2 W (RP2350): `releasetwo`, the release, whose `.uf2` carries its partition table and whose `.bin` boots on trial. The pt-BR and es-ES language packs and an OTA manifest ship alongside. An image with a different set of features comes from the [build configurator](https://angelointj.github.io/simut/configurador/), and CI builds it from `main`; the configurator does not build for the Pico 2 W yet. | | **Maturity** |
    • `release`: **stable**.
    • `alpha`: published and bench-tested, its 16×2 LCD included since 2026-09-26.
    • `air`: **experimental**. Its one long soak failed: a sleep in cycle 119 never woke (F28). A watchdog across the wake now mitigates it; the root cause is not confirmed.
    • `releasetwo`: **new** in v2.11.0, bench-tested on one Pico 2 W (RP2350 A2). It updates over the air into its other slot, on trial, and goes back by itself when the update does not keep itself.
    | -| **Tests** | Every pull request runs 632 host test cases in 9 suites, 60 s of fuzzing and static analysis, and builds all seven firmware images from a cold cache. Behaviour on real hardware is verified on a bench — see [Verification on hardware](docs/VERIFICATION.md). | +| **Tests** | Every pull request runs 633 host test cases in 9 suites, 60 s of fuzzing and static analysis, and builds all seven firmware images from a cold cache. Behaviour on real hardware is verified on a bench — see [Verification on hardware](docs/VERIFICATION.md). | **Known limitations.** Each one is documented where it applies. - **Updates.** An update over the air reformats the filesystem: @@ -301,7 +301,7 @@ The device exposes a REST API at `http:///api/`: ### Host tests ```bash -pio test -e native # validators, telemetry cursor, labels, parsers, language packs, the License and update screens, the Settings menu, the password check, the web session (283 cases) +pio test -e native # validators, telemetry cursor, labels, parsers, language packs, the License and update screens, the Settings menu, the password check, the web session (284 cases) pio test -e native_history_v5 # V5 history codec (62) pio test -e native_cli # CLI parser (33) pio test -e native_logpolicy # edge-triggered log persistence, autopsy bands, touch wake (57) diff --git a/README.pt-BR.md b/README.pt-BR.md index e4e92c6a..34cf967b 100644 --- a/README.pt-BR.md +++ b/README.pt-BR.md @@ -48,7 +48,7 @@ Os três compartilham o mesmo núcleo: | **Release atual** | **v2.11.0** (04/10/2026); o [changelog](CHANGELOG.pt-BR.md) diz o que cada versão mudou. O SIMUT saiu do beta com a v2.7.0, com base em medições: soak de 8,18 h sem nenhum reboot e 6 de 6 atualizações pelo ar sem perder nada. | | **Imagens publicadas** | Quatro imagens, cada uma em `.uf2` e `.bin`. Para o Pico W: `release` (painel touch TFT), `alpha` (LCD 16×2 com console Bluetooth) e `air` (registrador a bateria sem display). Para o Pico 2 W (RP2350): `releasetwo`, a release, cujo `.uf2` traz a tabela de partições e cujo `.bin` sobe em teste. Junto vêm os packs de idioma pt-BR e es-ES e um manifesto de OTA. Uma imagem com outro conjunto de recursos sai do [configurador de build](https://angelointj.github.io/simut/configurador/), e o CI a compila da `main`; o configurador ainda não compila para o Pico 2 W. | | **Maturidade** |
    • `release`: **estável**.
    • `alpha`: publicado e testado na bancada, com o LCD 16×2 incluído desde 26/09/2026.
    • `air`: **experimental**. O único soak longo dele falhou: um sono no ciclo 119 nunca acordou (F28). Um watchdog ao longo do wake hoje mitiga o problema; a causa raiz não foi confirmada.
    • `releasetwo`: **nova** na v2.11.0, testada na bancada num Pico 2 W (RP2350 A2). Atualiza pelo ar no outro slot, em teste, e volta sozinha quando a atualização não se confirma.
    | -| **Testes** | Todo pull request roda 632 casos de teste no host em 9 suítes, 60 s de fuzzing e análise estática, e compila as sete imagens de firmware com o cache frio. O comportamento no hardware real é verificado numa bancada — ver [Verificação no hardware](docs/VERIFICATION.pt-BR.md). | +| **Testes** | Todo pull request roda 633 casos de teste no host em 9 suítes, 60 s de fuzzing e análise estática, e compila as sete imagens de firmware com o cache frio. O comportamento no hardware real é verificado numa bancada — ver [Verificação no hardware](docs/VERIFICATION.pt-BR.md). | **Limitações conhecidas.** Cada uma está documentada onde se aplica. - **Atualização.** A atualização pelo ar reformata o sistema de arquivos: @@ -299,7 +299,7 @@ O aparelho expõe uma API REST em `http:///api/`: ### Testes no host ```bash -pio test -e native # validadores, cursor de telemetria, rótulos, parsers, pacotes de idioma, as telas Licença e de atualização, o menu de Configurações, a verificação de senha, a sessão web (283 casos) +pio test -e native # validadores, cursor de telemetria, rótulos, parsers, pacotes de idioma, as telas Licença e de atualização, o menu de Configurações, a verificação de senha, a sessão web (284 casos) pio test -e native_history_v5 # codec do histórico V5 (62) pio test -e native_cli # parser do CLI (33) pio test -e native_logpolicy # persistência de log por transição, faixas da autópsia, leitura do toque (57) diff --git a/docs/MANUAL.pt-BR.html b/docs/MANUAL.pt-BR.html index 39ebc5d5..e0f79c9f 100644 --- a/docs/MANUAL.pt-BR.html +++ b/docs/MANUAL.pt-BR.html @@ -4399,7 +4399,7 @@

    Usuários no painel

    Sem permissão -A conta identificada não tem mais a permissão Usuários +A conta identificada não tem mais a permissão Usuários, ou marcou uma permissão de painel que ela mesma não tem @@ -4442,7 +4442,8 @@

    Usuários no painel

    -

    As três primeiras linhas só mudam na tela; SALVAR grava, e o painel mostra Usuário salvo! (código 453, Permissões alteradas pelo painel). As permissões web da conta ficam como estavam. Definir PIN grava ao fim da confirmação (código 452, PIN do painel definido).

    +

    As três primeiras linhas só mudam na tela; SALVAR grava, e o painel mostra Usuário salvo! (código 453, Permissões alteradas pelo painel). As permissões web da conta ficam como estavam.

    +

    O painel só concede as permissões de painel que a conta identificada tem. Marcar, numa conta nova ou numa existente, uma permissão que ela não tem é recusado com Sem permissão, e nada é gravado: o log registra o código 458, Ação do painel recusada: sem permissão. Desmarcar é sempre possível, e uma permissão que a conta editada já tinha pode ficar. A regra é a mesma da página Usuários da web, e vale também para a própria conta. Até a v2.11.0, quem tinha a permissão Usuários concedia as três a qualquer conta, inclusive a si mesmo. Definir PIN grava ao fim da confirmação (código 452, PIN do painel definido).

    Excluir uma conta no painel:

    1. No editor da conta, toque em Excluir usuário.
    2. diff --git a/docs/_manual/cap08-contas.md b/docs/_manual/cap08-contas.md index a6a2b5f4..8f2e47c5 100644 --- a/docs/_manual/cap08-contas.md +++ b/docs/_manual/cap08-contas.md @@ -426,7 +426,7 @@ O painel mostra **Usuário salvo!** e grava na hora, sem reiniciar. O log regist | **Nome inválido ou repetido** | Nome com aspas, barra invertida ou caractere de controle, já usado por outra conta, ou `admin` | | **Sem vaga para conta nova** | As 32 posições estão ocupadas | | **PIN já em uso!** | Outra conta tem esse PIN; a conta não é criada | -| **Sem permissão** | A conta identificada não tem mais a permissão **Usuários** | +| **Sem permissão** | A conta identificada não tem mais a permissão **Usuários**, ou marcou uma permissão de painel que ela mesma não tem | Uma conta criada no painel tem só as três permissões de painel e uma senha web aleatória que ninguém conhece. Ela não abre nenhuma página web até um administrador recriá-la pela página **Usuários** com permissões web e entregar a senha de uso único ([Mudar as permissões de uma conta](#cap-08-mudar)). @@ -452,7 +452,9 @@ O PIN da conta nova, digitado duas vezes no teclado ordenado. | **Definir PIN** | Define um PIN novo para a conta, digitado duas vezes | | **Excluir usuário** | Exclui a conta, depois de confirmar | -As três primeiras linhas só mudam na tela; **SALVAR** grava, e o painel mostra **Usuário salvo!** (código 453, **Permissões alteradas pelo painel**). As permissões web da conta ficam como estavam. **Definir PIN** grava ao fim da confirmação (código 452, **PIN do painel definido**). +As três primeiras linhas só mudam na tela; **SALVAR** grava, e o painel mostra **Usuário salvo!** (código 453, **Permissões alteradas pelo painel**). As permissões web da conta ficam como estavam. + +**O painel só concede as permissões de painel que a conta identificada tem.** Marcar, numa conta nova ou numa existente, uma permissão que ela não tem é recusado com **Sem permissão**, e nada é gravado: o log registra o código 458, **Ação do painel recusada: sem permissão**. Desmarcar é sempre possível, e uma permissão que a conta editada já tinha pode ficar. A regra é a mesma da página **Usuários** da web, e vale também para a própria conta. Até a v2.11.0, quem tinha a permissão **Usuários** concedia as três a qualquer conta, inclusive a si mesmo. **Definir PIN** grava ao fim da confirmação (código 452, **PIN do painel definido**). **Excluir uma conta no painel:** diff --git a/src/AppManager.h b/src/AppManager.h index 58fa9cf0..240ee911 100644 --- a/src/AppManager.h +++ b/src/AppManager.h @@ -191,6 +191,11 @@ class AppManager { uint8_t _panelAuthFor = PAUTH_SETTINGS; bool handlePanelEvent(const UiEvent& ev); bool panelAllowed(uint16_t bit, int8_t slot); + /** panelAllowed( ), and an account's panel bits going from `before` to + * `after` add none the session lacks (panelGrantAllowed, SessionCheck.h). + * An overload, not two default arguments: those cost each of the other + * call sites 4 B, 56 B in all (pico_w_release, 2026-10-04). */ + bool panelAllowed(uint16_t bit, int8_t slot, uint16_t before, uint16_t after); bool panelSessionCurrent( ); const char* panelUserName( ) const; void panelIdentify( ); diff --git a/src/AppManager_Panel.cpp b/src/AppManager_Panel.cpp index 076360a9..5eca5828 100644 --- a/src/AppManager_Panel.cpp +++ b/src/AppManager_Panel.cpp @@ -43,6 +43,7 @@ bool AppManager::handlePanelEvent(const UiEvent& ev) { (void)ev; return false; } void AppManager::panelSaveAlarmLimits(int slot) { (void)slot; } void AppManager::panelSetUserPin(int slot, const char* pin, UiMode returnTo) { (void)slot; (void)pin; (void)returnTo; } bool AppManager::panelAllowed(uint16_t bit, int8_t slot) { (void)bit; (void)slot; return false; } +bool AppManager::panelAllowed(uint16_t bit, int8_t slot, uint16_t before, uint16_t after) { (void)bit; (void)slot; (void)before; (void)after; return false; } bool AppManager::panelSessionCurrent( ) { return false; } #else @@ -78,14 +79,24 @@ bool AppManager::panelSessionCurrent( ) { } /* Every action passes through here first. A missing bit is a refusal the - * operator hears (error tone) and the log keeps, with who tried what. */ -bool AppManager::panelAllowed(uint16_t bit, int8_t slot) { - if (panelSessionCurrent( ) && (_panelPerms & bit)) return true; + * operator hears (error tone) and the log keeps, with who tried what. So is + * changing an account's panel bits from `before` to `after` by adding one the + * session lacks (panelGrantAllowed, SessionCheck.h), which the two account + * events ask. noinline: inlined into the overload below, the body would be + * there twice. */ +__attribute__((noinline)) +bool AppManager::panelAllowed(uint16_t bit, int8_t slot, uint16_t before, uint16_t after) { + if (panelSessionCurrent( ) && (_panelPerms & bit) && + panelGrantAllowed(before, after, _panelPerms)) return true; LOG_CODE(LOG_WARN, "APP", APP_UI_PERM_DENIED, panelCtx(_panelUser, slot), ""); _soundMgr->play(SND_ERROR); return false; } +bool AppManager::panelAllowed(uint16_t bit, int8_t slot) { + return panelAllowed(bit, slot, 0, 0); +} + const char* AppManager::panelUserName( ) const { if (_panelUser < 0 || _panelUser >= MAX_USERS) return ""; return _storageMgr->getConfig( ).users[_panelUser].username; @@ -371,7 +382,11 @@ bool AppManager::handlePanelEvent(const UiEvent& ev) { } case UiEvent::EVT_USER_ADD: { - if (!panelAllowed(PERM_USER_MGR, -1)) { + /* Users, and no panel bit the session lacks: a new account starts from 0 + * (finding 8 of docs/analysis/PLANO_REVISAO_EXTERNA.md; the web's V-09). + * Refused whole, not trimmed: an account other than the one asked for, + * under a success, is what the web's rule refuses too. */ + if (!panelAllowed(PERM_USER_MGR, -1, 0, (uint16_t)(ev.param & PERM_PANEL_ALARM_ANY))) { _displayMgr->clearEnteredPin( ); _displayMgr->showPanelMessage(false, TR_NO_PERMISSION, MODE_SETTINGS_USERS); return true; @@ -461,17 +476,24 @@ bool AppManager::handlePanelEvent(const UiEvent& ev) { case UiEvent::EVT_USER_PERMS: { const int slot = ev.id; - if (!panelAllowed(PERM_USER_MGR, -1)) { + const bool known = slot > 0 && slot < MAX_USERS && cfg.users[slot].active; + /* only the three panel bits move; whatever the web granted stays. And only + * by adding bits the session holds: an account with Users and no panel bit + * gave all three to anyone, itself included (finding 8 of + * docs/analysis/PLANO_REVISAO_EXTERNA.md). */ + const uint16_t after = (uint16_t)(ev.param & PERM_PANEL_ALARM_ANY); + if (!panelAllowed(PERM_USER_MGR, -1, + known ? (uint16_t)(cfg.users[slot].permissions & PERM_PANEL_ALARM_ANY) : 0, + after)) { _displayMgr->showPanelMessage(false, TR_NO_PERMISSION, MODE_SETTINGS_USERS); return true; } - if (slot <= 0 || slot >= MAX_USERS || !cfg.users[slot].active) { + if (!known) { _displayMgr->showPanelMessage(false, TR_NAME_INVALID, MODE_SETTINGS_USERS); return true; } - /* only the three panel bits move; whatever the web granted stays */ const uint16_t keep = (uint16_t)(cfg.users[slot].permissions & ~PERM_PANEL_ALARM_ANY); - cfg.users[slot].permissions = (uint16_t)(keep | (ev.param & PERM_PANEL_ALARM_ANY)); + cfg.users[slot].permissions = (uint16_t)(keep | after); _storageMgr->saveConfiguration( ); _soundMgr->play(SND_CONFIRM); LOG_CODE(LOG_WARN, "APP", APP_UI_USER_PERMS, slot, cfg.users[slot].username); diff --git a/src/SessionCheck.h b/src/SessionCheck.h index 020a00d2..9e5e88ba 100644 --- a/src/SessionCheck.h +++ b/src/SessionCheck.h @@ -51,6 +51,24 @@ inline bool panelAccountStillThere(const UserT* users, int count, int slot, return true; } +/** + * May a panel session holding `caller` change an account's panel bits from + * `before` to `after`? Only by ADDING bits the caller holds itself. + * + * The web refuses to grant what the caller lacks since V-09 + * (commitGrantAllowed in WebCommitSections.h); the panel did not, so an + * account with Users and no panel bit gave Limits, Block and Maintenance to + * any account, its own included (finding 8 of + * docs/analysis/PLANO_REVISAO_EXTERNA.md). + * Taking a bit away is not escalation, and neither is leaving one the caller + * lacks where it already was: deleting the account outright needs no more than + * Users. A new account is `before` 0. Its own account is no exception: what it + * adds to itself it already holds. + */ +inline constexpr bool panelGrantAllowed(uint16_t before, uint16_t after, uint16_t caller) { + return ((after & (uint16_t)~before) & (uint16_t)~caller) == 0; +} + /** * Is the account in `slot` still the one this web session was opened for — * same name, and a password nobody else has set since (`salt`, stamped at diff --git a/test/test_validators/test_main.cpp b/test/test_validators/test_main.cpp index e933d9f5..4887e593 100644 --- a/test/test_validators/test_main.cpp +++ b/test/test_validators/test_main.cpp @@ -1290,6 +1290,33 @@ void test_commit_pin_target_protects_the_admin_slot(void) { TEST_ASSERT_TRUE(commitPinTargetAllowed(31, 4, P_USERMGR_ONLY)); } +/* Finding 8 of the manual's triage (23/09): the panel granted Limits, Block + * and Maintenance to any account, the caller's own included, without asking + * whether the caller held them. The web closed the same hole in V-09, just + * above. A change may only ADD bits the caller holds; taking one away, or + * leaving one the caller lacks where it was, is no escalation. */ +void test_panel_grant_adds_only_bits_the_caller_holds(void) { + const uint16_t L = PERM_ALARM_LIMITS, B = PERM_ALARM_BLOCK, M = PERM_MAINT; + /* Users and no panel bit: nothing to give, to a new account or an old one. */ + TEST_ASSERT_FALSE(panelGrantAllowed(0, L | M, P_USERMGR_ONLY)); + TEST_ASSERT_FALSE(panelGrantAllowed(0, B, P_USERMGR_ONLY)); + /* Nor to itself: its own bits are `before`, and the new ones are not its. */ + TEST_ASSERT_FALSE(panelGrantAllowed(P_USERMGR_ONLY & PERM_PANEL_ALARM_ANY, L, P_USERMGR_ONLY)); + /* Holding a bit, it may pass that bit on. */ + TEST_ASSERT_TRUE(panelGrantAllowed(0, L, P_USERMGR_ONLY | L)); + /* But not a second one alongside it. */ + TEST_ASSERT_FALSE(panelGrantAllowed(0, L | B, P_USERMGR_ONLY | L)); + /* Taking away is allowed, even a bit it lacks. */ + TEST_ASSERT_TRUE(panelGrantAllowed(L | B, L, P_USERMGR_ONLY)); + TEST_ASSERT_TRUE(panelGrantAllowed(B, 0, P_USERMGR_ONLY)); + /* A bit it lacks, already there and left alone, while it adds one it holds. */ + TEST_ASSERT_TRUE(panelGrantAllowed(B, B | L, P_USERMGR_ONLY | L)); + /* No change is no grant. */ + TEST_ASSERT_TRUE(panelGrantAllowed(L | M, L | M, P_USERMGR_ONLY)); + /* The full admin gives anything. */ + TEST_ASSERT_TRUE(panelGrantAllowed(0, L | B | M, PERM_FULL_ADMIN)); +} + void test_commit_netonly_can_commit_net_only(void) { int st[SEC_COUNT]; String net("{\"net\":{\"ssid\":\"lab\",\"use_dhcp\":1}}"); @@ -4856,6 +4883,7 @@ int main(int /*argc*/, char** /*argv*/) { RUN_TEST(test_commit_usermgr_can_commit_users_only); RUN_TEST(test_commit_grant_refuses_bits_the_caller_lacks); RUN_TEST(test_commit_pin_target_protects_the_admin_slot); + RUN_TEST(test_panel_grant_adds_only_bits_the_caller_holds); RUN_TEST(test_commit_netonly_can_commit_net_only); RUN_TEST(test_commit_admin_passes_everything); RUN_TEST(test_commit_entry_perms_exclude_viewer); From 4bff8e5b874d5d9a2291060a8b2c78d7c62edad1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=82ngelo=20Mois=C3=A9s=20Alves?= Date: Sun, 4 Oct 2026 22:22:57 -0300 Subject: [PATCH 3/7] fix(web): Adopt reads the probe on the slot's own GPIO, and reloads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Finding 1 of docs/analysis/PLANO_REVISAO_EXTERNA.md. "Adopt the probe wired here" read the 1-Wire bus on the GPIO whose number is the slot's, and wrote that number into the slot's pins[0]. A slot is not a pin. And it saved the new ROM without reloading the running sensor, which keeps its own copy and checks the probe against it every fifth read. sensor_accept now reads the GPIO the slot has saved and keeps it, and saves and reloads the sensors under quiet mode, as the calibration commit and the console's accept do. A slot that is not saved as an active DS18B20 has no GPIO to read: it answers 409 {"error":"notds18b20"}, and the page says "Save this slot as an active DS18B20 first." (new key sens_accept_unsaved, in both packs). checkAndAutoHealSensors( ), the 3 s health check, had the same mistake: it read GP and flagged by that number. It reads the slot's GPIO now, and logs it as the read path does. On the rig, pico_w_test, each image from the S1 bench dump: - slot 1 on GP1, told by `sensor define` it holds slot 0's probe, as after a swap, sits in quarantine. Adopt answered 200 on both. main: 0 valid reads of 15 in the next 90 s. This branch: 14 of 14, the first at once. - the probe on GP1 moved to slot 7: main read GP7 and answered 422 "badrom" for a probe GP1 was reading at 23.7 °C; this branch answered 200 and slot 7 stayed on GP1, reading again 8 s after the reload. - in 160 s neither logged a "missing" (106) for slot 7: the rig's empty GP7 answers a ROM that fails its CRC, which the health check skips. Manual chapter 6 says what Adopt does now, and the warning about the slot's number is gone; MANUAL.pt-BR.html regenerated. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01BniVDEDSHGQgcoqmCRn5um --- WebUI.h | 2 ++ data/lang/language_es-ES.lng | 2 +- data/lang/language_pt-BR.lng | 2 +- docs/MANUAL.pt-BR.html | 13 ++++++++----- docs/_manual/cap06-sensores.md | 11 +++++------ src/AppManager_Sensors.cpp | 32 ++++++++++++++++++++------------ src/WebManager_Calib.cpp | 27 +++++++++++++++++++++++---- 7 files changed, 60 insertions(+), 29 deletions(-) diff --git a/WebUI.h b/WebUI.h index 78fe5c3e..0e03d209 100644 --- a/WebUI.h +++ b/WebUI.h @@ -4350,6 +4350,8 @@ static const char CFG_PAGE[] PROGMEM = R"raw( showToast(window.t('sens_accept_none', 'No probe answered on this GPIO.'), 'err'); } else if (r.status === 422) { showToast(window.t('sens_accept_bad', 'The probe answered with an invalid ROM.'), 'err'); + } else if (r.status === 409) { + showToast(window.t('sens_accept_unsaved', 'Save this slot as an active DS18B20 first.'), 'err'); } else { showToast(window.t('act_fail', 'Action failed.'), 'err'); } diff --git a/data/lang/language_es-ES.lng b/data/lang/language_es-ES.lng index 855c8f44..794683b1 100644 --- a/data/lang/language_es-ES.lng +++ b/data/lang/language_es-ES.lng @@ -493,4 +493,4 @@ ap time =========================================== @WEBDICT -{"log_usr":"Usuario","log_pas":"Contraseña","log_show":"Mostrar contraseña","log_btn":"Entrar","log_chpass_link":"Cambiar contraseña","log_chpass_title":"Cambiar Contraseña","log_oldpass":"Contraseña Actual","log_newpass":"Nueva Contraseña","log_chpass_req_len":"Mínimo 8 caracteres","log_chpass_req_letter":"Letra","log_chpass_req_digit":"Dígito","log_chpass_req_symbol":"Símbolo","log_newpass2":"Repetir Nueva Contraseña","log_chpass_btn":"Guardar Nueva Contraseña","log_chpass_back":"← Volver al Login","fcp_wel":"¡Bienvenido!","fcp_msg":"Defina una nueva contraseña fuerte para acceder al sistema.","fcp_btn":"Guardar e Iniciar Sesión","nav_dash":"Panel de Control","dash_dt":"Fecha y Hora","dash_ntp_wait":"Esperando...","dash_up":"Tiempo Activo","dash_wifi":"Señal Wi-Fi","dash_pend":"Registros Pendientes","dash_tel_ok":"Sincronizado","dash_tel_off":"Telemetría desactivada","dash_tel_na":"Telemetría no disponible","dash_tel_wait":"Esperando sinc.","dash_ram":"Uso de RAM","dash_fs":"Almacenamiento","dash_metr_lblk":"Bloque Mayor","dash_metr_hmin":"Heap Mín.","dash_metr_hmin_sub":"menor libre","dash_metr_net":"Reconexiones Red","dash_metr_net_sub":"wifi / mqtt","dash_metr_tel":"Telemetría Env.","dash_metr_data":"Datos Telemetría","dash_metr_snr":"Lecturas Sensor","dash_metr_cfg":"Guardados Config","dash_metr_cfg_sub":"a flash","dash_stat":"Estado","dash_gpio":"Slot","dash_type":"Tipo","dash_id":"ID","dash_read":"Lectura","dash_name":"Nombre","dash_load":"Cargando sensores...","dash_disp":"Pantalla","dash_disp_msg":"Haga clic para capturar","dash_disp_cap":"Capturando...","dash_disp_btn":"Capturar","nav_hist":"Historial y Logs","hist_title":"Telemetría","hist_src":"Sensores:","hist_loading":"Cargando...","hist_cal":"Calendario","cal_su":"Dom","cal_mo":"Lun","cal_tu":"Mar","cal_we":"Mié","cal_th":"Jue","cal_fr":"Vie","cal_sa":"Sáb","ch_temp":"Temp","ch_hum":"Hum","ch_press":"Pres","ch_lux":"Luz","ch_unk":"Canal","hist_logs":"Eventos del Sistema","hist_filt":"Filtrar...","hist_load_btn":"Cargar","exp_btn":"Exportar CSV","hist_clear":"Borrar","hist_dtlog":"Fecha y Hora","hist_uptime":"Tiempo","hist_lvl":"Nivel","hist_module":"Módulo","hist_desc":"Descripción","hist_prompt":"Haga clic en 'Cargar' para mostrar los logs.","nav_cfg":"Configuración","cfg_title":"Configuración","cfg_load_fail":"No se pudieron cargar las configuraciones actuales. Los campos están deshabilitados para evitar guardar valores en blanco sobre su configuración.","cfg_retry":"Intentar de nuevo","cfg_gen":"Identidad","cfg_dev":"Nombre","cfg_tz":"Zona Horaria","cfg_log":"Registro Local","cfg_datetime":"Fecha y Hora","cfg_ntp_auto":"Sincronizar automáticamente vía NTP","cfg_date":"Fecha","cfg_time":"Hora","cfg_apply_now":"Aplicar Ahora","cfg_manual_hint":"Usa zona horaria del dispositivo. Guarde y Reinicie para persistir el toggle del NTP.","cfg_hw":"Hardware","cfg_res":"Resolución DS18B20","cfg_r9":"9-bit","cfg_r12":"12-bit","cfg_sint":"Muestreo (ms)","cfg_hint":"Intervalo Historial (min)","cfg_hint_hint":"1 a 1440 min (24h). Predeterminado: 1.","sens_title":"Sensores y GPIO","sens_hint":"GP0–GP15 están disponibles para sensores; de GP16 en adelante pertenecen a la pantalla, al táctil y al zumbador.","sens_slot":"Slot","sens_type":"Tipo","sens_gpios":"GPIOs","sens_hwid":"ID de Hardware","sens_name":"Nombre","sens_add":"+ Agregar slot de sensor","sens_scan":"Buscar sondas","sens_no_free":"Los 16 slots están en uso. Libere uno para agregar otro.","cfg_tel":"Telemetría","cfg_transport":"Transporte","cfg_tr_http":"HTTP(S)","cfg_tr_mqtt":"MQTT(S)","cfg_srv":"IP del Servidor","cfg_port":"Puerto","cfg_sec":"Usar TLS / SSL","cfg_path":"Endpoint","cfg_key":"API Key","cfg_mq_topic":"Tópico","cfg_mq_cid":"Client ID","cfg_mq_user":"Usuario","cfg_mq_pass":"Contraseña","cfg_mq_qos":"QoS","cfg_mq_q0":"0","cfg_mq_ka":"Keep-Alive","cfg_mq_retain":"Retener","cfg_mq_had":"Home Assistant Discovery","cfg_mq_had_hint":"Publica mensajes de configuración retenidos para que Home Assistant cree el dispositivo y sus sensores automáticamente. Requiere payload JSON; las entidades aparecen en el próximo envío.","cfg_tint":"Lote mínimo (registros)","cfg_tint_hint":"Transmite cuando haya esta cantidad de registros esperando. 0 deshabilita la telemetría.","cfg_bat":"Lote máximo (registros)","cfg_tel_disabled":"Telemetría deshabilitada (lote mínimo = 0). Defina un valor para activar.","tel_no_mqtt":"Esta imagen no tiene MQTT, así que la telemetría va por HTTP. La configuración del equipo todavía pide MQTT y no se envía nada: guardar la cambia a HTTP.","tel_no_tls":"Esta imagen no tiene cliente TLS, así que la telemetría va sin cifrar. La configuración del equipo todavía pide cifrado y no se envía nada: guardar lo desactiva (revise también el puerto).","tel_sync":"Enviar ahora","tel_reset":"Restablecer cursor de envío","tel_hint":"Enviar ahora vuelca lo pendiente sin esperar el lote mínimo. Reiniciar cursor hace que el equipo reenvíe hasta 30 días — úselo tras una parada larga del servidor.","cfg_vis":"Constructor","cfg_fmt":"Formato","cfg_f0":"JSON","cfg_f1":"CSV","cfg_f2":"Dinámico","cfg_leg":"Tags","cfg_leg1":"Globales:","cfg_leg2":"Datos:","cfg_leg3":"Claves Inteligentes:","cfg_leg4":"Formatos:","cfg_tpl1":"1. Global","cfg_tpl2":"2. Fila","cfg_tpl3":"3. Separador","cfg_prev":"Vista Previa:","tel_pv_live":"Construida con los datos de este equipo: el nombre, la MAC y las lecturas de ahora. El primer registro muestra un sensor en falla.","al_pv_live":"Un registro de cada código, con los sensores, los límites y la cuenta de este equipo. Los eventos en sí son ejemplos.","cfg_touch_title":"Calibración Táctil","cfg_touch_reset":"Restablecer Calibración","cfg_touch_hint":"Borra la calibración guardada e inicia el asistente en la pantalla — siga allí los pasos indicados.","cfg_reboot_title":"Reiniciar","cfg_reboot_btn":"Reiniciar sin guardar","cfg_reboot_hint":"Reinicia el dispositivo de inmediato. Los cambios sin guardar en esta página se descartan; se mantiene la configuración guardada. El dispositivo queda sin conexión ~10 segundos.","cfg_reboot_confirm":"¿Reiniciar ahora SIN guardar?\n\nLos cambios sin guardar en esta página se perderán.\nEl dispositivo quedará sin conexión ~10 segundos.","cfg_reboot_fail":"No se pudo reiniciar.","cfg_slog":"Syslog Remoto (Auditoría)","cfg_slog_hint":"Reenvía registros a un colector syslog (RFC 5424 / UDP), manteniendo la auditoría fuera del dispositivo. Usa la IPv4 del colector en la red local (no un hostname). Sin garantía de entrega.","cfg_slog_en":"Activar reenvío syslog","cfg_slog_srv":"IP del Colector","cfg_slog_port":"Puerto UDP","cfg_slog_lvl":"Nivel Mínimo","nav_net":"Red","net_curr":"Estado de Conexión","net_stat":"Estado","net_ip":"IP","net_mask":"Máscara","net_gw":"Gateway","net_dns":"DNS","net_mac":"MAC","net_cfg":"Configuración de Red","net_wifi":"Wi-Fi","net_ssid":"SSID","net_pass":"Contraseña","net_scan":"Buscar","net_scanning":"Buscando...","net_scan_busy":"Radio ocupada — inténtelo de nuevo en unos instantes","net_scan_fail":"La búsqueda falló","net_scan_none":"No se encontró ninguna red","net_pass_show":"Mostrar contraseña","net_pass_hide":"Ocultar contraseña","net_secured":"Red protegida","net_open":"Red abierta","net_ipv4":"IPv4","net_dhcp":"DHCP","net_sip":"IP Estático","net_dns_title":"Configuración de DNS","net_dns_auto":"Obtener DNS automáticamente (DHCP)","net_dns1":"DNS Primario","net_dns2":"DNS Secundario","net_ntp_title":"Servidor de Hora (NTP)","net_ntp_disabled":"NTP está deshabilitado.","net_ntp_goto_cfg":"Habilite en Configuración del Sistema →","net_ntp_lbl":"Dirección del Servidor","net_ntp_hint":"Deje vacío para usar el predeterminado (pool.ntp.org)","net_web_title":"Servidor Web","net_web_port":"Puerto HTTP","net_web_port_hint":"Predeterminado: 80. Después de guardar, el navegador redirige automáticamente al nuevo puerto.","net_web_ka":"Conexiones persistentes (keep-alive)","net_web_ka_hint":"Reutiliza la conexión TLS entre solicitudes — las páginas cargan ~60% más rápido por HTTPS. Desactívalo solo si un proxy o cliente no funciona bien con conexiones persistentes.","nav_usr":"Usuarios","usr_mgt":"Gestión de Accesos","usr_usr":"Usuario","usr_perm":"Permisos","usr_act":"Acciones","usr_add":"Agregar","usr_name":"Nombre","usr_pdash":"Panel","usr_phist":"Historial","usr_plog":"Logs","usr_psys":"Sistema","usr_pnet":"Red","usr_pfr":"Lectura","usr_pfu":"Upload","usr_pfd":"Eliminar","usr_pusr":"Usuarios","usr_pcal":"Calibración","usr_btn":"Crear","usr_warn":"Se muestra una contraseña de un solo uso al guardar. Cópiela — solo se muestra una vez, y el usuario debe cambiarla en el primer inicio.","usr_sup":"Super","usr_prot":"Protegido","usr_rst":"Reset","usr_del":"Eliminar","nav_file":"Archivos","fil_title":"Sistema de Archivos","fil_down":"Descargar","fil_backup":"Respaldo","fil_restore":"Restaurar","fil_fw":"Firmware","fil_del":"Eliminar","fil_uphere":"Subir Aquí","fil_name":"Nombre","fil_sz":"Tamaño","fil_loading":"Cargando...","nav_alm":"Alarmas y Sonidos","alm_title":"Alarmas y Sonidos","alm_limits":"Límites de Alarma","alm_none":"Ningún sensor configurado.","alm_sounds":"Configuración de Sonidos","alm_touch":"Toque","alm_confirm":"Confirmación","alm_mel_asc":"Ascendente","alm_error":"Error","alm_mel_desc":"Descendente","alm_alarm":"Alarma","alm_mel_siren":"Sirena","alm_web":"Sonidos Web","alm_attention":"Atención","alm_mute":"Silencio Global","alm_volume":"Vol. Sistema","alm_alarm_vol":"Vol. Alarma","nav_lic":"Licencia","lic_title":"Licencia de Software","lic_mit":"Licencia MIT","lic_notice":"Avisos de Terceros","lic_sub":"Software libre. Abajo, lo que eso significa en la práctica.","lic_summary_title":"En resumen","lic_summary":"Puedes usar, copiar, modificar, fusionar, publicar, distribuir, sublicenciar y vender copias de este software, gratis y sin restricción.\n\nLa única obligación es mantener el aviso de copyright y el aviso de permiso en todas las copias.\n\nEl software se entrega \"tal cual\", sin garantía de ningún tipo. El autor no responde por ningún daño derivado de su uso.","lic_summary_note":"Resumen informativo, escrito para entenderse. No sustituye el texto de la licencia.","lic_legal_note":"El texto de abajo queda en inglés porque es la versión original — es la que tiene valor legal.","greet_logout":"Salir","log_lock":"Bloqueado por {s}s. Intentos excesivos.","log_full":"Límite de sesiones alcanzado. Intente más tarde.","log_err":"Usuario o contraseña incorrectos.","log_cookie_loop":"Sesión iniciada, pero el navegador conserva una sesión segura de una visita HTTPS anterior. Abra una ventana privada o borre las cookies de este sitio e inicie sesión de nuevo.","log_chpass_mismatch":"Las contraseñas no coinciden.","log_chpass_same":"La nueva contraseña debe ser distinta de la actual.","log_chpass_ok":"Contraseña cambiada. Inicie sesión.","dash_ntp_ok":"Sincronizado","dash_ntp_fail":"Fallo NTP","dash_ntp_conn":"Conectando...","dash_eval":"Evaluando...","dash_metr_min":"min","dash_metr_max":"max","dash_metr_ok":"ok","dash_metr_fail":"fallos","dash_metr_retry":"retry","dash_metr_last":"últ.","dash_metr_err":"error","dash_free":"Libre","dash_tot":"Total","greet_hello":"Hola","greet_morning":"Buenos días","greet_afternoon":"Buenas tardes","greet_evening":"Buenas noches","hist_none_sel":"Ninguno","hist_n_sel":"seleccionados","m_jan":"Ene","m_feb":"Feb","m_mar":"Mar","m_apr":"Abr","m_may":"May","m_jun":"Jun","m_jul":"Jul","m_aug":"Ago","m_sep":"Sep","m_oct":"Oct","m_nov":"Nov","m_dec":"Dic","hist_done":"Completado","hist_down_msg":"Cargando...","hist_err_json":"Error.","hist_no_data":"Sin datos.","hist_conn_lost":"Conexión perdida.","hist_ref":"Actualizar","hist_none":"Ningún evento.","hist_clear_msg":"¿Borrar logs?","display_busy":"Pantalla en uso. Intente nuevamente en unos segundos.","hist_clear_err":"Fallo al borrar logs.","hist_cleared":"Logs borrados.","net_conn_err":"Error de conexión.","exp_no_chart":"Cargue un intervalo en el gráfico","exp_cancelled":"Cancelado","exp_empty":"Sin datos en este período","exp_logs_empty":"Haga clic en 'Cargar' primero","exp_rows":"filas","cfg_sec_mqtt":"Usar MQTTS (TLS)","cfg_time_need":"Complete fecha y hora.","cfg_time_ok":"Hora aplicada.","cfg_time_fail":"Fallo al aplicar.","cfg_touch_confirm":"¿Restablecer la calibración táctil a los valores de fábrica?","cfg_touch_done":"Calibración restablecida. Recalibre por display.","sens_load_fail":"No se pudieron cargar los slots de sensores.","sens_free":"libre","sens_active":"Activo","sens_none":"Ningún slot de sensor configurado todavía.","sens_empty":"vacío","sens_hwid_hint":"Clave usada por el historial y por la telemetría. Cambiarla inicia una serie nueva.","sens_state":"Estado","sens_alarms":"Alarmas habilitadas","sens_pins":"Asignación de GPIO","sens_pin":"Pin","sens_calib":"Calibración","sens_ntp_no":"NTP no sincronizado.","sens_hist":"Grabación en el historial","sens_rebind_hint":"Un slot agregado o renombrado hoy no tiene columna en el archivo de historial del día, así que no se graba hasta mañana. Esto reescribe ese archivo para todos los slots, conservando los registros ya tomados. El dispositivo se reinicia al final.","sens_rebind":"Recomponer el historial ahora","sens_hw":"Hardware","sens_accept_hint":"Vuelve a leer la sonda conectada a este slot y vincula el slot a ella. Úselo tras cambiar un DS18B20, cuando el dispositivo informe discrepancia de hardware.","sens_accept":"Adoptar la sonda conectada aquí","sens_wipe_hint":"Marca todo lo grabado antes de ahora como perteneciente al sensor anterior. Los registros siguen en el disco; lo que cambia es que los gráficos dejan de atribuirlos a este slot.","sens_wipe":"Restablecer época del historial","sens_free_slot":"Liberar slot","sens_revert":"Descartar cambios","sens_done":"Completado","sens_stage_hint":"Las ediciones se acumulan mientras escribe y se graban al Guardar y Reiniciar.","sens_scan_busy":"Buscando...","sens_scan_none":"Ninguna sonda respondió.","sens_scan_found":"encontrada(s)","act_fail":"Fallo en la acción.","sens_accept_ok":"Slot vinculado a la sonda encontrada en él.","sens_accept_none":"Ninguna sonda respondió en este GPIO.","sens_accept_bad":"La sonda respondió con una ROM inválida.","sens_wipe_confirm":"¿Restablecer la época del historial de este slot?\n\nLos registros tomados antes de ahora dejan de atribuírsele. No se borra nada.","sens_wipe_ok":"Época del historial restablecida.","tel_sync_ok":"Envío disparado. Siga el resultado en el registro.","tel_reset_confirm":"¿Restablecer el cursor de telemetría?\n\nEl dispositivo reenviará hasta 30 días de historial en el próximo envío. Espere una ráfaga de tráfico.","tel_reset_ok":"Cursor restablecido. Los próximos envíos cubren hasta 30 días atrás.","sens_rebind_pending":"Guarde y Reinicie antes — recomponer ahora usaría la configuración anterior.","sens_rebind_confirm":"¿Reescribir el archivo de historial del día para los slots actuales?\n\nLos registros ya tomados se conservan. El dispositivo se reinicia al final.","sens_rebind_busy":"Reescribiendo...","sens_rebind_ok_forced":"Historial reconstruido","sens_rebind_meas":"mediciones","sens_rebind_ok":"Historial reescrito","sens_rebind_recs":"registros conservados","sens_rebind_reboot":"Reiniciando...","sens_rebind_force":"El archivo de historial del día no se pudo leer, así que los registros no se pueden aprovechar.\n\n¿Recrearlo vacío? Los registros de hoy se pierden. Los días anteriores quedan intactos.","sens_rebind_kept":"Nada cambió.","sens_rebind_err":"Fallo al reescribir","sens_rebind_unsure":"Sin respuesta del dispositivo. Recargue la página y revise el log antes de intentarlo de nuevo.","sens_need_type":"Elija un tipo antes de activar este slot.","sens_need_pin":"Asigne un GPIO al pin","sens_taken":"ya está en uso por el slot","sens_clear_confirm":"¿Liberar este slot? Se borran su tipo, GPIOs e identificación.","net_conn":"Conectado","net_off":"Desconectado","usr_pend_del":"Pendiente: Eliminar","usr_pend_rst":"Pendiente: Reset","usr_pend_add":"Pendiente: Nuevo","usr_del_msg":"¿Eliminar?","usr_rst_msg":"¿Forzar reset?","fil_parent":"Arriba","fil_empty":"Vacío","fil_protected":"Archivo protegido","fil_folder":"Carpeta","fil_uploaded":"Subida completada.","fil_up_err":"Error al subir.","fil_sel_del":"Seleccione","fil_conf_del":"¿Eliminar N?","fil_del_err":"Algunos archivos no pudieron ser eliminados.","fil_deleted":"Archivos eliminados.","fil_sel_down":"Seleccione","fil_conf_down":"¿Descargar N?","fil_backup_started":"Descarga del respaldo iniciada.","fil_rst_val":"Paso 1/3: Validando el respaldo (CRC + chip ID)...","fil_rst_val_fail":"Falló la validación: ","fil_rst_confirm":"Respaldo válido — N archivos (X bytes).\n\nEsto SOBRESCRIBE los archivos actuales del dispositivo (history, configs, sensores, themes).\nSe restaurarán la configuración de Wi-Fi y la contraseña de admin del respaldo.\n\n¿Continuar con la restauración?","fil_rst_app":"Paso 2/3: Aplicando la restauración (puede tardar ~15 s)...","fil_rst_app_fail":"Fallo al aplicar: ","fil_rst_reb":"Paso 3/3: Reiniciando el dispositivo (~25 s)...","fil_rst_off":"Dispositivo sin conexión tras 90 s. Verifique la conexión y enciéndalo manualmente si hace falta.","fil_fw_warn":"Actualización de firmware vía OTA.\n\nLa partición de archivos se REFORMATEA — el área de staging comparte la partición con ella.\n\nSolo la configuración sobrevive (capturada antes, restaurada después):\n • Wi-Fi, contraseña de administrador, telemetría\n • Mapeo de sensores y límites de alarma\n\nTodo lo demás en el dispositivo se pierde:\n • Historial de lecturas (/history)\n • Paquetes de idioma (/lang)\n • Temas personalizados (/themes)\n • Calibración táctil (/calib)\n • Archivos en /web\n • El registro de eventos\n\nUn respaldo .bkp se descarga automáticamente — restáurelo después para recuperar todo.\n\n¿Continuar?","fil_fw_warn_slot":"Actualización de firmware vía OTA.\n\nLa imagen nueva va al slot desde el que el equipo no arrancó. Los archivos del equipo quedan como están.\n\nTras el reinicio, la versión nueva funciona a prueba. Se queda cuando haya funcionado sana durante un minuto. Si no lo hace en cinco minutos, o si el equipo se reinicia antes, vuelve a la versión que ejecuta ahora.\n\nPrimero se descarga un respaldo .bkp.\n\n¿Continuar?","fil_fw_need_bin":"Seleccione un archivo .bin de firmware","fil_rst_need_bkp":"Seleccione un archivo .bkp de respaldo","fil_fw_bk":"Paso 1/4: Descargando el respaldo .bkp...","fil_fw_bk_fail":"Fallo al descargar el respaldo. Intente nuevamente.","fil_fw_bk_corrupt":"Respaldo corrupto (CRC). Abortado.","fil_fw_apply":"Respaldo guardado en su computadora.\n\n¿Iniciar la actualización?\n • Subida del firmware: ~30 s\n • Aplicación + reinicio: ~25 s\n • Arranque completo: ~25 s\n\nTotal: ~80 s. No apague el dispositivo durante el proceso.","fil_fw_up":"Paso 2/4: Subiendo el firmware (~30 s)...","fil_fw_stage_fail":"Fallo en el envío (validación v=","fil_fw_stage_refused":"Envío rechazado (HTTP ","fil_fw_unsigned":"Este .bin no está firmado, y el equipo solo instala imágenes firmadas. Use un .bin de release de GitHub o una compilación del configurador.","fil_fw_v7":"La imagen es de otro modelo (env ","fil_fw_v8":"La imagen no está firmada. Use un .bin de release de GitHub o una compilación del configurador.","fil_fw_v9":"La firma no coincide: el archivo cambió después de firmarse, o lo firmó una clave en la que este equipo no confía. Descargue el .bin de nuevo.","fil_fw_v10":"Se firmó con una clave retirada. Use una release más reciente.","fil_fw_v11":"Está por debajo del nivel de seguridad instalado, y volver a ella por el aire está bloqueado. Use esta versión o una más reciente.","fil_fw_v12":"Se firmó con la clave del banco de pruebas, que este equipo no acepta. Use un .bin de release de GitHub.","fil_fw_app":"Paso 3/4: Aplicando el firmware...","fil_fw_reb":"Paso 4/4: Esperando el arranque (~25 s)...","fw_trial":"Esta versión está a prueba: se queda cuando haya funcionado sana durante un minuto. Reiniciar el equipo antes trae de vuelta la versión anterior.","fw_reverted":"La actualización a la v{v} no se confirmó y el equipo volvió a esta versión (código 613 en el registro de eventos).","pending_notice":"Haga clic en \"Guardar y Reiniciar\" para aplicar los cambios.","commit_confirm":"Esto guardará todos los cambios y reiniciará el sistema.\n\nEl dispositivo quedará offline por ~10 segundos.\nCualquier grabación de historial/log en curso será interrumpida.\n\n¿Continuar?","calib_err":"Error de calibración: ","commit_btn":"Guardar y reiniciar","calib_timeout":"Tiempo agotado en la calibración. Intente nuevamente.","net_saved_port":"Guardado. Redirigiendo al nuevo puerto...","commit_saved":"¡Guardado! Reiniciando sistema...","commit_err":"Fallo al guardar.","commit_apply":"Aplicar ahora","commit_try":"Probar","commit_live":"No requiere reinicio","commit_why":"Reinicia por","commit_pend":"Cambios sin guardar","commit_applied":"Aplicado y guardado","commit_tried":"Aplicado SIN guardar — un reinicio lo deshace","commit_rej":"Campos no aplicados","cal_pts_hint":"Hasta 5 puntos por magnitud, cada uno ligando la lectura bruta al valor mostrado por un instrumento confiable. Un punto aplica un offset constante; más puntos doblan la corrección entre ellos, recta más allá de los extremos. Deje el bruto vacío para capturar la lectura al guardar.","cal_raw":"Bruto","cal_ref":"Referencia","cal_corr":"corregido","cal_cap":"Usar la lectura bruta actual","cal_del":"Quitar este punto","cal_add":"Añadir punto","cal_clear":"Quitar corrección","cal_none":"Sin corrección — valor del sensor.","cal_legacy":"Offset constante","cal_save_hint":"Las correcciones se graban en Guardar y Reiniciar. Requiere NTP sincronizado.","cal_err_max":"como máximo 5 puntos.","cal_err_num":"cada punto necesita bruto y referencia numéricos.","cal_err_noread":"sin lectura en vivo para capturar — rellene el valor bruto.","cal_err_rng":"punto fuera del rango plausible","cal_err_dup":"dos puntos con el mismo valor bruto.","cal_mode_lbl":"Interpolación","cal_mode_lin":"Recta","cal_mode_cub":"Suave","cal_mode_hint":"Suave es una cúbica monótona: dobla por las anclas sin jamás sobrepasarlas. Necesita 3+ puntos; con menos se comporta como recta.","fil_fw_apply_err":"Fallo al aplicar: ","fil_fw_apply_fail":"Aplicación rechazada (HTTP ","fil_view":"Ver","tv_org":"Organizado","tv_raw":"Original","tv_exp":"Expandir todo","tv_col":"Contraer todo","tv_close":"Cerrar","tv_bad":"JSON incompleto o no válido: organizado solo por sus llaves y corchetes. El texto exacto está en Original.","tv_bin":"Este archivo no es texto. Descárguelo para abrirlo.","tv_err":"No se pudo leer el archivo.","hist_cancel":"Cancelar","hist_lost":"perdidas","hist_partial":"Parcial: ","cfg_tls_noverify":"TLS sin validación de certificado — la conexión está cifrada pero no autenticada (MITM posible). Suba /cert.pem en Archivos para validar el servidor.","creds_title":"Contraseña temporal","creds_warn":"Cópiela ahora — se muestra una sola vez. El usuario debe cambiarla en el primer inicio de sesión.","creds_ok":"La guardé — recargar","hist_files":"Archivos","nav_tel":"Telemetría","tel_title":"Telemetría","tel_desc":"Transporte, formatos de payload y la segunda línea (alarmas). Guardar y Reiniciar aplica los cambios pendientes; Enviar ahora / Restablecer cursor actúan sobre el dispositivo en ejecución.","al_title":"Payload de Alarmas — 2ª Línea de Telemetría","al_hint":"Línea separada para alarmas de sensores. Los registros permanecen en cola en RAM y solo se eliminan cuando el servidor confirma la recepción (HTTP 2xx o ACK MQTT en /alarm/ack). El transporte, el servidor, las credenciales y TLS siguen la configuración de la telemetría principal.","al_pending":"Pendientes:","al_en":"Habilitar línea de telemetría de alarmas","al_qmax":"Tamaño de cola (RAM)","al_path":"Ruta HTTP","al_leg":"Etiquetas de la línea de alarmas (un registro por borde de límite/error):","al_smart":"Claves inteligentes","al_smart2":"(nombre de clave = token, minúsculas o mayúsculas):","al_smart_val":"número, o la clave completa se elimina en caso de fallo","al_smart_err":"código, o la clave completa se elimina cuando el otro dominio está activo","cfg_bat_hint":"Máximo de registros por envío. Una cola mayor sale en lotes de este tamaño hasta agotarse.","dash_disp_live":"En vivo","dash_disp_stop":"Detener","dash_disp_err":"Fallo de lectura","dash_disp_tap":"Clic para tocar, mantén para pulsación larga","dash_disp_taperr":"Toque rechazado","usr_plim":"Límites (panel)","usr_pblk":"Bloqueo (panel)","usr_pmnt":"Mant. (panel)","usr_pin":"PIN del panel (opcional)","usr_pin_btn":"PIN","usr_haspin":"PIN","usr_pend_pin":"Pendiente: PIN","usr_pin_msg":"Vacío elimina el PIN.","usr_pin_bad":"PIN: %M a %X de %S.","cfg_pinkb":"Teclado del panel: glifos por tecla","cfg_pinalpha":"Caracteres del PIN","cfg_pinmin":"Tamaño mínimo del PIN","cfg_ct":"Encabezado Content-Type"} +{"log_usr":"Usuario","log_pas":"Contraseña","log_show":"Mostrar contraseña","log_btn":"Entrar","log_chpass_link":"Cambiar contraseña","log_chpass_title":"Cambiar Contraseña","log_oldpass":"Contraseña Actual","log_newpass":"Nueva Contraseña","log_chpass_req_len":"Mínimo 8 caracteres","log_chpass_req_letter":"Letra","log_chpass_req_digit":"Dígito","log_chpass_req_symbol":"Símbolo","log_newpass2":"Repetir Nueva Contraseña","log_chpass_btn":"Guardar Nueva Contraseña","log_chpass_back":"← Volver al Login","fcp_wel":"¡Bienvenido!","fcp_msg":"Defina una nueva contraseña fuerte para acceder al sistema.","fcp_btn":"Guardar e Iniciar Sesión","nav_dash":"Panel de Control","dash_dt":"Fecha y Hora","dash_ntp_wait":"Esperando...","dash_up":"Tiempo Activo","dash_wifi":"Señal Wi-Fi","dash_pend":"Registros Pendientes","dash_tel_ok":"Sincronizado","dash_tel_off":"Telemetría desactivada","dash_tel_na":"Telemetría no disponible","dash_tel_wait":"Esperando sinc.","dash_ram":"Uso de RAM","dash_fs":"Almacenamiento","dash_metr_lblk":"Bloque Mayor","dash_metr_hmin":"Heap Mín.","dash_metr_hmin_sub":"menor libre","dash_metr_net":"Reconexiones Red","dash_metr_net_sub":"wifi / mqtt","dash_metr_tel":"Telemetría Env.","dash_metr_data":"Datos Telemetría","dash_metr_snr":"Lecturas Sensor","dash_metr_cfg":"Guardados Config","dash_metr_cfg_sub":"a flash","dash_stat":"Estado","dash_gpio":"Slot","dash_type":"Tipo","dash_id":"ID","dash_read":"Lectura","dash_name":"Nombre","dash_load":"Cargando sensores...","dash_disp":"Pantalla","dash_disp_msg":"Haga clic para capturar","dash_disp_cap":"Capturando...","dash_disp_btn":"Capturar","nav_hist":"Historial y Logs","hist_title":"Telemetría","hist_src":"Sensores:","hist_loading":"Cargando...","hist_cal":"Calendario","cal_su":"Dom","cal_mo":"Lun","cal_tu":"Mar","cal_we":"Mié","cal_th":"Jue","cal_fr":"Vie","cal_sa":"Sáb","ch_temp":"Temp","ch_hum":"Hum","ch_press":"Pres","ch_lux":"Luz","ch_unk":"Canal","hist_logs":"Eventos del Sistema","hist_filt":"Filtrar...","hist_load_btn":"Cargar","exp_btn":"Exportar CSV","hist_clear":"Borrar","hist_dtlog":"Fecha y Hora","hist_uptime":"Tiempo","hist_lvl":"Nivel","hist_module":"Módulo","hist_desc":"Descripción","hist_prompt":"Haga clic en 'Cargar' para mostrar los logs.","nav_cfg":"Configuración","cfg_title":"Configuración","cfg_load_fail":"No se pudieron cargar las configuraciones actuales. Los campos están deshabilitados para evitar guardar valores en blanco sobre su configuración.","cfg_retry":"Intentar de nuevo","cfg_gen":"Identidad","cfg_dev":"Nombre","cfg_tz":"Zona Horaria","cfg_log":"Registro Local","cfg_datetime":"Fecha y Hora","cfg_ntp_auto":"Sincronizar automáticamente vía NTP","cfg_date":"Fecha","cfg_time":"Hora","cfg_apply_now":"Aplicar Ahora","cfg_manual_hint":"Usa zona horaria del dispositivo. Guarde y Reinicie para persistir el toggle del NTP.","cfg_hw":"Hardware","cfg_res":"Resolución DS18B20","cfg_r9":"9-bit","cfg_r12":"12-bit","cfg_sint":"Muestreo (ms)","cfg_hint":"Intervalo Historial (min)","cfg_hint_hint":"1 a 1440 min (24h). Predeterminado: 1.","sens_title":"Sensores y GPIO","sens_hint":"GP0–GP15 están disponibles para sensores; de GP16 en adelante pertenecen a la pantalla, al táctil y al zumbador.","sens_slot":"Slot","sens_type":"Tipo","sens_gpios":"GPIOs","sens_hwid":"ID de Hardware","sens_name":"Nombre","sens_add":"+ Agregar slot de sensor","sens_scan":"Buscar sondas","sens_no_free":"Los 16 slots están en uso. Libere uno para agregar otro.","cfg_tel":"Telemetría","cfg_transport":"Transporte","cfg_tr_http":"HTTP(S)","cfg_tr_mqtt":"MQTT(S)","cfg_srv":"IP del Servidor","cfg_port":"Puerto","cfg_sec":"Usar TLS / SSL","cfg_path":"Endpoint","cfg_key":"API Key","cfg_mq_topic":"Tópico","cfg_mq_cid":"Client ID","cfg_mq_user":"Usuario","cfg_mq_pass":"Contraseña","cfg_mq_qos":"QoS","cfg_mq_q0":"0","cfg_mq_ka":"Keep-Alive","cfg_mq_retain":"Retener","cfg_mq_had":"Home Assistant Discovery","cfg_mq_had_hint":"Publica mensajes de configuración retenidos para que Home Assistant cree el dispositivo y sus sensores automáticamente. Requiere payload JSON; las entidades aparecen en el próximo envío.","cfg_tint":"Lote mínimo (registros)","cfg_tint_hint":"Transmite cuando haya esta cantidad de registros esperando. 0 deshabilita la telemetría.","cfg_bat":"Lote máximo (registros)","cfg_tel_disabled":"Telemetría deshabilitada (lote mínimo = 0). Defina un valor para activar.","tel_no_mqtt":"Esta imagen no tiene MQTT, así que la telemetría va por HTTP. La configuración del equipo todavía pide MQTT y no se envía nada: guardar la cambia a HTTP.","tel_no_tls":"Esta imagen no tiene cliente TLS, así que la telemetría va sin cifrar. La configuración del equipo todavía pide cifrado y no se envía nada: guardar lo desactiva (revise también el puerto).","tel_sync":"Enviar ahora","tel_reset":"Restablecer cursor de envío","tel_hint":"Enviar ahora vuelca lo pendiente sin esperar el lote mínimo. Reiniciar cursor hace que el equipo reenvíe hasta 30 días — úselo tras una parada larga del servidor.","cfg_vis":"Constructor","cfg_fmt":"Formato","cfg_f0":"JSON","cfg_f1":"CSV","cfg_f2":"Dinámico","cfg_leg":"Tags","cfg_leg1":"Globales:","cfg_leg2":"Datos:","cfg_leg3":"Claves Inteligentes:","cfg_leg4":"Formatos:","cfg_tpl1":"1. Global","cfg_tpl2":"2. Fila","cfg_tpl3":"3. Separador","cfg_prev":"Vista Previa:","tel_pv_live":"Construida con los datos de este equipo: el nombre, la MAC y las lecturas de ahora. El primer registro muestra un sensor en falla.","al_pv_live":"Un registro de cada código, con los sensores, los límites y la cuenta de este equipo. Los eventos en sí son ejemplos.","cfg_touch_title":"Calibración Táctil","cfg_touch_reset":"Restablecer Calibración","cfg_touch_hint":"Borra la calibración guardada e inicia el asistente en la pantalla — siga allí los pasos indicados.","cfg_reboot_title":"Reiniciar","cfg_reboot_btn":"Reiniciar sin guardar","cfg_reboot_hint":"Reinicia el dispositivo de inmediato. Los cambios sin guardar en esta página se descartan; se mantiene la configuración guardada. El dispositivo queda sin conexión ~10 segundos.","cfg_reboot_confirm":"¿Reiniciar ahora SIN guardar?\n\nLos cambios sin guardar en esta página se perderán.\nEl dispositivo quedará sin conexión ~10 segundos.","cfg_reboot_fail":"No se pudo reiniciar.","cfg_slog":"Syslog Remoto (Auditoría)","cfg_slog_hint":"Reenvía registros a un colector syslog (RFC 5424 / UDP), manteniendo la auditoría fuera del dispositivo. Usa la IPv4 del colector en la red local (no un hostname). Sin garantía de entrega.","cfg_slog_en":"Activar reenvío syslog","cfg_slog_srv":"IP del Colector","cfg_slog_port":"Puerto UDP","cfg_slog_lvl":"Nivel Mínimo","nav_net":"Red","net_curr":"Estado de Conexión","net_stat":"Estado","net_ip":"IP","net_mask":"Máscara","net_gw":"Gateway","net_dns":"DNS","net_mac":"MAC","net_cfg":"Configuración de Red","net_wifi":"Wi-Fi","net_ssid":"SSID","net_pass":"Contraseña","net_scan":"Buscar","net_scanning":"Buscando...","net_scan_busy":"Radio ocupada — inténtelo de nuevo en unos instantes","net_scan_fail":"La búsqueda falló","net_scan_none":"No se encontró ninguna red","net_pass_show":"Mostrar contraseña","net_pass_hide":"Ocultar contraseña","net_secured":"Red protegida","net_open":"Red abierta","net_ipv4":"IPv4","net_dhcp":"DHCP","net_sip":"IP Estático","net_dns_title":"Configuración de DNS","net_dns_auto":"Obtener DNS automáticamente (DHCP)","net_dns1":"DNS Primario","net_dns2":"DNS Secundario","net_ntp_title":"Servidor de Hora (NTP)","net_ntp_disabled":"NTP está deshabilitado.","net_ntp_goto_cfg":"Habilite en Configuración del Sistema →","net_ntp_lbl":"Dirección del Servidor","net_ntp_hint":"Deje vacío para usar el predeterminado (pool.ntp.org)","net_web_title":"Servidor Web","net_web_port":"Puerto HTTP","net_web_port_hint":"Predeterminado: 80. Después de guardar, el navegador redirige automáticamente al nuevo puerto.","net_web_ka":"Conexiones persistentes (keep-alive)","net_web_ka_hint":"Reutiliza la conexión TLS entre solicitudes — las páginas cargan ~60% más rápido por HTTPS. Desactívalo solo si un proxy o cliente no funciona bien con conexiones persistentes.","nav_usr":"Usuarios","usr_mgt":"Gestión de Accesos","usr_usr":"Usuario","usr_perm":"Permisos","usr_act":"Acciones","usr_add":"Agregar","usr_name":"Nombre","usr_pdash":"Panel","usr_phist":"Historial","usr_plog":"Logs","usr_psys":"Sistema","usr_pnet":"Red","usr_pfr":"Lectura","usr_pfu":"Upload","usr_pfd":"Eliminar","usr_pusr":"Usuarios","usr_pcal":"Calibración","usr_btn":"Crear","usr_warn":"Se muestra una contraseña de un solo uso al guardar. Cópiela — solo se muestra una vez, y el usuario debe cambiarla en el primer inicio.","usr_sup":"Super","usr_prot":"Protegido","usr_rst":"Reset","usr_del":"Eliminar","nav_file":"Archivos","fil_title":"Sistema de Archivos","fil_down":"Descargar","fil_backup":"Respaldo","fil_restore":"Restaurar","fil_fw":"Firmware","fil_del":"Eliminar","fil_uphere":"Subir Aquí","fil_name":"Nombre","fil_sz":"Tamaño","fil_loading":"Cargando...","nav_alm":"Alarmas y Sonidos","alm_title":"Alarmas y Sonidos","alm_limits":"Límites de Alarma","alm_none":"Ningún sensor configurado.","alm_sounds":"Configuración de Sonidos","alm_touch":"Toque","alm_confirm":"Confirmación","alm_mel_asc":"Ascendente","alm_error":"Error","alm_mel_desc":"Descendente","alm_alarm":"Alarma","alm_mel_siren":"Sirena","alm_web":"Sonidos Web","alm_attention":"Atención","alm_mute":"Silencio Global","alm_volume":"Vol. Sistema","alm_alarm_vol":"Vol. Alarma","nav_lic":"Licencia","lic_title":"Licencia de Software","lic_mit":"Licencia MIT","lic_notice":"Avisos de Terceros","lic_sub":"Software libre. Abajo, lo que eso significa en la práctica.","lic_summary_title":"En resumen","lic_summary":"Puedes usar, copiar, modificar, fusionar, publicar, distribuir, sublicenciar y vender copias de este software, gratis y sin restricción.\n\nLa única obligación es mantener el aviso de copyright y el aviso de permiso en todas las copias.\n\nEl software se entrega \"tal cual\", sin garantía de ningún tipo. El autor no responde por ningún daño derivado de su uso.","lic_summary_note":"Resumen informativo, escrito para entenderse. No sustituye el texto de la licencia.","lic_legal_note":"El texto de abajo queda en inglés porque es la versión original — es la que tiene valor legal.","greet_logout":"Salir","log_lock":"Bloqueado por {s}s. Intentos excesivos.","log_full":"Límite de sesiones alcanzado. Intente más tarde.","log_err":"Usuario o contraseña incorrectos.","log_cookie_loop":"Sesión iniciada, pero el navegador conserva una sesión segura de una visita HTTPS anterior. Abra una ventana privada o borre las cookies de este sitio e inicie sesión de nuevo.","log_chpass_mismatch":"Las contraseñas no coinciden.","log_chpass_same":"La nueva contraseña debe ser distinta de la actual.","log_chpass_ok":"Contraseña cambiada. Inicie sesión.","dash_ntp_ok":"Sincronizado","dash_ntp_fail":"Fallo NTP","dash_ntp_conn":"Conectando...","dash_eval":"Evaluando...","dash_metr_min":"min","dash_metr_max":"max","dash_metr_ok":"ok","dash_metr_fail":"fallos","dash_metr_retry":"retry","dash_metr_last":"últ.","dash_metr_err":"error","dash_free":"Libre","dash_tot":"Total","greet_hello":"Hola","greet_morning":"Buenos días","greet_afternoon":"Buenas tardes","greet_evening":"Buenas noches","hist_none_sel":"Ninguno","hist_n_sel":"seleccionados","m_jan":"Ene","m_feb":"Feb","m_mar":"Mar","m_apr":"Abr","m_may":"May","m_jun":"Jun","m_jul":"Jul","m_aug":"Ago","m_sep":"Sep","m_oct":"Oct","m_nov":"Nov","m_dec":"Dic","hist_done":"Completado","hist_down_msg":"Cargando...","hist_err_json":"Error.","hist_no_data":"Sin datos.","hist_conn_lost":"Conexión perdida.","hist_ref":"Actualizar","hist_none":"Ningún evento.","hist_clear_msg":"¿Borrar logs?","display_busy":"Pantalla en uso. Intente nuevamente en unos segundos.","hist_clear_err":"Fallo al borrar logs.","hist_cleared":"Logs borrados.","net_conn_err":"Error de conexión.","exp_no_chart":"Cargue un intervalo en el gráfico","exp_cancelled":"Cancelado","exp_empty":"Sin datos en este período","exp_logs_empty":"Haga clic en 'Cargar' primero","exp_rows":"filas","cfg_sec_mqtt":"Usar MQTTS (TLS)","cfg_time_need":"Complete fecha y hora.","cfg_time_ok":"Hora aplicada.","cfg_time_fail":"Fallo al aplicar.","cfg_touch_confirm":"¿Restablecer la calibración táctil a los valores de fábrica?","cfg_touch_done":"Calibración restablecida. Recalibre por display.","sens_load_fail":"No se pudieron cargar los slots de sensores.","sens_free":"libre","sens_active":"Activo","sens_none":"Ningún slot de sensor configurado todavía.","sens_empty":"vacío","sens_hwid_hint":"Clave usada por el historial y por la telemetría. Cambiarla inicia una serie nueva.","sens_state":"Estado","sens_alarms":"Alarmas habilitadas","sens_pins":"Asignación de GPIO","sens_pin":"Pin","sens_calib":"Calibración","sens_ntp_no":"NTP no sincronizado.","sens_hist":"Grabación en el historial","sens_rebind_hint":"Un slot agregado o renombrado hoy no tiene columna en el archivo de historial del día, así que no se graba hasta mañana. Esto reescribe ese archivo para todos los slots, conservando los registros ya tomados. El dispositivo se reinicia al final.","sens_rebind":"Recomponer el historial ahora","sens_hw":"Hardware","sens_accept_hint":"Vuelve a leer la sonda conectada a este slot y vincula el slot a ella. Úselo tras cambiar un DS18B20, cuando el dispositivo informe discrepancia de hardware.","sens_accept":"Adoptar la sonda conectada aquí","sens_wipe_hint":"Marca todo lo grabado antes de ahora como perteneciente al sensor anterior. Los registros siguen en el disco; lo que cambia es que los gráficos dejan de atribuirlos a este slot.","sens_wipe":"Restablecer época del historial","sens_free_slot":"Liberar slot","sens_revert":"Descartar cambios","sens_done":"Completado","sens_stage_hint":"Las ediciones se acumulan mientras escribe y se graban al Guardar y Reiniciar.","sens_scan_busy":"Buscando...","sens_scan_none":"Ninguna sonda respondió.","sens_scan_found":"encontrada(s)","act_fail":"Fallo en la acción.","sens_accept_ok":"Slot vinculado a la sonda encontrada en él.","sens_accept_none":"Ninguna sonda respondió en este GPIO.","sens_accept_unsaved":"Guarde antes este slot como un DS18B20 activo.","sens_accept_bad":"La sonda respondió con una ROM inválida.","sens_wipe_confirm":"¿Restablecer la época del historial de este slot?\n\nLos registros tomados antes de ahora dejan de atribuírsele. No se borra nada.","sens_wipe_ok":"Época del historial restablecida.","tel_sync_ok":"Envío disparado. Siga el resultado en el registro.","tel_reset_confirm":"¿Restablecer el cursor de telemetría?\n\nEl dispositivo reenviará hasta 30 días de historial en el próximo envío. Espere una ráfaga de tráfico.","tel_reset_ok":"Cursor restablecido. Los próximos envíos cubren hasta 30 días atrás.","sens_rebind_pending":"Guarde y Reinicie antes — recomponer ahora usaría la configuración anterior.","sens_rebind_confirm":"¿Reescribir el archivo de historial del día para los slots actuales?\n\nLos registros ya tomados se conservan. El dispositivo se reinicia al final.","sens_rebind_busy":"Reescribiendo...","sens_rebind_ok_forced":"Historial reconstruido","sens_rebind_meas":"mediciones","sens_rebind_ok":"Historial reescrito","sens_rebind_recs":"registros conservados","sens_rebind_reboot":"Reiniciando...","sens_rebind_force":"El archivo de historial del día no se pudo leer, así que los registros no se pueden aprovechar.\n\n¿Recrearlo vacío? Los registros de hoy se pierden. Los días anteriores quedan intactos.","sens_rebind_kept":"Nada cambió.","sens_rebind_err":"Fallo al reescribir","sens_rebind_unsure":"Sin respuesta del dispositivo. Recargue la página y revise el log antes de intentarlo de nuevo.","sens_need_type":"Elija un tipo antes de activar este slot.","sens_need_pin":"Asigne un GPIO al pin","sens_taken":"ya está en uso por el slot","sens_clear_confirm":"¿Liberar este slot? Se borran su tipo, GPIOs e identificación.","net_conn":"Conectado","net_off":"Desconectado","usr_pend_del":"Pendiente: Eliminar","usr_pend_rst":"Pendiente: Reset","usr_pend_add":"Pendiente: Nuevo","usr_del_msg":"¿Eliminar?","usr_rst_msg":"¿Forzar reset?","fil_parent":"Arriba","fil_empty":"Vacío","fil_protected":"Archivo protegido","fil_folder":"Carpeta","fil_uploaded":"Subida completada.","fil_up_err":"Error al subir.","fil_sel_del":"Seleccione","fil_conf_del":"¿Eliminar N?","fil_del_err":"Algunos archivos no pudieron ser eliminados.","fil_deleted":"Archivos eliminados.","fil_sel_down":"Seleccione","fil_conf_down":"¿Descargar N?","fil_backup_started":"Descarga del respaldo iniciada.","fil_rst_val":"Paso 1/3: Validando el respaldo (CRC + chip ID)...","fil_rst_val_fail":"Falló la validación: ","fil_rst_confirm":"Respaldo válido — N archivos (X bytes).\n\nEsto SOBRESCRIBE los archivos actuales del dispositivo (history, configs, sensores, themes).\nSe restaurarán la configuración de Wi-Fi y la contraseña de admin del respaldo.\n\n¿Continuar con la restauración?","fil_rst_app":"Paso 2/3: Aplicando la restauración (puede tardar ~15 s)...","fil_rst_app_fail":"Fallo al aplicar: ","fil_rst_reb":"Paso 3/3: Reiniciando el dispositivo (~25 s)...","fil_rst_off":"Dispositivo sin conexión tras 90 s. Verifique la conexión y enciéndalo manualmente si hace falta.","fil_fw_warn":"Actualización de firmware vía OTA.\n\nLa partición de archivos se REFORMATEA — el área de staging comparte la partición con ella.\n\nSolo la configuración sobrevive (capturada antes, restaurada después):\n • Wi-Fi, contraseña de administrador, telemetría\n • Mapeo de sensores y límites de alarma\n\nTodo lo demás en el dispositivo se pierde:\n • Historial de lecturas (/history)\n • Paquetes de idioma (/lang)\n • Temas personalizados (/themes)\n • Calibración táctil (/calib)\n • Archivos en /web\n • El registro de eventos\n\nUn respaldo .bkp se descarga automáticamente — restáurelo después para recuperar todo.\n\n¿Continuar?","fil_fw_warn_slot":"Actualización de firmware vía OTA.\n\nLa imagen nueva va al slot desde el que el equipo no arrancó. Los archivos del equipo quedan como están.\n\nTras el reinicio, la versión nueva funciona a prueba. Se queda cuando haya funcionado sana durante un minuto. Si no lo hace en cinco minutos, o si el equipo se reinicia antes, vuelve a la versión que ejecuta ahora.\n\nPrimero se descarga un respaldo .bkp.\n\n¿Continuar?","fil_fw_need_bin":"Seleccione un archivo .bin de firmware","fil_rst_need_bkp":"Seleccione un archivo .bkp de respaldo","fil_fw_bk":"Paso 1/4: Descargando el respaldo .bkp...","fil_fw_bk_fail":"Fallo al descargar el respaldo. Intente nuevamente.","fil_fw_bk_corrupt":"Respaldo corrupto (CRC). Abortado.","fil_fw_apply":"Respaldo guardado en su computadora.\n\n¿Iniciar la actualización?\n • Subida del firmware: ~30 s\n • Aplicación + reinicio: ~25 s\n • Arranque completo: ~25 s\n\nTotal: ~80 s. No apague el dispositivo durante el proceso.","fil_fw_up":"Paso 2/4: Subiendo el firmware (~30 s)...","fil_fw_stage_fail":"Fallo en el envío (validación v=","fil_fw_stage_refused":"Envío rechazado (HTTP ","fil_fw_unsigned":"Este .bin no está firmado, y el equipo solo instala imágenes firmadas. Use un .bin de release de GitHub o una compilación del configurador.","fil_fw_v7":"La imagen es de otro modelo (env ","fil_fw_v8":"La imagen no está firmada. Use un .bin de release de GitHub o una compilación del configurador.","fil_fw_v9":"La firma no coincide: el archivo cambió después de firmarse, o lo firmó una clave en la que este equipo no confía. Descargue el .bin de nuevo.","fil_fw_v10":"Se firmó con una clave retirada. Use una release más reciente.","fil_fw_v11":"Está por debajo del nivel de seguridad instalado, y volver a ella por el aire está bloqueado. Use esta versión o una más reciente.","fil_fw_v12":"Se firmó con la clave del banco de pruebas, que este equipo no acepta. Use un .bin de release de GitHub.","fil_fw_app":"Paso 3/4: Aplicando el firmware...","fil_fw_reb":"Paso 4/4: Esperando el arranque (~25 s)...","fw_trial":"Esta versión está a prueba: se queda cuando haya funcionado sana durante un minuto. Reiniciar el equipo antes trae de vuelta la versión anterior.","fw_reverted":"La actualización a la v{v} no se confirmó y el equipo volvió a esta versión (código 613 en el registro de eventos).","pending_notice":"Haga clic en \"Guardar y Reiniciar\" para aplicar los cambios.","commit_confirm":"Esto guardará todos los cambios y reiniciará el sistema.\n\nEl dispositivo quedará offline por ~10 segundos.\nCualquier grabación de historial/log en curso será interrumpida.\n\n¿Continuar?","calib_err":"Error de calibración: ","commit_btn":"Guardar y reiniciar","calib_timeout":"Tiempo agotado en la calibración. Intente nuevamente.","net_saved_port":"Guardado. Redirigiendo al nuevo puerto...","commit_saved":"¡Guardado! Reiniciando sistema...","commit_err":"Fallo al guardar.","commit_apply":"Aplicar ahora","commit_try":"Probar","commit_live":"No requiere reinicio","commit_why":"Reinicia por","commit_pend":"Cambios sin guardar","commit_applied":"Aplicado y guardado","commit_tried":"Aplicado SIN guardar — un reinicio lo deshace","commit_rej":"Campos no aplicados","cal_pts_hint":"Hasta 5 puntos por magnitud, cada uno ligando la lectura bruta al valor mostrado por un instrumento confiable. Un punto aplica un offset constante; más puntos doblan la corrección entre ellos, recta más allá de los extremos. Deje el bruto vacío para capturar la lectura al guardar.","cal_raw":"Bruto","cal_ref":"Referencia","cal_corr":"corregido","cal_cap":"Usar la lectura bruta actual","cal_del":"Quitar este punto","cal_add":"Añadir punto","cal_clear":"Quitar corrección","cal_none":"Sin corrección — valor del sensor.","cal_legacy":"Offset constante","cal_save_hint":"Las correcciones se graban en Guardar y Reiniciar. Requiere NTP sincronizado.","cal_err_max":"como máximo 5 puntos.","cal_err_num":"cada punto necesita bruto y referencia numéricos.","cal_err_noread":"sin lectura en vivo para capturar — rellene el valor bruto.","cal_err_rng":"punto fuera del rango plausible","cal_err_dup":"dos puntos con el mismo valor bruto.","cal_mode_lbl":"Interpolación","cal_mode_lin":"Recta","cal_mode_cub":"Suave","cal_mode_hint":"Suave es una cúbica monótona: dobla por las anclas sin jamás sobrepasarlas. Necesita 3+ puntos; con menos se comporta como recta.","fil_fw_apply_err":"Fallo al aplicar: ","fil_fw_apply_fail":"Aplicación rechazada (HTTP ","fil_view":"Ver","tv_org":"Organizado","tv_raw":"Original","tv_exp":"Expandir todo","tv_col":"Contraer todo","tv_close":"Cerrar","tv_bad":"JSON incompleto o no válido: organizado solo por sus llaves y corchetes. El texto exacto está en Original.","tv_bin":"Este archivo no es texto. Descárguelo para abrirlo.","tv_err":"No se pudo leer el archivo.","hist_cancel":"Cancelar","hist_lost":"perdidas","hist_partial":"Parcial: ","cfg_tls_noverify":"TLS sin validación de certificado — la conexión está cifrada pero no autenticada (MITM posible). Suba /cert.pem en Archivos para validar el servidor.","creds_title":"Contraseña temporal","creds_warn":"Cópiela ahora — se muestra una sola vez. El usuario debe cambiarla en el primer inicio de sesión.","creds_ok":"La guardé — recargar","hist_files":"Archivos","nav_tel":"Telemetría","tel_title":"Telemetría","tel_desc":"Transporte, formatos de payload y la segunda línea (alarmas). Guardar y Reiniciar aplica los cambios pendientes; Enviar ahora / Restablecer cursor actúan sobre el dispositivo en ejecución.","al_title":"Payload de Alarmas — 2ª Línea de Telemetría","al_hint":"Línea separada para alarmas de sensores. Los registros permanecen en cola en RAM y solo se eliminan cuando el servidor confirma la recepción (HTTP 2xx o ACK MQTT en /alarm/ack). El transporte, el servidor, las credenciales y TLS siguen la configuración de la telemetría principal.","al_pending":"Pendientes:","al_en":"Habilitar línea de telemetría de alarmas","al_qmax":"Tamaño de cola (RAM)","al_path":"Ruta HTTP","al_leg":"Etiquetas de la línea de alarmas (un registro por borde de límite/error):","al_smart":"Claves inteligentes","al_smart2":"(nombre de clave = token, minúsculas o mayúsculas):","al_smart_val":"número, o la clave completa se elimina en caso de fallo","al_smart_err":"código, o la clave completa se elimina cuando el otro dominio está activo","cfg_bat_hint":"Máximo de registros por envío. Una cola mayor sale en lotes de este tamaño hasta agotarse.","dash_disp_live":"En vivo","dash_disp_stop":"Detener","dash_disp_err":"Fallo de lectura","dash_disp_tap":"Clic para tocar, mantén para pulsación larga","dash_disp_taperr":"Toque rechazado","usr_plim":"Límites (panel)","usr_pblk":"Bloqueo (panel)","usr_pmnt":"Mant. (panel)","usr_pin":"PIN del panel (opcional)","usr_pin_btn":"PIN","usr_haspin":"PIN","usr_pend_pin":"Pendiente: PIN","usr_pin_msg":"Vacío elimina el PIN.","usr_pin_bad":"PIN: %M a %X de %S.","cfg_pinkb":"Teclado del panel: glifos por tecla","cfg_pinalpha":"Caracteres del PIN","cfg_pinmin":"Tamaño mínimo del PIN","cfg_ct":"Encabezado Content-Type"} diff --git a/data/lang/language_pt-BR.lng b/data/lang/language_pt-BR.lng index 51402ae8..6251f9f8 100644 --- a/data/lang/language_pt-BR.lng +++ b/data/lang/language_pt-BR.lng @@ -494,4 +494,4 @@ time Ajusta o relógio à mão (sem rede) =========================================== @WEBDICT -{"log_usr":"Usuário","log_pas":"Senha","log_show":"Mostrar senha","log_btn":"Entrar","log_chpass_link":"Trocar senha","log_chpass_title":"Trocar Senha","log_oldpass":"Senha Atual","log_newpass":"Nova Senha","log_chpass_req_len":"Mínimo 8 caracteres","log_chpass_req_letter":"Letra","log_chpass_req_digit":"Dígito","log_chpass_req_symbol":"Símbolo","log_newpass2":"Repetir Nova Senha","log_chpass_btn":"Salvar Nova Senha","log_chpass_back":"← Voltar ao Login","fcp_wel":"Bem-vindo!","fcp_msg":"Defina uma nova senha forte para acessar o sistema.","fcp_btn":"Salvar e Entrar","nav_dash":"Painel de Controle","dash_dt":"Data e Hora","dash_ntp_wait":"Aguardando...","dash_up":"Tempo Ativo","dash_wifi":"Sinal Wi-Fi","dash_pend":"Registros Pendentes","dash_tel_ok":"Sincronizado","dash_tel_off":"Telemetria desligada","dash_tel_na":"Telemetria indisponível","dash_tel_wait":"Aguardando sinc.","dash_ram":"Uso de RAM","dash_fs":"Armazenamento","dash_metr_lblk":"Maior Bloco","dash_metr_hmin":"Heap Mín.","dash_metr_hmin_sub":"menor livre","dash_metr_net":"Reconexões Rede","dash_metr_net_sub":"wifi / mqtt","dash_metr_tel":"Telemetria Env.","dash_metr_data":"Dados Telemetria","dash_metr_snr":"Leituras Sensor","dash_metr_cfg":"Saves Config","dash_metr_cfg_sub":"para flash","dash_stat":"Status","dash_gpio":"Slot","dash_type":"Tipo","dash_id":"ID","dash_read":"Leitura","dash_name":"Nome","dash_load":"Carregando sensores...","dash_disp":"Tela","dash_disp_msg":"Clique para capturar","dash_disp_cap":"Capturando...","dash_disp_btn":"Capturar","nav_hist":"Histórico e Logs","hist_title":"Telemetria","hist_src":"Origem:","hist_loading":"Carregando...","hist_cal":"Calendário","cal_su":"Dom","cal_mo":"Seg","cal_tu":"Ter","cal_we":"Qua","cal_th":"Qui","cal_fr":"Sex","cal_sa":"Sáb","ch_temp":"Temp","ch_hum":"Umid","ch_press":"Press","ch_lux":"Luz","ch_unk":"Canal","hist_logs":"Eventos do Sistema","hist_filt":"Filtrar...","hist_load_btn":"Carregar","exp_btn":"Exportar CSV","hist_clear":"Limpar","hist_dtlog":"Data","hist_uptime":"Tempo","hist_lvl":"Nível","hist_module":"Módulo","hist_desc":"Descrição","hist_prompt":"Clique em 'Carregar' para exibir os logs.","nav_cfg":"Configurações","cfg_title":"Configurações","cfg_load_fail":"Não foi possível carregar as configurações atuais. Os campos estão desabilitados para evitar salvar valores em branco sobre a sua configuração.","cfg_retry":"Tentar novamente","cfg_gen":"Identidade","cfg_dev":"Nome","cfg_tz":"Fuso Horário","cfg_log":"Registro Local","cfg_datetime":"Data e Hora","cfg_ntp_auto":"Sincronizar automaticamente via NTP","cfg_date":"Data","cfg_time":"Hora","cfg_apply_now":"Aplicar Agora","cfg_manual_hint":"Usa fuso horário do dispositivo. Salve e Reinicie para persistir o toggle do NTP.","cfg_hw":"Hardware","cfg_res":"Resolução DS18B20","cfg_r9":"9-bit","cfg_r12":"12-bit","cfg_sint":"Amostra (ms)","cfg_hint":"Intervalo Histórico (min)","cfg_hint_hint":"1 a 1440 min (24h). Padrão: 1.","sens_title":"Sensores e GPIO","sens_hint":"GP0–GP15 estão disponíveis para sensores; GP16 em diante pertencem ao display, ao touch e ao buzzer.","sens_slot":"Slot","sens_type":"Tipo","sens_gpios":"GPIOs","sens_hwid":"ID de Hardware","sens_name":"Nome","sens_add":"+ Adicionar slot de sensor","sens_scan":"Procurar sondas","sens_no_free":"Os 16 slots estão em uso. Libere um para adicionar outro.","cfg_tel":"Telemetria","cfg_transport":"Transporte","cfg_tr_http":"HTTP(S)","cfg_tr_mqtt":"MQTT(S)","cfg_srv":"IP Servidor","cfg_port":"Porta","cfg_sec":"Usar TLS / SSL","cfg_path":"Endpoint","cfg_key":"API Key","cfg_mq_topic":"Tópico","cfg_mq_cid":"Client ID","cfg_mq_user":"Usuário","cfg_mq_pass":"Senha","cfg_mq_qos":"QoS","cfg_mq_q0":"0","cfg_mq_ka":"Keep-Alive","cfg_mq_retain":"Reter","cfg_mq_had":"Home Assistant Discovery","cfg_mq_had_hint":"Publica mensagens de configuração retidas para o Home Assistant criar o dispositivo e seus sensores automaticamente. Exige payload JSON; as entidades aparecem no próximo envio.","cfg_tint":"Lote mínimo (registros)","cfg_tint_hint":"Transmite quando houver esta quantidade de registros esperando. 0 desliga a telemetria.","cfg_bat":"Lote máximo (registros)","cfg_tel_disabled":"Telemetria desabilitada (lote mínimo = 0). Defina um valor para ativar.","tel_no_mqtt":"Esta imagem não tem MQTT, então a telemetria vai por HTTP. A configuração do aparelho ainda pede MQTT e nada é enviado: salvar a troca para HTTP.","tel_no_tls":"Esta imagem não tem cliente TLS, então a telemetria vai sem criptografia. A configuração do aparelho ainda pede criptografia e nada é enviado: salvar a desliga (confira também a porta).","tel_sync":"Enviar agora","tel_reset":"Resetar cursor de envio","tel_hint":"Enviar agora despeja o que estiver pendente sem esperar o lote mínimo. Reiniciar cursor faz o aparelho reenviar até 30 dias — use depois de uma parada longa do servidor.","cfg_vis":"Construtor","cfg_fmt":"Formato","cfg_f0":"JSON","cfg_f1":"CSV","cfg_f2":"Dinâmico","cfg_leg":"Tags","cfg_leg1":"Globais:","cfg_leg2":"Dados:","cfg_leg3":"Chaves Inteligentes:","cfg_leg4":"Formatos:","cfg_tpl1":"1. Global","cfg_tpl2":"2. Linha","cfg_tpl3":"3. Separador","cfg_prev":"Prévia ao Vivo:","tel_pv_live":"Montada com os dados deste aparelho: o nome, o MAC e as leituras de agora. O primeiro registro mostra um sensor em falha.","al_pv_live":"Um registro de cada código, com os sensores, os limites e a conta deste aparelho. Os eventos em si são exemplos.","cfg_touch_title":"Calibração do Touch","cfg_touch_reset":"Resetar Calibração","cfg_touch_hint":"Restaura padrão de fábrica. Recalibre pelo menu do display.","cfg_reboot_title":"Reiniciar","cfg_reboot_btn":"Reiniciar sem salvar","cfg_reboot_hint":"Reinicia o dispositivo imediatamente. Alterações não salvas nesta página são descartadas; a configuração salva é mantida. O dispositivo fica offline por ~10 segundos.","cfg_reboot_confirm":"Reiniciar agora SEM salvar?\n\nAlterações não salvas nesta página serão perdidas.\nO dispositivo ficará offline por ~10 segundos.","cfg_reboot_fail":"Não foi possível reiniciar.","cfg_slog":"Syslog Remoto (Auditoria)","cfg_slog_hint":"Encaminha logs para um coletor syslog (RFC 5424 / UDP), mantendo a trilha de auditoria fora do dispositivo. Use o IPv4 do coletor na rede local (não hostname). Sem garantia de entrega.","cfg_slog_en":"Habilitar encaminhamento syslog","cfg_slog_srv":"IP do Coletor","cfg_slog_port":"Porta UDP","cfg_slog_lvl":"Nível Mínimo","nav_net":"Rede","net_curr":"Status da Conexão","net_stat":"Status","net_ip":"IP","net_mask":"Máscara","net_gw":"Gateway","net_dns":"DNS","net_mac":"MAC","net_cfg":"Configuração","net_wifi":"Wi-Fi","net_ssid":"SSID","net_pass":"Senha","net_scan":"Buscar","net_scanning":"Buscando...","net_scan_busy":"Rádio ocupado — tente de novo em instantes","net_scan_fail":"A busca falhou","net_scan_none":"Nenhuma rede encontrada","net_pass_show":"Mostrar senha","net_pass_hide":"Ocultar senha","net_secured":"Rede protegida","net_open":"Rede aberta","net_ipv4":"IPv4","net_dhcp":"DHCP","net_sip":"IP Estático","net_dns_title":"Configuração de DNS","net_dns_auto":"Obter DNS automaticamente (DHCP)","net_dns1":"DNS Primário","net_dns2":"DNS Secundário","net_ntp_title":"Servidor de Hora (NTP)","net_ntp_disabled":"NTP está desabilitado.","net_ntp_goto_cfg":"Habilite em Configurações do Sistema →","net_ntp_lbl":"Endereço do Servidor","net_ntp_hint":"Deixe vazio para usar o padrão (pool.ntp.org)","net_web_title":"Servidor Web","net_web_port":"Porta HTTP","net_web_port_hint":"Padrão: 80. Após salvar, o navegador redireciona automaticamente para a nova porta.","net_web_ka":"Conexões persistentes (keep-alive)","net_web_ka_hint":"Reaproveita a conexão TLS entre as requisições — as páginas carregam ~60% mais rápido em HTTPS. Desabilite apenas se um proxy ou cliente se comportar mal com conexões persistentes.","nav_usr":"Usuários","usr_mgt":"Gestão de Acessos","usr_usr":"Usuário","usr_perm":"Permissões","usr_act":"Ações","usr_add":"Adicionar","usr_name":"Nome","usr_pdash":"Painel","usr_phist":"Histórico","usr_plog":"Logs","usr_psys":"Sistema","usr_pnet":"Rede","usr_pfr":"Leitura","usr_pfu":"Upload","usr_pfd":"Excluir","usr_pusr":"Usuários","usr_pcal":"Calibração","usr_btn":"Criar","usr_warn":"Uma senha de uso único aparece ao salvar. Copie — ela é mostrada só uma vez, e o usuário deve trocá-la no 1º login.","usr_sup":"Super","usr_prot":"Protegido","usr_rst":"Reset","usr_del":"Excluir","nav_file":"Arquivos","fil_title":"Sistema de Arquivos","fil_down":"Baixar","fil_backup":"Backup","fil_restore":"Restaurar","fil_fw":"Firmware","fil_del":"Excluir","fil_uphere":"Enviar","fil_name":"Nome","fil_sz":"Tamanho","fil_loading":"Carregando...","nav_alm":"Alarmes e Sons","alm_title":"Alarmes e Sons","alm_limits":"Limites de Alarme","alm_none":"Nenhum sensor configurado.","alm_sounds":"Configuração de Sons","alm_touch":"Toque","alm_confirm":"Confirmação","alm_mel_asc":"Ascendente","alm_error":"Erro","alm_mel_desc":"Descendente","alm_alarm":"Alarme","alm_mel_siren":"Sirene","alm_web":"Sons Web","alm_attention":"Atenção","alm_mute":"Mudo Global","alm_volume":"Vol. Sistema","alm_alarm_vol":"Vol. Alarme","nav_lic":"Licença","lic_title":"Licença de Software","lic_mit":"Licença MIT","lic_notice":"Avisos de Terceiros","lic_sub":"Software livre. Abaixo, o que isso significa na prática.","lic_summary_title":"Em resumo","lic_summary":"Você pode usar, copiar, alterar, juntar, publicar, distribuir, sublicenciar e vender cópias deste software, de graça e sem restrição.\n\nA única obrigação é manter o aviso de copyright e o aviso de permissão em todas as cópias.\n\nO software é fornecido \"como está\", sem garantia de espécie alguma. O autor não responde por nenhum dano decorrente do uso.","lic_summary_note":"Resumo informativo, escrito para ser entendido. Não substitui o texto da licença.","lic_legal_note":"O texto abaixo fica em inglês porque é a versão original — é ela que tem valor legal.","greet_logout":"Sair","log_lock":"Bloqueado por {s}s. Tentativas excessivas.","log_full":"Limite de sessões atingido. Tente mais tarde.","log_err":"Usuário ou senha incorretos.","log_cookie_loop":"Login aceito, mas o navegador guarda uma sessão segura de uma visita HTTPS anterior. Abra uma janela anônima ou limpe os cookies deste site e entre de novo.","log_chpass_mismatch":"As senhas não coincidem.","log_chpass_same":"A nova senha deve ser diferente da atual.","log_chpass_ok":"Senha trocada. Faça login.","dash_ntp_ok":"Sincronizado","dash_ntp_fail":"Falha NTP","dash_ntp_conn":"Conectando...","dash_eval":"Avaliando...","dash_metr_min":"min","dash_metr_max":"max","dash_metr_ok":"ok","dash_metr_fail":"falhas","dash_metr_retry":"retry","dash_metr_last":"últ.","dash_metr_err":"erro","dash_free":"Livre","dash_tot":"Total","greet_hello":"Olá","greet_morning":"Bom dia","greet_afternoon":"Boa tarde","greet_evening":"Boa noite","hist_none_sel":"Nenhum","hist_n_sel":"selecionados","m_jan":"Jan","m_feb":"Fev","m_mar":"Mar","m_apr":"Abr","m_may":"Mai","m_jun":"Jun","m_jul":"Jul","m_aug":"Ago","m_sep":"Set","m_oct":"Out","m_nov":"Nov","m_dec":"Dez","hist_done":"Concluído","hist_down_msg":"Carregando...","hist_err_json":"Erro.","hist_no_data":"Sem dados.","hist_conn_lost":"Conexão perdida.","hist_ref":"Atualizar","hist_none":"Nenhum evento.","hist_clear_msg":"Limpar logs?","display_busy":"Display em uso. Tente novamente em alguns segundos.","hist_clear_err":"Falha ao limpar logs.","hist_cleared":"Logs limpos.","net_conn_err":"Erro de conexão.","exp_no_chart":"Carregue um intervalo no gráfico","exp_cancelled":"Cancelado","exp_empty":"Sem dados neste período","exp_logs_empty":"Clique em 'Carregar' primeiro","exp_rows":"linhas","cfg_sec_mqtt":"Usar MQTTS (TLS)","cfg_time_need":"Preencha data e hora.","cfg_time_ok":"Hora aplicada.","cfg_time_fail":"Falhou ao aplicar.","cfg_touch_confirm":"Resetar calibração do touch para padrão de fábrica?","cfg_touch_done":"Calibração resetada. Recalibre pelo display.","sens_load_fail":"Não foi possível carregar os slots de sensores.","sens_free":"livre","sens_active":"Ativo","sens_none":"Nenhum slot de sensor configurado ainda.","sens_empty":"vazio","sens_hwid_hint":"Chave usada pelo histórico e pela telemetria. Alterá-la inicia uma nova série.","sens_state":"Estado","sens_alarms":"Alarmes habilitados","sens_pins":"Atribuição de GPIO","sens_pin":"Pino","sens_calib":"Calibração","sens_ntp_no":"NTP não sincronizado.","sens_hist":"Gravação no histórico","sens_rebind_hint":"Um slot adicionado ou renomeado hoje não tem coluna no arquivo de histórico do dia, então não é gravado até amanhã. Isto reescreve esse arquivo para todos os slots, preservando os registros já feitos. O dispositivo reinicia ao final.","sens_rebind":"Recompor o histórico agora","sens_hw":"Hardware","sens_accept_hint":"Relê a sonda ligada neste slot e vincula o slot a ela. Use depois de trocar um DS18B20, quando o dispositivo acusar divergência de hardware.","sens_accept":"Adotar a sonda ligada aqui","sens_wipe_hint":"Marca tudo o que foi gravado antes de agora como pertencente ao sensor anterior. Os registros continuam no disco; os gráficos é que param de atribuí-los a este slot.","sens_wipe":"Resetar época do histórico","sens_free_slot":"Liberar slot","sens_revert":"Descartar alterações","sens_done":"Concluído","sens_stage_hint":"As edições são acumuladas conforme você digita e gravadas ao Salvar e Reiniciar.","sens_scan_busy":"Procurando...","sens_scan_none":"Nenhuma sonda respondeu.","sens_scan_found":"encontrada(s)","act_fail":"Falha na ação.","sens_accept_ok":"Slot vinculado à sonda encontrada nele.","sens_accept_none":"Nenhuma sonda respondeu neste GPIO.","sens_accept_bad":"A sonda respondeu com uma ROM inválida.","sens_wipe_confirm":"Resetar a época do histórico deste slot?\n\nOs registros feitos antes de agora deixam de ser atribuídos a ele. Nada é apagado.","sens_wipe_ok":"Época do histórico resetada.","tel_sync_ok":"Envio disparado. Acompanhe o resultado no log.","tel_reset_confirm":"Resetar o cursor de telemetria?\n\nO dispositivo vai reenviar até 30 dias de histórico no próximo envio. Espere uma rajada de tráfego.","tel_reset_ok":"Cursor resetado. Os próximos envios cobrem até 30 dias atrás.","sens_rebind_pending":"Salve e Reinicie antes — recompor agora usaria a configuração anterior.","sens_rebind_confirm":"Reescrever o arquivo de histórico do dia para os slots atuais?\n\nOs registros já feitos são preservados. O dispositivo reinicia ao final.","sens_rebind_busy":"Reescrevendo...","sens_rebind_ok_forced":"Histórico reconstruído","sens_rebind_meas":"medições","sens_rebind_ok":"Histórico reescrito","sens_rebind_recs":"registros preservados","sens_rebind_reboot":"Reiniciando...","sens_rebind_force":"O arquivo de histórico do dia não pôde ser lido, então os registros não têm como ser aproveitados.\n\nRecriar vazio? Os registros de hoje se perdem. Os dias anteriores ficam intactos.","sens_rebind_kept":"Nada mudou.","sens_rebind_err":"Falha ao reescrever","sens_rebind_unsure":"Sem resposta do dispositivo. Recarregue a página e confira o log antes de tentar de novo.","sens_need_type":"Escolha um tipo antes de ativar este slot.","sens_need_pin":"Atribua um GPIO ao pino","sens_taken":"já está em uso pelo slot","sens_clear_confirm":"Liberar este slot? Tipo, GPIOs e identificação serão apagados.","net_conn":"Conectado","net_off":"Desconectado","usr_pend_del":"Pendente: Excluir","usr_pend_rst":"Pendente: Reset","usr_pend_add":"Pendente: Novo","usr_del_msg":"Excluir?","usr_rst_msg":"Forçar reset?","fil_parent":"Subir","fil_empty":"Vazio","fil_protected":"Arquivo protegido","fil_folder":"Pasta","fil_uploaded":"Upload concluído.","fil_up_err":"Erro no upload.","fil_sel_del":"Selecione","fil_conf_del":"Excluir N?","fil_del_err":"Alguns arquivos não puderam ser excluídos.","fil_deleted":"Arquivos excluídos.","fil_sel_down":"Selecione","fil_conf_down":"Baixar N?","fil_backup_started":"Download do backup iniciado.","fil_rst_val":"Etapa 1/3: Validando backup (CRC + chip ID)...","fil_rst_val_fail":"Validação falhou: ","fil_rst_confirm":"Backup válido — N arquivos (X bytes).\n\nIsto SOBRESCREVE os arquivos atuais do dispositivo (history, configs, sensores, themes).\nA configuração de Wi-Fi e a senha de admin do backup serão restauradas.\n\nProsseguir com a restauração?","fil_rst_app":"Etapa 2/3: Aplicando restore (pode levar ~15 s)...","fil_rst_app_fail":"Falha ao aplicar: ","fil_rst_reb":"Etapa 3/3: Reiniciando dispositivo (~25 s)...","fil_rst_off":"Dispositivo offline após 90 s. Verifique conexão e ligue manualmente se necessário.","fil_fw_warn":"Atualização de firmware via OTA.\n\nA partição de arquivos é REFORMATADA — a área de staging divide a partição com ela.\n\nSó a configuração sobrevive (capturada antes, restaurada depois):\n • Wi-Fi, senha de administrador, telemetria\n • Mapeamento de sensores e limites de alarme\n\nTodo o resto que está no dispositivo se perde:\n • Histórico de leituras (/history)\n • Pacotes de idioma (/lang)\n • Temas customizados (/themes)\n • Calibração de touch (/calib)\n • Arquivos em /web\n • O log de eventos\n\nUm backup .bkp será baixado automaticamente — restaure depois para recuperar tudo.\n\nProsseguir?","fil_fw_warn_slot":"Atualização de firmware via OTA.\n\nA imagem nova vai para o slot de onde o aparelho não subiu. Os arquivos do aparelho ficam como estão.\n\nDepois do reinício, a versão nova roda em teste. Ela fica depois de rodar saudável por um minuto. Se não rodar em cinco minutos, ou se o aparelho reiniciar antes disso, ele volta para a versão que roda agora.\n\nUm backup .bkp é baixado antes.\n\nProsseguir?","fil_fw_need_bin":"Selecione um arquivo .bin de firmware","fil_rst_need_bkp":"Selecione um arquivo .bkp de backup","fil_fw_bk":"Etapa 1/4: Baixando backup .bkp...","fil_fw_bk_fail":"Falha ao baixar backup. Tente novamente.","fil_fw_bk_corrupt":"Backup corrompido (CRC). Abortado.","fil_fw_apply":"Backup salvo no seu computador.\n\nIniciar a atualização?\n • Upload do firmware: ~30 s\n • Aplicação + reboot: ~25 s\n • Boot completo: ~25 s\n\nTotal: ~80 s. Não desligue o dispositivo durante o processo.","fil_fw_up":"Etapa 2/4: Enviando firmware (~30 s)...","fil_fw_stage_fail":"Falha no envio (validação v=","fil_fw_stage_refused":"Envio recusado (HTTP ","fil_fw_unsigned":"Este .bin não é assinado, e o aparelho só instala imagem assinada. Use um .bin de release do GitHub ou uma build do configurador.","fil_fw_v7":"A imagem é de outro modelo (env ","fil_fw_v8":"A imagem não é assinada. Use um .bin de release do GitHub ou uma build do configurador.","fil_fw_v9":"A assinatura não confere: o arquivo mudou depois de assinado, ou foi assinado por uma chave em que este aparelho não confia. Baixe o .bin de novo.","fil_fw_v10":"Foi assinada com uma chave aposentada. Use uma release mais nova.","fil_fw_v11":"Está abaixo do nível de segurança instalado, e voltar a ela pelo ar está bloqueado. Use esta versão ou uma mais nova.","fil_fw_v12":"Foi assinada com a chave de bancada, que este aparelho não aceita. Use um .bin de release do GitHub.","fil_fw_app":"Etapa 3/4: Aplicando firmware...","fil_fw_reb":"Etapa 4/4: Aguardando boot (~25 s)...","fw_trial":"Esta versão está em teste: ela fica depois de rodar saudável por um minuto. Reiniciar o aparelho antes disso traz de volta a versão anterior.","fw_reverted":"A atualização para a v{v} não se confirmou, e o aparelho voltou para esta versão (código 613 no log de eventos).","pending_notice":"Clique em \"Salvar e Reiniciar\" para aplicar as alterações.","commit_confirm":"Isto salvará todas as alterações e reiniciará o sistema.\n\nO dispositivo ficará offline por ~10 segundos.\nQualquer gravação de histórico/log em andamento será interrompida.\n\nContinuar?","calib_err":"Erro na calibração: ","commit_btn":"Salvar e reiniciar","calib_timeout":"Tempo esgotado na calibração. Tente novamente.","net_saved_port":"Salvo. Redirecionando para nova porta...","commit_saved":"Salvo! Reiniciando sistema...","commit_err":"Falha ao salvar.","commit_apply":"Aplicar agora","commit_try":"Testar","commit_live":"Não exige reinício","commit_why":"Reinicia por","commit_pend":"Alterações não salvas","commit_applied":"Aplicado e salvo","commit_tried":"Aplicado SEM salvar — um reinício desfaz","commit_rej":"Campos não aplicados","cal_pts_hint":"Até 5 pontos por grandeza, cada um ligando a leitura bruta ao valor mostrado por um instrumento confiável. Um ponto aplica um offset constante; mais pontos dobram a correção entre eles, mantida reta além das pontas. Deixe o bruto vazio para captar a leitura ao salvar.","cal_raw":"Bruto","cal_ref":"Referência","cal_corr":"corrigido","cal_cap":"Usar a leitura bruta atual","cal_del":"Remover este ponto","cal_add":"Adicionar ponto","cal_clear":"Remover correção","cal_none":"Sem correção — padrão do sensor.","cal_legacy":"Offset constante","cal_save_hint":"As correções são gravadas no Salvar e Reiniciar. Requer NTP sincronizado.","cal_err_max":"no máximo 5 pontos.","cal_err_num":"todo ponto precisa de bruto e referência numéricos.","cal_err_noread":"sem leitura ao vivo para captar — preencha o valor bruto.","cal_err_rng":"ponto fora da faixa plausível","cal_err_dup":"dois pontos com o mesmo valor bruto.","cal_mode_lbl":"Interpolação","cal_mode_lin":"Reta","cal_mode_cub":"Suave","cal_mode_hint":"Suave é uma cúbica monótona: dobra pelas âncoras sem jamais ultrapassá-las. Precisa de 3+ pontos; com menos, comporta-se como reta.","fil_fw_apply_err":"Falha ao aplicar: ","fil_fw_apply_fail":"Aplicação recusada (HTTP ","fil_view":"Ver","tv_org":"Organizado","tv_raw":"Original","tv_exp":"Expandir tudo","tv_col":"Recolher tudo","tv_close":"Fechar","tv_bad":"JSON incompleto ou inválido: organizado só pelas chaves e colchetes. O texto exato está em Original.","tv_bin":"Este arquivo não é texto. Baixe-o para abrir.","tv_err":"Não deu para ler o arquivo.","hist_cancel":"Cancelar","hist_lost":"perdidas","hist_partial":"Parcial: ","cfg_tls_noverify":"TLS sem validação de certificado — a conexão é cifrada mas não autenticada (MITM possível). Envie /cert.pem em Arquivos para validar o servidor.","creds_title":"Senha temporária","creds_warn":"Copie agora — ela aparece uma única vez. O usuário deve trocá-la no primeiro login.","creds_ok":"Guardei — recarregar","hist_files":"Arquivos","nav_tel":"Telemetria","tel_title":"Telemetria","tel_desc":"Transporte, formatos de payload e a segunda linha (alarmes). Salvar e Reiniciar aplica as alterações pendentes; Enviar agora / Resetar cursor atuam no dispositivo em execução.","al_title":"Payload de Alarmes — 2ª Linha de Telemetria","al_hint":"Linha separada para alarmes de sensores. Os registros ficam em fila na RAM e só saem após o servidor confirmar o recebimento (HTTP 2xx ou ACK MQTT em /alarm/ack). Transporte, servidor, credenciais e TLS seguem as configurações da telemetria principal.","al_pending":"Pendentes:","al_en":"Habilitar linha de telemetria de alarmes","al_qmax":"Tamanho da fila (RAM)","al_path":"Caminho HTTP","al_leg":"Tags da linha de alarme (um registro por borda de limite/erro):","al_smart":"Chaves Inteligentes","al_smart2":"(nome da chave = token, minúsculo ou maiúsculo):","al_smart_val":"número, ou a chave inteira é removida em caso de falha","al_smart_err":"código, ou a chave inteira é removida quando o outro domínio está ativo","cfg_bat_hint":"Máximo de registros por envio. Uma fila maior sai em lotes deste tamanho até acabar.","dash_disp_live":"Ao vivo","dash_disp_stop":"Parar","dash_disp_err":"Falha na leitura","dash_disp_tap":"Clique para tocar, segure para toque longo","dash_disp_taperr":"Toque recusado","usr_plim":"Limites (painel)","usr_pblk":"Bloqueio (painel)","usr_pmnt":"Manut. (painel)","usr_pin":"PIN do painel (opcional)","usr_pin_btn":"PIN","usr_haspin":"PIN","usr_pend_pin":"Pendente: PIN","usr_pin_msg":"Vazio remove o PIN.","usr_pin_bad":"PIN: %M a %X de %S.","cfg_pinkb":"Teclado do painel: glifos por tecla","cfg_pinalpha":"Caracteres do PIN","cfg_pinmin":"Tamanho mínimo do PIN","cfg_ct":"Cabeçalho Content-Type"} +{"log_usr":"Usuário","log_pas":"Senha","log_show":"Mostrar senha","log_btn":"Entrar","log_chpass_link":"Trocar senha","log_chpass_title":"Trocar Senha","log_oldpass":"Senha Atual","log_newpass":"Nova Senha","log_chpass_req_len":"Mínimo 8 caracteres","log_chpass_req_letter":"Letra","log_chpass_req_digit":"Dígito","log_chpass_req_symbol":"Símbolo","log_newpass2":"Repetir Nova Senha","log_chpass_btn":"Salvar Nova Senha","log_chpass_back":"← Voltar ao Login","fcp_wel":"Bem-vindo!","fcp_msg":"Defina uma nova senha forte para acessar o sistema.","fcp_btn":"Salvar e Entrar","nav_dash":"Painel de Controle","dash_dt":"Data e Hora","dash_ntp_wait":"Aguardando...","dash_up":"Tempo Ativo","dash_wifi":"Sinal Wi-Fi","dash_pend":"Registros Pendentes","dash_tel_ok":"Sincronizado","dash_tel_off":"Telemetria desligada","dash_tel_na":"Telemetria indisponível","dash_tel_wait":"Aguardando sinc.","dash_ram":"Uso de RAM","dash_fs":"Armazenamento","dash_metr_lblk":"Maior Bloco","dash_metr_hmin":"Heap Mín.","dash_metr_hmin_sub":"menor livre","dash_metr_net":"Reconexões Rede","dash_metr_net_sub":"wifi / mqtt","dash_metr_tel":"Telemetria Env.","dash_metr_data":"Dados Telemetria","dash_metr_snr":"Leituras Sensor","dash_metr_cfg":"Saves Config","dash_metr_cfg_sub":"para flash","dash_stat":"Status","dash_gpio":"Slot","dash_type":"Tipo","dash_id":"ID","dash_read":"Leitura","dash_name":"Nome","dash_load":"Carregando sensores...","dash_disp":"Tela","dash_disp_msg":"Clique para capturar","dash_disp_cap":"Capturando...","dash_disp_btn":"Capturar","nav_hist":"Histórico e Logs","hist_title":"Telemetria","hist_src":"Origem:","hist_loading":"Carregando...","hist_cal":"Calendário","cal_su":"Dom","cal_mo":"Seg","cal_tu":"Ter","cal_we":"Qua","cal_th":"Qui","cal_fr":"Sex","cal_sa":"Sáb","ch_temp":"Temp","ch_hum":"Umid","ch_press":"Press","ch_lux":"Luz","ch_unk":"Canal","hist_logs":"Eventos do Sistema","hist_filt":"Filtrar...","hist_load_btn":"Carregar","exp_btn":"Exportar CSV","hist_clear":"Limpar","hist_dtlog":"Data","hist_uptime":"Tempo","hist_lvl":"Nível","hist_module":"Módulo","hist_desc":"Descrição","hist_prompt":"Clique em 'Carregar' para exibir os logs.","nav_cfg":"Configurações","cfg_title":"Configurações","cfg_load_fail":"Não foi possível carregar as configurações atuais. Os campos estão desabilitados para evitar salvar valores em branco sobre a sua configuração.","cfg_retry":"Tentar novamente","cfg_gen":"Identidade","cfg_dev":"Nome","cfg_tz":"Fuso Horário","cfg_log":"Registro Local","cfg_datetime":"Data e Hora","cfg_ntp_auto":"Sincronizar automaticamente via NTP","cfg_date":"Data","cfg_time":"Hora","cfg_apply_now":"Aplicar Agora","cfg_manual_hint":"Usa fuso horário do dispositivo. Salve e Reinicie para persistir o toggle do NTP.","cfg_hw":"Hardware","cfg_res":"Resolução DS18B20","cfg_r9":"9-bit","cfg_r12":"12-bit","cfg_sint":"Amostra (ms)","cfg_hint":"Intervalo Histórico (min)","cfg_hint_hint":"1 a 1440 min (24h). Padrão: 1.","sens_title":"Sensores e GPIO","sens_hint":"GP0–GP15 estão disponíveis para sensores; GP16 em diante pertencem ao display, ao touch e ao buzzer.","sens_slot":"Slot","sens_type":"Tipo","sens_gpios":"GPIOs","sens_hwid":"ID de Hardware","sens_name":"Nome","sens_add":"+ Adicionar slot de sensor","sens_scan":"Procurar sondas","sens_no_free":"Os 16 slots estão em uso. Libere um para adicionar outro.","cfg_tel":"Telemetria","cfg_transport":"Transporte","cfg_tr_http":"HTTP(S)","cfg_tr_mqtt":"MQTT(S)","cfg_srv":"IP Servidor","cfg_port":"Porta","cfg_sec":"Usar TLS / SSL","cfg_path":"Endpoint","cfg_key":"API Key","cfg_mq_topic":"Tópico","cfg_mq_cid":"Client ID","cfg_mq_user":"Usuário","cfg_mq_pass":"Senha","cfg_mq_qos":"QoS","cfg_mq_q0":"0","cfg_mq_ka":"Keep-Alive","cfg_mq_retain":"Reter","cfg_mq_had":"Home Assistant Discovery","cfg_mq_had_hint":"Publica mensagens de configuração retidas para o Home Assistant criar o dispositivo e seus sensores automaticamente. Exige payload JSON; as entidades aparecem no próximo envio.","cfg_tint":"Lote mínimo (registros)","cfg_tint_hint":"Transmite quando houver esta quantidade de registros esperando. 0 desliga a telemetria.","cfg_bat":"Lote máximo (registros)","cfg_tel_disabled":"Telemetria desabilitada (lote mínimo = 0). Defina um valor para ativar.","tel_no_mqtt":"Esta imagem não tem MQTT, então a telemetria vai por HTTP. A configuração do aparelho ainda pede MQTT e nada é enviado: salvar a troca para HTTP.","tel_no_tls":"Esta imagem não tem cliente TLS, então a telemetria vai sem criptografia. A configuração do aparelho ainda pede criptografia e nada é enviado: salvar a desliga (confira também a porta).","tel_sync":"Enviar agora","tel_reset":"Resetar cursor de envio","tel_hint":"Enviar agora despeja o que estiver pendente sem esperar o lote mínimo. Reiniciar cursor faz o aparelho reenviar até 30 dias — use depois de uma parada longa do servidor.","cfg_vis":"Construtor","cfg_fmt":"Formato","cfg_f0":"JSON","cfg_f1":"CSV","cfg_f2":"Dinâmico","cfg_leg":"Tags","cfg_leg1":"Globais:","cfg_leg2":"Dados:","cfg_leg3":"Chaves Inteligentes:","cfg_leg4":"Formatos:","cfg_tpl1":"1. Global","cfg_tpl2":"2. Linha","cfg_tpl3":"3. Separador","cfg_prev":"Prévia ao Vivo:","tel_pv_live":"Montada com os dados deste aparelho: o nome, o MAC e as leituras de agora. O primeiro registro mostra um sensor em falha.","al_pv_live":"Um registro de cada código, com os sensores, os limites e a conta deste aparelho. Os eventos em si são exemplos.","cfg_touch_title":"Calibração do Touch","cfg_touch_reset":"Resetar Calibração","cfg_touch_hint":"Restaura padrão de fábrica. Recalibre pelo menu do display.","cfg_reboot_title":"Reiniciar","cfg_reboot_btn":"Reiniciar sem salvar","cfg_reboot_hint":"Reinicia o dispositivo imediatamente. Alterações não salvas nesta página são descartadas; a configuração salva é mantida. O dispositivo fica offline por ~10 segundos.","cfg_reboot_confirm":"Reiniciar agora SEM salvar?\n\nAlterações não salvas nesta página serão perdidas.\nO dispositivo ficará offline por ~10 segundos.","cfg_reboot_fail":"Não foi possível reiniciar.","cfg_slog":"Syslog Remoto (Auditoria)","cfg_slog_hint":"Encaminha logs para um coletor syslog (RFC 5424 / UDP), mantendo a trilha de auditoria fora do dispositivo. Use o IPv4 do coletor na rede local (não hostname). Sem garantia de entrega.","cfg_slog_en":"Habilitar encaminhamento syslog","cfg_slog_srv":"IP do Coletor","cfg_slog_port":"Porta UDP","cfg_slog_lvl":"Nível Mínimo","nav_net":"Rede","net_curr":"Status da Conexão","net_stat":"Status","net_ip":"IP","net_mask":"Máscara","net_gw":"Gateway","net_dns":"DNS","net_mac":"MAC","net_cfg":"Configuração","net_wifi":"Wi-Fi","net_ssid":"SSID","net_pass":"Senha","net_scan":"Buscar","net_scanning":"Buscando...","net_scan_busy":"Rádio ocupado — tente de novo em instantes","net_scan_fail":"A busca falhou","net_scan_none":"Nenhuma rede encontrada","net_pass_show":"Mostrar senha","net_pass_hide":"Ocultar senha","net_secured":"Rede protegida","net_open":"Rede aberta","net_ipv4":"IPv4","net_dhcp":"DHCP","net_sip":"IP Estático","net_dns_title":"Configuração de DNS","net_dns_auto":"Obter DNS automaticamente (DHCP)","net_dns1":"DNS Primário","net_dns2":"DNS Secundário","net_ntp_title":"Servidor de Hora (NTP)","net_ntp_disabled":"NTP está desabilitado.","net_ntp_goto_cfg":"Habilite em Configurações do Sistema →","net_ntp_lbl":"Endereço do Servidor","net_ntp_hint":"Deixe vazio para usar o padrão (pool.ntp.org)","net_web_title":"Servidor Web","net_web_port":"Porta HTTP","net_web_port_hint":"Padrão: 80. Após salvar, o navegador redireciona automaticamente para a nova porta.","net_web_ka":"Conexões persistentes (keep-alive)","net_web_ka_hint":"Reaproveita a conexão TLS entre as requisições — as páginas carregam ~60% mais rápido em HTTPS. Desabilite apenas se um proxy ou cliente se comportar mal com conexões persistentes.","nav_usr":"Usuários","usr_mgt":"Gestão de Acessos","usr_usr":"Usuário","usr_perm":"Permissões","usr_act":"Ações","usr_add":"Adicionar","usr_name":"Nome","usr_pdash":"Painel","usr_phist":"Histórico","usr_plog":"Logs","usr_psys":"Sistema","usr_pnet":"Rede","usr_pfr":"Leitura","usr_pfu":"Upload","usr_pfd":"Excluir","usr_pusr":"Usuários","usr_pcal":"Calibração","usr_btn":"Criar","usr_warn":"Uma senha de uso único aparece ao salvar. Copie — ela é mostrada só uma vez, e o usuário deve trocá-la no 1º login.","usr_sup":"Super","usr_prot":"Protegido","usr_rst":"Reset","usr_del":"Excluir","nav_file":"Arquivos","fil_title":"Sistema de Arquivos","fil_down":"Baixar","fil_backup":"Backup","fil_restore":"Restaurar","fil_fw":"Firmware","fil_del":"Excluir","fil_uphere":"Enviar","fil_name":"Nome","fil_sz":"Tamanho","fil_loading":"Carregando...","nav_alm":"Alarmes e Sons","alm_title":"Alarmes e Sons","alm_limits":"Limites de Alarme","alm_none":"Nenhum sensor configurado.","alm_sounds":"Configuração de Sons","alm_touch":"Toque","alm_confirm":"Confirmação","alm_mel_asc":"Ascendente","alm_error":"Erro","alm_mel_desc":"Descendente","alm_alarm":"Alarme","alm_mel_siren":"Sirene","alm_web":"Sons Web","alm_attention":"Atenção","alm_mute":"Mudo Global","alm_volume":"Vol. Sistema","alm_alarm_vol":"Vol. Alarme","nav_lic":"Licença","lic_title":"Licença de Software","lic_mit":"Licença MIT","lic_notice":"Avisos de Terceiros","lic_sub":"Software livre. Abaixo, o que isso significa na prática.","lic_summary_title":"Em resumo","lic_summary":"Você pode usar, copiar, alterar, juntar, publicar, distribuir, sublicenciar e vender cópias deste software, de graça e sem restrição.\n\nA única obrigação é manter o aviso de copyright e o aviso de permissão em todas as cópias.\n\nO software é fornecido \"como está\", sem garantia de espécie alguma. O autor não responde por nenhum dano decorrente do uso.","lic_summary_note":"Resumo informativo, escrito para ser entendido. Não substitui o texto da licença.","lic_legal_note":"O texto abaixo fica em inglês porque é a versão original — é ela que tem valor legal.","greet_logout":"Sair","log_lock":"Bloqueado por {s}s. Tentativas excessivas.","log_full":"Limite de sessões atingido. Tente mais tarde.","log_err":"Usuário ou senha incorretos.","log_cookie_loop":"Login aceito, mas o navegador guarda uma sessão segura de uma visita HTTPS anterior. Abra uma janela anônima ou limpe os cookies deste site e entre de novo.","log_chpass_mismatch":"As senhas não coincidem.","log_chpass_same":"A nova senha deve ser diferente da atual.","log_chpass_ok":"Senha trocada. Faça login.","dash_ntp_ok":"Sincronizado","dash_ntp_fail":"Falha NTP","dash_ntp_conn":"Conectando...","dash_eval":"Avaliando...","dash_metr_min":"min","dash_metr_max":"max","dash_metr_ok":"ok","dash_metr_fail":"falhas","dash_metr_retry":"retry","dash_metr_last":"últ.","dash_metr_err":"erro","dash_free":"Livre","dash_tot":"Total","greet_hello":"Olá","greet_morning":"Bom dia","greet_afternoon":"Boa tarde","greet_evening":"Boa noite","hist_none_sel":"Nenhum","hist_n_sel":"selecionados","m_jan":"Jan","m_feb":"Fev","m_mar":"Mar","m_apr":"Abr","m_may":"Mai","m_jun":"Jun","m_jul":"Jul","m_aug":"Ago","m_sep":"Set","m_oct":"Out","m_nov":"Nov","m_dec":"Dez","hist_done":"Concluído","hist_down_msg":"Carregando...","hist_err_json":"Erro.","hist_no_data":"Sem dados.","hist_conn_lost":"Conexão perdida.","hist_ref":"Atualizar","hist_none":"Nenhum evento.","hist_clear_msg":"Limpar logs?","display_busy":"Display em uso. Tente novamente em alguns segundos.","hist_clear_err":"Falha ao limpar logs.","hist_cleared":"Logs limpos.","net_conn_err":"Erro de conexão.","exp_no_chart":"Carregue um intervalo no gráfico","exp_cancelled":"Cancelado","exp_empty":"Sem dados neste período","exp_logs_empty":"Clique em 'Carregar' primeiro","exp_rows":"linhas","cfg_sec_mqtt":"Usar MQTTS (TLS)","cfg_time_need":"Preencha data e hora.","cfg_time_ok":"Hora aplicada.","cfg_time_fail":"Falhou ao aplicar.","cfg_touch_confirm":"Resetar calibração do touch para padrão de fábrica?","cfg_touch_done":"Calibração resetada. Recalibre pelo display.","sens_load_fail":"Não foi possível carregar os slots de sensores.","sens_free":"livre","sens_active":"Ativo","sens_none":"Nenhum slot de sensor configurado ainda.","sens_empty":"vazio","sens_hwid_hint":"Chave usada pelo histórico e pela telemetria. Alterá-la inicia uma nova série.","sens_state":"Estado","sens_alarms":"Alarmes habilitados","sens_pins":"Atribuição de GPIO","sens_pin":"Pino","sens_calib":"Calibração","sens_ntp_no":"NTP não sincronizado.","sens_hist":"Gravação no histórico","sens_rebind_hint":"Um slot adicionado ou renomeado hoje não tem coluna no arquivo de histórico do dia, então não é gravado até amanhã. Isto reescreve esse arquivo para todos os slots, preservando os registros já feitos. O dispositivo reinicia ao final.","sens_rebind":"Recompor o histórico agora","sens_hw":"Hardware","sens_accept_hint":"Relê a sonda ligada neste slot e vincula o slot a ela. Use depois de trocar um DS18B20, quando o dispositivo acusar divergência de hardware.","sens_accept":"Adotar a sonda ligada aqui","sens_wipe_hint":"Marca tudo o que foi gravado antes de agora como pertencente ao sensor anterior. Os registros continuam no disco; os gráficos é que param de atribuí-los a este slot.","sens_wipe":"Resetar época do histórico","sens_free_slot":"Liberar slot","sens_revert":"Descartar alterações","sens_done":"Concluído","sens_stage_hint":"As edições são acumuladas conforme você digita e gravadas ao Salvar e Reiniciar.","sens_scan_busy":"Procurando...","sens_scan_none":"Nenhuma sonda respondeu.","sens_scan_found":"encontrada(s)","act_fail":"Falha na ação.","sens_accept_ok":"Slot vinculado à sonda encontrada nele.","sens_accept_none":"Nenhuma sonda respondeu neste GPIO.","sens_accept_unsaved":"Salve antes este slot como um DS18B20 ativo.","sens_accept_bad":"A sonda respondeu com uma ROM inválida.","sens_wipe_confirm":"Resetar a época do histórico deste slot?\n\nOs registros feitos antes de agora deixam de ser atribuídos a ele. Nada é apagado.","sens_wipe_ok":"Época do histórico resetada.","tel_sync_ok":"Envio disparado. Acompanhe o resultado no log.","tel_reset_confirm":"Resetar o cursor de telemetria?\n\nO dispositivo vai reenviar até 30 dias de histórico no próximo envio. Espere uma rajada de tráfego.","tel_reset_ok":"Cursor resetado. Os próximos envios cobrem até 30 dias atrás.","sens_rebind_pending":"Salve e Reinicie antes — recompor agora usaria a configuração anterior.","sens_rebind_confirm":"Reescrever o arquivo de histórico do dia para os slots atuais?\n\nOs registros já feitos são preservados. O dispositivo reinicia ao final.","sens_rebind_busy":"Reescrevendo...","sens_rebind_ok_forced":"Histórico reconstruído","sens_rebind_meas":"medições","sens_rebind_ok":"Histórico reescrito","sens_rebind_recs":"registros preservados","sens_rebind_reboot":"Reiniciando...","sens_rebind_force":"O arquivo de histórico do dia não pôde ser lido, então os registros não têm como ser aproveitados.\n\nRecriar vazio? Os registros de hoje se perdem. Os dias anteriores ficam intactos.","sens_rebind_kept":"Nada mudou.","sens_rebind_err":"Falha ao reescrever","sens_rebind_unsure":"Sem resposta do dispositivo. Recarregue a página e confira o log antes de tentar de novo.","sens_need_type":"Escolha um tipo antes de ativar este slot.","sens_need_pin":"Atribua um GPIO ao pino","sens_taken":"já está em uso pelo slot","sens_clear_confirm":"Liberar este slot? Tipo, GPIOs e identificação serão apagados.","net_conn":"Conectado","net_off":"Desconectado","usr_pend_del":"Pendente: Excluir","usr_pend_rst":"Pendente: Reset","usr_pend_add":"Pendente: Novo","usr_del_msg":"Excluir?","usr_rst_msg":"Forçar reset?","fil_parent":"Subir","fil_empty":"Vazio","fil_protected":"Arquivo protegido","fil_folder":"Pasta","fil_uploaded":"Upload concluído.","fil_up_err":"Erro no upload.","fil_sel_del":"Selecione","fil_conf_del":"Excluir N?","fil_del_err":"Alguns arquivos não puderam ser excluídos.","fil_deleted":"Arquivos excluídos.","fil_sel_down":"Selecione","fil_conf_down":"Baixar N?","fil_backup_started":"Download do backup iniciado.","fil_rst_val":"Etapa 1/3: Validando backup (CRC + chip ID)...","fil_rst_val_fail":"Validação falhou: ","fil_rst_confirm":"Backup válido — N arquivos (X bytes).\n\nIsto SOBRESCREVE os arquivos atuais do dispositivo (history, configs, sensores, themes).\nA configuração de Wi-Fi e a senha de admin do backup serão restauradas.\n\nProsseguir com a restauração?","fil_rst_app":"Etapa 2/3: Aplicando restore (pode levar ~15 s)...","fil_rst_app_fail":"Falha ao aplicar: ","fil_rst_reb":"Etapa 3/3: Reiniciando dispositivo (~25 s)...","fil_rst_off":"Dispositivo offline após 90 s. Verifique conexão e ligue manualmente se necessário.","fil_fw_warn":"Atualização de firmware via OTA.\n\nA partição de arquivos é REFORMATADA — a área de staging divide a partição com ela.\n\nSó a configuração sobrevive (capturada antes, restaurada depois):\n • Wi-Fi, senha de administrador, telemetria\n • Mapeamento de sensores e limites de alarme\n\nTodo o resto que está no dispositivo se perde:\n • Histórico de leituras (/history)\n • Pacotes de idioma (/lang)\n • Temas customizados (/themes)\n • Calibração de touch (/calib)\n • Arquivos em /web\n • O log de eventos\n\nUm backup .bkp será baixado automaticamente — restaure depois para recuperar tudo.\n\nProsseguir?","fil_fw_warn_slot":"Atualização de firmware via OTA.\n\nA imagem nova vai para o slot de onde o aparelho não subiu. Os arquivos do aparelho ficam como estão.\n\nDepois do reinício, a versão nova roda em teste. Ela fica depois de rodar saudável por um minuto. Se não rodar em cinco minutos, ou se o aparelho reiniciar antes disso, ele volta para a versão que roda agora.\n\nUm backup .bkp é baixado antes.\n\nProsseguir?","fil_fw_need_bin":"Selecione um arquivo .bin de firmware","fil_rst_need_bkp":"Selecione um arquivo .bkp de backup","fil_fw_bk":"Etapa 1/4: Baixando backup .bkp...","fil_fw_bk_fail":"Falha ao baixar backup. Tente novamente.","fil_fw_bk_corrupt":"Backup corrompido (CRC). Abortado.","fil_fw_apply":"Backup salvo no seu computador.\n\nIniciar a atualização?\n • Upload do firmware: ~30 s\n • Aplicação + reboot: ~25 s\n • Boot completo: ~25 s\n\nTotal: ~80 s. Não desligue o dispositivo durante o processo.","fil_fw_up":"Etapa 2/4: Enviando firmware (~30 s)...","fil_fw_stage_fail":"Falha no envio (validação v=","fil_fw_stage_refused":"Envio recusado (HTTP ","fil_fw_unsigned":"Este .bin não é assinado, e o aparelho só instala imagem assinada. Use um .bin de release do GitHub ou uma build do configurador.","fil_fw_v7":"A imagem é de outro modelo (env ","fil_fw_v8":"A imagem não é assinada. Use um .bin de release do GitHub ou uma build do configurador.","fil_fw_v9":"A assinatura não confere: o arquivo mudou depois de assinado, ou foi assinado por uma chave em que este aparelho não confia. Baixe o .bin de novo.","fil_fw_v10":"Foi assinada com uma chave aposentada. Use uma release mais nova.","fil_fw_v11":"Está abaixo do nível de segurança instalado, e voltar a ela pelo ar está bloqueado. Use esta versão ou uma mais nova.","fil_fw_v12":"Foi assinada com a chave de bancada, que este aparelho não aceita. Use um .bin de release do GitHub.","fil_fw_app":"Etapa 3/4: Aplicando firmware...","fil_fw_reb":"Etapa 4/4: Aguardando boot (~25 s)...","fw_trial":"Esta versão está em teste: ela fica depois de rodar saudável por um minuto. Reiniciar o aparelho antes disso traz de volta a versão anterior.","fw_reverted":"A atualização para a v{v} não se confirmou, e o aparelho voltou para esta versão (código 613 no log de eventos).","pending_notice":"Clique em \"Salvar e Reiniciar\" para aplicar as alterações.","commit_confirm":"Isto salvará todas as alterações e reiniciará o sistema.\n\nO dispositivo ficará offline por ~10 segundos.\nQualquer gravação de histórico/log em andamento será interrompida.\n\nContinuar?","calib_err":"Erro na calibração: ","commit_btn":"Salvar e reiniciar","calib_timeout":"Tempo esgotado na calibração. Tente novamente.","net_saved_port":"Salvo. Redirecionando para nova porta...","commit_saved":"Salvo! Reiniciando sistema...","commit_err":"Falha ao salvar.","commit_apply":"Aplicar agora","commit_try":"Testar","commit_live":"Não exige reinício","commit_why":"Reinicia por","commit_pend":"Alterações não salvas","commit_applied":"Aplicado e salvo","commit_tried":"Aplicado SEM salvar — um reinício desfaz","commit_rej":"Campos não aplicados","cal_pts_hint":"Até 5 pontos por grandeza, cada um ligando a leitura bruta ao valor mostrado por um instrumento confiável. Um ponto aplica um offset constante; mais pontos dobram a correção entre eles, mantida reta além das pontas. Deixe o bruto vazio para captar a leitura ao salvar.","cal_raw":"Bruto","cal_ref":"Referência","cal_corr":"corrigido","cal_cap":"Usar a leitura bruta atual","cal_del":"Remover este ponto","cal_add":"Adicionar ponto","cal_clear":"Remover correção","cal_none":"Sem correção — padrão do sensor.","cal_legacy":"Offset constante","cal_save_hint":"As correções são gravadas no Salvar e Reiniciar. Requer NTP sincronizado.","cal_err_max":"no máximo 5 pontos.","cal_err_num":"todo ponto precisa de bruto e referência numéricos.","cal_err_noread":"sem leitura ao vivo para captar — preencha o valor bruto.","cal_err_rng":"ponto fora da faixa plausível","cal_err_dup":"dois pontos com o mesmo valor bruto.","cal_mode_lbl":"Interpolação","cal_mode_lin":"Reta","cal_mode_cub":"Suave","cal_mode_hint":"Suave é uma cúbica monótona: dobra pelas âncoras sem jamais ultrapassá-las. Precisa de 3+ pontos; com menos, comporta-se como reta.","fil_fw_apply_err":"Falha ao aplicar: ","fil_fw_apply_fail":"Aplicação recusada (HTTP ","fil_view":"Ver","tv_org":"Organizado","tv_raw":"Original","tv_exp":"Expandir tudo","tv_col":"Recolher tudo","tv_close":"Fechar","tv_bad":"JSON incompleto ou inválido: organizado só pelas chaves e colchetes. O texto exato está em Original.","tv_bin":"Este arquivo não é texto. Baixe-o para abrir.","tv_err":"Não deu para ler o arquivo.","hist_cancel":"Cancelar","hist_lost":"perdidas","hist_partial":"Parcial: ","cfg_tls_noverify":"TLS sem validação de certificado — a conexão é cifrada mas não autenticada (MITM possível). Envie /cert.pem em Arquivos para validar o servidor.","creds_title":"Senha temporária","creds_warn":"Copie agora — ela aparece uma única vez. O usuário deve trocá-la no primeiro login.","creds_ok":"Guardei — recarregar","hist_files":"Arquivos","nav_tel":"Telemetria","tel_title":"Telemetria","tel_desc":"Transporte, formatos de payload e a segunda linha (alarmes). Salvar e Reiniciar aplica as alterações pendentes; Enviar agora / Resetar cursor atuam no dispositivo em execução.","al_title":"Payload de Alarmes — 2ª Linha de Telemetria","al_hint":"Linha separada para alarmes de sensores. Os registros ficam em fila na RAM e só saem após o servidor confirmar o recebimento (HTTP 2xx ou ACK MQTT em /alarm/ack). Transporte, servidor, credenciais e TLS seguem as configurações da telemetria principal.","al_pending":"Pendentes:","al_en":"Habilitar linha de telemetria de alarmes","al_qmax":"Tamanho da fila (RAM)","al_path":"Caminho HTTP","al_leg":"Tags da linha de alarme (um registro por borda de limite/erro):","al_smart":"Chaves Inteligentes","al_smart2":"(nome da chave = token, minúsculo ou maiúsculo):","al_smart_val":"número, ou a chave inteira é removida em caso de falha","al_smart_err":"código, ou a chave inteira é removida quando o outro domínio está ativo","cfg_bat_hint":"Máximo de registros por envio. Uma fila maior sai em lotes deste tamanho até acabar.","dash_disp_live":"Ao vivo","dash_disp_stop":"Parar","dash_disp_err":"Falha na leitura","dash_disp_tap":"Clique para tocar, segure para toque longo","dash_disp_taperr":"Toque recusado","usr_plim":"Limites (painel)","usr_pblk":"Bloqueio (painel)","usr_pmnt":"Manut. (painel)","usr_pin":"PIN do painel (opcional)","usr_pin_btn":"PIN","usr_haspin":"PIN","usr_pend_pin":"Pendente: PIN","usr_pin_msg":"Vazio remove o PIN.","usr_pin_bad":"PIN: %M a %X de %S.","cfg_pinkb":"Teclado do painel: glifos por tecla","cfg_pinalpha":"Caracteres do PIN","cfg_pinmin":"Tamanho mínimo do PIN","cfg_ct":"Cabeçalho Content-Type"} diff --git a/docs/MANUAL.pt-BR.html b/docs/MANUAL.pt-BR.html index e0f79c9f..0b123f93 100644 --- a/docs/MANUAL.pt-BR.html +++ b/docs/MANUAL.pt-BR.html @@ -2953,7 +2953,7 @@

      Adotar a sonda ligada aqui

    3. Abra o editor do slot.
    4. Toque em Adotar a sonda ligada aqui (Adopt the probe wired here).
    -

    O aparelho lê a ROM da sonda, liga o slot a ela e grava. Se a ROM já constar do arquivo calib.csv, o slot recebe o ID de hardware e o nome guardados para ela; se não constar, recebe o ID LIB_SENS. Quando o ID muda, o aparelho grava a data como a época do slot (Resetar época do histórico).

    +

    O aparelho lê a ROM da sonda no GPIO do slot, liga o slot a ela, grava e recarrega os sensores: a leitura segue com a sonda nova na hora, sem reiniciar. O GPIO do slot não muda. Se a ROM já constar do arquivo calib.csv, o slot recebe o ID de hardware e o nome guardados para ela; se não constar, recebe o ID LIB_SENS. Quando o ID muda, o aparelho grava a data como a época do slot (Resetar época do histórico).

    @@ -2972,18 +2972,21 @@

    Adotar a sonda ligada aqui

    - + + + + +
    Nenhuma sonda respondeu neste GPIO.Nada respondeu no pino lidoNada respondeu no GPIO do slot
    A sonda respondeu com uma ROM inválida. A ROM lida falhou na conferência
    Salve antes este slot como um DS18B20 ativo.O slot gravado no aparelho não é um DS18B20 ativo: por exemplo, o tipo foi escolhido no editor e ainda não foi salvo
    -