mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-27 02:41:17 +00:00
fix: cloud printers were using the wrong MQTT endpoint
This commit is contained in:
@@ -971,9 +971,9 @@ int OrcaCloudServiceAgent::connect_server()
|
||||
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: cloud health result=" << result << " http_code=" << http_code
|
||||
<< " connected=" << connected << " response_bytes=" << response.size();
|
||||
|
||||
// connect_server() is a REST health probe only. The long-lived MQTT socket is
|
||||
// per-printer now, driven by set_user_selected_machine ->
|
||||
// configure_selected_printer_mqtt; this method must not touch mqtt_connection.
|
||||
// connect_server() remains a REST health probe. The long-lived fleet MQTT socket
|
||||
// is started lazily by set_user_selected_machine -> configure_selected_printer_mqtt;
|
||||
// subscriptions queued before that point are replayed when it starts.
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(state_mutex);
|
||||
is_connected = connected;
|
||||
@@ -1035,10 +1035,12 @@ int OrcaCloudServiceAgent::del_subscribe(std::vector<std::string> dev_list)
|
||||
return queued ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECT_FAILED;
|
||||
}
|
||||
|
||||
int OrcaCloudServiceAgent::configure_selected_printer_mqtt(const std::string& dev_id)
|
||||
int OrcaCloudServiceAgent::configure_selected_printer_mqtt(const std::string& dev_id,
|
||||
OrcaMqttConnection::StateHandler state_handler)
|
||||
{
|
||||
(void) dev_id;
|
||||
OrcaMqttConnection::Config cfg;
|
||||
cfg.url = "wss://" + api_base_url + "/api/v1/printers/" + dev_id + "/mqtt";
|
||||
cfg.url = "wss://" + api_base_url + "/api/v1/printers/mqtt";
|
||||
cfg.use_tls = true;
|
||||
cfg.bearer_provider = [this] { return get_access_token(); };
|
||||
cfg.client_id = "OrcaSlicer";
|
||||
@@ -1048,7 +1050,13 @@ int OrcaCloudServiceAgent::configure_selected_printer_mqtt(const std::string& de
|
||||
std::lock_guard<std::mutex> lock(m_selected_url_mutex);
|
||||
m_selected_printer_mqtt_url = cfg.url;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: configuring per-printer MQTT endpoint=" << cfg.url;
|
||||
|
||||
if (mqtt_connection->is_running()) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: fleet MQTT connection already running";
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: configuring fleet MQTT endpoint=" << cfg.url;
|
||||
|
||||
// NOTE: no lock is held across start() — it blocks for the whole initial connect
|
||||
// attempt (up to ~10s), and the message handler below re-enters callback_mutex on
|
||||
@@ -1056,8 +1064,8 @@ int OrcaCloudServiceAgent::configure_selected_printer_mqtt(const std::string& de
|
||||
const bool ok = mqtt_connection->start(
|
||||
cfg,
|
||||
[this](const std::string& id, const std::string& payload) { deliver_cloud_message(id, payload); },
|
||||
[this](bool, bool) {});
|
||||
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: per-printer MQTT start returned=" << ok;
|
||||
std::move(state_handler));
|
||||
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: fleet MQTT start returned=" << ok;
|
||||
return ok ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECTION_TO_SERVER_FAILED;
|
||||
}
|
||||
|
||||
@@ -1119,6 +1127,23 @@ int OrcaCloudServiceAgent::send_printer_command(const std::string& dev_id, const
|
||||
: BAMBU_NETWORK_ERR_CONNECT_FAILED;
|
||||
}
|
||||
|
||||
int OrcaCloudServiceAgent::send_print_job(const std::string& dev_id,
|
||||
const std::string& filename,
|
||||
const std::string& gcode,
|
||||
bool start)
|
||||
{
|
||||
if (dev_id.empty() || filename.empty() || !is_user_login())
|
||||
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
|
||||
|
||||
const std::string path = std::string(ORCA_CLOUD_PRINTER) + "/" + Http::url_encode(dev_id) + "/print-jobs?filename=" +
|
||||
Http::url_encode(filename) + "&start=" + (start ? "true" : "false");
|
||||
unsigned int http_code = 0;
|
||||
const int result = http_post(path, gcode, nullptr, &http_code, "text/plain");
|
||||
if (result != BAMBU_NETWORK_SUCCESS)
|
||||
return result;
|
||||
return http_code >= 200 && http_code < 300 ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECT_FAILED;
|
||||
}
|
||||
|
||||
void OrcaCloudServiceAgent::enable_multi_machine(bool enable)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(state_mutex);
|
||||
@@ -2275,7 +2300,11 @@ int OrcaCloudServiceAgent::http_get(const std::string& path, std::string* respon
|
||||
return (res.success && !suppress) ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECT_FAILED;
|
||||
}
|
||||
|
||||
int OrcaCloudServiceAgent::http_post(const std::string& path, const std::string& body, std::string* response_body, unsigned int* http_code)
|
||||
int OrcaCloudServiceAgent::http_post(const std::string& path,
|
||||
const std::string& body,
|
||||
std::string* response_body,
|
||||
unsigned int* http_code,
|
||||
const std::string& content_type)
|
||||
{
|
||||
std::string url = api_base_url + path;
|
||||
BOOST_LOG_TRIVIAL(trace) << "OrcaCloudServiceAgent: POST " << url;
|
||||
@@ -2299,7 +2328,7 @@ int OrcaCloudServiceAgent::http_post(const std::string& path, const std::string&
|
||||
http.header("Authorization", "Bearer " + token);
|
||||
}
|
||||
|
||||
http.header("Content-Type", "application/json");
|
||||
http.header("Content-Type", content_type);
|
||||
http.set_post_body(body);
|
||||
|
||||
http.on_complete([&](std::string resp_body, unsigned resp_status) {
|
||||
|
||||
@@ -218,7 +218,7 @@ public:
|
||||
int del_subscribe(std::vector<std::string> dev_list) override;
|
||||
void enable_multi_machine(bool enable) override;
|
||||
|
||||
// The per-printer MQTT socket carries both directions: inbound reports from
|
||||
// The fleet MQTT socket carries both directions: inbound reports from
|
||||
// device/<id>/report and commands PUBLISHed to device/<id>/request on this
|
||||
// socket. OrcaPrinterAgent registers its message callback here to receive the
|
||||
// inbound half; pass an empty fn to clear it before the agent is destroyed.
|
||||
@@ -230,6 +230,12 @@ public:
|
||||
// that need non-blocking behaviour run it on their own thread.
|
||||
int send_printer_command(const std::string& dev_id, const std::string& body);
|
||||
|
||||
// Submit raw sliced G-code to the cloud printer print-jobs endpoint.
|
||||
int send_print_job(const std::string& dev_id,
|
||||
const std::string& filename,
|
||||
const std::string& gcode,
|
||||
bool start = true);
|
||||
|
||||
// ========================================================================
|
||||
// ICloudServiceAgent Interface Implementation - Settings Synchronization
|
||||
// ========================================================================
|
||||
@@ -377,16 +383,19 @@ public:
|
||||
return mqtt_connection.get();
|
||||
}
|
||||
|
||||
// Per-printer cloud socket: wss://<api_base_url>/api/v1/printers/<dev_id>/mqtt.
|
||||
// Account-scoped cloud socket: wss://<api_base_url>/api/v1/printers/mqtt.
|
||||
// configure_ blocks for the duration of the initial connect attempt, so callers
|
||||
// drive it off the UI thread; teardown_ is synchronous.
|
||||
int configure_selected_printer_mqtt(const std::string& dev_id);
|
||||
// drive it off the UI thread; teardown_ is synchronous. The dev_id argument is
|
||||
// retained for source compatibility with the printer-agent lifecycle; it does
|
||||
// not participate in endpoint construction.
|
||||
int configure_selected_printer_mqtt(const std::string& dev_id,
|
||||
OrcaMqttConnection::StateHandler state_handler = {});
|
||||
void teardown_selected_printer_mqtt();
|
||||
// Test hook: the wss:// URL of the current per-printer socket ("" when none).
|
||||
// Test hook: the wss:// URL of the current fleet socket ("" when none).
|
||||
std::string selected_printer_mqtt_url() const;
|
||||
|
||||
private:
|
||||
// Fans one inbound per-printer MQTT message out to printer_status_callback.
|
||||
// Fans one inbound fleet MQTT message out to printer_status_callback.
|
||||
void deliver_cloud_message(const std::string& dev_id, const std::string& payload);
|
||||
|
||||
// Sync protocol helpers
|
||||
@@ -412,7 +421,11 @@ private:
|
||||
|
||||
// HTTP request helpers
|
||||
int http_get(const std::string& path, std::string* response_body, unsigned int* http_code);
|
||||
int http_post(const std::string& path, const std::string& body, std::string* response_body, unsigned int* http_code);
|
||||
int http_post(const std::string& path,
|
||||
const std::string& body,
|
||||
std::string* response_body,
|
||||
unsigned int* http_code,
|
||||
const std::string& content_type = "application/json");
|
||||
int http_put(const std::string& path, const std::string& body, std::string* response_body, unsigned int* http_code);
|
||||
int http_delete(const std::string& path, std::string* response_body, unsigned int* http_code);
|
||||
std::map<std::string, std::string> data_headers();
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
namespace Slic3r {
|
||||
|
||||
// Minimal MQTT 3.1.1 codec + WebSocket transport (ws:// and wss://), shared by the
|
||||
// LAN (OrcaSonar) and cloud (per-printer) printer connections. Both PUBLISH
|
||||
// LAN (OrcaSonar) and cloud (fleet) printer connections. Both PUBLISH
|
||||
// commands to device/<id>/request and SUBSCRIBE device/<id>/report; Config is the
|
||||
// only per-transport difference.
|
||||
class OrcaMqttConnection
|
||||
|
||||
@@ -19,11 +19,14 @@
|
||||
#include <cstdio>
|
||||
#include <condition_variable>
|
||||
#include <cmath>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
#include <random>
|
||||
#include <ratio>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
@@ -1353,24 +1356,48 @@ int OrcaPrinterAgent::set_user_selected_machine(std::string dev_id)
|
||||
const uint64_t gen = ++m_cloud_generation;
|
||||
|
||||
auto* conn = cloud->get_mqtt_connection();
|
||||
if (!previous.empty() && conn && conn->is_connected())
|
||||
conn->send_request(previous, build_pushing_stop(seq(5)));
|
||||
cloud->teardown_selected_printer_mqtt();
|
||||
|
||||
if (m_cloud_connect_thread.joinable())
|
||||
m_cloud_connect_thread.join(); // teardown_selected_printer_mqtt() above stopped the old cloud conn
|
||||
if (!dev_id.empty()) {
|
||||
m_cloud_connect_thread = std::thread([this, cloud, dev_id, gen] {
|
||||
if (gen != m_cloud_generation.load())
|
||||
return; // superseded before we ran: do not raise a socket nobody tears down
|
||||
if (cloud->configure_selected_printer_mqtt(dev_id) == BAMBU_NETWORK_SUCCESS && gen == m_cloud_generation.load())
|
||||
on_connected(dev_id, cloud->get_mqtt_connection(), gen);
|
||||
});
|
||||
} else {
|
||||
// Deselect: the joined thread may have raised a fresh socket between the
|
||||
// teardown above and the join. stop() is not sticky, so tear down again.
|
||||
cloud->teardown_selected_printer_mqtt();
|
||||
if (!previous.empty()) {
|
||||
cloud->del_subscribe({previous});
|
||||
if (conn && conn->is_connected())
|
||||
conn->send_request(previous, build_pushing_stop(seq(5)));
|
||||
}
|
||||
|
||||
// A previous initial-connect worker may still be blocking in start(). Stop it
|
||||
// only when no fleet connection has reached CONNACK; an established fleet
|
||||
// socket survives printer selection changes.
|
||||
if (m_cloud_connect_thread.joinable()) {
|
||||
if (conn && !conn->is_connected())
|
||||
conn->stop();
|
||||
m_cloud_connect_thread.join();
|
||||
}
|
||||
|
||||
if (dev_id.empty())
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
|
||||
if (conn && conn->is_running()) {
|
||||
on_connected(dev_id, conn, gen);
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
m_cloud_connect_thread = std::thread([this, cloud, dev_id, gen] {
|
||||
if (gen != m_cloud_generation.load())
|
||||
return; // superseded before we ran: do not raise a socket nobody owns
|
||||
|
||||
auto state_handler = [this](bool connected, bool initial) {
|
||||
if (!connected || initial)
|
||||
return;
|
||||
auto* current_cloud = get_orca_cloud_agent();
|
||||
OrcaMqttConnection* current_conn = current_cloud ? current_cloud->get_mqtt_connection() : nullptr;
|
||||
const std::string selected = get_user_selected_machine();
|
||||
if (current_conn && !selected.empty())
|
||||
on_connected(selected, current_conn, m_cloud_generation.load());
|
||||
};
|
||||
|
||||
if (cloud->configure_selected_printer_mqtt(dev_id, std::move(state_handler)) == BAMBU_NETWORK_SUCCESS &&
|
||||
gen == m_cloud_generation.load()) {
|
||||
on_connected(dev_id, cloud->get_mqtt_connection(), gen);
|
||||
}
|
||||
});
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -1385,7 +1412,47 @@ AgentInfo OrcaPrinterAgent::get_agent_info_static()
|
||||
// ============================================================================
|
||||
|
||||
int OrcaPrinterAgent::start_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn)
|
||||
{ return BAMBU_NETWORK_SUCCESS; }
|
||||
{
|
||||
(void) wait_fn;
|
||||
|
||||
if (update_fn)
|
||||
update_fn(PrintingStageCreate, 0, "Preparing...");
|
||||
|
||||
if (cancel_fn && cancel_fn())
|
||||
return BAMBU_NETWORK_ERR_CANCELED;
|
||||
|
||||
auto* cloud = get_orca_cloud_agent();
|
||||
if (!cloud || params.dev_id.empty())
|
||||
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
|
||||
|
||||
const std::string local_path = resolve_local_gcode_path(params);
|
||||
const fs::path source(local_path);
|
||||
boost::filesystem::ifstream input(source, std::ios::in | std::ios::binary);
|
||||
if (!input) {
|
||||
BOOST_LOG_TRIVIAL(error) << "OrcaPrinterAgent: G-code file does not exist: " << local_path;
|
||||
return BAMBU_NETWORK_ERR_FILE_NOT_EXIST;
|
||||
}
|
||||
|
||||
const std::string gcode((std::istreambuf_iterator<char>(input)), std::istreambuf_iterator<char>());
|
||||
if (input.bad()) {
|
||||
BOOST_LOG_TRIVIAL(error) << "OrcaPrinterAgent: failed to read G-code file: " << local_path;
|
||||
return BAMBU_NETWORK_ERR_PRINT_WR_POST_TASK_FAILED;
|
||||
}
|
||||
if (cancel_fn && cancel_fn())
|
||||
return BAMBU_NETWORK_ERR_CANCELED;
|
||||
|
||||
if (update_fn)
|
||||
update_fn(PrintingStageUpload, 0, "Uploading G-code...");
|
||||
|
||||
const int result = cloud->send_print_job(params.dev_id, remote_gcode_name(params), gcode, true);
|
||||
if (result != BAMBU_NETWORK_SUCCESS)
|
||||
return result;
|
||||
|
||||
if (update_fn)
|
||||
update_fn(PrintingStageFinished, 100, "Print started");
|
||||
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::start_local_print_with_record(PrintParams params,
|
||||
OnUpdateStatusFn update_fn,
|
||||
|
||||
@@ -214,8 +214,8 @@ private:
|
||||
OrcaMqttConnection* get_appropriate_mqtt_connection(bool is_lan = true);
|
||||
static bool parse_nonnegative_command_id(const std::string& value, int& result);
|
||||
|
||||
// Route one command payload to device/<dev_id>/request on the LAN or the cloud
|
||||
// per-printer connection. The uniform send path for both send_message* overrides.
|
||||
// Route one command payload to device/<dev_id>/request on the LAN or the shared
|
||||
// cloud connection. The uniform send path for both send_message* overrides.
|
||||
int route_send(bool is_lan, const std::string& dev_id, const std::string& json_str);
|
||||
|
||||
// Callbacks
|
||||
|
||||
@@ -118,7 +118,7 @@ TEST_CASE("post-connect sequence is subscribe then 4 requests in order", "[OrcaP
|
||||
}
|
||||
|
||||
// Hidden: spawns the connect worker and attempts a real (failing) connect.
|
||||
TEST_CASE("selecting a cloud printer configures the per-printer socket", "[OrcaPrinterAgent][.integration]") {
|
||||
TEST_CASE("selecting a cloud printer configures the fleet socket", "[OrcaPrinterAgent][.integration]") {
|
||||
auto cloud = std::make_shared<Slic3r::OrcaCloudServiceAgent>("/tmp");
|
||||
cloud->set_api_base_url("api.example.com");
|
||||
OrcaPrinterAgent agent("/tmp");
|
||||
@@ -132,10 +132,10 @@ TEST_CASE("selecting a cloud printer configures the per-printer socket", "[OrcaP
|
||||
if (!url.empty()) break;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
CHECK(url == "wss://api.example.com/api/v1/printers/printer-uuid-1/mqtt");
|
||||
CHECK(url == "wss://api.example.com/api/v1/printers/mqtt");
|
||||
|
||||
agent.set_user_selected_machine(""); // teardown is synchronous
|
||||
CHECK(cloud->selected_printer_mqtt_url().empty());
|
||||
agent.set_user_selected_machine(""); // selection changes do not tear down the fleet socket
|
||||
CHECK(cloud->selected_printer_mqtt_url() == "wss://api.example.com/api/v1/printers/mqtt");
|
||||
}
|
||||
|
||||
TEST_CASE("a stale-generation inbound message is dropped", "[OrcaPrinterAgent]") {
|
||||
|
||||
Reference in New Issue
Block a user