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

[Fix] X1C Virtual Printer not accepting sends (#735)

  X1C and X1 virtual printers used legacy SSDP model codes
  (3DPrinter-X1-Carbon, 3DPrinter-X1) that BambuStudio doesn't
  recognize, causing "incompatible printer preset" errors when
  sending prints. Changed to the correct codes (BL-P001, BL-P002)
  that real printers report via SSDP.

  Also fixed proxy mode auto-inherit storing printer display names
  (e.g. "X1C") instead of SSDP codes, by adding a resolution layer
  that maps display names to model codes.

  DB migration auto-converts existing VPs on startup.
maziggy 2 месяцев назад
Родитель
Сommit
4f617c21e1

+ 1 - 0
CHANGELOG.md

@@ -9,6 +9,7 @@ All notable changes to Bambuddy will be documented in this file.
 - **Per-User Email Notifications** ([#693](https://github.com/maziggy/bambuddy/pull/693)) — When Advanced Authentication is enabled, individual users can now receive email notifications for their own print jobs. A new "Notifications" page lets each user toggle notifications for print start, complete, failed, and stopped events. Only prints submitted by that user trigger their email — other users' prints are not affected. Requires SMTP to be configured and the "User Notifications" toggle enabled in Settings → Notifications. Administrators and Operators have access by default; Viewers do not. Contributed by @cadtoolbox.
 - **Per-User Email Notifications** ([#693](https://github.com/maziggy/bambuddy/pull/693)) — When Advanced Authentication is enabled, individual users can now receive email notifications for their own print jobs. A new "Notifications" page lets each user toggle notifications for print start, complete, failed, and stopped events. Only prints submitted by that user trigger their email — other users' prints are not affected. Requires SMTP to be configured and the "User Notifications" toggle enabled in Settings → Notifications. Administrators and Operators have access by default; Viewers do not. Contributed by @cadtoolbox.
 
 
 ### Fixed
 ### Fixed
+- **X1C Virtual Printer Not Accepting Sends** ([#735](https://github.com/maziggy/bambuddy/issues/735)) — X1C (and X1) virtual printers were advertised with legacy SSDP model codes (`3DPrinter-X1-Carbon` / `3DPrinter-X1`) that BambuStudio doesn't recognize, causing "incompatible printer preset" when sending. Fixed to use the correct codes (`BL-P001` / `BL-P002`). Also fixed proxy mode auto-inherit storing the printer's display name (e.g. `X1C`) instead of the SSDP code. Existing VPs are automatically migrated on startup. Reported by @RosdasHH.
 - **White Filament Color Swatches Invisible in Light Theme** ([#726](https://github.com/maziggy/bambuddy/issues/726)) — Filament color circles used a white border that was invisible against light theme backgrounds, making white spools indistinguishable. Changed to a dark border (`border-black/20`) across all views: Inventory, Archives, Assign Spool, Configure AMS Slot, Calendar, Projects, Filament Trends, Local Profiles, Link Spool, and Spoolman Settings. Reported by user.
 - **White Filament Color Swatches Invisible in Light Theme** ([#726](https://github.com/maziggy/bambuddy/issues/726)) — Filament color circles used a white border that was invisible against light theme backgrounds, making white spools indistinguishable. Changed to a dark border (`border-black/20`) across all views: Inventory, Archives, Assign Spool, Configure AMS Slot, Calendar, Projects, Filament Trends, Local Profiles, Link Spool, and Spoolman Settings. Reported by user.
 - **Webhook Notifications Missing Camera Snapshot** ([#679](https://github.com/maziggy/bambuddy/issues/679)) — Webhook notification providers did not include camera snapshots (e.g. from First Layer Complete notifications), even though providers like Telegram, Pushover, ntfy, and Discord already attached them. The webhook payload now includes a base64-encoded `image` field when a snapshot is available (generic format only, not Slack format). Reported by @Arn0uDz.
 - **Webhook Notifications Missing Camera Snapshot** ([#679](https://github.com/maziggy/bambuddy/issues/679)) — Webhook notification providers did not include camera snapshots (e.g. from First Layer Complete notifications), even though providers like Telegram, Pushover, ntfy, and Discord already attached them. The webhook payload now includes a base64-encoded `image` field when a snapshot is available (generic format only, not Slack format). Reported by @Arn0uDz.
 - **Mobile Sidebar Not Scrollable** — On mobile devices with many navigation items, the sidebar did not scroll, making bottom items unreachable. Added overflow scrolling to the nav section while keeping the logo and footer pinned.
 - **Mobile Sidebar Not Scrollable** — On mobile devices with many navigation items, the sidebar did not scroll, making bottom items unreachable. Added overflow scrolling to the nav section while keeping the logo and footer pinned.

+ 20 - 3
backend/app/api/routes/virtual_printers.py

@@ -40,6 +40,23 @@ class VirtualPrinterUpdate(BaseModel):
     remote_interface_ip: str | None = None
     remote_interface_ip: str | None = None
 
 
 
 
+def _resolve_printer_model(printer_model: str | None) -> str | None:
+    """Map a printer's model (display name or SSDP code) to a valid VP SSDP model code.
+
+    Printers store display names like 'X1C' while VPs need SSDP codes like 'BL-P001'.
+    """
+    if not printer_model:
+        return None
+    from backend.app.services.virtual_printer import VIRTUAL_PRINTER_MODELS
+    from backend.app.services.virtual_printer.manager import DISPLAY_NAME_TO_MODEL_CODE
+
+    # Already a valid SSDP model code
+    if printer_model in VIRTUAL_PRINTER_MODELS:
+        return printer_model
+    # Map display name to SSDP code
+    return DISPLAY_NAME_TO_MODEL_CODE.get(printer_model)
+
+
 def _vp_to_dict(vp, status: dict | None = None) -> dict:
 def _vp_to_dict(vp, status: dict | None = None) -> dict:
     """Convert VirtualPrinter model to response dict."""
     """Convert VirtualPrinter model to response dict."""
     from backend.app.services.virtual_printer import VIRTUAL_PRINTER_MODELS
     from backend.app.services.virtual_printer import VIRTUAL_PRINTER_MODELS
@@ -172,7 +189,7 @@ async def create_virtual_printer(
         enabled=body.enabled,
         enabled=body.enabled,
         mode=body.mode,
         mode=body.mode,
         model=body.model
         model=body.model
-        or (target_printer.model if target_printer and target_printer.model and body.mode == "proxy" else None)
+        or _resolve_printer_model(target_printer.model if target_printer and body.mode == "proxy" else None)
         or DEFAULT_VIRTUAL_PRINTER_MODEL,
         or DEFAULT_VIRTUAL_PRINTER_MODEL,
         access_code=body.access_code,
         access_code=body.access_code,
         target_printer_id=body.target_printer_id,
         target_printer_id=body.target_printer_id,
@@ -276,7 +293,7 @@ async def update_virtual_printer(
         vp.target_printer_id = body.target_printer_id
         vp.target_printer_id = body.target_printer_id
         # Auto-inherit model from target printer in proxy mode (unless user explicitly set model)
         # Auto-inherit model from target printer in proxy mode (unless user explicitly set model)
         if body.model is None and vp.mode == "proxy" and target_printer.model:
         if body.model is None and vp.mode == "proxy" and target_printer.model:
-            vp.model = target_printer.model
+            vp.model = _resolve_printer_model(target_printer.model) or target_printer.model
     if body.auto_dispatch is not None:
     if body.auto_dispatch is not None:
         vp.auto_dispatch = body.auto_dispatch
         vp.auto_dispatch = body.auto_dispatch
     if body.bind_ip is not None:
     if body.bind_ip is not None:
@@ -291,7 +308,7 @@ async def update_virtual_printer(
         result = await db.execute(select(PrinterModel).where(PrinterModel.id == vp.target_printer_id))
         result = await db.execute(select(PrinterModel).where(PrinterModel.id == vp.target_printer_id))
         existing_target = result.scalar_one_or_none()
         existing_target = result.scalar_one_or_none()
         if existing_target and existing_target.model:
         if existing_target and existing_target.model:
-            vp.model = existing_target.model
+            vp.model = _resolve_printer_model(existing_target.model) or existing_target.model
 
 
     # Determine final enabled state
     # Determine final enabled state
     explicitly_enabling = body.enabled is True
     explicitly_enabling = body.enabled is True

+ 28 - 1
backend/app/core/database.py

@@ -1322,7 +1322,7 @@ async def run_migrations(conn):
 
 
                 result = await conn.execute(text("SELECT value FROM settings WHERE key = 'virtual_printer_model'"))
                 result = await conn.execute(text("SELECT value FROM settings WHERE key = 'virtual_printer_model'"))
                 row = result.fetchone()
                 row = result.fetchone()
-                old_model = row[0] if row else "3DPrinter-X1-Carbon"
+                old_model = row[0] if row else "BL-P001"
 
 
                 result = await conn.execute(
                 result = await conn.execute(
                     text("SELECT value FROM settings WHERE key = 'virtual_printer_target_printer_id'")
                     text("SELECT value FROM settings WHERE key = 'virtual_printer_target_printer_id'")
@@ -1441,6 +1441,33 @@ async def run_migrations(conn):
     except OperationalError:
     except OperationalError:
         pass  # Already applied
         pass  # Already applied
 
 
+    # Migration: Fix VP model codes — convert legacy SSDP codes and display names to correct SSDP codes
+    # Legacy codes (from multi-VP refactor) and display names (from proxy auto-inherit)
+    vp_model_fixes = {
+        "3DPrinter-X1-Carbon": "BL-P001",
+        "3DPrinter-X1": "BL-P002",
+        "X1C": "BL-P001",
+        "X1": "BL-P002",
+        "X1E": "C13",
+        "P1P": "C11",
+        "P1S": "C12",
+        "P2S": "N7",
+        "A1": "N2S",
+        "A1 Mini": "N1",
+        "H2D": "O1D",
+        "H2C": "O1C",
+        "H2S": "O1S",
+    }
+    for old_val, new_val in vp_model_fixes.items():
+        await conn.execute(
+            text("UPDATE virtual_printers SET model = :new WHERE model = :old"),
+            {"old": old_val, "new": new_val},
+        )
+        await conn.execute(
+            text("UPDATE settings SET value = :new WHERE key = 'virtual_printer_model' AND value = :old"),
+            {"old": old_val, "new": new_val},
+        )
+
     # Migration: Add per-user Bambu Cloud credential columns
     # Migration: Add per-user Bambu Cloud credential columns
     try:
     try:
         await conn.execute(text("ALTER TABLE users ADD COLUMN cloud_token VARCHAR(500)"))
         await conn.execute(text("ALTER TABLE users ADD COLUMN cloud_token VARCHAR(500)"))

+ 8 - 5
backend/app/services/virtual_printer/manager.py

@@ -28,8 +28,8 @@ logger = logging.getLogger(__name__)
 #   - https://github.com/psychoticbeef/BambuLabOrcaSlicerDiscovery
 #   - https://github.com/psychoticbeef/BambuLabOrcaSlicerDiscovery
 VIRTUAL_PRINTER_MODELS = {
 VIRTUAL_PRINTER_MODELS = {
     # X1 Series
     # X1 Series
-    "3DPrinter-X1-Carbon": "X1C",  # X1 Carbon
-    "3DPrinter-X1": "X1",  # X1
+    "BL-P001": "X1C",  # X1 Carbon
+    "BL-P002": "X1",  # X1
     "C13": "X1E",  # X1E
     "C13": "X1E",  # X1E
     # P Series
     # P Series
     "C11": "P1P",  # P1P
     "C11": "P1P",  # P1P
@@ -56,8 +56,8 @@ VIRTUAL_PRINTER_MODELS = {
 #   UUUUU = Unit number (5 chars)
 #   UUUUU = Unit number (5 chars)
 MODEL_SERIAL_PREFIXES = {
 MODEL_SERIAL_PREFIXES = {
     # X1 Series
     # X1 Series
-    "3DPrinter-X1-Carbon": "00M00A",  # X1C
-    "3DPrinter-X1": "00M00A",  # X1
+    "BL-P001": "00M00A",  # X1C
+    "BL-P002": "00M00A",  # X1
     "C13": "03W00A",  # X1E
     "C13": "03W00A",  # X1E
     # P Series
     # P Series
     "C11": "01S00A",  # P1P
     "C11": "01S00A",  # P1P
@@ -73,8 +73,11 @@ MODEL_SERIAL_PREFIXES = {
     "O1S": "09400A",  # H2S
     "O1S": "09400A",  # H2S
 }
 }
 
 
+# Reverse mapping: display name → SSDP model code (for auto-inheriting from printer model)
+DISPLAY_NAME_TO_MODEL_CODE = {v: k for k, v in VIRTUAL_PRINTER_MODELS.items()}
+
 # Default model
 # Default model
-DEFAULT_VIRTUAL_PRINTER_MODEL = "3DPrinter-X1-Carbon"  # X1C
+DEFAULT_VIRTUAL_PRINTER_MODEL = "BL-P001"  # X1C
 
 
 
 
 def _get_serial_for_model(model: str, serial_suffix: str) -> str:
 def _get_serial_for_model(model: str, serial_suffix: str) -> str:

+ 2 - 2
backend/app/services/virtual_printer/mqtt_server.py

@@ -18,8 +18,8 @@ MQTT_PORT = 8883
 
 
 # Model code → product_name for version response (must match what slicer expects)
 # Model code → product_name for version response (must match what slicer expects)
 MODEL_PRODUCT_NAMES = {
 MODEL_PRODUCT_NAMES = {
-    "3DPrinter-X1-Carbon": "X1 Carbon",
-    "3DPrinter-X1": "X1",
+    "BL-P001": "X1 Carbon",
+    "BL-P002": "X1",
     "C13": "X1E",
     "C13": "X1E",
     "C11": "P1P",
     "C11": "P1P",
     "C12": "P1S",
     "C12": "P1S",

+ 46 - 9
backend/tests/unit/services/test_virtual_printer.py

@@ -54,7 +54,7 @@ class TestVirtualPrinterInstance:
             vp_id=2,
             vp_id=2,
             name="X1C",
             name="X1C",
             mode="immediate",
             mode="immediate",
-            model="3DPrinter-X1-Carbon",
+            model="BL-P001",
             access_code="12345678",
             access_code="12345678",
             serial_suffix="391800002",
             serial_suffix="391800002",
             base_dir=tmp_path,
             base_dir=tmp_path,
@@ -1305,7 +1305,7 @@ class TestVirtualPrinterInstanceIPOverride:
             vp_id=20,
             vp_id=20,
             name="IPTest",
             name="IPTest",
             mode="immediate",
             mode="immediate",
-            model="3DPrinter-X1-Carbon",
+            model="BL-P001",
             access_code="12345678",
             access_code="12345678",
             serial_suffix="391800020",
             serial_suffix="391800020",
             bind_ip="192.168.1.50",
             bind_ip="192.168.1.50",
@@ -1342,7 +1342,7 @@ class TestVirtualPrinterInstanceIPOverride:
             vp_id=21,
             vp_id=21,
             name="NoRemote",
             name="NoRemote",
             mode="immediate",
             mode="immediate",
-            model="3DPrinter-X1-Carbon",
+            model="BL-P001",
             access_code="12345678",
             access_code="12345678",
             serial_suffix="391800021",
             serial_suffix="391800021",
             bind_ip="192.168.1.50",
             bind_ip="192.168.1.50",
@@ -1368,7 +1368,7 @@ class TestVirtualPrinterInstanceIPOverride:
             vp_id=22,
             vp_id=22,
             name="NoIPs",
             name="NoIPs",
             mode="immediate",
             mode="immediate",
-            model="3DPrinter-X1-Carbon",
+            model="BL-P001",
             access_code="12345678",
             access_code="12345678",
             serial_suffix="391800022",
             serial_suffix="391800022",
             base_dir=tmp_path,
             base_dir=tmp_path,
@@ -1396,7 +1396,7 @@ class TestBindServer:
 
 
         return BindServer(
         return BindServer(
             serial="01S00C000000001",
             serial="01S00C000000001",
-            model="3DPrinter-X1-Carbon",
+            model="BL-P001",
             name="Bambuddy",
             name="Bambuddy",
         )
         )
 
 
@@ -1464,7 +1464,7 @@ class TestBindServer:
     def test_bind_server_stores_config(self, bind_server):
     def test_bind_server_stores_config(self, bind_server):
         """Verify config is stored correctly."""
         """Verify config is stored correctly."""
         assert bind_server.serial == "01S00C000000001"
         assert bind_server.serial == "01S00C000000001"
-        assert bind_server.model == "3DPrinter-X1-Carbon"
+        assert bind_server.model == "BL-P001"
         assert bind_server.name == "Bambuddy"
         assert bind_server.name == "Bambuddy"
         assert bind_server.version == "01.00.00.00"
         assert bind_server.version == "01.00.00.00"
 
 
@@ -1474,7 +1474,7 @@ class TestBindServer:
 
 
         server = BindServer(
         server = BindServer(
             serial="01S00C000000001",
             serial="01S00C000000001",
-            model="3DPrinter-X1-Carbon",
+            model="BL-P001",
             name="Bambuddy",
             name="Bambuddy",
             version="01.09.00.10",
             version="01.09.00.10",
         )
         )
@@ -1501,7 +1501,7 @@ class TestBindServer:
             vp_id=99,
             vp_id=99,
             name="Bambuddy",
             name="Bambuddy",
             mode="immediate",
             mode="immediate",
-            model="3DPrinter-X1-Carbon",
+            model="BL-P001",
             access_code="12345678",
             access_code="12345678",
             serial_suffix="391800099",
             serial_suffix="391800099",
             bind_ip="192.168.1.50",
             bind_ip="192.168.1.50",
@@ -1524,9 +1524,46 @@ class TestBindServer:
 
 
             mock_bind_cls.assert_called_once_with(
             mock_bind_cls.assert_called_once_with(
                 serial=inst.serial,
                 serial=inst.serial,
-                model="3DPrinter-X1-Carbon",
+                model="BL-P001",
                 name="Bambuddy",
                 name="Bambuddy",
                 bind_address="192.168.1.50",
                 bind_address="192.168.1.50",
                 cert_path=Path("/tmp/cert.pem"),  # nosec B108
                 cert_path=Path("/tmp/cert.pem"),  # nosec B108
                 key_path=Path("/tmp/key.pem"),  # nosec B108
                 key_path=Path("/tmp/key.pem"),  # nosec B108
             )
             )
+
+
+class TestResolveModelCodes:
+    """Tests for model code resolution (display name → SSDP code)."""
+
+    def test_display_name_to_model_code_maps_all_models(self):
+        """Verify reverse mapping covers all VIRTUAL_PRINTER_MODELS entries."""
+        from backend.app.services.virtual_printer.manager import DISPLAY_NAME_TO_MODEL_CODE, VIRTUAL_PRINTER_MODELS
+
+        for _code, display_name in VIRTUAL_PRINTER_MODELS.items():
+            assert display_name in DISPLAY_NAME_TO_MODEL_CODE
+            # For non-duplicate display names, should map back to a valid code
+            assert DISPLAY_NAME_TO_MODEL_CODE[display_name] in VIRTUAL_PRINTER_MODELS
+
+    def test_resolve_printer_model_with_ssdp_code(self):
+        """SSDP codes pass through unchanged."""
+        from backend.app.api.routes.virtual_printers import _resolve_printer_model
+
+        assert _resolve_printer_model("BL-P001") == "BL-P001"
+        assert _resolve_printer_model("O1D") == "O1D"
+        assert _resolve_printer_model("N2S") == "N2S"
+
+    def test_resolve_printer_model_with_display_name(self):
+        """Display names resolve to SSDP codes."""
+        from backend.app.api.routes.virtual_printers import _resolve_printer_model
+
+        assert _resolve_printer_model("X1C") == "BL-P001"
+        assert _resolve_printer_model("H2D") == "O1D"
+        assert _resolve_printer_model("A1") == "N2S"
+        assert _resolve_printer_model("P1S") == "C12"
+
+    def test_resolve_printer_model_with_none_or_unknown(self):
+        """None and unknown values return None."""
+        from backend.app.api.routes.virtual_printers import _resolve_printer_model
+
+        assert _resolve_printer_model(None) is None
+        assert _resolve_printer_model("UnknownModel") is None

+ 2 - 2
frontend/src/__tests__/components/VirtualPrinterCard.test.tsx

@@ -30,7 +30,7 @@ vi.mock('../../api/client', () => ({
 import { multiVirtualPrinterApi } from '../../api/client';
 import { multiVirtualPrinterApi } from '../../api/client';
 
 
 const models: Record<string, string> = {
 const models: Record<string, string> = {
-  '3DPrinter-X1-Carbon': 'X1C',
+  'BL-P001': 'X1C',
   'C12': 'P1S',
   'C12': 'P1S',
 };
 };
 
 
@@ -39,7 +39,7 @@ const createMockPrinter = (overrides: Partial<VirtualPrinterConfig> = {}): Virtu
   name: 'Test VP',
   name: 'Test VP',
   enabled: false,
   enabled: false,
   mode: 'immediate',
   mode: 'immediate',
-  model: '3DPrinter-X1-Carbon',
+  model: 'BL-P001',
   model_name: 'X1C',
   model_name: 'X1C',
   access_code_set: false,
   access_code_set: false,
   serial: '00M00A391800001',
   serial: '00M00A391800001',

+ 5 - 5
frontend/src/__tests__/components/VirtualPrinterSettings.test.tsx

@@ -27,7 +27,7 @@ vi.mock('../../api/client', () => ({
     updateSettings: vi.fn(),
     updateSettings: vi.fn(),
     getModels: vi.fn().mockResolvedValue({
     getModels: vi.fn().mockResolvedValue({
       models: {
       models: {
-        '3DPrinter-X1-Carbon': 'X1C',
+        'BL-P001': 'X1C',
         'C12': 'P1S',
         'C12': 'P1S',
         'N7': 'P2S',
         'N7': 'P2S',
       },
       },
@@ -43,7 +43,7 @@ const createMockSettings = (overrides = {}) => ({
   enabled: false,
   enabled: false,
   access_code_set: false,
   access_code_set: false,
   mode: 'immediate' as const,
   mode: 'immediate' as const,
-  model: '3DPrinter-X1-Carbon',
+  model: 'BL-P001',
   target_printer_id: null as number | null,
   target_printer_id: null as number | null,
   remote_interface_ip: null as string | null,
   remote_interface_ip: null as string | null,
   status: {
   status: {
@@ -52,7 +52,7 @@ const createMockSettings = (overrides = {}) => ({
     mode: 'immediate',
     mode: 'immediate',
     name: 'Bambuddy',
     name: 'Bambuddy',
     serial: '00M00A391800001',
     serial: '00M00A391800001',
-    model: '3DPrinter-X1-Carbon',
+    model: 'BL-P001',
     model_name: 'X1C',
     model_name: 'X1C',
     pending_files: 0,
     pending_files: 0,
   },
   },
@@ -158,7 +158,7 @@ describe('VirtualPrinterSettings', () => {
             mode: 'immediate',
             mode: 'immediate',
             name: 'Bambuddy',
             name: 'Bambuddy',
             serial: '00M00A391800001',
             serial: '00M00A391800001',
-            model: '3DPrinter-X1-Carbon',
+            model: 'BL-P001',
             model_name: 'X1C',
             model_name: 'X1C',
             pending_files: 0,
             pending_files: 0,
           },
           },
@@ -512,7 +512,7 @@ describe('VirtualPrinterSettings', () => {
             mode: 'proxy',
             mode: 'proxy',
             name: 'Bambuddy (Proxy)',
             name: 'Bambuddy (Proxy)',
             serial: '00M00A391800001',
             serial: '00M00A391800001',
-            model: '3DPrinter-X1-Carbon',
+            model: 'BL-P001',
             model_name: 'X1C',
             model_name: 'X1C',
             pending_files: 0,
             pending_files: 0,
             proxy: {
             proxy: {

+ 1 - 1
frontend/src/components/VirtualPrinterSettings.tsx

@@ -17,7 +17,7 @@ export function VirtualPrinterSettings() {
   const [localEnabled, setLocalEnabled] = useState(false);
   const [localEnabled, setLocalEnabled] = useState(false);
   const [localAccessCode, setLocalAccessCode] = useState('');
   const [localAccessCode, setLocalAccessCode] = useState('');
   const [localMode, setLocalMode] = useState<LocalMode>('immediate');
   const [localMode, setLocalMode] = useState<LocalMode>('immediate');
-  const [localModel, setLocalModel] = useState('3DPrinter-X1-Carbon');
+  const [localModel, setLocalModel] = useState('BL-P001');
   const [localTargetPrinterId, setLocalTargetPrinterId] = useState<number | null>(null);
   const [localTargetPrinterId, setLocalTargetPrinterId] = useState<number | null>(null);
   const [localRemoteInterfaceIp, setLocalRemoteInterfaceIp] = useState('');
   const [localRemoteInterfaceIp, setLocalRemoteInterfaceIp] = useState('');
   const [showAccessCode, setShowAccessCode] = useState(false);
   const [showAccessCode, setShowAccessCode] = useState(false);