thrustlab 0.5.0__tar.gz → 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (338) hide show
  1. {thrustlab-0.5.0 → thrustlab-0.6.0}/CHANGELOG.md +36 -0
  2. {thrustlab-0.5.0 → thrustlab-0.6.0}/PKG-INFO +29 -1
  3. {thrustlab-0.5.0 → thrustlab-0.6.0}/README.md +28 -0
  4. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/components/create_component.py +2 -2
  5. thrustlab-0.6.0/examples/dynamic/watch_and_cancel.py +132 -0
  6. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/fmu/ardupilot_sitl/README.md +10 -1
  7. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/fmu/ardupilot_sitl_tilttri/README.md +10 -1
  8. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/fmu/ardupilot_sitl_y6/README.md +21 -8
  9. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/fmu/betaflight_sitl/README.md +10 -1
  10. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/fmu/endurance/README.md +10 -1
  11. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/fmu/gazebo/README.md +10 -1
  12. thrustlab-0.6.0/examples/fmu/simulink/.gitignore +2 -0
  13. thrustlab-0.6.0/examples/fmu/simulink/README.md +53 -0
  14. thrustlab-0.6.0/examples/fmu/simulink/fmu_cosim.png +0 -0
  15. thrustlab-0.6.0/examples/fmu/simulink/run_fmu_simulink.m +93 -0
  16. thrustlab-0.6.0/examples/sweeps/component_axis_grid.py +85 -0
  17. {thrustlab-0.5.0 → thrustlab-0.6.0}/pyproject.toml +3 -0
  18. thrustlab-0.6.0/tests/test_resources/test_simulations_cancel_batch.py +62 -0
  19. thrustlab-0.6.0/tests/test_resources/test_simulations_run_batch.py +80 -0
  20. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/__init__.py +20 -0
  21. thrustlab-0.6.0/thrustlab/_models/cancel_batch_result.py +97 -0
  22. thrustlab-0.6.0/thrustlab/_models/cancel_selected_body.py +42 -0
  23. thrustlab-0.6.0/thrustlab/_models/cancel_skipped.py +71 -0
  24. thrustlab-0.6.0/thrustlab/_models/cancel_skipped_reason.py +10 -0
  25. thrustlab-0.6.0/thrustlab/_models/component_filter_bounds.py +97 -0
  26. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/component_ids_response.py +27 -3
  27. thrustlab-0.6.0/thrustlab/_models/component_ids_response_filters.py +63 -0
  28. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/component_list_response.py +19 -0
  29. thrustlab-0.6.0/thrustlab/_models/component_list_response_filters.py +63 -0
  30. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/component_sweep_axis_in.py +1 -1
  31. thrustlab-0.6.0/thrustlab/_models/concurrency_resource.py +108 -0
  32. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/credit_balance_resource.py +1 -1
  33. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/credit_usage_event_resource.py +1 -1
  34. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/credit_usage_summary_resource.py +1 -1
  35. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/dynamic_resource.py +87 -4
  36. thrustlab-0.6.0/thrustlab/_models/dynamic_resource_progress_type_0.py +47 -0
  37. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/simulation_resource.py +67 -31
  38. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_create_body.py +6 -3
  39. thrustlab-0.6.0/thrustlab/_models/sweep_credits_breakdown.py +73 -0
  40. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_param_range_in.py +12 -6
  41. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_resource.py +122 -31
  42. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/user_resource.py +29 -0
  43. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_version.py +1 -1
  44. thrustlab-0.6.0/thrustlab/resources/simulations.py +186 -0
  45. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/resources/sweeps.py +1 -1
  46. thrustlab-0.5.0/thrustlab/resources/simulations.py +0 -87
  47. {thrustlab-0.5.0 → thrustlab-0.6.0}/.gitignore +0 -0
  48. {thrustlab-0.5.0 → thrustlab-0.6.0}/LICENSE +0 -0
  49. {thrustlab-0.5.0 → thrustlab-0.6.0}/NOTICE +0 -0
  50. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/dynamic/run_and_poll.py +0 -0
  51. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/fmu/ardupilot_sitl/fly_mission.py +0 -0
  52. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/fmu/ardupilot_sitl/fmu.parm +0 -0
  53. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/fmu/ardupilot_sitl/plot_mission.py +0 -0
  54. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/fmu/ardupilot_sitl/quad_x.yaml +0 -0
  55. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/fmu/ardupilot_sitl/render_mission_video.py +0 -0
  56. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/fmu/ardupilot_sitl_tilttri/fly_transition.py +0 -0
  57. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/fmu/ardupilot_sitl_tilttri/tilttri.parm +0 -0
  58. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/fmu/ardupilot_sitl_tilttri/tilttri.yaml +0 -0
  59. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/fmu/ardupilot_sitl_y6/plot_coax_split.py +0 -0
  60. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/fmu/ardupilot_sitl_y6/y6.parm +0 -0
  61. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/fmu/ardupilot_sitl_y6/y6.yaml +0 -0
  62. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/fmu/betaflight_sitl/betaflight_config.txt +0 -0
  63. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/fmu/betaflight_sitl/betaflight_quad_x.yaml +0 -0
  64. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/fmu/betaflight_sitl/fly_hover.py +0 -0
  65. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/fmu/endurance/hover_endurance.py +0 -0
  66. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/fmu/export_and_download.py +0 -0
  67. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/fmu/gazebo/quad_fmu.sdf +0 -0
  68. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/fmu/quad_5inch_x.yaml +0 -0
  69. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/outputs/read_dynamic.py +0 -0
  70. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/outputs/read_steady_state.py +0 -0
  71. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/projects/create.py +0 -0
  72. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/projects/list.py +0 -0
  73. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/projects/retrieve.py +0 -0
  74. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/quickstart.py +0 -0
  75. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/simulations/run_sync.py +0 -0
  76. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/simulations/wait.py +0 -0
  77. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/sweeps/run_and_poll.py +0 -0
  78. {thrustlab-0.5.0 → thrustlab-0.6.0}/examples/webhooks/verify_handler.py +0 -0
  79. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/__init__.py +0 -0
  80. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/conftest.py +0 -0
  81. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/sitl/__init__.py +0 -0
  82. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/sitl/ardupilot_parser.py +0 -0
  83. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/sitl/betaflight_fc.py +0 -0
  84. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/sitl/conftest.py +0 -0
  85. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/sitl/mock_fmu.py +0 -0
  86. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/sitl/test_betaflight_bridge.py +0 -0
  87. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/sitl/test_betaflight_protocol.py +0 -0
  88. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/sitl/test_kinematics.py +0 -0
  89. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/sitl/test_protocol.py +0 -0
  90. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/sitl/test_rigidbody.py +0 -0
  91. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/sitl/test_state_machine.py +0 -0
  92. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/sitl/test_step_path.py +0 -0
  93. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/sitl/test_tilt_wing.py +0 -0
  94. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/sitl/test_vehicle.py +0 -0
  95. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/test_async_polling.py +0 -0
  96. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/test_client.py +0 -0
  97. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/test_components_create.py +0 -0
  98. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/test_components_find.py +0 -0
  99. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/test_dynamic_resource.py +0 -0
  100. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/test_exceptions.py +0 -0
  101. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/test_http.py +0 -0
  102. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/test_pagination.py +0 -0
  103. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/test_resources/__init__.py +0 -0
  104. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/test_resources/test_components.py +0 -0
  105. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/test_resources/test_compute_units.py +0 -0
  106. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/test_resources/test_fmu.py +0 -0
  107. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/test_resources/test_projects.py +0 -0
  108. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/test_resources/test_simulations.py +0 -0
  109. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/test_resources/test_starred_components.py +0 -0
  110. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/test_resources/test_submissions.py +0 -0
  111. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/test_resources/test_sweeps.py +0 -0
  112. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/test_resources/test_users.py +0 -0
  113. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/test_resources/test_webhook_endpoints.py +0 -0
  114. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/test_spec_coverage.py +0 -0
  115. {thrustlab-0.5.0 → thrustlab-0.6.0}/tests/test_webhooks.py +0 -0
  116. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/__init__.py +0 -0
  117. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_async_polling.py +0 -0
  118. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_http.py +0 -0
  119. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/airfoil_breakpoint.py +0 -0
  120. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/airfoil_list_response.py +0 -0
  121. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/airfoil_resource.py +0 -0
  122. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/analyze_geometry_request.py +0 -0
  123. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/analyze_geometry_request_mode.py +0 -0
  124. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/analyze_geometry_request_target_type_type_0.py +0 -0
  125. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/analyze_geometry_response.py +0 -0
  126. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/analyze_geometry_response_envelope_limiting_factor_type_0.py +0 -0
  127. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/analyze_geometry_response_envelope_status_type_0.py +0 -0
  128. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/analyze_station_row.py +0 -0
  129. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/body_import_airfoil_v1_airfoils_import_post.py +0 -0
  130. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/calibrate_accepted_response.py +0 -0
  131. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/component_brand.py +0 -0
  132. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/component_brands_response.py +0 -0
  133. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/component_create_body.py +0 -0
  134. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/component_create_body_spec_json.py +0 -0
  135. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/component_patch.py +0 -0
  136. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/component_patch_spec_json_type_0.py +0 -0
  137. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/component_resource.py +0 -0
  138. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/component_resource_spec_json.py +0 -0
  139. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/component_specs_response.py +0 -0
  140. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/component_specs_response_spec_json.py +0 -0
  141. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/component_sweep_axis_in_axis.py +0 -0
  142. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/component_write_response.py +0 -0
  143. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/component_write_response_spec_json.py +0 -0
  144. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/component_write_warning.py +0 -0
  145. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/create_webhook_endpoint_v1_webhook_endpoints_post_response_create_webhook_endpoint_v1_webhook_endpoints_post.py +0 -0
  146. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/credit_bucket_breakdown.py +0 -0
  147. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/credit_bucket_breakdown_type.py +0 -0
  148. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/credit_usage_event_resource_type.py +0 -0
  149. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/credit_usage_event_resource_unit_type.py +0 -0
  150. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/credit_usage_list_response.py +0 -0
  151. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/credit_usage_related_resource.py +0 -0
  152. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/credit_usage_related_resource_object.py +0 -0
  153. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/credit_usage_summary_resource_window.py +0 -0
  154. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/design_request.py +0 -0
  155. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/design_request_target_mode.py +0 -0
  156. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/dynamic_create_body.py +0 -0
  157. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/dynamic_estimate_body.py +0 -0
  158. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/dynamic_estimate_resource.py +0 -0
  159. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/dynamic_list_response.py +0 -0
  160. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/dynamic_patch.py +0 -0
  161. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/dynamic_resource_battery_topology_type_0.py +0 -0
  162. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/dynamic_resource_display_labels_type_0.py +0 -0
  163. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/dynamic_resource_error_type_0.py +0 -0
  164. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/dynamic_resource_input_snapshot_type_0.py +0 -0
  165. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/dynamic_resource_result_type_0.py +0 -0
  166. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/dynamic_resource_status.py +0 -0
  167. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/dynamic_rotor_group_in.py +0 -0
  168. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/dynamic_rotor_group_in_esc_timing.py +0 -0
  169. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/dynamic_rotor_group_in_esc_type.py +0 -0
  170. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/dynamic_rotor_group_in_motor_cooling_source.py +0 -0
  171. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/dynamic_rotor_in.py +0 -0
  172. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/dynamic_rotor_in_esc_timing.py +0 -0
  173. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/dynamic_rotor_in_esc_type.py +0 -0
  174. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/dynamic_rotor_in_motor_cooling_source.py +0 -0
  175. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/dynamic_rotor_in_rotation_sense.py +0 -0
  176. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/entitlements_resource.py +0 -0
  177. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/export_geometry_request.py +0 -0
  178. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/export_geometry_request_format.py +0 -0
  179. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/export_geometry_request_rotation_type_0.py +0 -0
  180. {thrustlab-0.5.0 → thrustlab-0.6.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
  181. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/fmu_export_accepted_response.py +0 -0
  182. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/fmu_export_job_error.py +0 -0
  183. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/fmu_export_status_response.py +0 -0
  184. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/generate_geometry_request.py +0 -0
  185. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/geometry_response.py +0 -0
  186. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/geometry_style_resource.py +0 -0
  187. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/geometry_styles_list_response.py +0 -0
  188. {thrustlab-0.5.0 → thrustlab-0.6.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
  189. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/get_event_v1_events_event_id_get_response_get_event_v1_events_event_id_get.py +0 -0
  190. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/get_public_result_v1_public_results_slug_get_response_get_public_result_v1_public_results_slug_get.py +0 -0
  191. {thrustlab-0.5.0 → thrustlab-0.6.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
  192. {thrustlab-0.5.0 → thrustlab-0.6.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
  193. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/http_validation_error.py +0 -0
  194. {thrustlab-0.5.0 → thrustlab-0.6.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
  195. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/list_events_v1_events_get_response_list_events_v1_events_get.py +0 -0
  196. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/list_public_propellers_v1_public_propellers_get_response_list_public_propellers_v1_public_propellers_get.py +0 -0
  197. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/list_webhook_endpoints_v1_webhook_endpoints_get_response_list_webhook_endpoints_v1_webhook_endpoints_get.py +0 -0
  198. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/pack_leaf_in.py +0 -0
  199. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/pack_parallel_in.py +0 -0
  200. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/pack_series_in.py +0 -0
  201. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/pack_topology_in.py +0 -0
  202. {thrustlab-0.5.0 → thrustlab-0.6.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
  203. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/project_create_body.py +0 -0
  204. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/project_list_response.py +0 -0
  205. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/project_patch.py +0 -0
  206. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/project_resource.py +0 -0
  207. {thrustlab-0.5.0 → thrustlab-0.6.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
  208. {thrustlab-0.5.0 → thrustlab-0.6.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
  209. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/rotor_group_in.py +0 -0
  210. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/rotor_group_in_esc_timing.py +0 -0
  211. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/rotor_group_in_esc_type.py +0 -0
  212. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/rotor_group_in_motor_cooling_source.py +0 -0
  213. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/rotor_group_resource.py +0 -0
  214. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/rotor_in.py +0 -0
  215. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/rotor_in_esc_timing.py +0 -0
  216. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/rotor_in_esc_type.py +0 -0
  217. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/rotor_in_motor_cooling_source.py +0 -0
  218. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/rotor_in_rotation_sense.py +0 -0
  219. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/run_queued_v1v1_simulations_run_queued_post_response_run_queued_v1v1_simulations_run_queued_post.py +0 -0
  220. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/run_selected_body.py +0 -0
  221. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/run_selected_v1v1_simulations_run_selected_post_response_run_selected_v1v1_simulations_run_selected_post.py +0 -0
  222. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/schedule_in.py +0 -0
  223. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/schedule_in_interpolation_type_0.py +0 -0
  224. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/schedule_in_mode.py +0 -0
  225. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/segment_group_command_in.py +0 -0
  226. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/segment_group_command_in_throttle_ramp_type_0.py +0 -0
  227. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/segment_group_command_in_tilt_ramp_type_0.py +0 -0
  228. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/segment_in.py +0 -0
  229. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/segment_in_airspeed_ramp.py +0 -0
  230. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/segment_in_per_group.py +0 -0
  231. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/segment_in_throttle_ramp.py +0 -0
  232. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/segment_in_vertical_speed_ramp.py +0 -0
  233. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/share_resource.py +0 -0
  234. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/simulation_cancel_body.py +0 -0
  235. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/simulation_create_body.py +0 -0
  236. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/simulation_create_body_cooling_source_type_0.py +0 -0
  237. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/simulation_create_body_flight_regime.py +0 -0
  238. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/simulation_create_body_inflow_mode.py +0 -0
  239. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/simulation_create_body_launch_intent.py +0 -0
  240. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/simulation_inputs.py +0 -0
  241. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/simulation_list_response.py +0 -0
  242. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/simulation_patch.py +0 -0
  243. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/simulation_patch_plot_config_json_type_0_item.py +0 -0
  244. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/simulation_resource_battery_topology_type_0.py +0 -0
  245. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/simulation_resource_display_labels_type_0.py +0 -0
  246. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/simulation_resource_error_type_0.py +0 -0
  247. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/simulation_resource_input_snapshot_type_0.py +0 -0
  248. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/simulation_resource_plot_config_json_type_0_item.py +0 -0
  249. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/simulation_resource_result_type_0.py +0 -0
  250. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/simulation_resource_result_type_0_additional_property.py +0 -0
  251. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/simulation_resource_status.py +0 -0
  252. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/spline_data.py +0 -0
  253. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/starred_component_create_body.py +0 -0
  254. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/starred_component_list_response.py +0 -0
  255. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/starred_component_resource.py +0 -0
  256. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/submission_create_body.py +0 -0
  257. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/submission_create_body_data_json.py +0 -0
  258. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/submission_list_response.py +0 -0
  259. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/submission_patch.py +0 -0
  260. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/submission_patch_data_json_type_0.py +0 -0
  261. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/submission_resource.py +0 -0
  262. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/submission_resource_data_json.py +0 -0
  263. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_cancel_body.py +0 -0
  264. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_config_in.py +0 -0
  265. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_config_in_esc_timing_values_type_0_item.py +0 -0
  266. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_create_body_cooling_source.py +0 -0
  267. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_create_body_inflow_mode.py +0 -0
  268. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_create_body_launch_intent.py +0 -0
  269. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_inputs.py +0 -0
  270. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_list_response.py +0 -0
  271. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_param_range_in_mode.py +0 -0
  272. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_patch.py +0 -0
  273. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_patch_plot_config_json_type_0_item.py +0 -0
  274. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_point_component_selection.py +0 -0
  275. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_point_inputs.py +0 -0
  276. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_point_list_response.py +0 -0
  277. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_point_list_response_display_labels_type_0.py +0 -0
  278. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_point_patch.py +0 -0
  279. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_point_resource.py +0 -0
  280. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_point_resource_rotors_type_0.py +0 -0
  281. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_point_resource_rotors_type_0_additional_property.py +0 -0
  282. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_resource_error_type_0.py +0 -0
  283. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_resource_input_snapshot_type_0.py +0 -0
  284. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_resource_plot_config_json_type_0_item.py +0 -0
  285. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_resource_status.py +0 -0
  286. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_resource_summary_type_0.py +0 -0
  287. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_resource_sweep_config_type_0.py +0 -0
  288. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_rotor_group_in.py +0 -0
  289. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_rotor_group_in_esc_timing.py +0 -0
  290. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_rotor_group_in_esc_type.py +0 -0
  291. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_rotor_group_in_motor_cooling_source.py +0 -0
  292. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_rotor_in.py +0 -0
  293. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_rotor_in_esc_timing.py +0 -0
  294. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_rotor_in_esc_type.py +0 -0
  295. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_rotor_in_motor_cooling_source.py +0 -0
  296. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/sweep_rotor_in_rotation_sense.py +0 -0
  297. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/termination_in.py +0 -0
  298. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/termination_in_mode.py +0 -0
  299. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/types.py +0 -0
  300. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/user_resource_gate_required_tiers.py +0 -0
  301. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/user_resource_unit_system.py +0 -0
  302. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/v1_custom_component_override.py +0 -0
  303. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/v1_custom_component_override_spec_json.py +0 -0
  304. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/v1_health_v1_health_get_response_v1_health_v1_health_get.py +0 -0
  305. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/validation_error.py +0 -0
  306. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/webhook_endpoint_create.py +0 -0
  307. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_models/webhook_endpoint_patch.py +0 -0
  308. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/_pagination.py +0 -0
  309. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/client.py +0 -0
  310. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/exceptions.py +0 -0
  311. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/resources/__init__.py +0 -0
  312. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/resources/_base.py +0 -0
  313. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/resources/components.py +0 -0
  314. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/resources/compute_units.py +0 -0
  315. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/resources/dynamic_simulations.py +0 -0
  316. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/resources/fmu.py +0 -0
  317. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/resources/projects.py +0 -0
  318. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/resources/starred_components.py +0 -0
  319. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/resources/submissions.py +0 -0
  320. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/resources/users.py +0 -0
  321. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/resources/webhook_endpoints.py +0 -0
  322. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/sitl/README.md +0 -0
  323. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/sitl/__init__.py +0 -0
  324. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/sitl/__main__.py +0 -0
  325. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/sitl/bridge.py +0 -0
  326. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/sitl/cli.py +0 -0
  327. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/sitl/fmu.py +0 -0
  328. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/sitl/kinematics.py +0 -0
  329. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/sitl/model.py +0 -0
  330. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/sitl/protocol.py +0 -0
  331. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/sitl/rigidbody.py +0 -0
  332. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/sitl/rpmlog.py +0 -0
  333. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/sitl/targets/__init__.py +0 -0
  334. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/sitl/targets/ardupilot.py +0 -0
  335. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/sitl/targets/base.py +0 -0
  336. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/sitl/targets/betaflight.py +0 -0
  337. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/sitl/vehicle.py +0 -0
  338. {thrustlab-0.5.0 → thrustlab-0.6.0}/thrustlab/webhooks.py +0 -0
@@ -8,6 +8,42 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [0.6.0] - 2026-09-09
12
+
13
+ ### Added
14
+
15
+ - `client.simulations.cancel_selected(run_ids)` cancels up to 1000 runs of any
16
+ kind (`sim_`, `sweep_`, `dyn_`) in one call and reports which were canceled,
17
+ which were asked to stop, and which were skipped and why.
18
+ `client.simulations.cancel_queued(project_id=...)` cancels every queued run
19
+ you own, optionally within one project.
20
+ - `client.simulations.run_queued()` and
21
+ `client.simulations.run_selected(simulation_ids)` dispatch runs parked with
22
+ `launch_intent="queue"` — all of them, or a named subset. Both accept sweep
23
+ ids as well as single-point ids.
24
+ - Generated models for the batch cancel response (`CancelBatchResult`,
25
+ `CancelSkipped`) and request body (`CancelSelectedBody`).
26
+ - Two runnable examples. `examples/sweeps/component_axis_grid.py` crosses a
27
+ propeller component axis with a throttle range in one submission and reads
28
+ the propeller name back off each point.
29
+ `examples/dynamic/watch_and_cancel.py` watches a mission's `progress` and
30
+ cancels it part-way through.
31
+
32
+ ### Changed
33
+
34
+ - Cancelling a running sweep is now a durable request: the server records it
35
+ and the worker stops after the point it is computing, even if the stop takes
36
+ longer than a minute to reach.
37
+ - Cancelling a RUNNING dynamic mission now reports under `requested` rather
38
+ than `canceled`. The mission keeps its partial result (`run_meta.partial`,
39
+ `run_meta.canceled_at_t_s`), the credits are refunded, and the execution slot
40
+ frees when the worker stops at its next integration window.
41
+ - Run resources carry `started_at`, the instant the solver began, alongside
42
+ `created_at`, `dispatched_at` and `completed_at`. Queue wait is
43
+ `started_at - dispatched_at`.
44
+ - Dynamic run resources carry `progress` and `cancel_requested_at`. Sweep
45
+ resources carry `cancel_requested_at` and `credits_breakdown`.
46
+
11
47
  ## [0.5.0] - 2026-08-31
12
48
 
13
49
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: thrustlab
3
- Version: 0.5.0
3
+ Version: 0.6.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
@@ -93,6 +93,33 @@ Runnable end-to-end examples for every simulation type live in
93
93
  [`sweeps/run_and_poll.py`](./examples/sweeps/run_and_poll.py), and
94
94
  [`dynamic/run_and_poll.py`](./examples/dynamic/run_and_poll.py).
95
95
 
96
+ ## FMU export and SITL
97
+
98
+ Export a completed simulation as an FMI 3.0 co-simulation FMU:
99
+
100
+ ```python
101
+ job_id = client.fmu.export(sim_id)["id"]
102
+ client.fmu.wait(job_id)
103
+ client.fmu.download(job_id, "powertrain.fmu")
104
+ ```
105
+
106
+ The FMU exposes per-rotor inputs and outputs, battery and thermal state, and a
107
+ validity envelope; it runs in any FMI 3.0 co-simulation host and is verified
108
+ in Simulink.
109
+
110
+ `thrustlab-sitl`, installed with the SDK, flies the FMU in ArduPilot SITL or
111
+ Betaflight SITL (`--target betaflight`). See
112
+ [`thrustlab/sitl/README.md`](./thrustlab/sitl/README.md) for the full
113
+ documentation.
114
+
115
+ ```shell
116
+ thrustlab-sitl --fmu powertrain.fmu --vehicle quad_x.yaml --target betaflight
117
+ ```
118
+
119
+ See [`examples/fmu/`](./examples/fmu/) for bundled ready-to-fly FMUs with
120
+ quadcopter, Y6 coax, and tilt-tricopter missions, an endurance study, and
121
+ replay rendering.
122
+
96
123
  ## Why the SDK (and not `urllib` / `wget`)?
97
124
 
98
125
  The SDK gives you automatic retries with backoff, idempotency keys, cursor
@@ -131,6 +158,7 @@ client.submissions
131
158
  client.starred_components
132
159
  client.compute_units
133
160
  client.webhook_endpoints
161
+ client.fmu
134
162
  ```
135
163
 
136
164
  ## Error handling
@@ -57,6 +57,33 @@ Runnable end-to-end examples for every simulation type live in
57
57
  [`sweeps/run_and_poll.py`](./examples/sweeps/run_and_poll.py), and
58
58
  [`dynamic/run_and_poll.py`](./examples/dynamic/run_and_poll.py).
59
59
 
60
+ ## FMU export and SITL
61
+
62
+ Export a completed simulation as an FMI 3.0 co-simulation FMU:
63
+
64
+ ```python
65
+ job_id = client.fmu.export(sim_id)["id"]
66
+ client.fmu.wait(job_id)
67
+ client.fmu.download(job_id, "powertrain.fmu")
68
+ ```
69
+
70
+ The FMU exposes per-rotor inputs and outputs, battery and thermal state, and a
71
+ validity envelope; it runs in any FMI 3.0 co-simulation host and is verified
72
+ in Simulink.
73
+
74
+ `thrustlab-sitl`, installed with the SDK, flies the FMU in ArduPilot SITL or
75
+ Betaflight SITL (`--target betaflight`). See
76
+ [`thrustlab/sitl/README.md`](./thrustlab/sitl/README.md) for the full
77
+ documentation.
78
+
79
+ ```shell
80
+ thrustlab-sitl --fmu powertrain.fmu --vehicle quad_x.yaml --target betaflight
81
+ ```
82
+
83
+ See [`examples/fmu/`](./examples/fmu/) for bundled ready-to-fly FMUs with
84
+ quadcopter, Y6 coax, and tilt-tricopter missions, an endurance study, and
85
+ replay rendering.
86
+
60
87
  ## Why the SDK (and not `urllib` / `wget`)?
61
88
 
62
89
  The SDK gives you automatic retries with backoff, idempotency keys, cursor
@@ -95,6 +122,7 @@ client.submissions
95
122
  client.starred_components
96
123
  client.compute_units
97
124
  client.webhook_endpoints
125
+ client.fmu
98
126
  ```
99
127
 
100
128
  ## Error handling
@@ -18,8 +18,8 @@ Notes on validation:
18
18
  one resistance figure (c_rating or pack_resistance_mOhm), and bounding-box
19
19
  dims; form_factor is derived from chemistry when omitted.
20
20
  * Propeller: per-station radius/chord/twist and rotation are required, and
21
- persisting a propeller needs the hobbyist tier or higher — the create below
22
- reports the tier gate cleanly on a free account.
21
+ persisting a propeller needs the Pro plan — the create below reports the
22
+ tier gate cleanly on a free account.
23
23
  """
24
24
 
25
25
  from thrustlab import Client
@@ -0,0 +1,132 @@
1
+ """Dynamic progress + cancel: submit -> watch `progress` -> cancel -> read the partial.
2
+
3
+ Run it:
4
+ export THRUSTLAB_API_KEY=key_... # never hard-code the key
5
+ python examples/dynamic/watch_and_cancel.py
6
+
7
+ Submits a mission that flies until the pack is empty, watches `progress` while
8
+ it runs, cancels it part-way through, and reads the partial result.
9
+
10
+ `progress` is {fraction, sim_time_s, sim_time_total_s, wall_s, updated_at}, and
11
+ is None until the worker picks the run up.
12
+
13
+ Cancelling a RUNNING dynamic mission does not stop it instantly. The response
14
+ keeps status "running" and sets `cancel_requested_at`; the worker stops at its
15
+ next integration window, at most 20 seconds of mission time. What comes back is
16
+ a real partial result: `run_meta.partial` is true, `run_meta.canceled_at_t_s`
17
+ gives the mission time it stopped at, and the credits are refunded.
18
+
19
+ This mission runs to depletion, so `fraction` is an estimate rather than an
20
+ exact ratio.
21
+ """
22
+
23
+ import time
24
+
25
+ from thrustlab import Client
26
+
27
+ client = Client() # reads $THRUSTLAB_API_KEY from the environment
28
+
29
+ project = client.projects.create(name="sdk dynamic progress example")
30
+
31
+ motor = client.components.find(name="BadAss 2826-820Kv")
32
+ prop = client.components.find(name="10.5x4.5")
33
+ battery = client.components.find(
34
+ name="Liperior 5000mAh 4S 35C 14.8V Lipo Battery With XT90 Plug"
35
+ )
36
+
37
+ # A hover flown until the pack is empty.
38
+ #
39
+ # The schedule steps to 15% before it ramps, rather than ramping up from rest.
40
+ # One external study observed ramps starting at near-zero duty producing a
41
+ # handful of non-converged rows in their first milliseconds; as a fraction of a
42
+ # short flight's rows that was enough to get a run rejected with
43
+ # `solver_no_convergence`. Stepping to a working throttle first avoided it.
44
+ dyn = client.dynamic_simulations.create(
45
+ project_id=project["id"],
46
+ battery_component_id=battery["id"],
47
+ density_kg_m3=1.225,
48
+ battery_charge_pct=100,
49
+ ambient_temp_c=25,
50
+ rotor_groups=[
51
+ {
52
+ "label": "main",
53
+ "count": 4,
54
+ "motor_component_id": motor["id"],
55
+ "propeller_component_id": prop["id"],
56
+ }
57
+ ],
58
+ schedule={
59
+ "mode": "segments",
60
+ "segments": [
61
+ # Step to the ESC's working range first.
62
+ {
63
+ "duration_s": 0.2,
64
+ "airspeed_target": 0.0,
65
+ "per_group": {"main": {"throttle_target": 15, "throttle_ramp": "step"}},
66
+ },
67
+ # Then ramp from there to the hover setting.
68
+ {
69
+ "duration_s": 2.0,
70
+ "airspeed_target": 0.0,
71
+ "per_group": {"main": {"throttle_target": 65, "throttle_ramp": "linear"}},
72
+ },
73
+ # Hold until the pack is empty. `until_depleted` takes no duration.
74
+ {
75
+ "until_depleted": True,
76
+ "airspeed_target": 0.0,
77
+ "per_group": {"main": {"throttle_target": 65}},
78
+ },
79
+ ],
80
+ },
81
+ # The total mission time is not known in advance, so progress["fraction"]
82
+ # is an estimate rather than an exact ratio.
83
+ termination={"mode": "until_depleted", "soc_cutoff_pct": 20.0},
84
+ )
85
+ print(f"created {dyn['id']}, status={dyn['status']}")
86
+
87
+ # Watch progress. 2-5 s is the right cadence; anything faster spends requests
88
+ # on a value the worker only rewrites once per integration window.
89
+ CANCEL_AFTER_S = 45.0
90
+ deadline = time.monotonic() + CANCEL_AFTER_S
91
+
92
+ while time.monotonic() < deadline:
93
+ time.sleep(3.0)
94
+ run = client.dynamic_simulations.retrieve(dyn["id"])
95
+ if run["status"] not in ("queued", "running"):
96
+ break
97
+ # `progress` is None until the worker picks the run up.
98
+ p = run.get("progress")
99
+ if p is None:
100
+ print(f" {run['status']}, no progress yet")
101
+ continue
102
+ frac = p.get("fraction")
103
+ pct = f"{frac * 100:5.1f}%" if frac is not None else " ? %"
104
+ print(f" {pct} sim {p['sim_time_s']:.1f} s wall {p['wall_s']:.1f} s")
105
+
106
+ run = client.dynamic_simulations.retrieve(dyn["id"])
107
+ if run["status"] in ("queued", "running"):
108
+ # Cancel whatever state the deadline caught it in. A queued run comes back
109
+ # `canceled` immediately, with the compute units refunded and no result. A
110
+ # running one comes back still `running` with cancel_requested_at set, and
111
+ # the worker stops at its next integration window.
112
+ #
113
+ # Cancelling both matters here: with every execution slot busy this run can
114
+ # still be queued at the deadline, and skipping it would leave it to
115
+ # execute later against a script that has already exited.
116
+ accepted = client.dynamic_simulations.cancel(dyn["id"])
117
+ print(f"\ncancel accepted: status={accepted['status']}, "
118
+ f"cancel_requested_at={accepted.get('cancel_requested_at')}")
119
+ run = client.dynamic_simulations.wait(dyn["id"], timeout=300)
120
+
121
+ print(f"final status: {run['status']}")
122
+
123
+ blob = run.get("result")
124
+ if blob:
125
+ meta = blob["run_meta"]
126
+ if meta.get("partial"):
127
+ print(f"partial result, stopped at t = {meta['canceled_at_t_s']:.1f} s of mission time")
128
+ sc = blob["scorecard"]
129
+ print(f"flight time: {sc['flight_time_s']:.1f} s")
130
+ print(f"energy: {sc['energy_wh']:.1f} Wh")
131
+ print(f"peak winding: {sc['peak_winding_temp_c']:.1f} C")
132
+ print(f"samples returned: {len(blob['samples'])}")
@@ -22,7 +22,7 @@ state of charge — all from the `.fmu` you exported.
22
22
  ```sh
23
23
  mkdir -p mission_out
24
24
  python -m thrustlab.sitl \
25
- --fmu my-powertrain.fmu --vehicle quad_x.yaml \
25
+ --fmu ../validation-quad.fmu --vehicle quad_x.yaml \
26
26
  --rpm-log mission_out/mission_rpm_log.csv
27
27
  ```
28
28
 
@@ -78,3 +78,12 @@ RPM lane — so the bridge logs it to `mission_out/mission_rpm_log.csv` for
78
78
  reconciliation against flight logs. If you changed the vehicle mass in
79
79
  `quad_x.yaml`, set `WEIGHT_N` at the top of `plot_mission.py` to match — it
80
80
  aligns the two recordings' clocks on the takeoff event.
81
+
82
+ ## The bundled FMU
83
+
84
+ This example ships with a ready-to-fly FMU exported from ThrustLab
85
+ (`../validation-quad.fmu`). The exact powertrain it reproduces is documented
86
+ in the archive's own `resources/README.md`; unzip the FMU to read it. To
87
+ recreate the FMU, build the same powertrain in ThrustLab or through the
88
+ API/SDK, then export it with **Export → FMU** or `client.fmu`; your export
89
+ will fly these examples identically.
@@ -31,7 +31,7 @@ Everything the quad example needs (`../ardupilot_sitl/README.md`), plus:
31
31
  ```sh
32
32
  mkdir -p mission_out
33
33
  python -m thrustlab.sitl \
34
- --fmu my-tri-powertrain.fmu --vehicle tilttri.yaml \
34
+ --fmu example-tri.fmu --vehicle tilttri.yaml \
35
35
  --rpm-log mission_out/mission_rpm_log.csv
36
36
  ```
37
37
 
@@ -74,3 +74,12 @@ physics. And a stopped rotor's flag means nothing: the rear rotor is
74
74
  deliberately stopped for most of the circuit while the wing carries the
75
75
  weight, so judge the envelope over spinning rotors only — the replay video
76
76
  does exactly that.
77
+
78
+ ## The bundled FMU
79
+
80
+ This example ships with a ready-to-fly FMU exported from ThrustLab
81
+ (`example-tri.fmu`). The exact powertrain it reproduces is documented in the
82
+ archive's own `resources/README.md`; unzip the FMU to read it. To recreate
83
+ the FMU, build the same powertrain in ThrustLab or through the API/SDK, then
84
+ export it with **Export → FMU** or `client.fmu`; your export will fly these
85
+ examples identically.
@@ -22,7 +22,7 @@ Same three terminals as the quad example, swapping in this directory's files:
22
22
  ```sh
23
23
  mkdir -p mission_out
24
24
  python -m thrustlab.sitl \
25
- --fmu my-y6-powertrain.fmu --vehicle y6.yaml \
25
+ --fmu example-y6.fmu --vehicle y6.yaml \
26
26
  --rpm-log mission_out/mission_rpm_log.csv
27
27
  ```
28
28
 
@@ -32,15 +32,19 @@ python -m thrustlab.sitl \
32
32
  ```
33
33
 
34
34
  ```sh
35
- python ../ardupilot_sitl/fly_mission.py --out-dir ./mission_out --speed-scale 0.3
35
+ python ../ardupilot_sitl/fly_mission.py --out-dir ./mission_out --speed-scale 0.42
36
36
  ```
37
37
 
38
- `--speed-scale 0.3` matters here: a coax lane's sampled envelope is tighter
39
- than a solo rotor's map — edgewise speed caps near 5 m/s at this scale and
40
- descent states are not sampled at all — so the full-speed mission spends
41
- most of its time flagged `all_in_envelope=false` (outputs clamp to the
42
- envelope edge instead of being measured). Scaled down, the flight stays on
43
- sampled physics except for brief descents.
38
+ `--speed-scale 0.42` uses the bundled FMU's wider sampled coax envelope: at
39
+ hover rotor speed, it covers level edgewise flight to about 7 m/s, descent to
40
+ 3 m/s, and gentle approach legs (about 1.5 m/s down) to about 4.5 m/s
41
+ edgewise. The entire 397-second demo mission stays on measured physics, with
42
+ 99.2% of armed frames `all_in_envelope`; the remainder are brief arm/disarm
43
+ spool transients. The binding limit is the slowest rotor because the edgewise
44
+ axis scales with rotor speed, advance-ratio style: in forward flight the
45
+ unloaded rear-lower rotor reaches the envelope edge near 6.3 m/s, while
46
+ loaded rotors remain covered to about 7.3 m/s. Beyond the envelope, outputs
47
+ clamp to its edge and set `all_in_envelope=false` rather than extrapolating.
44
48
 
45
49
  ## The plots
46
50
 
@@ -60,3 +64,12 @@ python plot_coax_split.py ./mission_out ./mission_out/plots
60
64
  Its bottom panel is the point of the whole example: the lower rotor's share
61
65
  of each stack's thrust sits below 50% for the entire flight — the wake
62
66
  coupling, resolved per rotor, in a model the autopilot drives at 400 Hz.
67
+
68
+ ## The bundled FMU
69
+
70
+ This example ships with a ready-to-fly FMU exported from ThrustLab
71
+ (`example-y6.fmu`). The exact powertrain it reproduces is documented in the
72
+ archive's own `resources/README.md`; unzip the FMU to read it. To recreate
73
+ the FMU, build the same powertrain in ThrustLab or through the API/SDK, then
74
+ export it with **Export → FMU** or `client.fmu`; your export will fly these
75
+ examples identically.
@@ -81,7 +81,7 @@ ls eeprom.bin
81
81
  ```bash
82
82
  python -m thrustlab.sitl \
83
83
  --target betaflight \
84
- --fmu ~/validation-quad.fmu \
84
+ --fmu ../validation-quad.fmu \
85
85
  --vehicle betaflight_quad_x.yaml \
86
86
  --rpm-log mission_rpm_log.csv \
87
87
  --truth-port 9005 \
@@ -240,3 +240,12 @@ servo channels at all. The error reports the received size.
240
240
  - Betaflight master provides waypoint navigation through `BOX_AUTOPILOT` and
241
241
  `waypoint insert` in the CLI. The bridge supplies the required data, but this
242
242
  example does not test it.
243
+
244
+ ## The bundled FMU
245
+
246
+ This example ships with a ready-to-fly FMU exported from ThrustLab
247
+ (`../validation-quad.fmu`). The exact powertrain it reproduces is documented
248
+ in the archive's own `resources/README.md`; unzip the FMU to read it. To
249
+ recreate the FMU, build the same powertrain in ThrustLab or through the
250
+ API/SDK, then export it with **Export → FMU** or `client.fmu`; your export
251
+ will fly these examples identically.
@@ -16,7 +16,7 @@ of extrapolated.
16
16
 
17
17
  ```sh
18
18
  pip install fmpy numpy matplotlib
19
- python hover_endurance.py my-powertrain.fmu --mass-kg 1.20
19
+ python hover_endurance.py ../validation-quad.fmu --mass-kg 1.20
20
20
  ```
21
21
 
22
22
  `--mass-kg` is the all-up mass WITHOUT payload (airframe + battery).
@@ -29,3 +29,12 @@ each flight — the spread between the two is the battery sag made visible.
29
29
 
30
30
  Export an FMU with `examples/fmu/export_and_download.py` or the dashboard's
31
31
  Share menu.
32
+
33
+ ## The bundled FMU
34
+
35
+ This example ships with a ready-to-fly FMU exported from ThrustLab
36
+ (`../validation-quad.fmu`). The exact powertrain it reproduces is documented
37
+ in the archive's own `resources/README.md`; unzip the FMU to read it. To
38
+ recreate the FMU, build the same powertrain in ThrustLab or through the
39
+ API/SDK, then export it with **Export → FMU** or `client.fmu`; your export
40
+ will fly these examples identically.
@@ -7,7 +7,7 @@ links — spin-up dynamics, battery state and envelope flags included.
7
7
 
8
8
  ## What you need
9
9
 
10
- - An exported FMU, **unzipped** (`unzip my-powertrain.fmu -d my-powertrain/`).
10
+ - An exported FMU, **unzipped** (`unzip ../validation-quad.fmu -d validation-quad/`).
11
11
  - The `fmu-gz-plugin` ThrustlabFmuSystem plugin, built against gz-sim ≥ 9
12
12
  (see its README for the CMake build; it dlopens the FMU's own
13
13
  `binaries/x86_64-linux/*.so`).
@@ -61,3 +61,12 @@ For an ArduPilot- or PX4-controlled Gazebo vehicle, keep their standard
61
61
  Gazebo integration for control surfaces and use this plugin as the
62
62
  propulsion + battery physics; the ArduPilot JSON bridge
63
63
  (`thrustlab.sitl`) is the lighter path when you don't need a 3D world.
64
+
65
+ ## The bundled FMU
66
+
67
+ This example ships with a ready-to-fly FMU exported from ThrustLab
68
+ (`../validation-quad.fmu`). The exact powertrain it reproduces is documented
69
+ in the archive's own `resources/README.md`; unzip the FMU to read it. To
70
+ recreate the FMU, build the same powertrain in ThrustLab or through the
71
+ API/SDK, then export it with **Export → FMU** or `client.fmu`; your export
72
+ will fly these examples identically.
@@ -0,0 +1,2 @@
1
+ slprj/
2
+ *.slxc
@@ -0,0 +1,53 @@
1
+ # Simulink co-simulation
2
+
3
+ `run_fmu_simulink.m` builds a Simulink model programmatically, imports the
4
+ bundled quadcopter FMU, commands a fixed throttle on every rotor, and runs
5
+ FMI 3.0 Co-Simulation. It plots thrust, bus voltage, and battery state of
6
+ charge to `fmu_cosim.png`.
7
+
8
+ ![Thrust, bus voltage, and battery state of charge](fmu_cosim.png)
9
+
10
+ ## Requirements
11
+
12
+ MATLAB + Simulink, verified on R2025a. No toolboxes beyond Simulink are
13
+ required.
14
+
15
+ ## Run it
16
+
17
+ From this folder:
18
+
19
+ ```sh
20
+ matlab -batch "run('run_fmu_simulink.m')"
21
+ ```
22
+
23
+ Or open `run_fmu_simulink.m` in MATLAB and run it interactively.
24
+
25
+ ## FMU import gotchas
26
+
27
+ Three things that will silently ruin the model if changed:
28
+
29
+ - The FMU Import block lives in the Simulink Extras library
30
+ (`'simulink_extras/FMU Import/FMU'`), NOT in User-Defined Functions.
31
+ - The block wants a BARE file name with the FMU's folder on the MATLAB
32
+ path — a full path in `FMUName` is not accepted.
33
+ - Every FMU output you care about must be wired to a root Outport;
34
+ unconnected outputs are removed by block reduction and the simulation
35
+ proves nothing about them.
36
+
37
+ Port order follows the FMU's `modelDescription.xml` declaration order and is
38
+ the same for every ThrustLab export regardless of rotor count. The full
39
+ input and output list is in the script header.
40
+
41
+ ## Use your own FMU
42
+
43
+ Export an FMU from a completed simulation through **Share → Export FMU** or
44
+ `client.fmu`. Put its folder on the MATLAB path, then set `FMU_FILE` to the
45
+ bare archive name and `N_ROTORS` to its rotor count in
46
+ `run_fmu_simulink.m`.
47
+
48
+ ## The bundled FMU
49
+
50
+ This example ships with a quadcopter FMU exported from ThrustLab
51
+ (`../validation-quad.fmu`). The exact powertrain it reproduces is documented
52
+ in the archive's own `resources/README.md`; unzip the FMU to read it. An
53
+ equivalent export will fly this example identically.
@@ -0,0 +1,93 @@
1
+ % run_fmu_simulink.m — co-simulate a ThrustLab FMI 3.0 FMU in Simulink.
2
+ %
3
+ % Builds a model programmatically, imports the bundled quadcopter FMU,
4
+ % commands a fixed throttle on every rotor, runs 30 s of co-simulation, and
5
+ % plots thrust, bus voltage, and battery state of charge.
6
+ %
7
+ % Requires MATLAB + Simulink (verified on R2025a). Run from this folder:
8
+ %
9
+ % matlab -batch "run('run_fmu_simulink.m')"
10
+ %
11
+ % Three things that will silently ruin the model if changed (all measured):
12
+ % * The FMU Import block lives in the Simulink Extras library
13
+ % ('simulink_extras/FMU Import/FMU'), NOT in User-Defined Functions.
14
+ % * The block wants a BARE file name with the FMU's folder on the MATLAB
15
+ % path — a full path in FMUName is not accepted.
16
+ % * Every FMU output you care about must be wired to a root Outport;
17
+ % unconnected outputs are removed by block reduction and the simulation
18
+ % proves nothing about them.
19
+ %
20
+ % Port order follows the FMU's modelDescription.xml declaration order and is
21
+ % the same for every ThrustLab export regardless of rotor count:
22
+ % inputs : 1 throttle[N] 2 v_axial_m_s[N] 3 air_density_kg_m3
23
+ % 4 v_edge_m_s[N] 5 ambient_temp_C 6 v_body_m_s
24
+ % 7 omega_body_rad_s 8 tilt_rad[N] 9 wind_m_s
25
+ % outputs: 1 thrust_N[N] 2 torque_Nm[N] 3 force_inplane_N[N] 4 rpm[N]
26
+ % 5 current_A[N] 6 motor_temp_C[N] 7 voltage_bus_V
27
+ % 8 current_bus_A 9 power_bus_W 10 battery_soc
28
+ % 11 battery_temp_C 12 force_total_N 13 moment_total_Nm
29
+ % 14 sub_steps (+ per-rotor envelope flags and the rotor manifest)
30
+
31
+ FMU_FILE = 'validation-quad.fmu'; % bundled one folder up
32
+ N_ROTORS = 4; % rotor count of THIS FMU
33
+ THROTTLE = 0.65; % commanded on every rotor, 0..1
34
+ STOP_S = 30;
35
+
36
+ here = fileparts(mfilename('fullpath'));
37
+ addpath(fullfile(here, '..')); % FMU block needs the folder on the path
38
+
39
+ mdl = 'tl_fmu_cosim';
40
+ try close_system(mdl, 0); catch, end
41
+ new_system(mdl);
42
+ blk = [mdl '/powertrain'];
43
+ load_system('simulink_extras');
44
+ add_block('simulink_extras/FMU Import/FMU', blk);
45
+ set_param(blk, 'FMUName', FMU_FILE);
46
+ ph = get_param(blk, 'PortHandles');
47
+ fprintf('FMU ports: %d in, %d out\n', numel(ph.Inport), numel(ph.Outport));
48
+
49
+ % Drive throttle (input 1, an N-wide array) and air density (input 3).
50
+ add_block('simulink/Sources/Constant', [mdl '/throttle'], ...
51
+ 'Value', sprintf('%g*ones(1,%d)', THROTTLE, N_ROTORS));
52
+ add_block('simulink/Sources/Constant', [mdl '/rho'], 'Value', '1.225');
53
+ th = get_param([mdl '/throttle'], 'PortHandles');
54
+ rh = get_param([mdl '/rho'], 'PortHandles');
55
+ add_line(mdl, th.Outport(1), ph.Inport(1));
56
+ add_line(mdl, rh.Outport(1), ph.Inport(3));
57
+
58
+ % Root Outports on every FMU output (see the block-reduction note above).
59
+ for p = 1:numel(ph.Outport)
60
+ op = sprintf('%s/out%d', mdl, p);
61
+ add_block('simulink/Sinks/Out1', op);
62
+ oph = get_param(op, 'PortHandles');
63
+ add_line(mdl, ph.Outport(p), oph.Inport(1));
64
+ end
65
+
66
+ set_param(mdl, 'StopTime', num2str(STOP_S), ...
67
+ 'SolverType', 'Fixed-step', 'FixedStep', '0.01');
68
+ out = sim(mdl);
69
+ y = out.yout;
70
+
71
+ thrust = y{1}.Values; % thrust_N, N columns
72
+ voltage = y{7}.Values; % voltage_bus_V
73
+ soc = y{10}.Values; % battery_soc
74
+
75
+ fprintf('final per-rotor thrust [N]: %s\n', mat2str(thrust.Data(end, :), 5));
76
+ fprintf('final bus voltage [V]: %.3f\n', voltage.Data(end));
77
+ fprintf('final state of charge: %.4f\n', soc.Data(end));
78
+
79
+ fig = figure('Visible', 'off', 'Position', [0 0 900 700]);
80
+ subplot(3, 1, 1);
81
+ plot(thrust.Time, thrust.Data); grid on;
82
+ ylabel('thrust [N]'); title(sprintf('%s at %.0f%% throttle', FMU_FILE, 100 * THROTTLE));
83
+ subplot(3, 1, 2);
84
+ plot(voltage.Time, voltage.Data); grid on;
85
+ ylabel('bus voltage [V]');
86
+ subplot(3, 1, 3);
87
+ plot(soc.Time, soc.Data); grid on;
88
+ ylabel('state of charge'); xlabel('time [s]');
89
+ saveas(fig, fullfile(here, 'fmu_cosim.png'));
90
+ fprintf('wrote %s\n', fullfile(here, 'fmu_cosim.png'));
91
+
92
+ close_system(mdl, 0);
93
+ disp('FMU_COSIM_DONE');