flashforge-python-api 1.0.0__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.
Files changed (43) hide show
  1. flashforge/__init__.py +188 -0
  2. flashforge/api/__init__.py +17 -0
  3. flashforge/api/constants/__init__.py +10 -0
  4. flashforge/api/constants/commands.py +10 -0
  5. flashforge/api/constants/endpoints.py +11 -0
  6. flashforge/api/controls/__init__.py +16 -0
  7. flashforge/api/controls/control.py +357 -0
  8. flashforge/api/controls/files.py +171 -0
  9. flashforge/api/controls/info.py +292 -0
  10. flashforge/api/controls/job_control.py +561 -0
  11. flashforge/api/controls/temp_control.py +89 -0
  12. flashforge/api/filament/__init__.py +7 -0
  13. flashforge/api/filament/filament.py +39 -0
  14. flashforge/api/misc/__init__.py +8 -0
  15. flashforge/api/misc/scientific_notation.py +28 -0
  16. flashforge/api/misc/temperature.py +42 -0
  17. flashforge/api/network/__init__.py +10 -0
  18. flashforge/api/network/fnet_code.py +15 -0
  19. flashforge/api/network/utils.py +55 -0
  20. flashforge/client.py +381 -0
  21. flashforge/discovery/__init__.py +12 -0
  22. flashforge/discovery/discovery.py +388 -0
  23. flashforge/models/__init__.py +50 -0
  24. flashforge/models/machine_info.py +247 -0
  25. flashforge/models/responses.py +123 -0
  26. flashforge/tcp/__init__.py +41 -0
  27. flashforge/tcp/ff_client.py +587 -0
  28. flashforge/tcp/gcode/__init__.py +11 -0
  29. flashforge/tcp/gcode/gcode_controller.py +296 -0
  30. flashforge/tcp/gcode/gcodes.py +93 -0
  31. flashforge/tcp/parsers/__init__.py +27 -0
  32. flashforge/tcp/parsers/endstop_status.py +273 -0
  33. flashforge/tcp/parsers/location_info.py +68 -0
  34. flashforge/tcp/parsers/print_status.py +182 -0
  35. flashforge/tcp/parsers/printer_info.py +154 -0
  36. flashforge/tcp/parsers/temp_info.py +217 -0
  37. flashforge/tcp/parsers/thumbnail_info.py +242 -0
  38. flashforge/tcp/tcp_client.py +448 -0
  39. flashforge_python_api-1.0.0.dist-info/METADATA +123 -0
  40. flashforge_python_api-1.0.0.dist-info/RECORD +43 -0
  41. flashforge_python_api-1.0.0.dist-info/WHEEL +4 -0
  42. flashforge_python_api-1.0.0.dist-info/entry_points.txt +2 -0
  43. flashforge_python_api-1.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,561 @@
1
+ """
2
+ FlashForge Python API - Job Control Module
3
+ """
4
+ import base64
5
+ import json
6
+ import re
7
+ from pathlib import Path
8
+ from typing import TYPE_CHECKING, Optional
9
+
10
+ import aiohttp
11
+
12
+ from ...models.responses import AD5XMaterialMapping, AD5XUploadParams, AD5XLocalJobParams, AD5XSingleColorJobParams
13
+ from ..constants.endpoints import Endpoints
14
+ from ..network.utils import NetworkUtils
15
+
16
+ if TYPE_CHECKING:
17
+ from ...client import FlashForgeClient
18
+ from .control import Control
19
+
20
+
21
+ class JobControl:
22
+ """
23
+ Provides methods for managing print jobs on the FlashForge 3D printer.
24
+ This includes pausing, resuming, canceling prints, uploading files for printing,
25
+ and starting prints from local files.
26
+ """
27
+
28
+ def __init__(self, client: "FlashForgeClient"):
29
+ """
30
+ Creates an instance of the JobControl class.
31
+
32
+ Args:
33
+ client: The FlashForgeClient instance used for communication with the printer.
34
+ """
35
+ self.client = client
36
+ self._control: Optional[Control] = None
37
+
38
+ @property
39
+ def control(self) -> "Control":
40
+ """Get the control instance."""
41
+ if self._control is None:
42
+ self._control = self.client.control
43
+ return self._control
44
+
45
+ async def pause_print_job(self) -> bool:
46
+ """
47
+ Pauses the current print job.
48
+
49
+ Returns:
50
+ True if the command is successful, False otherwise.
51
+ """
52
+ return await self.control.send_job_control_cmd("pause")
53
+
54
+ async def resume_print_job(self) -> bool:
55
+ """
56
+ Resumes a paused print job.
57
+
58
+ Returns:
59
+ True if the command is successful, False otherwise.
60
+ """
61
+ return await self.control.send_job_control_cmd("continue")
62
+
63
+ async def cancel_print_job(self) -> bool:
64
+ """
65
+ Cancels the current print job.
66
+
67
+ Returns:
68
+ True if the command is successful, False otherwise.
69
+ """
70
+ return await self.control.send_job_control_cmd("cancel")
71
+
72
+ def _is_new_firmware_version(self) -> bool:
73
+ """
74
+ Checks if the printer's firmware version is 3.1.3 or newer.
75
+ This is used to determine which API payload format to use for certain commands.
76
+
77
+ Returns:
78
+ True if the firmware is new (>= 3.1.3), False otherwise or if version cannot be determined.
79
+ """
80
+ try:
81
+ current_version = self.client.firmware_ver.split('.')
82
+ min_version = [3, 1, 3]
83
+
84
+ for i in range(3):
85
+ current = int(current_version[i] if i < len(current_version) else '0')
86
+ if current > min_version[i]:
87
+ return True
88
+ if current < min_version[i]:
89
+ return False
90
+
91
+ return True # Equal versions
92
+ except Exception:
93
+ return False
94
+
95
+ async def clear_platform(self) -> bool:
96
+ """
97
+ Sends a command to clear the printer's build platform.
98
+
99
+ Returns:
100
+ True if the command is successful, False otherwise.
101
+ """
102
+ args = {
103
+ "action": "setClearPlatform"
104
+ }
105
+
106
+ return await self.control.send_control_command("stateCtrl_cmd", args)
107
+
108
+ async def upload_file(self, file_path: str, start_print: bool, level_before_print: bool) -> bool:
109
+ """
110
+ Uploads a G-code or 3MF file to the printer and optionally starts printing.
111
+ It handles different API requirements based on the printer's firmware version.
112
+
113
+ Args:
114
+ file_path: The local path to the G-code or 3MF file to upload.
115
+ start_print: If True, the printer will start printing the file immediately after upload.
116
+ level_before_print: If True, the printer will perform bed leveling before starting the print.
117
+
118
+ Returns:
119
+ True if the file upload (and optional print start) is successful, False otherwise.
120
+ """
121
+ file_path_obj = Path(file_path)
122
+
123
+ if not file_path_obj.exists():
124
+ print(f"UploadFile error: File not found at {file_path}")
125
+ return False
126
+
127
+ file_size = file_path_obj.stat().st_size
128
+ file_name = file_path_obj.name
129
+
130
+ print(f"Starting upload for {file_name}, Size: {file_size}, Start: {start_print}, Level: {level_before_print}")
131
+
132
+ try:
133
+ # Prepare the custom HTTP headers with metadata
134
+ custom_headers = {
135
+ 'serialNumber': self.client.serial_number,
136
+ 'checkCode': self.client.check_code,
137
+ 'fileSize': str(file_size),
138
+ 'printNow': str(start_print).lower(),
139
+ 'levelingBeforePrint': str(level_before_print).lower(),
140
+ 'Expect': '100-continue'
141
+ }
142
+
143
+ # Add additional headers for new firmware
144
+ if self._is_new_firmware_version():
145
+ print("Using new firmware headers for upload.")
146
+ custom_headers['flowCalibration'] = 'false'
147
+ custom_headers['useMatlStation'] = 'false'
148
+ custom_headers['gcodeToolCnt'] = '0'
149
+ # Base64 encode "[]" which is "W10="
150
+ custom_headers['materialMappings'] = 'W10='
151
+ else:
152
+ print("Using old firmware headers for upload.")
153
+
154
+ print("Upload Request Headers:", custom_headers)
155
+
156
+ # Create multipart form data
157
+ async with aiohttp.ClientSession() as session:
158
+ with open(file_path, 'rb') as f:
159
+ data = aiohttp.FormData()
160
+ data.add_field('gcodeFile', f, filename=file_name, content_type='application/octet-stream')
161
+
162
+ async with session.post(
163
+ self.client.get_endpoint(Endpoints.UPLOAD_FILE),
164
+ data=data,
165
+ headers=custom_headers
166
+ ) as response:
167
+ print(f"Upload Response Status: {response.status}")
168
+
169
+ if response.status != 200:
170
+ print(f"Upload failed: Printer responded with status {response.status}")
171
+ return False
172
+
173
+ # Fix for FlashForge printer's malformed Content-Type header
174
+ # Some printers return "appliation/json" instead of "application/json"
175
+ try:
176
+ result = await response.json()
177
+ except aiohttp.ContentTypeError:
178
+ # Fallback: manually parse as JSON if Content-Type is malformed
179
+ text = await response.text()
180
+ import json
181
+ result = json.loads(text)
182
+
183
+ print("Upload Response Data:", result)
184
+
185
+ if NetworkUtils.is_ok(result):
186
+ print("Upload successful according to printer response.")
187
+ return True
188
+ else:
189
+ print(f"Upload failed: Printer response code={result.get('code')}, message={result.get('message')}")
190
+ return False
191
+
192
+ except Exception as e:
193
+ print(f"UploadFile error: {e}")
194
+ return False
195
+
196
+ async def print_local_file(self, file_name: str, leveling_before_print: bool) -> bool:
197
+ """
198
+ Starts printing a file that is already stored locally on the printer.
199
+ It handles different API payload formats based on the printer's firmware version.
200
+
201
+ Args:
202
+ file_name: The name of the file on the printer (e.g., "my_model.gcode") to print.
203
+ leveling_before_print: If True, the printer will perform bed leveling before starting the print.
204
+
205
+ Returns:
206
+ True if the print command is successfully sent and acknowledged, False otherwise.
207
+ """
208
+ if self._is_new_firmware_version():
209
+ # New format for firmware >= 3.1.3
210
+ payload = {
211
+ "serialNumber": self.client.serial_number,
212
+ "checkCode": self.client.check_code,
213
+ "fileName": file_name,
214
+ "levelingBeforePrint": leveling_before_print,
215
+ "flowCalibration": False,
216
+ "useMatlStation": False,
217
+ "gcodeToolCnt": 0,
218
+ "materialMappings": [] # Empty array for materialMappings
219
+ }
220
+ else:
221
+ # Old format for firmware < 3.1.3
222
+ payload = {
223
+ "serialNumber": self.client.serial_number,
224
+ "checkCode": self.client.check_code,
225
+ "fileName": file_name,
226
+ "levelingBeforePrint": leveling_before_print
227
+ }
228
+
229
+ try:
230
+ async with aiohttp.ClientSession() as session:
231
+ async with session.post(
232
+ self.client.get_endpoint(Endpoints.GCODE_PRINT),
233
+ json=payload,
234
+ headers={"Content-Type": "application/json"}
235
+ ) as response:
236
+ if response.status != 200:
237
+ return False
238
+
239
+ # Fix for FlashForge printer's malformed Content-Type header
240
+ # Some printers return "appliation/json" instead of "application/json"
241
+ try:
242
+ result = await response.json()
243
+ except aiohttp.ContentTypeError:
244
+ # Fallback: manually parse as JSON if Content-Type is malformed
245
+ text = await response.text()
246
+ import json
247
+ result = json.loads(text)
248
+
249
+ return NetworkUtils.is_ok(result)
250
+
251
+ except Exception as error:
252
+ print(f"PrintLocalFile error: {error}")
253
+ raise error
254
+
255
+ async def upload_file_ad5x(self, params: AD5XUploadParams) -> bool:
256
+ """
257
+ Uploads a G-code or 3MF file to AD5X printer with material station support.
258
+ Handles material mappings, flow calibration, and other AD5X-specific features.
259
+ Material mappings are base64-encoded in HTTP headers according to AD5X API requirements.
260
+
261
+ Args:
262
+ params: AD5X upload parameters including file path, print options, and material mappings
263
+
264
+ Returns:
265
+ True if the file upload is successful, False otherwise
266
+ """
267
+ # Validate that this is an AD5X printer
268
+ if not self._validate_ad5x_printer():
269
+ return False
270
+
271
+ # Validate material mappings
272
+ if not self._validate_material_mappings(params.material_mappings):
273
+ return False
274
+
275
+ # Validate file exists
276
+ file_path_obj = Path(params.file_path)
277
+ if not file_path_obj.exists():
278
+ print(f"UploadFileAD5X error: File not found at {params.file_path}")
279
+ return False
280
+
281
+ file_size = file_path_obj.stat().st_size
282
+ file_name = file_path_obj.name
283
+
284
+ print(f"Starting AD5X upload for {file_name}, Size: {file_size}, Start: {params.start_print}, Level: {params.leveling_before_print}, Tools: {len(params.material_mappings)}")
285
+
286
+ try:
287
+ # Encode material mappings to base64
288
+ material_mappings_base64 = self._encode_material_mappings_to_base64(params.material_mappings)
289
+
290
+ # Prepare AD5X-specific HTTP headers
291
+ custom_headers = {
292
+ 'serialNumber': self.client.serial_number,
293
+ 'checkCode': self.client.check_code,
294
+ 'fileSize': str(file_size),
295
+ 'printNow': str(params.start_print).lower(),
296
+ 'levelingBeforePrint': str(params.leveling_before_print).lower(),
297
+ 'flowCalibration': str(params.flow_calibration).lower(),
298
+ 'firstLayerInspection': str(params.first_layer_inspection).lower(),
299
+ 'timeLapseVideo': str(params.time_lapse_video).lower(),
300
+ 'useMatlStation': 'true', # Always true for AD5X uploads with material mappings
301
+ 'gcodeToolCnt': str(len(params.material_mappings)),
302
+ 'materialMappings': material_mappings_base64,
303
+ 'Expect': '100-continue'
304
+ }
305
+
306
+ print("AD5X Upload Request Headers:", custom_headers)
307
+
308
+ # Create multipart form data
309
+ async with aiohttp.ClientSession() as session:
310
+ with open(params.file_path, 'rb') as f:
311
+ data = aiohttp.FormData()
312
+ data.add_field('gcodeFile', f, filename=file_name, content_type='application/octet-stream')
313
+
314
+ async with session.post(
315
+ self.client.get_endpoint(Endpoints.UPLOAD_FILE),
316
+ data=data,
317
+ headers=custom_headers
318
+ ) as response:
319
+ print(f"AD5X Upload Response Status: {response.status}")
320
+
321
+ if response.status != 200:
322
+ print(f"AD5X Upload failed: Printer responded with status {response.status}")
323
+ return False
324
+
325
+ # Fix for FlashForge printer's malformed Content-Type header
326
+ try:
327
+ result = await response.json()
328
+ except aiohttp.ContentTypeError:
329
+ text = await response.text()
330
+ result = json.loads(text)
331
+
332
+ print("AD5X Upload Response Data:", result)
333
+
334
+ if NetworkUtils.is_ok(result):
335
+ print("AD5X Upload successful according to printer response.")
336
+ return True
337
+ else:
338
+ print(f"AD5X Upload failed: Printer response code={result.get('code')}, message={result.get('message')}")
339
+ return False
340
+
341
+ except Exception as e:
342
+ print(f"UploadFileAD5X error: {e}")
343
+ return False
344
+
345
+ async def start_ad5x_multi_color_job(self, params: AD5XLocalJobParams) -> bool:
346
+ """
347
+ Starts a multi-color local print job on AD5X printers with material mappings.
348
+ This method automatically configures the material station settings and validates
349
+ all parameters before sending the print command.
350
+
351
+ Args:
352
+ params: Job parameters including file name, leveling option, and material mappings
353
+
354
+ Returns:
355
+ True if successful, False if validation fails or printer rejects
356
+ """
357
+ # Validate that this is an AD5X printer
358
+ if not self._validate_ad5x_printer():
359
+ return False
360
+
361
+ # Validate material mappings
362
+ if not self._validate_material_mappings(params.material_mappings):
363
+ return False
364
+
365
+ # Validate file name
366
+ if not params.file_name or params.file_name.strip() == '':
367
+ print('AD5X Multi-Color Job error: fileName cannot be empty')
368
+ return False
369
+
370
+ # Create payload with AD5X-specific parameters
371
+ payload = {
372
+ "serialNumber": self.client.serial_number,
373
+ "checkCode": self.client.check_code,
374
+ "fileName": params.file_name,
375
+ "levelingBeforePrint": params.leveling_before_print,
376
+ "firstLayerInspection": False,
377
+ "flowCalibration": False,
378
+ "timeLapseVideo": False,
379
+ "useMatlStation": True, # Automatically set to true for multi-color jobs
380
+ "gcodeToolCnt": len(params.material_mappings), # Set based on material mappings count
381
+ "materialMappings": [
382
+ {
383
+ "toolId": m.tool_id,
384
+ "slotId": m.slot_id,
385
+ "materialName": m.material_name,
386
+ "toolMaterialColor": m.tool_material_color,
387
+ "slotMaterialColor": m.slot_material_color
388
+ }
389
+ for m in params.material_mappings
390
+ ]
391
+ }
392
+
393
+ try:
394
+ async with aiohttp.ClientSession() as session:
395
+ async with session.post(
396
+ self.client.get_endpoint(Endpoints.GCODE_PRINT),
397
+ json=payload,
398
+ headers={"Content-Type": "application/json"}
399
+ ) as response:
400
+ if response.status != 200:
401
+ return False
402
+
403
+ # Fix for FlashForge printer's malformed Content-Type header
404
+ try:
405
+ result = await response.json()
406
+ except aiohttp.ContentTypeError:
407
+ text = await response.text()
408
+ result = json.loads(text)
409
+
410
+ return NetworkUtils.is_ok(result)
411
+
412
+ except Exception as error:
413
+ print(f"AD5X Multi-Color Job error: {error}")
414
+ raise error
415
+
416
+ async def start_ad5x_single_color_job(self, params: AD5XSingleColorJobParams) -> bool:
417
+ """
418
+ Starts a single-color local print job on AD5X printers.
419
+ This method automatically configures the printer for single-color printing
420
+ without using the material station.
421
+
422
+ Args:
423
+ params: Job parameters including file name and leveling option
424
+
425
+ Returns:
426
+ True if successful, False if validation fails or printer rejects
427
+ """
428
+ # Validate that this is an AD5X printer
429
+ if not self._validate_ad5x_printer():
430
+ return False
431
+
432
+ # Validate file name
433
+ if not params.file_name or params.file_name.strip() == '':
434
+ print('AD5X Single-Color Job error: fileName cannot be empty')
435
+ return False
436
+
437
+ # Create payload with AD5X-specific parameters for single-color printing
438
+ payload = {
439
+ "serialNumber": self.client.serial_number,
440
+ "checkCode": self.client.check_code,
441
+ "fileName": params.file_name,
442
+ "levelingBeforePrint": params.leveling_before_print,
443
+ "firstLayerInspection": False,
444
+ "flowCalibration": False,
445
+ "timeLapseVideo": False,
446
+ "useMatlStation": False, # Set to false for single-color jobs
447
+ "gcodeToolCnt": 0, # Set to 0 for single-color jobs
448
+ "materialMappings": [] # Empty array for single-color jobs
449
+ }
450
+
451
+ try:
452
+ async with aiohttp.ClientSession() as session:
453
+ async with session.post(
454
+ self.client.get_endpoint(Endpoints.GCODE_PRINT),
455
+ json=payload,
456
+ headers={"Content-Type": "application/json"}
457
+ ) as response:
458
+ if response.status != 200:
459
+ return False
460
+
461
+ # Fix for FlashForge printer's malformed Content-Type header
462
+ try:
463
+ result = await response.json()
464
+ except aiohttp.ContentTypeError:
465
+ text = await response.text()
466
+ result = json.loads(text)
467
+
468
+ return NetworkUtils.is_ok(result)
469
+
470
+ except Exception as error:
471
+ print(f"AD5X Single-Color Job error: {error}")
472
+ raise error
473
+
474
+ def _validate_ad5x_printer(self) -> bool:
475
+ """
476
+ Validates that the current printer is an AD5X model.
477
+
478
+ Returns:
479
+ True if the printer is AD5X, false otherwise
480
+ """
481
+ if not self.client.is_ad5x:
482
+ print('AD5X Job error: This method can only be used with AD5X printers')
483
+ return False
484
+ return True
485
+
486
+ def _encode_material_mappings_to_base64(self, material_mappings: list[AD5XMaterialMapping]) -> str:
487
+ """
488
+ Encodes material mappings array to base64 string for HTTP headers.
489
+ Converts AD5XMaterialMapping array to JSON and then to base64 encoding.
490
+
491
+ Args:
492
+ material_mappings: Array of material mappings to encode
493
+
494
+ Returns:
495
+ Base64-encoded JSON string
496
+ """
497
+ try:
498
+ json_array = [
499
+ {
500
+ "toolId": m.tool_id,
501
+ "slotId": m.slot_id,
502
+ "materialName": m.material_name,
503
+ "toolMaterialColor": m.tool_material_color,
504
+ "slotMaterialColor": m.slot_material_color
505
+ }
506
+ for m in material_mappings
507
+ ]
508
+ json_string = json.dumps(json_array)
509
+ return base64.b64encode(json_string.encode('utf-8')).decode('utf-8')
510
+ except Exception as error:
511
+ print('Failed to encode material mappings to base64:', error)
512
+ raise Exception('Failed to encode material mappings for upload')
513
+
514
+ def _validate_material_mappings(self, material_mappings: list[AD5XMaterialMapping]) -> bool:
515
+ """
516
+ Validates material mappings for AD5X multi-color jobs.
517
+ Checks toolId range (0-3), slotId range (1-4), and color format (#RRGGBB).
518
+
519
+ Args:
520
+ material_mappings: Array of material mappings to validate
521
+
522
+ Returns:
523
+ True if all mappings are valid, false otherwise
524
+ """
525
+ if not material_mappings or len(material_mappings) == 0:
526
+ print('Material mappings validation error: materialMappings array cannot be empty for multi-color jobs')
527
+ return False
528
+
529
+ if len(material_mappings) > 4:
530
+ print('Material mappings validation error: Maximum 4 material mappings allowed')
531
+ return False
532
+
533
+ hex_color_regex = re.compile(r'^#[0-9A-Fa-f]{6}$')
534
+
535
+ for i, mapping in enumerate(material_mappings):
536
+ # Validate toolId (0-3)
537
+ if mapping.tool_id < 0 or mapping.tool_id > 3:
538
+ print(f'Material mappings validation error: toolId must be between 0-3, got {mapping.tool_id} at index {i}')
539
+ return False
540
+
541
+ # Validate slotId (1-4)
542
+ if mapping.slot_id < 1 or mapping.slot_id > 4:
543
+ print(f'Material mappings validation error: slotId must be between 1-4, got {mapping.slot_id} at index {i}')
544
+ return False
545
+
546
+ # Validate materialName is not empty
547
+ if not mapping.material_name or mapping.material_name.strip() == '':
548
+ print(f'Material mappings validation error: materialName cannot be empty at index {i}')
549
+ return False
550
+
551
+ # Validate toolMaterialColor format
552
+ if not hex_color_regex.match(mapping.tool_material_color):
553
+ print(f'Material mappings validation error: toolMaterialColor must be in #RRGGBB format, got {mapping.tool_material_color} at index {i}')
554
+ return False
555
+
556
+ # Validate slotMaterialColor format
557
+ if not hex_color_regex.match(mapping.slot_material_color):
558
+ print(f'Material mappings validation error: slotMaterialColor must be in #RRGGBB format, got {mapping.slot_material_color} at index {i}')
559
+ return False
560
+
561
+ return True
@@ -0,0 +1,89 @@
1
+ """
2
+ FlashForge Python API - Temperature Control Module
3
+ """
4
+ from typing import TYPE_CHECKING, Optional
5
+
6
+ if TYPE_CHECKING:
7
+ from ...client import FlashForgeClient
8
+ from ...tcp.ff_client import FlashForgeClient as TcpClient
9
+
10
+
11
+ class TempControl:
12
+ """
13
+ Provides methods for controlling the temperatures of various printer components,
14
+ including extruders and the print bed.
15
+ """
16
+
17
+ def __init__(self, client: "FlashForgeClient"):
18
+ """
19
+ Creates an instance of the TempControl class.
20
+
21
+ Args:
22
+ client: The FlashForgeClient instance used for communication with the printer.
23
+ """
24
+ self.client = client
25
+ self._tcp_client: Optional[TcpClient] = None
26
+
27
+ @property
28
+ def tcp_client(self) -> "TcpClient":
29
+ """Get the TCP client instance."""
30
+ if self._tcp_client is None:
31
+ self._tcp_client = self.client.tcp_client
32
+ return self._tcp_client
33
+
34
+ async def set_extruder_temp(self, temperature: int, wait_for: bool = False) -> bool:
35
+ """
36
+ Sets the target temperature for an extruder.
37
+
38
+ Args:
39
+ temperature: The target temperature in Celsius.
40
+ wait_for: Whether to wait for the heating operation to complete
41
+
42
+ Returns:
43
+ True if the command is successful, False otherwise.
44
+ """
45
+ return await self.tcp_client.set_extruder_temp(temperature, wait_for)
46
+
47
+ async def set_bed_temp(self, temperature: int, wait_for: bool = False) -> bool:
48
+ """
49
+ Sets the target temperature for the print bed.
50
+
51
+ Args:
52
+ temperature: The target bed temperature in Celsius.
53
+ wait_for: Whether to wait for the heating operation to complete
54
+
55
+ Returns:
56
+ True if the command is successful, False otherwise.
57
+ """
58
+ return await self.tcp_client.set_bed_temp(temperature, wait_for)
59
+
60
+ async def cancel_extruder_temp(self) -> bool:
61
+ """
62
+ Cancels the heating of an extruder (sets target temperature to 0).
63
+
64
+ Returns:
65
+ True if the command is successful, False otherwise.
66
+ """
67
+ return await self.tcp_client.cancel_extruder_temp()
68
+
69
+ async def cancel_bed_temp(self) -> bool:
70
+ """
71
+ Cancels the heating of the print bed (sets target temperature to 0).
72
+
73
+ Returns:
74
+ True if the command is successful, False otherwise.
75
+ """
76
+ return await self.tcp_client.cancel_bed_temp()
77
+
78
+ async def wait_for_part_cool(self, target_temp: float = 50.0, timeout_seconds: int = 1800) -> bool:
79
+ """
80
+ Waits for printer components to cool down to a safe temperature.
81
+
82
+ Args:
83
+ target_temp: The target temperature to wait for (default: 50°C).
84
+ timeout_seconds: Maximum time to wait in seconds (default: 30 minutes).
85
+
86
+ Returns:
87
+ True if components cooled to target temperature, False if timeout or error.
88
+ """
89
+ return await self.tcp_client.wait_for_part_cool(target_temp, timeout_seconds)
@@ -0,0 +1,7 @@
1
+ """
2
+ FlashForge Python API - Filament Module
3
+ """
4
+
5
+ from .filament import Filament
6
+
7
+ __all__ = ["Filament"]