wirestudio 0.45.0__tar.gz → 0.46.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 (431) hide show
  1. {wirestudio-0.45.0/wirestudio.egg-info → wirestudio-0.46.0}/PKG-INFO +5 -4
  2. {wirestudio-0.45.0 → wirestudio-0.46.0}/README.md +4 -3
  3. wirestudio-0.46.0/tests/test_board_check.py +205 -0
  4. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_community.py +141 -2
  5. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_firmware_gen.py +73 -5
  6. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_kicad.py +6 -0
  7. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_kicad_erc.py +29 -5
  8. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_kicad_pcb.py +16 -0
  9. wirestudio-0.46.0/tests/test_lorawan_fragments.py +279 -0
  10. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_mcp_server.py +3 -0
  11. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/__init__.py +1 -1
  12. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/agent/agent.py +1 -1
  13. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/agent/tools.py +80 -0
  14. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/api/app.py +54 -9
  15. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/api/community.py +140 -27
  16. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/api/schemas.py +18 -0
  17. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/kicad/erc.py +19 -0
  18. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/kicad/generator.py +3 -1
  19. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/kicad/netlist.py +27 -0
  20. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/__init__.py +94 -48
  21. wirestudio-0.46.0/wirestudio/library/board_check.py +376 -0
  22. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/boards/esp32-c3-devkitm-1.yaml +24 -0
  23. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/boards/esp32-s3-devkitc-1.yaml +31 -0
  24. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/check.py +1 -1
  25. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/bl0906.yaml +164 -0
  26. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/cse7766.yaml +58 -0
  27. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/ld2410.yaml +56 -0
  28. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/ld2420.yaml +64 -0
  29. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/max31855.yaml +38 -0
  30. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/mcp23008.yaml +1 -0
  31. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/mcp23017.yaml +1 -0
  32. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/mcp23s17.yaml +1 -0
  33. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/mcp3008.yaml +17 -0
  34. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/mcp3008_channel.yaml +22 -0
  35. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/mhz19.yaml +36 -0
  36. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/modbus.yaml +54 -0
  37. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/pcf8574.yaml +1 -0
  38. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/pmsx003.yaml +55 -0
  39. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/pzemac.yaml +71 -0
  40. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/sdm_meter.yaml +78 -0
  41. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/sn74hc595.yaml +1 -0
  42. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/uart_gps.yaml +2 -1
  43. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/mcp/server.py +41 -0
  44. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/targets/lorawan/codec.py +13 -0
  45. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/targets/lorawan/firmware_gen.py +99 -57
  46. {wirestudio-0.45.0 → wirestudio-0.46.0/wirestudio.egg-info}/PKG-INFO +5 -4
  47. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio.egg-info/SOURCES.txt +3 -0
  48. {wirestudio-0.45.0 → wirestudio-0.46.0}/LICENSE +0 -0
  49. {wirestudio-0.45.0 → wirestudio-0.46.0}/LICENSES.md +0 -0
  50. {wirestudio-0.45.0 → wirestudio-0.46.0}/pyproject.toml +0 -0
  51. {wirestudio-0.45.0 → wirestudio-0.46.0}/setup.cfg +0 -0
  52. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_active_design.py +0 -0
  53. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_agent.py +0 -0
  54. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_agent_eval.py +0 -0
  55. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_agent_stream.py +0 -0
  56. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_api.py +0 -0
  57. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_api_auth.py +0 -0
  58. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_api_route.py +0 -0
  59. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_ascii_gen.py +0 -0
  60. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_build_backend.py +0 -0
  61. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_check_upgrade.py +0 -0
  62. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_chirpstack.py +0 -0
  63. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_circuitpython.py +0 -0
  64. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_codec.py +0 -0
  65. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_compatibility.py +0 -0
  66. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_compile.py +0 -0
  67. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_component_corpus.py +0 -0
  68. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_connectors.py +0 -0
  69. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_coverage_baseline.py +0 -0
  70. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_design_events.py +0 -0
  71. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_designs.py +0 -0
  72. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_designs_seed.py +0 -0
  73. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_display_intent.py +0 -0
  74. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_electrical.py +0 -0
  75. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_enclosure.py +0 -0
  76. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_enclosure_gate.py +0 -0
  77. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_enclosure_search.py +0 -0
  78. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_errors.py +0 -0
  79. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_esphome_dashboard.py +0 -0
  80. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_esphome_support.py +0 -0
  81. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_fleet.py +0 -0
  82. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_footprints.py +0 -0
  83. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_framework_baseline.py +0 -0
  84. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_hardware_gate.py +0 -0
  85. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_intent.py +0 -0
  86. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_inventory.py +0 -0
  87. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_jlcpcb.py +0 -0
  88. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_kicad_drc.py +0 -0
  89. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_kicad_fab.py +0 -0
  90. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_kicad_import.py +0 -0
  91. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_kicad_render.py +0 -0
  92. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_kicad_route.py +0 -0
  93. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_kicad_sch.py +0 -0
  94. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_library_check.py +0 -0
  95. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_library_hbridge.py +0 -0
  96. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_library_rtttl.py +0 -0
  97. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_lorawan.py +0 -0
  98. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_lorawan_api.py +0 -0
  99. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_lorawan_sugar_migration.py +0 -0
  100. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_mcp_auth.py +0 -0
  101. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_mcp_client.py +0 -0
  102. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_mcp_hardware.py +0 -0
  103. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_mcp_jobs.py +0 -0
  104. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_mcp_resources.py +0 -0
  105. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_meshtastic.py +0 -0
  106. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_micropython.py +0 -0
  107. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_micropython_registers.py +0 -0
  108. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_migrate.py +0 -0
  109. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_modules.py +0 -0
  110. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_pin_protocols.py +0 -0
  111. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_pin_solver.py +0 -0
  112. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_printwarden.py +0 -0
  113. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_python_blocks.py +0 -0
  114. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_recommend.py +0 -0
  115. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_register_code.py +0 -0
  116. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_rf_ook.py +0 -0
  117. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_schema.py +0 -0
  118. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_schema_doc.py +0 -0
  119. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_schematics_gate.py +0 -0
  120. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_security_cors.py +0 -0
  121. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_seed_onboard.py +0 -0
  122. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_serve.py +0 -0
  123. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_sqlite_store.py +0 -0
  124. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_sqlite_stores.py +0 -0
  125. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_strict.py +0 -0
  126. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_targets.py +0 -0
  127. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_tasmota.py +0 -0
  128. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_tbeam_lorawan_example.py +0 -0
  129. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_tools_add_bus.py +0 -0
  130. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_tools_add_component.py +0 -0
  131. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_tools_solve_pins.py +0 -0
  132. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_uart_frames.py +0 -0
  133. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_validate.py +0 -0
  134. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_wled.py +0 -0
  135. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_workbench.py +0 -0
  136. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_workbench_boot.py +0 -0
  137. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_workbench_images.py +0 -0
  138. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_workbench_provision.py +0 -0
  139. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_workbench_serial.py +0 -0
  140. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_ws2812_method.py +0 -0
  141. {wirestudio-0.45.0 → wirestudio-0.46.0}/tests/test_yaml_gen.py +0 -0
  142. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/agent/__init__.py +0 -0
  143. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/agent/eval.py +0 -0
  144. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/agent/eval_tasks.yaml +0 -0
  145. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/agent/session.py +0 -0
  146. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/api/__init__.py +0 -0
  147. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/api/__main__.py +0 -0
  148. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/api/auth.py +0 -0
  149. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/api/circuitpython.py +0 -0
  150. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/api/design_input.py +0 -0
  151. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/api/meshtastic.py +0 -0
  152. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/api/micropython.py +0 -0
  153. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/api/serve.py +0 -0
  154. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/api/workbench.py +0 -0
  155. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/csp/__init__.py +0 -0
  156. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/csp/compatibility.py +0 -0
  157. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/csp/pin_solver.py +0 -0
  158. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/designs/__init__.py +0 -0
  159. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/designs/active.py +0 -0
  160. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/designs/events.py +0 -0
  161. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/designs/seed.py +0 -0
  162. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/designs/store.py +0 -0
  163. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/enclosure/__init__.py +0 -0
  164. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/enclosure/openscad.py +0 -0
  165. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/enclosure/search.py +0 -0
  166. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/enclosure/yapp/LICENSE +0 -0
  167. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/enclosure/yapp/YAPPgenerator_v3.scad +0 -0
  168. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/errors.py +0 -0
  169. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/esphome_dashboard/__init__.py +0 -0
  170. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/__init__.py +0 -0
  171. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/access-panel.json +0 -0
  172. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/actuator-harness.json +0 -0
  173. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/air-quality-station.json +0 -0
  174. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/analog-node.json +0 -0
  175. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/atom-echo.json +0 -0
  176. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/atom-lite.json +0 -0
  177. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/atom-matrix.json +0 -0
  178. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/atoms3-lcd.json +0 -0
  179. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/atoms3-lite.json +0 -0
  180. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/atoms3-onboard.json +0 -0
  181. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/atomu-onboard.json +0 -0
  182. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/atomu.json +0 -0
  183. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/attic-logger.json +0 -0
  184. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/awning-control.json +0 -0
  185. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/bench-io.json +0 -0
  186. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/bl0906-mainmeter.json +0 -0
  187. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/blind-stepper.json +0 -0
  188. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/bluemotion.json +0 -0
  189. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/bluesonoff.json +0 -0
  190. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/button-toggles-light.json +0 -0
  191. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/c3-supermini.json +0 -0
  192. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/c6-supermini.json +0 -0
  193. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/co2-display.json +0 -0
  194. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/desk-climate.json +0 -0
  195. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/desk-matrix.json +0 -0
  196. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/distance-sensor.json +0 -0
  197. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/encoder-drives-stepper.json +0 -0
  198. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/energy-monitor.json +0 -0
  199. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/esp32-audio.json +0 -0
  200. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/esp32cam-wrover.json +0 -0
  201. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/esp32cam.json +0 -0
  202. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/flow-meter.json +0 -0
  203. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/garage-motion.json +0 -0
  204. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/grill-probe.json +0 -0
  205. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/guarded-button-light.json +0 -0
  206. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/heltec-v4-gnss-tracker.json +0 -0
  207. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/keypad.json +0 -0
  208. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/kitchen-scale.json +0 -0
  209. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/led-display.json +0 -0
  210. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/lorawan-battery-uplink.json +0 -0
  211. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/motion-turns-on-light.json +0 -0
  212. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/motor-position.json +0 -0
  213. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/multi-temp.json +0 -0
  214. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/nextion-thermostat.json +0 -0
  215. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/oled-knob.json +0 -0
  216. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/oled.json +0 -0
  217. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/output-hub.json +0 -0
  218. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/parking-distance.json +0 -0
  219. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/presence-media-node.json +0 -0
  220. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/presence-rf.json +0 -0
  221. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/rc522.json +0 -0
  222. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/room-climate.json +0 -0
  223. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/rs485-energy.json +0 -0
  224. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/s3-devkitc.json +0 -0
  225. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/s3-supermini.json +0 -0
  226. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/securitypanel.json +0 -0
  227. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/smart-plug-v1.json +0 -0
  228. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/smart-plug.json +0 -0
  229. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/solder-fan.json +0 -0
  230. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/spi-io-node.json +0 -0
  231. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/subghz-rfid.json +0 -0
  232. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/t-beam-lorawan.json +0 -0
  233. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/t-beam.json +0 -0
  234. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/temp-above-turns-on-fan.json +0 -0
  235. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/temp-turns-on-fan.json +0 -0
  236. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/tft-touch.json +0 -0
  237. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/ttgo-lora32.json +0 -0
  238. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/tuya-smart-plug.json +0 -0
  239. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/usbc-led-strip.json +0 -0
  240. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/vent-damper.json +0 -0
  241. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/wasserpir.json +0 -0
  242. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/weather-station.json +0 -0
  243. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/wemosgps.json +0 -0
  244. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/wled-sensor-strip.json +0 -0
  245. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/examples/xiao-grove-climate.json +0 -0
  246. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/fleet/__init__.py +0 -0
  247. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/fleet/client.py +0 -0
  248. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/generate/__init__.py +0 -0
  249. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/generate/__main__.py +0 -0
  250. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/generate/ascii_gen.py +0 -0
  251. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/generate/circuitpython_gen.py +0 -0
  252. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/generate/display_intent.py +0 -0
  253. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/generate/micropython_gen.py +0 -0
  254. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/generate/yaml_gen.py +0 -0
  255. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/intent.py +0 -0
  256. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/inventory/__init__.py +0 -0
  257. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/inventory/buy.py +0 -0
  258. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/inventory/check.py +0 -0
  259. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/inventory/csv_io.py +0 -0
  260. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/inventory/match.py +0 -0
  261. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/inventory/store.py +0 -0
  262. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/jlcpcb/__init__.py +0 -0
  263. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/jlcpcb/__main__.py +0 -0
  264. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/jlcpcb/check.py +0 -0
  265. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/jlcpcb/client.py +0 -0
  266. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/kicad/__init__.py +0 -0
  267. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/kicad/drc.py +0 -0
  268. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/kicad/fab.py +0 -0
  269. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/kicad/import.py +0 -0
  270. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/kicad/importer.py +0 -0
  271. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/kicad/pcb.py +0 -0
  272. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/kicad/pcbnew_bridge.py +0 -0
  273. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/kicad/render.py +0 -0
  274. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/kicad/route.py +0 -0
  275. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/kicad/schematic.py +0 -0
  276. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/kicad/symbol_parser.py +0 -0
  277. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/boards/esp01_1m.yaml +0 -0
  278. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/boards/esp32-c3-supermini.yaml +0 -0
  279. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/boards/esp32-c6-supermini.yaml +0 -0
  280. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/boards/esp32-devkitc-v4.yaml +0 -0
  281. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/boards/esp32-s3-supermini.yaml +0 -0
  282. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/boards/esp32-wrover-cam.yaml +0 -0
  283. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/boards/esp32cam-ai-thinker.yaml +0 -0
  284. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/boards/esp8285-1m.yaml +0 -0
  285. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/boards/heltec-wifi-lora32-v2.yaml +0 -0
  286. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/boards/heltec-wifi-lora32-v3.yaml +0 -0
  287. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/boards/heltec-wifi-lora32-v4.yaml +0 -0
  288. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/boards/m5stack-atom-echo.yaml +0 -0
  289. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/boards/m5stack-atom-matrix.yaml +0 -0
  290. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/boards/m5stack-atom.yaml +0 -0
  291. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/boards/m5stack-atoms3-lite.yaml +0 -0
  292. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/boards/m5stack-atoms3.yaml +0 -0
  293. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/boards/m5stack-atomu.yaml +0 -0
  294. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/boards/nodemcu-32s.yaml +0 -0
  295. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/boards/nodemcu-v2.yaml +0 -0
  296. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/boards/seeed-xiao-esp32c3.yaml +0 -0
  297. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/boards/ttgo-lora32-v1.yaml +0 -0
  298. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/boards/ttgo-lora32-v2.yaml +0 -0
  299. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/boards/ttgo-t-beam.yaml +0 -0
  300. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/boards/wemos-d1-mini.yaml +0 -0
  301. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/adc.yaml +0 -0
  302. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/ads1115.yaml +0 -0
  303. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/ads1115_channel.yaml +0 -0
  304. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/aht10.yaml +0 -0
  305. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/apa102.yaml +0 -0
  306. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/axp192.yaml +0 -0
  307. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/battery_adc.yaml +0 -0
  308. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/bh1750.yaml +0 -0
  309. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/bme280.yaml +0 -0
  310. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/bme680.yaml +0 -0
  311. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/bmp180.yaml +0 -0
  312. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/bmp280.yaml +0 -0
  313. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/cc1101.yaml +0 -0
  314. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/connector_1x02.yaml +0 -0
  315. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/connector_1x03.yaml +0 -0
  316. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/connector_1x04.yaml +0 -0
  317. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/connector_1x05.yaml +0 -0
  318. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/connector_1x06.yaml +0 -0
  319. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/connector_1x07.yaml +0 -0
  320. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/connector_1x08.yaml +0 -0
  321. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/connector_2x05_idc.yaml +0 -0
  322. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/ct_clamp.yaml +0 -0
  323. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/dht.yaml +0 -0
  324. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/ds18b20.yaml +0 -0
  325. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/esp32_camera.yaml +0 -0
  326. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/esp32_rmt_led_strip.yaml +0 -0
  327. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/gpio_input.yaml +0 -0
  328. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/gpio_output.yaml +0 -0
  329. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/grove-aht20.yaml +0 -0
  330. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/hbridge.yaml +0 -0
  331. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/hbridge_mosfet.yaml +0 -0
  332. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/hc-sr04.yaml +0 -0
  333. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/hc-sr501.yaml +0 -0
  334. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/hlw8012.yaml +0 -0
  335. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/htu21d.yaml +0 -0
  336. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/hx711.yaml +0 -0
  337. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/i2s_microphone.yaml +0 -0
  338. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/ili9xxx.yaml +0 -0
  339. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/ina219.yaml +0 -0
  340. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/lcd_pcf8574.yaml +0 -0
  341. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/led_indicator.yaml +0 -0
  342. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/level_shifter.yaml +0 -0
  343. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/max7219.yaml +0 -0
  344. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/max98357a.yaml +0 -0
  345. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/mosfet_low_side_driver.yaml +0 -0
  346. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/mpu6050.yaml +0 -0
  347. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/mpu6886.yaml +0 -0
  348. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/nextion.yaml +0 -0
  349. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/npn_low_side_driver.yaml +0 -0
  350. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/pca9685.yaml +0 -0
  351. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/pn532.yaml +0 -0
  352. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/potentiometer.yaml +0 -0
  353. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/pulse_counter.yaml +0 -0
  354. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/pwm_fan.yaml +0 -0
  355. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/rc522.yaml +0 -0
  356. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/rcwl-0516.yaml +0 -0
  357. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/rdm6300.yaml +0 -0
  358. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/remote_receiver.yaml +0 -0
  359. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/remote_transmitter.yaml +0 -0
  360. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/rf_bridge.yaml +0 -0
  361. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/rotary_encoder.yaml +0 -0
  362. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/rtttl.yaml +0 -0
  363. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/scd4x.yaml +0 -0
  364. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/servo.yaml +0 -0
  365. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/sgp4x.yaml +0 -0
  366. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/sht3xd.yaml +0 -0
  367. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/sht4x.yaml +0 -0
  368. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/ssd1306.yaml +0 -0
  369. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/st7789.yaml +0 -0
  370. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/sx127x.yaml +0 -0
  371. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/tm1637.yaml +0 -0
  372. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/tm1638.yaml +0 -0
  373. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/tsl2561.yaml +0 -0
  374. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/tuya.yaml +0 -0
  375. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/tuya_sensor.yaml +0 -0
  376. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/tuya_switch.yaml +0 -0
  377. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/uln2003.yaml +0 -0
  378. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/usb_c_power_in.yaml +0 -0
  379. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/veml7700.yaml +0 -0
  380. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/vl53l0x.yaml +0 -0
  381. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/voltage_divider.yaml +0 -0
  382. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/wiegand.yaml +0 -0
  383. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/ws2812b.yaml +0 -0
  384. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/components/xpt2046.yaml +0 -0
  385. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/electrical.py +0 -0
  386. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/modules/env-display.yaml +0 -0
  387. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/modules/fan-controller.yaml +0 -0
  388. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/modules/motion-light.yaml +0 -0
  389. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/modules/oled-knob-13.yaml +0 -0
  390. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/library/modules/relay-4ch.yaml +0 -0
  391. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/mcp/__init__.py +0 -0
  392. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/mcp/auth.py +0 -0
  393. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/mcp/hardware.py +0 -0
  394. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/mcp/jobs.py +0 -0
  395. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/migrate.py +0 -0
  396. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/model.py +0 -0
  397. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/printwarden/__init__.py +0 -0
  398. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/printwarden/client.py +0 -0
  399. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/recommend/__init__.py +0 -0
  400. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/recommend/recommender.py +0 -0
  401. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/schema/__init__.py +0 -0
  402. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/schema/design.schema.json +0 -0
  403. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/seed.py +0 -0
  404. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/sqlite_store.py +0 -0
  405. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/targets/__init__.py +0 -0
  406. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/targets/base.py +0 -0
  407. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/targets/build_backend.py +0 -0
  408. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/targets/esphome.py +0 -0
  409. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/targets/lorawan/__init__.py +0 -0
  410. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/targets/lorawan/__main__.py +0 -0
  411. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/targets/lorawan/api.py +0 -0
  412. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/targets/lorawan/build_local.py +0 -0
  413. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/targets/lorawan/chirpstack.py +0 -0
  414. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/targets/lorawan/compile.py +0 -0
  415. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/targets/lorawan/templates/main.cpp.j2 +0 -0
  416. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/targets/lorawan/templates/platformio.ini.j2 +0 -0
  417. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/targets/lorawan/workbench_provision.py +0 -0
  418. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/targets/tasmota.py +0 -0
  419. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/targets/wled.py +0 -0
  420. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/targets/wled_build.py +0 -0
  421. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/validate.py +0 -0
  422. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/workbench/__init__.py +0 -0
  423. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/workbench/boot.py +0 -0
  424. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/workbench/client.py +0 -0
  425. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/workbench/gate.py +0 -0
  426. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/workbench/images.py +0 -0
  427. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio/workbench/serial.py +0 -0
  428. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio.egg-info/dependency_links.txt +0 -0
  429. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio.egg-info/entry_points.txt +0 -0
  430. {wirestudio-0.45.0 → wirestudio-0.46.0}/wirestudio.egg-info/requires.txt +0 -0
  431. {wirestudio-0.45.0 → wirestudio-0.46.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.45.0
3
+ Version: 0.46.0
4
4
  Summary: Agent-driven IoT device design tool that produces ESPHome YAML.
5
5
  Author: wirestudio contributors
6
6
  License: MIT
@@ -147,8 +147,8 @@ Tiers, in priority order:
147
147
  | **Verified** | CSP pin solver + compat checker | assign legal pins, surface boot-strap / ADC2-WiFi / voltage / locked-pin issues | unit tests + property checks in `tests/test_pin_solver.py` + `tests/test_compatibility.py` |
148
148
  | **Verified** | Fleet handoff | push YAML to `fleet-for-esphome` ha-addon, optional compile + log relay | round-trip tests in `tests/test_fleet.py` |
149
149
  | **Verified** | KiCad schematic | write a KiCad 8 `.kicad_sch` the studio draws itself (board, peripherals, subcircuits drawn as circuits from their library layouts; symbols embedded), plus a SKiDL script for the same design | every bundled example builds a KiCad netlist against the pinned upstream symbol libraries, every PR ([gate](.github/workflows/kicad-schematic.yml)) — no unresolved symbols or pins; kicad-cli reads the netlist back from every written sheet's geometry and it must match the design's, then `kicad-cli sch erc` runs on all of them against a baseline of the violation types the writer cannot avoid ([gate](.github/workflows/kicad-render.yml)). Parts KiCad ships no symbol for (sensor/module breakouts) render as labeled generic headers |
150
- | **Verified** | KiCad PCB layout | emit a placed, unrouted `.kicad_pcb` — footprints, nets, ratsnest, edge cuts | every bundled example emits a structurally sound board, every PR ([gate](.github/workflows/pcb-layout.yml)); a DRC tier opens each board in real KiCad ([gate](.github/workflows/pcb-drc.yml)) |
151
- | **Verified** | Fab outputs | JLCPCB upload bundle — BOM, CPL, Gerber + drill (`/design/fab/*`) | CPL positions match the `.kicad_pcb` placement; the DRC tier smoke-tests the Gerber export. Boards are unrouted until the Freerouting step, so Gerbers carry pads but no traces (`is_routed` flags it) |
150
+ | **Verified** | KiCad PCB layout | emit a placed, unrouted `.kicad_pcb` — footprints, nets, ratsnest, edge cuts, and a GND pour on the bottom layer that the route step fills | every bundled example emits a structurally sound board, every PR ([gate](.github/workflows/pcb-layout.yml)); a DRC tier opens each board in real KiCad ([gate](.github/workflows/pcb-drc.yml)) |
151
+ | **Verified** | Fab outputs | JLCPCB upload bundle — BOM, CPL, Gerber + drill (`/design/fab/*`) | CPL positions match the `.kicad_pcb` placement; the DRC tier smoke-tests the Gerber export. `?route=true` routes the board first; without it Gerbers carry pads but no tracks or pour (`is_routed` flags it). The routed gate checks a routed board's B.Cu Gerber carries the GND pour ([gate](.github/workflows/pcb-route.yml)) |
152
152
  | **Verified** | Subcircuits + building blocks | a component built from discrete parts (five bundled blocks, two H-bridges) expands into the schematic, PCB, BOM, CPL and inventory check; part values are parametric; each block's `verify.checks` rule runs at `validate` with the real params and rails | goldens for every subcircuit example pin YAML + BOM; `component_check` fails a block whose defaults break its own rule (`tests/test_electrical.py`); the schematic and PCB gates cover the expanded parts like any other |
153
153
  | **Verified** | Inventory check | cross-check a design's BOM against the drawer (library modules + discrete parts), propose same-package substitutes ranked by pinout, package, rating headroom and stock, accept one or all of them as `part_overrides` that every fab output honours, pick list by location, buy list priced on JLCPCB | matching, substitution and coverage tests in `tests/test_inventory.py` against the real drawer CSV; JLCPCB pricing is mocked and degrades to an unpriced list when the parts API is down |
154
154
  | **Verified** | Parametric enclosure | OpenSCAD `.scad` from board mount-hole metadata, built on [YAPP_Box](https://github.com/mrWheel/YAPP_Box): base and lid with standoffs, port cutouts, a ridge and snap joins | every enclosure-capable board renders through real OpenSCAD to a non-empty, manifold (closed, printable) solid, every PR ([gate](.github/workflows/enclosure-render.yml)) |
@@ -160,12 +160,13 @@ Tiers, in priority order:
160
160
  | **Works (lighter checks)** | Meshtastic flashing + config | proxy the official release factory image (`/meshtastic/firmware`) for the radio boards, flash it via the unified WebSerial dialog, then push region, modem preset, owner and primary channel as protobufs over the same port | endpoint tests with mocked upstream; board-to-variant map checked against `meshtastic/firmware` variants; config push tested against a fake device; no live-flash gate |
161
161
  | **Works (lighter checks)** | MicroPython flashing + main.py | proxy the official release image for the board's chip (`/micropython/firmware`; every library board maps to a generic port image, ESP8266 included), flash via the unified dialog, generate `main.py` from the design over the `machine` API (`/micropython/code`) and push it through the raw REPL over the same serial port | endpoint tests with a captured download page; every board's starter and every example's main.py parse as Python; the raw-REPL push is tested against a fake board; the micropython.org page format could not be fetched from the build session, so the first live flash validates it; no live-flash gate |
162
162
  | **Verified** | Component authoring | write a component YAML in the studio or through `component_create`; the same gate as `component_check` (schema, subcircuit nets, template render, KiCad symbols / pins / footprint) before it lands in the user library | a user-authored component goes through the gate and its synthetic design through upstream `esphome config`, every PR ([gate](.github/workflows/esphome-config.yml)); the gate rejects every file in a corpus of bad components for the named reason (`tests/test_component_corpus.py`); KiCad resolution runs only where the toolchain is present |
163
+ | **Verified** | Board authoring | write a board YAML in the studio or through `board_create`; the same gate as `board_check` (schema, every bus / onboard / radio pin against `gpio_capabilities`, KiCad symbol pins and footprint, enclosure geometry, ESPHome's board table, an ESPHome render with the onboard peripherals) before it lands in the user library | every bundled board passes the gate (`tests/test_board_check.py`); a user-authored board with its onboard peripherals goes through upstream `esphome config`, every PR ([gate](.github/workflows/esphome-config.yml)); the pinout is not checked against the datasheet |
163
164
  | **Verified** | ESPHome dashboard compile | push the rendered YAML to a running ESPHome Device Builder (`/esphome/push`), start a compile there, stream the log and serve the firmware, with the same job shapes as the fleet path | nightly, against a live Device Builder (ESPHome 2026.10.0) open and password-protected: push, compile over the WebSocket API, fetch the image, with the client's wire assumptions asserted ([dashboard](.github/workflows/esphome-compile.yml)); client + route tests against a fake of the same in `tests/test_esphome_dashboard.py` |
164
165
  | **Works (lighter checks)** | CircuitPython flashing | proxy the official release image (`/circuitpython/firmware`) for ESP32/S3/C3/C6 boards, flash via the unified dialog, serve a generated starter `code.py` (`/circuitpython/code`) | endpoint tests with mocked upstream; every board-id mapping verified against downloads.circuitpython.org; generated starters parse as valid Python; no live-flash gate |
165
166
  | **Verified** | MCP server | 53 tools over the Model Context Protocol: 31 design / library / inventory / fab, plus 22 that reach hardware — workbench flash and boot verification, LoRaWAN compile / provision / activation, fleet and dashboard push + build status, and job polling for the long ones | the `mcp` package's own client connects to the mounted server over Streamable HTTP with the bearer token, lists the tools, drives a design from `create_design` to `render` and reads a resource, every PR (`tests/test_mcp_client.py`); tool / auth / resource tests in `tests/test_mcp_*.py`; the hardware tools drive the real clients against wire-level fakes, and were exercised against a live bench, ChirpStack and fleet during bring-up |
166
167
  | **Experimental** | Thingiverse search relay | rank community models for a board | smoke-tested; depends on a third-party search API that ranks unevenly |
167
168
  | **Verified** | Agent (Claude tool-using) | natural-language design driving | scored nightly against the 28-task list in `wirestudio/agent/eval_tasks.yaml`: the design it hands back passes `esphome config`, carries the asked-for change, and the turn stayed within its tool-call budget ([gate](.github/workflows/agent-eval.yml)); every task's reference solution passes the same scorer on every PR (`tests/test_agent_eval.py`). The nightly run drives the studio through Claude Code over MCP with the `CLAUDE_CODE_OAUTH_TOKEN` secret (a subscription); the embedded agent is scored too when `ANTHROPIC_API_KEY` is set. Each driver's first scored run sets its gate |
168
- | **Verified** | PCB autorouting | Freerouting roundtrip — board → Specctra DSN → routed → SES import; SSE route endpoint, `route_pcb` MCP/agent tool, web-UI Route button, `?route=true` fab exports, `-pcb` image variant | representative examples route with zero unconnected items and pass routed DRC ([gate](.github/workflows/pcb-route.yml)) |
169
+ | **Verified** | PCB autorouting | Freerouting roundtrip — board → Specctra DSN → routed → SES import, GND pour filled; via cost and keepout rectangles as parameters; KiCad's DRC of the routed board reported back; SSE route endpoint, `route_pcb` MCP/agent tool, web-UI Route button, `?route=true` fab exports, `-pcb` image variant | representative examples route with zero unconnected items, a filled pour and a clean routed DRC; a high via cost must remove vias and a keepout must clear the track under it ([gate](.github/workflows/pcb-route.yml)) |
169
170
 
170
171
  The **Verified** tier is the bar the project is asking to be judged
171
172
  on. Everything else is offered with the caveat that's spelled out in
@@ -113,8 +113,8 @@ Tiers, in priority order:
113
113
  | **Verified** | CSP pin solver + compat checker | assign legal pins, surface boot-strap / ADC2-WiFi / voltage / locked-pin issues | unit tests + property checks in `tests/test_pin_solver.py` + `tests/test_compatibility.py` |
114
114
  | **Verified** | Fleet handoff | push YAML to `fleet-for-esphome` ha-addon, optional compile + log relay | round-trip tests in `tests/test_fleet.py` |
115
115
  | **Verified** | KiCad schematic | write a KiCad 8 `.kicad_sch` the studio draws itself (board, peripherals, subcircuits drawn as circuits from their library layouts; symbols embedded), plus a SKiDL script for the same design | every bundled example builds a KiCad netlist against the pinned upstream symbol libraries, every PR ([gate](.github/workflows/kicad-schematic.yml)) — no unresolved symbols or pins; kicad-cli reads the netlist back from every written sheet's geometry and it must match the design's, then `kicad-cli sch erc` runs on all of them against a baseline of the violation types the writer cannot avoid ([gate](.github/workflows/kicad-render.yml)). Parts KiCad ships no symbol for (sensor/module breakouts) render as labeled generic headers |
116
- | **Verified** | KiCad PCB layout | emit a placed, unrouted `.kicad_pcb` — footprints, nets, ratsnest, edge cuts | every bundled example emits a structurally sound board, every PR ([gate](.github/workflows/pcb-layout.yml)); a DRC tier opens each board in real KiCad ([gate](.github/workflows/pcb-drc.yml)) |
117
- | **Verified** | Fab outputs | JLCPCB upload bundle — BOM, CPL, Gerber + drill (`/design/fab/*`) | CPL positions match the `.kicad_pcb` placement; the DRC tier smoke-tests the Gerber export. Boards are unrouted until the Freerouting step, so Gerbers carry pads but no traces (`is_routed` flags it) |
116
+ | **Verified** | KiCad PCB layout | emit a placed, unrouted `.kicad_pcb` — footprints, nets, ratsnest, edge cuts, and a GND pour on the bottom layer that the route step fills | every bundled example emits a structurally sound board, every PR ([gate](.github/workflows/pcb-layout.yml)); a DRC tier opens each board in real KiCad ([gate](.github/workflows/pcb-drc.yml)) |
117
+ | **Verified** | Fab outputs | JLCPCB upload bundle — BOM, CPL, Gerber + drill (`/design/fab/*`) | CPL positions match the `.kicad_pcb` placement; the DRC tier smoke-tests the Gerber export. `?route=true` routes the board first; without it Gerbers carry pads but no tracks or pour (`is_routed` flags it). The routed gate checks a routed board's B.Cu Gerber carries the GND pour ([gate](.github/workflows/pcb-route.yml)) |
118
118
  | **Verified** | Subcircuits + building blocks | a component built from discrete parts (five bundled blocks, two H-bridges) expands into the schematic, PCB, BOM, CPL and inventory check; part values are parametric; each block's `verify.checks` rule runs at `validate` with the real params and rails | goldens for every subcircuit example pin YAML + BOM; `component_check` fails a block whose defaults break its own rule (`tests/test_electrical.py`); the schematic and PCB gates cover the expanded parts like any other |
119
119
  | **Verified** | Inventory check | cross-check a design's BOM against the drawer (library modules + discrete parts), propose same-package substitutes ranked by pinout, package, rating headroom and stock, accept one or all of them as `part_overrides` that every fab output honours, pick list by location, buy list priced on JLCPCB | matching, substitution and coverage tests in `tests/test_inventory.py` against the real drawer CSV; JLCPCB pricing is mocked and degrades to an unpriced list when the parts API is down |
120
120
  | **Verified** | Parametric enclosure | OpenSCAD `.scad` from board mount-hole metadata, built on [YAPP_Box](https://github.com/mrWheel/YAPP_Box): base and lid with standoffs, port cutouts, a ridge and snap joins | every enclosure-capable board renders through real OpenSCAD to a non-empty, manifold (closed, printable) solid, every PR ([gate](.github/workflows/enclosure-render.yml)) |
@@ -126,12 +126,13 @@ Tiers, in priority order:
126
126
  | **Works (lighter checks)** | Meshtastic flashing + config | proxy the official release factory image (`/meshtastic/firmware`) for the radio boards, flash it via the unified WebSerial dialog, then push region, modem preset, owner and primary channel as protobufs over the same port | endpoint tests with mocked upstream; board-to-variant map checked against `meshtastic/firmware` variants; config push tested against a fake device; no live-flash gate |
127
127
  | **Works (lighter checks)** | MicroPython flashing + main.py | proxy the official release image for the board's chip (`/micropython/firmware`; every library board maps to a generic port image, ESP8266 included), flash via the unified dialog, generate `main.py` from the design over the `machine` API (`/micropython/code`) and push it through the raw REPL over the same serial port | endpoint tests with a captured download page; every board's starter and every example's main.py parse as Python; the raw-REPL push is tested against a fake board; the micropython.org page format could not be fetched from the build session, so the first live flash validates it; no live-flash gate |
128
128
  | **Verified** | Component authoring | write a component YAML in the studio or through `component_create`; the same gate as `component_check` (schema, subcircuit nets, template render, KiCad symbols / pins / footprint) before it lands in the user library | a user-authored component goes through the gate and its synthetic design through upstream `esphome config`, every PR ([gate](.github/workflows/esphome-config.yml)); the gate rejects every file in a corpus of bad components for the named reason (`tests/test_component_corpus.py`); KiCad resolution runs only where the toolchain is present |
129
+ | **Verified** | Board authoring | write a board YAML in the studio or through `board_create`; the same gate as `board_check` (schema, every bus / onboard / radio pin against `gpio_capabilities`, KiCad symbol pins and footprint, enclosure geometry, ESPHome's board table, an ESPHome render with the onboard peripherals) before it lands in the user library | every bundled board passes the gate (`tests/test_board_check.py`); a user-authored board with its onboard peripherals goes through upstream `esphome config`, every PR ([gate](.github/workflows/esphome-config.yml)); the pinout is not checked against the datasheet |
129
130
  | **Verified** | ESPHome dashboard compile | push the rendered YAML to a running ESPHome Device Builder (`/esphome/push`), start a compile there, stream the log and serve the firmware, with the same job shapes as the fleet path | nightly, against a live Device Builder (ESPHome 2026.10.0) open and password-protected: push, compile over the WebSocket API, fetch the image, with the client's wire assumptions asserted ([dashboard](.github/workflows/esphome-compile.yml)); client + route tests against a fake of the same in `tests/test_esphome_dashboard.py` |
130
131
  | **Works (lighter checks)** | CircuitPython flashing | proxy the official release image (`/circuitpython/firmware`) for ESP32/S3/C3/C6 boards, flash via the unified dialog, serve a generated starter `code.py` (`/circuitpython/code`) | endpoint tests with mocked upstream; every board-id mapping verified against downloads.circuitpython.org; generated starters parse as valid Python; no live-flash gate |
131
132
  | **Verified** | MCP server | 53 tools over the Model Context Protocol: 31 design / library / inventory / fab, plus 22 that reach hardware — workbench flash and boot verification, LoRaWAN compile / provision / activation, fleet and dashboard push + build status, and job polling for the long ones | the `mcp` package's own client connects to the mounted server over Streamable HTTP with the bearer token, lists the tools, drives a design from `create_design` to `render` and reads a resource, every PR (`tests/test_mcp_client.py`); tool / auth / resource tests in `tests/test_mcp_*.py`; the hardware tools drive the real clients against wire-level fakes, and were exercised against a live bench, ChirpStack and fleet during bring-up |
132
133
  | **Experimental** | Thingiverse search relay | rank community models for a board | smoke-tested; depends on a third-party search API that ranks unevenly |
133
134
  | **Verified** | Agent (Claude tool-using) | natural-language design driving | scored nightly against the 28-task list in `wirestudio/agent/eval_tasks.yaml`: the design it hands back passes `esphome config`, carries the asked-for change, and the turn stayed within its tool-call budget ([gate](.github/workflows/agent-eval.yml)); every task's reference solution passes the same scorer on every PR (`tests/test_agent_eval.py`). The nightly run drives the studio through Claude Code over MCP with the `CLAUDE_CODE_OAUTH_TOKEN` secret (a subscription); the embedded agent is scored too when `ANTHROPIC_API_KEY` is set. Each driver's first scored run sets its gate |
134
- | **Verified** | PCB autorouting | Freerouting roundtrip — board → Specctra DSN → routed → SES import; SSE route endpoint, `route_pcb` MCP/agent tool, web-UI Route button, `?route=true` fab exports, `-pcb` image variant | representative examples route with zero unconnected items and pass routed DRC ([gate](.github/workflows/pcb-route.yml)) |
135
+ | **Verified** | PCB autorouting | Freerouting roundtrip — board → Specctra DSN → routed → SES import, GND pour filled; via cost and keepout rectangles as parameters; KiCad's DRC of the routed board reported back; SSE route endpoint, `route_pcb` MCP/agent tool, web-UI Route button, `?route=true` fab exports, `-pcb` image variant | representative examples route with zero unconnected items, a filled pour and a clean routed DRC; a high via cost must remove vias and a keepout must clear the track under it ([gate](.github/workflows/pcb-route.yml)) |
135
136
 
136
137
  The **Verified** tier is the bar the project is asking to be judged
137
138
  on. Everything else is offered with the caveat that's spelled out in
@@ -0,0 +1,205 @@
1
+ """Checking and creating library boards at runtime."""
2
+ from __future__ import annotations
3
+
4
+ from pathlib import Path
5
+
6
+ import pytest
7
+ from fastapi.testclient import TestClient
8
+
9
+ from wirestudio.api.app import create_app
10
+ from wirestudio.library import Library
11
+ from wirestudio.library.board_check import check_board_yaml, create_board
12
+
13
+ BUNDLED = Path(__file__).resolve().parent.parent / "wirestudio" / "library"
14
+ DRAFT = (Path(__file__).resolve().parent / "fixtures" / "boards" / "lolin-s2-mini.yaml").read_text()
15
+ DEVKIT = (BUNDLED / "boards" / "esp32-devkitc-v4.yaml").read_text()
16
+ C3 = (BUNDLED / "boards" / "esp32-c3-devkitm-1.yaml").read_text()
17
+
18
+
19
+ @pytest.fixture
20
+ def lib(tmp_path):
21
+ return Library(BUNDLED, tmp_path / "user")
22
+
23
+
24
+ @pytest.fixture
25
+ def client(tmp_path):
26
+ return TestClient(create_app(library=Library(BUNDLED, tmp_path / "user")))
27
+
28
+
29
+ def _errors(text: str, lib: Library) -> str:
30
+ return " | ".join(check_board_yaml(text, lib).errors)
31
+
32
+
33
+ def test_every_bundled_board_passes(lib):
34
+ for path in sorted((BUNDLED / "boards").glob("*.yaml")):
35
+ report = check_board_yaml(path.read_text(), lib)
36
+ assert report.ok, (path.stem, report.errors)
37
+ assert report.exists == "bundled"
38
+
39
+
40
+ def test_draft_passes_and_renders_its_onboard_parts(lib):
41
+ report = check_board_yaml(DRAFT, lib)
42
+ assert report.ok, report.errors
43
+ assert report.library_id == "lolin-s2-mini" and report.exists == ""
44
+ assert any("gpio_output, gpio_input" in v for v in report.verified)
45
+ assert any("34.3 x 25.4" in v for v in report.verified)
46
+ assert any("no kicad: block" in w for w in report.warnings)
47
+ assert any("datasheet" in n for n in report.not_checked)
48
+
49
+
50
+ def test_unparseable_and_invalid_files_are_reported(lib):
51
+ assert "not valid YAML" in _errors("id: [", lib)
52
+ assert "top level must be a mapping" in _errors("- a", lib)
53
+ assert "platformio_board" in _errors(DRAFT.replace("platformio_board: lolin_s2_mini\n", ""), lib)
54
+ assert "names the file" in _errors(DRAFT.replace("id: lolin-s2-mini", "id: Lolin S2"), lib)
55
+
56
+
57
+ def test_platform_fields_must_agree(lib):
58
+ assert "not an ESP32 variant" in _errors(DRAFT.replace("chip_variant: esp32s2", "chip_variant: esp32s9"), lib)
59
+ assert "needs mcu esp32" in _errors(DRAFT.replace("mcu: esp32\n", "mcu: esp8266\n"), lib)
60
+ assert "not an ESPHome platform" in _errors(DRAFT.replace("chip_variant: esp32s2", "chip_variant: stm32"), lib)
61
+ assert "framework" in _errors(DRAFT.replace("framework: arduino", "framework: zephyr"), lib)
62
+
63
+
64
+ def test_every_named_pin_must_be_a_gpio(lib):
65
+ errors = _errors(DRAFT.replace("sda: GPIO33", "sda: GPIO34").replace("pin: GPIO15", "pin: GPIO14"), lib)
66
+ assert "default_buses.i2c.sda: GPIO34 is not in gpio_capabilities" in errors
67
+ assert "onboard_peripherals.led.pin: GPIO14 is not in gpio_capabilities" in errors
68
+ assert "no scl pin" in _errors(DRAFT.replace(", scl: GPIO35", ""), lib)
69
+ assert "unknown bus type" in _errors(DRAFT.replace(" spi: {", " qspi: {"), lib)
70
+ assert "no GND rail" in _errors(DRAFT.replace(' - {name: "GND", voltage: 0.0}\n', ""), lib)
71
+
72
+
73
+ def test_an_input_only_pin_cannot_drive_a_bus_line(lib):
74
+ text = DRAFT.replace("GPIO33: [gpio, pwm, i2c_sda]", "GPIO33: [gpio, input_only]")
75
+ assert "GPIO33 is input_only and cannot drive sda" in _errors(text, lib)
76
+
77
+
78
+ def test_radio_pins_are_cross_checked(lib):
79
+ heltec = (BUNDLED / "boards" / "heltec-wifi-lora32-v2.yaml").read_text()
80
+ assert check_board_yaml(heltec, lib).ok
81
+ broken = heltec.replace("pins: {cs: GPIO18", "pins: {cs: GPIO99")
82
+ assert broken != heltec
83
+ assert "radio.pins.cs: GPIO99" in _errors(broken, lib)
84
+
85
+
86
+ def test_enclosure_geometry_stays_on_the_pcb(lib):
87
+ off = DRAFT.replace("offset_mm: 8.95", "offset_mm: 20.0")
88
+ assert "runs off the 25.4 mm short_a edge" in _errors(off, lib)
89
+ edge = DRAFT.replace("edge: short_a", "edge: top")
90
+ assert "edge 'top'" in _errors(edge, lib)
91
+
92
+
93
+ def test_pin_map_is_checked_against_the_symbol(lib, tmp_path, monkeypatch):
94
+ symdir = tmp_path / "symbols"
95
+ symdir.mkdir()
96
+ pins = " ".join(
97
+ f'(pin passive line (at 0 0 0) (length 2.54) (name "Pin_{n}") (number "{n}"))' for n in range(1, 31))
98
+ (symdir / "Connector_Generic.kicad_sym").write_text(
99
+ '(kicad_symbol_lib (version 20231120) (generator kicad_symbol_editor)'
100
+ f' (symbol "Conn_02x15_Odd_Even" {pins}))')
101
+ monkeypatch.setenv("KICAD8_SYMBOL_DIR", str(symdir))
102
+ report = check_board_yaml(C3, lib)
103
+ assert report.ok, report.errors
104
+ assert any("has every pin_map pin" in v for v in report.verified)
105
+
106
+ assert "has no pin '31'" in _errors(C3.replace('GPIO1: "19"', 'GPIO1: "31"'), lib)
107
+ assert "GPIO1 and GPIO0 share pin 17, which would short them" in _errors(C3.replace('GPIO1: "19"', 'GPIO1: "17"'), lib)
108
+ assert "no symbol 'Conn_02x20'" in _errors(C3.replace("Conn_02x15_Odd_Even", "Conn_02x20"), lib)
109
+ assert "symbol library 'RF_Module' not found" in _errors(DEVKIT, lib)
110
+
111
+
112
+ def test_create_saves_into_the_user_library_only(lib, tmp_path):
113
+ report = create_board(DRAFT, lib)
114
+ assert report.ok and report.saved.endswith("user/boards/lolin-s2-mini.yaml")
115
+ assert Path(report.saved).read_text() == DRAFT
116
+ assert lib.board_source("lolin-s2-mini") == "user"
117
+ assert lib.board("lolin-s2-mini").chip_variant == "esp32s2"
118
+ assert "lolin-s2-mini" in [b.id for b in lib.list_boards()]
119
+
120
+ again = create_board(DRAFT, lib)
121
+ assert not again.ok and any("already exists" in e for e in again.errors)
122
+ assert create_board(DRAFT, lib, overwrite=True).ok
123
+
124
+ bundled = create_board(DEVKIT, lib)
125
+ assert not bundled.ok and any("bundled board" in e for e in bundled.errors)
126
+ assert not (tmp_path / "user" / "boards" / "esp32-devkitc-v4.yaml").exists()
127
+
128
+ with pytest.raises(PermissionError):
129
+ lib.delete_board("esp32-devkitc-v4")
130
+ lib.delete_board("lolin-s2-mini")
131
+ assert lib.board_source("lolin-s2-mini") == ""
132
+ with pytest.raises(FileNotFoundError):
133
+ lib.board("lolin-s2-mini")
134
+
135
+ assert not create_board(DRAFT, Library(BUNDLED)).ok # no user dir configured
136
+
137
+
138
+ def test_a_bundled_board_wins_over_a_user_file(lib, tmp_path):
139
+ (tmp_path / "user" / "boards").mkdir(parents=True)
140
+ (tmp_path / "user" / "boards" / "esp32-devkitc-v4.yaml").write_text(DRAFT.replace("lolin-s2-mini", "esp32-devkitc-v4"))
141
+ assert lib.board_source("esp32-devkitc-v4") == "bundled"
142
+ assert lib.board("esp32-devkitc-v4").chip_variant == "esp32"
143
+ assert [b.id for b in lib.list_boards()].count("esp32-devkitc-v4") == 1
144
+
145
+
146
+ def test_user_board_flows_into_a_design(lib):
147
+ from wirestudio.generate.yaml_gen import render_yaml
148
+ from wirestudio.model import Design
149
+
150
+ assert create_board(DRAFT, lib).ok
151
+ design = Design.model_validate({
152
+ "schema_version": "0.1", "id": "t", "name": "t",
153
+ "board": {"library_id": "lolin-s2-mini", "mcu": "esp32"},
154
+ "power": {"supply": "usb-5v", "rail_voltage_v": 5.0, "budget_ma": 500},
155
+ "components": [{"id": "led", "library_id": "gpio_output", "label": "LED"}],
156
+ "buses": [], "requirements": [], "warnings": [],
157
+ "connections": [
158
+ {"component_id": "led", "pin_role": "OUT", "target": {"kind": "gpio", "pin": "GPIO15"}},
159
+ ],
160
+ })
161
+ rendered = render_yaml(design, lib)
162
+ assert "board: lolin_s2_mini" in rendered and "variant: ESP32S2" in rendered
163
+ assert "pin: GPIO15" in rendered
164
+
165
+
166
+ def test_api_round_trip(client):
167
+ check = client.post("/library/boards/check", json={"yaml": DRAFT})
168
+ assert check.status_code == 200 and check.json()["ok"]
169
+
170
+ created = client.post("/library/boards", json={"yaml": DRAFT})
171
+ assert created.status_code == 201 and created.json()["saved"]
172
+ listed = {b["id"]: b for b in client.get("/library/boards").json()}
173
+ assert listed["lolin-s2-mini"]["source"] == "user"
174
+ assert listed["esp32-devkitc-v4"]["source"] == "bundled"
175
+ assert client.get("/library/boards/lolin-s2-mini").json()["chip_variant"] == "esp32s2"
176
+ assert client.get("/library/boards/lolin-s2-mini/yaml").text == DRAFT
177
+
178
+ assert client.post("/library/boards", json={"yaml": DRAFT}).status_code == 409
179
+ assert client.post("/library/boards", json={"yaml": DEVKIT}).status_code == 409
180
+ bad = client.post("/library/boards", json={"yaml": DRAFT.replace("pin: GPIO15", "pin: GPIO14")})
181
+ assert bad.status_code == 422 and not bad.json()["ok"]
182
+
183
+ assert client.delete("/library/boards/esp32-devkitc-v4").status_code == 409
184
+ assert client.delete("/library/boards/lolin-s2-mini").status_code == 200
185
+ assert client.delete("/library/boards/lolin-s2-mini").status_code == 404
186
+ assert "lolin-s2-mini" not in {b["id"] for b in client.get("/library/boards").json()}
187
+
188
+
189
+ def test_agent_tools(lib):
190
+ from wirestudio.agent.tools import _run_board_check, _run_board_create, _run_board_delete, _run_list_boards
191
+
192
+ assert _run_board_check({}, lib, yaml=DRAFT)["ok"]
193
+ assert _run_board_create({}, lib, yaml=DRAFT)["saved"]
194
+ sources = {b["library_id"]: b["source"] for b in _run_list_boards({}, lib)["boards"]}
195
+ assert sources["lolin-s2-mini"] == "user"
196
+ assert _run_board_delete({}, lib, library_id="lolin-s2-mini")["ok"]
197
+ assert not _run_board_delete({}, lib, library_id="esp32-devkitc-v4")["ok"]
198
+
199
+
200
+ def test_platformio_board_is_looked_up_in_esphome(lib):
201
+ pytest.importorskip("esphome")
202
+ assert any("lolin_s2_mini is an ESPHome esp32 board" in v for v in check_board_yaml(DRAFT, lib).verified)
203
+ unknown = check_board_yaml(DRAFT.replace("lolin_s2_mini", "no_such_board"), lib)
204
+ assert unknown.ok and any("not in ESPHome's esp32 board list" in w for w in unknown.warnings)
205
+ assert "is an esp32c3 board in ESPHome, not esp32s2" in _errors(DRAFT.replace("lolin_s2_mini", "esp32-c3-devkitm-1"), lib)
@@ -43,9 +43,10 @@ def studio(monkeypatch, user_library):
43
43
  raise RuntimeError(f"404 {url}")
44
44
  return files[url]
45
45
 
46
- def publish(*entries, designs=None, components=None):
46
+ def publish(*entries, designs=None, components=None, library=None):
47
47
  files[f"{RAW}/index/index.json"] = json.dumps(
48
- {"version": 1, "generated": "2026-10-23T00:00:00+00:00", "designs": list(entries)}).encode()
48
+ {"version": 1, "generated": "2026-10-23T00:00:00+00:00", "designs": list(entries),
49
+ **(library or {})}).encode()
49
50
  for slug, doc in (designs or {}).items():
50
51
  files[f"{RAW}/main/designs/{slug}/design.json"] = json.dumps(doc).encode()
51
52
  for url, text in (components or {}).items():
@@ -246,3 +247,141 @@ def test_share_packs_the_user_components_the_design_uses(studio, user_library):
246
247
  assert z.read(f"{slug}/components/led_driver_npn.yaml").decode() == LED_DRIVER
247
248
  assert not [n for n in z.namelist() if "/components/" in n and "led_driver_npn" not in n], \
248
249
  "bundled components are not packed"
250
+
251
+
252
+ S2_MINI = (Path(__file__).resolve().parent / "fixtures" / "boards" / "lolin-s2-mini.yaml").read_text()
253
+ LIB_LED_URL = f"{RAW}/main/library/components/led_driver_npn/component.yaml"
254
+ LIB_S2_URL = f"{RAW}/main/library/boards/lolin-s2-mini/board.yaml"
255
+
256
+
257
+ def _publish_library(publish, **over):
258
+ publish(library={
259
+ "components": [{"id": "led_driver_npn", "name": "NPN LED driver", "author": "octocat",
260
+ "license": "MIT", "url": LIB_LED_URL, **over}],
261
+ "boards": [{"id": "lolin-s2-mini", "name": "LOLIN S2 Mini", "author": "octocat",
262
+ "license": "MIT", "url": LIB_S2_URL}],
263
+ }, components={LIB_LED_URL: LED_DRIVER, LIB_S2_URL: S2_MINI})
264
+
265
+
266
+ def test_library_lists_entries_and_what_is_here(studio, user_library):
267
+ app, publish, _ = studio
268
+ _publish_library(publish)
269
+ user_library.save_component("led_driver_npn", LED_DRIVER)
270
+ body = app.get("/community/library").json()
271
+ assert [(e["id"], e["installed"]) for e in body["components"]] == [("led_driver_npn", "user")]
272
+ assert [(e["id"], e["installed"]) for e in body["boards"]] == [("lolin-s2-mini", "")]
273
+
274
+
275
+ def test_an_index_without_library_entries_lists_none(studio):
276
+ app, publish, _ = studio
277
+ publish(_entry("porch-motion"))
278
+ body = app.get("/community/library").json()
279
+ assert body["components"] == [] and body["boards"] == []
280
+
281
+
282
+ def test_install_runs_the_gate_and_is_idempotent(studio, user_library):
283
+ app, publish, _ = studio
284
+ _publish_library(publish)
285
+ r = app.post("/community/library/boards/lolin-s2-mini/install")
286
+ assert r.status_code == 200 and r.json()["installed"] is True
287
+ assert user_library.board_source("lolin-s2-mini") == "user"
288
+ assert app.post("/community/library/boards/lolin-s2-mini/install").json()["installed"] is False
289
+ r = app.post("/community/library/components/led_driver_npn/install")
290
+ assert r.status_code == 200 and user_library.component_source("led_driver_npn") == "user"
291
+
292
+
293
+ def test_install_refuses_conflicts_and_foreign_urls(studio, user_library):
294
+ app, publish, _ = studio
295
+ _publish_library(publish)
296
+ user_library.save_component("led_driver_npn", LED_DRIVER.replace("NPN low-side LED driver", "mine", 1))
297
+ r = app.post("/community/library/components/led_driver_npn/install")
298
+ assert r.status_code == 409 and "different component" in r.json()["detail"]
299
+ assert "mine" in user_library.component_yaml("led_driver_npn")
300
+ assert app.post("/community/library/components/nope/install").status_code == 404
301
+ assert app.post("/community/library/modules/x/install").status_code == 404
302
+
303
+
304
+ def test_install_only_follows_urls_inside_the_catalogue(studio, user_library):
305
+ app, publish, fetched = studio
306
+ _publish_library(publish, url="https://evil.example/led.yaml")
307
+ r = app.post("/community/library/components/led_driver_npn/install")
308
+ assert r.status_code == 502 and "points outside" in r.json()["detail"]
309
+ assert "https://evil.example/led.yaml" not in fetched
310
+
311
+
312
+ def test_install_refuses_an_entry_whose_file_names_another_id(studio, user_library):
313
+ app, publish, _ = studio
314
+ publish(library={"boards": [{"id": "lolin-s2", "url": LIB_S2_URL}]}, components={LIB_S2_URL: S2_MINI})
315
+ r = app.post("/community/library/boards/lolin-s2/install")
316
+ assert r.status_code == 502 and "declares id 'lolin-s2-mini'" in r.json()["detail"]
317
+ assert user_library.board_source("lolin-s2-mini") == ""
318
+
319
+
320
+ def test_install_of_a_broken_entry_is_reported_not_saved(studio, user_library):
321
+ app, publish, _ = studio
322
+ broken = S2_MINI.replace("pin: GPIO15", "pin: GPIO14")
323
+ publish(library={"boards": [{"id": "lolin-s2-mini", "url": LIB_S2_URL}]}, components={LIB_S2_URL: broken})
324
+ r = app.post("/community/library/boards/lolin-s2-mini/install")
325
+ assert r.status_code == 502 and "GPIO14 is not in gpio_capabilities" in r.json()["detail"]
326
+ assert user_library.board_source("lolin-s2-mini") == ""
327
+
328
+
329
+ def _share_library(app, **over):
330
+ body = {"kind": "boards", "id": "lolin-s2-mini", "author": "@octocat", "tags": ["esp32s2"],
331
+ "source": "https://www.wemos.cc/en/latest/s2/s2_mini.html", **over}
332
+ return app.post("/community/library/share", json=body)
333
+
334
+
335
+ def test_share_library_packs_the_entry_folder(studio, user_library):
336
+ app, _, _ = studio
337
+ user_library.save_board("lolin-s2-mini", S2_MINI)
338
+ r = _share_library(app, notes="Pinout from the WEMOS docs.")
339
+ assert r.status_code == 200, r.text
340
+ z = zipfile.ZipFile(io.BytesIO(r.content))
341
+ assert sorted(z.namelist()) == ["lolin-s2-mini/README.md", "lolin-s2-mini/board.yaml", "lolin-s2-mini/meta.yaml"]
342
+ assert z.read("lolin-s2-mini/board.yaml").decode() == S2_MINI
343
+ assert yaml.safe_load(z.read("lolin-s2-mini/meta.yaml")) == {
344
+ "author": "octocat", "tags": ["esp32s2"], "source": "https://www.wemos.cc/en/latest/s2/s2_mini.html"}
345
+
346
+ user_library.save_component("led_driver_npn", LED_DRIVER)
347
+ r = _share_library(app, kind="components", id="led_driver_npn", source="")
348
+ assert sorted(zipfile.ZipFile(io.BytesIO(r.content)).namelist()) == [
349
+ "led_driver_npn/component.yaml", "led_driver_npn/meta.yaml"]
350
+
351
+
352
+ def test_share_library_refuses_what_is_not_yours_or_not_clean(studio, user_library):
353
+ app, _, _ = studio
354
+ assert _share_library(app, id="esp32-devkitc-v4").status_code == 422
355
+ user_library.save_board("lolin-s2-mini", S2_MINI)
356
+ assert "GitHub handle" in _share_library(app, author="not a handle").json()["detail"]
357
+ assert "https://" in _share_library(app, source="ftp://x").json()["detail"]
358
+ assert _share_library(app, kind="widgets").status_code == 404
359
+ user_library.save_board("lolin-s2-mini", S2_MINI.replace("pin: GPIO15", "pin: GPIO14"))
360
+ assert "no longer passes its gate" in _share_library(app).json()["detail"]
361
+
362
+
363
+ def _s2_design() -> dict:
364
+ return {**EXAMPLE, "id": "s2-blink",
365
+ "board": {"library_id": "lolin-s2-mini", "mcu": "esp32"},
366
+ "components": [{"id": "led", "library_id": "gpio_output", "label": "LED", "params": {}}],
367
+ "buses": [],
368
+ "connections": [{"component_id": "led", "pin_role": "OUT", "target": {"kind": "gpio", "pin": "GPIO15"}}]}
369
+
370
+
371
+ def test_design_share_and_import_carry_a_user_board(studio, user_library):
372
+ app, publish, _ = studio
373
+ user_library.save_board("lolin-s2-mini", S2_MINI)
374
+ r = _share(app, design=_s2_design())
375
+ assert r.status_code == 200, r.text
376
+ z = zipfile.ZipFile(io.BytesIO(r.content))
377
+ assert z.read("s2-blink/boards/lolin-s2-mini.yaml").decode() == S2_MINI
378
+
379
+ user_library.delete_board("lolin-s2-mini")
380
+ url = f"{RAW}/main/designs/s2-blink/boards/lolin-s2-mini.yaml"
381
+ publish(_entry("s2-blink", user_boards=[{"id": "lolin-s2-mini", "url": url}]),
382
+ designs={"s2-blink": _s2_design()}, components={url: S2_MINI})
383
+ r = app.post("/community/designs/s2-blink/import")
384
+ assert r.status_code == 200, r.text
385
+ assert r.json()["installed_boards"] == ["lolin-s2-mini"]
386
+ assert user_library.board_source("lolin-s2-mini") == "user"
387
+
@@ -7,6 +7,7 @@ import pytest
7
7
  from wirestudio.library import default_library
8
8
  from wirestudio.migrate import migrate
9
9
  from wirestudio.model import Design
10
+ from wirestudio.targets.lorawan.codec import decode_js, fields_for
10
11
  from wirestudio.targets.lorawan.firmware_gen import generate_firmware, write_firmware
11
12
 
12
13
 
@@ -230,14 +231,11 @@ def test_ci_component_designs_carry_every_lorawan_block(lib):
230
231
  assert built | board_built == {c.id for c in lib.list_components() if c.lorawan is not None}
231
232
  for design in designs:
232
233
  main = generate_firmware(design, lib)["src/main.cpp"]
233
- for comp in design.components:
234
- for field in lib.component(comp.library_id).lorawan.fields:
235
- assert field.cpp_expr in main
234
+ for field in fields_for(design, lib):
235
+ assert field["cpp_expr"] in main
236
236
 
237
237
 
238
238
  def test_i2c_sensor_blocks_bring_up_wire_and_report_scaled_fields(lib):
239
- from wirestudio.targets.lorawan.codec import fields_for
240
-
241
239
  design = _design_with("bme280", "sht4x")
242
240
  names = [f["name"] for f in fields_for(design, lib)]
243
241
  assert {"bme280_temp_c", "bme280_humidity", "bme280_pressure", "sht4x_temp_c"} <= set(names)
@@ -245,3 +243,73 @@ def test_i2c_sensor_blocks_bring_up_wire_and_report_scaled_fields(lib):
245
243
  assert "Wire.begin(" in main
246
244
  assert "bme280Ok = bme280.begin(0x76, &Wire);" in main
247
245
  assert "(int16_t)(bme280TempC * 100.0)" in main
246
+
247
+
248
+ def _wired(components, buses, connections) -> Design:
249
+ return Design.model_validate({
250
+ "schema_version": "1.0", "id": "dev1", "name": "Dev 1", "target": "lorawan", "lorawan": {},
251
+ "board": {"library_id": "ttgo-lora32-v2", "mcu": "esp32"},
252
+ "power": {"supply": "usb", "rail_voltage_v": 3.3},
253
+ "components": [{"id": i, "library_id": c, "label": i, "params": p} for i, c, p in components],
254
+ "buses": buses,
255
+ "connections": [{"component_id": c, "pin_role": r, "target": t} for c, r, t in connections],
256
+ })
257
+
258
+
259
+ def _uart(bus_id, rx, tx, baud=9600):
260
+ return {"id": bus_id, "type": "uart", "rx": rx, "tx": tx, "baud_rate": baud}
261
+
262
+
263
+ def _on(bus_id):
264
+ return {"kind": "bus", "bus_id": bus_id}
265
+
266
+
267
+ def test_each_uart_bus_gets_its_own_hardware_uart(lib):
268
+ parts = [("co2", "mhz19", {}), ("pm", "pmsx003", {})]
269
+ buses = [_uart("u1", "GPIO16", "GPIO17"), _uart("u2", "GPIO32", "GPIO33")]
270
+ conns = [("co2", "TX", _on("u1")), ("co2", "RX", _on("u1")), ("pm", "TX", _on("u2")), ("pm", "RX", _on("u2"))]
271
+ main = generate_firmware(_wired(parts, buses, conns), lib)["src/main.cpp"]
272
+ assert "HardwareSerial co2Serial(1);" in main and "HardwareSerial pmSerial(2);" in main
273
+ assert "co2Serial.begin(9600, SERIAL_8N1, 16, 17);" in main
274
+ third = _wired(parts + [("radar", "ld2410", {})], buses + [_uart("u3", "GPIO4", "GPIO25", 256000)],
275
+ conns + [("radar", "TX", _on("u3")), ("radar", "RX", _on("u3"))])
276
+ with pytest.raises(ValueError, match="hardware UART"):
277
+ generate_firmware(third, lib)
278
+
279
+
280
+ def test_a_uart_bus_keeps_its_parity(lib):
281
+ design = _wired([("plug", "cse7766", {})], [{**_uart("u1", "GPIO16", "GPIO17", 4800), "parity": "EVEN"}],
282
+ [("plug", "TX", _on("u1"))])
283
+ assert "plugSerial.begin(4800, SERIAL_8E1, 16, 17);" in generate_firmware(design, lib)["src/main.cpp"]
284
+
285
+
286
+ def test_spi_parts_share_the_radio_bus(lib):
287
+ spi = lib.board("ttgo-lora32-v2").default_buses["spi"]
288
+ conns = [("tc", "CLK", _on("s")), ("tc", "MISO", _on("s")), ("tc", "CS", {"kind": "gpio", "pin": "GPIO25"})]
289
+ main = generate_firmware(_wired([("tc", "max31855", {})], [{"id": "s", "type": "spi", **spi}], conns),
290
+ lib)["src/main.cpp"]
291
+ assert "#include <SPI.h>" in main and "SPI.begin(5, 19, 27);" in main
292
+ elsewhere = {"id": "s", "type": "spi", "clk": "GPIO14", "miso": "GPIO12", "mosi": "GPIO13"}
293
+ with pytest.raises(ValueError, match="radio's bus"):
294
+ generate_firmware(_wired([("tc", "max31855", {})], [elsewhere], conns), lib)
295
+
296
+
297
+ def test_parts_on_a_hub_read_through_it_and_name_their_fields(lib):
298
+ spi = lib.board("ttgo-lora32-v2").default_buses["spi"]
299
+ parts = [("adc", "mcp3008", {}), ("ch0", "mcp3008_channel", {"number": 0}),
300
+ ("ch5", "mcp3008_channel", {"number": 5, "reference_voltage": "5.0V"}),
301
+ ("bus", "modbus", {}), ("mains", "pzemac", {"address": 2})]
302
+ conns = [("adc", r, _on("s")) for r in ("CLK", "MOSI", "MISO")] + [
303
+ ("adc", "CS", {"kind": "gpio", "pin": "GPIO2"}),
304
+ ("ch0", "HUB", {"kind": "component", "component_id": "adc"}),
305
+ ("ch5", "HUB", {"kind": "component", "component_id": "adc"}),
306
+ ("bus", "UART", _on("u1")), ("bus", "FLOW", {"kind": "gpio", "pin": "GPIO4"}),
307
+ ("mains", "HUB", {"kind": "component", "component_id": "bus"})]
308
+ design = _wired(parts, [{"id": "s", "type": "spi", **spi}, _uart("u1", "GPIO16", "GPIO17")], conns)
309
+ main = generate_firmware(design, lib)["src/main.cpp"]
310
+ assert "ch5Mv = (uint16_t)(adcRead(5) * 5.0 * 1000.0 / 1023.0 + 0.5);" in main
311
+ assert "busRead(2, 0x04, 0, 10, _d)" in main
312
+ assert "digitalWrite(4, HIGH); // RS485 driver on" in main
313
+ names = [f["name"] for f in fields_for(design, lib)]
314
+ assert {"ch0_voltage", "ch5_voltage", "mains_voltage", "mains_energy"} <= set(names)
315
+ assert "ch5_voltage" in decode_js(fields_for(design, lib))
@@ -318,3 +318,9 @@ def test_subcircuit_refs_do_not_disturb_other_prefixes(lib):
318
318
  assert refs[part_key("bridge", "q_hi_a")] == "Q1"
319
319
  assert refs[part_key("bridge", "r_gate_a")] == "R1"
320
320
  assert refs["feedback"] == "U1"
321
+
322
+
323
+ def test_expander_pads_join_the_expander_pin_net(lib):
324
+ script = generate_skidl(_design("keypad"), lib)
325
+ assert 'Net("kpd_hub_GP0")' in script
326
+ assert 'c_kpd_hub["P0"] += Net.fetch("kpd_hub_GP0")' in script
@@ -21,9 +21,9 @@ REPORT = {
21
21
  "violations": [
22
22
  {"type": "pin_not_driven", "severity": "error", "description": "Input Power pin not driven by any Output Power pins",
23
23
  "items": [{"description": "Symbol U1 Pin 3 [VCC, Power input]"}], "excluded": False},
24
- {"type": "lib_symbol_issues", "severity": "warning", "description": "Pin not connected",
24
+ {"type": "global_label_dangling", "severity": "warning", "description": "Pin not connected",
25
25
  "items": [{"description": "Symbol J2 Pin 4"}], "excluded": False},
26
- {"type": "lib_symbol_issues", "severity": "warning", "description": "Pin not connected",
26
+ {"type": "global_label_dangling", "severity": "warning", "description": "Pin not connected",
27
27
  "items": [{"description": "Symbol J2 Pin 5"}], "excluded": True},
28
28
  {"type": "endpoint_off_grid", "severity": "error", "description": "Pins of type Output and Output are connected",
29
29
  "items": [{"description": "Symbol U1 Pin 7"}, {"description": "Symbol U2 Pin 2"}], "excluded": False},
@@ -51,7 +51,7 @@ def _writer_ok(monkeypatch):
51
51
  def test_summarize_flattens_counts_and_drops_exclusions():
52
52
  out = summarize(REPORT)
53
53
  assert out["kicad_version"] == "8.0.9"
54
- assert out["counts"] == {"lib_symbol_issues": 1, "endpoint_off_grid": 1, "pin_not_driven": 1}
54
+ assert out["counts"] == {"global_label_dangling": 1, "endpoint_off_grid": 1, "pin_not_driven": 1}
55
55
  assert out["errors"] == 2 and out["warnings"] == 1
56
56
  assert out["violations"][0]["items"] == ["Symbol U1 Pin 3 [VCC, Power input]"]
57
57
  assert summarize({}) == {"kicad_version": None, "violations": [], "counts": {}, "errors": 0, "warnings": 0}
@@ -103,11 +103,11 @@ def _load_script():
103
103
  def test_gate_judges_against_the_baseline():
104
104
  mod = _load_script()
105
105
  allowed = mod.load_baseline()
106
- assert {"pin_not_driven", "lib_symbol_issues"} <= set(allowed)
106
+ assert {"pin_not_driven", "global_label_dangling"} <= set(allowed)
107
107
  results = {"garage-motion": summarize(REPORT)}
108
108
  failures, stale = mod.judge(results, allowed, check_stale=True)
109
109
  assert failures == ["garage-motion: [error] endpoint_off_grid: Pins of type Output and Output are connected (Symbol U1 Pin 7)"]
110
- assert stale == sorted(set(allowed) - {"pin_not_driven", "lib_symbol_issues"})
110
+ assert stale == sorted(set(allowed) - {"pin_not_driven", "global_label_dangling"})
111
111
  only_power = {"x": summarize({"sheets": [{"path": "/", "violations": [REPORT["sheets"][0]["violations"][0]]}]})}
112
112
  failures, stale = mod.judge(only_power, allowed, check_stale=True)
113
113
  assert failures == [] and stale == sorted(set(allowed) - {"pin_not_driven"})
@@ -119,3 +119,27 @@ def test_gate_checks_every_example_by_default():
119
119
  paths = mod.example_paths([])
120
120
  assert len(paths) == len(list(mod.EXAMPLES_DIR.glob("*.json"))) > 50
121
121
  assert mod.example_paths(["garage-motion"]) == [mod.EXAMPLES_DIR / "garage-motion.json"]
122
+
123
+
124
+ def test_run_erc_checks_the_sheet_inside_a_project(monkeypatch, garage_motion_design, library, tmp_path):
125
+ """kicad-cli reports every embedded symbol's library as not configured
126
+ unless the sheet sits in a project whose sym-lib-table names it."""
127
+ from wirestudio.kicad import erc as E
128
+
129
+ (tmp_path / "Sensor.kicad_sym").write_text("(kicad_symbol_lib)")
130
+ monkeypatch.setattr(E, "default_symbol_dirs", lambda: [tmp_path])
131
+ monkeypatch.setattr(R.shutil, "which", lambda exe: "/usr/bin/" + exe)
132
+ _writer_ok(monkeypatch)
133
+ monkeypatch.setattr(R, "generate_kicad_sch", lambda d, lib: '(kicad_sch (symbol (lib_id "Sensor:BME280")) (symbol (lib_id "power:GND")))')
134
+ seen = {}
135
+
136
+ def run(cmd, **kw):
137
+ sch = Path(cmd[-1])
138
+ seen["table"] = (sch.parent / "sym-lib-table").read_text()
139
+ seen["pro"] = sch.with_suffix(".kicad_pro").is_file()
140
+ return _fake_run(cmd, **kw)
141
+ monkeypatch.setattr(subprocess, "run", run)
142
+ run_erc(garage_motion_design, library)
143
+ assert seen["pro"]
144
+ assert f'(name "Sensor")(type "KiCad")(uri "{tmp_path / "Sensor.kicad_sym"}")' in seen["table"]
145
+ assert '"power"' not in seen["table"] # no such library here, so no row