wirestudio 0.45.0__tar.gz → 0.47.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 (433) hide show
  1. {wirestudio-0.45.0 → wirestudio-0.47.0}/LICENSES.md +4 -3
  2. {wirestudio-0.45.0/wirestudio.egg-info → wirestudio-0.47.0}/PKG-INFO +7 -6
  3. {wirestudio-0.45.0 → wirestudio-0.47.0}/README.md +6 -5
  4. {wirestudio-0.45.0 → wirestudio-0.47.0}/pyproject.toml +1 -0
  5. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_agent.py +43 -0
  6. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_agent_eval.py +18 -0
  7. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_api.py +2 -1
  8. wirestudio-0.47.0/tests/test_board_check.py +214 -0
  9. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_community.py +141 -2
  10. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_designs_seed.py +12 -0
  11. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_firmware_gen.py +163 -5
  12. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_kicad.py +6 -0
  13. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_kicad_erc.py +29 -5
  14. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_kicad_pcb.py +16 -0
  15. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_lorawan.py +16 -0
  16. wirestudio-0.47.0/tests/test_lorawan_fragments.py +315 -0
  17. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_mcp_server.py +3 -0
  18. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_migrate.py +49 -0
  19. wirestudio-0.47.0/tests/test_promote_library.py +121 -0
  20. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_tbeam_lorawan_example.py +5 -5
  21. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_tools_add_bus.py +15 -0
  22. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_validate.py +56 -1
  23. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_wled.py +67 -1
  24. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_workbench.py +95 -3
  25. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_yaml_gen.py +53 -0
  26. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/__init__.py +1 -1
  27. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/agent/agent.py +1 -1
  28. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/agent/eval_tasks.yaml +2 -2
  29. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/agent/tools.py +122 -9
  30. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/api/app.py +62 -12
  31. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/api/community.py +140 -27
  32. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/api/schemas.py +22 -0
  33. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/api/workbench.py +44 -1
  34. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/designs/seed.py +23 -0
  35. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/heltec-v4-gnss-tracker.json +2 -1
  36. wirestudio-0.47.0/wirestudio/generate/esphome_integrations.txt +720 -0
  37. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/generate/yaml_gen.py +49 -1
  38. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/kicad/erc.py +19 -0
  39. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/kicad/generator.py +3 -1
  40. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/kicad/netlist.py +27 -0
  41. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/__init__.py +147 -53
  42. wirestudio-0.47.0/wirestudio/library/board_check.py +377 -0
  43. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/boards/esp32-c3-devkitm-1.yaml +24 -0
  44. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/boards/esp32-s3-devkitc-1.yaml +31 -0
  45. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/boards/heltec-wifi-lora32-v4.yaml +15 -2
  46. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/boards/m5stack-atomu.yaml +17 -5
  47. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/check.py +1 -1
  48. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/adc.yaml +5 -0
  49. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/ads1115.yaml +25 -0
  50. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/ads1115_channel.yaml +26 -0
  51. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/battery_adc.yaml +10 -0
  52. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/bl0906.yaml +164 -0
  53. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/bme680.yaml +9 -0
  54. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/bmp180.yaml +34 -0
  55. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/cse7766.yaml +58 -0
  56. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/ct_clamp.yaml +43 -0
  57. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/gpio_input.yaml +15 -0
  58. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/hc-sr04.yaml +29 -0
  59. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/hlw8012.yaml +63 -0
  60. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/hx711.yaml +21 -0
  61. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/i2s_microphone.yaml +14 -0
  62. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/ld2410.yaml +66 -0
  63. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/ld2420.yaml +64 -0
  64. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/max31855.yaml +38 -0
  65. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/mcp23008.yaml +1 -0
  66. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/mcp23017.yaml +1 -0
  67. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/mcp23s17.yaml +1 -0
  68. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/mcp3008.yaml +17 -0
  69. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/mcp3008_channel.yaml +22 -0
  70. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/mhz19.yaml +36 -0
  71. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/modbus.yaml +54 -0
  72. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/mpu6050.yaml +11 -0
  73. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/mpu6886.yaml +78 -0
  74. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/pcf8574.yaml +1 -0
  75. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/pmsx003.yaml +55 -0
  76. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/pn532.yaml +55 -0
  77. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/potentiometer.yaml +5 -0
  78. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/pulse_counter.yaml +17 -0
  79. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/pzemac.yaml +71 -0
  80. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/remote_receiver.yaml +47 -0
  81. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/rotary_encoder.yaml +42 -0
  82. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/rtttl.yaml +6 -0
  83. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/sdm_meter.yaml +78 -0
  84. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/sgp4x.yaml +45 -0
  85. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/sn74hc595.yaml +1 -0
  86. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/ssd1306.yaml +13 -0
  87. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/st7789.yaml +13 -0
  88. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/uart_gps.yaml +2 -1
  89. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/voltage_divider.yaml +12 -0
  90. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/wiegand.yaml +58 -0
  91. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/mcp/server.py +51 -4
  92. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/migrate.py +92 -41
  93. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/model.py +6 -0
  94. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/schema/design.schema.json +2 -1
  95. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/targets/lorawan/codec.py +20 -0
  96. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/targets/lorawan/firmware_gen.py +101 -58
  97. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/targets/wled.py +23 -9
  98. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/targets/wled_build.py +12 -5
  99. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/validate.py +55 -0
  100. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/workbench/client.py +66 -3
  101. {wirestudio-0.45.0 → wirestudio-0.47.0/wirestudio.egg-info}/PKG-INFO +7 -6
  102. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio.egg-info/SOURCES.txt +5 -0
  103. {wirestudio-0.45.0 → wirestudio-0.47.0}/LICENSE +0 -0
  104. {wirestudio-0.45.0 → wirestudio-0.47.0}/setup.cfg +0 -0
  105. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_active_design.py +0 -0
  106. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_agent_stream.py +0 -0
  107. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_api_auth.py +0 -0
  108. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_api_route.py +0 -0
  109. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_ascii_gen.py +0 -0
  110. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_build_backend.py +0 -0
  111. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_check_upgrade.py +0 -0
  112. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_chirpstack.py +0 -0
  113. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_circuitpython.py +0 -0
  114. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_codec.py +0 -0
  115. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_compatibility.py +0 -0
  116. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_compile.py +0 -0
  117. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_component_corpus.py +0 -0
  118. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_connectors.py +0 -0
  119. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_coverage_baseline.py +0 -0
  120. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_design_events.py +0 -0
  121. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_designs.py +0 -0
  122. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_display_intent.py +0 -0
  123. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_electrical.py +0 -0
  124. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_enclosure.py +0 -0
  125. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_enclosure_gate.py +0 -0
  126. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_enclosure_search.py +0 -0
  127. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_errors.py +0 -0
  128. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_esphome_dashboard.py +0 -0
  129. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_esphome_support.py +0 -0
  130. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_fleet.py +0 -0
  131. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_footprints.py +0 -0
  132. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_framework_baseline.py +0 -0
  133. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_hardware_gate.py +0 -0
  134. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_intent.py +0 -0
  135. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_inventory.py +0 -0
  136. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_jlcpcb.py +0 -0
  137. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_kicad_drc.py +0 -0
  138. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_kicad_fab.py +0 -0
  139. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_kicad_import.py +0 -0
  140. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_kicad_render.py +0 -0
  141. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_kicad_route.py +0 -0
  142. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_kicad_sch.py +0 -0
  143. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_library_check.py +0 -0
  144. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_library_hbridge.py +0 -0
  145. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_library_rtttl.py +0 -0
  146. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_lorawan_api.py +0 -0
  147. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_lorawan_sugar_migration.py +0 -0
  148. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_mcp_auth.py +0 -0
  149. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_mcp_client.py +0 -0
  150. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_mcp_hardware.py +0 -0
  151. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_mcp_jobs.py +0 -0
  152. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_mcp_resources.py +0 -0
  153. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_meshtastic.py +0 -0
  154. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_micropython.py +0 -0
  155. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_micropython_registers.py +0 -0
  156. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_modules.py +0 -0
  157. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_pin_protocols.py +0 -0
  158. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_pin_solver.py +0 -0
  159. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_printwarden.py +0 -0
  160. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_python_blocks.py +0 -0
  161. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_recommend.py +0 -0
  162. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_register_code.py +0 -0
  163. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_rf_ook.py +0 -0
  164. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_schema.py +0 -0
  165. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_schema_doc.py +0 -0
  166. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_schematics_gate.py +0 -0
  167. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_security_cors.py +0 -0
  168. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_seed_onboard.py +0 -0
  169. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_serve.py +0 -0
  170. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_sqlite_store.py +0 -0
  171. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_sqlite_stores.py +0 -0
  172. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_strict.py +0 -0
  173. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_targets.py +0 -0
  174. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_tasmota.py +0 -0
  175. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_tools_add_component.py +0 -0
  176. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_tools_solve_pins.py +0 -0
  177. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_uart_frames.py +0 -0
  178. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_workbench_boot.py +0 -0
  179. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_workbench_images.py +0 -0
  180. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_workbench_provision.py +0 -0
  181. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_workbench_serial.py +0 -0
  182. {wirestudio-0.45.0 → wirestudio-0.47.0}/tests/test_ws2812_method.py +0 -0
  183. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/agent/__init__.py +0 -0
  184. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/agent/eval.py +0 -0
  185. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/agent/session.py +0 -0
  186. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/api/__init__.py +0 -0
  187. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/api/__main__.py +0 -0
  188. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/api/auth.py +0 -0
  189. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/api/circuitpython.py +0 -0
  190. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/api/design_input.py +0 -0
  191. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/api/meshtastic.py +0 -0
  192. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/api/micropython.py +0 -0
  193. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/api/serve.py +0 -0
  194. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/csp/__init__.py +0 -0
  195. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/csp/compatibility.py +0 -0
  196. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/csp/pin_solver.py +0 -0
  197. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/designs/__init__.py +0 -0
  198. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/designs/active.py +0 -0
  199. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/designs/events.py +0 -0
  200. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/designs/store.py +0 -0
  201. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/enclosure/__init__.py +0 -0
  202. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/enclosure/openscad.py +0 -0
  203. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/enclosure/search.py +0 -0
  204. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/enclosure/yapp/LICENSE +0 -0
  205. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/enclosure/yapp/YAPPgenerator_v3.scad +0 -0
  206. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/errors.py +0 -0
  207. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/esphome_dashboard/__init__.py +0 -0
  208. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/__init__.py +0 -0
  209. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/access-panel.json +0 -0
  210. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/actuator-harness.json +0 -0
  211. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/air-quality-station.json +0 -0
  212. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/analog-node.json +0 -0
  213. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/atom-echo.json +0 -0
  214. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/atom-lite.json +0 -0
  215. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/atom-matrix.json +0 -0
  216. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/atoms3-lcd.json +0 -0
  217. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/atoms3-lite.json +0 -0
  218. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/atoms3-onboard.json +0 -0
  219. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/atomu-onboard.json +0 -0
  220. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/atomu.json +0 -0
  221. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/attic-logger.json +0 -0
  222. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/awning-control.json +0 -0
  223. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/bench-io.json +0 -0
  224. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/bl0906-mainmeter.json +0 -0
  225. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/blind-stepper.json +0 -0
  226. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/bluemotion.json +0 -0
  227. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/bluesonoff.json +0 -0
  228. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/button-toggles-light.json +0 -0
  229. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/c3-supermini.json +0 -0
  230. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/c6-supermini.json +0 -0
  231. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/co2-display.json +0 -0
  232. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/desk-climate.json +0 -0
  233. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/desk-matrix.json +0 -0
  234. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/distance-sensor.json +0 -0
  235. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/encoder-drives-stepper.json +0 -0
  236. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/energy-monitor.json +0 -0
  237. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/esp32-audio.json +0 -0
  238. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/esp32cam-wrover.json +0 -0
  239. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/esp32cam.json +0 -0
  240. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/flow-meter.json +0 -0
  241. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/garage-motion.json +0 -0
  242. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/grill-probe.json +0 -0
  243. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/guarded-button-light.json +0 -0
  244. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/keypad.json +0 -0
  245. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/kitchen-scale.json +0 -0
  246. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/led-display.json +0 -0
  247. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/lorawan-battery-uplink.json +0 -0
  248. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/motion-turns-on-light.json +0 -0
  249. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/motor-position.json +0 -0
  250. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/multi-temp.json +0 -0
  251. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/nextion-thermostat.json +0 -0
  252. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/oled-knob.json +0 -0
  253. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/oled.json +0 -0
  254. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/output-hub.json +0 -0
  255. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/parking-distance.json +0 -0
  256. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/presence-media-node.json +0 -0
  257. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/presence-rf.json +0 -0
  258. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/rc522.json +0 -0
  259. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/room-climate.json +0 -0
  260. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/rs485-energy.json +0 -0
  261. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/s3-devkitc.json +0 -0
  262. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/s3-supermini.json +0 -0
  263. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/securitypanel.json +0 -0
  264. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/smart-plug-v1.json +0 -0
  265. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/smart-plug.json +0 -0
  266. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/solder-fan.json +0 -0
  267. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/spi-io-node.json +0 -0
  268. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/subghz-rfid.json +0 -0
  269. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/t-beam-lorawan.json +0 -0
  270. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/t-beam.json +0 -0
  271. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/temp-above-turns-on-fan.json +0 -0
  272. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/temp-turns-on-fan.json +0 -0
  273. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/tft-touch.json +0 -0
  274. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/ttgo-lora32.json +0 -0
  275. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/tuya-smart-plug.json +0 -0
  276. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/usbc-led-strip.json +0 -0
  277. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/vent-damper.json +0 -0
  278. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/wasserpir.json +0 -0
  279. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/weather-station.json +0 -0
  280. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/wemosgps.json +0 -0
  281. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/wled-sensor-strip.json +0 -0
  282. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/examples/xiao-grove-climate.json +0 -0
  283. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/fleet/__init__.py +0 -0
  284. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/fleet/client.py +0 -0
  285. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/generate/__init__.py +0 -0
  286. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/generate/__main__.py +0 -0
  287. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/generate/ascii_gen.py +0 -0
  288. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/generate/circuitpython_gen.py +0 -0
  289. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/generate/display_intent.py +0 -0
  290. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/generate/micropython_gen.py +0 -0
  291. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/intent.py +0 -0
  292. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/inventory/__init__.py +0 -0
  293. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/inventory/buy.py +0 -0
  294. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/inventory/check.py +0 -0
  295. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/inventory/csv_io.py +0 -0
  296. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/inventory/match.py +0 -0
  297. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/inventory/store.py +0 -0
  298. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/jlcpcb/__init__.py +0 -0
  299. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/jlcpcb/__main__.py +0 -0
  300. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/jlcpcb/check.py +0 -0
  301. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/jlcpcb/client.py +0 -0
  302. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/kicad/__init__.py +0 -0
  303. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/kicad/drc.py +0 -0
  304. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/kicad/fab.py +0 -0
  305. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/kicad/import.py +0 -0
  306. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/kicad/importer.py +0 -0
  307. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/kicad/pcb.py +0 -0
  308. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/kicad/pcbnew_bridge.py +0 -0
  309. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/kicad/render.py +0 -0
  310. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/kicad/route.py +0 -0
  311. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/kicad/schematic.py +0 -0
  312. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/kicad/symbol_parser.py +0 -0
  313. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/boards/esp01_1m.yaml +0 -0
  314. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/boards/esp32-c3-supermini.yaml +0 -0
  315. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/boards/esp32-c6-supermini.yaml +0 -0
  316. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/boards/esp32-devkitc-v4.yaml +0 -0
  317. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/boards/esp32-s3-supermini.yaml +0 -0
  318. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/boards/esp32-wrover-cam.yaml +0 -0
  319. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/boards/esp32cam-ai-thinker.yaml +0 -0
  320. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/boards/esp8285-1m.yaml +0 -0
  321. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/boards/heltec-wifi-lora32-v2.yaml +0 -0
  322. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/boards/heltec-wifi-lora32-v3.yaml +0 -0
  323. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/boards/m5stack-atom-echo.yaml +0 -0
  324. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/boards/m5stack-atom-matrix.yaml +0 -0
  325. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/boards/m5stack-atom.yaml +0 -0
  326. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/boards/m5stack-atoms3-lite.yaml +0 -0
  327. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/boards/m5stack-atoms3.yaml +0 -0
  328. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/boards/nodemcu-32s.yaml +0 -0
  329. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/boards/nodemcu-v2.yaml +0 -0
  330. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/boards/seeed-xiao-esp32c3.yaml +0 -0
  331. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/boards/ttgo-lora32-v1.yaml +0 -0
  332. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/boards/ttgo-lora32-v2.yaml +0 -0
  333. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/boards/ttgo-t-beam.yaml +0 -0
  334. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/boards/wemos-d1-mini.yaml +0 -0
  335. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/aht10.yaml +0 -0
  336. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/apa102.yaml +0 -0
  337. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/axp192.yaml +0 -0
  338. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/bh1750.yaml +0 -0
  339. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/bme280.yaml +0 -0
  340. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/bmp280.yaml +0 -0
  341. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/cc1101.yaml +0 -0
  342. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/connector_1x02.yaml +0 -0
  343. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/connector_1x03.yaml +0 -0
  344. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/connector_1x04.yaml +0 -0
  345. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/connector_1x05.yaml +0 -0
  346. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/connector_1x06.yaml +0 -0
  347. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/connector_1x07.yaml +0 -0
  348. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/connector_1x08.yaml +0 -0
  349. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/connector_2x05_idc.yaml +0 -0
  350. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/dht.yaml +0 -0
  351. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/ds18b20.yaml +0 -0
  352. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/esp32_camera.yaml +0 -0
  353. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/esp32_rmt_led_strip.yaml +0 -0
  354. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/gpio_output.yaml +0 -0
  355. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/grove-aht20.yaml +0 -0
  356. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/hbridge.yaml +0 -0
  357. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/hbridge_mosfet.yaml +0 -0
  358. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/hc-sr501.yaml +0 -0
  359. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/htu21d.yaml +0 -0
  360. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/ili9xxx.yaml +0 -0
  361. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/ina219.yaml +0 -0
  362. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/lcd_pcf8574.yaml +0 -0
  363. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/led_indicator.yaml +0 -0
  364. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/level_shifter.yaml +0 -0
  365. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/max7219.yaml +0 -0
  366. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/max98357a.yaml +0 -0
  367. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/mosfet_low_side_driver.yaml +0 -0
  368. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/nextion.yaml +0 -0
  369. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/npn_low_side_driver.yaml +0 -0
  370. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/pca9685.yaml +0 -0
  371. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/pwm_fan.yaml +0 -0
  372. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/rc522.yaml +0 -0
  373. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/rcwl-0516.yaml +0 -0
  374. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/rdm6300.yaml +0 -0
  375. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/remote_transmitter.yaml +0 -0
  376. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/rf_bridge.yaml +0 -0
  377. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/scd4x.yaml +0 -0
  378. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/servo.yaml +0 -0
  379. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/sht3xd.yaml +0 -0
  380. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/sht4x.yaml +0 -0
  381. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/sx127x.yaml +0 -0
  382. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/tm1637.yaml +0 -0
  383. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/tm1638.yaml +0 -0
  384. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/tsl2561.yaml +0 -0
  385. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/tuya.yaml +0 -0
  386. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/tuya_sensor.yaml +0 -0
  387. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/tuya_switch.yaml +0 -0
  388. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/uln2003.yaml +0 -0
  389. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/usb_c_power_in.yaml +0 -0
  390. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/veml7700.yaml +0 -0
  391. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/vl53l0x.yaml +0 -0
  392. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/ws2812b.yaml +0 -0
  393. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/components/xpt2046.yaml +0 -0
  394. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/electrical.py +0 -0
  395. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/modules/env-display.yaml +0 -0
  396. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/modules/fan-controller.yaml +0 -0
  397. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/modules/motion-light.yaml +0 -0
  398. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/modules/oled-knob-13.yaml +0 -0
  399. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/library/modules/relay-4ch.yaml +0 -0
  400. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/mcp/__init__.py +0 -0
  401. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/mcp/auth.py +0 -0
  402. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/mcp/hardware.py +0 -0
  403. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/mcp/jobs.py +0 -0
  404. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/printwarden/__init__.py +0 -0
  405. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/printwarden/client.py +0 -0
  406. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/recommend/__init__.py +0 -0
  407. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/recommend/recommender.py +0 -0
  408. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/schema/__init__.py +0 -0
  409. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/seed.py +0 -0
  410. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/sqlite_store.py +0 -0
  411. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/targets/__init__.py +0 -0
  412. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/targets/base.py +0 -0
  413. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/targets/build_backend.py +0 -0
  414. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/targets/esphome.py +0 -0
  415. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/targets/lorawan/__init__.py +0 -0
  416. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/targets/lorawan/__main__.py +0 -0
  417. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/targets/lorawan/api.py +0 -0
  418. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/targets/lorawan/build_local.py +0 -0
  419. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/targets/lorawan/chirpstack.py +0 -0
  420. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/targets/lorawan/compile.py +0 -0
  421. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/targets/lorawan/templates/main.cpp.j2 +0 -0
  422. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/targets/lorawan/templates/platformio.ini.j2 +0 -0
  423. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/targets/lorawan/workbench_provision.py +0 -0
  424. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/targets/tasmota.py +0 -0
  425. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/workbench/__init__.py +0 -0
  426. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/workbench/boot.py +0 -0
  427. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/workbench/gate.py +0 -0
  428. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/workbench/images.py +0 -0
  429. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio/workbench/serial.py +0 -0
  430. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio.egg-info/dependency_links.txt +0 -0
  431. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio.egg-info/entry_points.txt +0 -0
  432. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio.egg-info/requires.txt +0 -0
  433. {wirestudio-0.45.0 → wirestudio-0.47.0}/wirestudio.egg-info/top_level.txt +0 -0
@@ -33,15 +33,16 @@ them when the user (or a CI gate) builds. None is vendored.
33
33
  | the LoRaWAN target (`templates/platformio.ini.j2`) | [RadioLib](https://github.com/jgromes/RadioLib) | MIT |
34
34
  | the LoRaWAN target | [LoRaWAN_ESP32](https://github.com/ropg/LoRaWAN_ESP32) | MIT |
35
35
  | `lorawan:` blocks on `axp192`, `dht`, `ssd1306`, `uart_gps` | [XPowersLib](https://github.com/lewisxhe/XPowersLib) (MIT), [Adafruit DHT sensor library](https://github.com/adafruit/DHT-sensor-library) (MIT), [Adafruit SSD1306](https://github.com/adafruit/Adafruit_SSD1306) (BSD-3-Clause), [TinyGPSPlus](https://github.com/mikalhart/TinyGPSPlus) (LGPL-2.1-or-later) | as listed |
36
- | `wled:` usermod blocks on `bme280`, `bmp280`, `sht3xd`, `ds18b20`, `dht` | [BME280](https://github.com/finitespace/BME280) (GPL-3.0), [SHT85](https://github.com/RobTillaart/SHT85) (MIT), [OneWire](https://github.com/PaulStoffregen/OneWire) (MIT-style notice in the source), [DHT_nonblocking](https://github.com/alwynallan/DHT_nonblocking) (GPL-3.0) | as listed |
36
+ | `wled:` usermod blocks on `bme280`, `bmp280`, `sht3xd`, `ds18b20`, `dht`, `vl53l0x`, `i2s_microphone`, `ld2410`, `ssd1306`, `st7789`, `mpu6050`, `bme680` | [BME280](https://github.com/finitespace/BME280) (GPL-3.0), [SHT85](https://github.com/RobTillaart/SHT85) (MIT), [OneWire](https://github.com/PaulStoffregen/OneWire) (MIT-style notice in the source), [DHT_nonblocking](https://github.com/alwynallan/DHT_nonblocking) (GPL-3.0), [VL53L0X](https://github.com/pololu/vl53l0x-arduino) (MIT), [arduinoFFT](https://github.com/kosme/arduinoFFT) (GPL-3.0), [ld2410](https://github.com/ncmreynolds/ld2410) (LGPL-2.1), [U8g2](https://github.com/olikraus/u8g2) (BSD-2-Clause), [TFT_eSPI](https://github.com/Bodmer/TFT_eSPI) (FreeBSD and MIT, per its licence file), [MPU6050](https://github.com/ElectronicCats/mpu6050) (MIT), [Bolder Flight Systems Eigen](https://github.com/bolderflight/eigen) (MIT, wrapping Eigen under MPL-2.0), [BSEC Software Library](https://github.com/boschsensortec/BSEC-Arduino-library) (Bosch's own licence: binary only, for use with Bosch sensors) | as listed |
37
37
  | `circuitpython:` blocks, copied into `CIRCUITPY/lib` | the `adafruit_*` and `neopixel` libraries of the [Adafruit CircuitPython Bundle](https://github.com/adafruit/Adafruit_CircuitPython_Bundle) | MIT |
38
38
  | `micropython:` blocks, installed by `mip` | [micropython-lib](https://github.com/micropython/micropython-lib) `ssd1306`, `logging`, `lora-sx127x` and `lora-sync` (MIT); [BME280](https://github.com/robert-hh/BME280), [BME680-Micropython](https://github.com/robert-hh/BME680-Micropython), [micropython_ahtx0](https://github.com/targetblank/micropython_ahtx0), [micropython-bmp280](https://github.com/dafvid/micropython-bmp280), [pyb_ina219](https://github.com/chrisb2/pyb_ina219), [micropython-mcp23008](https://github.com/mcauser/micropython-mcp23008), [micropython-mcp23017](https://github.com/mcauser/micropython-mcp23017), [micropython-mpu9x50](https://github.com/micropython-IMU/micropython-mpu9x50), [micropython-adafruit-pca9685](https://github.com/adafruit/micropython-adafruit-pca9685), [MicroPython_SCD4X](https://github.com/peter-l5/MicroPython_SCD4X), [micropython-sht31](https://github.com/kfricke/micropython-sht31), [MicroPython_SHT4X](https://github.com/jposada202020/MicroPython_SHT4X), [micropython-adafruit-tsl2561](https://github.com/adafruit/micropython-adafruit-tsl2561), [veml7700](https://github.com/palouf34/veml7700), [micropython-max7219](https://github.com/mcauser/micropython-max7219), [micropython-ili9341](https://github.com/rdagger/micropython-ili9341), [st7789py_mpy](https://github.com/russhughes/st7789py_mpy), [ads1x15](https://github.com/robert-hh/ads1x15), [hx711](https://github.com/robert-hh/hx711), [micropython_ir](https://github.com/peterhinch/micropython_ir), [upy-rtttl](https://github.com/dhylands/upy-rtttl) (MIT, some by the notice in the source); [vl53l0x](https://github.com/kevinmcaleer/vl53l0x) (BSD-3-Clause) | as listed |
39
39
  | the ESPHome generator, for the external-component LoRaWAN path | [lorawan-for-esphome](https://github.com/moellere/lorawan-for-esphome), pinned by commit | MIT |
40
40
  | the `cc1101` component's notes | the `radiolib_cc1101` ESPHome fork, which the user adds as an `external_components` entry; the generator does not emit it | per that fork |
41
41
 
42
42
  A firmware the user builds from these is the user's: its licence
43
- follows from the libraries it links, and a WLED build with the BME280 or
44
- DHT usermod links GPL-3.0 code.
43
+ follows from the libraries it links, and a WLED build with the BME280,
44
+ DHT or audioreactive usermod links GPL-3.0 code; one with the BME680
45
+ links Bosch's binary BSEC library under its own terms.
45
46
 
46
47
  ## Compiled into the web bundle
47
48
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: wirestudio
3
- Version: 0.45.0
3
+ Version: 0.47.0
4
4
  Summary: Agent-driven IoT device design tool that produces ESPHome YAML.
5
5
  Author: wirestudio contributors
6
6
  License: MIT
@@ -125,7 +125,7 @@ Detailed docs live in [`docs/`](docs/):
125
125
 
126
126
  ## Status
127
127
 
128
- `v0.40.0` — on PyPI (`pip install wirestudio`). The studio has wide
128
+ `v0.46.0` — on PyPI (`pip install wirestudio`). The studio has wide
129
129
  surface area (YAML, schematic, PCB + fab outputs, enclosure,
130
130
  subcircuits with electrical rules, inventory matching, component
131
131
  authoring, agent, MCP server, two ESPHome build paths, remote
@@ -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
- | **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)) |
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. Both gates are 25 of 28 (`gate.min_pass`, `gate.claude_code_min_pass`), one below the lower of two runs on one commit |
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
@@ -91,7 +91,7 @@ Detailed docs live in [`docs/`](docs/):
91
91
 
92
92
  ## Status
93
93
 
94
- `v0.40.0` — on PyPI (`pip install wirestudio`). The studio has wide
94
+ `v0.46.0` — on PyPI (`pip install wirestudio`). The studio has wide
95
95
  surface area (YAML, schematic, PCB + fab outputs, enclosure,
96
96
  subcircuits with electrical rules, inventory matching, component
97
97
  authoring, agent, MCP server, two ESPHome build paths, remote
@@ -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
- | **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)) |
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. Both gates are 25 of 28 (`gate.min_pass`, `gate.claude_code_min_pass`), one below the lower of two runs on one commit |
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
@@ -72,6 +72,7 @@ include = ["wirestudio*"]
72
72
  # tries to read components/ + boards/.
73
73
  "wirestudio.library" = ["components/*.yaml", "boards/*.yaml", "modules/*.yaml"]
74
74
  "wirestudio.agent" = ["*.yaml"]
75
+ "wirestudio.generate" = ["esphome_integrations.txt"]
75
76
  "wirestudio.schema" = ["*.json"]
76
77
  "wirestudio.examples" = ["*.json"]
77
78
  "wirestudio.targets.lorawan" = ["templates/*.j2"]
@@ -75,6 +75,33 @@ def test_set_board_drops_a_flash_size_measured_on_the_old_board(lib, garage_moti
75
75
  assert "flash_size_mb" not in garage_motion_design["board"]
76
76
 
77
77
 
78
+ def test_set_board_takes_a_revision_and_drops_it_with_the_board(lib, garage_motion_design):
79
+ out, is_error = execute_tool(
80
+ "set_board", {"library_id": "heltec-wifi-lora32-v4", "revision": "4.3"}, garage_motion_design, lib,
81
+ )
82
+ assert is_error is False
83
+ assert json.loads(out)["board"]["revision"] == "4.3"
84
+ assert garage_motion_design["board"]["revision"] == "4.3"
85
+ execute_tool("set_board", {"library_id": "wemos-d1-mini"}, garage_motion_design, lib)
86
+ assert "revision" not in garage_motion_design["board"]
87
+
88
+
89
+ def test_set_board_unlisted_revision_returns_error(lib, garage_motion_design):
90
+ out, is_error = execute_tool(
91
+ "set_board", {"library_id": "heltec-wifi-lora32-v4", "revision": "4.9"}, garage_motion_design, lib,
92
+ )
93
+ assert is_error is True
94
+ assert "4.2, 4.3" in json.loads(out)["error"]
95
+ assert garage_motion_design["board"]["library_id"] != "heltec-wifi-lora32-v4"
96
+
97
+
98
+ def test_list_boards_shows_revisions(lib, garage_motion_design):
99
+ out, _ = execute_tool("list_boards", {}, garage_motion_design, lib)
100
+ by_id = {b["library_id"]: b for b in json.loads(out)["boards"]}
101
+ assert by_id["heltec-wifi-lora32-v4"]["revisions"] == ["4.2", "4.3"]
102
+ assert by_id["esp32-devkitc-v4"]["revisions"] == []
103
+
104
+
78
105
  def test_set_board_unknown_returns_error(lib, garage_motion_design):
79
106
  out, is_error = execute_tool(
80
107
  "set_board", {"library_id": "no-such-board"}, garage_motion_design, lib,
@@ -506,3 +533,19 @@ def test_search_components_puts_an_alias_above_a_use_case(lib):
506
533
  body = json.loads(execute_tool("search_components", {"query": alias}, {}, lib)[0])
507
534
  assert body["matches"][0]["score"] >= 90
508
535
  assert alias_owner.id in {m["library_id"] for m in body["matches"] if m["score"] >= 90}
536
+
537
+
538
+ def test_add_component_hint_esphome_refuses_is_suffixed_with_a_note(lib, garage_motion_design):
539
+ out, _ = execute_tool(
540
+ "add_component", {"library_id": "bh1750", "instance_id_hint": "bh1750"}, garage_motion_design, lib,
541
+ )
542
+ body = json.loads(out)
543
+ assert body["instance_id"] == "bh1750_1"
544
+ assert body["note"].startswith("instance_id_hint 'bh1750' names an ESPHome integration")
545
+ out, _ = execute_tool(
546
+ "add_component", {"library_id": "bh1750", "instance_id_hint": "bh1750_1"}, garage_motion_design, lib,
547
+ )
548
+ body = json.loads(out)
549
+ assert body["instance_id"] == "bh1750_2" and "is already in use" in body["note"]
550
+ out, _ = execute_tool("add_component", {"library_id": "bh1750", "instance_id_hint": "lux"}, garage_motion_design, lib)
551
+ assert "note" not in json.loads(out)
@@ -345,3 +345,21 @@ def test_a_replayed_call_must_fit_the_tool_schema(library):
345
345
  ev.apply_calls(design, [("add_bus", {"id": "w", "type": "1wire", "pin": "D5", "bogus": 1})], library)
346
346
  with pytest.raises(RuntimeError, match="not an agent tool"):
347
347
  ev.apply_calls(design, [("create_design", {})], library)
348
+
349
+
350
+ def test_first_i2c_part_survives_the_library_id_as_hint(library, tmp_path):
351
+ """The hint a model reaches for first, the library id, names the
352
+ integration the part loads; the studio suffixes it instead of handing
353
+ ESPHome an id it refuses (#410)."""
354
+ task = BY_ID["first-i2c-part"]
355
+
356
+ def hinting(t, design):
357
+ final = copy.deepcopy(design)
358
+ log = ev.apply_calls(final, [("add_component", {"library_id": "bh1750", "instance_id_hint": "bh1750"}),
359
+ ("solve_pins", {})], library)
360
+ return final, log, {}, ""
361
+
362
+ [r] = ev.run_tasks([task], library, hinting, esphome=esphome_available(), workdir=tmp_path,
363
+ log=lambda s: None)
364
+ assert r.passed, r.reasons
365
+ assert r.tools == ["add_component", "solve_pins"]
@@ -309,10 +309,11 @@ def test_list_examples(client):
309
309
  assert r.status_code == 200
310
310
  examples = r.json()
311
311
  ids = {e["id"] for e in examples}
312
- assert {"garage-motion-v1", "awning-control", "ttgo-lora32"} <= ids
312
+ assert {"garage-motion", "awning-control", "ttgo-lora32"} <= ids
313
313
  for e in examples:
314
314
  assert e["board_library_id"]
315
315
  assert e["mcu"] in {"esp8266", "esp32"}
316
+ assert client.get(f"/examples/{e['id']}").status_code == 200
316
317
 
317
318
 
318
319
  def test_get_example_returns_design_json(client):
@@ -0,0 +1,214 @@
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_revision_pins_are_checked(lib):
41
+ v4 = (BUNDLED / "boards" / "heltec-wifi-lora32-v4.yaml").read_text()
42
+ assert check_board_yaml(v4, lib).errors == []
43
+ broken = v4.replace('"4.3": {radio: {txen: GPIO5}}', '"4.3": {radio: {txen: GPIO99}}')
44
+ assert "revisions.4.3.radio.txen: GPIO99 is not in gpio_capabilities" in _errors(broken, lib)
45
+ bad_key = v4.replace('"4.3": {radio: {txen: GPIO5}}', '"4.3": {radio: {pa_pin: GPIO5}}')
46
+ assert "unknown keys ['pa_pin']" in _errors(bad_key, lib)
47
+
48
+
49
+ def test_draft_passes_and_renders_its_onboard_parts(lib):
50
+ report = check_board_yaml(DRAFT, lib)
51
+ assert report.ok, report.errors
52
+ assert report.library_id == "lolin-s2-mini" and report.exists == ""
53
+ assert any("gpio_output, gpio_input" in v for v in report.verified)
54
+ assert any("34.3 x 25.4" in v for v in report.verified)
55
+ assert any("no kicad: block" in w for w in report.warnings)
56
+ assert any("datasheet" in n for n in report.not_checked)
57
+
58
+
59
+ def test_unparseable_and_invalid_files_are_reported(lib):
60
+ assert "not valid YAML" in _errors("id: [", lib)
61
+ assert "top level must be a mapping" in _errors("- a", lib)
62
+ assert "platformio_board" in _errors(DRAFT.replace("platformio_board: lolin_s2_mini\n", ""), lib)
63
+ assert "names the file" in _errors(DRAFT.replace("id: lolin-s2-mini", "id: Lolin S2"), lib)
64
+
65
+
66
+ def test_platform_fields_must_agree(lib):
67
+ assert "not an ESP32 variant" in _errors(DRAFT.replace("chip_variant: esp32s2", "chip_variant: esp32s9"), lib)
68
+ assert "needs mcu esp32" in _errors(DRAFT.replace("mcu: esp32\n", "mcu: esp8266\n"), lib)
69
+ assert "not an ESPHome platform" in _errors(DRAFT.replace("chip_variant: esp32s2", "chip_variant: stm32"), lib)
70
+ assert "framework" in _errors(DRAFT.replace("framework: arduino", "framework: zephyr"), lib)
71
+
72
+
73
+ def test_every_named_pin_must_be_a_gpio(lib):
74
+ errors = _errors(DRAFT.replace("sda: GPIO33", "sda: GPIO34").replace("pin: GPIO15", "pin: GPIO14"), lib)
75
+ assert "default_buses.i2c.sda: GPIO34 is not in gpio_capabilities" in errors
76
+ assert "onboard_peripherals.led.pin: GPIO14 is not in gpio_capabilities" in errors
77
+ assert "no scl pin" in _errors(DRAFT.replace(", scl: GPIO35", ""), lib)
78
+ assert "unknown bus type" in _errors(DRAFT.replace(" spi: {", " qspi: {"), lib)
79
+ assert "no GND rail" in _errors(DRAFT.replace(' - {name: "GND", voltage: 0.0}\n', ""), lib)
80
+
81
+
82
+ def test_an_input_only_pin_cannot_drive_a_bus_line(lib):
83
+ text = DRAFT.replace("GPIO33: [gpio, pwm, i2c_sda]", "GPIO33: [gpio, input_only]")
84
+ assert "GPIO33 is input_only and cannot drive sda" in _errors(text, lib)
85
+
86
+
87
+ def test_radio_pins_are_cross_checked(lib):
88
+ heltec = (BUNDLED / "boards" / "heltec-wifi-lora32-v2.yaml").read_text()
89
+ assert check_board_yaml(heltec, lib).ok
90
+ broken = heltec.replace("pins: {cs: GPIO18", "pins: {cs: GPIO99")
91
+ assert broken != heltec
92
+ assert "radio.pins.cs: GPIO99" in _errors(broken, lib)
93
+
94
+
95
+ def test_enclosure_geometry_stays_on_the_pcb(lib):
96
+ off = DRAFT.replace("offset_mm: 8.95", "offset_mm: 20.0")
97
+ assert "runs off the 25.4 mm short_a edge" in _errors(off, lib)
98
+ edge = DRAFT.replace("edge: short_a", "edge: top")
99
+ assert "edge 'top'" in _errors(edge, lib)
100
+
101
+
102
+ def test_pin_map_is_checked_against_the_symbol(lib, tmp_path, monkeypatch):
103
+ symdir = tmp_path / "symbols"
104
+ symdir.mkdir()
105
+ pins = " ".join(
106
+ f'(pin passive line (at 0 0 0) (length 2.54) (name "Pin_{n}") (number "{n}"))' for n in range(1, 31))
107
+ (symdir / "Connector_Generic.kicad_sym").write_text(
108
+ '(kicad_symbol_lib (version 20231120) (generator kicad_symbol_editor)'
109
+ f' (symbol "Conn_02x15_Odd_Even" {pins}))')
110
+ monkeypatch.setenv("KICAD8_SYMBOL_DIR", str(symdir))
111
+ report = check_board_yaml(C3, lib)
112
+ assert report.ok, report.errors
113
+ assert any("has every pin_map pin" in v for v in report.verified)
114
+
115
+ assert "has no pin '31'" in _errors(C3.replace('GPIO1: "19"', 'GPIO1: "31"'), lib)
116
+ assert "GPIO1 and GPIO0 share pin 17, which would short them" in _errors(C3.replace('GPIO1: "19"', 'GPIO1: "17"'), lib)
117
+ assert "no symbol 'Conn_02x20'" in _errors(C3.replace("Conn_02x15_Odd_Even", "Conn_02x20"), lib)
118
+ assert "symbol library 'RF_Module' not found" in _errors(DEVKIT, lib)
119
+
120
+
121
+ def test_create_saves_into_the_user_library_only(lib, tmp_path):
122
+ report = create_board(DRAFT, lib)
123
+ assert report.ok and report.saved.endswith("user/boards/lolin-s2-mini.yaml")
124
+ assert Path(report.saved).read_text() == DRAFT
125
+ assert lib.board_source("lolin-s2-mini") == "user"
126
+ assert lib.board("lolin-s2-mini").chip_variant == "esp32s2"
127
+ assert "lolin-s2-mini" in [b.id for b in lib.list_boards()]
128
+
129
+ again = create_board(DRAFT, lib)
130
+ assert not again.ok and any("already exists" in e for e in again.errors)
131
+ assert create_board(DRAFT, lib, overwrite=True).ok
132
+
133
+ bundled = create_board(DEVKIT, lib)
134
+ assert not bundled.ok and any("bundled board" in e for e in bundled.errors)
135
+ assert not (tmp_path / "user" / "boards" / "esp32-devkitc-v4.yaml").exists()
136
+
137
+ with pytest.raises(PermissionError):
138
+ lib.delete_board("esp32-devkitc-v4")
139
+ lib.delete_board("lolin-s2-mini")
140
+ assert lib.board_source("lolin-s2-mini") == ""
141
+ with pytest.raises(FileNotFoundError):
142
+ lib.board("lolin-s2-mini")
143
+
144
+ assert not create_board(DRAFT, Library(BUNDLED)).ok # no user dir configured
145
+
146
+
147
+ def test_a_bundled_board_wins_over_a_user_file(lib, tmp_path):
148
+ (tmp_path / "user" / "boards").mkdir(parents=True)
149
+ (tmp_path / "user" / "boards" / "esp32-devkitc-v4.yaml").write_text(DRAFT.replace("lolin-s2-mini", "esp32-devkitc-v4"))
150
+ assert lib.board_source("esp32-devkitc-v4") == "bundled"
151
+ assert lib.board("esp32-devkitc-v4").chip_variant == "esp32"
152
+ assert [b.id for b in lib.list_boards()].count("esp32-devkitc-v4") == 1
153
+
154
+
155
+ def test_user_board_flows_into_a_design(lib):
156
+ from wirestudio.generate.yaml_gen import render_yaml
157
+ from wirestudio.model import Design
158
+
159
+ assert create_board(DRAFT, lib).ok
160
+ design = Design.model_validate({
161
+ "schema_version": "0.1", "id": "t", "name": "t",
162
+ "board": {"library_id": "lolin-s2-mini", "mcu": "esp32"},
163
+ "power": {"supply": "usb-5v", "rail_voltage_v": 5.0, "budget_ma": 500},
164
+ "components": [{"id": "led", "library_id": "gpio_output", "label": "LED"}],
165
+ "buses": [], "requirements": [], "warnings": [],
166
+ "connections": [
167
+ {"component_id": "led", "pin_role": "OUT", "target": {"kind": "gpio", "pin": "GPIO15"}},
168
+ ],
169
+ })
170
+ rendered = render_yaml(design, lib)
171
+ assert "board: lolin_s2_mini" in rendered and "variant: ESP32S2" in rendered
172
+ assert "pin: GPIO15" in rendered
173
+
174
+
175
+ def test_api_round_trip(client):
176
+ check = client.post("/library/boards/check", json={"yaml": DRAFT})
177
+ assert check.status_code == 200 and check.json()["ok"]
178
+
179
+ created = client.post("/library/boards", json={"yaml": DRAFT})
180
+ assert created.status_code == 201 and created.json()["saved"]
181
+ listed = {b["id"]: b for b in client.get("/library/boards").json()}
182
+ assert listed["lolin-s2-mini"]["source"] == "user"
183
+ assert listed["esp32-devkitc-v4"]["source"] == "bundled"
184
+ assert client.get("/library/boards/lolin-s2-mini").json()["chip_variant"] == "esp32s2"
185
+ assert client.get("/library/boards/lolin-s2-mini/yaml").text == DRAFT
186
+
187
+ assert client.post("/library/boards", json={"yaml": DRAFT}).status_code == 409
188
+ assert client.post("/library/boards", json={"yaml": DEVKIT}).status_code == 409
189
+ bad = client.post("/library/boards", json={"yaml": DRAFT.replace("pin: GPIO15", "pin: GPIO14")})
190
+ assert bad.status_code == 422 and not bad.json()["ok"]
191
+
192
+ assert client.delete("/library/boards/esp32-devkitc-v4").status_code == 409
193
+ assert client.delete("/library/boards/lolin-s2-mini").status_code == 200
194
+ assert client.delete("/library/boards/lolin-s2-mini").status_code == 404
195
+ assert "lolin-s2-mini" not in {b["id"] for b in client.get("/library/boards").json()}
196
+
197
+
198
+ def test_agent_tools(lib):
199
+ from wirestudio.agent.tools import _run_board_check, _run_board_create, _run_board_delete, _run_list_boards
200
+
201
+ assert _run_board_check({}, lib, yaml=DRAFT)["ok"]
202
+ assert _run_board_create({}, lib, yaml=DRAFT)["saved"]
203
+ sources = {b["library_id"]: b["source"] for b in _run_list_boards({}, lib)["boards"]}
204
+ assert sources["lolin-s2-mini"] == "user"
205
+ assert _run_board_delete({}, lib, library_id="lolin-s2-mini")["ok"]
206
+ assert not _run_board_delete({}, lib, library_id="esp32-devkitc-v4")["ok"]
207
+
208
+
209
+ def test_platformio_board_is_looked_up_in_esphome(lib):
210
+ pytest.importorskip("esphome")
211
+ assert any("lolin_s2_mini is an ESPHome esp32 board" in v for v in check_board_yaml(DRAFT, lib).verified)
212
+ unknown = check_board_yaml(DRAFT.replace("lolin_s2_mini", "no_such_board"), lib)
213
+ assert unknown.ok and any("not in ESPHome's esp32 board list" in w for w in unknown.warnings)
214
+ 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
+
@@ -207,3 +207,15 @@ def test_connector_adds_with_unassigned_positions(library):
207
207
  add_component_with_connections(design, library, library_id="connector_1x04", label="Plug", instance_id_hint="plug")
208
208
  targets = [c["target"] for c in design["connections"] if c["component_id"] == "plug"]
209
209
  assert len(targets) == 4 and all(t == {"kind": "net", "component_id": "", "net": ""} for t in targets)
210
+
211
+
212
+ def test_a_hint_esphome_refuses_is_suffixed(library):
213
+ """`bh1750` as the id of a bh1750 collides with the integration the
214
+ render loads, so ESPHome refuses it; `fan` on an output is fine."""
215
+ design = _empty_design()
216
+ instance_id, _ = add_component_with_connections(design, library, library_id="bh1750", instance_id_hint="bh1750")
217
+ assert instance_id == "bh1750_1"
218
+ assert [c["id"] for c in design["components"]] == ["bh1750_1"]
219
+ assert {c["component_id"] for c in design["connections"]} == {"bh1750_1"}
220
+ instance_id, _ = add_component_with_connections(design, library, library_id="gpio_output", instance_id_hint="fan")
221
+ assert instance_id == "fan"