mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-16 05:27:50 +00:00
fix: LAN paths and camera stream
This commit is contained in:
@@ -3046,6 +3046,13 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
|
|||||||
}
|
}
|
||||||
} catch (...) {}
|
} catch (...) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (j.contains("info"))
|
||||||
|
parse_new_info2(j["info"]);
|
||||||
|
} catch (...) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "parse_json: failed to parse OrcaSonar capability info";
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (auto ptr = m_fila_system->GetAmsFirmwareSwitch().lock()) {
|
if (auto ptr = m_fila_system->GetAmsFirmwareSwitch().lock()) {
|
||||||
ptr->ParseFirmwareSwitch(j);
|
ptr->ParseFirmwareSwitch(j);
|
||||||
@@ -5432,6 +5439,86 @@ void MachineObject::parse_new_info(json print)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MachineObject::parse_new_info2(const json& info)
|
||||||
|
{
|
||||||
|
if (!info.is_object() || info.value("command", "") != "get_capabilities")
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto capabilities_it = info.find("capabilities");
|
||||||
|
if (capabilities_it == info.end() || !capabilities_it->is_object())
|
||||||
|
return;
|
||||||
|
const auto flags_it = capabilities_it->find("flags");
|
||||||
|
if (flags_it == capabilities_it->end() || !flags_it->is_object())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const json& flags = *flags_it;
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "parse_new_info2: OrcaSonar capability flags=" << flags.dump();
|
||||||
|
|
||||||
|
auto parse_bool = [&flags](const char* name, bool& target) {
|
||||||
|
const auto it = flags.find(name);
|
||||||
|
if (it != flags.end() && it->is_boolean())
|
||||||
|
target = it->get<bool>();
|
||||||
|
};
|
||||||
|
|
||||||
|
parse_bool("support_send_to_sd", is_support_send_to_sdcard);
|
||||||
|
parse_bool("support_filament_backup", is_support_filament_backup);
|
||||||
|
parse_bool("support_update_remain", is_support_update_remain);
|
||||||
|
parse_bool("support_auto_recovery_step_loss", is_support_auto_recovery_step_loss);
|
||||||
|
parse_bool("support_ams_humidity", is_support_ams_humidity);
|
||||||
|
parse_bool("support_prompt_sound", is_support_prompt_sound);
|
||||||
|
parse_bool("support_filament_tangle_detect", is_support_filament_tangle_detect);
|
||||||
|
parse_bool("support_1080dpi", is_support_1080dpi);
|
||||||
|
parse_bool("support_cloud_print_only", is_support_cloud_print_only);
|
||||||
|
parse_bool("support_command_ams_switch", is_support_command_ams_switch);
|
||||||
|
parse_bool("support_mqtt_alive", is_support_mqtt_alive);
|
||||||
|
parse_bool("support_motor_noise_cali", is_support_motor_noise_cali);
|
||||||
|
parse_bool("support_timelapse", is_support_timelapse);
|
||||||
|
parse_bool("support_user_preset", is_support_user_preset);
|
||||||
|
parse_bool("support_refresh_nozzle", is_support_refresh_nozzle);
|
||||||
|
parse_bool("support_flow_calibration", is_support_flow_calibration);
|
||||||
|
parse_bool("support_build_plate_marker_detect", is_support_build_plate_marker_detect);
|
||||||
|
parse_bool("support_nozzle_blob_detect", is_support_nozzle_blob_detection);
|
||||||
|
|
||||||
|
if (!m_manager->IsMultiMachineEnabled() && !is_support_agora)
|
||||||
|
parse_bool("support_tunnel_mqtt", is_support_tunnel_mqtt);
|
||||||
|
|
||||||
|
const auto bed_leveling_it = flags.find("support_bed_leveling");
|
||||||
|
if (bed_leveling_it != flags.end() && bed_leveling_it->is_number_integer())
|
||||||
|
is_support_bed_leveling = bed_leveling_it->get<int>();
|
||||||
|
|
||||||
|
auto copy_bool = [&flags](json& target, const char* name) {
|
||||||
|
const auto it = flags.find(name);
|
||||||
|
if (it != flags.end() && it->is_boolean())
|
||||||
|
target[name] = *it;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The capability manifest uses an object for this range, while the legacy
|
||||||
|
// DeviceCore parser consumes a boolean plus a two-element range array.
|
||||||
|
json device_config;
|
||||||
|
copy_bool(device_config, "support_chamber");
|
||||||
|
copy_bool(device_config, "support_first_layer_inspect");
|
||||||
|
copy_bool(device_config, "support_ai_monitoring");
|
||||||
|
copy_bool(device_config, "support_lidar_calibration");
|
||||||
|
const auto chamber_edit_it = flags.find("support_chamber_temp_edit");
|
||||||
|
if (chamber_edit_it != flags.end() && chamber_edit_it->is_boolean()) {
|
||||||
|
device_config["support_chamber_temp_edit"] = *chamber_edit_it;
|
||||||
|
} else if (chamber_edit_it != flags.end() && chamber_edit_it->is_object()) {
|
||||||
|
const auto min_it = chamber_edit_it->find("min");
|
||||||
|
const auto max_it = chamber_edit_it->find("max");
|
||||||
|
if (min_it != chamber_edit_it->end() && max_it != chamber_edit_it->end() && min_it->is_number() && max_it->is_number()) {
|
||||||
|
device_config["support_chamber_temp_edit"] = true;
|
||||||
|
device_config["support_chamber_temp_edit_range"] = {*min_it, *max_it};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
json fan_config;
|
||||||
|
copy_bool(fan_config, "support_aux_fan");
|
||||||
|
copy_bool(fan_config, "support_chamber_fan");
|
||||||
|
|
||||||
|
m_config->ParseConfig(device_config);
|
||||||
|
m_fan->ParseV2_0(fan_config);
|
||||||
|
}
|
||||||
|
|
||||||
static bool is_hex_digit(char c) {
|
static bool is_hex_digit(char c) {
|
||||||
return std::isxdigit(static_cast<unsigned char>(c)) != 0;
|
return std::isxdigit(static_cast<unsigned char>(c)) != 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -947,6 +947,7 @@ public:
|
|||||||
/*for parse new info*/
|
/*for parse new info*/
|
||||||
bool check_enable_np(const json& print) const;
|
bool check_enable_np(const json& print) const;
|
||||||
void parse_new_info(json print);
|
void parse_new_info(json print);
|
||||||
|
void parse_new_info2(const json& info);
|
||||||
int get_flag_bits(std::string str, int start, int count = 1) const;
|
int get_flag_bits(std::string str, int start, int count = 1) const;
|
||||||
uint32_t get_flag_bits_no_border(std::string str, int start_idx, int count = 1) const;
|
uint32_t get_flag_bits_no_border(std::string str, int start_idx, int count = 1) const;
|
||||||
int get_flag_bits(int num, int start, int count = 1, int base = 10) const;
|
int get_flag_bits(int num, int start, int count = 1, int base = 10) const;
|
||||||
|
|||||||
@@ -52,10 +52,13 @@ void WebMediaController::Play()
|
|||||||
"refreshCameraFrame();"
|
"refreshCameraFrame();"
|
||||||
"setInterval(refreshCameraFrame,200);"
|
"setInterval(refreshCameraFrame,200);"
|
||||||
"</script></body></html>";
|
"</script></body></html>";
|
||||||
} else {
|
|
||||||
html += " src=\"" + url + "\"></body></html>";
|
|
||||||
}
|
|
||||||
m_webview->SetPage(html, url);
|
m_webview->SetPage(html, url);
|
||||||
|
} else {
|
||||||
|
// Load MJPEG streams as the top-level document. Some embedded WebView
|
||||||
|
// backends buffer a multipart stream when it is used as an <img> resource,
|
||||||
|
// which introduces noticeable live-view latency.
|
||||||
|
m_webview->LoadURL(url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebMediaController::Stop()
|
void WebMediaController::Stop()
|
||||||
|
|||||||
@@ -125,6 +125,9 @@ void OrcaMqttConnection::stop() {
|
|||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(mutex);
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
connected = false;
|
connected = false;
|
||||||
|
acknowledged_subscriptions.clear();
|
||||||
|
pending_subscribe_packets.clear();
|
||||||
|
pending_requests.clear();
|
||||||
if (!initial_completed) {
|
if (!initial_completed) {
|
||||||
initial_completed = true;
|
initial_completed = true;
|
||||||
initial_result = false;
|
initial_result = false;
|
||||||
@@ -173,6 +176,11 @@ bool OrcaMqttConnection::subscribe(const std::string& dev_id) {
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(mutex);
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
|
if (subscriptions.count(topic) != 0 && pending_unsubscriptions.count(topic) == 0) {
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: MQTT subscribe already queued or active topic=" << topic
|
||||||
|
<< " acknowledged=" << (acknowledged_subscriptions.count(topic) != 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
subscriptions.insert(topic);
|
subscriptions.insert(topic);
|
||||||
pending_unsubscriptions.erase(topic);
|
pending_unsubscriptions.erase(topic);
|
||||||
pending_subscriptions.insert(topic);
|
pending_subscriptions.insert(topic);
|
||||||
@@ -190,8 +198,21 @@ bool OrcaMqttConnection::unsubscribe(const std::string& dev_id) {
|
|||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(mutex);
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
subscriptions.erase(topic);
|
subscriptions.erase(topic);
|
||||||
|
acknowledged_subscriptions.erase(topic);
|
||||||
pending_subscriptions.erase(topic);
|
pending_subscriptions.erase(topic);
|
||||||
pending_unsubscriptions.insert(topic);
|
pending_unsubscriptions.insert(topic);
|
||||||
|
for (auto it = pending_subscribe_packets.begin(); it != pending_subscribe_packets.end();) {
|
||||||
|
if (it->second == topic)
|
||||||
|
it = pending_subscribe_packets.erase(it);
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
for (auto it = pending_requests.begin(); it != pending_requests.end();) {
|
||||||
|
if (it->first == dev_id)
|
||||||
|
it = pending_requests.erase(it);
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
}
|
||||||
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: MQTT unsubscribe queued topic=" << topic
|
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: MQTT unsubscribe queued topic=" << topic
|
||||||
<< " total_subscriptions=" << subscriptions.size()
|
<< " total_subscriptions=" << subscriptions.size()
|
||||||
<< " connected=" << connected.load();
|
<< " connected=" << connected.load();
|
||||||
@@ -207,6 +228,9 @@ void OrcaMqttConnection::clear_subscriptions() {
|
|||||||
subscriptions.clear();
|
subscriptions.clear();
|
||||||
pending_subscriptions.clear();
|
pending_subscriptions.clear();
|
||||||
pending_unsubscriptions.clear();
|
pending_unsubscriptions.clear();
|
||||||
|
acknowledged_subscriptions.clear();
|
||||||
|
pending_subscribe_packets.clear();
|
||||||
|
pending_requests.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OrcaMqttConnection::parse_endpoint(const std::string& url, Endpoint& endpoint) {
|
bool OrcaMqttConnection::parse_endpoint(const std::string& url, Endpoint& endpoint) {
|
||||||
@@ -450,6 +474,17 @@ bool OrcaMqttConnection::send_request(const std::string& dev_id, const std::stri
|
|||||||
<< " dev_id=" << dev_id;
|
<< " dev_id=" << dev_id;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
const std::string report = report_topic(dev_id);
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
|
if (subscriptions.count(report) != 0 && acknowledged_subscriptions.count(report) == 0) {
|
||||||
|
pending_requests.emplace_back(dev_id, payload);
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: MQTT request queued until SUBACK"
|
||||||
|
<< " dev_id=" << dev_id << " payload_bytes=" << payload.size()
|
||||||
|
<< " pending_requests=" << pending_requests.size();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
// ws_write() serialises the write via write_mutex; do not lock it here.
|
// ws_write() serialises the write via write_mutex; do not lock it here.
|
||||||
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: MQTT PUBLISH request dev_id=" << dev_id
|
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: MQTT PUBLISH request dev_id=" << dev_id
|
||||||
@@ -531,6 +566,14 @@ void OrcaMqttConnection::connect_and_read() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_connection_stage = "reading MQTT messages";
|
m_connection_stage = "reading MQTT messages";
|
||||||
|
// The subscription acknowledgement belongs to this MQTT session. Clear
|
||||||
|
// the previous session's state before notifying the owner, because the
|
||||||
|
// reconnect callback immediately queues the printer's initial requests.
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
|
acknowledged_subscriptions.clear();
|
||||||
|
pending_subscribe_packets.clear();
|
||||||
|
}
|
||||||
notify_state(true);
|
notify_state(true);
|
||||||
reconnect_delay_seconds.store(1); // a fresh CONNACK resets the backoff
|
reconnect_delay_seconds.store(1); // a fresh CONNACK resets the backoff
|
||||||
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: MQTT connection is ready; sending current subscriptions";
|
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: MQTT connection is ready; sending current subscriptions";
|
||||||
@@ -572,10 +615,18 @@ void OrcaMqttConnection::send_current_subscriptions(Connection& conn) {
|
|||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(mutex);
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
topics.assign(subscriptions.begin(), subscriptions.end());
|
topics.assign(subscriptions.begin(), subscriptions.end());
|
||||||
|
acknowledged_subscriptions.clear();
|
||||||
|
pending_subscribe_packets.clear();
|
||||||
|
for (const std::string& topic : topics)
|
||||||
|
pending_subscriptions.erase(topic);
|
||||||
}
|
}
|
||||||
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: sending current MQTT subscriptions count=" << topics.size();
|
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: sending current MQTT subscriptions count=" << topics.size();
|
||||||
for (const std::string& topic : topics) {
|
for (const std::string& topic : topics) {
|
||||||
const uint16_t packet_id = next_packet_id++;
|
const uint16_t packet_id = next_packet_id++;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
|
pending_subscribe_packets[packet_id] = topic;
|
||||||
|
}
|
||||||
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: sending SUBSCRIBE topic=" << topic << " packet_id=" << packet_id;
|
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: sending SUBSCRIBE topic=" << topic << " packet_id=" << packet_id;
|
||||||
ws_write(conn, make_subscribe_packet(packet_id, topic, 1));
|
ws_write(conn, make_subscribe_packet(packet_id, topic, 1));
|
||||||
}
|
}
|
||||||
@@ -593,6 +644,10 @@ void OrcaMqttConnection::send_pending_subscriptions(Connection& conn) {
|
|||||||
}
|
}
|
||||||
for (const std::string& topic : subscribe_topics) {
|
for (const std::string& topic : subscribe_topics) {
|
||||||
const uint16_t packet_id = next_packet_id++;
|
const uint16_t packet_id = next_packet_id++;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
|
pending_subscribe_packets[packet_id] = topic;
|
||||||
|
}
|
||||||
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: sending pending SUBSCRIBE topic=" << topic
|
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: sending pending SUBSCRIBE topic=" << topic
|
||||||
<< " packet_id=" << packet_id;
|
<< " packet_id=" << packet_id;
|
||||||
ws_write(conn, make_subscribe_packet(packet_id, topic, 1));
|
ws_write(conn, make_subscribe_packet(packet_id, topic, 1));
|
||||||
@@ -617,6 +672,8 @@ void OrcaMqttConnection::handle_packet(const std::string& packet) {
|
|||||||
<< " bytes=" << packet.size();
|
<< " bytes=" << packet.size();
|
||||||
if (packet_type != 3) { // Only QoS 0 PUBLISH carries printer status.
|
if (packet_type != 3) { // Only QoS 0 PUBLISH carries printer status.
|
||||||
if (packet_type == 9 && packet.size() >= 5) {
|
if (packet_type == 9 && packet.size() >= 5) {
|
||||||
|
const uint16_t packet_id = (static_cast<unsigned int>(static_cast<uint8_t>(packet[2])) << 8) |
|
||||||
|
static_cast<unsigned int>(static_cast<uint8_t>(packet[3]));
|
||||||
std::ostringstream result_codes;
|
std::ostringstream result_codes;
|
||||||
for (size_t index = 4; index < packet.size(); ++index) {
|
for (size_t index = 4; index < packet.size(); ++index) {
|
||||||
if (index != 4)
|
if (index != 4)
|
||||||
@@ -624,9 +681,51 @@ void OrcaMqttConnection::handle_packet(const std::string& packet) {
|
|||||||
result_codes << "0x" << std::hex << static_cast<unsigned int>(static_cast<uint8_t>(packet[index]));
|
result_codes << "0x" << std::hex << static_cast<unsigned int>(static_cast<uint8_t>(packet[index]));
|
||||||
}
|
}
|
||||||
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: received SUBACK packet_id="
|
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: received SUBACK packet_id="
|
||||||
<< ((static_cast<unsigned int>(static_cast<uint8_t>(packet[2])) << 8) |
|
<< packet_id
|
||||||
static_cast<unsigned int>(static_cast<uint8_t>(packet[3])))
|
|
||||||
<< " result_codes=" << result_codes.str();
|
<< " result_codes=" << result_codes.str();
|
||||||
|
|
||||||
|
// Each production SUBSCRIBE packet currently contains one topic.
|
||||||
|
// MQTT grants QoS 0 or 1 for a requested QoS 1 subscription; 0x80
|
||||||
|
// means the subscription was rejected.
|
||||||
|
const uint8_t result = static_cast<uint8_t>(packet[4]);
|
||||||
|
std::string topic;
|
||||||
|
std::deque<std::pair<std::string, std::string>> requests;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
|
auto pending = pending_subscribe_packets.find(packet_id);
|
||||||
|
if (pending != pending_subscribe_packets.end()) {
|
||||||
|
topic = pending->second;
|
||||||
|
pending_subscribe_packets.erase(pending);
|
||||||
|
for (auto it = pending_requests.begin(); it != pending_requests.end();) {
|
||||||
|
if (report_topic(it->first) == topic) {
|
||||||
|
requests.push_back(std::move(*it));
|
||||||
|
it = pending_requests.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result == 0 || result == 1) {
|
||||||
|
acknowledged_subscriptions.insert(topic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (topic.empty()) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "Orca diagnostic: SUBACK has no pending topic packet_id=" << packet_id;
|
||||||
|
} else if (result == 0 || result == 1) {
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: report subscription active topic=" << topic
|
||||||
|
<< " granted_qos=" << static_cast<unsigned int>(result)
|
||||||
|
<< " releasing_requests=" << requests.size();
|
||||||
|
for (const auto& request : requests) {
|
||||||
|
if (!send_request(request.first, request.second)) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "Orca diagnostic: queued MQTT request could not be sent"
|
||||||
|
<< " after SUBACK dev_id=" << request.first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "Orca diagnostic: report subscription rejected topic=" << topic
|
||||||
|
<< " result_code=0x" << std::hex << static_cast<unsigned int>(result) << std::dec
|
||||||
|
<< " dropped_requests=" << requests.size();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,15 @@
|
|||||||
#include <boost/beast/websocket.hpp>
|
#include <boost/beast/websocket.hpp>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
|
#include <deque>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <optional>
|
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@@ -130,6 +132,12 @@ private:
|
|||||||
std::set<std::string> subscriptions;
|
std::set<std::string> subscriptions;
|
||||||
std::set<std::string> pending_subscriptions;
|
std::set<std::string> pending_subscriptions;
|
||||||
std::set<std::string> pending_unsubscriptions;
|
std::set<std::string> pending_unsubscriptions;
|
||||||
|
// Requests for a subscribed device wait until the corresponding SUBACK is
|
||||||
|
// received. Otherwise an immediate pushall response can be published by
|
||||||
|
// the broker before this client is actually subscribed to the report topic.
|
||||||
|
std::set<std::string> acknowledged_subscriptions;
|
||||||
|
std::map<uint16_t, std::string> pending_subscribe_packets;
|
||||||
|
std::deque<std::pair<std::string, std::string>> pending_requests;
|
||||||
std::atomic<uint16_t> next_packet_id{1};
|
std::atomic<uint16_t> next_packet_id{1};
|
||||||
std::atomic<int> m_last_connack_rc{-1};
|
std::atomic<int> m_last_connack_rc{-1};
|
||||||
uint64_t m_attempt_number{0}; // worker-thread diagnostic sequence
|
uint64_t m_attempt_number{0}; // worker-thread diagnostic sequence
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "OrcaPrinterAgent.hpp"
|
#include "OrcaPrinterAgent.hpp"
|
||||||
|
#include "IPrinterAgent.hpp"
|
||||||
#include "NetworkAgentFactory.hpp"
|
#include "NetworkAgentFactory.hpp"
|
||||||
#include "OrcaCloudServiceAgent.hpp"
|
#include "OrcaCloudServiceAgent.hpp"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -9,6 +10,8 @@
|
|||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
|
#include <cmath>
|
||||||
|
#include <limits>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
#include <random>
|
#include <random>
|
||||||
@@ -213,6 +216,16 @@ private:
|
|||||||
|
|
||||||
OrcaPrinterAgent::OrcaPrinterAgent(std::string log_dir) : log_dir(std::move(log_dir)) {}
|
OrcaPrinterAgent::OrcaPrinterAgent(std::string log_dir) : log_dir(std::move(log_dir)) {}
|
||||||
|
|
||||||
|
const char* OrcaPrinterAgent::connection_type_name(CurrentConn connection)
|
||||||
|
{
|
||||||
|
switch (connection) {
|
||||||
|
case LAN: return "LAN";
|
||||||
|
case CLOUD: return "cloud";
|
||||||
|
case NONE: return "none";
|
||||||
|
}
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
OrcaPrinterAgent::~OrcaPrinterAgent()
|
OrcaPrinterAgent::~OrcaPrinterAgent()
|
||||||
{
|
{
|
||||||
start_discovery(false, false);
|
start_discovery(false, false);
|
||||||
@@ -300,6 +313,8 @@ void OrcaPrinterAgent::deliver_to_sink(const std::string& dev_id, const std::str
|
|||||||
|
|
||||||
void OrcaPrinterAgent::deliver_to_local_sink(const std::string& dev_id, const std::string& payload)
|
void OrcaPrinterAgent::deliver_to_local_sink(const std::string& dev_id, const std::string& payload)
|
||||||
{
|
{
|
||||||
|
parse_ipcam_info(dev_id, payload);
|
||||||
|
|
||||||
OnMessageFn fn;
|
OnMessageFn fn;
|
||||||
QueueOnMainFn q;
|
QueueOnMainFn q;
|
||||||
{
|
{
|
||||||
@@ -380,12 +395,141 @@ void OrcaPrinterAgent::set_cloud_agent(std::shared_ptr<ICloudServiceAgent> cloud
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Communication - All Stubs
|
// Communication
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
int OrcaPrinterAgent::send_message(std::string dev_id, std::string json_str, int /*qos*/, int /*flag*/)
|
int OrcaPrinterAgent::send_message(std::string dev_id, std::string json_str, int /*qos*/, int /*flag*/)
|
||||||
{ return route_send(/*is_lan=*/false, dev_id, json_str); }
|
{ return route_send(/*is_lan=*/false, dev_id, json_str); }
|
||||||
|
|
||||||
|
int OrcaPrinterAgent::command_ams_refresh_rfid(std::string dev_id, std::string tray_id, int sequence_id, bool lan_mode)
|
||||||
|
{
|
||||||
|
int tray_number = 0;
|
||||||
|
if (!parse_nonnegative_command_id(tray_id, tray_number)) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "OrcaPrinterAgent: invalid RFID tray id=" << tray_id;
|
||||||
|
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json j;
|
||||||
|
j["print"]["command"] = "ams_get_rfid";
|
||||||
|
j["print"]["sequence_id"] = std::to_string(sequence_id);
|
||||||
|
j["print"]["tray_id"] = tray_number;
|
||||||
|
return route_send(lan_mode, dev_id, j.dump());
|
||||||
|
}
|
||||||
|
|
||||||
|
int OrcaPrinterAgent::command_ams_calibrate(std::string /*dev_id*/, int /*ams_id*/, int /*sequence_id*/, bool /*lan_mode*/)
|
||||||
|
{
|
||||||
|
// OrcaSonar has no ams_calibrate command. Do not send the Bambu M620 C
|
||||||
|
// dialect through the vendor-neutral OrcaSonar gcode_line command.
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "OrcaPrinterAgent: AMS calibration is not part of the OrcaSonar API";
|
||||||
|
return ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
int OrcaPrinterAgent::command_ams_select_tray(std::string dev_id, std::string tray_id, int sequence_id, bool lan_mode)
|
||||||
|
{
|
||||||
|
int tray_number = 0;
|
||||||
|
if (!parse_nonnegative_command_id(tray_id, tray_number)) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "OrcaPrinterAgent: invalid AMS target tray id=" << tray_id;
|
||||||
|
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json j;
|
||||||
|
j["print"]["command"] = "ams_change_filament";
|
||||||
|
j["print"]["sequence_id"] = std::to_string(sequence_id);
|
||||||
|
j["print"]["target"] = tray_number;
|
||||||
|
return route_send(lan_mode, dev_id, j.dump());
|
||||||
|
}
|
||||||
|
|
||||||
|
int OrcaPrinterAgent::command_start_camera(std::string /*dev_id*/)
|
||||||
|
{
|
||||||
|
// OrcaSonar exposes camera.ipcam_* controls, not the legacy start_camera
|
||||||
|
// operation used by the Bambu agent.
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "OrcaPrinterAgent: camera start is not part of the OrcaSonar API";
|
||||||
|
return ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
int OrcaPrinterAgent::command_xyz_abs(std::string dev_id, int sequence_id, bool lan_mode)
|
||||||
|
{
|
||||||
|
nlohmann::json j;
|
||||||
|
j["print"]["command"] = "gcode_line";
|
||||||
|
j["print"]["param"] = "G90\n";
|
||||||
|
j["print"]["sequence_id"] = std::to_string(sequence_id);
|
||||||
|
return route_send(lan_mode, dev_id, j.dump());
|
||||||
|
}
|
||||||
|
|
||||||
|
int OrcaPrinterAgent::command_auto_leveling(std::string dev_id, int sequence_id, bool lan_mode)
|
||||||
|
{
|
||||||
|
nlohmann::json j;
|
||||||
|
j["print"]["command"] = "gcode_line";
|
||||||
|
j["print"]["param"] = "G29\n";
|
||||||
|
j["print"]["sequence_id"] = std::to_string(sequence_id);
|
||||||
|
return route_send(lan_mode, dev_id, j.dump());
|
||||||
|
}
|
||||||
|
|
||||||
|
int OrcaPrinterAgent::command_go_home(std::string dev_id, bool is_printing, bool supports_mqtt_homing,
|
||||||
|
int sequence_id, bool lan_mode)
|
||||||
|
{
|
||||||
|
nlohmann::json j;
|
||||||
|
j["print"]["sequence_id"] = std::to_string(sequence_id);
|
||||||
|
if (supports_mqtt_homing) {
|
||||||
|
j["print"]["command"] = "back_to_center";
|
||||||
|
} else {
|
||||||
|
// Preserve the existing safety behavior: never home Z/Y during a print.
|
||||||
|
j["print"]["command"] = "gcode_line";
|
||||||
|
j["print"]["param"] = is_printing ? "G28 X\n" : "G28\n";
|
||||||
|
}
|
||||||
|
return route_send(lan_mode, dev_id, j.dump());
|
||||||
|
}
|
||||||
|
|
||||||
|
int OrcaPrinterAgent::command_set_bed(std::string dev_id, int temp, bool /*supports_mqtt_bed_ctrl*/,
|
||||||
|
int sequence_id, bool lan_mode)
|
||||||
|
{
|
||||||
|
nlohmann::json j;
|
||||||
|
j["print"]["command"] = "set_bed_temp";
|
||||||
|
j["print"]["sequence_id"] = std::to_string(sequence_id);
|
||||||
|
j["print"]["temp"] = temp;
|
||||||
|
return route_send(lan_mode, dev_id, j.dump());
|
||||||
|
}
|
||||||
|
|
||||||
|
int OrcaPrinterAgent::command_set_nozzle(std::string dev_id, int temp, int sequence_id, bool lan_mode)
|
||||||
|
{
|
||||||
|
nlohmann::json j;
|
||||||
|
j["print"]["command"] = "set_nozzle_temp";
|
||||||
|
j["print"]["sequence_id"] = std::to_string(sequence_id);
|
||||||
|
j["print"]["extruder_index"] = 0;
|
||||||
|
j["print"]["target_temp"] = temp;
|
||||||
|
return route_send(lan_mode, dev_id, j.dump());
|
||||||
|
}
|
||||||
|
|
||||||
|
int OrcaPrinterAgent::command_axis_control(std::string dev_id, std::string axis, double unit, double input_val,
|
||||||
|
int /*speed*/, bool is_core_xy, bool /*supports_mqtt_axis_control*/,
|
||||||
|
int sequence_id, bool lan_mode)
|
||||||
|
{
|
||||||
|
std::transform(axis.begin(), axis.end(), axis.begin(), [](unsigned char c) { return static_cast<char>(std::toupper(c)); });
|
||||||
|
if (axis != "X" && axis != "Y" && axis != "Z" && axis != "E") {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "OrcaPrinterAgent: invalid axis control axis=" << axis;
|
||||||
|
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
const double requested_distance = input_val * unit;
|
||||||
|
if (!std::isfinite(requested_distance) || requested_distance == 0.0) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "OrcaPrinterAgent: invalid axis control distance input=" << input_val
|
||||||
|
<< " unit=" << unit;
|
||||||
|
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int direction = requested_distance > 0.0 ? 1 : -1;
|
||||||
|
if (!is_core_xy && (axis == "Y" || axis == "Z"))
|
||||||
|
direction = -direction;
|
||||||
|
|
||||||
|
nlohmann::json j;
|
||||||
|
j["print"]["command"] = "xyz_ctrl";
|
||||||
|
j["print"]["sequence_id"] = std::to_string(sequence_id);
|
||||||
|
j["print"]["axis"] = axis;
|
||||||
|
j["print"]["dir"] = direction;
|
||||||
|
j["print"]["distance"] = requested_distance < 0.0 ? -requested_distance : requested_distance;
|
||||||
|
return route_send(lan_mode, dev_id, j.dump());
|
||||||
|
}
|
||||||
|
|
||||||
bool OrcaPrinterAgent::parse_lan_endpoint(const std::string& dev_ip, std::string& host, std::string& port)
|
bool OrcaPrinterAgent::parse_lan_endpoint(const std::string& dev_ip, std::string& host, std::string& port)
|
||||||
{
|
{
|
||||||
std::string s = dev_ip;
|
std::string s = dev_ip;
|
||||||
@@ -420,6 +564,97 @@ std::string OrcaPrinterAgent::make_lan_client_id(const std::string& dev_id)
|
|||||||
return "orcaslicer-lan-" + dev_id + "-" + suffix;
|
return "orcaslicer-lan-" + dev_id + "-" + suffix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool OrcaPrinterAgent::parse_nonnegative_command_id(const std::string& value, int& result)
|
||||||
|
{
|
||||||
|
if (value.empty())
|
||||||
|
return false;
|
||||||
|
try {
|
||||||
|
std::size_t consumed = 0;
|
||||||
|
const long long parsed = std::stoll(value, &consumed);
|
||||||
|
if (consumed != value.size() || parsed < 0 || parsed > std::numeric_limits<int>::max())
|
||||||
|
return false;
|
||||||
|
result = static_cast<int>(parsed);
|
||||||
|
return true;
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OrcaPrinterAgent::parse_ipcam_info(const std::string& dev_id, const std::string& payload)
|
||||||
|
{
|
||||||
|
const nlohmann::json envelope = nlohmann::json::parse(payload, nullptr, false);
|
||||||
|
if (!envelope.is_object())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto print_it = envelope.find("print");
|
||||||
|
if (print_it == envelope.end() || !print_it->is_object())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const nlohmann::json& print = *print_it;
|
||||||
|
const auto command_it = print.find("command");
|
||||||
|
const bool is_push_status = command_it != print.end() && command_it->is_string() && command_it->get<std::string>() == "push_status";
|
||||||
|
bool is_full_snapshot = false;
|
||||||
|
if (is_push_status) {
|
||||||
|
const auto msg_it = print.find("msg");
|
||||||
|
is_full_snapshot = msg_it == print.end() || (msg_it->is_number_integer() && msg_it->get<int>() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
CameraStreamMode stream_mode = CameraStreamMode::none;
|
||||||
|
std::string stream_url;
|
||||||
|
bool has_camera_update = false;
|
||||||
|
const auto ipcam_it = print.find("ipcam");
|
||||||
|
if (ipcam_it != print.end() && ipcam_it->is_object()) {
|
||||||
|
const auto stream_modes_it = ipcam_it->find("stream_mode");
|
||||||
|
if (stream_modes_it != ipcam_it->end() && stream_modes_it->is_array()) {
|
||||||
|
has_camera_update = true;
|
||||||
|
for (const auto& stream : *stream_modes_it) {
|
||||||
|
if (!stream.is_object())
|
||||||
|
continue;
|
||||||
|
const auto mode_it = stream.find("mode");
|
||||||
|
const auto url_it = stream.find("url");
|
||||||
|
if (mode_it == stream.end() || url_it == stream.end() || !mode_it->is_string() || !url_it->is_string())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const std::string mode = mode_it->get<std::string>();
|
||||||
|
if (mode == "rtsp")
|
||||||
|
stream_mode = CameraStreamMode::rtsp;
|
||||||
|
else if (mode == "http")
|
||||||
|
stream_mode = CameraStreamMode::http;
|
||||||
|
else if (mode == "http_snapshot")
|
||||||
|
stream_mode = CameraStreamMode::http_snapshot;
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
|
||||||
|
stream_url = url_it->get<std::string>();
|
||||||
|
break; // OrcaSonar orders entries by preference.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (is_full_snapshot) {
|
||||||
|
has_camera_update = true;
|
||||||
|
}
|
||||||
|
} else if (is_full_snapshot) {
|
||||||
|
// A full push_status without ipcam means the printer has no camera
|
||||||
|
// stream information. Diff reports omit unchanged domains.
|
||||||
|
has_camera_update = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!has_camera_update)
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(state_mutex);
|
||||||
|
if (m_current_connection != LAN || m_lan_dev_id != dev_id) {
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: ignoring camera update for inactive LAN printer dev_id=" << dev_id;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_camera_stream_mode = stream_mode;
|
||||||
|
m_camera_url = std::move(stream_url);
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: updated camera state dev_id=" << dev_id
|
||||||
|
<< " transport=LAN"
|
||||||
|
<< " mode=" << static_cast<int>(m_camera_stream_mode)
|
||||||
|
<< " url=" << m_camera_url;
|
||||||
|
}
|
||||||
|
|
||||||
std::string OrcaPrinterAgent::lan_connection_target() const
|
std::string OrcaPrinterAgent::lan_connection_target() const
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> l(state_mutex);
|
std::lock_guard<std::mutex> l(state_mutex);
|
||||||
@@ -493,14 +728,20 @@ int OrcaPrinterAgent::connect_printer(std::string dev_id, std::string dev_ip, st
|
|||||||
<< " client_id=" << cfg.client_id;
|
<< " client_id=" << cfg.client_id;
|
||||||
|
|
||||||
OrcaMqttConnection* conn = nullptr;
|
OrcaMqttConnection* conn = nullptr;
|
||||||
|
CurrentConn previous_connection;
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> l(state_mutex);
|
std::lock_guard<std::mutex> l(state_mutex);
|
||||||
|
previous_connection = m_current_connection;
|
||||||
m_lan_dev_id = dev_id;
|
m_lan_dev_id = dev_id;
|
||||||
m_lan_url = cfg.url;
|
m_lan_url = cfg.url;
|
||||||
|
m_camera_stream_mode = CameraStreamMode::none;
|
||||||
|
m_camera_url.clear();
|
||||||
m_current_connection = LAN;
|
m_current_connection = LAN;
|
||||||
lan_mqtt_connection = std::make_unique<OrcaMqttConnection>();
|
lan_mqtt_connection = std::make_unique<OrcaMqttConnection>();
|
||||||
conn = lan_mqtt_connection.get();
|
conn = lan_mqtt_connection.get();
|
||||||
}
|
}
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "OrcaPrinterAgent: selected LAN printer dev_id=" << dev_id
|
||||||
|
<< " transport=" << connection_type_name(previous_connection) << "->LAN";
|
||||||
|
|
||||||
if (m_lan_connect_thread.joinable())
|
if (m_lan_connect_thread.joinable())
|
||||||
m_lan_connect_thread.join(); // disconnect_printer() above already stopped the old conn, so this is fast
|
m_lan_connect_thread.join(); // disconnect_printer() above already stopped the old conn, so this is fast
|
||||||
@@ -561,17 +802,26 @@ int OrcaPrinterAgent::disconnect_printer()
|
|||||||
++m_lan_generation; // fence stale worker callbacks
|
++m_lan_generation; // fence stale worker callbacks
|
||||||
std::unique_ptr<OrcaMqttConnection> doomed;
|
std::unique_ptr<OrcaMqttConnection> doomed;
|
||||||
std::string prev_dev;
|
std::string prev_dev;
|
||||||
|
CurrentConn previous_connection;
|
||||||
|
CurrentConn current_connection;
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> l(state_mutex);
|
std::lock_guard<std::mutex> l(state_mutex);
|
||||||
|
previous_connection = m_current_connection;
|
||||||
doomed = std::move(lan_mqtt_connection);
|
doomed = std::move(lan_mqtt_connection);
|
||||||
prev_dev = m_lan_dev_id;
|
prev_dev = m_lan_dev_id;
|
||||||
m_lan_dev_id.clear();
|
m_lan_dev_id.clear();
|
||||||
if (m_current_connection == LAN)
|
if (m_current_connection == LAN) {
|
||||||
m_current_connection = NONE;
|
m_current_connection = NONE;
|
||||||
|
m_camera_stream_mode = CameraStreamMode::none;
|
||||||
|
m_camera_url.clear();
|
||||||
|
}
|
||||||
|
current_connection = m_current_connection;
|
||||||
}
|
}
|
||||||
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: LAN disconnect generation=" << m_lan_generation.load()
|
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: LAN disconnect generation=" << m_lan_generation.load()
|
||||||
<< " previous_dev_id=" << prev_dev << " had_connection=" << (doomed ? "yes" : "no")
|
<< " previous_dev_id=" << prev_dev << " had_connection=" << (doomed ? "yes" : "no")
|
||||||
<< " connected=" << (doomed && doomed->is_connected() ? "yes" : "no");
|
<< " connected=" << (doomed && doomed->is_connected() ? "yes" : "no")
|
||||||
|
<< " transport=" << connection_type_name(previous_connection) << "->"
|
||||||
|
<< connection_type_name(current_connection);
|
||||||
// Tell the printer to stop pushing and drop the report topic before the socket
|
// Tell the printer to stop pushing and drop the report topic before the socket
|
||||||
// goes away (§3.2/§5.4: deselect issues pushing.stop on both transports).
|
// goes away (§3.2/§5.4: deselect issues pushing.stop on both transports).
|
||||||
if (doomed && !prev_dev.empty() && doomed->is_connected()) {
|
if (doomed && !prev_dev.empty() && doomed->is_connected()) {
|
||||||
@@ -591,14 +841,34 @@ int OrcaPrinterAgent::send_message_to_printer(std::string dev_id, std::string js
|
|||||||
|
|
||||||
int OrcaPrinterAgent::route_send(bool is_lan, const std::string& dev_id, const std::string& json_str)
|
int OrcaPrinterAgent::route_send(bool is_lan, const std::string& dev_id, const std::string& json_str)
|
||||||
{
|
{
|
||||||
|
std::string command = "<unparsed>";
|
||||||
|
try {
|
||||||
|
const nlohmann::json envelope = nlohmann::json::parse(json_str);
|
||||||
|
for (const char* namespace_name : {"pushing", "info", "print", "system", "camera", "xcam", "upgrade", "event", "files"}) {
|
||||||
|
const auto namespace_it = envelope.find(namespace_name);
|
||||||
|
if (namespace_it != envelope.end() && namespace_it->is_object()) {
|
||||||
|
const auto command_it = namespace_it->find("command");
|
||||||
|
if (command_it != namespace_it->end() && command_it->is_string()) {
|
||||||
|
command = std::string(namespace_name) + "." + command_it->get<std::string>();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
// Preserve the transport's existing behavior for malformed payloads;
|
||||||
|
// the printer will report the protocol error asynchronously.
|
||||||
|
}
|
||||||
BOOST_LOG_TRIVIAL(info) << "OrcaPrinterAgent::route_send is_lan=" << is_lan << " dev_id=" << dev_id
|
BOOST_LOG_TRIVIAL(info) << "OrcaPrinterAgent::route_send is_lan=" << is_lan << " dev_id=" << dev_id
|
||||||
<< " payload_bytes=" << json_str.size();
|
<< " command=" << command << " payload_bytes=" << json_str.size();
|
||||||
if (dev_id.empty())
|
if (dev_id.empty())
|
||||||
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
|
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
|
||||||
OrcaMqttConnection* conn = get_appropriate_mqtt_connection(is_lan);
|
OrcaMqttConnection* conn = get_appropriate_mqtt_connection(is_lan);
|
||||||
if (!conn)
|
if (!conn)
|
||||||
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
|
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
|
||||||
return conn->send_request(dev_id, json_str) ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECTION_TO_SERVER_FAILED;
|
const bool queued = conn->send_request(dev_id, json_str);
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "OrcaPrinterAgent::route_send command=" << command << " queued=" << queued
|
||||||
|
<< " is_lan=" << is_lan << " dev_id=" << dev_id;
|
||||||
|
return queued ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECTION_TO_SERVER_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -706,17 +976,40 @@ int OrcaPrinterAgent::set_user_selected_machine(std::string dev_id)
|
|||||||
{
|
{
|
||||||
auto* cloud = get_orca_cloud_agent();
|
auto* cloud = get_orca_cloud_agent();
|
||||||
std::string previous;
|
std::string previous;
|
||||||
|
CurrentConn previous_connection;
|
||||||
|
CurrentConn current_connection;
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(state_mutex);
|
std::lock_guard<std::mutex> lock(state_mutex);
|
||||||
if (dev_id == selected_machine) {
|
previous_connection = m_current_connection;
|
||||||
BOOST_LOG_TRIVIAL(info) << "OrcaPrinterAgent::set_user_selected_machine: unchanged dev_id=" << dev_id;
|
// An empty cloud selection must not clear an independently active LAN
|
||||||
|
// selection. Conversely, selecting a cloud machine with the same id
|
||||||
|
// while LAN is active is still a transport switch and must proceed.
|
||||||
|
const bool same_selection = dev_id == selected_machine;
|
||||||
|
const bool same_transport = dev_id.empty() ? m_current_connection != CLOUD : m_current_connection == CLOUD;
|
||||||
|
if (same_selection && same_transport) {
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "OrcaPrinterAgent::set_user_selected_machine: unchanged dev_id=" << dev_id
|
||||||
|
<< " transport=" << connection_type_name(m_current_connection);
|
||||||
return BAMBU_NETWORK_SUCCESS;
|
return BAMBU_NETWORK_SUCCESS;
|
||||||
}
|
}
|
||||||
previous = selected_machine;
|
previous = selected_machine;
|
||||||
selected_machine = dev_id;
|
selected_machine = dev_id;
|
||||||
|
if (dev_id.empty()) {
|
||||||
|
if (m_current_connection == CLOUD) {
|
||||||
|
m_current_connection = NONE;
|
||||||
|
m_camera_stream_mode = CameraStreamMode::none;
|
||||||
|
m_camera_url.clear();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m_current_connection = CLOUD;
|
||||||
|
m_camera_stream_mode = CameraStreamMode::none;
|
||||||
|
m_camera_url.clear();
|
||||||
|
}
|
||||||
|
current_connection = m_current_connection;
|
||||||
}
|
}
|
||||||
BOOST_LOG_TRIVIAL(info) << "OrcaPrinterAgent::set_user_selected_machine: previous=" << previous << " new=" << dev_id
|
BOOST_LOG_TRIVIAL(info) << "OrcaPrinterAgent::set_user_selected_machine: previous=" << previous << " new=" << dev_id
|
||||||
<< " cloud=" << (cloud ? "set" : "<null>");
|
<< " cloud=" << (cloud ? "set" : "<null>") << " transport="
|
||||||
|
<< connection_type_name(previous_connection) << "->"
|
||||||
|
<< connection_type_name(current_connection);
|
||||||
if (!cloud) {
|
if (!cloud) {
|
||||||
BOOST_LOG_TRIVIAL(warning) << "OrcaPrinterAgent::set_user_selected_machine: no Orca cloud agent";
|
BOOST_LOG_TRIVIAL(warning) << "OrcaPrinterAgent::set_user_selected_machine: no Orca cloud agent";
|
||||||
return BAMBU_NETWORK_SUCCESS;
|
return BAMBU_NETWORK_SUCCESS;
|
||||||
@@ -732,11 +1025,6 @@ int OrcaPrinterAgent::set_user_selected_machine(std::string dev_id)
|
|||||||
conn->send_request(previous, build_pushing_stop(seq(5)));
|
conn->send_request(previous, build_pushing_stop(seq(5)));
|
||||||
cloud->teardown_selected_printer_mqtt();
|
cloud->teardown_selected_printer_mqtt();
|
||||||
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(state_mutex);
|
|
||||||
m_current_connection = dev_id.empty() ? NONE : CLOUD;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_cloud_connect_thread.joinable())
|
if (m_cloud_connect_thread.joinable())
|
||||||
m_cloud_connect_thread.join(); // teardown_selected_printer_mqtt() above stopped the old cloud conn
|
m_cloud_connect_thread.join(); // teardown_selected_printer_mqtt() above stopped the old cloud conn
|
||||||
if (!dev_id.empty()) {
|
if (!dev_id.empty()) {
|
||||||
@@ -845,4 +1133,20 @@ int OrcaPrinterAgent::set_queue_on_main_fn(QueueOnMainFn fn)
|
|||||||
return BAMBU_NETWORK_SUCCESS;
|
return BAMBU_NETWORK_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CameraStreamMode OrcaPrinterAgent::get_camera_stream_mode() const
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(state_mutex);
|
||||||
|
if (m_current_connection == CLOUD)
|
||||||
|
return CameraStreamMode::webrtc;
|
||||||
|
return m_camera_stream_mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string OrcaPrinterAgent::get_camera_url() const
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(state_mutex);
|
||||||
|
if (m_current_connection != LAN)
|
||||||
|
return {};
|
||||||
|
return m_camera_url;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|||||||
@@ -18,12 +18,13 @@ namespace Slic3r {
|
|||||||
class OrcaCloudServiceAgent;
|
class OrcaCloudServiceAgent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OrcaPrinterAgent - Stub implementation for printer operations.
|
* OrcaPrinterAgent - OrcaSonar MQTT printer agent.
|
||||||
*
|
*
|
||||||
* All printer-related operations are currently stubs that return success.
|
* LAN and cloud commands use the same OrcaSonar protocol payloads; only the
|
||||||
* Actual printer connectivity requires the BBL SDK or future Orca implementation.
|
* MQTT connection selected by route_send() differs.
|
||||||
*/
|
*/
|
||||||
class OrcaPrinterAgent : public IPrinterAgent {
|
class OrcaPrinterAgent : public IPrinterAgent
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
explicit OrcaPrinterAgent(std::string log_dir);
|
explicit OrcaPrinterAgent(std::string log_dir);
|
||||||
~OrcaPrinterAgent() override;
|
~OrcaPrinterAgent() override;
|
||||||
@@ -50,12 +51,21 @@ public:
|
|||||||
// Binding
|
// Binding
|
||||||
int ping_bind(std::string ping_code) override;
|
int ping_bind(std::string ping_code) override;
|
||||||
int bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect) override;
|
int bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect) override;
|
||||||
int bind(std::string dev_ip, std::string dev_id, std::string dev_model, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn) override;
|
int bind(std::string dev_ip,
|
||||||
|
std::string dev_id,
|
||||||
|
std::string dev_model,
|
||||||
|
std::string sec_link,
|
||||||
|
std::string timezone,
|
||||||
|
bool improved,
|
||||||
|
OnUpdateStatusFn update_fn) override;
|
||||||
int unbind(std::string dev_id) override;
|
int unbind(std::string dev_id) override;
|
||||||
int request_bind_ticket(std::string* ticket) override;
|
int request_bind_ticket(std::string* ticket) override;
|
||||||
int get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback) override;
|
int get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback) override;
|
||||||
int set_server_callback(OnServerErrFn fn) override;
|
int set_server_callback(OnServerErrFn fn) override;
|
||||||
|
|
||||||
|
CameraStreamMode get_camera_stream_mode() const override;
|
||||||
|
std::string get_camera_url() const override;
|
||||||
|
|
||||||
// Machine Selection
|
// Machine Selection
|
||||||
std::string get_user_selected_machine() override;
|
std::string get_user_selected_machine() override;
|
||||||
int set_user_selected_machine(std::string dev_id) override;
|
int set_user_selected_machine(std::string dev_id) override;
|
||||||
@@ -85,8 +95,28 @@ public:
|
|||||||
int set_on_local_message_fn(OnMessageFn fn) override;
|
int set_on_local_message_fn(OnMessageFn fn) override;
|
||||||
int set_queue_on_main_fn(QueueOnMainFn fn) override;
|
int set_queue_on_main_fn(QueueOnMainFn fn) override;
|
||||||
|
|
||||||
|
int command_ams_refresh_rfid(std::string dev_id, std::string tray_id, int sequence_id, bool lan_mode) override;
|
||||||
|
int command_ams_calibrate(std::string dev_id, int ams_id, int sequence_id, bool lan_mode) override;
|
||||||
|
int command_ams_select_tray(std::string dev_id, std::string tray_id, int sequence_id, bool lan_mode) override;
|
||||||
|
int command_start_camera(std::string dev_id) override;
|
||||||
|
int command_xyz_abs(std::string dev_id, int sequence_id, bool lan_mode) override;
|
||||||
|
int command_auto_leveling(std::string dev_id, int sequence_id, bool lan_mode) override;
|
||||||
|
int command_go_home(std::string dev_id, bool is_printing, bool supports_mqtt_homing, int sequence_id, bool lan_mode) override;
|
||||||
|
int command_set_bed(std::string dev_id, int temp, bool supports_mqtt_bed_ctrl, int sequence_id, bool lan_mode) override;
|
||||||
|
int command_set_nozzle(std::string dev_id, int temp, int sequence_id, bool lan_mode) override;
|
||||||
|
int command_axis_control(std::string dev_id,
|
||||||
|
std::string axis,
|
||||||
|
double unit,
|
||||||
|
double input_val,
|
||||||
|
int speed,
|
||||||
|
bool is_core_xy,
|
||||||
|
bool supports_mqtt_axis_control,
|
||||||
|
int sequence_id,
|
||||||
|
bool lan_mode) override;
|
||||||
|
|
||||||
// Test-only: drive emit_connect_sequence directly (no socket).
|
// Test-only: drive emit_connect_sequence directly (no socket).
|
||||||
void run_connect_sequence_for_test(const std::string& dev_id) {
|
void run_connect_sequence_for_test(const std::string& dev_id)
|
||||||
|
{
|
||||||
emit_connect_sequence(dev_id, [](const std::string&) {}, [](const std::string&) {});
|
emit_connect_sequence(dev_id, [](const std::string&) {}, [](const std::string&) {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,6 +130,10 @@ protected:
|
|||||||
// thread via queue_on_main_fn when set). Body of every connection's MessageHandler.
|
// thread via queue_on_main_fn when set). Body of every connection's MessageHandler.
|
||||||
void deliver_to_sink(const std::string& dev_id, const std::string& payload);
|
void deliver_to_sink(const std::string& dev_id, const std::string& payload);
|
||||||
|
|
||||||
|
// Extract OrcaSonar's print.ipcam.stream_mode from LAN reports before they
|
||||||
|
// are forwarded to the GUI. The getters below then read this agent-owned state.
|
||||||
|
void parse_ipcam_info(const std::string& dev_id, const std::string& payload);
|
||||||
|
|
||||||
// LAN has a separate callback in the existing IPrinterAgent contract because the
|
// LAN has a separate callback in the existing IPrinterAgent contract because the
|
||||||
// GUI parses local reports with the "lan" dialect and looks up local machines.
|
// GUI parses local reports with the "lan" dialect and looks up local machines.
|
||||||
void deliver_to_local_sink(const std::string& dev_id, const std::string& payload);
|
void deliver_to_local_sink(const std::string& dev_id, const std::string& payload);
|
||||||
@@ -140,11 +174,12 @@ private:
|
|||||||
std::string log_dir;
|
std::string log_dir;
|
||||||
std::string selected_machine;
|
std::string selected_machine;
|
||||||
|
|
||||||
enum CurrentConn {
|
enum CurrentConn { NONE, CLOUD, LAN };
|
||||||
NONE,
|
static const char* connection_type_name(CurrentConn connection);
|
||||||
CLOUD,
|
|
||||||
LAN
|
// The transport for the printer currently selected by the UI. LAN and
|
||||||
};
|
// cloud sessions have separate connection objects, so this is selection
|
||||||
|
// state rather than an inference from whichever socket happens to exist.
|
||||||
CurrentConn m_current_connection = NONE;
|
CurrentConn m_current_connection = NONE;
|
||||||
|
|
||||||
std::shared_ptr<ICloudServiceAgent> m_cloud_agent;
|
std::shared_ptr<ICloudServiceAgent> m_cloud_agent;
|
||||||
@@ -166,10 +201,13 @@ private:
|
|||||||
|
|
||||||
std::string m_lan_dev_id; // guarded by state_mutex
|
std::string m_lan_dev_id; // guarded by state_mutex
|
||||||
std::string m_lan_url; // guarded by state_mutex — the Config.url of the live LAN session
|
std::string m_lan_url; // guarded by state_mutex — the Config.url of the live LAN session
|
||||||
|
CameraStreamMode m_camera_stream_mode = CameraStreamMode::none; // guarded by state_mutex
|
||||||
|
std::string m_camera_url; // guarded by state_mutex
|
||||||
|
|
||||||
OrcaCloudServiceAgent* get_orca_cloud_agent();
|
OrcaCloudServiceAgent* get_orca_cloud_agent();
|
||||||
|
|
||||||
OrcaMqttConnection* get_appropriate_mqtt_connection(bool is_lan = true);
|
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
|
// 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.
|
// per-printer connection. The uniform send path for both send_message* overrides.
|
||||||
|
|||||||
Reference in New Issue
Block a user