From caee07fde9ab9c807356806bb2f22a4a169c9678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leopoldo=20Rodr=C3=ADguez?= Date: Fri, 18 Sep 2026 11:46:48 -0600 Subject: [PATCH] pagina Mi cuenta: cambio y restablecimiento de contrasena --- DESPLIEGUE.md | 38 ++++ backend/actas-hbox.service | 2 + backend/app.py | 128 +++++++++++- docs/cuenta.html | 183 ++++++++++++++++++ docs/descargas/SHA256SUMS.txt | 2 +- docs/descargas/hbox-v5-bringup-README.md | 17 +- ...up_0.4.0.bin => hbox-v5-bringup_0.5.0.bin} | Bin 75524 -> 77312 bytes docs/descargas/index.md | 20 +- docs/index.md | 8 + docs/procedimientos/v5-ensamble.md | 2 +- docs/procedimientos/v5-prueba-final-unidad.md | 16 +- docs/procedimientos/v5-prueba-y-flasheo.md | 8 +- mkdocs.yml | 1 + scripts/sync-docs.sh | 2 +- 14 files changed, 400 insertions(+), 27 deletions(-) create mode 100644 docs/cuenta.html rename docs/descargas/{hbox-v5-bringup_0.4.0.bin => hbox-v5-bringup_0.5.0.bin} (57%) diff --git a/DESPLIEGUE.md b/DESPLIEGUE.md index d839fce..6ba6fe5 100644 --- a/DESPLIEGUE.md +++ b/DESPLIEGUE.md @@ -98,6 +98,42 @@ sudo cut -d: -f1 /etc/nginx/hbox-docs.htpasswd # lista de usuarios Administradores (quién puede borrar o reabrir actas) = variable `ACTAS_ADMINS` del servicio, hoy `leo`. Alan y Daniel quedan como técnicos: llenan y envían, no borran. +### 7 · Permisos para que los técnicos cambien su contraseña (una sola vez) + +La página **Mi cuenta** (`/cuenta.html`) deja que cada quien cambie su propia contraseña, y que el +administrador restablezca la de otro. Para eso el servicio tiene que poder **escribir** el archivo de +usuarios que nginx solo **lee**: + +```bash +# quién es el usuario de nginx (normalmente www-data) +grep -E "^\s*user" /etc/nginx/nginx.conf + +sudo chown leo:www-data /etc/nginx/hbox-docs.htpasswd +sudo chmod 640 /etc/nginx/hbox-docs.htpasswd +sudo systemctl restart actas-hbox +``` + +Así: **leo** (el servicio) escribe, **www-data** (nginx) lee, y nadie más puede ni leerlo. + +Comprueba que las contraseñas estén en el formato que el servicio sabe verificar: + +```bash +sudo cut -d: -f2 /etc/nginx/hbox-docs.htpasswd | cut -c1-6 +``` + +Todas deben decir `$apr1$`. Si alguna sale distinta (`$2y$` es bcrypt), esa persona no va a poder +cambiar la suya sola: restablécesela desde la propia página, que la vuelve a guardar en `$apr1$`. + +!!! note "Por qué no se usa el comando `htpasswd` desde el servicio" + Pasar una contraseña como argumento (`htpasswd -b … clave`) la deja visible en `ps` para + cualquiera con sesión en el servidor. El hash `$apr1$` se calcula dentro del proceso; está + verificado carácter por carácter contra la salida de `htpasswd`, incluyendo acentos y espacios. + +!!! tip "Al cambiar la contraseña, la sesión no se cae" + HTTP Basic se guarda en el navegador: quien cambie la suya sigue navegando con la anterior hasta + que cierre sesión o cierre el navegador. La página lo explica y ofrece la liga de **Salir** para + probarla de inmediato. No es un error. + --- ## B · Verificación después de desplegar @@ -158,3 +194,5 @@ rsync -az leo@git.helgen.online:/srv/projects/drogheda/adjuntos/ ~/respaldo-foto | Las fotos se ven rotas en el tablero | falta la carpeta `adjuntos` o su permiso | paso 3 | | `mkdocs build --strict` falla | un enlace del `nav` apunta a un archivo que no existe | revisa `mkdocs.yml` | | El técnico no puede entrar | usuario no creado, o se usó `htpasswd -c` y borró a los demás | paso 5 | +| «El servicio no tiene permiso de escribir el archivo de usuarios» | falta el `chown`/`chmod` del htpasswd | paso 7 | +| «Tu contraseña está guardada en un formato que no puedo verificar» | esa entrada es bcrypt, no `$apr1$` | restablecerla desde `/cuenta.html` como administrador | diff --git a/backend/actas-hbox.service b/backend/actas-hbox.service index d826880..c55df89 100644 --- a/backend/actas-hbox.service +++ b/backend/actas-hbox.service @@ -12,6 +12,8 @@ Environment=ADJUNTO_MAX_MB=8 Environment=ARCHIVO_MAX_MB=20 Environment=ACTAS_REPETIBLES=diagnostico-campo,instalacion-campo Environment=ACTAS_ADMINS=leo +Environment=HTPASSWD_FILE=/etc/nginx/hbox-docs.htpasswd +Environment=PASS_MIN=8 Environment=PORT=8081 ExecStart=/srv/projects/drogheda/hbox-docs/backend/venv/bin/gunicorn -w 2 -b 127.0.0.1:8081 app:app Restart=on-failure diff --git a/backend/app.py b/backend/app.py index 91733f8..44a1f53 100644 --- a/backend/app.py +++ b/backend/app.py @@ -26,7 +26,7 @@ Rutas: Actas compuestas (integracion, ensamble): traen d["componentes"] = {clave: serie_modulo}. En los tipos de TIPOS_UNICOS un módulo no puede aparecer en dos actas reales (409). """ -import os, re, json, csv, datetime, uuid +import os, re, json, csv, datetime, uuid, hashlib from flask import Flask, request, jsonify, abort, send_file app = Flask(__name__) @@ -435,5 +435,131 @@ def _muy_grande(e): f"logs: máximo {LOG_MAX_MB} MB)."), 413 +# ------------------------------------------------- contraseñas (HTTP Basic de nginx) +# El archivo de usuarios de nginx se escribe aquí mismo, sin invocar `htpasswd`: +# pasar una contraseña como argumento de un proceso la deja visible en `ps`. +HTPASSWD = os.environ.get("HTPASSWD_FILE", "/etc/nginx/hbox-docs.htpasswd") +PASS_MIN = int(os.environ.get("PASS_MIN", "8")) +ITOA64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + + +def _to64(v, n): + return "".join(ITOA64[(v >> (6 * i)) & 0x3F] for i in range(n)) + + +def apr1(password, salt): + """Hash Apache MD5 ($apr1$): lo que escribe htpasswd por defecto y lo que lee nginx.""" + pw = password.encode("utf-8") + sl = salt.encode("ascii") + ctx = hashlib.md5(pw + b"$apr1$" + sl) + final = hashlib.md5(pw + sl + pw).digest() + i = len(pw) + while i > 0: + ctx.update(final[:16] if i > 16 else final[:i]); i -= 16 + i = len(pw) + while i: + ctx.update(b"\0" if (i & 1) else pw[:1]); i >>= 1 + final = ctx.digest() + for i in range(1000): + c = hashlib.md5() + c.update(pw if (i & 1) else final) + if i % 3: c.update(sl) + if i % 7: c.update(pw) + c.update(final if (i & 1) else pw) + final = c.digest() + out = "".join(_to64((final[a] << 16) | (final[b] << 8) | final[c], 4) + for a, b, c in ((0, 6, 12), (1, 7, 13), (2, 8, 14), (3, 9, 15), (4, 10, 5))) + return "$apr1$" + salt + "$" + out + _to64(final[11], 2) + + +def _leer_htpasswd(): + if not os.path.isfile(HTPASSWD): + return None + with open(HTPASSWD, encoding="utf-8") as f: + return f.read().splitlines() + + +def _escribir_htpasswd(lineas): + """Reescribe el archivo de forma atómica, conservando permisos y dueño.""" + carpeta = os.path.dirname(HTPASSWD) or "." + st = os.stat(HTPASSWD) + tmp = os.path.join(carpeta, ".htpasswd.tmp-" + uuid.uuid4().hex[:8]) + with open(tmp, "w", encoding="utf-8") as f: + f.write("\n".join(lineas) + "\n") + os.chmod(tmp, st.st_mode & 0o7777) + try: + os.chown(tmp, st.st_uid, st.st_gid) + except PermissionError: + pass + os.replace(tmp, HTPASSWD) + + +@app.get("/api/usuarios") +def usuarios(): + """Lista de usuarios (solo administrador), para el restablecimiento.""" + if not is_admin(): + return jsonify(ok=False, error="Solo un administrador puede ver la lista."), 403 + lineas = _leer_htpasswd() + if lineas is None: + return jsonify(ok=False, error="No encuentro el archivo de usuarios."), 500 + return jsonify(ok=True, usuarios=sorted(l.split(":", 1)[0] for l in lineas if ":" in l)) + + +@app.post("/api/password") +def cambiar_password(): + yo = current_user() + if not yo: + return jsonify(ok=False, error="No puedo identificarte. Vuelve a iniciar sesión."), 403 + try: + d = request.get_json(force=True) or {} + except Exception: + return jsonify(ok=False, error="JSON inválido"), 400 + + destino = str(d.get("usuario") or yo).strip() + actual = str(d.get("actual") or "") + nueva = str(d.get("nueva") or "") + confirma = str(d.get("confirma") or "") + es_reset = (destino.lower() != yo.lower()) + + if es_reset and not is_admin(): + return jsonify(ok=False, error="Solo puedes cambiar tu propia contraseña."), 403 + if len(nueva) < PASS_MIN: + return jsonify(ok=False, error=f"La nueva contraseña debe tener al menos {PASS_MIN} caracteres."), 400 + if nueva != confirma: + return jsonify(ok=False, error="Las dos contraseñas nuevas no coinciden."), 400 + if not es_reset and nueva == actual: + return jsonify(ok=False, error="La nueva contraseña es igual a la actual."), 400 + + lineas = _leer_htpasswd() + if lineas is None: + return jsonify(ok=False, error="No encuentro el archivo de usuarios en el servidor."), 500 + + idx = next((i for i, l in enumerate(lineas) + if ":" in l and l.split(":", 1)[0] == destino), None) + if idx is None: + return jsonify(ok=False, error=f"El usuario {destino} no existe."), 404 + + guardado = lineas[idx].split(":", 1)[1] + if not es_reset: + partes = guardado.split("$") + if not guardado.startswith("$apr1$") or len(partes) < 4: + return jsonify(ok=False, error="Tu contraseña está guardada en un formato que este " + "servicio no puede verificar. Pide a Leo que la restablezca."), 400 + if apr1(actual, partes[2]) != guardado: + return jsonify(ok=False, error="La contraseña actual no es correcta."), 403 + + salt = "".join(ITOA64[b % 64] for b in os.urandom(8)) + lineas[idx] = destino + ":" + apr1(nueva, salt) + try: + _escribir_htpasswd(lineas) + except PermissionError: + return jsonify(ok=False, error="El servicio no tiene permiso de escribir el archivo de " + "usuarios. Avisa a Leo (permisos de hbox-docs.htpasswd)."), 500 + except OSError as e: + return jsonify(ok=False, error="No se pudo guardar: " + str(e)), 500 + + return jsonify(ok=True, usuario=destino, reset=es_reset) + + if __name__ == "__main__": app.run(host="127.0.0.1", port=int(os.environ.get("PORT", "8081"))) diff --git a/docs/cuenta.html b/docs/cuenta.html new file mode 100644 index 0000000..7f78b1c --- /dev/null +++ b/docs/cuenta.html @@ -0,0 +1,183 @@ + + + + + +Mi cuenta — HBOX + + + + +
+
+

Mi cuenta

+

Aquí cambias tu contraseña de acceso al sitio.

+
+
+ ← Documentación + Cerrar sesión +
+ +
Consultando quién eres…
+ +
+

Cambiar mi contraseña

+

Elige algo que recuerdes sin apuntarlo. Mínimo 8 caracteres.

+
+ + +
+
+ + +
+
Mientras más larga, mejor. Una frase con espacios funciona y se recuerda.
+
+
+ + + 👁 Ver lo que escribí +
+ +
+
+ + + +
+

Si olvidaste tu contraseña

+

+ No hay recuperación por correo: pídele a Leo que te la restablezca desde esta misma página. + Es cosa de un minuto. +

+
+
+ + + + diff --git a/docs/descargas/SHA256SUMS.txt b/docs/descargas/SHA256SUMS.txt index eef56a1..8c0bf9b 100644 --- a/docs/descargas/SHA256SUMS.txt +++ b/docs/descargas/SHA256SUMS.txt @@ -1 +1 @@ -a8de74b3d7768e9d5656ae0bff05475445c0cd2599ecfdc4ab081e517ed3d6ef hbox-v5-bringup_0.4.0.bin +98656ddcc4e209e291d554341318c74d3f05713ce7198c7f6dc2d816eb3c6c3f hbox-v5-bringup_0.5.0.bin diff --git a/docs/descargas/hbox-v5-bringup-README.md b/docs/descargas/hbox-v5-bringup-README.md index 9eb1cbe..6954428 100644 --- a/docs/descargas/hbox-v5-bringup-README.md +++ b/docs/descargas/hbox-v5-bringup-README.md @@ -1,7 +1,7 @@ # hbox_v5_bringup — EinarBoard v5 first-flash bring-up firmware Bench-proves a newly assembled v5 board over USB-C **J11** only. Never powers the RTK stack. -0.4.0 field-minimum: AUTO by default (stack self-powers with 5 V on P2, kept on through 5 V loss), watchdog, fan, link recovery, LEDs; charge OFF. Bench mode: `P`+`Y` stack ON/OFF, `n` NMEA monitor (fix 4 = RTK FIX). Console (0.2.1): CR+LF lines, `EVT` on every state change, named `STAT` every 5 s, `$PHLG` every 10 s in the fleet v3.x layout (server-parseable). +0.5.0: Modo B in-band $PHLG (server Salud column) on top of the 0.4.0 field-minimum: AUTO by default (stack self-powers with 5 V on P2, kept on through 5 V loss), watchdog, fan, link recovery, LEDs; charge OFF. Bench mode: `P`+`Y` stack ON/OFF, `n` NMEA monitor (fix 4 = RTK FIX). Console (0.2.1): CR+LF lines, `EVT` on every state change, named `STAT` every 5 s, `$PHLG` every 10 s in the fleet v3.x layout (server-parseable). Checks: HSE 25 MHz + USB CDC · LSE Y4 · I2C: PCF8574 0x20 / SHT40 0x44 / TMP102 0x48 / SC16IS752 0x4D · gauge + RGB LED walk · ADC · digital inputs. Prints PASS/FAIL, then a `STAT` line every 2 s. Console commands: `r` rerun · `g` gauge walk · `i` I2C scan · `h` help. @@ -23,14 +23,15 @@ generic variant defaults to HSI, which is not accurate enough for USB. ## Flash (DFU, J11) Hold **SW2** (BOOT0) → tap **SW1** (RESET) → release SW2 → device `0483:DF11` appears. ```bash -dfu-util -a 0 -s 0x08000000:leave -D release/hbox-v5-bringup_0.4.0.bin +dfu-util -a 0 -s 0x08000000:leave -D release/hbox-v5-bringup_0.5.0.bin ``` -or STM32CubeProgrammer → USB → Connect → Download `release/hbox-v5-bringup_0.4.0.bin` @ `0x08000000`. +or STM32CubeProgrammer → USB → Connect → Download `release/hbox-v5-bringup_0.5.0.bin` @ `0x08000000`. ## Release | File | Version | SHA-256 (first 16) | Size | |---|---|---|---| -| **`release/hbox-v5-bringup_0.4.0.bin`** | **0.4.0 (2026-09-17) — USE THIS (field-minimum, AUTO by default)** | `a8de74b3d7768e9d` | 75 524 B | +| **`release/hbox-v5-bringup_0.5.0.bin`** | **0.5.0 (2026-09-18) — USE THIS (field-minimum + Modo B telemetry)** | `98656ddcc4e209e2` | 77 312 B | +| `release/superseded/hbox-v5-bringup_0.4.0.bin` | 0.4.0 — superseded (no in-band telemetry) | `a8de74b3d7768e9d` | 75 524 B | | `release/superseded/hbox-v5-bringup_0.3.0.bin` | 0.3.0 — superseded (AUTO opt-in, no watchdog/fan/recovery) | `d4608c739cef2661` | 72 860 B | | `release/superseded/hbox-v5-bringup_0.2.1.bin` | 0.2.1 — superseded (no AUTO mode; stack OFF on 5 V loss) | `78b34f4603550c05` | 70 084 B | | `release/superseded/hbox-v5-bringup_0.2.0.bin` | 0.2.0 — superseded (LF-only console, no EVT/$PHLG) | `8491625d494f9ee1` | 64 780 B | @@ -38,6 +39,14 @@ or STM32CubeProgrammer → USB → Connect → Download `release/hbox-v5-bringup | `release/hbox-v5-bringup_0.1.0.bin` | 0.1.0 — SUPERSEDED (no float printf, ETH_CS driven) | `1a9a77ae479c8f86` | 41 040 B | ## Changelog +- **0.5.0** (2026-09-18) — **Modo B: `$PHLG` in band.** Once the stack is on and the F9P has been talking for 10 s, USART1 + (PA10 tap / PA9) is started **before** K1 (PB5) is energised; the F9P TX2 stream then flows F9P → PA10 → MCU → PA9 → K1 NO → modem DIN + byte for byte, and a `$PHLG` (same 24 fields) is injected every 30 s only after > 8 ms of line idle (a GGA is never split). + Corrections modem → F9P stay on their direct wire. Fail-safe is hardware: PB5 low (reset, IWDG, any stack-off) = K1 NC = direct path. + Prerequisites shipped with it: **256-byte UART rings** (`build_opt.h`, `static_assert`ed at compile), **non-blocking SHT40** + (two-phase kick/poll), console STAT/`$PHLG` deferred to line-idle gaps, K1 released before `EEPROM.write` (flash erase stalls the core) + and before the `r` self-tests. `B` toggles Modo B (default allowed). `STAT` adds `modoB= relay= phlg_aire=`. + Server: no change needed — `retranslator._parse_phlg` already accepts the sentence; units get the **Salud** column. - **0.4.0** (2026-09-17) — **field-minimum ("-fm")**: what a unit needs to be left on a machine for days **without the production charge governor**. **AUTO is now the default** (only the flash byte `0x5A` means MANUAL; a virgin board powers the stack by itself). IWDG 16 s watchdog · fan policy = v3.0.3 (vehicle 5 V+ign → 100 %; battery → thermostat 48/45 C on the hottest sensor) · diff --git a/docs/descargas/hbox-v5-bringup_0.4.0.bin b/docs/descargas/hbox-v5-bringup_0.5.0.bin similarity index 57% rename from docs/descargas/hbox-v5-bringup_0.4.0.bin rename to docs/descargas/hbox-v5-bringup_0.5.0.bin index 7939648ece7287ee885cf1d3dc98387ac003a02e..3288d08f626401f84dcc87881c65a46d92121fea 100644 GIT binary patch delta 16116 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 iy8E}?d;aS_brAxuxi`8N+Y^v_?$GgYr6ST6Z%) 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 50 °C. `A` de nuevo regresa a MANUAL. El cargador sigue apagado en todos los modos. diff --git a/docs/index.md b/docs/index.md index 81875e7..1f0691b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -109,6 +109,14 @@ Sitio interno de Helgen Technologies. **Busca abajo lo que vas a hacer hoy.** [Índice de actas](actas/index.md) · [Tablero de producción](tablero.html) +- ### 👤 Mi cuenta + + --- + + Cambiar tu contraseña del sitio. Si la olvidaste, Leo te la restablece desde ahí mismo. + + [Cambiar mi contraseña](cuenta.html) + - ### 🌿 Git y archivos de diseño --- diff --git a/docs/procedimientos/v5-ensamble.md b/docs/procedimientos/v5-ensamble.md index c4bc6f0..6002551 100644 --- a/docs/procedimientos/v5-ensamble.md +++ b/docs/procedimientos/v5-ensamble.md @@ -53,7 +53,7 @@ si el módulo ya está asignado. | Ítem | Notas | |---|---| | Gabinete HBOX con panel perforado (GX16-5, 3 × SMA, RJ45, prensaestopas) | según el diseño mecánico vigente | -| EinarBoard **V5-NN** aprobada, con firmware de bring-up **0.4.0 o superior** | ver acta de tarjeta | +| EinarBoard **V5-NN** aprobada, con firmware de bring-up **0.5.0 o superior** | ver acta de tarjeta | | simpleRTK2B Pro **ARD-NNN** y módem 4G NTRIP Master **RTK-NNNN** aprobados e integrados | con sus bolsas | | Fuente **Mean Well SD-15B-05** (24 V → 5 V) | va dentro del HBOX | | Pack de batería **1S4P** con NTC (3 hilos → P4) | conectar al final | diff --git a/docs/procedimientos/v5-prueba-final-unidad.md b/docs/procedimientos/v5-prueba-final-unidad.md index f06fd9b..ed10ded 100644 --- a/docs/procedimientos/v5-prueba-final-unidad.md +++ b/docs/procedimientos/v5-prueba-final-unidad.md @@ -1,13 +1,13 @@ -# HBOX v5 — Prueba final de unidad ensamblada con firmware 0.4.0 (field-minimum) +# HBOX v5 — Prueba final de unidad ensamblada con firmware 0.5.0 (field-minimum + telemetría) **Alcance:** unidad completa (EinarBoard V5 + ArduSimple + módem 4G + batería + antenas) dentro del HBOX, antes de embarcar. -**Firmware:** `hbox-v5-bringup_0.4.0.bin` (SHA-256 `a8de74b3…`) — descarga en . +**Firmware:** `hbox-v5-bringup_0.5.0.bin` (SHA-256 `98656ddc…`) — descarga en . **Duración:** ~45 min por unidad (P1–P6). Registrar todo en el acta de ensamble (`HBOX5-Exx-Axxx-Rxxxx`). -!!! warning "Lo que 0.4.0 NO hace" +!!! warning "Lo que 0.5.0 NO hace" **No carga la batería** (cargador apagado). El paquete es de un solo uso: sostiene la unidad cuando se va el 5 V del vehículo hasta 3.40 V y después la unidad solo funciona con alimentación del vehículo. Las unidades regresan a laptop (DFU en J11, 2 min) - cuando salga el firmware de producción. Anotar en el acta: *«fw 0.4.0-fm, cargador OFF»*. + cuando salga el firmware de producción. Anotar en el acta: *«fw 0.5.0-fm, cargador OFF»*. ## 0. Preparación - Consola: cable USB-C en **J11**, PuTTY/CoolTerm **115200**. Cada línea es `STAT`, `EVT` o `$PHLG`; teclear `l` para la leyenda. @@ -20,7 +20,7 @@ Energizar 24 V (con ignición) y **no tocar la consola**. | Esperado | Si falla | |---|---| -| Banner `hbox-v5-bringup 0.4.0` · `modo=AUTO` · `inputs at boot: v5=5.2–5.4V(ok) ign=ON` | otra versión → re-flashear; `ign=OFF` → arnés pin 3 / divisor 22k-10k | +| Banner `hbox-v5-bringup 0.5.0` · `modo=AUTO` · `inputs at boot: v5=5.2–5.4V(ok) ign=ON` | otra versión → re-flashear; `ign=OFF` → arnés pin 3 / divisor 22k-10k | | `---- RESULT: 12 PASS / 0 FAIL ----` · `>>> BOARD OK` | cualquier `[FAIL]` → tarjeta a revisión, no embarcar | | `EVT … STACK ON reason=auto` **sin teclear nada** (≈10 s tras el banner) | no enciende → `STAT` muestra `v5=` bajo 4.5 o `modo=MAN` (teclear `A`) | | `EVT … NMEA primera trama` < 3 s después · `EVT FAN ON (vehiculo: 5V+ign)` | sin NMEA → ArduSimple no arranca: medir SW_5V (MOD1 pin 5) ≈ 5 V | @@ -33,7 +33,9 @@ Dejar la unidad 10 min encendida con el módem registrándose (LEDs del módem / |---|---| | `STAT … v5=5.2x–5.3x(ok)` **todo el tiempo**; nunca `(BAJO)` ni `EVT V5 BAJO` | `BAJO` → el riel cae con la ráfaga LTE: SD-15B ajustado a ~5.3 V en P2 (memo 24-Jul, PFI LTC4040) | | `tbat`/`tair`/`tmcu` estables (< 45 °C en banco) · `i2cerr=0` | `i2cerr` subiendo → bus I²C / soldadura U3/U4/U13 | -| `$PHLG,0.4.0-fm,RUN,…` cada 10 s, `rec=0`, sin `LIMP` | `rec` > 0 sin causa → NMEA intermitente: revisar MOD1 / UART1 F9P | +| `$PHLG,0.5.0-fm,RUN,…` cada 10 s, `rec=0`, sin `LIMP` | `rec` > 0 sin causa → NMEA intermitente: revisar MOD1 / UART1 F9P | +| **Modo B:** ≈10 s tras la primera NMEA, `EVT MODOB ON`; luego `STAT … modoB=ON relay=` (sube cientos/s) `phlg_aire=` (+1 cada 30 s) | `modoB=off` permanente → sin NMEA en PA3 o `B` bloqueado; `relay=0` con `modoB=ON` → K1 / PA10 | +| **Servidor:** en ≤ 2 min la unidad `H00xx` muestra la columna **Salud** (fw `0.5.0-fm`, vbat, temperaturas) y sigue reportando posición 1 Hz | posición sí pero sin Salud → el módem no reenvía la `$PHLG`: revisar `$$$AS+` (modo transparente) | ## P3 — Respaldo de batería (6 min) — **la prueba que nunca se había hecho en v5** Con la etapa ON: **desconectar el 24 V** (se va el 5 V del SD-15B). Esperar 5 min. Reconectar. @@ -63,7 +65,7 @@ Con la etapa ON y > 90 s de encendida: **retirar el ArduSimple del socket** (o s ## P6 — Apagado y acta (2 min) - Quitar ignición: `EVT IGN OFF`, `EVT FAN OFF (bateria: termostato)`; la etapa sigue ON mientras haya 5 V (el SD-15B sigue con 24 V). - Quitar 24 V: si la batería está, entra `BACKUP` (P3); dejarla hasta `SLEEP` **no** es necesario — reconectar y apagar con `P`. -- Guardar el log. En el acta: versión (`0.4.0-fm`), `uid=` del banner, resultado P1–P5, `vbat` final, y la nota *cargador OFF*. +- Guardar el log. En el acta: versión (`0.5.0-fm`), `uid=` del banner, resultado P1–P5, `vbat` final, y la nota *cargador OFF*. ## Lectura rápida del LED RGB (para el técnico de campo) | Color | Significado | diff --git a/docs/procedimientos/v5-prueba-y-flasheo.md b/docs/procedimientos/v5-prueba-y-flasheo.md index e3f1c64..4c4b8e1 100644 --- a/docs/procedimientos/v5-prueba-y-flasheo.md +++ b/docs/procedimientos/v5-prueba-y-flasheo.md @@ -37,7 +37,7 @@ y dejarla flasheada con el firmware de arranque entregado por ingeniería. | 1 | Fuente de banco 0–30 V con límite de corriente ajustable | | 1 | Cable USB-C ↔ USB-C (o USB-A ↔ USB-C) **con datos** (no solo carga) | | 1 | Laptop con **STM32CubeProgrammer** instalado (o `dfu-util` en terminal) | -| 1 | Archivo de firmware: **`hbox-v5-bringup_0.4.0.bin`** — descarga: **https://drogheda.helgen.online/descargas/** (repo: `04-Source-Code/firmware/hbox_v5_bringup/release/`, SHA-256 inicia `a8de74b3`) | +| 1 | Archivo de firmware: **`hbox-v5-bringup_0.5.0.bin`** — descarga: **https://drogheda.helgen.online/descargas/** (repo: `04-Source-Code/firmware/hbox_v5_bringup/release/`, SHA-256 inicia `98656ddc`) | | 1 | Jumper 2.54 mm para P11 (interruptor de corte) | | 1 | Lupa o lámpara con aumento | | — | Hoja de registro (sección 7) | @@ -183,7 +183,7 @@ Solo para tarjetas que pasaron las secciones 1 a 4. 2. Arriba a la derecha, en el selector de conexión elegir **USB**. Pulsar el botón de refrescar; debe aparecer `USB1`. Pulsar **Connect**. 3. Ir a la pestaña **Erasing & Programming** (icono de flecha hacia abajo, lado izquierdo). -4. **File path** → seleccionar `hbox-v5-bringup_0.4.0.bin`. +4. **File path** → seleccionar `hbox-v5-bringup_0.5.0.bin`. 5. **Start address** = `0x08000000`. Marcar **Verify programming** y **Run after programming**. 6. Pulsar **Start Programming**. Esperar "File download complete" y "Verify… successful". 7. Pulsar **Disconnect**. @@ -192,7 +192,7 @@ Solo para tarjetas que pasaron las secciones 1 a 4. ```bash dfu-util -l # debe listar [0483:df11] -dfu-util -a 0 -s 0x08000000:leave -D hbox-v5-bringup_0.4.0.bin +dfu-util -a 0 -s 0x08000000:leave -D hbox-v5-bringup_0.5.0.bin ``` ### 5d. Verificación después del flasheo @@ -206,7 +206,7 @@ dfu-util -a 0 -s 0x08000000:leave -D hbox-v5-bringup_0.4.0.bin | **D17** (LED azul) + LED de panel en P8 (si hay) | **Parpadean** a 1 Hz (MCU ejecutando el firmware) | | **DB1 → DB4** (LEDs del indicador de batería) | Se encienden **en secuencia** una vez, luego se apagan; después R, G, B en P14 si hay LED RGB conectado | | Laptop | Aparece un **puerto serie** nuevo ("STMicroelectronics Virtual COM Port" / `usbmodem…`) | -| Terminal serie a **115200 baud** | Encabezado `hbox-v5-bringup 0.4.0` (cualquier otra version: re-flashear), luego líneas `[PASS]` / `[FAIL]`, y al final **`>>> BOARD OK`** | +| Terminal serie a **115200 baud** | Encabezado `hbox-v5-bringup 0.5.0` (cualquier otra version: re-flashear), luego líneas `[PASS]` / `[FAIL]`, y al final **`>>> BOARD OK`** | 3. Si el terminal se abrió tarde y no se vio el reporte: teclear **`r`** en el terminal → repite todas las pruebas (y vuelve a encender los LEDs en secuencia). diff --git a/mkdocs.yml b/mkdocs.yml index 0b05248..bd0407b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -88,5 +88,6 @@ nav: - "Histórico — Proto bench v3": procedimientos/historico-proto-bench-v3.md - "Histórico — Protoboard": procedimientos/historico-protoboard.md + - "👤 Mi cuenta (cambiar contraseña)": cuenta.html - "⚙ Administración (Leo)": admin.html - "🚪 Salir": salir.html diff --git a/scripts/sync-docs.sh b/scripts/sync-docs.sh index b468443..e075e80 100644 --- a/scripts/sync-docs.sh +++ b/scripts/sync-docs.sh @@ -22,7 +22,7 @@ declare -a MAP=( "03-Architecture-Hardware/kicad/docs/GIT-GUIDE_Colaboradores-Externos_ES.md|docs/git/guia-git-es.md" "03-Architecture-Hardware/kicad/docs/GIT-GUIDE_External-Collaborators_EN.md|docs/git/git-guide-en.md" # Descargas para técnicos (firmware de bring-up v5) - "04-Source-Code/firmware/hbox_v5_bringup/release/hbox-v5-bringup_0.4.0.bin|docs/descargas/hbox-v5-bringup_0.4.0.bin" + "04-Source-Code/firmware/hbox_v5_bringup/release/hbox-v5-bringup_0.5.0.bin|docs/descargas/hbox-v5-bringup_0.5.0.bin" "04-Source-Code/firmware/hbox_v5_bringup/release/SHA256SUMS|docs/descargas/SHA256SUMS.txt" "04-Source-Code/firmware/hbox_v5_bringup/README.md|docs/descargas/hbox-v5-bringup-README.md" )