wirestudio 0.43.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.43.0/wirestudio.egg-info → wirestudio-0.45.0}/PKG-INFO +2 -1
  2. {wirestudio-0.43.0 → wirestudio-0.45.0}/pyproject.toml +1 -0
  3. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_api_route.py +6 -0
  4. wirestudio-0.45.0/tests/test_community.py +248 -0
  5. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_compatibility.py +83 -19
  6. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_kicad.py +23 -12
  7. wirestudio-0.45.0/tests/test_kicad_drc.py +112 -0
  8. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_kicad_pcb.py +35 -8
  9. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_kicad_route.py +73 -1
  10. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_migrate.py +50 -0
  11. wirestudio-0.45.0/tests/test_rf_ook.py +100 -0
  12. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/__init__.py +1 -1
  13. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/agent/tools.py +66 -7
  14. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/api/app.py +41 -2
  15. wirestudio-0.45.0/wirestudio/api/community.py +305 -0
  16. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/csp/compatibility.py +37 -0
  17. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/generate/yaml_gen.py +35 -4
  18. wirestudio-0.45.0/wirestudio/kicad/drc.py +145 -0
  19. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/kicad/fab.py +3 -3
  20. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/kicad/generator.py +27 -14
  21. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/kicad/netlist.py +58 -13
  22. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/kicad/pcb.py +29 -1
  23. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/kicad/pcbnew_bridge.py +5 -1
  24. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/kicad/route.py +138 -17
  25. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/__init__.py +6 -0
  26. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/ttgo-lora32-v2.yaml +8 -2
  27. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/check.py +5 -0
  28. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/apa102.yaml +2 -2
  29. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/bh1750.yaml +1 -1
  30. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/esp32_rmt_led_strip.yaml +1 -1
  31. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/hc-sr501.yaml +1 -1
  32. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/hlw8012.yaml +3 -3
  33. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/ili9xxx.yaml +3 -3
  34. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/ld2410.yaml +1 -1
  35. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/max98357a.yaml +1 -1
  36. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/mcp23008.yaml +1 -1
  37. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/mcp23017.yaml +1 -1
  38. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/mcp23s17.yaml +1 -1
  39. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/pca9685.yaml +1 -1
  40. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/pcf8574.yaml +1 -1
  41. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/pmsx003.yaml +1 -1
  42. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/rc522.yaml +1 -1
  43. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/rcwl-0516.yaml +1 -1
  44. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/remote_receiver.yaml +28 -5
  45. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/servo.yaml +1 -1
  46. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/sn74hc595.yaml +4 -4
  47. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/ssd1306.yaml +1 -1
  48. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/st7789.yaml +3 -3
  49. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/sx127x.yaml +24 -3
  50. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/tm1637.yaml +2 -2
  51. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/vl53l0x.yaml +2 -2
  52. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/wiegand.yaml +2 -2
  53. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/ws2812b.yaml +1 -1
  54. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/xpt2046.yaml +1 -1
  55. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/mcp/server.py +12 -3
  56. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/migrate.py +59 -2
  57. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/model.py +11 -0
  58. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/schema/design.schema.json +13 -0
  59. {wirestudio-0.43.0 → wirestudio-0.45.0/wirestudio.egg-info}/PKG-INFO +2 -1
  60. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio.egg-info/SOURCES.txt +5 -0
  61. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio.egg-info/requires.txt +1 -0
  62. {wirestudio-0.43.0 → wirestudio-0.45.0}/LICENSE +0 -0
  63. {wirestudio-0.43.0 → wirestudio-0.45.0}/LICENSES.md +0 -0
  64. {wirestudio-0.43.0 → wirestudio-0.45.0}/README.md +0 -0
  65. {wirestudio-0.43.0 → wirestudio-0.45.0}/setup.cfg +0 -0
  66. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_active_design.py +0 -0
  67. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_agent.py +0 -0
  68. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_agent_eval.py +0 -0
  69. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_agent_stream.py +0 -0
  70. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_api.py +0 -0
  71. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_api_auth.py +0 -0
  72. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_ascii_gen.py +0 -0
  73. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_build_backend.py +0 -0
  74. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_check_upgrade.py +0 -0
  75. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_chirpstack.py +0 -0
  76. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_circuitpython.py +0 -0
  77. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_codec.py +0 -0
  78. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_compile.py +0 -0
  79. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_component_corpus.py +0 -0
  80. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_connectors.py +0 -0
  81. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_coverage_baseline.py +0 -0
  82. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_design_events.py +0 -0
  83. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_designs.py +0 -0
  84. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_designs_seed.py +0 -0
  85. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_display_intent.py +0 -0
  86. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_electrical.py +0 -0
  87. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_enclosure.py +0 -0
  88. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_enclosure_gate.py +0 -0
  89. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_enclosure_search.py +0 -0
  90. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_errors.py +0 -0
  91. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_esphome_dashboard.py +0 -0
  92. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_esphome_support.py +0 -0
  93. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_firmware_gen.py +0 -0
  94. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_fleet.py +0 -0
  95. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_footprints.py +0 -0
  96. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_framework_baseline.py +0 -0
  97. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_hardware_gate.py +0 -0
  98. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_intent.py +0 -0
  99. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_inventory.py +0 -0
  100. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_jlcpcb.py +0 -0
  101. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_kicad_erc.py +0 -0
  102. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_kicad_fab.py +0 -0
  103. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_kicad_import.py +0 -0
  104. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_kicad_render.py +0 -0
  105. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_kicad_sch.py +0 -0
  106. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_library_check.py +0 -0
  107. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_library_hbridge.py +0 -0
  108. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_library_rtttl.py +0 -0
  109. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_lorawan.py +0 -0
  110. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_lorawan_api.py +0 -0
  111. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_lorawan_sugar_migration.py +0 -0
  112. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_mcp_auth.py +0 -0
  113. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_mcp_client.py +0 -0
  114. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_mcp_hardware.py +0 -0
  115. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_mcp_jobs.py +0 -0
  116. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_mcp_resources.py +0 -0
  117. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_mcp_server.py +0 -0
  118. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_meshtastic.py +0 -0
  119. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_micropython.py +0 -0
  120. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_micropython_registers.py +0 -0
  121. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_modules.py +0 -0
  122. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_pin_protocols.py +0 -0
  123. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_pin_solver.py +0 -0
  124. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_printwarden.py +0 -0
  125. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_python_blocks.py +0 -0
  126. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_recommend.py +0 -0
  127. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_register_code.py +0 -0
  128. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_schema.py +0 -0
  129. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_schema_doc.py +0 -0
  130. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_schematics_gate.py +0 -0
  131. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_security_cors.py +0 -0
  132. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_seed_onboard.py +0 -0
  133. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_serve.py +0 -0
  134. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_sqlite_store.py +0 -0
  135. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_sqlite_stores.py +0 -0
  136. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_strict.py +0 -0
  137. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_targets.py +0 -0
  138. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_tasmota.py +0 -0
  139. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_tbeam_lorawan_example.py +0 -0
  140. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_tools_add_bus.py +0 -0
  141. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_tools_add_component.py +0 -0
  142. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_tools_solve_pins.py +0 -0
  143. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_uart_frames.py +0 -0
  144. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_validate.py +0 -0
  145. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_wled.py +0 -0
  146. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_workbench.py +0 -0
  147. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_workbench_boot.py +0 -0
  148. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_workbench_images.py +0 -0
  149. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_workbench_provision.py +0 -0
  150. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_workbench_serial.py +0 -0
  151. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_ws2812_method.py +0 -0
  152. {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_yaml_gen.py +0 -0
  153. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/agent/__init__.py +0 -0
  154. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/agent/agent.py +0 -0
  155. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/agent/eval.py +0 -0
  156. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/agent/eval_tasks.yaml +0 -0
  157. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/agent/session.py +0 -0
  158. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/api/__init__.py +0 -0
  159. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/api/__main__.py +0 -0
  160. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/api/auth.py +0 -0
  161. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/api/circuitpython.py +0 -0
  162. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/api/design_input.py +0 -0
  163. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/api/meshtastic.py +0 -0
  164. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/api/micropython.py +0 -0
  165. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/api/schemas.py +0 -0
  166. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/api/serve.py +0 -0
  167. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/api/workbench.py +0 -0
  168. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/csp/__init__.py +0 -0
  169. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/csp/pin_solver.py +0 -0
  170. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/designs/__init__.py +0 -0
  171. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/designs/active.py +0 -0
  172. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/designs/events.py +0 -0
  173. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/designs/seed.py +0 -0
  174. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/designs/store.py +0 -0
  175. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/enclosure/__init__.py +0 -0
  176. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/enclosure/openscad.py +0 -0
  177. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/enclosure/search.py +0 -0
  178. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/enclosure/yapp/LICENSE +0 -0
  179. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/enclosure/yapp/YAPPgenerator_v3.scad +0 -0
  180. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/errors.py +0 -0
  181. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/esphome_dashboard/__init__.py +0 -0
  182. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/__init__.py +0 -0
  183. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/access-panel.json +0 -0
  184. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/actuator-harness.json +0 -0
  185. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/air-quality-station.json +0 -0
  186. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/analog-node.json +0 -0
  187. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/atom-echo.json +0 -0
  188. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/atom-lite.json +0 -0
  189. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/atom-matrix.json +0 -0
  190. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/atoms3-lcd.json +0 -0
  191. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/atoms3-lite.json +0 -0
  192. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/atoms3-onboard.json +0 -0
  193. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/atomu-onboard.json +0 -0
  194. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/atomu.json +0 -0
  195. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/attic-logger.json +0 -0
  196. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/awning-control.json +0 -0
  197. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/bench-io.json +0 -0
  198. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/bl0906-mainmeter.json +0 -0
  199. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/blind-stepper.json +0 -0
  200. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/bluemotion.json +0 -0
  201. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/bluesonoff.json +0 -0
  202. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/button-toggles-light.json +0 -0
  203. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/c3-supermini.json +0 -0
  204. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/c6-supermini.json +0 -0
  205. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/co2-display.json +0 -0
  206. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/desk-climate.json +0 -0
  207. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/desk-matrix.json +0 -0
  208. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/distance-sensor.json +0 -0
  209. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/encoder-drives-stepper.json +0 -0
  210. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/energy-monitor.json +0 -0
  211. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/esp32-audio.json +0 -0
  212. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/esp32cam-wrover.json +0 -0
  213. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/esp32cam.json +0 -0
  214. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/flow-meter.json +0 -0
  215. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/garage-motion.json +0 -0
  216. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/grill-probe.json +0 -0
  217. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/guarded-button-light.json +0 -0
  218. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/heltec-v4-gnss-tracker.json +0 -0
  219. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/keypad.json +0 -0
  220. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/kitchen-scale.json +0 -0
  221. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/led-display.json +0 -0
  222. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/lorawan-battery-uplink.json +0 -0
  223. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/motion-turns-on-light.json +0 -0
  224. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/motor-position.json +0 -0
  225. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/multi-temp.json +0 -0
  226. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/nextion-thermostat.json +0 -0
  227. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/oled-knob.json +0 -0
  228. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/oled.json +0 -0
  229. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/output-hub.json +0 -0
  230. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/parking-distance.json +0 -0
  231. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/presence-media-node.json +0 -0
  232. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/presence-rf.json +0 -0
  233. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/rc522.json +0 -0
  234. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/room-climate.json +0 -0
  235. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/rs485-energy.json +0 -0
  236. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/s3-devkitc.json +0 -0
  237. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/s3-supermini.json +0 -0
  238. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/securitypanel.json +0 -0
  239. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/smart-plug-v1.json +0 -0
  240. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/smart-plug.json +0 -0
  241. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/solder-fan.json +0 -0
  242. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/spi-io-node.json +0 -0
  243. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/subghz-rfid.json +0 -0
  244. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/t-beam-lorawan.json +0 -0
  245. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/t-beam.json +0 -0
  246. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/temp-above-turns-on-fan.json +0 -0
  247. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/temp-turns-on-fan.json +0 -0
  248. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/tft-touch.json +0 -0
  249. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/ttgo-lora32.json +0 -0
  250. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/tuya-smart-plug.json +0 -0
  251. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/usbc-led-strip.json +0 -0
  252. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/vent-damper.json +0 -0
  253. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/wasserpir.json +0 -0
  254. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/weather-station.json +0 -0
  255. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/wemosgps.json +0 -0
  256. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/wled-sensor-strip.json +0 -0
  257. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/xiao-grove-climate.json +0 -0
  258. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/fleet/__init__.py +0 -0
  259. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/fleet/client.py +0 -0
  260. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/generate/__init__.py +0 -0
  261. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/generate/__main__.py +0 -0
  262. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/generate/ascii_gen.py +0 -0
  263. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/generate/circuitpython_gen.py +0 -0
  264. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/generate/display_intent.py +0 -0
  265. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/generate/micropython_gen.py +0 -0
  266. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/intent.py +0 -0
  267. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/inventory/__init__.py +0 -0
  268. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/inventory/buy.py +0 -0
  269. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/inventory/check.py +0 -0
  270. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/inventory/csv_io.py +0 -0
  271. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/inventory/match.py +0 -0
  272. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/inventory/store.py +0 -0
  273. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/jlcpcb/__init__.py +0 -0
  274. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/jlcpcb/__main__.py +0 -0
  275. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/jlcpcb/check.py +0 -0
  276. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/jlcpcb/client.py +0 -0
  277. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/kicad/__init__.py +0 -0
  278. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/kicad/erc.py +0 -0
  279. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/kicad/import.py +0 -0
  280. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/kicad/importer.py +0 -0
  281. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/kicad/render.py +0 -0
  282. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/kicad/schematic.py +0 -0
  283. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/kicad/symbol_parser.py +0 -0
  284. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/esp01_1m.yaml +0 -0
  285. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/esp32-c3-devkitm-1.yaml +0 -0
  286. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/esp32-c3-supermini.yaml +0 -0
  287. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/esp32-c6-supermini.yaml +0 -0
  288. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/esp32-devkitc-v4.yaml +0 -0
  289. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/esp32-s3-devkitc-1.yaml +0 -0
  290. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/esp32-s3-supermini.yaml +0 -0
  291. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/esp32-wrover-cam.yaml +0 -0
  292. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/esp32cam-ai-thinker.yaml +0 -0
  293. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/esp8285-1m.yaml +0 -0
  294. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/heltec-wifi-lora32-v2.yaml +0 -0
  295. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/heltec-wifi-lora32-v3.yaml +0 -0
  296. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/heltec-wifi-lora32-v4.yaml +0 -0
  297. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/m5stack-atom-echo.yaml +0 -0
  298. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/m5stack-atom-matrix.yaml +0 -0
  299. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/m5stack-atom.yaml +0 -0
  300. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/m5stack-atoms3-lite.yaml +0 -0
  301. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/m5stack-atoms3.yaml +0 -0
  302. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/m5stack-atomu.yaml +0 -0
  303. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/nodemcu-32s.yaml +0 -0
  304. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/nodemcu-v2.yaml +0 -0
  305. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/seeed-xiao-esp32c3.yaml +0 -0
  306. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/ttgo-lora32-v1.yaml +0 -0
  307. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/ttgo-t-beam.yaml +0 -0
  308. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/wemos-d1-mini.yaml +0 -0
  309. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/adc.yaml +0 -0
  310. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/ads1115.yaml +0 -0
  311. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/ads1115_channel.yaml +0 -0
  312. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/aht10.yaml +0 -0
  313. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/axp192.yaml +0 -0
  314. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/battery_adc.yaml +0 -0
  315. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/bl0906.yaml +0 -0
  316. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/bme280.yaml +0 -0
  317. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/bme680.yaml +0 -0
  318. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/bmp180.yaml +0 -0
  319. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/bmp280.yaml +0 -0
  320. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/cc1101.yaml +0 -0
  321. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/connector_1x02.yaml +0 -0
  322. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/connector_1x03.yaml +0 -0
  323. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/connector_1x04.yaml +0 -0
  324. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/connector_1x05.yaml +0 -0
  325. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/connector_1x06.yaml +0 -0
  326. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/connector_1x07.yaml +0 -0
  327. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/connector_1x08.yaml +0 -0
  328. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/connector_2x05_idc.yaml +0 -0
  329. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/cse7766.yaml +0 -0
  330. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/ct_clamp.yaml +0 -0
  331. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/dht.yaml +0 -0
  332. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/ds18b20.yaml +0 -0
  333. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/esp32_camera.yaml +0 -0
  334. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/gpio_input.yaml +0 -0
  335. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/gpio_output.yaml +0 -0
  336. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/grove-aht20.yaml +0 -0
  337. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/hbridge.yaml +0 -0
  338. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/hbridge_mosfet.yaml +0 -0
  339. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/hc-sr04.yaml +0 -0
  340. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/htu21d.yaml +0 -0
  341. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/hx711.yaml +0 -0
  342. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/i2s_microphone.yaml +0 -0
  343. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/ina219.yaml +0 -0
  344. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/lcd_pcf8574.yaml +0 -0
  345. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/ld2420.yaml +0 -0
  346. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/led_indicator.yaml +0 -0
  347. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/level_shifter.yaml +0 -0
  348. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/max31855.yaml +0 -0
  349. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/max7219.yaml +0 -0
  350. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/mcp3008.yaml +0 -0
  351. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/mcp3008_channel.yaml +0 -0
  352. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/mhz19.yaml +0 -0
  353. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/modbus.yaml +0 -0
  354. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/mosfet_low_side_driver.yaml +0 -0
  355. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/mpu6050.yaml +0 -0
  356. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/mpu6886.yaml +0 -0
  357. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/nextion.yaml +0 -0
  358. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/npn_low_side_driver.yaml +0 -0
  359. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/pn532.yaml +0 -0
  360. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/potentiometer.yaml +0 -0
  361. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/pulse_counter.yaml +0 -0
  362. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/pwm_fan.yaml +0 -0
  363. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/pzemac.yaml +0 -0
  364. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/rdm6300.yaml +0 -0
  365. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/remote_transmitter.yaml +0 -0
  366. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/rf_bridge.yaml +0 -0
  367. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/rotary_encoder.yaml +0 -0
  368. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/rtttl.yaml +0 -0
  369. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/scd4x.yaml +0 -0
  370. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/sdm_meter.yaml +0 -0
  371. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/sgp4x.yaml +0 -0
  372. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/sht3xd.yaml +0 -0
  373. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/sht4x.yaml +0 -0
  374. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/tm1638.yaml +0 -0
  375. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/tsl2561.yaml +0 -0
  376. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/tuya.yaml +0 -0
  377. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/tuya_sensor.yaml +0 -0
  378. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/tuya_switch.yaml +0 -0
  379. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/uart_gps.yaml +0 -0
  380. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/uln2003.yaml +0 -0
  381. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/usb_c_power_in.yaml +0 -0
  382. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/veml7700.yaml +0 -0
  383. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/voltage_divider.yaml +0 -0
  384. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/electrical.py +0 -0
  385. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/modules/env-display.yaml +0 -0
  386. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/modules/fan-controller.yaml +0 -0
  387. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/modules/motion-light.yaml +0 -0
  388. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/modules/oled-knob-13.yaml +0 -0
  389. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/modules/relay-4ch.yaml +0 -0
  390. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/mcp/__init__.py +0 -0
  391. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/mcp/auth.py +0 -0
  392. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/mcp/hardware.py +0 -0
  393. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/mcp/jobs.py +0 -0
  394. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/printwarden/__init__.py +0 -0
  395. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/printwarden/client.py +0 -0
  396. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/recommend/__init__.py +0 -0
  397. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/recommend/recommender.py +0 -0
  398. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/schema/__init__.py +0 -0
  399. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/seed.py +0 -0
  400. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/sqlite_store.py +0 -0
  401. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/__init__.py +0 -0
  402. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/base.py +0 -0
  403. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/build_backend.py +0 -0
  404. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/esphome.py +0 -0
  405. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/__init__.py +0 -0
  406. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/__main__.py +0 -0
  407. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/api.py +0 -0
  408. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/build_local.py +0 -0
  409. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/chirpstack.py +0 -0
  410. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/codec.py +0 -0
  411. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/compile.py +0 -0
  412. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/firmware_gen.py +0 -0
  413. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/templates/main.cpp.j2 +0 -0
  414. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/templates/platformio.ini.j2 +0 -0
  415. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/workbench_provision.py +0 -0
  416. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/tasmota.py +0 -0
  417. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/wled.py +0 -0
  418. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/wled_build.py +0 -0
  419. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/validate.py +0 -0
  420. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/workbench/__init__.py +0 -0
  421. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/workbench/boot.py +0 -0
  422. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/workbench/client.py +0 -0
  423. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/workbench/gate.py +0 -0
  424. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/workbench/images.py +0 -0
  425. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/workbench/serial.py +0 -0
  426. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio.egg-info/dependency_links.txt +0 -0
  427. {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio.egg-info/entry_points.txt +0 -0
  428. {wirestudio-0.43.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.43.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
@@ -19,6 +19,7 @@ Requires-Dist: httpx>=0.27
19
19
  Requires-Dist: slowapi>=0.1.9
20
20
  Requires-Dist: mcp<3,>=2.0
21
21
  Requires-Dist: websockets>=12
22
+ Requires-Dist: pillow>=10
22
23
  Provides-Extra: dev
23
24
  Requires-Dist: pytest>=8.0; extra == "dev"
24
25
  Requires-Dist: ruff>=0.4; extra == "dev"
@@ -27,6 +27,7 @@ dependencies = [
27
27
  "slowapi>=0.1.9",
28
28
  "mcp>=2.0,<3",
29
29
  "websockets>=12",
30
+ "pillow>=10",
30
31
  ]
31
32
 
32
33
  [project.optional-dependencies]
@@ -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
@@ -0,0 +1,248 @@
1
+ """Community designs: browse and import go through the catalogue's index
2
+ only, and share refuses what the catalogue's CI would."""
3
+ from __future__ import annotations
4
+
5
+ import io
6
+ import json
7
+ import zipfile
8
+ from pathlib import Path
9
+
10
+ import pytest
11
+ import yaml
12
+ from fastapi.testclient import TestClient
13
+
14
+ from wirestudio.api.app import create_app
15
+
16
+ REPO = "moellere/wirestudio-designs"
17
+ RAW = f"https://raw.githubusercontent.com/{REPO}"
18
+ EXAMPLE = json.loads((Path(__file__).resolve().parent.parent / "wirestudio" / "examples"
19
+ / "garage-motion.json").read_text())
20
+
21
+
22
+ def _entry(slug, **over):
23
+ return {"slug": slug, "name": "Porch motion", "author": "octocat", "license": "CC-BY-4.0",
24
+ "design_url": f"{RAW}/main/designs/{slug}/design.json", **over}
25
+
26
+
27
+ @pytest.fixture
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):
36
+ monkeypatch.delenv("WIRESTUDIO_DESIGNS_REPO", raising=False)
37
+ files: dict[str, bytes] = {}
38
+ fetched: list[str] = []
39
+
40
+ async def fetch(url):
41
+ fetched.append(url)
42
+ if url not in files:
43
+ raise RuntimeError(f"404 {url}")
44
+ return files[url]
45
+
46
+ def publish(*entries, designs=None, components=None):
47
+ files[f"{RAW}/index/index.json"] = json.dumps(
48
+ {"version": 1, "generated": "2026-10-23T00:00:00+00:00", "designs": list(entries)}).encode()
49
+ for slug, doc in (designs or {}).items():
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()
53
+
54
+ app = TestClient(create_app(library=user_library, community_fetch=fetch))
55
+ return app, publish, fetched
56
+
57
+
58
+ def test_status_names_the_catalogue(studio):
59
+ app, _, _ = studio
60
+ s = app.get("/community/status").json()
61
+ assert s["enabled"] and s["repo"] == REPO
62
+ assert s["upload_url"] == f"https://github.com/{REPO}/upload/main/designs"
63
+
64
+
65
+ def test_empty_repo_setting_turns_it_off(studio, monkeypatch):
66
+ app, _, _ = studio
67
+ monkeypatch.setenv("WIRESTUDIO_DESIGNS_REPO", "")
68
+ assert app.get("/community/status").json() == {"enabled": False}
69
+ assert app.get("/community/designs").status_code == 503
70
+
71
+
72
+ def test_browse_lists_the_index_and_caches_it(studio):
73
+ app, publish, fetched = studio
74
+ publish(_entry("porch-motion"))
75
+ assert [d["slug"] for d in app.get("/community/designs").json()["designs"]] == ["porch-motion"]
76
+ app.get("/community/designs")
77
+ assert fetched.count(f"{RAW}/index/index.json") == 1
78
+
79
+
80
+ def test_an_unreachable_index_is_a_502(studio):
81
+ app, _, _ = studio
82
+ r = app.get("/community/designs")
83
+ assert r.status_code == 502 and "index unavailable" in r.json()["detail"]
84
+
85
+
86
+ def test_import_stamps_the_origin(studio):
87
+ app, publish, _ = studio
88
+ publish(_entry("porch-motion"), designs={"porch-motion": {**EXAMPLE, "id": "porch-motion"}})
89
+ body = app.post("/community/designs/porch-motion/import").json()
90
+ assert body["design"]["origin"] == {"catalogue": REPO, "slug": "porch-motion",
91
+ "author": "octocat", "license": "CC-BY-4.0"}
92
+ assert app.post("/design/validate", json=body["design"]).status_code == 200
93
+
94
+
95
+ def test_import_only_follows_urls_inside_the_catalogue(studio):
96
+ app, publish, fetched = studio
97
+ publish(_entry("evil", design_url="http://169.254.169.254/latest/meta-data"))
98
+ assert app.post("/community/designs/evil/import").status_code == 502
99
+ assert not any("169.254" in u for u in fetched)
100
+
101
+
102
+ def test_import_of_an_unknown_slug_is_a_404(studio):
103
+ app, publish, _ = studio
104
+ publish()
105
+ assert app.post("/community/designs/nope/import").status_code == 404
106
+
107
+
108
+ def _share(app, **over):
109
+ body = {"design": {**EXAMPLE, "id": "Porch Motion v2"}, "author": "@octocat", "tags": ["motion"], **over}
110
+ return app.post("/community/share", json=body)
111
+
112
+
113
+ def test_share_packs_the_folder_the_catalogue_expects(studio):
114
+ app, _, _ = studio
115
+ r = _share(app, notes="Built on a DevKitC.")
116
+ assert r.status_code == 200 and r.headers["content-type"] == "application/zip"
117
+ z = zipfile.ZipFile(io.BytesIO(r.content))
118
+ assert sorted(z.namelist()) == ["porch-motion-v2/README.md", "porch-motion-v2/design.json",
119
+ "porch-motion-v2/meta.yaml"]
120
+ assert json.loads(z.read("porch-motion-v2/design.json"))["id"] == "porch-motion-v2"
121
+ assert yaml.safe_load(z.read("porch-motion-v2/meta.yaml")) == {"author": "octocat", "tags": ["motion"]}
122
+
123
+
124
+ def test_share_credits_the_design_it_was_imported_from(studio):
125
+ app, _, _ = studio
126
+ origin = {"catalogue": REPO, "slug": "porch-motion", "author": "someone", "license": "CC-BY-4.0"}
127
+ r = _share(app, design={**EXAMPLE, "id": "porch-motion-v2", "origin": origin})
128
+ z = zipfile.ZipFile(io.BytesIO(r.content))
129
+ assert yaml.safe_load(z.read("porch-motion-v2/meta.yaml"))["derived_from"] == "porch-motion"
130
+ assert "origin" not in json.loads(z.read("porch-motion-v2/design.json"))
131
+
132
+
133
+ def test_resharing_your_own_design_does_not_derive_from_itself(studio):
134
+ app, _, _ = studio
135
+ origin = {"catalogue": REPO, "slug": "porch-motion", "author": "octocat", "license": "CC-BY-4.0"}
136
+ r = _share(app, design={**EXAMPLE, "id": "porch-motion", "origin": origin})
137
+ z = zipfile.ZipFile(io.BytesIO(r.content))
138
+ assert "derived_from" not in yaml.safe_load(z.read("porch-motion/meta.yaml"))
139
+
140
+
141
+ def test_share_refuses_a_literal_credential(studio):
142
+ app, _, _ = studio
143
+ design = json.loads(json.dumps(EXAMPLE))
144
+ design["fleet"]["secrets_ref"]["wifi_password"] = "hunter22"
145
+ r = _share(app, design=design)
146
+ assert r.status_code == 422 and "fleet.secrets_ref.wifi_password" in r.json()["detail"]
147
+
148
+
149
+ @pytest.mark.parametrize("over, match", [
150
+ ({"author": "not a handle"}, "GitHub handle"),
151
+ ({"tags": ["Has Space"]}, "tags"),
152
+ ({"slug": "Bad_Slug"}, "not a usable name"),
153
+ ])
154
+ def test_share_refuses_what_the_catalogue_would(studio, over, match):
155
+ app, _, _ = studio
156
+ r = _share(app, **over)
157
+ assert r.status_code == 422 and match in r.json()["detail"]
158
+
159
+
160
+ def _jpeg(size=(3000, 2000), exif=True, fmt="JPEG") -> str:
161
+ import base64
162
+
163
+ from PIL import Image
164
+
165
+ img = Image.new("RGB", size, (200, 80, 40))
166
+ kwargs = {}
167
+ if exif:
168
+ e = Image.Exif()
169
+ e[0x010F] = "PhoneMaker"
170
+ e[0x0112] = 6 # Orientation: rotate 90 CW on display
171
+ kwargs["exif"] = e
172
+ buf = io.BytesIO()
173
+ img.save(buf, fmt, **kwargs)
174
+ return base64.b64encode(buf.getvalue()).decode()
175
+
176
+
177
+ def test_share_re_encodes_photos_without_metadata(studio):
178
+ from PIL import Image
179
+
180
+ app, _, _ = studio
181
+ r = _share(app, photos=[{"name": "IMG_0042.JPG", "data": _jpeg()},
182
+ {"name": "IMG_0042.JPG", "data": _jpeg(fmt="PNG", exif=False)}])
183
+ assert r.status_code == 200, r.text
184
+ z = zipfile.ZipFile(io.BytesIO(r.content))
185
+ names = sorted(n for n in z.namelist() if "/photos/" in n)
186
+ assert names == ["porch-motion-v2/photos/img-0042-2.jpg", "porch-motion-v2/photos/img-0042.jpg"]
187
+ with Image.open(io.BytesIO(z.read("porch-motion-v2/photos/img-0042.jpg"))) as img:
188
+ assert img.format == "JPEG" and not img.getexif()
189
+ assert img.size == (1365, 2048), "the EXIF rotation is applied before EXIF is dropped"
190
+ assert all(z.getinfo(n).file_size <= 1024 * 1024 for n in names)
191
+
192
+
193
+ def test_share_refuses_what_is_not_a_photo(studio):
194
+ import base64
195
+
196
+ app, _, _ = studio
197
+ r = _share(app, photos=[{"name": "notes.txt", "data": base64.b64encode(b"hello").decode()}])
198
+ assert r.status_code == 422 and "not a readable image" in r.json()["detail"]
199
+ r = _share(app, photos=[{"name": f"p{i}.jpg", "data": _jpeg((10, 10))} for i in range(5)])
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"]