-
Notifications
You must be signed in to change notification settings - Fork 2
feat: preliminary multicopter plugin #17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d71871a
80d1b0e
e928ac2
693a8a6
3860bcf
1f76b4c
cf5949d
ab7ea58
8bfabe3
13a17b7
fdc2f95
e850bfd
79e6429
3c3cfe3
bd0d0b7
1e0072c
25c1a8a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| # Flying multicopters | ||
|
|
||
| The `MULTICOPTER` plugin (in `packages/minisky-multicopter/`) makes MiniSky fly | ||
| small electric multirotors — DJI MAVIC/M600-class camera drones and | ||
| Amazon/Matternet-style delivery drones — with multicopter behaviour that the | ||
| fixed-wing core cannot express: | ||
|
|
||
| - **Hover and yaw at zero speed.** A multicopter can stop (`SPD 0`), hold | ||
| position, and rotate its nose in place at a yaw-rate limit instead of the | ||
| bank-angle turn rate (which degenerates as speed approaches zero). | ||
| - **Velocity decoupled from heading.** The direction of travel follows the | ||
| *track* commanded by LNAV or conflict resolution, while the nose points | ||
| wherever `YAW` put it — the aircraft can strafe, and course changes at | ||
| waypoints are thrust redirections with no turn radius. | ||
| - **Electric performance.** Thrust-based power draw, a battery state of | ||
| charge, and an envelope that tightens when the battery runs low. | ||
|
|
||
| Fixed-wing aircraft in the same simulation are untouched, and helicopters are | ||
| deliberately out of scope (the `EC35` keeps its default envelope-only | ||
| behaviour). | ||
|
|
||
| ## Loading the plugin | ||
|
|
||
| Add an (empty) plugin table to your [config file](configuration.md): | ||
|
|
||
| ```toml | ||
| [plugins.multicopter] | ||
| ``` | ||
|
|
||
| or load it at runtime — from a scenario or the console with | ||
| `PLUGINS LOAD MULTICOPTER`, or from Python with | ||
| `await runtime.plugins.load("MULTICOPTER")`. | ||
|
|
||
| On the first simulation step after loading, the plugin selects its | ||
| implementations of five [replaceable traffic components](../architecture.md#replaceable-components) | ||
| (kinematics, pilot logic, autopilot, waypoint capture, and performance), and | ||
| re-selects them after every `RESET`. A manual `SELECTIMPL` afterwards is | ||
| respected until the next reset. | ||
|
|
||
| ## Which aircraft count as multicopters | ||
|
|
||
| Membership follows the aircraft type: creating any of | ||
|
|
||
| ``` | ||
| MAVIC PHAN4 M100 M200 M600 MNET AMZN HORSEFLY | ||
| ``` | ||
|
|
||
| produces a multicopter. Everything else — including the `EC35` helicopter — | ||
| keeps stock behaviour. Query or override per aircraft with `MCOPT`: | ||
|
|
||
| ``` | ||
| MCOPT DRONE1 -> MCOPT DRONE1: ON | ||
| MCOPT DRONE1 OFF -> back to fixed-wing kinematics | ||
| ``` | ||
|
|
||
| ## Commands | ||
|
|
||
| | Command | What it does | | ||
| | --- | --- | | ||
| | `MCOPT acid [ON/OFF]` | Query or set whether an aircraft is flown as a multicopter | | ||
| | `YAW acid hdg` | Point the nose at a body heading; the velocity vector is unaffected | | ||
| | `YAWRATE acid [rate]` | Query or set the maximum yaw rate (default 90 deg/s) | | ||
| | `HOVER acid [time] [alt]` | Hold position — optionally for `time` seconds, optionally moving vertically to `alt` | | ||
| | `BATT acid` | Report battery state of charge, power draw, and endurance | | ||
|
|
||
| By default the nose follows the direction of travel, like any aircraft. The | ||
| first `YAW` decouples them: the nose stays where you put it while LNAV, | ||
| conflict resolution, and waypoint corners steer the velocity vector | ||
| underneath. For a multicopter, `HDG` is an alias of `YAW` — there is no | ||
| command that couples heading back to track short of `MCOPT acid OFF`. | ||
|
|
||
| `HOVER` is composable rather than a scripted manoeuvre: | ||
|
|
||
| - `HOVER DRONE1` brakes to a stop and holds position until `LNAV DRONE1 ON` | ||
| resumes the route. | ||
| - `HOVER DRONE1 30` holds for 30 seconds — counted while actually stopped at | ||
| the selected altitude — then restores the saved LNAV/VNAV/speed state. | ||
| - `HOVER DRONE1 30 200` also moves vertically to 200 ft while holding | ||
| position; the route resumes at the hover altitude. | ||
| - Re-issuing `HOVER` while hovering updates the hold time and altitude, and a | ||
| plain `ALT` changes the hover altitude too. | ||
|
|
||
| A delivery profile is therefore just scenario vocabulary: fly a route, `HOVER` | ||
| over the drop point at a descent altitude, climb back with `ALT`, `LNAV ON` | ||
| to fly home. See `packages/minisky-multicopter/scenarios/multicopter_delivery.scn` | ||
| for a complete example: | ||
|
|
||
| ```bash | ||
| uv run minisky run --scenario ../minisky-multicopter/scenarios/multicopter_delivery.scn --speed 10 | ||
| ``` | ||
|
|
||
| (scenario paths resolve relative to the `packages/minisky/` package root). | ||
|
|
||
| Multicopters fly point-to-point: their waypoints default to fly-over with a | ||
| fixed 10 m capture radius, and `SPD 0` is valid (the rotor envelopes have a | ||
| negative minimum speed). One thing to keep in mind: LNAV commands only the | ||
| track, so a multicopter created at rest also needs a speed source — a `SPD` | ||
| command or waypoint speed constraints — before it starts moving. | ||
|
|
||
| ## The battery model | ||
|
|
||
| Multicopter rows replace fuel flow with an electric model, updated every | ||
| simulation step: | ||
|
|
||
| - **Required thrust** supports the weight and overcomes flat-plate parasite | ||
| drag: `T = hypot(m * sqrt(g^2 + az^2), 0.5 * rho * v^2 * CdS)`. | ||
| - **Electrical power** follows a momentum-theory scaling anchored to the | ||
| installed power from the OpenAP rotor coefficients | ||
| (`P = P_max * (T / T_max)^1.5`, with `T_max = TWR * m * g` and a default | ||
| thrust-to-weight ratio of 2). | ||
| - **State of charge** integrates that power against a usable pack energy — | ||
| an ideal energy tank, with no terminal-voltage or current modelling. | ||
| - **Envelope feedback**: below 20% charge the maximum speed shrinks to 60% | ||
| and the maximum climb rate to 50%. Descent stays unrestricted — a low | ||
| battery should not keep an aircraft airborne. | ||
|
|
||
| `BATT` reports the live state: | ||
|
|
||
| ``` | ||
| BATT DRONE1 -> BATT DRONE1: 50%, drawing 1022 W, endurance 18 min | ||
| ``` | ||
|
|
||
| The absolute forward-flight power is approximate (momentum-theory shape, not | ||
| measured propeller data), but hover figures and the qualitative trends are | ||
| sound — hover endurance for the MAVIC comes out around 28 minutes. A | ||
| measured-data upgrade path is sketched in the plan document | ||
| (`docs/multicopter-plan.md`). | ||
|
amorfinv marked this conversation as resolved.
|
||
|
|
||
| ## Adding a new multicopter type | ||
|
|
||
| Three places, all keyed on the ICAO-style typecode: | ||
|
|
||
| 1. **Performance data** — add a rotor entry to | ||
| `packages/minisky/minisky/data/performance/openap/rotor/aircraft.json` | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To make plugins truly self contained we should allow users to configure their own multicopter parameters in their cache directory (the core already has |
||
| with the masses (`oew`, `mtow`, in kg), `n_engines`, per-engine power | ||
| (`engines`, in kW), and the flight envelope (`v_min`/`v_max`, | ||
| `vs_min`/`vs_max`, `h_max`, `d_range_max`). Give `v_min` a negative value | ||
| so the aircraft may stop. | ||
| 2. **Membership** — add the typecode to `MULTICOPTER_TYPES` in | ||
| `minisky_multicopter/entity.py`. | ||
| 3. **Battery capacity** — add a `CONSTANTS` entry in | ||
| `minisky_multicopter/perf.py` with the usable pack energy `battery_wh` | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Code should be closed for modification, so we should not encourage users to modify the source code just to add a new multicopter entry. This is especially true when we decide to publish things on PyPI. Can you make the plugin read a TOML configuration file (which stores all of the multicopter performance data) from the user cache dir, defining Pydantic shapes, and load/validate that on plugin startup? |
||
| (the one datum the rotor `aircraft.json` cannot carry — its `mfc` fuel | ||
| field is unused for electric types), plus optional `cds` (flat-plate drag | ||
| area, m²) and `twr` (thrust-to-weight ratio) overrides. Without an entry | ||
| the pack energy is derived from the envelope's `d_range_max` flown at | ||
| cruise speed, like the delivery-drone types that have no public pack spec. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| [project] | ||
| name = "minisky-multicopter" | ||
| version = "0.1.0" | ||
| description = "MiniSky plugin flying small electric multirotors with decoupled track and heading" | ||
| readme = { text = "A MiniSky plugin for simulating small electric multirotors.", content-type = "text/markdown" } | ||
| requires-python = ">=3.11" | ||
| dependencies = ["minisky>=0.1.0", "numpy>=2.2.2"] | ||
| classifiers = ["Private :: Do Not Upload"] | ||
|
|
||
| [build-system] | ||
| requires = ["hatchling"] | ||
| build-backend = "hatchling.build" | ||
|
|
||
| [tool.hatch.build.targets.wheel] | ||
| packages = ["src/minisky_multicopter"] | ||
|
|
||
| [tool.uv.sources] | ||
| minisky = { workspace = true } | ||
|
|
||
| [project.entry-points."minisky.plugins"] | ||
| multicopter = "minisky_multicopter:plugin" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| # Multicopter delivery mission: an M600 takes off vertically, flies a package | ||
| # to a drop point, hovers down for the delivery, climbs back up vertically and | ||
| # returns home — exercising the MULTICOPTER plugin | ||
| # (packages/minisky-multicopter): decoupled yaw, the composable HOVER | ||
| # primitive, and the battery model. | ||
| 00:00:00.00>PLUGINS LOAD MULTICOPTER | ||
|
|
||
| # The drop point, 1.4 km east of home. | ||
| 00:00:01.00>DEFWPT HOME 52.0000 4.0000 | ||
| 00:00:01.00>DEFWPT DROP 52.0000 4.0200 | ||
|
|
||
| # Create the drone on the ground at home, stationary. M600 is a multicopter | ||
| # typecode, so MCOPT reports ON without being set. | ||
| 00:00:02.00>CRE PH1 M600 52.0 4.0 90 0 0 | ||
| 00:00:03.00>MCOPT PH1 | ||
|
|
||
| # Out and back. Multicopter waypoints are fly-over with a fixed capture | ||
| # radius: the course change at DROP is a thrust redirection, no turn arc. | ||
| 00:00:03.00>ADDWPT PH1 DROP | ||
| 00:00:03.00>ADDWPT PH1 HOME | ||
|
|
||
| # The delivery, composed on the waypoint stack: when PH1 reaches DROP, | ||
| # hold position at 50 ft, and yaw the camera south — the nose stays there | ||
| # for the rest of the flight, so the return leg is flown strafing (track | ||
| # west, nose south). The 45 s is a guard that never expires: the timed | ||
| # climb-out below re-issues the hover after 30 s of hold. | ||
| 00:00:03.00>AT PH1 DROP DO PH1 HOVER 45 50 | ||
| 00:00:03.00>AT PH1 DROP DO PH1 YAW 180 | ||
| # When PH1 arrives back at HOME, stop and hover indefinitely. | ||
| 00:00:03.00>AT PH1 HOME DO PH1 HOVER | ||
|
|
||
| # Vertical takeoff: climb straight up to 100 ft and hold 5 s. A hover that | ||
| # started with the route disengaged does not engage it on expiry, so the | ||
| # departure below is a separate timed command. | ||
| 00:00:04.00>HOVER PH1 5 100 | ||
|
|
||
| # Depart eastbound once the takeoff hover has expired. | ||
| 00:00:20.00>SPD PH1 20 | ||
| 00:00:20.00>LNAV PH1 ON | ||
|
|
||
| # Enroute to the drop point. | ||
| 00:01:00.00>POS PH1 | ||
| 00:01:00.00>BATT PH1 | ||
|
|
||
| # Mid-delivery: stopped over DROP at 50 ft, nose south. | ||
| 00:03:00.00>POS PH1 | ||
| 00:03:00.00>BATT PH1 | ||
|
|
||
| # Climb out of the delivery: re-issuing the hover while it is still held | ||
| # keeps the position pinned and sets a new target altitude — a pure | ||
| # vertical climb back to cruise altitude, a 10 s hold at the top, then the | ||
| # route resumes on its own for the return leg. | ||
| 00:03:10.00>HOVER PH1 10 100 | ||
|
|
||
| # Hovering at home with the mission complete. | ||
| 00:06:20.00>POS PH1 | ||
| 00:06:20.00>BATT PH1 | ||
| 00:06:30.00>QUIT |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| """MULTICOPTER — simulate small electric multirotors. | ||
|
|
||
| Makes MiniSky fly DJI MAVIC/M600/PHAN4-class and Amazon/Matternet-style | ||
| delivery drones with multicopter behaviour: hover and yaw at zero airspeed, | ||
| and a velocity vector decoupled from the body heading (the aircraft can | ||
| strafe — change course without rotating the nose). | ||
|
|
||
| Everything is implemented as replaceable subclasses of core entities, which | ||
| the plugin registers on load and keeps selected through its hooks (the first | ||
| simulation step after loading, and every reset): | ||
|
|
||
| - ``KINEMATICS`` -> :class:`MulticopterKinematics` — yaw-rate-limited | ||
| heading, track-driven velocity vector. | ||
| - ``APORASAS`` -> :class:`MulticopterAPorASAS` — no track-to-heading | ||
| coupling for multicopter rows. | ||
| - ``AUTOPILOT`` -> :class:`MulticopterAutopilot` — HOVER primitive, | ||
| HDG-yaws-the-nose semantics, fly-over route defaults. | ||
| - ``ACTIVEWAYPOINT`` -> :class:`MulticopterActiveWaypoint` — fixed waypoint | ||
| capture radius (the bank-angle turn distance degenerates at hover speeds). | ||
| - ``OPENAP`` -> :class:`MulticopterPerf` — electric performance: | ||
| required thrust, momentum-theory power, battery state of charge with | ||
| envelope feedback at low charge. | ||
|
|
||
| Fixed-wing aircraft in the same simulation are untouched: every override | ||
| calls ``super()`` and adjusts only the multicopter rows. Helicopters are out | ||
| of scope — membership is a typecode set, not ``LIFT_ROTOR``. | ||
|
|
||
| Stack commands: MCOPT, YAW, YAWRATE, HOVER, BATT. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from minisky import plugin as plugin_api | ||
|
|
||
| from minisky_multicopter.activewp import MulticopterActiveWaypoint | ||
| from minisky_multicopter.aporasas import MulticopterAPorASAS | ||
| from minisky_multicopter.autopilot import MulticopterAutopilot | ||
| from minisky_multicopter.entity import Multicopter, get_multicopter | ||
| from minisky_multicopter.kinematics import MulticopterKinematics | ||
| from minisky_multicopter.perf import MulticopterPerf | ||
|
|
||
| __all__ = ( | ||
| "Multicopter", | ||
| "MulticopterAPorASAS", | ||
| "MulticopterActiveWaypoint", | ||
| "MulticopterAutopilot", | ||
| "MulticopterKinematics", | ||
| "MulticopterPerf", | ||
| "get_multicopter", | ||
| "plugin", | ||
| ) | ||
|
|
||
|
|
||
| def build(context: plugin_api.PluginContext[object]) -> plugin_api.PluginSpec: | ||
| """Build the multicopter components for one MiniSky runtime. | ||
|
|
||
| Mounts the per-aircraft state entity (which also carries the stack | ||
| commands and the implementation-selection hooks) and registers the | ||
| multicopter implementations as runtime-local replacements. | ||
| """ | ||
| context.mount(Multicopter()) | ||
| return context.finish( | ||
| replacements=( | ||
| MulticopterKinematics, | ||
| MulticopterAPorASAS, | ||
| MulticopterAutopilot, | ||
| MulticopterActiveWaypoint, | ||
| MulticopterPerf, | ||
| ) | ||
| ) | ||
|
|
||
|
|
||
| plugin = plugin_api.Plugin(build=build) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should use KaTeX (I will enable the arithmatex plugin soon)
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see #45