← Commit history
control-panel/transport.py+6
@@ -20,6 +20,8 @@ class SerialLoad:         self.owner = secrets.token_hex(12)      def request(self, cmd, **fields):+        if self.seq >= 0xFFFFFFFF:+            raise LinkError("Sequence exhausted; a fresh device boot is required")         self.seq += 1         packet = dict(id=self.seq, cmd=cmd, **fields)         if self.boot is not None:@@ -52,6 +54,10 @@ class SerialLoad:             raise LinkError("Wrong instrument identity or unsupported protocol")         if not isinstance(d.get("boot"), str) or not 8 <= len(d["boot"]) <= 32:             raise LinkError("Missing boot identity")+        floor = d.get("last_sequence")+        if type(floor) is not int or not 0 <= floor <= 0xFFFFFFFF - 4:+            raise LinkError("Missing or exhausted device sequence floor")+        self.seq = max(self.seq, floor)         self.boot = d["boot"]         self.request("claim")         self.request("off")
demos/protocol/README.mdadded+12
@@ -0,0 +1,12 @@+# Device protocol host simulation++This demo sends fixed JSON requests to the actual compiled portable C endpoint. It never opens a serial port or accesses hardware. Synthetic telemetry is fixed; assembly qualification is deliberately false. The browser displays returned fields, with presentation delays between requests.++From the project root:++```sh+gcc -std=c11 -Wall -Wextra -Werror firmware/load_core.c firmware/load_protocol.c tests/protocol_peer.c -lm -o /tmp/load-protocol-peer+python3 demos/protocol/server.py --peer /tmp/load-protocol-peer+```++Open localhost port 45956 and run the test. It demonstrates identify/claim/OFF, a setpoint that cannot enable by itself, refused ARM, rejected replay, and explicit fault clearing that returns to OFF. This is not a USB enumeration or physical shutdown demonstration. Full automated checks: `bash tools/check_firmware.sh`.
demos/protocol/index.htmladded+11
@@ -0,0 +1,11 @@+<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Adom Load · C protocol test</title>+<style>:root{color-scheme:dark;font:18px system-ui;background:#101723;color:#e9f2fa}body{max-width:1160px;margin:35px auto;padding:25px}.badge{display:inline-block;border:1px solid #ad803e;padding:8px 14px;color:#ffcf81;border-radius:6px}h1{font-size:38px;margin-bottom:10px}p,small{color:#aabed0}.cards{display:grid;grid-template-columns:repeat(3,1fr);gap:20px;margin:28px 0}.card,pre{background:#192639;border:1px solid #344760;border-radius:10px;padding:20px}.value{font-size:36px;font-weight:650;color:#74e6c9;margin-top:8px}.wire{display:grid;grid-template-columns:1fr 1fr;gap:20px}pre{font:16px ui-monospace,monospace;white-space:pre-wrap;min-height:230px;margin:12px 0}button{font:inherit;background:#74e6c9;border:0;border-radius:6px;padding:12px 25px;color:#11282c;cursor:pointer}#note{min-height:62px;font-size:23px}footer{font-size:14px;color:#aabed0;line-height:1.5;margin-top:20px}.bad{color:#ff9d8a}.controls{display:flex;gap:20px;align-items:center}</style>+<div class="badge">HOST SIMULATION · COMPILED C · NO HARDWARE CONNECTED</div><h1>Commands must earn permission to enable.</h1><p>Live requests to the portable device protocol. Synthetic telemetry; assembly qualification stays false.</p>+<div class="cards"><div class="card"><small>Controller state</small><div class="value" id="state">OFF</div></div><div class="card"><small>Enable output command</small><div class="value" id="enabled">LOW</div></div><div class="card"><small>DAC command</small><div class="value" id="dac">0</div></div></div>+<div id="note">Ready: identify, claim, setpoint, interlock refusal, replay refusal, explicit fault clear.</div>+<div class="wire"><div><small>Request to C firmware logic</small><pre id="request">Waiting for a request.</pre></div><div><small>Actual C response · selected fields</small><pre id="response">Waiting for a response.</pre></div></div>+<div class="controls"><button id="run">Run protocol test</button><span id="progress">0 / 8</span></div><footer>Runs the actual load_protocol.c and load_core.c in a local process. The interface displays their responses; it does not operate USB, GPIO, a DAC or a physical load. Hardware callbacks, MCU timing and USB enumeration remain unverified. Delays between requests are for presentation only.</footer>+<script>+const $=id=>document.getElementById(id),notes=['Identity received. No enable request.','Control session bound to this boot. Output stays OFF.','Explicit OFF confirmed: DAC zero and enable LOW.','0.4 A requested. A setpoint alone cannot enable the load.','ARM refused: this assembly is not qualified. Fault latched.','Repeated sequence rejected. No replay and no automatic restart.','Fresh heartbeat received. Fault still latched.','Explicit fault clear returns to OFF. It does not arm.'];+$('run').onclick=async()=>{const button=$('run');button.disabled=true;try{await fetch('/reset',{method:'POST'});for(let i=0;i<8;i++){const r=await fetch('/step',{method:'POST'});if(!r.ok)throw Error(await r.text());const d=await r.json(),s=d.response;$('request').textContent=JSON.stringify({...d.request,owner:d.request.owner?'[test owner token]':undefined},null,2);$('response').textContent=JSON.stringify({id:s.id,ok:s.ok,error:s.error||undefined,state:s.state,enabled:s.enabled,dac_code:s.dac_code,requested_a:s.requested_a},null,2);$('state').textContent=s.state;$('state').className='value '+(s.state==='FAULT'?'bad':'');$('enabled').textContent=s.enabled?'HIGH':'LOW';$('dac').textContent=s.dac_code;$('note').textContent=notes[i];$('progress').textContent=(i+1)+' / 8';await new Promise(resolve=>setTimeout(resolve,1400));}}catch(e){$('note').textContent='Test failed: '+e.message;}finally{button.disabled=false;button.textContent='Run again';}};+</script></html>
demos/protocol/server.pyadded+44
@@ -0,0 +1,44 @@+"""Local HOST SIMULATION. Fixed requests to the compiled C protocol; no hardware."""+import json+from http.server import HTTPServer, BaseHTTPRequestHandler+from pathlib import Path+import subprocess+import selectors+import argparse+root = Path(__file__).parent+owner = '0123456789abcdef01234567'+class Demo:+    def __init__(self, binary): self.binary, self.peer = binary, None; self.reset()+    def reset(self):+        if self.peer:+            self.peer.stdin.close(); self.peer.wait(timeout=3); self.peer.stdout.close()+        self.peer = subprocess.Popen([self.binary], stdin=subprocess.PIPE, stdout=subprocess.PIPE)+        self.index = 0+    def step(self):+        packets = [dict(id=1,cmd='identify'), dict(id=2,cmd='claim'), dict(id=3,cmd='off'), dict(id=4,cmd='set_current',amps=.4), dict(id=5,cmd='arm'), dict(id=5,cmd='arm'), dict(id=6,cmd='heartbeat'), dict(id=7,cmd='clear_fault')]+        if self.index == len(packets): return {'done':True}+        q = packets[self.index]; self.index += 1+        if q['cmd'] != 'identify': q.update(boot='simboot01',owner=owner)+        self.peer.stdin.write(json.dumps(q,separators=(',',':')).encode()+b'\n'); self.peer.stdin.flush()+        with selectors.DefaultSelector() as selector:+            selector.register(self.peer.stdout,selectors.EVENT_READ)+            if not selector.select(3): raise TimeoutError('C peer did not reply')+        response = json.loads(self.peer.stdout.readline(2048))+        return {'request':q,'response':response,'step':self.index,'total':len(packets),'simulation':True}+class Handler(BaseHTTPRequestHandler):+    def log_message(self,*args): pass+    def do_GET(self):+        if self.path not in ('/','/index.html'): self.send_error(404); return+        data=(root/'index.html').read_bytes();self.send_response(200);self.send_header('Content-Type','text/html');self.send_header('Content-Length',str(len(data)));self.end_headers();self.wfile.write(data)+    def do_POST(self):+        try:+            if self.path=='/reset':demo.reset();result={'ok':True}+            elif self.path=='/step':result=demo.step()+            else:self.send_error(404);return+            data=json.dumps(result).encode();self.send_response(200);self.send_header('Content-Type','application/json');self.send_header('Content-Length',str(len(data)));self.end_headers();self.wfile.write(data)+        except Exception as exc:+            self.send_error(500,str(exc))+if __name__=='__main__':+    parser=argparse.ArgumentParser();parser.add_argument('--peer',required=True);parser.add_argument('--port',type=int,default=45956);args=parser.parse_args();demo=Demo(args.peer)+    print('Host C protocol simulation on 127.0.0.1:'+str(args.port),flush=True)+    HTTPServer(('127.0.0.1',args.port),Handler).serve_forever()
design/firmware-protocol-check.logadded+10
@@ -0,0 +1,10 @@+load_core: safety policy tests passed (host C build; no MCU/HAL validation)+fan_tach: qualification, RPM, stale input, noise, slow fan and timer wrap passed+load_protocol: host safety/framing tests passed; no MCU or USB validation+.................+----------------------------------------------------------------------+Ran 17 tests in 0.042s++OK+OK: external command completed; its running time is in this session+Hint: Use exec for build, browser automation and other work outside AI Flow. Do not start an idle heartbeat; only actual work counts.
design/mcu-pinmap.jsonadded+140
@@ -0,0 +1,140 @@+{+  "part": "STM32G0B1KCT6",+  "package": "LQFP32 GP, not the N-suffix variant",+  "source": "https://www.st.com/resource/en/datasheet/stm32g0b1kc.pdf",+  "sourceRevision": "DS13560 Rev 6",+  "matchedSignalPins": 20,+  "matchedSupplyPins": 2,+  "pins": [+    {+      "packagePin": 6,+      "gpio": "PF2",+      "net": "NRST",+      "plannedFunction": "reset"+    },+    {+      "packagePin": 7,+      "gpio": "PA0",+      "net": "TEMP_SENSE",+      "plannedFunction": "ADC_IN0"+    },+    {+      "packagePin": 8,+      "gpio": "PA1",+      "net": "DUT_ADC",+      "plannedFunction": "ADC_IN1"+    },+    {+      "packagePin": 9,+      "gpio": "PA2",+      "net": "UART_TX",+      "plannedFunction": "USART2_TX AF1"+    },+    {+      "packagePin": 10,+      "gpio": "PA3",+      "net": "UART_RX",+      "plannedFunction": "USART2_RX AF1"+    },+    {+      "packagePin": 11,+      "gpio": "PA4",+      "net": "ARM_CLK",+      "plannedFunction": "GPIO output, initialize LOW"+    },+    {+      "packagePin": 12,+      "gpio": "PA5",+      "net": "MCU_ENABLE",+      "plannedFunction": "GPIO output, initialize LOW"+    },+    {+      "packagePin": 13,+      "gpio": "PA6",+      "net": "WDI",+      "plannedFunction": "GPIO output, healthy-cycle toggle only"+    },+    {+      "packagePin": 14,+      "gpio": "PA7",+      "net": "FAN_TACH",+      "plannedFunction": "GPIO EXTI7 rising; timestamp from 32-bit timer"+    },+    {+      "packagePin": 15,+      "gpio": "PB0",+      "net": "USB_DETECT",+      "plannedFunction": "GPIO input"+    },+    {+      "packagePin": 17,+      "gpio": "PB2",+      "net": "HW_OK",+      "plannedFunction": "GPIO input"+    },+    {+      "packagePin": 20,+      "gpio": "PC6",+      "net": "ALERT",+      "plannedFunction": "GPIO input"+    },+    {+      "packagePin": 22,+      "gpio": "PA11",+      "net": "USB_DM",+      "plannedFunction": "USB_DM, default pin remap"+    },+    {+      "packagePin": 23,+      "gpio": "PA12",+      "net": "USB_DP",+      "plannedFunction": "USB_DP, default pin remap"+    },+    {+      "packagePin": 24,+      "gpio": "PA13",+      "net": "SWDIO",+      "plannedFunction": "SWDIO AF0"+    },+    {+      "packagePin": 25,+      "gpio": "PA14",+      "net": "SWCLK",+      "plannedFunction": "SWCLK AF0"+    },+    {+      "packagePin": 26,+      "gpio": "PA15",+      "net": "LATCH_Q",+      "plannedFunction": "GPIO input"+    },+    {+      "packagePin": 27,+      "gpio": "PB3",+      "net": "FAULT_CLEAR",+      "plannedFunction": "GPIO output; polarity requires latch-chain audit"+    },+    {+      "packagePin": 30,+      "gpio": "PB6",+      "net": "I2C_SCL",+      "plannedFunction": "I2C1_SCL AF6 open-drain"+    },+    {+      "packagePin": 31,+      "gpio": "PB7",+      "net": "I2C_SDA",+      "plannedFunction": "I2C1_SDA AF6 open-drain"+    }+  ],+  "limits": [+    "Static assignment audit only; HAL and option-byte validation pending",+    "USB clock recovery, VBUS detection and self-powered descriptors pending",+    "Tach EXTI timestamps require a 32-bit microsecond timebase and measured ISR latency",+    "Boot identity must be unique on every reset; no fixed production boot nonce"+  ],+  "inputSha256": {+    "design/netlist.json": "de7b88e327356a7d269f0a043e2bfc56a6760d00868564b0740bea3f79dc3bfd",+    "kicad/adom-custom-electronic-load.kicad_pcb": "b2a4d8321f24af5d3f95f9df6fab570b96abb298b4cc70b3f932037d2fec9f8d"+  }+}
design/thermal-r13-summary.jsonadded+12
@@ -0,0 +1,12 @@+{+  "boardSha256": "b2a4d8321f24af5d3f95f9df6fab570b96abb298b4cc70b3f932037d2fec9f8d",+  "snapshot": "aiflow/run/board-14.kicad_pcb",+  "candidateDrc": "checkpoints/thermal-r13-diode/native-drc.json",+  "liveReceipts": "design/thermal-r13-live",+  "physicalCopperParityRoundingMm": 1e-05,+  "fields": "checkpoints/thermal-r13-fields/fields.json",+  "fieldsScope": "Experimental corrected two-layer/PTH solver, electrical diagnostic; external case heatsink omitted",+  "fieldsSourcePr": "https://wiki.adom.inc/adom/adom-fields/prs/5",+  "thermalQualified": false,+  "fabricationReleased": false+}\ No newline at end of file
docs/completion-plan.md+3−3
@@ -4,13 +4,13 @@ The active objective is to finish this electronic-load design through AIFlow, th  ## Latest accepted board -AIFlow aiflow/run/board-13.kicad_pcb, native-saved SHA-256 `74350d625de85228c9ee876f83748d8ed08d021b026cd5ebbec9d69e059fccff`. Cooling-clearance relocation and unused-branch removal are landed. Candidate native DRC: zero errors/opens, fifteen library warnings; saved physical copper matches. Placement/routing proof refresh is in progress. See `design/cooling-clearance-accepted.json`.+AIFlow aiflow/run/board-14.kicad_pcb, native-saved SHA-256 `b2a4d8321f24af5d3f95f9df6fab570b96abb298b4cc70b3f932037d2fec9f8d`. Cooling-clearance changes and five verified vias are landed. Native candidate DRC: zero errors/opens, fifteen library lookup warnings; saved physical copper matches. Current step: thermal-vias, with unacknowledged shortfalls. Three 3 A current-neck flags and the external case-cooling model remain unresolved. Portable device protocol and host interoperability checks pass.  ## Resume here -1. Inspect the live AdomLapper board and AIFlow run; current required step is placement (return visit for cooling clearance). Preserve other threads and project rules.+1. Inspect the live AdomLapper board and AIFlow run; current required step is thermal-vias (visit 3). Preserve other threads and project rules. 2. Continue the compact 5 W prototype basis announced on 9 October. It follows the original size/cost priority and 5 V/1 A DUT. The prior 25 W assistant assumption is documented as a future option, not a blocker. Keep the 55 C trip and all validation requirements.-3. Solve the actual MOSFET case/interface/sink/fan assembly and probe clearance. Rejected shunt-via candidates disconnected ground; do not replay them. Via-count acknowledgements require actual engineering justification, not a desire to advance the timeline.+3. Solve the actual MOSFET case/interface/sink/fan assembly and probe clearance. The shunt-via candidate now passes only with the verified GND stitch at (133.018,77.818). Earlier candidates without that stitch disconnected ground; do not replay them. Via-count acknowledgements require actual engineering justification, not a desire to advance the timeline. 4. Complete and revalidate pours, outline, same-board current/thermal Fields screens, net tour, full fitted-model/silkscreen review, native 3D, finish and fabrication exports in the enforced sequence. Record significant real actions. Never mark these passed to bypass an unresolved gate. 5. Complete firmware/HAL/USB integration and control-panel behavior, full component/provenance review, molecule assets, cost/BOM coverage and wiki delivery. Label hardware-dependent calibration, airflow, fan-stall and thermal qualification explicitly unverified until tested. 6. Edit and review the 4–5 minute narrated film from actual clips. Label simulations and historical reviews; exclude static/rejected takes. No YouTube upload is implied by preparation alone.
docs/firmware-contract.md+14
@@ -25,3 +25,17 @@ After identify verifies protocol and exact serial number, the host binds a boot Fan telemetry includes mandatory `rpm_valid`. Qualified RPM is finite and in range; unqualified feedback uses `rpm:null,rpm_valid:false`, never a fabricated zero. The UI labels it unknown and refuses arm; the device core independently requires 5000–10000 RPM. The host CSV preserves RPM and validity.  The core now faults on measured power above 1.10 times the configured 5 W ceiling, independently of its setpoint clamp. The 10% measurement allowance is provisional and requires calibration/transient validation. Invalid or unqualified higher power ceilings are refused. An arm request without hardware-latch acknowledgement faults after 100 ms, even if host heartbeats continue. Host tests cover both paths and clock wrap; no physical shutdown latency is claimed.++## Device protocol implementation — 2026-10-09++`firmware/load_protocol.c` now implements a fixed-size, allocation-free device endpoint for the existing host transport. It checks a boot identity, a 24-hex-character owner token and increasing 32-bit request IDs. Requests are bounded to 256 bytes including newline. The strict flat-JSON subset rejects duplicate/unknown fields, escaped strings, nested values, nonfinite numbers and invalid number syntax. Identity strings are restricted to alphanumeric, underscore and hyphen.++Invalid framing, overflow, stale sequence, identity/owner errors and disconnect call the mandatory synchronous hardware-OFF callback and latch a link fault. A complete stale OFF request also calls OFF before rejecting its authorization. A partial frame expires after 100 ms; ownership expires after 1 s without a heartbeat. The adapter must call `load_protocol_poll` on time even when no bytes arrive, deassert the physical enable before waiting on I2C/USB, and bound transport work per control cycle. These are integration requirements, not measured shutdown guarantees.++Only a trusted build/measurement configuration supplies assembly qualification. No protocol command can set it. ARM remains a request to the core; the hardware latch handshake must still be implemented. The current parser supports one selected transport. Do not create independently owning USB and UART instances; simultaneous transport support needs shared arbitration and separate bounded input buffers.++Run `bash tools/check_firmware.sh`. Host checks cover core policy, tach feedback, the device parser, 100,000 deterministic fuzz bytes under ASan/UBSan, and the real Python host transport communicating with the compiled C endpoint. Seventeen Python tests pass. The test peer is explicitly simulated and cannot touch hardware. MCU/HAL, flashable image, USB descriptors/enumeration, production boot identity and physical validation remain unfinished.++`design/mcu-pinmap.json` records the static package/net/peripheral audit. Twenty signal and two supply assignments match the selected STM32 package, design netlist and PCB. It does not validate register configuration or board-level signal integrity.++Request sequence numbers remain monotonic across all owners during one device boot. `identify` returns `last_sequence`; the host advances beyond it before `claim`. Expiry or disconnect never resets the sequence floor. A fresh unique boot identity is mandatory after MCU reset, and sequence exhaustion requires a new boot. Tests cover delayed claims after expiry and host reconnection against the same C endpoint.
docs/status.md+8
@@ -1,3 +1,11 @@+# Thermal-via and protocol checkpoint — 2026-10-09++AIFlow adopted aiflow/run/board-14.kicad_pcb (native SHA-256 `b2a4d8321f24af5d3f95f9df6fab570b96abb298b4cc70b3f932037d2fec9f8d`). Five vias were added after isolated native DRC: two SHUNT_P thermal vias, two LOAD_POS diode vias and one GND stitch that prevents an isolated ground region. Saved native copper matches the checked candidate; its native DRC has zero errors/opens and fifteen existing library lookup warnings. The thermal-via heuristic still reports D1 6/10, Q1 0/16 and RSH1 2/6; no shortfall was acknowledged.++The experimental two-layer/PTH-corrected Fields binary converged on all six loaded nets and both board-only thermal solves. Three 3 A current-neck flags remain. The board-only model omits the required case-mounted heatsink, so its high temperatures are a failed screen, not a prediction of the intended cooled assembly or a pass. The shared Fields PR remains open.++The portable device protocol now interoperates with the Python control-panel transport. Tests cover boot-wide sequence replay protection, owner/lease rules, bounded parsing, fault/OFF callbacks, and fuzz input. All C host checks and 17 Python tests pass. MCU/HAL/USB deployment and physical timing remain unfinished. The video inventory contains 51 raw takes including rejected/static takes; a new clearly labeled host-simulation clip shows the real compiled C protocol refusing ARM and replay.+ # Cooling-clearance revision — 2026-10-09  The native board now has relocated current/temperature probes and fan header, with unused copper branches removed. The checked candidate has zero native DRC errors or opens and fifteen existing library lookup warnings; saved native copper matches it. AIFlow adopted aiflow/run/board-13.kicad_pcb. Native 2D and 3D views were inspected. The dashboard shows the current saved review.
docs/thermal-budget.md+6
@@ -21,3 +21,9 @@ The [ATS-CPX050050010-121-C1-R0 manufacturer sheet](https://www.qats.com/DataShe The [ATS-X51450D-C1-R0 sheet](https://www.qats.com/DataSheet/ATS-X51450D-C1-R0) gives a 9.5 mm high BGA sink with 45 mm component dimensions and a 61.48 mm dimension in its assembly drawing. Its superGRIP attachment is intended for a BGA package, not the load MOSFET. Do not treat the nominal 45 mm designation as the complete assembly envelope or use the BGA attachment unmodified.  A shorter sink can improve the height budget, but neither candidate resolves the mechanical mounting, fan operating point, hot-spot sensing or continuous-power qualification by itself.++## Interface source review — 2026-10-09++The [IXTP15N50L2 manufacturer sheet](https://www.littelfuse.com/~/media/electronics/datasheets/discrete_mosfets/littelfuse_discrete_mosfets_n-channel_linear_ixt_15n50_datasheet.pdf.pdf) lists junction-to-case resistance 0.42 K/W and TO-220 case-to-sink 0.50 K/W. The latter does not establish the resistance of an added electrical isolator. The drain tab must remain isolated from a touchable sink. A selected isolator, both contact interfaces, spreading and mounting force need their own budget.++The [Boyd accessory catalog, page 101](https://info.boydcorp.com/hubfs/Thermal/Air-Cooling/Boyd-Board-Level-Heatsinks-Catalog.pdf) provides TO-220 alumina washer drawings, but its listed 1.57–1.78 mm thicknesses do not match the nominal 0.2 mm interface clearance study. These are research alternatives, not selected BOM items. Do not adopt distributor conductivity figures at elevated temperature as room-temperature design data.
firmware/load_core.c+1−1
@@ -1,6 +1,6 @@ /* Portable policy core. Hardware adapter and USB stack are not yet implemented.  * No production enable: assembly_qualified must come from verified configuration,- * never a serial command. Default 5W bring-up ceiling, target 25W after qualification.+ * never a serial command. Default 5 W compact R0 ceiling; higher power requires a new qualified assembly.  */ #include "load_core.h" #include <math.h>
firmware/load_protocol.cadded+141
@@ -0,0 +1,141 @@+/* Fixed-size, allocation-free device protocol. Strict flat JSON deliberately+ * rejects escapes, nesting, duplicate/unknown keys and nonfinite numbers.+ * The existing host emits this subset. Hardware/USB adapter is still required. */+#include "load_protocol.h"+#include <ctype.h>+#include <math.h>+#include <stdio.h>+#include <stdlib.h>+#include <string.h>+#include <stdint.h>+static void stop(load_protocol *p,bool fault){+ load_off(p->core);+ if(fault){p->core->faults|=F_LINK;p->core->state=LOAD_FAULT;}+ if(p->hard_off)p->hard_off(p->context);+}+static bool identity(const char *s,size_t minimum,size_t maximum){+ if(!s)return false;+ size_t n=strlen(s);if(n<minimum||n>maximum)return false;+ for(size_t i=0;i<n;i++)if(!isalnum((unsigned char)s[i])&&s[i]!='_'&&s[i]!='-')return false;+ return true;+}+bool load_protocol_init(load_protocol *p,load_core *c,const char *serial,const char *boot,load_hard_off off,void *ctx){+ if(!p||!c)return false;+ memset(p,0,sizeof(*p));p->core=c;p->hard_off=off;p->context=ctx;stop(p,false);+ if(!off||!identity(serial,1,32)||!identity(boot,8,32))return false;+ strcpy(p->serial,serial);strcpy(p->boot,boot);p->ready=true;return true;+}+void load_protocol_disconnect(load_protocol *p){+ if(!p||!p->core)return;+ stop(p,true);p->owned=false;p->used=0;p->dropping=false;memset(p->owner,0,sizeof(p->owner));+}+void load_protocol_poll(load_protocol *p,const load_sample *s){+ if(!p||!p->ready||!s)return;+ if(p->owned&&(uint32_t)(s->now_ms-p->lease_ms)>1000){stop(p,true);p->owned=false;}+ if(p->used&&(uint32_t)(s->now_ms-p->frame_started_ms)>100){stop(p,true);p->used=0;p->dropping=true;}+}+typedef struct {uint32_t id;char cmd[20],boot[33],owner[25];float amps;unsigned seen;} request;+static void ws(const char **s){while(**s==' '||**s=='\t'||**s=='\r')++*s;}+static bool string(const char **s,char *out,size_t size){+ if(*(*s)++!='"')return false;+ size_t n=0;+ while(**s&&**s!='"'){+  unsigned char c=(unsigned char)*(*s)++;+  if(c<32||c>=127||c=='\\'||n+1>=size)return false;+ out[n++]=(char)c;+ }+ if(**s!='"')return false;+ ++*s;out[n]=0;return true;+}+/* JSON number grammar, excluding leading zeroes and nonfinite extensions. */+static bool number(const char **s,char out[40]){+ const char *start=*s;if(**s=='-')++*s;+ if(**s=='0')++*s;else{if(**s<'1'||**s>'9')return false;+ while(isdigit((unsigned char)**s))++*s;}+ if(**s=='.'){++*s;if(!isdigit((unsigned char)**s))return false;+ while(isdigit((unsigned char)**s))++*s;}+ if(**s=='e'||**s=='E'){++*s;if(**s=='+'||**s=='-')++*s;if(!isdigit((unsigned char)**s))return false;+ while(isdigit((unsigned char)**s))++*s;}+ size_t n=(size_t)(*s-start);if(n>=40)return false;+ memcpy(out,start,n);out[n]=0;return true;+}+static bool parse(const char *s,request *r){+ memset(r,0,sizeof(*r));ws(&s);if(*s++!='{')return false;+ ws(&s);+ if(*s=='}')return false;+ for(;;){+  char key[16],val[40];if(!string(&s,key,sizeof(key)))return false;+ ws(&s);if(*s++!=':')return false;+ ws(&s);+  unsigned bit=!strcmp(key,"id")?1:!strcmp(key,"cmd")?2:!strcmp(key,"boot")?4:!strcmp(key,"owner")?8:!strcmp(key,"amps")?16:0;+  if(!bit||(r->seen&bit))return false;+ r->seen|=bit;+  if(bit==1){+   if(!number(&s,val))return false;+ uint64_t id=0;+   for(size_t i=0;val[i];i++){if(!isdigit((unsigned char)val[i]))return false;+ id=id*10+(unsigned)(val[i]-'0');if(id>UINT32_MAX)return false;}+   if(!id)return false;+ r->id=(uint32_t)id;+  }else if(bit==16){if(!number(&s,val))return false;+ r->amps=strtof(val,NULL);if(!isfinite(r->amps))return false;}+  else if(bit==2){if(!string(&s,r->cmd,sizeof(r->cmd)))return false;}+  else if(bit==4){if(!string(&s,r->boot,sizeof(r->boot)))return false;}+  else if(!string(&s,r->owner,sizeof(r->owner)))return false;+  ws(&s);if(*s=='}'){s++;ws(&s);return !*s&&(r->seen&3)==3;}+  if(*s++!=',')return false;+ ws(&s);+ }+}+static bool owner_valid(const char *s){+ if(strlen(s)!=24)return false;+ for(unsigned i=0;i<24;i++)if(!isxdigit((unsigned char)s[i]))return false;+ return true;+}+static const char *finite_text(float x,char out[32]){+ if(!isfinite(x))return "null";+ snprintf(out,32,"%.7g",(double)x);return out;+}+static size_t reply(load_protocol *p,const request *r,const load_sample *s,const char *error,char *out,size_t cap){+ if(cap<LOAD_REPLY_BYTES){stop(p,true);return 0;}+ const char *states[]={"OFF","ARMED","RUNNING","FAULT"};char v[32],a[32],t[32],rpm[32];+ int n=snprintf(out,cap,"{\"id\":%lu,\"ok\":%s,\"error\":\"%s\",\"protocol\":\"adom.load/1\",\"serial\":\"%s\",\"boot\":\"%s\",\"state\":\"%s\",\"enabled\":%s,\"dac_code\":%u,\"faults\":%lu,\"last_sequence\":%lu,\"volts\":%s,\"amps\":%s,\"case_c\":%s,\"rpm\":%s,\"rpm_valid\":%s,\"requested_a\":%.7g,\"applied_a\":%.7g,\"power_limit_w\":%.7g,\"sample_age_ms\":%lu}\n",+ (unsigned long)r->id,error?"false":"true",error?error:"",p->serial,p->boot,states[p->core->state],p->core->enable?"true":"false",p->core->dac_code,(unsigned long)p->core->faults,(unsigned long)p->sequence,+ finite_text(s->volts,v),finite_text(s->amps,a),finite_text(s->case_c,t),s->fan_rpm_valid&&isfinite(s->fan_rpm)?finite_text(s->fan_rpm,rpm):"null",s->fan_rpm_valid&&isfinite(s->fan_rpm)?"true":"false",(double)p->core->requested_a,(double)p->core->applied_a,(double)p->core->power_limit_w,(unsigned long)(s->now_ms-s->sample_ms));+ if(n<0||(size_t)n>=cap){stop(p,true);return 0;}return (size_t)n;+}+static const char *dispatch(load_protocol *p,const request *r,const load_sample *s){+ bool off=!strcmp(r->cmd,"off");if(off)stop(p,false); /* OFF is fail-safe even if authorization is stale. */+ if(!strcmp(r->cmd,"identify"))return r->seen==3?NULL:"unexpected_field";+ if((r->seen&15)!=15||strcmp(r->boot,p->boot)||!owner_valid(r->owner))return "identity_or_owner";+ if(!strcmp(r->cmd,"claim")){+  if(r->seen!=15)return "unexpected_field";+  if(p->owned&&strcmp(r->owner,p->owner))return "owner_busy";+  if(r->id<=p->sequence)return "stale_sequence";+  stop(p,false);strcpy(p->owner,r->owner);p->owned=true;p->sequence=r->id;p->lease_ms=s->now_ms;load_heartbeat(p->core,s->now_ms);return NULL;+ }+ if(!p->owned||strcmp(p->owner,r->owner))return "owner_required";+ if(r->id<=p->sequence)return "stale_sequence";+ p->sequence=r->id;+ bool set=!strcmp(r->cmd,"set_current");if(r->seen!=(set?31u:15u))return "unexpected_field";+ if(off||!strcmp(r->cmd,"status"))return NULL;+ if(!strcmp(r->cmd,"heartbeat")){p->lease_ms=s->now_ms;load_heartbeat(p->core,s->now_ms);return NULL;}+ if(set)return load_set_current(p->core,r->amps)?NULL:"current_range";+ if(!strcmp(r->cmd,"arm"))return load_arm(p->core,s)?NULL:"arm_interlock";+ if(!strcmp(r->cmd,"clear_fault"))return load_clear_fault(p->core,s)?NULL:"fault_interlock";+ return "unknown_command";+}+size_t load_protocol_byte(load_protocol *p,unsigned char c,const load_sample *s,char *out,size_t cap){+ if(!p||!p->ready||!s||!out)return 0;+ load_protocol_poll(p,s);+ if(c=='\n'){+  request r={0};const char *error=NULL;+  if(p->dropping){error="invalid_frame";p->dropping=false;}+  else{p->frame[p->used]=0;if(!parse(p->frame,&r))error="invalid_json";else error=dispatch(p,&r,s);}+  p->used=0;if(error)stop(p,true);return reply(p,&r,s,error,out,cap);+ }+ if(p->dropping)return 0;+ if(c==0||c<9||(c>13&&c<32)||c>=127||p->used>=LOAD_REQUEST_BYTES-1){stop(p,true);p->used=0;p->dropping=true;return 0;}+ if(!p->used)p->frame_started_ms=s->now_ms;+ p->frame[p->used++]=(char)c;return 0;+}
firmware/load_protocol.hadded+24
@@ -0,0 +1,24 @@+#ifndef ADOM_LOAD_PROTOCOL_H+#define ADOM_LOAD_PROTOCOL_H+#include "load_core.h"+#include <stddef.h>+#define LOAD_REQUEST_BYTES 256+#define LOAD_REPLY_BYTES 1024+/* Callbacks must synchronously deassert MCU_ENABLE. DAC zero follows; they must+ * not wait for USB output. MCU/HAL integration and physical timing are unverified. */+typedef void (*load_hard_off)(void *context);+typedef struct {+ load_core *core; load_hard_off hard_off; void *context;+ char serial[33], boot[33], owner[25], frame[LOAD_REQUEST_BYTES];+ uint32_t sequence, lease_ms, frame_started_ms;+ size_t used; bool owned, dropping, ready;+} load_protocol;+bool load_protocol_init(load_protocol *,load_core *,const char *serial,const char *boot,+                        load_hard_off,void *context);+/* Feed bytes in arrival order. Newline completes one request. No command queue.+ * Returns response byte count, zero if incomplete; output buffer >= LOAD_REPLY_BYTES.+ * HAL must bound ingress work per control cycle and call poll even without input. */+size_t load_protocol_byte(load_protocol *,unsigned char,const load_sample *,char *,size_t);+void load_protocol_poll(load_protocol *,const load_sample *);+void load_protocol_disconnect(load_protocol *);+#endif
kicad/adom-custom-electronic-load.kicad_pcb+318−253
@@ -23221,6 +23221,14 @@ 		(net "GND")
 		(uuid "d4c23f80-c7c9-4f8a-b29c-63910f889dde")
 	)
+	(via
+		(at 133.018 77.818)
+		(size 0.6)
+		(drill 0.3)
+		(layers "F.Cu" "B.Cu")
+		(net "GND")
+		(uuid "d8c75c07-9254-4331-9c84-3ecbbb2ca27b")
+	)
 	(via
 		(at 147.9 91.7)
 		(size 0.8)
@@ -27917,6 +27925,14 @@ 		(net "SHUNT_P")
 		(uuid "34d3207a-b14a-40d8-be82-5b108c2b867b")
 	)
+	(via
+		(at 135.4317 86.566983)
+		(size 0.6)
+		(drill 0.3)
+		(layers "F.Cu" "B.Cu")
+		(net "SHUNT_P")
+		(uuid "68aadbec-f17f-48c1-8535-55715f47673c")
+	)
 	(via
 		(at 133.8 84.2)
 		(size 0.8)
@@ -27925,6 +27941,14 @@ 		(net "SHUNT_P")
 		(uuid "aa6c5964-74d4-410e-a69e-ffb2cd9c9999")
 	)
+	(via
+		(at 134.980977 87.344023)
+		(size 0.6)
+		(drill 0.3)
+		(layers "F.Cu" "B.Cu")
+		(net "SHUNT_P")
+		(uuid "c9ea56b9-a5fe-4008-a8e9-e709fd82352b")
+	)
 	(segment
 		(start 124.9 82.3)
 		(end 124.92 82)
@@ -36741,6 +36765,14 @@ 		(net "LOAD_POS")
 		(uuid "13444d64-9a40-4686-bbee-589453fd7ad6")
 	)
+	(via
+		(at 113.219 89.936)
+		(size 0.6)
+		(drill 0.3)
+		(layers "F.Cu" "B.Cu")
+		(net "LOAD_POS")
+		(uuid "1ed60b06-3263-4f79-be31-e1b5bc94910c")
+	)
 	(via
 		(at 112.4 93)
 		(size 0.8)
@@ -36757,6 +36789,14 @@ 		(net "LOAD_POS")
 		(uuid "b0bfb6f7-46c3-4ab7-8f17-6db7570c9a70")
 	)
+	(via
+		(at 112.473 90.756)
+		(size 0.6)
+		(drill 0.3)
+		(layers "F.Cu" "B.Cu")
+		(net "LOAD_POS")
+		(uuid "ee1b61ee-3af4-434d-b855-d48e26aa8d50")
+	)
 	(via
 		(at 110 93)
 		(size 0.8)
@@ -37069,91 +37109,92 @@ 			(layer "F.Cu")
 			(pts
 				(xy 153.008426 86.555521) (xy 153.03367 86.562934) (xy 153.086416 86.59423) (xy 153.092686 86.6005)
-				(xy 153.174939 86.647989) (xy 153.17494 86.64799) (xy 153.205928 86.665881) (xy 153.20681 86.66639)
-				(xy 153.206811 86.66639) (xy 153.206814 86.666392) (xy 153.334108 86.7005) (xy 153.33411 86.7005)
-				(xy 153.465889 86.7005) (xy 153.46589 86.7005) (xy 153.465892 86.7005) (xy 153.576191 86.670945)
-				(xy 153.611227 86.666756) (xy 153.625104 86.667086) (xy 153.691654 86.688359) (xy 153.736141 86.742237)
-				(xy 153.736141 86.742238) (xy 153.739488 86.750054) (xy 153.7495 86.798867) (xy 153.7495 88.144203)
-				(xy 153.744476 88.17914) (xy 153.737064 88.204381) (xy 153.705769 88.257125) (xy 152.232609 89.730284)
-				(xy 152.204353 89.751436) (xy 152.190632 89.758928) (xy 152.122363 89.773779) (xy 152.120559 89.77365)
-				(xy 152.062372 89.754287) (xy 152.055862 89.750104) (xy 152.035215 89.733467) (xy 152.003224 89.701475)
-				(xy 152.003221 89.701473) (xy 152.00322 89.701472) (xy 151.883126 89.640281) (xy 151.883124 89.64028)
-				(xy 151.883121 89.640279) (xy 151.883119 89.640278) (xy 151.88312 89.640278) (xy 151.783497 89.6245)
-				(xy 151.783493 89.6245) (xy 151.783488 89.6245) (xy 151.216512 89.6245) (xy 151.216507 89.6245)
-				(xy 151.216501 89.6245) (xy 151.116882 89.640277) (xy 151.116878 89.640278) (xy 150.996786 89.701468)
-				(xy 150.996771 89.701477) (xy 150.901477 89.796771) (xy 150.901468 89.796786) (xy 150.840278 89.916878)
-				(xy 150.840277 89.916881) (xy 150.8245 90.016501) (xy 150.8245 90.269204) (xy 150.819476 90.304141)
-				(xy 150.812064 90.329382) (xy 150.780769 90.382126) (xy 149.372551 91.790343) (xy 149.346879 91.810044)
-				(xy 149.277267 91.850242) (xy 149.273167 91.853033) (xy 149.192686 91.899499) (xy 149.192677 91.899506)
-				(xy 149.099506 91.992677) (xy 149.099502 91.992682) (xy 149.099493 91.992696) (xy 149.03361 92.106806)
-				(xy 149.033609 92.106808) (xy 149.020384 92.156166) (xy 148.9995 92.234108) (xy 148.9995 92.365891)
-				(xy 149.033608 92.493187) (xy 149.048786 92.519475) (xy 149.0995 92.607314) (xy 149.099502 92.607316)
-				(xy 149.167397 92.675211) (xy 149.188546 92.703461) (xy 149.193967 92.713388) (xy 149.208821 92.781661)
-				(xy 149.184407 92.847126) (xy 149.128475 92.888999) (xy 149.128471 92.889001) (xy 149.121283 92.891682)
-				(xy 149.077949 92.8995) (xy 148.445726 92.8995) (xy 148.442959 92.899759) (xy 148.407726 92.898018)
-				(xy 148.383826 92.893369) (xy 148.334288 92.871727) (xy 148.333677 92.87128) (xy 148.319211 92.858884)
-				(xy 146.480535 91.020208) (xy 146.459384 90.991954) (xy 146.447099 90.969456) (xy 146.431997 90.905989)
-				(xy 146.442359 90.588269) (xy 146.448518 90.553522) (xy 146.452964 90.540026) (xy 146.45341 90.538672)
-				(xy 146.493066 90.481172) (xy 146.495309 90.479352) (xy 146.549059 90.454072) (xy 146.550827 90.453717)
-				(xy 146.554824 90.452917) (xy 146.579187 90.4505) (xy 146.683262 90.4505) (xy 146.687803 90.449838)
-				(xy 146.705971 90.447191) (xy 146.751393 90.440573) (xy 146.856483 90.389198) (xy 146.939198 90.306483)
-				(xy 146.990573 90.201393) (xy 147.0005 90.13326) (xy 147.0005 89.76674) (xy 147.000031 89.763524)
-				(xy 146.990573 89.698607) (xy 146.980015 89.677011) (xy 146.940871 89.59694) (xy 146.939433 89.593752)
-				(xy 146.939196 89.593514) (xy 146.856485 89.510803) (xy 146.856484 89.510802) (xy 146.827898 89.496828)
-				(xy 146.827898 89.496827) (xy 146.827896 89.496827) (xy 146.751929 89.459689) (xy 146.751406 89.459431)
-				(xy 146.751391 89.459426) (xy 146.683261 89.4495) (xy 146.68326 89.4495) (xy 146.683256 89.4495)
-				(xy 145.591745 89.4495) (xy 145.59174 89.4495) (xy 145.591739 89.4495) (xy 145.53354 89.457979)
-				(xy 145.523607 89.459426) (xy 145.418511 89.510804) (xy 145.335804 89.593511) (xy 145.284426 89.698607)
-				(xy 145.284008 89.701474) (xy 145.2745 89.766739) (xy 145.2745 90.13326) (xy 145.28036 90.173483)
-				(xy 145.284426 90.201391) (xy 145.335803 90.306485) (xy 145.419622 90.390304) (xy 145.421957 90.390879)
-				(xy 145.523607 90.440573) (xy 145.542277 90.443293) (xy 145.557438 90.445502) (xy 145.55744 90.445502)
-				(xy 145.557673 90.445536) (xy 145.591739 90.4505) (xy 145.59174 90.4505) (xy 145.649567 90.4505)
-				(xy 145.684503 90.455523) (xy 145.699501 90.459927) (xy 145.758209 90.497622) (xy 145.758689 90.498175)
-				(xy 145.786624 90.555074) (xy 145.788507 90.564463) (xy 145.788767 90.568846) (xy 145.790862 90.592886)
-				(xy 145.787971 90.681576) (xy 145.787971 90.681578) (xy 145.783055 90.832298) (xy 145.776896 90.86705)
-				(xy 145.773358 90.877791) (xy 145.733688 90.935307) (xy 145.66922 90.962245) (xy 145.629842 90.955266)
-				(xy 145.620091 90.955887) (xy 145.613892 90.952439) (xy 145.600422 90.950052) (xy 145.592234 90.945985)
-				(xy 145.559714 90.922611) (xy 145.376616 90.739513) (xy 145.376614 90.739511) (xy 145.376611 90.739509)
-				(xy 145.376607 90.739506) (xy 145.304594 90.697929) (xy 145.304591 90.697928) (xy 145.273889 90.680201)
-				(xy 145.261777 90.676956) (xy 145.261777 90.676955) (xy 145.249675 90.673713) (xy 145.249675 90.673712)
-				(xy 145.24967 90.673712) (xy 145.221706 90.666218) (xy 145.211475 90.663477) (xy 145.211475 90.663476)
-				(xy 145.159314 90.6495) (xy 145.159309 90.6495) (xy 145.159308 90.6495) (xy 144.755795 90.6495)
-				(xy 144.720859 90.644477) (xy 144.695617 90.637065) (xy 144.642872 90.605769) (xy 144.60418 90.567077)
-				(xy 144.583028 90.538821) (xy 144.575536 90.5251) (xy 144.560684 90.45684) (xy 144.560813 90.455031)
-				(xy 144.580179 90.396834) (xy 144.584367 90.390318) (xy 144.600993 90.369686) (xy 144.664198 90.306483)
-				(xy 144.715573 90.201393) (xy 144.7255 90.13326) (xy 144.7255 89.76674) (xy 144.725031 89.763524)
-				(xy 144.715573 89.698607) (xy 144.705015 89.677011) (xy 144.669314 89.603983) (xy 144.659908 89.577478)
-				(xy 144.653689 89.550597) (xy 144.6505 89.522653) (xy 144.6505 88.477345) (xy 144.653689 88.449403)
-				(xy 144.659909 88.422517) (xy 144.669316 88.396013) (xy 144.715573 88.301393) (xy 144.7255 88.23326)
-				(xy 144.7255 87.86674) (xy 144.718971 87.821932) (xy 144.715573 87.798607) (xy 144.689356 87.744979)
-				(xy 144.678525 87.711389) (xy 144.678457 87.710991) (xy 144.676189 87.697705) (xy 144.684309 87.628311)
-				(xy 144.728659 87.574322) (xy 144.728666 87.574317) (xy 144.729766 87.573569) (xy 144.731873 87.572134)
-				(xy 144.782406 87.552154) (xy 144.7834 87.551997) (xy 144.802615 87.5505) (xy 145.189244 87.5505)
-				(xy 145.224176 87.555521) (xy 145.237495 87.559432) (xy 145.296273 87.597206) (xy 145.325298 87.660757)
-				(xy 145.32585 87.664595) (xy 145.321597 87.718819) (xy 145.321301 87.719778) (xy 145.314218 87.737667)
-				(xy 145.284426 87.798607) (xy 145.28244 87.812234) (xy 145.2745 87.866739) (xy 145.2745 88.23326)
-				(xy 145.281017 88.277989) (xy 145.284426 88.301391) (xy 145.284432 88.301411) (xy 145.284646 88.301843)
-				(xy 145.317199 88.368431) (xy 145.334748 88.404328) (xy 145.335803 88.406485) (xy 145.418514 88.489196)
-				(xy 145.418515 88.489196) (xy 145.418753 88.489434) (xy 145.421952 88.490877) (xy 145.523607 88.540573)
-				(xy 145.542277 88.543293) (xy 145.557438 88.545502) (xy 145.55744 88.545502) (xy 145.557673 88.545536)
-				(xy 145.591739 88.5505) (xy 145.59174 88.5505) (xy 145.591741 88.5505) (xy 146.683261 88.5505) (xy 146.683262 88.5505)
-				(xy 146.687803 88.549838) (xy 146.705971 88.547191) (xy 146.751393 88.540573) (xy 146.856483 88.489198)
-				(xy 146.891781 88.453899) (xy 146.920025 88.432754) (xy 146.942502 88.420481) (xy 147.00616 88.405387)
-				(xy 147.393916 88.418643) (xy 147.428656 88.424858) (xy 147.454384 88.433378) (xy 147.503083 88.46341)
-				(xy 147.61632 88.576647) (xy 147.725573 88.685899) (xy 147.746723 88.714151) (xy 147.759331 88.73724)
-				(xy 147.7745 88.796669) (xy 147.7745 90.097873) (xy 147.774499 90.09845) (xy 147.774497 90.09888)
-				(xy 147.774494 90.099328) (xy 147.774155 90.14024) (xy 147.774216 90.140696) (xy 147.774278 90.141176)
-				(xy 147.774334 90.141613) (xy 147.774424 90.142328) (xy 147.774478 90.142773) (xy 147.78507 90.182302)
-				(xy 147.785298 90.183167) (xy 147.785368 90.183436) (xy 147.7854 90.183559) (xy 147.795674 90.223206)
-				(xy 147.795676 90.223212) (xy 147.795804 90.223521) (xy 147.795994 90.223981) (xy 147.796162 90.224392)
-				(xy 147.796342 90.224831) (xy 147.796636 90.225559) (xy 147.796637 90.225561) (xy 147.796641 90.225569)
-				(xy 147.817055 90.260927) (xy 147.817287 90.26133) (xy 147.817548 90.261786) (xy 147.817825 90.262273)
-				(xy 147.833695 90.290294) (xy 147.837932 90.297773) (xy 147.837933 90.297775) (xy 147.838216 90.298144)
-				(xy 147.8384 90.298385) (xy 147.838831 90.29895) (xy 147.839202 90.299438) (xy 147.839479 90.299806)
-				(xy 147.839481 90.299808) (xy 147.868366 90.328693) (xy 147.868729 90.329057) (xy 147.868969 90.329299)
-				(xy 147.869328 90.329662) (xy 148.131402 90.596104) (xy 148.152316 90.624528) (xy 148.164818 90.647881)
-				(xy 148.1795 90.706407) (xy 148.1795 90.884318) (xy 148.18593 90.933171) (xy 148.185974 90.933313)
-				(xy 148.186644 90.9347) (xy 148.235935 91.040404) (xy 148.319596 91.124065) (xy 148.426827 91.174068)
+				(xy 153.174939 86.647989) (xy 153.17494 86.64799) (xy 153.196583 86.660486) (xy 153.196584 86.660486)
+				(xy 153.206814 86.666392) (xy 153.334108 86.7005) (xy 153.33411 86.7005) (xy 153.465889 86.7005)
+				(xy 153.46589 86.7005) (xy 153.465892 86.7005) (xy 153.576191 86.670945) (xy 153.611227 86.666756)
+				(xy 153.625104 86.667086) (xy 153.691654 86.688359) (xy 153.736141 86.742237) (xy 153.736141 86.742238)
+				(xy 153.739488 86.750054) (xy 153.7495 86.798867) (xy 153.7495 88.144203) (xy 153.744476 88.17914)
+				(xy 153.737064 88.204381) (xy 153.705769 88.257125) (xy 152.232609 89.730284) (xy 152.204353 89.751436)
+				(xy 152.190632 89.758928) (xy 152.122363 89.773779) (xy 152.120559 89.77365) (xy 152.062372 89.754287)
+				(xy 152.055862 89.750104) (xy 152.035215 89.733467) (xy 152.003224 89.701475) (xy 152.003221 89.701473)
+				(xy 152.00322 89.701472) (xy 151.883126 89.640281) (xy 151.883124 89.64028) (xy 151.883121 89.640279)
+				(xy 151.883119 89.640278) (xy 151.88312 89.640278) (xy 151.783497 89.6245) (xy 151.783493 89.6245)
+				(xy 151.783488 89.6245) (xy 151.216512 89.6245) (xy 151.216507 89.6245) (xy 151.216501 89.6245)
+				(xy 151.116882 89.640277) (xy 151.116878 89.640278) (xy 150.996786 89.701468) (xy 150.996771 89.701477)
+				(xy 150.90148 89.796768) (xy 150.901475 89.796774) (xy 150.901457 89.796803) (xy 150.901256 89.797203)
+				(xy 150.864112 89.870105) (xy 150.86411 89.870108) (xy 150.840278 89.916878) (xy 150.840277 89.916881)
+				(xy 150.8245 90.0165) (xy 150.8245 90.269204) (xy 150.819476 90.304141) (xy 150.812064 90.329382)
+				(xy 150.780769 90.382126) (xy 149.372551 91.790343) (xy 149.346879 91.810044) (xy 149.277267 91.850242)
+				(xy 149.273167 91.853033) (xy 149.192686 91.899499) (xy 149.192677 91.899506) (xy 149.099506 91.992677)
+				(xy 149.099502 91.992682) (xy 149.099493 91.992696) (xy 149.03361 92.106806) (xy 149.033609 92.106808)
+				(xy 149.020384 92.156166) (xy 148.9995 92.234108) (xy 148.9995 92.365891) (xy 149.033608 92.493187)
+				(xy 149.048786 92.519475) (xy 149.0995 92.607314) (xy 149.099502 92.607316) (xy 149.167397 92.675211)
+				(xy 149.188546 92.703461) (xy 149.193967 92.713388) (xy 149.208821 92.781661) (xy 149.184407 92.847126)
+				(xy 149.128475 92.888999) (xy 149.128471 92.889001) (xy 149.121283 92.891682) (xy 149.077949 92.8995)
+				(xy 148.445726 92.8995) (xy 148.442959 92.899759) (xy 148.407726 92.898018) (xy 148.383826 92.893369)
+				(xy 148.334288 92.871727) (xy 148.333677 92.87128) (xy 148.319211 92.858884) (xy 146.480535 91.020208)
+				(xy 146.459384 90.991954) (xy 146.447099 90.969456) (xy 146.431997 90.905989) (xy 146.442359 90.588269)
+				(xy 146.448518 90.553522) (xy 146.452965 90.540023) (xy 146.45341 90.538672) (xy 146.493066 90.481172)
+				(xy 146.495309 90.479352) (xy 146.549059 90.454072) (xy 146.550827 90.453717) (xy 146.554824 90.452917)
+				(xy 146.579187 90.4505) (xy 146.683262 90.4505) (xy 146.696351 90.448592) (xy 146.722532 90.444778)
+				(xy 146.751393 90.440573) (xy 146.856483 90.389198) (xy 146.939198 90.306483) (xy 146.990573 90.201393)
+				(xy 147.0005 90.13326) (xy 147.0005 89.76674) (xy 147.000031 89.763524) (xy 146.990573 89.698607)
+				(xy 146.980015 89.677011) (xy 146.940871 89.59694) (xy 146.939433 89.593752) (xy 146.939196 89.593514)
+				(xy 146.856485 89.510803) (xy 146.856484 89.510802) (xy 146.827898 89.496828) (xy 146.827898 89.496827)
+				(xy 146.827896 89.496827) (xy 146.751929 89.459689) (xy 146.751406 89.459431) (xy 146.751391 89.459426)
+				(xy 146.683261 89.4495) (xy 146.68326 89.4495) (xy 146.683256 89.4495) (xy 145.591745 89.4495) (xy 145.59174 89.4495)
+				(xy 145.591739 89.4495) (xy 145.53354 89.457979) (xy 145.523607 89.459426) (xy 145.418511 89.510804)
+				(xy 145.335804 89.593511) (xy 145.284426 89.698607) (xy 145.284008 89.701474) (xy 145.2745 89.766739)
+				(xy 145.2745 90.13326) (xy 145.284426 90.201391) (xy 145.335803 90.306485) (xy 145.418514 90.389196)
+				(xy 145.418515 90.389196) (xy 145.419622 90.390303) (xy 145.421956 90.390879) (xy 145.523607 90.440573)
+				(xy 145.552466 90.444777) (xy 145.552467 90.444778) (xy 145.591738 90.4505) (xy 145.591739 90.4505)
+				(xy 145.59174 90.4505) (xy 145.649567 90.4505) (xy 145.684503 90.455523) (xy 145.699501 90.459927)
+				(xy 145.758209 90.497622) (xy 145.758689 90.498175) (xy 145.786624 90.555074) (xy 145.788507 90.564463)
+				(xy 145.788767 90.568846) (xy 145.790862 90.592886) (xy 145.787971 90.681576) (xy 145.787971 90.681578)
+				(xy 145.783055 90.832298) (xy 145.776896 90.86705) (xy 145.773358 90.877791) (xy 145.733688 90.935307)
+				(xy 145.66922 90.962245) (xy 145.629842 90.955266) (xy 145.620091 90.955887) (xy 145.613892 90.952439)
+				(xy 145.600422 90.950052) (xy 145.592234 90.945985) (xy 145.559714 90.922611) (xy 145.376616 90.739513)
+				(xy 145.376614 90.739511) (xy 145.376611 90.739509) (xy 145.376607 90.739506) (xy 145.304594 90.697929)
+				(xy 145.304591 90.697928) (xy 145.273889 90.680201) (xy 145.261777 90.676956) (xy 145.261777 90.676955)
+				(xy 145.249675 90.673713) (xy 145.249675 90.673712) (xy 145.24967 90.673712) (xy 145.221706 90.666218)
+				(xy 145.211475 90.663477) (xy 145.211475 90.663476) (xy 145.159314 90.6495) (xy 145.159309 90.6495)
+				(xy 145.159308 90.6495) (xy 144.755795 90.6495) (xy 144.720859 90.644477) (xy 144.695617 90.637065)
+				(xy 144.642872 90.605769) (xy 144.60418 90.567077) (xy 144.583028 90.538821) (xy 144.575536 90.5251)
+				(xy 144.560684 90.45684) (xy 144.560813 90.455031) (xy 144.580179 90.396834) (xy 144.584367 90.390318)
+				(xy 144.600993 90.369686) (xy 144.664198 90.306483) (xy 144.715573 90.201393) (xy 144.7255 90.13326)
+				(xy 144.7255 89.76674) (xy 144.725031 89.763524) (xy 144.715573 89.698607) (xy 144.705015 89.677011)
+				(xy 144.669314 89.603983) (xy 144.659908 89.577478) (xy 144.653689 89.550597) (xy 144.6505 89.522653)
+				(xy 144.6505 88.477345) (xy 144.653689 88.449403) (xy 144.659909 88.422517) (xy 144.669316 88.396013)
+				(xy 144.715573 88.301393) (xy 144.7255 88.23326) (xy 144.7255 87.86674) (xy 144.718971 87.821932)
+				(xy 144.715573 87.798607) (xy 144.689356 87.744979) (xy 144.678525 87.711389) (xy 144.678457 87.710991)
+				(xy 144.676189 87.697705) (xy 144.684309 87.628311) (xy 144.728659 87.574322) (xy 144.728666 87.574317)
+				(xy 144.729766 87.573569) (xy 144.731873 87.572134) (xy 144.782406 87.552154) (xy 144.7834 87.551997)
+				(xy 144.802615 87.5505) (xy 145.189244 87.5505) (xy 145.224176 87.555521) (xy 145.237495 87.559432)
+				(xy 145.296273 87.597206) (xy 145.325298 87.660757) (xy 145.32585 87.664595) (xy 145.321597 87.718819)
+				(xy 145.321301 87.719778) (xy 145.314218 87.737667) (xy 145.284426 87.798607) (xy 145.28244 87.812234)
+				(xy 145.2745 87.866739) (xy 145.2745 88.23326) (xy 145.281017 88.277989) (xy 145.284426 88.301391)
+				(xy 145.284432 88.301411) (xy 145.284646 88.301843) (xy 145.317199 88.368431) (xy 145.334748 88.404328)
+				(xy 145.335803 88.406485) (xy 145.418514 88.489196) (xy 145.418515 88.489196) (xy 145.418753 88.489434)
+				(xy 145.421952 88.490877) (xy 145.523607 88.540573) (xy 145.542277 88.543293) (xy 145.557438 88.545502)
+				(xy 145.55744 88.545502) (xy 145.557673 88.545536) (xy 145.591739 88.5505) (xy 145.59174 88.5505)
+				(xy 145.591741 88.5505) (xy 146.683261 88.5505) (xy 146.683262 88.5505) (xy 146.687803 88.549838)
+				(xy 146.705971 88.547191) (xy 146.751393 88.540573) (xy 146.856483 88.489198) (xy 146.891781 88.453899)
+				(xy 146.920025 88.432754) (xy 146.942502 88.420481) (xy 147.00616 88.405387) (xy 147.393916 88.418643)
+				(xy 147.428656 88.424858) (xy 147.454384 88.433378) (xy 147.503083 88.46341) (xy 147.61632 88.576647)
+				(xy 147.725573 88.685899) (xy 147.746723 88.714151) (xy 147.759331 88.73724) (xy 147.7745 88.796669)
+				(xy 147.7745 90.097873) (xy 147.774499 90.09845) (xy 147.774497 90.09888) (xy 147.774494 90.099328)
+				(xy 147.774155 90.14024) (xy 147.774216 90.140696) (xy 147.774278 90.141176) (xy 147.774334 90.141613)
+				(xy 147.774424 90.142328) (xy 147.774478 90.142773) (xy 147.78507 90.182302) (xy 147.785298 90.183167)
+				(xy 147.785368 90.183436) (xy 147.7854 90.183559) (xy 147.795674 90.223206) (xy 147.795676 90.223212)
+				(xy 147.795804 90.223521) (xy 147.795994 90.223981) (xy 147.796162 90.224392) (xy 147.796342 90.224831)
+				(xy 147.796636 90.225559) (xy 147.796637 90.225561) (xy 147.796641 90.225569) (xy 147.817055 90.260927)
+				(xy 147.817287 90.26133) (xy 147.817548 90.261786) (xy 147.817825 90.262273) (xy 147.833695 90.290294)
+				(xy 147.837932 90.297773) (xy 147.837933 90.297775) (xy 147.838216 90.298144) (xy 147.8384 90.298385)
+				(xy 147.838831 90.29895) (xy 147.839202 90.299438) (xy 147.839479 90.299806) (xy 147.839481 90.299808)
+				(xy 147.868366 90.328693) (xy 147.868729 90.329057) (xy 147.868969 90.329299) (xy 147.869328 90.329662)
+				(xy 148.131402 90.596104) (xy 148.152316 90.624528) (xy 148.164818 90.647881) (xy 148.1795 90.706407)
+				(xy 148.1795 90.884318) (xy 148.18593 90.933168) (xy 148.18593 90.933169) (xy 148.185931 90.933171)
+				(xy 148.196712 90.95629) (xy 148.235935 91.040404) (xy 148.319596 91.124065) (xy 148.426827 91.174068)
 				(xy 148.475683 91.1805) (xy 148.475684 91.1805) (xy 148.924317 91.1805) (xy 148.944141 91.17789)
 				(xy 148.973173 91.174068) (xy 149.080404 91.124065) (xy 149.164065 91.040404) (xy 149.214068 90.933173)
 				(xy 149.2205 90.884316) (xy 149.2205 90.715758) (xy 149.225523 90.680822) (xy 149.232966 90.655473)
@@ -37628,7 +37669,7 @@ 				(xy 128.925639 86.996681) (xy 128.882786 86.985199) (xy 128.879221 86.983026) (xy 128.879168 86.98321)
 				(xy 128.817395 86.965072) (xy 128.758617 86.927298) (xy 128.758609 86.927288) (xy 128.757423 86.925919)
 				(xy 128.729977 86.871083) (xy 128.728332 86.863521) (xy 128.7255 86.837168) (xy 128.7255 86.816738)
-				(xy 128.715573 86.748608) (xy 128.71557 86.748602) (xy 128.714564 86.746544) (xy 128.665872 86.646941)
+				(xy 128.717987 86.765172) (xy 128.717986 86.765171) (xy 128.715573 86.748607) (xy 128.665871 86.646941)
 				(xy 128.664433 86.643751) (xy 128.64597 86.625288) (xy 128.624818 86.597032) (xy 128.617326 86.583311)
 				(xy 128.602474 86.515051) (xy 128.602603 86.513242) (xy 128.621973 86.455041) (xy 128.626157 86.44853)
 				(xy 128.642787 86.427895) (xy 128.664196 86.406485) (xy 128.664198 86.406483) (xy 128.715573 86.301393)
@@ -39608,7 +39649,8 @@ 				(xy 99.580653 84.965816) (xy 99.596682 85.02564) (xy 99.613415 85.054621) (xy 99.613416 85.054625)
 				(xy 99.613418 85.054625) (xy 99.620172 85.066324) (xy 99.639533 85.099859) (xy 99.639536 85.099863)
 				(xy 100.850573 86.3109) (xy 100.871723 86.339152) (xy 100.884331 86.362241) (xy 100.8995 86.42167)
-				(xy 100.8995 86.465892) (xy 100.902315 86.476397) (xy 100.933608 86.593187) (xy 100.962665 86.643514)
+				(xy 100.8995 86.465895) (xy 100.909838 86.504478) (xy 100.909839 86.504478) (xy 100.909839 86.504479)
+				(xy 100.926949 86.568334) (xy 100.933608 86.593187) (xy 100.962662 86.64351) (xy 100.962665 86.643514)
 				(xy 100.9995 86.707314) (xy 101.092686 86.8005) (xy 101.206814 86.866392) (xy 101.334108 86.9005)
 				(xy 101.33411 86.9005) (xy 101.46589 86.9005) (xy 101.465892 86.9005) (xy 101.593186 86.866392)
 				(xy 101.598056 86.863579) (xy 101.630824 86.85046) (xy 101.644311 86.847188) (xy 101.714102 86.850512)
@@ -39680,33 +39722,33 @@ 				(xy 127.999506 89.292677) (xy 127.999502 89.292682) (xy 127.999493 89.292696) (xy 127.93361 89.406806)
 				(xy 127.933609 89.406809) (xy 127.933608 89.406811) (xy 127.933608 89.406812) (xy 127.90454 89.5153)
 				(xy 127.904539 89.515302) (xy 127.904538 89.515304) (xy 127.8995 89.534102) (xy 127.8995 89.665893)
-				(xy 127.908265 89.698607) (xy 127.908266 89.698607) (xy 127.933608 89.793187) (xy 127.933609 89.793188)
-				(xy 127.933608 89.793188) (xy 127.949846 89.821311) (xy 127.959586 89.838181) (xy 127.9995 89.907314)
-				(xy 128.092686 90.0005) (xy 128.206814 90.066392) (xy 128.334108 90.1005) (xy 128.33411 90.1005)
-				(xy 128.465891 90.1005) (xy 128.465892 90.1005) (xy 128.468908 90.099691) (xy 128.503952 90.095499)
-				(xy 128.519587 90.095871) (xy 128.586132 90.117141) (xy 128.592445 90.121414) (xy 128.633429 90.16781)
-				(xy 128.651471 90.203218) (xy 128.651476 90.203225) (xy 128.746774 90.298523) (xy 128.746778 90.298526)
-				(xy 128.74678 90.298528) (xy 128.866874 90.359719) (xy 128.866876 90.359719) (xy 128.867502 90.360038)
-				(xy 128.871627 90.360859) (xy 128.871644 90.360863) (xy 128.876508 90.361246) (xy 128.966506 90.3755)
-				(xy 128.966507 90.3755) (xy 128.966512 90.3755) (xy 129.483492 90.3755) (xy 129.483493 90.3755)
-				(xy 129.520232 90.369681) (xy 129.573488 90.361246) (xy 129.578362 90.360862) (xy 129.57838 90.360858)
-				(xy 129.587938 90.358956) (xy 129.59285 90.358178) (xy 129.605093 90.348526) (xy 129.70322 90.298528)
-				(xy 129.798528 90.20322) (xy 129.859719 90.083126) (xy 129.868503 90.027657) (xy 129.878925 89.99395)
-				(xy 129.884474 89.982246) (xy 129.930974 89.930102) (xy 129.998287 89.911375) (xy 130.060531 89.92916)
-				(xy 130.062728 89.930485) (xy 130.107549 89.97726) (xy 130.119135 89.998479) (xy 130.124609 90.009842)
-				(xy 130.131128 90.025348) (xy 130.14028 90.083123) (xy 130.140279 90.083123) (xy 130.159884 90.1216)
-				(xy 130.201472 90.20322) (xy 130.201474 90.203222) (xy 130.201476 90.203225) (xy 130.209143 90.210892)
-				(xy 130.209144 90.210894) (xy 130.296771 90.298521) (xy 130.296774 90.298523) (xy 130.296778 90.298526)
-				(xy 130.29678 90.298528) (xy 130.416874 90.359719) (xy 130.416876 90.359719) (xy 130.416878 90.35972)
-				(xy 130.45752 90.366157) (xy 130.491237 90.376581) (xy 130.519413 90.389938) (xy 130.553977 90.414304)
-				(xy 130.600138 90.460465) (xy 130.628109 90.476614) (xy 130.674361 90.503318) (xy 130.757147 90.525501)
-				(xy 130.757149 90.525501) (xy 130.850449 90.525501) (xy 130.852353 90.525501) (xy 130.852416 90.5255)
-				(xy 133.442851 90.5255) (xy 133.442853 90.5255) (xy 133.525639 90.503318) (xy 133.599862 90.460465)
-				(xy 136.065786 87.99454) (xy 136.127107 87.961057) (xy 136.196799 87.966041) (xy 136.252732 88.007913)
-				(xy 136.262889 88.023896) (xy 136.28324 88.062085) (xy 136.29243 88.072691) (xy 136.329 88.114895)
-				(xy 136.34539 88.131622) (xy 136.346586 88.132843) (xy 136.346587 88.132844) (xy 136.346589 88.132845)
-				(xy 136.414534 88.170851) (xy 136.426403 88.17749) (xy 136.493442 88.197175) (xy 136.55134 88.2055)
-				(xy 136.551344 88.2055) (xy 137.4505 88.2055) (xy 137.517539 88.225185) (xy 137.563294 88.277989)
+				(xy 127.908266 89.698608) (xy 127.933607 89.793184) (xy 127.933608 89.793187) (xy 127.935681 89.796778)
+				(xy 127.938704 89.802012) (xy 127.938705 89.802015) (xy 127.999497 89.90731) (xy 127.999499 89.907312)
+				(xy 127.9995 89.907314) (xy 128.092686 90.0005) (xy 128.206814 90.066392) (xy 128.334108 90.1005)
+				(xy 128.33411 90.1005) (xy 128.465891 90.1005) (xy 128.465892 90.1005) (xy 128.468908 90.099691)
+				(xy 128.503952 90.095499) (xy 128.519587 90.095871) (xy 128.586132 90.117141) (xy 128.592445 90.121414)
+				(xy 128.633429 90.16781) (xy 128.651471 90.203218) (xy 128.651476 90.203225) (xy 128.746774 90.298523)
+				(xy 128.746778 90.298526) (xy 128.74678 90.298528) (xy 128.866874 90.359719) (xy 128.866876 90.359719)
+				(xy 128.867502 90.360038) (xy 128.871627 90.360859) (xy 128.871644 90.360863) (xy 128.876508 90.361246)
+				(xy 128.966506 90.3755) (xy 128.966507 90.3755) (xy 128.966512 90.3755) (xy 129.483492 90.3755)
+				(xy 129.483493 90.3755) (xy 129.520232 90.369681) (xy 129.573488 90.361246) (xy 129.578362 90.360862)
+				(xy 129.57838 90.360858) (xy 129.587938 90.358956) (xy 129.59285 90.358178) (xy 129.605093 90.348526)
+				(xy 129.70322 90.298528) (xy 129.798528 90.20322) (xy 129.859719 90.083126) (xy 129.868503 90.027657)
+				(xy 129.878925 89.99395) (xy 129.884474 89.982246) (xy 129.930974 89.930102) (xy 129.998287 89.911375)
+				(xy 130.060531 89.92916) (xy 130.062728 89.930485) (xy 130.107549 89.97726) (xy 130.119135 89.998479)
+				(xy 130.124609 90.009842) (xy 130.131128 90.025348) (xy 130.14028 90.083123) (xy 130.140279 90.083123)
+				(xy 130.159884 90.1216) (xy 130.201472 90.20322) (xy 130.201474 90.203222) (xy 130.201476 90.203225)
+				(xy 130.209143 90.210892) (xy 130.209144 90.210894) (xy 130.296771 90.298521) (xy 130.296774 90.298523)
+				(xy 130.296778 90.298526) (xy 130.29678 90.298528) (xy 130.416874 90.359719) (xy 130.416876 90.359719)
+				(xy 130.416878 90.35972) (xy 130.45752 90.366157) (xy 130.491237 90.376581) (xy 130.519413 90.389938)
+				(xy 130.553977 90.414304) (xy 130.600138 90.460465) (xy 130.628109 90.476614) (xy 130.674361 90.503318)
+				(xy 130.757147 90.525501) (xy 130.757149 90.525501) (xy 130.850449 90.525501) (xy 130.852353 90.525501)
+				(xy 130.852416 90.5255) (xy 133.442851 90.5255) (xy 133.442853 90.5255) (xy 133.525639 90.503318)
+				(xy 133.599862 90.460465) (xy 136.065786 87.99454) (xy 136.127107 87.961057) (xy 136.196799 87.966041)
+				(xy 136.252732 88.007913) (xy 136.262889 88.023896) (xy 136.28324 88.062085) (xy 136.29243 88.072691)
+				(xy 136.329 88.114895) (xy 136.34539 88.131622) (xy 136.346586 88.132843) (xy 136.346587 88.132844)
+				(xy 136.346589 88.132845) (xy 136.414534 88.170851) (xy 136.426403 88.17749) (xy 136.493442 88.197175)
+				(xy 136.55134 88.2055) (xy 136.551344 88.2055) (xy 137.4505 88.2055) (xy 137.517539 88.225185) (xy 137.563294 88.277989)
 				(xy 137.5745 88.3295) (xy 137.5745 97.89598) (xy 137.569476 97.930917) (xy 137.562064 97.956158)
 				(xy 137.530769 98.008902) (xy 136.396637 99.143033) (xy 136.368381 99.164185) (xy 136.35466 99.171677)
 				(xy 136.286395 99.186528) (xy 136.26829 99.185234) (xy 136.25282 99.182548) (xy 136.25271 99.183103)
@@ -40470,6 +40512,28 @@ 				(xy 134.5 84.7) (xy 138 84.7) (xy 138 88) (xy 134.5 88)
 			)
 		)
+		(filled_polygon
+			(layer "B.Cu")
+			(pts
+				(xy 137.943039 84.719685) (xy 137.988794 84.772489) (xy 138 84.824) (xy 138 86.488311) (xy 137.980315 86.55535)
+				(xy 137.963681 86.575992) (xy 137.601492 86.938181) (xy 137.540169 86.971666) (xy 137.513811 86.9745)
+				(xy 136.733676 86.9745) (xy 136.666637 86.954815) (xy 136.645995 86.938181) (xy 136.607316 86.899502)
+				(xy 136.607314 86.8995) (xy 136.539704 86.860465) (xy 136.493187 86.833608) (xy 136.429539 86.816554)
+				(xy 136.365892 86.7995) (xy 136.234108 86.7995) (xy 136.106812 86.833608) (xy 135.992686 86.8995)
+				(xy 135.992683 86.899502) (xy 135.899502 86.992683) (xy 135.8995 86.992686) (xy 135.833608 87.106812)
+				(xy 135.7995 87.234108) (xy 135.7995 87.365891) (xy 135.833608 87.493187) (xy 135.866554 87.55025)
+				(xy 135.8995 87.607314) (xy 135.992686 87.7005) (xy 135.992687 87.700501) (xy 135.992689 87.700502)
+				(xy 136.11066 87.768613) (xy 136.158876 87.81918) (xy 136.172098 87.887787) (xy 136.14613 87.952652)
+				(xy 136.089216 87.99318) (xy 136.04866 88) (xy 134.624 88) (xy 134.556961 87.980315) (xy 134.511206 87.927511)
+				(xy 134.5 87.876) (xy 134.5 87.186509) (xy 134.519685 87.11947) (xy 134.572489 87.073715) (xy 134.591917 87.066731)
+				(xy 134.593186 87.066392) (xy 134.707314 87.0005) (xy 134.8005 86.907314) (xy 134.866392 86.793186)
+				(xy 134.9005 86.665892) (xy 134.9005 86.611187) (xy 134.920185 86.544148) (xy 134.936815 86.52351)
+				(xy 135.889652 85.570672) (xy 135.889657 85.570669) (xy 135.89986 85.560465) (xy 135.899862 85.560465)
+				(xy 135.960465 85.499862) (xy 136.003318 85.425638) (xy 136.017058 85.374361) (xy 136.025501 85.342853)
+				(xy 136.025501 85.257147) (xy 136.025501 85.249552) (xy 136.0255 85.249534) (xy 136.0255 84.824)
+				(xy 136.045185 84.756961) (xy 136.097989 84.711206) (xy 136.1495 84.7) (xy 137.876 84.7)
+			)
+		)
 		(filled_polygon
 			(layer "B.Cu")
 			(pts
@@ -40503,24 +40567,65 @@ 		(filled_polygon
 			(layer "B.Cu")
 			(pts
-				(xy 133.664514 76.408658) (xy 133.706383 76.464594) (xy 133.711364 76.534286) (xy 133.711361 76.534297)
-				(xy 133.710359 76.5389) (xy 133.696755 76.574085) (xy 133.689678 76.600499) (xy 133.684579 76.619531)
-				(xy 133.680299 76.635504) (xy 133.6745 76.657144) (xy 133.6745 80.042849) (xy 133.674499 80.042849)
-				(xy 133.6745 80.042852) (xy 133.6745 80.042853) (xy 133.682942 80.07436) (xy 133.696682 80.12564)
-				(xy 133.709744 80.148262) (xy 133.709745 80.148266) (xy 133.709746 80.148266) (xy 133.739535 80.199862)
-				(xy 133.739537 80.199864) (xy 138.939383 85.39971) (xy 138.960535 85.427966) (xy 138.968027 85.441687)
-				(xy 138.982879 85.509947) (xy 138.98275 85.511753) (xy 138.963382 85.569956) (xy 138.959201 85.576462)
-				(xy 138.942565 85.597105) (xy 137.614099 86.925573) (xy 137.585848 86.946722) (xy 137.56276 86.95933)
-				(xy 137.50333 86.9745) (xy 136.751506 86.9745) (xy 136.71657 86.969477) (xy 136.691328 86.962065)
-				(xy 136.638583 86.930769) (xy 136.607316 86.899502) (xy 136.607314 86.8995) (xy 136.539702 86.860464)
-				(xy 136.493187 86.833608) (xy 136.429539 86.816554) (xy 136.365892 86.7995) (xy 136.365891 86.7995)
-				(xy 136.234108 86.7995) (xy 136.106808 86.833609) (xy 136.106806 86.83361) (xy 135.992696 86.899493)
-				(xy 135.992682 86.899502) (xy 135.992677 86.899506) (xy 135.899506 86.992677) (xy 135.899502 86.992682)
-				(xy 135.899493 86.992696) (xy 135.83361 87.106806) (xy 135.833609 87.106809) (xy 135.817644 87.166392)
-				(xy 135.7995 87.234106) (xy 135.7995 87.234108) (xy 135.7995 87.365892) (xy 135.802251 87.376158)
-				(xy 135.833608 87.493187) (xy 135.849592 87.520871) (xy 135.8995 87.607314) (xy 135.992686 87.7005)
-				(xy 136.106814 87.766392) (xy 136.234108 87.8005) (xy 136.23411 87.8005) (xy 136.36589 87.8005)
-				(xy 136.365892 87.8005) (xy 136.493186 87.766392) (xy 136.607314 87.7005) (xy 136.633392 87.674421)
+				(xy 122.797316 80.805521) (xy 122.810635 80.809432) (xy 122.869413 80.847206) (xy 122.898439 80.910761)
+				(xy 122.898444 80.910799) (xy 122.898675 80.912409) (xy 122.89311 80.970582) (xy 122.891472 80.975314)
+				(xy 122.881682 80.996751) (xy 122.840422 81.068215) (xy 122.837238 81.080101) (xy 122.801405 81.213832)
+				(xy 122.7995 81.220943) (xy 122.7995 81.379057) (xy 122.810399 81.419731) (xy 122.831945 81.500144)
+				(xy 122.840134 81.530703) (xy 122.840423 81.531783) (xy 122.840426 81.53179) (xy 122.916405 81.663392)
+				(xy 122.920472 81.670436) (xy 122.920312 81.670528) (xy 122.929443 81.688848) (xy 122.941156 81.719141)
+				(xy 122.9495 81.763858) (xy 122.9495 82.859309) (xy 122.964564 82.915536) (xy 122.964566 82.915539)
+				(xy 122.980197 82.973881) (xy 122.980199 82.973884) (xy 122.993175 82.996359) (xy 122.993176 82.996362)
+				(xy 122.993177 82.996362) (xy 123.039508 83.07661) (xy 123.039513 83.076616) (xy 123.875224 83.912327)
+				(xy 123.896375 83.94058) (xy 123.905393 83.957095) (xy 123.920526 84.019475) (xy 123.920278 84.029888)
+				(xy 123.916089 84.059026) (xy 123.914073 84.066552) (xy 123.8995 84.120941) (xy 123.8995 84.120943)
+				(xy 123.8995 84.279057) (xy 123.907071 84.307313) (xy 123.937485 84.420818) (xy 123.940422 84.43178)
+				(xy 123.940424 84.431786) (xy 123.940428 84.431793) (xy 124.017629 84.565513) (xy 124.019012 84.568084)
+				(xy 124.019398 84.568634) (xy 124.131336 84.680572) (xy 124.131851 84.68095) (xy 124.134461 84.682355)
+				(xy 124.229447 84.737194) (xy 124.268216 84.759577) (xy 124.420943 84.8005) (xy 124.420945 84.8005)
+				(xy 124.579055 84.8005) (xy 124.579057 84.8005) (xy 124.731784 84.759577) (xy 124.868716 84.68052)
+				(xy 124.98052 84.568716) (xy 125.059577 84.431784) (xy 125.1005 84.279057) (xy 125.1005 84.161901)
+				(xy 125.105521 84.126974) (xy 125.108707 84.116121) (xy 125.146477 84.05734) (xy 125.210031 84.028311)
+				(xy 125.279186 84.038249) (xy 125.286172 84.041439) (xy 125.322342 84.066552) (xy 125.894083 84.638295)
+				(xy 125.894088 84.638299) (xy 125.894092 84.638302) (xy 126.049763 84.742319) (xy 126.049776 84.742326)
+				(xy 126.160755 84.788294) (xy 126.22275 84.813973) (xy 126.222751 84.813973) (xy 126.222752 84.813973)
+				(xy 126.222757 84.813975) (xy 126.40344 84.849914) (xy 126.405648 84.850409) (xy 126.406381 84.8505)
+				(xy 126.593619 84.8505) (xy 126.597739 84.8505) (xy 126.605591 84.848117) (xy 126.678539 84.833608)
+				(xy 126.777242 84.813975) (xy 126.777242 84.813974) (xy 126.777247 84.813974) (xy 126.777249 84.813973)
+				(xy 126.77725 84.813973) (xy 126.780363 84.812683) (xy 126.788419 84.809352) (xy 126.78846 84.80933)
+				(xy 126.890488 84.767069) (xy 126.950231 84.742323) (xy 126.966722 84.731302) (xy 126.998555 84.716072)
+				(xy 127.025576 84.707612) (xy 127.030201 84.706164) (xy 127.06725 84.7005) (xy 132.533544 84.7005)
+				(xy 132.568476 84.705521) (xy 132.59372 84.712934) (xy 132.646466 84.74423) (xy 132.852447 84.950211)
+				(xy 132.873596 84.978461) (xy 132.879017 84.988388) (xy 132.893871 85.056661) (xy 132.869457 85.122126)
+				(xy 132.813525 85.163999) (xy 132.813521 85.164001) (xy 132.806333 85.166682) (xy 132.762999 85.1745)
+				(xy 122.451506 85.1745) (xy 122.41657 85.169477) (xy 122.391328 85.162065) (xy 122.338583 85.130769)
+				(xy 122.307316 85.099502) (xy 122.307314 85.0995) (xy 122.238597 85.059826) (xy 122.193187 85.033608)
+				(xy 122.129539 85.016554) (xy 122.065892 84.9995) (xy 122.065891 84.9995) (xy 121.934108 84.9995)
+				(xy 121.806808 85.033609) (xy 121.806806 85.03361) (xy 121.692696 85.099493) (xy 121.692682 85.099502)
+				(xy 121.692677 85.099506) (xy 121.599506 85.192677) (xy 121.599502 85.192682) (xy 121.599493 85.192696)
+				(xy 121.53361 85.306806) (xy 121.533609 85.306809) (xy 121.533608 85.306811) (xy 121.533608 85.306812)
+				(xy 121.530146 85.319732) (xy 121.501768 85.425638) (xy 121.501769 85.425639) (xy 121.500403 85.430738)
+				(xy 121.4995 85.434108) (xy 121.4995 85.565892) (xy 121.502332 85.576462) (xy 121.51494 85.623515)
+				(xy 121.533606 85.693185) (xy 121.533608 85.693189) (xy 121.558741 85.73672) (xy 121.594989 85.799501)
+				(xy 121.5995 85.807314) (xy 121.692686 85.9005) (xy 121.743089 85.9296) (xy 121.806796 85.966382)
+				(xy 121.806814 85.966392) (xy 121.934108 86.0005) (xy 121.93411 86.0005) (xy 122.06589 86.0005)
+				(xy 122.065892 86.0005) (xy 122.193186 85.966392) (xy 122.307314 85.9005) (xy 122.333392 85.874421)
+				(xy 122.36164 85.853275) (xy 122.384732 85.840667) (xy 122.444157 85.8255) (xy 132.695981 85.8255)
+				(xy 132.730913 85.830521) (xy 132.756157 85.837934) (xy 132.808903 85.86923) (xy 133.800138 86.860465)
+				(xy 133.855931 86.892677) (xy 133.855941 86.892683) (xy 133.855944 86.892685) (xy 133.861846 86.896092)
+				(xy 133.867751 86.899502) (xy 133.874361 86.903318) (xy 133.874363 86.903318) (xy 133.874364 86.903319)
+				(xy 133.874366 86.90332) (xy 133.910245 86.912933) (xy 133.957147 86.9255) (xy 133.957154 86.9255)
+				(xy 133.96425 86.926435) (xy 133.983 86.930397) (xy 134.008669 86.937934) (xy 134.061416 86.96923)
+				(xy 134.092686 87.0005) (xy 134.206814 87.066392) (xy 134.206816 87.066392) (xy 134.214326 87.069504)
+				(xy 134.213363 87.071828) (xy 134.262246 87.101617) (xy 134.292781 87.164461) (xy 134.2945 87.185036)
+				(xy 134.2945 87.876007) (xy 134.299197 87.919686) (xy 134.310397 87.971174) (xy 134.31289 87.981372)
+				(xy 134.312891 87.981375) (xy 134.355899 88.062083) (xy 134.355901 88.062086) (xy 134.40166 88.114895)
+				(xy 134.419242 88.132839) (xy 134.419246 88.132843) (xy 134.419247 88.132844) (xy 134.419249 88.132845)
+				(xy 134.468825 88.160576) (xy 134.499063 88.17749) (xy 134.566102 88.197175) (xy 134.624 88.2055)
+				(xy 136.048649 88.2055) (xy 136.04866 88.2055) (xy 136.082739 88.202655) (xy 136.105461 88.198833)
+				(xy 136.123162 88.195858) (xy 136.123438 88.19581) (xy 136.123982 88.195718) (xy 136.208417 88.160576)
+				(xy 136.265331 88.120048) (xy 136.284878 88.104241) (xy 136.33691 88.029029) (xy 136.362878 87.964164)
+				(xy 136.370775 87.940302) (xy 136.372497 87.88968) (xy 136.394448 87.823351) (xy 136.448777 87.779417)
+				(xy 136.464313 87.774128) (xy 136.493186 87.766392) (xy 136.607314 87.7005) (xy 136.633392 87.674421)
 				(xy 136.66164 87.653275) (xy 136.684732 87.640667) (xy 136.744157 87.6255) (xy 137.742851 87.6255)
 				(xy 137.742853 87.6255) (xy 137.825639 87.603318) (xy 137.899862 87.560465) (xy 139.285903 86.174423)
 				(xy 139.314153 86.153275) (xy 139.337245 86.140667) (xy 139.39667 86.1255) (xy 141.142851 86.1255)
@@ -40563,7 +40668,7 @@ 				(xy 132.042896 86.376537) (xy 132.027682 86.370235) (xy 132.01952 86.369161) (xy 131.976198 86.357553)
 				(xy 131.946144 86.3495) (xy 131.946143 86.3495) (xy 130.726506 86.3495) (xy 130.69157 86.344477)
 				(xy 130.666328 86.337065) (xy 130.613583 86.305769) (xy 130.607316 86.299502) (xy 130.607314 86.2995)
-				(xy 130.549102 86.265891) (xy 130.493188 86.233608) (xy 130.393188 86.206814) (xy 130.393186 86.206813)
+				(xy 130.53832 86.259666) (xy 130.493188 86.233608) (xy 130.393188 86.206814) (xy 130.393186 86.206813)
 				(xy 130.365893 86.1995) (xy 130.365892 86.1995) (xy 130.234108 86.1995) (xy 130.106808 86.233609)
 				(xy 130.106806 86.23361) (xy 129.992696 86.299493) (xy 129.99269 86.299497) (xy 129.992689 86.299498)
 				(xy 129.992686 86.2995) (xy 129.992683 86.299502) (xy 129.99268 86.299504) (xy 129.99267 86.299513)
@@ -40573,107 +40678,28 @@ 				(xy 129.064703 86.373389) (xy 128.984791 86.419526) (xy 128.984786 86.419531) (xy 128.984777 86.419538)
 				(xy 128.719531 86.684784) (xy 128.700011 86.718597) (xy 128.700008 86.718602) (xy 128.696791 86.724175)
 				(xy 128.695946 86.725638) (xy 128.695944 86.72564) (xy 128.689547 86.73672) (xy 128.673335 86.764798)
-				(xy 128.672904 86.766508) (xy 128.663797 86.800499) (xy 128.663796 86.800498) (xy 128.6495 86.853854)
-				(xy 128.6495 87.346148) (xy 128.672996 87.433837) (xy 128.672999 87.433845) (xy 128.673385 87.435287)
-				(xy 128.673387 87.43529) (xy 128.721252 87.518196) (xy 128.729519 87.535475) (xy 128.741155 87.565568)
-				(xy 128.7495 87.610288) (xy 128.7495 88.785625) (xy 128.744476 88.820562) (xy 128.737064 88.845803)
-				(xy 128.705769 88.898547) (xy 128.553743 89.050573) (xy 128.525492 89.071722) (xy 128.502404 89.08433)
-				(xy 128.442974 89.0995) (xy 128.334108 89.0995) (xy 128.206808 89.133609) (xy 128.206806 89.13361)
-				(xy 128.092696 89.199493) (xy 128.092682 89.199502) (xy 128.092677 89.199506) (xy 127.999506 89.292677)
-				(xy 127.999502 89.292682) (xy 127.999493 89.292696) (xy 127.93361 89.406806) (xy 127.933609 89.406808)
-				(xy 127.8995 89.534108) (xy 127.8995 89.635626) (xy 127.894476 89.670563) (xy 127.887064 89.695804)
-				(xy 127.855769 89.748548) (xy 125.803744 91.800573) (xy 125.775493 91.821722) (xy 125.752405 91.83433)
-				(xy 125.692975 91.8495) (xy 113.228019 91.8495) (xy 113.193084 91.844477) (xy 113.18223 91.84129)
-				(xy 113.123452 91.803516) (xy 113.094427 91.73996) (xy 113.104367 91.670811) (xy 113.107557 91.663824)
-				(xy 113.132666 91.62766) (xy 115.956872 88.803455) (xy 118.860465 85.899862) (xy 118.883654 85.859696)
-				(xy 118.885876 85.857577) (xy 118.90822 85.81887) (xy 118.908222 85.818871) (xy 118.926391 85.787397)
-				(xy 118.9461 85.761713) (xy 118.951317 85.756497) (xy 118.961562 85.746252) (xy 119.0005 85.707314)
-				(xy 119.066392 85.593186) (xy 119.1005 85.465892) (xy 119.1005 85.334108) (xy 119.066392 85.206814)
-				(xy 119.0005 85.092686) (xy 118.907314 84.9995) (xy 118.793186 84.933608) (xy 118.706554 84.910395)
-				(xy 118.674111 84.896501) (xy 118.662258 84.889276) (xy 118.615262 84.837575) (xy 118.603677 84.768687)
-				(xy 118.603871 84.767065) (xy 118.624304 84.712281) (xy 118.62712 84.70812) (xy 118.642115 84.68996)
-				(xy 122.482654 80.849423) (xy 122.510904 80.828275) (xy 122.533996 80.815667) (xy 122.593421 80.8005)
-				(xy 122.762384 80.8005) (xy 122.797316 80.805521) (xy 122.810635 80.809432) (xy 122.869413 80.847206)
-				(xy 122.898439 80.910761) (xy 122.898444 80.910799) (xy 122.898675 80.912409) (xy 122.89311 80.970582)
-				(xy 122.891472 80.975314) (xy 122.881682 80.996751) (xy 122.840422 81.068215) (xy 122.837238 81.080101)
-				(xy 122.801405 81.213832) (xy 122.7995 81.220943) (xy 122.7995 81.379057) (xy 122.810399 81.419731)
-				(xy 122.831945 81.500144) (xy 122.840134 81.530703) (xy 122.840423 81.531783) (xy 122.840426 81.53179)
-				(xy 122.916405 81.663392) (xy 122.920472 81.670436) (xy 122.920312 81.670528) (xy 122.929443 81.688848)
-				(xy 122.941156 81.719141) (xy 122.9495 81.763858) (xy 122.9495 82.859309) (xy 122.964564 82.915536)
-				(xy 122.964566 82.915539) (xy 122.980197 82.973881) (xy 122.980199 82.973884) (xy 122.993175 82.996359)
-				(xy 122.993176 82.996362) (xy 122.993177 82.996362) (xy 123.039508 83.07661) (xy 123.039513 83.076616)
-				(xy 123.875224 83.912327) (xy 123.896375 83.94058) (xy 123.905393 83.957095) (xy 123.920526 84.019475)
-				(xy 123.920278 84.029888) (xy 123.916089 84.059026) (xy 123.914073 84.066552) (xy 123.8995 84.120941)
-				(xy 123.8995 84.120943) (xy 123.8995 84.279057) (xy 123.907071 84.307313) (xy 123.937485 84.420818)
-				(xy 123.940422 84.43178) (xy 123.940424 84.431786) (xy 123.940428 84.431793) (xy 124.017629 84.565513)
-				(xy 124.019012 84.568084) (xy 124.019398 84.568634) (xy 124.131336 84.680572) (xy 124.131851 84.68095)
-				(xy 124.134461 84.682355) (xy 124.229447 84.737194) (xy 124.268216 84.759577) (xy 124.420943 84.8005)
-				(xy 124.420945 84.8005) (xy 124.579055 84.8005) (xy 124.579057 84.8005) (xy 124.731784 84.759577)
-				(xy 124.868716 84.68052) (xy 124.98052 84.568716) (xy 125.059577 84.431784) (xy 125.1005 84.279057)
-				(xy 125.1005 84.161901) (xy 125.105521 84.126974) (xy 125.108707 84.116121) (xy 125.146477 84.05734)
-				(xy 125.210031 84.028311) (xy 125.279186 84.038249) (xy 125.286172 84.041439) (xy 125.322342 84.066552)
-				(xy 125.894083 84.638295) (xy 125.894088 84.638299) (xy 125.894092 84.638302) (xy 126.049763 84.742319)
-				(xy 126.049776 84.742326) (xy 126.160755 84.788294) (xy 126.22275 84.813973) (xy 126.222751 84.813973)
-				(xy 126.222752 84.813973) (xy 126.222757 84.813975) (xy 126.40344 84.849914) (xy 126.405648 84.850409)
-				(xy 126.406381 84.8505) (xy 126.593619 84.8505) (xy 126.597739 84.8505) (xy 126.605591 84.848117)
-				(xy 126.678539 84.833608) (xy 126.777242 84.813975) (xy 126.777242 84.813974) (xy 126.777247 84.813974)
-				(xy 126.777249 84.813973) (xy 126.77725 84.813973) (xy 126.780363 84.812683) (xy 126.788419 84.809352)
-				(xy 126.78846 84.80933) (xy 126.890488 84.767069) (xy 126.950231 84.742323) (xy 126.966722 84.731302)
-				(xy 126.998555 84.716072) (xy 127.025576 84.707612) (xy 127.030201 84.706164) (xy 127.06725 84.7005)
-				(xy 132.533544 84.7005) (xy 132.568476 84.705521) (xy 132.59372 84.712934) (xy 132.646466 84.74423)
-				(xy 132.852447 84.950211) (xy 132.873596 84.978461) (xy 132.879017 84.988388) (xy 132.893871 85.056661)
-				(xy 132.869457 85.122126) (xy 132.813525 85.163999) (xy 132.813521 85.164001) (xy 132.806333 85.166682)
-				(xy 132.762999 85.1745) (xy 122.451506 85.1745) (xy 122.41657 85.169477) (xy 122.391328 85.162065)
-				(xy 122.338583 85.130769) (xy 122.307316 85.099502) (xy 122.307314 85.0995) (xy 122.238597 85.059826)
-				(xy 122.193187 85.033608) (xy 122.129539 85.016554) (xy 122.065892 84.9995) (xy 122.065891 84.9995)
-				(xy 121.934108 84.9995) (xy 121.806808 85.033609) (xy 121.806806 85.03361) (xy 121.692696 85.099493)
-				(xy 121.692682 85.099502) (xy 121.692677 85.099506) (xy 121.599506 85.192677) (xy 121.599502 85.192682)
-				(xy 121.599493 85.192696) (xy 121.53361 85.306806) (xy 121.533609 85.306809) (xy 121.533608 85.306811)
-				(xy 121.533608 85.306812) (xy 121.530146 85.319732) (xy 121.501768 85.425638) (xy 121.501769 85.425639)
-				(xy 121.500403 85.430738) (xy 121.4995 85.434108) (xy 121.4995 85.565892) (xy 121.502332 85.576462)
-				(xy 121.51494 85.623515) (xy 121.533606 85.693185) (xy 121.533608 85.693189) (xy 121.558741 85.73672)
-				(xy 121.594989 85.799501) (xy 121.5995 85.807314) (xy 121.692686 85.9005) (xy 121.743089 85.9296)
-				(xy 121.806796 85.966382) (xy 121.806814 85.966392) (xy 121.934108 86.0005) (xy 121.93411 86.0005)
-				(xy 122.06589 86.0005) (xy 122.065892 86.0005) (xy 122.193186 85.966392) (xy 122.307314 85.9005)
-				(xy 122.333392 85.874421) (xy 122.36164 85.853275) (xy 122.384732 85.840667) (xy 122.444157 85.8255)
-				(xy 132.695981 85.8255) (xy 132.730913 85.830521) (xy 132.756157 85.837934) (xy 132.808903 85.86923)
-				(xy 133.800138 86.860465) (xy 133.855931 86.892677) (xy 133.855941 86.892683) (xy 133.855944 86.892685)
-				(xy 133.861846 86.896092) (xy 133.867751 86.899502) (xy 133.874361 86.903318) (xy 133.874363 86.903318)
-				(xy 133.874364 86.903319) (xy 133.874366 86.90332) (xy 133.910245 86.912933) (xy 133.957147 86.9255)
-				(xy 133.957154 86.9255) (xy 133.96425 86.926435) (xy 133.983 86.930397) (xy 134.008669 86.937934)
-				(xy 134.061416 86.96923) (xy 134.092686 87.0005) (xy 134.206814 87.066392) (xy 134.334108 87.1005)
-				(xy 134.33411 87.1005) (xy 134.46589 87.1005) (xy 134.465892 87.1005) (xy 134.593186 87.066392)
-				(xy 134.707314 87.0005) (xy 134.8005 86.907314) (xy 134.866392 86.793186) (xy 134.9005 86.665892)
-				(xy 134.9005 86.629016) (xy 134.905522 86.594083) (xy 134.910164 86.578274) (xy 134.912934 86.568838)
-				(xy 134.944228 86.516096) (xy 135.882418 85.577905) (xy 135.882425 85.577901) (xy 135.899861 85.560465)
-				(xy 135.899862 85.560465) (xy 135.960465 85.499862) (xy 135.978617 85.468422) (xy 136.003316 85.425642)
-				(xy 136.003317 85.425639) (xy 136.003318 85.425638) (xy 136.011077 85.396682) (xy 136.025501 85.342853)
-				(xy 136.025501 85.257147) (xy 136.025501 85.249552) (xy 136.025501 85.247868) (xy 136.0255 85.247794)
-				(xy 136.0255 83.857149) (xy 136.0255 83.857147) (xy 136.003318 83.774362) (xy 136.003314 83.774355)
-				(xy 135.960469 83.700144) (xy 135.960463 83.700136) (xy 135.88265 83.622323) (xy 135.871968 83.610142)
-				(xy 135.86215 83.597347) (xy 135.853143 83.583866) (xy 135.813589 83.515357) (xy 135.800502 83.492689)
-				(xy 135.800498 83.492684) (xy 135.707316 83.399502) (xy 135.707314 83.3995) (xy 135.62207 83.350284)
-				(xy 135.593187 83.333608) (xy 135.493188 83.306814) (xy 135.493186 83.306813) (xy 135.465893 83.2995)
-				(xy 135.465892 83.2995) (xy 135.334108 83.2995) (xy 135.206808 83.333609) (xy 135.206806 83.33361)
-				(xy 135.092696 83.399493) (xy 135.092682 83.399502) (xy 134.999498 83.492687) (xy 134.999493 83.492694)
-				(xy 134.998043 83.494849) (xy 134.980741 83.515357) (xy 134.970405 83.525212) (xy 134.908302 83.557227)
-				(xy 134.838749 83.550584) (xy 134.836741 83.549779) (xy 134.834317 83.548809) (xy 134.780106 83.506585)
-				(xy 134.756694 83.474361) (xy 134.75669 83.474358) (xy 134.748569 83.463181) (xy 134.71569 83.417926)
-				(xy 134.282074 82.98431) (xy 134.129199 82.87324) (xy 134.101858 82.859309) (xy 133.960836 82.787454)
-				(xy 133.781118 82.729059) (xy 133.781112 82.729058) (xy 133.594491 82.6995) (xy 133.594486 82.6995)
-				(xy 133.594481 82.6995) (xy 133.405519 82.6995) (xy 133.405514 82.6995) (xy 133.405509 82.6995)
-				(xy 133.218886 82.729058) (xy 133.218884 82.729058) (xy 133.218881 82.729059) (xy 133.039163 82.787454)
-				(xy 133.01006 82.802283) (xy 132.870799 82.87324) (xy 132.717916 82.984317) (xy 132.651659 83.050575)
-				(xy 132.623407 83.071725) (xy 132.600322 83.084331) (xy 132.540893 83.0995) (xy 131.29483 83.0995)
-				(xy 131.259889 83.094475) (xy 131.24489 83.09007) (xy 131.186144 83.052327) (xy 131.184957 83.050958)
-				(xy 131.157477 82.996083) (xy 131.155832 82.988521) (xy 131.153 82.962168) (xy 131.153 80.980063)
-				(xy 131.152569 80.978081) (xy 131.141368 80.92177) (xy 131.138475 80.916358) (xy 131.134581 80.911613)
-				(xy 131.109137 80.873534) (xy 131.097052 80.855447) (xy 131.044279 80.820185) (xy 131.040885 80.817917)
-				(xy 131.036141 80.814024) (xy 131.03073 80.811132) (xy 131.030729 80.811131) (xy 130.972252 80.7995)
-				(xy 130.97225 80.7995) (xy 129.438019 80.7995) (xy 129.403084 80.794477) (xy 129.39223 80.79129)
-				(xy 129.333452 80.753516) (xy 129.304427 80.68996) (xy 129.314367 80.620811) (xy 129.317557 80.613824)
-				(xy 129.342666 80.57766) (xy 131.420827 78.4995) (xy 133.497161 76.423165) (xy 133.525412 76.402016)
-				(xy 133.530778 76.399086) (xy 133.599051 76.384238)
+				(xy 128.672902 86.766518) (xy 128.6495 86.853856) (xy 128.6495 87.346149) (xy 128.651566 87.353857)
+				(xy 128.668039 87.415334) (xy 128.668051 87.415378) (xy 128.668197 87.415918) (xy 128.673386 87.43529)
+				(xy 128.721253 87.518199) (xy 128.72952 87.535477) (xy 128.741155 87.565567) (xy 128.7495 87.610288)
+				(xy 128.7495 88.785625) (xy 128.744476 88.820562) (xy 128.737064 88.845803) (xy 128.705769 88.898547)
+				(xy 128.553743 89.050573) (xy 128.525492 89.071722) (xy 128.502404 89.08433) (xy 128.442974 89.0995)
+				(xy 128.334108 89.0995) (xy 128.206808 89.133609) (xy 128.206806 89.13361) (xy 128.092696 89.199493)
+				(xy 128.092682 89.199502) (xy 128.092677 89.199506) (xy 127.999506 89.292677) (xy 127.999502 89.292682)
+				(xy 127.999493 89.292696) (xy 127.93361 89.406806) (xy 127.933609 89.406808) (xy 127.8995 89.534108)
+				(xy 127.8995 89.635626) (xy 127.894476 89.670563) (xy 127.887064 89.695804) (xy 127.855769 89.748548)
+				(xy 125.803744 91.800573) (xy 125.775493 91.821722) (xy 125.752405 91.83433) (xy 125.692975 91.8495)
+				(xy 113.228019 91.8495) (xy 113.193084 91.844477) (xy 113.18223 91.84129) (xy 113.123452 91.803516)
+				(xy 113.094427 91.73996) (xy 113.104367 91.670811) (xy 113.107557 91.663824) (xy 113.132666 91.62766)
+				(xy 115.956872 88.803455) (xy 118.860465 85.899862) (xy 118.883654 85.859696) (xy 118.885876 85.857577)
+				(xy 118.90822 85.81887) (xy 118.908222 85.818871) (xy 118.926391 85.787397) (xy 118.9461 85.761713)
+				(xy 118.951317 85.756497) (xy 118.961562 85.746252) (xy 119.0005 85.707314) (xy 119.066392 85.593186)
+				(xy 119.1005 85.465892) (xy 119.1005 85.334108) (xy 119.066392 85.206814) (xy 119.0005 85.092686)
+				(xy 118.907314 84.9995) (xy 118.793186 84.933608) (xy 118.706554 84.910395) (xy 118.674111 84.896501)
+				(xy 118.662258 84.889276) (xy 118.615262 84.837575) (xy 118.603677 84.768687) (xy 118.603871 84.767065)
+				(xy 118.624304 84.712281) (xy 118.62712 84.70812) (xy 118.642115 84.68996) (xy 122.482657 80.84942)
+				(xy 122.510902 80.828276) (xy 122.520876 80.82283) (xy 122.522254 80.821895) (xy 122.522576 80.821791)
+				(xy 122.523436 80.821267) (xy 122.523562 80.821474) (xy 122.588779 80.800539) (xy 122.591883 80.8005)
+				(xy 122.762384 80.8005)
 			)
 		)
 		(filled_polygon
@@ -40995,6 +41021,44 @@ 				(xy 109.028634 60.75221) (xy 109.049161 60.7505) (xy 109.544205 60.7505)
 			)
 		)
+		(filled_polygon
+			(layer "B.Cu")
+			(pts
+				(xy 133.664514 76.408658) (xy 133.706383 76.464594) (xy 133.711364 76.534286) (xy 133.711361 76.534297)
+				(xy 133.710359 76.5389) (xy 133.696755 76.574085) (xy 133.689678 76.600499) (xy 133.684579 76.619531)
+				(xy 133.680299 76.635504) (xy 133.6745 76.657144) (xy 133.6745 80.042849) (xy 133.674499 80.042849)
+				(xy 133.6745 80.042852) (xy 133.6745 80.042853) (xy 133.682942 80.07436) (xy 133.696682 80.12564)
+				(xy 133.709744 80.148262) (xy 133.709745 80.148266) (xy 133.709746 80.148266) (xy 133.739535 80.199862)
+				(xy 133.739537 80.199864) (xy 137.822492 84.282819) (xy 137.855977 84.344142) (xy 137.850993 84.413834)
+				(xy 137.809121 84.469767) (xy 137.743657 84.494184) (xy 137.734811 84.4945) (xy 136.1495 84.4945)
+				(xy 136.082461 84.474815) (xy 136.036706 84.422011) (xy 136.0255 84.3705) (xy 136.0255 83.857149)
+				(xy 136.0255 83.857147) (xy 136.003318 83.774362) (xy 136.003314 83.774355) (xy 135.960469 83.700144)
+				(xy 135.960463 83.700136) (xy 135.88265 83.622323) (xy 135.871968 83.610142) (xy 135.86215 83.597347)
+				(xy 135.853143 83.583866) (xy 135.813589 83.515357) (xy 135.800502 83.492689) (xy 135.800498 83.492684)
+				(xy 135.707316 83.399502) (xy 135.707314 83.3995) (xy 135.62207 83.350284) (xy 135.593187 83.333608)
+				(xy 135.493188 83.306814) (xy 135.493186 83.306813) (xy 135.465893 83.2995) (xy 135.465892 83.2995)
+				(xy 135.334108 83.2995) (xy 135.206808 83.333609) (xy 135.206806 83.33361) (xy 135.092696 83.399493)
+				(xy 135.092682 83.399502) (xy 134.999498 83.492687) (xy 134.999493 83.492694) (xy 134.998043 83.494849)
+				(xy 134.980741 83.515357) (xy 134.970405 83.525212) (xy 134.908302 83.557227) (xy 134.838749 83.550584)
+				(xy 134.836741 83.549779) (xy 134.834317 83.548809) (xy 134.780106 83.506585) (xy 134.756694 83.474361)
+				(xy 134.75669 83.474358) (xy 134.748569 83.463181) (xy 134.71569 83.417926) (xy 134.282074 82.98431)
+				(xy 134.129199 82.87324) (xy 134.101858 82.859309) (xy 133.960836 82.787454) (xy 133.781118 82.729059)
+				(xy 133.781112 82.729058) (xy 133.594491 82.6995) (xy 133.594486 82.6995) (xy 133.594481 82.6995)
+				(xy 133.405519 82.6995) (xy 133.405514 82.6995) (xy 133.405509 82.6995) (xy 133.218886 82.729058)
+				(xy 133.218884 82.729058) (xy 133.218881 82.729059) (xy 133.039163 82.787454) (xy 133.01006 82.802283)
+				(xy 132.870799 82.87324) (xy 132.717916 82.984317) (xy 132.651659 83.050575) (xy 132.623407 83.071725)
+				(xy 132.600322 83.084331) (xy 132.540893 83.0995) (xy 131.29483 83.0995) (xy 131.259889 83.094475)
+				(xy 131.24489 83.09007) (xy 131.186144 83.052327) (xy 131.184957 83.050958) (xy 131.157477 82.996083)
+				(xy 131.155832 82.988521) (xy 131.153 82.962168) (xy 131.153 80.980063) (xy 131.152569 80.978081)
+				(xy 131.141368 80.92177) (xy 131.138475 80.916358) (xy 131.134581 80.911613) (xy 131.109137 80.873534)
+				(xy 131.097052 80.855447) (xy 131.055138 80.827441) (xy 131.040885 80.817917) (xy 131.036141 80.814024)
+				(xy 131.03073 80.811132) (xy 131.030729 80.811131) (xy 130.972252 80.7995) (xy 130.97225 80.7995)
+				(xy 129.438019 80.7995) (xy 129.403084 80.794477) (xy 129.39223 80.79129) (xy 129.333452 80.753516)
+				(xy 129.304427 80.68996) (xy 129.314367 80.620811) (xy 129.317557 80.613824) (xy 129.342666 80.57766)
+				(xy 131.420827 78.4995) (xy 133.497161 76.423165) (xy 133.525412 76.402016) (xy 133.530778 76.399086)
+				(xy 133.599051 76.384238)
+			)
+		)
 		(filled_polygon
 			(layer "B.Cu")
 			(pts
@@ -42135,26 +42199,26 @@ 				(xy 98.342862 100.699556) (xy 98.339525 100.692194) (xy 98.336412 100.684679) (xy 98.333573 100.677138)
 				(xy 98.309844 100.607234) (xy 98.304923 100.587582) (xy 98.301656 100.56779) (xy 98.3 100.547595)
 				(xy 98.3 85.234106) (xy 100.8995 85.234106) (xy 100.8995 85.365891) (xy 100.911934 85.412297) (xy 100.91194 85.412318)
-				(xy 100.933608 85.493188) (xy 100.937458 85.499855) (xy 100.943654 85.510586) (xy 100.943656 85.510593)
+				(xy 100.933608 85.493188) (xy 100.937829 85.500497) (xy 100.943654 85.510586) (xy 100.943656 85.510593)
 				(xy 100.943658 85.510593) (xy 100.9995 85.607314) (xy 101.092686 85.7005) (xy 101.15019 85.7337)
 				(xy 101.177927 85.755512) (xy 101.187508 85.76556) (xy 101.219523 85.827661) (xy 101.212885 85.897207)
 				(xy 101.212281 85.898716) (xy 101.17836 85.946352) (xy 101.174571 85.949635) (xy 101.155393 85.963294)
 				(xy 101.150047 85.966382) (xy 101.150025 85.966395) (xy 101.092691 85.999496) (xy 101.092682 85.999502)
 				(xy 101.092677 85.999506) (xy 100.999506 86.092677) (xy 100.999502 86.092682) (xy 100.999493 86.092696)
 				(xy 100.93361 86.206806) (xy 100.933609 86.206809) (xy 100.933608 86.206811) (xy 100.933608 86.206812)
-				(xy 100.906813 86.306814) (xy 100.8995 86.334108) (xy 100.8995 86.465892) (xy 100.914939 86.52351)
-				(xy 100.916554 86.529539) (xy 100.933608 86.593188) (xy 100.934125 86.594083) (xy 100.943997 86.61118)
-				(xy 100.943999 86.611187) (xy 100.944001 86.611187) (xy 100.9995 86.707314) (xy 101.092686 86.8005)
-				(xy 101.206814 86.866392) (xy 101.334108 86.9005) (xy 101.370981 86.9005) (xy 101.405913 86.905521)
-				(xy 101.431157 86.912934) (xy 101.483903 86.94423) (xy 106.39375 91.854077) (xy 106.427235 91.9154)
-				(xy 106.422251 91.985092) (xy 106.387283 92.035461) (xy 106.385113 92.037341) (xy 106.38511 92.037344)
-				(xy 106.36716 92.054931) (xy 106.367154 92.054938) (xy 106.322511 92.134748) (xy 106.322509 92.134753)
-				(xy 106.302826 92.201785) (xy 106.302825 92.20179) (xy 106.302825 92.201791) (xy 106.298147 92.234329)
-				(xy 106.2945 92.259692) (xy 106.2945 94.876007) (xy 106.299197 94.919686) (xy 106.310397 94.971174)
-				(xy 106.31289 94.981372) (xy 106.312891 94.981375) (xy 106.355899 95.062083) (xy 106.355901 95.062086)
-				(xy 106.40166 95.114895) (xy 106.419242 95.132839) (xy 106.419246 95.132843) (xy 106.419247 95.132844)
-				(xy 106.419249 95.132845) (xy 106.499059 95.177488) (xy 106.499063 95.17749) (xy 106.566102 95.197175)
-				(xy 106.624 95.2055) (xy 114.375991 95.2055) (xy 114.376 95.2055) (xy 114.419684 95.200803) (xy 114.448875 95.194452)
+				(xy 100.906813 86.306814) (xy 100.90588 86.310297) (xy 100.8995 86.334107) (xy 100.8995 86.465893)
+				(xy 100.916442 86.529125) (xy 100.916443 86.529125) (xy 100.916443 86.529126) (xy 100.933608 86.593187)
+				(xy 100.966554 86.65025) (xy 100.9995 86.707314) (xy 101.092686 86.8005) (xy 101.206814 86.866392)
+				(xy 101.334108 86.9005) (xy 101.370981 86.9005) (xy 101.405913 86.905521) (xy 101.431157 86.912934)
+				(xy 101.483903 86.94423) (xy 106.39375 91.854077) (xy 106.427235 91.9154) (xy 106.422251 91.985092)
+				(xy 106.387283 92.035461) (xy 106.385113 92.037341) (xy 106.38511 92.037344) (xy 106.36716 92.054931)
+				(xy 106.367154 92.054938) (xy 106.322511 92.134748) (xy 106.322509 92.134753) (xy 106.302826 92.201785)
+				(xy 106.302825 92.20179) (xy 106.302825 92.201791) (xy 106.298147 92.234329) (xy 106.2945 92.259692)
+				(xy 106.2945 94.876007) (xy 106.299197 94.919686) (xy 106.310397 94.971174) (xy 106.31289 94.981372)
+				(xy 106.312891 94.981375) (xy 106.355899 95.062083) (xy 106.355901 95.062086) (xy 106.40166 95.114895)
+				(xy 106.419242 95.132839) (xy 106.419246 95.132843) (xy 106.419247 95.132844) (xy 106.419249 95.132845)
+				(xy 106.499059 95.177488) (xy 106.499063 95.17749) (xy 106.566102 95.197175) (xy 106.624 95.2055)
+				(xy 114.375991 95.2055) (xy 114.376 95.2055) (xy 114.419684 95.200803) (xy 114.448875 95.194452)
 				(xy 114.471174 95.189602) (xy 114.47119 95.189598) (xy 114.471195 95.189597) (xy 114.481373 95.18711)
 				(xy 114.562085 95.1441) (xy 114.614889 95.098345) (xy 114.632843 95.080754) (xy 114.67749 95.000937)
 				(xy 114.697175 94.933898) (xy 114.7055 94.876) (xy 114.7055 92.6745) (xy 114.725185 92.607461) (xy 114.777989 92.561706)
@@ -42289,7 +42353,7 @@ 				(xy 147.10775 85.196682) (xy 147.133608 85.293187) (xy 147.162282 85.342851) (xy 147.162283 85.342853)
 				(xy 147.162284 85.342853) (xy 147.1995 85.407314) (xy 147.292686 85.5005) (xy 147.292688 85.500501)
 				(xy 147.292692 85.500504) (xy 147.36139 85.540167) (xy 147.361393 85.540168) (xy 147.361395 85.540169)
-				(xy 147.396548 85.560465) (xy 147.405928 85.565881) (xy 147.40681 85.56639) (xy 147.406811 85.56639)
+				(xy 147.397644 85.561098) (xy 147.405928 85.565881) (xy 147.40681 85.56639) (xy 147.406811 85.56639)
 				(xy 147.406814 85.566392) (xy 147.534108 85.6005) (xy 147.53411 85.6005) (xy 147.66589 85.6005)
 				(xy 147.665892 85.6005) (xy 147.793186 85.566392) (xy 147.907314 85.5005) (xy 148.0005 85.407314)
 				(xy 148.066392 85.293186) (xy 148.1005 85.165892) (xy 148.1005 85.034108) (xy 148.066392 84.906814)
@@ -42356,15 +42420,16 @@ 				(xy 153.593187 85.733608) (xy 153.529539 85.716554) (xy 153.465892 85.6995) (xy 153.465891 85.6995)
 				(xy 153.334108 85.6995) (xy 153.206808 85.733609) (xy 153.206806 85.73361) (xy 153.092696 85.799493)
 				(xy 153.092682 85.799502) (xy 153.092677 85.799506) (xy 152.999506 85.892677) (xy 152.999502 85.892682)
-				(xy 152.999493 85.892696) (xy 152.933607 86.00681) (xy 152.933607 86.006811) (xy 152.9106 86.092681)
-				(xy 152.9106 86.092682) (xy 152.8995 86.134107) (xy 152.8995 86.265891) (xy 152.908504 86.299499)
+				(xy 152.999493 85.892696) (xy 152.93361 86.006806) (xy 152.933609 86.006809) (xy 152.933608 86.006811)
+				(xy 152.933608 86.006812) (xy 152.930778 86.017375) (xy 152.908805 86.099377) (xy 152.908805 86.09938)
+				(xy 152.8995 86.134108) (xy 152.8995 86.134111) (xy 152.8995 86.265893) (xy 152.908504 86.299499)
 				(xy 152.908505 86.299499) (xy 152.908505 86.2995) (xy 152.933608 86.393187) (xy 152.948818 86.419531)
-				(xy 152.9995 86.507314) (xy 153.092686 86.6005) (xy 153.142077 86.629016) (xy 153.182965 86.652623)
+				(xy 152.9995 86.507314) (xy 153.092686 86.6005) (xy 153.156145 86.637138) (xy 153.182965 86.652623)
 				(xy 153.182966 86.652623) (xy 153.182969 86.652625) (xy 153.20681 86.66639) (xy 153.206814 86.666392)
 				(xy 153.334108 86.7005) (xy 153.33411 86.7005) (xy 153.46589 86.7005) (xy 153.465892 86.7005) (xy 153.593186 86.666392)
 				(xy 153.707314 86.6005) (xy 153.708387 86.599426) (xy 153.73664 86.578275) (xy 153.759322 86.565891)
 				(xy 153.759732 86.565667) (xy 153.819157 86.5505) (xy 154.573494 86.5505) (xy 154.608426 86.555521)
-				(xy 154.63367 86.562934) (xy 154.686416 86.59423) (xy 154.692686 86.6005) (xy 154.742077 86.629016)
+				(xy 154.63367 86.562934) (xy 154.686416 86.59423) (xy 154.692686 86.6005) (xy 154.756145 86.637138)
 				(xy 154.782965 86.652623) (xy 154.782966 86.652623) (xy 154.782969 86.652625) (xy 154.80681 86.66639)
 				(xy 154.806814 86.666392) (xy 154.934108 86.7005) (xy 154.93411 86.7005) (xy 155.065889 86.7005)
 				(xy 155.06589 86.7005) (xy 155.065892 86.7005) (xy 155.153573 86.677006) (xy 155.157326 86.677095)
tests/protocol_peer.cadded+17
@@ -0,0 +1,17 @@+/* HOST-ONLY SIMULATED INSTRUMENT for transport interoperability tests.+ * No GPIO, USB peripheral, DAC or physical hardware is accessed. */+#include "../firmware/load_protocol.h"+#include <stdio.h>+static unsigned stopped;+static void hard_off(void *ctx){(*(unsigned *)ctx)++;}+int main(void){+ load_core c;load_protocol p;load_init(&c,0);+ if(!load_protocol_init(&p,&c,"LOAD-HOST-SIM","simboot01",hard_off,&stopped))return 1;+ char response[LOAD_REPLY_BYTES];int byte;+ load_sample s={.now_ms=0,.sample_ms=0,.last_tach_ms=0,.volts=5,.amps=0,.case_c=25,.fan_rpm=7000,.fan_rpm_valid=true,.sensors_valid=true,.hw_ok=true,.assembly_qualified=false};+ while((byte=getchar())!=EOF){+  size_t n=load_protocol_byte(&p,(unsigned char)byte,&s,response,sizeof(response));+  if(n){if(fwrite(response,1,n,stdout)!=n)return 2;fflush(stdout);}+ }+ load_protocol_disconnect(&p);return 0;+}
tests/test_load_protocol.cadded+49
@@ -0,0 +1,49 @@+#include "../firmware/load_protocol.h"+#include <assert.h>+#include <stdio.h>+#include <string.h>+static unsigned stops;+static void off(void *context){(*(unsigned *)context)++;}+static load_core c;static load_protocol p;static char reply[LOAD_REPLY_BYTES];+static load_sample sample(uint32_t t){return (load_sample){.now_ms=t,.sample_ms=t,.last_tach_ms=t,.fan_rpm=7000,.fan_rpm_valid=true,.volts=5,.case_c=25,.sensors_valid=true,.hw_ok=true,.assembly_qualified=true};}+static void init(void){load_init(&c,0);assert(load_protocol_init(&p,&c,"LOAD-TEST","boot0001",off,&stops));}+static size_t send(const char *q,uint32_t t){load_sample s=sample(t);size_t n=0;for(size_t i=0;q[i];i++)n=load_protocol_byte(&p,(unsigned char)q[i],&s,reply,sizeof(reply));return n;}+#define OWNER "0123456789abcdef01234567"+static void command(unsigned id,const char *cmd,const char *extra,uint32_t t){char q[256];snprintf(q,sizeof(q),"{\"id\":%u,\"cmd\":\"%s\",\"boot\":\"boot0001\",\"owner\":\"" OWNER "\"%s}\n",id,cmd,extra);assert(send(q,t)>0);}+static void ok(void){assert(strstr(reply,"\"ok\":true"));}+static void fault(void){assert(c.state==LOAD_FAULT&&!c.enable&&c.dac_code==0);}+int main(void){+ init();assert(send("{\"id\":1,\"cmd\":\"identify\"}\n",0));ok();+ command(2,"claim","",0);ok();command(3,"off","",0);ok();command(4,"status","",0);ok();+ command(5,"set_current",",\"amps\":0.5",0);ok();assert(c.requested_a==.5f);+ command(6,"arm","",0);ok();assert(c.state==LOAD_ARMED&&!c.enable);+ load_sample s=sample(10);s.latch_q=true;load_tick(&c,&s);assert(c.enable);+ unsigned before=stops;command(6,"arm","",10);assert(strstr(reply,"stale_sequence"));fault();assert(stops>before);+ init();command(2,"claim","",0);command(3,"set_current",",\"amps\":0.5",0);command(4,"arm","",0);+ s=sample(1001);load_protocol_poll(&p,&s);fault();assert(!p.owned);+ /* A delayed claim cannot restart an expired session with an old sequence. */+ command(2,"claim","",1002);assert(strstr(reply,"stale_sequence"));fault();assert(!p.owned);+ command(20,"claim","",1002);ok();assert(p.owned&&!c.enable&&c.state==LOAD_FAULT);+ /* Stale authorized OFF must still immediately deassert the hardware output. */+ init();command(2,"claim","",0);command(3,"arm","",0);s=sample(10);s.latch_q=true;load_tick(&c,&s);before=stops;command(1,"off","",10);fault();assert(stops>before);+ const char *bad[]={"{}\n","{\"id\":01,\"cmd\":\"identify\"}\n","{\"id\":1,\"id\":2,\"cmd\":\"identify\"}\n","{\"id\":1,\"cmd\":\"identify\",\"x\":0}\n","{\"id\":1,\"cmd\":\"identify\",}\n","{\"id\":1,\"cmd\":\"ident\\u0069fy\"}\n","{\"id\":4294967296,\"cmd\":\"identify\"}\n","{\"id\":1e2,\"cmd\":\"identify\"}\n"};+ for(unsigned i=0;i<sizeof(bad)/sizeof(bad[0]);i++){init();assert(send(bad[i],0));fault();assert(strstr(reply,"\"ok\":false"));}+ init();command(2,"claim","",0);command(3,"set_current",",\"amps\":1e99",0);fault();+ init();s=sample(0);for(unsigned i=0;i<260;i++)assert(!load_protocol_byte(&p,' ',&s,reply,sizeof(reply)));fault();assert(load_protocol_byte(&p,'\n',&s,reply,sizeof(reply)));assert(strstr(reply,"invalid_frame"));+ init();assert(!send("{",UINT32_MAX-50));s=sample(60);load_protocol_poll(&p,&s);fault();assert(p.dropping);+ init();command(2,"claim","",0);load_protocol_disconnect(&p);fault();assert(!p.owned);+ init();command(2,"claim","",0);command(3,"clear_fault","",0);fault(); /* invalid clear cannot enable */+ /* Unknown feedback serializes as null, not a fabricated zero RPM. */+ init();s=sample(0);s.fan_rpm_valid=false;const char *q="{\"id\":1,\"cmd\":\"identify\"}\n";for(size_t i=0;q[i];i++)load_protocol_byte(&p,q[i],&s,reply,sizeof(reply));assert(strstr(reply,"\"rpm\":null,\"rpm_valid\":false"));+ /* All truncated prefixes must be bounded and must not execute a command. */+ q="{\"id\":1,\"cmd\":\"identify\"}";+ for(size_t n=0;n<strlen(q);n++){init();s=sample(0);for(size_t i=0;i<n;i++)load_protocol_byte(&p,q[i],&s,reply,sizeof(reply));load_protocol_byte(&p,'\n',&s,reply,sizeof(reply));fault();}+ /* A serial field can never supply trusted assembly qualification. */+ init();command(2,"claim","",0);command(3,"arm",",\"assembly_qualified\":true",0);fault();+ init();command(2,"claim","",0);before=stops;+ assert(send("{\"id\":3,\"cmd\":\"arm\",\"boot\":\"boot9999\",\"owner\":\"" OWNER "\"}\n",0));fault();assert(stops>before);+ /* Bounded deterministic byte fuzz under ASan/UBSan, including 0xff and NUL. */+ uint32_t seed=0x12345678u;init();s=sample(0);+ for(unsigned i=0;i<100000;i++){seed=seed*1664525u+1013904223u;load_protocol_byte(&p,(unsigned char)(seed>>24),&s,reply,sizeof(reply));if(i%257==0)load_protocol_byte(&p,'\n',&s,reply,sizeof(reply));}+ puts("load_protocol: host safety/framing tests passed; no MCU or USB validation");+}
tests/test_protocol_interop.pyadded+53
@@ -0,0 +1,53 @@+"""Existing host transport talks to compiled C protocol over a local process.+Explicit simulation: no serial device, USB peripheral or hardware is opened.+"""+import os+from pathlib import Path+import subprocess+import sys+import unittest+sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'control-panel'))+from transport import SerialLoad, LinkError++class ProcessWire:+    def __init__(self, binary):+        self.process = subprocess.Popen([binary], stdin=subprocess.PIPE, stdout=subprocess.PIPE)+    def write(self, data):+        self.process.stdin.write(data)+        self.process.stdin.flush()+        return len(data)+    def read_until(self, terminator, size):+        return self.process.stdout.readline(size)+    def close(self):+        self.process.stdin.close()+        self.process.wait(timeout=5)+        self.process.stdout.close()+        if self.process.returncode:+            raise RuntimeError('C protocol peer failed: ' + str(self.process.returncode))+[email protected](os.getenv('LOAD_PROTOCOL_PEER'), 'Build host C peer and set LOAD_PROTOCOL_PEER')+class ProtocolInterop(unittest.TestCase):+    def test_real_host_to_c_protocol(self):+        wire = ProcessWire(os.environ['LOAD_PROTOCOL_PEER'])+        try:+            device = SerialLoad(wire, 'LOAD-HOST-SIM')+            status = device.connect()+            self.assertEqual(status['state'], 'OFF')+            self.assertFalse(status['enabled'])+            self.assertEqual(status['rpm'], 7000)+            device.request('set_current', amps=0.4)+            self.assertAlmostEqual(device.status()['requested_a'], 0.4)+            with self.assertRaisesRegex(LinkError, 'arm_interlock'):+                device.request('arm')  # Unqualified assembly must remain disabled.+            self.assertEqual(device.status()['state'], 'FAULT')+            self.assertFalse(device.status()['enabled'])+            device.request('heartbeat')+            device.request('clear_fault')+            self.assertEqual(device.status()['state'], 'OFF')+            # Same process, new host object: identify supplies the boot-wide floor.+            reconnect = SerialLoad(wire, 'LOAD-HOST-SIM')+            reconnect.owner = device.owner+            self.assertFalse(reconnect.connect()['enabled'])+            self.assertGreater(reconnect.seq, device.seq)+        finally:+            wire.close()
tests/test_transport.py+11−1
@@ -17,7 +17,7 @@ class FakeWire:     def read_until(self, terminator, size):         q = self.sent[-1]         d = dict(id=q["id"], ok=True, protocol="adom.load/1", serial="LOAD-TEST",-                 boot="boot0001", enabled=False, state="OFF", dac_code=0,+                 boot="boot0001", last_sequence=0, enabled=False, state="OFF", dac_code=0,                  volts=5, amps=0, case_c=25, requested_a=0, applied_a=0,                  power_limit_w=5, sample_age_ms=10, rpm=7000, rpm_valid=True)         return json.dumps(self.change(d)).encode() + b"\n"@@ -37,6 +37,16 @@ class TransportTests(unittest.TestCase):         self.load.serial = "ANOTHER"         with self.assertRaises(LinkError): self.load.connect()         self.assertEqual(len(self.wire.sent), 1)+    def test_reconnect_advances_past_device_sequence(self):+        self.wire.change = lambda d: dict(d, last_sequence=500)+        self.load.connect()+        self.assertEqual([q["id"] for q in self.wire.sent], [1, 501, 502, 503])+    def test_invalid_sequence_floor_prevents_claim(self):+        for value in (None, True, -1, 0xFFFFFFFF):+            self.setUp()+            self.wire.change = lambda d: dict(d, last_sequence=value)+            with self.assertRaises(LinkError): self.load.connect()+            self.assertEqual(len(self.wire.sent), 1)     def test_stale_reply_not_retried(self):         self.wire.change = lambda d: dict(d, id=0)         with self.assertRaises(LinkError): self.load.request("arm")
tools/audit_mcu_pinmap.pyadded+16
@@ -0,0 +1,16 @@+"""Bind the routed MCU pins to the selected package and peripheral assignments.+Source: ST DS13560 Rev 6, LQFP32 GP pinout and alternate-function tables.+This does not validate option bytes, register setup, electrical limits or hardware.+"""+from pathlib import Path+import json,sys+import sexp+p=Path(__file__).resolve().parents[1]+map_by_pin={6:('PF2','NRST','reset'),7:('PA0','TEMP_SENSE','ADC_IN0'),8:('PA1','DUT_ADC','ADC_IN1'),9:('PA2','UART_TX','USART2_TX AF1'),10:('PA3','UART_RX','USART2_RX AF1'),11:('PA4','ARM_CLK','GPIO output, initialize LOW'),12:('PA5','MCU_ENABLE','GPIO output, initialize LOW'),13:('PA6','WDI','GPIO output, healthy-cycle toggle only'),14:('PA7','FAN_TACH','GPIO EXTI7 rising; timestamp from 32-bit timer'),15:('PB0','USB_DETECT','GPIO input'),17:('PB2','HW_OK','GPIO input'),20:('PC6','ALERT','GPIO input'),22:('PA11','USB_DM','USB_DM, default pin remap'),23:('PA12','USB_DP','USB_DP, default pin remap'),24:('PA13','SWDIO','SWDIO AF0'),25:('PA14','SWCLK','SWCLK AF0'),26:('PA15','LATCH_Q','GPIO input'),27:('PB3','FAULT_CLEAR','GPIO output; polarity requires latch-chain audit'),30:('PB6','I2C_SCL','I2C1_SCL AF6 open-drain'),31:('PB7','I2C_SDA','I2C1_SDA AF6 open-drain')}+n=json.load(open(p/'design/netlist.json'));c=next(c for c in n['components']if c['ref']=='U1');assert c['mpn']=='STM32G0B1KCT6'+b=sexp.parse((p/'kicad/adom-custom-electronic-load.kicad_pcb').read_text());f=next(f for f in sexp.find(b,'footprint')if any(v[1:3]==['Reference','U1']for v in sexp.find(f,'property')));boardpins={x[1]:(sexp.first(x,'net')[-1]if sexp.first(x,'net')else None)for x in sexp.find(f,'pad')};rows=[]+for pin,(gpio,net,function)in map_by_pin.items():+ assert c['pins'][str(pin)]==net and boardpins[str(pin)]==net,(pin,net)+ rows.append({'packagePin':pin,'gpio':gpio,'net':net,'plannedFunction':function})+for pin,net in [(4,'3V3'),(5,'GND')]:assert c['pins'][str(pin)]==net and boardpins[str(pin)]==net+out={'part':c['mpn'],'package':'LQFP32 GP, not the N-suffix variant','source':'https://www.st.com/resource/en/datasheet/stm32g0b1kc.pdf','sourceRevision':'DS13560 Rev 6','matchedSignalPins':len(rows),'matchedSupplyPins':2,'pins':rows,'limits':['Static assignment audit only; HAL and option-byte validation pending','USB clock recovery, VBUS detection and self-powered descriptors pending','Tach EXTI timestamps require a 32-bit microsecond timebase and measured ISR latency','Boot identity must be unique on every reset; no fixed production boot nonce']};(p/'design/mcu-pinmap.json').write_text(json.dumps(out,indent=2)+'\n');print('MCU map: 20 signal pins and 2 supply pins agree between design netlist, PCB and selected package assignments.')
tools/check_firmware.shadded+15
@@ -0,0 +1,15 @@+#!/usr/bin/env bash+set -euo pipefail+cd "$(dirname "$0")/.."+load_check_dir=$(mktemp -d)+trap 'rm -rf "$load_check_dir"' EXIT+cc_args=(-std=c11 -Wall -Wextra -Werror -fsanitize=address,undefined -g)+gcc "${cc_args[@]}" firmware/load_core.c tests/test_load_core.c -lm -o "$load_check_dir/core"+"$load_check_dir/core"+gcc "${cc_args[@]}" firmware/fan_tach.c tests/test_fan_tach.c -lm -o "$load_check_dir/tach"+"$load_check_dir/tach"+gcc "${cc_args[@]}" firmware/load_core.c firmware/load_protocol.c tests/test_load_protocol.c -lm -o "$load_check_dir/protocol"+"$load_check_dir/protocol"+gcc "${cc_args[@]}" -c firmware/instruments.c -o "$load_check_dir/instruments.o"+gcc "${cc_args[@]}" firmware/load_core.c firmware/load_protocol.c tests/protocol_peer.c -lm -o "$load_check_dir/peer"+LOAD_PROTOCOL_PEER="$load_check_dir/peer" python3 -m unittest discover -s tests -p 'test_*.py'
tools/diode_via_candidates.pyadded+28
@@ -0,0 +1,28 @@+"""Propose vias inside actual two-face LOAD_POS fill, excluding pads/drills.+Candidate only; native refill/DRC and ground connectivity acceptance required.+"""+from pathlib import Path+import sys,json,math+from shapely.geometry import Polygon,Point,box+from shapely.ops import unary_union,polylabel+from shapely.affinity import rotate,translate+import sexp+p=Path(__file__).resolve().parents[1];b=sexp.parse((p/'kicad/adom-custom-electronic-load.kicad_pcb').read_text());polys={'F.Cu':[],'B.Cu':[]}+for z in sexp.find(b,'zone'):+ if sexp.first(z,'net')[-1]!='LOAD_POS':continue+ for f in sexp.find(z,'filled_polygon'):+  polys[sexp.first(f,'layer')[-1]].append(Polygon([tuple(map(float,t[1:]))for t in sexp.first(f,'pts')[1:]]).buffer(0))+area=unary_union(polys['F.Cu']).intersection(unary_union(polys['B.Cu'])).buffer(-.31).intersection(Point(110.6,91).buffer(3))+for fp in sexp.find(b,'footprint'):+ at=sexp.first(fp,'at');x,y=map(float,at[1:3]);rot=float(at[3])if len(at)>3 else 0+ for pad in sexp.find(fp,'pad'):+  a=sexp.first(pad,'at');px,py=map(float,a[1:3]);sz=sexp.first(pad,'size');sx,sy=map(float,sz[1:3]);angle=(float(a[3])if len(a)>3 else rot)-rot+  g=rotate(box(-sx/2,-sy/2,sx/2,sy/2),-angle,origin=(0,0));g=translate(g,px,py);g=rotate(g,-rot,origin=(0,0));g=translate(g,x,y);area=area.difference(g.buffer(.5))+for v in sexp.find(b,'via'):+ x,y=map(float,sexp.first(v,'at')[1:3]);dr=float(sexp.first(v,'drill')[1]);area=area.difference(Point(x,y).buffer(dr/2+.15+.4))+selected=[]+for _ in range(6):+ shapes=list(area.geoms)if hasattr(area,'geoms')else[area];shapes=[g for g in shapes if g.geom_type=='Polygon'and g.area>.005]+ if not shapes:break+ pts=[polylabel(g,tolerance=.01)for g in shapes];i=max(range(len(pts)),key=lambda i:pts[i].distance(shapes[i].boundary));pt=pts[i];selected.append([round(pt.x,3),round(pt.y,3)]);area=area.difference(pt.buffer(.75))+r={'net':'LOAD_POS','viaSize':.6,'viaDrill':.3,'points':selected,'candidateOnly':True,'method':'Native filled two-face overlap; conservative pad bounds; 0.4 mm nominal hole gap; within 3 mm of D1 LOAD_POS pad center'};(p/'checkpoints/thermal-r13/diode-via-candidates.json').write_text(json.dumps(r,indent=2));print(json.dumps(r))
tools/land_thermal_r13.pyadded+11
@@ -0,0 +1,11 @@+"""Land the five-via, native-DRC-checked candidate on the exact load board."""+from pathlib import Path+import json,subprocess+p=Path(__file__).resolve().parents[1];out=p/'design/thermal-r13-live';out.mkdir(exist_ok=True);board='C:/Users/john/adom-projects/adom-custom-electronic-load/kicad/adom-custom-electronic-load.kicad_pcb'+def ab(v,a,label):+ d=json.loads(subprocess.check_output(['adom-bridge','--target','AdomLapper','--ai-thread','adom-custom-load',v,json.dumps(a)],text=True));(out/(label+'.json')).write_text(json.dumps(d,indent=2));assert d.get('success')is not False and not d.get('error'),str(d)[:1000];return d+r=json.load(open(p/'checkpoints/thermal-r13-diode/native-drc.json'));assert not r['unconnected_items']and not any(v['severity']=='error'for v in r['violations'])+s=ab('kicad_routing_state',{'filePath':board},'before');plan=json.load(open(p/'checkpoints/thermal-r13/with-diode-plan.json'))+for i,e in enumerate(sorted(plan['nets'],key=lambda e:e['net']!='GND')):+ args={k:v for k,v in e.items()if k in ['net','width','paths','viaSize','viaDrill']};d=ab('kicad_route_net',dict(args,filePath=board,expectedRevision=s['revision'],validate=False),f'via-{i}-{e["net"]}');s=ab('kicad_routing_state',{'filePath':board},f'after-{i}')+ab('kicad_zone_state',{'filePath':board,'refill':True},'refill-request')
tools/thermal_ground_stitch_candidates.pyadded+51
@@ -0,0 +1,51 @@+"""Propose ground stitching from native filled copper. Native DRC remains mandatory."""+import sys,json,math+from pathlib import Path+from shapely.geometry import Polygon,Point+from shapely.ops import polylabel+sys.path.insert(0,str(Path(__file__).parent));from sexp import parse,find,first+b=parse(Path(sys.argv[1]).read_text())+polys=[];layers=[]+for z in find(b,'zone'):+ if first(z,'net') is None or first(z,'net')[-1]!='GND':continue+ for f in find(z,'filled_polygon'):+  poly=Polygon([(float(p[1]),float(p[2])) for p in first(f,'pts')[1:]]).buffer(0)+  polys.append(poly);layers.append(first(f,'layer')[1])+parent=list(range(len(polys)))+def root(i):+ while parent[i]!=i:i=parent[i]+ return i+def join(ids):+ if ids:+  for i in ids[1:]:parent[root(i)]=root(ids[0])+obstacles=[]+for v in find(b,'via'):+ x,y=map(float,first(v,'at')[1:3]);size=float(first(v,'size')[1]);drill=float(first(v,'drill')[1]);p=Point(x,y);obstacles.append(p.buffer(drill/2+.4))+ if first(v,'net')[1]=='GND':join([i for i,g in enumerate(polys) if g.distance(p)<size/2+.001])+for f in find(b,'footprint'):+ at=first(f,'at');x,y=map(float,at[1:3]);a=math.radians(float(at[3]) if len(at)>3 else 0)+ for pad in find(f,'pad'):+  if pad[2] not in ['thru_hole','np_thru_hole']:continue+  atp=first(pad,'at');u,v=map(float,atp[1:3]);p=Point(x+u*math.cos(a)+v*math.sin(a),y-u*math.sin(a)+v*math.cos(a));sz=first(pad,'size');dr=first(pad,'drill');rad=max(float(q) for q in dr[1:] if str(q).replace('.','',1).isdigit())/2+  obstacles.append(p.buffer(rad+.4))+  net=first(pad,'net')+  if net and net[1]=='GND':join([i for i,g in enumerate(polys) if g.distance(p)<min(float(sz[1]),float(sz[2]))/2+.001])+edges=[]+for i,g in enumerate(polys):+ for j,h in enumerate(polys[:i]):+  if layers[i]==layers[j]:continue+  overlap=g.buffer(-.36).intersection(h.buffer(-.36))+  for obs in obstacles:+   if not overlap.is_empty and overlap.intersects(obs):overlap=overlap.difference(obs)+  if overlap.is_empty:continue+  shapes=list(overlap.geoms) if hasattr(overlap,'geoms') else [overlap]+  for s in shapes:+   if s.geom_type!='Polygon' or s.area<.005:continue+   p=polylabel(s,tolerance=.03);score=p.distance(s.boundary)+   edges.append((score,i,j,p.x,p.y))+initial=len({root(i) for i in parent});selected=[]+for score,i,j,x,y in sorted(edges,reverse=True):+ if root(i)==root(j):continue+ join([i,j]);selected.append({'x':round(x,3),'y':round(y,3),'from':i,'to':j,'marginMm':round(score,3)})+report={'initialComponents':initial,'remainingComponents':len({root(i) for i in parent}),'stitches':selected,'polygons':[{'id':i,'layer':layers[i],'area':round(g.area,3),'group':root(i)} for i,g in enumerate(polys)]}+Path(sys.argv[2]).write_text(json.dumps(report,indent=2)+'\n');print(json.dumps(report))
videos/inventory.json+31−1
@@ -1,5 +1,5 @@ {-  "rawTakeCount": 46,+  "rawTakeCount": 51,   "reviewedExcerptCount": 16,   "countingRule": "Unique raw window recording filenames in this project; excludes derivative action/speed exports and reference projects. Raw count includes rejected takes.",   "takes": [@@ -278,6 +278,36 @@       "durationSeconds": 11.566667,       "topic": "Native 3D top, tilt, rotation and bottom review after cooling clearance relocation",       "review": "Full decode passed. Contact sheet shows changing native views; exact edit selection pending. Cooling assembly absent and must be labeled."+    },+    {+      "file": "aiflow/run/window-1514974-20261009-140743.mp4",+      "durationSeconds": 34.066667,+      "topic": "Placement/routing proof review; mostly static, exclude from action edit",+      "review": "Full decode passed; final editorial selection pending. "+    },+    {+      "file": "aiflow/run/window-1514974-20261009-140829.mp4",+      "durationSeconds": 382.8,+      "topic": "Thermal-via planning interval; mostly static, exclude from action edit",+      "review": "Full decode passed; final editorial selection pending. "+    },+    {+      "file": "aiflow/run/window-1514974-20261009-141513.mp4",+      "durationSeconds": 42.266667,+      "topic": "Actual native ground stitch and four thermal-via additions",+      "review": "Full decode passed; final editorial selection pending. Native via-action contact sheet inspected."+    },+    {+      "file": "videos/raw/window-5969372-20261009-142545.mp4",+      "durationSeconds": 14.566667,+      "topic": "First C protocol simulation take, superseded by boot-wide sequence fix",+      "review": "Full decode passed; final editorial selection pending. "+    },+    {+      "file": "videos/raw/window-5969372-20261009-142836.mp4",+      "durationSeconds": 13.833333,+      "topic": "Current compiled C protocol simulation: ARM refusal, stale sequence refusal, explicit fault clear",+      "review": "Full decode passed; final editorial selection pending. "     }   ] }