@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.
Files changed (192) hide show
  1. package/README.md +116 -0
  2. package/config/mcp/antigravity.json +22 -0
  3. package/config/mcp/claude.json +22 -0
  4. package/config/mcp/codex.toml +25 -0
  5. package/docs/adr/ADR-001-KICAD-OPENSCAD-MCP-STRATEGY.md +334 -0
  6. package/docs/review_documentation.md +121 -0
  7. package/installer.js +358 -0
  8. package/mcp_servers/kicad-mcp-server/.env.example +22 -0
  9. package/mcp_servers/kicad-mcp-server/.github/workflows/ci.yml +36 -0
  10. package/mcp_servers/kicad-mcp-server/CLAUDE.md +487 -0
  11. package/mcp_servers/kicad-mcp-server/README.md +316 -0
  12. package/mcp_servers/kicad-mcp-server/docs/DEVICE_TREE.md +416 -0
  13. package/mcp_servers/kicad-mcp-server/docs/INSTALLATION.md +332 -0
  14. package/mcp_servers/kicad-mcp-server/docs/PIN_ANALYSIS.md +332 -0
  15. package/mcp_servers/kicad-mcp-server/docs/README.md +240 -0
  16. package/mcp_servers/kicad-mcp-server/docs/TESTING.md +613 -0
  17. package/mcp_servers/kicad-mcp-server/docs/VALIDATION.md +268 -0
  18. package/mcp_servers/kicad-mcp-server/pyproject.toml +96 -0
  19. package/mcp_servers/kicad-mcp-server/requirements-dev.txt +16 -0
  20. package/mcp_servers/kicad-mcp-server/requirements-test.txt +24 -0
  21. package/mcp_servers/kicad-mcp-server/requirements.txt +13 -0
  22. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/__init__.py +3 -0
  23. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/__main__.py +17 -0
  24. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/config.py +46 -0
  25. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/models/__init__.py +1 -0
  26. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/models/types.py +87 -0
  27. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/__init__.py +1 -0
  28. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/netlist_parser.py +234 -0
  29. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/pcb_parser.py +375 -0
  30. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/pcb_parser_kicad.py +327 -0
  31. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/schematic_parser.py +902 -0
  32. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/server.py +71 -0
  33. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/__init__.py +1 -0
  34. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/arduino/connectivity_test.cpp.j2 +189 -0
  35. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/atmega.dts.j2 +77 -0
  36. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/esp32.dts.j2 +77 -0
  37. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/nrf52.dts.j2 +77 -0
  38. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/stm32f4.dts.j2 +89 -0
  39. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/esp_idf/test_suite.c.j2 +340 -0
  40. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/pytest/test_connectivity.py.j2 +147 -0
  41. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/st_hal/hal_test.c.j2 +313 -0
  42. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_gpio_test.py.j2 +99 -0
  43. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_i2c_test.py.j2 +117 -0
  44. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_pinmux_test.py.j2 +43 -0
  45. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_spi_test.py.j2 +94 -0
  46. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/unity_gpio_test.c.j2 +113 -0
  47. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/unity_i2c_test.c.j2 +101 -0
  48. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/unity_spi_test.c.j2 +94 -0
  49. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/unittest/test_schematic.py.j2 +172 -0
  50. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/__init__.py +36 -0
  51. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/device_tree.py +1187 -0
  52. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/hierarchical_analysis.py +211 -0
  53. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/netlist.py +320 -0
  54. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/parts_registry.py +142 -0
  55. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/pcb.py +955 -0
  56. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/pcb_layout.py +308 -0
  57. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/pin_analysis.py +765 -0
  58. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/project.py +196 -0
  59. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/schematic.py +319 -0
  60. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/schematic_editor.py +674 -0
  61. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/schematic_search.py +158 -0
  62. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/validation.py +866 -0
  63. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/visualization.py +225 -0
  64. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/__init__.py +1 -0
  65. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/file_handlers.py +65 -0
  66. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/kicad_cli.py +103 -0
  67. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/kicad_version.py +103 -0
  68. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/parts_registry.py +197 -0
  69. package/mcp_servers/kicad-mcp-server/tests/__init__.py +1 -0
  70. package/mcp_servers/kicad-mcp-server/tests/examples/ESP32S3_TEST.md +219 -0
  71. package/mcp_servers/kicad-mcp-server/tests/fixtures/README.md +65 -0
  72. package/mcp_servers/kicad-mcp-server/tests/fixtures/__init__.py +1 -0
  73. package/mcp_servers/kicad-mcp-server/tests/fixtures/example_pcb.kicad_pcb +177 -0
  74. package/mcp_servers/kicad-mcp-server/tests/fixtures/example_schematic.kicad_sch +145 -0
  75. package/mcp_servers/kicad-mcp-server/tests/fixtures/hier/child.kicad_sch +24 -0
  76. package/mcp_servers/kicad-mcp-server/tests/fixtures/hier/root.kicad_sch +38 -0
  77. package/mcp_servers/kicad-mcp-server/tests/test_tools/__init__.py +1 -0
  78. package/mcp_servers/kicad-mcp-server/tests/test_tools/test_hierarchical_labels.py +195 -0
  79. package/mcp_servers/kicad-mcp-server/tests/test_tools/test_kicad_cli.py +116 -0
  80. package/mcp_servers/kicad-mcp-server/tests/test_tools/test_netlist_cache_path.py +29 -0
  81. package/mcp_servers/kicad-mcp-server/tests/test_tools/test_schematic.py +189 -0
  82. package/mcp_servers/kicad-mcp-server/tests/test_tools/test_schematic_hierarchy.py +69 -0
  83. package/mcp_servers/kicad-mcp-server/tests/test_tools/test_visualization.py +136 -0
  84. package/mcp_servers/kicad-mcp-server/uv.lock +2873 -0
  85. package/mcp_servers/openscad-mcp-server/.dockerignore +9 -0
  86. package/mcp_servers/openscad-mcp-server/.github/workflows/test.yml +40 -0
  87. package/mcp_servers/openscad-mcp-server/Dockerfile +29 -0
  88. package/mcp_servers/openscad-mcp-server/LICENSE +21 -0
  89. package/mcp_servers/openscad-mcp-server/README.md +154 -0
  90. package/mcp_servers/openscad-mcp-server/docs/audit.md +56 -0
  91. package/mcp_servers/openscad-mcp-server/docs/docker.md +66 -0
  92. package/mcp_servers/openscad-mcp-server/docs/issue-followup.md +19 -0
  93. package/mcp_servers/openscad-mcp-server/docs/jetson.md +17 -0
  94. package/mcp_servers/openscad-mcp-server/glama.json +4 -0
  95. package/mcp_servers/openscad-mcp-server/legacy/README.md +15 -0
  96. package/mcp_servers/openscad-mcp-server/legacy/README.original.md +294 -0
  97. package/mcp_servers/openscad-mcp-server/legacy/implementation_plan.md +100 -0
  98. package/mcp_servers/openscad-mcp-server/legacy/old/download_sam2_checkpoint.py +115 -0
  99. package/mcp_servers/openscad-mcp-server/legacy/old/src/ai/sam_segmentation.py +209 -0
  100. package/mcp_servers/openscad-mcp-server/legacy/old/src/models/threestudio_generator.py +231 -0
  101. package/mcp_servers/openscad-mcp-server/legacy/old/src/workflow/image_to_model_pipeline.py +260 -0
  102. package/mcp_servers/openscad-mcp-server/legacy/old/test_sam2_segmentation.py +96 -0
  103. package/mcp_servers/openscad-mcp-server/legacy/requirements.txt +57 -0
  104. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/README.md +39 -0
  105. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/decisions/ai-driven-code-generation.md +122 -0
  106. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/decisions/export-formats.md +76 -0
  107. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/ai/ai_service.py.md +51 -0
  108. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/main.py.md +63 -0
  109. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/models/code_generator.py.md +63 -0
  110. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/nlp/parameter_extractor.py.md +63 -0
  111. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/ai/natural-language-processing.md +78 -0
  112. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/nlp/parameter-extraction.md +173 -0
  113. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/openscad/export-formats.md +91 -0
  114. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/openscad/openscad-basics.md +66 -0
  115. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/openscad/primitive-testing.md +79 -0
  116. package/mcp_servers/openscad-mcp-server/legacy/src/__init__.py +0 -0
  117. package/mcp_servers/openscad-mcp-server/legacy/src/ai/ai_service.py +257 -0
  118. package/mcp_servers/openscad-mcp-server/legacy/src/ai/gemini_api.py +161 -0
  119. package/mcp_servers/openscad-mcp-server/legacy/src/ai/venice_api.py +203 -0
  120. package/mcp_servers/openscad-mcp-server/legacy/src/config.py +121 -0
  121. package/mcp_servers/openscad-mcp-server/legacy/src/main.py +1456 -0
  122. package/mcp_servers/openscad-mcp-server/legacy/src/main.py.new +404 -0
  123. package/mcp_servers/openscad-mcp-server/legacy/src/main_remote.py +401 -0
  124. package/mcp_servers/openscad-mcp-server/legacy/src/models/__init__.py +0 -0
  125. package/mcp_servers/openscad-mcp-server/legacy/src/models/code_generator.py +321 -0
  126. package/mcp_servers/openscad-mcp-server/legacy/src/models/cuda_mvs.py +209 -0
  127. package/mcp_servers/openscad-mcp-server/legacy/src/models/scad_templates/basic_shapes.scad +144 -0
  128. package/mcp_servers/openscad-mcp-server/legacy/src/nlp/__init__.py +0 -0
  129. package/mcp_servers/openscad-mcp-server/legacy/src/nlp/parameter_extractor.py +388 -0
  130. package/mcp_servers/openscad-mcp-server/legacy/src/openscad_wrapper/__init__.py +0 -0
  131. package/mcp_servers/openscad-mcp-server/legacy/src/openscad_wrapper/wrapper.py +418 -0
  132. package/mcp_servers/openscad-mcp-server/legacy/src/printer_discovery/__init__.py +1 -0
  133. package/mcp_servers/openscad-mcp-server/legacy/src/printer_discovery/printer_discovery.py +471 -0
  134. package/mcp_servers/openscad-mcp-server/legacy/src/remote/connection_manager.py +537 -0
  135. package/mcp_servers/openscad-mcp-server/legacy/src/remote/cuda_mvs_client.py +435 -0
  136. package/mcp_servers/openscad-mcp-server/legacy/src/remote/cuda_mvs_server.py +787 -0
  137. package/mcp_servers/openscad-mcp-server/legacy/src/remote/error_handling.py +415 -0
  138. package/mcp_servers/openscad-mcp-server/legacy/src/testing/__init__.py +0 -0
  139. package/mcp_servers/openscad-mcp-server/legacy/src/testing/primitive_tester.py +203 -0
  140. package/mcp_servers/openscad-mcp-server/legacy/src/testing/test_primitives.py +98 -0
  141. package/mcp_servers/openscad-mcp-server/legacy/src/utils/__init__.py +1 -0
  142. package/mcp_servers/openscad-mcp-server/legacy/src/utils/cad_exporter.py +241 -0
  143. package/mcp_servers/openscad-mcp-server/legacy/src/utils/format_validator.py +206 -0
  144. package/mcp_servers/openscad-mcp-server/legacy/src/utils/stl_exporter.py +140 -0
  145. package/mcp_servers/openscad-mcp-server/legacy/src/utils/stl_repair.py +91 -0
  146. package/mcp_servers/openscad-mcp-server/legacy/src/utils/stl_validator.py +123 -0
  147. package/mcp_servers/openscad-mcp-server/legacy/src/visualization/__init__.py +0 -0
  148. package/mcp_servers/openscad-mcp-server/legacy/src/visualization/headless_renderer.py +52 -0
  149. package/mcp_servers/openscad-mcp-server/legacy/src/visualization/renderer.py +177 -0
  150. package/mcp_servers/openscad-mcp-server/legacy/src/visualization/web_interface.py +639 -0
  151. package/mcp_servers/openscad-mcp-server/legacy/src/workflow/image_approval.py +148 -0
  152. package/mcp_servers/openscad-mcp-server/legacy/src/workflow/multi_view_to_model_pipeline.py +338 -0
  153. package/mcp_servers/openscad-mcp-server/legacy/test_complete_workflow.py +374 -0
  154. package/mcp_servers/openscad-mcp-server/legacy/test_cuda_mvs.py +191 -0
  155. package/mcp_servers/openscad-mcp-server/legacy/test_gemini_api.py +168 -0
  156. package/mcp_servers/openscad-mcp-server/legacy/test_image_approval.py +192 -0
  157. package/mcp_servers/openscad-mcp-server/legacy/test_image_approval_workflow.py +251 -0
  158. package/mcp_servers/openscad-mcp-server/legacy/test_image_to_model_pipeline.py +145 -0
  159. package/mcp_servers/openscad-mcp-server/legacy/test_model_selection.py +41 -0
  160. package/mcp_servers/openscad-mcp-server/legacy/test_multi_view_pipeline.py +290 -0
  161. package/mcp_servers/openscad-mcp-server/legacy/test_primitives.sh +13 -0
  162. package/mcp_servers/openscad-mcp-server/legacy/test_rabbit_direct.py +71 -0
  163. package/mcp_servers/openscad-mcp-server/legacy/test_remote_cuda_mvs.py +283 -0
  164. package/mcp_servers/openscad-mcp-server/legacy/test_venice_example.py +69 -0
  165. package/mcp_servers/openscad-mcp-server/pyproject.toml +33 -0
  166. package/mcp_servers/openscad-mcp-server/requirements.txt +2 -0
  167. package/mcp_servers/openscad-mcp-server/scad/simple_cube.scad +2 -0
  168. package/mcp_servers/openscad-mcp-server/scripts/test_docker.py +220 -0
  169. package/mcp_servers/openscad-mcp-server/src/openscad_mcp/__init__.py +3 -0
  170. package/mcp_servers/openscad-mcp-server/src/openscad_mcp/__main__.py +3 -0
  171. package/mcp_servers/openscad-mcp-server/src/openscad_mcp/engine.py +94 -0
  172. package/mcp_servers/openscad-mcp-server/src/openscad_mcp/geometry.py +242 -0
  173. package/mcp_servers/openscad-mcp-server/src/openscad_mcp/server.py +245 -0
  174. package/mcp_servers/openscad-mcp-server/src/openscad_mcp/service.py +190 -0
  175. package/mcp_servers/openscad-mcp-server/tests/conftest.py +17 -0
  176. package/mcp_servers/openscad-mcp-server/tests/test_engine.py +37 -0
  177. package/mcp_servers/openscad-mcp-server/tests/test_geometry.py +106 -0
  178. package/mcp_servers/openscad-mcp-server/tests/test_integration.py +172 -0
  179. package/mcp_servers/openscad-mcp-server/tests/test_transports.py +314 -0
  180. package/mcp_servers/openscad-mcp-server/uv.lock +1123 -0
  181. package/package.json +44 -0
  182. package/scripts/install_mcps.sh +86 -0
  183. package/scripts/openscad_wrapper.sh +62 -0
  184. package/scripts/run_kicad_mcp.sh +25 -0
  185. package/scripts/run_openscad_mcp.sh +41 -0
  186. package/scripts/test_mcp_connection.py +626 -0
  187. package/scripts/test_mcp_connection.sh +168 -0
  188. package/skills/code-first-hardware-design/SKILL.md +260 -0
  189. package/skills/pcb-constraint-definition/SKILL.md +236 -0
  190. package/skills/pcb-layout-routing-automation/SKILL.md +153 -0
  191. package/skills/pcb-validation-dfm-signoff/SKILL.md +194 -0
  192. 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
+ ```"""