Developing a Mobile App for 3D Printer Control
Most modern 3D printers running Marlin or Klipper firmware are controlled through OctoPrint or Moonraker—REST API + WebSocket on top of G-code. The mobile app task: upload G-code file, start print, monitor temperatures and progress, watch the webcam stream, and stop if issues arise. All of this is already solved at the API level; the main complexity lies in UX and robust handling of unstable WiFi connections (printer in the workshop, phone anywhere).
OctoPrint API: Core Endpoints
interface OctoPrintApi {
@GET("api/printer")
suspend fun getPrinterState(): PrinterState
@GET("api/job")
suspend fun getCurrentJob(): JobInfo
@POST("api/job")
suspend fun controlJob(@Body command: JobCommand): Response<Unit>
@GET("api/files/{location}")
suspend fun getFiles(@Path("location") location: String = "local"): FilesResponse
@Multipart
@POST("api/files/{location}")
suspend fun uploadFile(
@Path("location") location: String,
@Part file: MultipartBody.Part,
@Part("print") print: RequestBody, // "true" for immediate start
): UploadResponse
@POST("api/printer/command")
suspend fun sendGCode(@Body command: GCodeCommand): Response<Unit>
}
data class PrinterState(
val temperature: TemperatureState,
val state: StateFlags,
)
data class TemperatureState(
val tool0: ToolTemp,
val bed: ToolTemp,
)
data class ToolTemp(
val actual: Double,
val target: Double,
val offset: Double,
)
Real-time via WebSocket
Temperatures and progress arrive through OctoPrint WebSocket (/sockjs/websocket). Events come every 1-2 seconds:
class OctoPrintSocket(private val baseUrl: String, private val apiKey: String) {
fun observe(): Flow<OctoPrintEvent> = callbackFlow {
val client = OkHttpClient()
val ws = client.newWebSocket(
Request.Builder().url("ws://$baseUrl/sockjs/websocket")
.header("X-Api-Key", apiKey).build(),
object : WebSocketListener() {
override fun onOpen(webSocket: WebSocket, response: Response) {
// Auth via first message
webSocket.send("""{"auth": "$apiKey"}""")
}
override fun onMessage(webSocket: WebSocket, text: String) {
val event = parseEvent(text)
trySend(event)
}
}
)
awaitClose { ws.close(1000, null) }
}
private fun parseEvent(json: String): OctoPrintEvent {
// Event types: "current", "history", "event", "plugin"
val root = JsonParser.parseString(json).asJsonObject
return when {
root.has("current") -> OctoPrintEvent.Current(
parsePrinterState(root["current"].asJsonObject))
root.has("event") -> OctoPrintEvent.PrintEvent(
root["event"].asJsonObject["type"].asString)
else -> OctoPrintEvent.Unknown
}
}
}
Camera: MJPEG Stream
OctoPrint broadcasts MJPEG via /webcam/?action=stream. Coil and Glide don't support MJPEG—a specialized component is needed:
class MjpegStream(private val url: String) {
fun frames(): Flow<Bitmap> = flow {
val connection = URL(url).openConnection() as HttpURLConnection
val inputStream = BufferedInputStream(connection.inputStream)
val buffer = ByteArrayOutputStream()
while (true) {
val byte = inputStream.read()
if (byte == -1) break
buffer.write(byte)
val data = buffer.toByteArray()
// JPEG end marker (FF D9)
if (data.size >= 2 &&
data[data.size - 2] == 0xFF.toByte() &&
data[data.size - 1] == 0xD9.toByte()) {
val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size)
if (bitmap != null) emit(bitmap)
buffer.reset()
}
}
}.flowOn(Dispatchers.IO)
}
Moonraker (Klipper) as Alternative
Klipper-based printers use Moonraker API—a more modern REST + WebSocket (/websocket with JSON-RPC). The concepts are the same, endpoints differ: /printer/objects/query for state, /printer/gcode/script for G-code commands, Fluidd/Mainsail as web interfaces.
Uploading large G-code files (100+ MB) via Moonraker with progress:
Future<void> uploadGCode(File file, void Function(double) onProgress) async {
final stream = http.ByteStream(file.openRead());
final length = await file.length();
final request = http.MultipartRequest('POST',
Uri.parse('$baseUrl/server/files/upload'));
request.files.add(http.MultipartFile('file', stream, length,
filename: file.path.split('/').last));
final response = await request.send();
// Progress tracked via Content-Length and bytes received from stream
}
Developing a mobile app for 3D printer control (OctoPrint/Moonraker): 4–6 weeks. Multi-printer support, temperature history, and completion notifications: 7–10 weeks. Cost is individually quoted.







