wirestudio 0.42.2__tar.gz → 0.44.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 (427) hide show
  1. {wirestudio-0.42.2/wirestudio.egg-info → wirestudio-0.44.0}/PKG-INFO +4 -3
  2. {wirestudio-0.42.2 → wirestudio-0.44.0}/README.md +2 -2
  3. {wirestudio-0.42.2 → wirestudio-0.44.0}/pyproject.toml +1 -0
  4. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_codec.py +9 -9
  5. wirestudio-0.44.0/tests/test_community.py +191 -0
  6. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_firmware_gen.py +13 -13
  7. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_hardware_gate.py +31 -16
  8. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_lorawan_api.py +9 -10
  9. wirestudio-0.42.2/tests/test_lorawan_lower.py → wirestudio-0.44.0/tests/test_lorawan_sugar_migration.py +46 -52
  10. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_mcp_client.py +1 -1
  11. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_migrate.py +23 -2
  12. wirestudio-0.44.0/tests/test_rf_ook.py +100 -0
  13. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_targets.py +9 -8
  14. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_workbench_boot.py +42 -12
  15. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_workbench_serial.py +3 -3
  16. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/__init__.py +1 -1
  17. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/agent/tools.py +11 -1
  18. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/api/app.py +9 -23
  19. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/api/circuitpython.py +2 -6
  20. wirestudio-0.44.0/wirestudio/api/community.py +268 -0
  21. wirestudio-0.44.0/wirestudio/api/design_input.py +26 -0
  22. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/api/micropython.py +2 -6
  23. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/api/workbench.py +5 -2
  24. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/access-panel.json +1 -1
  25. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/actuator-harness.json +1 -1
  26. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/air-quality-station.json +1 -1
  27. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/analog-node.json +1 -1
  28. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/atom-echo.json +1 -1
  29. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/atom-lite.json +1 -1
  30. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/atom-matrix.json +1 -1
  31. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/atoms3-lcd.json +1 -1
  32. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/atoms3-lite.json +1 -1
  33. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/atoms3-onboard.json +1 -1
  34. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/atomu-onboard.json +1 -1
  35. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/atomu.json +1 -1
  36. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/attic-logger.json +1 -1
  37. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/awning-control.json +1 -1
  38. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/bench-io.json +1 -1
  39. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/bl0906-mainmeter.json +1 -1
  40. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/blind-stepper.json +1 -1
  41. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/bluemotion.json +1 -1
  42. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/bluesonoff.json +1 -1
  43. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/button-toggles-light.json +1 -1
  44. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/c3-supermini.json +1 -1
  45. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/c6-supermini.json +1 -1
  46. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/co2-display.json +1 -1
  47. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/desk-climate.json +1 -1
  48. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/desk-matrix.json +1 -1
  49. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/distance-sensor.json +1 -1
  50. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/encoder-drives-stepper.json +1 -1
  51. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/energy-monitor.json +1 -1
  52. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/esp32-audio.json +1 -1
  53. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/esp32cam-wrover.json +1 -1
  54. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/esp32cam.json +1 -1
  55. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/flow-meter.json +1 -1
  56. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/garage-motion.json +1 -1
  57. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/grill-probe.json +1 -1
  58. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/guarded-button-light.json +1 -1
  59. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/heltec-v4-gnss-tracker.json +1 -1
  60. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/keypad.json +1 -1
  61. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/kitchen-scale.json +1 -1
  62. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/led-display.json +1 -1
  63. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/lorawan-battery-uplink.json +1 -1
  64. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/motion-turns-on-light.json +1 -1
  65. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/motor-position.json +1 -1
  66. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/multi-temp.json +1 -1
  67. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/nextion-thermostat.json +1 -1
  68. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/oled-knob.json +1 -1
  69. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/oled.json +1 -1
  70. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/output-hub.json +1 -1
  71. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/parking-distance.json +1 -1
  72. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/presence-media-node.json +1 -1
  73. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/presence-rf.json +1 -1
  74. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/rc522.json +1 -1
  75. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/room-climate.json +1 -1
  76. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/rs485-energy.json +1 -1
  77. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/s3-devkitc.json +1 -1
  78. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/s3-supermini.json +1 -1
  79. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/securitypanel.json +1 -1
  80. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/smart-plug-v1.json +1 -1
  81. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/smart-plug.json +1 -1
  82. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/solder-fan.json +1 -1
  83. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/spi-io-node.json +1 -1
  84. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/subghz-rfid.json +1 -1
  85. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/t-beam-lorawan.json +1 -1
  86. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/t-beam.json +1 -1
  87. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/temp-above-turns-on-fan.json +1 -1
  88. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/temp-turns-on-fan.json +1 -1
  89. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/tft-touch.json +1 -1
  90. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/ttgo-lora32.json +1 -1
  91. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/tuya-smart-plug.json +1 -1
  92. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/usbc-led-strip.json +1 -1
  93. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/vent-damper.json +1 -1
  94. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/wasserpir.json +1 -1
  95. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/weather-station.json +1 -1
  96. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/wemosgps.json +1 -1
  97. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/wled-sensor-strip.json +1 -1
  98. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/xiao-grove-climate.json +1 -1
  99. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/generate/yaml_gen.py +35 -4
  100. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/jlcpcb/__init__.py +2 -2
  101. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/kicad/erc.py +2 -1
  102. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/kicad/netlist.py +2 -1
  103. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/kicad/pcb.py +2 -1
  104. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/kicad/render.py +2 -1
  105. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/remote_receiver.yaml +26 -4
  106. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/sx127x.yaml +22 -1
  107. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/mcp/hardware.py +10 -3
  108. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/migrate.py +107 -24
  109. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/model.py +13 -32
  110. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/schema/design.schema.json +14 -25
  111. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/targets/lorawan/__init__.py +0 -15
  112. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/targets/lorawan/api.py +16 -6
  113. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/targets/lorawan/codec.py +2 -5
  114. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/targets/lorawan/compile.py +2 -1
  115. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/targets/lorawan/firmware_gen.py +1 -3
  116. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/targets/tasmota.py +8 -3
  117. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/targets/wled.py +6 -7
  118. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/workbench/boot.py +46 -18
  119. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/workbench/client.py +10 -0
  120. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/workbench/gate.py +7 -16
  121. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/workbench/images.py +1 -1
  122. {wirestudio-0.42.2 → wirestudio-0.44.0/wirestudio.egg-info}/PKG-INFO +4 -3
  123. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio.egg-info/SOURCES.txt +5 -2
  124. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio.egg-info/requires.txt +1 -0
  125. wirestudio-0.42.2/wirestudio/targets/lorawan/lower.py +0 -111
  126. {wirestudio-0.42.2 → wirestudio-0.44.0}/LICENSE +0 -0
  127. {wirestudio-0.42.2 → wirestudio-0.44.0}/LICENSES.md +0 -0
  128. {wirestudio-0.42.2 → wirestudio-0.44.0}/setup.cfg +0 -0
  129. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_active_design.py +0 -0
  130. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_agent.py +0 -0
  131. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_agent_eval.py +0 -0
  132. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_agent_stream.py +0 -0
  133. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_api.py +0 -0
  134. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_api_auth.py +0 -0
  135. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_api_route.py +0 -0
  136. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_ascii_gen.py +0 -0
  137. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_build_backend.py +0 -0
  138. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_check_upgrade.py +0 -0
  139. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_chirpstack.py +0 -0
  140. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_circuitpython.py +0 -0
  141. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_compatibility.py +0 -0
  142. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_compile.py +0 -0
  143. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_component_corpus.py +0 -0
  144. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_connectors.py +0 -0
  145. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_coverage_baseline.py +0 -0
  146. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_design_events.py +0 -0
  147. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_designs.py +0 -0
  148. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_designs_seed.py +0 -0
  149. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_display_intent.py +0 -0
  150. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_electrical.py +0 -0
  151. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_enclosure.py +0 -0
  152. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_enclosure_gate.py +0 -0
  153. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_enclosure_search.py +0 -0
  154. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_errors.py +0 -0
  155. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_esphome_dashboard.py +0 -0
  156. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_esphome_support.py +0 -0
  157. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_fleet.py +0 -0
  158. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_footprints.py +0 -0
  159. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_framework_baseline.py +0 -0
  160. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_intent.py +0 -0
  161. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_inventory.py +0 -0
  162. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_jlcpcb.py +0 -0
  163. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_kicad.py +0 -0
  164. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_kicad_erc.py +0 -0
  165. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_kicad_fab.py +0 -0
  166. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_kicad_import.py +0 -0
  167. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_kicad_pcb.py +0 -0
  168. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_kicad_render.py +0 -0
  169. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_kicad_route.py +0 -0
  170. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_kicad_sch.py +0 -0
  171. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_library_check.py +0 -0
  172. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_library_hbridge.py +0 -0
  173. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_library_rtttl.py +0 -0
  174. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_lorawan.py +0 -0
  175. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_mcp_auth.py +0 -0
  176. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_mcp_hardware.py +0 -0
  177. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_mcp_jobs.py +0 -0
  178. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_mcp_resources.py +0 -0
  179. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_mcp_server.py +0 -0
  180. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_meshtastic.py +0 -0
  181. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_micropython.py +0 -0
  182. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_micropython_registers.py +0 -0
  183. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_modules.py +0 -0
  184. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_pin_protocols.py +0 -0
  185. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_pin_solver.py +0 -0
  186. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_printwarden.py +0 -0
  187. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_python_blocks.py +0 -0
  188. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_recommend.py +0 -0
  189. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_register_code.py +0 -0
  190. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_schema.py +0 -0
  191. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_schema_doc.py +0 -0
  192. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_schematics_gate.py +0 -0
  193. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_security_cors.py +0 -0
  194. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_seed_onboard.py +0 -0
  195. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_serve.py +0 -0
  196. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_sqlite_store.py +0 -0
  197. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_sqlite_stores.py +0 -0
  198. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_strict.py +0 -0
  199. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_tasmota.py +0 -0
  200. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_tbeam_lorawan_example.py +0 -0
  201. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_tools_add_bus.py +0 -0
  202. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_tools_add_component.py +0 -0
  203. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_tools_solve_pins.py +0 -0
  204. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_uart_frames.py +0 -0
  205. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_validate.py +0 -0
  206. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_wled.py +0 -0
  207. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_workbench.py +0 -0
  208. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_workbench_images.py +0 -0
  209. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_workbench_provision.py +0 -0
  210. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_ws2812_method.py +0 -0
  211. {wirestudio-0.42.2 → wirestudio-0.44.0}/tests/test_yaml_gen.py +0 -0
  212. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/agent/__init__.py +0 -0
  213. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/agent/agent.py +0 -0
  214. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/agent/eval.py +0 -0
  215. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/agent/eval_tasks.yaml +0 -0
  216. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/agent/session.py +0 -0
  217. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/api/__init__.py +0 -0
  218. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/api/__main__.py +0 -0
  219. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/api/auth.py +0 -0
  220. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/api/meshtastic.py +0 -0
  221. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/api/schemas.py +0 -0
  222. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/api/serve.py +0 -0
  223. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/csp/__init__.py +0 -0
  224. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/csp/compatibility.py +0 -0
  225. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/csp/pin_solver.py +0 -0
  226. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/designs/__init__.py +0 -0
  227. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/designs/active.py +0 -0
  228. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/designs/events.py +0 -0
  229. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/designs/seed.py +0 -0
  230. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/designs/store.py +0 -0
  231. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/enclosure/__init__.py +0 -0
  232. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/enclosure/openscad.py +0 -0
  233. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/enclosure/search.py +0 -0
  234. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/enclosure/yapp/LICENSE +0 -0
  235. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/enclosure/yapp/YAPPgenerator_v3.scad +0 -0
  236. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/errors.py +0 -0
  237. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/esphome_dashboard/__init__.py +0 -0
  238. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/examples/__init__.py +0 -0
  239. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/fleet/__init__.py +0 -0
  240. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/fleet/client.py +0 -0
  241. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/generate/__init__.py +0 -0
  242. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/generate/__main__.py +0 -0
  243. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/generate/ascii_gen.py +0 -0
  244. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/generate/circuitpython_gen.py +0 -0
  245. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/generate/display_intent.py +0 -0
  246. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/generate/micropython_gen.py +0 -0
  247. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/intent.py +0 -0
  248. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/inventory/__init__.py +0 -0
  249. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/inventory/buy.py +0 -0
  250. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/inventory/check.py +0 -0
  251. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/inventory/csv_io.py +0 -0
  252. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/inventory/match.py +0 -0
  253. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/inventory/store.py +0 -0
  254. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/jlcpcb/__main__.py +0 -0
  255. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/jlcpcb/check.py +0 -0
  256. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/jlcpcb/client.py +0 -0
  257. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/kicad/__init__.py +0 -0
  258. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/kicad/fab.py +0 -0
  259. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/kicad/generator.py +0 -0
  260. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/kicad/import.py +0 -0
  261. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/kicad/importer.py +0 -0
  262. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/kicad/pcbnew_bridge.py +0 -0
  263. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/kicad/route.py +0 -0
  264. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/kicad/schematic.py +0 -0
  265. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/kicad/symbol_parser.py +0 -0
  266. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/__init__.py +0 -0
  267. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/boards/esp01_1m.yaml +0 -0
  268. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/boards/esp32-c3-devkitm-1.yaml +0 -0
  269. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/boards/esp32-c3-supermini.yaml +0 -0
  270. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/boards/esp32-c6-supermini.yaml +0 -0
  271. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/boards/esp32-devkitc-v4.yaml +0 -0
  272. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/boards/esp32-s3-devkitc-1.yaml +0 -0
  273. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/boards/esp32-s3-supermini.yaml +0 -0
  274. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/boards/esp32-wrover-cam.yaml +0 -0
  275. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/boards/esp32cam-ai-thinker.yaml +0 -0
  276. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/boards/esp8285-1m.yaml +0 -0
  277. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/boards/heltec-wifi-lora32-v2.yaml +0 -0
  278. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/boards/heltec-wifi-lora32-v3.yaml +0 -0
  279. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/boards/heltec-wifi-lora32-v4.yaml +0 -0
  280. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/boards/m5stack-atom-echo.yaml +0 -0
  281. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/boards/m5stack-atom-matrix.yaml +0 -0
  282. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/boards/m5stack-atom.yaml +0 -0
  283. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/boards/m5stack-atoms3-lite.yaml +0 -0
  284. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/boards/m5stack-atoms3.yaml +0 -0
  285. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/boards/m5stack-atomu.yaml +0 -0
  286. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/boards/nodemcu-32s.yaml +0 -0
  287. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/boards/nodemcu-v2.yaml +0 -0
  288. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/boards/seeed-xiao-esp32c3.yaml +0 -0
  289. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/boards/ttgo-lora32-v1.yaml +0 -0
  290. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/boards/ttgo-lora32-v2.yaml +0 -0
  291. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/boards/ttgo-t-beam.yaml +0 -0
  292. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/boards/wemos-d1-mini.yaml +0 -0
  293. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/check.py +0 -0
  294. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/adc.yaml +0 -0
  295. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/ads1115.yaml +0 -0
  296. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/ads1115_channel.yaml +0 -0
  297. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/aht10.yaml +0 -0
  298. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/apa102.yaml +0 -0
  299. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/axp192.yaml +0 -0
  300. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/battery_adc.yaml +0 -0
  301. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/bh1750.yaml +0 -0
  302. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/bl0906.yaml +0 -0
  303. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/bme280.yaml +0 -0
  304. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/bme680.yaml +0 -0
  305. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/bmp180.yaml +0 -0
  306. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/bmp280.yaml +0 -0
  307. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/cc1101.yaml +0 -0
  308. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/connector_1x02.yaml +0 -0
  309. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/connector_1x03.yaml +0 -0
  310. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/connector_1x04.yaml +0 -0
  311. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/connector_1x05.yaml +0 -0
  312. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/connector_1x06.yaml +0 -0
  313. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/connector_1x07.yaml +0 -0
  314. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/connector_1x08.yaml +0 -0
  315. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/connector_2x05_idc.yaml +0 -0
  316. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/cse7766.yaml +0 -0
  317. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/ct_clamp.yaml +0 -0
  318. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/dht.yaml +0 -0
  319. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/ds18b20.yaml +0 -0
  320. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/esp32_camera.yaml +0 -0
  321. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/esp32_rmt_led_strip.yaml +0 -0
  322. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/gpio_input.yaml +0 -0
  323. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/gpio_output.yaml +0 -0
  324. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/grove-aht20.yaml +0 -0
  325. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/hbridge.yaml +0 -0
  326. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/hbridge_mosfet.yaml +0 -0
  327. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/hc-sr04.yaml +0 -0
  328. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/hc-sr501.yaml +0 -0
  329. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/hlw8012.yaml +0 -0
  330. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/htu21d.yaml +0 -0
  331. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/hx711.yaml +0 -0
  332. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/i2s_microphone.yaml +0 -0
  333. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/ili9xxx.yaml +0 -0
  334. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/ina219.yaml +0 -0
  335. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/lcd_pcf8574.yaml +0 -0
  336. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/ld2410.yaml +0 -0
  337. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/ld2420.yaml +0 -0
  338. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/led_indicator.yaml +0 -0
  339. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/level_shifter.yaml +0 -0
  340. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/max31855.yaml +0 -0
  341. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/max7219.yaml +0 -0
  342. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/max98357a.yaml +0 -0
  343. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/mcp23008.yaml +0 -0
  344. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/mcp23017.yaml +0 -0
  345. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/mcp23s17.yaml +0 -0
  346. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/mcp3008.yaml +0 -0
  347. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/mcp3008_channel.yaml +0 -0
  348. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/mhz19.yaml +0 -0
  349. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/modbus.yaml +0 -0
  350. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/mosfet_low_side_driver.yaml +0 -0
  351. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/mpu6050.yaml +0 -0
  352. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/mpu6886.yaml +0 -0
  353. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/nextion.yaml +0 -0
  354. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/npn_low_side_driver.yaml +0 -0
  355. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/pca9685.yaml +0 -0
  356. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/pcf8574.yaml +0 -0
  357. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/pmsx003.yaml +0 -0
  358. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/pn532.yaml +0 -0
  359. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/potentiometer.yaml +0 -0
  360. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/pulse_counter.yaml +0 -0
  361. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/pwm_fan.yaml +0 -0
  362. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/pzemac.yaml +0 -0
  363. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/rc522.yaml +0 -0
  364. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/rcwl-0516.yaml +0 -0
  365. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/rdm6300.yaml +0 -0
  366. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/remote_transmitter.yaml +0 -0
  367. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/rf_bridge.yaml +0 -0
  368. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/rotary_encoder.yaml +0 -0
  369. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/rtttl.yaml +0 -0
  370. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/scd4x.yaml +0 -0
  371. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/sdm_meter.yaml +0 -0
  372. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/servo.yaml +0 -0
  373. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/sgp4x.yaml +0 -0
  374. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/sht3xd.yaml +0 -0
  375. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/sht4x.yaml +0 -0
  376. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/sn74hc595.yaml +0 -0
  377. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/ssd1306.yaml +0 -0
  378. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/st7789.yaml +0 -0
  379. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/tm1637.yaml +0 -0
  380. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/tm1638.yaml +0 -0
  381. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/tsl2561.yaml +0 -0
  382. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/tuya.yaml +0 -0
  383. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/tuya_sensor.yaml +0 -0
  384. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/tuya_switch.yaml +0 -0
  385. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/uart_gps.yaml +0 -0
  386. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/uln2003.yaml +0 -0
  387. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/usb_c_power_in.yaml +0 -0
  388. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/veml7700.yaml +0 -0
  389. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/vl53l0x.yaml +0 -0
  390. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/voltage_divider.yaml +0 -0
  391. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/wiegand.yaml +0 -0
  392. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/ws2812b.yaml +0 -0
  393. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/components/xpt2046.yaml +0 -0
  394. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/electrical.py +0 -0
  395. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/modules/env-display.yaml +0 -0
  396. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/modules/fan-controller.yaml +0 -0
  397. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/modules/motion-light.yaml +0 -0
  398. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/modules/oled-knob-13.yaml +0 -0
  399. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/library/modules/relay-4ch.yaml +0 -0
  400. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/mcp/__init__.py +0 -0
  401. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/mcp/auth.py +0 -0
  402. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/mcp/jobs.py +0 -0
  403. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/mcp/server.py +0 -0
  404. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/printwarden/__init__.py +0 -0
  405. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/printwarden/client.py +0 -0
  406. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/recommend/__init__.py +0 -0
  407. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/recommend/recommender.py +0 -0
  408. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/schema/__init__.py +0 -0
  409. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/seed.py +0 -0
  410. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/sqlite_store.py +0 -0
  411. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/targets/__init__.py +0 -0
  412. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/targets/base.py +0 -0
  413. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/targets/build_backend.py +0 -0
  414. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/targets/esphome.py +0 -0
  415. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/targets/lorawan/__main__.py +0 -0
  416. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/targets/lorawan/build_local.py +0 -0
  417. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/targets/lorawan/chirpstack.py +0 -0
  418. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/targets/lorawan/templates/main.cpp.j2 +0 -0
  419. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/targets/lorawan/templates/platformio.ini.j2 +0 -0
  420. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/targets/lorawan/workbench_provision.py +0 -0
  421. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/targets/wled_build.py +0 -0
  422. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/validate.py +0 -0
  423. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/workbench/__init__.py +0 -0
  424. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio/workbench/serial.py +0 -0
  425. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio.egg-info/dependency_links.txt +0 -0
  426. {wirestudio-0.42.2 → wirestudio-0.44.0}/wirestudio.egg-info/entry_points.txt +0 -0
  427. {wirestudio-0.42.2 → wirestudio-0.44.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.42.2
3
+ Version: 0.44.0
4
4
  Summary: Agent-driven IoT device design tool that produces ESPHome YAML.
5
5
  Author: wirestudio contributors
6
6
  License: MIT
@@ -19,6 +19,7 @@ Requires-Dist: httpx>=0.27
19
19
  Requires-Dist: slowapi>=0.1.9
20
20
  Requires-Dist: mcp<3,>=2.0
21
21
  Requires-Dist: websockets>=12
22
+ Requires-Dist: pillow>=10
22
23
  Provides-Extra: dev
23
24
  Requires-Dist: pytest>=8.0; extra == "dev"
24
25
  Requires-Dist: ruff>=0.4; extra == "dev"
@@ -151,8 +152,8 @@ Tiers, in priority order:
151
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 |
152
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 |
153
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)) |
154
- | **Works (hardware-validated)** | LoRaWAN target | build RadioLib + LoRaWAN_ESP32 firmware for US915 radio boards, flash over WebSerial, provision against ChirpStack | every radio board's firmware, and designs carrying every component's LoRaWAN block, build in CI ([gate](.github/workflows/lorawan-firmware.yml)); validated end-to-end on a TTGO T-Beam and Heltec WiFi LoRa 32 V2 and V3 against live ChirpStack 4.17 — no automated live-device gate |
155
- | **Works (hardware-validated)** | LoRaWAN external-component path | emit ESPHome YAML referencing `lorawan-for-esphome`, provision keys into `secrets.yaml`, build through fleet | validated end-to-end on a Heltec WiFi LoRa 32 V4 (SX1262 + external PA): joined, uplinked and decoded against live ChirpStack. Payloads over 11 bytes need the data rate asserted per uplink — US915 DR0 caps at 11 and ADR drives to DR0 on a strong link. No automated live-device gate |
155
+ | **Verified** | LoRaWAN target | build RadioLib + LoRaWAN_ESP32 firmware for US915 radio boards, flash over WebSerial, provision against ChirpStack | every radio board's firmware, and designs carrying every component's LoRaWAN block, build on every PR that touches the target and daily ([gate](.github/workflows/lorawan-firmware.yml)); nightly, the hardware gate asserts a TTGO T-Beam running this firmware is on the bench, logging and seen by ChirpStack within the hour ([gate](docs/workbench.md#the-gate)). Validated end-to-end on a T-Beam and Heltec WiFi LoRa 32 V2 and V3 against live ChirpStack 4.17. The nightly gate does not reflash: each reflash costs a rejoin and a DevNonce |
156
+ | **Verified** | LoRaWAN external-component path | emit ESPHome YAML referencing `lorawan-for-esphome`, provision keys into `secrets.yaml`, build through fleet | the bundled examples that use it pass `esphome config` with the component fetched, every PR ([gate](.github/workflows/esphome-config.yml)); nightly, the hardware gate asserts three boards running it (Heltec WiFi LoRa 32 V4 and V2, TTGO T3) are on the bench, logging and seen by ChirpStack within the hour ([gate](docs/workbench.md#the-gate)). Validated end-to-end on the V4 (SX1262 + external PA): joined, uplinked and decoded against live ChirpStack. Payloads over 11 bytes need the data rate asserted per uplink — US915 DR0 caps at 11 and ADR drives to DR0 on a strong link |
156
157
  | **Works (hardware-validated)** | Remote workbench | flash a board on a bench slot, and run the whole LoRaWAN bring-up (flash → register → key push → verify) headlessly | wire format verified against a reference bench; flashing and LoRaWAN bring-up exercised repeatedly on a live Pi bench with an ESP32-S3. Transport tests in `tests/test_workbench.py` use wire-level fakes; no automated live-bench gate |
157
158
  | **Verified** | Tasmota target | emit a Tasmota device template (`/tasmota/template`) mapping solved pins to Tasmota GPIO function ids | function ids + per-chip layouts sourced from `tasmota_template.h`; unit tests pin the Sonoff S31 template convention for the smart-plug example |
158
159
  | **Verified** | WLED target | emit WLED's `cfg.json` (`/wled/cfg`): LED outputs, buttons, the IR receiver, the relay and the bus pins, the runtime tier a stock release image reads at boot; sensors are usermods, compiled into the pinned release through a generated `platformio_override.ini` (`/wled/compile`, streamed) with their settings in `cfg.json`'s `um` section | keys and type codes sourced from `wled00/const.h` and `cfg.cpp`; goldens pin four examples in `tests/test_wled.py`; the usermod example builds against WLED v0.15.2 on every PR that touches the target and nightly ([gate](.github/workflows/wled-firmware.yml)) |
@@ -118,8 +118,8 @@ Tiers, in priority order:
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)) |
121
- | **Works (hardware-validated)** | LoRaWAN target | build RadioLib + LoRaWAN_ESP32 firmware for US915 radio boards, flash over WebSerial, provision against ChirpStack | every radio board's firmware, and designs carrying every component's LoRaWAN block, build in CI ([gate](.github/workflows/lorawan-firmware.yml)); validated end-to-end on a TTGO T-Beam and Heltec WiFi LoRa 32 V2 and V3 against live ChirpStack 4.17 — no automated live-device gate |
122
- | **Works (hardware-validated)** | LoRaWAN external-component path | emit ESPHome YAML referencing `lorawan-for-esphome`, provision keys into `secrets.yaml`, build through fleet | validated end-to-end on a Heltec WiFi LoRa 32 V4 (SX1262 + external PA): joined, uplinked and decoded against live ChirpStack. Payloads over 11 bytes need the data rate asserted per uplink — US915 DR0 caps at 11 and ADR drives to DR0 on a strong link. No automated live-device gate |
121
+ | **Verified** | LoRaWAN target | build RadioLib + LoRaWAN_ESP32 firmware for US915 radio boards, flash over WebSerial, provision against ChirpStack | every radio board's firmware, and designs carrying every component's LoRaWAN block, build on every PR that touches the target and daily ([gate](.github/workflows/lorawan-firmware.yml)); nightly, the hardware gate asserts a TTGO T-Beam running this firmware is on the bench, logging and seen by ChirpStack within the hour ([gate](docs/workbench.md#the-gate)). Validated end-to-end on a T-Beam and Heltec WiFi LoRa 32 V2 and V3 against live ChirpStack 4.17. The nightly gate does not reflash: each reflash costs a rejoin and a DevNonce |
122
+ | **Verified** | LoRaWAN external-component path | emit ESPHome YAML referencing `lorawan-for-esphome`, provision keys into `secrets.yaml`, build through fleet | the bundled examples that use it pass `esphome config` with the component fetched, every PR ([gate](.github/workflows/esphome-config.yml)); nightly, the hardware gate asserts three boards running it (Heltec WiFi LoRa 32 V4 and V2, TTGO T3) are on the bench, logging and seen by ChirpStack within the hour ([gate](docs/workbench.md#the-gate)). Validated end-to-end on the V4 (SX1262 + external PA): joined, uplinked and decoded against live ChirpStack. Payloads over 11 bytes need the data rate asserted per uplink — US915 DR0 caps at 11 and ADR drives to DR0 on a strong link |
123
123
  | **Works (hardware-validated)** | Remote workbench | flash a board on a bench slot, and run the whole LoRaWAN bring-up (flash → register → key push → verify) headlessly | wire format verified against a reference bench; flashing and LoRaWAN bring-up exercised repeatedly on a live Pi bench with an ESP32-S3. Transport tests in `tests/test_workbench.py` use wire-level fakes; no automated live-bench gate |
124
124
  | **Verified** | Tasmota target | emit a Tasmota device template (`/tasmota/template`) mapping solved pins to Tasmota GPIO function ids | function ids + per-chip layouts sourced from `tasmota_template.h`; unit tests pin the Sonoff S31 template convention for the smart-plug example |
125
125
  | **Verified** | WLED target | emit WLED's `cfg.json` (`/wled/cfg`): LED outputs, buttons, the IR receiver, the relay and the bus pins, the runtime tier a stock release image reads at boot; sensors are usermods, compiled into the pinned release through a generated `platformio_override.ini` (`/wled/compile`, streamed) with their settings in `cfg.json`'s `um` section | keys and type codes sourced from `wled00/const.h` and `cfg.cpp`; goldens pin four examples in `tests/test_wled.py`; the usermod example builds against WLED v0.15.2 on every PR that touches the target and nightly ([gate](.github/workflows/wled-firmware.yml)) |
@@ -27,6 +27,7 @@ dependencies = [
27
27
  "slowapi>=0.1.9",
28
28
  "mcp>=2.0,<3",
29
29
  "websockets>=12",
30
+ "pillow>=10",
30
31
  ]
31
32
 
32
33
  [project.optional-dependencies]
@@ -1,20 +1,20 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from wirestudio.library import default_library
4
+ from wirestudio.migrate import migrate
4
5
  from wirestudio.model import Design
5
6
  from wirestudio.targets.lorawan import codec
6
7
 
7
8
 
8
9
  def _design(board_id: str, **lorawan) -> Design:
9
- return Design(
10
- schema_version="0.1",
11
- id="d",
12
- name="D",
13
- target="lorawan",
14
- lorawan=lorawan,
15
- board={"library_id": board_id, "mcu": "esp32"},
16
- power={"supply": "usb", "rail_voltage_v": 3.3},
17
- )
10
+ """`lorawan` may carry the retired gps / dht22 / oled blocks, which the
11
+ migrator turns into components."""
12
+ return Design.model_validate(migrate({
13
+ "schema_version": "0.3", "id": "d", "name": "D", "target": "lorawan",
14
+ "lorawan": lorawan,
15
+ "board": {"library_id": board_id, "mcu": "esp32"},
16
+ "power": {"supply": "usb", "rail_voltage_v": 3.3},
17
+ }).document)
18
18
 
19
19
 
20
20
  def test_builtin_only_layout():
@@ -0,0 +1,191 @@
1
+ """Community designs: browse and import go through the catalogue's index
2
+ only, and share refuses what the catalogue's CI would."""
3
+ from __future__ import annotations
4
+
5
+ import io
6
+ import json
7
+ import zipfile
8
+ from pathlib import Path
9
+
10
+ import pytest
11
+ import yaml
12
+ from fastapi.testclient import TestClient
13
+
14
+ from wirestudio.api.app import create_app
15
+
16
+ REPO = "moellere/wirestudio-designs"
17
+ RAW = f"https://raw.githubusercontent.com/{REPO}"
18
+ EXAMPLE = json.loads((Path(__file__).resolve().parent.parent / "wirestudio" / "examples"
19
+ / "garage-motion.json").read_text())
20
+
21
+
22
+ def _entry(slug, **over):
23
+ return {"slug": slug, "name": "Porch motion", "author": "octocat", "license": "CC-BY-4.0",
24
+ "design_url": f"{RAW}/main/designs/{slug}/design.json", **over}
25
+
26
+
27
+ @pytest.fixture
28
+ def studio(monkeypatch):
29
+ monkeypatch.delenv("WIRESTUDIO_DESIGNS_REPO", raising=False)
30
+ files: dict[str, bytes] = {}
31
+ fetched: list[str] = []
32
+
33
+ async def fetch(url):
34
+ fetched.append(url)
35
+ if url not in files:
36
+ raise RuntimeError(f"404 {url}")
37
+ return files[url]
38
+
39
+ def publish(*entries, designs=None):
40
+ files[f"{RAW}/index/index.json"] = json.dumps(
41
+ {"version": 1, "generated": "2026-10-23T00:00:00+00:00", "designs": list(entries)}).encode()
42
+ for slug, doc in (designs or {}).items():
43
+ files[f"{RAW}/main/designs/{slug}/design.json"] = json.dumps(doc).encode()
44
+
45
+ app = TestClient(create_app(community_fetch=fetch))
46
+ return app, publish, fetched
47
+
48
+
49
+ def test_status_names_the_catalogue(studio):
50
+ app, _, _ = studio
51
+ s = app.get("/community/status").json()
52
+ assert s["enabled"] and s["repo"] == REPO
53
+ assert s["upload_url"] == f"https://github.com/{REPO}/upload/main/designs"
54
+
55
+
56
+ def test_empty_repo_setting_turns_it_off(studio, monkeypatch):
57
+ app, _, _ = studio
58
+ monkeypatch.setenv("WIRESTUDIO_DESIGNS_REPO", "")
59
+ assert app.get("/community/status").json() == {"enabled": False}
60
+ assert app.get("/community/designs").status_code == 503
61
+
62
+
63
+ def test_browse_lists_the_index_and_caches_it(studio):
64
+ app, publish, fetched = studio
65
+ publish(_entry("porch-motion"))
66
+ assert [d["slug"] for d in app.get("/community/designs").json()["designs"]] == ["porch-motion"]
67
+ app.get("/community/designs")
68
+ assert fetched.count(f"{RAW}/index/index.json") == 1
69
+
70
+
71
+ def test_an_unreachable_index_is_a_502(studio):
72
+ app, _, _ = studio
73
+ r = app.get("/community/designs")
74
+ assert r.status_code == 502 and "index unavailable" in r.json()["detail"]
75
+
76
+
77
+ def test_import_stamps_the_origin(studio):
78
+ app, publish, _ = studio
79
+ publish(_entry("porch-motion"), designs={"porch-motion": {**EXAMPLE, "id": "porch-motion"}})
80
+ body = app.get("/community/designs/porch-motion").json()
81
+ assert body["design"]["origin"] == {"catalogue": REPO, "slug": "porch-motion",
82
+ "author": "octocat", "license": "CC-BY-4.0"}
83
+ assert app.post("/design/validate", json=body["design"]).status_code == 200
84
+
85
+
86
+ def test_import_only_follows_urls_inside_the_catalogue(studio):
87
+ app, publish, fetched = studio
88
+ publish(_entry("evil", design_url="http://169.254.169.254/latest/meta-data"))
89
+ assert app.get("/community/designs/evil").status_code == 502
90
+ assert not any("169.254" in u for u in fetched)
91
+
92
+
93
+ def test_import_of_an_unknown_slug_is_a_404(studio):
94
+ app, publish, _ = studio
95
+ publish()
96
+ assert app.get("/community/designs/nope").status_code == 404
97
+
98
+
99
+ def _share(app, **over):
100
+ body = {"design": {**EXAMPLE, "id": "Porch Motion v2"}, "author": "@octocat", "tags": ["motion"], **over}
101
+ return app.post("/community/share", json=body)
102
+
103
+
104
+ def test_share_packs_the_folder_the_catalogue_expects(studio):
105
+ app, _, _ = studio
106
+ r = _share(app, notes="Built on a DevKitC.")
107
+ assert r.status_code == 200 and r.headers["content-type"] == "application/zip"
108
+ z = zipfile.ZipFile(io.BytesIO(r.content))
109
+ assert sorted(z.namelist()) == ["porch-motion-v2/README.md", "porch-motion-v2/design.json",
110
+ "porch-motion-v2/meta.yaml"]
111
+ assert json.loads(z.read("porch-motion-v2/design.json"))["id"] == "porch-motion-v2"
112
+ assert yaml.safe_load(z.read("porch-motion-v2/meta.yaml")) == {"author": "octocat", "tags": ["motion"]}
113
+
114
+
115
+ def test_share_credits_the_design_it_was_imported_from(studio):
116
+ app, _, _ = studio
117
+ origin = {"catalogue": REPO, "slug": "porch-motion", "author": "someone", "license": "CC-BY-4.0"}
118
+ r = _share(app, design={**EXAMPLE, "id": "porch-motion-v2", "origin": origin})
119
+ z = zipfile.ZipFile(io.BytesIO(r.content))
120
+ assert yaml.safe_load(z.read("porch-motion-v2/meta.yaml"))["derived_from"] == "porch-motion"
121
+ assert "origin" not in json.loads(z.read("porch-motion-v2/design.json"))
122
+
123
+
124
+ def test_resharing_your_own_design_does_not_derive_from_itself(studio):
125
+ app, _, _ = studio
126
+ origin = {"catalogue": REPO, "slug": "porch-motion", "author": "octocat", "license": "CC-BY-4.0"}
127
+ r = _share(app, design={**EXAMPLE, "id": "porch-motion", "origin": origin})
128
+ z = zipfile.ZipFile(io.BytesIO(r.content))
129
+ assert "derived_from" not in yaml.safe_load(z.read("porch-motion/meta.yaml"))
130
+
131
+
132
+ def test_share_refuses_a_literal_credential(studio):
133
+ app, _, _ = studio
134
+ design = json.loads(json.dumps(EXAMPLE))
135
+ design["fleet"]["secrets_ref"]["wifi_password"] = "hunter22"
136
+ r = _share(app, design=design)
137
+ assert r.status_code == 422 and "fleet.secrets_ref.wifi_password" in r.json()["detail"]
138
+
139
+
140
+ @pytest.mark.parametrize("over, match", [
141
+ ({"author": "not a handle"}, "GitHub handle"),
142
+ ({"tags": ["Has Space"]}, "tags"),
143
+ ({"slug": "Bad_Slug"}, "not a usable name"),
144
+ ])
145
+ def test_share_refuses_what_the_catalogue_would(studio, over, match):
146
+ app, _, _ = studio
147
+ r = _share(app, **over)
148
+ assert r.status_code == 422 and match in r.json()["detail"]
149
+
150
+
151
+ def _jpeg(size=(3000, 2000), exif=True, fmt="JPEG") -> str:
152
+ import base64
153
+
154
+ from PIL import Image
155
+
156
+ img = Image.new("RGB", size, (200, 80, 40))
157
+ kwargs = {}
158
+ if exif:
159
+ e = Image.Exif()
160
+ e[0x010F] = "PhoneMaker"
161
+ e[0x0112] = 6 # Orientation: rotate 90 CW on display
162
+ kwargs["exif"] = e
163
+ buf = io.BytesIO()
164
+ img.save(buf, fmt, **kwargs)
165
+ return base64.b64encode(buf.getvalue()).decode()
166
+
167
+
168
+ def test_share_re_encodes_photos_without_metadata(studio):
169
+ from PIL import Image
170
+
171
+ app, _, _ = studio
172
+ r = _share(app, photos=[{"name": "IMG_0042.JPG", "data": _jpeg()},
173
+ {"name": "IMG_0042.JPG", "data": _jpeg(fmt="PNG", exif=False)}])
174
+ assert r.status_code == 200, r.text
175
+ z = zipfile.ZipFile(io.BytesIO(r.content))
176
+ names = sorted(n for n in z.namelist() if "/photos/" in n)
177
+ assert names == ["porch-motion-v2/photos/img-0042-2.jpg", "porch-motion-v2/photos/img-0042.jpg"]
178
+ with Image.open(io.BytesIO(z.read("porch-motion-v2/photos/img-0042.jpg"))) as img:
179
+ assert img.format == "JPEG" and not img.getexif()
180
+ assert img.size == (1365, 2048), "the EXIF rotation is applied before EXIF is dropped"
181
+ assert all(z.getinfo(n).file_size <= 1024 * 1024 for n in names)
182
+
183
+
184
+ def test_share_refuses_what_is_not_a_photo(studio):
185
+ import base64
186
+
187
+ app, _, _ = studio
188
+ r = _share(app, photos=[{"name": "notes.txt", "data": base64.b64encode(b"hello").decode()}])
189
+ assert r.status_code == 422 and "not a readable image" in r.json()["detail"]
190
+ r = _share(app, photos=[{"name": f"p{i}.jpg", "data": _jpeg((10, 10))} for i in range(5)])
191
+ assert r.status_code == 422 and "at most 4" in r.json()["detail"]
@@ -5,28 +5,28 @@ from pathlib import Path
5
5
  import pytest
6
6
 
7
7
  from wirestudio.library import default_library
8
+ from wirestudio.migrate import migrate
8
9
  from wirestudio.model import Design
9
10
  from wirestudio.targets.lorawan.firmware_gen import generate_firmware, write_firmware
10
11
 
11
12
 
12
13
  def _design(board_id: str, **lorawan) -> Design:
13
- kw = {}
14
+ """A 0.3 design; `lorawan` may carry the retired gps / dht22 / oled
15
+ blocks, which the migrator turns into components."""
16
+ raw = {
17
+ "schema_version": "0.3", "id": "dev1", "name": "Dev 1",
18
+ "board": {"library_id": board_id, "mcu": "esp32"},
19
+ "power": {"supply": "usb", "rail_voltage_v": 3.3},
20
+ }
14
21
  if lorawan:
15
- kw["target"] = "lorawan"
16
- kw["lorawan"] = lorawan
17
- return Design(
18
- schema_version="0.1",
19
- id="dev1",
20
- name="Dev 1",
21
- board={"library_id": board_id, "mcu": "esp32"},
22
- power={"supply": "usb", "rail_voltage_v": 3.3},
23
- **kw,
24
- )
22
+ raw["target"] = "lorawan"
23
+ raw["lorawan"] = lorawan
24
+ return Design.model_validate(migrate(raw).document)
25
25
 
26
26
 
27
27
  def _design_with(*library_ids: str) -> Design:
28
28
  return Design.model_validate({
29
- "schema_version": "0.3", "id": "dev1", "name": "Dev 1", "target": "lorawan", "lorawan": {},
29
+ "schema_version": "1.0", "id": "dev1", "name": "Dev 1", "target": "lorawan", "lorawan": {},
30
30
  "board": {"library_id": "ttgo-lora32-v2", "mcu": "esp32"},
31
31
  "power": {"supply": "usb", "rail_voltage_v": 3.3},
32
32
  "components": [{"id": cid, "library_id": cid, "label": cid, "params": {}} for cid in library_ids],
@@ -180,7 +180,7 @@ def test_lorawan_matches_golden(lib):
180
180
 
181
181
 
182
182
  def test_synthesized_dht_emits_its_configured_pin(lib):
183
- """A DHT requested via `lorawan.dht22` has no wiring, so the render
183
+ """A DHT from a retired `lorawan.dht22` block has no wiring, so the render
184
184
  context sets `pin` to None. Jinja's `default()` only fires on *undefined*,
185
185
  so the old template fell through to a literal "None" in the C++."""
186
186
  src = generate_firmware(
@@ -258,13 +258,25 @@ def test_an_off_bench_device_without_a_dev_eui_is_rejected(tmp_path):
258
258
 
259
259
 
260
260
  class FlashBench(FakeBench):
261
- """Records writes; after one, the slot prints what `boot_lines` scripts
262
- for the framework whose image was written last."""
261
+ """Records writes. Like the real bench, the boot that follows a write
262
+ goes unrecorded; a reset afterwards prints what `boot_lines` scripts
263
+ for the framework written last."""
263
264
 
264
265
  def __init__(self, slots, boot_lines, fail_write=False):
265
266
  super().__init__(slots)
266
267
  self.boot_lines, self.fail_write = boot_lines, fail_write
267
268
  self.writes: list[dict] = []
269
+ self.resets: list[str] = []
270
+ self._written: dict[str, str] = {}
271
+
272
+ async def slot(self, label):
273
+ return next(s for s in self._slots if s.label == label)
274
+
275
+ async def reset(self, slot):
276
+ self.resets.append(slot)
277
+ self._lines[slot] = [{"ts": time.time(), "text": t}
278
+ for t in self.boot_lines.get(self._written.get(slot, ""), [])]
279
+ return {"ok": True}
268
280
 
269
281
  async def flash(self, slot, images, chip="esp32", erase=False, baud=921600):
270
282
  from wirestudio.workbench import WorkbenchUnavailable
@@ -272,8 +284,8 @@ class FlashBench(FakeBench):
272
284
  if self.fail_write:
273
285
  raise WorkbenchUnavailable("flash failed: A fatal error occurred: No serial data received.")
274
286
  self.writes.append({"slot": slot, "images": images, "chip": chip, "erase": erase})
275
- self._lines[slot] = [{"ts": time.time(), "text": t}
276
- for t in self.boot_lines.get(images[0][1].decode(), [])]
287
+ self._written[slot] = images[0][1].decode()
288
+ self._lines[slot] = []
277
289
  yield {"type": "log", "data": "Hash of data verified."}
278
290
  yield {"type": "done", "ok": True, "slot": slot, "returncode": 0}
279
291
 
@@ -298,7 +310,6 @@ def fake_images(monkeypatch):
298
310
 
299
311
 
300
312
  FLASH_CONFIG = {
301
- "boot_capture_s": 0,
302
313
  "devices": [{"slot": "SLOT2", "name": "spare S3", "board": "esp32-s3-devkitc-1",
303
314
  "flash": ["esphome", "micropython", "circuitpython"]}],
304
315
  }
@@ -307,7 +318,7 @@ BOOTS = {
307
318
  "esphome": ["[I][app:117]: setup() finished successfully!"],
308
319
  "micropython": ["MicroPython v1.26.1 on 2025-09-11; Generic ESP32S3 module with ESP32S3"],
309
320
  "circuitpython": ["Adafruit CircuitPython 10.3.1 on 2026-09-14; Espressif ESP32 DevKitc V4 WROOM-32E with ESP32"],
310
- "meshtastic": ["INFO | ??:??:?? 0 Booting Meshtastic"],
321
+ "meshtastic": ["INFO | ??:??:?? 0 Booted, wake cause 0 (boot count 1), reset_reason=reset"],
311
322
  }
312
323
 
313
324
 
@@ -331,18 +342,22 @@ async def test_a_flash_device_skips_the_serial_stage(fake_images):
331
342
  assert not [r for r in report.rows if r[0] == "serial"]
332
343
 
333
344
 
334
- async def test_a_framework_without_a_marker_fails_with_what_it_printed(fake_images):
335
- """Passing it would claim a verdict nobody established; the captured
336
- output is what establishes one."""
345
+ async def test_boot_is_read_from_a_reset_after_each_write(fake_images):
346
+ """covbench, 2026-10-23: the boot esptool triggers is never recorded,
347
+ so without a reset ESPHome failed on a healthy board."""
337
348
  report = gate.Report()
338
- bench = FlashBench([FakeSlot("SLOT3", chip="esp32s3")], BOOTS)
339
- cfg = {"boot_capture_s": 0, "devices": [
340
- {"slot": "SLOT3", "board": "heltec-wifi-lora32-v3", "flash": ["meshtastic"]}]}
341
- await gate.run(cfg, report, client=bench)
349
+ bench = FlashBench([FakeSlot("SLOT2", chip="esp32s3")], BOOTS)
350
+ await gate.run(FLASH_CONFIG, report, client=bench)
351
+ assert bench.resets == ["SLOT2"] * 3
342
352
 
343
- row = next(r for r in report.rows if r[:2] == ("boot", "SLOT3/meshtastic"))
344
- assert row[2] is False
345
- assert "Booting Meshtastic" in row[3]
353
+
354
+ async def test_meshtastic_boots_on_its_marker(fake_images):
355
+ report = gate.Report()
356
+ bench = FlashBench([FakeSlot("SLOT3", chip="esp32")], BOOTS)
357
+ cfg = {"devices": [
358
+ {"slot": "SLOT3", "board": "ttgo-lora32-v2", "flash": ["meshtastic"]}]}
359
+ await gate.run(cfg, report, client=bench)
360
+ assert ("boot", "SLOT3/meshtastic", True) in [r[:3] for r in report.rows]
346
361
 
347
362
 
348
363
  async def test_circuitpython_boots_on_its_banner(fake_images):
@@ -57,15 +57,14 @@ def client() -> TestClient:
57
57
 
58
58
 
59
59
  def _design(board_id: str, **lorawan) -> dict:
60
- return Design(
61
- schema_version="0.1",
62
- id="d",
63
- name="D",
64
- target="lorawan",
65
- lorawan=lorawan,
66
- board={"library_id": board_id, "mcu": "esp32"},
67
- power={"supply": "usb", "rail_voltage_v": 3.3},
68
- ).model_dump(mode="json", exclude_none=True)
60
+ """A 0.3 request body; `lorawan` may carry the retired gps / dht22 / oled
61
+ blocks, which the endpoints migrate into components."""
62
+ return {
63
+ "schema_version": "0.3", "id": "d", "name": "D", "target": "lorawan",
64
+ "lorawan": lorawan,
65
+ "board": {"library_id": board_id, "mcu": "esp32"},
66
+ "power": {"supply": "usb", "rail_voltage_v": 3.3},
67
+ }
69
68
 
70
69
 
71
70
  def _seed_cache(tmp_path, design: dict, *, bin_bytes=b"FAKEBIN", log="cached log") -> str:
@@ -305,7 +304,7 @@ def test_provision_esphome_uses_esphome_profile_naming(client, monkeypatch):
305
304
  assert fake.provisioned["device_profile_name"] == "wirestudio-esphome-us915-sub2"
306
305
  # Codec is None on the new path -- the decoder is generated from
307
306
  # design.lorawan.payload by a follow-up endpoint, not by the standalone
308
- # codec.py (which knows only gps/dht22/oled).
307
+ # codec.py (which packs the standalone path's components).
309
308
  assert fake.provisioned["codec"] is None
310
309
 
311
310
 
@@ -1,17 +1,18 @@
1
- """`lorawan.gps` / `dht22` / `oled` are sugar for a component, a bus and a
2
- few connections. Lowering them is what every reader of a standalone design
3
- does first, so a design written either way produces the same firmware,
4
- payload and profile name."""
1
+ """`lorawan.gps` / `dht22` / `oled` were sugar for a component, a bus and a
2
+ few connections. The 1.0 model drops them; the migrator rewrites a document
3
+ that still carries them into the components they stood for, so a design
4
+ written either way produces the same firmware, payload and profile name."""
5
5
  from __future__ import annotations
6
6
 
7
7
  import pytest
8
+ from pydantic import ValidationError
8
9
 
9
10
  from wirestudio.library import default_library
11
+ from wirestudio.migrate import CURRENT_SCHEMA_VERSION, _lower_lorawan_sugar, migrate
10
12
  from wirestudio.model import Design
11
13
  from wirestudio.targets import get_target
12
14
  from wirestudio.targets.lorawan.codec import fields_for, generate_codec, profile_name
13
15
  from wirestudio.targets.lorawan.firmware_gen import generate_firmware
14
- from wirestudio.targets.lorawan.lower import lower_lorawan_sugar, sugar_in_use
15
16
 
16
17
 
17
18
  @pytest.fixture
@@ -19,14 +20,18 @@ def lib():
19
20
  return default_library()
20
21
 
21
22
 
22
- def _design(board_id: str, *, lorawan: dict | None = None, **extra) -> Design:
23
- return Design.model_validate({
24
- "schema_version": "0.1", "id": "d", "name": "D", "target": "lorawan",
23
+ def _raw(board_id: str, *, lorawan: dict | None = None, version: str = "0.3", **extra) -> dict:
24
+ return {
25
+ "schema_version": version, "id": "d", "name": "D", "target": "lorawan",
25
26
  "lorawan": lorawan if lorawan is not None else {},
26
27
  "board": {"library_id": board_id, "mcu": "esp32"},
27
28
  "power": {"supply": "usb", "rail_voltage_v": 3.3},
28
29
  **extra,
29
- })
30
+ }
31
+
32
+
33
+ def _design(board_id: str, **kw) -> Design:
34
+ return Design.model_validate(migrate(_raw(board_id, **kw)).document)
30
35
 
31
36
 
32
37
  SUGAR = {"gps": {"rx_pin": "GPIO23", "tx_pin": "GPIO17"}, "dht22": {"pin": "GPIO13"}, "oled": {}}
@@ -59,12 +64,13 @@ EXPLICIT = {
59
64
  }
60
65
 
61
66
 
62
- def test_lowering_is_the_explicit_design(lib):
63
- lowered = lower_lorawan_sugar(_design("heltec-wifi-lora32-v3", lorawan=SUGAR), lib)
64
- explicit = _design("heltec-wifi-lora32-v3", **EXPLICIT)
65
- dump = lambda d: d.model_dump(mode="json", exclude={"lorawan"}) # noqa: E731
66
- assert dump(lowered) == dump(explicit)
67
- assert sugar_in_use(lowered) == []
67
+ def test_the_blocks_become_the_explicit_design():
68
+ result = migrate(_raw("heltec-wifi-lora32-v3", lorawan=SUGAR))
69
+ assert result.applied == ["retire_lorawan_sugar"]
70
+ assert result.to_version == CURRENT_SCHEMA_VERSION == "1.0"
71
+ doc = result.document
72
+ assert {k: doc[k] for k in ("components", "buses", "connections")} == EXPLICIT
73
+ assert doc["lorawan"] == {}
68
74
 
69
75
 
70
76
  def test_sugar_and_explicit_designs_generate_the_same_artifacts(lib):
@@ -78,12 +84,22 @@ def test_sugar_and_explicit_designs_generate_the_same_artifacts(lib):
78
84
  assert {"lat", "lon", "temp_c", "humidity", "batt_mv"} <= set(names)
79
85
 
80
86
 
81
- def test_lowering_leaves_the_input_alone_and_is_idempotent(lib):
82
- design = _design("heltec-wifi-lora32-v3", lorawan=SUGAR)
83
- lowered = lower_lorawan_sugar(design, lib)
84
- assert design.lorawan.gps is not None and design.components == []
85
- assert lower_lorawan_sugar(lowered, lib) is lowered
86
- assert lower_lorawan_sugar(_design("heltec-wifi-lora32-v3"), lib) is not lowered
87
+ def test_migration_leaves_the_input_alone_and_is_idempotent(lib):
88
+ raw = _raw("heltec-wifi-lora32-v3", lorawan=SUGAR)
89
+ doc = migrate(raw).document
90
+ assert raw["lorawan"]["gps"] and "components" not in raw
91
+ assert _lower_lorawan_sugar(doc, lib) is doc
92
+ assert migrate(doc).document is doc
93
+
94
+
95
+ def test_a_0_3_design_without_the_blocks_only_changes_its_version():
96
+ raw = _raw("heltec-wifi-lora32-v3", lorawan={"region": "EU868"})
97
+ assert migrate(raw).document == {**raw, "schema_version": "1.0"}
98
+
99
+
100
+ def test_the_1_0_model_refuses_the_blocks():
101
+ with pytest.raises(ValidationError, match="gps"):
102
+ Design.model_validate(_raw("heltec-wifi-lora32-v3", lorawan=SUGAR, version="1.0"))
87
103
 
88
104
 
89
105
  def test_a_block_whose_component_is_already_placed_is_dropped(lib):
@@ -93,54 +109,32 @@ def test_a_block_whose_component_is_already_placed_is_dropped(lib):
93
109
  connections=[{"component_id": "env", "pin_role": "DATA",
94
110
  "target": {"kind": "gpio", "pin": "GPIO4"}}],
95
111
  )
96
- lowered = lower_lorawan_sugar(design, lib)
97
- assert [c.id for c in lowered.components] == ["env"]
98
- assert lowered.lorawan.dht22 is None
112
+ assert [c.id for c in design.components] == ["env"]
99
113
  assert "DHT dht(4, DHT22);" in generate_firmware(design, lib)["src/main.cpp"]
100
114
 
101
115
 
102
- def test_ids_do_not_collide_with_what_the_design_has(lib):
116
+ def test_ids_do_not_collide_with_what_the_design_has():
103
117
  design = _design(
104
118
  "heltec-wifi-lora32-v3", lorawan={"gps": {"rx_pin": "GPIO23", "tx_pin": "GPIO17"}},
105
119
  components=[{"id": "gps", "library_id": "gpio_input", "label": "Button", "params": {}}],
106
120
  buses=[{"id": "gps_uart", "type": "uart", "rx": "GPIO3", "tx": "GPIO1"}],
107
121
  )
108
- lowered = lower_lorawan_sugar(design, lib)
109
- gps = next(c for c in lowered.components if c.library_id == "uart_gps")
122
+ gps = next(c for c in design.components if c.library_id == "uart_gps")
110
123
  assert gps.id == "gps2"
111
- assert {b.id for b in lowered.buses} == {"gps_uart", "gps_uart2"}
124
+ assert {b.id for b in design.buses} == {"gps_uart", "gps_uart2"}
112
125
 
113
126
 
114
127
  def test_onboard_gps_stays_synthesized_without_sugar(lib):
115
128
  """The T-Beam's GPS comes from the board file, not from a block; the
116
129
  golden firmware for it does not move."""
117
130
  design = _design("ttgo-t-beam")
118
- assert lower_lorawan_sugar(design, lib) is design
131
+ assert design.components == []
119
132
  assert "GPSSerial.begin(9600, SERIAL_8N1, 34, 12)" in generate_firmware(design, lib)["src/main.cpp"]
120
133
 
121
134
 
122
- def test_oled_disabled_is_no_oled(lib):
123
- design = _design("heltec-wifi-lora32-v3", lorawan={"oled": {"enabled": False}})
124
- assert lower_lorawan_sugar(design, lib).components == []
125
-
135
+ def test_oled_disabled_is_no_oled():
136
+ assert _design("heltec-wifi-lora32-v3", lorawan={"oled": {"enabled": False}}).components == []
126
137
 
127
- def test_validate_names_each_lowered_block(lib):
128
- warnings = get_target("lorawan").validate(_design("heltec-wifi-lora32-v3", lorawan=SUGAR), lib)
129
- lowered = [w for w in warnings if w.code == "lorawan_sugar_lowered"]
130
- assert [w.level for w in lowered] == ["info"] * 3
131
- assert all(f"lorawan.{f}" in w.text for f, w in zip(("gps", "dht22", "oled"), lowered))
132
- assert get_target("lorawan").validate(_design("heltec-wifi-lora32-v3", **EXPLICIT), lib) == []
133
138
 
134
-
135
- def test_console_uart_check_reads_the_bus_either_way(lib):
136
- on_console = {"gps": {"rx_pin": "GPIO3", "tx_pin": "GPIO1"}}
137
- codes = {w.code for w in get_target("lorawan").validate(
138
- _design("heltec-wifi-lora32-v2", lorawan=on_console), lib)}
139
- assert "lorawan_gps_on_console_uart" in codes
140
- explicit = _design(
141
- "heltec-wifi-lora32-v2",
142
- components=[{"id": "gnss", "library_id": "uart_gps", "label": "GNSS", "params": {}}],
143
- buses=[{"id": "u", "type": "uart", "rx": "GPIO3", "tx": "GPIO1"}],
144
- connections=[{"component_id": "gnss", "pin_role": "TX", "target": {"kind": "bus", "bus_id": "u"}}],
145
- )
146
- assert "lorawan_gps_on_console_uart" in {w.code for w in get_target("lorawan").validate(explicit, lib)}
139
+ def test_the_migrated_design_validates_clean(lib):
140
+ assert get_target("lorawan").validate(_design("heltec-wifi-lora32-v3", lorawan=SUGAR), lib) == []
@@ -20,7 +20,7 @@ pytestmark = pytest.mark.anyio
20
20
 
21
21
  TOKEN = "ci-token"
22
22
  DESIGN = {
23
- "schema_version": "0.3",
23
+ "schema_version": "1.0",
24
24
  "id": "ci-bench",
25
25
  "name": "CI bench",
26
26
  "board": {"library_id": "esp32-devkitc-v4", "mcu": "esp32", "framework": "arduino"},