Розробка мобільного додатку для управління 3D-принтером
Більшість сучасних 3D-принтерів на мікропрограмному забезпеченні Marlin або Klipper керуються через OctoPrint або Moonraker — REST API + WebSocket на основі G-code. Задача мобільного додатку: завантажити файл G-code, запустити друк, стежити за температурами та прогресом, дивитися на веб-камеру та зупиняти при проблемах. Усе це вже вирішено на рівні API, основна складність полягає в UX та надійній обробці нестабільного WiFi-з'єднання (принтер у майстерні, телефон — де завгодно).
OctoPrint API: основні кінцеві точки
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" для негайного старту
): 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,
)
Реальний час через WebSocket
Температури та прогрес приходять через OctoPrint WebSocket (/sockjs/websocket). События надходять кожні 1-2 секунди:
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) {
// Аутентифікація через перше повідомлення
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 {
// Типи подій: "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
}
}
}
Камера: MJPEG-потік
OctoPrint транслює MJPEG через /webcam/?action=stream. Coil та Glide не підтримують MJPEG — потрібен спеціальний компонент:
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 (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) як альтернатива
Принтери на Klipper використовують Moonraker API — більш сучасний REST + WebSocket (/websocket з JSON-RPC). Концепції ті самі, кінцеві точки інші: /printer/objects/query для стану, /printer/gcode/script для команд G-code, Fluidd/Mainsail як веб-інтерфейси.
Завантаження великих файлів G-code (100+ MB) через Moonraker з прогресом:
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();
// Прогрес відстежується через Content-Length та отримані байти зі потоку
}
Розробка мобільного додатку для управління 3D-принтером (OctoPrint/Moonraker): 4–6 тижнів. Підтримка кількох принтерів, історія температур та сповіщення про завершення: 7–10 тижнів. Вартість розраховується індивідуально.







