flashforge-python-api 1.0.2__py3-none-any.whl → 1.2.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.
- flashforge/__init__.py +70 -29
- flashforge/api/__init__.py +1 -0
- flashforge/api/constants/__init__.py +1 -0
- flashforge/api/constants/commands.py +1 -0
- flashforge/api/constants/endpoints.py +7 -0
- flashforge/api/controls/__init__.py +1 -0
- flashforge/api/controls/control.py +64 -61
- flashforge/api/controls/files.py +28 -26
- flashforge/api/controls/info.py +84 -85
- flashforge/api/controls/job_control.py +120 -82
- flashforge/api/controls/temp_control.py +14 -11
- flashforge/api/misc/__init__.py +1 -1
- flashforge/api/network/__init__.py +2 -1
- flashforge/api/network/utils.py +7 -8
- flashforge/client.py +185 -64
- flashforge/discovery/__init__.py +30 -3
- flashforge/discovery/discovery.py +641 -308
- flashforge/models/__init__.py +11 -10
- flashforge/models/machine_info.py +225 -100
- flashforge/models/responses.py +103 -43
- flashforge/tcp/__init__.py +30 -17
- flashforge/tcp/a3_client.py +408 -0
- flashforge/tcp/a4_client.py +300 -0
- flashforge/tcp/ff_client.py +93 -90
- flashforge/tcp/gcode/__init__.py +4 -2
- flashforge/tcp/gcode/a3_gcode_controller.py +109 -0
- flashforge/tcp/gcode/gcode_controller.py +48 -36
- flashforge/tcp/gcode/gcodes.py +7 -1
- flashforge/tcp/parsers/__init__.py +11 -11
- flashforge/tcp/parsers/endstop_status.py +103 -132
- flashforge/tcp/parsers/location_info.py +26 -13
- flashforge/tcp/parsers/print_status.py +49 -56
- flashforge/tcp/parsers/printer_info.py +38 -48
- flashforge/tcp/parsers/temp_info.py +46 -47
- flashforge/tcp/parsers/thumbnail_info.py +65 -40
- flashforge/tcp/tcp_client.py +296 -293
- flashforge_python_api-1.2.0.dist-info/METADATA +133 -0
- flashforge_python_api-1.2.0.dist-info/RECORD +46 -0
- {flashforge_python_api-1.0.2.dist-info → flashforge_python_api-1.2.0.dist-info}/WHEEL +1 -1
- flashforge_python_api-1.0.2.dist-info/METADATA +0 -284
- flashforge_python_api-1.0.2.dist-info/RECORD +0 -43
- {flashforge_python_api-1.0.2.dist-info → flashforge_python_api-1.2.0.dist-info}/entry_points.txt +0 -0
- {flashforge_python_api-1.0.2.dist-info → flashforge_python_api-1.2.0.dist-info}/licenses/LICENSE +0 -0
flashforge/tcp/ff_client.py
CHANGED
|
@@ -7,11 +7,10 @@ building upon the basic TCP communication provided by FlashForgeTcpClient.
|
|
|
7
7
|
|
|
8
8
|
import asyncio
|
|
9
9
|
import logging
|
|
10
|
-
from typing import Optional
|
|
11
10
|
|
|
12
11
|
from .gcode import GCodeController, GCodes
|
|
13
12
|
from .parsers import EndstopStatus, LocationInfo, PrinterInfo, PrintStatus, TempInfo, ThumbnailInfo
|
|
14
|
-
from .tcp_client import FlashForgeTcpClient
|
|
13
|
+
from .tcp_client import FlashForgeTcpClient, FlashForgeTcpClientOptions
|
|
15
14
|
|
|
16
15
|
logger = logging.getLogger(__name__)
|
|
17
16
|
|
|
@@ -19,23 +18,23 @@ logger = logging.getLogger(__name__)
|
|
|
19
18
|
class FlashForgeClient(FlashForgeTcpClient):
|
|
20
19
|
"""
|
|
21
20
|
High-level client for interacting with FlashForge 3D printers via TCP/IP.
|
|
22
|
-
|
|
21
|
+
|
|
23
22
|
This class implements specific G-code commands and workflows for printer control,
|
|
24
23
|
such as initialization, LED control, job management, homing, temperature settings,
|
|
25
24
|
filament operations, and retrieving various printer statuses.
|
|
26
|
-
|
|
25
|
+
|
|
27
26
|
It uses a "legacy" API approach primarily based on sending G-code/M-code commands
|
|
28
27
|
and parsing text-based responses.
|
|
29
28
|
"""
|
|
30
29
|
|
|
31
|
-
def __init__(self, hostname: str) -> None:
|
|
30
|
+
def __init__(self, hostname: str, options: FlashForgeTcpClientOptions | None = None) -> None:
|
|
32
31
|
"""
|
|
33
32
|
Create an instance of FlashForgeClient.
|
|
34
|
-
|
|
33
|
+
|
|
35
34
|
Args:
|
|
36
35
|
hostname: The IP address or hostname of the FlashForge printer
|
|
37
36
|
"""
|
|
38
|
-
super().__init__(hostname)
|
|
37
|
+
super().__init__(hostname, options)
|
|
39
38
|
self._control = GCodeController(self)
|
|
40
39
|
self._is_5m_pro = False
|
|
41
40
|
"""Flag indicating if the connected printer is a 5M Pro model, which may have specific features."""
|
|
@@ -43,7 +42,7 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
43
42
|
def get_ip(self) -> str:
|
|
44
43
|
"""
|
|
45
44
|
Get the IP address or hostname of the connected printer.
|
|
46
|
-
|
|
45
|
+
|
|
47
46
|
Returns:
|
|
48
47
|
The printer's hostname or IP address
|
|
49
48
|
"""
|
|
@@ -52,9 +51,9 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
52
51
|
def gcode(self) -> GCodeController:
|
|
53
52
|
"""
|
|
54
53
|
Get the GCodeController instance associated with this client.
|
|
55
|
-
|
|
54
|
+
|
|
56
55
|
Provides access to specific G-code command methods.
|
|
57
|
-
|
|
56
|
+
|
|
58
57
|
Returns:
|
|
59
58
|
The GCodeController instance
|
|
60
59
|
"""
|
|
@@ -63,10 +62,10 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
63
62
|
async def init_control(self) -> bool:
|
|
64
63
|
"""
|
|
65
64
|
Initialize the control connection with the printer.
|
|
66
|
-
|
|
65
|
+
|
|
67
66
|
This typically involves sending a login command, retrieving printer info,
|
|
68
67
|
and starting a keep-alive mechanism. Retries on failure.
|
|
69
|
-
|
|
68
|
+
|
|
70
69
|
Returns:
|
|
71
70
|
True if control is successfully initialized, False otherwise
|
|
72
71
|
"""
|
|
@@ -100,7 +99,7 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
100
99
|
async def led_on(self) -> bool:
|
|
101
100
|
"""
|
|
102
101
|
Turn the printer's LED lights on.
|
|
103
|
-
|
|
102
|
+
|
|
104
103
|
Returns:
|
|
105
104
|
True if the command is successful, False otherwise
|
|
106
105
|
"""
|
|
@@ -109,7 +108,7 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
109
108
|
async def led_off(self) -> bool:
|
|
110
109
|
"""
|
|
111
110
|
Turn the printer's LED lights off.
|
|
112
|
-
|
|
111
|
+
|
|
113
112
|
Returns:
|
|
114
113
|
True if the command is successful, False otherwise
|
|
115
114
|
"""
|
|
@@ -118,7 +117,7 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
118
117
|
async def pause_job(self) -> bool:
|
|
119
118
|
"""
|
|
120
119
|
Pause the current print job.
|
|
121
|
-
|
|
120
|
+
|
|
122
121
|
Returns:
|
|
123
122
|
True if the command is successful, False otherwise
|
|
124
123
|
"""
|
|
@@ -127,7 +126,7 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
127
126
|
async def resume_job(self) -> bool:
|
|
128
127
|
"""
|
|
129
128
|
Resume a paused print job.
|
|
130
|
-
|
|
129
|
+
|
|
131
130
|
Returns:
|
|
132
131
|
True if the command is successful, False otherwise
|
|
133
132
|
"""
|
|
@@ -136,7 +135,7 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
136
135
|
async def stop_job(self) -> bool:
|
|
137
136
|
"""
|
|
138
137
|
Stop the current print job.
|
|
139
|
-
|
|
138
|
+
|
|
140
139
|
Returns:
|
|
141
140
|
True if the command is successful, False otherwise
|
|
142
141
|
"""
|
|
@@ -145,10 +144,10 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
145
144
|
async def start_job(self, name: str) -> bool:
|
|
146
145
|
"""
|
|
147
146
|
Start a print job from a file stored on the printer.
|
|
148
|
-
|
|
147
|
+
|
|
149
148
|
Args:
|
|
150
149
|
name: The name of the file to print (typically without path)
|
|
151
|
-
|
|
150
|
+
|
|
152
151
|
Returns:
|
|
153
152
|
True if the command is successful, False otherwise
|
|
154
153
|
"""
|
|
@@ -157,7 +156,7 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
157
156
|
async def home_axes(self) -> bool:
|
|
158
157
|
"""
|
|
159
158
|
Home all axes (X, Y, Z) of the printer.
|
|
160
|
-
|
|
159
|
+
|
|
161
160
|
Returns:
|
|
162
161
|
True if the command is successful, False otherwise
|
|
163
162
|
"""
|
|
@@ -166,7 +165,7 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
166
165
|
async def rapid_home(self) -> bool:
|
|
167
166
|
"""
|
|
168
167
|
Perform a rapid homing of all axes.
|
|
169
|
-
|
|
168
|
+
|
|
170
169
|
Returns:
|
|
171
170
|
True if the command is successful, False otherwise
|
|
172
171
|
"""
|
|
@@ -175,9 +174,9 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
175
174
|
async def turn_runout_sensor_on(self) -> bool:
|
|
176
175
|
"""
|
|
177
176
|
Turn on the filament runout sensor.
|
|
178
|
-
|
|
177
|
+
|
|
179
178
|
This functionality is only available on specific printer models (e.g., 5M Pro).
|
|
180
|
-
|
|
179
|
+
|
|
181
180
|
Returns:
|
|
182
181
|
True if the command is successful and applicable, False otherwise
|
|
183
182
|
"""
|
|
@@ -190,9 +189,9 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
190
189
|
async def turn_runout_sensor_off(self) -> bool:
|
|
191
190
|
"""
|
|
192
191
|
Turn off the filament runout sensor.
|
|
193
|
-
|
|
192
|
+
|
|
194
193
|
This functionality is only available on specific printer models (e.g., 5M Pro).
|
|
195
|
-
|
|
194
|
+
|
|
196
195
|
Returns:
|
|
197
196
|
True if the command is successful and applicable, False otherwise
|
|
198
197
|
"""
|
|
@@ -205,11 +204,11 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
205
204
|
async def set_extruder_temp(self, temp: int, wait_for: bool = False) -> bool:
|
|
206
205
|
"""
|
|
207
206
|
Set the target temperature for the extruder.
|
|
208
|
-
|
|
207
|
+
|
|
209
208
|
Args:
|
|
210
209
|
temp: The target temperature in Celsius
|
|
211
210
|
wait_for: If True, the method will wait until the target temperature is reached
|
|
212
|
-
|
|
211
|
+
|
|
213
212
|
Returns:
|
|
214
213
|
True if the command is successful, False otherwise
|
|
215
214
|
"""
|
|
@@ -218,7 +217,7 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
218
217
|
async def cancel_extruder_temp(self) -> bool:
|
|
219
218
|
"""
|
|
220
219
|
Cancel extruder heating and set its target temperature to 0.
|
|
221
|
-
|
|
220
|
+
|
|
222
221
|
Returns:
|
|
223
222
|
True if the command is successful, False otherwise
|
|
224
223
|
"""
|
|
@@ -227,11 +226,11 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
227
226
|
async def set_bed_temp(self, temp: int, wait_for: bool = False) -> bool:
|
|
228
227
|
"""
|
|
229
228
|
Set the target temperature for the print bed.
|
|
230
|
-
|
|
229
|
+
|
|
231
230
|
Args:
|
|
232
231
|
temp: The target temperature in Celsius
|
|
233
232
|
wait_for: If True, the method will wait until the target temperature is reached
|
|
234
|
-
|
|
233
|
+
|
|
235
234
|
Returns:
|
|
236
235
|
True if the command is successful, False otherwise
|
|
237
236
|
"""
|
|
@@ -240,10 +239,10 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
240
239
|
async def cancel_bed_temp(self, wait_for_cool: bool = False) -> bool:
|
|
241
240
|
"""
|
|
242
241
|
Cancel print bed heating and set its target temperature to 0.
|
|
243
|
-
|
|
242
|
+
|
|
244
243
|
Args:
|
|
245
244
|
wait_for_cool: If True, waits for the bed to cool down after canceling
|
|
246
|
-
|
|
245
|
+
|
|
247
246
|
Returns:
|
|
248
247
|
True if the command is successful, False otherwise
|
|
249
248
|
"""
|
|
@@ -252,13 +251,13 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
252
251
|
async def extrude(self, length: float, feedrate: int = 450) -> bool:
|
|
253
252
|
"""
|
|
254
253
|
Command the extruder to extrude a specific length of filament.
|
|
255
|
-
|
|
254
|
+
|
|
256
255
|
Uses G1 E[length] F[feedrate] command.
|
|
257
|
-
|
|
256
|
+
|
|
258
257
|
Args:
|
|
259
258
|
length: The length of filament to extrude in millimeters
|
|
260
259
|
feedrate: The feedrate for extrusion in mm/min (default: 450)
|
|
261
|
-
|
|
260
|
+
|
|
262
261
|
Returns:
|
|
263
262
|
True if the command is successful, False otherwise
|
|
264
263
|
"""
|
|
@@ -267,14 +266,14 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
267
266
|
async def move_extruder(self, x: float, y: float, feedrate: int) -> bool:
|
|
268
267
|
"""
|
|
269
268
|
Move the extruder to a specified X, Y position.
|
|
270
|
-
|
|
269
|
+
|
|
271
270
|
Uses G1 X[x] Y[y] F[feedrate] command.
|
|
272
|
-
|
|
271
|
+
|
|
273
272
|
Args:
|
|
274
273
|
x: The target X coordinate
|
|
275
274
|
y: The target Y coordinate
|
|
276
275
|
feedrate: The feedrate for the movement in mm/min
|
|
277
|
-
|
|
276
|
+
|
|
278
277
|
Returns:
|
|
279
278
|
True if the command is successful, False otherwise
|
|
280
279
|
"""
|
|
@@ -283,15 +282,15 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
283
282
|
async def move(self, x: float, y: float, z: float, feedrate: int) -> bool:
|
|
284
283
|
"""
|
|
285
284
|
Move the extruder to a specified X, Y, Z position.
|
|
286
|
-
|
|
285
|
+
|
|
287
286
|
Uses G1 X[x] Y[y] Z[z] F[feedrate] command.
|
|
288
|
-
|
|
287
|
+
|
|
289
288
|
Args:
|
|
290
289
|
x: The target X coordinate
|
|
291
290
|
y: The target Y coordinate
|
|
292
291
|
z: The target Z coordinate
|
|
293
292
|
feedrate: The feedrate for the movement in mm/min
|
|
294
|
-
|
|
293
|
+
|
|
295
294
|
Returns:
|
|
296
295
|
True if the command is successful, False otherwise
|
|
297
296
|
"""
|
|
@@ -300,12 +299,12 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
300
299
|
async def send_cmd_ok(self, cmd: str) -> bool:
|
|
301
300
|
"""
|
|
302
301
|
Send a G-code/M-code command to the printer and check for an "ok" response.
|
|
303
|
-
|
|
302
|
+
|
|
304
303
|
Expects the printer's reply to include "Received." and "ok" to be considered successful.
|
|
305
|
-
|
|
304
|
+
|
|
306
305
|
Args:
|
|
307
306
|
cmd: The command string to send (e.g., "~M115")
|
|
308
|
-
|
|
307
|
+
|
|
309
308
|
Returns:
|
|
310
309
|
True if the command is acknowledged with "ok", False otherwise or on error
|
|
311
310
|
"""
|
|
@@ -321,29 +320,29 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
321
320
|
async def send_raw_cmd(self, cmd: str) -> str:
|
|
322
321
|
"""
|
|
323
322
|
Send a raw command string to the printer and return the raw response.
|
|
324
|
-
|
|
323
|
+
|
|
325
324
|
Handles a special case for "M661" (list files), which is processed differently.
|
|
326
|
-
|
|
325
|
+
|
|
327
326
|
Args:
|
|
328
327
|
cmd: The raw command string to send
|
|
329
|
-
|
|
328
|
+
|
|
330
329
|
Returns:
|
|
331
330
|
The printer's raw string response, or an empty string on failure.
|
|
332
331
|
For "M661", it returns a newline-separated list of files.
|
|
333
332
|
"""
|
|
334
333
|
if "M661" not in cmd:
|
|
335
334
|
result = await self.send_command_async(cmd)
|
|
336
|
-
return result or
|
|
335
|
+
return result or ""
|
|
337
336
|
|
|
338
337
|
file_list = await self.get_file_list_async()
|
|
339
338
|
return "\n".join(file_list)
|
|
340
339
|
|
|
341
|
-
async def get_printer_info(self) ->
|
|
340
|
+
async def get_printer_info(self) -> PrinterInfo | None:
|
|
342
341
|
"""
|
|
343
342
|
Retrieve general printer information (model, firmware, etc.).
|
|
344
|
-
|
|
343
|
+
|
|
345
344
|
Sends CMD_INFO_STATUS and parses the response into a PrinterInfo object.
|
|
346
|
-
|
|
345
|
+
|
|
347
346
|
Returns:
|
|
348
347
|
A PrinterInfo object, or None if retrieval fails
|
|
349
348
|
"""
|
|
@@ -352,12 +351,12 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
352
351
|
return PrinterInfo().from_replay(response)
|
|
353
352
|
return None
|
|
354
353
|
|
|
355
|
-
async def get_temp_info(self) ->
|
|
354
|
+
async def get_temp_info(self) -> TempInfo | None:
|
|
356
355
|
"""
|
|
357
356
|
Retrieve current temperature information (extruder, bed).
|
|
358
|
-
|
|
357
|
+
|
|
359
358
|
Sends CMD_TEMP and parses the response into a TempInfo object.
|
|
360
|
-
|
|
359
|
+
|
|
361
360
|
Returns:
|
|
362
361
|
A TempInfo object, or None if retrieval fails
|
|
363
362
|
"""
|
|
@@ -366,12 +365,12 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
366
365
|
return TempInfo().from_replay(response)
|
|
367
366
|
return None
|
|
368
367
|
|
|
369
|
-
async def get_location_info(self) ->
|
|
368
|
+
async def get_location_info(self) -> LocationInfo | None:
|
|
370
369
|
"""
|
|
371
370
|
Retrieve the current XYZ coordinates of the print head.
|
|
372
|
-
|
|
371
|
+
|
|
373
372
|
Sends CMD_INFO_XYZAB and parses the response into a LocationInfo object.
|
|
374
|
-
|
|
373
|
+
|
|
375
374
|
Returns:
|
|
376
375
|
A LocationInfo object, or None if retrieval fails
|
|
377
376
|
"""
|
|
@@ -383,37 +382,39 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
383
382
|
async def _get_nozzle_temp(self) -> float:
|
|
384
383
|
"""
|
|
385
384
|
Retrieve the current temperature of the nozzle (extruder).
|
|
386
|
-
|
|
385
|
+
|
|
387
386
|
Returns:
|
|
388
387
|
The current nozzle temperature in Celsius, or 0 if unavailable
|
|
389
388
|
"""
|
|
390
389
|
temps = await self.get_temp_info()
|
|
391
|
-
if temps
|
|
392
|
-
|
|
390
|
+
if temps:
|
|
391
|
+
extruder_temp = temps.get_extruder_temp()
|
|
392
|
+
if extruder_temp:
|
|
393
|
+
return extruder_temp.get_current()
|
|
393
394
|
return 0.0
|
|
394
395
|
|
|
395
|
-
async def get_endstop_status(self) ->
|
|
396
|
+
async def get_endstop_status(self) -> EndstopStatus | None:
|
|
396
397
|
"""
|
|
397
398
|
Retrieve the current endstop status and machine state information.
|
|
398
|
-
|
|
399
|
+
|
|
399
400
|
Sends M119 command and parses the response into an EndstopStatus object.
|
|
400
401
|
This includes endstop states, machine status, move mode, LED status, and current file.
|
|
401
|
-
|
|
402
|
+
|
|
402
403
|
Returns:
|
|
403
404
|
An EndstopStatus object, or None if retrieval fails
|
|
404
405
|
"""
|
|
405
|
-
response = await self.send_command_async(GCodes.
|
|
406
|
+
response = await self.send_command_async(GCodes.CMD_ENDSTOP_INFO)
|
|
406
407
|
if response:
|
|
407
408
|
return EndstopStatus().from_replay(response)
|
|
408
409
|
return None
|
|
409
410
|
|
|
410
|
-
async def get_print_status(self) ->
|
|
411
|
+
async def get_print_status(self) -> PrintStatus | None:
|
|
411
412
|
"""
|
|
412
413
|
Retrieve the current print status and progress information.
|
|
413
|
-
|
|
414
|
+
|
|
414
415
|
Sends M27 command and parses the response into a PrintStatus object.
|
|
415
416
|
This includes SD card byte progress and layer progress.
|
|
416
|
-
|
|
417
|
+
|
|
417
418
|
Returns:
|
|
418
419
|
A PrintStatus object, or None if retrieval fails
|
|
419
420
|
"""
|
|
@@ -422,15 +423,15 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
422
423
|
return PrintStatus().from_replay(response)
|
|
423
424
|
return None
|
|
424
425
|
|
|
425
|
-
async def get_thumbnail(self, file_name: str) ->
|
|
426
|
+
async def get_thumbnail(self, file_name: str) -> ThumbnailInfo | None:
|
|
426
427
|
"""
|
|
427
428
|
Retrieve the thumbnail image for a specific file.
|
|
428
|
-
|
|
429
|
+
|
|
429
430
|
Sends M662 command and parses the response to extract PNG thumbnail data.
|
|
430
|
-
|
|
431
|
+
|
|
431
432
|
Args:
|
|
432
433
|
file_name: The name of the file to get the thumbnail for
|
|
433
|
-
|
|
434
|
+
|
|
434
435
|
Returns:
|
|
435
436
|
A ThumbnailInfo object containing the PNG image data, or None if retrieval fails
|
|
436
437
|
"""
|
|
@@ -444,7 +445,7 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
444
445
|
async def check_machine_state(self) -> str:
|
|
445
446
|
"""
|
|
446
447
|
Get a simplified machine state string for quick status checking.
|
|
447
|
-
|
|
448
|
+
|
|
448
449
|
Returns:
|
|
449
450
|
A string indicating the machine state ("printing", "ready", "paused", "complete", "unknown")
|
|
450
451
|
"""
|
|
@@ -463,10 +464,10 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
463
464
|
else:
|
|
464
465
|
return "unknown"
|
|
465
466
|
|
|
466
|
-
async def get_current_print_file(self) ->
|
|
467
|
+
async def get_current_print_file(self) -> str | None:
|
|
467
468
|
"""
|
|
468
469
|
Get the name of the currently loaded/printing file.
|
|
469
|
-
|
|
470
|
+
|
|
470
471
|
Returns:
|
|
471
472
|
The filename of the current print job, or None if no file is loaded
|
|
472
473
|
"""
|
|
@@ -478,7 +479,7 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
478
479
|
async def is_printer_ready(self) -> bool:
|
|
479
480
|
"""
|
|
480
481
|
Check if the printer is ready for new commands.
|
|
481
|
-
|
|
482
|
+
|
|
482
483
|
Returns:
|
|
483
484
|
True if the printer is in a ready state, False otherwise
|
|
484
485
|
"""
|
|
@@ -490,7 +491,7 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
490
491
|
async def get_print_progress(self) -> tuple[int, int, int]:
|
|
491
492
|
"""
|
|
492
493
|
Get comprehensive print progress information.
|
|
493
|
-
|
|
494
|
+
|
|
494
495
|
Returns:
|
|
495
496
|
A tuple of (layer_percent, sd_percent, current_layer) where:
|
|
496
497
|
- layer_percent: Progress based on layers (0-100)
|
|
@@ -518,35 +519,37 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
518
519
|
|
|
519
520
|
return (int(layer_percent), int(sd_percent), current_layer)
|
|
520
521
|
|
|
521
|
-
async def wait_for_part_cool(
|
|
522
|
+
async def wait_for_part_cool(
|
|
523
|
+
self, target_temp: float = 50.0, timeout_seconds: int = 1800
|
|
524
|
+
) -> bool:
|
|
522
525
|
"""
|
|
523
526
|
Wait for printer components (extruder and bed) to cool down to a safe temperature.
|
|
524
|
-
|
|
527
|
+
|
|
525
528
|
This method cancels heating for both extruder and bed, then waits for them to cool down.
|
|
526
|
-
|
|
529
|
+
|
|
527
530
|
Args:
|
|
528
531
|
target_temp: The target temperature to wait for (default: 50°C)
|
|
529
532
|
timeout_seconds: Maximum time to wait in seconds (default: 30 minutes)
|
|
530
|
-
|
|
533
|
+
|
|
531
534
|
Returns:
|
|
532
535
|
True if components cooled to target temperature, False if timeout or error
|
|
533
536
|
"""
|
|
534
537
|
logger.info(f"Starting cooling process - waiting for parts to cool to {target_temp}°C")
|
|
535
|
-
|
|
538
|
+
|
|
536
539
|
# Cancel heating for both extruder and bed
|
|
537
540
|
extruder_cancel_ok = await self.cancel_extruder_temp()
|
|
538
541
|
bed_cancel_ok = await self.cancel_bed_temp()
|
|
539
|
-
|
|
542
|
+
|
|
540
543
|
if not (extruder_cancel_ok and bed_cancel_ok):
|
|
541
544
|
logger.error("Failed to cancel heating - cannot wait for cooling")
|
|
542
545
|
return False
|
|
543
|
-
|
|
546
|
+
|
|
544
547
|
start_time = asyncio.get_event_loop().time()
|
|
545
548
|
timeout_time = start_time + timeout_seconds
|
|
546
|
-
|
|
549
|
+
|
|
547
550
|
extruder_cooled = False
|
|
548
551
|
bed_cooled = False
|
|
549
|
-
|
|
552
|
+
|
|
550
553
|
while asyncio.get_event_loop().time() < timeout_time:
|
|
551
554
|
# Check current temperatures
|
|
552
555
|
temp_info = await self.get_temp_info()
|
|
@@ -554,7 +557,7 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
554
557
|
logger.warning("Could not get temperature info during cooling")
|
|
555
558
|
await asyncio.sleep(10)
|
|
556
559
|
continue
|
|
557
|
-
|
|
560
|
+
|
|
558
561
|
# Check extruder temperature
|
|
559
562
|
if not extruder_cooled:
|
|
560
563
|
extruder_temp = temp_info.get_extruder_temp()
|
|
@@ -563,7 +566,7 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
563
566
|
if current_extruder_temp <= target_temp:
|
|
564
567
|
extruder_cooled = True
|
|
565
568
|
logger.info(f"Extruder cooled to {current_extruder_temp}°C")
|
|
566
|
-
|
|
569
|
+
|
|
567
570
|
# Check bed temperature
|
|
568
571
|
if not bed_cooled:
|
|
569
572
|
bed_temp = temp_info.get_bed_temp()
|
|
@@ -572,15 +575,15 @@ class FlashForgeClient(FlashForgeTcpClient):
|
|
|
572
575
|
if current_bed_temp <= target_temp:
|
|
573
576
|
bed_cooled = True
|
|
574
577
|
logger.info(f"Bed cooled to {current_bed_temp}°C")
|
|
575
|
-
|
|
578
|
+
|
|
576
579
|
# Check if both have cooled
|
|
577
580
|
if extruder_cooled and bed_cooled:
|
|
578
581
|
logger.info("All components have cooled to target temperature")
|
|
579
582
|
return True
|
|
580
|
-
|
|
583
|
+
|
|
581
584
|
# Wait before next check
|
|
582
585
|
await asyncio.sleep(10) # Check every 10 seconds
|
|
583
|
-
|
|
586
|
+
|
|
584
587
|
# Timeout reached
|
|
585
588
|
logger.warning(f"Timeout after {timeout_seconds} seconds waiting for cooling")
|
|
586
589
|
logger.warning(f"Extruder cooled: {extruder_cooled}, Bed cooled: {bed_cooled}")
|
flashforge/tcp/gcode/__init__.py
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
G-code related modules for FlashForge printer communication.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from .a3_gcode_controller import A3GCodeController
|
|
5
6
|
from .gcode_controller import GCodeController
|
|
6
7
|
from .gcodes import GCodes
|
|
7
8
|
|
|
8
9
|
__all__ = [
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
"GCodes",
|
|
11
|
+
"GCodeController",
|
|
12
|
+
"A3GCodeController",
|
|
11
13
|
]
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""
|
|
2
|
+
G-code controller for FlashForge Adventurer 3 printers.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from .gcode_controller import GCodeController
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from ..a3_client import A3FileEntry, A3PrinterInfo, A3Thumbnail, FlashForgeA3Client
|
|
13
|
+
from ..parsers import EndstopStatus, LocationInfo, PrintStatus
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class A3GCodeController(GCodeController):
|
|
17
|
+
"""High-level G-code controller for Adventurer 3 printers."""
|
|
18
|
+
|
|
19
|
+
client: FlashForgeA3Client
|
|
20
|
+
|
|
21
|
+
def __init__(self, client: FlashForgeA3Client) -> None:
|
|
22
|
+
super().__init__(client)
|
|
23
|
+
self.client = client
|
|
24
|
+
|
|
25
|
+
async def set_printer_name(self, name: str) -> bool:
|
|
26
|
+
return await self.client.set_printer_name(name)
|
|
27
|
+
|
|
28
|
+
async def get_print_status(self) -> PrintStatus | None:
|
|
29
|
+
return await self.client.get_print_status()
|
|
30
|
+
|
|
31
|
+
async def list_files(self) -> list[A3FileEntry]:
|
|
32
|
+
return await self.client.list_files()
|
|
33
|
+
|
|
34
|
+
async def get_thumbnail(self, filename: str) -> A3Thumbnail | None:
|
|
35
|
+
return await self.client.get_thumbnail(filename)
|
|
36
|
+
|
|
37
|
+
async def select_file(self, filename: str) -> bool:
|
|
38
|
+
return await self.client.select_file(filename)
|
|
39
|
+
|
|
40
|
+
async def start_job(self, filename: str) -> bool:
|
|
41
|
+
if not await self.client.select_file(filename):
|
|
42
|
+
return False
|
|
43
|
+
return await self.client.start_print()
|
|
44
|
+
|
|
45
|
+
async def get_current_position(self) -> LocationInfo | None:
|
|
46
|
+
return await self.client.get_position()
|
|
47
|
+
|
|
48
|
+
async def get_endstop_status(self) -> EndstopStatus | None:
|
|
49
|
+
return await self.client.get_endstop_status()
|
|
50
|
+
|
|
51
|
+
async def enable_motors(self) -> bool:
|
|
52
|
+
return await self.client.enable_motors()
|
|
53
|
+
|
|
54
|
+
async def disable_motors(self) -> bool:
|
|
55
|
+
return await self.client.disable_motors()
|
|
56
|
+
|
|
57
|
+
async def emergency_stop(self) -> bool:
|
|
58
|
+
return await self.client.emergency_stop()
|
|
59
|
+
|
|
60
|
+
async def cancel_heat_wait(self) -> bool:
|
|
61
|
+
return await self.client.cancel_heat_wait()
|
|
62
|
+
|
|
63
|
+
async def led_control(self, params: str) -> bool:
|
|
64
|
+
return await self.client.led_control(params)
|
|
65
|
+
|
|
66
|
+
async def custom_m144(self, params: str | None = None) -> str | None:
|
|
67
|
+
return await self.client.custom_m144(params)
|
|
68
|
+
|
|
69
|
+
async def custom_m145(self, params: str | None = None) -> str | None:
|
|
70
|
+
return await self.client.custom_m145(params)
|
|
71
|
+
|
|
72
|
+
async def get_printer_model(self) -> str | None:
|
|
73
|
+
info = await self.client.get_printer_info()
|
|
74
|
+
return info.machine_type if info else None
|
|
75
|
+
|
|
76
|
+
async def custom_m651(self) -> str | None:
|
|
77
|
+
return await self.client.custom_m651()
|
|
78
|
+
|
|
79
|
+
async def custom_m652(self) -> str | None:
|
|
80
|
+
return await self.client.custom_m652()
|
|
81
|
+
|
|
82
|
+
async def custom_m653(self, params: str) -> bool:
|
|
83
|
+
return await self.client.custom_m653(params)
|
|
84
|
+
|
|
85
|
+
async def custom_m654(self, params: str) -> bool:
|
|
86
|
+
return await self.client.custom_m654(params)
|
|
87
|
+
|
|
88
|
+
async def custom_m611(self) -> str | None:
|
|
89
|
+
return await self.client.custom_m611()
|
|
90
|
+
|
|
91
|
+
async def custom_m612(self) -> str | None:
|
|
92
|
+
return await self.client.custom_m612()
|
|
93
|
+
|
|
94
|
+
async def get_printer_info(self) -> A3PrinterInfo | None:
|
|
95
|
+
return await self.client.get_printer_info()
|
|
96
|
+
|
|
97
|
+
async def is_adventurer_3(self) -> bool:
|
|
98
|
+
info = await self.get_printer_info()
|
|
99
|
+
if not info:
|
|
100
|
+
return False
|
|
101
|
+
machine_type = info.machine_type.lower()
|
|
102
|
+
return (
|
|
103
|
+
"adventurer3" in machine_type
|
|
104
|
+
or "adventurer 3" in machine_type
|
|
105
|
+
or "adventurer iii" in machine_type
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
async def get_detailed_position(self) -> LocationInfo | None:
|
|
109
|
+
return await self.client.get_position_xyze()
|