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.
Files changed (329) hide show
  1. {thrustlab-0.3.2 → thrustlab-0.5.0}/CHANGELOG.md +123 -0
  2. {thrustlab-0.3.2 → thrustlab-0.5.0}/PKG-INFO +6 -2
  3. {thrustlab-0.3.2 → thrustlab-0.5.0}/examples/dynamic/run_and_poll.py +4 -2
  4. thrustlab-0.5.0/examples/fmu/ardupilot_sitl/README.md +80 -0
  5. thrustlab-0.5.0/examples/fmu/ardupilot_sitl/fly_mission.py +225 -0
  6. thrustlab-0.5.0/examples/fmu/ardupilot_sitl/fmu.parm +6 -0
  7. thrustlab-0.5.0/examples/fmu/ardupilot_sitl/plot_mission.py +267 -0
  8. thrustlab-0.5.0/examples/fmu/ardupilot_sitl/quad_x.yaml +62 -0
  9. thrustlab-0.5.0/examples/fmu/ardupilot_sitl/render_mission_video.py +454 -0
  10. thrustlab-0.5.0/examples/fmu/ardupilot_sitl_tilttri/README.md +76 -0
  11. thrustlab-0.5.0/examples/fmu/ardupilot_sitl_tilttri/fly_transition.py +211 -0
  12. thrustlab-0.5.0/examples/fmu/ardupilot_sitl_tilttri/tilttri.parm +30 -0
  13. thrustlab-0.5.0/examples/fmu/ardupilot_sitl_tilttri/tilttri.yaml +128 -0
  14. thrustlab-0.5.0/examples/fmu/ardupilot_sitl_y6/README.md +62 -0
  15. thrustlab-0.5.0/examples/fmu/ardupilot_sitl_y6/plot_coax_split.py +131 -0
  16. thrustlab-0.5.0/examples/fmu/ardupilot_sitl_y6/y6.parm +6 -0
  17. thrustlab-0.5.0/examples/fmu/ardupilot_sitl_y6/y6.yaml +107 -0
  18. thrustlab-0.5.0/examples/fmu/betaflight_sitl/README.md +242 -0
  19. thrustlab-0.5.0/examples/fmu/betaflight_sitl/betaflight_config.txt +41 -0
  20. thrustlab-0.5.0/examples/fmu/betaflight_sitl/betaflight_quad_x.yaml +129 -0
  21. thrustlab-0.5.0/examples/fmu/betaflight_sitl/fly_hover.py +697 -0
  22. thrustlab-0.5.0/examples/fmu/endurance/README.md +31 -0
  23. thrustlab-0.5.0/examples/fmu/endurance/hover_endurance.py +215 -0
  24. thrustlab-0.5.0/examples/fmu/export_and_download.py +80 -0
  25. thrustlab-0.5.0/examples/fmu/gazebo/README.md +63 -0
  26. thrustlab-0.5.0/examples/fmu/gazebo/quad_fmu.sdf +373 -0
  27. thrustlab-0.5.0/examples/fmu/quad_5inch_x.yaml +142 -0
  28. {thrustlab-0.3.2 → thrustlab-0.5.0}/examples/outputs/read_dynamic.py +22 -8
  29. {thrustlab-0.3.2 → thrustlab-0.5.0}/examples/outputs/read_steady_state.py +4 -2
  30. {thrustlab-0.3.2 → thrustlab-0.5.0}/examples/quickstart.py +4 -2
  31. {thrustlab-0.3.2 → thrustlab-0.5.0}/examples/simulations/run_sync.py +6 -4
  32. {thrustlab-0.3.2 → thrustlab-0.5.0}/examples/sweeps/run_and_poll.py +4 -2
  33. {thrustlab-0.3.2 → thrustlab-0.5.0}/pyproject.toml +31 -2
  34. thrustlab-0.5.0/tests/sitl/__init__.py +1 -0
  35. thrustlab-0.5.0/tests/sitl/ardupilot_parser.py +160 -0
  36. thrustlab-0.5.0/tests/sitl/betaflight_fc.py +119 -0
  37. thrustlab-0.5.0/tests/sitl/conftest.py +47 -0
  38. thrustlab-0.5.0/tests/sitl/mock_fmu.py +249 -0
  39. thrustlab-0.5.0/tests/sitl/test_betaflight_bridge.py +149 -0
  40. thrustlab-0.5.0/tests/sitl/test_betaflight_protocol.py +329 -0
  41. thrustlab-0.5.0/tests/sitl/test_kinematics.py +283 -0
  42. thrustlab-0.5.0/tests/sitl/test_protocol.py +222 -0
  43. thrustlab-0.5.0/tests/sitl/test_rigidbody.py +250 -0
  44. thrustlab-0.5.0/tests/sitl/test_state_machine.py +256 -0
  45. thrustlab-0.5.0/tests/sitl/test_step_path.py +487 -0
  46. thrustlab-0.5.0/tests/sitl/test_tilt_wing.py +315 -0
  47. thrustlab-0.5.0/tests/sitl/test_vehicle.py +273 -0
  48. {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_async_polling.py +5 -5
  49. thrustlab-0.5.0/tests/test_resources/test_fmu.py +148 -0
  50. {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_spec_coverage.py +0 -1
  51. {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_webhooks.py +1 -1
  52. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_async_polling.py +2 -2
  53. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_http.py +7 -3
  54. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/__init__.py +72 -10
  55. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/analyze_geometry_request.py +3 -1
  56. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/analyze_geometry_response.py +176 -0
  57. thrustlab-0.5.0/thrustlab/_models/analyze_geometry_response_envelope_limiting_factor_type_0.py +13 -0
  58. thrustlab-0.5.0/thrustlab/_models/analyze_geometry_response_envelope_status_type_0.py +10 -0
  59. thrustlab-0.5.0/thrustlab/_models/component_specs_response.py +101 -0
  60. thrustlab-0.5.0/thrustlab/_models/component_specs_response_spec_json.py +47 -0
  61. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/credit_balance_resource.py +17 -3
  62. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/credit_usage_event_resource.py +10 -3
  63. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/credit_usage_summary_resource.py +2 -2
  64. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/design_request.py +74 -7
  65. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/dynamic_create_body.py +127 -23
  66. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/dynamic_estimate_body.py +86 -0
  67. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/dynamic_resource.py +51 -0
  68. thrustlab-0.5.0/thrustlab/_models/dynamic_resource_battery_topology_type_0.py +47 -0
  69. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/dynamic_rotor_group_in.py +84 -5
  70. thrustlab-0.5.0/thrustlab/_models/dynamic_rotor_group_in_esc_timing.py +11 -0
  71. thrustlab-0.5.0/thrustlab/_models/dynamic_rotor_group_in_esc_type.py +9 -0
  72. thrustlab-0.5.0/thrustlab/_models/dynamic_rotor_in.py +254 -0
  73. thrustlab-0.5.0/thrustlab/_models/dynamic_rotor_in_esc_timing.py +11 -0
  74. thrustlab-0.5.0/thrustlab/_models/dynamic_rotor_in_esc_type.py +9 -0
  75. thrustlab-0.5.0/thrustlab/_models/dynamic_rotor_in_motor_cooling_source.py +10 -0
  76. thrustlab-0.5.0/thrustlab/_models/dynamic_rotor_in_rotation_sense.py +9 -0
  77. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/entitlements_resource.py +31 -2
  78. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/export_geometry_request.py +3 -2
  79. thrustlab-0.5.0/thrustlab/_models/fmu_export_accepted_response.py +82 -0
  80. thrustlab-0.5.0/thrustlab/_models/fmu_export_job_error.py +69 -0
  81. thrustlab-0.5.0/thrustlab/_models/fmu_export_status_response.py +393 -0
  82. thrustlab-0.5.0/thrustlab/_models/generate_geometry_request.py +382 -0
  83. thrustlab-0.5.0/thrustlab/_models/pack_leaf_in.py +92 -0
  84. thrustlab-0.5.0/thrustlab/_models/pack_parallel_in.py +113 -0
  85. thrustlab-0.5.0/thrustlab/_models/pack_series_in.py +104 -0
  86. thrustlab-0.5.0/thrustlab/_models/pack_topology_in.py +116 -0
  87. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/rotor_group_in.py +54 -32
  88. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/rotor_group_resource.py +27 -0
  89. thrustlab-0.5.0/thrustlab/_models/rotor_in.py +424 -0
  90. thrustlab-0.5.0/thrustlab/_models/rotor_in_esc_timing.py +11 -0
  91. thrustlab-0.5.0/thrustlab/_models/rotor_in_esc_type.py +9 -0
  92. thrustlab-0.5.0/thrustlab/_models/rotor_in_motor_cooling_source.py +10 -0
  93. thrustlab-0.5.0/thrustlab/_models/rotor_in_rotation_sense.py +9 -0
  94. thrustlab-0.5.0/thrustlab/_models/segment_group_command_in.py +119 -0
  95. 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
  96. 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
  97. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_create_body.py +83 -3
  98. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_resource.py +42 -0
  99. thrustlab-0.5.0/thrustlab/_models/simulation_resource_battery_topology_type_0.py +47 -0
  100. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_config_in.py +6 -2
  101. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_create_body.py +65 -17
  102. thrustlab-0.5.0/thrustlab/_models/sweep_point_component_selection.py +112 -0
  103. thrustlab-0.5.0/thrustlab/_models/sweep_point_inputs.py +282 -0
  104. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_point_resource.py +28 -6
  105. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_resource.py +9 -0
  106. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_rotor_group_in.py +15 -9
  107. thrustlab-0.5.0/thrustlab/_models/sweep_rotor_in.py +426 -0
  108. thrustlab-0.5.0/thrustlab/_models/sweep_rotor_in_esc_timing.py +11 -0
  109. thrustlab-0.5.0/thrustlab/_models/sweep_rotor_in_esc_type.py +9 -0
  110. thrustlab-0.5.0/thrustlab/_models/sweep_rotor_in_motor_cooling_source.py +10 -0
  111. thrustlab-0.5.0/thrustlab/_models/sweep_rotor_in_rotation_sense.py +9 -0
  112. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/user_resource.py +26 -0
  113. thrustlab-0.5.0/thrustlab/_models/user_resource_gate_required_tiers.py +47 -0
  114. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_version.py +1 -1
  115. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/client.py +3 -1
  116. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/resources/dynamic_simulations.py +6 -2
  117. thrustlab-0.5.0/thrustlab/resources/fmu.py +147 -0
  118. thrustlab-0.5.0/thrustlab/sitl/README.md +362 -0
  119. thrustlab-0.5.0/thrustlab/sitl/__init__.py +93 -0
  120. thrustlab-0.5.0/thrustlab/sitl/__main__.py +10 -0
  121. thrustlab-0.5.0/thrustlab/sitl/bridge.py +173 -0
  122. thrustlab-0.5.0/thrustlab/sitl/cli.py +217 -0
  123. thrustlab-0.5.0/thrustlab/sitl/fmu.py +494 -0
  124. thrustlab-0.5.0/thrustlab/sitl/kinematics.py +147 -0
  125. thrustlab-0.5.0/thrustlab/sitl/model.py +66 -0
  126. thrustlab-0.5.0/thrustlab/sitl/protocol.py +240 -0
  127. thrustlab-0.5.0/thrustlab/sitl/rigidbody.py +439 -0
  128. thrustlab-0.5.0/thrustlab/sitl/rpmlog.py +115 -0
  129. thrustlab-0.5.0/thrustlab/sitl/targets/__init__.py +42 -0
  130. thrustlab-0.5.0/thrustlab/sitl/targets/ardupilot.py +221 -0
  131. thrustlab-0.5.0/thrustlab/sitl/targets/base.py +292 -0
  132. thrustlab-0.5.0/thrustlab/sitl/targets/betaflight.py +714 -0
  133. thrustlab-0.5.0/thrustlab/sitl/vehicle.py +687 -0
  134. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/webhooks.py +1 -2
  135. thrustlab-0.3.2/.impeccable/hook.cache.json +0 -1
  136. thrustlab-0.3.2/Makefile +0 -24
  137. thrustlab-0.3.2/ci/public-ci.yml +0 -35
  138. thrustlab-0.3.2/scripts/publish_public_repo.sh +0 -185
  139. thrustlab-0.3.2/scripts/regen_models.py +0 -152
  140. thrustlab-0.3.2/thrustlab/_models/generate_geometry_request.py +0 -205
  141. thrustlab-0.3.2/thrustlab/_models/geometry_preview_response.py +0 -112
  142. thrustlab-0.3.2/thrustlab/_models/preview_request.py +0 -88
  143. thrustlab-0.3.2/thrustlab/_models/segment_group_command_in.py +0 -88
  144. thrustlab-0.3.2/thrustlab/_models/sweep_point_resource_inputs.py +0 -47
  145. {thrustlab-0.3.2 → thrustlab-0.5.0}/.gitignore +0 -0
  146. {thrustlab-0.3.2 → thrustlab-0.5.0}/LICENSE +0 -0
  147. {thrustlab-0.3.2 → thrustlab-0.5.0}/NOTICE +0 -0
  148. {thrustlab-0.3.2 → thrustlab-0.5.0}/README.md +0 -0
  149. {thrustlab-0.3.2 → thrustlab-0.5.0}/examples/components/create_component.py +0 -0
  150. {thrustlab-0.3.2 → thrustlab-0.5.0}/examples/projects/create.py +0 -0
  151. {thrustlab-0.3.2 → thrustlab-0.5.0}/examples/projects/list.py +0 -0
  152. {thrustlab-0.3.2 → thrustlab-0.5.0}/examples/projects/retrieve.py +0 -0
  153. {thrustlab-0.3.2 → thrustlab-0.5.0}/examples/simulations/wait.py +0 -0
  154. {thrustlab-0.3.2 → thrustlab-0.5.0}/examples/webhooks/verify_handler.py +0 -0
  155. {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/__init__.py +0 -0
  156. {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/conftest.py +0 -0
  157. {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_client.py +0 -0
  158. {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_components_create.py +0 -0
  159. {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_components_find.py +0 -0
  160. {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_dynamic_resource.py +0 -0
  161. {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_exceptions.py +0 -0
  162. {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_http.py +0 -0
  163. {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_pagination.py +0 -0
  164. {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_resources/__init__.py +0 -0
  165. {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_resources/test_components.py +0 -0
  166. {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_resources/test_compute_units.py +0 -0
  167. {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_resources/test_projects.py +0 -0
  168. {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_resources/test_simulations.py +0 -0
  169. {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_resources/test_starred_components.py +0 -0
  170. {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_resources/test_submissions.py +0 -0
  171. {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_resources/test_sweeps.py +0 -0
  172. {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_resources/test_users.py +0 -0
  173. {thrustlab-0.3.2 → thrustlab-0.5.0}/tests/test_resources/test_webhook_endpoints.py +0 -0
  174. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/__init__.py +0 -0
  175. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/airfoil_breakpoint.py +0 -0
  176. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/airfoil_list_response.py +0 -0
  177. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/airfoil_resource.py +0 -0
  178. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/analyze_geometry_request_mode.py +0 -0
  179. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/analyze_geometry_request_target_type_type_0.py +0 -0
  180. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/analyze_station_row.py +0 -0
  181. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/body_import_airfoil_v1_airfoils_import_post.py +0 -0
  182. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/calibrate_accepted_response.py +0 -0
  183. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/component_brand.py +0 -0
  184. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/component_brands_response.py +0 -0
  185. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/component_create_body.py +0 -0
  186. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/component_create_body_spec_json.py +0 -0
  187. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/component_ids_response.py +0 -0
  188. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/component_list_response.py +0 -0
  189. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/component_patch.py +0 -0
  190. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/component_patch_spec_json_type_0.py +0 -0
  191. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/component_resource.py +0 -0
  192. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/component_resource_spec_json.py +0 -0
  193. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/component_sweep_axis_in.py +0 -0
  194. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/component_sweep_axis_in_axis.py +0 -0
  195. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/component_write_response.py +0 -0
  196. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/component_write_response_spec_json.py +0 -0
  197. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/component_write_warning.py +0 -0
  198. {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
  199. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/credit_bucket_breakdown.py +0 -0
  200. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/credit_bucket_breakdown_type.py +0 -0
  201. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/credit_usage_event_resource_type.py +0 -0
  202. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/credit_usage_event_resource_unit_type.py +0 -0
  203. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/credit_usage_list_response.py +0 -0
  204. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/credit_usage_related_resource.py +0 -0
  205. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/credit_usage_related_resource_object.py +0 -0
  206. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/credit_usage_summary_resource_window.py +0 -0
  207. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/design_request_target_mode.py +0 -0
  208. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/dynamic_estimate_resource.py +0 -0
  209. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/dynamic_list_response.py +0 -0
  210. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/dynamic_patch.py +0 -0
  211. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/dynamic_resource_display_labels_type_0.py +0 -0
  212. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/dynamic_resource_error_type_0.py +0 -0
  213. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/dynamic_resource_input_snapshot_type_0.py +0 -0
  214. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/dynamic_resource_result_type_0.py +0 -0
  215. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/dynamic_resource_status.py +0 -0
  216. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/dynamic_rotor_group_in_motor_cooling_source.py +0 -0
  217. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/export_geometry_request_format.py +0 -0
  218. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/export_geometry_request_rotation_type_0.py +0 -0
  219. {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
  220. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/geometry_response.py +0 -0
  221. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/geometry_style_resource.py +0 -0
  222. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/geometry_styles_list_response.py +0 -0
  223. {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
  224. {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
  225. {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
  226. {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
  227. {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
  228. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/http_validation_error.py +0 -0
  229. {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
  230. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/list_events_v1_events_get_response_list_events_v1_events_get.py +0 -0
  231. {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
  232. {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
  233. {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
  234. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/project_create_body.py +0 -0
  235. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/project_list_response.py +0 -0
  236. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/project_patch.py +0 -0
  237. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/project_resource.py +0 -0
  238. {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
  239. {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
  240. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/rotor_group_in_esc_timing.py +0 -0
  241. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/rotor_group_in_esc_type.py +0 -0
  242. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/rotor_group_in_motor_cooling_source.py +0 -0
  243. {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
  244. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/run_selected_body.py +0 -0
  245. {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
  246. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/schedule_in.py +0 -0
  247. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/schedule_in_interpolation_type_0.py +0 -0
  248. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/schedule_in_mode.py +0 -0
  249. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/segment_in.py +0 -0
  250. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/segment_in_airspeed_ramp.py +0 -0
  251. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/segment_in_per_group.py +0 -0
  252. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/segment_in_throttle_ramp.py +0 -0
  253. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/segment_in_vertical_speed_ramp.py +0 -0
  254. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/share_resource.py +0 -0
  255. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_cancel_body.py +0 -0
  256. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_create_body_cooling_source_type_0.py +0 -0
  257. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_create_body_flight_regime.py +0 -0
  258. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_create_body_inflow_mode.py +0 -0
  259. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_create_body_launch_intent.py +0 -0
  260. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_inputs.py +0 -0
  261. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_list_response.py +0 -0
  262. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_patch.py +0 -0
  263. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_patch_plot_config_json_type_0_item.py +0 -0
  264. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_resource_display_labels_type_0.py +0 -0
  265. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_resource_error_type_0.py +0 -0
  266. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_resource_input_snapshot_type_0.py +0 -0
  267. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_resource_plot_config_json_type_0_item.py +0 -0
  268. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_resource_result_type_0.py +0 -0
  269. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_resource_result_type_0_additional_property.py +0 -0
  270. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/simulation_resource_status.py +0 -0
  271. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/spline_data.py +0 -0
  272. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/starred_component_create_body.py +0 -0
  273. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/starred_component_list_response.py +0 -0
  274. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/starred_component_resource.py +0 -0
  275. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/submission_create_body.py +0 -0
  276. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/submission_create_body_data_json.py +0 -0
  277. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/submission_list_response.py +0 -0
  278. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/submission_patch.py +0 -0
  279. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/submission_patch_data_json_type_0.py +0 -0
  280. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/submission_resource.py +0 -0
  281. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/submission_resource_data_json.py +0 -0
  282. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_cancel_body.py +0 -0
  283. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_config_in_esc_timing_values_type_0_item.py +0 -0
  284. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_create_body_cooling_source.py +0 -0
  285. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_create_body_inflow_mode.py +0 -0
  286. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_create_body_launch_intent.py +0 -0
  287. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_inputs.py +0 -0
  288. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_list_response.py +0 -0
  289. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_param_range_in.py +0 -0
  290. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_param_range_in_mode.py +0 -0
  291. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_patch.py +0 -0
  292. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_patch_plot_config_json_type_0_item.py +0 -0
  293. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_point_list_response.py +0 -0
  294. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_point_list_response_display_labels_type_0.py +0 -0
  295. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_point_patch.py +0 -0
  296. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_point_resource_rotors_type_0.py +0 -0
  297. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_point_resource_rotors_type_0_additional_property.py +0 -0
  298. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_resource_error_type_0.py +0 -0
  299. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_resource_input_snapshot_type_0.py +0 -0
  300. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_resource_plot_config_json_type_0_item.py +0 -0
  301. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_resource_status.py +0 -0
  302. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_resource_summary_type_0.py +0 -0
  303. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_resource_sweep_config_type_0.py +0 -0
  304. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_rotor_group_in_esc_timing.py +0 -0
  305. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_rotor_group_in_esc_type.py +0 -0
  306. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/sweep_rotor_group_in_motor_cooling_source.py +0 -0
  307. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/termination_in.py +0 -0
  308. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/termination_in_mode.py +0 -0
  309. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/types.py +0 -0
  310. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/user_resource_unit_system.py +0 -0
  311. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/v1_custom_component_override.py +0 -0
  312. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/v1_custom_component_override_spec_json.py +0 -0
  313. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/v1_health_v1_health_get_response_v1_health_v1_health_get.py +0 -0
  314. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/validation_error.py +0 -0
  315. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/webhook_endpoint_create.py +0 -0
  316. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_models/webhook_endpoint_patch.py +0 -0
  317. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/_pagination.py +0 -0
  318. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/exceptions.py +0 -0
  319. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/resources/__init__.py +0 -0
  320. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/resources/_base.py +0 -0
  321. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/resources/components.py +0 -0
  322. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/resources/compute_units.py +0 -0
  323. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/resources/projects.py +0 -0
  324. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/resources/simulations.py +0 -0
  325. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/resources/starred_components.py +0 -0
  326. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/resources/submissions.py +0 -0
  327. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/resources/sweeps.py +0 -0
  328. {thrustlab-0.3.2 → thrustlab-0.5.0}/thrustlab/resources/users.py +0 -0
  329. {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.2
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>=0.21; extra == 'dev'
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="Spektrum Avian 4260 800Kv")
22
+ motor = client.components.find(name="BadAss 2826-820Kv")
23
23
  prop = client.components.find(name="10.5x4.5")
24
- battery = client.components.find(name="Liperior 5000mAh 4S 35C")
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())
@@ -0,0 +1,6 @@
1
+ # Parameters the FMU bridge flight needs on top of copter.parm.
2
+ FRAME_CLASS 1
3
+ FRAME_TYPE 1
4
+ # The bridge feeds pack voltage/current back over the JSON protocol; monitor
5
+ # type 4 (analog V+I) is how the firmware reads them.
6
+ BATT_MONITOR 4