From f5741e24c84e3fd5a6958265f6bd2381471a491f Mon Sep 17 00:00:00 2001 From: sudharsan gandhi Date: Thu, 30 Jul 2026 16:15:20 -0400 Subject: [PATCH] Fix device page and camera streaming over LAN connections Three related defects leave the device page degraded and the camera dark on any locally connected printer. Present on main, develop, release_0715 and beta_hunse. 1. ComGetDevProductDetail reported the whole command unsupported on LAN. The network library does expose LAN equivalents, already used individually by ComGetDevProduct and ComGetDevDetail a few lines above, so devProduct and devDetail stayed null on every LAN connection and each capability the device page reads from them was silently absent: camera, lightCtrlState, internalFanCtrlState. Now fetches both via getLanDevProduct and getLanDevDetail, freeing the product if the detail call fails. 2. ComCameraStreamCtrl also returned COM_UNSUPPORTED on LAN, and here there is genuinely no ctrlLanDev* counterpart to fnet_camera_stream_ctrl_t. But SingleDeviceState issues the "open" command precisely when connectMode is LAN, and the camera only serves frames once explicitly opened, so the stream could never start. Implemented the LAN path as a POST to the printer's local /control endpoint: the same request the library performs for lightControl_cmd via fnet_ctrlLanDevLight, using the ip, port, serialNumber and checkCode already carried in com_command_exec_data_t. 3. SingleDeviceState::fillValue dereferenced data.devDetail unconditionally, including on the WAN path where status is read from wanDevInfo instead. devDetail can legitimately be null when the fetch has not run or failed, so this was a live null dereference; it now returns early. fillValue also did nothing when the printer reported no stream URL, which happens on LAN, and now falls back to the local MJPEG endpoint. Light control is unaffected: ComLightCtrl already had a working LAN branch, which is why lights respond locally while the camera does not. --- src/slic3r/GUI/FlashForge/ComCommand.cpp | 64 +++++++++++++++++++ src/slic3r/GUI/FlashForge/ComCommand.hpp | 38 ++++++++++- .../GUI/FlashForge/SingleDeviceState.cpp | 21 +++++- 3 files changed, 119 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/FlashForge/ComCommand.cpp b/src/slic3r/GUI/FlashForge/ComCommand.cpp index df6696a981..95b3eafab7 100644 --- a/src/slic3r/GUI/FlashForge/ComCommand.cpp +++ b/src/slic3r/GUI/FlashForge/ComCommand.cpp @@ -1,7 +1,71 @@ #include "ComCommand.hpp" +#include +#include + +#include "slic3r/Utils/Http.hpp" + namespace Slic3r { namespace GUI { int ComCommand::s_commandNum = ComInvalidCommandId + 1; +ComErrno ComCameraStreamCtrl::sendLanCameraStreamCtrl(const com_command_exec_data_t &data, + const std::string &action) +{ + if (data.ip == nullptr || data.serialNumber == nullptr || data.checkCode == nullptr) { + return COM_ERROR; + } + // Same request shape the network library uses for the other LAN control + // commands (see fnet_ctrlLanDevLight): a POST to /control on the printer's + // local HTTP port, authenticated by serial number plus check code, wrapping + // the command and its arguments in a "payload" object. + nlohmann::json body; + body["serialNumber"] = data.serialNumber; + body["checkCode"] = data.checkCode; + body["payload"]["cmd"] = "streamCtrl_cmd"; + body["payload"]["args"] = {{"action", action}}; + + std::string url = std::string("http://") + data.ip + ":" + std::to_string(data.port) + "/control"; + + ComErrno result = COM_ERROR; + // Deliberately not logging the body: it carries the serial number and check + // code, and these lines end up pasted into bug reports. + BOOST_LOG_TRIVIAL(info) << "sendLanCameraStreamCtrl: streamCtrl_cmd action=" << action; + + Http::post(url) + .header("Content-Type", "application/json") + .timeout_connect(ComTimeoutLanA / 1000) + .timeout_max(ComTimeoutLanA / 1000) + .set_post_body(body.dump()) + .on_complete([&result](std::string reply, unsigned status) { + if (status < 200 || status >= 300) { + BOOST_LOG_TRIVIAL(warning) << "sendLanCameraStreamCtrl: http status " << status; + return; + } + // The printer answers {"code":0,...} on success. Treat a missing or + // unparsable code as success so a firmware that only varies its + // reply shape does not disable the camera, since a 2xx already means + // the command was accepted. + try { + nlohmann::json json = nlohmann::json::parse(reply); + if (json.contains("code") && json["code"].is_number_integer() && + json["code"].get() != 0) { + BOOST_LOG_TRIVIAL(warning) << "sendLanCameraStreamCtrl: printer returned code " + << json["code"].get(); + return; + } + } catch (const std::exception &) { + BOOST_LOG_TRIVIAL(debug) << "sendLanCameraStreamCtrl: reply was not json"; + } + result = COM_OK; + }) + .on_error([](std::string, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(warning) << "sendLanCameraStreamCtrl failed, status " << status + << ", " << error; + }) + .perform_sync(); + + return result; +} + }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/FlashForge/ComCommand.hpp b/src/slic3r/GUI/FlashForge/ComCommand.hpp index 3cd7c6064e..adb3a22003 100644 --- a/src/slic3r/GUI/FlashForge/ComCommand.hpp +++ b/src/slic3r/GUI/FlashForge/ComCommand.hpp @@ -116,7 +116,26 @@ class ComGetDevProductDetail : public ComCommand { int ret; if (data.connectMode == COM_CONNECT_LAN) { - return COM_UNSUPPORTED; + // The network library exposes LAN equivalents of the WAN + // product/detail call, and they are already used individually by + // ComGetDevProduct and ComGetDevDetail above. Fetch both here + // rather than reporting the whole command unsupported: while this + // returned COM_UNSUPPORTED, devProduct and devDetail stayed null on + // every LAN connection, so each capability the device page reads + // from them (camera, lightCtrlState, internalFanCtrlState, ...) was + // silently absent. + ret = data.networkIntfc->getLanDevProduct(data.ip, data.port, data.serialNumber, + data.checkCode, &m_devProduct, ComTimeoutLanA); + if (ret != FNET_OK) { + return MultiComUtils::fnetRet2ComErrno(ret); + } + ret = data.networkIntfc->getLanDevDetail(data.ip, data.port, data.serialNumber, + data.checkCode, &m_devDetail, ComTimeoutLanA); + if (ret != FNET_OK) { + // Don't leak the product that was just fetched. + data.networkIntfc->freeDevProduct(m_devProduct); + m_devProduct = nullptr; + } } else { ret = data.networkIntfc->getWanDevProductDetail(data.clientId, data.accessToken, data.devId, &m_devProduct, &m_devDetail, ComTimeoutWanB); @@ -762,13 +781,28 @@ class ComCameraStreamCtrl : public ComCommand ComErrno exec(const com_command_exec_data_t &data) { if (data.connectMode == COM_CONNECT_LAN) { - return COM_UNSUPPORTED; + // The network library has no LAN camera entry point (there is no + // ctrlLanDev* counterpart to fnet_camera_stream_ctrl_t), so this + // previously returned COM_UNSUPPORTED. SingleDeviceState issues the + // "open" command precisely on a LAN connection, which meant the + // stream was never started and the device page showed no video on + // any locally connected printer. + // + // Post the command over the printer's local HTTP control endpoint + // instead. This is the same request the library performs for + // lightControl_cmd via fnet_ctrlLanDevLight, using the same + // ip/port/serialNumber/checkCode already carried in exec data. + return sendLanCameraStreamCtrl(data, m_action); } else { return ComWanConn::inst()->sendCameraStreamCtrl(data.devTopic, m_cameraStreamCtrl); } } private: + // Implemented in ComCommand.cpp to keep the HTTP client out of this header. + static ComErrno sendLanCameraStreamCtrl(const com_command_exec_data_t &data, + const std::string &action); + std::string m_action; fnet_camera_stream_ctrl_t m_cameraStreamCtrl; }; diff --git a/src/slic3r/GUI/FlashForge/SingleDeviceState.cpp b/src/slic3r/GUI/FlashForge/SingleDeviceState.cpp index 806ef27a82..b4ba996fdd 100644 --- a/src/slic3r/GUI/FlashForge/SingleDeviceState.cpp +++ b/src/slic3r/GUI/FlashForge/SingleDeviceState.cpp @@ -3348,6 +3348,13 @@ wxString SingleDeviceState::convertSecondsToHMS(int totalSeconds) void SingleDeviceState::fillValue(const com_dev_data_t& data,bool wanDev) { + // devDetail is filled in by ComGetDevProductDetail, which can legitimately + // fail or not have run yet. Every read below goes through it, so bail out + // instead of dereferencing null. + if (data.devDetail == nullptr) { + BOOST_LOG_TRIVIAL(debug) << "fillValue: devDetail not available yet"; + return; + } std::string state = data.devDetail->status; // 状态 if (wanDev) { state = data.wanDevInfo.status; @@ -3361,14 +3368,24 @@ void SingleDeviceState::fillValue(const com_dev_data_t& data,bool wanDev) m_busy_lamp_bar->SetCameraState(false); m_idle_lamp_bar->SetCameraState(false); std::string stram_url = data.devDetail->cameraStreamUrl; - if (!stram_url.empty() && m_camera_stream_url != data.devDetail->cameraStreamUrl) { + if (stram_url.empty() && data.connectMode == COM_CONNECT_LAN && + data.lanDevInfo.ip[0] != '\0') { + // Some firmware reports camera support without also reporting a stream + // URL on a LAN connection. The camera serves the usual MJPEG endpoint + // regardless, so fall back to the local address rather than leaving the + // panel with nothing to load. + constexpr unsigned short cameraStreamPort = 8080; + stram_url = std::string("http://") + data.lanDevInfo.ip + ":" + + std::to_string(cameraStreamPort) + "/?action=stream"; + } + if (!stram_url.empty() && m_camera_stream_url != stram_url) { if (0 == data.connectMode) { // 通知设备开流 ComCameraStreamCtrl *cameraStreamCtrl = new ComCameraStreamCtrl(OPEN); Slic3r::GUI::MultiComMgr::inst()->putCommand(m_cur_id, cameraStreamCtrl); } - m_camera_stream_url = data.devDetail->cameraStreamUrl; + m_camera_stream_url = stram_url; m_camera_panel->setStreamUrl(m_camera_stream_url); } else if (stram_url.empty()) { m_camera_panel->setOffline();