wirestudio 0.44.0__tar.gz → 0.45.0__tar.gz

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 (428) hide show
  1. {wirestudio-0.44.0/wirestudio.egg-info → wirestudio-0.45.0}/PKG-INFO +1 -1
  2. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_api_route.py +6 -0
  3. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_community.py +63 -6
  4. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_compatibility.py +83 -19
  5. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_kicad.py +23 -12
  6. wirestudio-0.45.0/tests/test_kicad_drc.py +112 -0
  7. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_kicad_pcb.py +35 -8
  8. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_kicad_route.py +73 -1
  9. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_migrate.py +50 -0
  10. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/__init__.py +1 -1
  11. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/agent/tools.py +55 -6
  12. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/api/app.py +36 -2
  13. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/api/community.py +44 -7
  14. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/csp/compatibility.py +37 -0
  15. wirestudio-0.45.0/wirestudio/kicad/drc.py +145 -0
  16. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/kicad/fab.py +3 -3
  17. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/kicad/generator.py +27 -14
  18. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/kicad/netlist.py +58 -13
  19. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/kicad/pcb.py +29 -1
  20. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/kicad/pcbnew_bridge.py +5 -1
  21. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/kicad/route.py +138 -17
  22. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/__init__.py +6 -0
  23. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/boards/ttgo-lora32-v2.yaml +8 -2
  24. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/check.py +5 -0
  25. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/apa102.yaml +2 -2
  26. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/bh1750.yaml +1 -1
  27. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/esp32_rmt_led_strip.yaml +1 -1
  28. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/hc-sr501.yaml +1 -1
  29. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/hlw8012.yaml +3 -3
  30. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/ili9xxx.yaml +3 -3
  31. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/ld2410.yaml +1 -1
  32. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/max98357a.yaml +1 -1
  33. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/mcp23008.yaml +1 -1
  34. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/mcp23017.yaml +1 -1
  35. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/mcp23s17.yaml +1 -1
  36. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/pca9685.yaml +1 -1
  37. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/pcf8574.yaml +1 -1
  38. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/pmsx003.yaml +1 -1
  39. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/rc522.yaml +1 -1
  40. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/rcwl-0516.yaml +1 -1
  41. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/remote_receiver.yaml +3 -2
  42. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/servo.yaml +1 -1
  43. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/sn74hc595.yaml +4 -4
  44. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/ssd1306.yaml +1 -1
  45. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/st7789.yaml +3 -3
  46. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/sx127x.yaml +2 -2
  47. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/tm1637.yaml +2 -2
  48. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/vl53l0x.yaml +2 -2
  49. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/wiegand.yaml +2 -2
  50. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/ws2812b.yaml +1 -1
  51. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/xpt2046.yaml +1 -1
  52. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/mcp/server.py +12 -3
  53. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/migrate.py +59 -2
  54. {wirestudio-0.44.0 → wirestudio-0.45.0/wirestudio.egg-info}/PKG-INFO +1 -1
  55. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio.egg-info/SOURCES.txt +2 -0
  56. {wirestudio-0.44.0 → wirestudio-0.45.0}/LICENSE +0 -0
  57. {wirestudio-0.44.0 → wirestudio-0.45.0}/LICENSES.md +0 -0
  58. {wirestudio-0.44.0 → wirestudio-0.45.0}/README.md +0 -0
  59. {wirestudio-0.44.0 → wirestudio-0.45.0}/pyproject.toml +0 -0
  60. {wirestudio-0.44.0 → wirestudio-0.45.0}/setup.cfg +0 -0
  61. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_active_design.py +0 -0
  62. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_agent.py +0 -0
  63. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_agent_eval.py +0 -0
  64. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_agent_stream.py +0 -0
  65. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_api.py +0 -0
  66. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_api_auth.py +0 -0
  67. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_ascii_gen.py +0 -0
  68. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_build_backend.py +0 -0
  69. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_check_upgrade.py +0 -0
  70. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_chirpstack.py +0 -0
  71. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_circuitpython.py +0 -0
  72. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_codec.py +0 -0
  73. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_compile.py +0 -0
  74. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_component_corpus.py +0 -0
  75. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_connectors.py +0 -0
  76. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_coverage_baseline.py +0 -0
  77. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_design_events.py +0 -0
  78. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_designs.py +0 -0
  79. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_designs_seed.py +0 -0
  80. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_display_intent.py +0 -0
  81. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_electrical.py +0 -0
  82. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_enclosure.py +0 -0
  83. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_enclosure_gate.py +0 -0
  84. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_enclosure_search.py +0 -0
  85. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_errors.py +0 -0
  86. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_esphome_dashboard.py +0 -0
  87. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_esphome_support.py +0 -0
  88. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_firmware_gen.py +0 -0
  89. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_fleet.py +0 -0
  90. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_footprints.py +0 -0
  91. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_framework_baseline.py +0 -0
  92. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_hardware_gate.py +0 -0
  93. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_intent.py +0 -0
  94. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_inventory.py +0 -0
  95. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_jlcpcb.py +0 -0
  96. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_kicad_erc.py +0 -0
  97. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_kicad_fab.py +0 -0
  98. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_kicad_import.py +0 -0
  99. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_kicad_render.py +0 -0
  100. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_kicad_sch.py +0 -0
  101. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_library_check.py +0 -0
  102. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_library_hbridge.py +0 -0
  103. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_library_rtttl.py +0 -0
  104. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_lorawan.py +0 -0
  105. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_lorawan_api.py +0 -0
  106. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_lorawan_sugar_migration.py +0 -0
  107. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_mcp_auth.py +0 -0
  108. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_mcp_client.py +0 -0
  109. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_mcp_hardware.py +0 -0
  110. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_mcp_jobs.py +0 -0
  111. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_mcp_resources.py +0 -0
  112. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_mcp_server.py +0 -0
  113. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_meshtastic.py +0 -0
  114. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_micropython.py +0 -0
  115. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_micropython_registers.py +0 -0
  116. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_modules.py +0 -0
  117. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_pin_protocols.py +0 -0
  118. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_pin_solver.py +0 -0
  119. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_printwarden.py +0 -0
  120. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_python_blocks.py +0 -0
  121. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_recommend.py +0 -0
  122. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_register_code.py +0 -0
  123. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_rf_ook.py +0 -0
  124. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_schema.py +0 -0
  125. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_schema_doc.py +0 -0
  126. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_schematics_gate.py +0 -0
  127. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_security_cors.py +0 -0
  128. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_seed_onboard.py +0 -0
  129. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_serve.py +0 -0
  130. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_sqlite_store.py +0 -0
  131. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_sqlite_stores.py +0 -0
  132. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_strict.py +0 -0
  133. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_targets.py +0 -0
  134. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_tasmota.py +0 -0
  135. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_tbeam_lorawan_example.py +0 -0
  136. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_tools_add_bus.py +0 -0
  137. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_tools_add_component.py +0 -0
  138. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_tools_solve_pins.py +0 -0
  139. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_uart_frames.py +0 -0
  140. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_validate.py +0 -0
  141. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_wled.py +0 -0
  142. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_workbench.py +0 -0
  143. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_workbench_boot.py +0 -0
  144. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_workbench_images.py +0 -0
  145. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_workbench_provision.py +0 -0
  146. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_workbench_serial.py +0 -0
  147. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_ws2812_method.py +0 -0
  148. {wirestudio-0.44.0 → wirestudio-0.45.0}/tests/test_yaml_gen.py +0 -0
  149. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/agent/__init__.py +0 -0
  150. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/agent/agent.py +0 -0
  151. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/agent/eval.py +0 -0
  152. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/agent/eval_tasks.yaml +0 -0
  153. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/agent/session.py +0 -0
  154. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/api/__init__.py +0 -0
  155. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/api/__main__.py +0 -0
  156. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/api/auth.py +0 -0
  157. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/api/circuitpython.py +0 -0
  158. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/api/design_input.py +0 -0
  159. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/api/meshtastic.py +0 -0
  160. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/api/micropython.py +0 -0
  161. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/api/schemas.py +0 -0
  162. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/api/serve.py +0 -0
  163. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/api/workbench.py +0 -0
  164. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/csp/__init__.py +0 -0
  165. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/csp/pin_solver.py +0 -0
  166. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/designs/__init__.py +0 -0
  167. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/designs/active.py +0 -0
  168. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/designs/events.py +0 -0
  169. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/designs/seed.py +0 -0
  170. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/designs/store.py +0 -0
  171. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/enclosure/__init__.py +0 -0
  172. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/enclosure/openscad.py +0 -0
  173. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/enclosure/search.py +0 -0
  174. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/enclosure/yapp/LICENSE +0 -0
  175. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/enclosure/yapp/YAPPgenerator_v3.scad +0 -0
  176. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/errors.py +0 -0
  177. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/esphome_dashboard/__init__.py +0 -0
  178. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/__init__.py +0 -0
  179. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/access-panel.json +0 -0
  180. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/actuator-harness.json +0 -0
  181. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/air-quality-station.json +0 -0
  182. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/analog-node.json +0 -0
  183. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/atom-echo.json +0 -0
  184. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/atom-lite.json +0 -0
  185. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/atom-matrix.json +0 -0
  186. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/atoms3-lcd.json +0 -0
  187. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/atoms3-lite.json +0 -0
  188. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/atoms3-onboard.json +0 -0
  189. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/atomu-onboard.json +0 -0
  190. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/atomu.json +0 -0
  191. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/attic-logger.json +0 -0
  192. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/awning-control.json +0 -0
  193. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/bench-io.json +0 -0
  194. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/bl0906-mainmeter.json +0 -0
  195. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/blind-stepper.json +0 -0
  196. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/bluemotion.json +0 -0
  197. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/bluesonoff.json +0 -0
  198. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/button-toggles-light.json +0 -0
  199. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/c3-supermini.json +0 -0
  200. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/c6-supermini.json +0 -0
  201. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/co2-display.json +0 -0
  202. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/desk-climate.json +0 -0
  203. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/desk-matrix.json +0 -0
  204. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/distance-sensor.json +0 -0
  205. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/encoder-drives-stepper.json +0 -0
  206. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/energy-monitor.json +0 -0
  207. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/esp32-audio.json +0 -0
  208. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/esp32cam-wrover.json +0 -0
  209. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/esp32cam.json +0 -0
  210. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/flow-meter.json +0 -0
  211. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/garage-motion.json +0 -0
  212. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/grill-probe.json +0 -0
  213. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/guarded-button-light.json +0 -0
  214. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/heltec-v4-gnss-tracker.json +0 -0
  215. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/keypad.json +0 -0
  216. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/kitchen-scale.json +0 -0
  217. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/led-display.json +0 -0
  218. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/lorawan-battery-uplink.json +0 -0
  219. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/motion-turns-on-light.json +0 -0
  220. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/motor-position.json +0 -0
  221. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/multi-temp.json +0 -0
  222. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/nextion-thermostat.json +0 -0
  223. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/oled-knob.json +0 -0
  224. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/oled.json +0 -0
  225. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/output-hub.json +0 -0
  226. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/parking-distance.json +0 -0
  227. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/presence-media-node.json +0 -0
  228. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/presence-rf.json +0 -0
  229. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/rc522.json +0 -0
  230. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/room-climate.json +0 -0
  231. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/rs485-energy.json +0 -0
  232. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/s3-devkitc.json +0 -0
  233. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/s3-supermini.json +0 -0
  234. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/securitypanel.json +0 -0
  235. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/smart-plug-v1.json +0 -0
  236. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/smart-plug.json +0 -0
  237. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/solder-fan.json +0 -0
  238. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/spi-io-node.json +0 -0
  239. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/subghz-rfid.json +0 -0
  240. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/t-beam-lorawan.json +0 -0
  241. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/t-beam.json +0 -0
  242. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/temp-above-turns-on-fan.json +0 -0
  243. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/temp-turns-on-fan.json +0 -0
  244. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/tft-touch.json +0 -0
  245. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/ttgo-lora32.json +0 -0
  246. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/tuya-smart-plug.json +0 -0
  247. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/usbc-led-strip.json +0 -0
  248. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/vent-damper.json +0 -0
  249. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/wasserpir.json +0 -0
  250. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/weather-station.json +0 -0
  251. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/wemosgps.json +0 -0
  252. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/wled-sensor-strip.json +0 -0
  253. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/examples/xiao-grove-climate.json +0 -0
  254. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/fleet/__init__.py +0 -0
  255. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/fleet/client.py +0 -0
  256. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/generate/__init__.py +0 -0
  257. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/generate/__main__.py +0 -0
  258. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/generate/ascii_gen.py +0 -0
  259. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/generate/circuitpython_gen.py +0 -0
  260. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/generate/display_intent.py +0 -0
  261. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/generate/micropython_gen.py +0 -0
  262. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/generate/yaml_gen.py +0 -0
  263. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/intent.py +0 -0
  264. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/inventory/__init__.py +0 -0
  265. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/inventory/buy.py +0 -0
  266. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/inventory/check.py +0 -0
  267. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/inventory/csv_io.py +0 -0
  268. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/inventory/match.py +0 -0
  269. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/inventory/store.py +0 -0
  270. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/jlcpcb/__init__.py +0 -0
  271. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/jlcpcb/__main__.py +0 -0
  272. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/jlcpcb/check.py +0 -0
  273. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/jlcpcb/client.py +0 -0
  274. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/kicad/__init__.py +0 -0
  275. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/kicad/erc.py +0 -0
  276. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/kicad/import.py +0 -0
  277. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/kicad/importer.py +0 -0
  278. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/kicad/render.py +0 -0
  279. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/kicad/schematic.py +0 -0
  280. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/kicad/symbol_parser.py +0 -0
  281. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/boards/esp01_1m.yaml +0 -0
  282. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/boards/esp32-c3-devkitm-1.yaml +0 -0
  283. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/boards/esp32-c3-supermini.yaml +0 -0
  284. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/boards/esp32-c6-supermini.yaml +0 -0
  285. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/boards/esp32-devkitc-v4.yaml +0 -0
  286. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/boards/esp32-s3-devkitc-1.yaml +0 -0
  287. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/boards/esp32-s3-supermini.yaml +0 -0
  288. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/boards/esp32-wrover-cam.yaml +0 -0
  289. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/boards/esp32cam-ai-thinker.yaml +0 -0
  290. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/boards/esp8285-1m.yaml +0 -0
  291. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/boards/heltec-wifi-lora32-v2.yaml +0 -0
  292. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/boards/heltec-wifi-lora32-v3.yaml +0 -0
  293. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/boards/heltec-wifi-lora32-v4.yaml +0 -0
  294. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/boards/m5stack-atom-echo.yaml +0 -0
  295. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/boards/m5stack-atom-matrix.yaml +0 -0
  296. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/boards/m5stack-atom.yaml +0 -0
  297. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/boards/m5stack-atoms3-lite.yaml +0 -0
  298. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/boards/m5stack-atoms3.yaml +0 -0
  299. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/boards/m5stack-atomu.yaml +0 -0
  300. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/boards/nodemcu-32s.yaml +0 -0
  301. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/boards/nodemcu-v2.yaml +0 -0
  302. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/boards/seeed-xiao-esp32c3.yaml +0 -0
  303. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/boards/ttgo-lora32-v1.yaml +0 -0
  304. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/boards/ttgo-t-beam.yaml +0 -0
  305. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/boards/wemos-d1-mini.yaml +0 -0
  306. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/adc.yaml +0 -0
  307. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/ads1115.yaml +0 -0
  308. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/ads1115_channel.yaml +0 -0
  309. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/aht10.yaml +0 -0
  310. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/axp192.yaml +0 -0
  311. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/battery_adc.yaml +0 -0
  312. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/bl0906.yaml +0 -0
  313. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/bme280.yaml +0 -0
  314. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/bme680.yaml +0 -0
  315. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/bmp180.yaml +0 -0
  316. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/bmp280.yaml +0 -0
  317. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/cc1101.yaml +0 -0
  318. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/connector_1x02.yaml +0 -0
  319. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/connector_1x03.yaml +0 -0
  320. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/connector_1x04.yaml +0 -0
  321. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/connector_1x05.yaml +0 -0
  322. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/connector_1x06.yaml +0 -0
  323. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/connector_1x07.yaml +0 -0
  324. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/connector_1x08.yaml +0 -0
  325. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/connector_2x05_idc.yaml +0 -0
  326. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/cse7766.yaml +0 -0
  327. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/ct_clamp.yaml +0 -0
  328. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/dht.yaml +0 -0
  329. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/ds18b20.yaml +0 -0
  330. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/esp32_camera.yaml +0 -0
  331. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/gpio_input.yaml +0 -0
  332. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/gpio_output.yaml +0 -0
  333. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/grove-aht20.yaml +0 -0
  334. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/hbridge.yaml +0 -0
  335. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/hbridge_mosfet.yaml +0 -0
  336. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/hc-sr04.yaml +0 -0
  337. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/htu21d.yaml +0 -0
  338. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/hx711.yaml +0 -0
  339. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/i2s_microphone.yaml +0 -0
  340. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/ina219.yaml +0 -0
  341. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/lcd_pcf8574.yaml +0 -0
  342. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/ld2420.yaml +0 -0
  343. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/led_indicator.yaml +0 -0
  344. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/level_shifter.yaml +0 -0
  345. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/max31855.yaml +0 -0
  346. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/max7219.yaml +0 -0
  347. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/mcp3008.yaml +0 -0
  348. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/mcp3008_channel.yaml +0 -0
  349. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/mhz19.yaml +0 -0
  350. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/modbus.yaml +0 -0
  351. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/mosfet_low_side_driver.yaml +0 -0
  352. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/mpu6050.yaml +0 -0
  353. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/mpu6886.yaml +0 -0
  354. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/nextion.yaml +0 -0
  355. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/npn_low_side_driver.yaml +0 -0
  356. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/pn532.yaml +0 -0
  357. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/potentiometer.yaml +0 -0
  358. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/pulse_counter.yaml +0 -0
  359. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/pwm_fan.yaml +0 -0
  360. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/pzemac.yaml +0 -0
  361. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/rdm6300.yaml +0 -0
  362. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/remote_transmitter.yaml +0 -0
  363. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/rf_bridge.yaml +0 -0
  364. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/rotary_encoder.yaml +0 -0
  365. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/rtttl.yaml +0 -0
  366. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/scd4x.yaml +0 -0
  367. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/sdm_meter.yaml +0 -0
  368. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/sgp4x.yaml +0 -0
  369. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/sht3xd.yaml +0 -0
  370. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/sht4x.yaml +0 -0
  371. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/tm1638.yaml +0 -0
  372. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/tsl2561.yaml +0 -0
  373. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/tuya.yaml +0 -0
  374. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/tuya_sensor.yaml +0 -0
  375. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/tuya_switch.yaml +0 -0
  376. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/uart_gps.yaml +0 -0
  377. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/uln2003.yaml +0 -0
  378. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/usb_c_power_in.yaml +0 -0
  379. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/veml7700.yaml +0 -0
  380. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/components/voltage_divider.yaml +0 -0
  381. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/electrical.py +0 -0
  382. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/modules/env-display.yaml +0 -0
  383. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/modules/fan-controller.yaml +0 -0
  384. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/modules/motion-light.yaml +0 -0
  385. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/modules/oled-knob-13.yaml +0 -0
  386. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/library/modules/relay-4ch.yaml +0 -0
  387. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/mcp/__init__.py +0 -0
  388. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/mcp/auth.py +0 -0
  389. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/mcp/hardware.py +0 -0
  390. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/mcp/jobs.py +0 -0
  391. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/model.py +0 -0
  392. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/printwarden/__init__.py +0 -0
  393. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/printwarden/client.py +0 -0
  394. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/recommend/__init__.py +0 -0
  395. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/recommend/recommender.py +0 -0
  396. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/schema/__init__.py +0 -0
  397. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/schema/design.schema.json +0 -0
  398. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/seed.py +0 -0
  399. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/sqlite_store.py +0 -0
  400. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/targets/__init__.py +0 -0
  401. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/targets/base.py +0 -0
  402. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/targets/build_backend.py +0 -0
  403. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/targets/esphome.py +0 -0
  404. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/__init__.py +0 -0
  405. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/__main__.py +0 -0
  406. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/api.py +0 -0
  407. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/build_local.py +0 -0
  408. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/chirpstack.py +0 -0
  409. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/codec.py +0 -0
  410. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/compile.py +0 -0
  411. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/firmware_gen.py +0 -0
  412. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/templates/main.cpp.j2 +0 -0
  413. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/templates/platformio.ini.j2 +0 -0
  414. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/workbench_provision.py +0 -0
  415. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/targets/tasmota.py +0 -0
  416. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/targets/wled.py +0 -0
  417. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/targets/wled_build.py +0 -0
  418. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/validate.py +0 -0
  419. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/workbench/__init__.py +0 -0
  420. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/workbench/boot.py +0 -0
  421. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/workbench/client.py +0 -0
  422. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/workbench/gate.py +0 -0
  423. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/workbench/images.py +0 -0
  424. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio/workbench/serial.py +0 -0
  425. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio.egg-info/dependency_links.txt +0 -0
  426. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio.egg-info/entry_points.txt +0 -0
  427. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio.egg-info/requires.txt +0 -0
  428. {wirestudio-0.44.0 → wirestudio-0.45.0}/wirestudio.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: wirestudio
3
- Version: 0.44.0
3
+ Version: 0.45.0
4
4
  Summary: Agent-driven IoT device design tool that produces ESPHome YAML.
5
5
  Author: wirestudio contributors
6
6
  License: MIT
@@ -99,3 +99,9 @@ def test_fab_gerbers_route_unavailable_is_503(client, monkeypatch):
99
99
  def test_fab_status_reports_route(client):
100
100
  body = client.get("/design/fab/status").json()
101
101
  assert "route" in body and "route_reason" in body
102
+
103
+
104
+ def test_route_bad_params_are_422(client, toolchain):
105
+ for query in ("keepout=1,2,3", "keepout=1,2,3,4,pads", "via_costs=-5", "passes=0"):
106
+ resp = client.post(f"/design/kicad/route?{query}", json=_design())
107
+ assert resp.status_code == 422, query
@@ -25,7 +25,14 @@ def _entry(slug, **over):
25
25
 
26
26
 
27
27
  @pytest.fixture
28
- def studio(monkeypatch):
28
+ def user_library(tmp_path):
29
+ from wirestudio.library import Library, default_library
30
+
31
+ return Library(default_library().root, tmp_path / "user-library")
32
+
33
+
34
+ @pytest.fixture
35
+ def studio(monkeypatch, user_library):
29
36
  monkeypatch.delenv("WIRESTUDIO_DESIGNS_REPO", raising=False)
30
37
  files: dict[str, bytes] = {}
31
38
  fetched: list[str] = []
@@ -36,13 +43,15 @@ def studio(monkeypatch):
36
43
  raise RuntimeError(f"404 {url}")
37
44
  return files[url]
38
45
 
39
- def publish(*entries, designs=None):
46
+ def publish(*entries, designs=None, components=None):
40
47
  files[f"{RAW}/index/index.json"] = json.dumps(
41
48
  {"version": 1, "generated": "2026-10-23T00:00:00+00:00", "designs": list(entries)}).encode()
42
49
  for slug, doc in (designs or {}).items():
43
50
  files[f"{RAW}/main/designs/{slug}/design.json"] = json.dumps(doc).encode()
51
+ for url, text in (components or {}).items():
52
+ files[url] = text.encode()
44
53
 
45
- app = TestClient(create_app(community_fetch=fetch))
54
+ app = TestClient(create_app(library=user_library, community_fetch=fetch))
46
55
  return app, publish, fetched
47
56
 
48
57
 
@@ -77,7 +86,7 @@ def test_an_unreachable_index_is_a_502(studio):
77
86
  def test_import_stamps_the_origin(studio):
78
87
  app, publish, _ = studio
79
88
  publish(_entry("porch-motion"), designs={"porch-motion": {**EXAMPLE, "id": "porch-motion"}})
80
- body = app.get("/community/designs/porch-motion").json()
89
+ body = app.post("/community/designs/porch-motion/import").json()
81
90
  assert body["design"]["origin"] == {"catalogue": REPO, "slug": "porch-motion",
82
91
  "author": "octocat", "license": "CC-BY-4.0"}
83
92
  assert app.post("/design/validate", json=body["design"]).status_code == 200
@@ -86,14 +95,14 @@ def test_import_stamps_the_origin(studio):
86
95
  def test_import_only_follows_urls_inside_the_catalogue(studio):
87
96
  app, publish, fetched = studio
88
97
  publish(_entry("evil", design_url="http://169.254.169.254/latest/meta-data"))
89
- assert app.get("/community/designs/evil").status_code == 502
98
+ assert app.post("/community/designs/evil/import").status_code == 502
90
99
  assert not any("169.254" in u for u in fetched)
91
100
 
92
101
 
93
102
  def test_import_of_an_unknown_slug_is_a_404(studio):
94
103
  app, publish, _ = studio
95
104
  publish()
96
- assert app.get("/community/designs/nope").status_code == 404
105
+ assert app.post("/community/designs/nope/import").status_code == 404
97
106
 
98
107
 
99
108
  def _share(app, **over):
@@ -189,3 +198,51 @@ def test_share_refuses_what_is_not_a_photo(studio):
189
198
  assert r.status_code == 422 and "not a readable image" in r.json()["detail"]
190
199
  r = _share(app, photos=[{"name": f"p{i}.jpg", "data": _jpeg((10, 10))} for i in range(5)])
191
200
  assert r.status_code == 422 and "at most 4" in r.json()["detail"]
201
+
202
+
203
+ LED_DRIVER = (Path(__file__).resolve().parent / "fixtures" / "components" / "led_driver_npn.yaml").read_text()
204
+ LED_URL = f"{RAW}/main/designs/porch-led/components/led_driver_npn.yaml"
205
+
206
+
207
+ def _led_design() -> dict:
208
+ doc = {**EXAMPLE, "id": "porch-led"}
209
+ doc["components"] = [*EXAMPLE["components"],
210
+ {"id": "status", "library_id": "led_driver_npn", "label": "Status LED", "params": {}}]
211
+ return doc
212
+
213
+
214
+ def _publish_led(publish):
215
+ publish(_entry("porch-led", user_components=[{"id": "led_driver_npn", "url": LED_URL}]),
216
+ designs={"porch-led": _led_design()}, components={LED_URL: LED_DRIVER})
217
+
218
+
219
+ def test_import_installs_the_designs_user_components(studio, user_library):
220
+ app, publish, _ = studio
221
+ _publish_led(publish)
222
+ r = app.post("/community/designs/porch-led/import")
223
+ assert r.status_code == 200, r.text
224
+ assert r.json()["installed_components"] == ["led_driver_npn"]
225
+ assert user_library.component_source("led_driver_npn") == "user"
226
+ again = app.post("/community/designs/porch-led/import").json()
227
+ assert again["installed_components"] == [], "the same text is already here"
228
+
229
+
230
+ def test_import_never_overwrites_a_different_user_component(studio, user_library):
231
+ app, publish, _ = studio
232
+ _publish_led(publish)
233
+ user_library.save_component("led_driver_npn", LED_DRIVER.replace("NPN low-side LED driver", "mine", 1))
234
+ r = app.post("/community/designs/porch-led/import")
235
+ assert r.status_code == 409 and "different component 'led_driver_npn'" in r.json()["detail"]
236
+ assert "mine" in user_library.component_yaml("led_driver_npn")
237
+
238
+
239
+ def test_share_packs_the_user_components_the_design_uses(studio, user_library):
240
+ app, _, _ = studio
241
+ user_library.save_component("led_driver_npn", LED_DRIVER)
242
+ r = _share(app, design=_led_design())
243
+ assert r.status_code == 200, r.text
244
+ z = zipfile.ZipFile(io.BytesIO(r.content))
245
+ slug = "porch-led"
246
+ assert z.read(f"{slug}/components/led_driver_npn.yaml").decode() == LED_DRIVER
247
+ assert not [n for n in z.namelist() if "/components/" in n and "led_driver_npn" not in n], \
248
+ "bundled components are not packed"
@@ -58,31 +58,41 @@ def test_no_board_silently_returns_empty(lib):
58
58
  assert check_pin_compatibility({"components": [], "connections": []}, lib) == []
59
59
 
60
60
 
61
- def test_input_only_as_output_is_an_error(lib):
62
- """
63
- GPIO34 on ESP32-DevKitC-V4 is input-only. Wiring an output role to it
64
- should emit a hard error.
65
- """
66
- d = _load("garage-motion")
67
- # Reroute pir1.OUT (digital_out) to GPIO34 (input-only).
61
+ def _with_lamp(d: dict, pin: str) -> dict:
62
+ """garage-motion plus a relay/LED output the MCU drives, on `pin`."""
63
+ d["components"].append({"id": "lamp", "library_id": "gpio_output", "label": "Lamp", "params": {}})
64
+ d["connections"].append({"component_id": "lamp", "pin_role": "OUT",
65
+ "target": {"kind": "gpio", "pin": pin}})
66
+ return d
67
+
68
+
69
+ def _move_pir(d: dict, pin: str) -> dict:
68
70
  for c in d["connections"]:
69
71
  if c["component_id"] == "pir1" and c["pin_role"] == "OUT":
70
- c["target"] = {"kind": "gpio", "pin": "GPIO34"}
71
- warnings = check_pin_compatibility(d, lib)
72
+ c["target"] = {"kind": "gpio", "pin": pin}
73
+ return d
74
+
75
+
76
+ def test_input_only_as_output_is_an_error(lib):
77
+ """GPIO34 on ESP32-DevKitC-V4 is input-only; an output the MCU drives
78
+ there is a hard error."""
79
+ warnings = check_pin_compatibility(_with_lamp(_load("garage-motion"), "GPIO34"), lib)
72
80
  errs = _by_code(warnings, "input_only_as_output")
73
81
  assert len(errs) == 1
74
82
  assert errs[0].severity == "error"
75
83
  assert errs[0].pin == "GPIO34"
76
84
 
77
85
 
86
+ def test_a_sensor_output_on_an_input_only_pin_is_fine(lib):
87
+ """A PIR's OUT is the MCU's input: GPIO34-39 are the usual place for it."""
88
+ warnings = check_pin_compatibility(_move_pir(_load("garage-motion"), "GPIO34"), lib)
89
+ assert warnings == []
90
+
91
+
78
92
  def test_boot_high_pin_used_as_output_warns(lib):
79
93
  """ESP32 GPIO0 is boot_high (must be HIGH at boot). Driving it as a digital
80
94
  output should warn."""
81
- d = _load("garage-motion")
82
- for c in d["connections"]:
83
- if c["component_id"] == "pir1" and c["pin_role"] == "OUT":
84
- c["target"] = {"kind": "gpio", "pin": "GPIO0"}
85
- warnings = check_pin_compatibility(d, lib)
95
+ warnings = check_pin_compatibility(_with_lamp(_load("garage-motion"), "GPIO0"), lib)
86
96
  boot = _by_code(warnings, "boot_strap_output")
87
97
  assert len(boot) == 1
88
98
  assert boot[0].severity == "warn"
@@ -92,16 +102,23 @@ def test_boot_high_pin_used_as_output_warns(lib):
92
102
  def test_boot_low_pin_used_as_output_warns(lib):
93
103
  """ESP32 GPIO12 is boot_low (must be LOW at boot, controls flash voltage).
94
104
  Output use should warn."""
95
- d = _load("garage-motion")
96
- for c in d["connections"]:
97
- if c["component_id"] == "pir1" and c["pin_role"] == "OUT":
98
- c["target"] = {"kind": "gpio", "pin": "GPIO12"}
99
- warnings = check_pin_compatibility(d, lib)
105
+ warnings = check_pin_compatibility(_with_lamp(_load("garage-motion"), "GPIO12"), lib)
100
106
  boot = _by_code(warnings, "boot_strap_output")
101
107
  assert len(boot) == 1
102
108
  assert "LOW at boot" in boot[0].message
103
109
 
104
110
 
111
+ @pytest.mark.parametrize("pin, level", [("GPIO0", "HIGH"), ("GPIO12", "LOW")])
112
+ def test_a_strap_pin_driven_by_a_sensor_warns(lib, pin, level):
113
+ """A PIR holds its output at power-up, so on a strap pin it decides the
114
+ boot mode -- the hazard is the sensor's, even though the MCU reads it."""
115
+ warnings = check_pin_compatibility(_move_pir(_load("garage-motion"), pin), lib)
116
+ driven = _by_code(warnings, "boot_strap_driven")
117
+ assert len(driven) == 1 and driven[0].severity == "warn"
118
+ assert f"must be {level} at boot" in driven[0].message
119
+ assert _by_code(warnings, "boot_strap_output") == []
120
+
121
+
105
122
  def test_boot_strap_pin_as_input_does_not_warn(lib):
106
123
  """Buttons wired to boot strap pins (e.g., Sonoff GPIO0 button) are read
107
124
  as inputs; the validator should not flag this even though the pin is a
@@ -362,3 +379,50 @@ def test_a_reserved_bus_id_is_an_error(lib):
362
379
  assert w.severity == "error"
363
380
  assert w.component_id == "uart0"
364
381
  assert "uart_bus0" in w.message
382
+
383
+
384
+ PART_DRIVEN = {
385
+ "hc-sr501": ["OUT"], "rcwl-0516": ["OUT"], "ld2410": ["OUT"], "remote_receiver": ["OUT"],
386
+ "hlw8012": ["CF", "CF1"], "sx127x": ["DIO0"], "wiegand": ["D0", "D1"],
387
+ }
388
+
389
+
390
+ @pytest.mark.parametrize("library_id, roles", sorted(PART_DRIVEN.items()))
391
+ def test_part_driven_signals_are_mcu_inputs(lib, library_id, roles):
392
+ """The audit's verdicts: these lines are outputs of the part, so inputs
393
+ of the MCU, and the part drives them."""
394
+ pins = {p.role: p for p in lib.component(library_id).electrical.pins}
395
+ for role in roles:
396
+ assert pins[role].kind == "digital_in" and pins[role].driven, f"{library_id}.{role}"
397
+
398
+
399
+ def test_the_component_gate_refuses_driven_on_an_output(lib):
400
+ from wirestudio.library.check import check_component_yaml
401
+
402
+ text = (REPO_ROOT / "tests" / "fixtures" / "components" / "led_driver_npn.yaml").read_text()
403
+ assert check_component_yaml(text, lib).ok
404
+ bad = text.replace("kind: digital_out", "kind: digital_out, driven: true", 1)
405
+ assert bad != text
406
+ report = check_component_yaml(bad, lib)
407
+ assert not report.ok and any("`driven`" in e for e in report.errors)
408
+
409
+
410
+ def _with_receivers(d: dict, n: int, **params) -> dict:
411
+ for i, pin in enumerate(("GPIO32", "GPIO33", "GPIO25", "GPIO26")[:n]):
412
+ d["components"].append({"id": f"rx{i}", "library_id": "remote_receiver",
413
+ "label": f"RX {i}", "params": dict(params)})
414
+ d["connections"].append({"component_id": f"rx{i}", "pin_role": "OUT",
415
+ "target": {"kind": "gpio", "pin": pin}})
416
+ return d
417
+
418
+
419
+ def test_a_third_remote_receiver_on_a_classic_esp32_warns(lib):
420
+ assert _by_code(check_pin_compatibility(_with_receivers(_load("garage-motion"), 2), lib), "rmt_memory") == []
421
+ warns = _by_code(check_pin_compatibility(_with_receivers(_load("garage-motion"), 3), lib), "rmt_memory")
422
+ assert len(warns) == 1 and warns[0].component_id == "rx2"
423
+ assert "rmt_symbols" in warns[0].message
424
+
425
+
426
+ def test_receivers_with_rmt_symbols_set_do_not_warn(lib):
427
+ d = _with_receivers(_load("garage-motion"), 3, rmt_symbols=128)
428
+ assert _by_code(check_pin_compatibility(d, lib), "rmt_memory") == []
@@ -14,12 +14,14 @@ The generator is pure text-out, so we lean on substring assertions +
14
14
  from __future__ import annotations
15
15
 
16
16
  import json
17
+ import re
17
18
  from pathlib import Path
18
19
 
19
20
  import pytest
20
21
 
21
22
  from wirestudio.kicad import generate_skidl
22
23
  from wirestudio.kicad.generator import _py_var
24
+ from wirestudio.kicad.netlist import build_netlist
23
25
  from wirestudio.library import default_library
24
26
  from wirestudio.model import Design
25
27
 
@@ -160,15 +162,24 @@ def test_rail_targets_become_named_nets(lib):
160
162
  assert 'NET_PLUS__3V3 = Net("+3V3")' in script
161
163
 
162
164
 
163
- def test_bus_targets_share_a_single_net(lib):
164
- """All connections targeting bus_id=i2c0 should resolve to the same
165
- NET_BUS_i2c0 handle so the schematic's I2C pins land on one net."""
165
+ def test_bus_targets_land_on_a_net_per_line(lib):
166
+ """A bus is a net per line: the BME280's SDA and SCL go to i2c0's SDA
167
+ and SCL nets, never one shared net. The symbol's I2C pins are SDI/SCK
168
+ (pin_map rewrites SDA->SDI, SCL->SCK)."""
166
169
  script = generate_skidl(_design("garage-motion"), lib)
167
- assert 'NET_BUS_i2c0 = Net("BUS_i2c0")' in script
168
- # Both BME280's I2C connections go to that single bus net. The
169
- # symbol's I2C pins are SDI/SCK (pin_map rewrites SDA->SDI, SCL->SCK).
170
- assert 'c_bme1["SDI"] += NET_BUS_i2c0' in script
171
- assert 'c_bme1["SCK"] += NET_BUS_i2c0' in script
170
+ assert 'NET_BUS_i2c0_SDA = Net("BUS_i2c0_SDA")' in script
171
+ assert 'NET_BUS_i2c0_SCL = Net("BUS_i2c0_SCL")' in script
172
+ assert 'Net("BUS_i2c0")' not in script
173
+ assert 'c_bme1["SDI"] += NET_BUS_i2c0_SDA' in script
174
+ assert 'c_bme1["SCK"] += NET_BUS_i2c0_SCL' in script
175
+
176
+
177
+ def test_uart_lines_cross_over(lib):
178
+ """A UART part's TX is the MCU's RX line, and its RX the MCU's TX."""
179
+ nets = {n.name: {(p.ref, p.pin_role) for p in n.pads}
180
+ for n in build_netlist(_design("nextion-thermostat"), lib)}
181
+ assert nets["BUS_nextion_uart_RX"] == {("U1", "TX"), ("M1", "IO16")}
182
+ assert nets["BUS_nextion_uart_TX"] == {("U1", "RX"), ("M1", "IO17")}
172
183
 
173
184
 
174
185
  def test_gpio_targets_share_one_declared_net(lib):
@@ -187,8 +198,8 @@ def test_board_pins_join_gpio_rail_and_bus_nets(lib):
187
198
  the module's VDD, and 5V has no module pin, which the script says."""
188
199
  script = generate_skidl(_design("garage-motion"), lib)
189
200
  assert 'board["IO13"] += NET_GPIO_GPIO13 # GPIO13' in script
190
- assert 'board["IO21"] += NET_BUS_i2c0 # GPIO21' in script
191
- assert 'board["IO22"] += NET_BUS_i2c0 # GPIO22' in script
201
+ assert 'board["IO21"] += NET_BUS_i2c0_SDA # GPIO21' in script
202
+ assert 'board["IO22"] += NET_BUS_i2c0_SCL # GPIO22' in script
192
203
  assert 'board["VDD"] += NET_PLUS__3V3 # 3V3' in script
193
204
  assert 'board["GND"] += GND # GND' in script
194
205
  assert "# 5V: no pin on RF_Module:ESP32-WROOM-32" in script
@@ -198,8 +209,8 @@ def test_board_pins_use_the_boards_own_role_names(lib):
198
209
  """A D1 mini design names pins D1/D2; the pin_map turns them into the
199
210
  symbol's SCL/D1 and SDA/D2."""
200
211
  script = generate_skidl(_design("room-climate"), lib)
201
- assert 'board["SDA/D2"] += NET_BUS_' in script
202
- assert 'board["SCL/D1"] += NET_BUS_' in script
212
+ assert re.search(r'board\["SDA/D2"\] \+= NET_BUS_\w+_SDA ', script)
213
+ assert re.search(r'board\["SCL/D1"\] \+= NET_BUS_\w+_SCL ', script)
203
214
  assert 'board["3V3"] += NET_PLUS__3V3' in script
204
215
 
205
216
 
@@ -0,0 +1,112 @@
1
+ """Routed DRC: kicad-cli's report summarised for people, stored with the
2
+ routed board, and served by the API and route_pcb.
3
+
4
+ kicad-cli itself runs in CI's pcb-route gate, which also checks this path
5
+ reports the airwires of an unrouted board; here its JSON comes from a
6
+ fixture captured from KiCad 8 on the unrouted garage-motion board."""
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ from pathlib import Path
11
+
12
+ import pytest
13
+
14
+ from wirestudio.kicad import drc as drc_mod
15
+ from wirestudio.kicad.drc import WAIVED, summarize
16
+ from wirestudio.kicad.route import routed_drc
17
+
18
+ FIXTURE = json.loads((Path(__file__).parent / "fixtures" / "drc_unrouted_garage_motion.json").read_text())
19
+
20
+
21
+ def test_summary_leads_with_the_airwires():
22
+ s = summarize(FIXTURE)
23
+ assert s["unconnected"] == 7 and s["ok"] is False
24
+ assert s["groups"][0]["type"] == "unconnected_items"
25
+ first = s["groups"][0]["violations"][0]["items"][0]
26
+ assert first["description"].startswith("Pad ") and first["pos"]["x"] is not None
27
+
28
+
29
+ def test_footprint_inherent_types_are_waived_not_failed():
30
+ s = summarize(FIXTURE)
31
+ drill = next(g for g in s["groups"] if g["type"] == "drill_out_of_range")
32
+ assert drill["waived"] and drill["count"] == 12 and drill["reason"] == WAIVED["drill_out_of_range"]
33
+ assert s["errors"] == 0, "the only errors besides airwires are waived"
34
+ assert [g["waived"] for g in s["groups"]][-1] is True, "waived groups sort last"
35
+
36
+
37
+ def test_a_routed_board_with_a_short_is_not_ok():
38
+ report = {"violations": [{"type": "shorting_items", "severity": "error",
39
+ "description": "Items shorting two nets", "items": []}],
40
+ "unconnected_items": []}
41
+ s = summarize(report)
42
+ assert s["ok"] is False and s["errors"] == 1 and s["unconnected"] == 0
43
+
44
+
45
+ def test_a_clean_routed_board_is_ok():
46
+ report = {"violations": [{"type": "silk_overlap", "severity": "warning", "description": "x", "items": []}],
47
+ "unconnected_items": []}
48
+ s = summarize(report)
49
+ assert s["ok"] is True and s["warnings"] == 0 and s["waived"] == 1
50
+
51
+
52
+ @pytest.fixture
53
+ def slot(tmp_path):
54
+ key = "0123456789abcdef"
55
+ (tmp_path / key).mkdir()
56
+ (tmp_path / key / "routed.kicad_pcb").write_text("(kicad_pcb (segment (net 1)))")
57
+ return tmp_path, key
58
+
59
+
60
+ def test_routed_drc_runs_once_and_is_stored(slot, monkeypatch):
61
+ cache, key = slot
62
+ calls = []
63
+ monkeypatch.setattr(drc_mod, "drc_status", lambda: {"available": True, "reason": None})
64
+ monkeypatch.setattr(drc_mod, "run_drc", lambda text: calls.append(text) or summarize(FIXTURE))
65
+ first = routed_drc(key, cache_dir=cache)
66
+ second = routed_drc(key, cache_dir=cache)
67
+ assert first == second and first["unconnected"] == 7
68
+ assert len(calls) == 1 and (cache / key / "drc.json").is_file()
69
+
70
+
71
+ def test_routed_drc_without_kicad_cli_is_none(slot, monkeypatch):
72
+ cache, key = slot
73
+ monkeypatch.setattr(drc_mod, "drc_status", lambda: {"available": False, "reason": "no"})
74
+ assert routed_drc(key, cache_dir=cache) is None
75
+ assert routed_drc("ffffffffffffffff", cache_dir=cache) is None
76
+
77
+
78
+ def test_the_api_serves_the_routed_drc(slot, monkeypatch):
79
+ from fastapi.testclient import TestClient
80
+
81
+ from wirestudio.api.app import create_app
82
+
83
+ cache, key = slot
84
+ monkeypatch.setenv("WIRESTUDIO_ROUTE_CACHE", str(cache))
85
+ monkeypatch.setattr(drc_mod, "drc_status", lambda: {"available": True, "reason": None})
86
+ monkeypatch.setattr(drc_mod, "run_drc", lambda text: summarize(FIXTURE))
87
+ app = TestClient(create_app())
88
+ body = app.get(f"/design/kicad/route/{key}/drc").json()
89
+ assert body["unconnected"] == 7 and body["groups"][0]["type"] == "unconnected_items"
90
+ assert app.get("/design/kicad/route/ffffffffffffffff/drc").status_code == 404
91
+ assert app.get("/design/kicad/route/nope/drc").status_code == 422
92
+
93
+
94
+ def test_route_pcb_brief_drops_item_detail():
95
+ from wirestudio.agent.tools import _drc_brief
96
+
97
+ brief = _drc_brief(summarize(FIXTURE))
98
+ unconnected = brief["groups"][0]
99
+ assert unconnected == {"type": "unconnected_items", "severity": "error", "count": 7, "waived": False,
100
+ "examples": ["Missing connection between items"] * 3}
101
+ assert "examples" not in brief["groups"][-1], "waived groups carry counts only"
102
+
103
+
104
+ def test_route_pcb_refuses_bad_parameters():
105
+ from wirestudio.agent.tools import _run_route_pcb
106
+ from wirestudio.library import default_library
107
+
108
+ design = json.loads((Path(__file__).resolve().parent.parent / "wirestudio/examples/garage-motion.json").read_text())
109
+ out = _run_route_pcb(design, default_library(), keepouts=[{"x": 0, "y": 0, "w": 0, "h": 1}])
110
+ assert out["ok"] is False and "bad route parameters" in out["error"]
111
+ out = _run_route_pcb(design, default_library(), via_costs=-1)
112
+ assert out["ok"] is False and "bad route parameters" in out["error"]
@@ -43,10 +43,12 @@ def _design(name: str) -> Design:
43
43
 
44
44
  def test_build_netlist_groups_rails_and_buses(lib):
45
45
  nets = {n.name: n for n in build_netlist(_design("garage-motion"), lib)}
46
- assert {"GND", "+5V", "+3V3", "BUS_i2c0"} <= set(nets)
47
- # The BME280's SDA + SCL both land on the single I2C bus net.
48
- roles = {(p.component_id, p.pin_role) for p in nets["BUS_i2c0"].pads}
49
- assert ("bme1", "SDA") in roles and ("bme1", "SCL") in roles
46
+ assert {"GND", "+5V", "+3V3", "BUS_i2c0_SDA", "BUS_i2c0_SCL"} <= set(nets)
47
+ assert "BUS_i2c0" not in nets
48
+ sda = {(p.component_id, p.pin_role) for p in nets["BUS_i2c0_SDA"].pads}
49
+ scl = {(p.component_id, p.pin_role) for p in nets["BUS_i2c0_SCL"].pads}
50
+ assert sda == {("bme1", "SDA"), ("__board__", "IO21")}
51
+ assert scl == {("bme1", "SCL"), ("__board__", "IO22")}
50
52
 
51
53
 
52
54
  def test_build_netlist_refs_agree_with_assign_refs(lib):
@@ -85,6 +87,30 @@ def test_pcb_starts_correctly_and_is_paren_balanced(lib):
85
87
  assert '(layer "Edge.Cuts")' in pcb and "gr_rect" in pcb
86
88
 
87
89
 
90
+ @libs_required
91
+ def test_pcb_pours_gnd_on_the_bottom_inside_the_outline(lib):
92
+ pcb = generate_kicad_pcb(_design("garage-motion"), lib)
93
+ gnd = re.search(r'\(net (\d+) "GND"\)', pcb).group(1)
94
+ zones = re.findall(r"^\t\(zone\n(.*?)^\t\)$", pcb, re.S | re.M)
95
+ assert len(zones) == 1
96
+ zone = zones[0]
97
+ assert f"(net {gnd})" in zone and '(layer "B.Cu")' in zone
98
+ assert "(connect_pads yes" in zone
99
+ assert "(filled_polygon" not in pcb # the route step fills it
100
+ ox0, oy0, ox1, oy1 = map(float, re.search(
101
+ r"\(gr_rect\n\t\t\(start ([\d.]+) ([\d.]+)\)\n\t\t\(end ([\d.]+) ([\d.]+)\)", pcb).groups())
102
+ pts = [tuple(map(float, p)) for p in re.findall(r"\(xy ([\d.]+) ([\d.]+)\)", zone)]
103
+ assert all(ox0 < x < ox1 and oy0 < y < oy1 for x, y in pts)
104
+
105
+
106
+ @libs_required
107
+ def test_pcb_without_a_gnd_net_has_no_pour(lib):
108
+ design = _design("garage-motion")
109
+ design.connections = [c for c in design.connections
110
+ if not (c.target.kind == "rail" and c.target.rail.lower() == "gnd")]
111
+ assert not re.search(r"^\t\(zone$", generate_kicad_pcb(design, lib), re.M)
112
+
113
+
88
114
  @libs_required
89
115
  def test_pcb_embeds_board_and_component_footprints(lib):
90
116
  pcb = generate_kicad_pcb(_design("garage-motion"), lib)
@@ -96,11 +122,12 @@ def test_pcb_embeds_board_and_component_footprints(lib):
96
122
  @libs_required
97
123
  def test_pcb_binds_pin_mapped_pad_to_net(lib):
98
124
  """The BME280's VCC role -> VDD pin -> a real footprint pad, carrying the
99
- +3V3 net; SDA/SCL land on the I2C bus net. Exercises symbol pin-name ->
100
- number -> pad resolution end to end."""
125
+ +3V3 net; SDA/SCL land on the I2C bus's line nets. Exercises symbol
126
+ pin-name -> number -> pad resolution end to end."""
101
127
  pcb = generate_kicad_pcb(_design("garage-motion"), lib)
102
128
  assert re.search(r'\(net \d+ "\+3V3"\)', pcb)
103
- assert re.search(r'\(net \d+ "BUS_i2c0"\)', pcb)
129
+ assert re.search(r'\(net \d+ "BUS_i2c0_SDA"\)', pcb)
130
+ assert re.search(r'\(net \d+ "BUS_i2c0_SCL"\)', pcb)
104
131
 
105
132
 
106
133
  @libs_required
@@ -228,7 +255,7 @@ def test_build_netlist_binds_board_pins(lib):
228
255
  nets = {n.name: n for n in build_netlist(_design("garage-motion"), lib)}
229
256
  board = {(p.pin_role, name) for name, n in nets.items() for p in n.pads if p.ref == "M1"}
230
257
  assert ("IO13", "GPIO_GPIO13") in board
231
- assert ("IO21", "BUS_i2c0") in board and ("IO22", "BUS_i2c0") in board
258
+ assert ("IO21", "BUS_i2c0_SDA") in board and ("IO22", "BUS_i2c0_SCL") in board
232
259
  assert ("VDD", "+3V3") in board
233
260
  assert ("GND", "GND") in board
234
261
  assert all(p.component_id == "__board__" for n in nets.values() for p in n.pads if p.ref == "M1")
@@ -14,7 +14,9 @@ from pathlib import Path
14
14
  import pytest
15
15
 
16
16
  from wirestudio.kicad.route import (
17
+ Keepout,
17
18
  RouteError,
19
+ RouteParams,
18
20
  RouteUnavailable,
19
21
  route_board,
20
22
  route_cache_key,
@@ -76,7 +78,9 @@ def test_cache_key_folds_in_board_and_passes():
76
78
  a = route_cache_key(UNROUTED)
77
79
  assert a == route_cache_key(UNROUTED)
78
80
  assert a != route_cache_key(ROUTED)
79
- assert a != route_cache_key(UNROUTED, max_passes=5)
81
+ assert a != route_cache_key(UNROUTED, RouteParams(max_passes=5))
82
+ assert a != route_cache_key(UNROUTED, RouteParams(via_costs=500))
83
+ assert a != route_cache_key(UNROUTED, RouteParams(keepouts=(Keepout(1, 1, 2, 2),)))
80
84
 
81
85
 
82
86
  def test_route_events_roundtrip(toolchain, tmp_path):
@@ -142,3 +146,71 @@ def test_route_board_returns_text_or_raises(toolchain, tmp_path):
142
146
  _script(tmp_path / "fake-java", 'echo "could not route"\n')
143
147
  with pytest.raises(RouteError, match="could not route"):
144
148
  route_board(ROUTED, cache_dir=tmp_path / "c2")
149
+
150
+
151
+ def test_a_routed_board_carries_its_drc_and_a_cache_hit_reuses_it(toolchain, tmp_path, monkeypatch):
152
+ from wirestudio.kicad import drc
153
+
154
+ runs = []
155
+ monkeypatch.setattr(drc, "drc_status", lambda: {"available": True, "reason": None})
156
+ monkeypatch.setattr(drc, "run_drc", lambda text: runs.append(text) or {"ok": True, "unconnected": 0})
157
+ cache = tmp_path / "cache"
158
+ first = list(route_events(UNROUTED, cache_dir=cache))[-1]
159
+ again = list(route_events(UNROUTED, cache_dir=cache))[-1]
160
+ assert first["drc"] == again["drc"] == {"ok": True, "unconnected": 0}
161
+ assert again["cache_hit"] and len(runs) == 1
162
+
163
+
164
+ def test_keepout_parse_and_validation():
165
+ assert Keepout.parse("1, 2, 3, 4") == Keepout(1, 2, 3, 4)
166
+ assert Keepout.parse("1,2,3,4,vias") == Keepout(1, 2, 3, 4, tracks=False, vias=True)
167
+ assert Keepout.parse("1,2,3,4,tracks") == Keepout(1, 2, 3, 4, tracks=True, vias=False)
168
+ for bad in ("1,2,3", "1,2,3,4,pads", "a,2,3,4", "1,2,0,4", "1,2,3,-1"):
169
+ with pytest.raises(ValueError):
170
+ Keepout.parse(bad)
171
+ with pytest.raises(ValueError):
172
+ Keepout(0, 0, 1, 1, tracks=False, vias=False)
173
+
174
+
175
+ def test_route_params_validation():
176
+ for kwargs in ({"max_passes": 0}, {"max_passes": 201}, {"via_costs": -1}, {"via_costs": 10_001}):
177
+ with pytest.raises(ValueError):
178
+ RouteParams(**kwargs)
179
+
180
+
181
+ def test_apply_adds_rule_areas_inside_the_board():
182
+ board = RouteParams(keepouts=(Keepout(1, 2, 3, 4, vias=False),)).apply(UNROUTED)
183
+ assert board.count("(") == board.count(")")
184
+ assert board.rstrip().endswith(")")
185
+ assert "(keepout (tracks not_allowed) (vias allowed)" in board
186
+ assert "(xy 1 2) (xy 4 2) (xy 4 6) (xy 1 6)" in board
187
+ assert RouteParams().apply(UNROUTED) == UNROUTED
188
+
189
+
190
+ def test_freerouting_args():
191
+ default = RouteParams().freerouting_args()
192
+ assert "--usage_and_diagnostic_data.disable_analytics=true" in default
193
+ assert not any("via_costs" in a for a in default)
194
+ assert "--router.scoring.via_costs=800" in RouteParams(via_costs=800).freerouting_args()
195
+
196
+
197
+ def test_params_reach_freerouting_and_the_board(toolchain, tmp_path):
198
+ seen = tmp_path / "seen"
199
+ _script(
200
+ tmp_path / "fake-java",
201
+ f'echo "$@" > {seen}/args\necho session > "$6"\n',
202
+ )
203
+ _script(
204
+ tmp_path / "fake-python",
205
+ 'case "$2" in\n'
206
+ "probe) echo 8.0.9 ;;\n"
207
+ f'dsn) cp "$3" {seen}/board; echo dsn > "$4" ;;\n'
208
+ 'ses) printf \'%s\' \'' + ROUTED.replace("\n", "") + '\' > "$3" ;;\n'
209
+ "esac\n",
210
+ )
211
+ seen.mkdir()
212
+ params = RouteParams(via_costs=1500, keepouts=(Keepout(5, 5, 2, 2),))
213
+ done = list(route_events(UNROUTED, params, cache_dir=tmp_path / "cache"))[-1]
214
+ assert done["ok"] is True
215
+ assert "--router.scoring.via_costs=1500" in (seen / "args").read_text()
216
+ assert "wirestudio keepout" in (seen / "board").read_text()