Control a FIMI X8 Mini drone from a computer, through its own remote controller.
openfimi replaces the FIMI phone app. Your computer talks to the RC, and the RC relays everything over its long-range radio:
- telemetry: position, height, attitude, speeds, battery, GPS, home point, route progress, the RC's own sticks and buttons
- flight actions: take-off, land, return home and fly-to-point
- waypoint routes: upload, verify by read-back, fly, with POIs and photo or video actions; load routes straight from the FIMI app's database
- per-waypoint gimbal pitch, which the aircraft ignores on routes, applied from the ground station during the flight
- hands-off launch: wait for GPS, a home point and the drone set down level and still, then take off and fly
- realtime gimbal tilt, camera control, and virtual sticks with a dead-man timeout
- live FPV video: H.265 720p access units for ffmpeg, OpenCV or your model
It's a pure-Python library with no runtime dependencies, plus a CLI, a simulator and an Android bridge app. The aim is to make the aircraft scriptable, including by AI agents.
Status: alpha, flight-tested. The protocol was reverse-engineered from the FIMI X8M Android app and verified on an X8 Mini V3 with an RCX6E remote (October 2026). Working in real flight: telemetry, take-off, landing, fly-to-point, return home, 7-waypoint routes with photo actions, the gimbal follower, waiting for GPS and settling before launch, and FPV video. Virtual sticks do not work through the remote (flight-tested: the aircraft ignores them there; keyboard flying uses fly-to moves via
openfimi.manual). Read Safety.
The RC is a USB host that expects the phone to be a USB device (an Android Open Accessory), so a normal PC USB port can't talk to it. Something has to sit in the middle. Every option ends at the same TCP byte stream, so the library works the same way over all of them:
| Option | URL | Range | Needs | Status |
|---|---|---|---|---|
| Android bridge app on your phone | tcp://<phone-ip> |
full RC range | an Android phone | flight-tested |
Pi Zero 2 W as USB gadget + openfimi bridge |
tcp://pi.local |
full RC range | a ~US$15 Pi | built, not yet tested |
| USB gadget on this machine (Pi, SBC with OTG) | usb |
full RC range | root, device-mode USB | built, not yet tested |
| Direct Wi-Fi to the aircraft | udp |
short, bench only | Wi-Fi adapter | built |
| Simulator | tcp://127.0.0.1:10052 |
n/a | nothing | built |
See docs/connecting.md.
pip install git+https://github.com/smnz/openfimi
The bridge app is in android/: ./gradlew assembleDebug, install
the APK, plug the phone into the RC and choose openfimi bridge when Android
asks. The FIMI app can't use the RC at the same time.
openfimi mission fly -u tcp://<phone-ip> --fimi-db fimi.db --route "My route"
This runs the pre-flight checks, takes off, uploads the route in the air,
reads it back and compares it, starts it, drives the per-waypoint gimbal and
follows it to landing. A route's finish action decides whether it returns
home. Routes can also come from JSON (examples/route.json).
Hands-off launch: start it, then switch the drone on and carry it to the launch spot:
openfimi mission fly ... --wait-gps --min-sats 16 -y
It waits for satellites, a home point, the drone's own take-off clearance
and for the drone to sit level and still for 5 s. Being carried keeps it
waiting. -y skips the confirmation, so it takes off as soon as all of that
holds.
Each waypoint has a pitch (−90 = straight down, 0 = level, +10 up) and a mode
(the FIMI database's otherwise unused GIMBAL_MODE column):
- 0 none (default): the gimbal is left alone.
- 1 before arrival: in position
--leadseconds (15) before reaching the waypoint, or on leaving the previous one if the leg is shorter. Use this for photos. - 2 on arrival: moves when the waypoint is reached. Use it for video. A photo action at that waypoint is always taken before the move (measured: about 1.1 s).
The ground station applies these during the flight, so the RC must stay in range for the whole route.
openfimi sim & # simulated aircraft on :10052
export OPENFIMI_URL=tcp://127.0.0.1:10052
openfimi monitor # live telemetry as JSON lines
openfimi mission fly examples/route.json --pitch -60 -y
openfimi send gimbal -- -45
The simulator uses the telemetry codes the real aircraft sends (flight phases, route progress, the 65535 start-of-route placeholder, action timing).
from openfimi import Drone, transport
from openfimi.mission import mission_from_fimi_db
with Drone(transport.from_url("tcp://192.168.1.50")) as d:
d.wait_for_telemetry()
print(d.state.summary()) # lat, lon, height, battery, sats, faults, ...
route = mission_from_fimi_db("fimi.db", "My route")
d.fly_route(route, wait_ready=600, min_satellites=12, on_event=print)Lower-level pieces:
d.takeoff(); d.land(); d.return_home()
d.fly_to(lat, lon, alt_m=30, speed_ms=5) # sets the target (3/52), then goes (3/48)
d.gimbal_pitch(-90) # reached within ~0.1 deg in under 0.6 s
d.take_photo()
d.emergency_rth() # stop sticks, exit the route or fly-to, return home
print(d.preflight()) # [] when ready, else reasonsVideo, for example into OpenCV through PyAV:
import av
codec = av.CodecContext.create("hevc", "r")
for pkt in d.video_packets():
for frame in codec.decode(av.Packet(pkt.data)):
img = frame.to_ndarray(format="bgr24") # hand to your modelKeyboard-style flying through the remote, as autopilot moves (the aircraft ignores virtual sticks over the remote's link; they only work over direct Wi-Fi). Moves are fly-to targets relative to a desired heading; yaw turns that heading and is flown as a one-point route with a POI along it (Free heading). Inputs are −1 to 1, and releasing or 0.6 s without updates hovers:
from openfimi.manual import ManualFlight
with ManualFlight(d) as m:
m.set(yaw=0.5) # turn right
m.set(pitch=0.5) # forward, along the new headingLower layers are public too: Link (sequence numbers, ACKs, retransmit),
framing, commands (exact payload builders), telemetry decoders, and
commands.raw() for experimenting with unwrapped opcodes.
- Speeds: a waypoint's speed is flown on the leg arriving at that waypoint. Acceleration is gentle (about 0.5 m/s²), so legs under about 45 m never get much above 4.5 m/s. The FIMI app ignores per-waypoint speeds; openfimi uses them.
- Heat: the drone is cooled by flying. Left powered on the ground it
overheats, then refuses take-off (code 236, "sensor temperature too high").
Launch promptly;
preflight()reports it. - Photos and POIs: with a plain photo action, the photo at a waypoint faces that waypoint's POI.
- Full protocol notes and every flight-test finding: docs/PROTOCOL.md.
openfimi monitor --record flight.ofcap # record everything both ways
openfimi decode flight.ofcap --video fpv.h265 # list frames, extract video
openfimi video -u tcp://<phone-ip> # watch live (sends nothing)
- There is no emergency motor stop. The FIMI app has none either. The only remedies are the RC's own sticks and switches, plus land and return home. Keep the RC in hand with a pilot ready to take over.
- Hands-off launch (
--wait-gps -y) takes off by itself. Keep clear of the drone once it's set down. - Keyboard flying (
ManualFlight) is not yet flight-tested. Its moves are autopilot legs that start and stop gently; keep the remote in hand. - Fly within your local regulations and keep line of sight. You are responsible for what your code makes the aircraft do.
This is an independent interoperability project. It is not affiliated with or endorsed by FIMI or Xiaomi, and it contains no FIMI code: the protocol is described from observation and reimplemented from scratch. "FIMI" is a trademark of its owner.
MIT licensed.