diff --git a/backend/app.py b/backend/app.py index 44a1f53..9483fa4 100644 --- a/backend/app.py +++ b/backend/app.py @@ -36,7 +36,7 @@ ADMINS = {u.strip().lower() for u in os.environ.get("ACTAS_ADMINS", "leo").split TIPOS_UNICOS = {"ensamble"} # tipos en los que un componente solo puede usarse en un acta real # Tipos REPETIBLES: una misma serie puede tener varias actas (cada visita a campo es un evento nuevo). TIPOS_REPETIBLES = {t.strip() for t in os.environ.get( - "ACTAS_REPETIBLES", "diagnostico-campo,instalacion-campo").split(",") if t.strip()} + "ACTAS_REPETIBLES", "diagnostico-campo,diagnostico-banco,instalacion-campo,reflasheo").split(",") if t.strip()} # Subida de fotos MAX_MB = int(os.environ.get("ADJUNTO_MAX_MB", "8")) @@ -561,5 +561,98 @@ def cambiar_password(): return jsonify(ok=True, usuario=destino, reset=es_reset) +# ------------------------------------------------- expediente de una unidad +def _listar_adjuntos(serie): + """Todas las fotos y logs subidos para esa serie, de cualquier tipo de acta.""" + fotos, logs = [], [] + if not os.path.isdir(ADJ): + return fotos, logs + for tipo in sorted(os.listdir(ADJ)): + base = os.path.join(ADJ, tipo, serie) + if not os.path.isdir(base): + continue + for fn in sorted(os.listdir(base)): + fp = os.path.join(base, fn) + if os.path.isfile(fp): + fotos.append({"tipo": tipo, "ruta": f"{tipo}/{serie}/{fn}", + "bytes": os.path.getsize(fp), "ts": int(os.path.getmtime(fp))}) + dlogs = os.path.join(base, "logs") + if os.path.isdir(dlogs): + for fn in sorted(os.listdir(dlogs)): + fp = os.path.join(dlogs, fn) + if os.path.isfile(fp): + logs.append({"tipo": tipo, "ruta": f"{tipo}/{serie}/logs/{fn}", + "nombre": fn[16:] if len(fn) > 16 else fn, + "bytes": os.path.getsize(fp), "ts": int(os.path.getmtime(fp))}) + return fotos, logs + + +@app.get("/api/unidad") +def expediente(): + """Todo lo que existe en el servidor sobre una serie: actas de cualquier tipo, + fotos, archivos de log, y las actas de otras unidades que la mencionan como componente.""" + serie = slug(request.args.get("serie"), 24) + if not serie: + return jsonify(ok=False, error="falta serie"), 400 + + actas, relacionadas = [], [] + if os.path.isdir(BASE): + for tipo in sorted(os.listdir(BASE)): + carpeta = os.path.join(BASE, tipo) + if not os.path.isdir(carpeta): + continue + for fn in sorted(os.listdir(carpeta)): + if not fn.endswith(".json"): + continue + try: + with open(os.path.join(carpeta, fn), encoding="utf-8") as f: + d = json.load(f) + except Exception: + continue + fila = {"tipo": tipo, "archivo": fn, "serie": d.get("serie"), + "recibido": d.get("_recibido"), "fecha": d.get("fecha"), + "tecnico": d.get("tecnico"), "resultado": d.get("resultado"), + "clasificacion": d.get("clasificacion"), "accion": d.get("accion"), + "motivo": d.get("motivo"), "maquina": d.get("maquina_id") or d.get("maquina"), + "fotos": [f2.get("ruta") for f2 in (d.get("fotos") or []) if f2.get("ruta")], + "archivos": [{"ruta": a.get("ruta"), "nombre": a.get("nombre")} + for a in (d.get("archivos") or []) if a.get("ruta")]} + if d.get("serie") == serie: + actas.append(fila) + elif isinstance(d.get("componentes"), dict) and serie in d["componentes"].values(): + fila["componentes"] = d["componentes"] + relacionadas.append(fila) + + # Orden del expediente: manda la FECHA que anotó el técnico (la de la visita); + # la hora de recepción solo desempata. Un acta capturada días después no debe + # aparecer como si el trabajo se hubiera hecho ese día. + def _clave(r): + return ((r.get("fecha") or (r.get("recibido") or "")[:10]), r.get("recibido") or "") + actas.sort(key=_clave, reverse=True) + relacionadas.sort(key=_clave, reverse=True) + fotos, logs = _listar_adjuntos(serie) + return jsonify(ok=True, serie=serie, actas=actas, relacionadas=relacionadas, + fotos=fotos, logs=logs, + total=len(actas), total_fotos=len(fotos), total_logs=len(logs)) + + +@app.get("/api/series") +def series(): + """Todas las series que tienen algo guardado — para el buscador del expediente.""" + vistas = {} + if os.path.isdir(BASE): + for tipo in sorted(os.listdir(BASE)): + carpeta = os.path.join(BASE, tipo) + if not os.path.isdir(carpeta): + continue + for fn in os.listdir(carpeta): + if fn.endswith(".json"): + ser = fn.rsplit("_", 1)[0] if "_" in fn else fn[:-5] + ser = fn.split("_2")[0] if "_2" in fn else ser + vistas.setdefault(ser, set()).add(tipo) + return jsonify(ok=True, series=[{"serie": k, "tipos": sorted(v)} + for k, v in sorted(vistas.items())]) + + if __name__ == "__main__": app.run(host="127.0.0.1", port=int(os.environ.get("PORT", "8081"))) diff --git a/docs/descargas/SHA256SUMS.txt b/docs/descargas/SHA256SUMS.txt index bd241b4..3a2434f 100644 --- a/docs/descargas/SHA256SUMS.txt +++ b/docs/descargas/SHA256SUMS.txt @@ -1 +1,2 @@ e1f351084e803306c376989b284ca0f9f9f1a3445e59494f8a5ff7a9652ad44c hbox-v5-bringup_0.5.1.bin +a8de74b3d7768e9d5656ae0bff05475445c0cd2599ecfdc4ab081e517ed3d6ef hbox-v5-bringup_0.4.0.bin diff --git a/docs/descargas/hbox-v5-bringup_0.4.0.bin b/docs/descargas/hbox-v5-bringup_0.4.0.bin new file mode 100644 index 0000000..7939648 Binary files /dev/null and b/docs/descargas/hbox-v5-bringup_0.4.0.bin differ diff --git a/docs/descargas/index.md b/docs/descargas/index.md index 38418f5..c7ad0e9 100644 --- a/docs/descargas/index.md +++ b/docs/descargas/index.md @@ -3,11 +3,38 @@ Archivos que el técnico necesita en el banco. Verificar el SHA-256 antes de flashear (`shasum -a 256 archivo` en macOS, `sha256sum archivo` en Linux, `certutil -hashfile archivo SHA256` en Windows). -## Firmware de bring-up — EinarBoard v5 +## Qué archivo va a qué cosa + +Una unidad HBOX tiene **tres cerebros distintos** y cada uno se carga por su propio camino. Antes de +descargar nada, ubica cuál vas a tocar: + +| Qué | Qué se le carga | Cómo | Dónde está | +|---|---|---|---| +| **Tarjeta HBOX** (Blackpill / EinarBoard) | Firmware `.bin` | DFU por USB | [§ Firmware del HBOX](#firmware-del-hbox-por-generacion-de-tarjeta) | +| **Módem 4G NTRIP Master** | Archivo de **parámetros** `$$$AS+…` | Terminal serie por el USB *XBEE* | [§ Módem 4G](#parametros-del-modem-4g-ntrip-master) | +| **simpleRTK2B Pro (ZED-F9P)** | Configuración u-center **y** firmware u-blox | u-center por el USB *POWER+GPS* | [§ ZED-F9P](#firmware-y-configuracion-del-simplertk2b-pro-zed-f9p) | + +Cada carga se registra en el **[acta de reflasheo](../formularios/reflasheo.html)**, y queda en el +[expediente de esa unidad](../unidad.html) junto con las capturas de antes y después. + +!!! warning "Verifica el SHA-256 antes de flashear" + Un binario incompleto o de otra generación puede dejar la tarjeta inservible. + `shasum -a 256 archivo` (macOS) · `sha256sum archivo` (Linux) · `certutil -hashfile archivo SHA256` (Windows). + +## Firmware del HBOX (por generación de tarjeta) + +| Generación | Archivo | Para qué sirve | +|---|---|---| +| **V3** — flota desplegada (RTK-003, RTK-006, RTK-014) | ⬜ hay que compilarlo | Firmware de campo: gobierna la unidad en la máquina. El código existe (`blackpillrtkcode_v3`, v3.0.3) | +| **V4** | ⬜ hay que compilarlo | `blackpillrtkcode_v4` (v4.0.0) — telemetría en banda: el `$PHLG` llega al servidor por el módem, no solo al cable USB | +| **V5** — EinarBoard v5 | ✅ `hbox-v5-bringup_0.5.1.bin` (abajo) | Bring-up de tarjeta en banco | + +### Firmware de bring-up — EinarBoard v5 | Archivo | Versión | SHA-256 | Uso | |---|---|---|---| -| [**hbox-v5-bringup_0.5.1.bin**](hbox-v5-bringup_0.5.1.bin) | **0.5.1** (2026-09-18) | `e1f351084e803306…` ([SHA256SUMS](SHA256SUMS.txt)) | Sección 5 del [procedimiento de prueba y flasheo v5](../procedimientos/v5-prueba-y-flasheo.md) y sección 5 del [ensamble](../procedimientos/v5-ensamble.md) (modo de banco `P` / `n`) | +| [**hbox-v5-bringup_0.4.0.bin**](hbox-v5-bringup_0.4.0.bin) | **0.4.0** (2026-09-17) — **campo, sin telemetría en banda** (probado en unidad) | `a8de74b3d7768e9d…` ([SHA256SUMS](SHA256SUMS.txt)) | +| [**hbox-v5-bringup_0.5.1.bin**](hbox-v5-bringup_0.5.1.bin) | **0.5.1** (2026-09-18) — **campo + Modo B / telemetría — EN PRUEBA** (reportado: etapa RTK no enciende) | `e1f351084e803306…` | Sección 5 del [procedimiento de prueba y flasheo v5](../procedimientos/v5-prueba-y-flasheo.md) y sección 5 del [ensamble](../procedimientos/v5-ensamble.md) (modo de banco `P` / `n`) | Flasheo por DFU en **J11** (mantener **SW2**, pulsar y soltar **SW1**, soltar SW2): @@ -17,8 +44,14 @@ dfu-util -a 0 -s 0x08000000:leave -D hbox-v5-bringup_0.5.1.bin o con **STM32CubeProgrammer** → USB → Connect → Download del `.bin` en `0x08000000`. +!!! danger "18-sep, tarde: A/B entre 0.4.0 y 0.5.1" + Se reportó que con **0.5.1** la etapa RTK no enciende y con **0.4.0** sí. Hasta cerrar el diagnóstico: + **embarcar con 0.4.0**. Para diagnosticar 0.5.1 en UNA unidad: consola en J11, tap a SW1, guardar todo desde el banner + hasta 2 min después; luego teclear `s`, `v` y `B` (bloquea Modo B) y anotar si la etapa enciende con `B`. + Mandar el log completo — el diagnóstico sale de ahí, no del LED. + !!! note "Tarjetas ya flasheadas con cualquier versión anterior" - Re-flashear con 0.5.1 por el mismo procedimiento DFU. **La primera línea de la consola tras reset dice la versión** + Re-flashear con 0.4.0 (o 0.5.1 solo para la prueba A/B) por el mismo procedimiento DFU. **La primera línea de la consola tras reset dice la versión** (`hbox-v5-bringup 0.5.1 built …`): si la línea `STAT` no termina en `stack=…`, la tarjeta sigue con 0.1.1. !!! tip "Consola 0.5.1" @@ -46,17 +79,19 @@ o con **STM32CubeProgrammer** → USB → Connect → Download del `.bin` en `0x LEDs, ADC) sin encender la etapa RTK ni el cargador. **Nunca flashear `blackpillrtkcode_v3` (v3.0.3) en una v5.** +**Novedades de 0.5.x:** el `$PHLG` gana 5 campos nuevos (`up_s`, `pwr`, `v5min`, `vbat_min`, `bkp_n` — `vbat_min` caza los hundimientos de 50-200 ms de las ráfagas LTE que una foto cada 30 s nunca ve) y aparece `$PHEV`, una trama por cada cambio de estado. Los analizadores viejos ignoran los campos extra. + Notas de compilación y cambios: [README del firmware de bring-up](hbox-v5-bringup-README.md). -## Firmware de campo — tarjetas V3 y V4 +### Firmware de campo — tarjetas V3 y V4 (detalle) Es el firmware que gobierna la unidad en la máquina (`blackpillrtkcode_v3` / `v4`), distinto del de bring-up del v5. Se usa en el [reflasheo en taller](../procedimientos/reflasheo-unidades.md). | Generación | Archivo | Estado | |---|---|---| -| **V3** — flota desplegada (RTK-003, RTK-006, RTK-014) | `hbox-fw-v3_X.Y.Z.bin` | ⬜ **pendiente de publicar** | -| **V4** | `hbox-fw-v4_X.Y.Z.bin` | ⬜ **pendiente de publicar** | +| **V3** — flota desplegada | `hbox-fw-v3_3.0.3.bin` | ⬜ **falta compilarlo y probarlo en banco** | +| **V4** | `hbox-fw-v4_4.0.0.bin` | ⬜ **falta compilarlo y probarlo en banco**. Escrito y validado en el simulador de host el 18-sep | !!! danger "Mientras diga «pendiente», no hay binario aprobado" No flashees una unidad V3 o V4 con un archivo que no esté publicado aquí con su SHA-256. Los @@ -100,15 +135,44 @@ Referencia rápida de los parámetros que más se consultan: ## Firmware y configuración del simpleRTK2B Pro (ZED-F9P) -| Archivo | Estado | Notas | +Aquí hay que separar dos cosas que se confunden: + +| Qué | De dónde sale | Estado | |---|---|---| -| Configuración del ZED-F9P (u-center `.txt`) | ⬜ **pendiente de publicar** | UART1 a 115200 (es lo que lee la tarjeta) y UART2 a la velocidad del módem | -| Firmware del ZED-F9P (u-blox) | Se descarga de u-blox | Anotar la versión instalada con **UBX-MON-VER** | +| **Firmware del ZED-F9P** | **Estándar de u-blox**, no es nuestro: [u-blox → ZED-F9P → Documentation & resources → Firmware Update](https://www.u-blox.com/en/product/zed-f9p-module). Serie **HPG**. Se carga con **u-center** (solo Windows) a 115200 | ✅ disponible en el sitio de u-blox | +| **Configuración de Helgen** (`.txt` de u-center) | **Nuestra.** Define UART1 a 115200 (que es lo que lee la tarjeta), UART2 a la velocidad del módem y qué mensajes NMEA salen | ⬜ **pendiente de publicar — este es el que de verdad falta** | + +!!! warning "No actualices el firmware del F9P sin una razón concreta" + El firmware de u-blox **no tiene nada que ver con nuestras fallas**: las correcciones llegan por + el módem 4G, no por el GNSS, y en las capturas de junio los receptores estaban sanos. Actualizarlo + es un proceso solo-Windows que puede dejar el receptor a medias, y **borra o invalida la + configuración**, que es justo la parte que sí importa. + + Primero anota qué versión trae (**u-center → View → Messages → UBX-MON-VER**) y déjala así. + Solo se actualiza si u-blox publica un arreglo para un problema que estemos viendo, y siempre en + banco, nunca en una unidad instalada. Para leer o cargar la configuración: u-center → *Tools → Receiver Configuration*. Después de cargar, **guardar en FLASH** y reconectar para confirmar que sobrevive al reinicio — una configuración que solo quedó en RAM se pierde al primer corte de energía, y así perdimos tiempo en junio. +## Cómo se publica un firmware nuevo + +Para que un archivo pueda aparecer en esta página tiene que pasar por aquí. No es burocracia: un +binario mal compilado deja unidades muertas en el patio. + +- [ ] Compilado con el `TARGET_` correcto para esa generación de tarjeta. +- [ ] **Probado en banco sobre hardware real**, no solo compilado sin errores. +- [ ] Verificada la polaridad del control de carga sobre la tarjeta. +- [ ] Nombre con versión: `hbox-fw-v3_X.Y.Z.bin`. +- [ ] Su línea en `SHA256SUMS.txt`. +- [ ] Anotado en el `CHANGELOG.md` del repositorio de firmware, con fecha y quién lo probó. +- [ ] Actualizada la tabla de esta página. + +Los archivos viven en `docs/descargas/` del repositorio `hbox-docs` y se publican con +`./scripts/deploy.sh`. Los `.bin` de firmware de campo están en `.gitignore` por tamaño y por +control de versiones: se copian a mano a esa carpeta antes de publicar. + ## Herramientas - **STM32CubeProgrammer** (Windows/macOS/Linux): https://www.st.com/en/development-tools/stm32cubeprog.html diff --git a/docs/diagnostico/tablero-campo.html b/docs/diagnostico/tablero-campo.html index cc11c8d..519be9e 100644 --- a/docs/diagnostico/tablero-campo.html +++ b/docs/diagnostico/tablero-campo.html @@ -132,7 +132,11 @@ function render(actas){ const vs = (porUnidad[u.serie]||[]); const box = el('div',{class:'unidad'}); const cab = el('div',{class:'cab'}); - cab.append(el('div',null, el('div',{class:'s',text:u.serie}), el('div',{class:'m',text:(u.maquina||'')+(u.nota?' · '+u.nota:'')}))); + cab.append(el('div',null, + el('div',{class:'s'}, el('a',{href:'../unidad.html?serie='+encodeURIComponent(u.serie), + style:'color:inherit;text-decoration:none;border-bottom:1px dotted currentColor', + title:'Ver el expediente completo',text:u.serie})), + el('div',{class:'m',text:(u.maquina||'')+(u.nota?' · '+u.nota:'')}))); const [cls,txt] = vs.length ? pill(vs[0]) : ['pend','Sin diagnóstico']; cab.append(el('span',{class:'pill '+cls,text:txt})); box.append(cab); diff --git a/docs/formularios/reflasheo.html b/docs/formularios/reflasheo.html new file mode 100644 index 0000000..f2f7589 --- /dev/null +++ b/docs/formularios/reflasheo.html @@ -0,0 +1,149 @@ + + +
+ + +