flashforge-python-api 1.2.0__py3-none-any.whl → 1.2.2__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.2"
131
131
  __author__ = "FlashForge Python API Contributors"
132
132
  __email__ = "notghosttypes@gmail.com"
133
133
  __description__ = "Python library for controlling FlashForge 3D printers"
@@ -4,12 +4,10 @@ FlashForge Python API - Control Module
4
4
 
5
5
  from typing import TYPE_CHECKING, Any
6
6
 
7
- import aiohttp
8
-
9
7
  from ...models.responses import FilamentArgs
10
8
  from ..constants.commands import Commands
11
9
  from ..constants.endpoints import Endpoints
12
- from ..network.utils import NetworkUtils
10
+ from ..network.utils import NetworkUtils, json_from_response
13
11
 
14
12
  if TYPE_CHECKING:
15
13
  from ...client import FlashForgeClient
@@ -239,26 +237,16 @@ class Control:
239
237
  try:
240
238
  await self.client.is_http_client_busy()
241
239
 
242
- async with aiohttp.ClientSession() as session:
243
- async with session.post(
244
- self.client.get_endpoint(Endpoints.CONTROL),
245
- json=payload,
246
- headers={"Content-Type": "application/json"},
247
- ) as response:
248
- # Fix for FlashForge printer's malformed Content-Type header
249
- # Some printers return "appliation/json" instead of "application/json"
250
- try:
251
- data = await response.json()
252
- except aiohttp.ContentTypeError:
253
- # Fallback: manually parse as JSON if Content-Type is malformed
254
- text = await response.text()
255
- import json
256
-
257
- data = json.loads(text)
258
-
259
- print(f"Command reply: {data}")
260
-
261
- return NetworkUtils.is_ok(data)
240
+ session = await self.client.get_http_session()
241
+ async with session.post(
242
+ self.client.get_endpoint(Endpoints.CONTROL),
243
+ json=payload,
244
+ headers={"Content-Type": "application/json"},
245
+ ) as response:
246
+ data = await json_from_response(response)
247
+ print(f"Command reply: {data}")
248
+
249
+ return NetworkUtils.is_ok(data)
262
250
 
263
251
  except Exception as e:
264
252
  print(f"Error in send_control_command: {e}")
@@ -5,13 +5,12 @@ FlashForge Python API - Files Module
5
5
  import base64
6
6
  from typing import TYPE_CHECKING
7
7
 
8
- import aiohttp
9
8
  from pydantic import ValidationError
10
9
 
11
10
  from ...models.machine_info import FFGcodeFileEntry
12
11
  from ...models.responses import GCodeListResponse, ThumbnailResponse
13
12
  from ..constants.endpoints import Endpoints
14
- from ..network.utils import NetworkUtils
13
+ from ..network.utils import NetworkUtils, json_from_response
15
14
 
16
15
  if TYPE_CHECKING:
17
16
  from ...client import FlashForgeClient
@@ -65,74 +64,65 @@ class Files:
65
64
  payload = {"serialNumber": self.client.serial_number, "checkCode": self.client.check_code}
66
65
 
67
66
  try:
68
- async with aiohttp.ClientSession() as session:
69
- async with session.post(
70
- self.client.get_endpoint(Endpoints.GCODE_LIST),
71
- json=payload,
72
- headers={"Content-Type": "application/json"},
73
- ) as response:
74
- if response.status != 200:
75
- return []
76
-
77
- # Fix for FlashForge printer's malformed Content-Type header
78
- # Some printers return "appliation/json" instead of "application/json"
79
- try:
80
- data = await response.json()
81
- except aiohttp.ContentTypeError:
82
- # Fallback: manually parse as JSON if Content-Type is malformed
83
- text = await response.text()
84
- import json
85
-
86
- data = json.loads(text)
87
-
88
- if not NetworkUtils.is_ok(data):
89
- print(f"Error retrieving file list: {NetworkUtils.get_error_message(data)}")
90
- return []
91
-
92
- # Parse the response using GCodeListResponse
93
- try:
94
- result = GCodeListResponse(**data)
95
- except ValidationError:
96
- raw_list = data.get("gcodeList", [])
97
- if isinstance(raw_list, list):
98
- entries: list[FFGcodeFileEntry] = []
99
- for file_name in raw_list:
100
- if isinstance(file_name, str):
101
- entries.append(
102
- FFGcodeFileEntry(
103
- gcodeFileName=file_name,
104
- printingTime=0,
105
- )
106
- )
107
- return entries
108
- return []
109
-
110
- # AD5X and newer printers provide detailed info in gcodeListDetail
111
- if result.gcode_list_detail and len(result.gcode_list_detail) > 0:
112
- return result.gcode_list_detail
113
-
114
- # Fallback for older printers using gcodeList
115
- if result.gcode_list and len(result.gcode_list) > 0:
116
- # Check if it's a list of strings or already FFGcodeFileEntry objects
117
- first_item = result.gcode_list[0]
118
-
119
- if isinstance(first_item, str):
120
- # Convert string array to FFGcodeFileEntry objects
121
- return [
122
- FFGcodeFileEntry(gcodeFileName=file_name, printingTime=0)
123
- for file_name in result.gcode_list
124
- if isinstance(file_name, str)
125
- ]
126
- elif isinstance(first_item, FFGcodeFileEntry):
127
- # Already FFGcodeFileEntry objects - need explicit type narrowing
128
- return [
129
- item
130
- for item in result.gcode_list
131
- if isinstance(item, FFGcodeFileEntry)
132
- ]
67
+ session = await self.client.get_http_session()
68
+ async with session.post(
69
+ self.client.get_endpoint(Endpoints.GCODE_LIST),
70
+ json=payload,
71
+ headers={"Content-Type": "application/json"},
72
+ ) as response:
73
+ if response.status != 200:
74
+ return []
133
75
 
76
+ data = await json_from_response(response)
77
+
78
+ if not NetworkUtils.is_ok(data):
79
+ print(f"Error retrieving file list: {NetworkUtils.get_error_message(data)}")
134
80
  return []
135
81
 
82
+ # Parse the response using GCodeListResponse
83
+ try:
84
+ result = GCodeListResponse(**data)
85
+ except ValidationError:
86
+ raw_list = data.get("gcodeList", [])
87
+ if isinstance(raw_list, list):
88
+ entries: list[FFGcodeFileEntry] = []
89
+ for file_name in raw_list:
90
+ if isinstance(file_name, str):
91
+ entries.append(
92
+ FFGcodeFileEntry(
93
+ gcodeFileName=file_name,
94
+ printingTime=0,
95
+ )
96
+ )
97
+ return entries
98
+ return []
99
+
100
+ # AD5X and newer printers provide detailed info in gcodeListDetail
101
+ if result.gcode_list_detail and len(result.gcode_list_detail) > 0:
102
+ return result.gcode_list_detail
103
+
104
+ # Fallback for older printers using gcodeList
105
+ if result.gcode_list and len(result.gcode_list) > 0:
106
+ # Check if it's a list of strings or already FFGcodeFileEntry objects
107
+ first_item = result.gcode_list[0]
108
+
109
+ if isinstance(first_item, str):
110
+ # Convert string array to FFGcodeFileEntry objects
111
+ return [
112
+ FFGcodeFileEntry(gcodeFileName=file_name, printingTime=0)
113
+ for file_name in result.gcode_list
114
+ if isinstance(file_name, str)
115
+ ]
116
+ elif isinstance(first_item, FFGcodeFileEntry):
117
+ # Already FFGcodeFileEntry objects - need explicit type narrowing
118
+ return [
119
+ item
120
+ for item in result.gcode_list
121
+ if isinstance(item, FFGcodeFileEntry)
122
+ ]
123
+
124
+ return []
125
+
136
126
  except Exception as err:
137
127
  print(f"GetRecentFileList error: {err}")
138
128
  return []
@@ -156,33 +146,24 @@ class Files:
156
146
  }
157
147
 
158
148
  try:
159
- async with aiohttp.ClientSession() as session:
160
- async with session.post(
161
- self.client.get_endpoint(Endpoints.GCODE_THUMB),
162
- json=payload,
163
- headers={"Content-Type": "application/json"},
164
- ) as response:
165
- if response.status != 200:
166
- return None
167
-
168
- # Fix for FlashForge printer's malformed Content-Type header
169
- # Some printers return "appliation/json" instead of "application/json"
170
- try:
171
- data = await response.json()
172
- except aiohttp.ContentTypeError:
173
- # Fallback: manually parse as JSON if Content-Type is malformed
174
- text = await response.text()
175
- import json
176
-
177
- data = json.loads(text)
178
-
179
- if NetworkUtils.is_ok(data):
180
- # Parse response and return decoded image bytes
181
- result = ThumbnailResponse(**data)
182
- return base64.b64decode(result.image_data)
183
- else:
184
- print(f"Error retrieving thumbnail: {NetworkUtils.get_error_message(data)}")
185
- return None
149
+ session = await self.client.get_http_session()
150
+ async with session.post(
151
+ self.client.get_endpoint(Endpoints.GCODE_THUMB),
152
+ json=payload,
153
+ headers={"Content-Type": "application/json"},
154
+ ) as response:
155
+ if response.status != 200:
156
+ return None
157
+
158
+ data = await json_from_response(response)
159
+
160
+ if NetworkUtils.is_ok(data):
161
+ # Parse response and return decoded image bytes
162
+ result = ThumbnailResponse(**data)
163
+ return base64.b64decode(result.image_data)
164
+ else:
165
+ print(f"Error retrieving thumbnail: {NetworkUtils.get_error_message(data)}")
166
+ return None
186
167
 
187
168
  except Exception as err:
188
169
  print(f"GetGcodeThumbnail error: {err}")
@@ -5,11 +5,10 @@ FlashForge Python API - Info Module
5
5
  from datetime import datetime, timedelta
6
6
  from typing import TYPE_CHECKING
7
7
 
8
- import aiohttp
9
-
10
8
  from ...models.machine_info import FFMachineInfo, MachineState, Temperature
11
9
  from ...models.responses import DetailResponse, FFPrinterDetail
12
10
  from ..constants.endpoints import Endpoints
11
+ from ..network.utils import json_from_response
13
12
 
14
13
  if TYPE_CHECKING:
15
14
  from ...client import FlashForgeClient
@@ -262,29 +261,18 @@ class Info:
262
261
  payload = {"serialNumber": self.client.serial_number, "checkCode": self.client.check_code}
263
262
 
264
263
  try:
265
- async with aiohttp.ClientSession() as session:
266
- async with session.post(
267
- self.client.get_endpoint(Endpoints.DETAIL),
268
- json=payload,
269
- headers={"Content-Type": "application/json"},
270
- ) as response:
271
- if response.status != 200:
272
- print(f"Non-200 status from detail endpoint: {response.status}")
273
- return None
274
-
275
- # Fix for FlashForge printer's malformed Content-Type header
276
- # Some printers return "appliation/json" instead of "application/json"
277
- # We'll manually parse the text as JSON to bypass Content-Type validation
278
- try:
279
- data = await response.json()
280
- except aiohttp.ContentTypeError:
281
- # Fallback: manually parse as JSON if Content-Type is malformed
282
- text = await response.text()
283
- import json
284
-
285
- data = json.loads(text)
286
-
287
- return DetailResponse(**data)
264
+ session = await self.client.get_http_session()
265
+ async with session.post(
266
+ self.client.get_endpoint(Endpoints.DETAIL),
267
+ json=payload,
268
+ headers={"Content-Type": "application/json"},
269
+ ) as response:
270
+ if response.status != 200:
271
+ print(f"Non-200 status from detail endpoint: {response.status}")
272
+ return None
273
+
274
+ data = await json_from_response(response)
275
+ return DetailResponse(**data)
288
276
 
289
277
  except Exception as error:
290
278
  print(f"GetDetailResponse Request error: {error}")
@@ -17,7 +17,7 @@ from ...models.responses import (
17
17
  AD5XUploadParams,
18
18
  )
19
19
  from ..constants.endpoints import Endpoints
20
- from ..network.utils import NetworkUtils
20
+ from ..network.utils import NetworkUtils, json_from_response
21
21
 
22
22
  if TYPE_CHECKING:
23
23
  from ...client import FlashForgeClient
@@ -180,17 +180,7 @@ class JobControl:
180
180
  print(f"Upload failed: Printer responded with status {response.status}")
181
181
  return False
182
182
 
183
- # Fix for FlashForge printer's malformed Content-Type header
184
- # Some printers return "appliation/json" instead of "application/json"
185
- try:
186
- result = await response.json()
187
- except aiohttp.ContentTypeError:
188
- # Fallback: manually parse as JSON if Content-Type is malformed
189
- text = await response.text()
190
- import json
191
-
192
- result = json.loads(text)
193
-
183
+ result = await json_from_response(response)
194
184
  print("Upload Response Data:", result)
195
185
 
196
186
  if NetworkUtils.is_ok(result):
@@ -240,27 +230,17 @@ class JobControl:
240
230
  }
241
231
 
242
232
  try:
243
- async with aiohttp.ClientSession() as session:
244
- async with session.post(
245
- self.client.get_endpoint(Endpoints.GCODE_PRINT),
246
- json=payload,
247
- headers={"Content-Type": "application/json"},
248
- ) as response:
249
- if response.status != 200:
250
- return False
251
-
252
- # Fix for FlashForge printer's malformed Content-Type header
253
- # Some printers return "appliation/json" instead of "application/json"
254
- try:
255
- result = await response.json()
256
- except aiohttp.ContentTypeError:
257
- # Fallback: manually parse as JSON if Content-Type is malformed
258
- text = await response.text()
259
- import json
260
-
261
- result = json.loads(text)
262
-
263
- return NetworkUtils.is_ok(result)
233
+ session = await self.client.get_http_session()
234
+ async with session.post(
235
+ self.client.get_endpoint(Endpoints.GCODE_PRINT),
236
+ json=payload,
237
+ headers={"Content-Type": "application/json"},
238
+ ) as response:
239
+ if response.status != 200:
240
+ return False
241
+
242
+ result = await json_from_response(response)
243
+ return NetworkUtils.is_ok(result)
264
244
 
265
245
  except Exception as error:
266
246
  print(f"PrintLocalFile error: {error}")
@@ -344,13 +324,7 @@ class JobControl:
344
324
  )
345
325
  return False
346
326
 
347
- # Fix for FlashForge printer's malformed Content-Type header
348
- try:
349
- result = await response.json()
350
- except aiohttp.ContentTypeError:
351
- text = await response.text()
352
- result = json.loads(text)
353
-
327
+ result = await json_from_response(response)
354
328
  print("AD5X Upload Response Data:", result)
355
329
 
356
330
  if NetworkUtils.is_ok(result):
@@ -415,23 +389,17 @@ class JobControl:
415
389
  }
416
390
 
417
391
  try:
418
- async with aiohttp.ClientSession() as session:
419
- async with session.post(
420
- self.client.get_endpoint(Endpoints.GCODE_PRINT),
421
- json=payload,
422
- headers={"Content-Type": "application/json"},
423
- ) as response:
424
- if response.status != 200:
425
- return False
426
-
427
- # Fix for FlashForge printer's malformed Content-Type header
428
- try:
429
- result = await response.json()
430
- except aiohttp.ContentTypeError:
431
- text = await response.text()
432
- result = json.loads(text)
433
-
434
- return NetworkUtils.is_ok(result)
392
+ session = await self.client.get_http_session()
393
+ async with session.post(
394
+ self.client.get_endpoint(Endpoints.GCODE_PRINT),
395
+ json=payload,
396
+ headers={"Content-Type": "application/json"},
397
+ ) as response:
398
+ if response.status != 200:
399
+ return False
400
+
401
+ result = await json_from_response(response)
402
+ return NetworkUtils.is_ok(result)
435
403
 
436
404
  except Exception as error:
437
405
  print(f"AD5X Multi-Color Job error: {error}")
@@ -473,23 +441,17 @@ class JobControl:
473
441
  }
474
442
 
475
443
  try:
476
- async with aiohttp.ClientSession() as session:
477
- async with session.post(
478
- self.client.get_endpoint(Endpoints.GCODE_PRINT),
479
- json=payload,
480
- headers={"Content-Type": "application/json"},
481
- ) as response:
482
- if response.status != 200:
483
- return False
484
-
485
- # Fix for FlashForge printer's malformed Content-Type header
486
- try:
487
- result = await response.json()
488
- except aiohttp.ContentTypeError:
489
- text = await response.text()
490
- result = json.loads(text)
491
-
492
- return NetworkUtils.is_ok(result)
444
+ session = await self.client.get_http_session()
445
+ async with session.post(
446
+ self.client.get_endpoint(Endpoints.GCODE_PRINT),
447
+ json=payload,
448
+ headers={"Content-Type": "application/json"},
449
+ ) as response:
450
+ if response.status != 200:
451
+ return False
452
+
453
+ result = await json_from_response(response)
454
+ return NetworkUtils.is_ok(result)
493
455
 
494
456
  except Exception as error:
495
457
  print(f"AD5X Single-Color Job error: {error}")
@@ -2,9 +2,26 @@
2
2
  FlashForge Python API - Network Utilities
3
3
  """
4
4
 
5
+ import json
6
+
7
+ import aiohttp
8
+
5
9
  from ...models.responses import GenericResponse
6
10
 
7
11
 
12
+ async def json_from_response(response: aiohttp.ClientResponse) -> dict:
13
+ """Parse JSON from a response, with a fallback for malformed Content-Type headers.
14
+
15
+ Some FlashForge printers return "appliation/json" instead of "application/json",
16
+ causing aiohttp to raise ContentTypeError. This helper handles both cases.
17
+ """
18
+ try:
19
+ return await response.json() # type: ignore[no-any-return]
20
+ except aiohttp.ContentTypeError:
21
+ text = await response.text()
22
+ return json.loads(text)
23
+
24
+
8
25
  class NetworkUtils:
9
26
  """Utility class for handling network responses and status codes."""
10
27
 
flashforge/client.py CHANGED
@@ -13,7 +13,7 @@ import aiohttp
13
13
  from .api.constants.endpoints import CAMERA_STREAM_PORT, Endpoints
14
14
  from .api.controls import Control, Files, Info, JobControl, TempControl
15
15
  from .api.controls.info import MachineInfoParser
16
- from .api.network.utils import NetworkUtils
16
+ from .api.network.utils import NetworkUtils, json_from_response
17
17
  from .models import FFMachineInfo, Product, ProductResponse
18
18
  from .tcp import FlashForgeClient as TcpClient
19
19
  from .tcp import FlashForgeTcpClientOptions, PrinterInfo
@@ -60,7 +60,7 @@ class FlashForgeClient:
60
60
 
61
61
  # Constants
62
62
  self._PORT = options.http_port if options and options.http_port is not None else 8898
63
- self._HTTP_TIMEOUT = 5.0
63
+ self._HTTP_TIMEOUT = 15.0
64
64
 
65
65
  # HTTP client state
66
66
  self._http_session: aiohttp.ClientSession | None = None
@@ -379,16 +379,7 @@ class FlashForgeClient:
379
379
  if response.status != 200:
380
380
  return False
381
381
 
382
- # Fix for FlashForge printer's malformed Content-Type header
383
- # Some printers return "appliation/json" instead of "application/json"
384
- try:
385
- data = await response.json()
386
- except aiohttp.ContentTypeError:
387
- # Fallback: manually parse as JSON if Content-Type is malformed
388
- text = await response.text()
389
- import json
390
-
391
- data = json.loads(text)
382
+ data = await json_from_response(response)
392
383
 
393
384
  # Validate response structure
394
385
  if not NetworkUtils.is_ok(data):
@@ -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"]
@@ -218,7 +218,7 @@ class FFPrinterDetail(BaseModel):
218
218
  coordinate: list[float] | None = Field(default=None, alias="coordinate")
219
219
  cumulative_filament: float | None = Field(default=None, ge=0, alias="cumulativeFilament")
220
220
  cumulative_print_time: int | None = Field(default=None, ge=0, alias="cumulativePrintTime")
221
- current_print_speed: int | None = Field(default=None, ge=0, le=200, alias="currentPrintSpeed")
221
+ current_print_speed: int | None = Field(default=None, ge=0, le=1000, alias="currentPrintSpeed")
222
222
  door_status: str | None = Field(default=None, alias="doorStatus")
223
223
  error_code: str | None = Field(default=None, alias="errorCode")
224
224
  estimated_left_len: float | None = Field(default=None, ge=0, alias="estimatedLeftLen")
@@ -259,7 +259,7 @@ class FFPrinterDetail(BaseModel):
259
259
  print_file_thumb_url: str | None = Field(default=None, alias="printFileThumbUrl")
260
260
  print_layer: int | None = Field(default=None, ge=0, alias="printLayer")
261
261
  print_progress: float | None = Field(default=None, ge=0, le=100, alias="printProgress")
262
- print_speed_adjust: int | None = Field(default=None, ge=0, le=200, alias="printSpeedAdjust")
262
+ print_speed_adjust: int | None = Field(default=None, ge=0, le=1000, alias="printSpeedAdjust")
263
263
  remaining_disk_space: float | None = Field(default=None, ge=0, alias="remainingDiskSpace")
264
264
  right_filament_type: str | None = Field(default=None, alias="rightFilamentType")
265
265
  right_target_temp: float | None = Field(default=None, ge=-50, le=500, alias="rightTargetTemp")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flashforge-python-api
3
- Version: 1.2.0
3
+ Version: 1.2.2
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,14 +1,14 @@
1
- flashforge/__init__.py,sha256=3fmBeEJ4vFJDSsOQV0Yixb1yLyUw8nHDZKQHbXIbtmo,5336
2
- flashforge/client.py,sha256=lmkPfkhcfDslbROv6XouwTIHshz4rHS0wNNJeicBHiA,17334
1
+ flashforge/__init__.py,sha256=NjSFURsQlavWjvD6CikOj01SXqpdmGF-PlAHyLXtc-4,5336
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
5
5
  flashforge/api/constants/commands.py,sha256=S9cBgU3pBXQm-dp3Bv4795NL059pmTY1NtxWzPA5gWU,329
6
6
  flashforge/api/constants/endpoints.py,sha256=zU_ONPyLUATr1KPlk0x8SLrK4ojs4QiiWr1-WwLVwTY,338
7
7
  flashforge/api/controls/__init__.py,sha256=53s-H25Pjwr0kvPk8MZ2Twy8VHGqGcO6Q6uBphhEOh4,293
8
- flashforge/api/controls/control.py,sha256=hifFWRPs2fRvNaJMSLgDJ4ytkHrEMGqzgCazuAC6ewA,11787
9
- flashforge/api/controls/files.py,sha256=fFjxOCVbK7q1P4GM9RcgB2tsJsywT_HiTBcL3-U0_Yo,7749
10
- flashforge/api/controls/info.py,sha256=r_gi20ZlgpdtCJVqtoyncV714-MQcDjg4Qg4xeTWHNA,12969
11
- flashforge/api/controls/job_control.py,sha256=uhuQE3weXJPvWBER0P7lvqWEzAVHXFNXWWBFfnZwtR8,23656
8
+ flashforge/api/controls/control.py,sha256=8wK2YOGf9NphWXsjIDapyQEfx-qGZgytXZFsjKeWvSA,11291
9
+ flashforge/api/controls/files.py,sha256=8T9kzgss_2DYFL4WVuEFj8W9sOPPPMIMq3dTq1d551w,6560
10
+ flashforge/api/controls/info.py,sha256=KVExwaeDd1SYiSOBZVVtif3Rf_cJkw4Bizhgm6dbAS8,12398
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
14
14
  flashforge/api/filament/filament.py,sha256=TtcC2G2nD9Sq4cOrCZzFLZ6Q0Ve-zfrfuEF9uQ8M3eE,1214
@@ -17,11 +17,11 @@ flashforge/api/misc/scientific_notation.py,sha256=KSMTrrYhPWLv0gbcEAD6u-jEOcIwUj
17
17
  flashforge/api/misc/temperature.py,sha256=qeHlCdPzLyqGDEB874Ib5AgYHMR9Qtw61shnw4G095E,1066
18
18
  flashforge/api/network/__init__.py,sha256=G5fBoAlq-NQPDkWp579mFIqsj0v7B1Lb2TAzO6IZA0Q,164
19
19
  flashforge/api/network/fnet_code.py,sha256=BR2niVKKk4ZfXtizUDIMXzDhlCxPHzqWg5AQQIcoj3g,402
20
- flashforge/api/network/utils.py,sha256=jwqyJaByr4q80_MJOTwS0nX8I8FZ54T_CHBwkKTXptA,1519
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=BvIaefjpApsbSVQA8eOG3NSm2L1ltZ1kcHH1118frZc,14974
24
+ flashforge/models/machine_info.py,sha256=t7TJk03PG8kf-g6XnWd3NiWuCVLY1Wuod-aI4zdXu4Y,14976
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.0.dist-info/METADATA,sha256=UYZ0LVlLhqxWzMWv0-IjO35VefbUEBBqN3NZhK21uD4,4194
43
- flashforge_python_api-1.2.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
44
- flashforge_python_api-1.2.0.dist-info/entry_points.txt,sha256=AkOxlsLvQ7cvMLxn7tlzfKp_DCH2hXhbVceHIXxawpU,66
45
- flashforge_python_api-1.2.0.dist-info/licenses/LICENSE,sha256=-cTA-hrmvlb3pqlQrBZQXUnKayhUjsLJMPb7TD91frM,1067
46
- flashforge_python_api-1.2.0.dist-info/RECORD,,
42
+ flashforge_python_api-1.2.2.dist-info/METADATA,sha256=fgIgmb7nLlupuynCJcbY5V-fF_g4hNI48e9d61Qym0s,4787
43
+ flashforge_python_api-1.2.2.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
44
+ flashforge_python_api-1.2.2.dist-info/entry_points.txt,sha256=AkOxlsLvQ7cvMLxn7tlzfKp_DCH2hXhbVceHIXxawpU,66
45
+ flashforge_python_api-1.2.2.dist-info/licenses/LICENSE,sha256=-cTA-hrmvlb3pqlQrBZQXUnKayhUjsLJMPb7TD91frM,1067
46
+ flashforge_python_api-1.2.2.dist-info/RECORD,,