thrustlab 0.3.2__tar.gz → 0.5.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.
- {thrustlab-0.3.2 → thrustlab-0.5.0}/CHANGELOG.md +123 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/PKG-INFO +6 -2
- {thrustlab-0.3.2 → thrustlab-0.5.0}/examples/dynamic/run_and_poll.py +4 -2
- thrustlab-0.5.0/examples/fmu/ardupilot_sitl/README.md +80 -0
- thrustlab-0.5.0/examples/fmu/ardupilot_sitl/fly_mission.py +225 -0
- thrustlab-0.5.0/examples/fmu/ardupilot_sitl/fmu.parm +6 -0
- thrustlab-0.5.0/examples/fmu/ardupilot_sitl/plot_mission.py +267 -0
- thrustlab-0.5.0/examples/fmu/ardupilot_sitl/quad_x.yaml +62 -0
- thrustlab-0.5.0/examples/fmu/ardupilot_sitl/render_mission_video.py +454 -0
- thrustlab-0.5.0/examples/fmu/ardupilot_sitl_tilttri/README.md +76 -0
- thrustlab-0.5.0/examples/fmu/ardupilot_sitl_tilttri/fly_transition.py +211 -0
- thrustlab-0.5.0/examples/fmu/ardupilot_sitl_tilttri/tilttri.parm +30 -0
- thrustlab-0.5.0/examples/fmu/ardupilot_sitl_tilttri/tilttri.yaml +128 -0
- thrustlab-0.5.0/examples/fmu/ardupilot_sitl_y6/README.md +62 -0
- thrustlab-0.5.0/examples/fmu/ardupilot_sitl_y6/plot_coax_split.py +131 -0
- thrustlab-0.5.0/examples/fmu/ardupilot_sitl_y6/y6.parm +6 -0
- thrustlab-0.5.0/examples/fmu/ardupilot_sitl_y6/y6.yaml +107 -0
- thrustlab-0.5.0/examples/fmu/betaflight_sitl/README.md +242 -0
- thrustlab-0.5.0/examples/fmu/betaflight_sitl/betaflight_config.txt +41 -0
- thrustlab-0.5.0/examples/fmu/betaflight_sitl/betaflight_quad_x.yaml +129 -0
- thrustlab-0.5.0/examples/fmu/betaflight_sitl/fly_hover.py +697 -0
- thrustlab-0.5.0/examples/fmu/endurance/README.md +31 -0
- thrustlab-0.5.0/examples/fmu/endurance/hover_endurance.py +215 -0
- thrustlab-0.5.0/examples/fmu/export_and_download.py +80 -0
- thrustlab-0.5.0/examples/fmu/gazebo/README.md +63 -0
- thrustlab-0.5.0/examples/fmu/gazebo/quad_fmu.sdf +373 -0
- thrustlab-0.5.0/examples/fmu/quad_5inch_x.yaml +142 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/examples/outputs/read_dynamic.py +22 -8
- {thrustlab-0.3.2 → thrustlab-0.5.0}/examples/outputs/read_steady_state.py +4 -2
- {thrustlab-0.3.2 → thrustlab-0.5.0}/examples/quickstart.py +4 -2
- {thrustlab-0.3.2 → thrustlab-0.5.0}/examples/simulations/run_sync.py +6 -4
- {thrustlab-0.3.2 → thrustlab-0.5.0}/examples/sweeps/run_and_poll.py +4 -2
- {thrustlab-0.3.2 → thrustlab-0.5.0}/pyproject.toml +31 -2
- thrustlab-0.5.0/tests/sitl/__init__.py +1 -0
- thrustlab-0.5.0/tests/sitl/ardupilot_parser.py +160 -0
- thrustlab-0.5.0/tests/sitl/betaflight_fc.py +119 -0
- thrustlab-0.5.0/tests/sitl/conftest.py +47 -0
- thrustlab-0.5.0/tests/sitl/mock_fmu.py +249 -0
- thrustlab-0.5.0/tests/sitl/test_betaflight_bridge.py +149 -0
- thrustlab-0.5.0/tests/sitl/test_betaflight_protocol.py +329 -0
- thrustlab-0.5.0/tests/sitl/test_kinematics.py +283 -0
- thrustlab-0.5.0/tests/sitl/test_protocol.py +222 -0
- thrustlab-0.5.0/tests/sitl/test_rigidbody.py +250 -0
- thrustlab-0.5.0/tests/sitl/test_state_machine.py +256 -0
- thrustlab-0.5.0/tests/sitl/test_step_path.py +487 -0
- thrustlab-0.5.0/tests/sitl/test_tilt_wing.py +315 -0
- thrustlab-0.5.0/tests/sitl/test_vehicle.py +273 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_async_polling.py +5 -5
- thrustlab-0.5.0/tests/test_resources/test_fmu.py +148 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_spec_coverage.py +0 -1
- {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_webhooks.py +1 -1
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_async_polling.py +2 -2
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_http.py +7 -3
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/__init__.py +72 -10
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/analyze_geometry_request.py +3 -1
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/analyze_geometry_response.py +176 -0
- thrustlab-0.5.0/thrustlab/_models/analyze_geometry_response_envelope_limiting_factor_type_0.py +13 -0
- thrustlab-0.5.0/thrustlab/_models/analyze_geometry_response_envelope_status_type_0.py +10 -0
- thrustlab-0.5.0/thrustlab/_models/component_specs_response.py +101 -0
- thrustlab-0.5.0/thrustlab/_models/component_specs_response_spec_json.py +47 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/credit_balance_resource.py +17 -3
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/credit_usage_event_resource.py +10 -3
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/credit_usage_summary_resource.py +2 -2
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/design_request.py +74 -7
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/dynamic_create_body.py +127 -23
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/dynamic_estimate_body.py +86 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/dynamic_resource.py +51 -0
- thrustlab-0.5.0/thrustlab/_models/dynamic_resource_battery_topology_type_0.py +47 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/dynamic_rotor_group_in.py +84 -5
- thrustlab-0.5.0/thrustlab/_models/dynamic_rotor_group_in_esc_timing.py +11 -0
- thrustlab-0.5.0/thrustlab/_models/dynamic_rotor_group_in_esc_type.py +9 -0
- thrustlab-0.5.0/thrustlab/_models/dynamic_rotor_in.py +254 -0
- thrustlab-0.5.0/thrustlab/_models/dynamic_rotor_in_esc_timing.py +11 -0
- thrustlab-0.5.0/thrustlab/_models/dynamic_rotor_in_esc_type.py +9 -0
- thrustlab-0.5.0/thrustlab/_models/dynamic_rotor_in_motor_cooling_source.py +10 -0
- thrustlab-0.5.0/thrustlab/_models/dynamic_rotor_in_rotation_sense.py +9 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/entitlements_resource.py +31 -2
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/export_geometry_request.py +3 -2
- thrustlab-0.5.0/thrustlab/_models/fmu_export_accepted_response.py +82 -0
- thrustlab-0.5.0/thrustlab/_models/fmu_export_job_error.py +69 -0
- thrustlab-0.5.0/thrustlab/_models/fmu_export_status_response.py +393 -0
- thrustlab-0.5.0/thrustlab/_models/generate_geometry_request.py +382 -0
- thrustlab-0.5.0/thrustlab/_models/pack_leaf_in.py +92 -0
- thrustlab-0.5.0/thrustlab/_models/pack_parallel_in.py +113 -0
- thrustlab-0.5.0/thrustlab/_models/pack_series_in.py +104 -0
- thrustlab-0.5.0/thrustlab/_models/pack_topology_in.py +116 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/rotor_group_in.py +54 -32
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/rotor_group_resource.py +27 -0
- thrustlab-0.5.0/thrustlab/_models/rotor_in.py +424 -0
- thrustlab-0.5.0/thrustlab/_models/rotor_in_esc_timing.py +11 -0
- thrustlab-0.5.0/thrustlab/_models/rotor_in_esc_type.py +9 -0
- thrustlab-0.5.0/thrustlab/_models/rotor_in_motor_cooling_source.py +10 -0
- thrustlab-0.5.0/thrustlab/_models/rotor_in_rotation_sense.py +9 -0
- thrustlab-0.5.0/thrustlab/_models/segment_group_command_in.py +119 -0
- thrustlab-0.3.2/thrustlab/_models/segment_group_command_in_tilt_ramp.py → thrustlab-0.5.0/thrustlab/_models/segment_group_command_in_throttle_ramp_type_0.py +1 -1
- thrustlab-0.3.2/thrustlab/_models/segment_group_command_in_throttle_ramp.py → thrustlab-0.5.0/thrustlab/_models/segment_group_command_in_tilt_ramp_type_0.py +1 -1
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_create_body.py +83 -3
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_resource.py +42 -0
- thrustlab-0.5.0/thrustlab/_models/simulation_resource_battery_topology_type_0.py +47 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_config_in.py +6 -2
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_create_body.py +65 -17
- thrustlab-0.5.0/thrustlab/_models/sweep_point_component_selection.py +112 -0
- thrustlab-0.5.0/thrustlab/_models/sweep_point_inputs.py +282 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_point_resource.py +28 -6
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_resource.py +9 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_rotor_group_in.py +15 -9
- thrustlab-0.5.0/thrustlab/_models/sweep_rotor_in.py +426 -0
- thrustlab-0.5.0/thrustlab/_models/sweep_rotor_in_esc_timing.py +11 -0
- thrustlab-0.5.0/thrustlab/_models/sweep_rotor_in_esc_type.py +9 -0
- thrustlab-0.5.0/thrustlab/_models/sweep_rotor_in_motor_cooling_source.py +10 -0
- thrustlab-0.5.0/thrustlab/_models/sweep_rotor_in_rotation_sense.py +9 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/user_resource.py +26 -0
- thrustlab-0.5.0/thrustlab/_models/user_resource_gate_required_tiers.py +47 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_version.py +1 -1
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/client.py +3 -1
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/resources/dynamic_simulations.py +6 -2
- thrustlab-0.5.0/thrustlab/resources/fmu.py +147 -0
- thrustlab-0.5.0/thrustlab/sitl/README.md +362 -0
- thrustlab-0.5.0/thrustlab/sitl/__init__.py +93 -0
- thrustlab-0.5.0/thrustlab/sitl/__main__.py +10 -0
- thrustlab-0.5.0/thrustlab/sitl/bridge.py +173 -0
- thrustlab-0.5.0/thrustlab/sitl/cli.py +217 -0
- thrustlab-0.5.0/thrustlab/sitl/fmu.py +494 -0
- thrustlab-0.5.0/thrustlab/sitl/kinematics.py +147 -0
- thrustlab-0.5.0/thrustlab/sitl/model.py +66 -0
- thrustlab-0.5.0/thrustlab/sitl/protocol.py +240 -0
- thrustlab-0.5.0/thrustlab/sitl/rigidbody.py +439 -0
- thrustlab-0.5.0/thrustlab/sitl/rpmlog.py +115 -0
- thrustlab-0.5.0/thrustlab/sitl/targets/__init__.py +42 -0
- thrustlab-0.5.0/thrustlab/sitl/targets/ardupilot.py +221 -0
- thrustlab-0.5.0/thrustlab/sitl/targets/base.py +292 -0
- thrustlab-0.5.0/thrustlab/sitl/targets/betaflight.py +714 -0
- thrustlab-0.5.0/thrustlab/sitl/vehicle.py +687 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/webhooks.py +1 -2
- thrustlab-0.3.2/.impeccable/hook.cache.json +0 -1
- thrustlab-0.3.2/Makefile +0 -24
- thrustlab-0.3.2/ci/public-ci.yml +0 -35
- thrustlab-0.3.2/scripts/publish_public_repo.sh +0 -185
- thrustlab-0.3.2/scripts/regen_models.py +0 -152
- thrustlab-0.3.2/thrustlab/_models/generate_geometry_request.py +0 -205
- thrustlab-0.3.2/thrustlab/_models/geometry_preview_response.py +0 -112
- thrustlab-0.3.2/thrustlab/_models/preview_request.py +0 -88
- thrustlab-0.3.2/thrustlab/_models/segment_group_command_in.py +0 -88
- thrustlab-0.3.2/thrustlab/_models/sweep_point_resource_inputs.py +0 -47
- {thrustlab-0.3.2 → thrustlab-0.5.0}/.gitignore +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/LICENSE +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/NOTICE +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/README.md +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/examples/components/create_component.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/examples/projects/create.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/examples/projects/list.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/examples/projects/retrieve.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/examples/simulations/wait.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/examples/webhooks/verify_handler.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/__init__.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/conftest.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_client.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_components_create.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_components_find.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_dynamic_resource.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_exceptions.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_http.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_pagination.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_resources/__init__.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_resources/test_components.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_resources/test_compute_units.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_resources/test_projects.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_resources/test_simulations.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_resources/test_starred_components.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_resources/test_submissions.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_resources/test_sweeps.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_resources/test_users.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_resources/test_webhook_endpoints.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/__init__.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/airfoil_breakpoint.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/airfoil_list_response.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/airfoil_resource.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/analyze_geometry_request_mode.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/analyze_geometry_request_target_type_type_0.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/analyze_station_row.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/body_import_airfoil_v1_airfoils_import_post.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/calibrate_accepted_response.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/component_brand.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/component_brands_response.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/component_create_body.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/component_create_body_spec_json.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/component_ids_response.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/component_list_response.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/component_patch.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/component_patch_spec_json_type_0.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/component_resource.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/component_resource_spec_json.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/component_sweep_axis_in.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/component_sweep_axis_in_axis.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/component_write_response.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/component_write_response_spec_json.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/component_write_warning.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/create_webhook_endpoint_v1_webhook_endpoints_post_response_create_webhook_endpoint_v1_webhook_endpoints_post.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/credit_bucket_breakdown.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/credit_bucket_breakdown_type.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/credit_usage_event_resource_type.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/credit_usage_event_resource_unit_type.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/credit_usage_list_response.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/credit_usage_related_resource.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/credit_usage_related_resource_object.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/credit_usage_summary_resource_window.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/design_request_target_mode.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/dynamic_estimate_resource.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/dynamic_list_response.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/dynamic_patch.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/dynamic_resource_display_labels_type_0.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/dynamic_resource_error_type_0.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/dynamic_resource_input_snapshot_type_0.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/dynamic_resource_result_type_0.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/dynamic_resource_status.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/dynamic_rotor_group_in_motor_cooling_source.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/export_geometry_request_format.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/export_geometry_request_rotation_type_0.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/fire_test_event_v1_webhook_endpoints_public_id_test_post_response_fire_test_event_v1_webhook_endpoints_public_id_test_post.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/geometry_response.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/geometry_style_resource.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/geometry_styles_list_response.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/get_delivery_v1_webhook_endpoints_public_id_deliveries_delivery_public_id_get_response_get_delivery_v1_webhook_endpoints_public_id_deliveries_delivery_public_id_get.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/get_event_v1_events_event_id_get_response_get_event_v1_events_event_id_get.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/get_public_result_v1_public_results_slug_get_response_get_public_result_v1_public_results_slug_get.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/get_shared_result_v1_public_share_share_token_get_response_get_shared_result_v1_public_share_share_token_get.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/get_webhook_endpoint_v1_webhook_endpoints_public_id_get_response_get_webhook_endpoint_v1_webhook_endpoints_public_id_get.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/http_validation_error.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/list_deliveries_v1_webhook_endpoints_public_id_deliveries_get_response_list_deliveries_v1_webhook_endpoints_public_id_deliveries_get.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/list_events_v1_events_get_response_list_events_v1_events_get.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/list_public_propellers_v1_public_propellers_get_response_list_public_propellers_v1_public_propellers_get.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/list_webhook_endpoints_v1_webhook_endpoints_get_response_list_webhook_endpoints_v1_webhook_endpoints_get.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/patch_webhook_endpoint_v1_webhook_endpoints_public_id_patch_response_patch_webhook_endpoint_v1_webhook_endpoints_public_id_patch.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/project_create_body.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/project_list_response.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/project_patch.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/project_resource.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/retry_delivery_v1_webhook_endpoints_public_id_deliveries_delivery_public_id_retry_post_response_retry_delivery_v1_webhook_endpoints_public_id_deliveries_delivery_public_id_retry_post.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/rotate_webhook_secret_v1_webhook_endpoints_public_id_rotate_secret_post_response_rotate_webhook_secret_v1_webhook_endpoints_public_id_rotate_secret_post.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/rotor_group_in_esc_timing.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/rotor_group_in_esc_type.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/rotor_group_in_motor_cooling_source.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/run_queued_v1v1_simulations_run_queued_post_response_run_queued_v1v1_simulations_run_queued_post.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/run_selected_body.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/run_selected_v1v1_simulations_run_selected_post_response_run_selected_v1v1_simulations_run_selected_post.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/schedule_in.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/schedule_in_interpolation_type_0.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/schedule_in_mode.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/segment_in.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/segment_in_airspeed_ramp.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/segment_in_per_group.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/segment_in_throttle_ramp.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/segment_in_vertical_speed_ramp.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/share_resource.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_cancel_body.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_create_body_cooling_source_type_0.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_create_body_flight_regime.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_create_body_inflow_mode.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_create_body_launch_intent.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_inputs.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_list_response.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_patch.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_patch_plot_config_json_type_0_item.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_resource_display_labels_type_0.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_resource_error_type_0.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_resource_input_snapshot_type_0.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_resource_plot_config_json_type_0_item.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_resource_result_type_0.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_resource_result_type_0_additional_property.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_resource_status.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/spline_data.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/starred_component_create_body.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/starred_component_list_response.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/starred_component_resource.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/submission_create_body.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/submission_create_body_data_json.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/submission_list_response.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/submission_patch.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/submission_patch_data_json_type_0.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/submission_resource.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/submission_resource_data_json.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_cancel_body.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_config_in_esc_timing_values_type_0_item.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_create_body_cooling_source.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_create_body_inflow_mode.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_create_body_launch_intent.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_inputs.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_list_response.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_param_range_in.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_param_range_in_mode.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_patch.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_patch_plot_config_json_type_0_item.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_point_list_response.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_point_list_response_display_labels_type_0.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_point_patch.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_point_resource_rotors_type_0.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_point_resource_rotors_type_0_additional_property.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_resource_error_type_0.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_resource_input_snapshot_type_0.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_resource_plot_config_json_type_0_item.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_resource_status.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_resource_summary_type_0.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_resource_sweep_config_type_0.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_rotor_group_in_esc_timing.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_rotor_group_in_esc_type.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_rotor_group_in_motor_cooling_source.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/termination_in.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/termination_in_mode.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/types.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/user_resource_unit_system.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/v1_custom_component_override.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/v1_custom_component_override_spec_json.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/v1_health_v1_health_get_response_v1_health_v1_health_get.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/validation_error.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/webhook_endpoint_create.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/webhook_endpoint_patch.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_pagination.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/exceptions.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/resources/__init__.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/resources/_base.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/resources/components.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/resources/compute_units.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/resources/projects.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/resources/simulations.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/resources/starred_components.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/resources/submissions.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/resources/sweeps.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/resources/users.py +0 -0
- {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/resources/webhook_endpoints.py +0 -0
|
@@ -8,6 +8,129 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.5.0] - 2026-08-31
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- `client.fmu` can export a completed simulation as an FMI 3.0 co-simulation
|
|
16
|
+
FMU with per-rotor inputs and outputs, battery and thermal state, and a
|
|
17
|
+
validity envelope. Exports can be enqueued, checked with paced polling,
|
|
18
|
+
downloaded when ready, or canceled.
|
|
19
|
+
- The new `thrustlab.sitl` module flies exported FMUs in ArduPilot SITL and
|
|
20
|
+
Betaflight SITL using a six-degree-of-freedom rigid body, per-rotor servo
|
|
21
|
+
mapping, and battery voltage, current, and airspeed feedback to the firmware.
|
|
22
|
+
The `thrustlab-sitl` console command launches the bridge.
|
|
23
|
+
- The new `examples/fmu` gallery includes quadcopter and Y6 SITL missions, an
|
|
24
|
+
endurance study, and replay rendering.
|
|
25
|
+
- Generated models now cover component datasheets and the `component_specs`
|
|
26
|
+
entitlement flag.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- NumPy, PyYAML, and FMPy are now included in the core installation, so
|
|
31
|
+
`pip install thrustlab` provides everything needed for SITL. The `[sitl]`
|
|
32
|
+
extra remains available as an empty compatibility alias.
|
|
33
|
+
|
|
34
|
+
## [0.4.0] - 2026-08-27
|
|
35
|
+
|
|
36
|
+
Contract refresh for the production `/v1/` API. This release adds the current
|
|
37
|
+
multi-rotor, battery-topology, geometry, entitlement, and native Rust solver
|
|
38
|
+
response models. It also removes generated models for an endpoint that is not
|
|
39
|
+
part of the public API.
|
|
40
|
+
|
|
41
|
+
### Breaking
|
|
42
|
+
|
|
43
|
+
- Paid accounts now represent unlimited monthly simulation usage explicitly:
|
|
44
|
+
`CreditBalanceResource.total` and `CreditUsageEventResource.balance_after`
|
|
45
|
+
may be `None`, and `CreditBalanceResource.unlimited` states whether the
|
|
46
|
+
account has no monthly usage allowance. Concurrency and HTTP rate limits are
|
|
47
|
+
still enforced separately.
|
|
48
|
+
- Removed `PreviewRequest` and `GeometryPreviewResponse`. The corresponding
|
|
49
|
+
`/v1/geometry/preview` operation was not a public live endpoint; use
|
|
50
|
+
`client.geometry.generate(...)` or `client.geometry.analyze(...)` instead.
|
|
51
|
+
- The generated sweep-point payload is now typed as `SweepPointInputs` with a
|
|
52
|
+
nested `SweepPointComponentSelection`, replacing the unstructured
|
|
53
|
+
`SweepPointResourceInputs` model.
|
|
54
|
+
- Generated dynamic schedule enum names now include the nullable-union suffix:
|
|
55
|
+
`SegmentGroupCommandInThrottleRampType0` and
|
|
56
|
+
`SegmentGroupCommandInTiltRampType0` replace their unsuffixed counterparts.
|
|
57
|
+
|
|
58
|
+
### Added
|
|
59
|
+
|
|
60
|
+
- Single-point, sweep, and dynamic requests accept an ordered `rotors` list.
|
|
61
|
+
Each rotor can carry its rotation sense and coaxial stack/position metadata;
|
|
62
|
+
legacy grouped inputs remain accepted for compatibility. Rotor-group counts
|
|
63
|
+
now support up to 40 rotors.
|
|
64
|
+
- Structured `PackTopologyIn` battery definitions with series, parallel, and
|
|
65
|
+
leaf nodes. Simulation and dynamic resources return the stored topology.
|
|
66
|
+
- Simulation, sweep, and dynamic resources expose `solver_engine`; dynamic
|
|
67
|
+
resources also expose `snapshot_version`.
|
|
68
|
+
- Dynamic rotor groups expose the full ESC contract: type, timing, PWM
|
|
69
|
+
frequency, resistance, motor-wire resistance, and synchronous rectification.
|
|
70
|
+
- Geometry design/generation accepts explicit radial station arrays, hub ratio,
|
|
71
|
+
airfoil layout, and one to 16 blades. Analysis responses expose validation
|
|
72
|
+
envelope status/limiting factor and `Ct_raw`, `Cp_raw`, `thrust_raw`,
|
|
73
|
+
`torque_raw`, and `power_raw`.
|
|
74
|
+
- Entitlements expose `max_concurrent_runs` and `fmi_export`. User resources
|
|
75
|
+
expose first/last name and required-tier gate metadata.
|
|
76
|
+
|
|
77
|
+
### Changed
|
|
78
|
+
|
|
79
|
+
- The published contract and generated models now identify native Rust solver
|
|
80
|
+
results via the `solver_engine` response field (`prom-rs/0.2.1` in this
|
|
81
|
+
release).
|
|
82
|
+
- Hobbyist and Pro usage is unlimited. `max_concurrent_runs` communicates the
|
|
83
|
+
account-wide queue width (one for Hobbyist and five for Pro); request rate
|
|
84
|
+
limiting remains independent of simulation concurrency.
|
|
85
|
+
- **Physical inputs that were silently accepted out of range are now rejected
|
|
86
|
+
with a `422` naming the field.** The intervals below were already enforced one
|
|
87
|
+
layer inside the API, but they were absent from the request schema — so a
|
|
88
|
+
request carrying one of these values either returned an unhelpful `500` or, for
|
|
89
|
+
the pinned motor temperatures, was accepted and run. Nothing in range changes:
|
|
90
|
+
every previously-valid request is still valid and returns the same numbers.
|
|
91
|
+
|
|
92
|
+
| Field | Accepted range |
|
|
93
|
+
| --- | --- |
|
|
94
|
+
| `density_kg_m3` (simulation, sweep, dynamic, dynamic estimate) | `0 < x <= 2.0` kg/m³ |
|
|
95
|
+
| `airspeed_m_s` (simulation, sweep) | `0 – 100` m/s |
|
|
96
|
+
| `airspeed_target` (dynamic schedule segment and CSV row) | `0 – 100` m/s |
|
|
97
|
+
| `throttle_pct` (rotor group) | `0 – 100` % |
|
|
98
|
+
| `esc_resistance_mohm`, `esc_motor_wire_resistance_mohm` (rotor group) | `0 – 10000` mΩ |
|
|
99
|
+
| `battery_esc_wire_resistance_mohm` (simulation, sweep) | `0 – 10000` mΩ |
|
|
100
|
+
| `motor_t_w`, `motor_t_mag` (rotor group) | `-60 – 250` °C |
|
|
101
|
+
|
|
102
|
+
The motor-temperature pins are the ones worth checking in existing code: they
|
|
103
|
+
are **degrees Celsius**, and a value in kelvin (e.g. `293.15`) used to be
|
|
104
|
+
accepted and quietly changed the winding resistance and torque constant. It is
|
|
105
|
+
now a `422`.
|
|
106
|
+
|
|
107
|
+
Every one of these bounds also appears in the OpenAPI spec now, so a
|
|
108
|
+
spec-generated client can see them.
|
|
109
|
+
|
|
110
|
+
- **`esc_type` now defaults to `six_step` instead of `foc`** on `RotorGroupIn`
|
|
111
|
+
and `SweepRotorGroupIn` (ESC-7). This is a BEHAVIOUR change for any caller that
|
|
112
|
+
omits the field: `esc_type` selects the commutation convention — K_volt
|
|
113
|
+
(1/sqrt(3) vs sqrt(3)/pi), copper and iron multipliers, the six-step advance
|
|
114
|
+
physics, and the throttle→duty map. Measured on a 400 kV / 14-pole / 50 mOhm
|
|
115
|
+
motor at 22.2 V: RPM −4.56% at full throttle (−2.56% at 75%, −2.84% at 50%,
|
|
116
|
+
−3.71% at 25%), thrust −8.91% at full throttle to −5.06% at 75%.
|
|
117
|
+
|
|
118
|
+
Why: the API defaulted to `foc` while the web app has always sent `six_step`,
|
|
119
|
+
so the same powertrain returned different numbers depending on which surface
|
|
120
|
+
submitted it. Six-step is the researched default — catalog Kv is measured under
|
|
121
|
+
trapezoidal drive and real hobby ESCs are 120° block commutation — so aligning
|
|
122
|
+
the API to it makes the two agree without moving the web app.
|
|
123
|
+
|
|
124
|
+
**To keep the previous behaviour, send `esc_type="foc"` explicitly.** Stored
|
|
125
|
+
simulations are unaffected: the column default changed with no backfill, so a
|
|
126
|
+
run recorded as FOC still re-runs as FOC.
|
|
127
|
+
|
|
128
|
+
### Fixed
|
|
129
|
+
|
|
130
|
+
- The source archive now uses an explicit public-package manifest, excluding
|
|
131
|
+
monorepo-only release tooling while retaining the SDK, tests, examples,
|
|
132
|
+
changelog, and legal files.
|
|
133
|
+
|
|
11
134
|
## [0.3.2] - 2026-07-16
|
|
12
135
|
|
|
13
136
|
Bug-fix release: a docs-vs-code audit found several SDK surfaces that
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: thrustlab
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Official Python SDK for the ThrustLab API
|
|
5
5
|
Project-URL: Homepage, https://thrustlab.com
|
|
6
6
|
Project-URL: Documentation, https://thrustlab.com/docs
|
|
@@ -20,14 +20,18 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.13
|
|
21
21
|
Requires-Python: >=3.10
|
|
22
22
|
Requires-Dist: attrs>=23.0
|
|
23
|
+
Requires-Dist: fmpy>=0.3.31
|
|
23
24
|
Requires-Dist: httpx<1.0,>=0.27
|
|
25
|
+
Requires-Dist: numpy>=1.24
|
|
24
26
|
Requires-Dist: pydantic<3.0,>=2.7
|
|
25
27
|
Requires-Dist: python-dateutil>=2.8
|
|
28
|
+
Requires-Dist: pyyaml>=6.0
|
|
26
29
|
Provides-Extra: dev
|
|
27
|
-
Requires-Dist: openapi-python-client
|
|
30
|
+
Requires-Dist: openapi-python-client==0.28.4; extra == 'dev'
|
|
28
31
|
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
29
32
|
Requires-Dist: pytest>=8; extra == 'dev'
|
|
30
33
|
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
34
|
+
Provides-Extra: sitl
|
|
31
35
|
Description-Content-Type: text/markdown
|
|
32
36
|
|
|
33
37
|
# thrustlab — official Python SDK for the ThrustLab API
|
|
@@ -19,9 +19,11 @@ client = Client() # reads $THRUSTLAB_API_KEY from the environment
|
|
|
19
19
|
project = client.projects.create(name="sdk dynamic example")
|
|
20
20
|
|
|
21
21
|
# Resolve by name, or paste explicit IDs: motor_id = "comp_motor_xxx"
|
|
22
|
-
motor = client.components.find(name="
|
|
22
|
+
motor = client.components.find(name="BadAss 2826-820Kv")
|
|
23
23
|
prop = client.components.find(name="10.5x4.5")
|
|
24
|
-
battery = client.components.find(
|
|
24
|
+
battery = client.components.find(
|
|
25
|
+
name="Liperior 5000mAh 4S 35C 14.8V Lipo Battery With XT90 Plug"
|
|
26
|
+
)
|
|
25
27
|
|
|
26
28
|
dyn = client.dynamic_simulations.create(
|
|
27
29
|
project_id=project["id"],
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# ArduPilot SITL — fly your exported powertrain
|
|
2
|
+
|
|
3
|
+
Fly a real ArduPilot mission where the vehicle physics is YOUR ThrustLab
|
|
4
|
+
powertrain: per-rotor thrust and torque, true rotor spin-up, battery sag and
|
|
5
|
+
state of charge — all from the `.fmu` you exported.
|
|
6
|
+
|
|
7
|
+
## What you need
|
|
8
|
+
|
|
9
|
+
- An exported FMU (`examples/fmu/export_and_download.py`, or the dashboard's
|
|
10
|
+
Share menu → Export powertrain).
|
|
11
|
+
- `pip install thrustlab` — the SDK ships the JSON-backend physics bridge
|
|
12
|
+
(`thrustlab.sitl`).
|
|
13
|
+
- An ArduPilot SITL build (`./waf copter`), any recent Copter.
|
|
14
|
+
- `pip install pymavlink matplotlib numpy` for the mission script and plots.
|
|
15
|
+
|
|
16
|
+
## Run it (three terminals)
|
|
17
|
+
|
|
18
|
+
1. The bridge listens first, with your FMU and a vehicle layout. The rpm log
|
|
19
|
+
goes into `mission_out/` so the plot step finds every recording in one
|
|
20
|
+
place (create the directory first — the bridge won't):
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
mkdir -p mission_out
|
|
24
|
+
python -m thrustlab.sitl \
|
|
25
|
+
--fmu my-powertrain.fmu --vehicle quad_x.yaml \
|
|
26
|
+
--rpm-log mission_out/mission_rpm_log.csv
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`quad_x.yaml` maps servo outputs to rotors (positions, spin directions,
|
|
30
|
+
mass). Start from the annotated `../quad_5inch_x.yaml` and edit mass
|
|
31
|
+
and geometry to your airframe.
|
|
32
|
+
|
|
33
|
+
2. ArduPilot with the JSON model — run from your ArduPilot checkout, and
|
|
34
|
+
point at this example directory's `fmu.parm` by absolute path:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
./build/sitl/bin/arducopter --model JSON:127.0.0.1 \
|
|
38
|
+
--defaults Tools/autotest/default_params/copter.parm,/path/to/examples/fmu/ardupilot_sitl/fmu.parm -I0
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`fmu.parm` needs at least `BATT_MONITOR 4` (the bridge feeds pack voltage
|
|
42
|
+
and current back into the firmware's battery monitor).
|
|
43
|
+
|
|
44
|
+
3. The mission:
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
python fly_mission.py --out-dir ./mission_out
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
It uploads a multi-leg AUTO mission (takeoff, 13 m/s dash, climb, loiter
|
|
51
|
+
turns, fast descent, RTL), flies it, and records MAVLink telemetry beside
|
|
52
|
+
the bridge's per-rotor log.
|
|
53
|
+
|
|
54
|
+
## The plots
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
python plot_mission.py ./mission_out ./mission_out/plots
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Three figures from the recorded flight: the mission track colored by
|
|
61
|
+
electrical power, the powertrain time series (per-rotor speed, bus voltage
|
|
62
|
+
and current, power and state of charge), and the loiter-exit close-up where
|
|
63
|
+
the mixer splits the four rotors.
|
|
64
|
+
|
|
65
|
+
The same recording also renders as an animated replay — the track draws
|
|
66
|
+
itself colored by power while live panels play the powertrain through the
|
|
67
|
+
mission:
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
python render_mission_video.py ./mission_out ./mission_out/video
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Frames land in `video/frames/`; with ffmpeg on PATH the mp4 is encoded
|
|
74
|
+
automatically, otherwise the exact encode command is printed.
|
|
75
|
+
|
|
76
|
+
Per-rotor RPM never crosses the ArduPilot JSON protocol — the protocol has no
|
|
77
|
+
RPM lane — so the bridge logs it to `mission_out/mission_rpm_log.csv` for
|
|
78
|
+
reconciliation against flight logs. If you changed the vehicle mass in
|
|
79
|
+
`quad_x.yaml`, set `WEIGHT_N` at the top of `plot_mission.py` to match — it
|
|
80
|
+
aligns the two recordings' clocks on the takeoff event.
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""Fly a multi-leg AUTO mission against a running ArduPilot SITL whose physics
|
|
2
|
+
is your exported ThrustLab FMU (via thrustlab.sitl), recording MAVLink
|
|
3
|
+
telemetry for the plot script.
|
|
4
|
+
|
|
5
|
+
Usage: python fly_mission.py [--connect tcp:127.0.0.1:5760] [--out-dir .]
|
|
6
|
+
|
|
7
|
+
Writes <out-dir>/mission_telemetry.csv and <out-dir>/mission_events.csv; the
|
|
8
|
+
per-rotor truth (rpm, thrust, bus V/I, SOC) is the bridge's own --rpm-log CSV.
|
|
9
|
+
"""
|
|
10
|
+
import argparse
|
|
11
|
+
import csv
|
|
12
|
+
import math
|
|
13
|
+
import sys
|
|
14
|
+
import time
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from pymavlink import mavutil
|
|
18
|
+
|
|
19
|
+
M_PER_DEG_LAT = 111320.0
|
|
20
|
+
WALL_CAP_S = 900.0
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def build_mission(home_lat, home_lon, speed_scale=1.0):
|
|
24
|
+
frame = mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT_INT
|
|
25
|
+
mav = mavutil.mavlink
|
|
26
|
+
items = []
|
|
27
|
+
|
|
28
|
+
def spd(m_s):
|
|
29
|
+
return max(2.0, m_s * speed_scale)
|
|
30
|
+
|
|
31
|
+
def add(cmd, p1=0, p2=0, p3=0, p4=0, dn=0.0, de=0.0, alt=0.0):
|
|
32
|
+
lat = home_lat + dn / M_PER_DEG_LAT
|
|
33
|
+
lon = home_lon + de / (M_PER_DEG_LAT * math.cos(math.radians(home_lat)))
|
|
34
|
+
items.append((len(items), frame, cmd, p1, p2, p3, p4,
|
|
35
|
+
int(lat * 1e7), int(lon * 1e7), float(alt)))
|
|
36
|
+
|
|
37
|
+
add(mav.MAV_CMD_NAV_WAYPOINT) # home slot
|
|
38
|
+
add(mav.MAV_CMD_NAV_TAKEOFF, alt=10.0)
|
|
39
|
+
add(mav.MAV_CMD_DO_CHANGE_SPEED, p1=1, p2=spd(13.0))
|
|
40
|
+
add(mav.MAV_CMD_NAV_WAYPOINT, dn=220.0, de=0.0, alt=15.0) # fast dash
|
|
41
|
+
add(mav.MAV_CMD_DO_CHANGE_SPEED, p1=1, p2=spd(6.0))
|
|
42
|
+
add(mav.MAV_CMD_NAV_WAYPOINT, dn=220.0, de=140.0, alt=32.0) # slow climb
|
|
43
|
+
add(mav.MAV_CMD_NAV_LOITER_TURNS, p1=2, p3=25.0, dn=220.0, de=140.0, alt=32.0)
|
|
44
|
+
add(mav.MAV_CMD_DO_CHANGE_SPEED, p1=1, p2=spd(14.0))
|
|
45
|
+
add(mav.MAV_CMD_NAV_WAYPOINT, dn=60.0, de=60.0, alt=14.0) # fast descent
|
|
46
|
+
add(mav.MAV_CMD_DO_CHANGE_SPEED, p1=1, p2=spd(5.0))
|
|
47
|
+
add(mav.MAV_CMD_NAV_WAYPOINT, dn=25.0, de=0.0, alt=8.0) # slow approach
|
|
48
|
+
add(mav.MAV_CMD_NAV_RETURN_TO_LAUNCH)
|
|
49
|
+
return items
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def upload_mission(m, items):
|
|
53
|
+
m.mav.mission_clear_all_send(m.target_system, m.target_component)
|
|
54
|
+
m.recv_match(type="MISSION_ACK", blocking=True, timeout=5)
|
|
55
|
+
m.mav.mission_count_send(m.target_system, m.target_component, len(items))
|
|
56
|
+
t0 = time.time()
|
|
57
|
+
while time.time() - t0 < 60:
|
|
58
|
+
msg = m.recv_match(
|
|
59
|
+
type=["MISSION_REQUEST", "MISSION_REQUEST_INT", "MISSION_ACK"],
|
|
60
|
+
blocking=True, timeout=10,
|
|
61
|
+
)
|
|
62
|
+
if msg is None:
|
|
63
|
+
raise RuntimeError("mission upload stalled")
|
|
64
|
+
if msg.get_type() == "MISSION_ACK":
|
|
65
|
+
if msg.type == mavutil.mavlink.MAV_MISSION_ACCEPTED:
|
|
66
|
+
print(f"mission accepted ({len(items)} items)", flush=True)
|
|
67
|
+
return
|
|
68
|
+
raise RuntimeError(f"mission rejected: ack type {msg.type}")
|
|
69
|
+
s, frame, cmd, p1, p2, p3, p4, lat, lon, alt = items[msg.seq]
|
|
70
|
+
m.mav.mission_item_int_send(
|
|
71
|
+
m.target_system, m.target_component, s, frame, cmd,
|
|
72
|
+
1 if s == 0 else 0, 1, p1, p2, p3, p4, lat, lon, alt,
|
|
73
|
+
)
|
|
74
|
+
raise RuntimeError("mission upload timed out")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def main():
|
|
78
|
+
ap = argparse.ArgumentParser()
|
|
79
|
+
ap.add_argument("--connect", default="tcp:127.0.0.1:5760")
|
|
80
|
+
ap.add_argument("--out-dir", type=Path, default=Path("."))
|
|
81
|
+
ap.add_argument("--speed-scale", type=float, default=1.0,
|
|
82
|
+
help="scale every mission leg speed (floor 2 m/s). A coax "
|
|
83
|
+
"export's edgewise envelope is tighter than a solo "
|
|
84
|
+
"rotor's; ~0.3 keeps a Y6 inside it.")
|
|
85
|
+
args = ap.parse_args()
|
|
86
|
+
args.out_dir.mkdir(parents=True, exist_ok=True)
|
|
87
|
+
|
|
88
|
+
m = mavutil.mavlink_connection(args.connect, source_system=250)
|
|
89
|
+
m.wait_heartbeat(timeout=120)
|
|
90
|
+
print(f"heartbeat from sys {m.target_system}", flush=True)
|
|
91
|
+
|
|
92
|
+
# Let the EKF settle; keep the streams coming at 10 Hz.
|
|
93
|
+
t0 = time.time()
|
|
94
|
+
while time.time() - t0 < 40:
|
|
95
|
+
m.mav.request_data_stream_send(
|
|
96
|
+
m.target_system, m.target_component,
|
|
97
|
+
mavutil.mavlink.MAV_DATA_STREAM_ALL, 10, 1,
|
|
98
|
+
)
|
|
99
|
+
st = m.recv_match(type="STATUSTEXT", blocking=False)
|
|
100
|
+
if st:
|
|
101
|
+
print("statustext:", st.text, flush=True)
|
|
102
|
+
time.sleep(0.5)
|
|
103
|
+
|
|
104
|
+
# Home = where the vehicle sits now.
|
|
105
|
+
gpi = m.recv_match(type="GLOBAL_POSITION_INT", blocking=True, timeout=10)
|
|
106
|
+
home_lat, home_lon = gpi.lat / 1e7, gpi.lon / 1e7
|
|
107
|
+
upload_mission(m, build_mission(home_lat, home_lon, args.speed_scale))
|
|
108
|
+
|
|
109
|
+
m.set_mode("GUIDED")
|
|
110
|
+
# GUIDED refuses to arm until the EKF is actually fusing GPS, and how
|
|
111
|
+
# long that takes in SIM time depends on the machine (a heavy FMU lowers
|
|
112
|
+
# the effective speedup). Retry instead of racing, and surface every
|
|
113
|
+
# refusal text instead of hanging in motors_armed_wait().
|
|
114
|
+
arm_deadline = time.time() + 300
|
|
115
|
+
armed = False
|
|
116
|
+
while not armed:
|
|
117
|
+
if time.time() > arm_deadline:
|
|
118
|
+
raise RuntimeError("arming timed out — see the refusal texts above")
|
|
119
|
+
m.arducopter_arm()
|
|
120
|
+
drain_until = time.time() + 5.0
|
|
121
|
+
while time.time() < drain_until:
|
|
122
|
+
msg = m.recv_match(type=["HEARTBEAT", "STATUSTEXT"],
|
|
123
|
+
blocking=True, timeout=1)
|
|
124
|
+
if msg is None:
|
|
125
|
+
continue
|
|
126
|
+
if msg.get_type() == "STATUSTEXT":
|
|
127
|
+
print("statustext:", msg.text, flush=True)
|
|
128
|
+
elif (msg.get_srcSystem() == m.target_system
|
|
129
|
+
and msg.base_mode
|
|
130
|
+
& mavutil.mavlink.MAV_MODE_FLAG_SAFETY_ARMED):
|
|
131
|
+
armed = True
|
|
132
|
+
break
|
|
133
|
+
print("armed", flush=True)
|
|
134
|
+
m.set_mode("AUTO")
|
|
135
|
+
for _ in range(10):
|
|
136
|
+
m.mav.command_long_send(
|
|
137
|
+
m.target_system, m.target_component,
|
|
138
|
+
mavutil.mavlink.MAV_CMD_MISSION_START, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
139
|
+
)
|
|
140
|
+
ack = m.recv_match(type="COMMAND_ACK", blocking=True, timeout=3)
|
|
141
|
+
if ack and ack.command == mavutil.mavlink.MAV_CMD_MISSION_START:
|
|
142
|
+
if ack.result == mavutil.mavlink.MAV_RESULT_ACCEPTED:
|
|
143
|
+
break
|
|
144
|
+
time.sleep(1)
|
|
145
|
+
|
|
146
|
+
tele = open(args.out_dir / "mission_telemetry.csv", "w", newline="")
|
|
147
|
+
tw = csv.writer(tele)
|
|
148
|
+
tw.writerow(["t_s", "lat", "lon", "alt_rel_m", "vn_m_s", "ve_m_s", "vd_m_s",
|
|
149
|
+
"groundspeed_m_s", "climb_m_s", "throttle_pct",
|
|
150
|
+
"roll_deg", "pitch_deg", "yaw_deg",
|
|
151
|
+
"voltage_V", "current_A", "battery_pct", "mode"])
|
|
152
|
+
ev = open(args.out_dir / "mission_events.csv", "w", newline="")
|
|
153
|
+
ew = csv.writer(ev)
|
|
154
|
+
ew.writerow(["t_s", "kind", "detail"])
|
|
155
|
+
|
|
156
|
+
last = {"vfr": None, "att": None, "sys": None, "mode": None, "gpi": None}
|
|
157
|
+
armed, was_flying, rows = True, False, 0
|
|
158
|
+
wall0 = time.time()
|
|
159
|
+
|
|
160
|
+
def now_s():
|
|
161
|
+
return last["gpi"].time_boot_ms / 1000.0 if last["gpi"] else 0.0
|
|
162
|
+
|
|
163
|
+
while time.time() - wall0 < WALL_CAP_S:
|
|
164
|
+
msg = m.recv_match(blocking=True, timeout=10)
|
|
165
|
+
if msg is None:
|
|
166
|
+
continue
|
|
167
|
+
t = msg.get_type()
|
|
168
|
+
if t == "VFR_HUD":
|
|
169
|
+
last["vfr"] = msg
|
|
170
|
+
elif t == "ATTITUDE":
|
|
171
|
+
last["att"] = msg
|
|
172
|
+
elif t == "SYS_STATUS":
|
|
173
|
+
last["sys"] = msg
|
|
174
|
+
elif t == "HEARTBEAT":
|
|
175
|
+
mode = mavutil.mode_string_v10(msg)
|
|
176
|
+
if mode != last["mode"]:
|
|
177
|
+
ew.writerow([f"{now_s():.2f}", "mode", mode])
|
|
178
|
+
print(f"mode -> {mode}", flush=True)
|
|
179
|
+
last["mode"] = mode
|
|
180
|
+
armed = bool(msg.base_mode & mavutil.mavlink.MAV_MODE_FLAG_SAFETY_ARMED)
|
|
181
|
+
if was_flying and not armed:
|
|
182
|
+
print("disarmed after flight — mission complete", flush=True)
|
|
183
|
+
break
|
|
184
|
+
elif t == "MISSION_ITEM_REACHED":
|
|
185
|
+
ew.writerow([f"{now_s():.2f}", "wp", msg.seq])
|
|
186
|
+
print(f"reached wp {msg.seq}", flush=True)
|
|
187
|
+
elif t == "STATUSTEXT":
|
|
188
|
+
ew.writerow([f"{now_s():.2f}", "text", msg.text])
|
|
189
|
+
elif t == "GLOBAL_POSITION_INT":
|
|
190
|
+
last["gpi"] = msg
|
|
191
|
+
if msg.relative_alt / 1000.0 > 2.0:
|
|
192
|
+
was_flying = True
|
|
193
|
+
vfr, att, s = last["vfr"], last["att"], last["sys"]
|
|
194
|
+
tw.writerow([
|
|
195
|
+
f"{msg.time_boot_ms / 1000.0:.2f}", msg.lat / 1e7, msg.lon / 1e7,
|
|
196
|
+
f"{msg.relative_alt / 1000.0:.2f}",
|
|
197
|
+
f"{msg.vx / 100.0:.2f}", f"{msg.vy / 100.0:.2f}", f"{msg.vz / 100.0:.2f}",
|
|
198
|
+
f"{vfr.groundspeed:.2f}" if vfr else "",
|
|
199
|
+
f"{vfr.climb:.2f}" if vfr else "",
|
|
200
|
+
vfr.throttle if vfr else "",
|
|
201
|
+
f"{math.degrees(att.roll):.1f}" if att else "",
|
|
202
|
+
f"{math.degrees(att.pitch):.1f}" if att else "",
|
|
203
|
+
f"{math.degrees(att.yaw):.1f}" if att else "",
|
|
204
|
+
f"{s.voltage_battery / 1000.0:.3f}" if s else "",
|
|
205
|
+
f"{s.current_battery / 100.0:.2f}" if s else "",
|
|
206
|
+
s.battery_remaining if s else "",
|
|
207
|
+
last["mode"] or "",
|
|
208
|
+
])
|
|
209
|
+
rows += 1
|
|
210
|
+
|
|
211
|
+
tele.close()
|
|
212
|
+
ev.close()
|
|
213
|
+
print(f"telemetry rows: {rows}", flush=True)
|
|
214
|
+
if not was_flying:
|
|
215
|
+
print("FAIL: never left the ground", flush=True)
|
|
216
|
+
return 2
|
|
217
|
+
if armed:
|
|
218
|
+
print("FAIL: still armed at wall cap", flush=True)
|
|
219
|
+
return 1
|
|
220
|
+
print("MISSION COMPLETE", flush=True)
|
|
221
|
+
return 0
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
if __name__ == "__main__":
|
|
225
|
+
sys.exit(main())
|