Audited the package against the two-stage procedure before a first PyPI publish. The sdist and wheel were already tight, but three things needed fixing and the controls needed to become real rather than documented. The simulator volunteered high="PI04" as its product serial, which is the real Bebop 2 serial prefix. Nothing unique to one aircraft, but a realistic prefix invites being quoted into a bug report as a specimen, so it now reads "N0TAREAL" / "0000000SIM" with a comment saying why it is nonsense on purpose. The low half and the 500.0 no-fix GPS sentinel were already fake. Hardened [tool.uv.build-backend] source-exclude well past the directories that exist today: captures at any depth, log dumps, recorded media by extension, caches, and anything credential-shaped. .gitignore governs git and source-exclude governs the sdist; a capture can sit in one and not the other, which is how this kind of data reaches an immutable index. Verified the broad patterns do not over-reach: arsdk-xml/ with PROVENANCE.md and tools/logs.py both still ship. Added LICENSE (MIT) and LICENSE-arsdk-xml (Parrot SA's BSD-3-Clause), and corrected the declared licence to "MIT AND BSD-3-Clause". The vendored XML ships in both artifacts because nothing here decodes a command without it, so MIT alone understated what is in the box. Both texts now appear in the artifacts and in the metadata. test_packaging.py grows privacy guards that fail on a serial prefix, a P7 CPU id, any MAC, a high-precision coordinate, an absolute home path, or any private address other than the drone's own documented 192.168.42.0/24. Each pattern was checked against the real identifiers to confirm it bites, since a guard that passes on an empty tree proves nothing. Example address in test_arsdk_session.py moved to RFC 5737 space. 504 tests pass, ruff clean.
96 lines
3.8 KiB
Markdown
96 lines
3.8 KiB
Markdown
# mcbebop
|
|
|
|
An MCP server for the Parrot Bebop 2, so an agent can talk to the drone without
|
|
anyone writing code for each question.
|
|
|
|
Parrot abandoned the Bebop line, but the aircraft is still a capable, cheap
|
|
platform and its protocol is fully described by Parrot's own XML, which this
|
|
package vendors. Every one of the **264 commands** is reachable; nothing is
|
|
hand-wrapped.
|
|
|
|
> **Status: in development.** Not flight-tested. See the safety section.
|
|
|
|
## What it talks to
|
|
|
|
| Interface | Notes |
|
|
|---|---|
|
|
| ARSDK3 over UDP | commands and telemetry; this package implements the protocol directly |
|
|
| ARStream2 | live H.264 video. The Bebop 2 serves no RTSP, despite what older libraries assume |
|
|
| FTP | media, flight plans, blackbox logs |
|
|
| Telnet | read-only shell, only after the drone's debug mode is enabled |
|
|
|
|
## Safety
|
|
|
|
Commands are classified by consequence. Observing and configuring are open;
|
|
anything that can spin a motor or change the flight envelope refuses until
|
|
`arm()` is called with a reason, and re-locks on a timer and on disconnect.
|
|
`Landing` and `Emergency` are deliberately never locked, because refusing to
|
|
land an airborne aircraft is the more dangerous answer.
|
|
|
|
`connect(target="sim")` runs everything against a protocol-accurate simulator,
|
|
which is where anything involving motion should be rehearsed.
|
|
|
|
## The simulator streams video
|
|
|
|
The simulator can push RTP/H.264 exactly as the aircraft does, so a viewer's
|
|
whole video path can be developed and measured without a drone. It answers the
|
|
handshake with `arstream2_server_stream_port: 5004`, sends nothing until
|
|
`ardrone3.MediaStreaming.VideoEnable` arrives with 1, then streams from its own
|
|
5004 to whatever `arstream2_client_stream_port` the client named, and stops on a
|
|
0, on a link loss, or at shutdown.
|
|
|
|
```bash
|
|
python -m mcbebop.sim --video clip.h264 # steady 30 fps
|
|
python -m mcbebop.sim --video clip.h264 --start-offset random --seed 7
|
|
python -m mcbebop.sim --video flight.rtpcap # a real capture, replayed
|
|
```
|
|
|
|
`MCBEBOP_SIM_VIDEO_SOURCE=clip.h264` does the same for `connect(target="sim")`.
|
|
|
|
Two kinds of source, and they are **different instruments**:
|
|
|
|
| Source | Pacing | Use it for |
|
|
|---|---|---|
|
|
| `.h264` Annex-B elementary stream | packetised here, steady frame rate | does the decoder work, does the renderer work |
|
|
| `.rtpcap` capture off the aircraft | the recorded inter-packet gaps, packet for packet | latency and jitter, bursts, loss behaviour |
|
|
|
|
Make the first from any video, at the resolution the aircraft streams:
|
|
|
|
```bash
|
|
ffmpeg -i anything.mp4 -t 10 -vf scale=856:480 -r 30 \
|
|
-c:v libx264 -preset ultrafast -pix_fmt yuv420p -g 30 -f h264 clip.h264
|
|
```
|
|
|
|
`-f h264` already writes Annex-B, so no bitstream filter is wanted;
|
|
`h264_mp4toannexb` converts the other direction and ffmpeg rejects it here.
|
|
|
|
Make the second from a real drone. Start the recorder first, because RTP is
|
|
connectionless and anything sent before the bind is gone, then enable video
|
|
from a session that holds the ARSDK link:
|
|
|
|
```bash
|
|
python -m mcbebop.media.capture flight.rtpcap --seconds 30 # binds 55004
|
|
```
|
|
|
|
`--start-offset random` is worth knowing about. It begins mid-GOP, which is
|
|
what a viewer switched on while the drone is already flying is handed, and
|
|
`--seed` makes a failure repeatable. Parameter sets repeat about once a second
|
|
on the packetised path, which is what lets a late joiner recover at all.
|
|
|
|
## Install
|
|
|
|
```bash
|
|
uvx mcbebop
|
|
claude mcp add mcbebop -- uvx mcbebop
|
|
```
|
|
|
|
## Licence
|
|
|
|
This package is MIT (`LICENSE`), and it vendors one third-party component:
|
|
`src/mcbebop/arsdk-xml/` is Parrot SA's own protocol definition, BSD-3-Clause
|
|
(`LICENSE-arsdk-xml`). It ships in both the sdist and the wheel because nothing
|
|
here can decode a single command without it. What that snapshot is and how it
|
|
differs from upstream is recorded in `arsdk-xml/PROVENANCE.md`.
|
|
|
|
So the distribution as a whole is `MIT AND BSD-3-Clause`.
|