wirestudio 0.43.0__tar.gz → 0.45.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {wirestudio-0.43.0/wirestudio.egg-info → wirestudio-0.45.0}/PKG-INFO +2 -1
- {wirestudio-0.43.0 → wirestudio-0.45.0}/pyproject.toml +1 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_api_route.py +6 -0
- wirestudio-0.45.0/tests/test_community.py +248 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_compatibility.py +83 -19
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_kicad.py +23 -12
- wirestudio-0.45.0/tests/test_kicad_drc.py +112 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_kicad_pcb.py +35 -8
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_kicad_route.py +73 -1
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_migrate.py +50 -0
- wirestudio-0.45.0/tests/test_rf_ook.py +100 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/__init__.py +1 -1
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/agent/tools.py +66 -7
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/api/app.py +41 -2
- wirestudio-0.45.0/wirestudio/api/community.py +305 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/csp/compatibility.py +37 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/generate/yaml_gen.py +35 -4
- wirestudio-0.45.0/wirestudio/kicad/drc.py +145 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/kicad/fab.py +3 -3
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/kicad/generator.py +27 -14
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/kicad/netlist.py +58 -13
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/kicad/pcb.py +29 -1
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/kicad/pcbnew_bridge.py +5 -1
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/kicad/route.py +138 -17
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/__init__.py +6 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/ttgo-lora32-v2.yaml +8 -2
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/check.py +5 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/apa102.yaml +2 -2
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/bh1750.yaml +1 -1
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/esp32_rmt_led_strip.yaml +1 -1
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/hc-sr501.yaml +1 -1
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/hlw8012.yaml +3 -3
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/ili9xxx.yaml +3 -3
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/ld2410.yaml +1 -1
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/max98357a.yaml +1 -1
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/mcp23008.yaml +1 -1
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/mcp23017.yaml +1 -1
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/mcp23s17.yaml +1 -1
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/pca9685.yaml +1 -1
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/pcf8574.yaml +1 -1
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/pmsx003.yaml +1 -1
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/rc522.yaml +1 -1
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/rcwl-0516.yaml +1 -1
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/remote_receiver.yaml +28 -5
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/servo.yaml +1 -1
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/sn74hc595.yaml +4 -4
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/ssd1306.yaml +1 -1
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/st7789.yaml +3 -3
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/sx127x.yaml +24 -3
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/tm1637.yaml +2 -2
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/vl53l0x.yaml +2 -2
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/wiegand.yaml +2 -2
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/ws2812b.yaml +1 -1
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/xpt2046.yaml +1 -1
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/mcp/server.py +12 -3
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/migrate.py +59 -2
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/model.py +11 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/schema/design.schema.json +13 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0/wirestudio.egg-info}/PKG-INFO +2 -1
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio.egg-info/SOURCES.txt +5 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio.egg-info/requires.txt +1 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/LICENSE +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/LICENSES.md +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/README.md +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/setup.cfg +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_active_design.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_agent.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_agent_eval.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_agent_stream.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_api.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_api_auth.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_ascii_gen.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_build_backend.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_check_upgrade.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_chirpstack.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_circuitpython.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_codec.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_compile.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_component_corpus.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_connectors.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_coverage_baseline.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_design_events.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_designs.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_designs_seed.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_display_intent.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_electrical.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_enclosure.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_enclosure_gate.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_enclosure_search.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_errors.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_esphome_dashboard.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_esphome_support.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_firmware_gen.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_fleet.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_footprints.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_framework_baseline.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_hardware_gate.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_intent.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_inventory.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_jlcpcb.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_kicad_erc.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_kicad_fab.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_kicad_import.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_kicad_render.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_kicad_sch.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_library_check.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_library_hbridge.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_library_rtttl.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_lorawan.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_lorawan_api.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_lorawan_sugar_migration.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_mcp_auth.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_mcp_client.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_mcp_hardware.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_mcp_jobs.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_mcp_resources.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_mcp_server.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_meshtastic.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_micropython.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_micropython_registers.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_modules.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_pin_protocols.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_pin_solver.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_printwarden.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_python_blocks.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_recommend.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_register_code.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_schema.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_schema_doc.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_schematics_gate.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_security_cors.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_seed_onboard.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_serve.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_sqlite_store.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_sqlite_stores.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_strict.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_targets.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_tasmota.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_tbeam_lorawan_example.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_tools_add_bus.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_tools_add_component.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_tools_solve_pins.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_uart_frames.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_validate.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_wled.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_workbench.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_workbench_boot.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_workbench_images.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_workbench_provision.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_workbench_serial.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_ws2812_method.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/tests/test_yaml_gen.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/agent/__init__.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/agent/agent.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/agent/eval.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/agent/eval_tasks.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/agent/session.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/api/__init__.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/api/__main__.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/api/auth.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/api/circuitpython.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/api/design_input.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/api/meshtastic.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/api/micropython.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/api/schemas.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/api/serve.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/api/workbench.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/csp/__init__.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/csp/pin_solver.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/designs/__init__.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/designs/active.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/designs/events.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/designs/seed.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/designs/store.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/enclosure/__init__.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/enclosure/openscad.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/enclosure/search.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/enclosure/yapp/LICENSE +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/enclosure/yapp/YAPPgenerator_v3.scad +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/errors.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/esphome_dashboard/__init__.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/__init__.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/access-panel.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/actuator-harness.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/air-quality-station.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/analog-node.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/atom-echo.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/atom-lite.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/atom-matrix.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/atoms3-lcd.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/atoms3-lite.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/atoms3-onboard.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/atomu-onboard.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/atomu.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/attic-logger.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/awning-control.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/bench-io.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/bl0906-mainmeter.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/blind-stepper.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/bluemotion.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/bluesonoff.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/button-toggles-light.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/c3-supermini.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/c6-supermini.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/co2-display.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/desk-climate.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/desk-matrix.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/distance-sensor.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/encoder-drives-stepper.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/energy-monitor.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/esp32-audio.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/esp32cam-wrover.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/esp32cam.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/flow-meter.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/garage-motion.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/grill-probe.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/guarded-button-light.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/heltec-v4-gnss-tracker.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/keypad.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/kitchen-scale.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/led-display.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/lorawan-battery-uplink.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/motion-turns-on-light.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/motor-position.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/multi-temp.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/nextion-thermostat.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/oled-knob.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/oled.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/output-hub.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/parking-distance.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/presence-media-node.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/presence-rf.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/rc522.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/room-climate.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/rs485-energy.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/s3-devkitc.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/s3-supermini.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/securitypanel.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/smart-plug-v1.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/smart-plug.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/solder-fan.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/spi-io-node.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/subghz-rfid.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/t-beam-lorawan.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/t-beam.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/temp-above-turns-on-fan.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/temp-turns-on-fan.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/tft-touch.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/ttgo-lora32.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/tuya-smart-plug.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/usbc-led-strip.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/vent-damper.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/wasserpir.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/weather-station.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/wemosgps.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/wled-sensor-strip.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/examples/xiao-grove-climate.json +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/fleet/__init__.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/fleet/client.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/generate/__init__.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/generate/__main__.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/generate/ascii_gen.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/generate/circuitpython_gen.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/generate/display_intent.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/generate/micropython_gen.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/intent.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/inventory/__init__.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/inventory/buy.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/inventory/check.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/inventory/csv_io.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/inventory/match.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/inventory/store.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/jlcpcb/__init__.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/jlcpcb/__main__.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/jlcpcb/check.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/jlcpcb/client.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/kicad/__init__.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/kicad/erc.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/kicad/import.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/kicad/importer.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/kicad/render.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/kicad/schematic.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/kicad/symbol_parser.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/esp01_1m.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/esp32-c3-devkitm-1.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/esp32-c3-supermini.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/esp32-c6-supermini.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/esp32-devkitc-v4.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/esp32-s3-devkitc-1.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/esp32-s3-supermini.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/esp32-wrover-cam.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/esp32cam-ai-thinker.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/esp8285-1m.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/heltec-wifi-lora32-v2.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/heltec-wifi-lora32-v3.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/heltec-wifi-lora32-v4.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/m5stack-atom-echo.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/m5stack-atom-matrix.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/m5stack-atom.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/m5stack-atoms3-lite.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/m5stack-atoms3.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/m5stack-atomu.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/nodemcu-32s.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/nodemcu-v2.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/seeed-xiao-esp32c3.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/ttgo-lora32-v1.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/ttgo-t-beam.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/boards/wemos-d1-mini.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/adc.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/ads1115.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/ads1115_channel.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/aht10.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/axp192.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/battery_adc.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/bl0906.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/bme280.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/bme680.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/bmp180.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/bmp280.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/cc1101.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/connector_1x02.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/connector_1x03.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/connector_1x04.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/connector_1x05.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/connector_1x06.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/connector_1x07.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/connector_1x08.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/connector_2x05_idc.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/cse7766.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/ct_clamp.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/dht.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/ds18b20.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/esp32_camera.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/gpio_input.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/gpio_output.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/grove-aht20.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/hbridge.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/hbridge_mosfet.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/hc-sr04.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/htu21d.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/hx711.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/i2s_microphone.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/ina219.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/lcd_pcf8574.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/ld2420.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/led_indicator.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/level_shifter.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/max31855.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/max7219.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/mcp3008.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/mcp3008_channel.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/mhz19.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/modbus.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/mosfet_low_side_driver.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/mpu6050.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/mpu6886.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/nextion.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/npn_low_side_driver.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/pn532.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/potentiometer.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/pulse_counter.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/pwm_fan.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/pzemac.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/rdm6300.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/remote_transmitter.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/rf_bridge.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/rotary_encoder.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/rtttl.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/scd4x.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/sdm_meter.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/sgp4x.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/sht3xd.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/sht4x.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/tm1638.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/tsl2561.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/tuya.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/tuya_sensor.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/tuya_switch.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/uart_gps.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/uln2003.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/usb_c_power_in.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/veml7700.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/components/voltage_divider.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/electrical.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/modules/env-display.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/modules/fan-controller.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/modules/motion-light.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/modules/oled-knob-13.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/library/modules/relay-4ch.yaml +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/mcp/__init__.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/mcp/auth.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/mcp/hardware.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/mcp/jobs.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/printwarden/__init__.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/printwarden/client.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/recommend/__init__.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/recommend/recommender.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/schema/__init__.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/seed.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/sqlite_store.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/__init__.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/base.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/build_backend.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/esphome.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/__init__.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/__main__.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/api.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/build_local.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/chirpstack.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/codec.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/compile.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/firmware_gen.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/templates/main.cpp.j2 +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/templates/platformio.ini.j2 +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/lorawan/workbench_provision.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/tasmota.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/wled.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/targets/wled_build.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/validate.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/workbench/__init__.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/workbench/boot.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/workbench/client.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/workbench/gate.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/workbench/images.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio/workbench/serial.py +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio.egg-info/dependency_links.txt +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio.egg-info/entry_points.txt +0 -0
- {wirestudio-0.43.0 → wirestudio-0.45.0}/wirestudio.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: wirestudio
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.45.0
|
|
4
4
|
Summary: Agent-driven IoT device design tool that produces ESPHome YAML.
|
|
5
5
|
Author: wirestudio contributors
|
|
6
6
|
License: MIT
|
|
@@ -19,6 +19,7 @@ Requires-Dist: httpx>=0.27
|
|
|
19
19
|
Requires-Dist: slowapi>=0.1.9
|
|
20
20
|
Requires-Dist: mcp<3,>=2.0
|
|
21
21
|
Requires-Dist: websockets>=12
|
|
22
|
+
Requires-Dist: pillow>=10
|
|
22
23
|
Provides-Extra: dev
|
|
23
24
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
24
25
|
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
@@ -99,3 +99,9 @@ def test_fab_gerbers_route_unavailable_is_503(client, monkeypatch):
|
|
|
99
99
|
def test_fab_status_reports_route(client):
|
|
100
100
|
body = client.get("/design/fab/status").json()
|
|
101
101
|
assert "route" in body and "route_reason" in body
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_route_bad_params_are_422(client, toolchain):
|
|
105
|
+
for query in ("keepout=1,2,3", "keepout=1,2,3,4,pads", "via_costs=-5", "passes=0"):
|
|
106
|
+
resp = client.post(f"/design/kicad/route?{query}", json=_design())
|
|
107
|
+
assert resp.status_code == 422, query
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""Community designs: browse and import go through the catalogue's index
|
|
2
|
+
only, and share refuses what the catalogue's CI would."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import io
|
|
6
|
+
import json
|
|
7
|
+
import zipfile
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
import yaml
|
|
12
|
+
from fastapi.testclient import TestClient
|
|
13
|
+
|
|
14
|
+
from wirestudio.api.app import create_app
|
|
15
|
+
|
|
16
|
+
REPO = "moellere/wirestudio-designs"
|
|
17
|
+
RAW = f"https://raw.githubusercontent.com/{REPO}"
|
|
18
|
+
EXAMPLE = json.loads((Path(__file__).resolve().parent.parent / "wirestudio" / "examples"
|
|
19
|
+
/ "garage-motion.json").read_text())
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _entry(slug, **over):
|
|
23
|
+
return {"slug": slug, "name": "Porch motion", "author": "octocat", "license": "CC-BY-4.0",
|
|
24
|
+
"design_url": f"{RAW}/main/designs/{slug}/design.json", **over}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.fixture
|
|
28
|
+
def user_library(tmp_path):
|
|
29
|
+
from wirestudio.library import Library, default_library
|
|
30
|
+
|
|
31
|
+
return Library(default_library().root, tmp_path / "user-library")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@pytest.fixture
|
|
35
|
+
def studio(monkeypatch, user_library):
|
|
36
|
+
monkeypatch.delenv("WIRESTUDIO_DESIGNS_REPO", raising=False)
|
|
37
|
+
files: dict[str, bytes] = {}
|
|
38
|
+
fetched: list[str] = []
|
|
39
|
+
|
|
40
|
+
async def fetch(url):
|
|
41
|
+
fetched.append(url)
|
|
42
|
+
if url not in files:
|
|
43
|
+
raise RuntimeError(f"404 {url}")
|
|
44
|
+
return files[url]
|
|
45
|
+
|
|
46
|
+
def publish(*entries, designs=None, components=None):
|
|
47
|
+
files[f"{RAW}/index/index.json"] = json.dumps(
|
|
48
|
+
{"version": 1, "generated": "2026-10-23T00:00:00+00:00", "designs": list(entries)}).encode()
|
|
49
|
+
for slug, doc in (designs or {}).items():
|
|
50
|
+
files[f"{RAW}/main/designs/{slug}/design.json"] = json.dumps(doc).encode()
|
|
51
|
+
for url, text in (components or {}).items():
|
|
52
|
+
files[url] = text.encode()
|
|
53
|
+
|
|
54
|
+
app = TestClient(create_app(library=user_library, community_fetch=fetch))
|
|
55
|
+
return app, publish, fetched
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_status_names_the_catalogue(studio):
|
|
59
|
+
app, _, _ = studio
|
|
60
|
+
s = app.get("/community/status").json()
|
|
61
|
+
assert s["enabled"] and s["repo"] == REPO
|
|
62
|
+
assert s["upload_url"] == f"https://github.com/{REPO}/upload/main/designs"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_empty_repo_setting_turns_it_off(studio, monkeypatch):
|
|
66
|
+
app, _, _ = studio
|
|
67
|
+
monkeypatch.setenv("WIRESTUDIO_DESIGNS_REPO", "")
|
|
68
|
+
assert app.get("/community/status").json() == {"enabled": False}
|
|
69
|
+
assert app.get("/community/designs").status_code == 503
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_browse_lists_the_index_and_caches_it(studio):
|
|
73
|
+
app, publish, fetched = studio
|
|
74
|
+
publish(_entry("porch-motion"))
|
|
75
|
+
assert [d["slug"] for d in app.get("/community/designs").json()["designs"]] == ["porch-motion"]
|
|
76
|
+
app.get("/community/designs")
|
|
77
|
+
assert fetched.count(f"{RAW}/index/index.json") == 1
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_an_unreachable_index_is_a_502(studio):
|
|
81
|
+
app, _, _ = studio
|
|
82
|
+
r = app.get("/community/designs")
|
|
83
|
+
assert r.status_code == 502 and "index unavailable" in r.json()["detail"]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_import_stamps_the_origin(studio):
|
|
87
|
+
app, publish, _ = studio
|
|
88
|
+
publish(_entry("porch-motion"), designs={"porch-motion": {**EXAMPLE, "id": "porch-motion"}})
|
|
89
|
+
body = app.post("/community/designs/porch-motion/import").json()
|
|
90
|
+
assert body["design"]["origin"] == {"catalogue": REPO, "slug": "porch-motion",
|
|
91
|
+
"author": "octocat", "license": "CC-BY-4.0"}
|
|
92
|
+
assert app.post("/design/validate", json=body["design"]).status_code == 200
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_import_only_follows_urls_inside_the_catalogue(studio):
|
|
96
|
+
app, publish, fetched = studio
|
|
97
|
+
publish(_entry("evil", design_url="http://169.254.169.254/latest/meta-data"))
|
|
98
|
+
assert app.post("/community/designs/evil/import").status_code == 502
|
|
99
|
+
assert not any("169.254" in u for u in fetched)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_import_of_an_unknown_slug_is_a_404(studio):
|
|
103
|
+
app, publish, _ = studio
|
|
104
|
+
publish()
|
|
105
|
+
assert app.post("/community/designs/nope/import").status_code == 404
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _share(app, **over):
|
|
109
|
+
body = {"design": {**EXAMPLE, "id": "Porch Motion v2"}, "author": "@octocat", "tags": ["motion"], **over}
|
|
110
|
+
return app.post("/community/share", json=body)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_share_packs_the_folder_the_catalogue_expects(studio):
|
|
114
|
+
app, _, _ = studio
|
|
115
|
+
r = _share(app, notes="Built on a DevKitC.")
|
|
116
|
+
assert r.status_code == 200 and r.headers["content-type"] == "application/zip"
|
|
117
|
+
z = zipfile.ZipFile(io.BytesIO(r.content))
|
|
118
|
+
assert sorted(z.namelist()) == ["porch-motion-v2/README.md", "porch-motion-v2/design.json",
|
|
119
|
+
"porch-motion-v2/meta.yaml"]
|
|
120
|
+
assert json.loads(z.read("porch-motion-v2/design.json"))["id"] == "porch-motion-v2"
|
|
121
|
+
assert yaml.safe_load(z.read("porch-motion-v2/meta.yaml")) == {"author": "octocat", "tags": ["motion"]}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_share_credits_the_design_it_was_imported_from(studio):
|
|
125
|
+
app, _, _ = studio
|
|
126
|
+
origin = {"catalogue": REPO, "slug": "porch-motion", "author": "someone", "license": "CC-BY-4.0"}
|
|
127
|
+
r = _share(app, design={**EXAMPLE, "id": "porch-motion-v2", "origin": origin})
|
|
128
|
+
z = zipfile.ZipFile(io.BytesIO(r.content))
|
|
129
|
+
assert yaml.safe_load(z.read("porch-motion-v2/meta.yaml"))["derived_from"] == "porch-motion"
|
|
130
|
+
assert "origin" not in json.loads(z.read("porch-motion-v2/design.json"))
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_resharing_your_own_design_does_not_derive_from_itself(studio):
|
|
134
|
+
app, _, _ = studio
|
|
135
|
+
origin = {"catalogue": REPO, "slug": "porch-motion", "author": "octocat", "license": "CC-BY-4.0"}
|
|
136
|
+
r = _share(app, design={**EXAMPLE, "id": "porch-motion", "origin": origin})
|
|
137
|
+
z = zipfile.ZipFile(io.BytesIO(r.content))
|
|
138
|
+
assert "derived_from" not in yaml.safe_load(z.read("porch-motion/meta.yaml"))
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def test_share_refuses_a_literal_credential(studio):
|
|
142
|
+
app, _, _ = studio
|
|
143
|
+
design = json.loads(json.dumps(EXAMPLE))
|
|
144
|
+
design["fleet"]["secrets_ref"]["wifi_password"] = "hunter22"
|
|
145
|
+
r = _share(app, design=design)
|
|
146
|
+
assert r.status_code == 422 and "fleet.secrets_ref.wifi_password" in r.json()["detail"]
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@pytest.mark.parametrize("over, match", [
|
|
150
|
+
({"author": "not a handle"}, "GitHub handle"),
|
|
151
|
+
({"tags": ["Has Space"]}, "tags"),
|
|
152
|
+
({"slug": "Bad_Slug"}, "not a usable name"),
|
|
153
|
+
])
|
|
154
|
+
def test_share_refuses_what_the_catalogue_would(studio, over, match):
|
|
155
|
+
app, _, _ = studio
|
|
156
|
+
r = _share(app, **over)
|
|
157
|
+
assert r.status_code == 422 and match in r.json()["detail"]
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _jpeg(size=(3000, 2000), exif=True, fmt="JPEG") -> str:
|
|
161
|
+
import base64
|
|
162
|
+
|
|
163
|
+
from PIL import Image
|
|
164
|
+
|
|
165
|
+
img = Image.new("RGB", size, (200, 80, 40))
|
|
166
|
+
kwargs = {}
|
|
167
|
+
if exif:
|
|
168
|
+
e = Image.Exif()
|
|
169
|
+
e[0x010F] = "PhoneMaker"
|
|
170
|
+
e[0x0112] = 6 # Orientation: rotate 90 CW on display
|
|
171
|
+
kwargs["exif"] = e
|
|
172
|
+
buf = io.BytesIO()
|
|
173
|
+
img.save(buf, fmt, **kwargs)
|
|
174
|
+
return base64.b64encode(buf.getvalue()).decode()
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def test_share_re_encodes_photos_without_metadata(studio):
|
|
178
|
+
from PIL import Image
|
|
179
|
+
|
|
180
|
+
app, _, _ = studio
|
|
181
|
+
r = _share(app, photos=[{"name": "IMG_0042.JPG", "data": _jpeg()},
|
|
182
|
+
{"name": "IMG_0042.JPG", "data": _jpeg(fmt="PNG", exif=False)}])
|
|
183
|
+
assert r.status_code == 200, r.text
|
|
184
|
+
z = zipfile.ZipFile(io.BytesIO(r.content))
|
|
185
|
+
names = sorted(n for n in z.namelist() if "/photos/" in n)
|
|
186
|
+
assert names == ["porch-motion-v2/photos/img-0042-2.jpg", "porch-motion-v2/photos/img-0042.jpg"]
|
|
187
|
+
with Image.open(io.BytesIO(z.read("porch-motion-v2/photos/img-0042.jpg"))) as img:
|
|
188
|
+
assert img.format == "JPEG" and not img.getexif()
|
|
189
|
+
assert img.size == (1365, 2048), "the EXIF rotation is applied before EXIF is dropped"
|
|
190
|
+
assert all(z.getinfo(n).file_size <= 1024 * 1024 for n in names)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def test_share_refuses_what_is_not_a_photo(studio):
|
|
194
|
+
import base64
|
|
195
|
+
|
|
196
|
+
app, _, _ = studio
|
|
197
|
+
r = _share(app, photos=[{"name": "notes.txt", "data": base64.b64encode(b"hello").decode()}])
|
|
198
|
+
assert r.status_code == 422 and "not a readable image" in r.json()["detail"]
|
|
199
|
+
r = _share(app, photos=[{"name": f"p{i}.jpg", "data": _jpeg((10, 10))} for i in range(5)])
|
|
200
|
+
assert r.status_code == 422 and "at most 4" in r.json()["detail"]
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
LED_DRIVER = (Path(__file__).resolve().parent / "fixtures" / "components" / "led_driver_npn.yaml").read_text()
|
|
204
|
+
LED_URL = f"{RAW}/main/designs/porch-led/components/led_driver_npn.yaml"
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _led_design() -> dict:
|
|
208
|
+
doc = {**EXAMPLE, "id": "porch-led"}
|
|
209
|
+
doc["components"] = [*EXAMPLE["components"],
|
|
210
|
+
{"id": "status", "library_id": "led_driver_npn", "label": "Status LED", "params": {}}]
|
|
211
|
+
return doc
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _publish_led(publish):
|
|
215
|
+
publish(_entry("porch-led", user_components=[{"id": "led_driver_npn", "url": LED_URL}]),
|
|
216
|
+
designs={"porch-led": _led_design()}, components={LED_URL: LED_DRIVER})
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def test_import_installs_the_designs_user_components(studio, user_library):
|
|
220
|
+
app, publish, _ = studio
|
|
221
|
+
_publish_led(publish)
|
|
222
|
+
r = app.post("/community/designs/porch-led/import")
|
|
223
|
+
assert r.status_code == 200, r.text
|
|
224
|
+
assert r.json()["installed_components"] == ["led_driver_npn"]
|
|
225
|
+
assert user_library.component_source("led_driver_npn") == "user"
|
|
226
|
+
again = app.post("/community/designs/porch-led/import").json()
|
|
227
|
+
assert again["installed_components"] == [], "the same text is already here"
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def test_import_never_overwrites_a_different_user_component(studio, user_library):
|
|
231
|
+
app, publish, _ = studio
|
|
232
|
+
_publish_led(publish)
|
|
233
|
+
user_library.save_component("led_driver_npn", LED_DRIVER.replace("NPN low-side LED driver", "mine", 1))
|
|
234
|
+
r = app.post("/community/designs/porch-led/import")
|
|
235
|
+
assert r.status_code == 409 and "different component 'led_driver_npn'" in r.json()["detail"]
|
|
236
|
+
assert "mine" in user_library.component_yaml("led_driver_npn")
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def test_share_packs_the_user_components_the_design_uses(studio, user_library):
|
|
240
|
+
app, _, _ = studio
|
|
241
|
+
user_library.save_component("led_driver_npn", LED_DRIVER)
|
|
242
|
+
r = _share(app, design=_led_design())
|
|
243
|
+
assert r.status_code == 200, r.text
|
|
244
|
+
z = zipfile.ZipFile(io.BytesIO(r.content))
|
|
245
|
+
slug = "porch-led"
|
|
246
|
+
assert z.read(f"{slug}/components/led_driver_npn.yaml").decode() == LED_DRIVER
|
|
247
|
+
assert not [n for n in z.namelist() if "/components/" in n and "led_driver_npn" not in n], \
|
|
248
|
+
"bundled components are not packed"
|
|
@@ -58,31 +58,41 @@ def test_no_board_silently_returns_empty(lib):
|
|
|
58
58
|
assert check_pin_compatibility({"components": [], "connections": []}, lib) == []
|
|
59
59
|
|
|
60
60
|
|
|
61
|
-
def
|
|
62
|
-
"""
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
d
|
|
67
|
-
|
|
61
|
+
def _with_lamp(d: dict, pin: str) -> dict:
|
|
62
|
+
"""garage-motion plus a relay/LED output the MCU drives, on `pin`."""
|
|
63
|
+
d["components"].append({"id": "lamp", "library_id": "gpio_output", "label": "Lamp", "params": {}})
|
|
64
|
+
d["connections"].append({"component_id": "lamp", "pin_role": "OUT",
|
|
65
|
+
"target": {"kind": "gpio", "pin": pin}})
|
|
66
|
+
return d
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _move_pir(d: dict, pin: str) -> dict:
|
|
68
70
|
for c in d["connections"]:
|
|
69
71
|
if c["component_id"] == "pir1" and c["pin_role"] == "OUT":
|
|
70
|
-
c["target"] = {"kind": "gpio", "pin":
|
|
71
|
-
|
|
72
|
+
c["target"] = {"kind": "gpio", "pin": pin}
|
|
73
|
+
return d
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_input_only_as_output_is_an_error(lib):
|
|
77
|
+
"""GPIO34 on ESP32-DevKitC-V4 is input-only; an output the MCU drives
|
|
78
|
+
there is a hard error."""
|
|
79
|
+
warnings = check_pin_compatibility(_with_lamp(_load("garage-motion"), "GPIO34"), lib)
|
|
72
80
|
errs = _by_code(warnings, "input_only_as_output")
|
|
73
81
|
assert len(errs) == 1
|
|
74
82
|
assert errs[0].severity == "error"
|
|
75
83
|
assert errs[0].pin == "GPIO34"
|
|
76
84
|
|
|
77
85
|
|
|
86
|
+
def test_a_sensor_output_on_an_input_only_pin_is_fine(lib):
|
|
87
|
+
"""A PIR's OUT is the MCU's input: GPIO34-39 are the usual place for it."""
|
|
88
|
+
warnings = check_pin_compatibility(_move_pir(_load("garage-motion"), "GPIO34"), lib)
|
|
89
|
+
assert warnings == []
|
|
90
|
+
|
|
91
|
+
|
|
78
92
|
def test_boot_high_pin_used_as_output_warns(lib):
|
|
79
93
|
"""ESP32 GPIO0 is boot_high (must be HIGH at boot). Driving it as a digital
|
|
80
94
|
output should warn."""
|
|
81
|
-
|
|
82
|
-
for c in d["connections"]:
|
|
83
|
-
if c["component_id"] == "pir1" and c["pin_role"] == "OUT":
|
|
84
|
-
c["target"] = {"kind": "gpio", "pin": "GPIO0"}
|
|
85
|
-
warnings = check_pin_compatibility(d, lib)
|
|
95
|
+
warnings = check_pin_compatibility(_with_lamp(_load("garage-motion"), "GPIO0"), lib)
|
|
86
96
|
boot = _by_code(warnings, "boot_strap_output")
|
|
87
97
|
assert len(boot) == 1
|
|
88
98
|
assert boot[0].severity == "warn"
|
|
@@ -92,16 +102,23 @@ def test_boot_high_pin_used_as_output_warns(lib):
|
|
|
92
102
|
def test_boot_low_pin_used_as_output_warns(lib):
|
|
93
103
|
"""ESP32 GPIO12 is boot_low (must be LOW at boot, controls flash voltage).
|
|
94
104
|
Output use should warn."""
|
|
95
|
-
|
|
96
|
-
for c in d["connections"]:
|
|
97
|
-
if c["component_id"] == "pir1" and c["pin_role"] == "OUT":
|
|
98
|
-
c["target"] = {"kind": "gpio", "pin": "GPIO12"}
|
|
99
|
-
warnings = check_pin_compatibility(d, lib)
|
|
105
|
+
warnings = check_pin_compatibility(_with_lamp(_load("garage-motion"), "GPIO12"), lib)
|
|
100
106
|
boot = _by_code(warnings, "boot_strap_output")
|
|
101
107
|
assert len(boot) == 1
|
|
102
108
|
assert "LOW at boot" in boot[0].message
|
|
103
109
|
|
|
104
110
|
|
|
111
|
+
@pytest.mark.parametrize("pin, level", [("GPIO0", "HIGH"), ("GPIO12", "LOW")])
|
|
112
|
+
def test_a_strap_pin_driven_by_a_sensor_warns(lib, pin, level):
|
|
113
|
+
"""A PIR holds its output at power-up, so on a strap pin it decides the
|
|
114
|
+
boot mode -- the hazard is the sensor's, even though the MCU reads it."""
|
|
115
|
+
warnings = check_pin_compatibility(_move_pir(_load("garage-motion"), pin), lib)
|
|
116
|
+
driven = _by_code(warnings, "boot_strap_driven")
|
|
117
|
+
assert len(driven) == 1 and driven[0].severity == "warn"
|
|
118
|
+
assert f"must be {level} at boot" in driven[0].message
|
|
119
|
+
assert _by_code(warnings, "boot_strap_output") == []
|
|
120
|
+
|
|
121
|
+
|
|
105
122
|
def test_boot_strap_pin_as_input_does_not_warn(lib):
|
|
106
123
|
"""Buttons wired to boot strap pins (e.g., Sonoff GPIO0 button) are read
|
|
107
124
|
as inputs; the validator should not flag this even though the pin is a
|
|
@@ -362,3 +379,50 @@ def test_a_reserved_bus_id_is_an_error(lib):
|
|
|
362
379
|
assert w.severity == "error"
|
|
363
380
|
assert w.component_id == "uart0"
|
|
364
381
|
assert "uart_bus0" in w.message
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
PART_DRIVEN = {
|
|
385
|
+
"hc-sr501": ["OUT"], "rcwl-0516": ["OUT"], "ld2410": ["OUT"], "remote_receiver": ["OUT"],
|
|
386
|
+
"hlw8012": ["CF", "CF1"], "sx127x": ["DIO0"], "wiegand": ["D0", "D1"],
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
@pytest.mark.parametrize("library_id, roles", sorted(PART_DRIVEN.items()))
|
|
391
|
+
def test_part_driven_signals_are_mcu_inputs(lib, library_id, roles):
|
|
392
|
+
"""The audit's verdicts: these lines are outputs of the part, so inputs
|
|
393
|
+
of the MCU, and the part drives them."""
|
|
394
|
+
pins = {p.role: p for p in lib.component(library_id).electrical.pins}
|
|
395
|
+
for role in roles:
|
|
396
|
+
assert pins[role].kind == "digital_in" and pins[role].driven, f"{library_id}.{role}"
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def test_the_component_gate_refuses_driven_on_an_output(lib):
|
|
400
|
+
from wirestudio.library.check import check_component_yaml
|
|
401
|
+
|
|
402
|
+
text = (REPO_ROOT / "tests" / "fixtures" / "components" / "led_driver_npn.yaml").read_text()
|
|
403
|
+
assert check_component_yaml(text, lib).ok
|
|
404
|
+
bad = text.replace("kind: digital_out", "kind: digital_out, driven: true", 1)
|
|
405
|
+
assert bad != text
|
|
406
|
+
report = check_component_yaml(bad, lib)
|
|
407
|
+
assert not report.ok and any("`driven`" in e for e in report.errors)
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def _with_receivers(d: dict, n: int, **params) -> dict:
|
|
411
|
+
for i, pin in enumerate(("GPIO32", "GPIO33", "GPIO25", "GPIO26")[:n]):
|
|
412
|
+
d["components"].append({"id": f"rx{i}", "library_id": "remote_receiver",
|
|
413
|
+
"label": f"RX {i}", "params": dict(params)})
|
|
414
|
+
d["connections"].append({"component_id": f"rx{i}", "pin_role": "OUT",
|
|
415
|
+
"target": {"kind": "gpio", "pin": pin}})
|
|
416
|
+
return d
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def test_a_third_remote_receiver_on_a_classic_esp32_warns(lib):
|
|
420
|
+
assert _by_code(check_pin_compatibility(_with_receivers(_load("garage-motion"), 2), lib), "rmt_memory") == []
|
|
421
|
+
warns = _by_code(check_pin_compatibility(_with_receivers(_load("garage-motion"), 3), lib), "rmt_memory")
|
|
422
|
+
assert len(warns) == 1 and warns[0].component_id == "rx2"
|
|
423
|
+
assert "rmt_symbols" in warns[0].message
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def test_receivers_with_rmt_symbols_set_do_not_warn(lib):
|
|
427
|
+
d = _with_receivers(_load("garage-motion"), 3, rmt_symbols=128)
|
|
428
|
+
assert _by_code(check_pin_compatibility(d, lib), "rmt_memory") == []
|
|
@@ -14,12 +14,14 @@ The generator is pure text-out, so we lean on substring assertions +
|
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
16
|
import json
|
|
17
|
+
import re
|
|
17
18
|
from pathlib import Path
|
|
18
19
|
|
|
19
20
|
import pytest
|
|
20
21
|
|
|
21
22
|
from wirestudio.kicad import generate_skidl
|
|
22
23
|
from wirestudio.kicad.generator import _py_var
|
|
24
|
+
from wirestudio.kicad.netlist import build_netlist
|
|
23
25
|
from wirestudio.library import default_library
|
|
24
26
|
from wirestudio.model import Design
|
|
25
27
|
|
|
@@ -160,15 +162,24 @@ def test_rail_targets_become_named_nets(lib):
|
|
|
160
162
|
assert 'NET_PLUS__3V3 = Net("+3V3")' in script
|
|
161
163
|
|
|
162
164
|
|
|
163
|
-
def
|
|
164
|
-
"""
|
|
165
|
-
|
|
165
|
+
def test_bus_targets_land_on_a_net_per_line(lib):
|
|
166
|
+
"""A bus is a net per line: the BME280's SDA and SCL go to i2c0's SDA
|
|
167
|
+
and SCL nets, never one shared net. The symbol's I2C pins are SDI/SCK
|
|
168
|
+
(pin_map rewrites SDA->SDI, SCL->SCK)."""
|
|
166
169
|
script = generate_skidl(_design("garage-motion"), lib)
|
|
167
|
-
assert '
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
assert 'c_bme1["SDI"] +=
|
|
171
|
-
assert 'c_bme1["SCK"] +=
|
|
170
|
+
assert 'NET_BUS_i2c0_SDA = Net("BUS_i2c0_SDA")' in script
|
|
171
|
+
assert 'NET_BUS_i2c0_SCL = Net("BUS_i2c0_SCL")' in script
|
|
172
|
+
assert 'Net("BUS_i2c0")' not in script
|
|
173
|
+
assert 'c_bme1["SDI"] += NET_BUS_i2c0_SDA' in script
|
|
174
|
+
assert 'c_bme1["SCK"] += NET_BUS_i2c0_SCL' in script
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def test_uart_lines_cross_over(lib):
|
|
178
|
+
"""A UART part's TX is the MCU's RX line, and its RX the MCU's TX."""
|
|
179
|
+
nets = {n.name: {(p.ref, p.pin_role) for p in n.pads}
|
|
180
|
+
for n in build_netlist(_design("nextion-thermostat"), lib)}
|
|
181
|
+
assert nets["BUS_nextion_uart_RX"] == {("U1", "TX"), ("M1", "IO16")}
|
|
182
|
+
assert nets["BUS_nextion_uart_TX"] == {("U1", "RX"), ("M1", "IO17")}
|
|
172
183
|
|
|
173
184
|
|
|
174
185
|
def test_gpio_targets_share_one_declared_net(lib):
|
|
@@ -187,8 +198,8 @@ def test_board_pins_join_gpio_rail_and_bus_nets(lib):
|
|
|
187
198
|
the module's VDD, and 5V has no module pin, which the script says."""
|
|
188
199
|
script = generate_skidl(_design("garage-motion"), lib)
|
|
189
200
|
assert 'board["IO13"] += NET_GPIO_GPIO13 # GPIO13' in script
|
|
190
|
-
assert 'board["IO21"] +=
|
|
191
|
-
assert 'board["IO22"] +=
|
|
201
|
+
assert 'board["IO21"] += NET_BUS_i2c0_SDA # GPIO21' in script
|
|
202
|
+
assert 'board["IO22"] += NET_BUS_i2c0_SCL # GPIO22' in script
|
|
192
203
|
assert 'board["VDD"] += NET_PLUS__3V3 # 3V3' in script
|
|
193
204
|
assert 'board["GND"] += GND # GND' in script
|
|
194
205
|
assert "# 5V: no pin on RF_Module:ESP32-WROOM-32" in script
|
|
@@ -198,8 +209,8 @@ def test_board_pins_use_the_boards_own_role_names(lib):
|
|
|
198
209
|
"""A D1 mini design names pins D1/D2; the pin_map turns them into the
|
|
199
210
|
symbol's SCL/D1 and SDA/D2."""
|
|
200
211
|
script = generate_skidl(_design("room-climate"), lib)
|
|
201
|
-
assert 'board["SDA/D2"]
|
|
202
|
-
assert 'board["SCL/D1"]
|
|
212
|
+
assert re.search(r'board\["SDA/D2"\] \+= NET_BUS_\w+_SDA ', script)
|
|
213
|
+
assert re.search(r'board\["SCL/D1"\] \+= NET_BUS_\w+_SCL ', script)
|
|
203
214
|
assert 'board["3V3"] += NET_PLUS__3V3' in script
|
|
204
215
|
|
|
205
216
|
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Routed DRC: kicad-cli's report summarised for people, stored with the
|
|
2
|
+
routed board, and served by the API and route_pcb.
|
|
3
|
+
|
|
4
|
+
kicad-cli itself runs in CI's pcb-route gate, which also checks this path
|
|
5
|
+
reports the airwires of an unrouted board; here its JSON comes from a
|
|
6
|
+
fixture captured from KiCad 8 on the unrouted garage-motion board."""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
from wirestudio.kicad import drc as drc_mod
|
|
15
|
+
from wirestudio.kicad.drc import WAIVED, summarize
|
|
16
|
+
from wirestudio.kicad.route import routed_drc
|
|
17
|
+
|
|
18
|
+
FIXTURE = json.loads((Path(__file__).parent / "fixtures" / "drc_unrouted_garage_motion.json").read_text())
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_summary_leads_with_the_airwires():
|
|
22
|
+
s = summarize(FIXTURE)
|
|
23
|
+
assert s["unconnected"] == 7 and s["ok"] is False
|
|
24
|
+
assert s["groups"][0]["type"] == "unconnected_items"
|
|
25
|
+
first = s["groups"][0]["violations"][0]["items"][0]
|
|
26
|
+
assert first["description"].startswith("Pad ") and first["pos"]["x"] is not None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_footprint_inherent_types_are_waived_not_failed():
|
|
30
|
+
s = summarize(FIXTURE)
|
|
31
|
+
drill = next(g for g in s["groups"] if g["type"] == "drill_out_of_range")
|
|
32
|
+
assert drill["waived"] and drill["count"] == 12 and drill["reason"] == WAIVED["drill_out_of_range"]
|
|
33
|
+
assert s["errors"] == 0, "the only errors besides airwires are waived"
|
|
34
|
+
assert [g["waived"] for g in s["groups"]][-1] is True, "waived groups sort last"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_a_routed_board_with_a_short_is_not_ok():
|
|
38
|
+
report = {"violations": [{"type": "shorting_items", "severity": "error",
|
|
39
|
+
"description": "Items shorting two nets", "items": []}],
|
|
40
|
+
"unconnected_items": []}
|
|
41
|
+
s = summarize(report)
|
|
42
|
+
assert s["ok"] is False and s["errors"] == 1 and s["unconnected"] == 0
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_a_clean_routed_board_is_ok():
|
|
46
|
+
report = {"violations": [{"type": "silk_overlap", "severity": "warning", "description": "x", "items": []}],
|
|
47
|
+
"unconnected_items": []}
|
|
48
|
+
s = summarize(report)
|
|
49
|
+
assert s["ok"] is True and s["warnings"] == 0 and s["waived"] == 1
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@pytest.fixture
|
|
53
|
+
def slot(tmp_path):
|
|
54
|
+
key = "0123456789abcdef"
|
|
55
|
+
(tmp_path / key).mkdir()
|
|
56
|
+
(tmp_path / key / "routed.kicad_pcb").write_text("(kicad_pcb (segment (net 1)))")
|
|
57
|
+
return tmp_path, key
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_routed_drc_runs_once_and_is_stored(slot, monkeypatch):
|
|
61
|
+
cache, key = slot
|
|
62
|
+
calls = []
|
|
63
|
+
monkeypatch.setattr(drc_mod, "drc_status", lambda: {"available": True, "reason": None})
|
|
64
|
+
monkeypatch.setattr(drc_mod, "run_drc", lambda text: calls.append(text) or summarize(FIXTURE))
|
|
65
|
+
first = routed_drc(key, cache_dir=cache)
|
|
66
|
+
second = routed_drc(key, cache_dir=cache)
|
|
67
|
+
assert first == second and first["unconnected"] == 7
|
|
68
|
+
assert len(calls) == 1 and (cache / key / "drc.json").is_file()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_routed_drc_without_kicad_cli_is_none(slot, monkeypatch):
|
|
72
|
+
cache, key = slot
|
|
73
|
+
monkeypatch.setattr(drc_mod, "drc_status", lambda: {"available": False, "reason": "no"})
|
|
74
|
+
assert routed_drc(key, cache_dir=cache) is None
|
|
75
|
+
assert routed_drc("ffffffffffffffff", cache_dir=cache) is None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_the_api_serves_the_routed_drc(slot, monkeypatch):
|
|
79
|
+
from fastapi.testclient import TestClient
|
|
80
|
+
|
|
81
|
+
from wirestudio.api.app import create_app
|
|
82
|
+
|
|
83
|
+
cache, key = slot
|
|
84
|
+
monkeypatch.setenv("WIRESTUDIO_ROUTE_CACHE", str(cache))
|
|
85
|
+
monkeypatch.setattr(drc_mod, "drc_status", lambda: {"available": True, "reason": None})
|
|
86
|
+
monkeypatch.setattr(drc_mod, "run_drc", lambda text: summarize(FIXTURE))
|
|
87
|
+
app = TestClient(create_app())
|
|
88
|
+
body = app.get(f"/design/kicad/route/{key}/drc").json()
|
|
89
|
+
assert body["unconnected"] == 7 and body["groups"][0]["type"] == "unconnected_items"
|
|
90
|
+
assert app.get("/design/kicad/route/ffffffffffffffff/drc").status_code == 404
|
|
91
|
+
assert app.get("/design/kicad/route/nope/drc").status_code == 422
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_route_pcb_brief_drops_item_detail():
|
|
95
|
+
from wirestudio.agent.tools import _drc_brief
|
|
96
|
+
|
|
97
|
+
brief = _drc_brief(summarize(FIXTURE))
|
|
98
|
+
unconnected = brief["groups"][0]
|
|
99
|
+
assert unconnected == {"type": "unconnected_items", "severity": "error", "count": 7, "waived": False,
|
|
100
|
+
"examples": ["Missing connection between items"] * 3}
|
|
101
|
+
assert "examples" not in brief["groups"][-1], "waived groups carry counts only"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_route_pcb_refuses_bad_parameters():
|
|
105
|
+
from wirestudio.agent.tools import _run_route_pcb
|
|
106
|
+
from wirestudio.library import default_library
|
|
107
|
+
|
|
108
|
+
design = json.loads((Path(__file__).resolve().parent.parent / "wirestudio/examples/garage-motion.json").read_text())
|
|
109
|
+
out = _run_route_pcb(design, default_library(), keepouts=[{"x": 0, "y": 0, "w": 0, "h": 1}])
|
|
110
|
+
assert out["ok"] is False and "bad route parameters" in out["error"]
|
|
111
|
+
out = _run_route_pcb(design, default_library(), via_costs=-1)
|
|
112
|
+
assert out["ok"] is False and "bad route parameters" in out["error"]
|