Compare commits

..
Author SHA1 Message Date
Ian Chua 576cce2f72 test: bump elegoo profile to test OFL OTA E2E [TO BE REVERTED] (#15954)
Merged by /bot merge on behalf of @peachismomo (id 52488812).
Grants: resources/profiles/OrcaFilamentLibrary/filament/Elegoo, resources/profiles/OrcaFilamentLibrary.json, resources/profiles/Elegoo, resources/profiles/Elegoo.json
Head: d4a18c633e
2026-09-28 10:47:41 +00:00
Kenneth Rapleeandyw4z f3a8f711fd Catch Standard_Failure before std::exception (OCCT >= 8) (#15826)
Co-authored-by: yw4z <ywsyildiz@gmail.com>
2026-09-28 13:44:32 +03:00
Kenneth Rapleeandyw4z 00bde9265b Add missing OCCT (8.x) header includes (#15825)
* Add missing TopTools includes to GeometryEngine.cpp

* Add missing TDF_LabelSequence include in STEP.cpp

---------

Co-authored-by: yw4z <ywsyildiz@gmail.com>
2026-09-28 13:42:25 +03:00
112 changed files with 1522 additions and 10828 deletions
+1 -42
View File
@@ -834,37 +834,6 @@ find_package(OpenSSL REQUIRED)
find_package(CURL REQUIRED) find_package(CURL REQUIRED)
find_package(Freetype REQUIRED) find_package(Freetype REQUIRED)
if (SLIC3R_GUI)
# LibDataChannel's installed export references its bundled dependencies,
# but does not install their CMake targets. Recreate those targets from
# the same dependency prefix before loading the LibDataChannel config.
if (NOT TARGET Usrsctp::usrsctp)
find_library(_ORCA_USRSCTP_LIBRARY NAMES usrsctp
PATHS "${CMAKE_PREFIX_PATH}/lib" NO_DEFAULT_PATH)
if (_ORCA_USRSCTP_LIBRARY)
add_library(Usrsctp::usrsctp UNKNOWN IMPORTED GLOBAL)
set_target_properties(Usrsctp::usrsctp PROPERTIES
IMPORTED_LOCATION "${_ORCA_USRSCTP_LIBRARY}"
IMPORTED_LINK_INTERFACE_LANGUAGES C
INTERFACE_LINK_LIBRARIES "Threads::Threads")
endif()
endif()
if (NOT TARGET LibJuice::LibJuice)
find_library(_ORCA_LIBJUICE_LIBRARY NAMES juice
PATHS "${CMAKE_PREFIX_PATH}/lib" NO_DEFAULT_PATH)
if (_ORCA_LIBJUICE_LIBRARY)
add_library(LibJuice::LibJuice UNKNOWN IMPORTED GLOBAL)
set_target_properties(LibJuice::LibJuice PROPERTIES
IMPORTED_LOCATION "${_ORCA_LIBJUICE_LIBRARY}"
IMPORTED_LINK_INTERFACE_LANGUAGES C
INTERFACE_LINK_LIBRARIES "Threads::Threads")
endif()
endif()
find_package(LibDataChannel CONFIG REQUIRED)
endif()
add_library(libcurl INTERFACE) add_library(libcurl INTERFACE)
target_link_libraries(libcurl INTERFACE CURL::libcurl) target_link_libraries(libcurl INTERFACE CURL::libcurl)
@@ -1145,7 +1114,6 @@ function(orcaslicer_copy_dlls target config postfix output_dlls)
endif () endif ()
file(COPY ${_occt_dlls} file(COPY ${_occt_dlls}
${CMAKE_PREFIX_PATH}/bin/freetype.dll ${CMAKE_PREFIX_PATH}/bin/freetype.dll
${CMAKE_PREFIX_PATH}/bin/avformat-61.dll
${CMAKE_PREFIX_PATH}/bin/avcodec-61.dll ${CMAKE_PREFIX_PATH}/bin/avcodec-61.dll
${CMAKE_PREFIX_PATH}/bin/swresample-5.dll ${CMAKE_PREFIX_PATH}/bin/swresample-5.dll
${CMAKE_PREFIX_PATH}/bin/swscale-8.dll ${CMAKE_PREFIX_PATH}/bin/swscale-8.dll
@@ -1158,7 +1126,6 @@ function(orcaslicer_copy_dlls target config postfix output_dlls)
${_out_dir}/WebView2Loader.dll ${_out_dir}/WebView2Loader.dll
${_out_dir}/freetype.dll ${_out_dir}/freetype.dll
${_out_dir}/avformat-61.dll
${_out_dir}/avcodec-61.dll ${_out_dir}/avcodec-61.dll
${_out_dir}/swresample-5.dll ${_out_dir}/swresample-5.dll
${_out_dir}/swscale-8.dll ${_out_dir}/swscale-8.dll
@@ -1182,10 +1149,7 @@ function(orcaslicer_copy_sos target config postfix output_sos)
set(_out_dir "${CMAKE_CURRENT_BINARY_DIR}") set(_out_dir "${CMAKE_CURRENT_BINARY_DIR}")
endif () endif ()
file(COPY ${CMAKE_PREFIX_PATH}/lib/libavformat.so file(COPY ${CMAKE_PREFIX_PATH}/lib/libavcodec.so
${CMAKE_PREFIX_PATH}/lib/libavformat.so.61
${CMAKE_PREFIX_PATH}/lib/libavformat.so.61.1.100
${CMAKE_PREFIX_PATH}/lib/libavcodec.so
${CMAKE_PREFIX_PATH}/lib/libavcodec.so.61 ${CMAKE_PREFIX_PATH}/lib/libavcodec.so.61
${CMAKE_PREFIX_PATH}/lib/libavcodec.so.61.3.100 ${CMAKE_PREFIX_PATH}/lib/libavcodec.so.61.3.100
${CMAKE_PREFIX_PATH}/lib/libavutil.so ${CMAKE_PREFIX_PATH}/lib/libavutil.so
@@ -1200,9 +1164,6 @@ function(orcaslicer_copy_sos target config postfix output_sos)
DESTINATION ${_out_dir}) DESTINATION ${_out_dir})
set(${output_sos} set(${output_sos}
${_out_dir}/libavformat.so
${_out_dir}/libavformat.so.61
${_out_dir}/libavformat.so.61.1.100
${_out_dir}/libavcodec.so ${_out_dir}/libavcodec.so
${_out_dir}/libavcodec.so.61 ${_out_dir}/libavcodec.so.61
${_out_dir}/libavcodec.so.61.3.100 ${_out_dir}/libavcodec.so.61.3.100
@@ -1332,8 +1293,6 @@ endif ()
if (CMAKE_SYSTEM_NAME STREQUAL "Linux") if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(LIBRARY_FILES set(LIBRARY_FILES
${LIBDIR_BIN}/libavformat.so.61
${LIBDIR_BIN}/libavformat.so.61.1.100
${LIBDIR_BIN}/libavcodec.so.61 ${LIBDIR_BIN}/libavcodec.so.61
${LIBDIR_BIN}/libavcodec.so.61.3.100 ${LIBDIR_BIN}/libavcodec.so.61.3.100
${LIBDIR_BIN}/libavutil.so.59 ${LIBDIR_BIN}/libavutil.so.59
+6 -18
View File
@@ -164,7 +164,7 @@ if (NOT _is_multi AND NOT CMAKE_BUILD_TYPE)
endif () endif ()
function(orcaslicer_add_cmake_project projectname) function(orcaslicer_add_cmake_project projectname)
cmake_parse_arguments(P_ARGS "FORWARD_CONFIG" "INSTALL_DIR;BUILD_COMMAND;INSTALL_COMMAND;SOURCE_DIR" "CMAKE_ARGS" ${ARGN}) cmake_parse_arguments(P_ARGS "FORWARD_CONFIG" "INSTALL_DIR;BUILD_COMMAND;INSTALL_COMMAND" "CMAKE_ARGS" ${ARGN})
# MSVC is true for clang-cl as well, so the sub-build toolchain has to key on the # MSVC is true for clang-cl as well, so the sub-build toolchain has to key on the
# generator. A non-Visual-Studio superbuild passes its own generator down, and with # generator. A non-Visual-Studio superbuild passes its own generator down, and with
@@ -210,18 +210,12 @@ function(orcaslicer_add_cmake_project projectname)
set(_build_j "-j${NPROC}") set(_build_j "-j${NPROC}")
endif () endif ()
set(_source_dir_arg "")
if (P_ARGS_SOURCE_DIR)
set(_source_dir_arg SOURCE_DIR ${P_ARGS_SOURCE_DIR})
endif ()
if (NOT IS_CROSS_COMPILE OR NOT APPLE) if (NOT IS_CROSS_COMPILE OR NOT APPLE)
ExternalProject_Add( ExternalProject_Add(
dep_${projectname} dep_${projectname}
EXCLUDE_FROM_ALL ON EXCLUDE_FROM_ALL ON
INSTALL_DIR ${DESTDIR} INSTALL_DIR ${DESTDIR}
DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/${projectname} DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/${projectname}
${_source_dir_arg}
${_gen} ${_gen}
CMAKE_ARGS CMAKE_ARGS
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_POLICY_VERSION_MINIMUM=3.5
@@ -255,14 +249,12 @@ if (NOT IS_CROSS_COMPILE OR NOT APPLE)
# note for future devs: shared libs may actually create a size reduction # note for future devs: shared libs may actually create a size reduction
# but orcaslicer_deps tends to get really funny regarding linking after that (notably boost) # but orcaslicer_deps tends to get really funny regarding linking after that (notably boost)
# so, as much as I would like to use that, it's not happening # so, as much as I would like to use that, it's not happening
if (NOT P_ARGS_SOURCE_DIR)
ExternalProject_Add_Step(dep_${projectname} free_download_space ExternalProject_Add_Step(dep_${projectname} free_download_space
DEPENDEES download # do after download DEPENDEES download # do after download
COMMENT "Freeing Space: Removing source archive" COMMENT "Freeing Space: Removing source archive"
WORKING_DIRECTORY ${DEP_DOWNLOAD_DIR} WORKING_DIRECTORY ${DEP_DOWNLOAD_DIR}
COMMAND ${CMAKE_COMMAND} -E rm -rf ${projectname} COMMAND ${CMAKE_COMMAND} -E rm -r ${projectname}
) )
endif ()
ExternalProject_Add_Step(dep_${projectname} free_build_space ExternalProject_Add_Step(dep_${projectname} free_build_space
DEPENDEES install # do after install DEPENDEES install # do after install
COMMENT "Freeing Space: Removing source and build files" COMMENT "Freeing Space: Removing source and build files"
@@ -276,7 +268,6 @@ else()
EXCLUDE_FROM_ALL ON EXCLUDE_FROM_ALL ON
INSTALL_DIR ${DESTDIR} INSTALL_DIR ${DESTDIR}
DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/${projectname} DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/${projectname}
${_source_dir_arg}
${_gen} ${_gen}
CMAKE_ARGS CMAKE_ARGS
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_POLICY_VERSION_MINIMUM=3.5
@@ -405,6 +396,10 @@ include(libnoise/libnoise.cmake)
include(Draco/Draco.cmake) include(Draco/Draco.cmake)
include(FFMPEG/FFMPEG.cmake)
include(Assimp/Assimp.cmake)
# I *think* 1.1 is used for *just* md5 hashing? # I *think* 1.1 is used for *just* md5 hashing?
# 3.1 has everything in the right place, but the md5 funcs used are deprecated # 3.1 has everything in the right place, but the md5 funcs used are deprecated
# a grep across the repo shows it is used for other things # a grep across the repo shows it is used for other things
@@ -415,12 +410,6 @@ if(NOT OPENSSL_FOUND)
set(OPENSSL_PKG dep_OpenSSL) set(OPENSSL_PKG dep_OpenSSL)
endif() endif()
include(FFMPEG/FFMPEG.cmake)
include(Assimp/Assimp.cmake)
include(DataChannel/DataChannel.cmake)
set(DATACHANNEL_PKG dep_DataChannel)
# we don't want to load a "wrong" openssl when loading curl # we don't want to load a "wrong" openssl when loading curl
# so, just don't even bother # so, just don't even bother
# ...i think this is how it works? change if wrong # ...i think this is how it works? change if wrong
@@ -494,7 +483,6 @@ set(_dep_list
dep_wxInspector dep_wxInspector
dep_FFMPEG dep_FFMPEG
dep_Assimp dep_Assimp
${DATACHANNEL_PKG}
) )
if (MSVC) if (MSVC)
-37
View File
@@ -1,37 +0,0 @@
# libdatachannel is the native ICE/DTLS/SCTP implementation used by the
# GUI WebRTC camera controller. Keep the source revision fixed: the signaling
# protocol is evolving independently of this transport dependency.
#
# It vendors plog, usrsctp and libjuice as git submodules, which a plain
# GitHub tag tarball does not include. The flatpak sandbox has no network
# access during the build, so there the manifest itself clones the repo
# (submodules and all) into the dependency download directory before the
# sandbox closes. ExternalProject_Add is pointed at that existing checkout
# instead of being given its own network-dependent download method.
if (FLATPAK)
set(_datachannel_source
SOURCE_DIR ${DEP_DOWNLOAD_DIR}/DataChannel
)
else()
set(_datachannel_source
GIT_REPOSITORY https://github.com/paullouisageneau/libdatachannel.git
GIT_TAG v0.24.5
GIT_SHALLOW ON
GIT_SUBMODULES_RECURSE ON
)
endif()
orcaslicer_add_cmake_project(DataChannel
DEPENDS ${OPENSSL_PKG}
CMAKE_ARGS
-DNO_EXAMPLES=ON
-DNO_TESTS=ON
-DNO_WEBSOCKET=ON
-DNO_MEDIA=ON
-DUSE_NICE=OFF
-DUSE_SYSTEM_JUICE=OFF
-DUSE_SYSTEM_USRSCTP=OFF
-DOPENSSL_ROOT_DIR:PATH=${DESTDIR}
-DOPENSSL_USE_STATIC_LIBS=ON
${_datachannel_source}
)
+7 -23
View File
@@ -1,26 +1,14 @@
set(_conf_cmd ./configure) set(_conf_cmd ./configure)
set(_ffmpeg_depends)
set(_ffmpeg_configure_command ${_conf_cmd})
if (TARGET dep_OpenSSL)
set(_ffmpeg_depends DEPENDS dep_OpenSSL)
set(_ffmpeg_configure_command
${CMAKE_COMMAND} -E env
"PKG_CONFIG_PATH=${DESTDIR}/lib/pkgconfig:$ENV{PKG_CONFIG_PATH}"
${_conf_cmd}
)
endif()
if (MSVC) if (MSVC)
set(_source_dir "${CMAKE_BINARY_DIR}/dep_FFMPEG-prefix/src/dep_FFMPEG") set(_source_dir "${CMAKE_BINARY_DIR}/dep_FFMPEG-prefix/src/dep_FFMPEG")
set(PREBUILD_URL_arm64 "https://github.com/Noisyfox/FFmpeg-Builds-Orca/releases/download/autobuild-2026-09-18-16-50/ffmpeg-n7.0.3-33-g887d4b4919-winarm64-orca-shared-7.0.zip") set(PREBUILD_URL_arm64 "https://github.com/Noisyfox/FFmpeg-Builds-Orca/releases/download/autobuild-2026-07-17-14-28/ffmpeg-n7.0.3-31-g9b6ffd74b5-winarm64-orca-shared-7.0.zip")
set(PREBUILD_HASH_arm64 "da480cbb39680056de824c57ec4dc3bd577b479ebbc310ff1f9dc55cf014b4c1") set(PREBUILD_HASH_arm64 "12f4140279f2f8469885e1b5b2e8be9d788882914c21523cacd56989f3548054")
set(PREBUILD_URL_x64 "https://github.com/Noisyfox/FFmpeg-Builds-Orca/releases/download/autobuild-2026-09-18-16-50/ffmpeg-n7.0.3-33-g887d4b4919-win64-orca-shared-7.0.zip") set(PREBUILD_URL_x64 "https://github.com/Noisyfox/FFmpeg-Builds-Orca/releases/download/autobuild-2026-07-17-14-28/ffmpeg-n7.0.3-31-g9b6ffd74b5-win64-orca-shared-7.0.zip")
set(PREBUILD_HASH_x64 "85da19daf198f5548259d8aabb349db84997a3f6e6886d8d7764114add9c6dae") set(PREBUILD_HASH_x64 "e65916020ddb9ef84b2666dfbcbfc9b1d67f69d15b4a66db53754637bf2d498c")
ExternalProject_Add(dep_FFMPEG ExternalProject_Add(dep_FFMPEG
${_ffmpeg_depends}
URL ${PREBUILD_URL_${DEPS_ARCH}} URL ${PREBUILD_URL_${DEPS_ARCH}}
URL_HASH SHA256=${PREBUILD_HASH_${DEPS_ARCH}} URL_HASH SHA256=${PREBUILD_HASH_${DEPS_ARCH}}
DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/FFMPEG DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/FFMPEG
@@ -33,8 +21,6 @@ if (MSVC)
) )
else () else ()
set(_openssl_cmd --enable-openssl)
if (APPLE) if (APPLE)
set(_minos_cmd set(_minos_cmd
"--extra-cflags=-mmacosx-version-min=${DEP_OSX_TARGET}" "--extra-cflags=-mmacosx-version-min=${DEP_OSX_TARGET}"
@@ -66,11 +52,10 @@ else ()
endif() endif()
ExternalProject_Add(dep_FFMPEG ExternalProject_Add(dep_FFMPEG
${_ffmpeg_depends}
URL https://github.com/FFmpeg/FFmpeg/archive/refs/tags/n7.0.3.tar.gz URL https://github.com/FFmpeg/FFmpeg/archive/refs/tags/n7.0.3.tar.gz
URL_HASH SHA256=DEEDCABE339165214A3637DF4C86A507AEF0D793CF8774FF68735F4737E8DDBC URL_HASH SHA256=DEEDCABE339165214A3637DF4C86A507AEF0D793CF8774FF68735F4737E8DDBC
DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/FFMPEG DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/FFMPEG
CONFIGURE_COMMAND ${_ffmpeg_configure_command} CONFIGURE_COMMAND ${_conf_cmd}
${_cross_cmd} ${_cross_cmd}
${_pic_cmd} ${_pic_cmd}
${_arch_cmd} ${_arch_cmd}
@@ -78,21 +63,20 @@ else ()
"--prefix=${DESTDIR}" "--prefix=${DESTDIR}"
${_link_cmd} ${_link_cmd}
${_minos_cmd} ${_minos_cmd}
${_openssl_cmd}
--disable-doc --disable-doc
--enable-small --enable-small
--disable-outdevs --disable-outdevs
--disable-filters --disable-filters
--enable-filter=*null*,afade,*fifo,*format,*resample,aeval,allrgb,allyuv,atempo,pan,*bars,color,*key,crop,draw*,eq*,framerate,*_qsv,*_vaapi,*v4l2*,hw*,scale,volume,test* --enable-filter=*null*,afade,*fifo,*format,*resample,aeval,allrgb,allyuv,atempo,pan,*bars,color,*key,crop,draw*,eq*,framerate,*_qsv,*_vaapi,*v4l2*,hw*,scale,volume,test*
--disable-protocols --disable-protocols
--enable-protocol=file,fd,pipe,http,https,rtp,tcp,udp --enable-protocol=file,fd,pipe,rtp,udp
--disable-muxers --disable-muxers
--enable-muxer=rtp --enable-muxer=rtp
--disable-encoders --disable-encoders
--disable-decoders --disable-decoders
--enable-decoder=*aac*,h264*,mp3*,mjpeg,rv* --enable-decoder=*aac*,h264*,mp3*,mjpeg,rv*
--disable-demuxers --disable-demuxers
--enable-demuxer=h264,mp3,mov,mpjpeg,rtsp,sdp --enable-demuxer=h264,mp3,mov
--disable-zlib --disable-zlib
--disable-avdevice --disable-avdevice
BUILD_IN_SOURCE ON BUILD_IN_SOURCE ON
-240
View File
@@ -1,240 +0,0 @@
# Printer agents
Printer agents isolate printer-specific communication from the rest of OrcaSlicer. The GUI and
`DeviceManager` operate on a shared set of printer operations and device state; a selected printer
agent implements those operations for a particular printer ecosystem. The agent boundary allows
Bambu, Moonraker-based printers, built-in integrations, and Python-provided integrations to use the
same application workflow without making the GUI understand every printer protocol.
The current boundary is an adapter boundary around the existing application contract. In particular,
some request fields and message payloads still use the Bambu-shaped representation that existing
`MachineObject` and `DeviceManager` code consumes. The printer agent is responsible for translating
that representation into the protocol spoken by its printer. This is an intentional compatibility
constraint of the current design; the interface is not yet a neutral printer protocol.
The v1 dialect migration path is deliberately narrow. `DeviceManager` currently speaks the Bambu JSON
dialect because that is the payload shape already used throughout the command and state workflow. The
v1 `OrcaPrinterAgent` also accepts that Bambu dialect. Its transport path places the small translation
needed for the target printer at `deliver_to_sink`, keeping the compatibility code at the edge rather
than spreading it through `DeviceManager` or the agent interface.
The eventual direction is for `DeviceManager` to produce an Orca JSON dialect. The Bambu agent will then
own the translation from Orca JSON to Bambu's protocol, while `OrcaPrinterAgent` can forward the Orca
payload directly to its sink. The v1 translation at `deliver_to_sink` can then be removed without
changing `DeviceManager`, the command callers, or the rest of the agent workflow.
## Components
The system has four relevant layers:
```text
GUI / DeviceManager / MachineObject
|
NetworkAgent
/ \
IPrinterAgent ICloudServiceAgent
| |
printer protocol authentication and cloud services
```
### `DeviceManager` and `MachineObject`
`DeviceManager` owns the application-facing printer workflow. It maintains `MachineObject` instances,
updates their state, filters devices for the active printer agent, and initiates operations such as
homing, temperature changes, printing, subscriptions, and camera playback.
`MachineObject` remains the shared state model used by the GUI. It does not contain the implementation
of a printer protocol. When a device is discovered or returned by a cloud query, the device is tagged
with the active `printer_agent_id`. Device lists and selected-machine operations use that tag to avoid
sending an operation through an agent that does not own the device.
### `NetworkAgent`
`NetworkAgent` is the façade used by the GUI and `DeviceManager`. It owns:
- the currently selected `IPrinterAgent`;
- the registered cloud-service instances, indexed by provider;
- callbacks shared by the active printer agent and the application;
- the forwarding methods for printer commands and cloud operations.
There is one active printer agent for the currently selected printer preset. Switching the preset
increments the machine-list generation, disconnects the old printer agent, removes its callbacks, and
installs the newly selected agent. The façade then forwards printer operations to that agent.
Cloud operations are selected separately using a provider key. `NetworkAgent` forwards a cloud request
to the matching `ICloudServiceAgent`, and forwards cloud camera operations with a device ID. The
printer agent receives a cloud-agent pointer through `set_cloud_agent()` when it is created, allowing
printer communication to obtain cloud tokens without depending on a concrete cloud implementation.
### `IPrinterAgent`
`IPrinterAgent` is the printer-facing contract. It covers:
- cloud-relay and direct-LAN message delivery;
- LAN connection, discovery, binding, and certificates;
- printer subscriptions and callbacks;
- print operations;
- filament synchronization;
- camera capability and local camera URL reporting;
- printer command methods.
Concrete built-in implementations include the Bambu wrapper, the native Orca/Moonraker path, and
other printer-agent implementations registered by the application. A printer agent may use either
the cloud agent, a direct LAN connection, or both.
### `ICloudServiceAgent`
`ICloudServiceAgent` owns authentication and services provided by a cloud backend. It covers login
state, tokens, user and printer lists, settings synchronization, model services, cloud messages, and
cloud camera operations.
Cloud camera operations are device-scoped:
- `get_camera_url(dev_id, callback)` obtains a stream URL for one device;
- `create_camera_signaling_channel(dev_id)` creates signaling for one device where the provider
supports it.
This is separate from the local camera URL exposed by `IPrinterAgent`, which is currently scoped to
the active printer agent because a normal LAN agent represents one physical printer connection.
## Agent registration and selection
`NetworkAgentFactory` maintains the printer-agent registry. Each registry entry contains an agent ID,
a display name, and a factory function. Built-in agents register during application initialization.
Python printer-agent capabilities register dynamically and contribute an agent ID and factory entry.
The selected printer preset contains the printer-agent choice. If no explicit choice is stored, the
application preserves the existing default behavior: Bambu presets select the Bambu agent and other
presets select the native Orca agent. When a preset is changed, `GUI_App` resolves the effective agent
ID, obtains the corresponding cloud agent, creates the printer agent through the registry, and installs
it in `NetworkAgent`.
The registry rejects conflicting agent IDs. This matters for Python plugins because an agent ID is the
stable identity used by presets and device ownership; two enabled plugin capabilities must not claim
the same ID.
## Message and command flow
There are two low-level message paths:
- `send_message()` publishes a command through the printer's cloud relay;
- `send_message_to_printer()` sends a command directly to the printer over the LAN path.
Both paths accept a JSON string, quality-of-service and flag values, and return the existing network
status code domain. The agent owns the conversion from that JSON contract to its native transport.
The typed `command_*` methods are the application-facing convenience layer. The five generic defaults
currently implemented by `IPrinterAgent` construct the existing JSON dialect and route through the
same message path:
| Method | Default operation |
| --- | --- |
| `command_xyz_abs()` | Send `G90` for absolute positioning |
| `command_auto_leveling()` | Send `G29` for bed leveling |
| `command_go_home()` | Use the supported homing operation or send `G28` |
| `command_set_bed()` | Use the supported bed control or send `M140` |
| `command_set_nozzle()` | Send `M104` for nozzle temperature |
These are compatibility defaults for common printer workflows, not a guarantee that every firmware
implements every command identically. An agent can override a method when its protocol needs another
operation. For example, a Klipper configuration may use `BED_MESH_CALIBRATE` instead of `G29`.
The remaining common command methods default to `ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED` because their
existing behavior is vendor-specific or has no portable implementation:
- AMS RFID refresh;
- AMS calibration;
- AMS tray selection;
- camera start;
- axis control.
The methods remain on the common interface so an agent that supports them can override them explicitly.
`sequence_id` remains part of the command contract because `DeviceManager` creates and tracks it as
the command ID.
## Device ownership and stale responses
Printer-agent ownership is represented by `printer_agent_id` on device records and `MachineObject`
instances. The active agent ID is attached when a device is discovered, returned by a cloud list, or
reused after a preset switch. Local-machine configuration also persists the agent ID so a saved LAN
device is not silently reused by an unrelated agent.
Cloud printer-list responses carry three pieces of request context added by `NetworkAgent`:
```text
provider cloud provider used for the request
agent_id active printer agent when the request was made
generation machine-list generation when the request was made
```
`DeviceManager` accepts the response only when those values still match the current provider, active
agent, and generation. This prevents a slow response from the previous preset or provider from
repopulating the current device list.
The provider mapping is currently selected by `GUI_App`: the Bambu agent maps to the Bambu cloud
provider and other agents map to the Orca cloud provider. The generation check protects that existing
selection from races; it does not make cloud-provider ownership intrinsic to an agent. Cloud-printer
ownership and the broader Orca cloud services are therefore still separate architectural concerns.
## Python printer agents
`PrinterAgentPluginCapability` implements `IPrinterAgent` directly. The live capability object is
registered with `NetworkAgentFactory` and handed out as the printer agent when its agent ID is selected.
The plugin receives the selected `ICloudServiceAgent` through `set_cloud_agent()` just like a built-in
printer agent.
Python plugins must implement the core communication and lifecycle methods required by the interface,
including agent metadata, printer connection, discovery callbacks, and the two message-send methods.
Methods that are meaningful only to a particular printer are optional overrides where the C++ base
class provides a default.
All ten `command_*` methods are available in the Python binding and in the trampoline. Their override
status is intentionally optional:
- the five generic commands use the C++ default when Python does not override them;
- the five vendor-specific commands return `NOT_SUPPORTED` unless Python supplies an implementation;
- a Python implementation can replace either behavior for its own protocol.
The Python camera binding exposes HTTP, HTTPS, RTSP, and HTTP-snapshot modes. WebRTC remains a
built-in C++ camera mode, but is not exposed as a Python mode because the current Python capability
does not provide the corresponding cloud signaling-channel contract.
## Camera playback boundary
The camera stream mode describes how a stream is obtained; it does not by itself define ownership of
the wxWidgets view that renders it. `MediaPlayCtrl` selects and tears down the active backend, while
the wx parent owns the child window or renderer. This is important because a web view, native media
control, and frame-based/WebRTC renderer have different wx window-lifetime requirements.
Cloud URL and signaling requests are routed through `NetworkAgent` to the cloud provider selected for
the device. Local URL requests are routed to the active printer agent. The distinction keeps cloud
account services device-scoped while preserving the current one-LAN-agent/one-printer model.
## Compatibility constraints
The printer-agent boundary intentionally preserves several existing application contracts:
- Bambu-shaped JSON is still the shared command representation;
- existing network status codes are reused, with Orca-specific unsupported/capability errors added
in the Orca-reserved range;
- `MachineObject` remains the shared device-state model;
- preset and local-machine data retain compatibility with the existing agent-selection behavior;
- Python plugins use the existing capability and pybind11 registration system.
The agent abstraction is therefore responsible for containing vendor differences, not for pretending
that all vendor protocols are identical. The planned Orca JSON dialect is the protocol-neutral command
model for the `DeviceManager`/agent boundary. Once it is introduced, Bambu-specific translation remains
inside the Bambu agent and the Orca agent's v1 sink adapter can be removed as a self-contained cleanup.
## Main implementation locations
- [`IPrinterAgent`](../../src/slic3r/Utils/IPrinterAgent.hpp) — printer-agent contract and generic command defaults
- [`ICloudServiceAgent`](../../src/slic3r/Utils/ICloudServiceAgent.hpp) — cloud service and per-device
cloud camera contract
- [`NetworkAgent`](../../src/slic3r/Utils/NetworkAgent.hpp) — façade and dispatch between active agents
- [`NetworkAgentFactory`](../../src/slic3r/Utils/NetworkAgentFactory.hpp) — built-in and Python agent registry
- [`DeviceManager`](../../src/slic3r/GUI/DeviceCore/DevManager.cpp) — device ownership, filtering, and
stale-response checks
- [`PrinterAgentPluginCapability`](../../src/slic3r/plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.cpp)
— Python bindings
- [`MediaPlayCtrl`](../../src/slic3r/GUI/MediaPlayCtrl.cpp) — camera backend selection and playback lifecycle
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "OrcaFilamentLibrary", "name": "OrcaFilamentLibrary",
"version": "02.04.00.12", "version": "02.04.00.13",
"force_update": "0", "force_update": "0",
"description": "Orca Filament Library", "description": "Orca Filament Library",
"filament_list": [ "filament_list": [
@@ -6,7 +6,7 @@
"filament_id": "OFvgE0Zh", "filament_id": "OFvgE0Zh",
"instantiation": "false", "instantiation": "false",
"fan_cooling_layer_time": [ "fan_cooling_layer_time": [
"80" "81"
], ],
"fan_min_speed": [ "fan_min_speed": [
"50" "50"
+1 -3
View File
@@ -575,7 +575,7 @@ function CapabilityCanRun(plugin, capability) {
} }
function IsPluginChecked(plugin) { function IsPluginChecked(plugin) {
return plugin.is_loaded; return GetStatus(plugin) === "Activated";
} }
function HasMixedCapabilityState(plugin) { function HasMixedCapabilityState(plugin) {
@@ -1347,8 +1347,6 @@ function StatusDescription(plugin) {
return "This plugin is still loading."; return "This plugin is still loading.";
case "Error": case "Error":
return "This plugin is blocked until its error is fixed."; return "This plugin is blocked until its error is fixed.";
case "RuntimeError":
return "This plugin is loaded but a capability reported an error.";
case "Inactive": case "Inactive":
default: default:
return "This plugin is inactive. Activate it to install or load it."; return "This plugin is inactive. Activate it to install or load it.";
@@ -424,11 +424,6 @@ body.pane-resizing {
font-weight: 600; font-weight: 600;
} }
.status-cell.status-runtimeerror {
color: var(--plugin-status-warn);
font-weight: 600;
}
.status-cell.status-loading { .status-cell.status-loading {
color: var(--plugin-status-warn); color: var(--plugin-status-warn);
font-weight: 600; font-weight: 600;
@@ -685,11 +680,6 @@ body.pane-resizing {
color: var(--plugin-status-danger); color: var(--plugin-status-danger);
} }
.detail-status-chip.status-runtimeerror {
background: var(--plugin-status-warn-bg);
color: var(--plugin-status-warn);
}
.detail-status-chip.status-loading { .detail-status-chip.status-loading {
background: var(--plugin-status-warn-bg); background: var(--plugin-status-warn-bg);
color: var(--plugin-status-warn); color: var(--plugin-status-warn);
@@ -331,15 +331,6 @@ modules:
sha256: deedcabe339165214a3637df4c86a507aef0d793cf8774ff68735f4737e8ddbc sha256: deedcabe339165214a3637df4c86a507aef0d793cf8774ff68735f4737e8ddbc
dest: external-packages/FFMPEG dest: external-packages/FFMPEG
# libdatachannel v0.24.5
# The Git checkout includes the submodules that are missing from the
# GitHub source archive. DataChannel.cmake consumes it as SOURCE_DIR.
- type: git
url: https://github.com/paullouisageneau/libdatachannel.git
tag: v0.24.5
commit: 443f6934d9007eb7076ab7825ba330f355fcbead
dest: external-packages/DataChannel
# --------------------------------------------------------------- # ---------------------------------------------------------------
# Fallback archives for deps normally provided by the GNOME SDK. # Fallback archives for deps normally provided by the GNOME SDK.
# These are only used if find_package() fails to locate them. # These are only used if find_package() fails to locate them.
+2 -2
View File
@@ -2673,10 +2673,10 @@ void CadDocument::apply_feature(TopoDS_Shape& result, bool& have_body,
ridge = pipe.Shape(); ridge = pipe.Shape();
have_ridge = !ridge.IsNull(); have_ridge = !ridge.IsNull();
} }
} catch (const Standard_Failure&) {
have_ridge = false; // OCCT failure — on OCCT >= 8 Standard_Failure derives from std::exception, so this handler must come first
} catch (const std::exception&) { } catch (const std::exception&) {
have_ridge = false; // fall back to the bare cylinder/bore below have_ridge = false; // fall back to the bare cylinder/bore below
} catch (const Standard_Failure&) {
have_ridge = false; // OCCT failure (not a std::exception) — must be caught here too
} }
if (f.thread_internal) { if (f.thread_internal) {
+2
View File
@@ -15,6 +15,8 @@
#include <TopExp.hxx> #include <TopExp.hxx>
#include <TopTools.hxx> #include <TopTools.hxx>
#include <TopTools_IndexedMapOfShape.hxx> #include <TopTools_IndexedMapOfShape.hxx>
#include <TopTools_ListOfShape.hxx>
#include <TopTools_IndexedDataMapOfShapeListOfShape.hxx>
#include <Poly_Triangulation.hxx> #include <Poly_Triangulation.hxx>
#include <gp_Ax2.hxx> #include <gp_Ax2.hxx>
#include <gp_Dir.hxx> #include <gp_Dir.hxx>
+1
View File
@@ -25,6 +25,7 @@
#include "XCAFDoc_DocumentTool.hxx" #include "XCAFDoc_DocumentTool.hxx"
#include "XCAFDoc_ShapeTool.hxx" #include "XCAFDoc_ShapeTool.hxx"
#include "XCAFApp_Application.hxx" #include "XCAFApp_Application.hxx"
#include "TDF_LabelSequence.hxx"
#include "TopoDS_Solid.hxx" #include "TopoDS_Solid.hxx"
#include "TopoDS_Compound.hxx" #include "TopoDS_Compound.hxx"
#include "TopoDS_Builder.hxx" #include "TopoDS_Builder.hxx"
+1
View File
@@ -3824,6 +3824,7 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
ConfigOptionStrings *filament_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type"); ConfigOptionStrings *filament_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type");
ConfigOptionInts * filament_map = project_config.option<ConfigOptionInts>("filament_map"); ConfigOptionInts * filament_map = project_config.option<ConfigOptionInts>("filament_map");
ConfigOptionInts * filament_volume_map = project_config.option<ConfigOptionInts>("filament_volume_map"); ConfigOptionInts * filament_volume_map = project_config.option<ConfigOptionInts>("filament_volume_map");
// Snapshot and temporarily strip mixed filament slots so AMS sync operates on physical // Snapshot and temporarily strip mixed filament slots so AMS sync operates on physical
// filaments only. A mixed slot is virtual and has no tray to sync against; leaving it in // filaments only. A mixed slot is virtual and has no tray to sync against; leaving it in
// would let AMS mapping overwrite it and would break the physical-first slot ordering the // would let AMS mapping overwrite it and would break the physical-first slot ordering the
+9 -22
View File
@@ -236,7 +236,6 @@ set(SLIC3R_GUI_SOURCES
GUI/GLToolbar.hpp GUI/GLToolbar.hpp
GUI/ImageDPIFrame.cpp GUI/ImageDPIFrame.cpp
GUI/ImageDPIFrame.hpp GUI/ImageDPIFrame.hpp
GUI/IMediaController.hpp
GUI/GUI_App.cpp GUI/GUI_App.cpp
GUI/GUI_App.hpp GUI/GUI_App.hpp
GUI/GUI_AuxiliaryList.cpp GUI/GUI_AuxiliaryList.cpp
@@ -366,8 +365,6 @@ set(SLIC3R_GUI_SOURCES
GUI/MediaFilePanel.h GUI/MediaFilePanel.h
GUI/MediaPlayCtrl.cpp GUI/MediaPlayCtrl.cpp
GUI/MediaPlayCtrl.h GUI/MediaPlayCtrl.h
GUI/WebRtcMediaController.cpp
GUI/WebRtcMediaController.hpp
GUI/MeshUtils.cpp GUI/MeshUtils.cpp
GUI/MeshUtils.hpp GUI/MeshUtils.hpp
GUI/ModelMall.cpp GUI/ModelMall.cpp
@@ -563,8 +560,6 @@ set(SLIC3R_GUI_SOURCES
GUI/WebUserLoginDialog.hpp GUI/WebUserLoginDialog.hpp
GUI/WebViewDialog.cpp GUI/WebViewDialog.cpp
GUI/WebViewDialog.hpp GUI/WebViewDialog.hpp
GUI/WebMediaController.hpp
GUI/WebMediaController.cpp
GUI/Widgets/AMSControl.cpp GUI/Widgets/AMSControl.cpp
GUI/Widgets/AMSControl.hpp GUI/Widgets/AMSControl.hpp
GUI/Widgets/AMSItem.cpp GUI/Widgets/AMSItem.cpp
@@ -759,15 +754,10 @@ set(SLIC3R_GUI_SOURCES
Utils/NetworkAgentFactory.cpp Utils/NetworkAgentFactory.cpp
Utils/ICloudServiceAgent.hpp Utils/ICloudServiceAgent.hpp
Utils/IPrinterAgent.hpp Utils/IPrinterAgent.hpp
Utils/ICameraSignalingChannel.hpp
Utils/OrcaCloudServiceAgent.cpp Utils/OrcaCloudServiceAgent.cpp
Utils/OrcaCloudServiceAgent.hpp Utils/OrcaCloudServiceAgent.hpp
Utils/OrcaMqttConnection.cpp
Utils/OrcaMqttConnection.hpp
Utils/OrcaPrinterAgent.cpp Utils/OrcaPrinterAgent.cpp
Utils/OrcaPrinterAgent.hpp Utils/OrcaPrinterAgent.hpp
Utils/OrcaCloudSignalingChannel.cpp
Utils/OrcaCloudSignalingChannel.hpp
Utils/QidiPrinterAgent.cpp Utils/QidiPrinterAgent.cpp
Utils/QidiPrinterAgent.hpp Utils/QidiPrinterAgent.hpp
Utils/SnapmakerPrinterAgent.cpp Utils/SnapmakerPrinterAgent.cpp
@@ -908,7 +898,7 @@ target_include_directories(libslic3r_gui PRIVATE Utils ${CMAKE_CURRENT_BINARY_DI
if (WIN32) if (WIN32)
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../deps/WebView2/include) target_include_directories(libslic3r_gui SYSTEM PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../deps/WebView2/include)
target_link_libraries(libslic3r_gui Advapi32 Crypt32) target_link_libraries(libslic3r_gui Advapi32)
endif() endif()
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SLIC3R_GUI_SOURCES}) source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SLIC3R_GUI_SOURCES})
@@ -934,7 +924,7 @@ else()
set(_opengl_link_lib OpenGL::GL) set(_opengl_link_lib OpenGL::GL)
endif() endif()
target_link_libraries(libslic3r_gui libslic3r cereal::cereal imgui imguizmo minilzo libvgcode md4c-html glad ${_opengl_link_lib} hidapi mdns ${wxWidgets_LIBRARIES} glfw libcurl OpenSSL::SSL OpenSSL::Crypto LibDataChannel::LibDataChannel noise::noise pybind11::embed) target_link_libraries(libslic3r_gui libslic3r cereal::cereal imgui imguizmo minilzo libvgcode md4c-html glad ${_opengl_link_lib} hidapi mdns ${wxWidgets_LIBRARIES} glfw libcurl OpenSSL::SSL OpenSSL::Crypto noise::noise pybind11::embed)
if (CMAKE_SYSTEM_NAME STREQUAL "Linux") if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
# Linux finds wxWidgets in module mode, whose include dirs and definitions # Linux finds wxWidgets in module mode, whose include dirs and definitions
@@ -991,32 +981,29 @@ endif ()
if (APPLE) if (APPLE)
# Static FFmpeg from the deps install: nothing to bundle into the .app, # Static FFmpeg from the deps install: nothing to bundle into the .app,
# no rpath/install_name handling. Order matters: avformat -> avcodec -> swscale -> avutil. # no rpath/install_name handling. Order matters: avcodec -> swscale -> avutil.
find_library(LIBAVFORMAT_LIBRARY NAMES libavformat.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
find_library(LIBAVCODEC_LIBRARY NAMES libavcodec.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH) find_library(LIBAVCODEC_LIBRARY NAMES libavcodec.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
find_library(LIBSWSCALE_LIBRARY NAMES libswscale.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH) find_library(LIBSWSCALE_LIBRARY NAMES libswscale.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
find_library(LIBAVUTIL_LIBRARY NAMES libavutil.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH) find_library(LIBAVUTIL_LIBRARY NAMES libavutil.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
if (NOT LIBAVFORMAT_LIBRARY OR NOT LIBAVCODEC_LIBRARY OR NOT LIBSWSCALE_LIBRARY OR NOT LIBAVUTIL_LIBRARY) if (NOT LIBAVCODEC_LIBRARY OR NOT LIBSWSCALE_LIBRARY OR NOT LIBAVUTIL_LIBRARY)
message(FATAL_ERROR "Static FFmpeg (libavformat.a/libavcodec.a/libswscale.a/libavutil.a) not found under ${CMAKE_PREFIX_PATH}/lib. Rebuild the deps — FFMPEG builds static-only on macOS.") message(FATAL_ERROR "Static FFmpeg (libavcodec.a/libswscale.a/libavutil.a) not found under ${CMAKE_PREFIX_PATH}/lib. Rebuild the deps — FFMPEG builds static-only on macOS.")
endif () endif ()
target_link_libraries(libslic3r_gui ${LIBAVFORMAT_LIBRARY} ${LIBAVCODEC_LIBRARY} ${LIBSWSCALE_LIBRARY} ${LIBAVUTIL_LIBRARY}) target_link_libraries(libslic3r_gui ${LIBAVCODEC_LIBRARY} ${LIBSWSCALE_LIBRARY} ${LIBAVUTIL_LIBRARY})
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${CMAKE_PREFIX_PATH}/include) target_include_directories(libslic3r_gui SYSTEM PRIVATE ${CMAKE_PREFIX_PATH}/include)
elseif (WIN32) elseif (WIN32)
# Prebuilt shared FFmpeg from the deps install. Windows has no pkg-config, # Prebuilt shared FFmpeg from the deps install. Windows has no pkg-config,
# so resolve the import libraries out of the deps prefix directly; the DLLs # so resolve the import libraries out of the deps prefix directly; the DLLs
# are copied next to the executable by the top level CMakeLists. # are copied next to the executable by the top level CMakeLists.
find_library(LIBAVFORMAT_LIBRARY NAMES avformat PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
find_library(LIBAVCODEC_LIBRARY NAMES avcodec PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH) find_library(LIBAVCODEC_LIBRARY NAMES avcodec PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
find_library(LIBSWSCALE_LIBRARY NAMES swscale PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH) find_library(LIBSWSCALE_LIBRARY NAMES swscale PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
find_library(LIBAVUTIL_LIBRARY NAMES avutil PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH) find_library(LIBAVUTIL_LIBRARY NAMES avutil PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
if (NOT LIBAVFORMAT_LIBRARY OR NOT LIBAVCODEC_LIBRARY OR NOT LIBSWSCALE_LIBRARY OR NOT LIBAVUTIL_LIBRARY) if (NOT LIBAVCODEC_LIBRARY OR NOT LIBSWSCALE_LIBRARY OR NOT LIBAVUTIL_LIBRARY)
message(FATAL_ERROR "FFmpeg (avformat/avcodec/swscale/avutil) not found under ${CMAKE_PREFIX_PATH}/lib. Rebuild the deps.") message(FATAL_ERROR "FFmpeg (avcodec/swscale/avutil) not found under ${CMAKE_PREFIX_PATH}/lib. Rebuild the deps.")
endif () endif ()
target_link_libraries(libslic3r_gui ${LIBAVFORMAT_LIBRARY} ${LIBAVCODEC_LIBRARY} ${LIBSWSCALE_LIBRARY} ${LIBAVUTIL_LIBRARY}) target_link_libraries(libslic3r_gui ${LIBAVCODEC_LIBRARY} ${LIBSWSCALE_LIBRARY} ${LIBAVUTIL_LIBRARY})
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${CMAKE_PREFIX_PATH}/include) target_include_directories(libslic3r_gui SYSTEM PRIVATE ${CMAKE_PREFIX_PATH}/include)
else () else ()
pkg_check_modules(LIBAV REQUIRED IMPORTED_TARGET pkg_check_modules(LIBAV REQUIRED IMPORTED_TARGET
libavformat
libavcodec libavcodec
libswscale libswscale
libavutil libavutil
-27
View File
@@ -45,25 +45,6 @@ int AVVideoDecoder::open(Bambu_StreamInfo const &info)
return 0; return 0;
} }
int AVVideoDecoder::open(AVCodecParameters const &parameters)
{
if (avcodec_parameters_to_context(codec_ctx_, &parameters) < 0)
return -1;
auto codec = avcodec_find_decoder(codec_ctx_->codec_id);
if (codec == nullptr) {
fprintf(stderr, "AVVideoDecoder: unsupported codec!\n");
return -1;
}
if (avcodec_open2(codec_ctx_, codec, nullptr) < 0) {
fprintf(stderr, "AVVideoDecoder: could not open codec\n");
return -1;
}
frame_ = av_frame_alloc();
return frame_ == nullptr ? -1 : 0;
}
int AVVideoDecoder::decode(const Bambu_Sample &sample) int AVVideoDecoder::decode(const Bambu_Sample &sample)
{ {
int ret = -1; int ret = -1;
@@ -90,14 +71,6 @@ int AVVideoDecoder::decode(const Bambu_Sample &sample)
return ret; return ret;
} }
int AVVideoDecoder::decode(const AVPacket &packet)
{
int ret = avcodec_send_packet(codec_ctx_, &packet);
if (ret == 0)
got_frame_ = avcodec_receive_frame(codec_ctx_, frame_) == 0;
return ret;
}
int AVVideoDecoder::flush() int AVVideoDecoder::flush()
{ {
int ret = avcodec_send_packet(codec_ctx_, nullptr); int ret = avcodec_send_packet(codec_ctx_, nullptr);
-10
View File
@@ -23,10 +23,8 @@ public:
public: public:
int open(Bambu_StreamInfo const &info); int open(Bambu_StreamInfo const &info);
int open(AVCodecParameters const &parameters);
int decode(Bambu_Sample const &sample); int decode(Bambu_Sample const &sample);
int decode(AVPacket const &packet);
int flush(); int flush();
@@ -36,14 +34,6 @@ public:
bool toWxBitmap(wxBitmap &bitmap, wxSize const & size); bool toWxBitmap(wxBitmap &bitmap, wxSize const & size);
// Native size of the most recently decoded frame, or an unspecified size if
// nothing has decoded yet. Lets a caller learn the video dimensions when the
// container/probe could not report them up front.
wxSize decoded_frame_size() const
{
return got_frame_ && frame_ ? wxSize{frame_->width, frame_->height} : wxSize{};
}
private: private:
AVCodecContext *codec_ctx_ = nullptr; AVCodecContext *codec_ctx_ = nullptr;
AVFrame * frame_ = nullptr; AVFrame * frame_ = nullptr;
+4 -4
View File
@@ -4862,13 +4862,13 @@ void DesignPanel::on_import_mesh()
try { try {
shape = GeometryEngine::mesh_to_brep(mesh.its, MESH_IMPORT_TOLERANCE, shape = GeometryEngine::mesh_to_brep(mesh.its, MESH_IMPORT_TOLERANCE,
MESH_IMPORT_MERGE_ANGLE_DEG, stats); MESH_IMPORT_MERGE_ANGLE_DEG, stats);
} catch (const std::exception& e) { } catch (const Standard_Failure& e) { // on OCCT >= 8 Standard_Failure derives from std::exception — must precede that handler
fail(_L("Mesh conversion failed: ") + wxString::FromUTF8(e.what()));
return;
} catch (const Standard_Failure& e) { // OCCT throws outside std::exception
fail(_L("Mesh conversion failed: ") + wxString::FromUTF8( fail(_L("Mesh conversion failed: ") + wxString::FromUTF8(
e.GetMessageString() ? e.GetMessageString() : "OCCT error")); e.GetMessageString() ? e.GetMessageString() : "OCCT error"));
return; return;
} catch (const std::exception& e) {
fail(_L("Mesh conversion failed: ") + wxString::FromUTF8(e.what()));
return;
} }
if (shape.IsNull()) { fail(_L("Mesh conversion produced no geometry")); return; } if (shape.IsNull()) { fail(_L("Mesh conversion produced no geometry")); return; }
+1 -1
View File
@@ -328,7 +328,7 @@ void SelectMObjectPopup::update_user_devices()
} }
m_bind_machine_list.clear(); m_bind_machine_list.clear();
m_bind_machine_list = dev->get_my_machine_list(dev->get_current_printer_agent_id()); m_bind_machine_list = dev->get_my_machine_list();
//sort list //sort list
std::vector<std::pair<std::string, MachineObject*>> user_machine_list; std::vector<std::pair<std::string, MachineObject*>> user_machine_list;
+29 -16
View File
@@ -6,10 +6,8 @@
#include "Widgets/Label.hpp" #include "Widgets/Label.hpp"
#include "MsgDialog.hpp" #include "MsgDialog.hpp"
#include "libslic3r/Print.hpp" #include "libslic3r/Print.hpp"
#include "PrePrintChecker.hpp"
#include "DeviceCore/DevConfig.h" #include "DeviceCore/DevConfig.h"
#include "DeviceCore/DevConfigUtil.h"
#include "DeviceCore/DevExtruderSystem.h" #include "DeviceCore/DevExtruderSystem.h"
#include "DeviceCore/DevFilaBlackList.h" #include "DeviceCore/DevFilaBlackList.h"
#include "DeviceCore/DevFilaSystem.h" #include "DeviceCore/DevFilaSystem.h"
@@ -1640,6 +1638,29 @@ void CalibrationPresetPage::update_combobox_filaments(MachineObject* obj)
select_default_compatible_filament(); select_default_compatible_filament();
} }
bool CalibrationPresetPage::is_blocking_printing()
{
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
if (!dev) return true;
MachineObject* obj_ = dev->get_selected_machine();
if (obj_ == nullptr) return true;
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
auto source_model = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle);
auto target_model = obj_->printer_type;
if (source_model != target_model) {
std::vector<std::string> compatible_machine = obj_->get_compatible_machine();
vector<std::string>::iterator it = find(compatible_machine.begin(), compatible_machine.end(), source_model);
if (it == compatible_machine.end()) {
return true;
}
}
return false;
}
bool CalibrationPresetPage::is_nozzle_info_synced() const bool CalibrationPresetPage::is_nozzle_info_synced() const
{ {
if (!curr_obj || !curr_obj->is_info_ready()) if (!curr_obj || !curr_obj->is_info_ready())
@@ -1721,13 +1742,11 @@ void CalibrationPresetPage::update_show_status()
} }
} }
bool has_optional_printer_model = DevPrinterConfigUtil::is_optional_printer_model_id(obj_->printer_type); //if (is_blocking_printing()) {
if (PresetBundle *preset_bundle = wxGetApp().preset_bundle) { // show_status(CaliPresetPageStatus::CaliPresetStatusUnsupportedPrinter);
has_optional_printer_model = has_optional_printer_model || // return;
DevPrinterConfigUtil::is_optional_printer_model_id( //}
preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle)); //else
}
if (obj_->is_connecting() || !obj_->is_connected()) { if (obj_->is_connecting() || !obj_->is_connected()) {
show_status(CaliPresetPageStatus::CaliPresetStatusInConnecting); show_status(CaliPresetPageStatus::CaliPresetStatusInConnecting);
return; return;
@@ -1779,9 +1798,7 @@ void CalibrationPresetPage::update_show_status()
return; return;
} }
show_status(has_optional_printer_model ? show_status(CaliPresetPageStatus::CaliPresetStatusNormal);
CaliPresetPageStatus::CaliPresetStatusOptionalPrinterModel :
CaliPresetPageStatus::CaliPresetStatusNormal);
} }
@@ -1835,10 +1852,6 @@ void CalibrationPresetPage::show_status(CaliPresetPageStatus status)
Layout(); Layout();
Fit(); Fit();
} }
else if (status == CaliPresetPageStatus::CaliPresetStatusOptionalPrinterModel) {
update_print_status_msg(PrePrintChecker::get_pre_state_msg(PrintDialogStatus::PrintStatusOptionalPrinterModel), true);
Enable_Send_Button(true);
}
else if (status == CaliPresetPageStatus::CaliPresetStatusNoUserLogin) { else if (status == CaliPresetPageStatus::CaliPresetStatusNoUserLogin) {
wxString msg_text = _L("No login account, only printers in LAN mode are displayed."); wxString msg_text = _L("No login account, only printers in LAN mode are displayed.");
update_print_status_msg(msg_text, false); update_print_status_msg(msg_text, false);
@@ -152,8 +152,7 @@ enum CaliPresetPageStatus
CaliPresetStatusInConnecting, CaliPresetStatusInConnecting,
CaliPresetStatusFilamentIncompatible, CaliPresetStatusFilamentIncompatible,
CaliPresetStatusLanModeSDcardNotAvailable, CaliPresetStatusLanModeSDcardNotAvailable,
CaliPresetStatusDifferentNozzleDiameters, CaliPresetStatusDifferentNozzleDiameters
CaliPresetStatusOptionalPrinterModel
}; };
class CalibrationPresetPage : public CalibrationWizardPage class CalibrationPresetPage : public CalibrationWizardPage
@@ -269,6 +268,7 @@ protected:
bool is_nozzle_info_synced() const; bool is_nozzle_info_synced() const;
void show_status(CaliPresetPageStatus status); void show_status(CaliPresetPageStatus status);
void Enable_Send_Button(bool enable); void Enable_Send_Button(bool enable);
bool is_blocking_printing();
bool need_check_sdcard(MachineObject* obj); bool need_check_sdcard(MachineObject* obj);
CaliPresetPageStatus get_status() { return m_page_status; } CaliPresetPageStatus get_status() { return m_page_status; }
+60
View File
@@ -28,6 +28,7 @@ wxEND_EVENT_TABLE()
wxDEFINE_EVENT(EVT_VCAMERA_SWITCH, wxMouseEvent); wxDEFINE_EVENT(EVT_VCAMERA_SWITCH, wxMouseEvent);
wxDEFINE_EVENT(EVT_SDCARD_ABSENT_HINT, wxCommandEvent); wxDEFINE_EVENT(EVT_SDCARD_ABSENT_HINT, wxCommandEvent);
wxDEFINE_EVENT(EVT_CAM_SOURCE_CHANGE, wxCommandEvent);
#define CAMERAPOPUP_CLICK_INTERVAL 20 #define CAMERAPOPUP_CLICK_INTERVAL 20
@@ -101,6 +102,34 @@ CameraPopup::CameraPopup(wxWindow *parent)
top_sizer->Add(0, 0, wxALL, 0); top_sizer->Add(0, 0, wxALL, 0);
} }
// Orca: custom IP camera source — lets the user point Live Video at any camera URL (Orca feature; not in the reference)
m_custom_camera_input_confirm = new Button(m_panel, _L("Enable"));
m_custom_camera_input_confirm->SetBackgroundColor(wxColour(38, 166, 154));
m_custom_camera_input_confirm->SetBorderColor(wxColour(38, 166, 154));
m_custom_camera_input_confirm->SetTextColor(wxColour(0xFFFFFE));
m_custom_camera_input_confirm->SetFont(Label::Body_14);
m_custom_camera_input_confirm->SetMinSize(wxSize(FromDIP(90), FromDIP(30)));
m_custom_camera_input_confirm->SetPosition(wxDefaultPosition);
m_custom_camera_input_confirm->SetCornerRadius(FromDIP(12));
m_custom_camera_input = new TextInput(m_panel, wxEmptyString, wxEmptyString, wxEmptyString, wxDefaultPosition, wxDefaultSize);
m_custom_camera_input->GetTextCtrl()->SetHint(_L("Hostname or IP"));
m_custom_camera_input->GetTextCtrl()->SetFont(Label::Body_14);
m_custom_camera_hint = new wxStaticText(m_panel, wxID_ANY, _L("Custom camera source"));
m_custom_camera_hint->Wrap(-1);
m_custom_camera_hint->SetFont(Label::Head_14);
m_custom_camera_hint->SetForegroundColour(TEXT_COL);
m_custom_camera_input_confirm->Bind(wxEVT_BUTTON, &CameraPopup::on_camera_source_changed, this);
if (!wxGetApp().app_config->get("camera", "custom_source").empty()) {
m_custom_camera_input->GetTextCtrl()->SetValue(wxGetApp().app_config->get("camera", "custom_source"));
set_custom_cam_button_state(wxGetApp().app_config->get("camera", "enable_custom_source") == "true");
}
top_sizer->Add(m_custom_camera_hint, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxALL, FromDIP(5));
top_sizer->Add(0, 0, wxALL, 0);
top_sizer->Add(m_custom_camera_input, 2, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxALL, FromDIP(5));
top_sizer->Add(m_custom_camera_input_confirm, 1, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, FromDIP(5));
main_sizer->Add(top_sizer, 0, wxALL, FromDIP(10)); main_sizer->Add(top_sizer, 0, wxALL, FromDIP(10));
auto url = wxString(L"https://www.orcaslicer.com/wiki/"); // Orca: neutral wiki link (vendor URL removed) auto url = wxString(L"https://www.orcaslicer.com/wiki/"); // Orca: neutral wiki link (vendor URL removed)
@@ -155,6 +184,37 @@ void CameraPopup::sdcard_absent_hint()
GetEventHandler()->ProcessEvent(evt); GetEventHandler()->ProcessEvent(evt);
} }
void CameraPopup::on_camera_source_changed(wxCommandEvent &event)
{
if (m_obj && !m_custom_camera_input->GetTextCtrl()->IsEmpty()) {
handle_camera_source_change();
}
}
void CameraPopup::handle_camera_source_change()
{
m_custom_camera_enabled = !m_custom_camera_enabled;
set_custom_cam_button_state(m_custom_camera_enabled);
wxGetApp().app_config->set("camera", "custom_source", m_custom_camera_input->GetTextCtrl()->GetValue().ToStdString());
wxGetApp().app_config->set("camera", "enable_custom_source", m_custom_camera_enabled);
wxCommandEvent evt(EVT_CAM_SOURCE_CHANGE);
evt.SetEventObject(this);
GetEventHandler()->ProcessEvent(evt);
}
void CameraPopup::set_custom_cam_button_state(bool state)
{
m_custom_camera_enabled = state;
auto stateColour = state ? wxColour(170, 0, 0) : wxColour(38, 166, 154);
auto stateText = state ? "Disable" : "Enable";
m_custom_camera_input_confirm->SetBackgroundColor(stateColour);
m_custom_camera_input_confirm->SetBorderColor(stateColour);
m_custom_camera_input_confirm->SetLabel(_L(stateText));
}
void CameraPopup::on_switch_recording(wxCommandEvent& event) void CameraPopup::on_switch_recording(wxCommandEvent& event)
{ {
if (!m_obj) return; if (!m_obj) return;
+9 -4
View File
@@ -15,12 +15,14 @@
#include "Widgets/SwitchButton.hpp" #include "Widgets/SwitchButton.hpp"
#include "Widgets/RadioBox.hpp" #include "Widgets/RadioBox.hpp"
#include "Widgets/PopupWindow.hpp" #include "Widgets/PopupWindow.hpp"
#include "Widgets/TextInput.hpp"
namespace Slic3r { namespace Slic3r {
namespace GUI { namespace GUI {
wxDECLARE_EVENT(EVT_VCAMERA_SWITCH, wxMouseEvent); wxDECLARE_EVENT(EVT_VCAMERA_SWITCH, wxMouseEvent);
wxDECLARE_EVENT(EVT_SDCARD_ABSENT_HINT, wxCommandEvent); wxDECLARE_EVENT(EVT_SDCARD_ABSENT_HINT, wxCommandEvent);
wxDECLARE_EVENT(EVT_CAM_SOURCE_CHANGE, wxCommandEvent);
class CameraPopup : public PopupWindow class CameraPopup : public PopupWindow
{ {
@@ -51,6 +53,9 @@ protected:
void on_switch_recording(wxCommandEvent& event); void on_switch_recording(wxCommandEvent& event);
void on_set_resolution(); void on_set_resolution();
void sdcard_absent_hint(); void sdcard_absent_hint();
void on_camera_source_changed(wxCommandEvent& event);
void handle_camera_source_change();
void set_custom_cam_button_state(bool state);
wxWindow * create_item_radiobox(wxString title, wxWindow *parent, wxString tooltip, int padding_left); wxWindow * create_item_radiobox(wxString title, wxWindow *parent, wxString tooltip, int padding_left);
void select_curr_radiobox(int btn_idx); void select_curr_radiobox(int btn_idx);
@@ -71,10 +76,10 @@ private:
wxStaticText* m_text_liveview_retry; wxStaticText* m_text_liveview_retry;
SwitchButton* m_switch_liveview_retry; SwitchButton* m_switch_liveview_retry;
#endif //BBL_RELEASE_TO_PUBLIC #endif //BBL_RELEASE_TO_PUBLIC
// wxStaticText* m_custom_camera_hint; wxStaticText* m_custom_camera_hint;
// TextInput* m_custom_camera_input; TextInput* m_custom_camera_input;
// Button* m_custom_camera_input_confirm; Button* m_custom_camera_input_confirm;
// bool m_custom_camera_enabled{ false }; bool m_custom_camera_enabled{ false };
wxStaticText* m_text_resolution; wxStaticText* m_text_resolution;
wxWindow* m_resolution_options[RESOLUTION_OPTIONS_NUM]; wxWindow* m_resolution_options[RESOLUTION_OPTIONS_NUM];
wxScrolledWindow *m_panel; wxScrolledWindow *m_panel;
+1 -3
View File
@@ -35,9 +35,7 @@ ConnectPrinterDialog::ConnectPrinterDialog(wxWindow *parent, wxWindowID id, cons
sizer_connect = new wxBoxSizer(wxHORIZONTAL); sizer_connect = new wxBoxSizer(wxHORIZONTAL);
m_textCtrl_code = new TextInput(this, wxEmptyString); m_textCtrl_code = new TextInput(this, wxEmptyString);
// OrcaSonar uses a 12-character base32 access code. Keep this field long m_textCtrl_code->GetTextCtrl()->SetMaxLength(10);
// enough for it while retaining the existing validation for LAN codes.
m_textCtrl_code->GetTextCtrl()->SetMaxLength(12);
m_textCtrl_code->SetFont(Label::Body_14); m_textCtrl_code->SetFont(Label::Body_14);
m_textCtrl_code->SetCornerRadius(FromDIP(5)); m_textCtrl_code->SetCornerRadius(FromDIP(5));
m_textCtrl_code->SetSize(wxSize(FromDIP(330), FromDIP(40))); m_textCtrl_code->SetSize(wxSize(FromDIP(330), FromDIP(40)));
@@ -1,7 +1,5 @@
#include "DevConfigUtil.h" #include "DevConfigUtil.h"
#include "slic3r/GUI/DeviceManager.hpp"
#include <wx/dir.h> #include <wx/dir.h>
#include <boost/filesystem/operations.hpp> #include <boost/filesystem/operations.hpp>
#include "../I18N.hpp" #include "../I18N.hpp"
@@ -43,19 +41,6 @@ static void _toolhead_translation_markers()
std::string DevPrinterConfigUtil::m_resource_file_path = ""; std::string DevPrinterConfigUtil::m_resource_file_path = "";
bool DevPrinterConfigUtil::is_printer_model_compatible(const std::string& source_model, MachineObject& machine)
{
const std::string& target_model = machine.printer_type;
if (is_optional_printer_model_id(source_model) || is_optional_printer_model_id(target_model))
return true;
if (source_model == target_model)
return true;
const auto compatible_machine = machine.get_compatible_machine();
return std::find(compatible_machine.begin(), compatible_machine.end(), source_model) != compatible_machine.end();
}
std::map<std::string, std::string> DevPrinterConfigUtil::get_all_model_id_with_name() std::map<std::string, std::string> DevPrinterConfigUtil::get_all_model_id_with_name()
{ {
@@ -25,8 +25,6 @@
namespace Slic3r namespace Slic3r
{ {
class MachineObject;
/// Toolhead component type (extruder / nozzle / hotend) /// Toolhead component type (extruder / nozzle / hotend)
enum class ToolHeadComponent { enum class ToolHeadComponent {
Extruder, Extruder,
@@ -61,10 +59,6 @@ public:
/*printer*/ /*printer*/
// info // info
static std::map<std::string, std::string> get_all_model_id_with_name(); static std::map<std::string, std::string> get_all_model_id_with_name();
// A printer agent may not know the physical model. Keep that case optional so
// model compatibility checks do not turn missing identity into a hard error.
static bool is_printer_model_compatible(const std::string& source_model, MachineObject& machine);
static bool is_optional_printer_model_id(const std::string& model_id) { return model_id.empty(); }
static std::string get_printer_type(const std::string& type_str) { return get_value_from_config<std::string>(type_str, "printer_type"); } static std::string get_printer_type(const std::string& type_str) { return get_value_from_config<std::string>(type_str, "printer_type"); }
static std::string get_printer_display_name(const std::string& type_str) { return get_value_from_config<std::string>(type_str, "display_name"); } static std::string get_printer_display_name(const std::string& type_str) { return get_value_from_config<std::string>(type_str, "display_name"); }
static std::string get_printer_series_str(std::string type_str) { return get_value_from_config<std::string>(type_str, "printer_series"); } static std::string get_printer_series_str(std::string type_str) { return get_value_from_config<std::string>(type_str, "printer_series"); }
+3 -3
View File
@@ -762,9 +762,9 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
{ {
curr_tray->remain = -1; curr_tray->remain = -1;
} }
// The tray objects are reused across status updates. Reset this if (tray_it->contains("tray_slot_placeholder")) {
// state when a previously empty slot receives a filament again. curr_tray->is_slot_placeholder = true;
curr_tray->is_slot_placeholder = tray_it->contains("tray_slot_placeholder"); }
int ams_id_int = 0; int ams_id_int = 0;
int tray_id_int = 0; int tray_id_int = 0;
try try
+49 -130
View File
@@ -1,10 +1,8 @@
#include "DevManager.h"
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <exception> #include <exception>
#include <libslic3r/AppConfig.hpp> #include "DevManager.h"
#include "CloudProvider.hpp"
#include "DevUtil.h" #include "DevUtil.h"
// TODO: remove this include // TODO: remove this include
@@ -17,8 +15,6 @@
#include "libslic3r/Time.hpp" #include "libslic3r/Time.hpp"
#include "IPrinterAgent.hpp"
using namespace nlohmann; using namespace nlohmann;
namespace { namespace {
@@ -50,11 +46,9 @@ namespace {
namespace Slic3r namespace Slic3r
{ {
DeviceManager::DeviceManager(NetworkAgent* agent, bool enable_refresher, AppConfig* app_config) DeviceManager::DeviceManager(NetworkAgent* agent)
{ {
m_agent = agent; m_agent = agent;
m_app_config = app_config;
if (enable_refresher)
m_refresher = new DeviceManagerRefresher(this); m_refresher = new DeviceManagerRefresher(this);
DevPrinterConfigUtil::InitFilePath(resources_dir()); DevPrinterConfigUtil::InitFilePath(resources_dir());
@@ -66,14 +60,9 @@ namespace Slic3r
} }
} }
AppConfig* DeviceManager::get_app_config() const
{
return m_app_config ? m_app_config : GUI::wxGetApp().app_config;
}
void DeviceManager::load_local_machines_from_config() void DeviceManager::load_local_machines_from_config()
{ {
AppConfig* config = get_app_config(); AppConfig* config = GUI::wxGetApp().app_config;
if (!config) if (!config)
return; return;
const auto local_machines = config->get_local_machines(); const auto local_machines = config->get_local_machines();
@@ -99,8 +88,9 @@ namespace Slic3r
} }
} }
void DeviceManager::update_local_machine(const MachineObject& m, AppConfig* config) void DeviceManager::update_local_machine(const MachineObject& m)
{ {
AppConfig* config = GUI::wxGetApp().app_config;
if (config) { if (config) {
if (m.is_lan_mode_printer()) { if (m.is_lan_mode_printer()) {
if (m.has_access_right()) { if (m.has_access_right()) {
@@ -121,7 +111,6 @@ namespace Slic3r
DeviceManager::~DeviceManager() DeviceManager::~DeviceManager()
{ {
if (m_refresher)
delete m_refresher; delete m_refresher;
for (auto it = localMachineList.begin(); it != localMachineList.end(); it++) for (auto it = localMachineList.begin(); it != localMachineList.end(); it++)
@@ -182,22 +171,14 @@ namespace Slic3r
return printer_agent ? printer_agent->get_agent_info().id : ""; return printer_agent ? printer_agent->get_agent_info().id : "";
} }
std::string DeviceManager::get_current_cloud_provider() const
{
const std::string agent_id = get_current_printer_agent_id();
if (!agent_id.empty())
return agent_id == BBL_PRINTER_AGENT_ID ? BBL_CLOUD_PROVIDER : ORCA_CLOUD_PROVIDER;
return GUI::wxGetApp().get_printer_cloud_provider();
}
void DeviceManager::EnableMultiMachine(bool enable) void DeviceManager::EnableMultiMachine(bool enable)
{ {
m_agent->enable_multi_machine(enable); m_agent->enable_multi_machine(enable);
m_enable_mutil_machine = enable; m_enable_mutil_machine = enable;
} }
void DeviceManager::start_refresher() { if (m_refresher) m_refresher->Start(); } void DeviceManager::start_refresher() { m_refresher->Start(); }
void DeviceManager::stop_refresher() { if (m_refresher) m_refresher->Stop(); } void DeviceManager::stop_refresher() { m_refresher->Stop(); }
void DeviceManager::keep_alive() void DeviceManager::keep_alive()
@@ -284,10 +265,6 @@ namespace Slic3r
/* update userMachineList info */ /* update userMachineList info */
auto it = userMachineList.find(dev_id); auto it = userMachineList.find(dev_id);
if (it != userMachineList.end()) { if (it != userMachineList.end()) {
// A reused entry may have been created while another printer agent was active.
// The response was obtained through the current agent, so move ownership with
// the entry; otherwise agent-scoped lists hide it after a preset switch.
it->second->printer_agent_id = get_current_printer_agent_id();
if (it->second->get_dev_ip() != dev_ip || if (it->second->get_dev_ip() != dev_ip ||
it->second->bind_state != bind_state || it->second->bind_state != bind_state ||
it->second->bind_sec_link != sec_link || it->second->bind_sec_link != sec_link ||
@@ -314,15 +291,10 @@ namespace Slic3r
/* update localMachineList */ /* update localMachineList */
it = localMachineList.find(dev_id); it = localMachineList.find(dev_id);
AppConfig* config = get_app_config();
if (it != localMachineList.end()) { if (it != localMachineList.end()) {
// update properties // update properties
/* ip changed */ /* ip changed */
obj = it->second; obj = it->second;
// A reused LAN entry may have been discovered while another printer agent was
// active. The current discovery message establishes ownership for this agent.
obj->printer_agent_id = get_current_printer_agent_id();
if (obj->get_dev_ip().compare(dev_ip) != 0) { if (obj->get_dev_ip().compare(dev_ip) != 0) {
if ( connection_name.empty() ) { if ( connection_name.empty() ) {
@@ -412,6 +384,7 @@ namespace Slic3r
obj->m_is_online = true; obj->m_is_online = true;
//load access code //load access code
AppConfig* config = Slic3r::GUI::wxGetApp().app_config;
if (config) { if (config) {
obj->set_access_code(get_access_code_with_legacy_fallback(config, dev_id, obj->printer_agent_id), false); obj->set_access_code(get_access_code_with_legacy_fallback(config, dev_id, obj->printer_agent_id), false);
} }
@@ -434,7 +407,7 @@ namespace Slic3r
fire_lifecycle_event(LifecycleEvent::DeviceDiscovered, ctx); fire_lifecycle_event(LifecycleEvent::DeviceDiscovered, ctx);
} }
} }
update_local_machine(*obj, config); update_local_machine(*obj);
} }
catch (...) { catch (...) {
; ;
@@ -449,9 +422,6 @@ namespace Slic3r
auto it = localMachineList.find(machine.dev_id); auto it = localMachineList.find(machine.dev_id);
if (it != localMachineList.end()) { if (it != localMachineList.end()) {
obj = it->second; obj = it->second;
// insert_local_device is called by the active agent, so a reused entry must follow
// that agent as well; otherwise the agent-scoped printer list hides it.
obj->printer_agent_id = get_current_printer_agent_id();
} else { } else {
obj = new MachineObject(this, m_agent, machine.dev_name, machine.dev_id, machine.dev_ip); obj = new MachineObject(this, m_agent, machine.dev_name, machine.dev_id, machine.dev_ip);
obj->printer_agent_id = get_current_printer_agent_id(); obj->printer_agent_id = get_current_printer_agent_id();
@@ -469,16 +439,11 @@ namespace Slic3r
obj->last_alive = Slic3r::Utils::get_current_time_utc(); obj->last_alive = Slic3r::Utils::get_current_time_utc();
obj->set_access_code(access_code, false); obj->set_access_code(access_code, false);
update_local_machine(*obj, get_app_config()); update_local_machine(*obj);
return obj; return obj;
} }
void DeviceManager::update_local_machine(const MachineObject& m)
{
update_local_machine(m, GUI::wxGetApp().app_config);
}
int DeviceManager::query_bind_status(std::string& msg, const std::string& provider) int DeviceManager::query_bind_status(std::string& msg, const std::string& provider)
{ {
if (!m_agent) if (!m_agent)
@@ -549,7 +514,7 @@ namespace Slic3r
MachineObject* DeviceManager::get_my_machine(std::string dev_id) MachineObject* DeviceManager::get_my_machine(std::string dev_id)
{ {
auto list = get_my_machine_list(get_current_printer_agent_id()); auto list = get_my_machine_list();
auto it = list.find(dev_id); auto it = list.find(dev_id);
if (it != list.end()) if (it != list.end())
{ {
@@ -586,15 +551,25 @@ namespace Slic3r
OnSelectedMachineChanged(previous_selected_machine, selected_machine); OnSelectedMachineChanged(previous_selected_machine, selected_machine);
} }
void DeviceManager::clear_other_devices() void DeviceManager::clear_other_devices(const std::string& target_agent_id)
{ {
// Device entries are now scoped by printer_agent_id when they are presented. Keep // why: on agent swap, keep "My Devices" but drop the transient "Other Devices"
// agent-owned discoveries across a switch so agents without automatic discovery (and // Those belong to the previous agent's network scan; the new agent's start_discovery re-populates its own.
// plugins whose devices have not received an access code yet) do not lose their list. //
// Entries without an owner are legacy/unscoped and cannot safely be shown. // Also drop "My Devices" stamped by a different agent than the one we're swapping to
// (target_agent_id, passed by the caller since the live agent hasn't been repointed yet
// at this point): otherwise a device first discovered under agent A survives every swap
// with a stale printer_agent_id, stays hidden from every agent's filtered list, and only
// gets re-tagged if something happens to delete and re-create it (e.g. account logout).
// Dropping it here instead lets the new agent's start_discovery re-insert and re-stamp it
// like any other fresh device.
const auto my = get_my_machine_list();
for (auto it = localMachineList.begin(); it != localMachineList.end();) for (auto it = localMachineList.begin(); it != localMachineList.end();)
{ {
if (!it->second || it->second->printer_agent_id.empty()) const bool is_my_device = my.find(it->first) != my.end();
const bool agent_mismatch = !target_agent_id.empty() && it->second &&
it->second->printer_agent_id != target_agent_id;
if (!is_my_device || agent_mismatch)
{ {
delete it->second; delete it->second;
it = localMachineList.erase(it); it = localMachineList.erase(it);
@@ -610,22 +585,8 @@ namespace Slic3r
{ {
BOOST_LOG_TRIVIAL(info) << "set_selected_machine=" << dev_id BOOST_LOG_TRIVIAL(info) << "set_selected_machine=" << dev_id
<< " cur_selected=" << selected_machine; << " cur_selected=" << selected_machine;
auto my_machine_list = get_my_machine_list(get_current_printer_agent_id()); auto my_machine_list = get_my_machine_list();
auto it = my_machine_list.find(dev_id); auto it = my_machine_list.find(dev_id);
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: set_selected_machine lookup dev_id=" << dev_id
<< " found=" << (it != my_machine_list.end())
<< " my_machine_count=" << my_machine_list.size()
<< " current_agent=" << get_current_printer_agent_id()
<< " provider=" << GUI::wxGetApp().get_printer_cloud_provider();
if (it != my_machine_list.end() && it->second) {
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: target machine dev_id=" << it->second->get_dev_id()
<< " printer_agent_id=" << it->second->printer_agent_id
<< " connection_type=" << it->second->connection_type()
<< " dev_connection_type=" << it->second->dev_connection_type;
} else if (!dev_id.empty()) {
BOOST_LOG_TRIVIAL(warning) << "Orca diagnostic: target machine was not found in the current agent's machine list";
return false;
}
// disconnect last if dev_id difference from previous one // disconnect last if dev_id difference from previous one
auto last_selected = my_machine_list.find(selected_machine); auto last_selected = my_machine_list.find(selected_machine);
@@ -636,9 +597,7 @@ namespace Slic3r
m_agent->disconnect_printer(); m_agent->disconnect_printer();
} }
else if (last_selected->second->connection_type() == "cloud") { else if (last_selected->second->connection_type() == "cloud") {
const int result = m_agent->set_user_selected_machine(""); m_agent->set_user_selected_machine("");
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: cleared previous cloud selection dev_id="
<< selected_machine << " result=" << result;
} }
} }
@@ -673,7 +632,11 @@ namespace Slic3r
m_agent->disconnect_printer(); m_agent->disconnect_printer();
it->second->reset(); it->second->reset();
it->second->connect(); #if !BBL_RELEASE_TO_PUBLIC
it->second->connect(Slic3r::GUI::wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false);
#else
it->second->connect(it->second->local_use_ssl);
#endif
it->second->set_lan_mode_connection_state(true); it->second->set_lan_mode_connection_state(true);
} }
} }
@@ -686,16 +649,18 @@ namespace Slic3r
{ {
// diff dev_id, cloud => set_user_selected_machine(new) // diff dev_id, cloud => set_user_selected_machine(new)
BOOST_LOG_TRIVIAL(info) << "set_selected_machine: select new cloud machine, dev_id =" << dev_id; BOOST_LOG_TRIVIAL(info) << "set_selected_machine: select new cloud machine, dev_id =" << dev_id;
const int result = m_agent->set_user_selected_machine(dev_id); m_agent->set_user_selected_machine(dev_id);
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: set new cloud selection dev_id="
<< dev_id << " result=" << result;
it->second->reset(); it->second->reset();
} }
else else
{ {
BOOST_LOG_TRIVIAL(info) << "set_selected_machine: select new lan machine, dev_id =" << dev_id; BOOST_LOG_TRIVIAL(info) << "set_selected_machine: select new lan machine, dev_id =" << dev_id;
it->second->reset(); it->second->reset();
it->second->connect(); #if !BBL_RELEASE_TO_PUBLIC
it->second->connect(Slic3r::GUI::wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false);
#else
it->second->connect(it->second->local_use_ssl);
#endif
it->second->set_lan_mode_connection_state(true); it->second->set_lan_mode_connection_state(true);
} }
} }
@@ -712,8 +677,6 @@ namespace Slic3r
selected_machine = dev_id; selected_machine = dev_id;
record_user_last_machine(selected_machine); record_user_last_machine(selected_machine);
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: DeviceManager selection complete selected_machine="
<< selected_machine;
return true; return true;
} }
@@ -744,9 +707,7 @@ namespace Slic3r
dev_list.push_back(it->first); dev_list.push_back(it->first);
BOOST_LOG_TRIVIAL(trace) << "add_user_subscribe: " << it->first; BOOST_LOG_TRIVIAL(trace) << "add_user_subscribe: " << it->first;
} }
const int result = m_agent->add_subscribe(dev_list); m_agent->add_subscribe(dev_list);
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: add_user_subscribe count=" << dev_list.size()
<< " result=" << result;
} }
@@ -759,9 +720,7 @@ namespace Slic3r
dev_list.push_back(it->first); dev_list.push_back(it->first);
BOOST_LOG_TRIVIAL(trace) << "del_user_subscribe: " << it->first; BOOST_LOG_TRIVIAL(trace) << "del_user_subscribe: " << it->first;
} }
const int result = m_agent->del_subscribe(dev_list); m_agent->del_subscribe(dev_list);
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: del_user_subscribe count=" << dev_list.size()
<< " result=" << result;
} }
void DeviceManager::subscribe_device_list(std::vector<std::string> dev_list) void DeviceManager::subscribe_device_list(std::vector<std::string> dev_list)
@@ -880,24 +839,7 @@ namespace Slic3r
try try
{ {
json j = json::parse(body); json j = json::parse(body);
const std::string provider = GUI::wxGetApp().get_printer_cloud_provider();
const bool has_request_context = j.contains("provider") && j.contains("agent_id") && j.contains("generation");
const std::string current_provider = get_current_cloud_provider();
const std::string provider = j.contains("provider") ? j["provider"].get<std::string>()
: current_provider;
const std::string agent_id = j.contains("agent_id") ? j["agent_id"].get<std::string>()
: get_current_printer_agent_id();
const std::uint64_t generation = j.value("generation", std::uint64_t(0));
if (has_request_context &&
(provider != current_provider ||
agent_id != get_current_printer_agent_id() ||
generation != (m_agent ? m_agent->get_user_machine_list_generation() : 0))) {
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << ": ignoring stale response provider="
<< provider << " agent_id=" << agent_id
<< " generation=" << generation;
return;
}
#if !BBL_RELEASE_TO_PUBLIC #if !BBL_RELEASE_TO_PUBLIC
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": " << j; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": " << j;
@@ -920,14 +862,11 @@ namespace Slic3r
/* update field */ /* update field */
obj = iter->second; obj = iter->second;
obj->set_dev_id(dev_id); obj->set_dev_id(dev_id);
// A device can be rediscovered by a different agent after a preset
// switch while retaining the same MachineObject instance.
obj->printer_agent_id = agent_id;
} }
else else
{ {
obj = new MachineObject(this, m_agent, "", "", ""); obj = new MachineObject(this, m_agent, "", "", "");
obj->printer_agent_id = agent_id; obj->printer_agent_id = get_current_printer_agent_id();
if (m_agent) if (m_agent)
{ {
obj->set_bind_status(m_agent->get_user_name(provider)); obj->set_bind_status(m_agent->get_user_name(provider));
@@ -935,20 +874,13 @@ namespace Slic3r
if (obj->get_dev_ip().empty()) if (obj->get_dev_ip().empty())
{ {
if (AppConfig* config = get_app_config()) obj->get_dev_ip() = Slic3r::GUI::wxGetApp().app_config->get("ip_address", dev_id);
obj->get_dev_ip() = config->get("ip_address", dev_id);
} }
userMachineList.insert(std::make_pair(dev_id, obj)); userMachineList.insert(std::make_pair(dev_id, obj));
} }
if (!obj) continue; if (!obj) continue;
// Orca cloud printers are only ever delivered through this REST
// account list; tag them so DeviceManager's cloud/lan branches
// (subscribe + deselect in set_selected_machine) treat them right.
if (provider == ORCA_CLOUD_PROVIDER)
obj->dev_connection_type = "cloud";
if (!elem["dev_id"].is_null()) if (!elem["dev_id"].is_null())
obj->set_dev_id(elem["dev_id"].get<std::string>()); obj->set_dev_id(elem["dev_id"].get<std::string>());
if (!elem["dev_name"].is_null()) if (!elem["dev_name"].is_null())
@@ -980,12 +912,6 @@ namespace Slic3r
acc_code.erase(std::remove(acc_code.begin(), acc_code.end(), '\n'), acc_code.end()); acc_code.erase(std::remove(acc_code.begin(), acc_code.end(), '\n'), acc_code.end());
obj->set_access_code(acc_code); obj->set_access_code(acc_code);
} }
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: parsed cloud machine dev_id=" << dev_id
<< " name=" << obj->get_dev_name()
<< " agent_id=" << obj->printer_agent_id
<< " connection_type=" << obj->connection_type()
<< " online=" << obj->m_is_online;
} }
//remove MachineObject from userMachineList //remove MachineObject from userMachineList
@@ -1001,9 +927,6 @@ namespace Slic3r
iterat++; iterat++;
} }
} }
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: parse_user_print_info complete provider=" << provider
<< " parsed_count=" << new_list.size()
<< " stored_count=" << userMachineList.size();
} }
} }
catch (std::exception& e) catch (std::exception& e)
@@ -1020,29 +943,25 @@ namespace Slic3r
unsigned int http_code; unsigned int http_code;
std::string body; std::string body;
int result = m_agent->get_user_print_info(&http_code, &body, provider); int result = m_agent->get_user_print_info(&http_code, &body, provider);
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: get_user_print_info provider=" << provider
<< " result=" << result << " http_code=" << http_code
<< " body_bytes=" << body.size();
if (result == 0) if (result == 0)
{ {
// parse_user_print_info and on_machine_alive (SSDP for discovery) both mutate the same userMachineList map. // parse_user_print_info and on_machine_alive (SSDP for discovery) both mutate the same userMachineList map.
// on_machine_alive mutates the map on the UI thread, do the same for parse_user_print_info. // on_machine_alive mutates the map on the UI thread, do the same for parse_user_print_info.
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: queueing parse_user_print_info on UI thread";
Slic3r::GUI::wxGetApp().CallAfter([this, body]() { parse_user_print_info(body); }); Slic3r::GUI::wxGetApp().CallAfter([this, body]() { parse_user_print_info(body); });
} }
} }
void DeviceManager::record_user_last_machine(const std::string& dev_id) void DeviceManager::record_user_last_machine(const std::string& dev_id)
{ {
if (AppConfig* config = get_app_config()) { if (Slic3r::GUI::wxGetApp().app_config) {
config->set("user_last_selected_machine", dev_id); Slic3r::GUI::wxGetApp().app_config->set("user_last_selected_machine", dev_id);
} }
} }
std::string DeviceManager::get_user_last_machine() const std::string DeviceManager::get_user_last_machine() const
{ {
if (AppConfig* config = get_app_config()) { if (Slic3r::GUI::wxGetApp().app_config) {
const auto& user_last_machine = config->get("user_last_selected_machine"); const auto& user_last_machine = Slic3r::GUI::wxGetApp().app_config->get("user_last_selected_machine");
if (!user_last_machine.empty()) { if (!user_last_machine.empty()) {
return user_last_machine; return user_last_machine;
} else if (m_agent) { } else if (m_agent) {
+5 -10
View File
@@ -12,7 +12,6 @@ namespace Slic3r
struct BBLocalMachine; struct BBLocalMachine;
class MachineObject; class MachineObject;
class NetworkAgent; class NetworkAgent;
class AppConfig;
namespace GUI { namespace GUI {
class GUI_App; class GUI_App;
@@ -25,7 +24,6 @@ class DeviceManager
friend class DeviceManagerRefresher; friend class DeviceManagerRefresher;
private: private:
NetworkAgent* m_agent{ nullptr }; NetworkAgent* m_agent{ nullptr };
AppConfig* m_app_config{ nullptr };
DeviceManagerRefresher* m_refresher{ nullptr }; DeviceManagerRefresher* m_refresher{ nullptr };
bool m_enable_mutil_machine = false; bool m_enable_mutil_machine = false;
@@ -37,13 +35,11 @@ private:
std::map<std::string, MachineObject*> userMachineList; /* dev_id -> MachineObject* cloudMachine of User */ std::map<std::string, MachineObject*> userMachineList; /* dev_id -> MachineObject* cloudMachine of User */
public: public:
DeviceManager(NetworkAgent* agent = nullptr, bool enable_refresher = true, DeviceManager(NetworkAgent* agent = nullptr);
AppConfig* app_config = nullptr);
~DeviceManager(); ~DeviceManager();
public: public:
NetworkAgent* get_agent() const { return m_agent; } NetworkAgent* get_agent() const { return m_agent; }
AppConfig* get_app_config() const;
void set_agent(NetworkAgent* agent); void set_agent(NetworkAgent* agent);
void start_refresher(); void start_refresher();
@@ -78,9 +74,10 @@ public:
void erase_user_machine(std::string dev_id) { userMachineList.erase(dev_id); } void erase_user_machine(std::string dev_id) { userMachineList.erase(dev_id); }
void clean_user_info(bool keep_local_selection = false); void clean_user_info(bool keep_local_selection = false);
// Retain agent-owned LAN discoveries across a switch; the active-agent list filter keeps // target_agent_id: id of the agent being swapped to (empty = no agent-mismatch check,
// entries from other agents hidden while allowing them to reappear when switched back. // just the original "drop Other Devices" behavior). Pass the incoming agent's id, not the
void clear_other_devices(); // live one - this runs before the live agent is repointed.
void clear_other_devices(const std::string& target_agent_id = "");
void load_last_machine(); void load_last_machine();
void update_user_machine_list_info(const std::string& provider); void update_user_machine_list_info(const std::string& provider);
@@ -125,7 +122,6 @@ private:
void keep_alive(); void keep_alive();
void check_pushing(); void check_pushing();
std::string get_current_cloud_provider() const;
void OnMachineBindStateChanged(MachineObject* obj, const std::string& new_state); void OnMachineBindStateChanged(MachineObject* obj, const std::string& new_state);
void OnSelectedMachineChanged(const std::string& pre_dev_id, const std::string& new_dev_id); void OnSelectedMachineChanged(const std::string& pre_dev_id, const std::string& new_dev_id);
@@ -138,7 +134,6 @@ public:
std::string connection_type, std::string bind_state, std::string version, std::string connection_type, std::string bind_state, std::string version,
std::string access_code); std::string access_code);
static void update_local_machine(const MachineObject& m); static void update_local_machine(const MachineObject& m);
static void update_local_machine(const MachineObject& m, AppConfig* config);
}; };
class DeviceManagerRefresher : public wxObject class DeviceManagerRefresher : public wxObject
+95 -212
View File
@@ -4,7 +4,6 @@
#include "I18N.hpp" #include "I18N.hpp"
#include "libslic3r/Time.hpp" #include "libslic3r/Time.hpp"
#include "libslic3r/Thread.hpp" #include "libslic3r/Thread.hpp"
#include "slic3r/Utils/Http.hpp"
#include "slic3r/Utils/NetworkAgent.hpp" #include "slic3r/Utils/NetworkAgent.hpp"
#include "slic3r/plugin/PluginManager.hpp" #include "slic3r/plugin/PluginManager.hpp"
#include "slic3r/Utils/NetworkAgentFactory.hpp" #include "slic3r/Utils/NetworkAgentFactory.hpp"
@@ -18,7 +17,6 @@
#include "ReleaseNote.hpp" #include "ReleaseNote.hpp"
#include <thread> #include <thread>
#include <mutex> #include <mutex>
#include <charconv>
#include <codecvt> #include <codecvt>
#include <boost/foreach.hpp> #include <boost/foreach.hpp>
#include <boost/typeof/typeof.hpp> #include <boost/typeof/typeof.hpp>
@@ -56,7 +54,6 @@
#include "DeviceCore/DevStatus.h" #include "DeviceCore/DevStatus.h"
#include "DeviceCore/DevUpgrade.h" #include "DeviceCore/DevUpgrade.h"
#include "IPrinterAgent.hpp"
#define CALI_DEBUG #define CALI_DEBUG
#define MINUTE_30 1800000 //ms #define MINUTE_30 1800000 //ms
@@ -377,22 +374,8 @@ NozzleVolumeType convert_to_nozzle_type(const std::string &str)
wxString MachineObject::get_printer_type_display_str() const wxString MachineObject::get_printer_type_display_str() const
{ {
std::string display_name = DevPrinterConfigUtil::get_printer_display_name(printer_type); std::string display_name = DevPrinterConfigUtil::get_printer_display_name(printer_type);
// Bambu printers use m_resource_file_path + "/printers/" + type_str + ".json", which is a semantic that only works for their profiles.
// For any other profile, we can simply consult preset bundle if the model_id exists.
if (display_name.empty()) {
for (const auto& [vendor_id, vendor] : GUI::wxGetApp().preset_bundle->vendors) {
for (const auto& model : vendor.models) {
if (printer_type == model.model_id)
display_name = model.name;
}
}
}
if (!display_name.empty()) if (!display_name.empty())
return display_name; return display_name;
else if (printer_type == "orcasonar")
return "OrcaSonar Printer";
else else
return _L("Unknown"); return _L("Unknown");
} }
@@ -479,7 +462,7 @@ void MachineObject::set_access_code(std::string code, bool only_refresh)
{ {
this->access_code = code; this->access_code = code;
if (only_refresh) { if (only_refresh) {
AppConfig* config = m_manager ? m_manager->get_app_config() : GUI::wxGetApp().app_config; AppConfig* config = GUI::wxGetApp().app_config;
if (config) { if (config) {
if (is_lan_mode_printer()) { if (is_lan_mode_printer()) {
// why: LAN codes are scoped via BBLocalMachine::access_code, keyed by dev_id and // why: LAN codes are scoped via BBLocalMachine::access_code, keyed by dev_id and
@@ -492,7 +475,7 @@ void MachineObject::set_access_code(std::string code, bool only_refresh)
// fresh from the cloud API's current response, so there's no cross-agent leakage // fresh from the cloud API's current response, so there's no cross-agent leakage
// risk to guard against there. // risk to guard against there.
if (!code.empty()) { if (!code.empty()) {
DeviceManager::update_local_machine(*this, config); DeviceManager::update_local_machine(*this);
} else { } else {
// Only patch an existing record's code - don't persist a brand-new // Only patch an existing record's code - don't persist a brand-new
// never-bound entry just because set_access_code("") was called on it. // never-bound entry just because set_access_code("") was called on it.
@@ -1359,6 +1342,7 @@ int MachineObject::command_get_access_code() {
return this->publish_json(j); return this->publish_json(j);
} }
int MachineObject::command_request_push_all(bool request_now) int MachineObject::command_request_push_all(bool request_now)
{ {
auto curr_time = std::chrono::system_clock::now(); auto curr_time = std::chrono::system_clock::now();
@@ -1490,20 +1474,26 @@ int MachineObject::command_upgrade_module(std::string url, std::string module_ty
int MachineObject::command_xyz_abs() int MachineObject::command_xyz_abs()
{ {
if (!m_agent) return -1; return this->publish_gcode("G90 \n");
return command_with_dialog(m_agent->command_xyz_abs(get_dev_id(), MachineObject::m_sequence_id++, is_lan_mode_printer()));
} }
int MachineObject::command_auto_leveling() int MachineObject::command_auto_leveling()
{ {
if (!m_agent) return -1; return this->publish_gcode("G29 \n");
return command_with_dialog(m_agent->command_auto_leveling(get_dev_id(), MachineObject::m_sequence_id++, is_lan_mode_printer()));
} }
int MachineObject::command_go_home() int MachineObject::command_go_home()
{ {
if (!m_agent) return -1; if (m_support_mqtt_homing)
return command_with_dialog(m_agent->command_go_home(get_dev_id(), this->is_in_printing(), m_support_mqtt_homing, MachineObject::m_sequence_id++, is_lan_mode_printer())); {
json j;
j["print"]["command"] = "back_to_center";
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
return this->publish_json(j);
}
// gcode command
return this->is_in_printing() ? this->publish_gcode("G28 X\n") : this->publish_gcode("G28 \n");
} }
int MachineObject::command_task_partskip(std::vector<int> part_ids) int MachineObject::command_task_partskip(std::vector<int> part_ids)
@@ -1625,14 +1615,23 @@ int MachineObject::command_stop_buzzer()
int MachineObject::command_set_bed(int temp) int MachineObject::command_set_bed(int temp)
{ {
if (!m_agent) return -1; if (m_support_mqtt_bet_ctrl)
return command_with_dialog(m_agent->command_set_bed(get_dev_id(), temp, m_support_mqtt_bet_ctrl, MachineObject::m_sequence_id++, is_lan_mode_printer())); {
json j;
j["print"]["command"] = "set_bed_temp";
j["print"]["temp"] = temp;
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
return this->publish_json(j);
}
std::string gcode_str = (boost::format("M140 S%1%\n") % temp).str();
return this->publish_gcode(gcode_str);
} }
int MachineObject::command_set_nozzle(int temp) int MachineObject::command_set_nozzle(int temp)
{ {
if (!m_agent) return -1; std::string gcode_str = (boost::format("M104 S%1%\n") % temp).str();
return command_with_dialog(m_agent->command_set_nozzle(get_dev_id(), temp, MachineObject::m_sequence_id++, is_lan_mode_printer())); return this->publish_gcode(gcode_str);
} }
int MachineObject::command_set_nozzle_new(int nozzle_id, int temp) int MachineObject::command_set_nozzle_new(int nozzle_id, int temp)
@@ -1737,8 +1736,9 @@ int MachineObject::command_ams_user_settings(bool start_read_opt, bool tray_read
int MachineObject::command_ams_calibrate(int ams_id) int MachineObject::command_ams_calibrate(int ams_id)
{ {
if (!m_agent) return -1; std::string gcode_cmd = (boost::format("M620 C%1% \n") % ams_id).str();
return command_with_dialog(m_agent->command_ams_calibrate(get_dev_id(), ams_id, MachineObject::m_sequence_id++, is_lan_mode_printer())); BOOST_LOG_TRIVIAL(trace) << "ams_debug: gcode_cmd" << gcode_cmd;
return this->publish_gcode(gcode_cmd);
} }
int MachineObject::command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, int nozzle_temp_min, int nozzle_temp_max) int MachineObject::command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, int nozzle_temp_min, int nozzle_temp_max)
@@ -1774,23 +1774,29 @@ int MachineObject::command_ams_filament_settings(int ams_id, int slot_id, std::s
return this->publish_json(j); return this->publish_json(j);
} }
int MachineObject::command_ams_refresh_rfid(int ams_id, int slot_id) int MachineObject::command_ams_refresh_rfid(std::string tray_id)
{ {
if (!m_agent) return -1; std::string gcode_cmd = (boost::format("M620 R%1% \n") % tray_id).str();
return command_with_dialog(m_agent->command_ams_refresh_rfid(get_dev_id(), ams_id, slot_id, MachineObject::m_sequence_id++, is_lan_mode_printer())); BOOST_LOG_TRIVIAL(trace) << "ams_debug: gcode_cmd" << gcode_cmd;
return this->publish_gcode(gcode_cmd);
} }
int MachineObject::command_start_camera() int MachineObject::command_ams_refresh_rfid2(int ams_id, int slot_id)
{ {
if (!m_agent) return -1; json j;
return m_agent->command_start_camera(get_dev_id()); j["print"]["command"] = "ams_get_rfid";
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["print"]["ams_id"] = ams_id;
j["print"]["slot_id"] = slot_id;
return this->publish_json(j);
} }
int MachineObject::command_ams_select_tray(std::string tray_id) int MachineObject::command_ams_select_tray(std::string tray_id)
{ {
if (!m_agent) return -1; std::string gcode_cmd = (boost::format("M620 P%1% \n") % tray_id).str();
return command_with_dialog(m_agent->command_ams_select_tray(get_dev_id(), tray_id, MachineObject::m_sequence_id++, is_lan_mode_printer())); BOOST_LOG_TRIVIAL(trace) << "ams_debug: gcode_cmd" << gcode_cmd;
return this->publish_gcode(gcode_cmd);
} }
int MachineObject::command_ams_control(std::string action) int MachineObject::command_ams_control(std::string action)
@@ -1949,10 +1955,47 @@ int MachineObject::command_ams_air_print_detect(bool air_print_detect)
int MachineObject::command_axis_control(std::string axis, double unit, double input_val, int speed) int MachineObject::command_axis_control(std::string axis, double unit, double input_val, int speed)
{ {
if (!m_agent) return -1; if (m_support_mqtt_axis_control)
return command_with_dialog(m_agent->command_axis_control(get_dev_id(), axis, unit, input_val, speed, is_core_xy(), {
m_support_mqtt_axis_control, MachineObject::m_sequence_id++, int dir = input_val > 0 ? 1 : -1;
is_lan_mode_printer())); // i3-arch printers move the bed for Y/Z, so the on-screen direction is
// reversed — same negation the g-code fallback below applies.
if (!is_core_xy() && (axis.compare("Y") == 0 || axis.compare("Z") == 0)) {
dir = -dir;
}
json j;
j["print"]["command"] = "xyz_ctrl";
j["print"]["axis"] = axis;
j["print"]["dir"] = dir;
j["print"]["mode"] = (std::abs(input_val) >= 10) ? 1 : 0;
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
return this->publish_json(j);
}
double value = input_val;
if (!is_core_xy()) {
if ( axis.compare("Y") == 0
|| axis.compare("Z") == 0) {
value = -1.0 * input_val;
}
}
char cmd[256];
if (axis.compare("X") == 0
|| axis.compare("Y") == 0
|| axis.compare("Z") == 0) {
sprintf(cmd, "M211 S \nM211 X1 Y1 Z1\nM1002 push_ref_mode\nG91 \nG1 %s%0.1f F%d\nM1002 pop_ref_mode\nM211 R\n", axis.c_str(), value * unit, speed);
}
else if (axis.compare("E") == 0) {
sprintf(cmd, "M83 \nG0 %s%0.1f F%d\n", axis.c_str(), value * unit, speed);
}
else {
return -1;
}
return this->publish_gcode(cmd);
} }
int MachineObject::command_extruder_control(int nozzle_id, double val) int MachineObject::command_extruder_control(int nozzle_id, double val)
@@ -2577,12 +2620,7 @@ void MachineObject::reset()
vt_slot.erase(vt_slot.begin() + 1); vt_slot.erase(vt_slot.begin() + 1);
} }
} }
// why: reset reuses MachineObject, so release its lazy subtask
// before dropping the pointer to prevent reconnect leaks.
if (subtask_) {
delete subtask_;
subtask_ = nullptr; subtask_ = nullptr;
}
has_extra_flow_type = false; has_extra_flow_type = false;
m_partskip_ids.clear(); m_partskip_ids.clear();
} }
@@ -2600,63 +2638,15 @@ void MachineObject::set_print_state(std::string status)
} }
} }
// why: printer agents can report progress without BBL cloud task identity. int MachineObject::connect(bool use_openssl)
void MachineObject::update_print_progress(const json& value)
{
if (value.is_string()) {
const std::string progress = value.get<std::string>();
int parsed_progress;
const auto result = std::from_chars(progress.data(), progress.data() + progress.size(), parsed_progress);
if (result.ec != std::errc{} || result.ptr != progress.data() + progress.size())
return;
mc_print_percent = parsed_progress;
} else if (value.is_number_integer())
mc_print_percent = value.get<int>();
else
return;
if (BBLSubTask* curr_task = get_subtask())
curr_task->task_progress = mc_print_percent;
}
int MachineObject::connect()
{ {
if (get_dev_ip().empty()) return -1; if (get_dev_ip().empty()) return -1;
std::string username = m_agent ? m_agent->default_lan_username() : std::string(); std::string username = "bblp";
std::string password = get_access_code(); std::string password = get_access_code();
std::string port;
std::string input = get_dev_ip();
const bool use_ssl = input.rfind("https", 0) == 0;
// This strips out the http/https prefix
std::string host = Http::get_host_from_url(input, &port);
std::string ca_file;
if (GUI::wxGetApp().preset_bundle) {
const auto& config = GUI::wxGetApp().preset_bundle->printers.get_edited_preset().config;
if (port.empty())
port = config.opt_string("printhost_port");
ca_file = config.opt_string("printhost_cafile");
}
if (host.empty())
host = get_dev_ip();
if (m_agent) { if (m_agent) {
try { try {
PrinterConnectionParams params{ return m_agent->connect_printer(get_dev_id(), get_dev_ip(), username, password, use_openssl);
get_dev_id(),
host,
port,
username,
password,
use_ssl,
ca_file
};
return m_agent->connect_printer(params);
} catch (...) { } catch (...) {
; ;
} }
@@ -2772,14 +2762,6 @@ int MachineObject::publish_json(const json& json_item, int qos, int flag)
BOOST_LOG_TRIVIAL(error) << "publish_json: " << json_item.dump() << " code: " << rtn; BOOST_LOG_TRIVIAL(error) << "publish_json: " << json_item.dump() << " code: " << rtn;
} }
// why: the agent is the only thing that knows what it can translate, so it reports
// not-supported in its return value and this - the single funnel every command_* builder
// passes through - is the one place that turns it into something the user sees. No list of
// unsupported commands is needed anywhere: an agent that has no case for a command says so.
if (rtn == ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED || rtn == ORCA_NETWORK_ERR_CAP_NOT_AVAILABLE) {
show_unsupported_dlg(rtn);
}
return rtn; return rtn;
} }
@@ -3098,13 +3080,6 @@ 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);
@@ -3357,7 +3332,10 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
print_type = jj["print_type"].get<std::string>(); print_type = jj["print_type"].get<std::string>();
} }
if (jj.contains("mc_percent")) { if (jj.contains("mc_percent")) {
update_print_progress(jj["mc_percent"]); if (jj["mc_percent"].is_string())
mc_print_percent = stoi(j["print"]["mc_percent"].get<std::string>());
else if (jj["mc_percent"].is_number_integer())
mc_print_percent = j["print"]["mc_percent"].get<int>();
} }
if (jj.contains("mc_print_sub_stage")) { if (jj.contains("mc_print_sub_stage")) {
if (jj["mc_print_sub_stage"].is_number_integer()) if (jj["mc_print_sub_stage"].is_number_integer())
@@ -3527,9 +3505,6 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
this->task_id_ = jj["task_id"].get<std::string>(); this->task_id_ = jj["task_id"].get<std::string>();
} }
if (jj.contains("thumbnail_url") && jj["thumbnail_url"].is_string())
m_agent_thumbnail_url = jj["thumbnail_url"].get<std::string>();
if (jj.contains("job_attr")) { if (jj.contains("job_attr")) {
int jobAttr = jj["job_attr"].get<int>(); int jobAttr = jj["job_attr"].get<int>();
jobState_ = get_flag_bits(jobAttr, 4, 4); jobState_ = get_flag_bits(jobAttr, 4, 4);
@@ -3575,6 +3550,7 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
update_slice_info(jj["project_id"].get<std::string>(), jj["profile_id"].get<std::string>(), jj["subtask_id"].get<std::string>(), plate_index); update_slice_info(jj["project_id"].get<std::string>(), jj["profile_id"].get<std::string>(), jj["subtask_id"].get<std::string>(), plate_index);
BBLSubTask* curr_task = get_subtask(); BBLSubTask* curr_task = get_subtask();
if (curr_task) { if (curr_task) {
curr_task->task_progress = mc_print_percent;
curr_task->printing_status = print_status; curr_task->printing_status = print_status;
curr_task->task_id = jj["subtask_id"].get<std::string>(); curr_task->task_id = jj["subtask_id"].get<std::string>();
} }
@@ -3877,7 +3853,6 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
has_ipcam = true; has_ipcam = true;
} else { } else {
has_ipcam = false; has_ipcam = false;
webcam_stream_url.clear();
} }
} }
if (ipcam.contains("resolution")) { if (ipcam.contains("resolution")) {
@@ -3912,9 +3887,6 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
liveview_local = local_rtsp_url.empty() ? LVL_None : local_rtsp_url == "disable" liveview_local = local_rtsp_url.empty() ? LVL_None : local_rtsp_url == "disable"
? LVL_Disable : boost::algorithm::starts_with(local_rtsp_url, "rtsps") ? LVL_Rtsps : LVL_Rtsp; ? LVL_Disable : boost::algorithm::starts_with(local_rtsp_url, "rtsps") ? LVL_Rtsps : LVL_Rtsp;
} }
if (ipcam.contains("stream_url") && ipcam["stream_url"].is_string()) {
webcam_stream_url = ipcam["stream_url"].get<std::string>();
}
if (ipcam.contains("tutk_server")) { if (ipcam.contains("tutk_server")) {
tutk_state = ipcam["tutk_server"].get<std::string>(); tutk_state = ipcam["tutk_server"].get<std::string>();
} }
@@ -4673,7 +4645,7 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
if (diff.count() > 10.0f) { if (diff.count() > 10.0f) {
BOOST_LOG_TRIVIAL(trace) << "parse_json timeout = " << diff.count(); BOOST_LOG_TRIVIAL(trace) << "parse_json timeout = " << diff.count();
} }
DeviceManager::update_local_machine(*this, m_manager ? m_manager->get_app_config() : GUI::wxGetApp().app_config); DeviceManager::update_local_machine(*this);
return 0; return 0;
} }
@@ -5481,86 +5453,6 @@ 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;
} }
@@ -6066,15 +5958,6 @@ bool MachineObject::HasAms() const
return m_fila_system->HasAms(); return m_fila_system->HasAms();
} }
int MachineObject::command_with_dialog(int cmd_result)
{
if (!m_agent)
return -1;
if (cmd_result == ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED || cmd_result == ORCA_NETWORK_ERR_CAP_NOT_AVAILABLE)
show_unsupported_dlg(cmd_result);
return cmd_result;
}
void change_the_opacity(wxColour& colour) void change_the_opacity(wxColour& colour)
{ {
if (colour.Alpha() == 255) { if (colour.Alpha() == 255) {
+28 -14
View File
@@ -18,7 +18,6 @@
#include "boost/bimap/bimap.hpp" #include "boost/bimap/bimap.hpp"
#include "libslic3r/calib.hpp" #include "libslic3r/calib.hpp"
#include "libslic3r/Utils.hpp" #include "libslic3r/Utils.hpp"
#include "slic3r/Utils/PrinterNetworkTypes.hpp"
#include "DeviceCore/DevDefs.h" #include "DeviceCore/DevDefs.h"
#include "DeviceCore/DevConfigUtil.h" #include "DeviceCore/DevConfigUtil.h"
@@ -101,6 +100,7 @@ struct DevPrintTaskRatingInfo;
// given nozzle diameter (mm), bucketed per nozzle size to mirror the printer firmware. // given nozzle diameter (mm), bucketed per nozzle size to mirror the printer firmware.
bool is_stringing_prone_filament(const std::string& filament_id, float nozzle_diameter); bool is_stringing_prone_filament(const std::string& filament_id, float nozzle_diameter);
class MachineObject class MachineObject
{ {
private: private:
@@ -547,12 +547,30 @@ public:
bool xcam_first_layer_inspector { false }; bool xcam_first_layer_inspector { false };
time_t xcam_first_layer_hold_start = 0; time_t xcam_first_layer_hold_start = 0;
std::string local_rtsp_url; std::string local_rtsp_url;
std::string webcam_stream_url;
std::string tutk_state; std::string tutk_state;
LiveviewLocal liveview_local{ LiveviewLocal::LVL_None }; enum LiveviewLocal {
LiveviewRemote liveview_remote{ LiveviewRemote::LVR_None}; LVL_None,
FileLocal file_local{ FileLocal::FL_None }; LVL_Disable,
FileRemote file_remote{ FileRemote::FR_None }; LVL_Local,
LVL_Rtsps,
LVL_Rtsp
} liveview_local{ LVL_None };
enum LiveviewRemote {
LVR_None,
LVR_Tutk,
LVR_Agora,
LVR_TutkAgora
} liveview_remote{ LVR_None };
enum FileLocal {
FL_None,
FL_Local
} file_local{ FL_None };
enum FileRemote {
FR_None,
FR_Tutk,
FR_Agora,
FR_TutkAgora
} file_remote{ FR_None };
enum PlateMakerDectect : int enum PlateMakerDectect : int
{ {
@@ -678,7 +696,7 @@ public:
/* machine mqtt apis */ /* machine mqtt apis */
int connect(); int connect(bool use_openssl = true);
int disconnect(); int disconnect();
json_diff print_json; json_diff print_json;
@@ -690,8 +708,6 @@ public:
std::string subtask_id_; std::string subtask_id_;
std::string job_id_; std::string job_id_;
std::string last_subtask_id_; std::string last_subtask_id_;
// note: printer-agent-supplied thumbnail url, empty when the agent supplies none.
std::string m_agent_thumbnail_url;
BBLSliceInfo* slice_info {nullptr}; BBLSliceInfo* slice_info {nullptr};
boost::thread* get_slice_info_thread { nullptr }; boost::thread* get_slice_info_thread { nullptr };
boost::thread* get_model_task_thread { nullptr }; boost::thread* get_model_task_thread { nullptr };
@@ -749,7 +765,6 @@ public:
int command_set_printer_nozzle(std::string nozzle_type, float diameter); int command_set_printer_nozzle(std::string nozzle_type, float diameter);
int command_set_printer_nozzle2(int id, std::string nozzle_type, float diameter); int command_set_printer_nozzle2(int id, std::string nozzle_type, float diameter);
int command_get_access_code(); int command_get_access_code();
int command_start_camera();
int command_ack_proceed(json& proceed); int command_ack_proceed(json& proceed);
int command_purification_disable(); int command_purification_disable();
int command_dont_remind_next_time(json& mqtt_guard_json); int command_dont_remind_next_time(json& mqtt_guard_json);
@@ -796,7 +811,8 @@ public:
int command_ams_calibrate(int ams_id); int command_ams_calibrate(int ams_id);
int command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, int nozzle_temp_min, int nozzle_temp_max); int command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, int nozzle_temp_min, int nozzle_temp_max);
int command_ams_select_tray(std::string tray_id); int command_ams_select_tray(std::string tray_id);
int command_ams_refresh_rfid(int ams_id, int slot_id); int command_ams_refresh_rfid(std::string tray_id);
int command_ams_refresh_rfid2(int ams_id, int slot_id);
int command_ams_control(std::string action); int command_ams_control(std::string action);
int command_ams_drying_stop(); int command_ams_drying_stop();
int command_start_extrusion_cali(int tray_index, int nozzle_temp, int bed_temp, float max_volumetric_speed, std::string setting_id = ""); int command_start_extrusion_cali(int tray_index, int nozzle_temp, int bed_temp, float max_volumetric_speed, std::string setting_id = "");
@@ -878,7 +894,6 @@ public:
static bool is_in_printing_status(std::string status); static bool is_in_printing_status(std::string status);
void set_print_state(std::string status); void set_print_state(std::string status);
void update_print_progress(const json& value);
bool is_connected(); bool is_connected();
bool is_connecting(); bool is_connecting();
@@ -947,7 +962,6 @@ 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;
@@ -976,7 +990,7 @@ public:
void command_set_save_remote_print_file_to_storage(bool save); void command_set_save_remote_print_file_to_storage(bool save);
private: private:
int command_with_dialog(int cmd_result);
/* xcam door open check*/ /* xcam door open check*/
bool is_support_door_open_check = false; bool is_support_door_open_check = false;
DoorOpenCheckState xcam_door_open_check = DoorOpenCheckState::DOOR_OPEN_CHECK_DISABLE; DoorOpenCheckState xcam_door_open_check = DoorOpenCheckState::DOOR_OPEN_CHECK_DISABLE;
+44 -31
View File
@@ -4,7 +4,6 @@
#include "libslic3r/Platform.hpp" #include "libslic3r/Platform.hpp"
#include "GUI_App.hpp" #include "GUI_App.hpp"
#include "Shortcuts.hpp" #include "Shortcuts.hpp"
#include "DeviceCore/DevConfigUtil.h"
#include "BindDialog.hpp" #include "BindDialog.hpp"
#include "DeviceManager.hpp" #include "DeviceManager.hpp"
#include "HMS.hpp" #include "HMS.hpp"
@@ -25,6 +24,7 @@
#include <boost/locale/encoding_utf.hpp> #include <boost/locale/encoding_utf.hpp>
#include <boost/log/detail/native_typeof.hpp> #include <boost/log/detail/native_typeof.hpp>
#include <libslic3r/Config.hpp> #include <libslic3r/Config.hpp>
#include <mutex>
#include <slic3r/plugin/PythonPluginInterface.hpp> #include <slic3r/plugin/PythonPluginInterface.hpp>
#include <wx/event.h> #include <wx/event.h>
@@ -41,11 +41,9 @@
#include <iterator> #include <iterator>
#include <exception> #include <exception>
#include <cstdlib> #include <cstdlib>
#include <mutex>
#include <regex> #include <regex>
#include <thread> #include <thread>
#include <string_view> #include <string_view>
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <boost/format.hpp> #include <boost/format.hpp>
@@ -90,6 +88,7 @@
#include "libslic3r/Thread.hpp" #include "libslic3r/Thread.hpp"
#include "libslic3r/miniz_extension.hpp" #include "libslic3r/miniz_extension.hpp"
#include "libslic3r/Utils.hpp" #include "libslic3r/Utils.hpp"
#include "libslic3r/Color.hpp"
#include "slic3r/plugin/PluginManager.hpp" #include "slic3r/plugin/PluginManager.hpp"
#include "slic3r/plugin/host/PluginHostUi.hpp" #include "slic3r/plugin/host/PluginHostUi.hpp"
#include "slic3r/plugin/PythonInterpreter.hpp" #include "slic3r/plugin/PythonInterpreter.hpp"
@@ -109,18 +108,19 @@
#include "../Utils/PrintHost.hpp" #include "../Utils/PrintHost.hpp"
#include "../Utils/Process.hpp" #include "../Utils/Process.hpp"
#include "../Utils/wxInspectorPlugins/Registration.hpp" #include "../Utils/wxInspectorPlugins/Registration.hpp"
#include "../Utils/MacDarkMode.hpp"
#include "../Utils/Http.hpp" #include "../Utils/Http.hpp"
#include "../Utils/InstanceID.hpp" #include "../Utils/InstanceID.hpp"
#include "../Utils/UndoRedo.hpp" #include "../Utils/UndoRedo.hpp"
#include "slic3r/Config/Snapshot.hpp" #include "slic3r/Config/Snapshot.hpp"
#include "Preferences.hpp" #include "Preferences.hpp"
#include "Tab.hpp" #include "Tab.hpp"
#include "SysInfoDialog.hpp"
#include "UpdateDialogs.hpp" #include "UpdateDialogs.hpp"
#include "Mouse3DController.hpp" #include "Mouse3DController.hpp"
#include "RemovableDriveManager.hpp" #include "RemovableDriveManager.hpp"
#include "InstanceCheck.hpp" #include "InstanceCheck.hpp"
#ifdef __APPLE__ #ifdef __APPLE__
#include "../Utils/MacDarkMode.hpp"
#include "DeepLinkHandlerMac.h" #include "DeepLinkHandlerMac.h"
#endif #endif
#include "NotificationManager.hpp" #include "NotificationManager.hpp"
@@ -129,6 +129,8 @@
#include "PrintHostDialogs.hpp" #include "PrintHostDialogs.hpp"
#include "NetworkPluginDialog.hpp" #include "NetworkPluginDialog.hpp"
#include "DesktopIntegrationDialog.hpp" #include "DesktopIntegrationDialog.hpp"
#include "SendSystemInfoDialog.hpp"
#include "ParamsDialog.hpp"
#include "KBShortcutsDialog.hpp" #include "KBShortcutsDialog.hpp"
#include "DownloadProgressDialog.hpp" #include "DownloadProgressDialog.hpp"
#include "TroubleshootDialog.hpp" #include "TroubleshootDialog.hpp"
@@ -139,6 +141,7 @@
#include "Widgets/ProgressDialog.hpp" #include "Widgets/ProgressDialog.hpp"
//BBS: DailyTip and UserGuide Dialog //BBS: DailyTip and UserGuide Dialog
#include "WebDownPluginDlg.hpp"
#include "WebGuideDialog.hpp" #include "WebGuideDialog.hpp"
#include "ReleaseNote.hpp" #include "ReleaseNote.hpp"
#include "PrivacyUpdateDialog.hpp" #include "PrivacyUpdateDialog.hpp"
@@ -2395,23 +2398,34 @@ GUI_App::~GUI_App()
bool GUI_App::is_blocking_printing(MachineObject *obj_) bool GUI_App::is_blocking_printing(MachineObject *obj_)
{ {
PresetBundle *preset_bundle = wxGetApp().preset_bundle; DeviceManager *dev = Slic3r::GUI::wxGetApp().getDeviceManager();
const std::string source_model = preset_bundle if (!dev) return true;
? preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle) std::string target_model;
: std::string(); if (obj_ == nullptr) {
return is_blocking_printing(obj_, source_model); obj_ = dev->get_selected_machine();
if (obj_) {
target_model = obj_->printer_type;
}
} else {
target_model = obj_->printer_type;
} }
bool GUI_App::is_blocking_printing(MachineObject *obj_, const std::string& source_model)
{
DeviceManager *dev = getDeviceManager();
if (!dev) return true;
if (obj_ == nullptr)
obj_ = dev->get_selected_machine();
if (!obj_) if (!obj_)
{
return false; return false;
}
return !DevPrinterConfigUtil::is_printer_model_compatible(source_model, *obj_); PresetBundle *preset_bundle = wxGetApp().preset_bundle;
std::string source_model = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle);
if (source_model != target_model) {
std::vector<std::string> compatible_machine = obj_->get_compatible_machine();
vector<std::string>::iterator it = find(compatible_machine.begin(), compatible_machine.end(), source_model);
if (it == compatible_machine.end()) {
return true;
}
}
return false;
} }
// If formatted for github, plaintext with OpenGL extensions enclosed into <details>. // If formatted for github, plaintext with OpenGL extensions enclosed into <details>.
@@ -3972,16 +3986,20 @@ void GUI_App::set_live_printer_agent(std::shared_ptr<IPrinterAgent> agent)
m_agent->set_user_selected_machine(""); m_agent->set_user_selected_machine("");
// note: belt-and-suspenders (precedent: DeviceManagerRefresher::on_timer) // note: belt-and-suspenders (precedent: DeviceManagerRefresher::on_timer)
dev->OnSelectedMachineLost(); // why: clear stale sidebar sync-status / AMS dev->OnSelectedMachineLost(); // why: clear stale sidebar sync-status / AMS
// why: retain agent-owned LAN discoveries so agents without automatic discovery (for // why: drop stale LAN discoveries; keep My Devices, but only those belonging to the
// example the Moonraker-based Qidi/Snapmaker agents) can reuse them after a switch. // agent we're about to swap to, so a device stamped by the outgoing agent doesn't
dev->clear_other_devices(); // linger hidden - the new agent's start_discovery re-inserts and re-stamps it fresh.
// agent is null when clearing the live agent entirely (e.g. plugin unload); there's no
// target to filter against then, so fall back to the original "keep all My Devices"
// behavior rather than guessing.
dev->clear_other_devices(agent ? agent->get_agent_info().id : std::string());
} }
m_agent->set_printer_agent(agent); m_agent->set_printer_agent(agent);
sidebar().update_all_preset_comboboxes(); sidebar().update_all_preset_comboboxes();
} }
std::string GUI_App::resolve_printer_agent_id(const std::string& stored_id) const std::string GUI_App::resolve_printer_agent_id(const std::string& stored_id)
{ {
if (!stored_id.empty()) if (!stored_id.empty())
return stored_id; return stored_id;
@@ -4017,7 +4035,6 @@ void GUI_App::switch_printer_agent()
std::string log_dir = data_dir(); std::string log_dir = data_dir();
std::string cloud_agent_id = agent_info.id == BBL_PRINTER_AGENT_ID ? BBL_CLOUD_PROVIDER : ORCA_CLOUD_PROVIDER; std::string cloud_agent_id = agent_info.id == BBL_PRINTER_AGENT_ID ? BBL_CLOUD_PROVIDER : ORCA_CLOUD_PROVIDER;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << agent_info.id;
std::shared_ptr<ICloudServiceAgent> cloud_agent = m_agent->get_cloud_agent(cloud_agent_id); std::shared_ptr<ICloudServiceAgent> cloud_agent = m_agent->get_cloud_agent(cloud_agent_id);
// Create new printer agent via registry // Create new printer agent via registry
@@ -4031,10 +4048,8 @@ void GUI_App::switch_printer_agent()
return; return;
} }
// Compare the registered IDs, not only the implementation pointer. Different registry IDs // The factory caches agents per ID, so an identical pointer means the agent type is unchanged.
// may intentionally be backed by the same implementation object (especially for plugins). if (m_agent->get_printer_agent() == new_printer_agent) {
const auto current_printer_agent = m_agent->get_printer_agent();
if (current_printer_agent && current_printer_agent->get_agent_info().id == effective_agent_id) {
// Orca: the agent type is unchanged (e.g. switching between two Moonraker/Klipper // Orca: the agent type is unchanged (e.g. switching between two Moonraker/Klipper
// printer presets), so the selected machine and the agent's cached device_info still // printer presets), so the selected machine and the agent's cached device_info still
// point at the previously active printer preset. Re-select the machine when the new // point at the previously active printer preset. Re-select the machine when the new
@@ -5017,16 +5032,14 @@ bool GUI_App::is_user_login(const std::string& provider/* = ORCA_CLOUD_PROVIDER*
return false; return false;
} }
std::string GUI_App::get_printer_cloud_provider() const const std::string& GUI_App::get_printer_cloud_provider() const
{ {
const std::string agent_id = resolve_printer_agent_id( // Orca todo: this need to be revisted. currently it is mainly used for device manager and related clausses and only bambu machines use them.
preset_bundle ? preset_bundle->printers.get_edited_preset().config.opt_string("printer_agent") //
: std::string()); return BBL_CLOUD_PROVIDER;
return agent_id == BBL_PRINTER_AGENT_ID ? BBL_CLOUD_PROVIDER : ORCA_CLOUD_PROVIDER;
} }
bool GUI_App::check_login(const std::string& provider/* = ORCA_CLOUD_PROVIDER*/) bool GUI_App::check_login(const std::string& provider/* = ORCA_CLOUD_PROVIDER*/)
{ {
bool result = false; bool result = false;
+5 -3
View File
@@ -24,6 +24,9 @@
#include <wx/snglinst.h> #include <wx/snglinst.h>
#include <wx/msgdlg.h> #include <wx/msgdlg.h>
#include <mutex>
#include <stack>
//#define BBL_HAS_FIRST_PAGE 1 //#define BBL_HAS_FIRST_PAGE 1
#define STUDIO_INACTIVE_TIMEOUT 15*60*1000 #define STUDIO_INACTIVE_TIMEOUT 15*60*1000
#define LOG_FILES_MAX_NUM 30 #define LOG_FILES_MAX_NUM 30
@@ -370,7 +373,6 @@ public:
EAppMode get_app_mode() const { return m_app_mode; } EAppMode get_app_mode() const { return m_app_mode; }
Slic3r::DeviceManager* getDeviceManager() { return m_device_manager; } Slic3r::DeviceManager* getDeviceManager() { return m_device_manager; }
bool is_blocking_printing(MachineObject *obj_ = nullptr); bool is_blocking_printing(MachineObject *obj_ = nullptr);
bool is_blocking_printing(MachineObject *obj_, const std::string& source_model);
Slic3r::TaskManager* getTaskManager() { return m_task_manager; } Slic3r::TaskManager* getTaskManager() { return m_task_manager; }
HMSQuery* get_hms_query() { return hms_query; } HMSQuery* get_hms_query() { return hms_query; }
NetworkAgent* getAgent() { return m_agent; } NetworkAgent* getAgent() { return m_agent; }
@@ -378,7 +380,7 @@ public:
// Reconcile the live printer agent with the stored preset selection. // Reconcile the live printer agent with the stored preset selection.
void switch_printer_agent(); void switch_printer_agent();
std::string resolve_printer_agent_id(const std::string& stored_id) const; std::string resolve_printer_agent_id(const std::string& stored_id);
// ORCA TODO: in the future, bbl presets should specify "bbl" printer agent id // ORCA TODO: in the future, bbl presets should specify "bbl" printer agent id
// then, all resolve and canonical would just be ORCA<->"" // then, all resolve and canonical would just be ORCA<->""
std::string canonical_printer_agent_id(const std::string& picked_id); std::string canonical_printer_agent_id(const std::string& picked_id);
@@ -509,7 +511,7 @@ public:
bool check_login(const std::string& provider = ORCA_CLOUD_PROVIDER); bool check_login(const std::string& provider = ORCA_CLOUD_PROVIDER);
void get_login_info(const std::string& provider = ORCA_CLOUD_PROVIDER); void get_login_info(const std::string& provider = ORCA_CLOUD_PROVIDER);
bool is_user_login(const std::string& provider = ORCA_CLOUD_PROVIDER); bool is_user_login(const std::string& provider = ORCA_CLOUD_PROVIDER);
std::string get_printer_cloud_provider() const; const std::string& get_printer_cloud_provider() const;
void request_user_login(int online_login = 0, const std::string& provider = ORCA_CLOUD_PROVIDER); void request_user_login(int online_login = 0, const std::string& provider = ORCA_CLOUD_PROVIDER);
void request_user_handle(int online_login = 0, const std::string& provider = ORCA_CLOUD_PROVIDER); void request_user_handle(int online_login = 0, const std::string& provider = ORCA_CLOUD_PROVIDER);
-26
View File
@@ -1,26 +0,0 @@
#pragma once
#include <wx/mediactrl.h>
#include <wx/uri.h>
namespace Slic3r { namespace GUI {
class IMediaController
{
public:
virtual ~IMediaController() = default;
virtual void Load(wxURI url) = 0;
virtual void Play() = 0;
virtual void Stop() = 0;
virtual wxMediaState GetState() { return wxMediaState{}; }
virtual int GetLastError() const { return {}; };
virtual wxSize GetVideoSize() const { return {}; };
};
}} // namespace Slic3r::GUI
+1 -2
View File
@@ -15,7 +15,6 @@
#include "slic3r/Utils/FileTransferUtils.hpp" #include "slic3r/Utils/FileTransferUtils.hpp"
#include "slic3r/Utils/BBLNetworkPlugin.hpp" #include "slic3r/Utils/BBLNetworkPlugin.hpp"
#include "NetworkAgent.hpp"
namespace Slic3r { namespace Slic3r {
namespace GUI { namespace GUI {
@@ -217,7 +216,7 @@ void PrintJob::process(Ctl &ctl)
params.dev_ip = m_dev_ip; params.dev_ip = m_dev_ip;
params.use_ssl_for_ftp = m_local_use_ssl_for_ftp; params.use_ssl_for_ftp = m_local_use_ssl_for_ftp;
params.use_ssl_for_mqtt = m_local_use_ssl; params.use_ssl_for_mqtt = m_local_use_ssl;
params.username = m_agent->default_lan_username(); params.username = "bblp";
params.password = m_access_code; params.password = m_access_code;
// check access code and ip address // check access code and ip address
+2 -2
View File
@@ -127,7 +127,7 @@ void SendJob::process(Ctl &ctl)
if (m_is_check_mode) { if (m_is_check_mode) {
PrintParams verify_params; PrintParams verify_params;
verify_params.dev_ip = m_dev_ip; verify_params.dev_ip = m_dev_ip;
verify_params.username = agent->default_lan_username(); verify_params.username = "bblp";
verify_params.password = m_access_code; verify_params.password = m_access_code;
verify_params.use_ssl_for_ftp = m_local_use_ssl_for_ftp; verify_params.use_ssl_for_ftp = m_local_use_ssl_for_ftp;
verify_params.use_ssl_for_mqtt = m_local_use_ssl; verify_params.use_ssl_for_mqtt = m_local_use_ssl;
@@ -219,7 +219,7 @@ void SendJob::process(Ctl &ctl)
// local print access // local print access
params.dev_ip = m_dev_ip; params.dev_ip = m_dev_ip;
params.username = agent->default_lan_username(); params.username = "bblp";
params.password = m_access_code; params.password = m_access_code;
params.use_ssl_for_ftp = m_local_use_ssl_for_ftp; params.use_ssl_for_ftp = m_local_use_ssl_for_ftp;
params.use_ssl_for_mqtt = m_local_use_ssl; params.use_ssl_for_mqtt = m_local_use_ssl;
+1 -1
View File
@@ -4443,7 +4443,7 @@ void MainFrame::load_printer_url()
if (auto *device_manager = wxGetApp().getDeviceManager()) { if (auto *device_manager = wxGetApp().getDeviceManager()) {
auto *machine = device_manager->get_selected_machine(); auto *machine = device_manager->get_selected_machine();
if (!machine) { if (!machine) {
auto machines = device_manager->get_my_machine_list(device_manager->get_current_printer_agent_id()); auto machines = device_manager->get_my_machine_list();
if (machines.size() == 1) if (machines.size() == 1)
machine = machines.begin()->second; machine = machines.begin()->second;
} }
+43 -291
View File
@@ -1,6 +1,4 @@
#include "MediaPlayCtrl.h" #include "MediaPlayCtrl.h"
#include "WebMediaController.hpp"
#include "IPrinterAgent.hpp"
#include "Widgets/Button.hpp" #include "Widgets/Button.hpp"
#include "Widgets/CheckBox.hpp" #include "Widgets/CheckBox.hpp"
#include "Widgets/Label.hpp" #include "Widgets/Label.hpp"
@@ -9,7 +7,6 @@
#include "DeviceManager.hpp" #include "DeviceManager.hpp"
#include "DeviceCore/DevConfigUtil.h" #include "DeviceCore/DevConfigUtil.h"
#include "slic3r/Utils/NetworkAgent.hpp" #include "slic3r/Utils/NetworkAgent.hpp"
#include "slic3r/Utils/NetworkAgentFactory.hpp"
#include "libslic3r/Thread.hpp" #include "libslic3r/Thread.hpp"
#include "libslic3r/AppConfig.hpp" #include "libslic3r/AppConfig.hpp"
#include "I18N.hpp" #include "I18N.hpp"
@@ -18,15 +15,11 @@
#include "slic3r/Utils/BBLNetworkPlugin.hpp" #include "slic3r/Utils/BBLNetworkPlugin.hpp"
#include <algorithm>
#include <boost/lexical_cast.hpp> #include <boost/lexical_cast.hpp>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <boost/nowide/cstdio.hpp> #include <boost/nowide/cstdio.hpp>
#include <boost/nowide/fstream.hpp> #include <boost/nowide/fstream.hpp>
#include <boost/nowide/utf8_codecvt.hpp> #include <boost/nowide/utf8_codecvt.hpp>
#include <slic3r/GUI/DeviceManager.hpp>
#include <wx/mediactrl.h>
#undef pid_t #undef pid_t
#include <boost/process.hpp> #include <boost/process.hpp>
#ifdef __WIN32__ #ifdef __WIN32__
@@ -55,7 +48,6 @@ namespace GUI {
MediaPlayCtrl::MediaPlayCtrl(wxWindow *parent, wxMediaCtrl3 *media_ctrl, const wxPoint &pos, const wxSize &size) MediaPlayCtrl::MediaPlayCtrl(wxWindow *parent, wxMediaCtrl3 *media_ctrl, const wxPoint &pos, const wxSize &size)
: wxPanel(parent, wxID_ANY, pos, size) : wxPanel(parent, wxID_ANY, pos, size)
, m_media_ctrl(media_ctrl) , m_media_ctrl(media_ctrl)
, m_active_media_controller(media_ctrl)
{ {
SetLabel("MediaPlayCtrl"); SetLabel("MediaPlayCtrl");
SetBackgroundColour(*wxWHITE); SetBackgroundColour(*wxWHITE);
@@ -102,10 +94,7 @@ MediaPlayCtrl::MediaPlayCtrl(wxWindow *parent, wxMediaCtrl3 *media_ctrl, const w
}); });
m_button_play->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](auto &e) { TogglePlay(); }); m_button_play->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](auto &e) { TogglePlay(); });
m_button_play->Bind(wxEVT_RIGHT_UP, [this](auto & e) { m_button_play->Bind(wxEVT_RIGHT_UP, [this](auto & e) { m_media_ctrl->Play(); });
if (m_active_media_controller)
m_active_media_controller->Play();
});
// Orca: live-view FAQ link binding removed (vendor URL) // Orca: live-view FAQ link binding removed (vendor URL)
Bind(wxEVT_RIGHT_UP, [this](auto & e) { Bind(wxEVT_RIGHT_UP, [this](auto & e) {
@@ -150,12 +139,9 @@ MediaPlayCtrl::MediaPlayCtrl(wxWindow *parent, wxMediaCtrl3 *media_ctrl, const w
MediaPlayCtrl::~MediaPlayCtrl() MediaPlayCtrl::~MediaPlayCtrl()
{ {
if (m_webrtc_ctrl)
m_webrtc_ctrl->Stop();
m_media_ctrl->EndExternalStream();
{ {
boost::unique_lock lock(m_mutex); boost::unique_lock lock(m_mutex);
m_tasks.push_back({"<exit>", nullptr}); m_tasks.push_back("<exit>");
m_cond.notify_all(); m_cond.notify_all();
} }
while (!m_thread.try_join_for(boost::chrono::milliseconds(10))) { while (!m_thread.try_join_for(boost::chrono::milliseconds(10))) {
@@ -165,79 +151,9 @@ MediaPlayCtrl::~MediaPlayCtrl()
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": " << this; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": " << this;
} }
void MediaPlayCtrl::SetWebMediaController(IMediaController *ctrl)
{
m_web_ctrl = ctrl;
set_active_media_controller(current_mode());
}
void MediaPlayCtrl::set_active_media_controller(CameraStreamMode mode)
{
switch (mode) {
case CameraStreamMode::http_snapshot:
if (auto *web_ctrl = dynamic_cast<WebMediaController *>(m_web_ctrl))
web_ctrl->set_mode(mode);
m_active_media_controller = m_web_ctrl;
break;
case CameraStreamMode::webrtc:
if (!m_webrtc_ctrl) {
m_webrtc_ctrl = std::make_unique<WebRtcMediaController>(
[this](const wxImage& image, wxSize size) { m_media_ctrl->SetExternalFrame(image, size); },
[this, token = std::weak_ptr<int>(m_token)](WebRtcMediaController::Status status) {
if (token.expired())
return;
CallAfter([this, status] { on_webrtc_status(status); });
});
}
m_active_media_controller = m_webrtc_ctrl.get();
break;
default:
m_active_media_controller = m_media_ctrl;
break;
}
}
CameraStreamMode MediaPlayCtrl::current_mode() const
{
auto agent = wxGetApp().getAgent();
return agent ? agent->get_camera_stream_mode() : CameraStreamMode::none;
}
void MediaPlayCtrl::SetMachineObject(MachineObject* obj) void MediaPlayCtrl::SetMachineObject(MachineObject* obj)
{ {
const CameraStreamMode mode = current_mode(); std::string machine = obj ? obj->get_dev_id() : "";
if (mode != m_last_mode) {
if (m_last_state != MEDIASTATE_IDLE) {
m_failed_code = 0; // a mode switch is not a stream failure - don't arm back-off
Stop(" ");
}
m_last_mode = mode;
}
set_active_media_controller(mode);
const bool uses_local_camera_url = mode == CameraStreamMode::http || mode == CameraStreamMode::https ||
mode == CameraStreamMode::http_snapshot || mode == CameraStreamMode::rtsp;
const bool uses_webrtc = mode == CameraStreamMode::webrtc;
const std::string machine = obj ? obj->get_dev_id() : "";
bool changed = false;
if (uses_local_camera_url) {
auto agent = wxGetApp().getAgent();
std::string url = agent ? agent->get_local_camera_stream_url() : "";
m_camera_exists = !url.empty();
Enable(obj && m_camera_exists);
changed = machine != m_machine || url != m_agent_camera_url;
m_agent_camera_url = url;
m_url = from_u8(url);
} else if (uses_webrtc) {
m_camera_exists = obj != nullptr;
Enable(obj != nullptr);
changed = machine != m_machine;
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::SetMachineObject webrtc: changed=" << changed
<< " last_state=" << m_last_state << " web_user_stopped=" << m_web_user_stopped;
m_url.clear();
m_agent_camera_url.clear();
} else {
if (obj) { if (obj) {
m_camera_exists = obj->has_ipcam; m_camera_exists = obj->has_ipcam;
m_dev_ver = obj->get_ota_version(); m_dev_ver = obj->get_ota_version();
@@ -251,12 +167,12 @@ void MediaPlayCtrl::SetMachineObject(MachineObject* obj)
if (DevPrinterConfigUtil::get_printer_series_str(obj->printer_type) == "series_o" && BBLNetworkPlugin::instance().use_legacy_network()) { if (DevPrinterConfigUtil::get_printer_series_str(obj->printer_type) == "series_o" && BBLNetworkPlugin::instance().use_legacy_network()) {
// Legacy plugin cannot support remote play for H2D, force using local mode // Legacy plugin cannot support remote play for H2D, force using local mode
m_remote_proto = LiveviewRemote::LVR_None; m_remote_proto = MachineObject::LVR_None;
} }
} else { } else {
m_camera_exists = false; m_camera_exists = false;
m_lan_mode = false; m_lan_mode = false;
m_lan_proto = LiveviewLocal::LVL_None; m_lan_proto = MachineObject::LVL_None;
m_lan_ip.clear(); m_lan_ip.clear();
m_lan_passwd.clear(); m_lan_passwd.clear();
m_dev_ver.clear(); m_dev_ver.clear();
@@ -265,9 +181,11 @@ void MediaPlayCtrl::SetMachineObject(MachineObject* obj)
m_device_busy = false; m_device_busy = false;
} }
Enable(obj && obj->is_info_ready() && obj->m_push_count > 0); Enable(obj && obj->is_info_ready() && obj->m_push_count > 0);
if (machine == m_machine) if (machine == m_machine) {
if (m_last_state == MEDIASTATE_IDLE && IsEnabled())
Play();
return; return;
}
m_machine = machine; m_machine = machine;
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl switch machine: " << m_machine; BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl switch machine: " << m_machine;
m_disable_lan = false; m_disable_lan = false;
@@ -286,23 +204,6 @@ void MediaPlayCtrl::SetMachineObject(MachineObject* obj)
m_next_retry = wxDateTime::Now() + wxTimeSpan::Seconds(2); m_next_retry = wxDateTime::Now() + wxTimeSpan::Seconds(2);
else else
SetStatus("", false); SetStatus("", false);
return;
}
m_machine = machine;
if (!changed)
return;
// A genuine target switch is not a stream failure and should clear the
// manual-stop state before the new target is allowed to play.
m_web_user_stopped = false;
if (uses_local_camera_url) {
m_failed_code = 0;
m_failed_retry = 0;
m_next_retry = wxDateTime();
}
if (m_last_state != MEDIASTATE_IDLE)
Stop(" ");
} }
wxString hide_id_middle_string(wxString const &str, size_t offset = 0, size_t length = -1) wxString hide_id_middle_string(wxString const &str, size_t offset = 0, size_t length = -1)
@@ -353,71 +254,13 @@ void refresh_agora_url(char const* device, char const* dev_ver, char const* chan
void MediaPlayCtrl::Play() void MediaPlayCtrl::Play()
{ {
if ((m_next_retry.IsValid() && wxDateTime::Now() < m_next_retry) || !IsShownOnScreen() || m_last_state != MEDIASTATE_IDLE) if (!m_next_retry.IsValid() || wxDateTime::Now() < m_next_retry)
return; return;
if (!IsShownOnScreen())
const CameraStreamMode mode = current_mode();
set_active_media_controller(mode);
m_last_mode = mode;
auto agent = wxGetApp().getAgent();
auto printer_agent = agent ? agent->get_printer_agent() : nullptr;
const bool is_bbl = (printer_agent ? printer_agent->get_agent_info().id : "") == BBL_PRINTER_AGENT_ID;
if (!is_bbl) {
const bool is_webrtc = mode == CameraStreamMode::webrtc;
const bool is_snapshot = mode == CameraStreamMode::http_snapshot;
const bool is_http_stream = mode == CameraStreamMode::http || mode == CameraStreamMode::https || mode == CameraStreamMode::rtsp;
if (!is_webrtc && !is_snapshot && !is_http_stream)
return; return;
if (m_last_state != MEDIASTATE_IDLE) {
auto *webrtc_ctrl = is_webrtc ? dynamic_cast<WebRtcMediaController *>(m_active_media_controller) : nullptr;
if (is_webrtc && (!webrtc_ctrl || !m_media_ctrl)) {
Stop(_L("Please confirm if the printer is connected."));
return; return;
} }
if (webrtc_ctrl && webrtc_ctrl->is_active())
return;
m_failed_code = 0;
if (!m_active_media_controller || m_machine.empty() || !IsEnabled() || !m_camera_exists ||
(!is_webrtc && m_url.IsEmpty())) {
Stop(_L("Please confirm if the printer is connected."));
return;
}
if (is_webrtc) {
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::Play webrtc: last_state=" << m_last_state << " failed_retry=" << m_failed_retry
<< " shown=" << IsShownOnScreen();
auto channel = agent ? agent->create_camera_signaling_channel(m_machine, wxGetApp().get_printer_cloud_provider()) : nullptr;
if (!channel) {
Stop(_L("Sign in to OrcaCloud to view the camera."));
return;
}
webrtc_ctrl->set_signalling_channel(std::move(channel));
m_media_ctrl->BeginExternalStream();
m_last_state = MEDIASTATE_INITIALIZING;
SetStatus(_L("Initializing..."), false);
} else if (is_snapshot) {
m_last_state = wxMEDIASTATE_PLAYING;
SetStatus(_L("Playing..."), false);
}
m_button_play->SetIcon("media_stop");
if (m_active_media_controller == m_media_ctrl) {
// wxMediaCtrl3 reports when it has a decoded frame; load() waits for
// that event before queuing Play so the stream is not marked stopped.
load();
} else {
m_active_media_controller->Load(wxURI(m_url));
m_active_media_controller->Play();
}
if (webrtc_ctrl) {
m_webrtc_epoch = webrtc_ctrl->epoch();
}
return;
}
m_failed_code = 0; m_failed_code = 0;
if (m_machine.empty()) { if (m_machine.empty()) {
Stop(_L("Please confirm if the printer is connected.")); Stop(_L("Please confirm if the printer is connected."));
@@ -438,15 +281,16 @@ void MediaPlayCtrl::Play()
} }
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::Play: " << m_lan_proto << m_remote_proto << m_disable_lan; BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::Play: " << m_lan_proto << m_remote_proto << m_disable_lan;
NetworkAgent *agent = wxGetApp().getAgent();
std::string agent_version = agent ? agent->get_version() : ""; std::string agent_version = agent ? agent->get_version() : "";
if (m_lan_proto > LiveviewLocal::LVL_Disable && (m_lan_mode || !m_remote_proto) && !m_disable_lan && !m_lan_ip.empty()) { if (m_lan_proto > MachineObject::LVL_Disable && (m_lan_mode || !m_remote_proto) && !m_disable_lan && !m_lan_ip.empty()) {
m_disable_lan = m_remote_proto && !m_lan_mode; // try remote next time m_disable_lan = m_remote_proto && !m_lan_mode; // try remote next time
std::string url; std::string url;
if (m_lan_proto == LiveviewLocal::LVL_Local) if (m_lan_proto == MachineObject::LVL_Local)
url = "bambu:///local/" + m_lan_ip + ".?port=6000&user=" + m_lan_user + "&passwd=" + m_lan_passwd; url = "bambu:///local/" + m_lan_ip + ".?port=6000&user=" + m_lan_user + "&passwd=" + m_lan_passwd;
else if (m_lan_proto == LiveviewLocal::LVL_Rtsps) else if (m_lan_proto == MachineObject::LVL_Rtsps)
url = "bambu:///rtsps___" + m_lan_user + ":" + m_lan_passwd + "@" + m_lan_ip + "/streaming/live/1?proto=rtsps"; url = "bambu:///rtsps___" + m_lan_user + ":" + m_lan_passwd + "@" + m_lan_ip + "/streaming/live/1?proto=rtsps";
else if (m_lan_proto == LiveviewLocal::LVL_Rtsp) else if (m_lan_proto == MachineObject::LVL_Rtsp)
url = "bambu:///rtsp___" + m_lan_user + ":" + m_lan_passwd + "@" + m_lan_ip + "/streaming/live/1?proto=rtsp"; url = "bambu:///rtsp___" + m_lan_user + ":" + m_lan_passwd + "@" + m_lan_ip + "/streaming/live/1?proto=rtsp";
url += "&device=" + m_machine; url += "&device=" + m_machine;
url += "&net_ver=" + agent_version; url += "&net_ver=" + agent_version;
@@ -468,8 +312,8 @@ void MediaPlayCtrl::Play()
// !m_lan_mode && !m_remote_proto && m_lan_proto == LVL_Disable (*) // !m_lan_mode && !m_remote_proto && m_lan_proto == LVL_Disable (*)
// !m_lan_mode && !m_remote_proto && m_lan_proto == LVL_None (x) // !m_lan_mode && !m_remote_proto && m_lan_proto == LVL_None (x)
if (m_lan_proto <= LiveviewLocal::LVL_Disable && (m_lan_mode || !m_remote_proto)) { if (m_lan_proto <= MachineObject::LVL_Disable && (m_lan_mode || !m_remote_proto)) {
Stop(m_lan_proto == LiveviewLocal::LVL_None Stop(m_lan_proto == MachineObject::LVL_None
? _L("A problem occurred. Please update the printer firmware and try again.") ? _L("A problem occurred. Please update the printer firmware and try again.")
: _L("LAN Only Liveview is off. Please turn on the liveview on printer screen.")); : _L("LAN Only Liveview is off. Please turn on the liveview on printer screen."));
return; return;
@@ -537,78 +381,15 @@ void MediaPlayCtrl::Play()
void start_ping_test(); void start_ping_test();
void MediaPlayCtrl::StopWebStream()
{
if (m_last_state == MEDIASTATE_IDLE)
return;
if (m_active_media_controller && m_active_media_controller == m_web_ctrl)
m_active_media_controller->Stop();
m_button_play->SetIcon("media_play");
m_last_state = MEDIASTATE_IDLE;
SetStatus(_L("Video Stopped."), false);
}
void MediaPlayCtrl::Stop(wxString const &msg, wxString const &msg2) void MediaPlayCtrl::Stop(wxString const &msg, wxString const &msg2)
{ {
const bool webrtc_active = m_last_mode == CameraStreamMode::webrtc &&
dynamic_cast<WebRtcMediaController *>(m_active_media_controller) != nullptr;
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::Stop: last_state=" << m_last_state
<< " webrtc_active=" << webrtc_active << " failed_code=" << m_failed_code
<< " msg='" << msg.ToUTF8().data() << "'";
if (webrtc_active) {
m_active_media_controller->Stop();
m_media_ctrl->EndExternalStream();
}
switch (m_last_mode) {
case CameraStreamMode::http:
case CameraStreamMode::https:
case CameraStreamMode::http_snapshot: {
const bool snapshot = m_last_mode == CameraStreamMode::http_snapshot;
if (m_last_state != MEDIASTATE_IDLE) {
if (snapshot) {
if (m_active_media_controller)
m_active_media_controller->Stop();
} else {
// http/https mode plays through the ffmpeg backend (m_media_ctrl), not
// the webview - tear its read thread down too, otherwise it keeps
// pulling and painting frames after the UI says "Video Stopped".
boost::unique_lock lock(m_mutex);
m_tasks.push_back({"<stop>", m_active_media_controller});
m_cond.notify_all();
}
m_button_play->SetIcon("media_play");
m_last_state = MEDIASTATE_IDLE;
if (!msg.IsEmpty())
SetStatus(msg);
else
SetStatus(_L("Video Stopped."), false);
// Keep retries bounded for an explicit or retry-driven playback attempt.
// m_failed_retry is cleared on success (onStateChanged) and on a deliberate
// machine switch (SetMachineObject); manual playback via TogglePlay resets it.
if (m_failed_code != 0) {
const bool auto_retry = wxGetApp().app_config->get("liveview", "auto_retry") != "false";
++m_failed_retry;
m_next_retry = auto_retry
? wxDateTime::Now() + wxTimeSpan::Seconds(std::min(5 * m_failed_retry, 30))
: wxDateTime::Now() + wxTimeSpan::Days(1); // "off": wait for a manual retry
}
} else if (!msg.IsEmpty()) {
SetStatus(msg, false);
}
return;
}
default:
break;
}
int last_state = m_last_state; int last_state = m_last_state;
if (m_last_state != MEDIASTATE_IDLE) { if (m_last_state != MEDIASTATE_IDLE) {
m_media_ctrl->InvalidateBestSize(); m_media_ctrl->InvalidateBestSize();
m_button_play->SetIcon("media_play"); m_button_play->SetIcon("media_play");
boost::unique_lock lock(m_mutex); boost::unique_lock lock(m_mutex);
if (!webrtc_active) m_tasks.push_back("<stop>");
m_tasks.push_back({"<stop>", m_active_media_controller});
m_cond.notify_all(); m_cond.notify_all();
if (!msg.IsEmpty()) if (!msg.IsEmpty())
SetStatus(msg); SetStatus(msg);
@@ -673,38 +454,15 @@ void MediaPlayCtrl::Stop(wxString const &msg, wxString const &msg2)
m_next_retry = wxDateTime::Now() + wxTimeSpan::Seconds(5 * m_failed_retry); m_next_retry = wxDateTime::Now() + wxTimeSpan::Seconds(5 * m_failed_retry);
} }
void MediaPlayCtrl::on_webrtc_status(WebRtcMediaController::Status status)
{
// Drop CallAfter-queued events from a superseded Play attempt.
if (status.epoch != m_webrtc_epoch)
return;
if (status.kind == WebRtcMediaController::Status::Connecting) {
m_last_state = MEDIASTATE_INITIALIZING;
SetStatus(_L("Initializing..."), false);
} else if (status.kind == WebRtcMediaController::Status::Playing) {
m_last_state = wxMEDIASTATE_PLAYING;
m_failed_code = 0;
m_failed_retry = 0;
SetStatus(_L("Playing..."), false);
} else if (status.kind == WebRtcMediaController::Status::Failed) {
m_failed_code = static_cast<int>(status.code) + 1;
Stop();
}
// Status::Stopped needs no action: a genuine failure arrives as Failed, and
// a stop we initiated is already handled by Stop() itself.
}
void MediaPlayCtrl::TogglePlay() void MediaPlayCtrl::TogglePlay()
{ {
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::TogglePlay"; BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::TogglePlay";
if (m_last_state != MEDIASTATE_IDLE) { if (m_last_state != MEDIASTATE_IDLE) {
m_next_retry = wxDateTime(); m_next_retry = wxDateTime();
m_web_user_stopped = true;
Stop(); Stop();
} else { } else {
m_failed_retry = 0; m_failed_retry = 0;
m_user_triggered = true; m_user_triggered = true;
m_web_user_stopped = false;
if (m_last_user_play + wxTimeSpan::Minutes(5) < wxDateTime::Now()) { if (m_last_user_play + wxTimeSpan::Minutes(5) < wxDateTime::Now()) {
m_last_failed_codes.clear(); m_last_failed_codes.clear();
m_last_user_play = wxDateTime::Now(); m_last_user_play = wxDateTime::Now();
@@ -770,13 +528,13 @@ void MediaPlayCtrl::ToggleStream()
wxGetApp().app_config->set("not_show_vcamera_stop_prev", "1"); wxGetApp().app_config->set("not_show_vcamera_stop_prev", "1");
if (res == wxID_CANCEL) return; if (res == wxID_CANCEL) return;
} }
if (m_lan_proto > LiveviewLocal::LVL_Disable && (m_lan_mode || !m_remote_proto) && !m_disable_lan && !m_lan_ip.empty()) { if (m_lan_proto > MachineObject::LVL_Disable && (m_lan_mode || !m_remote_proto) && !m_disable_lan && !m_lan_ip.empty()) {
std::string url; std::string url;
if (m_lan_proto == LiveviewLocal::LVL_Local) if (m_lan_proto == MachineObject::LVL_Local)
url = "bambu:///local/" + m_lan_ip + ".?port=6000&user=" + m_lan_user + "&passwd=" + m_lan_passwd; url = "bambu:///local/" + m_lan_ip + ".?port=6000&user=" + m_lan_user + "&passwd=" + m_lan_passwd;
else if (m_lan_proto == LiveviewLocal::LVL_Rtsps) else if (m_lan_proto == MachineObject::LVL_Rtsps)
url = "bambu:///rtsps___" + m_lan_user + ":" + m_lan_passwd + "@" + m_lan_ip + "/streaming/live/1?proto=rtsps"; url = "bambu:///rtsps___" + m_lan_user + ":" + m_lan_passwd + "@" + m_lan_ip + "/streaming/live/1?proto=rtsps";
else if (m_lan_proto == LiveviewLocal::LVL_Rtsp) else if (m_lan_proto == MachineObject::LVL_Rtsp)
url = "bambu:///rtsp___" + m_lan_user + ":" + m_lan_passwd + "@" + m_lan_ip + "/streaming/live/1?proto=rtsp"; url = "bambu:///rtsp___" + m_lan_user + ":" + m_lan_passwd + "@" + m_lan_ip + "/streaming/live/1?proto=rtsp";
url += "&device=" + into_u8(m_machine); url += "&device=" + into_u8(m_machine);
url += "&dev_ver=" + m_dev_ver; url += "&dev_ver=" + m_dev_ver;
@@ -836,9 +594,7 @@ void MediaPlayCtrl::jump_to_play()
void MediaPlayCtrl::onStateChanged(wxMediaEvent &event) void MediaPlayCtrl::onStateChanged(wxMediaEvent &event)
{ {
auto last_state = m_last_state; auto last_state = m_last_state;
if (m_active_media_controller != m_media_ctrl) auto state = m_media_ctrl->GetState();
return;
auto state = m_active_media_controller->GetState();
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::onStateChanged: " << state << ", last_state: " << last_state; BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::onStateChanged: " << state << ", last_state: " << last_state;
if ((int) state < 0) return; if ((int) state < 0) return;
{ {
@@ -850,14 +606,14 @@ void MediaPlayCtrl::onStateChanged(wxMediaEvent &event)
} }
if ((last_state == MEDIASTATE_IDLE || last_state == MEDIASTATE_INITIALIZING) && state == wxMEDIASTATE_STOPPED) { return; } if ((last_state == MEDIASTATE_IDLE || last_state == MEDIASTATE_INITIALIZING) && state == wxMEDIASTATE_STOPPED) { return; }
if ((last_state == wxMEDIASTATE_PAUSED || last_state == wxMEDIASTATE_PLAYING) && state == wxMEDIASTATE_STOPPED) { if ((last_state == wxMEDIASTATE_PAUSED || last_state == wxMEDIASTATE_PLAYING) && state == wxMEDIASTATE_STOPPED) {
m_failed_code = m_active_media_controller->GetLastError(); m_failed_code = m_media_ctrl->GetLastError();
Stop(); Stop();
return; return;
} }
if (last_state == MEDIASTATE_LOADING && (state == wxMEDIASTATE_STOPPED || state == wxMEDIASTATE_PAUSED)) { if (last_state == MEDIASTATE_LOADING && (state == wxMEDIASTATE_STOPPED || state == wxMEDIASTATE_PAUSED)) {
wxSize size = m_active_media_controller->GetVideoSize(); wxSize size = m_media_ctrl->GetVideoSize();
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::onStateChanged: size: " << size.x << "x" << size.y; BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::onStateChanged: size: " << size.x << "x" << size.y;
m_failed_code = m_active_media_controller->GetLastError(); m_failed_code = m_media_ctrl->GetLastError();
if (size.GetWidth() >= 320) { if (size.GetWidth() >= 320) {
m_last_state = state; m_last_state = state;
m_failed_code = 0; m_failed_code = 0;
@@ -867,7 +623,7 @@ void MediaPlayCtrl::onStateChanged(wxMediaEvent &event)
m_failed_retry = 0; m_failed_retry = 0;
m_disable_lan = false; m_disable_lan = false;
boost::unique_lock lock(m_mutex); boost::unique_lock lock(m_mutex);
m_tasks.push_back({"<play>", m_active_media_controller}); m_tasks.push_back("<play>");
m_cond.notify_all(); m_cond.notify_all();
} else if (event.GetId()) { } else if (event.GetId()) {
if (m_failed_code == 0) if (m_failed_code == 0)
@@ -910,8 +666,7 @@ void MediaPlayCtrl::load()
{ {
m_last_state = MEDIASTATE_LOADING; m_last_state = MEDIASTATE_LOADING;
SetStatus(_L("Loading...")); SetStatus(_L("Loading..."));
const auto mode = current_mode(); if (wxGetApp().app_config->get("internal_developer_mode") == "true") {
if (mode == CameraStreamMode::none && wxGetApp().app_config->get("internal_developer_mode") == "true") {
std::string file_h264 = data_dir() + "/video.h264"; std::string file_h264 = data_dir() + "/video.h264";
std::string file_info = data_dir() + "/video.info"; std::string file_info = data_dir() + "/video.info";
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl dump video to " << file_h264; BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl dump video to " << file_h264;
@@ -922,7 +677,7 @@ void MediaPlayCtrl::load()
m_url = m_url + "&dump_info=" + boost::lexical_cast<std::string>(dump_info_file); m_url = m_url + "&dump_info=" + boost::lexical_cast<std::string>(dump_info_file);
} }
boost::unique_lock lock(m_mutex); boost::unique_lock lock(m_mutex);
m_tasks.push_back({m_url, m_active_media_controller}); m_tasks.push_back(m_url);
m_cond.notify_all(); m_cond.notify_all();
} }
@@ -931,8 +686,9 @@ void MediaPlayCtrl::on_show_hide(wxShowEvent &evt)
evt.Skip(); evt.Skip();
if (m_isBeingDeleted) return; if (m_isBeingDeleted) return;
m_failed_retry = 0; m_failed_retry = 0;
if (!IsShownOnScreen()) if (m_next_retry.IsValid()) // Try open 2 seconds later, to avoid quick play/stop
Stop(); m_next_retry = wxDateTime::Now() + wxTimeSpan::Seconds(2);
IsShownOnScreen() ? Play() : Stop();
} }
void MediaPlayCtrl::media_proc() void MediaPlayCtrl::media_proc()
@@ -942,34 +698,30 @@ void MediaPlayCtrl::media_proc()
while (m_tasks.empty()) { while (m_tasks.empty()) {
m_cond.wait(lock); m_cond.wait(lock);
} }
MediaTask task = m_tasks.front(); wxString url = m_tasks.front();
if (m_tasks.size() >= 2 && !task.command.IsEmpty() && task.command[0] != '<' && if (m_tasks.size() >= 2 && !url.IsEmpty() && url[0] != '<' && m_tasks[1] == "<stop>") {
m_tasks[1].command == "<stop>" && task.controller == m_tasks[1].controller) { BOOST_LOG_TRIVIAL(trace) << "MediaPlayCtrl: busy skip url: " << url;
BOOST_LOG_TRIVIAL(trace) << "MediaPlayCtrl: busy skip url: " << task.command;
m_tasks.pop_front(); m_tasks.pop_front();
m_tasks.pop_front(); m_tasks.pop_front();
continue; continue;
} }
lock.unlock(); lock.unlock();
if (task.command == "<stop>") { if (url == "<stop>") {
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: start stop"; BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: start stop";
if (task.controller) m_media_ctrl->Stop();
task.controller->Stop();
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: end stop"; BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: end stop";
} }
else if (task.command == "<exit>") { else if (url == "<exit>") {
break; break;
} }
else if (task.command == "<play>") { else if (url == "<play>") {
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: start play"; BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: start play";
if (task.controller) m_media_ctrl->Play();
task.controller->Play();
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: end play"; BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: end play";
} }
else { else {
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: start load"; BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: start load";
if (task.controller) m_media_ctrl->Load(wxURI(url));
task.controller->Load(wxURI(task.command));
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: end load"; BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: end load";
} }
lock.lock(); lock.lock();
+1 -23
View File
@@ -9,9 +9,6 @@
#define MediaPlayCtrl_h #define MediaPlayCtrl_h
#include "wxMediaCtrl3.h" #include "wxMediaCtrl3.h"
#include "IMediaController.hpp"
#include "WebRtcMediaController.hpp"
#include "slic3r/Utils/IPrinterAgent.hpp"
#include <wx/panel.h> #include <wx/panel.h>
@@ -39,10 +36,6 @@ public:
void SetMachineObject(MachineObject * obj); void SetMachineObject(MachineObject * obj);
void SetWebMediaController(IMediaController *ctrl);
void StopWebStream();
bool IsStreaming() const; bool IsStreaming() const;
void ToggleStream(); void ToggleStream();
@@ -61,7 +54,6 @@ protected:
void TogglePlay(); void TogglePlay();
void SetStatus(wxString const &msg, bool hyperlink = true); void SetStatus(wxString const &msg, bool hyperlink = true);
void on_webrtc_status(WebRtcMediaController::Status status);
private: private:
void load(); void load();
@@ -74,9 +66,6 @@ private:
static bool get_stream_url(std::string *url = nullptr); static bool get_stream_url(std::string *url = nullptr);
CameraStreamMode current_mode() const;
void set_active_media_controller(CameraStreamMode mode);
private: private:
static inline const wxMediaState MEDIASTATE_IDLE = static_cast<wxMediaState>(3); static inline const wxMediaState MEDIASTATE_IDLE = static_cast<wxMediaState>(3);
static inline const wxMediaState MEDIASTATE_INITIALIZING = static_cast<wxMediaState>(4); static inline const wxMediaState MEDIASTATE_INITIALIZING = static_cast<wxMediaState>(4);
@@ -87,13 +76,6 @@ private:
std::shared_ptr<int> m_token = std::make_shared<int>(0); std::shared_ptr<int> m_token = std::make_shared<int>(0);
wxMediaCtrl3 * m_media_ctrl; wxMediaCtrl3 * m_media_ctrl;
IMediaController * m_active_media_controller = nullptr;
IMediaController * m_web_ctrl = nullptr;
std::unique_ptr<WebRtcMediaController> m_webrtc_ctrl;
CameraStreamMode m_last_mode = CameraStreamMode::none;
std::uint64_t m_webrtc_epoch = 0;
std::string m_agent_camera_url;
bool m_web_user_stopped = false;
wxMediaState m_last_state = MEDIASTATE_IDLE; wxMediaState m_last_state = MEDIASTATE_IDLE;
std::string m_machine; std::string m_machine;
int m_lan_proto = 0; int m_lan_proto = 0;
@@ -109,11 +91,7 @@ private:
bool m_disable_lan = false; bool m_disable_lan = false;
wxString m_url; wxString m_url;
struct MediaTask { std::deque<wxString> m_tasks;
wxString command;
IMediaController *controller = nullptr;
};
std::deque<MediaTask> m_tasks;
boost::mutex m_mutex; boost::mutex m_mutex;
boost::condition_variable m_cond; boost::condition_variable m_cond;
boost::thread m_thread; boost::thread m_thread;
+1 -11
View File
@@ -34,8 +34,6 @@
#include "DeviceCore/DevManager.h" #include "DeviceCore/DevManager.h"
#include <boost/log/trivial.hpp>
namespace Slic3r { namespace Slic3r {
namespace GUI { namespace GUI {
@@ -267,7 +265,6 @@ void MonitorPanel::msw_rescale()
void MonitorPanel::select_machine(std::string machine_sn) void MonitorPanel::select_machine(std::string machine_sn)
{ {
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: MonitorPanel::select_machine queueing machine_sn=" << machine_sn;
wxCommandEvent *event = new wxCommandEvent(wxEVT_COMMAND_CHOICE_SELECTED); wxCommandEvent *event = new wxCommandEvent(wxEVT_COMMAND_CHOICE_SELECTED);
event->SetString(machine_sn); event->SetString(machine_sn);
wxQueueEvent(this, event); wxQueueEvent(this, event);
@@ -285,20 +282,13 @@ void MonitorPanel::on_timer(wxTimerEvent& event)
void MonitorPanel::on_select_printer(wxCommandEvent& event) void MonitorPanel::on_select_printer(wxCommandEvent& event)
{ {
Slic3r::DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager(); Slic3r::DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
const std::string requested_dev_id = event.GetString().ToStdString();
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: MonitorPanel::on_select_printer requested_dev_id="
<< requested_dev_id << " device_manager=" << (dev ? "set" : "null");
if (!dev) return; if (!dev) return;
if ( dev->get_selected_machine() && (dev->get_selected_machine()->get_dev_id() != event.GetString().ToStdString()) && m_hms_panel) { if ( dev->get_selected_machine() && (dev->get_selected_machine()->get_dev_id() != event.GetString().ToStdString()) && m_hms_panel) {
m_hms_panel->clear_hms_tag(); m_hms_panel->clear_hms_tag();
} }
const bool selected = dev->set_selected_machine(requested_dev_id); if (!dev->set_selected_machine(event.GetString().ToStdString()))
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: MonitorPanel::on_select_printer set_selected_machine result="
<< selected << " selected_dev_id="
<< (dev->get_selected_machine() ? dev->get_selected_machine()->get_dev_id() : "<null>");
if (!selected)
return; return;
set_default(); set_default();
+22 -1
View File
@@ -51,7 +51,7 @@ void DeviceItem::sync_state()
state_printable = 6; state_printable = 6;
} }
if (wxGetApp().is_blocking_printing(obj_)) { if (is_blocking_printing(obj_)) {
state_printable = 5; state_printable = 5;
} }
@@ -104,6 +104,27 @@ void DeviceItem::unselected()
} }
} }
bool DeviceItem::is_blocking_printing(MachineObject* obj_)
{
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
if (!dev) return true;
auto target_model = obj_->printer_type;
std::string source_model = "";
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
source_model = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle);
if (source_model != target_model) {
std::vector<std::string> compatible_machine = obj_->get_compatible_machine();
vector<std::string>::iterator it = find(compatible_machine.begin(), compatible_machine.end(), source_model);
if (it == compatible_machine.end()) {
return true;
}
}
return false;
}
void DeviceItem::update_item(const DeviceItem* item) void DeviceItem::update_item(const DeviceItem* item)
{ {
// Except for the selected status, everything else is updated // Except for the selected status, everything else is updated
+1
View File
@@ -59,6 +59,7 @@ public:
void selected(); void selected();
void unselected(); void unselected();
bool is_blocking_printing(MachineObject* obj_);
void update_item(const DeviceItem* item); void update_item(const DeviceItem* item);
}; };
+24 -89
View File
@@ -2027,14 +2027,12 @@ bool Sidebar::priv::sync_extruder_list(bool &only_external_material, bool is_man
std::string machine_print_name = obj->get_show_printer_type(); std::string machine_print_name = obj->get_show_printer_type();
PresetBundle *preset_bundle = wxGetApp().preset_bundle; PresetBundle *preset_bundle = wxGetApp().preset_bundle;
std::string target_model_id = preset_bundle->printers.get_selected_preset().get_printer_type(preset_bundle); std::string target_model_id = preset_bundle->printers.get_selected_preset().get_printer_type(preset_bundle);
const bool optional_printer_model = DevPrinterConfigUtil::is_optional_printer_model_id(obj->printer_type); Preset* machine_preset = get_printer_preset(obj);
const bool optional_target_model = DevPrinterConfigUtil::is_optional_printer_model_id(target_model_id); if (!machine_preset) {
Preset* machine_preset = optional_printer_model ? nullptr : get_printer_preset(obj);
if (!optional_printer_model && !optional_target_model && !machine_preset) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << "check error: machine_preset empty"; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << "check error: machine_preset empty";
return false; return false;
} }
if (!optional_printer_model && !optional_target_model && machine_print_name != target_model_id) { if (machine_print_name != target_model_id) {
MessageDialog dlg(this->plater, _L("The currently selected machine preset is inconsistent with the connected printer type.\n" MessageDialog dlg(this->plater, _L("The currently selected machine preset is inconsistent with the connected printer type.\n"
"Are you sure to continue syncing?"), _L("Sync printer information"), wxICON_WARNING | wxYES | wxNO); "Are you sure to continue syncing?"), _L("Sync printer information"), wxICON_WARNING | wxYES | wxNO);
if (dlg.ShowModal() == wxID_NO) { if (dlg.ShowModal() == wxID_NO) {
@@ -2226,11 +2224,6 @@ void Sidebar::priv::update_sync_status(const MachineObject *obj)
return; return;
} }
if (DevPrinterConfigUtil::is_optional_printer_model_id(obj->printer_type)) {
clear_all_sync_status();
return;
}
bool printer_synced = false; bool printer_synced = false;
// 1. update printer status // 1. update printer status
const Preset &cur_preset = wxGetApp().preset_bundle->printers.get_edited_preset(); const Preset &cur_preset = wxGetApp().preset_bundle->printers.get_edited_preset();
@@ -5829,30 +5822,11 @@ void Sidebar::load_ams_list(MachineObject* obj)
filament_ams_list = build_filament_ams_list(obj); filament_ams_list = build_filament_ams_list(obj);
} }
bool device_change = false;
const std::string& device = obj ? obj->get_dev_id() : ""; const std::string& device = obj ? obj->get_dev_id() : "";
const bool same_device = p->ams_list_device == device; if (p->ams_list_device != device) {
// Keep sync metadata out of the device payload, but preserve it across a
// subscription refresh when the physical filament in a slot is unchanged.
// Otherwise the refreshed configs differ only by the missing
// filament_changed key, causing combo boxes to rebuild and lose their
// transient post-sync badges.
auto &previous_filament_ams_list = wxGetApp().preset_bundle->filament_ams_list;
for (auto &entry : filament_ams_list) {
auto previous = previous_filament_ams_list.find(entry.first);
const auto *previous_changed = previous == previous_filament_ams_list.end() ? nullptr :
dynamic_cast<const ConfigOptionBool *>(previous->second.option("filament_changed"));
if (!same_device || previous_changed == nullptr ||
previous->second.opt_string("filament_id", 0u) != entry.second.opt_string("filament_id", 0u)) {
continue;
}
entry.second.set_key_value("filament_changed",
new ConfigOptionBool{previous_changed->value});
}
bool device_change = !same_device;
if (device_change) {
p->ams_list_device = device; p->ams_list_device = device;
device_change = true;
} }
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1% items") % filament_ams_list.size(); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1% items") % filament_ams_list.size();
if (wxGetApp().preset_bundle->filament_ams_list == filament_ams_list && !device_change) if (wxGetApp().preset_bundle->filament_ams_list == filament_ams_list && !device_change)
@@ -5862,27 +5836,9 @@ void Sidebar::load_ams_list(MachineObject* obj)
wxGetApp().preset_bundle->filament_ams_list = filament_ams_list; wxGetApp().preset_bundle->filament_ams_list = filament_ams_list;
for (auto c : p->combos_filament){ for (auto c : p->combos_filament){
c->set_sync_badge(false);
c->update(); c->update();
} if (device_change) {
c->ShowBadge(false);//change printer,then clear badge
if (!device_change) {
size_t combo_index = 0;
for (const auto &entry : filament_ams_list) {
const auto &tray = entry.second;
const bool has_filament = !tray.opt_string("filament_id", 0u).empty();
const bool is_placeholder = tray.has("filament_slot_placeholder") &&
tray.opt_bool("filament_slot_placeholder", 0u);
if (!has_filament && !is_placeholder) {
continue;
}
if (combo_index >= p->combos_filament.size()) {
break;
}
const auto *filament_changed = dynamic_cast<const ConfigOptionBool *>(tray.option("filament_changed"));
p->combos_filament[combo_index]->set_sync_badge(
has_filament && !is_placeholder && filament_changed != nullptr && filament_changed->value);
++combo_index;
} }
} }
@@ -6056,32 +6012,18 @@ void Sidebar::sync_ams_list(bool is_from_big_sync_btn)
auto tip = sync_color_only ? _L("Only filament color information has been synchronized from printer.") : auto tip = sync_color_only ? _L("Only filament color information has been synchronized from printer.") :
_L("Filament type and color information have been synchronized, but slot information is not included."); _L("Filament type and color information have been synchronized, but slot information is not included.");
c->SetToolTip(tip); c->SetToolTip(tip);
c->set_sync_badge(true); c->ShowBadge(true);
}; };
{ // badge ams filament { // badge ams filament
clear_combos_filament_badge(); clear_combos_filament_badge();
if (sync_result.direct_sync) { if (sync_result.direct_sync) {
// A placeholder contributes a preserved project filament to the // Orca: PresetBundle::sync_ams_list rebuilds combos_filament
// overwrite result, but it is not AMS-sourced and must not get a // 1:1 from the AMS trays that produce a combo (loaded trays + placeholders; non-placeholder
// sync badge. Non-placeholder empty trays are omitted entirely. // empty trays are skipped), so every resulting combo is AMS-sourced and gets a badge. The
size_t combo_index = 0; // previous per-tray index walked the full filament_ams_list (including the skipped empties),
for (const auto &entry : wxGetApp().preset_bundle->filament_ams_list) { // so an empty slot before a loaded one dropped the badge for the trailing filaments.
const auto &tray = entry.second; for (auto &c : p->combos_filament) {
const bool has_filament = !tray.opt_string("filament_id", 0u).empty(); badge_combox_filament(c);
const bool is_placeholder = tray.has("filament_slot_placeholder") &&
tray.opt_bool("filament_slot_placeholder", 0u);
if (!has_filament && !is_placeholder) {
continue;
}
if (combo_index >= p->combos_filament.size()) {
break;
}
if (is_placeholder) {
p->combos_filament[combo_index]->set_sync_badge(false);
} else {
badge_combox_filament(p->combos_filament[combo_index]);
}
++combo_index;
} }
} }
} }
@@ -6349,11 +6291,6 @@ template<typename T> void setup_dialog_position(T& info)
void Sidebar::pop_sync_nozzle_and_ams_dialog() { void Sidebar::pop_sync_nozzle_and_ams_dialog() {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " begin pop_sync_nozzle_and_ams_dialog"; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " begin pop_sync_nozzle_and_ams_dialog";
auto agent = wxGetApp().getAgent();
if (!agent || agent->get_filament_sync_mode() == FilamentSyncMode::none) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " filament synchronization is not supported; skipping dialog";
return;
}
wxTheApp->CallAfter([this]() { wxTheApp->CallAfter([this]() {
SyncNozzleAndAmsDialog::InputInfo temp_na_info; SyncNozzleAndAmsDialog::InputInfo temp_na_info;
wxPoint big_btn_pt; wxPoint big_btn_pt;
@@ -6485,14 +6422,17 @@ void Sidebar::clear_combos_filament_badge()
{ {
auto &combos_filament = p->combos_filament; auto &combos_filament = p->combos_filament;
for (auto &c : combos_filament) { // clear flag for (auto &c : combos_filament) { // clear flag
c->set_sync_badge(false); c->ShowBadge(false);
} }
} }
void Sidebar::udpate_combos_filament_badge() { void Sidebar::udpate_combos_filament_badge() {
auto &combos_filament = p->combos_filament; auto &combos_filament = p->combos_filament;
for (auto &c : combos_filament) { for (auto &c : combos_filament) {
c->update_badge_according_flag(); auto selection = c->GetSelection();
auto select_flag = c->GetFlag(selection);
auto ok = select_flag == (int) PresetComboBox::FilamentAMSType::FROM_AMS;
c->ShowBadge(ok);
} }
} }
@@ -12477,7 +12417,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
sidebar->auto_calc_flushing_volumes(idx); sidebar->auto_calc_flushing_volumes(idx);
} }
auto select_flag = combo->GetFlag(selection); auto select_flag = combo->GetFlag(selection);
combo->set_sync_badge(select_flag == (int)PresetComboBox::FilamentAMSType::FROM_AMS); combo->ShowBadge(select_flag == (int)PresetComboBox::FilamentAMSType::FROM_AMS);
q->on_filament_change(idx); q->on_filament_change(idx);
} }
bool select_preset = !combo->selection_is_changed_according_to_physical_printers(); bool select_preset = !combo->selection_is_changed_according_to_physical_printers();
@@ -21172,14 +21112,9 @@ bool Plater::is_same_printer_for_connected_and_selected(bool popup_warning)
} }
if (!check_printer_initialized(obj, true, popup_warning)) if (!check_printer_initialized(obj, true, popup_warning))
return false; return false;
const std::string machine_model = obj->printer_type; Preset * machine_preset = get_printer_preset(obj);
PresetBundle *preset_bundle = wxGetApp().preset_bundle; if (!machine_preset)
const std::string selected_model = preset_bundle ? preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle) : std::string();
if (!DevPrinterConfigUtil::is_optional_printer_model_id(machine_model) &&
!DevPrinterConfigUtil::is_optional_printer_model_id(selected_model) &&
!get_printer_preset(obj)) {
return false; return false;
}
if (wxGetApp().is_blocking_printing()) { if (wxGetApp().is_blocking_printing()) {
if (popup_warning) { if (popup_warning) {
-18
View File
@@ -11,7 +11,6 @@ namespace Slic3r
// IMPORTANT: ordinal order is the Plugins dialog Status sort priority. // IMPORTANT: ordinal order is the Plugins dialog Status sort priority.
Activated, Activated,
Error, Error,
RuntimeError,
Inactive, Inactive,
Loading Loading
}; };
@@ -22,28 +21,11 @@ namespace Slic3r
{ {
case PluginStatus::Activated: return "Activated"; case PluginStatus::Activated: return "Activated";
case PluginStatus::Error: return "Error"; case PluginStatus::Error: return "Error";
case PluginStatus::RuntimeError: return "RuntimeError";
case PluginStatus::Inactive: return "Inactive"; case PluginStatus::Inactive: return "Inactive";
case PluginStatus::Loading: return "Loading"; case PluginStatus::Loading: return "Loading";
} }
return "Inactive"; return "Inactive";
} }
// why: a plugin whose module is live but whose catalog carries an error is a
// RUNTIME fault (e.g. a capability rejected at register time) - it stays
// loaded/checked and is only flagged, distinct from a load-time Error where
// the module never came up. Loading wins over both so an in-flight reload
// never flashes an error.
inline PluginStatus resolve_plugin_status(bool loading, bool has_error, bool is_loaded)
{
if (loading)
return PluginStatus::Loading;
if (has_error)
return is_loaded ? PluginStatus::RuntimeError : PluginStatus::Error;
if (is_loaded)
return PluginStatus::Activated;
return PluginStatus::Inactive;
}
} }
} // namespace Slic3r::GUI } // namespace Slic3r::GUI
+8 -5
View File
@@ -193,7 +193,6 @@ nlohmann::json build_plugin_payload_item(const PluginDialogItem& dialog_item)
payload_item["label"] = dialog_item.display_name; payload_item["label"] = dialog_item.display_name;
payload_item["source"] = to_string(dialog_item.source); payload_item["source"] = to_string(dialog_item.source);
payload_item["status"] = to_string(dialog_item.status); payload_item["status"] = to_string(dialog_item.status);
payload_item["is_loaded"] = dialog_item.is_loaded;
payload_item["error"] = dialog_item.error_text; payload_item["error"] = dialog_item.error_text;
payload_item["update_status"] = to_string(dialog_item.update_status); payload_item["update_status"] = to_string(dialog_item.update_status);
payload_item["unauthorized"] = dialog_item.unauthorized; payload_item["unauthorized"] = dialog_item.unauthorized;
@@ -338,7 +337,14 @@ PluginDialogItem build_plugin_dialog_item(const PluginDescriptor& descriptor)
item.sharing_token = descriptor.sharing_token; item.sharing_token = descriptor.sharing_token;
item.thumbnail_url = descriptor.thumbnail_url; item.thumbnail_url = descriptor.thumbnail_url;
item.status = resolve_plugin_status(item.loading, item.has_error, item.is_loaded); if (item.loading)
item.status = PluginStatus::Loading;
else if (item.has_error)
item.status = PluginStatus::Error;
else if (item.is_loaded)
item.status = PluginStatus::Activated;
else
item.status = PluginStatus::Inactive;
item.available_actions = evaluate_action_policy(item); item.available_actions = evaluate_action_policy(item);
const bool has_enabled_script = std::any_of(item.capabilities.begin(), item.capabilities.end(), const bool has_enabled_script = std::any_of(item.capabilities.begin(), item.capabilities.end(),
@@ -784,9 +790,6 @@ void PluginsDialog::toggle_plugin(const std::string& plugin_key, bool enabled)
} }
BOOST_LOG_TRIVIAL(info) << "Plugin unloaded from Plugins dialog: " << plugin_key; BOOST_LOG_TRIVIAL(info) << "Plugin unloaded from Plugins dialog: " << plugin_key;
// A user-disabled plugin has no meaningful error state.
if (!manager.clear_plugin_error(plugin_key))
BOOST_LOG_TRIVIAL(warning) << "Failed to clear plugin error for " << plugin_key << " (failed to find)";
// A prior activation of this plugin is moot now; drop it so no stale "Activated" arrives later. // A prior activation of this plugin is moot now; drop it so no stale "Activated" arrives later.
if (m_activating_plugin_key == plugin_key) if (m_activating_plugin_key == plugin_key)
m_activating_plugin_key.clear(); m_activating_plugin_key.clear();
+1 -2
View File
@@ -63,7 +63,6 @@ std::string PrePrintChecker::get_print_status_info(PrintDialogStatus status)
case PrintStatusRackReading: return "PrintStatusRackReading"; case PrintStatusRackReading: return "PrintStatusRackReading";
case PrintStatusRackNozzleNumUnmeetWarning: return "PrintStatusRackNozzleNumUnmeetWarning"; case PrintStatusRackNozzleNumUnmeetWarning: return "PrintStatusRackNozzleNumUnmeetWarning";
case PrintStatusHasUnreliableNozzleWarning: return "PrintStatusHasUnreliableNozzleWarning"; case PrintStatusHasUnreliableNozzleWarning: return "PrintStatusHasUnreliableNozzleWarning";
case PrintStatusOptionalPrinterModel: return "PrintStatusOptionalPrinterModel";
case PrintStatusWarningExtFilamentNotMatch: return "PrintStatusWarningExtFilamentNotMatch"; case PrintStatusWarningExtFilamentNotMatch: return "PrintStatusWarningExtFilamentNotMatch";
case PrintStatusFilamentWarningNozzleHRC: return "PrintStatusFilamentWarningNozzleHRC"; case PrintStatusFilamentWarningNozzleHRC: return "PrintStatusFilamentWarningNozzleHRC";
case PrintStatusTPUUnsupportCaliOn: return "PrintStatusTPUUnsupportCaliOn"; case PrintStatusTPUUnsupportCaliOn: return "PrintStatusTPUUnsupportCaliOn";
@@ -105,7 +104,6 @@ wxString PrePrintChecker::get_pre_state_msg(PrintDialogStatus status)
case PrintStatusNeedConsistencyUpgrading: return _L("Cannot send the print job to a printer whose firmware must be updated."); case PrintStatusNeedConsistencyUpgrading: return _L("Cannot send the print job to a printer whose firmware must be updated.");
case PrintStatusBlankPlate: return _L("Cannot send a print job for an empty plate."); case PrintStatusBlankPlate: return _L("Cannot send a print job for an empty plate.");
case PrintStatusTimelapseNoSdcard: return _L("Storage needs to be inserted to record timelapse."); case PrintStatusTimelapseNoSdcard: return _L("Storage needs to be inserted to record timelapse.");
case PrintStatusOptionalPrinterModel: return _L("The selected printer model could not be identified, so compatibility with the print file configuration cannot be verified. Please verify the printer preset before sending.");
case PrintStatusMixAmsAndVtSlotWarning: return _L("You have selected both external and AMS filaments for an extruder. You will need to manually switch the external filament during printing."); case PrintStatusMixAmsAndVtSlotWarning: return _L("You have selected both external and AMS filaments for an extruder. You will need to manually switch the external filament during printing.");
case PrintStatusTPUUnsupportAutoCali: return _L("TPU 90A/TPU 85A is too soft and does not support automatic Flow Dynamics calibration."); case PrintStatusTPUUnsupportAutoCali: return _L("TPU 90A/TPU 85A is too soft and does not support automatic Flow Dynamics calibration.");
case PrintStatusWarningKvalueNotUsed: return _L("Set dynamic flow calibration to 'OFF' to enable custom dynamic flow value."); case PrintStatusWarningKvalueNotUsed: return _L("Set dynamic flow calibration to 'OFF' to enable custom dynamic flow value.");
@@ -381,3 +379,4 @@ bool PrinterMsgPanel::UpdateInfos(const std::vector<prePrintInfo>& infos)
} }
}; };
+1 -2
View File
@@ -112,7 +112,6 @@ enum PrintDialogStatus : unsigned int {
// Orca: a nozzle diameter that differs from the one the printer remembers is a warning, // Orca: a nozzle diameter that differs from the one the printer remembers is a warning,
// not an error, so non-standard nozzles can still be printed with. // not an error, so non-standard nozzles can still be printed with.
PrintStatusNozzleDiameterMismatch, PrintStatusNozzleDiameterMismatch,
PrintStatusOptionalPrinterModel,
PrintStatusPrinterWarningEnd, PrintStatusPrinterWarningEnd,
// Warnings for filament // Warnings for filament
@@ -171,7 +170,7 @@ public:
void add_with_checkbox(PrintDialogStatus state, wxString msg, wxString checkbox_label, bool checked, std::function<void(bool)> checkbox_callback); void add_with_checkbox(PrintDialogStatus state, wxString msg, wxString checkbox_label, bool checked, std::function<void(bool)> checkbox_callback);
static ::std::string get_print_status_info(PrintDialogStatus status); static ::std::string get_print_status_info(PrintDialogStatus status);
static wxString get_pre_state_msg(PrintDialogStatus status); wxString get_pre_state_msg(PrintDialogStatus status);
static bool is_error(PrintDialogStatus status) { return (PrintStatusErrorBegin < status) && (PrintStatusErrorEnd > status); }; static bool is_error(PrintDialogStatus status) { return (PrintStatusErrorBegin < status) && (PrintStatusErrorEnd > status); };
static bool is_error_printer(PrintDialogStatus status) { return (PrintStatusPrinterErrorBegin < status) && (PrintStatusPrinterErrorEnd > status); }; static bool is_error_printer(PrintDialogStatus status) { return (PrintStatusPrinterErrorBegin < status) && (PrintStatusPrinterErrorEnd > status); };
static bool is_error_filament(PrintDialogStatus status) { return (PrintStatusFilamentErrorBegin < status) && (PrintStatusFilamentErrorEnd > status); }; static bool is_error_filament(PrintDialogStatus status) { return (PrintStatusFilamentErrorBegin < status) && (PrintStatusFilamentErrorEnd > status); };
+3 -11
View File
@@ -361,8 +361,7 @@ wxString PresetComboBox::get_preset_item_name(unsigned int index)
return GetString(index); return GetString(index);
} }
std::map<std::string, MachineObject *> machine_list = std::map<std::string, MachineObject *> machine_list = dev->get_my_machine_list();
dev->get_my_machine_list(dev->get_current_printer_agent_id());
if (machine_list.empty()) { if (machine_list.empty()) {
assert(false); assert(false);
m_selected_dev_id.clear(); m_selected_dev_id.clear();
@@ -480,8 +479,7 @@ void PresetComboBox::add_connected_printers(std::string selected, bool alias_nam
if (!dev) if (!dev)
return; return;
std::map<std::string, MachineObject *> machine_list = std::map<std::string, MachineObject *> machine_list = dev->get_my_machine_list();
dev->get_my_machine_list(dev->get_current_printer_agent_id());
if (machine_list.empty()) if (machine_list.empty())
return; return;
@@ -1001,13 +999,7 @@ void PlaterPresetComboBox::update_badge_according_flag() {
auto selection = GetSelection(); auto selection = GetSelection();
auto select_flag = GetFlag(selection); auto select_flag = GetFlag(selection);
auto ok = select_flag == (int) PresetComboBox::FilamentAMSType::FROM_AMS; auto ok = select_flag == (int) PresetComboBox::FilamentAMSType::FROM_AMS;
ShowBadge(m_sync_badge || ok); ShowBadge(ok);
}
void PlaterPresetComboBox::set_sync_badge(bool show)
{
m_sync_badge = show;
ShowBadge(show);
} }
bool PlaterPresetComboBox::switch_to_tab() bool PlaterPresetComboBox::switch_to_tab()
-2
View File
@@ -205,7 +205,6 @@ public:
void msw_rescale() override; void msw_rescale() override;
void OnSelect(wxCommandEvent& evt) override; void OnSelect(wxCommandEvent& evt) override;
void update_badge_according_flag(); void update_badge_according_flag();
void set_sync_badge(bool show);
FilamentColor get_cur_color_info(); FilamentColor get_cur_color_info();
void show_default_color_picker(); void show_default_color_picker();
@@ -215,7 +214,6 @@ public:
private: private:
// BBS // BBS
wxColor m_color; wxColor m_color;
bool m_sync_badge{false};
}; };
+16 -84
View File
@@ -20,7 +20,6 @@
#include <wx/progdlg.h> #include <wx/progdlg.h>
#include <wx/clipbrd.h> #include <wx/clipbrd.h>
#include <wx/dcgraph.h> #include <wx/dcgraph.h>
#include <wx/filedlg.h>
#include <miniz.h> #include <miniz.h>
#include <algorithm> #include <algorithm>
#include "Plater.hpp" #include "Plater.hpp"
@@ -30,7 +29,6 @@
#include "DeviceCore/DevManager.h" #include "DeviceCore/DevManager.h"
#include "DeviceCore/DevStorage.h" #include "DeviceCore/DevStorage.h"
#include "md4c/src/md4c-html.h" #include "md4c/src/md4c-html.h"
#include "../Utils/Http.hpp"
namespace Slic3r { namespace GUI { namespace Slic3r { namespace GUI {
@@ -1463,45 +1461,6 @@ InputIpAddressDialog::InputIpAddressDialog(wxWindow *parent)
m_input_top_sizer->Add(0, 0, 0, wxTOP, FromDIP(4)); m_input_top_sizer->Add(0, 0, 0, wxTOP, FromDIP(4));
m_input_top_sizer->Add(m_input_area, 0, wxRIGHT | wxEXPAND, FromDIP(18)); m_input_top_sizer->Add(m_input_area, 0, wxRIGHT | wxEXPAND, FromDIP(18));
m_tips_cafile = new Label(ip_input_top_panel, _L("HTTPS CA File"));
m_input_cafile = new wxTextCtrl(ip_input_top_panel, wxID_ANY);
m_input_cafile->SetMinSize(wxSize(FromDIP(260), FromDIP(28)));
m_input_cafile->SetMaxSize(wxSize(FromDIP(260), FromDIP(28)));
m_button_cafile = new Button(ip_input_top_panel, _L("Browse") + " " + dots);
m_button_cafile->SetStyle(ButtonStyle::Regular, ButtonType::Parameter);
m_button_cafile->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
static const auto filemasks = _L("Certificate files (*.crt, *.pem)|*.crt;*.pem|All files|*.*");
wxFileDialog openFileDialog(this, _L("Open CA certificate file"), "", "", filemasks,
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (openFileDialog.ShowModal() != wxID_CANCEL)
m_input_cafile->SetValue(openFileDialog.GetPath());
});
auto cafile_input_sizer = new wxBoxSizer(wxHORIZONTAL);
cafile_input_sizer->Add(m_input_cafile, 1, wxALIGN_CENTER_VERTICAL);
cafile_input_sizer->Add(m_button_cafile, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(10));
m_cafile_hint = new Label(ip_input_top_panel, _L("HTTPS CA file is optional. It is only needed if you use HTTPS with a self-signed certificate."));
m_cafile_hint->Wrap(FromDIP(352));
m_input_top_sizer->Add(m_tips_cafile, 0, wxTOP | wxEXPAND, FromDIP(10));
m_input_top_sizer->Add(cafile_input_sizer, 0, wxTOP | wxEXPAND, FromDIP(4));
m_input_top_sizer->Add(m_cafile_hint, 0, wxTOP | wxEXPAND, FromDIP(4));
if (!Http::ca_file_supported()) {
m_input_cafile->Disable();
m_button_cafile->Disable();
m_cafile_hint->SetLabel(_L("This system uses HTTPS certificates from the system Certificate Store or Keychain. To use a custom CA file, import it there."));
m_cafile_hint->Wrap(FromDIP(352));
}
if (wxGetApp().preset_bundle) {
const auto& config = wxGetApp().preset_bundle->printers.get_edited_preset().config;
if (config.has("printhost_cafile"))
m_input_cafile->SetValue(from_u8(config.opt_string("printhost_cafile")));
}
ip_input_top_panel->SetSizer(m_input_top_sizer); ip_input_top_panel->SetSizer(m_input_top_sizer);
ip_input_top_panel->Layout(); ip_input_top_panel->Layout();
ip_input_top_panel->Fit(); ip_input_top_panel->Fit();
@@ -1777,8 +1736,7 @@ void InputIpAddressDialog::set_machine_obj(MachineObject* obj)
auto str_ip = m_input_ip->GetTextCtrl()->GetValue(); auto str_ip = m_input_ip->GetTextCtrl()->GetValue();
auto str_access_code = m_input_access_code->GetTextCtrl()->GetValue(); auto str_access_code = m_input_access_code->GetTextCtrl()->GetValue();
// ORCA enabling / disabling buttons with conditions enough to change its style // ORCA enabling / disabling buttons with conditions enough to change its style
m_button_ok->Enable(isValidEndpoint(str_ip.ToStdString()) && m_button_ok->Enable(isIp(str_ip.ToStdString()) && str_access_code.Length() == 8);
(str_access_code.IsEmpty() || str_access_code.Length() >= 8));
Layout(); Layout();
Fit(); Fit();
@@ -1815,29 +1773,19 @@ void InputIpAddressDialog::update_test_msg(wxString msg,bool connected)
Fit(); Fit();
} }
bool InputIpAddressDialog::isValidEndpoint(std::string endpoint) bool InputIpAddressDialog::isIp(std::string ipstr)
{ {
if (endpoint.empty() || std::any_of(endpoint.begin(), endpoint.end(), [](unsigned char c) { istringstream ipstream(ipstr);
return std::isspace(c) != 0; int num[4];
})) char point[3];
return false; string end;
ipstream >> num[0] >> point[0] >> num[1] >> point[1] >> num[2] >> point[2] >> num[3] >> end;
const bool has_http_scheme = endpoint.rfind("http://", 0) == 0; for (int i = 0; i < 3; ++i) {
const bool has_https_scheme = endpoint.rfind("https://", 0) == 0; if (num[i] < 0 || num[i]>255) return false;
const auto scheme_pos = endpoint.find("://"); if (point[i] != '.') return false;
if (scheme_pos != std::string::npos && !has_http_scheme && !has_https_scheme) }
return false; if (num[3] < 0 || num[3]>255) return false;
if (!end.empty()) return false;
std::string port;
const std::string host = Http::get_host_from_url(endpoint, &port);
if (host.empty())
return false;
// get_host_from_url returns its input when libcurl cannot parse it. For a
// URL with a scheme, that means a failed parse still needs to be rejected.
if (scheme_pos != std::string::npos && host == endpoint)
return false;
return true; return true;
} }
@@ -1853,8 +1801,6 @@ void InputIpAddressDialog::on_ok(wxMouseEvent& evt)
m_trouble_shoot->Hide(); m_trouble_shoot->Hide();
std::string str_ip = m_input_ip->GetTextCtrl()->GetValue().ToStdString(); std::string str_ip = m_input_ip->GetTextCtrl()->GetValue().ToStdString();
std::string str_access_code = m_input_access_code->GetTextCtrl()->GetValue().ToStdString(); std::string str_access_code = m_input_access_code->GetTextCtrl()->GetValue().ToStdString();
if (str_access_code.empty())
str_access_code = "88888888";
std::string str_name = m_input_printer_name->GetTextCtrl()->GetValue().Strip(wxString::both).ToStdString(); std::string str_name = m_input_printer_name->GetTextCtrl()->GetValue().Strip(wxString::both).ToStdString();
// Serial number should not contain lower case letters, and bambu_network plugin crashes // Serial number should not contain lower case letters, and bambu_network plugin crashes
// if user entered the wrong serial number, so we call `Upper()` here. // if user entered the wrong serial number, so we call `Upper()` here.
@@ -1874,17 +1820,6 @@ void InputIpAddressDialog::on_ok(wxMouseEvent& evt)
Layout(); Layout();
Fit(); Fit();
if (wxGetApp().preset_bundle) {
auto& config = wxGetApp().preset_bundle->printers.get_edited_preset().config;
std::string port;
Http::get_host_from_url(str_ip, &port);
config.opt_string("print_host") = str_ip;
if (!port.empty())
config.opt_string("printhost_port") = port;
if (Http::ca_file_supported())
config.opt_string("printhost_cafile") = m_input_cafile->GetValue().ToStdString();
}
token_.reset(this, nop_deleter); token_.reset(this, nop_deleter);
m_thread = new boost::thread(boost::bind(&InputIpAddressDialog::workerThreadFunc, this, str_ip, str_access_code, str_sn, str_model_id, str_name)); m_thread = new boost::thread(boost::bind(&InputIpAddressDialog::workerThreadFunc, this, str_ip, str_access_code, str_sn, str_model_id, str_name));
} }
@@ -1900,8 +1835,6 @@ void InputIpAddressDialog::on_send_retry()
Fit(); Fit();
wxString ip = m_input_ip->GetTextCtrl()->GetValue(); wxString ip = m_input_ip->GetTextCtrl()->GetValue();
wxString str_access_code = m_input_access_code->GetTextCtrl()->GetValue(); wxString str_access_code = m_input_access_code->GetTextCtrl()->GetValue();
if (str_access_code.IsEmpty())
str_access_code = "88888888";
// check support function // check support function
if (!m_obj) return; if (!m_obj) return;
@@ -2123,7 +2056,7 @@ void InputIpAddressDialog::on_text(wxCommandEvent &evt)
auto str_ip = m_input_ip->GetTextCtrl()->GetValue(); auto str_ip = m_input_ip->GetTextCtrl()->GetValue();
auto str_access_code = m_input_access_code->GetTextCtrl()->GetValue(); auto str_access_code = m_input_access_code->GetTextCtrl()->GetValue();
if (str_access_code.IsEmpty()) { if (str_access_code.empty()) {
str_access_code = "88888888"; str_access_code = "88888888";
} }
@@ -2139,8 +2072,7 @@ void InputIpAddressDialog::on_text(wxCommandEvent &evt)
} }
// ORCA enabling / disabling buttons with conditions enough to change its style // ORCA enabling / disabling buttons with conditions enough to change its style
bool valid_access_code_length = str_access_code.IsEmpty() || str_access_code.Length() >= 8; bool enable_btns = isIp(str_ip.ToStdString()) && str_access_code.Length() == 8 && invalid_access_code;
bool enable_btns = isValidEndpoint(str_ip.ToStdString()) && valid_access_code_length && invalid_access_code;
m_button_manual_setup->Enable(enable_btns); m_button_manual_setup->Enable(enable_btns);
m_button_ok->Enable(enable_btns); m_button_ok->Enable(enable_btns);
@@ -2155,7 +2087,7 @@ InputIpAddressDialog::~InputIpAddressDialog()
void InputIpAddressDialog::on_dpi_changed(const wxRect& suggested_rect) void InputIpAddressDialog::on_dpi_changed(const wxRect& suggested_rect)
{ {
m_button_cafile->Rescale();
} }
+1 -5
View File
@@ -315,16 +315,12 @@ public:
Button* m_button_manual_setup{ nullptr }; Button* m_button_manual_setup{ nullptr };
Label* m_tips_ip{ nullptr }; Label* m_tips_ip{ nullptr };
Label* m_tips_access_code{ nullptr }; Label* m_tips_access_code{ nullptr };
Label* m_tips_cafile{ nullptr };
Label* m_cafile_hint{ nullptr };
Label* m_tips_sn{nullptr}; Label* m_tips_sn{nullptr};
Label* m_tips_modelID{nullptr}; Label* m_tips_modelID{nullptr};
Label* m_test_right_msg{ nullptr }; Label* m_test_right_msg{ nullptr };
Label* m_test_wrong_msg{ nullptr }; Label* m_test_wrong_msg{ nullptr };
TextInput* m_input_ip{ nullptr }; TextInput* m_input_ip{ nullptr };
TextInput* m_input_access_code{ nullptr }; TextInput* m_input_access_code{ nullptr };
wxTextCtrl* m_input_cafile{ nullptr };
Button* m_button_cafile{ nullptr };
TextInput* m_input_printer_name{ nullptr }; TextInput* m_input_printer_name{ nullptr };
TextInput* m_input_sn{ nullptr }; TextInput* m_input_sn{ nullptr };
ComboBox* m_input_modelID{ nullptr }; ComboBox* m_input_modelID{ nullptr };
@@ -348,7 +344,7 @@ public:
void update_title(wxString title); void update_title(wxString title);
void set_machine_obj(MachineObject* obj); void set_machine_obj(MachineObject* obj);
void update_test_msg(wxString msg, bool connected); void update_test_msg(wxString msg, bool connected);
bool isValidEndpoint(std::string endpoint); bool isIp(std::string ipstr);
void check_ip_address_failed(int result); void check_ip_address_failed(int result);
void on_check_ip_address_failed(wxCommandEvent& evt); void on_check_ip_address_failed(wxCommandEvent& evt);
void on_ok(wxMouseEvent& evt); void on_ok(wxMouseEvent& evt);
+35 -74
View File
@@ -21,7 +21,6 @@
#include "Jobs/PlaterWorker.hpp" #include "Jobs/PlaterWorker.hpp"
#include "DeviceCore/DevConfig.h" #include "DeviceCore/DevConfig.h"
#include "DeviceCore/DevConfigUtil.h"
#include "DeviceCore/DevNozzleSystem.h" #include "DeviceCore/DevNozzleSystem.h"
#include "DeviceCore/DevNozzleRack.h" #include "DeviceCore/DevNozzleRack.h"
#include "DeviceCore/DevExtensionTool.h" #include "DeviceCore/DevExtensionTool.h"
@@ -1126,10 +1125,7 @@ bool SelectMachineDialog::do_ams_mapping(MachineObject *obj_,bool use_ams)
int filament_result = 0; int filament_result = 0;
std::vector<bool> map_opt; //four values: use_left_ams, use_right_ams, use_left_ext, use_right_ext std::vector<bool> map_opt; //four values: use_left_ams, use_right_ams, use_left_ext, use_right_ext
// Orca: only do the per-physical-extruder left/right split when the device actually reports if (nozzle_nums > 1){
// 2+ extruders. A non-BBL multi-nozzle printer (e.g. Snapmaker U1) reports a single extruder
// with one filament pool, so it maps as a single surface via the else branch below.
if (nozzle_nums > 1 && obj_->GetExtderSystem()->GetTotalExtderCount() > 1){
//get nozzle property, the extders are same? //get nozzle property, the extders are same?
if (true/*!can_hybrid_mapping(obj_get_extder_data())*/){ if (true/*!can_hybrid_mapping(obj_get_extder_data())*/){
std::vector<FilamentInfo> m_ams_mapping_result_left, m_ams_mapping_result_right; std::vector<FilamentInfo> m_ams_mapping_result_left, m_ams_mapping_result_right;
@@ -2316,10 +2312,8 @@ void SelectMachineDialog::show_status(PrintDialogStatus status, std::vector<wxSt
} else if (status == PrintDialogStatus::PrintStatusNoSdcard) { } else if (status == PrintDialogStatus::PrintStatusNoSdcard) {
Enable_Refresh_Button(true); Enable_Refresh_Button(true);
Enable_Send_Button(false); Enable_Send_Button(false);
}else if (status == PrintDialogStatus::PrintStatusUnsupportedPrinter || }else if (status == PrintDialogStatus::PrintStatusUnsupportedPrinter) {
status == PrintDialogStatus::PrintStatusOptionalPrinterModel) {
wxString msg_text; wxString msg_text;
const bool block_send = status == PrintDialogStatus::PrintStatusUnsupportedPrinter;
try try
{ {
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager(); DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
@@ -2345,21 +2339,17 @@ void SelectMachineDialog::show_status(PrintDialogStatus status, std::vector<wxSt
auto target_print_name = wxString(DevPrinterConfigUtil::get_printer_display_name(target_model_id)); auto target_print_name = wxString(DevPrinterConfigUtil::get_printer_display_name(target_model_id));
target_print_name.Replace(wxT("Bambu Lab "), wxEmptyString); target_print_name.Replace(wxT("Bambu Lab "), wxEmptyString);
if (block_send) {
msg_text = wxString::Format(_L("The selected printer (%s) is incompatible with the print file configuration (%s). Please adjust the printer preset in the prepare page or choose a compatible printer on this page."), sourcet_print_name, target_print_name); msg_text = wxString::Format(_L("The selected printer (%s) is incompatible with the print file configuration (%s). Please adjust the printer preset in the prepare page or choose a compatible printer on this page."), sourcet_print_name, target_print_name);
} else {
msg_text = wxString::Format(_L("The selected printer (%s) has an unknown model, so compatibility with the print file configuration (%s) cannot be verified. Please verify the printer preset before sending."), sourcet_print_name, target_print_name);
}
msg = msg_text; msg = msg_text;
Enable_Refresh_Button(true); Enable_Refresh_Button(true);
Enable_Send_Button(!block_send); Enable_Send_Button(false);
} }
catch (...) catch (...)
{ {
Enable_Refresh_Button(true); Enable_Refresh_Button(true);
Enable_Send_Button(!block_send); Enable_Send_Button(false);
} }
@@ -2520,13 +2510,31 @@ void SelectMachineDialog::on_cancel(wxCloseEvent &event)
bool SelectMachineDialog::is_blocking_printing(MachineObject* obj_) bool SelectMachineDialog::is_blocking_printing(MachineObject* obj_)
{ {
if (m_print_type == PrintFromType::FROM_NORMAL) DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
return wxGetApp().is_blocking_printing(obj_); if (!dev) return true;
auto target_model = obj_->printer_type;
std::string source_model = "";
std::string source_model; if (m_print_type == PrintFromType::FROM_NORMAL) {
if (m_print_type == PrintFromType::FROM_SDCARD_VIEW && !m_required_data_plate_data_list.empty()) PresetBundle* preset_bundle = wxGetApp().preset_bundle;
source_model = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle);
}else if (m_print_type == PrintFromType::FROM_SDCARD_VIEW) {
if (m_required_data_plate_data_list.size() > 0) {
source_model = m_required_data_plate_data_list[m_print_plate_idx]->printer_model_id; source_model = m_required_data_plate_data_list[m_print_plate_idx]->printer_model_id;
return wxGetApp().is_blocking_printing(obj_, source_model); }
}
if (source_model != target_model) {
std::vector<std::string> compatible_machine = obj_->get_compatible_machine();
vector<std::string>::iterator it = find(compatible_machine.begin(), compatible_machine.end(), source_model);
if (it == compatible_machine.end()) {
return true;
}
}
return false;
} }
static std::unordered_set<int> _get_used_nozzle_idxes() static std::unordered_set<int> _get_used_nozzle_idxes()
@@ -2614,10 +2622,6 @@ bool SelectMachineDialog::is_same_printer_model()
if(preset_bundle == nullptr) return result; if(preset_bundle == nullptr) return result;
const auto source_model = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle); const auto source_model = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle);
const auto target_model = obj_->printer_type; const auto target_model = obj_->printer_type;
if (DevPrinterConfigUtil::is_optional_printer_model_id(source_model) ||
DevPrinterConfigUtil::is_optional_printer_model_id(target_model)) {
return true;
}
// Orca: ignore P1P -> P1S // Orca: ignore P1P -> P1S
if (source_model != target_model) { if (source_model != target_model) {
if ((source_model == "C12" && target_model == "C11") || (source_model == "C11" && target_model == "C12") || if ((source_model == "C12" && target_model == "C11") || (source_model == "C11" && target_model == "C12") ||
@@ -3692,31 +3696,9 @@ void SelectMachineDialog::on_send_print()
m_print_job->on_success([this]() { finish_mode(); }); m_print_job->on_success([this]() { finish_mode(); });
m_print_job->on_check_ip_address_fail([this]() { m_print_job->on_check_ip_address_fail([this]() {
// Invoked from the PrintJob worker thread when the LAN pre-flight (file upload
// verification) fails. Marshal device/UI access to the main thread.
CallAfter([this]()
{
// Reset the dialog out of sending mode so the user can retry.
wxCommandEvent* evt = new wxCommandEvent(EVT_CLEAR_IPADDRESS); wxCommandEvent* evt = new wxCommandEvent(EVT_CLEAR_IPADDRESS);
wxQueueEvent(this, evt); wxQueueEvent(this, evt);
DeviceManager* dev = wxGetApp().getDeviceManager();
MachineObject* obj = dev ? dev->get_selected_machine() : nullptr;
if (obj && obj->is_connected())
{
// Connected: failed on file upload
MessageDialog dlg(this,
_L("Failed to upload the file to the printer's storage. Please try again."),
_L("Send Failed"), wxOK | wxICON_ERROR);
dlg.ShowModal();
}
else
{
// Not connected: reenter ip and access code
wxGetApp().show_ip_address_enter_dialog(); wxGetApp().show_ip_address_enter_dialog();
}
});
}); });
// update ota version // update ota version
@@ -4551,13 +4533,11 @@ bool SelectMachineDialog::CheckErrorExtruderNozzleWithSlicing(MachineObject* obj
// check nozzle data valid // check nozzle data valid
{ {
// Commented out the following as ntUndefine and 0.0f are default values if (installed_ext_nozzle.GetNozzleType() == NozzleType::ntUndefine ||
// (signifying that the value is not given) that should PASS, not fail installed_ext_nozzle.GetNozzleDiameter() <= 0.0f) {
// if (installed_ext_nozzle.GetNozzleType() == NozzleType::ntUndefine || show_status(PrintDialogStatus::PrintStatusNozzleDataInvalid);
// installed_ext_nozzle.GetNozzleDiameter() <= 0.0f) { return false;
// show_status(PrintDialogStatus::PrintStatusNozzleDataInvalid); }
// return false;
// }
if (obj_->is_nozzle_flow_type_supported() && if (obj_->is_nozzle_flow_type_supported() &&
installed_ext_nozzle.GetNozzleFlowType() == NozzleFlowType::NONE_FLOWTYPE) { installed_ext_nozzle.GetNozzleFlowType() == NozzleFlowType::NONE_FLOWTYPE) {
@@ -4586,10 +4566,7 @@ bool SelectMachineDialog::CheckErrorExtruderNozzleWithSlicing(MachineObject* obj
// check nozzle diameter // check nozzle diameter
{ {
// 0.0f is default when there is no nozzle diameter is given. if (slicing_ext.nozzle_diameter != installed_ext_nozzle.GetNozzleDiameter()) {
// In nozzle_diameter == 0.0f case, it passes and does not require a comparison
if (installed_ext_nozzle.GetNozzleDiameter() > 0.0f &&
slicing_ext.nozzle_diameter != installed_ext_nozzle.GetNozzleDiameter()) {
std::vector<wxString> msg_params; std::vector<wxString> msg_params;
if (ext_sys->GetTotalExtderCount() == 2) { if (ext_sys->GetTotalExtderCount() == 2) {
const wxString& mismatch_nozzle_str = _get_nozzle_name(ext_sys->GetTotalExtderCount(), slicing_ext_idx); const wxString& mismatch_nozzle_str = _get_nozzle_name(ext_sys->GetTotalExtderCount(), slicing_ext_idx);
@@ -4783,22 +4760,6 @@ void SelectMachineDialog::update_show_status(MachineObject* obj_)
return; return;
} }
bool has_optional_printer_model = DevPrinterConfigUtil::is_optional_printer_model_id(obj_->printer_type);
if (m_print_type == PrintFromType::FROM_NORMAL) {
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
has_optional_printer_model = has_optional_printer_model ||
(preset_bundle && DevPrinterConfigUtil::is_optional_printer_model_id(
preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle)));
} else if (m_print_type == PrintFromType::FROM_SDCARD_VIEW && !m_required_data_plate_data_list.empty()) {
has_optional_printer_model = has_optional_printer_model ||
DevPrinterConfigUtil::is_optional_printer_model_id(
m_required_data_plate_data_list[m_print_plate_idx]->printer_model_id);
}
if (has_optional_printer_model) {
show_status(PrintDialogStatus::PrintStatusOptionalPrinterModel);
}
if (is_blocking_printing(obj_)) { if (is_blocking_printing(obj_)) {
show_status(PrintDialogStatus::PrintStatusUnsupportedPrinter); show_status(PrintDialogStatus::PrintStatusUnsupportedPrinter);
return; return;
@@ -5521,8 +5482,8 @@ void SelectMachineDialog::reset_and_sync_ams_list()
item = new MaterialItem(m_filament_left_panel, colour_rgb, _L(display_materials[extruder])); item = new MaterialItem(m_filament_left_panel, colour_rgb, _L(display_materials[extruder]));
m_sizer_ams_mapping_left->Add(item, 0, wxALL, FromDIP(5)); m_sizer_ams_mapping_left->Add(item, 0, wxALL, FromDIP(5));
} }
else // map == 2, or (non-BBL multi-nozzle) 3+; update_material_item_pos() collapses else if (m_filaments_map[extruder] == 2)
{ // these into the single panel when the device reports < 2 extruders. {
item = new MaterialItem(m_filament_right_panel, colour_rgb, _L(display_materials[extruder])); item = new MaterialItem(m_filament_right_panel, colour_rgb, _L(display_materials[extruder]));
m_sizer_ams_mapping_right->Add(item, 0, wxALL, FromDIP(5)); m_sizer_ams_mapping_right->Add(item, 0, wxALL, FromDIP(5));
} }
+1 -1
View File
@@ -738,7 +738,7 @@ void SendMultiMachinePage::on_send(wxCommandEvent& event)
if (obj && obj->is_online() && !obj->can_abort() && !obj->is_in_upgrading() && it->second->get_state_selected() == 1 && it->second->state_printable <= 2) { if (obj && obj->is_online() && !obj->can_abort() && !obj->is_in_upgrading() && it->second->get_state_selected() == 1 && it->second->state_printable <= 2) {
if (!wxGetApp().is_blocking_printing(obj)) { if (!it->second->is_blocking_printing(obj)) {
PrintParams params = request_params(obj); PrintParams params = request_params(obj);
print_params.push_back(params); print_params.push_back(params);
} }
+48 -92
View File
@@ -24,7 +24,6 @@
#include "BitmapCache.hpp" #include "BitmapCache.hpp"
#include "DeviceCore/DevManager.h" #include "DeviceCore/DevManager.h"
#include "DeviceCore/DevConfigUtil.h"
#include "DeviceCore/DevStorage.h" #include "DeviceCore/DevStorage.h"
#include "slic3r/Utils/FileTransferUtils.hpp" #include "slic3r/Utils/FileTransferUtils.hpp"
@@ -301,7 +300,7 @@ SendToPrinterDialog::SendToPrinterDialog(Plater *plater)
m_storage_panel->Layout(); m_storage_panel->Layout();
// try to connect // try to connect
m_statictext_printer_msg = new wxStaticText(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(FromDIP(400), -1), wxALIGN_CENTER_HORIZONTAL); m_statictext_printer_msg = new wxStaticText(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER_HORIZONTAL);
m_statictext_printer_msg->SetFont(::Label::Body_13); m_statictext_printer_msg->SetFont(::Label::Body_13);
m_statictext_printer_msg->SetForegroundColour(*wxBLACK); m_statictext_printer_msg->SetForegroundColour(*wxBLACK);
m_statictext_printer_msg->Hide(); m_statictext_printer_msg->Hide();
@@ -761,25 +760,9 @@ void SendToPrinterDialog::update_priner_status_msg(wxString msg, bool is_warning
if (str_new != str_old) { if (str_new != str_old) {
if (m_statictext_printer_msg->GetLabel() != msg) { if (m_statictext_printer_msg->GetLabel() != msg) {
m_statictext_printer_msg->SetLabel(msg); m_statictext_printer_msg->SetLabel(msg);
const int wrap_width = FromDIP(400); m_statictext_printer_msg->SetMinSize(wxSize(FromDIP(400), -1));
m_statictext_printer_msg->Wrap(wrap_width); m_statictext_printer_msg->SetMaxSize(wxSize(FromDIP(400), -1));
int line_count = 1; m_statictext_printer_msg->Wrap(FromDIP(400));
const wxString wrapped_label = m_statictext_printer_msg->GetLabel();
for (size_t i = 0; i < wrapped_label.length(); ++i) {
if (wrapped_label[i] == '\n')
++line_count;
}
wxCoord text_width = 0;
wxCoord text_height = 0;
m_statictext_printer_msg->GetTextExtent(msg, &text_width, &text_height);
const int extent_line_count = text_width > 0 ?
std::max(1, (static_cast<int>(text_width) + wrap_width - 1) / wrap_width) : 1;
line_count = std::max(line_count, extent_line_count);
const int line_height = std::max(m_statictext_printer_msg->GetCharHeight(), static_cast<int>(text_height));
const int min_height = std::max(m_statictext_printer_msg->GetBestSize().GetHeight(),
line_count * line_height + FromDIP(2));
m_statictext_printer_msg->SetMinSize(wxSize(wrap_width, min_height));
m_statictext_printer_msg->SetMaxSize(wxDefaultSize);
m_statictext_printer_msg->Show(); m_statictext_printer_msg->Show();
Layout(); Layout();
Fit(); Fit();
@@ -1119,7 +1102,7 @@ void SendToPrinterDialog::update_user_printer()
wxArrayString machine_list_name; wxArrayString machine_list_name;
std::map<std::string, MachineObject*> option_list; std::map<std::string, MachineObject*> option_list;
option_list = dev->get_my_machine_list(dev->get_current_printer_agent_id()); option_list = dev->get_my_machine_list();
// same machine only appear once // same machine only appear once
for (auto it = option_list.begin(); it != option_list.end(); it++) { for (auto it = option_list.begin(); it != option_list.end(); it++) {
@@ -1277,7 +1260,7 @@ void SendToPrinterDialog::update_show_status()
reset_timeout(); reset_timeout();
// reading done // reading done
if (wxGetApp().is_blocking_printing(obj_)) { if (is_blocking_printing(obj_)) {
show_status(PrintDialogStatus::PrintStatusUnsupportedPrinter); show_status(PrintDialogStatus::PrintStatusUnsupportedPrinter);
return; return;
} }
@@ -1342,6 +1325,26 @@ void SendToPrinterDialog::update_show_status()
} }
} }
bool SendToPrinterDialog::is_blocking_printing(MachineObject* obj_)
{
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
if (!dev) return true;
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
auto source_model = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle);
auto target_model = obj_->printer_type;
if (source_model != target_model) {
std::vector<std::string> compatible_machine = obj_->get_compatible_machine();
vector<std::string>::iterator it = find(compatible_machine.begin(), compatible_machine.end(), source_model);
if (it == compatible_machine.end()) {
return true;
}
}
return false;
}
void SendToPrinterDialog::Enable_Refresh_Button(bool en) void SendToPrinterDialog::Enable_Refresh_Button(bool en)
{ {
if (!en) { if (!en) {
@@ -1483,9 +1486,6 @@ void SendToPrinterDialog::show_status(PrintDialogStatus status, std::vector<wxSt
Enable_Send_Button(false); Enable_Send_Button(false);
Enable_Refresh_Button(true); Enable_Refresh_Button(true);
} else if (status == PrintDialogStatus::PrintStatusPublicInitFailed) { } else if (status == PrintDialogStatus::PrintStatusPublicInitFailed) {
wxString msg_text = _L(
"Failed to initialize the printer file transfer. Please check the connection and try again.");
update_print_status_msg(msg_text, true, true);
Enable_Send_Button(false); Enable_Send_Button(false);
Enable_Refresh_Button(true); Enable_Refresh_Button(true);
} else if (status == PrintDialogStatus::PrintStatusPublicUploadFiled) { } else if (status == PrintDialogStatus::PrintStatusPublicUploadFiled) {
@@ -1663,16 +1663,28 @@ extern void refresh_agora_url(char const *device, char const *dev_ver, char
void SendToPrinterDialog::GetConnection() void SendToPrinterDialog::GetConnection()
{ {
DeviceManager *dm = GUI::wxGetApp().getDeviceManager(); DeviceManager *dm = GUI::wxGetApp().getDeviceManager();
MachineObject *obj = dm ? dm->get_selected_machine() : nullptr;
if (!obj) MachineObject *obj = dm->get_selected_machine();
if (obj == nullptr) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " : obj is empty"; BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " : obj is empty";
if (obj && !obj->get_file_remote()) m_connection_status = ConnectionStatus::NOT_START;
}
int remote_proto = obj->get_file_remote();
if (!remote_proto) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " : remote_proto is not support"; BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " : remote_proto is not support";
if (obj && obj->is_camera_busy_off()) m_connection_status = ConnectionStatus::NOT_START;
}
if (obj->is_camera_busy_off()) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " : camera is busy"; BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " : camera is busy";
m_connection_status = ConnectionStatus::NOT_START;
}
NetworkAgent *agent = wxGetApp().getAgent(); NetworkAgent *agent = wxGetApp().getAgent();
std::string agent_version = agent ? agent->get_version() : "";
std::string dev_ver = obj->get_ota_version();
std::string dev_id = obj->get_dev_id();
if (m_url_timer && m_url_timer->IsRunning()) if (m_url_timer && m_url_timer->IsRunning())
{ {
@@ -1697,41 +1709,20 @@ void SendToPrinterDialog::GetConnection()
m_url_timer->GetId()); m_url_timer->GetId());
m_url_timer->StartOnce(8000); m_url_timer->StartOnce(8000);
if (obj && agent) if (agent) {
{
std::string dev_ver = obj->get_ota_version();
std::string dev_id = obj->get_dev_id();
if (m_tcp_try_connect) { if (m_tcp_try_connect) {
std::string devIP = obj->get_dev_ip(); std::string devIP = obj->get_dev_ip();
std::string accessCode = obj->get_access_code(); std::string accessCode = obj->get_access_code();
std::string url = "bambu:///local/" + devIP + "?port=6000&user=" + "bblp" + "&passwd=" + accessCode; std::string url = "bambu:///local/" + devIP + "?port=6000&user=" + "bblp" + "&passwd=" + accessCode;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Connect method tcp, dev_id=" << dev_id BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Connect method tcp";
<< ", dev_ip=" << devIP << ", access_code_len=" << accessCode.size();
try
{
m_filetransfer_tunnel = std::make_unique<FileTransferTunnel>(module(), url); m_filetransfer_tunnel = std::make_unique<FileTransferTunnel>(module(), url);
m_filetransfer_tunnel->on_connection([this](bool is_success, int err_code, std::string error_msg) m_filetransfer_tunnel->on_connection([this](bool is_success, int err_code, std::string error_msg) {
{ CallAfter([this, is_success, err_code, error_msg]() {
CallAfter([this, is_success, err_code, error_msg]()
{
OnConnection(is_success, err_code, error_msg); OnConnection(is_success, err_code, error_msg);
}); });
}); });
m_filetransfer_tunnel->start_connect(); m_filetransfer_tunnel->start_connect();
} }
catch (const std::exception& e)
{
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": tcp FileTransferTunnel unavailable for dev_id=" <<
dev_id
<< " dev_ip=" << devIP << ": " << e.what();
if (m_url_timer && m_url_timer->IsRunning()) m_url_timer->Stop();
m_filetransfer_tunnel.reset();
m_connection_status = ConnectionStatus::CONNECTION_FAILED;
show_status(PrintDialogStatus::PrintStatusPublicInitFailed);
}
}
else if (m_tutk_try_connect) else if (m_tutk_try_connect)
{ {
std::string protocols[] = {"", "\"tutk\"", "\"agora\"", "\"tutk\",\"agora\""}; std::string protocols[] = {"", "\"tutk\"", "\"agora\"", "\"tutk\",\"agora\""};
@@ -1758,29 +1749,12 @@ void SendToPrinterDialog::GetConnection()
if (boost::algorithm::starts_with(url, "bambu:///")) if (boost::algorithm::starts_with(url, "bambu:///"))
{ {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Connect method tutk"; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Connect method tutk";
try
{
m_filetransfer_tunnel = std::make_unique<FileTransferTunnel>(module(), url); m_filetransfer_tunnel = std::make_unique<FileTransferTunnel>(module(), url);
m_filetransfer_tunnel->on_connection( m_filetransfer_tunnel->on_connection([this](bool is_success, int err_code, std::string error_msg) {
[this](bool is_success, int err_code, std::string error_msg) CallAfter([this, is_success, err_code, error_msg]() { OnConnection(is_success, err_code, error_msg); });
{
CallAfter([this, is_success, err_code, error_msg]()
{
OnConnection(is_success, err_code, error_msg);
});
}); });
m_filetransfer_tunnel->start_connect(); m_filetransfer_tunnel->start_connect();
} }
catch (const std::exception& e)
{
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": tutk FileTransferTunnel unavailable: " << e.
what();
if (m_url_timer && m_url_timer->IsRunning()) m_url_timer->Stop();
m_filetransfer_tunnel.reset();
m_connection_status = ConnectionStatus::CONNECTION_FAILED;
show_status(PrintDialogStatus::PrintStatusPublicInitFailed);
}
}
else else
{ {
std::string res = ""; std::string res = "";
@@ -1879,16 +1853,7 @@ void SendToPrinterDialog::ResetTunnelAndJob()
void SendToPrinterDialog::CreateMediaAbilityJob() void SendToPrinterDialog::CreateMediaAbilityJob()
{ {
nlohmann::json media_ability = {{"cmd_type", 7}}; nlohmann::json media_ability = {{"cmd_type", 7}};
try
{
m_filetransfer_mediability_job = std::make_unique<FileTransferJob>(module(), std::string(media_ability.dump())); m_filetransfer_mediability_job = std::make_unique<FileTransferJob>(module(), std::string(media_ability.dump()));
}
catch (const std::exception& e)
{
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": FileTransferJob unavailable: " << e.what();
show_status(PrintDialogStatus::PrintStatusPublicInitFailed);
return;
}
m_filetransfer_mediability_job->on_result([this](int res, int resp_ec, std::string json_res, std::vector<std::byte> bin_res) { m_filetransfer_mediability_job->on_result([this](int res, int resp_ec, std::string json_res, std::vector<std::byte> bin_res) {
//this pl //this pl
CallAfter([this, res, resp_ec, json_res] { CallAfter([this, res, resp_ec, json_res] {
@@ -1947,16 +1912,7 @@ void SendToPrinterDialog::CreateUploadFileJob(const std::string &path, const std
upload_params["file_path"] = path; upload_params["file_path"] = path;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Begin CreateUploadFileJob"; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Begin CreateUploadFileJob";
try
{
m_filetransfer_uploadfile_job = std::make_unique<FileTransferJob>(module(), std::string(upload_params.dump())); m_filetransfer_uploadfile_job = std::make_unique<FileTransferJob>(module(), std::string(upload_params.dump()));
}
catch (const std::exception& e)
{
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": FileTransferJob unavailable: " << e.what();
show_status(PrintDialogStatus::PrintStatusPublicUploadFiled);
return;
}
m_filetransfer_uploadfile_job->on_result([this](int res, int resp_ec, std::string json_res, std::vector<std::byte> bin_res) { // m_filetransfer_uploadfile_job->on_result([this](int res, int resp_ec, std::string json_res, std::vector<std::byte> bin_res) { //
CallAfter([this, res, resp_ec, json_res, bin_res] { CallAfter([this, res, resp_ec, json_res, bin_res] {
UploadFileRessultCallback(res, resp_ec,json_res, bin_res); UploadFileRessultCallback(res, resp_ec,json_res, bin_res);
+1
View File
@@ -181,6 +181,7 @@ public:
void reset_timeout(); void reset_timeout();
void update_user_printer(); void update_user_printer();
void update_show_status(); void update_show_status();
bool is_blocking_printing(MachineObject* obj_);
void prepare(int print_plate_idx); void prepare(int print_plate_idx);
void check_focus(wxWindow* window); void check_focus(wxWindow* window);
void check_fcous_state(wxWindow* window); void check_fcous_state(wxWindow* window);
+86 -87
View File
@@ -22,7 +22,6 @@
#include <wx/mstream.h> #include <wx/mstream.h>
#include <wx/sstream.h> #include <wx/sstream.h>
#include <wx/zstream.h> #include <wx/zstream.h>
#include <chrono>
#include "DeviceCore/DevBed.h" #include "DeviceCore/DevBed.h"
#include "DeviceCore/DevCtrl.h" #include "DeviceCore/DevCtrl.h"
@@ -1502,26 +1501,27 @@ wxBoxSizer *StatusBasePanel::create_monitoring_page()
m_setting_button->SetMinSize(wxSize(FromDIP(38), FromDIP(24))); m_setting_button->SetMinSize(wxSize(FromDIP(38), FromDIP(24)));
m_setting_button->SetBackgroundColour(STATUS_TITLE_BG); m_setting_button->SetBackgroundColour(STATUS_TITLE_BG);
// m_camera_switch_button = new wxStaticBitmap(m_panel_monitoring_title, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize(FromDIP(38), FromDIP(24)), 0); m_camera_switch_button = new wxStaticBitmap(m_panel_monitoring_title, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize(FromDIP(38), FromDIP(24)), 0);
// m_camera_switch_button->SetMinSize(wxSize(FromDIP(38), FromDIP(24))); m_camera_switch_button->SetMinSize(wxSize(FromDIP(38), FromDIP(24)));
// m_camera_switch_button->SetBackgroundColour(STATUS_TITLE_BG); m_camera_switch_button->SetBackgroundColour(STATUS_TITLE_BG);
// m_camera_switch_button->SetBitmap(m_bitmap_switch_camera.bmp()); m_camera_switch_button->SetBitmap(m_bitmap_switch_camera.bmp());
// m_camera_switch_button->Bind(wxEVT_RIGHT_DOWN, [this](auto& e) { m_camera_switch_button->Bind(wxEVT_LEFT_DOWN, &StatusBasePanel::on_camera_switch_toggled, this);
// const std::string js_request_pip = R"( m_camera_switch_button->Bind(wxEVT_RIGHT_DOWN, [this](auto& e) {
// document.querySelector('video').requestPictureInPicture(); const std::string js_request_pip = R"(
// )"; document.querySelector('video').requestPictureInPicture();
// m_custom_camera_view->RunScript(js_request_pip); )";
// }); m_custom_camera_view->RunScript(js_request_pip);
// m_camera_switch_button->Hide(); });
m_camera_switch_button->Hide();
m_bitmap_sdcard_img->SetToolTip(_L("Storage")); m_bitmap_sdcard_img->SetToolTip(_L("Storage"));
m_bitmap_timelapse_img->SetToolTip(_L("Timelapse")); m_bitmap_timelapse_img->SetToolTip(_L("Timelapse"));
m_bitmap_recording_img->SetToolTip(_L("Video")); m_bitmap_recording_img->SetToolTip(_L("Video"));
m_bitmap_vcamera_img->SetToolTip(_L("Go Live")); m_bitmap_vcamera_img->SetToolTip(_L("Go Live"));
m_setting_button->SetToolTip(_L("Camera Setting")); m_setting_button->SetToolTip(_L("Camera Setting"));
// m_camera_switch_button->SetToolTip(_L("Switch Camera View")); m_camera_switch_button->SetToolTip(_L("Switch Camera View"));
// bSizer_monitoring_title->Add(m_camera_switch_button, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5)); bSizer_monitoring_title->Add(m_camera_switch_button, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
bSizer_monitoring_title->Add(m_bitmap_sdcard_img, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5)); bSizer_monitoring_title->Add(m_bitmap_sdcard_img, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
bSizer_monitoring_title->Add(m_bitmap_timelapse_img, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5)); bSizer_monitoring_title->Add(m_bitmap_timelapse_img, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
bSizer_monitoring_title->Add(m_bitmap_recording_img, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5)); bSizer_monitoring_title->Add(m_bitmap_recording_img, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
@@ -1544,18 +1544,19 @@ wxBoxSizer *StatusBasePanel::create_monitoring_page()
m_custom_camera_view = WebView::CreateWebView(this, wxEmptyString); m_custom_camera_view = WebView::CreateWebView(this, wxEmptyString);
m_custom_camera_view->EnableContextMenu(false); m_custom_camera_view->EnableContextMenu(false);
Bind(wxEVT_WEBVIEW_NAVIGATING, &StatusBasePanel::on_webview_navigating, this, m_custom_camera_view->GetId()); Bind(wxEVT_WEBVIEW_NAVIGATING, &StatusBasePanel::on_webview_navigating, this, m_custom_camera_view->GetId());
m_web_media_controller = std::make_unique<WebMediaController>(m_custom_camera_view);
m_media_play_ctrl = new MediaPlayCtrl(this, m_media_ctrl, wxDefaultPosition, wxSize(-1, FromDIP(40))); m_media_play_ctrl = new MediaPlayCtrl(this, m_media_ctrl, wxDefaultPosition, wxSize(-1, FromDIP(40)));
m_media_play_ctrl->SetWebMediaController(m_web_media_controller.get());
m_custom_camera_view->Hide(); m_custom_camera_view->Hide();
// m_custom_camera_view->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, [this](wxWebViewEvent& evt) { m_custom_camera_view->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, [this](wxWebViewEvent& evt) {
// if (evt.GetString() == "leavepictureinpicture") { if (evt.GetString() == "leavepictureinpicture") {
// // When leaving PiP, video gets paused in some cases and toggling play // When leaving PiP, video gets paused in some cases and toggling play
// // programmatically does not work. // programmatically does not work.
// m_custom_camera_view->Reload(); m_custom_camera_view->Reload();
// } }
// }); else if (evt.GetString() == "enterpictureinpicture") {
toggle_builtin_camera();
}
});
sizer->Add(m_media_ctrl, 1, wxEXPAND | wxALL, 0); sizer->Add(m_media_ctrl, 1, wxEXPAND | wxALL, 0);
sizer->Add(m_custom_camera_view, 1, wxEXPAND | wxALL, 0); sizer->Add(m_custom_camera_view, 1, wxEXPAND | wxALL, 0);
@@ -1565,6 +1566,10 @@ wxBoxSizer *StatusBasePanel::create_monitoring_page()
// //
// sizer->Add(media_ctrl_panel, 1, wxEXPAND | wxALL, 1); // sizer->Add(media_ctrl_panel, 1, wxEXPAND | wxALL, 1);
if (wxGetApp().app_config->get("camera", "enable_custom_source") == "true") {
handle_camera_source_change();
}
return sizer; return sizer;
} }
@@ -2328,27 +2333,6 @@ void StatusPanel::update_camera_state(MachineObject* obj)
{ {
if (!obj) return; if (!obj) return;
auto agent = wxGetApp().getAgent();
const auto camera_mode = agent ? agent->get_camera_stream_mode() : CameraStreamMode::none;
const bool use_webview = camera_mode == CameraStreamMode::http_snapshot;
if (use_webview) {
//m_camera_switch_button->Hide();
if (!m_custom_camera_view->IsShown()) {
// why: do not reload the WebView URL per tick, or redirects can cause a reload loop.
// MediaPlayCtrl (via its WebMediaController) owns loading/playing the stream itself.
m_custom_camera_view->Show();
m_media_ctrl->Hide();
}
} else {
if (m_custom_camera_view->IsShown()) {
m_custom_camera_view->Hide();
// Stop the snapshot WebView before switching to native playback
// or leaving the camera mode.
m_media_play_ctrl->StopWebStream();
}
m_media_ctrl->Show();
}
//sdcard //sdcard
auto sdcard_state = obj->GetStorage()->get_sdcard_state(); auto sdcard_state = obj->GetStorage()->get_sdcard_state();
if (m_last_sdcard != sdcard_state) { if (m_last_sdcard != sdcard_state) {
@@ -2380,12 +2364,7 @@ void StatusPanel::update_camera_state(MachineObject* obj)
m_last_recording = obj->is_recording() ? 1 : 0; m_last_recording = obj->is_recording() ? 1 : 0;
} }
if (use_webview) { if (!m_bitmap_recording_img->IsShown()) {
if (m_bitmap_recording_img->IsShown()) {
m_bitmap_recording_img->Hide();
m_panel_monitoring_title->Layout();
}
} else if (!m_bitmap_recording_img->IsShown()) {
m_bitmap_recording_img->Show(); m_bitmap_recording_img->Show();
m_panel_monitoring_title->Layout(); m_panel_monitoring_title->Layout();
} }
@@ -2442,8 +2421,6 @@ void StatusPanel::update_camera_state(MachineObject* obj)
bool show_vcamera = m_media_play_ctrl->IsStreaming(); bool show_vcamera = m_media_play_ctrl->IsStreaming();
m_camera_popup->update(show_vcamera); m_camera_popup->update(show_vcamera);
} }
m_setting_button->Show(!use_webview);
} }
StatusPanel::StatusPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style, const wxString &name) StatusPanel::StatusPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style, const wxString &name)
@@ -3974,18 +3951,9 @@ void StatusPanel::update_cloud_subtask(MachineObject *obj)
update_calib_bitmap(); update_calib_bitmap();
if (obj->slice_info) { if (obj->slice_info) {
m_request_url = wxString(obj->slice_info->thumbnail_url); m_request_url = wxString(obj->slice_info->thumbnail_url);
load_thumbnail_from_url(m_request_url, obj); if (!m_request_url.IsEmpty()) {
}
}
}
bool StatusPanel::load_thumbnail_from_url(const wxString &url, MachineObject *obj)
{
if (url.IsEmpty())
return false;
wxImage img; wxImage img;
std::map<wxString, wxImage>::iterator it = img_list.find(url); std::map<wxString, wxImage>::iterator it = img_list.find(m_request_url);
if (it != img_list.end()) { if (it != img_list.end()) {
if (m_current_print_mode != PrintingTaskType::CALIBRATION ||(m_calib_mode == CalibMode::Calib_Flow_Rate && m_calib_method == CalibrationMethod::CALI_METHOD_MANUAL)) { if (m_current_print_mode != PrintingTaskType::CALIBRATION ||(m_calib_mode == CalibMode::Calib_Flow_Rate && m_calib_method == CalibrationMethod::CALI_METHOD_MANUAL)) {
img = it->second; img = it->second;
@@ -4001,33 +3969,21 @@ bool StatusPanel::load_thumbnail_from_url(const wxString &url, MachineObject *ob
task_thumbnail_state = ThumbnailState::TASK_THUMBNAIL; task_thumbnail_state = ThumbnailState::TASK_THUMBNAIL;
BOOST_LOG_TRIVIAL(trace) << "web_request: use cache image"; BOOST_LOG_TRIVIAL(trace) << "web_request: use cache image";
} else { } else {
m_request_url = url;
web_request = wxWebSession::GetDefault().CreateRequest(this, m_request_url); web_request = wxWebSession::GetDefault().CreateRequest(this, m_request_url);
BOOST_LOG_TRIVIAL(trace) << "monitor: start request thumbnail, url = " << m_request_url; BOOST_LOG_TRIVIAL(trace) << "monitor: start request thumbnail, url = " << m_request_url;
web_request.Start(); web_request.Start();
m_start_loading_thumbnail = false; m_start_loading_thumbnail = false;
} }
return true; }
}
}
} }
void StatusPanel::update_sdcard_subtask(MachineObject *obj) void StatusPanel::update_sdcard_subtask(MachineObject *obj)
{ {
if (!obj) return; if (!obj) return;
const wxString thumbnail_url = wxString(obj->m_agent_thumbnail_url); if (!m_load_sdcard_thumbnail) {
if (!thumbnail_url.IsEmpty()) {
if (m_request_url != thumbnail_url || !m_load_sdcard_thumbnail) {
if (web_request.IsOk() && web_request.GetState() == wxWebRequest::State_Active)
web_request.Cancel();
update_calib_bitmap();
m_request_url = thumbnail_url;
load_thumbnail_from_url(thumbnail_url, obj);
m_load_sdcard_thumbnail = true;
}
return;
}
if (!m_load_sdcard_thumbnail || !m_request_url.IsEmpty()) {
update_calib_bitmap(); update_calib_bitmap();
if (m_current_print_mode != PrintingTaskType::CALIBRATION) { if (m_current_print_mode != PrintingTaskType::CALIBRATION) {
m_project_task_panel->get_bitmap_thumbnail()->SetBitmap(m_thumbnail_sdcard.bmp()); m_project_task_panel->get_bitmap_thumbnail()->SetBitmap(m_thumbnail_sdcard.bmp());
@@ -4035,7 +3991,6 @@ void StatusPanel::update_sdcard_subtask(MachineObject *obj)
} }
task_thumbnail_state = ThumbnailState::SDCARD_THUMBNAIL; task_thumbnail_state = ThumbnailState::SDCARD_THUMBNAIL;
m_load_sdcard_thumbnail = true; m_load_sdcard_thumbnail = true;
m_request_url.clear();
} }
} }
@@ -4765,11 +4720,11 @@ void StatusPanel::on_ams_refresh_rfid(wxCommandEvent &event)
try { try {
if (!use_new_command) { if (!use_new_command) {
int tray_index = atoi(curr_ams_id.c_str()) * 4 + atoi(slot_it->second->id.c_str()); int tray_index = atoi(curr_ams_id.c_str()) * 4 + atoi(slot_it->second->id.c_str());
obj->command_ams_refresh_rfid(-1, tray_index); obj->command_ams_refresh_rfid(std::to_string(tray_index));
} }
if (use_new_command) { if (use_new_command) {
obj->command_ams_refresh_rfid(stoi(curr_ams_id), stoi(curr_can_id)); obj->command_ams_refresh_rfid2(stoi(curr_ams_id), stoi(curr_can_id));
} }
} catch (...) { } catch (...) {
@@ -5035,6 +4990,7 @@ void StatusPanel::on_camera_enter(wxMouseEvent& event)
} }
sdcard_hint_dlg->on_show(); sdcard_hint_dlg->on_show();
}); });
m_camera_popup->Bind(EVT_CAM_SOURCE_CHANGE, &StatusPanel::on_camera_source_change, this);
wxWindow* ctrl = (wxWindow*)event.GetEventObject(); wxWindow* ctrl = (wxWindow*)event.GetEventObject();
wxPoint pos = ctrl->ClientToScreen(wxPoint(0, 0)); wxPoint pos = ctrl->ClientToScreen(wxPoint(0, 0));
wxSize sz = ctrl->GetSize(); wxSize sz = ctrl->GetSize();
@@ -5046,6 +5002,54 @@ void StatusPanel::on_camera_enter(wxMouseEvent& event)
} }
} }
void StatusBasePanel::on_camera_source_change(wxCommandEvent& event)
{
handle_camera_source_change();
}
void StatusBasePanel::handle_camera_source_change()
{
const auto new_cam_url = wxGetApp().app_config->get("camera", "custom_source");
const auto enabled = wxGetApp().app_config->get("camera", "enable_custom_source") == "true";
if (enabled && !new_cam_url.empty()) {
m_custom_camera_view->LoadURL(new_cam_url);
toggle_custom_camera();
m_camera_switch_button->Show();
} else {
toggle_builtin_camera();
m_camera_switch_button->Hide();
}
}
void StatusBasePanel::toggle_builtin_camera()
{
m_custom_camera_view->Hide();
m_media_ctrl->Show();
m_media_play_ctrl->Show();
}
void StatusBasePanel::toggle_custom_camera()
{
const auto enabled = wxGetApp().app_config->get("camera", "enable_custom_source") == "true";
if (enabled) {
m_custom_camera_view->Show();
m_media_ctrl->Hide();
m_media_play_ctrl->Hide();
}
}
void StatusBasePanel::on_camera_switch_toggled(wxMouseEvent& event)
{
const auto enabled = wxGetApp().app_config->get("camera", "enable_custom_source") == "true";
if (enabled && m_media_ctrl->IsShown()) {
toggle_custom_camera();
} else {
toggle_builtin_camera();
}
}
void StatusBasePanel::remove_controls() void StatusBasePanel::remove_controls()
{ {
const std::string js_cleanup_video_element = R"( const std::string js_cleanup_video_element = R"(
@@ -5198,11 +5202,6 @@ bool StatusPanel::is_stage_list_info_changed(MachineObject *obj)
void StatusPanel::set_default() void StatusPanel::set_default()
{ {
BOOST_LOG_TRIVIAL(trace) << "status_panel: set_default"; BOOST_LOG_TRIVIAL(trace) << "status_panel: set_default";
if (m_custom_camera_view->IsShown()) {
m_custom_camera_view->Hide();
m_media_ctrl->Show();
m_media_play_ctrl->StopWebStream();
}
obj = nullptr; obj = nullptr;
last_subtask = nullptr; last_subtask = nullptr;
last_tray_exist_bits = -1; last_tray_exist_bits = -1;
+6 -6
View File
@@ -14,9 +14,7 @@
#include <wx/sizer.h> #include <wx/sizer.h>
#include <wx/gbsizer.h> #include <wx/gbsizer.h>
#include <wx/webrequest.h> #include <wx/webrequest.h>
#include <memory>
#include "MediaPlayCtrl.h" #include "MediaPlayCtrl.h"
#include "WebMediaController.hpp"
#include "AMSSetting.hpp" #include "AMSSetting.hpp"
#include "Calibration.hpp" #include "Calibration.hpp"
#include "CalibrationWizardPage.hpp" #include "CalibrationWizardPage.hpp"
@@ -442,7 +440,7 @@ protected:
wxStaticBitmap *m_bitmap_sdcard_img; wxStaticBitmap *m_bitmap_sdcard_img;
wxStaticBitmap *m_bitmap_static_use_time; wxStaticBitmap *m_bitmap_static_use_time;
wxStaticBitmap *m_bitmap_static_use_weight; wxStaticBitmap *m_bitmap_static_use_weight;
// wxStaticBitmap* m_camera_switch_button; wxStaticBitmap* m_camera_switch_button;
wxMediaCtrl3 * m_media_ctrl; wxMediaCtrl3 * m_media_ctrl;
@@ -464,8 +462,6 @@ protected:
ScalableButton *m_button_abort; ScalableButton *m_button_abort;
Button * m_button_clean; Button * m_button_clean;
wxWebView * m_custom_camera_view{nullptr}; wxWebView * m_custom_camera_view{nullptr};
std::unique_ptr<WebMediaController> m_web_media_controller;
wxSimplebook* m_extruder_book; wxSimplebook* m_extruder_book;
std::vector<ExtruderImage *> m_extruderImage; std::vector<ExtruderImage *> m_extruderImage;
@@ -581,8 +577,13 @@ protected:
virtual void on_axis_ctrl_e_up_10(wxCommandEvent &event) { event.Skip(); } virtual void on_axis_ctrl_e_up_10(wxCommandEvent &event) { event.Skip(); }
virtual void on_axis_ctrl_e_down_10(wxCommandEvent &event) { event.Skip(); } virtual void on_axis_ctrl_e_down_10(wxCommandEvent &event) { event.Skip(); }
virtual void on_nozzle_selected(wxCommandEvent &event) { event.Skip(); } virtual void on_nozzle_selected(wxCommandEvent &event) { event.Skip(); }
void on_camera_source_change(wxCommandEvent& event);
void handle_camera_source_change();
void remove_controls(); void remove_controls();
void on_webview_navigating(wxWebViewEvent& evt); void on_webview_navigating(wxWebViewEvent& evt);
void on_camera_switch_toggled(wxMouseEvent& event);
void toggle_custom_camera();
void toggle_builtin_camera();
public: public:
StatusBasePanel(wxWindow * parent, StatusBasePanel(wxWindow * parent,
@@ -630,7 +631,6 @@ class StatusPanel : public StatusBasePanel
private: private:
friend class MonitorPanel; friend class MonitorPanel;
void wire_controls(); void wire_controls();
bool load_thumbnail_from_url(const wxString &url, MachineObject *obj);
protected: protected:
std::shared_ptr<SliceInfoPopup> m_slice_info_popup; std::shared_ptr<SliceInfoPopup> m_slice_info_popup;
+21 -32
View File
@@ -1742,8 +1742,6 @@ void SyncAmsInfoDialog::show_status(PrintDialogStatus status, std::vector<wxStri
update_print_status_msg(msg_text, true, true); update_print_status_msg(msg_text, true, true);
} else if (status == PrintDialogStatus::PrintStatusAmsMappingSuccess) { } else if (status == PrintDialogStatus::PrintStatusAmsMappingSuccess) {
update_print_status_msg(wxEmptyString, false, false); update_print_status_msg(wxEmptyString, false, false);
} else if (status == PrintDialogStatus::PrintStatusOptionalPrinterModel) {
update_print_status_msg(PrePrintChecker::get_pre_state_msg(PrintDialogStatus::PrintStatusOptionalPrinterModel), true, true);
} else if (status == PrintDialogStatus::PrintStatusAmsMappingInvalid) { } else if (status == PrintDialogStatus::PrintStatusAmsMappingInvalid) {
update_print_status_msg(wxEmptyString, true, false); update_print_status_msg(wxEmptyString, true, false);
} else if (status == PrintDialogStatus::PrintStatusAmsMappingMixInvalid) { } else if (status == PrintDialogStatus::PrintStatusAmsMappingMixInvalid) {
@@ -1863,13 +1861,26 @@ void SyncAmsInfoDialog::on_cancel(wxCloseEvent &event)
bool SyncAmsInfoDialog::is_blocking_printing(MachineObject *obj_) bool SyncAmsInfoDialog::is_blocking_printing(MachineObject *obj_)
{ {
if (m_print_type == PrintFromType::FROM_NORMAL) DeviceManager *dev = Slic3r::GUI::wxGetApp().getDeviceManager();
return wxGetApp().is_blocking_printing(obj_); if (!dev) return true;
auto target_model = obj_->printer_type;
std::string source_model = "";
std::string source_model; if (m_print_type == PrintFromType::FROM_NORMAL) {
if (m_print_type == PrintFromType::FROM_SDCARD_VIEW && !m_required_data_plate_data_list.empty()) PresetBundle *preset_bundle = wxGetApp().preset_bundle;
source_model = m_required_data_plate_data_list[m_print_plate_idx]->printer_model_id; source_model = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle);
return wxGetApp().is_blocking_printing(obj_, source_model);
} else if (m_print_type == PrintFromType::FROM_SDCARD_VIEW) {
if (m_required_data_plate_data_list.size() > 0) { source_model = m_required_data_plate_data_list[m_print_plate_idx]->printer_model_id; }
}
if (source_model != target_model) {
std::vector<std::string> compatible_machine = obj_->get_compatible_machine();
vector<std::string>::iterator it = find(compatible_machine.begin(), compatible_machine.end(), source_model);
if (it == compatible_machine.end()) { return true; }
}
return false;
} }
bool SyncAmsInfoDialog::is_same_nozzle_type(std::string &filament_type, NozzleType &tag_nozzle_type) bool SyncAmsInfoDialog::is_same_nozzle_type(std::string &filament_type, NozzleType &tag_nozzle_type)
@@ -1919,13 +1930,7 @@ bool SyncAmsInfoDialog::is_same_printer_model()
if (obj_ == nullptr) { return result; } if (obj_ == nullptr) { return result; }
PresetBundle *preset_bundle = wxGetApp().preset_bundle; PresetBundle *preset_bundle = wxGetApp().preset_bundle;
const std::string source_model = preset_bundle ? preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle) : std::string(); if (preset_bundle && preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle) != obj_->printer_type) {
if (DevPrinterConfigUtil::is_optional_printer_model_id(source_model) ||
DevPrinterConfigUtil::is_optional_printer_model_id(obj_->printer_type)) {
return true;
}
if (preset_bundle && source_model != obj_->printer_type) {
if ((obj_->is_support_upgrade_kit && obj_->installed_upgrade_kit) && (preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle) == "C12")) { if ((obj_->is_support_upgrade_kit && obj_->installed_upgrade_kit) && (preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle) == "C12")) {
return true; return true;
} }
@@ -2126,7 +2131,7 @@ void SyncAmsInfoDialog::update_user_printer()
std::map<std::string, MachineObject *> option_list; std::map<std::string, MachineObject *> option_list;
// user machine list // user machine list
option_list = dev->get_my_machine_list(dev->get_current_printer_agent_id()); option_list = dev->get_my_machine_list();
// same machine only appear once // same machine only appear once
for (auto it = option_list.begin(); it != option_list.end(); it++) { for (auto it = option_list.begin(); it != option_list.end(); it++) {
@@ -2259,18 +2264,6 @@ void SyncAmsInfoDialog::update_show_status()
reset_timeout(); reset_timeout();
bool has_optional_printer_model = DevPrinterConfigUtil::is_optional_printer_model_id(obj_->printer_type);
if (m_print_type == PrintFromType::FROM_NORMAL) {
if (PresetBundle *preset_bundle = wxGetApp().preset_bundle) {
has_optional_printer_model = has_optional_printer_model ||
DevPrinterConfigUtil::is_optional_printer_model_id(
preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle));
}
} else if (m_print_type == PrintFromType::FROM_SDCARD_VIEW && !m_required_data_plate_data_list.empty()) {
has_optional_printer_model = has_optional_printer_model ||
DevPrinterConfigUtil::is_optional_printer_model_id(m_required_data_plate_data_list[m_print_plate_idx]->printer_model_id);
}
if (!obj_->GetConfig()->SupportPrintAllPlates() && m_print_plate_idx == PLATE_ALL_IDX) { if (!obj_->GetConfig()->SupportPrintAllPlates() && m_print_plate_idx == PLATE_ALL_IDX) {
show_status(PrintDialogStatus::PrintStatusNotSupportedPrintAll); show_status(PrintDialogStatus::PrintStatusNotSupportedPrintAll);
return; return;
@@ -2364,9 +2357,6 @@ void SyncAmsInfoDialog::update_show_status()
} }
} }
} }
if (has_optional_printer_model)
show_status(PrintDialogStatus::PrintStatusOptionalPrinterModel);
} }
bool SyncAmsInfoDialog::has_timelapse_warning() bool SyncAmsInfoDialog::has_timelapse_warning()
@@ -3216,7 +3206,6 @@ SyncAmsInfoDialog::~SyncAmsInfoDialog() {
void SyncAmsInfoDialog::set_info(SyncInfo &info) void SyncAmsInfoDialog::set_info(SyncInfo &info)
{ {
m_input_info = info; m_input_info = info;
reinit_dialog();
} }
void SyncAmsInfoDialog::update_lan_machine_list() void SyncAmsInfoDialog::update_lan_machine_list()
-90
View File
@@ -1,90 +0,0 @@
#include "WebMediaController.hpp"
#include <wx/webview.h>
namespace Slic3r { namespace GUI {
namespace {
void initialize_webview(wxWebView* webview)
{
if (!webview)
return;
webview->SetBackgroundColour(*wxBLACK);
webview->SetPage("<html><head><style>html,body{margin:0;height:100%;background:#000;}</style></head><body></body></html>", "");
}
} // namespace
WebMediaController::WebMediaController(wxWebView* webview)
: m_webview(webview)
{
initialize_webview(m_webview);
}
void WebMediaController::Load(wxURI url)
{
m_url = url.BuildURI().ToStdString();
}
void WebMediaController::set_mode(CameraStreamMode mode)
{
m_stream_mode = mode;
}
void WebMediaController::Play()
{
if (!m_webview)
return;
wxString url = wxString::FromUTF8(m_url);
wxString html = "<html><head><style>"
"html,body{margin:0;height:100%;background:#000;overflow:hidden;}"
"img{width:100%;height:100%;object-fit:contain;display:block;}"
"</style></head><body><img id=\"camera-frame\"";
if (m_stream_mode == CameraStreamMode::http_snapshot) {
html += " data-camera-url=\"" + url +
"\"><script>"
"const cameraFrame=document.getElementById('camera-frame');"
"const cameraUrl=cameraFrame.dataset.cameraUrl;"
"let cameraFrameLoading=false;"
"function refreshCameraFrame(){"
"if(cameraFrameLoading)return;"
"cameraFrameLoading=true;"
"const nextFrame=new Image();"
"nextFrame.onload=function(){cameraFrame.src=nextFrame.src;cameraFrameLoading=false;};"
"nextFrame.onerror=function(){cameraFrameLoading=false;};"
"nextFrame.src=cameraUrl+(cameraUrl.indexOf('?')>=0?'&':'?')+'_orca_frame='+Date.now();"
"}"
"let cameraRefreshInterval=null;"
"function stopCameraRefresh(){"
"if(cameraRefreshInterval!==null){"
"clearInterval(cameraRefreshInterval);"
"cameraRefreshInterval=null;"
"}"
"}"
"refreshCameraFrame();"
"cameraRefreshInterval = setInterval(refreshCameraFrame,200);"
"</script></body></html>";
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()
{
if (m_webview) {
m_webview->RunScript("if(typeof stopCameraRefresh==='function') stopCameraRefresh();");
m_webview->Stop();
m_webview->SetPage("", "about:blank");
m_webview->ClearHistory();
}
m_url.clear();
}
}} // namespace Slic3r::GUI
-31
View File
@@ -1,31 +0,0 @@
#pragma once
#include <slic3r/GUI/IMediaController.hpp>
#include <slic3r/Utils/IPrinterAgent.hpp>
#include <string>
class wxWebView;
namespace Slic3r { namespace GUI {
class WebMediaController : public IMediaController
{
public:
explicit WebMediaController(wxWebView* webview);
void Load(wxURI url) override;
void set_mode(CameraStreamMode mode);
void Play() override;
void Stop() override;
private:
wxWebView* m_webview = nullptr;
std::string m_url;
CameraStreamMode m_stream_mode = CameraStreamMode::http;
};
}} // namespace Slic3r::GUI
-401
View File
@@ -1,401 +0,0 @@
#include "WebRtcMediaController.hpp"
#include <rtc/common.hpp>
#include <rtc/rtc.hpp>
#include <mutex>
#include <wx/mstream.h>
#include <boost/log/trivial.hpp>
namespace {
void init_rtc_logger_once()
{
static std::once_flag flag;
std::call_once(flag, [] {
rtc::InitLogger(rtc::LogLevel::Warning, [](rtc::LogLevel level, std::string message) {
BOOST_LOG_TRIVIAL(trace) << "[rtc:" << static_cast<int>(level) << "] " << message;
});
});
}
} // namespace
namespace Slic3r { namespace GUI {
WebRtcMediaController::WebRtcMediaController(std::function<void(const wxImage&, wxSize)> frame_sink,
std::function<void(Status)> on_status)
: m_frame_sink(std::move(frame_sink))
, m_on_status(std::move(on_status))
{
}
WebRtcMediaController::~WebRtcMediaController()
{
Stop();
}
void WebRtcMediaController::report(Status status)
{
status.epoch = m_epoch.load();
BOOST_LOG_TRIVIAL(info) << "WebRTC: report kind=" << static_cast<int>(status.kind)
<< " code=" << static_cast<int>(status.code) << " epoch=" << status.epoch;
{
std::lock_guard<std::mutex> lock(m_mutex);
if (status.kind == Status::Connecting)
m_state = static_cast<wxMediaState>(4);
else if (status.kind == Status::Playing)
m_state = wxMEDIASTATE_PLAYING;
else
m_state = static_cast<wxMediaState>(3);
}
if (m_on_status)
m_on_status(status);
}
void WebRtcMediaController::set_signalling_channel(std::unique_ptr<ICameraSignalingChannel> channel)
{
m_pending_signaling = std::move(channel);
}
void WebRtcMediaController::Play()
{
std::unique_ptr<ICameraSignalingChannel> channel = std::move(m_pending_signaling);
// Tear down any previous attempt WITHOUT notifying: the Stopped that would
// otherwise be delivered (async, via CallAfter) races the new attempt's
// Connecting and makes the consumer cancel a session that is mid-connect.
teardown(false);
if (!channel)
return;
m_epoch.fetch_add(1);
m_alive.store(true);
{
std::lock_guard<std::mutex> lock(m_mutex);
m_signaling = std::move(channel);
m_jpeg_queue.clear();
m_pending_candidates.clear();
m_remote_description_set = false;
m_video_size = wxDefaultSize;
m_has_frame = false;
m_last_frame_time = {};
}
ICameraSignalingChannel* signaling = nullptr;
{
std::lock_guard<std::mutex> lock(m_mutex);
signaling = m_signaling.get();
}
signaling->on_ready = [this](std::vector<CameraIceServer> servers) {
if (m_alive.load())
on_ready(std::move(servers));
};
signaling->on_answer = [this](std::string sdp) {
if (m_alive.load())
on_answer(std::move(sdp));
};
signaling->on_ice = [this](std::string candidate, std::string mid) {
if (m_alive.load())
on_ice(std::move(candidate), std::move(mid));
};
signaling->on_unavailable = [this](CameraUnavailableReason reason, std::string detail) {
if (m_alive.load())
on_unavailable(reason, std::move(detail));
};
m_decode_thread = std::thread([this] { decode_loop(); });
report({Status::Connecting});
signaling->open();
}
void WebRtcMediaController::Stop()
{
teardown(true);
}
void WebRtcMediaController::teardown(bool notify)
{
const bool was_alive = m_alive.exchange(false);
if (!was_alive && !m_decode_thread.joinable())
return;
m_cond.notify_all();
std::unique_ptr<ICameraSignalingChannel> signaling;
std::shared_ptr<rtc::PeerConnection> peer_connection;
{
std::lock_guard<std::mutex> lock(m_mutex);
signaling = std::move(m_signaling);
peer_connection = std::move(m_peer_connection);
m_data_channel.reset();
}
if (signaling)
signaling->close();
if (peer_connection)
peer_connection->close();
if (m_decode_thread.joinable())
m_decode_thread.join();
{
std::lock_guard<std::mutex> lock(m_mutex);
m_jpeg_queue.clear();
}
if (was_alive && notify)
report({Status::Stopped});
}
wxMediaState WebRtcMediaController::GetState()
{
std::lock_guard<std::mutex> lock(m_mutex);
return m_state;
}
wxSize WebRtcMediaController::GetVideoSize() const
{
std::lock_guard<std::mutex> lock(m_mutex);
return m_video_size;
}
void WebRtcMediaController::bind_data_channel(const std::shared_ptr<rtc::DataChannel>& dc)
{
const std::string label = dc->label();
dc->onOpen([label] { BOOST_LOG_TRIVIAL(info) << "WebRTC: data channel '" << label << "' open"; });
dc->onClosed([label] { BOOST_LOG_TRIVIAL(info) << "WebRTC: data channel '" << label << "' closed"; });
dc->onError([label](std::string e) {
BOOST_LOG_TRIVIAL(warning) << "WebRTC: data channel '" << label << "' error: " << e;
});
dc->onMessage(
[this](rtc::binary data) {
if (m_alive.load())
enqueue_jpeg(std::vector<std::byte>(data.begin(), data.end()));
},
[](rtc::string) {});
}
void WebRtcMediaController::on_ready(std::vector<CameraIceServer> servers)
{
init_rtc_logger_once();
rtc::Configuration configuration;
// Allow complete-JPEG DataChannel messages up to 1 MiB. This value is
// advertised in SDP and becomes the upper bound for frames OrcaSonar can
// send to OrcaSlicer.
configuration.maxMessageSize = 1024 * 1024;
for (const CameraIceServer& server : servers) {
try {
rtc::IceServer ice_server(server.urls);
ice_server.username = server.username;
ice_server.password = server.credential;
configuration.iceServers.emplace_back(std::move(ice_server));
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(warning) << "WebRTC: invalid ICE server: " << e.what();
}
}
BOOST_LOG_TRIVIAL(info) << "WebRTC: creating peer connection with " << configuration.iceServers.size() << " ice servers";
auto peer_connection = std::make_shared<rtc::PeerConnection>(std::move(configuration));
peer_connection->onLocalDescription([this](rtc::Description description) {
if (!m_alive.load())
return;
const std::string sdp(description);
BOOST_LOG_TRIVIAL(info) << "WebRTC: local description ready (" << description.typeString()
<< "), OFFER SDP:\n" << sdp;
std::lock_guard<std::mutex> lock(m_mutex);
if (m_signaling)
m_signaling->send_offer(sdp);
});
peer_connection->onLocalCandidate([this](rtc::Candidate candidate) {
if (!m_alive.load())
return;
std::lock_guard<std::mutex> lock(m_mutex);
if (m_signaling)
m_signaling->send_ice(std::string(candidate), candidate.mid());
});
peer_connection->onStateChange([this](rtc::PeerConnection::State state) {
BOOST_LOG_TRIVIAL(info) << "WebRTC: peer state -> " << static_cast<int>(state);
if (!m_alive.load())
return;
if (state == rtc::PeerConnection::State::Failed || state == rtc::PeerConnection::State::Disconnected)
report({Status::Failed, Status::ICE_FAILED});
});
peer_connection->onGatheringStateChange([](rtc::PeerConnection::GatheringState state) {
BOOST_LOG_TRIVIAL(info) << "WebRTC: gathering state -> " << static_cast<int>(state);
});
// Accept a DataChannel opened by the remote peer (OrcaSonar may create the
// "camera" channel from its side rather than answering the one we offer).
peer_connection->onDataChannel([this](std::shared_ptr<rtc::DataChannel> dc) {
BOOST_LOG_TRIVIAL(info) << "WebRTC: remote opened data channel '" << dc->label() << "'";
bind_data_channel(dc);
std::lock_guard<std::mutex> lock(m_mutex);
m_data_channel = std::move(dc);
});
rtc::DataChannelInit init;
init.reliability.unordered = true;
// init.reliability.maxPacketLifeTime = std::chrono::milliseconds(350);
// Request one complete JPEG frame per DataChannel message. OrcaSonar
// keeps the legacy chunked protocol for clients that omit this property.
init.protocol = "orca-jpeg";
auto data_channel = peer_connection->createDataChannel("camera", init);
if (data_channel)
bind_data_channel(data_channel);
// Camera media is carried as one complete JPEG per DataChannel message;
// no RTP video track or application-level framing is required.
std::shared_ptr<rtc::PeerConnection> peer_for_description;
{
std::lock_guard<std::mutex> lock(m_mutex);
if (!m_alive.load())
return;
m_peer_connection = std::move(peer_connection);
peer_for_description = m_peer_connection;
m_data_channel = std::move(data_channel);
}
if (peer_for_description)
peer_for_description->setLocalDescription();
}
void WebRtcMediaController::on_answer(std::string sdp)
{
std::shared_ptr<rtc::PeerConnection> peer_connection;
{
std::lock_guard<std::mutex> lock(m_mutex);
peer_connection = m_peer_connection;
}
BOOST_LOG_TRIVIAL(info) << "WebRTC: applying remote answer (" << sdp.size() << " bytes), ANSWER SDP:\n" << sdp;
if (!peer_connection)
return;
try {
peer_connection->setRemoteDescription(rtc::Description(sdp, "answer"));
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(warning) << "WebRTC: setRemoteDescription failed: " << e.what();
report({Status::Failed, Status::ICE_FAILED});
return;
}
// Flush any remote candidates that arrived before the answer.
std::vector<std::pair<std::string, std::string>> pending;
{
std::lock_guard<std::mutex> lock(m_mutex);
m_remote_description_set = true;
pending.swap(m_pending_candidates);
}
BOOST_LOG_TRIVIAL(info) << "WebRTC: remote description set, flushing " << pending.size()
<< " buffered candidate(s)";
for (const auto& c : pending) {
try {
peer_connection->addRemoteCandidate(rtc::Candidate(c.first, c.second));
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(warning) << "WebRTC: addRemoteCandidate (buffered) failed: " << e.what();
}
}
}
void WebRtcMediaController::on_ice(std::string candidate, std::string mid)
{
std::shared_ptr<rtc::PeerConnection> peer_connection;
{
std::lock_guard<std::mutex> lock(m_mutex);
if (!m_remote_description_set) {
m_pending_candidates.emplace_back(std::move(candidate), std::move(mid));
return;
}
peer_connection = m_peer_connection;
}
if (!peer_connection)
return;
try {
peer_connection->addRemoteCandidate(rtc::Candidate(candidate, mid));
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(warning) << "WebRTC: addRemoteCandidate failed: " << e.what();
}
}
void WebRtcMediaController::on_unavailable(CameraUnavailableReason reason, std::string detail)
{
BOOST_LOG_TRIVIAL(warning) << "WebRTC camera unavailable: " << detail;
Status::Code code = Status::UNAVAILABLE_ERROR;
if (reason == CameraUnavailableReason::Busy)
code = Status::UNAVAILABLE_BUSY;
else if (reason == CameraUnavailableReason::Disabled)
code = Status::UNAVAILABLE_DISABLED;
else if (reason == CameraUnavailableReason::Closed)
code = Status::SIGNALING_CLOSED;
report({Status::Failed, code});
}
void WebRtcMediaController::enqueue_jpeg(std::vector<std::byte> jpeg)
{
std::lock_guard<std::mutex> lock(m_mutex);
if (m_jpeg_queue.size() >= 4)
m_jpeg_queue.pop_front();
m_jpeg_queue.emplace_back(std::move(jpeg));
m_cond.notify_one();
}
void WebRtcMediaController::deliver_jpeg(std::vector<std::byte> jpeg)
{
const auto now = std::chrono::steady_clock::now();
{
std::lock_guard<std::mutex> lock(m_mutex);
if (m_last_frame_time != std::chrono::steady_clock::time_point{} &&
now - m_last_frame_time < std::chrono::milliseconds(33))
return;
m_last_frame_time = now;
}
wxMemoryInputStream stream(jpeg.data(), jpeg.size());
wxImage image;
if (!image.LoadFile(stream, wxBITMAP_TYPE_JPEG)) {
report({Status::Failed, Status::DECODE_ERROR});
return;
}
bool first_frame = false;
{
std::lock_guard<std::mutex> lock(m_mutex);
m_video_size = image.GetSize();
first_frame = !m_has_frame;
m_has_frame = true;
}
if (m_frame_sink)
m_frame_sink(image, image.GetSize());
if (first_frame)
report({Status::Playing});
}
void WebRtcMediaController::decode_loop()
{
int stall_polls = 0;
std::unique_lock<std::mutex> lock(m_mutex);
while (m_alive.load()) {
const bool woke = m_cond.wait_for(lock, std::chrono::seconds(2), [this] {
return !m_alive.load() || !m_jpeg_queue.empty();
});
if (!m_alive.load())
break;
if (!woke && !m_has_frame) {
const int pc_state = m_peer_connection ? static_cast<int>(m_peer_connection->state()) : -1;
std::string dc = "none";
if (m_data_channel)
dc = "label='" + m_data_channel->label() + "' open=" +
(m_data_channel->isOpen() ? "1" : "0");
lock.unlock();
BOOST_LOG_TRIVIAL(info) << "WebRTC: waiting for frames; peer_state=" << pc_state
<< " data_channel=" << dc;
if (++stall_polls >= 8) { // ~16s connected with no frame -> give up so the UI can retry
report({Status::Failed, Status::TIMEOUT});
lock.lock();
break;
}
lock.lock();
continue;
}
stall_polls = 0;
if (!m_jpeg_queue.empty()) {
auto jpeg = std::move(m_jpeg_queue.front());
m_jpeg_queue.pop_front();
lock.unlock();
deliver_jpeg(std::move(jpeg));
lock.lock();
}
}
}
}} // namespace Slic3r::GUI
-93
View File
@@ -1,93 +0,0 @@
#pragma once
#include "IMediaController.hpp"
#include <slic3r/Utils/ICameraSignalingChannel.hpp>
#include <wx/image.h>
#include <atomic>
#include <chrono>
#include <cstddef>
#include <condition_variable>
#include <cstdint>
#include <deque>
#include <functional>
#include <memory>
#include <mutex>
#include <thread>
#include <vector>
namespace rtc {
class DataChannel;
class PeerConnection;
}
namespace Slic3r { namespace GUI {
class WebRtcMediaController : public IMediaController {
public:
struct Status {
enum Kind { Connecting, Playing, Stopped, Failed } kind = Stopped;
enum Code {
ICE_FAILED,
SIGNALING_CLOSED,
UNAVAILABLE_BUSY,
UNAVAILABLE_ERROR,
UNAVAILABLE_DISABLED,
DECODE_ERROR,
TIMEOUT,
} code = ICE_FAILED;
// Identifies the Play attempt this status belongs to, so the
// consumer can drop CallAfter-queued events from a superseded attempt.
std::uint64_t epoch = 0;
};
WebRtcMediaController(std::function<void(const wxImage&, wxSize)> frame_sink,
std::function<void(Status)> on_status);
~WebRtcMediaController() override;
void set_signalling_channel(std::unique_ptr<ICameraSignalingChannel> channel);
std::uint64_t epoch() const { return m_epoch.load(); }
bool is_active() const { return m_alive.load(); }
void Load(wxURI) override {}
void Play() override;
void Stop() override;
wxMediaState GetState() override;
wxSize GetVideoSize() const override;
private:
void teardown(bool notify);
void report(Status status);
void bind_data_channel(const std::shared_ptr<rtc::DataChannel>& dc);
void on_ready(std::vector<CameraIceServer> servers);
void on_answer(std::string sdp);
void on_ice(std::string candidate, std::string mid);
void on_unavailable(CameraUnavailableReason reason, std::string detail);
void decode_loop();
void enqueue_jpeg(std::vector<std::byte> jpeg);
void deliver_jpeg(std::vector<std::byte> jpeg);
mutable std::mutex m_mutex;
std::condition_variable m_cond;
std::deque<std::vector<std::byte>> m_jpeg_queue;
// Remote candidates can arrive before the answer; libdatachannel rejects
// addRemoteCandidate until a remote description is set, so buffer them.
std::vector<std::pair<std::string, std::string>> m_pending_candidates;
bool m_remote_description_set = false;
std::unique_ptr<ICameraSignalingChannel> m_pending_signaling;
std::unique_ptr<ICameraSignalingChannel> m_signaling;
std::shared_ptr<rtc::PeerConnection> m_peer_connection;
std::shared_ptr<rtc::DataChannel> m_data_channel;
std::thread m_decode_thread;
std::atomic<bool> m_alive{false};
std::atomic<std::uint64_t> m_epoch{0};
wxMediaState m_state = static_cast<wxMediaState>(3);
wxSize m_video_size = wxDefaultSize;
std::function<void(const wxImage&, wxSize)> m_frame_sink;
std::function<void(Status)> m_on_status;
bool m_has_frame = false;
std::chrono::steady_clock::time_point m_last_frame_time{};
};
}} // namespace Slic3r::GUI
+3 -285
View File
@@ -4,14 +4,6 @@
#include "libslic3r/Utils.hpp" #include "libslic3r/Utils.hpp"
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <wx/dcclient.h> #include <wx/dcclient.h>
#include <cstdarg>
#include <cstdlib>
#include <cstring>
#include <mutex>
extern "C" {
#include <libavformat/avformat.h>
#include <libavutil/log.h>
}
#ifdef __WIN32__ #ifdef __WIN32__
#include <versionhelpers.h> #include <versionhelpers.h>
#include <wx/msw/registry.h> #include <wx/msw/registry.h>
@@ -51,13 +43,9 @@ wxMediaCtrl3::~wxMediaCtrl3()
m_thread.join(); m_thread.join();
} }
static void adjust_frame_size(wxSize& frame, wxSize const& video, wxSize const& window);
void wxMediaCtrl3::Load(wxURI url) void wxMediaCtrl3::Load(wxURI url)
{ {
std::unique_lock<std::mutex> lk(m_mutex); std::unique_lock<std::mutex> lk(m_mutex);
if (m_external)
return;
m_video_size = wxDefaultSize; m_video_size = wxDefaultSize;
m_error = 0; m_error = 0;
m_url.reset(new wxURI(url)); m_url.reset(new wxURI(url));
@@ -67,8 +55,6 @@ void wxMediaCtrl3::Load(wxURI url)
void wxMediaCtrl3::Play() void wxMediaCtrl3::Play()
{ {
std::unique_lock<std::mutex> lk(m_mutex); std::unique_lock<std::mutex> lk(m_mutex);
if (m_external)
return;
if (m_state != wxMEDIASTATE_PLAYING) { if (m_state != wxMEDIASTATE_PLAYING) {
m_state = wxMEDIASTATE_PLAYING; m_state = wxMEDIASTATE_PLAYING;
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED); wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
@@ -88,62 +74,6 @@ void wxMediaCtrl3::Stop()
Refresh(); Refresh();
} }
void wxMediaCtrl3::SetExternalFrame(const wxImage& frame, wxSize videoSize)
{
if (!frame.IsOk())
return;
{
std::unique_lock<std::mutex> lk(m_mutex);
if (!m_external)
return;
m_frame = frame;
m_video_size = videoSize.IsFullySpecified() ? videoSize : frame.GetSize();
adjust_frame_size(m_frame_size, m_video_size, GetSize());
}
CallAfter([this] { Refresh(); });
}
#ifdef _WIN32
void wxMediaCtrl3::SetExternalFrame(const wxBitmap& frame, wxSize videoSize)
{
if (!frame.IsOk())
return;
{
std::unique_lock<std::mutex> lk(m_mutex);
if (!m_external)
return;
m_frame = frame;
m_video_size = videoSize.IsFullySpecified() ? videoSize : frame.GetSize();
adjust_frame_size(m_frame_size, m_video_size, GetSize());
}
CallAfter([this] { Refresh(); });
}
#endif
void wxMediaCtrl3::BeginExternalStream()
{
std::unique_lock<std::mutex> lk(m_mutex);
m_external = true;
m_url.reset();
m_active_url.reset();
m_video_size = wxDefaultSize;
m_frame = wxImage(m_idle_image);
m_cond.notify_all();
Refresh();
}
void wxMediaCtrl3::EndExternalStream()
{
std::unique_lock<std::mutex> lk(m_mutex);
m_external = false;
m_url.reset();
m_active_url.reset();
m_video_size = wxDefaultSize;
m_frame = wxImage(m_idle_image);
m_cond.notify_all();
Refresh();
}
void wxMediaCtrl3::SetIdleImage(wxString const &image) void wxMediaCtrl3::SetIdleImage(wxString const &image)
{ {
if (m_idle_image == image) if (m_idle_image == image)
@@ -163,13 +93,13 @@ wxMediaState wxMediaCtrl3::GetState()
return m_state; return m_state;
} }
int wxMediaCtrl3::GetLastError() const int wxMediaCtrl3::GetLastError()
{ {
std::unique_lock<std::mutex> lk(m_mutex); std::unique_lock<std::mutex> lk(m_mutex);
return m_error; return m_error;
} }
wxSize wxMediaCtrl3::GetVideoSize() const wxSize wxMediaCtrl3::GetVideoSize()
{ {
std::unique_lock<std::mutex> lk(m_mutex); std::unique_lock<std::mutex> lk(m_mutex);
return m_video_size; return m_video_size;
@@ -255,210 +185,6 @@ void wxMediaCtrl3::bambu_log(void *ctx, int level, tchar const *msg2)
BOOST_LOG_TRIVIAL(info) << msg.ToUTF8().data(); BOOST_LOG_TRIVIAL(info) << msg.ToUTF8().data();
} }
// FFmpeg's own diagnostics (HTTP status, "Invalid data found", demuxer choice,
// missing stream dimensions, ...) are otherwise swallowed: a failed camera open
// only surfaces as wxMediaCtrl3's generic error code, which MediaPlayCtrl maps to
// the misleading "Player is malfunctioning" string. Forward them to the Orca log
// instead. Verbosity defaults to AV_LOG_VERBOSE and can be raised at runtime with
// ORCA_FFMPEG_LOG_LEVEL=debug|trace|... (or lowered to warning/error/quiet).
static int ffmpeg_log_level_from_env()
{
const char *env = std::getenv("ORCA_FFMPEG_LOG_LEVEL");
if (env == nullptr || *env == '\0')
return AV_LOG_VERBOSE;
const wxString v = wxString(env).Lower();
if (v == "quiet") return AV_LOG_QUIET;
if (v == "panic") return AV_LOG_PANIC;
if (v == "fatal") return AV_LOG_FATAL;
if (v == "error") return AV_LOG_ERROR;
if (v == "warning") return AV_LOG_WARNING;
if (v == "info") return AV_LOG_INFO;
if (v == "verbose") return AV_LOG_VERBOSE;
if (v == "debug") return AV_LOG_DEBUG;
if (v == "trace") return AV_LOG_TRACE;
return AV_LOG_VERBOSE;
}
static void ffmpeg_log_callback(void *avcl, int level, const char *fmt, va_list vl)
{
if (level > av_log_get_level())
return;
thread_local int print_prefix = 1;
char line[1024];
av_log_format_line2(avcl, level, fmt, vl, line, (int) sizeof(line), &print_prefix);
size_t len = std::strlen(line);
while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r' || line[len - 1] == ' '))
line[--len] = '\0';
if (len == 0)
return;
if (level <= AV_LOG_ERROR)
BOOST_LOG_TRIVIAL(error) << "ffmpeg: " << line;
else if (level <= AV_LOG_WARNING)
BOOST_LOG_TRIVIAL(warning) << "ffmpeg: " << line;
else if (level <= AV_LOG_INFO)
BOOST_LOG_TRIVIAL(info) << "ffmpeg: " << line;
else
BOOST_LOG_TRIVIAL(debug) << "ffmpeg: " << line;
}
static void install_ffmpeg_logger()
{
av_log_set_level(ffmpeg_log_level_from_env());
av_log_set_callback(&ffmpeg_log_callback);
}
int wxMediaCtrl3::ffmpeg_interrupt_callback(void *opaque)
{
auto *ctrl = static_cast<wxMediaCtrl3 *>(opaque);
std::lock_guard<std::mutex> lock(ctrl->m_mutex);
return ctrl->m_url != ctrl->m_active_url;
}
int wxMediaCtrl3::PlayFfmpeg(std::shared_ptr<wxURI> const &url, std::unique_lock<std::mutex> &lock)
{
static std::once_flag logger_once;
std::call_once(logger_once, install_ffmpeg_logger);
if (avformat_network_init() < 0)
return 2;
AVFormatContext *format_context = avformat_alloc_context();
if (!format_context) {
avformat_network_deinit();
return 2;
}
format_context->interrupt_callback = {&wxMediaCtrl3::ffmpeg_interrupt_callback, this};
format_context->flags |= AVFMT_FLAG_NOBUFFER;
format_context->max_delay = 0;
m_active_url = url;
auto finish = [&](int error) {
lock.unlock();
avformat_close_input(&format_context);
avformat_network_deinit();
lock.lock();
m_active_url.reset();
return error;
};
const std::string uri = url->BuildURI().ToUTF8().data();
const wxString scheme = url->GetScheme();
const bool http_stream = scheme.CmpNoCase("http") == 0 || scheme.CmpNoCase("https") == 0;
AVDictionary *options = nullptr;
if (http_stream) {
// Live multipart MJPEG. fflags=nobuffer / AVFMT_FLAG_NOBUFFER / max_delay=0
// (set above) are the low-latency levers - they disable the demuxer
// read-ahead queue. probesize / analyzeduration only bound the one-off
// avformat_find_stream_info() at open; a 32-byte budget returned before a
// whole JPEG frame was seen, so width/height came back unset and the open
// was rejected. Give it room to identify one frame (a startup cost only).
// rw_timeout / timeout bound a wedged connect or read so a stale stream
// fails fast and is retried, instead of the reader thread hanging.
// avioflags=direct is deliberately NOT set: unbuffered reads make the
// mpjpeg demuxer emit "Packet corrupt" and bail on any short read across
// a multipart boundary.
av_dict_set(&options, "fflags", "nobuffer", 0);
av_dict_set(&options, "probesize", "5000000", 0);
av_dict_set(&options, "analyzeduration", "1000000", 0);
av_dict_set(&options, "rw_timeout", "5000000", 0);
av_dict_set(&options, "timeout", "5000000", 0);
} else {
av_dict_set(&options, "rtsp_transport", "tcp", 0);
}
lock.unlock();
int error = avformat_open_input(&format_context, uri.c_str(), nullptr, &options);
av_dict_free(&options);
lock.lock();
if (error < 0)
return finish(2);
lock.unlock();
error = avformat_find_stream_info(format_context, nullptr);
lock.lock();
if (error < 0)
return finish(2);
const int video_stream = av_find_best_stream(format_context, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
if (video_stream < 0)
return finish(2);
AVVideoDecoder decoder;
if (decoder.open(*format_context->streams[video_stream]->codecpar) < 0)
return finish(2);
// Prefer the dimensions the container reported. A small probe budget, or a
// camera that doesn't announce a size up front, can leave these unset - in
// that case fill them in from the first frame that decodes (below) rather
// than failing the open outright.
auto apply_video_size = [&](wxSize size) {
if (!size.IsFullySpecified() || size.x <= 0 || size.y <= 0)
return false;
m_video_size = size;
adjust_frame_size(m_frame_size, m_video_size, GetSize());
NotifyStopped();
return true;
};
bool have_size = apply_video_size({format_context->streams[video_stream]->codecpar->width,
format_context->streams[video_stream]->codecpar->height});
int size_probe_frames = 0; // frames spent still waiting for a usable size
AVPacket *packet = av_packet_alloc();
if (!packet)
return finish(2);
while (m_url == url) {
lock.unlock();
error = av_read_frame(format_context, packet);
lock.lock();
if (m_url != url)
break;
if (error < 0)
break;
if (packet->stream_index == video_stream) {
const int decode_error = decoder.decode(*packet);
if (decode_error == 0) {
if (!have_size) {
have_size = apply_video_size(decoder.decoded_frame_size());
if (!have_size) {
av_packet_unref(packet);
// MJPEG yields a sized frame on the first full packet; if
// several seconds of frames never do, treat it as a bad
// stream instead of sitting in "Loading..." forever.
if (++size_probe_frames > 120)
break;
continue;
}
}
auto frame_size = m_frame_size;
lock.unlock();
#ifdef _WIN32
wxBitmap frame;
decoder.toWxBitmap(frame, frame_size);
#else
wxImage frame;
decoder.toWxImage(frame, frame_size);
#endif
lock.lock();
if (m_url != url)
break;
if (frame.IsOk())
m_frame = frame;
if (!m_refresh_pending.exchange(true)) {
CallAfter([this] {
m_refresh_pending.store(false);
Refresh();
});
}
}
}
av_packet_unref(packet);
}
av_packet_free(&packet);
return finish(m_url == url ? 2 : 1);
}
void wxMediaCtrl3::PlayThread() void wxMediaCtrl3::PlayThread()
{ {
using namespace std::chrono_literals; using namespace std::chrono_literals;
@@ -471,16 +197,9 @@ void wxMediaCtrl3::PlayThread()
continue; continue;
if (!url->HasScheme()) if (!url->HasScheme())
break; break;
const wxString scheme = url->GetScheme();
const bool generic_ffmpeg = scheme.CmpNoCase("http") == 0 || scheme.CmpNoCase("https") == 0 ||
scheme.CmpNoCase("rtsp") == 0 || scheme.CmpNoCase("rtsps") == 0;
int error = 0;
if (generic_ffmpeg) {
error = PlayFfmpeg(url, lk);
} else {
lk.unlock(); lk.unlock();
Bambu_Tunnel tunnel = nullptr; Bambu_Tunnel tunnel = nullptr;
error = Bambu_Create(&tunnel, m_url->BuildURI().ToUTF8()); int error = Bambu_Create(&tunnel, m_url->BuildURI().ToUTF8());
if (error == 0) { if (error == 0) {
Bambu_SetLogger(tunnel, &wxMediaCtrl3::bambu_log, this); Bambu_SetLogger(tunnel, &wxMediaCtrl3::bambu_log, this);
error = Bambu_Open(tunnel); error = Bambu_Open(tunnel);
@@ -575,7 +294,6 @@ void wxMediaCtrl3::PlayThread()
tunnel = nullptr; tunnel = nullptr;
lk.lock(); lk.lock();
} }
}
if (m_url == url) if (m_url == url)
m_error = error; m_error = error;
m_frame_size = wxDefaultSize; m_frame_size = wxDefaultSize;
+11 -26
View File
@@ -13,51 +13,41 @@
#include "wx/bitmap.h" #include "wx/bitmap.h"
#include "wx/uri.h" #include "wx/uri.h"
#include "wx/mediactrl.h" #include "wx/mediactrl.h"
#include "IMediaController.hpp"
wxDECLARE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent); wxDECLARE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent);
void wxMediaCtrl_OnSize(wxWindow * ctrl, wxSize const & videoSize, int width, int height); void wxMediaCtrl_OnSize(wxWindow * ctrl, wxSize const & videoSize, int width, int height);
#define BAMBU_DYNAMIC #define BAMBU_DYNAMIC
#include <atomic>
#include <condition_variable> #include <condition_variable>
#include <thread> #include <thread>
#ifndef _WIN32
#include <wx/image.h> #include <wx/image.h>
#endif
#include "Printer/BambuTunnel.h" #include "Printer/BambuTunnel.h"
class AVVideoDecoder; class AVVideoDecoder;
class wxMediaCtrl3 : public wxWindow, public Slic3r::GUI::IMediaController, BambuLib class wxMediaCtrl3 : public wxWindow, BambuLib
{ {
public: public:
wxMediaCtrl3(wxWindow *parent); wxMediaCtrl3(wxWindow *parent);
~wxMediaCtrl3() override; ~wxMediaCtrl3();
void Load(wxURI url) override; void Load(wxURI url);
void Play() override; void Play();
void Stop() override; void Stop();
// Render frames supplied by a controller which owns its own transport.
// The frame is copied while m_mutex is held; callers may release it after
// this method returns.
void SetExternalFrame(const wxImage& frame, wxSize videoSize);
#ifdef _WIN32
void SetExternalFrame(const wxBitmap& frame, wxSize videoSize);
#endif
void BeginExternalStream();
void EndExternalStream();
void SetIdleImage(wxString const & image); void SetIdleImage(wxString const & image);
wxMediaState GetState() override; wxMediaState GetState();
int GetLastError() const override; int GetLastError();
wxSize GetVideoSize() const override; wxSize GetVideoSize();
protected: protected:
DECLARE_EVENT_TABLE() DECLARE_EVENT_TABLE()
@@ -69,10 +59,8 @@ protected:
void DoSetSize(int x, int y, int width, int height, int sizeFlags) override; void DoSetSize(int x, int y, int width, int height, int sizeFlags) override;
static void bambu_log(void *ctx, int level, tchar const *msg); static void bambu_log(void *ctx, int level, tchar const *msg);
static int ffmpeg_interrupt_callback(void *opaque);
void PlayThread(); void PlayThread();
int PlayFfmpeg(std::shared_ptr<wxURI> const &url, std::unique_lock<std::mutex> &lock);
void NotifyStopped(); void NotifyStopped();
@@ -89,15 +77,12 @@ private:
#endif #endif
std::shared_ptr<wxURI> m_url; std::shared_ptr<wxURI> m_url;
std::shared_ptr<wxURI> m_active_url;
bool m_external = false;
std::uint64_t m_last_PTS{0}; std::uint64_t m_last_PTS{0};
std::chrono::system_clock::time_point m_last_PTS_expected; std::chrono::system_clock::time_point m_last_PTS_expected;
std::chrono::system_clock::time_point m_last_PTS_practical; std::chrono::system_clock::time_point m_last_PTS_practical;
mutable std::mutex m_mutex; std::mutex m_mutex;
std::condition_variable m_cond; std::condition_variable m_cond;
std::thread m_thread; std::thread m_thread;
std::atomic_bool m_refresh_pending{false};
}; };
#endif /* wxMediaCtrl3_h */ #endif /* wxMediaCtrl3_h */
+4 -121
View File
@@ -1,13 +1,8 @@
#include "BBLPrinterAgent.hpp" #include "BBLPrinterAgent.hpp"
#include "BBLNetworkPlugin.hpp" #include "BBLNetworkPlugin.hpp"
#include "IPrinterAgent.hpp"
#include "NetworkAgentFactory.hpp" #include "NetworkAgentFactory.hpp"
#include "libslic3r/Utils.hpp" #include "libslic3r/Utils.hpp"
#include "NetworkAgent.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/DeviceCore/DevManager.h"
#include <boost/format.hpp>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <boost/nowide/fstream.hpp> #include <boost/nowide/fstream.hpp>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
@@ -15,10 +10,6 @@ using json = nlohmann::json;
#include <type_traits> #include <type_traits>
#include <unordered_map> #include <unordered_map>
#include <memory>
#include <nlohmann/json.hpp>
#include <cmath>
#include <slic3r/GUI/DeviceManager.hpp>
namespace Slic3r { namespace Slic3r {
@@ -142,7 +133,7 @@ BBLPrinterAgent::~BBLPrinterAgent() = default;
void BBLPrinterAgent::set_cloud_agent(std::shared_ptr<ICloudServiceAgent> cloud) void BBLPrinterAgent::set_cloud_agent(std::shared_ptr<ICloudServiceAgent> cloud)
{ {
(void) cloud; m_cloud_agent = cloud;
// BBL DLL manages tokens internally, so this is just for interface compliance // BBL DLL manages tokens internally, so this is just for interface compliance
} }
@@ -150,97 +141,6 @@ void BBLPrinterAgent::set_cloud_agent(std::shared_ptr<ICloudServiceAgent> cloud)
// Communication // Communication
// ============================================================================ // ============================================================================
int BBLPrinterAgent::command_ams_refresh_rfid(std::string dev_id, int ams_id, int slot_id, int sequence_id, bool lan_mode)
{
nlohmann::json j;
if (ams_id == -1) {
const std::string gcode = (boost::format("M620 R%1% \n") % slot_id).str();
j["print"]["command"] = "gcode_line";
j["print"]["param"] = gcode;
j["print"]["sequence_id"] = std::to_string(sequence_id);
return publish(dev_id, j, lan_mode);
}
j["print"]["command"] = "ams_get_rfid";
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["print"]["ams_id"] = ams_id;
j["print"]["slot_id"] = slot_id;
return publish(dev_id, j, lan_mode);
}
int BBLPrinterAgent::command_ams_calibrate(std::string dev_id, int ams_id, int sequence_id, bool lan_mode)
{
const std::string gcode = (boost::format("M620 C%1% \n") % ams_id).str();
nlohmann::json j;
j["print"]["command"] = "gcode_line";
j["print"]["param"] = gcode;
j["print"]["sequence_id"] = std::to_string(sequence_id);
return publish(dev_id, j, lan_mode);
}
int BBLPrinterAgent::command_ams_select_tray(std::string dev_id, std::string tray_id, int sequence_id, bool lan_mode)
{
const std::string gcode = (boost::format("M620 P%1% \n") % tray_id).str();
nlohmann::json j;
j["print"]["command"] = "gcode_line";
j["print"]["param"] = gcode;
j["print"]["sequence_id"] = std::to_string(sequence_id);
return publish(dev_id, j, lan_mode);
}
int BBLPrinterAgent::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)
{
nlohmann::json j;
j["print"]["sequence_id"] = std::to_string(sequence_id);
if (supports_mqtt_axis_control) {
int dir = input_val > 0 ? 1 : -1;
// i3-arch printers move the bed for Y/Z, so the on-screen direction is
// reversed -- same negation the g-code fallback below applies.
if (!is_core_xy && (axis == "Y" || axis == "Z")) {
dir = -dir;
}
j["print"]["command"] = "xyz_ctrl";
j["print"]["axis"] = axis;
j["print"]["dir"] = dir;
j["print"]["mode"] = (std::abs(input_val) >= 10) ? 1 : 0;
return publish(dev_id, j, lan_mode);
}
double value = input_val;
if (!is_core_xy && (axis == "Y" || axis == "Z")) {
value = -1.0 * input_val;
}
std::string value_str = (boost::format("%.1f") % (value * unit)).str();
std::string gcode;
if (axis == "X" || axis == "Y" || axis == "Z") {
gcode = (boost::format("M211 S \nM211 X1 Y1 Z1\nM1002 push_ref_mode\nG91 \nG1 %1%%2% F%3%\nM1002 pop_ref_mode\nM211 R\n")
% axis % value_str % speed).str();
} else if (axis == "E") {
gcode = (boost::format("M83 \nG0 %1%%2% F%3%\n") % axis % value_str % speed).str();
} else {
return -1;
}
j["print"]["command"] = "gcode_line";
j["print"]["param"] = gcode;
return publish(dev_id, j, lan_mode);
}
int BBLPrinterAgent::publish(const std::string& dev_id, const nlohmann::json& j, bool lan_mode)
{
const int rtn = lan_mode ? send_message_to_printer(dev_id, j.dump(), 0, 0) : send_message(dev_id, j.dump(), 0, 0);
if (rtn == 0) {
BOOST_LOG_TRIVIAL(info) << "publish_json: " << j.dump() << " code: " << rtn;
} else {
BOOST_LOG_TRIVIAL(error) << "publish_json: " << j.dump() << " code: " << rtn;
}
return rtn;
}
int BBLPrinterAgent::send_message(std::string dev_id, std::string json_str, int qos, int flag) int BBLPrinterAgent::send_message(std::string dev_id, std::string json_str, int qos, int flag)
{ {
json_str = from_orca_payload(std::move(json_str)); json_str = from_orca_payload(std::move(json_str));
@@ -265,23 +165,13 @@ int BBLPrinterAgent::send_message(std::string dev_id, std::string json_str, int
return -1; return -1;
} }
int BBLPrinterAgent::connect_printer(const PrinterConnectionParams& params) int BBLPrinterAgent::connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl)
{ {
auto& plugin = BBLNetworkPlugin::instance(); auto& plugin = BBLNetworkPlugin::instance();
auto agent = plugin.get_agent(); auto agent = plugin.get_agent();
auto func = plugin.get_connect_printer(); auto func = plugin.get_connect_printer();
#if !BBL_RELEASE_TO_PUBLIC
const bool use_ssl_for_mqtt = GUI::wxGetApp().app_config &&
GUI::wxGetApp().app_config->get_bool("enable_ssl_for_mqtt");
#else
bool use_ssl_for_mqtt = true;
if (auto* dev_manager = GUI::wxGetApp().getDeviceManager()) {
if (auto* machine = dev_manager->get_my_machine(params.dev_id))
use_ssl_for_mqtt = machine->local_use_ssl;
}
#endif
if (func && agent) { if (func && agent) {
return func(agent, params.dev_id, params.host, params.username, params.password, use_ssl_for_mqtt); return func(agent, dev_id, dev_ip, username, password, use_ssl);
} }
return -1; return -1;
} }
@@ -583,15 +473,8 @@ int BBLPrinterAgent::start_local_print_with_record(PrintParams params, OnUpdateS
int BBLPrinterAgent::start_send_gcode_to_sdcard(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) int BBLPrinterAgent::start_send_gcode_to_sdcard(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn)
{ {
int result = dispatch_start<func_start_send_gcode_to_sdcard_legacy, func_start_send_gcode_to_sdcard_0203>( return dispatch_start<func_start_send_gcode_to_sdcard_legacy, func_start_send_gcode_to_sdcard_0203>(
BBLNetworkPlugin::instance().get_start_send_gcode_to_sdcard(), params, update_fn, cancel_fn, wait_fn); BBLNetworkPlugin::instance().get_start_send_gcode_to_sdcard(), params, update_fn, cancel_fn, wait_fn);
if (result != 0) {
BOOST_LOG_TRIVIAL(error) << "start_send_gcode_to_sdcard failed: result=" << result
<< ", try_emmc_print=" << params.try_emmc_print
<< ", legacy_mode=" << BBLNetworkPlugin::instance().use_legacy_network()
<< ", dev_ip=" << params.dev_ip << ", dev_id=" << params.dev_id;
}
return result;
} }
int BBLPrinterAgent::start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) int BBLPrinterAgent::start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn)
+2 -10
View File
@@ -5,7 +5,6 @@
#include "ICloudServiceAgent.hpp" #include "ICloudServiceAgent.hpp"
#include <string> #include <string>
#include <memory> #include <memory>
#include <nlohmann/json.hpp>
namespace Slic3r { namespace Slic3r {
@@ -29,13 +28,7 @@ public:
// Communication // Communication
int send_message(std::string dev_id, std::string json_str, int qos, int flag) override; int send_message(std::string dev_id, std::string json_str, int qos, int flag) override;
int command_ams_refresh_rfid(std::string dev_id, int ams_id, int slot_id, int sequence_id, bool lan_mode) override; int connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) 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_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;
std::string default_lan_username() const override { return "bblp"; }
int connect_printer(const PrinterConnectionParams& params) override;
int disconnect_printer() override; int disconnect_printer() override;
int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag) override; int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag) override;
@@ -106,8 +99,7 @@ public:
static std::string from_orca_payload(std::string json_text); static std::string from_orca_payload(std::string json_text);
private: private:
// why: the lan/cloud DECISION stays machine-side; keep this mechanical branch in sync with publish_json. std::shared_ptr<ICloudServiceAgent> m_cloud_agent;
int publish(const std::string& dev_id, const nlohmann::json& j, bool lan_mode);
}; };
} // namespace Slic3r } // namespace Slic3r
+1 -4
View File
@@ -282,11 +282,8 @@ bool CrealityPrintAgent::parse_cfs_response(const std::string& response,
return true; return true;
} }
bool CrealityPrintAgent::fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode) bool CrealityPrintAgent::fetch_filament_info(std::string dev_id)
{ {
if (sync_mode != get_filament_sync_mode())
return false;
if (device_info.dev_ip.empty()) { if (device_info.dev_ip.empty()) {
BOOST_LOG_TRIVIAL(warning) BOOST_LOG_TRIVIAL(warning)
<< "CrealityPrintAgent::fetch_filament_info: no device IP, falling back to base agent"; << "CrealityPrintAgent::fetch_filament_info: no device IP, falling back to base agent";
+1 -2
View File
@@ -1,7 +1,6 @@
#ifndef __CREALITY_PRINT_AGENT_HPP__ #ifndef __CREALITY_PRINT_AGENT_HPP__
#define __CREALITY_PRINT_AGENT_HPP__ #define __CREALITY_PRINT_AGENT_HPP__
#include "IPrinterAgent.hpp"
#include "MoonrakerPrinterAgent.hpp" #include "MoonrakerPrinterAgent.hpp"
#include <string> #include <string>
@@ -42,7 +41,7 @@ public:
static AgentInfo get_agent_info_static(); static AgentInfo get_agent_info_static();
AgentInfo get_agent_info() override { return get_agent_info_static(); } AgentInfo get_agent_info() override { return get_agent_info_static(); }
bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode = FilamentSyncMode::pull) override; bool fetch_filament_info(std::string dev_id) override;
// Parse the boxsInfo JSON returned by CrealityPrint::query_boxes_info() into // Parse the boxsInfo JSON returned by CrealityPrint::query_boxes_info() into
// a flat list of loaded slots, plus the count of CFS boxes the printer reports. // a flat list of loaded slots, plus the count of CFS boxes the printer reports.
+6 -57
View File
@@ -1,6 +1,5 @@
#include "Http.hpp" #include "Http.hpp"
#include <atomic>
#include <cstdlib> #include <cstdlib>
#include <functional> #include <functional>
#include <thread> #include <thread>
@@ -15,19 +14,8 @@
#include <curl/curl.h> #include <curl/curl.h>
#include <openssl/err.h> #ifdef OPENSSL_CERT_OVERRIDE
#include <openssl/ssl.h>
#include <openssl/x509.h> #include <openssl/x509.h>
#include <openssl/x509err.h>
#ifdef _WIN32
# ifndef NOMINMAX
# define NOMINMAX
# endif
# include <windows.h>
# include <wincrypt.h>
// wincrypt.h uses this token for a certificate-name property identifier.
# undef X509_NAME
#endif #endif
namespace fs = boost::filesystem; namespace fs = boost::filesystem;
@@ -134,7 +122,7 @@ struct Http::priv
std::string error_buffer; // Used for CURLOPT_ERRORBUFFER std::string error_buffer; // Used for CURLOPT_ERRORBUFFER
std::string headers; std::string headers;
size_t limit; size_t limit;
std::atomic_bool cancel; bool cancel;
std::unique_ptr<form_file> putFile; std::unique_ptr<form_file> putFile;
std::thread io_thread; std::thread io_thread;
@@ -272,9 +260,9 @@ int Http::priv::xfercb(void *userp, curl_off_t dltotal, curl_off_t dlnow, curl_o
self->progressfn(progress, cb_cancel); self->progressfn(progress, cb_cancel);
} }
if (cb_cancel) { self->cancel.store(true); } if (cb_cancel) { self->cancel = true; }
return self->cancel.load(); return self->cancel;
} }
int Http::priv::xfercb_legacy(void *userp, double dltotal, double dlnow, double ultotal, double ulnow) int Http::priv::xfercb_legacy(void *userp, double dltotal, double dlnow, double ultotal, double ulnow)
@@ -485,7 +473,7 @@ void Http::priv::http_perform()
if (res != CURLE_OK) { if (res != CURLE_OK) {
if (res == CURLE_ABORTED_BY_CALLBACK) { if (res == CURLE_ABORTED_BY_CALLBACK) {
if (cancel.load()) { if (cancel) {
// The abort comes from the request being cancelled programatically // The abort comes from the request being cancelled programatically
Progress dummyprogress(0, 0, 0, 0, std::string()); Progress dummyprogress(0, 0, 0, 0, std::string());
bool cancel = true; bool cancel = true;
@@ -796,7 +784,7 @@ void Http::perform_sync()
void Http::cancel() void Http::cancel()
{ {
if (p) { p->cancel.store(true); } if (p) { p->cancel = true; }
} }
void Http::print() const void Http::print() const
@@ -951,45 +939,6 @@ std::string Http::tls_system_cert_store()
return ret; return ret;
} }
void Http::add_platform_root_certificates(SSL_CTX* ssl_context)
{
#ifdef _WIN32
X509_STORE* openssl_store = SSL_CTX_get_cert_store(ssl_context);
if (!openssl_store)
throw std::runtime_error("unable to get OpenSSL certificate store");
const auto load_store = [&](DWORD location) {
HCERTSTORE windows_store = CertOpenStore(CERT_STORE_PROV_SYSTEM_W, 0, 0,
location | CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG,
L"ROOT");
if (!windows_store)
return;
PCCERT_CONTEXT windows_certificate = nullptr;
while ((windows_certificate = CertEnumCertificatesInStore(windows_store, windows_certificate)) != nullptr) {
const unsigned char* encoded = windows_certificate->pbCertEncoded;
X509* certificate = d2i_X509(nullptr, &encoded, static_cast<long>(windows_certificate->cbCertEncoded));
if (!certificate) {
ERR_clear_error();
continue;
}
ERR_clear_error();
if (X509_STORE_add_cert(openssl_store, certificate) != 1)
ERR_clear_error();
X509_free(certificate);
}
CertCloseStore(windows_store, 0);
};
load_store(CERT_SYSTEM_STORE_CURRENT_USER);
load_store(CERT_SYSTEM_STORE_LOCAL_MACHINE);
#else
(void)ssl_context;
#endif
}
std::string Http::url_encode(const std::string &str) std::string Http::url_encode(const std::string &str)
{ {
::CURL *curl = ::curl_easy_init(); ::CURL *curl = ::curl_easy_init();
-6
View File
@@ -11,8 +11,6 @@
#include "libslic3r/Exception.hpp" #include "libslic3r/Exception.hpp"
#include "libslic3r_version.h" #include "libslic3r_version.h"
typedef struct ssl_ctx_st SSL_CTX;
#define MAX_SIZE_TO_FILE 3*1024 #define MAX_SIZE_TO_FILE 3*1024
namespace Slic3r { namespace Slic3r {
@@ -200,10 +198,6 @@ public:
// Return empty string on success or error message on fail. // Return empty string on success or error message on fail.
static std::string tls_global_init(); static std::string tls_global_init();
static std::string tls_system_cert_store(); static std::string tls_system_cert_store();
// Add platform root certificates to a standalone OpenSSL context. This
// supplements set_default_verify_paths() on platforms where OpenSSL does
// not use the native certificate store.
static void add_platform_root_certificates(SSL_CTX* ssl_context);
// converts the given string to an url_encoded_string // converts the given string to an url_encoded_string
static std::string url_encode(const std::string &str); static std::string url_encode(const std::string &str);
@@ -1,39 +0,0 @@
#pragma once
#include <functional>
#include <string>
#include <vector>
namespace Slic3r {
struct CameraIceServer {
std::string urls;
std::string username;
std::string credential;
};
enum class CameraUnavailableReason {
Busy,
Error,
Disabled,
Closed,
};
class ICameraSignalingChannel {
public:
virtual ~ICameraSignalingChannel() = default;
virtual void open() = 0;
virtual void close() = 0;
virtual void send_offer(std::string sdp) = 0;
virtual void send_ice(std::string candidate, std::string mid) = 0;
// These callbacks are invoked by the channel's worker thread. Consumers
// must marshal UI work to the GUI thread themselves.
std::function<void(std::vector<CameraIceServer>)> on_ready;
std::function<void(std::string)> on_answer;
std::function<void(std::string, std::string)> on_ice;
std::function<void(CameraUnavailableReason, std::string)> on_unavailable;
};
} // namespace Slic3r
+1 -12
View File
@@ -1,11 +1,11 @@
#ifndef __I_CLOUD_SERVICE_AGENT_HPP__ #ifndef __I_CLOUD_SERVICE_AGENT_HPP__
#define __I_CLOUD_SERVICE_AGENT_HPP__ #define __I_CLOUD_SERVICE_AGENT_HPP__
#include "ICameraSignalingChannel.hpp"
#include "bambu_networking.hpp" #include "bambu_networking.hpp"
#include "CloudProvider.hpp" #include "CloudProvider.hpp"
#include "../../libslic3r/ProjectTask.hpp" #include "../../libslic3r/ProjectTask.hpp"
#include <string> #include <string>
#include <string_view>
#include <map> #include <map>
#include <vector> #include <vector>
#include <functional> #include <functional>
@@ -328,17 +328,6 @@ public:
*/ */
virtual int get_camera_url(std::string dev_id, std::function<void(std::string)> callback) = 0; virtual int get_camera_url(std::string dev_id, std::function<void(std::string)> callback) = 0;
/**
* Create a camera signaling channel object for P2P camera streaming over WebRTC.
*
*/
virtual std::unique_ptr<ICameraSignalingChannel>
create_camera_signaling_channel(const std::string& dev_id)
{
(void) dev_id;
return nullptr;
}
/** /**
* Fetch staff-picked designs from model mall. * Fetch staff-picked designs from model mall.
*/ */
+10 -186
View File
@@ -11,11 +11,6 @@
#define ORCA_NETWORK_ERR_CAP_NOT_AVAILABLE -7020 // a translation exists; this printer lacks the capability #define ORCA_NETWORK_ERR_CAP_NOT_AVAILABLE -7020 // a translation exists; this printer lacks the capability
#include <string> #include <string>
#include <memory> #include <memory>
#include <vector>
#include <functional>
#include <cmath>
#include <nlohmann/json.hpp>
#include <boost/format.hpp>
namespace Slic3r { namespace Slic3r {
@@ -34,17 +29,6 @@ struct AgentInfo {
std::string description; ///< Brief description of the agent's capabilities, e.g. "Orca printer agent" std::string description; ///< Brief description of the agent's capabilities, e.g. "Orca printer agent"
}; };
struct PrinterConnectionParams
{
std::string dev_id;
std::string host; // host address, usually the IP address without the http/https protocol
std::string port; // optional
std::string username;
std::string password;
bool use_ssl = false; // indicates if http or https
std::string ca_file;
};
/** /**
* FilamentSyncMode - Modes for filament data synchronization. * FilamentSyncMode - Modes for filament data synchronization.
* *
@@ -59,15 +43,6 @@ enum class FilamentSyncMode {
pull ///< On-demand fetch via REST API (blocking call) pull ///< On-demand fetch via REST API (blocking call)
}; };
enum class CameraStreamMode {
none = 0,
http, // LAN or Cloud
https, // LAN or Cloud over TLS
rtsp, // LAN only
webrtc, // Cloud only
http_snapshot // HTTP endpoint returning one image per request
};
/** /**
* IPrinterAgent - Interface for printer operations. * IPrinterAgent - Interface for printer operations.
* *
@@ -109,112 +84,10 @@ public:
*/ */
virtual int send_message(std::string dev_id, std::string json_str, int qos, int flag) = 0; virtual int send_message(std::string dev_id, std::string json_str, int qos, int flag) = 0;
// why: gcode is firmware dialect, not a waist concept - commands whose body is Bambu-dialect
// gcode live on the agent that speaks it; the default is an honest refusal that MachineObject's
// publish funnel turns into a dialog.
virtual int command_ams_refresh_rfid(std::string, int, int, int, bool)
{ return ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED; }
virtual int command_ams_calibrate(std::string, int, int, bool)
{ return ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED; }
virtual int command_ams_select_tray(std::string, std::string, int, bool)
{ return ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED; }
virtual int command_start_camera(std::string)
{ return ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED; }
private:
int publish_command_json(std::string dev_id, const nlohmann::json& j, bool lan_mode)
{
return lan_mode ? send_message_to_printer(dev_id, j.dump(), 0, 0)
: send_message(dev_id, j.dump(), 0, 0);
}
public:
virtual int 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 publish_command_json(dev_id, j, lan_mode);
}
virtual int 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 publish_command_json(dev_id, j, lan_mode);
}
virtual int 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 {
j["print"]["command"] = "gcode_line";
j["print"]["param"] = is_printing ? "G28 X\n" : "G28 \n";
}
return publish_command_json(dev_id, j, lan_mode);
}
virtual int 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"]["sequence_id"] = std::to_string(sequence_id);
if (supports_mqtt_bed_ctrl) {
j["print"]["command"] = "set_bed_temp";
j["print"]["temp"] = temp;
} else {
j["print"]["command"] = "gcode_line";
j["print"]["param"] = (boost::format("M140 S%1%\n") % temp).str();
}
return publish_command_json(dev_id, j, lan_mode);
}
virtual int command_set_nozzle(std::string dev_id, int temp, int sequence_id, bool lan_mode)
{
nlohmann::json j;
j["print"]["command"] = "gcode_line";
j["print"]["param"] = (boost::format("M104 S%1%\n") % temp).str();
j["print"]["sequence_id"] = std::to_string(sequence_id);
return publish_command_json(dev_id, j, lan_mode);
}
virtual 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)
{
(void) supports_mqtt_axis_control;
double value = input_val;
if (!is_core_xy && (axis == "Y" || axis == "Z"))
value = -input_val;
std::string value_str = (boost::format("%.1f") % (value * unit)).str();
std::string gcode;
if (axis == "X" || axis == "Y" || axis == "Z") {
gcode = (boost::format("G91 \nG1 %1%%2% F%3%\nG90 \n") % axis % value_str % speed).str();
} else if (axis == "E") {
gcode = (boost::format("M83 \nG0 %1%%2% F%3%\n") % axis % value_str % speed).str();
} else {
return -1;
}
nlohmann::json j;
j["print"]["command"] = "gcode_line";
j["print"]["param"] = gcode;
j["print"]["sequence_id"] = std::to_string(sequence_id);
return publish_command_json(dev_id, j, lan_mode);
}
/**
* Default LAN account username for this agent's protocol, if it has a fixed one.
* Returns an empty string if the agent has no fixed default (e.g. caller must supply one).
*/
virtual std::string default_lan_username() const { return {}; }
/** /**
* Establish a direct LAN connection to a printer. * Establish a direct LAN connection to a printer.
*/ */
virtual int connect_printer(const PrinterConnectionParams& params) = 0; virtual int connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) = 0;
/** /**
* Tear down the active LAN printer connection. * Tear down the active LAN printer connection.
@@ -232,16 +105,12 @@ public:
/** /**
* Validate current user certificates for the printer. * Validate current user certificates for the printer.
*/ */
virtual int check_cert() { return BAMBU_NETWORK_SUCCESS; } virtual int check_cert() = 0;
/** /**
* Install or refresh device certificate for LAN TLS. * Install or refresh device certificate for LAN TLS.
*/ */
virtual void install_device_cert(std::string dev_id, bool lan_only) virtual void install_device_cert(std::string dev_id, bool lan_only) = 0;
{
(void) dev_id;
(void) lan_only;
}
// ======================================================================== // ========================================================================
// Discovery // Discovery
@@ -257,11 +126,7 @@ public:
/** /**
* Ping the binding endpoint to check printer readiness. * Ping the binding endpoint to check printer readiness.
*/ */
virtual int ping_bind(std::string ping_code) virtual int ping_bind(std::string ping_code) = 0;
{
(void) ping_code;
return BAMBU_NETWORK_SUCCESS;
}
/** /**
* Perform binding detection/handshake on a LAN printer. * Perform binding detection/handshake on a LAN printer.
@@ -271,50 +136,23 @@ public:
/** /**
* Execute the multi-stage printer binding workflow. * Execute the multi-stage printer binding workflow.
*/ */
virtual int bind(std::string dev_ip, std::string dev_id, std::string dev_model, std::string sec_link, virtual 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) = 0;
std::string timezone, bool improved, OnUpdateStatusFn update_fn)
{
(void) dev_ip;
(void) dev_id;
(void) dev_model;
(void) sec_link;
(void) timezone;
(void) improved;
(void) update_fn;
return BAMBU_NETWORK_SUCCESS;
}
/** /**
* Remove the association between account and printer. * Remove the association between account and printer.
*/ */
virtual int unbind(std::string dev_id) virtual int unbind(std::string dev_id) = 0;
{
(void) dev_id;
return BAMBU_NETWORK_SUCCESS;
}
/** /**
* Request a one-time bind ticket from the server. * Request a one-time bind ticket from the server.
*/ */
virtual int request_bind_ticket(std::string* ticket) virtual int request_bind_ticket(std::string* ticket) = 0;
{
if (ticket)
*ticket = {};
return BAMBU_NETWORK_SUCCESS;
}
/** /**
* Fetch the cloud snapshot image captured at a print failure. * Fetch the cloud snapshot image captured at a print failure.
* Returns 0 if the request was dispatched; the image body arrives via callback(body, http_status). * Returns 0 if the request was dispatched; the image body arrives via callback(body, http_status).
*/ */
virtual int get_hms_snapshot(std::string dev_id, std::string file_name, virtual int get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback) = 0;
std::function<void(std::string, int)> callback)
{
(void) dev_id;
(void) file_name;
(void) callback;
return -1;
}
/** /**
* Register callback for fatal HTTP errors. * Register callback for fatal HTTP errors.
@@ -330,7 +168,7 @@ public:
virtual std::string get_user_selected_machine() = 0; virtual std::string get_user_selected_machine() = 0;
/** /**
* Update the selected cloud machine preference. * Update the selected machine preference.
*/ */
virtual int set_user_selected_machine(std::string dev_id) = 0; virtual int set_user_selected_machine(std::string dev_id) = 0;
@@ -446,20 +284,12 @@ public:
*/ */
virtual FilamentSyncMode get_filament_sync_mode() const { return FilamentSyncMode::none; } virtual FilamentSyncMode get_filament_sync_mode() const { return FilamentSyncMode::none; }
/**
* Get the camera stream mode for this agent. This value can be deterministic and derived at
* runtime if the printer supports multiple camera stream modes. E.g. LAN => HTTP/HTTPS/RTSP, Cloud => WebRTC.
*
* @return CameraStreamMode indicating how the camera stream is obtained or used:
*/
virtual CameraStreamMode get_camera_stream_mode() const { return CameraStreamMode::none; }
/** /**
* Refresh filament info from the printer synchronously. * Refresh filament info from the printer synchronously.
* Should only be called when get_filament_sync_mode() returns FilamentSyncMode::pull. * Should only be called when get_filament_sync_mode() returns FilamentSyncMode::pull.
* Populates the MachineObject's DevFilaSystem with fetched filament data. * Populates the MachineObject's DevFilaSystem with fetched filament data.
*/ */
virtual bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode = FilamentSyncMode::pull) { return false; } virtual bool fetch_filament_info(std::string dev_id) { return false; }
/** /**
* Translate one filament id across the printer boundary. * Translate one filament id across the printer boundary.
@@ -470,12 +300,6 @@ public:
*/ */
virtual std::string to_orca_filament_id(const std::string& printer_filament_id) const { return printer_filament_id; } virtual std::string to_orca_filament_id(const std::string& printer_filament_id) const { return printer_filament_id; }
virtual std::string from_orca_filament_id(const std::string& orca_filament_id) const { return orca_filament_id; } virtual std::string from_orca_filament_id(const std::string& orca_filament_id) const { return orca_filament_id; }
/**
* Get the current camera stream URL for this agent's active machine.
* Only meaningful when get_camera_stream_mode() returns an HTTP, HTTPS, or RTSP mode.
*/
virtual std::string get_camera_url() const { return {}; }
}; };
} // namespace Slic3r } // namespace Slic3r
+5 -20
View File
@@ -38,25 +38,12 @@ wxString Moonraker::get_test_failed_msg(wxString &msg) const
std::string Moonraker::make_url(const std::string &path) const std::string Moonraker::make_url(const std::string &path) const
{ {
const bool has_scheme = m_host.find("http://") == 0 || m_host.find("https://") == 0; if (m_host.find("http://") == 0 || m_host.find("https://") == 0) {
const bool use_ssl = m_host.find("https://") == 0; if (m_host.back() == '/')
std::string base = has_scheme ? m_host : ("http://" + m_host); return (boost::format("%1%%2%") % m_host % path).str();
return (boost::format("%1%/%2%") % m_host % path).str();
const size_t authority_start = base.find("://") + 3;
const size_t authority_end = base.find('/', authority_start);
const std::string authority = base.substr(authority_start, authority_end - authority_start);
const size_t closing_bracket = authority.rfind(']');
const bool has_port = closing_bracket != std::string::npos
? closing_bracket + 1 < authority.size() && authority[closing_bracket + 1] == ':'
: authority.find(':') != std::string::npos;
if (!has_port) {
const std::string default_port = use_ssl ? ":7130" : ":7125";
base.insert(authority_end == std::string::npos ? base.size() : authority_end, default_port);
} }
return (boost::format("http://%1%/%2%") % m_host % path).str();
if (base.back() == '/')
return (boost::format("%1%%2%") % base % path).str();
return (boost::format("%1%/%2%") % base % path).str();
} }
void Moonraker::set_auth(Http &http) const void Moonraker::set_auth(Http &http) const
@@ -66,8 +53,6 @@ void Moonraker::set_auth(Http &http) const
// filled the user/password fields — those are PrusaLink/OctoPrint conventions. // filled the user/password fields — those are PrusaLink/OctoPrint conventions.
if (!m_apikey.empty()) if (!m_apikey.empty())
http.header("X-Api-Key", m_apikey); http.header("X-Api-Key", m_apikey);
const bool use_ssl = m_host.find("https://") == 0;
http.tls_verify(use_ssl);
if (!m_cafile.empty()) if (!m_cafile.empty())
http.ca_file(m_cafile); http.ca_file(m_cafile);
} }
File diff suppressed because it is too large Load Diff
+32 -134
View File
@@ -9,48 +9,11 @@
#include <set> #include <set>
#include <string> #include <string>
#include <thread> #include <thread>
#include <chrono>
#include <condition_variable>
#include <deque>
#include <functional>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
namespace Slic3r { namespace Slic3r {
class Http;
bool moonraker_is_light_name(const std::string& name);
class MoonrakerWebsocket
{
public:
enum class ReadResult
{
message,
timeout,
closed,
error,
};
MoonrakerWebsocket(bool secure, std::string api_key, std::string ca_file);
~MoonrakerWebsocket();
void connect(const std::string& host, const std::string& port, std::chrono::seconds timeout);
void tls_handshake(const std::string& host);
void handshake(const std::string& host, const std::string& target);
void text(bool enabled);
void write(const std::string& body);
ReadResult read(std::string& payload, std::string& error_message);
void close();
void expires_after(std::chrono::seconds timeout);
void abort();
private:
struct Impl;
std::unique_ptr<Impl> m_impl;
};
class MoonrakerPrinterAgent : public IPrinterAgent class MoonrakerPrinterAgent : public IPrinterAgent
{ {
public: public:
@@ -65,15 +28,24 @@ public:
// Communication // Communication
int send_message(std::string dev_id, std::string json_str, int qos, int flag) override; int send_message(std::string dev_id, std::string json_str, int qos, int flag) override;
int connect_printer(const PrinterConnectionParams& params) override; int connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) override;
int disconnect_printer() override; int disconnect_printer() override;
int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag) override; int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag) override;
// Certificates
int check_cert() override;
void install_device_cert(std::string dev_id, bool lan_only) override;
// Discovery // Discovery
bool start_discovery(bool start, bool sending) override; bool start_discovery(bool start, bool sending) override;
// Binding // Binding
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 unbind(std::string dev_id) 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 set_server_callback(OnServerErrFn fn) override; int set_server_callback(OnServerErrFn fn) override;
// Machine Selection // Machine Selection
@@ -96,20 +68,12 @@ public:
int set_on_local_connect_fn(OnLocalConnectedFn fn) override; int set_on_local_connect_fn(OnLocalConnectedFn fn) override;
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;
bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode = FilamentSyncMode::pull) override;
CameraStreamMode get_camera_stream_mode() const override; // Pull-mode agent (on-demand filament sync)
std::string get_camera_url() const override; FilamentSyncMode get_filament_sync_mode() const override { return FilamentSyncMode::pull; }
bool fetch_filament_info(std::string dev_id) override;
protected: protected:
struct ConnectionSettings
{
std::string dev_id;
std::string base_url;
std::string api_key;
bool use_ssl = false;
std::string ca_file;
};
struct MoonrakerDeviceInfo struct MoonrakerDeviceInfo
{ {
std::string dev_id; std::string dev_id;
@@ -121,9 +85,7 @@ protected:
std::string dev_name; std::string dev_name;
std::string version; std::string version;
std::string klippy_state; std::string klippy_state;
float nozzle_diameter = 0.0f;
bool use_ssl = false; bool use_ssl = false;
std::string ca_file;
} device_info; } device_info;
// Tray data for AMS payload building // Tray data for AMS payload building
@@ -141,24 +103,15 @@ protected:
void build_ams_payload(int ams_count, int max_lane_index, const std::vector<AmsTrayData>& trays); void build_ams_payload(int ams_count, int max_lane_index, const std::vector<AmsTrayData>& trays);
// Methods that derived classes may need to override or access // Methods that derived classes may need to override or access
virtual bool init_device_info(const PrinterConnectionParams& params); 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 ConnectionSettings& connection, MoonrakerDeviceInfo& info, std::string& error) const; virtual bool fetch_device_info(const std::string& base_url, const std::string& api_key, MoonrakerDeviceInfo& info, std::string& error) const;
ConnectionSettings get_connection_settings() const;
void configure_http(Http& http, const ConnectionSettings& connection) const;
static float parse_nozzle_diameter(const nlohmann::json& response);
// State access for derived classes // State access for derived classes
mutable std::recursive_mutex state_mutex; mutable std::recursive_mutex state_mutex;
// Counts detached fetch_filament_info() background threads currently touching `this`
// (see QidiPrinterAgent::fetch_filament_info). Those threads hold a raw `this` with no
// other lifetime protection, so the destructor waits for this to reach 0 before any part
// of the object is torn down — see ~MoonrakerPrinterAgent().
std::atomic<int> filament_fetch_in_flight{0};
// Helpers // Helpers
bool is_numeric(const std::string& value); bool is_numeric(const std::string& value);
std::string normalize_base_url(bool use_ssl, const std::string& host, const std::string& port); std::string normalize_base_url(std::string host, const std::string& port);
std::string sanitize_filename(const std::string& filename); std::string sanitize_filename(const std::string& filename);
std::string join_url(const std::string& base_url, const std::string& path) const; std::string join_url(const std::string& base_url, const std::string& path) const;
@@ -168,42 +121,23 @@ protected:
// Map filament type to OrcaFilamentLibrary preset ID for AMS sync compatibility // Map filament type to OrcaFilamentLibrary preset ID for AMS sync compatibility
static std::string map_filament_type_to_generic_id(const std::string& filament_type); static std::string map_filament_type_to_generic_id(const std::string& filament_type);
// Send a G-code script via Moonraker (/printer/gcode/script)
bool send_gcode(const std::string& dev_id, const std::string& gcode) const;
bool send_gcode(const std::string& dev_id, const std::string& gcode,
const ConnectionSettings& connection) const;
bool post_print_action(const std::string& action) const;
bool post_print_action(const std::string& action,
const ConnectionSettings& connection) const;
bool send_ws_rpc(const std::string& method, const nlohmann::json& params);
virtual void on_status_loop_tick(const std::string& dev_id) {}
// Queue work that may use agent state. The command worker is joined during
// destruction, so queued commands cannot outlive the agent.
void enqueue_command(std::function<void()> fn);
private: private:
int handle_request(const std::string& dev_id, const std::string& json_str); int handle_request(const std::string& dev_id, const std::string& json_str);
int send_version_info(const std::string& dev_id); int send_version_info(const std::string& dev_id);
int send_access_code(const std::string& dev_id); int send_access_code(const std::string& dev_id);
bool fetch_object_list(const ConnectionSettings& connection, std::set<std::string>& objects, std::string& error) const; bool fetch_object_list(const std::string& base_url, const std::string& api_key, std::set<std::string>& objects, std::string& error) const;
bool query_printer_status(const ConnectionSettings& connection, nlohmann::json& status, std::string& error) const; bool query_printer_status(const std::string& base_url, const std::string& api_key, nlohmann::json& status, std::string& error) const;
bool send_gcode_sync(const std::string& dev_id, const std::string& gcode) const; bool send_gcode(const std::string& dev_id, const std::string& gcode) const;
void send_gcode_async(const std::string& dev_id, const std::string& gcode,
std::function<void(bool)> on_result = {}) const;
void announce_printhost_device(); void announce_printhost_device();
void dispatch_local_connect(int state, const std::string& dev_id, const std::string& msg); void dispatch_local_connect(int state, const std::string& dev_id, const std::string& msg);
void dispatch_printer_connected(const std::string& dev_id); void dispatch_printer_connected(const std::string& dev_id);
void dispatch_message(const std::string& dev_id, const std::string& payload); void dispatch_message(const std::string& dev_id, const std::string& payload);
void start_status_stream(const std::string& dev_id, ConnectionSettings connection); void start_status_stream(const std::string& dev_id, const std::string& base_url, const std::string& api_key);
void stop_status_stream(); void stop_status_stream();
void run_status_stream(std::string dev_id, ConnectionSettings connection); void run_status_stream(std::string dev_id, std::string base_url, std::string api_key);
void handle_ws_message(std::string dev_id, std::string payload, ConnectionSettings connection); void handle_ws_message(const std::string& dev_id, const std::string& payload);
void refresh_thumbnail_url(const ConnectionSettings& connection);
void update_status_cache(const nlohmann::json& updates); void update_status_cache(const nlohmann::json& updates);
nlohmann::json build_print_payload_locked() const; nlohmann::json build_print_payload_locked() const;
@@ -214,28 +148,22 @@ private:
// File upload // File upload
bool upload_gcode(const std::string& local_path, const std::string& filename, bool upload_gcode(const std::string& local_path, const std::string& filename,
const ConnectionSettings& connection, const std::string& base_url, const std::string& api_key,
OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn); OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn);
// Start a print of a previously uploaded G-code file (path relative to the // JSON-RPC helper
// Moonraker gcodes root). bool send_jsonrpc_command(const std::string& base_url, const std::string& api_key,
bool start_print_file(const ConnectionSettings& connection, const nlohmann::json& request, std::string& response) const;
const std::string& filename, std::string& error_msg) const;
// Connection thread management // Connection thread management
void perform_connection_async(const std::string& dev_id, void perform_connection_async(const std::string& dev_id,
ConnectionSettings connection, const std::string& base_url,
const std::string& api_key,
uint64_t generation); uint64_t generation);
// why: a printer with no /server/webcams/list entry can still name its stream directly;
// subclasses (e.g. printers with a fixed webcam path) can override this instead.
virtual std::string webcam_stream_override(const std::string& base_url) const { return {}; }
void refresh_webcam_info() const;
bool fetch_webcam_info(const ConnectionSettings& connection, uint64_t generation) const;
// System-specific filament fetch methods // System-specific filament fetch methods
bool fetch_hh_filament_info(const ConnectionSettings& connection, std::vector<AmsTrayData>& trays, int& max_lane_index); bool fetch_hh_filament_info(std::vector<AmsTrayData>& trays, int& max_lane_index);
bool fetch_moonraker_filament_data(const ConnectionSettings& connection, std::vector<AmsTrayData>& trays, int& max_lane_index); bool fetch_moonraker_filament_data(std::vector<AmsTrayData>& trays, int& max_lane_index);
// JSON helper methods // JSON helper methods
static std::string safe_json_string(const nlohmann::json& obj, const char* key); static std::string safe_json_string(const nlohmann::json& obj, const char* key);
@@ -261,38 +189,15 @@ private:
mutable std::recursive_mutex payload_mutex; mutable std::recursive_mutex payload_mutex;
nlohmann::json status_cache; nlohmann::json status_cache;
// note: guarded by payload_mutex; filled by refresh_thumbnail_url(), empty url = looked up, none found
std::string thumbnail_filename;
std::string thumbnail_url;
mutable std::string webcam_stream_url;
mutable CameraStreamMode webcam_stream_mode = CameraStreamMode::none;
mutable uint64_t webcam_info_last_lookup_ms = 0;
mutable uint64_t webcam_info_generation = 0;
unsigned thumbnail_lookup_attempts = 0;
static constexpr uint64_t WEBCAM_INFO_REFRESH_INTERVAL_MS = 1000;
std::atomic<int> next_jsonrpc_id{1}; std::atomic<int> next_jsonrpc_id{1};
std::set<std::string> available_objects; // Track for feature detection std::set<std::string> available_objects; // Track for feature detection
bool assumed_light_on = false;
std::atomic<bool> ws_stop{false}; std::atomic<bool> ws_stop{false};
std::atomic<bool> ws_reconnect_requested{false}; // Flag to trigger reconnection std::atomic<bool> ws_reconnect_requested{false}; // Flag to trigger reconnection
std::atomic<uint64_t> ws_last_emit_ms{0}; std::atomic<uint64_t> ws_last_emit_ms{0};
std::thread ws_thread; std::thread ws_thread;
// stop_status_stream() invokes ws_abort_io to wake a blocked synchronous
// ws.read()/ws.write()/handshake in run_status_stream(): ws_stop is only
// observed between reads, and Beast's expires_after() does not bound
// synchronous operations.
std::mutex ws_abort_mutex;
std::function<void()> ws_abort_io; // guarded by ws_abort_mutex
// AMS/filament refresh cadence, independent of telemetry dispatch so a steady
// stream of status updates can't starve it (ws_last_emit_ms is reset by those).
static constexpr uint64_t AMS_REFRESH_INTERVAL_MS = 10000;
std::atomic<uint64_t> ams_last_fetch_ms{0};
// Throttling configuration for WebSocket updates // Throttling configuration for WebSocket updates
// Critical changes (state transitions) dispatch immediately; telemetry is throttled // Critical changes (state transitions) dispatch immediately; telemetry is throttled
static constexpr uint64_t STATUS_UPDATE_INTERVAL_MS = 1000; // 1 update/sec for telemetry static constexpr uint64_t STATUS_UPDATE_INTERVAL_MS = 1000; // 1 update/sec for telemetry
@@ -302,14 +207,7 @@ private:
// Connection thread management // Connection thread management
std::atomic<uint64_t> connect_generation{0}; std::atomic<uint64_t> connect_generation{0};
std::thread connect_thread; std::thread connect_thread;
mutable std::recursive_mutex connect_mutex; std::recursive_mutex connect_mutex;
void run_command_worker();
std::thread cmd_thread;
std::deque<std::function<void()>> cmd_queue;
std::mutex cmd_mutex;
std::condition_variable cmd_cv;
bool cmd_stop = false;
}; };
} // namespace Slic3r } // namespace Slic3r
+17 -153
View File
@@ -1,10 +1,11 @@
#include "NetworkAgent.hpp" #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <set>
#include <algorithm>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <nlohmann/json.hpp> #include "libslic3r/Utils.hpp"
#include "IPrinterAgent.hpp" #include "NetworkAgent.hpp"
#include "BBLNetworkPlugin.hpp" #include "BBLNetworkPlugin.hpp"
namespace Slic3r { namespace Slic3r {
@@ -114,8 +115,6 @@ void NetworkAgent::add_cloud_agent(const std::string& provider, std::shared_ptr<
void NetworkAgent::set_printer_agent(std::shared_ptr<IPrinterAgent> printer_agent) void NetworkAgent::set_printer_agent(std::shared_ptr<IPrinterAgent> printer_agent)
{ {
m_user_machine_list_generation.fetch_add(1);
// Disconnect all callbacks from the old agent // Disconnect all callbacks from the old agent
auto old_printer_agent = m_printer_agent; auto old_printer_agent = m_printer_agent;
@@ -433,26 +432,10 @@ int NetworkAgent::check_user_task_report(int* task_id, bool* printable, const st
int NetworkAgent::get_user_print_info(unsigned int* http_code, std::string* http_body, const std::string& provider) int NetworkAgent::get_user_print_info(unsigned int* http_code, std::string* http_body, const std::string& provider)
{ {
const std::string request_agent_id = m_printer_agent_id;
const std::uint64_t request_generation = m_user_machine_list_generation.fetch_add(1) + 1;
const auto cloud_agent = get_cloud_agent(provider); const auto cloud_agent = get_cloud_agent(provider);
if (!cloud_agent) if (cloud_agent)
return cloud_agent->get_user_print_info(http_code, http_body);
return -1; return -1;
const int result = cloud_agent->get_user_print_info(http_code, http_body);
if (result == 0 && http_body) {
try {
nlohmann::json response = nlohmann::json::parse(*http_body);
response["provider"] = provider;
response["agent_id"] = request_agent_id;
response["generation"] = request_generation;
*http_body = response.dump();
}
catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " metadata injection exception=" << e.what();
}
}
return result;
} }
int NetworkAgent::get_user_tasks(TaskQueryParams params, std::string* http_body, const std::string& provider) int NetworkAgent::get_user_tasks(TaskQueryParams params, std::string* http_body, const std::string& provider)
@@ -532,15 +515,6 @@ int NetworkAgent::get_camera_url(std::string dev_id, std::function<void(std::str
return -1; return -1;
} }
std::unique_ptr<ICameraSignalingChannel>
NetworkAgent::create_camera_signaling_channel(const std::string& dev_id, const std::string& provider)
{
const auto cloud_agent = get_cloud_agent(provider);
if (cloud_agent)
return cloud_agent->create_camera_signaling_channel(dev_id);
return nullptr;
}
int NetworkAgent::get_design_staffpick(int offset, int limit, std::function<void(std::string)> callback, const std::string& provider) int NetworkAgent::get_design_staffpick(int offset, int limit, std::function<void(std::string)> callback, const std::string& provider)
{ {
const auto cloud_agent = get_cloud_agent(provider); const auto cloud_agent = get_cloud_agent(provider);
@@ -793,81 +767,10 @@ int NetworkAgent::send_message(std::string dev_id, std::string json_str, int qos
return -1; return -1;
} }
int NetworkAgent::command_ams_refresh_rfid(std::string dev_id, int ams_id, int slot_id, int sequence_id, bool lan_mode) int NetworkAgent::connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl)
{ {
if (m_printer_agent) if (m_printer_agent)
return m_printer_agent->command_ams_refresh_rfid(dev_id, ams_id, slot_id, sequence_id, lan_mode); return m_printer_agent->connect_printer(dev_id, dev_ip, username, password, use_ssl);
return -1;
}
int NetworkAgent::command_ams_calibrate(std::string dev_id, int ams_id, int sequence_id, bool lan_mode)
{
if (m_printer_agent)
return m_printer_agent->command_ams_calibrate(dev_id, ams_id, sequence_id, lan_mode);
return -1;
}
int NetworkAgent::command_ams_select_tray(std::string dev_id, std::string tray_id, int sequence_id, bool lan_mode)
{
if (m_printer_agent)
return m_printer_agent->command_ams_select_tray(dev_id, tray_id, sequence_id, lan_mode);
return -1;
}
int NetworkAgent::command_start_camera(std::string dev_id)
{
if (m_printer_agent)
return m_printer_agent->command_start_camera(dev_id);
return -1;
}
int NetworkAgent::command_xyz_abs(std::string dev_id, int sequence_id, bool lan_mode)
{
if (m_printer_agent)
return m_printer_agent->command_xyz_abs(dev_id, sequence_id, lan_mode);
return -1;
}
int NetworkAgent::command_auto_leveling(std::string dev_id, int sequence_id, bool lan_mode)
{
if (m_printer_agent)
return m_printer_agent->command_auto_leveling(dev_id, sequence_id, lan_mode);
return -1;
}
int NetworkAgent::command_go_home(std::string dev_id, bool is_printing, bool supports_mqtt_homing, int sequence_id, bool lan_mode)
{
if (m_printer_agent)
return m_printer_agent->command_go_home(dev_id, is_printing, supports_mqtt_homing, sequence_id, lan_mode);
return -1;
}
int NetworkAgent::command_set_bed(std::string dev_id, int temp, bool supports_mqtt_bed_ctrl, int sequence_id, bool lan_mode)
{
if (m_printer_agent)
return m_printer_agent->command_set_bed(dev_id, temp, supports_mqtt_bed_ctrl, sequence_id, lan_mode);
return -1;
}
int NetworkAgent::command_set_nozzle(std::string dev_id, int temp, int sequence_id, bool lan_mode)
{
if (m_printer_agent)
return m_printer_agent->command_set_nozzle(dev_id, temp, sequence_id, lan_mode);
return -1;
}
int NetworkAgent::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)
{
if (m_printer_agent)
return m_printer_agent->command_axis_control(dev_id, axis, unit, input_val, speed, is_core_xy, supports_mqtt_axis_control, sequence_id, lan_mode);
return -1;
}
int NetworkAgent::connect_printer(const PrinterConnectionParams& params)
{
if (m_printer_agent)
return m_printer_agent->connect_printer(params);
return -1; return -1;
} }
@@ -885,13 +788,6 @@ int NetworkAgent::send_message_to_printer(std::string dev_id, std::string json_s
return -1; return -1;
} }
std::string NetworkAgent::default_lan_username() const
{
if (m_printer_agent)
return m_printer_agent->default_lan_username();
return {};
}
int NetworkAgent::check_cert() int NetworkAgent::check_cert()
{ {
if (m_printer_agent) if (m_printer_agent)
@@ -950,14 +846,8 @@ std::string NetworkAgent::get_user_selected_machine()
int NetworkAgent::set_user_selected_machine(std::string dev_id) int NetworkAgent::set_user_selected_machine(std::string dev_id)
{ {
BOOST_LOG_TRIVIAL(trace) << "NetworkAgent::set_user_selected_machine: dev_id=" << dev_id if (m_printer_agent)
<< " printer_agent=" << (m_printer_agent ? m_printer_agent->get_agent_info().id : "<null>"); return m_printer_agent->set_user_selected_machine(dev_id);
if (m_printer_agent) {
const int result = m_printer_agent->set_user_selected_machine(dev_id);
BOOST_LOG_TRIVIAL(trace) << "NetworkAgent::set_user_selected_machine: result=" << result;
return result;
}
BOOST_LOG_TRIVIAL(warning) << "NetworkAgent::set_user_selected_machine: no printer agent";
return -1; return -1;
} }
@@ -977,27 +867,15 @@ int NetworkAgent::stop_subscribe(std::string module)
int NetworkAgent::add_subscribe(std::vector<std::string> dev_list) int NetworkAgent::add_subscribe(std::vector<std::string> dev_list)
{ {
BOOST_LOG_TRIVIAL(trace) << "NetworkAgent::add_subscribe: count=" << dev_list.size() if (m_printer_agent)
<< " printer_agent=" << (m_printer_agent ? m_printer_agent->get_agent_info().id : "<null>"); return m_printer_agent->add_subscribe(std::move(dev_list));
if (m_printer_agent) {
const int result = m_printer_agent->add_subscribe(std::move(dev_list));
BOOST_LOG_TRIVIAL(trace) << "NetworkAgent::add_subscribe: result=" << result;
return result;
}
BOOST_LOG_TRIVIAL(warning) << "NetworkAgent::add_subscribe: no printer agent";
return -1; return -1;
} }
int NetworkAgent::del_subscribe(std::vector<std::string> dev_list) int NetworkAgent::del_subscribe(std::vector<std::string> dev_list)
{ {
BOOST_LOG_TRIVIAL(trace) << "NetworkAgent::del_subscribe: count=" << dev_list.size() if (m_printer_agent)
<< " printer_agent=" << (m_printer_agent ? m_printer_agent->get_agent_info().id : "<null>"); return m_printer_agent->del_subscribe(std::move(dev_list));
if (m_printer_agent) {
const int result = m_printer_agent->del_subscribe(std::move(dev_list));
BOOST_LOG_TRIVIAL(trace) << "NetworkAgent::del_subscribe: result=" << result;
return result;
}
BOOST_LOG_TRIVIAL(warning) << "NetworkAgent::del_subscribe: no printer agent";
return -1; return -1;
} }
@@ -1043,10 +921,10 @@ FilamentSyncMode NetworkAgent::get_filament_sync_mode() const
return FilamentSyncMode::none; return FilamentSyncMode::none;
} }
bool NetworkAgent::fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode) bool NetworkAgent::fetch_filament_info(std::string dev_id)
{ {
if (m_printer_agent) { if (m_printer_agent) {
return m_printer_agent->fetch_filament_info(dev_id, sync_mode); return m_printer_agent->fetch_filament_info(dev_id);
} }
return false; return false;
} }
@@ -1065,20 +943,6 @@ std::string NetworkAgent::from_orca_filament_id(const std::string& orca_filament
return orca_filament_id; return orca_filament_id;
} }
CameraStreamMode NetworkAgent::get_camera_stream_mode() const
{
if (m_printer_agent)
return m_printer_agent->get_camera_stream_mode();
return CameraStreamMode::none;
}
std::string NetworkAgent::get_local_camera_stream_url() const
{
if (m_printer_agent)
return m_printer_agent->get_camera_url();
return {};
}
int NetworkAgent::request_bind_ticket(std::string* ticket) int NetworkAgent::request_bind_ticket(std::string* ticket)
{ {
if (m_printer_agent) if (m_printer_agent)
+2 -25
View File
@@ -2,22 +2,16 @@
#define __NETWORK_Agent_HPP__ #define __NETWORK_Agent_HPP__
#include "bambu_networking.hpp" #include "bambu_networking.hpp"
#include "libslic3r/ProjectTask.hpp" #include "libslic3r/ProjectTask.hpp"
#include "ICloudServiceAgent.hpp" #include "ICloudServiceAgent.hpp"
#include "IPrinterAgent.hpp" #include "IPrinterAgent.hpp"
#include <map> #include <map>
#include <atomic>
#include <cstdint>
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
namespace Slic3r { namespace Slic3r {
class IPrinterAgent;
// Forward declaration // Forward declaration
class BBLNetworkPlugin; class BBLNetworkPlugin;
@@ -56,7 +50,6 @@ public:
// Sub-agent accessors // Sub-agent accessors
std::shared_ptr<ICloudServiceAgent> get_cloud_agent(const std::string& provider = ORCA_CLOUD_PROVIDER) const; std::shared_ptr<ICloudServiceAgent> get_cloud_agent(const std::string& provider = ORCA_CLOUD_PROVIDER) const;
std::shared_ptr<IPrinterAgent> get_printer_agent() const { return m_printer_agent; } std::shared_ptr<IPrinterAgent> get_printer_agent() const { return m_printer_agent; }
std::uint64_t get_user_machine_list_generation() const { return m_user_machine_list_generation.load(); }
// Shared agent management // Shared agent management
void add_cloud_agent(const std::string& provider, std::shared_ptr<ICloudServiceAgent> agent); void add_cloud_agent(const std::string& provider, std::shared_ptr<ICloudServiceAgent> agent);
@@ -116,7 +109,6 @@ public:
int query_bind_status(std::vector<std::string> query_list, unsigned int* http_code, std::string* http_body, const std::string& provider = ORCA_CLOUD_PROVIDER); int query_bind_status(std::vector<std::string> query_list, unsigned int* http_code, std::string* http_body, const std::string& provider = ORCA_CLOUD_PROVIDER);
int modify_printer_name(std::string dev_id, std::string dev_name, const std::string& provider = ORCA_CLOUD_PROVIDER); int modify_printer_name(std::string dev_id, std::string dev_name, const std::string& provider = ORCA_CLOUD_PROVIDER);
int get_camera_url(std::string dev_id, std::function<void(std::string)> callback, const std::string& provider = ORCA_CLOUD_PROVIDER); int get_camera_url(std::string dev_id, std::function<void(std::string)> callback, const std::string& provider = ORCA_CLOUD_PROVIDER);
std::unique_ptr<ICameraSignalingChannel> create_camera_signaling_channel(const std::string& dev_id, const std::string& provider = ORCA_CLOUD_PROVIDER);
int get_design_staffpick(int offset, int limit, std::function<void(std::string)> callback, const std::string& provider = ORCA_CLOUD_PROVIDER); int get_design_staffpick(int offset, int limit, std::function<void(std::string)> callback, const std::string& provider = ORCA_CLOUD_PROVIDER);
int start_publish(PublishParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, std::string* out, const std::string& provider = ORCA_CLOUD_PROVIDER); int start_publish(PublishParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, std::string* out, const std::string& provider = ORCA_CLOUD_PROVIDER);
int get_model_publish_url(std::string* url, const std::string& provider = ORCA_CLOUD_PROVIDER); int get_model_publish_url(std::string* url, const std::string& provider = ORCA_CLOUD_PROVIDER);
@@ -150,21 +142,9 @@ public:
int set_on_local_message_fn(OnMessageFn fn); int set_on_local_message_fn(OnMessageFn fn);
int set_server_callback(OnServerErrFn fn); int set_server_callback(OnServerErrFn fn);
int send_message(std::string dev_id, std::string json_str, int qos, int flag); int send_message(std::string dev_id, std::string json_str, int qos, int flag);
int command_ams_refresh_rfid(std::string dev_id, int ams_id, int slot_id, int sequence_id, bool lan_mode); int connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl);
int command_ams_calibrate(std::string dev_id, int ams_id, int sequence_id, bool lan_mode);
int command_ams_select_tray(std::string dev_id, std::string tray_id, int sequence_id, bool lan_mode);
int command_start_camera(std::string dev_id);
int command_xyz_abs(std::string dev_id, int sequence_id, bool lan_mode);
int command_auto_leveling(std::string dev_id, int sequence_id, bool lan_mode);
int command_go_home(std::string dev_id, bool is_printing, bool supports_mqtt_homing, int sequence_id, bool lan_mode);
int command_set_bed(std::string dev_id, int temp, bool supports_mqtt_bed_ctrl, int sequence_id, bool lan_mode);
int command_set_nozzle(std::string dev_id, int temp, int sequence_id, bool lan_mode);
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);
int connect_printer(const PrinterConnectionParams& params);
int disconnect_printer(); int disconnect_printer();
int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag); int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag);
std::string default_lan_username() const;
int check_cert(); int check_cert();
void install_device_cert(std::string dev_id, bool lan_only); void install_device_cert(std::string dev_id, bool lan_only);
bool start_discovery(bool start, bool sending); bool start_discovery(bool start, bool sending);
@@ -184,9 +164,7 @@ public:
int start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn); int start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn);
int start_sdcard_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn); int start_sdcard_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn);
FilamentSyncMode get_filament_sync_mode() const; FilamentSyncMode get_filament_sync_mode() const;
bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode = FilamentSyncMode::pull); bool fetch_filament_info(std::string dev_id);
CameraStreamMode get_camera_stream_mode() const;
std::string get_local_camera_stream_url() const;
std::string to_orca_filament_id(const std::string& printer_filament_id) const; std::string to_orca_filament_id(const std::string& printer_filament_id) const;
std::string from_orca_filament_id(const std::string& orca_filament_id) const; std::string from_orca_filament_id(const std::string& orca_filament_id) const;
int request_bind_ticket(std::string* ticket); int request_bind_ticket(std::string* ticket);
@@ -217,7 +195,6 @@ private:
std::map<std::string, std::shared_ptr<ICloudServiceAgent>> m_cloud_agents; std::map<std::string, std::shared_ptr<ICloudServiceAgent>> m_cloud_agents;
std::shared_ptr<IPrinterAgent> m_printer_agent; std::shared_ptr<IPrinterAgent> m_printer_agent;
std::string m_printer_agent_id; std::string m_printer_agent_id;
std::atomic<std::uint64_t> m_user_machine_list_generation{0};
}; };
} }
+5 -33
View File
@@ -16,10 +16,8 @@
#include <mutex> #include <mutex>
#include <utility> #include <utility>
#include <slic3r/GUI/GUI_App.hpp> #include <slic3r/GUI/GUI_App.hpp>
#include <slic3r/GUI/I18N.hpp>
#include <slic3r/plugin/PluginDescriptor.hpp> #include <slic3r/plugin/PluginDescriptor.hpp>
#include <slic3r/plugin/PythonPluginInterface.hpp> #include <slic3r/plugin/PythonPluginInterface.hpp>
#include <wx/msgdlg.h>
namespace Slic3r { namespace Slic3r {
namespace { namespace {
@@ -316,21 +314,6 @@ void NetworkAgentFactory::register_python_printer_agent(const std::string& plugi
std::shared_ptr<IPrinterAgent> cached_agent; std::shared_ptr<IPrinterAgent> cached_agent;
auto reject_conflicting_capability = [plugin_key, capability_name](const std::string& error_message) {
if (!wxTheApp || GUI::wxGetApp().is_closing())
return;
GUI::wxGetApp().CallAfter([plugin_key, capability_name, error_message]() {
if (GUI::wxGetApp().is_closing())
return;
PluginManager& manager = PluginManager::instance();
manager.set_plugin_error(plugin_key, error_message);
// note: the unload callback triggered by disabling will call deregister,
// which will be a no-op since the printer agent is never registered
manager.set_capability_enabled({PluginCapabilityType::PrinterConnection, capability_name, plugin_key}, false);
wxMessageBox(wxString::FromUTF8(error_message.c_str()), _L("Plugins"), wxOK | wxICON_WARNING, GUI::wxGetApp().GetTopWindow());
});
};
{ {
std::lock_guard<std::mutex> lock(s_registry_mutex); std::lock_guard<std::mutex> lock(s_registry_mutex);
@@ -349,10 +332,9 @@ void NetworkAgentFactory::register_python_printer_agent(const std::string& plugi
auto& python_agent_ids = get_python_printer_agent_ids(); auto& python_agent_ids = get_python_printer_agent_ids();
for (const auto& pair : python_agent_ids) { for (const auto& pair : python_agent_ids) {
if (pair.first != capability_key && pair.second == info.id) { if (pair.first != capability_key && pair.second == info.id) {
const std::string error_message = "Printer-agent '" + info.name + "' could not be enabled: agent ID '" + info.id + BOOST_LOG_TRIVIAL(warning) << "Printer-agent plugin '" << capability_name << "' uses duplicate agent ID '" << info.id
"' is already registered by capability '" + pair.first.second + "' from plugin '" + pair.first.first + "'."; << "' already registered by capability '" << pair.first.second << "' from plugin '"
BOOST_LOG_TRIVIAL(warning) << error_message; << pair.first.first << "'";
reject_conflicting_capability(error_message);
return; return;
} }
} }
@@ -372,22 +354,12 @@ void NetworkAgentFactory::register_python_printer_agent(const std::string& plugi
auto& agents = get_printer_agents(); auto& agents = get_printer_agents();
auto agent_it = agents.find(info.id); auto agent_it = agents.find(info.id);
// why: reject only when the ID is owned by SOMEONE ELSE - a built-in has an empty
// plugin_identifier, another plugin/capability has a different plugin_full_ref. When it
// IS the same plugin_full_ref, this capability is just re-registering itself, so fall
// through and refresh.
if (agent_it != agents.end() && agent_it->second.plugin_identifier != plugin_full_ref) { if (agent_it != agents.end() && agent_it->second.plugin_identifier != plugin_full_ref) {
const std::string error_message = "Printer-agent '" + info.name + "' could not be enabled: agent ID '" + info.id + BOOST_LOG_TRIVIAL(warning) << "Printer-agent plugin '" << capability_name << "' uses agent ID '" << info.id
"' is already registered by '" + agent_it->second.display_name + "'."; << "' already registered by '" << agent_it->second.display_name << "'";
BOOST_LOG_TRIVIAL(warning) << error_message;
reject_conflicting_capability(error_message);
return; return;
} }
// why: insert_or_assign, not emplace - reaching here means the ID is new, or the same
// capability is re-registering (same plugin_full_ref). In the re-register case we WANT to
// overwrite so the factory closure points at the current live capability instance; emplace
// would silently keep the stale entry.
agents.insert_or_assign(info.id, PrinterAgentInfo(info.id, info.name, plugin_full_ref, std::move(factory))); agents.insert_or_assign(info.id, PrinterAgentInfo(info.id, info.name, plugin_full_ref, std::move(factory)));
python_agent_ids[capability_key] = info.id; python_agent_ids[capability_key] = info.id;
+10 -335
View File
@@ -1,15 +1,11 @@
#include "OrcaCloudServiceAgent.hpp" #include "OrcaCloudServiceAgent.hpp"
#include "Http.hpp" #include "Http.hpp"
#include "ICameraSignalingChannel.hpp"
#include "OrcaCloudSignalingChannel.hpp"
#include "libslic3r/Utils.hpp" #include "libslic3r/Utils.hpp"
#include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI_App.hpp"
#include "libslic3r/AppConfig.hpp" #include "libslic3r/AppConfig.hpp"
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <boost/beast/core/detail/base64.hpp> #include <boost/beast/core/detail/base64.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <boost/uuid/uuid.hpp> #include <boost/uuid/uuid.hpp>
@@ -25,18 +21,14 @@
#include <openssl/hmac.h> #include <openssl/hmac.h>
#include <openssl/rand.h> #include <openssl/rand.h>
#include <openssl/sha.h> #include <openssl/sha.h>
#include <openssl/ssl.h>
#include <algorithm> #include <algorithm>
#include <cctype> #include <cctype>
#include <condition_variable>
#include <cstdint>
#include <cstdlib> #include <cstdlib>
#include <fstream> #include <fstream>
#include <iomanip> #include <iomanip>
#include <optional> #include <optional>
#include <random> #include <random>
#include <set>
#include <sstream> #include <sstream>
#include <string> #include <string>
@@ -91,7 +83,6 @@ constexpr const char* ORCA_UNSUBSCRIBE_PLUGINS = "/api/v1/plugins/subscriptions"
constexpr const char* ORCA_PLUGINS_MINE = "/api/v1/plugins/mine"; constexpr const char* ORCA_PLUGINS_MINE = "/api/v1/plugins/mine";
constexpr const char* ORCA_PLUGINS_BASE = "/api/v1/plugins"; constexpr const char* ORCA_PLUGINS_BASE = "/api/v1/plugins";
constexpr const char* ORCA_PLUGIN_DOWNLOAD_URL = "/api/v1/plugins/download"; constexpr const char* ORCA_PLUGIN_DOWNLOAD_URL = "/api/v1/plugins/download";
constexpr const char* ORCA_CLOUD_PRINTER = "/api/v1/printers";
constexpr const char* ORCA_CLOUD_LOGIN_PATH = "/orcaslicer-login"; constexpr const char* ORCA_CLOUD_LOGIN_PATH = "/orcaslicer-login";
@@ -500,7 +491,6 @@ OrcaCloudServiceAgent::OrcaCloudServiceAgent(std::string log_dir)
, api_base_url(ORCA_DEFAULT_API_URL) , api_base_url(ORCA_DEFAULT_API_URL)
, auth_base_url(ORCA_DEFAULT_AUTH_URL) , auth_base_url(ORCA_DEFAULT_AUTH_URL)
, cloud_base_url(ORCA_DEFAULT_CLOUD_URL) , cloud_base_url(ORCA_DEFAULT_CLOUD_URL)
, mqtt_connection(std::make_unique<OrcaMqttConnection>())
{ {
auth_headers["apikey"] = ORCA_DEFAULT_PUB_KEY; auth_headers["apikey"] = ORCA_DEFAULT_PUB_KEY;
pkce_bundle.loopback_port = choose_loopback_port(); pkce_bundle.loopback_port = choose_loopback_port();
@@ -511,8 +501,6 @@ OrcaCloudServiceAgent::OrcaCloudServiceAgent(std::string log_dir)
OrcaCloudServiceAgent::~OrcaCloudServiceAgent() OrcaCloudServiceAgent::~OrcaCloudServiceAgent()
{ {
if (mqtt_connection)
mqtt_connection->stop();
if (refresh_thread.joinable()) { if (refresh_thread.joinable()) {
refresh_thread.join(); refresh_thread.join();
} }
@@ -953,45 +941,22 @@ bool OrcaCloudServiceAgent::ensure_token_fresh(const std::string& reason) { retu
int OrcaCloudServiceAgent::connect_server() int OrcaCloudServiceAgent::connect_server()
{ {
const bool logged_in = is_user_login();
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: OrcaCloudServiceAgent::connect_server logged_in=" << logged_in
<< " api_base_url=" << api_base_url;
if (!logged_in) {
if (mqtt_connection)
mqtt_connection->stop();
{
std::lock_guard<std::recursive_mutex> lock(state_mutex);
is_connected = false;
}
BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: connect_server requires a logged-in user";
invoke_server_connected_callback(-1, 401);
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
}
std::string response; std::string response;
unsigned int http_code = 0; unsigned int http_code = 0;
int result = http_get(ORCA_HEALTH_PATH, &response, &http_code); int result = http_get(ORCA_HEALTH_PATH, &response, &http_code);
bool connected = (result == BAMBU_NETWORK_SUCCESS && http_code >= 200 && http_code < 300); bool connected = (result == BAMBU_NETWORK_SUCCESS && http_code >= 200 && http_code < 300);
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: cloud health result=" << result << " http_code=" << http_code
<< " connected=" << connected << " response_bytes=" << response.size();
// 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); std::lock_guard<std::recursive_mutex> lock(state_mutex);
is_connected = connected; is_connected = connected;
} }
invoke_server_connected_callback(connected ? 0 : -1, http_code); invoke_server_connected_callback(connected ? 0 : -1, http_code);
return connected ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECTION_TO_SERVER_FAILED; return connected ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECTION_TO_SERVER_FAILED;
} }
bool OrcaCloudServiceAgent::is_server_connected() bool OrcaCloudServiceAgent::is_server_connected()
{ {
// The REST health probe is the signal; the per-printer MQTT socket does not gate
// whole-cloud connectivity (one printer reconnecting must not report the whole
// cloud as lost).
std::lock_guard<std::recursive_mutex> lock(state_mutex); std::lock_guard<std::recursive_mutex> lock(state_mutex);
return is_connected; return is_connected;
} }
@@ -1012,238 +977,13 @@ int OrcaCloudServiceAgent::stop_subscribe(std::string module)
int OrcaCloudServiceAgent::add_subscribe(std::vector<std::string> dev_list) int OrcaCloudServiceAgent::add_subscribe(std::vector<std::string> dev_list)
{ {
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: OrcaCloudServiceAgent::add_subscribe count=" << dev_list.size() (void) dev_list;
<< " logged_in=" << is_user_login() << " mqtt_connection=" << (mqtt_connection ? "set" : "null"); return BAMBU_NETWORK_SUCCESS;
if (!is_user_login() || !mqtt_connection) {
BOOST_LOG_TRIVIAL(warning) << "Orca diagnostic: add_subscribe rejected because cloud is not ready";
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
}
bool queued = true;
for (const std::string& dev_id : dev_list)
queued = mqtt_connection->subscribe(dev_id) && queued;
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: add_subscribe queued=" << queued;
return queued ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECT_FAILED;
} }
int OrcaCloudServiceAgent::del_subscribe(std::vector<std::string> dev_list) int OrcaCloudServiceAgent::del_subscribe(std::vector<std::string> dev_list)
{ {
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: OrcaCloudServiceAgent::del_subscribe count=" << dev_list.size() (void) dev_list;
<< " logged_in=" << is_user_login() << " mqtt_connection=" << (mqtt_connection ? "set" : "null");
if (!is_user_login() || !mqtt_connection) {
BOOST_LOG_TRIVIAL(warning) << "Orca diagnostic: del_subscribe rejected because cloud is not ready";
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
}
bool queued = true;
for (const std::string& dev_id : dev_list)
queued = mqtt_connection->unsubscribe(dev_id) && queued;
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: del_subscribe queued=" << queued;
return queued ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECT_FAILED;
}
int OrcaCloudServiceAgent::configure_selected_printer_mqtt(const std::string& dev_id,
OrcaMqttConnection::StateHandler state_handler)
{
(void) dev_id;
if (!ensure_token_fresh("configure_selected_printer_mqtt"))
{
BOOST_LOG_TRIVIAL(warning) << "ensure_token_fresh returned false";
return BAMBU_NETWORK_ERR_CONNECTION_TO_SERVER_FAILED;
}
OrcaMqttConnection::Config cfg;
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";
cfg.keepalive_seconds = 300;
{
std::lock_guard<std::mutex> lock(m_selected_url_mutex);
m_selected_printer_mqtt_url = 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
// the MQTT worker thread.
const bool ok = mqtt_connection->start(
cfg,
[this](const std::string& id, const std::string& payload) { deliver_cloud_message(id, payload); },
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;
}
void OrcaCloudServiceAgent::teardown_selected_printer_mqtt()
{
if (mqtt_connection) {
mqtt_connection->stop();
// The connection object is reused for the next printer; drop this printer's
// report topic so its 1:1 socket does not re-subscribe the previous device.
mqtt_connection->clear_subscriptions();
}
std::lock_guard<std::mutex> lock(m_selected_url_mutex);
m_selected_printer_mqtt_url.clear();
}
std::string OrcaCloudServiceAgent::selected_printer_mqtt_url() const
{
std::lock_guard<std::mutex> lock(m_selected_url_mutex);
return m_selected_printer_mqtt_url;
}
void OrcaCloudServiceAgent::deliver_cloud_message(const std::string& dev_id, const std::string& payload)
{
OnMessageFn callback;
{
std::lock_guard<std::mutex> lock(callback_mutex);
callback = printer_status_callback;
}
if (callback)
callback(dev_id, payload);
}
int OrcaCloudServiceAgent::set_printer_status_callback(OnMessageFn fn)
{
std::lock_guard<std::mutex> lock(callback_mutex);
printer_status_callback = std::move(fn);
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: printer status callback=" << (printer_status_callback ? "set" : "clear");
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::send_printer_command(const std::string& dev_id, const std::string& body)
{
BOOST_LOG_TRIVIAL(info) << "Orca diagnostic: send_printer_command dev_id=" << dev_id
<< " body_bytes=" << body.size() << " logged_in=" << is_user_login();
if (dev_id.empty() || !is_user_login()) {
BOOST_LOG_TRIVIAL(warning) << "Orca diagnostic: send_printer_command rejected";
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
}
const std::string path = std::string(ORCA_CLOUD_PRINTER) + "/" + dev_id + "/commands";
std::string response;
unsigned int http_code = 0;
int result = http_post(path, body, &response, &http_code);
BOOST_LOG_TRIVIAL(info) << "OrcaCloudServiceAgent: command dev=" << dev_id
<< " http=" << http_code << " result=" << result
<< " response_bytes=" << response.size();
return (result == BAMBU_NETWORK_SUCCESS && http_code >= 200 && http_code < 300)
? BAMBU_NETWORK_SUCCESS
: BAMBU_NETWORK_ERR_CONNECT_FAILED;
}
int OrcaCloudServiceAgent::upload_gcode_via_cloud(const std::string& dev_id,
const std::string& local_gcode_path,
std::string* job_id,
OnUpdateStatusFn update_fn,
WasCancelledFn cancel_fn)
{
if (dev_id.empty() || local_gcode_path.empty() || !is_user_login())
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
if (cancel_fn && cancel_fn())
return BAMBU_NETWORK_ERR_CANCELED;
// Step 1: POST print-jobs/uploads -> a short-lived presigned R2 PUT URL. No
// metadata rides this request; filename/start are only relevant to the HTTP
// .../start finalize route, which this MQTT-driven flow does not call.
const std::string uploads_path = std::string(ORCA_CLOUD_PRINTER) + "/" + Http::url_encode(dev_id) + "/print-jobs/uploads";
std::string response;
unsigned int http_code = 0;
int result = http_post(uploads_path, "{}", &response, &http_code);
if (result != BAMBU_NETWORK_SUCCESS || http_code < 200 || http_code >= 300) {
BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: print-jobs/uploads failed http_code=" << http_code;
return BAMBU_NETWORK_ERR_CONNECT_FAILED;
}
std::string upload_job_id;
std::string upload_url;
try {
const nlohmann::json j = nlohmann::json::parse(response);
upload_job_id = j.value("job_id", "");
upload_url = j.value("upload_url", "");
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: failed to parse print-jobs/uploads response: " << e.what();
return BAMBU_NETWORK_ERR_CONNECT_FAILED;
}
if (upload_job_id.empty() || upload_url.empty()) {
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: print-jobs/uploads response missing job_id/upload_url";
return BAMBU_NETWORK_ERR_CONNECT_FAILED;
}
if (cancel_fn && cancel_fn())
return BAMBU_NETWORK_ERR_CANCELED;
// Step 2: PUT the G-code straight to R2 with the one-time URL from step 1. This
// is a scoped, PUT-only, short-TTL capability with no bearer token of its own,
// so it bypasses http_put (which always prefixes api_base_url and attaches the
// cloud session's Authorization header - neither belongs on an R2 PUT).
bool canceled = false;
unsigned put_status = 0;
std::string put_error;
Http::put(upload_url)
.tls_verify(true)
.header("Content-Type", "text/x.gcode")
.set_put_body(boost::filesystem::path(local_gcode_path))
.timeout_connect(5)
.timeout_max(300) // large G-code over a slow link
.on_progress([&](Http::Progress progress, bool& cancel) {
if (cancel_fn && cancel_fn()) {
cancel = true;
canceled = true;
return;
}
if (update_fn && progress.ultotal > 0) {
const int percent = static_cast<int>((progress.ulnow * 100) / progress.ultotal);
update_fn(PrintingStageUpload, percent, "Uploading...");
}
})
.on_complete([&](std::string, unsigned status) { put_status = status; })
.on_error([&](std::string, std::string err, unsigned status) {
put_status = status;
put_error = std::move(err);
})
.perform_sync();
if (canceled)
return BAMBU_NETWORK_ERR_CANCELED;
if (put_status < 200 || put_status >= 300) {
BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: R2 upload failed status=" << put_status << " error=" << put_error;
return BAMBU_NETWORK_ERR_PRINT_SG_UPLOAD_FTP_FAILED;
}
if (job_id)
*job_id = std::move(upload_job_id);
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::start_cloud_print_job(const std::string& dev_id,
const std::string& job_id,
const std::string& filename,
bool start)
{
if (dev_id.empty() || job_id.empty() || !is_user_login())
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
nlohmann::json body;
if (!filename.empty())
body["filename"] = filename;
body["start"] = start;
const std::string path = std::string(ORCA_CLOUD_PRINTER) + "/" + Http::url_encode(dev_id) + "/print-jobs/" +
Http::url_encode(job_id) + "/start";
std::string response;
unsigned int http_code = 0;
const int result = http_post(path, body.dump(), &response, &http_code);
if (result != BAMBU_NETWORK_SUCCESS || http_code < 200 || http_code >= 300) {
BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: print-jobs/" << job_id << "/start failed http_code=" << http_code;
return BAMBU_NETWORK_ERR_CONNECT_FAILED;
}
return BAMBU_NETWORK_SUCCESS; return BAMBU_NETWORK_SUCCESS;
} }
@@ -2284,10 +2024,6 @@ bool OrcaCloudServiceAgent::set_user_session(const json& session_json, bool noti
void OrcaCloudServiceAgent::clear_session() void OrcaCloudServiceAgent::clear_session()
{ {
if (mqtt_connection) {
mqtt_connection->stop();
mqtt_connection->clear_subscriptions();
}
{ {
std::lock_guard<std::mutex> lock(session_mutex); std::lock_guard<std::mutex> lock(session_mutex);
session = SessionInfo{}; session = SessionInfo{};
@@ -2403,11 +2139,7 @@ int OrcaCloudServiceAgent::http_get(const std::string& path, std::string* respon
return (res.success && !suppress) ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECT_FAILED; return (res.success && !suppress) ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECT_FAILED;
} }
int OrcaCloudServiceAgent::http_post(const std::string& path, int OrcaCloudServiceAgent::http_post(const std::string& path, const std::string& body, std::string* response_body, unsigned int* http_code)
const std::string& body,
std::string* response_body,
unsigned int* http_code,
const std::string& content_type)
{ {
std::string url = api_base_url + path; std::string url = api_base_url + path;
BOOST_LOG_TRIVIAL(trace) << "OrcaCloudServiceAgent: POST " << url; BOOST_LOG_TRIVIAL(trace) << "OrcaCloudServiceAgent: POST " << url;
@@ -2431,7 +2163,7 @@ int OrcaCloudServiceAgent::http_post(const std::string& path,
http.header("Authorization", "Bearer " + token); http.header("Authorization", "Bearer " + token);
} }
http.header("Content-Type", content_type); http.header("Content-Type", "application/json");
http.set_post_body(body); http.set_post_body(body);
http.on_complete([&](std::string resp_body, unsigned resp_status) { http.on_complete([&](std::string resp_body, unsigned resp_status) {
@@ -2891,65 +2623,11 @@ int OrcaCloudServiceAgent::check_user_task_report(int* task_id, bool* printable)
int OrcaCloudServiceAgent::get_user_print_info(unsigned int* http_code, std::string* http_body) int OrcaCloudServiceAgent::get_user_print_info(unsigned int* http_code, std::string* http_body)
{ {
std::string response; BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_user_print_info (stub)";
unsigned int code = 0;
int result = http_get(ORCA_CLOUD_PRINTER, &response, &code);
if (http_code) if (http_code)
*http_code = code; *http_code = 200;
if (http_body)
if (result != 0 || code != 200) { *http_body = "{}";
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: get_user_print_info failed - http_code=" << code << ", response=" << response;
return result != 0 ? result : BAMBU_NETWORK_ERR_GET_SETTING_LIST_FAILED;
}
BOOST_LOG_TRIVIAL(trace) << "OrcaCloudServiceAgent: get_user_print_info fetched - http_code=" << code << ", response=" << response;
try {
auto resp_json = nlohmann::json::parse(response);
nlohmann::json devices = nlohmann::json::array();
for (const auto& printer : resp_json.value("data", nlohmann::json::array())) {
nlohmann::json device;
std::string role = printer.value("access_role", "");
// A printer with the role "view" only has monitoring access for orca cloud.
// The printer is owned by a different person and was shared to the current user without
// any permission to control the printer so we discard this printer. Comment this out if
// OrcaSlicer wants to support view only printers.
if (role.empty() || role == "viewer")
continue;
device["dev_id"] = printer.value("id", "");
device["dev_name"] = printer.value("name", "");
if (printer.contains("model") && printer["model"].is_string())
device["dev_model_name"] = printer["model"].get<std::string>();
bool online = false;
if (printer.contains("status_snapshot") && printer["status_snapshot"].is_object()) {
const auto& status = printer["status_snapshot"].value("status", nlohmann::json::object());
online = status.value("connection", nlohmann::json::object()).value("state", "") == "online";
if (status.contains("job") && status["job"].is_object())
device["task_status"] = status["job"].value("state", "");
}
device["dev_online"] = online;
devices.push_back(device);
}
if (http_body) {
nlohmann::json out;
out["devices"] = devices;
*http_body = out.dump();
}
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_user_print_info parsed - device_count=" << devices.size()
<< ", devices=" << devices.dump();
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: get_user_print_info parse exception - " << e.what();
return BAMBU_NETWORK_ERR_GET_SETTING_LIST_FAILED;
}
return BAMBU_NETWORK_SUCCESS; return BAMBU_NETWORK_SUCCESS;
} }
@@ -3031,9 +2709,6 @@ int OrcaCloudServiceAgent::get_camera_url(std::string dev_id, std::function<void
return BAMBU_NETWORK_SUCCESS; return BAMBU_NETWORK_SUCCESS;
} }
std::unique_ptr<ICameraSignalingChannel> OrcaCloudServiceAgent::create_camera_signaling_channel(const std::string& dev_id)
{ return std::make_unique<OrcaCloudSignalingChannel>(this, dev_id); }
int OrcaCloudServiceAgent::get_design_staffpick(int offset, int limit, std::function<void(std::string)> callback) int OrcaCloudServiceAgent::get_design_staffpick(int offset, int limit, std::function<void(std::string)> callback)
{ {
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_design_staffpick (stub)"; BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_design_staffpick (stub)";
+1 -59
View File
@@ -1,14 +1,7 @@
#ifndef __ORCA_CLOUD_SERVICE_AGENT_HPP__ #ifndef __ORCA_CLOUD_SERVICE_AGENT_HPP__
#define __ORCA_CLOUD_SERVICE_AGENT_HPP__ #define __ORCA_CLOUD_SERVICE_AGENT_HPP__
#include "ICameraSignalingChannel.hpp"
#include "ICloudServiceAgent.hpp" #include "ICloudServiceAgent.hpp"
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/ssl.hpp>
#include <boost/beast/websocket.hpp>
#include <cstdlib> #include <cstdlib>
#include <string> #include <string>
#include <map> #include <map>
@@ -16,17 +9,12 @@
#include <atomic> #include <atomic>
#include <chrono> #include <chrono>
#include <functional> #include <functional>
#include <condition_variable>
#include <cstdint>
#include <set>
#include <memory> #include <memory>
#include <thread> #include <thread>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include "OrcaMqttConnection.hpp"
class wxSecretStore; class wxSecretStore;
namespace Slic3r { namespace Slic3r {
@@ -163,8 +151,6 @@ public:
// Configuration // Configuration
void configure_urls(AppConfig* app_config); void configure_urls(AppConfig* app_config);
// Hostname only; cloud REST, MQTT, and WebRTC signaling use fixed TLS
// endpoints on port 443.
void set_api_base_url(const std::string& url); void set_api_base_url(const std::string& url);
void set_auth_base_url(const std::string& url); void set_auth_base_url(const std::string& url);
void set_cloud_base_url(const std::string& url); void set_cloud_base_url(const std::string& url);
@@ -220,19 +206,6 @@ public:
int add_subscribe(std::vector<std::string> dev_list) override; int add_subscribe(std::vector<std::string> dev_list) override;
int del_subscribe(std::vector<std::string> dev_list) override; int del_subscribe(std::vector<std::string> dev_list) override;
void enable_multi_machine(bool enable) override; void enable_multi_machine(bool enable) override;
int set_printer_status_callback(OnMessageFn fn);
int send_printer_command(const std::string& dev_id, const std::string& body);
int upload_gcode_via_cloud(const std::string& dev_id,
const std::string& local_gcode_path,
std::string* job_id,
OnUpdateStatusFn update_fn,
WasCancelledFn cancel_fn);
int start_cloud_print_job(const std::string& dev_id,
const std::string& job_id,
const std::string& filename,
bool start = true);
// ======================================================================== // ========================================================================
// ICloudServiceAgent Interface Implementation - Settings Synchronization // ICloudServiceAgent Interface Implementation - Settings Synchronization
@@ -268,7 +241,6 @@ public:
// ICloudServiceAgent Interface Implementation - Model Mall & Publishing // ICloudServiceAgent Interface Implementation - Model Mall & Publishing
// ======================================================================== // ========================================================================
int get_camera_url(std::string dev_id, std::function<void(std::string)> callback) override; int get_camera_url(std::string dev_id, std::function<void(std::string)> callback) override;
std::unique_ptr<ICameraSignalingChannel> create_camera_signaling_channel(const std::string& dev_id) override;
int get_design_staffpick(int offset, int limit, std::function<void(std::string)> callback) override; int get_design_staffpick(int offset, int limit, std::function<void(std::string)> callback) override;
int start_publish(PublishParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, std::string* out) override; int start_publish(PublishParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, std::string* out) override;
int get_model_publish_url(std::string* url) override; int get_model_publish_url(std::string* url) override;
@@ -374,29 +346,7 @@ public:
static std::string generate_uuid_for_setting_id(const std::string& name, const std::string& user_id = ""); static std::string generate_uuid_for_setting_id(const std::string& name, const std::string& user_id = "");
OrcaMqttConnection* get_mqtt_connection() noexcept {
return mqtt_connection.get();
}
const OrcaMqttConnection* get_mqtt_connection() const noexcept {
return mqtt_connection.get();
}
// 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. 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 fleet socket ("" when none).
std::string selected_printer_mqtt_url() const;
private: private:
// 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 // Sync protocol helpers
int sync_pull( int sync_pull(
std::function<void(const SyncPullResponse&)> on_success, std::function<void(const SyncPullResponse&)> on_success,
@@ -420,11 +370,7 @@ private:
// HTTP request helpers // HTTP request helpers
int http_get(const std::string& path, std::string* response_body, unsigned int* http_code); int http_get(const std::string& path, std::string* response_body, unsigned int* http_code);
int http_post(const std::string& path, int http_post(const std::string& path, const std::string& body, std::string* response_body, unsigned int* http_code);
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_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); int http_delete(const std::string& path, std::string* response_body, unsigned int* http_code);
std::map<std::string, std::string> data_headers(); std::map<std::string, std::string> data_headers();
@@ -477,9 +423,6 @@ private:
std::chrono::system_clock::now().time_since_epoch()).count()}; std::chrono::system_clock::now().time_since_epoch()).count()};
// Member variables - connection state // Member variables - connection state
std::unique_ptr<OrcaMqttConnection> mqtt_connection;
std::string m_selected_printer_mqtt_url; // guarded by m_selected_url_mutex
mutable std::mutex m_selected_url_mutex;
bool is_connected{false}; bool is_connected{false};
bool enable_track{false}; bool enable_track{false};
bool multi_machine_enabled{false}; bool multi_machine_enabled{false};
@@ -493,7 +436,6 @@ private:
AppOnHttpErrorFn on_http_error_fn; AppOnHttpErrorFn on_http_error_fn;
GetCountryCodeFn get_country_code_fn; GetCountryCodeFn get_country_code_fn;
QueueOnMainFn queue_on_main_fn; QueueOnMainFn queue_on_main_fn;
OnMessageFn printer_status_callback;
mutable std::mutex callback_mutex; mutable std::mutex callback_mutex;
// Thread safety // Thread safety
@@ -1,337 +0,0 @@
#include "OrcaCloudSignalingChannel.hpp"
#include "Http.hpp"
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/post.hpp>
#include <boost/beast/core.hpp>
#include <boost/log/trivial.hpp>
#include <nlohmann/json.hpp>
#include <openssl/ssl.h>
#include <cctype>
#include <iomanip>
#include <sstream>
#include <stdexcept>
namespace Slic3r {
OrcaCloudSignalingChannel::OrcaCloudSignalingChannel(OrcaCloudServiceAgent* cloud, std::string dev_id)
: m_cloud(cloud)
, m_dev_id(std::move(dev_id))
{
}
OrcaCloudSignalingChannel::~OrcaCloudSignalingChannel()
{
close();
}
void OrcaCloudSignalingChannel::open()
{
bool expected = false;
if (!m_open.compare_exchange_strong(expected, true))
return;
m_stop.store(false);
m_thread = std::thread([this] { run(); });
}
void OrcaCloudSignalingChannel::close()
{
m_stop.store(true);
std::shared_ptr<Connection> conn;
Http::Ptr request;
{
std::lock_guard<std::mutex> lock(m_mutex);
conn = m_conn;
request = m_inflight_requests;
}
// m_conn is installed only after the live-token request completes, so
// cancel the request independently before waiting for the worker.
if (request)
request->cancel();
if (conn) {
// Established session: close the socket on the io_context's own thread so
// the pending async_read completes and io_context.run() unwinds.
boost::asio::post(conn->io_context, [conn] {
boost::system::error_code ec;
boost::beast::get_lowest_layer(conn->websocket).cancel(ec);
boost::beast::get_lowest_layer(conn->websocket).close(ec);
});
// Pre-run() phase (still in the synchronous connect/handshake): best-effort
// direct interruption.
boost::system::error_code ec;
boost::beast::get_lowest_layer(conn->websocket).cancel(ec);
boost::beast::get_lowest_layer(conn->websocket).close(ec);
}
if (m_thread.joinable())
m_thread.join();
m_open.store(false);
}
void OrcaCloudSignalingChannel::send_offer(std::string sdp)
{
send_json(nlohmann::json{{"type", "webrtc.offer"}, {"sdp", std::move(sdp)}}.dump());
}
void OrcaCloudSignalingChannel::send_ice(std::string candidate, std::string mid)
{
send_json(nlohmann::json{{"type", "webrtc.ice"},
{"candidate", std::move(candidate)},
{"sdpMid", std::move(mid)}}
.dump());
}
std::string OrcaCloudSignalingChannel::encode_path_component(const std::string& value)
{
std::ostringstream encoded;
encoded << std::uppercase << std::hex;
for (unsigned char c : value) {
if (std::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~')
encoded << c;
else
encoded << '%' << std::setw(2) << std::setfill('0') << static_cast<unsigned int>(c);
}
return encoded.str();
}
void OrcaCloudSignalingChannel::unavailable(CameraUnavailableReason reason, std::string detail)
{
if (on_unavailable)
on_unavailable(reason, std::move(detail));
}
void OrcaCloudSignalingChannel::run()
{
try {
if (!m_cloud || !m_cloud->ensure_token_fresh("camera")) {
unavailable(CameraUnavailableReason::Error, "Unable to refresh OrcaCloud credentials");
m_open.store(false);
return;
}
const std::string token = m_cloud->get_access_token();
// OrcaCloud exposes a bare API hostname. WebRTC signaling uses the
// fixed HTTPS/WSS endpoints on port 443; custom schemes, ports, and
// base paths are not supported by this agent.
const std::string host = m_cloud->get_cloud_service_host();
if (token.empty() || host.empty()) {
unavailable(CameraUnavailableReason::Error, "OrcaCloud session is unavailable");
m_open.store(false);
return;
}
const std::string live_token_url =
"https://" + host + "/api/v1/printers/" + encode_path_component(m_dev_id) + "/live-token";
BOOST_LOG_TRIVIAL(info) << "signaling: POST " << live_token_url << " (dev_id=" << m_dev_id << ")";
nlohmann::json token_response;
std::string token_body;
unsigned int http_code = 0;
auto request = std::make_shared<Slic3r::Http>(Http::post(live_token_url));
request->set_post_body(std::string("{}"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.tls_verify(true)
.timeout_max(30)
.on_complete([&token_body, &http_code](std::string body, unsigned status) {
http_code = status;
token_body = std::move(body);
})
.on_error([&http_code](std::string, std::string, unsigned status) {
http_code = status;
});
{
std::lock_guard<std::mutex> lock(m_mutex);
m_inflight_requests = request;
}
// If close() raced with request setup, make sure this request is
// cancelled before entering the blocking call.
if (m_stop.load())
request->cancel();
try {
request->perform_sync();
} catch (...) {
std::lock_guard<std::mutex> lock(m_mutex);
if (m_inflight_requests == request)
m_inflight_requests.reset();
throw;
}
{
std::lock_guard<std::mutex> lock(m_mutex);
if (m_inflight_requests == request)
m_inflight_requests.reset();
}
try {
token_response = nlohmann::json::parse(token_body);
} catch (const std::exception&) {
}
if (http_code < 200 || http_code >= 300 || !token_response.contains("token")) {
unavailable(CameraUnavailableReason::Error, "Unable to mint camera live token");
m_open.store(false);
return;
}
std::vector<CameraIceServer> ice_servers;
if (token_response.contains("ice_servers") && token_response["ice_servers"].is_array()) {
for (const auto& entry : token_response["ice_servers"]) {
if (entry.is_string()) {
ice_servers.push_back({entry.get<std::string>(), {}, {}});
} else if (entry.is_object()) {
// RTCIceServer.urls is "string | string[]" (Cloudflare
// Realtime returns an array). Emit one CameraIceServer per
// URL, sharing the credentials.
const std::string username = entry.value("username", std::string{});
const std::string credential = entry.value("credential", std::string{});
const auto add_url = [&](const nlohmann::json& url) {
if (url.is_string() && !url.get<std::string>().empty())
ice_servers.push_back({url.get<std::string>(), username, credential});
};
const auto urls = entry.find("urls");
if (urls != entry.end()) {
if (urls->is_array()) {
for (const auto& url : *urls)
add_url(url);
} else {
add_url(*urls);
}
}
}
}
}
auto conn = std::make_shared<Connection>();
conn->ssl_context.set_default_verify_paths();
Http::add_platform_root_certificates(conn->ssl_context.native_handle());
{
std::lock_guard<std::mutex> lock(m_mutex);
m_conn = conn;
}
auto& websocket = conn->websocket;
boost::asio::ip::tcp::resolver resolver(conn->io_context);
const auto endpoints = resolver.resolve(host, "443");
boost::asio::connect(boost::beast::get_lowest_layer(websocket), endpoints);
if (!SSL_set_tlsext_host_name(websocket.next_layer().native_handle(), host.c_str()))
throw std::runtime_error("Unable to configure TLS server name");
websocket.next_layer().set_verify_mode(boost::asio::ssl::verify_peer);
websocket.next_layer().set_verify_callback(boost::asio::ssl::host_name_verification(host));
websocket.next_layer().handshake(boost::asio::ssl::stream_base::client);
const std::string ws_target = "/api/v1/printers/" + encode_path_component(m_dev_id) +
"/camera/live?token=" +
encode_path_component(token_response["token"].get<std::string>());
websocket.handshake(host, ws_target);
BOOST_LOG_TRIVIAL(info) << "signaling: websocket handshake ok (" << ice_servers.size()
<< " ice servers)";
if (on_ready)
on_ready(std::move(ice_servers));
send_json(nlohmann::json{{"type", "camera.mode"}, {"mode", "webrtc"}}.dump());
// Async read loop, driven by the connection's own io_context. run()
// returns once close() has shut the socket down, giving a bounded,
// deadlock-free teardown from any thread.
do_read(conn);
conn->io_context.run();
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(warning) << "signaling: run() exception: " << e.what();
if (!m_stop.load())
unavailable(CameraUnavailableReason::Closed, e.what());
}
{
std::lock_guard<std::mutex> lock(m_mutex);
m_conn.reset();
}
m_open.store(false);
}
void OrcaCloudSignalingChannel::do_read(std::shared_ptr<Connection> conn)
{
auto buffer = std::make_shared<boost::beast::flat_buffer>();
conn->websocket.async_read(
*buffer, [this, conn, buffer](boost::system::error_code ec, std::size_t) {
if (ec) {
if (!m_stop.load())
unavailable(CameraUnavailableReason::Closed, ec.message());
return; // do not re-arm; io_context.run() unwinds
}
const std::string raw = boost::beast::buffers_to_string(buffer->data());
// A malformed or unexpectedly-shaped message must not tear down the
// session: parse/dispatch is guarded.
try {
dispatch_message(nlohmann::json::parse(raw), raw);
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(warning) << "signaling: ignoring malformed message: " << e.what()
<< " raw=" << raw.substr(0, 256);
}
if (!m_stop.load())
do_read(conn);
});
}
// Returns the string at key, or "" if absent or not a string (JSON null included).
static std::string json_string(const nlohmann::json& object, const char* key)
{
const auto it = object.find(key);
return (it != object.end() && it->is_string()) ? it->get<std::string>() : std::string{};
}
void OrcaCloudSignalingChannel::dispatch_message(const nlohmann::json& message, const std::string& raw)
{
const std::string type = json_string(message, "type");
BOOST_LOG_TRIVIAL(info) << "signaling: recv type=" << type << " raw=" << raw.substr(0, 256);
if (type == "webrtc.answer") {
const std::string sdp = json_string(message, "sdp");
if (on_answer && !sdp.empty())
on_answer(sdp);
} else if (type == "webrtc.ice" && message.contains("candidate")) {
// The peer may send "candidate" as a flat string or as a nested
// RTCIceCandidateInit object { candidate, sdpMid, sdpMLineIndex }.
const nlohmann::json& candidate = message["candidate"];
std::string sdp_candidate;
std::string mid = json_string(message, "sdpMid");
if (candidate.is_string()) {
sdp_candidate = candidate.get<std::string>();
} else if (candidate.is_object()) {
sdp_candidate = json_string(candidate, "candidate");
std::string nested_mid = json_string(candidate, "sdpMid");
if (!nested_mid.empty())
mid = std::move(nested_mid);
}
if (on_ice && !sdp_candidate.empty())
on_ice(sdp_candidate, mid);
} else if (type == "webrtc.unavailable") {
const std::string reason = json_string(message, "reason");
unavailable(reason == "busy" ? CameraUnavailableReason::Busy
: reason == "disabled" ? CameraUnavailableReason::Disabled
: CameraUnavailableReason::Error,
reason.empty() ? "error" : reason);
}
}
void OrcaCloudSignalingChannel::send_json(const std::string& message)
{
std::shared_ptr<Connection> conn;
{
std::lock_guard<std::mutex> lock(m_mutex);
conn = m_conn;
}
if (!conn || m_stop.load())
return;
// Serialize the write onto the io_context thread (same thread that runs
// async_read), so reads and writes never touch the stream concurrently.
auto payload = std::make_shared<std::string>(message);
boost::asio::post(conn->io_context, [this, conn, payload] {
if (m_stop.load())
return;
boost::system::error_code ec;
conn->websocket.write(boost::asio::buffer(*payload), ec);
if (ec && !m_stop.load())
unavailable(CameraUnavailableReason::Closed, ec.message());
});
}
} // namespace Slic3r
@@ -1,66 +0,0 @@
#pragma once
#include "ICameraSignalingChannel.hpp"
#include "OrcaCloudServiceAgent.hpp"
#include "Http.hpp"
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/beast/ssl.hpp>
#include <boost/beast/websocket.hpp>
#include <nlohmann/json_fwd.hpp>
#include <atomic>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
namespace Slic3r {
class OrcaCloudSignalingChannel : public ICameraSignalingChannel {
public:
OrcaCloudSignalingChannel(OrcaCloudServiceAgent* cloud, std::string dev_id);
~OrcaCloudSignalingChannel() override;
void open() override;
void close() override;
void send_offer(std::string sdp) override;
void send_ice(std::string candidate, std::string mid) override;
private:
using WebSocket = boost::beast::websocket::stream<
boost::beast::ssl_stream<boost::asio::ip::tcp::socket>>;
// The io_context and ssl_context must outlive the websocket stream that
// references them. Bundling them here with the stream declared last makes
// the destruction order correct (stream first, then contexts), and lets a
// single shared_ptr own the whole set.
struct Connection {
boost::asio::io_context io_context;
boost::asio::ssl::context ssl_context{boost::asio::ssl::context::tls_client};
WebSocket websocket{io_context, ssl_context};
};
void run();
void do_read(std::shared_ptr<Connection> conn);
void dispatch_message(const nlohmann::json& message, const std::string& raw);
void send_json(const std::string& message);
void unavailable(CameraUnavailableReason reason, std::string detail);
static std::string encode_path_component(const std::string& value);
OrcaCloudServiceAgent* m_cloud;
Http::Ptr m_inflight_requests{nullptr};
std::string m_dev_id;
std::atomic<bool> m_stop{false};
std::atomic<bool> m_open{false};
std::thread m_thread;
mutable std::mutex m_mutex;
std::shared_ptr<Connection> m_conn;
};
} // namespace Slic3r
-805
View File
@@ -1,805 +0,0 @@
#include "OrcaMqttConnection.hpp"
#include "Http.hpp"
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/ssl.hpp>
#include <boost/beast/websocket.hpp>
#include <openssl/ssl.h>
#include <algorithm>
#include <chrono>
#include <memory>
#include <optional>
#include <sstream>
#include <stdexcept>
#include <utility>
namespace Slic3r {
struct OrcaMqttConnection::Connection {
boost::asio::io_context io_context;
boost::asio::ssl::context ssl_context;
boost::asio::ip::tcp::resolver resolver;
boost::asio::steady_timer keepalive_timer;
boost::beast::flat_buffer read_buffer;
std::deque<std::shared_ptr<std::vector<uint8_t>>> outbound_packets;
boost::system::error_code terminal_error;
std::atomic_bool async_session_started{false};
bool write_in_progress{false};
// Exactly one of these is engaged once ws_handshake() has run: wss for
// wss:// endpoints, ws for plaintext ws://.
std::optional<TlsWebSocket> wss;
std::optional<PlainWebSocket> ws;
Connection()
: ssl_context(boost::asio::ssl::context::tls_client)
, resolver(io_context)
, keepalive_timer(io_context)
{}
};
namespace {
// Apply / clear a tcp_stream timeout on whichever websocket is engaged.
// Templated on the connection type only because Connection is a private nested
// type: a deduced parameter needs no (inaccessible) name for it.
template<class Conn> void expires_after(Conn& conn, std::chrono::seconds timeout) {
if (conn.wss) boost::beast::get_lowest_layer(*conn.wss).expires_after(timeout);
else if (conn.ws) boost::beast::get_lowest_layer(*conn.ws).expires_after(timeout);
}
template<class Conn> void expires_never(Conn& conn) {
if (conn.wss) boost::beast::get_lowest_layer(*conn.wss).expires_never();
else if (conn.ws) boost::beast::get_lowest_layer(*conn.ws).expires_never();
}
} // namespace
OrcaMqttConnection::~OrcaMqttConnection() { stop(); }
bool OrcaMqttConnection::start(const Config& config, MessageHandler on_message, StateHandler on_state) {
std::lock_guard<std::recursive_mutex> lifecycle_lock(lifecycle_mutex);
stop();
{
std::lock_guard<std::mutex> lock(mutex);
current_config = config;
this->on_message = std::move(on_message);
this->on_state = std::move(on_state);
initial_result = false;
initial_completed = false;
connected = false;
m_last_connack_rc.store(-1);
}
stopping.store(false);
worker = std::thread(&OrcaMqttConnection::run, this);
std::unique_lock<std::mutex> lock(mutex);
if (!initial_cv.wait_for(lock, std::chrono::seconds(10), [this] { return initial_completed; })) {
initial_completed = true;
initial_result = false;
}
return initial_result;
}
void OrcaMqttConnection::stop() {
std::lock_guard<std::recursive_mutex> lifecycle_lock(lifecycle_mutex);
stopping.store(true);
state_cv.notify_all();
{
std::lock_guard<std::mutex> lock(connection_mutex);
if (active_connection) {
if (active_connection->async_session_started.load()) {
// The worker owns the live WebSocket. Stop dispatching its
// asynchronous operations; the worker closes the socket after
// leaving the event loop.
active_connection->io_context.stop();
} else {
// Setup still uses synchronous operations on the worker. Wake a
// pending resolve/connect/CONNACK read without competing with a
// live asynchronous session.
auto shutdown_socket = [](auto& websocket) {
auto& socket = boost::beast::get_lowest_layer(websocket).socket();
boost::system::error_code socket_error;
if (socket.cancel(socket_error))
return;
if (socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, socket_error))
return;
if (socket.close(socket_error))
return;
};
if (active_connection->wss)
shutdown_socket(*active_connection->wss);
else if (active_connection->ws)
shutdown_socket(*active_connection->ws);
active_connection->resolver.cancel();
}
}
}
if (worker.joinable())
worker.join();
{
std::lock_guard<std::mutex> lock(mutex);
connected = false;
acknowledged_subscriptions.clear();
pending_subscribe_packets.clear();
pending_requests.clear();
if (!initial_completed) {
initial_completed = true;
initial_result = false;
}
}
initial_cv.notify_all();
}
bool OrcaMqttConnection::is_running() const {
return worker.joinable() && !stopping.load();
}
void OrcaMqttConnection::flush_subscription_change() {
std::shared_ptr<Connection> conn;
{
std::lock_guard<std::mutex> lock(connection_mutex);
conn = active_connection;
}
bool connacked;
{
std::lock_guard<std::mutex> lock(mutex);
connacked = connected;
}
if (!conn || !connacked)
return; // no live MQTT session yet — the worker sends the set on CONNACK
boost::asio::post(conn->io_context, [this, conn] {
if (!stopping.load() && connected.load())
send_pending_subscriptions(conn);
});
}
bool OrcaMqttConnection::subscribe(const std::string& dev_id) {
if (dev_id.empty()) {
return false;
}
const std::string topic = report_topic(dev_id);
if (topic.size() > 96) { // MQTT topic filter cap enforced by the service
return false;
}
{
std::lock_guard<std::mutex> lock(mutex);
if (subscriptions.count(topic) != 0 && pending_unsubscriptions.count(topic) == 0) {
return true;
}
subscriptions.insert(topic);
pending_unsubscriptions.erase(topic);
pending_subscriptions.insert(topic);
}
state_cv.notify_all();
flush_subscription_change(); // ask the worker to emit SUBSCRIBE now (no reconnect)
return true;
}
bool OrcaMqttConnection::unsubscribe(const std::string& dev_id) {
const std::string topic = report_topic(dev_id);
{
std::lock_guard<std::mutex> lock(mutex);
subscriptions.erase(topic);
acknowledged_subscriptions.erase(topic);
pending_subscriptions.erase(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;
}
}
state_cv.notify_all();
flush_subscription_change(); // ask the worker to emit UNSUBSCRIBE now (no reconnect)
return true;
}
void OrcaMqttConnection::clear_subscriptions() {
std::lock_guard<std::mutex> lock(mutex);
subscriptions.clear();
pending_subscriptions.clear();
pending_unsubscriptions.clear();
acknowledged_subscriptions.clear();
pending_subscribe_packets.clear();
pending_requests.clear();
}
bool OrcaMqttConnection::parse_endpoint(const std::string& url, Endpoint& endpoint) {
std::string rest;
std::string default_port;
if (url.rfind("wss://", 0) == 0) { rest = url.substr(6); default_port = "443"; }
else if (url.rfind("ws://", 0) == 0) { rest = url.substr(5); default_port = "80"; }
else return false;
const auto slash = rest.find('/');
const std::string authority = rest.substr(0, slash);
endpoint.target = (slash == std::string::npos) ? "/" : rest.substr(slash);
// host[:port] — leave an unbracketed IPv6 literal alone
const auto colon = authority.rfind(':');
if (colon != std::string::npos && authority.find(']') == std::string::npos) {
endpoint.host = authority.substr(0, colon);
endpoint.port = authority.substr(colon + 1);
} else {
endpoint.host = authority;
endpoint.port = default_port;
}
return !endpoint.host.empty() && !endpoint.port.empty() && !endpoint.target.empty();
}
void OrcaMqttConnection::append_string(std::vector<uint8_t>& packet, const std::string& value) {
if (value.size() > 0xffff)
throw std::runtime_error("MQTT string is too long");
packet.push_back(static_cast<uint8_t>(value.size() >> 8));
packet.push_back(static_cast<uint8_t>(value.size() & 0xff));
packet.insert(packet.end(), value.begin(), value.end());
}
void OrcaMqttConnection::prepend_remaining_length(std::vector<uint8_t>& packet, size_t length) {
std::vector<uint8_t> encoded;
do {
uint8_t byte = static_cast<uint8_t>(length % 128);
length /= 128;
if (length != 0)
byte |= 0x80;
encoded.push_back(byte);
} while (length != 0);
packet.insert(packet.begin() + 1, encoded.begin(), encoded.end());
}
std::vector<uint8_t> OrcaMqttConnection::make_connect_packet(
const std::string& client_id, const std::string& username,
const std::string& password, int keepalive_seconds) {
std::vector<uint8_t> packet{0x10};
append_string(packet, "MQTT");
packet.push_back(4); // protocol level 3.1.1
uint8_t flags = 0x02; // clean session
if (!username.empty()) { flags |= 0x80; if (!password.empty()) flags |= 0x40; }
packet.push_back(flags);
packet.push_back(static_cast<uint8_t>(keepalive_seconds >> 8));
packet.push_back(static_cast<uint8_t>(keepalive_seconds & 0xff));
append_string(packet, client_id.empty() ? "OrcaSlicer" : client_id);
if (!username.empty()) {
append_string(packet, username);
if (!password.empty()) append_string(packet, password);
}
prepend_remaining_length(packet, packet.size() - 1);
return packet;
}
std::string OrcaMqttConnection::report_topic(const std::string& device_id) { return "device/" + device_id + "/report"; }
std::string OrcaMqttConnection::request_topic(const std::string& id) { return "device/" + id + "/request"; }
std::vector<uint8_t> OrcaMqttConnection::make_publish_packet(const std::string& topic, const std::string& payload) {
std::vector<uint8_t> packet{0x30}; // PUBLISH, QoS 0, no retain
append_string(packet, topic); // no packet id at QoS 0
packet.insert(packet.end(), payload.begin(), payload.end());
prepend_remaining_length(packet, packet.size() - 1);
return packet;
}
std::vector<uint8_t> OrcaMqttConnection::make_subscribe_packet(uint16_t id, const std::string& topic, uint8_t qos) {
std::vector<uint8_t> packet{0x82};
packet.push_back(id >> 8); packet.push_back(id & 0xff);
append_string(packet, topic);
packet.push_back(qos);
prepend_remaining_length(packet, packet.size() - 1);
return packet;
}
std::vector<uint8_t> OrcaMqttConnection::make_unsubscribe_packet(uint16_t id, const std::string& topic) {
std::vector<uint8_t> packet{0xA2};
packet.push_back(id >> 8); packet.push_back(id & 0xff);
append_string(packet, topic);
prepend_remaining_length(packet, packet.size() - 1);
return packet;
}
std::vector<uint8_t> OrcaMqttConnection::make_ping_packet() { return {0xc0, 0}; }
void OrcaMqttConnection::ws_write(Connection& conn, const std::vector<uint8_t>& packet) {
if (packet.empty()) {
return;
}
if (conn.wss) {
conn.wss->binary(true);
conn.wss->write(boost::asio::buffer(packet));
} else if (conn.ws) {
conn.ws->binary(true);
conn.ws->write(boost::asio::buffer(packet));
}
}
void OrcaMqttConnection::close_connection(Connection& conn) {
boost::system::error_code error;
if (conn.wss) {
auto& socket = boost::beast::get_lowest_layer(*conn.wss).socket();
if (socket.cancel(error))
return;
if (socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, error))
return;
if (socket.close(error))
return;
} else if (conn.ws) {
auto& socket = boost::beast::get_lowest_layer(*conn.ws).socket();
if (socket.cancel(error))
return;
if (socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, error))
return;
if (socket.close(error))
return;
}
}
void OrcaMqttConnection::enqueue_packet(const std::shared_ptr<Connection>& conn,
std::vector<uint8_t> packet) {
if (!conn || packet.empty())
return;
conn->outbound_packets.emplace_back(std::make_shared<std::vector<uint8_t>>(std::move(packet)));
start_async_write(conn);
}
void OrcaMqttConnection::start_async_write(const std::shared_ptr<Connection>& conn) {
if (!conn || conn->write_in_progress || conn->outbound_packets.empty() || stopping.load())
return;
conn->write_in_progress = true;
const auto packet = conn->outbound_packets.front();
auto on_write = [this, conn](const boost::system::error_code& error, std::size_t) {
conn->write_in_progress = false;
if (error) {
if (!stopping.load())
conn->terminal_error = error;
conn->io_context.stop();
return;
}
conn->outbound_packets.pop_front();
start_async_write(conn);
};
if (conn->wss) {
conn->wss->binary(true);
conn->wss->async_write(boost::asio::buffer(*packet), std::move(on_write));
} else if (conn->ws) {
conn->ws->binary(true);
conn->ws->async_write(boost::asio::buffer(*packet), std::move(on_write));
} else {
conn->write_in_progress = false;
conn->outbound_packets.pop_front();
}
}
void OrcaMqttConnection::start_async_read(const std::shared_ptr<Connection>& conn) {
if (!conn || stopping.load())
return;
auto on_read = [this, conn](const boost::system::error_code& error, std::size_t) {
if (error) {
if (!stopping.load())
conn->terminal_error = error;
conn->io_context.stop();
return;
}
const std::string packet = boost::beast::buffers_to_string(conn->read_buffer.data());
conn->read_buffer.consume(conn->read_buffer.size());
handle_packet(packet);
start_async_read(conn);
};
if (conn->wss)
conn->wss->async_read(conn->read_buffer, std::move(on_read));
else if (conn->ws)
conn->ws->async_read(conn->read_buffer, std::move(on_read));
}
void OrcaMqttConnection::schedule_keepalive(const std::shared_ptr<Connection>& conn) {
const int keepalive = current_config.keepalive_seconds;
if (!conn || keepalive <= 0 || stopping.load())
return;
conn->keepalive_timer.expires_after(std::chrono::seconds(std::max(1, keepalive / 2)));
conn->keepalive_timer.async_wait([this, conn](const boost::system::error_code& error) {
if (error || stopping.load())
return;
enqueue_packet(conn, make_ping_packet());
schedule_keepalive(conn);
});
}
void OrcaMqttConnection::post_packet(const std::shared_ptr<Connection>& conn,
std::vector<uint8_t> packet) {
if (!conn || packet.empty())
return;
boost::asio::post(conn->io_context, [this, conn, packet = std::move(packet)]() mutable {
if (!stopping.load())
enqueue_packet(conn, std::move(packet));
});
}
std::size_t OrcaMqttConnection::ws_read(Connection& conn, boost::beast::flat_buffer& buffer,
boost::system::error_code& ec) {
if (conn.wss)
return conn.wss->read(buffer, ec);
if (conn.ws)
return conn.ws->read(buffer, ec);
ec = boost::asio::error::not_connected;
return 0;
}
void OrcaMqttConnection::ws_handshake(Connection& conn, const Config& config, const Endpoint& endpoint) {
const auto results = conn.resolver.resolve(endpoint.host, endpoint.port);
std::string token;
if (config.bearer_provider) {
token = config.bearer_provider();
}
auto decorator = [token](boost::beast::websocket::request_type& request) {
request.set(boost::beast::http::field::user_agent, "OrcaSlicer");
if (!token.empty())
request.set(boost::beast::http::field::authorization, "Bearer " + token);
request.set("Sec-WebSocket-Protocol", "mqtt");
};
boost::beast::http::response<boost::beast::http::string_body> response;
boost::system::error_code handshake_error;
if (config.use_tls) {
// stop() inspects the engaged optional under connection_mutex; publish it
// under the same lock, then release before the blocking connect.
{
std::lock_guard<std::mutex> lock(connection_mutex);
conn.wss.emplace(conn.io_context, conn.ssl_context);
}
auto& websocket = *conn.wss;
auto& stream = boost::beast::get_lowest_layer(websocket);
stream.expires_after(std::chrono::seconds(10));
stream.connect(results);
// Set SNI before the TLS handshake so the cloud edge selects the correct
// certificate.
auto& tls_stream = websocket.next_layer();
if (!SSL_set_tlsext_host_name(tls_stream.native_handle(), endpoint.host.c_str()))
throw std::runtime_error("failed to set Orca Cloud TLS server name");
if (!config.ca_file.empty())
conn.ssl_context.load_verify_file(config.ca_file);
else
conn.ssl_context.set_default_verify_paths();
Http::add_platform_root_certificates(conn.ssl_context.native_handle());
tls_stream.set_verify_mode(boost::asio::ssl::verify_peer);
tls_stream.set_verify_callback(boost::asio::ssl::host_name_verification(endpoint.host));
tls_stream.handshake(boost::asio::ssl::stream_base::client);
websocket.set_option(boost::beast::websocket::stream_base::decorator(decorator));
websocket.handshake(response, endpoint.host, endpoint.target, handshake_error);
} else {
{
std::lock_guard<std::mutex> lock(connection_mutex);
conn.ws.emplace(conn.io_context);
}
auto& websocket = *conn.ws;
auto& stream = boost::beast::get_lowest_layer(websocket);
stream.expires_after(std::chrono::seconds(10));
stream.connect(results);
websocket.set_option(boost::beast::websocket::stream_base::decorator(decorator));
websocket.handshake(response, endpoint.host, endpoint.target, handshake_error);
}
if (handshake_error) {
throw boost::system::system_error(handshake_error, "Orca WebSocket handshake");
}
if (response["Sec-WebSocket-Protocol"] != "mqtt") {
throw std::runtime_error("Orca WebSocket did not negotiate MQTT");
}
}
bool OrcaMqttConnection::send_request(const std::string& dev_id, const std::string& payload) {
if (dev_id.empty()) {
return false;
}
if (!connected.load()) {
return false;
}
std::shared_ptr<Connection> conn;
{
std::lock_guard<std::mutex> lock(connection_mutex);
conn = active_connection;
}
if (!conn) {
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);
return true;
}
}
post_packet(conn, make_publish_packet(request_topic(dev_id), payload));
return true;
}
void OrcaMqttConnection::connect_and_read() {
auto connection = std::make_shared<Connection>();
{
std::lock_guard<std::mutex> lock(connection_mutex);
active_connection = connection;
if (stopping.load()) {
active_connection.reset();
return;
}
}
auto clear_connection = [this, connection] {
close_connection(*connection);
std::lock_guard<std::mutex> lock(connection_mutex);
if (active_connection == connection)
active_connection.reset();
};
try {
Endpoint endpoint;
if (!parse_endpoint(current_config.url, endpoint)) {
throw std::runtime_error("invalid Orca Cloud WebSocket endpoint");
}
ws_handshake(*connection, current_config, endpoint);
// Auth precedence: a bearer_provider authenticates the WebSocket upgrade, so the
// CONNECT username/password fields are omitted entirely (the cloud form).
const bool use_bearer = static_cast<bool>(current_config.bearer_provider);
ws_write(*connection, make_connect_packet(current_config.client_id,
use_bearer ? std::string() : current_config.username,
use_bearer ? std::string() : current_config.password,
current_config.keepalive_seconds));
boost::beast::flat_buffer buffer;
expires_after(*connection, std::chrono::seconds(10));
boost::system::error_code connack_error;
ws_read(*connection, buffer, connack_error);
if (connack_error)
throw boost::system::system_error(connack_error, "read Orca MQTT CONNACK");
// Beast leaves this expiry armed after the synchronous CONNACK read above;
// disable it before starting the long-lived async WebSocket session.
expires_never(*connection);
const std::string connack = boost::beast::buffers_to_string(buffer.data());
// rc: 0 accepted, 1..5 refusal, -1 malformed/not a CONNACK.
const int rc = (connack.size() == 4 && static_cast<uint8_t>(connack[0]) == 0x20)
? static_cast<int>(static_cast<uint8_t>(connack[3]))
: -1;
m_last_connack_rc.store(rc);
if (rc != 0) {
if (rc == 4 || rc == 5) {
// Bad credentials / not authorized — retrying cannot help. Make run()'s
// loop exit and unblock any waiting start().
stopping.store(true);
{
std::lock_guard<std::mutex> lock(mutex);
initial_completed = true;
initial_result = false;
}
initial_cv.notify_all();
}
throw std::runtime_error("Orca MQTT CONNECT refused rc=" + std::to_string(rc));
}
// 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();
}
connection->async_session_started.store(true);
notify_state(true);
reconnect_delay_seconds.store(1); // a fresh CONNACK resets the backoff
send_current_subscriptions(connection);
start_async_read(connection);
schedule_keepalive(connection);
const std::size_t handlers_run = connection->io_context.run();
if (handlers_run == 0 && !stopping.load())
throw std::runtime_error("Orca MQTT event loop stopped unexpectedly");
if (connection->terminal_error && !stopping.load())
throw boost::system::system_error(connection->terminal_error, "read Orca MQTT message");
clear_connection();
if (!stopping.load())
notify_state(false);
} catch (...) {
clear_connection();
throw;
}
}
void OrcaMqttConnection::send_current_subscriptions(const std::shared_ptr<Connection>& conn) {
std::vector<std::string> topics;
{
std::lock_guard<std::mutex> lock(mutex);
topics.assign(subscriptions.begin(), subscriptions.end());
acknowledged_subscriptions.clear();
pending_subscribe_packets.clear();
for (const std::string& topic : topics)
pending_subscriptions.erase(topic);
}
for (const std::string& topic : topics) {
const uint16_t packet_id = next_packet_id++;
{
std::lock_guard<std::mutex> lock(mutex);
pending_subscribe_packets[packet_id] = topic;
}
enqueue_packet(conn, make_subscribe_packet(packet_id, topic, 1));
}
}
void OrcaMqttConnection::send_pending_subscriptions(const std::shared_ptr<Connection>& conn) {
std::vector<std::string> subscribe_topics;
std::vector<std::string> unsubscribe_topics;
{
std::lock_guard<std::mutex> lock(mutex);
subscribe_topics.assign(pending_subscriptions.begin(), pending_subscriptions.end());
unsubscribe_topics.assign(pending_unsubscriptions.begin(), pending_unsubscriptions.end());
pending_subscriptions.clear();
pending_unsubscriptions.clear();
}
for (const std::string& topic : subscribe_topics) {
const uint16_t packet_id = next_packet_id++;
{
std::lock_guard<std::mutex> lock(mutex);
pending_subscribe_packets[packet_id] = topic;
}
enqueue_packet(conn, make_subscribe_packet(packet_id, topic, 1));
}
for (const std::string& topic : unsubscribe_topics) {
const uint16_t packet_id = next_packet_id++;
enqueue_packet(conn, make_unsubscribe_packet(packet_id, topic));
}
}
void OrcaMqttConnection::handle_packet(const std::string& packet) {
if (packet.size() < 2) {
return;
}
const uint8_t header = static_cast<uint8_t>(packet[0]);
const uint8_t packet_type = header >> 4;
if (packet_type != 3) { // Only QoS 0 PUBLISH carries printer status.
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;
for (size_t index = 4; index < packet.size(); ++index) {
if (index != 4)
result_codes << ',';
result_codes << "0x" << std::hex << static_cast<unsigned int>(static_cast<uint8_t>(packet[index]));
}
// 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()) {
} else if (result == 0 || result == 1) {
for (const auto& request : requests) {
if (!send_request(request.first, request.second)) {
}
}
} else {
}
}
return;
}
size_t index = 1;
size_t multiplier = 1;
size_t remaining = 0;
uint8_t encoded = 0;
do {
if (index >= packet.size() || multiplier > 128 * 128 * 128) {
return;
}
encoded = static_cast<uint8_t>(packet[index++]);
remaining += (encoded & 0x7f) * multiplier;
multiplier *= 128;
} while ((encoded & 0x80) != 0);
const size_t remaining_end = index + remaining;
if (remaining_end > packet.size() || remaining < 2 || index + 2 > remaining_end) {
return;
}
const uint16_t topic_length = (static_cast<uint8_t>(packet[index]) << 8) |
static_cast<uint8_t>(packet[index + 1]);
index += 2;
if (topic_length > packet.size() - index) {
return;
}
const std::string topic(packet.data() + index, topic_length);
index += topic_length;
if (((header >> 1) & 0x03) != 0) {
if (index + 2 > remaining_end) {
return;
}
index += 2; // QoS 1/2 packet identifier; the service currently sends QoS 0.
}
const size_t payload_size = remaining_end - index;
// topic is "device/<id>/report" (or "/request"); hand the id up, drop anything else.
std::string dev_id;
if (topic.rfind("device/", 0) == 0) {
const size_t id_start = 7;
const size_t id_end = topic.rfind('/');
if (id_end != std::string::npos && id_end > id_start)
dev_id = topic.substr(id_start, id_end - id_start);
}
if (dev_id.empty()) {
} else if (on_message) {
on_message(dev_id, packet.substr(index, remaining_end - index));
} else {
}
}
void OrcaMqttConnection::notify_state(bool is_now_connected) {
StateHandler callback;
bool initial = false;
{
std::lock_guard<std::mutex> lock(mutex);
connected = is_now_connected;
initial = !initial_completed;
if (initial) {
initial_result = is_now_connected;
initial_completed = true;
}
callback = on_state;
}
if (initial)
initial_cv.notify_all();
else if (callback)
callback(is_now_connected, false);
}
void OrcaMqttConnection::run() {
while (!stopping.load()) {
const int retry_seconds = reconnect_delay_seconds.load();
const uint64_t attempt = ++m_attempt_number;
try {
connect_and_read();
} catch (const std::exception& error) {
if (!stopping.load())
notify_state(false);
}
if (stopping.load())
break;
// Grow the backoff only across attempts that never reached CONNACK; a
// successful connection resets reconnect_delay_seconds to 1 (connect_and_read).
reconnect_delay_seconds.store(std::min(retry_seconds * 2, 30));
std::unique_lock<std::mutex> lock(mutex);
state_cv.wait_for(lock, std::chrono::seconds(retry_seconds), [this] { return stopping.load(); });
}
}
} // namespace Slic3r
-155
View File
@@ -1,155 +0,0 @@
#ifndef slic3r_OrcaMqttConnection_hpp_
#define slic3r_OrcaMqttConnection_hpp_
#include <boost/asio/ssl.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/ssl.hpp>
#include <boost/beast/websocket.hpp>
#include <atomic>
#include <condition_variable>
#include <deque>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <set>
#include <string>
#include <thread>
#include <utility>
#include <vector>
#include <cstddef>
#include <cstdint>
namespace Slic3r {
// Minimal MQTT 3.1.1 codec + WebSocket transport (ws:// and wss://), shared by the
// 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
{
public:
using TokenProvider = std::function<std::string()>;
using MessageHandler = std::function<void(const std::string&, const std::string&)>;
using StateHandler = std::function<void(bool connected, bool initial)>;
struct Endpoint { std::string host; std::string port; std::string target; };
struct Config {
std::string url;
bool use_tls = false;
std::string ca_file;
TokenProvider bearer_provider; // set => bearer on WS upgrade, CONNECT creds omitted
std::string username;
std::string password;
std::string client_id = "OrcaSlicer";
int keepalive_seconds = 60;
};
static bool parse_endpoint(const std::string& url, Endpoint& endpoint);
// Build an MQTT 3.1.1 CONNECT packet. Clean-session is always set; the
// username/password connect flags and payload fields are added only when
// username is non-empty (the cloud form authenticates via a bearer on the
// WebSocket upgrade and omits CONNECT credentials). Public for unit tests.
static std::vector<uint8_t> make_connect_packet(const std::string& client_id,
const std::string& username,
const std::string& password,
int keepalive_seconds);
// Topic-string helpers for the per-device request/report channels and the
// MQTT 3.1.1 PUBLISH / SUBSCRIBE / UNSUBSCRIBE packet builders. All public
// for unit tests. make_publish_packet emits QoS 0 (no packet identifier).
static std::string request_topic(const std::string& dev_id); // "device/<id>/request"
static std::string report_topic(const std::string& dev_id); // "device/<id>/report"
static std::vector<uint8_t> make_publish_packet(const std::string& topic, const std::string& payload);
static std::vector<uint8_t> make_subscribe_packet(uint16_t packet_id, const std::string& topic, uint8_t qos);
static std::vector<uint8_t> make_unsubscribe_packet(uint16_t packet_id, const std::string& topic);
~OrcaMqttConnection();
bool start(const Config& config, MessageHandler on_message, StateHandler on_state);
void stop();
// True while the worker thread is alive (connected OR retrying). Lets callers
// avoid restarting a healthy connection.
bool is_running() const;
// True once CONNACK has been received and the socket has not since dropped.
bool is_connected() const { return connected.load(); }
bool subscribe(const std::string& dev_id);
bool unsubscribe(const std::string& dev_id);
// Last MQTT CONNACK return code: 0 ok, 1..5 refusal, -1 none seen this attempt.
int last_connack_rc() const { return m_last_connack_rc.load(); }
void clear_subscriptions();
bool send_request(const std::string& dev_id, const std::string& payload);
private:
// The endpoint may be either a TLS (wss://) or a plaintext (ws://) WebSocket;
// Connection holds whichever one is engaged and the ws_* helpers below
// dispatch on it.
using TlsWebSocket = boost::beast::websocket::stream<
boost::asio::ssl::stream<boost::beast::tcp_stream>>;
using PlainWebSocket = boost::beast::websocket::stream<boost::beast::tcp_stream>;
struct Connection;
static void append_string(std::vector<uint8_t>& packet, const std::string& value);
static void prepend_remaining_length(std::vector<uint8_t>& packet, size_t length);
static std::vector<uint8_t> make_ping_packet();
// Transport dispatch: each forwards to conn.wss (TLS) or conn.ws (plaintext).
// These synchronous operations are called only by the MQTT worker during
// connection setup. Once the MQTT session is established, all socket I/O is
// asynchronous and owned by that worker's io_context.
void ws_write(Connection& conn, const std::vector<uint8_t>& packet);
std::size_t ws_read(Connection& conn, boost::beast::flat_buffer& buffer, boost::system::error_code& ec);
void ws_handshake(Connection& conn, const Config& config, const Endpoint& endpoint);
void close_connection(Connection& conn);
void enqueue_packet(const std::shared_ptr<Connection>& conn, std::vector<uint8_t> packet);
void start_async_write(const std::shared_ptr<Connection>& conn);
void start_async_read(const std::shared_ptr<Connection>& conn);
void schedule_keepalive(const std::shared_ptr<Connection>& conn);
void post_packet(const std::shared_ptr<Connection>& conn, std::vector<uint8_t> packet);
// Ask the MQTT worker to emit subscription changes on its own io_context.
void flush_subscription_change();
void connect_and_read();
void send_current_subscriptions(const std::shared_ptr<Connection>& conn);
void send_pending_subscriptions(const std::shared_ptr<Connection>& conn);
void handle_packet(const std::string& packet);
void notify_state(bool is_now_connected);
void run();
std::atomic_bool stopping{true};
std::atomic_int reconnect_delay_seconds{1};
// Serialises the whole of start() and stop() against each other, so the UI
// thread's stop() (disconnect / dtor) cannot race the connect thread's start()
// into a concurrent worker.join(). Recursive because start() calls stop().
std::recursive_mutex lifecycle_mutex;
std::thread worker;
std::mutex mutex;
std::mutex connection_mutex;
std::shared_ptr<Connection> active_connection;
std::condition_variable initial_cv;
std::condition_variable state_cv;
Config current_config;
MessageHandler on_message;
StateHandler on_state;
// Full report-topic strings ("device/<id>/report"), not bare device ids.
std::set<std::string> subscriptions;
std::set<std::string> pending_subscriptions;
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<int> m_last_connack_rc{-1};
uint64_t m_attempt_number{0}; // worker-thread diagnostic sequence
bool initial_result{false};
bool initial_completed{false};
std::atomic_bool connected{false};
};
} // namespace Slic3r
#endif // slic3r_OrcaMqttConnection_hpp_
File diff suppressed because it is too large Load Diff
+14 -137
View File
@@ -3,28 +3,19 @@
#include "IPrinterAgent.hpp" #include "IPrinterAgent.hpp"
#include "ICloudServiceAgent.hpp" #include "ICloudServiceAgent.hpp"
#include "OrcaCloudServiceAgent.hpp"
#include "OrcaMqttConnection.hpp"
#include <atomic>
#include <cstdint>
#include <functional>
#include <string> #include <string>
#include <mutex> #include <mutex>
#include <memory> #include <memory>
#include <thread>
namespace Slic3r { namespace Slic3r {
class OrcaCloudServiceAgent;
/** /**
* OrcaPrinterAgent - OrcaSonar MQTT printer agent. * OrcaPrinterAgent - Stub implementation for printer operations.
* *
* LAN and cloud commands use the same OrcaSonar protocol payloads; only the * All printer-related operations are currently stubs that return success.
* MQTT connection selected by route_send() differs. * Actual printer connectivity requires the BBL SDK or future Orca implementation.
*/ */
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;
@@ -34,20 +25,27 @@ public:
// ======================================================================== // ========================================================================
void set_cloud_agent(std::shared_ptr<ICloudServiceAgent> cloud) override; void set_cloud_agent(std::shared_ptr<ICloudServiceAgent> cloud) override;
CameraStreamMode get_camera_stream_mode() const override;
std::string get_camera_url() const override;
// Communication // Communication
int send_message(std::string dev_id, std::string json_str, int qos, int flag) override; int send_message(std::string dev_id, std::string json_str, int qos, int flag) override;
int connect_printer(const PrinterConnectionParams& params) override; int connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) override;
int disconnect_printer() override; int disconnect_printer() override;
int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag) override; int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag) override;
// Certificates
int check_cert() override;
void install_device_cert(std::string dev_id, bool lan_only) override;
// Discovery // Discovery
bool start_discovery(bool start, bool sending) override; bool start_discovery(bool start, bool sending) override;
// Binding // Binding
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 unbind(std::string dev_id) 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 set_server_callback(OnServerErrFn fn) override; int set_server_callback(OnServerErrFn fn) override;
// Machine Selection // Machine Selection
@@ -79,131 +77,10 @@ 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, int ams_id, int 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).
void run_connect_sequence_for_test(const std::string& dev_id)
{
emit_connect_sequence(dev_id, [](const std::string&) {}, [](const std::string&) {});
}
// Test-only: advance the LAN connection epoch without a connect/disconnect cycle.
void bump_lan_generation_for_test() { ++m_lan_generation; }
// Test-only: the same for the (independent) cloud selection epoch.
void bump_cloud_generation_for_test() { ++m_cloud_generation; }
FilamentSyncMode get_filament_sync_mode() const override { return FilamentSyncMode::subscription; }
protected:
// Forward one inbound printer message to on_message_fn or on_local_message_fn (marshalled onto the UI
// 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, bool local);
// 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);
// Orca-dialect -> Bambu-dialect compatibility shim for inbound reports: the single
// place Orca Protocol JSON is rewritten into the shapes MachineObject::parse_json
// already handles, so parse_json needs no Orca-specific changes. Self-contained
// (its cache is a function-local static) and deletable together with its call site
// once parse_json reads the Orca dialect natively. See the definition for the
// per-rule detail. Returns the payload unchanged when no rule applies.
std::string merge_capabilities(const std::string& dev_id, const std::string& payload);
// Report the asynchronous LAN connection state using the same callback contract as
// the other printer agents. The transport result cannot be returned by
// connect_printer(), which only starts the worker.
void dispatch_local_connect(int state, const std::string& dev_id, const std::string& message);
// The LAN inbound-message handler for one connection generation: forwards to
// deliver_to_sink only while `generation` is still the live epoch.
std::function<void(const std::string&, const std::string&)> make_lan_message_handler(uint64_t generation);
// Pure LAN-address parsing + client-id. protected static so the test Probe reaches them.
static bool parse_lan_endpoint(const std::string& dev_ip, std::string& host, std::string& port);
static std::string make_lan_client_id(const std::string& dev_id);
// Test hook: the ws:// URL connect_printer built for the current LAN session ("" if none).
std::string lan_connection_target() const;
// Shared post-connect sequence: SUBSCRIBE, then pushing.start, pushall,
// info.get_version, info.get_capabilities. Runs identically on LAN and cloud.
void on_connected(const std::string& dev_id, OrcaMqttConnection* conn, uint64_t generation);
// The post-connect command sequence, factored behind a seam so a test can
// observe the SUBSCRIBE + 4 request payloads without a live OrcaMqttConnection.
virtual void emit_connect_sequence(const std::string& dev_id,
std::function<void(const std::string&)> subscribe,
std::function<void(const std::string&)> request);
static std::string seq(int n); // decimal string in the OrcaSlicer 20000..29999 band
static std::string build_pushing_start(const std::string& sequence_id);
static std::string build_pushing_stop(const std::string& sequence_id);
static std::string build_pushall(const std::string& sequence_id);
static std::string build_get_version(const std::string& sequence_id);
static std::string build_get_capabilities(const std::string& sequence_id);
private: private:
class OrcaSonarDiscovery;
std::string log_dir; std::string log_dir;
std::string selected_machine; std::string selected_machine;
enum CurrentConn { NONE, CLOUD, LAN };
static const char* connection_type_name(CurrentConn connection);
// 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;
std::shared_ptr<ICloudServiceAgent> m_cloud_agent; std::shared_ptr<ICloudServiceAgent> m_cloud_agent;
std::unique_ptr<OrcaMqttConnection> lan_mqtt_connection;
// Two independent epochs: a cloud (de)selection must not fence the live LAN
// feed, and vice versa. Each transport's connect thread and inbound handler
// compare against their own counter only.
std::atomic<uint64_t> m_lan_generation{0};
std::atomic<uint64_t> m_cloud_generation{0};
// The short-lived threads that run the blocking initial connect for the current
// LAN / cloud session. Joined members (never detached) so they cannot outlive
// *this or the connection they hold a raw pointer to.
std::thread m_lan_connect_thread;
std::thread m_cloud_connect_thread;
std::unique_ptr<OrcaSonarDiscovery> m_discovery;
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
bool m_lan_use_ssl = false; // guarded by state_mutex
std::string m_lan_ca_file; // guarded by state_mutex
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();
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 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 // Callbacks
OnMsgArrivedFn on_ssdp_msg_fn; OnMsgArrivedFn on_ssdp_msg_fn;
-34
View File
@@ -1,34 +0,0 @@
#pragma once
#include <string>
namespace Slic3r {
enum LiveviewLocal {
LVL_None,
LVL_Disable,
LVL_Local,
LVL_Rtsps,
LVL_Rtsp
};
enum LiveviewRemote {
LVR_None,
LVR_Tutk,
LVR_Agora,
LVR_TutkAgora
};
enum FileLocal {
FL_None,
FL_Local
};
enum FileRemote {
FR_None,
FR_Tutk,
FR_Agora,
FR_TutkAgora
};
} // namespace Slic3r
+29 -153
View File
@@ -1,6 +1,5 @@
#include "QidiPrinterAgent.hpp" #include "QidiPrinterAgent.hpp"
#include "Http.hpp" #include "Http.hpp"
#include "IPrinterAgent.hpp"
#include "libslic3r/PresetBundle.hpp" #include "libslic3r/PresetBundle.hpp"
#include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI_App.hpp"
@@ -9,7 +8,6 @@
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <cctype> #include <cctype>
#include <sstream> #include <sstream>
#include <thread>
using json = nlohmann::json; using json = nlohmann::json;
@@ -29,15 +27,6 @@ bool has_visible_base_preset(const PresetCollection& filaments, const std::strin
return false; return false;
} }
// RAII decrement for MoonrakerPrinterAgent::filament_fetch_in_flight — guarantees the
// counter drops back down on every exit path (early return or fall-through) inside the
// detached fetch thread below, so ~MoonrakerPrinterAgent()'s wait loop can't stall forever.
struct InFlightGuard
{
std::atomic<int>& counter;
~InFlightGuard() { counter.fetch_sub(1, std::memory_order_relaxed); }
};
} // anonymous namespace } // anonymous namespace
const std::string QidiPrinterAgent_VERSION = "0.0.1"; const std::string QidiPrinterAgent_VERSION = "0.0.1";
@@ -51,148 +40,51 @@ AgentInfo QidiPrinterAgent::get_agent_info_static()
return AgentInfo{"qidi", "Qidi", QidiPrinterAgent_VERSION, "Qidi printer agent"}; return AgentInfo{"qidi", "Qidi", QidiPrinterAgent_VERSION, "Qidi printer agent"};
} }
FilamentSyncMode QidiPrinterAgent::get_filament_sync_mode() const bool QidiPrinterAgent::fetch_filament_info(std::string dev_id)
{ {
if (GUI::wxGetApp().app_config->get_bool("use_printer_agents"))
return FilamentSyncMode::subscription;
return FilamentSyncMode::pull;
}
bool QidiPrinterAgent::fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode)
{
if (sync_mode != get_filament_sync_mode())
return false;
// Snapshot only what the fetch needs, rather than reading device_info live from the
// background thread below — device_info can be concurrently rewritten by a reconnect
// on another thread while this fetch is still in flight.
ConnectionSettings connection = get_connection_settings();
std::string model_id = device_info.model_id;
std::string model_name = device_info.model_name;
filament_fetch_in_flight.fetch_add(1, std::memory_order_relaxed);
std::thread([this, connection = std::move(connection), model_id, model_name]() mutable {
InFlightGuard guard{filament_fetch_in_flight};
std::string error; std::string error;
// 1. Fetch device info and infer series_id // 1. Fetch device info and infer series_id
std::string series_id; std::string series_id;
{ {
MoonrakerDeviceInfo info; MoonrakerDeviceInfo info;
if (fetch_device_info(connection, info, error)) { if (fetch_device_info(device_info.base_url, device_info.api_key, info, error)) {
series_id = infer_series_id(info.model_id, info.dev_name); series_id = infer_series_id(info.model_id, info.dev_name);
} }
} }
if (series_id.empty()) { if (series_id.empty()) {
// Fall back to the configured Orca model if Moonraker doesn't expose a usable identifier. // Fall back to the configured Orca model if Moonraker doesn't expose a usable identifier.
series_id = infer_series_id(model_id, model_name); series_id = infer_series_id(device_info.model_id, device_info.model_name);
} }
// 2. Fetch filament dictionary // 2. Fetch filament dictionary
QidiFilamentDict dict; QidiFilamentDict dict;
if (!fetch_filament_dict(connection, dict, error)) { if (!fetch_filament_dict(device_info.base_url, device_info.api_key, dict, error)) {
BOOST_LOG_TRIVIAL(warning) << "QidiPrinterAgent::fetch_filament_info: Failed to fetch filament dict: " << error; BOOST_LOG_TRIVIAL(warning) << "QidiPrinterAgent::fetch_filament_info: Failed to fetch filament dict: " << error;
} }
// 3. Fetch slot info and build AmsTrayData directly // 3. Fetch slot info and build AmsTrayData directly
std::vector<AmsTrayData> trays; std::vector<AmsTrayData> trays;
int box_count = 0; int box_count = 0;
if (!fetch_slot_info(connection, dict, series_id, trays, box_count, error)) { if (!fetch_slot_info(device_info.base_url, device_info.api_key, dict, series_id, trays, box_count, error)) {
BOOST_LOG_TRIVIAL(warning) << "QidiPrinterAgent::fetch_filament_info: Failed to fetch slot info: " << error; BOOST_LOG_TRIVIAL(warning) << "QidiPrinterAgent::fetch_filament_info: Failed to fetch slot info: " << error;
return; return false;
} }
// 4. Build the AMS payload // 4. Build the AMS payload
build_ams_payload(box_count, box_count * 4 - 1, trays); build_ams_payload(box_count, box_count * 4 - 1, trays);
}).detach();
return true; return true;
} }
bool QidiPrinterAgent::apply_box_mapping(const PrintParams& params) const bool QidiPrinterAgent::fetch_slot_info(const std::string& base_url,
{ const std::string& api_key,
// enable_box mirrors task_use_ams: engage the multi-color box only when this
// job actually routes filament through it. (See qidi-ams-findings.md §2/§8.3 —
// if firmware treats enable_box as "a box exists" rather than "use it this job",
// switch this gate to HasAms()/box_count instead.)
const int enable = params.task_use_ams ? 1 : 0;
if (!send_gcode(device_info.dev_id, "SAVE_VARIABLE VARIABLE=enable_box VALUE=" + std::to_string(enable))) {
BOOST_LOG_TRIVIAL(error) << "QidiPrinterAgent::apply_box_mapping: failed to set enable_box";
return false;
}
// When the box isn't used this job, leave the existing value_t<tool> slot
// assignments untouched (enable_box=0 is enough to disengage it).
if (!enable)
return true;
if (params.ams_mapping.empty()) {
BOOST_LOG_TRIVIAL(warning) << "QidiPrinterAgent::apply_box_mapping: enable_box set but ams_mapping is empty";
return true;
}
// ams_mapping (v0) is a JSON array indexed by filament/tool; each value is the
// physical box slot (-1 = unmapped). Mirror it onto the printer's value_t<tool>
// variables: SAVE_VARIABLE VARIABLE=value_t<tool> VALUE='slot<n>'.
auto mapping = nlohmann::json::parse(params.ams_mapping, nullptr, /*allow_exceptions*/ false);
if (mapping.is_discarded() || !mapping.is_array()) {
BOOST_LOG_TRIVIAL(error) << "QidiPrinterAgent::apply_box_mapping: invalid ams_mapping: " << params.ams_mapping;
return false;
}
for (size_t tool = 0; tool < mapping.size(); ++tool) {
if (!mapping[tool].is_number_integer())
continue;
const int slot = mapping[tool].get<int>();
if (slot < 0)
continue; // unmapped filament — skip
const std::string gcode = "SAVE_VARIABLE VARIABLE=value_t" + std::to_string(tool) +
" VALUE=\"'slot" + std::to_string(slot) + "'\"";
if (!send_gcode(device_info.dev_id, gcode)) {
BOOST_LOG_TRIVIAL(error) << "QidiPrinterAgent::apply_box_mapping: failed to set value_t" << tool;
return false;
}
}
return true;
}
int QidiPrinterAgent::start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn)
{
if (!apply_box_mapping(params))
return BAMBU_NETWORK_ERR_PRINT_LP_PUBLISH_MSG_FAILED;
return MoonrakerPrinterAgent::start_local_print(std::move(params), update_fn, cancel_fn);
}
int QidiPrinterAgent::start_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn)
{
if (!apply_box_mapping(params))
return BAMBU_NETWORK_ERR_PRINT_LP_PUBLISH_MSG_FAILED;
return MoonrakerPrinterAgent::start_print(std::move(params), update_fn, cancel_fn, wait_fn);
}
int QidiPrinterAgent::start_local_print_with_record(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn)
{
if (!apply_box_mapping(params))
return BAMBU_NETWORK_ERR_PRINT_WR_UPLOAD_FTP_FAILED;
return MoonrakerPrinterAgent::start_local_print_with_record(std::move(params), update_fn, cancel_fn, wait_fn);
}
int QidiPrinterAgent::start_sdcard_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn)
{
if (!apply_box_mapping(params))
return BAMBU_NETWORK_ERR_PRINT_LP_PUBLISH_MSG_FAILED;
return MoonrakerPrinterAgent::start_sdcard_print(std::move(params), update_fn, cancel_fn);
}
bool QidiPrinterAgent::fetch_slot_info(const ConnectionSettings& connection,
const QidiFilamentDict& dict, const QidiFilamentDict& dict,
const std::string& series_id, const std::string& series_id,
std::vector<AmsTrayData>& trays, std::vector<AmsTrayData>& trays,
int& box_count, int& box_count,
std::string& error) std::string& error)
{ {
std::string url = join_url(connection.base_url, "/printer/objects/query?save_variables=variables"); std::string url = join_url(base_url, "/printer/objects/query?save_variables=variables");
for (int i = 0; i < 16; ++i) { for (int i = 0; i < 16; ++i) {
url += "&box_stepper%20slot" + std::to_string(i) + "=runout_button"; url += "&box_stepper%20slot" + std::to_string(i) + "=runout_button";
} }
@@ -202,9 +94,8 @@ bool QidiPrinterAgent::fetch_slot_info(const ConnectionSettings& connection,
std::string http_error; std::string http_error;
auto http = Http::get(url); auto http = Http::get(url);
configure_http(http, connection); if (!api_key.empty()) {
if (!connection.api_key.empty()) { http.header("X-Api-Key", api_key);
http.header("X-Api-Key", connection.api_key);
} }
http.timeout_connect(5) http.timeout_connect(5)
.timeout_max(10) .timeout_max(10)
@@ -229,10 +120,20 @@ bool QidiPrinterAgent::fetch_slot_info(const ConnectionSettings& connection,
return false; return false;
} }
nlohmann::json status; auto json = nlohmann::json::parse(response_body, nullptr, false, true);
nlohmann::json variables; if (json.is_discarded()) {
if (!parse_slot_response(response_body, status, variables, error)) error = "Invalid JSON response";
return false; return false;
}
if (!json.contains("result") || !json["result"].contains("status") || !json["result"]["status"].contains("save_variables") ||
!json["result"]["status"]["save_variables"].contains("variables")) {
error = "Unexpected JSON structure";
return false;
}
auto& variables = json["result"]["status"]["save_variables"]["variables"];
auto& status = json["result"]["status"];
box_count = variables.value("box_count", 1); box_count = variables.value("box_count", 1);
if (box_count < 0) { if (box_count < 0) {
@@ -307,45 +208,20 @@ bool QidiPrinterAgent::fetch_slot_info(const ConnectionSettings& connection,
return true; return true;
} }
bool QidiPrinterAgent::parse_slot_response(const std::string& response_body, bool QidiPrinterAgent::fetch_filament_dict(const std::string& base_url,
nlohmann::json& status, const std::string& api_key,
nlohmann::json& variables,
std::string& error)
{
auto json = nlohmann::json::parse(response_body, nullptr, false, true);
if (json.is_discarded()) {
error = "Invalid JSON response";
return false;
}
if (!json.is_object() || !json.contains("result") || !json["result"].is_object() || !json["result"].contains("status") ||
!json["result"]["status"].is_object() || !json["result"]["status"].contains("save_variables") ||
!json["result"]["status"]["save_variables"].is_object() || !json["result"]["status"]["save_variables"].contains("variables") ||
!json["result"]["status"]["save_variables"]["variables"].is_object()) {
// why: Qidi firmware may send null here, but json::value() throws for it.
error = "Unexpected JSON structure: save_variables.variables must be an object";
return false;
}
status = json["result"]["status"];
variables = status["save_variables"]["variables"];
return true;
}
bool QidiPrinterAgent::fetch_filament_dict(const ConnectionSettings& connection,
QidiFilamentDict& dict, QidiFilamentDict& dict,
std::string& error) const std::string& error) const
{ {
std::string url = join_url(connection.base_url, "/server/files/config/officiall_filas_list.cfg"); std::string url = join_url(base_url, "/server/files/config/officiall_filas_list.cfg");
std::string response_body; std::string response_body;
bool success = false; bool success = false;
std::string http_error; std::string http_error;
auto http = Http::get(url); auto http = Http::get(url);
configure_http(http, connection); if (!api_key.empty()) {
if (!connection.api_key.empty()) { http.header("X-Api-Key", api_key);
http.header("X-Api-Key", connection.api_key);
} }
http.timeout_connect(5) http.timeout_connect(5)
.timeout_max(10) .timeout_max(10)
+4 -21
View File
@@ -1,9 +1,7 @@
#ifndef __QIDI_PRINTER_AGENT_HPP__ #ifndef __QIDI_PRINTER_AGENT_HPP__
#define __QIDI_PRINTER_AGENT_HPP__ #define __QIDI_PRINTER_AGENT_HPP__
#include "IPrinterAgent.hpp"
#include "MoonrakerPrinterAgent.hpp" #include "MoonrakerPrinterAgent.hpp"
#include "nlohmann/json_fwd.hpp"
#include <map> #include <map>
#include <string> #include <string>
@@ -21,25 +19,9 @@ public:
AgentInfo get_agent_info() override { return get_agent_info_static(); } AgentInfo get_agent_info() override { return get_agent_info_static(); }
// Override filament sync (Qidi-specific implementation) // Override filament sync (Qidi-specific implementation)
bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode = FilamentSyncMode::pull) override; bool fetch_filament_info(std::string dev_id) override;
static bool parse_slot_response(const std::string& response_body,
nlohmann::json& status,
nlohmann::json& variables,
std::string& error);
// Print operations — emit QiDi multi-color box config, then delegate to base.
int start_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override;
int start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) override;
int start_local_print_with_record(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override;
int start_sdcard_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) override;
FilamentSyncMode get_filament_sync_mode() const override;
private: private:
// Push enable_box + value_t<tool> SAVE_VARIABLEs before a print starts.
// Returns false if any command fails (caller should abort the print).
bool apply_box_mapping(const PrintParams& params) const;
struct QidiFilamentDict struct QidiFilamentDict
{ {
std::map<int, std::string> colors; std::map<int, std::string> colors;
@@ -47,13 +29,14 @@ private:
}; };
// Qidi-specific methods // Qidi-specific methods
bool fetch_slot_info(const ConnectionSettings& connection, bool fetch_slot_info(const std::string& base_url,
const std::string& api_key,
const QidiFilamentDict& dict, const QidiFilamentDict& dict,
const std::string& series_id, const std::string& series_id,
std::vector<AmsTrayData>& trays, std::vector<AmsTrayData>& trays,
int& box_count, int& box_count,
std::string& error); std::string& error);
bool fetch_filament_dict(const ConnectionSettings& connection, QidiFilamentDict& dict, std::string& error) const; bool fetch_filament_dict(const std::string& base_url, const std::string& api_key, QidiFilamentDict& dict, std::string& error) const;
std::string normalize_filament_type(const std::string& filament_type); std::string normalize_filament_type(const std::string& filament_type);
std::string infer_series_id(const std::string& model_id, const std::string& dev_name); std::string infer_series_id(const std::string& model_id, const std::string& dev_name);
std::string normalize_model_key(std::string value); std::string normalize_model_key(std::string value);
+16 -74
View File
@@ -1,14 +1,10 @@
#include "SnapmakerPrinterAgent.hpp" #include "SnapmakerPrinterAgent.hpp"
#include "Http.hpp" #include "Http.hpp"
#include "IPrinterAgent.hpp"
#include "libslic3r/PresetBundle.hpp" #include "libslic3r/PresetBundle.hpp"
#include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI_App.hpp"
#include "nlohmann/json.hpp" #include "nlohmann/json.hpp"
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <chrono>
#include <sstream>
#include <thread>
using json = nlohmann::json; using json = nlohmann::json;
@@ -17,13 +13,6 @@ namespace Slic3r {
namespace { namespace {
constexpr const char* SNAPMAKER_AGENT_VERSION = "0.0.1"; constexpr const char* SNAPMAKER_AGENT_VERSION = "0.0.1";
constexpr int64_t CAMERA_REFRESH_INTERVAL_MS = 300'000;
int64_t now_ms()
{
return std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count();
}
// Safely access a parallel array by index, returning a fallback if out of bounds. // Safely access a parallel array by index, returning a fallback if out of bounds.
template<typename T> template<typename T>
@@ -80,31 +69,6 @@ std::string find_closest_color_preset_by_vendor_and_type(const PresetCollection&
SnapmakerPrinterAgent::SnapmakerPrinterAgent(std::string log_dir) : MoonrakerPrinterAgent(std::move(log_dir)) {} SnapmakerPrinterAgent::SnapmakerPrinterAgent(std::string log_dir) : MoonrakerPrinterAgent(std::move(log_dir)) {}
void SnapmakerPrinterAgent::start_camera_monitor()
{
enqueue_command([this] {
send_ws_rpc("camera.start_monitor",
{{"domain", "lan"}, {"interval", 0}, {"expect_pw", false}});
});
m_camera_last_fire_ms.store(now_ms());
}
void SnapmakerPrinterAgent::on_status_loop_tick(const std::string& dev_id)
{
(void) dev_id;
const int64_t last = m_camera_last_fire_ms.load();
if (last == 0 || now_ms() - last >= CAMERA_REFRESH_INTERVAL_MS) {
start_camera_monitor();
}
}
int SnapmakerPrinterAgent::command_start_camera(std::string dev_id)
{
(void) dev_id;
start_camera_monitor();
return BAMBU_NETWORK_SUCCESS;
}
AgentInfo SnapmakerPrinterAgent::get_agent_info_static() AgentInfo SnapmakerPrinterAgent::get_agent_info_static()
{ {
return AgentInfo{"snapmaker", "Snapmaker", SNAPMAKER_AGENT_VERSION, "Snapmaker printer agent"}; return AgentInfo{"snapmaker", "Snapmaker", SNAPMAKER_AGENT_VERSION, "Snapmaker printer agent"};
@@ -140,33 +104,17 @@ std::string SnapmakerPrinterAgent::combine_filament_type(const std::string& type
return base; return base;
} }
bool SnapmakerPrinterAgent::fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode) bool SnapmakerPrinterAgent::fetch_filament_info(std::string dev_id)
{ {
(void) dev_id; std::string url = join_url(device_info.base_url, "/printer/objects/query?print_task_config&filament_detect");
if (sync_mode != get_filament_sync_mode())
return false;
const std::string base_url = device_info.base_url;
const std::string api_key = device_info.api_key;
filament_fetch_in_flight.fetch_add(1, std::memory_order_relaxed);
std::thread([this, base_url, api_key]() {
struct InFlightGuard
{
std::atomic<int>& counter;
~InFlightGuard() { counter.fetch_sub(1, std::memory_order_relaxed); }
} guard{filament_fetch_in_flight};
const std::string url = join_url(base_url, "/printer/objects/query?print_task_config&filament_detect");
std::string response_body; std::string response_body;
bool success = false; bool success = false;
std::string http_error; std::string http_error;
auto http = Http::get(url); auto http = Http::get(url);
if (!api_key.empty()) { if (!device_info.api_key.empty()) {
http.header("X-Api-Key", api_key); http.header("X-Api-Key", device_info.api_key);
} }
http.timeout_connect(5) http.timeout_connect(5)
.timeout_max(10) .timeout_max(10)
@@ -188,19 +136,20 @@ bool SnapmakerPrinterAgent::fetch_filament_info(std::string dev_id, FilamentSync
if (!success) { if (!success) {
BOOST_LOG_TRIVIAL(warning) << "SnapmakerPrinterAgent::fetch_filament_info: HTTP request failed: " << http_error; BOOST_LOG_TRIVIAL(warning) << "SnapmakerPrinterAgent::fetch_filament_info: HTTP request failed: " << http_error;
return; return false;
} }
auto json = nlohmann::json::parse(response_body, nullptr, false, true); auto json = nlohmann::json::parse(response_body, nullptr, false, true);
if (json.is_discarded()) { if (json.is_discarded()) {
BOOST_LOG_TRIVIAL(warning) << "SnapmakerPrinterAgent::fetch_filament_info: Invalid JSON response"; BOOST_LOG_TRIVIAL(warning) << "SnapmakerPrinterAgent::fetch_filament_info: Invalid JSON response";
return; return false;
} }
// Navigate to result.status.print_task_config // Navigate to result.status.print_task_config
if (!json.contains("result") || !json["result"].contains("status") || !json["result"]["status"].contains("print_task_config")) { if (!json.contains("result") || !json["result"].contains("status") ||
!json["result"]["status"].contains("print_task_config")) {
BOOST_LOG_TRIVIAL(warning) << "SnapmakerPrinterAgent::fetch_filament_info: Missing print_task_config in response"; BOOST_LOG_TRIVIAL(warning) << "SnapmakerPrinterAgent::fetch_filament_info: Missing print_task_config in response";
return; return false;
} }
auto& ptc = json["result"]["status"]["print_task_config"]; auto& ptc = json["result"]["status"]["print_task_config"];
@@ -215,12 +164,13 @@ bool SnapmakerPrinterAgent::fetch_filament_info(std::string dev_id, FilamentSync
const int slot_count = static_cast<int>(filament_exist.size()); const int slot_count = static_cast<int>(filament_exist.size());
if (slot_count == 0) { if (slot_count == 0) {
BOOST_LOG_TRIVIAL(info) << "SnapmakerPrinterAgent::fetch_filament_info: No filament slots reported"; BOOST_LOG_TRIVIAL(info) << "SnapmakerPrinterAgent::fetch_filament_info: No filament slots reported";
return; return false;
} }
// Read NFC filament_detect data for temperature info (optional) // Read NFC filament_detect data for temperature info (optional)
nlohmann::json nfc_info; nlohmann::json nfc_info;
if (json["result"]["status"].contains("filament_detect") && json["result"]["status"]["filament_detect"].contains("info")) { if (json["result"]["status"].contains("filament_detect") &&
json["result"]["status"]["filament_detect"].contains("info")) {
nfc_info = json["result"]["status"]["filament_detect"]["info"]; nfc_info = json["result"]["status"]["filament_detect"]["info"];
} }
@@ -236,7 +186,8 @@ bool SnapmakerPrinterAgent::fetch_filament_info(std::string dev_id, FilamentSync
tray.has_filament = filament_exist[i]; tray.has_filament = filament_exist[i];
if (tray.has_filament) { if (tray.has_filament) {
tray.tray_type = combine_filament_type(safe_at(filament_type, i, empty_str), safe_at(filament_sub_type, i, empty_str)); tray.tray_type = combine_filament_type(safe_at(filament_type, i, empty_str),
safe_at(filament_sub_type, i, empty_str));
tray.tray_color = safe_at(filament_color, i, default_color); tray.tray_color = safe_at(filament_color, i, default_color);
auto* bundle = GUI::wxGetApp().preset_bundle; auto* bundle = GUI::wxGetApp().preset_bundle;
@@ -249,8 +200,8 @@ bool SnapmakerPrinterAgent::fetch_filament_info(std::string dev_id, FilamentSync
if (!filament_id.empty()) { if (!filament_id.empty()) {
tray.tray_info_idx = filament_id; tray.tray_info_idx = filament_id;
BOOST_LOG_TRIVIAL(warning) BOOST_LOG_TRIVIAL(warning) << "Filament sync: Found manufacturer-specific profile for slot " << i << ": "
<< "Filament sync: Found manufacturer-specific profile for slot " << i << ": " << filament_id; << filament_id;
} else { } else {
tray.tray_info_idx = bundle->filaments.filament_id_by_type(tray.tray_type); tray.tray_info_idx = bundle->filaments.filament_id_by_type(tray.tray_type);
} }
@@ -273,16 +224,7 @@ bool SnapmakerPrinterAgent::fetch_filament_info(std::string dev_id, FilamentSync
} }
build_ams_payload(1, slot_count - 1, trays); build_ams_payload(1, slot_count - 1, trays);
}).detach();
return true; return true;
} }
FilamentSyncMode SnapmakerPrinterAgent::get_filament_sync_mode() const
{
if (GUI::wxGetApp().app_config->get_bool("use_printer_agents"))
return FilamentSyncMode::subscription;
return FilamentSyncMode::pull;
}
} // namespace Slic3r } // namespace Slic3r
+1 -13
View File
@@ -1,10 +1,7 @@
#pragma once #pragma once
#include "IPrinterAgent.hpp"
#include "MoonrakerPrinterAgent.hpp" #include "MoonrakerPrinterAgent.hpp"
#include <atomic>
#include <cstdint>
#include <string> #include <string>
namespace Slic3r { namespace Slic3r {
@@ -18,20 +15,11 @@ public:
static AgentInfo get_agent_info_static(); static AgentInfo get_agent_info_static();
AgentInfo get_agent_info() override { return get_agent_info_static(); } AgentInfo get_agent_info() override { return get_agent_info_static(); }
bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode = FilamentSyncMode::pull) override; bool fetch_filament_info(std::string dev_id) override;
FilamentSyncMode get_filament_sync_mode() const override;
int command_start_camera(std::string dev_id) override;
CameraStreamMode get_camera_stream_mode() const override { return CameraStreamMode::http_snapshot; }
std::string get_camera_url() const override { return device_info.base_url + "/server/files/camera/monitor.jpg"; }
private: private:
// Combine filament_type + filament_sub_type into a unified type string // Combine filament_type + filament_sub_type into a unified type string
static std::string combine_filament_type(const std::string& type, const std::string& sub_type); static std::string combine_filament_type(const std::string& type, const std::string& sub_type);
void start_camera_monitor();
void on_status_loop_tick(const std::string& dev_id) override;
std::atomic<int64_t> m_camera_last_fire_ms{0};
}; };
} // namespace Slic3r } // namespace Slic3r
@@ -23,14 +23,6 @@ void PrinterAgentPluginCapability::RegisterBindings(pybind11::module_& module)
.value("Pull", FilamentSyncMode::pull) .value("Pull", FilamentSyncMode::pull)
.export_values(); .export_values();
py::enum_<CameraStreamMode>(printer_agent_module, "CameraStreamMode")
.value("None_", CameraStreamMode::none)
.value("HTTP", CameraStreamMode::http)
.value("HTTPS", CameraStreamMode::https)
.value("HTTP_SNAPSHOT", CameraStreamMode::http_snapshot)
.value("RTSP", CameraStreamMode::rtsp)
.export_values();
py::class_<AgentInfo>(printer_agent_module, "AgentInfo") py::class_<AgentInfo>(printer_agent_module, "AgentInfo")
.def(py::init<>()) .def(py::init<>())
.def(py::init([](std::string id, std::string name, std::string version, std::string description) { .def(py::init([](std::string id, std::string name, std::string version, std::string description) {
@@ -97,16 +89,6 @@ void PrinterAgentPluginCapability::RegisterBindings(pybind11::module_& module)
.def_readwrite("task_ext_change_assist", &PrintParams::task_ext_change_assist) .def_readwrite("task_ext_change_assist", &PrintParams::task_ext_change_assist)
.def_readwrite("try_emmc_print", &PrintParams::try_emmc_print); .def_readwrite("try_emmc_print", &PrintParams::try_emmc_print);
py::class_<PrinterConnectionParams>(printer_agent_module, "PrinterConnectionParams")
.def(py::init<>())
.def_readwrite("dev_id", &PrinterConnectionParams::dev_id)
.def_readwrite("host", &PrinterConnectionParams::host)
.def_readwrite("port", &PrinterConnectionParams::port)
.def_readwrite("username", &PrinterConnectionParams::username)
.def_readwrite("password", &PrinterConnectionParams::password)
.def_readwrite("use_ssl", &PrinterConnectionParams::use_ssl)
.def_readwrite("ca_file", &PrinterConnectionParams::ca_file);
py::class_<PrinterAgentPluginCapability, PluginCapabilityInterface, PyPrinterAgentPluginCapabilityTrampoline, std::shared_ptr<PrinterAgentPluginCapability>>( py::class_<PrinterAgentPluginCapability, PluginCapabilityInterface, PyPrinterAgentPluginCapabilityTrampoline, std::shared_ptr<PrinterAgentPluginCapability>>(
printer_agent_module, "PrinterAgentBase") printer_agent_module, "PrinterAgentBase")
.def(py::init<>()) .def(py::init<>())
@@ -116,16 +98,6 @@ void PrinterAgentPluginCapability::RegisterBindings(pybind11::module_& module)
.def("disconnect_printer", &PrinterAgentPluginCapability::disconnect_printer) .def("disconnect_printer", &PrinterAgentPluginCapability::disconnect_printer)
.def("send_message", &PrinterAgentPluginCapability::send_message) .def("send_message", &PrinterAgentPluginCapability::send_message)
.def("send_message_to_printer", &PrinterAgentPluginCapability::send_message_to_printer) .def("send_message_to_printer", &PrinterAgentPluginCapability::send_message_to_printer)
.def("command_ams_refresh_rfid", &PrinterAgentPluginCapability::command_ams_refresh_rfid)
.def("command_ams_calibrate", &PrinterAgentPluginCapability::command_ams_calibrate)
.def("command_ams_select_tray", &PrinterAgentPluginCapability::command_ams_select_tray)
.def("command_start_camera", &PrinterAgentPluginCapability::command_start_camera)
.def("command_xyz_abs", &PrinterAgentPluginCapability::command_xyz_abs)
.def("command_auto_leveling", &PrinterAgentPluginCapability::command_auto_leveling)
.def("command_go_home", &PrinterAgentPluginCapability::command_go_home)
.def("command_set_bed", &PrinterAgentPluginCapability::command_set_bed)
.def("command_set_nozzle", &PrinterAgentPluginCapability::command_set_nozzle)
.def("command_axis_control", &PrinterAgentPluginCapability::command_axis_control)
.def("start_discovery", &PrinterAgentPluginCapability::start_discovery) .def("start_discovery", &PrinterAgentPluginCapability::start_discovery)
.def("bind_detect", &PrinterAgentPluginCapability::bind_detect) .def("bind_detect", &PrinterAgentPluginCapability::bind_detect)
.def("get_user_selected_machine", &PrinterAgentPluginCapability::get_user_selected_machine) .def("get_user_selected_machine", &PrinterAgentPluginCapability::get_user_selected_machine)
@@ -133,9 +105,7 @@ void PrinterAgentPluginCapability::RegisterBindings(pybind11::module_& module)
.def("start_send_gcode_to_sdcard", &PrinterAgentPluginCapability::start_send_gcode_to_sdcard) .def("start_send_gcode_to_sdcard", &PrinterAgentPluginCapability::start_send_gcode_to_sdcard)
.def("start_local_print", &PrinterAgentPluginCapability::start_local_print) .def("start_local_print", &PrinterAgentPluginCapability::start_local_print)
.def("get_filament_sync_mode", &PrinterAgentPluginCapability::get_filament_sync_mode) .def("get_filament_sync_mode", &PrinterAgentPluginCapability::get_filament_sync_mode)
.def("get_camera_stream_mode", &PrinterAgentPluginCapability::get_camera_stream_mode)
.def("fetch_filament_info", &PrinterAgentPluginCapability::fetch_filament_info) .def("fetch_filament_info", &PrinterAgentPluginCapability::fetch_filament_info)
.def("get_camera_url", &PrinterAgentPluginCapability::get_camera_url)
.def("check_cert", &PrinterAgentPluginCapability::check_cert) .def("check_cert", &PrinterAgentPluginCapability::check_cert)
.def("install_device_cert", &PrinterAgentPluginCapability::install_device_cert) .def("install_device_cert", &PrinterAgentPluginCapability::install_device_cert)
.def("ping_bind", &PrinterAgentPluginCapability::ping_bind) .def("ping_bind", &PrinterAgentPluginCapability::ping_bind)
@@ -5,6 +5,7 @@
#include "IPrinterAgent.hpp" #include "IPrinterAgent.hpp"
#include <functional>
#include <memory> #include <memory>
#include <string> #include <string>
@@ -23,22 +24,44 @@ public:
PluginCapabilityType get_type() const override { return PluginCapabilityType::PrinterConnection; } PluginCapabilityType get_type() const override { return PluginCapabilityType::PrinterConnection; }
// set_cloud_agent is the host-managed dependency injection point — the host hands the // set_cloud_agent is the host-managed dependency injection point — the host hands the
// capability its ICloudServiceAgent — so it is the one operation kept native here. Optional // capability its ICloudServiceAgent — so it is the one operation kept native here. Every
// operations inherit IPrinterAgent's defaults; the remaining operations are plugin-defined. // other IPrinterAgent operation is pure: the Python plugin must implement all of them.
void set_cloud_agent(std::shared_ptr<ICloudServiceAgent> cloud) final override { (void) cloud; } void set_cloud_agent(std::shared_ptr<ICloudServiceAgent> cloud) final override { (void) cloud; }
AgentInfo get_agent_info() override = 0; AgentInfo get_agent_info() override = 0;
int connect_printer(const PrinterConnectionParams& params) override = 0; int connect_printer(
std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) override = 0;
int send_message(std::string dev_id, std::string json_str, int qos, int flag) override = 0; int send_message(std::string dev_id, std::string json_str, int qos, int flag) override = 0;
int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag) override = 0; int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag) override = 0;
bool start_discovery(bool start, bool sending) override = 0; bool start_discovery(bool start, bool sending) override = 0;
int bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect) override = 0; int bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect) override = 0;
std::string get_user_selected_machine() override = 0; std::string get_user_selected_machine() override = 0;
int set_user_selected_machine(std::string dev_id) override = 0; int set_user_selected_machine(std::string dev_id) override = 0;
int start_send_gcode_to_sdcard(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override = 0; int start_send_gcode_to_sdcard(PrintParams params,
OnUpdateStatusFn update_fn,
WasCancelledFn cancel_fn,
OnWaitFn wait_fn) override = 0;
int start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) override = 0; int start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) override = 0;
FilamentSyncMode get_filament_sync_mode() const override = 0;
bool fetch_filament_info(std::string dev_id) override = 0;
int check_cert() override = 0;
void install_device_cert(std::string dev_id, bool lan_only) override = 0;
int ping_bind(std::string ping_code) override = 0;
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 = 0;
int unbind(std::string dev_id) override = 0;
// request_bind_ticket has a std::string* out-param that cannot round-trip through a
// pybind11 override directly; the trampoline wraps it (the Python plugin returns a
// (result, ticket) tuple), so it stays pure here like the rest.
int request_bind_ticket(std::string* ticket) override = 0;
int get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback) override = 0;
int set_server_callback(OnServerErrFn fn) override = 0; int set_server_callback(OnServerErrFn fn) override = 0;
int start_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override = 0; int start_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override = 0;
int start_local_print_with_record(PrintParams params, int start_local_print_with_record(PrintParams params,
@@ -55,7 +78,6 @@ public:
int set_on_local_connect_fn(OnLocalConnectedFn fn) override = 0; int set_on_local_connect_fn(OnLocalConnectedFn fn) override = 0;
int set_on_local_message_fn(OnMessageFn fn) override = 0; int set_on_local_message_fn(OnMessageFn fn) override = 0;
int set_queue_on_main_fn(QueueOnMainFn fn) override = 0; int set_queue_on_main_fn(QueueOnMainFn fn) override = 0;
}; };
} // namespace Slic3r } // namespace Slic3r
@@ -5,10 +5,7 @@
#include "../../PyPluginTrampoline.hpp" #include "../../PyPluginTrampoline.hpp"
#include "IPrinterAgent.hpp" #include "IPrinterAgent.hpp"
#include "pybind11/pybind11.h"
#include <slic3r/plugin/PluginAuditManager.hpp>
#include <slic3r/plugin/PythonPluginInterface.hpp> #include <slic3r/plugin/PythonPluginInterface.hpp>
#include <string>
#include <type_traits> #include <type_traits>
@@ -25,12 +22,6 @@
} ORCA_PY_AGENT_CATCH(name) \ } ORCA_PY_AGENT_CATCH(name) \
return printer_agent_failure<ret>() return printer_agent_failure<ret>()
#define ORCA_PY_AGENT_OVERRIDE_DEFAULT(ret, name, ...) \
try { \
ORCA_PY_OVERRIDE_AUDITED([] {}, PYBIND11_OVERRIDE, ret, PrinterAgentPluginCapability, name, ##__VA_ARGS__); \
} ORCA_PY_AGENT_CATCH(name) \
return printer_agent_failure<ret>()
namespace Slic3r { namespace Slic3r {
// NetworkAgent's no-agent answer: -1 for a status code, the empty value (false, "", none) otherwise. // NetworkAgent's no-agent answer: -1 for a status code, the empty value (false, "", none) otherwise.
template<typename T> T printer_agent_failure() template<typename T> T printer_agent_failure()
@@ -51,9 +42,9 @@ public:
ORCA_PY_AGENT_OVERRIDE(AgentInfo, get_agent_info); ORCA_PY_AGENT_OVERRIDE(AgentInfo, get_agent_info);
} }
int connect_printer(const PrinterConnectionParams& params) override int connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) override
{ {
ORCA_PY_AGENT_OVERRIDE(int, connect_printer, params); ORCA_PY_AGENT_OVERRIDE(int, connect_printer, dev_id, dev_ip, username, password, use_ssl);
} }
int disconnect_printer() override int disconnect_printer() override
@@ -71,61 +62,6 @@ public:
ORCA_PY_AGENT_OVERRIDE(int, send_message_to_printer, dev_id, json_str, qos, flag); ORCA_PY_AGENT_OVERRIDE(int, send_message_to_printer, dev_id, json_str, qos, flag);
} }
int command_ams_refresh_rfid(std::string dev_id, int ams_id,int slot_id, int sequence_id, bool lan_mode) override
{
ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, command_ams_refresh_rfid, dev_id, ams_id, slot_id, sequence_id, lan_mode);
}
int command_ams_calibrate(std::string dev_id, int ams_id, int sequence_id, bool lan_mode) override
{
ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, command_ams_calibrate, dev_id, ams_id, sequence_id, lan_mode);
}
int command_ams_select_tray(std::string dev_id, std::string tray_id, int sequence_id, bool lan_mode) override
{
ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, command_ams_select_tray, dev_id, tray_id, sequence_id, lan_mode);
}
int command_start_camera(std::string dev_id) override
{
ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, command_start_camera, dev_id);
}
int command_xyz_abs(std::string dev_id, int sequence_id, bool lan_mode) override
{
ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, command_xyz_abs, dev_id, sequence_id, lan_mode);
}
int command_auto_leveling(std::string dev_id, int sequence_id, bool lan_mode) override
{
ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, command_auto_leveling, dev_id, sequence_id, lan_mode);
}
int command_go_home(std::string dev_id, bool is_printing, bool supports_mqtt_homing,
int sequence_id, bool lan_mode) override
{
ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, command_go_home, dev_id, is_printing, supports_mqtt_homing, sequence_id, lan_mode);
}
int command_set_bed(std::string dev_id, int temp, bool supports_mqtt_bed_ctrl,
int sequence_id, bool lan_mode) override
{
ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, command_set_bed, dev_id, temp, supports_mqtt_bed_ctrl, sequence_id, lan_mode);
}
int command_set_nozzle(std::string dev_id, int temp, int sequence_id, bool lan_mode) override
{
ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, command_set_nozzle, dev_id, temp, sequence_id, lan_mode);
}
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
{
ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, command_axis_control, dev_id, axis, unit, input_val, speed,
is_core_xy, supports_mqtt_axis_control, sequence_id, lan_mode);
}
bool start_discovery(bool start, bool sending) override bool start_discovery(bool start, bool sending) override
{ {
ORCA_PY_AGENT_OVERRIDE(bool, start_discovery, start, sending); ORCA_PY_AGENT_OVERRIDE(bool, start_discovery, start, sending);
@@ -159,22 +95,12 @@ public:
FilamentSyncMode get_filament_sync_mode() const override FilamentSyncMode get_filament_sync_mode() const override
{ {
ORCA_PY_AGENT_OVERRIDE_DEFAULT(FilamentSyncMode, get_filament_sync_mode); ORCA_PY_AGENT_OVERRIDE(FilamentSyncMode, get_filament_sync_mode);
} }
CameraStreamMode get_camera_stream_mode() const override bool fetch_filament_info(std::string dev_id) override
{ {
ORCA_PY_AGENT_OVERRIDE_DEFAULT(CameraStreamMode, get_camera_stream_mode); ORCA_PY_AGENT_OVERRIDE(bool, fetch_filament_info, dev_id);
}
std::string get_camera_url() const override
{
ORCA_PY_AGENT_OVERRIDE_DEFAULT(std::string, get_camera_url);
}
bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode) override
{
ORCA_PY_AGENT_OVERRIDE_DEFAULT(bool, fetch_filament_info, dev_id, sync_mode);
} }
int check_cert() override int check_cert() override
@@ -184,7 +110,7 @@ public:
void install_device_cert(std::string dev_id, bool lan_only) override void install_device_cert(std::string dev_id, bool lan_only) override
{ {
ORCA_PY_AGENT_OVERRIDE_DEFAULT(void, install_device_cert, dev_id, lan_only); ORCA_PY_AGENT_OVERRIDE(void, install_device_cert, dev_id, lan_only);
} }
int ping_bind(std::string ping_code) override int ping_bind(std::string ping_code) override
@@ -219,7 +145,7 @@ public:
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
{ {
ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, get_hms_snapshot, dev_id, file_name, callback); ORCA_PY_AGENT_OVERRIDE(int, get_hms_snapshot, dev_id, file_name, callback);
} }
int set_server_callback(OnServerErrFn fn) override int set_server_callback(OnServerErrFn fn) override
@@ -270,8 +196,6 @@ public:
// request_bind_ticket returns its ticket through a std::string* out-param, which pybind11 // request_bind_ticket returns its ticket through a std::string* out-param, which pybind11
// cannot marshal back through a plain override. We dispatch manually: the Python plugin // cannot marshal back through a plain override. We dispatch manually: the Python plugin
// returns a (result, ticket) tuple, which we unpack into the int result and the out-param. // returns a (result, ticket) tuple, which we unpack into the int result and the out-param.
// Not required to be implemented, so a missing override answers with the same failure value
// as the other printer-agent operations. Leave the out-param untouched on failure.
int request_bind_ticket(std::string* ticket) override int request_bind_ticket(std::string* ticket) override
{ {
try { try {
@@ -283,7 +207,7 @@ public:
pybind11::function override = pybind11::function override =
pybind11::get_override(static_cast<const PrinterAgentPluginCapability*>(this), "request_bind_ticket"); pybind11::get_override(static_cast<const PrinterAgentPluginCapability*>(this), "request_bind_ticket");
if (!override) if (!override)
return printer_agent_failure<int>(); pybind11::pybind11_fail("Tried to call pure virtual function \"PrinterAgentPluginCapability::request_bind_ticket\"");
try { try {
pybind11::tuple result = override().cast<pybind11::tuple>(); pybind11::tuple result = override().cast<pybind11::tuple>();
if (ticket) if (ticket)
+3 -13
View File
@@ -57,28 +57,18 @@ function(orcaslicer_copy_test_dlls)
# into it). Copy them next to the executable and give it an $ORIGIN # into it). Copy them next to the executable and give it an $ORIGIN
# rpath so the loader finds them when the tests run on the CI unit-test # rpath so the loader finds them when the tests run on the CI unit-test
# runner, which only receives this build/tests tree. # runner, which only receives this build/tests tree.
# get_target_property(_linked_libs ${_TEST_NAME}_tests LINK_LIBRARIES)
# Optional first arg: the target to patch, for directories that define
# more than one test executable. Defaults to ${_TEST_NAME}_tests so
# existing single-executable callers don't need to pass it.
if (ARGC GREATER 0)
set(_target ${ARGV0})
else()
set(_target ${_TEST_NAME}_tests)
endif()
get_target_property(_linked_libs ${_target} LINK_LIBRARIES)
if (NOT "libslic3r_gui" IN_LIST _linked_libs) if (NOT "libslic3r_gui" IN_LIST _linked_libs)
return() return()
endif() endif()
set_property(TARGET ${_target} PROPERTY BUILD_RPATH "$ORIGIN") set_property(TARGET ${_TEST_NAME}_tests PROPERTY BUILD_RPATH "$ORIGIN")
set(_configs ${CMAKE_CONFIGURATION_TYPES}) set(_configs ${CMAKE_CONFIGURATION_TYPES})
if (NOT _configs) if (NOT _configs)
set(_configs "${CMAKE_BUILD_TYPE}") set(_configs "${CMAKE_BUILD_TYPE}")
endif() endif()
foreach(_cfg IN LISTS _configs) foreach(_cfg IN LISTS _configs)
orcaslicer_copy_sos(${_target} "${_cfg}" "" _unused_sos) orcaslicer_copy_sos(${_TEST_NAME}_tests "${_cfg}" "" _unused_sos)
endforeach() endforeach()
endif() endif()
endfunction() endfunction()
+1 -9
View File
@@ -5,9 +5,6 @@ add_executable(${_TEST_NAME}_tests
test_creality_cfs_match.cpp test_creality_cfs_match.cpp
test_dev_mapping.cpp test_dev_mapping.cpp
test_filament_bitmap_utils.cpp test_filament_bitmap_utils.cpp
test_device_progress.cpp
test_device_manager_integration.cpp
test_web_media_controller.cpp
test_scene_raycaster.cpp test_scene_raycaster.cpp
test_lazy.cpp test_lazy.cpp
test_prebuild_queue.cpp test_prebuild_queue.cpp
@@ -18,11 +15,6 @@ add_executable(${_TEST_NAME}_tests
test_plugin_capability_config.cpp test_plugin_capability_config.cpp
test_plugin_config.cpp test_plugin_config.cpp
test_plugin_capabilities_in_use.cpp test_plugin_capabilities_in_use.cpp
test_plugin_status.cpp
test_printer_agent.cpp
test_qidi_printer_agent.cpp
test_orca_mqtt_connection.cpp
test_orca_printer_agent.cpp
test_plugin_install.cpp test_plugin_install.cpp
test_plugin_lifecycle.cpp test_plugin_lifecycle.cpp
test_plugin_printer_agent.cpp test_plugin_printer_agent.cpp
@@ -43,7 +35,7 @@ endif ()
target_link_libraries(${_TEST_NAME}_tests test_common libslic3r_gui libslic3r pybind11::embed Catch2::Catch2WithMain) target_link_libraries(${_TEST_NAME}_tests test_common libslic3r_gui libslic3r pybind11::embed Catch2::Catch2WithMain)
set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests") set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
orcaslicer_copy_test_dlls(${_TEST_NAME}_tests) orcaslicer_copy_test_dlls()
if (WIN32) if (WIN32)
add_custom_command(TARGET ${_TEST_NAME}_tests POST_BUILD add_custom_command(TARGET ${_TEST_NAME}_tests POST_BUILD
-325
View File
@@ -1,325 +0,0 @@
#pragma once
// In-process plaintext MQTT-over-WebSocket broker for the OrcaMqtt tests.
//
// It speaks just enough of MQTT 3.1.1 to drive OrcaMqttConnection /
// OrcaPrinterAgent end to end without a real network: CONNECT/CONNACK,
// SUBSCRIBE/SUBACK, UNSUBSCRIBE/UNSUBACK, client PUBLISH (QoS 0), PINGREQ and
// DISCONNECT. The outbound PUBLISH frame is built with the production
// OrcaMqttConnection::make_publish_packet() so the tests never depend on a
// second, hand-rolled MQTT encoder.
#include <slic3r/Utils/OrcaMqttConnection.hpp>
#include <boost/asio.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <algorithm>
#include <atomic>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <mutex>
#include <optional>
#include <string>
#include <thread>
#include <utility>
#include <vector>
namespace orca_mqtt_test {
namespace net = boost::asio;
namespace beast = boost::beast;
namespace ws = boost::beast::websocket;
using tcp = boost::asio::ip::tcp;
// Decode an MQTT remaining-length varint starting at packet[offset].
// Returns {value, bytes_consumed}; bytes_consumed == 0 means malformed.
inline std::pair<std::size_t, std::size_t> mqtt_decode_remaining_length(const std::string& packet, std::size_t offset)
{
std::size_t value = 0;
std::size_t multiplier = 1;
std::size_t used = 0;
while (offset + used < packet.size() && used < 4) {
const std::uint8_t byte = static_cast<std::uint8_t>(packet[offset + used]);
value += static_cast<std::size_t>(byte & 0x7f) * multiplier;
multiplier *= 128;
++used;
if ((byte & 0x80) == 0)
return {value, used};
}
return {0, 0};
}
inline bool mqtt_topic_is_request(const std::string& topic)
{
static const std::string suffix = "/request";
return topic.size() >= suffix.size() &&
topic.compare(topic.size() - suffix.size(), suffix.size(), suffix) == 0;
}
class MockBroker
{
public:
// refuse_auth: answer every CONNECT with CONNACK rc 5 (not authorized) and
// close, so the reconnect/refusal paths can be exercised.
explicit MockBroker(bool refuse_auth = false) : m_refuse_auth(refuse_auth), m_acceptor(m_io)
{
const tcp::endpoint endpoint(net::ip::make_address("127.0.0.1"), 0);
m_acceptor.open(endpoint.protocol());
m_acceptor.set_option(net::socket_base::reuse_address(true));
m_acceptor.bind(endpoint);
m_acceptor.listen(net::socket_base::max_listen_connections);
m_port = std::to_string(m_acceptor.local_endpoint().port());
// why: a non-blocking acceptor lets the accept loop poll a stop flag, so
// the destructor never has to interrupt a blocking accept().
m_acceptor.non_blocking(true);
m_thread = std::thread([this] { run(); });
}
~MockBroker()
{
m_stopping.store(true);
drop_client(); // unblocks the worker's blocking read
if (m_thread.joinable())
m_thread.join();
boost::system::error_code ec;
m_acceptor.close(ec); // after join: the acceptor is worker-owned
m_io.stop();
}
MockBroker(const MockBroker&) = delete;
MockBroker& operator=(const MockBroker&) = delete;
std::string ws_url() const { return "ws://127.0.0.1:" + m_port + "/mqtt"; }
std::pair<std::string, std::string> host_port() const { return {std::string("127.0.0.1"), m_port}; }
// Server -> client PUBLISH on device/<dev_id>/report.
void push_report(const std::string& dev_id, const std::string& payload)
{
const std::vector<std::uint8_t> packet =
Slic3r::OrcaMqttConnection::make_publish_packet("device/" + dev_id + "/report", payload);
std::lock_guard<std::mutex> lock(m_mutex);
if (!m_stream || !m_stream_ready)
return;
boost::system::error_code ec;
m_stream->binary(true);
m_stream->write(net::buffer(packet), ec); // a vanished client is not a test failure
}
// Force-close the live client socket; the worker's read returns an error and
// the accept loop picks up the client's reconnect.
void drop_client()
{
std::lock_guard<std::mutex> lock(m_mutex);
close_client_locked();
}
// Payloads the client PUBLISHed to any device/<id>/request topic.
std::vector<std::string> received_requests() const
{
std::lock_guard<std::mutex> lock(m_mutex);
return m_received_requests;
}
// MQTT CONNECTs seen; increments again after a reconnect.
int connect_count() const { return m_connect_count.load(); }
// MQTT PINGREQs seen while the client has no other traffic.
int ping_count() const { return m_ping_count.load(); }
private:
void run()
{
try {
while (!m_stopping.load()) {
tcp::socket socket(m_io);
boost::system::error_code ec;
m_acceptor.accept(socket, ec);
if (ec == net::error::would_block || ec == net::error::try_again) {
std::this_thread::sleep_for(std::chrono::milliseconds(5));
continue;
}
if (ec)
return;
try {
serve(std::move(socket));
} catch (...) {
// a client dying mid-session must not take the broker down
}
std::lock_guard<std::mutex> lock(m_mutex);
close_client_locked();
m_stream.reset();
}
} catch (...) {
// never let an exception escape the broker thread
}
}
void serve(tcp::socket socket)
{
ws::stream<beast::tcp_stream>* stream = nullptr;
{
std::lock_guard<std::mutex> lock(m_mutex);
m_stream.emplace(std::move(socket));
m_stream_ready = false;
stream = &*m_stream;
}
// why: no io_context is ever run here, so a tcp_stream timer would never
// fire; the sync operations below carry no timeout of their own.
beast::get_lowest_layer(*stream).expires_never();
stream->set_option(ws::stream_base::decorator(
[](ws::response_type& res) { res.set("Sec-WebSocket-Protocol", "mqtt"); }));
boost::system::error_code ec;
stream->accept(ec);
if (ec)
return;
stream->binary(true);
{
std::lock_guard<std::mutex> lock(m_mutex);
m_stream_ready = true;
}
read_loop(*stream);
}
// The client sends every MQTT packet as one binary WebSocket message, so one
// read yields exactly one packet.
void read_loop(ws::stream<beast::tcp_stream>& stream)
{
beast::flat_buffer buffer;
while (!m_stopping.load()) {
boost::system::error_code ec;
buffer.clear();
stream.read(buffer, ec);
if (ec)
return;
const std::string packet = beast::buffers_to_string(buffer.data());
if (packet.empty())
continue;
if (!handle_packet(stream, packet))
return;
}
}
// Returns false when the session must be closed.
bool handle_packet(ws::stream<beast::tcp_stream>& stream, const std::string& packet)
{
switch (static_cast<std::uint8_t>(packet[0]) & 0xf0) {
case 0x10: { // CONNECT
++m_connect_count;
if (m_refuse_auth) {
write_packet(stream, {0x20, 0x02, 0x00, 0x05}); // CONNACK not authorized
return false;
}
write_packet(stream, {0x20, 0x02, 0x00, 0x00}); // CONNACK accepted
return true;
}
case 0x80: { // SUBSCRIBE (0x82) - packet id follows the remaining-length varint
const auto id = packet_id(packet);
if (id)
write_packet(stream, {0x90, 0x03, id->first, id->second, 0x00}); // SUBACK, QoS 0
return true;
}
case 0xa0: { // UNSUBSCRIBE (0xa2)
const auto id = packet_id(packet);
if (id)
write_packet(stream, {0xb0, 0x02, id->first, id->second}); // UNSUBACK
return true;
}
case 0x30: { // PUBLISH, QoS 0 (no packet identifier)
record_publish(packet);
return true;
}
case 0xc0: // PINGREQ
++m_ping_count;
write_packet(stream, {0xd0, 0x00});
return true;
case 0xe0: // DISCONNECT
return false;
default:
return true;
}
}
// The two packet-identifier bytes sitting right after the remaining-length varint.
static std::optional<std::pair<std::uint8_t, std::uint8_t>> packet_id(const std::string& packet)
{
const auto varint = mqtt_decode_remaining_length(packet, 1);
if (varint.second == 0)
return std::nullopt;
const std::size_t pos = 1 + varint.second;
if (pos + 2 > packet.size())
return std::nullopt;
return std::make_pair(static_cast<std::uint8_t>(packet[pos]), static_cast<std::uint8_t>(packet[pos + 1]));
}
void record_publish(const std::string& packet)
{
const auto varint = mqtt_decode_remaining_length(packet, 1);
if (varint.second == 0)
return;
std::size_t pos = 1 + varint.second;
if (pos + 2 > packet.size())
return;
const std::size_t topic_len = (static_cast<std::size_t>(static_cast<std::uint8_t>(packet[pos])) << 8) |
static_cast<std::uint8_t>(packet[pos + 1]);
pos += 2;
if (pos + topic_len > packet.size())
return;
const std::string topic = packet.substr(pos, topic_len);
pos += topic_len;
const std::size_t end = std::min(packet.size(), 1 + varint.second + varint.first);
if (end < pos)
return;
if (!mqtt_topic_is_request(topic))
return;
std::lock_guard<std::mutex> lock(m_mutex);
m_received_requests.push_back(packet.substr(pos, end - pos));
}
// Every write - the worker's own replies and push_report() from the test
// thread - is serialised by m_mutex. The production client keeps all of its
// WebSocket operations on its MQTT worker instead.
void write_packet(ws::stream<beast::tcp_stream>& stream, const std::vector<std::uint8_t>& packet)
{
std::lock_guard<std::mutex> lock(m_mutex);
boost::system::error_code ec;
stream.binary(true);
stream.write(net::buffer(packet), ec);
}
void close_client_locked()
{
if (!m_stream)
return;
m_stream_ready = false;
boost::system::error_code ec;
auto& socket = beast::get_lowest_layer(*m_stream).socket();
if (socket.cancel(ec))
return;
// shutdown() before close() is what actually wakes a blocking read on the
// worker thread; close() alone does not on POSIX.
if (socket.shutdown(tcp::socket::shutdown_both, ec))
return;
if (socket.close(ec))
return;
}
const bool m_refuse_auth;
net::io_context m_io;
tcp::acceptor m_acceptor;
std::string m_port;
std::thread m_thread;
std::atomic_bool m_stopping{false};
std::atomic<int> m_connect_count{0};
std::atomic<int> m_ping_count{0};
mutable std::mutex m_mutex;
std::optional<ws::stream<beast::tcp_stream>> m_stream; // guarded by m_mutex
bool m_stream_ready = false; // guarded by m_mutex
std::vector<std::string> m_received_requests; // guarded by m_mutex
};
} // namespace orca_mqtt_test
-31
View File
@@ -26,37 +26,6 @@
using json = nlohmann::json; using json = nlohmann::json;
using namespace Slic3r; using namespace Slic3r;
TEST_CASE("AMS tray placeholder state follows the latest status", "[DevFilaSystem]")
{
MachineObject obj(nullptr, nullptr, "test", "test_dev", "127.0.0.1");
const json empty_slot = json::parse(R"({
"ams": {
"tray_exist_bits": "0",
"ams": [ { "id": "0", "info": "00000001", "tray": [
{ "id": "0", "tray_slot_placeholder": "1", "tray_color": "00000000" }
] } ]
}
})");
DevFilaSystemParser::ParseV1_0(empty_slot, &obj, obj.GetFilaSystem().get(), false);
DevAmsTray* tray = obj.GetFilaSystem()->GetAmsTray("0", "0");
REQUIRE(tray != nullptr);
REQUIRE(tray->is_slot_placeholder);
const json loaded_slot = json::parse(R"({
"ams": {
"tray_exist_bits": "1",
"ams": [ { "id": "0", "info": "00000001", "tray": [
{ "id": "0", "tray_color": "FF0000FF" }
] } ]
}
})");
DevFilaSystemParser::ParseV1_0(loaded_slot, &obj, obj.GetFilaSystem().get(), false);
CHECK_FALSE(tray->is_slot_placeholder);
}
TEST_CASE("Switch-bound AMS trays map to the left extruder", "[DevMapping]") TEST_CASE("Switch-bound AMS trays map to the left extruder", "[DevMapping]")
{ {
MachineObject obj(nullptr, nullptr, "test", "test_dev", "127.0.0.1"); MachineObject obj(nullptr, nullptr, "test", "test_dev", "127.0.0.1");
@@ -1,159 +0,0 @@
#include <catch2/catch_all.hpp>
#include <slic3r/GUI/DeviceCore/DevManager.h>
#include <slic3r/GUI/DeviceManager.hpp>
#include <libslic3r/AppConfig.hpp>
#include <slic3r/Utils/NetworkAgent.hpp>
#include <slic3r/Utils/OrcaCloudServiceAgent.hpp>
#include <slic3r/Utils/OrcaPrinterAgent.hpp>
#include <nlohmann/json.hpp>
#include <memory>
#include <cstdint>
#include <string>
#include <utility>
using namespace Slic3r;
using json = nlohmann::json;
namespace {
class StubCloudAgent final : public OrcaCloudServiceAgent
{
public:
StubCloudAgent() : OrcaCloudServiceAgent("") {}
int get_user_print_info(unsigned int* http_code, std::string* http_body) override
{
if (http_code)
*http_code = 200;
if (http_body)
*http_body = R"({"devices":[]})";
return 0;
}
std::string get_user_name() override { return "integration-test-user"; }
};
class TestPrinterAgent final : public OrcaPrinterAgent
{
public:
explicit TestPrinterAgent(std::string id)
: OrcaPrinterAgent(""), m_info{std::move(id), "Integration Test Agent", "1.0", "test agent"}
{
}
AgentInfo get_agent_info() override { return m_info; }
private:
AgentInfo m_info;
};
struct ScopedAppConfig
{
AppConfig config;
};
std::string machine_list_response(const std::string& provider, const std::string& agent_id,
std::uint64_t generation, const std::string& name)
{
json machine;
machine["dev_id"] = "integration-device";
machine["dev_name"] = name;
machine["dev_online"] = true;
machine["task_status"] = "idle";
json response;
response["provider"] = provider;
response["agent_id"] = agent_id;
response["generation"] = generation;
response["devices"] = json::array({machine});
return response.dump();
}
} // namespace
TEST_CASE("Network agent stamps user-machine responses with request context", "[DeviceManager][integration]")
{
auto cloud = std::make_shared<StubCloudAgent>();
NetworkAgent network(cloud, nullptr);
network.set_printer_agent(std::make_shared<TestPrinterAgent>("integration-agent-a"));
const std::uint64_t generation_before = network.get_user_machine_list_generation();
unsigned int http_code = 0;
std::string body;
REQUIRE(network.get_user_print_info(&http_code, &body, ORCA_CLOUD_PROVIDER) == 0);
const json response = json::parse(body);
CHECK(http_code == 200);
CHECK(response["provider"] == ORCA_CLOUD_PROVIDER);
CHECK(response["agent_id"] == "integration-agent-a");
CHECK(response["generation"] == generation_before + 1);
}
TEST_CASE("Device manager ignores stale cloud machine responses", "[DeviceManager][integration]")
{
ScopedAppConfig app_config;
NetworkAgent network(nullptr, std::make_shared<TestPrinterAgent>("integration-agent"));
network.set_printer_agent(std::make_shared<TestPrinterAgent>("integration-agent"));
DeviceManager manager(&network, false, &app_config.config);
const std::uint64_t current_generation = network.get_user_machine_list_generation();
manager.parse_user_print_info(machine_list_response(ORCA_CLOUD_PROVIDER, "integration-agent",
current_generation, "Fresh name"));
auto machines = manager.get_user_machinelist();
REQUIRE(machines.size() == 1);
REQUIRE(machines.at("integration-device") != nullptr);
CHECK(machines.at("integration-device")->get_dev_name() == "Fresh name");
manager.parse_user_print_info(machine_list_response(BBL_CLOUD_PROVIDER, "integration-agent",
current_generation, "Stale provider"));
manager.parse_user_print_info(machine_list_response(ORCA_CLOUD_PROVIDER, "other-agent",
current_generation, "Stale agent"));
manager.parse_user_print_info(machine_list_response(ORCA_CLOUD_PROVIDER, "integration-agent",
current_generation + 1, "Stale generation"));
machines = manager.get_user_machinelist();
REQUIRE(machines.size() == 1);
CHECK(machines.at("integration-device")->get_dev_name() == "Fresh name");
}
TEST_CASE("Device manager filters and rehomes devices by printer-agent ownership", "[DeviceManager][integration]")
{
ScopedAppConfig app_config;
auto agent_a = std::make_shared<TestPrinterAgent>("integration-agent-a");
auto agent_b = std::make_shared<TestPrinterAgent>("integration-agent-b");
NetworkAgent network(nullptr, agent_a);
DeviceManager manager(&network, false, &app_config.config);
BBLocalMachine machine;
machine.dev_id = "integration-lan-device";
machine.dev_name = "Integration LAN device";
machine.dev_ip = "192.0.2.10";
machine.printer_type = "C11";
MachineObject* object = manager.insert_local_device(machine, "lan", "free", "", "access-code");
REQUIRE(object != nullptr);
CHECK(object->printer_agent_id == "integration-agent-a");
CHECK(manager.get_my_machine_list("integration-agent-a").count(machine.dev_id) == 1);
CHECK(manager.get_my_machine_list("integration-agent-b").empty());
network.set_printer_agent(agent_b);
CHECK(manager.get_my_machine_list("integration-agent-b").empty());
manager.on_machine_alive(R"({
"dev_name":"Rediscovered device",
"dev_id":"integration-lan-device",
"dev_ip":"192.0.2.10",
"dev_type":"C11",
"dev_signal":"strong",
"connect_type":"lan",
"bind_state":"free"
})");
CHECK(object->printer_agent_id == "integration-agent-b");
CHECK(manager.get_my_machine_list("integration-agent-a").empty());
CHECK(manager.get_my_machine_list("integration-agent-b").count(machine.dev_id) == 1);
}

Some files were not shown because too many files have changed in this diff Show More