flashforge-python-api 1.2.1__py3-none-any.whl → 1.2.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
flashforge/__init__.py CHANGED
@@ -127,7 +127,7 @@ from .tcp import (
127
127
  )
128
128
 
129
129
  FiveMClient = FlashForgeClient
130
- __version__ = "1.1.1"
130
+ __version__ = "1.2.3"
131
131
  __author__ = "FlashForge Python API Contributors"
132
132
  __email__ = "notghosttypes@gmail.com"
133
133
  __description__ = "Python library for controlling FlashForge 3D printers"
@@ -14,6 +14,14 @@ if TYPE_CHECKING:
14
14
  from ...client import FlashForgeClient
15
15
 
16
16
 
17
+ # Firmware-reported PIDs from FlashForge's /detail endpoint. These are stable
18
+ # identifiers set by firmware, unlike the user-mutable `name` field.
19
+ PID_5M = 35
20
+ PID_5M_PRO = 36
21
+ PID_AD5X = 38
22
+ KNOWN_HTTP_PIDS = {PID_5M, PID_5M_PRO, PID_AD5X}
23
+
24
+
17
25
  class MachineInfoParser:
18
26
  """
19
27
  Transforms printer detail data from the API response format into a structured FFMachineInfo object.
@@ -68,8 +76,17 @@ class MachineInfoParser:
68
76
  or len(getattr(getattr(detail, "matl_station_info", None), "slot_infos", []) or []) > 0
69
77
  )
70
78
  printer_name = getattr(detail, "name", "") or ""
71
- is_ad5x = printer_name.upper() == "AD5X" or has_material_station
72
- is_pro = "Pro" in printer_name and not is_ad5x
79
+ pid = getattr(detail, "pid", None)
80
+
81
+ if pid in KNOWN_HTTP_PIDS:
82
+ is_ad5x = pid == PID_AD5X
83
+ is_pro = pid == PID_5M_PRO
84
+ else:
85
+ # Fallback for firmware that doesn't report pid: legacy
86
+ # name+capability heuristic. Vulnerable to user renames, which
87
+ # is why pid-based detection is preferred when available.
88
+ is_ad5x = printer_name.upper() == "AD5X" or has_material_station
89
+ is_pro = "Pro" in printer_name and not is_ad5x
73
90
 
74
91
  # Build the FFMachineInfo object
75
92
  machine_info = FFMachineInfo(
@@ -107,6 +124,7 @@ class MachineInfoParser:
107
124
  fill_amount=getattr(detail, "fill_amount", 0) or 0,
108
125
  firmware_version=getattr(detail, "firmware_version", "") or "",
109
126
  name=printer_name,
127
+ pid=pid,
110
128
  is_pro=is_pro,
111
129
  is_ad5x=is_ad5x,
112
130
  nozzle_size=getattr(detail, "nozzle_model", "") or "",
@@ -12,7 +12,7 @@ from dataclasses import dataclass, field
12
12
  from enum import IntEnum, StrEnum
13
13
  from typing import Any
14
14
 
15
- import netifaces
15
+ import ifaddr
16
16
 
17
17
  logger = logging.getLogger(__name__)
18
18
 
@@ -535,26 +535,27 @@ class PrinterDiscovery:
535
535
  broadcast_addresses: list[str] = []
536
536
 
537
537
  try:
538
- for interface_name in netifaces.interfaces():
539
- try:
540
- addresses = netifaces.ifaddresses(interface_name)
541
- if netifaces.AF_INET not in addresses:
538
+ for adapter in ifaddr.get_adapters():
539
+ for ip in adapter.ips:
540
+ if not ip.is_IPv4:
541
+ continue
542
+
543
+ ip_addr = ip.ip
544
+ if not isinstance(ip_addr, str) or ip_addr.startswith("127."):
542
545
  continue
543
546
 
544
- for addr_info in addresses[netifaces.AF_INET]:
545
- if addr_info.get("addr", "").startswith("127."):
546
- continue
547
+ prefix = ip.network_prefix
548
+ if not isinstance(prefix, int) or not 0 < prefix <= 32:
549
+ continue
547
550
 
548
- ip_addr = addr_info.get("addr")
549
- netmask = addr_info.get("netmask")
550
- if not ip_addr or not netmask:
551
- continue
551
+ mask_int = (0xFFFFFFFF << (32 - prefix)) & 0xFFFFFFFF
552
+ netmask = ".".join(
553
+ str((mask_int >> (8 * (3 - i))) & 0xFF) for i in range(4)
554
+ )
552
555
 
553
- broadcast = self.calculate_broadcast_address(ip_addr, netmask)
554
- if broadcast and broadcast not in broadcast_addresses:
555
- broadcast_addresses.append(broadcast)
556
- except Exception as error:
557
- logger.warning("Error processing interface %s: %s", interface_name, error)
556
+ broadcast = self.calculate_broadcast_address(ip_addr, netmask)
557
+ if broadcast and broadcast not in broadcast_addresses:
558
+ broadcast_addresses.append(broadcast)
558
559
  except Exception as error:
559
560
  logger.error("Error getting network interfaces: %s", error)
560
561
  broadcast_addresses = ["255.255.255.255", "192.168.1.255", "192.168.0.255"]
@@ -324,6 +324,7 @@ class FFMachineInfo(BaseModel):
324
324
  fill_amount: float = 0.0
325
325
  firmware_version: str = ""
326
326
  name: str = ""
327
+ pid: int | None = None
327
328
  is_pro: bool = False
328
329
  is_ad5x: bool = False
329
330
  nozzle_size: str = ""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flashforge-python-api
3
- Version: 1.2.1
3
+ Version: 1.2.3
4
4
  Summary: A comprehensive Python library for controlling FlashForge 3D printers
5
5
  Project-URL: Homepage, https://github.com/GhostTypes/ff-5mp-api-py
6
6
  Project-URL: Documentation, https://github.com/GhostTypes/ff-5mp-api-py#readme
@@ -16,12 +16,13 @@ Classifier: Programming Language :: Python :: 3
16
16
  Classifier: Programming Language :: Python :: 3.11
17
17
  Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
19
20
  Classifier: Topic :: Scientific/Engineering
20
21
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
22
  Classifier: Topic :: System :: Hardware :: Hardware Drivers
22
23
  Requires-Python: >=3.11
23
24
  Requires-Dist: aiohttp>=3.8.0
24
- Requires-Dist: netifaces>=0.11.0
25
+ Requires-Dist: ifaddr>=0.2.0
25
26
  Requires-Dist: pydantic>=2.0.0
26
27
  Requires-Dist: requests>=2.31.0
27
28
  Provides-Extra: all
@@ -70,14 +71,20 @@ Modern LAN-mode HTTP printers require:
70
71
 
71
72
  - printer IP address
72
73
  - serial number
73
- - check code
74
+ - check code (per-printer credential, not returned by discovery)
74
75
 
75
76
  ```python
76
77
  import asyncio
77
- from flashforge import FlashForgeClient, PrinterDiscovery
78
+ import os
79
+ from flashforge import FlashForgeClient, FiveMClientConnectionOptions, PrinterDiscovery
78
80
 
79
81
 
80
82
  async def main():
83
+ check_code = os.getenv("FLASHFORGE_CHECK_CODE", "").strip()
84
+ if not check_code:
85
+ print("Set FLASHFORGE_CHECK_CODE before running this example")
86
+ return
87
+
81
88
  discovery = PrinterDiscovery()
82
89
  printers = await discovery.discover()
83
90
 
@@ -86,13 +93,23 @@ async def main():
86
93
  return
87
94
 
88
95
  printer = printers[0]
96
+ if not printer.serial_number:
97
+ print("Discovered printer did not report a serial number")
98
+ return
99
+
100
+ options = FiveMClientConnectionOptions(
101
+ http_port=printer.event_port,
102
+ tcp_port=printer.command_port,
103
+ )
89
104
 
90
105
  async with FlashForgeClient(
91
106
  printer.ip_address,
92
- printer.serial_number or "SERIAL_NUMBER",
93
- "CHECK_CODE",
107
+ printer.serial_number,
108
+ check_code,
109
+ options=options,
94
110
  ) as client:
95
- if not await client.initialize():
111
+ status = await client.get_printer_status()
112
+ if not status:
96
113
  return
97
114
 
98
115
  await client.init_control()
@@ -1,4 +1,4 @@
1
- flashforge/__init__.py,sha256=3fmBeEJ4vFJDSsOQV0Yixb1yLyUw8nHDZKQHbXIbtmo,5336
1
+ flashforge/__init__.py,sha256=TkwDdU0toTFKhbDE6-9P85X8o4_GKwkdFTp4CcdFMt4,5336
2
2
  flashforge/client.py,sha256=lplKJxNOLhUJMusm025cj7rZvWRAiv2j5qENdBIctuA,16920
3
3
  flashforge/api/__init__.py,sha256=vQz-DkG6LTH39bI4fyiUc0D9jQzemLh45pORLptSOlg,335
4
4
  flashforge/api/constants/__init__.py,sha256=Q0HL2tqSBYPd4Oz49VHLS3qUvRuv__GCvTGecaLrQ-Y,163
@@ -7,7 +7,7 @@ flashforge/api/constants/endpoints.py,sha256=zU_ONPyLUATr1KPlk0x8SLrK4ojs4QiiWr1
7
7
  flashforge/api/controls/__init__.py,sha256=53s-H25Pjwr0kvPk8MZ2Twy8VHGqGcO6Q6uBphhEOh4,293
8
8
  flashforge/api/controls/control.py,sha256=8wK2YOGf9NphWXsjIDapyQEfx-qGZgytXZFsjKeWvSA,11291
9
9
  flashforge/api/controls/files.py,sha256=8T9kzgss_2DYFL4WVuEFj8W9sOPPPMIMq3dTq1d551w,6560
10
- flashforge/api/controls/info.py,sha256=KVExwaeDd1SYiSOBZVVtif3Rf_cJkw4Bizhgm6dbAS8,12398
10
+ flashforge/api/controls/info.py,sha256=CesJXAQZkmF_o9dNKeZmeu_44ysDK4Lj8uHRwAVcv4w,13086
11
11
  flashforge/api/controls/job_control.py,sha256=-DglR97DWBzl45mmqGkauYpUM_I-C0tXrbp9SKPYOVs,21814
12
12
  flashforge/api/controls/temp_control.py,sha256=TGkX63WHygVA9DeYfLARbktZ0o-bYX6MNyYOX6V_RB4,3034
13
13
  flashforge/api/filament/__init__.py,sha256=isT2dl0hzUS0xMTXMm4Tip0GTG1gCsm8LKWa9xPu4Y8,104
@@ -19,9 +19,9 @@ flashforge/api/network/__init__.py,sha256=G5fBoAlq-NQPDkWp579mFIqsj0v7B1Lb2TAzO6
19
19
  flashforge/api/network/fnet_code.py,sha256=BR2niVKKk4ZfXtizUDIMXzDhlCxPHzqWg5AQQIcoj3g,402
20
20
  flashforge/api/network/utils.py,sha256=hh-9SGI61ii0CRkLVnAp-Eq2E0a9PAKPyNLMGaYPNsI,2065
21
21
  flashforge/discovery/__init__.py,sha256=G0WiP70EhfHdjXR1RNlv6xjhyHOdo-HWyOm9J0ds4SM,828
22
- flashforge/discovery/discovery.py,sha256=lNyD-nDDdYKV23kUOQpuA5ZX4r9T7_Zk3SzXoUwgMPI,26163
22
+ flashforge/discovery/discovery.py,sha256=ka75bIXnn2a14DEpzpmJxUGajF-tpLHUR2EHA88NIGc,26073
23
23
  flashforge/models/__init__.py,sha256=MNpnXdS6Yah5kSwBB7cbA2Hd-8azffE62pgUrrKSZk4,984
24
- flashforge/models/machine_info.py,sha256=t7TJk03PG8kf-g6XnWd3NiWuCVLY1Wuod-aI4zdXu4Y,14976
24
+ flashforge/models/machine_info.py,sha256=HDOzXi9RaYgFPkqtYe8dLyM5c8ZUCFfZ_uefGIj1VYM,15003
25
25
  flashforge/models/responses.py,sha256=befMeXAc7XXiK7emF8Ep2cd1GuhZ-zyYPbbPqS2lcQI,7214
26
26
  flashforge/tcp/__init__.py,sha256=hpnqoWHeRtTwJPzdsVlwGt1njKbAkrgIHKADkLSIRec,1317
27
27
  flashforge/tcp/a3_client.py,sha256=a1jdDcBnTSNrzJFKTuVV13fSLbZmxKEZ94n3cGf3wOA,15603
@@ -39,8 +39,8 @@ flashforge/tcp/parsers/print_status.py,sha256=4q9ZlJfO0c7_t1o15tgxbguMxpDjEW9pMy
39
39
  flashforge/tcp/parsers/printer_info.py,sha256=QY6LECfcOhVHSIdsvQZwJago0OjXroUwiX0H-uQaH2A,5411
40
40
  flashforge/tcp/parsers/temp_info.py,sha256=9Waf68tt-4QBcChyUwfMHDwWdIzM4mmDpZAKeAzklgs,7908
41
41
  flashforge/tcp/parsers/thumbnail_info.py,sha256=ZmxdEbVFOzqR1AfhXXZyjENVI66BdRdE7Q8LQ8an9FY,10201
42
- flashforge_python_api-1.2.1.dist-info/METADATA,sha256=eMFS4ENMi13-7DADIKwTNTSQyBoFjHrGrLMb_gw4dZw,4194
43
- flashforge_python_api-1.2.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
44
- flashforge_python_api-1.2.1.dist-info/entry_points.txt,sha256=AkOxlsLvQ7cvMLxn7tlzfKp_DCH2hXhbVceHIXxawpU,66
45
- flashforge_python_api-1.2.1.dist-info/licenses/LICENSE,sha256=-cTA-hrmvlb3pqlQrBZQXUnKayhUjsLJMPb7TD91frM,1067
46
- flashforge_python_api-1.2.1.dist-info/RECORD,,
42
+ flashforge_python_api-1.2.3.dist-info/METADATA,sha256=8Gji1l3nedh4E53ZM5AE7wkP0JoPBGOucAoBIK_yEO4,4787
43
+ flashforge_python_api-1.2.3.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
44
+ flashforge_python_api-1.2.3.dist-info/entry_points.txt,sha256=AkOxlsLvQ7cvMLxn7tlzfKp_DCH2hXhbVceHIXxawpU,66
45
+ flashforge_python_api-1.2.3.dist-info/licenses/LICENSE,sha256=-cTA-hrmvlb3pqlQrBZQXUnKayhUjsLJMPb7TD91frM,1067
46
+ flashforge_python_api-1.2.3.dist-info/RECORD,,