Просмотр исходного кода

Fix A1 virtual printer proxy by adding ports 2024-2026 (#757)

  BambuStudio connects to undocumented proprietary ports 2024-2026
  on A1/P1S models during the print flow. The proxy wasn't forwarding
  these ports, causing connection refused (RST) and triggering the
  access code dialog instead of printing. MQTT and FTP worked fine —
  port 2024 was the sole blocker.

  Added transparent TCP pass-through proxies for ports 2024-2026,
  following the same pattern as the existing FileTransfer (6000) and
  RTSP (322) proxies. Silently ignored on models that don't use them.
maziggy 2 месяцев назад
Родитель
Сommit
8e5625d1d9

+ 1 - 0
CHANGELOG.md

@@ -5,6 +5,7 @@ All notable changes to Bambuddy will be documented in this file.
 ## [0.2.3.b1] - Unreleased
 ## [0.2.3.b1] - Unreleased
 
 
 ### Fixed
 ### Fixed
+- **Virtual Printer Proxy A1 Printing Fails** ([#757](https://github.com/maziggy/bambuddy/issues/757)) — BambuStudio could not send prints to A1 (and potentially P1S) virtual printers in proxy mode. The slicer connects to undocumented proprietary ports 2024-2026 on these models, which the proxy was not forwarding, causing BambuStudio to show an access code dialog instead of printing. Added transparent TCP pass-through proxying for ports 2024-2026. These ports are silently ignored on models that don't use them (X1C, H2C, P2S). Reported by @Utility9298.
 - **Spool Assignment on Empty AMS Slots** ([#784](https://github.com/maziggy/bambuddy/issues/784)) — Empty AMS slots (no physical spool detected) showed "Assign Spool" and "Configure" buttons in the hover popup. Assigning a spool to an empty slot created a stuck state because no "Unassign" button is available for empty slots. Removed both buttons from empty AMS and HT AMS slot popups. External spool holders are unaffected. Reported by @RosdasHH.
 - **Spool Assignment on Empty AMS Slots** ([#784](https://github.com/maziggy/bambuddy/issues/784)) — Empty AMS slots (no physical spool detected) showed "Assign Spool" and "Configure" buttons in the hover popup. Assigning a spool to an empty slot created a stuck state because no "Unassign" button is available for empty slots. Removed both buttons from empty AMS and HT AMS slot popups. External spool holders are unaffected. Reported by @RosdasHH.
 
 
 ## [0.2.2.1] - 2026-03-22
 ## [0.2.2.1] - 2026-03-22

+ 31 - 0
backend/app/services/virtual_printer/tcp_proxy.py

@@ -1421,6 +1421,9 @@ class SlicerProxyManager:
     PRINTER_MQTT_PORT = 8883
     PRINTER_MQTT_PORT = 8883
     PRINTER_FILE_TRANSFER_PORT = 6000
     PRINTER_FILE_TRANSFER_PORT = 6000
     PRINTER_RTSP_PORT = 322  # X1/H2/P2 series camera (A1/P1 use port 6000)
     PRINTER_RTSP_PORT = 322  # X1/H2/P2 series camera (A1/P1 use port 6000)
+    # Undocumented proprietary ports used by some models (A1, P1S, etc.)
+    # BambuStudio requires port 2024 for printing; OrcaSlicer also needs 2025.
+    PRINTER_AUX_PORTS = [2024, 2025, 2026]
     PRINTER_BIND_PORTS = [3000, 3002]
     PRINTER_BIND_PORTS = [3000, 3002]
 
 
     # Local listen ports - must match what Bambu Studio expects
     # Local listen ports - must match what Bambu Studio expects
@@ -1461,6 +1464,7 @@ class SlicerProxyManager:
         self._mqtt_proxy: TLSProxy | None = None
         self._mqtt_proxy: TLSProxy | None = None
         self._file_transfer_proxy: TCPProxy | None = None
         self._file_transfer_proxy: TCPProxy | None = None
         self._rtsp_proxy: TCPProxy | None = None
         self._rtsp_proxy: TCPProxy | None = None
+        self._aux_proxies: list[TCPProxy] = []
         self._bind_proxies: list[TCPProxy] = []
         self._bind_proxies: list[TCPProxy] = []
         self._bind_server = None
         self._bind_server = None
         self._probe_servers: list[asyncio.Server] = []
         self._probe_servers: list[asyncio.Server] = []
@@ -1560,6 +1564,22 @@ class SlicerProxyManager:
             bind_address=self.bind_address,
             bind_address=self.bind_address,
         )
         )
 
 
+        # Auxiliary ports (2024-2026) — raw TCP pass-through for undocumented
+        # proprietary services. Required by BambuStudio/OrcaSlicer for some
+        # models (A1, P1S). Silently ignored if the printer doesn't listen.
+        for aux_port in self.PRINTER_AUX_PORTS:
+            self._aux_proxies.append(
+                TCPProxy(
+                    name=f"Aux-{aux_port}",
+                    listen_port=aux_port,
+                    target_host=self.target_host,
+                    target_port=aux_port,
+                    on_connect=lambda cid, p=aux_port: self._log_activity(f"Aux-{p}", f"connected: {cid}"),
+                    on_disconnect=lambda cid, p=aux_port: self._log_activity(f"Aux-{p}", f"disconnected: {cid}"),
+                    bind_address=self.bind_address,
+                )
+            )
+
         # Bind/auth — respond with VP identity instead of proxying to printer.
         # Bind/auth — respond with VP identity instead of proxying to printer.
         # The detect response contains the printer name, serial, model, and
         # The detect response contains the printer name, serial, model, and
         # bind status. Proxying it would leak the real printer's identity and
         # bind status. Proxying it would leak the real printer's identity and
@@ -1628,6 +1648,13 @@ class SlicerProxyManager:
                 name="slicer_proxy_rtsp",
                 name="slicer_proxy_rtsp",
             ),
             ),
         ]
         ]
+        for ap in self._aux_proxies:
+            self._tasks.append(
+                asyncio.create_task(
+                    run_with_logging(ap),
+                    name=f"slicer_proxy_aux_{ap.listen_port}",
+                )
+            )
         if self._bind_server:
         if self._bind_server:
             self._tasks.append(
             self._tasks.append(
                 asyncio.create_task(
                 asyncio.create_task(
@@ -1702,6 +1729,10 @@ class SlicerProxyManager:
             await self._rtsp_proxy.stop()
             await self._rtsp_proxy.stop()
             self._rtsp_proxy = None
             self._rtsp_proxy = None
 
 
+        for ap in self._aux_proxies:
+            await ap.stop()
+        self._aux_proxies = []
+
         if self._bind_server:
         if self._bind_server:
             await self._bind_server.stop()
             await self._bind_server.stop()
             self._bind_server = None
             self._bind_server = None

+ 10 - 0
backend/tests/unit/services/test_virtual_printer.py

@@ -1091,6 +1091,8 @@ class TestSlicerProxyManager:
         assert proxy_manager.PRINTER_MQTT_PORT == 8883
         assert proxy_manager.PRINTER_MQTT_PORT == 8883
         assert proxy_manager.PRINTER_FILE_TRANSFER_PORT == 6000
         assert proxy_manager.PRINTER_FILE_TRANSFER_PORT == 6000
         assert proxy_manager.PRINTER_RTSP_PORT == 322
         assert proxy_manager.PRINTER_RTSP_PORT == 322
+        # Auxiliary ports: undocumented proprietary ports for A1/P1S etc.
+        assert proxy_manager.PRINTER_AUX_PORTS == [2024, 2025, 2026]
         # Bind ports: both 3000 and 3002 for slicer compatibility
         # Bind ports: both 3000 and 3002 for slicer compatibility
         assert proxy_manager.PRINTER_BIND_PORTS == [3000, 3002]
         assert proxy_manager.PRINTER_BIND_PORTS == [3000, 3002]
         # FTP data port range for transparent EPSV proxying
         # FTP data port range for transparent EPSV proxying
@@ -1157,6 +1159,14 @@ class TestSlicerProxyManager:
         # MQTT should be TLSProxy (TLS-terminated for IP rewriting)
         # MQTT should be TLSProxy (TLS-terminated for IP rewriting)
         assert isinstance(mgr._mqtt_proxy, TLSProxy), "MQTT should be TLSProxy (TLS-terminated)"
         assert isinstance(mgr._mqtt_proxy, TLSProxy), "MQTT should be TLSProxy (TLS-terminated)"
 
 
+        # Auxiliary ports (2024-2026) should be TCPProxy (transparent)
+        assert len(mgr._aux_proxies) == 3, "Should have 3 aux port proxies"
+        for ap in mgr._aux_proxies:
+            assert isinstance(ap, TCPProxy), "Aux proxies should be TCPProxy"
+        assert mgr._aux_proxies[0].listen_port == 2024
+        assert mgr._aux_proxies[0].target_port == 2024
+        assert mgr._aux_proxies[2].listen_port == 2026
+
         # FTP data ports should be pre-created as TCPProxy instances
         # FTP data ports should be pre-created as TCPProxy instances
         assert len(mgr._ftp_data_proxies) == 101  # 50000-50100 inclusive
         assert len(mgr._ftp_data_proxies) == 101  # 50000-50100 inclusive
         for dp in mgr._ftp_data_proxies:
         for dp in mgr._ftp_data_proxies:

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
static/assets/index-C2JN3KVZ.js


Некоторые файлы не были показаны из-за большого количества измененных файлов