iOS app that exposes a high-level REST API to control DJI drones and fetch certain state variables. Primarily created to control DJI drones from code in a GPS-denied indoor environment.
This is an iOS port of dkapur17/DJIControlServer
(originally an Android/Kotlin app). The REST API is byte-for-byte compatible
with the original, so existing clients — including the Python
DJIControlClient — work
unchanged. The drone connects to an Apple device running this app, and the
control server becomes available on the device's IP address on port 8080.
| Concern | Android (original) | iOS (this fork) |
|---|---|---|
| Language | Kotlin | Swift 5.5 |
| UI | Android Views (activity_main.xml) |
SwiftUI |
| Drone SDK | DJI Mobile SDK for Android 4.16.1 |
DJI Mobile SDK for iOS ~> 4.16 |
| HTTP server | Ktor + Netty | Embedded server on Apple's Network framework |
| JSON | Gson | Codable / JSONEncoder |
| Async | Kotlin coroutines (suspendCoroutine) |
Swift concurrency (async/await, withCheckedContinuation) |
| App key location | AndroidManifest.xml meta-data |
Info.plist → DJISDKAppKey |
The motion-planning maths (constant, trapezoidal and S-curve velocity profiles) and every endpoint's behaviour and JSON shape are preserved.
DJIControlServer/
├── App/
│ ├── DJIControlServerApp.swift # SwiftUI @main entry point
│ ├── AppDelegate.swift # Registers the SDK + starts the server
│ └── Info.plist # DJI app key, permissions, MFi protocols
├── Models/
│ └── Models.swift # CommandCompleted, DroneState, IMUState, enums
├── Server/
│ ├── HTTPServer.swift # Embedded HTTP/1.1 server (Network framework)
│ ├── Router.swift # `{param}` path routing
│ └── NetworkInfo.swift # Wi-Fi IPv4 lookup for the UI
├── Drone/
│ ├── DroneController.swift # SDK connection + flight state
│ ├── DroneController+DJIDelegates.swift # SDK/flight/battery callbacks
│ ├── DroneController+Routes.swift # Endpoint registration + handlers
│ ├── DroneController+Motion.swift # Velocity-profile motion planning
│ └── DroneController+Camera.swift # Camera & gimbal commands
└── UI/
└── ContentView.swift # Status screen
The project is tracked as plain source plus an XcodeGen
spec and a CocoaPods Podfile, so no binary .xcodeproj is committed.
- Install the tooling (one time):
brew install xcodegen sudo gem install cocoapods # or: brew install cocoapods - Generate the Xcode project:
xcodegen generate
- Install the DJI SDK pod and create the workspace:
pod install
- Open
DJIControlServer.xcworkspacein Xcode. - In
DJIControlServer/App/Info.plist, replaceYOUR-DJI-APP-KEY-HEREwith an app key generated on the DJI developer portal. The key must be registered against the bundle identifiercom.rrc.djiControlServer(or change both to match). - Select your team for code signing, build, and install on an iOS device.
The DJI Mobile SDK requires a physical device connected to a DJI remote controller (or supported aircraft). It does not run in the iOS Simulator.
Launch the app and connect the iOS device to a DJI remote controller. Once the
drone is connected, all endpoints are available at the device's IP on port
8080 (shown on screen).
curl http://<device-ip>:8080/ # -> Connected
curl http://<device-ip>:8080/takeoff
curl http://<device-ip>:8080/moveForward/1.5All endpoints are GET requests, identical to the original server.
Connection
/— connection test, returnsConnected.
Takeoff and landing
/takeoff,/land,/confirmLanding/isLandingProtectionEnabled,/enableLandingProtection,/disableLandingProtection
Control modes
/getControlMode,/setControlMode/{mode}—modeisPOSITIONorVELOCITY(defaultPOSITION).
Motion planning settings
/getMaxSpeed,/setMaxSpeed/{speed}(m/s)/getMaxAngularSpeed,/setMaxAngularSpeed/{speed}(deg/s)/getVelocityProfile,/setVelocityProfile/{profile}—CONSTANT,TRAPEZOIDALorS_CURVE(defaultCONSTANT).
IMU state reading
/startCollectingIMUState/{interval},/stopCollectingIMUState/getCollectedIMUStates,/clearCollectedIMUStates,/getCurrentIMUState
Positional movement and rotation (only in POSITION mode)
/moveForward/{dist},/moveBackward/{dist},/moveLeft/{dist},/moveRight/{dist}/moveUp/{dist},/moveDown/{dist}/rotateClockwise/{angle},/rotateCounterClockwise/{angle}
Velocity control (only in VELOCITY mode)
/startVelocityControl,/stopVelocityControl/setVelocityCommand/{xVel}/{yVel}/{zVel}/{yawVel},/getCurrentVelocityCommand
Camera and gimbal
/captureShot,/startVideoRecording,/stopVideoRecording,/capturePanorama/pitchGimbal/{angle},/fetchPreviewFromIndex/{n}
Other
/getHeading— heading relative to true north./getAltitude— ultrasonic height in metres (when the sensor is active).
These carry over from the original project, plus a couple specific to iOS:
- All motion commands are open-loop, so distances are approximate. The
CONSTANTprofile tracks the requested distance best;TRAPEZOIDALandS_CURVEare smoother but accumulate more error. confirmLandingbehaves as expected in the DJI simulator but may differ on a physical drone.- No GPS-based functionality is implemented — this is for indoor, GPS-denied use.
/rebootis implemented for API parity but returns an error: the iOS Mobile SDK does not expose a public flight-controller reboot (the Android SDK does).- The camera/gimbal endpoints target Mobile SDK 4.16 API names. If you build
against a different SDK version, a few method/enum names
(
setFlatMode,DJIFlatCameraMode,DJIGimbalRotation) may need adjusting. - iOS only serves the local-network API while the app is in the foreground
(or briefly via the
external-accessorybackground mode). Keep the app on-screen during a flight session.
