mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-08-05 17:17:42 +00:00
Bring Moonraker device panel to feature parity
The monitor panel showed wrong or missing
data for Moonraker printers, and its
controls did nothing.
Push payload now carries layer number and
total layers. Remaining time replaces the
wrong total_duration - print_duration
formula. The chamber light toggle maps to
Klipper SET_PIN / SET_LED, and pause,
resume and stop post to
/printer/print/{action}. Task thumbnails
resolve via /server/files/thumbnails onto
a new MachineObject thumbnail url.
Filament sync switches to pull mode so the
agent is queried on demand.
Not compiled or run.
This commit is contained in:
@@ -3470,6 +3470,9 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
|
||||
this->task_id_ = jj["task_id"].get<std::string>();
|
||||
}
|
||||
|
||||
if (jj.contains("thumbnail_url") && jj["thumbnail_url"].is_string())
|
||||
m_agent_thumbnail_url = jj["thumbnail_url"].get<std::string>();
|
||||
|
||||
if (jj.contains("job_attr")) {
|
||||
int jobAttr = jj["job_attr"].get<int>();
|
||||
jobState_ = get_flag_bits(jobAttr, 4, 4);
|
||||
|
||||
@@ -702,6 +702,8 @@ public:
|
||||
std::string subtask_id_;
|
||||
std::string job_id_;
|
||||
std::string last_subtask_id_;
|
||||
// note: printer-agent-supplied thumbnail url, empty when the agent supplies none.
|
||||
std::string m_agent_thumbnail_url;
|
||||
BBLSliceInfo* slice_info {nullptr};
|
||||
boost::thread* get_slice_info_thread { nullptr };
|
||||
boost::thread* get_model_task_thread { nullptr };
|
||||
|
||||
@@ -3920,39 +3920,61 @@ void StatusPanel::update_cloud_subtask(MachineObject *obj)
|
||||
update_calib_bitmap();
|
||||
if (obj->slice_info) {
|
||||
m_request_url = wxString(obj->slice_info->thumbnail_url);
|
||||
if (!m_request_url.IsEmpty()) {
|
||||
wxImage img;
|
||||
std::map<wxString, wxImage>::iterator it = img_list.find(m_request_url);
|
||||
if (it != img_list.end()) {
|
||||
if (m_current_print_mode != PrintingTaskType::CALIBRATION ||(m_calib_mode == CalibMode::Calib_Flow_Rate && m_calib_method == CalibrationMethod::CALI_METHOD_MANUAL)) {
|
||||
img = it->second;
|
||||
wxImage resize_img = img.Scale(m_project_task_panel->get_bitmap_thumbnail()->GetSize().x, m_project_task_panel->get_bitmap_thumbnail()->GetSize().y);
|
||||
m_project_task_panel->set_thumbnail_img(resize_img, "");
|
||||
m_project_task_panel->set_brightness_value(get_brightness_value(resize_img));
|
||||
}
|
||||
if (this->obj) {
|
||||
m_project_task_panel->set_plate_index(obj->m_plate_index);
|
||||
} else {
|
||||
m_project_task_panel->set_plate_index(-1);
|
||||
}
|
||||
task_thumbnail_state = ThumbnailState::TASK_THUMBNAIL;
|
||||
BOOST_LOG_TRIVIAL(trace) << "web_request: use cache image";
|
||||
} else {
|
||||
web_request = wxWebSession::GetDefault().CreateRequest(this, m_request_url);
|
||||
BOOST_LOG_TRIVIAL(trace) << "monitor: start request thumbnail, url = " << m_request_url;
|
||||
web_request.Start();
|
||||
m_start_loading_thumbnail = false;
|
||||
}
|
||||
}
|
||||
load_thumbnail_from_url(m_request_url, obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool StatusPanel::load_thumbnail_from_url(const wxString &url, MachineObject *obj)
|
||||
{
|
||||
if (url.IsEmpty())
|
||||
return false;
|
||||
|
||||
wxImage img;
|
||||
std::map<wxString, wxImage>::iterator it = img_list.find(url);
|
||||
if (it != img_list.end()) {
|
||||
if (m_current_print_mode != PrintingTaskType::CALIBRATION ||(m_calib_mode == CalibMode::Calib_Flow_Rate && m_calib_method == CalibrationMethod::CALI_METHOD_MANUAL)) {
|
||||
img = it->second;
|
||||
wxImage resize_img = img.Scale(m_project_task_panel->get_bitmap_thumbnail()->GetSize().x, m_project_task_panel->get_bitmap_thumbnail()->GetSize().y);
|
||||
m_project_task_panel->set_thumbnail_img(resize_img, "");
|
||||
m_project_task_panel->set_brightness_value(get_brightness_value(resize_img));
|
||||
}
|
||||
if (this->obj) {
|
||||
m_project_task_panel->set_plate_index(obj->m_plate_index);
|
||||
} else {
|
||||
m_project_task_panel->set_plate_index(-1);
|
||||
}
|
||||
task_thumbnail_state = ThumbnailState::TASK_THUMBNAIL;
|
||||
BOOST_LOG_TRIVIAL(trace) << "web_request: use cache image";
|
||||
} else {
|
||||
m_request_url = url;
|
||||
web_request = wxWebSession::GetDefault().CreateRequest(this, m_request_url);
|
||||
BOOST_LOG_TRIVIAL(trace) << "monitor: start request thumbnail, url = " << m_request_url;
|
||||
web_request.Start();
|
||||
m_start_loading_thumbnail = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void StatusPanel::update_sdcard_subtask(MachineObject *obj)
|
||||
{
|
||||
if (!obj) return;
|
||||
|
||||
if (!m_load_sdcard_thumbnail) {
|
||||
const wxString thumbnail_url = wxString(obj->m_agent_thumbnail_url);
|
||||
if (!thumbnail_url.IsEmpty()) {
|
||||
// why: Moonraker has no prediction or weight data, so keep it on the sdcard path.
|
||||
if (m_request_url != thumbnail_url || !m_load_sdcard_thumbnail) {
|
||||
if (web_request.IsOk() && web_request.GetState() == wxWebRequest::State_Active)
|
||||
web_request.Cancel();
|
||||
update_calib_bitmap();
|
||||
m_request_url = thumbnail_url;
|
||||
load_thumbnail_from_url(thumbnail_url, obj);
|
||||
m_load_sdcard_thumbnail = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_load_sdcard_thumbnail || !m_request_url.IsEmpty()) {
|
||||
update_calib_bitmap();
|
||||
if (m_current_print_mode != PrintingTaskType::CALIBRATION) {
|
||||
m_project_task_panel->get_bitmap_thumbnail()->SetBitmap(m_thumbnail_sdcard.bmp());
|
||||
@@ -3960,6 +3982,7 @@ void StatusPanel::update_sdcard_subtask(MachineObject *obj)
|
||||
}
|
||||
task_thumbnail_state = ThumbnailState::SDCARD_THUMBNAIL;
|
||||
m_load_sdcard_thumbnail = true;
|
||||
m_request_url.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -630,6 +630,7 @@ class StatusPanel : public StatusBasePanel
|
||||
{
|
||||
private:
|
||||
friend class MonitorPanel;
|
||||
bool load_thumbnail_from_url(const wxString &url, MachineObject *obj);
|
||||
|
||||
protected:
|
||||
std::shared_ptr<SliceInfoPopup> m_slice_info_popup;
|
||||
|
||||
@@ -1017,6 +1017,59 @@ int MoonrakerPrinterAgent::handle_request(const std::string& dev_id, const std::
|
||||
if (command.is_string() && command.get<std::string>() == "get_access_code") {
|
||||
return send_access_code(dev_id);
|
||||
}
|
||||
if (command.is_string() && command.get<std::string>() == "ledctrl") {
|
||||
const auto& system = json["system"];
|
||||
if (!system.contains("led_node") || !system["led_node"].is_string() || !system.contains("led_mode") ||
|
||||
!system["led_mode"].is_string()) {
|
||||
return BAMBU_NETWORK_ERR_INVALID_RESULT;
|
||||
}
|
||||
const std::string led_mode = system["led_mode"].get<std::string>();
|
||||
// why: Klipper has no flash primitive, so flashing collapses to full brightness.
|
||||
const std::string value = (led_mode == "on" || led_mode == "flashing") ? "1" : "0";
|
||||
std::string gcode;
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(payload_mutex);
|
||||
// why: only light-named objects may be driven - available_objects is alphabetical, so an
|
||||
// unqualified "first output_pin" match would happily toggle a beeper or a fan power pin.
|
||||
const auto is_light = [](const std::string& name) {
|
||||
size_t pos = name.find("led");
|
||||
while (pos != std::string::npos) {
|
||||
const bool at_start = pos == 0 || name[pos - 1] == '_' || name[pos - 1] == '-';
|
||||
const size_t end = pos + 3;
|
||||
const bool at_end = end == name.size() || name[end] == '_' || name[end] == '-';
|
||||
if (at_start && at_end) {
|
||||
return true;
|
||||
}
|
||||
pos = name.find("led", pos + 1);
|
||||
}
|
||||
return name.find("light") != std::string::npos;
|
||||
};
|
||||
for (const auto& object : available_objects) {
|
||||
// why: Klipper wants the bare pin name, not the "output_pin " section prefix.
|
||||
const size_t prefix = object.rfind("output_pin ", 0) == 0 ? 11 : 0;
|
||||
if (prefix != 0 && object.size() > prefix && is_light(object.substr(prefix))) {
|
||||
gcode = "SET_PIN PIN=" + object.substr(prefix) + " VALUE=" + value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (gcode.empty()) {
|
||||
for (const auto& object : available_objects) {
|
||||
const size_t prefix = object.rfind("led ", 0) == 0 ? 4 : object.rfind("neopixel ", 0) == 0 ? 9 : 0;
|
||||
if (prefix != 0 && object.size() > prefix && is_light(object.substr(prefix))) {
|
||||
gcode = "SET_LED LED=" + object.substr(prefix) + " RED=" + value + " GREEN=" + value +
|
||||
" BLUE=" + value + " WHITE=" + value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (gcode.empty()) {
|
||||
// why: match the other unmapped commands - the UI treats a failure code as a printer error dialog.
|
||||
BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent: ledctrl - no light object found, dropping";
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
return send_gcode(dev_id, gcode) ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECTION_TO_PRINTER_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle print commands
|
||||
@@ -1241,6 +1294,51 @@ bool MoonrakerPrinterAgent::query_printer_status(const std::string& base_url,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MoonrakerPrinterAgent::post_print_action(const std::string& action) const
|
||||
{
|
||||
// why: /printer/print/{pause,resume,cancel} map to Klipper's pause_resume
|
||||
// webhook (a direct interrupt). A raw PAUSE/RESUME/CANCEL_PRINT queued through
|
||||
// /printer/gcode/script waits behind the gcode queue and can no-op while the
|
||||
// printer is busy (long move, heating, inside a macro).
|
||||
// note: empty JSON body avoids a body-less POST (curl would treat it as a
|
||||
// streamed upload) - same reason start_print_file sends a body.
|
||||
const std::string full_url = join_url(device_info.base_url, "/printer/print/" + action);
|
||||
bool success = false;
|
||||
std::string http_error;
|
||||
|
||||
auto http = Http::post(full_url);
|
||||
if (!device_info.api_key.empty()) {
|
||||
http.header("X-Api-Key", device_info.api_key);
|
||||
}
|
||||
http.header("Content-Type", "application/json")
|
||||
.set_post_body(std::string("{}"))
|
||||
.timeout_connect(5)
|
||||
.timeout_max(10)
|
||||
.on_complete([&](std::string body, unsigned status_code) {
|
||||
(void) body;
|
||||
if (status_code == 200) {
|
||||
success = true;
|
||||
} else {
|
||||
http_error = "HTTP error: " + std::to_string(status_code);
|
||||
}
|
||||
})
|
||||
.on_error([&](std::string body, std::string err, unsigned status_code) {
|
||||
(void) body;
|
||||
http_error = err;
|
||||
if (status_code > 0) {
|
||||
http_error += " (HTTP " + std::to_string(status_code) + ")";
|
||||
}
|
||||
})
|
||||
.perform_sync();
|
||||
|
||||
if (!success) {
|
||||
BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: print/" << action << " failed: " << http_error;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MoonrakerPrinterAgent::send_gcode(const std::string& dev_id, const std::string& gcode) const
|
||||
{
|
||||
nlohmann::json payload;
|
||||
@@ -1625,7 +1723,7 @@ void MoonrakerPrinterAgent::run_status_stream(std::string dev_id, std::string ba
|
||||
connection_lost = true;
|
||||
break;
|
||||
}
|
||||
handle_ws_message(dev_id, beast::buffers_to_string(buffer.data()));
|
||||
handle_ws_message(dev_id, beast::buffers_to_string(buffer.data()), base_url, api_key);
|
||||
// Check if handle_ws_message triggered reconnection request
|
||||
if (ws_reconnect_requested.exchange(false)) {
|
||||
connection_lost = true;
|
||||
@@ -1669,7 +1767,7 @@ void MoonrakerPrinterAgent::run_status_stream(std::string dev_id, std::string ba
|
||||
}
|
||||
}
|
||||
|
||||
void MoonrakerPrinterAgent::handle_ws_message(const std::string& dev_id, const std::string& payload)
|
||||
void MoonrakerPrinterAgent::handle_ws_message(std::string dev_id, std::string payload, std::string base_url, std::string api_key)
|
||||
{
|
||||
auto json = nlohmann::json::parse(payload, nullptr, false);
|
||||
if (json.is_discarded()) {
|
||||
@@ -1740,6 +1838,8 @@ void MoonrakerPrinterAgent::handle_ws_message(const std::string& dev_id, const s
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
refresh_thumbnail_url(base_url, api_key);
|
||||
|
||||
const auto now_ms = static_cast<uint64_t>(
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count());
|
||||
const auto last_dispatch_ms = ws_last_dispatch_ms.load();
|
||||
@@ -1788,6 +1888,105 @@ void MoonrakerPrinterAgent::update_status_cache(const nlohmann::json& updates)
|
||||
}
|
||||
}
|
||||
|
||||
// why: resolving a thumbnail is a blocking HTTP round-trip, so it must stay OUT of
|
||||
// build_print_payload_locked() - that runs on the ws thread holding payload_mutex, which the UI
|
||||
// thread also takes. Cached per filename, and a miss caches empty so a printer whose gcode has no
|
||||
// embedded thumbnail is not re-queried on every push.
|
||||
void MoonrakerPrinterAgent::refresh_thumbnail_url(std::string base_url, std::string api_key)
|
||||
{
|
||||
std::string filename;
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(payload_mutex);
|
||||
if (status_cache.contains("print_stats") && status_cache["print_stats"].contains("filename") &&
|
||||
status_cache["print_stats"]["filename"].is_string()) {
|
||||
filename = status_cache["print_stats"]["filename"].get<std::string>();
|
||||
}
|
||||
if (filename.empty() || (filename == thumbnail_filename && (!thumbnail_url.empty() || thumbnail_lookup_attempts >= 3))) {
|
||||
return;
|
||||
}
|
||||
if (filename != thumbnail_filename) {
|
||||
// why: claim the filename on the FIRST attempt, not on give-up. Leaving it unclaimed made
|
||||
// every transient failure look like a new file and reset the counter, so an unreachable
|
||||
// printer re-issued this blocking lookup on every push instead of stopping after 3.
|
||||
thumbnail_filename = filename;
|
||||
thumbnail_url.clear();
|
||||
thumbnail_lookup_attempts = 0;
|
||||
}
|
||||
++thumbnail_lookup_attempts;
|
||||
}
|
||||
|
||||
std::string response_body;
|
||||
bool request_succeeded = false;
|
||||
auto http = Http::get(join_url(base_url, "/server/files/thumbnails?filename=" + Http::url_encode(filename)));
|
||||
if (!api_key.empty()) {
|
||||
http.header("X-Api-Key", api_key);
|
||||
}
|
||||
http.timeout_connect(2)
|
||||
.timeout_max(4)
|
||||
.on_complete([&](std::string body, unsigned status) {
|
||||
if (status == 200) {
|
||||
response_body = std::move(body);
|
||||
request_succeeded = true;
|
||||
}
|
||||
})
|
||||
.on_error([&](std::string, std::string, unsigned) { request_succeeded = false; })
|
||||
.on_progress([this](Http::Progress, bool& cancel) { cancel = ws_stop.load(); })
|
||||
.perform_sync();
|
||||
|
||||
// why: transient (connection refused, non-2xx, timeout, cancel) - leave the counter standing so the
|
||||
// next push retries, and the >= 3 guard above stops it. Only a clean 200 caches a verdict.
|
||||
if (!request_succeeded) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string path;
|
||||
int best_width = -1;
|
||||
const auto thumbnails = nlohmann::json::parse(response_body, nullptr, false, true);
|
||||
if (!thumbnails.is_discarded() && thumbnails.contains("result") && thumbnails["result"].is_array()) {
|
||||
for (const auto& item : thumbnails["result"]) {
|
||||
if (!item.is_object()) {
|
||||
continue;
|
||||
}
|
||||
// note: Moonraker renamed this key across versions; accept both spellings.
|
||||
const char* key = item.contains("thumbnail_path") ? "thumbnail_path" : "relative_path";
|
||||
if (!item.contains(key) || !item[key].is_string()) {
|
||||
continue;
|
||||
}
|
||||
// why: the list is ordered smallest-first, so taking [0] would pick the 32x32 icon.
|
||||
const int width = (item.contains("width") && item["width"].is_number()) ? item["width"].get<int>() : 0;
|
||||
if (width > best_width) {
|
||||
best_width = width;
|
||||
path = item[key].get<std::string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string url;
|
||||
if (!path.empty()) {
|
||||
// why: the returned path is relative to the gcodes root, which is served at /server/files/gcodes.
|
||||
const std::string root = path.rfind("gcodes/", 0) == 0 ? "/server/files/" : "/server/files/gcodes/";
|
||||
std::string encoded_path;
|
||||
size_t segment_start = 0;
|
||||
while (segment_start <= path.size()) {
|
||||
const size_t segment_end = path.find('/', segment_start);
|
||||
if (!encoded_path.empty() || segment_start > 0) {
|
||||
encoded_path += '/';
|
||||
}
|
||||
encoded_path += Http::url_encode(path.substr(segment_start, segment_end - segment_start));
|
||||
if (segment_end == std::string::npos) {
|
||||
break;
|
||||
}
|
||||
segment_start = segment_end + 1;
|
||||
}
|
||||
url = join_url(base_url, root + encoded_path);
|
||||
}
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(payload_mutex);
|
||||
thumbnail_filename = filename;
|
||||
thumbnail_url = url;
|
||||
thumbnail_lookup_attempts = url.empty() ? 3 : 0;
|
||||
}
|
||||
|
||||
nlohmann::json MoonrakerPrinterAgent::build_print_payload_locked() const
|
||||
{
|
||||
nlohmann::json payload;
|
||||
@@ -1913,6 +2112,23 @@ nlohmann::json MoonrakerPrinterAgent::build_print_payload_locked() const
|
||||
|
||||
if (status_cache.contains("print_stats") && status_cache["print_stats"].contains("filename")) {
|
||||
payload["print"]["gcode_file"] = status_cache["print_stats"]["filename"];
|
||||
if (status_cache["print_stats"]["filename"].is_string()) {
|
||||
const std::string filename = status_cache["print_stats"]["filename"].get<std::string>();
|
||||
payload["print"]["task_id"] = filename;
|
||||
// why: url is resolved by refresh_thumbnail_url() off this lock - the lookup is a blocking
|
||||
// HTTP round-trip and this builder runs on the ws thread holding payload_mutex.
|
||||
if (thumbnail_filename == filename && !thumbnail_url.empty())
|
||||
payload["print"]["thumbnail_url"] = thumbnail_url;
|
||||
}
|
||||
}
|
||||
|
||||
if (status_cache.contains("print_stats") && status_cache["print_stats"].contains("info") &&
|
||||
status_cache["print_stats"]["info"].contains("current_layer") && status_cache["print_stats"]["info"]["current_layer"].is_number()) {
|
||||
payload["print"]["layer_num"] = status_cache["print_stats"]["info"]["current_layer"];
|
||||
}
|
||||
if (status_cache.contains("print_stats") && status_cache["print_stats"].contains("info") &&
|
||||
status_cache["print_stats"]["info"].contains("total_layer") && status_cache["print_stats"]["info"]["total_layer"].is_number()) {
|
||||
payload["print"]["total_layer_num"] = status_cache["print_stats"]["info"]["total_layer"];
|
||||
}
|
||||
|
||||
int mc_percent = -1;
|
||||
@@ -1927,13 +2143,15 @@ nlohmann::json MoonrakerPrinterAgent::build_print_payload_locked() const
|
||||
payload["print"]["mc_percent"] = mc_percent;
|
||||
}
|
||||
|
||||
if (status_cache.contains("print_stats") && status_cache["print_stats"].contains("total_duration") &&
|
||||
status_cache["print_stats"].contains("print_duration") && status_cache["print_stats"]["total_duration"].is_number() &&
|
||||
status_cache["print_stats"]["print_duration"].is_number()) {
|
||||
const double total = status_cache["print_stats"]["total_duration"].get<double>();
|
||||
const double elapsed = status_cache["print_stats"]["print_duration"].get<double>();
|
||||
if (total > 0.0 && elapsed >= 0.0) {
|
||||
const auto remaining_minutes = std::max(0, static_cast<int>((total - elapsed) / 60.0));
|
||||
// why: total_duration and print_duration are both elapsed counters; their difference is overhead, not ETA.
|
||||
if (status_cache.contains("print_stats") && status_cache["print_stats"].contains("print_duration") &&
|
||||
status_cache["print_stats"]["print_duration"].is_number() && status_cache.contains("virtual_sdcard") &&
|
||||
status_cache["virtual_sdcard"].contains("progress") && status_cache["virtual_sdcard"]["progress"].is_number()) {
|
||||
const double elapsed = status_cache["print_stats"]["print_duration"].get<double>();
|
||||
const double progress = status_cache["virtual_sdcard"]["progress"].get<double>();
|
||||
// why: progress is file position and starts before the print, so ETA is garbage below 2%.
|
||||
if (elapsed >= 0.0 && progress >= 0.02) {
|
||||
const auto remaining_minutes = std::max(0, static_cast<int>((elapsed * (1.0 - progress) / progress) / 60.0));
|
||||
payload["print"]["mc_remaining_time"] = remaining_minutes;
|
||||
}
|
||||
}
|
||||
@@ -2052,17 +2270,20 @@ bool MoonrakerPrinterAgent::upload_gcode(const std::string& local_path,
|
||||
|
||||
int MoonrakerPrinterAgent::pause_print(const std::string& dev_id)
|
||||
{
|
||||
return send_gcode(dev_id, "PAUSE") ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_SEND_MSG_FAILED;
|
||||
(void) dev_id;
|
||||
return post_print_action("pause") ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_SEND_MSG_FAILED;
|
||||
}
|
||||
|
||||
int MoonrakerPrinterAgent::resume_print(const std::string& dev_id)
|
||||
{
|
||||
return send_gcode(dev_id, "RESUME") ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_SEND_MSG_FAILED;
|
||||
(void) dev_id;
|
||||
return post_print_action("resume") ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_SEND_MSG_FAILED;
|
||||
}
|
||||
|
||||
int MoonrakerPrinterAgent::cancel_print(const std::string& dev_id)
|
||||
{
|
||||
return send_gcode(dev_id, "CANCEL_PRINT") ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_SEND_MSG_FAILED;
|
||||
(void) dev_id;
|
||||
return post_print_action("cancel") ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_SEND_MSG_FAILED;
|
||||
}
|
||||
|
||||
bool MoonrakerPrinterAgent::start_print_file(const std::string& base_url,
|
||||
|
||||
@@ -70,7 +70,7 @@ public:
|
||||
int set_queue_on_main_fn(QueueOnMainFn fn) override;
|
||||
|
||||
// Pull-mode agent (on-demand filament sync)
|
||||
FilamentSyncMode get_filament_sync_mode() const override { return FilamentSyncMode::subscription; }
|
||||
FilamentSyncMode get_filament_sync_mode() const override { return FilamentSyncMode::pull; }
|
||||
bool fetch_filament_info(std::string dev_id) override;
|
||||
|
||||
protected:
|
||||
@@ -123,6 +123,7 @@ protected:
|
||||
|
||||
// Send a G-code script via Moonraker (/printer/gcode/script)
|
||||
bool send_gcode(const std::string& dev_id, const std::string& gcode) const;
|
||||
bool post_print_action(const std::string& action) const;
|
||||
|
||||
private:
|
||||
int handle_request(const std::string& dev_id, const std::string& json_str);
|
||||
@@ -139,7 +140,8 @@ private:
|
||||
void start_status_stream(const std::string& dev_id, const std::string& base_url, const std::string& api_key);
|
||||
void stop_status_stream();
|
||||
void run_status_stream(std::string dev_id, std::string base_url, std::string api_key);
|
||||
void handle_ws_message(const std::string& dev_id, const std::string& payload);
|
||||
void handle_ws_message(std::string dev_id, std::string payload, std::string base_url, std::string api_key);
|
||||
void refresh_thumbnail_url(std::string base_url, std::string api_key);
|
||||
void update_status_cache(const nlohmann::json& updates);
|
||||
nlohmann::json build_print_payload_locked() const;
|
||||
|
||||
@@ -192,6 +194,10 @@ private:
|
||||
|
||||
mutable std::recursive_mutex payload_mutex;
|
||||
nlohmann::json status_cache;
|
||||
// note: guarded by payload_mutex; filled by refresh_thumbnail_url(), empty url = looked up, none found
|
||||
std::string thumbnail_filename;
|
||||
std::string thumbnail_url;
|
||||
unsigned thumbnail_lookup_attempts = 0;
|
||||
|
||||
std::atomic<int> next_jsonrpc_id{1};
|
||||
std::set<std::string> available_objects; // Track for feature detection
|
||||
|
||||
Reference in New Issue
Block a user