@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,1187 @@
1
+ """Device tree generation tools for KiCad MCP Server.
2
+
3
+ This module provides device tree source (.dts) generation capabilities including:
4
+ - GPIO pin configuration extraction
5
+ - I2C/SPI/UART device detection
6
+ - Power domain analysis
7
+ - SOC-specific device tree generation
8
+ """
9
+
10
+ from pathlib import Path
11
+
12
+ from jinja2 import Template
13
+
14
+ from ..parsers.netlist_parser import NetlistParser
15
+ from ..parsers.schematic_parser import SchematicParser
16
+ from ..server import mcp
17
+
18
+ # Device tree compatible strings database
19
+ DEVICE_TREE_BINDINGS = {
20
+ "Sensor": {
21
+ "BMP280": {"compatible": "bosch,bmp280"},
22
+ "BME280": {"compatible": "bosch,bme280"},
23
+ "BMP388": {"compatible": "bosch,bmp388"},
24
+ "LSM6DS3": {"compatible": "st,lsm6ds3"},
25
+ "MPU6050": {"compatible": "invensense,mpu6050"},
26
+ "MPU9250": {"compatible": "invensense,mpu9250"},
27
+ "HTS221": {"compatible": "st,hts221"},
28
+ "LPS22HB": {"compatible": "st,lps22hb"},
29
+ "DHT22": {"compatible": "dht22"},
30
+ "SHT30": {"compatible": "sensirion,sht3x"},
31
+ "MAX31855": {"compatible": "maxim,max31855"},
32
+ },
33
+ "Display": {
34
+ "ST7789": {"compatible": "sitronix,st7789"},
35
+ "ST7735": {"compatible": "sitronix,st7735"},
36
+ "ILI9341": {"compatible": "ilitek,ili9341"},
37
+ "SSD1306": {"compatible": "solomon,ssd1306"},
38
+ "SH1106": {"compatible": "sinowealth,sh1106"},
39
+ },
40
+ "Memory": {
41
+ "AT24C256": {"compatible": "atmel,24c256"},
42
+ "W25Q128": {"compatible": "jedec,spi-nor"},
43
+ "GD25Q16": {"compatible": "jedec,spi-nor"},
44
+ },
45
+ "Wireless": {
46
+ "ESP8266": {"compatible": "espressif,esp8266"},
47
+ "nRF24L01": {"compatible": "nordic,nrf24l01"},
48
+ "SX1278": {"compatible": "semtech,sx1278"},
49
+ },
50
+ "Connectivity": {
51
+ "CP2102": {"compatible": "silabs,cp2102"},
52
+ "FT232RL": {"compatible": "ftdi,ft232rl"},
53
+ "CH340G": {"compatible": "wch,ch340"},
54
+ },
55
+ "Power": {
56
+ "TP4056": {"compatible": "tp4056"},
57
+ "AXP192": {"compatible": "x-powers,axp192"},
58
+ "LP3985": {"compatible": "ti,lp3985"},
59
+ "AP2112": {"compatible": "tps,ap2112"},
60
+ },
61
+ }
62
+
63
+
64
+ # SOC-specific device tree templates
65
+ DEVICE_TREE_TEMPLATES = {
66
+ "stm32f4": """/* Device Tree for STM32F4 SOC */
67
+ /* Auto-generated from KiCad schematic */
68
+
69
+ #include <dt-bindings/gpio/gpio.h>
70
+ #include <dt-bindings/pinctrl/stm32-pinctrl.h>
71
+
72
+ / {
73
+ model = "{{ board_name }}";
74
+ compatible = "st,stm32f4";
75
+
76
+ /* GPIO Configuration */
77
+ {% for pin in gpio_pins %}
78
+ gpio_{{ pin.name }}: gpio@{{ pin.number }} {
79
+ gpio-controller;
80
+ #gpio-cells = <2>;
81
+ status = "okay";
82
+ };
83
+ {% endfor %}
84
+
85
+ /* I2C Devices */
86
+ {% for bus in i2c_buses %}
87
+ i2c{{ bus.number }}: i2c@{{ bus.address }} {
88
+ compatible = "st,stm32-i2c";
89
+ #address-cells = <1>;
90
+ #size-cells = <0>;
91
+ status = "okay";
92
+
93
+ {% for device in bus.devices %}
94
+ {{ device.name }}: {{ device.compatible }}@{{ device.address }} {
95
+ compatible = "{{ device.compatible }}";
96
+ reg = <{{ device.address }}>;
97
+ {% if device.properties %}
98
+ {% for key, value in device.properties.items() %}
99
+ {{ key }} = <{{ value }}>;
100
+ {% endfor %}
101
+ {% endif %}
102
+ };
103
+ {% endfor %}
104
+ };
105
+
106
+ {% endfor %}
107
+
108
+ /* SPI Devices */
109
+ {% for bus in spi_buses %}
110
+ spi{{ bus.number }}: spi@{{ bus.address }} {
111
+ compatible = "st,stm32-spi";
112
+ #address-cells = <1>;
113
+ #size-cells = <0>;
114
+ status = "okay";
115
+
116
+ {% for device in bus.devices %}
117
+ {{ device.name }}: {{ device.compatible }}@{{ device.cs }} {
118
+ compatible = "{{ device.compatible }}";
119
+ reg = <{{ device.cs }}>;
120
+ spi-max-frequency = <{{ device.frequency }}>;
121
+ {% if device.properties %}
122
+ {% for key, value in device.properties.items() %}
123
+ {{ key }} = <{{ value }}>;
124
+ {% endfor %}
125
+ {% endif %}
126
+ };
127
+ {% endfor %}
128
+ };
129
+
130
+ {% endfor %}
131
+
132
+ /* UART Devices */
133
+ {% for uart in uarts %}
134
+ usart{{ uart.number }}: serial@{{ uart.address }} {
135
+ compatible = "st,stm32-usart";
136
+ status = "okay";
137
+ };
138
+ {% endfor %}
139
+ };
140
+ """,
141
+ "esp32": """/* Device Tree for ESP32 SOC */
142
+ /* Auto-generated from KiCad schematic */
143
+
144
+ #include <dt-bindings/gpio/gpio.h>
145
+
146
+ / {
147
+ model = "{{ board_name }}";
148
+ compatible = "espressif,esp32";
149
+
150
+ /* GPIO Configuration */
151
+ {% for pin in gpio_pins %}
152
+ gpio_{{ pin.number }}: gpio@{{ pin.number }} {
153
+ gpio-controller;
154
+ #gpio-cells = <2>;
155
+ status = "okay";
156
+ };
157
+ {% endfor %}
158
+
159
+ /* I2C Devices */
160
+ {% for bus in i2c_buses %}
161
+ i2c{{ bus.number }}: i2c@{{ bus.address }} {
162
+ compatible = "espressif,esp32-i2c";
163
+ #address-cells = <1>;
164
+ #size-cells = <0>;
165
+ status = "okay";
166
+
167
+ {% for device in bus.devices %}
168
+ {{ device.name }}: {{ device.compatible }}@{{ device.address }} {
169
+ compatible = "{{ device.compatible }}";
170
+ reg = <{{ device.address }}>;
171
+ {% if device.properties %}
172
+ {% for key, value in device.properties.items() %}
173
+ {{ key }} = <{{ value }}>;
174
+ {% endfor %}
175
+ {% endif %}
176
+ };
177
+ {% endfor %}
178
+ };
179
+
180
+ {% endfor %}
181
+
182
+ /* SPI Devices */
183
+ {% for bus in spi_buses %}
184
+ spi{{ bus.number }}: spi@{{ bus.address }} {
185
+ compatible = "espressif,esp32-spi";
186
+ #address-cells = <1>;
187
+ #size-cells = <0>;
188
+ status = "okay";
189
+
190
+ {% for device in bus.devices %}
191
+ {{ device.name }}: {{ device.compatible }}@{{ device.cs }} {
192
+ compatible = "{{ device.compatible }}";
193
+ reg = <{{ device.cs }}>;
194
+ spi-max-frequency = <{{ device.frequency }}>;
195
+ {% if device.properties %}
196
+ {% for key, value in device.properties.items() %}
197
+ {{ key }} = <{{ value }}>;
198
+ {% endfor %}
199
+ {% endif %}
200
+ };
201
+ {% endfor %}
202
+ };
203
+
204
+ {% endfor %}
205
+ };
206
+ """,
207
+ "nrf52": """/* Device Tree for nRF52 SOC */
208
+ /* Auto-generated from KiCad schematic */
209
+
210
+ #include <dt-bindings/gpio/gpio.h>
211
+
212
+ / {
213
+ model = "{{ board_name }}";
214
+ compatible = "nordic,nrf52";
215
+
216
+ /* GPIO Configuration */
217
+ {% for pin in gpio_pins %}
218
+ gpio_{{ pin.number }}: gpio@{{ pin.number }} {
219
+ gpio-controller;
220
+ #gpio-cells = <2>;
221
+ status = "okay";
222
+ };
223
+ {% endfor %}
224
+
225
+ /* I2C Devices */
226
+ {% for bus in i2c_buses %}
227
+ i2c{{ bus.number }}: i2c@{{ bus.address }} {
228
+ compatible = "nordic,nrf52-i2c";
229
+ #address-cells = <1>;
230
+ #size-cells = <0>;
231
+ status = "okay";
232
+
233
+ {% for device in bus.devices %}
234
+ {{ device.name }}: {{ device.compatible }}@{{ device.address }} {
235
+ compatible = "{{ device.compatible }}";
236
+ reg = <{{ device.address }}>;
237
+ {% if device.properties %}
238
+ {% for key, value in device.properties.items() %}
239
+ {{ key }} = <{{ value }}>;
240
+ {% endfor %}
241
+ {% endif %}
242
+ };
243
+ {% endfor %}
244
+ };
245
+
246
+ {% endfor %}
247
+
248
+ /* SPI Devices */
249
+ {% for bus in spi_buses %}
250
+ spi{{ bus.number }}: spi@{{ bus.address }} {
251
+ compatible = "nordic,nrf52-spi";
252
+ #address-cells = <1>;
253
+ #size-cells = <0>;
254
+ status = "okay";
255
+
256
+ {% for device in bus.devices %}
257
+ {{ device.name }}: {{ device.compatible }}@{{ device.cs }} {
258
+ compatible = "{{ device.compatible }}";
259
+ reg = <{{ device.cs }}>;
260
+ spi-max-frequency = <{{ device.frequency }}>;
261
+ {% if device.properties %}
262
+ {% for key, value in device.properties.items() %}
263
+ {{ key }} = <{{ value }}>;
264
+ {% endfor %}
265
+ {% endif %}
266
+ };
267
+ {% endfor %}
268
+ };
269
+
270
+ {% endfor %}
271
+ };
272
+ """,
273
+ }
274
+
275
+
276
+ def _find_component_binding(component_value: str, component_type: str) -> dict | None:
277
+ """Find device tree binding for a component.
278
+
279
+ Args:
280
+ component_value: Component value/part number
281
+ component_type: Component category (Sensor, Display, etc.)
282
+
283
+ Returns:
284
+ Device tree binding dict or None if not found
285
+ """
286
+ if not component_value or not component_type:
287
+ return None
288
+
289
+ component_value_upper = component_value.upper()
290
+
291
+ # Try exact match first
292
+ if component_type in DEVICE_TREE_BINDINGS:
293
+ if component_value_upper in DEVICE_TREE_BINDINGS[component_type]:
294
+ return DEVICE_TREE_BINDINGS[component_type][component_value_upper]
295
+
296
+ # Try partial match
297
+ for key, value in DEVICE_TREE_BINDINGS[component_type].items():
298
+ if key in component_value_upper or component_value_upper in key:
299
+ return value
300
+
301
+ return None
302
+
303
+
304
+ def _infer_peripheral_type(net_name: str) -> str | None:
305
+ """Infer peripheral type from net name.
306
+
307
+ Args:
308
+ net_name: Net name to analyze
309
+
310
+ Returns:
311
+ Peripheral type (I2C, SPI, UART, GPIO) or None
312
+ """
313
+ if not net_name:
314
+ return None
315
+
316
+ net_name_upper = net_name.upper()
317
+
318
+ if "I2C" in net_name_upper or "TWI" in net_name_upper:
319
+ return "I2C"
320
+ elif "SPI" in net_name_upper:
321
+ return "SPI"
322
+ elif "UART" in net_name_upper or "USART" in net_name_upper or "SERIAL" in net_name_upper:
323
+ return "UART"
324
+ elif "GPIO" in net_name_upper or "IO" in net_name_upper:
325
+ return "GPIO"
326
+
327
+ return None
328
+
329
+
330
+ def _extract_i2c_address_from_net(net_name: str) -> int | None:
331
+ """Extract I2C address from net name.
332
+
333
+ Args:
334
+ net_name: Net name (e.g., I2C_SDA_0x76)
335
+
336
+ Returns:
337
+ I2C address as integer or None
338
+ """
339
+ import re
340
+
341
+ # Look for hex address pattern
342
+ match = re.search(r"0x[0-9A-Fa-f]+", net_name)
343
+ if match:
344
+ return int(match.group(0), 16)
345
+
346
+ # Look for decimal address pattern
347
+ match = re.search(r"[_\s](\d{2,3})$", net_name)
348
+ if match:
349
+ address = int(match.group(1))
350
+ if 1 <= address <= 127: # Valid I2C address range
351
+ return address
352
+
353
+ return None
354
+
355
+
356
+ @mcp.tool()
357
+ async def generate_device_tree(
358
+ schematic_path: str,
359
+ target_soc: str = "stm32f4",
360
+ output_path: str = "",
361
+ ) -> str:
362
+ """Generate device tree source (.dts) file from schematic.
363
+
364
+ This function analyzes a KiCad schematic and generates a device tree
365
+ source file compatible with the specified SOC family.
366
+
367
+ Args:
368
+ schematic_path: Path to .kicad_sch file
369
+ target_soc: Target SOC family (stm32f4, esp32, nrf52)
370
+ output_path: Optional output file path
371
+
372
+ Returns:
373
+ Generated device tree source code or error message
374
+ """
375
+ try:
376
+ sch_path = Path(schematic_path)
377
+ if not sch_path.exists():
378
+ return f"X **Schematic file not found:** {schematic_path}"
379
+
380
+ # Validate SOC family
381
+ if target_soc not in DEVICE_TREE_TEMPLATES:
382
+ return f"""X **Unsupported SOC Family**
383
+
384
+ **Requested:** {target_soc}
385
+ **Supported:** {', '.join(DEVICE_TREE_TEMPLATES.keys())}
386
+
387
+ Please specify a supported SOC family."""
388
+
389
+ # Parse schematic
390
+ schematic_parser = SchematicParser(str(sch_path))
391
+
392
+ # Obtain a netlist (next to schematic, in temp, or freshly generated).
393
+ # generate_netlist writes to the temp dir — NOT next to the schematic —
394
+ # so we must look there too (previously this always raised FileNotFoundError).
395
+ from .pin_analysis import _ensure_netlist
396
+
397
+ netlist_path = await _ensure_netlist(sch_path)
398
+ if netlist_path is None:
399
+ return "X **Failed to generate netlist for device tree analysis**"
400
+
401
+ # Parse netlist
402
+ netlist_parser = NetlistParser(str(netlist_path))
403
+
404
+ # Extract device tree data
405
+ dt_data = {
406
+ "board_name": sch_path.stem,
407
+ "gpio_pins": [],
408
+ "i2c_buses": [],
409
+ "spi_buses": [],
410
+ "uarts": [],
411
+ }
412
+
413
+ # Analyze components and extract peripheral configuration
414
+ components = list(schematic_parser.get_components(recursive=True))
415
+
416
+ for component in components:
417
+ comp_ref = component.reference
418
+ comp_value = component.value
419
+ # Find device tree binding
420
+ for category, _bindings in DEVICE_TREE_BINDINGS.items():
421
+ binding = _find_component_binding(comp_value, category)
422
+ if binding:
423
+ # Get net connections. trace_connection(ref, pin) returns a
424
+ # dict {net, ...} (not a list); iterate the component's real
425
+ # pins from the netlist instead of guessing pin numbers 1..8.
426
+ connections = []
427
+ nl_comp = netlist_parser.get_components().get(comp_ref)
428
+ pin_numbers = list(nl_comp.pins.keys()) if nl_comp else []
429
+ for pin_num in pin_numbers:
430
+ try:
431
+ pin_conn = netlist_parser.trace_connection(comp_ref, str(pin_num))
432
+ net_name = pin_conn.get("net") if pin_conn else None
433
+ if net_name:
434
+ peripheral_type = _infer_peripheral_type(net_name)
435
+ connections.append({
436
+ "net": net_name,
437
+ "peripheral": peripheral_type,
438
+ "pin": pin_num,
439
+ })
440
+ except Exception:
441
+ continue
442
+
443
+ if connections:
444
+ # Determine peripheral type based on connections
445
+ i2c_nets = [c for c in connections if c["peripheral"] == "I2C"]
446
+ spi_nets = [c for c in connections if c["peripheral"] == "SPI"]
447
+ uart_nets = [c for c in connections if c["peripheral"] == "UART"]
448
+
449
+ if i2c_nets:
450
+ # I2C device
451
+ i2c_address = _extract_i2c_address_from_net(i2c_nets[0]["net"])
452
+
453
+ device = {
454
+ "name": comp_ref,
455
+ "compatible": binding["compatible"],
456
+ "address": i2c_address or 0x00,
457
+ "properties": {},
458
+ }
459
+
460
+ # Add to I2C bus. Bus MMIO base is SoC-specific; left
461
+ # empty instead of wrongly stamping an STM32 address on
462
+ # every target (esp32, nrf52, ...).
463
+ dt_data["i2c_buses"].append({
464
+ "number": 1, # Default to I2C1
465
+ "address": "",
466
+ "devices": [device],
467
+ })
468
+
469
+ elif spi_nets:
470
+ # SPI device
471
+ device = {
472
+ "name": comp_ref,
473
+ "compatible": binding["compatible"],
474
+ "cs": 0, # Default CS
475
+ "frequency": 1000000, # 1 MHz default
476
+ "properties": {},
477
+ }
478
+
479
+ # Add to SPI bus (address left empty — SoC-specific)
480
+ dt_data["spi_buses"].append({
481
+ "number": 1, # Default to SPI1
482
+ "address": "",
483
+ "devices": [device],
484
+ })
485
+
486
+ elif uart_nets:
487
+ # UART device (address left empty — SoC-specific)
488
+ dt_data["uarts"].append({
489
+ "number": 1, # Default to USART1
490
+ "address": "",
491
+ })
492
+
493
+ # Extract GPIO pins
494
+ for component in components:
495
+ comp_ref = component.reference
496
+ comp_value = component.value
497
+
498
+ # Check if this is an MCU
499
+ if any(soc in comp_value.upper() for soc in ["STM32", "ESP32", "NRF"]):
500
+ # Extract GPIO pins from netlist (iterate the MCU's real pins)
501
+ nl_comp = netlist_parser.get_components().get(comp_ref)
502
+ if nl_comp:
503
+ for pin_num in nl_comp.pins:
504
+ try:
505
+ pin_conn = netlist_parser.trace_connection(comp_ref, str(pin_num))
506
+ net_name = pin_conn.get("net") if pin_conn else None
507
+ if net_name and ("GPIO" in net_name.upper() or "IO" in net_name.upper()):
508
+ dt_data["gpio_pins"].append({
509
+ "number": pin_num,
510
+ "name": f"P{pin_num}",
511
+ })
512
+ except Exception:
513
+ continue
514
+
515
+ # Generate device tree from template
516
+ template = Template(DEVICE_TREE_TEMPLATES[target_soc])
517
+ device_tree = template.render(**dt_data)
518
+
519
+ # Save to file if output path specified
520
+ if output_path:
521
+ output = Path(output_path)
522
+ output.parent.mkdir(parents=True, exist_ok=True)
523
+
524
+ with open(output, "w") as f:
525
+ f.write(device_tree)
526
+
527
+ return f"""OK **Device Tree Generated Successfully**
528
+
529
+ **Schematic:** {schematic_path}
530
+ **Target SOC:** {target_soc}
531
+ **Output:** {output_path}
532
+
533
+ ## Device Tree Contents
534
+
535
+ - **GPIO Pins:** {len(dt_data['gpio_pins'])}
536
+ - **I2C Buses:** {len(dt_data['i2c_buses'])}
537
+ - **SPI Buses:** {len(dt_data['spi_buses'])}
538
+ - **UARTs:** {len(dt_data['uarts'])}
539
+
540
+ The device tree source file has been generated and saved to the specified location.
541
+
542
+ ## Next Steps
543
+
544
+ 1. Review and customize the generated device tree
545
+ 2. Compile with device tree compiler: `dtc -I dts -O dtb -o output.dtb {output_path}`
546
+ 3. Test on target hardware
547
+ 4. Iterate as needed"""
548
+
549
+ else:
550
+ # Return device tree content directly
551
+ return f"""OK **Device Tree Generated Successfully**
552
+
553
+ **Schematic:** {schematic_path}
554
+ **Target SOC:** {target_soc}
555
+
556
+ ## Device Tree Contents
557
+
558
+ - **GPIO Pins:** {len(dt_data['gpio_pins'])}
559
+ - **I2C Buses:** {len(dt_data['i2c_buses'])}
560
+ - **SPI Buses:** {len(dt_data['spi_buses'])}
561
+ - **UARTs:** {len(dt_data['uarts'])}
562
+
563
+ ## Generated Device Tree
564
+
565
+ ```dts
566
+ {device_tree}
567
+ ```
568
+
569
+ ## Next Steps
570
+
571
+ 1. Review and customize the generated device tree
572
+ 2. Save to file: `{sch_path.stem}.dts`
573
+ 3. Compile with device tree compiler: `dtc -I dts -O dtb -o output.dtb`
574
+ 4. Test on target hardware"""
575
+
576
+ except Exception as e:
577
+ import traceback
578
+
579
+ return f"""X **Device Tree Generation Failed**
580
+
581
+ **Schematic:** {schematic_path}
582
+ **Target SOC:** {target_soc}
583
+
584
+ **Error:** {str(e)}
585
+
586
+ **Traceback:**
587
+ ```
588
+ {traceback.format_exc()}
589
+ ```"""
590
+
591
+
592
+ @mcp.tool()
593
+ async def extract_gpio_config(
594
+ schematic_path: str,
595
+ soc_family: str = "",
596
+ ) -> str:
597
+ """Extract GPIO pin configurations from schematic.
598
+
599
+ Args:
600
+ schematic_path: Path to .kicad_sch file
601
+ soc_family: Optional SOC family filter
602
+
603
+ Returns:
604
+ GPIO configuration report
605
+ """
606
+ try:
607
+ sch_path = Path(schematic_path)
608
+ if not sch_path.exists():
609
+ return f"X **Schematic file not found:** {schematic_path}"
610
+
611
+ # Obtain netlist
612
+ from .pin_analysis import _ensure_netlist
613
+
614
+ netlist_path = await _ensure_netlist(sch_path)
615
+ if netlist_path is None:
616
+ return "X **Failed to generate netlist for GPIO extraction**"
617
+
618
+ netlist_parser = NetlistParser(str(netlist_path))
619
+ schematic_parser = SchematicParser(str(sch_path))
620
+
621
+ # pin number -> name from schematic lib_symbols
622
+ pin_name_of: dict[tuple[str, str], str] = {}
623
+ for c in schematic_parser.get_components(recursive=True):
624
+ for p in c.pins:
625
+ pin_name_of[(c.reference, str(p.get("number", "")))] = p.get("name", "")
626
+
627
+ # Extract GPIO configurations
628
+ gpio_configs = []
629
+
630
+ # NetlistParser.get_components() returns dict[ref, NetlistComponent];
631
+ # there is no get_all_components() (previous call always crashed).
632
+ for comp_ref, comp in netlist_parser.get_components().items():
633
+ comp_value = comp.value
634
+
635
+ # Filter by SOC family if specified
636
+ if soc_family and soc_family.lower() not in comp_value.lower():
637
+ continue
638
+
639
+ # Check if this is an MCU
640
+ if any(soc in comp_value.upper() for soc in ["STM32", "ESP32", "NRF", "ATMEGA"]):
641
+ for pin_num in comp.pins:
642
+ try:
643
+ pin_conn = netlist_parser.trace_connection(comp_ref, str(pin_num))
644
+ net_name = pin_conn.get("net") if pin_conn else None
645
+ if net_name and ("GPIO" in net_name.upper() or "IO" in net_name.upper()):
646
+ gpio_configs.append({
647
+ "component": comp_ref,
648
+ "pin_number": str(pin_num),
649
+ "pin_name": pin_name_of.get((comp_ref, str(pin_num)), str(pin_num)),
650
+ "net": net_name,
651
+ "soc": comp_value,
652
+ })
653
+ except Exception:
654
+ continue
655
+
656
+ if not gpio_configs:
657
+ return f"""WARN **No GPIO Configurations Found**
658
+
659
+ **Schematic:** {schematic_path}
660
+ {'**SOC Family Filter:** ' + soc_family if soc_family else ''}
661
+
662
+ No GPIO configurations were found in the schematic.
663
+
664
+ **Next Steps:**
665
+ 1. Ensure schematic has MCU components
666
+ 2. Check net names contain 'GPIO' or 'IO'
667
+ 3. Try without SOC family filter"""
668
+
669
+ # Format results
670
+ result = f"""# GPIO Configuration Extraction
671
+
672
+ **Schematic:** {schematic_path}
673
+ {'**SOC Family:** ' + soc_family if soc_family else ''}
674
+ **Total GPIO Pins:** {len(gpio_configs)}
675
+
676
+ ## GPIO Pin Details
677
+
678
+ | Component | Pin | Net | SOC |
679
+ |-----------|-----|-----|-----|
680
+ """
681
+
682
+ for config in gpio_configs:
683
+ result += f"| {config['component']} | {config['pin_name']} ({config['pin_number']}) | {config['net']} | {config['soc']} |\n"
684
+
685
+ # Add code generation suggestions
686
+ result += "\n## Code Generation Suggestions\n\n"
687
+
688
+ # Group by SOC
689
+ soc_groups = {}
690
+ for config in gpio_configs:
691
+ soc = config["soc"]
692
+ if soc not in soc_groups:
693
+ soc_groups[soc] = []
694
+ soc_groups[soc].append(config)
695
+
696
+ for soc, configs in soc_groups.items():
697
+ result += f"### {soc}\n\n```c\n"
698
+ result += f"// GPIO configuration for {soc}\n\n"
699
+
700
+ for config in configs[:10]: # Show first 10
701
+ result += f"// {config['pin_name']} ({config['pin_number']}): {config['net']}\n"
702
+ if "STM32" in soc.upper():
703
+ result += f"GPIO_InitTypeDef gpio_{config['pin_name'].lower()};\n"
704
+ result += f"gpio_{config['pin_name'].lower()}.Pin = GPIO_PIN_{config['pin_number']};\n"
705
+ result += f"gpio_{config['pin_name'].lower()}.Mode = GPIO_MODE_OUTPUT_PP;\n"
706
+ result += f"HAL_GPIO_Init(GPIO{config['pin_name'][1]}, &gpio_{config['pin_name'].lower()});\n\n"
707
+ elif "ESP32" in soc.upper():
708
+ result += f"gpio_set_direction(GPIO{config['pin_number']}, GPIO_MODE_OUTPUT);\n\n"
709
+
710
+ if len(configs) > 10:
711
+ result += f"// ... and {len(configs) - 10} more GPIO pins\n"
712
+
713
+ result += "```\n\n"
714
+
715
+ return result
716
+
717
+ except Exception as e:
718
+ import traceback
719
+
720
+ return f"""X **GPIO Configuration Extraction Failed**
721
+
722
+ **Schematic:** {schematic_path}
723
+ {'**SOC Family:** ' + soc_family if soc_family else ''}
724
+
725
+ **Error:** {str(e)}
726
+
727
+ **Traceback:**
728
+ ```
729
+ {traceback.format_exc()}
730
+ ```"""
731
+
732
+
733
+ @mcp.tool()
734
+ async def extract_i2c_devices(
735
+ schematic_path: str,
736
+ ) -> str:
737
+ """Extract I2C bus and device configurations.
738
+
739
+ Args:
740
+ schematic_path: Path to .kicad_sch file
741
+
742
+ Returns:
743
+ I2C device configuration report
744
+ """
745
+ try:
746
+ sch_path = Path(schematic_path)
747
+ if not sch_path.exists():
748
+ return f"X **Schematic file not found:** {schematic_path}"
749
+
750
+ # Obtain netlist
751
+ from .pin_analysis import _ensure_netlist
752
+
753
+ netlist_path = await _ensure_netlist(sch_path)
754
+ if netlist_path is None:
755
+ return "X **Failed to generate netlist for I2C extraction**"
756
+
757
+ netlist_parser = NetlistParser(str(netlist_path))
758
+ schematic_parser = SchematicParser(str(sch_path))
759
+
760
+ # Extract I2C devices
761
+ i2c_devices = []
762
+
763
+ components = list(schematic_parser.get_components(recursive=True))
764
+
765
+ for component in components:
766
+ comp_ref = component.reference
767
+ comp_value = component.value
768
+ # Check if this is an I2C device
769
+ for category, _bindings in DEVICE_TREE_BINDINGS.items():
770
+ binding = _find_component_binding(comp_value, category)
771
+ if binding:
772
+ nl_comp = netlist_parser.get_components().get(comp_ref)
773
+ pin_numbers = list(nl_comp.pins.keys()) if nl_comp else []
774
+ for pin_num in pin_numbers:
775
+ try:
776
+ pin_conn = netlist_parser.trace_connection(comp_ref, str(pin_num))
777
+ net_name = pin_conn.get("net") if pin_conn else None
778
+ if net_name and ("I2C" in net_name.upper() or "TWI" in net_name.upper()):
779
+ i2c_address = _extract_i2c_address_from_net(net_name)
780
+ i2c_devices.append({
781
+ "component": comp_ref,
782
+ "value": comp_value,
783
+ "compatible": binding["compatible"],
784
+ "address": i2c_address,
785
+ "net": net_name,
786
+ "pin": pin_num,
787
+ })
788
+ break
789
+ except Exception:
790
+ continue
791
+
792
+ if not i2c_devices:
793
+ return f"""WARN **No I2C Devices Found**
794
+
795
+ **Schematic:** {schematic_path}
796
+
797
+ No I2C devices were found in the schematic.
798
+
799
+ **Next Steps:**
800
+ 1. Ensure schematic has I2C peripherals
801
+ 2. Check net names contain 'I2C' or 'TWI'
802
+ 3. Include I2C addresses in net names (e.g., I2C_SDA_0x76)"""
803
+
804
+ # Format results
805
+ result = f"""# I2C Device Extraction
806
+
807
+ **Schematic:** {schematic_path}
808
+ **Total I2C Devices:** {len(i2c_devices)}
809
+
810
+ ## I2C Device Details
811
+
812
+ | Component | Device | Compatible | Address | Net |
813
+ |-----------|--------|------------|---------|-----|
814
+ """
815
+
816
+ for device in i2c_devices:
817
+ address_str = f"0x{device['address']:02X}" if device['address'] else "Unknown"
818
+ result += f"| {device['component']} | {device['value']} | {device['compatible']} | {address_str} | {device['net']} |\n"
819
+
820
+ # Add device tree generation
821
+ result += "\n## Device Tree Configuration\n\n```dts\n"
822
+ result += "&i2c1 {\n"
823
+ result += " status = \"okay\";\n\n"
824
+
825
+ for device in i2c_devices:
826
+ address_str = f"0x{device['address']:02X}" if device['address'] else "0x00"
827
+ result += f" {device['component'].lower()}: {device['compatible']}@{address_str} {{\n"
828
+ result += f" compatible = \"{device['compatible']}\";\n"
829
+ result += f" reg = <{address_str}>;\n"
830
+ result += " };\n\n"
831
+
832
+ result += "};\n```"
833
+
834
+ return result
835
+
836
+ except Exception as e:
837
+ import traceback
838
+
839
+ return f"""X **I2C Device Extraction Failed**
840
+
841
+ **Schematic:** {schematic_path}
842
+
843
+ **Error:** {str(e)}
844
+
845
+ **Traceback:**
846
+ ```
847
+ {traceback.format_exc()}
848
+ ```"""
849
+
850
+
851
+ @mcp.tool()
852
+ async def extract_spi_devices(
853
+ schematic_path: str,
854
+ ) -> str:
855
+ """Extract SPI bus and device configurations.
856
+
857
+ Args:
858
+ schematic_path: Path to .kicad_sch file
859
+
860
+ Returns:
861
+ SPI device configuration report
862
+ """
863
+ try:
864
+ sch_path = Path(schematic_path)
865
+ if not sch_path.exists():
866
+ return f"X **Schematic file not found:** {schematic_path}"
867
+
868
+ # Obtain netlist
869
+ from .pin_analysis import _ensure_netlist
870
+
871
+ netlist_path = await _ensure_netlist(sch_path)
872
+ if netlist_path is None:
873
+ return "X **Failed to generate netlist for SPI extraction**"
874
+
875
+ netlist_parser = NetlistParser(str(netlist_path))
876
+ schematic_parser = SchematicParser(str(sch_path))
877
+
878
+ # Extract SPI devices
879
+ spi_devices = []
880
+
881
+ components = list(schematic_parser.get_components(recursive=True))
882
+
883
+ for component in components:
884
+ comp_ref = component.reference
885
+ comp_value = component.value
886
+
887
+ # Check if this is an SPI device
888
+ for category, _bindings in DEVICE_TREE_BINDINGS.items():
889
+ binding = _find_component_binding(comp_value, category)
890
+ if binding:
891
+ nl_comp = netlist_parser.get_components().get(comp_ref)
892
+ pin_numbers = list(nl_comp.pins.keys()) if nl_comp else []
893
+ for pin_num in pin_numbers:
894
+ try:
895
+ pin_conn = netlist_parser.trace_connection(comp_ref, str(pin_num))
896
+ net_name = pin_conn.get("net") if pin_conn else None
897
+ if net_name and "SPI" in net_name.upper():
898
+ spi_devices.append({
899
+ "component": comp_ref,
900
+ "value": comp_value,
901
+ "compatible": binding["compatible"],
902
+ "net": net_name,
903
+ "pin": pin_num,
904
+ })
905
+ break
906
+ except Exception:
907
+ continue
908
+
909
+ if not spi_devices:
910
+ return f"""WARN **No SPI Devices Found**
911
+
912
+ **Schematic:** {schematic_path}
913
+
914
+ No SPI devices were found in the schematic.
915
+
916
+ **Next Steps:**
917
+ 1. Ensure schematic has SPI peripherals
918
+ 2. Check net names contain 'SPI'
919
+ 3. Include CS signals in net naming"""
920
+
921
+ # Format results
922
+ result = f"""# SPI Device Extraction
923
+
924
+ **Schematic:** {schematic_path}
925
+ **Total SPI Devices:** {len(spi_devices)}
926
+
927
+ ## SPI Device Details
928
+
929
+ | Component | Device | Compatible | Net |
930
+ |-----------|--------|------------|-----|
931
+ """
932
+
933
+ for device in spi_devices:
934
+ result += f"| {device['component']} | {device['value']} | {device['compatible']} | {device['net']} |\n"
935
+
936
+ # Add device tree generation
937
+ result += "\n## Device Tree Configuration\n\n```dts\n"
938
+ result += "&spi1 {\n"
939
+ result += " status = \"okay\";\n\n"
940
+
941
+ for i, device in enumerate(spi_devices):
942
+ result += f" {device['component'].lower()}: {device['compatible']}@{i} {{\n"
943
+ result += f" compatible = \"{device['compatible']}\";\n"
944
+ result += f" reg = <{i}>;\n"
945
+ result += " spi-max-frequency = <1000000>;\n"
946
+ result += " };\n\n"
947
+
948
+ result += "};\n```"
949
+
950
+ return result
951
+
952
+ except Exception as e:
953
+ import traceback
954
+
955
+ return f"""X **SPI Device Extraction Failed**
956
+
957
+ **Schematic:** {schematic_path}
958
+
959
+ **Error:** {str(e)}
960
+
961
+ **Traceback:**
962
+ ```
963
+ {traceback.format_exc()}
964
+ ```"""
965
+
966
+
967
+ @mcp.tool()
968
+ async def extract_power_domains(
969
+ schematic_path: str,
970
+ ) -> str:
971
+ """Extract power domain and regulator configurations.
972
+
973
+ Args:
974
+ schematic_path: Path to .kicad_sch file
975
+
976
+ Returns:
977
+ Power domain configuration report
978
+ """
979
+ try:
980
+ sch_path = Path(schematic_path)
981
+ if not sch_path.exists():
982
+ return f"X **Schematic file not found:** {schematic_path}"
983
+
984
+ # Parse schematic
985
+ schematic_parser = SchematicParser(str(sch_path))
986
+
987
+ # Extract power components
988
+ power_components = []
989
+
990
+ components = list(schematic_parser.get_components(recursive=True))
991
+
992
+ for component in components:
993
+ comp_ref = component.reference
994
+ comp_value = component.value
995
+ comp_library = component.library_id
996
+
997
+ # Check if this is a power-related component
998
+ power_keywords = ["REG", "LDO", "BUCK", "BOOST", "TPS", "AP", "LP", "AXP", "PMIC", "NPM"]
999
+ if any(keyword in comp_value.upper() for keyword in power_keywords):
1000
+ power_components.append({
1001
+ "reference": comp_ref,
1002
+ "value": comp_value,
1003
+ "library": comp_library,
1004
+ })
1005
+
1006
+ if not power_components:
1007
+ return f"""WARN **No Power Components Found**
1008
+
1009
+ **Schematic:** {schematic_path}
1010
+
1011
+ No power management components were found in the schematic.
1012
+
1013
+ **Next Steps:**
1014
+ 1. Check for regulators, LDOs, power ICs
1015
+ 2. Verify component values contain power-related keywords"""
1016
+
1017
+ # Format results
1018
+ result = f"""# Power Domain Extraction
1019
+
1020
+ **Schematic:** {schematic_path}
1021
+ **Total Power Components:** {len(power_components)}
1022
+
1023
+ ## Power Components
1024
+
1025
+ | Reference | Component | Type |
1026
+ |-----------|-----------|------|
1027
+ """
1028
+
1029
+ for comp in power_components:
1030
+ # Determine component type
1031
+ comp_type = "Regulator"
1032
+ if "LDO" in comp["value"].upper():
1033
+ comp_type = "LDO Regulator"
1034
+ elif "BUCK" in comp["value"].upper():
1035
+ comp_type = "Buck Converter"
1036
+ elif "BOOST" in comp["value"].upper():
1037
+ comp_type = "Boost Converter"
1038
+ elif "PMIC" in comp["value"].upper() or "NPM" in comp["value"].upper():
1039
+ comp_type = "PMIC"
1040
+
1041
+ result += f"| {comp['reference']} | {comp['value']} | {comp_type} |\n"
1042
+
1043
+ # Add device tree generation suggestions
1044
+ result += "\n## Device Tree Configuration\n\n```dts\n"
1045
+ result += "/ {\n"
1046
+ result += " regulators {\n\n"
1047
+
1048
+ for comp in power_components[:5]: # Limit to first 5
1049
+ result += f" {comp['reference'].lower()}: {comp['reference'].lower()} {{\n"
1050
+ result += " compatible = \"regulator-fixed\";\n"
1051
+ result += " regulator-name = \"" + comp['reference'].lower() + "\";\n"
1052
+ result += " regulator-min-microvolt = <3300000>;\n"
1053
+ result += " regulator-max-microvolt = <3300000>;\n"
1054
+ result += " };\n\n"
1055
+
1056
+ if len(power_components) > 5:
1057
+ result += " // ... and " + str(len(power_components) - 5) + " more regulators\n"
1058
+
1059
+ result += " };\n"
1060
+ result += "};\n```"
1061
+
1062
+ return result
1063
+
1064
+ except Exception as e:
1065
+ import traceback
1066
+
1067
+ return f"""X **Power Domain Extraction Failed**
1068
+
1069
+ **Schematic:** {schematic_path}
1070
+
1071
+ **Error:** {str(e)}
1072
+
1073
+ **Traceback:**
1074
+ ```
1075
+ {traceback.format_exc()}
1076
+ ```"""
1077
+
1078
+
1079
+ @mcp.tool()
1080
+ async def validate_pin_configuration(
1081
+ schematic_path: str,
1082
+ ) -> str:
1083
+ """Validate pin configuration conflicts for device tree generation.
1084
+
1085
+ This function validates that the pin configuration is suitable for
1086
+ device tree generation and reports any conflicts or issues.
1087
+
1088
+ Args:
1089
+ schematic_path: Path to .kicad_sch file
1090
+
1091
+ Returns:
1092
+ Validation report with conflicts and recommendations
1093
+ """
1094
+ try:
1095
+ sch_path = Path(schematic_path)
1096
+ if not sch_path.exists():
1097
+ return f"X **Schematic file not found:** {schematic_path}"
1098
+
1099
+ # First, run pin conflict detection
1100
+ from .pin_analysis import detect_pin_conflicts
1101
+
1102
+ conflict_result = await detect_pin_conflicts(str(sch_path))
1103
+
1104
+ if "X" in conflict_result:
1105
+ return f"""X **Pin Configuration Validation Failed**
1106
+
1107
+ The schematic has pin conflicts that must be resolved before
1108
+ device tree generation.
1109
+
1110
+ {conflict_result}
1111
+
1112
+ ## Device Tree Generation Blocked
1113
+
1114
+ Pin conflicts prevent reliable device tree generation. Please
1115
+ resolve all conflicts before attempting device tree generation."""
1116
+
1117
+ # Check for MCU components
1118
+ schematic_parser = SchematicParser(str(sch_path))
1119
+ components = list(schematic_parser.get_components(recursive=True))
1120
+
1121
+ mcu_found = False
1122
+ for component in components:
1123
+ comp_value = component.value
1124
+ if any(soc in comp_value.upper() for soc in ["STM32", "ESP32", "NRF", "ATMEGA"]):
1125
+ mcu_found = True
1126
+ break
1127
+
1128
+ if not mcu_found:
1129
+ return f"""WARN **No MCU Component Found**
1130
+
1131
+ **Schematic:** {schematic_path}
1132
+
1133
+ Device tree generation requires an MCU component in the schematic.
1134
+
1135
+ **Supported MCU families:**
1136
+ - STM32 (STM32F, STM32H, STM32L series)
1137
+ - ESP32 (ESP32, ESP32-S2, ESP32-S3, etc.)
1138
+ - nRF52 (nRF52832, nRF52840, etc.)
1139
+ - ATmega (ATmega328P, ATmega2560, etc.)
1140
+
1141
+ **Next Steps:**
1142
+ 1. Add an MCU component to the schematic
1143
+ 2. Verify component value matches supported patterns
1144
+ 3. Re-run validation"""
1145
+
1146
+ return f"""OK **Pin Configuration Validation Passed**
1147
+
1148
+ **Schematic:** {schematic_path}
1149
+
1150
+ The schematic is ready for device tree generation.
1151
+
1152
+ ## Validation Results
1153
+
1154
+ - OK No pin conflicts detected
1155
+ - OK MCU component found
1156
+ - OK Net connections valid
1157
+ - OK Pin assignments compatible
1158
+
1159
+ ## Ready for Device Tree Generation
1160
+
1161
+ You can now proceed with device tree generation:
1162
+
1163
+ ```bash
1164
+ # Generate device tree
1165
+ generate_device_tree(
1166
+ schematic_path="{schematic_path}",
1167
+ target_soc="stm32f4",
1168
+ output_path="{sch_path.stem}.dts"
1169
+ )
1170
+ ```
1171
+
1172
+ **Supported SOC families:** {', '.join(DEVICE_TREE_TEMPLATES.keys())}"""
1173
+
1174
+ except Exception as e:
1175
+ import traceback
1176
+
1177
+ return f"""X **Validation Failed**
1178
+
1179
+ **Schematic:** {schematic_path}
1180
+
1181
+ **Error:** {str(e)}
1182
+
1183
+ **Traceback:**
1184
+ ```
1185
+ {traceback.format_exc()}
1186
+ ```
1187
+ """