From eef023e15ac432ae65076d6dcd68b06fd1cc9f0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leopoldo=20Rodr=C3=ADguez?= Date: Mon, 22 Jun 2026 18:00:16 -0600 Subject: [PATCH] auto-sync 2026-06-22 18:00 --- .../code-review-scale-readiness.md | 109 ++++++++++ .../watchdog-connections.xlsx | Bin 0 -> 21938 bytes .../watchdog-health-telemetry.md | 52 ++++- .../watchdog-pcb-v3-blockdiagram.svg | 13 +- .../watchdog-pcb-v3-bom.xlsx | Bin 17910 -> 17874 bytes .../watchdog-pcb-v3-design.md | 136 ++++++++---- .../watchdog-pcb-v3-kicad-guide.md | 10 +- .../watchdog-protoboard-bringup.md | 123 +++++++++++ .../~$Drogheda-BOM-Maestro.xlsx | Bin 0 -> 165 bytes .../~$watchdog-pcb-v3-bom.xlsx | Bin 0 -> 165 bytes 04-Source-Code/htracker | 2 +- ...6-22_Drogheda_Manual-Logs-Grafana_v1.0.pdf | Bin 0 -> 62399 bytes ...6-22_Drogheda_Manual-Logs-Grafana_v1.0.tex | 195 ++++++++++++++++++ 05-Analytics-Reports/estilo-helgen.tex | 117 +++++++++++ ...26-06-20_Drogheda_GNSS-Findings-Summary.md | 24 +++ CLAUDE.md | 10 +- 16 files changed, 731 insertions(+), 60 deletions(-) create mode 100644 02-Architecture-Software/code-review-scale-readiness.md create mode 100644 03-Architecture-Hardware/watchdog-connections.xlsx create mode 100644 03-Architecture-Hardware/watchdog-protoboard-bringup.md create mode 100644 03-Architecture-Hardware/~$Drogheda-BOM-Maestro.xlsx create mode 100644 03-Architecture-Hardware/~$watchdog-pcb-v3-bom.xlsx create mode 100644 05-Analytics-Reports/2026-06-22_Drogheda_Manual-Logs-Grafana_v1.0.pdf create mode 100644 05-Analytics-Reports/2026-06-22_Drogheda_Manual-Logs-Grafana_v1.0.tex create mode 100644 05-Analytics-Reports/estilo-helgen.tex diff --git a/02-Architecture-Software/code-review-scale-readiness.md b/02-Architecture-Software/code-review-scale-readiness.md new file mode 100644 index 0000000..d0dbdaf --- /dev/null +++ b/02-Architecture-Software/code-review-scale-readiness.md @@ -0,0 +1,109 @@ +--- +tags: [project-drogheda, backend, code-review, scale] +project: Project Drogheda +author: Helgen CTO +date: 2026-06-20 +status: Review — for the "add more units" test campaign +--- + +# Code Review — retranslator + monitor (scale readiness) + +> Review of the **hardened** `retranslator.py`, `web_monitor.py`, and the +> `gps_logger.py` they depend on, ahead of bringing more units online. The three +> 🔴 field-killers (HOL blocking, anti-jump black-hole, CGNAT identity) are already +> fixed — this pass is about **what else bites as the fleet grows**. Nothing here is +> on fire; the top items are the ones you'll actually hit while testing. + +Severity: 🔴 blocks/badly misleads at scale · 🟠 should fix before the fleet grows · +🟡 polish. + +> **Status (2026-06-20):** **R1, R2, G1, G2 are implemented and verified** — smoke +> (10/10, no regression), an R1/R2 focused test (7/7), and a standalone index+prune +> test all pass. Remaining: **M1** (dashboard auth — an nginx change, server-side) and +> the 🟡 polish items. New env knob: `GPS_RETENTION_DAYS` (default 30, 0 = off). + +## Summary + +The hardened code is solid and the concurrency model is sound. The remaining risks +are mostly **operational at scale**: a new unit that isn't in `device_config.json` +silently won't forward (and the monitor won't tell you why); the SQLite history has +no index and no retention (it will scan slowly and grow until the small disk fills); +the dashboard has no auth and re-renders 500 rows every 2 s; and a couple of latent +parsing/ack assumptions. No SQL injection (queries are parameterized) and no obvious +XSS. + +## 🔴 / 🟠 Issues to fix before adding units + +| # | File · location | Issue | Sev | +|---|---|---|---| +| R1 | `retranslator.py` · `_process_device_telemetry` (`wialon_id = device_cell_id_to_wialon_id.get(cell_id)`) | **An unconfigured `cell_id` is silently never forwarded.** A new unit's GPTXT marks it *online* (so it appears active in the monitor) but with no `wialon_id` mapping it's never sent to Wialon — and it shows as `wialon_state="idle"`, not a clear "unconfigured". This is the #1 thing you'll hit adding units. | 🔴 | +| G2 | `gps_logger.py` · whole file | **SQLite history grows unbounded.** ~288 rows/unit/day → ~144k/day at 500 units, no pruning. On the space-limited server this **fills the disk** over weeks. | 🔴 | +| G1 | `gps_logger.py` · `_create_table` | **No index** on `gps_positions`. `/api/availability` does `WHERE received_at … GROUP BY day,device` → full scan; at millions of rows the report crawls. | 🟠 | +| M1 | `web_monitor.py` / nginx | **No auth on the dashboard or API.** `monitor.helgen.dev` exposes live fleet positions + `/api/status` + `/api/availability` to anyone with the URL. Client-sensitive. | 🟠 | +| R2 | `retranslator.py` · `main()` UDP loop (`raw = udp_data.decode(...).strip()`) | **One NMEA sentence per UDP datagram is assumed.** If a unit ever batches sentences in one packet (`$GPTXT…\r\n$GNGGA…`), only the first is parsed. Latent today (firmware sends one per datagram) but a trap for the planned `$PHLG` injection. | 🟠 | +| R3 | `retranslator.py` · `WialonForwarder._deliver` (`response = s.recv(1024)`) | **Wialon data-ack isn't validated.** Only the *login* ack (`#AL#1`) is checked; the per-packet ack is read and discarded, so a **rejected** packet still counts as `acked` → monitor shows the unit healthy when Wialon isn't accepting it. | 🟠 | + +## 🟡 Worth doing + +| # | File · location | Suggestion | +|---|---|---| +| R4 | `retranslator.py` · config load | New units need a **restart** (`device_config.json` read once). Add a `SIGHUP` handler to reload the map live — no fleet-wide blip every time you add a test unit. | +| M2 | `web_monitor.py` · `renderTable()` (`tbody.innerHTML = rows.map(...)`) | Re-renders **all 500 rows every 2 s**, churning the DOM and dropping scroll/selection. Diff rows, or only re-render on change. | +| R5 | `retranslator.py` · `_build_data_packet` (`datetime.utcnow()`) | Wialon timestamp = **server receive time**, not the GGA fix time. Sub-second for a slow fleet, but parse the GGA UTC if you want exact fix times. | +| R6 | `retranslator.py` · `WialonForwarder.forget` | A `forget()` on a full worker queue is silently dropped → a socket can leak until the next send error. Bounded; low impact. | +| R7 | `retranslator.py` · `_deliver` ack read | Residual per-worker HOL: one slow ack blocks that worker's ~8 devices for up to `TCP_RW_TIMEOUT` (15 s). Far better than global; consider lowering the read timeout. | +| G3 | `gps_logger.py` · `_insert` | `commit()` per row. Fine at the 300 s throttle; revisit if the throttle drops or inserts burst. | +| M3 | `web_monitor.py` · `sse_events` | Re-parses the whole status JSON every 2 s only to validate. Trivial now; grows with the device count. | + +## What looks good ✅ + +- **Concurrency model** — UDP ingest fully decoupled from Wialon I/O; per-worker + queues with drop-oldest backpressure; stats behind a lock; `list()` snapshots in + the stats/cleanup threads (no "dict changed size"). +- **Identity** — `(ip,port)` transport + `cell_id` state is the right CGNAT-safe shape. +- **Anti-jump** — time-aware + self-healing + cleared on disconnect; can't black-hole. +- **Safety** — atomic status-file write (`.tmp` + `rename`); fail-safe drops; SQL writer + is a bounded background thread that never blocks the loop. +- **Security hygiene** — availability SQL is **parameterized** (`?`), DB opened + read-only, free-text escaped (`esc()`), SSE degrades to "gateway down" cleanly. + +## Before you test more units — do these first + +1. **R1 — make unconfigured units obvious.** Two cheap parts: log a one-time + `WARNING` per unknown `cell_id`, and surface a distinct `wialon_state="unconfigured"` + in the monitor so a new unit that isn't forwarding is self-explanatory. + ```python + # in _process_device_telemetry, where wialon_id is looked up: + wialon_id = device_cell_id_to_wialon_id.get(cell_id) + if not wialon_id: + if cell_id not in _warned_unconfigured: # module-level set() + _warned_unconfigured.add(cell_id) + log.warning(f"[CONFIG] cell_id {cell_id} sin wialon_id — " + f"agrégalo a device_config.json o no se reenvía.") + # still record telemetry/odometer; just don't forward + ``` +2. **G2 — cap the SQLite growth.** A nightly prune keeps the disk safe: + ```sql + DELETE FROM gps_positions WHERE received_at < datetime('now','-30 days'); + ``` + (run from the writer on a daily timer, or a cron `sqlite3` one-liner + `VACUUM`). +3. **G1 — add the index** (one line in `_create_table`, idempotent): + ```sql + CREATE INDEX IF NOT EXISTS idx_gps_recv_dev + ON gps_positions(received_at, device_cell_id); + ``` +4. **M1 — gate the dashboard.** Fastest is nginx Basic Auth on `monitor.helgen.dev` + (one `auth_basic` + htpasswd), or an IP allowlist for the office/VPN. +5. **R2 — split multi-sentence datagrams** (`for line in raw.split('\n'): …`) so the + loop is robust before you wire any new sender (incl. `$PHLG`). + +## Verdict + +**Approve, with the R1/G1/G2 items recommended before the test campaign.** The core is +production-grade; the gaps are operational (config ergonomics, DB lifecycle, dashboard +auth) and quick to close. R1 and G2 are the two that will actually bite you while +adding units. + +## Related +- [[02-Architecture-Software/backend-review|backend-review]] (the original 🔴 findings + resolution) · + `04-Source-Code/htracker/retranslator.py` · `web_monitor.py` · `gps_logger.py` diff --git a/03-Architecture-Hardware/watchdog-connections.xlsx b/03-Architecture-Hardware/watchdog-connections.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..aab7f8f72fc4a8e1c693f21c4812efbe5f0b40f0 GIT binary patch literal 21938 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 HqhJ36pn<<# literal 0 HcmV?d00001 diff --git a/03-Architecture-Hardware/watchdog-health-telemetry.md b/03-Architecture-Hardware/watchdog-health-telemetry.md index 5f0e144..79d99eb 100644 --- a/03-Architecture-Hardware/watchdog-health-telemetry.md +++ b/03-Architecture-Hardware/watchdog-health-telemetry.md @@ -106,7 +106,10 @@ talker** instead — `$PHLG` (`P` = proprietary, `HLG` = Helgen): ```text $PHLG,,,,,,,,,,,,,,*CS -e.g. $PHLG,2.1,RUN,LIMP,1,12,34192,4.01,5.02,63,71,2,1,420,IWDG*4F +e.g. $PHLG,2.2,RUN,DEGR,1,12,34192,3.96,5.01,58,69,0,0,180,POR*11 + (checksum is the standard NMEA XOR over the chars between $ and *, computed at + runtime by buildPHLG(); -127 in tc/tmax = "n/a". The earlier *4F here was a wrong + hand-written placeholder — the correct XOR for that body is *49.) ``` Fields mirror the v2.1 `TLM` record (state, link, fix, sats, rx, battery, 5 V, @@ -120,12 +123,55 @@ live**: MCU temp / `tMax` (the thermal-hypothesis evidence, without pulling the reset cause (reboot-loop forensics), recovery count, and LIMP state — and it ships to Loki for free. +### Backend `$PHLG` branch (concrete — review before deploy) + +Drop-in for `retranslator.py`. `cell_id` is already resolved from `(ip,port)` by the +existing CGNAT-safe map; this only adds recognition + checksum + store. Tolerant of the +**extra trailing fields** the v3 board will append (air/RH/battery/fan). + +```python +# --- $PHLG health branch (v2.2) — REVIEW BEFORE DEPLOY ------------------------ +def _nmea_ok(s: str) -> bool: + if not s.startswith('$') or '*' not in s: + return False + body, _, cs = s[1:].partition('*') + x = 0 + for ch in body: + x ^= ord(ch) + return f"{x:02X}" == cs.strip()[:2].upper() + +PHLG_FIELDS = ("fw","state","link","fix","sat","rx","vbat","v5v", + "tc","tmax","rec","limp","nmea_age_ms","reset") + +def handle_phlg(line: str, cell_id: str, store: dict) -> bool: + if not line.startswith('$PHLG,') or not _nmea_ok(line): + return False + body = line[1:].split('*', 1)[0] + parts = body.split(',')[1:] # drop the 'PHLG' talker + rec = dict(zip(PHLG_FIELDS, parts)) # extra v3 trailing fields are simply ignored + rec["ts"] = time.time() + store[cell_id] = rec # latest health per unit (also ship to Loki) + log.info("PHLG %s temp=%s tmax=%s reset=%s limp=%s rec=%s", + cell_id, rec.get("tc"), rec.get("tmax"), rec.get("reset"), + rec.get("limp"), rec.get("rec")) + return True + +# In the inbound loop, BEFORE the GGA/position path: +# if handle_phlg(line, cell_id, PHLG_BY_CELL): +# continue # health record, NOT a position fix — don't forward to Wialon +# ----------------------------------------------------------------------------- +``` + --- ## 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:* +1. **Does the 4G NTRIP Master forward a proprietary `$P…` sentence?** + ✅ **RESOLVED 2026-06-22 — YES.** Enabling `$PUBX` (a u-blox proprietary `$P…` talker) + on the F9P UART2 → it arrives at the server `udp/5010` **intact**, alongside GGA/RMC. The + modem is a transparent forwarder; **no whitelist**, so the `$GPTXT,HLT` fallback is not + needed. (Detail: `03-Architecture-Hardware/watchdog-pcb-v3-design.md` §7a.) Original note: + 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.** diff --git a/03-Architecture-Hardware/watchdog-pcb-v3-blockdiagram.svg b/03-Architecture-Hardware/watchdog-pcb-v3-blockdiagram.svg index 5390b02..18e5e64 100644 --- a/03-Architecture-Hardware/watchdog-pcb-v3-blockdiagram.svg +++ b/03-Architecture-Hardware/watchdog-pcb-v3-blockdiagram.svg @@ -1,6 +1,6 @@ HBOX Watchdog Carrier PCB v3 — power and signal map -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. +24 V cabinet feed passes reverse-polarity protection, then splits: to a new dedicated fan buck feeding a 5 V fan driver and the extraction fan; 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 driver connect to the MCU. @@ -28,21 +28,16 @@ Fan buck U2 24→5 V dedicated rail · JP1 -Fan drv 1 +Fan driver Q2 · flyback - -Fan drv 2 -Q3 · flyback -2× 5 V fans +1× 5 V fan extraction · ext 24 V - 5V_FAN - STM32F411 Blackpill watchdog · on headers @@ -75,7 +70,7 @@ PB0 -PWM + tach PA6/7 · PB10/12 +PWM + tach PA6 · PB10 BAT sense PA0 diff --git a/03-Architecture-Hardware/watchdog-pcb-v3-bom.xlsx b/03-Architecture-Hardware/watchdog-pcb-v3-bom.xlsx index 062f759a20f1ef95b8c9d7460793f9d94466213d..05afc703b9ab83da201ccbb085f88657770a0bbd 100644 GIT binary patch delta 10673 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 TA^fi}A72EbM~WB!NBw^QiOeZj diff --git a/03-Architecture-Hardware/watchdog-pcb-v3-design.md b/03-Architecture-Hardware/watchdog-pcb-v3-design.md index 1e6ebe8..2f5cca6 100644 --- a/03-Architecture-Hardware/watchdog-pcb-v3-design.md +++ b/03-Architecture-Hardware/watchdog-pcb-v3-design.md @@ -23,9 +23,10 @@ supersedes_section: hardware-respec-and-pcb-brief.md §4 (watchdog carrier detai | # | 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). | +| 2 | **5 V fan ×1** | One extraction fan, low-side (MOSFET PWM) + tach. ⚠ Loads the same 5 V the modem bursts on — mitigated with a **dedicated fan rail** (§0a). Needs a **passive filtered inlet** to pull air through, and has **no redundancy** — a fan failure falls back to conduction + thermal derate (tach-fault flags it). | | 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. | +| 5 | **Live telemetry ($PHLG): bench-test first, NOT built into v3** (2026-06-22) | The 4G modem keeps its proven home on the F9P **XBee socket = UART2**; there is **no spare UART** to tap (see §2a). v3 ships health on the **USB STREAM log** (already works). PA9/PA10 header is **reserved/DNP** only. The forwarding question is settled by an isolated **bench experiment** (§7a), not by a board change. | --- @@ -33,15 +34,15 @@ supersedes_section: hardware-respec-and-pcb-brief.md §4 (watchdog carrier detai ### 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 +**~2–2.5 A on LTE-TX bursts** — the brownout the whole redesign is fixing. One +5 V fan adds **~0.15–0.25 A plus inrush**. Coincident worst case ≈ 2.7 A still +nears 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 fan its **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. +inside the HBOX). A **0 Ω jumper (JP1)** lets you instead bridge the fan to the main +5 V — with a single ~0.2 A fan that's more defensible than with two, but the +dedicated rail stays the safe default. **Do not power the fan from the *switched* +(load-switch) stack 5 V** — it 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 @@ -85,7 +86,7 @@ fragile hardware**. Keep the brains, fix the body. | 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 thermal *actuation* | firmware can derate but can't cool | **1 fan driver + closed-loop control** (§4, §7) | | No SWD/header bring-out | can't debug a sealed unit | **SWD + USB + reserve-USART1** headers (§6) | --- @@ -103,11 +104,11 @@ fragile hardware**. Keep the brains, fix the body. 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) + ── NEW for v3 (fan + temperature) ──────────────────────────────────────── + PA6 FAN_PWM TIM3_CH1 25 kHz PWM → fan low-side gate + PB10 FAN_TACH EXTI fan tach (open-collector, 10k pull-up to 3V3) + PA7 (spare) TIM3_CH2 reserved — 2nd fan PWM if ever needed + PB12 (spare) EXTI reserved — 2nd fan tach if ever needed 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) ─── @@ -117,10 +118,29 @@ fragile hardware**. Keep the brains, fix the body. 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 +No conflicts: TIM3_CH1 (PA6) does fan PWM; PB0 (TIM3_CH3) stays plain-GPIO +LED. The reserved USART1 pair (H3) **future-proofs** the board for the [[03-Architecture-Hardware/watchdog-health-telemetry|`$PHLG` health-in-server]] upgrade -without a respin. +without a respin — but in v3 it is **left unpopulated; no relay is wired** (see §2a + the +decision row above). **PA10 caveat:** PA10 is USART1-RX *and* a known Blackpill DFU-entry +pin — a load/float here can block flash mode. Keep H3 unpopulated, or fit a **~22 kΩ +pull-up on PA10 to 3V3**, so this board never inherits the flaky-flashing gremlin. + +### 2a. Why there is no spare UART (the telemetry reality) + +The simpleRTK2B **Pro** breaks out **both** F9P UARTs, but only one is free: + +| F9P port | Header pins | Used by | Free? | +|---|---|---|---| +| **UART1** | TX1 / RX1 | **watchdog** (NMEA read + UBX rate) — PA2/PA3 | taken | +| **UART2** | TX2 / RX2 **and the XBee socket** | **4G NTRIP Master** (server uplink) | **no — shared bus** | + +`TX2` = F9P-UART2-TX = the **modem's DIN** (the wire the modem reads and forwards to the +server). The F9P already drives it, so the watchdog can't simply "connect a second wire" to +send `$PHLG` — that's two transmitters on one line. Getting health to the server therefore +needs a **relay** (interpose the MCU between F9P-UART2 and the modem), which is a deliberate +architecture change **deferred out of v3**. To *learn whether it's even possible*, run the +isolated bench experiment in **§7a** — it touches no PCB. --- @@ -139,10 +159,8 @@ without a respin. 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 + 5V_FAN ───► Fan (+) ┌─── Fan tach ─10k─► PB10 + Fan (−) ─ Q2 (AO3400) ◄PWM PA6 ┘ (D2 SS34 flyback ∥ fan) I²C1 (PB6/7) ─┬─ SHT40 (U3, 0x44) board ambient temp + RH └─ TMP102 (U4, 0x48) on P5 pigtail → battery/modem hot spot @@ -202,19 +220,19 @@ solid, sourceable SMD, plus a resettable fuse for the missing current limit: 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): +### 4.4 Fan driver ×1 (low-side PWM + tach) — Q2 +Single extraction fan: ``` - 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] + 5V_FAN ──► Fan(+) Fan tach ──[R_t 1k]──┬──► PB10 (EXTI) + Fan(−) ──┬──► Q2 Drain │ + D2 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 / +- **Q2 = 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 +- **D2 = SS34** Schottky **flyback** across the 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 @@ -266,8 +284,7 @@ ADC stays ≤3.3 V with margin (verified in firmware-review). Firmware | 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 | +| P6 | JST-XH 4-pos | 4 | 5V_FAN, Fan−(PWM), GND, Tach → fan | | 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 | @@ -284,9 +301,8 @@ ADC stays ≤3.3 V with margin (verified in firmware-review). Firmware 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. +1. **Pins**: add `PIN_FAN_PWM PA6`, `PIN_FAN_TACH PB10` (PA7/PB12 stay spare); + init TIM3 CH1 PWM at **25 kHz**; attach an EXTI counter on the tach pin. 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)`): @@ -298,15 +314,55 @@ sandbox can't build STM32). New globals + functions, no state-machine surgery. 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. +5. **Telemetry (on-unit only in v3)**: extend the v2.1 `TLM` record with + `tair,rh,tbat,fan_rpm,fan_pwm,fan_fault`, captured to the **USB STREAM log**. + These are the same fields a future `$PHLG`-to-server sentence would carry — but **v3 does + NOT push them live to the server** (no UART relay; see §2a + decision row 5). Live + telemetry is gated on the bench experiment below. 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. +### 7a. The one bench experiment that decides live telemetry (no PCB change) + +> **✅ RESULT — 2026-06-22: PASS (no extra hardware needed).** Enabled `$PUBX` (a u-blox +> **proprietary** `$P…` talker — same class + checksum format as `$PHLG`) on the F9P +> **UART2** and watched the server `udp/5010` capture: `$PUBX,00,…` arrives **end-to-end, +> intact**, alongside GGA/RMC. → **The 4G NTRIP Master forwards proprietary `$P…` +> sentences, so `$PHLG` health-in-server is confirmed viable.** No XBee-breakout test +> needed. Remaining work = the **UART2 relay** (interpose the watchdog to inject `$PHLG`) +> \+ backend parser — a deliberate later board, still out of v3 scope. +> +> *Incidental (flagged for GNSS follow-up):* the same capture caught two live units — +> bench **H0014 (Querétaro) at SPS (GGA quality 1), RSSI −93**, and field +> **H0017 (Veracruz/ICAVE) at DGPS (quality 2, RMC mode `D`, diff base id 0133), RSSI −51**. +> Per Leo the bench is in a **basement** (poor sky + weak cellular) — which alone explains +> the bench SPS, since **RTK fix needs open sky (carrier-phase) regardless of +> corrections/config**. But note H0017 has *good* sky+signal and still only reaches **DGPS, +> not RTK (4/5)** → the **corrections pipeline** (free rtk2go caster + RAM-only F9P config) +> is a *separate* ceiling from antenna placement. One **open-sky test with a proper caster + +> FLASH-saved config** separates the two. (Reinforces the v2.1 SPS-thrash guard: a healthy +> basement/field unit may legitimately sit at SPS/DGPS — do **not** power-cycle it.) + +Settles "does the 4G NTRIP Master forward a proprietary `$P…` sentence?" by **isolating the +modem** — the F9P is not involved, so there's no bus collision: + +1. Remove the **4G NTRIP Master** from the F9P XBee socket; mount it on a bench/XBee adapter + with **3.3 V power, the SIM, and the LTE antenna**. +2. Wire **Blackpill PA9 (USART1 TX) → modem DIN** (the `TX2`/F9P-UART2-TX net in the socket) + and **modem DOUT → Blackpill PA10 (RX)** *or* a USB-UART so a PC can watch it. +3. Match the modem's UART baud (ArduSimple default **115200**, 3.3 V). Let it register. +4. Emit `$PHLG,2.1,RUN,LIMP,1,12,34192,4.01,5.02,63,71,2,1,420,IWDG*CS\r\n` and watch the + **server `tcpdump`**. + - **Appears →** modem forwards proprietary sentences → `$PHLG` is viable → *then* plan a + relay-capable board. + - **Doesn't →** modem whitelists talkers → fall back to a `$GPTXT,HLT,…` subtype + (special-cased by **field 1** so it never collides with the cell-id parse). + +Either way, **v3 ships unchanged** — this experiment only informs a *later* decision. + --- ## 8. Layout, stackup & DFM (for a relative beginner — the rules that matter) @@ -342,8 +398,8 @@ Pin map and control law live here; the implementation lands in 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`. +3. **Fan**: ramp PA6 PWM 0→100 %, scope the gate + fan node, confirm flyback, + read **tach RPM** vs PWM; pull the 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. @@ -359,7 +415,7 @@ Pin map and control law live here; the implementation lands in - [ ] **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 +- [ ] **Fan choice (×1)**: 5 V **3-wire** (tach), size (40/50/60 mm), + a **filtered passive inlet** so the single extraction fan can pull air through. 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. diff --git a/03-Architecture-Hardware/watchdog-pcb-v3-kicad-guide.md b/03-Architecture-Hardware/watchdog-pcb-v3-kicad-guide.md index b10c447..571c362 100644 --- a/03-Architecture-Hardware/watchdog-pcb-v3-kicad-guide.md +++ b/03-Architecture-Hardware/watchdog-pcb-v3-kicad-guide.md @@ -96,11 +96,11 @@ For each, place the symbols, wire locally, and tie to power/net labels: 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**. +- **Fan driver ×1 (§4.4):** Q2 AO3400A, D2 SS34 flyback, Rg/Rgs gate parts, tach + pull-up + series into **PB10**. PWM gate from **PA6** (PA7/PB12 left as spares). - **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. +- **Connectors (§6):** P1–P6 + P8, H1, H3 (DNP), and the two 2×20 sockets. ### 4.4 Annotate + ERC - **Tools → Annotate** (gives refs R1, U2, …). @@ -140,7 +140,7 @@ on **easyeda2kicad** / the JLC part page, grab the EasyEDA footprint and import. **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 + corner, load switch near P3, fan driver near P6, 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). @@ -204,7 +204,7 @@ pigtail when it arrives. - **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 +- **Polarity**: double-check D10 TVS, C10 electrolytic, D2 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 diff --git a/03-Architecture-Hardware/watchdog-protoboard-bringup.md b/03-Architecture-Hardware/watchdog-protoboard-bringup.md new file mode 100644 index 0000000..0194f58 --- /dev/null +++ b/03-Architecture-Hardware/watchdog-protoboard-bringup.md @@ -0,0 +1,123 @@ +--- +tags: [project-drogheda, hardware, watchdog, prototype, breadboard, bom, bring-up] +project: Project Drogheda +author: Helgen CTO +date: 2026-06-22 +status: Active — shop list for the Blackpill bring-up +--- + +# Watchdog Bring‑up — Protoboard Shopping List (print me) + +> Goal: breadboard the v2.2 watchdog on your **existing Blackpill**, prove the +> state machine + sensing + load switch + fans + temp loop, **before** any PCB. +> All parts are **through‑hole / breadboard‑friendly** and stocked at a normal MX +> electronics shop (Steren / AG Electrónica / local). Quantities include spares. +> Pin map: [[03-Architecture-Hardware/watchdog-pcb-v3-design#2|design §2]] · +> firmware: `blackpillrtkcode_v2.ino` (v2.2). + +## 0. Already have — do NOT buy +STM32F411 Blackpill · USB‑C/micro cable · 18650 + PowerBoost · Mean Well SD‑15B‑05 +(or a bench PSU) · multimeter · the F9P + 4G master (only needed later, not for the +bring‑up). A **heat source** (hair dryer / lighter / hot water cup) to trigger the +thermal/fan test — household, no need to buy. + +--- + +## 1. Core — bring up the "brains" (buy this even if the shop is sparse) + +| # | Item | Qty | Value / part | ~MXN | For | +|---|---|---|---|---|---| +| 1 | Breadboard 830‑pt | 2 | half/full | 120 | build surface (2 = room for fans/sensors) | +| 2 | Jumper wires M‑M | 1 pk | 65 pcs | 60 | board wiring | +| 3 | Jumper wires M‑F | 1 pk | 40 pcs | 50 | to Blackpill / modules | +| 4 | Resistor assortment box | 1 | ¼ W (incl. 330Ω,1k,4.7k,10k,100k,220Ω) | 120 | dividers, gates, pull‑ups | +| 5 | LED (any) | 5 | 3/5 mm | 10 | status LED on PB0 | +| 6 | Ceramic cap 100 nF | 10 | 0.1 µF (104) | 20 | ADC filters + decoupling | +| 7 | Electrolytic 100 µF / ≥16 V | 4 | — | 20 | rail bulk / modem‑burst reservoir | +| 8 | **2N3904** (NPN, TO‑92) | 3 | load‑switch driver | 15 | PA1 → P‑FET gate | +| 9 | **IRF9540 / IRF9540N** (P‑MOSFET, TO‑220) | 2 | high‑side 5 V switch | 40 | switched 5 V to "stack" | +| 10 | **10 kΩ trim pot** | 2 | breadboard trimmer | 30 | sweep VBAT / ignition to test thresholds | +| 11 | Tactile button or slide switch | 2 | — | 20 | simulate ignition on/off | +| 12 | Male header strip | 1 | 1×40, 2.54 mm | 15 | solder onto any module that needs pins | + +**Core subtotal ≈ 520 MXN.** With this you can flash v2.2, watch the `$PHLG`/TLM log, +and validate the **load switch + the three sense dividers + LED** — the whole watchdog +logic — without the thermal parts. + +--- + +## 2. Thermal add‑ons — fans + temperature (the v3 new stuff) + +| # | Item | Qty | Value / part | ~MXN | For / note | +|---|---|---|---|---|---| +| 13 | 5 V fan, 40–60 mm, **3‑wire** | 1 | 3‑wire = has tach | 60 | one extraction fan; 3‑wire so PB10 reads RPM. Grab a 2nd as a spare if cheap | +| 14 | **IRLZ44N** (logic‑level N‑MOSFET, TO‑220) | 2 | fan low‑side PWM | 30 | 1 + spare; **must be logic‑level**; see substitute below | +| 15 | Schottky **1N5819** | 3 | fan flyback | 10 | one across the fan (1N4007 ok if out) | +| 16 | I²C temp+RH breakout | 1 | **SHT31 / SHT40** ideal | 120 | matches firmware (0x44). RH catches condensation | +| 17 | Analog temp **LM35** (TO‑92) | 2 | sure‑thing fallback | 25 | every shop has it; heat it → watch fans ramp | +| 18 | **DS18B20** waterproof probe | 1 | 1‑wire | 45 | great as the battery/modem hot‑spot probe | +| 19 | LM2596 buck module (adj.) | 1 | 24/12 → 5 V | 35 | dedicated **fan 5 V** rail (caveat §0a) if no 2nd bench PSU | + +**Thermal subtotal ≈ 260 MXN** (one fan + driver + one temp option). Grab #13–15 for the fan, and **one** temperature option +(#16 ideal, else #17/#18). + +--- + +## 3. Substitutions (shop stock varies — these all work) + +| If they're out of… | Buy instead | Catch | +|---|---|---| +| IRLZ44N (logic‑level FET) | **2N2222A** (TO‑92) or **TIP120** (Darlington) | BJT: add 1 kΩ base resistor; fine for a <0.5 A fan. **Plain IRF540/IRFZ44 won't fully turn on from 3.3 V — avoid.** | +| SHT31/SHT40 I²C breakout | **BME280/BMP280** (I²C, common) | address 0x76/0x77 → small firmware change | +| Any I²C sensor | **LM35** (analog) or **DS18B20** (1‑wire) | not I²C → firmware reads ADC / 1‑wire instead (I'll patch it) | +| IRF9540 P‑MOSFET | keep your original, or **2N2222 high‑side** is awkward — ask for any P‑channel ≥30 V | gate fully pulled to GND by the 2N3904, so a standard P‑FET is OK here | +| 1N5819 Schottky | **1N4007** | slightly slower; fine at fan PWM | + +--- + +## 4. Bring‑up order (so the list maps to actions) + +1. **Just USB** — flash v2.2, open serial @115200. Confirm `BOOT/COLS/TLM` lines **and a + `$PHLG,...*CS` every 30 s**. (No external parts — this proves the firmware.) +2. **LED on PB0** via 330 Ω → watch the blink codes change with state. +3. **Load switch** — 2N3904 + IRF9540 + 1 kΩ (base) + 10 kΩ (gate pull‑up) from a 5 V rail; + put an LED+330 Ω on the switched output. Toggle: confirm **OFF at reset**, ON when PA1 + drives. (This is the heart of recovery.) +4. **Sense dividers** — two 10 kΩ each (÷2) into **PA0** (battery via 18650) and **PA4** + (5 V rail); a 10 kΩ pot into **PA5** (ignition). Sweep them and watch `veh`/`ign` flip in + the TLM with hysteresis/debounce. +5. **Fan** — PA6 → gate (220 Ω) of IRLZ44N → fan(−); flyback diode across the fan; + 100 kΩ gate→GND. Watch it spin/ramp; tach wire → 10 kΩ pull‑up to 3V3 → PB10 → + RPM in the log; **unplug the fan → `FAN_FAULT`**. +6. **Temperature** — I²C breakout on PB6/PB7 (+4.7 kΩ pull‑ups), or LM35 on an ADC pin. + Heat it → fans ramp per the 45→70 °C curve; at 75 °C the existing thermal derate kicks in. + +--- + +## 5. Wiring cheat‑sheet (Blackpill side) + +``` +PA0 ← BAT ÷2 (10k/10k) from 18650+ PA6 → 220Ω → IRLZ44N gate; fan(−); 1N5819 ∥ fan +PA4 ← 5V ÷2 (10k/10k) from 5V rail PB10 ← fan tach, 10k pull-up → 3V3 (1k series) +PA5 ← IGN ÷2 (10k/10k) from pot/switch PB6/PB7 → I²C SCL/SDA (+4.7k each → 3V3) +PA1 → 1k → 2N3904 base; collector → IRF9540 PB0 → 330Ω → LED → GND + gate; 10k gate→5V; drain → switched (PA7/PB12 left free — 2nd fan if ever needed) + 5V → stack (use an LED load to test) +GND/5V/3V3 rails common; fan 5V from a SEPARATE 5V (LM2596 or 2nd PSU) — not the stack rail. +``` + +--- + +## 6. Note on the temp sensor + firmware +The v2.2 firmware reads **I²C SHT40 (0x44) + TMP102 (0x48)**. Whatever you bring back: +- **SHT31/SHT40** → works as‑is (SHT40) or a 1‑line address tweak (SHT31). +- **BME280** → change address + read call. +- **LM35 / DS18B20** → analog / 1‑wire; tell me which and I'll patch `buildPHLG()` + + the fan‑curve input to read it. For *tomorrow's* bring‑up the fan loop can also be + driven off the **MCU‑internal** temp (already in firmware) — heat the whole board and + the derate still triggers, so you can prove the fan path even before the sensor arrives. + +## Related +- [[03-Architecture-Hardware/watchdog-pcb-v3-bom|v3 BOM (production + bench, xlsx)]] · + [[03-Architecture-Hardware/watchdog-pcb-v3-design|v3 design (pin map, sub‑circuits)]] · + `04-Source-Code/firmware/blackpillrtkcode_v2/blackpillrtkcode_v2.ino` diff --git a/03-Architecture-Hardware/~$Drogheda-BOM-Maestro.xlsx b/03-Architecture-Hardware/~$Drogheda-BOM-Maestro.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..bb7c09c662dca4fd8bc9e5c39ef724bbd842ae4d GIT binary patch literal 165 zcmbV>!3jVi3_~-Arw-&vCn!`zC{zkwoZd|E4?iIxts literal 0 HcmV?d00001 diff --git a/03-Architecture-Hardware/~$watchdog-pcb-v3-bom.xlsx b/03-Architecture-Hardware/~$watchdog-pcb-v3-bom.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..bb7c09c662dca4fd8bc9e5c39ef724bbd842ae4d GIT binary patch literal 165 zcmbV>!3jVi3_~-Arw-&vCn!`zC{zkwoZd|E4?iIxts literal 0 HcmV?d00001 diff --git a/04-Source-Code/htracker b/04-Source-Code/htracker index f93a36d..ed51abe 160000 --- a/04-Source-Code/htracker +++ b/04-Source-Code/htracker @@ -1 +1 @@ -Subproject commit f93a36d3ae8d64ead1036f422a32024eb3cb9cbf +Subproject commit ed51abe602ba401350347b0be135197af333e027 diff --git a/05-Analytics-Reports/2026-06-22_Drogheda_Manual-Logs-Grafana_v1.0.pdf b/05-Analytics-Reports/2026-06-22_Drogheda_Manual-Logs-Grafana_v1.0.pdf new file mode 100644 index 0000000000000000000000000000000000000000..9cfe241f2b595a0effaeda0ab20950498a2d8a5d GIT binary patch literal 62399 zcmd43bC4x#o9ki+qP}nwr$(CZChQoZFkx3I(wfu@y(ui-}!dziRp;>V`Z$B z3vpk0t?QRh-uYw_%L$24)6+0R5g*=MzCbbK)8X41SU_=c(JHyy8PlrC8dw+`I?*aR z8#w*>TEy1D`u{Hed>5adRzSekO_T1AFVWNe--kA>w7!#rxtk{aAM5^D^XF2VR>9cO z*4e?(*b$25j};2EBIZ_3#tyV1R{BoHLdJ%+M#i*K#x|x-X7~)uJUmd2P7cQU)=+Ni ziga>zs&sN7m>U|4 zI_SIe@cbnQ^nV8J|IZj^`u`#b!2+P5g80S|hp0f}!1zINW9?9HHtk?gG_O$pd}RPj z5CD~Tuqyu=5&A!)_dow%`{&A8|7Vql{6%>cFs$moQT}`A@XwY19Yy}3=OTYqjtr~z zugV#I4`2SVa)!Ud&_7f@{8#0~u=h2d}c!tjswDIx!{Q-K6%Cxs3DSI-!K4`BYoGseFo$A8l^ z#^1w-e@sTkzrzUgKlJQBb_#!ZCJr0^Z#?@wg!zkSe}|6$u4lgo5dYk>zvIY1^^D~I zW8)v5S;9vC8_#|ZV*cXU-?8Jr=^4}S1_aY@0tD0V0mOf7ai+gZ{(pCi|L%A*{ib*` z{V$0()Bkk5P5v71XxQj~jW^TpiT6Jy8`IxW;GdH1_^;A)U}OGOI`i)V#6OnK{C6Dr zr_%pp!21W(Yhh#mjr8Axn13$)?@07drKkMW^*-3Rf0fSi7uWymW*|KSGt*y3te2`& zF<7iHJEi?OU30gVRyjxETOv#kIN$MNEu3o`$UCK*-RZRO;W! z6+T`Ve^vq&-vU!$epoR34*SBtcqN?dJ$}vn+7WDev8)==JuiH_oZr{YWyxaLo^0O- zaLO%I&AxVY0ST?Gs6)p~8#^38Rlqk_eU>Aea=iO#D=Z*1ZT;pnmWK(L+5u6svKy_O zy99eqpRmtf#+xkQ?O`2%JpNMztmex;9xyofYVHF+MMLZ*lN*ufI{lXy;+gby16(>y zxuA7}qu;Q9D370G%(N?@yF-ghXAIPrPp{6=Mt!X@84b?|dX-nHQT28uJf6Lp+zT)d zWzvcur{6Y4DF@bD8dA2vkKm5_Zf_;qZGQpR668h?uI#h7HZSxh7nyU#c|BCvw^7IQ zk=WSlrP|obQ^8E;VxJ${IEVMV4^i-JDGC8$u>{DISV}>1*A0i!k(voCHZZvMjz1kC zt}0Rq-JQ?%VG-P~WZ7mkXgw~XCV*?OnT7JSwQe(1ObW2f7pB|v(|6nI!iZH=&czV$ z5RSL>n^xeGy^~zd^QEyjZX#x{g~<+AHx6}Va9d{bE}7N(2WrT-ABKF1b4a>!egrsWf$X*Sed9NELRHRx${-f z3jm7&BpAzJRfMcztHXn`wUub<1tI|0jw^CDE!Vdy*LEu7RAkdh8=8mD?DyM9J#;^{ z_LXh5hxerY^3DzT@b9&2FF|Q9q5?9n``-%f zfymq(S$?8OG_y$KA zJtoB1AUz}e!WD3mCBDy+e}f71#R3APPkn+W2tWt`*bojNSc|28BaYq*L?WTGtbHo$ z)B^n8)50QuY?)vyT$2YcDJCAH#Y40)PhNbSY+`2a&9bd>rCr9)-cG-oe>?2I=RJIA z4L{uq!ixo>(uX*1L%JbcXIsyPITK)cvxv`=YI4@D*4r=CUS6@_m0mLnfN|hhUD2MV z#v-qM@Yt@95HmU{?QdW@wMms?&}`?au}G04e!t!%QLE{I!J3JAr*tH5)Trt{nV8hR zFl6M!?kv%RZn&-M+)g)VH&r?h2kyINU6y>?Wx6}7=v2JsLqvCV^emJns zAyr-B2t@1451%QrQ|sDtOR$qfZ*uQ2yDoeN6_hfc-zAdd$z$1VQQ0g!vTZi27HFkY z&C?JJhoeWDPK*7+s(qR4{*7ACGpbg}C04n!s!n~!uIDl^?Af-Q%WS#t010(@KCYD--y?qm6mlN15TYO4OBJ;w^-ZUON9U)c$M{eX{16=XiWqlYo$Pk3^;KCj@M zz6*N)%1X9B$6Noearh^M^5!L@T_i~DSAE4_Yd9$Z}hn|OT1mM!P1_y?Yg&K_Pnr}u_9 zpw2m{4+!r_W zxgEkI!g|IEO%UE`53L|n5$yKP%@^uf)3k#ah$DLdBKCgSqM)Dz_oMb+MuEe*ZGKum zA5jORM$HbBlQb(Lundns5EYA?g)RJe{Lb}bkY+^MPb{Faj~Q-z01ZppbRy`~-4;Wa z;_wokFgWTZRuT8BndCSCTFqE*Q&>oneVVs02(p51_Mi(m4y<<^dVGmqnjR$~Od28t z;g%DsN#PZ7nkzG6lg6Ji35FU`qSoF*+LyE%Zm1M8lJ}aFimyn3^wH0ECcKLXE18Fl z^o}?yj{gYoQ^q*fQy8CZ@iG3GbQUS2mxjAg*9X2l7McduMt7A(seGuupgbz|$Bn0h z>*2s>8y5ETH{FdPP-s)heH#)inWJlVM5c6!Udu_zog!JPDQ7r$^xX#5b|r(tso1N2 zB+>#n$<3MCeMkUH47>WiaNZ&OB-;Y)#5m9-)@%8=BwLW&r~28@rk^5~WDiKLUXuan zln37cT!TN@Mk(mo9QV|Ikn6CDtZPVrsSv)Z81>MwGEPPb*8bWFR z7G`|Qk)+JcEp=}p(;o8w9el8Xj!pPQRQ2I1?8BzUfRIBNZrDmfHpbxx^3FR zalcQfz25ZG{L>E|t1ngMxT#46-1i2;i6&lSVJF;&RZj+kzUQ2% z8}NTttnS5@r);6K-Z(}pz=W`jv$-)h9ycU>|}2)^WA6-j)~@L!pxBx z1}YZXCFF8eZ#{5jRgt5el6zuz-0tRlZq^jE)ap<59Q4!HR~apVO#I`Ml>vtwwlTt) z-_=2Nh;>?06qTPVhed2?qa&xa)j8yRWK%e(vy5fZguLwD$NY6@8;h*hd3HKU)>Aeu znpc)sGGFv!$J#+0)M#2s-z1X*7qZ!`^oGLlcQ#wQ&W;xqpeZLtA+snW zTgEV?&u4dcTT3t0Nmra5^cSudxV1_&MD~DO(@{sU-V<1>MXS5Q zNK)806Z(Xhv~Nd(k5?HeB-IF{6uQQGi;hyA)j7kfYLy3O;1 zxkK${p5L;X$t{{Y2}$?}rtuZW(5P#~)@PE*4w2U*Ghw|hvVQ~q>2HPw#W7EgoV)D8 zpM2tI%4ZJIYQKwQ5cp04q4Y{AI6+crv>=y3inc_V>I!e>GaM}0outPIsoYS!ojut< ziLk(u;GOK?)x~_Fkk2wO%Vj0E9~9eyLf&Ieockz^*3@2Y&cI^MEvL9100GsyYs(9d>&>&5cYEwqtqmoZ7Y=q#lIeRx zqHC%tR-4E-9H-;_{EfgP0cKXG&A9R4uK8(X*|^nB*3@U8-a~xsv*NH0npwy7uJF8+ z%({`Hy8-l$O@@^X{%Sj7Dj(O)Uep!^S%nPJD6W+f9kgB$?S5h1b*redsX~<25Z=_h zw7(6FqMWSes8w4b7a7W1Ax}w)B5ANaEMsZOv`laA8J$eW4|B|S_KB=_qLub&EJ>aW zuY5;yv4>RrWxD0)sHAm!)JDWobFpR}aK5$j?+2I5;YXP($CioCYW`f@>qL+*^7E3UOFc{>r`NQuv z1wfZ(h(9RsY~rc{Fjzo$VN8FJ8Gx1q-)p#hA4{@RYo1UCjdD=Q7oKL&#cX1%b?)PX zW~T&Jp!dNJ*F|_CUuKmf4;fcG%*^Mpn0s@l9&aJgpt>I2w;=yu7ABo;7^0ds%qozq zk5u;6+<(^DSSH1l|FGk+RVyA{*IuPdDl<#7KvGfURWXh0J|=FUoXPWyHkCtDjZO*l zr0R^i&{MZ*hwPqZppt$-6WYpeFR;C}+6@RqrC{A*eEViSN4-IOupLHW<1XIrk9$Bd>ZB1 zkC@=tEPWRz&@%+^6&7L&H8FBXg$36lWx`18Z)Zm4=vYiN?;a2)CoWK}d`!DKtZx}V z5l1|0>RwKGCsJdjQQUV3X#y>R7khB+&pNT~)sv)~%`yXw*Kbp}PrO^PEsL>J9HFjc zO0{rbz@#=D%+$$=Wr=Z=QhoqIh+0vUEY!aV-4lUGl#P)ydOpT6a9!DjnOGq}!SD)K z=Ebs!FmC6Qn5B-GNo+fnC{l2Bi!BN?*^=qS^v-W=ZW*?OJ~Sm}8N(y<858bUt+hvN zXe7w(pvXKjj}h27_3-C5fz4$Y#+>OHOtj(%>t)`Aee471-Q3@QHdH_!u;Yl%bL<)D zuAXO{9e3eou)CTJFmuVzBy+%xAVEgYcJL}lne&d8>r4_mSa*&06pZ&V^gM zsU=yocM!sgeqGLnVwd$$P|+XGGAat7C#VSK!?Z_n#gjA(x0W^m`1GVtgNz;m`VHB% z;E3W;l&;qU-ODk8U#b-GKEt&-u5qBe#cM4FqwBuUjI<{NzjEc%JL%d_oyA*!ES}cI zSV$)R02GLEA|ATR?n+<)J|072=kPHtwRTESuZzs2sH)@dJ+`8uog}{;VeqD2S=z9u z65J}AS#)Y^acQuP&7Rl3?nol0szbt^fYZxxx)^j*kuxx#AA2J{=afoYb@vSDiw}w( zKOOLf3Ibq8j1Pd(=a zPx~Wi7@0UqhCukrH+1L2MY!g@g5&XJ=oFWDe()`&bn$U@g(l$JzT3mOoZun>=qW_z zHy5$?Xsy~bG)-jQXR5s{D)yrqm$l`xS{bWtclrDx3MD#1X|T;Jn4$4YDoo&UiUt3} zq38ihL76isCVr4=InRNy5GZ^LJDh~BL3-(Guq@06d%u?T=FoEIIl8kvBtegTh+(p( zg<%FVn|=XxOW5<^LG%=y$$Fz%jb7&k5A}OGTCymi;mw(*95rg7p->S2^~F$XQZ_w_ zjw6S=Dix)Zui>Bc1Zc(U8a(R*ib*TLD-&L${1g1grAcu~xuX}vL`TohO&xyC1N?kz z_@&{>9NDb?1z6NkOgIvXu&x%xVO3zDn-$Gq8KWBLp_mllhT{oVfS zQ$OCyA)Hsk4S8Y13L35mD*80*%tV09W;_k(Vl08-%UsNAb>H>3s$SAlF=faP9!xbX z`9PaeT^UC0=@Z1R6r(dtj zD)49d>!gu>u!p&mLptT50te0;LHYgEWx|2rheGR8VLIy~6pZl)kQB}~H4T)3{a_35 z1TIbucUGJ`t3w*`-ZxX|N$78%*5m`mAawxPMhvA_;!HCf&#U%1FLiA^Hc=Nt&ySkP zwIF~@SryiZOsvnTDIGX?_3(1p-a&PjQp7`L)LC|$S)yOP7U1s-j`2xv8X=c_*77E7 z1-Mh=DCJ~ghqg0KgtH2z2+Sl#Pe%rkekTRb*ElVjm<|BT#(c2{c?Pp!MLIA2zNk+c z^T~uC0Uanb$IunbH?2j@d>!7;Rs+kGx1QhSbM zy~QPYKR%Ayc*W;j#abxh19r+LXF7G~Ycb~D!5P9h0_G`F;MxH9P5X4lu=0eI%#u=d ze%_a(y>O26B-k@DA0pB%zo|vj$jYVfn1Ms7Rl3O91gO>^r zvhwD70E%4M=8CY^6Sympy|l0?)x>Tn9@rZOxaiudB%Qd0`|K`Ed+->owOb$MuM%7~ z*9KHOA-pIJEI6WQegkASxz}-gR3>0obv&;gx-x{u?lLM>Vn@qndhtQh@0cMLl)x8| z$b}i9tal+c#V+-R9?bL~z;La15`2-GeSepiX6O`MFW0bn>0Md6G# z)*tS-sSFJb;(}d|lbKI+!?dUx%uUw0d$RemcB&`~%6Hfhn!>ewmeA(qP{4S?4DWDb zC_y^O9nPT~&ix(#2uoGd*!~ke68UH^3~504&ga%>_ZG9EUwQtDa>8_UWU6!8gH&)Y zB?o*XbecMX0A{5d4p27A(A{IV`i4lrM{Y?2x9DZB15%nDAsfOsDtL z#!;>H8%3a(nvNg1APjJ9_fJ)|4iXqfBt0dA3y$oqXF@?WV{My}v#8de*>CEE6W_Is z(r5@fS-&gU)iyPN^P1XMJx?B1%Cg)Wpn%Rk_7$?!x+6192wJbe;D)pnPXBTwWZxQb z&>F&wPA{X~CckieEt)=d`|&mMS`-!ZJejos_GtEbU^Gq7;-&WO030^>PJsWzkj%#~ z-wt0+_-!wAU-`9Y2&4f1MC_KS;y8gr)*X_3jJ8;dgGnG-7{oG07Z`@!2bSY7fyHbF z%=(g1PFR1Gm|A7c_1KL?LKDhxmiV;D2+3xEg|nBve*Soteuf_XRa-A-`b)qpJb9GW zc(&?DYQ~Rvfze5<%4#-;c8xM-l;d644t(k}Hjhrq)Jd!AxG?IbO zo{&#Ah^0GWH6ym;r<+%<()2m!Flh<_6q2VoMX4hH=+j+FLPJCH6~Ao>dIGnT_yc}z zfN)`G*RMl)eAz&r<6aSCTVU_j{Kbt2wjlg012Dnf5Z^lH$o7U7HYSXRh>`t`mku?r z4i|CP6UbOP*7zgzVXuOU8WGjSyLz1zMcO7xB3Kux#&#Aryg4!lpQGt^>U&$p2|?)M z)g#T0ZT_4DQstJS@!FiRgz}1&hGgB98sqaJ%@RI9EeeDH(!8_n*b^?W&t{S~#(;7X z_x51&trje_oYr=23|)jGnsnvpUIz!-#O{gatT2up0c0nZ`o6-}vbR?avZsT=$Idbd zckYbU)GK6&iURd+p`(?1odSXL*Nko(WExiPwJ_tbRZ~WxBozKfNWtx{&8>ahN@lq` z@WEtT!&&Am6;+7k<&d>fLyhgE3wK9rdwwgA7FULu zti-@OJP)dm$j^_<)Ok%XkJ9y!Dg~#(XtO9#thS7v3&cvMa5uE% zp`Aj@&#kC!qX+vlJfRrKQ$9ak4>s;sHJ@sFXKQ=#%YHVJ2d>4^E*)GKNV=xX3iq{` zO2&4dNZh9BPXBS7hJ_`T(L90qe9^M=AbO~Pp|tPq0hr?%>iYIbPe)VmiGdn;Q{kDa zt8+KO`-#Wk1D)}2ImE9|iv!q(fIS~;6Bex_$oH-rFyaKIgs z^vu1$qLi#aoF8KZy}24~Evp_AUC$<5^d=7)291$J6b&v33247;HuY<@13K5mj%vV(@$w#}xRdrt50}K+M+f2YdIR;9SfgSE zJkoe!^JpOb)Ip9>c>H#Yp2%MzBkr-(VG(E-S2IQo}ShS;l zTLpON7q1lb6Q4ku)h@_YwC8FZ{a5oM7iRH8Y z>HWNi!Paz{1UQH-90A$7M9b+R!Z{;*CN%SUinw{q?H945DCC3)@s%oYyz{yd>Sa2E zeh3}?uEF>sH0$uZb@h(7+W_!jyO$U|QiG?QiT`c%yYXH^1e*us9FNkebCYWRKjU7i1J!*XrN|w6N)c8gO=iK{dkz6-yr{ zGm!Fq1wSxK0VVJ+Lj@)8GuKU=nt96p}qDZ1egGDh) zJtXLs4RN5V-I4HPlAIKBMmkgo9$V~E32TYfuy2s17TLEI1Jn!7&BkdAtaD8m0_GGe z&hKD|DoCH0ELrpTM@TwaVNE*@Xv;Ge;FGuOiT;Mc0Vr|^MxfS2>F`idSl(bqn4@*< z@`XJCSFpwL9a=9TB!%Gbw~rb&gO3m^#oR*h=+W?Vus*v3s>!vOR3&8uA z*(jcMehFG|Z}}O6Lop;UxrWv4Mw#_3@c20V9W0jBO7opxW#sa-gJ zF*)#TP&}TP7d*F1{@9&aJKGpPgR8^V{SH%i@rnhS5X|CT1Fn$3pddVqV%(MQBiH9%&KlQc+Wutv-hmv=8yMT5X24@ z>cXeg`+55FA!0tTncVR_ig3DyAb73L>a32wgf> z@qF)d!Ld3Kj>{il$(!-oj}4^f;(`44gs!>SknAx9(!Cr| z?TX!j<>t*UQx-TE`83k8oVw^A00}O0LYQA|pg5?c9Ruv)GKDUz@JLo@X429@P2Gl( zcW!PIz+Z5bj0E5Tn>&H;{W`e31GOZ}V>o3AmLK&DI2~8uR3YbKheSO#+6rAU zK)Jh$WOdPv7Zvtsy4*+sj&71VOmvVyFUJaP?8g+RV`q^!)ue>T+_YpwX|WV&3AijY zs`nYV&D-Hyhc}FCnt{P$+fZ~HN@JWbG5J2>WQ6==aNo_9`cQ|tP7y}|W?+7W9z#|* z4p8E=AZjA9Hj~P=?u8+p2IHdP#H=EbkdvfQ2DoY{@+Q!Up)t0SW{=C1kv!-!y*jwO zt7hY9oRP|4HZ5OmRPJ(t+De6#NqZ%OgjglVtgttAYZ6*QeZ=xrLQSUuie}?K*YeL3BO<{Bn7I=k1lhIsS47O*9 zC0sdn(@jO3!u@2oVskOwW~`WUsd^P^#;9_pl+z#PHoN3^Rd|*%;u&7uN$tRdo(30m zOlH2Ydn7yLvP9N&qSz}~)6HEMfzkxlK)k^N%s4-~lw;{hxDoICA!RE>{U+b(|1oaZ znTf+`+4hd(2JMF zz~oZnyriq;2g=R;8jhg#L&3#;zhl3}ELXP$9re1Va}?{%hLAB=oiD}}0W<9=hPE7Z zUoqCRq&#AfOlfIpk+KQiVMalo)>Xz`#X$6O=#jnt8NDn{qP9~MFeOc~+G|bmqE1OE zi|KYYC>CZPe#AmrrLrrDqNG=jZtT=MdHU>;EN`e|B$YJ33Zd%nDqYE(k}hBOdB%9y zx1-*4MAQb&M~z+ZK($h>(Hmh3M@Jo)n-h7(rZa1CZec;r`MzeSY51)3_LK7XL`8mZ zaxd8WyAdU4*SUh!L7;@th60%U%)F3_+s1x;tS4NfKQ@HvjvqQB78wNP)~CNsSJ;`J z%;}=A9Ic=ok`rjCW>k{khjBoBc%BBPw^#W0q6;67h-H{7GyBLgTl@MW3d0m{hVcwz zJhoBuQytO0jv%SrGsBPfMNkoQh3H+o_xYjhGoSH23&JIekEFLg_mXMp-LB!7MTB^1 z?L)MadR8@eg9b}2q-NP@>vGTf>Un~xMjSq>)j-JBZyDY?V8bF5aRI!(y7;LWLp&Z` zvhb~sJl0o~zHeRA2;-ct6-!avPtk;I9a_4fMpDtixtQarGGopk>kC|;X`D7t;K}A?Sh( zHF=OYF{DQ;HhMh$*Ht15Ev*A1A`HJAgLbl}xko2M>*Xe*n*El3oK_ao*h9bpI&Zxe z@I8(+AN)9`avLskhr2@f0CfR=PPel68b7t}AA`I;xHVIe0@+e^kB;4nOaYw?Isr&- z>6F0EQg2fnZHpn5YOfBMsVu9U1O|U09I3r1D{?AH&@mI=^w64+(malJgzzEv939Jx zM}fTsk1T_8c|e@ZYROCO$)0trM!1IA*(Hg#1pb-sVPXdPjB(%LrP3Ob$u-1)>#`oO(@ogtJ$;^$=5JYd zO>5U)3`uOqUvW|@twg3IqSE3zomHn`keEAD8 zQ@6d5MN_zFCT&tmd_#-N@8`ZpY>T&;OcK*-PUhe__FdK=sYkNOL>ZB%triV3OJ>@r zpDXH`$+s7=F`34uqn&6~A(%b^RVh#x2;ks$R4@%E5ddXBB)k9 z?f|CfFIl{BK&QEDoMfk%CQi0<-U2B#DO0gAXh53~!-%bSPPzdRzYhXV8f8TQ-V`B# zE0|e2d2UWuPNd9Su66NX&67!HBz3UH%#A~tZc~K??*fK(0hjpT=%zSsAzg+$UcQbloa76)x^K4EGE{6Uld1Q95JHePtC36s8>fE%43xUE-YD9v3&Uvz0e2f zgit1GZk@|bPU;>`40%16C#mu%!+?PF8|$|S9LK<$4nWTFh`DCaw7CJ55Nk#|$_^#F z)bJ8GZ5>acbq)?q4b|=Pi6K+?Y1PeVilH2LtD$?gh`SSfMi_1HGp^j66@khd?0CN- z0bAHaw$tjhDv7yvm>OPuuCQjImVvEqQ*hX4bCFA@8h#Mtm5~5O&f6$hD8%;_$Lt=d#cawi7~`LQ);+erM*Loms8Cs=aE_vOOP* zD^d@8H-TJRx4)#B1LjUc2Ge%ZM_W#`F5RbtcyS-Aq z0j?VJ1_T+P(}SmDUF(4LGHV#UtYa(goq z$c_GD3cuFx>)7VSkv3!k@0%TavohlKav#eX{q+VqymvBk67>Sb{dKcu z)tIrM%liScu&SS-gqjH8o6)C32>=a2rH<{l8$IQ4sp1{6YXeHkvib~2(sSdW2lz*m zBlFy&0bs$zuVG%m{*Bk?#p3}B@t&AwyPXpVh=mk>LIO>TKt3!b?Ym<1DF&1i&EsL? z?6+AC5nNF2U>KKfa!3KNH~jghBtUaVu#V2RZ$aJEH?1OGN(P5H8zQHWQoXBCD_`;| z_Rf$1w+jSDoGe~kA4fO8-t}y;Q1KG5Uf$JG)B@^uh&lC>9C7%I|~Yxs?LL_DJ6WYgD*k^(l&^V z@&*h5Mo8AgSRB`@0g=dX^9q(_uS9JfT$j-43a=QEPBBISW;D(kGZI`-)-of(v95(_ zXY*(Kj&->&$q3UXhC5&hJQfZG$h-r(VqsMdq7^6T4FMd4?#>;5mPdT%n3*Vd+z^=@ z%I#{6I266w7f0dL+?r_1L_?b2>JpMX_iL7d5Z0kpMynGl{(KB^stT^@*J0W7(PFEe zwQMa2+|_tzPX?uRyP@Br7ZHMLac+#t{;ZkpTTCCtrMgJ%0R!kt3xL`;(;H z(@+5A;2q}DgIWs~)vlFj)MlehnN?oJ&jS2ma@R}WZ%0D>g5#Ha!K3gSeVl8`MJ{Pi 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 literal 0 HcmV?d00001 diff --git a/05-Analytics-Reports/2026-06-22_Drogheda_Manual-Logs-Grafana_v1.0.tex b/05-Analytics-Reports/2026-06-22_Drogheda_Manual-Logs-Grafana_v1.0.tex new file mode 100644 index 0000000..0d0b2c6 --- /dev/null +++ b/05-Analytics-Reports/2026-06-22_Drogheda_Manual-Logs-Grafana_v1.0.tex @@ -0,0 +1,195 @@ +% Helgen Drogheda — Manual de Consulta de Logs (Grafana / Loki · backend htracker) +% Compilar: latexmk -xelatex 2026-06-22_Drogheda_Manual-Logs-Grafana_v1.0.tex (XeLaTeX) +\documentclass[10pt]{article} +\newcommand{\sistema}{Helgen Drogheda} +\newcommand{\sistemapdf}{Manual de Consulta de Logs — Grafana / Loki} +\newcommand{\doctipo}{Manual de Operación} +\input{estilo-helgen.tex} +\usepackage{amssymb} + +\begin{document} + +\helgenportada{Consulta de Logs en Grafana}{Observabilidad del backend \texttt{htracker} — Grafana Loki (capa gratuita)}{% +\begin{tabular}{@{}ll@{}} +Documento & Manual de Operación\\ +Versión & 1.0\\ +Fecha & 22 de junio de 2026\\ +Autor & Dr.\ Leopoldo Rodríguez Salazar — Helgen Technologies\\ +Sistema & \texttt{htracker} (retranslator + monitor), servidor \texttt{HelgenServer}\\ +Fuente & \texttt{04-Source-Code/htracker/log-shipping/}\\ +Clasificación & Confidencial — Uso interno\\ +\end{tabular}} + +\tableofcontents +\vspace{4pt} + +\section{Propósito} +El servidor del backend tiene \textbf{muy poco disco}, por lo que el gateway \textbf{no} +guarda logs localmente. En su lugar, los registros se \textbf{envían fuera del servidor} +a \textbf{Grafana Loki} (capa gratuita) y se consultan desde \textbf{Grafana}. Este manual +explica cómo \emph{entrar} a Grafana y \emph{consultar} los logs de la flota — no requiere +acceso al servidor para leer. + +\begin{helgennotice}[En una frase] +Los logs del backend viven en \textbf{Grafana Loki}. Para verlos: abre Grafana +$\rightarrow$ \textbf{Explore} $\rightarrow$ fuente de datos \textbf{Loki} $\rightarrow$ +escribe \texttt{\{job="htracker"\}}. +\end{helgennotice} + +\section{Arquitectura del envío de logs} +El gateway escribe a la salida estándar; \texttt{systemd} la captura en \texttt{journald} +(un búfer en anillo \textbf{limitado a 200 MB}, que nunca llena el disco); un agente ligero +(\textbf{Vector}, o \textbf{Grafana Alloy}) lee el journal y lo empuja por HTTPS a Loki. + +\begin{center} +\begin{tikzpicture}[node distance=6mm and 8mm] + \node[caja] (rt) {retranslator\\\scriptsize(stdout)}; + \node[caja, right=of rt] (jd) {journald\\\scriptsize(tope 200 MB)}; + \node[caja, right=of jd] (vec) {Vector\\\scriptsize(o Alloy)}; + \node[cajaoscura, right=of vec] (loki) {Grafana\\Loki}; + \node[caja, right=of loki] (graf) {Grafana\\\scriptsize(UI)}; + \draw[flecha] (rt)--(jd); \draw[flecha] (jd)--(vec); + \draw[flechaacento] (vec)-- node[etiqueta, above]{HTTPS} (loki); \draw[flecha] (loki)--(graf); +\end{tikzpicture} +\end{center} + +\noindent\textbf{Por qué así:} el gateway nunca escribe un archivo que crece; \texttt{journald} +es un búfer pequeño y limitado (sobrevive cortes breves de Loki); el agente empuja con un +búfer acotado que \emph{descarta lo más nuevo} si Loki no responde — de modo que la ruta de +logs \textbf{jamás} bloquea ni tumba al gateway. + +\begin{helgennotice}[¿Vector o Alloy? — verifícalo una vez] +El \texttt{setup-logging.sh} instala \textbf{Vector} por defecto (etiqueta +\texttt{job="htracker"}). Si en su lugar se usó el \emph{onboarding de Linux} de Grafana +Cloud, el agente es \textbf{Alloy} y los logs aparecen por unidad de systemd +(\texttt{\{unit="htracker.service"\}}). Si una consulta no devuelve nada, usa el +\textbf{Label browser} de Grafana para ver qué etiquetas existen. +\end{helgennotice} + +\section{Acceso a Grafana} +\subsection{Grafana en la nube (nada que instalar)} +\begin{enumerate}[leftmargin=1.5em] + \item Entra a tu \textbf{stack de Grafana Cloud} (URL del tipo \texttt{https://.grafana.net}). + \item Menú lateral $\rightarrow$ \textbf{Explore}. + \item Arriba, selecciona la fuente de datos \textbf{Loki}. + \item Escribe la consulta (sección 4) y presiona \textbf{Run query}. Ajusta el rango de + tiempo arriba a la derecha. +\end{enumerate} + +\subsection{Grafana local (si prefieres correr la interfaz tú mismo)} +Los datos siguen en Grafana Cloud; sólo el visor es local. +\begin{verbatim} +docker run -d --name grafana -p 3000:3000 grafana/grafana-oss +# http://localhost:3000 (admin/admin) +# Connections -> Data sources -> Loki: +# URL: Auth: Basic user: password: +\end{verbatim} + +\begin{helgennotice}[Credenciales] +\texttt{LOKI\_URL}, \texttt{LOKI\_USER} y \texttt{LOKI\_TOKEN} están en +\texttt{/etc/vector/htracker.env} del servidor y en la consola de Grafana Cloud +(\emph{Connections} $\rightarrow$ \emph{Hosted logs}). \textbf{Son secretos} — viven en +\texttt{.env.vector} (ignorado por git); nunca se suben al repo. +\end{helgennotice} + +\section{Cómo consultar los logs (LogQL)} +En \textbf{Explore} con la fuente \textbf{Loki}. Loki indexa \textbf{etiquetas}, no el texto; +todo lo demás se filtra con \texttt{|=} (contiene) en el momento de la consulta. + +\begin{verbatim} +{job="htracker"} # todo el backend +{job="htracker", level="error"} # sólo errores +{job="htracker", unit="htracker.service"} # sólo el gateway (retranslator) +{job="htracker"} |= "Anti-Salto" # rechazos del filtro anti-salto +{job="htracker"} |= "victimas_backend" # el resumen 1/min (victimas de backend) +{job="htracker"} |= "ADDR-CHANGE" # cambios de direccion SIM/NAT +\end{verbatim} + +\noindent Si el envío es por \textbf{Alloy} (no Vector), usa la unidad de systemd: +\begin{verbatim} +{unit="htracker.service"} +{unit="htracker.service"} |= "victimas_backend" +\end{verbatim} + +\section{Qué se registra} +\begin{center}\footnotesize\renewcommand{\arraystretch}{1.3} +\begin{tabularx}{\linewidth}{@{}>{\bfseries\color{HelgenIndigo}}L{3.6cm} X@{}} +\toprule +Etiquetas (labels) & \texttt{job=htracker}, \texttt{host}, \texttt{unit} + (\texttt{htracker.service} / \texttt{-web} / \texttt{-notifier}), \texttt{level} + (\texttt{info}/\texttt{warn}/\texttt{error}/\texttt{debug})\\ +Nivel por defecto & \texttt{INFO}: sólo \textbf{eventos} + una línea de \textbf{resumen cada + 60 s} (\texttt{[SUMMARY]} / \texttt{victimas\_backend}). El detalle por paquete es + \texttt{DEBUG} y \textbf{no} se envía.\\ +Eventos clave & anti-salto (\texttt{Anti-Salto}), cambios de dirección SIM/NAT + (\texttt{ADDR-CHANGE}), víctimas de backend (unidad en línea en htracker pero ausente en + Wialon).\\ +\bottomrule +\end{tabularx} +\end{center} + +\section{Retención, límites y seguridad de disco} +\begin{itemize} + \item \textbf{Loki (capa gratuita):} 50 GB/mes, \textbf{retención de 14 días}, Grafana + incluido. Tras el recorte de verbosidad se usa una fracción mínima. + \item \textbf{journald (servidor):} \texttt{SystemMaxUse=200M}, retención 3 días + (\texttt{/etc/systemd/journald.conf.d/htracker.conf}). Aunque Loki esté caído días, + el disco \textbf{no} se llena. + \item \textbf{Búfer de Vector:} en memoria, descarta lo más nuevo si Loki no responde. +\end{itemize} + +\section{Verbosidad (nivel de log)} +\begin{itemize} + \item \texttt{GW\_LOG\_LEVEL} (por defecto \texttt{INFO}). Para depurar una unidad en vivo, + ponlo en \texttt{DEBUG} \emph{temporalmente}. + \item \texttt{GW\_LOG\_SUMMARY\_S} (por defecto 60) — cadencia del resumen. +\end{itemize} +\begin{helgennotice}[No dejes DEBUG encendido] +\texttt{DEBUG} registra cada paquete e inunda la tubería (y consume la cuota de Loki). +Úsalo unos minutos y regresa a \texttt{INFO}. +\end{helgennotice} + +\section{Salud del envío y diagnóstico} +En el servidor (\texttt{ssh HelgenServer}): +\begin{verbatim} +systemctl status vector # esta activo el agente? +journalctl -u vector -f # esta enviando? errores de Loki? +\end{verbatim} +\noindent \textbf{No aparecen logs en Grafana:} (1) confirma \texttt{vector} activo; (2) revisa +credenciales/endpoint en \texttt{/etc/vector/htracker.env}; (3) verifica el rango de tiempo en +Grafana; (4) revisa el \textbf{Label browser} (¿\texttt{job} o \texttt{unit}?, Vector vs Alloy). + +\section{Puesta en marcha / reinstalación} +Sólo si hay que reinstalar el envío (normalmente ya está activo). En el servidor, desde +\texttt{/home/itadmin/htracker/log-shipping}: +\begin{verbatim} +cp .env.vector.example .env.vector +nano .env.vector # pega LOKI_URL / LOKI_USER / LOKI_TOKEN +./setup-logging.sh # limita journald, --no-filelog, instala Vector, arranca +# ./setup-logging.sh --journald-only # si Grafana Alloy ya envia el journal +\end{verbatim} +\noindent \texttt{deploy.sh} ya sincroniza la carpeta \texttt{log-shipping/} al servidor. +Detalle completo: \texttt{04-Source-Code/htracker/log-shipping/README.md}. + +\section{Referencia rápida de consultas} +\begin{center}\footnotesize\renewcommand{\arraystretch}{1.3} +\begin{tabularx}{\linewidth}{@{}>{\ttfamily\color{HelgenIndigo}}L{6.3cm} X@{}} +\toprule +\normalfont\bfseries Consulta & \normalfont\bfseries Qué muestra\\ +\midrule +\{job="htracker"\} & Todo el backend\\ +\{job="htracker", level="error"\} & Sólo errores\\ +\{job="htracker", unit="htracker.service"\} & Sólo el gateway (retranslator)\\ +\{job="htracker"\} |= "victimas\_backend" & Resumen 1/min — víctimas de backend\\ +\{job="htracker"\} |= "Anti-Salto" & Rechazos del filtro anti-salto\\ +\{job="htracker"\} |= "ADDR-CHANGE" & Cambios de dirección SIM/NAT\\ +\bottomrule +\end{tabularx} +\end{center} + +\vfill +\begin{center}\scriptsize\color{gray} +Helgen Technologies · Project Drogheda · Observabilidad del backend · Documento interno · v1.0 (22 jun 2026) +\end{center} + +\end{document} diff --git a/05-Analytics-Reports/estilo-helgen.tex b/05-Analytics-Reports/estilo-helgen.tex new file mode 100644 index 0000000..328e489 --- /dev/null +++ b/05-Analytics-Reports/estilo-helgen.tex @@ -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}} diff --git a/06-Testing-Validation/2026-06-20_Drogheda_GNSS-Findings-Summary.md b/06-Testing-Validation/2026-06-20_Drogheda_GNSS-Findings-Summary.md index 41565e8..8c9c915 100644 --- a/06-Testing-Validation/2026-06-20_Drogheda_GNSS-Findings-Summary.md +++ b/06-Testing-Validation/2026-06-20_Drogheda_GNSS-Findings-Summary.md @@ -41,6 +41,30 @@ tags: [drogheda, hbox, gnss, rtk, ntrip, findings, presentation] - So **"healthy delivery" ≠ "RTK."** The baseline is *healthy SPS + reliable delivery*; raise the bar to a fix-≥4 capture once corrections work. +## Update — 22 Jun 2026: live field capture + two ceilings on RTK + +> A fresh live `udp/5010` server capture (not the 20-Jun bench) caught two units at once. + +- **Telemetry forwarding CONFIRMED.** Enabling `$PUBX` (u-blox *proprietary* `$P…`, the + same class as our planned `$PHLG`) on the F9P **UART2** → it reaches the server **intact**, + next to GGA/RMC. The 4G NTRIP Master is a **transparent forwarder**, so **in-band unit + health (`$PHLG`) to the server is viable** — proven with zero extra hardware. +- **A natural A/B on RTK** (same capture): + - **H0014 (Querétaro bench)** — RSSI **−93**, **SPS (quality 1)**. It sits in a **basement**. + - **H0017 (Veracruz / ICAVE field)** — RSSI **−51**, **DGPS (quality 2, RMC mode `D`, + base id 0133)** — i.e. it **is** applying corrections. +- **Two independent ceilings on RTK** (refines §2's "RTK not achieved"): + 1. **Sky / antenna placement** — RTK fix is carrier-phase; a **basement caps it** no matter + how good the corrections are. (Per Leo: bench antenna placement — not fixable there.) + 2. **Corrections pipeline** — H0017 has good sky + signal yet only reaches **DGPS, not RTK + (4/5)**, pointing back at the **free rtk2go caster + RAM-only F9P config** as a *separate* + limit present everywhere. +- **Cleanest next test:** one **open-sky capture** (roof/outdoors) with a **proper caster + + FLASH-saved config** — that isolates the two ceilings and shows whether a fix ≥4 is reachable. +- **Watchdog implication:** a healthy unit can legitimately sit at SPS/DGPS (basement, or field + on a weak caster) → **never power-cycle on no-RTK alone** (already enforced by the v2.1 + SPS-thrash guard; the v2.2 `$PHLG` will make this visible per-unit). + ## 4 · The diagnostic trap (and a retraction) - The **4G-NTRIP-Master console is an unreliable health signal.** The provably-healthy diff --git a/CLAUDE.md b/CLAUDE.md index ea9afc6..b652d71 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -130,9 +130,9 @@ these before presenting; if it breaks one, say so and propose the compliant fix. | `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` | +| `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: +1 fan, +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` · `watchdog-protoboard-bringup.md` (shop list) · `watchdog-connections.xlsx` (pin/net connection list, PCB+proto) | | `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)_ | +| `05-Analytics-Reports` | SQL views, dashboards (VERONICA/PowerBI), **observability** | `2026-06-22_Drogheda_Manual-Logs-Grafana_v1.0.pdf` (Grafana/Loki log-consultation manual) | | `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` | @@ -185,6 +185,12 @@ Montacargas 12 V. **Target:** 30 units by August. `Project-Drogheda/` once synced. - **Backend repo:** `ssh://git.helgen.dev:6611/htracker` (retranslator + monitor), cloned at `04-Source-Code/htracker` (branch `feature/test500unidades`). +- **Observability (logs):** backend logs ship off-box to **Grafana Loki** (free tier, 50 GB/mo, + 14-day) via **Vector** (default) or **Grafana Alloy** reading journald (capped 200 MB) — + `04-Source-Code/htracker/log-shipping/` (`README.md` runbook · `setup-logging.sh` · `vector.toml`). + Read in Grafana → Explore → Loki: `{job="htracker"}` (Vector) or `{unit="htracker.service"}` + (Alloy). Consultation manual (PDF): `05-Analytics-Reports/2026-06-22_Drogheda_Manual-Logs-Grafana_v1.0.pdf`. + Creds in `/etc/vector/htracker.env` (server) — never in the repo. - **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.