Initial commit (Hub backfill)

This commit is contained in:
Leopoldo Rodríguez 2026-06-21 23:39:49 -06:00
commit fd7b26b357
77 changed files with 6179 additions and 0 deletions

View File

@ -0,0 +1,49 @@
---
name: project-drogheda
description: Load Project Drogheda context — the Helgen HBOX GPS-RTK tracker fleet deployed at Hutchison Ports ICAVE (Veracruz). Use whenever Leo works on anything Drogheda/HBOX-related: the simpleRTK2B / u-blox ZED-F9P, the 4G NTRIP Master, the Blackpill STM32 watchdog (blackpillrtkcode.ino), PowerBoost/LiPo power, the IP65 enclosure, retranslator.py / NTRIP caster / Wialon, the field failures and thermal issues, the watchdog PCB design, or any document/diagram/firmware destined for the Project-Drogheda folder — even if "Drogheda" isn't named.
---
# Project Drogheda — HBOX RTK Tracker
You are helping **Helgen Technologies** with **Project Drogheda**: a fleet of
vehicle-mounted high-precision **GPS-RTK trackers (HBOX)** deployed on heavy
equipment at **Hutchison Ports ICAVE, Veracruz, Mexico**, streaming RTK-corrected
position to the client's **Wialon** portal.
## Always do first
1. Read `CLAUDE.md` in the project root — it is the source of truth for hardware,
firmware, backend, deployment, people, and the current crisis.
2. Read `PLAN.md` for current priorities and open tasks.
3. For failures/diagnosis, read `06-Testing-Validation/RCA-2026-06-field-failures.md` (keep it a
ranked differential — don't over-anchor on "heat" before the cheap backend/
SIM/caster checks are ruled out).
## System in one breath
simpleRTK2B (u-blox ZED-F9P) + 4G NTRIP Master pull RTCM3 over LTE and emit NMEA;
a Blackpill STM32 **watchdog** manages power (vehicle/ignition/LiPo) and restarts
the stack when the link drops; `retranslator.py` filters NMEA (anti-jump + speed)
and forwards to Wialon. Power: 12–24 V → PowerBoost 1000C (+LiPo) → switched 5 V.
## Hard-won facts (don't relearn these)
- **NMEA GGA quality:** 0 none · 1 GPS · 2 DGPS · **4 RTK-fixed** · **5 RTK-float**.
Corrections-healthy = quality **≥4** (fixed AND float are good).
- The **ZED-F9P is rated −40…+85 °C** — it's rarely the heat casualty. The weak
links are the **LiPo** (charge ≤45 °C), the **PowerBoost**, and the **4G modem**.
- Corrections come over the **4G modem**, not the GNSS — so a GNSS hot-reset does
**not** fix a dead NTRIP link. Recover by **power-cycling the stack**.
- UBX CFG-RATE 1 Hz/30 s commands and checksums are verified (see firmware-review).
## Conventions
- Firmware: edit `04-Source-Code/firmware/blackpillrtkcode_v2/blackpillrtkcode_v2.ino`; **bench-test before field
flash** (the sandbox can't compile for STM32). Keep v1 as reference.
- Hardware/BOM changes go in `03-Architecture-Hardware/hardware-respec-and-pcb-brief.md`.
- Dates absolute (e.g., 2026-06-19). Don't leak internal paths to the client.
- Client-facing material is **Spanish** and routes through Leo.
## People
Helgen: Leo (founder/CEO), Adriana, Paola, Daniel, Gibran.
Client (ICAVE): **Agustín Alfonseca** (Supervisor de Investigación de Tecnología),
Leon David Piña (Hutchison Ports).
## Codename
Helgen uses Irish place-names (Ennis, **Drogheda**). This project = "Drogheda."

25
.gitignore vendored Normal file
View File

@ -0,0 +1,25 @@
# Secrets — never commit
.mcp.json
**/.env
**/.env.*
!**/*.example
**/token.json
**/credentials.json
**/*.pem
**/*.key
**/id_rsa*
# OS / editor
.DS_Store
**/.DS_Store
*.swp
# Python / Node / build output
**/__pycache__/
*.py[cod]
.venv/
venv/
**/node_modules/
**/dist/
**/build/
*.log
# Claude local settings
**/.claude/settings.local.json

View File

@ -0,0 +1,56 @@
---
tags: [project-drogheda, drive, upload, ops]
project: Project Drogheda
author: Helgen CTO
date: 2026-06-20
status: Active
---
# Subir artefactos a Drive (Project-Drogheda)
## Por qué no es automático (la fricción real)
- El **conector de Drive** (MCP) puede crear carpetas y Google Docs nativos, pero **no
sube archivos binarios** (PDF/.xlsx).
- **Claude in Chrome** no puede manejar el **diálogo nativo del sistema** que Drive abre
al pulsar "Subir archivo" (está fuera del navegador).
Por eso los PDF/.xlsx de marca requieren uno de los caminos de abajo. Es una limitación
de la UI de Drive, no de la conexión.
## Tres formas, de la más fácil a la más automatizable
1. **Google Drive para escritorio (sin script)** — *la más fácil.* Instala Drive for
Desktop; la carpeta `Client Projects/Project-Drogheda` aparece como carpeta local;
arrastras los archivos ahí y se sincronizan solos. Cero scripts.
2. **rclone (una línea)** — tras `rclone config` con tu Drive:
```bash
FID_01=1R_ysNsVV7_f0vHevfMcSkXAdaSYgzI95 # 01-Requirements
rclone copy 01-Requirements/Drogheda-HBOX-Ficha-Tecnica.pdf gdrive: --drive-root-folder-id=$FID_01
```
3. **Script de Python (Drive API)** — `drive_upload.py` (en esta carpeta) con el mapeo ya
incluido. Hace *upsert* (actualiza si ya existe). Ver el encabezado del archivo para el
setup (OAuth `credentials.json` una vez). Uso:
```bash
cd ~/Documents/Claude/Projects/Drogheda
python3 00-Project-Management/Drive-Upload/drive_upload.py --dry-run # ver el plan
python3 00-Project-Management/Drive-Upload/drive_upload.py # subir
```
## Mapa: qué documento va a qué carpeta
| Archivo (en el repo) | Carpeta Drive | Folder ID |
|---|---|---|
| `01-Requirements/Drogheda-HBOX-Ficha-Tecnica.pdf` | 01-Requirements | `1R_ysNsVV7_f0vHevfMcSkXAdaSYgzI95` |
| `01-Requirements/Drogheda-SRS-Requerimientos.pdf` | 01-Requirements | `1R_ysNsVV7_f0vHevfMcSkXAdaSYgzI95` |
| `01-Requirements/Drogheda-Requirements-Tracker.xlsx` | 01-Requirements | `1R_ysNsVV7_f0vHevfMcSkXAdaSYgzI95` |
| `03-Architecture-Hardware/Drogheda-Manual-Instalacion-Operacion.pdf` | 03-Architecture-Hardware | `1LBUHOk9dnrrb5UxD5q9k-jBbO7lTlXos` |
| `03-Architecture-Hardware/Drogheda-BOM-Maestro.xlsx` | 03-Architecture-Hardware | `1LBUHOk9dnrrb5UxD5q9k-jBbO7lTlXos` |
| `06-Testing-Validation/Drogheda-RCA-Informe-Falla.pdf` | 06-Testing-Validation | `1CNLnjSa7ro3rkRGQ6w_DBUOIp5ukizi2` |
| `Marketing/HBOX-Datasheet/Drogheda-HBOX-Datasheet.pdf` | Commercial (Cliente) | `1orZKyRi36nebjuHBXDLZIroXx_eWHXYZ` |
| `Marketing/HBOX-Brochure-RTK/Drogheda-HBOX-Brochure.pdf` | Commercial (Cliente) | `1orZKyRi36nebjuHBXDLZIroXx_eWHXYZ` |
Carpeta raíz: **Project-Drogheda** → `1sAwXEq7PU7LMfEq4-swWa1O0bDBCKZ13`
(dentro de "Client Projects"). Los `.tex`/fuentes editables se quedan en el repo; aquí
sólo van los PDF/.xlsx finalizados. **Regla:** el `.tex` del repo y su PDF en Drive son el
mismo artefacto en dos formatos.

View File

@ -0,0 +1,92 @@
#!/usr/bin/env python3
"""
drive_upload.py — push Drogheda branded artifacts to the Project-Drogheda Drive folders.
WHY THIS EXISTS
The Drive MCP connector can create folders/native Docs but NOT upload binary files,
and the browser extension can't drive Drive's native file-picker dialog. This script
uses the Google Drive API directly, so it CAN upload the PDFs/xlsx. It "upserts":
if a file with the same name already exists in the target folder, it updates it;
otherwise it creates it. Run it again any time you rebuild a doc.
ONE-TIME SETUP
1) pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib
2) In Google Cloud Console: create an OAuth client (Desktop app), enable the Drive API,
download the JSON as credentials.json next to this script.
3) Run: python3 drive_upload.py → a browser opens once to authorize; a token.json
is cached so future runs are non-interactive.
USAGE
cd ~/Documents/Claude/Projects/Drogheda
python3 00-Project-Management/Drive-Upload/drive_upload.py # upload all
python3 00-Project-Management/Drive-Upload/drive_upload.py --dry-run # show plan only
"""
import os, sys, argparse
REPO = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
# file (relative to repo root) -> Drive folder ID (Project-Drogheda subfolders)
MAPPING = [
("01-Requirements/Drogheda-HBOX-Ficha-Tecnica.pdf", "1R_ysNsVV7_f0vHevfMcSkXAdaSYgzI95"), # 01-Requirements
("01-Requirements/Drogheda-SRS-Requerimientos.pdf", "1R_ysNsVV7_f0vHevfMcSkXAdaSYgzI95"), # 01-Requirements
("01-Requirements/Drogheda-Requirements-Tracker.xlsx", "1R_ysNsVV7_f0vHevfMcSkXAdaSYgzI95"), # 01-Requirements
("03-Architecture-Hardware/Drogheda-Manual-Instalacion-Operacion.pdf", "1LBUHOk9dnrrb5UxD5q9k-jBbO7lTlXos"), # 03-Architecture-Hardware
("03-Architecture-Hardware/Drogheda-BOM-Maestro.xlsx", "1LBUHOk9dnrrb5UxD5q9k-jBbO7lTlXos"), # 03-Architecture-Hardware
("06-Testing-Validation/Drogheda-RCA-Informe-Falla.pdf", "1CNLnjSa7ro3rkRGQ6w_DBUOIp5ukizi2"), # 06-Testing-Validation
("Marketing/HBOX-Datasheet/Drogheda-HBOX-Datasheet.pdf", "1orZKyRi36nebjuHBXDLZIroXx_eWHXYZ"), # Commercial (Cliente) — ficha técnica
("Marketing/HBOX-Brochure-RTK/Drogheda-HBOX-Brochure.pdf", "1orZKyRi36nebjuHBXDLZIroXx_eWHXYZ"), # Commercial (Cliente) — flyer intro
]
SCOPES = ["https://www.googleapis.com/auth/drive.file"]
HERE = os.path.dirname(os.path.abspath(__file__))
def service():
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from googleapiclient.discovery import build
tok, cred = os.path.join(HERE, "token.json"), os.path.join(HERE, "credentials.json")
creds = Credentials.from_authorized_user_file(tok, SCOPES) if os.path.exists(tok) else None
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
if not os.path.exists(cred):
sys.exit("Missing credentials.json — see ONE-TIME SETUP in this file's header.")
creds = InstalledAppFlow.from_client_secrets_file(cred, SCOPES).run_local_server(port=0)
open(tok, "w").write(creds.to_json())
return build("drive", "v3", credentials=creds)
def upsert(svc, path, folder_id, dry):
from googleapiclient.http import MediaFileUpload
name = os.path.basename(path)
full = os.path.join(REPO, path)
if not os.path.exists(full):
print(f" SKIP {name} (not found at {path})"); return
if dry:
print(f" PLAN {name} -> {folder_id}"); return
q = f"name = '{name}' and '{folder_id}' in parents and trashed = false"
hits = svc.files().list(q=q, fields="files(id)").execute().get("files", [])
media = MediaFileUpload(full, resumable=True)
if hits:
svc.files().update(fileId=hits[0]["id"], media_body=media).execute()
print(f" UPDATED {name}")
else:
svc.files().create(body={"name": name, "parents": [folder_id]},
media_body=media, fields="id").execute()
print(f" CREATED {name}")
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--dry-run", action="store_true")
args = ap.parse_args()
svc = None if args.dry_run else service()
print("Uploading Drogheda artifacts to Project-Drogheda (Drive):")
for path, fid in MAPPING:
upsert(svc, path, fid, args.dry_run)
print("Done.")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,385 @@
---
tags: [project-drogheda, handover, onboarding, rca, architecture]
project: Project Drogheda
author: Helgen CTO
date: 2026-06-20
status: Active
audience: Helgen engineering (new + existing)
---
# Project Drogheda — Engineering Handover & Deep Context
> **Read this if you're new to Drogheda, or returning after time away.** It is the
> human-readable brain-dump: what the product is, why it failed in the field, what we
> are fixing, how the repo is laid out, and what's still open. The terse machine index
> is [`CLAUDE.md`](../CLAUDE.md); current priorities are [`PLAN.md`](PLAN.md). When this
> doc and `CLAUDE.md` disagree, fix both — they should agree.
---
## 1. TL;DR (60 seconds)
**HBOX** is Helgen's GPS-**RTK** tracker for heavy port equipment. We deployed a first
batch on **Hutchison Ports ICAVE** machines in **Veracruz**. They worked "ish," then
failed. We are **mid-crisis and hardening the product for scale** — target **30 units
operational by August**, hundreds of cabinets planned across the Hutchison terminals
(**ICAVE, TIMSA, EIT, LCT**).
The failure is **not one bug**. Three independent fronts, fixed in parallel:
1. **Backend** (confirmed in code) — the production `retranslator.py` blocks the whole
fleet on any upstream slowness (head-of-line), and an anti-jump filter can
**permanently black-hole a healthy unit**. Some "dead" units are backend victims.
2. **Thermal** (component-level) — an **ASA** HBOX (UV-stable, softens ~95–100 °C — the
case is fine) sealed inside a sealed **ABS IP67** cabinet that *also* holds the
heat-generating **Mean Well** PSU, in full Veracruz sun → a nested heat trap for the
battery / boost / modem.
3. **Prototype hardware** — Dupont jumpers, hand-wiring, 18650 + PowerBoost. Fragile
under continuous machine vibration. The client (ICAVE) called this out themselves.
**Current physical reality:** all units pulled from machines. **4 in hand (3 dead, 1
working-ish)**, physically in **Veracruz**; the team is in **Querétaro** → we debug
**remotely**. Units run **firmware v1** (no thermal log), so we cannot yet *prove* the
thermal story from logs — hence the diagnostic protocol and v2 firmware.
**Free triage you can do today:** a unit that is **ONLINE in htracker but OFFLINE in
Wialon** is a **backend victim**, not dead hardware. Check that before condemning a radio.
---
## 2. What the product actually is
Two-component RTK system:
- **Rover (HBOX)** — rides on the machine (RTG crane, reach stacker, empty handler,
tractoplana, forklift). Computes cm-class position.
- **Base Station** — fixed reference (Ardusimple **AS-PRO-CORS**) that publishes **RTCM3**
corrections over **NTRIP**. One per terminal. Comes in **110 V and 230 V** flavors.
The rover gets corrections **over the 4G modem (NTRIP), not over the GNSS**. This single
fact drives the whole recovery design: **if the link dies, you power-cycle the 4G stack —
a GNSS reset never fixes lost NTRIP.**
**HBOX internals (per unit, 24 V production variant):**
simpleRTK2B **Pro** (u-blox **ZED-F9P**, L1/L2/L5/L6) · **4G NTRIP Master** (XBee LTE) ·
**STM32F411 Blackpill** watchdog · **Mean Well SD-15B-05** (24→5 V, 3 A) · **PowerBoost
1000C** + **18650 1S4P** Li-ion backup · **DFR1015** (ignition conditioning) · 2N3904 +
IRF9540N + 2N7000 (watchdog power gate) · **GX16-5** connector (HBOX ↔ cabinet) · U-blox
multiband GNSS antenna + 2× LTE antennas · Telcel micro-SIM · ASA case.
---
## 3. Corrected system architecture
This is the **corrected** picture (the original BOM hid the cabinet/HBOX split). The
cabinet does the power conditioning and surge/ignition isolation; the HBOX receives
**clean 24 V** and makes its own rails.
```text
Machine power+ignition (12/24/48/110/230 V, per equipment)
│
▼ ┌──────────────── Gabinete IP67 (ABS, Yetlebox P3901) ─────────────────┐
breaker│ Phoenix SPD · fuse holder · DIN rail · SSR/opto (ignition isolation)│
+ SPD ►│ Mean Well DDR-60G-24 ──► regulated 24 Vdc ───┐ │
│ │ GX16-5 connector │
│ ┌────────── HBOX (3D-printed ASA) ─────────┼────┐ │
│ │ Mean Well SD-15B-05 (18–36V→5V,3A) ──► 5V rail │ │
│ │ ├► PowerBoost 1000C (+18650 Li-ion 1S4P) backup │
│ │ ├► STM32F411 Blackpill WATCHDOG ──UART(PA2)──► simpleRTK2B Pro │
│ │ │ └ switches RTK 5V via 2N3904→IRF9540N (PA1) │
│ │ ├► simpleRTK2B Pro (ZED-F9P) + 4G NTRIP Master (XBee LTE) │
│ │ └► ignition 24V ──► DFR1015 buck ──► STM32 PA5 (ign sense) │
│ └──────────────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────┘
4G/LTE ▲ RTCM3 in (NTRIP) │ NMEA out (UDP)
Base station (Ardusimple AS-PRO-CORS) ────┘ ▼
retranslator.py ─(anti-jump+speed filter)─► Wialon / Traccar ─► VERONICA
```
**ICAVE machine voltages:** RTG 24 V · Reach Stacker 24 V · Empty Handler 24 V ·
Tractoplana 12 V · Montacargas 12 V. **24 V is the production variant**; the others are
theoretical until built. Base station is 110/230 V.
---
## 4. Root-cause analysis (the three fronts, in depth)
Full write-up: [`06-Testing-Validation/RCA-2026-06-field-failures.md`](../06-Testing-Validation/RCA-2026-06-field-failures.md).
Branded client version (es-MX): `06-Testing-Validation/RCA-Informe-Falla/` → PDF.
### 4.1 Backend (confirmed in `htracker` code — not a hypothesis)
The retranslator is the funnel every unit's NMEA passes through on its way to Wialon.
Three defects, all in [`02-Architecture-Software/backend-review.md`](../02-Architecture-Software/backend-review.md):
- **Head-of-line blocking.** Single-threaded/synchronous forward: one slow upstream
(Wialon/Traccar) stalls **the entire fleet**, not just one unit. At 500 units this is
fatal. Fix → **async retranslator**, per-unit isolation; load-test with a deliberately
slow upstream.
- **Anti-jump filter black-holes healthy units.** On a rejected "jump," the reference
position is **not updated** and the lockout **persists across reconnect** → a healthy
unit can be silently excluded from Wialon forever. Fix → time/speed-based threshold,
**re-anchor** after N rejects, **clear `last_pos` on TTL**.
- **Identity by source IP breaks under CGNAT.** Telcel CGNAT means source IP is not a
stable unit identity → units collide/swap. Fix → identity by **`cell_id`** (provisioned
per unit), never source IP.
- **Live secret.** The MSSQL `sa` password is **committed in the repo** (`.env` /
`.htracker_env`). **Rotate it and purge from git history.** This is invariant #6.
### 4.2 Thermal (component-level; not yet log-proven because v1 has no sensor)
- **The ASA case is not the weak material.** ASA is UV-stable and softens at ~95–100 °C,
with margin over the cabinet interior. (Corrects an earlier PLA assumption — the case is fine.)
- **Nested heat trap.** The ASA HBOX is sealed inside a sealed ABS IP67 cabinet that also
holds the **Mean Well** (a heat source), under direct sun. Cabinet interior can run far
above ambient. **Design to measured cabinet-interior temperature, not spec-sheet ambient.**
- **The GNSS is not the casualty either.** ZED-F9P is rated **−40…+85 °C**. The heat victims
are the **18650/PowerBoost** (charge ≤45 °C; swells/degrades hot) and the **4G modem**.
- **A bare fan inside a sealed box does not cool it** — it just stirs hot air. **Conduct heat
out** (metal plate / cabinet wall), or use an **engineered waterproof duct + Blackpill-
controlled fan** (v3.0, preserves IP). Fixes: keep the ASA case (optional metal for extra
heatsinking), thermal path to a metal plate, resolve the battery (drop / LiFePO4 / supercap),
reduce solar load (reflective cabinet / sunshade / relocate).
### 4.3 Prototype hardware
Dupont + hand-wiring + 18650 + PowerBoost survive a bench, not a machine. Continuous
vibration walks connectors loose. Fix = **ruggedize now** (retained connectors, strain
relief, no Dupont) **+ a production PCB (Series 2)** that integrates the watchdog gate,
power, and ignition conditioning. Brief: [`03-Architecture-Hardware/hardware-respec-and-pcb-brief.md`](../03-Architecture-Hardware/hardware-respec-and-pcb-brief.md).
---
## 5. The fixes (what "done" looks like)
### 5.1 Firmware v2 — `04-Source-Code/firmware/` (own git repo `hbox-firmware`)
> The firmware now lives in its **own git repo** (`ssh://git.helgen.dev:6611/hbox-firmware`)
> at `04-Source-Code/firmware/`, with README, `docs/PINOUT.md`, a redacted RTK-config
> template, and CHANGELOG. Canonical sketch: `blackpillrtkcode_v2/blackpillrtkcode_v2.ino`.
The watchdog is an **STM32F411**. v2 encodes the recovery doctrine:
- **Recover the link by power-cycling the 4G stack** (gate via 2N3904→IRF9540N on PA1),
**never** by resetting the GNSS.
- **Exponential backoff** between recovery attempts — never an infinite fast-reboot loop.
- **LIMP mode** — if corrections are lost, keep reporting **standalone** position rather
than going silent. *Never vanish, never loop.*
- **NMEA-silence watchdog** — detect a wedged receiver by absence of NMEA, not just power.
- **Thermal logging** — v1 has no temperature log; v2 adds it so the next field batch
*proves* the thermal story.
- **Power-off is the safe state** — gate pull-up keeps the RTK stack OFF until the MCU
asserts confidence.
> **Hard rule: bench-test firmware before any field flash.** The sandbox cannot build
> STM32 — UBX checksums and logic were hand-verified, but a real build + bench cycle is
> mandatory before touching a field unit. Edit the **`_v2`** file; v1 is reference only.
Firmware review + watchdog wiring: [`03-Architecture-Hardware/firmware-review.md`](../03-Architecture-Hardware/firmware-review.md),
[`03-Architecture-Hardware/watchdog-schematic.md`](../03-Architecture-Hardware/watchdog-schematic.md).
### 5.2 Backend — `04-Source-Code/htracker/` (branch `feature/test500unidades`)
Async retranslator (no HOL) · identity by `cell_id` · anti-jump rewrite (threshold +
re-anchor + TTL) · 500-unit load test against a slow upstream · **rotate + purge the MSSQL
secret**. Notifier (no secrets, reads `$MATTERMOST_WEBHOOK_URL`):
`04-Source-Code/notifications/notify_mattermost.py`.
### 5.3 Hardware / thermal (Series 2)
Keep ASA case (optional metal) · conduction path to metal · resolve battery · reduce solar
load · production PCB integrating watchdog/power/ignition · retained connectors. Locked
BOM + assembly doc required before the August lot.
---
## 6. Canonical invariants (never violate; check every output against these)
1. **Power-off is the safe state.** RTK stack defaults OFF until the MCU asserts; loss of
confidence → safe state, not best-guess.
2. **Never vanish, never loop.** Keep reporting (standalone if needed); never enter an
infinite fast-reboot loop.
3. **Corrections ride the 4G modem, not the GNSS** — recover the link by power-cycling the
stack; a GNSS reset is never a fix for lost NTRIP.
4. **No material that softens below ~80 °C (PLA-class) in the thermal path** — the ASA
enclosure complies. Design to measured cabinet-interior temperature.
5. **Backend reads/forwards without head-of-line blocking**, and the anti-jump filter must
never permanently lock out a healthy unit.
6. **No secrets in the repo — ever.** (The committed MSSQL password is a live violation to
remediate.)
**Hard constraints:** HBOX input is the cabinet's regulated 24 V + isolated ignition
(don't re-implement the cabinet's surge/breaker/SSR on the HBOX) · **IP67 maintained**
(conduction cooling only; no IP-breaking vents/fans) · **bench-test before field flash** ·
client-facing docs are **Spanish**, routed through Leo.
---
## 7. Repo map (Helgen English `NN-` standard)
| Folder | Holds | Key files |
|---|---|---|
| `00-Project-Management` | plan, ethos, hackathon, financials, **this handover**, client comms, Drive-upload tooling | `PLAN.md` · `ethos.md` · `hackathon-plan.md` · `Financials.md` · `Engineering-Handover.md` · `Drive-Upload/` · `client-comms/` |
| `01-Requirements` | what we build | `SRS.md` · `HBOX-product-spec.md` · branded SRS + ficha técnica (`.tex`→PDF) · requirements tracker (`.xlsx`) |
| `02-Architecture-Software` | backend/software analysis | `backend-review.md` |
| `03-Architecture-Hardware` | HBOX/cabinet, wiring, BOM, PCB | `hardware-respec-and-pcb-brief.md` · `cabinet-electrical-bom.md` · `watchdog-schematic.md` · `firmware-review.md` · `BOM-master.md` · BOM (`.xlsx`) · install/op manual (`.tex`→PDF) |
| `04-Source-Code` | firmware + backend + tooling | `firmware/` (**own repo `hbox-firmware`**: `blackpillrtkcode_v2/` deploy · `legacy/` v1) · `htracker/` · `notifications/` · `rtk-config/` |
| `05-Analytics-Reports` | SQL views, dashboards (VERONICA/PowerBI) | _(placeholder)_ |
| `06-Testing-Validation` | RCA, diagnostics, evidence | `RCA-2026-06-field-failures.md` · `remote-diagnostic-protocol.md` · branded RCA (`.tex`→PDF) |
| `Marketing` | product-facing collateral | `HBOX-Datasheet/` (industrial spec sheet) · `HBOX-Brochure-RTK/` (flyer) |
| `_brand` | shared LaTeX brand style | `estilo-helgen.tex` |
| `99-Archive` | superseded raw inputs (read-only) | `situation_ORIGINAL.md` · `blackpillrtkcode_ORIGINAL_loose.ino` |
| `.claude/skills` | vendored project skill | `project-drogheda/` |
---
## 8. How to build / run
**Firmware** — its own git repo (`hbox-firmware`) at `04-Source-Code/firmware/`; open
`blackpillrtkcode_v2/blackpillrtkcode_v2.ino` in the STM32duino toolchain on a real machine
(the sandbox cannot build STM32). Build/flash + push instructions are in that repo's
`README.md`. **Bench-test before any field flash.**
**Backend** — `04-Source-Code/htracker/` (`ssh://git.helgen.dev:6611/htracker`, branch
`feature/test500unidades`). See `backend-review.md` before changing the retranslator.
**Branded documents (LaTeX)** — each doc folder has `main.tex` + `cuerpo.tex`, sharing
`_brand/estilo-helgen.tex`. Build with **XeLaTeX** (fonts: **League Spartan** titles +
**MADE Evolve Sans** body — the latter is a *personal-use* license, **needs a commercial
license before client-facing embedded use**):
```bash
cd 01-Requirements/HBOX-Ficha-Tecnica # or any doc folder
latexmk -xelatex main.tex # → main.pdf ; latexmk -c to clean aux
```
**Trackers / BOM (xlsx)** — built with openpyxl + Helgen brand colors; build scripts were
generated in the scratchpad. The committed `.xlsx` are the source of truth for the data.
---
## 9. Deliverables produced this engagement (2026-06)
**Branded LaTeX (customer-facing, es-MX), source in repo → PDF:**
- `01-Requirements/HBOX-Ficha-Tecnica/` — DRG-FT-001 (product spec sheet)
- `Marketing/HBOX-Datasheet/` — **industrial datasheet** (Mean Well-style, dense specs +
full **Ambiental/Térmica** section + thermal operating-window chart + power/signal block
diagram). Models the Canva "SPEC SHEET H-BOX". **This is the brochure-as-datasheet.**
- `01-Requirements/Especificacion-Requerimientos/` — DRG-REQ-001 (SRS)
- `03-Architecture-Hardware/Manual-Instalacion-Operacion/` — DRG-MAN-001 (field manual)
- `06-Testing-Validation/RCA-Informe-Falla/` — DRG-RCA-001 (failure report)
**Branded xlsx (Helgen colors, openpyxl):**
- `01-Requirements/Drogheda-Requirements-Tracker.xlsx` — 34 reqs (FR/NFR/TR/BR), status +
category summaries, open-points sheet (modeled on Project Ennis's GateLogic tracker).
- `03-Architecture-Hardware/Drogheda-BOM-Maestro.xlsx` — HBOX common + 24 V production +
theoretical variants + cost summary + base station, one table per sheet.
**Tooling:** `00-Project-Management/Drive-Upload/drive_upload.py` (+ README mapping) ·
`04-Source-Code/notifications/notify_mattermost.py`.
---
## 10. Where things live (storage model — one home per artifact)
| Home | Role | Holds |
|---|---|---|
| **This repo** (`~/Documents/Claude/Projects/Drogheda`) | Working **source of truth** | Editable `.md`/`.tex`, code, `CLAUDE.md`, vendored skill |
| **Google Drive** `Drogheda (GPS RTK)` | Institutional / client-facing | Finalized exports: branded PDFs, native Docs/Sheets |
| **Second Brain vault** `03 Projects/Helgen/Project-Drogheda/` | Personal / thinking | Distilled decisions, lessons, contacts |
**Flow:** author in repo → export finalized deliverable to the matching Drive folder →
distill the evergreen residue into the vault. A repo `.tex` and its Drive PDF are the same
artifact in two formats — the only allowed redundancy.
**Drive upload reality (so the next engineer doesn't relearn it):** the Drive MCP
connector *can* upload binaries (base64), but the bytes pass through the model as text, so
it is **only reliable for small files** (≈≤15 KB; the Requirements Tracker and BOM `.xlsx`
were uploaded this way and verified by exact byte-size). For the 30–50 KB **PDFs**, use
**Google Drive for Desktop (drag)** or **`drive_upload.py`** (one-time OAuth, then it
upserts all artifacts to the right folders). Folder→file mapping:
`00-Project-Management/Drive-Upload/README.md`. **Never change Drive sharing/permissions —
that is Leo's action.**
---
## 11. Status snapshot (2026-06-20)
- **Units:** all pulled from machines. **4 in hand: 3 dead, 1 working-ish**, in **Veracruz**.
- **Team:** in **Querétaro** → **remote** debug. Diagnostic protocol:
`06-Testing-Validation/remote-diagnostic-protocol.md`.
- **Firmware in field:** **v1** (no thermal log). v2 ready for bench → field once tested.
- **Commercial:** productized across ICAVE/TIMSA/EIT/LCT; **30 units by August**.
- **Naming:** renamed **Galway → Drogheda** on 2026-06-20; repo migrated to Helgen English
`NN-` layout. Legacy Spanish dirs removed locally.
- **Drive:** Requirements Tracker + BOM `.xlsx` uploaded (verified); remaining branded PDFs
pending drag/script upload.
---
## 12. Open items / next steps (priority order)
**P0 (this week):**
1. **Backend anti-jump hotfix** — threshold + re-anchor + clear `last_pos` on TTL; stop
black-holing healthy units. (Owner: Gibran)
2. **`cell_id` identity** in the retranslator — kill the source-IP/CGNAT bug. (Gibran)
3. **Rotate + purge the MSSQL secret** from the repo and git history. (Gibran)
4. **Bench-test firmware v2**, then flash the working-ish unit; confirm 4G power-cycle
recovery + thermal logging. (Leo)
**P1 (hardening for August):**
5. **Async retranslator** + 500-unit load test (slow upstream). (Gibran)
6. **Thermal redesign** — conduction path to metal + active waterproof-duct fan (v3.0);
resolve battery, reduce solar load. (ASA case is adequate — no swap.) (Daniel)
7. **Production PCB (Series 2)** — integrate watchdog/power/ignition; retained connectors. (Daniel)
8. **Lock BOM + assembly doc** for the August lot. (Adriana)
**Housekeeping:**
9. Delete the legacy English folders + stray Google Docs in the **old** Drogheda Drive
(Leo to do).
10. Finish mirroring branded PDFs to Drive (drag / `drive_upload.py`).
11. Replace datasheet "validar en banco" power figures with measured values once available.
---
## 13. People
| Name | Email | Role |
|---|---|---|
| Leo Rodriguez | leo@helgen.tech / ceo@helgen.tech | Helgen — founder/CEO; routes client docs |
| Adriana Del Angel | adriana@helgen.tech | Helgen — implementation/plan |
| Paola Rodriguez | paola@helgen.tech | Helgen |
| Angel Daniel Martinez | daniel@helgen.tech | Helgen — hardware/thermal |
| Gibran Ortega | gibran@helgen.tech | Helgen — GPS/telemetry/backend |
| **Agustín Alfonseca** | alfonseca.agustin@icave.com.mx | **Client** — ICAVE Tech Research Supervisor |
| Leon David Piña | pina.david@hutchisonports.com.mx | Hutchison Ports |
---
## 14. Glossary (don't relearn these)
- **HBOX** — the rover unit (the product).
- **RTK** — Real-Time Kinematic; cm-class GNSS using carrier-phase + corrections.
- **NTRIP / RTCM3** — protocol / format for streaming RTK corrections over IP (here, 4G).
- **ZED-F9P** — u-blox multiband RTK receiver; rated **−40…+85 °C** (not the heat casualty).
- **NMEA GGA quality** — **≥4 = healthy** (4 = RTK-fixed, 5 = RTK-float); <4 = degraded.
- **LIMP mode** — keep reporting standalone position when corrections are lost.
- **HOL (head-of-line) blocking** — one slow consumer stalls everyone in a shared queue.
- **Anti-jump filter** — backend logic that rejects implausible position jumps (currently
over-aggressive; can lock out healthy units).
- **GX16-5** — 5-pin circular connector between HBOX and cabinet.
- **cell_id** — stable per-unit identity (use this, not source IP, under CGNAT).
- **Series 1 / Series 2** — prototype (ASA case, hand-wired) / production (PCB, ruggedized).
- **VERONICA** — downstream platform fed from Wialon/Traccar.
---
## 15. Pointers
- Machine index for Claude: [`CLAUDE.md`](../CLAUDE.md)
- Current priorities: [`PLAN.md`](PLAN.md) · Weekend plan: [`hackathon-plan.md`](hackathon-plan.md)
- RCA (full): [`06-Testing-Validation/RCA-2026-06-field-failures.md`](../06-Testing-Validation/RCA-2026-06-field-failures.md)
- Backend review: [`02-Architecture-Software/backend-review.md`](../02-Architecture-Software/backend-review.md)
- Remote diagnostics: [`06-Testing-Validation/remote-diagnostic-protocol.md`](../06-Testing-Validation/remote-diagnostic-protocol.md)
- Hardware re-spec + PCB: [`03-Architecture-Hardware/hardware-respec-and-pcb-brief.md`](../03-Architecture-Hardware/hardware-respec-and-pcb-brief.md)
- Drive upload mapping: [`Drive-Upload/README.md`](Drive-Upload/README.md)

View File

@ -0,0 +1,48 @@
---
tags: [project-drogheda, finance, red-item, placeholder]
project: Project Drogheda
author: Helgen CTO
date: 2026-06-20
status: Draft
---
# Drogheda — Financials (🔴 RED ITEM — placeholder)
> **Placeholder.** Finance is a tracked **red item** for Drogheda; the detailed
> work happens in a separate Claude conversation. This stub holds the structure and
> the open questions so the project doesn't lose the thread. Fill in as numbers land.
> Pricing/margins are **internal** — do not export to client-facing Drive folders.
## Why it's red
The project is scaling to **hundreds of cabinets** across four BUs with a hard
**30-units-by-August** target, while mid-crisis on reliability. Cost, cashflow, and
margin are as much a risk as the thermal/backend issues — and currently un-modeled
here.
## What goes here (to fill in)
- [ ] **Unit economics** — per-variant cost → price → **margin** (the BOM cost
rollup already exists: [[03-Architecture-Hardware/BOM-master|BOM-master]];
HBOX ≈ 8,041 MXN + 24 V cabinet ≈ 6,365 MXN). Add the sell price + %.
- [ ] **Quote vs cost** — Helgen vs alternate-supplier ("Israel") deltas already in
the BOM sheet; decide sourcing and lock landed cost.
- [ ] **Per-BU rollup** — ICAVE / TIMSA / EIT / LCT unit counts × unit cost/price →
revenue + COGS per terminal (counts live in the component sheet).
- [ ] **Cashflow / billing** — milestone billing vs delivery; deposit terms;
currency (MXN/USD components — Ardusimple/Phoenix are USD-priced).
- [ ] **Burn / runway** — hackathon + rework + PCB NRE + pilot batch.
- [ ] **Base-station + infra** — AS-PRO-CORS base station, SIM/data plans (Telcel
per-unit), server/caster hosting.
- [ ] **Warranty / rework reserve** — the field-failure rework is a real cost line.
## Open questions
- [ ] Target gross margin per unit? Current implied margin from the sheet?
- [ ] Who owns the finance model (Adriana?) and where is the canonical sheet?
- [ ] MXN vs USD reporting; FX assumption.
## Related
- [[CLAUDE|Master Index]] · [[03-Architecture-Hardware/BOM-master|BOM-master]] ·
[[00-Project-Management/PLAN|PLAN]]

View File

@ -0,0 +1,158 @@
# PLAN.md — Project Drogheda URGENT Remediation
**Updated:** 2026-06-19 · **Owner:** Leo Rodriguez
**Situation:** **all HBOX units have been removed from the machines.** We have
**4 units in hand — 3 not working, 1 working-ish.** They are **physically in
Veracruz**; the team is in **Querétaro → debugging is remote** until they ship or
someone on-site is our hands. Units run **v1 firmware** (no thermal log).
Strategy: **ruggedize now + PCB in parallel**; near-term work pivots to **remote
backend analysis (doable from QRO today)** + a **remote diagnostic protocol**
(`06-Testing-Validation/remote-diagnostic-protocol.md`).
Legend: ⬜ todo · 🔄 in progress · ✅ done · 👤 owner TBD · ⛔ blocker
---
## P0 — DO THIS WEEK (stop the bleeding, learn the truth)
### Logistics — units are in Veracruz, we're in Querétaro (decide first)
- ⬜ 👤 **Ship the 4 units to Querétaro** *(recommended)* — small parcels; remote-
debugging dead electronics is slow and inconclusive vs 1–2 days for full bench
access. **Bring the working-ish unit too — it's the control.** Confirm with ICAVE
who **owns** the units / whether they may leave the terminal.
- ⬜ 👤 If they can't ship: stand up **remote-hands** in Veracruz using
`06-Testing-Validation/remote-diagnostic-protocol.md` (tiered: photos+current draw → USB dashboard
→ remote desktop). Need a cooperative on-site tech with a bench PSU + laptop.
### Diagnosis (cheapest first — could change everything)
- ⬜ 👤 **Backend triage — now the #1 lead** (backend code review done, see
`02-Architecture-Software/backend-review.md`; two confirmed software defects can take healthy units
off Wialon). Do these free checks first:
- **htracker-online vs Wialon-offline:** any unit ONLINE in Helgen's monitor but
OFFLINE in the client's Wialon = **backend victim, not dead hardware.** Check
ZPM 020 + RS-SAN-001 this way *before* the teardown.
- Confirm the **live process** (`systemctl status htracker` — service runs the
blocking `retranslator.py`; `run_retranslator.sh` runs a different file).
- Gateway log + **`netstat -su`** UDP drops in the death window; did all units
drop within seconds (→ HOL stall) or one-by-one (→ anti-jump lockout)?
- NTRIP caster (HelgenSRV) availability + **per-SIM data balance/suspension**.
- ⬜ 👤 **Pull the field logs** (battery V, fix quality, reset timestamps) and
correlate Wialon gaps with the ~5/10-min v1 reset cadence.
- ⬜ 👤 **Check the installed fuse** rating vs measured worst-case + inrush (H5).
### Client (ICAVE) — relationship is on the line
- ✅ Units **removed** from the machines (all of them, not just ZPM 020 / RS-SAN-001).
- ⬜ 👤 **Reply to Agustín/ICAVE** (Spanish): acknowledge each review point, share
the plan, commit dates. *Draft not yet written — ask Claude to draft it; Leo sends.*
- ⬜ 👤 Agree **custody/shipping** of the 4 pulled units with ICAVE (see Logistics).
### Bench (unblocks the hardware spec)
- ⬜ 👤 **Measure the power budget** — idle/1 Hz/**LTE-TX-burst** current + inrush
at 12 V and 24 V (`hardware brief §2`). Picks the DC-DC and fuse.
- ⬜ 👤 **Bench-test firmware v2** on a Blackpill + simpleRTK2B stack: power-cycle
recovery, NMEA-timeout, thermal read, LED patterns. *Do not field-flash
untested.*
---
## P1 — SHORT TERM (≈1–3 weeks): ruggedize the fleet (Phase 1)
- ⬜ 👤 **Quantify the heat:** thermocouple/logger inside a representative cabinet
for a full sunny shift → real interior-temp number to design against.
- ⬜ 👤 Build a **Phase-1 ruggedization kit** and rework **one reference unit**:
- **the ASA inner box stays** (UV-stable, ~95–100 °C — adequate); optionally
mount the boards to the cabinet plate for better conduction
- replace Dupont → locking JST/Molex connectors
- replace PowerBoost 1000C → **24 V→5 V** industrial buck ≥3 A (cabinet feeds 24 V)
- **verify the cabinet blade fuse** (Phoenix PT 6-FSI/C) vs measured worst-case
- resolve battery (relocate/replace/drop — see decision below)
- vibration mounting (isolated standoffs), retain boards, torque SMAs
- **thermal: conduct heat to the metal plate, sunshade/reflective — NO internal
fan in a sealed box (does nothing); keep a Gore vent for condensation**
- conformal coat; front-panel SIM access
- ⬜ 👤 **Flash firmware v2** on every unit (thermal logging + real 4G recovery).
- ⬜ 👤 **Triage the 4 pulled units** (3 dead + 1 working-ish as control) via
`06-Testing-Validation/remote-diagnostic-protocol.md`: bench power-on with **current-draw reading**,
LED + USB-dashboard capture, photos (swollen LiPo? discoloured boost? cracked
solder? loose SMA?). Classify each against the decision tree; **teardown the
genuinely-dead ones in QRO**. Update `06-Testing-Validation/RCA-2026-06-field-failures.md`.
- ⬜ 👤 **Instrument one unit** with an interior thermocouple for a full sunny
shift — quantify the thermal problem with a number.
- ⬜ 👤 Roll the validated Phase-1 build across the fleet.
### Backend (parallel to the hardware work — see `02-Architecture-Software/backend-review.md`)
- ⬜ 👤 **Anti-jump hotfix** to the running `retranslator.py`: make the threshold
time/speed-aware, **re-anchor after N rejects (self-heal)**, and **clear
`device_last_pos` on TTL cleanup**. Small, safe, stops permanent black-holing.
- ⬜ 👤 Set `SQL_THROTTLE_S` back to its intended 300 s; bump UDP `SO_RCVBUF`.
- ⬜ 👤 **Rotate the MSSQL `sa` password** (committed in `README.md`) and purge
secrets (`.env`, `.htracker_env`) from git history.
---
## P2 — PERMANENT FIX (≈3–8 weeks): the named incomplete tasks
### ⭐ Incomplete task #1 — **Design the Watchdog PCB**
- ⬜ 👤 Schematic for the integrated carrier (protection front-end, ≥3 A buck,
load switch/eFuse, STM32 + sense + SWD/debug, hold-up footprint, locking I/O,
bulkhead SMA, front-panel/MFF2 SIM) — `hardware brief §4`.
- ⬜ 👤 Layout (4-layer, industrial temp, GNSS RF kept clean of switcher noise).
- ⬜ 👤 Fab + assemble **2–3 pilot boards**; bring up with v2 firmware.
- ⬜ 👤 Environmental test pilots (thermal soak + vibration) before fleet rollout.
### ⭐ Backend — migrate to the async retranslator (the real concurrency fix)
- ⬜ 👤 Adopt `retranslator_async.py` (decoupled UDP→queue→workers, per-connection
async) **after** porting in: the corrected anti-jump filter, **cell_id-based
identity** (not source IP — CGNAT-safe), and realistic load testing.
- ⬜ 👤 Rewrite `load_test.py` to measure **end-to-end** (packets forwarded to
Wialon, ack latency, UDP drops) against a **slow upstream**, not just UDP sends.
### ⭐ Incomplete task #2 — **Re-spec HBOX components for production**
- ⬜ 👤 Lock the BOM decisions (`hardware brief §6`): **battery strategy**, SIM
strategy, MCU choice, enclosure (plastic+sunshade vs metal heatsink), modem.
- ⬜ 👤 Produce a documented, repeatable production BOM + assembly doc ("apt for
production" — directly answers the client).
---
## Key decisions Leo must make (block design — see hardware brief §6)
0. **Logistics (now):** **ship the 4 units to QRO** (recommended) vs **remote-hands
in Veracruz** — gates how fast we can confirm the hardware root cause.
1. **Battery mode:** keep (→ LiFePO4/relocated), ride-through only (→ supercap),
or **drop** (vehicle-only)? *Recommended: drop if ICAVE doesn't need parked
tracking — removes the worst thermal/safety liability. Confirm with client.*
2. **Enclosure:** **ASA inner box is adequate** (UV-stable, ~95–100 °C) — no swap
needed; keep ABS IP67 cabinet + sunshade + **conduct heat to a metal plate**, or go
**metal cabinet as heatsink**. (A bare internal fan ≠ a fix; v3.0 uses a ducted fan.)
3. **SIM:** swappable front-panel vs **MFF2 eSIM**.
4. **MCU:** reuse F411 vs industrial-temp STM32.
---
## 🔴 Cross-cutting red item — FINANCIALS
- ⬜ 👤 Model **unit economics** (BOM cost → price → margin), per-BU revenue/COGS,
cashflow/billing, burn (PCB NRE + pilot), warranty reserve. Placeholder +
open questions in [[00-Project-Management/Financials|Financials.md]]. *Detailed
work happens in a separate conversation; keep it in sync here.*
## Deliverables in this repo (Helgen `NN-` layout, updated 2026-06-20)
| File | What |
|---|---|
| `CLAUDE.md` | master index (inherits Helgen ethos) |
| `00-Project-Management/` | `PLAN.md` · `ethos.md` · `hackathon-plan.md` · `Financials.md` · `client-comms/` |
| `01-Requirements/` | `SRS.md` · `HBOX-product-spec.md` |
| `02-Architecture-Software/backend-review.md` | htracker review (HOL block + anti-jump lockout) |
| `03-Architecture-Hardware/` | `hardware-respec-and-pcb-brief.md` · `cabinet-electrical-bom.md` · `watchdog-schematic.md` · `firmware-review.md` · `BOM-master.md` |
| `04-Source-Code/` | `firmware/` (v2 deploy · v1 ref) · `htracker/` · `notifications/` (Mattermost) |
| `06-Testing-Validation/` | `RCA-2026-06-field-failures.md` · `remote-diagnostic-protocol.md` |
## Still open / not done here
- ✅ `htracker` **code review done** → `02-Architecture-Software/backend-review.md`.
- ✅ Repo migrated to the **Helgen English `NN-` standard**; Mattermost notifier wired.
- ⬜ Apply the **anti-jump hotfix** + secret rotation (P1 backend, above).
- ⬜ **Financials** model (red item, above) — separate conversation.
- **Client reply (Spanish)** — not drafted yet (Leo opted to hold); ask when ready.
- Field logs not yet ingested — RCA stays DRAFT until they are.
- 🧹 Delete empty legacy Spanish dirs locally (the mount can't): `cd ~/Documents/Claude/Projects/Drogheda && rmdir 0?_* 99_Archivo docs hardware firmware repo emails backend skills 2>/dev/null`

View File

@ -0,0 +1,84 @@
# Actualización de Confiabilidad — Plataforma de Telemetría HBOX
**Proyecto Drogheda (GPS-RTK) · Backend / Servidor**
Helgen Technologies · 20 de junio de 2026 · Preparado para ICAVE — Hutchison Ports
---
## Resumen ejecutivo
A raíz de las desconexiones reportadas, realizamos un **análisis de causa raíz del
backend** (el servidor que recibe la telemetría de las unidades y la reenvía a Wialon).
Identificamos y **corregimos tres causas de raíz en el software del servidor** que estaban
sacando unidades de Wialon — incluyendo casos en los que **la unidad transmitía
correctamente pero la plataforma la descartaba**.
**Las tres correcciones ya están desplegadas en producción y verificadas.** La
ruggedización de la electrónica de campo, el PCB de producción y el manejo térmico del
gabinete avanzan **en paralelo**, como frentes independientes.
## Lo que se observó en campo
- La flota completa se desconectaba prácticamente **al mismo tiempo**.
- Unidades que "morían" y **no regresaban** a Wialon, aun estando encendidas y transmitiendo.
## Causa raíz (en el backend, no en el radio)
1. **Bloqueo en cadena.** El servidor enviaba a Wialon y esperaba la confirmación dentro del
mismo hilo en que recibía los datos de toda la flota. Un solo envío lento, o un enlace
superior intermitente, **congelaba la recepción de todas las unidades a la vez** → explica
la caída simultánea de la flota.
2. **Filtro "anti-salto" demasiado rígido.** Descartaba saltos de posición grandes, pero **sin
actualizar su punto de referencia**: una unidad que se movía mientras estaba desconectada
(o con un primer dato atípico) quedaba **bloqueada de forma permanente** — transmitía bien,
pero ya nunca volvía a aparecer.
3. **Identidad por dirección IP bajo CGNAT.** El operador celular agrupa muchas SIM detrás de
una misma IP pública; al identificar por IP, el servidor **confundía unas unidades con
otras**, generando saltos falsos y mezcla de datos.
## Hallazgo clave
Confirmamos que **varias unidades reportadas como "muertas" estaban en realidad EN LÍNEA y
transmitiendo correctamente**: la falla estaba en el backend, no en su hardware. Esto nos
permite separar con certeza las fallas de software de las de campo.
## Correcciones desplegadas
- **Procesamiento concurrente:** el reenvío a Wialon ya no bloquea la recepción. Una unidad
lenta o un enlace superior caído **ya no afecta al resto de la flota**.
- **Filtro anti-salto con auto-recuperación:** **nunca** bloquea de forma permanente a una
unidad sana — se re-ancla por sí solo — y sigue rechazando saltos físicamente imposibles.
- **Identidad por dispositivo (a prueba de CGNAT):** se elimina la mezcla de datos entre
unidades que comparten IP del operador.
- **Despliegue seguro:** con respaldo automático, verificación de estado y **reversión
automática** ante cualquier falla.
## Mayor visibilidad
- El tablero (**monitor.helgen.dev**) ahora distingue una **"unidad sin señal"** de una
**unidad en línea que el backend no estaba reenviando** — para no confundir un problema de
plataforma con una falla de equipo.
- Las bitácoras se envían a una plataforma en la nube (Grafana) para diagnóstico y
trazabilidad, **sin saturar el disco del servidor**.
## Validación
Pruebas de extremo a extremo que reproducen enlace superior lento, IPs compartidas (CGNAT) y
saltos de posición: **10 de 10 verificaciones aprobadas** antes de salir a producción.
## Estado y próximos pasos
| Frente | Estado |
|---|---|
| Backend / servidor (confiabilidad) | ✅ **Corregido y en producción** |
| Ruggedización de la electrónica de campo | 🔧 En proceso (paralelo) |
| PCB de producción | 🔧 En diseño (paralelo) |
| Manejo térmico del gabinete | 🔧 En proceso (paralelo) |
El backend se **rediseñó para escalar a cientos de unidades**, en línea con la meta de **30
unidades operativas para agosto**.
---
*Borrador preparado por Helgen Technologies para revisión de Leo antes de compartirse con el
cliente.*

View File

@ -0,0 +1,59 @@
---
tags: [project-drogheda, ethos, principles]
project: Project Drogheda
author: Helgen CTO
date: 2026-06-20
status: Active
---
# Drogheda — Project & Product Ethos
> Why we build HBOX the way we do. When a decision is unclear, it should be
> resolved in favor of these principles. Short on purpose.
## The mission
Give port operators **position they can trust** on equipment that runs in one of
the most hostile environments we ship into — heat, salt, vibration, dust, around
the clock. Centimetres, continuously, unattended.
## Principles
1. **Production-grade, not academic.** The ICAVE review was fair: prototype
wiring belongs on a bench, not on an RTG. Every unit we ship is repeatable,
documented, and assembled the same way. No Dupont in the field. No "it works
on mine."
2. **Fail-safe and self-healing.** Power-off is the safe state. The watchdog
recovers the link by itself and, when it can't, **keeps reporting standalone
position rather than going dark**. A unit must never silently disappear, and
never thrash in a reboot loop.
3. **Survive the environment — don't fight it.** We design for the cabinet's real
interior temperature, not the spec-sheet ambient. No materials that soften in
the sun (no PLA-class material; the current ASA case complies). Cooling by
conduction (or an engineered waterproof duct), sealing kept (IP67). Heat, vibration,
and humidity are requirements, not surprises.
4. **Measure before you blame; rule out the cheap cause first.** A "dead" unit is
guilty until the logs say so. Backend, SIM, and caster get checked before a
truck rolls. Every unit carries enough telemetry to tell its own story.
5. **Observable by design.** Temperature, battery, fix quality, resets — logged.
If we can't see it, we can't fix it, and we can't prove it to the client.
6. **One source of truth.** One BOM, one folder structure, one master index
(`CLAUDE.md`). The Drive mirrors it. Versioned, dated, no "final_v3_real".
7. **ICAVE is the stress lab.** We harden in the worst terminal first. What
survives Veracruz ships clean to TIMSA, EIT, and LCT.
## What "done" means
A unit is done when it **boots, fixes, transmits, recovers from a forced outage
without looping, survives a sunny-shift soak, and a stranger can build the next
one from the BOM and assembly doc.** Not before.
## Related
- [[CLAUDE|Master Index]] · [[01-Requirements/SRS|SRS]] · [[00-Project-Management/PLAN|PLAN]]

View File

@ -0,0 +1,115 @@
---
tags: [project-drogheda, plan, hackathon]
project: Project Drogheda
author: Helgen CTO
date: 2026-06-20
status: Active
---
# Drogheda Hackathon — Run-of-Show, Backlog & Gantt
> A weekend push to stabilize the fleet and de-risk the production build. Dates
> below assume a **Fri-evening → Sun** weekend; **adjust to your chosen weekend.**
> Goal: leave Monday with the backend fixed, firmware v2 bench-validated, the 4
> pulled units triaged, the thermal number measured, and the PCB schematic started.
## 1. Objectives (definition of success)
1. **Backend stops killing healthy units** — anti-jump hotfix + cell_id identity
deployed; 500-unit slow-upstream load test passes; secrets rotated.
2. **Firmware v2 bench-validated** — recovery/backoff/LIMP/thermal verified on a
real Blackpill + simpleRTK2B; flash candidate signed off.
3. **The 4 pulled units classified** — hardware-dead vs backend-victim; RCA updated.
4. **Thermal quantified** — a real cabinet-interior temperature from a soak test;
battery + thermal-path decisions made.
5. **Production kicked off** — watchdog schematic → first PCB schematic draft;
BOM + SRS signed off; Notion board live.
## 2. Tracks & suggested owners
| Track | Focus | Suggested owner |
|---|---|---|
| A — Backend | anti-jump hotfix, cell_id identity, load test, deploy, secrets | Gibran + Leo |
| B — Firmware | bench-test v2, recovery/thermal validation, flash candidate | Leo |
| C — Hardware/Thermal | soak test, battery & thermal-path decisions, conduction mock-up, fuse check | Daniel |
| D — Diagnostics | triage 4 pulled units (remote-hands/shipped), classify, RCA update | Gibran |
| E — PCB/Docs | schematic from watchdog spec, BOM/SRS sign-off | Leo + Daniel |
| F — PM/Integrations | Notion board, Drive sync, client comms, run-of-show | Adriana |
## 3. Run-of-show
| When | Block | Output |
|---|---|---|
| **Fri PM** | Kickoff; assign tracks; **backend historical triage** (htracker-online vs Wialon-offline); bench setup | which "dead" units are backend victims; benches ready |
| **Sat AM** | A: anti-jump hotfix + cell_id · B: v2 bench bring-up · C: start thermocouple soak · D: unit power-on + current-draw | hotfix in test; v2 booting; soak logging; first unit classified |
| **Sat PM** | A: 500-unit slow-upstream load test · B: forced-outage recovery test · C: battery/thermal-path decision · E: PCB schematic start | load test result; recovery proven; material decisions; schematic skeleton |
| **Sun AM** | A: deploy fixed retranslator + rotate secrets · D: finish triage + RCA update · E: BOM/SRS sign-off | backend live; 4 units classified; docs frozen |
| **Sun PM** | Integrate; **demo**; definition-of-done checklist; retro; next-week plan | go/no-go; backlog for week +1 |
## 4. Gantt
```mermaid
gantt
dateFormat YYYY-MM-DD
title Drogheda Hackathon (adjust dates to your weekend)
axisFormat %a
section A Backend
Historical triage :a1, 2026-06-19, 1d
Anti-jump hotfix + cell_id :a2, 2026-06-20, 1d
500-unit load test :a3, after a2, 1d
Deploy + rotate secrets :a4, 2026-06-21, 1d
section B Firmware
v2 bench bring-up :b1, 2026-06-20, 1d
Recovery/thermal validation :b2, after b1, 1d
Flash candidate sign-off :b3, 2026-06-21, 1d
section C Hardware/Thermal
Thermocouple soak :c1, 2026-06-20, 2d
Battery + thermal decision :c2, 2026-06-20, 1d
Conduction mock-up :c3, 2026-06-21, 1d
section D Diagnostics
Power-on + current draw :d1, 2026-06-20, 1d
Classify + RCA update :d2, 2026-06-21, 1d
section E PCB/Docs
PCB schematic draft :e1, 2026-06-20, 2d
BOM + SRS sign-off :e2, 2026-06-21, 1d
section F PM
Notion board + Drive sync :f1, 2026-06-19, 1d
Demo + retro :f2, 2026-06-21, 1d
```
## 5. Prioritized backlog (pull into Notion)
| P | Item | Track | DoD |
|---|---|---|---|
| P0 | Anti-jump hotfix (time-aware + self-heal + clear on TTL) | A | healthy unit can't be permanently dropped; unit test |
| P0 | cell_id identity (drop source-IP keying) | A | CGNAT-safe pairing; verified with 2 sims behind 1 IP |
| P0 | v2 forced-outage recovery test | B | recovers via power-cycle within backoff, no loop |
| P0 | Thermocouple soak (measure cabinet interior) | C | a real °C number over a sunny shift |
| P0 | Classify the 4 pulled units | D | each tagged dead/backend-victim; RCA updated |
| P1 | 500-unit slow-upstream load test | A | no unit dropped; metrics captured |
| P1 | Confirm ASA case survives soak (expected OK) | C | sample part survives soak |
| P1 | Battery decision (drop/LiFePO4/supercap) | C | FR-7 closed |
| P1 | Rotate MSSQL secret + purge from git | A | secret out of repo + history |
| P1 | PCB schematic from watchdog spec | E | reviewable schematic |
| P2 | Front-panel SIM / MFF2 | E | maintainability path chosen |
| P2 | Verify cabinet blade fuse | C | rated vs worst-case + inrush |
## 6. Logistics / pre-reqs
- Benches: 24 V supply with **current limit + meter**, Blackpill + simpleRTK2B +
4G master + antennas with sky view, USB-serial laptop.
- The **4 pulled units** in QRO (ship) or remote-hands in VER —
[[06-Testing-Validation/remote-diagnostic-protocol|protocol]].
- htracker repo + a staging caster/Wialon endpoint for the load test.
- Notion workspace + Drive access for the team.
## Related
- [[CLAUDE|Master Index]] · [[00-Project-Management/PLAN|PLAN]] · [[01-Requirements/SRS|SRS]] ·
[[02-Architecture-Software/backend-review|backend-review]] · [[06-Testing-Validation/RCA-2026-06-field-failures|RCA]]

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,73 @@
\section{Alcance}
Un rastreador GPS-RTK montado en máquina y alojado en gabinete (\textbf{HBOX}) que
transmite posición corregida por RTK sobre 4G al backend (\textit{retranslator}
$\rightarrow$ Wialon/Traccar $\rightarrow$ VERONICA), sobrevive un entorno portuario
hostil (calor, vibración, humedad salina) y es manufacturable y mantenible a escala de
flota. Meta: \textbf{30 unidades operativas para agosto} en ICAVE, TIMSA, EIT y LCT.
Este documento es el complemento formal del tracker de requerimientos (.xlsx).
\section{Requerimientos funcionales}
\hwtab{%
\begin{tabularx}{\textwidth}{@{}>{\bfseries}l X >{\centering\arraybackslash}p{1.2cm}@{}}
\toprule
\rowcolor{HelgenIndigo}\textcolor{white}{ID} & \textcolor{white}{Requerimiento} & \textcolor{white}{Pri.}\\
\midrule
FR-01 & Adquirir posición GNSS multibanda (simpleRTK2B Pro / ZED-F9P). & O\\
FR-02 & Obtener correcciones RTCM3 vía NTRIP sobre 4G; lograr fix/float en cielo abierto. & O\\
FR-03 & Emitir NMEA (GGA+RMC) por UDP al backend a la tasa configurada. & O\\
FR-04 & Control de tasa: 1 Hz con ignición; 30 s en batería/reposo. & O\\
FR-05 & Watchdog: recuperar el enlace reiniciando el stack con \textit{backoff}; nunca en bucle de reinicio. & O\\
FR-06 & Reporte degradado (LIMP): seguir reportando posición estándar si no hay correcciones. & O\\
FR-07 & Operar con 24 V del gabinete; proteger la celda (no cargar $>$45\,\textdegree C). & O\\
FR-08 & LED de estado legible en campo. & R\\
FR-09 & Identidad por unidad (cell\_id) y aprovisionamiento repetible. & O\\
FR-10 & SIM/chip de datos serviciable sin desensamble total. & R\\
FR-11 & Observabilidad: registrar temperatura, batería, calidad de fix y reinicios. & R\\
FR-12 & Backend: ingesta concurrente sin bloqueo de cabecera; el filtro anti-salto nunca debe bloquear de forma permanente a una unidad sana. & O\\
\bottomrule
\end{tabularx}}
\section{Requerimientos no funcionales}
\hwtab{%
\begin{tabularx}{\textwidth}{@{}>{\bfseries}l X L{4.2cm}@{}}
\toprule
\rowcolor{HelgenIndigo}\textcolor{white}{ID} & \textcolor{white}{Requerimiento} & \textcolor{white}{Objetivo}\\
\midrule
NFR-01 Térmico & Operar dentro del gabinete sellado bajo sol de Veracruz. & Componentes $\geq$75\,\textdegree C; \textbf{sin PLA} en la ruta térmica; diseñar a la temperatura interior medida.\\
NFR-02 Vibración & Resistir vibración continua de maquinaria. & Sin Dupont; conectores con seguro; placas retenidas; objetivo $\geq$2 G (validar).\\
NFR-03 Ingreso & Mantener IP67; sin condensación. & Sellado; enfriamiento sólo por conducción (sin ventilación que rompa IP).\\
NFR-04 Eléctrico & Tolerar el ambiente eléctrico de la máquina. & Surge/breaker/ignición en el gabinete; TVS local modesto en el HBOX.\\
NFR-05 Disponibilidad & Minimizar desconexiones en Wialon. & Sin bucles (FR-05); sin bloqueo de cabecera en backend (FR-12); definir SLO.\\
NFR-06 Mantenibilidad & Serviciable, documentado, repetible. & Una PCB; etiquetado; puntos de prueba; BOM publicado.\\
NFR-07 Escalabilidad & Backend a escala de flota. & $\geq$500 unidades concurrentes sin pérdidas (prueba con \textit{upstream} lento).\\
NFR-08 Seguridad & Sin secretos en el repo. & Rotar la contraseña MSSQL; secretos vía entorno/almacén; mínimo privilegio.\\
NFR-09 Manufactura & Construible para el lote de agosto. & BOM bloqueado; documento de ensamble; pilotos validados antes de la flota.\\
\bottomrule
\end{tabularx}}
\section{Restricciones y supuestos}
\begin{itemize}
\item La entrada del HBOX es el \textbf{24 V regulado} del gabinete + una señal de ignición \textbf{aislada}.
\item El \textbf{gabinete} (Yetlebox IP67 ABS + componentes DIN) permanece; el rediseño ataca los \textbf{internos del HBOX} + lo térmico/mecánico.
\item El ZED-F9P ($-40$ a $+85$\,\textdegree C) no es el eslabón térmico débil; lo son el \textbf{18650/PowerBoost}, el \textbf{módem 4G} y la \textbf{carcasa PLA}.
\item Las correcciones llegan por 4G, no por el GNSS (gobierna FR-05/FR-06).
\item El firmware se valida en banco antes del \textit{flasheo} en campo.
\end{itemize}
\section{Criterios de aceptación (alto nivel)}
\begin{itemize}
\item En banco: una unidad arranca, obtiene fix/float, transmite y \textbf{se recupera automáticamente} de un corte forzado de 4G/NTRIP dentro de la ventana de \textit{backoff}, sin entrar en bucle.
\item Backend: bajo una prueba de 500 unidades con \textit{upstream} lento, \textbf{ninguna unidad se descarta} ni queda filtrada de forma permanente.
\item Térmico: una unidad sobrevive un turno soleado a la temperatura interior medida del gabinete, sin deformación de PLA ni sobre-temperatura de batería.
\item Una unidad retirada permite distinguir, por sus registros, una muerte de hardware de una víctima del backend.
\end{itemize}
\begin{helgennotice}[Trazabilidad]
Cada requerimiento se rastrea en el \textbf{Tablero Helgen Drogheda — Tracker de
Requerimientos} (.xlsx), con estado, verificación, responsable y puntos abiertos.
\end{helgennotice}

Binary file not shown.

View File

@ -0,0 +1,18 @@
% Helgen Drogheda — Especificación de Requerimientos (SRS) · DRG-REQ-001
\documentclass[11pt]{article}
\newcommand{\sistema}{Helgen Drogheda — HBOX RTK}
\newcommand{\sistemapdf}{Helgen Drogheda HBOX RTK}
\newcommand{\doctipo}{Especificación de Requerimientos}
\input{../../_brand/estilo-helgen.tex}
\begin{document}
\helgenportada{Especificación de Requerimientos}{Sistema de rastreo GPS-RTK HBOX para equipo portuario}{%
\begin{tabular}{@{}ll@{}}
\textbf{Documento} & DRG-REQ-001 — Especificación de Requerimientos\\
\textbf{Versión} & 1.0 (Borrador)\\
\textbf{Fecha} & 20 de junio de 2026\\
\textbf{Autor} & Dr.\ Leopoldo Rodríguez Salazar — Helgen Technologies\\
\textbf{Clasificación} & Confidencial — uso de Hutchison Ports y Helgen\\
\textbf{Asistencia IA} & Elaborado con asistencia de Claude (Anthropic), revisado por el autor\\
\end{tabular}}
\input{cuerpo.tex}
\end{document}

View File

@ -0,0 +1,108 @@
\section{Descripción del producto}
El \textbf{HBOX} es una unidad de telemetría \textbf{GPS-RTK} de precisión, montada en
equipo portuario pesado, que entrega posición de \textbf{precisión centimétrica} sobre
\textbf{4G} al backend de Helgen (\textit{retranslator} $\rightarrow$ Wialon/Traccar
$\rightarrow$ VERONICA). Un \textbf{watchdog} embebido mantiene vivo el enlace de
correcciones de forma autónoma. Se entrega como un \textbf{ensamble de gabinete}: un
gabinete industrial IP67 (potencia, protección y aislamiento de ignición) que aloja el
HBOX interno (RTK + 4G + watchdog).
\begin{helgennotice}[En una frase]
Centímetros de precisión, de forma continua y desatendida, en uno de los entornos más
hostiles en los que operamos: calor, salinidad, vibración y polvo, las 24 horas.
\end{helgennotice}
\section{Variantes}
La electrónica interna del HBOX es \textbf{idéntica en todas las variantes}; sólo cambia
la etapa de potencia del gabinete según el voltaje de la máquina.
\hwtab{%
\begin{tabularx}{\textwidth}{@{}>{\bfseries}l L{4.6cm} X@{}}
\toprule
\rowcolor{HelgenIndigo}\textcolor{white}{Variante} & \textcolor{white}{Alimentación} & \textcolor{white}{Notas}\\
\midrule
24 V & 24 Vcd $\rightarrow$ 24 V & \textbf{En producción} — RTG, Reach Stacker, Empty Handler\\
12 V & 12 Vcd $\rightarrow$ 24 V & Teórico — Tractoplana, Montacargas\\
48 V & 48 Vcd $\rightarrow$ 24 V & Teórico\\
110 V & 110 Vca $\rightarrow$ 24 V & Teórico (+ SPD CA, termomagnético)\\
230 V & 220 Vca $\rightarrow$ 24 V & Teórico (+ fusible Bussman)\\
Base & 110 / 230 Vca & Estación base RTK/NTRIP (Ardusimple AS-PRO-CORS), 1 por terminal\\
\bottomrule
\end{tabularx}}
\section{Especificaciones técnicas (objetivo)}
\hwtab{%
\begin{tabularx}{\textwidth}{@{}>{\bfseries}L{4.4cm} X@{}}
\toprule
\rowcolor{HelgenIndigo}\textcolor{white}{Parámetro} & \textcolor{white}{Especificación}\\
\midrule
GNSS & u-blox ZED-F9P, multibanda L1/L2, multiconstelación\\
Precisión de posición & RTK fijo: $\sim$1–2 cm + 1 ppm CEP (cielo abierto, correcciones válidas)\\
Tasa de actualización & 1 Hz (activo) / 30 s (reposo/batería)\\
Correcciones & NTRIP/RTCM3 sobre 4G LTE (ArduSimple 4G NTRIP Master)\\
Telemetría de salida & NMEA sobre UDP $\rightarrow$ retranslator $\rightarrow$ Wialon/Traccar $\rightarrow$ VERONICA\\
Entrada del HBOX & 24 Vcd (desde el gabinete) + ignición aislada\\
Riel interno de 5 V & Mean Well SD-15B-05 (18–36 V $\rightarrow$ 5 V, 3 A)\\
Respaldo de energía & PowerBoost 1000C + 18650 Li-ion 1S4P\\
MCU / watchdog & STM32F411 Blackpill (firmware \texttt{blackpillrtkcode\_v2})\\
Conector HBOX$\leftrightarrow$gabinete & GX16-5 circular con seguro\\
Antenas & 1$\times$ GNSS multibanda (SMA) + 2$\times$ LTE (AR1582)\\
Gabinete & Yetlebox P3901, ABS, \textbf{IP67}, 39$\times$29$\times$16 cm, riel DIN\\
Ambiental (objetivo) & $-10$ a $+75$\,\textdegree C interno; vibración $\geq$2 G (a validar); humedad costera\\
\bottomrule
\end{tabularx}}
\section{Arquitectura del sistema}
\begin{center}
\begin{tikzpicture}[node distance=8mm and 12mm]
\node[cajaoscura, text width=2.6cm] (maq) {Máquina\\12/24/48/110/230 V};
\node[caja, right=of maq, text width=3.1cm] (gab) {Gabinete IP67\\SPD · breaker · SSR\\Mean Well $\rightarrow$ 24 V};
\node[caja, right=of gab, text width=3.2cm] (hbox) {HBOX\\simpleRTK2B Pro\\+ 4G NTRIP + watchdog};
\node[caja, below=of hbox, text width=3.2cm] (be) {retranslator\\Wialon / Traccar};
\draw[flecha] (maq) -- (gab);
\draw[flecha] (gab) -- node[etiqueta,above]{GX16-5 · 24 V} (hbox);
\draw[flechaacento] (hbox) -- node[etiqueta,right]{NMEA / UDP} (be);
\node[etiqueta, above=1mm of hbox] {RTCM3 / NTRIP (4G)};
\end{tikzpicture}
\end{center}
Las correcciones llegan por el \textbf{módem 4G} (no por el GNSS); ante una caída del
enlace, el watchdog recupera el sistema \textbf{reiniciando el stack}, nunca con un
\textit{reset} del receptor GNSS.
\section{Interfaces y conectividad}
\begin{itemize}
\item \textbf{GX16-5 (HBOX$\leftrightarrow$gabinete):} 24 V, GND, ignición (+ 2 reserva).
\item \textbf{Antenas:} SMA de mamparo (GNSS) + 2$\times$ LTE por glándula; con torque y fijador de rosca.
\item \textbf{SIM:} Telcel micro (Serie 1, interna) $\rightarrow$ panel frontal o eSIM MFF2 (Serie 2).
\item \textbf{Diagnóstico:} tablero ANSI por USB/UART (batería, fix, paquetes, temperatura, estado).
\end{itemize}
\section{Indicación de estado (LED)}
\hwtab{%
\begin{tabularx}{\textwidth}{@{}>{\bfseries}L{3.6cm} X@{}}
\toprule
\rowcolor{HelgenIndigo}\textcolor{white}{Patrón} & \textcolor{white}{Significado}\\
\midrule
Parpadeo rápido & Arranque — esperando 4G/GNSS\\
Encendido fijo & Operando con energía de máquina, RTK fijo\\
Latido (1 s) & Modo batería, 30 s\\
Parpadeo lento & Buscando / sin correcciones\\
Doble (tipo SOS) & LIMP — sin 4G, sigue reportando posición estándar\\
Estrobo & Reiniciando el stack\\
Apagado & Reposo / batería crítica\\
\bottomrule
\end{tabularx}}
\section{Hoja de ruta — Serie 2 (producción)}
PLA $\rightarrow$ ASA/PC o metal\, ·\, cableado Dupont $\rightarrow$ PCB con conectores con
seguro\, ·\, watchdog discreto $\rightarrow$ \textit{load switch} integrado\, ·\, decisión de
batería\, ·\, SIM al panel frontal\, ·\, enfriamiento por conducción\, ·\, ensamble
documentado. La Serie 2 es el objetivo de PCB de producción para el lote de agosto.

Binary file not shown.

View File

@ -0,0 +1,23 @@
% =====================================================================
% Helgen Drogheda — HBOX · Ficha Técnica (cliente)
% Compilar: latexmk -xelatex main.tex (fuentes de marca vía ~/.fonts)
% Fuente en el repo; el PDF es artefacto. PDF final -> Drive.
% =====================================================================
\documentclass[11pt]{article}
\newcommand{\sistema}{Helgen Drogheda — HBOX RTK}
\newcommand{\sistemapdf}{Helgen Drogheda HBOX RTK}
\newcommand{\doctipo}{Ficha Técnica}
\input{../../_brand/estilo-helgen.tex}
\begin{document}
\helgenportada{Ficha Técnica del Producto}{HBOX — Rastreador GPS-RTK para equipo portuario pesado}{%
\begin{tabular}{@{}ll@{}}
\textbf{Documento} & DRG-FT-001 — Ficha Técnica HBOX\\
\textbf{Versión} & 1.0\\
\textbf{Fecha} & 20 de junio de 2026\\
\textbf{Autor} & Dr.\ Leopoldo Rodríguez Salazar — Helgen Technologies\\
\textbf{Clasificación} & Confidencial — uso de Hutchison Ports y Helgen\\
\textbf{Asistencia IA} & Elaborado con asistencia de Claude (Anthropic), revisado por el autor\\
\end{tabular}}
\input{cuerpo.tex}
\end{document}

View File

@ -0,0 +1,91 @@
---
tags: [project-drogheda, product-spec, hbox, datasheet]
project: Project Drogheda
author: Helgen CTO
date: 2026-06-20
status: Draft
---
# HBOX — Product Specification (Series 1 → Series 2)
> The HBOX as a product: what it is, its variants, interfaces, and target specs.
> Requirements live in [[01-Requirements/SRS|SRS]]; parts in
> [[03-Architecture-Hardware/BOM-master|BOM]]. **Series 1** = current
> module-based build (ASA case + Dupont wiring). **Series 2** = the production PCB target.
## 1. Overview
HBOX is a ruggedized, cabinet-housed **RTK GPS telemetry unit** for heavy port
equipment. It delivers **centimetre-class** position over 4G to the Helgen
backend, with an onboard **watchdog** that keeps the correction link alive
unattended. Sold as a **gabinete (cabinet) assembly**: an industrial IP67 cabinet
(power, protection, ignition isolation) + the internal HBOX (RTK + 4G + watchdog).
## 2. Variants
| Variant | Machine power | Cabinet supply → | Notes |
|---|---|---|---|
| 12 V | 12 Vdc | Mean Well DDR-60G-24 → 24 V | Tractoplana, Montacargas |
| **24 V** | 24 Vdc | DDR-60G-24 → 24 V | **dominant**: RTG, Reach Stacker, Empty Handler |
| 48 V | 48 Vdc | DDR-60L-24 → 24 V | — |
| 110 V | 110 Vac/dc | HDR-60-24 / Siemens → 24 V | + AC SPD + termomagnético |
| 230 V | 220 Vac | HDR-60-24 → 24 V | + Bussman fusing |
| **Base Station** | 110 Vac | — | Ardusimple AS-PRO-CORS (RTK source/NTRIP caster) |
Internal HBOX electronics are **identical across variants**; only the cabinet
power/protection stage changes with machine voltage.
## 3. Key specifications (targets)
| Parameter | Spec |
|---|---|
| GNSS | u-blox ZED-F9P, multi-band L1/L2, multi-constellation |
| Position accuracy | RTK fixed: ~1–2 cm + 1 ppm CEP (open sky, valid corrections) |
| Update rate | 1 Hz (active) / 30 s (idle/battery) |
| Corrections | NTRIP/RTCM3 over 4G LTE (ArduSimple 4G NTRIP Master) |
| Telemetry out | NMEA over UDP → retranslator → Wialon/Traccar → VERONICA |
| HBOX input | 24 Vdc (from cabinet) + isolated ignition |
| Internal 5 V | Mean Well SD-15B-05 (18–36 V→5 V, 3 A) |
| Backup power | PowerBoost 1000C + 18650 Li-ion 1S4P *(under review — see SRS FR-7)* |
| MCU / watchdog | STM32F411 Blackpill, firmware `blackpillrtkcode_v2` |
| HBOX↔cabinet | GX16-5 locking circular connector |
| Antennas | 1× multiband GNSS (SMA) + 2× LTE (AR1582) |
| Status | external LED (multi-pattern) + onboard LED + debug UART dashboard |
| Cabinet | Yetlebox P3901, ABS, **IP67**, 39×29×16 cm, DIN rail |
| Environmental (target) | −10…+75 °C internal; ≥2 G vibration *(to validate)*; coastal humidity |
| Enclosure (HBOX) | **Series 1: ASA (adequate)** → Series 2: ASA or metal / plate-mount |
## 4. Interfaces
- **GX16-5 (HBOX↔cabinet):** 24 V, GND, ignition, (spare ×2) — finalize pinout on
the schematic before PCB.
- **Antennas:** bulkhead SMA (GNSS) + 2× LTE via glands; torque + thread-lock.
- **SIM:** Telcel micro (Series 1, internal) → front-panel or MFF2 eSIM (Series 2).
- **Debug:** USB/UART ANSI dashboard (battery, fix, packets, temp, state).
## 5. Status indication (LED)
Boot (fast blink) · Running+RTK (solid) · Battery mode (heartbeat) · Searching/
degraded (slow blink) · LIMP/no-4G (SOS-ish double) · Power-cycling (strobe) ·
Sleep/critical (off). See firmware v2.
## 6. Model / SKU
`H-BOX Series1` (current) → `H-BOX Series2` (PCB). Each unit carries a Helgen
data label (modelo, serie, datos técnicos) + warning + input-voltage label.
## 7. Series 2 (production) deltas
ASA case retained (optional metal) · Dupont/hand-wire → PCB + locking connectors · discrete
watchdog → integrated load switch · battery decision · front-panel SIM ·
conduction cooling · documented assembly. See
[[03-Architecture-Hardware/hardware-respec-and-pcb-brief|hardware brief]].
## Open Questions
- [ ] Finalize GX16-5 pinout · battery strategy · enclosure material.
- [ ] Validate the 2 G vibration and IP67-after-mods claims for the datasheet.
## Related
- [[CLAUDE|Master Index]] · [[01-Requirements/SRS|SRS]] · [[03-Architecture-Hardware/BOM-master|BOM]]

83
01-Requirements/SRS.md Normal file
View File

@ -0,0 +1,83 @@
---
tags: [project-drogheda, requirements, srs]
project: Project Drogheda
author: Helgen CTO
date: 2026-06-20
status: Draft
---
# Software/System Requirements — Drogheda HBOX RTK
> What the Drogheda system must do and how well. Derived from the ICAVE field
> failures, the client review, and the productization goal (30 units by August
> across ICAVE/TIMSA/EIT/LCT). IDs are stable; trace fixes and tests back to them.
## 1. Scope
A vehicle-mounted, cabinet-housed **RTK GPS tracker (HBOX)** that streams
RTK-corrected position over 4G to the backend (`retranslator` → Wialon/Traccar →
VERONICA), survives a harsh, hot, high-vibration port environment, and is
manufacturable and serviceable at fleet scale.
## 2. Functional requirements
| ID | Requirement | Priority |
|---|---|---|
| FR-1 | Acquire GNSS position via simpleRTK2B Pro (ZED-F9P), multiband. | Must |
| FR-2 | Obtain RTCM3 corrections via NTRIP over 4G from the Helgen base station; achieve RTK fix/float in open sky. | Must |
| FR-3 | Emit NMEA (GGA+RMC) and deliver it via UDP to the backend at the configured rate. | Must |
| FR-4 | Rate control: 1 Hz when powered+ignition-on; 30 s on battery/idle (power saving). | Must |
| FR-5 | **Watchdog recovery:** detect loss of corrections / NMEA silence and recover by **power-cycling the RTK+4G stack** with exponential backoff; never enter an infinite fast-reboot loop. | Must |
| FR-6 | **Degraded reporting:** when corrections can't be restored, keep reporting standalone GPS (LIMP) instead of going offline. | Must |
| FR-7 | Power management: run on cabinet 24 V; ride-through / battery behavior per the chosen battery strategy; protect the cell (no charge >45 °C if a cell is retained). | Must |
| FR-8 | Field-readable status LED (boot / running / battery / searching / LIMP / power-cycle / sleep). | Should |
| FR-9 | Per-unit identity (cell_id) and provisioning (UART config, unique ID, NVM save) repeatable per the SOP. | Must |
| FR-10 | SIM/data-chip serviceable without full disassembly. | Should |
| FR-11 | Observability: log MCU/enclosure temperature, battery V, fix quality, reset events for field diagnosis. | Should |
| FR-12 | **Backend:** ingest from many units concurrently without head-of-line blocking; forward valid fixes to Wialon; anti-jump filter must never permanently lock out a healthy unit. | Must |
## 3. Non-functional requirements
| ID | Requirement | Target |
|---|---|---|
| NFR-1 Thermal | Operate continuously inside the sealed cabinet in Veracruz sun. | Internal components rated/derated for **≥75 °C**; enclosure is **ASA** (UV-stable, ~95–100 °C — no PLA-class material in the thermal path); measured cabinet-interior target set from a sunny-shift log. |
| NFR-2 Vibration | Survive continuous heavy-machinery vibration (RTG, reach stacker). | No Dupont/hand-wire; locking connectors; retained boards; conformal coat. Target ≥2 G per spec sheet — **validate**. |
| NFR-3 Ingress | Maintain **IP67**; no internal condensation. | Sealed; Gore vent for pressure/humidity; cooling via conduction only (no IP-breaking vents). |
| NFR-4 Electrical | Tolerate the machine electrical environment. | Surge/breaker/ignition isolation handled at the **cabinet** (Phoenix SPD + breaker + SSR); HBOX adds a modest local TVS + reverse-polarity. |
| NFR-5 Availability | Minimize Wialon disconnects per unit. | No reset loops (FR-5); backend HOL-block eliminated (FR-12); define a monthly availability SLO once stabilized. |
| NFR-6 Maintainability | Serviceable, documented, repeatable. | One PCB; labeled; test points; published BOM; SIM access (FR-10). |
| NFR-7 Scalability | Backend handles fleet scale. | ≥500 concurrent units without drop under realistic (slow-upstream) load tests. |
| NFR-8 Security | No secrets in the repo. | Rotate the committed MSSQL password; secrets via env/secret store; least privilege. |
| NFR-9 Manufacturability | Buildable for the August batch. | Production BOM locked; assembly doc; pilot units validated before fleet. |
## 4. Constraints & assumptions
- HBOX input is the cabinet's **regulated 24 V** + an **isolated ignition** signal.
- The **cabinet** (Yetlebox IP67 ABS + DIN industrial parts) stays; the redesign
targets the **HBOX internals** + thermal/mechanical.
- ZED-F9P (−40…+85 °C) is not the thermal weak link; the **18650/PowerBoost** and
**4G modem** are (the **ASA case** is adequate).
- Corrections flow over 4G, not the GNSS (drives FR-5/FR-6).
- Firmware must be bench-validated before field flash (no STM32 build in sandbox).
## 5. Acceptance criteria (high level)
- A unit on the bench: boots, gets RTK fix/float, transmits, and **recovers
automatically** from a forced 4G/NTRIP outage within the backoff window without
looping (FR-5/6).
- Backend: under a 500-unit slow-upstream load test, **no unit is dropped** and no
unit is permanently filtered out (FR-12, NFR-7).
- Thermal: a unit survives a full sunny-shift soak at the measured cabinet
interior temperature with no enclosure deformation and no battery over-temp (NFR-1).
- A pulled unit's logs distinguish hardware death from backend victimhood (FR-11).
## Open Questions
- [ ] Battery strategy (drop / LiFePO4 / supercap) → finalizes FR-7, NFR-1.
- [ ] Confirm the 2 G vibration claim and IP67 after the thermal mods (NFR-2/3).
- [ ] Define the numeric availability SLO (NFR-5) once the fleet is stable.
## Related
- [[CLAUDE|Master Index]] · [[01-Requirements/HBOX-product-spec|HBOX Product Spec]] ·
[[06-Testing-Validation/RCA-2026-06-field-failures|RCA]] · [[00-Project-Management/PLAN|PLAN]]

View File

@ -0,0 +1,230 @@
# Backend Review — `htracker` retranslator + monitor
**Date:** 2026-06-19 · **Reviewer:** Claude (for Leo) · branch `feature/test500unidades`
**Scope:** `retranslator.py`, `retranslator_async.py`, `gps_logger.py`,
`web_monitor.py`, `notifier.py`, `load_test.py`, service/unit files.
**Why:** the RCA flagged a **backend common-mode** (H2) as the cheapest, most
likely explanation for *all units disconnecting at once* — and for a *single unit
"dying" and never coming back*. **This review found concrete mechanisms for both,
in software, independent of the field hardware.**
Severity: 🔴 can take units off Wialon · 🟠 reliability/scale · 🟡 hygiene.
---
## ✅ RESOLUTION (2026-06-20) — fixed & deployed to production
All three 🔴 mechanisms were fixed in `retranslator.py` and **deployed** via the new
`deploy.sh` (backup + health-check + auto-rollback; gateway/monitor/notifier all active).
Approach: **harden in place**, not the async rewrite.
| # | Issue | Fix shipped |
|---|-------|-------------|
| P1 | Synchronous Wialon I/O in the receive loop (HOL blocking) | `WialonForwarder` worker pool decouples TCP I/O from UDP ingest; the receive loop never blocks. Verified: a healthy unit kept forwarding while another unit's upstream was dark. |
| P1 | Anti-jump filter permanently locks units out | Time-aware plausibility envelope (`MAX_SPEED·Δt`) + self-heal (re-anchor after N rejects) + clear anchor on disconnect. Verified: a teleporting unit recovers instead of being black-holed. |
| P2 | Identity keyed by source IP (CGNAT) | Transport = full `(ip, port)`; all per-device state + GGA/RMC pairing keyed by `cell_id`. Verified: two SIMs on one IP don't cross-mix. |
| 🟠 | `SQL_THROTTLE_S = 0`; thread-safety | Throttle set to 300 s; stats threads snapshot shared dicts with `list(...)`. |
| 🟠 | Load test measured the wrong thing | Superseded by an end-to-end smoke test (mock Wialon + slow/dark upstream + CGNAT + jump cases). |
| 🟡 | Secrets in repo | Parameterized out of README/compose into `.env` (gitignored) + `.env.example`. **Open:** rotate the password + purge git history. |
Also shipped: the **backend-victim signal** in the monitor (ONLINE-on-UDP but not forwarding),
an **offline-unit detail panel**, a ~100× **log-volume trim** + off-box shipping to **Grafana
Loki** (via Alloy → journald, capped), and the unit-aware `deploy.sh`. Outstanding: secret
rotation + history purge; optional convergence of the dead retranslator variants; code review
(Leo to run). The original analysis is preserved below for the record.
---
## TL;DR (read this if nothing else)
1. **Production runs the *blocking* `retranslator.py`** (per `htracker.service`),
which sends each position to Wialon and **waits for the TCP ack — and even the
20 s connect — inside the single UDP receive loop**. One slow/unreachable
upstream stalls the loop and **every device drops off Wialon at once**. This is
a textbook common-mode and will get dramatically worse at 500 units. 🔴
2. **The anti-jump filter can permanently black-hole a healthy unit.** On a
rejected "jump" it returns **without updating the reference position**, and the
reference **survives disconnects**. A unit that moves >500 m while offline (or
whose first fix after reconnect is an outlier) has **100 % of its packets
dropped forever** — it transmits fine but never appears in Wialon. 🔴
3. **Device identity is keyed by source IP**, which collides under carrier CGNAT
(500 SIMs, shared/rotating IPs) → **cross-device data mixing** → false jumps →
disconnects. 🔴
4. There's a **correct async rewrite** (`retranslator_async.py`) that fixes the
blocking model — but it's **not deployed**, still **keys by IP**, and **dropped
the anti-jump filter** entirely.
**Powerful triage signal (use today):** Helgen's own monitor marks a unit
**ONLINE on UDP receipt** (`_mark_seen`), *before* the anti-jump filter and the
Wialon send. So a unit hit by #2 or #1 shows **ONLINE in htracker but OFFLINE in
Wialon**. **Any unit with that split is a backend victim, not a dead radio** —
check this against ZPM 020 / RS-SAN-001 before blaming the hardware.
---
## What is actually running (confirm this first)
- `htracker.service` → `ExecStart=/usr/bin/python3 retranslator.py` ← **the
blocking gateway**, "Up to 500 Wialon TCP sockets," `LimitNOFILE=8192`.
- `htracker_ctl.sh` → also launches `retranslator.py`.
- `run_retranslator.sh` → launches **`rtk2mqtt_retranslator.py`** (a third
variant). ⚠️ **Ambiguity:** confirm which process is live in prod
(`systemctl status htracker`), because the review's #1/#2 are about
`retranslator.py`.
---
## 🔴 P1 — Synchronous Wialon I/O in the single receive loop (HOL blocking)
`main()` is one thread doing `recvfrom` → parse → `_process_device_telemetry` →
`send_to_wialon`, which **blocks**:
- on connect: `s.connect((DEST_HOST, DEST_PORT))` with `TCP_TIMEOUT_S = 20`;
- on every packet: `current_sock.sendall(...)` then **`current_sock.recv(1024)`**
waiting for the Wialon ack.
While that one device's socket is slow, **no UDP from any other device is read**;
the kernel UDP buffer fills and **datagrams from all 500 units are silently
dropped**. A brief upstream hiccup at `us.gpsgsm.org` therefore looks exactly like
"the whole fleet disconnected." Reconnect storms make it worse: cellular IP churn
closes/reopens sockets (each a 20 s-timeout `connect` in the loop) with
exponential backoff — all serialized in the same loop.
**Symptom match:** simultaneous death (A) + recurrent disconnects (B).
**Fix:** deploy the async model (decouple UDP ingest from Wialon I/O via a queue +
worker tasks; per-connection async with timeouts) — see `retranslator_async.py`,
which already does this. Until then, at minimum make `send_to_wialon`
non-blocking / move it off the receive loop, and bump `SO_RCVBUF`.
---
## 🔴 P1 — Anti-jump filter permanently locks units out
In `_process_device_telemetry`:
```python
_prev = device_last_pos.get(cell_id)
if _prev:
_dist = haversine(_prev, (_lat,_lon))
if _dist >= 0.5: # flat 500 m, ignores Δt
log.warning("Anti-Salto… descartado")
return # ← returns WITHOUT updating reference
device_last_pos[cell_id] = (_lat, _lon) # only runs if accepted
```
Three compounding defects:
1. **Reference not updated on reject** → once "stuck," every later fix is measured
against a stale anchor. If the device keeps moving, every packet is >500 m from
the anchor → **permanent 100 % drop**.
2. **Anchor persists across disconnects** — `device_last_pos` is **not** cleared by
the TTL cleanup (unlike every other per-device dict). A unit that moves >500 m
while powered-off and reconnects is **black-holed on return**.
3. **Flat 500 m threshold ignores time.** After any packet-loss gap (see P1
above), a normally-moving unit legitimately advances >500 m between *received*
fixes → rejected → then locked out by defect #1. **P1 packet loss directly
triggers P2 lockouts.**
Also: a device's **first** fix after (re)appearing has no `_prev`, so it's accepted
unconditionally — if that first fix is an RTK outlier, it becomes the anchor and
locks out all subsequent *good* fixes.
**Symptom match:** a unit that "died" and never came back, while its hardware and
UDP stream are fine (→ shows ONLINE in htracker, OFFLINE in Wialon).
**Minimal safe hotfix** (time-aware + self-healing + clear on disconnect):
```python
# store (lat, lon, ts); clear device_last_pos[cell_id] in the TTL cleanup too
_prev = device_last_pos.get(cell_id)
now = time.time()
if _prev:
dt = max(1.0, now - _prev[2])
max_km = (MAX_SPEED_KMH / 3600.0) * dt * 1.5 # plausibility envelope
if _dist > max(0.5, max_km):
n = device_jump_rejects.get(cell_id, 0) + 1
device_jump_rejects[cell_id] = n
if n < 3: # tolerate a few outliers…
return # …then re-anchor to self-heal
device_jump_rejects[cell_id] = 0
device_last_pos[cell_id] = (_lat, _lon, now)
# …send to Wialon…
```
This keeps odometer protection (rejects true teleports) but **cannot lock a unit
out forever**.
---
## 🔴 P2 — Identity keyed by source IP breaks under CGNAT
`ip_to_device_cell_id[source_ip]`, `ip_to_last_gga[source_ip]`,
`ip_to_last_rmc[source_ip]` assume **one device ⇄ one IP**. On cellular at fleet
scale that's false: carrier **CGNAT** puts many SIMs behind shared public IPs and
rotates them. Consequences:
- Two devices sharing a public IP **overwrite each other's** cell-id mapping and
**mix GGA/RMC** → the anti-jump filter sees a "jump" between two trucks'
positions → drops + can trigger the lockout above.
- The GGA↔RMC pairing (by IP, 2 s window) pairs the **wrong** device's sentences.
**Fix:** key everything by **`cell_id`** (the `$GPTXT` identity), not IP. Buffer
last GGA/RMC per `cell_id`; treat `source_ip` only as transport. The async version
inherits this bug too and should be fixed the same way.
---
## 🟠 Reliability / scale
- **`load_test.py` measures the wrong thing.** It only counts **UDP sends from the
simulator** ("errores UDP"), never whether the retranslator kept up or whether
Wialon received anything. A green load test gives **false confidence** — it
can't see the P1 HOL stall (the actual bottleneck is the upstream TCP path).
Add end-to-end metrics: packets forwarded to Wialon, ack latency, UDP-buffer
drops (`netstat -su`), queue depth.
- **`SQL_THROTTLE_S = 0`** while the comment/log say "every 5 min." Every packet is
enqueued to the SQL writer (bounded queue 1000). The writer is correctly a
background thread (good — not blocking the loop), but at 500 pkt/s it can
saturate/drop. Set the throttle to the intended 300 s, or document the change.
- **GGA/RMC pairing is fragile**: requires *both* within 2 s, every cycle, or
nothing is sent. Under packet loss this drops position updates even with a valid
GGA fix. Consider sending on GGA alone with last-known speed/course.
- **Thread-safety:** worker threads mutate shared dicts the main loop also mutates
(`device_last_seen`, `wialon_sockets`, `device_first_connected`…). The GIL makes
single ops atomic, but `_save_device_state` iterating a dict the main loop grows
can raise `RuntimeError: dictionary changed size during iteration` (caught, so a
stats cycle is skipped). Snapshot with `list(...)` or add a lock. The async
model (single event loop) avoids this entirely.
## 🟡 Hygiene / security
- **Secrets in the repo.** `README.md` ships a **plaintext MSSQL `sa` password**;
`.env` and `.htracker_env` are committed. Rotate that password now, purge from
git history, and move to env/secret store. (`gps_logger.py` already does the
right thing — env var, "NUNCA hardcodear" — so just align the README/compose.)
- File literally named `import socket.py` in the repo root — will shadow the stdlib
`socket` if ever imported by name; delete/rename.
- Three live retranslator variants (`retranslator.py`, `_async`, `rtk2mqtt_…`,
plus `-original`) — pick one source of truth to avoid "which one is prod?"
---
## Recommended sequence
1. **Now (hours):** confirm the live process; pull the gateway log around the
death window; check **UDP buffer drops** (`netstat -su`) and whether all units
dropped within seconds of each other (→ P1) or one-by-one (→ P2 lockouts).
Cross-check **htracker-online-vs-Wialon-offline** for ZPM 020 / RS-SAN-001.
2. **This week (small, safe):** ship the **anti-jump hotfix** above to the running
`retranslator.py` and **clear `device_last_pos` on TTL cleanup**. This stops the
permanent black-holing with minimal risk.
3. **Migrate to `retranslator_async.py`** as the real concurrency fix, but first:
re-add the corrected anti-jump filter, **switch identity to `cell_id`**, and
re-run a **realistic** load test (slow-upstream + measured forwarding).
4. **Hygiene:** rotate/purge the DB secret; converge on one retranslator.
> Backend fixes and the hardware/firmware fixes are complementary: the hardware
> can be perfect and units will still vanish from Wialon under P1/P2 — and vice
> versa. The RCA differential is updated to reflect that H2 now has **confirmed
> code-level mechanisms.**

View File

@ -0,0 +1,99 @@
---
tags: [project-drogheda, bom, sourcing, hardware]
project: Project Drogheda
author: Helgen CTO
date: 2026-06-20
status: Active
---
# BOM Maestro — HBOX + Gabinete (24 V)
> Updated master BOM for the **24 V** Drogheda unit (the dominant ICAVE variant).
> Costs in **MXN**, from `Selección de componentes para gabinete2.xlsx` and the
> H-BOX BOM sheet. This supersedes the older loose BOM. For 12/48/110/230 V
> variants only the cabinet power/protection parts change (see source sheet).
> **Why this exists:** the earlier BOM drifted from reality. This pins the **as-built**
> parts (so diagnosis/PCB work targets the right hardware) and flags the
> **change-list** for the production fix.
## 1. HBOX — internal unit (per unit, 24 V)
| # | Item | Marca / Modelo | Qty | MXN | Notes |
|---|---|---|---:|---:|---|
| 1 | H-BOX Case | Helgen Series1 (**ASA print**) | 1 | 100 | UV-stable, ~95–100 °C — adequate |
| 2 | H-BOX Top | Helgen Series1 (**ASA**) | 1 | 50 | |
| 3 | DC PSU bracket | Helgen Series1 (ASA) | 1 | 20 | |
| 4 | Battery Case | Helgen Series1 (ASA) | 1 | 30 | houses 18650 |
| 5 | SIM | Telcel micro | 1 | 100 | per-unit data plan |
| 6 | **GNSS RTK** | Ardusimple simpleRTK2B **Pro** (AS-RTK2B-PROF9P-L1L2-NH-00) | 1 | 3,340 | ZED-F9P |
| 7 | **4G/NTRIP** | Ardusimple 4G NTRIP Master (AS-XBEE-4G-NA-UFL-00) | 1 | 2,440 | XBee LTE |
| 8 | **MCU watchdog** | DFRobot STM32F411 Blackpill | 1 | 104 | runs `blackpillrtkcode_v2` |
| 9 | GNSS antenna | Ardusimple AS-ANT2B-ANN-L1L2-50SMA | 1 | 700 | multiband |
| 10 | LTE antenna | Unit Electronics AR1582 (SMA→IPEX) | 2 | 42 | 4G diversity |
| 11 | Headers F 6/8/10-pin | 2.54 mm | 1ea | 10 | board interconnect |
| 12 | **5 V supply** | **Mean Well SD-15B-05** (18–36 V→5 V, 3 A) | 1 | 300 | ✅ industrial — keep |
| 13 | Boost / charger | Adafruit **PowerBoost 1000C** (2465) | 1 | 400 | battery path — review |
| 14 | **Ignition cond.** | DFRobot **DFR1015** buck | 1 | 106 | 24 V ign → Blackpill PA5 |
| 15 | **Battery** | Hyvel **18650 1S4P** (FG-0889, 3.6 V) | 1 | 204 | ⚠ charge ≤45 °C — decide |
| 16 | Status LED + holder | Steren 5 mm + bezel | 1 | 3 | PB0 path |
| 17 | Resistor | 1.5 kΩ (use TBC — see watchdog-schematic) | 1 | 1 | verify value |
| 18 | **HBOX↔cabinet** | **GX16-5** macho/hembra | 1 | 22 | ✅ locking — keep |
| 19 | Watchdog discretes | 2N3904, IRF9540N, 2N7000, R (1k/10k/220Ω) | — | — | → consolidate on PCB |
| 20 | Dupont terminals | red + black | 2 | 4 | ⚠ vibration — eliminate |
| 21 | Wire, glands, screws, label, switch | genérico / Steren | — | ~110 | NPT 3/8 gland |
| | **HBOX subtotal** | | | **≈ 8,041** | |
## 2. Gabinete — cabinet (per unit, 24 V)
| Item | Marca / Modelo | Qty | MXN |
|---|---|---:|---:|
| Enclosure IP67 (ABS) | Yetlebox **P3901** (39×29×16) | 1 | 1,435 |
| DIN rail | Weidmüller TS 35×7.5 | 1 | 50 |
| Neutral terminals | Weidmüller WDU 2.5 | 5 | 58 |
| Ground terminals | Weidmüller WPE4 | 4 | 154 |
| Fuse-holder terminal | Phoenix **PT 6-FSI/C** (3212166) | 3 | 900 |
| End stops | Wago 249-116 | 4 | 56 |
| Ignition SSR | Weidmüller **TOS 24VDC 2A** | 1 | 684 |
| Surge (SPD) | Phoenix **TTC-6-TVSD-C-24DC-PT-I** | 2 | 1,076 |
| Breaker | Phoenix **TMC 81C 05A** | 1 | 484 |
| **Supply → 24 V** | Mean Well **DDR-60G-24** | 1 | 748 |
| Fuse (blade) | automotriz **2 A** | 3 | 6 |
| Cable / ferrules / glands / labels | Indiana / Weidmüller / Helgen | — | ~110 |
| Shipping to port | — | 1 | 600 |
| **Cabinet subtotal** | | | **≈ 6,365** |
**≈ Total per 24 V unit: ~14,400 MXN** (HBOX ~8,041 + cabinet ~6,365). An
alternate supplier ("Israel" column in the sheet) shows ~1,284 MXN/cabinet
savings at 24 V — sourcing decision lives here in `09_`.
## 3. ✅ What's already good (don't re-buy / don't duplicate)
- **Cabinet** is industrial-grade: SPD + breaker + isolated SSR ignition + Mean
Well 24 V. The HBOX needn't re-implement input surge/protection.
- **SD-15B-05** (24→5 V, 3 A) is a real industrial supply — keep.
- **GX16-5** locking HBOX↔cabinet connector — keep (good vibration practice).
## 4. ⚠ Change-list for the production fix (feeds the PCB + hackathon)
| Change | From → To | Why | Ref |
|---|---|---|---|
| Inner case | **ASA — keep** (adequate); optional metal / plate-mount | conduction, not material, is the lever | RCA, hardware-brief §2b |
| Internal wiring | **Dupont/hand-wire → PCB + locking connectors** | vibration intermittents | hardware-brief §4 |
| Battery path | **PowerBoost 1000C + 18650 → decide:** drop / LiFePO4 / supercap | heat + safety; do we need parked tracking? | hardware-brief §6.1 |
| Fuse | **verify the 2 A blade** vs measured worst-case + inrush | client's "under-rated fuse" | RCA H5 |
| Watchdog discretes | **2N3904/IRF9540N/pull-ups → integrated load switch on PCB** | fewer parts, defined turn-on | hardware-brief §4 |
| SIM | micro-SIM buried → **front-panel SIM or MFF2 eSIM** | maintainability | hardware-brief §6.2 |
| Thermal | **conduct heat to metal plate / sunshade; NO internal fan** | sealed-box physics | hardware-brief §2b |
## Open Questions
- [ ] Confirm the **installed blade-fuse value** (2 A may be under-rated for LTE inrush).
- [ ] Battery decision (drop vs LiFePO4 vs supercap) — blocks the PCB power section.
- [ ] Lock supplier per line (Helgen vs alternate quote) for the August batch.
## Related
- [[CLAUDE|Master Index]] · [[03-Architecture-Hardware/cabinet-electrical-bom|Cabinet BOM detail]] ·
[[03-Architecture-Hardware/hardware-respec-and-pcb-brief|Hardware re-spec + PCB brief]] ·
[[03-Architecture-Hardware/watchdog-schematic|Watchdog schematic]]

Binary file not shown.

View File

@ -0,0 +1,87 @@
\section{Seguridad — leer primero}
\begin{helgennotice}[Riesgo de batería de litio]
La unidad contiene una batería \textbf{18650 Li-ion}. Una celda \textbf{inflada, caliente
o deformada} es riesgo de incendio. \textbf{No} la cargue, no la perfore ni siga
energizando una unidad con la batería visiblemente dañada. Desconecte, aísle en bolsa o
recipiente seguro, ventile y repórtelo.
\end{helgennotice}
\begin{itemize}
\item Trabaje con la máquina \textbf{apagada y bloqueada} (LOTO) durante la instalación.
\item Use EPP completo en todo momento en el patio.
\item Respete los voltajes: la entrada al HBOX es \textbf{24 V} desde el gabinete; nunca conecte el HBOX directo a la línea de la máquina.
\end{itemize}
\section{Contenido del kit}
\begin{itemize}
\item Gabinete IP67 (Yetlebox P3901) con platina, riel DIN y componentes de potencia/protección.
\item Unidad \textbf{HBOX} (simpleRTK2B Pro + 4G NTRIP Master + watchdog) con conector \textbf{GX16-5}.
\item Antena GNSS multibanda (SMA) y 2 antenas LTE.
\item SIM Telcel, glándulas, herrajes de montaje y etiquetas.
\end{itemize}
\section{Instalación del gabinete}
\begin{enumerate}[leftmargin=1.5em]
\item \textbf{Ubicación.} Monte el gabinete en un punto firme, con la menor exposición solar posible (preferir sombra) y buena vista al cielo para las antenas.
\item \textbf{Alimentación.} Conecte la alimentación de la máquina al breaker/SPD del gabinete según el voltaje (esta unidad: \textbf{24 V}). La fuente Mean Well entrega 24 V regulados al HBOX.
\item \textbf{Ignición.} Lleve la señal de ignición al relevador de estado sólido (SSR); éste entrega una señal aislada al HBOX.
\item \textbf{Conexión HBOX.} Acople el \textbf{GX16-5} entre el gabinete y el HBOX; apriete el anillo de seguro.
\item \textbf{Antenas.} Instale la antena GNSS y las 2 LTE por glándula; aplique torque y fijador de rosca a los SMA. La antena GNSS debe ver el cielo.
\item \textbf{SIM.} Verifique que la SIM Telcel esté insertada y activa.
\item \textbf{Sellado.} Cierre el gabinete asegurando el sello IP67; verifique glándulas apretadas.
\end{enumerate}
\section{Encendido y verificación}
Al energizar, observe el LED de estado. La secuencia normal es: \textbf{parpadeo rápido}
(arranque, esperando 4G/GNSS) $\rightarrow$ \textbf{parpadeo lento} (buscando) $\rightarrow$
\textbf{encendido fijo} (operando con RTK fijo). Confirme en la plataforma que la unidad
reporta posición.
\section{Estados del LED}
\hwtab{%
\begin{tabularx}{\textwidth}{@{}>{\bfseries}L{3.8cm} X@{}}
\toprule
\rowcolor{HelgenIndigo}\textcolor{white}{Patrón} & \textcolor{white}{Significado / acción}\\
\midrule
Parpadeo rápido & Arranque; espere a que el 4G/GNSS estabilice (normal al inicio).\\
Encendido fijo & Operando con energía de máquina y RTK fijo. Correcto.\\
Latido (1 s) & Modo batería (máquina apagada), reporte cada 30 s. Normal.\\
Parpadeo lento & Buscando / sin correcciones. Si persiste, revise antenas y señal 4G.\\
Doble (tipo SOS) & LIMP: sin 4G, sigue reportando posición estándar. Reporte a Helgen.\\
Estrobo & Reiniciando el stack (recuperación automática). Espere.\\
Apagado & Reposo / batería crítica. Verifique alimentación de 24 V.\\
\bottomrule
\end{tabularx}}
\section{Mantenimiento}
\begin{itemize}
\item Inspección trimestral: estado del sello, glándulas, torque de antenas y conectores; sin señales de calor (decoloración) ni batería inflada.
\item Mantenga el gabinete cerrado y sellado; \textbf{no} agregue ventiladores ni perforaciones (rompen el IP67).
\item Ante reemplazo de SIM o componentes, siga el procedimiento de Helgen y registre el cambio.
\end{itemize}
\section{Solución de problemas}
\hwtab{%
\begin{tabularx}{\textwidth}{@{}>{\bfseries}L{5.2cm} X@{}}
\toprule
\rowcolor{HelgenIndigo}\textcolor{white}{Síntoma} & \textcolor{white}{Acción}\\
\midrule
LED apagado con alimentación & Verifique 24 V en la salida de la fuente, breaker y fusible; revise el GX16-5.\\
LED en parpadeo lento persistente & Revise vista al cielo de la antena GNSS, torque de SMA y cobertura/saldo de la SIM 4G.\\
Desconexiones intermitentes en plataforma & Reporte a Helgen con el ID de la unidad; puede ser un punto de backend, no la unidad.\\
Batería caliente o inflada & Detenga, desconecte y aísle la batería; \textbf{riesgo de incendio}. Reporte de inmediato.\\
\bottomrule
\end{tabularx}}
\begin{helgennotice}[Soporte]
Reporte fallas a Helgen con el \textbf{ID de la unidad (cell\_id)} y la máquina. Una unidad
que se ve en el monitor de Helgen pero no en la plataforma del cliente suele ser un punto
de backend, no una falla de campo.
\end{helgennotice}

View File

@ -0,0 +1,18 @@
% Helgen Drogheda — Manual de Instalación y Operación · DRG-MAN-001 (es-MX)
\documentclass[11pt]{article}
\newcommand{\sistema}{Helgen Drogheda — HBOX RTK}
\newcommand{\sistemapdf}{Helgen Drogheda HBOX RTK}
\newcommand{\doctipo}{Manual de Instalación y Operación}
\input{../../_brand/estilo-helgen.tex}
\begin{document}
\helgenportada{Manual de Instalación y Operación}{HBOX + Gabinete — equipo portuario (24 V)}{%
\begin{tabular}{@{}ll@{}}
\textbf{Documento} & DRG-MAN-001 — Manual de Instalación y Operación\\
\textbf{Versión} & 1.0\\
\textbf{Fecha} & 20 de junio de 2026\\
\textbf{Dirigido a} & Personal de instalación y mantenimiento en sitio\\
\textbf{Clasificación} & Confidencial — uso de Hutchison Ports y Helgen\\
\textbf{Asistencia IA} & Elaborado con asistencia de Claude (Anthropic), revisado por el autor\\
\end{tabular}}
\input{cuerpo.tex}
\end{document}

View File

@ -0,0 +1,77 @@
# Cabinet Electrical BOM — the box the HBOX lives inside
**Source:** `Selección de componentes para gabinete2.xlsx` (Helgen, 2026-06)
**Date captured:** 2026-06-19
**Key point:** the HBOX is **not** mounted bare on a machine — it sits **inside an
industrial electrical cabinet (gabinete)** with DIN-rail power and protection
components. This BOM is that cabinet. It is **productized across voltages** (12 /
24 / 48 / 110 / 230 V machines) and **across multiple port terminals** (ICAVE,
Hutchison, TIMSA, EIT, LCT) — i.e. this is heading to **hundreds of cabinets**, so
the fixes must be production-grade and repeatable. *(Pricing/margins omitted here —
see the sheet.)*
---
## Nested-enclosure architecture (important for thermal)
```
Machine power (12/24/48/110/230 V) + ignition
│
▼ ┌──────────────── Gabinete IP67 (ABS, Yetlebox P3901, 39×29×16 cm) ───────────┐
breaker │ DIN rail · terminals · SPD · fuse holder · SSR/opto (ignition sense) │
+ SPD ──►│ Mean Well DDR-60G-24 ──► clean 24 Vdc ─────────────┐ (supply dissipates │
│ │ heat *inside* box) │
│ ┌────────── HBOX (3D-printed ASA, sealed) ────────┼───┐ │
│ │ PowerBoost 1000C + LiPo · simpleRTK2B+4G · STM32 watchdog │ │
│ └───────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────────────┘
```
Two sealed boxes, one inside the other, **with a 60 W-class power supply generating
heat in the outer one**, in full sun. See the thermal note in
`03-Architecture-Hardware/hardware-respec-and-pcb-brief.md` §thermal.
---
## Cabinet catalog (Helgen final selection, by machine voltage)
| Function | 12 V | 24 V | 48 V | 110–230 V |
|---|---|---|---|---|
| **Enclosure (IP67, ABS)** | Yetlebox P3901 | P3901 | P3901 | P3901 |
| DIN rail | Weidmüller TS 35×7.5 | ← | ← | ← |
| Neutral terminals ×5 | Weidmüller WDU 2.5 PA/VI | ← | ← | ← |
| Ground terminals | Weidmüller WPE4 | ← | ← | ← |
| Fuse-holder terminal ×3 | Phoenix Contact 3212166 **PT 6-FSI/C** (blade) | ← | ← | ← |
| Ignition isolation (SSR / opto) | Weidmüller **MOS 12-28VDC** | Weidmüller **TOS 24VDC 2A** | Wago **859-744** | IDEC **RV8S-L-D24-A120** |
| Surge protection (SPD) | Phoenix **TTC-6-TVSD-C-12DC-PT-I** | …**-24DC-** | …**-48DC-** | Phoenix DC + Total Ground **A2021** (AC) |
| Breaker | Phoenix **TMC 81C 05A** (5 A) | ← | ← | Schneider **A9F74202** (2 A, AC) |
| **Power supply → 24 Vdc** | Mean Well **DDR-60G-24** | Mean Well **DDR-60G-24** | Mean Well **DDR-60L-24** | Siemens **6EP1332-1LB00** (SITOP) |
Output rail in all cases: **24 Vdc, ~2.4 A, 60 W**.
---
## What this means for the fix (engineering read)
1. **The cabinet's power & protection are already industrial-grade.** Surge
suppression (Phoenix TVS/SPD), a breaker, isolated ignition sensing (SSR/opto),
and a quality regulated supply (Mean Well / Siemens) are present at the cabinet
level. **The HBOX PCB does NOT need to re-implement input surge/breaker** — it
can assume a **clean 24 Vdc feed** and an **isolated ignition signal**. That
simplifies the HBOX redesign (see hardware brief).
2. **So the weak link is confirmed to be inside the HBOX**, exactly where the
client pointed: PowerBoost 1000C + LiPo + Dupont — not
the cabinet (the **ASA enclosure** is adequate). The HBOX-internal job is **24 V → 5 V (robust buck) + watchdog +
the RTK stack**, with the LiPo decision (drop/replace) still open.
3. **Verify the actual fuse value.** The PT 6-FSI/C is a *holder*; the fitted blade
fuse rating is what matters for the client's "under-rated fuse" concern — read
the installed fuse and compare to the measured worst-case + inrush.
4. **The Mean Well runs hot inside a sealed box.** At 60 W class it dissipates
several watts continuously; that heat has nowhere to go in a sealed ABS IP67
cabinet → it raises the ambient the HBOX (and its 18650 + boost + modem) must
survive. This is now a **primary thermal driver**, not just solar load.
> Open question for Leo to confirm: does the HBOX receive the cabinet's **24 V**
> (then step down internally), or the raw machine voltage? The BOM implies a clean
> 24 V feed + isolated ignition — confirm so the HBOX power redesign targets the
> right input.

View File

@ -0,0 +1,120 @@
# Firmware Review — `blackpillrtkcode.ino` (v1 → v2)
Review of the as-deployed watchdog firmware, with the fixes shipped in
`04-Source-Code/firmware/blackpillrtkcode_v2/blackpillrtkcode_v2.ino`. Severity: 🔴 likely field-impacting ·
🟠 robustness · 🟡 cleanup.
UBX command checksums and ADC divider math were **verified programmatically**
(see end of this doc) — those parts of v1 are correct.
---
## Issues found
### 🔴 1. Loss-of-corrections recovery targets the wrong subsystem
**v1:** after `SOFT_RESET_DELAY` (5 min) of `fixQuality < 4`, it sends
`UBX_SOFT_RESET` (a GNSS hot-reset) to the ZED-F9P.
**Problem:** corrections arrive over the **4G NTRIP modem**, not from the GNSS.
Hot-resetting the receiver does nothing for a hung modem — the dominant field
failure per the client. Five minutes are spent on a no-op before the only
effective action (a full power-cycle at 10 min).
**v2:** drops the GNSS hot-reset; recovery = **power-cycle the whole stack**
(which restarts the 4G module), gated by a grace window and backoff.
### 🔴 2. Hard-reset has no backoff → reboot-loop / Wialon flapping
**v1:** after `HARD_RESET_DELAY` (10 min) it power-cycles, returns to BROWNOUT,
immediately powers back on, and — if the fault persists — does it **again every
~10 min indefinitely**. Each cycle is a multi-minute disconnect.
**Problem:** a genuinely dead 4G card, or a unit parked without sky view, makes
the unit **flap off Wialon forever**. This is a prime suspect for "desconexiones
recurrentes."
**v2:** **exponential backoff** (1→2→4→8→16 min, capped 20 min) then a **latched
LIMP mode** that keeps the unit **reporting standalone GPS** and only retries
recovery every 30 min. The unit never silently vanishes.
### 🔴 3. No detection of a fully hung stack (NMEA silence)
**v1:** only reacts via `fixQuality`. If the GNSS/4G stack hangs and stops
emitting NMEA entirely, `fixQuality` simply freezes at its last value — the
watchdog may not act at all, or acts on stale data.
**v2:** tracks `lastNmeaMs`; **no NMEA for 60 s while powered ⇒ power-cycle now**.
Catches hung hardware far faster than the fix-quality ladder.
### 🟠 4. No hysteresis / debounce on vehicle-power & ignition
**v1:** `pa4_on = (v5vF > 4.0)` is a single hard threshold; ignition is a raw
`digitalRead`. Around the threshold (alternator noise, ignition flicker), the
rate mode (1 Hz↔30 s) and state can chatter.
**v2:** hysteresis band (ON >4.2 V, OFF <3.8 V) + 750 ms debounce on both
vehicle-power and ignition.
### 🟠 5. Brownout exit has no debounce (oscillation risk)
**v1:** entry to BROWNOUT is debounced 3 s, but the **exit** powers on the instant
`pa4_on || pa5_on || vBat>3.7`. A brief blip near the threshold can start a
power-on it can't sustain → drain → repeat.
**v2:** wake is driven by the same debounced/hysteretic signals; brownout entry
uses a 5 s confirmation.
### 🟠 6. No thermal awareness (the actual field failure mode)
**v1:** nothing measures temperature, despite "died from heat."
**v2:** reads the STM32 internal temp sensor (F411 factory cal), logs session
**min/max**, **derates to 30 s** above 75 °C to cut self-heat/power, and shows it
on the dashboard. Firmware can't cool the box, but a pulled unit's `tMax` now
**proves or kills the thermal hypothesis**. (Disable with `ENABLE_MCU_THERMAL 0`
if your core lacks `ATEMP`/`AVREF`.)
### 🟡 7. `String` heap churn on an ARM MCU
**v1:** comments claim `const char*` avoids fragmentation, but `nmeaBuffer` is a
`String`, and `drawDashboard` builds a `String paddedMsg` every refresh.
**v2:** fixed `char nmeaBuf[110]` parser (no heap) and `snprintf` padding.
### 🟡 8. Doc/firmware mismatch: boot delay
The LED table says "waiting 25 s"; v1 `BOOT_STABILIZE_TIME` is **15 s**.
**v2:** set to **25 s** to match the documented SOP (gives the 4G modem more time
to attach before the supervisor starts judging the link). Tune on the bench.
### 🟡 9. Minor: redundant `pinMode` before `GNSS.begin()`
Harmless (the serial peripheral reconfigures the pins); left as explicit intent
in v2.
---
## What v1 got *right* (kept in v2)
- **Float is not treated as failure.** `fixQuality >= 4` correctly counts both
RTK **fixed (4)** and **float (5)** as healthy. (Earlier verbal claim that
FLOAT triggered resets was wrong — corrected here.)
- Floating the TX pin on power-off to avoid back-powering the rail through a GPIO.
- Low-pass filtering the ADC readings.
- Hysteresis between `BAT_CRITICAL` (3.4 V) and `BAT_RECOVERY` (3.7 V).
- The UBX rate commands and their checksums (verified below).
---
## v2 recovery state machine (summary)
```
SLEEP ──(vehicle pwr | ignition | Vbat>3.7)──► BOOTING ──(25 s)──► RUNNING
▲ │
│ Vbat<3.4 & no pwr & no ign (5 s) │
└───────────────────────────────────────────────────────── RUNNING supervises:
• no NMEA 60 s ........................► POWER_CYCLE (gated by backoff)
• RTK lost > 120 s grace .............► POWER_CYCLE (gated by backoff)
• RTK healthy 5 min .................► clear backoff + LIMP latch
POWER_CYCLE (6 s rail off) ──► BOOTING ; after 5 fails ──► LIMP (keep reporting)
```
---
## Verification performed (2026-06-19)
- **UBX checksums** recomputed (8-bit Fletcher over class…payload): `UBX_1HZ`
→ `01 39` ✓, `UBX_30S` → `1E AD` ✓, `UBX_SOFT_RESET` → `0F 66` ✓.
- **CFG-RATE decode:** 1 Hz = measRate 1000 ms × navRate 1; 30 s = 1000 ms × 30 ✓.
- **ADC dividers (12-bit, 3.3 V, ÷2):** LiPo 3.4–4.2 V → 1.70–2.10 V at pin
(2110–2606 counts); 5 V rail → 2.50 V (3102 counts). No ADC clipping ✓.
- **Backoff ladder:** 1, 2, 4, 8, 16 min → LIMP @ 30 min, as designed ✓.
- **Compile:** v2 builds clean under `g++ -std=c++14 -Wall -Wextra` against a
hardware stub (logic/types/flow only).
> ⚠️ The stub compile validates **logic**, not the STM32 target. **Bench-flash and
> test v2 on real hardware** (Arduino IDE / PlatformIO, STM32duino core) before
> any field deployment — especially the thermal read and the power-cycle timing.

View File

@ -0,0 +1,260 @@
# Hardware Re-spec + Watchdog PCB Brief
**Date:** 2026-06-19 · **Owner:** Leo Rodriguez (Helgen)
**Strategy (Leo's call):** **phased** — ship a ruggedized interim to the fleet
now (Phase 1), design the production PCB in parallel (Phase 2).
**Drivers:** the RCA (`06-Testing-Validation/RCA-2026-06-field-failures.md`) and the ICAVE client
review (prototype-grade, vibration, connectors, thermal, 4G failures).
> This is an engineering brief, not a finished schematic. Specific part numbers
> are **starting points** to price/validate — confirm against the measured power
> budget (see §2) and your supplier availability.
---
## 1. Design requirements (the bar the fix must clear)
> **Context update (2026-06-19):** the HBOX sits **inside an industrial cabinet**
> that already provides surge protection, a breaker, isolated ignition sensing, and
> a regulated **24 Vdc** supply (Mean Well DDR-60G-24) — see
> `03-Architecture-Hardware/cabinet-electrical-bom.md`. So the HBOX can assume a **clean 24 V feed**
> and **need not re-implement input surge/reverse-polarity**. The redesign focus
> narrows to: **24 V→5 V**, the watchdog, the LiPo decision, and — now the top
> issue — **thermal management** (see §thermal).
| Requirement | Target | Why |
|---|---|---|
| Operating temp | **−10…+75 °C** internal, survive +85 °C | sealed **ASA** box **inside** a sealed ABS IP67 cabinet that **also holds the heat-dissipating Mean Well supply**, in full sun |
| **Enclosure material** | **ASA (current) — keep**; UV-stable, ~95–100 °C | confirmed ASA (not PLA): margin over the cabinet interior; the case is not the liability |
| Input voltage | **clean ~24 V** from the cabinet (design the buck 9–36 V for margin) | cabinet's Mean Well already regulates; isolated ignition provided |
| Surge/transient | **handled at cabinet level** (Phoenix SPD + breaker); HBOX keeps a modest TVS | don't duplicate the cabinet's protection |
| Vibration | survive continuous machinery vibration | no Dupont, locking connectors, retained boards |
| Ingress | **IP67 maintained**, no internal condensation | coastal humidity; sealing constrains cooling (§thermal) |
| 5 V rail | **≥3 A continuous**, handle LTE TX bursts (~2 A) | modem peaks brown out a 1 A hobby boost |
| Maintainability | **SIM swap without disassembly**; labeled, keyed | client point #5 |
| Serviceability | one PCB, documented, test points, repeatable | "apt for production" |
| Observability | log temp + battery + link health | prove failures in the field |
---
## 2. First, measure the power budget (blocks everything)
Before choosing a DC-DC or fuse, **bench-measure** the stack on the actual
hardware:
- Idle, 1 Hz, and **LTE-TX-burst** current at 5 V (modem bursts are the spike).
- Inrush at power-on (caps + modem).
- Repeat at 12 V and 24 V input.
Expected ballpark (validate): avg ~0.3–0.6 A @5 V, **bursts 1.5–2 A** during LTE
transmit. That burst is almost certainly **beyond the PowerBoost 1000C's ~1 A
continuous rating** — a strong candidate for both the instability *and* the
"under-rated fuse" the client flagged. Everything below assumes ~2.5 A peak @5 V;
correct it once measured.
---
## 2b. Thermal & enclosure — now the #1 physical fix
**The problem is a nested thermal trap.** A sealed **ASA** HBOX sits inside a sealed
**ABS IP67** cabinet that **also contains the Mean Well supply** (60 W class,
dissipating several watts continuously), in full Veracruz sun. Two failure drivers
stack:
- **The ASA shell is not the problem.** ASA is UV-stable and softens at ~95–100 °C,
with margin over the cabinet interior, so the HBOX shell will **not sag/warp** at
these temperatures. (Corrects an earlier PLA assumption.)
- **The LiPo is in that same hot space** (charge limit 45 °C) → degradation,
swelling, voltage sag → brownout.
### About adding a fan — read this first
**A fan *inside* a sealed IP67 box does almost nothing.** It only stirs the same
trapped air; with no path to outside air, **no heat actually leaves the box** (it
even adds a little). Cooling requires either **moving heat out by conduction** or
**exchanging air with the outside** — and air exchange **breaks IP67**, which in a
salty, humid, dusty port invites corrosion. So:
| Option | Removes heat? | Keeps IP67? | Verdict |
|---|---|---|---|
| Internal fan only (sealed) | ✗ (just stirs air) | ✓ | **Don't bother** |
| Filtered vents + extractor fan | ✓ | ✗ (→ ~IP54) | Only with filters + maintenance; risky in this environment |
| **Conduction: mount hot parts to a metal plate / metal enclosure** | ✓✓ | ✓ | **Best** — heatsink to the outside, stay sealed |
| **Reduce solar gain** (white/reflective, **sunshade**, relocate out of sun) | ✓ | ✓ | **Cheap, do it now** |
| **Reduce internal generation** (efficient DC-DC, **remove LiPo**) | ✓ | ✓ | **Do it** |
| Gore pressure-vent | ✗ (condensation only) | ✓ (membrane) | Keep for humidity, not for cooling |
| Active (Peltier / vortex / heat-exchanger) | ✓ | ✓ | Overkill/expensive — last resort |
**Recommended thermal strategy (sealed, no IP compromise):**
1. **Keep the ASA enclosure** (UV-stable, ~95–100 °C — adequate). Optionally move to
**metal** for extra heatsinking, or **eliminate the inner box** and
mount the PCB directly to the cabinet plate.
2. **Conduct heat out:** bolt the DC-DC (and the new HBOX board's hot spots) to the
cabinet's **metal mounting plate / gland plate**, or move to a **metal (aluminium)
cabinet** that acts as a heatsink. Use thermal pads/gap-filler.
3. **Cut the solar load:** white/reflective cabinet, a **sunshade**, and mount out
of direct sun where the machine allows.
4. **Cut internal heat:** a higher-efficiency 24 V→5 V buck and **removing the
LiPo** both lower the steady-state interior temperature.
5. **Quantify it:** put a thermocouple/logger inside a representative cabinet for a
full sunny shift — design to the measured number, not a guess.
---
## 3. Phase 1 — Interim ruggedization (do now, on existing modules)
Goal: stop the bleeding on the deployed fleet without waiting for a PCB. All of
this is achievable with off-the-shelf parts and rework.
1. **Kill the Dupont jumpers.** Replace every jumper with **locking, keyed
connectors** — JST-XH (signal) / Molex Micro-Fit 3.0 (power). Crimped, not
hand-twisted. This alone removes the #1 vibration intermittent.
2. **Replace the PowerBoost 1000C** with an **industrial DC-DC** stepping the
cabinet's **24 V → 5 V, ≥3 A** (e.g., a sealed **Mornsun/Recom** module or a
**TI LMR33630-class** buck for the LTE bursts). Design the input wide (9–36 V)
for margin, but the normal feed is the cabinet's clean 24 V. Sized from §2.
3. **Resolve the battery (decision — see §6).** At minimum **relocate the LiPo**
to the coolest wall, away from the modem/DC-DC, and add a temperature cutoff;
better, **replace it** with LiFePO4 or a supercap hold-up, or **drop battery
mode** if the client doesn't need parked tracking.
4. **Verify the cabinet fuse.** Read the **blade fuse fitted in the Phoenix PT
6-FSI/C** holder and compare to the measured worst-case + inrush — this is the
"under-rated fuse" the client flagged. Surge/breaker are already handled at the
cabinet (Phoenix SPD + breaker); the HBOX keeps only a **modest local TVS**.
5. **Mechanical lockdown.** Mount each board on **standoffs with vibration
isolators/grommets**; retain the simpleRTK2B + 4G master in their sockets with
a bracket or retention clip; **thread-lock + torque** the SMA bulkheads.
6. **Conformal-coat** the watchdog/discrete wiring (acrylic or urethane) against
humidity and to stabilize solder joints under vibration.
7. **Thermal quick wins (see §2b — do NOT add an internal fan to a sealed box):**
keep the **ASA** inner box (adequate) or remount the boards on the metal plate; cut
solar gain (white/reflective cabinet + **sunshade**, relocate out of sun);
**conduct** the DC-DC/modem heat to the cabinet's metal plate; keep a
**Gore-style pressure-vent** for condensation only.
8. **SIM access:** bring the SIM to a **front-panel holder** so swaps don't need
teardown.
9. **Flash firmware v2** (thermal logging + proper 4G recovery) on every unit.
10. **Instrument** at least one unit with an interior **thermocouple log** for a
full sunny shift to quantify the thermal problem.
Phase 1 is the "permanent-enough" stabilization while Phase 2 is fabricated.
---
## 4. Phase 2 — Integrated Watchdog Carrier PCB (the real fix)
A single **4-layer, industrial-temp** carrier board that replaces all the
hand-wiring and the hobby power boards, and onto which the simpleRTK2B and 4G
NTRIP Master mount via **locking board-to-board** (or retained XBee socket).
### Block diagram
```
Cabinet 24V (already SPD/breaker-protected) ─► [keyed plug]
└► modest TVS + reverse-polarity FET ─► bulk cap
└► DC-DC buck 24V→5V (design 9–36V), ≥3A ─► 5V rail
├► STM32 watchdog (3.3V LDO) ── UART ──► simpleRTK2B RX1 (UBX)
│ ├ sense: Vin, 5V, battery dividers (filtered)
│ ├ status LED + SWD header + debug UART (dashboard)
│ └ optional: 4G modem health/RESET line, RTC
├► high-side LOAD SWITCH / eFuse ─► switched 5V ─► simpleRTK2B + 4G stack
└► hold-up: supercap bank OR LiFePO4 charger (see §6)
Antennas: bulkhead SMA pigtails (GNSS L1/L2 + LTE) · SIM: front-panel holder/MFF2
```
### Key design choices vs the current discrete design
| Current (discrete) | PCB replacement | Benefit |
|---|---|---|
| 2N3904 + IRF9540N + 10 k pull-up | integrated **high-side load switch / eFuse** (e.g., TPS25200 / TPS2596 / load-switch IC) | current-limited, fewer parts, defined turn-on, no gate-pull-up gotchas |
| PowerBoost 1000C | automotive **buck ≥3 A** + protection front-end | survives LTE bursts, wide Vin, transient-tolerant |
| LiPo in hot box | supercap hold-up **or** LiFePO4 (or none) | removes the thermal/safety liability |
| **ASA inner box (UV-stable, ~95–100 °C)** | keep (adequate); optional metal for extra heatsinking, or mount PCB to the cabinet plate | survives the cabinet's internal temperature |
| Dupont + screw terminals | **locking JST/Molex** + pluggable terminal block | vibration-proof |
| Bare boards loose in box | retained board-to-board + isolated standoffs | no movement, no solder flex |
| SIM buried in stack | **front-panel SIM** or **MFF2 eSIM** | client point #5 solved |
| No local protection | **modest TVS + RP FET** (cabinet has the SPD/breaker) | survives without duplicating the cabinet |
| No temp/observability | MCU temp + sense dividers + test points | provable diagnostics |
### Board-level details to specify
- **Protection front-end (light — the cabinet does the heavy lifting):** the
cabinet already provides surge (Phoenix SPD) + breaker + isolated ignition. The
HBOX board needs only a **reverse-polarity P-FET** and a **modest TVS** on its
24 V input — don't duplicate the cabinet's SPD/eFuse.
- **DC-DC:** qualify input 9–36 V; ≥3 A @5 V; good thermal pad to a copper pour /
enclosure; spread-spectrum or proper filtering for GNSS RF cleanliness
(switcher noise can hurt the L1/L2 front-end — keep it away from the antenna
feed and filter it).
- **MCU:** keep STM32F411 to reuse v2 firmware, or step to an
**automotive/industrial-temp STM32 (G0/L4)**. Bring out **SWD** + a **debug
UART** for the dashboard. Add the internal-temp logging already in v2; consider
an external temp sensor near the LiPo/modem.
- **GNSS RF integrity:** keep the switcher and digital noise away from the GNSS
antenna trace; solid ground plane; the ZED-F9P front-end is sensitive — this
protects fix quality (and therefore the watchdog's view of "link health").
- **Connectors:** all locking/keyed; bulkhead SMA with lock washers; a single
keyed vehicle-power plug.
- **Mechanical:** mounting holes with vibration isolators; design for conformal
coat or selective potting of the power section.
- **DFM/serviceability:** silkscreen labels, test points on every rail + UART,
a fab/assembly package, and a documented BOM (this is literally the "apt for
production" the client asked for).
---
## 5. Indicative BOM (validate prices/availability/temp grade)
| Function | Interim (Phase 1) | PCB (Phase 2) |
|---|---|---|
| DC-DC **24→5 V** ≥3 A | Recom/Mornsun/TI buck module | TI LMR33630-class on-board buck |
| Load switch | keep NPN+P-FET (retained) | TPS25200 / TPS2596 / load-switch IC |
| Input protection | modest TVS + RP diode (cabinet has SPD/breaker) | RP FET + modest TVS on-board |
| Hold-up / battery | relocate LiPo + temp cutoff | supercap bank **or** LiFePO4 charger |
| **Inner enclosure** | **ASA — keep** (adequate); or mount to plate | metal box / mount PCB to cabinet plate |
| MCU | existing Blackpill | STM32F411 (or G0/L4) on-board |
| Interconnect | JST-XH / Molex Micro-Fit | board-to-board + locking I/O |
| Antennas | re-torque bulkhead SMA | bulkhead SMA pigtails |
| SIM | front-panel holder | front-panel holder or MFF2 eSIM |
| Coating | acrylic conformal coat | conformal coat / pot power section |
| Thermal | sunshade + **conduct heat to metal plate** (no internal fan) | metal cabinet as heatsink; sealed (keep IP67) |
---
## 6. Open decisions for Leo (these change the design)
1. **Battery mode — keep, change, or drop?**
- *Keep tracking while the vehicle is off* (anti-theft / parked location)?
Then use **LiFePO4** (wider temp, safer) or a relocated/temperature-managed
cell — **not** a LiPo baking next to the modem.
- *Only need ride-through* across brief power dips? A **supercap** hold-up is
simpler, safer, and temperature-tolerant — but won't give long parked
tracking.
- *Vehicle-only* acceptable? **Drop the battery** entirely — removes the
single worst thermal/safety liability. **(Recommended if the client doesn't
require parked tracking — confirm with ICAVE.)**
2. **SIM strategy:** swappable **front-panel SIM** vs **MFF2 eSIM** (eliminates
the swap problem but commits the carrier). Depends on how often SIMs change.
3. **MCU:** reuse F411 (fast, keeps v2 firmware) vs move to an industrial-temp
STM32 (more margin, small port effort).
4. **Inner enclosure material:** **confirmed ASA** (UV-stable, ~95–100 °C) — adequate
for the nested-cabinet temperatures; no material change required. Optionally
eliminate the inner box and mount the PCB to the cabinet plate for better conduction.
5. **Cabinet thermal path:** keep ABS IP67 + sunshade + **conduct heat to a metal
plate**, or move to a **metal (aluminium) cabinet** as a heatsink (best, costs
more). **An internal fan in a sealed box is not a fix (see §2b)** — only
conduction or (IP-breaking) ventilation removes heat.
6. **Make vs buy the modem:** keep the ArduSimple 4G NTRIP Master, or move to a
qualified industrial LTE module with a known wide-temp rating.
---
## 7. Suggested next actions (feeds `PLAN.md`)
- Bench power-budget + inrush measurement (§2) → picks the 24→5 V buck.
- **Quantify cabinet temperature** (thermocouple in a representative cabinet, full
sunny shift) → design target for §2b.
- Decide §6.1 (battery), §6.4 (inner-box material), §6.5 (cabinet thermal path) —
the biggest levers. **The ASA enclosure is adequate — no material swap needed; focus on the conduction path + active thermal.**
- Phase-1 ruggedization kit BOM + rework one unit as the reference build.
- Phase-2 schematic → layout → fab/assembly; pilot 2–3 boards before fleet.
- Cabinet BOM is industrial-grade and stays — see `03-Architecture-Hardware/cabinet-electrical-bom.md`.

View File

@ -0,0 +1,192 @@
---
tags: [project-drogheda, hardware, firmware, watchdog, telemetry, nmea]
project: Project Drogheda
author: Helgen CTO
date: 2026-06-20
status: Analysis — review & bench-test before any wiring/firmware change
---
# Watchdog Health Telemetry — Feasibility & Improvement Analysis
> **The question:** can the STM32F411 watchdog transmit its health (temp, reset
> cause, link state, recovery count…) over UART so it **lands in our server
> message**, e.g. as a custom `$GPTXT`? You've seen mixed information. This
> resolves it against the **as-built wiring** and proposes a clean design.
---
## TL;DR
- **As wired today: No.** The watchdog sits on the F9P's **UART1** (it reads NMEA
and sends UBX rate commands). The data that reaches the server flows out the
F9P's **UART2 → 4G NTRIP Master → server**. The watchdog is **not on that path**,
so nothing it prints can reach the backend.
- The `$GPTXT,NET,<id>,TELCEL,<rssi>,<cell>` you see at the server is the **4G
modem's** own network-status line (the `<id>` is the modem's station ID set via
`AS+K`, which the backend treats as the `cell_id`). It is **not** produced by the
F9P or the watchdog.
- **It is achievable** — but it requires putting the watchdog **in the modem's
NMEA-uplink path** (a relay/injector) and emitting a **proprietary sentence**
(`$PHLG…`, *not* `$GPTXT` — see the collision note). This is a **wiring change**,
clean on the Phase-2 PCB, and it must be **bench-validated** against one unknown:
*does the ArduSimple 4G NTRIP Master forward arbitrary/proprietary NMEA, or only a
whitelist?* That single question is the source of the "mixed information."
- **Lower-risk interim (no rewiring):** the watchdog already has a full health
record — ship the **v2.1 parseable log** (`docs/LOGGING-SPEC.md`) over its console.
It's authoritative per-unit health, captured on pulled units; it just isn't *live
to the server*. In-band-to-server telemetry is the Phase-2 upgrade.
---
## 1. The as-built data path (why the answer is "not yet")
```text
┌──────────────── simpleRTK2B Pro (ZED-F9P) ─────────────────┐
│ │
UART1 ◄──┤ TX1 → NMEA (GGA…) ──► STM32 PA3 (RX) [watchdog: READS] │
│ RX1 ← UBX rate ◄── STM32 PA2 (TX) [watchdog: WRITES]│
│ │
UART2 ◄──┤ TX2 → NMEA/GGA @1Hz ──► 4G NTRIP Master (XBee) ──► SERVER │ ← server path
│ RX2 ← RTCM3 ◄── 4G NTRIP Master (NTRIP caster) │
└────────────────────────────────────────────────────────────┘
│
4G modem also emits its OWN $GPTXT,NET,<AS+K id>,TELCEL,…
(network status + station id = the backend's cell_id)
```
Key facts (confirmed in firmware + the 20-Jun bench analysis):
- Firmware: `HardwareSerial GNSS(PA3, PA2)` → **F9P UART1**. `parseNMEA()` only
*reads* GGA (for `fixQuality`/`satellites`/`rxCount`); `applyRate()` only *writes*
UBX. On power-off it **floats** PA2/PA3. The watchdog never transmits toward the
modem.
- The modem is on the Pro's **XBee socket = UART2** (the "XBEE↔UART2 switch"); GGA is
configured out **UART2 @ 1 Hz**. The server stream and the NTRIP GGA-uplink both
ride UART2.
- Therefore the watchdog (UART1) and the server path (UART2) are **electrically
separate**. No firmware change alone can bridge them.
---
## 2. The three theoretical ways in (and which one to use)
1. **Make the F9P emit it.** ✗ Not possible. The ZED-F9P has no command to inject an
arbitrary NMEA string into its UART2 output. It emits only its own sentences + UBX
replies.
2. **Tell the 4G modem to carry it.** ✗ No runtime path. The modem's `$GPTXT` is
network status; `AS+K` (station id) is *set-once* config, not a data channel the
MCU can stuff health into per-cycle.
3. **Put the watchdog in the uplink NMEA path (relay/injector).** ✓ **The viable
design.** Route the F9P→modem NMEA *through* the STM32:
```text
F9P TX2 (NMEA) ──► STM32 RX ──[relay verbatim]──► STM32 TX ──► modem RX
└─[inject $PHLG health @ ~1/30s in the idle gap]
modem ──► forwards EVERYTHING (GGA/RMC/GPTXT + $PHLG) ──► server
RTCM from the modem can still go straight to the F9P RX2 (corrections path intact).
```
The backend already maps **(ip,port) → cell_id** (from the modem's `$GPTXT`), so a
`$PHLG` arriving on the same socket is **auto-associated to the right unit** — no
need to put the id in the health sentence. (This reuses the CGNAT-safe identity work
shipped in `retranslator.py`.)
> **Retrofit variant (no full relay):** diode-OR / open-drain the STM32 TX onto the
> modem's RX and inject only during the F9P's idle gaps (at 115200 baud, 1 Hz NMEA
> leaves the line idle >90 % of the time). Cheaper to bolt on, but collision-prone and
> harder to guarantee — **prefer the clean relay on the PCB.**
---
## 3. The proprietary sentence (don't reuse `$GPTXT`)
**Collision warning:** the backend parses `$GPTXT` by taking **field 2 as the
`cell_id`**. A health `$GPTXT` would be mis-read as a device id. Use a **proprietary
talker** instead — `$PHLG` (`P` = proprietary, `HLG` = Helgen):
```text
$PHLG,<fw>,<state>,<link>,<fix>,<sat>,<rx>,<vbat>,<v5v>,<tc>,<tmax>,<rec>,<limp>,<nmea_age_ms>,<reset>*CS
e.g. $PHLG,2.1,RUN,LIMP,1,12,34192,4.01,5.02,63,71,2,1,420,IWDG*4F
```
Fields mirror the v2.1 `TLM` record (state, link, fix, sats, rx, battery, 5 V,
**MCU temp + session max**, recovery count, limp latch, NMEA age, **reset cause**).
`CS` = standard NMEA XOR checksum.
**Backend side** is a ~15-line addition to the hardened `retranslator.py`: a branch
that recognises `$PHLG`, verifies the checksum, and stores the fields per `cell_id`
(keyed by the existing `(ip,port)` map). Then the monitor can surface, **per unit and
live**: MCU temp / `tMax` (the thermal-hypothesis evidence, without pulling the unit),
reset cause (reboot-loop forensics), recovery count, and LIMP state — and it ships to
Loki for free.
---
## 4. What to bench-test before committing (resolves the "mixed info")
1. **Does the 4G NTRIP Master forward a proprietary `$P…` sentence?** Inject one on
UART2-RX on the bench and watch the server `tcpdump`. *If it whitelists talkers:*
fall back to a `$GPTXT,HLT,…` subtype and special-case it in the backend by **field
1** (`HLT` vs `NET`) so it never collides with the cell-id parse. **This is the one
experiment that settles the whole question.**
2. **GGA integrity for NTRIP.** The modem relays the F9P's GGA *up to the caster* for
network RTK. The relay must be **byte-exact and low-latency** and only inject in
idle gaps — never delay or mangle GGA, or corrections degrade.
3. **Rate / cost.** Health at **1 / 30–60 s** keeps cellular + backend load trivial.
4. **Checksum** must be correct or the backend (and possibly the modem) drops it.
---
## 5. Other watchdog improvements (independent of the telemetry question)
### 🔴 5a. The "no-RTK ≠ dead-modem" thrash risk — *act on this now*
v2 treats `fixQuality >= 4` (RTK) as the only healthy link, and after
`NTRIP_GRACE` (120 s) of `< 4` it **power-cycles the stack**. But the 20-Jun bench
reality is **every unit is SPS (quality 1), no RTK** (caster/FLASH/sky issue, *not* a
modem hang). With the watchdog connected as-is, a unit that is **GNSS-healthy and
forwarding fine at SPS** would be **power-cycled every ~2 min + backoff** — manufacturing
the very disconnects we're trying to kill. **Fix:** only power-cycle on evidence of a
*link/modem* hang (NMEA silence is already handled); treat *stable SPS with incrementing
`rx`* as a reportable **DEGRADED** state, **not** a recovery trigger — at least until the
corrections pipeline (move off rtk2go, save F9P config to FLASH, open-sky retest) is
fixed. This is the highest-value firmware change right now.
### 🟠 5b. Ship the v2.1 parseable log (`docs/LOGGING-SPEC.md`)
The ANSI dashboard overwrites itself and has no timestamps/reset-cause → useless as a
field record. The v2.1 spec (BOOT/COLS/TLM/EVT, reset cause, ~60 additive lines) makes
the watchdog the **authoritative per-unit health record**. It's also the exact data the
`$PHLG` sentence would carry — **do this first**, so every later change is observable.
### 🟠 5c. Independent watchdog (IWDG) — watch the watcher
Nothing currently recovers the Blackpill itself if *it* hangs. Enabling the STM32 IWDG
makes an MCU lockup self-reset and show `reset=IWDG` next boot — closing the last
"silent vanish" gap for a device whose whole job is fail-safe.
### 🟠 5d. External temp sensor + persisted counters
v2 logs **MCU** temp, but the heat casualties are the **18650 / 4G modem**, not the die.
Add a small external sensor near the cell/modem (PCB brief already flags this), and
**persist** `tMax` / `recoveryCount` / reset history to RTC backup registers or flash so
a pulled unit's forensics survive the power-cycle. Directly serves the thermal RCA.
---
## 6. Recommendation (phased)
| When | Action | Risk |
|---|---|---|
| **Now** (firmware only) | 5a (stop SPS-driven power-cycling) + 5b (v2.1 log) + 5c (IWDG). Bench-test, bump v2.1, then flash. | Low — additive / behavioural guard. |
| **Bench experiment** | Test #1: does the modem forward `$P…`? Decide `$PHLG` vs `$GPTXT,HLT`. | None — measurement. |
| **Phase 2 (PCB)** | Watchdog as NMEA relay/injector on the UART2 uplink + external temp sensor; backend `$PHLG` parser + monitor/Loki surfacing. | Medium — relay must preserve GGA/NTRIP; validate before fleet. |
**Bottom line for the deck:** *Yes, we can put unit health (incl. temperature) into the
live server feed — it needs the watchdog inserted into the modem's uplink path (a
Phase-2 PCB change) and one bench test of the modem's forwarding. Until then the same
health is captured on-unit via the v2.1 log. Separately, before we connect the watchdog
we must stop it from power-cycling healthy SPS units.*
## Related
- [[03-Architecture-Hardware/watchdog-schematic|watchdog-schematic]] ·
[[03-Architecture-Hardware/firmware-review|firmware-review]] ·
[[03-Architecture-Hardware/hardware-respec-and-pcb-brief|PCB brief]] ·
`04-Source-Code/firmware/docs/LOGGING-SPEC.md` ·
`04-Source-Code/firmware/blackpillrtkcode_v2/blackpillrtkcode_v2.ino`

View File

@ -0,0 +1,83 @@
<svg viewBox="0 0 680 460" xmlns="http://www.w3.org/2000/svg" font-family="Helvetica, Arial, sans-serif">
<title>HBOX Watchdog Carrier PCB v3 — power and signal map</title>
<desc>24 V cabinet feed passes reverse-polarity protection, then splits: to a new dedicated fan buck feeding two 5 V fan drivers and the extraction fans; and to the external Mean Well 5 V supply that powers the STM32F411 Blackpill, the RTK-stack load switch (to the external simpleRTK2B + 4G stack), and sense circuits. New I2C SHT40 + TMP102 temperature sensors and the fan drivers connect to the MCU.</desc>
<rect x="0" y="0" width="680" height="460" fill="#ffffff"/>
<defs>
<marker id="ap" markerWidth="9" markerHeight="9" refX="6.5" refY="3" orient="auto"><path d="M0,0 L6.5,3 L0,6 z" fill="#555555"/></marker>
<marker id="as" markerWidth="9" markerHeight="9" refX="6.5" refY="3" orient="auto"><path d="M0,0 L6.5,3 L0,6 z" fill="#999999"/></marker>
</defs>
<text x="16" y="20" font-size="14" font-weight="700" fill="#1a2233">HBOX Watchdog Carrier PCB v3 — power &amp; signal map</text>
<line x1="16" y1="36" x2="38" y2="36" stroke="#E07A2F" stroke-width="2"/>
<text x="43" y="39" font-size="10.5" fill="#555555">new in v3 (fans + temp)</text>
<rect x="196" y="31" width="15" height="9" rx="2" fill="none" stroke="#888888" stroke-width="1.3"/>
<text x="216" y="39" font-size="10.5" fill="#555555">existing</text>
<rect x="276" y="31" width="15" height="9" rx="2" fill="none" stroke="#aaaaaa" stroke-width="1.3" stroke-dasharray="4 3"/>
<text x="296" y="39" font-size="10.5" fill="#555555">external module</text>
<text x="487" y="39" font-size="9.5" fill="#999999">— power - - signal</text>
<rect x="16" y="54" width="118" height="38" rx="7" fill="#ffffff" stroke="#aaaaaa" stroke-width="1.3" stroke-dasharray="4 3"/>
<text x="27" y="71" font-size="12.5" font-weight="600" fill="#1a2233">24 V feed</text>
<text x="27" y="85" font-size="10.5" fill="#555555">cabinet · GX16-5</text>
<rect x="150" y="54" width="120" height="38" rx="7" fill="#ffffff" stroke="#888888" stroke-width="1.3"/>
<text x="161" y="71" font-size="12.5" font-weight="600" fill="#1a2233">Rev-pol + TVS</text>
<text x="161" y="85" font-size="10.5" fill="#555555">Q10 · D10</text>
<rect x="470" y="54" width="150" height="38" rx="7" fill="#ffffff" stroke="#aaaaaa" stroke-width="1.3" stroke-dasharray="4 3"/>
<text x="481" y="71" font-size="12.5" font-weight="600" fill="#1a2233">Mean Well 5 V</text>
<text x="481" y="85" font-size="10.5" fill="#555555">SD-15B-05 · ext</text>
<line x1="134" y1="73" x2="148" y2="73" stroke="#555555" stroke-width="1.6" marker-end="url(#ap)"/>
<rect x="16" y="150" width="208" height="40" rx="7" fill="#FCF1E8" stroke="#E07A2F" stroke-width="1.8"/>
<text x="27" y="167" font-size="12.5" font-weight="600" fill="#1a2233">Fan buck U2</text>
<text x="27" y="181" font-size="10.5" fill="#555555">24→5 V dedicated rail · JP1</text>
<rect x="16" y="214" width="100" height="44" rx="7" fill="#FCF1E8" stroke="#E07A2F" stroke-width="1.8"/>
<text x="26" y="232" font-size="12" font-weight="600" fill="#1a2233">Fan drv 1</text>
<text x="26" y="246" font-size="10.5" fill="#555555">Q2 · flyback</text>
<rect x="124" y="214" width="100" height="44" rx="7" fill="#FCF1E8" stroke="#E07A2F" stroke-width="1.8"/>
<text x="134" y="232" font-size="12" font-weight="600" fill="#1a2233">Fan drv 2</text>
<text x="134" y="246" font-size="10.5" fill="#555555">Q3 · flyback</text>
<rect x="40" y="300" width="160" height="40" rx="7" fill="#ffffff" stroke="#aaaaaa" stroke-width="1.3" stroke-dasharray="4 3"/>
<text x="51" y="317" font-size="12.5" font-weight="600" fill="#1a2233">2× 5 V fans</text>
<text x="51" y="331" font-size="10.5" fill="#555555">extraction · ext</text>
<path d="M205,92 L205,120 L120,120 L120,148" fill="none" stroke="#555555" stroke-width="1.6" marker-end="url(#ap)"/>
<text x="124" y="116" font-size="9.5" fill="#555555">24 V</text>
<line x1="66" y1="190" x2="66" y2="212" stroke="#555555" stroke-width="1.6" marker-end="url(#ap)"/>
<line x1="174" y1="190" x2="174" y2="212" stroke="#555555" stroke-width="1.6" marker-end="url(#ap)"/>
<text x="70" y="206" font-size="9.5" fill="#555555">5V_FAN</text>
<path d="M66,258 L66,300" fill="none" stroke="#555555" stroke-width="1.6" marker-end="url(#ap)"/>
<path d="M174,258 L174,300" fill="none" stroke="#555555" stroke-width="1.6" marker-end="url(#ap)"/>
<rect x="250" y="150" width="172" height="72" rx="7" fill="#ffffff" stroke="#1a2233" stroke-width="1.5"/>
<text x="262" y="180" font-size="13" font-weight="700" fill="#1a2233">STM32F411 Blackpill</text>
<text x="262" y="198" font-size="10.5" fill="#555555">watchdog · on headers</text>
<text x="262" y="212" font-size="10.5" fill="#555555">reuses v2.1 firmware</text>
<rect x="250" y="300" width="172" height="40" rx="7" fill="#ffffff" stroke="#aaaaaa" stroke-width="1.3" stroke-dasharray="4 3"/>
<text x="261" y="317" font-size="12.5" font-weight="600" fill="#1a2233">18650 / PowerBoost</text>
<text x="261" y="331" font-size="10.5" fill="#555555">battery · ext</text>
<rect x="250" y="392" width="172" height="42" rx="7" fill="#FCF1E8" stroke="#E07A2F" stroke-width="1.8"/>
<text x="261" y="410" font-size="12.5" font-weight="600" fill="#1a2233">SHT40 + TMP102</text>
<text x="261" y="424" font-size="10.5" fill="#555555">I²C temp + humidity</text>
<rect x="470" y="150" width="150" height="40" rx="7" fill="#ffffff" stroke="#888888" stroke-width="1.3"/>
<text x="481" y="167" font-size="12.5" font-weight="600" fill="#1a2233">Load switch</text>
<text x="481" y="181" font-size="10.5" fill="#555555">Q1 · Q5 · PPTC</text>
<rect x="470" y="212" width="150" height="42" rx="7" fill="#ffffff" stroke="#aaaaaa" stroke-width="1.3" stroke-dasharray="4 3"/>
<text x="481" y="230" font-size="12.5" font-weight="600" fill="#1a2233">simpleRTK2B + 4G</text>
<text x="481" y="244" font-size="10.5" fill="#555555">SW_5V · ext</text>
<rect x="470" y="300" width="150" height="38" rx="7" fill="#ffffff" stroke="#888888" stroke-width="1.3"/>
<text x="481" y="317" font-size="12.5" font-weight="600" fill="#1a2233">Status LED</text>
<text x="481" y="331" font-size="10.5" fill="#555555">PB0</text>
<path d="M545,92 L545,150" fill="none" stroke="#555555" stroke-width="1.6" marker-end="url(#ap)"/>
<text x="550" y="118" font-size="9.5" fill="#555555">MAIN 5V</text>
<path d="M486,92 L486,120 L424,120 L424,150" fill="none" stroke="#555555" stroke-width="1.6" marker-end="url(#ap)"/>
<text x="392" y="116" font-size="9.5" fill="#555555">5 V</text>
<line x1="545" y1="190" x2="545" y2="210" stroke="#555555" stroke-width="1.6" marker-end="url(#ap)"/>
<text x="550" y="205" font-size="9.5" fill="#555555">SW_5V</text>
<line x1="422" y1="166" x2="468" y2="166" stroke="#999999" stroke-width="1.3" stroke-dasharray="4 3" marker-end="url(#as)"/>
<text x="430" y="161" font-size="9.5" fill="#999999">PA1 EN</text>
<path d="M422,205 L450,205 L450,232 L468,232" fill="none" stroke="#999999" stroke-width="1.3" stroke-dasharray="4 3" marker-end="url(#as)"/>
<text x="426" y="201" font-size="9.5" fill="#999999">UART PA2/3</text>
<path d="M414,222 L440,260 L440,316 L468,316" fill="none" stroke="#999999" stroke-width="1.3" stroke-dasharray="4 3" marker-end="url(#as)"/>
<text x="444" y="288" font-size="9.5" fill="#999999">PB0</text>
<path d="M250,196 L235,210 L228,228" fill="none" stroke="#999999" stroke-width="1.3" stroke-dasharray="4 3" marker-end="url(#as)"/>
<text x="150" y="200" font-size="9.5" fill="#999999">PWM + tach PA6/7 · PB10/12</text>
<line x1="300" y1="300" x2="300" y2="224" stroke="#999999" stroke-width="1.3" stroke-dasharray="4 3" marker-end="url(#as)"/>
<text x="304" y="262" font-size="9.5" fill="#999999">BAT sense PA0</text>
<path d="M414,222 L436,250 L436,410 L424,410" fill="none" stroke="#999999" stroke-width="1.3" stroke-dasharray="4 3" marker-end="url(#as)"/>
<text x="440" y="380" font-size="9.5" fill="#999999">I²C PB6/7</text>
</svg>

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

View File

@ -0,0 +1,378 @@
---
tags: [project-drogheda, hardware, watchdog, pcb, thermal, fans, schematic, v3]
project: Project Drogheda
author: Helgen CTO
date: 2026-06-21
status: Active — design for review; bench-validate before fab
supersedes_section: hardware-respec-and-pcb-brief.md §4 (watchdog carrier detail)
---
# HBOX Watchdog Carrier PCB — v3.0 Design (Watchdog + Smart-Fan Thermal)
> The production PCB for the STM32F411 watchdog, **expanded** to drive two
> extraction fans and monitor internal temperature/humidity. This is the
> schematic-capture spec: every sub-circuit, every value, every part, the pin
> map, the firmware deltas, and the layout/fab path. Pairs with:
> [[03-Architecture-Hardware/watchdog-pcb-v3-bom|v3 BOM (xlsx)]] ·
> [[03-Architecture-Hardware/watchdog-pcb-v3-kicad-guide|KiCad + JLCPCB guide]] ·
> [[04-Source-Code/firmware/blackpillrtkcode_v2/blackpillrtkcode_v2|firmware v2.1]] ·
> [[03-Architecture-Hardware/watchdog-schematic|as-built watchdog]].
**Decisions driving this revision (2026-06-21, Leo):**
| # | Decision | Consequence |
|---|---|---|
| 1 | **Scope = watchdog + thermal *carrier*** | Blackpill plugs on via 2×20 headers (reuse v2.1 firmware, no MCU bring-up). Mean Well SD-15B-05 and the battery stay **external modules**. Lowest-risk first board. |
| 2 | **5 V fans** | Driven low-side (MOSFET PWM) + tach. ⚠ They load the same 5 V the modem bursts on — mitigated with a **dedicated fan rail** (see §0a). |
| 3 | **I²C temp + humidity** | SHT40 (board ambient + RH) + TMP102 probe (battery/modem hot spot) on one I²C bus. |
| 4 | **JLCPCB with assembly** | Parts chosen from JLC/LCSC stock; we output Gerbers + BOM + CPL. Footprints biased to JLC-placeable + hand-reworkable. |
---
## 0. Read this first — two engineering caveats
### 0a. 5 V fans must NOT share the RTK stack rail
The Mean Well **SD-15B-05** gives **5 V @ 3 A**. The RTK+4G stack already peaks
**~2–2.5 A on LTE-TX bursts** — the brownout the whole redesign is fixing. Two
5 V fans add **~0.3–0.5 A plus inrush**. Coincident worst case ≈ 2.8–3.0 A sits
right on the supply limit, and **fan inrush + a modem burst at the same instant
is exactly the sag that drops the link.** So the board gives the fans their **own
5 V** from a small **24→5 V buck** tapped off the cabinet 24 V (which is already
inside the HBOX). A **0 Ω jumper (JP1)** lets you instead bridge fans to the main
5 V if you later upsize the supply — one board, both strategies. **Do not power
fans from the *switched* (load-switch) stack 5 V** — fans must run when hot even
while the stack is power-cycling.
### 0b. Fans + IP67 is a real tradeoff — own it
**Moving air in/out of the box breaks the IP67 seal.** A fan only helps if it
*exchanges* HBOX/cabinet air with outside air; that path is an opening. The honest
position for the v3 mechanical:
- Ventilation drops the rating from **IP67 → ~IP54/IP55** at best, *with* filtered,
hooded, downward-facing louvers. In a **salty, humid, dusty port** that is a
maintenance item (filters clog/corrode).
- Keep every electronic mitigation we already have: **conformal-coat the PCB**
(acrylic/urethane), keep a **Gore pressure-vent** for condensation, and treat the
fans as **assist**, not the whole thermal solution. **Conduction to the metal
plate (hardware brief §2b) still does the heavy lifting**; fans buy the last few °C.
- The firmware runs the fans **only when temperature demands it** (closed loop, §7),
minimising the hours the vents are actively pulling dirty air.
> If ICAVE can accept IP54 with filter maintenance, fans are fine. If they require
> IP67, the answer is conduction + sunshade, not fans. **This board supports both** —
> populate or depopulate the fan section. Confirm the rating target with ICAVE.
---
## 1. What the close look at the watchdog found
The as-built watchdog (`watchdog-schematic.md`) + v2.1 firmware is **sound logic on
fragile hardware**. Keep the brains, fix the body.
**Keep (proven):**
- The **high-side power gate** concept (PA1 → stack 5 V), **fail-safe OFF** at boot
via the gate pull-up. This is the heart of 4G/NTRIP recovery — don't change its
behaviour, only its build quality.
- Three **sense dividers** (battery PA0, 5 V PA4, ignition PA5) and the v2.1
recovery state machine (backoff + LIMP + NMEA-silence + IWDG + MCU-thermal).
- USART2 (PA2/PA3) to the F9P UART1 for NMEA read + UBX rate.
**Fix (the liabilities):**
| Liability | Root issue | v3 board fix |
|---|---|---|
| 2N3904 + IRF9540N + pull-up, hand-wired | vibration intermittents; IRF9540N is **not** logic-level (marginal gate drive from a 5 V swing) | SMD discrete high-side switch (**AO3401A** logic-level P-FET + **MMBT3904**) + **PPTC** overcurrent; optional eFuse upgrade |
| LED + dividers on Dupont | mechanical | all on-board, 0603, conformal-coatable |
| `1.5 kΩ "????"` mystery R | undocumented | resolved on this schematic (it was the **LED series R**; dividers are 10k/10k — see §6) |
| MCU temp only | heat casualties are the **18650/modem**, not the die | **external** I²C sensors at the hot spot (§5) |
| No thermal *actuation* | firmware can derate but can't cool | **2× fan drivers + closed-loop control** (§4, §7) |
| No SWD/header bring-out | can't debug a sealed unit | **SWD + USB + reserve-USART1** headers (§6) |
---
## 2. Expanded STM32F411 pin map (v2.1 pins kept; 6 added)
```
STM32F411 "Blackpill" (mounted on 2×20 headers)
── existing (firmware v2.1, unchanged) ───────────────────────────────────
PA0 BAT_SENSE ADC1_IN0 18650 ÷2 divider
PA1 PWR_CTRL GPIO out → RTK-stack load switch enable (HIGH = ON)
PA2 GNSS_TX USART2_TX → simpleRTK2B UART1 RX (UBX rate cmds)
PA3 GNSS_RX USART2_RX ← simpleRTK2B UART1 TX (NMEA read)
PA4 5V_SENSE ADC1_IN4 stack 5 V ÷2 divider (vehicle-power present)
PA5 IGN_SENSE GPIO in ignition via DFR1015 (INPUT_PULLDOWN)
PB0 EXT_LED GPIO out external status LED (low-side MOSFET)
PC13 LED GPIO out onboard LED (active-LOW)
── NEW for v3 (fans + temperature) ───────────────────────────────────────
PA6 FAN1_PWM TIM3_CH1 25 kHz PWM → fan-1 low-side gate
PA7 FAN2_PWM TIM3_CH2 25 kHz PWM → fan-2 low-side gate
PB10 FAN1_TACH EXTI fan-1 tach (open-collector, 10k pull-up to 3V3)
PB12 FAN2_TACH EXTI fan-2 tach (open-collector, 10k pull-up to 3V3)
PB6 I2C1_SCL I2C1 SHT40 + TMP102 (4.7k pull-ups to 3V3)
PB7 I2C1_SDA I2C1 SHT40 + TMP102
── reserved (do not populate now; future in-band $PHLG telemetry relay) ───
PA9 USART1_TX header → future: inject $PHLG into modem uplink
PA10 USART1_RX header ← future: F9P UART2 NMEA tap
PA13 SWDIO / PA14 SWCLK SWD debug header
PA11 USB_DM / PA12 USB_DP Blackpill micro-USB = STREAM log capture
```
No conflicts: TIM3_CH1/CH2 (PA6/PA7) do fan PWM; PB0 (TIM3_CH3) stays plain-GPIO
LED. The reserved USART1 pair future-proofs the board for the
[[03-Architecture-Hardware/watchdog-health-telemetry|`$PHLG` health-in-server]] upgrade
without a respin.
---
## 3. Block diagram
```text
Cabinet 24 V ──GX16-5(pin1/2)──► P1 [24V_IN]
│ ├─► reverse-pol P-FET (Q10) + TVS (D10, SMBJ33A) ─► 24V rail
│ │ ├──────────────────────────────► FAN buck in
│ (ignition 24V ─GX16 pin3─► DFR1015 ext module ─► P1[IGN] ─► PA5 divider)
│
Mean Well SD-15B-05 (EXTERNAL, 24→5 V 3 A) ──► P2 [5V_IN] ─► MAIN 5V rail
MAIN 5V ─► Blackpill 5V pin (onboard LDO → 3V3)
MAIN 5V ─► high-side load switch (Q1 AO3401A / U1 eFuse opt) ─EN◄PA1─► SW_5V ─► P3 simpleRTK2B + 4G
MAIN 5V ─► PA4 sense divider
18650 / PowerBoost (EXTERNAL) ──► P4 [BAT+ , BAT_SENSE] ─► PA0 divider
FAN rail: 24V ─► U2 buck (MP1584EN) ─► 5V_FAN (JP1: alt-bridge to MAIN 5V)
5V_FAN ─┬─► Fan1 (+) ───┐ ┌─── Fan1 tach ─10k─► PB10
│ Fan1 (−) ─ Q2 (AO3400) ◄PWM PA6 │ (D2 SS34 flyback ∥ fan)
└─► Fan2 (+) ───┘ └─── Fan2 tach ─10k─► PB12
Fan2 (−) ─ Q3 (AO3400) ◄PWM PA7
I²C1 (PB6/7) ─┬─ SHT40 (U3, 0x44) board ambient temp + RH
└─ TMP102 (U4, 0x48) on P5 pigtail → battery/modem hot spot
PB0 ─► Q4 (AO3400) ─► status LED (D1) SWD/USB/USART1 headers (H1/H2/H3)
```
---
## 4. Sub-circuit detail (capture this exactly)
### 4.1 24 V input protection (light — the cabinet does the heavy lifting)
The cabinet already has the Phoenix SPD + breaker, so the board needs only:
- **Q10** reverse-polarity P-MOSFET in the 24 V high side (e.g., **AO3401A** if
fan+buck draw <few-hundred mA; for margin use a bigger P-FET like **SI2305** —
buck input current at 24 V is small, ~0.2 A, so AO3401A is fine).
- **D10** TVS **SMBJ33A** (33 V standoff) 24 V→GND, plus **C10** 100 µF/50 V
electrolytic + **C11** 100 nF bulk/decoupling.
### 4.2 Dedicated fan 5 V buck (U2) — see caveat §0a
- **U2 = MP1584EN** (SOP-8, 4.5–28 V in, up to 3 A) set to **5.0 V** out.
- FB divider for 5 V: **R20 = 100 kΩ top**, **R21 = 19.1 kΩ bottom** (Vout =
0.8 V·(1+R20/R21) ≈ 5.0 V — verify against the MP1584 datasheet FB = 0.8 V).
- **L2 = 10 µH** (≥1 A sat), **C22 = 22 µF** in, **C23 = 22 µF** out, freewheel
Schottky per datasheet (SS34), EN tied to 24V-rail-derived enable.
- **JP1**: 0 Ω / solder-jumper. **Open** = fans on U2 (recommended). **Closed** =
fans bridged to MAIN 5 V (only if SD-15B-05 is upsized; never both).
- Keep U2's switching node small; **keep this buck away from the GNSS antenna feed**
(RF cleanliness — see §8).
> Simpler alternative if you'd rather not lay out a buck on the first spin: drop U2,
> close **JP1**, and **upsize the external 5 V supply to a 5 V/5 A** unit so the rail
> has true headroom. The board supports it; it's a BOM/jumper choice, not a respin.
### 4.3 RTK-stack high-side load switch (Q1 trio — proven, SMD)
Keeps the as-built topology (so **firmware PA1 logic is unchanged**) but in
solid, sourceable SMD, plus a resettable fuse for the missing current limit:
```
MAIN 5V ──[F1 PPTC 3A]──┬───────────────[R2 10k]──┐
│ │
Q1 AO3401A Source Gate ── Q5 MMBT3904 Collector
(P-MOS) │ │
Drain ──► SW_5V ─► P3 stack Q5 Emitter ── GND
│
PA1 ──[R1 1k]── Q5 Base ───────────────┘
```
- PA1 **HIGH** → MMBT3904 on → P-FET gate low → **SW_5V ON**. PA1 LOW → R2 holds
gate high → **OFF** (fail-safe at boot). Identical to as-built.
- **AO3401A** is logic-level and switches fully from the 5 V gate swing (the old
**IRF9540N was not** — a real latent bug we're removing).
- **F1 PPTC** (3 A hold / ~6 A trip, e.g., **MF-MSMF300**) gives overcurrent
protection without a hard-to-source eFuse, and **cannot permanently latch a healthy
unit out** (it self-resets) — satisfies the "never black-hole" invariant.
- **C3 = 47 µF** bulk on SW_5V (modem-burst reservoir).
- **Optional upgrade footprint U1**: pin-compatible-ish current-limit load switch /
eFuse (e.g., **AP2553W6-7** SOT-23-6, EN active-high, FLAGn). Lay the footprint
*and* the discrete; populate one. Verify LCSC stock at order; **default = discrete**.
### 4.4 Fan drivers ×2 (low-side PWM + tach) — Q2/Q3
Per fan (identical for fan-1 and fan-2):
```
5V_FAN ──► Fan(+) Fan tach ──[R_t 1k]──┬──► PBxx (EXTI)
Fan(−) ──┬──► Q2/Q3 Drain │
D2/D3 SS34 ∥ fan │ (AO3400A, N-MOS) [R 10k pull-up → 3V3]
(cathode→5V_FAN) │ Source ── GND
PWM PAx ─[Rg 100Ω]─ Gate ──[Rgs 100k → GND]
```
- **Q2/Q3 = AO3400A** logic-level N-MOSFET (SOT-23): full-on from 3.3 V PWM, 5.7 A /
30 V — vast margin over a <0.5 A fan.
- **D2/D3 = SS34** Schottky **flyback** across each fan (motor is inductive — protects
the FET at PWM turn-off). Cathode to 5V_FAN, anode to drain.
- **Rg 100 Ω** gate series (tames edges/EMI), **Rgs 100 kΩ** gate→GND pull-down =
**fans OFF until the MCU drives PWM** (fail-safe; consistent with the ethos — if the
MCU is dead the stack is also unpowered, so there's nothing to cool).
- **Tach**: 3-wire fan's open-collector tach → **10 kΩ pull-up to 3.3 V** + **1 kΩ**
series into the EXTI pin. 2 pulses/rev → MCU computes RPM and flags a stalled/failed
fan. **Use 3-wire (or 4-wire) fans so tach exists.** PWM is on the **ground leg**, so
on a 3-wire fan the tach is valid only while PWM is high — firmware samples RPM during
a brief 100 % window (§7). *(4-wire fans avoid this entirely; if you can source 5 V
4-wire, drive their PWM pin from PAx via a 2N7002 open-drain and leave power
always-on — cleaner tach. 5 V 4-wire fans are rarer, hence 3-wire default.)*
### 4.5 Status LED (unchanged topology, SMD)
`3V3 ─[R3 1k]─►|D1 LED|─► Q4 (AO3400) drain; source→GND; PB0 ─ gate, [10k → GND]
pull-down.` (Resolves the "1.5 kΩ ????" — it was this series resistor; 1 kΩ is the
clean value for a 3.3 V drive.)
### 4.6 Sense conditioning (filtered; protect the ignition input)
| Net | Divider | RC filter | Protection |
|---|---|---|---|
| PA0 BAT (3.0–4.2 V) | R5 10k / R6 10k (÷2) | C5 100 nF | — |
| PA4 5V (≈5 V) | R7 10k / R8 10k (÷2) | C7 100 nF | — |
| PA5 IGN (from DFR1015) | R9 10k / R10 10k (÷2) | C9 100 nF | **D5 BAT54 clamp to 3V3 + 100 Ω series**; the DFR1015 already bucks 24 V→logic, divider+clamp is belt-and-suspenders |
ADC stays ≤3.3 V with margin (verified in firmware-review). Firmware
`DIVIDER_RATIO = 2.0` matches the 10k/10k legs.
---
## 5. Temperature + humidity sensing (I²C1)
| Ref | Part | Addr | Where | Why |
|---|---|---|---|---|
| U3 | **SHT40-AD1B-R2** (DFN-4) | 0x44 | on-board, near an air-path edge | board-ambient **temp + RH** (RH flags condensation risk — IP/coastal) |
| U4 | **TMP102AIDRLR** (SOT-563) | 0x48 (ADD0→GND) | on **P5 pigtail** to the 18650/modem | the **actual heat casualty** spot — the data the thermal RCA needs |
- One bus, **4.7 kΩ pull-ups** (R30/R31) on SCL/SDA to 3.3 V. Both 3.3 V parts.
- 100 nF decoupling each. SHT40 wants a small keep-out/slot so it reads air, not board.
- Firmware reads all three temps (SHT40, TMP102, **plus** the existing MCU-internal)
→ triangulated thermal picture and the fan-control input (§7).
---
## 6. Connectors & headers (all keyed/locking)
| Ref | Connector | Pins | Net |
|---|---|---|---|
| P1 | GX16-5 (HBOX↔cabinet, **keep**) | 5 | 24V_IN, GND, IGN, (spare ×2) |
| P2 | Molex Micro-Fit 3.0, 2-pos | 2 | 5V_IN from SD-15B-05, GND |
| P3 | JST-XH 4-pos | 4 | SW_5V, GND, GNSS_TX(PA2), GNSS_RX(PA3) → simpleRTK2B |
| P4 | JST-XH 2-pos | 2 | BAT+ (to PA0 divider), GND (18650/PowerBoost) |
| P5 | JST-XH 4-pos | 4 | 3V3, GND, SDA, SCL → TMP102 probe pigtail |
| P6 | JST-XH 4-pos | 4 | 5V_FAN, Fan1−(PWM), GND, Tach1 → fan 1 |
| P7 | JST-XH 4-pos | 4 | 5V_FAN, Fan2−(PWM), GND, Tach2 → fan 2 |
| P8 | JST-XH 2-pos | 2 | EXT_LED+, EXT_LED− (panel LED) |
| H1 | 1×4 0.1″ | 4 | SWD: 3V3, SWDIO, SWCLK, GND |
| H2 | (Blackpill micro-USB) | — | STREAM log capture / flashing |
| H3 | 1×3 0.1″ **DNP** | 3 | reserve USART1: PA9, PA10, GND (future $PHLG) |
| — | 2× 2×20 female headers | 40 | Blackpill seats here |
> **Pin-1 / keying:** silkscreen pin-1 arrows + the GX16 keyway; JST-XH is already
> keyed. Label every connector net on silk (DFM/serviceability requirement).
---
## 7. Firmware deltas (spec only — bench-test before any field flash)
The v2.1 `.ino` changes are **small and additive** (the ethos: bench first; the
sandbox can't build STM32). New globals + functions, no state-machine surgery.
1. **Pins**: add `PIN_FAN1_PWM PA6`, `PIN_FAN2_PWM PA7`, `PIN_FAN1_TACH PB10`,
`PIN_FAN2_TACH PB12`; init TIM3 CH1/CH2 PWM at **25 kHz**; attach EXTI counters on
the tach pins.
2. **I²C temps**: `Wire.begin()` on PB6/PB7; read SHT40 (0x44, temp+RH) and TMP102
(0x48). Add `tAirC`, `rhPct`, `tBatC` to telemetry. Keep MCU-internal as cross-check.
3. **Closed-loop fan curve** (input = `max(tAirC, tBatC)`):
- `< 45 °C` → fans **off** (0 % — minimise vent hours / dust ingress).
- `45–70 °C` → **linear 30 → 100 %** PWM.
- `≥ 70 °C` → **100 %**.
- **Hysteresis 3 °C** on the off→on edge (no chatter). This *replaces nothing* —
the existing `THERM_WARN_C 75` derate-to-30 s still stands as the last resort.
4. **Tach fault**: once/30 s, command a fan to **100 % for ~300 ms**, count tach edges,
compute RPM. `0 RPM while commanded > 0` → latch `FAN_FAULT`, log `EVT`, raise it in
telemetry. A dead fan in a hot box must be **visible**, not silent.
5. **Telemetry**: extend the v2.1 `TLM`/`$PHLG` record with
`tair,rh,tbat,fan1_rpm,fan2_rpm,fan_pwm,fan_fault`. These are exactly the fields the
`$PHLG`-to-server upgrade will carry — capture them on-unit now via the STREAM log.
6. **IWDG-safe**: fan/tach/I²C work is non-blocking; keep petting the dog. I²C read uses
a short timeout so a stuck sensor can't hang the loop.
Pin map and control law live here; the implementation lands in
`blackpillrtkcode_v2.ino` **after** bench validation on the prototype board.
---
## 8. Layout, stackup & DFM (for a relative beginner — the rules that matter)
- **Stackup**: **2-layer is acceptable** for this mostly-DC/low-speed board and is
cheaper; **4-layer** (GND + power inner planes) is better for the GNSS RF and the
buck noise — recommended if budget allows. Either way: **solid ground pour** on a
full layer.
- **Star-ground the power**: fan return currents (PWM, pulsed) and the modem-burst
return must **not** share a thin trace with the ADC/sensor grounds. Bring power
grounds to the pour at one stitching region; keep sensor/MCU ground quiet.
- **Buck (U2) discipline**: tiny switch-node copper, input cap **right at** the IC,
ground the cap to the pour with vias. **Keep U2 and the fan-PWM traces away from the
GNSS antenna pigtail / P3** — switcher harmonics degrade the ZED-F9P front-end (= the
watchdog's own "link health" signal).
- **Trace widths** (1 oz Cu): 24 V_IN and 5 V rails ≥ **0.5 mm** (fan + stack current);
SW_5V to P3 ≥ **0.8 mm** (modem bursts); signals 0.25 mm fine.
- **Thermal**: copper pour + vias under Q1/Q10/U2 tabs. Mounting holes positioned to
let the board **bolt to the cabinet metal plate** (conduction path, hardware brief §2b).
- **Creepage**: nothing >24 V here, so standard clearances are ample; still keep the
24 V input section tidy.
- **Conformal coat** after assembly (mask connectors/headers/SHT40 slot) — humidity +
vibration joint stability.
- **JLC specifics**: 0603 passives (hand-reworkable *and* machine-placeable),
SOT-23 actives, 1.6 mm FR-4, HASL or ENIG. Add **fiducials** if you want tight
placement. Put the JLC order number on silk (free, helps reorders).
---
## 9. Bring-up & test plan (prototype first)
1. **Bare-board power**: 24 V in, confirm **5V_FAN = 5.0 V**, reverse-polarity FET
blocks a flipped input, TVS not hot.
2. **Load switch**: toggle PA1 → SW_5V on/off; confirm OFF at reset; PPTC holds the
measured stack current, trips on a deliberate short.
3. **Fans**: ramp PA6/PA7 PWM 0→100 %, scope the gate + fan node, confirm flyback,
read **tach RPM** vs PWM; pull a fan → confirm `FAN_FAULT`.
4. **Sensors**: I²C scan finds 0x44 + 0x48; heat the TMP102 probe → fan ramps per curve.
5. **Power budget (the §0a check)**: with **JP1 open**, scope MAIN 5 V during an LTE
burst **and** a fan ramp simultaneously — confirm the stack rail no longer sags.
Repeat with JP1 closed to *demonstrate* the coupling you're avoiding.
6. **Full stack**: mount Blackpill + F9P + 4G, run v2.1+fan firmware, watch the STREAM
log for temps/RPM, confirm RTK recovery behaviour is unchanged.
7. Only then: pilot 2–3 boards in real cabinets before the fleet (mirrors the
August-30-unit ramp).
---
## 10. Open questions / decisions
- [ ] **IP rating target** with ICAVE: IP67 (no vents → conduction only) vs IP54/55
(filtered louvers + fans). Determines whether the fan section is populated.
- [ ] **Fan choice**: 5 V **3-wire** (tach) confirmed? Size (40/50/60 mm) + measured
static-pressure need through the chosen duct/filter. Source 2–3 candidates.
- [ ] **U2 buck vs upsize supply** (§4.2): lay the buck (default) or go 5 V/5 A external
+ JP1-closed. Recommend laying the buck for rail isolation.
- [ ] **Battery**: still drop / LiFePO4 / supercap (hardware brief §6.1). Board only
exposes P4 + sense; charger stays external until decided.
- [ ] Confirm the **DFR1015** stays an external module (P1 IGN) or folds on-board later.
- [ ] **Load switch**: discrete (default, sourceable) vs eFuse U1 — verify AP2553 LCSC
stock at order; otherwise ship discrete.
## Related
- [[CLAUDE|Master Index]] · [[03-Architecture-Hardware/watchdog-schematic|as-built watchdog]] ·
[[03-Architecture-Hardware/hardware-respec-and-pcb-brief|hardware re-spec + PCB brief]] ·
[[03-Architecture-Hardware/watchdog-health-telemetry|health telemetry ($PHLG)]] ·
[[03-Architecture-Hardware/BOM-master|BOM master]] ·
[[03-Architecture-Hardware/watchdog-pcb-v3-bom|v3 PCB BOM (xlsx)]] ·
[[03-Architecture-Hardware/watchdog-pcb-v3-kicad-guide|KiCad + JLCPCB guide]]

View File

@ -0,0 +1,234 @@
---
tags: [project-drogheda, hardware, pcb, kicad, jlcpcb, guide, how-to]
project: Project Drogheda
author: Helgen CTO
date: 2026-06-21
status: Active — working guide
---
# KiCad → JLCPCB: build & deploy the Watchdog Carrier PCB v3
> A from-zero, do-this-then-that guide for capturing the
> [[03-Architecture-Hardware/watchdog-pcb-v3-design|v3 design]] in **KiCad** and getting it
> **assembled by JLCPCB**. Written for someone who has done PCB work *a long time ago*.
> The schematic content (every net, part, value) lives in the design doc and the
> [[03-Architecture-Hardware/watchdog-pcb-v3-bom|BOM]] — this is the *tooling* path.
---
## 0. Pick your path (read first)
You have two realistic routes to a JLC-assembled board. Both end at the same place.
| Path | Best when | Trade-off |
|---|---|---|
| **A — KiCad** (this guide) | you want a proper, version-controllable design you own outright | steeper, but the right long-term tool; offline; git-friendly |
| **B — EasyEDA (Std/Pro)** | you want the *fastest* first board with least friction | JLC-native: every part already carries its **LCSC number + footprint**, and "Order at JLCPCB" is one button. Less portable. |
**My recommendation for *this* board, given you're newer to PCB and going JLC-assembly:**
do **Path A (KiCad)** because you'll iterate this board for the fleet and want to own it —
but if you feel stuck on footprints/part-matching, **Path B (EasyEDA) is a legitimate
shortcut** and uses the exact same LCSC parts in our BOM. Don't lose a week to tooling.
The rest of this doc is Path A.
---
## 1. Install KiCad + two plugins
1. **KiCad 9 or 10** (current 2026) from kicad.org → install the full suite.
2. Open KiCad → **Plugin and Content Manager** (button on the home screen) and install:
- **Fabrication Toolkit** (by *bennymeg*) — one-click export of Gerbers + **BOM + CPL**
already in JLCPCB's format. This is the deploy button.
- **JLCPCB Tools** (*Bouni / kicad-jlcpcb-tools*) — lets you assign the **LCSC part
number** to each component from inside KiCad and query JLC stock. This is how the BOM
parts get matched.
> Both are free, in the official plugin manager. With these two, you never hand-edit a
> JLC CSV.
---
## 2. The mental model (the whole workflow in one picture)
```
Symbols + wires Footprints Place + route One-click export
(Schematic editor) ──► (per component) ──► (PCB editor) ──► (Fabrication Toolkit)
│ │ │ │
ERC ✓ "what each part DRC ✓ Gerbers.zip + bom.csv
(no missing nets) physically is" (no shorts/ + positions.csv ──► JLCPCB
clearance) upload + assemble
```
You always go **left to right**. Don't start routing until the schematic passes ERC.
---
## 3. Create the project
1. **File → New Project** → name it `hbox-watchdog-v3`. Save it **inside the repo** at
`03-Architecture-Hardware/` (or a new `kicad/` subfolder) so it's version-controlled.
2. You get `.kicad_sch` (schematic) and `.kicad_pcb` (board). Open the **Schematic Editor**.
---
## 4. Draw the schematic
Work **one sub-circuit at a time**, matching design-doc §4. Use **net labels**, not long
wires — it keeps things readable.
### 4.1 Place the power symbols first
Add power ports (the "place power symbol" tool): **+24V, +5V (main), +5V_FAN, +3V3, SW_5V,
GND**. Every supply/return connects to these by name — no need to draw rails across the page.
### 4.2 The Blackpill (the trick that saves you)
You are **not** designing the STM32 chip — the Blackpill module plugs into **two 1×20
headers**. So represent it as **two `Conn_01x20` connector symbols** (or grab a ready
"WeAct Blackpill" symbol from a community KiCad library — search GitHub for "Blackpill
KiCad library"). Label each pin with its **net name from design-doc §2** (PA0=BAT_SENSE,
PA1=PWR_CTRL, PA6=FAN1_PWM, PB6=I2C1_SCL, …). The header footprints become the socket the
module sits in. This avoids all MCU bring-up.
### 4.3 Drop in each sub-circuit (design-doc §4)
For each, place the symbols, wire locally, and tie to power/net labels:
- **Input + fan buck (§4.1–4.2):** Q10 (AO3401A), D10 TVS, C10/C11; then U2 **MP1584EN** with
L2, C22, C23, the FB divider R20/R21, and **JP1** (solder-jumper symbol).
- **Load switch (§4.3):** Q1 AO3401A + Q5 MMBT3904 + R1/R2 + **F1 PPTC** + C3. Net the drain to
**SW_5V**. (Optional U1 eFuse footprint — leave it **DNP**.)
- **Fan drivers ×2 (§4.4):** Q2/Q3 AO3400A, D2/D3 SS34 flyback, Rg/Rgs gate parts, tach
pull-up + series into **PB10 / PB12**. PWM gates from **PA6 / PA7**.
- **Sensors (§5):** U3 SHT40 + U4 TMP102 on **PB6/PB7**, 4.7k pull-ups, 100nF each.
- **Sense + LED (§4.5–4.6):** the three 10k/10k dividers (PA0/PA4/PA5), D5 clamp, Q4 + D1 + R3.
- **Connectors (§6):** P1–P8, H1, H3 (DNP), and the two 2×20 sockets.
### 4.4 Annotate + ERC
- **Tools → Annotate** (gives refs R1, U2, …).
- **Inspect → Electrical Rules Check.** Fix every error (usually an unconnected pin or a
missing power flag). **Schematic is not done until ERC is clean.**
---
## 5. Assign footprints
Open **Tools → Assign Footprints** (or the Footprint Assignment tool). Map each part to a
JLC-friendly footprint:
| Part type | Footprint |
|---|---|
| Resistors / caps | `Resistor_SMD:R_0603_1608Metric` / `Capacitor_SMD:C_0603_1608Metric` (C3/C22/C10 → 0805/1206/elec) |
| AO3400A / AO3401A / MMBT3904 | `Package_TO_SOT_SMD:SOT-23` |
| SS34 | `Diode_SMD:D_SMA` (DO-214AC) |
| MP1584EN | `Package_SO:SOIC-8-1EP_…` (the EP/thermal-pad variant) |
| SHT40 | `Sensor:Sensirion_DFN-4-1EP_…` (or import from LCSC — see below) |
| TMP102 | `Package_SON:SOT-563` (or import from LCSC) |
| JST-XH | `Connector_JST:JST_XH_B*` (B4B/B2B etc.) |
| Headers | `Connector_PinHeader_2.54mm:…` (1×4, 1×3, **2×20** for the sockets) |
**For the odd parts (SHT40, TMP102, MP1584, JST):** the **JLCPCB Tools** plugin can pull the
**exact LCSC footprint** so it matches the assembler perfectly. Or use the classic trick:
on **easyeda2kicad** / the JLC part page, grab the EasyEDA footprint and import. Either way,
**verify pin-1 orientation** against the datasheet.
---
## 6. Lay out the PCB
1. Open the **PCB Editor** → **Update PCB from Schematic** (pulls in all parts + the
ratsnest of connections).
2. **Board outline:** draw a rectangle on `Edge.Cuts` (~100 × 80 mm is plenty). Put the
**mounting holes** so the board can bolt to the cabinet **metal plate** (design-doc §8 —
that's your conduction path).
3. **Place by functional block** (keep each §4 sub-circuit together): power-in + buck in one
corner, load switch near P3, fan drivers near P6/P7, sensors near an air edge / P5, the
Blackpill sockets central, connectors on the edges.
4. **Stackup:** 2-layer is fine (cheaper); **4-layer** is better for RF/buck noise. Add a
**ground pour** (filled zone = GND) on the bottom (and an inner layer if 4-layer).
5. **Route** (design-doc §8 rules): power/5V ≥0.5 mm, **SW_5V ≥0.8 mm** (modem bursts),
signals 0.25 mm. **Keep the MP1584 switch node + fan-PWM traces away from the GNSS
antenna pigtail.** Star-ground the pulsed (fan/modem) returns separately from the ADC/
sensor ground.
6. **DRC** (**Inspect → Design Rules Checker**). Zero unrouted, zero clearance errors.
7. Add **silkscreen labels** on every connector (net names) + pin-1 arrows, and a board
title/rev. (Serviceability requirement.)
---
## 7. Tell JLC which physical part goes where (LCSC numbers)
Open the **JLCPCB Tools** plugin. For each component, set its **LCSC field** to the code in
the BOM (e.g., AO3400A → `C347222`, SHT40 → `C2909890`, TMP102 → `C99269`). For the
**yellow "verify" parts** in the BOM, search the plugin's parts DB live and pick an in-stock
equivalent. Mark **U1 (eFuse)** and **H3 (USART1 reserve)** as **DNP / "Do not place."**
> This step is what makes assembly "just work" — the assembler places exactly these LCSC
> parts. The **Blackpill, the 2×20 sockets, and the GX16 wiring you hand-solder yourself**
> (mark them DNP for JLC, or "PCBA: no").
---
## 8. Export (the deploy button)
Run **Fabrication Toolkit** (Tools/External-plugins menu in the PCB editor). One click
produces a `production/` folder with:
- **Gerbers + drill**, zipped — the board.
- **bom.csv** — Designator, Footprint, Qty, Value, **LCSC** — already JLC-formatted.
- **positions.csv (CPL)** — Designator, Mid X/Y, Rotation, Layer — already JLC-formatted.
No hand-editing. (If you ever do it manually: Plot Gerbers with JLC's layer settings, then
generate BOM + CPL — but the plugin removes that pain.)
---
## 9. Order on JLCPCB
1. **jlcpcb.com → Add gerber file →** upload the Gerber **.zip**. Confirm the preview looks
like your board.
2. Set basics: layers (2 or 4), 1.6 mm, HASL or ENIG, qty **5**.
3. Toggle **PCB Assembly = ON**. Choose **Economic** (cheaper, top-side only is fine here)
and **assembled qty 2–3**.
4. Upload **bom.csv** and **positions.csv** when prompted. JLC matches LCSC parts → fix any
"not found" by picking an in-stock alternative (this is why we kept to Basic parts).
5. **Review placements** carefully — **the #1 beginner bug is wrong part rotation** on
SOT-23 / SOIC / connectors. Check each against the datasheet pin-1; rotate in the review
tool as needed.
6. Confirm DNP parts (U1, H3) are excluded. Place the order.
Lead time ~1–2 weeks to Mexico. Hand-solder the Blackpill, the 2×20 sockets, and the GX16
pigtail when it arrives.
---
## 10. First-board gotchas (the stuff that bites beginners)
- **Rotations**: JLC's 0° reference often differs from KiCad's. Always eyeball the placement
preview; SOT-23 and electrolytics are the usual offenders.
- **Polarity**: double-check D10 TVS, C10 electrolytic, D2/D3 Schottky, the LED, and Q10/Q1
source/drain before ordering.
- **The buck (U2)** is the only "real" analog block — if you're nervous, **populate JP1
closed + upsize the 5 V supply** and leave U2 unpopulated for the first spin (design-doc
§4.2). One board, two options.
- **Don't let JLC place the Blackpill or the GX16** — those are hand-soldered.
- **Order 1–2 spare bare boards** for hand-rework experiments.
- **Conformal-coat** after you've tested it (mask the SHT40 slot + connectors).
---
## 11. Pre-order checklist
- [ ] Schematic **ERC clean**; every Blackpill pin labelled per design-doc §2.
- [ ] All footprints assigned; SHT40/TMP102/MP1584/JST verified against datasheet pin-1.
- [ ] PCB **DRC clean**; GND pour stitched; SW_5V wide; buck/fan noise away from GNSS feed.
- [ ] Mounting holes positioned to bolt to the cabinet metal plate.
- [ ] LCSC numbers set on all placed parts; **U1 + H3 = DNP**; Blackpill/GX16 = hand-solder.
- [ ] Fabrication Toolkit exported Gerbers + BOM + CPL.
- [ ] JLC placement preview rotations checked.
- [ ] Bench-test plan ready (design-doc §9) — **especially the §0a 5 V-rail sag test** before
you trust it in a cabinet.
## Related
- [[03-Architecture-Hardware/watchdog-pcb-v3-design|v3 design (schematic spec)]] ·
[[03-Architecture-Hardware/watchdog-pcb-v3-bom|v3 BOM (LCSC parts)]] ·
[[03-Architecture-Hardware/watchdog-schematic|as-built watchdog]] ·
[[CLAUDE|Master Index]]

View File

@ -0,0 +1,116 @@
---
tags: [project-drogheda, hardware, watchdog, schematic, pcb]
project: Project Drogheda
author: Helgen CTO
date: 2026-06-20
status: Active
---
# Watchdog — Wiring & Schematic Spec (STM32F411 Blackpill)
> The as-built watchdog circuit, formalized for the production PCB. Pairs with
> the firmware [[04-Source-Code/firmware/blackpillrtkcode_v2/blackpillrtkcode_v2|blackpillrtkcode_v2.ino]]
> (pin map must match) and the [[03-Architecture-Hardware/BOM-master|BOM]].
## 1. Power tree (corrected)
```text
Cabinet 24 V ─GX16-5─► HBOX
└► Mean Well SD-15B-05 (24→5 V, 3 A) ─► 5 V rail ─► PowerBoost 1000C (USB in)
PowerBoost (BAT=18650 1S4P) ─► PowerBoost 5V OUT ─► STM32 5V + IRF9540N source
24 V ignition ─► DFR1015 (buck) ─► STM32 PA5 (ignition sense, ≤3.3 V)
```
## 2. STM32F411 pin map (matches firmware v2)
| Pin | Net | Function |
|---|---|---|
| PA0 | BAT_SENSE | 18650 voltage ÷ divider → ADC |
| PA4 | 5V_SENSE | PowerBoost USB (vehicle-power present) ÷ divider → ADC |
| PA5 | IGN_SENSE | ignition via **DFR1015** (digital, INPUT_PULLDOWN) |
| PA1 | PWR_CTRL | drives the RTK power switch (NPN→P-MOS) |
| PA2 | GNSS TX2 | UBX commands → simpleRTK2B RX1 |
| PA3 | GNSS RX | NMEA from simpleRTK2B (read-back) |
| PB0 | EXT_LED | external status LED (via N-MOSFET) |
| PC13 | onboard LED | active-LOW |
## 3. RTK power switch (high-side, P-channel)
```text
5V ───┬──────────────[10kΩ pull-up]──┐
│ │
IRF9540N Source Gate ── 2N3904 Collector
│ │
Drain ──► simpleRTK2B 5V 2N3904 Emitter ── GND
│
STM32 PA1 ──[1kΩ]── 2N3904 Base ──────────┘
```
- PA1 **HIGH** → 2N3904 conducts → pulls IRF9540N gate **LOW** → P-MOS **ON** →
5 V to the simpleRTK2B + 4G stack. PA1 LOW → gate held high by 10 kΩ → OFF.
- 10 kΩ gate pull-up = defined OFF at boot/reset (fail-safe: stack off until the
MCU asserts).
> **PCB note:** replace the 2N3904 + IRF9540N + pull-up with an **integrated
> high-side load switch / eFuse** (e.g., TPS25200 / TPS2596) — current-limited,
> fewer parts, defined turn-on, no gate-pull-up gotchas. Keep the same fail-safe
> (load off until MCU enables).
## 4. Status LED (low-side N-MOSFET, logic-level)
```text
3.3V ──[220–330Ω]──►|LED|──► 2N7000/BS170 Drain
Source ── GND
STM32 PB0 ── Gate ──┬── 10kΩ ── GND (pull-down: LED off while booting)
```
- 2N7000/BS170 are logic-level (switch fully at 3.3 V). 10 kΩ pull-down keeps the
LED off during MCU boot. PC13 onboard LED mirrors inverted (active-low).
## 5. Sense dividers
| Net | Source | Divider | At ADC (max) | Note |
|---|---|---|---|---|
| PA0 BAT | 18650 (3.0–4.2 V) | ÷2 (e.g. 10k/10k) | 1.5–2.1 V | firmware `DIVIDER_RATIO=2.0` |
| PA4 5V | PowerBoost USB ~5 V | ÷2 | ~2.5 V | "vehicle power present" |
| PA5 IGN | 24 V ignition | **DFR1015 buck** → ≤3.3 V | digital | isolated/scaled by DFR1015 |
> ⚠ The BOM lists a lone **1.5 kΩ "????"** resistor — confirm whether it's a
> divider leg or the LED resistor and pin it down on the schematic. Keep ADC
> inputs ≤3.3 V with margin.
## 6. Resistor / discrete BOM (watchdog)
| Ref | Value | Purpose |
|---|---|---|
| R1 | 1 kΩ | PA1 → 2N3904 base |
| R2 | 10 kΩ | IRF9540N gate pull-up (to 5 V) |
| R3 | 220–330 Ω | LED current limit |
| R4 | 10 kΩ | PB0 LED-MOSFET gate pull-down |
| R5/R6 | 10 kΩ/10 kΩ | PA0 battery divider (÷2) |
| R7/R8 | 10 kΩ/10 kΩ | PA4 5 V divider (÷2) |
| Q1 | 2N3904 (NPN) | gate driver |
| Q2 | IRF9540N (P-MOS) | RTK 5 V high-side switch |
| Q3 | 2N7000 / BS170 (N-MOS) | LED switch |
## 7. For the production PCB
- Integrated load switch (above) instead of the discrete trio.
- All ADC dividers with RC filtering; clamp/Zener protection on PA5 ignition.
- Bring out **SWD** + a **debug UART** (the v2 ANSI dashboard) on a keyed header.
- Add an external temp sensor near the 18650 / modem (v2 logs MCU temp; an
external one near the battery is more representative).
- Keep switching-regulator noise away from the GNSS antenna feed (RF integrity).
- Single keyed **GX16-5** for the 24 V + ignition interface to the cabinet.
## Open Questions
- [ ] Pin down the 1.5 kΩ resistor's role; finalize all divider values.
- [ ] Confirm DFR1015 output level into PA5 (≤3.3 V) and whether it's analog or digital sense.
- [ ] Load-switch part choice + current limit (from the measured power budget).
## Related
- [[CLAUDE|Master Index]] · [[04-Source-Code/firmware/blackpillrtkcode_v2/blackpillrtkcode_v2|firmware v2]] ·
[[03-Architecture-Hardware/hardware-respec-and-pcb-brief|Hardware re-spec + PCB brief]] ·
[[03-Architecture-Hardware/BOM-master|BOM]]

@ -0,0 +1 @@
Subproject commit 1eab7b92ff5a9421f0e358936ce038dbee8e39e1

@ -0,0 +1 @@
Subproject commit f93a36d3ae8d64ead1036f422a32024eb3cb9cbf

View File

@ -0,0 +1,49 @@
---
tags: [project-drogheda, notifications, mattermost]
project: Project Drogheda
author: Helgen CTO
date: 2026-06-20
status: Active
---
# Mattermost notifications
There is **no Mattermost MCP connector** available (checked the connector registry),
so we post to Mattermost the simple, robust way: an **Incoming Webhook** + the
dependency-free `notify_mattermost.py`. No secrets in the repo — the webhook URL
lives in an environment variable.
## One-time setup
1. **Create the webhook** in Mattermost: *Main Menu → Integrations → Incoming
Webhooks → Add Incoming Webhook*. Choose the target channel (e.g. `#drogheda`)
and copy the URL: `https://<your-mattermost>/hooks/xxxxxxxxxxxxxxxxx`.
*(If "Integrations" is hidden, a System Admin must enable Incoming Webhooks in
System Console → Integrations → Integration Management.)*
2. **Export the URL** (add to your shell profile or a secrets manager — not git):
```bash
export MATTERMOST_WEBHOOK_URL="https://<your-mattermost>/hooks/xxxxxxxxxxxxxxxxx"
```
## Use
```bash
python3 notify_mattermost.py "🛰️ Drogheda: anti-jump hotfix deployed ✅"
python3 notify_mattermost.py --channel drogheda --username "Drogheda Bot" "RTK fix restored on ZPM 020"
echo "line 1\nline 2" | python3 notify_mattermost.py - # read from stdin
python3 notify_mattermost.py --dry-run "test" # print payload, don't send
```
Markdown works in the message body (tables, bullets, `code`). The script exits `0`
on success, `2` on config/usage error, `1` on a send failure.
## Wire it into workflows
- **Hackathon / CI:** call it at the end of a deploy or test step.
- **Backend health:** the `htracker` notifier already does Discord+SMTP; a Mattermost
sink can be added the same way, or pipe its events through this script.
- **Scheduled status:** cron / launchd → a daily "fleet online/offline" digest.
> If we later want richer two-way Mattermost integration (read channels, react to
> messages), build a small custom MCP with the `mcp-builder` skill — but for
> push notifications the webhook above is sufficient and lower-maintenance.

View File

@ -0,0 +1,90 @@
#!/usr/bin/env python3
"""
notify_mattermost.py — post a message to a Mattermost channel via Incoming Webhook.
Dependency-free (stdlib urllib). The webhook URL is a secret and is read from the
environment — NEVER hardcode it (Helgen ethos: no secrets in the repo).
Setup (one-time):
1. In Mattermost: Main Menu → Integrations → Incoming Webhooks → Add. Pick the
channel, copy the URL (looks like https://<host>/hooks/xxxxxxxxxxxxxxxxx).
2. export MATTERMOST_WEBHOOK_URL="https://<host>/hooks/xxxxxxxxxxxxxxxxx"
Usage:
python3 notify_mattermost.py "Backend hotfix deployed ✅"
echo "multi-line\nstatus" | python3 notify_mattermost.py -
python3 notify_mattermost.py --channel ops --username "Drogheda Bot" "RTK fix restored"
python3 notify_mattermost.py --dry-run "test" # print payload, don't send
Exit codes: 0 ok · 2 usage/config error · 1 send failure.
"""
import argparse
import json
import os
import sys
import urllib.request
import urllib.error
DEFAULT_USERNAME = "Drogheda Bot"
DEFAULT_ICON = ":satellite:"
def build_payload(text, channel=None, username=DEFAULT_USERNAME, icon=DEFAULT_ICON):
payload = {"text": text, "username": username}
if icon:
payload["icon_emoji"] = icon
if channel:
payload["channel"] = channel # override the webhook's default channel
return payload
def send(url, payload, timeout=10):
data = json.dumps(payload).encode("utf-8")
req = urllib.request.Request(
url, data=data, headers={"Content-Type": "application/json"}, method="POST"
)
with urllib.request.urlopen(req, timeout=timeout) as resp:
return resp.status, resp.read().decode("utf-8", "ignore")
def main(argv=None):
p = argparse.ArgumentParser(description="Post a message to Mattermost via webhook.")
p.add_argument("text", help="Message text (Markdown ok). Use '-' to read from stdin.")
p.add_argument("--channel", help="Override channel (name without #).")
p.add_argument("--username", default=DEFAULT_USERNAME)
p.add_argument("--icon", default=DEFAULT_ICON, help="Emoji, e.g. :satellite:")
p.add_argument("--url", default=os.environ.get("MATTERMOST_WEBHOOK_URL"),
help="Webhook URL (defaults to $MATTERMOST_WEBHOOK_URL).")
p.add_argument("--dry-run", action="store_true", help="Print payload; do not send.")
args = p.parse_args(argv)
text = sys.stdin.read().rstrip("\n") if args.text == "-" else args.text
if not text.strip():
print("error: empty message", file=sys.stderr)
return 2
payload = build_payload(text, args.channel, args.username, args.icon)
if args.dry_run:
print(json.dumps(payload, indent=2, ensure_ascii=False))
return 0
if not args.url:
print("error: no webhook URL. Set MATTERMOST_WEBHOOK_URL or pass --url.",
file=sys.stderr)
return 2
try:
status, body = send(args.url, payload)
if status == 200:
print("ok")
return 0
print(f"send failed: HTTP {status}: {body}", file=sys.stderr)
return 1
except urllib.error.URLError as e:
print(f"send failed: {e}", file=sys.stderr)
return 1
if __name__ == "__main__":
raise SystemExit(main())

View File

@ -0,0 +1,41 @@
$$$AS+I=1111
$$$AS+A=internet.itelcel.com
$$$AS+Q=webgprs
$$$AS+q=webgprs2002
$$$AS+O=0
$$$AS+L=1
$$$AS+D=1
$$$AS+f=0
$$$AS+B=115200
$$$AS+N=1
$$$AS+S=rtk2go.com
$$$AS+P=2101
$$$AS+U=rtkh0021@helgen.tech:none
$$$AS+M=HelgenTestStation
$$$AS+n=0
$$$AS+s=
$$$AS+p=
$$$AS+u=
$$$AS+m=0
$$$AS+i=
$$$AS+a=0
$$$AS+Z=0
$$$AS+z=pp.services.u-blox.com
$$$AS+Y=
$$$AS+y=0
$$$AS+X=0
$$$AS+x=0
$$$AS+W=0
$$$AS+w=
$$$AS+V=
$$$AS+v=
$$$AS+t=
$$$AS+T=1
$$$AS+r=199.85.208.94
$$$AS+h=5010
$$$AS+k=2
$$$AS+K=H0021
$$$AS+j=300
$$$AS+C=1
$$$AS+c=0
$$$AS+F=1

View File

@ -0,0 +1,23 @@
---
tags: [project-drogheda, analytics, placeholder]
project: Project Drogheda
author: Helgen CTO
date: 2026-06-20
status: Draft
---
# Analytics & Reports (placeholder)
> Reporting reads **curated stores / SQL views — never raw or transactional data**
> in the hot path (Helgen ethos #4). This folder will hold the SQL views, dashboard
> definitions, and report templates once the backend stabilizes.
Planned:
- **Fleet availability** views (per unit / per BU / per terminal) — feeds the
online/offline picture (see `htracker` web_monitor + notifier).
- **VERONICA / PowerBI** consumer mappings.
- Uptime + km-traveled + fix-quality reporting per unit.
Source backend: [[02-Architecture-Software/backend-review|backend-review]] ·
`04-Source-Code/htracker`.

View File

@ -0,0 +1,233 @@
---
title: "Drogheda HBOX — Bench Log + Config Analysis (20 Jun 2026)"
created: 2026-06-20
revised: 2026-06-20 (rev 2 — corrected swap labels + config files)
owner: Leo Rodriguez (Helgen)
status: draft-for-review
type: test-evidence
scope: 7 swap-matrix bench captures + NTRIP-caster & u-blox config files, v1 firmware
source_logs: "Drive: 06-Testing-Validation/Logs Prueba 20JUN26 (*_GPS.ubx, *_NTRIP.log, 4G_NTRIP_HelgenSRV-NMEA.txt, configuration_GNSS.txt)"
tooling: "06-Testing-Validation/log-analysis/analyze_logs.py + dashboard.html"
tags: [drogheda, hbox, rtk, ntrip, gnss, bench-test, rca, thermal, config]
---
# Drogheda HBOX — Bench Log + Config Analysis
**One-line verdict (rev 3):** Of 7 bench captures, 4 pass the comms chain and
**only 1 is a confirmed fault** — `RTK21NTRIP21` (master 21: modem won't register,
511 AT errors). The two "no-forward" captures are **indeterminate**, not failed: the
NTRIP-master console can't prove forwarding either way (the healthy RTK14 baseline
logs the same sparse console). The confirmed fault reproduces cold → not heat. The
u-blox config is correct (GGA on UART2 @1 Hz); corrections come from the **free public
rtk2go caster**, and no unit reached RTK fix (SPS only — bench sky + corrections rarely
applied).
> **Rev-3 correction (post-baseline, 2026-06-20).** A healthy RTK14 capture
> (`RTK14NTRIP14_440`) — **proven forwarding 1 Hz to the server by tcpdump and live in
> Wialon** — logs the *same* sparse NTRIP-master console (only `NET`; no `State main` /
> `UDP Bytes sent`) as the captures I had called "no-forward." **Absence of those console
> lines does not mean a unit failed.** So the earlier **"master-14 ingest fault" is
> withdrawn** (master 14 demonstrably forwards — it *is* the baseline). Only
> `RTK21NTRIP21` is a confirmed fault; `RTK14NTRIP14` and `RTK21NTRIP14` are
> **indeterminate**. §§3–4 below predate this and are superseded on the no-forward point.
> The structural fix: make the **Blackpill log fix + packets + state** so health is
> unambiguous — see `04-Source-Code/firmware/docs/LOGGING-SPEC.md`.
> **Baseline (RTK14NTRIP14_440), 3 views:** receiver `.ubx` = 341 epochs, 100% valid,
> **12 sats, HDOP 0.75, SPS** (C/N0 ~20); server tcpdump = **984 pkts, steady 1 Hz, max
> gap 0.86 s, zero dropouts** to `…:5010`; master console = registered only. Healthy
> end-to-end **but SPS, not RTK** (corrections not applied). Use as the healthy-GNSS
> baseline; raise the bar to a fix≥4 capture once corrections work.
> **Rev-2 note:** two captures were re-labelled by their swap pair —
> `RTK04NTRIP24 → RTK24NTRIP04` and `RTK14NTRIP21 → RTK21NTRIP14` (byte-identical,
> md5-verified). This **moved the no-forward fault from "board 14" to "master 14"**
> (the element common to both no-forward captures). Earlier drafts said board 14.
---
## 1. Executive summary
Two log streams were captured per unit on the Querétaro bench (20 Jun 2026):
`*_GPS.ubx` (the ZED-F9P over USB, via u-center) and `*_NTRIP.log` (the 4G NTRIP
Master console). Filenames `RTK<bb>NTRIP<mm>` = RTK board `bb` + NTRIP master `mm`,
forming a **swap matrix** that isolates board vs master faults. Two config files
were also analysed: the 4G master config and the F9P config dump.
**What the bench can / cannot show:** it validates the **integration/comms chain**
(boot → GNSS position → cell registration → UDP forwarding). It does **not** validate
RTK accuracy (needs open sky + live corrections) and **cannot** measure heat (v1 has
no temperature channel; the bench runs cold).
| Outcome | Captures | Why |
|---|---|---|
| **Healthy (bench)** | RTK17NTRIP17, RTK24NTRIP04, RTK24NTRIP24, RTK25NTRIP25 | boot → GNSS alive → TELCEL registered → UDP forwarding |
| **Failed — no forwarding** | RTK14NTRIP14, RTK21NTRIP14 | GNSS valid on USB, modem registered, but **0 UDP** and no state machine — **both use master 14** |
| **Failed — modem** | RTK21NTRIP21 | modem **never registers** (`0xFFFFF`, **511× AT-parser errors**), stuck in init |
**Isolated faults:**
1. **NTRIP master 14 — GGA ingest.** With *two different* GPS boards (14 and 21),
master 14 forwarded **nothing** while the F9P produced valid NMEA on USB. The
common element is the master → **master 14's serial input / NMEA ingest**, not the
board. (See config note: GGA *is* enabled on UART2, so it is not a message-rate
error.)
2. **NTRIP master 21 — LTE modem.** Will not register on Telcel; continuous
AT-parser errors. The GPS feeding it (board 21) is fine.
Neither is the GNSS receiver; neither needs heat.
---
## 2. The heat question (headline for the deck)
**These logs do not prove the field failures are thermal — and that is useful.**
They are room-temperature bench captures with no thermal instrumentation, yet they
reproduce hard faults **cold** (master-14 ingest, master-21 modem). A fault you can
reproduce cold is **not** caused by heat → the fleet problem is **not a single
thermal cause**, matching the RCA's "don't collapse everything into heat."
This neither confirms nor refutes the **field** thermal hypothesis (18650 / boost / modem
in a sealed cabinet in Veracruz sun, RCA H1/H7) — that needs a **thermocouple soak**
of a representative cabinet and/or **v2 MCU-temp logging**.
---
## 3. Per-device findings
Bench context is common: indoor/obstructed sky → **C/N0 ≈ 17–21 dB-Hz**, high HDOP,
**zero RTK fixes** anywhere. Read the GNSS columns as "is the receiver alive and
emitting valid NMEA," not as a positioning grade.
| Capture | Board / Master | Status | GNSS (USB) | Cell | UDP out | dBm | AT err | Headline |
|---|---|---|---|---|---|---|---|---|
| **RTK24NTRIP04** | 24 / 04 | 🟢 Healthy | alive, SPS, 98% valid | TELCEL | **1555 dg** | −51 | 0 | full chain OK (master 04 good) |
| **RTK24NTRIP24** | 24 / 24 | 🟢 Healthy | alive, SPS, 85% valid | TELCEL | **1831 dg** | −51 | 0 | full chain OK |
| **RTK25NTRIP25** | 25 / 25 | 🟢 Healthy | alive, SPS, 100% valid | TELCEL | **2413 dg** | −51 | 0 | best forwarder |
| **RTK17NTRIP17** | 17 / 17 | 🟢 Healthy | alive, 12% valid (poor sky this run) | TELCEL | **2267 dg** | −63 | 0 | comms healthiest; GNSS had no sky |
| **RTK14NTRIP14** | 14 / 14 | 🔴 Failed | **alive, SPS, 100% valid** | TELCEL | **0** | −75 | 0 | registered but forwards nothing |
| **RTK21NTRIP14** | 21 / 14 | 🔴 Failed | **alive, SPS, 100% valid, 10 sats** | TELCEL | **0** | −83 | 0 | same master-14 symptom, other board |
| **RTK21NTRIP21** | 21 / 21 | 🔴 Failed | alive, SPS, 97% valid | **NOT reg** | **0** | −113 | **511** | modem never attaches |
---
## 4. Swap-matrix analysis (isolation)
**By NTRIP master:**
| Master | Captures | Result |
|---|---|---|
| 04, 17, 24, 25 | one each | **healthy** — forwards position |
| **14** | RTK14NTRIP14 (board 14) **and** RTK21NTRIP14 (board 21) | **both fail to forward** despite valid USB NMEA |
| **21** | RTK21NTRIP21 (board 21) | modem **not registered**, AT errors |
→ Master 14 fails with two different boards → **fault is master 14's NMEA ingest**
(serial input), confirmed common factor. Master 21 is a modem-registration fault.
Masters 04/17/24/25 are good.
**By RTK board:**
| Board | Captures | Read |
|---|---|---|
| 24 | NTRIP04 (✓), NTRIP24 (✓) | **healthy with two masters** → board 24 GNSS + UART2 output good |
| 17, 25 | one each (✓) | healthy |
| 21 | NTRIP14 (master-14 fault), NTRIP21 (master-21 fault) | GNSS alive on USB; its failures belong to the masters it was paired with |
| 14 | NTRIP14 only (master-14 fault) | GNSS alive on USB; **board-14 UART2 output unproven** — only ever paired with the bad master 14 |
**Open item:** test board 14 (and board 21) with a *known-good* master to clear/convict
their UART2 output; test master 14 with a known-good board to confirm the ingest fault.
---
## 5. Configuration findings (from the two config files)
### 5.1 4G NTRIP Master (`4G_NTRIP_HelgenSRV-NMEA.txt`)
| Item | Value | Note |
|---|---|---|
| NTRIP caster | **rtk2go.com : 2101**, mountpoint **HelgenTestStation** | **free public community caster — best-effort, no SLA** |
| APN | internet.itelcel.com (Telcel) | correct |
| UART baud (to F9P) | 115200 | matches F9P UART2 baud |
| NTRIP restart timer | **300 s** | matches "NTRIP DELAY n/300" in the logs |
| Telemetry out | UDP → Helgen server :5010, device **H0014** | position forwarding path |
| PointPerfect | configured but disabled | not in use |
> **Flag:** corrections from a **free public caster (rtk2go)** is a reliability risk
> for a production fleet and a plausible reason the correction client rarely connected
> and no unit reached RTK. Move production to a **dedicated/own caster (HelgenSRV)**.
### 5.2 ZED-F9P (`configuration_GNSS.txt`, UBX CFG-VALGET dump)
| Item | Value | Note |
|---|---|---|
| Model / FW | ZED-F9P, HPG 1.32 | rover-grade |
| Mode | **Rover** (TMODE=0) | correct (not a base) |
| Update rate | **1 Hz** (1000 ms) | correct |
| **GGA on UART2** | **ENABLED (1/epoch)** | the feed the master needs — **correctly configured** |
| RMC on UART2 | ENABLED (1/epoch) | not required by NTRIP, harmless |
| UART2 | enabled · NMEA-out + RTCM3-in · 115200 | textbook 4G-NTRIP rover wiring |
| USB | full NMEA (GGA/GLL/GSA/GSV/RMC/VTG) | matches the `.ubx` captures |
| **Dump layer** | **RAM only** | **flash persistence NOT verified** |
> **Two implications:**
> 1. **The message config is correct** — GGA *is* on UART2 @1 Hz. So master 14's dead
> feed is **not** a missing-GGA setting → it's the master's ingest (or a per-unit
> deviation), reinforcing §4.
> 2. **The dump is RAM-layer only.** A RAM-only config is **lost on every power cycle**
> (the classic "forgot Save Config to flash" trap). Confirm the **FLASH layer** on
> each unit — a unit that boots without GGA-on-UART2 would feed the master nothing.
---
## 6. Recommendations / next steps
1. **Master 14 — bench the ingest.** Feed master 14 a known-good GGA stream (or pair
with a healthy board) and watch for `State main` + `UDP Bytes sent`. If still zero,
the master's serial-in / firmware is the fault → swap/repair the master.
2. **Master 21 — modem triage.** Re-test registration with a known-good SIM + antenna;
511 AT-parser errors + `0xFFFFF` point at the LTE modem / its AT link / power. Swap
the modem to confirm.
3. **Persist & verify the F9P config to FLASH** on every unit (u-center → Save Config),
then dump the **flash layer** to prove GGA-on-UART2 survives a power cycle. Check the
simpleRTK2B Pro **XBEE↔UART2 switch** too (UART2 is off by default).
4. **Move corrections off rtk2go** to a dedicated caster (HelgenSRV) with uptime, then
**re-run outdoors** with open sky to actually grade RTK (quality 4/5).
5. **Close the heat question separately** — thermocouple soak of a sealed cabinet in
full sun and/or v2 MCU-temp logging.
6. **Retranslator log (separate effort)** — pair this unit-side analysis with the
backend (HOL-stall / anti-jump) evidence for the full RCA picture.
---
## 7. Method & reproducibility
- **Tool:** `log-analysis/analyze_logs.py` (Python stdlib). Parses NMEA (GGA/RMC/GSV) +
UBX from `.ubx`, and the GPTXT diagnostic stream (`State main`, `NET`, `UDP Bytes
sent`, `NTRIP DELAY`, `SM_REG`, AT errors) from `.log`. Emits per-device metrics,
a swap-matrix with **common-factor isolation**, and per-device time-series.
- **Dashboard:** `log-analysis/dashboard.html` — dark (GitHub Dark-Dimmed),
self-contained: config summary, signal-flow diagram (marks the break + names the
common master), zoomable time-series, status board, swap matrix.
- **Config decode:** 4G master `$$$AS+` params; F9P UBX **CFG-VALGET** keys
(CFG-MSGOUT-NMEA_ID_GGA_UART2, CFG-UART2*, CFG-RATE*, CFG-TMODE-MODE).
- **Re-run:** `python3 analyze_logs.py --in raw_cur --out out && python3 build_dashboard.py`
- **Validation:** classification spot-checked vs raw logs; the two relabels were
md5-verified byte-identical to the originals.
**Caveats:** ~10-min captures; indoor bench sky; v1 (no temperature); F9P config dump
is RAM-layer only; `ntr/tcp/pp` enum values inferred from behavior. None affect the
pass/fail conclusions, which rest on presence/absence of UDP forwarding, cellular
registration, and the swap-matrix common factor.
---
## 8. Glossary
- **GGA fix quality:** 0 no-fix · 1 SPS · 2 DGPS · **4 RTK-fixed** · **5 RTK-float**.
- **C/N0 (dB-Hz):** satellite carrier-to-noise; ~35–50 open sky, ~20 indoors.
- **NTRIP master state `main,ntr,tcp,pp,ntr_srv,udp`:** main=4 operational / 1 init;
udp=1 forwarding position out; ntr = correction-client state (0 idle … 2 active).
- **`NET,<op>,<dBm>,<MCCMNC>`:** TELCEL = registered; **`0xFFFFF` = not registered**.
- **NTRIP DELAY n/300:** seconds since last correction vs the 300 s restart watchdog.
- **GGA→UART2:** whether the F9P's GGA is reaching the 4G master over UART2 (XBee
socket). Inferred from the master running its loop + forwarding UDP.

View File

@ -0,0 +1,85 @@
---
title: "Project Drogheda — GNSS Findings Summary (Beamer source)"
created: 2026-06-20
owner: Leo Rodriguez (Helgen)
type: findings-summary
purpose: source material for a later LaTeX Beamer deck
scope: 20 Jun 2026 bench captures + RTK14_440 healthy baseline + config files
tags: [drogheda, hbox, gnss, rtk, ntrip, findings, presentation]
---
# GNSS Findings Summary — Project Drogheda (20 Jun 2026)
> Presentation-ready. Each `##` is a candidate slide; bullets are speaker points.
> Full detail: `2026-06-20_Drogheda_Bench-Log-Analysis_v1.0.md`. Tooling: `log-analysis/`.
## 1 · What we did
- Analyzed three independent log views per unit from the 20 Jun bench: u-blox receiver
(`.ubx`), 4G-NTRIP-Master console (`.log`), and a server-side retranslator capture
(tcpdump). Decoded the NTRIP-caster and ZED-F9P config files.
- Built local tooling (in-browser **Log Explorer**, batch analyzer, dark dashboard) and
established a **healthy GNSS baseline**.
## 2 · Headline: the GNSS receivers are fine — RTK is not being achieved
- **Every board produces valid NMEA** — good satellite counts (6–12) and HDOP (≈0.75–1.5).
The **ZED-F9P is not the failure.**
- **No unit reached RTK fix.** Every epoch is **SPS (quality 1)**; zero quality 4/5.
Centimetre positioning was never delivered.
- **Why:** corrections are not being applied — a combination of (a) corrections sourced
from a **free public caster (rtk2go.com, no SLA)**, (b) the NTRIP correction client
rarely reaching "connected," and (c) bench/indoor sky. The F9P config is actually
**correct** (GGA on UART2 @1 Hz) — but the dump is **RAM-only; flash persistence is
unverified** (a config lost on power-cycle would silently break the feed).
## 3 · The healthy baseline (RTK14_440) — what "good" looks like today
- Proven **end-to-end**: **984 packets, steady 1 Hz, zero dropouts**, valid Querétaro
position delivered to the server — live in Wialon and the monitor.
- **12 sats, HDOP 0.75** — excellent geometry. **But SPS, not RTK.**
- So **"healthy delivery" ≠ "RTK."** The baseline is *healthy SPS + reliable delivery*;
raise the bar to a fix-≥4 capture once corrections work.
## 4 · The diagnostic trap (and a retraction)
- The **4G-NTRIP-Master console is an unreliable health signal.** The provably-healthy
baseline logs the **same sparse console** (only cellular `NET` lines, no forwarding
lines) as units that *looked* failed.
- **Retraction:** the earlier "master-14 ingest fault" is **withdrawn** — master 14 is the
baseline and demonstrably forwards. **Only master 21 is a confirmed fault** (modem never
registers: `0xFFFFF` + 511 AT-parser errors).
- **Lesson:** per-unit health must come from a component that *parses the fix itself* — the
Blackpill — logged in a trustworthy form.
## 5 · Heat is not implicated by these logs
- Room-temperature bench captures, no thermal channel (v1). The faults reproduce **cold**,
so heat is **not** the cause of what we see here. The **field** thermal hypothesis stays
open — needs a thermocouple cabinet soak and/or v2 MCU-temp logging.
## 6 · Changes made this activity
- **Blackpill v2.1 logging spec** — replace the self-overwriting ANSI dashboard with a
**structured, timestamped, parseable** stream (state, fix, packets, battery, MCU temp,
recovery, **reset cause**). Makes per-unit health unambiguous from the unit's own log.
- **Log tooling** — `hbox-log-tools` repo on `git.helgen.dev`: in-browser Log Explorer
(parse + auto-plot + compare to baseline + PNG/SVG export), batch analyzer, dark dashboard.
- **Corrected analysis + findings doc** (the master-14 retraction above).
## 7 · Next steps
1. **Persist the F9P config to FLASH and verify** (RAM-only is a power-cycle risk); check
the simpleRTK2B Pro `XBEE↔UART2` switch.
2. **Move corrections off rtk2go** to a dedicated caster (HelgenSRV) with uptime.
3. **Open-sky RTK retest** with a verified caster → confirm fix ≥4/5.
4. **Connect the Blackpill** with v2.1 logging → capture a true per-unit health log
(drops into the Log Explorer as a fifth view).
5. **Modem triage master 21** (known-good SIM/antenna; swap to confirm).
6. **Thermal soak test** to settle the field heat hypothesis.
## One-line takeaway
> The radios work; we are delivering a **standalone-GPS** position reliably, **not RTK**.
> The blockers are the **correction source/persistence** and **observability** — both now
> addressed by config/caster changes and the new Blackpill logging + tooling.

View File

@ -0,0 +1,263 @@
% Project Drogheda — GNSS / Log Analysis findings (Helgen Beamer / Metropolis)
% Built from the helgen-beamer template. Compile: latexmk -xelatex <file>.tex
\documentclass[10pt,aspectratio=169]{beamer}
\usetheme[progressbar=frametitle, numbering=fraction, block=fill]{metropolis}
% ---------- Helgen official palette (Guia de Identidad v1.5, 2026) ----------
\definecolor{HelgenIndigo}{HTML}{2C3C5B}
\definecolor{HelgenCyan}{HTML}{51F3DA}
\definecolor{HelgenTeal}{HTML}{17A08C}
\definecolor{HelgenMist}{HTML}{EFF1F5}
\setbeamercolor{normal text}{fg=HelgenIndigo, bg=white}
\setbeamercolor{alerted text}{fg=HelgenTeal}
\setbeamercolor{frametitle}{bg=HelgenIndigo, fg=white}
\setbeamercolor{progress bar}{fg=HelgenCyan, bg=HelgenMist}
\setbeamercolor{title separator}{fg=HelgenCyan}
\setbeamercolor{block title}{bg=HelgenMist, fg=HelgenIndigo}
% ---------- Fonts: XeLaTeX + bundled brand fonts, clean fallback otherwise ----------
\usepackage{iftex}
\ifXeTeX
\usepackage{fontspec}
\IfFontExistsTF{League Spartan}{%
\newfontfamily\helgentitle{League Spartan}[BoldFont={* Bold}]
\setbeamerfont{title}{family=\helgentitle, series=\bfseries}
\setbeamerfont{frametitle}{family=\helgentitle, series=\bfseries}
\setbeamerfont{section title}{family=\helgentitle, series=\bfseries}
\setbeamerfont{standout}{family=\helgentitle, series=\bfseries}
}{}
\IfFontExistsTF{MADE Evolve Sans}{\setsansfont{MADE Evolve Sans}}{}
\else
\usepackage[T1]{fontenc}
\fi
\usepackage{booktabs}
\usepackage{tikz}
\usetikzlibrary{arrows.meta, positioning, shapes.misc, fit}
\usepackage{listings}
\lstset{
basicstyle=\ttfamily\scriptsize,
keywordstyle=\color{HelgenIndigo}\bfseries,
commentstyle=\color{gray},
stringstyle=\color{HelgenTeal},
showstringspaces=false, breaklines=true, frame=single,
framerule=0.2pt, rulecolor=\color{HelgenMist}
}
% Reusable block / arrow styles (Helgen palette)
\tikzset{
blk/.style={rounded rectangle, draw=HelgenIndigo, fill=HelgenMist, font=\scriptsize,
minimum height=8mm, inner xsep=2.4mm, align=center},
acc/.style={rounded rectangle, draw=HelgenTeal, fill=white, font=\scriptsize,
minimum height=8mm, inner xsep=2.4mm, align=center},
arr/.style={-{Stealth}, thick, HelgenTeal},
arrx/.style={-{Stealth}, thick, red!70!black, dashed},
}
% ---------- Footer ----------
\setbeamertemplate{frame footer}{%
\scriptsize\color{gray}\insertshortauthor\ · \insertshorttitle\ · Confidencial — Helgen Servicios Especializados}
% ---------- Metadata ----------
\title[Drogheda · GNSS/Logs]{Project Drogheda — GNSS \& Log Analysis}
\subtitle{HBOX RTK fleet: bench triage, healthy baseline, and observability}
\author[Helgen]{Helgen Technologies — CTO/CSO}
\date{20 June 2026}
\titlegraphic{}
\begin{document}
\maketitle
\begin{frame}{Agenda}
\tableofcontents
\end{frame}
% =====================================================================
\section{Context}
\begin{frame}{Where we are}
\begin{columns}[T]
\column{0.58\textwidth}
\begin{itemize}
\item HBOX RTK trackers on Hutchison Ports heavy equipment (ICAVE, Veracruz) — worked, then failed.
\item Three independent failure fronts, fixed in parallel:
\begin{itemize}\footnotesize
\item \textbf{Backend} (retranslator) — could drop the fleet / black-hole a unit
\item \textbf{Thermal} — PLA box in a sealed cabinet (heat trap)
\item \textbf{Prototype HW + observability} — hand-wired; logs untrustworthy
\end{itemize}
\item Field units pulled $\rightarrow$ bench triage in Querétaro (firmware v1, no thermal log).
\end{itemize}
\column{0.4\textwidth}
\begin{alertblock}{Invariant}
Corrections ride the \textbf{4G modem}, not the GNSS — recover the link by power-cycling the stack.
\end{alertblock}
\end{columns}
\end{frame}
\begin{frame}{The signal chain}
\centering
\begin{tikzpicture}[node distance=7mm and 13mm]
\node[blk] (f9p) {ZED-F9P\\(GNSS)};
\node[blk, right=of f9p] (mas) {4G NTRIP\\Master};
\node[blk, right=of mas] (ret) {retranslator\\(htracker)};
\node[blk, right=of ret] (wial) {Wialon};
\node[acc, above=10mm of mas] (cas) {NTRIP caster\\(rtk2go)};
\draw[arr] (f9p) -- node[above, font=\tiny]{UART2: GGA} (mas);
\draw[arr] (mas) -- node[above, font=\tiny]{UDP} (ret);
\draw[arr] (ret) -- (wial);
\draw[arr] (cas) -- node[right, font=\tiny]{RTCM in} (mas);
\end{tikzpicture}
\vfill
\footnotesize Corrections come \emph{down} from the caster over 4G; position goes \emph{out} by UDP to the retranslator. The GNSS receiver never talks to the network directly.
\end{frame}
% =====================================================================
\section{Backend (retranslator)}
\begin{frame}{Backend — hardened \& deployed to prod}
\begin{itemize}
\item \textbf{Head-of-line blocking removed:} a \texttt{WialonForwarder} thread pool does all Wialon TCP I/O \emph{off} the UDP receive loop — one slow upstream no longer drops the fleet.
\item \textbf{Anti-jump now time-aware + self-healing:} re-anchors after rejects, clears on disconnect — a unit is never permanently black-holed.
\item \textbf{Identity keyed by \texttt{cell\_id}} (CGNAT-safe), not \texttt{(ip,port)}.
\item Monitor gained a \textbf{backend-victim} KPI; logs ship to Grafana Loki. \alert{Live in production.}
\end{itemize}
\begin{alertblock}{Free triage}
ONLINE in htracker + OFFLINE in Wialon = \textbf{backend victim}, not a dead radio.
\end{alertblock}
\end{frame}
% =====================================================================
\section{GNSS log findings}
\begin{frame}{What we analyzed}
\begin{itemize}
\item \textbf{7 swap-matrix bench captures} (RTK board $\times$ NTRIP master) + one healthy baseline.
\item \textbf{Three views per unit:} u-blox \texttt{.ubx} (receiver), 4G-NTRIP console \texttt{.log}, server retranslator \texttt{tcpdump}.
\item Decoded the NTRIP-caster and ZED-F9P configuration files.
\item Built local tooling so this is repeatable (explorer + analyzer + dashboard).
\end{itemize}
\end{frame}
\begin{frame}{Headline: receivers are fine — RTK is not being achieved}
\begin{itemize}
\item Every board emits \textbf{valid NMEA} (6–12 sats, good HDOP). The ZED-F9P is \emph{not} the failure.
\item But \textbf{every epoch is SPS (quality 1)} — zero RTK (4/5). Centimetre position never delivered.
\item \textbf{Why:} free public \textbf{rtk2go} caster (no SLA) + F9P config saved to \textbf{RAM only} + indoor/bench sky.
\item The F9P message config itself is \textbf{correct} — GGA on UART2 @ 1\,Hz.
\end{itemize}
\end{frame}
\begin{frame}{Every unit is SPS — none reached RTK}
\centering
\includegraphics[width=0.88\textwidth]{fig/fig_fix_compare.pdf}\\[2mm]
\footnotesize Max GGA fix quality per capture (7 bench + baseline) — all flat at 1 (SPS); the RTK line (4) is never reached.
\end{frame}
\begin{frame}{Why: the corrections never settle}
\centering
\includegraphics[width=0.82\textwidth]{fig/fig_ntrip_delay.pdf}\\[2mm]
\footnotesize Even on a healthy unit the NTRIP correction delay sawtooths up to the 300\,s restart watchdog — the free rtk2go feed is intermittent.
\end{frame}
\begin{frame}{Healthy baseline — \texttt{RTK14\_440} (three views agree)}
\begin{columns}[T]
\column{0.56\textwidth}
\begin{itemize}\small
\item \textbf{Receiver:} 341 epochs, 100\% valid, 12 sats, HDOP 0.75 — SPS.
\item \textbf{Server:} 984 packets, steady 1\,Hz, \textbf{zero dropouts}, live in Wialon.
\item \textbf{Console:} ``registered'' only — no forwarding lines.
\end{itemize}
\column{0.4\textwidth}
\begin{alertblock}{The trap}
Healthy \textbf{delivery} $\neq$ \textbf{RTK}. Baseline = healthy SPS + reliable delivery.
\end{alertblock}
\end{columns}
\end{frame}
\begin{frame}{Baseline receiver — healthy geometry, stuck at SPS}
\centering
\includegraphics[width=0.93\textwidth]{fig/fig_baseline_gnss.pdf}\\[1mm]
\footnotesize \texttt{RTK14\_440} u-blox capture: 12 sats, HDOP 0.75, C/N0 $\sim$20 dB-Hz — but the fix never leaves 1 (SPS).
\end{frame}
\begin{frame}{Baseline delivery — healthy end-to-end}
\centering
\includegraphics[width=0.93\textwidth]{fig/fig_delivery.pdf}\\[1mm]
\footnotesize Server-side tcpdump: 984 datagrams, steady $\sim$1\,Hz, max gap 0.86\,s — the unit is genuinely reporting (live in Wialon).
\end{frame}
\begin{frame}{The console blind spot — and a retraction}
\begin{itemize}
\item The \emph{provably-healthy} unit logs the \textbf{same sparse console} as units that looked ``failed''.
\item $\Rightarrow$ the 4G-NTRIP console \textbf{cannot prove forwarding}. Absence of lines $\neq$ failure.
\item \alert{Retraction:} the earlier ``master-14 ingest fault'' is \textbf{withdrawn}.
\item Only \textbf{master 21} is a confirmed fault: modem won't register (\texttt{0xFFFFF} + 511 AT-parser errors).
\end{itemize}
\end{frame}
\begin{frame}{Per-master verdict}
\centering
\begin{tabular}{ll}
\toprule
\textbf{NTRIP master} & \textbf{Result} \\
\midrule
\#04 · \#17 · \#24 · \#25 & Healthy — registered + forwarding \\
\#14 & \alert{Indeterminate} — console can't confirm; baseline proves it forwards \\
\#21 & \alert{Failed} — modem won't register (\texttt{0xFFFFF} + 511 AT errors) \\
\bottomrule
\end{tabular}
\vfill
\footnotesize Confirm a unit only from a \texttt{.ubx} (fix), the Blackpill, or a server delivery capture — never the console alone.
\end{frame}
\begin{frame}{Heat — not implicated (by these logs)}
\begin{itemize}
\item Room-temperature bench captures, \textbf{no thermal channel} (firmware v1).
\item The hard faults reproduce \textbf{cold} $\Rightarrow$ heat is \emph{not} the cause of what we see here.
\item The \textbf{field} thermal hypothesis stays open — needs a thermocouple cabinet soak and/or v2 MCU-temp logging.
\end{itemize}
\end{frame}
% =====================================================================
\section{Changes \& tooling}
\begin{frame}{Observability — the real fix}
\begin{itemize}
\item The Blackpill watchdog already computes fix, packets, state, battery, MCU temp.
\item \textbf{v2.1 logging spec:} replace the self-overwriting ANSI dashboard with a \textbf{structured, timestamped, parseable} stream (+ decoded reset cause).
\item \textbf{\texttt{hbox-log-tools}} repo: in-browser Log Explorer (parse + plot + compare to baseline, offline), batch analyzer, dashboard, manual.
\end{itemize}
\end{frame}
\begin{frame}[fragile]{The new per-unit log line (v2.1)}
\begin{lstlisting}
BOOT,32,fw=2.1,reset=POR
COLS,ms,state,link,fix,sat,rx,rate,vbat,v5v,veh,ign,tc,tmax,der,rec,limp,nmea_age
TLM,190000,RUN,OK,4,12,340,1HZ,5.01,1,1,48,49,0,0,0,310
EVT,490000,RUN,DEGR,RTK lost -> recycle 4G/NTRIP
\end{lstlisting}
\footnotesize Fix + packets + state + reset cause $\Rightarrow$ per-unit health is unambiguous from the unit's own log — no server tcpdump needed.
\end{frame}
% =====================================================================
\section{Next steps}
\begin{frame}{Next steps}
\begin{enumerate}
\item Save the F9P config to \textbf{FLASH} + verify (RAM-only is a power-cycle risk); check the simpleRTK2B Pro \texttt{XBEE$\leftrightarrow$UART2} switch.
\item Move corrections off rtk2go $\rightarrow$ a \textbf{dedicated caster} (HelgenSRV) with uptime.
\item \textbf{Open-sky RTK retest} with a verified caster $\rightarrow$ confirm fix $\geq 4$.
\item Connect the \textbf{Blackpill} with v2.1 logging; modem-triage master 21; thermal soak.
\end{enumerate}
\end{frame}
\begin{frame}[standout]
The radios work.\\
We deliver \textbf{SPS} reliably — \textbf{not RTK}.\\[2mm]
{\normalsize The blockers are \textbf{corrections} and \textbf{observability} — both now addressed.}
\end{frame}
\end{document}

Binary file not shown.

View File

@ -0,0 +1,200 @@
# RCA — HBOX RTK Field Failures (Veracruz / ICAVE)
**Status:** DRAFT — open hypotheses, awaiting field logs
**Date:** 2026-06-19 · **Owner:** Leo Rodriguez (Helgen)
**Scope:** 5 HBOX units at Hutchison Ports ICAVE (RTG crane, forklift, electric
truck, 2 reachstackers). "Working-ish," then a near-simultaneous death; chronic
recurrent disconnects in Wialon. Client escalation 2026-06-17.
> ⚠️ This RCA is **hypothesis-ranked**, not concluded. The single most useful
> next input is the field log set (battery voltage, MCU temp, fix quality,
> reconnect timestamps) plus the backend/SIM/caster logs. Update §6 when they
> arrive.
---
## 1. The two failures are probably *different* failures
Do not collapse these into one cause:
- **(A) Sudden, near-simultaneous death of the fleet.** Multiple independent
units failing at ~the same time points to a **common-mode** stressor, not five
coincidental hardware faults. Candidates: a heat spell, a backend/caster/SIM
outage, or a server/retranslator problem.
- **(B) Chronic "recurrent disconnections" in Wialon over time.** This is the
drip-drip the client has been logging for weeks. Candidates: vibration-induced
intermittents (Dupont/SMA/solder), 4G NTRIP card hangs, and a watchdog that
recovers slowly and noisily.
Treating (A) as "heat" and stopping there risks missing a cheap backend fix —
and treating (B) as "heat" risks shipping a thermal redesign that doesn't stop
the vibration intermittents. Both need addressing; the permanent fix does.
---
## 2. Differential diagnosis (check cheap/common before exotic)
| # | Hypothesis | Explains A? | Explains B? | How to confirm (fast) | Prior |
|---|---|:--:|:--:|---|:--:|
| H1 | **Thermal (component-level).** A sealed **ASA** HBOX sits **inside a sealed ABS IP67 cabinet that also holds the Mean Well supply** (internal heat source), in full sun → cooks the **18650 / modem / boost**. The **ASA shell itself is fine** (UV-stable, softens ~95–100 °C). | ✓✓ | ✓ | Inspect pulled units for swollen **18650**, discoloured boost; thermocouple inside a representative cabinet over a sunny shift | **High** |
| H2 | **Backend common-mode — CONFIRMED mechanism.** The production `retranslator.py` sends to Wialon and waits for the TCP ack (and 20 s connect) **inside the single UDP loop** → one slow upstream stalls the loop and **all units drop at once**. Plus caster/SIM-plan exhaustion. | ✓✓ | ✓ | Gateway log + `netstat -su` UDP drops in the death window; did all units drop within seconds (→ this) or one-by-one (→ H2b)? See `02-Architecture-Software/backend-review.md` | **High (and cheapest)** |
| H2b | **Anti-jump filter lock-out — CONFIRMED mechanism.** On a rejected "jump" the filter never updates its reference and the reference survives reconnects → a healthy unit's packets are dropped **forever**; it transmits but never reaches Wialon. | ✗ | ✓✓ | The unit shows **ONLINE in htracker but OFFLINE in Wialon** (monitor marks online on UDP receipt, before the filter). Check this per unit. | **High** |
| H3 | **Vibration intermittents** — Dupont/SMA/terminal back-out, cracked solder | ✗ | ✓✓ | Pulled-unit inspection: wiggle test, continuity under vibration, visual on joints/SMA torque | **High** |
| H4 | **4G NTRIP card hang/failure** (client's #1 point) | ~ | ✓✓ | Pulled-unit bench: does modem re-enumerate after power cycle? thermal-soak test | **High** |
| H5 | **Under-rated fuse** (client's one remaining power suspicion) | ✓ (if blown by inrush/load) | ~ | Check fuse rating vs measured worst-case + inrush current; look for blown/derated fuses | **Medium** |
| H6 | **Watchdog logic** turning brief RTK loss into long outages / reboot loops | ✗ | ✓✓ | Code review (done — §4) + correlate Wialon gaps with ~5/10-min cadence | **High for B** |
| H7 | **LiPo brownout** — hot/aged cell sags under load, watchdog cuts power at 3.4 V | ✓ | ✓ | Battery-voltage log; capacity test on pulled cells | **Medium-High** |
| H8 | **Power-rail/EMI on machinery** — alternator/inverter transients, ground bounce on a crane | ~ | ✓ | Scope the 12–24 V input under operation; check for resets correlated with motion | **Medium** |
The two **cheapest** checks (H2 backend, H5 fuse) should be done **first** — they
can be confirmed or killed in an hour without pulling a single unit, and H2 in
particular could explain the simultaneous death entirely.
> **Backend code review is done (`02-Architecture-Software/backend-review.md`) and it materially
> raised the priors on H2/H2b.** Two software defects can take healthy units off
> Wialon: (1) synchronous Wialon send-and-ack in the single receive loop →
> head-of-line stall → *simultaneous* fleet dropout; (2) an anti-jump filter that
> can *permanently* black-hole one unit. The single best triage step is now free:
> **compare Helgen's htracker monitor (online on UDP receipt) against the client's
> Wialon (online on positions received).** Units that are online in one and
> offline in the other are **backend victims, not dead hardware** — check
> ZPM 020 / RS-SAN-001 this way before the teardown.
---
## 3. Leading physical hypothesis: thermal (H1/H7)
Why heat is the prime suspect for the sudden death — and **the new component data
makes it worse than first assumed**:
- **It's a nested thermal trap, not one box.** The HBOX is a **3D-printed ASA**
shell sealed **inside** a second sealed enclosure — the **ABS IP67 cabinet** —
which **also contains the Mean Well 24 V supply** (60 W class, several watts of
continuous self-heat). So the HBOX bakes in an enclosure that has **both** solar
gain **and** an internal heat source, with **no airflow** (IP67). See
`03-Architecture-Hardware/cabinet-electrical-bom.md`.
- **The ASA enclosure is adequate — not the liability.** ASA is UV-stable and
**softens at ~95–100 °C**, with margin over the cabinet's interior temperature, so
the shell will **not sag/warp** at these temperatures. (This corrects an earlier
assumption that the inner shell was PLA; both the ABS outer cabinet, Tg ~105 °C,
and the inner **ASA** are fine — the thermal risk is to the **components**.)
- **Environment:** Veracruz in June — coastal, hot, humid, full sun. Sealed
enclosure interiors commonly run **20–35 °C above ambient**; with an internal
supply added, **60–80 °C is realistic**.
- **The weak components sit below the GNSS:**
- **LiPo** — safe **charge only 0–45 °C**; above ~45 °C you get lithium
plating, swelling, capacity loss, and (worst case) thermal runaway. Optimal
is 20–25 °C. A hot LiPo also **sags under load** → the watchdog reads
<3.4 V → cuts power → unit goes dark ("Solid OFF / hibernate"). This is a
clean mechanism for *simultaneous* death during a hot spell.
- **PowerBoost 1000C** — a hobby-grade boost/charger; efficiency and thermal
headroom fall as it heats; possible thermal shutdown.
- **4G NTRIP Master modem** — LTE modems self-heat and thermally throttle/drop;
matches the client's "recurrent 4G NTRIP card failures."
- **The GNSS itself is *not* the weak link:** the **ZED-F9P is rated −40…+85 °C**.
So "the RTK died of heat" most likely means the **battery, charger, or modem**
around it died — not the receiver.
**Implication for the fix:** you cannot firmware your way out of heat. The
permanent fix is thermal-mechanical: the **ASA inner box stays** (it's adequate;
optionally move to metal for extra heatsinking, or mount the PCB straight to the
cabinet plate), **remove/relocate the battery**,
replace the boost with an efficient 24 V→5 V buck, and **conduct heat out to a
metal plate/cabinet** — **not** a bare internal fan, which does nothing in a sealed box
(see hardware brief §2b). Firmware's job is to **measure** temperature (v2 logs MCU
max temp) so the pulled units prove or kill this hypothesis.
---
## 4. Firmware's contribution to the *chronic* disconnects (H6)
Full bug list in `03-Architecture-Hardware/firmware-review.md`. The two that most plausibly create
Wialon "desconexiones recurrentes":
1. **The 5-minute soft-reset is aimed at the wrong subsystem.** v1 reacts to lost
corrections by sending a **GNSS hot-reset** (`UBX-CFG-RST`). That restarts the
ZED-F9P only — it **cannot** revive a hung 4G/NTRIP modem, which is the
client's #1 reported failure. Five minutes are wasted on a no-op.
2. **The 10-minute hard reset has no backoff → reboot-loop flapping.** If the 4G
card is genuinely stuck (or the unit is parked where it can't see sky), v1
power-cycles the whole stack **every ~10 minutes, forever**. Each cycle is a
multi-minute Wialon disconnect. A bank of units doing this looks exactly like
"recurrent disconnections."
Both are fixed in **v2**: recover by power-cycling the **4G stack** (the thing
that actually restarts the modem), detect NMEA silence in ~60 s, apply
**exponential backoff**, and after repeated failures drop into **LIMP mode that
keeps reporting standalone GPS** instead of vanishing. A unit with a dead modem
now still shows on Wialon (degraded) rather than disconnecting.
> Note: v1 does **not** mistreat RTK **float** (quality 5) as a failure — it
> correctly treats fixed(4) and float(5) as healthy. The chronic-disconnect
> driver is the recovery *strategy*, not the fix-quality threshold.
---
## 5. The client's hardware indictment is correct (H3/H4)
ICAVE's review is, frankly, a fair description of a lab prototype shipped to a
brutal industrial environment. Each point maps to a real failure mechanism:
| Client point | Mechanism | Permanent fix |
|---|---|---|
| Dupont connectors | vibration backs them out → intermittent open | locking connectors (JST-XH/Molex), no jumpers |
| Solder-joint stress | flex + heat-cycling cracks joints | PCB with strain relief; no dead-bug wiring |
| No vibration design | resonance loosens everything | PCB + standoffs + grommets + conformal coat |
| Loose terminals/antennas | SMA + screw terminals work loose | torqued bulkhead SMA, retained connectors |
| 4G NTRIP card failures | thermal + mechanical + no recovery | thermal fix + v2 recovery + qualified modem |
| Hard SIM swaps | SIM buried in the stack | front-panel SIM access / eSIM |
| "Not apt for production" | hand-wired one-offs | designed PCB, documented, repeatable |
None of this is solved by firmware. It's the core of the **PCB + ruggedization**
work in `03-Architecture-Hardware/hardware-respec-and-pcb-brief.md`.
---
## 6. Evidence still needed (fill this in)
> **Constraint (2026-06-19):** all units are pulled and sitting in **Veracruz**;
> team is in **Querétaro**. Hands-on items below are gated on **shipping to QRO**
> or **remote-hands** — see `06-Testing-Validation/remote-diagnostic-protocol.md`. These units are
> **v1 (no MCU thermal log)**, so thermal evidence = **physical inspection + bench
> power test**, not firmware logs.
- [ ] **Historical logs** (do from QRO now): battery V, fix quality, reset
timestamps; and the **htracker-online vs Wialon-offline** split per unit
- [ ] **Backend**: anti-jump warnings spamming a `cell_id` + `netstat -su` UDP
drops around the death window (`02-Architecture-Software/backend-review.md`)
- [ ] **Caster**: HelgenSRV NTRIP availability log
- [ ] **Carrier**: per-SIM data balance / suspension status (rules H2 in/out fast)
- [ ] **Fuse**: installed rating vs measured worst-case + inrush (H5)
- [ ] **4 pulled units (3 dead + 1 working-ish control)**: bench **current-draw**
on power-up, LED + USB-dashboard capture, photos of LiPo (swelling?), boost
(discolour?), solder, SMA. Genuinely-dead units → **teardown in QRO**
(modem power-cycle test, LiPo capacity, thermal-soak)
- [ ] **Thermal quantification**: thermocouple in a sealed box in full sun (can be
done with a spare enclosure in VER or QRO) to get a real interior-temp number
---
## 7. Provisional conclusion
The evidence now points to **at least two independent causes, not one**:
1. **Backend (H2/H2b) — confirmed in code.** The blocking retranslator can drop
the *whole fleet* at once on any upstream hiccup, and the anti-jump filter can
*permanently* black-hole an individual unit. Some — possibly much — of the
"deaths" and "recurrent disconnects" may be **server-side, with healthy radios
in the field.** This is testable for free (htracker-online vs Wialon-offline)
and must be done before/alongside the teardown.
2. **Hardware/thermal (H1/H7/H3/H4).** A heat event on the LiPo/modem/boost, on
top of a vibration-fragile prototype, remains the leading *physical* cause for
units that are genuinely dead — confirmed once units are pulled (swelling,
discolour, cracked joints) and from the v2 temperature log.
3. **Firmware (H6)** amplifies real blips into long outages — fixed in v2.
The permanent fix is deliberately robust to all of them: **backend hardening
(async + cell_id identity + fixed anti-jump)** + thermal-mechanical redesign +
ruggedized interconnect (PCB) + smarter firmware (v2) + verified fusing. Do not
spend the whole budget on a thermal redesign before confirming how much of the
problem is the server.

View File

@ -0,0 +1,89 @@
\section{Resumen ejecutivo}
Cinco unidades HBOX en Hutchison Ports ICAVE (Veracruz) operaron de forma intermitente y
luego fallaron de manera casi simultánea, con desconexiones recurrentes en la plataforma.
La evidencia apunta a \textbf{al menos dos causas independientes}, no una sola:
(1) defectos de \textbf{backend} confirmados en el código que pueden sacar de la plataforma
a unidades sanas, y (2) un \textbf{evento térmico} sobre un prototipo frágil. El watchdog
(firmware) amplificaba cada interrupción. Este informe es preliminar hasta ingerir los
registros de campo.
\section{Las dos fallas son, probablemente, fallas distintas}
\begin{itemize}
\item \textbf{(A) Muerte súbita y casi simultánea de la flota.} Apunta a un factor de
\textbf{modo común} (no a cinco fallas coincidentes): un golpe de calor, o un corte de
backend/SIM/caster.
\item \textbf{(B) Desconexiones recurrentes a lo largo del tiempo.} Intermitentes por
vibración (Dupont/SMA/soldadura), bloqueos de la tarjeta 4G y un watchdog que se recupera
lento y ruidoso.
\end{itemize}
\section{Diagnóstico diferencial (lo barato primero)}
\hwtab{%
\begin{tabularx}{\textwidth}{@{}>{\bfseries}l X >{\centering\arraybackslash}p{2.2cm}@{}}
\toprule
\rowcolor{HelgenIndigo}\textcolor{white}{Hipótesis} & \textcolor{white}{Mecanismo} & \textcolor{white}{Prioridad}\\
\midrule
Backend (modo común) & El \textit{retranslator} espera el ACK TCP dentro del único bucle UDP $\rightarrow$ una lentitud aguas arriba tira a toda la flota. & Muy alta\\
Térmico & Carcasa PLA dentro de gabinete ABS IP67 sellado con la fuente Mean Well dentro: trampa de calor anidada. & Muy alta\\
Filtro anti-salto & Bloquea de forma permanente a una unidad sana (no actualiza referencia; sobrevive reconexiones). & Alta\\
Vibración & Dupont/SMA/soldadura se aflojan $\rightarrow$ intermitencias. & Alta\\
SIM/caster & Saldo de datos agotado o caster NTRIP caído. & Media\\
Fusible sub-dimensionado & Posible disparo bajo inrush/carga real (sospecha del cliente). & Media\\
\bottomrule
\end{tabularx}}
\begin{helgennotice}[Prueba gratuita primero]
Una unidad que aparece \textbf{en línea en el monitor de Helgen pero fuera de línea en la
plataforma del cliente} es \textbf{víctima del backend, no hardware muerto}. Verificar esto
en los registros históricos no cuesta nada y puede exonerar al hardware.
\end{helgennotice}
\section{Hipótesis térmica (reforzada por el BOM)}
Es una \textbf{trampa de calor anidada}: una carcasa \textbf{PLA} (ablanda $\sim$50–60\,\textdegree C)
sellada dentro de un gabinete \textbf{ABS IP67} que además aloja la fuente Mean Well (fuente
interna de calor), bajo sol pleno. El PLA puede \textbf{deformarse} antes de que ningún chip
esté sobre-temperatura. El ZED-F9P está calificado a $-40$\,a\,$+85$\,\textdegree C; los
afectados por calor son el \textbf{18650/PowerBoost}, el \textbf{módem 4G} y la \textbf{carcasa
PLA}, no el receptor. Un \textbf{ventilador dentro de una caja sellada no la enfría}; el calor
debe conducirse afuera (o se rompe el IP67).
\section{Backend — defectos confirmados en el código}
\begin{itemize}
\item \textbf{Recuperación mal dirigida (5 min).} El v1 envía un \textit{reset} del GNSS,
que no revive un módem 4G colgado (la falla dominante reportada).
\item \textbf{Reinicio duro sin \textit{backoff} (10 min).} Si la tarjeta 4G está colgada, la
unidad se reinicia cada $\sim$10 min indefinidamente $\rightarrow$ desconexiones recurrentes.
\item \textbf{Filtro anti-salto.} Puede dejar a una unidad fuera de la plataforma de forma
permanente. (Ambos corregidos en el firmware v2 y en el plan de backend.)
\end{itemize}
\section{La observación del cliente es correcta}
ICAVE describe correctamente un prototipo de laboratorio en un entorno industrial brutal:
conectores Dupont, estrés en soldaduras, sin diseño para vibración, conectores flojos,
fallas recurrentes de la tarjeta 4G, baja mantenibilidad de SIM y electrónica «no apta para
producción». Nada de esto se resuelve por software: es el núcleo del trabajo de
\textbf{ruggedización + PCB} (Serie 2).
\section{Conclusión provisional}
Lo más probable es un \textbf{evento térmico} sobre un \textbf{prototipo frágil}, con el
\textbf{backend} amplificando cada interrupción —— a menos que la muerte simultánea resulte
ser un \textbf{modo común de backend/SIM/caster}, que debe descartarse primero por ser lo más
barato. El plan de corrección es robusto a todo lo anterior: rediseño térmico-mecánico
(sin PLA, sin batería en el punto caliente, conducción de calor), interconexión ruggedizada
(PCB), firmware más inteligente (v2), fusible verificado y \textbf{endurecimiento del backend}.
\section{Evidencia pendiente}
\begin{itemize}
\item Registros de campo (V de batería, calidad de fix, marcas de tiempo de reinicio) y la
comparación monitor-Helgen vs.\ plataforma del cliente.
\item Backend: \textit{logs} del \textit{retranslator} + caídas de buffer UDP en la ventana de la falla.
\item Unidades retiradas: fotos de PLA deformado, batería inflada, soldaduras; prueba de banco del módem; soak térmico.
\end{itemize}

Binary file not shown.

View File

@ -0,0 +1,18 @@
% Helgen Drogheda — RCA / Informe de Falla de Campo · DRG-RCA-001
\documentclass[11pt]{article}
\newcommand{\sistema}{Helgen Drogheda — HBOX RTK}
\newcommand{\sistemapdf}{Helgen Drogheda HBOX RTK}
\newcommand{\doctipo}{RCA — Informe de Falla de Campo}
\input{../../_brand/estilo-helgen.tex}
\begin{document}
\helgenportada{Análisis de Causa Raíz (RCA)}{Fallas de campo de la flota HBOX — ICAVE, Veracruz}{%
\begin{tabular}{@{}ll@{}}
\textbf{Documento} & DRG-RCA-001 — RCA / Informe de Falla\\
\textbf{Versión} & 1.0 (Borrador — pendiente de registros)\\
\textbf{Fecha} & 20 de junio de 2026\\
\textbf{Autor} & Dr.\ Leopoldo Rodríguez Salazar — Helgen Technologies\\
\textbf{Clasificación} & Confidencial — uso de Hutchison Ports y Helgen\\
\textbf{Asistencia IA} & Elaborado con asistencia de Claude (Anthropic), revisado por el autor\\
\end{tabular}}
\input{cuerpo.tex}
\end{document}

View File

@ -0,0 +1,486 @@
% Helgen Technologies — HBOX RTK · RCA + Plan de Robustez (Project Drogheda)
% Audiencia: ICAVE (cliente, supervisor tecnico). Tono: responsable, tecnico, honesto.
% Compilar con XeLaTeX: latexmk -xelatex -interaction=nonstopmode <file>.tex
\documentclass[10pt,aspectratio=169]{beamer}
\usetheme[progressbar=frametitle, numbering=fraction, block=fill]{metropolis}
% ---------- Paleta oficial Helgen (Guia de Identidad v1.5, 2026) ----------
\definecolor{HelgenIndigo}{HTML}{2C3C5B} % Indigo — color primario
\definecolor{HelgenCyan}{HTML}{51F3DA} % Verde cian — acento
\definecolor{HelgenTeal}{HTML}{17A08C} % derivado: cian oscurecido para TEXTO sobre blanco
\definecolor{HelgenMist}{HTML}{EFF1F5} % derivado: tinte indigo 8% para rellenos
\definecolor{HelgenAmber}{HTML}{C0392B} % rojo de alerta (umbrales/peligro)
\setbeamercolor{normal text}{fg=HelgenIndigo, bg=white}
\setbeamercolor{alerted text}{fg=HelgenTeal}
\setbeamercolor{frametitle}{bg=HelgenIndigo, fg=white}
\setbeamercolor{progress bar}{fg=HelgenCyan, bg=HelgenMist}
\setbeamercolor{title separator}{fg=HelgenCyan}
\setbeamercolor{block title}{bg=HelgenMist, fg=HelgenIndigo}
\setbeamercolor{block title alerted}{bg=HelgenIndigo, fg=white}
% ---------- Fuentes de marca + idioma ----------
\usepackage{iftex}
\ifXeTeX
\usepackage{fontspec}
\IfFontExistsTF{League Spartan}{%
\newfontfamily\helgentitle{League Spartan}[BoldFont={* Bold}]
\setbeamerfont{title}{family=\helgentitle, series=\bfseries}
\setbeamerfont{frametitle}{family=\helgentitle, series=\bfseries}
\setbeamerfont{section title}{family=\helgentitle, series=\bfseries}
\setbeamerfont{standout}{family=\helgentitle, series=\bfseries}
}{}
\IfFontExistsTF{MADE Evolve Sans}{\setsansfont{MADE Evolve Sans}}{}
\else
\usepackage[T1]{fontenc}
\fi
% babel-spanish no esta en este TeX Live; XeLaTeX maneja acentos UTF-8 nativamente.
\usepackage{booktabs}
\usepackage{graphicx}
\usepackage{array}
\newcolumntype{L}[1]{>{\raggedright\arraybackslash}p{#1}}
\usepackage{tikz}
\usetikzlibrary{arrows.meta, positioning, shapes.misc, fit, backgrounds}
\usepackage{listings}
\lstset{
basicstyle=\ttfamily\scriptsize,
keywordstyle=\color{HelgenIndigo}\bfseries,
commentstyle=\color{gray},
stringstyle=\color{HelgenTeal},
showstringspaces=false, breaklines=true, frame=single,
framerule=0.2pt, rulecolor=\color{HelgenMist}
}
% ---------- Estilos TikZ ----------
\tikzset{
box/.style={rounded rectangle, draw=HelgenIndigo, fill=HelgenMist,
font=\scriptsize, minimum height=7mm, inner xsep=2mm, align=center},
st/.style={rounded rectangle, draw=HelgenIndigo, fill=HelgenMist,
font=\scriptsize, minimum height=7mm, inner xsep=2mm, align=center},
hot/.style={rounded rectangle, draw=HelgenAmber, fill=HelgenMist, very thick,
font=\scriptsize, minimum height=7mm, inner xsep=2mm, align=center},
arr/.style={-{Stealth}, thick, HelgenTeal},
arrb/.style={-{Stealth}, thick, HelgenIndigo}
}
% ---------- Vinetas + separador (la fuente de marca no trae bullet ni middot) ----------
\setbeamertemplate{itemize item}{\textcolor{HelgenTeal}{\rule[0.25ex]{0.85ex}{0.85ex}}}
\setbeamertemplate{itemize subitem}{\textcolor{HelgenTeal}{\rule[0.25ex]{0.6ex}{0.6ex}}}
\setbeamertemplate{itemize subsubitem}{\textcolor{HelgenTeal}{\rule[0.25ex]{0.5ex}{0.5ex}}}
\newcommand{\sep}{\raisebox{0.35ex}{\rule{0.45ex}{0.45ex}}}
% Pie de figura con ancho acotado (evita desbordes al centrar texto largo)
\newcommand{\figcap}[1]{\par\vspace{1.2mm}\centerline{\begin{minipage}{0.92\textwidth}\footnotesize\centering #1\end{minipage}}}
% ---------- Pie de pagina ----------
\setbeamertemplate{frame footer}{%
\scriptsize\color{gray}\insertshortauthor\ \sep\ \insertshorttitle\ \sep\ Confidencial — Helgen Servicios Especializados}
% ---------- Metadatos ----------
\title[RCA HBOX RTK \sep\ ICAVE]{HBOX RTK — Análisis de Causa Raíz y Plan de Robustez}
\subtitle{Fallas de campo en ICAVE (Veracruz) \sep\ Camino a HBOX v3.0}
\author[Helgen Technologies]{Helgen Technologies — Ingeniería de Producto}
\date{22 de junio de 2026}
\begin{document}
\maketitle
\begin{frame}{Agenda}
\setbeamertemplate{section in toc}[sections numbered]
\tableofcontents[hideallsubsections]
\end{frame}
% =====================================================================
\section{Contexto y hallazgo principal}
\begin{frame}{¿Qué pasó?}
\begin{itemize}
\item 5 HBOX RTK instalados en equipo pesado de ICAVE (RTG, reach stackers, montacargas, tractoplana).
\item Operación intermitente desde el arranque y, después, una \alert{falla casi simultánea} en toda la flota.
\item En paralelo, durante semanas: \alert{desconexiones recurrentes} en Wialon.
\item Escalamiento del cliente (17 jun). Unidades retiradas: 4 en mano (3 fuera de servicio, 1 funcional) en Veracruz; equipo en Querétaro.
\end{itemize}
\vfill
\begin{alertblock}{Lo primero: transparencia}
Nuestra primera hipótesis fue el calor. El análisis con datos revela un cuadro más complejo, y este informe \textbf{corrige el diagnóstico con evidencia}.
\end{alertblock}
\end{frame}
\begin{frame}{Hallazgo principal}
\begin{itemize}
\item \textbf{No hay una causa única.} La evidencia apunta a \alert{al menos dos causas independientes}, amplificadas por una tercera.
\item \textbf{(1) Backend} — \alert{confirmado en el código}: puede retirar de Wialon a unidades que en realidad están \emph{sanas}.
\item \textbf{(2) Hardware / térmico} — causa física probable de las unidades genuinamente dañadas (a confirmar con desmontaje + termopar).
\item \textbf{(3) Firmware} — convierte interrupciones breves en cortes prolongados (ya corregido en v2).
\end{itemize}
\vfill
\begin{block}{Una consecuencia favorable}
Varias unidades reportadas como «caídas» pueden tener el \textbf{radio sano} y ser \textbf{víctimas del servidor}: esa parte es de corrección inmediata y de bajo costo.
\end{block}
\end{frame}
\begin{frame}{En una línea}
\begin{block}{Síntesis}
\large Los radios \textbf{funcionan}: entregamos posición GPS estándar (SPS) de forma confiable, \alert{todavía no RTK}. Los frentes por cerrar son la \textbf{fuente de correcciones}, la \textbf{observabilidad}, el \textbf{térmico} y la \textbf{vibración} — los cuatro, en corrección.
\end{block}
\end{frame}
% =====================================================================
\section{Metodología de diagnóstico}
\begin{frame}{Cómo diagnosticamos}
\begin{itemize}
\item \textbf{Principio:} validar primero las causas \alert{más probables y de comprobación inmediata} —sin desmontar unidades— antes que las hipótesis exóticas.
\item \textbf{3 vistas de log independientes por unidad:} receptor u-blox (\texttt{.ubx}), consola del 4G-NTRIP-Master (\texttt{.log}) y captura del retranslator en el servidor (\texttt{tcpdump}).
\item \textbf{+} archivos de configuración (caster NTRIP y ZED-F9P) \textbf{+} un \alert{baseline sano} de referencia (\texttt{RTK14\_440}).
\item \textbf{Herramientas propias} (\texttt{hbox-log-tools}): Log Explorer en navegador, analizador por lotes y dashboard.
\end{itemize}
\end{frame}
\begin{frame}{Diagnóstico diferencial (H1–H8)}
\centering\scriptsize
\setlength{\tabcolsep}{4pt}\renewcommand{\arraystretch}{1.15}
\begin{tabular}{@{}c L{0.42\textwidth} c c L{0.18\textwidth}@{}}
\toprule
\# & Hipótesis & A: simult. & B: descon. & Estado / Prior \\
\midrule
H1 & Térmico (batería / booster / módem en gabinete sellado + Mean Well) & Sí & Sí & Alto \\
H2 & Backend HOL (retranslator bloqueante) & Sí & Sí & \textbf{Confirmado (código)} \\
H2b & Filtro anti-salto (bloqueo permanente) & No & Sí & \textbf{Confirmado (código)} \\
H3 & Vibración (Dupont / SMA / soldadura) & No & Sí & Alto \\
H4 & Módulo 4G NTRIP (cuelgue / falla) & Parc. & Sí & Alto \\
H5 & Fusible sub-dimensionado & Sí* & Parc. & Medio \\
H6 & Lógica de watchdog (reset / loop) & No & Sí & Alto (para B) \\
H7 & Brownout de batería (caída de tensión por calor) & Sí & Sí & Medio-alto \\
H8 & Riel de potencia / EMI de la máquina & Parc. & Sí & Medio \\
\bottomrule
\end{tabular}
\vfill
\footnotesize Las dos comprobaciones \alert{más rápidas} (backend H2, fusible H5) se confirman en una hora, \textbf{sin desmontar una sola unidad}.
\end{frame}
% =====================================================================
\section{Evidencia: los datos}
\begin{frame}{Dos fallas, probablemente \emph{dos} causas}
\begin{columns}[T]
\column{0.5\textwidth}
\textbf{(A) Falla casi simultánea}
\begin{itemize}\small
\item Apunta a un estresor de \alert{modo común}.
\item Candidatos: calor, backend / caster / SIM, servidor.
\end{itemize}
\column{0.5\textwidth}
\textbf{(B) Desconexión crónica}
\begin{itemize}\small
\item Apunta a \alert{intermitencias}.
\item Candidatos: vibración, cuelgue del 4G, watchdog lento.
\end{itemize}
\end{columns}
\vfill
\begin{alertblock}{Por qué no reducirlo todo a «calor»}
Atribuir todo al calor pierde una corrección barata de \textbf{backend}, y un rediseño térmico \textbf{no} detendría las intermitencias por \textbf{vibración}. Se atienden ambas.
\end{alertblock}
\end{frame}
\begin{frame}{Ninguna unidad alcanzó RTK}
\centering
\includegraphics[width=\linewidth,height=0.76\textheight,keepaspectratio]{figs/fig_fix_compare.pdf}
\vfill
\figcap{Todas las capturas en \textbf{SPS (calidad 1)}; \alert{cero en RTK (4/5)}. El posicionamiento centimétrico nunca se entregó.}
\end{frame}
\begin{frame}{El receptor está sano — el problema no es el ZED-F9P}
\centering
\includegraphics[width=\linewidth,height=0.76\textheight,keepaspectratio]{figs/fig_baseline_gnss.pdf}
\vfill
\figcap{Baseline \texttt{RTK14\_440}: \textbf{12 satélites, HDOP 0.75}, C/N0 sano — pero atrapado en SPS. El receptor funciona.}
\end{frame}
\begin{frame}{La entrega extremo-a-extremo es sana}
\centering
\includegraphics[width=\linewidth,height=0.74\textheight,keepaspectratio]{figs/fig_delivery.pdf}
\vfill
\figcap{\texttt{RTK14\_440}: \textbf{984 paquetes, 1 Hz estable, cero caídas} al servidor. Lección clave: \alert{«entrega sana» $\neq$ «RTK»}.}
\end{frame}
\begin{frame}{Las correcciones son marginales}
\centering
\includegraphics[width=\linewidth,height=0.70\textheight,keepaspectratio]{figs/fig_ntrip_delay.pdf}
\vfill
\figcap{\textbf{Causa:} caster público gratuito \texttt{rtk2go} (sin SLA) + configuración del F9P \alert{solo en RAM} + cielo interior (banco). El cliente NTRIP rara vez alcanza el estado «conectado».}
\end{frame}
\begin{frame}{La trampa diagnóstica (y una rectificación)}
\begin{itemize}
\item La \textbf{consola del 4G-NTRIP-Master no es un indicador de salud confiable}: el baseline \emph{sano} registra la misma consola escasa que las unidades que «parecían» falladas.
\item \alert{Rectificación:} la supuesta «falla de ingest en master-14» queda \textbf{retirada} — master-14 \emph{es} el baseline y demuestra reenvío.
\item \textbf{Solo master-21} es una falla confirmada (el módem nunca registra: \texttt{0xFFFFF} + 511 errores del parser AT).
\end{itemize}
\vfill
\begin{block}{Lección}
La salud por unidad debe provenir de un componente que \textbf{interpreta el fix} —el Blackpill— registrado en un formato confiable.
\end{block}
\end{frame}
% =====================================================================
\section{Causas raíz}
\begin{frame}{Térmico (H1 / H7) — qué es real y qué falta probar}
\begin{itemize}
\item \textbf{Trampa térmica anidada:} el HBOX va sellado dentro de un gabinete ABS IP67 que \emph{además} aloja la fuente Mean Well, a pleno sol en Veracruz (interior estimado 60–80\,°C).
\item \textbf{La carcasa ASA fue una elección correcta:} UV-estable y reblandece a $\sim$95–100\,°C, con margen frente a esa temperatura. \alert{El gabinete no es el eslabón débil.}
\item \textbf{El eslabón débil son los componentes de potencia de grado prototipo} en ese espacio caliente: batería 18650 (carga $\leq$45\,°C $\rightarrow$ caída de tensión / hinchamiento), PowerBoost 1000C (booster \emph{hobby}) y el módem 4G (throttling térmico).
\item El \textbf{ZED-F9P} ($-40\ldots+85$\,°C) \alert{no} es la víctima.
\end{itemize}
\vfill
\begin{alertblock}{Honesto}
En banco las fallas se reproducen \textbf{en frío} $\rightarrow$ el calor \emph{no} explica lo observado en estos logs. La hipótesis térmica de \textbf{campo} sigue \alert{abierta} (falta soak con termopar).
\end{alertblock}
\end{frame}
\begin{frame}[fragile]{Backend (H2 / H2b) — confirmado en el código}
\begin{itemize}\small
\item \textbf{H2:} el envío a Wialon (con \texttt{connect} de 20\,s y espera de ACK) ocurre \alert{dentro} del único bucle UDP $\rightarrow$ un upstream lento detiene el bucle y \textbf{toda la flota cae a la vez}.
\item \textbf{H2b:} el filtro anti-salto no actualiza su referencia tras un «salto» rechazado $\rightarrow$ una unidad sana queda \alert{fuera de Wialon de forma permanente}.
\end{itemize}
\begin{lstlisting}
# retranslator.py (defecto): envio sincrono en el bucle de recepcion
for pkt in udp_recv_loop(): # un solo hilo, toda la flota
wialon.connect(timeout=20) # bloquea a TODOS si el upstream tarda
wialon.send(pkt); wialon.wait_ack() # head-of-line stall
\end{lstlisting}
\footnotesize Triage \alert{sin costo}: ONLINE en htracker pero OFFLINE en Wialon $\Rightarrow$ víctima del backend, no hardware dañado.
\end{frame}
\begin{frame}{Firmware (H6) — convierte una interrupción breve en un corte largo}
\begin{itemize}
\item v1 reacciona a la pérdida de correcciones con un \texttt{hot-reset} del \textbf{GNSS} (\texttt{UBX-CFG-RST}) $\rightarrow$ reinicia el ZED-F9P, \alert{no} el módem 4G colgado (la falla \#1 reportada). 5 min en una acción sin efecto.
\item v1: reset duro cada $\sim$10 min \alert{sin backoff} $\rightarrow$ \textbf{ciclo de reinicios}; cada ciclo equivale a varios minutos de desconexión. Una flota así produce «desconexiones recurrentes».
\end{itemize}
\vfill
\begin{block}{v2 (ya disponible)}
Recupera \textbf{ciclando el stack 4G}, detecta silencio de NMEA en $\sim$60\,s, aplica \textbf{backoff exponencial} y cae a \textbf{modo LIMP} (sigue reportando GPS estándar) en lugar de desaparecer.
\end{block}
\end{frame}
\begin{frame}{La revisión de ICAVE es correcta (H3 / H4)}
\centering\scriptsize
\setlength{\tabcolsep}{4pt}\renewcommand{\arraystretch}{1.18}
\begin{tabular}{@{}L{0.27\textwidth}L{0.33\textwidth}L{0.32\textwidth}@{}}
\toprule
Punto del cliente & Mecanismo & Corrección permanente \\
\midrule
Conectores Dupont & la vibración los afloja $\rightarrow$ apertura intermitente & conectores con seguro (JST / Molex) \\
Esfuerzo en soldaduras & flexión + ciclos térmicos los agrietan & PCB con alivio de esfuerzos \\
Sin diseño anti-vibración & la resonancia afloja todo & PCB + standoffs + grommets + coat \\
SMA / terminales flojos & se aflojan con la vibración & SMA con torque, conectores retenidos \\
Fallas de la tarjeta 4G & térmico + mecánico + sin recuperación & fix térmico + v2 + módem calificado \\
Cambio de SIM difícil & SIM enterrada en el stack & SIM en panel frontal / eSIM \\
«No apto para producción» & cableado a mano, piezas únicas & PCB diseñada, documentada, repetible \\
\bottomrule
\end{tabular}
\vfill
\footnotesize Nada de esto se resuelve con firmware: es el núcleo del trabajo de \textbf{PCB + ruggedización}.
\end{frame}
% =====================================================================
\section{Acciones ya tomadas}
\begin{frame}{Backend — reescrito}
\begin{itemize}
\item Retranslator + monitor: \alert{envío asíncrono}, sin bloqueo de cabeza de línea (HOL).
\item \textbf{Identidad por \texttt{cell\_id}} (no por orden de llegada) $\rightarrow$ la trazabilidad por unidad deja de depender del orden de los paquetes.
\item Filtro anti-salto \textbf{corregido}: ya no deja fuera de forma permanente a una unidad sana.
\end{itemize}
\vfill
\begin{block}{Resultado esperado}
Una unidad sana \textbf{ya no puede quedar fuera por el servidor}. Esto descarta una de las dos causas de modo común antes de la próxima prueba de campo.
\end{block}
\end{frame}
\begin{frame}{Observabilidad — Blackpill v2.1 + herramientas}
\begin{itemize}
\item \textbf{Spec de logging v2.1:} reemplaza el dashboard ANSI auto-sobrescrito por un flujo \alert{estructurado, con marca de tiempo y parseable}: estado, fix, paquetes, batería, \textbf{temperatura del MCU}, recuperación y \textbf{causa del reset}.
\item \textbf{\texttt{hbox-log-tools}:} Log Explorer (parseo + graficado automático + comparación contra baseline + export PNG/SVG), analizador por lotes y dashboard.
\end{itemize}
\vfill
\begin{block}{Efecto}
La salud por unidad deja de ser ambigua: proviene de la \textbf{propia unidad}, no de una consola que no es confiable.
\end{block}
\end{frame}
\begin{frame}[fragile]{GNSS / correcciones — persistencia y caster}
\begin{itemize}\small
\item Persistir la configuración del F9P en \alert{FLASH} (hoy es \textbf{solo RAM} $\rightarrow$ se pierde en un ciclo de energía y rompe el feed de forma silenciosa).
\item Migrar las correcciones de \texttt{rtk2go} (gratuito, sin SLA) a un \textbf{caster dedicado (HelgenSRV)} con disponibilidad garantizada.
\item Reverificar el switch \texttt{XBEE$\leftrightarrow$UART2} del simpleRTK2B Pro.
\end{itemize}
\begin{lstlisting}
; Persistir configuracion en BBR + FLASH (no solo RAM) -- ilustrativo
UBX-CFG-CFG saveMask = ioPort | msgConf | navConf
devices = devBBR | devFlash
; GGA en UART2 @1 Hz -> verificado correcto
\end{lstlisting}
\end{frame}
% =====================================================================
\section{Camino a HBOX v3.0}
\begin{frame}{Estrategia fásica}
\begin{columns}[T]
\column{0.5\textwidth}
\begin{block}{Fase 1 — Ruggedizar ahora}
\small Sobre los módulos actuales, sin esperar la PCB: eliminar Dupont, buck industrial 24$\rightarrow$5\,V $\geq$3\,A (retira el PowerBoost), fijación mecánica + conformal coat, SIM frontal, conducción térmica al plato + parasol, flashear v2.
\end{block}
\column{0.5\textwidth}
\begin{block}{Fase 2 — PCB de producción}
\small Una placa \emph{carrier} industrial que reemplaza todo el cableado a mano y las placas \emph{hobby}. La corrección \textbf{de fondo}, en paralelo.
\end{block}
\end{columns}
\vfill
\footnotesize Estabilización «permanente-suficiente» de la flota \textbf{ahora}; la PCB se fabrica en paralelo.
\end{frame}
\begin{frame}{Térmico v3.0 — gestión activa con ducto impermeable}
\begin{itemize}
\item \textbf{Hecho de ingeniería:} un ventilador dentro de una caja \emph{sellada} no enfría — solo agita el aire atrapado.
\item \textbf{v3.0:} un \alert{ducto impermeable} (membrana hidrófoba tipo Gore / laberinto sellado) crea una trayectoria de aire controlada que \emph{sí} extrae calor \textbf{sin comprometer el ingreso} (agua / polvo / salitre).
\item \textbf{Control inteligente del ventilador} gobernado por el Blackpill: sensa la temperatura interior $\rightarrow$ modula el flujo.
\item \textbf{La carcasa ASA se mantiene} (es adecuada); el foco es \textbf{conducir el calor} al plato metálico, reducir la ganancia solar (parasol) y bajar el calor interno (buck eficiente, retirar / reubicar la batería).
\end{itemize}
\vfill
\footnotesize \alert{Abierto:} definir la membrana / ducto y validar con soak de termopar. Opcional: gabinete metálico como disipador.
\end{frame}
\begin{frame}{PCB de producción (Fase 2) — diagrama de bloques}
\centering
\begin{tikzpicture}[node distance=4.5mm and 6mm]
\node[box] (vin) {24\,V gabinete\\(ya protegido)};
\node[box, right=of vin] (prot) {TVS +\\FET anti-inv.};
\node[box, right=of prot] (buck) {Buck 24$\rightarrow$5\,V\\$\geq$3\,A};
\node[box, right=of buck] (rail) {Riel 5\,V};
\node[box, above=of rail] (mcu) {STM32\\watchdog};
\node[box, right=of rail] (sw) {Load switch\\/ eFuse};
\node[box, right=of sw] (stack){Stack RTK\\+ 4G};
\node[box, below=of buck] (sense){Sense: Vin / 5\,V / batt / \alert{temp}};
\draw[arrb] (vin)--(prot); \draw[arrb] (prot)--(buck); \draw[arrb] (buck)--(rail);
\draw[arrb] (rail)--(sw); \draw[arrb] (sw)--(stack);
\draw[arr] (rail)--(mcu); \draw[arr] (mcu) -| (sw) node[midway,above,font=\tiny,text=HelgenIndigo]{enable};
\draw[arr] (sense)-- (mcu.south west);
\draw[arr] (mcu.east) -- ++(0.6,0) |- (stack.north) node[near start,above,font=\tiny,text=HelgenIndigo]{UART/UBX};
\end{tikzpicture}
\vfill
\footnotesize Conectores con seguro \sep\ SMA bulkhead con torque \sep\ SIM en panel frontal / MFF2 \sep\ test points en cada riel.
\end{frame}
\begin{frame}{Vibración — mitigación del módulo 4G NTRIP}
\begin{itemize}
\item Standoffs con \textbf{aisladores / grommets}; retención del módulo XBee 4G en su socket con bracket o clip.
\item Conformal coat del cableado discreto; conectores con seguro; SMA con thread-lock + torque.
\end{itemize}
\vfill
\begin{alertblock}{Abierto — en definición}
Definir el \textbf{esquema de amortiguación específico del módulo 4G} (montaje aislado vs. retención rígida), a confirmar con una \textbf{prueba de vibración} controlada. Este es el punto que todavía estamos cerrando.
\end{alertblock}
\end{frame}
\begin{frame}{Watchdog — FSM de recuperación (v2 $\rightarrow$ v3)}
\centering
\begin{tikzpicture}[node distance=8mm and 14mm]
\node[st] (normal) {NORMAL\\(reporta RTK/SPS)};
\node[hot, right=of normal] (cycle) {CICLAR STACK 4G\\(backoff exp.)};
\node[st, right=of cycle] (limp) {LIMP\\(GPS estándar,\\sigue reportando)};
\node[st, below=of cycle] (temp) {SENSAR TEMP\\$\rightarrow$ CTRL VENTILADOR};
\draw[arr] (normal) -- node[above,font=\tiny,text=HelgenIndigo]{silencio NMEA 60\,s} (cycle);
\draw[arr] (cycle) -- node[above,font=\tiny,text=HelgenIndigo]{persiste} (limp);
\draw[arr] (limp.north) to[bend right=35] node[above,font=\tiny,text=HelgenIndigo]{enlace OK} (normal.north);
\draw[arr] (cycle.south west) to[bend right=20] node[left,font=\tiny,text=HelgenIndigo]{recupera} (normal.south);
\draw[arrb, dashed] (normal) -- (temp);
\draw[arrb, dashed] (temp) -- (limp);
\end{tikzpicture}
\vfill
\footnotesize Invariantes: \textbf{nunca desaparecer, nunca ciclo infinito}; el estado seguro por defecto es \alert{OFF}. v3 añade el lazo térmico (líneas punteadas).
\end{frame}
% =====================================================================
\section{Estado honesto y plan a una semana}
\begin{frame}{Dónde estamos — sin adornos}
\begin{columns}[T]
\column{0.34\textwidth}
\begin{block}{Confirmado}
\scriptsize
\begin{itemize}
\item Backend HOL + anti-salto (en código)
\item Receptores GNSS sanos; SPS, no RTK
\item Consola 4G no es indicador de salud
\item master-21: módem caído
\end{itemize}
\end{block}
\column{0.32\textwidth}
\begin{block}{Hipótesis}
\scriptsize
\begin{itemize}
\item Térmico de \textbf{campo} (falta termopar / soak)
\item Fusible sub-dimensionado
\item Brownout de batería
\end{itemize}
\end{block}
\column{0.34\textwidth}
\begin{alertblock}{Abierto}
\scriptsize
\begin{itemize}
\item ¿Resuelto? — pendiente reflash v2 + análisis en entorno controlado (QRO)
\item Anti-vibración del 4G
\item Fix RTK $\geq$4 con caster dedicado en cielo abierto
\end{itemize}
\end{alertblock}
\end{columns}
\end{frame}
\begin{frame}{Plan a una semana $\rightarrow$ viernes 26 de junio}
\centering\footnotesize
\setlength{\tabcolsep}{6pt}\renewcommand{\arraystretch}{1.25}
\begin{tabular}{@{}l L{0.78\textwidth}@{}}
\toprule
Ventana & Actividad \\
\midrule
Lun–Mar & Desplegar \textbf{watchdog v2} y analizar en \textbf{entorno controlado (QRO)}; persistir F9P en FLASH + caster dedicado \\
Mar–Mié & \textbf{Retest RTK en cielo abierto} (confirmar fix $\geq$4/5); kit de ruggedización Fase 1 sobre 1 unidad de referencia \\
Mié–Jue & Definir \textbf{ducto + ventilador inteligente} + esquema anti-vibración del 4G; soak térmico con termopar \\
\textbf{Vie 26} & \alert{Enviar la versión más robusta del HBOX a Veracruz} para continuar las pruebas \\
\bottomrule
\end{tabular}
\vfill
\footnotesize El objetivo de esta semana no es «cerrado»: es un \textbf{HBOX medible y más robusto} de vuelta en el campo.
\end{frame}
\begin{frame}{Compromisos — responsable y fecha}
\centering\footnotesize
\setlength{\tabcolsep}{6pt}\renewcommand{\arraystretch}{1.25}
\begin{tabular}{@{}lll@{}}
\toprule
Acción & Responsable & Fecha \\
\midrule
Watchdog v2 + análisis controlado (QRO) & Leo / Daniel & 23–24 jun \\
Persistir F9P en FLASH + caster dedicado & Gibran & 24 jun \\
Retest RTK en cielo abierto & Gibran & 25 jun \\
Kit de ruggedización Fase 1 (unidad ref.) & Adriana & 25 jun \\
Diseño ducto + fan + anti-vibración 4G & Leo & 25–26 jun \\
Envío del HBOX robusto a Veracruz & Adriana & 26 jun \\
\bottomrule
\end{tabular}
\end{frame}
% =====================================================================
\begin{frame}[standout]
Diagnóstico con datos.\\
Corrección en ejecución.\\[2mm]
{\normalsize HBOX v3.0 — más robusto — a prueba el \textbf{26 de junio}.}
\vfill
{\footnotesize Gracias \sep\ Preguntas}
\end{frame}
\end{document}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -0,0 +1 @@
Subproject commit cda046b9d4bef72f35e8fd8af7da846ae0200082

View File

@ -0,0 +1,180 @@
# Remote Diagnostic Protocol — Pulled HBOX Units
**Date:** 2026-06-19 · **Owner:** Leo Rodriguez
**Situation:** all HBOX units were **removed from the machines**. We have **4 units
in hand: 3 not working, 1 working-ish.** They are **physically in Veracruz**; the
team is in **Querétaro** → debugging is **remote** until they're shipped or someone
on-site acts as our hands.
**Firmware on these units = v1** (no MCU thermal log) → thermal evidence must come
from **physical inspection + a bench power test**, not from the firmware.
> The single working-ish unit is the **control**. Every measurement below should be
> taken on it too, so "bad" readings on the dead units have a baseline to compare
> against.
---
## 0. Decision gate (make this call first)
**Recommendation: ship all 4 units to Querétaro.** They are small, and remote-
debugging *dead* electronics is slow and rarely conclusive — a 1–2 day courier buys
full bench access (PSU with current limit, scope, u-center, reflash, thermal soak,
teardown). Bring the working-ish unit too; it's the reference.
Choose remote-hands **only if** shipping is blocked (client chain-of-custody, the
units must stay on-site, or courier delay is unacceptable). Confirm with ICAVE who
**owns** the units and whether they may leave the terminal.
| Path | When | Cost |
|---|---|---|
| **Ship to QRO** *(recommended)* | units may leave Veracruz | courier + 1–2 days |
| **Remote-hands in Veracruz** | units must stay; cooperative on-site tech exists | needs the protocol below + their time |
Either way, **start §2 (backend analysis) now from Querétaro** — it needs no units.
---
## 1. What we can / cannot learn remotely
| Question | Remote-hands? | How |
|---|---|---|
| Is the LiPo swollen / burnt / leaking? | ✅ | photo checklist (§3 Tier 0) |
| Connectors/SMA loose, solder cracked? | ✅ | photos + wiggle test |
| Does it draw current / boot on power? | ✅ | bench PSU **current reading** (§3) |
| LED state on power-up? | ✅ | film the LED, decode (§4) |
| Battery V, fix quality, state, packets | ✅ (Tier 1) | USB dashboard capture |
| Does the 4G modem attach / get a fix? | ✅ (Tier 1–2) | dashboard / u-center / remote desktop |
| Does it reach our gateway when powered? | ✅ | watch htracker for that unit's `cell_id` |
| Exact failed component / thermal soak | ❌ (needs bench) | **ship to QRO** |
So ~80% of triage is remotely achievable; only component-level confirmation and
thermal reproduction strictly require the units in QRO.
---
## 2. Do this NOW from Querétaro (no units needed)
The backend is on our own server — fully debuggable from QRO, and per
`02-Architecture-Software/backend-review.md` it can take healthy units off Wialon by itself.
1. **Historical online-vs-offline split.** For the days before removal, compare each
unit's **htracker** record (online on UDP receipt) against **Wialon** (online on
positions received). A unit that was **online in htracker but offline in Wialon**
was a **backend victim** (anti-jump lock-out / HOL stall), *not* dead hardware —
that reframes which "dead" units are actually hardware failures.
2. **Gateway logs around the death window:** look for the anti-jump warning
(`¡Filtro Anti-Salto Activado!`) spamming one `cell_id` (→ lock-out), and for
`netstat -su` UDP receive-buffer drops (→ HOL stall).
3. **Bench-reproduce with the working-ish unit** once it's powered (in VER via
remote-hands, or in QRO): point it at the gateway and watch whether the
retranslator forwards it cleanly or trips the filter.
4. Caster (HelgenSRV) uptime + **per-SIM data balance/suspension** for the window.
This is the highest-leverage work we can do today and it needs nobody in Veracruz.
---
## 3. Remote-hands protocol (tiered by what the on-site person has)
Give the on-site tech the matching tier. A ready-to-send Spanish version is in §6.
### Tier 0 — power + a phone camera (minimum)
For **each** unit, before powering:
1. **Photograph**: the whole inside, then close-ups of the **LiPo** (any swelling,
bulging, brown/black marks, leaks?), the **PowerBoost**, every **connector/Dupont**,
the **SMA antenna** joints, and any **discoloured/burnt** components.
2. **Wiggle test** (gently): do any Dupont/terminal/SMA connectors move or fall off?
3. **Bench power-on with a current-limited PSU** at the unit's normal voltage
(12 V or 24 V), current limit ~2 A:
- Read **inrush** then **steady current**. Record the number.
- **0 mA / trips at 0:** open circuit / dead input stage / blown fuse.
- **Hits the current limit / very hot:** short → **power off immediately**.
- **~50–150 mA steady, nothing else:** MCU only, stack not powering (load switch
/ boost / modem).
- **Normal-ish draw + LED activity:** boots — go to Tier 1.
- Compare every reading to the **working-ish unit**.
4. **Film the LED** for ~60 s from power-up (decode in §4).
### Tier 1 — Tier 0 + USB cable + a laptop
5. Plug the Blackpill **USB** into the laptop; open **PuTTY/TeraTerm at 115200**.
The v1 watchdog prints a live dashboard. **Capture 3–5 min** (log to file) and
send it. Read off: **BATTERY V**, **SYSTEM STATE**, **RTK FIX**, **PACKETS TX'd**,
PWR/IGN inputs. (No MCU-temp line — these are v1.)
6. Note whether **PACKETS TX'd increments** (GNSS+serial alive) and whether **RTK
FIX** ever reaches 4/5 (corrections arriving = 4G/NTRIP working).
### Tier 2 — Tier 1 + remote-desktop (AnyDesk/TeamViewer) into the laptop
7. We drive from QRO: read the live dashboard, run **u-center** on the ZED-F9P
(fix, sat levels, config), issue **AT commands** to the 4G modem (attach, signal),
and if useful **reflash v2** to gain thermal logging + better recovery.
---
## 4. LED quick-decode (v1)
| LED | State | Read as |
|---|---|---|
| Fast blink | BOOTING | powered, waiting for 4G/GNSS |
| Solid ON | RUNNING, vehicle power, RTK fixed | healthy |
| Heartbeat (1 s) | RUNNING on battery | on LiPo, 30 s rate |
| Slow blink | searching / no corrections | 4G/NTRIP not delivering |
| Off (with power applied) | SLEEP / dead | battery-critical logic **or** not booting |
"Off while powered from a good bench supply" on a unit that should boot = a strong
**no-boot / power-stage** signal — correlate with the current reading.
---
## 5. Per-unit triage decision tree
```
Power on bench PSU (current-limited)
├─ 0 mA ............................. dead input / blown fuse / open → ship to QRO
├─ hits limit / hot ................ SHORT (power off!) ............... → ship to QRO
└─ draws current
├─ no LED, no USB dashboard ...... MCU/boot dead .................... → ship to QRO
└─ boots (LED + dashboard)
├─ PACKETS not incrementing ... GNSS/serial dead ................ Tier 2 / ship
├─ FIX stuck 0/1 (never 4/5) .. 4G/NTRIP not delivering ......... check modem/SIM/antenna
└─ FIX 4/5 + packets OK ........ HARDWARE IS FINE ............... → backend victim (§2)
```
The last branch is the important one: a pulled unit that **boots, fixes, and
transmits on the bench is not the failure** — its disappearance from Wialon is then
backend (§2), and re-installing it unchanged would just repeat the problem.
---
## 6. Safety — heat-damaged LiPo (tell the on-site tech)
A swollen/puffy or hot LiPo is a **fire risk**. Do **not** charge it, puncture it, or
keep powering a unit whose battery is visibly swollen. Disconnect it, place it in a
**LiPo-safe / fireproof bag or a metal container**, keep it away from anything
flammable, and ventilate. Photograph first, then isolate. If it's hot, vented, or
smells: stop and isolate immediately.
---
## 6b. Spanish — instrucciones para el técnico en sitio (Tier 0/1)
> Para **cada** unidad: (1) **Fotos** del interior y de cerca de la **batería LiPo**
> (¿inflada, manchas cafés/negras, fuga?), del **PowerBoost**, de **cada conector
> Dupont**, de las **antenas SMA**, y de cualquier componente **quemado/decolorado**.
> (2) Prueba de **movimiento** suave: ¿algún conector se mueve o se cae? (3) Enciéndela
> con una **fuente de banco** a su voltaje normal (12 V/24 V), límite de corriente ~2 A,
> y anota la **corriente** (arranque y estable). Si llega al límite o se calienta,
> **apágala de inmediato** (corto). (4) **Graba el LED** ~60 s desde el encendido.
> (5) Si hay laptop: conecta el **USB** del Blackpill, abre **PuTTY/TeraTerm a 115200**
> y **graba 3–5 min** del tablero (BATERÍA V, ESTADO, RTK FIX, PAQUETES). **Seguridad:**
> una batería **inflada o caliente** es riesgo de incendio — no la cargues ni la
> perfores; aíslala en bolsa/recipiente seguro y ventila.
---
## 7. What to send back to Querétaro (capture checklist)
Per unit, labeled with its `cell_id`/machine: the **photos**, the **current reading**
(inrush + steady), the **LED video**, the **3–5 min USB dashboard capture**, and a
one-line note (boots? fix? packets?). With that, we can classify all 4 units against
the tree in §5 and the backend evidence in §2 — and decide which truly need teardown
in QRO.

View File

@ -0,0 +1,338 @@
#include <Arduino.h>
// --- Pin Map ---
#define PIN_BAT_SENSE PA0 // Battery (3.6V nominal)
#define PIN_5V_SENSE PA4 // Powerboost 5V Rail
#define PIN_IGN_SENSE PA5 // Extra Signal: 12V Ignition
#define PIN_PWR_CTRL PA1 // MOSFET to Power SimpleRTK2B
#define PIN_LED PC13 // Onboard LED (Active LOW)
#define PIN_EXT_LED PB0 // External LED (via MOSFET)
// --- Constants & Thresholds ---
const float ADC_REF = 3.3;
const float DIVIDER_RATIO = 2.0;
const float BAT_CRITICAL = 3.4;
const float BAT_RECOVERY = 3.7;
// --- Timing (ms) ---
const unsigned long SOFT_RESET_DELAY = 300000; // 5 mins
const unsigned long HARD_RESET_DELAY = 600000; // 10 mins total
const unsigned long BOOT_STABILIZE_TIME = 15000; // Time for 4G/GNSS to boot
const unsigned long POWER_CYCLE_TIME = 5000; // 5 seconds to drain caps on Hard Reset
// --- UBX Commands ---
// UBX-CFG-RATE: measRate = 1000ms, navRate = 1 (1Hz Output)
const byte UBX_1HZ[] = {0xB5, 0x62, 0x06, 0x08, 0x06, 0x00, 0xE8, 0x03, 0x01, 0x00, 0x01, 0x00, 0x01, 0x39};
// UBX-CFG-RATE: measRate = 1000ms, navRate = 30 (30s Output)
const byte UBX_30S[] = {0xB5, 0x62, 0x06, 0x08, 0x06, 0x00, 0xE8, 0x03, 0x1E, 0x00, 0x01, 0x00, 0x1E, 0xAD};
// UBX-CFG-RST: Hot Start / Software Reset
const byte UBX_SOFT_RESET[] = {0xB5, 0x62, 0x06, 0x04, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x0F, 0x66};
// --- State Variables ---
enum SystemState { BROWNOUT, BOOTING, RUNNING, HARD_RESETTING };
SystemState currentState = BROWNOUT;
int fixQuality = 0;
int satellites = 0;
unsigned long rxCount = 0;
float vBatF = 3.7, v5vF = 5.0;
unsigned long lastDashUpdate = 0;
unsigned long bootTimer = 0;
unsigned long resetCooldownTimer = 0;
unsigned long brownoutTimer = 0;
bool isSlowTxRate = false;
bool ntripWasWorking = false;
unsigned long failureTimer = 0;
bool softResetDone = false;
bool newDataReceived = false;
// Optimized: Using const char* prevents memory fragmentation on ARM MCUs
const char* sysMsg = "System Initialized.";
String nmeaBuffer = "";
HardwareSerial GNSS(PA3, PA2);
// --- UTILITIES ---
// Formats uptime safely into a character buffer
void getUptimeString(char* buffer, size_t maxLen) {
unsigned long seconds = millis() / 1000;
int d = seconds / 86400;
int h = (seconds % 86400) / 3600;
int m = (seconds % 3600) / 60;
snprintf(buffer, maxLen, "%dd%dh%dm", d, h, m);
}
void updateSensors() {
float rawBat = (analogRead(PIN_BAT_SENSE) / 4095.0) * ADC_REF * DIVIDER_RATIO;
float raw5v = (analogRead(PIN_5V_SENSE) / 4095.0) * ADC_REF * DIVIDER_RATIO;
vBatF = (vBatF * 0.9) + (rawBat * 0.1); // Low-pass filter for stability
v5vF = (v5vF * 0.9) + (raw5v * 0.1);
}
void setPower(bool ON) {
if (ON) {
pinMode(PA2, OUTPUT);
pinMode(PA3, INPUT);
digitalWrite(PIN_PWR_CTRL, HIGH);
GNSS.begin(115200);
bootTimer = millis();
currentState = BOOTING;
fixQuality = 0;
satellites = 0;
sysMsg = "POWER ON: Booting...";
} else {
digitalWrite(PIN_PWR_CTRL, LOW);
GNSS.end();
pinMode(PA2, INPUT);
pinMode(PA3, INPUT);
ntripWasWorking = false;
sysMsg = "POWER OFF: Standby";
}
}
void parseNMEA() {
while (GNSS.available()) {
char c = GNSS.read();
if (c == '$') { nmeaBuffer = "$"; continue; }
if (c == '\n') {
if (nmeaBuffer.startsWith("$GNGGA") || nmeaBuffer.startsWith("$GPGGA")) {
int commaIndex = 0;
int satStart = -1;
for (unsigned int i = 0; i < nmeaBuffer.length(); i++) {
if (nmeaBuffer[i] == ',') {
commaIndex++;
if (commaIndex == 6 && (i + 1) < nmeaBuffer.length()) {
fixQuality = nmeaBuffer[i + 1] - '0';
}
else if (commaIndex == 7 && (i + 1) < nmeaBuffer.length()) {
satStart = i + 1;
}
else if (commaIndex == 8 && satStart != -1) {
satellites = nmeaBuffer.substring(satStart, i).toInt();
break;
}
}
}
rxCount++;
newDataReceived = true;
}
nmeaBuffer = "";
} else if (c != '\r' && nmeaBuffer.length() < 100) {
nmeaBuffer += c;
}
}
}
// Local Dashboard (ANSI Colors for PuTTY/TeraTerm)
void drawDashboard(bool pa4_on, bool pa5_on) {
char uptimeBuf[16];
getUptimeString(uptimeBuf, sizeof(uptimeBuf));
Serial.print("\033[H"); // Reset cursor to top-left
Serial.println("===========================================");
Serial.println(" RTK BLACKPILL TELEMETRY SYSTEM ");
Serial.println("===========================================");
// 1. System State
Serial.print("SYSTEM STATE: ");
if (currentState == RUNNING) Serial.println("\033[1;32mRUNNING \033[0m");
else if (currentState == BOOTING) Serial.println("\033[1;33mBOOTING \033[0m");
else if (currentState == HARD_RESETTING) Serial.println("\033[1;31mPWR CYCLING \033[0m");
else Serial.println("\033[1;35mBROWNOUT \033[0m");
// 2. Hardware Monitors
Serial.print("BATTERY: "); Serial.print(vBatF, 2); Serial.println(" V ");
Serial.print("UPTIME: "); Serial.println(uptimeBuf);
Serial.print("PWR IN (PA4): ");
if (pa4_on) Serial.println("\033[1;32mACTIVE (>4.0V)\033[0m ");
else Serial.println("\033[1;37mOFF (BATTERY)\033[0m ");
Serial.print("IGN IN (PA5): ");
if (pa5_on) Serial.println("\033[1;32mACTIVE (HIGH)\033[0m ");
else Serial.println("\033[1;37mOFF (LOW)\033[0m ");
// 3. Update Rate (Restored)
Serial.print("GNSS TX RATE: ");
if (currentState == BOOTING || currentState == BROWNOUT) {
Serial.println("\033[1;37mOFFLINE \033[0m");
} else if (isSlowTxRate) {
Serial.println("\033[1;33mEVERY 30 SECONDS\033[0m");
} else {
Serial.println("\033[1;36m1Hz (1 SECOND) \033[0m");
}
// 4. GNSS Status
Serial.print("RTK FIX: ");
if (fixQuality == 4) Serial.println("\033[1;32m[4] FIXED \033[0m ");
else if (fixQuality == 5) Serial.println("\033[1;36m[5] FLOAT \033[0m ");
else if (fixQuality > 0) Serial.println("\033[1;33m[1] 3D FIX\033[0m ");
else Serial.println("\033[1;31m[0] NO FIX\033[0m ");
Serial.print("PACKETS TX'd: "); Serial.print(rxCount); Serial.println(" ");
// 5. System Logs
Serial.println("-------------------------------------------");
Serial.print("LAST EVENT: ");
// Padding the const char* message to cleanly overwrite previous longer messages
String paddedMsg = String(sysMsg);
while(paddedMsg.length() < 30) paddedMsg += " ";
Serial.println(paddedMsg);
Serial.println("===========================================");
}
// Visual State Indicator
void updateLEDs() {
unsigned long m = millis();
bool ext = false;
switch (currentState) {
case BROWNOUT:
ext = (m % 2000 < 50); // Heartbeat pulse (Sleep)
break;
case BOOTING:
ext = (m % 500 < 250); // Steady Medium Blink (Booting GNSS)
break;
case HARD_RESETTING:
ext = (m % 100 < 50); // Aggressive Strobe (Fatal Error / Cycle)
break;
case RUNNING:
if (fixQuality == 4) {
ext = true; // Solid ON (RTK Fixed - Perfect)
}
else if (fixQuality > 0) {
unsigned long cycle = m % 1000;
ext = (cycle < 100) || (cycle > 200 && cycle < 300); // Double blink (Float/3D)
}
else {
ext = (m % 1000 < 500); // Steady Slow Blink (Searching)
}
break;
}
digitalWrite(PIN_EXT_LED, ext);
digitalWrite(PIN_LED, !ext); // Inverted for onboard Active-Low LED
}
// --- SETUP ---
void setup() {
pinMode(PIN_PWR_CTRL, OUTPUT);
pinMode(PIN_LED, OUTPUT);
pinMode(PIN_EXT_LED, OUTPUT);
pinMode(PIN_IGN_SENSE, INPUT_PULLDOWN);
Serial.begin(115200);
analogReadResolution(12);
// Reserving memory prevents heap fragmentation
nmeaBuffer.reserve(100);
Serial.print("\033[2J\033[H"); // Clear entire screen in PuTTY on boot
setPower(false);
currentState = BROWNOUT;
}
// --- MAIN LOOP ---
void loop() {
updateSensors();
parseNMEA();
updateLEDs();
bool pa4_on = (v5vF > 4.0);
bool pa5_on = (digitalRead(PIN_IGN_SENSE) == HIGH);
// Rate Truth Table: 1Hz only when both 5V and Ignition are active
bool fastTxMode = (pa4_on && pa5_on);
// Dashboard Update
if (newDataReceived || millis() - lastDashUpdate > 2000) {
drawDashboard(pa4_on, pa5_on);
lastDashUpdate = millis();
newDataReceived = false;
}
// Brownout Check Failsafe
if (vBatF < BAT_CRITICAL && !pa4_on && !pa5_on) {
if (brownoutTimer == 0) brownoutTimer = millis();
if (millis() - brownoutTimer > 3000) {
if (currentState != BROWNOUT && currentState != HARD_RESETTING) {
sysMsg = "ERR: Bat Critical. Sleeping.";
setPower(false);
currentState = BROWNOUT;
}
}
} else { brownoutTimer = 0; }
// State Machine
switch (currentState) {
case BROWNOUT:
if (pa4_on || pa5_on || vBatF > BAT_RECOVERY) setPower(true);
break;
case BOOTING:
if (millis() - bootTimer > BOOT_STABILIZE_TIME) {
sysMsg = "Applying initial UBX.";
if (fastTxMode) {
GNSS.write(UBX_1HZ, sizeof(UBX_1HZ));
isSlowTxRate = false;
} else {
GNSS.write(UBX_30S, sizeof(UBX_30S));
isSlowTxRate = true;
}
currentState = RUNNING;
failureTimer = 0;
}
break;
case HARD_RESETTING:
if (millis() - resetCooldownTimer > POWER_CYCLE_TIME) currentState = BROWNOUT;
break;
case RUNNING:
// A. Dynamic Rate Switching
if (fastTxMode && isSlowTxRate) {
sysMsg = "Ignition ON: 1Hz Mode";
GNSS.write(UBX_1HZ, sizeof(UBX_1HZ));
isSlowTxRate = false;
} else if (!fastTxMode && !isSlowTxRate) {
sysMsg = "Ignition OFF: 30s Mode";
GNSS.write(UBX_30S, sizeof(UBX_30S));
isSlowTxRate = true;
}
// B. Watchdog Logic (Reset triggers if RTK drops)
if (fixQuality >= 4) {
if (!ntripWasWorking) {
sysMsg = "Fix OK. Watchdog Armed.";
ntripWasWorking = true;
}
failureTimer = 0;
softResetDone = false;
}
else if (ntripWasWorking && fixQuality < 4) {
if (failureTimer == 0) failureTimer = millis();
unsigned long elapsed = millis() - failureTimer;
// Soft Reset Trigger
if (elapsed > SOFT_RESET_DELAY && !softResetDone) {
sysMsg = "WARN: Fix Lost. Soft Reset...";
GNSS.write(UBX_SOFT_RESET, sizeof(UBX_SOFT_RESET));
softResetDone = true;
}
// Hard Reset Trigger
if (elapsed > HARD_RESET_DELAY) {
sysMsg = "FATAL: Hard Cycle...";
setPower(false);
currentState = HARD_RESETTING;
resetCooldownTimer = millis();
}
}
break;
}
}

View File

@ -0,0 +1,22 @@
# Contexto del Sistema de Rastreo GPS RTK
## 1. Hardware y Ensamblaje
* **Dispositivo GPS:** Unidades Ardusimple modelo RTK2GO con módulos 4G integrados.
* **Alimentación:** Rango operativo de 12V a 24V, adaptable según la unidad en la que se instale.
* **Carcasa Interna:** Cajas "HBOX" impresas en 3D.
* **Protección Externa:** Gabinete con certificación IP65 para resistir las inclemencias del clima.
* **Control de Fallos (Watchdog):** Circuito interno basado en un microcontrolador Blackpill. Ejecuta un firmware en C++ (`blackpillrtkcode.ino`) encargado de monitorear la energía (batería e ignición) y reiniciar el sistema GPS en caso de pérdida de conexión NTRIP.
## 2. Proceso de Configuración del GPS
La configuración de cada módulo se realiza mediante las herramientas **4G NTRIP Master** y **u-center** siguiendo este procedimiento:
* Desactivar temporalmente los puertos UART 1 y UART 2 (baudrate a 115200).
* Cargar el archivo de parámetros base (`4G_NTRIP_HelgenSRV-NMEA.txt`), modificando el ID de manera individual para cada unidad.
* Reactivar los puertos UART 1 y UART 2 con la siguiente configuración:
* **Protocol In:** RTCM3
* **Protocol Out:** NMEA
* Guardar la configuración en la memoria no volátil del dispositivo.
## 3. Recepción de Datos y Backend
* **Gateway/Servicio:** Un script en Python (`retranslator.py`) recibe vía UDP la telemetría NMEA que envían los módulos 4G.
* **Filtros de Seguridad:** El script cuenta con validaciones anti-saltos y un filtro de velocidad. Si el vehículo reporta un avance o brinco superior al límite establecido (más de 50 km/h o brincos de coordenadas irreales), el sistema descarta esa transmisión para proteger la integridad del odómetro hasta que la métrica regrese a la normalidad.
* **Plataforma de Cliente:** Una vez procesada y filtrada, la información legítima se retransmite hacia **Wialon**, que sirve como el panel final de visualización y reporte para el cliente.

235
CLAUDE.md Normal file
View File

@ -0,0 +1,235 @@
---
tags: [project-drogheda, master-index, gps-rtk, hbox]
project: Project Drogheda
author: Helgen CTO
date: 2026-06-20
status: Active
---
# Project Drogheda — Master Index (CLAUDE.md)
> **Source of truth for this workspace.** Read this first, then
> [[00-Project-Management/PLAN|PLAN]] for current priorities. **Human engineers
> onboarding** should start with [[00-Project-Management/Engineering-Handover|Engineering-Handover]]
> (deep context: product, RCA, fixes, open items, glossary) and the repo `README.md`.
> You are acting as the
> **CTO/CSO of Helgen Technologies** — principal hardware/software architect for
> the Drogheda GPS-RTK fleet. Tone: precise, fail-safe first. Keep this index and
> its directory table current as files change.
> **Codename:** the team / Google Drive / official manual call this project
> **Drogheda (GPS RTK)**. This local folder was renamed from "Galway" → "Drogheda"
> on 2026-06-20; all docs now use **Drogheda**.
---
## 0. Where we are (2026-06-20)
Helgen deployed **HBOX** RTK trackers on heavy port equipment at **Hutchison Ports
ICAVE (Veracruz)**. They worked "ish," then failed. We are mid-crisis and
hardening the product for scale. Three independent failure fronts, all being
fixed in parallel:
1. **Backend (confirmed in code).** The production `retranslator.py` blocks the
whole fleet on any upstream slowness, and an anti-jump filter can permanently
black-hole a healthy unit → some "dead" units may be backend victims, not dead
radios. See [[02-Architecture-Software/backend-review|backend-review]].
2. **Thermal (component-level).** The **ASA** HBOX (UV-stable, softens ~95–100 °C —
the case is fine) sits sealed inside a sealed **ABS IP67** cabinet that also holds
the **Mean Well** supply → nested heat trap that cooks the **battery / boost /
modem** in full sun. See [[06-Testing-Validation/RCA-2026-06-field-failures|RCA]].
3. **Prototype hardware.** Dupont, hand-wiring, 18650 + PowerBoost — the client's
(ICAVE) own indictment. Fix = ruggedize now + production PCB.
**Status of units:** all pulled from machines. **4 in hand (3 dead, 1
working-ish)**, physically in **Veracruz**; team in **Querétaro** → remote debug.
Units run **firmware v1** (no thermal log). Triage:
[[06-Testing-Validation/remote-diagnostic-protocol|remote-diagnostic-protocol]].
**Commercial reality:** productized across **ICAVE, TIMSA, EIT, LCT** (Hutchison
terminals); target **30 units operational by August**; hundreds of cabinets
planned. The fix must be production-grade and repeatable.
**Logs & observability (2026-06-20).** Analyzed the first real bench + healthy-baseline
log set (u-blox `.ubx`, 4G-NTRIP console `.log`, server retranslator tcpdump) + the
caster/F9P config. **GNSS receivers are healthy but no unit reached RTK — all SPS**
(corrections not applied: free **rtk2go** caster + **RAM-only** F9P config + indoor sky).
The **4G-NTRIP console is an unreliable health signal** (see §6 — "master-14 fault"
retracted; only **master 21** confirmed). Built **`hbox-log-tools`** (Log Explorer +
analyzer + dashboard + manual) and a **Blackpill v2.1 logging spec** to fix observability.
Canonical: `06-Testing-Validation/2026-06-20_Drogheda_GNSS-Findings-Summary.md`.
---
## 1. System architecture (corrected from the BOM, 2026-06-20)
```text
Machine power+ignition (12/24/48/110/230 V, per equipment)
│
▼ ┌──────────────── Gabinete IP67 (ABS, Yetlebox P3901) ─────────────────┐
breaker│ Phoenix SPD · fuse holder · DIN rail · SSR/opto (ignition isolation)│
+ SPD ►│ Mean Well DDR-60G-24 ──► regulated 24 Vdc ───┐ │
│ │ GX16-5 connector │
│ ┌────────── HBOX (3D-printed ASA) ─────────┼────┐ │
│ │ Mean Well SD-15B-05 (18–36V→5V,3A) ──► 5V rail │ │
│ │ ├► PowerBoost 1000C (+18650 Li-ion 1S4P) backup │
│ │ ├► STM32F411 Blackpill WATCHDOG ──UART(PA2)──► simpleRTK2B Pro │
│ │ │ └ switches RTK 5V via 2N3904→IRF9540N (PA1) │
│ │ ├► simpleRTK2B Pro (ZED-F9P) + 4G NTRIP Master (XBee LTE) │
│ │ └► ignition 24V ──► DFR1015 buck ──► STM32 PA5 (ign sense) │
│ └──────────────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────┘
4G/LTE ▲ RTCM3 in (NTRIP) │ NMEA out (UDP)
Base station (Ardusimple AS-PRO-CORS) ────┘ ▼
retranslator.py ─(anti-jump+speed filter)─► Wialon / Traccar ─► VERONICA
```
- **Power into HBOX = clean 24 V** from the cabinet (via **GX16-5**). Internal
**Mean Well SD-15B-05** makes 5 V; **PowerBoost 1000C + 18650** is battery
backup; **DFR1015** conditions ignition to the Blackpill.
- **Corrections arrive over the 4G modem** (RTCM3 via NTRIP), not the GNSS — so a
GNSS reset can't fix a dead link; recover by power-cycling the stack (v2 does).
- **Watchdog** = STM32F411; see [[03-Architecture-Hardware/watchdog-schematic|watchdog-schematic]].
---
## 1b. Canonical invariants (inherits `Helgen Project Ethos`)
The rules this system must never violate. Check every design/code output against
these before presenting; if it breaks one, say so and propose the compliant fix.
1. **Power-off is the safe state.** The RTK stack defaults OFF (gate pull-up) until
the MCU asserts; loss of confidence → safe state, not best-guess.
2. **Never vanish, never loop.** A unit must keep reporting (standalone if needed)
rather than go silent, and must never enter an infinite fast-reboot loop.
3. **Corrections ride the 4G modem, not the GNSS** — recover the link by
power-cycling the stack; a GNSS reset is never a fix for lost NTRIP.
4. **No material that softens below ~80 °C (PLA-class) in the thermal path** — the
current **ASA** enclosure complies. Design to the measured cabinet-interior
temperature, not spec-sheet ambient.
5. **Backend reads/forwards without head-of-line blocking**, and the anti-jump
filter must never permanently lock out a healthy unit.
6. **No secrets in the repo — ever.** (The committed MSSQL password is a live
violation to remediate.)
## 1c. Hard constraints (non-negotiables)
- HBOX input is the cabinet's **regulated 24 V** + isolated ignition; don't
re-implement the cabinet's surge/breaker/SSR on the HBOX.
- **IP67 maintained** — cooling by conduction only; no IP-breaking vents/fans.
- **Bench-test firmware before any field flash** (no STM32 build in the sandbox).
- Client-facing docs are **Spanish**, routed through Leo.
- Use the **file tools** for file changes; verify the shell mount before bulk moves.
---
## 2. Directory (Helgen English `NN-` standard — keep this table current)
| Folder | Holds | Key files |
|---|---|---|
| `00-Project-Management` | plan, ethos, hackathon, **financials**, **handover**, client comms, Drive tooling | [[00-Project-Management/PLAN\|PLAN]] · [[00-Project-Management/Engineering-Handover\|Engineering-Handover]] · `ethos.md` · `hackathon-plan.md` · `Financials.md` · `Drive-Upload/` · `client-comms/` |
| `01-Requirements` | what we build | `SRS.md` · `HBOX-product-spec.md` |
| `02-Architecture-Software` | backend/software analysis, ADRs | `backend-review.md` |
| `03-Architecture-Hardware` | HBOX/cabinet, wiring, BOM, **watchdog PCB v3** | `hardware-respec-and-pcb-brief.md` · `cabinet-electrical-bom.md` · `watchdog-schematic.md` · `watchdog-health-telemetry.md` · `firmware-review.md` · `BOM-master.md` · **`watchdog-pcb-v3-design.md`** (schematic spec: +2 fans, +I²C temp/RH) · `watchdog-pcb-v3-bom.xlsx` (proto+production, LCSC) · `watchdog-pcb-v3-kicad-guide.md` (KiCad→JLCPCB) · `watchdog-pcb-v3-blockdiagram.svg` |
| `04-Source-Code` | firmware + backend repos | `firmware/` (**own git repo `hbox-firmware`**: `blackpillrtkcode_v2/` deploy · `legacy/` v1 · `docs/LOGGING-SPEC.md` v2.1) · `htracker/` · `notifications/` · `rtk-config/` |
| `05-Analytics-Reports` | SQL views, dashboards (VERONICA/PowerBI) | _(placeholder)_ |
| `06-Testing-Validation` | RCA, diagnostics, test evidence, **log analysis & tooling** | `RCA-2026-06-field-failures.md` · `remote-diagnostic-protocol.md` · `2026-06-20_Drogheda_Bench-Log-Analysis_v1.0.md` (rev-3) · `2026-06-20_Drogheda_GNSS-Findings-Summary.md` · `log-analysis/` (repo **`hbox-log-tools`**: `log-explorer.html` · `analyze_logs.py` · `build_dashboard.py` · `docs/manual.pdf`) |
| `Marketing` | product-facing collateral (LaTeX→PDF) | `HBOX-Datasheet/` (industrial spec sheet) · `HBOX-Brochure-RTK/` (flyer) |
| `99-Archive` | superseded raw inputs (read-only) | `situation_ORIGINAL.md` |
| `.claude/skills/` | vendored project skill | `project-drogheda/` |
> Structure follows the **[[Helgen Project Template]]** (English `NN-`). The Google
> Drive mirror keeps the legacy Spanish `00_…99_` scheme (Drive manual); finalized
> deliverables export there. Naming: `AAAA-MM-DD_Drogheda_Descripción_vX.X` — no
> loose files in root except this index. **Empty legacy Spanish dirs remain (the
> mount can't delete them) — remove locally:** `rmdir 0?_* 99_Archivo docs hardware …`
---
## 3. HBOX BOM (per unit, 24 V variant — see `03-Architecture-Hardware/BOM-master`)
simpleRTK2B **Pro** (AS-RTK2B-PROF9P-L1L2-NH-00) · 4G NTRIP Master
(AS-XBEE-4G-NA-UFL-00) · Blackpill STM32F411 · U-blox multiband antenna
(AS-ANT2B-ANN-L1L2-50SMA) + 2× LTE antennas (AR1582) · **Mean Well SD-15B-05**
(24→5 V) · **PowerBoost 1000C** + **18650 1S4P** · **DFR1015** (ignition) ·
2N3904 + IRF9540N + 2N7000 watchdog · **GX16-5** HBOX↔cabinet · ASA case/top/
brackets · Telcel micro-SIM.
---
## 4. Deployment & people
**BUs / terminals:** ICAVE (stress lab, live), TIMSA, EIT, LCT. **ICAVE machine
voltages:** RTG 24 V, Reach Stacker 24 V, Empty Handler 24 V, Tractoplana 12 V,
Montacargas 12 V. **Target:** 30 units by August.
| Name | Email | Role |
|---|---|---|
| Leo Rodriguez | leo@helgen.tech / ceo@helgen.tech | Helgen — founder/CEO |
| Adriana Del Angel | adriana@helgen.tech | Helgen — implementation/plan |
| Paola Rodriguez | paola@helgen.tech | Helgen |
| Angel Daniel Martinez | daniel@helgen.tech | Helgen |
| Gibran Ortega | gibran@helgen.tech | Helgen — GPS/telemetry |
| **Agustín Alfonseca** | alfonseca.agustin@icave.com.mx | **Client** — ICAVE Tech Research Supervisor |
| Leon David Piña | pina.david@hutchisonports.com.mx | Hutchison Ports |
---
## 5. Where things live (off this workspace)
- **Google Drive (team source of truth):** `Drogheda (GPS RTK)` — official
numbered structure 00–99 + manual `LEEME_Drogheda_Estructura_y_Uso`. Mirror this
workspace's structure to it for finalized deliverables.
- **Second Brain vault** (`~/Documents/Second Brain/03 Projects/Helgen/`): only
polished, evergreen notes (decisions, lessons, contacts) — see
`Project-Drogheda/` once synced.
- **Backend repo:** `ssh://git.helgen.dev:6611/htracker` (retranslator + monitor),
cloned at `04-Source-Code/htracker` (branch `feature/test500unidades`).
- **Firmware repo:** `ssh://git.helgen.dev:6611/hbox-firmware` (STM32F411 watchdog),
lives at `04-Source-Code/firmware` (branch `main`) — its own git repo with README,
`docs/PINOUT.md`, redacted RTK-config template, CHANGELOG. Push instructions in its README.
**v2.1 logging spec** at `docs/LOGGING-SPEC.md` (structured/parseable Blackpill log; not yet flashed).
- **Log-tools repo:** `ssh://git.helgen.dev:6611/hbox-log-tools` (HBOX log analysis —
in-browser **Log Explorer** + batch **analyzer** + dashboard + Spanish LaTeX **manual**),
lives at `06-Testing-Validation/log-analysis/`. Raw logs + config files git-ignored
(credentials). Built 2026-06-20.
- **Project tracker:** Notion — **`HBOX Unit Status — Bench Triage`** board under
*Project Drogheda — GPS RTK* (corrected to rev-3, 2026-06-20) + Mattermost (team chat).
---
## 6. Hard facts — don't relearn these
- NMEA GGA quality ≥4 = healthy (4 = RTK-fixed, 5 = RTK-float).
- ZED-F9P rated −40…+85 °C — heat casualties are the **18650/PowerBoost** and the
**4G modem**, not the GNSS or the case.
- **The case is ASA** (UV-stable, softens ~95–100 °C) — adequate for the nested ABS
IP67 cabinet interior; the enclosure is **not** the liability. **A bare fan inside a
sealed box does not cool it** — conduct heat out, or use a ducted fan (v3.0). See hardware brief §2b.
- Corrections come over 4G → recover by power-cycling the stack (v2), not a GNSS
reset. Backend can take healthy units off Wialon (HOL block + anti-jump lockout).
- HBOX input = **24 V** from the cabinet; design the redesign around that.
- Firmware: edit `04-Source-Code/firmware/blackpillrtkcode_v2/blackpillrtkcode_v2.ino`
(own git repo `hbox-firmware`); **bench-test before field flash** (sandbox can't build STM32).
- **20 Jun logs — receivers are fine; RTK is not being achieved.** Every board emits valid
NMEA (good sats/HDOP) but **every epoch is SPS (1), zero RTK (4/5)**. Corrections aren't
applied: **free public rtk2go caster** (no SLA), F9P config dumped from **RAM only** (verify
saved to FLASH; check the Pro's XBEE↔UART2 switch), plus indoor sky. F9P message config is
correct (GGA on UART2 @1 Hz).
- **The 4G-NTRIP-Master console does NOT prove forwarding** — a healthy unit logs the same
sparse console ("registered", no `State main`/`UDP Bytes sent`) as a failed-looking one.
Confirm health only from a `.ubx` (fix), the Blackpill, or a server delivery capture.
→ **"master-14 ingest fault" RETRACTED**; only **master 21** (modem `0xFFFFF` + 511 AT errors)
is a confirmed fault.
- **Healthy GNSS baseline = `RTK14NTRIP14_440`** (server-verified 1 Hz delivery, 12 sats,
HDOP 0.75, **but SPS not RTK**). Raise the bar to an open-sky fix-≥4 capture.
- **Heat is NOT implicated** by the bench logs (faults reproduce cold; v1 has no thermal
channel). Field thermal hypothesis still needs a thermocouple cabinet soak.
- **`dashboard.html` predates the rev-3 retraction** (still marks the no-forward captures
"Failed"); the canonical corrected record is the GNSS summary + the rev-3 findings doc.
## 7. Working notes
- Use the **file tools** (Read/Write/Edit) for file changes; the shell mount can
go stale — verify before bulk shell moves. Six empty legacy dirs (`docs`,
`firmware`, …) remain from the restructure; delete them manually.
- Client-facing docs are **Spanish** and route through Leo.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,84 @@
% Helgen Drogheda — HBOX · Ficha comercial de producto (flyer 1 pág.)
% Compilar: latexmk -xelatex main.tex
\documentclass[10pt]{article}
\newcommand{\sistema}{HBOX}
\newcommand{\sistemapdf}{HBOX RTK — Localización de Alta Precisión}
\newcommand{\doctipo}{Ficha de Producto}
\input{../../_brand/estilo-helgen.tex}
\geometry{margin=1.3cm}
\pagestyle{empty}
\setlength{\parindent}{0pt}
\begin{document}
% ---------- Cabecera de marca ----------
\begin{tcolorbox}[enhanced, colback=HelgenIndigo, colframe=HelgenIndigo, arc=3pt,
left=14pt,right=14pt,top=12pt,bottom=12pt, boxrule=0pt]
{\helgentitle\fontsize{26}{28}\selectfont\color{white}Helgen}\hfill
{\helgentitle\small\color{HelgenCyan}Ingeniería que produce, mantenimiento que protege}\\[8pt]
{\color{HelgenCyan}\rule{\linewidth}{1.4pt}}\\[8pt]
{\helgentitle\fontsize{24}{27}\selectfont\color{white}HBOX — Localización de Alta Precisión}\\[3pt]
{\helgentitle\fontsize{13}{16}\selectfont\color{HelgenCyan}GPS RTK · precisión centimétrica para equipo portuario e industrial}
\end{tcolorbox}
\vspace{6pt}
{\helgentitle\large\color{HelgenIndigo}\textbf{Precisión centimétrica. Conectividad robusta. Ingeniería Helgen.}}\\[4pt]
La \textbf{HBOX} es una solución integral de posicionamiento \textbf{RTK} para entornos
donde la exactitud y la confiabilidad son esenciales. Arquitectura de dos componentes —
\textbf{Rover} (en el equipo) + \textbf{Estación Base} (correcciones diferenciales) — que
entrega posición de \textbf{nivel centimétrico}, estable y continua, sobre red 4G.
\vspace{8pt}
\begin{minipage}[t]{0.56\textwidth}
{\helgentitle\large\color{HelgenIndigo}\textbf{Beneficios clave}}\\[5pt]
\begin{itemize}[leftmargin=1.3em, itemsep=4pt]
\item Precisión \textbf{$<$2 cm} con NTRIP hasta \textbf{35 km}, actualización desde 1 s.
\item Arquitectura \textbf{Rover + Base Station} para máxima precisión y continuidad.
\item Tecnología \textbf{RTK} confiable en patios, grúas y maquinaria pesada.
\item Carcasa \textbf{IP67}: resistente a polvo, agua y vibración.
\item Diseño \textbf{compacto y robusto}, de fácil integración en cualquier plataforma.
\item \textbf{Watchdog} embebido: recupera el enlace de forma autónoma y nunca queda en silencio.
\end{itemize}
\vspace{4pt}
{\helgentitle\large\color{HelgenIndigo}\textbf{Aplicaciones}}\\[4pt]
\textcolor{HelgenTeal}{\rule[0.18ex]{0.5em}{0.5em}}~Portuaria \quad
\textcolor{HelgenTeal}{\rule[0.18ex]{0.5em}{0.5em}}~Logística \quad
\textcolor{HelgenTeal}{\rule[0.18ex]{0.5em}{0.5em}}~Industrial \quad
\textcolor{HelgenTeal}{\rule[0.18ex]{0.5em}{0.5em}}~Agrícola \quad
\textcolor{HelgenTeal}{\rule[0.18ex]{0.5em}{0.5em}}~Marítima \quad
\textcolor{HelgenTeal}{\rule[0.18ex]{0.5em}{0.5em}}~Urbana
\end{minipage}\hfill
\begin{minipage}[t]{0.40\textwidth}
\begin{tcolorbox}[enhanced, colback=HelgenMist, colframe=HelgenIndigo, arc=2pt, boxrule=0.6pt,
left=8pt,right=8pt,top=7pt,bottom=7pt, coltitle=white, colbacktitle=HelgenIndigo,
fonttitle=\helgentitle\bfseries\small, title={Especificaciones}]
\footnotesize
\begin{tabularx}{\linewidth}{@{}>{\bfseries\color{HelgenIndigo}}l X@{}}
GNSS & u-blox ZED-F9P, multibanda\\
Bandas & L1 · L2 · L5 · L6\\
Precisión & $<$2 cm (NTRIP, $\leq$35 km)\\
Tasa & desde 1 Hz\\
Correcciones & NTRIP/RTCM3 sobre 4G\\
Red & 2G · 3G · 4G (MicroSIM)\\
Entrada & 24 V (gabinete) + ignición\\
Conector & GX16-5 con seguro\\
Carcasa & IP67 (polvo/agua/vibración)\\
\end{tabularx}
\end{tcolorbox}
\vspace{6pt}
\begin{center}
\begin{tikzpicture}[node distance=5mm and 7mm, font=\scriptsize]
\node[cajaoscura, text width=2.0cm] (base) {Estación\\Base RTK};
\node[caja, below=of base, text width=2.0cm] (rover) {Rover\\HBOX};
\node[etiqueta, right=10mm of base] (sat) {NTRIP / 4G};
\draw[flechaacento] (base) -- node[etiqueta,right]{RTCM3} (rover);
\end{tikzpicture}
\end{center}
\end{minipage}
\vfill
{\color{HelgenCyan}\rule{\linewidth}{1.2pt}}\\[3pt]
{\footnotesize\helgentitle\color{HelgenIndigo}\textbf{Helgen Technologies}\quad\textcolor{gray}{·\ \ Helgen Servicios Especializados S.A. de C.V.\ \ ·\ \ ventas@helgen.tech\ \ ·\ \ Confidencial}}
\end{document}

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,175 @@
% Helgen Drogheda — HBOX · Ficha Técnica / Datasheet (estilo industrial, 2 pág.)
% Modelo de referencia: SPEC SHEET H-BOX (Canva). Compilar: latexmk -xelatex main.tex
\documentclass[9pt]{extarticle}
\newcommand{\sistema}{HBOX}
\newcommand{\sistemapdf}{HBOX Datasheet}
\newcommand{\doctipo}{Ficha Técnica}
\input{../../_brand/estilo-helgen.tex}
\usepackage{amssymb}
\geometry{margin=1.0cm}
\pagestyle{empty}
\setlength{\parindent}{0pt}
\setlength{\parskip}{0pt}
\renewcommand{\arraystretch}{1.04}
% Banda de sección compacta
\newcommand{\dssec}[1]{\vspace{3pt}{\setlength{\fboxsep}{2.2pt}\noindent\colorbox{HelgenIndigo}%
{\makebox[\dimexpr\linewidth-2\fboxsep][l]{\helgentitle\footnotesize\color{white}\,#1}}}\vspace{1.5pt}\\[0.5pt]}
% Fila de spec: \spec{Parámetro}{Valor}
\newcommand{\dstab}[1]{\begin{tabularx}{\linewidth}{@{}>{\bfseries\color{HelgenIndigo}}p{4.6cm} X@{}}#1\end{tabularx}}
\begin{document}
% ---------- Cabecera ----------
\noindent
\begin{minipage}[c]{0.30\linewidth}{\helgentitle\fontsize{22}{24}\selectfont\color{HelgenIndigo}Helgen}\end{minipage}%
\begin{minipage}[c]{0.42\linewidth}\centering{\helgentitle\large\color{HelgenIndigo}HBOX\,–\,24V\,–\,LTE}\\[1pt]{\scriptsize\color{HelgenTeal}Ficha Técnica · Rastreador GPS-RTK}\end{minipage}%
\begin{minipage}[c]{0.28\linewidth}\raggedleft{\helgentitle\large\color{HelgenIndigo}HBOX}\,{\footnotesize\color{HelgenTeal}series}\end{minipage}\\[3pt]
{\color{HelgenCyan}\rule{\linewidth}{1.4pt}}\\[4pt]
{\footnotesize La \textbf{HBOX} entrega posición \textbf{RTK de precisión centimétrica} sobre 4G en equipo
portuario e industrial. Arquitectura \textbf{Rover + Estación Base}, gabinete \textbf{IP67}, watchdog
embebido. La electrónica interna es idéntica en todas las variantes de gabinete (12/24/48/110/230 V).}\\[2pt]
% ================= GNSS / RTK =================
\dssec{GNSS / RTK — Desempeño}
\dstab{%
GNSS & u-blox ZED-F9P, multibanda, multiconstelación (GPS, GLONASS, Galileo, BeiDou)\\
Bandas & L1 · L2 · L5 · L6\\
Precisión (RTK fijo) & $<$2 cm + 1 ppm CEP, con NTRIP hasta 35 km\\
Tasa de salida & 1 Hz (activo) / 30 s (reposo, ahorro de energía)\\
Salida de posición & NMEA (GGA, RMC) por UDP $\rightarrow$ retranslator $\rightarrow$ Wialon/Traccar/VERONICA\\
}
% ================= Comunicaciones =================
\dssec{Comunicaciones — 4G / NTRIP}
\dstab{%
Correcciones & NTRIP / RTCM3 sobre LTE (ArduSimple 4G NTRIP Master)\\
Redes & 2G · 3G · 4G — MicroSIM (Telcel)\\
Antenas & 1$\times$ GNSS multibanda (SMA) + 2$\times$ LTE (AR1582)\\
Recuperación de enlace & Watchdog reinicia el stack con \textit{backoff}; modo LIMP sigue reportando posición estándar\\
}
% ================= Entrada =================
\dssec{Entrada (Input)}
\dstab{%
Voltaje de entrada al HBOX & 24 Vcd (desde el gabinete; diseño 18–36 Vcd)\\
Voltaje de máquina (gabinete) & 12 / 24 / 48 Vcd · 110 / 230 Vca, según equipo\\
Ignición & Señal aislada vía relevador de estado sólido (SSR) del gabinete\\
Consumo típico / pico & $\sim$0.3–0.6 A @ 5 V; picos de TX LTE $\sim$1.5–2 A (validar en banco)\\
}
% ================= Salida / rieles internos =================
\dssec{Rieles internos (Output)}
\dstab{%
Fuente 5 V & Mean Well SD-15B-05 (18–36 V $\rightarrow$ 5 V, 3 A, 15 W)\\
Fuente del gabinete & Mean Well DDR-60G-24 / HDR-60-24 (entrada por voltaje $\rightarrow$ 24 V, 60 W)\\
Respaldo de energía & PowerBoost 1000C + batería 18650 Li-ion 1S4P\\
MCU / watchdog & STM32F411 Blackpill (firmware \texttt{blackpillrtkcode\_v2})\\
}
% ================= Protección =================
\dssec{Protección y seguridad}
\dstab{%
Sobrecorriente (fuente) & Modo \textit{Hiccup}: recuperación automática al retirar la falla\\
Sobre-temperatura (fuente) & Corte de salida con \textbf{recuperación automática} al disminuir la temperatura\\
Protección de entrada & Fusible + supresor de picos (SPD Phoenix) + interruptor (breaker), a nivel gabinete\\
Aislamiento & Ignición aislada (SSR/opto); ZED-F9P alimentado por riel conmutado\\
}
% ================= Diagrama de potencia y señal =================
\dssec{Diagrama de potencia y señal}
\begin{center}
\begin{tikzpicture}[node distance=6mm and 8mm, font=\scriptsize, every node/.style={align=center}]
\node[cajaoscura, text width=1.9cm] (m) {Máquina\\12–230 V};
\node[caja, right=of m, text width=3.1cm] (gab) {Gabinete IP67\\{\scriptsize SPD · Breaker · SSR}\\Mean Well DDR-60G-24\\$\to$ 24 Vcd};
\node[caja, right=of gab, text width=3.5cm] (hb) {HBOX\\{\scriptsize SD-15B-05 $\to$ 5 V · PowerBoost+18650}\\STM32 watchdog · ZED-F9P · 4G};
\node[cajaoscura, above=7mm of hb, text width=2.3cm] (base) {Estación Base\\RTK / NTRIP};
\node[caja, right=of hb, text width=1.8cm] (cloud) {Wialon /\\VERONICA};
\draw[flecha] (m) -- node[etiqueta,above]{pot.+ign.} (gab);
\draw[flecha] (gab) -- node[etiqueta,above]{GX16-5} (hb);
\draw[flechaacento] (base) -- node[etiqueta,right]{RTCM3 · 4G} (hb);
\draw[flecha] (hb) -- node[etiqueta,above]{NMEA} (cloud);
\end{tikzpicture}
\end{center}
\newpage
% ================= AMBIENTAL / TÉRMICA (destacado) =================
\dssec{Ambiental / Térmica}
\dstab{%
Temp.\ de operación (interno) & \textbf{$-10$ a $+75$\,\textdegree C} objetivo — componentes calificados $\geq$75\,\textdegree C\\
Temp.\ del receptor GNSS & $-40$ a $+85$\,\textdegree C (ZED-F9P)\\
Temp.\ de almacenamiento & $-40$ a $+85$\,\textdegree C\\
Carga de batería (18650) & \textbf{$\leq$45\,\textdegree C} — no cargar por encima (riesgo de degradación/inflado)\\
Material de carcasa & \textbf{Serie 1: PLA (ablanda $\sim$50–60\,\textdegree C — a reemplazar)} $\rightarrow$ Serie 2: ASA/PC o metal\\
Protección térmica & Corte de salida con auto-recuperación (fuente Mean Well)\\
Humedad & Hasta 95\% RH, sin condensación\\
Ingreso & \textbf{IP67} (polvo, agua, vibración)\\
Enfriamiento & Por \textbf{conducción} (caja sellada; sin ventilación que rompa IP67)\\
}
\begin{helgennotice}[Nota térmica de diseño (crítica)]
El HBOX opera dentro de un gabinete sellado bajo sol directo: la temperatura interior real
puede superar ampliamente el ambiente. \textbf{Diseñar a la temperatura medida del gabinete}, no
a la ambiente de placa de datos. \textbf{Sin PLA} en la ruta térmica; \textbf{un ventilador dentro
de una caja sellada no la enfría} — el calor debe conducirse al exterior.
\end{helgennotice}
% ================= Ventana térmica (derating) =================
\dssec{Ventana térmica de operación}
\begin{center}
\begin{tikzpicture}[xscale=0.082, font=\scriptsize]
\fill[red!14] (50,-0.15) rectangle (60,2.55);
\fill[HelgenTeal!20] (-40,0.0) rectangle (85,0.55);
\fill[HelgenCyan!50] (-10,0.7) rectangle (75,1.25);
\fill[HelgenIndigo!20] (-10,1.4) rectangle (45,1.95);
\node[right] at (-40,0.275) {\,ZED-F9P: $-40$ a $+85$\,\textdegree C};
\node[right] at (-10,0.975) {\,Objetivo interno: $-10$ a $+75$\,\textdegree C};
\node[right] at (-10,1.675) {\,Carga 18650: $\leq 45$\,\textdegree C};
\node[red!65!black, align=center, font=\scriptsize\bfseries] at (55,2.78) {PLA\\50\textendash60\,\textdegree C};
\draw[->,thick,HelgenIndigo] (-42,-0.28) -- (90,-0.28);
\foreach \t in {-40,-10,0,25,45,60,75,85}
\draw[HelgenIndigo] (\t,-0.20) -- (\t,-0.36) node[below,font=\tiny]{\t\textdegree};
\node[below,font=\tiny,HelgenIndigo] at (90,-0.46) {Temp.\ ambiente};
\end{tikzpicture}
\end{center}
% ================= Mecánico =================
\dssec{Mecánico}
\dstab{%
Gabinete & Yetlebox P3901, ABS, IP67, 39$\times$29$\times$16 cm, riel DIN, platina\\
Conector HBOX$\leftrightarrow$gabinete & GX16-5 circular con seguro\\
Antenas & SMA de mamparo (GNSS) + 2$\times$ LTE por glándula (NPT 3/8)\\
Montaje & Sobre estructura firme; preferir sombra y buena vista al cielo\\
}
% ================= Variantes =================
\dssec{Variantes de gabinete}
\hwtab{%
\begin{tabularx}{\linewidth}{@{}>{\bfseries}l X >{\centering\arraybackslash}p{2.8cm}@{}}
\rowcolor{HelgenIndigo}\textcolor{white}{Voltaje} & \textcolor{white}{Etapa de potencia (cambia con el voltaje)} & \textcolor{white}{Estado}\\
24 V & Mean Well DDR-60G-24 · SSR TOS 24VDC · SPD 24DC & \cellcolor{HelgenCyan}\textbf{En producción}\\
12 V & DDR-60G-24 · SSR MOS 12-28VDC · SPD 12DC & Teórico\\
48 V & DDR-60L-24 · opto Wago 859-744 · SPD 48DC & Teórico\\
110 V & HDR-60-24 / Siemens · SSR IDEC · SPD CA & Teórico\\
230 V & HDR-60-24 · SSR IDEC · SPD CA + fusible Bussman & Teórico\\
Base & Estación base RTK/NTRIP (Ardusimple AS-PRO-CORS), 110/230 Vca & 1 por terminal\\
\end{tabularx}}
% ================= Nomenclatura / pedido =================
\vspace{4pt}
\noindent\begin{minipage}[t]{0.52\linewidth}
\dssec{Nomenclatura de modelo}
{\footnotesize \texttt{HBOX – \underline{\ \ V\ \ } – LTE} \quad donde \texttt{V} = voltaje del gabinete\\[2pt]
Ej.: \texttt{HBOX–24V–LTE} (unidad en producción).}
\end{minipage}\hfill
\begin{minipage}[t]{0.44\linewidth}
\dssec{Uso previsto}
{\footnotesize $\square$ Flotilla \quad $\square$ Particular \quad $\square$ Pruebas \quad $\square$ Otro: \rule{2.2cm}{0.4pt}}
\end{minipage}
\vfill
{\color{HelgenCyan}\rule{\linewidth}{1pt}}\\[2pt]
{\scriptsize\helgentitle\color{HelgenIndigo}\textbf{Helgen Technologies}\ ·\ Helgen Servicios Especializados S.A.\ de C.V.\ ·\ ventas@helgen.tech \hfill File: HBOX DS · 2026-06-20 · Confidencial}
\end{document}

Binary file not shown.

87
README.md Normal file
View File

@ -0,0 +1,87 @@
# Project Drogheda (GPS RTK)
Helgen Technologies' **HBOX** RTK GPS tracker fleet for **Hutchison Ports** heavy
equipment (ICAVE / TIMSA / EIT / LCT) — centimetre-class position over 4G, streamed to the
Helgen backend (`retranslator` → Wialon/Traccar → VERONICA), built to survive a hot, salty,
high-vibration port environment.
> **New here? Read [`00-Project-Management/Engineering-Handover.md`](00-Project-Management/Engineering-Handover.md) first.**
> It is the deep context: product, failure analysis, fixes, repo map, open items, glossary.
> This README is the 2-minute orientation; [`CLAUDE.md`](CLAUDE.md) is the terse machine index.
---
## Where we are (2026-06-20)
We deployed a first batch of HBOX trackers on ICAVE machines in **Veracruz**. They worked
"ish," then failed. We are **mid-crisis, hardening for scale** — target **30 units by
August**. The failure is **three independent fronts**, fixed in parallel:
1. **Backend** (confirmed in code) — `retranslator.py` head-of-line blocks the whole fleet
on slow upstream, and an anti-jump filter can **permanently black-hole a healthy unit**.
2. **Thermal** — an **ASA** case (UV-stable, ~95–100 °C — adequate) sealed in a sealed **IP67**
cabinet that also holds the heat-generating **Mean Well** PSU, in full sun → nested heat trap
for the **battery / boost / modem** (not the case).
3. **Prototype hardware** — Dupont + hand-wiring + 18650/PowerBoost; fragile under vibration.
**Units:** all pulled — **4 in hand (3 dead, 1 working-ish)** in Veracruz; team in Querétaro
→ **remote debug**. Field firmware is **v1** (no thermal log).
> **Free triage:** a unit **ONLINE in htracker but OFFLINE in Wialon** is a **backend
> victim**, not dead hardware. Check that before condemning a radio.
---
## The product in one breath
**Rover (HBOX)** on the machine + **Base Station** (Ardusimple AS-PRO-CORS, 110/230 V)
publishing **RTCM3** corrections over **NTRIP**. The rover gets corrections **over the 4G
modem, not the GNSS** → recover a dead link by **power-cycling the 4G stack**, never a GNSS
reset. HBOX core: simpleRTK2B **Pro** (ZED-F9P) · 4G NTRIP Master · STM32F411 watchdog ·
Mean Well SD-15B-05 (24→5 V) · PowerBoost + 18650 · GX16-5 to the cabinet. **24 V is the
production variant.**
## Invariants (never violate)
1. Power-off is the safe state. 2. Never vanish, never loop. 3. Corrections ride the 4G
modem, not the GNSS. 4. No material that softens below ~80 °C (PLA-class) in the thermal path —
the ASA enclosure complies; design to *measured* cabinet temperature. 5. Backend forwards without head-of-line blocking; anti-jump never
locks out a healthy unit. 6. **No secrets in the repo** (the committed MSSQL password is a
live violation to rotate).
## Repo layout
`00-Project-Management` (plan, ethos, financials, **handover**, Drive tooling) ·
`01-Requirements` (SRS, product spec, tracker) · `02-Architecture-Software` (backend review) ·
`03-Architecture-Hardware` (cabinet/HBOX, BOM, PCB brief, watchdog, manual) ·
`04-Source-Code` (`firmware/` · `htracker/` · `notifications/`) ·
`05-Analytics-Reports` (SQL views — placeholder) · `06-Testing-Validation` (RCA, diagnostics) ·
`Marketing` (datasheet, brochure) · `_brand` (LaTeX style) · `99-Archive` (raw inputs).
## Quick start
```bash
# Branded docs (XeLaTeX; League Spartan + MADE Evolve Sans):
cd 01-Requirements/HBOX-Ficha-Tecnica && latexmk -xelatex main.tex
# Firmware (own repo 'hbox-firmware', at 04-Source-Code/firmware):
# open blackpillrtkcode_v2/blackpillrtkcode_v2.ino in the STM32duino toolchain.
# ⚠ Bench-test before any field flash — the sandbox cannot build STM32.
# Backend: 04-Source-Code/htracker (ssh://git.helgen.dev:6611/htracker, feature/test500unidades)
```
## Storage model (one home per artifact)
**This repo** = working source of truth (editable `.md`/`.tex`, code). **Google Drive**
`Drogheda (GPS RTK)` = finalized exports (branded PDFs, native Docs/Sheets). **Second Brain
vault** = distilled decisions/lessons. Author in repo → export final to Drive → distill to
vault. Drive upload how-to + mapping: `00-Project-Management/Drive-Upload/README.md`. Never
change Drive sharing — that's Leo's call.
## Go deeper
- **Deep context / onboarding** → [`00-Project-Management/Engineering-Handover.md`](00-Project-Management/Engineering-Handover.md)
- Current priorities → [`00-Project-Management/PLAN.md`](00-Project-Management/PLAN.md)
- Machine index → [`CLAUDE.md`](CLAUDE.md)
- Failure analysis → [`06-Testing-Validation/RCA-2026-06-field-failures.md`](06-Testing-Validation/RCA-2026-06-field-failures.md)

117
_brand/estilo-helgen.tex Normal file
View File

@ -0,0 +1,117 @@
% ---------- Estilo de marca Helgen (compartido, Project Drogheda) ----------
% Reutiliza la identidad de Project Ennis (_estilo-helgen.tex). Cada documento define
% \sistema, \sistemapdf y \doctipo antes de \input{...}. Compilar con XeLaTeX.
% ---------- Geometría y base ----------
\usepackage[letterpaper, margin=2.4cm, headheight=14pt]{geometry}
\usepackage{polyglossia}
\setmainlanguage[variant=mexican]{spanish}
\usepackage{xcolor}
\usepackage{colortbl}
\usepackage{graphicx}
\usepackage{booktabs}
\usepackage{array}
\usepackage{tabularx}
\usepackage{enumitem}
\usepackage{titlesec}
\usepackage{float}
\usepackage{fancyhdr}
\usepackage{tikz}
\usetikzlibrary{arrows.meta, positioning, shapes.misc, calc, fit, automata}
\usepackage{caption}
\captionsetup{font=small, labelfont={bf,color=HelgenIndigo}}
\usepackage{microtype}
\setlength{\emergencystretch}{3em}\tolerance=2000\hbadness=2000
\usepackage{hyperref}
\usepackage{textcomp}
\usepackage{ragged2e}
\usepackage{xltabular}
\usepackage[export]{adjustbox}
\usepackage[most]{tcolorbox}
\providecommand{\sistema}{Helgen Drogheda}
\providecommand{\sistemapdf}{Helgen Drogheda}
\providecommand{\doctipo}{Documento}
% ---------- Paleta oficial Helgen ----------
\definecolor{HelgenIndigo}{HTML}{2C3C5B}
\definecolor{HelgenCyan}{HTML}{51F3DA}
\definecolor{HelgenTeal}{HTML}{17A08C}
\definecolor{HelgenMist}{HTML}{EFF1F5}
\definecolor{HelgenLightCyan}{HTML}{E7FCF8}
\definecolor{HelgenLightIndigo}{HTML}{EDEFF4}
\newtcolorbox{helgennotice}[1][Aviso]{enhanced,breakable,colback=HelgenLightCyan,colframe=HelgenIndigo,boxrule=0.6pt,arc=2pt,left=9pt,right=9pt,top=7pt,bottom=7pt,coltitle=white,colbacktitle=HelgenIndigo,fonttitle=\helgentitle\bfseries\small,title={#1}}
\newcommand{\hwtab}[1]{\begin{center}\footnotesize\renewcommand{\arraystretch}{1.25}#1\end{center}}
\hypersetup{colorlinks=true, linkcolor=HelgenTeal, urlcolor=HelgenTeal, pdftitle={\sistemapdf{} - \doctipo}, pdfauthor={Dr. Leopoldo Rodríguez Salazar (Helgen Technologies)}}
% ---------- Tipografía de marca (XeLaTeX) ----------
\usepackage{iftex}
\ifXeTeX
\usepackage{fontspec}
\IfFontExistsTF{MADE Evolve Sans}{\setmainfont{MADE Evolve Sans}[AutoFakeSlant=0.18]}{\setmainfont{Latin Modern Sans}[AutoFakeSlant=0.18]}
\IfFontExistsTF{League Spartan}{\newfontfamily\helgentitle{League Spartan}[BoldFont={League Spartan SemiBold}]}{\newcommand{\helgentitle}{\sffamily\bfseries}}
\else
\usepackage[T1]{fontenc}
\renewcommand{\familydefault}{\sfdefault}
\newcommand{\helgentitle}{\sffamily\bfseries}
\fi
% ---------- Color de texto global ----------
\color{HelgenIndigo}
% ---------- Títulos de sección ----------
\titleformat{\section}{\helgentitle\Large\bfseries\color{HelgenIndigo}}{\thesection}{0.8em}{}[{\color{HelgenCyan}\titlerule[1.6pt]}]
\titleformat{\subsection}{\helgentitle\large\bfseries\color{HelgenIndigo}}{\thesubsection}{0.8em}{}
\titleformat{\subsubsection}{\helgentitle\normalsize\bfseries\color{HelgenIndigo}}{\thesubsubsection}{0.8em}{}
\titlespacing{\section}{0pt}{18pt}{10pt}
% ---------- Encabezado / pie ----------
\pagestyle{fancy}
\fancyhf{}
\fancyhead[L]{\helgentitle\small\color{HelgenIndigo}\textbf{\sistema} — \doctipo}
\fancyhead[R]{\helgentitle\small\color{HelgenIndigo}Hutchison Ports · ICAVE}
\fancyfoot[L]{\helgentitle\scriptsize\color{gray}Confidencial — Helgen Servicios Especializados S.A. de C.V.}
\fancyfoot[R]{\helgentitle\scriptsize\color{HelgenIndigo}\thepage}
\renewcommand{\headrulewidth}{0.4pt}
\renewcommand{\headrule}{\color{HelgenCyan}\hrule width\headwidth height 1.2pt}
% ---------- Estilos TikZ Helgen ----------
\tikzset{
caja/.style={draw=HelgenIndigo, fill=HelgenMist, rounded corners=2pt, text=HelgenIndigo,
align=center, font=\small, inner sep=5pt, minimum height=9mm},
cajaoscura/.style={draw=HelgenIndigo, fill=HelgenIndigo, rounded corners=2pt, text=white,
align=center, font=\small\bfseries, inner sep=5pt, minimum height=9mm},
zona/.style={draw=HelgenIndigo!50, dashed, rounded corners=4pt, inner sep=8pt},
etiqueta/.style={font=\scriptsize\bfseries, text=HelgenTeal},
flecha/.style={-{Stealth[length=2.6mm]}, thick, draw=HelgenIndigo},
flechaacento/.style={-{Stealth[length=2.6mm]}, very thick, draw=HelgenTeal},
}
\newcolumntype{L}[1]{>{\raggedright\arraybackslash}p{#1}}
\newcolumntype{C}[1]{>{\centering\arraybackslash}p{#1}}
% ---------- Listas con viñeta de marca ----------
\setlist[itemize]{label={\textcolor{HelgenTeal}{\rule[0.18ex]{0.5em}{0.5em}}}, leftmargin=1.4em, itemsep=2pt}
% ---------- Portada de marca (comando reutilizable) ----------
% Uso: \helgenportada{Título}{Subtítulo cian}{Bloque de metadatos (tabular)}
\newcommand{\helgenportada}[3]{%
\begin{titlepage}
\begin{tikzpicture}[remember picture, overlay]
\fill[HelgenIndigo] (current page.south west) rectangle (current page.north east);
\draw[HelgenCyan, line width=2.5pt] ([xshift=2.4cm, yshift=-8.1cm]current page.north west) -- ([xshift=-2.4cm, yshift=-8.1cm]current page.north east);
\node[anchor=north west, text=white] at ([xshift=2.4cm, yshift=-3.2cm]current page.north west)
{\helgentitle\fontsize{30}{34}\selectfont Helgen};
\node[anchor=north west, text=HelgenCyan] at ([xshift=2.4cm, yshift=-4.4cm]current page.north west)
{\helgentitle\small Ingeniería que produce, mantenimiento que protege};
\node[anchor=north west, text=white, text width=16.5cm] at ([xshift=2.4cm, yshift=-9.0cm]current page.north west)
{\helgentitle\fontsize{24}{30}\selectfont #1\\[6pt]
\helgentitle\fontsize{14}{19}\selectfont\color{HelgenCyan} #2};
\node[anchor=north west, text=white, text width=16.5cm] at ([xshift=2.4cm, yshift=-12.6cm]current page.north west)
{\helgentitle\fontsize{18}{22}\selectfont \sistema\\[5pt]
\normalsize Fleet RTK GPS — Hutchison Ports (ICAVE · TIMSA · EIT · LCT)};
\node[anchor=south west, text=white] at ([xshift=2.4cm, yshift=2.2cm]current page.south west)
{\footnotesize\helgentitle #3};
\end{tikzpicture}
\end{titlepage}}