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();