Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6406142b21 | ||
|
|
7d76a41540 | ||
|
|
c0f7a1ad51 | ||
|
|
06df47c934 | ||
|
|
60c1e279d6 | ||
|
|
8902b25d78 | ||
|
|
ece4ba77e3 | ||
|
|
31a22eba06 | ||
|
|
4f628a4098 | ||
|
|
b9653426e4 |
@@ -13,3 +13,6 @@ add_compile_definitions(
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
set(PROJECT_VER "0.1.0")
|
||||
project(esp32_serial_swiss_army_knife)
|
||||
|
||||
# Narrow mDNS 1.12.0 correctness fix; see tests/mdns_membership/README.md.
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/cmake/mdns_membership.cmake)
|
||||
|
||||
@@ -14,7 +14,9 @@ ESP32-S3 firmware for a secure, multi-transport RS-232 adapter. It operates one
|
||||
|
||||
## Development status
|
||||
|
||||
Hardware characterization, serial/USB/Wi-Fi/HTTPS/SSH and local display/control are implemented and hardware-validated. **Phase 8 role-based users and administration is complete:** 8A–8C were target-hardware validated and the user explicitly signed off tested firmware at **8D.22 (2026-09-13)**. See the [roadmap](docs/roadmap.md#phase-8--role-based-users-and-administrative-access--complete) and [acceptance evidence](docs/web_administration_acceptance.md). Very low internal/DMA lifetime minima remain a nonblocking headroom follow-up, not an approved reserve. Phase 9 hardening begins only on a separate request.
|
||||
Hardware characterization, serial/USB/Wi-Fi/HTTPS/SSH and local display/control are implemented and hardware-validated. **Phase 8 role-based users and administration is complete:** 8A–8C were target-hardware validated and the user explicitly signed off tested firmware at **8D.22 (2026-09-13)**. See the [roadmap](docs/roadmap.md#phase-8--role-based-users-and-administrative-access--complete) and its [consolidated acceptance evidence](docs/roadmap.md#phase8-acceptance-evidence). Very low internal/DMA lifetime minima remain a nonblocking headroom follow-up, not an approved reserve. The reduced Phase 9 focuses on intermittent trusted-network use, standard no-dump/silent-panic defaults and a short [operational checklist](docs/security_operations.md), without dependency patches or upgrades. **The reduced Phase 9 is complete by user sign-off on 2026-09-18.** The user waived a new device check based on prior Phase 8 validation; application code is unchanged, but crash-diagnostic defaults changed. No new hardware or panic-test pass is claimed.
|
||||
|
||||
**Phase 10 application firmware upload is complete by explicit user acceptance on 2026-09-18:** upload works and normal operation is verified. This does not claim specific fault-injection, NVS before/after comparison, power-loss or wired-recovery checks. Administrators can upload a locally built application from **Settings → HTTPS / Reboot → Firmware update**. Install this OTA-enabled firmware by wire first; future uploads use `.pio/build/esp32-s3-devkitc-1-n16r8/firmware.bin`. See [firmware update and recovery](docs/roadmap.md#phase10-update-and-recovery) for compatibility, interruption and validation limits.
|
||||
|
||||
### Browser administration
|
||||
|
||||
@@ -31,8 +33,11 @@ Keep UART0 ready for administrative recovery and native USB for network-independ
|
||||
- [Hardware wiring](docs/wiring.md): hardware profile, GPIO assignments, connector guidance, and safety notes.
|
||||
- [Electrical tests](docs/electrical_tests.md): OLED/buttons, MAX3243, UART loopback, and session-broker verification procedures.
|
||||
- [Role-based user database and UART0 administration](docs/user_administration_tests.md): user provisioning and administration, HTTPS/SSH authentication, session revocation, and reusable integrated web-administration regression procedures (not execution evidence).
|
||||
- [Web administration](docs/web_administration.md): current bounded API/owner and recovery contracts; [acceptance evidence](docs/web_administration_acceptance.md) records sign-off and telemetry limits.
|
||||
- [Web administration](docs/web_administration.md): current bounded API/owner and recovery contracts; [roadmap acceptance evidence](docs/roadmap.md#phase8-acceptance-evidence) records sign-off and telemetry limits.
|
||||
- [Firmware update](docs/roadmap.md#phase-10--simple-admin-web-firmware-upload): admin-only application upload, NVS preservation, uncertain outcomes, wired recovery, acceptance evidence and reusable regression checks.
|
||||
- [Security operations](docs/security_operations.md): trusted-network use, credentials, identity verification, shutdown and recovery; physical-extraction limits and lightweight upstream maintenance.
|
||||
- [Command reference](docs/command_reference.md): UART0/admin-SSH administration, serial, broker, USB, Wi-Fi, mDNS, web, SSH, and diagnostic commands.
|
||||
- [Dual-stack networking and discovery](docs/roadmap.md#phase-12--advanced-network-integration): DHCPv4 hostname publication, STA SLAAC/link-local IPv6, HTTPS/SSH DNS-SD and dual-stack access; complete by explicit user validation, including fresh boot and the full client mix at 230400 baud. Includes recorded heap/stack measurements, evidence limits, saved-config requirements and the narrowly guarded mDNS membership fix.
|
||||
|
||||
## Flash partition layout
|
||||
|
||||
@@ -51,7 +56,7 @@ The N16R8 target has 16 MiB flash and 8 MiB octal PSRAM. PlatformIO uses the cus
|
||||
|
||||
Application offsets are aligned to the ESP32-S3's required 64 KiB boundary. The final storage partition ends at `0x1000000`, exactly the end of the 16 MiB flash chip.
|
||||
|
||||
The table reserves OTA and storage space; it does not implement OTA downloads, rollback confirmation, core-dump handling, NVS encryption, or filesystem mounting.
|
||||
Admin HTTPS application upload uses the inactive OTA slot and `otadata`; NVS and other data partitions are untouched by the updater. It does not provide remote downloads, automatic rollback/health confirmation, core-dump handling, NVS encryption or filesystem mounting. Image validation does not guarantee a working application; compatible storage schemas and wired recovery remain the owner's responsibility.
|
||||
|
||||
### One-time migration from the default partition table
|
||||
|
||||
@@ -63,7 +68,7 @@ pio run --target upload
|
||||
pio device monitor -b 115200
|
||||
```
|
||||
|
||||
This removes saved serial configuration and all other flash contents. The firmware recreates NVS with safe defaults. Subsequent ordinary uploads do not need a full erase.
|
||||
This removes saved serial configuration and all other flash contents. The firmware recreates NVS with safe defaults. **Do not erase for subsequent ordinary wired or web application updates.** This destructive migration is only for the former default/factory layout, not for installing the updater on an existing custom-layout device.
|
||||
|
||||
## Build
|
||||
|
||||
@@ -71,6 +76,8 @@ This removes saved serial configuration and all other flash contents. The firmwa
|
||||
pio run
|
||||
```
|
||||
|
||||
The small Phase 9 sets `CONFIG_ESP_COREDUMP_ENABLE_TO_NONE=y` and `CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT=y` in `sdkconfig.defaults`. An existing generated `sdkconfig.esp32-s3-devkitc-1-n16r8` can retain earlier selections: check its resolved values after configuration, or change them using `pio run --target menuconfig`. Defaults alone do not enforce existing configurations. Silent panic reboot removes panic register/backtrace output; ordinary logs remain. No existing flash contents are erased, and stored secrets remain unencrypted.
|
||||
|
||||
## Upload and monitor
|
||||
|
||||
Connect the board's **USB-to-UART** port for firmware upload and the UART0 development console, then run:
|
||||
@@ -88,7 +95,7 @@ Serial, Wi-Fi, and mDNS hostname edits remain in RAM until explicitly saved with
|
||||
|
||||
## Security notes
|
||||
|
||||
The HTTPS interface uses a device-specific self-signed certificate and a same-origin login page with bounded server-side cookie sessions; HTTP Basic is no longer accepted. Open `/` or `/login`, sign in with a user-database password, and use **Sign out** before switching accounts. Four sessions have a one-hour absolute lifetime, including active serial connections; logout closes only that session's serial access. Login is globally limited to five credential verifications per 60 seconds, with explicit capacity/backoff errors. Direct-IP and mDNS access use separate host-only Secure/HttpOnly/SameSite=Strict cookies. Non-browser clients also require cookies, strict Origin and CSRF for mutations rather than Basic credentials. There is no plaintext HTTP or TCP serial listener. SSH accepts role-based passwords and authorized Ed25519/ECDSA P-256 public keys. User passwords are stored as salted PBKDF2-HMAC-SHA256 verifiers, but the HTTPS private key, SSH private key, and Wi-Fi credentials remain recoverable from unencrypted application-owned NVS blobs. Offline password guessing and stale append-oriented flash copies also remain possible. The reserved `nvs_key` partition does not enable encryption. Do not treat this firmware as resistant to physical flash or RAM extraction until the planned hardening work is complete.
|
||||
The HTTPS interface uses a device-specific self-signed certificate and a same-origin login page with bounded server-side cookie sessions; HTTP Basic is no longer accepted. Open `/` or `/login`, sign in with a user-database password, and use **Sign out** before switching accounts. Four sessions have a one-hour absolute lifetime, including active serial connections; logout closes only that session's serial access. Login is globally limited to five credential verifications per 60 seconds, with explicit capacity/backoff errors. Direct-IP and mDNS access use separate host-only Secure/HttpOnly/SameSite=Strict cookies. Non-browser clients also require cookies, strict Origin and CSRF for mutations rather than Basic credentials. There is no plaintext HTTP or TCP serial listener. SSH accepts role-based passwords and authorized Ed25519/ECDSA P-256 public keys. User passwords are stored as salted PBKDF2-HMAC-SHA256 verifiers, but the HTTPS private key, SSH private key, and Wi-Fi credentials remain recoverable from unencrypted application-owned NVS blobs. Offline password guessing and stale append-oriented flash copies also remain possible. The reserved `nvs_key` partition does not enable encryption. This firmware is not resistant to physical flash or RAM extraction; the completed reduced Phase 9 deliberately does not add that protection.
|
||||
|
||||
## License
|
||||
|
||||
@@ -98,4 +105,4 @@ This project is licensed under the [GNU General Public License version 3 only](L
|
||||
|
||||
Missing user storage is persisted as an empty database; no shared credential is imported or synchronized. Existing valid v1 user records retain their accounts, roles, IDs, verifiers and keys without a schema change. HTTPS `web_sec/material` upgrades valid 1,392-byte v1 storage to 1,340-byte TLS-only v2, retaining exact certificate/key DER, fingerprint and generation, and committing before publication. Invalid records or migration failures fail closed rather than triggering fallback replacement. `web certificate rotate --force` remains available; `web reset --force` replaces TLS identity only, not users.
|
||||
|
||||
**Downgrade warning:** older v1-only firmware cannot read v2 HTTPS material. Logical NVS replacement is not a secure flash wipe; historical plaintext credentials can remain in flash. This cleanup requires no factory/partition erase. See [implementation and evidence limits](docs/legacy_credential_removal.md); final integration build evidence is pending.
|
||||
**Downgrade warning:** older v1-only firmware cannot read v2 HTTPS material. Logical NVS replacement is not a secure flash wipe; historical plaintext credentials can remain in flash. This cleanup requires no factory/partition erase. See [consolidated compatibility and evidence limits](docs/roadmap.md#phase8-legacy-credential-compatibility); Phase 8 acceptance is recorded there.
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
# Only the lwIP backend needs this fix. Keep the managed component immutable.
|
||||
function(project_mdns_membership_overlay)
|
||||
if(CONFIG_MDNS_NETWORKING_SOCKET)
|
||||
return()
|
||||
endif()
|
||||
idf_component_get_property(mdns_dir espressif__mdns COMPONENT_DIR)
|
||||
idf_component_get_property(mdns_lib espressif__mdns COMPONENT_LIB)
|
||||
idf_component_get_property(mdns_version espressif__mdns COMPONENT_VERSION)
|
||||
if(NOT mdns_version STREQUAL "1.12.0")
|
||||
message(FATAL_ERROR "mDNS membership overlay requires component version 1.12.0; review upstream")
|
||||
endif()
|
||||
idf_build_get_property(python PYTHON)
|
||||
set(helper "${CMAKE_CURRENT_LIST_DIR}/mdns_membership.py")
|
||||
set(original "${mdns_dir}/mdns_networking_lwip.c")
|
||||
set(overlay "${CMAKE_BINARY_DIR}/mdns_membership/mdns_networking_lwip.c")
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
|
||||
"${helper}" "${original}" "${mdns_dir}/idf_component.yml" "${overlay}")
|
||||
execute_process(COMMAND "${python}" "${helper}" "${mdns_dir}" "${overlay}"
|
||||
RESULT_VARIABLE result OUTPUT_VARIABLE output ERROR_VARIABLE error)
|
||||
if(NOT result EQUAL 0)
|
||||
message(FATAL_ERROR "mDNS membership overlay failed: ${output}${error}")
|
||||
endif()
|
||||
|
||||
get_target_property(sources ${mdns_lib} SOURCES)
|
||||
get_target_property(source_dir ${mdns_lib} SOURCE_DIR)
|
||||
set(replaced 0)
|
||||
set(updated_sources)
|
||||
foreach(source IN LISTS sources)
|
||||
get_filename_component(absolute "${source}" ABSOLUTE BASE_DIR "${source_dir}")
|
||||
if(absolute STREQUAL original)
|
||||
list(APPEND updated_sources "${overlay}")
|
||||
math(EXPR replaced "${replaced} + 1")
|
||||
else()
|
||||
list(APPEND updated_sources "${source}")
|
||||
endif()
|
||||
endforeach()
|
||||
if(NOT replaced EQUAL 1)
|
||||
message(FATAL_ERROR "Expected exactly one mDNS lwIP target source, found ${replaced}; review upstream CMake")
|
||||
endif()
|
||||
set_property(TARGET ${mdns_lib} PROPERTY SOURCES "${updated_sources}")
|
||||
message(STATUS "mDNS 1.12.0: using guarded build-local multicast membership fix")
|
||||
endfunction()
|
||||
|
||||
project_mdns_membership_overlay()
|
||||
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate the narrowly guarded mDNS 1.12.0 membership overlay; never edit upstream."""
|
||||
import argparse
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
SOURCE_SHA256 = "adc139fa504a925ab644f21f8dce3659927f534e390a176b72b0ae3206c6a3ea"
|
||||
OLD_DEINIT = """ s_interfaces[tcpip_if].proto &= ~(ip_protocol == MDNS_IP_PROTOCOL_V4 ? PROTO_IPV4 : PROTO_IPV6);
|
||||
if (s_interfaces[tcpip_if].proto == 0) {
|
||||
s_interfaces[tcpip_if].ready = false;
|
||||
join_group(tcpip_if, ip_protocol, false);
|
||||
"""
|
||||
NEW_DEINIT = """ int proto = (ip_protocol == MDNS_IP_PROTOCOL_V4 ? PROTO_IPV4 : PROTO_IPV6);
|
||||
if (!(s_interfaces[tcpip_if].proto & proto)) {
|
||||
return;
|
||||
}
|
||||
join_group(tcpip_if, ip_protocol, false);
|
||||
s_interfaces[tcpip_if].proto &= ~proto;
|
||||
if (s_interfaces[tcpip_if].proto == 0) {
|
||||
s_interfaces[tcpip_if].ready = false;
|
||||
"""
|
||||
OLD_INIT = """ err = pcb_init();
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
"""
|
||||
NEW_INIT = """ err = pcb_init();
|
||||
if (err) {
|
||||
join_group(tcpip_if, ip_protocol, false);
|
||||
return err;
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def generate(component: Path, output: Path) -> None:
|
||||
manifest = (component / "idf_component.yml").read_text()
|
||||
if re.findall(r"^version:\s*(\S+)\s*$", manifest, re.MULTILINE) != ["1.12.0"]:
|
||||
raise ValueError("mDNS membership overlay requires exactly version 1.12.0; review upstream")
|
||||
original = (component / "mdns_networking_lwip.c").read_bytes()
|
||||
if hashlib.sha256(original).hexdigest() != SOURCE_SHA256:
|
||||
raise ValueError("mDNS networking source SHA-256 mismatch; review upstream, do not bypass guard")
|
||||
patched = original.decode("utf-8")
|
||||
for old, new in ((OLD_DEINIT, NEW_DEINIT), (OLD_INIT, NEW_INIT)):
|
||||
if patched.count(old) != 1:
|
||||
raise ValueError("mDNS membership replacement must match exactly once")
|
||||
patched = patched.replace(old, new, 1)
|
||||
result = patched.encode("utf-8")
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
if not output.exists() or output.read_bytes() != result:
|
||||
output.write_bytes(result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("component", type=Path)
|
||||
parser.add_argument("output", type=Path)
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
generate(args.component, args.output)
|
||||
except (OSError, ValueError) as error:
|
||||
parser.exit(1, f"mDNS membership overlay: {error}\n")
|
||||
@@ -104,7 +104,7 @@ TinyUSB callbacks enqueue/copy data and state; the transport task owns broker li
|
||||
|
||||
### HTTPS, WebSocket, and web serial
|
||||
|
||||
`web_server` owns HTTPS on port 443 with a persisted self-signed P-256 identity. `web_serial_transport` mediates two fixed WebSocket slots through the broker; HTTPD owns socket sends/close, the transport task owns broker IO. Four outstanding serial tickets, four cookie sessions, one optional admin WebSocket and six total HTTPD sockets are distinct limits; LRU is disabled. Current handler capacity is 39. Base HTTPS can serve authenticated non-WebSocket routes if optional serial/admin transport initialization fails.
|
||||
`web_server` owns HTTPS on port 443 with a persisted self-signed P-256 identity and IDF's dual-stack listener. Shared Host/Origin parsing accepts canonical bracketed IPv6 literals (no interface zones) while preserving same-origin/session binding; DNS discovery does not confer certificate trust. `web_serial_transport` mediates two fixed WebSocket slots through the broker; HTTPD owns socket sends/close, the transport task owns broker IO. Four outstanding serial tickets, four cookie sessions, one optional admin WebSocket and six total HTTPD sockets are distinct limits; LRU is disabled. Current handler capacity is 40. Base HTTPS can serve authenticated non-WebSocket routes if optional serial/admin transport initialization fails.
|
||||
|
||||
Cookie login/logout replaces Basic/cache. Digest-only records carry copied principals, CSRF state, absolute expiry and nonreused originating-session IDs. Strict same-origin/CSRF mutations and session/principal checks gate admission; logout invalidates its session before transport cleanup, account mutations invalidate only the affected account, and ongoing currentness is authoritative. Authentication initialization failure gates HTTPS; failed start/accepted stop wipes records. RNG/SHA/database calls run outside short spinlocks with post-call epoch/identity revalidation. [Authentication contract](../web_administration.md#authentication-and-admission).
|
||||
|
||||
@@ -122,11 +122,17 @@ Cookie login/logout replaces Basic/cache. Digest-only records carry copied princ
|
||||
|
||||
`web_server_replace_identity` reserves service before identity and retains both through commit → reserved stop/start. Direct security and canonical CLI/browser-shell paths share task-bound nonreused identity reservations. Crypto/NVS run outside short security/service locks; commit precedes publication/wipe. Precommit failure leaves identity/HTTPD/logins unchanged; postcommit lifecycle failure never rolls back identity and can leave served/stored fingerprints different. Failed stop skips start and retains canonical recovery. Public service/security projections are separate observations, not authorization. [HTTPS ownership, generation and recovery contract](../web_administration.md#https-and-reboot).
|
||||
|
||||
### Admin firmware upload
|
||||
|
||||
`web_firmware_update` streams a raw admin-only application image through HTTPD into the inactive OTA slot using standard ESP-IDF APIs. Existing Origin/CSRF/session checks precede body IO; raw length is bounded before HTTPD's narrowed length is trusted, SDK image validation and final principal currentness precede boot selection. One 4KiB buffer and a transient reboot task bound application storage; NVS and partition layout are untouched. HTTPD is occupied during upload, so normal web traffic can stall; deadlines bound receive progress, not flash-operation latency.
|
||||
|
||||
The server transition and identity reservations exclude competing lifecycle work. A separate atomic gate excludes ordinary reboot paths without depending on HTTPS initialization, preserving UART0 recovery. Successful response schedules delayed reset while retaining reservations; failed response after selection retains the selected-image latch but releases resources for manual reboot. A subsequent upload is refused until reset. No automatic retry, rollback, signing infrastructure or dependency patching. [Contract and validation limits](../roadmap.md#phase-10--simple-admin-web-firmware-upload).
|
||||
|
||||
### SSH
|
||||
|
||||
Typed SSH settings use the existing ID dispatcher and original-login result slot, never HTTPD wolfSSH calls or owner waits. Conditional lifecycle/session controls compare a saturated service generation and exact nonreused session ID under canonical locks. `ssh_transport_replace_identity` reserves service then identity before stop, retaining the command mutex across stop → commit → conditional restart. Failed stop skips mutation/start; failed persistence may follow disconnection; committed identity is never rolled back after restart failure. Only the SSH owner frees context after all slots retire, and start rejects orphan handles. Direct security/CLI/deferred SSH callers share task-bound identity reservations; crypto/NVS run outside security locks. HTTPS remains available, so no self-cutting HTTP ACK gate is needed. [SSH contracts](../web_administration.md#ssh).
|
||||
|
||||
`ssh_transport` uses wolfSSH on port 22 with two fixed session/handshake slots. Initialization calls `wolfSSH_Init()` in the caller before task creation; after that, one owner task pinned to core 1 exclusively owns runtime contexts/sessions and wolfSSH calls. It enforces bounded handshakes, authentication attempts, receive work, and session buffers.
|
||||
`ssh_transport` uses wolfSSH on port 22 with one explicitly dual-stack IPv6 wildcard listener and two fixed session/handshake slots. Peer formatting preserves IPv6 interface scope; IPv4 shares the listener, not a second socket. Initialization calls `wolfSSH_Init()` in the caller before task creation; after that, one owner task pinned to core 1 exclusively owns runtime contexts/sessions and wolfSSH calls. It enforces bounded handshakes, authentication attempts, receive work, and session buffers.
|
||||
|
||||
Authentication uses user-database passwords or stored Ed25519/ECDSA-P256 public keys. Public-key lookup authorizes a username/key pair, while wolfSSH verifies signed proof of possession. SSH host identity is a separate persisted P-256 key managed by `ssh_security`.
|
||||
|
||||
@@ -179,13 +185,23 @@ For SSH, standard output/error is redirected to the invoking session's bounded o
|
||||
|
||||
Admin SSH `exit`, remote reboot, SSH stop/disconnect, and host-key rotate/reset use deferred control. The control task waits up to ten seconds for command state plus administration and transport application buffers to clear, then adds a short delay; this is a bounded best-effort heuristic, not peer-delivery confirmation. UART0 invokes these actions synchronously. User mutations and their revocations are not part of this mechanism. UART0 linenoise and the SSH editor consume the same manually maintained completion matcher and candidate formatter, so the two administration routes cannot drift in offered or displayed ambiguous completions; the hints can still drift from command registration and are not an authorization list.
|
||||
|
||||
Browser stop/reboot uses this same owner-adapter control path. Exact forced certificate rotation instead uses the typed queue union and immutable `dispatcher_actions` mask to hand off after drain/200 ms to the existing 12 KiB dispatcher, not the 4 KiB control stack. Pending input is discarded through execution and an executing slot remains reserved across self-detach. Canonical shared service/identity replacement preserves commit/stop/start failure semantics. Parsed other-account interactive add/password and forced role/delete are allowed, but browser self/generated/key/recovery and restricted network/SSH shell actions are not; typed Settings has separate permissions. Post-prompt currentness is operation admission, not an atomic session-liveness/NVS guarantee. [Browser policy and recovery](../web_administration.md#browser-shell-policy).
|
||||
Browser stop/reboot uses this same owner-adapter control path. Exact forced certificate rotation instead uses the typed queue union and immutable `dispatcher_actions` mask to hand off after drain/200 ms to the existing 12 KiB dispatcher, not the 4 KiB control stack. Pending input is discarded through execution and an executing slot remains reserved across self-detach. Canonical shared service/identity replacement preserves commit/stop/start failure semantics. Parsed other-account interactive add/password and forced role/delete are allowed. Canonical Wi-Fi commands are also allowed, including hidden secret prompts and disruptive changes; these use the ordinary owner queue and may cut off the browser before output arrives, without a deferred acknowledgement guarantee. Browser self/generated/key/recovery, mDNS mutations and restricted SSH shell actions remain denied; typed Settings has separate permissions. Post-prompt currentness is operation admission, not an atomic session-liveness/NVS guarantee. [Browser policy and recovery](../web_administration.md#browser-shell-policy).
|
||||
|
||||
### Shared IPv4/IPv6 diagnostics
|
||||
|
||||
`network_console` owns root and `wifi`-alias ping/traceroute/nslookup for UART0, admin SSH and browser Admin. Family flags are parsed once; explicit IPv6 never silently falls back to IPv4. Hostname probes use IPv4-first resolution fallback only; nslookup queries A and AAAA separately and shows unscoped link-local answers. Probes require a device-local link-local zone (`%sta`, `%ap`, valid index/name). Resolver policy/DNS provisioning and serial transport ownership are unchanged.
|
||||
|
||||
Traceroute owns one raw socket and a 1280-byte transient receive buffer on the shared dispatcher stack, one eight-byte probe per hop (max30), and one-second absolute receive deadlines. It rejects incomplete/corrupt/unrelated packets and unsupported IPv6 extension headers/fragments. IDF5.5.0-specific framing, automatic ICMPv6 checksum, common `IP_TTL` hop-limit behavior and millisecond timeout flooring are pinned/retested; <1ms remaining returns timeout rather than configuring an infinite wait. DNS and send/scheduler latency are not covered by the receive deadline.
|
||||
|
||||
Ping keeps the 4200-byte PSRAM-only lazy queue payload and SDK transient task. Permanent callback context plus a pending reservation survives the count-based console deadline; later pings cannot reset a live producer's queue. SDK receive can outlast its timeout under unrelated ICMP, so a timed-out session stays reserved until END. END means callback production ended, not synchronous task/socket destruction; retiring resources can briefly overlap a new session. These diagnostics serialize administration but never execute on HTTPD or enter the serial hot path. Tests and exact limits: `tests/network_diagnostics/README.md`.
|
||||
|
||||
## Wi-Fi and persistence
|
||||
|
||||
`wifi_config` owns a fixed-width versioned NVS schema with four prioritized station profiles and AP policy `off`, `fallback`, or `always`. Missing configuration generates per-device defaults including a random AP password. Invalid stored data is generally left untouched while RAM defaults are used.
|
||||
|
||||
`wifi_manager` is a permanent task with one bounded command/event queue. ESP-IDF callbacks only copy compact events into the queue. The task owns association, DHCP deadlines, profile failover, AP policy, retries/backoff, next-profile requests, and the mDNS announcement lifecycle. `mdns_service` initializes the responder at most once after a validated STA `GOT_IP`; the managed component's own event handlers withdraw and restore the STA announcement across transient connectivity changes, while the project tracks whether announcement is currently expected. Initialization failure is latched rather than retried because partial upstream low-memory initialization is not safely recoverable; mDNS failure is nonfatal. It also reconciles against authoritative driver/netif state so dropped events do not permanently wedge policy. ESP-IDF Wi-Fi storage is RAM-only; the application blob is authoritative, and edits require explicit save. Edits to disabled station profiles are staged in RAM without restarting the radio; enabling/disabling a profile or changing enabled station/AP policy restarts it asynchronously. Start/stop—including local controls—intentionally update the RAM `enabled_at_boot` field. Working-configuration copies contain PSKs and must be securely wiped; routine status and the local UI use secret-free snapshots.
|
||||
`wifi_manager` is a permanent task with one bounded command/event queue. ESP-IDF callbacks only copy compact events into the queue. The task owns association, DHCP/address deadlines, profile failover, AP policy, retries/backoff, next-profile requests, and the mDNS announcement lifecycle. One-second reconciliation reads authoritative driver/netif state in TCP/IP context, enables STA SLAAC and creates link-local addresses without restarting DAD. IPv4 or preferred IPv6 (including link-local-only) establishes ONLINE and may retire the fallback AP; ONLINE does not imply a default route or Internet access. DHCPv4 option 12 carries the full `sak-<suffix>` from permanent bounded hostname storage installed before connection; rename updates subsequent exchanges without restarting DHCP. The bounded snapshot retains up to three preferred numeric IPv6 addresses from the same observation as its availability flags; compile-time capacity checks reject larger lwIP address-slot settings. `wifi status` formats these addresses and their scope. Web Network settings encode up to three fixed-width strings from the same snapshot and display link-local/ULA/GUA groups within a 2304-byte JSON bound; the overview/OLED retains availability-only reporting.
|
||||
|
||||
`mdns_service` initializes the responder at most once after either-family STA readiness. Initialization failure is latched rather than retried because partial upstream low-memory initialization is not safely recoverable; mDNS failure is nonfatal. HTTPS/SSH owners publish availability under a short portMUX, and only the Wi-Fi owner reconciles the two STA-only DNS-SD records and address families, including while offline. Component calls run outside project service mutexes. Missing-family disable is reasserted every pass and present-family enable/reprobe every 30 seconds because upstream actions can silently drop and expose no readiness acknowledgement. This is eventual convergence, not instantaneous withdrawal: a transient stale/zero-A window can occur after DHCP loss, and AAAA follows upstream valid-address semantics including deprecated addresses. A version/hash-guarded build-local mDNS 1.12.0 source overlay balances per-family multicast membership and join cleanup; managed source stays immutable. Tests and upgrade/removal contract: `tests/mdns_membership/README.md`. ESP-IDF Wi-Fi storage is RAM-only; the application blob is authoritative, and edits require explicit save. Edits to disabled station profiles are staged in RAM without restarting the radio; enabling/disabling a profile or changing enabled station/AP policy restarts it asynchronously. Start/stop—including local controls—intentionally update the RAM `enabled_at_boot` field. Working-configuration copies contain PSKs and must be securely wiped; routine status and the local UI use secret-free snapshots.
|
||||
|
||||
Persistent namespaces/blobs include:
|
||||
|
||||
@@ -231,4 +247,4 @@ Hardware diagnostics are synchronous console commands. RS-232 tests own the phys
|
||||
|
||||
`user_database_init(load_result)` has no credential input. Missing storage is persisted empty; `user_database_recover_empty()` is the unavailable-only destructive recovery API. Valid v1 user bytes load without rewriting or account changes. The private `v1_admin_marker` retains its byte position and is derived from administrator count during mutations; it is not a public bootstrap state, new role or schema change. No user migration/bootstrap/synchronization API remains.
|
||||
|
||||
`web_security` owns TLS only. A private reader validates 1,392-byte v1 `web_sec/material`, copies exact key/certificate DER, fingerprint and generation into 1,340-byte v2, commits, then publishes. Temporary v1 credential-bearing input is wiped; no public legacy credential type/getter/rotation remains. Malformed/unknown records and read/validation/commit failures fail closed, with no fallback regeneration or overwrite of rejected records. Missing material may be generated; explicit reset replaces TLS only. Downgrade to v1-only firmware is incompatible. Logical NVS replacement is not secure flash erasure. Contracts/evidence: [legacy compatibility](../legacy_credential_removal.md).
|
||||
`web_security` owns TLS only. A private reader validates 1,392-byte v1 `web_sec/material`, copies exact key/certificate DER, fingerprint and generation into 1,340-byte v2, commits, then publishes. Temporary v1 credential-bearing input is wiped; no public legacy credential type/getter/rotation remains. Malformed/unknown records and read/validation/commit failures fail closed, with no fallback regeneration or overwrite of rejected records. Missing material may be generated; explicit reset replaces TLS only. Downgrade to v1-only firmware is incompatible. Logical NVS replacement is not secure flash erasure. Contracts/evidence: [legacy compatibility](../roadmap.md#phase8-legacy-credential-compatibility).
|
||||
|
||||
@@ -12,6 +12,12 @@ This is a semantic map, not a complete file inventory. Start here, then read the
|
||||
- Dependencies: every subsystem initializer
|
||||
- Lifecycle constraint: optional display/network failures should not remove UART0 administrative recovery or USB UART1 access; the custom administration frontend starts only after command registration.
|
||||
|
||||
## Small Phase 9 operating baseline
|
||||
|
||||
- `sdkconfig.defaults` explicitly selects no core dumps and silent panic reboot; existing generated configs can override defaults. README documents verification. No dependency overlays, custom crypto policy or version changes are part of this phase.
|
||||
- User accepted the reduced Phase 9 on 2026-09-18 and waived a new hardware check; this is not a claim of a new hardware-test pass.
|
||||
- [Operational checklist](../security_operations.md) and [scope/acceptance](../roadmap.md#phase-9--security-and-production-hardening): intermittent trusted-network use, unchanged authentication/recovery, no physical-extraction protection. Keep upgrades separate and do not restore the abandoned extensive patchset.
|
||||
|
||||
## Secure randomness
|
||||
|
||||
**Responsibility:** provide the sole project-owned, mutex-serialized application DRBG, seeded before Wi-Fi/radio use.
|
||||
@@ -64,7 +70,7 @@ This is a semantic map, not a complete file inventory. Start here, then read the
|
||||
- Files: `src/web_server.{h,c}`, `src/web_serial_transport.{h,c}`, `src/web_ui.{h,c}`, `src/web_console.{h,c}`
|
||||
- Ordinary HTTPS idle cleanup: `src/web_httpd_idle.{c,h}`, owner sweep in `web_httpd_adapter.{c,h}`, lifecycle/TLS composition in `web_server.c`; `tests/web_httpd_idle/run.py`. Independent of diagnostics/optional transports: 15-second observed idle, one-second timer/one queued probe, six rows, actual WS/async/pending exemptions, safe current-owner shutdown and stop/restart fencing. No LRU/socket/timeout/stack increase. SDK queue/owner-delay and regression contract: `docs/https_idle_cleanup.md`.
|
||||
- Independent throughput diagnostics: `web_serial_transport.{c,h}` owns two fixed per-slot binary-TX aggregates and epoch fences; `web_console.c` exposes default-disabled `web performance enable|disable|show|clear`. Queue-entry/callback-entry, synchronous-send and completion/drain-return estimates, not peer receipt or scheduler-only latency. `tests/web_serial_performance/run.py`; resource/evidence limits and UART0 paired capture: `docs/web_throughput_diagnostics.md`.
|
||||
- Storage compatibility: `user_database` persists missing storage empty and preserves valid v1 user bytes; private derived `v1_admin_marker`, no public bootstrap/migration/sync APIs. `web_security` privately migrates v1 1392-byte material to TLS-only v2 1340-byte material, exact identity/generation retained, commit before publish, fail closed without fallback overwrite. Credential commands removed; user generated passwords and TLS rotation remain. Contracts, downgrade and evidence limits: `docs/legacy_credential_removal.md`.
|
||||
- Storage compatibility: `user_database` persists missing storage empty and preserves valid v1 user bytes; private derived `v1_admin_marker`, no public bootstrap/migration/sync APIs. `web_security` privately migrates v1 1392-byte material to TLS-only v2 1340-byte material, exact identity/generation retained, commit before publish, fail closed without fallback overwrite. Credential commands removed; user generated passwords and TLS rotation remain. Contracts, downgrade and evidence limits: [legacy compatibility](../roadmap.md#phase8-legacy-credential-compatibility).
|
||||
- Security files: `src/web_security.{h,c}`, `src/web_cookie_auth.{h,c}`, `src/web_session_store.{h,c}`, `src/web_auth_parse.{h,c}`. Private IDF boundary: `src/web_httpd_adapter.{h,c}`.
|
||||
- HTTP policy/UI: `web_cookie_auth` + `web_auth_parse` enforce bounded cookie/Origin/CSRF/admin admission; `web_login_ui.{c,h}` serves login, `web_ui.c` owns session-fenced Serial/Admin/Settings and shared quick controllers. Tests: `tests/web_cookie_auth/run.py` (domain variants), `tests/web_auth_parse/run.py`, `tests/web_login_ui/run.py`, `tests/web_ui_session/run.py`.
|
||||
- Admission diagnostics: `web_diagnostics.{c,h}`, `tests/web_diagnostics/run.py`; six post-TLS records/32-event opt-in ring, no HTTPD off-owner inspection. [Contract](../web_admission_diagnostics.md).
|
||||
@@ -82,10 +88,16 @@ This is a semantic map, not a complete file inventory. Start here, then read the
|
||||
### Browser admin backend
|
||||
|
||||
- Files: `src/web_admin_transport.{c,h}`, `src/web_admin_tickets.{c,h}`, protected registration/lifecycle in `web_server.c`, revocation through `web_serial_transport_revoke_*`, diagnostics in `web_console.c`.
|
||||
- Routes: CSRF-protected admin-only `POST /api/admin/ws-ticket`; ordinary `GET /ws/admin` with cookie/Origin/ticket/shared-console admission before explicit 101. Admin UI entry is explicit; no admin broker client. One socket, two tickets, existing two shared console slots; six total HTTPD sockets, LRU disabled; current overall capacity is 39 URI handlers.
|
||||
- Routes: CSRF-protected admin-only `POST /api/admin/ws-ticket`; ordinary `GET /ws/admin` with cookie/Origin/ticket/shared-console admission before explicit 101. Admin UI entry is explicit; no admin broker client. One socket, two tickets, existing two shared console slots; six total HTTPD sockets, LRU disabled; current overall capacity is 40 URI handlers.
|
||||
- Currentness/policy: `admin_ssh_console_open_available()` shares two slots with runtime SSH; transport-qualified tokens and owner adapters revalidate outside console locks before commands/prompts. Parsed browser policy remains narrower than typed Settings; [shell contract](../web_administration.md#browser-shell-policy). Tests: `tests/admin_console_boundary/{run,accounts,lifecycle}.py`, `tests/admin_ssh_policy/run.py`, `tests/web_admin_transport/run.py --tickets`, `tests/web_cookie_auth/run.py --admin`.
|
||||
- Ownership: 20 ms ESP timer queues at most one HTTPD poll, no new task; HTTPD owns 1,552 B PSRAM-only payload and IO. Closure uses HTTPD-owned `shutdown`, not IDF's reusable-pointer queued close. Detach fences submitters; only successful HTTPD stop retires queued state before restart. Session/principal currentness and generation checks protect all sensitive boundaries.
|
||||
|
||||
## Firmware upload
|
||||
|
||||
- `src/web_firmware_update.{c,h}`: admin raw `POST /api/firmware`, cookie/Origin/CSRF admission via `web_cookie_auth`, registration in `web_server`, UI in `web_ui`. Standard SDK OTA into inactive app only; bounded4KiB buffer, validated raw length/header/final image and principal before boot selection. No NVS/layout writes or vendor patches.
|
||||
- HTTPD handles streaming synchronously; upload is deliberately disruptive to web traffic. Server/identity reservations fence lifecycle changes; atomic ordinary-reboot gate also covers console/SSH/browser/button reset paths. Success-response schedules delayed reset; response failure after selection latches uploads until deliberate manual reboot. No blind retries.
|
||||
- Tests: `tests/web_firmware_update/run.py` (active-build SDK5.5.0 headers and actual begin/abort contract), `tests/web_ui_session/run.py`, existing auth/lifecycle tests. [Usage, acceptance limits and reusable regression checks](../roadmap.md#phase-10--simple-admin-web-firmware-upload). Phase 10 complete by explicit user acceptance on 2026-09-18: upload works and normal operation verified; no specific fault/NVS-comparison/power-loss/recovery pass implied. Initial install by wire; subsequent upload is application `firmware.bin`, not full-flash image.
|
||||
|
||||
## Typed settings source and regression map
|
||||
|
||||
HTTPD reads zero-wait projections and queues only IDs to the existing dispatcher. One original-login slot per domain; canonical owners compare/reserve at execution. [API/lifetime and failure contracts](../web_administration.md#typed-settings-api-and-operation-lifetime).
|
||||
@@ -138,6 +150,7 @@ Shared UI regression: `tests/web_ui_session/run.py` and its domain `.cjs` fixtur
|
||||
- Ownership: dispatcher is sole `esp_console_run()` caller; the SSH owner exclusively performs post-initialization wolfSSH runtime calls.
|
||||
- Lifecycle: remote session tokens include slot generation; fixed output/history/prompt state is wiped immediately on idle close or after an executing handler returns. Admin SSH `exit` and Ctrl+D on an empty command line request bounded deferred self-disconnect after best-effort output draining.
|
||||
- Constraint: one slow command or prompt serializes all administration. Admin SSH is unavailable until command registration and UART frontend creation complete; supported deferred actions wait only for a bounded application-buffer drain heuristic.
|
||||
- Dual-stack diagnostics: `network_console.*` is the shared root/`wifi` handler on all three admin surfaces. `-4`/`-6`, device-local `%sta`/`%ap`/index/name; default probes IPv4-first resolution fallback only, nslookup explicit A+AAAA and unscoped link-local display. One-second/hop raw IPv4/IPv6 traceroute validates full outer packet/quoted echo and rejects IPv6 extensions/fragments. IDF5.5.0 raw framing/hoplimit/checksum/timeout contract is version-guarded. Ping uses existing PSRAM queue and SDK transient task with permanent callback context/deadline/pending reservation; END does not imply completed SDK teardown. Tests: `tests/network_diagnostics` (actual handlers/packet and SDK contracts), `tests/network_diagnostics_surfaces` (routing/policy/completion). No serial hot-path changes.
|
||||
|
||||
## Wi-Fi
|
||||
|
||||
@@ -146,7 +159,9 @@ Shared UI regression: `tests/web_ui_session/run.py` and its domain `.cjs` fixtur
|
||||
- Files: `src/wifi_config.{h,c}`, `src/wifi_manager.{h,c}`, `src/wifi_console.{h,c}`, `src/mdns_config.{h,c}`, `src/mdns_service.{h,c}`, `src/mdns_console.{h,c}`, `src/network_console.{h,c}`
|
||||
- Interfaces: config defaults/validate/load/save; manager init/start/stop/apply/reconnect/next-profile/snapshot
|
||||
- Dependencies: secure random for default AP password, NVS, ESP-NETIF/Wi-Fi/events, Espressif mDNS, lwIP diagnostics
|
||||
- Lifecycle: permanent manager task and bounded queue; callbacks enqueue compact events only.
|
||||
- Lifecycle: permanent manager task and bounded queue; callbacks enqueue compact events only. One-second owner reconciliation handles DHCP hostname updates, preferred IPv4/IPv6 readiness, missed events and mDNS records/families. `ONLINE` includes preferred link-local-only IPv6 and may retire fallback AP without IPv4; snapshots retain IPv6 availability flags plus up to three preferred numeric addresses from the same observation. `wifi status` and web Network settings render those addresses; overview/OLED remain availability-only. The typed runtime adds `ipv6_addresses` (up to three fixed-width strings); backend and UI share a 2304-byte response bound. Regression coverage includes byte order, address clearing and the compile-time slot-capacity bound.
|
||||
- Phase 12: `mdns_service_set_https_available()` / `mdns_service_set_ssh_available()` publish under a short portMUX; only the Wi-Fi owner calls component APIs. STA DNS-SD has two records; family repair re-probes available families every 30 seconds because upstream action admission has no reliable acknowledgement. AAAA uses valid (including deprecated) upstream addresses. DHCPv4 uses full `sak-<suffix>` before connection and in later exchanges after rename; permanent raw-netif hostname storage is updated only in TCP/IP context.
|
||||
- Focused tests: `tests/wifi_phase12`, `tests/mdns_phase12`, `tests/ssh_phase12`, `tests/mdns_membership`; HTTPS IPv6 authority cases live in `tests/web_auth_parse` and cookie suites. Build-local `cmake/mdns_membership.*` replaces only reviewed mDNS 1.12.0 networking source to balance multicast membership; version/hash drift fails configuration. No managed source is edited.
|
||||
- Constraint: application NVS is authoritative (`WIFI_STORAGE_RAM`); working edits are not persisted until save. Start/stop, including local controls, intentionally update the RAM `enabled_at_boot` field. Working-config copies contain PSKs and must be tightly scoped and wiped; routine status/local UI must use secret-free snapshots.
|
||||
|
||||
## Local display and controls
|
||||
|
||||
@@ -1,44 +1,85 @@
|
||||
# Current project state
|
||||
|
||||
Working memory, not an implementation timeline. Source is authoritative; begin with [code map](code-map.md), then [architecture](architecture.md) and [decisions](design-decisions.md).
|
||||
Working memory, not an implementation timeline. Source is authoritative; begin with [code map](code-map.md), [architecture](architecture.md) and [decisions](design-decisions.md).
|
||||
|
||||
## Web popup cosmetics — 2026-09-14
|
||||
## Carrier PCB A1 — revised placement and CAD integration
|
||||
|
||||
- `src/web_ui.c`: contextual buttons now occupy entire status cards; bounded grid columns and clipped/ellipsized values prevent narrow-window overflow. Quick panels preserve the visible terminal title, admin toolbar and selected-view indication; full-page promotion retains drafts and restores Settings presentation.
|
||||
- Quick panels hide full-page explanations and duplicate detail readouts while preserving live status, errors and operation results. Wi-Fi exposes configured profile selection/enabled state and AP policy, plus Apply/Save/Refresh/Result; empty profile slots and advanced editors remain full-page-only. Profile selection is an editing target, not a direct-connect command; existing backend operations are unchanged.
|
||||
- Validation: all 161 UI behavior groups and C/HTML checks PASS; 36 Chromium geometry fixtures PASS at 320/600/900/1200px. `pio run` PASS (94,212 B linked RAM / 1,830,329 B flash). No generated-asset regeneration, upload, erase or hardware validation. Updated regression coverage in `tests/web_ui_session/`.
|
||||
- `hardware/PCB/serial-carrier.kicad_pcb`: routed 82 × 80 mm two-layer draft; 36 × 19 mm top-left notch, ESP32 shifted down for bottom USB access, RS-232 bare PCB front flush with carrier top. Exact ESP32 body/USB/antenna envelope remains pending user measurements; RF reserve is provisional.
|
||||
- Adafruit STEP metal geometry extracted and validated; provenance/dimensions in `hardware/PCB/db9-mechanical-notes.md`. Flange front sits 1.930 mm inside carrier edge, screw-lock mouths 2.870 mm outside, shell nose 3.870 mm outside. Case wall plane is not yet fixed. Seven board-local Dwgs.User projections added; rerun `add_db9_envelope.py` and `add_device_silkscreen.py` after moving modules.
|
||||
- Placement PDF and SVG/PNG/3D previews refreshed. Fresh PCB, independent routing, preservation and CAD validators pass: 38/38 connections, 565 segments, 22 vias, 0 physical DRC violations/unrouted, one expected provisional U2 footprint parity warning. Not fabrication-ready; no hardware qualification or Gerbers. Existing project/schematic/library work preserved; board saves must use `SaveBoard(..., True)` and require checking editor lock. Prior A0 preserved in validation backup.
|
||||
|
||||
## Focused cleanup / PSRAM review — 2026-09-13
|
||||
## Dual-stack diagnostics follow-up — implemented, device validation pending
|
||||
|
||||
- User-authorized post-acceptance code review: removed superseded `admin_ssh_console_open()` SSH-only wrapper; production already uses available-slot admission. Updated adapter regression to actual production entry. Wrapper was already linker-discarded: no binary saving attributed to removal.
|
||||
- Moved ping queue payload4,200 B and public user snapshot2,156 B to lazy PSRAM-only lifetime allocations, no fallback. Queue control/internal locks unchanged; allocation failure affects only ping or status/list/show, not UART0 registration/mutations/recovery. Snapshot fully wiped on success/error. No serial hot-path, stacks, CPU, queue bounds or external-BSS config changes.
|
||||
- Baseline pio PASS23.79s100,556 RAM/1,828,573 flash. Final parent pio PASS22.11s **94,212 RAM/1,828,809 flash: −6,344 B linked internal RAM/+236 B flash**. Requested lazy PSRAM6,356 B plus allocator overhead; target pointer sizes4 B, controls84 B verified. This is not measured runtime-minimum improvement. CPU160 retained.
|
||||
- Focused ping allocation/callback/end-capacity and accounts allocation/full-wipe/failure/retry tests PASS; console boundary/lifecycle/policy and SSH management/runtime suites PASS. Independent review found no actionable bugs and reran ping/accounts/boundary/diff PASS. Sanitizer linking unavailable (missing host runtimes), no sanitizer or hardware pass. No upload/erase/commit. Next target check: ping/user list/show repeatedly alongside NVS writes and full mix230400, collect serial/broker counters and memory before/after first allocations.
|
||||
- Further audited opportunities, not implemented: OLED framebuffer1,024 B (internal I2C staging retained), local/remote completion scratch1,024 B each, optional web diagnostic ring2,816 B, remote console output payload8,192 B. Console rings need explicit secret-output wiping/lifecycle and admission-failure isolation; do not relocate mixed state wholesale. Leave authoritative user database, driver/DMA buffers, locks and task stacks internal. Global external-BSS enable is not surgical: it also changes SDK library placement.
|
||||
- User explicitly requested IPv6 ping/traceroute/nslookup on all surfaces after accepting Phase12. Shared `network_console.*` now supports one `-4`/`-6` anywhere after command, preserves count/hop bounds, root and `wifi` aliases. Default probes try IPv4 then IPv6 on resolution failure only; nslookup performs A+AAAA queries separately (one selected address/family), displays unscoped link-local records. Link-local probes require ESP-local `%sta`/`%ap`/index/name; no client-interface guess or IPv4-mapped fallback. Help/completion updated; permissions unchanged.
|
||||
- Actual IDF5.5.0 audit: raw ICMPv6 includes outer IPv6 header, automatic TX checksum, common `IPPROTO_IP/IP_TTL` hoplimit; replies validate checksums/target/id/seq and reject extensions/fragments. Traceroute one socket/max30 probes/1s receive deadline, 1280B receive buffer. Review caught sub-ms timeout flooring to infinite SDK wait; <1000us now returns timeout, actual SDK conversion/mailbox tests verify. No dependency/config/partition/serial path change.
|
||||
- Ping retains4200B PSRAM queue, max20 probes and SDK transient task; permanent callback context plus pending reservation protects delayed END after count*2+2 second console deadline. SDK unrelated-ICMP receive loop may outlive timeout; later pings fail busy until END, which does not prove synchronous resource destruction. Read exact limits in command reference/tests README. IPv6-only unicast DNS still requires a configured resolver; no RDNSS/DHCPv6 provisioning added.
|
||||
- `pio run` PASS **94,452 B linked RAM / 1,857,673 B flash**, +8/+3,188 versus accepted prior build. Traceroute receive-wait object frame measured240→1408B (not runtime stack high-water). No new permanent task/socket/queue capacity. Hardware/ changes in working tree belong to other agent and were not read/edited.
|
||||
- Parent PASS: production diagnostics8027 checks+10k malformed packet inputs, seven installedSDK hashes+actualtimeout fixtures, 432 SSH/WEB root+alias routing cases/UART parity/ordinary-role and revocation guards/completion, existing admin policy and browser Wi-Fi secret fixture. Independent review fixes verified no remaining actionable findings in scoped paths. ASan/UBSan host runtimes absent. Broader console-boundary pre-existing missing rebootfake remains (not changed). No upload/device/network test; Phase12 acceptance remains valid only for its recorded baseline, not this follow-up.
|
||||
|
||||
## Accepted state — 2026-09-13
|
||||
## Phase 12 COMPLETE — explicit user validation
|
||||
|
||||
- **8D.22 explicitly signed off by the user:** “Yep, I tested the firmware thats a 8d.22 signoff.” The retained Phase 8D scope is complete; earlier per-slice pending target/review/integration gates are superseded. Roadmap already records 8A/B/C as complete and target-hardware validated, so **Phase 8 is complete**. Acceptance does not manufacture individual unreported test passes.
|
||||
- [Roadmap](../roadmap.md#phase-8--role-based-users-and-administrative-access--complete) holds the completed-phase gist; [web administration](../web_administration.md) holds current API/owner/recovery contracts; [acceptance evidence](../web_administration_acceptance.md) holds the latest report and limits. The old plan/baseline/per-slice histories are consolidated, not archived as another timeline. Test READMEs now link directly to current contracts and evidence; obsolete forwarding notes were removed too.
|
||||
- Final prior production build **PASS: 100,556 B linked RAM / 1,828,573 B flash, CPU 160 MHz**. No build/test/device execution is implied by this documentation update. The prior combined binary WebSocket-send fix was separately user-accepted at **160 MHz / 230400 baud with full mix including browser admin**; preserve combined send and bounded failed-send isolation.
|
||||
- Latest loaded capture: two serial WS, USB, two SSH roles with SSH serial writer; browser admin used then closed, not active in the capture. Internal/DMA/PSRAM free **31,508 / 23,752 / 8,136,624 B**, minima **2,052 / 460 / 8,065,972 B**, largest **18,432 / 18,432 / 7,995,392 B**; SSH minimum-free stack **15,028 B**. Full boot/loaded table is in acceptance evidence. Web send/queue/protocol and SSH IO errors zero; one SSH handshake failure/session revocation. Missing latest broker/serial counters prohibit an exact zero-drop inference.
|
||||
- User states: “I just validated Phase 12 successfully.” Record acceptance, not a pending phase. Canonical device evidence and reusable regression guidance: [Phase 12](../roadmap.md#phase-12--advanced-network-integration). No unreported DHCP capture, DNS-zone update, exhaustive address/service transition, fault/soak or byte-integrity pass is implied.
|
||||
- User validated IPv6 ping/HTTPS and Avahi AAAA; Fedora authselect dual-family mDNS resolved the normal hostname lookup issue. Fresh-boot and full-mix captures show healthy reported service lifecycle. At 230400 baud 8N1 RTS/CTS: two SSH sessions with IPv6 ULA peers (user observer/admin console), two serial WebSockets (writer/observer), active browser admin, USB observer; four broker clients, one writer, zero pending/event counts at capture.
|
||||
- Memory table preserved in roadmap. Fresh internal8/DMA/PSRAM free: 65,880 / 58,124 / 8,196,732 B. Full-mix free: 35,408 / 27,652 / 8,111,952 B; lifetime minima: 8,528 / 772 / 8,072,612 B; largest blocks: 22,528 / 22,528 / 7,995,392 B. Capability pools overlap; minima are conservative per-region lifetime sums, not simultaneous reserves. DMA772 remains a watch item, not proof of OOM or an acceptance blocker. SSH stack20,480B minimum-free18,476→16,284B.
|
||||
- SSH two successful handshakes, no auth/handshake/timeouts/I/O failures; RX68 accepted65 rejected3, TX161,209B, broker revocations2. Web serial RX27B accepted, TX318,230B across782 frames; send/queue/protocol failures0. Browser admin RX106/TX3,721B with no reported send/queue failures. Do not reinterpret nonzero rejected bytes/writer denials as proven transport loss or claim zero UART/observer drops without their counters.
|
||||
- Boot TLS -0x004C receive errors and auth-failure counts have no demonstrated cause. Snapshots are non-atomic and counts are not an aligned interval. This handoff changes documentation only; no new build/test/upload/erase/device operation/commit; hardware directory untouched. Prior latest build94,444B RAM/1,854,485B flash and UI174+CSP remain historical validation.
|
||||
|
||||
## Follow-ups, not acceptance blockers
|
||||
## Follow-up — IPv6 addresses in web Network settings
|
||||
|
||||
- Extremely low internal/DMA lifetime minima warrant correlated transient-headroom investigation; overlapping capability pools and conservative/non-simultaneous region minima do not prove allocation failure. Numeric reserves, HTTPD/dispatcher stack margins, peak correlation and detailed soak/fault evidence remain unapproved/unreported. Do not reopen functional sign-off or invent a reserve threshold.
|
||||
- SDK TLS `-0x004C` is generic NET_RECV_FAILED, not OOM. Two boot auth failures plausibly involve stale cookies, but causation is unconfirmed. Prior intermittent web admission issues and accepted idle cleanup do not justify claiming every admission failure fixed.
|
||||
- Real DNS/reannouncement, NVS power-loss, browser geometry/accessibility, individual fault cases and exact duration claims require explicit evidence if investigated; retained [regression procedures](../user_administration_tests.md) are not execution records.
|
||||
- **Next: Phase 9 security/production hardening only if requested.** No source, configuration, test implementation, build, upload, erase, branch or commit is authorized by documentation consolidation.
|
||||
- User confirms direct IPv6 ping/HTTPS and Avahi AAAA lookup for `sak-1024.local` succeed. Client NSS uses `mdns4_minimal [NOTFOUND=return]`; systemd-resolved explicit mDNS reports no eligible networks. This supports a client resolver integration issue, not failed firmware AAAA publication on the tested Avahi path. The user subsequently enabled dual-family mDNS through Fedora authselect and confirmed success; see acceptance above.
|
||||
- Added bounded runtime `ipv6_addresses` (<=3 fixed-width lowercase eight-hextet strings) to existing admin Network snapshot, grouped as link-local/ULA/GUA in Settings. Overview/OLED remain flags-only. No new netif call, allocation, task or snapshot storage. Backend/client JSON bound 2048→2304 (+256B response stack); maximum-escaped fixture with all three addresses is 2067B. Strict UI validation/text-only output and existing session fences preserved; generated assets untouched.
|
||||
- `pio run` PASS **94,444 B linked RAM / 1,854,485 B flash**, +0/+800 versus prior CLI-address build. Network cookie/owner regressions and UI174+CSP PASS. Initial cookie fixture had an obsolete 2048B output buffer; fixed to use the production bound and rerun PASS. Empty/full address lists, byte order, bad counts, malformed/injected UI entries and clearing covered. No upload or hardware operations; hardware directory untouched.
|
||||
|
||||
## Scope and safety to retain
|
||||
## Follow-up — browser Wi-Fi controls and IPv6 diagnosis
|
||||
|
||||
- 8D.15 dedicated typed network diagnostics was removed; shell diagnostics remain subject to frontend policy. Unimplemented 8D.19 ordinary browser-session/native-USB controls were removed; SSH settings remain. No implicit full browser-shell parity or browser identity reset/recovery/export.
|
||||
- One UART1 broker writer, isolated observers and binary transparency. UART0 is administrative recovery; native USB is network-independent UART1, not administration or uninterrupted reboot.
|
||||
- Typed operations carry original-login IDs to the existing dispatcher. Owner-reserved generations fence stale/ABA changes; later revocation/timeout does not cancel admitted work. HTTPS commits before stop/restart; SSH stops before commit/restart; committed identity never rolls back on lifecycle failure. Lost ACK/result means uncertainty, never automatic replay.
|
||||
- Preserve private IDF HTTPD version guards, at-most-one owner-work reservations through failed destruction, retained SSH context until all slots retire, bounded queues/buffers and secret-free metadata. Canonical recovery survives conditional-token exhaustion.
|
||||
- User requests browser Admin shell Wi-Fi parity with SSH/typed settings. Removed only the Wi-Fi status-only policy gate; canonical settings/lifecycle/persistence/diagnostics and hidden secret prompts now work. mDNS remains status-only and unrelated restrictions stay intact. Disruptive commands may cut off the response; owner admission is not peer acknowledgement or cancellation on disconnect.
|
||||
- User now confirms the CLI displays an IPv6 address, direct IPv6 ping works, and the web interface opens using IPv6. This validates those reported unicast operations, not all Phase 12 checks. `ping sak-1024.local -6` fails on the client with “Die Adressfamilie für Hostnamen wird nicht unterstützt.” That earlier client failure was subsequently resolved through Fedora authselect dual-family mDNS; Avahi AAAA lookup and normal IPv6 hostname access succeeded. Do not reopen this as an established firmware discovery defect.
|
||||
- Added three bounded preferred numeric addresses to manager snapshot, copied/cleared with the same TCP/IP observation, and labelled output in `wifi status`. Web/OLED schema unchanged. +48 bytes per snapshot/settings copy and shared static storage; compile rejects >3 lwIP slots. No new allocation/task/socket. Latest `pio run` PASS **94,444 B RAM / 1,853,685 B flash** (+48/+660 vs initial Phase12). No upload/device operations.
|
||||
- PASS: browser policy, new actual Wi-Fi secret-handler/browser-prompt fixture (cancellation/revocation/wiping/history/role guards), account/lifecycle boundary, preferred-address lifecycle/byte-order/rendering/capacity, Network settings, cookie Network and UI171+CSP, diff check. Broader `admin_console_boundary/run.py` passes console/certificate then fails existing SSH-adapter compilation due missing `web_firmware_update_reserve_reboot` fake; left unrelated fixture unchanged. ASan/UBSan unavailable at host link. Hardware directory untouched.
|
||||
|
||||
## Previous documentation consolidation handoff
|
||||
## Phase 12 implementation history — superseded by acceptance above
|
||||
|
||||
Initial Git status was clean. This task changes root `README.md`, `docs/` and five test-directory READMEs; executable source/tests/config/generated assets remain untouched. Independent documentation review checked acceptance scope, owner contracts and local links. It restored explicit pointer-backed HTTPD response-header lifetime and same-version SDK-patch audit warnings, updated test README links, and removed obsolete forwarding notes without reopening sign-off.
|
||||
- User authorized the agreed DHCPv4 hostname / dual-stack SLAAC / STA DNS-SD baseline. Another agent owns `hardware/`; this work did not read or edit it. Do not commit or revert that agent's work. No upload, erase or device operation performed.
|
||||
- Wi-Fi owner applies full `sak-<suffix>` before DHCP and on rename for future exchanges, using permanent TCP/IP-owned storage to preserve the existing 59-byte hostname maximum beyond IDF's setter limit. Preferred IPv6 link-local/ULA/GUA can establish ONLINE without IPv4; link-local-only can retire fallback AP. One-second reconciliation handles stale/missed events. Web/OLED expose availability; the follow-up above adds actual preferred addresses to CLI output.
|
||||
- HTTPS default listener verified dual-stack; shared authority parser now strictly canonicalizes bracketed IPv6 without zone IDs. SSH uses one explicitly dual-stack listener with scope-safe peers, preserving two slots. Service owners publish availability through short portMUX sections; Wi-Fi owner reconciles two DNS-SD records and address families. No new task/socket/broker slot/serial payload buffer, dependency version, partition or generated asset change.
|
||||
- mDNS action API can silently drop queued work; missing families disable each pass, available families repair/re-probe every 30 seconds. Transient stale/zero A window remains until processed; AAAA follows valid-address semantics including deprecated addresses. Upstream calls can block, so polling is not a hard deadline. The later user acceptance establishes the reported device/AAAA behavior, not exhaustive multicast fault/transition testing.
|
||||
- New **narrow** `cmake/mdns_membership.*` overlay fixes verified mDNS 1.12.0 per-family multicast reference imbalance and failed-PCB join cleanup. Only a build-local source copy is changed; version/source hash guarded, managed source immutable. This is not the abandoned Phase 9 patch set. Membership/CMake tests include negative controls and repeated transitions.
|
||||
- Final `pio run` PASS: **94,396 B RAM / 1,853,025 B flash** (+176 / +5,380 versus recorded Phase 10); not runtime headroom. Local saved sdkconfig mDNS capacity changed to 2; durable defaults pin IPv4/IPv6 and two services. Initial bool-atomic target failure fixed with portMUX; final target build includes overlay. Existing SDK Kconfig notes remain.
|
||||
- Host suites PASS: Wi-Fi, mDNS, membership, SSH dual-stack and existing management/runtime/security, auth parser689 (host + actual lwIP), cookie variants, Network settings (after updated netif fakes), UI171+CSP, HTTPS lifecycle45+status8+identity, firmware88+SDKcontract, broker diagnostics and session-store/serial. No network packet, hardware, fault-injection or new high-speed serial pass is implied. Canonical contracts, limits and device checklist: [Phase 12](../roadmap.md#phase-12--advanced-network-integration).
|
||||
|
||||
Validation completed: independent Python local-link/anchor audit PASS across 29 authored Markdown files (122 local links, 53 Markdown fragments); 213 authored source/test/document files checked with zero obsolete Phase 8D filename references. Earlier path audit resolved127 expanded navigation references. GPT logs, vendored/generated trees and remote URL fetching excluded. Twenty obsolete phase documents removed, with no forwarding stubs/archive dump. Git diff/scope checks confirm documentation-only changes. No firmware tests/build/device commands run.
|
||||
## Phase 10 COMPLETE — explicit user acceptance, 2026-09-18
|
||||
|
||||
- User confirmed after firmware upload implementation and the concise-UI fix: “That works perfectly. And the usual operation is also verified.” Acceptance establishes that upload works and normal operation is verified. Do not infer specific fault-injection, NVS before/after comparisons, power-loss or recovery passes. The roadmap's compact regression guidance is reusable, not an acceptance blocker.
|
||||
- Latest concise-UI change was copy-only; reported UI regression **169 groups PASS**. No rebuild after that text change. The integration build below is historical, not validation of a newly rebuilt UI or this documentation update.
|
||||
|
||||
- User authorized simple OTA implementation; initial Git clean. New web_firmware_update module with standard SDK APIs, raw POST /api/firmware, admin cookie/Origin/CSRF and final principal currentness. Settings → HTTPS / Reboot has File/XHR upload progress, confirmation, session fencing and no automatic retries. Only inactive app/otadata writes; NVS/layout untouched. No signatures/antirollback/automaticrollback, dependencies or generated assets changed. Consolidated procedure, contract and regression guidance: [Phase 10](../roadmap.md#phase-10--simple-admin-web-firmware-upload); standalone guide removed.
|
||||
- 4KiB internal buffer + transient2048B-stack reboot owner allocated before erase; 10s stall/120s receive-loop budget, not totalflashdeadline. HTTPD synchronously blocks other web work during upload; networkserial maystall/drop, reboot disruptsall. No task/request/socket lifetime capture after handler. Service/identity reservation and atomic ordinary-reboot gate cover UI/UART0/SSH/browser/localbutton paths. Failed response after bootselect schedules noautomaticreset; selected latch rejects further uploads409, manual reboot available. Successful response schedules500ms reboot retaining reservations.
|
||||
- Review fixed two actualSDK5.5.0 edge cases: failed esp_ota_begin maypublishlivehandle beforeeraseerror (abortthat handle); rawContentLength64 canwrapHTTPDsize_t32 (overflow-safe actualslotbound/strictdecimal/equality check beforebody/erase). End consumes handle evenerror. SDKvalidation followed by exactparsedimage length includingSHA; basic header requiresS3appdescriptor/hash. Unrelated old/privateSDK code unpatched.
|
||||
- Parent final pio PASS **94,220 B RAM / 1,847,645 B flash**, +24RAM/+19,080flash vsPhase9, not runtimeheadroom. Parent newbackend88cases+actualSDKbeginfailurecontract, UI169groups+CSP, serverlifecycle44, admin25, consolelifecycle, SSHruntime, cookielifecyclePASS. Additionalbase/admin/display/lifecyclecookie, idle18, SSHmanagement/runtime/security agentPASS after adding missing rebootfake to adminfixture (no productionchange). Independent review final noactionablefindings; realbuilt firmware parsed with SDKmetadata bothOTAoffsets (notdeviceflashproof).
|
||||
- **Scope decision (2026-09-18):** User removed the BLE transport/provisioning proposal entirely because it no longer fits the project concept. BLE is not planned; retain the existing USB, HTTPS/WebSocket and SSH transport scope. This earlier scope decision did not authorize implementation; Phase 12 was subsequently authorized and implemented as recorded above. Do not resurrect Phase 9 patches. This update changed documentation only; no build, test, upload, erase, device operation or commit was performed.
|
||||
|
||||
## Session logging scope decision
|
||||
|
||||
- User removed the filesystem-backed session recording proposal entirely. On-device session logging/transcripts are out of scope: users capture relevant output in their SSH or web terminal instead. Avoid adding RAM pressure, flash-write interference or potentially incomplete recordings to the serial path. The reserved `storage` partition remains unchanged and unmounted; this decision does not authorize partition migration.
|
||||
|
||||
## Accepted state — 2026-09-18
|
||||
|
||||
- **Reduced Phase 9 complete by explicit user sign-off.** User waived a new whole-phase device check based on prior Phase 8 validation. Application code was unchanged, but no-core-dump/silent-panic defaults changed. Do not record the waived check as executed or claim a new panic/hardware pass.
|
||||
- Small scope: standard `sdkconfig.defaults` options, source-reviewed [operational checklist](../security_operations.md), README/roadmap guidance. No dependency patches, crypto policy replacement, allocator hooks, SDK migration, encryption, eFuse or partition changes. User abandoned the extensive earlier Phase 9 and restored baseline `f40c09c`; do not resurrect it.
|
||||
- Historical Phase 9 build: normal `pio run` PASS on PlatformIO6.12.0 / IDF5.5.0 / original20241119 toolchains, **94,196 B linked RAM / 1,828,565 B flash**. Generated configuration confirmed no dumps/silent panic; compilation inputs had no abandoned overlays/crypto guard. Existing generated config already selected these options. Defaults do not override saved sdkconfig; README explains verification. No new build is implied by this documentation consolidation.
|
||||
- **Phase 8 complete:** 8A–C target validated; explicit 8D.22 user acceptance on 2026-09-13. Completion/telemetry and legacy compatibility now live in [roadmap acceptance](../roadmap.md#phase8-acceptance-evidence) and [storage compatibility](../roadmap.md#phase8-legacy-credential-compatibility). Separate acceptance/legacy history documents removed; active [web contracts](../web_administration.md) and [regression procedures](../user_administration_tests.md) retained.
|
||||
- Post-acceptance baseline includes PSRAM-only ping payload/user snapshot allocations and refined web quick panels. Prior focused host/geometry/build checks passed; those are not additional hardware claims. Preserve lazy-allocation failure isolation and unchanged serial hot path.
|
||||
|
||||
## Evidence limits and follow-ups
|
||||
|
||||
- Previously accepted combined binary WS send: CPU160MHz / 230400 baud full mix including browser admin. The earlier Phase 8 telemetry had very low internal/DMA lifetime minima (2,052/460 B); these are nonblocking headroom follow-ups, not approved reserves or proof of simultaneous allocation failure. Full table, capture workload and counter limits are preserved in the roadmap. The latest Phase 12 full-mix minima are 8,528/772 B, with the same evidence limits.
|
||||
- TLS `-0x004C` means generic NET_RECV_FAILED, not OOM. Historical authentication/admission symptoms do not establish a cause. Do not invent fault, soak, timing or power-loss passes.
|
||||
- Credentials remain unencrypted; old flash contents are not erased. Intermittent trusted-network operation reduces exposure, not physical-extraction risk. Upstream upgrades are separate deliberate tasks, not an endless local backport programme.
|
||||
- Phase 10 is complete by the explicit acceptance above; detailed unreported regression scenarios remain unevidenced, not completion blockers. Device operations, branch/reset, commits and dependency upgrades remain outside this documentation task.
|
||||
|
||||
## Contracts to preserve
|
||||
|
||||
- One UART1 writer, isolated observers and binary transparency. UART0 is administrative recovery; native USB is network-independent UART1, not administration or uninterrupted reboot.
|
||||
- Typed operations carry original-login IDs to the existing dispatcher. Owner-reserved generations fence stale/ABA changes; revocation/timeout does not cancel already admitted work. HTTPS commits before stop/restart; SSH stops before commit/restart; committed identity is not rolled back on lifecycle failure. Lost acknowledgement means uncertainty, not automatic replay.
|
||||
- Preserve private IDF HTTPD version guards, one outstanding owner-work reservation through failed destruction, SSH context lifetime until slots retire, bounded queues/buffers and secret-free metadata. Canonical recovery survives conditional-token exhaustion.
|
||||
- Removed Phase 8 scope stays removed: dedicated typed network diagnostics and ordinary browser-session/USB controls. No implicit browser-shell parity or browser identity reset/recovery/export.
|
||||
|
||||
@@ -128,7 +128,7 @@ Only constraints supported by implementation or current project documentation be
|
||||
|
||||
**Rationale/evidence:** Serial, Wi-Fi, local UI, web security, users, and SSH security each validate schema/size and own their namespace. User/security mutations build and validate candidate state before committing it; security modules avoid silently replacing an established identity. The live user database remains internal while its 5,360-byte candidate is a persistent PSRAM-preferred allocation with internal fallback and is wiped after every transaction.
|
||||
|
||||
**Consequence for future changes:** Add schema versions and transactional candidate validation. Do not overwrite unknown records automatically; provide explicit migration/reset behavior. Preserve the distinct persistence contracts: explicit save/load/default/reset for working configuration and per-blob commit-before-live-install for user and identity mutation. Keep candidate ownership mutex-local and wipe/free it on initialization or recovery failure. Recheck external-buffer staging in the flash/NVS implementation when upgrading from the pinned ESP-IDF 5.5 baseline. Legacy credential synchronization and reconciliation are removed. Missing user storage commits empty; valid user v1 bytes remain compatible, with private `v1_admin_marker` derived from admin count, not a public bootstrap contract. HTTPS v1 (1,392 bytes) migrates through a private validated reader to TLS-only v2 (1,340 bytes), preserving exact DER/fingerprint/generation and committing before publication. Failures fail closed without fallback regeneration or overwriting rejected records. See [legacy compatibility](../legacy_credential_removal.md).
|
||||
**Consequence for future changes:** Add schema versions and transactional candidate validation. Do not overwrite unknown records automatically; provide explicit migration/reset behavior. Preserve the distinct persistence contracts: explicit save/load/default/reset for working configuration and per-blob commit-before-live-install for user and identity mutation. Keep candidate ownership mutex-local and wipe/free it on initialization or recovery failure. Recheck external-buffer staging in the flash/NVS implementation when upgrading from the pinned ESP-IDF 5.5 baseline. Legacy credential synchronization and reconciliation are removed. Missing user storage commits empty; valid user v1 bytes remain compatible, with private `v1_admin_marker` derived from admin count, not a public bootstrap contract. HTTPS v1 (1,392 bytes) migrates through a private validated reader to TLS-only v2 (1,340 bytes), preserving exact DER/fingerprint/generation and committing before publication. Failures fail closed without fallback regeneration or overwriting rejected records. See [legacy compatibility](../roadmap.md#phase8-legacy-credential-compatibility).
|
||||
|
||||
**Relevant files:** `src/serial_config.c`, `src/wifi_config.c`, `src/mdns_config.c`, `src/mdns_service.c`, `src/local_ui_config.c`, `src/web_security.c`, `src/user_database.c`, `src/ssh_security.c`
|
||||
|
||||
@@ -144,13 +144,13 @@ Only constraints supported by implementation or current project documentation be
|
||||
|
||||
## Wi-Fi callbacks enqueue; the manager owns policy
|
||||
|
||||
**Decision:** ESP event callbacks copy bounded event data into the Wi-Fi manager queue. A permanent manager task performs driver operations, profile/AP policy, deadlines, reconciliation, and station mDNS announcement transitions. mDNS initializes at most once, remains allocated across transient disconnects while its component handlers withdraw/re-enable the STA interface, and treats failure as nonfatal.
|
||||
**Decision:** ESP event callbacks copy bounded event data into the Wi-Fi manager queue. A permanent manager task performs driver operations, profile/AP policy, deadlines, reconciliation, and station mDNS announcement transitions. mDNS initializes at most once, remains allocated across transient disconnects, and treats failure as nonfatal. IPv4 or preferred IPv6 including link-local-only establishes ONLINE; route/Internet reachability is not implied. Full DHCP hostname storage and IPv6 state access belong to TCP/IP context. HTTPS/SSH owners only publish availability with short critical sections; Wi-Fi owns DNS-SD record and family reconciliation. Public mDNS actions are not acknowledged, so absent-family disables repeat every pass and healthy enables re-probe on a slower 30-second repair cadence. Record/address withdrawal is eventual, and upstream AAAA includes deprecated-but-valid addresses.
|
||||
|
||||
**Rationale/evidence:** Callback paths avoid blocking, NVS, and policy work. Manager deadlines consult authoritative driver/netif state so dropped events are recoverable.
|
||||
|
||||
**Consequence for future changes:** Keep callbacks short and nonblocking. Add state transitions to the manager rather than directly invoking Wi-Fi policy from consoles, UI, or callbacks. Preserve queue-drop observability.
|
||||
|
||||
**Relevant files:** `src/wifi_manager.{h,c}`, `src/wifi_config.{h,c}`, `src/mdns_service.{h,c}`, `src/mdns_config.{h,c}`
|
||||
**Relevant files:** `src/wifi_manager.{h,c}`, `src/wifi_config.{h,c}`, `src/mdns_service.{h,c}`, `src/mdns_config.{h,c}`. `cmake/mdns_membership.*` is a narrowly reviewed exception for mDNS 1.12.0 multicast join/leave imbalance: patch only a build-local networking source with strict version/hash guards, never the managed source. Dependency upgrades must review/remove the overlay; do not bypass its guards or restore the abandoned Phase 9 patches.
|
||||
|
||||
## Optional local UI cannot become a core dependency
|
||||
|
||||
|
||||
@@ -4,16 +4,18 @@ UART0 and authenticated `admin` SSH sessions use the same registered command imp
|
||||
|
||||
Browser admin uses the same dispatcher with a [narrower parsed frontend policy](web_administration.md#browser-shell-policy), independent of typed Settings permissions. It supports bounded deferred `reboot`, `web stop`, exact `web certificate rotate --force` and owner-relative `exit`. Drain (up to ten seconds plus 200 ms) is best-effort application-buffer acknowledgement, not peer receipt or an execution deadline; pending input is discarded. Certificate work runs on the existing dispatcher through the shared service-before-identity reservation, commits before stop/restart and never rolls back a committed identity after lifecycle failure. Failed stop retains ownership and skips start. Verify changed trust through UART0 `web certificate info`, recover with UART0/admin SSH `web stop` / `web start`, then sign in freshly. HTTPS-only actions leave SSH/native USB/UART0 independent; reboot affects every transport and loses unsaved RAM.
|
||||
|
||||
Browser `web` allows only status/stop/exact forced certificate rotation; `wifi`/`mdns` allow status only. Browser `user` allows status/list/show and interactive add/password plus forced role/delete for **other accounts**, not self/generated/key/recovery commands. Restricted SSH stop/disconnect/reset/host-key mutation remains unavailable in the browser shell. Typed Accounts/Network/SSH settings separately provide their documented bounded workflows; this is not shell parity. See [web administration](web_administration.md) for lifecycle/API ownership and uncertainty.
|
||||
Browser `web` allows only status/stop/exact forced certificate rotation; `mdns` allows status only. Browser `wifi` supports the canonical commands, including bare `wifi` status, settings, hidden secret prompts and explicit `wifi ap show-secret`. Secret input is not echoed, retained in history or completed. Wi-Fi changes can disconnect the browser before a result arrives, without deferred drain or cancellation; reconnect and inspect before retrying, or recover through UART0. Browser `user` allows status/list/show and interactive add/password plus forced role/delete for **other accounts**, not self/generated/key/recovery commands. Restricted SSH stop/disconnect/reset/host-key mutation remains unavailable in the browser shell. Typed Accounts/Network/SSH settings separately provide their documented bounded workflows; this is not shell parity. See [web administration](web_administration.md) for lifecycle/API ownership and uncertainty.
|
||||
|
||||
## System
|
||||
|
||||
| Command | Description |
|
||||
|---|---|
|
||||
| `memory` | Show free memory, minimum free memory, and largest blocks for internal RAM, DMA-capable RAM, and PSRAM. |
|
||||
| `reboot` | Drain console output briefly and restart the ESP32. |
|
||||
| `reboot` | Drain console output briefly and restart the ESP32; refused while a firmware upload or another reboot owns exclusion. |
|
||||
| `exit` | Close the current administrative SSH or browser session after its acknowledgement drains; unavailable on UART0. Browser `exit` leaves serial connected. Ctrl+D on an empty administrative command line does the same. |
|
||||
|
||||
Firmware upload is an admin **Settings → HTTPS / Reboot** action, not a shell command. Ordinary UART0/admin-SSH/browser-shell/local-display and typed reboot paths are gated against an active upload. If upload boot selection succeeded but its response failed, no automatic restart is scheduled: the selected image remains, another admissible upload returns 409 until reset, and a deliberate manual `reboot` is permitted after reservations release. Inspect first; a lost response is not cancellation. See [firmware update and wired recovery](roadmap.md#phase10-update-and-recovery). Do not erase for an ordinary update; native USB is UART1 access, not recovery administration.
|
||||
|
||||
## Role-based users
|
||||
|
||||
| Command | Description |
|
||||
@@ -106,12 +108,38 @@ Opening `/dev/ttyACM*` with DTR asserted creates the `usb-cdc` broker client, st
|
||||
| `wifi ap secret` / `wifi ap show-secret` | Set or reveal the AP password. |
|
||||
| `wifi save|load|defaults|reset` | Persist, restore, reset in RAM, or reset and persist configuration. |
|
||||
| `wifi counters|clear-counters` | Show or clear Wi-Fi counters. |
|
||||
| `wifi ping <host> [count]` | Send 1–20 IPv4 or IPv6 ICMP probes. |
|
||||
| `wifi nslookup <host>` | Resolve and display unique IPv4/IPv6 addresses. |
|
||||
| `wifi traceroute <host> [max-hops]` | Run IPv4 ICMP traceroute with up to 30 hops. |
|
||||
| `wifi ping [-4\|-6] <host> [count]` | IPv4/IPv6 ICMP echo; 1–20 probes, default 4. |
|
||||
| `wifi nslookup [-4\|-6] <host>` | Explicit A/AAAA lookup; default queries both families. |
|
||||
| `wifi traceroute [-4\|-6] <host> [max-hops]` | IPv4/IPv6 ICMP echo traceroute; 1–30 hops, default 16. |
|
||||
|
||||
`ping`, `nslookup`, and `traceroute` are root aliases. The four station-profile slots use lower priority values first. Edits to a disabled profile's SSID, priority, security mode, or secret are staged in RAM and do not interrupt the current Wi-Fi connection. Enabling or disabling a profile, changing an enabled profile, or changing AP policy/configuration applies the new radio policy and may reconnect Wi-Fi. Use `wifi save` to persist working changes. Passwords are not displayed by ordinary status output.
|
||||
|
||||
### Dual-stack network diagnostics
|
||||
|
||||
Root `ping`, `traceroute` and `nslookup`, and their `wifi` aliases, share one implementation on **UART0, admin SSH and the browser Admin console**. They execute on the ESP32, not the client computer or the attached UART1 equipment. Ordinary serial-role users do not gain administration access. `-4` and `-6` completion is available for all six command prefixes.
|
||||
|
||||
```text
|
||||
ping -6 example.com 4
|
||||
ping example.com 4 -4
|
||||
wifi ping -6 fe80::1%sta 3
|
||||
traceroute -6 example.com 16
|
||||
wifi traceroute -4 example.com 16
|
||||
nslookup example.com
|
||||
nslookup -6 peer.local
|
||||
```
|
||||
|
||||
One optional family selector can appear before or after the host/count; duplicate/conflicting selectors and unknown options are rejected. Probe counts/hop limits are positional, not Linux `-c`/`-m` options. Without a selector, numeric addresses retain their family and probe hostnames try IPv4 resolution, then IPv6 if no address was obtained. A failed probe does **not** retry the other family; this is not Happy Eyeballs. Explicit `-6` never silently uses IPv4.
|
||||
|
||||
Link-local **probe destinations** require a scope on the ESP32: `%sta`, `%ap`, an existing numeric lwIP interface index, or an existing lwIP interface name. These are not the browser/SSH computer's interface names. If name resolution returns an unscoped link-local address, `nslookup` displays it, but ping/traceroute request an explicit scoped literal rather than guessing an interface. Numeric `nslookup` input is displayed without DNS/reverse lookup.
|
||||
|
||||
`nslookup` without a selector performs separate A and AAAA queries. The installed resolver returns one selected address per family (`CONFIG_LWIP_DNS_MAX_HOST_IP=1`), not a complete DNS RRset. Missing records, DNS timeouts and server failures can share the same SDK error; partial success is displayed rather than hidden. Unicast names require a usable configured DNS server; SLAAC alone does not provision one in this firmware. `.local` uses the existing lwIP mDNS query support. No DNS server or RA/DHCP policy is changed by these commands.
|
||||
|
||||
Traceroute sends one eight-byte echo probe per hop with a one-second absolute receive deadline; it can stop up to 999 microseconds early to avoid the SDK's zero-millisecond/infinite-wait conversion. Hop addresses are numeric. ICMPv6 replies are checked against the actual IDF raw framing/checksum, destination and probe identity; IPv6 extension headers/fragments in replies or quotes are unsupported and ignored. Filtering/rate limiting can produce `*` even when ordinary connectivity works. DNS, scheduler and send latency are outside the receive deadline.
|
||||
|
||||
Ping retains the SDK's bounded-count transient task and existing PSRAM event queue. Its console wait is bounded to `count * 2 + 2` seconds after start, but unrelated ICMP can extend the SDK receive loop: after a console timeout, further pings are refused until the old END event arrives. END finishes callback production; SDK resource teardown may briefly overlap a subsequent session. No IPv6 TTL is printed because the installed SDK does not provide a valid received IPv6 hop-limit profile. These commands serialize the administration dispatcher while executing; they do not run on HTTPD or change the broker/serial path.
|
||||
|
||||
Focused host/SDK and surface tests: `tests/network_diagnostics/` and `tests/network_diagnostics_surfaces/`. Real IPv4/IPv6/scoped ping, DNS and multi-hop traceroute remain device-validation tasks for this follow-up.
|
||||
|
||||
## mDNS
|
||||
|
||||
| Command | Description |
|
||||
@@ -121,7 +149,9 @@ Opening `/dev/ttyACM*` with DTR asserted creates the `usb-cdc` broker client, st
|
||||
| `mdns save` / `mdns load` | Save the working suffix to its independent NVS record or load it. |
|
||||
| `mdns defaults` / `mdns reset` | Restore the MAC-derived suffix in RAM, or restore and persist it. |
|
||||
|
||||
When the Wi-Fi station receives an IPv4 address, the Wi-Fi manager announces `sak-<suffix>.local`. The default suffix is the lower-case hexadecimal STA MAC address. Suffixes may contain lowercase ASCII letters, digits, and internal hyphens only. Changing a suffix while online causes a best-effort reannouncement; mDNS failures do not stop Wi-Fi, UART0, UART1, or native USB access.
|
||||
When the Wi-Fi station has IPv4 or a preferred IPv6 address, the Wi-Fi manager announces `sak-<suffix>.local` and advertises available HTTPS/SSH services through DNS-SD. This is STA-only, local-link discovery, not certificate or host-key trust. The default suffix is the lower-case hexadecimal STA MAC address. Suffixes may contain lowercase ASCII letters, digits, and internal hyphens only. Changing a suffix queues a best-effort reannouncement and updates the DHCPv4 hostname (`sak-<suffix>`, without `.local`) for subsequent DHCP exchanges; it does not force a lease restart. A configured DHCP/DNS server may publish that name in its own zone.
|
||||
|
||||
`wifi status` and browser Network status distinguish IPv4 absence and IPv6 link-local/ULA/GUA availability. `wifi` / `wifi status` and the web Network settings dialog additionally list up to three actual preferred IPv6 addresses, labelled link-local, ULA or GUA; these are copied with the flags, not inferred from enabled IPv6 support. A link-local destination needs the client's interface as its zone. To separate client address preference from IPv6 reachability, run `ping -6 -c 3 sak-1024.local` on an IPv6-capable client; choosing IPv4 with plain `ping` does not mean the device lacks IPv6. If lookup fails, inspect AAAA resolution (for example `avahi-resolve-host-name -6 sak-1024.local` where Avahi is installed), then test the numeric address from `wifi status` directly. Link-local-only connectivity counts as `ONLINE` and can retire the fallback AP after the existing stability interval; it does not establish Internet access. HTTPS/WebSocket and SSH support both families; browser IPv6 literals require brackets and cannot contain interface zones. Use the `.local` hostname for link-local browser access where supported by the client. On-device diagnostics support explicit family selection and scoped IPv6 as described above; Linux client syntax and interface scope are separate. mDNS failures do not stop Wi-Fi, UART0, UART1, or native USB access. See [Phase 12](roadmap.md#phase-12--advanced-network-integration) for discovery convergence limits and recorded user acceptance.
|
||||
|
||||
## HTTPS web terminal
|
||||
|
||||
@@ -189,4 +219,4 @@ UART0 and admin SSH use shared whole-line Tab completion. A unique/common prefix
|
||||
|
||||
Follow the exact wiring in [Electrical tests](electrical_tests.md) before invoking diagnostics. The OLED must be powered from 3.3 V because module I²C pull-ups may connect to `VCC`; verify that all external pull-ups also terminate at 3.3 V. Display diagnostics probe the standard SSD1315-compatible 7-bit `0x3c`/`0x3d` addresses. The currently tested module acknowledges at `0x3c`, whose 8-bit write/read forms are `0x78`/`0x79`; an explicit `scan --force` is available only for the dedicated local-UI bus. Diagnostics initially run at 100 kHz and treat an absent display as nonfatal. RS-232 diagnostics that require UART1 refuse to use it until `serial stop` releases it. The RGB LED shows test state: blue idle, yellow/orange running, green passed, red failed.
|
||||
|
||||
HTTPS storage migration preserves the exact TLS identity and commits TLS-only v2 before publication. Older v1-only firmware cannot read v2. Logical NVS replacement is not secure flash erasure; no factory erase is required. See [legacy removal](legacy_credential_removal.md).
|
||||
HTTPS storage migration preserves the exact TLS identity and commits TLS-only v2 before publication. Older v1-only firmware cannot read v2. Logical NVS replacement is not secure flash erasure; no factory erase is required. See [legacy removal](roadmap.md#phase8-legacy-credential-compatibility).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Bounded ordinary HTTPS idle retention
|
||||
|
||||
Current owner/SDK contract. The user accepted idle cleanup as working; [overall acceptance](web_administration_acceptance.md) records current status and evidence limits. No individual unreported soak, fault or reserve result is inferred.
|
||||
Current owner/SDK contract. The user accepted idle cleanup as working; [overall acceptance](roadmap.md#phase8-acceptance-evidence) records current status and evidence limits. No individual unreported soak, fault or reserve result is inferred.
|
||||
|
||||
## Evidence and scope
|
||||
|
||||
@@ -53,7 +53,7 @@ Detach first prevents submissions, then waits at most one second for any submitt
|
||||
|
||||
`tests/web_httpd_idle/run.py` exercises production lifecycle/sweep paths, installed SDK request cleanup, host socketpair/readiness/shutdown/fd reuse and deterministic TLS/timer/queue doubles. `tests/web_admin_transport/server_lifecycle.py` checks server composition. Historical host validation passed; these references do not claim a new run or real target scheduler/TLS timing.
|
||||
|
||||
Bounded storage: six observation rows (144 bytes), one persistent timer (installed non-profiled layout 32 bytes before allocator overhead), lifecycle/generation flags and one reserved probe. SDK control UDP/mailbox allocation is transient and separate. No per-connection payload/task/stack/socket growth. Actual HTTPD/timer margins and runtime overhead/reserve floors require target measurement, not host sizes. See [latest firmware resources](web_administration_acceptance.md#latest-firmware-evidence).
|
||||
Bounded storage: six observation rows (144 bytes), one persistent timer (installed non-profiled layout 32 bytes before allocator overhead), lifecycle/generation flags and one reserved probe. SDK control UDP/mailbox allocation is transient and separate. No per-connection payload/task/stack/socket growth. Actual HTTPD/timer margins and runtime overhead/reserve floors require target measurement, not host sizes. See [latest firmware resources](roadmap.md#phase8-acceptance-evidence).
|
||||
|
||||
## Regression procedure — not execution evidence
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
# Legacy credential removal and storage compatibility
|
||||
|
||||
Current storage/recovery contract. The user accepted cleanup on 2026-09-08, confirming the certificate fingerprint was unchanged and preexisting test users remained usable. [Overall acceptance and evidence limits](web_administration_acceptance.md) supersede the old per-slice handoff; no unreported provisioning, recovery, power-loss or all-key tests are implied.
|
||||
|
||||
## User database
|
||||
|
||||
- `user_database_init(load_result)` takes no legacy credential. Missing `user_db/database` storage is committed as an empty v1 database; no account/password is imported.
|
||||
- Create the first administrator on physical UART0 with `user add <username> admin`, optionally `--generate`. Entered passwords use hidden confirmation; generated passwords are shown once. There is no reserved bootstrap account or public bootstrap state.
|
||||
- `user recover --force` is UART0-only, calls `user_database_recover_empty()`, and destructively rebuilds only an unavailable database empty. It refuses a healthy initialized database, including an empty one. Follow with normal `user add`; unrelated configuration and TLS/SSH identities are untouched.
|
||||
- Valid existing v1 database bytes load without rewriting or changing accounts, roles, IDs, authentication generations, verifiers or authorized keys. Previously migrated role-`user` accounts are not automatically promoted. The private `v1_admin_marker` preserves the old wire byte and is derived from administrator count during mutations; it is not a new role, public bootstrap field or schema change.
|
||||
- No public bootstrap, legacy user migration or synchronization API remains. Final-admin protection, conditional mutations, copied principal currentness and target-only revocation remain the canonical account contracts.
|
||||
|
||||
## HTTPS identity storage
|
||||
|
||||
`web_security` owns only TLS material. At the unchanged `web_sec/material` key, a private byte-oriented reader validates the shipped **1,392-byte v1** layout and builds **1,340-byte TLS-only v2**. It retains the **exact private-key DER, certificate DER, SHA-256 fingerprint and material generation**: this migration is not certificate rotation.
|
||||
|
||||
The v2 candidate is validated and committed before live publication. Temporary credential-bearing v1 input is wiped. No public legacy credential reader/type, display, rotation or authentication path remains. Unknown/malformed material, read failures, invalid cryptographic identity and migration write/commit failures fail closed; they do not trigger fallback regeneration or overwrite of rejected material. Truly missing material may be generated and saved normally.
|
||||
|
||||
`web credentials show`, `web credentials rotate --force` and `user bootstrap` (including its generated form) are removed. `web certificate info` and `web certificate rotate --force` remain subject to existing frontend policy. `web reset --force` explicitly replaces TLS certificate/private key only; it does not reset passwords, import accounts or synchronize verifiers. TLS replacement/restart can close browser sessions and requires renewed certificate trust/login, without changing user credentials or revoking unrelated SSH sessions.
|
||||
|
||||
## Compatibility and physical-security limits
|
||||
|
||||
**Older v1-only firmware cannot read v2 HTTPS storage.** Do not assume a downgrade preserves usable HTTPS or restores removed credentials. Existing user database v1 compatibility is separate from this HTTPS downgrade incompatibility.
|
||||
|
||||
Replacing an NVS blob logically removes credential fields from the current record; it is **not secure flash wiping**. Append-oriented historical copies may retain plaintext legacy credentials, and current Wi-Fi secrets/TLS/SSH private keys remain unencrypted. PBKDF2 verifiers remain subject to offline guessing. No factory/partition erase is required or authorized by this cleanup.
|
||||
|
||||
## Regression references
|
||||
|
||||
Use `tests/web_security/run.py`, `tests/admin_console_boundary/accounts.py`, `tests/admin_ssh_policy/run.py` and [current legacy-removal procedures](user_administration_tests.md#current-legacy-removal-regression-procedure). Prior host tests used NVS fault doubles and a generated identity wrapped in the historical TLS layout, not an independently captured old-device fixture; they do not prove flash durability or power-loss behavior. Procedures are not claims of execution.
|
||||
@@ -1,10 +1,10 @@
|
||||
# Implementation roadmap
|
||||
|
||||
This document tracks the implementation and hardware-validation plan for the ESP32 Serial Swiss Army Knife. It describes intended sequencing rather than a release schedule. A phase is complete only when its implementation, documentation, build validation, and relevant hardware tests have passed.
|
||||
This document tracks the implementation and hardware-validation plan for the ESP32 Serial Swiss Army Knife. It describes intended sequencing rather than a release schedule. Completion requires implementation, documentation and build validation, plus relevant hardware tests or an explicitly recorded user waiver. A waiver is not a hardware-test pass.
|
||||
|
||||
## Status legend
|
||||
|
||||
- **Complete** — implemented and validated on the target hardware.
|
||||
- **Complete** — implemented and accepted, with target-hardware validation or an explicit phase-specific user waiver recorded below.
|
||||
- **In progress** — implementation or validation is actively underway, but the overall phase is not complete.
|
||||
- **Implemented; validation pending** — code is present and builds, but the current implementation still needs the listed hardware checks.
|
||||
- **Planned** — accepted project direction, not yet implemented.
|
||||
@@ -38,12 +38,11 @@ These constraints apply across all phases:
|
||||
| 5B | Offline xterm.js WebSocket serial terminal | **Complete** |
|
||||
| 6 | Authenticated SSH serial transport | **Complete** |
|
||||
| 7 | Local display and button interface | **Complete** |
|
||||
| 8 | Role-based users and administrative access | **In progress (8A–8C complete; integrated web administration 8D planned)** |
|
||||
| 9 | Security and production hardening | **Planned** |
|
||||
| 10 | Authenticated, rollback-capable OTA | **Planned** |
|
||||
| 11 | BLE serial transport and provisioning evaluation | **Planned** |
|
||||
| 12 | Advanced network integration | **Under evaluation** |
|
||||
| 13 | Optional filesystem-backed features | **Under evaluation** |
|
||||
| 8 | Role-based users and administrative access | **Complete** |
|
||||
| 9 | Small intermittent-use security baseline | **Complete (user signoff 2026-09-18; new hardware check waived)** |
|
||||
| 10 | Simple admin web firmware upload | **Complete (explicit user acceptance 2026-09-18; upload and normal operation verified)** |
|
||||
| 12 | Dual-stack networking and local service discovery | **Complete (explicit user validation; fresh boot and full client mix at 230400 baud)** |
|
||||
|
||||
|
||||
## Completed phases
|
||||
|
||||
@@ -200,93 +199,173 @@ Completed implementation (8A–8C target-hardware validated; 8D explicitly accep
|
||||
- Typed Serial, Accounts/password/authorized-key, Network/Wi-Fi/mDNS, Display, Broker and SSH settings; confirmed HTTPS stop/restart/reboot and HTTPS/SSH identity rotation. Canonical owners compare reserved service/identity/target generations, preserve commit/failure semantics and expose no stored secrets. Admitted work is not cancelled by later logout/timeout; uncertain mutations are never automatically replayed.
|
||||
- Accessible Serial/Wi-Fi/client/writer contextual controls reuse existing controllers. Writer transfer is explicit and generation-safe; one writer, isolated observers, binary transparency, UART0 administrative recovery and network-independent native USB UART1 remain invariants.
|
||||
- Scope removed by user: **8D.15** dedicated typed network diagnostics (shell diagnostics retained) and the unimplemented **8D.19** ordinary browser-session/native-USB controls (SSH controls retained). Browser identity reset/recovery/export is excluded; canonical recovery remains available.
|
||||
- Prior final build PASS: **100,556 B linked RAM / 1,828,573 B flash, CPU 160 MHz**. Earlier combined binary WebSocket-send fix was explicitly accepted at **160 MHz / 230400 baud with full client mix including browser admin**. Latest overall sign-off does not invent individual fault/soak/duration results or a latest exact zero-drop comparison.
|
||||
- Latest loaded internal/DMA minima **2,052 / 460 B** remain a conservative transient-headroom follow-up, not a blocker reopening acceptance or an approved reserve. See [acceptance and telemetry](web_administration_acceptance.md), [current contracts](web_administration.md), [regression procedures](user_administration_tests.md#integrated-web-administration-regression-procedure) and [legacy compatibility](legacy_credential_removal.md).
|
||||
- Acceptance evidence and legacy storage compatibility are condensed below. [Web administration](web_administration.md) retains current API/owner/recovery contracts; [regression procedures](user_administration_tests.md#integrated-web-administration-regression-procedure) retain practical checks, not claims of execution.
|
||||
|
||||
## Current and planned phases
|
||||
#### Phase8 acceptance evidence
|
||||
|
||||
Phase 8 is complete for its accepted scope. Phase 9 is next **only on a separate user request**; later work remains planned or under evaluation. Optional features must not weaken completed serial and recovery paths. General release gates below remain guidance for future work, not claims that every listed fault, soak or reserve measurement was individually performed for 8D.
|
||||
**8D.22 signoff, 2026-09-13:** the user explicitly accepted the tested firmware, superseding earlier per-slice target, review and integration gates for the retained scope above. Earlier scoped acceptance covered browser login/logout, shared administration, Serial/account/Network presentation and legacy cleanup. Prior host regression suites and independent reviews were reported passing; this consolidation asserts no new build or test execution.
|
||||
|
||||
Prior final Phase 8 build: **PASS, 100,556 B linked RAM / 1,828,573 B flash, CPU 160 MHz**. The combined binary WebSocket-send fix was separately accepted at **160 MHz / 230400 baud with the full client mix including browser admin**; retain combined send and bounded failed-send isolation, not the earlier frequency-only experiment. Ordinary HTTPS idle cleanup was accepted, not every possible admission failure or owner delay.
|
||||
|
||||
Latest reported Phase 8 telemetry (bytes):
|
||||
|
||||
| Sample / capability | Free | Minimum free | Largest block |
|
||||
|---|---:|---:|---:|
|
||||
| Boot internal 8-bit | 59,808 | 58,840 | 31,744 |
|
||||
| Boot internal DMA | 52,052 | 51,084 | 31,744 |
|
||||
| Boot PSRAM | 8,196,968 | 8,183,972 | 8,126,464 |
|
||||
| Loaded after burst, internal 8-bit | 31,508 | 2,052 | 18,432 |
|
||||
| Loaded after burst, internal DMA | 23,752 | 460 | 18,432 |
|
||||
| Loaded after burst, PSRAM | 8,136,624 | 8,065,972 | 7,995,392 |
|
||||
|
||||
Loaded SSH minimum-free stack: **15,028 B**. This sample had two SSH sessions (serial/admin), two serial WebSockets and USB, with SSH as writer. Browser admin had been used then closed; it was **not active in this capture**. Web send/queue/protocol and SSH IO errors were zero; one SSH handshake failure and one session revocation were reported without an established cause.
|
||||
|
||||
**Limits, not reopened acceptance gates:** missing latest broker/serial counters prevent an exact zero-drop or byte-integrity inference. No latest browser-admin-active full-mix, individual fault-injection, exact duration, soak, cleanup-cycle or reserve-floor result is inferred. Internal/DMA lifetime minima remain a nonblocking transient-headroom follow-up, not approved reserves or proof of allocation failure: capability pools overlap and per-region minima may be non-simultaneous, so do not sum them. HTTPD/dispatcher stack margins, correlated peaks and allocation-reserve policy remain unmeasured/unapproved here. SDK TLS `-0x004C` means NET_RECV_FAILED, not an OOM diagnosis; stale cookies as the cause of two boot authentication failures remain unconfirmed.
|
||||
|
||||
#### Phase8 legacy credential compatibility
|
||||
|
||||
Cleanup was accepted **2026-09-08**, with unchanged certificate fingerprint and usable preexisting test users. This is not an unreported provisioning, recovery, power-loss or all-key pass.
|
||||
|
||||
- **Users:** missing `user_db/database` is committed as an empty v1 database, with no legacy account/password import. First-admin provisioning is physical UART0 `user add <username> admin` (optional `--generate`); entered passwords use hidden confirmation and generated passwords appear once. UART0-only `user recover --force` rebuilds only an unavailable database empty, refuses healthy databases (including empty ones), and leaves other configuration and TLS/SSH identities untouched; follow with normal `user add`.
|
||||
- Valid existing v1 user bytes load without rewrite or changes to accounts, roles, IDs, authentication generations, verifiers or authorized keys; migrated `user` accounts are not promoted. Private `v1_admin_marker` preserves the old wire byte and is derived from admin count on mutation, not a new role/schema/public bootstrap state. Public bootstrap and legacy migration/synchronization APIs are removed; final-admin protection, conditional mutations, principal currentness and targeted revocation remain.
|
||||
- **TLS:** at unchanged `web_sec/material`, a private reader validates the **1,392-byte v1** layout and migrates to **1,340-byte TLS-only v2**, preserving exact private-key DER, certificate DER, SHA-256 fingerprint and material generation—not rotating identity. The candidate is validated and committed before publication; temporary credential-bearing input is wiped. Malformed/unknown material, read/crypto failures and migration write/commit failures fail closed without regeneration or overwrite; truly missing material may be generated normally.
|
||||
- `web credentials show`, `web credentials rotate --force` and `user bootstrap` are removed. `web certificate info` / `web certificate rotate --force` retain frontend policy. `web reset --force` replaces TLS identity only, not accounts/passwords/verifiers; replacement/restart may close browser sessions and require renewed certificate trust/login, without revoking unrelated SSH sessions. No public legacy credential reader, display, rotation or authentication path remains.
|
||||
- **Downgrade/security:** older v1-only firmware cannot read v2 HTTPS storage; user-database v1 compatibility does not make HTTPS downgrade safe or restore removed credentials. Logical NVS replacement is **not secure flash wiping**: historical plaintext legacy credentials may remain, Wi-Fi secrets and TLS/SSH private keys remain unencrypted, and PBKDF2 verifiers permit offline guessing. No erase is required or authorized by cleanup.
|
||||
- Prior host tests used NVS fault doubles and a generated identity wrapped in the historical layout, **not an independently captured old-device fixture**; they do not prove flash durability or power-loss behavior. Focused runners: `tests/web_security/run.py`, `tests/admin_console_boundary/accounts.py`, `tests/admin_ssh_policy/run.py`. See [legacy-removal regressions](user_administration_tests.md#current-legacy-removal-regression-procedure).
|
||||
|
||||
### Phase 9 — Security and production hardening
|
||||
|
||||
Harden stored credentials, boot integrity, update trust, failure diagnostics, and dependency maintenance. This phase must be staged carefully because some ESP32 eFuse operations are irreversible.
|
||||
**Complete — user signoff 2026-09-18; new hardware check explicitly waived.** Reduced scope: an intermittently powered wireless serial cable on a trusted network, not a permanently exposed Internet service. The earlier extensive Phase 9 implementation was abandoned in favor of the accepted pre-Phase-9 baseline. Short exposure reduces risk; it does not make upstream defects harmless or certify this firmware secure.
|
||||
|
||||
Planned work:
|
||||
Small implementation:
|
||||
|
||||
- Define development, test, and production security profiles.
|
||||
- Enable and validate NVS encryption for Wi-Fi, HTTPS, and SSH material.
|
||||
- Evaluate flash encryption and PSRAM encryption for the target hardware and threat model.
|
||||
- Enable secure boot with a documented signing-key and recovery process.
|
||||
- Define secret-safe core-dump handling; do not expose credentials or session material in diagnostics.
|
||||
- Audit key and session-material zeroization across application, mbedTLS, wolfSSL, and wolfSSH allocations.
|
||||
- Add authentication throttling, lockout/backoff, and security-event counters where appropriate.
|
||||
- Review TLS/SSH algorithms, certificate policy, password policy, and browser security headers.
|
||||
- Review current wolfSSL/wolfSSH and ESP-IDF security releases instead of treating integration versions as permanent pins.
|
||||
- Recheck all third-party licenses and preserve source/license notices.
|
||||
- Document provisioning, key rotation, factory reset, backup, recovery, and device decommissioning.
|
||||
- Keep the baseline PlatformIO/ESP-IDF and managed-library versions and upstream sources. No generated dependency patches, custom SSH state machine, crypto-policy replacement, allocator hooks, or SDK migration in this phase.
|
||||
- Explicitly disable core dumps and select silent panic reboot through standard `sdkconfig.defaults` options. Keep ordinary logging; reduced crash diagnostics are a deliberate trade-off. Existing generated configurations must be checked because defaults do not override saved selections.
|
||||
- Follow the [operational checklist](security_operations.md): unique credentials, trusted identity verification, no Internet forwarding, networking off/power off after use, and existing rotation/recovery procedures.
|
||||
- Review upstream maintenance releases occasionally and before planned upgrades. Evaluate reachable issues against actual use; any stock dependency upgrade is a separate build/device-tested task, not a reason to accumulate local backports. Preserve existing license notices.
|
||||
|
||||
Security features must be validated first on expendable hardware before any irreversible production configuration is recommended.
|
||||
**Excluded:** secure boot, NVS/flash/PSRAM encryption, eFuse changes, partition changes, exhaustive CVE/zeroization audits, new authentication throttling, and production certification. Existing authentication, session isolation and recovery behavior remain unchanged. There is no protection against physical extraction or replacement of firmware. Phase 10 uses administrator-selected firmware, without a separate image-signing system.
|
||||
|
||||
### Phase 10 — Authenticated OTA and rollback
|
||||
**Host/build evidence (2026-09-18):** normal `pio run` passed on PlatformIO 6.12.0 / ESP-IDF 5.5.0 with the original 20241119 toolchains: **94,196 B linked RAM / 1,828,565 B flash**. Generated configuration confirms no dumps and silent panic reboot; compilation inputs contain none of the abandoned Phase 9 source overlays or wolf crypto guards. No device operation or target validation was performed.
|
||||
|
||||
Use the existing dual 4 MiB application slots and `otadata` partition to add safe firmware updates.
|
||||
**Acceptance (2026-09-18):** the user signed off this small Phase 9 and explicitly waived a new whole-phase hardware check of boot, UART0 recovery, native USB serial, HTTPS/SSH login and normal serial sharing. Application code is unchanged, **but crash-diagnostic defaults changed**; this is a waiver, not evidence of target or panic-path validation. The prior build/configuration evidence above remains the validation record; no new hardware test was performed. A separately controlled panic check with synthetic secrets remains optional, not a completion gate; do not add a production panic endpoint or erase flash. Disabling dumps does not erase old contents.
|
||||
|
||||
Planned work:
|
||||
### Phase 10 — Simple admin web firmware upload
|
||||
|
||||
- Accept only authenticated, integrity-checked firmware images compatible with the selected secure-boot policy.
|
||||
- Support an update path that does not expose a plaintext management endpoint.
|
||||
- Stream downloads with bounded RAM use and explicit progress/error reporting.
|
||||
- Preserve serial, Wi-Fi, HTTPS, and SSH configuration across successful updates.
|
||||
- Mark new firmware pending until a post-boot health check succeeds.
|
||||
- Roll back automatically after failed boot or health confirmation.
|
||||
- Define power-loss behavior for every update stage.
|
||||
- Keep UART0 recovery and wired re-flashing documented and functional.
|
||||
- Add version/compatibility checks and reject accidental downgrade unless an explicit policy allows it.
|
||||
**Complete — explicit user acceptance on 2026-09-18: firmware upload works and normal operation is verified.** The admin **Settings → HTTPS / Reboot** firmware card uploads the locally built `.pio/build/esp32-s3-devkitc-1-n16r8/firmware.bin`. Install this OTA-enabled firmware by wire first, then use web application uploads. There is no release server, automatic download or update service. [Firmware update and recovery](#phase10-update-and-recovery) below covers operation; [regression guidance](#phase10-regression-guidance) covers future checks.
|
||||
|
||||
OTA is complete only after successful update, interrupted-update, invalid-image, rollback, and configuration-persistence tests on hardware.
|
||||
Implemented scope:
|
||||
|
||||
### Phase 11 — BLE
|
||||
- Raw `POST /api/firmware` requires exact `Content-Type: application/octet-stream`, known exact `Content-Length`, cookie, same-origin Origin and `X-CSRF-Token`; no multipart/JSON, Basic authentication or ordinary `user` access. Reject queries, ambiguous headers, chunked transfer and `Expect`; UI code leaves browser-managed headers alone. Admission precedes body/flash work; session/principal currentness is rechecked before boot selection. The card confirms upload/reboot and shows progress/results without automatic retries. HTTPD capacity is 40 method/path handlers.
|
||||
- Standard ESP-IDF OTA APIs (`esp_ota_begin`, `esp_ota_write`, `esp_ota_end`, `esp_ota_set_boot_partition`) stream one upload through a 4 KiB buffer into the inactive slot (two 4 MiB app slots). A reboot task with a 2 KiB stack is preallocated before erase. Lifecycle/identity reservations and ordinary software-reboot gating exclude competing work; no custom flash protocol or whole-image RAM buffer.
|
||||
- Accepts an ESP32-S3 application binary for this board/layout, not an ELF, ZIP, merged full-flash image, bootloader or partition-table image. Browser hints require a nonempty `.bin` of at most 4 MiB; the server bounds raw length against actual destination capacity before trusting HTTPD's narrowed length. Checks completeness, target/header and exact SDK image length/integrity, including mandatory appended SHA-256, before selection. The digest is not publisher authentication; neither it nor filename checks prove board compatibility or a working application.
|
||||
- Before selection, rejected/incomplete uploads and receive/write/validation failures abort any live OTA handle without selecting the candidate; the inactive slot may be erased/partially written. Boot-metadata failure (`firmware_commit_failed`) needs inspection and carries SDK transactional uncertainty. `200 {"ok":true,"rebooting":true}` means validation/selection succeeded; successful synchronous send schedules restart after 500 ms, retaining reservations, but proves neither browser receipt nor boot success. Response failure after selection schedules no restart, releases reservations for manual reboot and latches further admissible uploads to 409 `firmware_selected_reboot_required`; the latch survives HTTPS stop/start until device reset.
|
||||
- Synchronous HTTPD receive/flash blocks other HTTPD work: browser sessions can stall/drop. The 120-second total receive-loop and ten-second stall checks use the existing one-second socket timeout, not an absolute deadline: synchronous SDK erase/write/validation and scheduling are not preempted. The browser's 180-second timeout cannot cancel committed work. UART0/native USB remain independent paths, not guarantees of uninterrupted serial timing during flash; reboot interrupts all transports. Software exclusion cannot prevent physical reset, power loss or panic.
|
||||
- **Preserve NVS:** write only the inactive application slot and the OTA selection metadata. Do not erase the chip, rewrite the partition table/bootloader, or touch `nvs`, `nvs_key`, PHY, storage or other data partitions. Existing users, passwords, Wi-Fi/serial settings and HTTPS/SSH identities remain stored, as with an application-only wired update without erase. This preserves stored bytes; the uploaded firmware must still understand the existing schemas and must not itself erase/migrate them incompatibly.
|
||||
- Keep wired USB-to-UART flashing documented as recovery if the uploaded application does not boot or no longer serves the web UI. Basic image validation cannot prevent a valid but broken application from requiring wired recovery.
|
||||
|
||||
Evaluate and, if resource limits permit, add BLE as another bounded transport or provisioning path.
|
||||
**Not in scope:** secure boot, image signatures/signing-key management, anti-rollback/version-downgrade enforcement, automatic rollback/post-boot health-confirmation machinery, remote release discovery, partition migration or NVS backup/restore. Use upstream OTA support without dependency patches. The administrator is responsible for selecting trusted, compatible firmware, including when deliberately installing an older build.
|
||||
|
||||
Planned work:
|
||||
**Historical integration build and host evidence (parent-reported):** `pio run` **PASS**, **94,220 B linked RAM / 1,847,645 B flash**. Against Phase 9's 94,196 B / 1,828,565 B, this is **+24 B RAM / +19,080 B flash**. Final backend 88 cases plus the pinned SDK begin/abort failure-contract test, UI 169 groups, server lifecycle 44, admin transport 25, console lifecycle, SSH runtime and cookie lifecycle checks passed. Additional cookie base/admin/display/lifecycle, HTTPD idle cleanup 18, and SSH management/runtime/security checks passed; the extra cookie `--admin` run initially lacked a reboot symbol in its fixture, corrected in test-only code before passing. Host doubles are not target evidence. The later concise-UI fix was copy-only; its latest reported UI regression passed all **169 groups**, with no rebuild after the text change. The build figures above remain historical; this documentation update ran no build or tests.
|
||||
|
||||
- Decide whether BLE is primarily a serial transport, a provisioning interface, or both.
|
||||
- Require authenticated pairing/bonding appropriate to the device threat model.
|
||||
- Integrate serial access through the existing broker rather than bypassing writer ownership.
|
||||
- Preserve binary transparency through explicit framing and MTU-aware partial transfers.
|
||||
- Bound connections, queues, retries, and advertising behavior.
|
||||
- Measure coexistence with 2.4 GHz Wi-Fi, HTTPS, WebSocket, SSH, USB CDC, and UART service.
|
||||
- Store bond/provisioning data under the hardened secret-storage policy.
|
||||
- Provide UART0 controls and a physical recovery path for clearing BLE state.
|
||||
**Acceptance (2026-09-18):** after the firmware upload implementation and concise-UI fix, the user confirmed: “That works perfectly. And the usual operation is also verified.” Phase 10 is complete by this explicit acceptance of working upload and verified normal operation. Do not infer specific fault-injection, NVS before/after comparisons, power-loss or wired-recovery passes. The [regression guidance](#phase10-regression-guidance) below is reusable future guidance, not an acceptance blocker.
|
||||
|
||||
BLE remains subordinate to stable Wi-Fi and serial operation; it should be omitted if coexistence or memory costs cannot be bounded acceptably.
|
||||
#### Phase10 update and recovery
|
||||
|
||||
1. Keep **USB-to-UART** available for UART0 administration/flashing; native USB CDC is network-independent UART1 access, not administration. Install the updater by wire first: older firmware without the route cannot install its own first web updater. The custom OTA layout is required; [one-time old-layout migration](../README.md#one-time-migration-from-the-default-partition-table) is separate and destructive.
|
||||
2. Build trusted, schema-compatible `esp32-s3-devkitc-1-n16r8` firmware with `pio run`; choose **`.pio/build/esp32-s3-devkitc-1-n16r8/firmware.bin`**, never a merged image or a renamed non-application file. Observe the [downgrade warning](../README.md#legacy-credential-removal).
|
||||
3. Save desired RAM-only settings and record nonsecret configuration/public HTTPS/SSH fingerprints. Arrange a maintenance window and stable power/network; sign in as `admin` over trusted HTTPS, open **Settings → HTTPS / Reboot → Firmware update**, choose the file and confirm **Upload and reboot…**.
|
||||
4. Wait for validation/reboot, not just 100% transmitted bytes. Restore network reachability, reload and explicitly sign in; verify the running application, saved configuration and identities before another upload.
|
||||
5. A lost response, timeout, page close, sign-out or browser abort proves neither cancellation nor failure. The UI locks uncertain outcomes without replay; reload clears only browser locks, not the device latch. Inspect through UART0 and deliberately reboot if appropriate: any later reset can boot an already selected image.
|
||||
6. For initial wired installation or recovery when boot/HTTPS fails, connect USB-to-UART, select a known-good compatible checkout, run `pio run --target upload`, then `pio device monitor -b 115200`. **Do not erase for ordinary updates or recovery**; there is no automatic recovery flashing/rollback.
|
||||
|
||||
Replies: **400** malformed/incompatible/incomplete input; **401/403** authentication/Origin/CSRF or `admin_required`; **408** `firmware_timeout`; **409** selected-image latch; **413/415** destination capacity/content type; **500** `firmware_write_failed`/`firmware_commit_failed`; **503** busy/unavailable/resources. Unread rejected bodies close rather than drain; bounded secret-free JSON may never reach a disconnected browser.
|
||||
|
||||
#### Phase10 regression guidance
|
||||
|
||||
These are reusable checks, **not recorded passes or outstanding acceptance gates**. Record only performed outcomes; host flash/network/scheduling doubles are not hardware evidence. Focused host suites: `python3 tests/web_firmware_update/run.py` and `python3 tests/web_ui_session/run.py` (the SDK begin/abort case covers a live handle published on erase failure).
|
||||
|
||||
- Exercise wired-first install, known-good upload/reboot/new build and explicit reconnect; compare saved users/roles, password/key login, Wi-Fi/serial/display/hostname settings and public HTTPS/SSH fingerprints without recording secrets.
|
||||
- Interrupt/cancel receive; reject corrupt, truncated, wrong-target, non-application and oversized inputs without unintended selection/restart, then deliberately perform a valid update. Reject unauthenticated/ordinary-user, wrong-Origin and missing/wrong-CSRF requests before flash. Never attempt power interruption at commit without wired recovery ready.
|
||||
- Exercise competing upload, HTTPS lifecycle/identity and ordinary reboot exclusion; HTTPD requests may wait rather than promptly return busy. With controlled post-selection response-failure injection, verify no automatic restart, subsequent 409 (also after HTTPS restart), then manual reboot; an arbitrary disconnect does not establish this fault case.
|
||||
- Observe web stalls/drops and serial/network recovery, single-writer isolation, UART0 recovery and native USB UART1 access without networking; demonstrate known-good wired recovery without erase and recheck configuration/identities. Do not claim uninterrupted traffic.
|
||||
|
||||
## Current and planned phases
|
||||
|
||||
**Phases 8, 9, 10 and 12 are complete** for their accepted scopes. Phase 9 includes the explicit new-hardware-check waiver above; Phase 10 includes explicit user acceptance of upload and normal operation. Phase 12 includes explicit user validation and the fresh-boot/full-client evidence below. Optional features must not weaken completed serial and recovery paths. General release gates below guide future work, not claims that every fault, soak, recovery or reserve measurement was performed for completed phases.
|
||||
|
||||
### Phase 12 — Advanced network integration
|
||||
|
||||
These features are candidates, not current commitments:
|
||||
**Complete by explicit user validation:** “I just validated Phase 12 successfully.” Accepted baseline:
|
||||
|
||||
- Additional DNS-SD service advertisement and any certificate-name integration beyond the implemented configurable STA hostname.
|
||||
- Enterprise Wi-Fi support, subject to credential-storage and certificate-validation design.
|
||||
- IPv6 behavior and diagnostics beyond the current basic support.
|
||||
- WireGuard feasibility evaluation, including RAM, CPU, licensing, key storage, routing, and recovery impact.
|
||||
- Dual-stack local access and DNS-SD discovery: advertise available HTTPS and SSH services using the shared `sak-<suffix>.local` hostname, with appropriate IPv4 `A` and IPv6 `AAAA` records. Preserve IPv4 access and verify IPv6 support throughout HTTPS, WebSocket and SSH. Clients choose address-family preference and fallback; DNS-SD cannot mandate IPv6 preference. Certificate-name integration and trust remain separate concerns.
|
||||
- Send the configured device hostname (`sak-<suffix>`, without `.local`) through DHCPv4 Host Name option 12 so a suitably configured DHCP/DNS server can publish the lease address in its own DNS zone. Set the STA netif hostname before DHCP starts, define how hostname changes reach subsequent DHCP exchanges, and verify the transmitted option and server-side DNS registration. DNS publication and the DNS domain remain server policy; mDNS does not provide this integration.
|
||||
- IPv6 baseline: link-local addressing and SLAAC-provided ULA/global addresses, with mDNS/DNS-SD for local discovery; no DHCPv6 requirement. Preserve link-local interface scope and keep advertisements synchronized with usable addresses and service availability. Registration of SLAAC addresses in infrastructure DNS is a separate optional feature, outside this baseline; SLAAC does not provide automatic hostname registration.
|
||||
- Validate IPv4-only, IPv6-only and dual-stack operation, including reconnects, address changes and service lifecycle changes. Discovery remains local-link by default; preserve UART0 and native USB recovery regardless of network state.
|
||||
|
||||
The device is not intended to become a general-purpose router. Captive-portal interception, unauthenticated DNS redirection, NAPT, and a plaintext serial listener remain out of scope unless the project requirements are explicitly revised.
|
||||
|
||||
### Phase 13 — Optional filesystem-backed features
|
||||
#### Implementation and operational boundaries
|
||||
|
||||
The `storage` partition is reserved but not currently mounted. Possible uses must be justified individually:
|
||||
- The Wi-Fi owner installs the full configured hostname before STA connection and updates it after hostname edits. DHCPv4 option 12 uses `sak-<suffix>`, not `.local`. Rename does not force DHCP restart: the next DHCP exchange carries the new name, and DNS registration/cache cleanup remain server policy. Existing hostname limits and NVS formats are unchanged.
|
||||
- STA enables SLAAC and IPv6 link-local creation; either IPv4 or a preferred IPv6 address establishes `ONLINE`. **Link-local-only counts as online**, prevents IPv4-only timeout/failover, and can retire the fallback AP after the existing stability interval. This indicates local address availability, not Internet reachability or a default route. Web, CLI and OLED distinguish absent IPv4 from IPv6 availability. A diagnostic follow-up adds up to three preferred numeric IPv6 addresses to the snapshot, copied and cleared with the same observation; `wifi status` and browser Network settings print them in link-local/ULA/GUA groups; overview/OLED status retains availability flags. The settings JSON is bounded to 2304 bytes, with no extra netif calls on HTTPD.
|
||||
- HTTPS/WebSocket use IDF's existing dual-stack listener. SSH explicitly uses one dual-stack listener, retaining the existing two-session limit. Bracketed IPv6 HTTPS authorities are canonicalized and bound to the existing Host/Origin/session checks; scoped IPv6 literal URLs are rejected. Prefer the `.local` hostname for link-local browser access, subject to client resolver support. No certificate regeneration, new trust mechanism, or automatic IPv6 preference is introduced.
|
||||
- STA-only mDNS advertises `_https._tcp:443` and `_ssh._tcp:22` according to listener availability, without TXT metadata. Record and address-family changes converge through the existing Wi-Fi owner, including offline reconciliation; service setters do no component work. The responder is not restarted for ordinary changes. Initialization failure remains latched/nonfatal.
|
||||
- mDNS 1.12.0 exposes no readiness acknowledgement and can silently drop queued family actions. Absent families are disabled on each one-second owner pass; available families are re-enabled/reprobed on a 30-second repair cadence. Upstream synchronous calls can delay this cadence. There can be a transient stale/zero-A response window after IPv4 loss before disable is processed. AAAA records follow upstream **valid-address** semantics, including deprecated-but-still-valid addresses, not preferred-only filtering. Client caches expire independently.
|
||||
- `cmake/mdns_membership.*` applies one version/hash-guarded, build-local source overlay to mDNS 1.12.0: balance per-family multicast leaves and unwind joins after PCB creation failure. Managed sources and dependency versions remain unchanged. An upstream mismatch fails configuration for deliberate review; see `tests/mdns_membership/README.md` for maintenance/removal and regression evidence. This is not the abandoned Phase 9 patch set.
|
||||
- `sdkconfig.defaults` explicitly enables IPv4/IPv6 and increases mDNS service capacity from one to two. Existing saved configurations override defaults: verify `CONFIG_LWIP_IPV4=y`, `CONFIG_LWIP_IPV6=y`, `CONFIG_MDNS_MAX_SERVICES=2` and STA-only predefined mDNS interfaces before building. The local N16R8 saved configuration was updated accordingly. No new task, transport socket, broker slot, serial buffer, partition, filesystem or generated web asset was added.
|
||||
- The accepted baseline initially retained IPv4-only traceroute and limited family/scope selection. The later requested [dual-stack diagnostics follow-up](command_reference.md#dual-stack-network-diagnostics) adds `-4`/`-6` to ping/traceroute/nslookup across UART0, admin SSH and browser Admin, scoped IPv6 probes and explicit A+AAAA lookup. This follow-up is built/host-tested, not covered retroactively by the acceptance capture below. DHCPv6, infrastructure DNS registration of SLAAC addresses, and new IPv6 resolver provisioning remain outside scope.
|
||||
|
||||
- LittleFS-backed web assets to decouple large static files from the application image.
|
||||
- Bounded diagnostic logs with explicit retention and secret-redaction rules.
|
||||
- Import/export of non-secret configuration.
|
||||
- Optional user files needed by later administration features.
|
||||
#### Validation evidence and acceptance
|
||||
|
||||
Before enabling storage, define corruption recovery, wear limits, quotas, atomic update behavior, compatibility across OTA slots, and whether encryption is required. Serial operation and recovery must not depend on a mountable filesystem.
|
||||
`pio run` passed on PlatformIO 6.12.0 / ESP-IDF 5.5.0 with the guarded mDNS source compiled: **94,396 B linked RAM / 1,853,025 B flash**, +176 B RAM / +5,380 B flash versus the recorded Phase 10 build. These are static link sizes, not runtime heap or stack headroom. Initial target compilation rejected lock-free bool atomics; the implementation now uses short portMUX sections. Existing SDK Kconfig notes remain.
|
||||
|
||||
Host checks passed: Wi-Fi/address/hostname reconciliation, mDNS lifecycle/family repair, actual patched multicast-membership functions and CMake guards, SSH dual-stack/management/runtime/security, HTTPS authority parser (689 cases on host and actual installed lwIP parser), cookie authorization variants, Network settings, browser UI/CSP (171 groups), HTTPS lifecycle (45 groups and eight status projections), firmware upload (88 cases plus SDK contract), broker diagnostics, and session-store/serial integration. Network-settings host fakes were updated after the new netif dependency exposed a compilation failure; the rerun passed. These are not packet-level or hardware evidence.
|
||||
|
||||
Follow-up after the user's live status report: both preferred-address flags were `yes`, while plain client `ping` selected IPv4. Source verification confirms the flags require actual preferred addresses; this is not evidence of failed SLAAC or verified IPv6 reachability. `wifi status` now prints the addresses for diagnosis. Latest follow-up `pio run` passed: **94,444 B linked RAM / 1,853,685 B flash** (+48 / +660 versus the initial Phase 12 build). Browser Admin console now permits canonical Wi-Fi commands, including disruptive edits and hidden prompts, as explicitly requested. Focused policy/browser Wi-Fi prompt, address snapshot/rendering, Network/cookie and UI regressions passed. The broader console-boundary suite passes its console/certificate stages but has a pre-existing SSH-adapter fixture compilation failure for missing `web_firmware_update_reserve_reboot`; no production change was made to hide it.
|
||||
|
||||
Latest address-display build: `pio run` **94,444 B linked RAM / 1,854,485 B flash**; Network API/owner checks and **174 browser groups plus CSP** passed. These remain build/host results, separate from the user's device evidence below.
|
||||
|
||||
##### User-reported device acceptance
|
||||
|
||||
User explicitly validated Phase 12 after confirming numeric IPv6 ping/HTTPS, Avahi AAAA resolution and normal IPv6 hostname access following the Fedora `authselect` mDNS correction. The earlier hostname failure was a client resolver configuration issue, not missing device IPv6 addresses. The acceptance capture adds:
|
||||
|
||||
- **Fresh boot:** HTTPS, SSH and mDNS running without reported startup failures; zero SSH sessions, serial stopped, no broker clients, USB attached but not host-open. The configured UART profile is **230400 baud, 8N1, RTS/CTS, DTR active, RTS threshold 96**.
|
||||
- **Full client mix:** two authenticated public-key SSH sessions with IPv6 ULA peers (one serial observer, one admin console); two serial WebSockets (one writer, one observer); one active browser admin WebSocket; native USB host-open as an observer. Four broker clients, exactly one writer, all reported pending/event counts zero. Serial running with RX-available/TX-pending zero, CTS asserted and valid RS-232 voltage at observation time.
|
||||
- SSH: two successful handshakes, zero handshake/authentication failures or timeouts, zero stream I/O failures and zero session revocations. Stream counters **RX 68 / accepted 65 / rejected 3 / TX 161,209 bytes**; two broker writer revocations were reported. The rejected bytes are retained as evidence, not silently described as zero loss or assigned an unverified cause.
|
||||
- Web serial: two connections, **27 accepted RX frames/bytes**, zero rejected RX frames/bytes; **782 binary TX frames / 318,230 bytes**, 11 control frames / 948 bytes. Send/queue/protocol/close failure counters zero. Two writer requests were denied; the final broker snapshot still shows one writer. Browser admin: one connection, **106 RX / 3,721 TX bytes**, zero send/queue/protocol/authorization/backpressure failures. No response errors reported.
|
||||
- HTTPS and SSH both report running, not transitioning, `ESP_OK`; mDNS reports expected announcement and `ESP_OK`. Expected announcement alone is not packet-level service-record proof; the earlier successful Avahi lookup separately establishes the reported AAAA lookup.
|
||||
|
||||
All heap figures are bytes, copied from the user's observations:
|
||||
|
||||
| Observation | Heap capability | Free | Lifetime minimum-free | Largest block |
|
||||
|---|---|---:|---:|---:|
|
||||
| Fresh boot | Internal 8-bit | 65,880 | 64,912 | 31,744 |
|
||||
| Fresh boot | Internal DMA | 58,124 | 57,156 | 31,744 |
|
||||
| Fresh boot | External PSRAM | 8,196,732 | 8,187,980 | 8,126,464 |
|
||||
| Full client mix | Internal 8-bit | 35,408 | 8,528 | 22,528 |
|
||||
| Full client mix | Internal DMA | 27,652 | 772 | 22,528 |
|
||||
| Full client mix | External PSRAM | 8,111,952 | 8,072,612 | 7,995,392 |
|
||||
|
||||
SSH owner stack: **20,480 B** configured, **18,476 B minimum-free** at fresh-boot observation and **16,284 B minimum-free** under the full mix.
|
||||
|
||||
**Evidence limits:** minimum-free is the conservative sum of matching heap regions' lifetime minima, not a simultaneous free-space measurement or guaranteed allocation reserve. Internal 8-bit and DMA capabilities overlap and must not be added as independent pools. The **772 B DMA lifetime minimum remains a headroom watch item**, not an observed allocation failure or acceptance blocker. Boot output contains two TLS `-0x004C` receive errors and unauthenticated-request failures; the capture does not establish their cause or connect them to memory exhaustion. Command snapshots are non-atomic; differing cumulative request/authentication counts must not be treated as one aligned measurement interval. No UART overflow/per-observer drop counters, soak duration, byte-for-byte capture integrity, exhaustive fault injection, DHCP packet capture/server-zone update, or every address-family/service transition is established by this excerpt. User acceptance is recorded without inventing those passes.
|
||||
|
||||
##### Reusable regression guidance
|
||||
|
||||
The following procedures are retained for future regression testing, **not outstanding acceptance blockers or a claim that every item was executed**. Keep UART0 and USB recovery available:
|
||||
|
||||
1. On IPv4, IPv6-only and dual-stack STA networks, exercise HTTPS login, serial/admin WebSockets, SSH user/admin access and one-writer/observer isolation. Test hostname access plus IPv4 and unscoped ULA/GUA IPv6 literals; verify SSH host keys and HTTPS identity rather than trusting discovery.
|
||||
2. Capture DHCP DISCOVER/REQUEST option 12 at boot, rename/renew and reconnect, including a maximum-length suffix. With a configured DHCP/DNS server, verify the resulting zone entry and its server-controlled update/removal behavior.
|
||||
3. Browse `_https._tcp` and `_ssh._tcp` and inspect A/AAAA/SRV answers over both families. Stop/restart each service; rename; drop/reacquire DHCP while IPv6 survives; change/deprecate/expire RA prefixes; disconnect/reconnect. Confirm eventual withdrawal/restoration, valid-address AAAA semantics and no persistent zero-A response. Exercise repeated family transitions and, where practical, missed-event/action repair.
|
||||
4. Check link-local-only readiness and the fallback-AP transition explicitly. Confirm the client preserves interface scope; do not infer browser link-local success from `ONLINE` alone. Test multicast filtering/AP isolation separately from firmware address readiness.
|
||||
5. Repeat the previously accepted 230400-baud mixed USB/WebSocket/SSH workload with discovery and address changes. Capture current/minimum internal, DMA and PSRAM availability, task stack margins, UART overflow and per-client drop counters. No new runtime memory reserve or maximum-baud performance guarantee is claimed.
|
||||
|
||||
Phase 12 is complete by the explicit user validation above. This acceptance update changes documentation only; the agent did not run a new build, test, upload, erase or device operation.
|
||||
|
||||
## Cross-phase release gates
|
||||
|
||||
Every phase should satisfy the following before being marked complete:
|
||||
Every phase should satisfy the following before being marked complete, with any user-waived check explicitly recorded in that phase rather than reported as passed:
|
||||
|
||||
1. A clean release build for the ESP32-S3 N16R8 target.
|
||||
2. No new compiler or project diagnostics attributable to the change.
|
||||
@@ -305,7 +384,7 @@ The following are not implemented merely because flash partitions or library sup
|
||||
|
||||
- NVS, flash, or PSRAM encryption.
|
||||
- Secure boot or production eFuse provisioning.
|
||||
- OTA download, image confirmation, or rollback policy.
|
||||
- Automatic OTA downloads, image signing, post-boot health confirmation and automatic rollback. The simple admin upload is complete in Phase 10; these advanced update features remain deferred.
|
||||
- Core-dump collection or secret-safe core-dump processing.
|
||||
- Filesystem mounting.
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
# Security operations — intermittent hobby use
|
||||
|
||||
This checklist is for a wireless serial cable used occasionally on a trusted network,
|
||||
not an Internet-facing appliance or a production-security certification.
|
||||
It covers existing firmware operations only: no dependency patches, crypto upgrades,
|
||||
secure-boot, encrypted-NVS, or eFuse changes are part of this scope.
|
||||
|
||||
## Provision once, locally
|
||||
|
||||
- Use the physical **UART0 administration console** on a trusted computer.
|
||||
Native USB CDC is a separate UART1 serial-data path, not the admin console.
|
||||
- Create the first administrator with `user add <username> admin`.
|
||||
Use a unique password; the prompt accepts 12–64 printable ASCII characters.
|
||||
Add a separate `user` account for ordinary serial use, especially over SSH:
|
||||
SSH `admin` opens administration, whereas SSH `user` opens UART1.
|
||||
- Use unique Wi-Fi credentials, not passwords reused from other services.
|
||||
Configure station profiles using the [Wi-Fi command reference](command_reference.md#wi-fi);
|
||||
enter their passwords with `wifi profile secret <slot>`.
|
||||
If using the device AP, set its password with `wifi ap secret`.
|
||||
If the AP is unnecessary, select `wifi ap policy off`.
|
||||
Run `wifi save` to persist working Wi-Fi settings.
|
||||
- Before entering network credentials into a client, check device identity locally:
|
||||
run `web certificate info` and `ssh host-key info` on trusted UART0.
|
||||
Compare the SHA-256 certificate fingerprint in the browser certificate viewer
|
||||
and the SSH host-key fingerprint in the SSH client with those local values.
|
||||
Record the verified fingerprints; do not blindly dismiss certificate or changed-key warnings.
|
||||
- Keep passwords in a password manager. Generated account passwords (`--generate`)
|
||||
are displayed once; avoid terminal recording or sharing that output.
|
||||
|
||||
## Each use
|
||||
|
||||
- Use only a trusted LAN or the device's password-protected AP.
|
||||
Do not configure router port forwarding or otherwise expose HTTPS/SSH to the Internet.
|
||||
A trusted network reduces exposure; it does not make every attached client trustworthy.
|
||||
- Check unexpected identity warnings against UART0 before logging in.
|
||||
Keep UART0, native USB, and the connected RS-232 equipment physically controlled:
|
||||
local serial access is not protected by network account authentication.
|
||||
- When finished, sign out of the browser and close SSH clients.
|
||||
Browser serial **Disconnect** alone is not sign-out.
|
||||
- Prefer powering the device off when unused. If it must remain powered for USB
|
||||
serial use, run `wifi stop` from UART0 and check `wifi status` after the queued
|
||||
operation completes. This stops network access, not native USB UART1 access.
|
||||
To keep Wi-Fi disabled at the next boot, follow with `wifi save`.
|
||||
`wifi start` enables it again in RAM; save again only if that boot policy is wanted.
|
||||
Do not rely on a temporary stop surviving reboot without saving.
|
||||
|
||||
## Change credentials or identity when needed
|
||||
|
||||
Use trusted UART0 for these operations to avoid losing a remote session mid-change.
|
||||
Rotate after suspected disclosure, a lost client key, or a change of owner/access;
|
||||
there is no need for a calendar-driven rotation ritual for this use case.
|
||||
|
||||
| What changed | Built-in operation |
|
||||
|---|---|
|
||||
| Account password | `user password <username>` (no-echo prompt). |
|
||||
| Authorized SSH client key | `user show <username>` to identify its index; `user key delete <username> <0..2> --force`, then `user key add <username>` to enter the replacement public key. |
|
||||
| Account no longer needed | `user delete <username> --force`; the final administrator is protected. |
|
||||
| Wi-Fi password | Change the router credential as appropriate, then `wifi profile secret <slot>`; for the device AP use `wifi ap secret`. Finish with `wifi save`. |
|
||||
| HTTPS device identity | `web certificate rotate --force`. |
|
||||
| SSH device identity | `ssh host-key rotate --force`. |
|
||||
|
||||
Account changes invalidate that account's network sessions; identity replacement
|
||||
interrupts the affected service. Check command results, then verify new fingerprints
|
||||
through UART0 before updating browser trust or SSH known-host entries and reconnecting.
|
||||
Changing a password does not remove authorized SSH keys; review both after compromise.
|
||||
Identity rotation is not an account-password change. If an acknowledgement is lost,
|
||||
inspect current state on UART0 rather than blindly repeating the operation.
|
||||
|
||||
## Recovery and backup limits
|
||||
|
||||
- Keep UART0 available for forgotten passwords, Wi-Fi mistakes, and network-service
|
||||
recovery. Native USB can still carry UART1 data when networking fails, but cannot
|
||||
repair accounts or identities; neither path promises uninterrupted service through reboot.
|
||||
- For a healthy database, use normal UART0 account commands to repair access.
|
||||
`user recover --force` is **only** for an unavailable user database: it destroys
|
||||
that database's contents and rebuilds it empty, refuses a healthy database, and
|
||||
requires creating a new administrator afterward. It is not a forgotten-password reset.
|
||||
- `web reset --force` replaces HTTPS certificate/private-key material only;
|
||||
`ssh reset --force` replaces SSH host-key material. Neither resets accounts or Wi-Fi.
|
||||
These are scoped recovery operations, not a whole-device factory reset.
|
||||
- No supported configuration/credential/identity backup-export command was found
|
||||
in the application console. Keep a private reprovisioning record of settings,
|
||||
account names/roles, client public keys, and verified fingerprints; store passwords
|
||||
separately in a password manager. Do not expect status output to recover secrets
|
||||
or private keys. Reprovisioning may require accepting a newly verified device identity.
|
||||
- Physical extraction or firmware replacement is **not protected by this checklist**.
|
||||
Treat a lost device or a raw flash copy as potentially exposing stored Wi-Fi secrets,
|
||||
private identities, and password verifiers. Rotation is not a secure flash-erasure claim.
|
||||
|
||||
## Lightweight maintenance
|
||||
|
||||
Occasionally, and before a planned firmware update, review relevant ESP-IDF,
|
||||
wolfSSL/wolfSSH, and bundled web-library upstream release/security notes.
|
||||
If a relevant issue appears, stop network use until a separate upgrade decision is made;
|
||||
USB serial remains an option. Dependency upgrades and their validation are separate work,
|
||||
not an endless CVE audit or an implied promise that this firmware is vulnerability-free.
|
||||
|
||||
Commands and limits were checked against application sources and the [command reference](command_reference.md) at `f40c09c`.
|
||||
This is source-reviewed operating guidance, not a build, hardware test, security audit,
|
||||
or verification of any individual device's flash/eFuse configuration.
|
||||
@@ -1,6 +1,6 @@
|
||||
# User administration and authentication tests
|
||||
|
||||
Reusable procedures, **not execution evidence**. [Phase 8 is accepted](web_administration_acceptance.md); overall 8D.22 user sign-off does not assert that every checklist item below ran. Historical 8A/B sections describe their original baselines only; current cookie authentication, removed bootstrap/credentials and UART0-only recovery override them. Use current contracts and the integrated regression section for present behavior. Never log generated/entered passwords, cookies/tickets or private/verifier material.
|
||||
Reusable procedures, **not execution evidence**. [Phase 8 is accepted](roadmap.md#phase8-acceptance-evidence); overall 8D.22 user sign-off does not assert that every checklist item below ran. Historical 8A/B sections describe their original baselines only; current cookie authentication, removed bootstrap/credentials and UART0-only recovery override them. Use current contracts and the integrated regression section for present behavior. Never log generated/entered passwords, cookies/tickets or private/verifier material.
|
||||
|
||||
## Current Broker management regression procedure — 8D.16
|
||||
|
||||
@@ -26,7 +26,7 @@ The [Network contract](web_administration.md#network) defines the current byte c
|
||||
|
||||
## Current legacy-removal regression procedure
|
||||
|
||||
The legacy bootstrap/credential/reconciliation instructions in the phase baselines below are **historical only**, superseded by [legacy credential removal](legacy_credential_removal.md) and the current [command reference](command_reference.md). Basic authentication is also historical; current HTTPS uses cookie login. The current overrides apply to later Phase 8C/browser procedures too: no `user bootstrap` or `web credentials` command remains, first-admin creation uses normal UART0 `user add`, and recovery rebuilds empty. Never treat the checklist below as evidence of execution.
|
||||
The legacy bootstrap/credential/reconciliation instructions in the phase baselines below are **historical only**, superseded by [legacy credential removal](roadmap.md#phase8-legacy-credential-compatibility) and the current [command reference](command_reference.md). Basic authentication is also historical; current HTTPS uses cookie login. The current overrides apply to later Phase 8C/browser procedures too: no `user bootstrap` or `web credentials` command remains, first-admin creation uses normal UART0 `user add`, and recovery rebuilds empty. Never treat the checklist below as evidence of execution.
|
||||
|
||||
1. On a disposable controlled NVS image with only `user_db/database` missing, boot and confirm an empty database is persisted with zero accounts/admins and no imported credential. Reboot and confirm it remains empty. Keep physical UART0 attached; do not factory-erase the device for this test.
|
||||
2. Run `user add maint admin` on UART0, check hidden password confirmation and cancellation, then confirm account/password persistence after reboot. Separately exercise `user add operator user --generate` and secure one-time display. Final-administrator delete/demotion must still fail. Existing SSH own-password generation restrictions and typed browser generated-password support remain unchanged.
|
||||
@@ -36,7 +36,7 @@ The legacy bootstrap/credential/reconciliation instructions in the phase baselin
|
||||
6. Confirm help/completion/status expose no legacy credential/bootstrap operation or secret. Removed commands must reject without mutation. Exercise retained user generation and `web certificate rotate --force` through supported frontends. `web reset --force` must change TLS only, require new certificate trust/login after HTTPS restart, leave user credentials/generations unchanged, and not revoke unrelated SSH sessions.
|
||||
7. Recheck UART0/native USB availability and broker one-writer/isolated-observer behavior with network authentication unavailable. Record only nonsecret counters and telemetry. Older v1-only firmware cannot read HTTPS v2; logical NVS replacement is not secure flash erasure and no factory erase is required.
|
||||
|
||||
Acceptance and prior evidence limits are recorded in [legacy compatibility](legacy_credential_removal.md) and [overall acceptance](web_administration_acceptance.md); the preceding procedure is not an execution log.
|
||||
Acceptance and prior evidence limits are recorded in [legacy compatibility](roadmap.md#phase8-legacy-credential-compatibility) and [overall acceptance](roadmap.md#phase8-acceptance-evidence); the preceding procedure is not an execution log.
|
||||
|
||||
## Historical Phase 8A baseline — role-based database and UART0 administration
|
||||
|
||||
@@ -204,7 +204,7 @@ Finally, issue commands concurrently from UART0 and admin SSH, including `user l
|
||||
|
||||
## Integrated web administration regression procedure
|
||||
|
||||
Current retained-scope checks for future changes. [8D.22 sign-off](web_administration_acceptance.md) closes the phase; these are **not additional acceptance conditions or claims of individual execution**. Record exact revision, workload/client mix, duration, counters and heap/stack evidence for any new run. Do not treat host doubles as target timing/power-loss proof or reserve approval. Prepare UART0 recovery/native USB before disruptive tests and do not erase persisted data without explicit approval.
|
||||
Current retained-scope checks for future changes. [8D.22 sign-off](roadmap.md#phase8-acceptance-evidence) closes the phase; these are **not additional acceptance conditions or claims of individual execution**. Record exact revision, workload/client mix, duration, counters and heap/stack evidence for any new run. Do not treat host doubles as target timing/power-loss proof or reserve approval. Prepare UART0 recovery/native USB before disruptive tests and do not erase persisted data without explicit approval.
|
||||
|
||||
### 1. Integrated login and authorization
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Web administration contracts
|
||||
|
||||
Current, accepted firmware behavior. Phase status and executed-evidence limits belong in the [roadmap](roadmap.md#phase-8--role-based-users-and-administrative-access--complete) and [acceptance record](web_administration_acceptance.md), not in implementation timelines. [Regression procedures](user_administration_tests.md#integrated-web-administration-regression-procedure) describe checks, not results. Source is authoritative; start with the [code map](agent/code-map.md).
|
||||
Current implementation contracts; [Phase 10 firmware upload is complete by explicit user acceptance on 2026-09-18](roadmap.md#phase-10--simple-admin-web-firmware-upload), confirming upload and normal operation, not specific fault or recovery checks. Phase status and executed-evidence limits belong in the [roadmap](roadmap.md#phase-8--role-based-users-and-administrative-access--complete) and [acceptance record](roadmap.md#phase8-acceptance-evidence), not in implementation timelines. [Regression procedures](user_administration_tests.md#integrated-web-administration-regression-procedure) describe checks, not results. Source is authoritative; start with the [code map](agent/code-map.md).
|
||||
|
||||
## Authentication and admission
|
||||
|
||||
@@ -9,7 +9,7 @@ Current, accepted firmware behavior. Phase status and executed-evidence limits b
|
||||
- Mutation admission requires current cookie/principal, strict Origin and CSRF validation; administration additionally requires current `admin`. Normal users retain serial/status but cannot invoke administration directly. Authentication POST fetches use CORS mode with fixed same-origin URLs and same-origin credentials: do not accept Origin `null` to compensate for browser no-referrer behavior.
|
||||
- Session-store initialization is part of admitted HTTPS start; authentication failure gates HTTPS. Failed start/accepted stop disables and wipes session state. Logout invalidates only the originating session before socket cleanup; account mutation invalidates that account's sessions/tickets, including deletion/recreation, without revoking unrelated accounts. Currentness checks remain authoritative if best-effort notifications fail.
|
||||
- Four serial tickets and two admin tickets are digest-only, single-use, 30-second, session/principal-bound records. Cookie/Origin/ticket/currentness and transport admission precede explicit WebSocket 101. Store RNG/SHA/database calls run outside short spinlocks; IDs/expiry/epochs fence stale publication without nested store/transport locks.
|
||||
- HTTPD remains bounded to six sockets, two serial WebSockets, one admin WebSocket and 39 method/path handlers; LRU eviction is disabled. Sessions, sockets, tickets and the two shared remote-console slots are separate capacity limits. Optional settings/admin failures preserve unrelated routes where their initialization contract permits; UART0 and native USB remain independent of web readiness.
|
||||
- HTTPD remains bounded to six sockets, two serial WebSockets, one admin WebSocket and 40 method/path handlers; LRU eviction is disabled. Sessions, sockets, tickets and the two shared remote-console slots are separate capacity limits. Optional settings/admin failures preserve unrelated routes where their initialization contract permits; UART0 and native USB remain independent of web readiness.
|
||||
|
||||
`web_httpd_adapter` alone accesses private IDF 5.5.0 HTTPD state. It rejects duplicate/ambiguous headers, postpones 101 until admission, and wipes consumed header scratch while preserving right-aligned unread bytes. Optional Settings registration stages descriptor/name allocations before publishing either, avoiding the pinned public registration failure path. Re-audit these private boundaries on SDK upgrades and same-version SDK patches: the version guard does not detect patches that retain the same version number. HTTPD response headers are pointer-backed, not copied; both `Set-Cookie` value buffers must remain valid and distinct through response send. Do not reuse or wipe those buffers before sending completes. Do not enable header/ticket debug logging. Auth documents, scripts and sensitive responses are no-store with CSP/no-referrer/frame-denial protections; authored loader changes require matching CSP hashes. Generated assets are not a normal documentation/build output.
|
||||
|
||||
@@ -28,12 +28,14 @@ HTTPD alone owns browser-admin socket IO and its 1,552-byte PSRAM-only payload.
|
||||
Typed Settings permissions do not expand shell permissions. Parsed canonical arguments, not raw prefixes or completion suggestions, control admission:
|
||||
|
||||
- Browser `web` permits only `web status`, `web stop`, and exact `web certificate rotate --force`; certificate info/reset, diagnostics/performance and other web forms are denied.
|
||||
- Browser `wifi`/`mdns` permit only status. Network mutation belongs to typed Settings or UART0/admin SSH.
|
||||
- Browser `wifi` uses the canonical command handler, including bare `wifi` (status), profile/AP settings, persistence, lifecycle and network diagnostics. Profile/AP secret entry uses the shared hidden prompt: no input echo, history or completion of secret bytes; cancellation, disconnect and failed currentness discard the input. Explicit `wifi ap show-secret` reveals the AP password only in the invoking admin terminal; routine status and completion do not reveal credentials. Browser `mdns` still permits only exact `mdns status`. Root and `wifi`-prefixed ping/traceroute/nslookup support the same `-4`/`-6` and device-local IPv6 scope syntax as UART0/admin SSH; see [dual-stack diagnostics](command_reference.md#dual-stack-network-diagnostics). They run on the shared dispatcher, not HTTPD or the client browser; no extra endpoint or permission expansion is involved.
|
||||
- Browser `user` permits status/list/show and interactive add/password plus forced role/delete for **other accounts only**. Self changes, generated passwords, key commands and recovery are denied there; typed Accounts supports the separately bounded self/generated/key workflows.
|
||||
- Browser SSH stop/disconnect/reset and host-key mutation are denied; typed SSH Settings has its own safe owner path. Do not claim full browser-shell parity.
|
||||
- Browser `reboot` and owner-relative `exit` are supported. First-admin provisioning uses normal `user add` on UART0; unavailable-database recovery is UART0-only. The legacy `user bootstrap` and web credential commands no longer exist.
|
||||
|
||||
Self-affecting shell actions use the existing bounded drain/control path (up to ten seconds plus a short delay), not guaranteed peer delivery. Browser certificate rotation hands a typed action after drain/200 ms to the existing 12 KiB dispatcher, never crypto/NVS on the 4 KiB control stack. Pending input is discarded through execution, and an executing slot remains reserved across self-detach. UART0/admin SSH retain canonical recovery actions.
|
||||
Wi-Fi changes use the existing canonical manager path, not deferred browser drain/acknowledgement. Stop, reconnect, profile/AP edits, load/defaults/reset can disconnect network clients before output arrives. A lost result does not cancel admitted work: reconnect and inspect before retrying; use UART0 if networking is unavailable. Changes are RAM-only until `wifi save`, except `wifi reset`, which also persists defaults. Native USB remains independent UART1 access, not an administration console.
|
||||
|
||||
Deferred self-affecting shell actions use the existing bounded drain/control path (up to ten seconds plus a short delay), not guaranteed peer delivery. Browser certificate rotation hands a typed action after drain/200 ms to the existing 12 KiB dispatcher, never crypto/NVS on the 4 KiB control stack. Pending input is discarded through execution, and an executing slot remains reserved across self-detach. UART0/admin SSH retain canonical recovery actions.
|
||||
|
||||
## Typed settings API and operation lifetime
|
||||
|
||||
@@ -43,7 +45,7 @@ All routes below are under `/api/settings/`. Each domain has bodyless GET snapsh
|
||||
|---|---|---|
|
||||
| `web_serial_settings`, serial service | `serial` / `serial-operation` | 256 / 256 / 96 |
|
||||
| `web_account_settings`, user database | `accounts` / `account-operation` | 768 / 1024 (accounts), 512 (keys) / 96 |
|
||||
| `web_network_settings`, Wi-Fi + mDNS | `network` / `network-operation` | 768 / 2048 / 128 |
|
||||
| `web_network_settings`, Wi-Fi + mDNS | `network` / `network-operation` | 768 / 2304 / 128 |
|
||||
| `web_display_settings`, local status UI | `display` / `display-operation` | 256 / 128 / 96 |
|
||||
| `web_broker_settings`, session broker | `broker` / `broker-operation` | 256 / 2048 / 96 |
|
||||
| `web_ssh_settings`, SSH owner/security | `ssh` / `ssh-operation` | 256 / 768 / 96 |
|
||||
@@ -67,7 +69,7 @@ Separate bodyless POST `accounts/generate-password` returns one 24-character val
|
||||
|
||||
### Network
|
||||
|
||||
Wi-Fi config/runtime is one zero-wait consistent projection; mDNS is a separate projection, not cross-domain atomic authorization. Four stable profiles carry enabled/priority/security/SSID/password-configured metadata. `mixed` means WPA2-or-stronger, not open. `announced` is expected STA announcement, not verified DNS.
|
||||
Wi-Fi config/runtime is one zero-wait consistent projection; mDNS is a separate projection, not cross-domain atomic authorization. Four stable profiles carry enabled/priority/security/SSID/password-configured metadata. `mixed` means WPA2-or-stronger, not open. `announced` is expected STA announcement, not verified DNS. Runtime `ipv6_addresses` contains up to three preferred addresses from the same snapshot as the availability flags, encoded as eight lowercase four-digit hextets without a zone. Network settings display separate link-local, ULA and GUA lists; absent groups show `none`. Link-local access needs the client's interface scope, and address presence does not assert a route or Internet reachability. Encoding uses the existing snapshot only, not netif/driver calls on HTTPD; the 2304-byte response buffer adds 256 bytes of bounded stack storage.
|
||||
|
||||
SSID wire values are reversible **bytes**, maximum 32: printable ASCII, standard single-character JSON escapes and `\u00HH`, with no raw non-ASCII, non-byte Unicode or surrogates. NUL/non-UTF-8 round-trip. UI text is UTF-8-encoded before byte serialization; exact reversible text or literal hex preserves existing bytes and BOM, with no silent replacement/truncation.
|
||||
|
||||
@@ -111,6 +113,14 @@ HTTPS ordering is **commit → stop → restart**. Precommit generation/RNG/stor
|
||||
|
||||
Save drafts; rotation/restart invalidates all web logins and closes both browser routes. Inspect `web certificate info` through trusted UART0, verify fingerprint before renewing trust, then reload/sign in freshly. Accepting a warning alone is not trusted verification. Use canonical UART0/admin SSH `web stop` / `web start` for retained-server recovery. Network/SSH/USB are not stopped by HTTPS-only operations; whole-device reboot interrupts all transports and loses unsaved RAM.
|
||||
|
||||
### Application firmware upload
|
||||
|
||||
The admin-only firmware card is in **Settings → HTTPS / Reboot**. It sends a raw `POST /api/firmware` with `application/octet-stream`, known length, session cookie, same-origin Origin and `X-CSRF-Token`; it is not a JSON Settings operation or dispatcher/result-slot workflow. Standard SDK OTA APIs stream through a 4 KiB buffer to the inactive application slot and select it only after validation/currentness checks. Only that slot and `otadata` are written; NVS/data partitions are untouched.
|
||||
|
||||
One upload reserves HTTPS lifecycle/identity and excludes ordinary software reboot. The synchronous HTTPD handler can stall/drop browser serial/admin sessions. Its 120-second total receive-loop and ten-second stall checks are not preemptive flash deadlines or uninterrupted-traffic guarantees. Successful response send schedules a delayed reboot, not proof of peer receipt. Failed response after commit leaves the image selected, schedules no automatic reboot, releases reservations for manual reboot, and latches subsequent admissible uploads to 409 `firmware_selected_reboot_required` until reset. Never automatically retry an uncertain outcome.
|
||||
|
||||
See [firmware update](roadmap.md#phase-10--simple-admin-web-firmware-upload) for wired-first installation, exact image selection, manual recovery, acceptance limits and reusable future hardware regression checks. Phase 10 is accepted; there are no signature/version policies or automatic rollback.
|
||||
|
||||
### SSH
|
||||
|
||||
GET `ssh` supplies service/session state plus identity generation, fixed P-256 algorithm, unpadded OpenSSH `SHA256:` base64 fingerprint and rotatable flag. Service actions use exactly `action`, `generation`, `target`; rotate adds `identity_generation` and requires target zero. Start/stop and exact-session disconnect use published state, saturated service generation and the command mutex; exhausted SSH session slots retire rather than wrap. Disconnect success is an owner close request, not completed teardown. HTTPD never calls wolfSSH or waits for the SSH task.
|
||||
@@ -123,6 +133,6 @@ SSH changes leave invoking HTTPS available, so they use the ordinary ID-dispatch
|
||||
|
||||
## Diagnostics, recovery and scope boundaries
|
||||
|
||||
See [admission diagnostics](web_admission_diagnostics.md), [ordinary HTTPS idle cleanup](https_idle_cleanup.md), [throughput diagnostics](web_throughput_diagnostics.md) and [legacy storage compatibility](legacy_credential_removal.md). Broker read means transport handoff, not peer receipt; capture non-consuming counters before disconnect. TLS `-0x004C` is generic NET_RECV_FAILED, not evidence of OOM. Resource minima and counter observations require attribution, not inferred causes.
|
||||
See [admission diagnostics](web_admission_diagnostics.md), [ordinary HTTPS idle cleanup](https_idle_cleanup.md), [throughput diagnostics](web_throughput_diagnostics.md) and [legacy storage compatibility](roadmap.md#phase8-legacy-credential-compatibility). Broker read means transport handoff, not peer receipt; capture non-consuming counters before disconnect. TLS `-0x004C` is generic NET_RECV_FAILED, not evidence of OOM. Resource minima and counter observations require attribution, not inferred causes.
|
||||
|
||||
Phase 8D.15's dedicated typed network-diagnostics UI/API was removed: diagnostics remain shell-based, subject to frontend policy. The unimplemented 8D.19 ordinary browser-session/native-USB control expansion was removed; existing SSH controls remain. No full shell parity, browser identity recovery/reset/export, encryption, secure boot or OTA is implied by acceptance. UART0 is the administrative recovery authority; native USB is binary-transparent, network-independent UART1 access. Neither permits bypassing the broker's single writer or recalling already-admitted work.
|
||||
Phase 8D.15's dedicated typed network-diagnostics UI/API was removed: diagnostics remain shell-based, subject to frontend policy. The unimplemented 8D.19 ordinary browser-session/native-USB control expansion was removed; existing SSH controls remain. No full shell parity, browser identity recovery/reset/export, encryption or secure boot is implied by Phase 8 acceptance. Phase 10 application upload is complete by explicit user acceptance of upload and normal operation; unreported fault, NVS-comparison, power-loss and recovery checks are not implied. UART0 is the administrative recovery authority; native USB is binary-transparent, network-independent UART1 access. Neither permits bypassing the broker's single writer or recalling already-admitted work.
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
# Web administration acceptance
|
||||
|
||||
## Phase 8D.22 sign-off — 2026-09-13
|
||||
|
||||
The user explicitly accepted the tested firmware: **“Yep, I tested the firmware thats a 8d.22 signoff.”** Phase 8D is complete for its retained scope. This supersedes earlier per-slice target-pending, parent-review and integration-acceptance gates; it does not require additional testing to establish the user's sign-off. Phase 8A/B/C were already recorded as complete and target-hardware validated in the roadmap, so [Phase 8 is complete](roadmap.md#phase-8--role-based-users-and-administrative-access--complete).
|
||||
|
||||
Accepted functionality comprises cookie authentication, isolated browser serial/admin terminals, typed Serial/Accounts/authorized-key/Network/Display/Broker/SSH/HTTPS settings, contextual controls, confirmed generation-safe writer/service/identity actions and retained UART0/native-USB recovery. [Current contracts](web_administration.md) define actual permissions, bounded failures and partial effects; acceptance is not a claim of unrestricted browser-shell parity.
|
||||
|
||||
Scope decisions remain effective: 8D.15 dedicated typed network diagnostics was removed (shell diagnostics retained); the unimplemented 8D.19 ordinary browser-session/native-USB control remainder was removed (SSH controls retained). Browser identity Reset/recovery/export was not added. Phase 9 security/production hardening is next only on a separate user request.
|
||||
|
||||
## Latest firmware evidence
|
||||
|
||||
The **prior final firmware build passed**, reporting **100,556 bytes linked RAM / 1,828,573 bytes flash**, at **160 MHz**. This is recorded prior build evidence, not a build run during documentation consolidation. Host regression suites and independent implementation reviews were previously reported passing; no new host/runtime test pass is asserted here.
|
||||
|
||||
Latest user telemetry, bytes:
|
||||
|
||||
| Sample / memory capability | Free | Minimum free | Largest block |
|
||||
|---|---:|---:|---:|
|
||||
| Boot internal 8-bit | 59,808 | 58,840 | 31,744 |
|
||||
| Boot internal DMA | 52,052 | 51,084 | 31,744 |
|
||||
| Boot PSRAM | 8,196,968 | 8,183,972 | 8,126,464 |
|
||||
| Loaded after burst, internal 8-bit | 31,508 | 2,052 | 18,432 |
|
||||
| Loaded after burst, internal DMA | 23,752 | 460 | 18,432 |
|
||||
| Loaded after burst, PSRAM | 8,136,624 | 8,065,972 | 7,995,392 |
|
||||
|
||||
Loaded SSH minimum-free stack was **15,028 bytes**. The capture had two active SSH sessions across the serial/admin roles, two serial WebSockets and USB, with SSH holding the serial writer. Browser admin had been used and then closed; it was **not active in the captured loaded sample**. Web send/queue/protocol error counters were zero; SSH IO errors were zero, with one handshake failure and one session revocation retained without attributing a cause.
|
||||
|
||||
Latest broker/serial counters were not supplied, so these transport counters do **not** establish an exact latest zero-drop or byte-integrity result. No latest full-mix-with-browser-admin-active, individual fault-injection, exact duration, soak, cleanup-cycle or reserve-floor result is inferred. Generic SDK TLS `-0x004C` / NET_RECV_FAILED is not an OOM diagnosis. Two boot authentication failures could plausibly involve stale browser cookies, but that explanation is unconfirmed.
|
||||
|
||||
## Earlier acceptance retained without replaying the timeline
|
||||
|
||||
- M1 browser login/logout and M2 shared browser administration were explicitly accepted by the user; later Serial/account/Network presentation and legacy-credential cleanup also received scoped acceptance.
|
||||
- The user explicitly accepted the combined binary WebSocket-send fix at **160 MHz, 230400 baud with the full client mix, including browser admin**. That prior acceptance stands independently of the latest capture's closed browser-admin socket. It does not imply an unreported latest exact counter comparison or soak duration. Keep the combined send and bounded failed-send isolation, not the earlier frequency-only experiment.
|
||||
- Ordinary HTTPS idle cleanup was accepted as working; that is not a guarantee against all future admission failures or owner delays.
|
||||
|
||||
## Nonblocking follow-ups and evidence limits
|
||||
|
||||
The extremely low internal/DMA lifetime minima remain an unresolved transient-headroom follow-up, **not a blocker reopening 8D sign-off and not an approved reserve**. Capability pools overlap; summed per-region lifetime minima can be conservative/non-simultaneous and do not prove an allocation failure. Do not add internal and DMA numbers together or attribute an error to memory pressure without correlated evidence. HTTPD/dispatcher stack margins, peak correlation, allocation reserve policy and long-run cleanup/soak evidence remain distinct future measurements.
|
||||
|
||||
[Regression procedures](user_administration_tests.md#integrated-web-administration-regression-procedure) and focused test runners remain available for future changes; listing them is not evidence they all ran on hardware. Documentation-only consolidation changes no firmware, configuration, generated assets or test implementation and performs no build/upload/erase/commit. No new Phase 9 work is authorized by this acceptance.
|
||||
@@ -1,6 +1,6 @@
|
||||
# Web admission diagnostics
|
||||
|
||||
Current default-disabled instrumentation, not an admission fix or an allocation-failure detector. `src/web_diagnostics.{c,h}` observes public synchronous HTTPS create/close callbacks and the four serial/admin ticket/upgrade handlers. It adds no owner task, queued probe or socket-capacity change. See [ordinary idle cleanup](https_idle_cleanup.md) for the independent cleanup policy and [acceptance evidence](web_administration_acceptance.md) for user reports.
|
||||
Current default-disabled instrumentation, not an admission fix or an allocation-failure detector. `src/web_diagnostics.{c,h}` observes public synchronous HTTPS create/close callbacks and the four serial/admin ticket/upgrade handlers. It adds no owner task, queued probe or socket-capacity change. See [ordinary idle cleanup](https_idle_cleanup.md) for the independent cleanup policy and [acceptance evidence](roadmap.md#phase8-acceptance-evidence) for user reports.
|
||||
|
||||
## Usage and interpretation
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Current contract and accepted fix
|
||||
|
||||
Default-disabled instrumentation measures broker fan-out and binary WebSocket TX, not CPU usage, peer receipt or pure scheduler latency. The user separately accepted the combined binary header/payload-send fix at **160 MHz / 230400 baud with full mix including browser admin**. [Acceptance evidence](web_administration_acceptance.md) distinguishes that report from the latest capture and unmeasured reserves. The old frequency-only experiment is not the retained solution.
|
||||
Default-disabled instrumentation measures broker fan-out and binary WebSocket TX, not CPU usage, peer receipt or pure scheduler latency. The user separately accepted the combined binary header/payload-send fix at **160 MHz / 230400 baud with full mix including browser admin**. [Acceptance evidence](roadmap.md#phase8-acceptance-evidence) distinguishes that report from the latest capture and unmeasured reserves. The old frequency-only experiment is not the retained solution.
|
||||
|
||||
`web_httpd_ws_send_binary` is an IDF-5.5.0-pinned owner-only session-override send with a bounded 516-byte header/payload scratch copy and 512-byte payload. Generation validation and one outstanding item per slot remain. A non-full send installs a reject-only override before close/shutdown, preventing SDK automatic PONG/CLOSE from reentering TLS with different arguments after incomplete output, even if shutdown fails. No retry/replay; HTTPD owns TLS destruction. Text/control/admin retain the SDK sender. One API call does not promise one TLS record/packet or peer receipt. Keep CPU160, priorities, scheduling and 4096-byte broker/512-byte web payload bounds unless separately changing them with evidence.
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# KiCad per-user state, locks and automatic backups
|
||||
*.kicad_prl
|
||||
*.lck
|
||||
~*.lck
|
||||
*-backups/
|
||||
*.bak
|
||||
__pycache__/
|
||||
@@ -0,0 +1,116 @@
|
||||
(kicad_symbol_lib (version 20231120) (generator "kicad_symbol_editor")
|
||||
(symbol "HW678_N16R8" (pin_names (offset 0.762)) (in_bom yes) (on_board yes)
|
||||
(property "Reference" "U" (at 0 34.29 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "HW678_N16R8" (at 0 31.75 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "Carrier:HW678_2x22_Provisional" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Description" "HW-678 V0.0.0 / N16R8 carrier interface; project numbering; onboard USB/regulator retained" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(symbol "HW678_N16R8_0_1" (rectangle (start -17.78 29.21) (end 17.78 -29.21) (stroke (width 0.254) (type default)) (fill (type background))))
|
||||
(symbol "HW678_N16R8_1_1"
|
||||
(pin power_out line (at -22.86 26.67 0) (length 5.08) (name "3V3" (effects (font (size 1.016 1.016)) )) (number "1" (effects (font (size 1.016 1.016)) )))
|
||||
(pin passive line (at -22.86 24.13 0) (length 5.08) (name "3V3" (effects (font (size 1.016 1.016)) )) (number "2" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 21.59 0) (length 5.08) (name "EN" (effects (font (size 1.016 1.016)) )) (number "3" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 19.05 0) (length 5.08) (name "GPIO4" (effects (font (size 1.016 1.016)) )) (number "4" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 16.51 0) (length 5.08) (name "GPIO5" (effects (font (size 1.016 1.016)) )) (number "5" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 13.97 0) (length 5.08) (name "GPIO6" (effects (font (size 1.016 1.016)) )) (number "6" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -22.86 11.43 0) (length 5.08) (name "GPIO7" (effects (font (size 1.016 1.016)) )) (number "7" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -22.86 8.89 0) (length 5.08) (name "GPIO15" (effects (font (size 1.016 1.016)) )) (number "8" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 6.35 0) (length 5.08) (name "GPIO16" (effects (font (size 1.016 1.016)) )) (number "9" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -22.86 3.81 0) (length 5.08) (name "GPIO17" (effects (font (size 1.016 1.016)) )) (number "10" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 1.27 0) (length 5.08) (name "GPIO18" (effects (font (size 1.016 1.016)) )) (number "11" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 -1.27 0) (length 5.08) (name "GPIO8" (effects (font (size 1.016 1.016)) )) (number "12" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at -22.86 -3.81 0) (length 5.08) (name "GPIO3" (effects (font (size 1.016 1.016)) )) (number "13" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at -22.86 -6.35 0) (length 5.08) (name "GPIO46" (effects (font (size 1.016 1.016)) )) (number "14" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at -22.86 -8.89 0) (length 5.08) (name "GPIO9" (effects (font (size 1.016 1.016)) )) (number "15" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 -11.43 0) (length 5.08) (name "GPIO10" (effects (font (size 1.016 1.016)) )) (number "16" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at -22.86 -13.97 0) (length 5.08) (name "GPIO11" (effects (font (size 1.016 1.016)) )) (number "17" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -22.86 -16.51 0) (length 5.08) (name "GPIO12" (effects (font (size 1.016 1.016)) )) (number "18" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 -19.05 0) (length 5.08) (name "GPIO13" (effects (font (size 1.016 1.016)) )) (number "19" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 -21.59 0) (length 5.08) (name "GPIO14" (effects (font (size 1.016 1.016)) )) (number "20" (effects (font (size 1.016 1.016)) )))
|
||||
(pin passive line (at -22.86 -24.13 0) (length 5.08) (name "5V" (effects (font (size 1.016 1.016)) )) (number "21" (effects (font (size 1.016 1.016)) )))
|
||||
(pin power_out line (at -22.86 -26.67 0) (length 5.08) (name "GND" (effects (font (size 1.016 1.016)) )) (number "22" (effects (font (size 1.016 1.016)) )))
|
||||
(pin passive line (at 22.86 26.67 180) (length 5.08) (name "GND" (effects (font (size 1.016 1.016)) )) (number "23" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 24.13 180) (length 5.08) (name "GPIO43" (effects (font (size 1.016 1.016)) )) (number "24" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 21.59 180) (length 5.08) (name "GPIO44" (effects (font (size 1.016 1.016)) )) (number "25" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 19.05 180) (length 5.08) (name "GPIO1" (effects (font (size 1.016 1.016)) )) (number "26" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 16.51 180) (length 5.08) (name "GPIO2" (effects (font (size 1.016 1.016)) )) (number "27" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 13.97 180) (length 5.08) (name "GPIO42" (effects (font (size 1.016 1.016)) )) (number "28" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 11.43 180) (length 5.08) (name "GPIO41" (effects (font (size 1.016 1.016)) )) (number "29" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 8.89 180) (length 5.08) (name "GPIO40" (effects (font (size 1.016 1.016)) )) (number "30" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 6.35 180) (length 5.08) (name "GPIO39" (effects (font (size 1.016 1.016)) )) (number "31" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 3.81 180) (length 5.08) (name "GPIO38" (effects (font (size 1.016 1.016)) )) (number "32" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 1.27 180) (length 5.08) (name "GPIO37" (effects (font (size 1.016 1.016)) )) (number "33" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -1.27 180) (length 5.08) (name "GPIO36" (effects (font (size 1.016 1.016)) )) (number "34" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -3.81 180) (length 5.08) (name "GPIO35" (effects (font (size 1.016 1.016)) )) (number "35" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -6.35 180) (length 5.08) (name "GPIO0" (effects (font (size 1.016 1.016)) )) (number "36" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -8.89 180) (length 5.08) (name "GPIO45" (effects (font (size 1.016 1.016)) )) (number "37" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -11.43 180) (length 5.08) (name "GPIO48" (effects (font (size 1.016 1.016)) )) (number "38" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -13.97 180) (length 5.08) (name "GPIO47" (effects (font (size 1.016 1.016)) )) (number "39" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -16.51 180) (length 5.08) (name "GPIO21" (effects (font (size 1.016 1.016)) )) (number "40" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -19.05 180) (length 5.08) (name "GPIO20" (effects (font (size 1.016 1.016)) )) (number "41" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -21.59 180) (length 5.08) (name "GPIO19" (effects (font (size 1.016 1.016)) )) (number "42" (effects (font (size 1.016 1.016)) )))
|
||||
(pin passive line (at 22.86 -24.13 180) (length 5.08) (name "GND" (effects (font (size 1.016 1.016)) )) (number "43" (effects (font (size 1.016 1.016)) )))
|
||||
(pin passive line (at 22.86 -26.67 180) (length 5.08) (name "GND" (effects (font (size 1.016 1.016)) )) (number "44" (effects (font (size 1.016 1.016)) )))
|
||||
)
|
||||
)
|
||||
(symbol "MAX3243_Module_Reference" (pin_names (offset 0.762)) (in_bom yes) (on_board yes)
|
||||
(property "Reference" "U" (at 0 22.86 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "MAX3243_Module_Reference" (at 0 20.32 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Description" "Adafruit male 6253 intended; physical numbering from published female 5988 CAD, VERIFY MALE" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(symbol "MAX3243_Module_Reference_0_1" (rectangle (start -15.24 17.78) (end 15.24 -17.78) (stroke (width 0.254) (type default)) (fill (type background))))
|
||||
(symbol "MAX3243_Module_Reference_1_1"
|
||||
(pin power_in line (at -20.32 13.97 0) (length 5.08) (name "Vin" (effects (font (size 1.016 1.016)) )) (number "1" (effects (font (size 1.016 1.016)) )))
|
||||
(pin power_in line (at -20.32 11.43 0) (length 5.08) (name "GND" (effects (font (size 1.016 1.016)) )) (number "2" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -20.32 8.89 0) (length 5.08) (name "DCD" (effects (font (size 1.016 1.016)) )) (number "3" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -20.32 6.35 0) (length 5.08) (name "RX" (effects (font (size 1.016 1.016)) )) (number "4" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -20.32 3.81 0) (length 5.08) (name "TX" (effects (font (size 1.016 1.016)) )) (number "5" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -20.32 1.27 0) (length 5.08) (name "DTR" (effects (font (size 1.016 1.016)) )) (number "6" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -20.32 -1.27 0) (length 5.08) (name "DSR" (effects (font (size 1.016 1.016)) )) (number "7" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -20.32 -3.81 0) (length 5.08) (name "RTS" (effects (font (size 1.016 1.016)) )) (number "8" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -20.32 -6.35 0) (length 5.08) (name "CTS" (effects (font (size 1.016 1.016)) )) (number "9" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -20.32 -8.89 0) (length 5.08) (name "RI" (effects (font (size 1.016 1.016)) )) (number "10" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -20.32 -11.43 0) (length 5.08) (name "VLD" (effects (font (size 1.016 1.016)) )) (number "11" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -20.32 -13.97 0) (length 5.08) (name "OFF_N" (effects (font (size 1.016 1.016)) )) (number "12" (effects (font (size 1.016 1.016)) )))
|
||||
)
|
||||
)
|
||||
(symbol "OLED_I2C_26mm" (pin_names (offset 0.762)) (in_bom yes) (on_board yes)
|
||||
(property "Reference" "DS" (at 0 12.7 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "OLED_I2C_26mm" (at 0 10.16 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "Carrier:OLED_26mm_I2C_Provisional" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Description" "User fit-verified 26 mm OLED; front/top header GND VCC SCL SDA" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(symbol "OLED_I2C_26mm_0_1" (rectangle (start -10.16 7.62) (end 10.16 -7.62) (stroke (width 0.254) (type default)) (fill (type background))))
|
||||
(symbol "OLED_I2C_26mm_1_1"
|
||||
(pin power_in line (at -15.24 3.81 0) (length 5.08) (name "GND" (effects (font (size 1.016 1.016)) )) (number "1" (effects (font (size 1.016 1.016)) )))
|
||||
(pin power_in line (at -15.24 1.27 0) (length 5.08) (name "VCC" (effects (font (size 1.016 1.016)) )) (number "2" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -15.24 -1.27 0) (length 5.08) (name "SCL" (effects (font (size 1.016 1.016)) )) (number "3" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at -15.24 -3.81 0) (length 5.08) (name "SDA" (effects (font (size 1.016 1.016)) )) (number "4" (effects (font (size 1.016 1.016)) )))
|
||||
)
|
||||
)
|
||||
(symbol "SW_NO" (pin_names (offset 0.762)) (in_bom yes) (on_board yes)
|
||||
(property "Reference" "SW" (at 0 7.62 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "SW_NO" (at 0 5.08 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Description" "Normally open contact; footprint duplicates pads 1 and 2 for internally common terminal pairs" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(symbol "SW_NO_0_1" (polyline (pts (xy -2.54 0) (xy 2.54 1.524)) (stroke (width 0.254) (type default)) (fill (type none)))(circle (center -2.54 0) (radius 0.35) (stroke (width 0.1524) (type default)) (fill (type none)))(circle (center 2.54 0) (radius 0.35) (stroke (width 0.1524) (type default)) (fill (type none))))
|
||||
(symbol "SW_NO_1_1"
|
||||
(pin passive line (at -5.08 0 0) (length 2.54) (name "~" (effects (font (size 1.016 1.016)) )) (number "1" (effects (font (size 1.016 1.016)) )))
|
||||
(pin passive line (at 5.08 0 180) (length 2.54) (name "~" (effects (font (size 1.016 1.016)) )) (number "2" (effects (font (size 1.016 1.016)) )))
|
||||
)
|
||||
)
|
||||
(symbol "R" (pin_names (offset 0.762)) (in_bom yes) (on_board yes)
|
||||
(property "Reference" "R" (at 0 6.096 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "R" (at 0 3.556 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Description" "Resistor, axial DIN0207 footprint; exact MPN pending" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(symbol "R_0_1" (rectangle (start -2.54 1.016) (end 2.54 -1.016) (stroke (width 0.254) (type default)) (fill (type background))))
|
||||
(symbol "R_1_1"
|
||||
(pin passive line (at -5.08 0 0) (length 2.54) (name "~" (effects (font (size 1.016 1.016)) )) (number "1" (effects (font (size 1.016 1.016)) )))
|
||||
(pin passive line (at 5.08 0 180) (length 2.54) (name "~" (effects (font (size 1.016 1.016)) )) (number "2" (effects (font (size 1.016 1.016)) )))
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,56 @@
|
||||
(footprint "HW678_2x22_Provisional"
|
||||
(version 20260206)
|
||||
(generator "pcbnew")
|
||||
(layer "F.Cu")
|
||||
(descr "HW-678, component/top view antenna up USB down; origin pad 1; left top-bottom 1..22, right top-bottom 23..44; only header centres verified; no GPIO mapping, outline, courtyard or antenna keepout; PROVISIONAL connector fit: 1.70 mm pads / 1.00 mm drills from installed KiCad PinHeader_1x04_P2.54mm_Vertical, awaiting selected actual pin/socket datasheet")
|
||||
(tags "module provisional THT")
|
||||
(attr through_hole)
|
||||
(property "Reference" "REF**" (at 12.7 -3) (layer "F.SilkS") (effects (font (size 1 1) (thickness 0.15))))
|
||||
(property "Value" "HW678_2x22_Provisional" (at 12.7 57) (layer "F.Fab") (effects (font (size 1 1) (thickness 0.15))))
|
||||
(fp_text user "PROVISIONAL CONNECTOR FIT" (at 12.7 25) (layer "F.Fab") (effects (font (size 1 1) (thickness 0.15))))
|
||||
(fp_text user "ANTENNA UP / USB DOWN" (at 12.7 28) (layer "F.Fab") (effects (font (size 1 1) (thickness 0.15))))
|
||||
(pad "1" thru_hole rect (at 0.00 0.00) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "2" thru_hole circle (at 0.00 2.54) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "3" thru_hole circle (at 0.00 5.08) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "4" thru_hole circle (at 0.00 7.62) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "5" thru_hole circle (at 0.00 10.16) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "6" thru_hole circle (at 0.00 12.70) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "7" thru_hole circle (at 0.00 15.24) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "8" thru_hole circle (at 0.00 17.78) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "9" thru_hole circle (at 0.00 20.32) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "10" thru_hole circle (at 0.00 22.86) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "11" thru_hole circle (at 0.00 25.40) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "12" thru_hole circle (at 0.00 27.94) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "13" thru_hole circle (at 0.00 30.48) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "14" thru_hole circle (at 0.00 33.02) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "15" thru_hole circle (at 0.00 35.56) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "16" thru_hole circle (at 0.00 38.10) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "17" thru_hole circle (at 0.00 40.64) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "18" thru_hole circle (at 0.00 43.18) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "19" thru_hole circle (at 0.00 45.72) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "20" thru_hole circle (at 0.00 48.26) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "21" thru_hole circle (at 0.00 50.80) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "22" thru_hole circle (at 0.00 53.34) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "23" thru_hole circle (at 25.40 0.00) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "24" thru_hole circle (at 25.40 2.54) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "25" thru_hole circle (at 25.40 5.08) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "26" thru_hole circle (at 25.40 7.62) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "27" thru_hole circle (at 25.40 10.16) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "28" thru_hole circle (at 25.40 12.70) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "29" thru_hole circle (at 25.40 15.24) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "30" thru_hole circle (at 25.40 17.78) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "31" thru_hole circle (at 25.40 20.32) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "32" thru_hole circle (at 25.40 22.86) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "33" thru_hole circle (at 25.40 25.40) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "34" thru_hole circle (at 25.40 27.94) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "35" thru_hole circle (at 25.40 30.48) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "36" thru_hole circle (at 25.40 33.02) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "37" thru_hole circle (at 25.40 35.56) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "38" thru_hole circle (at 25.40 38.10) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "39" thru_hole circle (at 25.40 40.64) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "40" thru_hole circle (at 25.40 43.18) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "41" thru_hole circle (at 25.40 45.72) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "42" thru_hole circle (at 25.40 48.26) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "43" thru_hole circle (at 25.40 50.80) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "44" thru_hole circle (at 25.40 53.34) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
)
|
||||
@@ -0,0 +1,34 @@
|
||||
(footprint "MAX3243_Reference_Provisional"
|
||||
(version 20260206)
|
||||
(generator "pcbnew")
|
||||
(layer "F.Cu")
|
||||
(descr "PROVISIONAL female 5988 CAD reference / VERIFY MALE 6253 geometry and pad map. Top view DE9 toward -Y; origin JP2 pin 1. Female reference: 1 Vin, 2 GND, 3 DCD, 4 RX, 5 TX, 6 DTR, 7 DSR, 8 RTS, 9 CTS, 10 RI, 11 VLD, 12 OFF. PROVISIONAL connector fit: carrier pads 1.70 mm / drill 1.00 mm. No verified DE9 envelope or courtyard. CAD-derived outline: Limor Fried/Ladyada for Adafruit Industries, CC BY-SA 3.0; see rs232-footprint-notes.md")
|
||||
(tags "MAX3243 module female5988 reference male6253 provisional THT")
|
||||
(attr through_hole)
|
||||
(property "Reference" "REF**" (at 13.97 -23) (layer "F.SilkS") (effects (font (size 1 1) (thickness 0.15))))
|
||||
(property "Value" "MAX3243_Reference_Provisional" (at 13.97 -20.5) (layer "F.Fab") (effects (font (size 0.8 0.8) (thickness 0.12))))
|
||||
(fp_text user "PROVISIONAL" (at 13.97 -16) (layer "F.SilkS") (effects (font (size 1 1) (thickness 0.15))))
|
||||
(fp_text user "FEMALE CAD REF" (at 13.97 -14) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.12))))
|
||||
(fp_text user "VERIFY MALE" (at 13.97 -12.3) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.12))))
|
||||
(fp_text user "1" (at 0 1.7) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.12))))
|
||||
(fp_line (start 29.845 0.127) (end 29.845 -26.67) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
|
||||
(fp_line (start 29.845 -26.67) (end -1.905 -26.67) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
|
||||
(fp_line (start -1.905 -26.67) (end -1.905 0.127) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
|
||||
(fp_arc (start -1.905 0.127) (mid -1.161051224 1.923051224) (end 0.635 2.667) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
|
||||
(fp_line (start 0.635 2.667) (end 27.305 2.667) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
|
||||
(fp_arc (start 27.305 2.667) (mid 29.101051224 1.923051224) (end 29.845 0.127) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
|
||||
(pad "1" thru_hole rect (at 0 0) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "2" thru_hole circle (at 2.54 0) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "3" thru_hole circle (at 5.08 0) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "4" thru_hole circle (at 7.62 0) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "5" thru_hole circle (at 10.16 0) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "6" thru_hole circle (at 12.7 0) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "7" thru_hole circle (at 15.24 0) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "8" thru_hole circle (at 17.78 0) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "9" thru_hole circle (at 20.32 0) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "10" thru_hole circle (at 22.86 0) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "11" thru_hole circle (at 25.4 0) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "12" thru_hole circle (at 27.94 0) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "" np_thru_hole circle (at 1.905 -8.763) (size 3 3) (drill 3) (layers "*.Cu" "*.Mask"))
|
||||
(pad "" np_thru_hole circle (at 26.035 -8.763) (size 3 3) (drill 3) (layers "*.Cu" "*.Mask"))
|
||||
)
|
||||
@@ -0,0 +1,21 @@
|
||||
(footprint "OLED_26mm_I2C_Provisional"
|
||||
(version 20260206)
|
||||
(generator "pcbnew")
|
||||
(layer "F.Cu")
|
||||
(descr "User-verified front/display view, header at top; origin top-left PCB corner; 1 GND, 2 VCC, 3 SCL, 4 SDA; PROVISIONAL connector fit: 1.70 mm pads / 1.00 mm drills from installed KiCad PinHeader_1x04_P2.54mm_Vertical, awaiting selected actual pin/socket datasheet")
|
||||
(tags "module provisional THT")
|
||||
(attr through_hole)
|
||||
(property "Reference" "REF**" (at 13 -2) (layer "F.SilkS") (effects (font (size 1 1) (thickness 0.15))))
|
||||
(property "Value" "OLED_26mm_I2C_Provisional" (at 13 28) (layer "F.Fab") (effects (font (size 1 1) (thickness 0.15))))
|
||||
(fp_text user "PROVISIONAL CONNECTOR FIT" (at 13 30) (layer "F.Fab") (effects (font (size 1 1) (thickness 0.15))))
|
||||
(fp_rect (start 0 0) (end 26 26) (stroke (width 0.1) (type solid)) (fill no) (layer "F.Fab"))
|
||||
(fp_rect (start 1 4.5) (end 25.5 21) (stroke (width 0.1) (type dash)) (fill no) (layer "F.Fab"))
|
||||
(pad "1" thru_hole rect (at 9.19 1.50) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "2" thru_hole circle (at 11.73 1.50) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "3" thru_hole circle (at 14.27 1.50) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "4" thru_hole circle (at 16.81 1.50) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "" np_thru_hole circle (at 1.75 1.75) (size 2 2) (drill 2) (layers "*.Cu" "*.Mask"))
|
||||
(pad "" np_thru_hole circle (at 24.25 1.75) (size 2 2) (drill 2) (layers "*.Cu" "*.Mask"))
|
||||
(pad "" np_thru_hole circle (at 1.75 24.25) (size 2 2) (drill 2) (layers "*.Cu" "*.Mask"))
|
||||
(pad "" np_thru_hole circle (at 24.25 24.25) (size 2 2) (drill 2) (layers "*.Cu" "*.Mask"))
|
||||
)
|
||||
@@ -0,0 +1,268 @@
|
||||
# KiCad 10 module carrier — schematic and routed PCB draft
|
||||
|
||||
Open **`serial-carrier.kicad_pro`** in KiCad 10, then open
|
||||
**`serial-carrier.kicad_sch`**. A readable export is **`serial-carrier.pdf`**.
|
||||
This is a native, editable schematic with actual connected nets, not an image
|
||||
or a SKiDL-only netlist. **`serial-carrier.kicad_pcb` now contains a routed,
|
||||
82 × 80 mm two-copper-layer A1 carrier draft with a top-left antenna notch,
|
||||
USB toward the bottom and RS-232 toward the top.** It is **not fabrication-ready**.
|
||||
See **`pcb-draft-notes.md`** for placement, chosen dimensions/rules, provisional
|
||||
mechanical assumptions and validation. A review drawing is `pcb-placement-draft.pdf`.
|
||||
|
||||
The schematic contains the complete ESP32 development-board interface, MAX3243
|
||||
breakout logic interface, I²C OLED, three Omron B3F-1000 buttons and three 2.2 kΩ
|
||||
button pull-ups. USB connectors, regulator, reset/boot circuit, DE-9 and MAX3243
|
||||
charge pump are already on their respective modules and are not duplicated on
|
||||
the carrier. All 3.3 V and ground connections are explicit named nets.
|
||||
|
||||
## Files and use
|
||||
|
||||
| File | Purpose |
|
||||
|---|---|
|
||||
| `serial-carrier.kicad_pro`, `serial-carrier.kicad_sch` | Native KiCad project and A3 single-sheet schematic |
|
||||
| `serial-carrier.pdf` | Schematic review/print export |
|
||||
| `serial-carrier.kicad_pcb` | Routed 82 × 80 mm stepped, two-copper-layer PCB draft |
|
||||
| `pcb-draft-notes.md`, `pcb-placement-draft.pdf` | PCB design decisions/limits and 1:1 placement review |
|
||||
| `validation/pcb-draft.png`, `validation/pcb-draft-3d.png` | Copper/fab preview and carrier-only 3D render |
|
||||
| `validate_pcb_draft.py` | Fresh schematic-to-PCB net checks, bounds/layers and DRC/parity report |
|
||||
| `generate_pcb_draft.py`, `route_pcb_draft.py` | Optional destructive-regeneration tools with explicit overwrite guards |
|
||||
| `add_device_silkscreen.py` | Refresh board-local OLED/RS-232 outlines and ESP32 header guides after placement/routing; preserves copper and project settings |
|
||||
| `db9-mechanical-notes.md`, `reference/db9-cad/` | STEP-derived metal dimensions, originals, source licenses and repeatable extraction/validation |
|
||||
| `add_db9_envelope.py` | Refresh nominal connector metal projections on Dwgs.User; not a case cutout or assembly clearance |
|
||||
| `Carrier.kicad_sym`, `sym-lib-table` | Project-local module, switch and resistor symbols |
|
||||
| `Carrier.pretty/`, `fp-lib-table` | OLED/ESP32/RS-232-reference provisional footprints; standard KiCad 10 switch/resistor/mounting-hole library references |
|
||||
| `oled-fit-check.svg` | User-fit-verified OLED mechanical template |
|
||||
| `footprint-notes.md`, `rs232-footprint-notes.md` | Footprint origins, provisional pad/drill choices, validation and RS-232 adaptation attribution |
|
||||
| `adafruit-research.md`, `button-notes.md`, `reference/` | Manufacturer evidence, pad maps and source/license records |
|
||||
| `bom-draft.csv` | Draft procurement list and unresolved selections |
|
||||
| `validate_schematic.py`, `validation/` | Repeatable netlist/firmware/ERC checks and exported evidence |
|
||||
| `validate_footprints.py` | Native KiCad footprint geometry regression checks via system Python/pcbnew |
|
||||
| `generate_schematic.py` | Optional stdlib-only initial design generator; not needed to open/edit in KiCad |
|
||||
|
||||
Keep these files together so project-relative libraries resolve. Standard KiCad
|
||||
10 footprint libraries must be installed for the THT buttons, resistors and mounting holes.
|
||||
SKiDL 2.3.0 is installed, but no SKiDL dependency is required for this native design.
|
||||
|
||||
**Normal validation from the repository root:**
|
||||
|
||||
```sh
|
||||
python3 hardware/PCB/validate_schematic.py
|
||||
```
|
||||
|
||||
This checks the current schematic without regenerating it, exports its netlist,
|
||||
runs KiCad ERC, checks all 17 connected nets and all 15 firmware GPIO assignments,
|
||||
and refreshes PDF/SVG review exports. It requires Python 3 and `kicad-cli`.
|
||||
The expected-net snapshot records the initial design contract; intentional wiring
|
||||
changes need a deliberate update to the checks and snapshot.
|
||||
|
||||
**Do not casually rerun `generate_schematic.py`:** it overwrites the schematic,
|
||||
symbol library, library tables and expected-net snapshot, discarding subsequent
|
||||
manual edits to those files. It preserves existing project settings and does not
|
||||
modify footprints. The generated KiCad files are the editable design artifacts.
|
||||
|
||||
## Schematic conventions and design decisions
|
||||
|
||||
- U1 numbering is project-specific: viewed from the component side, antenna up
|
||||
and USB down, left row is 1–22 and right row is 23–44, both top-to-bottom.
|
||||
These numbers map directly to the local HW678 footprint, not to GPIO numbers
|
||||
or the official DevKit header reference designators. Signal order was transcribed
|
||||
from the user-supplied component-side board-label photos; the user confirmed
|
||||
matching board markings. This is photo/label evidence, not a continuity test:
|
||||
verify actual pin-to-function mapping before release, especially on clones.
|
||||
- DS1 pins 1–4 are GND, VCC, SCL, SDA, front/display view with header at top.
|
||||
- U2 pin numbering follows published Adafruit **5988 female** CAD as a provisional
|
||||
reference for the selected **6253 male** module. Its footprint is deliberately
|
||||
**unassigned in the schematic** until the actual male header and mechanics are
|
||||
verified. The PCB draft assigns `Carrier:MAX3243_Reference_Provisional` on the
|
||||
PCB only, causing one documented footprint-parity warning. Its reference nets
|
||||
are routed for review, not male-board qualification. See `adafruit-research.md`
|
||||
and `pcb-draft-notes.md`; this is not a generic DE-9 footprint.
|
||||
- No-connect crosses mean unused on the carrier, not disconnected internally.
|
||||
Native USB GPIO19/20, UART0 GPIO43/44, memory-conflicting GPIO35–37 and the 5 V
|
||||
header are not connected to carrier circuitry.
|
||||
- U1 pin 1 (3V3) and pin 22 (GND) model the onboard supply as power outputs for
|
||||
ERC; the other duplicate rail pins are passive. This does not verify the
|
||||
regulator's capacity or USB power isolation. GPIO9 is modeled as a programmable
|
||||
bidirectional pad; firmware **must use open-drain mode** for OFF. ERC does not
|
||||
enforce that configuration or model the breakout's internal OFF pull-up.
|
||||
- Selected buttons are Omron B3F-1000 with their verified KiCad THT footprint.
|
||||
Added R1–R3, **2.2 kΩ, 1%, at least 0.125 W**, from each button input to 3.3 V.
|
||||
They provide nominal 1.5 mA contact current / 4.95 mW dissipation while pressed,
|
||||
rather than relying on the internal pull-up for the silver-contact rated load.
|
||||
Firmware's internal pull-ups and debouncing remain enabled; no firmware changes
|
||||
are required. Final supply tolerance/load budget still needs checking.
|
||||
DIN0207 axial footprints are selected; exact resistor MPNs remain pending.
|
||||
- External I²C pull-ups are **not assigned guessed values**. Verify OLED onboard
|
||||
pull-ups, resistance and rail before electrical sign-off and add appropriately
|
||||
sized carrier pull-ups if needed. The OLED must be powered from 3.3 V.
|
||||
|
||||
## Validation status
|
||||
|
||||
KiCad CLI 10.0.6 loaded the schematic and exported PDF, SVG and XML netlist.
|
||||
ERC reported **zero errors/warnings**, with no project exclusions added. Exact
|
||||
net memberships, all 44 U1 pins and firmware GPIO assignments were checked.
|
||||
The PDF was rendered and visually inspected for layout/readability. Both local
|
||||
footprints loaded in KiCad's native footprint loader. Two retained regression tests
|
||||
passed with `/usr/bin/python3 -B hardware/PCB/validate_footprints.py`; the binding
|
||||
emits startup PROPERTY_ENUM assertions but completes the checks. See
|
||||
`footprint-notes.md` for scope and limitations.
|
||||
|
||||
OLED PCB/header/display/mounting-hole geometry was physically fit-verified by
|
||||
the user. This does **not** validate connector drills, male RS-232 geometry,
|
||||
electrical operation, power budget, procurement or fabrication.
|
||||
|
||||
The routed PCB draft has **0 physical DRC violations and 0 unconnected items**;
|
||||
a fresh netlist-to-PCB check passes. One deliberate U2 footprint-parity warning
|
||||
remains (PCB reference footprint versus blank schematic assignment). No added
|
||||
DRC exclusions hide this warning. Missing courtyards are ignored by default DRC
|
||||
and remain a manual release check. Run `/usr/bin/python3 -B
|
||||
hardware/PCB/validate_pcb_draft.py` from the repository root; see
|
||||
`pcb-draft-notes.md` for the complete verification scope and remaining gates.
|
||||
No firmware build or device tests were run: this PCB work did not modify firmware.
|
||||
|
||||
## Evidence and components
|
||||
|
||||
Repository sources: `../../docs/wiring.md`, `../../src/board_pins.h`,
|
||||
`../../docs/electrical_tests.md`, and `../../README.md`.
|
||||
|
||||
| Assembly | Repository identification | Footprint status |
|
||||
|---|---|---|
|
||||
| ESP32 development board | User confirmed HW-678 V0.0.0 markings and S3-N16R8 shield, two USB-C ports | Two 22-pin rows; nominal 2.54 mm pitch and 25.40 mm row spacing supported by simultaneous breadboard fit. Mechanical outline registration and socket/drill selection remain pending. |
|
||||
| RS-232 module | Adafruit MAX3243 full-pinout breakout; male is product 6253, female is 5988 | Eagle pin map/PCB geometry and STEP metal dimensions extracted. User confirms identical male/female external dimensions; actual fit and male electrical map remain release checks. Reference footprint used on PCB only. |
|
||||
| Display | 128×64 yellow/blue I²C OLED, address 0x3c; Amazon ASIN B0F13SSSFB | 26 mm square board, 22.50 mm hole spacing, Ø2 mm holes and centred header 1.50 mm from top edge are user-fit-verified. Local footprint assigned; header pad/drill selection remains provisional. |
|
||||
| Three buttons | Omron B3F-1000; previous, select, next; normally open to ground, active low | Datasheet and `Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x` pad mapping verified. External 2.2 kΩ pull-ups included. Live distributor stock remains unverified. |
|
||||
|
||||
Adafruit manufacturer CAD starting point:
|
||||
https://learn.adafruit.com/adafruit-rs-232-full-pinout-level-shifter-breakout/downloads
|
||||
|
||||
Published female-module Eagle CAD and its header map were retrieved and verified;
|
||||
see `adafruit-research.md` for pinned revision, hashes, attribution and geometry.
|
||||
The original files and CC BY-SA 3.0 notices are retained in `reference/`; they
|
||||
remain under their upstream license. Omron reference documents retain their
|
||||
manufacturer notices. The CAD-parts repository's STEP and MIT license are separately
|
||||
retained under `reference/db9-cad/`. External metal geometry is used for the male
|
||||
module on the user's explicit equivalence confirmation; this does not validate
|
||||
its electrical map, connector threads, tolerances, assembly fit or case cutout.
|
||||
|
||||
## Verified logical connections for the existing firmware profile
|
||||
|
||||
This table uses signal names, **not physical connector positions**. The schematic
|
||||
and its local symbols additionally capture the project's physical pad numbering.
|
||||
The wiring guide warns that compatible ESP32 boards may have different layouts.
|
||||
|
||||
| ESP32 connection | Peripheral connection |
|
||||
|---|---|
|
||||
| 3V3 | RS-232 Vin; existing I²C OLED VCC |
|
||||
| GND | RS-232 GND; OLED GND; one contact of each button |
|
||||
| GPIO17 | RS-232 TX |
|
||||
| GPIO18 | RS-232 RX |
|
||||
| GPIO15 | RS-232 RTS |
|
||||
| GPIO16 | RS-232 CTS |
|
||||
| GPIO7 | RS-232 DTR |
|
||||
| GPIO5 | RS-232 DSR |
|
||||
| GPIO4 | RS-232 DCD |
|
||||
| GPIO6 | RS-232 RI |
|
||||
| GPIO8 | RS-232 VLD |
|
||||
| GPIO9 | RS-232 OFF (active-low shutdown; open-drain drive) |
|
||||
| GPIO11 | Existing I²C OLED SDA |
|
||||
| GPIO12 | Existing I²C OLED SCL |
|
||||
| GPIO10 | Previous/back switch to GND |
|
||||
| GPIO13 | Select/confirm switch to GND |
|
||||
| GPIO14 | Next switch to GND |
|
||||
|
||||
## Electrical constraints from the repository
|
||||
|
||||
- Keep UART0 / GPIO43 and GPIO44 available through the onboard USB-UART bridge.
|
||||
- Reserve GPIO19 and GPIO20 for onboard native USB.
|
||||
- Existing firmware profile powers RS-232 Vin and OLED VCC from 3.3 V, not 5 V.
|
||||
- Confirm the exact development board's regulator capacity and USB power-path
|
||||
behavior before finalizing the power design, especially with both USB ports attached.
|
||||
- Display pull-up presence, resistance and termination rail must be checked on
|
||||
the actual module. Do not select external pull-up values without this information.
|
||||
- Firmware enables internal button pull-ups; this carrier additionally uses 2.2 kΩ
|
||||
external pull-ups for the selected buttons' contact-current requirement.
|
||||
- RS-232 OFF uses the breakout's onboard pull-up; GPIO9 releases it to enable.
|
||||
- The breakout is not galvanically isolated; signal grounds are common.
|
||||
- Preserve access to both development-board USB connectors and account for the
|
||||
antenna keepout using the exact board/module documentation.
|
||||
|
||||
## User-confirmed design direction
|
||||
|
||||
- ESP32 purchase: https://www.amazon.de/dp/B0F3XMYYQY
|
||||
- OLED purchase: https://www.amazon.de/dp/B0F13SSSFB — I²C, not SPI.
|
||||
- Both Amazon URLs returned an access-check page on inspection; the ASINs alone
|
||||
do not establish a verified manufacturer drawing or footprint.
|
||||
- Use the male Adafruit breakout. User reports male/female board dimensions match;
|
||||
verify the selected male board against its manufacturer CAD.
|
||||
- Carrier for complete modules: soldered module headers for final assembly,
|
||||
sockets for beta testing without a case. Header pitch, row spacing, drill size
|
||||
and socket clearance must be validated against selected hardware.
|
||||
- Power only through the ESP32 development board's USB connectors, typically a
|
||||
powerbank or the serial peer's USB port, with common ground. Do not introduce
|
||||
another carrier power input or assume simultaneous USB supplies are isolated.
|
||||
- Select readily available THT buttons; the existing random switches need not be reused.
|
||||
- Case will be 3D-printed after PCB finalization. Include mounting provisions in
|
||||
the layout, but screw size, hole diameter and insert bosses are not yet selected.
|
||||
|
||||
## User-supplied mechanical evidence
|
||||
|
||||
- ESP32 listing pictures show HW-678 V0.0.0; user confirms matching markings and
|
||||
actual shield S3-N16R8. Disregard the listing picture's N8R2 marking for memory
|
||||
identification and its erroneous USB labels: native USB is GPIO19 D− / GPIO20 D+.
|
||||
- ESP32 raw measurements: header first-to-last centres initially 52.6 mm, revised
|
||||
to 52.93 mm; row spacing measured 25.00 mm. User subsequently confirmed both
|
||||
rows fit a standard breadboard simultaneously without forcing. Use nominal
|
||||
2.54 mm pitch (53.34 mm over 21 intervals) and 25.40 mm row spacing based on
|
||||
that fit check, not the raw caliper measurements. Verify with a 1:1 print before fabrication.
|
||||
- ESP32 seller drawing states 28 mm width and annotates 63 mm length; its 20 mm
|
||||
arrow is not header row spacing. Antenna extent and header-to-outline offsets
|
||||
are not fully dimensioned.
|
||||
- OLED actual PCB outline measured by user: 26 × 26 mm, superseding the seller's
|
||||
28.7 × 27.8 mm dimensions. Seller's 4.1 mm height remains unverified.
|
||||
- OLED front view, connector at top: GND, VCC, SCL, SDA from left to right,
|
||||
confirmed on actual module. Raw first-to-last span revised from 7 mm to 7.49 mm;
|
||||
user confirms standard breadboard fit and agrees nominal pitch is 2.54 mm
|
||||
(7.62 mm span).
|
||||
- OLED mounting-hole diameter measured as 2 mm. User approved the latest edge
|
||||
measurements as superseding the earlier 22.16 mm hole-spacing measurement:
|
||||
centres are 1.75 mm from adjacent edges, giving 22.50 mm spacing in both axes.
|
||||
- User verified the template's PCB outline and mounting-hole placement against
|
||||
the actual OLED module: both fit perfectly. Header-pin centres were subsequently
|
||||
corrected to 1.50 mm from the top PCB edge, superseding 0.75 mm. The header row
|
||||
is therefore 0.25 mm nearer the top edge than the upper mounting-hole centres.
|
||||
- User measured the display outline margins: top 4.5 mm, bottom 5 mm, left 1 mm,
|
||||
right 0.5 mm. On the 26 mm PCB this defines a 24.5 × 16.5 mm rectangle from
|
||||
(1, 4.5) to (25.5, 21), shown dashed in the template. This records the supplied
|
||||
display boundary, not an independently verified active-pixel area or case cutout.
|
||||
- User subsequently confirmed the revised template is "about a perfect fit",
|
||||
validating the PCB outline, mounting-hole placement, corrected 1.50 mm header
|
||||
position and display outline against the actual module. This is a user-reported
|
||||
mechanical fit check, not electrical or fabrication validation.
|
||||
- User explicitly approved a horizontally centred header, superseding the earlier
|
||||
approximate 7.33 mm hole-to-pin measurements.
|
||||
- Approved coordinates in mm, viewed from the display side with the header at
|
||||
the top, origin at the top-left PCB corner, X right and Y down:
|
||||
GND (9.19, 1.50), VCC (11.73, 1.50), SCL (14.27, 1.50), SDA (16.81, 1.50).
|
||||
Mounting-hole centres: (1.75, 1.75), (24.25, 1.75), (1.75, 24.25),
|
||||
(24.25, 24.25), each diameter 2 mm. Board outline: (0, 0) to (26, 26).
|
||||
- `oled-fit-check.svg` is a 1:1 mechanical template of that geometry. Print at
|
||||
100%, without fit-to-page, and verify its 20 mm scale before checking the module.
|
||||
Crosshairs denote header centres, not specified pad or drill diameters. This
|
||||
template is not a KiCad footprint or a fabrication file.
|
||||
|
||||
## Remaining mechanical/electrical checks
|
||||
|
||||
1. OLED outline, mounting-hole placement, revised header position and display
|
||||
outline are user fit-verified. Select carrier header/socket pads and drills
|
||||
from the actual connector drawings.
|
||||
2. Before final module outlines/courtyards, establish their registration to header
|
||||
centres, connector overhang and display position from drawings or measurements.
|
||||
3. Verify power-path schematic/regulator capacity and OLED pull-up implementation.
|
||||
Photos alone do not establish those electrical properties.
|
||||
|
||||
The native schematic, local libraries and routed two-layer PCB draft are present.
|
||||
Before fabrication, finish the above checks, qualify the male RS-232 module map
|
||||
and footprint, and confirm procurement. The carrier now proposes an 82 × 80 mm
|
||||
stepped outline and four Ø3.2 mm M3 mounting holes; case hardware is not yet fixed.
|
||||
The PCB's RF reserve is a draft design allocation, not a verified module antenna
|
||||
keepout. Module/USB/DE-9 clearances must still be established from actual hardware.
|
||||
@@ -0,0 +1,142 @@
|
||||
# Adafruit MAX3243 breakout: authoritative CAD header research
|
||||
|
||||
Research date: 2026-09-20. Scope: source retrieval and pad/geometry extraction only; no project schematic or layout modified.
|
||||
|
||||
## Critical product distinction
|
||||
|
||||
**Product 5988 is the DE-9 female version. The male version is product 6253.** The product-5988 page's male variant link goes to `/product/6253`; each product description explicitly identifies its connector gender. Both link to the same Learn guide. Do not describe the downloaded CAD as verified male-specific CAD: its connector is `X2`, value `DE9-F`, package `F09HP`, and the repository README identifies product 5988.
|
||||
|
||||
The exact header map below is proven for the published 5988 board. It is useful reference for the family, but identical male-board geometry/pad numbering has **not** been established by a male-specific CAD file or physical measurement. Confirm the actual 6253 module before releasing a male carrier footprint. No inference from connector gender or a generic DE-9 footprint is substituted for that check.
|
||||
|
||||
## Provenance and downloaded originals
|
||||
|
||||
Authoritative chain:
|
||||
|
||||
1. [Adafruit Learn downloads](https://learn.adafruit.com/adafruit-rs-232-full-pinout-level-shifter-breakout/downloads) links to the Eagle repository below, a TI datasheet, fab print and 3D models.
|
||||
2. [Adafruit PCB repository](https://github.com/adafruit/Adafruit-RS-232-Level-Shifter-Breakout-PCB).
|
||||
3. Retrieved `main` commit **`63c6200bb3ef17d491089cfa6441b1810d79bb20`**, committed 2024-08-12T22:42:27Z. All four local originals were downloaded from this immutable revision, not a moving branch.
|
||||
4. [Product 5988, female](https://www.adafruit.com/product/5988), [product 6253, male](https://www.adafruit.com/product/6253), and [Learn pinouts](https://learn.adafruit.com/adafruit-rs-232-full-pinout-level-shifter-breakout/pinouts) corroborate identity and signal names.
|
||||
|
||||
Local files in `reference/`, unchanged upstream contents:
|
||||
|
||||
| File | SHA-256 |
|
||||
|---|---|
|
||||
| `Adafruit RS-232 Full Pinout Level-Shifter Breakout.brd` | `6a7b35ef909f5a2d243c326c34db3957aaa01275bf2aac8e0c749ff18162e870` |
|
||||
| `Adafruit RS-232 Full Pinout Level-Shifter Breakout.sch` | `4efbbe0ad14194e4e4563dd5f7dac007dfd52eba636f7390ffb822f3e81cd896` |
|
||||
| `README.md` | `2825ba741a8ce83abdaf2c7cb9170641473357630ccbee40a0016dcdb5cd80bb` |
|
||||
| `license.txt` | `075dad5e5fc96c27014fabc269f4f5732909cffd178a486f546d982b6cf86b74` |
|
||||
|
||||
Raw URL prefix for each filename (URL-encode spaces):
|
||||
|
||||
`https://raw.githubusercontent.com/adafruit/Adafruit-RS-232-Level-Shifter-Breakout-PCB/63c6200bb3ef17d491089cfa6441b1810d79bb20/`
|
||||
|
||||
**License:** upstream `license.txt` is **Creative Commons Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)**. README attribution: “Designed by Limor Fried/Ladyada for Adafruit Industries.” README explicitly requires all its preceding text in redistribution; the full original README and license are therefore retained. Preserve these notices when redistributing CAD and comply with applicable attribution/share-alike obligations for adaptations. The CAD-derived content of this report is attributed to that design and provided under CC BY-SA 3.0.
|
||||
|
||||
The guide also links [5988 3D CAD](https://github.com/adafruit/Adafruit_CAD_Parts/tree/main/5988%20RS-232%20Level%20Shifter). Its directory listing contains `.f3d`, `.step`, `.stl`, and `.jpg`; these were **not downloaded or used for measurements**, and their licensing was not independently assessed. No male-specific 3D model was verified in this research.
|
||||
|
||||
## Exact header numbering and net map
|
||||
|
||||
Source: Eagle XML `drawing/board/elements/element[@name='JP2']`, embedded `microbuilder` package `1X12_ROUND_76MIL`, and `drawing/board/signals/signal/contactref`. Cross-checked against schematic `JP2`, deviceset `HEADER-1X12`, selected device `76MIL`: symbol pin N maps directly to physical pad N.
|
||||
|
||||
**Viewing convention:** component/top view, DE-9 pointing toward the top of the page (+Y), logic header along the bottom. X increases rightward, Y upward. The origin is the lower-left bounding-box corner of the bare PCB (the corner itself is rounded). These are Eagle board coordinates in **millimetres**, not a mating-face view and not a bottom view.
|
||||
|
||||
```text
|
||||
DE-9 / +Y
|
||||
+-----------------------+
|
||||
| |
|
||||
| o o | module mounting holes
|
||||
| |
|
||||
| 1 2 3 ... 12 | JP2, y = 2.667 mm
|
||||
+-----------------------+
|
||||
Vin OFF
|
||||
+X ->
|
||||
```
|
||||
|
||||
| JP2 physical pad | Label / function | CAD net | X (mm) | Y (mm) | MAX3243 IC1 pad | DE-9 X2 pad via level shifter |
|
||||
|---:|---|---|---:|---:|---:|---:|
|
||||
| 1 | Vin | VCC | 1.905 | 2.667 | 26 | — |
|
||||
| 2 | GND | GND | 4.445 | 2.667 | 25 | 5 (direct ground) |
|
||||
| 3 | DCD | DCD | 6.985 | 2.667 | 15 | 1 |
|
||||
| 4 | RX | RXD | 9.525 | 2.667 | 16 | 2 |
|
||||
| 5 | TX | TXD | 12.065 | 2.667 | 13 | 3 |
|
||||
| 6 | DTR | DTR | 14.605 | 2.667 | 14 | 4 |
|
||||
| 7 | DSR | DSR | 17.145 | 2.667 | 17 | 6 |
|
||||
| 8 | RTS | RTS | 19.685 | 2.667 | 12 | 7 |
|
||||
| 9 | CTS | CTS | 22.225 | 2.667 | 19 | 8 |
|
||||
| 10 | RI | RI | 24.765 | 2.667 | 18 | 9 |
|
||||
| 11 | VLD | !INVALID | 27.305 | 2.667 | 21 | — |
|
||||
| 12 | OFF | !FORCEOFF | 29.845 | 2.667 | 22 | — |
|
||||
|
||||
**Left-to-right order: `Vin GND DCD RX TX DTR DSR RTS CTS RI VLD OFF`.** This is physical pad order, not the order of rows in the project's wiring table.
|
||||
|
||||
At the logic header, TX/DTR/RTS are inputs to the MAX3243; RX/DCD/DSR/CTS/RI are outputs from it. Schematic gates explicitly map TX to T2IN, DTR to T1IN, RTS to T3IN, RX to R4OUT, DCD to R5OUT, DSR to R3OUT, CTS to R1OUT and RI to R2OUT. RS-232 and logic signal nets are separated by the chip; matching signal names do not mean direct continuity.
|
||||
|
||||
OFF is **active low** (`!FORCEOFF`, IC1 pad 22) with 10 kΩ pull-up R1 to VCC. The Learn pinout page agrees. The upstream README and shop prose incorrectly say high shuts it down; do not propagate that prose error. VLD is `!INVALID`, high on valid input detection. Use 3.3 V Vin for this project's ESP32 interface, as already required by `docs/wiring.md`.
|
||||
|
||||
## Exact published PCB geometry
|
||||
|
||||
All figures below are nominal CAD dimensions, not measured tolerances.
|
||||
|
||||
### Logic header JP2
|
||||
|
||||
- Single row, **12 plated through-hole pads at 2.54 mm (0.100 inch) pitch**.
|
||||
- Element origin `(15.875, 2.667)`; no element rotation or mirroring (R0).
|
||||
- Package local pad centres: `x = -13.970 + 2.540 × (N - 1)`, `y = 0`, N = 1…12.
|
||||
- Board coordinates: `x = 1.905 + 2.540 × (N - 1)`, `y = 2.667`.
|
||||
- First-to-last centre span **27.940 mm (1.100 inch)**.
|
||||
- All holes: **1.000 mm drill**, explicit **1.9304 mm (76 mil) round copper diameter**. Each pad has `rot="R90"`; that does not rotate the header row or change a round pad's outline.
|
||||
- Both end pad centres are **1.905 mm** from the corresponding vertical board edge.
|
||||
- Row is **2.667 mm (0.105 inch)** above the lower bounding-box edge.
|
||||
- No square pad-1 marker is defined by this package: identify pad 1 using Vin and the stated view, not assumed square copper.
|
||||
|
||||
### Bare-board outline and mounting holes
|
||||
|
||||
Source: `drawing/board/plain/wire[@layer='20']` and mounting-hole element/package definitions.
|
||||
|
||||
- Bare PCB bounding box: **31.750 mm X × 29.337 mm Y** (1.250 × 1.155 inches), X = 0…31.750, Y = 0…29.337.
|
||||
- Straight top edge `(0,29.337)` to `(31.750,29.337)`; square top corners.
|
||||
- Bottom corners: **2.540 mm radius**, defined by 90° arcs `(0,2.540)` → `(2.540,0)` and `(29.210,0)` → `(31.750,2.540)`.
|
||||
- Bottom straight edge: X = 2.540…29.210 at Y = 0.
|
||||
- Module mounting holes: `U$2` at **(3.810, 11.430)** and `U$3` at **(27.940, 11.430)**.
|
||||
- Both use `MOUNTINGHOLE_3.0_PLATEDTHIN`, pad `P$1`, **3.000 mm drill / 3.600 mm copper diameter**. They are plated pads, not anonymous NPTH holes, and neither appears in the board's signal contactrefs.
|
||||
- Hole centre spacing **24.130 mm (0.950 inch)**; hole row is **8.763 mm** above the header row.
|
||||
|
||||
For a carrier footprint using header pad 1 as `(0,0)` with Y still upward:
|
||||
|
||||
- Header pads: `(2.540 × (N - 1), 0)`.
|
||||
- Mounting hole centres: **(1.905, 8.763)** and **(26.035, 8.763)**.
|
||||
- Bare-board bounding box: X = **−1.905…29.845**, Y = **−2.667…26.670**; retain corner arcs described above.
|
||||
|
||||
If the destination CAD uses screen coordinates with Y downward, transform Y deliberately; do not silently mirror physical pad numbering. The placement side and whether the module faces up or down must be fixed before generating a carrier footprint.
|
||||
|
||||
### DE-9 on the downloaded female CAD (not male qualification)
|
||||
|
||||
`X2` origin `(15.875,17.907)`, R0, package `F09HP`, value `DE9-F`.
|
||||
|
||||
| X2 pad | Board X (mm) | Board Y (mm) | CAD net |
|
||||
|---|---:|---:|---|
|
||||
| 1 | 10.3886 | 16.637 | RS_DCD |
|
||||
| 2 | 13.1318 | 16.637 | RS_RXD |
|
||||
| 3 | 15.8750 | 16.637 | RS_TXD |
|
||||
| 4 | 18.6182 | 16.637 | RS_DTR |
|
||||
| 5 | 21.3614 | 16.637 | GND |
|
||||
| 6 | 11.7602 | 19.177 | RS_DSR |
|
||||
| 7 | 14.5034 | 19.177 | RS_RTS |
|
||||
| 8 | 17.2466 | 19.177 | RS_CTS |
|
||||
| 9 | 19.9898 | 19.177 | RS_RI |
|
||||
| G1 | 3.3528 | 17.907 | no signal contactref |
|
||||
| G2 | 28.3972 | 17.907 | no signal contactref |
|
||||
|
||||
Signal holes have 1.016 mm drill and 1.778 mm octagonal copper; shell/mechanical pads G1/G2 have 3.302 mm drill and 5.080 mm copper, spaced 25.0444 mm. Shell pads are not shown tied to GND by signal contactrefs; DE-9 pin 5 is grounded. These are **PCB solder-tail coordinates**, not front-face pin coordinates.
|
||||
|
||||
The connector package's layer-21 drawing reaches local Y = 17.907, hence board Y = 35.814, beyond the board's Y = 29.337. This is library drawing evidence, **not a validated mechanical envelope**. Do not size an enclosure from silkscreen. Product pages list assembled dimensions of 33.6 × 32.0 × 14.1 mm (5988) and 33.7 × 31.8 × 14.2 mm (6253), with no coordinate/tolerance definition; these do not replace a male-specific mechanical drawing, and differ from the female library drawing extent.
|
||||
|
||||
## Validation and next-use boundary
|
||||
|
||||
- Parsed the actual Eagle board and schematic XML successfully using Python's standard-library XML parser.
|
||||
- Joined package pad names to board signal contactrefs, applied JP2's unrotated element translation, and cross-checked schematic header numbering/net assignments.
|
||||
- Extracted outline arcs, mounting-hole package dimensions, and connector pad data directly; no dimensions inferred from product photos.
|
||||
- Recorded SHA-256 hashes of the downloaded originals.
|
||||
- No firmware build or hardware tests performed: this task changed research/reference files only.
|
||||
- **Ready:** exact published 5988 header map, pad centres, board outline and hole coordinates for CAD reference.
|
||||
- **Still required for a male-module carrier release:** establish that the purchased module is 6253, verify its header order and mounting geometry against male-specific CAD or the physical module, and verify connector overhang, mating clearance, module height and mounting orientation. The shared guide alone is insufficient proof of identical male geometry.
|
||||
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Add nominal STEP-derived metal bounding projections on Dwgs.User only.
|
||||
|
||||
Not silkscreen, a courtyard, or a case cutout. See db9-mechanical-notes.md for
|
||||
source attribution, selected face subsets and the user-confirmed male/female
|
||||
external-geometry equivalence. No module height or case-wall thickness assumed.
|
||||
"""
|
||||
import json
|
||||
from pathlib import Path
|
||||
import uuid
|
||||
import pcbnew as p
|
||||
|
||||
ROOT=Path(__file__).resolve().parent
|
||||
PCB=ROOT/"serial-carrier.kicad_pcb"
|
||||
NS=uuid.UUID("ba7274a8-c35d-46bc-908d-cd86708fc6c4")
|
||||
|
||||
|
||||
def main():
|
||||
lock=ROOT/"~serial-carrier.kicad_pcb.lck"
|
||||
if lock.exists():
|
||||
raise SystemExit("Close PCB Editor before refreshing the CAD envelopes")
|
||||
before=PCB.read_bytes()
|
||||
project=ROOT/"serial-carrier.kicad_pro"
|
||||
project_before=project.read_bytes()
|
||||
board=p.LoadBoard(str(PCB))
|
||||
u2=next(f for f in board.GetFootprints() if f.GetReference()=="U2")
|
||||
assert u2.GetOrientationDegrees()==0, "Review transform for rotated module"
|
||||
x0,y0=p.ToMM(u2.GetPosition().x),p.ToMM(u2.GetPosition().y)
|
||||
data=json.loads((ROOT/"reference/db9-cad/measurements.json").read_text())
|
||||
owned={str(uuid.uuid5(NS,str(i))) for i in range(64)}
|
||||
for drawing in list(board.GetDrawings()):
|
||||
if drawing.m_Uuid.AsString() in owned:
|
||||
board.Delete(drawing)
|
||||
count=0
|
||||
|
||||
def point(x,y):
|
||||
# STEP/Eagle axes +Y toward connector; KiCad local Y is downward.
|
||||
return p.VECTOR2I(p.FromMM(x0+x-1.905),p.FromMM(y0+2.667-y))
|
||||
|
||||
def add(item):
|
||||
nonlocal count
|
||||
assert count<64
|
||||
item.SetLayer(p.Dwgs_User)
|
||||
item.SetUuid(p.KIID(str(uuid.uuid5(NS,str(count)))))
|
||||
board.Add(item)
|
||||
count+=1
|
||||
|
||||
for feature in ("front_flange_exposed_faces", "shell_outer_including_root_and_lip",
|
||||
"left_front_hex_post", "right_front_hex_post"):
|
||||
xmin,ymin,_,xmax,ymax,_=data[feature]["bounds"]
|
||||
rect=p.PCB_SHAPE(board)
|
||||
rect.SetShape(p.SHAPE_T_RECT)
|
||||
rect.SetStart(point(xmin,ymax)); rect.SetEnd(point(xmax,ymin))
|
||||
rect.SetWidth(p.FromMM(0.1))
|
||||
add(rect)
|
||||
for axis in data["front_lock_axes"]:
|
||||
x=axis["location"][0]
|
||||
# These mark front-facing Y axes, not holes through the carrier PCB.
|
||||
line=p.PCB_SHAPE(board)
|
||||
line.SetShape(p.SHAPE_T_SEGMENT)
|
||||
line.SetStart(point(x,26.907)); line.SetEnd(point(x,32.707))
|
||||
line.SetWidth(p.FromMM(0.08))
|
||||
add(line)
|
||||
label=p.PCB_TEXT(board)
|
||||
label.SetText("CAD METAL ENVELOPES - NOT CASE CUTOUT")
|
||||
label.SetPosition(p.VECTOR2I(p.FromMM(x0+13.97),p.FromMM(y0-17.67)))
|
||||
label.SetTextSize(p.VECTOR2I(p.FromMM(0.65),p.FromMM(0.65)))
|
||||
label.SetTextThickness(p.FromMM(0.1))
|
||||
add(label)
|
||||
for drawing in board.GetDrawings():
|
||||
if not isinstance(drawing,p.PCB_TEXT):
|
||||
continue
|
||||
value=drawing.GetText()
|
||||
if value.startswith("DE-9 OUT /"):
|
||||
drawing.SetPosition(p.VECTOR2I(p.FromMM(x0+13.97),p.FromMM(y0-20.67)))
|
||||
elif value=="82 x 80 mm / 2 copper layers":
|
||||
drawing.SetPosition(p.VECTOR2I(p.FromMM(141),p.FromMM(91)))
|
||||
elif value.startswith("Nominal RS-232 metal verified;"):
|
||||
drawing.SetPosition(p.VECTOR2I(p.FromMM(141),p.FromMM(93.5)))
|
||||
temp=ROOT/"serial-carrier-envelope-tmp.kicad_pcb"
|
||||
try:
|
||||
assert p.SaveBoard(str(temp),board,True)
|
||||
if lock.exists() or PCB.read_bytes()!=before:
|
||||
raise RuntimeError("PCB changed or reopened; refusing overwrite")
|
||||
assert project.read_bytes()==project_before
|
||||
temp.replace(PCB)
|
||||
finally:
|
||||
temp.unlink(missing_ok=True)
|
||||
print(f"Added {count} nominal CAD projection graphics on Dwgs.User; no copper/project changes")
|
||||
|
||||
|
||||
if __name__=="__main__":
|
||||
main()
|
||||
@@ -0,0 +1,181 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Refresh board-local device outlines without modifying libraries or project settings.
|
||||
|
||||
Uses pcbnew and Shapely from system Python. Only owned F.SilkS graphics are
|
||||
replaced; source F.Fab outlines, copper, footprints and board edges are retained.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import math
|
||||
import uuid
|
||||
import pcbnew as p
|
||||
from shapely.geometry import LineString, Point, box
|
||||
from shapely.ops import unary_union, polygonize
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
PCB = ROOT / "serial-carrier.kicad_pcb"
|
||||
NS = uuid.UUID("67964aa4-c3a5-45d8-9b54-250fb68e7c41")
|
||||
WIDTH = 0.12
|
||||
GAP = 0.22 # Additional clearance from mask openings and existing silk strokes.
|
||||
|
||||
|
||||
def xy(point):
|
||||
return p.ToMM(point.x), p.ToMM(point.y)
|
||||
|
||||
|
||||
def vec(point):
|
||||
return p.VECTOR2I(p.FromMM(point[0]), p.FromMM(point[1]))
|
||||
|
||||
|
||||
def outline_lines(shape):
|
||||
if shape.GetShape() == p.SHAPE_T_RECT:
|
||||
x1, y1 = xy(shape.GetStart())
|
||||
x2, y2 = xy(shape.GetEnd())
|
||||
return [LineString([(x1,y1),(x2,y1)]), LineString([(x2,y1),(x2,y2)]),
|
||||
LineString([(x2,y2),(x1,y2)]), LineString([(x1,y2),(x1,y1)])]
|
||||
if shape.GetShape() == p.SHAPE_T_SEGMENT:
|
||||
return [LineString([xy(shape.GetStart()), xy(shape.GetEnd())])]
|
||||
return []
|
||||
|
||||
|
||||
def add_outlines(board):
|
||||
owned = {str(uuid.uuid5(NS, str(i))) for i in range(4096)}
|
||||
for drawing in list(board.GetDrawings()):
|
||||
if drawing.m_Uuid.AsString() in owned:
|
||||
board.Delete(drawing)
|
||||
footprints = {f.GetReference(): f for f in board.GetFootprints()}
|
||||
obstacles = []
|
||||
for fp in board.GetFootprints():
|
||||
for pad in fp.Pads():
|
||||
if not pad.IsOnLayer(p.F_Mask):
|
||||
continue
|
||||
assert pad.GetOrientationDegrees() % 90 == 0
|
||||
x,y = xy(pad.GetPosition())
|
||||
expansion = max(0, p.ToMM(pad.GetSolderMaskExpansion(p.F_Mask)))
|
||||
if pad.GetShape() == p.PAD_SHAPE_CIRCLE:
|
||||
geom = Point(x,y).buffer(p.ToMM(pad.GetSize().x)/2, quad_segs=32)
|
||||
else:
|
||||
bb=pad.GetBoundingBox()
|
||||
geom=box(p.ToMM(bb.GetX()),p.ToMM(bb.GetY()),p.ToMM(bb.GetRight()),p.ToMM(bb.GetBottom()))
|
||||
obstacles.append(geom.buffer(expansion+GAP+WIDTH/2))
|
||||
for graphic in fp.GraphicalItems():
|
||||
if isinstance(graphic,p.PCB_SHAPE) and graphic.GetLayer()==p.F_SilkS:
|
||||
for line in outline_lines(graphic):
|
||||
obstacles.append(line.buffer(p.ToMM(graphic.GetWidth())/2+GAP+WIDTH/2))
|
||||
# Clear even tented vias for a robust visible guide, independent of mask settings.
|
||||
for track in board.GetTracks():
|
||||
if isinstance(track,p.PCB_VIA):
|
||||
obstacles.append(Point(xy(track.GetPosition())).buffer(p.ToMM(track.GetWidth(p.F_Cu))/2+GAP+WIDTH/2,quad_segs=32))
|
||||
blocked=unary_union(obstacles)
|
||||
edges = [LineString([xy(d.GetStart()), xy(d.GetEnd())])
|
||||
for d in board.GetDrawings() if d.GetLayer() == p.Edge_Cuts]
|
||||
polygons = list(polygonize(edges))
|
||||
assert len(polygons) == 1, 'Expected a single closed carrier outline'
|
||||
# U2's bare PCB front is flush with the carrier edge; do not print across it.
|
||||
silk_area = polygons[0].buffer(-(GAP + WIDTH / 2))
|
||||
count=0
|
||||
|
||||
def add(shape):
|
||||
nonlocal count
|
||||
assert count < 4096
|
||||
shape.SetLayer(p.F_SilkS)
|
||||
shape.SetUuid(p.KIID(str(uuid.uuid5(NS,str(count)))))
|
||||
board.Add(shape)
|
||||
count+=1
|
||||
|
||||
def line(a,b):
|
||||
clipped=LineString([a,b]).intersection(silk_area).difference(blocked)
|
||||
pieces=list(clipped.geoms) if hasattr(clipped,"geoms") else [clipped]
|
||||
for piece in pieces:
|
||||
if piece.geom_type!="LineString" or piece.length < 0.25:
|
||||
continue
|
||||
shape=p.PCB_SHAPE(board)
|
||||
shape.SetShape(p.SHAPE_T_SEGMENT)
|
||||
shape.SetStart(vec(piece.coords[0])); shape.SetEnd(vec(piece.coords[-1]))
|
||||
shape.SetWidth(p.FromMM(WIDTH))
|
||||
add(shape)
|
||||
|
||||
for ref in ("DS1","U2"):
|
||||
assert footprints[ref].GetOrientationDegrees()==0, "Review outlines for rotated modules"
|
||||
for source in footprints[ref].GraphicalItems():
|
||||
if not isinstance(source,p.PCB_SHAPE) or source.GetLayer()!=p.F_Fab:
|
||||
continue
|
||||
if source.GetShape()==p.SHAPE_T_ARC:
|
||||
# Test small spans for clearance, but retain true circular arcs
|
||||
# for each visible run rather than replacing corners with chords.
|
||||
cx,cy=xy(source.GetCenter())
|
||||
sx,sy=xy(source.GetStart()); mx,my=xy(source.GetArcMid())
|
||||
start=math.atan2(sy-cy,sx-cx)
|
||||
sweep=2*math.atan2((sx-cx)*(my-cy)-(sy-cy)*(mx-cx),
|
||||
(sx-cx)*(mx-cx)+(sy-cy)*(my-cy))
|
||||
radius=math.hypot(sx-cx,sy-cy)
|
||||
steps=max(1,math.ceil(abs(math.degrees(sweep))))
|
||||
def at(t):
|
||||
angle=start+sweep*t/steps
|
||||
return cx+radius*math.cos(angle),cy+radius*math.sin(angle)
|
||||
run=None
|
||||
for i in range(steps+1):
|
||||
span=LineString([at(i),at(i+0.5),at(i+1)])
|
||||
clear=i<steps and silk_area.covers(span) and not span.intersects(blocked)
|
||||
if clear and run is None:
|
||||
run=i
|
||||
if not clear and run is not None:
|
||||
if radius*abs(sweep)*(i-run)/steps>=0.25:
|
||||
arc=p.PCB_SHAPE(board)
|
||||
arc.SetShape(p.SHAPE_T_ARC)
|
||||
arc.SetArcGeometry(vec(at(run)),vec(at((run+i)/2)),vec(at(i)))
|
||||
arc.SetWidth(p.FromMM(WIDTH))
|
||||
add(arc)
|
||||
run=None
|
||||
continue
|
||||
for edge in outline_lines(source):
|
||||
# Display boundary is visually distinct from the PCB perimeter.
|
||||
dashed = ref=="DS1" and round(edge.bounds[0]-xy(footprints[ref].GetPosition())[0],2) in (1.0,25.5)
|
||||
if dashed:
|
||||
distance=0.0
|
||||
while distance < edge.length:
|
||||
line(edge.interpolate(distance).coords[0],edge.interpolate(min(distance+0.8,edge.length)).coords[0])
|
||||
distance+=1.3
|
||||
else:
|
||||
line(edge.coords[0],edge.coords[-1])
|
||||
# These are pad-row guides, not inferred socket/body or ESP32 PCB dimensions.
|
||||
fp=footprints["U1"]
|
||||
assert fp.GetOrientationDegrees()==0, "Review guide construction for rotated U1"
|
||||
pads={pad.GetNumber():xy(pad.GetPosition()) for pad in fp.Pads()}
|
||||
for first,last in (("1","22"),("23","44")):
|
||||
x,ytop=pads[first]; _,ybottom=pads[last]
|
||||
corners=[(x-1.2,ytop-1.2),(x+1.2,ytop-1.2),(x+1.2,ybottom+1.2),(x-1.2,ybottom+1.2)]
|
||||
for a,b in zip(corners,corners[1:]+corners[:1]):
|
||||
line(a,b)
|
||||
label=p.PCB_TEXT(board)
|
||||
label.SetText("HEADER GUIDES")
|
||||
a,b=pads["1"],pads["23"]
|
||||
label.SetPosition(vec(((a[0]+b[0])/2,a[1]+28)))
|
||||
label.SetTextSize(p.VECTOR2I(p.FromMM(0.85),p.FromMM(0.85)))
|
||||
label.SetTextThickness(p.FromMM(0.12))
|
||||
add(label)
|
||||
return count
|
||||
|
||||
|
||||
def main():
|
||||
lock=ROOT/"~serial-carrier.kicad_pcb.lck"
|
||||
if lock.exists():
|
||||
raise SystemExit("Close PCB Editor before updating silkscreen")
|
||||
before=PCB.read_bytes()
|
||||
project=ROOT/"serial-carrier.kicad_pro"
|
||||
project_before=project.read_bytes()
|
||||
board=p.LoadBoard(str(PCB))
|
||||
count=add_outlines(board)
|
||||
temp=ROOT/"validation/pcb-routing-silk-tmp.kicad_pcb"
|
||||
try:
|
||||
assert p.SaveBoard(str(temp),board,True)
|
||||
if lock.exists() or PCB.read_bytes()!=before:
|
||||
raise RuntimeError("PCB changed or reopened; refusing to overwrite")
|
||||
assert project.read_bytes()==project_before,"Project settings changed"
|
||||
temp.replace(PCB)
|
||||
finally:
|
||||
temp.unlink(missing_ok=True)
|
||||
print(f"Added {count} board-local silkscreen graphics; project settings preserved.")
|
||||
|
||||
|
||||
if __name__=="__main__":
|
||||
main()
|
||||
@@ -0,0 +1,8 @@
|
||||
References,Quantity,Description,Manufacturer,MPN or identification,Footprint,Status
|
||||
U1,1,ESP32-S3 N16R8 development board,User supplied,HW-678 V0.0.0 / ASIN B0F3XMYYQY,Carrier:HW678_2x22_Provisional,Header pitch/spacing confirmed; connector drill fit and module envelope pending
|
||||
U2,1,MAX3243 full-pinout RS-232 breakout male,Adafruit,6253,Carrier:MAX3243_Reference_Provisional,PCB-only reference footprint; schematic assignment blank; male header/geometry verification required
|
||||
DS1,1,128x64 yellow-blue I2C OLED 26mm PCB,User supplied,ASIN B0F13SSSFB,Carrier:OLED_26mm_I2C_Provisional,Module geometry user-fit-verified; carrier header drill fit pending
|
||||
SW1 SW2 SW3,3,Normally-open 6mm THT tactile switch,Omron,B3F-1000,Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x,Datasheet/footprint checked; stock unverified
|
||||
R1 R2 R3,3,2.2k ohm 1 percent axial resistor at least 0.125W,TBD,TBD,Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal,Design value selected; exact MPN and body/lead fit pending
|
||||
H1 H2 H3 H4,4,Proposed M3 case mounting locations with 3.2mm NPTH,TBD,TBD,MountingHole:MountingHole_3.2mm_M3,Board-only hole locations; screws and insert/standoff dimensions not selected
|
||||
Assembly hardware,TBD,Module headers and optional beta-test sockets plus mounting hardware,TBD,TBD,,Not yet selected; not included in electrical component count
|
||||
|
@@ -0,0 +1,107 @@
|
||||
# Button selection: Omron B3F-1000
|
||||
|
||||
Verified 2026-09-20 against the Omron datasheet and installed KiCad libraries. This research covers three active-low GPIO-to-GND buttons.
|
||||
|
||||
**Integration decision:** the A0 schematic now implements B3F-1000 switches with one **2.2 kΩ, 1%, at least 0.125 W external pull-up** per input. The lower-current/internal-pull-up-only discussion below records the selection rationale, not an unselected schematic option. Firmware is unchanged. Exact resistor MPN, supply tolerance/budget and stock remain release checks.
|
||||
|
||||
## Schematic-ready recommendation
|
||||
|
||||
**B3F-1000 fits the installed footprint mechanically and its normally-open contact topology matches the circuit. Low-current operation with internal pull-ups needs the qualification below.**
|
||||
|
||||
For each of the three buttons:
|
||||
|
||||
- Value / manufacturer part number: **B3F-1000**, Omron; standard silver-plated contacts, ivory flat plunger, no separate ground terminal.
|
||||
- Symbol: **`Switch:SW_Push`** (normally open, two passive pins).
|
||||
- Explicitly assign footprint: **`Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x`**. The installed symbol's default footprint field is empty.
|
||||
- Symbol pin **1 → GPIO**, symbol pin **2 → GND**. The switch is nonpolar; swapping these two nets is electrically equivalent.
|
||||
|
||||
| Function | Pin 1 net | Pin 2 net |
|
||||
|---|---|---|
|
||||
| Previous/back | GPIO10 | GND |
|
||||
| Select/confirm | GPIO13 | GND |
|
||||
| Next | GPIO14 | GND |
|
||||
|
||||
GPIO assignments verified in `src/board_pins.h`. The PCB README specifies normally-open buttons to GND with internal pull-ups. Released = high; pressed = low. No voltage source connects directly through the switch: any external pull-up goes through a resistor to 3.3 V.
|
||||
|
||||
## Exact terminal and pad mapping
|
||||
|
||||
The installed footprint has **four physical holes but only two distinct electrical pad numbers: `1,1,2,2`**, not four uniquely numbered pads. The installed `SW_Push` symbol has pin 1 at the left and pin 2 at the right in its default orientation; pin numbers are hidden by the symbol.
|
||||
|
||||
Unrotated footprint, front/top view; local coordinates in mm, positive Y downward:
|
||||
|
||||
```text
|
||||
6.5 mm
|
||||
KiCad pad 1 (0,0) ----------- KiCad pad 1 (6.5,0)
|
||||
Omron terminal 4 Omron terminal 3
|
||||
NO
|
||||
contact 4.5 mm
|
||||
Omron terminal 2 Omron terminal 1
|
||||
KiCad pad 2 (0,4.5) --------- KiCad pad 2 (6.5,4.5)
|
||||
```
|
||||
|
||||
The horizontal connections above are permanent internal connections; pressing joins the two rows. Omron datasheet **page 4**, upper-left **“Terminal Arrangement/Internal Connections (Top View)”**, explicitly shows terminals **4–3 common** and **2–1 common**. This maps to KiCad's upper pair numbered 1 and lower pair numbered 2 when aligned as shown. **Omron terminal 1 is not KiCad pad 1 in this orientation.** The manufacturer's separate bottom-view numbering sketch must not be mistaken for a top view. Omron states terminal numbers are not marked on the switches. A 180° insertion swaps the two electrical sides without affecting this nonpolar circuit.
|
||||
|
||||
Both pads numbered 1 receive the symbol pin-1 net; both pads numbered 2 receive pin-2's net. Do not attach a four-unique-pin symbol to this footprint without an explicit remapping. Connecting GPIO and GND to two terminals within the same common pair would permanently ground the GPIO.
|
||||
|
||||
The footprint sets `duplicate_pad_numbers_are_jumpers no`; do not assume PCB routing software will treat the internal metal as a routing jumper. Route/check the duplicate-pad nets normally.
|
||||
|
||||
## Manufacturer dimensions and ratings
|
||||
|
||||
Source: Omron B3F datasheet, ordering table p. 1, ratings/operating characteristics p. 3, B3F-1000 drawing p. 4. Dimensions below are mm.
|
||||
|
||||
| Item | Datasheet specification |
|
||||
|---|---|
|
||||
| Body | 6 ±0.2 × 6 ±0.2 |
|
||||
| Overall height above seating plane | 4.3 ±0.2 |
|
||||
| Plunger diameter | 3.5 |
|
||||
| Terminal row spacing | 4.5 ±0.2 |
|
||||
| Formed terminal span at hole-entry region | 6.5 ±0.5 |
|
||||
| Maximum-width dimension shown across formed leads | 7.7 ±0.5 |
|
||||
| Lead projection below seating plane | 3.5 |
|
||||
| Lead thickness / width callouts | 0.3 / 0.7 |
|
||||
| Recommended PCB hole-centre grid | 6.5 ±0.1 × 4.5 ±0.1 |
|
||||
| Recommended PCB holes | Four, diameter 1 ±0.1; reference drawing for PCB thickness 1.6 |
|
||||
| General dimensional tolerance | ±0.4 unless otherwise specified |
|
||||
| Contact form | SPST-NO |
|
||||
| Rated resistive load, standard silver version | **1–50 mA at 3–24 VDC** |
|
||||
| Minimum applicable load | **10 µA at 1 VDC, reference value** |
|
||||
| Initial contact resistance | 100 mΩ maximum |
|
||||
| Bounce | 5 ms maximum |
|
||||
| Operating force | 0.98 ±0.29 N (100 ±30 gf) |
|
||||
| Release force | 0.2 N minimum |
|
||||
| Pretravel | 0.25 +0.2/−0.1 |
|
||||
| Durability for B3F-1000, 0.98 N | 1,000,000 operations minimum |
|
||||
| Protection / washing | IP00 / washing not possible |
|
||||
|
||||
### Installed footprint comparison
|
||||
|
||||
Inspected `/usr/share/kicad/footprints/Button_Switch_THT.pretty/SW_TH_Tactile_Omron_B3F-100x.kicad_mod` (file format version `20260206`, generator version `10.0`) and `/usr/share/kicad/symbols/Switch.kicad_sym`.
|
||||
|
||||
- Hole centres: `(0,0)`, `(6.5,0)`, `(0,4.5)`, `(6.5,4.5)` — match Omron's nominal PCB grid.
|
||||
- All four plated through-hole pads: circular copper diameter **1.7**, drill **1.0**, front/back copper and mask — nominal drill matches Omron's reference hole diameter. Confirm the fabricator's finished-hole tolerance when ordering.
|
||||
- Fab body: `(0.25,-0.75)` to `(6.25,5.25)`, **6 × 6**, matching nominal body dimensions.
|
||||
- Courtyard: `(-1.1,-1.1)` to `(7.6,5.6)`, **8.7 × 6.7**. This is a library placement boundary, not an enclosure or finger-access clearance specification.
|
||||
- Footprint description specifies H4.3 mm. No physical sample fit, enclosure-height check, or independent 3D-model measurement was performed.
|
||||
|
||||
## Low-current qualification / selection decision
|
||||
|
||||
**Do not claim internal-pull-up operation meets the standard part's full rated-load range.** At 3.3 V, pull-up resistance greater than 3.3 kΩ gives less than 1 mA contact current. For illustration only, 45 kΩ would give about 73 µA; that is not a verified resistance specification for this board's GPIOs.
|
||||
|
||||
Omron's 10 µA / 1 V minimum is explicitly a **reference value**, not an unconditional low-current reliability guarantee. B3F-1000 is a reasonable prototype choice, but internal-pull-up-only contact reliability should be qualified in the intended environment and over life.
|
||||
|
||||
For a conservative silver-contact design within the stated rated load, consider **one external 2.2 kΩ pull-up to 3.3 V per button** (about 1.5 mA while pressed); verify supply/resistor tolerances and GPIO limits before adopting. This recommendation was subsequently adopted in the A0 carrier schematic; see the integration decision above. If low current is important, the datasheet also lists **B3F-1002-G** with gold contacts in the same p. 4 dimensional group, rated 100 µA–50 mA at 3–24 VDC, but with a different operating force (1.76 ±0.49 N); weak internal pull-ups are not automatically within that rated range either. It has not been selected or stock-checked here.
|
||||
|
||||
Retain firmware debouncing; a mechanical switch is not bounce-free. The device is unsealed (IP00), and the datasheet prohibits washing.
|
||||
|
||||
## Supply evidence
|
||||
|
||||
**Live stock not verified; no supply assurance.** On 2026-09-20 a request to the [DigiKey B3F-1000 product URL](https://www.digikey.com/en/products/detail/omron-electronics-inc-emc-div/B3F-1000/33150) returned HTTP 403 / an anti-bot challenge. No quantity, price, lead time, or lifecycle status could be established from that response. Confirm the exact MPN and available quantity with a distributor before BOM release; datasheet availability is not stock evidence.
|
||||
|
||||
## Evidence and remaining checks
|
||||
|
||||
- [Authoritative Omron datasheet](https://omronfs.omron.com/en_US/ecb/products/pdf/en-b3f.pdf), retrieved 2026-09-20.
|
||||
- [Local PDF](reference/button-omron-b3f.pdf).
|
||||
- [Extracted text](reference/button-omron-b3f.txt) for searching; use the PDF for drawing interpretation.
|
||||
- [Rendered page 4](reference/button-omron-b3f-page4.png), visually checked for the internal connections and dimensions.
|
||||
|
||||
Before manufacturing: validate the selected pull-up strategy against supply tolerances/budget; check ERC/net assignment and PCB DRC in the actual design; confirm finished holes, enclosure/actuator access and a sample's fit/continuity; confirm procurement. This review verifies datasheet-to-library compatibility, not a completed-board electrical or mechanical test.
|
||||
@@ -0,0 +1,133 @@
|
||||
# DE-9 mechanical reference — pinned Adafruit 5988 STEP
|
||||
|
||||
Research: 2026-09-21. **Nominal CAD geometry, not a toleranced connector drawing or physical fit qualification.** No board, schematic, footprint, application dependency or main README changes.
|
||||
|
||||
**User-confirmed assumption:** male and female modules have identical external dimensions; use the external geometry below for mechanical planning of the male module on that basis. This is not independent male-CAD verification and **does not verify the male pin map**. The source explicitly identifies `DE9-F` / `F09HP` (5988). Do not infer contact numbering from gender, the model, or the front view.
|
||||
|
||||
## Sources, attribution and licensing
|
||||
|
||||
- User's link: https://github.com/adafruit/Adafruit_CAD_Parts/tree/main/5988%20RS-232%20Level%20Shifter
|
||||
- Pinned repository revision: **`5b8a21fb5e2e48478faf8901077be812f5224066`**, 2024-09-05, Noe Ruiz, “Adding 5988 RS-232 Level Shifter”. GitHub path history returned this as the latest change to that directory at retrieval.
|
||||
- Immutable directory: https://github.com/adafruit/Adafruit_CAD_Parts/tree/5b8a21fb5e2e48478faf8901077be812f5224066/5988%20RS-232%20Level%20Shifter
|
||||
- Unmodified originals retained in `reference/db9-cad/`: STEP, repository-root `README.md`, repository-root `LICENSE`. `sources.json` records **full immutable raw URLs and SHA-256 hashes**.
|
||||
|
||||
| Original | SHA-256 |
|
||||
|---|---|
|
||||
| `5988 RS-232 Level Shifter.step` | `8eab9dc591a985fc987ceec2d7534e5e259263c73bebc64c0c3cd847e135906f` |
|
||||
| `LICENSE` | `f60436644e066301d4c2e8f1f0674a157b1f60e4fd5586717b99dde255cc18cd` |
|
||||
| `README.md` | `9ed9beac7600623b7b9bd0d02e1fdcd86610ce22966723179e6c6b618e399dce` |
|
||||
|
||||
The CAD repository root license is **MIT, Copyright (c) 2016 Adafruit Industries**; root README merely describes STEP/Fusion/STL parts. No part-specific license file is present in the four-file directory. Retain the original MIT notice and warranty disclaimer with redistribution. This assessment follows the repository's stated license, not an independent audit of any imported vendor geometry. F3D, STL and JPG were listed but not downloaded or used; no image-derived dimensions or mesh fallback were needed.
|
||||
|
||||
The separate Eagle board reference is from `Adafruit-RS-232-Level-Shifter-Breakout-PCB`, commit `63c6200bb3ef17d491089cfa6441b1810d79bb20`; its local `.brd` hash was rechecked (`6a7b35ef909f5a2d243c326c34db3957aaa01275bf2aac8e0c749ff18162e870`). Eagle-derived registration/hole information is attributed to **Limor Fried/Ladyada for Adafruit Industries**, under **CC BY-SA 3.0 Unported**; retain `reference/README.md` and `reference/license.txt`. The Eagle-derived portions of these notes use that license. Do not silently substitute the CAD-parts repository's MIT license for the Eagle design license.
|
||||
|
||||
## Extraction method and confidence
|
||||
|
||||
Used Python/OpenCascade (`cadquery-ocp==7.9.3.1.1`) in the isolated project `.cache/cad-tools/venv`; no application dependencies changed. The initially attempted older OCP wheel was unavailable for this Python; the pinned installed version succeeded.
|
||||
|
||||
- `reference/db9-cad/extract.py`: STEPControl import with assembly placements applied; `BRepBndLib.AddOptimal` with triangulation and shape-tolerance enlargement disabled; analytic planes/cylinders via `BRepAdaptor_Surface`. No tessellation used for measurement.
|
||||
- `geometry.json`: all 15 solids and their faces, bounds, validity, analytic cylinder radii/axes, plane normals. All 15 pass `BRepCheck_Analyzer`.
|
||||
- `validate.py`: checks original hashes, all 12 header centres against actual Eagle XML, board bounds, mounting holes and selected front-face geometry; writes `measurements.json` containing explicit face selections and their bounds.
|
||||
- For unique PCB/connector bodies, STEP `CLOSED_SHELL` face order is associated with import face order, checking face counts, every surface type and every cylinder radius. Appearance assignments corroborate feature identification; **colour is not a material certification**.
|
||||
- Imported solid 12 = STEP `#101` / `Body2`, the main connector. It fuses housing/insert, contacts, flange, shell and front hex posts; its whole-solid bounds are **not metal-only dimensions**. Solids 13/14 = `#102`/`#103` (`Body3`/`Body4`), separate anchor brackets.
|
||||
- Front shell/flange/hex-post faces have the shared `Opaque(245,245,246)` appearance and the expected geometric continuity. The report measures those **face subsets**, excluding plastic, contacts, PCB and ICs. There is no separate hollow metal shell solid from which to certify wall thickness.
|
||||
|
||||
**High confidence:** numerical nominal positions/bounds/radii of identified modeled features and STEP-to-Eagle board registration. **Moderate confidence:** physical fidelity of the simplified connector model and material boundaries; the contact/tail geometry differs from Eagle. **Unverified:** manufacturing tolerances, actual purchased-part fit, threads, male electrical mapping, cable/backshell envelope and final enclosure cutout. Extra decimal places below support reproducibility, not manufacturing accuracy.
|
||||
|
||||
## Coordinate system and orientation
|
||||
|
||||
All dimensions are **mm**. Use `(X,Y,Z)` in the imported STEP assembly:
|
||||
|
||||
- X rightward in component/top view; Y toward the connector/cable, Z above the PCB.
|
||||
- Bare breakout PCB: **X 0…31.750, Y 0…29.337, Z 0…1.570**. PCB front edge is **Y = 29.337**; underside is Z = 0, component-side surface Z = 1.570.
|
||||
- Logic header hole row: **Y = 2.667**, X = `1.905 + 2.540 × (N−1)`, N = 1…12. No installed header/socket body is modeled; this is the hole-axis datum, not a plastic-header edge.
|
||||
- STEP XY is already Eagle board XY: **identity transform**, no scale, rotation or translation. Verified by all 12 header holes, both mounting holes and PCB bounds. STEP connector symmetry alone would not establish this registration.
|
||||
- Existing carrier footprint coordinates (header pad 1 origin, Y downward): **`x = X − 1.905; y = 2.667 − Y`**. Height above module PCB top: **`h = Z − 1.570`**. Add the chosen module-top elevation later to get case/carrier height; socket/standoff stack is not yet selected.
|
||||
- Connector front/mating approach is along Y, **parallel to the PCB**, not down into it. A cable approaches from +Y and inserts toward −Y. A front view looks along −Y with +Z up; +X then appears to the viewer's **left**, unlike component/top view. Do not reuse top-view left/right pin numbering as front-view numbering.
|
||||
|
||||
## Metal feature dimensions — selected subsets, not assembly bounds
|
||||
|
||||
The Z centre of the D profile and front screw-lock axes is **7.84762024**, or **6.27762024 above the PCB top**. Connector centre X = **15.875** (carrier-local x = **13.970**).
|
||||
|
||||
| Feature | X bounds | Y bounds | Z bounds | Nominal dimensions / evidence |
|
||||
|---|---|---|---|---|
|
||||
| Front metal flange, exposed faces | 0.475…31.275 | 27.007…27.407 | 1.572620…14.122620 | **30.800 wide × 12.550 high × 0.400 axial thickness**; solid 12 faces 8, 11–14 |
|
||||
| D-shell straight outer wall region | 8.035981…23.714019 | 28.007…32.607 | 3.897620…11.797620 | **15.678038 wide × 7.900 high**, straight axial length **4.600**; faces 18–25 |
|
||||
| D-shell outer surface including root blend and rounded front lip | 7.435981…24.314019 | 27.407…33.207 | 3.297620…12.397620 | **16.878038 wide × 9.100 high × 5.800 axial extent**; faces 18–25, 81–88, 103–110 |
|
||||
| Left front hex screw-lock post | 0.719189…6.030811 | 27.407…32.207 | 5.547620…10.147620 | **4.600 across flats**, **5.311622 across corners**, **4.800 long**; faces 53–61, 89 |
|
||||
| Right front hex screw-lock post | 25.719189…31.030811 | 27.407…32.207 | 5.547620…10.147620 | Same dimensions; faces 62–70, 90 |
|
||||
|
||||
The flange is centred laterally on the PCB, inset **0.475** from each side. Its lower edge is only **0.002620** above the modeled PCB top, upper edge **12.552620** above it. Preserve this tiny source offset in registration; do not interpret it as a designed assembly clearance.
|
||||
|
||||
The D-shell has sloping side planes at **10° from vertical** in the front cross-section (`|normal X| = cos 10°`, `|normal Z| = sin 10°`), corner-cylinder radii **1.700**, root/lip rounding radius **0.600**. The wider side is upper (+Z). The maximum 16.878038 × 9.100 envelope occurs at the **root blend**, not uniformly along the projecting nose. These are bounding dimensions of a D shape, not a rectangular cutout prescription. Shell wall thickness / actual hollow cavity geometry is **not verified** by this fused representation.
|
||||
|
||||
### Front cable screw-lock holes: actual modeled cylinders, not thread specifications
|
||||
|
||||
- Axes **parallel to Y**, at `(X,Z) = (3.375,7.84762024)` and `(28.375,7.84762024)`.
|
||||
- **Axis spacing = 25.000**, symmetric ±12.500 about X = 15.875.
|
||||
- Carrier-local axis x = **1.470 and 26.470**; post front plane y = **−29.540**.
|
||||
- Solid 12 faces **60 / 69**, raw STEP faces **#13822 / #13831**, are inward-facing cylindrical hole surfaces: **radius 1.500, diameter 3.000**, Y = **27.407…32.007** (straight cylindrical length **4.600**).
|
||||
- Mouth cones (faces **89 / 90**) run Y = **32.007…32.207**, growing to **diameter 3.400** at the front: **0.200 axial × 0.200 radial** chamfer. Front mouth plane **Y = 32.207**.
|
||||
- Flat modeled bottoms at Y = **27.407** (faces 61/70): total modeled mouth-to-bottom depth **4.800** including chamfer.
|
||||
- **No helical threads modeled. Do not call this M3, 4-40, a tap drill, minor/major thread diameter, or usable engagement depth.** Actual screw standard, pitch, engagement and tolerances require a connector drawing or physical check.
|
||||
|
||||
## Front planes, PCB overhang and header offsets
|
||||
|
||||
There is no single interchangeable “front plane”. The flange, hex-post mouth and shell lip differ. The shell's foremost geometric plane is a useful **mating-face reference**, but the source does not establish a manufacturer's functional fully-mated datum or panel seating specification.
|
||||
|
||||
| Reference plane | STEP/Eagle Y | Forward of PCB front edge Y=29.337 | Forward of header row Y=2.667 | Carrier-local y |
|
||||
|---|---:|---:|---:|---:|
|
||||
| Flange rear face | 27.007 | −2.330 | 24.340 | −24.340 |
|
||||
| Flange front face / shell root | **27.407** | **−1.930** | **24.740** | **−24.740** |
|
||||
| Straight shell begins | 28.007 | −1.330 | 25.340 | −25.340 |
|
||||
| Breakout PCB front edge | **29.337** | 0 | **26.670** | **−26.670** |
|
||||
| Front hex-post mouth faces | **32.207** | **+2.870** | **29.540** | **−29.540** |
|
||||
| Straight shell ends / rounded lip begins | 32.607 | +3.270 | 29.940 | −29.940 |
|
||||
| Foremost shell lip / modeled insert face | **33.207** | **+3.870** | **30.540** | **−30.540** |
|
||||
|
||||
Thus the modeled shell nose projects **3.870 beyond the breakout PCB**; the hex posts project **2.870**, ending **1.000 behind the shell nose**. The flange itself is **1.930 behind the PCB front edge**. Do not position a case wall at the flange while ignoring that the PCB extends 1.930 beyond it.
|
||||
|
||||
For later unrotated, component-up carrier placement with header pad 1 at `(x0,y0)`:
|
||||
|
||||
- Breakout front edge: `y0 − 26.670`.
|
||||
- Flange front: `y0 − 24.740`.
|
||||
- Hex mouths: `y0 − 29.540`.
|
||||
- Shell nose: `y0 − 30.540`.
|
||||
- If the **breakout PCB front edge** is aligned with carrier top edge `ytop`, choose `y0 = ytop + 26.670`; shell nose is then at `ytop − 3.870`, hex mouths at `ytop − 2.870`.
|
||||
- If instead the **shell nose** is aligned with that edge, choose `y0 = ytop + 30.540`; breakout front edge is then 3.870 inward. These are different choices, not a recommended final layout.
|
||||
|
||||
## Three distinct mechanical systems — do not interchange
|
||||
|
||||
| System | Axes / coordinates | Spacing | Diameter or geometry | Purpose |
|
||||
|---|---|---:|---|---|
|
||||
| Front cable screw-lock holes | Y axes; X 3.375 / 28.375, Z 7.847620 | **25.000** | Modeled bore Ø3.000, mouth Ø3.400; no thread qualification | Retain mating cable at connector front |
|
||||
| Breakout PCB mounting holes | Z axes; `(3.810,11.430)` / `(27.940,11.430)` | **24.130** | Eagle drill Ø3.000 / plated copper Ø3.600; STEP hole cylinders Ø3.000 | Mount module to carrier/standoffs |
|
||||
| Connector shell/anchor PCB holes G1/G2 | Z axes; Eagle `(3.3528,17.907)` / `(28.3972,17.907)` | **25.0444** | Eagle drill Ø3.302 / copper Ø5.080; STEP rounds X to 3.353 / 28.397 | Receive connector anchor legs, **not** carrier mounting or cable screws |
|
||||
|
||||
### Separate metal anchor brackets and underside clearance
|
||||
|
||||
Solids **13/14** represent the bent anchor brackets/legs, not the front hex posts. Per bracket whole-solid bounds (leg-only components, not whole assembly):
|
||||
|
||||
- Right: **X 25.875…30.875, Y 17.907…24.307, Z −2.230…10.122620**.
|
||||
- Left: **X 0.875…5.875**, same Y and Z.
|
||||
- Each bracket envelope: **5.000 wide × 6.400 deep × 12.352620 high**. Bent sheet regions are **0.500 thick**; the lower two fingers occupy Y = **17.907…18.407**, not a circular screw shank.
|
||||
- Tips reach **2.230 below the module underside**, i.e. **3.800 below the module top**. This needs carrier/case underside clearance; do not turn these bounds into a mounting-hole drill recommendation.
|
||||
- Bracket symmetry centres X = **3.375 / 28.375**, lower sheet midplane Y = **18.157**. Relative to Eagle G1/G2, centre offsets are respectively **+0.0222 / −0.0222 in X** and **+0.250 in Y**. These are source-model differences, not a reason to move the Eagle pads.
|
||||
- Main fused body's signal tails separately reach Z = **−1.607380**. Their modeled rows are Y = **16.467 / 19.307**, versus Eagle **16.637 / 19.177**; horizontal pitch also differs (model 2.770 versus Eagle 2.7432). The 3D model is **not suitable as electrical pin/drill authority**.
|
||||
|
||||
No complete all-metal rear housing reconstruction is claimed: only the explicitly selected front surfaces and separate anchor brackets above. Main solid 12's 30.800 × 18.200 × 15.730 bounds include plastic/contact tails and must not be relabeled as a shell-only envelope.
|
||||
|
||||
## Validation, reproduction and release boundary
|
||||
|
||||
From project root:
|
||||
|
||||
```sh
|
||||
.cache/cad-tools/venv/bin/python -B hardware/PCB/reference/db9-cad/extract.py
|
||||
python3 -B hardware/PCB/reference/db9-cad/validate.py
|
||||
```
|
||||
|
||||
Both successfully ran; validation reported PASS for original hashes, 15 valid solids, all 12 Eagle header centres, PCB bounds, mounting holes, metal-appearance subsets and front lock cylinders. Extraction artifacts retain unrounded floating-point values. No firmware build, board DRC, hardware measurement or physical mating test was performed (research-only task).
|
||||
|
||||
**Ready for nominal placement planning:** metal flange dimensions, shell outer envelope, front hex-post dimensions and bore geometry, their distinct front planes, 25.000 front-axis spacing, PCB/header registration and overhang, anchor-leg underside extent. The A1 PCB now aligns the breakout front edge with the carrier top edge. Nominal metal bounding projections and screw-lock axes appear on Dwgs.User; companion layout/footprint notes have been updated. Neither projection nor nominal bound is an enclosure cutout or clearance qualification.
|
||||
|
||||
**Before fabrication / final case design:** verify actual male pin map; check physical connector profile and overhang against these nominal values; select header/socket and standoff heights; establish manufacturing/assembly clearances, cable backshell/tool access, wall thickness and cutout shape; verify thread standard and retention; perform a 1:1 fit/mating check. In particular, do not use exact-fit nominal bounds as an enclosure clearance or assume the CAD's Ø3.000 lock bore specifies a real screw thread.
|
||||
@@ -0,0 +1,105 @@
|
||||
# Provisional carrier module footprints
|
||||
|
||||
These project-local footprints are in `Carrier.pretty`. They are mechanical
|
||||
starting points, **not fabrication-ready connector selections**. Both are now
|
||||
assigned to their matching symbols in the native schematic through the project
|
||||
library tables and used in the routed PCB draft. The assembled module 3D models
|
||||
are not provided. See `pcb-draft-notes.md` for placement/routing and
|
||||
`rs232-footprint-notes.md` for the additional provisional RS-232 reference footprint.
|
||||
|
||||
## Shared pad/drill provenance and limits
|
||||
|
||||
Reference inspected locally: installed KiCad library footprint
|
||||
`Connector_PinHeader_2.54mm:PinHeader_1x04_P2.54mm_Vertical`, file
|
||||
`/usr/share/kicad/footprints/Connector_PinHeader_2.54mm.pretty/PinHeader_1x04_P2.54mm_Vertical.kicad_mod`
|
||||
(format version `20260206`; local KiCad `10.0.6-1.fc44`).
|
||||
|
||||
Both footprints use that reference's **1.70 × 1.70 mm plated through-hole pads,
|
||||
1.00 mm drills, rectangular pad 1, and circular remaining pads**, on
|
||||
`*.Cu` and `*.Mask`, with unused copper layers retained. Nominal pitch is 2.54 mm.
|
||||
Only the reference's pad/drill convention is reused, not its connector body,
|
||||
courtyard, or 3D model. HW678 pad 23 is circular: only overall pad 1 is rectangular.
|
||||
|
||||
**PROVISIONAL connector fit — awaiting the selected actual pin/socket datasheet.**
|
||||
Breadboard fit supports centre spacing, not drill size, finished-hole tolerance,
|
||||
annular-ring suitability, socket body clearance, mating height, or solderability.
|
||||
Validate those against the actual final headers and beta-test sockets and the PCB
|
||||
fabricator's process before release. No electrical nets or ESP32 GPIO assignments
|
||||
are inferred. No courtyards are supplied: module/connector assembly clearances
|
||||
remain incomplete, so automated courtyard checks cannot establish fit.
|
||||
|
||||
Coordinates below are millimetres, unrotated on the carrier front side, X right
|
||||
and Y down. Views are not mirrored. Placement on the back side or viewing the
|
||||
module from its solder side requires the appropriate KiCad transform; do not
|
||||
silently reverse the numbering.
|
||||
|
||||
## `Carrier:OLED_26mm_I2C_Provisional`
|
||||
|
||||
Origin: top-left corner of the module PCB, viewed from the front/display side,
|
||||
header at the top. User-verified geometry from the current request and the later
|
||||
mechanical-evidence section of `README.md`:
|
||||
|
||||
| Pad | Signal | X | Y |
|
||||
|---|---|---:|---:|
|
||||
| 1 | GND | 9.19 | 1.50 |
|
||||
| 2 | VCC | 11.73 | 1.50 |
|
||||
| 3 | SCL | 14.27 | 1.50 |
|
||||
| 4 | SDA | 16.81 | 1.50 |
|
||||
|
||||
- PCB boundary: (0, 0) to (26, 26), solid rectangle on `F.Fab`.
|
||||
This depicts the module, not the carrier's routed edge; no `Edge.Cuts` is used.
|
||||
- Four unnumbered, non-plated through holes, diameter 2.00 mm, at
|
||||
(1.75, 1.75), (24.25, 1.75), (1.75, 24.25), (24.25, 24.25).
|
||||
Hole centres are 1.75 mm from adjacent edges, with 22.50 mm spacing in both axes.
|
||||
These transfer the verified module hole geometry to the carrier; they do not
|
||||
select screws, inserts, bosses, or hardware clearance envelopes.
|
||||
- Display boundary: (1, 4.5) to (25.5, 21), 24.5 × 16.5 mm, dashed on `F.Fab`.
|
||||
This is the supplied display boundary, not a verified active-pixel area or case cutout.
|
||||
- Pad-to-signal mapping is documented here and in the footprint description;
|
||||
footprint pads themselves have numbers, not assigned nets.
|
||||
|
||||
The approved geometry supersedes the earlier 22.16 mm hole-spacing measurement:
|
||||
22.50 mm hole spacing, Y=1.50 mm header centres, and the centred header coordinates
|
||||
above apply. See README for the user-reported successful template fit check.
|
||||
|
||||
## `Carrier:HW678_2x22_Provisional`
|
||||
|
||||
Component/top view: antenna up, USB connectors down. Origin is the top-left header
|
||||
centre (pad 1), **not a PCB corner**.
|
||||
|
||||
- Left row: pads 1 through 22, top to bottom, X=0.
|
||||
- Right row: pads 23 through 44, top to bottom, X=25.40.
|
||||
- Y=0 through 53.34 in 2.54 mm increments for each row (21 intervals).
|
||||
- User breadboard fit verifies nominal 2.54 mm pitch and 25.40 mm row spacing;
|
||||
these supersede the earlier raw caliper dimensions in README.
|
||||
- Numbering follows the project's explicit convention, not a generic dual-row
|
||||
connector's alternating or counterclockwise numbering. Physical numbers do
|
||||
**not** establish GPIO, supply, or ground assignments.
|
||||
- Only pads and annotation text are present. **No PCB outline, antenna keepout,
|
||||
courtyard, USB envelope, mounting holes, or 3D model is guessed.** Text positions
|
||||
are annotations, not dimensional evidence. Outline registration, antenna extent,
|
||||
connector offsets/overhang, and assembly height remain unknown.
|
||||
|
||||
## Validation and release gates
|
||||
|
||||
Validation performed with KiCad 10.0.6 `pcbnew.FootprintLoad`: both footprints
|
||||
parsed successfully. Assertions passed for pad counts, all numbered pad positions,
|
||||
pad shapes/types/sizes/drills, OLED NPTH positions and diameters, both OLED `F.Fab`
|
||||
rectangle bounds, and absence of courtyards, `Edge.Cuts`, and keepout zones.
|
||||
HW678 was also checked to contain no graphical shapes. The local Python binding
|
||||
emitted three startup `PROPERTY_ENUM` assertions (also seen on a version-only
|
||||
import); these did not prevent loading or the checks from passing.
|
||||
|
||||
Repeat the retained footprint regression checks from the repository root with:
|
||||
|
||||
```sh
|
||||
/usr/bin/python3 -B hardware/PCB/validate_footprints.py
|
||||
```
|
||||
|
||||
This requires the system Python with KiCad's `pcbnew` binding. It checks the
|
||||
native-loaded pad counts, coordinates, pad types, sizes, drills and geometry. Parsing is
|
||||
not DRC, electrical verification, connector fit verification, or fabrication approval.
|
||||
Print both footprints at 1:1 and check the real assemblies, especially HW678 row
|
||||
numbering/orientation. Before layout release, select and verify connectors, map
|
||||
HW678 pins from reliable board evidence, and establish module envelopes,
|
||||
antenna restrictions, USB access, mounting hardware, and courtyards.
|
||||
@@ -0,0 +1,6 @@
|
||||
(fp_lib_table (version 7)
|
||||
(lib (name "Carrier")(type "KiCad")(uri "${KIPRJMOD}/Carrier.pretty")(options "")(descr "Provisional module carrier footprints; see footprint-notes.md"))
|
||||
(lib (name "Button_Switch_THT")(type "KiCad")(uri "${KICAD10_FOOTPRINT_DIR}/Button_Switch_THT.pretty")(options "")(descr "KiCad standard THT switches"))
|
||||
(lib (name "Resistor_THT")(type "KiCad")(uri "${KICAD10_FOOTPRINT_DIR}/Resistor_THT.pretty")(options "")(descr "KiCad standard axial resistors"))
|
||||
(lib (name "MountingHole")(type "KiCad")(uri "${KICAD10_FOOTPRINT_DIR}/MountingHole.pretty")(options "")(descr "KiCad standard mounting holes"))
|
||||
)
|
||||
@@ -0,0 +1,206 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Create the compact placement draft from a fresh KiCad XML netlist.
|
||||
|
||||
Requires system Python + pcbnew. Refuses to overwrite an existing PCB unless
|
||||
--overwrite is explicit. This is a draft generator, not a manufacturing tool.
|
||||
Does not modify the schematic, footprint libraries or project settings.
|
||||
SaveBoard(..., True) deliberately preserves the user's existing project rules.
|
||||
"""
|
||||
import argparse
|
||||
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import xml.etree.ElementTree as ET
|
||||
import pcbnew as p
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
PCB = ROOT / "serial-carrier.kicad_pcb"
|
||||
LIBS = Path("/usr/share/kicad/footprints")
|
||||
ORIGIN = (100, 100)
|
||||
OUTLINE = [(36,0), (82,0), (82,80), (0,80), (0,19), (36,19)]
|
||||
PLACEMENT = {
|
||||
"U1": ("Carrier:HW678_2x22_Provisional", 8, 23),
|
||||
"U2": ("Carrier:MAX3243_Reference_Provisional", 43, 26.670),
|
||||
"DS1": ("Carrier:OLED_26mm_I2C_Provisional", 43, 36),
|
||||
"R1": ("Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal", 43, 34),
|
||||
"R2": ("Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal", 54, 34),
|
||||
"R3": ("Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal", 65, 34),
|
||||
"SW1": ("Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x", 43, 68),
|
||||
"SW2": ("Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x", 55, 68),
|
||||
"SW3": ("Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x", 67, 68),
|
||||
}
|
||||
MOUNTS = [(3.5,22.5), (3.5,75), (78,4), (78,76)]
|
||||
# Deliberate allocation, NOT a measured antenna envelope or an RF guarantee.
|
||||
RF_RESERVE = [(10,19), (31,19), (31,32), (10,32)]
|
||||
|
||||
def mm(v):
|
||||
return p.FromMM(v)
|
||||
|
||||
def xy(x,y):
|
||||
return p.VECTOR2I(mm(x+ORIGIN[0]), mm(y+ORIGIN[1]))
|
||||
|
||||
def text(board, value, x, y, layer=p.F_SilkS, size=1):
|
||||
item = p.PCB_TEXT(board)
|
||||
item.SetText(value)
|
||||
item.SetPosition(xy(x,y))
|
||||
item.SetLayer(layer)
|
||||
item.SetTextSize(p.VECTOR2I(mm(size),mm(size)))
|
||||
item.SetTextThickness(mm(0.15))
|
||||
board.Add(item)
|
||||
return item
|
||||
|
||||
def polygon(zone, points):
|
||||
poly=zone.Outline()
|
||||
poly.NewOutline()
|
||||
for x,y in points:
|
||||
pt=xy(x,y)
|
||||
poly.Append(pt.x,pt.y)
|
||||
|
||||
def load(lib_id):
|
||||
lib,name=lib_id.split(":",1)
|
||||
folder=ROOT / "Carrier.pretty" if lib=="Carrier" else LIBS / (lib+".pretty")
|
||||
fp=p.FootprintLoad(str(folder),name)
|
||||
if fp is None:
|
||||
raise RuntimeError("Cannot load "+lib_id)
|
||||
fp.SetFPIDAsString(lib_id)
|
||||
return fp
|
||||
|
||||
def quiet_footprint(fp):
|
||||
# Retain all mechanical geometry, but replace sprawling research annotation
|
||||
# text with concise board annotations. Libraries are never modified.
|
||||
for graphic in list(fp.GraphicalItems()):
|
||||
if isinstance(graphic,p.PCB_TEXT):
|
||||
fp.Remove(graphic)
|
||||
fp.Value().SetVisible(False)
|
||||
fp.Reference().SetTextSize(p.VECTOR2I(mm(1),mm(1)))
|
||||
fp.Reference().SetTextThickness(mm(0.15))
|
||||
fp.Reference().SetLayer(p.F_SilkS)
|
||||
|
||||
|
||||
def copy_schematic_fields(fp, component):
|
||||
for field in component.findall("fields/field"):
|
||||
name=field.get("name")
|
||||
if name in ("Reference", "Value", "Footprint"):
|
||||
continue
|
||||
fp.SetField(name, field.text or "")
|
||||
fp.GetField(name).SetVisible(False)
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
parser=argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--overwrite",action="store_true")
|
||||
args=parser.parse_args()
|
||||
if PCB.exists() and not args.overwrite:
|
||||
parser.error("PCB exists; preserve manual edits or explicitly use --overwrite")
|
||||
if ROOT.joinpath("~serial-carrier.kicad_pcb.lck").exists():
|
||||
parser.error("PCB is open in KiCad; close it before regenerating")
|
||||
netfile=ROOT / "validation/pcb-source-netlist.xml"
|
||||
subprocess.run(["kicad-cli","sch","export","netlist","--format","kicadxml","-o",str(netfile),str(ROOT/"serial-carrier.kicad_sch")],check=True)
|
||||
source=ET.parse(netfile).getroot()
|
||||
components={c.get("ref"):c for c in source.findall("components/comp")}
|
||||
assert set(components)==set(PLACEMENT), "Schematic component set changed; review layout"
|
||||
schematic=ROOT.joinpath("serial-carrier.kicad_sch").read_text()
|
||||
root_uuid=re.search(r'\(uuid "?([a-f0-9-]{36})"?\)',schematic).group(1)
|
||||
board=p.BOARD()
|
||||
board.SetCopperLayerCount(2)
|
||||
settings=board.GetDesignSettings()
|
||||
settings.SetBoardThickness(mm(1.6))
|
||||
settings.m_MinClearance=mm(0.25)
|
||||
settings.m_CopperEdgeClearance=mm(0.5)
|
||||
settings.m_HoleClearance=mm(0.25)
|
||||
settings.m_TrackMinWidth=mm(0.25)
|
||||
settings.m_ViasMinSize=mm(0.7)
|
||||
# Proposed in-memory draft rules only; never overwrite the user's project settings.
|
||||
title=board.GetTitleBlock()
|
||||
title.SetTitle("Serial Swiss Army Knife - 82x80 mm carrier draft")
|
||||
title.SetRevision("A1 DRAFT")
|
||||
title.SetComment(0,"NOT FOR FABRICATION - USB / antenna geometry pending; verify module pinout / assembly")
|
||||
pad_nets={}
|
||||
for source_net in source.findall("nets/net"):
|
||||
net=p.NETINFO_ITEM(board,source_net.get("name"))
|
||||
board.Add(net)
|
||||
for node in source_net.findall("node"):
|
||||
pad_nets[node.get("ref"),node.get("pin")]=(net,node)
|
||||
for ref,(lib_id,x,y) in PLACEMENT.items():
|
||||
comp=components[ref]
|
||||
if ref != "U2":
|
||||
assert comp.findtext("footprint")==lib_id,(ref,"Schematic footprint changed")
|
||||
fp=load(lib_id)
|
||||
fp.SetReference(ref)
|
||||
fp.SetValue(comp.findtext("value"))
|
||||
path=p.KIID_PATH()
|
||||
path.push_back(p.KIID(root_uuid))
|
||||
path.push_back(p.KIID(comp.findtext("tstamps")))
|
||||
fp.SetPath(path)
|
||||
fp.SetSheetfile("serial-carrier.kicad_sch")
|
||||
fp.SetSheetname("serial-carrier")
|
||||
fp.SetPosition(xy(x,y))
|
||||
board.Add(fp)
|
||||
quiet_footprint(fp)
|
||||
copy_schematic_fields(fp,comp)
|
||||
if ref=="U1":
|
||||
fp.Reference().SetPosition(xy(20.7,44))
|
||||
elif ref=="U2":
|
||||
fp.Reference().SetPosition(xy(56.97,14.670))
|
||||
elif ref=="DS1":
|
||||
fp.Reference().SetPosition(xy(56,49))
|
||||
elif ref.startswith("SW"):
|
||||
fp.Reference().SetPosition(xy(x+3.25,y-2))
|
||||
else:
|
||||
fp.Reference().SetLayer(p.F_Fab)
|
||||
fp.Reference().SetPosition(xy(x+3.81,y-1.6))
|
||||
for pad in fp.Pads():
|
||||
key=ref,pad.GetNumber()
|
||||
if pad.GetNumber():
|
||||
net,node=pad_nets[key]
|
||||
pad.SetNet(net)
|
||||
pad.SetPinFunction(node.get("pinfunction", ""))
|
||||
pad.SetPinType(node.get("pintype", "passive"))
|
||||
for i,(x,y) in enumerate(MOUNTS,1):
|
||||
fp=load("MountingHole:MountingHole_3.2mm_M3")
|
||||
fp.SetReference("H"+str(i))
|
||||
fp.SetValue("M3 mounting proposal / 3.2mm")
|
||||
fp.SetBoardOnly(True)
|
||||
fp.SetExcludedFromBOM(True)
|
||||
fp.SetExcludedFromPosFiles(True)
|
||||
fp.SetPosition(xy(x,y))
|
||||
board.Add(fp)
|
||||
quiet_footprint(fp)
|
||||
fp.Reference().SetVisible(False)
|
||||
for a,b in zip(OUTLINE,OUTLINE[1:]+OUTLINE[:1]):
|
||||
edge=p.PCB_SHAPE(board)
|
||||
edge.SetShape(p.SHAPE_T_SEGMENT)
|
||||
edge.SetStart(xy(*a)); edge.SetEnd(xy(*b))
|
||||
edge.SetLayer(p.Edge_Cuts); edge.SetWidth(mm(0.05))
|
||||
board.Add(edge)
|
||||
reserve=p.ZONE(board)
|
||||
reserve.SetIsRuleArea(True)
|
||||
layers=p.LSET(); layers.AddLayer(p.F_Cu); layers.AddLayer(p.B_Cu)
|
||||
reserve.SetLayerSet(layers)
|
||||
reserve.SetZoneName("PROVISIONAL RF RESERVE - verify actual antenna clearance")
|
||||
reserve.SetDoNotAllowTracks(True); reserve.SetDoNotAllowVias(True)
|
||||
reserve.SetDoNotAllowZoneFills(True); reserve.SetDoNotAllowPads(True)
|
||||
reserve.SetDoNotAllowFootprints(False) # the module itself spans this region
|
||||
polygon(reserve,RF_RESERVE)
|
||||
board.Add(reserve)
|
||||
text(board,"RF RESERVE",20.5,26,p.Dwgs_User,1.2)
|
||||
text(board,"VERIFY ANTENNA",20.5,29,p.Dwgs_User,1)
|
||||
text(board,"ESP32 HW678",20.7,48)
|
||||
text(board,"USB ACCESS - VERIFY",18,83,p.Dwgs_User,1)
|
||||
text(board,"DE-9 OUT / NOMINAL MALE",56.97,-1.830,p.Dwgs_User,1)
|
||||
text(board,"MAX3243 REF",56.97,17.670)
|
||||
text(board,"26mm OLED",56,52)
|
||||
for label,x in zip(("BACK","SELECT","NEXT"),(46.25,58.25,70.25)):
|
||||
text(board,label,x,75, size=0.9)
|
||||
text(board,"DRAFT - NOT FOR FAB",56,78,size=0.9)
|
||||
text(board,"82 x 80 mm / 2 copper layers",41,-5,p.Dwgs_User,1.5)
|
||||
text(board,"Nominal RS-232 metal verified; ESP32 USB / antenna / assembly clearance pending",41,-2.5,p.Dwgs_User,0.9)
|
||||
board.BuildConnectivity()
|
||||
assert p.SaveBoard(str(PCB),board,True)
|
||||
print("Created",PCB,"with 9 circuit footprints, 4 proposed M3 mounts, 2 copper layers.")
|
||||
print("Placement draft only. U2 nominal male external metal dimensions user-confirmed; pin map / assembly still require qualification.")
|
||||
|
||||
if __name__=="__main__":
|
||||
main()
|
||||
@@ -0,0 +1,167 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate the initial native KiCad carrier schematic (Python stdlib only).
|
||||
|
||||
Explicit bootstrap/regeneration tool: overwrites the generated schematic, symbol
|
||||
library and library tables. Do not run over subsequent manual KiCad edits without
|
||||
reviewing/backing them up. It does not regenerate footprints or the fit template.
|
||||
"""
|
||||
import json
|
||||
from pathlib import Path
|
||||
import uuid
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
PROJECT = "serial-carrier"
|
||||
NS = uuid.UUID("b6f75dd1-a843-42f2-9067-57c995968145")
|
||||
|
||||
def uid(key):
|
||||
return str(uuid.uuid5(NS, key))
|
||||
|
||||
def q(value):
|
||||
return json.dumps(str(value), ensure_ascii=False)
|
||||
|
||||
def n(value):
|
||||
return f"{value:.4f}".rstrip("0").rstrip(".") if value else "0"
|
||||
|
||||
def effects(size=1.27, extra=""):
|
||||
return f"(effects (font (size {size} {size})) {extra})"
|
||||
|
||||
def prop(name, value, x=0, y=0, hidden=False):
|
||||
return f'(property {q(name)} {q(value)} (at {n(x)} {n(y)} 0) {effects(extra="(hide yes)" if hidden else "")})'
|
||||
|
||||
# Physical module numbering is a project convention, not ESP32 GPIO numbering:
|
||||
# component side, antenna up / USB down; left row 1..22, right row 23..44.
|
||||
left = ["3V3", "3V3", "EN", "GPIO4", "GPIO5", "GPIO6", "GPIO7", "GPIO15", "GPIO16", "GPIO17", "GPIO18", "GPIO8", "GPIO3", "GPIO46", "GPIO9", "GPIO10", "GPIO11", "GPIO12", "GPIO13", "GPIO14", "5V", "GND"]
|
||||
right = ["GND", "GPIO43", "GPIO44", "GPIO1", "GPIO2", "GPIO42", "GPIO41", "GPIO40", "GPIO39", "GPIO38", "GPIO37", "GPIO36", "GPIO35", "GPIO0", "GPIO45", "GPIO48", "GPIO47", "GPIO21", "GPIO20", "GPIO19", "GND", "GND"]
|
||||
GPIO_NET = {4:"RS_DCD",5:"RS_DSR",6:"RS_RI",7:"RS_DTR",8:"RS_VALID",9:"RS_OFF_N",10:"BTN_PREVIOUS",11:"OLED_SDA",12:"OLED_SCL",13:"BTN_SELECT",14:"BTN_NEXT",15:"RS_RTS",16:"RS_CTS",17:"RS_TX",18:"RS_RX"}
|
||||
OUTPUTS = {7, 12, 15, 17}
|
||||
INPUTS = {4, 5, 6, 8, 10, 13, 14, 16, 18}
|
||||
|
||||
def gpio_type(name):
|
||||
if not name.startswith("GPIO"):
|
||||
return "input" if name == "EN" else "passive"
|
||||
number = int(name[4:])
|
||||
if number == 9:
|
||||
# Model the programmable MCU pad, not a discrete open-collector device.
|
||||
# The required firmware open-drain mode and module pull-up are noted on sheet.
|
||||
return "bidirectional"
|
||||
return "output" if number in OUTPUTS else "input" if number in INPUTS else "bidirectional"
|
||||
|
||||
# Pin tuples: physical number, name, electrical type, local X, local Y, angle.
|
||||
MCU = []
|
||||
for side, names in enumerate((left, right)):
|
||||
for index, name in enumerate(names):
|
||||
number = index + 1 + side * 22
|
||||
kind = "power_out" if number in (1, 22) else gpio_type(name)
|
||||
MCU.append((str(number), name, kind, -22.86 if side == 0 else 22.86, 26.67-index*2.54, 0 if side == 0 else 180))
|
||||
|
||||
# Published Adafruit 5988 CAD reference; male 6253 correspondence is provisional.
|
||||
RS_NAMES = ["Vin", "GND", "DCD", "RX", "TX", "DTR", "DSR", "RTS", "CTS", "RI", "VLD", "OFF_N"]
|
||||
RS = [(str(i+1), name, "power_in" if i < 2 else "input" if name in ("TX","DTR","RTS","OFF_N") else "output", -20.32, 13.97-i*2.54, 0) for i,name in enumerate(RS_NAMES)]
|
||||
OLED = [(str(i+1),name,kind,-15.24,3.81-i*2.54,0) for i,(name,kind) in enumerate((("GND","power_in"),("VCC","power_in"),("SCL","input"),("SDA","bidirectional")))]
|
||||
TWO = [("1","~","passive",-5.08,0,0),("2","~","passive",5.08,0,180)]
|
||||
|
||||
SPECS = {
|
||||
"HW678_N16R8": (MCU, 17.78, 29.21, "U", "Carrier:HW678_2x22_Provisional", "HW-678 V0.0.0 / N16R8 carrier interface; project numbering; onboard USB/regulator retained"),
|
||||
"MAX3243_Module_Reference": (RS, 15.24, 17.78, "U", "", "Adafruit male 6253 intended; physical numbering from published female 5988 CAD, VERIFY MALE"),
|
||||
"OLED_I2C_26mm": (OLED, 10.16, 7.62, "DS", "Carrier:OLED_26mm_I2C_Provisional", "User fit-verified 26 mm OLED; front/top header GND VCC SCL SDA"),
|
||||
"SW_NO": (TWO, 2.54, 2.54, "SW", "Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x", "Normally open contact; footprint duplicates pads 1 and 2 for internally common terminal pairs"),
|
||||
"R": (TWO, 2.54, 1.016, "R", "Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal", "Resistor, axial DIN0207 footprint; exact MPN pending")
|
||||
}
|
||||
|
||||
def symbol_definition(name, embedded=False):
|
||||
pins, halfwidth, halfheight, ref, footprint, description = SPECS[name]
|
||||
out = [f'(symbol {q("Carrier:"+name if embedded else name)} (pin_names (offset 0.762)) (in_bom yes) (on_board yes)',
|
||||
prop("Reference", ref, 0, halfheight+5.08), prop("Value", name, 0, halfheight+2.54),
|
||||
prop("Footprint", footprint, hidden=True), prop("Datasheet", "", hidden=True), prop("Description",description,hidden=True)]
|
||||
if name == "SW_NO":
|
||||
shape = '(polyline (pts (xy -2.54 0) (xy 2.54 1.524)) (stroke (width 0.254) (type default)) (fill (type none)))'
|
||||
shape += ''.join(f'(circle (center {x} 0) (radius 0.35) (stroke (width 0.1524) (type default)) (fill (type none)))' for x in (-2.54,2.54))
|
||||
else:
|
||||
shape = f'(rectangle (start {-halfwidth} {halfheight}) (end {halfwidth} {-halfheight}) (stroke (width 0.254) (type default)) (fill (type background)))'
|
||||
out.append(f'(symbol {q(name+"_0_1")} {shape})')
|
||||
out.append(f'(symbol {q(name+"_1_1")}')
|
||||
for number,label,kind,x,y,angle in pins:
|
||||
length = abs(x)-halfwidth
|
||||
out.append(f'(pin {kind} line (at {n(x)} {n(y)} {angle}) (length {n(length)}) (name {q(label)} {effects(1.016)}) (number {q(number)} {effects(1.016)}))')
|
||||
out.extend([')', ')'])
|
||||
return '\n'.join(out)
|
||||
|
||||
ROOT.joinpath("Carrier.kicad_sym").write_text('(kicad_symbol_lib (version 20231120) (generator "kicad_symbol_editor")\n'+'\n'.join(symbol_definition(name) for name in SPECS)+'\n)\n')
|
||||
ROOT.joinpath("sym-lib-table").write_text('(sym_lib_table (version 7)\n (lib (name "Carrier")(type "KiCad")(uri "${KIPRJMOD}/Carrier.kicad_sym")(options "")(descr "Project-local module interfaces"))\n)\n')
|
||||
ROOT.joinpath("fp-lib-table").write_text('(fp_lib_table (version 7)\n (lib (name "Carrier")(type "KiCad")(uri "${KIPRJMOD}/Carrier.pretty")(options "")(descr "Provisional module carrier footprints; see footprint-notes.md"))\n (lib (name "Button_Switch_THT")(type "KiCad")(uri "${KICAD10_FOOTPRINT_DIR}/Button_Switch_THT.pretty")(options "")(descr "KiCad standard THT switches"))\n (lib (name "Resistor_THT")(type "KiCad")(uri "${KICAD10_FOOTPRINT_DIR}/Resistor_THT.pretty")(options "")(descr "KiCad standard axial resistors"))\n (lib (name "MountingHole")(type "KiCad")(uri "${KICAD10_FOOTPRINT_DIR}/MountingHole.pretty")(options "")(descr "KiCad standard mounting holes"))\n)\n')
|
||||
# Preserve project settings on regeneration.
|
||||
project_file = ROOT / (PROJECT + ".kicad_pro")
|
||||
if not project_file.exists():
|
||||
project_file.write_text(json.dumps({"meta":{"filename":project_file.name,"version":3}},indent=2)+"\n")
|
||||
|
||||
root_uuid = uid("root")
|
||||
items = [f'(kicad_sch (version 20250114) (generator "eeschema") (uuid {q(root_uuid)}) (paper "A3")',
|
||||
'(title_block (title "ESP32 Serial Swiss Army Knife - Module Carrier") (date "2026-09-20") (rev "A0 DRAFT") (comment 1 "Not fabrication-ready: provisional module footprints and male RS-232 mapping"))',
|
||||
'(lib_symbols '+'\n'.join(symbol_definition(name,True) for name in SPECS)+')']
|
||||
expected = {}
|
||||
|
||||
def wire(x1,y1,x2,y2,key):
|
||||
items.append(f'(wire (pts (xy {n(x1)} {n(y1)}) (xy {n(x2)} {n(y2)})) (stroke (width 0) (type default)) (uuid {q(uid("wire:"+key))}))')
|
||||
|
||||
def label(net,x,y,key):
|
||||
items.append(f'(label {q(net)} (at {n(x)} {n(y)} 0) {effects(1.016,"(justify left bottom)")} (uuid {q(uid("label:"+key))}))')
|
||||
|
||||
def note(text,x,y,size=1.27):
|
||||
items.append(f'(text {q(text)} (at {n(x)} {n(y)} 0) {effects(size,"(justify left top)")} (uuid {q(uid("text:"+text))}))')
|
||||
|
||||
def place(name,ref,value,x,y,nets,extra=None):
|
||||
pins,hw,hh,_,footprint,description = SPECS[name]
|
||||
instance = [f'(symbol (lib_id {q("Carrier:"+name)}) (at {n(x)} {n(y)} 0) (unit 1) (in_bom yes) (on_board yes) (dnp no) (uuid {q(uid(ref))})',
|
||||
prop("Reference",ref,x,y-hh-5.08),prop("Value",value,x,y-hh-2.54),prop("Footprint",footprint,x,y,True),prop("Datasheet","",x,y,True)]
|
||||
for k,v in (extra or {}).items():
|
||||
instance.append(prop(k,v,x,y,True))
|
||||
instance.extend(f'(pin {q(p[0])} (uuid {q(uid(ref+":"+p[0]))}))' for p in pins)
|
||||
instance.append(f'(instances (project {q(PROJECT)} (path {q("/"+root_uuid)} (reference {q(ref)}) (unit 1)))))')
|
||||
items.append('\n'.join(instance))
|
||||
for number,_,_,px,py,angle in pins:
|
||||
ax,ay=x+px,y-py
|
||||
net=nets.get(number)
|
||||
key=ref+":"+number
|
||||
if net is None:
|
||||
items.append(f'(no_connect (at {n(ax)} {n(ay)}) (uuid {q(uid("nc:"+key))}))')
|
||||
else:
|
||||
end=ax-12.7 if angle==0 else ax+12.7
|
||||
wire(ax,ay,end,ay,key)
|
||||
label(net,end,ay,key)
|
||||
expected.setdefault(net,[]).append([ref,number])
|
||||
|
||||
mcu_nets={}
|
||||
for number,name,*_ in MCU:
|
||||
if name == "3V3":
|
||||
mcu_nets[number]="+3V3"
|
||||
elif name == "GND":
|
||||
mcu_nets[number]="GND"
|
||||
elif name.startswith("GPIO") and int(name[4:]) in GPIO_NET:
|
||||
mcu_nets[number]=GPIO_NET[int(name[4:])]
|
||||
place("HW678_N16R8","U1","HW678 / S3-N16R8",95.25,109.22,mcu_nets,{"Status":"Header geometry nominal; outline/antenna clearance pending","Pin_numbering":"Left 1-22; right 23-44; both top-to-bottom, antenna up"})
|
||||
rs_nets=dict(zip(map(str,range(1,13)),["+3V3","GND","RS_DCD","RS_RX","RS_TX","RS_DTR","RS_DSR","RS_RTS","RS_CTS","RS_RI","RS_VALID","RS_OFF_N"]))
|
||||
place("MAX3243_Module_Reference","U2","Adafruit MAX3243 / male 6253",228.6,96.52,rs_nets,{"Status":"VERIFY male header numbering; footprint intentionally unassigned","Datasheet_source":"adafruit-research.md; reference female 5988 CAD"})
|
||||
place("OLED_I2C_26mm","DS1","128x64 I2C OLED / 0x3C",228.6,152.4,{"1":"GND","2":"+3V3","3":"OLED_SCL","4":"OLED_SDA"},{"Status":"Module outline/header/display user fit-verified; pad/drill fit provisional"})
|
||||
for index,(net,function) in enumerate((("BTN_PREVIOUS","Previous / back"),("BTN_SELECT","Select / confirm"),("BTN_NEXT","Next")),1):
|
||||
y=88.9+(index-1)*35.56
|
||||
place("R",f"R{index}","2.2k 1%",327.66,y,{"1":"+3V3","2":net},{"Rating":"At least 0.125 W; exact resistor MPN pending"})
|
||||
place("SW_NO",f"SW{index}","B3F-1000",327.66,y+15.24,{"1":net,"2":"GND"},{"Manufacturer":"Omron","MPN":"B3F-1000","Function":function,"Procurement":"Live stock not verified"})
|
||||
note(function,302.26,y+21.59,1.016)
|
||||
|
||||
note("MODULE CARRIER - INITIAL SCHEMATIC",15.24,15.24,2.54)
|
||||
note("Native KiCad 10 schematic. Named wire labels join matching nets on this sheet.\nRead hardware/PCB/README.md before layout or fabrication.",15.24,24.13)
|
||||
note("U1: complete development board",53.34,57.15,1.778)
|
||||
note("Antenna up / USB down: left row 1-22, right row 23-44.\nNC marks mean unused on carrier, not unused inside module.",53.34,64.77,1.016)
|
||||
note("U2: complete RS-232 breakout",177.8,57.15,1.778)
|
||||
note("Male 6253 selected. Numbers from published 5988 female CAD.\nVERIFY male header before routing; no footprint assigned.\nDE-9 and charge pump are already on the module.",177.8,64.77,1.016)
|
||||
note("Controls: active-low, firmware-debounced",292.1,57.15,1.778)
|
||||
note("External 2.2k pull-ups: ~1.5 mA pressed at 3.3 V.\nChosen for B3F-1000 rated contact load; internal\npull-ups may remain enabled. Exact resistor MPN pending.",292.1,64.77,1.016)
|
||||
note("DS1: front view GND / VCC / SCL / SDA",177.8,128.27,1.524)
|
||||
note("Power at 3.3 V only. Verify on-module I2C pull-ups;\nexternal values intentionally not guessed.",177.8,165.1,1.016)
|
||||
note("POWER / RECOVERY\nPower only through U1's existing USB ports. No carrier 5 V input.\nU1 regulator supplies +3V3; GND pins are common on the module.\nPin 1 +3V3 and pin 22 GND model the onboard supply for ERC.\nGPIO19/20 native USB and GPIO43/44 UART0 remain onboard.\nVerify USB power isolation before attaching two powered hosts.\nKeep both USB connectors and reset/boot buttons accessible.",15.24,185.42)
|
||||
note("RS-232 / ELECTRICAL LIMITS\nTX/RX and modem labels are at the ESP32 logic side.\nOFF_N is active low; GPIO9 must remain open-drain.\nBreakout provides its OFF pull-up. No galvanic isolation.\nNo carrier connection to raw RS-232 voltages.\nCarrier GND, USB GND and RS-232 signal ground are common.\nNever power OLED or MAX3243 logic from 5 V.",152.4,185.42)
|
||||
note("BEFORE PCB RELEASE\nVerify male breakout pin order/footprint.\nConfirm header/socket finished-hole requirements.\nComplete module courtyards and antenna clearance.\nVerify 3.3 V budget, OLED pull-ups and USB power path.\nChoose mounting hardware and confirm button stock.\nERC is not hardware or fabrication approval.",292.1,185.42)
|
||||
items.append(')')
|
||||
ROOT.joinpath(PROJECT+".kicad_sch").write_text('\n'.join(items)+'\n')
|
||||
ROOT.joinpath("validation/expected-nets.json").write_text(json.dumps(expected,indent=2,sort_keys=True)+'\n')
|
||||
print("Generated native KiCad schematic, project-local symbol library and tables.")
|
||||
@@ -0,0 +1,37 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100mm" height="90mm" viewBox="0 0 100 90">
|
||||
<title>OLED module mechanical fit check — front view</title>
|
||||
<desc>26 mm square PCB, four 2 mm mounting holes at 1.75 mm edge offsets. Centred four-pin header at 2.54 mm pitch, 1.50 mm from top edge. Display outline at (1, 4.5), measuring 24.5 × 16.5 mm. Header crosses indicate centres only, not drill sizes.</desc>
|
||||
<g font-family="sans-serif" font-size="3" fill="black">
|
||||
<text x="5" y="7">OLED mechanical fit check — front view</text>
|
||||
<text x="5" y="12">Print at 100%; disable fit-to-page.</text>
|
||||
<text x="5" y="17">Verify the scale below before use.</text>
|
||||
</g>
|
||||
<g transform="translate(35 27)" stroke="black" stroke-width="0.15" fill="none">
|
||||
<rect width="26" height="26"/>
|
||||
<rect id="display-outline" x="1" y="4.5" width="24.5" height="16.5" stroke-dasharray="0.6 0.4"/>
|
||||
<circle cx="1.75" cy="1.75" r="1"/>
|
||||
<circle cx="24.25" cy="1.75" r="1"/>
|
||||
<circle cx="1.75" cy="24.25" r="1"/>
|
||||
<circle cx="24.25" cy="24.25" r="1"/>
|
||||
<path id="header-centres" d="M8.79 1.5h.8 M9.19 1.1v.8 M11.33 1.5h.8 M11.73 1.1v.8 M13.87 1.5h.8 M14.27 1.1v.8 M16.41 1.5h.8 M16.81 1.1v.8"/>
|
||||
<g stroke="none" fill="black" font-family="sans-serif" font-size="1.5" text-anchor="middle">
|
||||
<text x="9.19" y="-1">GND</text>
|
||||
<text x="11.73" y="-1">VCC</text>
|
||||
<text x="14.27" y="-1">SCL</text>
|
||||
<text x="16.81" y="-1">SDA</text>
|
||||
<text x="13" y="7">Dashed: display outline</text>
|
||||
<text x="13" y="10">24.5 × 16.5 mm</text>
|
||||
<text x="13" y="13">26 × 26 mm PCB</text>
|
||||
<text x="13" y="16">Holes: Ø2 mm</text>
|
||||
<text x="13" y="19">Hole spacing: 22.50 mm</text>
|
||||
</g>
|
||||
</g>
|
||||
<g stroke="black" stroke-width="0.15" fill="none">
|
||||
<path d="M35 65h20 M35 63v4 M55 63v4"/>
|
||||
</g>
|
||||
<g font-family="sans-serif" font-size="2.5" fill="black">
|
||||
<text x="39" y="71">20 mm scale</text>
|
||||
<text x="5" y="79">Header crosses mark pin centres, not drill sizes.</text>
|
||||
<text x="5" y="84">Mechanical reference only — not a fabrication file.</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1,243 @@
|
||||
# Compact two-layer PCB — A1 draft
|
||||
|
||||
Open **`serial-carrier.kicad_pcb`** in KiCad 10. The routed board belongs to the
|
||||
existing `serial-carrier.kicad_pro` / `.kicad_sch` project. **Not for fabrication.**
|
||||
No Gerbers, production drills or manufacturing archive were generated.
|
||||
|
||||
## Size and placement
|
||||
|
||||
- **Carrier bounding dimensions: 82 × 80 mm**, within the requested 100 × 100 mm.
|
||||
- **Two copper layers**, F.Cu and B.Cu. All components mount on the front.
|
||||
- Proposed **1.6 mm** carrier thickness; fabrication stackup/process not selected.
|
||||
- Stepped outline: a **36 × 19 mm top-left antenna cutaway** fits within a simple
|
||||
rectangular/square case. The lower edge is now straight, with ESP32 USB toward
|
||||
the bottom and RS-232 toward the top. U1 moved down by 19 mm; its exact USB/body
|
||||
registration remains provisional pending the user's measured ESP32 geometry.
|
||||
- Module and mating-plug overhangs are not included in the carrier dimensions.
|
||||
The final complete assembly/enclosure envelope still needs measurement.
|
||||
|
||||
Coordinates in the following table are local millimetres from the carrier's
|
||||
upper-left corner, X right/Y down, viewed from the component side. In the KiCad
|
||||
file add (100, 100) mm to obtain absolute board coordinates. All footprints are
|
||||
unrotated; their origins are defined in the footprint notes, not necessarily
|
||||
component body corners.
|
||||
|
||||
| Reference | Local origin (X, Y) | Placement |
|
||||
|---|---|---|
|
||||
| U1 | (8, 23) | ESP32 left header pin 1; antenna toward top-left cutaway, USB toward bottom edge |
|
||||
| U2 | (43, 26.670) | MAX3243 header pin 1; bare module PCB front exactly flush with carrier top edge |
|
||||
| DS1 | (43, 36) | OLED module upper-left PCB corner; display faces up |
|
||||
| R1 / R2 / R3 | (43 / 54 / 65, 34) | Axial pull-ups in the gap between RS-232 and OLED |
|
||||
| SW1 / SW2 / SW3 | (43 / 55 / 67, 68) | Back, select, next below display; switch pad 1 origins |
|
||||
| H1 | (3.5, 22.5) | Proposed case mounting hole below antenna notch |
|
||||
| H2 | (3.5, 75) | Proposed case mounting hole near bottom-left edge |
|
||||
| H3 | (78, 4) | Proposed case mounting hole |
|
||||
| H4 | (78, 76) | Proposed case mounting hole |
|
||||
|
||||
Outline vertices: (36,0), (82,0), (82,80), (0,80), (0,19), (36,19), closed.
|
||||
The previous A0 board is retained in `validation/pcb-a0-before-reposition.kicad_pcb`.
|
||||
The board area is **58.76 cm²**; its bounding rectangle is 65.6 cm².
|
||||
|
||||
H1–H4 are proposed **Ø3.2 mm NPTH M3 clearance holes**, not a previously agreed
|
||||
case specification. They are marked board-only and excluded from the electrical
|
||||
BOM. The OLED and RS-232 module mounting holes are additional, separate holes.
|
||||
Screw heads, spacers, heat-set inserts, tool access and underside lead clearance
|
||||
must be checked against the final case and assembly heights. No insert dimensions
|
||||
or module stand-off heights are assumed.
|
||||
|
||||
## Routing and electrical status
|
||||
|
||||
The PCB contains actual copper, not just ratsnest placement:
|
||||
|
||||
- All **38 required physical-pad spanning connections** across **17 connected
|
||||
nets** routed, including both duplicated pads of each switch contact.
|
||||
- **565 segments**, signal width **0.25 mm**, 3.3 V and GND width **0.50 mm**.
|
||||
- **22 through vias**, **0.70 mm copper / 0.30 mm drill**.
|
||||
- **No copper pours**; ground is explicitly routed. This is an initial routing
|
||||
solution, not a reviewed return-current/EMI or power-integrity design.
|
||||
- Proposed minimum different-net clearance **0.25 mm** and copper-edge clearance
|
||||
**0.50 mm**. These are selected draft design rules, not fabrication qualification.
|
||||
- Project netclasses align interactive routing defaults with the draft: Default
|
||||
uses 0.25 mm tracks / 0.25 mm clearance, while Power matches `/+3V3` and `/GND`
|
||||
with 0.50 mm tracks. Both use 0.70 / 0.30 mm vias.
|
||||
- Schematic pad-net assignments, component values and UUID linkage are retained.
|
||||
U1's intentionally unused GPIO/USB/UART0/5 V header pads remain unused.
|
||||
|
||||
The bounded draft router prioritizes connectivity and conservative geometric
|
||||
clearance, not optimal trace topology. Do not treat DRC passage as evidence of
|
||||
current-carrying capacity, supply stability, I²C signal quality or EMC compliance.
|
||||
|
||||
## Explicit provisional mechanical choices
|
||||
|
||||
### ESP32 and RF
|
||||
|
||||
A two-layer **no-tracks/no-vias/no-pads/no-zone-fill rule area** reserves local
|
||||
X=10…31 mm, Y=19…32 mm beneath the antenna-side end of U1, adjacent to the notch. This is a deliberate carrier design
|
||||
allocation, **not a measured antenna envelope or a manufacturer RF keepout**.
|
||||
The module itself may span the rule area; footprint placement is not forbidden.
|
||||
No carrier copper enters the reserve, but copper runs close to its boundary.
|
||||
|
||||
U1's exact PCB/antenna/USB offsets are still unmeasured. The intended antenna
|
||||
overhang and USB access must be checked with the actual board and a printed
|
||||
placement template. Move U1, change the edge or enlarge the reserve if required.
|
||||
Missing RF clearance cannot be signed off from the current footprint alone.
|
||||
|
||||
### RS-232
|
||||
|
||||
U2 uses the new **`Carrier:MAX3243_Reference_Provisional`** footprint **on the PCB
|
||||
only**. Its bare-module outline/header/holes are adapted from published female
|
||||
5988 CAD for the intended male 6253 module. The user explicitly confirms identical
|
||||
male/female external dimensions. The linked Adafruit STEP has now been measured
|
||||
with OpenCascade; its nominal metal geometry is used on that basis. Male header
|
||||
mapping, actual fit, tolerances and the mounting stack still need qualification.
|
||||
See `rs232-footprint-notes.md` and `db9-mechanical-notes.md` for evidence/licenses.
|
||||
|
||||
The module's **bare PCB front is flush with carrier Y=0**, as requested. The
|
||||
connector has distinct front planes, not one universal case-wall datum:
|
||||
|
||||
| Feature | Position relative to carrier top edge | Meaning |
|
||||
|---|---:|---|
|
||||
| Flange front / shell root | 1.930 mm inside | Behind the PCB edge |
|
||||
| Breakout PCB front | 0 | Flush with the carrier edge |
|
||||
| Hex screw-lock mouth faces | 2.870 mm outside | Front cable-retention hole mouths |
|
||||
| Shell nose | 3.870 mm outside | Foremost modeled shell surface |
|
||||
|
||||
The front screw-lock axes are **25.000 mm apart**, with centres **6.277620 mm
|
||||
above the module PCB top**. They are not the two 24.130 mm-spaced PCB mounting
|
||||
holes. The source models Ø3.000 mm bores but **no threads**; this does not establish
|
||||
M3 or 4-40 hardware. The main metal flange is 30.800 × 12.550 × 0.400 mm; the
|
||||
shell envelope including its root/lip is 16.878 × 9.100 × 5.800 mm.
|
||||
|
||||
`add_db9_envelope.py` places the measured metal bounding projections and screw-lock
|
||||
axes on **Dwgs.User**, relative to U2. They are not silkscreen, a courtyard or a
|
||||
case cutout. Run the helper after moving U2; it preserves copper and project
|
||||
settings. The original STEP and its extraction/validation scripts are retained.
|
||||
|
||||
**Case-wall positioning remains a design decision:** PCB-edge alignment does not
|
||||
put the flange or screw-lock mouths at that same plane. For example, making the
|
||||
hex mouths flush with the case exterior would place that exterior 2.870 mm beyond
|
||||
the carrier edge. No wall thickness, cable-backshell clearance or sealing scheme
|
||||
has been assumed. The model's anchor legs extend **2.230 mm below the module
|
||||
underside**; select a stand-off/header stack or clearance solution before assembly.
|
||||
|
||||
The schematic footprint remains intentionally blank, preserving its caution
|
||||
and avoiding edits to the schematic that was open in KiCad. Consequently there
|
||||
is **one known schematic-parity warning**: U2 footprint differs from the empty
|
||||
schematic assignment. This is not excluded/suppressed, and is not a net mismatch.
|
||||
Once the male footprint is qualified, explicitly assign it in the schematic
|
||||
before relying on Update PCB from Schematic to manage U2's footprint.
|
||||
|
||||
### OLED and controls
|
||||
|
||||
OLED geometry, including its display boundary, is the user-fit-verified geometry.
|
||||
The module schematic/library files are unchanged. The new overall placement still
|
||||
needs an assembly fit check: adjacent resistor bodies, module underside parts,
|
||||
header/socket bodies, button actuation and finger/plunger clearance are not
|
||||
represented by complete assembly courtyards. In particular the axial resistors
|
||||
are tightly packed between the module outlines; confirm clearance and height.
|
||||
|
||||
## Device outlines on front silkscreen
|
||||
|
||||
The PCB now has **0.12 mm front-silkscreen outlines** for the OLED PCB and the
|
||||
published RS-232 reference module, plus a dashed OLED display boundary. The
|
||||
RS-232's visible rounded corners retain true arcs. Small gaps clear pad/mask
|
||||
openings, vias and adjacent resistor markings; the original full F.Fab geometry
|
||||
remains unchanged.
|
||||
|
||||
U1 instead has **header-row guides**, labelled `HEADER GUIDES`: guide edges are
|
||||
1.20 mm outside the outer pin centres. These are graphic alignment aids, **not
|
||||
claimed socket-body dimensions or an inferred ESP32 PCB outline**. The actual
|
||||
U1 body/antenna/USB outline still requires measured offsets. Existing button and
|
||||
resistor silkscreen remains unchanged.
|
||||
|
||||
These are board-local drawings, not edits to the footprint libraries. If the
|
||||
module placement or routing changes, close PCB Editor and refresh them with:
|
||||
|
||||
```sh
|
||||
/usr/bin/python3 -B hardware/PCB/add_device_silkscreen.py
|
||||
```
|
||||
|
||||
The script requires pcbnew and Shapely, replaces only its own generated graphics,
|
||||
and refuses an open PCB or concurrent board change. It leaves project settings,
|
||||
copper, pads and source F.Fab outlines unchanged. Run it **after** placement or
|
||||
routing regeneration; moving a footprint does not move these board-local drawings.
|
||||
It currently supports the draft's unrotated module placements.
|
||||
|
||||
The original A0 silkscreen-only change left its routing unchanged. A1 deliberately
|
||||
repositions U1/U2 and reroutes all connections, then refreshes these outlines.
|
||||
Silkscreen is clipped away from the new flush U2 front board edge. Physical DRC
|
||||
remains **0 violations**, **0 unconnected**, with the same U2 footprint-parity
|
||||
warning. PDF and both previews have been refreshed.
|
||||
|
||||
## Review outputs
|
||||
|
||||
- `validation/pcb-draft.svg` / `pcb-draft.png`: combined copper/outline/fab review.
|
||||
- `validation/pcb-draft-3d.png`: KiCad render of the carrier and available standard
|
||||
component models. **ESP32, OLED and RS-232 assembled module models are absent**;
|
||||
the render is not an assembled-device or enclosure fit check.
|
||||
- `pcb-placement-draft.pdf`: **1:1** front placement/pad/outline drawing. Print at
|
||||
100%, disable fit-to-page, and confirm the outer width is 82 mm before use.
|
||||
It includes the known module outlines and nominal STEP-derived DE-9 metal
|
||||
bounding projections; no guessed U1 body or case-wall geometry.
|
||||
|
||||
## Validation and limits
|
||||
|
||||
Run from the repository root with the system Python containing KiCad bindings:
|
||||
|
||||
```sh
|
||||
/usr/bin/python3 -B hardware/PCB/validate_pcb_draft.py
|
||||
/usr/bin/python3 -B hardware/PCB/validation/pcb-routing-check.py
|
||||
```
|
||||
|
||||
The first script exports a **fresh saved-schematic netlist** and checks the actual
|
||||
PCB pad assignments, component linkage, dimensions, layer count, nominal thickness,
|
||||
mounting holes, track/via sizes and KiCad DRC. It never rewrites the PCB or project.
|
||||
It reports the single expected U2 parity warning explicitly, and fails on any
|
||||
other parity issue, DRC violation or unconnected item. Reports are retained in
|
||||
`validation/pcb-parity-drc.json` and `pcb-validation-summary.json`.
|
||||
|
||||
The second independently checks continuous copper/hole/edge geometry and original
|
||||
placement/net/UUID preservation against the initial unrouted snapshot. It needs
|
||||
locally installed NumPy and Shapely in addition to pcbnew. It is not a substitute
|
||||
for current-schematic net validation and is specific to this initial layout.
|
||||
|
||||
Results with KiCad **10.0.6**:
|
||||
|
||||
- **0 physical DRC errors/warnings; 0 unconnected items.**
|
||||
- All saved-schematic pad nets match the PCB; **1 expected U2 footprint parity warning**.
|
||||
- Independent minimum different-net/copper-to-NPTH gap **0.270544 mm**; minimum
|
||||
copper/NPTH-to-board-edge gap **1.900 mm**.
|
||||
- All 9 electrical footprints and 4 board-only mounts retained; positions, outline
|
||||
and RF area revised as documented, with electrical linkage preserved.
|
||||
- Startup `PROPERTY_ENUM` assertions from the local pcbnew binding are still
|
||||
emitted, but the checks complete successfully.
|
||||
|
||||
DRC uses the existing default ignored checks: `missing_courtyard`,
|
||||
`track_not_centered_on_via`, `tuning_profile_track_geometries`,
|
||||
`footprint_filters_mismatch`, `footprint_type_mismatch`. No new exclusions were
|
||||
added to hide problems. **Missing module courtyards therefore remain an explicit
|
||||
manual release gate**. The draft rules do not represent a chosen fabricator's
|
||||
complete capability set. No electrical/hardware/fabrication tests were performed.
|
||||
|
||||
## Regeneration and project settings
|
||||
|
||||
Normal use is to **edit the native PCB in KiCad**, not regenerate it. The optional
|
||||
`generate_pcb_draft.py` recreates the *unrouted* A1 placement and discards manual
|
||||
PCB work only with explicit `--overwrite`; it refuses an open-project lock. A1 was
|
||||
made by mutating the existing board, not running that generator. All A1 saves use
|
||||
`SaveBoard(..., True)` to preserve user project settings. The original A0 creation
|
||||
populated the minimal project with rules; this revision leaves those settings and
|
||||
the schematic unchanged. Refresh device silkscreen and CAD envelope helpers after
|
||||
any intentional regeneration or placement/routing changes.
|
||||
|
||||
`route_pcb_draft.py` requires NumPy and pcbnew. By default it never saves; replacing
|
||||
existing tracks requires explicit `--route --overwrite --replace-routes`. Do not
|
||||
run that over manual routing without a backup. It is a bounded initial-draft
|
||||
router, **not** a general-purpose autorouter. See `validation/pcb-routing-summary.md`
|
||||
for algorithm limits and commands. These scripts do not need downloaded packages
|
||||
or services in the current environment and create no manufacturing outputs.
|
||||
|
||||
Before fabrication: qualify all module pin maps/footprints and connectors,
|
||||
complete assembly courtyards and RF/USB/DE-9 checks, review power/return paths,
|
||||
verify the existing USB power circuit and OLED pull-ups, confirm procurement and
|
||||
case hardware, rerun ERC/DRC, and perform an independent hardware design review.
|
||||
@@ -0,0 +1,32 @@
|
||||
## Adafruit RS-232 Full Pinout Level-Shifter Breakout PCB
|
||||
|
||||
<a href="http://www.adafruit.com/products/5988"><img src="assets/5988.jpg?raw=true" width="500px"><br/>
|
||||
Click here to purchase one from the Adafruit shop</a>
|
||||
|
||||
PCB files for the Adafruit RS-232 Full Pinout Level-Shifter Breakout.
|
||||
|
||||
Format is EagleCAD schematic and board layout
|
||||
* https://www.adafruit.com/product/5988
|
||||
|
||||
### Description
|
||||
|
||||
If you want to interface with telco, retro, or industrial equipment, you'll probably run into RS-232 interfaces. The Adafruit RS232 Full-Pinout Breakout with 8 Channels of UART to RS-232 Level Shifters is your friend in such cases. It gives you 5 input and 3 output channels of level shifting and takes care of the high/negative voltage generation all in a low-cost breakout board. We use the trusty MAX3243 from TI, a classic chip part of the MAX232 lineage, so you know it will work great for all your RS-232 needs, up to 250Kbps.
|
||||
|
||||
RS-232 is what we had before USB: a 9- or 25-pin D-Sub connector that allowed data plus flow control lines. Many folks may remember these interfaces were used for mice, modems, barcode scanners, teletypes, and more. We still find devices sold with RS-232 ports, although many folks use a USB to RS-232 adapter these days.
|
||||
|
||||
If you want to use a microcontroller or microcomputer to chat with an RS-232, then thankfully, all you need is a serial port / UART (something just about any microcontroller has) and a level shifter. The level shifter is required because while most UARTs are 0-3.3V or 0-5V logic level, RS-232 requires +-6 to +-10V, yep the signal voltage goes negative! That means a specialized shifter is required to generate extra high and low voltages and safely convert the logic levels.
|
||||
|
||||
Sure you could buy a raw MAX232 chip and wire up the necessary capacitors, but this board does it all for you and even comes with a DE-9 connector for plugging in directly into your 'client' device. It can run on 3.3V power and logic, which many older chips can't do. It also can do all 8 data pins, so you can use all of the flow control signals like RTS, CTS, DTR, DSR, DCD and RI.
|
||||
|
||||
We also include two separate lines from the MAX3243: 'Valid' and 'Off'. The Valid line output will have logic level high when the chip detects signal voltages from the device its plugged into. So you can use it as a 'connection made' signal. There's also the Off pin, which when set to logic high by the microcontroller will tri-state all the pins for power reduction.
|
||||
|
||||
This breakout comes fully assembled with a UART side for low-voltage power/logic level, and a DE-9F RS-232 side for high-voltage signals. We also include some header so you can solder to a breadboard in a few minutes.
|
||||
|
||||
### License
|
||||
|
||||
Adafruit invests time and resources providing this open source design, please support Adafruit and open-source hardware by purchasing products from [Adafruit](https://www.adafruit.com)!
|
||||
|
||||
Designed by Limor Fried/Ladyada for Adafruit Industries.
|
||||
|
||||
Creative Commons Attribution/Share-Alike, all text above must be included in any redistribution.
|
||||
See license.txt for additional details.
|
||||
|
After Width: | Height: | Size: 340 KiB |
@@ -0,0 +1,753 @@
|
||||
Tactile Switch B3F
|
||||
Through-hole-mounting Switches
|
||||
in a Wide Range of Models:
|
||||
6 × 6 mm, 12 × 12 mm, Side-operated
|
||||
Models, Gold-plated Contacts, and
|
||||
Radial Tape
|
||||
• Extended mechanical/electrical durability: 10 x 106
|
||||
operations for 12 x 12 mm type and 1 x 106
|
||||
operations for the 6 x 6 mm type
|
||||
• Taped radial type, vertical type and high force
|
||||
types are available.
|
||||
• Gold plated models available for increased
|
||||
contact reliability, resistance to corrosive gas and
|
||||
insulation failure prevention for ion migration in
|
||||
harsh environments
|
||||
• B32-series Key Tops mount to models with
|
||||
projected plungers.
|
||||
RoHS Compliant
|
||||
|
||||
■ List of Models
|
||||
|
||||
6 × 6 mm Models
|
||||
Type Contact Plunger Height Operating Plunger Bags
|
||||
material force (OF) color Without ground Minimum With ground Minimum
|
||||
terminal packing unit terminal packing unit
|
||||
Standard: Silver Flat type 4.3 mm 0.98 N {100 gf} lvory B3F-1000 B3F-1100
|
||||
B3F-1000 plated 1.47 N {150 gf} Yellow B3F-1002 B3F-1102
|
||||
Series
|
||||
2.55 N {260 gf} Orange B3F-1005 B3F-1105
|
||||
4.9 N {500 gf} Red B3F-1006 ---
|
||||
5.0 mm 0.98 N {100 gf} Black B3F-1020 B3F-1120
|
||||
1.47 N {150 gf} Gray B3F-1022 B3F-1122
|
||||
2.55 N {260 gf} Pink B3F-1025 B3F-1125
|
||||
4.9 N {500 gf} Blue B3F-1026 ---
|
||||
5.0 mm 0.98 N {100 gf} Black --- B3F-1110
|
||||
(7.5-mm pitch)
|
||||
7.0 mm 0.98 N {100 gf} Black B3F-1060 100 pcs --- 100 pcs
|
||||
1.47 N {150 gf} Yellow B3F-1062 ---
|
||||
9.5 mm 0.98 N {100 gf} Black B3F-1070 ---
|
||||
1.47 N {150 gf} Yellow B3F-1072 ---
|
||||
2.55 N {260 gf} Orange B3F-1075 ---
|
||||
7.3 mm 0.98 N {100 gf} lvory B3F-1050 B3F-1150
|
||||
Projected type
|
||||
1.47 N {150 gf} Yellow B3F-1052 B3F-1152
|
||||
|
||||
2.55 N {260 gf} Orange B3F-1055 B3F-1155
|
||||
|
||||
4.9 N {500 gf} Red B3F-1056 ---
|
||||
|
||||
|
||||
|
||||
|
||||
1
|
||||
B3F B3F
|
||||
|
||||
Type Contact Plunger Height Operating Plunger Bags
|
||||
material force (OF) color Without ground Minimum With ground Minimum
|
||||
terminal packing unit terminal packing unit
|
||||
Side- Silver 3.15 mm 0.98 N {100 gf} lvory --- B3F-3100
|
||||
Flat type
|
||||
operated: plated 1.47 N {150 gf} Yellow --- B3F-3102
|
||||
B3F-3000
|
||||
Series 2.55 N {260 gf} Orange --- B3F-3105
|
||||
3.85 mm 0.98 N {100 gf} Black --- B3F-3120
|
||||
1.47 N {150 gf} Gray --- B3F-3122
|
||||
2.55 N {260 gf} Pink --- B3F-3125
|
||||
Projected type 6.15 mm 0.98 N {100 gf} lvory --- B3F-3150
|
||||
|
||||
1.47 N {150 gf} Yellow --- B3F-3152
|
||||
|
||||
2.55 N {260 gf} Orange --- B3F-3155
|
||||
|
||||
High- Gold 4.3 mm 1.76 N {180 gf} Yellow B3F-1002-G B3F-1102-G
|
||||
Flat type
|
||||
reliability plated
|
||||
gold-plated: 5.0 mm Gray B3F-1022-G B3F-1122-G
|
||||
B3F-1000-G 7.0 mm Yellow B3F-1062-G 100 pcs --- 100 pcs
|
||||
Series (see note)
|
||||
9.5 mm Yellow B3F-1072-G ---
|
||||
(see note)
|
||||
Side- Projected type 7.3 mm Yellow B3F-1052-G ---
|
||||
operated
|
||||
with highly
|
||||
reliable
|
||||
gold-plated:
|
||||
B3F-3000-G
|
||||
Series
|
||||
3.85 mm Gray --- B3F-3122-G
|
||||
Flat type
|
||||
(see note)
|
||||
|
||||
|
||||
|
||||
|
||||
Note: Bulk Packaged, 100 Switches per bag. Order in multiples of the package quantity.
|
||||
12 × 12 mm Models
|
||||
Type Contact Plunger Height Operating Plunger Bags
|
||||
material (or LED color) force (OF) color Without ground Minimum With ground Minimum
|
||||
terminal packing unit terminal packing unit
|
||||
Standard: Silver Flat type 4.3 mm 1.27 N {130 gf} lvory B3F-4000 B3F-4100
|
||||
B3F-4000 plated 2.55 N {260 gf} Yellow B3F-4005 B3F-4105
|
||||
Series
|
||||
Projected type 7.3 mm 1.27 N {130 gf} lvory B3F-4050 B3F-4150
|
||||
2.55 N {260 gf} Yellow B3F-4055 B3F-4155
|
||||
Long durability: Silver Flat type 4.3 mm 1.27 N {130 gf} Blue B3F-5000 B3F-5100
|
||||
100 pcs 100 pcs
|
||||
B3F-5000 Se- plated
|
||||
ries Projected type 7.3 mm Blue B3F-5050 B3F-5150
|
||||
High reliability Gold Flat type 4.3 mm 1.27 N {130 gf} Blue B3F-5001 B3F-5101
|
||||
gold-plated: plated
|
||||
B3F-5001 Projected type 7.3 mm Blue B3F-5051 B3F-5151
|
||||
Series
|
||||
Note: Bulk Packaged, 100 switches per bag. Order in multiples of the package quantity.
|
||||
6 × 6 mm Radial Models (Taping Specifications)
|
||||
Type Contact Plunger Height Operating Plunger Taped Radial
|
||||
material force (OF) color Without ground Minimum With ground Minimum
|
||||
terminal packing unit terminal packing unit
|
||||
Taped Silver Flat type 4.3 mm 0.98 N {100 gf} lvory B3F-6000 B3F-6100
|
||||
Radial: plated 1.47 N {150 gf} Yellow B3F-6002 B3F-6102
|
||||
B3F-6000
|
||||
Series 5.0 mm 0.98 N {100 gf} Black B3F-6020 B3F-6120
|
||||
1,000 pcs 1,000 pcs
|
||||
1.47 N {150 gf} Gray B3F-6022 B3F-6122
|
||||
Projected type 7.3 mm 0.98 N {100 gf} lvory B3F-6050 B3F-6150
|
||||
1.47 N {150 gf} Yellow B3F-6052 B3F-6152
|
||||
Note: The switches are tape packaged in units of 1,000 per package. Order in multiples of the package size. Switches are not sold individually.
|
||||
|
||||
|
||||
|
||||
|
||||
2
|
||||
B3F B3F
|
||||
|
||||
■ Ratings/Characteristics
|
||||
|
||||
Rating (resistive load) 1 to 50 mA, 3 to 24 VDC (B3F-G: 100 μA to 50 mA, 3 to 24 VDC)
|
||||
Minimum applicable load (reference value) 10 μA at 1 VDC (resistive load)
|
||||
Ambient operating temperature -25°C to +70°C at 60%RH max. (with no icing or condensation)
|
||||
Ambient operating humidity 35% to 85% (at +5 to +35°C)
|
||||
Contact form SPST-NO
|
||||
Contact resistance (initial value) 100 mΩ max.
|
||||
Insulation resistance 100 MΩ min. (at 250 VDC with insulation tester)
|
||||
Dielectric strength 500 VAC, 50/60 Hz for 1 min
|
||||
Bounce time 5 ms max.
|
||||
Vibration resistance Malfunction: 10 to 55 Hz, 1.5 mm double amplitude
|
||||
Shock resistance Destruction: 1,000 m/s2 {approx. 100G} max.
|
||||
Malfunction: 100 m/s2 {approx. 10G} max.
|
||||
Durability B3F-1000, B3F-3000, B3F-6000:
|
||||
1,000,000 operations min (OF: 0.98 N {100 gf}) (B3F-1070: 500,000 operations min)
|
||||
300,000 operations min (OF: 1.47 N {150 gf})
|
||||
100,000 operations min (OF: 2.55 N {260 gf})
|
||||
50,000 operations min (OF: 4.9 N {500 gf})
|
||||
B3F-4000:
|
||||
3,000,000 operations min (OF: 1.27 N {130 gf})
|
||||
1,000,000 operations min (OF: 2.55 N {260 gf})
|
||||
B3F-5000/5001:
|
||||
10,000,000 operations min.
|
||||
B3F-G:
|
||||
300,000 operations min.
|
||||
Weight 6 × 6 mm models: approx. 0.25 g
|
||||
12 × 12 mm models (standard types): approx. 0.85 g
|
||||
Radial models: approx. 0.25 g
|
||||
Degree of protection IEC IP00
|
||||
Washing Not possible
|
||||
|
||||
|
||||
■ Operating Characteristics
|
||||
|
||||
6 × 6 mm Models
|
||||
B3F-1000, B3F-3000, B3F-6000 B3F-G
|
||||
Operating force (OF) 0.98 N 1.47 N 2.55 N 4.9 N 1.76 N
|
||||
B3F-1@@0 B3F-1@@2 B3F-1@@5 B3F-10@6 B3F-1@@2-G
|
||||
Item B3F-3@@0 B3F-3@@2 B3F-3@@5 B3F-3@@2-G
|
||||
B3F-6@@0 B3F-6@@2
|
||||
Operating force (OF) 0.98±0.29 N 1.47±0.49 N 2.55±0.69 N 4.9±1.47 N 1.76±0.49 N
|
||||
{100±30 gf} {150±50 gf} {260±70 gf} {500±150 gf} {180±50 gf}
|
||||
Releasing force (RF) 0.2 N {20 gf} min. 0.49 N {50 gf}min. 0.49 N {50 gf}min. 0.7 N {70 gf} min. 0.49 N {50 gf}min.
|
||||
Pretravel (PT) 0.25+0.2/–0.1 mm 0.25+0.2/–0.1 mm
|
||||
|
||||
|
||||
12 × 12 mm Models
|
||||
B3F-4000, B3F-5000, B3F-5001
|
||||
Operating force (OF) 1.27 N 2.55 N
|
||||
B3F-4@@0 B3F-4@@5
|
||||
Item B3F-5@@0
|
||||
B3F-5@@1
|
||||
Operating force (OF) 1.27±0.49 N 2.55±0.69 N
|
||||
{130±50 gf} {260±70 gf}
|
||||
Releasing force (RF) 0.29 N {30 gf} min. 0.49 N {50 gf} min.
|
||||
Pretravel (PT) 0.3+0.2/–0.1 mm
|
||||
|
||||
|
||||
|
||||
|
||||
3
|
||||
B3F B3F
|
||||
|
||||
■ Dimensions (Unit: mm)
|
||||
Note: The numbers used for terminals in the following graphics are indicated in the “Bottom View” diagram below. In this 2 1
|
||||
diagram, the Switch is rotated so that the terminals are on the right and left-hand sides, and the OMRON logo ap- 4 3
|
||||
pears the right way up. (Except Side-operated and Radial Models) (Bottom View)
|
||||
|
||||
6 × 6 mm Models
|
||||
|
||||
Standard, Flat Plunger Type Standard, Flat Plunger Type
|
||||
(without Ground Terminal) (with Ground Terminal)
|
||||
B3F-1000, B3F-1002, B3F-1005, B3F-1006 B3F-1100, B3F-1102, B3F-1105
|
||||
B3F-1020 (See note.), B3F-1022 (See note.), B3F-1120 (See note.), B3F-1122 (See note.)
|
||||
B3F-1025 (See note.), B3F-1026 (See note.) B3F-1125 (See note.)
|
||||
B3F-1002-G, B3F-1022-G (See note.) B3F-1102-G, B3F-1122-G (See note.)
|
||||
PCB Processing Dimensions PCB Processing Dimensions
|
||||
(Reference Only) (Top View) (Reference Only) (Top View)
|
||||
6±0.2 (PCB thickness, t=1.6) 6±0.2 (PCB thickness, t=1.6)
|
||||
|
||||
|
||||
6±0.2 4.5±0.2 6±0.2 4.5±0.2
|
||||
4.5±0.1 4.5±0.1
|
||||
|
||||
4.1±0.1
|
||||
1.5 3.5
|
||||
6.5±0.1 Four, 1±0.1 dia. dia. Five, 1±0.05 dia.
|
||||
3.5
|
||||
dia. (See note.) 6.5±0.1
|
||||
4.3±0.2 3.4
|
||||
(See note.) Terminal Arrangement/ Terminal Arrangement/Internal
|
||||
4.3±0.2 3.4 Internal Connections (Top View) Connections (Top View)
|
||||
3.5
|
||||
4 3 0.3
|
||||
3.5 4 3
|
||||
0.3 0.7
|
||||
0.7 0.7
|
||||
6.5±0.5 6.5±0.5
|
||||
0.3
|
||||
7.7±0.5 0.7 0.7 2 1 7.7±0.5 2 1
|
||||
Note: The height of B3F-1120, B3F-1122, and 5
|
||||
Note: The height of B3F-1020, B3F-1022, B3F-1025,
|
||||
and B3F-1026 is 5±0.2 mm. B3F-1125 is 5±0.2 mm.
|
||||
|
||||
|
||||
|
||||
Standard, Flat Plunger Type Standard, Flat Plunger Type
|
||||
(with Ground Terminal, Pitch: 7.5 mm) (without Ground Terminal)
|
||||
B3F-1110 B3F-1060, B3F-1062, B3F-1062-G
|
||||
PCB Processing Dimensions PCB Processing Dimensions
|
||||
(Reference Only) (Top View) (Reference Only) (Top View)
|
||||
(PCB thickness, t=1.6) (PCB thickness, t=1.6)
|
||||
6±0.2
|
||||
6±0.2
|
||||
|
||||
6±0.2 4.5±0.1
|
||||
4.5±0.2 4.5±0.1
|
||||
6±0.2 4.5±0.2
|
||||
4.1±0.1
|
||||
|
||||
Four, 3.5
|
||||
1.5 1.2±0.05 dia. dia.
|
||||
1±0.05 dia. 6.5±0.1
|
||||
Four,
|
||||
3.5 7.5±0.1 0.5 max. 1±0.05 dia.
|
||||
dia.
|
||||
7±0.2
|
||||
Terminal Arrangement/Internal 3.4
|
||||
5±0.2
|
||||
3.4
|
||||
Connections (Top View) Terminal Arrangement/
|
||||
(1.8)
|
||||
3.5
|
||||
4 3 0.3
|
||||
Internal Connections
|
||||
3.5 (Top View)
|
||||
0.3 6.5±0.5
|
||||
7.7±0.5 0.7 0.7
|
||||
4 3
|
||||
0.7 0.7 0.7 2 1
|
||||
7.5±0.5
|
||||
0.3 5
|
||||
9±0.5
|
||||
2 1
|
||||
|
||||
|
||||
Note: Unless otherwise specified, a tolerance of ±0.4 mm applies to all dimensions. No terminal numbers are indicated on the Switches.
|
||||
|
||||
|
||||
|
||||
|
||||
4
|
||||
B3F B3F
|
||||
|
||||
|
||||
Standard, Flat Plunger Type Standard, Projected Plunger Type
|
||||
(without Ground Terminal) (without Ground Terminal)
|
||||
B3F-1070, B3F-1072, B3F-1075, B3F-1072-G B3F-1050, B3F-1052
|
||||
B3F-1055, B3F-1056
|
||||
PCB Processing Dimensions B3F-1052-G PCB Processing Dimensions
|
||||
(Reference Only) (Top View) (Reference Only) (Top View)
|
||||
(PCB thickness, t=1.6) (PCB thickness, t=1.6)
|
||||
6±0.2 6±0.2
|
||||
|
||||
|
||||
6±0.2 4.5±0.2 4.5±0.1 6±0.2 4.5±0.2 4.5±0.2
|
||||
|
||||
|
||||
3.5
|
||||
dia. 6.5±0.1 Four,
|
||||
3 6.5±0.1 Four, 2.4 × 2.4±0.1 3.5 dia. 1±0.05 dia.
|
||||
dia. 1±0.05 .dia
|
||||
|
||||
0.5 max. Terminal Arrangement/Internal 1.8±0.1 Terminal Arrangement/Internal
|
||||
9.5 Connections (Top View) Connections (Top View)
|
||||
7.3±0.2
|
||||
3.4 4.3±0.2 3.4
|
||||
4 3 4 3
|
||||
(1.8)
|
||||
3.5 3.5
|
||||
0.3 0.3
|
||||
6.5±0.5 2 1 6.5±0.5 2 1
|
||||
7.7±0.5 0.7 0.7 0.7 0.7
|
||||
7.7±0.5
|
||||
|
||||
|
||||
|
||||
Standard, Projected Plunger Type Side-operated, Flat Plunger Type
|
||||
(with Ground Terminal) B3F-3100, B3F-3102, B3F-3105
|
||||
B3F-1150, B3F-1152, B3F-1155
|
||||
|
||||
PCB Processing Dimensions PCB Processing Dimensions
|
||||
(Reference Only) (Top View) (Reference Only) (Top View)
|
||||
(PCB thickness, t=1.6) 7.3 (PCB thickness, t=1.6)
|
||||
6±0.2
|
||||
Two, 1.5 ±0.05 dia.
|
||||
6.25
|
||||
6±0.2 4.5±0.2 4.5±0.1
|
||||
2.5±0.1
|
||||
4.1±0.1
|
||||
3.5 dia.
|
||||
1.5 Five, 1 ±0.05 dia. 4.5±0.1
|
||||
2.4 × 2.4±0.1 3.5 dia. 7±0.1 Two,
|
||||
6.5±0.1 1±0.05 dia.
|
||||
7.4
|
||||
1.8±0.1 Terminal Arrangement/ Terminal Arrangement/
|
||||
4
|
||||
7.3±0.2 Internal Connections Internal Connections
|
||||
4.3±0.2 3.4 (Top View) (Top View)
|
||||
3.5
|
||||
4 3 4
|
||||
3.5 0.3 3
|
||||
0.7 0.3 1
|
||||
0.3 4.5±0.5 1 2
|
||||
2 1 2.25
|
||||
0.7 0.7 0.7 7±0.5 2.5±0.5
|
||||
6.5±0.5 5 3.15±0.2
|
||||
0.3 8.7
|
||||
7.7±0.5
|
||||
|
||||
|
||||
|
||||
Side-operated, Flat Plunger Type (Height: 3.85 mm) Side-operated, Projected Plunger Type
|
||||
B3F-3120, B3F-3122, B3F-3125, B3F-3122-G B3F-3150, B3F-3152, B3F-3155
|
||||
PCB Processing Dimensions PCB Processing Dimensions
|
||||
7.3 7.3 (Reference Only) (Top View)
|
||||
(Reference Only) (Top View)
|
||||
(PCB thickness, t=1.6) (PCB thickness, t=1.6)
|
||||
Two, 1.5 ±0.05 dia. 6.25 Two, 1.5 ±0.05 dia.
|
||||
6.25
|
||||
|
||||
|
||||
2.5±0.1 2.5±0.1
|
||||
|
||||
3.5 dia.
|
||||
4.5±0.1 4.5±0.1
|
||||
3.5 dia.
|
||||
7±0.1 Two, 7±0.1 Two,
|
||||
1±0.05 dia. 1±0.05 dia.
|
||||
7.4 Terminal Arrangement/ 7.4 2.4 × 2.4±0.1 Terminal Arrangement/
|
||||
4 Internal Connections Internal Connections
|
||||
4
|
||||
(Top View) (Top View)
|
||||
3.5
|
||||
4 3.5 4
|
||||
3 3
|
||||
0.3
|
||||
0.7 0.3 1 0.3
|
||||
4.5±0.5 1 2 0.7 0.3 1 1 2
|
||||
4.5±0.5 1.8±0.1
|
||||
2.25
|
||||
7±0.5 2.5±0.5 2.25
|
||||
3.85±0.2 7±0.5 2.5±0.5
|
||||
8.7 6.15±0.2
|
||||
8.7
|
||||
|
||||
Note: Unless otherwise specified, a tolerance of ±0.4 mm applies to all dimensions. No terminal numbers are indicated on the Switches.
|
||||
|
||||
|
||||
|
||||
|
||||
5
|
||||
B3F B3F
|
||||
|
||||
12 × 12 mm Models
|
||||
Standard, Long-durability, Standard, Long-durability,
|
||||
and High-reliability Models and High-reliability Models
|
||||
Flat Plunger Type Flat Plunger Type
|
||||
(without Ground Terminal) (with Ground Terminal)
|
||||
B3F-4000, B3F-4005, B3F-4100, B3F-4105,
|
||||
B3F-5000, B3F-5001 B3F-5100, B3F-5101
|
||||
PCB Processing Dimensions PCB Processing Dimensions
|
||||
(Reference Only) (Top View) (Reference Only) (Top View)
|
||||
(PCB thickness, t=1.6) (PCB thickness, t=1.6)
|
||||
Two, 1.8±0.05 dia.
|
||||
Two, 1.8±0.05 dia. (for positioning boss)
|
||||
12±0.2 (for positioning boss) 12±0.2
|
||||
|
||||
|
||||
5±0.1 9±0.1
|
||||
5±0.1 9±0.1
|
||||
12±0.2 5±0.2 12±0.2 5±0.2
|
||||
6.9±0.1
|
||||
|
||||
Five,
|
||||
Four, 1.2±0.05 dia.
|
||||
12.5±0.1 1.2±0.05 dia.
|
||||
1.6 12.5±0.1
|
||||
|
||||
|
||||
7.1 dia.
|
||||
Terminal Arrangement/ Terminal Arrangement/
|
||||
7.1 dia.
|
||||
Internal Connections Internal Connections
|
||||
4.3±0.2
|
||||
(Top View) (Top View)
|
||||
3.5 4.3±0.2 3.5
|
||||
3.5 4 3 4 3
|
||||
3.5
|
||||
1.6 dia. 1.6 dia.
|
||||
0.3 1 1 0.9 0.3 1 1
|
||||
12.5±0.5 9±0.1 2 1 12.5±0.5 6.9 2 1
|
||||
13.8±0.5 13.8±0.5 9±0.1 5
|
||||
|
||||
|
||||
|
||||
|
||||
Standard, Long-durability, Standard, Long-durability,
|
||||
and High-reliability Models and High-reliability Models
|
||||
Projected Plunger Type Projected Plunger Type
|
||||
(without Ground Terminal) (with Ground Terminal)
|
||||
B3F-4050, B3F-4055, B3F-4150, B3F-4155,
|
||||
B3F-5050, B3F-5051 PCB Processing Dimensions B3F-5150, B3F-5151 PCB Processing Dimensions
|
||||
(Reference Only) (Top View) (Reference Only) (Top View)
|
||||
(PCB thickness, t=1.6) (PCB thickness, t=1.6)
|
||||
12±0.2 Two, 1.8±0.05 dia.
|
||||
Two, 1.8±0.05 dia.
|
||||
(for positioning boss) (for positioning boss)
|
||||
12±0.2
|
||||
|
||||
12±0.2 5±0.2
|
||||
5±0.1 9±0.1
|
||||
5±0.1 9±0.1
|
||||
12±0.2 5±0.2
|
||||
6.9±0.1
|
||||
|
||||
1.6 Five,
|
||||
Four, 1.2±0.05 dia.
|
||||
12.5±0.1 1.2±0.05 dia.
|
||||
@3.8±0.1 12.5±0.1
|
||||
@3.8±0.1
|
||||
7.1 dia. 7.1 dia.
|
||||
|
||||
Terminal Arrangement/ 1.8±0.2 Terminal Arrangement/
|
||||
1.8±0.2
|
||||
Internal Connections 7.3±0.2 Internal Connections
|
||||
7.3±0.2
|
||||
4.3±0.2 3.5 (Top View) 4.3±0.2 3.5 (Top View)
|
||||
3.5 4 3 3.5 4 3
|
||||
|
||||
1.6 dia. 1.6 dia.
|
||||
0.3 1 1 0.9 0.3 1 1
|
||||
12.5±0.5 9±0.1 2 1 12.5±0.5 6.9 2 1
|
||||
13.8±0.5 13.8±0.5 9±0.1
|
||||
5
|
||||
|
||||
|
||||
|
||||
|
||||
Note: Unless otherwise specified, a tolerance of ±0.4 mm applies to all dimensions.
|
||||
|
||||
|
||||
|
||||
|
||||
6
|
||||
B3F B3F
|
||||
|
||||
Note: The numbers used for terminals in the following graphics are indicated in the “Bottom View” diagram below. In this 1
|
||||
diagram, the Switch is rotated so that the terminals are on the right and left-hand sides, and the OMRON logo appears
|
||||
the right way up.
|
||||
2
|
||||
6 mm × 6 mm Radial Types (Taping Specifications): Sold in Units of 1,000 Switches
|
||||
(Bottom View)
|
||||
|
||||
|
||||
Flat Plunger Type 6±0.3 Surface B
|
||||
(without Ground Terminal)
|
||||
B3F-6000, B3F-6002 6±0.3
|
||||
12.7±1
|
||||
6.35±1
|
||||
2 max.
|
||||
3.5 dia. Surface A
|
||||
|
||||
0.5 max.
|
||||
Support
|
||||
0.9 tape 9
|
||||
(1) 4.3 0.5 max. +1
|
||||
3.4 180
|
||||
|
||||
6
|
||||
0.2 max. 9±0.5
|
||||
0.5
|
||||
+1
|
||||
5-0.2
|
||||
+0.8 0.3 18-0.5
|
||||
|
||||
4 dia. ±0.2
|
||||
Terminal Arrangement
|
||||
PCB Processing Dimensions /Internal Connections
|
||||
12.7±0.3 Carrier tape
|
||||
(Reference Only) (Top View) (Top View)
|
||||
Note: The tape is random
|
||||
(PCB thickness, t=1.6)
|
||||
between surface A
|
||||
and surface B.
|
||||
+0.1 2 1
|
||||
Two, 1 0 dia. 5±0.1
|
||||
|
||||
|
||||
|
||||
|
||||
Flat Plunger Type 6±0.3 12.7±1
|
||||
6.35±1
|
||||
(with Ground Terminal) 2 max.
|
||||
B3F-6100, B3F-6102 6±0.3 2
|
||||
|
||||
|
||||
3.5 dia.
|
||||
Support 11
|
||||
0.5 max. tape 0.5 max.
|
||||
20±0.5
|
||||
0.9
|
||||
(1) 4.3 3.4
|
||||
6
|
||||
9±0.5
|
||||
3.1
|
||||
+1
|
||||
0.2 dia. 18-0.5
|
||||
0.5 5±0.2 0.3
|
||||
+0.8 4±0.2 dia.
|
||||
5-0.2 0.6
|
||||
|
||||
|
||||
12.7±0.3 Carrier tape
|
||||
PCB Processing Dimensions Terminal Arrangement
|
||||
(Reference Only) (Top View) /Internal Connections
|
||||
(PCB thickness, t=1.6) (Top View)
|
||||
|
||||
|
||||
|
||||
+0.1 2 1
|
||||
Three, 1 0 dia. 5±0.1 3
|
||||
5±0.1
|
||||
|
||||
|
||||
|
||||
Flat Plunger Type 6±0.3 Surface B
|
||||
(without Ground Terminal) 12.7±1
|
||||
B3F-6020, B3F-6022 6.35±1
|
||||
2 max.
|
||||
6±0.3
|
||||
|
||||
|
||||
3.5 dia. Surface A
|
||||
Support 9
|
||||
0.5 max. tape 0.5 max. +1
|
||||
180
|
||||
0.9
|
||||
(1) 5
|
||||
3.4 6
|
||||
9±0.5
|
||||
+1
|
||||
0.2 max. 18-0.5
|
||||
0.5
|
||||
+0.8
|
||||
5-0.2 0.3 4±0.2 dia.
|
||||
|
||||
Terminal Arrangement Carrier tape
|
||||
Note: The tape is random PCB Processing Dimensions /Internal Connections
|
||||
12.7±0.3
|
||||
|
||||
between surface A (Reference Only) (Top View) (Top View)
|
||||
and surface B. (PCB thickness, t=1.6)
|
||||
|
||||
|
||||
+0.1 2 1
|
||||
Two, 1 0 dia. 5±0.1
|
||||
|
||||
|
||||
|
||||
Note: Unless otherwise specified, a tolerance of ±0.4 mm applies to all dimensions. No terminal numbers are indicated on the Switches.
|
||||
|
||||
|
||||
|
||||
7
|
||||
B3F B3F
|
||||
|
||||
|
||||
Flat Plunger Type 6±0.3
|
||||
(with Ground Terminal)
|
||||
B3F-6120, B3F-6122 6±0.3 2
|
||||
12.7±1
|
||||
6.35±1
|
||||
2 max.
|
||||
|
||||
3.5 dia.
|
||||
0.5 max.
|
||||
|
||||
0.9 Support 11
|
||||
(1) 5
|
||||
3.4 tape 0.5 max.
|
||||
20±0.5
|
||||
3.1
|
||||
0.2 max. 6
|
||||
9±0.5
|
||||
0.5 5±0.2 0.3
|
||||
+1
|
||||
+0.8
|
||||
5-0.2 0.6 18-0.5
|
||||
|
||||
4±0.2 dia.
|
||||
PCB Processing Dimensions Terminal Arrangement
|
||||
(Reference Only) (Top View) /Internal Connections Carrier tape
|
||||
12.7±0.3
|
||||
(PCB thickness, t=1.6) (Top View)
|
||||
|
||||
|
||||
+0.1 2 1
|
||||
Three, 1 0 dia. 5±0.1 3
|
||||
5±0.1
|
||||
|
||||
|
||||
|
||||
|
||||
Projected Plunger Type 6±0.3 Surface B
|
||||
(without Ground Terminal)
|
||||
B3F-6050, B3F-6052
|
||||
6±0.3
|
||||
|
||||
12.7±1
|
||||
3.5 dia. Surface A 6.35±1
|
||||
2.4 2 max.
|
||||
|
||||
|
||||
0.5 max.
|
||||
7.3 0.9
|
||||
(1) 3.4
|
||||
Support 9
|
||||
tape 0.5 dia. +1
|
||||
18 0
|
||||
0.2 max.
|
||||
0.5
|
||||
+0.8 0.3 6
|
||||
5-0.2 9±0.5
|
||||
+1
|
||||
18-0.5
|
||||
PCB Processing Dimensions Terminal Arrang ement
|
||||
(Reference Only) (Top View) /Internal Connections 4±0.2 dia.
|
||||
(PCB thickness, t=1.6) (Top View)
|
||||
12.7±0.3 Carrier tape
|
||||
+0.1
|
||||
Two, 1 0 dia. 5±0.1 2 1
|
||||
|
||||
|
||||
Note: The tape is random between surface A
|
||||
and surface B.
|
||||
|
||||
|
||||
Projected Plunger Type 6±0.3
|
||||
(with Ground Terminal)
|
||||
B3F-6150, B3F-6152
|
||||
6±0.3 2 12.7±1
|
||||
6.35±1
|
||||
2 max.
|
||||
|
||||
3.5 dia. 2.4
|
||||
|
||||
|
||||
0.5 max.
|
||||
7.3 0.9
|
||||
(1) 3.4 Support 11
|
||||
tape 0.5 max.
|
||||
20±0.5
|
||||
3.1
|
||||
0.2 max.
|
||||
6
|
||||
9±0.5
|
||||
0.5 5±0.2 0.3
|
||||
+0.8 +1
|
||||
5 -0.2 0.6 18-0.5
|
||||
|
||||
|
||||
Terminal Arrangement 4±0.2 dia.
|
||||
PCB Processing Dimensions
|
||||
(Reference Only) (Top View) /Internal Connections
|
||||
(PCB thickness, t=1.6) (Top View) 12.7±0.3 Carrier tape
|
||||
|
||||
|
||||
|
||||
+0.1
|
||||
Three, 1 0 dia. 5±0.1 2 1
|
||||
3
|
||||
5±0.1
|
||||
|
||||
|
||||
|
||||
Note: Unless otherwise specified, a tolerance of ±0.4 mm applies to all dimensions. No terminal numbers are indicated on the Switches.
|
||||
|
||||
|
||||
|
||||
8
|
||||
B3F B3F
|
||||
|
||||
■ Key Tops
|
||||
B32-series Key Tops are available for projected plungers. Refer to the Datasheet of B32 for details.
|
||||
|
||||
■ Precautions
|
||||
Be sure to read the safety precautions common to all Tactile Switches for correct use.
|
||||
|
||||
|
||||
|
||||
|
||||
• Application examples provided in this document are for reference only. In actual applications, confirm equipment functions and safety before using the product.
|
||||
• Consult your OMRON representative before using the product under conditions which are not described in the manual or applying the product to nuclear control systems, railroad
|
||||
systems, aviation systems, vehicles, combustion systems, medical equipment, amusement machines, safety equipment, and other systems or equipment that may have a serious
|
||||
influence on lives and property if used improperly. Make sure that the ratings and performance characteristics of the product provide a margin of safety for the system or
|
||||
equipment, and be sure to provide the system or equipment with double safety mechanisms.
|
||||
|
||||
|
||||
Note: Do not use this document to operate the Unit.
|
||||
|
||||
OMRON Corporation
|
||||
Electronic and Mechanical Components Company Contact: www.omron.com/ecb Cat. No. A070-E1-08
|
||||
1014(0207)(O)
|
||||
|
||||
|
||||
|
||||
9
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 Adafruit Industries
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,2 @@
|
||||
# Adafruit_CAD_Parts
|
||||
STEP, Fusion 360, and STL files for various boards, components and parts
|
||||
@@ -0,0 +1,86 @@
|
||||
"""Read pinned STEP B-rep; write reproducible solid/face measurements beside it."""
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
from OCP.STEPControl import STEPControl_Reader
|
||||
from OCP.IFSelect import IFSelect_RetDone
|
||||
from OCP.TopExp import TopExp_Explorer
|
||||
from OCP.TopAbs import TopAbs_SOLID, TopAbs_FACE
|
||||
from OCP.TopoDS import TopoDS
|
||||
from OCP.Bnd import Bnd_Box
|
||||
from OCP.BRepBndLib import BRepBndLib
|
||||
from OCP.BRepAdaptor import BRepAdaptor_Surface
|
||||
from OCP.GeomAbs import GeomAbs_Cylinder, GeomAbs_Plane
|
||||
from OCP.BRepCheck import BRepCheck_Analyzer
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
|
||||
def bounds(s):
|
||||
b = Bnd_Box()
|
||||
BRepBndLib.AddOptimal_s(s, b, False, False)
|
||||
return list(b.Get())
|
||||
|
||||
def xyz(p):
|
||||
return [p.X(), p.Y(), p.Z()]
|
||||
|
||||
r = STEPControl_Reader()
|
||||
assert r.ReadFile(str(HERE / '5988 RS-232 Level Shifter.step')) == IFSelect_RetDone
|
||||
r.TransferRoots()
|
||||
entities = dict(re.findall(r'#(\d+)\s*=\s*(.*?);', (HERE / '5988 RS-232 Level Shifter.step').read_text(), re.S))
|
||||
styles = {}
|
||||
for text in entities.values():
|
||||
if text.startswith('STYLED_ITEM('):
|
||||
refs = re.findall(r'#(\d+)', text)
|
||||
styles[refs[-1]] = refs[:-1]
|
||||
|
||||
def colors(refs, seen=None):
|
||||
seen = set() if seen is None else seen
|
||||
result = []
|
||||
for ref in refs:
|
||||
if ref in seen:
|
||||
continue
|
||||
seen.add(ref)
|
||||
text = entities[ref]
|
||||
if text.startswith('COLOUR_RGB('):
|
||||
result.append(text.replace('\n', ''))
|
||||
else:
|
||||
result.extend(colors(re.findall(r'#(\d+)', text), seen))
|
||||
return result
|
||||
|
||||
e = TopExp_Explorer(r.OneShape(), TopAbs_SOLID)
|
||||
out = []
|
||||
while e.More():
|
||||
s = TopoDS.Solid_s(e.Current())
|
||||
item = dict(id=len(out)+1, bounds=bounds(s), valid=BRepCheck_Analyzer(s).IsValid(), faces=[])
|
||||
f = TopExp_Explorer(s, TopAbs_FACE)
|
||||
while f.More():
|
||||
face = TopoDS.Face_s(f.Current())
|
||||
a = BRepAdaptor_Surface(face)
|
||||
# These unique bodies retain CLOSED_SHELL face order on this pinned import.
|
||||
shell_id = {1: '14428', 12: '14431', 13: '14432', 14: '14433'}.get(item['id'])
|
||||
refs = re.findall(r'#(\d+)', entities[shell_id]) if shell_id else []
|
||||
step_id = refs[len(item['faces'])] if refs else '0'
|
||||
if refs:
|
||||
surface_id = re.findall(r'#(\d+)', entities[step_id])[-1]
|
||||
surface = entities[surface_id]
|
||||
expected = str(a.GetType()).split('_')[-1].upper()
|
||||
assert surface.startswith({'PLANE': 'PLANE(', 'CYLINDER': 'CYLINDRICAL_SURFACE(', 'CONE': 'CONICAL_SURFACE(', 'TORUS': 'TOROIDAL_SURFACE('}[expected])
|
||||
if a.GetType() == GeomAbs_Cylinder:
|
||||
assert abs(float(surface.rsplit(',', 1)[1].rstrip(')')) - a.Cylinder().Radius()) < 1e-8
|
||||
row = dict(id=len(item['faces'])+1, bounds=bounds(face), type=str(a.GetType()), orientation=str(face.Orientation()))
|
||||
row.update(step_id=step_id, colors=colors(styles.get(step_id, [])))
|
||||
if a.GetType() == GeomAbs_Cylinder:
|
||||
c = a.Cylinder()
|
||||
row.update(radius=c.Radius(), location=xyz(c.Location()), axis=xyz(c.Axis().Direction()))
|
||||
elif a.GetType() == GeomAbs_Plane:
|
||||
p = a.Plane()
|
||||
row.update(location=xyz(p.Location()), normal=xyz(p.Axis().Direction()))
|
||||
item['faces'].append(row)
|
||||
f.Next()
|
||||
if shell_id:
|
||||
assert len(item['faces']) == len(refs)
|
||||
out.append(item)
|
||||
e.Next()
|
||||
(HERE / 'geometry.json').write_text(json.dumps(out, indent=2)+'\n')
|
||||
for s in out:
|
||||
print(s['id'], 'valid', s['valid'], 'bounds', [round(v,6) for v in s['bounds']], 'faces', len(s['faces']))
|
||||
@@ -0,0 +1,207 @@
|
||||
{
|
||||
"front_flange_exposed_faces": {
|
||||
"solid": 12,
|
||||
"faces": [
|
||||
8,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14
|
||||
],
|
||||
"bounds": [
|
||||
0.47499999999999787,
|
||||
27.006999999999998,
|
||||
1.5726202399843388,
|
||||
31.275000000000002,
|
||||
27.407,
|
||||
14.1226202399843
|
||||
],
|
||||
"dimensions": [
|
||||
30.800000000000004,
|
||||
0.40000000000000213,
|
||||
12.549999999999962
|
||||
]
|
||||
},
|
||||
"shell_straight_outer_surface": {
|
||||
"solid": 12,
|
||||
"faces": [
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25
|
||||
],
|
||||
"bounds": [
|
||||
8.035981107410171,
|
||||
28.006999999999998,
|
||||
3.89762023998434,
|
||||
23.71401889258985,
|
||||
32.607,
|
||||
11.79762023998434
|
||||
],
|
||||
"dimensions": [
|
||||
15.678037785179677,
|
||||
4.600000000000001,
|
||||
7.9
|
||||
]
|
||||
},
|
||||
"shell_outer_including_root_and_lip": {
|
||||
"solid": 12,
|
||||
"faces": [
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
81,
|
||||
82,
|
||||
83,
|
||||
84,
|
||||
85,
|
||||
86,
|
||||
87,
|
||||
88,
|
||||
103,
|
||||
104,
|
||||
105,
|
||||
106,
|
||||
107,
|
||||
108,
|
||||
109,
|
||||
110
|
||||
],
|
||||
"bounds": [
|
||||
7.4359811074101705,
|
||||
27.406999999999996,
|
||||
3.29762023998434,
|
||||
24.31401889258985,
|
||||
33.207,
|
||||
12.397620239984342
|
||||
],
|
||||
"dimensions": [
|
||||
16.87803778517968,
|
||||
5.800000000000004,
|
||||
9.100000000000001
|
||||
]
|
||||
},
|
||||
"left_front_hex_post": {
|
||||
"solid": 12,
|
||||
"faces": [
|
||||
53,
|
||||
54,
|
||||
55,
|
||||
56,
|
||||
57,
|
||||
58,
|
||||
59,
|
||||
60,
|
||||
61,
|
||||
89
|
||||
],
|
||||
"bounds": [
|
||||
0.7191887617277004,
|
||||
27.407,
|
||||
5.547620239984319,
|
||||
6.030811238272291,
|
||||
32.207,
|
||||
10.147620239984366
|
||||
],
|
||||
"dimensions": [
|
||||
5.31162247654459,
|
||||
4.800000000000001,
|
||||
4.600000000000048
|
||||
]
|
||||
},
|
||||
"right_front_hex_post": {
|
||||
"solid": 12,
|
||||
"faces": [
|
||||
62,
|
||||
63,
|
||||
64,
|
||||
65,
|
||||
66,
|
||||
67,
|
||||
68,
|
||||
69,
|
||||
70,
|
||||
90
|
||||
],
|
||||
"bounds": [
|
||||
25.71918876172773,
|
||||
27.407,
|
||||
5.547620239984328,
|
||||
31.030811238272328,
|
||||
32.207,
|
||||
10.14762023998434
|
||||
],
|
||||
"dimensions": [
|
||||
5.311622476544599,
|
||||
4.800000000000001,
|
||||
4.600000000000012
|
||||
]
|
||||
},
|
||||
"front_lock_axes": [
|
||||
{
|
||||
"id": 60,
|
||||
"bounds": [
|
||||
1.875,
|
||||
27.407,
|
||||
6.34762023998434,
|
||||
4.875,
|
||||
32.007,
|
||||
9.34762023998434
|
||||
],
|
||||
"type": "GeomAbs_SurfaceType.GeomAbs_Cylinder",
|
||||
"orientation": "TopAbs_Orientation.TopAbs_REVERSED",
|
||||
"step_id": "13822",
|
||||
"colors": [
|
||||
"COLOUR_RGB('Opaque(245,245,246)',0.96078431372549,0.96078431372549,0.964705882352941)"
|
||||
],
|
||||
"radius": 1.5,
|
||||
"location": [
|
||||
3.375,
|
||||
32.207,
|
||||
7.84762023998434
|
||||
],
|
||||
"axis": [
|
||||
0.0,
|
||||
-1.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 69,
|
||||
"bounds": [
|
||||
26.875,
|
||||
27.407,
|
||||
6.34762023998434,
|
||||
29.875,
|
||||
32.007,
|
||||
9.34762023998434
|
||||
],
|
||||
"type": "GeomAbs_SurfaceType.GeomAbs_Cylinder",
|
||||
"orientation": "TopAbs_Orientation.TopAbs_REVERSED",
|
||||
"step_id": "13831",
|
||||
"colors": [
|
||||
"COLOUR_RGB('Opaque(245,245,246)',0.96078431372549,0.96078431372549,0.964705882352941)"
|
||||
],
|
||||
"radius": 1.5,
|
||||
"location": [
|
||||
28.375,
|
||||
32.207,
|
||||
7.84762023998434
|
||||
],
|
||||
"axis": [
|
||||
0.0,
|
||||
-1.0,
|
||||
0.0
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
[
|
||||
{
|
||||
"file": "5988 RS-232 Level Shifter.step",
|
||||
"url": "https://raw.githubusercontent.com/adafruit/Adafruit_CAD_Parts/5b8a21fb5e2e48478faf8901077be812f5224066/5988%20RS-232%20Level%20Shifter/5988%20RS-232%20Level%20Shifter.step",
|
||||
"sha256": "8eab9dc591a985fc987ceec2d7534e5e259263c73bebc64c0c3cd847e135906f"
|
||||
},
|
||||
{
|
||||
"file": "LICENSE",
|
||||
"url": "https://raw.githubusercontent.com/adafruit/Adafruit_CAD_Parts/5b8a21fb5e2e48478faf8901077be812f5224066/LICENSE",
|
||||
"sha256": "f60436644e066301d4c2e8f1f0674a157b1f60e4fd5586717b99dde255cc18cd"
|
||||
},
|
||||
{
|
||||
"file": "README.md",
|
||||
"url": "https://raw.githubusercontent.com/adafruit/Adafruit_CAD_Parts/5b8a21fb5e2e48478faf8901077be812f5224066/README.md",
|
||||
"sha256": "9ed9beac7600623b7b9bd0d02e1fdcd86610ce22966723179e6c6b618e399dce"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,46 @@
|
||||
"""Validate registration and selected face subsets; no board writes."""
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
D = json.loads((HERE / 'geometry.json').read_text())
|
||||
assert len(D) == 15 and all(s['valid'] for s in D)
|
||||
for source in json.loads((HERE / 'sources.json').read_text()):
|
||||
assert hashlib.sha256((HERE / source['file']).read_bytes()).hexdigest() == source['sha256']
|
||||
board = HERE.parent / 'Adafruit RS-232 Full Pinout Level-Shifter Breakout.brd'
|
||||
assert hashlib.sha256(board.read_bytes()).hexdigest() == '6a7b35ef909f5a2d243c326c34db3957aaa01275bf2aac8e0c749ff18162e870'
|
||||
root = ET.parse(board).getroot()
|
||||
b = root.find('drawing/board')
|
||||
jp = b.find("elements/element[@name='JP2']")
|
||||
pkg = b.find("libraries/library[@name='microbuilder']/packages/package[@name='1X12_ROUND_76MIL']")
|
||||
for pad in pkg.findall('pad'):
|
||||
x, y = float(jp.get('x')) + float(pad.get('x')), float(jp.get('y')) + float(pad.get('y'))
|
||||
assert any(abs(f.get('radius', 0)-0.5)<1e-8 and abs(f['location'][0]-x)<1e-8 and abs(f['location'][1]-y)<1e-8 for f in D[0]['faces'])
|
||||
for x in [3.81, 27.94]:
|
||||
assert any(abs(f.get('radius',0)-1.5)<1e-8 and abs(f['location'][0]-x)<1e-8 and abs(f['location'][1]-11.43)<1e-8 for f in D[0]['faces'])
|
||||
assert all(abs(a-b)<1e-8 for a,b in zip(D[0]['bounds'], [0,0,0,31.75,29.337,1.57]))
|
||||
|
||||
subsets = {
|
||||
'front_flange_exposed_faces': [8,11,12,13,14],
|
||||
'shell_straight_outer_surface': list(range(18,26)),
|
||||
'shell_outer_including_root_and_lip': list(range(18,26))+list(range(81,89))+list(range(103,111)),
|
||||
'left_front_hex_post': list(range(53,62))+[89],
|
||||
'right_front_hex_post': list(range(62,71))+[90],
|
||||
}
|
||||
result = {}
|
||||
for name, ids in subsets.items():
|
||||
faces = [D[11]['faces'][i-1] for i in ids]
|
||||
assert all(any('Opaque(245,245,246)' in c for c in f['colors']) for f in faces)
|
||||
bounds = [min(f['bounds'][i] for f in faces) for i in range(3)] + [max(f['bounds'][i] for f in faces) for i in range(3,6)]
|
||||
result[name] = dict(solid=12, faces=ids, bounds=bounds, dimensions=[bounds[i+3]-bounds[i] for i in range(3)])
|
||||
left, right = [D[11]['faces'][i-1] for i in [60,69]]
|
||||
assert abs(right['location'][0]-left['location'][0]-25)<1e-8
|
||||
assert left['radius'] == right['radius'] == 1.5
|
||||
result['front_lock_axes'] = [left, right]
|
||||
(HERE / 'measurements.json').write_text(json.dumps(result,indent=2)+'\n')
|
||||
print('PASS: source hashes, 15 valid solids, all 12 Eagle header centres, PCB bounds, mounting holes, metal-appearance subsets and front lock cylinders.')
|
||||
for name, row in result.items():
|
||||
if isinstance(row,dict):
|
||||
print(name, [round(v,6) for v in row['bounds']], 'size', [round(v,6) for v in row['dimensions']])
|
||||
@@ -0,0 +1,359 @@
|
||||
Creative Commons Legal Code
|
||||
|
||||
Attribution-ShareAlike 3.0 Unported
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN
|
||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||
REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR
|
||||
DAMAGES RESULTING FROM ITS USE.
|
||||
|
||||
License
|
||||
|
||||
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE
|
||||
COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY
|
||||
COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS
|
||||
AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
|
||||
|
||||
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE
|
||||
TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY
|
||||
BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS
|
||||
CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND
|
||||
CONDITIONS.
|
||||
|
||||
1. Definitions
|
||||
|
||||
a. "Adaptation" means a work based upon the Work, or upon the Work and
|
||||
other pre-existing works, such as a translation, adaptation,
|
||||
derivative work, arrangement of music or other alterations of a
|
||||
literary or artistic work, or phonogram or performance and includes
|
||||
cinematographic adaptations or any other form in which the Work may be
|
||||
recast, transformed, or adapted including in any form recognizably
|
||||
derived from the original, except that a work that constitutes a
|
||||
Collection will not be considered an Adaptation for the purpose of
|
||||
this License. For the avoidance of doubt, where the Work is a musical
|
||||
work, performance or phonogram, the synchronization of the Work in
|
||||
timed-relation with a moving image ("synching") will be considered an
|
||||
Adaptation for the purpose of this License.
|
||||
b. "Collection" means a collection of literary or artistic works, such as
|
||||
encyclopedias and anthologies, or performances, phonograms or
|
||||
broadcasts, or other works or subject matter other than works listed
|
||||
in Section 1(f) below, which, by reason of the selection and
|
||||
arrangement of their contents, constitute intellectual creations, in
|
||||
which the Work is included in its entirety in unmodified form along
|
||||
with one or more other contributions, each constituting separate and
|
||||
independent works in themselves, which together are assembled into a
|
||||
collective whole. A work that constitutes a Collection will not be
|
||||
considered an Adaptation (as defined below) for the purposes of this
|
||||
License.
|
||||
c. "Creative Commons Compatible License" means a license that is listed
|
||||
at https://creativecommons.org/compatiblelicenses that has been
|
||||
approved by Creative Commons as being essentially equivalent to this
|
||||
License, including, at a minimum, because that license: (i) contains
|
||||
terms that have the same purpose, meaning and effect as the License
|
||||
Elements of this License; and, (ii) explicitly permits the relicensing
|
||||
of adaptations of works made available under that license under this
|
||||
License or a Creative Commons jurisdiction license with the same
|
||||
License Elements as this License.
|
||||
d. "Distribute" means to make available to the public the original and
|
||||
copies of the Work or Adaptation, as appropriate, through sale or
|
||||
other transfer of ownership.
|
||||
e. "License Elements" means the following high-level license attributes
|
||||
as selected by Licensor and indicated in the title of this License:
|
||||
Attribution, ShareAlike.
|
||||
f. "Licensor" means the individual, individuals, entity or entities that
|
||||
offer(s) the Work under the terms of this License.
|
||||
g. "Original Author" means, in the case of a literary or artistic work,
|
||||
the individual, individuals, entity or entities who created the Work
|
||||
or if no individual or entity can be identified, the publisher; and in
|
||||
addition (i) in the case of a performance the actors, singers,
|
||||
musicians, dancers, and other persons who act, sing, deliver, declaim,
|
||||
play in, interpret or otherwise perform literary or artistic works or
|
||||
expressions of folklore; (ii) in the case of a phonogram the producer
|
||||
being the person or legal entity who first fixes the sounds of a
|
||||
performance or other sounds; and, (iii) in the case of broadcasts, the
|
||||
organization that transmits the broadcast.
|
||||
h. "Work" means the literary and/or artistic work offered under the terms
|
||||
of this License including without limitation any production in the
|
||||
literary, scientific and artistic domain, whatever may be the mode or
|
||||
form of its expression including digital form, such as a book,
|
||||
pamphlet and other writing; a lecture, address, sermon or other work
|
||||
of the same nature; a dramatic or dramatico-musical work; a
|
||||
choreographic work or entertainment in dumb show; a musical
|
||||
composition with or without words; a cinematographic work to which are
|
||||
assimilated works expressed by a process analogous to cinematography;
|
||||
a work of drawing, painting, architecture, sculpture, engraving or
|
||||
lithography; a photographic work to which are assimilated works
|
||||
expressed by a process analogous to photography; a work of applied
|
||||
art; an illustration, map, plan, sketch or three-dimensional work
|
||||
relative to geography, topography, architecture or science; a
|
||||
performance; a broadcast; a phonogram; a compilation of data to the
|
||||
extent it is protected as a copyrightable work; or a work performed by
|
||||
a variety or circus performer to the extent it is not otherwise
|
||||
considered a literary or artistic work.
|
||||
i. "You" means an individual or entity exercising rights under this
|
||||
License who has not previously violated the terms of this License with
|
||||
respect to the Work, or who has received express permission from the
|
||||
Licensor to exercise rights under this License despite a previous
|
||||
violation.
|
||||
j. "Publicly Perform" means to perform public recitations of the Work and
|
||||
to communicate to the public those public recitations, by any means or
|
||||
process, including by wire or wireless means or public digital
|
||||
performances; to make available to the public Works in such a way that
|
||||
members of the public may access these Works from a place and at a
|
||||
place individually chosen by them; to perform the Work to the public
|
||||
by any means or process and the communication to the public of the
|
||||
performances of the Work, including by public digital performance; to
|
||||
broadcast and rebroadcast the Work by any means including signs,
|
||||
sounds or images.
|
||||
k. "Reproduce" means to make copies of the Work by any means including
|
||||
without limitation by sound or visual recordings and the right of
|
||||
fixation and reproducing fixations of the Work, including storage of a
|
||||
protected performance or phonogram in digital form or other electronic
|
||||
medium.
|
||||
|
||||
2. Fair Dealing Rights. Nothing in this License is intended to reduce,
|
||||
limit, or restrict any uses free from copyright or rights arising from
|
||||
limitations or exceptions that are provided for in connection with the
|
||||
copyright protection under copyright law or other applicable laws.
|
||||
|
||||
3. License Grant. Subject to the terms and conditions of this License,
|
||||
Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
|
||||
perpetual (for the duration of the applicable copyright) license to
|
||||
exercise the rights in the Work as stated below:
|
||||
|
||||
a. to Reproduce the Work, to incorporate the Work into one or more
|
||||
Collections, and to Reproduce the Work as incorporated in the
|
||||
Collections;
|
||||
b. to create and Reproduce Adaptations provided that any such Adaptation,
|
||||
including any translation in any medium, takes reasonable steps to
|
||||
clearly label, demarcate or otherwise identify that changes were made
|
||||
to the original Work. For example, a translation could be marked "The
|
||||
original work was translated from English to Spanish," or a
|
||||
modification could indicate "The original work has been modified.";
|
||||
c. to Distribute and Publicly Perform the Work including as incorporated
|
||||
in Collections; and,
|
||||
d. to Distribute and Publicly Perform Adaptations.
|
||||
e. For the avoidance of doubt:
|
||||
|
||||
i. Non-waivable Compulsory License Schemes. In those jurisdictions in
|
||||
which the right to collect royalties through any statutory or
|
||||
compulsory licensing scheme cannot be waived, the Licensor
|
||||
reserves the exclusive right to collect such royalties for any
|
||||
exercise by You of the rights granted under this License;
|
||||
ii. Waivable Compulsory License Schemes. In those jurisdictions in
|
||||
which the right to collect royalties through any statutory or
|
||||
compulsory licensing scheme can be waived, the Licensor waives the
|
||||
exclusive right to collect such royalties for any exercise by You
|
||||
of the rights granted under this License; and,
|
||||
iii. Voluntary License Schemes. The Licensor waives the right to
|
||||
collect royalties, whether individually or, in the event that the
|
||||
Licensor is a member of a collecting society that administers
|
||||
voluntary licensing schemes, via that society, from any exercise
|
||||
by You of the rights granted under this License.
|
||||
|
||||
The above rights may be exercised in all media and formats whether now
|
||||
known or hereafter devised. The above rights include the right to make
|
||||
such modifications as are technically necessary to exercise the rights in
|
||||
other media and formats. Subject to Section 8(f), all rights not expressly
|
||||
granted by Licensor are hereby reserved.
|
||||
|
||||
4. Restrictions. The license granted in Section 3 above is expressly made
|
||||
subject to and limited by the following restrictions:
|
||||
|
||||
a. You may Distribute or Publicly Perform the Work only under the terms
|
||||
of this License. You must include a copy of, or the Uniform Resource
|
||||
Identifier (URI) for, this License with every copy of the Work You
|
||||
Distribute or Publicly Perform. You may not offer or impose any terms
|
||||
on the Work that restrict the terms of this License or the ability of
|
||||
the recipient of the Work to exercise the rights granted to that
|
||||
recipient under the terms of the License. You may not sublicense the
|
||||
Work. You must keep intact all notices that refer to this License and
|
||||
to the disclaimer of warranties with every copy of the Work You
|
||||
Distribute or Publicly Perform. When You Distribute or Publicly
|
||||
Perform the Work, You may not impose any effective technological
|
||||
measures on the Work that restrict the ability of a recipient of the
|
||||
Work from You to exercise the rights granted to that recipient under
|
||||
the terms of the License. This Section 4(a) applies to the Work as
|
||||
incorporated in a Collection, but this does not require the Collection
|
||||
apart from the Work itself to be made subject to the terms of this
|
||||
License. If You create a Collection, upon notice from any Licensor You
|
||||
must, to the extent practicable, remove from the Collection any credit
|
||||
as required by Section 4(c), as requested. If You create an
|
||||
Adaptation, upon notice from any Licensor You must, to the extent
|
||||
practicable, remove from the Adaptation any credit as required by
|
||||
Section 4(c), as requested.
|
||||
b. You may Distribute or Publicly Perform an Adaptation only under the
|
||||
terms of: (i) this License; (ii) a later version of this License with
|
||||
the same License Elements as this License; (iii) a Creative Commons
|
||||
jurisdiction license (either this or a later license version) that
|
||||
contains the same License Elements as this License (e.g.,
|
||||
Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible
|
||||
License. If you license the Adaptation under one of the licenses
|
||||
mentioned in (iv), you must comply with the terms of that license. If
|
||||
you license the Adaptation under the terms of any of the licenses
|
||||
mentioned in (i), (ii) or (iii) (the "Applicable License"), you must
|
||||
comply with the terms of the Applicable License generally and the
|
||||
following provisions: (I) You must include a copy of, or the URI for,
|
||||
the Applicable License with every copy of each Adaptation You
|
||||
Distribute or Publicly Perform; (II) You may not offer or impose any
|
||||
terms on the Adaptation that restrict the terms of the Applicable
|
||||
License or the ability of the recipient of the Adaptation to exercise
|
||||
the rights granted to that recipient under the terms of the Applicable
|
||||
License; (III) You must keep intact all notices that refer to the
|
||||
Applicable License and to the disclaimer of warranties with every copy
|
||||
of the Work as included in the Adaptation You Distribute or Publicly
|
||||
Perform; (IV) when You Distribute or Publicly Perform the Adaptation,
|
||||
You may not impose any effective technological measures on the
|
||||
Adaptation that restrict the ability of a recipient of the Adaptation
|
||||
from You to exercise the rights granted to that recipient under the
|
||||
terms of the Applicable License. This Section 4(b) applies to the
|
||||
Adaptation as incorporated in a Collection, but this does not require
|
||||
the Collection apart from the Adaptation itself to be made subject to
|
||||
the terms of the Applicable License.
|
||||
c. If You Distribute, or Publicly Perform the Work or any Adaptations or
|
||||
Collections, You must, unless a request has been made pursuant to
|
||||
Section 4(a), keep intact all copyright notices for the Work and
|
||||
provide, reasonable to the medium or means You are utilizing: (i) the
|
||||
name of the Original Author (or pseudonym, if applicable) if supplied,
|
||||
and/or if the Original Author and/or Licensor designate another party
|
||||
or parties (e.g., a sponsor institute, publishing entity, journal) for
|
||||
attribution ("Attribution Parties") in Licensor's copyright notice,
|
||||
terms of service or by other reasonable means, the name of such party
|
||||
or parties; (ii) the title of the Work if supplied; (iii) to the
|
||||
extent reasonably practicable, the URI, if any, that Licensor
|
||||
specifies to be associated with the Work, unless such URI does not
|
||||
refer to the copyright notice or licensing information for the Work;
|
||||
and (iv) , consistent with Ssection 3(b), in the case of an
|
||||
Adaptation, a credit identifying the use of the Work in the Adaptation
|
||||
(e.g., "French translation of the Work by Original Author," or
|
||||
"Screenplay based on original Work by Original Author"). The credit
|
||||
required by this Section 4(c) may be implemented in any reasonable
|
||||
manner; provided, however, that in the case of a Adaptation or
|
||||
Collection, at a minimum such credit will appear, if a credit for all
|
||||
contributing authors of the Adaptation or Collection appears, then as
|
||||
part of these credits and in a manner at least as prominent as the
|
||||
credits for the other contributing authors. For the avoidance of
|
||||
doubt, You may only use the credit required by this Section for the
|
||||
purpose of attribution in the manner set out above and, by exercising
|
||||
Your rights under this License, You may not implicitly or explicitly
|
||||
assert or imply any connection with, sponsorship or endorsement by the
|
||||
Original Author, Licensor and/or Attribution Parties, as appropriate,
|
||||
of You or Your use of the Work, without the separate, express prior
|
||||
written permission of the Original Author, Licensor and/or Attribution
|
||||
Parties.
|
||||
d. Except as otherwise agreed in writing by the Licensor or as may be
|
||||
otherwise permitted by applicable law, if You Reproduce, Distribute or
|
||||
Publicly Perform the Work either by itself or as part of any
|
||||
Adaptations or Collections, You must not distort, mutilate, modify or
|
||||
take other derogatory action in relation to the Work which would be
|
||||
prejudicial to the Original Author's honor or reputation. Licensor
|
||||
agrees that in those jurisdictions (e.g. Japan), in which any exercise
|
||||
of the right granted in Section 3(b) of this License (the right to
|
||||
make Adaptations) would be deemed to be a distortion, mutilation,
|
||||
modification or other derogatory action prejudicial to the Original
|
||||
Author's honor and reputation, the Licensor will waive or not assert,
|
||||
as appropriate, this Section, to the fullest extent permitted by the
|
||||
applicable national law, to enable You to reasonably exercise Your
|
||||
right under Section 3(b) of this License (right to make Adaptations)
|
||||
but not otherwise.
|
||||
|
||||
5. Representations, Warranties and Disclaimer
|
||||
|
||||
UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR
|
||||
OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY
|
||||
KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE,
|
||||
INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF
|
||||
LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS,
|
||||
WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION
|
||||
OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
|
||||
|
||||
6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE
|
||||
LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR
|
||||
ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES
|
||||
ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS
|
||||
BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
7. Termination
|
||||
|
||||
a. This License and the rights granted hereunder will terminate
|
||||
automatically upon any breach by You of the terms of this License.
|
||||
Individuals or entities who have received Adaptations or Collections
|
||||
from You under this License, however, will not have their licenses
|
||||
terminated provided such individuals or entities remain in full
|
||||
compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will
|
||||
survive any termination of this License.
|
||||
b. Subject to the above terms and conditions, the license granted here is
|
||||
perpetual (for the duration of the applicable copyright in the Work).
|
||||
Notwithstanding the above, Licensor reserves the right to release the
|
||||
Work under different license terms or to stop distributing the Work at
|
||||
any time; provided, however that any such election will not serve to
|
||||
withdraw this License (or any other license that has been, or is
|
||||
required to be, granted under the terms of this License), and this
|
||||
License will continue in full force and effect unless terminated as
|
||||
stated above.
|
||||
|
||||
8. Miscellaneous
|
||||
|
||||
a. Each time You Distribute or Publicly Perform the Work or a Collection,
|
||||
the Licensor offers to the recipient a license to the Work on the same
|
||||
terms and conditions as the license granted to You under this License.
|
||||
b. Each time You Distribute or Publicly Perform an Adaptation, Licensor
|
||||
offers to the recipient a license to the original Work on the same
|
||||
terms and conditions as the license granted to You under this License.
|
||||
c. If any provision of this License is invalid or unenforceable under
|
||||
applicable law, it shall not affect the validity or enforceability of
|
||||
the remainder of the terms of this License, and without further action
|
||||
by the parties to this agreement, such provision shall be reformed to
|
||||
the minimum extent necessary to make such provision valid and
|
||||
enforceable.
|
||||
d. No term or provision of this License shall be deemed waived and no
|
||||
breach consented to unless such waiver or consent shall be in writing
|
||||
and signed by the party to be charged with such waiver or consent.
|
||||
e. This License constitutes the entire agreement between the parties with
|
||||
respect to the Work licensed here. There are no understandings,
|
||||
agreements or representations with respect to the Work not specified
|
||||
here. Licensor shall not be bound by any additional provisions that
|
||||
may appear in any communication from You. This License may not be
|
||||
modified without the mutual written agreement of the Licensor and You.
|
||||
f. The rights granted under, and the subject matter referenced, in this
|
||||
License were drafted utilizing the terminology of the Berne Convention
|
||||
for the Protection of Literary and Artistic Works (as amended on
|
||||
September 28, 1979), the Rome Convention of 1961, the WIPO Copyright
|
||||
Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996
|
||||
and the Universal Copyright Convention (as revised on July 24, 1971).
|
||||
These rights and subject matter take effect in the relevant
|
||||
jurisdiction in which the License terms are sought to be enforced
|
||||
according to the corresponding provisions of the implementation of
|
||||
those treaty provisions in the applicable national law. If the
|
||||
standard suite of rights granted under applicable copyright law
|
||||
includes additional rights not granted under this License, such
|
||||
additional rights are deemed to be included in the License; this
|
||||
License is not intended to restrict the license of any rights under
|
||||
applicable law.
|
||||
|
||||
|
||||
Creative Commons Notice
|
||||
|
||||
Creative Commons is not a party to this License, and makes no warranty
|
||||
whatsoever in connection with the Work. Creative Commons will not be
|
||||
liable to You or any party on any legal theory for any damages
|
||||
whatsoever, including without limitation any general, special,
|
||||
incidental or consequential damages arising in connection to this
|
||||
license. Notwithstanding the foregoing two (2) sentences, if Creative
|
||||
Commons has expressly identified itself as the Licensor hereunder, it
|
||||
shall have all rights and obligations of Licensor.
|
||||
|
||||
Except for the limited purpose of indicating to the public that the
|
||||
Work is licensed under the CCPL, Creative Commons does not authorize
|
||||
the use by either party of the trademark "Creative Commons" or any
|
||||
related trademark or logo of Creative Commons without the prior
|
||||
written consent of Creative Commons. Any permitted use will be in
|
||||
compliance with Creative Commons' then-current trademark usage
|
||||
guidelines, as may be published on its website or otherwise made
|
||||
available upon request from time to time. For the avoidance of doubt,
|
||||
this trademark restriction does not form part of the License.
|
||||
|
||||
Creative Commons may be contacted at https://creativecommons.org/.
|
||||
@@ -0,0 +1,369 @@
|
||||
#!/usr/bin/python3
|
||||
"""Bounded, board-specific two-layer draft router. Requires pcbnew and numpy.
|
||||
|
||||
Dry-run by default. --route --overwrite explicitly permits saving routing;
|
||||
existing copper additionally requires --replace-routes. Never edits the project,
|
||||
schematic, footprint libraries or generator. Review actual KiCad DRC afterwards.
|
||||
This is not a production autorouter or a fabrication qualification.
|
||||
"""
|
||||
import argparse
|
||||
from collections import defaultdict
|
||||
import hashlib
|
||||
import heapq
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
import time
|
||||
|
||||
import numpy as np
|
||||
import pcbnew as p
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
PCB = ROOT / 'serial-carrier.kicad_pcb'
|
||||
STEP = 0.05
|
||||
NX, NY = int(82 / STEP) + 1, int(80 / STEP) + 1
|
||||
N = NX * NY
|
||||
CLEARANCE = 0.25
|
||||
MARGIN = 0.02
|
||||
LAYERS = (p.F_Cu, p.B_Cu)
|
||||
X = 100 + np.arange(NX)[None, :] * STEP
|
||||
Y = 100 + np.arange(NY)[:, None] * STEP
|
||||
|
||||
|
||||
def mm(v):
|
||||
return p.FromMM(v)
|
||||
|
||||
|
||||
def point(xy):
|
||||
return p.VECTOR2I(mm(xy[0]), mm(xy[1]))
|
||||
|
||||
|
||||
def pos(item):
|
||||
v = item.GetPosition()
|
||||
return (p.ToMM(v.x), p.ToMM(v.y))
|
||||
|
||||
|
||||
def identity(board):
|
||||
"""Immutable geometry/linkage snapshot, independent of serialization order."""
|
||||
footprints = []
|
||||
for f in board.GetFootprints():
|
||||
pads = sorted((a.m_Uuid.AsString(), a.GetNumber(), a.GetNetname(),
|
||||
pos(a), (a.GetSize().x, a.GetSize().y),
|
||||
(a.GetDrillSize().x, a.GetDrillSize().y),
|
||||
a.GetShape(), a.GetAttribute(), a.GetLayerSet().FmtHex(),
|
||||
a.GetOrientationDegrees(), a.GetPinFunction(), a.GetPinType())
|
||||
for a in f.Pads())
|
||||
footprints.append((f.m_Uuid.AsString(), f.GetReference(), f.GetValue(),
|
||||
f.GetFPIDAsString(), f.GetPath().AsString(), pos(f),
|
||||
f.GetOrientationDegrees(), f.GetLayer(), pads))
|
||||
edges = sorted((a.m_Uuid.AsString(), a.GetShape(), a.GetStart().x,
|
||||
a.GetStart().y, a.GetEnd().x, a.GetEnd().y, a.GetWidth())
|
||||
for a in board.GetDrawings() if a.GetLayer() == p.Edge_Cuts)
|
||||
zones = sorted((z.m_Uuid.AsString(), z.GetLayerSet().FmtHex(),
|
||||
z.GetIsRuleArea(), z.GetDoNotAllowTracks(),
|
||||
z.GetDoNotAllowVias(), z.GetDoNotAllowZoneFills(),
|
||||
tuple((z.Outline().COutline(0).CPoint(i).x,
|
||||
z.Outline().COutline(0).CPoint(i).y)
|
||||
for i in range(z.Outline().COutline(0).PointCount())))
|
||||
for z in board.Zones())
|
||||
nets = sorted((n.GetNetCode(), n.GetNetname()) for n in board.GetNetsByNetcode().values())
|
||||
return (board.GetCopperLayerCount(), sorted(footprints), edges, zones, nets)
|
||||
|
||||
|
||||
def window(x0, y0, x1, y1):
|
||||
ix0 = max(0, int(math.floor((x0 - 100) / STEP)))
|
||||
iy0 = max(0, int(math.floor((y0 - 100) / STEP)))
|
||||
ix1 = min(NX, int(math.ceil((x1 - 100) / STEP)) + 1)
|
||||
iy1 = min(NY, int(math.ceil((y1 - 100) / STEP)) + 1)
|
||||
return slice(iy0, iy1), slice(ix0, ix1)
|
||||
|
||||
|
||||
def capsule(mask, a, b, radius):
|
||||
sy, sx = window(min(a[0], b[0]) - radius, min(a[1], b[1]) - radius,
|
||||
max(a[0], b[0]) + radius, max(a[1], b[1]) + radius)
|
||||
xx, yy = X[:, sx], Y[sy, :]
|
||||
dx, dy = b[0] - a[0], b[1] - a[1]
|
||||
length2 = dx * dx + dy * dy
|
||||
t = np.clip(((xx - a[0]) * dx + (yy - a[1]) * dy) / length2, 0, 1) if length2 else 0
|
||||
mask[sy, sx] |= (xx - a[0] - t * dx)**2 + (yy - a[1] - t * dy)**2 <= radius**2
|
||||
|
||||
|
||||
def pad_obstacle(mask, pad, extra, hole_only=False):
|
||||
xy = pos(pad)
|
||||
size = pad.GetDrillSize() if hole_only else pad.GetSize()
|
||||
rx, ry = p.ToMM(size.x) / 2, p.ToMM(size.y) / 2
|
||||
if not rx or not ry:
|
||||
return
|
||||
if abs(rx - ry) < 1e-6 and (hole_only or pad.GetShape() == p.PAD_SHAPE_CIRCLE):
|
||||
capsule(mask, xy, xy, rx + extra)
|
||||
else:
|
||||
# Bounding rectangle deliberately overestimates non-circular pad shapes.
|
||||
box = pad.GetBoundingBox()
|
||||
x0, y0 = p.ToMM(box.GetX()) - extra, p.ToMM(box.GetY()) - extra
|
||||
x1, y1 = p.ToMM(box.GetRight()) + extra, p.ToMM(box.GetBottom()) + extra
|
||||
sy, sx = window(x0, y0, x1, y1)
|
||||
mask[sy, sx] |= (X[:, sx] >= x0) & (X[:, sx] <= x1) & (Y[sy, :] >= y0) & (Y[sy, :] <= y1)
|
||||
|
||||
|
||||
def boundary(radius):
|
||||
edge = 0.5 + radius + MARGIN
|
||||
mask = np.broadcast_to((X < 100 + edge) | (X > 182 - edge) |
|
||||
(Y < 100 + edge) | (Y > 180 - edge), (NY, NX)).copy()
|
||||
mask |= (X < 136 + edge) & (Y < 119 + edge)
|
||||
# Expand the RF reservation by copper radius plus numerical safety margin.
|
||||
r = radius + MARGIN
|
||||
mask |= (X >= 110 - r) & (X <= 131 + r) & (Y >= 119 - r) & (Y <= 132 + r)
|
||||
return mask
|
||||
|
||||
|
||||
def masks(board, pads, net, width):
|
||||
blocked = np.stack([boundary(width / 2)] * 2)
|
||||
via = boundary(0.35)
|
||||
for pad in pads:
|
||||
if pad.GetNetCode() != net or not pad.GetNumber():
|
||||
for z, layer in enumerate(LAYERS):
|
||||
if pad.IsOnLayer(layer):
|
||||
pad_obstacle(blocked[z], pad, CLEARANCE + width / 2 + MARGIN)
|
||||
pad_obstacle(via, pad, CLEARANCE + 0.35 + MARGIN)
|
||||
# Even same-net holes must not receive a drilled via.
|
||||
pad_obstacle(via, pad, CLEARANCE + 0.35 + MARGIN, hole_only=True)
|
||||
for track in board.GetTracks():
|
||||
if track.GetNetCode() == net:
|
||||
if isinstance(track, p.PCB_VIA):
|
||||
capsule(via, pos(track), pos(track), 0.7 + CLEARANCE + MARGIN)
|
||||
continue
|
||||
a = (p.ToMM(track.GetStart().x), p.ToMM(track.GetStart().y))
|
||||
b = (p.ToMM(track.GetEnd().x), p.ToMM(track.GetEnd().y))
|
||||
radius = p.ToMM(track.GetWidth(p.F_Cu) if isinstance(track, p.PCB_VIA) else track.GetWidth()) / 2
|
||||
for z, layer in enumerate(LAYERS):
|
||||
if isinstance(track, p.PCB_VIA) or track.GetLayer() == layer:
|
||||
capsule(blocked[z], a, b, radius + CLEARANCE + width / 2 + MARGIN)
|
||||
capsule(via, a, b, radius + CLEARANCE + 0.35 + MARGIN)
|
||||
return blocked.reshape(-1), via.reshape(-1)
|
||||
|
||||
|
||||
def node(xy, layer=0):
|
||||
x, y = (int(round((v - 100) / STEP)) for v in xy)
|
||||
return layer * N + y * NX + x
|
||||
|
||||
|
||||
def decode(i):
|
||||
z, q = divmod(i, N)
|
||||
y, x = divmod(q, NX)
|
||||
return (100 + x * STEP, 100 + y * STEP), z
|
||||
|
||||
|
||||
def astar(blocked, via, start, target, deadline, max_expansions):
|
||||
tx = int(round((target[0] - 100) / STEP))
|
||||
ty = int(round((target[1] - 100) / STEP))
|
||||
targetq = ty * NX + tx
|
||||
|
||||
def h(q):
|
||||
y, x = divmod(q, NX)
|
||||
dx, dy = abs(x - tx), abs(y - ty)
|
||||
return 10 * max(dx, dy) + 4 * min(dx, dy)
|
||||
|
||||
dist = np.full(2 * N, 2147483647, dtype=np.int32)
|
||||
parent = np.full(2 * N, -1, dtype=np.int32)
|
||||
heap = []
|
||||
for z in (0, 1):
|
||||
i = node(start, z)
|
||||
if not blocked[i]:
|
||||
dist[i] = 0
|
||||
heapq.heappush(heap, (h(i % N), 0, i))
|
||||
expanded = 0
|
||||
moves = ((1, 0, 10), (-1, 0, 10), (0, 1, 10), (0, -1, 10),
|
||||
(1, 1, 14), (1, -1, 14), (-1, 1, 14), (-1, -1, 14))
|
||||
while heap:
|
||||
_, cost, i = heapq.heappop(heap)
|
||||
if cost != dist[i]:
|
||||
continue
|
||||
z, q = divmod(i, N)
|
||||
if q == targetq:
|
||||
path = [i]
|
||||
while parent[path[-1]] >= 0:
|
||||
path.append(int(parent[path[-1]]))
|
||||
return path[::-1], expanded
|
||||
expanded += 1
|
||||
if expanded >= max_expansions or (expanded % 1024 == 0 and time.monotonic() >= deadline):
|
||||
return None, expanded
|
||||
y, x = divmod(q, NX)
|
||||
for dx, dy, stepcost in moves:
|
||||
if not (0 <= x + dx < NX and 0 <= y + dy < NY):
|
||||
continue
|
||||
j = i + dy * NX + dx
|
||||
if blocked[j] or (dx and dy and (blocked[i + dx] or blocked[i + dy * NX])):
|
||||
continue
|
||||
# Mild layer preference prevents needless coincident layer choices.
|
||||
penalty = int((z == 0 and dx == 0) or (z == 1 and dy == 0))
|
||||
nc = cost + stepcost + penalty
|
||||
if nc < dist[j]:
|
||||
dist[j], parent[j] = nc, i
|
||||
heapq.heappush(heap, (nc + h(j % N), nc, j))
|
||||
j = (1 - z) * N + q
|
||||
if not via[q] and not blocked[j]:
|
||||
nc = cost + int(18 / STEP) # 1.8 mm equivalent cost for a layer change.
|
||||
if nc < dist[j]:
|
||||
dist[j], parent[j] = nc, i
|
||||
heapq.heappush(heap, (nc + h(q), nc, j))
|
||||
return None, expanded
|
||||
|
||||
|
||||
def add_path(board, path, start, target, net, width):
|
||||
points = [decode(i) for i in path]
|
||||
points = [(start, points[0][1])] + points + [(target, points[-1][1])]
|
||||
# Only merge exactly collinear grid runs; never shortcut obstacle checks.
|
||||
simple = []
|
||||
for item in points:
|
||||
if simple and item == simple[-1]:
|
||||
continue
|
||||
while len(simple) >= 2 and item[1] == simple[-1][1] == simple[-2][1]:
|
||||
a, b, c = simple[-2][0], simple[-1][0], item[0]
|
||||
cross = (b[0]-a[0])*(c[1]-b[1]) - (b[1]-a[1])*(c[0]-b[0])
|
||||
dot = (b[0]-a[0])*(c[0]-b[0]) + (b[1]-a[1])*(c[1]-b[1])
|
||||
if abs(cross) > 1e-8 or dot < 0:
|
||||
break
|
||||
simple.pop()
|
||||
simple.append(item)
|
||||
for (a, z), (b, zz) in zip(simple, simple[1:]):
|
||||
if z != zz:
|
||||
assert a == b
|
||||
item = p.PCB_VIA(board)
|
||||
item.SetPosition(point(a))
|
||||
item.SetWidth(mm(0.7))
|
||||
item.SetDrill(mm(0.3))
|
||||
item.SetViaType(p.VIATYPE_THROUGH)
|
||||
item.SetLayerPair(p.F_Cu, p.B_Cu)
|
||||
else:
|
||||
if a == b:
|
||||
continue
|
||||
item = p.PCB_TRACK(board)
|
||||
item.SetStart(point(a))
|
||||
item.SetEnd(point(b))
|
||||
item.SetWidth(mm(width))
|
||||
item.SetLayer(LAYERS[z])
|
||||
item.SetNetCode(net)
|
||||
board.Add(item)
|
||||
|
||||
|
||||
def pairs_for_net(pads):
|
||||
# Minimum spanning tree includes duplicate switch contacts explicitly.
|
||||
connected = {0}
|
||||
todo = set(range(1, len(pads)))
|
||||
pairs = []
|
||||
while todo:
|
||||
length, a, b = min((math.dist(pos(pads[a]), pos(pads[b])), a, b)
|
||||
for a in connected for b in todo)
|
||||
pairs.append((length, pads[a], pads[b]))
|
||||
connected.add(b)
|
||||
todo.remove(b)
|
||||
return pairs
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--route', action='store_true')
|
||||
parser.add_argument('--overwrite', action='store_true')
|
||||
parser.add_argument('--replace-routes', action='store_true')
|
||||
parser.add_argument('--seconds', type=float, default=240)
|
||||
parser.add_argument('--max-expansions', type=int, default=1200000)
|
||||
args = parser.parse_args()
|
||||
if args.route and not args.overwrite:
|
||||
parser.error('--route requires --overwrite; back up manual routing first')
|
||||
if not 0 < args.seconds <= 900 or not 0 < args.max_expansions <= 1500000:
|
||||
parser.error('bounds: seconds (0,900], max-expansions (0,1500000]')
|
||||
if ROOT.joinpath('~serial-carrier.kicad_pcb.lck').exists():
|
||||
parser.error('PCB editor lock exists; close the PCB before routing')
|
||||
original_bytes = PCB.read_bytes()
|
||||
project = ROOT / 'serial-carrier.kicad_pro'
|
||||
project_before = project.read_bytes()
|
||||
board = p.LoadBoard(str(PCB))
|
||||
before = identity(board)
|
||||
if board.GetCopperLayerCount() != 2 or len(board.GetFootprints()) != 13:
|
||||
parser.error('unexpected board structure; review router assumptions')
|
||||
if len(board.Zones()) != 1 or not board.Zones()[0].GetIsRuleArea():
|
||||
parser.error('unexpected zones; router supports only the original RF rule area')
|
||||
outline = [(136, 100), (182, 100), (182, 180), (100, 180), (100, 119), (136, 119)]
|
||||
expected_edges = {frozenset((a, b)) for a, b in zip(outline, outline[1:] + outline[:1])}
|
||||
actual_edges = {frozenset(((p.ToMM(e.GetStart().x), p.ToMM(e.GetStart().y)),
|
||||
(p.ToMM(e.GetEnd().x), p.ToMM(e.GetEnd().y))))
|
||||
for e in board.GetDrawings() if e.GetLayer() == p.Edge_Cuts}
|
||||
if actual_edges != expected_edges:
|
||||
parser.error('outline differs from the board-specific routing envelope')
|
||||
reserve = board.Zones()[0]
|
||||
contour = reserve.Outline().COutline(0)
|
||||
rf_points = {(p.ToMM(contour.CPoint(i).x), p.ToMM(contour.CPoint(i).y))
|
||||
for i in range(contour.PointCount())}
|
||||
if (rf_points != {(110, 119), (131, 119), (131, 132), (110, 132)} or
|
||||
not all(reserve.IsOnLayer(layer) for layer in LAYERS) or
|
||||
not all((reserve.GetDoNotAllowTracks(), reserve.GetDoNotAllowVias(),
|
||||
reserve.GetDoNotAllowZoneFills()))):
|
||||
parser.error('RF rule area differs from the board-specific reservation')
|
||||
if board.GetTracks() and not args.replace_routes:
|
||||
parser.error('existing routing protected; --replace-routes required even for preview')
|
||||
for track in list(board.GetTracks()):
|
||||
board.Delete(track)
|
||||
pads = [a for f in board.GetFootprints() for a in f.Pads()]
|
||||
if any(a.GetOrientationDegrees() % 90 or not all(a.IsOnLayer(l) for l in LAYERS) for a in pads):
|
||||
parser.error('router requires axis-aligned through-hole pads')
|
||||
nets = defaultdict(list)
|
||||
for pad in pads:
|
||||
if pad.GetNumber() and pad.GetNetCode():
|
||||
nets[pad.GetNetCode()].append(pad)
|
||||
work = []
|
||||
for net, netpads in nets.items():
|
||||
if len(netpads) > 1:
|
||||
name = netpads[0].GetNetname()
|
||||
for length, a, b in pairs_for_net(netpads):
|
||||
# Reserve short ground escapes before signal fanout can box them in.
|
||||
# Long ground links still run last, preserving space for signal paths.
|
||||
priority = (name == '/GND' and length > 15, length)
|
||||
work.append((priority, net, name, a, b))
|
||||
work.sort(key=lambda w: (w[0], w[2]))
|
||||
start_time = time.monotonic()
|
||||
deadline = start_time + args.seconds
|
||||
results = []
|
||||
for _, net, name, a, b in work:
|
||||
width = 0.5 if name in ('/+3V3', '/GND') else 0.25
|
||||
path, expanded = None, 0
|
||||
if time.monotonic() < deadline:
|
||||
blocked, via = masks(board, pads, net, width)
|
||||
path, expanded = astar(blocked, via, pos(a), pos(b), deadline, args.max_expansions)
|
||||
fallback = False
|
||||
if path is None and width == 0.5 and time.monotonic() < deadline:
|
||||
width, fallback = 0.25, True
|
||||
blocked, via = masks(board, pads, net, width)
|
||||
path, more = astar(blocked, via, pos(a), pos(b), deadline, args.max_expansions)
|
||||
expanded += more
|
||||
label = lambda pad: pad.GetParentFootprint().GetReference() + '.' + pad.GetNumber()
|
||||
result = dict(net=name, start=label(a), end=label(b), start_mm=pos(a), end_mm=pos(b),
|
||||
routed=path is not None, width_mm=width, power_fallback=fallback, expansions=expanded)
|
||||
results.append(result)
|
||||
if path is not None:
|
||||
add_path(board, path, pos(a), pos(b), net, width)
|
||||
print(json.dumps(result), flush=True)
|
||||
assert identity(board) == before, 'immutable board data changed'
|
||||
report = dict(draft_only=True, saved=args.route, grid_mm=STEP, clearance_mm=CLEARANCE,
|
||||
safety_margin_mm=MARGIN, via_diameter_mm=0.7, via_drill_mm=0.3,
|
||||
elapsed_seconds=time.monotonic()-start_time, connections=results,
|
||||
routed_tree_edges=sum(r['routed'] for r in results), total_tree_edges=len(results),
|
||||
tracks=sum(not isinstance(t, p.PCB_VIA) for t in board.GetTracks()),
|
||||
vias=sum(isinstance(t, p.PCB_VIA) for t in board.GetTracks()),
|
||||
immutable_identity_sha256=hashlib.sha256(repr(before).encode()).hexdigest(),
|
||||
input_sha256=hashlib.sha256(original_bytes).hexdigest(),
|
||||
validation='KiCad DRC must be run separately; tree-edge counts are not DRC connectivity')
|
||||
if args.route:
|
||||
if PCB.read_bytes() != original_bytes or ROOT.joinpath('~serial-carrier.kicad_pcb.lck').exists():
|
||||
raise RuntimeError('PCB changed or editor opened during routing; refusing overwrite')
|
||||
# Check a serialized candidate before replacing the user's board.
|
||||
candidate = ROOT / 'validation/pcb-routing-candidate.kicad_pcb'
|
||||
assert p.SaveBoard(str(candidate), board, True)
|
||||
assert project.read_bytes() == project_before, 'project settings changed'
|
||||
assert identity(p.LoadBoard(str(candidate))) == before, 'saved identity mismatch'
|
||||
candidate.replace(PCB)
|
||||
ROOT.joinpath('validation/pcb-routing-run.json').write_text(json.dumps(report, indent=2)+'\n')
|
||||
print(json.dumps({k: v for k, v in report.items() if k != 'connections'}, indent=2))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,119 @@
|
||||
# MAX3243 carrier — PROVISIONAL female CAD reference / VERIFY MALE
|
||||
|
||||
Footprint: `Carrier:MAX3243_Reference_Provisional` in
|
||||
`Carrier.pretty/MAX3243_Reference_Provisional.kicad_mod`.
|
||||
Intended only for a <=100 × 100 mm, two-copper-layer carrier PCB draft.
|
||||
No schematic assignment or edits are made. This footprint does not establish
|
||||
whole-board fit, connector access, DRC compliance, or fabrication readiness.
|
||||
|
||||
## Evidence and scope
|
||||
|
||||
The user reports that the male **6253** module has the same dimensions. This is
|
||||
not confirmation of its pad map. Geometry below is taken from published **female
|
||||
5988** Eagle CAD, not verified male-specific CAD. Confirm the purchased module's
|
||||
identity, orientation, header numbering, outline and mounting geometry before
|
||||
release. Never infer header order from DE-9 gender or mating-face numbering.
|
||||
|
||||
Source: [Adafruit PCB repository](https://github.com/adafruit/Adafruit-RS-232-Level-Shifter-Breakout-PCB),
|
||||
commit `63c6200bb3ef17d491089cfa6441b1810d79bb20`.
|
||||
Local original: `reference/Adafruit RS-232 Full Pinout Level-Shifter Breakout.brd`;
|
||||
SHA-256 `6a7b35ef909f5a2d243c326c34db3957aaa01275bf2aac8e0c749ff18162e870`
|
||||
was rechecked against the file. See `adafruit-research.md` for provenance and
|
||||
female/male distinctions.
|
||||
|
||||
CAD-derived geometry adapted from the design by **Limor Fried/Ladyada for Adafruit
|
||||
Industries**, under **CC BY-SA 3.0 Unported**:
|
||||
https://creativecommons.org/licenses/by-sa/3.0/ . The CAD-derived footprint and
|
||||
these CAD-derived notes use that license. Preserve the full upstream notices in
|
||||
`reference/README.md` and `reference/license.txt` when redistributing this adaptation.
|
||||
Changes: coordinate transformation, carrier pad convention, NPTH carrier holes,
|
||||
and provisional annotations; no module circuitry or connector package copied.
|
||||
|
||||
## View, origin and exact nominal geometry
|
||||
|
||||
Front/component top view, module facing up, DE-9 toward the top (**negative Y**).
|
||||
Origin is header JP2 pin 1, not a PCB corner. X increases rightward; Y downward.
|
||||
Transform Eagle board coordinates in mm with:
|
||||
|
||||
`x = X - 1.905; y = 2.667 - Y`.
|
||||
|
||||
- Pads 1…12: `(2.54 × (N - 1), 0)`, first-to-last span 27.94 mm.
|
||||
- Full bare-module outline is on `F.Fab`, **not carrier `Edge.Cuts`**.
|
||||
Bounds: X = -1.905…29.845, Y = -26.670…2.667 (31.750 × 29.337 mm).
|
||||
Top corners are square. Bottom corners retain the source 2.540 mm radius arcs.
|
||||
- Left arc: (-1.905, 0.127) to (0.635, 2.667), centre (0.635, 0.127).
|
||||
Right arc: (27.305, 2.667) to (29.845, 0.127), centre (27.305, 0.127).
|
||||
Midpoints use radius/sqrt(2), rounded to nine decimal places in the file;
|
||||
the arcs are not chamfers or a bounding-box approximation.
|
||||
- Two unnumbered **3.00 mm NPTH carrier holes**: (1.905, -8.763) and
|
||||
(26.035, -8.763), spacing 24.130 mm. These transfer module-hole centres and
|
||||
drill diameter only. The source module holes are actually plated, 3.60 mm
|
||||
copper / 3.00 mm drill, unconnected in the source signal map. Carrier NPTH
|
||||
deliberately does not copy that plating or assign electrical connections.
|
||||
No screw, washer, standoff or tool-access clearance is implied.
|
||||
|
||||
## Provisional carrier pads and female reference map
|
||||
|
||||
Same convention documented in `footprint-notes.md` for the other carrier modules:
|
||||
**1.70 × 1.70 mm PTH pads / 1.00 mm drills**, rectangular pad 1, circular pads
|
||||
2…12, `*.Cu` and `*.Mask`, unused copper layers retained. That convention derives
|
||||
from the installed KiCad `PinHeader_1x04_P2.54mm_Vertical`; it is not a selected
|
||||
header/socket qualification. The source module itself uses 1.9304 mm round pads.
|
||||
Confirm actual pins/sockets, finished-hole tolerances, annular rings and assembly
|
||||
height before fabrication. Square pad 1 is a carrier marker, not source evidence.
|
||||
|
||||
| Pad | X (mm), Y=0 | Female label | Female CAD net |
|
||||
|---:|---:|---|---|
|
||||
| 1 | 0.00 | Vin | VCC |
|
||||
| 2 | 2.54 | GND | GND |
|
||||
| 3 | 5.08 | DCD | DCD |
|
||||
| 4 | 7.62 | RX | RXD |
|
||||
| 5 | 10.16 | TX | TXD |
|
||||
| 6 | 12.70 | DTR | DTR |
|
||||
| 7 | 15.24 | DSR | DSR |
|
||||
| 8 | 17.78 | RTS | RTS |
|
||||
| 9 | 20.32 | CTS | CTS |
|
||||
| 10 | 22.86 | RI | RI |
|
||||
| 11 | 25.40 | VLD | !INVALID |
|
||||
| 12 | 27.94 | OFF | !FORCEOFF |
|
||||
|
||||
**This is the female reference map, NOT a confirmed male map.** Footprint pads
|
||||
have numbers only, no assigned nets. OFF is active low; VLD indicates valid input
|
||||
when high. This project's logic interface requires 3.3 V Vin.
|
||||
|
||||
## Intentionally omitted / release gates
|
||||
|
||||
The footprint F.Fab outline still describes only the bare module PCB. Subsequent
|
||||
STEP B-rep measurements now establish nominal shell/flange/hex-post envelopes and
|
||||
registration; see `db9-mechanical-notes.md`. The user explicitly confirms identical
|
||||
male/female external dimensions. A1 board-local Dwgs.User projections show those
|
||||
nominal bounds, without changing this footprint's pads or deriving drills from
|
||||
the 3D model. Mating-plug/cable, case wall/cutout, assembly height and actual fit
|
||||
remain unqualified. No guessed complete courtyard or 3D mounting offset is added.
|
||||
There is **no fake courtyard**; courtyard checks cannot establish assembly fit.
|
||||
Short silkscreen labels identify PROVISIONAL / FEMALE CAD REF / VERIFY MALE.
|
||||
|
||||
Before layout release: verify male pad map and orientation against the actual
|
||||
module or authoritative male CAD; perform a 1:1 fit check; select headers and
|
||||
mounting hardware; verify assembled and mating envelopes; then establish real
|
||||
clearances/courtyard and check the complete <=100 × 100 mm two-layer layout.
|
||||
|
||||
## Validation performed
|
||||
|
||||
KiCad **10.0.6** `pcbnew.FootprintLoad` successfully loaded the footprint using
|
||||
system Python (`/usr/bin/python3 -B`) in a read-only inline check. Assertions passed
|
||||
for all 12 numbered pad positions against source JP2 package/element coordinates,
|
||||
female signal-contactref order, pad sizes/drills/shapes and copper/mask presence,
|
||||
no assigned header nets, both NPTH centres/sizes/drills, and all six transformed
|
||||
source outline segments including both 90-degree, 2.540 mm radius arcs and centres.
|
||||
Also checked absence of courtyard, Edge.Cuts, zones and 3D models.
|
||||
|
||||
The first check assumed arc endpoint order was preserved; KiCad normalizes arc
|
||||
orientation. The corrected check matches endpoint pairs independent of order and
|
||||
verifies radius, angle and centre; it passed without changing geometry. The
|
||||
binding emitted the same three startup `PROPERTY_ENUM` assertions documented for
|
||||
other module validation; these did not prevent loading or successful checks.
|
||||
No hardware fit test, male electrical verification, board DRC or firmware build
|
||||
was performed. Existing `validate_footprints.py` was not changed and does not
|
||||
cover this new footprint. Only this notes file and the new footprint were written;
|
||||
schematic, project and lock files were left untouched.
|
||||
@@ -0,0 +1,703 @@
|
||||
{
|
||||
"board": {
|
||||
"3dviewports": [],
|
||||
"design_settings": {
|
||||
"defaults": {
|
||||
"apply_defaults_to_fp_barcodes": false,
|
||||
"apply_defaults_to_fp_dimensions": false,
|
||||
"apply_defaults_to_fp_fields": false,
|
||||
"apply_defaults_to_fp_shapes": false,
|
||||
"apply_defaults_to_fp_text": false,
|
||||
"board_outline_line_width": 0.05,
|
||||
"copper_line_width": 0.2,
|
||||
"copper_text_italic": false,
|
||||
"copper_text_size_h": 1.5,
|
||||
"copper_text_size_v": 1.5,
|
||||
"copper_text_thickness": 0.3,
|
||||
"copper_text_upright": false,
|
||||
"courtyard_line_width": 0.05,
|
||||
"dimension_precision": 4,
|
||||
"dimension_units": 3,
|
||||
"dimensions": {
|
||||
"arrow_length": 1270000,
|
||||
"extension_offset": 500000,
|
||||
"keep_text_aligned": true,
|
||||
"suppress_zeroes": true,
|
||||
"text_position": 0,
|
||||
"units_format": 0
|
||||
},
|
||||
"fab_line_width": 0.1,
|
||||
"fab_text_italic": false,
|
||||
"fab_text_size_h": 1.0,
|
||||
"fab_text_size_v": 1.0,
|
||||
"fab_text_thickness": 0.15,
|
||||
"fab_text_upright": false,
|
||||
"other_line_width": 0.1,
|
||||
"other_text_italic": false,
|
||||
"other_text_size_h": 1.0,
|
||||
"other_text_size_v": 1.0,
|
||||
"other_text_thickness": 0.15,
|
||||
"other_text_upright": false,
|
||||
"pads": {
|
||||
"drill": 0.8,
|
||||
"height": 1.27,
|
||||
"width": 1.27
|
||||
},
|
||||
"silk_line_width": 0.1,
|
||||
"silk_text_italic": false,
|
||||
"silk_text_size_h": 1.0,
|
||||
"silk_text_size_v": 1.0,
|
||||
"silk_text_thickness": 0.1,
|
||||
"silk_text_upright": false,
|
||||
"zones": {
|
||||
"border_display_style": 2,
|
||||
"border_hatch_pitch": 0.5,
|
||||
"corner_radius": 0.0,
|
||||
"corner_smoothing": 0,
|
||||
"fill_mode": 0,
|
||||
"hatch_gap": 1.5,
|
||||
"hatch_orientation": 0.0,
|
||||
"hatch_smoothing_level": 0,
|
||||
"hatch_smoothing_value": 0.1,
|
||||
"hatch_thickness": 1.0,
|
||||
"min_clearance": 0.5,
|
||||
"min_island_area": 10.0,
|
||||
"min_thickness": 0.25,
|
||||
"pad_connection": 1,
|
||||
"remove_islands": 0,
|
||||
"thermal_relief_gap": 0.5,
|
||||
"thermal_relief_spoke_width": 0.5
|
||||
}
|
||||
},
|
||||
"diff_pair_dimensions": [],
|
||||
"drc_exclusions": [],
|
||||
"meta": {
|
||||
"version": 2
|
||||
},
|
||||
"rule_severities": {
|
||||
"annular_width": "error",
|
||||
"clearance": "error",
|
||||
"connection_width": "warning",
|
||||
"copper_edge_clearance": "error",
|
||||
"copper_sliver": "warning",
|
||||
"courtyards_overlap": "error",
|
||||
"creepage": "error",
|
||||
"diff_pair_gap_out_of_range": "error",
|
||||
"diff_pair_uncoupled_length_too_long": "error",
|
||||
"drill_out_of_range": "error",
|
||||
"duplicate_footprints": "warning",
|
||||
"extra_footprint": "warning",
|
||||
"footprint": "error",
|
||||
"footprint_filters_mismatch": "ignore",
|
||||
"footprint_symbol_field_mismatch": "warning",
|
||||
"footprint_symbol_mismatch": "warning",
|
||||
"footprint_type_mismatch": "ignore",
|
||||
"hole_clearance": "error",
|
||||
"hole_to_hole": "warning",
|
||||
"holes_co_located": "warning",
|
||||
"invalid_outline": "error",
|
||||
"isolated_copper": "warning",
|
||||
"item_on_disabled_layer": "error",
|
||||
"items_not_allowed": "error",
|
||||
"length_out_of_range": "error",
|
||||
"lib_footprint_issues": "warning",
|
||||
"lib_footprint_mismatch": "warning",
|
||||
"malformed_courtyard": "error",
|
||||
"microvia_drill_out_of_range": "error",
|
||||
"mirrored_text_on_front_layer": "warning",
|
||||
"missing_courtyard": "ignore",
|
||||
"missing_footprint": "warning",
|
||||
"missing_tuning_profile": "warning",
|
||||
"net_conflict": "warning",
|
||||
"nonmirrored_text_on_back_layer": "warning",
|
||||
"npth_inside_courtyard": "error",
|
||||
"padstack": "warning",
|
||||
"pth_inside_courtyard": "error",
|
||||
"shorting_items": "error",
|
||||
"silk_edge_clearance": "warning",
|
||||
"silk_over_copper": "warning",
|
||||
"silk_overlap": "warning",
|
||||
"skew_out_of_range": "error",
|
||||
"solder_mask_bridge": "error",
|
||||
"starved_thermal": "error",
|
||||
"text_height": "warning",
|
||||
"text_on_edge_cuts": "error",
|
||||
"text_thickness": "warning",
|
||||
"through_hole_pad_without_hole": "error",
|
||||
"too_many_vias": "error",
|
||||
"track_angle": "error",
|
||||
"track_dangling": "warning",
|
||||
"track_not_centered_on_via": "ignore",
|
||||
"track_on_post_machined_layer": "error",
|
||||
"track_segment_length": "error",
|
||||
"track_width": "error",
|
||||
"tracks_crossing": "error",
|
||||
"tuning_profile_track_geometries": "ignore",
|
||||
"unconnected_items": "error",
|
||||
"unresolved_variable": "error",
|
||||
"via_dangling": "warning",
|
||||
"zones_intersect": "error"
|
||||
},
|
||||
"rules": {
|
||||
"max_error": 0.005,
|
||||
"min_clearance": 0.25,
|
||||
"min_connection": 0.0,
|
||||
"min_copper_edge_clearance": 0.5,
|
||||
"min_groove_width": 0.0,
|
||||
"min_hole_clearance": 0.25,
|
||||
"min_hole_to_hole": 0.25,
|
||||
"min_microvia_diameter": 0.2,
|
||||
"min_microvia_drill": 0.1,
|
||||
"min_resolved_spokes": 2,
|
||||
"min_silk_clearance": 0.0,
|
||||
"min_text_height": 0.8,
|
||||
"min_text_thickness": 0.08,
|
||||
"min_through_hole_diameter": 0.3,
|
||||
"min_track_width": 0.25,
|
||||
"min_via_annular_width": 0.1,
|
||||
"min_via_diameter": 0.7,
|
||||
"solder_mask_to_copper_clearance": 0.0,
|
||||
"use_height_for_length_calcs": true
|
||||
},
|
||||
"teardrop_options": [
|
||||
{
|
||||
"td_onpthpad": true,
|
||||
"td_onroundshapesonly": false,
|
||||
"td_onsmdpad": true,
|
||||
"td_ontrackend": false,
|
||||
"td_onvia": true
|
||||
}
|
||||
],
|
||||
"teardrop_parameters": [
|
||||
{
|
||||
"td_allow_use_two_tracks": true,
|
||||
"td_curve_segcount": 0,
|
||||
"td_height_ratio": 1.0,
|
||||
"td_length_ratio": 0.5,
|
||||
"td_maxheight": 2.0,
|
||||
"td_maxlen": 1.0,
|
||||
"td_on_pad_in_zone": false,
|
||||
"td_target_name": "td_round_shape",
|
||||
"td_width_to_size_filter_ratio": 0.9
|
||||
},
|
||||
{
|
||||
"td_allow_use_two_tracks": true,
|
||||
"td_curve_segcount": 0,
|
||||
"td_height_ratio": 1.0,
|
||||
"td_length_ratio": 0.5,
|
||||
"td_maxheight": 2.0,
|
||||
"td_maxlen": 1.0,
|
||||
"td_on_pad_in_zone": false,
|
||||
"td_target_name": "td_rect_shape",
|
||||
"td_width_to_size_filter_ratio": 0.9
|
||||
},
|
||||
{
|
||||
"td_allow_use_two_tracks": true,
|
||||
"td_curve_segcount": 0,
|
||||
"td_height_ratio": 1.0,
|
||||
"td_length_ratio": 0.5,
|
||||
"td_maxheight": 2.0,
|
||||
"td_maxlen": 1.0,
|
||||
"td_on_pad_in_zone": false,
|
||||
"td_target_name": "td_track_end",
|
||||
"td_width_to_size_filter_ratio": 0.9
|
||||
}
|
||||
],
|
||||
"track_widths": [],
|
||||
"tuning_pattern_settings": {
|
||||
"diff_pair_defaults": {
|
||||
"corner_radius_percentage": 80,
|
||||
"corner_style": 1,
|
||||
"max_amplitude": 1.0,
|
||||
"min_amplitude": 0.2,
|
||||
"single_sided": false,
|
||||
"spacing": 1.0
|
||||
},
|
||||
"diff_pair_skew_defaults": {
|
||||
"corner_radius_percentage": 80,
|
||||
"corner_style": 1,
|
||||
"max_amplitude": 1.0,
|
||||
"min_amplitude": 0.2,
|
||||
"single_sided": false,
|
||||
"spacing": 0.6
|
||||
},
|
||||
"single_track_defaults": {
|
||||
"corner_radius_percentage": 80,
|
||||
"corner_style": 1,
|
||||
"max_amplitude": 1.0,
|
||||
"min_amplitude": 0.2,
|
||||
"single_sided": false,
|
||||
"spacing": 0.6
|
||||
}
|
||||
},
|
||||
"via_dimensions": [],
|
||||
"zones_allow_external_fillets": false
|
||||
},
|
||||
"ipc2581": {
|
||||
"bom_rev": "",
|
||||
"dist": "",
|
||||
"distpn": "",
|
||||
"internal_id": "",
|
||||
"mfg": "",
|
||||
"mpn": "",
|
||||
"sch_revision": ""
|
||||
},
|
||||
"layer_pairs": [],
|
||||
"layer_presets": [],
|
||||
"viewports": []
|
||||
},
|
||||
"boards": [],
|
||||
"component_class_settings": {
|
||||
"assignments": [],
|
||||
"meta": {
|
||||
"version": 0
|
||||
},
|
||||
"sheet_component_classes": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
"cvpcb": {
|
||||
"equivalence_files": []
|
||||
},
|
||||
"erc": {
|
||||
"erc_exclusions": [],
|
||||
"meta": {
|
||||
"version": 0
|
||||
},
|
||||
"pin_map": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
2,
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2
|
||||
]
|
||||
],
|
||||
"rule_severities": {
|
||||
"bus_definition_conflict": "error",
|
||||
"bus_entry_needed": "error",
|
||||
"bus_to_bus_conflict": "error",
|
||||
"bus_to_net_conflict": "error",
|
||||
"different_unit_footprint": "error",
|
||||
"different_unit_net": "error",
|
||||
"duplicate_reference": "error",
|
||||
"duplicate_sheet_names": "error",
|
||||
"endpoint_off_grid": "warning",
|
||||
"extra_units": "error",
|
||||
"field_name_whitespace": "warning",
|
||||
"footprint_filter": "ignore",
|
||||
"footprint_link_issues": "warning",
|
||||
"four_way_junction": "ignore",
|
||||
"ground_pin_not_ground": "warning",
|
||||
"hier_label_mismatch": "error",
|
||||
"isolated_pin_label": "warning",
|
||||
"label_dangling": "error",
|
||||
"label_multiple_wires": "warning",
|
||||
"lib_symbol_issues": "warning",
|
||||
"lib_symbol_mismatch": "warning",
|
||||
"missing_bidi_pin": "warning",
|
||||
"missing_input_pin": "warning",
|
||||
"missing_power_pin": "error",
|
||||
"missing_unit": "warning",
|
||||
"multiple_net_names": "warning",
|
||||
"net_not_bus_member": "warning",
|
||||
"no_connect_connected": "warning",
|
||||
"no_connect_dangling": "warning",
|
||||
"pin_not_connected": "error",
|
||||
"pin_not_driven": "error",
|
||||
"pin_to_pin": "warning",
|
||||
"power_pin_not_driven": "error",
|
||||
"same_local_global_label": "warning",
|
||||
"similar_label_and_power": "warning",
|
||||
"similar_labels": "warning",
|
||||
"similar_power": "warning",
|
||||
"simulation_model_issue": "ignore",
|
||||
"single_global_label": "ignore",
|
||||
"stacked_pin_name": "warning",
|
||||
"unannotated": "error",
|
||||
"unconnected_wire_endpoint": "warning",
|
||||
"undefined_netclass": "error",
|
||||
"unit_value_mismatch": "error",
|
||||
"unresolved_variable": "error",
|
||||
"wire_dangling": "error"
|
||||
}
|
||||
},
|
||||
"libraries": {
|
||||
"pinned_footprint_libs": [],
|
||||
"pinned_symbol_libs": []
|
||||
},
|
||||
"meta": {
|
||||
"filename": "serial-carrier.kicad_pro",
|
||||
"version": 3
|
||||
},
|
||||
"net_settings": {
|
||||
"classes": [
|
||||
{
|
||||
"bus_width": 12,
|
||||
"clearance": 0.25,
|
||||
"diff_pair_gap": 0.25,
|
||||
"diff_pair_via_gap": 0.25,
|
||||
"diff_pair_width": 0.2,
|
||||
"line_style": 0,
|
||||
"microvia_diameter": 0.3,
|
||||
"microvia_drill": 0.1,
|
||||
"name": "Default",
|
||||
"pcb_color": "rgba(0, 0, 0, 0.000)",
|
||||
"priority": 2147483647,
|
||||
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
||||
"track_width": 0.25,
|
||||
"tuning_profile": "",
|
||||
"via_diameter": 0.7,
|
||||
"via_drill": 0.3,
|
||||
"wire_width": 6
|
||||
},
|
||||
{
|
||||
"bus_width": 12,
|
||||
"clearance": 0.25,
|
||||
"diff_pair_gap": 0.25,
|
||||
"diff_pair_via_gap": 0.25,
|
||||
"diff_pair_width": 0.25,
|
||||
"line_style": 0,
|
||||
"microvia_diameter": 0.3,
|
||||
"microvia_drill": 0.1,
|
||||
"name": "Power",
|
||||
"pcb_color": "rgba(0, 0, 0, 0.000)",
|
||||
"priority": 0,
|
||||
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
||||
"track_width": 0.5,
|
||||
"tuning_profile": "",
|
||||
"via_diameter": 0.7,
|
||||
"via_drill": 0.3,
|
||||
"wire_width": 6
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"version": 5
|
||||
},
|
||||
"net_colors": null,
|
||||
"netclass_assignments": null,
|
||||
"netclass_patterns": [
|
||||
{
|
||||
"netclass": "Power",
|
||||
"pattern": "/+3V3"
|
||||
},
|
||||
{
|
||||
"netclass": "Power",
|
||||
"pattern": "/GND"
|
||||
}
|
||||
]
|
||||
},
|
||||
"pcbnew": {
|
||||
"last_paths": {
|
||||
"idf": "",
|
||||
"netlist": "",
|
||||
"plot": "",
|
||||
"specctra_dsn": "",
|
||||
"step": "",
|
||||
"vrml": ""
|
||||
},
|
||||
"page_layout_descr_file": ""
|
||||
},
|
||||
"schematic": {
|
||||
"annotate_start_num": 0,
|
||||
"annotation": {
|
||||
"method": 0,
|
||||
"sort_order": 0
|
||||
},
|
||||
"bom_export_filename": "${PROJECTNAME}.csv",
|
||||
"bom_fmt_presets": [],
|
||||
"bom_fmt_settings": {
|
||||
"field_delimiter": ",",
|
||||
"keep_line_breaks": false,
|
||||
"keep_tabs": false,
|
||||
"name": "CSV",
|
||||
"ref_delimiter": ",",
|
||||
"ref_range_delimiter": "",
|
||||
"string_delimiter": "\""
|
||||
},
|
||||
"bom_presets": [],
|
||||
"bom_settings": {
|
||||
"exclude_dnp": false,
|
||||
"fields_ordered": [
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Reference",
|
||||
"name": "Reference",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Qty",
|
||||
"name": "${QUANTITY}",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": true,
|
||||
"label": "Value",
|
||||
"name": "Value",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": true,
|
||||
"label": "DNP",
|
||||
"name": "${DNP}",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": true,
|
||||
"label": "Exclude from BOM",
|
||||
"name": "${EXCLUDE_FROM_BOM}",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": true,
|
||||
"label": "Exclude from Board",
|
||||
"name": "${EXCLUDE_FROM_BOARD}",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": true,
|
||||
"label": "Exclude from Simulation",
|
||||
"name": "${EXCLUDE_FROM_SIM}",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": true,
|
||||
"label": "Exclude from Position Files",
|
||||
"name": "${EXCLUDE_FROM_POS_FILES}",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": true,
|
||||
"label": "Footprint",
|
||||
"name": "Footprint",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Datasheet",
|
||||
"name": "Datasheet",
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"filter_string": "",
|
||||
"group_symbols": true,
|
||||
"include_excluded_from_bom": true,
|
||||
"name": "Default Editing",
|
||||
"sort_asc": true,
|
||||
"sort_field": "Referenz"
|
||||
},
|
||||
"bus_aliases": {},
|
||||
"connection_grid_size": 50.0,
|
||||
"drawing": {
|
||||
"dashed_lines_dash_length_ratio": 12.0,
|
||||
"dashed_lines_gap_length_ratio": 3.0,
|
||||
"default_line_thickness": 6.0,
|
||||
"default_text_size": 50.0,
|
||||
"field_names": [],
|
||||
"hop_over_size_choice": 0,
|
||||
"intersheets_ref_own_page": false,
|
||||
"intersheets_ref_prefix": "",
|
||||
"intersheets_ref_short": false,
|
||||
"intersheets_ref_show": false,
|
||||
"intersheets_ref_suffix": "",
|
||||
"junction_size_choice": 3,
|
||||
"label_size_ratio": 0.375,
|
||||
"operating_point_overlay_i_precision": 3,
|
||||
"operating_point_overlay_i_range": "~A",
|
||||
"operating_point_overlay_v_precision": 3,
|
||||
"operating_point_overlay_v_range": "~V",
|
||||
"overbar_offset_ratio": 1.23,
|
||||
"pin_symbol_size": 25.0,
|
||||
"text_offset_ratio": 0.15
|
||||
},
|
||||
"legacy_lib_dir": "",
|
||||
"legacy_lib_list": [],
|
||||
"meta": {
|
||||
"version": 1
|
||||
},
|
||||
"page_layout_descr_file": "",
|
||||
"plot_directory": "",
|
||||
"reuse_designators": true,
|
||||
"subpart_first_id": 65,
|
||||
"subpart_id_separator": 0,
|
||||
"top_level_sheets": [
|
||||
{
|
||||
"filename": "serial-carrier.kicad_sch",
|
||||
"name": "serial-carrier",
|
||||
"uuid": "00000000-0000-0000-0000-000000000000"
|
||||
}
|
||||
],
|
||||
"used_designators": "",
|
||||
"variants": []
|
||||
},
|
||||
"sheets": [],
|
||||
"text_variables": {},
|
||||
"tuning_profiles": {
|
||||
"meta": {
|
||||
"version": 0
|
||||
},
|
||||
"tuning_profiles_impedance_geometric": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,400 @@
|
||||
(kicad_sch (version 20250114) (generator "eeschema") (uuid "c86e0d56-4290-5f2b-b790-14926c8cdeea") (paper "A3")
|
||||
(title_block (title "ESP32 Serial Swiss Army Knife - Module Carrier") (date "2026-09-20") (rev "A0 DRAFT") (comment 1 "Not fabrication-ready: provisional module footprints and male RS-232 mapping"))
|
||||
(lib_symbols (symbol "Carrier:HW678_N16R8" (pin_names (offset 0.762)) (in_bom yes) (on_board yes)
|
||||
(property "Reference" "U" (at 0 34.29 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "HW678_N16R8" (at 0 31.75 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "Carrier:HW678_2x22_Provisional" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Description" "HW-678 V0.0.0 / N16R8 carrier interface; project numbering; onboard USB/regulator retained" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(symbol "HW678_N16R8_0_1" (rectangle (start -17.78 29.21) (end 17.78 -29.21) (stroke (width 0.254) (type default)) (fill (type background))))
|
||||
(symbol "HW678_N16R8_1_1"
|
||||
(pin power_out line (at -22.86 26.67 0) (length 5.08) (name "3V3" (effects (font (size 1.016 1.016)) )) (number "1" (effects (font (size 1.016 1.016)) )))
|
||||
(pin passive line (at -22.86 24.13 0) (length 5.08) (name "3V3" (effects (font (size 1.016 1.016)) )) (number "2" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 21.59 0) (length 5.08) (name "EN" (effects (font (size 1.016 1.016)) )) (number "3" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 19.05 0) (length 5.08) (name "GPIO4" (effects (font (size 1.016 1.016)) )) (number "4" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 16.51 0) (length 5.08) (name "GPIO5" (effects (font (size 1.016 1.016)) )) (number "5" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 13.97 0) (length 5.08) (name "GPIO6" (effects (font (size 1.016 1.016)) )) (number "6" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -22.86 11.43 0) (length 5.08) (name "GPIO7" (effects (font (size 1.016 1.016)) )) (number "7" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -22.86 8.89 0) (length 5.08) (name "GPIO15" (effects (font (size 1.016 1.016)) )) (number "8" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 6.35 0) (length 5.08) (name "GPIO16" (effects (font (size 1.016 1.016)) )) (number "9" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -22.86 3.81 0) (length 5.08) (name "GPIO17" (effects (font (size 1.016 1.016)) )) (number "10" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 1.27 0) (length 5.08) (name "GPIO18" (effects (font (size 1.016 1.016)) )) (number "11" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 -1.27 0) (length 5.08) (name "GPIO8" (effects (font (size 1.016 1.016)) )) (number "12" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at -22.86 -3.81 0) (length 5.08) (name "GPIO3" (effects (font (size 1.016 1.016)) )) (number "13" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at -22.86 -6.35 0) (length 5.08) (name "GPIO46" (effects (font (size 1.016 1.016)) )) (number "14" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at -22.86 -8.89 0) (length 5.08) (name "GPIO9" (effects (font (size 1.016 1.016)) )) (number "15" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 -11.43 0) (length 5.08) (name "GPIO10" (effects (font (size 1.016 1.016)) )) (number "16" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at -22.86 -13.97 0) (length 5.08) (name "GPIO11" (effects (font (size 1.016 1.016)) )) (number "17" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -22.86 -16.51 0) (length 5.08) (name "GPIO12" (effects (font (size 1.016 1.016)) )) (number "18" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 -19.05 0) (length 5.08) (name "GPIO13" (effects (font (size 1.016 1.016)) )) (number "19" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 -21.59 0) (length 5.08) (name "GPIO14" (effects (font (size 1.016 1.016)) )) (number "20" (effects (font (size 1.016 1.016)) )))
|
||||
(pin passive line (at -22.86 -24.13 0) (length 5.08) (name "5V" (effects (font (size 1.016 1.016)) )) (number "21" (effects (font (size 1.016 1.016)) )))
|
||||
(pin power_out line (at -22.86 -26.67 0) (length 5.08) (name "GND" (effects (font (size 1.016 1.016)) )) (number "22" (effects (font (size 1.016 1.016)) )))
|
||||
(pin passive line (at 22.86 26.67 180) (length 5.08) (name "GND" (effects (font (size 1.016 1.016)) )) (number "23" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 24.13 180) (length 5.08) (name "GPIO43" (effects (font (size 1.016 1.016)) )) (number "24" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 21.59 180) (length 5.08) (name "GPIO44" (effects (font (size 1.016 1.016)) )) (number "25" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 19.05 180) (length 5.08) (name "GPIO1" (effects (font (size 1.016 1.016)) )) (number "26" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 16.51 180) (length 5.08) (name "GPIO2" (effects (font (size 1.016 1.016)) )) (number "27" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 13.97 180) (length 5.08) (name "GPIO42" (effects (font (size 1.016 1.016)) )) (number "28" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 11.43 180) (length 5.08) (name "GPIO41" (effects (font (size 1.016 1.016)) )) (number "29" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 8.89 180) (length 5.08) (name "GPIO40" (effects (font (size 1.016 1.016)) )) (number "30" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 6.35 180) (length 5.08) (name "GPIO39" (effects (font (size 1.016 1.016)) )) (number "31" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 3.81 180) (length 5.08) (name "GPIO38" (effects (font (size 1.016 1.016)) )) (number "32" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 1.27 180) (length 5.08) (name "GPIO37" (effects (font (size 1.016 1.016)) )) (number "33" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -1.27 180) (length 5.08) (name "GPIO36" (effects (font (size 1.016 1.016)) )) (number "34" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -3.81 180) (length 5.08) (name "GPIO35" (effects (font (size 1.016 1.016)) )) (number "35" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -6.35 180) (length 5.08) (name "GPIO0" (effects (font (size 1.016 1.016)) )) (number "36" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -8.89 180) (length 5.08) (name "GPIO45" (effects (font (size 1.016 1.016)) )) (number "37" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -11.43 180) (length 5.08) (name "GPIO48" (effects (font (size 1.016 1.016)) )) (number "38" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -13.97 180) (length 5.08) (name "GPIO47" (effects (font (size 1.016 1.016)) )) (number "39" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -16.51 180) (length 5.08) (name "GPIO21" (effects (font (size 1.016 1.016)) )) (number "40" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -19.05 180) (length 5.08) (name "GPIO20" (effects (font (size 1.016 1.016)) )) (number "41" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -21.59 180) (length 5.08) (name "GPIO19" (effects (font (size 1.016 1.016)) )) (number "42" (effects (font (size 1.016 1.016)) )))
|
||||
(pin passive line (at 22.86 -24.13 180) (length 5.08) (name "GND" (effects (font (size 1.016 1.016)) )) (number "43" (effects (font (size 1.016 1.016)) )))
|
||||
(pin passive line (at 22.86 -26.67 180) (length 5.08) (name "GND" (effects (font (size 1.016 1.016)) )) (number "44" (effects (font (size 1.016 1.016)) )))
|
||||
)
|
||||
)
|
||||
(symbol "Carrier:MAX3243_Module_Reference" (pin_names (offset 0.762)) (in_bom yes) (on_board yes)
|
||||
(property "Reference" "U" (at 0 22.86 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "MAX3243_Module_Reference" (at 0 20.32 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Description" "Adafruit male 6253 intended; physical numbering from published female 5988 CAD, VERIFY MALE" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(symbol "MAX3243_Module_Reference_0_1" (rectangle (start -15.24 17.78) (end 15.24 -17.78) (stroke (width 0.254) (type default)) (fill (type background))))
|
||||
(symbol "MAX3243_Module_Reference_1_1"
|
||||
(pin power_in line (at -20.32 13.97 0) (length 5.08) (name "Vin" (effects (font (size 1.016 1.016)) )) (number "1" (effects (font (size 1.016 1.016)) )))
|
||||
(pin power_in line (at -20.32 11.43 0) (length 5.08) (name "GND" (effects (font (size 1.016 1.016)) )) (number "2" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -20.32 8.89 0) (length 5.08) (name "DCD" (effects (font (size 1.016 1.016)) )) (number "3" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -20.32 6.35 0) (length 5.08) (name "RX" (effects (font (size 1.016 1.016)) )) (number "4" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -20.32 3.81 0) (length 5.08) (name "TX" (effects (font (size 1.016 1.016)) )) (number "5" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -20.32 1.27 0) (length 5.08) (name "DTR" (effects (font (size 1.016 1.016)) )) (number "6" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -20.32 -1.27 0) (length 5.08) (name "DSR" (effects (font (size 1.016 1.016)) )) (number "7" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -20.32 -3.81 0) (length 5.08) (name "RTS" (effects (font (size 1.016 1.016)) )) (number "8" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -20.32 -6.35 0) (length 5.08) (name "CTS" (effects (font (size 1.016 1.016)) )) (number "9" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -20.32 -8.89 0) (length 5.08) (name "RI" (effects (font (size 1.016 1.016)) )) (number "10" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -20.32 -11.43 0) (length 5.08) (name "VLD" (effects (font (size 1.016 1.016)) )) (number "11" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -20.32 -13.97 0) (length 5.08) (name "OFF_N" (effects (font (size 1.016 1.016)) )) (number "12" (effects (font (size 1.016 1.016)) )))
|
||||
)
|
||||
)
|
||||
(symbol "Carrier:OLED_I2C_26mm" (pin_names (offset 0.762)) (in_bom yes) (on_board yes)
|
||||
(property "Reference" "DS" (at 0 12.7 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "OLED_I2C_26mm" (at 0 10.16 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "Carrier:OLED_26mm_I2C_Provisional" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Description" "User fit-verified 26 mm OLED; front/top header GND VCC SCL SDA" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(symbol "OLED_I2C_26mm_0_1" (rectangle (start -10.16 7.62) (end 10.16 -7.62) (stroke (width 0.254) (type default)) (fill (type background))))
|
||||
(symbol "OLED_I2C_26mm_1_1"
|
||||
(pin power_in line (at -15.24 3.81 0) (length 5.08) (name "GND" (effects (font (size 1.016 1.016)) )) (number "1" (effects (font (size 1.016 1.016)) )))
|
||||
(pin power_in line (at -15.24 1.27 0) (length 5.08) (name "VCC" (effects (font (size 1.016 1.016)) )) (number "2" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -15.24 -1.27 0) (length 5.08) (name "SCL" (effects (font (size 1.016 1.016)) )) (number "3" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at -15.24 -3.81 0) (length 5.08) (name "SDA" (effects (font (size 1.016 1.016)) )) (number "4" (effects (font (size 1.016 1.016)) )))
|
||||
)
|
||||
)
|
||||
(symbol "Carrier:SW_NO" (pin_names (offset 0.762)) (in_bom yes) (on_board yes)
|
||||
(property "Reference" "SW" (at 0 7.62 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "SW_NO" (at 0 5.08 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Description" "Normally open contact; footprint duplicates pads 1 and 2 for internally common terminal pairs" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(symbol "SW_NO_0_1" (polyline (pts (xy -2.54 0) (xy 2.54 1.524)) (stroke (width 0.254) (type default)) (fill (type none)))(circle (center -2.54 0) (radius 0.35) (stroke (width 0.1524) (type default)) (fill (type none)))(circle (center 2.54 0) (radius 0.35) (stroke (width 0.1524) (type default)) (fill (type none))))
|
||||
(symbol "SW_NO_1_1"
|
||||
(pin passive line (at -5.08 0 0) (length 2.54) (name "~" (effects (font (size 1.016 1.016)) )) (number "1" (effects (font (size 1.016 1.016)) )))
|
||||
(pin passive line (at 5.08 0 180) (length 2.54) (name "~" (effects (font (size 1.016 1.016)) )) (number "2" (effects (font (size 1.016 1.016)) )))
|
||||
)
|
||||
)
|
||||
(symbol "Carrier:R" (pin_names (offset 0.762)) (in_bom yes) (on_board yes)
|
||||
(property "Reference" "R" (at 0 6.096 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "R" (at 0 3.556 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Description" "Resistor, axial DIN0207 footprint; exact MPN pending" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(symbol "R_0_1" (rectangle (start -2.54 1.016) (end 2.54 -1.016) (stroke (width 0.254) (type default)) (fill (type background))))
|
||||
(symbol "R_1_1"
|
||||
(pin passive line (at -5.08 0 0) (length 2.54) (name "~" (effects (font (size 1.016 1.016)) )) (number "1" (effects (font (size 1.016 1.016)) )))
|
||||
(pin passive line (at 5.08 0 180) (length 2.54) (name "~" (effects (font (size 1.016 1.016)) )) (number "2" (effects (font (size 1.016 1.016)) )))
|
||||
)
|
||||
))
|
||||
(symbol (lib_id "Carrier:HW678_N16R8") (at 95.25 109.22 0) (unit 1) (in_bom yes) (on_board yes) (dnp no) (uuid "3ab265d0-7fd3-5891-94eb-d39426c6a8a3")
|
||||
(property "Reference" "U1" (at 95.25 74.93 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "HW678 / S3-N16R8" (at 95.25 77.47 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "Carrier:HW678_2x22_Provisional" (at 95.25 109.22 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 95.25 109.22 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Status" "Header geometry nominal; outline/antenna clearance pending" (at 95.25 109.22 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Pin_numbering" "Left 1-22; right 23-44; both top-to-bottom, antenna up" (at 95.25 109.22 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(pin "1" (uuid "4a464e7d-e18b-557c-8fae-641c5fc5419f"))
|
||||
(pin "2" (uuid "8224a9d5-21e3-5814-9dda-20f82d0dff57"))
|
||||
(pin "3" (uuid "c8362eee-41d1-5268-ba40-ab780577836d"))
|
||||
(pin "4" (uuid "c38294c1-5e07-56bc-8afd-b92e622ee4c8"))
|
||||
(pin "5" (uuid "e9137085-cd7e-5481-8165-89ead64f6f0d"))
|
||||
(pin "6" (uuid "7400e635-9d97-5e1b-8bdb-cef9cc1eaaf9"))
|
||||
(pin "7" (uuid "21108d2d-b874-58a0-b220-e7ed30048e86"))
|
||||
(pin "8" (uuid "2d607a2c-f0d7-53db-9f0f-ba99c277bebf"))
|
||||
(pin "9" (uuid "f0feb47f-2080-575a-adaa-6086d2b111ce"))
|
||||
(pin "10" (uuid "f7e3c60e-a23d-5f8f-a0b4-e2259b587906"))
|
||||
(pin "11" (uuid "751f386b-e0e0-5fd7-b93b-5006c04bf57e"))
|
||||
(pin "12" (uuid "35b6d88b-5907-5977-aadc-eeb22f38e80a"))
|
||||
(pin "13" (uuid "351de5e5-c93c-5146-ab20-04e74cf929e8"))
|
||||
(pin "14" (uuid "6ecd8a0c-5a4e-5e23-82e6-4591738b1e23"))
|
||||
(pin "15" (uuid "6a21011c-83bd-5d62-8359-1c2d77b4b8d8"))
|
||||
(pin "16" (uuid "11810c32-a16c-5508-9766-59b4bace344a"))
|
||||
(pin "17" (uuid "44b4c7dd-10ab-59cb-bf10-6e996820493b"))
|
||||
(pin "18" (uuid "6d2d2610-f346-5f8b-924a-7b7a7b5dec7d"))
|
||||
(pin "19" (uuid "bde6865b-0c4c-51d5-b38d-38fd9c0b2ba2"))
|
||||
(pin "20" (uuid "dd0e2dad-30a0-5212-b7d5-873b048010e4"))
|
||||
(pin "21" (uuid "234394ae-14d9-5f7c-86fc-96d485769c7d"))
|
||||
(pin "22" (uuid "bfbc25e0-b266-5930-9094-3930cb4640e3"))
|
||||
(pin "23" (uuid "18243b22-b464-570f-8adf-2ccce1be2e2d"))
|
||||
(pin "24" (uuid "7a33ec03-7756-557b-8890-fddda9d64305"))
|
||||
(pin "25" (uuid "98b98ba2-2019-5fd3-b268-37af497dda99"))
|
||||
(pin "26" (uuid "c7309c87-2402-506d-8b55-d67799b08001"))
|
||||
(pin "27" (uuid "2744eff7-a534-579f-9cac-da2eb007b47e"))
|
||||
(pin "28" (uuid "50f8137f-f265-5cdb-844e-c4025996014c"))
|
||||
(pin "29" (uuid "03a45791-629b-5973-b58b-de8e7260c3d9"))
|
||||
(pin "30" (uuid "51687701-e717-570b-858f-d0037fde62d1"))
|
||||
(pin "31" (uuid "36822cf0-a680-5e1f-9105-38331f2639e6"))
|
||||
(pin "32" (uuid "2b4d7757-c4ba-5eff-9b68-ef9fbff349de"))
|
||||
(pin "33" (uuid "0864a47a-88c4-5564-be5c-54cede1eb4ff"))
|
||||
(pin "34" (uuid "87ae74c4-9ef5-520f-b909-5eef264c51cb"))
|
||||
(pin "35" (uuid "1359325a-5bab-59f5-97c1-0ef02ad26e24"))
|
||||
(pin "36" (uuid "d2ffd2b2-a6ed-55dc-b6f6-e23ab2ee6823"))
|
||||
(pin "37" (uuid "fdaaa89f-154c-565f-a9d1-6946c6b7a59a"))
|
||||
(pin "38" (uuid "5db889a0-00f5-55dc-bd75-68665bc1d769"))
|
||||
(pin "39" (uuid "9a24d8b0-bd02-5752-ac4d-5003fce71762"))
|
||||
(pin "40" (uuid "06937bd2-e8de-5f19-81fb-5d225f94caa6"))
|
||||
(pin "41" (uuid "7079189c-8336-52a4-a74b-10823730406d"))
|
||||
(pin "42" (uuid "08e6fb4c-12a9-5cdd-a96e-5a66b544c321"))
|
||||
(pin "43" (uuid "c0a0436c-8a72-5462-83b4-5aed704aaf5d"))
|
||||
(pin "44" (uuid "cf42b784-faa2-51f5-8b6e-9cbe009fc3ac"))
|
||||
(instances (project "serial-carrier" (path "/c86e0d56-4290-5f2b-b790-14926c8cdeea" (reference "U1") (unit 1)))))
|
||||
(wire (pts (xy 72.39 82.55) (xy 59.69 82.55)) (stroke (width 0) (type default)) (uuid "6a5c3165-28b8-521e-b59d-937eb48ebc86"))
|
||||
(label "+3V3" (at 59.69 82.55 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "8e9c5cf1-acd7-5bd0-b378-16feb2193986"))
|
||||
(wire (pts (xy 72.39 85.09) (xy 59.69 85.09)) (stroke (width 0) (type default)) (uuid "eadb072f-1c0c-5867-bf06-c526efaa7ffa"))
|
||||
(label "+3V3" (at 59.69 85.09 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "e35c02e0-4ded-52af-ae01-eebe07485e0a"))
|
||||
(no_connect (at 72.39 87.63) (uuid "14e4ee36-cb86-5b36-8d06-52b3a1804732"))
|
||||
(wire (pts (xy 72.39 90.17) (xy 59.69 90.17)) (stroke (width 0) (type default)) (uuid "d9ad45bb-26c2-586f-8ab9-30ddeba5fc31"))
|
||||
(label "RS_DCD" (at 59.69 90.17 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "af29ef10-6f48-5a58-801f-c006fcf9b755"))
|
||||
(wire (pts (xy 72.39 92.71) (xy 59.69 92.71)) (stroke (width 0) (type default)) (uuid "ca28ce58-fdf8-5a5f-89da-488eab1b4bfd"))
|
||||
(label "RS_DSR" (at 59.69 92.71 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "ac3c4cd0-0cce-5cdb-a61d-0b588434404d"))
|
||||
(wire (pts (xy 72.39 95.25) (xy 59.69 95.25)) (stroke (width 0) (type default)) (uuid "78f6e2c3-e080-56f6-9960-3033cc78e51a"))
|
||||
(label "RS_RI" (at 59.69 95.25 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "8de0b0a5-4244-5078-a0da-3ee5745e5680"))
|
||||
(wire (pts (xy 72.39 97.79) (xy 59.69 97.79)) (stroke (width 0) (type default)) (uuid "04298e7e-1e1a-5b86-92e8-c5785182688b"))
|
||||
(label "RS_DTR" (at 59.69 97.79 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "d9d01c92-5403-5947-8a87-fae7e62d0d8e"))
|
||||
(wire (pts (xy 72.39 100.33) (xy 59.69 100.33)) (stroke (width 0) (type default)) (uuid "9456c018-93ae-5ed8-9c1b-32a325d8b379"))
|
||||
(label "RS_RTS" (at 59.69 100.33 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "4d8c3f06-455e-55db-8fff-396fa0b7e11a"))
|
||||
(wire (pts (xy 72.39 102.87) (xy 59.69 102.87)) (stroke (width 0) (type default)) (uuid "d2ec21ba-7af9-5da0-9e08-1661dd30374a"))
|
||||
(label "RS_CTS" (at 59.69 102.87 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "8c1a0ed8-16a1-5fc1-ba90-0548b73dc83b"))
|
||||
(wire (pts (xy 72.39 105.41) (xy 59.69 105.41)) (stroke (width 0) (type default)) (uuid "037b448f-7b47-5741-87e1-1b6f49b9b9f0"))
|
||||
(label "RS_TX" (at 59.69 105.41 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "072eac8c-bd61-5168-a15c-94d4da50a863"))
|
||||
(wire (pts (xy 72.39 107.95) (xy 59.69 107.95)) (stroke (width 0) (type default)) (uuid "56d395e8-2d19-5606-9ca3-743ff07722be"))
|
||||
(label "RS_RX" (at 59.69 107.95 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "e024b664-2aef-5efc-ba59-2a443f66599f"))
|
||||
(wire (pts (xy 72.39 110.49) (xy 59.69 110.49)) (stroke (width 0) (type default)) (uuid "77cbac5d-1230-55d8-8e74-1e41c2cd8da4"))
|
||||
(label "RS_VALID" (at 59.69 110.49 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "35c2e93d-3962-566b-8683-cac12c0fa27a"))
|
||||
(no_connect (at 72.39 113.03) (uuid "4aefdb9e-0fca-5fcc-875b-f873870fdebe"))
|
||||
(no_connect (at 72.39 115.57) (uuid "2948fac0-bacc-54c4-a568-6ee3d49ff31d"))
|
||||
(wire (pts (xy 72.39 118.11) (xy 59.69 118.11)) (stroke (width 0) (type default)) (uuid "1aef75f4-ccce-555e-b023-5194df0779c2"))
|
||||
(label "RS_OFF_N" (at 59.69 118.11 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "b709828f-6e83-5536-afa3-68fa465f6c7c"))
|
||||
(wire (pts (xy 72.39 120.65) (xy 59.69 120.65)) (stroke (width 0) (type default)) (uuid "c7d046c1-c102-5045-a0d5-88b122a73c46"))
|
||||
(label "BTN_PREVIOUS" (at 59.69 120.65 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "1482502c-6db5-5ee4-a35b-e191cbdf59c0"))
|
||||
(wire (pts (xy 72.39 123.19) (xy 59.69 123.19)) (stroke (width 0) (type default)) (uuid "fda79afa-ecf6-5090-8178-a3d646b2a38b"))
|
||||
(label "OLED_SDA" (at 59.69 123.19 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "ce3c03fe-db6d-5427-98cb-b65007bbe53a"))
|
||||
(wire (pts (xy 72.39 125.73) (xy 59.69 125.73)) (stroke (width 0) (type default)) (uuid "9da4032e-2f54-5d2e-93f9-49b135df292b"))
|
||||
(label "OLED_SCL" (at 59.69 125.73 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "eca1f64d-3fe1-5d99-b1b8-acb054f69bb6"))
|
||||
(wire (pts (xy 72.39 128.27) (xy 59.69 128.27)) (stroke (width 0) (type default)) (uuid "c109b035-7762-5c19-b891-fa46ddfcdec5"))
|
||||
(label "BTN_SELECT" (at 59.69 128.27 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "b5214ec2-aedd-52d0-8a85-45d5c7d79446"))
|
||||
(wire (pts (xy 72.39 130.81) (xy 59.69 130.81)) (stroke (width 0) (type default)) (uuid "9d3c585c-1f7e-5a44-bcad-7339d79884d3"))
|
||||
(label "BTN_NEXT" (at 59.69 130.81 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "0d34aeea-5456-5b2f-8971-ad47f4c23529"))
|
||||
(no_connect (at 72.39 133.35) (uuid "bcd294b1-7855-539a-88ec-8de20fec460c"))
|
||||
(wire (pts (xy 72.39 135.89) (xy 59.69 135.89)) (stroke (width 0) (type default)) (uuid "8294090c-9506-522a-b079-913fdf29a6ad"))
|
||||
(label "GND" (at 59.69 135.89 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "8614b2df-be43-533a-b997-e83d9f4c10f9"))
|
||||
(wire (pts (xy 118.11 82.55) (xy 130.81 82.55)) (stroke (width 0) (type default)) (uuid "cde93e84-4bb2-5344-b18d-5f88f9a1f470"))
|
||||
(label "GND" (at 130.81 82.55 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "1d57f5aa-46b1-5519-ab0d-26076787e212"))
|
||||
(no_connect (at 118.11 85.09) (uuid "b0127af0-5316-5fdd-a572-e50e49541d94"))
|
||||
(no_connect (at 118.11 87.63) (uuid "8171ff7a-3067-5768-aba5-6df8c9bb0001"))
|
||||
(no_connect (at 118.11 90.17) (uuid "4e3e062e-eb44-5b93-ad09-fb7e0c85f42f"))
|
||||
(no_connect (at 118.11 92.71) (uuid "d3db7568-87aa-5f7a-809f-c72a30f1d126"))
|
||||
(no_connect (at 118.11 95.25) (uuid "16df0850-16e0-5696-8909-523afebe0b32"))
|
||||
(no_connect (at 118.11 97.79) (uuid "0a438c8b-add8-5a25-b26a-242898998c02"))
|
||||
(no_connect (at 118.11 100.33) (uuid "13b2680e-cbda-5e35-87d8-7342c24bee65"))
|
||||
(no_connect (at 118.11 102.87) (uuid "071dfd7b-7397-52ad-9228-900c74ee3cd6"))
|
||||
(no_connect (at 118.11 105.41) (uuid "0f2099f9-3f16-5cdd-9e67-692d8c63d801"))
|
||||
(no_connect (at 118.11 107.95) (uuid "1b7e29af-3042-5688-b8e3-b9072f2d45bc"))
|
||||
(no_connect (at 118.11 110.49) (uuid "da9f9ee2-d65e-5e7c-b541-066369051c98"))
|
||||
(no_connect (at 118.11 113.03) (uuid "c09c2fc0-4f3a-5a00-a922-dbf5240160d0"))
|
||||
(no_connect (at 118.11 115.57) (uuid "45c970fe-f750-5241-b68f-a3210b95f132"))
|
||||
(no_connect (at 118.11 118.11) (uuid "706fb938-caad-5342-90a4-6aa8d26a6983"))
|
||||
(no_connect (at 118.11 120.65) (uuid "c9aaa7a4-9c4c-5e1e-b271-2c56582d90c1"))
|
||||
(no_connect (at 118.11 123.19) (uuid "b205a9f5-8146-532d-ad52-36338d4e5a24"))
|
||||
(no_connect (at 118.11 125.73) (uuid "30825573-28ae-50d6-8a7d-0b77a1808f1f"))
|
||||
(no_connect (at 118.11 128.27) (uuid "9f2dc584-46f4-512b-90a1-38635223684a"))
|
||||
(no_connect (at 118.11 130.81) (uuid "ab0f10b3-46d5-5781-a1e1-7d23622f3c22"))
|
||||
(wire (pts (xy 118.11 133.35) (xy 130.81 133.35)) (stroke (width 0) (type default)) (uuid "bb4a0a06-459f-57bb-a130-ac9bfacba61e"))
|
||||
(label "GND" (at 130.81 133.35 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "bb66c329-fbef-5925-99b1-cc08edbbe010"))
|
||||
(wire (pts (xy 118.11 135.89) (xy 130.81 135.89)) (stroke (width 0) (type default)) (uuid "4571a4ff-b89e-51b0-adc4-45846a4d8d8c"))
|
||||
(label "GND" (at 130.81 135.89 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "9c4d50e5-720c-5ef0-8c5e-a0a59725f0a8"))
|
||||
(symbol (lib_id "Carrier:MAX3243_Module_Reference") (at 228.6 96.52 0) (unit 1) (in_bom yes) (on_board yes) (dnp no) (uuid "1127195e-e393-5d1d-9119-b25d10556144")
|
||||
(property "Reference" "U2" (at 228.6 73.66 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "Adafruit MAX3243 / male 6253" (at 228.6 76.2 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "" (at 228.6 96.52 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 228.6 96.52 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Status" "VERIFY male header numbering; footprint intentionally unassigned" (at 228.6 96.52 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet_source" "adafruit-research.md; reference female 5988 CAD" (at 228.6 96.52 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(pin "1" (uuid "14d1ea7b-f53a-592f-9305-545890b94099"))
|
||||
(pin "2" (uuid "b2027da1-66f1-5c08-b29b-b63e30a160ff"))
|
||||
(pin "3" (uuid "5d477802-5ff5-5dda-b518-af959915061c"))
|
||||
(pin "4" (uuid "86c4ca1b-d379-5b5c-93d3-383e356ea671"))
|
||||
(pin "5" (uuid "280aecb0-4e1c-5a83-b678-4ef080970881"))
|
||||
(pin "6" (uuid "339f561b-dd28-56b7-bd1b-1a8b222b3494"))
|
||||
(pin "7" (uuid "1960dc0c-b0fa-5dd9-9623-258d944e85d7"))
|
||||
(pin "8" (uuid "97cacb1f-982d-5831-b852-2ced708e5b19"))
|
||||
(pin "9" (uuid "40aa2c26-8837-51c7-89f3-c91fdfa456de"))
|
||||
(pin "10" (uuid "a890a4e8-65ab-5a8b-a491-6b1d4531d079"))
|
||||
(pin "11" (uuid "ec2dfe5e-4072-560e-880e-ab8f8a85cbe9"))
|
||||
(pin "12" (uuid "7e4f35fe-028a-5516-8170-04eadc2b1c28"))
|
||||
(instances (project "serial-carrier" (path "/c86e0d56-4290-5f2b-b790-14926c8cdeea" (reference "U2") (unit 1)))))
|
||||
(wire (pts (xy 208.28 82.55) (xy 195.58 82.55)) (stroke (width 0) (type default)) (uuid "e93d1edd-22c3-5eae-800d-3921e5221a7c"))
|
||||
(label "+3V3" (at 195.58 82.55 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "ac84abc3-1072-5498-9c83-e40d54a3bea6"))
|
||||
(wire (pts (xy 208.28 85.09) (xy 195.58 85.09)) (stroke (width 0) (type default)) (uuid "82783bca-950c-5c43-996b-9408064b2edf"))
|
||||
(label "GND" (at 195.58 85.09 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "75ec2808-cc79-570e-9845-9fb0a98d3231"))
|
||||
(wire (pts (xy 208.28 87.63) (xy 195.58 87.63)) (stroke (width 0) (type default)) (uuid "c0de1ff8-0c7f-5ae8-ba62-2b3302eadc93"))
|
||||
(label "RS_DCD" (at 195.58 87.63 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "9904c95d-f8a0-5894-afa6-445f8a3cfa00"))
|
||||
(wire (pts (xy 208.28 90.17) (xy 195.58 90.17)) (stroke (width 0) (type default)) (uuid "8d826d42-7bf4-55d8-b1f8-8346d7a75102"))
|
||||
(label "RS_RX" (at 195.58 90.17 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "4edfb969-0023-5b12-842b-d010d64b6c9f"))
|
||||
(wire (pts (xy 208.28 92.71) (xy 195.58 92.71)) (stroke (width 0) (type default)) (uuid "1de1a809-0c2b-5c66-93fa-b6572d03b40f"))
|
||||
(label "RS_TX" (at 195.58 92.71 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "8a97d238-7df7-5201-a7c2-215b084d0edd"))
|
||||
(wire (pts (xy 208.28 95.25) (xy 195.58 95.25)) (stroke (width 0) (type default)) (uuid "6df22e5c-3f66-557d-b3d0-d2ed031525c5"))
|
||||
(label "RS_DTR" (at 195.58 95.25 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "7c80f558-16d4-5ba6-b673-c2b2cbff09e1"))
|
||||
(wire (pts (xy 208.28 97.79) (xy 195.58 97.79)) (stroke (width 0) (type default)) (uuid "886620b9-42d6-52e1-8483-23facfd02f19"))
|
||||
(label "RS_DSR" (at 195.58 97.79 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "a0a94ae8-b47e-5b05-846f-0efd8f397f55"))
|
||||
(wire (pts (xy 208.28 100.33) (xy 195.58 100.33)) (stroke (width 0) (type default)) (uuid "70dbba76-347e-5f98-8da2-e037f8f592f9"))
|
||||
(label "RS_RTS" (at 195.58 100.33 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "09b654e4-afd9-5168-9822-b62724f90a67"))
|
||||
(wire (pts (xy 208.28 102.87) (xy 195.58 102.87)) (stroke (width 0) (type default)) (uuid "1894bdb0-a2a3-5854-a12b-6a69d76e862b"))
|
||||
(label "RS_CTS" (at 195.58 102.87 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "cf747b92-81c2-5cca-8e92-d01f0789fe3f"))
|
||||
(wire (pts (xy 208.28 105.41) (xy 195.58 105.41)) (stroke (width 0) (type default)) (uuid "3e973901-b924-5ee4-a21b-264b3911c50c"))
|
||||
(label "RS_RI" (at 195.58 105.41 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "cda14f57-0d83-5d8d-a65d-3fd304c5cef9"))
|
||||
(wire (pts (xy 208.28 107.95) (xy 195.58 107.95)) (stroke (width 0) (type default)) (uuid "1fa8fc2c-f25a-5d81-8a72-ab7b3168abd2"))
|
||||
(label "RS_VALID" (at 195.58 107.95 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "7e7957b3-d8ff-51ef-996c-19b68b0254a0"))
|
||||
(wire (pts (xy 208.28 110.49) (xy 195.58 110.49)) (stroke (width 0) (type default)) (uuid "ae100e76-5158-5372-b653-0e3d739e4d8d"))
|
||||
(label "RS_OFF_N" (at 195.58 110.49 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "734376cf-c18c-512e-a0a4-10246dd2fa26"))
|
||||
(symbol (lib_id "Carrier:OLED_I2C_26mm") (at 228.6 152.4 0) (unit 1) (in_bom yes) (on_board yes) (dnp no) (uuid "0ae411c1-94cc-5551-9037-1d59c01a8ee5")
|
||||
(property "Reference" "DS1" (at 228.6 139.7 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "128x64 I2C OLED / 0x3C" (at 228.6 142.24 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "Carrier:OLED_26mm_I2C_Provisional" (at 228.6 152.4 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 228.6 152.4 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Status" "Module outline/header/display user fit-verified; pad/drill fit provisional" (at 228.6 152.4 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(pin "1" (uuid "0d23b99f-a7aa-5862-b319-434604b281fc"))
|
||||
(pin "2" (uuid "5697134b-5fea-552c-a972-f5008c22b049"))
|
||||
(pin "3" (uuid "8b10ead6-0242-5709-97f0-2f4290180cdd"))
|
||||
(pin "4" (uuid "3908fc82-c607-5bec-ab4c-90efe237abf6"))
|
||||
(instances (project "serial-carrier" (path "/c86e0d56-4290-5f2b-b790-14926c8cdeea" (reference "DS1") (unit 1)))))
|
||||
(wire (pts (xy 213.36 148.59) (xy 200.66 148.59)) (stroke (width 0) (type default)) (uuid "41bf2da6-78f1-52d1-9f16-4b8edb2221fe"))
|
||||
(label "GND" (at 200.66 148.59 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "7942dcf6-75b1-533a-b9d5-c9a5c9032399"))
|
||||
(wire (pts (xy 213.36 151.13) (xy 200.66 151.13)) (stroke (width 0) (type default)) (uuid "8da97684-8a90-54ff-b1ca-8bc93e05eacc"))
|
||||
(label "+3V3" (at 200.66 151.13 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "5388601d-60c8-5d05-b5b4-c0abd390b4b2"))
|
||||
(wire (pts (xy 213.36 153.67) (xy 200.66 153.67)) (stroke (width 0) (type default)) (uuid "459e4f2f-bf9d-5e2f-a3c6-35fbef8ce0cf"))
|
||||
(label "OLED_SCL" (at 200.66 153.67 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "abe6b20c-25f7-555c-b995-c60fe96c0db5"))
|
||||
(wire (pts (xy 213.36 156.21) (xy 200.66 156.21)) (stroke (width 0) (type default)) (uuid "9a563337-ee5e-5679-a957-fac4fe55933a"))
|
||||
(label "OLED_SDA" (at 200.66 156.21 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "dc86b9ed-3904-50d0-8423-c02004518d88"))
|
||||
(symbol (lib_id "Carrier:R") (at 327.66 88.9 0) (unit 1) (in_bom yes) (on_board yes) (dnp no) (uuid "defb78eb-632f-5f9d-a59d-dffbb1d31dec")
|
||||
(property "Reference" "R1" (at 327.66 82.804 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "2.2k 1%" (at 327.66 85.344 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal" (at 327.66 88.9 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 327.66 88.9 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Rating" "At least 0.125 W; exact resistor MPN pending" (at 327.66 88.9 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(pin "1" (uuid "79a88116-1a52-5fcb-855e-da54defa9400"))
|
||||
(pin "2" (uuid "b56a5773-68f5-5c6d-936a-8289c11714de"))
|
||||
(instances (project "serial-carrier" (path "/c86e0d56-4290-5f2b-b790-14926c8cdeea" (reference "R1") (unit 1)))))
|
||||
(wire (pts (xy 322.58 88.9) (xy 309.88 88.9)) (stroke (width 0) (type default)) (uuid "72f62c22-6c21-5ce4-95b5-97fe6afaea38"))
|
||||
(label "+3V3" (at 309.88 88.9 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "da597fd4-3753-56ba-8a8c-b7ae3d6d6c20"))
|
||||
(wire (pts (xy 332.74 88.9) (xy 345.44 88.9)) (stroke (width 0) (type default)) (uuid "a965fd28-156e-51a9-a567-a09ee3288b26"))
|
||||
(label "BTN_PREVIOUS" (at 345.44 88.9 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "b0cdb46e-c123-5a1b-9450-8d6d573bf376"))
|
||||
(symbol (lib_id "Carrier:SW_NO") (at 327.66 104.14 0) (unit 1) (in_bom yes) (on_board yes) (dnp no) (uuid "dd5f1f75-0093-55fc-9814-437ed26871eb")
|
||||
(property "Reference" "SW1" (at 327.66 96.52 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "B3F-1000" (at 327.66 99.06 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x" (at 327.66 104.14 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 327.66 104.14 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Manufacturer" "Omron" (at 327.66 104.14 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "MPN" "B3F-1000" (at 327.66 104.14 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Function" "Previous / back" (at 327.66 104.14 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Procurement" "Live stock not verified" (at 327.66 104.14 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(pin "1" (uuid "0e490c78-3450-5a5b-b7d3-d55c32de988b"))
|
||||
(pin "2" (uuid "6bbc82d1-8cfb-5fd5-832c-8df2775a89c9"))
|
||||
(instances (project "serial-carrier" (path "/c86e0d56-4290-5f2b-b790-14926c8cdeea" (reference "SW1") (unit 1)))))
|
||||
(wire (pts (xy 322.58 104.14) (xy 309.88 104.14)) (stroke (width 0) (type default)) (uuid "5b028b05-8a36-55db-aaf9-9b382e614203"))
|
||||
(label "BTN_PREVIOUS" (at 309.88 104.14 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "5e45c195-3cf8-5a7b-93e1-84b53b154824"))
|
||||
(wire (pts (xy 332.74 104.14) (xy 345.44 104.14)) (stroke (width 0) (type default)) (uuid "4036fecd-dbe2-5786-a9b9-dce5b14fbf7f"))
|
||||
(label "GND" (at 345.44 104.14 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "d3b7a7cc-4c33-5080-b795-7892d393bb53"))
|
||||
(text "Previous / back" (at 302.26 110.49 0) (effects (font (size 1.016 1.016)) (justify left top)) (uuid "d84df288-be52-5de4-899d-bf29ba78f4f2"))
|
||||
(symbol (lib_id "Carrier:R") (at 327.66 124.46 0) (unit 1) (in_bom yes) (on_board yes) (dnp no) (uuid "405055c0-35ab-5334-ad4d-30d45c5ef6f2")
|
||||
(property "Reference" "R2" (at 327.66 118.364 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "2.2k 1%" (at 327.66 120.904 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal" (at 327.66 124.46 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 327.66 124.46 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Rating" "At least 0.125 W; exact resistor MPN pending" (at 327.66 124.46 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(pin "1" (uuid "c23dbef2-d61d-56fa-a158-bb9912ef48ac"))
|
||||
(pin "2" (uuid "f60eb205-60d1-5d8f-ac84-ca04f66e7106"))
|
||||
(instances (project "serial-carrier" (path "/c86e0d56-4290-5f2b-b790-14926c8cdeea" (reference "R2") (unit 1)))))
|
||||
(wire (pts (xy 322.58 124.46) (xy 309.88 124.46)) (stroke (width 0) (type default)) (uuid "eb68b8eb-dbe6-5133-8089-e9de01165396"))
|
||||
(label "+3V3" (at 309.88 124.46 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "5bef1a07-0985-5bb8-9b36-ed0c9e2c82ab"))
|
||||
(wire (pts (xy 332.74 124.46) (xy 345.44 124.46)) (stroke (width 0) (type default)) (uuid "b3b13ef4-4203-52d8-889b-70534a4a1b28"))
|
||||
(label "BTN_SELECT" (at 345.44 124.46 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "e8d69aad-ffba-5ace-a414-b44ff7528289"))
|
||||
(symbol (lib_id "Carrier:SW_NO") (at 327.66 139.7 0) (unit 1) (in_bom yes) (on_board yes) (dnp no) (uuid "c522a716-55a4-584d-a12e-ab135b4c08eb")
|
||||
(property "Reference" "SW2" (at 327.66 132.08 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "B3F-1000" (at 327.66 134.62 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x" (at 327.66 139.7 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 327.66 139.7 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Manufacturer" "Omron" (at 327.66 139.7 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "MPN" "B3F-1000" (at 327.66 139.7 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Function" "Select / confirm" (at 327.66 139.7 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Procurement" "Live stock not verified" (at 327.66 139.7 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(pin "1" (uuid "19bafbc6-666f-5e53-a4ef-265057c6dc8a"))
|
||||
(pin "2" (uuid "9e3f47ba-92ac-5503-9d87-6b5590a70ccb"))
|
||||
(instances (project "serial-carrier" (path "/c86e0d56-4290-5f2b-b790-14926c8cdeea" (reference "SW2") (unit 1)))))
|
||||
(wire (pts (xy 322.58 139.7) (xy 309.88 139.7)) (stroke (width 0) (type default)) (uuid "05f5a256-5e68-53ae-a051-81eab3a14d4d"))
|
||||
(label "BTN_SELECT" (at 309.88 139.7 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "a90ee2dc-ed10-5bb9-9082-99cdbfeac4c9"))
|
||||
(wire (pts (xy 332.74 139.7) (xy 345.44 139.7)) (stroke (width 0) (type default)) (uuid "04211708-439f-5d65-a5c6-21cf89318109"))
|
||||
(label "GND" (at 345.44 139.7 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "7f92da60-87bd-5c50-afc0-4046d0c67767"))
|
||||
(text "Select / confirm" (at 302.26 146.05 0) (effects (font (size 1.016 1.016)) (justify left top)) (uuid "c28f2639-f392-552b-858e-b031aabc8c0a"))
|
||||
(symbol (lib_id "Carrier:R") (at 327.66 160.02 0) (unit 1) (in_bom yes) (on_board yes) (dnp no) (uuid "78f9a74f-5d91-5687-b50e-68176333df23")
|
||||
(property "Reference" "R3" (at 327.66 153.924 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "2.2k 1%" (at 327.66 156.464 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal" (at 327.66 160.02 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 327.66 160.02 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Rating" "At least 0.125 W; exact resistor MPN pending" (at 327.66 160.02 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(pin "1" (uuid "d871ee2e-bc82-5f23-a436-7b7ab74b73a9"))
|
||||
(pin "2" (uuid "96528b84-ba49-5f38-8445-b6ca2cadc93b"))
|
||||
(instances (project "serial-carrier" (path "/c86e0d56-4290-5f2b-b790-14926c8cdeea" (reference "R3") (unit 1)))))
|
||||
(wire (pts (xy 322.58 160.02) (xy 309.88 160.02)) (stroke (width 0) (type default)) (uuid "ad7355d6-b196-5b55-b518-28095d4cd010"))
|
||||
(label "+3V3" (at 309.88 160.02 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "08ba6112-d16a-5597-b3f3-8794ed1ea849"))
|
||||
(wire (pts (xy 332.74 160.02) (xy 345.44 160.02)) (stroke (width 0) (type default)) (uuid "7a2af359-d23d-5777-9193-ff203ac538d8"))
|
||||
(label "BTN_NEXT" (at 345.44 160.02 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "5011a121-e49f-5298-8cc1-2998dfa40f7d"))
|
||||
(symbol (lib_id "Carrier:SW_NO") (at 327.66 175.26 0) (unit 1) (in_bom yes) (on_board yes) (dnp no) (uuid "35caa6bb-42a6-512c-848c-f1e4457aa97f")
|
||||
(property "Reference" "SW3" (at 327.66 167.64 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "B3F-1000" (at 327.66 170.18 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x" (at 327.66 175.26 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 327.66 175.26 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Manufacturer" "Omron" (at 327.66 175.26 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "MPN" "B3F-1000" (at 327.66 175.26 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Function" "Next" (at 327.66 175.26 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Procurement" "Live stock not verified" (at 327.66 175.26 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(pin "1" (uuid "85beb3b7-e16f-5450-ba94-5711245a7b25"))
|
||||
(pin "2" (uuid "300c4196-eb91-5e58-a9aa-c84b8f7c4219"))
|
||||
(instances (project "serial-carrier" (path "/c86e0d56-4290-5f2b-b790-14926c8cdeea" (reference "SW3") (unit 1)))))
|
||||
(wire (pts (xy 322.58 175.26) (xy 309.88 175.26)) (stroke (width 0) (type default)) (uuid "899495f9-e27d-5c7f-9961-bda42dd5b5b5"))
|
||||
(label "BTN_NEXT" (at 309.88 175.26 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "4917cd5a-7d37-54f4-8139-7ae4739c44c4"))
|
||||
(wire (pts (xy 332.74 175.26) (xy 345.44 175.26)) (stroke (width 0) (type default)) (uuid "f48c99dc-9974-5205-8a87-3b1278a4fdfd"))
|
||||
(label "GND" (at 345.44 175.26 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "3c7713ca-d8f2-53fd-8e98-6fd376e4570c"))
|
||||
(text "Next" (at 302.26 181.61 0) (effects (font (size 1.016 1.016)) (justify left top)) (uuid "0db41e9e-455f-5007-82ee-b485aacda4c4"))
|
||||
(text "MODULE CARRIER - INITIAL SCHEMATIC" (at 15.24 15.24 0) (effects (font (size 2.54 2.54)) (justify left top)) (uuid "81374533-4a9c-521b-8834-9fa30bb907b5"))
|
||||
(text "Native KiCad 10 schematic. Named wire labels join matching nets on this sheet.\nRead hardware/PCB/README.md before layout or fabrication." (at 15.24 24.13 0) (effects (font (size 1.27 1.27)) (justify left top)) (uuid "d9d89d85-2a9c-5e76-bd60-48c81ac7f524"))
|
||||
(text "U1: complete development board" (at 53.34 57.15 0) (effects (font (size 1.778 1.778)) (justify left top)) (uuid "320a8f4e-ff8b-5100-845b-dfe249b39a2e"))
|
||||
(text "Antenna up / USB down: left row 1-22, right row 23-44.\nNC marks mean unused on carrier, not unused inside module." (at 53.34 64.77 0) (effects (font (size 1.016 1.016)) (justify left top)) (uuid "649df1fa-48c0-580d-aeb8-d019ffd0b61d"))
|
||||
(text "U2: complete RS-232 breakout" (at 177.8 57.15 0) (effects (font (size 1.778 1.778)) (justify left top)) (uuid "5226007c-1904-547d-8ddb-008d14b83d51"))
|
||||
(text "Male 6253 selected. Numbers from published 5988 female CAD.\nVERIFY male header before routing; no footprint assigned.\nDE-9 and charge pump are already on the module." (at 177.8 64.77 0) (effects (font (size 1.016 1.016)) (justify left top)) (uuid "9b62e7ca-695a-51af-bbbe-e7ee4ee45c08"))
|
||||
(text "Controls: active-low, firmware-debounced" (at 292.1 57.15 0) (effects (font (size 1.778 1.778)) (justify left top)) (uuid "9d91985c-7375-5177-9a5e-8280e3bcaff6"))
|
||||
(text "External 2.2k pull-ups: ~1.5 mA pressed at 3.3 V.\nChosen for B3F-1000 rated contact load; internal\npull-ups may remain enabled. Exact resistor MPN pending." (at 292.1 64.77 0) (effects (font (size 1.016 1.016)) (justify left top)) (uuid "b2dd72f5-86fc-5ce1-b09a-67a6fe9404d4"))
|
||||
(text "DS1: front view GND / VCC / SCL / SDA" (at 177.8 128.27 0) (effects (font (size 1.524 1.524)) (justify left top)) (uuid "7b57748d-38ca-5712-97f9-bf6dab6dafaf"))
|
||||
(text "Power at 3.3 V only. Verify on-module I2C pull-ups;\nexternal values intentionally not guessed." (at 177.8 165.1 0) (effects (font (size 1.016 1.016)) (justify left top)) (uuid "fb23ec11-40f8-56c7-b78b-0da2d40e4b72"))
|
||||
(text "POWER / RECOVERY\nPower only through U1's existing USB ports. No carrier 5 V input.\nU1 regulator supplies +3V3; GND pins are common on the module.\nPin 1 +3V3 and pin 22 GND model the onboard supply for ERC.\nGPIO19/20 native USB and GPIO43/44 UART0 remain onboard.\nVerify USB power isolation before attaching two powered hosts.\nKeep both USB connectors and reset/boot buttons accessible." (at 15.24 185.42 0) (effects (font (size 1.27 1.27)) (justify left top)) (uuid "cadf6b77-e888-5f04-b4f6-3873114844ca"))
|
||||
(text "RS-232 / ELECTRICAL LIMITS\nTX/RX and modem labels are at the ESP32 logic side.\nOFF_N is active low; GPIO9 must remain open-drain.\nBreakout provides its OFF pull-up. No galvanic isolation.\nNo carrier connection to raw RS-232 voltages.\nCarrier GND, USB GND and RS-232 signal ground are common.\nNever power OLED or MAX3243 logic from 5 V." (at 152.4 185.42 0) (effects (font (size 1.27 1.27)) (justify left top)) (uuid "0f7da388-3edb-5cc3-9937-13492397a221"))
|
||||
(text "BEFORE PCB RELEASE\nVerify male breakout pin order/footprint.\nConfirm header/socket finished-hole requirements.\nComplete module courtyards and antenna clearance.\nVerify 3.3 V budget, OLED pull-ups and USB power path.\nChoose mounting hardware and confirm button stock.\nERC is not hardware or fabrication approval." (at 292.1 185.42 0) (effects (font (size 1.27 1.27)) (justify left top)) (uuid "1df2bd9e-fbff-5047-92a9-09f33d7cd7ac"))
|
||||
)
|
||||
@@ -0,0 +1,3 @@
|
||||
(sym_lib_table (version 7)
|
||||
(lib (name "Carrier")(type "KiCad")(uri "${KIPRJMOD}/Carrier.kicad_sym")(options "")(descr "Project-local module interfaces"))
|
||||
)
|
||||
@@ -0,0 +1,129 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Read-only regression checks for the two provisional module footprints.
|
||||
|
||||
Run with the system Python that provides KiCad's installed pcbnew binding:
|
||||
/usr/bin/python3 -B hardware/PCB/validate_footprints.py
|
||||
|
||||
Paths resolve relative to this script, independent of the working directory.
|
||||
No board, reports, or footprint files are written. Passing checks do not establish
|
||||
physical connector fit, electrical correctness, DRC, or fabrication readiness.
|
||||
"""
|
||||
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
try:
|
||||
import pcbnew as k
|
||||
except ImportError as exc:
|
||||
raise SystemExit(
|
||||
"KiCad pcbnew binding unavailable. Use the system Python associated with "
|
||||
"your KiCad installation (on this machine: /usr/bin/python3)."
|
||||
) from exc
|
||||
|
||||
|
||||
LIBRARY = Path(__file__).resolve().parent / "Carrier.pretty"
|
||||
|
||||
|
||||
def xy(vector):
|
||||
"""Keep native integer coordinates so comparisons do not hide deviations."""
|
||||
return vector.x, vector.y
|
||||
|
||||
|
||||
def mm(x, y):
|
||||
return k.FromMM(x), k.FromMM(y)
|
||||
|
||||
|
||||
class FootprintRegression(unittest.TestCase):
|
||||
def load(self, name):
|
||||
self.assertTrue((LIBRARY / (name + ".kicad_mod")).is_file(), name)
|
||||
footprint = k.FootprintLoad(str(LIBRARY), name)
|
||||
self.assertIsNotNone(footprint, f"KiCad could not parse {name}")
|
||||
self.assertEqual(footprint.GetValue(), name)
|
||||
self.assertEqual(footprint.GetLayer(), k.F_Cu)
|
||||
self.assertIn("PROVISIONAL connector fit", footprint.GetLibDescription())
|
||||
self.assertEqual(len(list(footprint.Zones())), 0, "Unexpected zone/keepout")
|
||||
self.assertEqual(len(list(footprint.Models())), 0, "Unexpected 3D envelope")
|
||||
graphics = list(footprint.GraphicalItems())
|
||||
for item in graphics:
|
||||
self.assertNotIn(item.GetLayer(), (k.F_CrtYd, k.B_CrtYd, k.Edge_Cuts))
|
||||
self.assertIsInstance(item, (k.PCB_TEXT, k.PCB_SHAPE),
|
||||
"Unexpected graphic/envelope type")
|
||||
self.assertTrue(any(isinstance(item, k.PCB_TEXT)
|
||||
and item.GetText() == "PROVISIONAL CONNECTOR FIT"
|
||||
for item in graphics), "Missing visible provisional label")
|
||||
return footprint
|
||||
|
||||
def check_header(self, footprint, positions, hole_count=0):
|
||||
pads = list(footprint.Pads())
|
||||
self.assertEqual(len(pads), len(positions) + hole_count)
|
||||
numbered = [pad for pad in pads if pad.GetNumber()]
|
||||
# Compare multiplicities so duplicate numbers cannot hide missing pads.
|
||||
self.assertEqual(Counter(p.GetNumber() for p in numbered),
|
||||
Counter({str(n): 1 for n in positions}))
|
||||
for pad in numbered:
|
||||
number = int(pad.GetNumber())
|
||||
with self.subTest(pad=number):
|
||||
self.assertEqual(xy(pad.GetPosition()), mm(*positions[number]))
|
||||
self.assertEqual(xy(pad.GetSize()), mm(1.7, 1.7))
|
||||
self.assertEqual(xy(pad.GetDrillSize()), mm(1, 1))
|
||||
self.assertEqual(pad.GetDrillShape(), k.PAD_DRILL_SHAPE_CIRCLE)
|
||||
self.assertEqual(pad.GetAttribute(), k.PAD_ATTRIB_PTH)
|
||||
self.assertEqual(pad.GetShape(),
|
||||
k.PAD_SHAPE_RECT if number == 1 else k.PAD_SHAPE_CIRCLE)
|
||||
self.assertEqual(pad.GetNetCode(), 0, "Unexpected assigned net")
|
||||
for layer in (k.F_Cu, k.B_Cu, k.F_Mask, k.B_Mask):
|
||||
self.assertTrue(pad.IsOnLayer(layer), "Missing copper/mask layer")
|
||||
for layer in (k.F_Paste, k.B_Paste):
|
||||
self.assertFalse(pad.IsOnLayer(layer), "Unexpected paste layer")
|
||||
return [pad for pad in pads if not pad.GetNumber()]
|
||||
|
||||
def test_oled(self):
|
||||
footprint = self.load("OLED_26mm_I2C_Provisional")
|
||||
positions = {n: (x, 1.5) for n, x in
|
||||
enumerate((9.19, 11.73, 14.27, 16.81), 1)}
|
||||
holes = self.check_header(footprint, positions, hole_count=4)
|
||||
self.assertEqual(Counter(xy(p.GetPosition()) for p in holes),
|
||||
Counter(mm(x, y) for x, y in (
|
||||
(1.75, 1.75), (24.25, 1.75),
|
||||
(1.75, 24.25), (24.25, 24.25))))
|
||||
for pad in holes:
|
||||
with self.subTest(hole=xy(pad.GetPosition())):
|
||||
self.assertEqual(pad.GetAttribute(), k.PAD_ATTRIB_NPTH)
|
||||
self.assertEqual(pad.GetShape(), k.PAD_SHAPE_CIRCLE)
|
||||
self.assertEqual(pad.GetDrillShape(), k.PAD_DRILL_SHAPE_CIRCLE)
|
||||
self.assertEqual(xy(pad.GetSize()), mm(2, 2))
|
||||
self.assertEqual(xy(pad.GetDrillSize()), mm(2, 2))
|
||||
self.assertEqual(pad.GetNetCode(), 0)
|
||||
shapes = [g for g in footprint.GraphicalItems() if isinstance(g, k.PCB_SHAPE)]
|
||||
self.assertEqual(len(shapes), 2, "Only board/display rectangles are verified")
|
||||
self.assertEqual(Counter((xy(g.GetStart()), xy(g.GetEnd())) for g in shapes),
|
||||
Counter([(mm(0, 0), mm(26, 26)),
|
||||
(mm(1, 4.5), mm(25.5, 21))]))
|
||||
for shape in shapes:
|
||||
self.assertEqual(shape.GetShape(), k.SHAPE_T_RECT)
|
||||
self.assertEqual(shape.GetLayer(), k.F_Fab)
|
||||
self.assertEqual(shape.GetWidth(), k.FromMM(0.1))
|
||||
self.assertFalse(shape.IsAnyFill())
|
||||
# Stroke styles are not checked: this binding returns opaque LINE_STYLE
|
||||
# pointers, so comparing GetLineStyle() results would compare identities,
|
||||
# not the solid/dash values, and also emits SWIG leak warnings.
|
||||
|
||||
def test_hw678(self):
|
||||
footprint = self.load("HW678_2x22_Provisional")
|
||||
positions = {
|
||||
row * 22 + index + 1: (x, round(index * 2.54, 2))
|
||||
for row, x in enumerate((0, 25.4)) for index in range(22)
|
||||
}
|
||||
self.check_header(footprint, positions)
|
||||
self.assertFalse(any(isinstance(g, k.PCB_SHAPE)
|
||||
for g in footprint.GraphicalItems()),
|
||||
"HW678 must not acquire guessed outlines or envelopes")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(f"KiCad {k.GetBuildVersion()}; library: {LIBRARY}", flush=True)
|
||||
suite = unittest.defaultTestLoader.loadTestsFromTestCase(FootprintRegression)
|
||||
result = unittest.TextTestRunner(verbosity=2).run(suite)
|
||||
sys.exit(0 if result.wasSuccessful() else 1)
|
||||
@@ -0,0 +1,129 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Read-only PCB checks and fresh KiCad reports; does not regenerate or save PCB.
|
||||
|
||||
Requires KiCad's pcbnew binding in system Python. The known U2 footprint parity
|
||||
warning is reported explicitly, not hidden or added to KiCad's exclusions.
|
||||
"""
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import xml.etree.ElementTree as ET
|
||||
import pcbnew as p
|
||||
|
||||
ROOT=Path(__file__).resolve().parent
|
||||
PCB=ROOT/"serial-carrier.kicad_pcb"
|
||||
OUT=ROOT/"validation"
|
||||
|
||||
def run(*args):
|
||||
subprocess.run(args,check=True,cwd=ROOT)
|
||||
|
||||
run("kicad-cli","sch","export","netlist","--format","kicadxml","-o",str(OUT/"pcb-source-netlist.xml"),str(ROOT/"serial-carrier.kicad_sch"))
|
||||
run("kicad-cli","pcb","drc","--format","json","--all-track-errors","--severity-all","--schematic-parity","-o",str(OUT/"pcb-parity-drc.json"),str(PCB))
|
||||
board=p.LoadBoard(str(PCB))
|
||||
source=ET.parse(OUT/"pcb-source-netlist.xml").getroot()
|
||||
comps={c.get("ref"):c for c in source.findall("components/comp")}
|
||||
footprints={f.GetReference():f for f in board.GetFootprints()}
|
||||
assert set(footprints)==set(comps)|{"H1","H2","H3","H4"}
|
||||
assert board.GetCopperLayerCount()==2
|
||||
assert p.ToMM(board.GetDesignSettings().GetBoardThickness())==1.6
|
||||
outline=[d for d in board.GetDrawings() if d.GetLayer()==p.Edge_Cuts]
|
||||
assert len(outline)==6
|
||||
points={(round(p.ToMM(v.x)-100,5),round(p.ToMM(v.y)-100,5)) for d in outline for v in (d.GetStart(),d.GetEnd())}
|
||||
expected_outline=[(36,0),(82,0),(82,80),(0,80),(0,19),(36,19)]
|
||||
assert points==set(expected_outline)
|
||||
expected_edges={frozenset((a,b)) for a,b in zip(expected_outline,expected_outline[1:]+expected_outline[:1])}
|
||||
actual_edges={frozenset((round(p.ToMM(v.x)-100,5),round(p.ToMM(v.y)-100,5))
|
||||
for v in (d.GetStart(),d.GetEnd())) for d in outline}
|
||||
assert actual_edges==expected_edges
|
||||
placements={"U1":(8,23),"U2":(43,26.670),"DS1":(43,36),
|
||||
"R1":(43,34),"R2":(54,34),"R3":(65,34),
|
||||
"SW1":(43,68),"SW2":(55,68),"SW3":(67,68),
|
||||
"H1":(3.5,22.5),"H2":(3.5,75),"H3":(78,4),"H4":(78,76)}
|
||||
for ref,(x,y) in placements.items():
|
||||
position=footprints[ref].GetPosition()
|
||||
assert position.x==p.FromMM(x+100) and position.y==p.FromMM(y+100),ref
|
||||
assert footprints[ref].GetOrientationDegrees()==0,ref
|
||||
assert len(board.Zones())==1
|
||||
reserve=board.Zones()[0]
|
||||
assert reserve.GetIsRuleArea() and reserve.IsOnLayer(p.F_Cu) and reserve.IsOnLayer(p.B_Cu)
|
||||
assert reserve.GetDoNotAllowTracks() and reserve.GetDoNotAllowVias() and reserve.GetDoNotAllowZoneFills()
|
||||
contour=reserve.Outline().COutline(0)
|
||||
assert {(p.ToMM(contour.CPoint(i).x),p.ToMM(contour.CPoint(i).y)) for i in range(contour.PointCount())}=={(110,119),(131,119),(131,132),(110,132)}
|
||||
# Reference bare-PCB front is 26.670 mm above the U2 pad-row origin.
|
||||
u2_front=p.ToMM(footprints['U2'].GetPosition().y)-26.670
|
||||
assert abs(u2_front-100)<1e-6
|
||||
assert any(isinstance(g,p.PCB_SHAPE) and g.GetLayer()==p.F_Fab and
|
||||
g.GetShape()==p.SHAPE_T_SEGMENT and
|
||||
g.GetStart().y==p.FromMM(100) and g.GetEnd().y==p.FromMM(100)
|
||||
for g in footprints['U2'].GraphicalItems()), 'U2 bare-PCB front not flush'
|
||||
assert max(x for x,y in points)-min(x for x,y in points)==82
|
||||
assert max(y for x,y in points)-min(y for x,y in points)==80
|
||||
padnets={}
|
||||
for net in source.findall("nets/net"):
|
||||
for node in net.findall("node"):
|
||||
padnets[(node.get("ref"),node.get("pin"))]=net.get("name")
|
||||
seen=set()
|
||||
for ref,comp in comps.items():
|
||||
fp=footprints[ref]
|
||||
assert fp.GetValue()==comp.findtext("value"),ref
|
||||
assert fp.GetPath().AsString().endswith("/"+comp.findtext("tstamps")),ref
|
||||
wanted=comp.findtext("footprint")
|
||||
if ref=="U2":
|
||||
assert not wanted
|
||||
assert fp.GetFPIDAsString()=="Carrier:MAX3243_Reference_Provisional"
|
||||
else:
|
||||
assert fp.GetFPIDAsString()==wanted,ref
|
||||
for pad in fp.Pads():
|
||||
if not pad.GetNumber():
|
||||
continue
|
||||
key=ref,pad.GetNumber()
|
||||
assert pad.GetNetname()==padnets[key],(key,pad.GetNetname(),padnets[key])
|
||||
seen.add(key)
|
||||
assert seen==set(padnets),"Missing or extra electrical pads"
|
||||
for ref in ("H1","H2","H3","H4"):
|
||||
assert footprints[ref].IsBoardOnly()
|
||||
pads=list(footprints[ref].Pads())
|
||||
assert len(pads)==1 and pads[0].GetDrillSize().x==p.FromMM(3.2)
|
||||
assert pads[0].GetAttribute()==p.PAD_ATTRIB_NPTH
|
||||
tracks=list(board.GetTracks())
|
||||
assert tracks,"Draft unexpectedly unrouted"
|
||||
for track in tracks:
|
||||
if isinstance(track,p.PCB_VIA):
|
||||
assert track.GetDrillValue()==p.FromMM(0.3)
|
||||
assert track.GetWidth(p.F_Cu)==p.FromMM(0.7)
|
||||
else:
|
||||
assert track.GetLayer() in (p.F_Cu,p.B_Cu)
|
||||
expected=0.5 if track.GetNetname() in ("/+3V3","/GND") else 0.25
|
||||
assert track.GetWidth()==p.FromMM(expected)
|
||||
report=json.loads((OUT/"pcb-parity-drc.json").read_text())
|
||||
assert not report["violations"],report["violations"]
|
||||
assert not report["unconnected_items"],report["unconnected_items"]
|
||||
parity=report["schematic_parity"]
|
||||
assert len(parity)==1 and parity[0]["type"]=="footprint_symbol_mismatch",parity
|
||||
assert len(parity[0]["items"])==1 and parity[0]["items"][0]["uuid"]==footprints["U2"].m_Uuid.AsString()
|
||||
summary={
|
||||
"carrier_bounds_mm":[82,80],
|
||||
"outline_local_mm":expected_outline,
|
||||
"placements_local_mm":placements,
|
||||
"rf_reserve_absolute_mm":[110,119,131,132],
|
||||
"u2_bare_pcb_front_local_y_mm":round(u2_front-100,6),
|
||||
"nominal_user_confirmed_rs232_metal_mm":{"shell_overhang":3.870,"hex_mouth_overhang":2.870,"flange_front_inset":1.930},
|
||||
"tracks":sum(not isinstance(t,p.PCB_VIA) for t in tracks),
|
||||
"vias":sum(isinstance(t,p.PCB_VIA) for t in tracks),
|
||||
"copper_layers":2,
|
||||
"draft_thickness_mm":1.6,
|
||||
"electrical_footprints":len(comps),
|
||||
"proposed_M3_mounts":4,
|
||||
"physical_DRC_violations":0,
|
||||
"unconnected_items":0,
|
||||
"schematic_pad_nets_match":True,
|
||||
"known_parity_warning":"U2 reference footprint assigned on PCB only; schematic footprint intentionally blank; nominal male external metal dimensions user-confirmed",
|
||||
"project_settings_sha256":hashlib.sha256((ROOT/'serial-carrier.kicad_pro').read_bytes()).hexdigest(),
|
||||
"ignored_DRC_checks":report["ignored_checks"],
|
||||
"not_validated":["male module pin map/header fit", "ESP32 geometry and complete assembly envelope/heights", "header/socket fit", "RF performance and antenna clearance", "power/return-path integrity", "USB power path", "OLED pull-ups", "fabrication process", "hardware operation"],
|
||||
}
|
||||
(OUT/"pcb-validation-summary.json").write_text(json.dumps(summary,indent=2)+"\n")
|
||||
print("PCB draft verified: 82 x 80 mm; 2 copper layers; 0 physical DRC violations; 0 unrouted.")
|
||||
print("All schematic pad nets match. One expected U2 footprint parity warning remains visible.")
|
||||
print("Not fabrication-ready; see pcb-draft-notes.md and ignored checks in the DRC report.")
|
||||
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Export and validate the checked-in schematic; never regenerates the design."""
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
OUT = ROOT / "validation"
|
||||
SCHEMATIC = ROOT / "serial-carrier.kicad_sch"
|
||||
|
||||
def run(*args):
|
||||
subprocess.run(args, check=True, cwd=ROOT)
|
||||
|
||||
run("kicad-cli", "sch", "export", "netlist", "--format", "kicadxml", "-o", str(OUT / "serial-carrier.xml"), str(SCHEMATIC))
|
||||
run("kicad-cli", "sch", "erc", "--format", "json", "--exit-code-violations", "-o", str(OUT / "erc.json"), str(SCHEMATIC))
|
||||
root = ET.parse(OUT / "serial-carrier.xml").getroot()
|
||||
nets = {net.get("name").removeprefix("/"): net.findall("node") for net in root.findall("nets/net")}
|
||||
members = {name: sorted((node.get("ref"), node.get("pin")) for node in nodes) for name, nodes in nets.items()}
|
||||
components = {comp.get("ref"): comp for comp in root.findall("components/comp")}
|
||||
assert set(components) == {"U1", "U2", "DS1", "SW1", "SW2", "SW3", "R1", "R2", "R3"}
|
||||
|
||||
# Independent firmware-to-module contract, not imported from the schematic generator.
|
||||
contracts = [
|
||||
("RS_TX", "RS232_TX_GPIO", "TX", "5"),
|
||||
("RS_RX", "RS232_RX_GPIO", "RX", "4"),
|
||||
("RS_RTS", "RS232_RTS_GPIO", "RTS", "8"),
|
||||
("RS_CTS", "RS232_CTS_GPIO", "CTS", "9"),
|
||||
("RS_DTR", "RS232_DTR_GPIO", "DTR", "6"),
|
||||
("RS_DSR", "RS232_DSR_GPIO", "DSR", "7"),
|
||||
("RS_DCD", "RS232_DCD_GPIO", "DCD", "3"),
|
||||
("RS_RI", "RS232_RI_GPIO", "RI", "10"),
|
||||
("RS_VALID", "RS232_VALID_GPIO", "VLD", "11"),
|
||||
("RS_OFF_N", "RS232_FORCE_OFF_N_GPIO", "OFF_N", "12"),
|
||||
("OLED_SDA", "LOCAL_UI_DISPLAY_SDA_GPIO", "SDA", "4"),
|
||||
("OLED_SCL", "LOCAL_UI_DISPLAY_SCL_GPIO", "SCL", "3"),
|
||||
("BTN_PREVIOUS", "LOCAL_UI_BUTTON_PREVIOUS_GPIO", None, None),
|
||||
("BTN_SELECT", "LOCAL_UI_BUTTON_SELECT_GPIO", None, None),
|
||||
("BTN_NEXT", "LOCAL_UI_BUTTON_NEXT_GPIO", None, None),
|
||||
]
|
||||
firmware = ROOT.parents[1].joinpath("src/board_pins.h").read_text()
|
||||
for net, macro, peripheral_name, peripheral_pin in contracts:
|
||||
match = re.search(r"^#define\s+" + re.escape(macro) + r"\s+GPIO_NUM_(\d+)\s*$", firmware, re.MULTILINE)
|
||||
assert match, macro
|
||||
gpio = "GPIO" + match.group(1)
|
||||
mcu = [node for node in nets[net] if node.get("ref") == "U1"]
|
||||
assert len(mcu) == 1 and mcu[0].get("pinfunction") == gpio + "_" + mcu[0].get("pin"), (net, gpio)
|
||||
if peripheral_name:
|
||||
ref = "DS1" if net.startswith("OLED_") else "U2"
|
||||
node = [node for node in nets[net] if node.get("ref") == ref]
|
||||
assert len(node) == 1 and node[0].get("pin") == peripheral_pin
|
||||
assert node[0].get("pinfunction") == peripheral_name + "_" + peripheral_pin
|
||||
assert len(nets[net]) == 2, (net, members[net])
|
||||
|
||||
for i, net in enumerate(("BTN_PREVIOUS", "BTN_SELECT", "BTN_NEXT"), 1):
|
||||
assert len(nets[net]) == 3
|
||||
assert (f"SW{i}", "1") in members[net] and (f"R{i}", "2") in members[net]
|
||||
assert components[f"SW{i}"].findtext("value") == "B3F-1000"
|
||||
assert components[f"R{i}"].findtext("value") == "2.2k 1%"
|
||||
assert members["+3V3"] == sorted([("U1","1"),("U1","2"),("U2","1"),("DS1","2"),("R1","1"),("R2","1"),("R3","1")])
|
||||
assert members["GND"] == sorted([("U1","22"),("U1","23"),("U1","43"),("U1","44"),("U2","2"),("DS1","1"),("SW1","2"),("SW2","2"),("SW3","2")])
|
||||
active = {name for name in nets if not name.startswith("unconnected-")}
|
||||
assert active == {"+3V3", "GND"} | {row[0] for row in contracts}
|
||||
for name, nodes in nets.items():
|
||||
if name.startswith("unconnected-"):
|
||||
assert len(nodes) == 1 and nodes[0].get("ref") == "U1"
|
||||
# All 44 MCU header pins must occur exactly once, including deliberate no-connects.
|
||||
assert sorted(int(node.get("pin")) for nodes in nets.values() for node in nodes if node.get("ref") == "U1") == list(range(1,45))
|
||||
# USB/UART0, 5 V and PSRAM-conflicting pins must never be carrier nets.
|
||||
for name in active:
|
||||
assert not any(node.get("ref") == "U1" and node.get("pin") in ("21","24","25","33","34","35","41","42") for node in nets[name])
|
||||
# U2's physical male footprint must stay explicitly unresolved in this draft.
|
||||
assert not components["U2"].findtext("footprint")
|
||||
expected = json.loads((OUT / "expected-nets.json").read_text())
|
||||
assert set(expected) == active
|
||||
for net, nodes in expected.items():
|
||||
assert members[net] == sorted(tuple(node) for node in nodes), net
|
||||
report = json.loads((OUT / "erc.json").read_text())
|
||||
assert not any(sheet["violations"] for sheet in report["sheets"])
|
||||
run("kicad-cli", "sch", "export", "pdf", "-o", str(ROOT / "serial-carrier.pdf"), str(SCHEMATIC))
|
||||
run("kicad-cli", "sch", "export", "svg", "-o", str(OUT) + "/", str(SCHEMATIC))
|
||||
summary = "\n".join([
|
||||
"Native schematic validation passed.",
|
||||
"KiCad ERC: zero reported errors/warnings; no project exclusions added.",
|
||||
"17 connected nets, 9 components; exact net memberships verified.",
|
||||
"15 firmware GPIO definitions independently checked against exported pin functions.",
|
||||
"All 44 MCU header pins accounted for; USB/UART0/5V/PSRAM pins unused on carrier.",
|
||||
"PDF and SVG exported successfully.",
|
||||
"Not validated: male-module physical mapping, power budget/path, OLED pull-ups,",
|
||||
"selected connector fit, PCB DRC/layout, procurement, or assembled hardware.",
|
||||
]) + "\n"
|
||||
(OUT / "summary.txt").write_text(summary)
|
||||
print(summary)
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"$schema": "https://schemas.kicad.org/erc.v1.json",
|
||||
"coordinate_units": "mm",
|
||||
"date": "2026-09-20T20:14:47",
|
||||
"ignored_checks": [
|
||||
{
|
||||
"description": "Global label only appears once in the schematic",
|
||||
"key": "single_global_label"
|
||||
},
|
||||
{
|
||||
"description": "Four connection points are joined together",
|
||||
"key": "four_way_junction"
|
||||
},
|
||||
{
|
||||
"description": "SPICE model issue",
|
||||
"key": "simulation_model_issue"
|
||||
},
|
||||
{
|
||||
"description": "Assigned footprint doesn't match footprint filters",
|
||||
"key": "footprint_filter"
|
||||
}
|
||||
],
|
||||
"included_severities": [
|
||||
"error",
|
||||
"warning"
|
||||
],
|
||||
"kicad_version": "10.0.6",
|
||||
"sheets": [
|
||||
{
|
||||
"path": "/",
|
||||
"uuid_path": "/c86e0d56-4290-5f2b-b790-14926c8cdeea",
|
||||
"violations": []
|
||||
}
|
||||
],
|
||||
"source": "serial-carrier.kicad_sch"
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
{
|
||||
"+3V3": [
|
||||
[
|
||||
"U1",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"U1",
|
||||
"2"
|
||||
],
|
||||
[
|
||||
"U2",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"DS1",
|
||||
"2"
|
||||
],
|
||||
[
|
||||
"R1",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"R2",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"R3",
|
||||
"1"
|
||||
]
|
||||
],
|
||||
"BTN_NEXT": [
|
||||
[
|
||||
"U1",
|
||||
"20"
|
||||
],
|
||||
[
|
||||
"R3",
|
||||
"2"
|
||||
],
|
||||
[
|
||||
"SW3",
|
||||
"1"
|
||||
]
|
||||
],
|
||||
"BTN_PREVIOUS": [
|
||||
[
|
||||
"U1",
|
||||
"16"
|
||||
],
|
||||
[
|
||||
"R1",
|
||||
"2"
|
||||
],
|
||||
[
|
||||
"SW1",
|
||||
"1"
|
||||
]
|
||||
],
|
||||
"BTN_SELECT": [
|
||||
[
|
||||
"U1",
|
||||
"19"
|
||||
],
|
||||
[
|
||||
"R2",
|
||||
"2"
|
||||
],
|
||||
[
|
||||
"SW2",
|
||||
"1"
|
||||
]
|
||||
],
|
||||
"GND": [
|
||||
[
|
||||
"U1",
|
||||
"22"
|
||||
],
|
||||
[
|
||||
"U1",
|
||||
"23"
|
||||
],
|
||||
[
|
||||
"U1",
|
||||
"43"
|
||||
],
|
||||
[
|
||||
"U1",
|
||||
"44"
|
||||
],
|
||||
[
|
||||
"U2",
|
||||
"2"
|
||||
],
|
||||
[
|
||||
"DS1",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"SW1",
|
||||
"2"
|
||||
],
|
||||
[
|
||||
"SW2",
|
||||
"2"
|
||||
],
|
||||
[
|
||||
"SW3",
|
||||
"2"
|
||||
]
|
||||
],
|
||||
"OLED_SCL": [
|
||||
[
|
||||
"U1",
|
||||
"18"
|
||||
],
|
||||
[
|
||||
"DS1",
|
||||
"3"
|
||||
]
|
||||
],
|
||||
"OLED_SDA": [
|
||||
[
|
||||
"U1",
|
||||
"17"
|
||||
],
|
||||
[
|
||||
"DS1",
|
||||
"4"
|
||||
]
|
||||
],
|
||||
"RS_CTS": [
|
||||
[
|
||||
"U1",
|
||||
"9"
|
||||
],
|
||||
[
|
||||
"U2",
|
||||
"9"
|
||||
]
|
||||
],
|
||||
"RS_DCD": [
|
||||
[
|
||||
"U1",
|
||||
"4"
|
||||
],
|
||||
[
|
||||
"U2",
|
||||
"3"
|
||||
]
|
||||
],
|
||||
"RS_DSR": [
|
||||
[
|
||||
"U1",
|
||||
"5"
|
||||
],
|
||||
[
|
||||
"U2",
|
||||
"7"
|
||||
]
|
||||
],
|
||||
"RS_DTR": [
|
||||
[
|
||||
"U1",
|
||||
"7"
|
||||
],
|
||||
[
|
||||
"U2",
|
||||
"6"
|
||||
]
|
||||
],
|
||||
"RS_OFF_N": [
|
||||
[
|
||||
"U1",
|
||||
"15"
|
||||
],
|
||||
[
|
||||
"U2",
|
||||
"12"
|
||||
]
|
||||
],
|
||||
"RS_RI": [
|
||||
[
|
||||
"U1",
|
||||
"6"
|
||||
],
|
||||
[
|
||||
"U2",
|
||||
"10"
|
||||
]
|
||||
],
|
||||
"RS_RTS": [
|
||||
[
|
||||
"U1",
|
||||
"8"
|
||||
],
|
||||
[
|
||||
"U2",
|
||||
"8"
|
||||
]
|
||||
],
|
||||
"RS_RX": [
|
||||
[
|
||||
"U1",
|
||||
"11"
|
||||
],
|
||||
[
|
||||
"U2",
|
||||
"4"
|
||||
]
|
||||
],
|
||||
"RS_TX": [
|
||||
[
|
||||
"U1",
|
||||
"10"
|
||||
],
|
||||
[
|
||||
"U2",
|
||||
"5"
|
||||
]
|
||||
],
|
||||
"RS_VALID": [
|
||||
[
|
||||
"U1",
|
||||
"12"
|
||||
],
|
||||
[
|
||||
"U2",
|
||||
"11"
|
||||
]
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 283 KiB |
|
After Width: | Height: | Size: 175 KiB |
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"$schema": "https://schemas.kicad.org/drc.v1.json",
|
||||
"coordinate_units": "mm",
|
||||
"date": "2026-09-21T11:39:10",
|
||||
"ignored_checks": [
|
||||
{
|
||||
"description": "Footprint has no courtyard defined",
|
||||
"key": "missing_courtyard"
|
||||
},
|
||||
{
|
||||
"description": "Track endpoint not centered on via",
|
||||
"key": "track_not_centered_on_via"
|
||||
},
|
||||
{
|
||||
"description": "Tuning profile track geometries",
|
||||
"key": "tuning_profile_track_geometries"
|
||||
},
|
||||
{
|
||||
"description": "Footprint doesn't match symbol's footprint filters",
|
||||
"key": "footprint_filters_mismatch"
|
||||
},
|
||||
{
|
||||
"description": "Footprint component type doesn't match footprint pads",
|
||||
"key": "footprint_type_mismatch"
|
||||
}
|
||||
],
|
||||
"included_severities": [
|
||||
"error",
|
||||
"warning",
|
||||
"exclusion"
|
||||
],
|
||||
"kicad_version": "10.0.6",
|
||||
"schematic_parity": [
|
||||
{
|
||||
"description": "Carrier:MAX3243_Reference_Provisional stimmt nicht mit dem Footprint des Symbols () überein.",
|
||||
"items": [
|
||||
{
|
||||
"description": "Footprint U2",
|
||||
"pos": {
|
||||
"x": 143.0,
|
||||
"y": 126.67
|
||||
},
|
||||
"uuid": "812387f6-6524-459e-8102-fe9c55122668"
|
||||
}
|
||||
],
|
||||
"severity": "warning",
|
||||
"type": "footprint_symbol_mismatch"
|
||||
}
|
||||
],
|
||||
"source": "serial-carrier.kicad_pcb",
|
||||
"unconnected_items": [],
|
||||
"violations": []
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/builddir/build/BUILD/kicad-10.0.6-build/kicad-10.0.6/include/properties/property.h(607): assert "m_choices.GetCount() > 0" failed in PROPERTY_ENUM(): No enum choices defined
|
||||
/builddir/build/BUILD/kicad-10.0.6-build/kicad-10.0.6/include/properties/property.h(607): assert "m_choices.GetCount() > 0" failed in PROPERTY_ENUM(): No enum choices defined
|
||||
/builddir/build/BUILD/kicad-10.0.6-build/kicad-10.0.6/include/properties/property.h(607): assert "m_choices.GetCount() > 0" failed in PROPERTY_ENUM(): No enum choices defined
|
||||
{"net": "/+3V3", "start": "U1.1", "end": "U1.2", "start_mm": [108.0, 123.0], "end_mm": [108.0, 125.54], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 52}
|
||||
{"net": "/+3V3", "start": "R2.1", "end": "DS1.2", "start_mm": [154.0, 134.0], "end_mm": [154.73, 137.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 911}
|
||||
{"net": "/BTN_NEXT", "start": "SW3.1", "end": "SW3.1", "start_mm": [167.0, 168.0], "end_mm": [173.5, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/BTN_PREVIOUS", "start": "SW1.1", "end": "SW1.1", "start_mm": [143.0, 168.0], "end_mm": [149.5, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/BTN_SELECT", "start": "SW2.1", "end": "SW2.1", "start_mm": [155.0, 168.0], "end_mm": [161.5, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/+3V3", "start": "U2.1", "end": "R1.1", "start_mm": [143.0, 126.67], "end_mm": [143.0, 134.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 148}
|
||||
{"net": "/+3V3", "start": "DS1.2", "end": "R3.1", "start_mm": [154.73, 137.5], "end_mm": [165.0, 134.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 5139}
|
||||
{"net": "/+3V3", "start": "R1.1", "end": "R2.1", "start_mm": [143.0, 134.0], "end_mm": [154.0, 134.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 14153}
|
||||
{"net": "/BTN_SELECT", "start": "SW2.1", "end": "R2.2", "start_mm": [161.5, 168.0], "end_mm": [161.62, 134.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 2039}
|
||||
{"net": "/BTN_NEXT", "start": "SW3.1", "end": "R3.2", "start_mm": [173.5, 168.0], "end_mm": [172.62, 134.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 12615}
|
||||
{"net": "/BTN_PREVIOUS", "start": "SW1.1", "end": "R1.2", "start_mm": [149.5, 168.0], "end_mm": [150.62, 134.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 15179}
|
||||
{"net": "/+3V3", "start": "U1.2", "end": "U2.1", "start_mm": [108.0, 125.54], "end_mm": [143.0, 126.67], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 297043}
|
||||
{"net": "/BTN_PREVIOUS", "start": "U1.16", "end": "SW1.1", "start_mm": [108.0, 161.1], "end_mm": [143.0, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 70393}
|
||||
{"net": "/RS_DCD", "start": "U1.4", "end": "U2.3", "start_mm": [108.0, 130.62], "end_mm": [148.08, 126.67], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 54772}
|
||||
{"net": "/BTN_SELECT", "start": "SW2.1", "end": "U1.19", "start_mm": [155.0, 168.0], "end_mm": [108.0, 168.72], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 36364}
|
||||
{"net": "/RS_RX", "start": "U1.11", "end": "U2.4", "start_mm": [108.0, 148.4], "end_mm": [150.62, 126.67], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 119946}
|
||||
{"net": "/RS_TX", "start": "U1.10", "end": "U2.5", "start_mm": [108.0, 145.86], "end_mm": [153.16, 126.67], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 297203}
|
||||
{"net": "/RS_DTR", "start": "U1.7", "end": "U2.6", "start_mm": [108.0, 138.24], "end_mm": [155.7, 126.67], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 217446}
|
||||
{"net": "/RS_DSR", "start": "U1.5", "end": "U2.7", "start_mm": [108.0, 133.16], "end_mm": [158.24, 126.67], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 177204}
|
||||
{"net": "/RS_RTS", "start": "U1.8", "end": "U2.8", "start_mm": [108.0, 140.78], "end_mm": [160.78, 126.67], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 268999}
|
||||
{"net": "/OLED_SCL", "start": "U1.18", "end": "DS1.3", "start_mm": [108.0, 166.18], "end_mm": [157.27, 137.5], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 453162}
|
||||
{"net": "/RS_CTS", "start": "U1.9", "end": "U2.9", "start_mm": [108.0, 143.32], "end_mm": [163.32, 126.67], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 336768}
|
||||
{"net": "/OLED_SDA", "start": "U1.17", "end": "DS1.4", "start_mm": [108.0, 163.64], "end_mm": [159.81, 137.5], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 574321}
|
||||
{"net": "/RS_RI", "start": "U1.6", "end": "U2.10", "start_mm": [108.0, 135.7], "end_mm": [165.86, 126.67], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 221877}
|
||||
{"net": "/BTN_NEXT", "start": "U1.20", "end": "SW3.1", "start_mm": [108.0, 171.26], "end_mm": [167.0, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 69853}
|
||||
{"net": "/RS_VALID", "start": "U1.12", "end": "U2.11", "start_mm": [108.0, 150.94], "end_mm": [168.4, 126.67], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 344679}
|
||||
{"net": "/RS_OFF_N", "start": "U1.15", "end": "U2.12", "start_mm": [108.0, 158.56], "end_mm": [170.94, 126.67], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 690965}
|
||||
{"net": "/GND", "start": "U1.43", "end": "U1.44", "start_mm": [133.4, 173.8], "end_mm": [133.4, 176.34], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 52}
|
||||
{"net": "/GND", "start": "SW2.2", "end": "SW1.2", "start_mm": [155.0, 172.5], "end_mm": [149.5, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 111}
|
||||
{"net": "/GND", "start": "SW2.2", "end": "SW3.2", "start_mm": [161.5, 172.5], "end_mm": [167.0, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 111}
|
||||
{"net": "/GND", "start": "SW2.2", "end": "SW2.2", "start_mm": [155.0, 172.5], "end_mm": [161.5, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/GND", "start": "SW3.2", "end": "SW3.2", "start_mm": [167.0, 172.5], "end_mm": [173.5, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/GND", "start": "SW1.2", "end": "SW1.2", "start_mm": [149.5, 172.5], "end_mm": [143.0, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/GND", "start": "SW1.2", "end": "U1.43", "start_mm": [143.0, 172.5], "end_mm": [133.4, 173.8], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 2466}
|
||||
{"net": "/GND", "start": "U2.2", "end": "U1.23", "start_mm": [145.54, 126.67], "end_mm": [133.4, 123.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 7318}
|
||||
{"net": "/GND", "start": "DS1.1", "end": "U2.2", "start_mm": [152.19, 137.5], "end_mm": [145.54, 126.67], "routed": true, "width_mm": 0.25, "power_fallback": true, "expansions": 2378118}
|
||||
{"net": "/GND", "start": "U1.44", "end": "U1.22", "start_mm": [133.4, 176.34], "end_mm": [108.0, 176.34], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 509}
|
||||
{"net": "/GND", "start": "SW1.2", "end": "DS1.1", "start_mm": [149.5, 172.5], "end_mm": [152.19, 137.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 19033}
|
||||
10:59:13: Debug: Adding duplicate image handler for 'PNG file'
|
||||
10:59:13: Debug: Adding duplicate image handler for 'JPEG file'
|
||||
10:59:13: Debug: Adding duplicate image handler for 'TIFF file'
|
||||
10:59:13: Debug: Adding duplicate image handler for 'GIF file'
|
||||
10:59:13: Debug: Adding duplicate image handler for 'PNM file'
|
||||
10:59:13: Debug: Adding duplicate image handler for 'PCX file'
|
||||
10:59:13: Debug: Adding duplicate image handler for 'IFF file'
|
||||
10:59:13: Debug: Adding duplicate image handler for 'Windows icon file'
|
||||
10:59:13: Debug: Adding duplicate image handler for 'Windows cursor file'
|
||||
10:59:13: Debug: Adding duplicate image handler for 'Windows animated cursor file'
|
||||
10:59:13: Debug: Adding duplicate image handler for 'TGA file'
|
||||
10:59:13: Debug: Adding duplicate image handler for 'XPM file'
|
||||
{
|
||||
"draft_only": true,
|
||||
"saved": true,
|
||||
"grid_mm": 0.05,
|
||||
"clearance_mm": 0.25,
|
||||
"safety_margin_mm": 0.02,
|
||||
"via_diameter_mm": 0.7,
|
||||
"via_drill_mm": 0.3,
|
||||
"elapsed_seconds": 31.17737436400057,
|
||||
"routed_tree_edges": 38,
|
||||
"total_tree_edges": 38,
|
||||
"tracks": 674,
|
||||
"vias": 16,
|
||||
"immutable_identity_sha256": "783ce7e212880e0836c93e1782d39bbfbc505fae856d9720ee6c50f5ba8f6275",
|
||||
"input_sha256": "df62a5aeb64a6d871a477a8c9fe94c100210d5b239a0617558907d9d3d9a6007",
|
||||
"validation": "KiCad DRC must be run separately; tree-edge counts are not DRC connectivity"
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/builddir/build/BUILD/kicad-10.0.6-build/kicad-10.0.6/include/properties/property.h(607): assert "m_choices.GetCount() > 0" failed in PROPERTY_ENUM(): No enum choices defined
|
||||
/builddir/build/BUILD/kicad-10.0.6-build/kicad-10.0.6/include/properties/property.h(607): assert "m_choices.GetCount() > 0" failed in PROPERTY_ENUM(): No enum choices defined
|
||||
/builddir/build/BUILD/kicad-10.0.6-build/kicad-10.0.6/include/properties/property.h(607): assert "m_choices.GetCount() > 0" failed in PROPERTY_ENUM(): No enum choices defined
|
||||
{"net": "/GND", "start": "U1.43", "end": "U1.44", "start_mm": [133.4, 173.8], "end_mm": [133.4, 176.34], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 52}
|
||||
{"net": "/+3V3", "start": "U1.1", "end": "U1.2", "start_mm": [108.0, 123.0], "end_mm": [108.0, 125.54], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 52}
|
||||
{"net": "/+3V3", "start": "R2.1", "end": "DS1.2", "start_mm": [154.0, 134.0], "end_mm": [154.73, 137.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 911}
|
||||
{"net": "/GND", "start": "SW2.2", "end": "SW1.2", "start_mm": [155.0, 172.5], "end_mm": [149.5, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 111}
|
||||
{"net": "/GND", "start": "SW2.2", "end": "SW3.2", "start_mm": [161.5, 172.5], "end_mm": [167.0, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 111}
|
||||
{"net": "/BTN_NEXT", "start": "SW3.1", "end": "SW3.1", "start_mm": [167.0, 168.0], "end_mm": [173.5, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/BTN_PREVIOUS", "start": "SW1.1", "end": "SW1.1", "start_mm": [143.0, 168.0], "end_mm": [149.5, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/BTN_SELECT", "start": "SW2.1", "end": "SW2.1", "start_mm": [155.0, 168.0], "end_mm": [161.5, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/GND", "start": "SW2.2", "end": "SW2.2", "start_mm": [155.0, 172.5], "end_mm": [161.5, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/GND", "start": "SW3.2", "end": "SW3.2", "start_mm": [167.0, 172.5], "end_mm": [173.5, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/GND", "start": "SW1.2", "end": "SW1.2", "start_mm": [149.5, 172.5], "end_mm": [143.0, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/+3V3", "start": "U2.1", "end": "R1.1", "start_mm": [143.0, 126.67], "end_mm": [143.0, 134.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 148}
|
||||
{"net": "/GND", "start": "SW1.2", "end": "U1.43", "start_mm": [143.0, 172.5], "end_mm": [133.4, 173.8], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 4535}
|
||||
{"net": "/+3V3", "start": "DS1.2", "end": "R3.1", "start_mm": [154.73, 137.5], "end_mm": [165.0, 134.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 5139}
|
||||
{"net": "/+3V3", "start": "R1.1", "end": "R2.1", "start_mm": [143.0, 134.0], "end_mm": [154.0, 134.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 14153}
|
||||
{"net": "/GND", "start": "U2.2", "end": "U1.23", "start_mm": [145.54, 126.67], "end_mm": [133.4, 123.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 9372}
|
||||
{"net": "/GND", "start": "DS1.1", "end": "U2.2", "start_mm": [152.19, 137.5], "end_mm": [145.54, 126.67], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 8327}
|
||||
{"net": "/BTN_SELECT", "start": "SW2.1", "end": "R2.2", "start_mm": [161.5, 168.0], "end_mm": [161.62, 134.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 2039}
|
||||
{"net": "/BTN_NEXT", "start": "SW3.1", "end": "R3.2", "start_mm": [173.5, 168.0], "end_mm": [172.62, 134.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 12615}
|
||||
{"net": "/BTN_PREVIOUS", "start": "SW1.1", "end": "R1.2", "start_mm": [149.5, 168.0], "end_mm": [150.62, 134.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 15179}
|
||||
{"net": "/+3V3", "start": "U1.2", "end": "U2.1", "start_mm": [108.0, 125.54], "end_mm": [143.0, 126.67], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 332527}
|
||||
{"net": "/BTN_PREVIOUS", "start": "U1.16", "end": "SW1.1", "start_mm": [108.0, 161.1], "end_mm": [143.0, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 70393}
|
||||
{"net": "/RS_DCD", "start": "U1.4", "end": "U2.3", "start_mm": [108.0, 130.62], "end_mm": [148.08, 126.67], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 21855}
|
||||
{"net": "/BTN_SELECT", "start": "SW2.1", "end": "U1.19", "start_mm": [155.0, 168.0], "end_mm": [108.0, 168.72], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 36364}
|
||||
{"net": "/RS_RX", "start": "U1.11", "end": "U2.4", "start_mm": [108.0, 148.4], "end_mm": [150.62, 126.67], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 123675}
|
||||
{"net": "/RS_TX", "start": "U1.10", "end": "U2.5", "start_mm": [108.0, 145.86], "end_mm": [153.16, 126.67], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 397389}
|
||||
{"net": "/RS_DTR", "start": "U1.7", "end": "U2.6", "start_mm": [108.0, 138.24], "end_mm": [155.7, 126.67], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 294174}
|
||||
{"net": "/RS_DSR", "start": "U1.5", "end": "U2.7", "start_mm": [108.0, 133.16], "end_mm": [158.24, 126.67], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 294332}
|
||||
{"net": "/RS_RTS", "start": "U1.8", "end": "U2.8", "start_mm": [108.0, 140.78], "end_mm": [160.78, 126.67], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 378833}
|
||||
{"net": "/OLED_SCL", "start": "U1.18", "end": "DS1.3", "start_mm": [108.0, 166.18], "end_mm": [157.27, 137.5], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 442107}
|
||||
{"net": "/RS_CTS", "start": "U1.9", "end": "U2.9", "start_mm": [108.0, 143.32], "end_mm": [163.32, 126.67], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 409990}
|
||||
{"net": "/OLED_SDA", "start": "U1.17", "end": "DS1.4", "start_mm": [108.0, 163.64], "end_mm": [159.81, 137.5], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 567654}
|
||||
{"net": "/RS_RI", "start": "U1.6", "end": "U2.10", "start_mm": [108.0, 135.7], "end_mm": [165.86, 126.67], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 436874}
|
||||
{"net": "/BTN_NEXT", "start": "U1.20", "end": "SW3.1", "start_mm": [108.0, 171.26], "end_mm": [167.0, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 273975}
|
||||
{"net": "/RS_VALID", "start": "U1.12", "end": "U2.11", "start_mm": [108.0, 150.94], "end_mm": [168.4, 126.67], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 425742}
|
||||
{"net": "/RS_OFF_N", "start": "U1.15", "end": "U2.12", "start_mm": [108.0, 158.56], "end_mm": [170.94, 126.67], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 645804}
|
||||
{"net": "/GND", "start": "U1.44", "end": "U1.22", "start_mm": [133.4, 176.34], "end_mm": [108.0, 176.34], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 509}
|
||||
{"net": "/GND", "start": "SW1.2", "end": "DS1.1", "start_mm": [149.5, 172.5], "end_mm": [152.19, 137.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 617019}
|
||||
11:12:00: Debug: Adding duplicate image handler for 'PNG file'
|
||||
11:12:00: Debug: Adding duplicate image handler for 'JPEG file'
|
||||
11:12:00: Debug: Adding duplicate image handler for 'TIFF file'
|
||||
11:12:00: Debug: Adding duplicate image handler for 'GIF file'
|
||||
11:12:00: Debug: Adding duplicate image handler for 'PNM file'
|
||||
11:12:00: Debug: Adding duplicate image handler for 'PCX file'
|
||||
11:12:00: Debug: Adding duplicate image handler for 'IFF file'
|
||||
11:12:00: Debug: Adding duplicate image handler for 'Windows icon file'
|
||||
11:12:00: Debug: Adding duplicate image handler for 'Windows cursor file'
|
||||
11:12:00: Debug: Adding duplicate image handler for 'Windows animated cursor file'
|
||||
11:12:00: Debug: Adding duplicate image handler for 'TGA file'
|
||||
11:12:00: Debug: Adding duplicate image handler for 'XPM file'
|
||||
{
|
||||
"draft_only": true,
|
||||
"saved": true,
|
||||
"grid_mm": 0.05,
|
||||
"clearance_mm": 0.25,
|
||||
"safety_margin_mm": 0.02,
|
||||
"via_diameter_mm": 0.7,
|
||||
"via_drill_mm": 0.3,
|
||||
"elapsed_seconds": 32.44472043699898,
|
||||
"routed_tree_edges": 38,
|
||||
"total_tree_edges": 38,
|
||||
"tracks": 565,
|
||||
"vias": 22,
|
||||
"immutable_identity_sha256": "783ce7e212880e0836c93e1782d39bbfbc505fae856d9720ee6c50f5ba8f6275",
|
||||
"input_sha256": "0ea7f0abff371eacc81262dc949f768cd42128f389ea4af9facee5034368d884",
|
||||
"validation": "KiCad DRC must be run separately; tree-edge counts are not DRC connectivity"
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"$schema": "https://schemas.kicad.org/drc.v1.json",
|
||||
"coordinate_units": "mm",
|
||||
"date": "2026-09-21T11:16:44",
|
||||
"ignored_checks": [
|
||||
{
|
||||
"description": "Footprint has no courtyard defined",
|
||||
"key": "missing_courtyard"
|
||||
},
|
||||
{
|
||||
"description": "Track endpoint not centered on via",
|
||||
"key": "track_not_centered_on_via"
|
||||
},
|
||||
{
|
||||
"description": "Tuning profile track geometries",
|
||||
"key": "tuning_profile_track_geometries"
|
||||
},
|
||||
{
|
||||
"description": "Footprint doesn't match symbol's footprint filters",
|
||||
"key": "footprint_filters_mismatch"
|
||||
},
|
||||
{
|
||||
"description": "Footprint component type doesn't match footprint pads",
|
||||
"key": "footprint_type_mismatch"
|
||||
}
|
||||
],
|
||||
"included_severities": [
|
||||
"error",
|
||||
"warning",
|
||||
"exclusion"
|
||||
],
|
||||
"kicad_version": "10.0.6",
|
||||
"schematic_parity": [],
|
||||
"source": "serial-carrier.kicad_pcb",
|
||||
"unconnected_items": [],
|
||||
"violations": []
|
||||
}
|
||||
@@ -0,0 +1,911 @@
|
||||
{
|
||||
"$schema": "https://schemas.kicad.org/drc.v1.json",
|
||||
"coordinate_units": "mm",
|
||||
"date": "2026-09-20T22:39:18",
|
||||
"ignored_checks": [
|
||||
{
|
||||
"description": "Footprint has no courtyard defined",
|
||||
"key": "missing_courtyard"
|
||||
},
|
||||
{
|
||||
"description": "Track endpoint not centered on via",
|
||||
"key": "track_not_centered_on_via"
|
||||
},
|
||||
{
|
||||
"description": "Tuning profile track geometries",
|
||||
"key": "tuning_profile_track_geometries"
|
||||
},
|
||||
{
|
||||
"description": "Footprint doesn't match symbol's footprint filters",
|
||||
"key": "footprint_filters_mismatch"
|
||||
},
|
||||
{
|
||||
"description": "Footprint component type doesn't match footprint pads",
|
||||
"key": "footprint_type_mismatch"
|
||||
}
|
||||
],
|
||||
"included_severities": [
|
||||
"error",
|
||||
"warning"
|
||||
],
|
||||
"kicad_version": "10.0.6",
|
||||
"schematic_parity": [],
|
||||
"source": "serial-carrier.kicad_pcb",
|
||||
"unconnected_items": [
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 19 [/BTN_SELECT] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 149.72
|
||||
},
|
||||
"uuid": "f7b9badd-1db5-4575-9da8-979a7c8f3946"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/BTN_SELECT] von SW2",
|
||||
"pos": {
|
||||
"x": 155.0,
|
||||
"y": 168.0
|
||||
},
|
||||
"uuid": "574d93e4-d3fc-4c28-b2d5-4e7d8682c22f"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/BTN_SELECT] von SW2",
|
||||
"pos": {
|
||||
"x": 155.0,
|
||||
"y": 168.0
|
||||
},
|
||||
"uuid": "574d93e4-d3fc-4c28-b2d5-4e7d8682c22f"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/BTN_SELECT] von SW2",
|
||||
"pos": {
|
||||
"x": 161.5,
|
||||
"y": 168.0
|
||||
},
|
||||
"uuid": "0398530b-79dc-45b1-886c-51343182e1d3"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/BTN_SELECT] von SW2",
|
||||
"pos": {
|
||||
"x": 161.5,
|
||||
"y": 168.0
|
||||
},
|
||||
"uuid": "0398530b-79dc-45b1-886c-51343182e1d3"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/BTN_SELECT] von R2",
|
||||
"pos": {
|
||||
"x": 161.62,
|
||||
"y": 134.0
|
||||
},
|
||||
"uuid": "c118f27f-8ad4-4abe-b9c0-a30a22fe60c9"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 22 [/GND] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 157.34
|
||||
},
|
||||
"uuid": "9d509ec2-7588-4c77-9146-8d03c8646e37"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 44 [/GND] von U1",
|
||||
"pos": {
|
||||
"x": 133.4,
|
||||
"y": 157.34
|
||||
},
|
||||
"uuid": "91b9e8e9-7e2c-43ab-b2ef-ec8ba16a25f1"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 23 [/GND] von U1",
|
||||
"pos": {
|
||||
"x": 133.4,
|
||||
"y": 104.0
|
||||
},
|
||||
"uuid": "b66490f6-5d60-415d-babb-215c70511b77"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/GND] von U2",
|
||||
"pos": {
|
||||
"x": 145.54,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "7744910d-b37b-4fd5-a46a-95ee262d2a08"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 43 [/GND] von U1",
|
||||
"pos": {
|
||||
"x": 133.4,
|
||||
"y": 154.8
|
||||
},
|
||||
"uuid": "23b353d7-7ede-43a0-a655-57496d4f8aee"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/GND] von DS1",
|
||||
"pos": {
|
||||
"x": 152.19,
|
||||
"y": 137.5
|
||||
},
|
||||
"uuid": "0d95ad6d-f278-4240-9618-08ad8fcb5e1c"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 44 [/GND] von U1",
|
||||
"pos": {
|
||||
"x": 133.4,
|
||||
"y": 157.34
|
||||
},
|
||||
"uuid": "91b9e8e9-7e2c-43ab-b2ef-ec8ba16a25f1"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 43 [/GND] von U1",
|
||||
"pos": {
|
||||
"x": 133.4,
|
||||
"y": 154.8
|
||||
},
|
||||
"uuid": "23b353d7-7ede-43a0-a655-57496d4f8aee"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/GND] von SW1",
|
||||
"pos": {
|
||||
"x": 143.0,
|
||||
"y": 172.5
|
||||
},
|
||||
"uuid": "cc9a376c-9410-4f75-a7a6-3a849cc384b3"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/GND] von SW1",
|
||||
"pos": {
|
||||
"x": 149.5,
|
||||
"y": 172.5
|
||||
},
|
||||
"uuid": "24c82a41-4f9f-4c94-aaaf-2a3df71bbd08"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/GND] von SW1",
|
||||
"pos": {
|
||||
"x": 143.0,
|
||||
"y": 172.5
|
||||
},
|
||||
"uuid": "cc9a376c-9410-4f75-a7a6-3a849cc384b3"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 44 [/GND] von U1",
|
||||
"pos": {
|
||||
"x": 133.4,
|
||||
"y": 157.34
|
||||
},
|
||||
"uuid": "91b9e8e9-7e2c-43ab-b2ef-ec8ba16a25f1"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/GND] von SW1",
|
||||
"pos": {
|
||||
"x": 149.5,
|
||||
"y": 172.5
|
||||
},
|
||||
"uuid": "24c82a41-4f9f-4c94-aaaf-2a3df71bbd08"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/GND] von SW2",
|
||||
"pos": {
|
||||
"x": 155.0,
|
||||
"y": 172.5
|
||||
},
|
||||
"uuid": "970e56ae-cc93-46cd-8807-ccc9cf082aa1"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/GND] von DS1",
|
||||
"pos": {
|
||||
"x": 152.19,
|
||||
"y": 137.5
|
||||
},
|
||||
"uuid": "0d95ad6d-f278-4240-9618-08ad8fcb5e1c"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/GND] von U2",
|
||||
"pos": {
|
||||
"x": 145.54,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "7744910d-b37b-4fd5-a46a-95ee262d2a08"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/GND] von SW2",
|
||||
"pos": {
|
||||
"x": 155.0,
|
||||
"y": 172.5
|
||||
},
|
||||
"uuid": "970e56ae-cc93-46cd-8807-ccc9cf082aa1"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/GND] von SW2",
|
||||
"pos": {
|
||||
"x": 161.5,
|
||||
"y": 172.5
|
||||
},
|
||||
"uuid": "2f44f580-c7e4-4f03-ba46-49605735875c"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/GND] von SW2",
|
||||
"pos": {
|
||||
"x": 161.5,
|
||||
"y": 172.5
|
||||
},
|
||||
"uuid": "2f44f580-c7e4-4f03-ba46-49605735875c"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/GND] von SW3",
|
||||
"pos": {
|
||||
"x": 167.0,
|
||||
"y": 172.5
|
||||
},
|
||||
"uuid": "3cdbd911-6e07-4f86-8887-5953fa3d2a01"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/GND] von SW3",
|
||||
"pos": {
|
||||
"x": 167.0,
|
||||
"y": 172.5
|
||||
},
|
||||
"uuid": "3cdbd911-6e07-4f86-8887-5953fa3d2a01"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/GND] von SW3",
|
||||
"pos": {
|
||||
"x": 173.5,
|
||||
"y": 172.5
|
||||
},
|
||||
"uuid": "8e780381-cbfb-4f62-ae3b-8b0b8fa2a6a8"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/+3V3] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 104.0
|
||||
},
|
||||
"uuid": "9aaad355-039f-439b-9cd7-308d07ace02e"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/+3V3] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 106.54
|
||||
},
|
||||
"uuid": "2c8cc7c5-7b73-4bc2-a384-b3a5ac459c92"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/+3V3] von U2",
|
||||
"pos": {
|
||||
"x": 143.0,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "f8aafd85-e635-47cd-ba22-ddf4a036d1cd"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/+3V3] von R1",
|
||||
"pos": {
|
||||
"x": 143.0,
|
||||
"y": 134.0
|
||||
},
|
||||
"uuid": "56ff90bc-b894-4f81-8274-7a34384229e7"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/+3V3] von U2",
|
||||
"pos": {
|
||||
"x": 143.0,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "f8aafd85-e635-47cd-ba22-ddf4a036d1cd"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/+3V3] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 106.54
|
||||
},
|
||||
"uuid": "2c8cc7c5-7b73-4bc2-a384-b3a5ac459c92"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/+3V3] von R2",
|
||||
"pos": {
|
||||
"x": 154.0,
|
||||
"y": 134.0
|
||||
},
|
||||
"uuid": "73a249ed-c3b1-46fb-afd5-f8bfe0e3fa65"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/+3V3] von R1",
|
||||
"pos": {
|
||||
"x": 143.0,
|
||||
"y": 134.0
|
||||
},
|
||||
"uuid": "56ff90bc-b894-4f81-8274-7a34384229e7"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/+3V3] von DS1",
|
||||
"pos": {
|
||||
"x": 154.73,
|
||||
"y": 137.5
|
||||
},
|
||||
"uuid": "62a98206-7a36-4b20-a201-d4ca10b404df"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/+3V3] von R2",
|
||||
"pos": {
|
||||
"x": 154.0,
|
||||
"y": 134.0
|
||||
},
|
||||
"uuid": "73a249ed-c3b1-46fb-afd5-f8bfe0e3fa65"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/+3V3] von DS1",
|
||||
"pos": {
|
||||
"x": 154.73,
|
||||
"y": 137.5
|
||||
},
|
||||
"uuid": "62a98206-7a36-4b20-a201-d4ca10b404df"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/+3V3] von R3",
|
||||
"pos": {
|
||||
"x": 165.0,
|
||||
"y": 134.0
|
||||
},
|
||||
"uuid": "72b8a1ea-1fa7-480b-8122-aceb08f4bb4b"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 4 [/RS_DCD] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 111.62
|
||||
},
|
||||
"uuid": "d9f0d2af-a701-43e9-9d1e-58a6c7cb5250"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 3 [/RS_DCD] von U2",
|
||||
"pos": {
|
||||
"x": 148.08,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "d589a6a4-0d53-4c2a-8f68-fb4407d9f10e"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 5 [/RS_DSR] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 114.16
|
||||
},
|
||||
"uuid": "0ceca603-53a8-4a53-85cc-f7f8e7be90e4"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 7 [/RS_DSR] von U2",
|
||||
"pos": {
|
||||
"x": 158.24,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "e2d6b2cb-6025-490c-8371-d7cc34ac37f6"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 6 [/RS_RI] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 116.7
|
||||
},
|
||||
"uuid": "f35f1696-b778-437b-8cce-67b8c4d5f5d0"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 10 [/RS_RI] von U2",
|
||||
"pos": {
|
||||
"x": 165.86,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "e28de6f2-fb85-4849-ba1a-e98e2a231492"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 7 [/RS_DTR] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 119.24
|
||||
},
|
||||
"uuid": "354ab122-85b1-4c9c-9385-d5091fe12303"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 6 [/RS_DTR] von U2",
|
||||
"pos": {
|
||||
"x": 155.7,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "81e8ad67-4bdc-4f57-90b1-68cff6a95261"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 8 [/RS_RTS] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 121.78
|
||||
},
|
||||
"uuid": "55162730-46fd-465d-a1c5-20aed09ed534"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 8 [/RS_RTS] von U2",
|
||||
"pos": {
|
||||
"x": 160.78,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "5c304724-2bc6-409d-9c59-2e40adc557b9"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 9 [/RS_CTS] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 124.32
|
||||
},
|
||||
"uuid": "f75c20f8-d3b5-4c3b-9919-cb044b2da5c3"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 9 [/RS_CTS] von U2",
|
||||
"pos": {
|
||||
"x": 163.32,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "a8411036-6859-4041-84b8-2c80c326c58e"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 10 [/RS_TX] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 126.86
|
||||
},
|
||||
"uuid": "22c323db-7146-44af-951b-04a1d7dbb2fe"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 5 [/RS_TX] von U2",
|
||||
"pos": {
|
||||
"x": 153.16,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "f919098b-4ad7-4600-9e8b-3375a1cff050"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 11 [/RS_RX] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 129.4
|
||||
},
|
||||
"uuid": "6573852d-5714-4c8d-8793-0be87f5a052a"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 4 [/RS_RX] von U2",
|
||||
"pos": {
|
||||
"x": 150.62,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "3d90cce4-14d8-46a0-b560-09524f11698f"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 12 [/RS_VALID] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 131.94
|
||||
},
|
||||
"uuid": "2fbc596b-c88f-4fc6-aa94-5dad4fcac9cc"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 11 [/RS_VALID] von U2",
|
||||
"pos": {
|
||||
"x": 168.4,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "9ad42f44-160f-4aa5-bcf0-a4466c090438"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 15 [/RS_OFF_N] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 139.56
|
||||
},
|
||||
"uuid": "34e1afe9-b3fc-4d3a-9e33-38b32244f19e"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 12 [/RS_OFF_N] von U2",
|
||||
"pos": {
|
||||
"x": 170.94,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "a43ba2f2-477d-4850-87de-e1591119d5ae"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/BTN_PREVIOUS] von SW1",
|
||||
"pos": {
|
||||
"x": 143.0,
|
||||
"y": 168.0
|
||||
},
|
||||
"uuid": "5214c011-545b-4ae7-9cdb-852ade815805"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/BTN_PREVIOUS] von SW1",
|
||||
"pos": {
|
||||
"x": 149.5,
|
||||
"y": 168.0
|
||||
},
|
||||
"uuid": "84202d30-b2fe-4170-8146-69b68d3b82a1"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/BTN_PREVIOUS] von SW1",
|
||||
"pos": {
|
||||
"x": 149.5,
|
||||
"y": 168.0
|
||||
},
|
||||
"uuid": "84202d30-b2fe-4170-8146-69b68d3b82a1"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/BTN_PREVIOUS] von R1",
|
||||
"pos": {
|
||||
"x": 150.62,
|
||||
"y": 134.0
|
||||
},
|
||||
"uuid": "8237f507-f5c7-4eb6-9aa1-f46af2981e7c"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/BTN_PREVIOUS] von R1",
|
||||
"pos": {
|
||||
"x": 150.62,
|
||||
"y": 134.0
|
||||
},
|
||||
"uuid": "8237f507-f5c7-4eb6-9aa1-f46af2981e7c"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 16 [/BTN_PREVIOUS] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 142.1
|
||||
},
|
||||
"uuid": "fc705960-92fa-4d8f-bb0e-ce2b82d1e7fc"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 17 [/OLED_SDA] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 144.64
|
||||
},
|
||||
"uuid": "1f91549c-5cb2-465f-86df-b2f1b0c16ed8"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 4 [/OLED_SDA] von DS1",
|
||||
"pos": {
|
||||
"x": 159.81,
|
||||
"y": 137.5
|
||||
},
|
||||
"uuid": "91a4dcef-1b36-4e3d-a15c-c4578077c27b"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 18 [/OLED_SCL] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 147.18
|
||||
},
|
||||
"uuid": "1c55fd03-0b3e-494f-a37b-6e0b3496cfc9"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 3 [/OLED_SCL] von DS1",
|
||||
"pos": {
|
||||
"x": 157.27,
|
||||
"y": 137.5
|
||||
},
|
||||
"uuid": "704668d0-88bf-4b51-840a-60a35804fafd"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 20 [/BTN_NEXT] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 152.26
|
||||
},
|
||||
"uuid": "e7159c90-4e8d-4b21-8e84-a7f59f793440"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/BTN_NEXT] von SW3",
|
||||
"pos": {
|
||||
"x": 167.0,
|
||||
"y": 168.0
|
||||
},
|
||||
"uuid": "c67d0b07-c47f-48b3-9e79-7aed82edbbfc"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/BTN_NEXT] von SW3",
|
||||
"pos": {
|
||||
"x": 167.0,
|
||||
"y": 168.0
|
||||
},
|
||||
"uuid": "c67d0b07-c47f-48b3-9e79-7aed82edbbfc"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/BTN_NEXT] von SW3",
|
||||
"pos": {
|
||||
"x": 173.5,
|
||||
"y": 168.0
|
||||
},
|
||||
"uuid": "62f3da3b-8297-4232-92ee-88f3ce4a7d36"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/BTN_NEXT] von SW3",
|
||||
"pos": {
|
||||
"x": 173.5,
|
||||
"y": 168.0
|
||||
},
|
||||
"uuid": "62f3da3b-8297-4232-92ee-88f3ce4a7d36"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/BTN_NEXT] von R3",
|
||||
"pos": {
|
||||
"x": 172.62,
|
||||
"y": 134.0
|
||||
},
|
||||
"uuid": "9e9dfe49-4b68-472b-a86c-bfcee913a81b"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
}
|
||||
],
|
||||
"violations": []
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"immutable_geometry_nets_pads_linkage_preserved": true,
|
||||
"board_sha256": "53e36538c0669604457b9e1a9ff1b1e20422970e8f9f356088c4581868ad5045",
|
||||
"independent_min_different_net_or_npth_clearance_mm": 0.2705441169852849,
|
||||
"closest_items": [
|
||||
"U1.38:4b8eabef-8493-437d-bd02-cb9ff4afd7cd",
|
||||
"d95f45cb-7fa4-4203-a786-0a069425e2b8"
|
||||
],
|
||||
"min_copper_or_npth_edge_gap_mm": 1.9,
|
||||
"min_rf_gap_mm": 0.025000000000005684,
|
||||
"min_via_to_other_drilled_hole_gap_mm": 0.8205441169852848,
|
||||
"track_width_counts": {
|
||||
"0.25": 451,
|
||||
"0.5": 114
|
||||
},
|
||||
"routed_length_mm_by_net": {
|
||||
"/BTN_SELECT": 88.94832552628598,
|
||||
"/GND": 141.68412246223386,
|
||||
"/+3V3": 90.29604731966161,
|
||||
"/RS_DCD": 43.068490178879834,
|
||||
"/RS_DSR": 55.09398706495997,
|
||||
"/RS_RI": 67.91373841554615,
|
||||
"/RS_DTR": 52.53487761643567,
|
||||
"/RS_RTS": 60.056714985368814,
|
||||
"/RS_CTS": 63.65082753507114,
|
||||
"/RS_TX": 53.135260491559045,
|
||||
"/RS_RX": 51.637429252872906,
|
||||
"/RS_VALID": 72.10630197626116,
|
||||
"/RS_OFF_N": 78.04100118378895,
|
||||
"/BTN_PREVIOUS": 78.83370879187974,
|
||||
"/OLED_SDA": 62.65168524184884,
|
||||
"/OLED_SCL": 61.1779295330028,
|
||||
"/BTN_NEXT": 101.24898628384834
|
||||
},
|
||||
"tracks": 565,
|
||||
"vias": 22,
|
||||
"copper_zones": 0,
|
||||
"drc_freshly_run": true,
|
||||
"drc_violations": 0,
|
||||
"unconnected_items": 0,
|
||||
"drc_ignored_checks": [
|
||||
{
|
||||
"description": "Footprint has no courtyard defined",
|
||||
"key": "missing_courtyard"
|
||||
},
|
||||
{
|
||||
"description": "Track endpoint not centered on via",
|
||||
"key": "track_not_centered_on_via"
|
||||
},
|
||||
{
|
||||
"description": "Tuning profile track geometries",
|
||||
"key": "tuning_profile_track_geometries"
|
||||
},
|
||||
{
|
||||
"description": "Footprint doesn't match symbol's footprint filters",
|
||||
"key": "footprint_filters_mismatch"
|
||||
},
|
||||
{
|
||||
"description": "Footprint component type doesn't match footprint pads",
|
||||
"key": "footprint_type_mismatch"
|
||||
}
|
||||
],
|
||||
"overwrite_guard_tests": [
|
||||
{
|
||||
"args": [
|
||||
"--route"
|
||||
],
|
||||
"exit_code": 2,
|
||||
"reason": "route_pcb_draft.py: error: --route requires --overwrite; back up manual routing first"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"--route",
|
||||
"--overwrite"
|
||||
],
|
||||
"exit_code": 2,
|
||||
"reason": "route_pcb_draft.py: error: existing routing protected; --replace-routes required even for preview"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"exit_code": 2,
|
||||
"reason": "route_pcb_draft.py: error: existing routing protected; --replace-routes required even for preview"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/python3
|
||||
"""Read-only draft audit; needs pcbnew, numpy, shapely and kicad-cli.
|
||||
Writes fresh DRC and audit JSON reports, never modifies the board.
|
||||
|
||||
Independent continuous centreline/radius checks complement KiCad DRC. Restricted
|
||||
here to the existing circle/axis-aligned rectangular THT pads and straight tracks.
|
||||
"""
|
||||
from collections import Counter
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
|
||||
import pcbnew as p
|
||||
from shapely.geometry import Point, LineString, Polygon, box
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
spec = importlib.util.spec_from_file_location('router', ROOT / 'route_pcb_draft.py')
|
||||
r = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(r)
|
||||
board = p.LoadBoard(str(r.PCB))
|
||||
original = p.LoadBoard(str(ROOT / 'validation/pcb-routing-input.kicad_pcb'))
|
||||
assert r.identity(board) == r.identity(original), 'immutable source geometry/linkage changed'
|
||||
items, holes = [], []
|
||||
for f in board.GetFootprints():
|
||||
for pad in f.Pads():
|
||||
xy = r.pos(pad)
|
||||
assert pad.GetOrientationDegrees() % 90 == 0
|
||||
assert pad.GetShape() in (p.PAD_SHAPE_CIRCLE, p.PAD_SHAPE_RECT)
|
||||
if pad.GetShape() == p.PAD_SHAPE_CIRCLE:
|
||||
assert pad.GetSize().x == pad.GetSize().y
|
||||
geom, radius = Point(xy), p.ToMM(pad.GetSize().x) / 2
|
||||
else:
|
||||
bb = pad.GetBoundingBox()
|
||||
geom = box(p.ToMM(bb.GetX()), p.ToMM(bb.GetY()),
|
||||
p.ToMM(bb.GetRight()), p.ToMM(bb.GetBottom()))
|
||||
radius = 0
|
||||
label = f.GetReference() + '.' + pad.GetNumber() + ':' + pad.m_Uuid.AsString()
|
||||
layers = {l for l in r.LAYERS if pad.IsOnLayer(l)}
|
||||
items.append((label, pad.GetNetCode(), layers, geom, radius))
|
||||
if pad.GetDrillSize().x:
|
||||
assert pad.GetDrillSize().x == pad.GetDrillSize().y
|
||||
holes.append((label, Point(xy), p.ToMM(pad.GetDrillSize().x) / 2, False))
|
||||
track_lengths = Counter()
|
||||
widths = Counter()
|
||||
for t in board.GetTracks():
|
||||
is_via = isinstance(t, p.PCB_VIA)
|
||||
if is_via:
|
||||
geom, radius, layers = Point(r.pos(t)), p.ToMM(t.GetWidth(p.F_Cu)) / 2, set(r.LAYERS)
|
||||
assert abs(radius - 0.35) < 1e-8 and t.GetDrillValue() == r.mm(0.3)
|
||||
holes.append((t.m_Uuid.AsString(), geom, 0.15, True))
|
||||
else:
|
||||
geom = LineString([(p.ToMM(t.GetStart().x), p.ToMM(t.GetStart().y)),
|
||||
(p.ToMM(t.GetEnd().x), p.ToMM(t.GetEnd().y))])
|
||||
radius, layers = p.ToMM(t.GetWidth()) / 2, {t.GetLayer()}
|
||||
expected = 0.5 if t.GetNetname() in ('/+3V3', '/GND') else 0.25
|
||||
assert abs(2 * radius - expected) < 1e-8
|
||||
widths[str(2 * radius)] += 1
|
||||
track_lengths[t.GetNetname()] += geom.length
|
||||
items.append((t.m_Uuid.AsString(), t.GetNetCode(), layers, geom, radius))
|
||||
|
||||
min_clearance = float('inf')
|
||||
closest = None
|
||||
for i, a in enumerate(items):
|
||||
for b in items[i + 1:]:
|
||||
if not a[2].intersection(b[2]) or (a[1] and a[1] == b[1]):
|
||||
continue
|
||||
distance = a[3].distance(b[3]) - a[4] - b[4]
|
||||
if distance < min_clearance:
|
||||
min_clearance, closest = distance, (a[0], b[0])
|
||||
assert min_clearance >= 0.25 - 1e-6, (min_clearance, closest)
|
||||
outline = Polygon([(136,100),(182,100),(182,180),(100,180),(100,119),(136,119)])
|
||||
rf = box(110, 119, 131, 132)
|
||||
min_edge, min_rf = float('inf'), float('inf')
|
||||
for label, net, layers, geom, radius in items:
|
||||
assert outline.covers(geom), label
|
||||
edge_gap = outline.boundary.distance(geom) - radius
|
||||
rf_gap = rf.distance(geom) - radius
|
||||
min_edge, min_rf = min(min_edge, edge_gap), min(min_rf, rf_gap)
|
||||
assert edge_gap >= 0.5 - 1e-6, (label, edge_gap)
|
||||
assert rf_gap > 0, (label, rf_gap)
|
||||
min_via_hole_gap = float('inf')
|
||||
for i, a in enumerate(holes):
|
||||
for b in holes[i + 1:]:
|
||||
if not (a[3] or b[3]):
|
||||
continue
|
||||
gap = a[1].distance(b[1]) - a[2] - b[2]
|
||||
min_via_hole_gap = min(min_via_hole_gap, gap)
|
||||
assert gap >= 0.25 - 1e-6, (a[0], b[0], gap)
|
||||
|
||||
# Refusal paths must leave the routed board byte-for-byte unchanged.
|
||||
before = r.PCB.read_bytes()
|
||||
guards = []
|
||||
for args in (['--route'], ['--route', '--overwrite'], []):
|
||||
result = subprocess.run(['/usr/bin/python3', '-B', str(ROOT/'route_pcb_draft.py'), *args],
|
||||
capture_output=True, text=True, timeout=20)
|
||||
assert result.returncode == 2, (args, result.stdout, result.stderr)
|
||||
assert r.PCB.read_bytes() == before
|
||||
guards.append({'args': args, 'exit_code': result.returncode,
|
||||
'reason': result.stderr.splitlines()[-1]})
|
||||
subprocess.run(['kicad-cli', 'pcb', 'drc', '--format', 'json', '--all-track-errors',
|
||||
'--severity-all', '--exit-code-violations', '-o',
|
||||
str(ROOT/'validation/pcb-routing-drc.json'), str(r.PCB)],
|
||||
check=True, timeout=60)
|
||||
assert r.PCB.read_bytes() == before, 'board changed during audit'
|
||||
drc = json.loads(ROOT.joinpath('validation/pcb-routing-drc.json').read_text())
|
||||
assert not drc['violations'] and not drc['unconnected_items']
|
||||
report = dict(immutable_geometry_nets_pads_linkage_preserved=True,
|
||||
board_sha256=hashlib.sha256(before).hexdigest(),
|
||||
independent_min_different_net_or_npth_clearance_mm=min_clearance,
|
||||
closest_items=closest, min_copper_or_npth_edge_gap_mm=min_edge,
|
||||
min_rf_gap_mm=min_rf, min_via_to_other_drilled_hole_gap_mm=min_via_hole_gap,
|
||||
track_width_counts=widths, routed_length_mm_by_net=track_lengths,
|
||||
tracks=sum(not isinstance(t,p.PCB_VIA) for t in board.GetTracks()),
|
||||
vias=sum(isinstance(t,p.PCB_VIA) for t in board.GetTracks()),
|
||||
copper_zones=sum(not z.GetIsRuleArea() for z in board.Zones()),
|
||||
drc_freshly_run=True,
|
||||
drc_violations=len(drc['violations']), unconnected_items=len(drc['unconnected_items']),
|
||||
drc_ignored_checks=drc['ignored_checks'], overwrite_guard_tests=guards)
|
||||
ROOT.joinpath('validation/pcb-routing-check.json').write_text(json.dumps(report, indent=2)+'\n')
|
||||
print(json.dumps(report, indent=2))
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"$schema": "https://schemas.kicad.org/drc.v1.json",
|
||||
"coordinate_units": "mm",
|
||||
"date": "2026-09-21T11:39:13",
|
||||
"ignored_checks": [
|
||||
{
|
||||
"description": "Footprint has no courtyard defined",
|
||||
"key": "missing_courtyard"
|
||||
},
|
||||
{
|
||||
"description": "Track endpoint not centered on via",
|
||||
"key": "track_not_centered_on_via"
|
||||
},
|
||||
{
|
||||
"description": "Tuning profile track geometries",
|
||||
"key": "tuning_profile_track_geometries"
|
||||
},
|
||||
{
|
||||
"description": "Footprint doesn't match symbol's footprint filters",
|
||||
"key": "footprint_filters_mismatch"
|
||||
},
|
||||
{
|
||||
"description": "Footprint component type doesn't match footprint pads",
|
||||
"key": "footprint_type_mismatch"
|
||||
}
|
||||
],
|
||||
"included_severities": [
|
||||
"error",
|
||||
"warning",
|
||||
"exclusion"
|
||||
],
|
||||
"kicad_version": "10.0.6",
|
||||
"schematic_parity": [],
|
||||
"source": "serial-carrier.kicad_pcb",
|
||||
"unconnected_items": [],
|
||||
"violations": []
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/builddir/build/BUILD/kicad-10.0.6-build/kicad-10.0.6/include/properties/property.h(607): assert "m_choices.GetCount() > 0" failed in PROPERTY_ENUM(): No enum choices defined
|
||||
/builddir/build/BUILD/kicad-10.0.6-build/kicad-10.0.6/include/properties/property.h(607): assert "m_choices.GetCount() > 0" failed in PROPERTY_ENUM(): No enum choices defined
|
||||
/builddir/build/BUILD/kicad-10.0.6-build/kicad-10.0.6/include/properties/property.h(607): assert "m_choices.GetCount() > 0" failed in PROPERTY_ENUM(): No enum choices defined
|
||||
{"net": "/+3V3", "start": "U1.1", "end": "U1.2", "start_mm": [108.0, 104.0], "end_mm": [108.0, 106.54], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 52}
|
||||
{"net": "/+3V3", "start": "R2.1", "end": "DS1.2", "start_mm": [154.0, 134.0], "end_mm": [154.73, 137.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 911}
|
||||
{"net": "/+3V3", "start": "U2.1", "end": "R1.1", "start_mm": [143.0, 130.0], "end_mm": [143.0, 134.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 81}
|
||||
{"net": "/BTN_NEXT", "start": "SW3.1", "end": "SW3.1", "start_mm": [167.0, 168.0], "end_mm": [173.5, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/BTN_PREVIOUS", "start": "SW1.1", "end": "SW1.1", "start_mm": [149.5, 168.0], "end_mm": [143.0, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/BTN_SELECT", "start": "SW2.1", "end": "SW2.1", "start_mm": [155.0, 168.0], "end_mm": [161.5, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/+3V3", "start": "DS1.2", "end": "R3.1", "start_mm": [154.73, 137.5], "end_mm": [165.0, 134.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 5139}
|
||||
{"net": "/+3V3", "start": "R1.1", "end": "R2.1", "start_mm": [143.0, 134.0], "end_mm": [154.0, 134.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 14153}
|
||||
{"net": "/BTN_SELECT", "start": "SW2.1", "end": "R2.2", "start_mm": [161.5, 168.0], "end_mm": [161.62, 134.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 2039}
|
||||
{"net": "/BTN_NEXT", "start": "SW3.1", "end": "R3.2", "start_mm": [173.5, 168.0], "end_mm": [172.62, 134.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 12615}
|
||||
{"net": "/BTN_PREVIOUS", "start": "R1.2", "end": "SW1.1", "start_mm": [150.62, 134.0], "end_mm": [149.5, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 15179}
|
||||
{"net": "/+3V3", "start": "U1.2", "end": "U2.1", "start_mm": [108.0, 106.54], "end_mm": [143.0, 130.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 894511}
|
||||
{"net": "/RS_RX", "start": "U1.11", "end": "U2.4", "start_mm": [108.0, 129.4], "end_mm": [150.62, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 51516}
|
||||
{"net": "/BTN_PREVIOUS", "start": "U1.16", "end": "R1.2", "start_mm": [108.0, 142.1], "end_mm": [150.62, 134.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 94479}
|
||||
{"net": "/RS_DCD", "start": "U1.4", "end": "U2.3", "start_mm": [108.0, 111.62], "end_mm": [148.08, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 266031}
|
||||
{"net": "/RS_TX", "start": "U1.10", "end": "U2.5", "start_mm": [108.0, 126.86], "end_mm": [153.16, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 175689}
|
||||
{"net": "/RS_DTR", "start": "U1.7", "end": "U2.6", "start_mm": [108.0, 119.24], "end_mm": [155.7, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 326823}
|
||||
{"net": "/OLED_SCL", "start": "U1.18", "end": "DS1.3", "start_mm": [108.0, 147.18], "end_mm": [157.27, 137.5], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 82557}
|
||||
{"net": "/BTN_SELECT", "start": "SW2.1", "end": "U1.19", "start_mm": [155.0, 168.0], "end_mm": [108.0, 149.72], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 147997}
|
||||
{"net": "/OLED_SDA", "start": "U1.17", "end": "DS1.4", "start_mm": [108.0, 144.64], "end_mm": [159.81, 137.5], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 266428}
|
||||
{"net": "/RS_DSR", "start": "U1.5", "end": "U2.7", "start_mm": [108.0, 114.16], "end_mm": [158.24, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 372014}
|
||||
{"net": "/RS_RTS", "start": "U1.8", "end": "U2.8", "start_mm": [108.0, 121.78], "end_mm": [160.78, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 298514}
|
||||
{"net": "/RS_CTS", "start": "U1.9", "end": "U2.9", "start_mm": [108.0, 124.32], "end_mm": [163.32, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 265272}
|
||||
{"net": "/RS_RI", "start": "U1.6", "end": "U2.10", "start_mm": [108.0, 116.7], "end_mm": [165.86, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 332506}
|
||||
{"net": "/RS_VALID", "start": "U1.12", "end": "U2.11", "start_mm": [108.0, 131.94], "end_mm": [168.4, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 348384}
|
||||
{"net": "/BTN_NEXT", "start": "U1.20", "end": "SW3.1", "start_mm": [108.0, 152.26], "end_mm": [167.0, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 272795}
|
||||
{"net": "/RS_OFF_N", "start": "U1.15", "end": "U2.12", "start_mm": [108.0, 139.56], "end_mm": [170.94, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 351630}
|
||||
{"net": "/GND", "start": "U1.44", "end": "U1.43", "start_mm": [133.4, 157.34], "end_mm": [133.4, 154.8], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 52}
|
||||
{"net": "/GND", "start": "SW2.2", "end": "SW1.2", "start_mm": [155.0, 172.5], "end_mm": [149.5, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 111}
|
||||
{"net": "/GND", "start": "SW2.2", "end": "SW3.2", "start_mm": [161.5, 172.5], "end_mm": [167.0, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 111}
|
||||
{"net": "/GND", "start": "SW2.2", "end": "SW2.2", "start_mm": [155.0, 172.5], "end_mm": [161.5, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/GND", "start": "SW3.2", "end": "SW3.2", "start_mm": [167.0, 172.5], "end_mm": [173.5, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/GND", "start": "SW1.2", "end": "SW1.2", "start_mm": [149.5, 172.5], "end_mm": [143.0, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/GND", "start": "DS1.1", "end": "U2.2", "start_mm": [152.19, 137.5], "end_mm": [145.54, 130.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 17468}
|
||||
{"net": "/GND", "start": "SW1.2", "end": "U1.44", "start_mm": [143.0, 172.5], "end_mm": [133.4, 157.34], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 27488}
|
||||
{"net": "/GND", "start": "U1.44", "end": "U1.22", "start_mm": [133.4, 157.34], "end_mm": [108.0, 157.34], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 73753}
|
||||
{"net": "/GND", "start": "U1.43", "end": "DS1.1", "start_mm": [133.4, 154.8], "end_mm": [152.19, 137.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 54021}
|
||||
{"net": "/GND", "start": "U2.2", "end": "U1.23", "start_mm": [145.54, 130.0], "end_mm": [133.4, 104.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 98250}
|
||||
{
|
||||
"draft_only": true,
|
||||
"saved": false,
|
||||
"grid_mm": 0.05,
|
||||
"clearance_mm": 0.25,
|
||||
"safety_margin_mm": 0.02,
|
||||
"via_diameter_mm": 0.7,
|
||||
"via_drill_mm": 0.3,
|
||||
"elapsed_seconds": 27.992941715005145,
|
||||
"routed_tree_edges": 38,
|
||||
"total_tree_edges": 38,
|
||||
"tracks": 569,
|
||||
"vias": 20,
|
||||
"immutable_identity_sha256": "81def52e6c1d574b4d3ab1a5859a5266b893df0ea6c5b9aa7d7b688cc4a98519",
|
||||
"input_sha256": "bf94b6c9b00c43a97f933054758a2c9d6541981ae09f2f285dcdb567bd73d9b1",
|
||||
"validation": "KiCad DRC must be run separately; tree-edge counts are not DRC connectivity"
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
{
|
||||
"$schema": "https://schemas.kicad.org/drc.v1.json",
|
||||
"coordinate_units": "mm",
|
||||
"date": "2026-09-20T22:43:07",
|
||||
"ignored_checks": [
|
||||
{
|
||||
"description": "Footprint has no courtyard defined",
|
||||
"key": "missing_courtyard"
|
||||
},
|
||||
{
|
||||
"description": "Track endpoint not centered on via",
|
||||
"key": "track_not_centered_on_via"
|
||||
},
|
||||
{
|
||||
"description": "Tuning profile track geometries",
|
||||
"key": "tuning_profile_track_geometries"
|
||||
},
|
||||
{
|
||||
"description": "Footprint doesn't match symbol's footprint filters",
|
||||
"key": "footprint_filters_mismatch"
|
||||
},
|
||||
{
|
||||
"description": "Footprint component type doesn't match footprint pads",
|
||||
"key": "footprint_type_mismatch"
|
||||
}
|
||||
],
|
||||
"included_severities": [
|
||||
"error",
|
||||
"warning"
|
||||
],
|
||||
"kicad_version": "10.0.6",
|
||||
"schematic_parity": [],
|
||||
"source": "serial-carrier.kicad_pcb",
|
||||
"unconnected_items": [
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 5 [/RS_DSR] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 114.16
|
||||
},
|
||||
"uuid": "0ceca603-53a8-4a53-85cc-f7f8e7be90e4"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 7 [/RS_DSR] von U2",
|
||||
"pos": {
|
||||
"x": 158.24,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "e2d6b2cb-6025-490c-8371-d7cc34ac37f6"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 6 [/RS_RI] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 116.7
|
||||
},
|
||||
"uuid": "f35f1696-b778-437b-8cce-67b8c4d5f5d0"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 10 [/RS_RI] von U2",
|
||||
"pos": {
|
||||
"x": 165.86,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "e28de6f2-fb85-4849-ba1a-e98e2a231492"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 7 [/RS_DTR] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 119.24
|
||||
},
|
||||
"uuid": "354ab122-85b1-4c9c-9385-d5091fe12303"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 6 [/RS_DTR] von U2",
|
||||
"pos": {
|
||||
"x": 155.7,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "81e8ad67-4bdc-4f57-90b1-68cff6a95261"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 8 [/RS_RTS] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 121.78
|
||||
},
|
||||
"uuid": "55162730-46fd-465d-a1c5-20aed09ed534"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 8 [/RS_RTS] von U2",
|
||||
"pos": {
|
||||
"x": 160.78,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "5c304724-2bc6-409d-9c59-2e40adc557b9"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 9 [/RS_CTS] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 124.32
|
||||
},
|
||||
"uuid": "f75c20f8-d3b5-4c3b-9919-cb044b2da5c3"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 9 [/RS_CTS] von U2",
|
||||
"pos": {
|
||||
"x": 163.32,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "a8411036-6859-4041-84b8-2c80c326c58e"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 10 [/RS_TX] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 126.86
|
||||
},
|
||||
"uuid": "22c323db-7146-44af-951b-04a1d7dbb2fe"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 5 [/RS_TX] von U2",
|
||||
"pos": {
|
||||
"x": 153.16,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "f919098b-4ad7-4600-9e8b-3375a1cff050"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 11 [/RS_RX] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 129.4
|
||||
},
|
||||
"uuid": "6573852d-5714-4c8d-8793-0be87f5a052a"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 4 [/RS_RX] von U2",
|
||||
"pos": {
|
||||
"x": 150.62,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "3d90cce4-14d8-46a0-b560-09524f11698f"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 12 [/RS_VALID] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 131.94
|
||||
},
|
||||
"uuid": "2fbc596b-c88f-4fc6-aa94-5dad4fcac9cc"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 11 [/RS_VALID] von U2",
|
||||
"pos": {
|
||||
"x": 168.4,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "9ad42f44-160f-4aa5-bcf0-a4466c090438"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 15 [/RS_OFF_N] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 139.56
|
||||
},
|
||||
"uuid": "34e1afe9-b3fc-4d3a-9e33-38b32244f19e"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 12 [/RS_OFF_N] von U2",
|
||||
"pos": {
|
||||
"x": 170.94,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "a43ba2f2-477d-4850-87de-e1591119d5ae"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
}
|
||||
],
|
||||
"violations": []
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
{
|
||||
"$schema": "https://schemas.kicad.org/drc.v1.json",
|
||||
"coordinate_units": "mm",
|
||||
"date": "2026-09-20T22:46:03",
|
||||
"ignored_checks": [
|
||||
{
|
||||
"description": "Footprint has no courtyard defined",
|
||||
"key": "missing_courtyard"
|
||||
},
|
||||
{
|
||||
"description": "Track endpoint not centered on via",
|
||||
"key": "track_not_centered_on_via"
|
||||
},
|
||||
{
|
||||
"description": "Tuning profile track geometries",
|
||||
"key": "tuning_profile_track_geometries"
|
||||
},
|
||||
{
|
||||
"description": "Footprint doesn't match symbol's footprint filters",
|
||||
"key": "footprint_filters_mismatch"
|
||||
},
|
||||
{
|
||||
"description": "Footprint component type doesn't match footprint pads",
|
||||
"key": "footprint_type_mismatch"
|
||||
}
|
||||
],
|
||||
"included_severities": [
|
||||
"error",
|
||||
"warning"
|
||||
],
|
||||
"kicad_version": "10.0.6",
|
||||
"schematic_parity": [],
|
||||
"source": "serial-carrier.kicad_pcb",
|
||||
"unconnected_items": [
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 5 [/RS_DSR] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 114.16
|
||||
},
|
||||
"uuid": "0ceca603-53a8-4a53-85cc-f7f8e7be90e4"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 7 [/RS_DSR] von U2",
|
||||
"pos": {
|
||||
"x": 158.24,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "e2d6b2cb-6025-490c-8371-d7cc34ac37f6"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 6 [/RS_RI] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 116.7
|
||||
},
|
||||
"uuid": "f35f1696-b778-437b-8cce-67b8c4d5f5d0"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 10 [/RS_RI] von U2",
|
||||
"pos": {
|
||||
"x": 165.86,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "e28de6f2-fb85-4849-ba1a-e98e2a231492"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 8 [/RS_RTS] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 121.78
|
||||
},
|
||||
"uuid": "55162730-46fd-465d-a1c5-20aed09ed534"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 8 [/RS_RTS] von U2",
|
||||
"pos": {
|
||||
"x": 160.78,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "5c304724-2bc6-409d-9c59-2e40adc557b9"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 9 [/RS_CTS] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 124.32
|
||||
},
|
||||
"uuid": "f75c20f8-d3b5-4c3b-9919-cb044b2da5c3"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 9 [/RS_CTS] von U2",
|
||||
"pos": {
|
||||
"x": 163.32,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "a8411036-6859-4041-84b8-2c80c326c58e"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 12 [/RS_VALID] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 131.94
|
||||
},
|
||||
"uuid": "2fbc596b-c88f-4fc6-aa94-5dad4fcac9cc"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 11 [/RS_VALID] von U2",
|
||||
"pos": {
|
||||
"x": 168.4,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "9ad42f44-160f-4aa5-bcf0-a4466c090438"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 15 [/RS_OFF_N] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 139.56
|
||||
},
|
||||
"uuid": "34e1afe9-b3fc-4d3a-9e33-38b32244f19e"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 12 [/RS_OFF_N] von U2",
|
||||
"pos": {
|
||||
"x": 170.94,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "a43ba2f2-477d-4850-87de-e1591119d5ae"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 17 [/OLED_SDA] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 144.64
|
||||
},
|
||||
"uuid": "1f91549c-5cb2-465f-86df-b2f1b0c16ed8"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 4 [/OLED_SDA] von DS1",
|
||||
"pos": {
|
||||
"x": 159.81,
|
||||
"y": 137.5
|
||||
},
|
||||
"uuid": "91a4dcef-1b36-4e3d-a15c-c4578077c27b"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 20 [/BTN_NEXT] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 152.26
|
||||
},
|
||||
"uuid": "e7159c90-4e8d-4b21-8e84-a7f59f793440"
|
||||
},
|
||||
{
|
||||
"description": "Leiterbahn [/BTN_NEXT] auf F.Cu, Länge 6,5000 mm",
|
||||
"pos": {
|
||||
"x": 167.0,
|
||||
"y": 168.0
|
||||
},
|
||||
"uuid": "97cfdf4d-f068-4e4a-8684-aae638a809ad"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
}
|
||||
],
|
||||
"violations": []
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/builddir/build/BUILD/kicad-10.0.6-build/kicad-10.0.6/include/properties/property.h(607): assert "m_choices.GetCount() > 0" failed in PROPERTY_ENUM(): No enum choices defined
|
||||
/builddir/build/BUILD/kicad-10.0.6-build/kicad-10.0.6/include/properties/property.h(607): assert "m_choices.GetCount() > 0" failed in PROPERTY_ENUM(): No enum choices defined
|
||||
/builddir/build/BUILD/kicad-10.0.6-build/kicad-10.0.6/include/properties/property.h(607): assert "m_choices.GetCount() > 0" failed in PROPERTY_ENUM(): No enum choices defined
|
||||
{"net": "/+3V3", "start": "U1.1", "end": "U1.2", "start_mm": [108.0, 104.0], "end_mm": [108.0, 106.54], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 26}
|
||||
{"net": "/+3V3", "start": "R2.1", "end": "DS1.2", "start_mm": [154.0, 134.0], "end_mm": [154.73, 137.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 239}
|
||||
{"net": "/+3V3", "start": "U2.1", "end": "R1.1", "start_mm": [143.0, 130.0], "end_mm": [143.0, 134.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 41}
|
||||
{"net": "/BTN_NEXT", "start": "SW3.1", "end": "SW3.1", "start_mm": [167.0, 168.0], "end_mm": [173.5, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 66}
|
||||
{"net": "/BTN_PREVIOUS", "start": "SW1.1", "end": "SW1.1", "start_mm": [149.5, 168.0], "end_mm": [143.0, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 66}
|
||||
{"net": "/BTN_SELECT", "start": "SW2.1", "end": "SW2.1", "start_mm": [155.0, 168.0], "end_mm": [161.5, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 66}
|
||||
{"net": "/+3V3", "start": "DS1.2", "end": "R3.1", "start_mm": [154.73, 137.5], "end_mm": [165.0, 134.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 1334}
|
||||
{"net": "/+3V3", "start": "R1.1", "end": "R2.1", "start_mm": [143.0, 134.0], "end_mm": [154.0, 134.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 3711}
|
||||
{"net": "/BTN_SELECT", "start": "SW2.1", "end": "R2.2", "start_mm": [161.5, 168.0], "end_mm": [161.62, 134.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 681}
|
||||
{"net": "/BTN_NEXT", "start": "SW3.1", "end": "R3.2", "start_mm": [173.5, 168.0], "end_mm": [172.62, 134.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 3329}
|
||||
{"net": "/BTN_PREVIOUS", "start": "R1.2", "end": "SW1.1", "start_mm": [150.62, 134.0], "end_mm": [149.5, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 3971}
|
||||
{"net": "/+3V3", "start": "U1.2", "end": "U2.1", "start_mm": [108.0, 106.54], "end_mm": [143.0, 130.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 224476}
|
||||
{"net": "/RS_RX", "start": "U1.11", "end": "U2.4", "start_mm": [108.0, 129.4], "end_mm": [150.62, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 274662}
|
||||
{"net": "/BTN_PREVIOUS", "start": "U1.16", "end": "R1.2", "start_mm": [108.0, 142.1], "end_mm": [150.62, 134.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 227867}
|
||||
{"net": "/RS_DCD", "start": "U1.4", "end": "U2.3", "start_mm": [108.0, 111.62], "end_mm": [148.08, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 201896}
|
||||
{"net": "/RS_TX", "start": "U1.10", "end": "U2.5", "start_mm": [108.0, 126.86], "end_mm": [153.16, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 271117}
|
||||
{"net": "/RS_DTR", "start": "U1.7", "end": "U2.6", "start_mm": [108.0, 119.24], "end_mm": [155.7, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 307013}
|
||||
{"net": "/OLED_SCL", "start": "U1.18", "end": "DS1.3", "start_mm": [108.0, 147.18], "end_mm": [157.27, 137.5], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 127246}
|
||||
{"net": "/BTN_SELECT", "start": "SW2.1", "end": "U1.19", "start_mm": [155.0, 168.0], "end_mm": [108.0, 149.72], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 73189}
|
||||
{"net": "/OLED_SDA", "start": "U1.17", "end": "DS1.4", "start_mm": [108.0, 144.64], "end_mm": [159.81, 137.5], "routed": false, "width_mm": 0.25, "power_fallback": false, "expansions": 272010}
|
||||
{"net": "/RS_DSR", "start": "U1.5", "end": "U2.7", "start_mm": [108.0, 114.16], "end_mm": [158.24, 130.0], "routed": false, "width_mm": 0.25, "power_fallback": false, "expansions": 272046}
|
||||
{"net": "/RS_RTS", "start": "U1.8", "end": "U2.8", "start_mm": [108.0, 121.78], "end_mm": [160.78, 130.0], "routed": false, "width_mm": 0.25, "power_fallback": false, "expansions": 272010}
|
||||
{"net": "/RS_CTS", "start": "U1.9", "end": "U2.9", "start_mm": [108.0, 124.32], "end_mm": [163.32, 130.0], "routed": false, "width_mm": 0.25, "power_fallback": false, "expansions": 272000}
|
||||
{"net": "/RS_RI", "start": "U1.6", "end": "U2.10", "start_mm": [108.0, 116.7], "end_mm": [165.86, 130.0], "routed": false, "width_mm": 0.25, "power_fallback": false, "expansions": 272008}
|
||||
{"net": "/RS_VALID", "start": "U1.12", "end": "U2.11", "start_mm": [108.0, 131.94], "end_mm": [168.4, 130.0], "routed": false, "width_mm": 0.25, "power_fallback": false, "expansions": 272015}
|
||||
{"net": "/BTN_NEXT", "start": "U1.20", "end": "SW3.1", "start_mm": [108.0, 152.26], "end_mm": [167.0, 168.0], "routed": false, "width_mm": 0.25, "power_fallback": false, "expansions": 272010}
|
||||
{"net": "/RS_OFF_N", "start": "U1.15", "end": "U2.12", "start_mm": [108.0, 139.56], "end_mm": [170.94, 130.0], "routed": false, "width_mm": 0.25, "power_fallback": false, "expansions": 272010}
|
||||
{"net": "/GND", "start": "U1.44", "end": "U1.43", "start_mm": [133.4, 157.34], "end_mm": [133.4, 154.8], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 26}
|
||||
{"net": "/GND", "start": "SW2.2", "end": "SW1.2", "start_mm": [155.0, 172.5], "end_mm": [149.5, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 56}
|
||||
{"net": "/GND", "start": "SW2.2", "end": "SW3.2", "start_mm": [161.5, 172.5], "end_mm": [167.0, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 56}
|
||||
{"net": "/GND", "start": "SW2.2", "end": "SW2.2", "start_mm": [155.0, 172.5], "end_mm": [161.5, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 66}
|
||||
{"net": "/GND", "start": "SW3.2", "end": "SW3.2", "start_mm": [167.0, 172.5], "end_mm": [173.5, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 66}
|
||||
{"net": "/GND", "start": "SW1.2", "end": "SW1.2", "start_mm": [149.5, 172.5], "end_mm": [143.0, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 66}
|
||||
{"net": "/GND", "start": "DS1.1", "end": "U2.2", "start_mm": [152.19, 137.5], "end_mm": [145.54, 130.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 3234}
|
||||
{"net": "/GND", "start": "SW1.2", "end": "U1.44", "start_mm": [143.0, 172.5], "end_mm": [133.4, 157.34], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 29110}
|
||||
{"net": "/GND", "start": "U1.44", "end": "U1.22", "start_mm": [133.4, 157.34], "end_mm": [108.0, 157.34], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 16915}
|
||||
{"net": "/GND", "start": "U1.43", "end": "DS1.1", "start_mm": [133.4, 154.8], "end_mm": [152.19, 137.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 57470}
|
||||
{"net": "/GND", "start": "U2.2", "end": "U1.23", "start_mm": [145.54, 130.0], "end_mm": [133.4, 104.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 34158}
|
||||
22:45:50: Debug: Adding duplicate image handler for 'PNG file'
|
||||
22:45:50: Debug: Adding duplicate image handler for 'JPEG file'
|
||||
22:45:50: Debug: Adding duplicate image handler for 'TIFF file'
|
||||
22:45:50: Debug: Adding duplicate image handler for 'GIF file'
|
||||
22:45:50: Debug: Adding duplicate image handler for 'PNM file'
|
||||
22:45:50: Debug: Adding duplicate image handler for 'PCX file'
|
||||
22:45:50: Debug: Adding duplicate image handler for 'IFF file'
|
||||
22:45:50: Debug: Adding duplicate image handler for 'Windows icon file'
|
||||
22:45:50: Debug: Adding duplicate image handler for 'Windows cursor file'
|
||||
22:45:50: Debug: Adding duplicate image handler for 'Windows animated cursor file'
|
||||
22:45:50: Debug: Adding duplicate image handler for 'TGA file'
|
||||
22:45:50: Debug: Adding duplicate image handler for 'XPM file'
|
||||
{
|
||||
"draft_only": true,
|
||||
"saved": true,
|
||||
"grid_mm": 0.1,
|
||||
"clearance_mm": 0.25,
|
||||
"safety_margin_mm": 0.04,
|
||||
"via_diameter_mm": 0.7,
|
||||
"via_drill_mm": 0.3,
|
||||
"elapsed_seconds": 20.799824999005068,
|
||||
"routed_tree_edges": 30,
|
||||
"total_tree_edges": 38,
|
||||
"tracks": 248,
|
||||
"vias": 13,
|
||||
"immutable_identity_sha256": "81def52e6c1d574b4d3ab1a5859a5266b893df0ea6c5b9aa7d7b688cc4a98519",
|
||||
"input_sha256": "ab3f0775dee575a11d06f80996169f5097dfff672b69152ad9b559707f70009b",
|
||||
"validation": "KiCad DRC must be run separately; tree-edge counts are not DRC connectivity"
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/builddir/build/BUILD/kicad-10.0.6-build/kicad-10.0.6/include/properties/property.h(607): assert "m_choices.GetCount() > 0" failed in PROPERTY_ENUM(): No enum choices defined
|
||||
/builddir/build/BUILD/kicad-10.0.6-build/kicad-10.0.6/include/properties/property.h(607): assert "m_choices.GetCount() > 0" failed in PROPERTY_ENUM(): No enum choices defined
|
||||
/builddir/build/BUILD/kicad-10.0.6-build/kicad-10.0.6/include/properties/property.h(607): assert "m_choices.GetCount() > 0" failed in PROPERTY_ENUM(): No enum choices defined
|
||||
{"net": "/+3V3", "start": "U1.1", "end": "U1.2", "start_mm": [108.0, 104.0], "end_mm": [108.0, 106.54], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 52}
|
||||
{"net": "/+3V3", "start": "R2.1", "end": "DS1.2", "start_mm": [154.0, 134.0], "end_mm": [154.73, 137.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 911}
|
||||
{"net": "/+3V3", "start": "U2.1", "end": "R1.1", "start_mm": [143.0, 130.0], "end_mm": [143.0, 134.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 81}
|
||||
{"net": "/BTN_NEXT", "start": "SW3.1", "end": "SW3.1", "start_mm": [167.0, 168.0], "end_mm": [173.5, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/BTN_PREVIOUS", "start": "SW1.1", "end": "SW1.1", "start_mm": [149.5, 168.0], "end_mm": [143.0, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/BTN_SELECT", "start": "SW2.1", "end": "SW2.1", "start_mm": [155.0, 168.0], "end_mm": [161.5, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/+3V3", "start": "DS1.2", "end": "R3.1", "start_mm": [154.73, 137.5], "end_mm": [165.0, 134.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 5139}
|
||||
{"net": "/+3V3", "start": "R1.1", "end": "R2.1", "start_mm": [143.0, 134.0], "end_mm": [154.0, 134.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 14153}
|
||||
{"net": "/BTN_SELECT", "start": "SW2.1", "end": "R2.2", "start_mm": [161.5, 168.0], "end_mm": [161.62, 134.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 2039}
|
||||
{"net": "/BTN_NEXT", "start": "SW3.1", "end": "R3.2", "start_mm": [173.5, 168.0], "end_mm": [172.62, 134.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 12615}
|
||||
{"net": "/BTN_PREVIOUS", "start": "R1.2", "end": "SW1.1", "start_mm": [150.62, 134.0], "end_mm": [149.5, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 15179}
|
||||
{"net": "/+3V3", "start": "U1.2", "end": "U2.1", "start_mm": [108.0, 106.54], "end_mm": [143.0, 130.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 894511}
|
||||
{"net": "/RS_RX", "start": "U1.11", "end": "U2.4", "start_mm": [108.0, 129.4], "end_mm": [150.62, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 51516}
|
||||
{"net": "/BTN_PREVIOUS", "start": "U1.16", "end": "R1.2", "start_mm": [108.0, 142.1], "end_mm": [150.62, 134.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 94479}
|
||||
{"net": "/RS_DCD", "start": "U1.4", "end": "U2.3", "start_mm": [108.0, 111.62], "end_mm": [148.08, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 266031}
|
||||
{"net": "/RS_TX", "start": "U1.10", "end": "U2.5", "start_mm": [108.0, 126.86], "end_mm": [153.16, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 175689}
|
||||
{"net": "/RS_DTR", "start": "U1.7", "end": "U2.6", "start_mm": [108.0, 119.24], "end_mm": [155.7, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 326823}
|
||||
{"net": "/OLED_SCL", "start": "U1.18", "end": "DS1.3", "start_mm": [108.0, 147.18], "end_mm": [157.27, 137.5], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 82557}
|
||||
{"net": "/BTN_SELECT", "start": "SW2.1", "end": "U1.19", "start_mm": [155.0, 168.0], "end_mm": [108.0, 149.72], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 147997}
|
||||
{"net": "/OLED_SDA", "start": "U1.17", "end": "DS1.4", "start_mm": [108.0, 144.64], "end_mm": [159.81, 137.5], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 266428}
|
||||
{"net": "/RS_DSR", "start": "U1.5", "end": "U2.7", "start_mm": [108.0, 114.16], "end_mm": [158.24, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 372014}
|
||||
{"net": "/RS_RTS", "start": "U1.8", "end": "U2.8", "start_mm": [108.0, 121.78], "end_mm": [160.78, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 298514}
|
||||
{"net": "/RS_CTS", "start": "U1.9", "end": "U2.9", "start_mm": [108.0, 124.32], "end_mm": [163.32, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 265272}
|
||||
{"net": "/RS_RI", "start": "U1.6", "end": "U2.10", "start_mm": [108.0, 116.7], "end_mm": [165.86, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 332506}
|
||||
{"net": "/RS_VALID", "start": "U1.12", "end": "U2.11", "start_mm": [108.0, 131.94], "end_mm": [168.4, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 348384}
|
||||
{"net": "/BTN_NEXT", "start": "U1.20", "end": "SW3.1", "start_mm": [108.0, 152.26], "end_mm": [167.0, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 272795}
|
||||
{"net": "/RS_OFF_N", "start": "U1.15", "end": "U2.12", "start_mm": [108.0, 139.56], "end_mm": [170.94, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 351630}
|
||||
{"net": "/GND", "start": "U1.44", "end": "U1.43", "start_mm": [133.4, 157.34], "end_mm": [133.4, 154.8], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 52}
|
||||
{"net": "/GND", "start": "SW2.2", "end": "SW1.2", "start_mm": [155.0, 172.5], "end_mm": [149.5, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 111}
|
||||
{"net": "/GND", "start": "SW2.2", "end": "SW3.2", "start_mm": [161.5, 172.5], "end_mm": [167.0, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 111}
|
||||
{"net": "/GND", "start": "SW2.2", "end": "SW2.2", "start_mm": [155.0, 172.5], "end_mm": [161.5, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/GND", "start": "SW3.2", "end": "SW3.2", "start_mm": [167.0, 172.5], "end_mm": [173.5, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/GND", "start": "SW1.2", "end": "SW1.2", "start_mm": [149.5, 172.5], "end_mm": [143.0, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/GND", "start": "DS1.1", "end": "U2.2", "start_mm": [152.19, 137.5], "end_mm": [145.54, 130.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 17468}
|
||||
{"net": "/GND", "start": "SW1.2", "end": "U1.44", "start_mm": [143.0, 172.5], "end_mm": [133.4, 157.34], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 27488}
|
||||
{"net": "/GND", "start": "U1.44", "end": "U1.22", "start_mm": [133.4, 157.34], "end_mm": [108.0, 157.34], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 73753}
|
||||
{"net": "/GND", "start": "U1.43", "end": "DS1.1", "start_mm": [133.4, 154.8], "end_mm": [152.19, 137.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 54021}
|
||||
{"net": "/GND", "start": "U2.2", "end": "U1.23", "start_mm": [145.54, 130.0], "end_mm": [133.4, 104.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 98250}
|
||||
22:47:09: Debug: Adding duplicate image handler for 'PNG file'
|
||||
22:47:09: Debug: Adding duplicate image handler for 'JPEG file'
|
||||
22:47:09: Debug: Adding duplicate image handler for 'TIFF file'
|
||||
22:47:09: Debug: Adding duplicate image handler for 'GIF file'
|
||||
22:47:09: Debug: Adding duplicate image handler for 'PNM file'
|
||||
22:47:09: Debug: Adding duplicate image handler for 'PCX file'
|
||||
22:47:09: Debug: Adding duplicate image handler for 'IFF file'
|
||||
22:47:09: Debug: Adding duplicate image handler for 'Windows icon file'
|
||||
22:47:09: Debug: Adding duplicate image handler for 'Windows cursor file'
|
||||
22:47:09: Debug: Adding duplicate image handler for 'Windows animated cursor file'
|
||||
22:47:09: Debug: Adding duplicate image handler for 'TGA file'
|
||||
22:47:09: Debug: Adding duplicate image handler for 'XPM file'
|
||||
{
|
||||
"draft_only": true,
|
||||
"saved": true,
|
||||
"grid_mm": 0.05,
|
||||
"clearance_mm": 0.25,
|
||||
"safety_margin_mm": 0.02,
|
||||
"via_diameter_mm": 0.7,
|
||||
"via_drill_mm": 0.3,
|
||||
"elapsed_seconds": 26.517815405000874,
|
||||
"routed_tree_edges": 38,
|
||||
"total_tree_edges": 38,
|
||||
"tracks": 569,
|
||||
"vias": 20,
|
||||
"immutable_identity_sha256": "81def52e6c1d574b4d3ab1a5859a5266b893df0ea6c5b9aa7d7b688cc4a98519",
|
||||
"input_sha256": "41643d6089dae5ef2eee1a2071a6d11ae4c00f3c922eddfeaafff619fc5fab19",
|
||||
"validation": "KiCad DRC must be run separately; tree-edge counts are not DRC connectivity"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"a0_snapshot_unchanged": true,
|
||||
"a1_unrouted_snapshot_unchanged": true,
|
||||
"protected_files_unchanged": [
|
||||
"serial-carrier.kicad_pro",
|
||||
"serial-carrier.kicad_sch",
|
||||
"serial-carrier.kicad_prl",
|
||||
"fp-lib-table",
|
||||
"sym-lib-table",
|
||||
"Carrier.kicad_sym",
|
||||
"Carrier.pretty/OLED_26mm_I2C_Provisional.kicad_mod",
|
||||
"Carrier.pretty/HW678_2x22_Provisional.kicad_mod",
|
||||
"Carrier.pretty/MAX3243_Reference_Provisional.kicad_mod"
|
||||
],
|
||||
"intentional_documentation_updates": [
|
||||
"README.md",
|
||||
"pcb-draft-notes.md"
|
||||
],
|
||||
"cad_projection_graphics": 7,
|
||||
"footprints_identical_except_authorized_placement": true,
|
||||
"footprint_count": 13,
|
||||
"unrelated_board_graphics_preserved": 5,
|
||||
"non_owned_board_graphic_uuids_preserved": true,
|
||||
"refreshed_device_silkscreen_items": 91,
|
||||
"pcb_editor_lock_absent": true,
|
||||
"final_board_sha256": "53e36538c0669604457b9e1a9ff1b1e20422970e8f9f356088c4581868ad5045"
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/python3
|
||||
"""Compare A1 against the untouched A0 snapshot, including unrelated graphics."""
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import uuid
|
||||
|
||||
ROOT=Path(__file__).resolve().parent.parent
|
||||
OUT=ROOT/'validation'
|
||||
|
||||
|
||||
def parse(path):
|
||||
tokens=iter(re.findall(r'"(?:\\.|[^"\\])*"|[()]|[^\s()]+',path.read_text()))
|
||||
def group():
|
||||
result=[]
|
||||
for token in tokens:
|
||||
if token==')':
|
||||
return result
|
||||
result.append(group() if token=='(' else token)
|
||||
raise ValueError('unterminated S-expression')
|
||||
assert next(tokens)=='('
|
||||
return group()
|
||||
|
||||
|
||||
def child(item,key):
|
||||
return next(v for v in item if isinstance(v,list) and v[0]==key)
|
||||
|
||||
|
||||
def ident(item):
|
||||
return child(item,'uuid')[1].strip('"')
|
||||
|
||||
|
||||
def sha(path):
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
report=json.loads(OUT.joinpath('pcb-routing-reposition.json').read_text())
|
||||
a0=OUT/'pcb-a0-before-reposition.kicad_pcb'
|
||||
a1=ROOT/'serial-carrier.kicad_pcb'
|
||||
assert sha(a0)==report['a0_snapshot_sha256'], 'A0 snapshot changed'
|
||||
assert sha(OUT/'pcb-routing-input.kicad_pcb')==report['a1_unrouted_sha256'], 'A1 routing input changed'
|
||||
# These two handoff documents are intentionally updated during A1 integration.
|
||||
protected={name:digest for name,digest in report['protected_file_sha256'].items()
|
||||
if name not in ('README.md','pcb-draft-notes.md')}
|
||||
for name,digest in protected.items():
|
||||
assert sha(ROOT/name)==digest, 'protected file changed: '+name
|
||||
old,new=parse(a0),parse(a1)
|
||||
old_fp={ident(x):x for x in old if isinstance(x,list) and x[0]=='footprint'}
|
||||
new_fp={ident(x):x for x in new if isinstance(x,list) and x[0]=='footprint'}
|
||||
assert set(old_fp)==set(new_fp)
|
||||
for key,fp in old_fp.items():
|
||||
# Footprint geometry, fields, UUIDs and connectivity must be identical;
|
||||
# only the top-level placement may differ, and only for the four moves.
|
||||
ref=next(v[2].strip('"') for v in fp if isinstance(v,list) and v[:2]==['property','"Reference"'])
|
||||
cleaned=lambda f:[v for v in f if not isinstance(v,list) or v[0]!='at']
|
||||
assert cleaned(fp)==cleaned(new_fp[key]), 'footprint content changed: '+ref
|
||||
if ref not in report['moves_local_mm']:
|
||||
assert child(fp,'at')==child(new_fp[key],'at'),ref
|
||||
else:
|
||||
expected=report['moves_local_mm'][ref][1]
|
||||
assert [round(float(v)-100,6) for v in child(new_fp[key],'at')[1:3]]==expected,ref
|
||||
ns=uuid.UUID('67964aa4-c3a5-45d8-9b54-250fb68e7c41')
|
||||
owned={str(uuid.uuid5(ns,str(i))) for i in range(4096)}
|
||||
cad_ns=uuid.UUID('ba7274a8-c35d-46bc-908d-cd86708fc6c4')
|
||||
cad_owned={str(uuid.uuid5(cad_ns,str(i))) for i in range(7)}
|
||||
changed_texts=set(report['changed_board_text_uuids'])
|
||||
for item in old:
|
||||
if isinstance(item,list) and item[0]=='gr_text':
|
||||
value=json.loads(item[1])
|
||||
if value=='82 x 80 mm / 2 copper layers' or value.startswith('DE-9 OUT /'):
|
||||
changed_texts.add(ident(item))
|
||||
|
||||
def drawings(tree):
|
||||
return {ident(x):x for x in tree if isinstance(x,list) and
|
||||
(x[0].startswith('gr_') or x[0] in ('dimension','target','image'))}
|
||||
|
||||
old_gr,new_gr=drawings(old),drawings(new)
|
||||
edge_ids={key for key,value in old_gr.items() if child(value,'layer')[1]=='"Edge.Cuts"'}
|
||||
assert cad_owned <= set(new_gr), 'missing CAD projection graphics'
|
||||
assert all(child(new_gr[key],'layer')[1]=='"Dwgs.User"' for key in cad_owned)
|
||||
excluded=owned|cad_owned|changed_texts|edge_ids
|
||||
unchanged_old={key:value for key,value in old_gr.items() if key not in excluded}
|
||||
unchanged_new={key:value for key,value in new_gr.items() if key not in excluded}
|
||||
assert unchanged_old==unchanged_new, 'unrelated board graphics changed'
|
||||
assert set(old_gr)-owned==set(new_gr)-owned-cad_owned, 'non-owned board graphic UUID set changed'
|
||||
assert [x for x in old if isinstance(x,list) and x[0]=='net']==[x for x in new if isinstance(x,list) and x[0]=='net']
|
||||
assert not (ROOT/'~serial-carrier.kicad_pcb.lck').exists(), 'PCB reopened before handoff'
|
||||
result=dict(a0_snapshot_unchanged=True,a1_unrouted_snapshot_unchanged=True,
|
||||
protected_files_unchanged=list(protected),
|
||||
intentional_documentation_updates=['README.md','pcb-draft-notes.md'],
|
||||
cad_projection_graphics=len(cad_owned),
|
||||
footprints_identical_except_authorized_placement=True,
|
||||
footprint_count=len(old_fp),unrelated_board_graphics_preserved=len(unchanged_old),
|
||||
non_owned_board_graphic_uuids_preserved=True,
|
||||
refreshed_device_silkscreen_items=len(set(new_gr)&owned),
|
||||
pcb_editor_lock_absent=True,final_board_sha256=sha(a1))
|
||||
OUT.joinpath('pcb-routing-preservation.json').write_text(json.dumps(result,indent=2)+'\n')
|
||||
print(json.dumps(result,indent=2))
|
||||
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"revision": "A1",
|
||||
"a0_snapshot_sha256": "25669051086d6a5315650580ead8ce847f42ae07b8d08af5d76f0a813131ff18",
|
||||
"a1_unrouted_sha256": "df62a5aeb64a6d871a477a8c9fe94c100210d5b239a0617558907d9d3d9a6007",
|
||||
"outline_local_mm": [
|
||||
[
|
||||
36,
|
||||
0
|
||||
],
|
||||
[
|
||||
82,
|
||||
0
|
||||
],
|
||||
[
|
||||
82,
|
||||
80
|
||||
],
|
||||
[
|
||||
0,
|
||||
80
|
||||
],
|
||||
[
|
||||
0,
|
||||
19
|
||||
],
|
||||
[
|
||||
36,
|
||||
19
|
||||
]
|
||||
],
|
||||
"moves_local_mm": {
|
||||
"U1": [
|
||||
[
|
||||
8,
|
||||
4
|
||||
],
|
||||
[
|
||||
8,
|
||||
23
|
||||
]
|
||||
],
|
||||
"U2": [
|
||||
[
|
||||
43,
|
||||
30
|
||||
],
|
||||
[
|
||||
43,
|
||||
26.67
|
||||
]
|
||||
],
|
||||
"H1": [
|
||||
[
|
||||
3.5,
|
||||
3.5
|
||||
],
|
||||
[
|
||||
3.5,
|
||||
22.5
|
||||
]
|
||||
],
|
||||
"H2": [
|
||||
[
|
||||
3.5,
|
||||
56
|
||||
],
|
||||
[
|
||||
3.5,
|
||||
75
|
||||
]
|
||||
]
|
||||
},
|
||||
"changed_board_text_uuids": [
|
||||
"e8228b54-6114-48a6-80ef-7f0a68175b1d",
|
||||
"ce061da3-831a-4fb1-9676-9177435d649e",
|
||||
"f45876c7-f270-4231-bfa4-b69ded09f3a4",
|
||||
"c695eb94-004e-4a56-801a-0ca6797a46b2",
|
||||
"94303a67-2a65-4a16-9499-2b409a657ba6",
|
||||
"fa700fde-d4f3-437c-9b51-4322d0fd68e0",
|
||||
"de46e0e9-8a3a-42d0-b1c7-8915a9856836"
|
||||
],
|
||||
"normalized_footprints_preserved": true,
|
||||
"nets_preserved": true,
|
||||
"protected_file_sha256": {
|
||||
"serial-carrier.kicad_pro": "9ad5b992785ebfe01fa7ea4e1aa391a2c7d7f1f2c3b1c13136173c301ecb6516",
|
||||
"serial-carrier.kicad_sch": "1876ef30aae16d9eb4a421efea7c090c50cddd16a59a9bfe866ed5c802e2571c",
|
||||
"serial-carrier.kicad_prl": "b448331403441aabec6e4718d3c82f4fac4073fa9430762d6bb0b5b34c16def9",
|
||||
"README.md": "d3339535217e2c2e214a620e7826878d2ae635eec288041f57012966e95765ca",
|
||||
"pcb-draft-notes.md": "7be3fa469867b98530a84a61fc978b89d853a1652963a8d24373a64f367f8360",
|
||||
"fp-lib-table": "a56dc436d1442de03c7953b426a90444dce078d57b24f70f1675d0beb19acd4e",
|
||||
"sym-lib-table": "f3520b095a4f6238fb7a46dd5c8ab460a8e842dc085a53d2c7f912609015b5ae",
|
||||
"Carrier.kicad_sym": "ba642459312b5b5cb5b5021d0b573c427c3ab93a2cde51941f018bbcba07716a",
|
||||
"Carrier.pretty/OLED_26mm_I2C_Provisional.kicad_mod": "ab2aa7cb0e84d31f18e2493088830964b04764f44873dfb0e30a4c7faa43ef37",
|
||||
"Carrier.pretty/HW678_2x22_Provisional.kicad_mod": "87a32630a388ebd1646ca04a1152e29833735a62e2fca980eaa187c7ca386e20",
|
||||
"Carrier.pretty/MAX3243_Reference_Provisional.kicad_mod": "90c7c0b24007a7e8cb6815641363880fe420fe60e76ea7670c83c217a6d47ecd"
|
||||
},
|
||||
"remaining_tracks": 0
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
#!/usr/bin/python3
|
||||
"""One-shot A0 -> A1 placement migration of the existing board, never regeneration.
|
||||
|
||||
Requires --apply and the untouched pcb-a0-before-reposition.kicad_pcb snapshot.
|
||||
Never replaces the snapshot or accepts an already edited/migrated input.
|
||||
"""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
import pcbnew as p
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(ROOT))
|
||||
from route_pcb_draft import identity
|
||||
|
||||
PCB = ROOT / 'serial-carrier.kicad_pcb'
|
||||
BACKUP = ROOT / 'validation/pcb-a0-before-reposition.kicad_pcb'
|
||||
LOCK = ROOT / '~serial-carrier.kicad_pcb.lck'
|
||||
OLD = [(0,0),(82,0),(82,80),(36,80),(36,61),(0,61)]
|
||||
NEW = [(36,0),(82,0),(82,80),(0,80),(0,19),(36,19)]
|
||||
MOVES = {'U1': ((8,4),(8,23)), 'U2': ((43,30),(43,26.670)),
|
||||
'H1': ((3.5,3.5),(3.5,22.5)), 'H2': ((3.5,56),(3.5,75))}
|
||||
TEXT_MOVES = {'RF RESERVE':19, 'VERIFY ANTENNA':19, 'ESP32 HW678':19,
|
||||
'USB ACCESS - VERIFY':19, 'DE-9 OUT / VERIFY MALE':-3.330,
|
||||
'MAX3243 REF':-3.330}
|
||||
|
||||
|
||||
def vec(x,y):
|
||||
return p.VECTOR2I(p.FromMM(x),p.FromMM(y))
|
||||
|
||||
|
||||
def local(v):
|
||||
return (round(p.ToMM(v.x)-100,6),round(p.ToMM(v.y)-100,6))
|
||||
|
||||
|
||||
def sha(path):
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def normalized_footprints(board):
|
||||
# Normalize placement only, retaining UUIDs, local pad geometry and linkage.
|
||||
entries = identity(board)[1]
|
||||
normalized = []
|
||||
for entry in entries:
|
||||
fields = list(entry)
|
||||
x,y = fields[5]
|
||||
fields[5] = (0,0)
|
||||
pads = []
|
||||
for pad in fields[-1]:
|
||||
values = list(pad)
|
||||
values[3] = (round(values[3][0]-x,6),round(values[3][1]-y,6))
|
||||
pads.append(tuple(values))
|
||||
fields[-1] = pads
|
||||
normalized.append(tuple(fields))
|
||||
return normalized
|
||||
|
||||
|
||||
def main():
|
||||
parser=argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--apply',action='store_true')
|
||||
args=parser.parse_args()
|
||||
if not args.apply:
|
||||
parser.error('explicit --apply required; only accepts the original A0 snapshot')
|
||||
assert not LOCK.exists(), 'PCB Editor lock exists'
|
||||
assert BACKUP.exists(), 'make the one-time A0 snapshot first'
|
||||
before=PCB.read_bytes()
|
||||
assert before==BACKUP.read_bytes(), 'current board differs from A0 snapshot; refusing migration'
|
||||
protected=[ROOT/name for name in ('serial-carrier.kicad_pro','serial-carrier.kicad_sch',
|
||||
'serial-carrier.kicad_prl','README.md','pcb-draft-notes.md','fp-lib-table',
|
||||
'sym-lib-table','Carrier.kicad_sym')]
|
||||
protected+=list((ROOT/'Carrier.pretty').glob('*'))
|
||||
hashes={str(path.relative_to(ROOT)):sha(path) for path in protected if path.is_file()}
|
||||
board=p.LoadBoard(str(PCB))
|
||||
immutable=normalized_footprints(board)
|
||||
net_table=identity(board)[-1]
|
||||
footprints={f.GetReference():f for f in board.GetFootprints()}
|
||||
for ref,(old,new) in MOVES.items():
|
||||
fp=footprints[ref]
|
||||
assert local(fp.GetPosition())==old,(ref,local(fp.GetPosition()))
|
||||
fp.SetPosition(vec(new[0]+100,new[1]+100))
|
||||
for track in list(board.GetTracks()):
|
||||
board.Delete(track)
|
||||
edges=[d for d in board.GetDrawings() if d.GetLayer()==p.Edge_Cuts]
|
||||
assert len(edges)==6
|
||||
for i,(a,b) in enumerate(zip(OLD,OLD[1:]+OLD[:1])):
|
||||
edge=next(e for e in edges if frozenset((local(e.GetStart()),local(e.GetEnd())))==frozenset((a,b)))
|
||||
aa,bb=NEW[i],NEW[(i+1)%6]
|
||||
edge.SetStart(vec(aa[0]+100,aa[1]+100))
|
||||
edge.SetEnd(vec(bb[0]+100,bb[1]+100))
|
||||
assert len(board.Zones())==1 and board.Zones()[0].GetIsRuleArea()
|
||||
zone=board.Zones()[0]
|
||||
contour=zone.Outline().COutline(0)
|
||||
assert {local(contour.CPoint(i)) for i in range(contour.PointCount())}=={(10,0),(31,0),(31,13),(10,13)}
|
||||
zone.Move(vec(0,19))
|
||||
changed_texts=[]
|
||||
for text,dy in TEXT_MOVES.items():
|
||||
found=[d for d in board.GetDrawings() if isinstance(d,p.PCB_TEXT) and d.GetText()==text]
|
||||
assert len(found)==1,(text,len(found))
|
||||
drawing=found[0]
|
||||
drawing.Move(vec(0,dy))
|
||||
if text=='DE-9 OUT / VERIFY MALE':
|
||||
drawing.SetText('DE-9 OUT / NOMINAL MALE')
|
||||
changed_texts.append(drawing.m_Uuid.AsString())
|
||||
for d in board.GetDrawings():
|
||||
if isinstance(d,p.PCB_TEXT) and d.GetText()=='Carrier envelope only; module overhang and connector clearance not qualified':
|
||||
d.SetText('Nominal RS-232 metal verified; ESP32 USB / antenna / assembly clearance pending')
|
||||
changed_texts.append(d.m_Uuid.AsString())
|
||||
title=board.GetTitleBlock()
|
||||
title.SetRevision('A1 DRAFT')
|
||||
title.SetComment(0,'NOT FOR FABRICATION - USB / antenna geometry pending; verify module pinout / assembly')
|
||||
assert normalized_footprints(board)==immutable
|
||||
assert identity(board)[-1]==net_table
|
||||
candidate=ROOT/'validation/pcb-routing-reposition-candidate.kicad_pcb'
|
||||
assert p.SaveBoard(str(candidate),board,True)
|
||||
saved=p.LoadBoard(str(candidate))
|
||||
assert normalized_footprints(saved)==immutable
|
||||
assert identity(saved)==identity(board)
|
||||
assert not LOCK.exists() and PCB.read_bytes()==before
|
||||
assert all(sha(ROOT/name)==digest for name,digest in hashes.items()), 'protected input changed'
|
||||
candidate.replace(PCB)
|
||||
report=dict(revision='A1',a0_snapshot_sha256=sha(BACKUP),a1_unrouted_sha256=sha(PCB),
|
||||
outline_local_mm=NEW,moves_local_mm=MOVES,changed_board_text_uuids=changed_texts,
|
||||
normalized_footprints_preserved=True,nets_preserved=True,
|
||||
protected_file_sha256=hashes,remaining_tracks=len(saved.GetTracks()))
|
||||
ROOT.joinpath('validation/pcb-routing-reposition.json').write_text(json.dumps(report,indent=2)+'\n')
|
||||
print(json.dumps(report,indent=2))
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
main()
|
||||
@@ -0,0 +1,665 @@
|
||||
{
|
||||
"draft_only": true,
|
||||
"saved": true,
|
||||
"grid_mm": 0.05,
|
||||
"clearance_mm": 0.25,
|
||||
"safety_margin_mm": 0.02,
|
||||
"via_diameter_mm": 0.7,
|
||||
"via_drill_mm": 0.3,
|
||||
"elapsed_seconds": 32.44472043699898,
|
||||
"connections": [
|
||||
{
|
||||
"net": "/GND",
|
||||
"start": "U1.43",
|
||||
"end": "U1.44",
|
||||
"start_mm": [
|
||||
133.4,
|
||||
173.8
|
||||
],
|
||||
"end_mm": [
|
||||
133.4,
|
||||
176.34
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 52
|
||||
},
|
||||
{
|
||||
"net": "/+3V3",
|
||||
"start": "U1.1",
|
||||
"end": "U1.2",
|
||||
"start_mm": [
|
||||
108.0,
|
||||
123.0
|
||||
],
|
||||
"end_mm": [
|
||||
108.0,
|
||||
125.54
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 52
|
||||
},
|
||||
{
|
||||
"net": "/+3V3",
|
||||
"start": "R2.1",
|
||||
"end": "DS1.2",
|
||||
"start_mm": [
|
||||
154.0,
|
||||
134.0
|
||||
],
|
||||
"end_mm": [
|
||||
154.73,
|
||||
137.5
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 911
|
||||
},
|
||||
{
|
||||
"net": "/GND",
|
||||
"start": "SW2.2",
|
||||
"end": "SW1.2",
|
||||
"start_mm": [
|
||||
155.0,
|
||||
172.5
|
||||
],
|
||||
"end_mm": [
|
||||
149.5,
|
||||
172.5
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 111
|
||||
},
|
||||
{
|
||||
"net": "/GND",
|
||||
"start": "SW2.2",
|
||||
"end": "SW3.2",
|
||||
"start_mm": [
|
||||
161.5,
|
||||
172.5
|
||||
],
|
||||
"end_mm": [
|
||||
167.0,
|
||||
172.5
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 111
|
||||
},
|
||||
{
|
||||
"net": "/BTN_NEXT",
|
||||
"start": "SW3.1",
|
||||
"end": "SW3.1",
|
||||
"start_mm": [
|
||||
167.0,
|
||||
168.0
|
||||
],
|
||||
"end_mm": [
|
||||
173.5,
|
||||
168.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 131
|
||||
},
|
||||
{
|
||||
"net": "/BTN_PREVIOUS",
|
||||
"start": "SW1.1",
|
||||
"end": "SW1.1",
|
||||
"start_mm": [
|
||||
143.0,
|
||||
168.0
|
||||
],
|
||||
"end_mm": [
|
||||
149.5,
|
||||
168.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 131
|
||||
},
|
||||
{
|
||||
"net": "/BTN_SELECT",
|
||||
"start": "SW2.1",
|
||||
"end": "SW2.1",
|
||||
"start_mm": [
|
||||
155.0,
|
||||
168.0
|
||||
],
|
||||
"end_mm": [
|
||||
161.5,
|
||||
168.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 131
|
||||
},
|
||||
{
|
||||
"net": "/GND",
|
||||
"start": "SW2.2",
|
||||
"end": "SW2.2",
|
||||
"start_mm": [
|
||||
155.0,
|
||||
172.5
|
||||
],
|
||||
"end_mm": [
|
||||
161.5,
|
||||
172.5
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 131
|
||||
},
|
||||
{
|
||||
"net": "/GND",
|
||||
"start": "SW3.2",
|
||||
"end": "SW3.2",
|
||||
"start_mm": [
|
||||
167.0,
|
||||
172.5
|
||||
],
|
||||
"end_mm": [
|
||||
173.5,
|
||||
172.5
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 131
|
||||
},
|
||||
{
|
||||
"net": "/GND",
|
||||
"start": "SW1.2",
|
||||
"end": "SW1.2",
|
||||
"start_mm": [
|
||||
149.5,
|
||||
172.5
|
||||
],
|
||||
"end_mm": [
|
||||
143.0,
|
||||
172.5
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 131
|
||||
},
|
||||
{
|
||||
"net": "/+3V3",
|
||||
"start": "U2.1",
|
||||
"end": "R1.1",
|
||||
"start_mm": [
|
||||
143.0,
|
||||
126.67
|
||||
],
|
||||
"end_mm": [
|
||||
143.0,
|
||||
134.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 148
|
||||
},
|
||||
{
|
||||
"net": "/GND",
|
||||
"start": "SW1.2",
|
||||
"end": "U1.43",
|
||||
"start_mm": [
|
||||
143.0,
|
||||
172.5
|
||||
],
|
||||
"end_mm": [
|
||||
133.4,
|
||||
173.8
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 4535
|
||||
},
|
||||
{
|
||||
"net": "/+3V3",
|
||||
"start": "DS1.2",
|
||||
"end": "R3.1",
|
||||
"start_mm": [
|
||||
154.73,
|
||||
137.5
|
||||
],
|
||||
"end_mm": [
|
||||
165.0,
|
||||
134.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 5139
|
||||
},
|
||||
{
|
||||
"net": "/+3V3",
|
||||
"start": "R1.1",
|
||||
"end": "R2.1",
|
||||
"start_mm": [
|
||||
143.0,
|
||||
134.0
|
||||
],
|
||||
"end_mm": [
|
||||
154.0,
|
||||
134.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 14153
|
||||
},
|
||||
{
|
||||
"net": "/GND",
|
||||
"start": "U2.2",
|
||||
"end": "U1.23",
|
||||
"start_mm": [
|
||||
145.54,
|
||||
126.67
|
||||
],
|
||||
"end_mm": [
|
||||
133.4,
|
||||
123.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 9372
|
||||
},
|
||||
{
|
||||
"net": "/GND",
|
||||
"start": "DS1.1",
|
||||
"end": "U2.2",
|
||||
"start_mm": [
|
||||
152.19,
|
||||
137.5
|
||||
],
|
||||
"end_mm": [
|
||||
145.54,
|
||||
126.67
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 8327
|
||||
},
|
||||
{
|
||||
"net": "/BTN_SELECT",
|
||||
"start": "SW2.1",
|
||||
"end": "R2.2",
|
||||
"start_mm": [
|
||||
161.5,
|
||||
168.0
|
||||
],
|
||||
"end_mm": [
|
||||
161.62,
|
||||
134.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 2039
|
||||
},
|
||||
{
|
||||
"net": "/BTN_NEXT",
|
||||
"start": "SW3.1",
|
||||
"end": "R3.2",
|
||||
"start_mm": [
|
||||
173.5,
|
||||
168.0
|
||||
],
|
||||
"end_mm": [
|
||||
172.62,
|
||||
134.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 12615
|
||||
},
|
||||
{
|
||||
"net": "/BTN_PREVIOUS",
|
||||
"start": "SW1.1",
|
||||
"end": "R1.2",
|
||||
"start_mm": [
|
||||
149.5,
|
||||
168.0
|
||||
],
|
||||
"end_mm": [
|
||||
150.62,
|
||||
134.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 15179
|
||||
},
|
||||
{
|
||||
"net": "/+3V3",
|
||||
"start": "U1.2",
|
||||
"end": "U2.1",
|
||||
"start_mm": [
|
||||
108.0,
|
||||
125.54
|
||||
],
|
||||
"end_mm": [
|
||||
143.0,
|
||||
126.67
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 332527
|
||||
},
|
||||
{
|
||||
"net": "/BTN_PREVIOUS",
|
||||
"start": "U1.16",
|
||||
"end": "SW1.1",
|
||||
"start_mm": [
|
||||
108.0,
|
||||
161.1
|
||||
],
|
||||
"end_mm": [
|
||||
143.0,
|
||||
168.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 70393
|
||||
},
|
||||
{
|
||||
"net": "/RS_DCD",
|
||||
"start": "U1.4",
|
||||
"end": "U2.3",
|
||||
"start_mm": [
|
||||
108.0,
|
||||
130.62
|
||||
],
|
||||
"end_mm": [
|
||||
148.08,
|
||||
126.67
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 21855
|
||||
},
|
||||
{
|
||||
"net": "/BTN_SELECT",
|
||||
"start": "SW2.1",
|
||||
"end": "U1.19",
|
||||
"start_mm": [
|
||||
155.0,
|
||||
168.0
|
||||
],
|
||||
"end_mm": [
|
||||
108.0,
|
||||
168.72
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 36364
|
||||
},
|
||||
{
|
||||
"net": "/RS_RX",
|
||||
"start": "U1.11",
|
||||
"end": "U2.4",
|
||||
"start_mm": [
|
||||
108.0,
|
||||
148.4
|
||||
],
|
||||
"end_mm": [
|
||||
150.62,
|
||||
126.67
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 123675
|
||||
},
|
||||
{
|
||||
"net": "/RS_TX",
|
||||
"start": "U1.10",
|
||||
"end": "U2.5",
|
||||
"start_mm": [
|
||||
108.0,
|
||||
145.86
|
||||
],
|
||||
"end_mm": [
|
||||
153.16,
|
||||
126.67
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 397389
|
||||
},
|
||||
{
|
||||
"net": "/RS_DTR",
|
||||
"start": "U1.7",
|
||||
"end": "U2.6",
|
||||
"start_mm": [
|
||||
108.0,
|
||||
138.24
|
||||
],
|
||||
"end_mm": [
|
||||
155.7,
|
||||
126.67
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 294174
|
||||
},
|
||||
{
|
||||
"net": "/RS_DSR",
|
||||
"start": "U1.5",
|
||||
"end": "U2.7",
|
||||
"start_mm": [
|
||||
108.0,
|
||||
133.16
|
||||
],
|
||||
"end_mm": [
|
||||
158.24,
|
||||
126.67
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 294332
|
||||
},
|
||||
{
|
||||
"net": "/RS_RTS",
|
||||
"start": "U1.8",
|
||||
"end": "U2.8",
|
||||
"start_mm": [
|
||||
108.0,
|
||||
140.78
|
||||
],
|
||||
"end_mm": [
|
||||
160.78,
|
||||
126.67
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 378833
|
||||
},
|
||||
{
|
||||
"net": "/OLED_SCL",
|
||||
"start": "U1.18",
|
||||
"end": "DS1.3",
|
||||
"start_mm": [
|
||||
108.0,
|
||||
166.18
|
||||
],
|
||||
"end_mm": [
|
||||
157.27,
|
||||
137.5
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 442107
|
||||
},
|
||||
{
|
||||
"net": "/RS_CTS",
|
||||
"start": "U1.9",
|
||||
"end": "U2.9",
|
||||
"start_mm": [
|
||||
108.0,
|
||||
143.32
|
||||
],
|
||||
"end_mm": [
|
||||
163.32,
|
||||
126.67
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 409990
|
||||
},
|
||||
{
|
||||
"net": "/OLED_SDA",
|
||||
"start": "U1.17",
|
||||
"end": "DS1.4",
|
||||
"start_mm": [
|
||||
108.0,
|
||||
163.64
|
||||
],
|
||||
"end_mm": [
|
||||
159.81,
|
||||
137.5
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 567654
|
||||
},
|
||||
{
|
||||
"net": "/RS_RI",
|
||||
"start": "U1.6",
|
||||
"end": "U2.10",
|
||||
"start_mm": [
|
||||
108.0,
|
||||
135.7
|
||||
],
|
||||
"end_mm": [
|
||||
165.86,
|
||||
126.67
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 436874
|
||||
},
|
||||
{
|
||||
"net": "/BTN_NEXT",
|
||||
"start": "U1.20",
|
||||
"end": "SW3.1",
|
||||
"start_mm": [
|
||||
108.0,
|
||||
171.26
|
||||
],
|
||||
"end_mm": [
|
||||
167.0,
|
||||
168.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 273975
|
||||
},
|
||||
{
|
||||
"net": "/RS_VALID",
|
||||
"start": "U1.12",
|
||||
"end": "U2.11",
|
||||
"start_mm": [
|
||||
108.0,
|
||||
150.94
|
||||
],
|
||||
"end_mm": [
|
||||
168.4,
|
||||
126.67
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 425742
|
||||
},
|
||||
{
|
||||
"net": "/RS_OFF_N",
|
||||
"start": "U1.15",
|
||||
"end": "U2.12",
|
||||
"start_mm": [
|
||||
108.0,
|
||||
158.56
|
||||
],
|
||||
"end_mm": [
|
||||
170.94,
|
||||
126.67
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 645804
|
||||
},
|
||||
{
|
||||
"net": "/GND",
|
||||
"start": "U1.44",
|
||||
"end": "U1.22",
|
||||
"start_mm": [
|
||||
133.4,
|
||||
176.34
|
||||
],
|
||||
"end_mm": [
|
||||
108.0,
|
||||
176.34
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 509
|
||||
},
|
||||
{
|
||||
"net": "/GND",
|
||||
"start": "SW1.2",
|
||||
"end": "DS1.1",
|
||||
"start_mm": [
|
||||
149.5,
|
||||
172.5
|
||||
],
|
||||
"end_mm": [
|
||||
152.19,
|
||||
137.5
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 617019
|
||||
}
|
||||
],
|
||||
"routed_tree_edges": 38,
|
||||
"total_tree_edges": 38,
|
||||
"tracks": 565,
|
||||
"vias": 22,
|
||||
"immutable_identity_sha256": "783ce7e212880e0836c93e1782d39bbfbc505fae856d9720ee6c50f5ba8f6275",
|
||||
"input_sha256": "0ea7f0abff371eacc81262dc949f768cd42128f389ea4af9facee5034368d884",
|
||||
"validation": "KiCad DRC must be run separately; tree-edge counts are not DRC connectivity"
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
# A1 top-left-notch / bottom-USB routing — current result
|
||||
|
||||
Date: 2026-09-21. Actual revised board: `../serial-carrier.kicad_pcb`.
|
||||
|
||||
**38/38 connections across 17 multi-pad nets; 0 physical DRC violations,
|
||||
0 unconnected items; exactly 1 expected U2 footprint parity warning.**
|
||||
The warning is the board-only `Carrier:MAX3243_Reference_Provisional` assignment
|
||||
versus the intentionally blank schematic footprint, not a net mismatch.
|
||||
|
||||
## Geometry and preservation
|
||||
|
||||
Coordinates below are local to absolute origin (100,100) mm; all dimensions mm.
|
||||
|
||||
- Carrier: 82 x 80, outline `[(36,0),(82,0),(82,80),(0,80),(0,19),(36,19)]`.
|
||||
- U1 origin: (8,23), formerly (8,4). Header end-pad centres now Y=76.340;
|
||||
USB guide Y=83 is 3 mm below the bottom edge. No ESP32 body was invented.
|
||||
- U2 origin: (43,26.670), formerly (43,30). Actual reference F.Fab bare-PCB
|
||||
front edge is absolute Y=100.000, flush with carrier local Y=0.
|
||||
- Nominal male metal external dimensions are user-confirmed: shell overhang
|
||||
3.870, hex-mouth overhang 2.870, flange front 1.930 behind the carrier edge.
|
||||
With this placement their local Y positions are -3.870, -2.870 and +1.930.
|
||||
Integration added seven board-local Dwgs.User graphics for the CAD metal
|
||||
projections and screw-lock axes.
|
||||
- H1: (3.5,22.5), H2: (3.5,75). H3/H4 unchanged at (78,4)/(78,76).
|
||||
- OLED, resistors and switches unchanged.
|
||||
- RF reserve: local X=10..31 / Y=19..32, absolute X=110..131 / Y=119..132,
|
||||
both layers; no tracks, vias or fills inside it. Boundary remains provisional.
|
||||
|
||||
The existing A0 board was mutated, **not regenerated**. The one-time
|
||||
`pcb-a0-before-reposition.kicad_pcb` backup remains byte-for-byte intact.
|
||||
`pcb-routing-input.kicad_pcb` is now the A1 unrouted audit input, not A0.
|
||||
`pcb-routing-preservation.py` verified all 13 complete serialized footprints
|
||||
identical except the four authorized top-level placements, including schematic
|
||||
fields, pad/net definitions, electrical UUID paths and all footprint graphics.
|
||||
Non-owned board-graphic UUIDs and unrelated graphics are preserved. ESP/RS/RF
|
||||
annotations moved with placement; the post-routing silkscreen refresh replaced
|
||||
91 owned graphics. Its edge clipping avoids silk across U2's flush front edge.
|
||||
|
||||
All board saves used `SaveBoard(..., True)`. Project `.pro`, `.prl`, schematic,
|
||||
symbol/footprint libraries and library tables retain their original hashes.
|
||||
README and pcb-draft-notes were intentionally updated during integration. The generator's constants/text were updated but it was **not
|
||||
run**; its automatic project/netclass writes were removed. PCB Editor lock was
|
||||
absent at migration, routing, silkscreen refresh and final preservation check.
|
||||
|
||||
## Routing and independent measurements
|
||||
|
||||
- F.Cu/B.Cu; **565 segments, 22 through vias**.
|
||||
- **451 signal segments at 0.25 mm; 114 +3V3/GND segments at 0.5 mm**.
|
||||
- Vias: 0.7 mm copper / 0.3 mm drill. No copper pours needed.
|
||||
- Continuous measured minimum different-net/NPTH clearance: **0.270544 mm**.
|
||||
- Minimum copper/NPTH-to-edge gap: **1.900 mm**.
|
||||
- Minimum copper/NPTH-to-RF-reservation gap: **0.025 mm**.
|
||||
- Minimum via-drill edge to other drilled-hole edge: **0.820544 mm**.
|
||||
|
||||
Two bounded passes, each with 240 seconds / 1,200,000 expansions per search:
|
||||
|
||||
1. 31.18 seconds, 38/38, 674 segments / 16 vias, physical DRC clean. One GND
|
||||
connection required the router's 0.25 mm fallback; strict width validation
|
||||
correctly failed. Retained as `pcb-routing-a1-pass1.kicad_pcb` for evidence.
|
||||
2. 32.44 seconds, 38/38, 565 segments / 22 vias. Short GND connections (<=15 mm)
|
||||
now route before longer signal fanout; long GND links remain last. All power
|
||||
and ground now use 0.5 mm; no fallback. This is the saved current result.
|
||||
|
||||
The grid remains 0.05 mm, physical clearance 0.25 mm plus 0.02 mm routing margin.
|
||||
No rip-up/unbounded retry or external network router was used.
|
||||
|
||||
## Validation / rerun commands
|
||||
|
||||
From `hardware/PCB`:
|
||||
|
||||
```sh
|
||||
/usr/bin/python3 -B validate_pcb_draft.py
|
||||
/usr/bin/python3 -B validation/pcb-routing-check.py
|
||||
/usr/bin/python3 -B validation/pcb-routing-preservation.py
|
||||
kicad-cli pcb drc --format json --all-track-errors --severity-all --exit-code-violations -o validation/pcb-routing-all-drc.json serial-carrier.kicad_pcb
|
||||
```
|
||||
|
||||
All passed on A1. Validators perform fresh KiCad DRC, fresh schematic-netlist
|
||||
comparison, geometry checks, continuous copper-distance checks, and routing
|
||||
save-refusal tests. DRC was read-only to preserve project settings; no copper
|
||||
zones exist to refill. Existing ignored DRC checks were not changed; see reports.
|
||||
The preservation audit includes the intentional documentation updates and seven
|
||||
CAD projection graphics while retaining electrical and project preservation checks.
|
||||
|
||||
Rerouting remains explicitly destructive to existing/manual copper and requires:
|
||||
|
||||
```sh
|
||||
/usr/bin/python3 -B route_pcb_draft.py --route --overwrite --replace-routes --seconds 240 --max-expansions 1200000
|
||||
/usr/bin/python3 -B add_device_silkscreen.py
|
||||
```
|
||||
|
||||
Then rerun the validators. The one-shot `pcb-routing-reposition.py --apply`
|
||||
refuses anything except the original A0 snapshot and must **not** be rerun on A1.
|
||||
|
||||
Current evidence: `pcb-routing-a1-pass2.log`, `pcb-routing-run.json`,
|
||||
`pcb-routing-drc.json`, `pcb-routing-all-drc.json`, `pcb-parity-drc.json`,
|
||||
`pcb-validation-summary.json`, `pcb-routing-check.json`,
|
||||
`pcb-routing-reposition.json`, and `pcb-routing-preservation.json`.
|
||||
The current post-integration board SHA-256 is recorded in
|
||||
`pcb-routing-check.json`; the CAD projection additions do not change copper.
|
||||
|
||||
**Still a draft, not fabrication-ready.** ESP32 geometry/antenna/USB access,
|
||||
module pinout/header fit, full assembly/height clearances, power integrity and
|
||||
hardware operation remain unqualified. Nominal RS-232 metal dimensions alone
|
||||
are not assembly qualification. Placement PDF, SVG/PNG and carrier-only 3D
|
||||
preview were refreshed after CAD envelope integration. No Gerbers generated.
|
||||
|
||||
---
|
||||
|
||||
# Historical A0 routing result — superseded by A1 above
|
||||
|
||||
The following records the earlier A0 routing/integration. Its dimensions, counts,
|
||||
measurements and no-save reproduction do not describe current A1. A0 `pass1`,
|
||||
`pass2`, `pass3`, `baseline` and `dry-run` files are historical; use the A1 and
|
||||
current reports listed above.
|
||||
|
||||
Date: 2026-09-20. KiCad 10.0.6; system `/usr/bin/python3`.
|
||||
|
||||
## Saved result
|
||||
|
||||
Actual copper routing is saved in `../serial-carrier.kicad_pcb`, not just a preview
|
||||
or an exported proposal. **38/38 required spanning-tree connections across 17
|
||||
multi-pad nets routed; KiCad reports 0 unconnected items, 0 errors, 0 warnings.**
|
||||
Intentional single-pad `unconnected-(...)` nets retain their original names and
|
||||
are not connected to anything else.
|
||||
|
||||
- Two copper layers, F.Cu and B.Cu.
|
||||
- 569 straight track segments: 425 at 0.25 mm for signals and 144 at 0.5 mm for
|
||||
+3V3/GND. No narrow power-track fallback was necessary.
|
||||
- 20 through vias, 0.7 mm copper diameter / 0.3 mm drill.
|
||||
- No copper pours: all ground connections were routed explicitly.
|
||||
- Original RF rule area, x110..131 / y100..113 mm on both copper layers, preserved.
|
||||
No tracks, vias or fills enter the reservation. Minimum copper-to-reservation
|
||||
gap measured independently is 0.025 mm; this is geometric compliance with the
|
||||
specified provisional boundary, **not validation of an antenna's RF needs**.
|
||||
- All 9 electrical footprints and 4 standalone mounting footprints preserved.
|
||||
Pad positions, shapes, sizes, drills, types, nets, functions, pin types and UUIDs;
|
||||
footprint placement, orientation, library IDs, values, UUIDs and schematic UUID
|
||||
paths; net table, outline and RF rule-area geometry/flags match the input.
|
||||
- Generator, README, schematic, project and libraries were not edited by routing.
|
||||
No external network dependency or manufacturing exports were used.
|
||||
|
||||
## Validation performed
|
||||
|
||||
From `hardware/PCB`:
|
||||
|
||||
```sh
|
||||
kicad-cli pcb drc --format json --refill-zones --save-board -o validation/pcb-routing-drc.json serial-carrier.kicad_pcb
|
||||
kicad-cli pcb drc --format json --all-track-errors --severity-all --exit-code-violations -o validation/pcb-routing-all-drc.json serial-carrier.kicad_pcb
|
||||
/usr/bin/python3 -B validation/pcb-routing-check.py
|
||||
```
|
||||
|
||||
Both DRC runs: **0 violations and 0 unconnected items**. No DRC settings or
|
||||
exclusions were added or changed. Existing project rules specify 0.25 mm minimum
|
||||
clearance, 0.5 mm copper-edge clearance, 0.25 mm minimum track width and 0.7 mm
|
||||
minimum via diameter.
|
||||
|
||||
Independent continuous-geometry audit (`pcb-routing-check.json`):
|
||||
|
||||
| Check | Measured minimum | Requirement |
|
||||
|---|---:|---:|
|
||||
| Different-net copper / copper-to-NPTH clearance | 0.275 mm | 0.25 mm |
|
||||
| Copper or NPTH edge to board edge | 0.525 mm | 0.5 mm |
|
||||
| Copper / NPTH edge to RF reserve | 0.025 mm | No intersection |
|
||||
| Via drill edge to another drilled hole edge | 0.840175 mm | 0.25 mm |
|
||||
|
||||
The audit uses exact straight-line/point/rectangle distance calculations with
|
||||
copper radii, rather than the router's raster masks. It also verifies dimensions,
|
||||
source-preservation invariants, and three overwrite-refusal cases. Requires
|
||||
locally installed `pcbnew`, NumPy and Shapely; the router itself only needs
|
||||
`pcbnew` and NumPy.
|
||||
|
||||
DRC still honors the project's pre-existing ignored checks:
|
||||
`missing_courtyard`, `track_not_centered_on_via`,
|
||||
`tuning_profile_track_geometries`, `footprint_filters_mismatch`, and
|
||||
`footprint_type_mismatch`. `--severity-all` does not enable ignored checks.
|
||||
The routing subtask compared electrical UUID linkage and pad-net assignments
|
||||
with the input board. Subsequent integration also ran fresh schematic-parity DRC
|
||||
and added missing symbol metadata fields to the PCB without changing copper.
|
||||
That check reports exactly one expected U2 footprint warning (provisional PCB
|
||||
footprint versus intentionally blank schematic assignment); no net mismatch.
|
||||
See `pcb-parity-drc.json` and `pcb-validation-summary.json`.
|
||||
|
||||
## Method and bounded passes
|
||||
|
||||
`../route_pcb_draft.py` uses a two-layer 8-direction raster A* search, an explicit
|
||||
via cost, exact-pad-centre terminal stubs, conservative foreign-copper and hole
|
||||
obstacles, and only collinear path compression. It preserves the L-shaped edge
|
||||
and RF reservation. Per-net minimum spanning trees connect every physical pad,
|
||||
including duplicate switch contacts; short links precede longer links and GND is
|
||||
last. It does not optimize return-current paths, topology or total via count.
|
||||
|
||||
1. Initial 0.1 mm raster / 0.04 mm safety margin / 250,000 expansions per search:
|
||||
29/38 connections; DRC 0 violations, 9 unconnected.
|
||||
2. Increased search allowance to 1,200,000 expansions: 30/38; DRC 0 violations,
|
||||
8 unconnected. Some raster header passages were blocked, not just expensive.
|
||||
3. Final 0.05 mm raster / 0.02 mm additional safety margin, with 0.25 mm physical
|
||||
clearance unchanged: 38/38 in approximately 26.5 seconds; DRC fully connected.
|
||||
Each pass had a 240-second routing deadline. No unresolved routing remains.
|
||||
|
||||
A KiCad Python binding ownership problem in the first replacement attempt caused
|
||||
an exception/segfault before any board save. Replacement now uses `board.Delete`
|
||||
instead of `board.Remove`; the subsequent save and no-save rerun both passed.
|
||||
The known startup PROPERTY_ENUM assertions do not prevent validation.
|
||||
|
||||
## Rerun safety
|
||||
|
||||
By default the script never saves. The existing routed board is protected even
|
||||
from a preview unless `--replace-routes` is given. A preview can be run with:
|
||||
|
||||
```sh
|
||||
/usr/bin/python3 -B route_pcb_draft.py --replace-routes --seconds 240
|
||||
```
|
||||
|
||||
This exact no-save workflow was tested: it again found 38/38 connections, 569
|
||||
segments and 20 vias, and left the board byte-for-byte unchanged.
|
||||
|
||||
**The following command discards all existing/manual tracks and vias**, then
|
||||
replaces them with fresh draft routing. Back up manual work first:
|
||||
|
||||
```sh
|
||||
/usr/bin/python3 -B route_pcb_draft.py --route --overwrite --replace-routes --seconds 240 --max-expansions 1200000
|
||||
```
|
||||
|
||||
The script refuses a PCB-editor lock, unexpected outline/RF/board structure,
|
||||
unsupported pad orientation/layers, unapproved existing routes, or detected board
|
||||
changes before save. Candidate serialization must preserve immutable geometry
|
||||
and linkage before replacing the board. It deliberately does not run DRC itself:
|
||||
rerun the validation commands after any routing change. Runtime/expansion limits
|
||||
can produce a partial board; inspect both the run report and actual DRC, not just
|
||||
the script exit status.
|
||||
|
||||
## Evidence and remaining limitations
|
||||
|
||||
- `pcb-routing-input.kicad_pcb`: unrouted input snapshot, after baseline DRC save.
|
||||
- `pcb-routing-baseline.json`: 38 unconnected / no violations before routing.
|
||||
- `pcb-routing-pass1-drc.json`, `pcb-routing-pass2-drc.json`: intermediate DRC.
|
||||
- `pcb-routing-pass2.log`, `pcb-routing-pass3.log`: bounded routing progress.
|
||||
- `pcb-routing-run.json`: final saved-route metrics and connection list.
|
||||
- `pcb-routing-drc.json`, `pcb-routing-all-drc.json`: final KiCad DRC evidence.
|
||||
- `pcb-routing-check.py`, `pcb-routing-check.json`: repeatable independent audit.
|
||||
- `pcb-routing-dry-run.log`: successful final no-save reproduction.
|
||||
|
||||
Routing-only DRC-saved snapshot SHA-256 (before integration copied schematic
|
||||
metadata fields): `bf94b6c9b00c43a97f933054758a2c9d6541981ae09f2f285dcdb567bd73d9b1`.
|
||||
The current post-integration hash is recorded by `pcb-routing-check.json`;
|
||||
geometry, pad-net linkage and copper are unchanged by the metadata copy.
|
||||
|
||||
**Still DRAFT / NOT FOR FABRICATION.** Provisional module/connector geometry,
|
||||
antenna envelope, assembly/courtyard clearances, USB access, male RS-232 connector
|
||||
fit, physical pin mapping, power integrity, EMI and hardware operation are not
|
||||
qualified by routing or DRC. All previously documented mechanical/electrical
|
||||
release gates remain. No physical-device or manufacturing validation performed.
|
||||
@@ -0,0 +1,604 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<export version="E">
|
||||
<design>
|
||||
<source>/home/mscholz/Repos/ESP32_serial_swiss_army_knife/hardware/PCB/serial-carrier.kicad_sch</source>
|
||||
<date>2026-09-21T11:39:08</date>
|
||||
<tool>Eeschema 10.0.6-1.fc44</tool>
|
||||
<sheet number="1" name="/" tstamps="/">
|
||||
<title_block>
|
||||
<title>ESP32 Serial Swiss Army Knife - Module Carrier</title>
|
||||
<company/>
|
||||
<rev>A0 DRAFT</rev>
|
||||
<date>2026-09-20</date>
|
||||
<source>serial-carrier.kicad_sch</source>
|
||||
<comment number="1" value="Not fabrication-ready: provisional module footprints and male RS-232 mapping"/>
|
||||
<comment number="2" value=""/>
|
||||
<comment number="3" value=""/>
|
||||
<comment number="4" value=""/>
|
||||
<comment number="5" value=""/>
|
||||
<comment number="6" value=""/>
|
||||
<comment number="7" value=""/>
|
||||
<comment number="8" value=""/>
|
||||
<comment number="9" value=""/>
|
||||
</title_block>
|
||||
</sheet>
|
||||
</design>
|
||||
<components>
|
||||
<comp ref="DS1">
|
||||
<value>128x64 I2C OLED / 0x3C</value>
|
||||
<footprint>Carrier:OLED_26mm_I2C_Provisional</footprint>
|
||||
<fields>
|
||||
<field name="Status">Module outline/header/display user fit-verified; pad/drill fit provisional</field>
|
||||
<field name="Footprint">Carrier:OLED_26mm_I2C_Provisional</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="OLED_I2C_26mm" description="User fit-verified 26 mm OLED; front/top header GND VCC SCL SDA"/>
|
||||
<property name="Status" value="Module outline/header/display user fit-verified; pad/drill fit provisional"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>0ae411c1-94cc-5551-9037-1d59c01a8ee5</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
<pin num="3"/>
|
||||
<pin num="4"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
<comp ref="R1">
|
||||
<value>2.2k 1%</value>
|
||||
<footprint>Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal</footprint>
|
||||
<fields>
|
||||
<field name="Rating">At least 0.125 W; exact resistor MPN pending</field>
|
||||
<field name="Footprint">Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="R" description="Resistor, axial DIN0207 footprint; exact MPN pending"/>
|
||||
<property name="Rating" value="At least 0.125 W; exact resistor MPN pending"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>defb78eb-632f-5f9d-a59d-dffbb1d31dec</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
<comp ref="R2">
|
||||
<value>2.2k 1%</value>
|
||||
<footprint>Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal</footprint>
|
||||
<fields>
|
||||
<field name="Rating">At least 0.125 W; exact resistor MPN pending</field>
|
||||
<field name="Footprint">Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="R" description="Resistor, axial DIN0207 footprint; exact MPN pending"/>
|
||||
<property name="Rating" value="At least 0.125 W; exact resistor MPN pending"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>405055c0-35ab-5334-ad4d-30d45c5ef6f2</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
<comp ref="R3">
|
||||
<value>2.2k 1%</value>
|
||||
<footprint>Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal</footprint>
|
||||
<fields>
|
||||
<field name="Rating">At least 0.125 W; exact resistor MPN pending</field>
|
||||
<field name="Footprint">Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="R" description="Resistor, axial DIN0207 footprint; exact MPN pending"/>
|
||||
<property name="Rating" value="At least 0.125 W; exact resistor MPN pending"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>78f9a74f-5d91-5687-b50e-68176333df23</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
<comp ref="SW1">
|
||||
<value>B3F-1000</value>
|
||||
<footprint>Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x</footprint>
|
||||
<fields>
|
||||
<field name="Manufacturer">Omron</field>
|
||||
<field name="MPN">B3F-1000</field>
|
||||
<field name="Function">Previous / back</field>
|
||||
<field name="Procurement">Live stock not verified</field>
|
||||
<field name="Footprint">Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="SW_NO" description="Normally open contact; footprint duplicates pads 1 and 2 for internally common terminal pairs"/>
|
||||
<property name="Manufacturer" value="Omron"/>
|
||||
<property name="MPN" value="B3F-1000"/>
|
||||
<property name="Function" value="Previous / back"/>
|
||||
<property name="Procurement" value="Live stock not verified"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>dd5f1f75-0093-55fc-9814-437ed26871eb</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
<comp ref="SW2">
|
||||
<value>B3F-1000</value>
|
||||
<footprint>Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x</footprint>
|
||||
<fields>
|
||||
<field name="Manufacturer">Omron</field>
|
||||
<field name="MPN">B3F-1000</field>
|
||||
<field name="Function">Select / confirm</field>
|
||||
<field name="Procurement">Live stock not verified</field>
|
||||
<field name="Footprint">Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="SW_NO" description="Normally open contact; footprint duplicates pads 1 and 2 for internally common terminal pairs"/>
|
||||
<property name="Manufacturer" value="Omron"/>
|
||||
<property name="MPN" value="B3F-1000"/>
|
||||
<property name="Function" value="Select / confirm"/>
|
||||
<property name="Procurement" value="Live stock not verified"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>c522a716-55a4-584d-a12e-ab135b4c08eb</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
<comp ref="SW3">
|
||||
<value>B3F-1000</value>
|
||||
<footprint>Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x</footprint>
|
||||
<fields>
|
||||
<field name="Manufacturer">Omron</field>
|
||||
<field name="MPN">B3F-1000</field>
|
||||
<field name="Function">Next</field>
|
||||
<field name="Procurement">Live stock not verified</field>
|
||||
<field name="Footprint">Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="SW_NO" description="Normally open contact; footprint duplicates pads 1 and 2 for internally common terminal pairs"/>
|
||||
<property name="Manufacturer" value="Omron"/>
|
||||
<property name="MPN" value="B3F-1000"/>
|
||||
<property name="Function" value="Next"/>
|
||||
<property name="Procurement" value="Live stock not verified"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>35caa6bb-42a6-512c-848c-f1e4457aa97f</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
<comp ref="U1">
|
||||
<value>HW678 / S3-N16R8</value>
|
||||
<footprint>Carrier:HW678_2x22_Provisional</footprint>
|
||||
<fields>
|
||||
<field name="Status">Header geometry nominal; outline/antenna clearance pending</field>
|
||||
<field name="Pin_numbering">Left 1-22; right 23-44; both top-to-bottom, antenna up</field>
|
||||
<field name="Footprint">Carrier:HW678_2x22_Provisional</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="HW678_N16R8" description="HW-678 V0.0.0 / N16R8 carrier interface; project numbering; onboard USB/regulator retained"/>
|
||||
<property name="Status" value="Header geometry nominal; outline/antenna clearance pending"/>
|
||||
<property name="Pin_numbering" value="Left 1-22; right 23-44; both top-to-bottom, antenna up"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>3ab265d0-7fd3-5891-94eb-d39426c6a8a3</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
<pin num="3"/>
|
||||
<pin num="4"/>
|
||||
<pin num="5"/>
|
||||
<pin num="6"/>
|
||||
<pin num="7"/>
|
||||
<pin num="8"/>
|
||||
<pin num="9"/>
|
||||
<pin num="10"/>
|
||||
<pin num="11"/>
|
||||
<pin num="12"/>
|
||||
<pin num="13"/>
|
||||
<pin num="14"/>
|
||||
<pin num="15"/>
|
||||
<pin num="16"/>
|
||||
<pin num="17"/>
|
||||
<pin num="18"/>
|
||||
<pin num="19"/>
|
||||
<pin num="20"/>
|
||||
<pin num="21"/>
|
||||
<pin num="22"/>
|
||||
<pin num="23"/>
|
||||
<pin num="24"/>
|
||||
<pin num="25"/>
|
||||
<pin num="26"/>
|
||||
<pin num="27"/>
|
||||
<pin num="28"/>
|
||||
<pin num="29"/>
|
||||
<pin num="30"/>
|
||||
<pin num="31"/>
|
||||
<pin num="32"/>
|
||||
<pin num="33"/>
|
||||
<pin num="34"/>
|
||||
<pin num="35"/>
|
||||
<pin num="36"/>
|
||||
<pin num="37"/>
|
||||
<pin num="38"/>
|
||||
<pin num="39"/>
|
||||
<pin num="40"/>
|
||||
<pin num="41"/>
|
||||
<pin num="42"/>
|
||||
<pin num="43"/>
|
||||
<pin num="44"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
<comp ref="U2">
|
||||
<value>Adafruit MAX3243 / male 6253</value>
|
||||
<fields>
|
||||
<field name="Status">VERIFY male header numbering; footprint intentionally unassigned</field>
|
||||
<field name="Datasheet_source">adafruit-research.md; reference female 5988 CAD</field>
|
||||
<field name="Footprint"/>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="MAX3243_Module_Reference" description="Adafruit male 6253 intended; physical numbering from published female 5988 CAD, VERIFY MALE"/>
|
||||
<property name="Status" value="VERIFY male header numbering; footprint intentionally unassigned"/>
|
||||
<property name="Datasheet_source" value="adafruit-research.md; reference female 5988 CAD"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>1127195e-e393-5d1d-9119-b25d10556144</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
<pin num="3"/>
|
||||
<pin num="4"/>
|
||||
<pin num="5"/>
|
||||
<pin num="6"/>
|
||||
<pin num="7"/>
|
||||
<pin num="8"/>
|
||||
<pin num="9"/>
|
||||
<pin num="10"/>
|
||||
<pin num="11"/>
|
||||
<pin num="12"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
</components>
|
||||
<libparts>
|
||||
<libpart lib="Carrier" part="HW678_N16R8">
|
||||
<description>HW-678 V0.0.0 / N16R8 carrier interface; project numbering; onboard USB/regulator retained</description>
|
||||
<fields>
|
||||
<field name="Reference">U</field>
|
||||
<field name="Value">HW678_N16R8</field>
|
||||
<field name="Footprint">Carrier:HW678_2x22_Provisional</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description">HW-678 V0.0.0 / N16R8 carrier interface; project numbering; onboard USB/regulator retained</field>
|
||||
</fields>
|
||||
<pins>
|
||||
<pin num="1" name="3V3" type="power_out"/>
|
||||
<pin num="2" name="3V3" type="passive"/>
|
||||
<pin num="3" name="EN" type="input"/>
|
||||
<pin num="4" name="GPIO4" type="input"/>
|
||||
<pin num="5" name="GPIO5" type="input"/>
|
||||
<pin num="6" name="GPIO6" type="input"/>
|
||||
<pin num="7" name="GPIO7" type="output"/>
|
||||
<pin num="8" name="GPIO15" type="output"/>
|
||||
<pin num="9" name="GPIO16" type="input"/>
|
||||
<pin num="10" name="GPIO17" type="output"/>
|
||||
<pin num="11" name="GPIO18" type="input"/>
|
||||
<pin num="12" name="GPIO8" type="input"/>
|
||||
<pin num="13" name="GPIO3" type="bidirectional"/>
|
||||
<pin num="14" name="GPIO46" type="bidirectional"/>
|
||||
<pin num="15" name="GPIO9" type="bidirectional"/>
|
||||
<pin num="16" name="GPIO10" type="input"/>
|
||||
<pin num="17" name="GPIO11" type="bidirectional"/>
|
||||
<pin num="18" name="GPIO12" type="output"/>
|
||||
<pin num="19" name="GPIO13" type="input"/>
|
||||
<pin num="20" name="GPIO14" type="input"/>
|
||||
<pin num="21" name="5V" type="passive"/>
|
||||
<pin num="22" name="GND" type="power_out"/>
|
||||
<pin num="23" name="GND" type="passive"/>
|
||||
<pin num="24" name="GPIO43" type="bidirectional"/>
|
||||
<pin num="25" name="GPIO44" type="bidirectional"/>
|
||||
<pin num="26" name="GPIO1" type="bidirectional"/>
|
||||
<pin num="27" name="GPIO2" type="bidirectional"/>
|
||||
<pin num="28" name="GPIO42" type="bidirectional"/>
|
||||
<pin num="29" name="GPIO41" type="bidirectional"/>
|
||||
<pin num="30" name="GPIO40" type="bidirectional"/>
|
||||
<pin num="31" name="GPIO39" type="bidirectional"/>
|
||||
<pin num="32" name="GPIO38" type="bidirectional"/>
|
||||
<pin num="33" name="GPIO37" type="bidirectional"/>
|
||||
<pin num="34" name="GPIO36" type="bidirectional"/>
|
||||
<pin num="35" name="GPIO35" type="bidirectional"/>
|
||||
<pin num="36" name="GPIO0" type="bidirectional"/>
|
||||
<pin num="37" name="GPIO45" type="bidirectional"/>
|
||||
<pin num="38" name="GPIO48" type="bidirectional"/>
|
||||
<pin num="39" name="GPIO47" type="bidirectional"/>
|
||||
<pin num="40" name="GPIO21" type="bidirectional"/>
|
||||
<pin num="41" name="GPIO20" type="bidirectional"/>
|
||||
<pin num="42" name="GPIO19" type="bidirectional"/>
|
||||
<pin num="43" name="GND" type="passive"/>
|
||||
<pin num="44" name="GND" type="passive"/>
|
||||
</pins>
|
||||
</libpart>
|
||||
<libpart lib="Carrier" part="MAX3243_Module_Reference">
|
||||
<description>Adafruit male 6253 intended; physical numbering from published female 5988 CAD, VERIFY MALE</description>
|
||||
<fields>
|
||||
<field name="Reference">U</field>
|
||||
<field name="Value">MAX3243_Module_Reference</field>
|
||||
<field name="Footprint"/>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description">Adafruit male 6253 intended; physical numbering from published female 5988 CAD, VERIFY MALE</field>
|
||||
</fields>
|
||||
<pins>
|
||||
<pin num="1" name="Vin" type="power_in"/>
|
||||
<pin num="2" name="GND" type="power_in"/>
|
||||
<pin num="3" name="DCD" type="output"/>
|
||||
<pin num="4" name="RX" type="output"/>
|
||||
<pin num="5" name="TX" type="input"/>
|
||||
<pin num="6" name="DTR" type="input"/>
|
||||
<pin num="7" name="DSR" type="output"/>
|
||||
<pin num="8" name="RTS" type="input"/>
|
||||
<pin num="9" name="CTS" type="output"/>
|
||||
<pin num="10" name="RI" type="output"/>
|
||||
<pin num="11" name="VLD" type="output"/>
|
||||
<pin num="12" name="OFF_N" type="input"/>
|
||||
</pins>
|
||||
</libpart>
|
||||
<libpart lib="Carrier" part="OLED_I2C_26mm">
|
||||
<description>User fit-verified 26 mm OLED; front/top header GND VCC SCL SDA</description>
|
||||
<fields>
|
||||
<field name="Reference">DS</field>
|
||||
<field name="Value">OLED_I2C_26mm</field>
|
||||
<field name="Footprint">Carrier:OLED_26mm_I2C_Provisional</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description">User fit-verified 26 mm OLED; front/top header GND VCC SCL SDA</field>
|
||||
</fields>
|
||||
<pins>
|
||||
<pin num="1" name="GND" type="power_in"/>
|
||||
<pin num="2" name="VCC" type="power_in"/>
|
||||
<pin num="3" name="SCL" type="input"/>
|
||||
<pin num="4" name="SDA" type="bidirectional"/>
|
||||
</pins>
|
||||
</libpart>
|
||||
<libpart lib="Carrier" part="R">
|
||||
<description>Resistor, axial DIN0207 footprint; exact MPN pending</description>
|
||||
<fields>
|
||||
<field name="Reference">R</field>
|
||||
<field name="Value">R</field>
|
||||
<field name="Footprint">Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description">Resistor, axial DIN0207 footprint; exact MPN pending</field>
|
||||
</fields>
|
||||
<pins>
|
||||
<pin num="1" name="" type="passive"/>
|
||||
<pin num="2" name="" type="passive"/>
|
||||
</pins>
|
||||
</libpart>
|
||||
<libpart lib="Carrier" part="SW_NO">
|
||||
<description>Normally open contact; footprint duplicates pads 1 and 2 for internally common terminal pairs</description>
|
||||
<fields>
|
||||
<field name="Reference">SW</field>
|
||||
<field name="Value">SW_NO</field>
|
||||
<field name="Footprint">Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description">Normally open contact; footprint duplicates pads 1 and 2 for internally common terminal pairs</field>
|
||||
</fields>
|
||||
<pins>
|
||||
<pin num="1" name="" type="passive"/>
|
||||
<pin num="2" name="" type="passive"/>
|
||||
</pins>
|
||||
</libpart>
|
||||
</libparts>
|
||||
<libraries>
|
||||
<library logical="Carrier">
|
||||
<uri>${KIPRJMOD}/Carrier.kicad_sym</uri>
|
||||
</library>
|
||||
</libraries>
|
||||
<nets>
|
||||
<net code="1" name="/+3V3" class="Power">
|
||||
<node ref="DS1" pin="2" pinfunction="VCC_2" pintype="power_in"/>
|
||||
<node ref="R1" pin="1" pintype="passive"/>
|
||||
<node ref="R2" pin="1" pintype="passive"/>
|
||||
<node ref="R3" pin="1" pintype="passive"/>
|
||||
<node ref="U1" pin="1" pinfunction="3V3_1" pintype="power_out"/>
|
||||
<node ref="U1" pin="2" pinfunction="3V3_2" pintype="passive"/>
|
||||
<node ref="U2" pin="1" pinfunction="Vin_1" pintype="power_in"/>
|
||||
</net>
|
||||
<net code="2" name="/BTN_NEXT" class="Default">
|
||||
<node ref="R3" pin="2" pintype="passive"/>
|
||||
<node ref="SW3" pin="1" pintype="passive"/>
|
||||
<node ref="U1" pin="20" pinfunction="GPIO14_20" pintype="input"/>
|
||||
</net>
|
||||
<net code="3" name="/BTN_PREVIOUS" class="Default">
|
||||
<node ref="R1" pin="2" pintype="passive"/>
|
||||
<node ref="SW1" pin="1" pintype="passive"/>
|
||||
<node ref="U1" pin="16" pinfunction="GPIO10_16" pintype="input"/>
|
||||
</net>
|
||||
<net code="4" name="/BTN_SELECT" class="Default">
|
||||
<node ref="R2" pin="2" pintype="passive"/>
|
||||
<node ref="SW2" pin="1" pintype="passive"/>
|
||||
<node ref="U1" pin="19" pinfunction="GPIO13_19" pintype="input"/>
|
||||
</net>
|
||||
<net code="5" name="/GND" class="Power">
|
||||
<node ref="DS1" pin="1" pinfunction="GND_1" pintype="power_in"/>
|
||||
<node ref="SW1" pin="2" pintype="passive"/>
|
||||
<node ref="SW2" pin="2" pintype="passive"/>
|
||||
<node ref="SW3" pin="2" pintype="passive"/>
|
||||
<node ref="U1" pin="22" pinfunction="GND_22" pintype="power_out"/>
|
||||
<node ref="U1" pin="23" pinfunction="GND_23" pintype="passive"/>
|
||||
<node ref="U1" pin="43" pinfunction="GND_43" pintype="passive"/>
|
||||
<node ref="U1" pin="44" pinfunction="GND_44" pintype="passive"/>
|
||||
<node ref="U2" pin="2" pinfunction="GND_2" pintype="power_in"/>
|
||||
</net>
|
||||
<net code="6" name="/OLED_SCL" class="Default">
|
||||
<node ref="DS1" pin="3" pinfunction="SCL_3" pintype="input"/>
|
||||
<node ref="U1" pin="18" pinfunction="GPIO12_18" pintype="output"/>
|
||||
</net>
|
||||
<net code="7" name="/OLED_SDA" class="Default">
|
||||
<node ref="DS1" pin="4" pinfunction="SDA_4" pintype="bidirectional"/>
|
||||
<node ref="U1" pin="17" pinfunction="GPIO11_17" pintype="bidirectional"/>
|
||||
</net>
|
||||
<net code="8" name="/RS_CTS" class="Default">
|
||||
<node ref="U1" pin="9" pinfunction="GPIO16_9" pintype="input"/>
|
||||
<node ref="U2" pin="9" pinfunction="CTS_9" pintype="output"/>
|
||||
</net>
|
||||
<net code="9" name="/RS_DCD" class="Default">
|
||||
<node ref="U1" pin="4" pinfunction="GPIO4_4" pintype="input"/>
|
||||
<node ref="U2" pin="3" pinfunction="DCD_3" pintype="output"/>
|
||||
</net>
|
||||
<net code="10" name="/RS_DSR" class="Default">
|
||||
<node ref="U1" pin="5" pinfunction="GPIO5_5" pintype="input"/>
|
||||
<node ref="U2" pin="7" pinfunction="DSR_7" pintype="output"/>
|
||||
</net>
|
||||
<net code="11" name="/RS_DTR" class="Default">
|
||||
<node ref="U1" pin="7" pinfunction="GPIO7_7" pintype="output"/>
|
||||
<node ref="U2" pin="6" pinfunction="DTR_6" pintype="input"/>
|
||||
</net>
|
||||
<net code="12" name="/RS_OFF_N" class="Default">
|
||||
<node ref="U1" pin="15" pinfunction="GPIO9_15" pintype="bidirectional"/>
|
||||
<node ref="U2" pin="12" pinfunction="OFF_N_12" pintype="input"/>
|
||||
</net>
|
||||
<net code="13" name="/RS_RI" class="Default">
|
||||
<node ref="U1" pin="6" pinfunction="GPIO6_6" pintype="input"/>
|
||||
<node ref="U2" pin="10" pinfunction="RI_10" pintype="output"/>
|
||||
</net>
|
||||
<net code="14" name="/RS_RTS" class="Default">
|
||||
<node ref="U1" pin="8" pinfunction="GPIO15_8" pintype="output"/>
|
||||
<node ref="U2" pin="8" pinfunction="RTS_8" pintype="input"/>
|
||||
</net>
|
||||
<net code="15" name="/RS_RX" class="Default">
|
||||
<node ref="U1" pin="11" pinfunction="GPIO18_11" pintype="input"/>
|
||||
<node ref="U2" pin="4" pinfunction="RX_4" pintype="output"/>
|
||||
</net>
|
||||
<net code="16" name="/RS_TX" class="Default">
|
||||
<node ref="U1" pin="10" pinfunction="GPIO17_10" pintype="output"/>
|
||||
<node ref="U2" pin="5" pinfunction="TX_5" pintype="input"/>
|
||||
</net>
|
||||
<net code="17" name="/RS_VALID" class="Default">
|
||||
<node ref="U1" pin="12" pinfunction="GPIO8_12" pintype="input"/>
|
||||
<node ref="U2" pin="11" pinfunction="VLD_11" pintype="output"/>
|
||||
</net>
|
||||
<net code="18" name="unconnected-(U1-5V-Pad21)" class="Default">
|
||||
<node ref="U1" pin="21" pinfunction="5V_21" pintype="passive+no_connect"/>
|
||||
</net>
|
||||
<net code="19" name="unconnected-(U1-EN-Pad3)" class="Default">
|
||||
<node ref="U1" pin="3" pinfunction="EN_3" pintype="input+no_connect"/>
|
||||
</net>
|
||||
<net code="20" name="unconnected-(U1-GPIO0-Pad36)" class="Default">
|
||||
<node ref="U1" pin="36" pinfunction="GPIO0_36" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="21" name="unconnected-(U1-GPIO1-Pad26)" class="Default">
|
||||
<node ref="U1" pin="26" pinfunction="GPIO1_26" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="22" name="unconnected-(U1-GPIO2-Pad27)" class="Default">
|
||||
<node ref="U1" pin="27" pinfunction="GPIO2_27" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="23" name="unconnected-(U1-GPIO3-Pad13)" class="Default">
|
||||
<node ref="U1" pin="13" pinfunction="GPIO3_13" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="24" name="unconnected-(U1-GPIO19-Pad42)" class="Default">
|
||||
<node ref="U1" pin="42" pinfunction="GPIO19_42" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="25" name="unconnected-(U1-GPIO20-Pad41)" class="Default">
|
||||
<node ref="U1" pin="41" pinfunction="GPIO20_41" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="26" name="unconnected-(U1-GPIO21-Pad40)" class="Default">
|
||||
<node ref="U1" pin="40" pinfunction="GPIO21_40" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="27" name="unconnected-(U1-GPIO35-Pad35)" class="Default">
|
||||
<node ref="U1" pin="35" pinfunction="GPIO35_35" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="28" name="unconnected-(U1-GPIO36-Pad34)" class="Default">
|
||||
<node ref="U1" pin="34" pinfunction="GPIO36_34" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="29" name="unconnected-(U1-GPIO37-Pad33)" class="Default">
|
||||
<node ref="U1" pin="33" pinfunction="GPIO37_33" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="30" name="unconnected-(U1-GPIO38-Pad32)" class="Default">
|
||||
<node ref="U1" pin="32" pinfunction="GPIO38_32" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="31" name="unconnected-(U1-GPIO39-Pad31)" class="Default">
|
||||
<node ref="U1" pin="31" pinfunction="GPIO39_31" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="32" name="unconnected-(U1-GPIO40-Pad30)" class="Default">
|
||||
<node ref="U1" pin="30" pinfunction="GPIO40_30" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="33" name="unconnected-(U1-GPIO41-Pad29)" class="Default">
|
||||
<node ref="U1" pin="29" pinfunction="GPIO41_29" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="34" name="unconnected-(U1-GPIO42-Pad28)" class="Default">
|
||||
<node ref="U1" pin="28" pinfunction="GPIO42_28" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="35" name="unconnected-(U1-GPIO43-Pad24)" class="Default">
|
||||
<node ref="U1" pin="24" pinfunction="GPIO43_24" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="36" name="unconnected-(U1-GPIO44-Pad25)" class="Default">
|
||||
<node ref="U1" pin="25" pinfunction="GPIO44_25" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="37" name="unconnected-(U1-GPIO45-Pad37)" class="Default">
|
||||
<node ref="U1" pin="37" pinfunction="GPIO45_37" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="38" name="unconnected-(U1-GPIO46-Pad14)" class="Default">
|
||||
<node ref="U1" pin="14" pinfunction="GPIO46_14" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="39" name="unconnected-(U1-GPIO47-Pad39)" class="Default">
|
||||
<node ref="U1" pin="39" pinfunction="GPIO47_39" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="40" name="unconnected-(U1-GPIO48-Pad38)" class="Default">
|
||||
<node ref="U1" pin="38" pinfunction="GPIO48_38" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
</nets>
|
||||
</export>
|
||||
@@ -0,0 +1,142 @@
|
||||
{
|
||||
"carrier_bounds_mm": [
|
||||
82,
|
||||
80
|
||||
],
|
||||
"outline_local_mm": [
|
||||
[
|
||||
36,
|
||||
0
|
||||
],
|
||||
[
|
||||
82,
|
||||
0
|
||||
],
|
||||
[
|
||||
82,
|
||||
80
|
||||
],
|
||||
[
|
||||
0,
|
||||
80
|
||||
],
|
||||
[
|
||||
0,
|
||||
19
|
||||
],
|
||||
[
|
||||
36,
|
||||
19
|
||||
]
|
||||
],
|
||||
"placements_local_mm": {
|
||||
"U1": [
|
||||
8,
|
||||
23
|
||||
],
|
||||
"U2": [
|
||||
43,
|
||||
26.67
|
||||
],
|
||||
"DS1": [
|
||||
43,
|
||||
36
|
||||
],
|
||||
"R1": [
|
||||
43,
|
||||
34
|
||||
],
|
||||
"R2": [
|
||||
54,
|
||||
34
|
||||
],
|
||||
"R3": [
|
||||
65,
|
||||
34
|
||||
],
|
||||
"SW1": [
|
||||
43,
|
||||
68
|
||||
],
|
||||
"SW2": [
|
||||
55,
|
||||
68
|
||||
],
|
||||
"SW3": [
|
||||
67,
|
||||
68
|
||||
],
|
||||
"H1": [
|
||||
3.5,
|
||||
22.5
|
||||
],
|
||||
"H2": [
|
||||
3.5,
|
||||
75
|
||||
],
|
||||
"H3": [
|
||||
78,
|
||||
4
|
||||
],
|
||||
"H4": [
|
||||
78,
|
||||
76
|
||||
]
|
||||
},
|
||||
"rf_reserve_absolute_mm": [
|
||||
110,
|
||||
119,
|
||||
131,
|
||||
132
|
||||
],
|
||||
"u2_bare_pcb_front_local_y_mm": 0.0,
|
||||
"nominal_user_confirmed_rs232_metal_mm": {
|
||||
"shell_overhang": 3.87,
|
||||
"hex_mouth_overhang": 2.87,
|
||||
"flange_front_inset": 1.93
|
||||
},
|
||||
"tracks": 565,
|
||||
"vias": 22,
|
||||
"copper_layers": 2,
|
||||
"draft_thickness_mm": 1.6,
|
||||
"electrical_footprints": 9,
|
||||
"proposed_M3_mounts": 4,
|
||||
"physical_DRC_violations": 0,
|
||||
"unconnected_items": 0,
|
||||
"schematic_pad_nets_match": true,
|
||||
"known_parity_warning": "U2 reference footprint assigned on PCB only; schematic footprint intentionally blank; nominal male external metal dimensions user-confirmed",
|
||||
"project_settings_sha256": "9ad5b992785ebfe01fa7ea4e1aa391a2c7d7f1f2c3b1c13136173c301ecb6516",
|
||||
"ignored_DRC_checks": [
|
||||
{
|
||||
"description": "Footprint has no courtyard defined",
|
||||
"key": "missing_courtyard"
|
||||
},
|
||||
{
|
||||
"description": "Track endpoint not centered on via",
|
||||
"key": "track_not_centered_on_via"
|
||||
},
|
||||
{
|
||||
"description": "Tuning profile track geometries",
|
||||
"key": "tuning_profile_track_geometries"
|
||||
},
|
||||
{
|
||||
"description": "Footprint doesn't match symbol's footprint filters",
|
||||
"key": "footprint_filters_mismatch"
|
||||
},
|
||||
{
|
||||
"description": "Footprint component type doesn't match footprint pads",
|
||||
"key": "footprint_type_mismatch"
|
||||
}
|
||||
],
|
||||
"not_validated": [
|
||||
"male module pin map/header fit",
|
||||
"ESP32 geometry and complete assembly envelope/heights",
|
||||
"header/socket fit",
|
||||
"RF performance and antenna clearance",
|
||||
"power/return-path integrity",
|
||||
"USB power path",
|
||||
"OLED pull-ups",
|
||||
"fabrication process",
|
||||
"hardware operation"
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
@@ -0,0 +1,604 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<export version="E">
|
||||
<design>
|
||||
<source>/home/mscholz/Repos/ESP32_serial_swiss_army_knife/hardware/PCB/serial-carrier.kicad_sch</source>
|
||||
<date>2026-09-20T20:14:45</date>
|
||||
<tool>Eeschema 10.0.6-1.fc44</tool>
|
||||
<sheet number="1" name="/" tstamps="/">
|
||||
<title_block>
|
||||
<title>ESP32 Serial Swiss Army Knife - Module Carrier</title>
|
||||
<company/>
|
||||
<rev>A0 DRAFT</rev>
|
||||
<date>2026-09-20</date>
|
||||
<source>serial-carrier.kicad_sch</source>
|
||||
<comment number="1" value="Not fabrication-ready: provisional module footprints and male RS-232 mapping"/>
|
||||
<comment number="2" value=""/>
|
||||
<comment number="3" value=""/>
|
||||
<comment number="4" value=""/>
|
||||
<comment number="5" value=""/>
|
||||
<comment number="6" value=""/>
|
||||
<comment number="7" value=""/>
|
||||
<comment number="8" value=""/>
|
||||
<comment number="9" value=""/>
|
||||
</title_block>
|
||||
</sheet>
|
||||
</design>
|
||||
<components>
|
||||
<comp ref="DS1">
|
||||
<value>128x64 I2C OLED / 0x3C</value>
|
||||
<footprint>Carrier:OLED_26mm_I2C_Provisional</footprint>
|
||||
<fields>
|
||||
<field name="Status">Module outline/header/display user fit-verified; pad/drill fit provisional</field>
|
||||
<field name="Footprint">Carrier:OLED_26mm_I2C_Provisional</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="OLED_I2C_26mm" description="User fit-verified 26 mm OLED; front/top header GND VCC SCL SDA"/>
|
||||
<property name="Status" value="Module outline/header/display user fit-verified; pad/drill fit provisional"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>0ae411c1-94cc-5551-9037-1d59c01a8ee5</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
<pin num="3"/>
|
||||
<pin num="4"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
<comp ref="R1">
|
||||
<value>2.2k 1%</value>
|
||||
<footprint>Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal</footprint>
|
||||
<fields>
|
||||
<field name="Rating">At least 0.125 W; exact resistor MPN pending</field>
|
||||
<field name="Footprint">Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="R" description="Resistor, axial DIN0207 footprint; exact MPN pending"/>
|
||||
<property name="Rating" value="At least 0.125 W; exact resistor MPN pending"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>defb78eb-632f-5f9d-a59d-dffbb1d31dec</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
<comp ref="R2">
|
||||
<value>2.2k 1%</value>
|
||||
<footprint>Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal</footprint>
|
||||
<fields>
|
||||
<field name="Rating">At least 0.125 W; exact resistor MPN pending</field>
|
||||
<field name="Footprint">Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="R" description="Resistor, axial DIN0207 footprint; exact MPN pending"/>
|
||||
<property name="Rating" value="At least 0.125 W; exact resistor MPN pending"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>405055c0-35ab-5334-ad4d-30d45c5ef6f2</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
<comp ref="R3">
|
||||
<value>2.2k 1%</value>
|
||||
<footprint>Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal</footprint>
|
||||
<fields>
|
||||
<field name="Rating">At least 0.125 W; exact resistor MPN pending</field>
|
||||
<field name="Footprint">Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="R" description="Resistor, axial DIN0207 footprint; exact MPN pending"/>
|
||||
<property name="Rating" value="At least 0.125 W; exact resistor MPN pending"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>78f9a74f-5d91-5687-b50e-68176333df23</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
<comp ref="SW1">
|
||||
<value>B3F-1000</value>
|
||||
<footprint>Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x</footprint>
|
||||
<fields>
|
||||
<field name="Manufacturer">Omron</field>
|
||||
<field name="MPN">B3F-1000</field>
|
||||
<field name="Function">Previous / back</field>
|
||||
<field name="Procurement">Live stock not verified</field>
|
||||
<field name="Footprint">Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="SW_NO" description="Normally open contact; footprint duplicates pads 1 and 2 for internally common terminal pairs"/>
|
||||
<property name="Manufacturer" value="Omron"/>
|
||||
<property name="MPN" value="B3F-1000"/>
|
||||
<property name="Function" value="Previous / back"/>
|
||||
<property name="Procurement" value="Live stock not verified"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>dd5f1f75-0093-55fc-9814-437ed26871eb</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
<comp ref="SW2">
|
||||
<value>B3F-1000</value>
|
||||
<footprint>Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x</footprint>
|
||||
<fields>
|
||||
<field name="Manufacturer">Omron</field>
|
||||
<field name="MPN">B3F-1000</field>
|
||||
<field name="Function">Select / confirm</field>
|
||||
<field name="Procurement">Live stock not verified</field>
|
||||
<field name="Footprint">Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="SW_NO" description="Normally open contact; footprint duplicates pads 1 and 2 for internally common terminal pairs"/>
|
||||
<property name="Manufacturer" value="Omron"/>
|
||||
<property name="MPN" value="B3F-1000"/>
|
||||
<property name="Function" value="Select / confirm"/>
|
||||
<property name="Procurement" value="Live stock not verified"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>c522a716-55a4-584d-a12e-ab135b4c08eb</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
<comp ref="SW3">
|
||||
<value>B3F-1000</value>
|
||||
<footprint>Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x</footprint>
|
||||
<fields>
|
||||
<field name="Manufacturer">Omron</field>
|
||||
<field name="MPN">B3F-1000</field>
|
||||
<field name="Function">Next</field>
|
||||
<field name="Procurement">Live stock not verified</field>
|
||||
<field name="Footprint">Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="SW_NO" description="Normally open contact; footprint duplicates pads 1 and 2 for internally common terminal pairs"/>
|
||||
<property name="Manufacturer" value="Omron"/>
|
||||
<property name="MPN" value="B3F-1000"/>
|
||||
<property name="Function" value="Next"/>
|
||||
<property name="Procurement" value="Live stock not verified"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>35caa6bb-42a6-512c-848c-f1e4457aa97f</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
<comp ref="U1">
|
||||
<value>HW678 / S3-N16R8</value>
|
||||
<footprint>Carrier:HW678_2x22_Provisional</footprint>
|
||||
<fields>
|
||||
<field name="Status">Header geometry nominal; outline/antenna clearance pending</field>
|
||||
<field name="Pin_numbering">Left 1-22; right 23-44; both top-to-bottom, antenna up</field>
|
||||
<field name="Footprint">Carrier:HW678_2x22_Provisional</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="HW678_N16R8" description="HW-678 V0.0.0 / N16R8 carrier interface; project numbering; onboard USB/regulator retained"/>
|
||||
<property name="Status" value="Header geometry nominal; outline/antenna clearance pending"/>
|
||||
<property name="Pin_numbering" value="Left 1-22; right 23-44; both top-to-bottom, antenna up"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>3ab265d0-7fd3-5891-94eb-d39426c6a8a3</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
<pin num="3"/>
|
||||
<pin num="4"/>
|
||||
<pin num="5"/>
|
||||
<pin num="6"/>
|
||||
<pin num="7"/>
|
||||
<pin num="8"/>
|
||||
<pin num="9"/>
|
||||
<pin num="10"/>
|
||||
<pin num="11"/>
|
||||
<pin num="12"/>
|
||||
<pin num="13"/>
|
||||
<pin num="14"/>
|
||||
<pin num="15"/>
|
||||
<pin num="16"/>
|
||||
<pin num="17"/>
|
||||
<pin num="18"/>
|
||||
<pin num="19"/>
|
||||
<pin num="20"/>
|
||||
<pin num="21"/>
|
||||
<pin num="22"/>
|
||||
<pin num="23"/>
|
||||
<pin num="24"/>
|
||||
<pin num="25"/>
|
||||
<pin num="26"/>
|
||||
<pin num="27"/>
|
||||
<pin num="28"/>
|
||||
<pin num="29"/>
|
||||
<pin num="30"/>
|
||||
<pin num="31"/>
|
||||
<pin num="32"/>
|
||||
<pin num="33"/>
|
||||
<pin num="34"/>
|
||||
<pin num="35"/>
|
||||
<pin num="36"/>
|
||||
<pin num="37"/>
|
||||
<pin num="38"/>
|
||||
<pin num="39"/>
|
||||
<pin num="40"/>
|
||||
<pin num="41"/>
|
||||
<pin num="42"/>
|
||||
<pin num="43"/>
|
||||
<pin num="44"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
<comp ref="U2">
|
||||
<value>Adafruit MAX3243 / male 6253</value>
|
||||
<fields>
|
||||
<field name="Status">VERIFY male header numbering; footprint intentionally unassigned</field>
|
||||
<field name="Datasheet_source">adafruit-research.md; reference female 5988 CAD</field>
|
||||
<field name="Footprint"/>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="MAX3243_Module_Reference" description="Adafruit male 6253 intended; physical numbering from published female 5988 CAD, VERIFY MALE"/>
|
||||
<property name="Status" value="VERIFY male header numbering; footprint intentionally unassigned"/>
|
||||
<property name="Datasheet_source" value="adafruit-research.md; reference female 5988 CAD"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>1127195e-e393-5d1d-9119-b25d10556144</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
<pin num="3"/>
|
||||
<pin num="4"/>
|
||||
<pin num="5"/>
|
||||
<pin num="6"/>
|
||||
<pin num="7"/>
|
||||
<pin num="8"/>
|
||||
<pin num="9"/>
|
||||
<pin num="10"/>
|
||||
<pin num="11"/>
|
||||
<pin num="12"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
</components>
|
||||
<libparts>
|
||||
<libpart lib="Carrier" part="HW678_N16R8">
|
||||
<description>HW-678 V0.0.0 / N16R8 carrier interface; project numbering; onboard USB/regulator retained</description>
|
||||
<fields>
|
||||
<field name="Reference">U</field>
|
||||
<field name="Value">HW678_N16R8</field>
|
||||
<field name="Footprint">Carrier:HW678_2x22_Provisional</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description">HW-678 V0.0.0 / N16R8 carrier interface; project numbering; onboard USB/regulator retained</field>
|
||||
</fields>
|
||||
<pins>
|
||||
<pin num="1" name="3V3" type="power_out"/>
|
||||
<pin num="2" name="3V3" type="passive"/>
|
||||
<pin num="3" name="EN" type="input"/>
|
||||
<pin num="4" name="GPIO4" type="input"/>
|
||||
<pin num="5" name="GPIO5" type="input"/>
|
||||
<pin num="6" name="GPIO6" type="input"/>
|
||||
<pin num="7" name="GPIO7" type="output"/>
|
||||
<pin num="8" name="GPIO15" type="output"/>
|
||||
<pin num="9" name="GPIO16" type="input"/>
|
||||
<pin num="10" name="GPIO17" type="output"/>
|
||||
<pin num="11" name="GPIO18" type="input"/>
|
||||
<pin num="12" name="GPIO8" type="input"/>
|
||||
<pin num="13" name="GPIO3" type="bidirectional"/>
|
||||
<pin num="14" name="GPIO46" type="bidirectional"/>
|
||||
<pin num="15" name="GPIO9" type="bidirectional"/>
|
||||
<pin num="16" name="GPIO10" type="input"/>
|
||||
<pin num="17" name="GPIO11" type="bidirectional"/>
|
||||
<pin num="18" name="GPIO12" type="output"/>
|
||||
<pin num="19" name="GPIO13" type="input"/>
|
||||
<pin num="20" name="GPIO14" type="input"/>
|
||||
<pin num="21" name="5V" type="passive"/>
|
||||
<pin num="22" name="GND" type="power_out"/>
|
||||
<pin num="23" name="GND" type="passive"/>
|
||||
<pin num="24" name="GPIO43" type="bidirectional"/>
|
||||
<pin num="25" name="GPIO44" type="bidirectional"/>
|
||||
<pin num="26" name="GPIO1" type="bidirectional"/>
|
||||
<pin num="27" name="GPIO2" type="bidirectional"/>
|
||||
<pin num="28" name="GPIO42" type="bidirectional"/>
|
||||
<pin num="29" name="GPIO41" type="bidirectional"/>
|
||||
<pin num="30" name="GPIO40" type="bidirectional"/>
|
||||
<pin num="31" name="GPIO39" type="bidirectional"/>
|
||||
<pin num="32" name="GPIO38" type="bidirectional"/>
|
||||
<pin num="33" name="GPIO37" type="bidirectional"/>
|
||||
<pin num="34" name="GPIO36" type="bidirectional"/>
|
||||
<pin num="35" name="GPIO35" type="bidirectional"/>
|
||||
<pin num="36" name="GPIO0" type="bidirectional"/>
|
||||
<pin num="37" name="GPIO45" type="bidirectional"/>
|
||||
<pin num="38" name="GPIO48" type="bidirectional"/>
|
||||
<pin num="39" name="GPIO47" type="bidirectional"/>
|
||||
<pin num="40" name="GPIO21" type="bidirectional"/>
|
||||
<pin num="41" name="GPIO20" type="bidirectional"/>
|
||||
<pin num="42" name="GPIO19" type="bidirectional"/>
|
||||
<pin num="43" name="GND" type="passive"/>
|
||||
<pin num="44" name="GND" type="passive"/>
|
||||
</pins>
|
||||
</libpart>
|
||||
<libpart lib="Carrier" part="MAX3243_Module_Reference">
|
||||
<description>Adafruit male 6253 intended; physical numbering from published female 5988 CAD, VERIFY MALE</description>
|
||||
<fields>
|
||||
<field name="Reference">U</field>
|
||||
<field name="Value">MAX3243_Module_Reference</field>
|
||||
<field name="Footprint"/>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description">Adafruit male 6253 intended; physical numbering from published female 5988 CAD, VERIFY MALE</field>
|
||||
</fields>
|
||||
<pins>
|
||||
<pin num="1" name="Vin" type="power_in"/>
|
||||
<pin num="2" name="GND" type="power_in"/>
|
||||
<pin num="3" name="DCD" type="output"/>
|
||||
<pin num="4" name="RX" type="output"/>
|
||||
<pin num="5" name="TX" type="input"/>
|
||||
<pin num="6" name="DTR" type="input"/>
|
||||
<pin num="7" name="DSR" type="output"/>
|
||||
<pin num="8" name="RTS" type="input"/>
|
||||
<pin num="9" name="CTS" type="output"/>
|
||||
<pin num="10" name="RI" type="output"/>
|
||||
<pin num="11" name="VLD" type="output"/>
|
||||
<pin num="12" name="OFF_N" type="input"/>
|
||||
</pins>
|
||||
</libpart>
|
||||
<libpart lib="Carrier" part="OLED_I2C_26mm">
|
||||
<description>User fit-verified 26 mm OLED; front/top header GND VCC SCL SDA</description>
|
||||
<fields>
|
||||
<field name="Reference">DS</field>
|
||||
<field name="Value">OLED_I2C_26mm</field>
|
||||
<field name="Footprint">Carrier:OLED_26mm_I2C_Provisional</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description">User fit-verified 26 mm OLED; front/top header GND VCC SCL SDA</field>
|
||||
</fields>
|
||||
<pins>
|
||||
<pin num="1" name="GND" type="power_in"/>
|
||||
<pin num="2" name="VCC" type="power_in"/>
|
||||
<pin num="3" name="SCL" type="input"/>
|
||||
<pin num="4" name="SDA" type="bidirectional"/>
|
||||
</pins>
|
||||
</libpart>
|
||||
<libpart lib="Carrier" part="R">
|
||||
<description>Resistor, axial DIN0207 footprint; exact MPN pending</description>
|
||||
<fields>
|
||||
<field name="Reference">R</field>
|
||||
<field name="Value">R</field>
|
||||
<field name="Footprint">Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description">Resistor, axial DIN0207 footprint; exact MPN pending</field>
|
||||
</fields>
|
||||
<pins>
|
||||
<pin num="1" name="" type="passive"/>
|
||||
<pin num="2" name="" type="passive"/>
|
||||
</pins>
|
||||
</libpart>
|
||||
<libpart lib="Carrier" part="SW_NO">
|
||||
<description>Normally open contact; footprint duplicates pads 1 and 2 for internally common terminal pairs</description>
|
||||
<fields>
|
||||
<field name="Reference">SW</field>
|
||||
<field name="Value">SW_NO</field>
|
||||
<field name="Footprint">Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description">Normally open contact; footprint duplicates pads 1 and 2 for internally common terminal pairs</field>
|
||||
</fields>
|
||||
<pins>
|
||||
<pin num="1" name="" type="passive"/>
|
||||
<pin num="2" name="" type="passive"/>
|
||||
</pins>
|
||||
</libpart>
|
||||
</libparts>
|
||||
<libraries>
|
||||
<library logical="Carrier">
|
||||
<uri>${KIPRJMOD}/Carrier.kicad_sym</uri>
|
||||
</library>
|
||||
</libraries>
|
||||
<nets>
|
||||
<net code="1" name="/+3V3" class="Default">
|
||||
<node ref="DS1" pin="2" pinfunction="VCC_2" pintype="power_in"/>
|
||||
<node ref="R1" pin="1" pintype="passive"/>
|
||||
<node ref="R2" pin="1" pintype="passive"/>
|
||||
<node ref="R3" pin="1" pintype="passive"/>
|
||||
<node ref="U1" pin="1" pinfunction="3V3_1" pintype="power_out"/>
|
||||
<node ref="U1" pin="2" pinfunction="3V3_2" pintype="passive"/>
|
||||
<node ref="U2" pin="1" pinfunction="Vin_1" pintype="power_in"/>
|
||||
</net>
|
||||
<net code="2" name="/BTN_NEXT" class="Default">
|
||||
<node ref="R3" pin="2" pintype="passive"/>
|
||||
<node ref="SW3" pin="1" pintype="passive"/>
|
||||
<node ref="U1" pin="20" pinfunction="GPIO14_20" pintype="input"/>
|
||||
</net>
|
||||
<net code="3" name="/BTN_PREVIOUS" class="Default">
|
||||
<node ref="R1" pin="2" pintype="passive"/>
|
||||
<node ref="SW1" pin="1" pintype="passive"/>
|
||||
<node ref="U1" pin="16" pinfunction="GPIO10_16" pintype="input"/>
|
||||
</net>
|
||||
<net code="4" name="/BTN_SELECT" class="Default">
|
||||
<node ref="R2" pin="2" pintype="passive"/>
|
||||
<node ref="SW2" pin="1" pintype="passive"/>
|
||||
<node ref="U1" pin="19" pinfunction="GPIO13_19" pintype="input"/>
|
||||
</net>
|
||||
<net code="5" name="/GND" class="Default">
|
||||
<node ref="DS1" pin="1" pinfunction="GND_1" pintype="power_in"/>
|
||||
<node ref="SW1" pin="2" pintype="passive"/>
|
||||
<node ref="SW2" pin="2" pintype="passive"/>
|
||||
<node ref="SW3" pin="2" pintype="passive"/>
|
||||
<node ref="U1" pin="22" pinfunction="GND_22" pintype="power_out"/>
|
||||
<node ref="U1" pin="23" pinfunction="GND_23" pintype="passive"/>
|
||||
<node ref="U1" pin="43" pinfunction="GND_43" pintype="passive"/>
|
||||
<node ref="U1" pin="44" pinfunction="GND_44" pintype="passive"/>
|
||||
<node ref="U2" pin="2" pinfunction="GND_2" pintype="power_in"/>
|
||||
</net>
|
||||
<net code="6" name="/OLED_SCL" class="Default">
|
||||
<node ref="DS1" pin="3" pinfunction="SCL_3" pintype="input"/>
|
||||
<node ref="U1" pin="18" pinfunction="GPIO12_18" pintype="output"/>
|
||||
</net>
|
||||
<net code="7" name="/OLED_SDA" class="Default">
|
||||
<node ref="DS1" pin="4" pinfunction="SDA_4" pintype="bidirectional"/>
|
||||
<node ref="U1" pin="17" pinfunction="GPIO11_17" pintype="bidirectional"/>
|
||||
</net>
|
||||
<net code="8" name="/RS_CTS" class="Default">
|
||||
<node ref="U1" pin="9" pinfunction="GPIO16_9" pintype="input"/>
|
||||
<node ref="U2" pin="9" pinfunction="CTS_9" pintype="output"/>
|
||||
</net>
|
||||
<net code="9" name="/RS_DCD" class="Default">
|
||||
<node ref="U1" pin="4" pinfunction="GPIO4_4" pintype="input"/>
|
||||
<node ref="U2" pin="3" pinfunction="DCD_3" pintype="output"/>
|
||||
</net>
|
||||
<net code="10" name="/RS_DSR" class="Default">
|
||||
<node ref="U1" pin="5" pinfunction="GPIO5_5" pintype="input"/>
|
||||
<node ref="U2" pin="7" pinfunction="DSR_7" pintype="output"/>
|
||||
</net>
|
||||
<net code="11" name="/RS_DTR" class="Default">
|
||||
<node ref="U1" pin="7" pinfunction="GPIO7_7" pintype="output"/>
|
||||
<node ref="U2" pin="6" pinfunction="DTR_6" pintype="input"/>
|
||||
</net>
|
||||
<net code="12" name="/RS_OFF_N" class="Default">
|
||||
<node ref="U1" pin="15" pinfunction="GPIO9_15" pintype="bidirectional"/>
|
||||
<node ref="U2" pin="12" pinfunction="OFF_N_12" pintype="input"/>
|
||||
</net>
|
||||
<net code="13" name="/RS_RI" class="Default">
|
||||
<node ref="U1" pin="6" pinfunction="GPIO6_6" pintype="input"/>
|
||||
<node ref="U2" pin="10" pinfunction="RI_10" pintype="output"/>
|
||||
</net>
|
||||
<net code="14" name="/RS_RTS" class="Default">
|
||||
<node ref="U1" pin="8" pinfunction="GPIO15_8" pintype="output"/>
|
||||
<node ref="U2" pin="8" pinfunction="RTS_8" pintype="input"/>
|
||||
</net>
|
||||
<net code="15" name="/RS_RX" class="Default">
|
||||
<node ref="U1" pin="11" pinfunction="GPIO18_11" pintype="input"/>
|
||||
<node ref="U2" pin="4" pinfunction="RX_4" pintype="output"/>
|
||||
</net>
|
||||
<net code="16" name="/RS_TX" class="Default">
|
||||
<node ref="U1" pin="10" pinfunction="GPIO17_10" pintype="output"/>
|
||||
<node ref="U2" pin="5" pinfunction="TX_5" pintype="input"/>
|
||||
</net>
|
||||
<net code="17" name="/RS_VALID" class="Default">
|
||||
<node ref="U1" pin="12" pinfunction="GPIO8_12" pintype="input"/>
|
||||
<node ref="U2" pin="11" pinfunction="VLD_11" pintype="output"/>
|
||||
</net>
|
||||
<net code="18" name="unconnected-(U1-5V-Pad21)" class="Default">
|
||||
<node ref="U1" pin="21" pinfunction="5V_21" pintype="passive+no_connect"/>
|
||||
</net>
|
||||
<net code="19" name="unconnected-(U1-EN-Pad3)" class="Default">
|
||||
<node ref="U1" pin="3" pinfunction="EN_3" pintype="input+no_connect"/>
|
||||
</net>
|
||||
<net code="20" name="unconnected-(U1-GPIO0-Pad36)" class="Default">
|
||||
<node ref="U1" pin="36" pinfunction="GPIO0_36" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="21" name="unconnected-(U1-GPIO1-Pad26)" class="Default">
|
||||
<node ref="U1" pin="26" pinfunction="GPIO1_26" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="22" name="unconnected-(U1-GPIO2-Pad27)" class="Default">
|
||||
<node ref="U1" pin="27" pinfunction="GPIO2_27" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="23" name="unconnected-(U1-GPIO3-Pad13)" class="Default">
|
||||
<node ref="U1" pin="13" pinfunction="GPIO3_13" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="24" name="unconnected-(U1-GPIO19-Pad42)" class="Default">
|
||||
<node ref="U1" pin="42" pinfunction="GPIO19_42" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="25" name="unconnected-(U1-GPIO20-Pad41)" class="Default">
|
||||
<node ref="U1" pin="41" pinfunction="GPIO20_41" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="26" name="unconnected-(U1-GPIO21-Pad40)" class="Default">
|
||||
<node ref="U1" pin="40" pinfunction="GPIO21_40" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="27" name="unconnected-(U1-GPIO35-Pad35)" class="Default">
|
||||
<node ref="U1" pin="35" pinfunction="GPIO35_35" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="28" name="unconnected-(U1-GPIO36-Pad34)" class="Default">
|
||||
<node ref="U1" pin="34" pinfunction="GPIO36_34" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="29" name="unconnected-(U1-GPIO37-Pad33)" class="Default">
|
||||
<node ref="U1" pin="33" pinfunction="GPIO37_33" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="30" name="unconnected-(U1-GPIO38-Pad32)" class="Default">
|
||||
<node ref="U1" pin="32" pinfunction="GPIO38_32" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="31" name="unconnected-(U1-GPIO39-Pad31)" class="Default">
|
||||
<node ref="U1" pin="31" pinfunction="GPIO39_31" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="32" name="unconnected-(U1-GPIO40-Pad30)" class="Default">
|
||||
<node ref="U1" pin="30" pinfunction="GPIO40_30" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="33" name="unconnected-(U1-GPIO41-Pad29)" class="Default">
|
||||
<node ref="U1" pin="29" pinfunction="GPIO41_29" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="34" name="unconnected-(U1-GPIO42-Pad28)" class="Default">
|
||||
<node ref="U1" pin="28" pinfunction="GPIO42_28" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="35" name="unconnected-(U1-GPIO43-Pad24)" class="Default">
|
||||
<node ref="U1" pin="24" pinfunction="GPIO43_24" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="36" name="unconnected-(U1-GPIO44-Pad25)" class="Default">
|
||||
<node ref="U1" pin="25" pinfunction="GPIO44_25" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="37" name="unconnected-(U1-GPIO45-Pad37)" class="Default">
|
||||
<node ref="U1" pin="37" pinfunction="GPIO45_37" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="38" name="unconnected-(U1-GPIO46-Pad14)" class="Default">
|
||||
<node ref="U1" pin="14" pinfunction="GPIO46_14" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="39" name="unconnected-(U1-GPIO47-Pad39)" class="Default">
|
||||
<node ref="U1" pin="39" pinfunction="GPIO47_39" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="40" name="unconnected-(U1-GPIO48-Pad38)" class="Default">
|
||||
<node ref="U1" pin="38" pinfunction="GPIO48_38" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
</nets>
|
||||
</export>
|
||||
@@ -0,0 +1,8 @@
|
||||
Native schematic validation passed.
|
||||
KiCad ERC: zero reported errors/warnings; no project exclusions added.
|
||||
17 connected nets, 9 components; exact net memberships verified.
|
||||
15 firmware GPIO definitions independently checked against exported pin functions.
|
||||
All 44 MCU header pins accounted for; USB/UART0/5V/PSRAM pins unused on carrier.
|
||||
PDF and SVG exported successfully.
|
||||
Not validated: male-module physical mapping, power budget/path, OLED pull-ups,
|
||||
selected connector fit, PCB DRC/layout, procurement, or assembled hardware.
|
||||
@@ -16,7 +16,7 @@ CONFIG_ESP_WIFI_RX_BA_WIN=6
|
||||
CONFIG_LWIP_TCP_OOSEQ_MAX_PBUFS=4
|
||||
|
||||
# Keep concurrent HTTPS handshakes from exhausting scarce internal DRAM.
|
||||
# Active TLS material remains unencrypted in PSRAM until the hardening phase.
|
||||
# Active TLS material remains unencrypted in PSRAM; physical extraction is outside scope.
|
||||
CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y
|
||||
# CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC is not set
|
||||
# Hardware AES can hang in the PSRAM DMA path and cannot be shared safely with
|
||||
@@ -39,6 +39,9 @@ CONFIG_ESP_HTTPS_SERVER_ENABLE=y
|
||||
CONFIG_HTTPD_WS_SUPPORT=y
|
||||
# Reserve capacity for HTTPS/WebSocket clients plus two bounded SSH sessions.
|
||||
CONFIG_LWIP_MAX_SOCKETS=16
|
||||
# One dual-stack listener per service; SLAAC is enabled on STA by its owner.
|
||||
CONFIG_LWIP_IPV4=y
|
||||
CONFIG_LWIP_IPV6=y
|
||||
# Keep work submission bounded; one-second socket timeouts limit shared-task stalls.
|
||||
# CONFIG_HTTPD_QUEUE_WORK_BLOCKING is not set
|
||||
|
||||
@@ -54,7 +57,7 @@ CONFIG_ESP_WOLFSSL_NO_STACK_SIZE_BUILD_WARNING=y
|
||||
# Keep configurable hostname discovery STA-only and bounded. The responder task
|
||||
# remains internal; general mDNS metadata prefers PSRAM to protect internal heap.
|
||||
CONFIG_MDNS_MAX_INTERFACES=1
|
||||
CONFIG_MDNS_MAX_SERVICES=1
|
||||
CONFIG_MDNS_MAX_SERVICES=2
|
||||
CONFIG_MDNS_PREDEF_NETIF_STA=y
|
||||
# CONFIG_MDNS_PREDEF_NETIF_AP is not set
|
||||
# CONFIG_MDNS_PREDEF_NETIF_ETH is not set
|
||||
@@ -72,3 +75,8 @@ CONFIG_ESP_WIFI_SOFTAP_SAE_SUPPORT=y
|
||||
|
||||
# Keep diagnostic and interactive-console logging concise but useful.
|
||||
CONFIG_LOG_DEFAULT_LEVEL_INFO=y
|
||||
|
||||
# Intermittent-use baseline: avoid secret-bearing crash dumps/register output.
|
||||
# Ordinary logging remains enabled; this is not physical-memory protection.
|
||||
CONFIG_ESP_COREDUMP_ENABLE_TO_NONE=y
|
||||
CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT=y
|
||||
|
||||
@@ -38,6 +38,7 @@ idf_component_register(
|
||||
"web_broker_settings.c"
|
||||
"web_ssh_settings.c"
|
||||
"web_lifecycle_settings.c"
|
||||
"web_firmware_update.c"
|
||||
"web_admin_tickets.c"
|
||||
"web_admin_transport.c"
|
||||
"web_assets_data.c"
|
||||
@@ -59,6 +60,7 @@ idf_component_register(
|
||||
"mdns_console.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES
|
||||
app_update
|
||||
bootloader_support
|
||||
console
|
||||
esp_driver_gpio
|
||||
|
||||
@@ -556,9 +556,9 @@ static bool remote_command_allowed(const admin_request_t *request)
|
||||
strcmp(argv[1], "recover") == 0) {
|
||||
allowed = false;
|
||||
}
|
||||
/* Temporary browser policy until lifecycle acknowledgements/revocation are
|
||||
* coordinated (8D.7). Classify parsed canonical arguments, not raw prefixes.
|
||||
* User mutations remain available through UART0/SSH, subject to their policy.
|
||||
/* Classify parsed canonical arguments, not raw prefixes. Wi-Fi uses the
|
||||
* canonical handler, including hidden prompts and disruptive changes;
|
||||
* unrelated browser restrictions remain narrower than UART0/SSH.
|
||||
*/
|
||||
if (request->token.transport == ADMIN_CONSOLE_TRANSPORT_WEB && argc > 0U) {
|
||||
if (strcmp(argv[0], "web") == 0) {
|
||||
@@ -566,7 +566,7 @@ static bool remote_command_allowed(const admin_request_t *request)
|
||||
strcmp(argv[1], "stop") == 0)) ||
|
||||
(argc == 4U && strcmp(argv[1], "certificate") == 0 &&
|
||||
strcmp(argv[2], "rotate") == 0 && strcmp(argv[3], "--force") == 0);
|
||||
} else if (strcmp(argv[0], "wifi") == 0 || strcmp(argv[0], "mdns") == 0) {
|
||||
} else if (strcmp(argv[0], "mdns") == 0) {
|
||||
allowed = argc == 2U && strcmp(argv[1], "status") == 0;
|
||||
} else if (strcmp(argv[0], "user") == 0) {
|
||||
allowed = admin_ssh_console_web_user_command_allowed(
|
||||
|
||||
@@ -164,8 +164,22 @@ static const char *const s_completion_candidates[] = {
|
||||
"wifi ap secret",
|
||||
"wifi ap show-secret",
|
||||
"wifi ping",
|
||||
"wifi ping -4",
|
||||
"wifi ping -6",
|
||||
"wifi nslookup",
|
||||
"wifi nslookup -4",
|
||||
"wifi nslookup -6",
|
||||
"wifi traceroute",
|
||||
"wifi traceroute -4",
|
||||
"wifi traceroute -6",
|
||||
|
||||
/* Shared diagnostics: fixed family flags only, never guessed hosts. */
|
||||
"ping -4",
|
||||
"ping -6",
|
||||
"nslookup -4",
|
||||
"nslookup -6",
|
||||
"traceroute -4",
|
||||
"traceroute -6",
|
||||
|
||||
/* Station mDNS hostname configuration. */
|
||||
"mdns status",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "esp_log.h"
|
||||
#include "esp_netif_ip_addr.h"
|
||||
#include "esp_system.h"
|
||||
#include "web_firmware_update.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/portmacro.h"
|
||||
#include "freertos/semphr.h"
|
||||
@@ -270,14 +271,14 @@ static char parity_letter(serial_config_parity_t parity)
|
||||
static void format_ipv4(uint32_t address, char output[16])
|
||||
{
|
||||
if (address == 0U) {
|
||||
memcpy(output, "0.0.0.0", sizeof("0.0.0.0"));
|
||||
memcpy(output, "none", sizeof("none"));
|
||||
return;
|
||||
}
|
||||
|
||||
esp_ip4_addr_t ip = {.addr = address};
|
||||
int written = snprintf(output, 16U, IPSTR, IP2STR(&ip));
|
||||
if (written < 0 || written >= 16) {
|
||||
memcpy(output, "0.0.0.0", sizeof("0.0.0.0"));
|
||||
memcpy(output, "unknown", sizeof("unknown"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -564,8 +565,8 @@ static void render_overview_page(const local_status_snapshot_t *snapshot)
|
||||
local_display_frame_draw_text(LOCAL_DISPLAY_PANEL_CONTENT, 10U, 24U, line);
|
||||
|
||||
format_ipv4(snapshot->wifi.ip, address);
|
||||
format_text(line, "IP:%s", address);
|
||||
local_display_frame_draw_text(LOCAL_DISPLAY_PANEL_CONTENT, 10U, 32U, line);
|
||||
format_text(line, "IPv4:%s", snapshot->wifi_available ? address : "unknown");
|
||||
local_display_frame_draw_text(LOCAL_DISPLAY_PANEL_CONTENT, 0U, 32U, line);
|
||||
|
||||
format_text(line, "Web:%u SSH:%u USB:%u",
|
||||
snapshot->web_serial_available ? snapshot->web_serial.active_sessions : 0U,
|
||||
@@ -663,7 +664,11 @@ static void render_network_page(const local_status_snapshot_t *snapshot)
|
||||
const char *ssid;
|
||||
uint8_t channel;
|
||||
|
||||
local_display_frame_draw_text(LOCAL_DISPLAY_PANEL_CONTENT, 0U, 0U, "Network / services");
|
||||
/* Address availability, not route/reachability; fit both flags in 21 columns. */
|
||||
format_text(line, "IPv6 LL:%c ULA/GUA:%c",
|
||||
!snapshot->wifi_available ? '?' : snapshot->wifi.ipv6_linklocal ? 'Y' : 'N',
|
||||
!snapshot->wifi_available ? '?' : snapshot->wifi.ipv6_routable ? 'Y' : 'N');
|
||||
local_display_frame_draw_text(LOCAL_DISPLAY_PANEL_CONTENT, 0U, 0U, line);
|
||||
wifi_identity(snapshot, &ssid, &channel);
|
||||
format_ssid(ssid, ssid_display);
|
||||
format_text(line, "WiFi:%s Ch:%u", ssid_display, channel);
|
||||
@@ -671,8 +676,8 @@ static void render_network_page(const local_status_snapshot_t *snapshot)
|
||||
wifi_snapshot_bitmap(snapshot));
|
||||
local_display_frame_draw_text(LOCAL_DISPLAY_PANEL_CONTENT, 10U, 8U, line);
|
||||
format_ipv4(snapshot->wifi.ip, address);
|
||||
format_text(line, "IP:%s", address);
|
||||
local_display_frame_draw_text(LOCAL_DISPLAY_PANEL_CONTENT, 10U, 16U, line);
|
||||
format_text(line, "IPv4:%s", snapshot->wifi_available ? address : "unknown");
|
||||
local_display_frame_draw_text(LOCAL_DISPLAY_PANEL_CONTENT, 0U, 16U, line);
|
||||
format_text(line, "AP:%s Clients:%u", on_off(snapshot->wifi.ap_running),
|
||||
snapshot->wifi.ap_client_count);
|
||||
local_display_frame_draw_text(LOCAL_DISPLAY_PANEL_CONTENT, 10U, 24U, line);
|
||||
@@ -1101,7 +1106,8 @@ static void execute_action(local_status_ui_state_t *state,
|
||||
}
|
||||
break;
|
||||
case LOCAL_STATUS_ACTION_REBOOT:
|
||||
state->restart_pending = true;
|
||||
error = web_firmware_update_reserve_reboot();
|
||||
state->restart_pending = error == ESP_OK;
|
||||
break;
|
||||
default:
|
||||
error = ESP_ERR_INVALID_ARG;
|
||||
|
||||