Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions docs/marlin_binary_file_transfer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Marlin Binary File Transfer

Enable `MARLIN_BINARY_FILE_TRANSFER_FEATURE` to copy a file from ESP3D flash
or its local SD card to storage managed by Marlin. Marlin must advertise
`Cap:BINARY_FILE_TRANSFER:1` in its `M115` response.

ESP3D negotiates the packet size and compression parameters. With
`compression=auto`, heatshrink is selected when Marlin offers it; otherwise
the file is sent uncompressed. While a transfer is active, ESP3D reserves the
printer connection and rejects other commands.

## HTTP API

Start a transfer with an authenticated POST request:

```text
POST /printer-sd-transfer?action=start&source=/FS/model.gcode&destination=/model.gcode&compression=auto
```

Use `/FS/...` for ESP flash and `/SD/...` for the ESP's local SD card. Optional
parameters are `compression=auto|none|heatshrink` and `dummy=true|false`.

Poll progress with:

```text
GET /printer-sd-transfer?action=status
```

The JSON response includes `status`, `active`, source and wire byte counts,
`progress`, negotiated compression parameters, retries, elapsed time, and an
error message. Terminal states are `completed`, `cancelled`, and `failed`.

Cancel an active transfer with:

```text
POST /printer-sd-transfer?action=cancel
```

The WebUI should poll status until `active` becomes false, then refresh the
Marlin SD listing after a successful transfer.
36 changes: 36 additions & 0 deletions docs/printer_link_exclusive_access.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Exclusive Printer-Link Access

`PrinterLinkService` lets a protocol temporarily reserve the connection to the
printer. This is useful for binary or otherwise stateful protocols that cannot
share the byte stream with terminal commands or automatic polling.

## API

Capture the link with an owner name and an optional receive callback:

```cpp
printer_link_service.acquire("binary-transfer", receiveData, context);
```

While captured:

- all printer RX is delivered only to `receiveData`;
- a null callback deliberately discards printer RX;
- normal commands, including WebUI polling, are rejected before serial TX;
- other clients cannot capture the link;
- the WebUI receives `printerLink:captured:binary-transfer`.

Release it using the same owner name:

```cpp
printer_link_service.release("binary-transfer");
```

Only the current owner can release the link. Release restores normal terminal
RX and printer TX and emits `printerLink:released`. A newly connected WebUI is
sent the current state immediately, so it can disable its terminal input and
polling controls even when capture began before the page connected.

Every successful `acquire()` must have a matching `release()` on success,
cancellation, timeout, and error paths. The service does not transmit protocol
bytes itself; the owner continues to use the selected serial service directly.
8 changes: 7 additions & 1 deletion esp3d/configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,12 @@
*/
#define HTTP_FEATURE

/* Copy files from the ESP flash / local SD to a Marlin-controlled SD card
* using Marlin's Binary File Transfer protocol (M28 B1).
* Marlin must be compiled with BINARY_FILE_TRANSFER.
*/
//#define MARLIN_BINARY_FILE_TRANSFER_FEATURE

/* Use telnet server
* Enable telnet light (raw tcp) communications
*/
Expand Down Expand Up @@ -705,4 +711,4 @@
#undef NOTIFICATION_FEATURE
#endif

#endif //_CONFIGURATION_H
#endif //_CONFIGURATION_H
6 changes: 6 additions & 0 deletions esp3d/src/core/esp3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@
#if defined(USB_SERIAL_FEATURE)
#include "../modules/usb-serial/usb_serial_service.h"
#endif // USB_SERIAL_FEATURE
#if defined(MARLIN_BINARY_FILE_TRANSFER_FEATURE)
#include "../modules/marlin_bft/marlin_bft_service.h"
#endif

bool Esp3D::restart = false;

Expand Down Expand Up @@ -193,6 +196,9 @@ void Esp3D::handle() {
esp3d_serial_service.handle();
#endif // COMMUNICATION_PROTOCOL == RAW_SERIAL || COMMUNICATION_PROTOCOL ==
// MKS_SERIAL
#if defined(MARLIN_BINARY_FILE_TRANSFER_FEATURE)
marlin_bft_service.handle();
#endif
#if defined(ESP_SERIAL_BRIDGE_OUTPUT)
serial_bridge_service.handle();
#endif // ESP_SERIAL_BRIDGE_OUTPUT
Expand Down
15 changes: 15 additions & 0 deletions esp3d/src/core/esp3d_commands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ const char *esp3dmsgstr[] = {"head", "core", "tail", "unique"};
#if defined(USB_SERIAL_FEATURE)
#include "../modules/usb-serial/usb_serial_service.h"
#endif // USB_SERIAL_FEATURE
#include "../modules/printer_link/printer_link_service.h"

ESP3DCommands esp3d_commands;

Expand Down Expand Up @@ -1329,6 +1330,13 @@ bool ESP3DCommands::dispatch(ESP3DMessage *msg) {
#if COMMUNICATION_PROTOCOL == RAW_SERIAL
case ESP3DClientType::serial:
esp3d_log("Serial message");
if (printer_link_service.captured()) {
esp3d_log_e("Printer link is captured by %s",
printer_link_service.owner());
esp3d_message_manager.deleteMsg(msg);
sendOk = false;
break;
}
if (!esp3d_serial_service.dispatch(msg)) {
sendOk = false;
esp3d_log_e("Serial dispatch failed");
Expand All @@ -1337,6 +1345,13 @@ bool ESP3DCommands::dispatch(ESP3DMessage *msg) {
#if defined(USB_SERIAL_FEATURE)
case ESP3DClientType::usb_serial:
esp3d_log("USB Serial message");
if (printer_link_service.captured()) {
esp3d_log_e("Printer link is captured by %s",
printer_link_service.owner());
esp3d_message_manager.deleteMsg(msg);
sendOk = false;
break;
}
if (!esp3d_usb_serial_service.dispatch(msg)) {
sendOk = false;
esp3d_log_e("USB Serial dispatch failed");
Expand Down
5 changes: 5 additions & 0 deletions esp3d/src/include/esp3d_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@
#endif
#endif

#if defined(MARLIN_BINARY_FILE_TRANSFER_FEATURE) && \
!defined(GLOBAL_FILESYSTEM_FEATURE)
#define GLOBAL_FILESYSTEM_FEATURE
#endif

#include "../core/esp3d_hal.h"
#include "../core/esp3d_log.h"
#include "../include/esp3d_pins.h"
Expand Down
12 changes: 12 additions & 0 deletions esp3d/src/include/esp3d_sanity.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,18 @@
#endif // defined(ESP_SERIAL_BRIDGE_OUTPUT)
#endif // COMMUNICATION_PROTOCOL == MKS_SERIAL

#if defined(MARLIN_BINARY_FILE_TRANSFER_FEATURE)
#if COMMUNICATION_PROTOCOL != RAW_SERIAL
#error MARLIN_BINARY_FILE_TRANSFER_FEATURE requires RAW_SERIAL
#endif
#if !defined(FILESYSTEM_FEATURE) && !defined(SD_DEVICE)
#error MARLIN_BINARY_FILE_TRANSFER_FEATURE requires FILESYSTEM_FEATURE or SD_DEVICE
#endif
#if !defined(HTTP_FEATURE)
#error MARLIN_BINARY_FILE_TRANSFER_FEATURE requires HTTP_FEATURE
#endif
#endif // MARLIN_BINARY_FILE_TRANSFER_FEATURE

/**************************
* USB-Serial
* ***********************/
Expand Down
7 changes: 7 additions & 0 deletions esp3d/src/modules/http/handlers/handle-command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "../../../core/esp3d_settings.h"
#include "../../../core/esp3d_string.h"
#include "../../authentication/authentication_service.h"
#include "../../printer_link/printer_link_service.h"


// Handle web command query and send answer//////////////////////////////
Expand Down Expand Up @@ -87,6 +88,12 @@ void HTTP_Server::handle_web_command() {
}
} else {
HTTP_Server::set_http_headers();
if (printer_link_service.captured()) {
String response = "Printer link is captured by ";
response += printer_link_service.owner();
_webserver->send(423, "text/plain", response);
return;
}
// the command is not ESP3D so it will be forwarded to the output client
// no need to wait to answer then
_webserver->send(200, "text/plain", "ESP3D says: command forwarded");
Expand Down
83 changes: 83 additions & 0 deletions esp3d/src/modules/http/handlers/handle-marlin-bft.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
handle-marlin-bft.cpp - Marlin Binary File Transfer HTTP API

Copyright (c) 2026 ESP3D contributors

This code is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
*/

#include "../../../include/esp3d_config.h"

#if defined(HTTP_FEATURE) && defined(MARLIN_BINARY_FILE_TRANSFER_FEATURE)

#if defined(ARDUINO_ARCH_ESP32)
#include <WebServer.h>
#else
#include <ESP8266WebServer.h>
#endif

#include "../http_server.h"
#include "../../authentication/authentication_service.h"
#include "../../marlin_bft/marlin_bft_service.h"

void HTTP_Server::handleMarlinBft() {
set_http_headers();
if (AuthenticationService::getAuthenticatedLevel() ==
ESP3DAuthenticationLevel::guest) {
_webserver->send(401, "application/json",
"{\"status\":\"error\",\"error\":\"Wrong authentication\"}");
return;
}

String action = _webserver->hasArg("action")
? _webserver->arg("action")
: "status";
action.toLowerCase();

if (action == "start") {
if (_webserver->method() != HTTP_POST) {
_webserver->send(405, "application/json",
"{\"status\":\"error\",\"error\":\"Use POST to start a transfer\"}");
return;
}
if (!_webserver->hasArg("source") ||
!_webserver->hasArg("destination")) {
_webserver->send(400, "application/json",
"{\"status\":\"error\",\"error\":\"source and destination are required\"}");
return;
}
const String compression = _webserver->hasArg("compression")
? _webserver->arg("compression")
: "auto";
const bool dummy = _webserver->hasArg("dummy") &&
_webserver->arg("dummy") == "true";
const bool started = marlin_bft_service.start(
_webserver->arg("source").c_str(),
_webserver->arg("destination").c_str(), compression.c_str(), dummy);
_webserver->send(started ? 202 : 409, "application/json",
marlin_bft_service.statusJson());
return;
}
if (action == "cancel") {
if (_webserver->method() != HTTP_POST) {
_webserver->send(405, "application/json",
"{\"status\":\"error\",\"error\":\"Use POST to cancel a transfer\"}");
return;
}
marlin_bft_service.cancel();
_webserver->send(202, "application/json",
marlin_bft_service.statusJson());
return;
}
if (action != "status") {
_webserver->send(400, "application/json",
"{\"status\":\"error\",\"error\":\"Unknown action\"}");
return;
}
_webserver->send(200, "application/json", marlin_bft_service.statusJson());
}

#endif // HTTP_FEATURE && MARLIN_BINARY_FILE_TRANSFER_FEATURE
3 changes: 3 additions & 0 deletions esp3d/src/modules/http/http_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ void HTTP_Server::init_handlers() {
#ifdef FILESYSTEM_FEATURE
_webserver->on("/files", HTTP_ANY, handleFSFileList, FSFileupload);
#endif // FILESYSTEM_FEATURE
#ifdef MARLIN_BINARY_FILE_TRANSFER_FEATURE
_webserver->on("/printer-sd-transfer", HTTP_ANY, handleMarlinBft);
#endif // MARLIN_BINARY_FILE_TRANSFER_FEATURE
#if COMMUNICATION_PROTOCOL == MKS_SERIAL
// MKS_SERIAL
_webserver->on("/upload", HTTP_ANY, handleMKSUpload, MKSFileupload);
Expand Down
3 changes: 3 additions & 0 deletions esp3d/src/modules/http/http_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ class HTTP_Server {
static void FSFileupload();
static void handleFSFileList();
#endif // FILESYSTEM_FEATURE
#ifdef MARLIN_BINARY_FILE_TRANSFER_FEATURE
static void handleMarlinBft();
#endif // MARLIN_BINARY_FILE_TRANSFER_FEATURE
#ifdef WEB_UPDATE_FEATURE
static void handleUpdate();
static void WebUpdateUpload();
Expand Down
Loading