@hybridlabor-api/bdb-hardware-pcb 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +116 -0
- package/config/mcp/antigravity.json +22 -0
- package/config/mcp/claude.json +22 -0
- package/config/mcp/codex.toml +25 -0
- package/docs/adr/ADR-001-KICAD-OPENSCAD-MCP-STRATEGY.md +334 -0
- package/docs/review_documentation.md +121 -0
- package/installer.js +358 -0
- package/mcp_servers/kicad-mcp-server/.env.example +22 -0
- package/mcp_servers/kicad-mcp-server/.github/workflows/ci.yml +36 -0
- package/mcp_servers/kicad-mcp-server/CLAUDE.md +487 -0
- package/mcp_servers/kicad-mcp-server/README.md +316 -0
- package/mcp_servers/kicad-mcp-server/docs/DEVICE_TREE.md +416 -0
- package/mcp_servers/kicad-mcp-server/docs/INSTALLATION.md +332 -0
- package/mcp_servers/kicad-mcp-server/docs/PIN_ANALYSIS.md +332 -0
- package/mcp_servers/kicad-mcp-server/docs/README.md +240 -0
- package/mcp_servers/kicad-mcp-server/docs/TESTING.md +613 -0
- package/mcp_servers/kicad-mcp-server/docs/VALIDATION.md +268 -0
- package/mcp_servers/kicad-mcp-server/pyproject.toml +96 -0
- package/mcp_servers/kicad-mcp-server/requirements-dev.txt +16 -0
- package/mcp_servers/kicad-mcp-server/requirements-test.txt +24 -0
- package/mcp_servers/kicad-mcp-server/requirements.txt +13 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/__init__.py +3 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/__main__.py +17 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/config.py +46 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/models/__init__.py +1 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/models/types.py +87 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/__init__.py +1 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/netlist_parser.py +234 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/pcb_parser.py +375 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/pcb_parser_kicad.py +327 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/schematic_parser.py +902 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/server.py +71 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/__init__.py +1 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/arduino/connectivity_test.cpp.j2 +189 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/atmega.dts.j2 +77 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/esp32.dts.j2 +77 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/nrf52.dts.j2 +77 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/stm32f4.dts.j2 +89 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/esp_idf/test_suite.c.j2 +340 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/pytest/test_connectivity.py.j2 +147 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/st_hal/hal_test.c.j2 +313 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_gpio_test.py.j2 +99 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_i2c_test.py.j2 +117 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_pinmux_test.py.j2 +43 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_spi_test.py.j2 +94 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/unity_gpio_test.c.j2 +113 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/unity_i2c_test.c.j2 +101 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/unity_spi_test.c.j2 +94 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/unittest/test_schematic.py.j2 +172 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/__init__.py +36 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/device_tree.py +1187 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/hierarchical_analysis.py +211 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/netlist.py +320 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/parts_registry.py +142 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/pcb.py +955 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/pcb_layout.py +308 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/pin_analysis.py +765 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/project.py +196 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/schematic.py +319 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/schematic_editor.py +674 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/schematic_search.py +158 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/validation.py +866 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/visualization.py +225 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/__init__.py +1 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/file_handlers.py +65 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/kicad_cli.py +103 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/kicad_version.py +103 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/parts_registry.py +197 -0
- package/mcp_servers/kicad-mcp-server/tests/__init__.py +1 -0
- package/mcp_servers/kicad-mcp-server/tests/examples/ESP32S3_TEST.md +219 -0
- package/mcp_servers/kicad-mcp-server/tests/fixtures/README.md +65 -0
- package/mcp_servers/kicad-mcp-server/tests/fixtures/__init__.py +1 -0
- package/mcp_servers/kicad-mcp-server/tests/fixtures/example_pcb.kicad_pcb +177 -0
- package/mcp_servers/kicad-mcp-server/tests/fixtures/example_schematic.kicad_sch +145 -0
- package/mcp_servers/kicad-mcp-server/tests/fixtures/hier/child.kicad_sch +24 -0
- package/mcp_servers/kicad-mcp-server/tests/fixtures/hier/root.kicad_sch +38 -0
- package/mcp_servers/kicad-mcp-server/tests/test_tools/__init__.py +1 -0
- package/mcp_servers/kicad-mcp-server/tests/test_tools/test_hierarchical_labels.py +195 -0
- package/mcp_servers/kicad-mcp-server/tests/test_tools/test_kicad_cli.py +116 -0
- package/mcp_servers/kicad-mcp-server/tests/test_tools/test_netlist_cache_path.py +29 -0
- package/mcp_servers/kicad-mcp-server/tests/test_tools/test_schematic.py +189 -0
- package/mcp_servers/kicad-mcp-server/tests/test_tools/test_schematic_hierarchy.py +69 -0
- package/mcp_servers/kicad-mcp-server/tests/test_tools/test_visualization.py +136 -0
- package/mcp_servers/kicad-mcp-server/uv.lock +2873 -0
- package/mcp_servers/openscad-mcp-server/.dockerignore +9 -0
- package/mcp_servers/openscad-mcp-server/.github/workflows/test.yml +40 -0
- package/mcp_servers/openscad-mcp-server/Dockerfile +29 -0
- package/mcp_servers/openscad-mcp-server/LICENSE +21 -0
- package/mcp_servers/openscad-mcp-server/README.md +154 -0
- package/mcp_servers/openscad-mcp-server/docs/audit.md +56 -0
- package/mcp_servers/openscad-mcp-server/docs/docker.md +66 -0
- package/mcp_servers/openscad-mcp-server/docs/issue-followup.md +19 -0
- package/mcp_servers/openscad-mcp-server/docs/jetson.md +17 -0
- package/mcp_servers/openscad-mcp-server/glama.json +4 -0
- package/mcp_servers/openscad-mcp-server/legacy/README.md +15 -0
- package/mcp_servers/openscad-mcp-server/legacy/README.original.md +294 -0
- package/mcp_servers/openscad-mcp-server/legacy/implementation_plan.md +100 -0
- package/mcp_servers/openscad-mcp-server/legacy/old/download_sam2_checkpoint.py +115 -0
- package/mcp_servers/openscad-mcp-server/legacy/old/src/ai/sam_segmentation.py +209 -0
- package/mcp_servers/openscad-mcp-server/legacy/old/src/models/threestudio_generator.py +231 -0
- package/mcp_servers/openscad-mcp-server/legacy/old/src/workflow/image_to_model_pipeline.py +260 -0
- package/mcp_servers/openscad-mcp-server/legacy/old/test_sam2_segmentation.py +96 -0
- package/mcp_servers/openscad-mcp-server/legacy/requirements.txt +57 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/README.md +39 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/decisions/ai-driven-code-generation.md +122 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/decisions/export-formats.md +76 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/ai/ai_service.py.md +51 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/main.py.md +63 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/models/code_generator.py.md +63 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/nlp/parameter_extractor.py.md +63 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/ai/natural-language-processing.md +78 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/nlp/parameter-extraction.md +173 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/openscad/export-formats.md +91 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/openscad/openscad-basics.md +66 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/openscad/primitive-testing.md +79 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/__init__.py +0 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/ai/ai_service.py +257 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/ai/gemini_api.py +161 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/ai/venice_api.py +203 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/config.py +121 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/main.py +1456 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/main.py.new +404 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/main_remote.py +401 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/models/__init__.py +0 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/models/code_generator.py +321 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/models/cuda_mvs.py +209 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/models/scad_templates/basic_shapes.scad +144 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/nlp/__init__.py +0 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/nlp/parameter_extractor.py +388 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/openscad_wrapper/__init__.py +0 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/openscad_wrapper/wrapper.py +418 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/printer_discovery/__init__.py +1 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/printer_discovery/printer_discovery.py +471 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/remote/connection_manager.py +537 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/remote/cuda_mvs_client.py +435 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/remote/cuda_mvs_server.py +787 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/remote/error_handling.py +415 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/testing/__init__.py +0 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/testing/primitive_tester.py +203 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/testing/test_primitives.py +98 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/utils/__init__.py +1 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/utils/cad_exporter.py +241 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/utils/format_validator.py +206 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/utils/stl_exporter.py +140 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/utils/stl_repair.py +91 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/utils/stl_validator.py +123 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/visualization/__init__.py +0 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/visualization/headless_renderer.py +52 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/visualization/renderer.py +177 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/visualization/web_interface.py +639 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/workflow/image_approval.py +148 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/workflow/multi_view_to_model_pipeline.py +338 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_complete_workflow.py +374 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_cuda_mvs.py +191 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_gemini_api.py +168 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_image_approval.py +192 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_image_approval_workflow.py +251 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_image_to_model_pipeline.py +145 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_model_selection.py +41 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_multi_view_pipeline.py +290 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_primitives.sh +13 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_rabbit_direct.py +71 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_remote_cuda_mvs.py +283 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_venice_example.py +69 -0
- package/mcp_servers/openscad-mcp-server/pyproject.toml +33 -0
- package/mcp_servers/openscad-mcp-server/requirements.txt +2 -0
- package/mcp_servers/openscad-mcp-server/scad/simple_cube.scad +2 -0
- package/mcp_servers/openscad-mcp-server/scripts/test_docker.py +220 -0
- package/mcp_servers/openscad-mcp-server/src/openscad_mcp/__init__.py +3 -0
- package/mcp_servers/openscad-mcp-server/src/openscad_mcp/__main__.py +3 -0
- package/mcp_servers/openscad-mcp-server/src/openscad_mcp/engine.py +94 -0
- package/mcp_servers/openscad-mcp-server/src/openscad_mcp/geometry.py +242 -0
- package/mcp_servers/openscad-mcp-server/src/openscad_mcp/server.py +245 -0
- package/mcp_servers/openscad-mcp-server/src/openscad_mcp/service.py +190 -0
- package/mcp_servers/openscad-mcp-server/tests/conftest.py +17 -0
- package/mcp_servers/openscad-mcp-server/tests/test_engine.py +37 -0
- package/mcp_servers/openscad-mcp-server/tests/test_geometry.py +106 -0
- package/mcp_servers/openscad-mcp-server/tests/test_integration.py +172 -0
- package/mcp_servers/openscad-mcp-server/tests/test_transports.py +314 -0
- package/mcp_servers/openscad-mcp-server/uv.lock +1123 -0
- package/package.json +44 -0
- package/scripts/install_mcps.sh +86 -0
- package/scripts/openscad_wrapper.sh +62 -0
- package/scripts/run_kicad_mcp.sh +25 -0
- package/scripts/run_openscad_mcp.sh +41 -0
- package/scripts/test_mcp_connection.py +626 -0
- package/scripts/test_mcp_connection.sh +168 -0
- package/skills/code-first-hardware-design/SKILL.md +260 -0
- package/skills/pcb-constraint-definition/SKILL.md +236 -0
- package/skills/pcb-layout-routing-automation/SKILL.md +153 -0
- package/skills/pcb-validation-dfm-signoff/SKILL.md +194 -0
- package/skills/schematic-datasheet-analysis/SKILL.md +202 -0
|
@@ -0,0 +1,765 @@
|
|
|
1
|
+
"""Pin analysis and connectivity tools for KiCad MCP Server.
|
|
2
|
+
|
|
3
|
+
This module provides advanced pin analysis capabilities including:
|
|
4
|
+
- Pin function inference (GPIO, I2C, SPI, UART, etc.)
|
|
5
|
+
- Pin conflict detection
|
|
6
|
+
- Pin multiplexing configuration extraction
|
|
7
|
+
- MCU-specific pin mapping support
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
import tempfile
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from ..parsers.netlist_parser import NetlistParser
|
|
15
|
+
from ..parsers.schematic_parser import SchematicParser
|
|
16
|
+
from ..server import mcp
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
async def _ensure_netlist(sch_path: Path) -> Path | None:
|
|
20
|
+
"""Find or generate a netlist for the given schematic.
|
|
21
|
+
|
|
22
|
+
Checks the schematic directory and the temp directory (legacy flat names
|
|
23
|
+
plus the per-schematic hashed cache name). If none exists, generates one
|
|
24
|
+
via kicad-cli (auto-redirects to root schematic for sub-sheets).
|
|
25
|
+
|
|
26
|
+
Returns the netlist Path, or None on failure.
|
|
27
|
+
"""
|
|
28
|
+
from .netlist import netlist_cache_path
|
|
29
|
+
|
|
30
|
+
# Candidate schematics: the given sheet and, for sub-sheets, the root
|
|
31
|
+
# schematic that generate_netlist redirects to.
|
|
32
|
+
pro_files = list(sch_path.parent.glob("*.kicad_pro"))
|
|
33
|
+
candidates: list[Path] = [sch_path]
|
|
34
|
+
if len(pro_files) == 1:
|
|
35
|
+
root_sch = pro_files[0].with_suffix(".kicad_sch")
|
|
36
|
+
if root_sch.exists() and root_sch.resolve() != sch_path.resolve():
|
|
37
|
+
candidates.append(root_sch)
|
|
38
|
+
|
|
39
|
+
def _probe() -> Path | None:
|
|
40
|
+
for cand in candidates:
|
|
41
|
+
for path in [
|
|
42
|
+
cand.parent / (cand.stem + ".xml"), # GUI-exported next to sheet
|
|
43
|
+
Path(tempfile.gettempdir()) / (cand.stem + ".xml"), # legacy flat name
|
|
44
|
+
netlist_cache_path(cand), # current hashed name
|
|
45
|
+
]:
|
|
46
|
+
if path.exists():
|
|
47
|
+
return path
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
found = _probe()
|
|
51
|
+
if found:
|
|
52
|
+
return found
|
|
53
|
+
|
|
54
|
+
from .netlist import generate_netlist
|
|
55
|
+
|
|
56
|
+
result = await generate_netlist(str(sch_path))
|
|
57
|
+
if "❌" in result:
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
return _probe()
|
|
61
|
+
|
|
62
|
+
# MCU family component patterns
|
|
63
|
+
MCU_PATTERNS = {
|
|
64
|
+
"stm32": r"STM32[FHL]\d[A-Za-z0-9]*",
|
|
65
|
+
"esp32": r"ESP32(?:-[A-Za-z0-9]+)?",
|
|
66
|
+
"nrf52": r"nRF5[2-9][\d A-Za-z0-9]*", # nRF52/nRF53/nRF54 (e.g. nRF54LM20B)
|
|
67
|
+
"atmega": r"ATmega\d+[A-Za-z]*",
|
|
68
|
+
"samd": r"ATSAMD\d+[A-Za-z]*",
|
|
69
|
+
"rp2040": r"RP2040",
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# Pin function inference patterns based on net names.
|
|
74
|
+
# IMPORTANT: use alternation groups (SDA|SCL), NOT character classes [SDA|SCL] —
|
|
75
|
+
# a character class matches any single char in the set {S,D,A,|,C,L} and would
|
|
76
|
+
# match almost every net name, producing nonsense matches.
|
|
77
|
+
NET_FUNCTION_PATTERNS = {
|
|
78
|
+
"I2C": [
|
|
79
|
+
r"I2C[_\d]*(?:SDA|SCL)",
|
|
80
|
+
r"SDA\d*",
|
|
81
|
+
r"SCL\d*",
|
|
82
|
+
r"TWI[_\d]*(?:SDA|SCL)",
|
|
83
|
+
],
|
|
84
|
+
"SPI": [
|
|
85
|
+
r"SPI[_\d]*(?:MISO|MOSI|SCK|CS)",
|
|
86
|
+
r"MISO\d*",
|
|
87
|
+
r"MOSI\d*",
|
|
88
|
+
r"SCK\d*",
|
|
89
|
+
r"CS\d*",
|
|
90
|
+
r"NSS\d*",
|
|
91
|
+
],
|
|
92
|
+
"UART": [
|
|
93
|
+
r"UART[_\d]*(?:TX|RX|CTS|RTS)",
|
|
94
|
+
r"USART[_\d]*(?:TX|RX|CTS|RTS)",
|
|
95
|
+
r"TX\d*",
|
|
96
|
+
r"RX\d*",
|
|
97
|
+
r"Serial\d*",
|
|
98
|
+
],
|
|
99
|
+
"GPIO": [
|
|
100
|
+
r"GPIO[_\d]+",
|
|
101
|
+
r"IO[_\d]+",
|
|
102
|
+
r"PA\d+",
|
|
103
|
+
r"PB\d+",
|
|
104
|
+
r"PC\d+",
|
|
105
|
+
r"P\d+",
|
|
106
|
+
],
|
|
107
|
+
"ADC": [
|
|
108
|
+
r"ADC[_\d]+",
|
|
109
|
+
r"AIN\d+",
|
|
110
|
+
r"AN\d+",
|
|
111
|
+
],
|
|
112
|
+
"PWM": [
|
|
113
|
+
r"PWM[_\d]+",
|
|
114
|
+
r"TIM[_\d]*CH*\d+",
|
|
115
|
+
],
|
|
116
|
+
"USB": [
|
|
117
|
+
r"USB[_\d]*(?:DM|DP|D-|D\+)",
|
|
118
|
+
r"UDM",
|
|
119
|
+
r"UDP",
|
|
120
|
+
r"D-",
|
|
121
|
+
r"D\+",
|
|
122
|
+
],
|
|
123
|
+
"INTERRUPT": [
|
|
124
|
+
r"INT\d*",
|
|
125
|
+
r"IRQ\d*",
|
|
126
|
+
r".*_INT",
|
|
127
|
+
],
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _infer_pin_function_from_net(net_name: str) -> str | None:
|
|
132
|
+
"""Infer pin function from net name pattern.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
net_name: Net name to analyze
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Inferred function or None if unknown
|
|
139
|
+
"""
|
|
140
|
+
if not net_name:
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
net_name_upper = net_name.upper()
|
|
144
|
+
|
|
145
|
+
# Check each function pattern
|
|
146
|
+
for function, patterns in NET_FUNCTION_PATTERNS.items():
|
|
147
|
+
for pattern in patterns:
|
|
148
|
+
if re.search(pattern, net_name_upper, re.IGNORECASE):
|
|
149
|
+
return function
|
|
150
|
+
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _identify_mcu_family(component_value: str) -> str | None:
|
|
155
|
+
"""Identify MCU family from component value.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
component_value: Component value/part number
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
MCU family identifier or None if not an MCU
|
|
162
|
+
"""
|
|
163
|
+
if not component_value:
|
|
164
|
+
return None
|
|
165
|
+
|
|
166
|
+
component_value_upper = component_value.upper()
|
|
167
|
+
|
|
168
|
+
for family, pattern in MCU_PATTERNS.items():
|
|
169
|
+
if re.search(pattern, component_value_upper, re.IGNORECASE):
|
|
170
|
+
return family
|
|
171
|
+
|
|
172
|
+
return None
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _get_mcu_pin_mapping(mcu_family: str, pin_name: str) -> dict:
|
|
176
|
+
"""Get MCU-specific pin mapping information.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
mcu_family: MCU family identifier
|
|
180
|
+
pin_name: Pin name to map
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
Dictionary with pin mapping information
|
|
184
|
+
"""
|
|
185
|
+
# Basic pin mappings for common MCU families
|
|
186
|
+
# This would be expanded with comprehensive pin databases
|
|
187
|
+
|
|
188
|
+
pin_mapping = {
|
|
189
|
+
"function": None,
|
|
190
|
+
"alternate_functions": [],
|
|
191
|
+
"max_current": 0.0,
|
|
192
|
+
"is_5v_tolerant": False,
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if mcu_family == "stm32":
|
|
196
|
+
# STM32 pin naming convention: PXn (e.g., PA0, PB12)
|
|
197
|
+
if re.match(r"P[A-Z][\d]+", pin_name, re.IGNORECASE):
|
|
198
|
+
pin_mapping["function"] = "GPIO"
|
|
199
|
+
pin_mapping["alternate_functions"] = [
|
|
200
|
+
"GPIO", "ADC", "TIM", "USART", "SPI", "I2C", "CAN"
|
|
201
|
+
]
|
|
202
|
+
pin_mapping["max_current"] = 25.0 # mA
|
|
203
|
+
pin_mapping["is_5v_tolerant"] = False # Most STM32 pins are not 5V tolerant
|
|
204
|
+
|
|
205
|
+
elif mcu_family == "esp32":
|
|
206
|
+
# ESP32 pin naming: IOx, GPIOx
|
|
207
|
+
if re.match(r"(?:IO|GPIO)[\d]+", pin_name, re.IGNORECASE):
|
|
208
|
+
pin_mapping["function"] = "GPIO"
|
|
209
|
+
pin_mapping["alternate_functions"] = [
|
|
210
|
+
"GPIO", "ADC", "DAC", "I2C", "SPI", "UART", "TOUCH"
|
|
211
|
+
]
|
|
212
|
+
pin_mapping["max_current"] = 40.0 # mA
|
|
213
|
+
pin_mapping["is_5v_tolerant"] = False
|
|
214
|
+
|
|
215
|
+
elif mcu_family == "nrf52":
|
|
216
|
+
# nRF52 pin naming: P0.xx, P1.xx
|
|
217
|
+
if re.match(r"P[01]\.[\d]+", pin_name, re.IGNORECASE):
|
|
218
|
+
pin_mapping["function"] = "GPIO"
|
|
219
|
+
pin_mapping["alternate_functions"] = [
|
|
220
|
+
"GPIO", "ADC", "SPI", "I2C", "UART", "PWM", "QSPI"
|
|
221
|
+
]
|
|
222
|
+
pin_mapping["max_current"] = 5.0 # mA (typical for nRF52)
|
|
223
|
+
pin_mapping["is_5v_tolerant"] = False
|
|
224
|
+
|
|
225
|
+
return pin_mapping
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@mcp.tool()
|
|
229
|
+
async def analyze_pin_functions(
|
|
230
|
+
schematic_path: str,
|
|
231
|
+
reference: str = "",
|
|
232
|
+
) -> str:
|
|
233
|
+
"""Analyze pin functions and detect conflicts.
|
|
234
|
+
|
|
235
|
+
This function analyzes schematic to determine pin functions by:
|
|
236
|
+
- Examining net names to infer functionality
|
|
237
|
+
- Identifying MCU components and their pin mappings
|
|
238
|
+
- Detecting potential pin conflicts
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
schematic_path: Path to .kicad_sch file
|
|
242
|
+
reference: Optional component reference (e.g., 'U1') to analyze specific component
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
Detailed pin function analysis report
|
|
246
|
+
"""
|
|
247
|
+
try:
|
|
248
|
+
sch_path = Path(schematic_path)
|
|
249
|
+
if not sch_path.exists():
|
|
250
|
+
return f"❌ **Schematic file not found:** {schematic_path}"
|
|
251
|
+
|
|
252
|
+
# Parse schematic
|
|
253
|
+
schematic_parser = SchematicParser(str(sch_path))
|
|
254
|
+
|
|
255
|
+
# Generate netlist for accurate connection analysis
|
|
256
|
+
netlist_path = await _ensure_netlist(sch_path)
|
|
257
|
+
if netlist_path is None:
|
|
258
|
+
return "❌ **Failed to generate netlist for pin analysis**"
|
|
259
|
+
|
|
260
|
+
# Parse netlist
|
|
261
|
+
netlist_parser = NetlistParser(str(netlist_path))
|
|
262
|
+
|
|
263
|
+
# Get components to analyze
|
|
264
|
+
if reference:
|
|
265
|
+
# Analyze specific component
|
|
266
|
+
components = [comp for comp in schematic_parser.get_components(recursive=True) if comp.reference == reference]
|
|
267
|
+
if not components:
|
|
268
|
+
return f"❌ **Component not found:** {reference}"
|
|
269
|
+
else:
|
|
270
|
+
# Analyze all components
|
|
271
|
+
components = list(schematic_parser.get_components(recursive=True))
|
|
272
|
+
|
|
273
|
+
# Analyze pin functions.
|
|
274
|
+
# The authoritative pin->net map comes from the netlist; pin name/type
|
|
275
|
+
# are enriched from the schematic's lib_symbols via SchematicComponent.pins.
|
|
276
|
+
# (Previously this relied on _extract_pin_info_from_symbol_details whose
|
|
277
|
+
# only parse branch was `pass`, so the tool always returned empty.)
|
|
278
|
+
sch_components = {c.reference: c for c in schematic_parser.get_components(recursive=True)}
|
|
279
|
+
nl_components = netlist_parser.get_components()
|
|
280
|
+
pin_analysis = []
|
|
281
|
+
|
|
282
|
+
for component in components:
|
|
283
|
+
comp_ref = component.reference
|
|
284
|
+
comp_value = component.value
|
|
285
|
+
mcu_family = _identify_mcu_family(comp_value)
|
|
286
|
+
|
|
287
|
+
nl_comp = nl_components.get(comp_ref)
|
|
288
|
+
pin_map = nl_comp.pins if nl_comp else {} # pin_number -> net_name
|
|
289
|
+
if not pin_map:
|
|
290
|
+
continue # not in netlist / nothing connected
|
|
291
|
+
|
|
292
|
+
# pin number -> (name, electrical_type) from schematic lib_symbols
|
|
293
|
+
pin_meta: dict[str, tuple[str, str]] = {}
|
|
294
|
+
sch_comp = sch_components.get(comp_ref)
|
|
295
|
+
if sch_comp:
|
|
296
|
+
for p in sch_comp.pins:
|
|
297
|
+
pin_meta[str(p.get("number", ""))] = (
|
|
298
|
+
p.get("name", ""),
|
|
299
|
+
p.get("electrical_type", ""),
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
for pin_num, net_name in pin_map.items():
|
|
303
|
+
pin_name, pin_type = pin_meta.get(str(pin_num), (str(pin_num), ""))
|
|
304
|
+
|
|
305
|
+
func = _infer_pin_function_from_net(net_name)
|
|
306
|
+
inferred_functions = [func] if func else []
|
|
307
|
+
|
|
308
|
+
mcu_mapping = _get_mcu_pin_mapping(mcu_family, pin_name) if mcu_family else None
|
|
309
|
+
|
|
310
|
+
pin_analysis.append({
|
|
311
|
+
"component": comp_ref,
|
|
312
|
+
"component_value": comp_value,
|
|
313
|
+
"mcu_family": mcu_family,
|
|
314
|
+
"pin_number": str(pin_num),
|
|
315
|
+
"pin_name": pin_name or str(pin_num),
|
|
316
|
+
"pin_type": pin_type,
|
|
317
|
+
"net_names": [net_name] if net_name else [],
|
|
318
|
+
"inferred_functions": inferred_functions,
|
|
319
|
+
"mcu_mapping": mcu_mapping,
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
if not pin_analysis:
|
|
323
|
+
return f"""⚠️ **No Pin Analysis Available**
|
|
324
|
+
|
|
325
|
+
**Schematic:** {schematic_path}
|
|
326
|
+
{'**Component:** ' + reference if reference else ''}
|
|
327
|
+
|
|
328
|
+
Unable to extract pin information. This could be due to:
|
|
329
|
+
- No components found in schematic
|
|
330
|
+
- Missing symbol definitions
|
|
331
|
+
- Netlist not available
|
|
332
|
+
|
|
333
|
+
**Next Steps:**
|
|
334
|
+
1. Ensure schematic has components with pins
|
|
335
|
+
2. Generate netlist first: `generate_netlist()`
|
|
336
|
+
3. Verify schematic file is valid KiCad 9.0+ format"""
|
|
337
|
+
|
|
338
|
+
# Format results
|
|
339
|
+
result = f"""# Pin Function Analysis
|
|
340
|
+
|
|
341
|
+
**Schematic:** {schematic_path}
|
|
342
|
+
{'**Component:** ' + reference if reference else ''}
|
|
343
|
+
**Total Pins Analyzed:** {len(pin_analysis)}
|
|
344
|
+
|
|
345
|
+
## Pin Details
|
|
346
|
+
|
|
347
|
+
| Component | Pin | Type | Nets | Inferred Functions | MCU Family |
|
|
348
|
+
|-----------|-----|------|------|-------------------|------------|
|
|
349
|
+
"""
|
|
350
|
+
|
|
351
|
+
for pin in pin_analysis[:50]: # Limit to first 50 pins
|
|
352
|
+
comp = pin["component"]
|
|
353
|
+
pin_name = pin["pin_name"]
|
|
354
|
+
pin_type = pin["pin_type"]
|
|
355
|
+
nets = ", ".join(pin["net_names"][:3]) if pin["net_names"] else "N/A"
|
|
356
|
+
if len(pin["net_names"]) > 3:
|
|
357
|
+
nets += f" (+{len(pin['net_names']) - 3} more)"
|
|
358
|
+
functions = ", ".join(pin["inferred_functions"]) if pin["inferred_functions"] else "Unknown"
|
|
359
|
+
mcu_family = pin["mcu_family"] if pin["mcu_family"] else "N/A"
|
|
360
|
+
|
|
361
|
+
result += f"| {comp} | {pin_name} ({pin['pin_number']}) | {pin_type} | {nets} | {functions} | {mcu_family} |\n"
|
|
362
|
+
|
|
363
|
+
if len(pin_analysis) > 50:
|
|
364
|
+
result += f"\n*... and {len(pin_analysis) - 50} more pins*\n"
|
|
365
|
+
|
|
366
|
+
# Add MCU-specific details if available
|
|
367
|
+
mcu_pins = [p for p in pin_analysis if p["mcu_family"] and p["mcu_mapping"]]
|
|
368
|
+
if mcu_pins:
|
|
369
|
+
result += "\n## MCU Pin Details\n\n"
|
|
370
|
+
|
|
371
|
+
for pin in mcu_pins[:10]: # Limit to first 10 MCU pins
|
|
372
|
+
mapping = pin["mcu_mapping"]
|
|
373
|
+
result += f"**{pin['component']} - {pin['pin_name']} ({pin['pin_number']})**\n"
|
|
374
|
+
result += f"- Primary Function: {mapping['function']}\n"
|
|
375
|
+
result += f"- Alternate Functions: {', '.join(mapping['alternate_functions'])}\n"
|
|
376
|
+
result += f"- Max Current: {mapping['max_current']} mA\n"
|
|
377
|
+
result += f"- 5V Tolerant: {'Yes' if mapping['is_5v_tolerant'] else 'No'}\n\n"
|
|
378
|
+
|
|
379
|
+
return result
|
|
380
|
+
|
|
381
|
+
except Exception as e:
|
|
382
|
+
import traceback
|
|
383
|
+
|
|
384
|
+
return f"""❌ **Pin Analysis Failed**
|
|
385
|
+
|
|
386
|
+
**Schematic:** {schematic_path}
|
|
387
|
+
{'**Component:** ' + reference if reference else ''}
|
|
388
|
+
|
|
389
|
+
**Error:** {str(e)}
|
|
390
|
+
|
|
391
|
+
**Traceback:**
|
|
392
|
+
```
|
|
393
|
+
{traceback.format_exc()}
|
|
394
|
+
```"""
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def _extract_pin_info_from_symbol_details(details_text: str) -> list[dict]:
|
|
398
|
+
"""Extract pin information from get_symbol_details output.
|
|
399
|
+
|
|
400
|
+
Args:
|
|
401
|
+
details_text: Formatted output from get_symbol_details
|
|
402
|
+
|
|
403
|
+
Returns:
|
|
404
|
+
List of pin information dictionaries
|
|
405
|
+
"""
|
|
406
|
+
pins = []
|
|
407
|
+
|
|
408
|
+
# Parse the get_symbol_details markdown table, e.g.:
|
|
409
|
+
# | Pin | Name | Type |
|
|
410
|
+
# | --- | ---- | ---- |
|
|
411
|
+
# | 1 | VDD | power_in |
|
|
412
|
+
lines = details_text.split("\n")
|
|
413
|
+
|
|
414
|
+
for line in lines:
|
|
415
|
+
if "|" not in line:
|
|
416
|
+
continue
|
|
417
|
+
cells = [c.strip() for c in line.split("|")]
|
|
418
|
+
cells = [c for c in cells if c != ""] # drop empty edge cells
|
|
419
|
+
if len(cells) < 3:
|
|
420
|
+
continue
|
|
421
|
+
first = cells[0].lower()
|
|
422
|
+
# skip header / separator rows
|
|
423
|
+
if first in ("pin", "number", "#", "no.", "ref"):
|
|
424
|
+
continue
|
|
425
|
+
if set(cells[0]) <= set("-:="):
|
|
426
|
+
continue
|
|
427
|
+
pins.append({"number": cells[0], "name": cells[1], "type": cells[2]})
|
|
428
|
+
|
|
429
|
+
return pins
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
@mcp.tool()
|
|
433
|
+
async def detect_pin_conflicts(
|
|
434
|
+
schematic_path: str,
|
|
435
|
+
) -> str:
|
|
436
|
+
"""Detect pins with conflicting electrical connections.
|
|
437
|
+
|
|
438
|
+
This function checks for:
|
|
439
|
+
- Multiple outputs on same net
|
|
440
|
+
- Power-to-power connections
|
|
441
|
+
- Unconnected input pins
|
|
442
|
+
- Pin type mismatches
|
|
443
|
+
|
|
444
|
+
Args:
|
|
445
|
+
schematic_path: Path to .kicad_sch file
|
|
446
|
+
|
|
447
|
+
Returns:
|
|
448
|
+
Detailed conflict detection report
|
|
449
|
+
"""
|
|
450
|
+
try:
|
|
451
|
+
sch_path = Path(schematic_path)
|
|
452
|
+
if not sch_path.exists():
|
|
453
|
+
return f"❌ **Schematic file not found:** {schematic_path}"
|
|
454
|
+
|
|
455
|
+
# Generate netlist for accurate connection analysis
|
|
456
|
+
netlist_path = await _ensure_netlist(sch_path)
|
|
457
|
+
if netlist_path is None:
|
|
458
|
+
return "❌ **Failed to generate netlist for conflict detection**"
|
|
459
|
+
|
|
460
|
+
# Parse netlist + schematic. The netlist carries pin->net but NOT pin
|
|
461
|
+
# electrical type; that comes from the schematic's lib_symbols.
|
|
462
|
+
netlist_parser = NetlistParser(str(netlist_path))
|
|
463
|
+
schematic_parser = SchematicParser(str(sch_path))
|
|
464
|
+
|
|
465
|
+
output_types = {"output", "power_out", "open_collector", "open_emitter", "tri_state"}
|
|
466
|
+
|
|
467
|
+
# (ref, pin_number) -> electrical_type
|
|
468
|
+
pin_type_of: dict[tuple[str, str], str] = {}
|
|
469
|
+
for c in schematic_parser.get_components(recursive=True):
|
|
470
|
+
for p in c.pins:
|
|
471
|
+
pin_type_of[(c.reference, str(p.get("number", "")))] = (
|
|
472
|
+
p.get("electrical_type", "").lower()
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
# Detect conflicts
|
|
476
|
+
conflicts = []
|
|
477
|
+
|
|
478
|
+
# Check for multiple output/driver pins on the same net.
|
|
479
|
+
# (Multiple power_in pins on a rail like +3V3 are normal and are NOT
|
|
480
|
+
# reported — only multiple drivers are a conflict.)
|
|
481
|
+
all_nets = netlist_parser.get_nets()
|
|
482
|
+
|
|
483
|
+
for net_name, net in all_nets.items():
|
|
484
|
+
connections = net.pins # list of (ref, pin_number)
|
|
485
|
+
|
|
486
|
+
output_pins = []
|
|
487
|
+
for ref, pin_num in connections:
|
|
488
|
+
pin_type = pin_type_of.get((ref, str(pin_num)), "")
|
|
489
|
+
if pin_type in output_types:
|
|
490
|
+
output_pins.append(f"{ref}:{pin_num}")
|
|
491
|
+
|
|
492
|
+
if len(output_pins) > 1:
|
|
493
|
+
conflicts.append({
|
|
494
|
+
"type": "multiple_outputs",
|
|
495
|
+
"severity": "error",
|
|
496
|
+
"net": net_name,
|
|
497
|
+
"description": f"Multiple output/driver pins on same net: {', '.join(output_pins)}",
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
# Check for single-pin nets (potential unconnected)
|
|
501
|
+
for net_name, net in all_nets.items():
|
|
502
|
+
if len(net.pins) == 1 and not net_name.startswith("unconnected-"):
|
|
503
|
+
ref, pin_num = net.pins[0]
|
|
504
|
+
conflicts.append({
|
|
505
|
+
"type": "single_pin_net",
|
|
506
|
+
"severity": "warning",
|
|
507
|
+
"net": net_name,
|
|
508
|
+
"description": f"Net '{net_name}' has only one pin: {ref}:{pin_num}",
|
|
509
|
+
})
|
|
510
|
+
|
|
511
|
+
# Check for unconnected input pins
|
|
512
|
+
all_components = netlist_parser.get_components()
|
|
513
|
+
|
|
514
|
+
for ref, comp in all_components.items():
|
|
515
|
+
for pin_num, net_name in comp.pins.items():
|
|
516
|
+
if net_name.startswith("unconnected-"):
|
|
517
|
+
conflicts.append({
|
|
518
|
+
"type": "unconnected_pin",
|
|
519
|
+
"severity": "info",
|
|
520
|
+
"net": net_name,
|
|
521
|
+
"description": f"{ref} pin {pin_num} is unconnected ({net_name})",
|
|
522
|
+
})
|
|
523
|
+
|
|
524
|
+
if not conflicts:
|
|
525
|
+
return f"""✅ **No Pin Conflicts Detected**
|
|
526
|
+
|
|
527
|
+
**Schematic:** {schematic_path}
|
|
528
|
+
|
|
529
|
+
The schematic has been analyzed and no pin conflicts were found.
|
|
530
|
+
|
|
531
|
+
**Checked:**
|
|
532
|
+
- ✅ No multiple output/driver pins on the same net
|
|
533
|
+
- ✅ No single-pin (potentially unconnected) nets
|
|
534
|
+
- ✅ No unconnected pins"""
|
|
535
|
+
|
|
536
|
+
# Format conflicts
|
|
537
|
+
result = f"""❌ **Pin Conflicts Detected**
|
|
538
|
+
|
|
539
|
+
**Schematic:** {schematic_path}
|
|
540
|
+
**Total Conflicts:** {len(conflicts)}
|
|
541
|
+
|
|
542
|
+
## Conflicts
|
|
543
|
+
|
|
544
|
+
| Severity | Type | Location | Description |
|
|
545
|
+
|----------|------|----------|-------------|
|
|
546
|
+
"""
|
|
547
|
+
|
|
548
|
+
for conflict in conflicts[:50]: # Limit to first 50
|
|
549
|
+
severity_icon = "❌" if conflict["severity"] == "error" else "⚠️"
|
|
550
|
+
conflict_type = conflict["type"]
|
|
551
|
+
location = conflict.get("net", conflict.get("component", "Unknown"))
|
|
552
|
+
description = conflict["description"]
|
|
553
|
+
|
|
554
|
+
result += f"| {severity_icon} {conflict['severity']} | {conflict_type} | {location} | {description} |\n"
|
|
555
|
+
|
|
556
|
+
if len(conflicts) > 50:
|
|
557
|
+
result += f"\n*... and {len(conflicts) - 50} more conflicts*\n"
|
|
558
|
+
|
|
559
|
+
result += "\n## Recommendations\n\n"
|
|
560
|
+
result += "1. **Fix all errors** before proceeding to PCB layout\n"
|
|
561
|
+
result += "2. Review warnings - some may be acceptable design choices\n"
|
|
562
|
+
result += "3. Verify pin connections in KiCad Eeschema\n"
|
|
563
|
+
result += "4. Re-run analysis after fixes\n"
|
|
564
|
+
|
|
565
|
+
return result
|
|
566
|
+
|
|
567
|
+
except Exception as e:
|
|
568
|
+
import traceback
|
|
569
|
+
|
|
570
|
+
return f"""❌ **Conflict Detection Failed**
|
|
571
|
+
|
|
572
|
+
**Schematic:** {schematic_path}
|
|
573
|
+
|
|
574
|
+
**Error:** {str(e)}
|
|
575
|
+
|
|
576
|
+
**Traceback:**
|
|
577
|
+
```
|
|
578
|
+
{traceback.format_exc()}
|
|
579
|
+
```"""
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
@mcp.tool()
|
|
583
|
+
async def extract_pinmux_config(
|
|
584
|
+
schematic_path: str,
|
|
585
|
+
component_type: str = "",
|
|
586
|
+
) -> str:
|
|
587
|
+
"""Extract pin multiplexing configuration for MCUs.
|
|
588
|
+
|
|
589
|
+
This function extracts pin multiplexing (pinmux) configuration
|
|
590
|
+
for MCU components, showing which peripherals are assigned to which pins.
|
|
591
|
+
|
|
592
|
+
Args:
|
|
593
|
+
schematic_path: Path to .kicad_sch file
|
|
594
|
+
component_type: Optional MCU type filter (e.g., 'stm32', 'esp32', 'nrf52')
|
|
595
|
+
|
|
596
|
+
Returns:
|
|
597
|
+
Detailed pinmux configuration report
|
|
598
|
+
"""
|
|
599
|
+
try:
|
|
600
|
+
sch_path = Path(schematic_path)
|
|
601
|
+
if not sch_path.exists():
|
|
602
|
+
return f"❌ **Schematic file not found:** {schematic_path}"
|
|
603
|
+
|
|
604
|
+
# Parse schematic
|
|
605
|
+
schematic_parser = SchematicParser(str(sch_path))
|
|
606
|
+
|
|
607
|
+
# Generate netlist for accurate connection analysis
|
|
608
|
+
netlist_path = await _ensure_netlist(sch_path)
|
|
609
|
+
if netlist_path is None:
|
|
610
|
+
return "❌ **Failed to generate netlist for pinmux extraction**"
|
|
611
|
+
|
|
612
|
+
# Parse netlist
|
|
613
|
+
netlist_parser = NetlistParser(str(netlist_path))
|
|
614
|
+
|
|
615
|
+
# Find MCU components
|
|
616
|
+
components = list(schematic_parser.get_components(recursive=True))
|
|
617
|
+
mcu_components = []
|
|
618
|
+
|
|
619
|
+
for component in components:
|
|
620
|
+
comp_ref = component.reference
|
|
621
|
+
comp_value = component.value
|
|
622
|
+
|
|
623
|
+
# Check if this is an MCU
|
|
624
|
+
mcu_family = _identify_mcu_family(comp_value)
|
|
625
|
+
|
|
626
|
+
if mcu_family:
|
|
627
|
+
# Filter by component_type if specified
|
|
628
|
+
if component_type and component_type.lower() != mcu_family.lower():
|
|
629
|
+
continue
|
|
630
|
+
|
|
631
|
+
mcu_components.append({
|
|
632
|
+
"reference": comp_ref,
|
|
633
|
+
"value": comp_value,
|
|
634
|
+
"mcu_family": mcu_family,
|
|
635
|
+
"library": component.library_id,
|
|
636
|
+
})
|
|
637
|
+
|
|
638
|
+
if not mcu_components:
|
|
639
|
+
return f"""⚠️ **No MCU Components Found**
|
|
640
|
+
|
|
641
|
+
**Schematic:** {schematic_path}
|
|
642
|
+
{'**Component Type Filter:** ' + component_type if component_type else ''}
|
|
643
|
+
|
|
644
|
+
No MCU components were found in the schematic.
|
|
645
|
+
|
|
646
|
+
**Supported MCU families:**
|
|
647
|
+
- STM32 (STM32F, STM32H, STM32L series)
|
|
648
|
+
- ESP32 (ESP32, ESP32-S2, ESP32-S3, etc.)
|
|
649
|
+
- nRF52 (nRF52832, nRF52840, etc.)
|
|
650
|
+
- ATmega (ATmega328P, ATmega2560, etc.)
|
|
651
|
+
- SAMD (ATSAMD21, ATSAMD51, etc.)
|
|
652
|
+
- RP2040
|
|
653
|
+
|
|
654
|
+
**Next Steps:**
|
|
655
|
+
1. Ensure schematic contains MCU components
|
|
656
|
+
2. Check component values match supported patterns
|
|
657
|
+
3. Try without component_type filter"""
|
|
658
|
+
|
|
659
|
+
# pin number -> name from schematic lib_symbols
|
|
660
|
+
sch_components = {c.reference: c for c in schematic_parser.get_components(recursive=True)}
|
|
661
|
+
|
|
662
|
+
# Extract pinmux configuration for each MCU
|
|
663
|
+
pinmux_configs = []
|
|
664
|
+
|
|
665
|
+
for mcu in mcu_components:
|
|
666
|
+
mcu_ref = mcu["reference"]
|
|
667
|
+
mcu_family = mcu["mcu_family"]
|
|
668
|
+
|
|
669
|
+
config = {
|
|
670
|
+
"component": mcu_ref,
|
|
671
|
+
"mcu_family": mcu_family,
|
|
672
|
+
"part_number": mcu["value"],
|
|
673
|
+
"pins": [],
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
pin_names: dict[str, str] = {}
|
|
677
|
+
sch_c = sch_components.get(mcu_ref)
|
|
678
|
+
if sch_c:
|
|
679
|
+
for p in sch_c.pins:
|
|
680
|
+
pin_names[str(p.get("number", ""))] = p.get("name", "")
|
|
681
|
+
|
|
682
|
+
comp = netlist_parser.get_components().get(mcu_ref)
|
|
683
|
+
if comp:
|
|
684
|
+
for pin_num, net_name in comp.pins.items():
|
|
685
|
+
peripheral = _infer_pin_function_from_net(net_name)
|
|
686
|
+
pin_name = pin_names.get(str(pin_num), str(pin_num))
|
|
687
|
+
mcu_mapping = _get_mcu_pin_mapping(mcu_family, pin_name)
|
|
688
|
+
pin_config = {
|
|
689
|
+
"pin_number": str(pin_num),
|
|
690
|
+
"pin_name": pin_name,
|
|
691
|
+
"net": net_name,
|
|
692
|
+
"peripheral": peripheral,
|
|
693
|
+
"alternate_functions": (mcu_mapping or {}).get("alternate_functions", []),
|
|
694
|
+
}
|
|
695
|
+
config["pins"].append(pin_config)
|
|
696
|
+
|
|
697
|
+
pinmux_configs.append(config)
|
|
698
|
+
|
|
699
|
+
# Format results
|
|
700
|
+
result = f"""# Pin Multiplexing Configuration
|
|
701
|
+
|
|
702
|
+
**Schematic:** {schematic_path}
|
|
703
|
+
{'**Component Type Filter:** ' + component_type if component_type else ''}
|
|
704
|
+
**Total MCUs Analyzed:** {len(pinmux_configs)}
|
|
705
|
+
|
|
706
|
+
"""
|
|
707
|
+
|
|
708
|
+
for mcu_config in pinmux_configs:
|
|
709
|
+
result += f"""## {mcu_config['component']}: {mcu_config['part_number']}
|
|
710
|
+
|
|
711
|
+
**MCU Family:** {mcu_config['mcu_family']}
|
|
712
|
+
|
|
713
|
+
| Pin | Net | Peripheral | Alternate Functions |
|
|
714
|
+
|-----|-----|------------|---------------------|
|
|
715
|
+
"""
|
|
716
|
+
|
|
717
|
+
for pin in mcu_config["pins"][:30]: # Limit to first 30 pins per MCU
|
|
718
|
+
pin_name = pin["pin_name"]
|
|
719
|
+
net = pin["net"]
|
|
720
|
+
peripheral = pin["peripheral"] if pin["peripheral"] else "GPIO"
|
|
721
|
+
alt_funcs = ", ".join(pin["alternate_functions"]) if pin["alternate_functions"] else "N/A"
|
|
722
|
+
|
|
723
|
+
result += f"| {pin_name} ({pin['pin_number']}) | {net} | {peripheral} | {alt_funcs} |\n"
|
|
724
|
+
|
|
725
|
+
if len(mcu_config["pins"]) > 30:
|
|
726
|
+
result += f"\n*... and {len(mcu_config['pins']) - 30} more pins*\n"
|
|
727
|
+
|
|
728
|
+
result += "\n"
|
|
729
|
+
|
|
730
|
+
# Add code generation suggestions
|
|
731
|
+
result += "## Code Generation Suggestions\n\n"
|
|
732
|
+
|
|
733
|
+
for mcu_config in pinmux_configs:
|
|
734
|
+
result += f"""### {mcu_config['component']} ({mcu_config['mcu_family']})
|
|
735
|
+
|
|
736
|
+
```c
|
|
737
|
+
// Pin configuration for {mcu_config['part_number']}
|
|
738
|
+
"""
|
|
739
|
+
|
|
740
|
+
for pin in mcu_config["pins"][:10]: # Show first 10 pins as example
|
|
741
|
+
if pin["peripheral"]:
|
|
742
|
+
result += f"// {pin['pin_name']}: {pin['peripheral']} ({pin['net']})\n"
|
|
743
|
+
if mcu_config["mcu_family"] == "stm32":
|
|
744
|
+
result += f"// GPIO_{pin['pin_name'][1]}_{pin['pin_name'][2:]} // {pin['peripheral']}_AF\n"
|
|
745
|
+
elif mcu_config["mcu_family"] == "esp32":
|
|
746
|
+
result += f"// GPIO{pin['pin_number']} // {pin['peripheral']}\n"
|
|
747
|
+
|
|
748
|
+
result += "```\n\n"
|
|
749
|
+
|
|
750
|
+
return result
|
|
751
|
+
|
|
752
|
+
except Exception as e:
|
|
753
|
+
import traceback
|
|
754
|
+
|
|
755
|
+
return f"""❌ **Pinmux Extraction Failed**
|
|
756
|
+
|
|
757
|
+
**Schematic:** {schematic_path}
|
|
758
|
+
{'**Component Type:** ' + component_type if component_type else ''}
|
|
759
|
+
|
|
760
|
+
**Error:** {str(e)}
|
|
761
|
+
|
|
762
|
+
**Traceback:**
|
|
763
|
+
```
|
|
764
|
+
{traceback.format_exc()}
|
|
765
|
+
```"""
|