feat: parse nozzle information for qidi and moonraker printer agents

This commit is contained in:
Ian Chua
2026-08-20 20:11:31 +08:00
parent c4bbcd322b
commit 0d0d281d0c
3 changed files with 119 additions and 4 deletions

View File

@@ -1364,12 +1364,57 @@ bool MoonrakerPrinterAgent::init_device_info(std::string dev_id, std::string dev
return true;
}
float MoonrakerPrinterAgent::parse_nozzle_diameter(const nlohmann::json& response)
{
const nlohmann::json* status = nullptr;
if (response.contains("result") && response["result"].is_object()) {
status = response["result"].contains("status") ? &response["result"]["status"] : &response["result"];
} else if (response.contains("status")) {
status = &response["status"];
}
if (status == nullptr || !status->is_object() || !status->contains("configfile") || !(*status)["configfile"].is_object()) {
return 0.0f;
}
const auto& configfile = (*status)["configfile"];
for (const char* key : {"settings", "config"}) {
if (!configfile.contains(key) || !configfile[key].is_object()) {
continue;
}
for (const auto& item : configfile[key].items()) {
if (item.key() != "extruder" && item.key().rfind("extruder", 0) != 0) {
continue;
}
const auto& section = item.value();
if (!section.is_object() || !section.contains("nozzle_diameter")) {
continue;
}
const auto& value = section["nozzle_diameter"];
try {
if (value.is_number()) {
return value.get<float>();
}
if (value.is_string()) {
return std::stof(value.get<std::string>());
}
} catch (...) {
return 0.0f;
}
}
}
return 0.0f;
}
bool MoonrakerPrinterAgent::fetch_device_info(const std::string& base_url,
const std::string& api_key,
MoonrakerDeviceInfo& info,
std::string& error) const
{
auto fetch_json = [&](const std::string& url, nlohmann::json& out) {
auto fetch_json = [&](const std::string& url, nlohmann::json& out, std::string& fetch_error) {
std::string response_body;
bool success = false;
std::string http_error;
@@ -1397,13 +1442,13 @@ bool MoonrakerPrinterAgent::fetch_device_info(const std::string& base_url,
.perform_sync();
if (!success) {
error = http_error.empty() ? "Connection failed" : http_error;
fetch_error = http_error.empty() ? "Connection failed" : http_error;
return false;
}
out = nlohmann::json::parse(response_body, nullptr, false, true);
if (out.is_discarded()) {
error = "Invalid JSON response";
fetch_error = "Invalid JSON response";
return false;
}
return true;
@@ -1411,7 +1456,7 @@ bool MoonrakerPrinterAgent::fetch_device_info(const std::string& base_url,
nlohmann::json json;
std::string url = join_url(base_url, "/server/info");
if (!fetch_json(url, json)) {
if (!fetch_json(url, json, error)) {
return false;
}
@@ -1420,6 +1465,17 @@ bool MoonrakerPrinterAgent::fetch_device_info(const std::string& base_url,
info.version = result.value("moonraker_version", "");
info.klippy_state = result.value("klippy_state", "");
// nozzle_diameter is part of Klipper's configfile object rather than the live
// extruder status object. Keep this optional so older/custom Moonraker builds
// remain connectable when they do not expose configfile through the API.
nlohmann::json config_response;
std::string config_error;
if (fetch_json(join_url(base_url, "/printer/objects/query?configfile"), config_response, config_error)) {
info.nozzle_diameter = parse_nozzle_diameter(config_response);
} else {
BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: nozzle configuration unavailable: " << config_error;
}
return true;
}
@@ -2461,6 +2517,14 @@ nlohmann::json MoonrakerPrinterAgent::build_print_payload_locked() const
payload["print"]["nozzle_temp_range"] = {100, 370}; // Typical Klipper range
payload["print"]["bed_temp_range"] = {0, 120}; // Typical bed range
// MachineObject::parse_json routes nozzle_diameter through the legacy nozzle
// parser only when nozzle_type is present as well. Moonraker/Klipper exposes
// the diameter but not Bambu's nozzle type, so use the parser's neutral value.
if (device_info.nozzle_diameter > 0.0f) {
payload["print"]["nozzle_diameter"] = device_info.nozzle_diameter;
payload["print"]["nozzle_type"] = "N/A";
}
payload["print"]["support_send_to_sd"] = true;
if (!webcam_stream_url.empty()) {
payload["print"]["ipcam"]["ipcam_dev"] = "1";
@@ -2816,6 +2880,7 @@ void MoonrakerPrinterAgent::perform_connection_async(const std::string& dev_id,
device_info.dev_name = fetched_info.dev_name;
device_info.version = fetched_info.version;
device_info.klippy_state = fetched_info.klippy_state;
device_info.nozzle_diameter = fetched_info.nozzle_diameter;
}
// Orca todo: disable websocket for now, as we don't use MonitorPanel for Moonraker printers yet

View File

@@ -90,6 +90,7 @@ protected:
std::string dev_name;
std::string version;
std::string klippy_state;
float nozzle_diameter = 0.0f;
bool use_ssl = false;
} device_info;
@@ -110,6 +111,7 @@ protected:
// Methods that derived classes may need to override or access
virtual bool init_device_info(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl);
virtual bool fetch_device_info(const std::string& base_url, const std::string& api_key, MoonrakerDeviceInfo& info, std::string& error) const;
static float parse_nozzle_diameter(const nlohmann::json& response);
// State access for derived classes
mutable std::recursive_mutex state_mutex;

View File

@@ -17,6 +17,54 @@
using namespace Slic3r;
namespace py = pybind11;
class MoonrakerParserProbe : public MoonrakerPrinterAgent
{
public:
using MoonrakerPrinterAgent::parse_nozzle_diameter;
explicit MoonrakerParserProbe(std::string log_dir) : MoonrakerPrinterAgent(std::move(log_dir)) {}
};
TEST_CASE("Moonraker parses nozzle diameter from configfile settings", "[unit][moonraker]")
{
const auto response = nlohmann::json::parse(R"({
"result": {
"status": {
"configfile": {
"settings": {
"extruder": {
"nozzle_diameter": 0.6
}
}
}
}
}
})");
CHECK(MoonrakerParserProbe::parse_nozzle_diameter(response) == Catch::Approx(0.6f));
}
TEST_CASE("Moonraker parses nozzle diameter from raw config and tolerates missing data", "[unit][moonraker]")
{
const auto raw_config_response = nlohmann::json::parse(R"({
"result": {
"status": {
"configfile": {
"config": {
"extruder": {
"nozzle_diameter": "0.8"
}
}
}
}
}
})");
const auto missing_response = nlohmann::json::object();
CHECK(MoonrakerParserProbe::parse_nozzle_diameter(raw_config_response) == Catch::Approx(0.8f));
CHECK(MoonrakerParserProbe::parse_nozzle_diameter(missing_response) == 0.0f);
}
// why: these builders preserve the Bambu firmware dialect byte-for-byte, including its trailing space.
TEST_CASE("unit: BBL AMS gcode builders preserve command bytes", "[unit][bbl]")
{