Compare commits

..

10 Commits

Author SHA1 Message Date
33b62d7052 Drogheda: T12 (MFF2 repoint as a replayable script) + kicad submodule bump
scripts/2026-09-15_v6_T12_sim_dual_footprint.py moves the U22 edits into the
T-script chain. Idempotent, dry-run by default, .T12bak backups, UUID invariant
guard, and a gate-0 assertion on the MFF2 land pattern. Verified to reproduce the
hand edits byte-for-byte. Part 2 (nano-SIM holder + 0R selects) pending the
holder datasheet.
2026-09-15 10:58:36 -06:00
564c3e450d auto-sync 2026-09-10 18:00 2026-09-10 18:00:14 -06:00
b877f0458c auto-sync 2026-09-09 18:00 2026-09-09 18:00:15 -06:00
768f49a860 gitmodules: kicad submodule now points at Helgen server (HBox_PCB)
ssh://git@git.helgen.online:222/Helgen-Services/HBox_PCB.git replaces the
GitHub mirror URL, matching the external-collaborator guide. Outer repo
gains a 'helgen' remote (Helgen-Services/Drogheda.git).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJ4eAB6zjQT49RVwpgfKdt
2026-09-07 18:47:32 +00:00
7f7a3d5d39 kicad submodule: C123 placement fix -> 47d1a1e
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJ4eAB6zjQT49RVwpgfKdt
2026-09-07 16:55:54 +00:00
61e10d8145 kicad submodule: external docs (git guide + Copper Plan EN/ES) -> 1a99fb3
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJ4eAB6zjQT49RVwpgfKdt
2026-09-07 16:47:21 +00:00
cb93840518 v6 Copper Plan: U18 V_BCKP (pin 36) decoupling fix
- pin 36 is V_BCKP on /3V3 (always-on), deliberately not /3V3_GNSS:
  RTC + ephemeris survive when U21's switched rail is off
- no /3V3 cap existed within 19 mm of the pin; spare Cu4 (100nF,
  already /3V3-GND) reassigned to sit at pin 36
- C123 clarified: U18 has only two VCC pins; C123 is the U21-output
  third 100nF, not a VCC-pin cap

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJ4eAB6zjQT49RVwpgfKdt
2026-09-05 01:11:02 +00:00
5e2dc775d7 v6 Copper Plan: add master proximity table (§8)
- every discrete mapped to its owner designator with distance class
  (glued <=3mm / close <=5mm / block <=15mm), computed from the live
  netlist + hand curation (ESD at connectors, ADC filters at MCU,
  comp caps at their regulator, DB1-4 -> U13, C106-108 -> U16 VDD)
- designators added to existing per-block tables (Leo's request)
- sch<->pcb designator audit: P1-P12 in sync, 0 value mismatches,
  only expected diffs (U22 fp TBD, MH5/6 board-only, PF* ERC flags)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJ4eAB6zjQT49RVwpgfKdt
2026-09-04 01:08:49 +00:00
538b62b72c v6 connector renumber (submodule b007c7c) + Copper Plan map
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJ4eAB6zjQT49RVwpgfKdt
2026-09-04 00:53:32 +00:00
94da1ad0eb v6 silkscreen + gravity placement (submodule) 2026-09-03 23:50:25 +00:00
9 changed files with 1149 additions and 30 deletions

2
.gitmodules vendored
View File

@ -9,5 +9,5 @@
url = ssh://git.helgen.dev:6611/hbox-log-tools
[submodule "kicad"]
path = 03-Architecture-Hardware/kicad
url = git@github.com:lrodriguez15/hbox-kicad.git
url = ssh://git@git.helgen.online:222/Helgen-Services/HBox_PCB.git
branch = main

View File

@ -57,13 +57,18 @@ ul{padding-left:20px}
li{margin:4px 0}
.tag{font-family:"Archivo",sans-serif;font-size:10.5px;text-transform:uppercase;letter-spacing:.08em;padding:2px 7px;border-radius:99px;border:1px solid var(--copper);color:var(--copper);white-space:nowrap}
.tag.q{border-color:var(--plane);color:var(--plane)}
.d{font-family:"Archivo",sans-serif;font-size:10px;font-weight:600;letter-spacing:.05em;padding:1px 6px;border-radius:99px;white-space:nowrap}
.d1{background:var(--crit-soft);color:var(--crit)}
.d2{background:var(--copper-soft);color:var(--copper)}
.d3{background:var(--plane-soft);color:var(--plane)}
tr.own td{font-family:"Archivo",sans-serif;font-size:12.5px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--copper);border-bottom:2px solid var(--copper);padding-top:18px;white-space:normal}
a{color:var(--plane)}
</style>
<div class="wrap">
<h1>EinarBoard v6 Copper Plan</h1>
<p class="sub">Placement companion for the 140 × 100 re-layout — every capacitor's owner, what clusters together, and the EMC order of work. Cap ownership below is computed from the board netlist, not from memory.</p>
<div class="warn"><b>Before placing anything:</b> run <i>Update PCB from Schematic</i> once more. The NTC2 label was sitting on PA7 (TIMEPULSE) and merged the nets — it is fixed in the schematic (commit <code>ba70f62</code>), and the sync re-nets <code>P18</code>/<code>R126</code>/<code>C157</code> to a clean <code>/NTC2</code>. Tick <i>“delete footprints with no symbol”</i> in that dialog.</div>
<div class="note"><b>Connector renumber (03-Sep, commit <code>b007c7c</code>):</b> P-refs now follow function order — <code>P1</code> 24V · <code>P2</code> 5V bench · <code>P3</code> battery (was P4) · <code>P4</code> fan (was P6) · <code>P5</code> cutoff (was P11) · <code>P6</code> reset (was P10) · <code>P7</code> NTC probe 1 (was P12) · <code>P8</code> NTC probe 2 (was P18) · <code>P9</code> panel LED (was P8) · <code>P10</code> RGB (was P14) · <code>P11</code> RS485 (was P15) · <code>P12</code> RS232 (was P16). ⚠ v5 field docs say "P12 = NTC" — in v6 that name is the RS232 port; update harness docs. The NTC2 net fix is applied and synced.</div>
<div class="note"><b>Known leftovers from the auto-clustering:</b> ~54 small courtyard overlaps, nearly all against the legacy V5 regulator families that kept their old spots — <code>L2</code>/<code>D2</code> (under U19's new outline), <code>L6</code>/<code>DB2</code>/<code>DB3</code>/<code>Q2</code>/<code>Dbk1</code> near U25, <code>C40</code>, <code>R40</code>/<code>R41</code> at Y3. Drag those few aside first and the board is clean.</div>
<h2><span class="no">§0</span>Three laws that decide EMC at placement time</h2>
@ -122,11 +127,11 @@ a{color:var(--plane)}
<div class="tblwrap"><table>
<tr><th>Ref</th><th>Value</th><th>Belongs to</th><th>Placement</th></tr>
<tr><td>C149 C150</td><td>2.2 µF/100 V</td><td>U25 VIN</td><td>2–3 mm from pins 2/7; the hot loop</td></tr>
<tr><td>C151</td><td>100 µF/50 V</td><td>input bulk</td><td>right behind C149/C150</td></tr>
<tr><td>C151</td><td>100 µF/50 V</td><td>U25 input bulk</td><td>right behind C149/C150</td></tr>
<tr><td>C152</td><td>100 nF</td><td>U25 BOOT</td><td>directly across pins 1–8</td></tr>
<tr><td>C153 C154</td><td>47 µF/16 V</td><td>5 V output</td><td>at L22 output; FB tap (R121) here, not at the inductor</td></tr>
<tr><td>D34</td><td>B560C</td><td>catch diode</td><td>anode ground ties straight into C149/C150 ground</td></tr>
<tr><td>D32</td><td>SMCJ33A</td><td>surge clamp</td><td>between Q23 and the input caps, short fat ties</td></tr>
<tr><td>C153 C154</td><td>47 µF/16 V</td><td>U25 5 V output</td><td>at L22 output; FB tap (R121) here, not at the inductor</td></tr>
<tr><td>D34</td><td>B560C</td><td>U25 catch diode</td><td>anode ground ties straight into C149/C150 ground</td></tr>
<tr><td>D32</td><td>SMCJ33A</td><td>U25 input surge clamp</td><td>between Q23 and the input caps, short fat ties</td></tr>
<tr><td>R120 · R123 C155 C156 · R121 R122 · R124 R125</td><td>RT · COMP · FB · UVLO (DNP)</td><td>U25 analog side</td><td>cluster at pins 3–6, opposite the SW node; keep FB thin and away from L22</td></tr>
</table></div>
<p>24 V rail housekeeping: <code>C10</code>/<code>C22</code> bulk near Q10, <code>C11</code> 100 nF at the load side. Keep the whole entry block in one corner so the dirty 24 V copper never crosses the board.</p>
@ -134,9 +139,9 @@ a{color:var(--plane)}
<h2><span class="no">§3</span>Battery backup — U6 LTC4040 <span class="tag q">kelvin</span></h2>
<div class="tblwrap"><table>
<tr><th>Ref</th><th>Value</th><th>Belongs to</th><th>Placement</th></tr>
<tr><td>C30 C31 C78</td><td>10 µF · 2.2 µF · 220 µF</td><td>VEH5V input</td><td>at U6 input pins, after RS1</td></tr>
<tr><td>C32 C33 C34</td><td>47 µF ×2 · 10 µF</td><td>MAIN5V output</td><td>at U6/Q7 output side</td></tr>
<tr><td>C35</td><td>22 µF</td><td>BAT_PLUS</td><td>near P4; <code>/SW_BAT</code> ≥ 2 mm polygon, <code>/BAT_PLUS</code> ≥ 1.5 mm — the blackout path, a hard v6 requirement</td></tr>
<tr><td>C30 C31 C78</td><td>10 µF · 2.2 µF · 220 µF</td><td>U6 VEH5V input</td><td>at U6 input pins, after RS1</td></tr>
<tr><td>C32 C33 C34</td><td>47 µF ×2 · 10 µF</td><td>U6 MAIN5V output</td><td>at U6/Q7 output side</td></tr>
<tr><td>C35</td><td>22 µF</td><td>U6 BAT pin → P3</td><td>near P3; <code>/SW_BAT</code> ≥ 2 mm polygon, <code>/BAT_PLUS</code> ≥ 1.5 mm — the blackout path, a hard v6 requirement</td></tr>
</table></div>
<div class="note"><b>RS1 (10 mΩ) is a Kelvin part:</b> the LTC4040 sense pins must tap the pad faces themselves, not the power trace. Place RS1 immediately at U6 with L22's output feeding it directly.</div>
@ -145,10 +150,10 @@ a{color:var(--plane)}
<p>The VBAT bank is a one-direction gradient into pins 55/56/57 — largest farthest, smallest touching the module:</p>
<div class="tblwrap"><table>
<tr><th>Ref</th><th>Value</th><th>Order</th><th>Placement</th></tr>
<tr><td>C130 C131</td><td>220 µF/10 V ×2</td><td>far end</td><td>the ≥300 µF droop reservoir (2.8 A bursts, ≤300 mV sag)</td></tr>
<tr><td>C132 → C133 → C134</td><td>22 µF → 1 µF → 100 nF</td><td>middle</td><td>stepping toward the module</td></tr>
<tr><td>C135 C136</td><td>33 pF · 10 pF</td><td>at the pins</td><td>within 2–3 mm of pins 55/56/57 — these keep TX harmonics off VBAT</td></tr>
<tr><td>D22</td><td>SMAJ5.0A</td><td>at the bank</td><td>across VBAT near C130/C131</td></tr>
<tr><td>C130 C131</td><td>220 µF/10 V ×2</td><td>U19 VBAT · far end</td><td>the ≥300 µF droop reservoir (2.8 A bursts, ≤300 mV sag)</td></tr>
<tr><td>C132 → C133 → C134</td><td>22 µF → 1 µF → 100 nF</td><td>U19 VBAT · middle</td><td>stepping toward the module</td></tr>
<tr><td>C135 C136</td><td>33 pF · 10 pF</td><td>U19 pins 55/56/57</td><td>within 2–3 mm — these keep TX harmonics off VBAT</td></tr>
<tr><td>D22</td><td>SMAJ5.0A</td><td>U19 VBAT bank</td><td>across VBAT near C130/C131</td></tr>
</table></div>
<p>VBAT copper ≥ 3 mm wide the whole way. Around the module: <code>C125</code> at pin 15 (1V8 out); <code>U22</code> SIM within 5 mm of pins 30–33 with <code>C126</code> at its VCC pad and <code>D23–D25</code> PESD at the SIM pads; <code>U23</code>/<code>U24</code> level shifters between U19 and the MCU side, each with its supply pair (<code>C137</code>/<code>C139</code> on 1V8, <code>C138</code>/<code>C140</code> on 3V3) at the pins; <code>Q20–Q22</code> near the control pins they drive. <code>J13</code> U.FL at the board edge; the DNP pi network <code>C146</code>/<code>C147</code> (+ its series element) sits inline in the 50 Ω run right at the connector, <code>D28</code> ESD at the connector ground side.</p>
@ -156,10 +161,12 @@ a{color:var(--plane)}
<div class="tblwrap"><table>
<tr><th>Ref</th><th>Value</th><th>Belongs to</th><th>Placement</th></tr>
<tr><td>C119</td><td>10 µF</td><td>U21 input (SW_5V)</td><td>at the LDO input pin</td></tr>
<tr><td>C120 C124</td><td>10 µF ×2</td><td>3V3_GNSS</td><td>one at U21 output, one at U18's main VCC entry</td></tr>
<tr><td>C121 C122 C123</td><td>100 nF ×3</td><td>U18 VCC pins</td><td>one per supply pin, at the pin</td></tr>
<tr><td>C144 C145</td><td>100 nF · 100 pF</td><td>VCC_RF / bias tee</td><td>at L21's feed side — the antenna supply injection</td></tr>
<tr><td>C143</td><td>100 nF RF</td><td>DC block</td><td>inline in the 50 Ω run between L21 tap and J12</td></tr>
<tr><td>C120 C124</td><td>10 µF ×2</td><td>U21 out → U18 VCC</td><td>one at U21 output, one at U18's main VCC entry</td></tr>
<tr><td>C121 C122</td><td>100 nF ×2</td><td>U18 VCC pins 33/34</td><td>one per supply pin, at the pin</td></tr>
<tr><td>C123</td><td>100 nF</td><td>U21 VOUT, beside C120</td><td>U18 has only two VCC pins — C123 is the rail-entry third</td></tr>
<tr><td>Cu4</td><td>100 nF</td><td>U18 pin 36 V_BCKP (/3V3!)</td><td>V_BCKP rides the always-on 3V3, not 3V3_GNSS — that is deliberate (RTC + ephemeris survive when U21 is switched off). It needs its own /3V3 cap at the pin; C120–C124 are all on /3V3_GNSS and cannot serve it. Repurpose the spare Cu4.</td></tr>
<tr><td>C144 C145</td><td>100 nF · 100 pF</td><td>U18 VCC_RF · L21 bias tee</td><td>at L21's feed side — the antenna supply injection</td></tr>
<tr><td>C143</td><td>100 nF RF</td><td>J12 RF line (DC block)</td><td>inline in the 50 Ω run between L21 tap and J12</td></tr>
<tr><td>D27</td><td>ESD9L (≤0.5 pF)</td><td>J12</td><td>at the connector, stub-less tap on the RF line</td></tr>
</table></div>
<p>This corner is the victim: no switcher, no inductor, no fast digital within ~25 mm (U25 sits ~95 mm away in the SE, the fan buck ~130 mm NE — keep it that way); nothing but solid In1 ground under the module and the RF run. <code>TIMEPULSE</code> to the MCU is the one digital line that leaves — route it away from the RF path.</p>
@ -167,27 +174,207 @@ a{color:var(--plane)}
<h2><span class="no">§6</span>MCU core — U16 <span class="tag q">analog care</span></h2>
<div class="tblwrap"><table>
<tr><th>Ref</th><th>Value</th><th>Belongs to</th><th>Placement</th></tr>
<tr><td>C111</td><td>4.7 µF</td><td>VCAP1</td><td>≤ 3 mm from the pin — the core regulator, non-negotiable</td></tr>
<tr><td>C109 C112</td><td>100 nF · 1 µF</td><td>VDDA</td><td>at the VDDA pin, on the quiet side, away from the switchers</td></tr>
<tr><td>C105–C108 + C110</td><td>100 nF ×4 + 4.7 µF</td><td>VDD pins</td><td>one 100 nF per VDD pin, bulk nearby</td></tr>
<tr><td>C104</td><td>100 nF</td><td>NRST</td><td>at the pin, with P10 reset routed clean</td></tr>
<tr><td>Y3 + C113 C114</td><td>HSE + 15 pF</td><td>crystal</td><td>tight triangle at the pins, local ground, ≥10 mm from any inductor</td></tr>
<tr><td>Y4 + C115 C116</td><td>LSE 32 kHz + 12 pF</td><td>crystal</td><td>even tighter — high-impedance oscillator, guard with ground</td></tr>
<tr><td>C5 C7 C9 C24 C157</td><td>100 nF ×5</td><td>ADC sense filters</td><td>with their dividers (R42, R126…) at the MCU pins, not at the jacks — the wire filters at the pin</td></tr>
<tr><td>C111</td><td>4.7 µF</td><td>U16 VCAP1</td><td>≤ 3 mm from the pin — the core regulator, non-negotiable</td></tr>
<tr><td>C109 C112</td><td>100 nF · 1 µF</td><td>U16 VDDA</td><td>at the VDDA pin, on the quiet side, away from the switchers</td></tr>
<tr><td>C105–C108 + C110</td><td>100 nF ×4 + 4.7 µF</td><td>U16 VDD pins</td><td>one 100 nF per VDD pin, bulk nearby</td></tr>
<tr><td>C104</td><td>100 nF</td><td>U16 NRST</td><td>at the pin, with the P6 reset jack routed clean</td></tr>
<tr><td>Y3 + C113 C114</td><td>HSE + 15 pF</td><td>U16 OSC pins</td><td>tight triangle at the pins, local ground, ≥10 mm from any inductor</td></tr>
<tr><td>Y4 + C115 C116</td><td>LSE 32 kHz + 12 pF</td><td>U16 OSC32 pins</td><td>even tighter — high-impedance oscillator, guard with ground</td></tr>
<tr><td>C5 C7 C9 C24 C157</td><td>100 nF ×5</td><td>U16 ADC pins</td><td>with their dividers (R42, R126…) at the MCU pins, not at the jacks — the wire filters at the pin</td></tr>
</table></div>
<h2><span class="no">§7</span>The rest of the families</h2>
<ul>
<li><b>Ethernet</b> (<code>U11</code>/<code>U12</code>, <code>Y1</code>, <code>C90–C103</code>): one block, U11 close to J10 (magnetics are inside the HR911105A), crystal + 18 pF at the pins, the 3V3_ETH locals (<code>C92–C94</code>, <code>C98</code>, <code>C102</code>, <code>C103</code>) around U11. Keep the RJ45 pair region free of other routes.</li>
<li><b>RS232</b> (<code>U15</code>): <code>C85–C88</code> are charge-pump flying caps — they carry the pump current, so they sit hard against U15; <code>C84</code> 100 nF at its supply pin. P16 at the edge.</li>
<li><b>RS232</b> (<code>U15</code>): <code>C85–C88</code> are charge-pump flying caps — they carry the pump current, so they sit hard against U15; <code>C84</code> 100 nF at its supply pin. P12 (RS232 jack) at the edge.</li>
<li><b>UART bridge</b> (<code>U10</code>, <code>Y2</code>, <code>C82</code>/<code>C83</code>): crystal caps at the pins, block near the MCU.</li>
<li><b>Fan buck</b> (<code>U2</code>, <code>Cbst1</code>, <code>Ccomp1</code>, <code>C23</code>): a third switcher — treat like §4's loop, and park it in the noisy quarter near the power entry, away from GNSS.</li>
<li><b>Accelerometer</b> (<code>U26</code>, <code>C158</code>/<code>C159</code> at VDD/VDDIO, <code>C160</code> bulk): ≤ 10 mm from a mounting hole, on stiff board — it should feel the chassis, not board flex.</li>
<li><b>Rail sprinkles</b> (<code>Cu3</code> <code>Cu4</code> <code>C80</code> <code>C81</code> <code>C89</code> <code>C105+</code>…): every 100 nF on 3V3 pairs with one IC's power pin — when you move an IC, its 100 nF moves with it. <code>C118</code> 22 µF is the 3V3 bulk at the regulator.</li>
<li><b>NTC channels</b>: pullup + filter (<code>R42</code>+<code>C24</code>, <code>R126</code>+<code>C157</code>) at the MCU; <code>P12</code>/<code>P18</code> jacks at the harness edge.</li>
<li><b>NTC channels</b>: pullup + filter (<code>R42</code>+<code>C24</code> → P7, <code>R126</code>+<code>C157</code> → P8) at the MCU; <code>P7</code>/<code>P8</code> jacks at the harness edge.</li>
</ul>
<h2><span class="no">§8</span>Separation rules</h2>
<h2><span class="no">§8</span>Master proximity table — every discrete, its owner designator, and how close</h2>
<p>Computed from the current board netlist (post-rename, all 300 footprints), then hand-corrected where the netlist heuristic picked a connected-but-wrong owner (ESD parts belong at connectors, ADC filters at the MCU, comp caps at their regulator). Distance classes:</p>
<p><span class="d d1">🔴 GLUED ≤3 mm</span> loop-critical — same pad ground, no via between &nbsp; <span class="d d2">🟠 CLOSE ≤5 mm</span> at the pin, short direct trace &nbsp; <span class="d d3">🔵 BLOCK ≤15 mm</span> same cluster, routing order free</p>
<div class="tblwrap"><table>
<tr><th>Ref</th><th>Value</th><th>Glue to</th><th></th><th>Why</th></tr>
<tr class="own"><td colspan="5">P1 — 24 V entry (SE corner)</td></tr>
<tr><td>D10</td><td>SMBJ33A</td><td>P1 pin 1 (24V_IN)</td><td><span class="d d1">🔴</span></td><td>surge clamp at the jack, before anything else</td></tr>
<tr><td>Rig1</td><td>22k</td><td>P1 ignition pin</td><td><span class="d d1">🔴</span></td><td>series element right at the jack</td></tr>
<tr><td>F2</td><td>2 A slow 1812</td><td>P1 → Q23</td><td><span class="d d3">🔵</span></td><td>first element in the chain</td></tr>
<tr><td>Q23</td><td>IRFR5305</td><td>F2 output</td><td><span class="d d3">🔵</span></td><td>reverse-polarity FET, chain order</td></tr>
<tr><td>R119 D33</td><td>100k · BZX84C15</td><td>Q23 G–S pads</td><td><span class="d d1">🔴</span></td><td>gate bias + 15 V clamp across the FET itself</td></tr>
<tr><td>D32</td><td>SMCJ33A</td><td>between Q23 and C149/C150</td><td><span class="d d2">🟠</span></td><td>clamps the line before the buck sees it</td></tr>
<tr><td>Q10</td><td>IRFR5305</td><td>P1 24 V branch</td><td><span class="d d3">🔵</span></td><td>legacy 24 V rail FET (feeds U2 + K1)</td></tr>
<tr><td>Rrp1 DzG1</td><td>10k · BZX84C15</td><td>Q10 G–S pads</td><td><span class="d d1">🔴</span></td><td>gate network — the part behind the v5.5 jumper check</td></tr>
<tr><td>C10 C22</td><td>47 µF/50 V · 22 µF/50 V</td><td>Q10 load side (/24V)</td><td><span class="d d3">🔵</span></td><td>24 V rail bulk</td></tr>
<tr class="own"><td colspan="5">U25 — TPS54360B vehicle buck</td></tr>
<tr><td>C149 C150</td><td>2.2 µF/100 V</td><td>U25 pins 2/7 (VIN–GND)</td><td><span class="d d1">🔴</span></td><td>THE hot loop of the board</td></tr>
<tr><td>D34</td><td>B560C</td><td>U25 SW + C149 ground</td><td><span class="d d1">🔴</span></td><td>closes the hot loop</td></tr>
<tr><td>C151</td><td>100 µF/50 V</td><td>behind C149/C150</td><td><span class="d d2">🟠</span></td><td>input bulk</td></tr>
<tr><td>C152</td><td>100 nF</td><td>U25 BOOT (pins 1–8)</td><td><span class="d d1">🔴</span></td><td>boot flyer</td></tr>
<tr><td>L22</td><td>10 µH SRP1265A</td><td>U25 SW pin</td><td><span class="d d2">🟠</span></td><td>minimal SW copper</td></tr>
<tr><td>C153 C154</td><td>47 µF/16 V</td><td>L22 output pad</td><td><span class="d d2">🟠</span></td><td>VEH5V output caps; FB taps here</td></tr>
<tr><td>R121 R122</td><td>53.6k · 10.2k</td><td>U25 FB pin, tap at C153</td><td><span class="d d2">🟠</span></td><td>thin trace, away from L22's field</td></tr>
<tr><td>R123 C155 C156</td><td>13k · 6.8 nF · 39 pF</td><td>U25 COMP pin</td><td><span class="d d2">🟠</span></td><td>compensation — quiet side, short ground</td></tr>
<tr><td>R120</td><td>162k</td><td>U25 RT pin</td><td><span class="d d2">🟠</span></td><td>frequency-set, short ground</td></tr>
<tr><td>R124 R125</td><td>UVLO (DNP)</td><td>U25 EN pin</td><td><span class="d d2">🟠</span></td><td>divider lands here if fitted</td></tr>
<tr class="own"><td colspan="5">U6 — LTC4040 battery backup (+P3)</td></tr>
<tr><td>RS1</td><td>10 mΩ 1%</td><td>U6 sense pins</td><td><span class="d d1">🔴</span></td><td>Kelvin — sense routes from the pad faces</td></tr>
<tr><td>L6</td><td>2.2 µH/8 A</td><td>U6 SW pins</td><td><span class="d d1">🔴</span></td><td>charger/boost loop tight</td></tr>
<tr><td>C30 C31 C78</td><td>10 µF · 2.2 µF · 220 µF</td><td>U6 VIN, after RS1</td><td><span class="d d2">🟠</span></td><td>VEH5V bank at the input pins</td></tr>
<tr><td>C32 C33 C34</td><td>47 µF ×2 · 10 µF</td><td>U6 MAIN5V output / Q7</td><td><span class="d d2">🟠</span></td><td>output bank</td></tr>
<tr><td>C35</td><td>22 µF</td><td>U6 BAT pin → P3</td><td><span class="d d2">🟠</span></td><td>on the wide /BAT_PLUS copper</td></tr>
<tr><td>Q7</td><td>AO4406A</td><td>U6 gate/PowerPath side</td><td><span class="d d2">🟠</span></td><td>the blackout switch</td></tr>
<tr><td>R50</td><td>825R 1%</td><td>U6 PROG pin</td><td><span class="d d1">🔴</span></td><td>precision current-program R</td></tr>
<tr><td>R51 R52</td><td>270k · 100k</td><td>U6 PFI pin</td><td><span class="d d2">🟠</span></td><td>power-fail divider</td></tr>
<tr><td>R53 R54</td><td>499k · 100k</td><td>U6 FB pin</td><td><span class="d d2">🟠</span></td><td>boost feedback</td></tr>
<tr><td>R55</td><td>10k 1%</td><td>U6 NTC pin</td><td><span class="d d2">🟠</span></td><td>pack-thermistor divider (probe enters at P3)</td></tr>
<tr><td>JP2 JP3 JP4</td><td>F-straps</td><td>U6 F0/F1/F2 pins</td><td><span class="d d3">🔵</span></td><td>logic straps</td></tr>
<tr><td>R56 R57 R58</td><td>100k ×3</td><td>U6 ↔ U16 route</td><td><span class="d d3">🔵</span></td><td>status/enable pulls, anywhere on the line</td></tr>
<tr><td>TP1 TP2</td><td>CLPROG · PFO</td><td>U6 block edge</td><td><span class="d d3">🔵</span></td><td>probe access</td></tr>
<tr class="own"><td colspan="5">U2 — MP1584 fan buck (+P4)</td></tr>
<tr><td>C11</td><td>100 nF</td><td>U2 VIN pin</td><td><span class="d d1">🔴</span></td><td>non-sync input loop (C10/C22 bulk behind)</td></tr>
<tr><td>Dbk1</td><td>SS34</td><td>U2 SW + input ground</td><td><span class="d d1">🔴</span></td><td>catch diode — same loop</td></tr>
<tr><td>Cbst1</td><td>10 nF</td><td>U2 BOOT–SW</td><td><span class="d d1">🔴</span></td><td>boot flyer</td></tr>
<tr><td>L2</td><td>10 µH</td><td>U2 SW</td><td><span class="d d2">🟠</span></td><td>minimal SW copper</td></tr>
<tr><td>C23</td><td>22 µF/16 V</td><td>L2 output → P4</td><td><span class="d d2">🟠</span></td><td>5V_FAN output cap</td></tr>
<tr><td>R20 R21</td><td>100k · 19.1k</td><td>U2 FB, tap at C23</td><td><span class="d d2">🟠</span></td><td>feedback divider</td></tr>
<tr><td>Rcomp1 Ccomp1</td><td>40.2k · 2.2 nF</td><td>U2 COMP pin</td><td><span class="d d2">🟠</span></td><td>compensation</td></tr>
<tr><td>Rfreq1</td><td>100k</td><td>U2 FREQ pin</td><td><span class="d d2">🟠</span></td><td>frequency-set</td></tr>
<tr><td>Ren1 Ren2</td><td>100k · 20k</td><td>U2 EN pin</td><td><span class="d d3">🔵</span></td><td>enable divider</td></tr>
<tr><td>D2</td><td>SS34</td><td>P4 pins</td><td><span class="d d1">🔴</span></td><td>flyback right at the fan jack</td></tr>
<tr><td>Q2 Rgs1</td><td>AO3400A · 100k</td><td>P4 return · Rgs1 on Q2 G–S</td><td><span class="d d2">🟠</span></td><td>low-side switch at the jack, G–S resistor glued</td></tr>
<tr><td>Q5 Rg1</td><td>MMBT3904 · 100R</td><td>Q2 gate</td><td><span class="d d3">🔵</span></td><td>gate driver stage</td></tr>
<tr><td>JP1</td><td>0R</td><td>P4 5V_FAN link</td><td><span class="d d3">🔵</span></td><td>current-measure link</td></tr>
<tr class="own"><td colspan="5">U20 — AP64350 modem buck</td></tr>
<tr><td>C127 C128</td><td>10 µF · 1 µF</td><td>U20 VIN pin 2</td><td><span class="d d1">🔴</span></td><td>sync input loop — 1 µF closest of all</td></tr>
<tr><td>C129</td><td>100 nF</td><td>U20 BOOT–SW</td><td><span class="d d1">🔴</span></td><td>boot flyer</td></tr>
<tr><td>L20</td><td>4.7 µH/5 A</td><td>U20 SW</td><td><span class="d d2">🟠</span></td><td>minimal SW copper</td></tr>
<tr><td>D21</td><td>PMEG3050EP</td><td>U20 VIN feed</td><td><span class="d d2">🟠</span></td><td>input ORing</td></tr>
<tr><td>D20</td><td>SS34</td><td>24 V → U20 input branch</td><td><span class="d d3">🔵</span></td><td>feed diode</td></tr>
<tr><td>R110 R111</td><td>75k · 20k</td><td>U20 FB pin</td><td><span class="d d2">🟠</span></td><td>feedback divider</td></tr>
<tr><td>R116 C141 C142</td><td>14k · 3.3 nF · 47 pF</td><td>U20 COMP pin</td><td><span class="d d2">🟠</span></td><td>compensation</td></tr>
<tr><td>R117</td><td>200k 1%</td><td>U20 RT pin</td><td><span class="d d2">🟠</span></td><td>frequency-set</td></tr>
<tr><td>R113</td><td>100k</td><td>U20 EN pin</td><td><span class="d d3">🔵</span></td><td>enable pull</td></tr>
<tr class="own"><td colspan="5">U19 — A7670SA modem (+U22 SIM · U23/U24 · K1 · J13)</td></tr>
<tr><td>C135 C136</td><td>33 pF · 10 pF</td><td>U19 pins 55/56/57</td><td><span class="d d1">🔴</span></td><td>TX-harmonic filters — the closest of the bank</td></tr>
<tr><td>C134 C133 C132</td><td>100 nF → 1 µF → 22 µF</td><td>stepping back from those pins</td><td><span class="d d2">🟠</span></td><td>the gradient</td></tr>
<tr><td>C130 C131</td><td>220 µF/10 V ×2</td><td>far end of U19's VBAT bank</td><td><span class="d d3">🔵</span></td><td>≥300 µF burst reservoir</td></tr>
<tr><td>D22</td><td>SMAJ5.0A</td><td>at C130/C131</td><td><span class="d d2">🟠</span></td><td>VBAT clamp</td></tr>
<tr><td>R112</td><td>1k</td><td>U19 VBAT side</td><td><span class="d d3">🔵</span></td><td>bleed/monitor</td></tr>
<tr><td>C125</td><td>2.2 µF</td><td>U19 pin 15 (1V8 out)</td><td><span class="d d1">🔴</span></td><td>LDO output cap</td></tr>
<tr><td>U22</td><td>SIM (fp TBD)</td><td>U19 pins 30–33, ≤5 mm</td><td><span class="d d2">🟠</span></td><td>unshielded SIM bus</td></tr>
<tr><td>C126</td><td>100 nF</td><td>U22 VCC pad</td><td><span class="d d1">🔴</span></td><td>SIM supply</td></tr>
<tr><td>D23 D24 D25</td><td>PESD5V0S1BA</td><td>U22 SIM pads</td><td><span class="d d1">🔴</span></td><td>ESD at the socket, not mid-route</td></tr>
<tr><td>U23 U24</td><td>74LVC2T45</td><td>between U19 and U16</td><td><span class="d d3">🔵</span></td><td>level shifters sit on the path</td></tr>
<tr><td>C137 C139</td><td>100 nF</td><td>U23/U24 1V8 pins</td><td><span class="d d1">🔴</span></td><td>supply pair, one each</td></tr>
<tr><td>C138 C140</td><td>100 nF</td><td>U23/U24 3V3 pins</td><td><span class="d d1">🔴</span></td><td>supply pair, one each</td></tr>
<tr><td>Q20 R114</td><td>2N7002 · 100k</td><td>U19 PWRKEY side</td><td><span class="d d3">🔵</span></td><td>open-drain driver</td></tr>
<tr><td>Q21 R115</td><td>2N7002 · 100k</td><td>U19 RESET side</td><td><span class="d d3">🔵</span></td><td>open-drain driver</td></tr>
<tr><td>R109 Q22 Rmled1 D4G1</td><td>NETLIGHT chain</td><td>U19 netlight pin → LED at a visible spot</td><td><span class="d d3">🔵</span></td><td>status LED driver</td></tr>
<tr><td>R106 R107 R108</td><td>100R · 0R ×2</td><td>U23/U24 line ends</td><td><span class="d d3">🔵</span></td><td>UART series elements</td></tr>
<tr><td>TP8</td><td>USB_BOOT</td><td>U19 BOOT pin</td><td><span class="d d3">🔵</span></td><td>probe access</td></tr>
<tr><td>R118 C146 C147</td><td>0R · DNP pi</td><td>inline in the 50 Ω run, U19 RF pad → J13</td><td><span class="d d1">🔴</span></td><td>matching seats — zero stubs</td></tr>
<tr><td>D28</td><td>ESD9L</td><td>J13</td><td><span class="d d1">🔴</span></td><td>at the connector, stub-less</td></tr>
<tr><td>D6</td><td>1N4148W</td><td>K1 coil pins</td><td><span class="d d1">🔴</span></td><td>flyback across the coil</td></tr>
<tr><td>Q6 R2 C101</td><td>MMBT3904 · 10k · 22 µF</td><td>K1 coil side</td><td><span class="d d2">🟠</span></td><td>relay driver + coil supply cap</td></tr>
<tr class="own"><td colspan="5">U18 — ZED-F9P GNSS (+U21 LDO · J12)</td></tr>
<tr><td>C121 C122</td><td>100 nF ×2</td><td>U18 VCC pins 33/34, one each</td><td><span class="d d1">🔴</span></td><td>per-pin decouplers</td></tr>
<tr><td>C123</td><td>100 nF</td><td>U21 VOUT beside C120</td><td><span class="d d2">🟠</span></td><td>third 100 nF — rail entry, not a third VCC pin</td></tr>
<tr><td>Cu4</td><td>100 nF</td><td>U18 pin 36 V_BCKP — on /3V3</td><td><span class="d d1">🔴</span></td><td>only /3V3 cap in the corner; backup rail stays alive when U21 is off</td></tr>
<tr><td>C120 C124</td><td>10 µF ×2</td><td>U21 output · U18 VCC entry</td><td><span class="d d1">🔴</span></td><td>LDO stability + local bulk</td></tr>
<tr><td>C119</td><td>10 µF</td><td>U21 input pin</td><td><span class="d d1">🔴</span></td><td>LDO input</td></tr>
<tr><td>C3 C40</td><td>47 µF · 22 µF</td><td>SW_5V feed near U21</td><td><span class="d d2">🟠</span></td><td>upstream bulk</td></tr>
<tr><td>Q1</td><td>AO3401A</td><td>upstream of U21 on SW_5V</td><td><span class="d d3">🔵</span></td><td>rail switch</td></tr>
<tr><td>R105</td><td>10k</td><td>U18 pull-up</td><td><span class="d d3">🔵</span></td><td>strap</td></tr>
<tr><td>L21 C144 C145</td><td>68 nH · 100 nF · 100 pF</td><td>bias-tee tap on the 50 Ω run</td><td><span class="d d1">🔴</span></td><td>antenna supply injection</td></tr>
<tr><td>C143</td><td>100 nF RF</td><td>inline, L21 tap → J12</td><td><span class="d d1">🔴</span></td><td>DC block</td></tr>
<tr><td>D27</td><td>ESD9L ≤0.5 pF</td><td>J12</td><td><span class="d d1">🔴</span></td><td>at the connector, stub-less</td></tr>
<tr class="own"><td colspan="5">U16 — STM32 MCU (+Y3/Y4 · J11/J9 USB · sense jacks)</td></tr>
<tr><td>C111</td><td>4.7 µF</td><td>U16 VCAP1 pin</td><td><span class="d d1">🔴</span></td><td>core regulator — non-negotiable</td></tr>
<tr><td>R104</td><td>10R</td><td>U16 VDDA feed</td><td><span class="d d1">🔴</span></td><td>analog supply filter element</td></tr>
<tr><td>C109 C112</td><td>100 nF · 1 µF</td><td>U16 VDDA pin, behind R104</td><td><span class="d d1">🔴</span></td><td>analog decoupling</td></tr>
<tr><td>C105 C106 C107 C108</td><td>100 nF ×4</td><td>one per U16 VDD pin</td><td><span class="d d1">🔴</span></td><td>the netlist pass parked C106–C108 near U3 — they are U16's, bring them back</td></tr>
<tr><td>C110</td><td>4.7 µF</td><td>U16 VDD bulk</td><td><span class="d d2">🟠</span></td><td>local reservoir</td></tr>
<tr><td>C104</td><td>100 nF</td><td>U16 NRST pin</td><td><span class="d d1">🔴</span></td><td>the P6 jack just routes to it</td></tr>
<tr><td>Y3 C113 C114</td><td>25 MHz · 15 pF</td><td>U16 OSC pins</td><td><span class="d d1">🔴</span></td><td>tight triangle, local ground</td></tr>
<tr><td>Y4 C115 C116</td><td>32.768 kHz · 12 pF</td><td>U16 OSC32 pins</td><td><span class="d d1">🔴</span></td><td>tighter still — high-impedance osc</td></tr>
<tr><td>R5 R6 C5</td><td>10k ×2 · 100 nF</td><td>U16 BAT_SENSE ADC pin</td><td><span class="d d2">🟠</span></td><td>divider + filter at the pin</td></tr>
<tr><td>R7 R8 C7</td><td>10k ×2 · 100 nF</td><td>U16 5V_SENSE ADC pin</td><td><span class="d d2">🟠</span></td><td>divider + filter at the pin</td></tr>
<tr><td>Rig2 Rigb1 C9 D5</td><td>22k · 3.3k · 100 nF · BAT54</td><td>U16 IGN_SENSE ADC pin</td><td><span class="d d2">🟠</span></td><td>divider + filter + clamp at the pin</td></tr>
<tr><td>R42 C24</td><td>10k 1% · 100 nF</td><td>U16 NTC1 ADC pin — P7 is just the jack</td><td><span class="d d2">🟠</span></td><td>the probe wire filters at the pin</td></tr>
<tr><td>R126 C157</td><td>10k 1% · 100 nF</td><td>U16 NTC2 ADC pin (PC4) — P8 is just the jack</td><td><span class="d d2">🟠</span></td><td>same rule</td></tr>
<tr><td>R98 R99</td><td>10k ×2</td><td>U16 BOOT straps</td><td><span class="d d3">🔵</span></td><td>anywhere in the block</td></tr>
<tr><td>Rdfu1</td><td>22k</td><td>U16 USART1_RX</td><td><span class="d d3">🔵</span></td><td>series element</td></tr>
<tr><td>TP3 TP4 TP5</td><td>SWDIO · SWCLK · NRST</td><td>U16 side / P6 side</td><td><span class="d d3">🔵</span></td><td>probe access</td></tr>
<tr><td>R1 R40 R56</td><td>1k · 1k · 100k</td><td>control lines U16 → power blocks</td><td><span class="d d3">🔵</span></td><td>anywhere along the route</td></tr>
<tr><td>D17</td><td>LED (blue)</td><td>U16 PC13 vicinity, visible</td><td><span class="d d3">🔵</span></td><td>heartbeat LED</td></tr>
<tr><td>Q4 Rled1</td><td>AO3400A · 10k</td><td>near the P9/P10 edge</td><td><span class="d d3">🔵</span></td><td>external-LED driver</td></tr>
<tr><td>D13</td><td>SS34</td><td>J11 VBUS</td><td><span class="d d2">🟠</span></td><td>VBUS ORing</td></tr>
<tr><td>D14 D15 D16</td><td>PESD5V0S1BA</td><td>J11 pads (DP/DM/VBUS)</td><td><span class="d d1">🔴</span></td><td>ESD at the jack — never mid-route</td></tr>
<tr><td>R100 R101</td><td>5.1k</td><td>J11 CC pads</td><td><span class="d d1">🔴</span></td><td>USB-C sink ID</td></tr>
<tr><td>Rcc1 Rcc2</td><td>5.1k</td><td>J9 CC pads</td><td><span class="d d1">🔴</span></td><td>USB-C sink ID</td></tr>
<tr class="own"><td colspan="5">U11 — W5500 Ethernet (+U12 buck · Y1 · J10)</td></tr>
<tr><td>C90</td><td>10 µF</td><td>U11 TOCAP pin</td><td><span class="d d1">🔴</span></td><td>internal-regulator cap</td></tr>
<tr><td>C91</td><td>4.7 µF</td><td>U11 1V2 pin</td><td><span class="d d1">🔴</span></td><td>core rail</td></tr>
<tr><td>C92 C93 C98</td><td>100 nF ×3</td><td>U11 VCC pins, one each</td><td><span class="d d1">🔴</span></td><td>per-pin decouplers</td></tr>
<tr><td>C94 C102</td><td>10 µF · 22 µF</td><td>U11 supply entry</td><td><span class="d d2">🟠</span></td><td>local bulk</td></tr>
<tr><td>Y1 C95 C96</td><td>25 MHz · 18 pF</td><td>U11 XI/XO pins</td><td><span class="d d1">🔴</span></td><td>tight triangle</td></tr>
<tr><td>R83</td><td>12.4k 1%</td><td>U11 RSET pin</td><td><span class="d d1">🔴</span></td><td>precision bias — short, clean ground</td></tr>
<tr><td>R84</td><td>10k</td><td>U11 PMODE</td><td><span class="d d3">🔵</span></td><td>strap</td></tr>
<tr><td>R92 R93</td><td>49.9R 1%</td><td>U11 TX pins → J10</td><td><span class="d d1">🔴</span></td><td>line terminations</td></tr>
<tr><td>R94 R95 C97</td><td>49.9R ×2 · 6.8 nF</td><td>J10 center taps</td><td><span class="d d1">🔴</span></td><td>Bob Smith / RCT network at the magjack</td></tr>
<tr><td>C103</td><td>100 µF/6.3 V</td><td>J10 3V3 feed</td><td><span class="d d2">🟠</span></td><td>magnetics supply bulk</td></tr>
<tr><td>R96 R97</td><td>330R</td><td>J10 LED pins</td><td><span class="d d3">🔵</span></td><td>link/act LEDs</td></tr>
<tr><td>D11</td><td>SS34</td><td>U12 SW + input ground</td><td><span class="d d1">🔴</span></td><td>ETH buck catch diode</td></tr>
<tr><td>C100</td><td>10 nF</td><td>U12 BOOT</td><td><span class="d d1">🔴</span></td><td>boot flyer</td></tr>
<tr><td>L8</td><td>10 µH/2 A</td><td>U12 SW</td><td><span class="d d2">🟠</span></td><td>minimal SW copper</td></tr>
<tr><td>R85 R86 R87 R88</td><td>FREQ · COMP · FB</td><td>U12 pins</td><td><span class="d d2">🟠</span></td><td>housekeeping at the IC</td></tr>
<tr class="own"><td colspan="5">U17 — AMS1117 3V3 LDO</td></tr>
<tr><td>C117</td><td>10 µF</td><td>U17 IN pin</td><td><span class="d d1">🔴</span></td><td>LDO input</td></tr>
<tr><td>C118</td><td>22 µF/16 V</td><td>U17 OUT pin</td><td><span class="d d1">🔴</span></td><td>LDO output / 3V3 bulk</td></tr>
<tr><td>D12</td><td>SS34</td><td>U17 input feed</td><td><span class="d d2">🟠</span></td><td>feed ORing</td></tr>
<tr class="own"><td colspan="5">Serial block — U15 RS232 · U10 UART · U14 RS485 · U13 expander</td></tr>
<tr><td>C85 C86 C87 C88</td><td>100 nF ×4</td><td>U15 pump pins, hard against the IC</td><td><span class="d d1">🔴</span></td><td>flying caps carry the pump current</td></tr>
<tr><td>C84</td><td>100 nF</td><td>U15 VCC pin</td><td><span class="d d1">🔴</span></td><td>supply</td></tr>
<tr><td>Y2 C82 C83</td><td>1.8432 MHz · 22 pF</td><td>U10 XTAL pins</td><td><span class="d d1">🔴</span></td><td>tight triangle</td></tr>
<tr><td>R30 R31</td><td>4.7k</td><td>U10 side of the I2C bus</td><td><span class="d d3">🔵</span></td><td>bus pull-ups</td></tr>
<tr><td>R80 R81</td><td>10k</td><td>U10 RST/IRQ</td><td><span class="d d3">🔵</span></td><td>pulls</td></tr>
<tr><td>C81 C89</td><td>100 nF ×2</td><td>U14 VCC (one is spare)</td><td><span class="d d1">🔴</span></td><td>supply</td></tr>
<tr><td>R82 JP5</td><td>120R · strap</td><td>P11 RS485 jack</td><td><span class="d d2">🟠</span></td><td>termination lives at the connector</td></tr>
<tr><td>C80</td><td>100 nF</td><td>U13 VCC pin</td><td><span class="d d1">🔴</span></td><td>supply</td></tr>
<tr><td>RB1 RB2 RB3 RB4<br>DB1 DB2 DB3 DB4</td><td>120R + LED ×4</td><td>U13 outputs → LEDs at the visible edge</td><td><span class="d d3">🔵</span></td><td>the netlist pass parked DB1–DB4 at U25 — they belong with U13</td></tr>
<tr class="own"><td colspan="5">U26 — SC7A20 accelerometer (at MH6)</td></tr>
<tr><td>C158 C159</td><td>100 nF ×2</td><td>U26 VDD/VDDIO pins</td><td><span class="d d1">🔴</span></td><td>one per supply pin</td></tr>
<tr><td>C160</td><td>10 µF</td><td>U26 bulk</td><td><span class="d d2">🟠</span></td><td>local reservoir; U26 itself ≤10 mm from MH6</td></tr>
<tr class="own"><td colspan="5">Panel · sensors · leftovers</td></tr>
<tr><td>Cu3</td><td>100 nF</td><td>U3 SHT40 VDD pin</td><td><span class="d d1">🔴</span></td><td>sensor supply</td></tr>
<tr><td>Cu4</td><td>100 nF</td><td>reassigned → U18 pin 36 V_BCKP</td><td><span class="d d1">🔴</span></td><td>see the GNSS group — it is already on /3V3–GND, so a board move is all it takes</td></tr>
<tr><td>C99</td><td>2.2 nF</td><td>U17/U12 area rail bypass</td><td><span class="d d3">🔵</span></td><td>verify its role in the sch before final spot</td></tr>
<tr><td>R41</td><td>10k</td><td>U3/U16 I2C area</td><td><span class="d d3">🔵</span></td><td>pull-up strap</td></tr>
<tr><td>R3 D1</td><td>120R · LED</td><td>P9 pins</td><td><span class="d d3">🔵</span></td><td>R3 at the jack</td></tr>
<tr><td>R89 R90 R91</td><td>120R ×3</td><td>P10 pins</td><td><span class="d d2">🟠</span></td><td>series at the jack — the wire radiates otherwise</td></tr>
<tr><td>D18 R102 R103</td><td>LED (green) · 1k ×2</td><td>3V3 rail, visible spot</td><td><span class="d d3">🔵</span></td><td>power LED</td></tr>
<tr><td>TP6 TP7</td><td>GND · 3V3</td><td>anywhere reachable</td><td><span class="d d3">🔵</span></td><td>probe access</td></tr>
</table></div>
<div class="note"><b>Rail 100 nF rule, restated:</b> every plain 100 nF above pairs with exactly one IC power pin and moves with that IC forever. If you relocate an IC during fine-tuning, its 🔴 rows move in the same drag.</div>
<h2><span class="no">§9</span>Separation rules</h2>
<div class="tblwrap"><table>
<tr><th>Keep apart</th><th>Min</th><th>Why</th></tr>
<tr><td>GNSS island ↔ any switcher L / SW node</td><td>25 mm</td><td>−167 dBm receiver vs. dV/dt fields</td></tr>
@ -198,7 +385,7 @@ a{color:var(--plane)}
<tr><td>U26 accel ↔ mounting hole</td><td>≤ 10 mm (close!)</td><td>chassis coupling, not board flex</td></tr>
</table></div>
<h2><span class="no">§9</span>Order of work from here</h2>
<h2><span class="no">§10</span>Order of work from here</h2>
<ol class="steps">
<li><b>Update PCB from Schematic</b> (NTC2 re-net), then fine-tune inside the §1 regions. Field wiring now enters mostly along the north edge + P1 at the SE corner — keep each connector beside its block, and its TVS/filter parts right at the jack, so cable common-mode currents stay off the board core.</li>
<li><b>Say the word and I re-add the four zones</b> — F.Cu GND pour, In1 GND (unbroken, especially under both RF runs and the crystals), In2 3V3, B.Cu GND — plus a stitching-via plan: 5 mm grid around RF, via fence along the board edge.</li>
@ -207,5 +394,5 @@ a{color:var(--plane)}
<li><b>Bench items carried forward:</b> measure U21's 3V3_GNSS rise (F9P ramp window), verify U25's EP against the TI DDA drawing, and the DzG1 jumper check on the fabbed v5.5 boards.</li>
</ol>
<p class="sub" style="margin-top:36px">EinarBoard HBOX v6 · Helgen · updated 03-Sep-2026 to the clustered layout (commits <code>ba70f62</code> · <code>c7d2552</code>).</p>
<p class="sub" style="margin-top:36px">EinarBoard HBOX v6 · Helgen · updated 05-Sep-2026: U18 V_BCKP (pin 36, /3V3) gets Cu4; C123 clarified as U21-output cap; §8 rows corrected.</p>
</div>

View File

@ -0,0 +1,138 @@
---
tags: [project-drogheda, hardware, bom, cost, power]
project: Project Drogheda
author: Helgen CTO
date: 2026-09-09
status: Draft — first-pass estimate
---
# 5 V source: on-board (V6 / U25) vs external Mean Well SD-15B-05 (V5)
> **Bottom line:** the on-board 24→5 V block already captured in V6 costs
> **≈ US$2.2 / MX$37 per board** in parts. The external Mean Well it replaces costs
> **≈ MX$330 per unit** all-in as currently purchased. **Net saving ≈ MX$290/unit**
> (≈ MX$8,700 at 30 units, ≈ MX$29,000 at 100). The money is not the interesting part —
> the isolation loss and the 3 A → 3.5 A headroom are.
Prices: LCSC/JLCPCB live database (2026-09-09) · FX **USD 1 = MXN 16.91**.
---
## 1. What each version actually does
| | **V5 (as built)** | **V6 (as captured in the schematic)** |
|---|---|---|
| 24→5 V | **External** Mean Well SD-15B-05 → `P2` (JST 2-pin) → `VEH5V` | **On-board** `U25` TPS54360B: `VIN_PROT` → `VEH5V` |
| Rating | 5 V / **3 A** isolated (1.5 kVDC) | 5 V / **3.5 A**, non-isolated, 60 V input |
| Entry chain | `Q10` (IRFR5305) + `D10` (SMBJ33A) → fan buck only | `F2 → Q23 → D32 (SMCJ33A) → C149/C150 → U25 → D34/L22 → C153/C154` |
| Extra parts on board | `P2` connector | 15 fitted + 2 DNP (`R124`/`R125`) |
V6 keeps the fan buck (`U2`) and the 3V3_ETH buck (`U12`); `U7` was deleted. Neither
ever made the main rail — this comparison is only about the `VEH5V` source.
---
## 2. On-board block — BOM cost per board
`U25` TPS54360B · `L22` SRP1265A-100M · `D34` B560C · `D32` SMCJ33A · `C149`/`C150`
2.2 µF/100 V · `C151` 100 µF/50 V · `C152` 100 nF · `C153`/`C154` 47 µF/16 V ·
`C155`/`C156` · `R120`–`R123` — plus the entry chain `F2` · `Q23` · `D33` · `R119`.
| Line | 30 boards | 100 boards | 500 boards |
|---|---:|---:|---:|
| `U25` TPS54360BDDAR (C524806) | $0.487 | $0.428 | $0.419 |
| `L22` SRP1265A-100M 10 µH/10 A (C840531) | $0.639 | $0.554 | $0.504 |
| `D34` B560C 5 A/60 V (C85100) | $0.220 | $0.179 | $0.158 |
| `D32` SMCJ33A TVS (C699039) | $0.114 | $0.091 | $0.079 |
| `C149`/`C150` 2.2 µF/100 V 1210 ×2 † | $0.180 | $0.180 | $0.160 |
| `C151` 100 µF/50 V elco (C134502) | $0.149 | $0.149 | $0.149 |
| `C153`/`C154` 47 µF/16 V 1210 ×2 (C5440143) | $0.185 | $0.185 | $0.127 |
| `C152`, `C155`, `C156`, `R120`–`R123` | $0.022 | $0.022 | $0.022 |
| **Buck subtotal** | **$1.996** | **$1.788** | **$1.618** |
| `Q23` IRFR5305 (C2624) | $0.262 | $0.227 | $0.193 |
| `F2` 2 A/1812 fuse †, `D33` BZX84C15, `R119` | $0.144 | $0.144 | $0.144 |
| **Entry subtotal** | **$0.406** | **$0.371** | **$0.337** |
| **Parts / board** | **$2.40** | **$2.16** | **$1.96** |
| JLC extended-part setup (7 new uniques × $3, amortised) | $0.70 | $0.21 | $0.04 |
| SMT placement (~50 joints) | $0.10 | $0.09 | $0.09 |
| **All-in / board** | **$3.20 · MX$54** | **$2.46 · MX$42** | **$2.09 · MX$35** |
† `C149`/`C150` and `F2` have no LCSC part pinned yet — estimated at $0.09 and $0.12.
Board outline stays 140×100, so **the block costs zero PCB area money**.
---
## 3. External Mean Well — cost per unit
| Item | MXN | Source |
|---|---:|---|
| Mean Well SD-15B-05 | **300** | `BOM-master.md` §1 item 12 (as purchased locally) |
| — same part, direct import | *189–200* | Mouser $11.15 / Arrow $11.85 @100 |
| DC PSU bracket (ASA print) | 20 | `BOM-master.md` §1 item 3 |
| `P2` JST-XH + housing + crimps + wire | ~12 | est. |
| **Total** | **≈ 332** *(≈ 221 if imported direct)* | |
Plus ~5 min/unit of mounting and wiring labour, and one more connector in the
vibration path — `P2` is exactly the class of joint the RCA blamed.
---
## 4. Net
| Volume | External (local price) | On-board | **Saving** |
|---|---:|---:|---:|
| per unit | MX$332 | MX$42 | **MX$290** |
| 30 units | MX$9,960 | MX$1,260 | **MX$8,700** |
| 100 units | MX$33,200 | MX$4,200 | **MX$29,000** |
Against best-case direct import (MX$221) the saving is still **≈ MX$180/unit**.
---
## 5. What actually decides this (not the money)
**For on-board:**
- **3 A → 3.5 A.** The 10-Ago boot-loop was the SD-15B's 3 A ceiling. It also makes the
standing `RS1` errata (3.12 A input limit sitting *above* a 3 A supply) go away by itself.
- One fewer sub-assembly, one fewer connector, no import lead time on a PSU.
**Against on-board:**
- **Isolation is lost.** SD-15B-05 is 1.5 kVDC isolated; TPS54360B is not. HBOX ground
becomes the cabinet 24 V return. The ignition already shares that return — worth a
deliberate look at the ground topology before this is locked.
- The EMC burden moves onto our board. The copper plan already calls `U25`'s input loop
the loudest on the PCB; that stops being Mean Well's problem.
- No field-replaceable part. Today a dead PSU is a 10-minute swap; after this it is a board.
---
## 6. 🔴 Side finding — the MP1584s cost more than this whole question
Genuine `MP1584EN-LF-Z` (C15051) is **$2.21 @100** on LCSC. V6 carries two (`U2`, `U12`)
= **$4.41 / MX$75 per board**, against **$0.43** for the TPS54360B or **$0.58** for the
AP64350 already qualified as `U20`.
Re-speccing those two rails onto a part already in the BOM saves **≈ $3.5 / MX$59 per
board** — more than the entire 5 V-source saving — and it also retires EMC findings 3 and 4,
which named the MP1584s directly. Worth a separate decision.
---
## 7. Battery — LiPo vs Li-ion, first pass
| | Energy | Price | **MXN / Wh** |
|---|---:|---:|---:|
| **Li-ion** Hyvel 18650 1S4P (current pack) | ~43 Wh | MX$204 | **≈ 4.7** |
| **LiPo** 2000 mAh 1S, SparkFun @ Sandorobotics | 7.4 Wh | MX$270 | **≈ 36** |
| **LiPo** 2000 mAh 1S, generic (indicative) | 7.4 Wh | ~MX$90–160 | **≈ 12–22** |
To reach today's 43 Wh with 2000 mAh pouches you need **6 in parallel**:
MX$540–1,620 against MX$204. Li-ion 18650 is **3–8× cheaper per Wh**, and it is also the
better cell for a box that runs hot — pouch cells swell, and heat is this product's
standing failure mode.
LiPo buys thickness and form factor, nothing else. Treat the 2000 mAh cells as bench
hardware. ⚠ The generic LiPo row is indicative — MercadoLibre/Amazon MX would not serve
prices to an automated fetch; get one real quote before this line is used in a decision.

View File

@ -0,0 +1,194 @@
---
tags: [project-drogheda, firmware, power, battery, thermal]
project: Project Drogheda
author: Helgen CTO
date: 2026-09-09
status: Draft — design proposal
---
# Backup mode: duty-cycled low power, and what it does to the charging problem
> **Bottom line:** yes, do the duty-cycle. But **the wake interval barely matters — the
> sleep floor is everything.** 10 min vs 60 min changes runtime by 2 %. Dropping the
> sleeping draw from 150 mW to 20 mW changes it by **5.5×**. And the payoff is bigger than
> runtime: at ~1.5 Wh/day the pack needs **~10 minutes of charging a day**, which the
> narrow cool window we already have can supply. **The duty-cycle largely dissolves the
> temperature problem** — that is the real reason to do it.
---
## 1. "Can't we just move the charge temperature?"
The **threshold** is ours (`RNTB`). The **limit** is the cell's. Charging an NMC/LCO 18650
at 55 °C is not a tuning choice — it is accelerated SEI growth, gas generation and a safety
liability inside a sealed IP67 box in full sun. Not something to ship on 30 units.
What you *can* legitimately buy, without changing the cell:
- **Lower the float.** Damage rate is temperature × voltage × time. You are already at
4.00 V rather than 4.20 V, which is most of the win. `JP3`/`JP4` → F1=0/F0=0 gives
**3.95 V** and a little more margin. That justifies nudging the hot pause to maybe
**48–50 °C** *if the cell's datasheet supports it*. A few degrees, not fifteen.
- Anything beyond that needs a cell rated for it — see the high-temp LFP note.
**But read §3 before deciding any of this.**
---
## 2. Duty-cycled backup — the numbers
Pack ~43 Wh, 85 % usable = **36.5 Wh**. Boost efficiency 88 %.
**Today, continuous:** ~2.5 W at the 5 V rail → **~13 h**; with the fan running, ~8 h.
**Duty-cycled**, wake = 10 s at 1.5 W (modem held in PSM, no re-attach):
| sleep floor | 10 min | 30 min | 60 min |
|---|---:|---:|---:|
| 150 mW | 7.7 d | 8.5 d | 8.7 d |
| 100 mW | 10.7 d | 12.4 d | 12.9 d |
| 50 mW | 17.9 d | **23.0 d** | 24.7 d |
| 20 mW | 29.8 d | **47.3 d** | 55.5 d |
| 10 mW | 38.3 d | 73.1 d | 94.6 d |
If the carrier will not give us PSM and every wake is a full LTE re-attach (~35 s):
| sleep floor | 10 min | 30 min | 60 min |
|---|---:|---:|---:|
| 150 mW | 5.6 d | 7.5 d | 8.1 d |
| 50 mW | 9.7 d | 16.9 d | 20.8 d |
| 20 mW | 12.5 d | 27.3 d | **38.8 d** |
**Read the rows, not the columns.** Your 10-minute instinct costs 20–50 % against 30 min
and buys the operator nothing they need. Go **30 min**, and spend the effort on the floor.
---
## 3. 🔴 The part that matters most — it also fixes the charging
| Duty cycle | Daily energy | Charging needed at 2.42 A |
|---|---:|---:|
| 30 min wake, 50 mW sleep | 1.59 Wh/day | **~10 min/day** |
| 30 min wake, 20 mW sleep | 0.77 Wh/day | **~5 min/day** |
| 10 min wake, 100 mW sleep | 3.41 Wh/day | ~22 min/day |
Today the pack must replace ~30 Wh after a long outage and we give it a charge window that
closes at 42 °C. At **1.6 Wh/day** you need *ten minutes* of cool weather. Port nights drop
well below 40 °C — the existing window is already enough.
So the ordering changes: **do the firmware work first.** It is free, it ships to the
installed fleet over the existing update path, and it may retire the cell question entirely.
Keep the high-temp LFP option open — but stop treating it as the first move.
It also means the 43 Wh pack is oversized. At 1.6 Wh/day a **single 12 Wh cell gives 6+
days** — cheaper, cooler, smaller, easier to keep topped up.
---
## 4. What actually sets the sleep floor
Ranked by what I expect to find on the bench. **All of these need measuring, not assuming.**
| Suspect | Est. | Fix |
|---|---:|---|
| **`U17` AMS1117-3.3** — quiescent 5 mA typ / 11 mA max, and it burns 1.7 V × I as heat | **25–55 mW** | Swap for a low-Iq LDO. `U21` (AP2112K, 55 µA) is already in the BOM; a TPS7A02-class part is ~1 µA. **This is probably the single biggest item and it is a one-part change** |
| **`K1` coil** — ~30 mA from SYS5V while the MCU is inserted | **150 mW** | See §5 |
| **LTC4040 boost** at very light load — light-load efficiency and its own Iq | 10–40 mW? | Measure. If bad, this caps everything and needs a rethink |
| Battery gauge LEDs `PB12–15`, `D17`, `D18`, `DB1–4`, `D4G1` | 50–150 mW | All off in backup, or one 50 ms blink per wake |
| `U12` MP1584 → `/3V3_ETH` + `W5500` (~130 mA) | 400+ mW | Ethernet is useless on battery — disable `U12`, hold `W5500` in reset |
| `MAX3232` charge pump, `SP3485`, `SC16IS752` | 5–15 mW | Shutdown pins / disable |
| `U18` `V_BCKP` | **0.15 mW** | ✅ already right — see §6 |
| MCU in STOP + LSE RTC | 0.1–0.2 mW | Achievable; today it is ~165 mW because `loop()` never stops |
⚠ **Today's `SLEEP` state is not a low-power state.** It cuts `PWR_CTRL` and then keeps
spinning `loop()` at 84 MHz with Serial, I²C polling and the gauge alive. Real STOP-mode
+ RTC wake is the substantive firmware change here. `Y4` (32.768 kHz) is on the board, so
LSE-driven RTC wake is available.
---
## 5. `K1` — reverse the earlier call
The Firmware v3.1 brief said: *"bobina ~30 mA de SYS5V … se queda insertado; 30 mA contra
12 Ah es ruido."* That was correct for a 13-hour continuous budget. **It is wrong for a
duty-cycled one** — 150 mW against a 20–50 mW target is 3–7× the entire rest of the floor,
and it alone would cap you at 8 days no matter what else you fix.
**New rule: `BYP_CTRL` low between wakes, high only during the TX window.** 150 mW × 10 s
per 30 min = 0.8 mW average — it disappears. The fail-safe semantics are unchanged
(de-energised = F9P→modem direct), and between wakes there is nothing to inject anyway.
Longer term, a **latching relay** (G6K-class, pulse to switch, zero hold) removes the
question permanently. Worth pricing for the v6 BOM.
---
## 6. Don't chase position — you already have the right parts
The machine is **parked**. Its position does not change during an outage, so re-acquiring
a fix every wake is pure waste.
- `V_BCKP` is already wired (`/3V3` via 1 kΩ + Schottky, 45 µA) and keeps ephemeris, so
when you *do* want a fix it is a hot start (~2–5 s), not a 30 s cold start. Good design,
already done.
- Report the **stored** position with a `pwr=BATT` flag and a staleness marker. Take a real
fix only every **N-th** wake (e.g. every 2 h) as an anti-theft check.
- **`U26` (SC7A20 accelerometer) is on the v6 board.** Wire its interrupt to a wake-capable
pin and you get motion-triggered wake for free: asleep while the machine is still, awake
within a second if someone moves it. That is strictly better than polling GNSS.
⚠ `U26` does not appear in any of the v6 briefs — confirm what it was placed for and that
its INT line reaches an EXTI/wake pin before counting on this.
---
## 7. Modem: PSM before power-cycling
Power-cycling the modem every 10 minutes means paying a full network attach every 10 minutes
— 20–30 s at 100–300 mA with 2 A bursts. **3GPP PSM keeps the module registered while it
sleeps at tens of µA**, with no re-attach cost; eDRX is the lighter variant.
- The A7670SA (Cat-1bis) supports PSM. **Whether you get it is a Telcel question**, not a
module question — it depends on the APN/plan and the network granting the requested TAU.
**Verify this early; the whole table in §2 hinges on which column you land in.**
- Fall back to `MODEM_PWR_EN` power-cycling only if PSM is refused — and then stretch the
interval to 30–60 min so the attach cost amortises.
⚠ **Inrush risk.** You already tripped the LTC4040 `PFI` at 4.60 V on LTE inrush with no
pack connected. Duty-cycling means taking that 2 A / 7.6 W burst **repeatedly, at low SoC,
on a hot or aged pack**. Hold the ≥ 470 µF modem bulk, and bench the worst case: coldest
SoC × hottest pack × attach burst. This is the most likely way the design fails in the field.
---
## 8. Proposed state machine
```
RUNNING ──(5V_SENSE PA4 falls, debounced)──► BACKUP_ENTRY
BACKUP_ENTRY: hot-start fix (or use last) → $PHLG{pwr=BATT, evt=POWER_LOSS} → TX now
(operators care most in the first minute — do not make them wait 30)
→ BACKUP_SLEEP: PWR_CTRL low · MODEM→PSM · BYP_CTRL low · U12/W5500 off
transceivers shutdown · LEDs off · fan off · MCU STOP + RTC(T)
→ wake on RTC(T) or accelerometer INT or PA4 rising:
read vbat/tbat → if vbat < BAT_CRITICAL: TX final "shutting down" → deep off
else: BYP_CTRL high → modem wake → TX heartbeat → back to BACKUP_SLEEP
→ PA4 rising: full wake, resume RUNNING
```
**Adaptive cadence** (cheap, and better product behaviour): 15 min for the first hour,
30 min to 24 h, 60 min after. Include the **drain rate** in `$PHLG` so ops can see how long
the unit has left rather than guessing.
**Fan: off in backup, full stop.** A 5 V fan is 0.75–1.25 W — it would consume the entire
budget in hours. If the box is too hot to sit idle without a fan, that is a thermal problem,
not a power-management one.
---
## 9. Do these three first
1. **Measure the sleeping current** on a v5.5 board with `PWR_CTRL` low, MCU in STOP, and
the loads in §4 disabled one at a time. That single number decides everything in §2.
2. **Ask Telcel about PSM/eDRX** on the current APN and plan. It sets which table applies.
3. **Pull the `tbat`/`tntc` fleet histogram** — with §3's 10 min/day figure in hand, the
question is no longer "is it ever cool enough" but "is it cool enough for ten minutes",
which is a much easier bar and may already be met.

View File

@ -0,0 +1,143 @@
---
tags: [project-drogheda, hardware, battery, thermal, power]
project: Project Drogheda
author: Helgen CTO
date: 2026-09-09
status: Draft — decision input
---
# Backup cell at 45 °C: why Li-ion is failing, and what replaces it
> **Bottom line:** LiPo is not an upgrade — a LiPo pouch *is* Li-ion, in a worse package.
> And before changing any cell: **`RNTB` = 10 k makes the LTC4040 stop charging at
> +40…+42 °C.** At a 45 °C port the pack essentially never charges. That is a resistor,
> not a chemistry. The chemistry that then lets you *raise* that limit is
> **high-temperature LiFePO4**, which the LTC4040 already supports — `JP2` re-strap, no respin.
---
## 1. LiPo is the wrong direction
A "LiPo" pouch is lithium-ion (LCO or NMC) in a soft laminate bag. Same 0–45 °C charge
window, same calendar aging, and **worse** in a hot sealed box: no vent, no CID, and it
swells. The 2000 mAh cells are bench hardware. Keep them on the bench.
---
## 2. 🔴 The charger is refusing to charge — and it is our resistor
`RNTB` (10 k 1 %) biases the pack NTC (`RT1`, 10 k, on the cell via `P4.3`). The LTC4040
pauses charging when the NTC falls to **53.6 % of `RBIAS`** and resumes below **325 %**.
With `RBIAS` = 10 k that puts the charge window at:
| NTC β | Charge window today |
|---|---|
| B3380 | **−3.1 °C … +42.4 °C** |
| B3950 | **+0.6 °C … +39.7 °C** |
Port operating temperature is ~45 °C. **The pack sits above the hot-pause threshold
for most of the working day**, so it never reaches full charge and the backup is not
there when the power drops. The MCU's own `CHRG_EN` hot-cut sits on top of this.
This is very likely most of the "poor performance" — and it is **not** the cell's fault.
Note the earlier fleet finding blamed no-charge on the LTC4040 safety-timer latch; a
pack that is repeatedly hot-paused mid-charge is exactly how you *reach* that latch.
### Re-scaling `RNTB`
You cannot simply raise this on the current cell — an NMC/LCO 18650 **must not** be
charged at 55 °C. Raise it *together with* the chemistry change:
| Target hot pause | β | New `RNTB` | Resulting window |
|---|---|---|---|
| +55 °C | B3380 | **6.62 k** | +6.1 … +55 °C |
| +55 °C | B3950 | 5.56 k | +12.3 … +55 °C |
| **+60 °C** | **B3380** | **5.67 k → 5.6 k E24** | **+9.7 … +60 °C** |
| +60 °C | B3950 | 4.64 k | +16.0 … +60 °C |
`RBIAS` sets both ends, so raising the hot limit raises the cold one. **Specify the
B3380 NTC** (the delta doc already calls B3380 "ideal", B3950 "acceptable") — with B3950
you would refuse to charge below +16 °C, which is a real winter night in Ensenada.
⚠ Computed from the datasheet's 53.6 % / 325 % ratios and a β model with R25 = 10 k.
Check against the actual NTC part's R/T table before cutting the BOM.
---
## 3. The cell: high-temperature LiFePO4 26650
| | Today — NMC/LCO 18650 | **High-temp LFP 26650** |
|---|---|---|
| Charge temp | 0…45 °C | **−20…60 °C** (Topwell 3600 mAh) · **0…75 °C** (JGNE 4000 mAh) |
| Discharge temp | −20…60 °C | −50…80 °C · −30…75 °C |
| Nominal | 3.6 V | 3.2 V |
| Cell energy | ~11 Wh (3 Ah) | 11.5 Wh (3.6 Ah) · **12.8 Wh (4 Ah)** |
| Cycle life | 300–500 | **≥ 2000** to 80 % |
| Thermal runaway onset | ~150–200 °C | ~270 °C (olivine) — does not propagate |
| Calendar aging at 45 °C | fast | far slower |
**4P of the 3.6 Ah cell = 46 Wh; 4P of the 4 Ah = 51 Wh** — both at or above today's
~43 Wh. Even 3P clears 38 Wh.
### The board already supports it — one jumper
LTC4040 `F2` selects chemistry: **high = Li-Ion, low = LiFePO4**. Float table:
| F1 | F0 | F2 = 1 (Li-Ion) | F2 = 0 (LiFePO4) |
|---|---|---|---|
| 0 | 0 | 3.95 V | 3.45 V |
| **0** | **1** | **4.00 V ← we are here** | **3.50 V ← we land here** |
| 1 | 0 | 4.05 V | 3.55 V |
| 1 | 1 | 4.10 V | 3.60 V |
Current straps are F2=1 / F1=0 / F0=1. **Flip `JP2` (F2) only** → 3.50 V LFP float.
The 2026-07-06 pre-fab audit already recorded this path as preserved. No schematic
change, no respin. 3.50 V on LFP is ~90 % SoC — deliberately shy of 3.65 V, which is
exactly what you want for calendar life at temperature. `JP3` to F1=1 gives 3.60 V if
you later want the last few percent.
### What else must change
1. **`RNTB` → 5.6 k** (§2) — without this the swap changes nothing; the charger still
pauses at 42 °C.
2. **Firmware `BAT_CRITICAL` / `BAT_RECOVERY`.** LFP's discharge curve is flat at ~3.2 V
from 90 % to 15 % SoC. Voltage-based SoC stops working. Re-derive the thresholds
against the knee (~2.9 V), and expect the gauge to be near-useless mid-band —
coulomb-count or accept a three-state indicator. The firmware review already flagged
these thresholds as bench-tune items for the 4.00 V pack; this makes it mandatory.
3. **MCU `CHRG_EN` hot-cut threshold** — raise in step with `RNTB`, or it becomes the
new binding limit.
4. **LTC4040 boost current.** 3.2 V nominal instead of 3.6 V ⇒ ~12 % more pack current
for the same backup watts. Re-check against the boost limit and the `RS1` errata fix.
5. **Mechanical.** 26650 is 26.5 mm vs 18.5 mm and ~85 g/cell. New ASA battery-case
print, and re-check the 4P footprint inside the HBOX.
6. **Sourcing.** The high-temp grades are Chinese-supplier parts (JGNE, Topwell class),
not a shelf item in MX. Check MOQ, lead time and whether they will give a real R/T
and cycle-at-temperature spec before this goes on the 30-unit BOM.
---
## 4. Alternatives, briefly
| Option | Verdict |
|---|---|
| **High-temp LFP 26650** | ✅ **Recommended.** Solves charge-at-temperature, board already supports it, cost/Wh comparable |
| **Supercap / LIC hold-up** | −40…+65/85 °C, no charge-temperature window at all, effectively infinite cycles — but ~1/20 the energy. **The right answer if the real requirement is "report a power-loss alarm and shut down cleanly" rather than "run for hours".** Pin the requirement before dismissing it again |
| **Smaller pack, better cell** | Nobody has pulled this lever. 43 Wh is a lot for a parked machine. Halve the requirement and the thermal problem halves with it |
| **LTO** | Best temperature range and cycle life, but 2.4 V nominal breaks the LTC4040's 1S window entirely — new charger. Low energy density, expensive, hard to source. **Out** |
| **High-temp-rated NMC 18650** | Keeps the form factor, but you are still fighting the same chemistry for a few °C. Marginal |
| **NiMH** | High self-discharge at temperature, and trickle-charging hot is its own failure mode. **Out** |
---
## 5. Do this first — you already have the data
Do **not** buy cells on an assumption. `tbat` (TMP102 on the pack) and `tntc` are already
in `$PHLG` and already landing in `htracker`. Pull the fleet histogram and answer:
1. What is the **actual** pack temperature distribution, by unit and by hour?
2. What **fraction of the day** does it sit above the current ~40–42 °C hot pause?
3. Does `cflt` / charge-fault correlate with those windows?
Question 2 is the number that decides whether this is a resistor change, a chemistry
change, or both. It is one query against data we are already collecting.

View File

@ -0,0 +1,158 @@
---
tags: [project-drogheda, test-plan, power, battery, firmware, backend]
project: Project Drogheda
author: Helgen CTO
date: 2026-09-10
status: Draft — evaluation procedure
---
# Backup-power evaluation: the procedure
> **It is not a lot of things to do. It is three measurements and a decision.**
> Everything else in the last three notes is *contingent* on them — most of that list
> deletes itself once D3, P1 and P2 come back. Do not start firmware before Gate G1.
**Critical path:** D3 (requirement) → P1 (sleep floor) → P2 (modem) → F1 (firmware) →
S1 (server) → decide the cell last. ≈ **3 weeks of elapsed time, ~7 person-days of work.**
---
## Phase D — desk, no hardware. 1 day. Kills most of the list.
| # | Task | Owner | Output |
|---|---|---|---|
| **D1** | Fleet `tbat`/`tntc` histogram from `htracker`: hourly distribution per unit over 30 d; **% of day above 42 °C**; longest continuous window **below** 40 °C; correlate `cflt` and charge events with those windows | backend | one chart + two numbers |
| **D2** | Ask Telcel: is **PSM / eDRX** available on our APN and plan? What TAU / Active Timer will the network grant? | Leo | yes/no + granted timers |
| **D3** | 🔴 **Pin the requirement.** From ICAVE ops / the contract, not from us: **how long must a unit keep reporting on battery, and what must it report?** | Leo | a number in hours, and a message spec |
**Gate G0 — do not proceed without D3.** Every number downstream is sized by it. If the
answer is "report the outage and survive until someone attends, ~24 h", the whole problem
is already solved by the firmware and the cell question closes. If it is "7 days of live
tracking", that is a different product and needs a different pack.
### D1 query sketch
```sql
SELECT cell_id, date_trunc('hour', ts) AS h,
avg(tbat) AS tbat_avg, max(tbat) AS tbat_max,
sum((tbat > 42)::int)::float / count(*) AS frac_above_42
FROM phlg WHERE ts > now() - interval '30 days' AND tbat > -100
GROUP BY 1,2 ORDER BY 1,2;
```
Then: per unit, the longest run of consecutive hours with `tbat_max < 40`.
**Success bar is not "always cool" — it is "≥ 15 min below 40 °C per day"** (see the
duty-cycle brief §3). That is a much easier bar and may already be met.
---
## Phase P — bench. The measurement that decides everything. 2–3 days.
### Rig
One v5.5 board, bench PSU at **3.7 V into `P4`** (pack position), current measured
**at `BAT+`** — that is what the pack actually delivers, and it captures the LTC4040
boost's light-load efficiency, which is one of our real unknowns. Measure the 5 V rail in
parallel so you get both numbers and can derive the boost efficiency.
⚠ **A bench DMM will not do this.** You need µA-to-A dynamic range on one trace: a
**Nordic PPK2** (~US$110) or Joulescope/Otii. Buy it — it pays for itself in this test
alone, and every low-power decision after this depends on trusting the number.
### P1 — the subtraction ladder 🔴
Measure, then remove one load, measure again. The **deltas are the deliverable** — they
tell you which fixes are worth writing and which are noise.
| Step | State | Expect |
|---|---|---|
| a | Full `RUNNING`, stack up, fan off | ~2.5 W baseline |
| b | `PWR_CTRL` low (RTK stack off) | |
| c | `BYP_CTRL` low (`K1` released) | **−150 mW** — confirms the K1 reversal |
| d | `U12` disabled + `W5500` held in reset | **−400 mW?** |
| e | All LEDs off (incl. gauge `PB12–15`) | −50…150 mW |
| f | `MAX3232` / `SP3485` / `SC16IS752` in shutdown | −5…15 mW |
| g | MCU in **STOP + LSE RTC** (not `loop()`) | −150 mW |
| h | `U17` lifted, `/3V3` fed from bench supply through the meter | **isolates the AMS1117's own Iq — the number I most expect to be ugly** |
**Gate G1: is the achievable floor under 50 mW?**
Yes → proceed to firmware. No → find out what is holding it up *before* writing any.
If step (h) is 25–55 mW as expected, the LDO swap is a one-part change worth doing first.
### P2 — modem energy and the burst
1. Real Telcel SIM. If D2 said yes: `AT+CPSMS=1,,,"<TAU>","<Active>"`. Measure **modem
sleep current in PSM**, and **energy per wake→TX→sleep cycle** in joules.
2. If D2 said no: measure **energy per full attach cycle** the same way.
3. 🔴 **The burst test.** With a real pack at **low SoC** and the pack at **45 °C**,
trigger an LTE attach and watch `MAIN5V` on a scope. Does `PFI` (4.60 V) trip?
This is the most likely way the design fails in the field and it is cheap to check now.
**Gate G2:** PSM or power-cycle, and the interval locked. Feed the measured joules back
into the runtime table.
---
## Phase F — firmware. 3–4 days, once G1 and G2 pass.
- `BACKUP_SLEEP` as a real state behind a compile flag (`ENABLE_BACKUP_DUTYCYCLE`), per the
state machine in the duty-cycle brief §8. Today's `SLEEP` stays as the brownout failsafe.
- Exercise it first in the **`hostsim` harness** already in the repo — state transitions,
adaptive cadence, `BAT_CRITICAL` final message — before it touches silicon.
- **F1 acceptance, on the bench:** real pack, real SIM, **72 h continuous** with the meter
logging. Deliverable = **measured Wh/day** and extrapolated runtime.
- **Gate G3: measured Wh/day within 30 % of the model.** If it is not, the model is wrong
and everything downstream needs re-deciding — do not paper over it.
---
## Phase S — server-side synthesis. Your idea, and it is a good one. 1–2 days.
Holding the position server-side decouples what the customer sees from what the radio does.
The device sends a heartbeat every 30–60 min; `retranslator.py` keeps feeding Wialon at
1 Hz from the last known fix. That is the right split.
**But three rules, and they are not optional:**
1. **Mark it.** Synthesized points carry an explicit source flag and render differently in
the UI. We are generating data that did not come from a sensor; anyone reading the track
must be able to see that. Do not let a synthesized point be indistinguishable from a real one.
2. 🔴 **Hard stop on a missed heartbeat.** The moment a heartbeat is late by more than one
interval, **stop synthesizing** and switch the unit to *"last seen at X, T ago —
unknown"*. Synthesizing past the heartbeat window turns a dead unit into a healthy-looking
one on the operator's screen. That is strictly worse than reporting nothing, and it is
exactly the failure this feature would be blamed for.
3. **Movement breaks the illusion instantly.** The `U26` accelerometer wake exists for this:
if the machine moves during an outage, the unit wakes and sends a real fix, and the server
drops out of synthesis on the spot. Without motion-wake, a towed machine keeps reporting
from the yard — with a port customer, that is a liability, not a bug.
**S1 test:** on the bench, cut vehicle power → verify Wialon shows a continuous 1 Hz track
flagged as battery-mode → cut the unit entirely → **verify the track stops and the unit goes
"unknown" within one heartbeat interval**, not later. Also check whether ICAVE's contract or
Wialon billing counts records — synthesized ones may not be acceptable to count.
---
## Phase C — the cell decision, *last*
Only now, with **measured Wh/day** (F1) and the **real temperature histogram** (D1):
- **Daily energy < ~2 Wh and D1 shows ≥ 15 min/day below 40 °C** → **keep the current cell.**
No LFP, no `RNTB` change, no new case. Consider *shrinking* the pack instead.
- **Otherwise** → high-temp LFP + `RNTB` → 5.6 k + B3380 NTC + `JP2` re-strap, per the
battery note. Order sample cells at D1 time so they are on the shelf if needed — that is
the only long-lead item, and sampling two cells costs nothing.
---
## Phase V — field pilot. 30 days.
Two units at ICAVE on the new firmware, rest of the fleet as control. Track: actual outage
events, hours survived, `vbat` trajectory, charge-window utilisation, and any operator
complaint about the synthesized track. **Ship to the fleet only after 30 clean days.**
---
## What this is *not*
Not a v6 respin. Nothing above requires new hardware except a possible one-part LDO swap
and, if Phase C says so, a cell and one resistor. The 30-unit build is not blocked by any
of it — but **D3 should be answered before that build ships**, because it may change what
pack goes in the box.

View File

@ -0,0 +1,72 @@
---
tags: [project-drogheda, comms, vendor, telecom]
project: Project Drogheda
author: Helgen CTO
date: 2026-09-10
status: Draft para envío — cubre la tarea D2 del procedimiento de evaluación
---
# Correo a Vectro — PSM / eDRX en las líneas Telcel
> Cubre **D2** de `2026-09-10_Drogheda_Backup-Power_Evaluation-Procedure_v1.0.md`.
> Las preguntas 1–4 están redactadas para reenviarse **tal cual** a Telcel; las 5–7 sólo
> las puede responder Vectro.
---
**Asunto:** Consulta técnica — modos de ahorro de energía PSM / eDRX en las líneas Telcel
Estimado [Nombre]:
Requerimos confirmar si las líneas Telcel que nos suministran soportan **PSM (Power Saving
Mode)** y **eDRX (extended DRX)**, definidos en **3GPP Release 13**. La respuesta determina
el diseño de nuestro modo de respaldo por batería, por lo que agradeceremos su apoyo para
escalar la consulta con Telcel si no está a su alcance responderla directamente.
**Contexto:** operamos equipos de telemetría GPS/RTK en terminales portuarias. Ante un corte
de energía, el equipo debe seguir reportando posición y estado durante varios días con
batería. PSM permite que el módem duerma a ~10 µA **manteniendo el registro en la red**, en
lugar de volver a registrarse en cada envío. En nuestros cálculos la diferencia en autonomía
es de **días contra semanas**, con el mismo hardware.
## Preguntas técnicas (pueden reenviarse tal cual a Telcel)
1. ¿Está habilitado **PSM** en el APN y el perfil de SIM que tenemos contratado, para
dispositivos **LTE Cat-1bis**?
2. En caso afirmativo, ¿qué valores otorga la red para el **T3412 extendido (TAU)** y el
**T3324 (Active Timer)**? Nuestra solicitud sería **TAU de 30 a 60 minutos** y
**Active Timer de 0 a 60 segundos**.
3. ¿Está habilitado **eDRX**? ¿Cuál es el **ciclo máximo** que otorga la red?
4. Si PSM o eDRX no están disponibles en nuestro APN actual, ¿existe un **APN de IoT/M2M**
donde sí lo estén, y cuál sería el procedimiento y el costo de migrar nuestras líneas?
## Preguntas comerciales y operativas (para Vectro)
5. ¿Existe alguna política de **suspensión o baja por inactividad**? Un equipo en PSM puede
permanecer **varios días sin generar tráfico**, y necesitamos confirmar que la línea no se
suspenda ni se dé de baja por ese motivo.
6. ¿El plan factura por **sesión / attach**, o únicamente por **volumen de datos**?
7. ¿Podemos habilitar **una línea de prueba** con PSM activo, para validarlo en banco antes
de aplicarlo a la flota?
Agradeceremos su respuesta **a más tardar el viernes 19 de septiembre**, ya que esta
definición bloquea la siguiente etapa de nuestro desarrollo.
Quedo atento a sus comentarios.
Saludos cordiales,
**Leo Rodríguez**
Helgen Technologies
---
## Notas para Leo (no enviar)
- Ajustar **[Nombre]** y la fecha del compromiso.
- La **pregunta 5 es la más importante para Vectro** y la que un revendedor suele pasar por
alto: una política de inactividad que suspenda la SIM anularía todo el beneficio de PSM.
- Si Vectro responde "no sé", el objetivo mínimo del correo es la **pregunta 7** — con una
sola línea de prueba podemos medir en banco lo que la red realmente otorga
(`AT+CPSMS?` y `AT+CEDRXRDP` devuelven los valores concedidos, que casi nunca son los
solicitados) y dejar de depender de la respuesta comercial.

@ -1 +1 @@
Subproject commit c7d25524a7f6ec2a579be615140f45c2ac064bc3
Subproject commit 94917b1993b6003efb653483a305794130d395b3

View File

@ -0,0 +1,227 @@
#!/usr/bin/env python3
"""v6 T12 - SIM block: MFF2 footprint repoint (part 1 of 2).
WHY THIS SCRIPT EXISTS
----------------------
U22 (MFF2 soldered eSIM, Block Q) shipped from T4 with a placeholder footprint field:
the literal string "Drogheda:MFF2_5x6_TBD", which resolved to nothing - Drogheda.pretty
held only the E-J1 socket. U22 was also absent from the board.
On 2026-09-15 the real land pattern was built as Drogheda.pretty/MFF2_6x5.kicad_mod
(geometry derived from ETSI TS 102 671 V19.0.0 section 6, cross-checked against the
ST ST4SIM-200M VFDFPN8 outline) and the three property fields were edited BY HAND.
Hand edits live outside the T-script replay chain, so a future v5 schematic change
would silently discard them. This script puts them back in the chain.
It is IDEMPOTENT: on an already-patched tree it reports 0 changes and exits 0.
NAMING: the old field said "5x6"; ETSI dimensions the package as E=6.00 x D=5.00,
so the footprint is MFF2_6x5.
PART 2 (pending, will be appended to this same script)
------------------------------------------------------
The nano-SIM holder alternate + eight 0R selects downstream of the existing ESD
(D23 SIM_DATA / D24 SIM_CLK / D25 SIM_RST) + DNP flags, so MFF2 and a card holder
can share the bus with exactly one populated. Blocked on the holder's land pattern.
LESSONS APPLIED (see drogheda-v6-resync-protocol)
-------------------------------------------------
- no bare numeric regex; every substitution is a complete quoted token or is scoped
to one paren-matched s-expression block
- hard invariant guard: aborts if ANY pre-existing UUID would change
- .T12bak written before any file is touched; dry-run is the default
Usage: python3 2026-09-15_v6_T12_sim_dual_footprint.py [--apply]
"""
import os
import re
import shutil
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
KI = os.path.join(HERE, '..', '03-Architecture-Hardware', 'kicad')
SYM = os.path.join(KI, 'Drogheda-sym.kicad_sym')
SCH = os.path.join(KI, 'hbox-watchdog-v6.kicad_sch')
FPF = os.path.join(KI, 'Drogheda.pretty', 'MFF2_6x5.kicad_mod')
APPLY = '--apply' in sys.argv
UUID_RE = re.compile(r'\(uuid "([^"]*)"\)')
OLD_FP = '"Drogheda:MFF2_5x6_TBD"'
NEW_FP = '"Drogheda:MFF2_6x5"'
OLD_DESC = (
'MFF2 / DFN-8 5x6 mm soldered M2M SIM (ETSI TS 102 671). Pads: 1=C1 VCC, 2=C2 RST, '
'3=C3 CLK, 4=C4 NC, 5=C5 GND, 6=C6 VPP (NC), 7=C7 I/O, 8=C8 NC. FOOTPRINT TBD - build '
'from the chosen carrier chip datasheet before layout. Carrier-supplied part, not on LCSC.'
)
NEW_DESC = (
'MFF2 / VFDFPN-8 6.0x5.0 mm soldered M2M eSIM (ETSI TS 102 671). Pads: 1=C1 VCC, 2=C2 RST, '
'3=C3 CLK, 4=C4 NC, 5=C5 GND, 6=C6 VPP (NC), 7=C7 I/O, 8=C8 NC - JEDEC DFN counterclockwise, '
'pin 1 top-left, matches ST ST4SIM-200M. Footprint Drogheda:MFF2_6x5 (2026-09-15). '
'Carrier-supplied part: NO LCSC number, exclude from the JLCPCB PCBA BOM, hand-solder or '
'consign. Verify pinout + die-pad exposure against the sourced carrier drawing before fab.'
)
U22_DESC = 'MFF2 eSIM (carrier-supplied, NO LCSC - do not order from JLCPCB)'
# pad number -> (x, y); size is 0.9 x 0.5 for all eight
EXPECTED_PADS = {
'1': (-2.85, -1.905), '2': (-2.85, -0.635), '3': (-2.85, 0.635), '4': (-2.85, 1.905),
'5': (2.85, 1.905), '6': (2.85, 0.635), '7': (2.85, -0.635), '8': (2.85, -1.905),
}
def die(msg):
sys.exit('REFUSING: ' + msg)
def balanced_end(s, j):
"""End index (exclusive) of the s-expression opening at j. String-aware."""
depth = 0
k = j
in_str = False
while k < len(s):
c = s[k]
if in_str:
if c == '\\':
k += 2
continue
if c == '"':
in_str = False
else:
if c == '"':
in_str = True
elif c == '(':
depth += 1
elif c == ')':
depth -= 1
if depth == 0:
return k + 1
k += 1
die('unbalanced s-expression starting at offset %d' % j)
def paren_ok(s, label):
depth = 0
k = 0
in_str = False
while k < len(s):
c = s[k]
if in_str:
if c == '\\':
k += 2
continue
if c == '"':
in_str = False
else:
if c == '"':
in_str = True
elif c == '(':
depth += 1
elif c == ')':
depth -= 1
if depth < 0:
die('%s: unbalanced ")" at offset %d' % (label, k))
k += 1
if depth != 0:
die('%s: %d unclosed "(" at EOF' % (label, depth))
if in_str:
die('%s: unterminated string at EOF' % label)
# ---------------------------------------------------------------- gate 0: footprint
print('== gate 0: the footprint file must exist and match the documented geometry ==')
if not os.path.isfile(FPF):
die('%s is missing - build the land pattern before running T12' % FPF)
fp_src = open(FPF, encoding='utf-8').read()
paren_ok(fp_src, 'MFF2_6x5.kicad_mod')
pads = dict(
(m.group(1), (float(m.group(2)), float(m.group(3))))
for m in re.finditer(r'\(pad "(\d)" smd rect \(at (-?[\d.]+) (-?[\d.]+)\) \(size 0\.9 0\.5\)', fp_src)
)
if pads != EXPECTED_PADS:
die('MFF2_6x5.kicad_mod pad geometry does not match the T12 reference.\n'
' found: %s\n expected: %s' % (sorted(pads.items()), sorted(EXPECTED_PADS.items())))
print(' OK 8 pads, 0.9 x 0.5, columns at X +/-2.85, rows at Y +/-1.905 / +/-0.635')
# ---------------------------------------------------------------- the edits
edits = 0
reports = []
for path in (SYM, SCH):
name = os.path.basename(path)
if not os.path.isfile(path):
die('%s is missing' % path)
src = open(path, encoding='utf-8').read()
paren_ok(src, name)
new = src
local = []
# (a) footprint field - complete quoted token only
n = new.count(OLD_FP)
if n:
new = new.replace(OLD_FP, NEW_FP)
local.append('%d x footprint field -> Drogheda:MFF2_6x5' % n)
# (b) library description (SYM: the symbol; SCH: the lib_symbols cache copy)
n = new.count(OLD_DESC)
if n:
new = new.replace(OLD_DESC, NEW_DESC)
local.append('%d x symbol description de-TBD-ed' % n)
# (c) U22 instance Description - scoped to U22's own paren-matched block
if name.endswith('.kicad_sch'):
targets = []
for m in re.finditer(r'\(symbol(?=[\s(])', new):
j = m.start()
e = balanced_end(new, j)
blk = new[j:e]
if '"Drogheda-sym:MFF2_SIM"' in blk and '(property "Reference" "U22"' in blk:
targets.append((j, e))
if len(targets) != 1:
die('expected exactly 1 U22 MFF2_SIM instance in %s, found %d' % (name, len(targets)))
j, e = targets[0]
blk = new[j:e]
if '(property "Description" ""' in blk:
blk2 = blk.replace('(property "Description" ""',
'(property "Description" "%s"' % U22_DESC, 1)
new = new[:j] + blk2 + new[e:]
local.append('1 x U22 instance Description set')
elif U22_DESC not in blk:
die('U22 Description is neither empty nor the T12 value - inspect by hand')
# sanity: U22 must carry no LCSC number (carrier-supplied part)
blk_now = new[j:balanced_end(new, j)]
mm = re.search(r'\(property "LCSC" "([^"]*)"', blk_now)
if mm and mm.group(1).strip():
die('U22 carries LCSC "%s" - it is a carrier-supplied part and must have none '
'(C730237 belongs to U23/U24, the 74LVC2T45s)' % mm.group(1))
if new == src:
reports.append('%-28s already at T12 state, nothing to do' % name)
continue
# invariants
if UUID_RE.findall(new) != UUID_RE.findall(src):
die('%s: a UUID would change' % name)
paren_ok(new, name + ' (post-edit)')
if OLD_FP in new or 'MFF2_5x6_TBD' in new:
die('%s: a TBD footprint reference survived the edit' % name)
reports.append('%-28s %s' % (name, '; '.join(local)))
edits += len(local)
if APPLY:
shutil.copy2(path, path + '.T12bak')
open(path, 'w', encoding='utf-8').write(new)
print()
print('== edits ==')
for r in reports:
print(' ' + r)
print()
print('total change groups: %d' % edits)
if APPLY:
print('APPLIED (.T12bak written beside each modified file)')
else:
print('DRY RUN - nothing written. Re-run with --apply.')