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
|
@@ -7,28 +7,36 @@ the basic TCP communication provided by FlashForgeTcpClient.
|
|
|
7
7
|
|
|
8
8
|
import asyncio
|
|
9
9
|
import logging
|
|
10
|
-
from typing import TYPE_CHECKING
|
|
10
|
+
from typing import TYPE_CHECKING, Protocol
|
|
11
11
|
|
|
12
12
|
from .gcodes import GCodes
|
|
13
13
|
|
|
14
14
|
if TYPE_CHECKING:
|
|
15
|
-
from ..
|
|
15
|
+
from ..parsers.temp_info import TempInfo
|
|
16
16
|
|
|
17
17
|
logger = logging.getLogger(__name__)
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
class SupportsGCodeClient(Protocol):
|
|
21
|
+
"""Client protocol implemented by both the legacy and Adventurer 3 TCP clients."""
|
|
22
|
+
|
|
23
|
+
async def send_cmd_ok(self, cmd: str) -> bool: ...
|
|
24
|
+
|
|
25
|
+
async def get_temp_info(self) -> "TempInfo | None": ...
|
|
26
|
+
|
|
27
|
+
|
|
20
28
|
class GCodeController:
|
|
21
29
|
"""
|
|
22
30
|
Controller for sending specific G-code commands to FlashForge printers.
|
|
23
|
-
|
|
31
|
+
|
|
24
32
|
This class provides convenient methods for common printer operations
|
|
25
33
|
like LED control, job management, movement, and temperature control.
|
|
26
34
|
"""
|
|
27
35
|
|
|
28
|
-
def __init__(self, client:
|
|
36
|
+
def __init__(self, client: SupportsGCodeClient) -> None:
|
|
29
37
|
"""
|
|
30
38
|
Initialize the G-code controller.
|
|
31
|
-
|
|
39
|
+
|
|
32
40
|
Args:
|
|
33
41
|
client: The FlashForgeClient instance for sending commands
|
|
34
42
|
"""
|
|
@@ -37,7 +45,7 @@ class GCodeController:
|
|
|
37
45
|
async def led_on(self) -> bool:
|
|
38
46
|
"""
|
|
39
47
|
Turn the printer's LED lights on.
|
|
40
|
-
|
|
48
|
+
|
|
41
49
|
Returns:
|
|
42
50
|
True if the command was successful, False otherwise
|
|
43
51
|
"""
|
|
@@ -46,7 +54,7 @@ class GCodeController:
|
|
|
46
54
|
async def led_off(self) -> bool:
|
|
47
55
|
"""
|
|
48
56
|
Turn the printer's LED lights off.
|
|
49
|
-
|
|
57
|
+
|
|
50
58
|
Returns:
|
|
51
59
|
True if the command was successful, False otherwise
|
|
52
60
|
"""
|
|
@@ -55,7 +63,7 @@ class GCodeController:
|
|
|
55
63
|
async def pause_job(self) -> bool:
|
|
56
64
|
"""
|
|
57
65
|
Pause the current print job.
|
|
58
|
-
|
|
66
|
+
|
|
59
67
|
Returns:
|
|
60
68
|
True if the command was successful, False otherwise
|
|
61
69
|
"""
|
|
@@ -64,7 +72,7 @@ class GCodeController:
|
|
|
64
72
|
async def resume_job(self) -> bool:
|
|
65
73
|
"""
|
|
66
74
|
Resume a paused print job.
|
|
67
|
-
|
|
75
|
+
|
|
68
76
|
Returns:
|
|
69
77
|
True if the command was successful, False otherwise
|
|
70
78
|
"""
|
|
@@ -73,7 +81,7 @@ class GCodeController:
|
|
|
73
81
|
async def stop_job(self) -> bool:
|
|
74
82
|
"""
|
|
75
83
|
Stop the current print job.
|
|
76
|
-
|
|
84
|
+
|
|
77
85
|
Returns:
|
|
78
86
|
True if the command was successful, False otherwise
|
|
79
87
|
"""
|
|
@@ -82,10 +90,10 @@ class GCodeController:
|
|
|
82
90
|
async def start_job(self, filename: str) -> bool:
|
|
83
91
|
"""
|
|
84
92
|
Start a print job from a file stored on the printer.
|
|
85
|
-
|
|
93
|
+
|
|
86
94
|
Args:
|
|
87
95
|
filename: The name of the file to print (typically without path)
|
|
88
|
-
|
|
96
|
+
|
|
89
97
|
Returns:
|
|
90
98
|
True if the command was successful, False otherwise
|
|
91
99
|
"""
|
|
@@ -95,7 +103,7 @@ class GCodeController:
|
|
|
95
103
|
async def home(self) -> bool:
|
|
96
104
|
"""
|
|
97
105
|
Home all axes (X, Y, Z) of the printer.
|
|
98
|
-
|
|
106
|
+
|
|
99
107
|
Returns:
|
|
100
108
|
True if the command was successful, False otherwise
|
|
101
109
|
"""
|
|
@@ -104,9 +112,9 @@ class GCodeController:
|
|
|
104
112
|
async def rapid_home(self) -> bool:
|
|
105
113
|
"""
|
|
106
114
|
Perform a rapid homing of all axes.
|
|
107
|
-
|
|
115
|
+
|
|
108
116
|
Note: This uses the same command as regular homing in the current implementation.
|
|
109
|
-
|
|
117
|
+
|
|
110
118
|
Returns:
|
|
111
119
|
True if the command was successful, False otherwise
|
|
112
120
|
"""
|
|
@@ -116,13 +124,13 @@ class GCodeController:
|
|
|
116
124
|
async def move(self, x: float, y: float, z: float, feedrate: int) -> bool:
|
|
117
125
|
"""
|
|
118
126
|
Move the extruder to a specified X, Y, Z position.
|
|
119
|
-
|
|
127
|
+
|
|
120
128
|
Args:
|
|
121
129
|
x: The target X coordinate
|
|
122
|
-
y: The target Y coordinate
|
|
130
|
+
y: The target Y coordinate
|
|
123
131
|
z: The target Z coordinate
|
|
124
132
|
feedrate: The feedrate for the movement in mm/min
|
|
125
|
-
|
|
133
|
+
|
|
126
134
|
Returns:
|
|
127
135
|
True if the command was successful, False otherwise
|
|
128
136
|
"""
|
|
@@ -132,12 +140,12 @@ class GCodeController:
|
|
|
132
140
|
async def move_extruder(self, x: float, y: float, feedrate: int) -> bool:
|
|
133
141
|
"""
|
|
134
142
|
Move the extruder to a specified X, Y position.
|
|
135
|
-
|
|
143
|
+
|
|
136
144
|
Args:
|
|
137
145
|
x: The target X coordinate
|
|
138
146
|
y: The target Y coordinate
|
|
139
147
|
feedrate: The feedrate for the movement in mm/min
|
|
140
|
-
|
|
148
|
+
|
|
141
149
|
Returns:
|
|
142
150
|
True if the command was successful, False otherwise
|
|
143
151
|
"""
|
|
@@ -147,11 +155,11 @@ class GCodeController:
|
|
|
147
155
|
async def extrude(self, length: float, feedrate: int = 450) -> bool:
|
|
148
156
|
"""
|
|
149
157
|
Command the extruder to extrude a specific length of filament.
|
|
150
|
-
|
|
158
|
+
|
|
151
159
|
Args:
|
|
152
160
|
length: The length of filament to extrude in millimeters
|
|
153
161
|
feedrate: The feedrate for extrusion in mm/min (default: 450)
|
|
154
|
-
|
|
162
|
+
|
|
155
163
|
Returns:
|
|
156
164
|
True if the command was successful, False otherwise
|
|
157
165
|
"""
|
|
@@ -161,11 +169,11 @@ class GCodeController:
|
|
|
161
169
|
async def set_extruder_temp(self, temp: int, wait_for: bool = False) -> bool:
|
|
162
170
|
"""
|
|
163
171
|
Set the target temperature for the extruder.
|
|
164
|
-
|
|
172
|
+
|
|
165
173
|
Args:
|
|
166
174
|
temp: The target temperature in Celsius
|
|
167
175
|
wait_for: If True, wait until the target temperature is reached
|
|
168
|
-
|
|
176
|
+
|
|
169
177
|
Returns:
|
|
170
178
|
True if the command was successful, False otherwise
|
|
171
179
|
"""
|
|
@@ -183,11 +191,11 @@ class GCodeController:
|
|
|
183
191
|
async def set_bed_temp(self, temp: int, wait_for: bool = False) -> bool:
|
|
184
192
|
"""
|
|
185
193
|
Set the target temperature for the print bed.
|
|
186
|
-
|
|
194
|
+
|
|
187
195
|
Args:
|
|
188
|
-
temp: The target temperature in Celsius
|
|
196
|
+
temp: The target temperature in Celsius
|
|
189
197
|
wait_for: If True, wait until the target temperature is reached
|
|
190
|
-
|
|
198
|
+
|
|
191
199
|
Returns:
|
|
192
200
|
True if the command was successful, False otherwise
|
|
193
201
|
"""
|
|
@@ -205,7 +213,7 @@ class GCodeController:
|
|
|
205
213
|
async def cancel_extruder_temp(self) -> bool:
|
|
206
214
|
"""
|
|
207
215
|
Cancel extruder heating and set its target temperature to 0.
|
|
208
|
-
|
|
216
|
+
|
|
209
217
|
Returns:
|
|
210
218
|
True if the command was successful, False otherwise
|
|
211
219
|
"""
|
|
@@ -216,10 +224,10 @@ class GCodeController:
|
|
|
216
224
|
async def cancel_bed_temp(self, wait_for_cool: bool = False) -> bool:
|
|
217
225
|
"""
|
|
218
226
|
Cancel print bed heating and set its target temperature to 0.
|
|
219
|
-
|
|
227
|
+
|
|
220
228
|
Args:
|
|
221
229
|
wait_for_cool: If True, wait for the bed to cool down after canceling
|
|
222
|
-
|
|
230
|
+
|
|
223
231
|
Returns:
|
|
224
232
|
True if the command was successful, False otherwise
|
|
225
233
|
"""
|
|
@@ -234,15 +242,17 @@ class GCodeController:
|
|
|
234
242
|
|
|
235
243
|
return True
|
|
236
244
|
|
|
237
|
-
async def wait_for_bed_temp(
|
|
245
|
+
async def wait_for_bed_temp(
|
|
246
|
+
self, target_temp: int, cooling: bool = False, timeout: int = 600
|
|
247
|
+
) -> bool:
|
|
238
248
|
"""
|
|
239
249
|
Wait for the bed temperature to reach the target.
|
|
240
|
-
|
|
250
|
+
|
|
241
251
|
Args:
|
|
242
252
|
target_temp: The target temperature to wait for
|
|
243
253
|
cooling: If True, wait for temperature to drop below target; otherwise wait to reach target
|
|
244
254
|
timeout: Maximum time to wait in seconds (default: 600)
|
|
245
|
-
|
|
255
|
+
|
|
246
256
|
Returns:
|
|
247
257
|
True if target temperature was reached, False on timeout or error
|
|
248
258
|
"""
|
|
@@ -264,17 +274,19 @@ class GCodeController:
|
|
|
264
274
|
|
|
265
275
|
await asyncio.sleep(5) # Check every 5 seconds
|
|
266
276
|
|
|
267
|
-
logger.warning(
|
|
277
|
+
logger.warning(
|
|
278
|
+
f"Timeout waiting for bed temperature {'cooling to' if cooling else 'heating to'} {target_temp}°C"
|
|
279
|
+
)
|
|
268
280
|
return False
|
|
269
281
|
|
|
270
282
|
async def wait_for_extruder_temp(self, target_temp: int, timeout: int = 600) -> bool:
|
|
271
283
|
"""
|
|
272
284
|
Wait for the extruder temperature to reach the target.
|
|
273
|
-
|
|
285
|
+
|
|
274
286
|
Args:
|
|
275
287
|
target_temp: The target temperature to wait for
|
|
276
288
|
timeout: Maximum time to wait in seconds (default: 600)
|
|
277
|
-
|
|
289
|
+
|
|
278
290
|
Returns:
|
|
279
291
|
True if target temperature was reached, False on timeout or error
|
|
280
292
|
"""
|
flashforge/tcp/gcode/gcodes.py
CHANGED
|
@@ -12,7 +12,7 @@ from typing import Final
|
|
|
12
12
|
class GCodes:
|
|
13
13
|
"""
|
|
14
14
|
Collection of G-code and M-code command strings for FlashForge 3D printers.
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
All commands use the FlashForge TCP protocol with '~' prefix.
|
|
17
17
|
"""
|
|
18
18
|
|
|
@@ -64,6 +64,12 @@ class GCodes:
|
|
|
64
64
|
CMD_GET_THUMBNAIL: Final[str] = "~M662"
|
|
65
65
|
"""Command to retrieve a thumbnail image for a specified G-code file."""
|
|
66
66
|
|
|
67
|
+
CMD_PREP_FILE_UPLOAD: Final[str] = "~M28 %%size%% 0:/user/%%filename%%"
|
|
68
|
+
"""Command to begin a legacy raw-binary file upload to printer storage."""
|
|
69
|
+
|
|
70
|
+
CMD_COMPLETE_FILE_UPLOAD: Final[str] = "~M29"
|
|
71
|
+
"""Command to finalize a legacy raw-binary file upload."""
|
|
72
|
+
|
|
67
73
|
# Camera control
|
|
68
74
|
TAKE_PICTURE: Final[str] = "~M240"
|
|
69
75
|
"""Command to instruct the printer to take a picture with its camera, if equipped."""
|
|
@@ -13,15 +13,15 @@ from .temp_info import TempData, TempInfo
|
|
|
13
13
|
from .thumbnail_info import ThumbnailInfo
|
|
14
14
|
|
|
15
15
|
__all__ = [
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
16
|
+
"PrinterInfo",
|
|
17
|
+
"TempInfo",
|
|
18
|
+
"TempData",
|
|
19
|
+
"LocationInfo",
|
|
20
|
+
"EndstopStatus",
|
|
21
|
+
"MachineStatus",
|
|
22
|
+
"MoveMode",
|
|
23
|
+
"Status",
|
|
24
|
+
"Endstop",
|
|
25
|
+
"PrintStatus",
|
|
26
|
+
"ThumbnailInfo",
|
|
27
27
|
]
|
|
@@ -3,13 +3,18 @@ FlashForge Python API - Endstop Status Parser
|
|
|
3
3
|
|
|
4
4
|
Parses endstop and machine status information from M119 command responses.
|
|
5
5
|
"""
|
|
6
|
+
|
|
6
7
|
import re
|
|
7
8
|
from enum import Enum
|
|
8
9
|
from typing import Optional
|
|
9
10
|
|
|
11
|
+
# Pre-compiled regex to match key:value pairs where value is an integer
|
|
12
|
+
KV_PATTERN = re.compile(r"([A-Za-z0-9-]+):\s*(\d+)")
|
|
13
|
+
|
|
10
14
|
|
|
11
15
|
class MachineStatus(Enum):
|
|
12
16
|
"""Enumerates the possible operational statuses of the machine."""
|
|
17
|
+
|
|
13
18
|
BUILDING_FROM_SD = "BUILDING_FROM_SD"
|
|
14
19
|
BUILDING_COMPLETED = "BUILDING_COMPLETED"
|
|
15
20
|
PAUSED = "PAUSED"
|
|
@@ -20,6 +25,7 @@ class MachineStatus(Enum):
|
|
|
20
25
|
|
|
21
26
|
class MoveMode(Enum):
|
|
22
27
|
"""Enumerates the possible movement modes of the printer."""
|
|
28
|
+
|
|
23
29
|
MOVING = "MOVING"
|
|
24
30
|
PAUSED = "PAUSED"
|
|
25
31
|
READY = "READY"
|
|
@@ -38,31 +44,15 @@ class Status:
|
|
|
38
44
|
"""
|
|
39
45
|
Creates an instance of Status by parsing a string line.
|
|
40
46
|
It uses regular expressions to find key-value pairs like "S:0".
|
|
41
|
-
|
|
42
|
-
Args:
|
|
43
|
-
data: The string line containing status flags (e.g., "Status S:0 L:0 J:0 F:0")
|
|
44
|
-
"""
|
|
45
|
-
self.s: int = self._get_value(data, "S")
|
|
46
|
-
self.l: int = self._get_value(data, "L")
|
|
47
|
-
self.j: int = self._get_value(data, "J")
|
|
48
|
-
self.f: int = self._get_value(data, "F")
|
|
49
47
|
|
|
50
|
-
def _get_value(self, input_str: str, key: str) -> int:
|
|
51
|
-
"""
|
|
52
|
-
Helper function to extract a numeric value associated with a key from a string.
|
|
53
|
-
|
|
54
48
|
Args:
|
|
55
|
-
|
|
56
|
-
key: The key whose numeric value is to be extracted
|
|
57
|
-
|
|
58
|
-
Returns:
|
|
59
|
-
The parsed integer value, or -1 if the key is not found or parsing fails
|
|
49
|
+
data: The string line containing status flags (e.g., "Status S:0 L:0 J:0 F:0")
|
|
60
50
|
"""
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
51
|
+
matches: dict[str, str] = dict(KV_PATTERN.findall(data))
|
|
52
|
+
self.s: int = int(matches.get("S", -1))
|
|
53
|
+
self.l: int = int(matches.get("L", -1))
|
|
54
|
+
self.j: int = int(matches.get("J", -1))
|
|
55
|
+
self.f: int = int(matches.get("F", -1))
|
|
66
56
|
|
|
67
57
|
|
|
68
58
|
class Endstop:
|
|
@@ -75,55 +65,40 @@ class Endstop:
|
|
|
75
65
|
"""
|
|
76
66
|
Creates an instance of Endstop by parsing a string line.
|
|
77
67
|
It uses regular expressions to find key-value pairs like "X-max:0".
|
|
78
|
-
|
|
79
|
-
Args:
|
|
80
|
-
data: The string line containing endstop states (e.g., "Endstop X-max:0 Y-max:0 Z-min:1")
|
|
81
|
-
"""
|
|
82
|
-
self.x_max: int = self._get_value(data, "X-max")
|
|
83
|
-
self.y_max: int = self._get_value(data, "Y-max")
|
|
84
|
-
self.z_min: int = self._get_value(data, "Z-min")
|
|
85
68
|
|
|
86
|
-
def _get_value(self, input_str: str, key: str) -> int:
|
|
87
|
-
"""
|
|
88
|
-
Helper function to extract a numeric value associated with a key from a string.
|
|
89
|
-
|
|
90
69
|
Args:
|
|
91
|
-
|
|
92
|
-
key: The key whose numeric value is to be extracted
|
|
93
|
-
|
|
94
|
-
Returns:
|
|
95
|
-
The parsed integer value, or -1 if the key is not found or parsing fails
|
|
70
|
+
data: The string line containing endstop states (e.g., "Endstop X-max:0 Y-max:0 Z-min:1")
|
|
96
71
|
"""
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
return -1
|
|
72
|
+
matches: dict[str, str] = dict(KV_PATTERN.findall(data))
|
|
73
|
+
self.x_max: int = int(matches.get("X-max", -1))
|
|
74
|
+
self.y_max: int = int(matches.get("Y-max", -1))
|
|
75
|
+
self.z_min: int = int(matches.get("Z-min", -1))
|
|
102
76
|
|
|
103
77
|
|
|
104
78
|
class EndstopStatus:
|
|
105
79
|
"""
|
|
106
80
|
Represents the status of the printer's endstops and various other machine states.
|
|
107
|
-
|
|
81
|
+
|
|
108
82
|
This information is typically parsed from the response of an M119 command or a similar
|
|
109
83
|
consolidated status report from the printer. It includes endstop states, machine operational status,
|
|
110
84
|
movement mode, LED status, and the currently loaded file.
|
|
111
85
|
"""
|
|
112
86
|
|
|
113
|
-
def __init__(self):
|
|
87
|
+
def __init__(self) -> None:
|
|
114
88
|
"""Initialize a new EndstopStatus instance."""
|
|
115
|
-
self.endstop:
|
|
89
|
+
self.endstop: Endstop | None = None
|
|
116
90
|
self.machine_status: MachineStatus = MachineStatus.DEFAULT
|
|
117
91
|
self.move_mode: MoveMode = MoveMode.DEFAULT
|
|
118
|
-
self.status:
|
|
92
|
+
self.status: Status | None = None
|
|
93
|
+
self.filament_status: str = ""
|
|
119
94
|
self.led_enabled: bool = False
|
|
120
|
-
self.current_file:
|
|
95
|
+
self.current_file: str | None = None
|
|
121
96
|
|
|
122
|
-
def from_replay(self, replay: str) -> Optional[
|
|
97
|
+
def from_replay(self, replay: str) -> Optional["EndstopStatus"]:
|
|
123
98
|
"""
|
|
124
99
|
Parses a raw string replay (typically from an M119 or similar status command)
|
|
125
100
|
to populate the properties of this EndstopStatus instance.
|
|
126
|
-
|
|
101
|
+
|
|
127
102
|
The replay is expected to be a multi-line string where each line provides specific information:
|
|
128
103
|
- Line 1 (data[0]): Usually a command echo or header, ignored
|
|
129
104
|
- Line 2 (data[1]): Parsed into the endstop object
|
|
@@ -132,10 +107,10 @@ class EndstopStatus:
|
|
|
132
107
|
- Line 5 (data[4]): Parsed into the status object
|
|
133
108
|
- Line 6 (data[5]): Parsed to determine led_enabled (1 for true, 0 for false)
|
|
134
109
|
- Line 7 (data[6]): Parsed to get current_file, or None if empty
|
|
135
|
-
|
|
110
|
+
|
|
136
111
|
Args:
|
|
137
112
|
replay: The raw multi-line string response from the printer
|
|
138
|
-
|
|
113
|
+
|
|
139
114
|
Returns:
|
|
140
115
|
The populated EndstopStatus instance, or None if parsing fails
|
|
141
116
|
"""
|
|
@@ -143,72 +118,66 @@ class EndstopStatus:
|
|
|
143
118
|
return None
|
|
144
119
|
|
|
145
120
|
try:
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
# Validate that we have enough lines for a valid response
|
|
149
|
-
# A valid M119 response should have at least 2 lines (command echo + endstop data)
|
|
150
|
-
if len(data) < 2:
|
|
121
|
+
lines = [line.strip() for line in replay.replace("\r", "\n").split("\n") if line.strip()]
|
|
122
|
+
if not lines:
|
|
151
123
|
return None
|
|
152
124
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if len(data) > 1 and "Endstop" not in data[1]:
|
|
125
|
+
endstop_line = next((line for line in lines if "Endstop" in line), None)
|
|
126
|
+
if endstop_line is None:
|
|
156
127
|
return None
|
|
157
128
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
self.
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
self.
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
current_file = data[6].replace("CurrentFile: ", "").strip()
|
|
211
|
-
self.current_file = current_file if current_file else None
|
|
129
|
+
self.endstop = Endstop(endstop_line)
|
|
130
|
+
|
|
131
|
+
for line in lines:
|
|
132
|
+
if line.startswith("MachineStatus:"):
|
|
133
|
+
machine_status = line.replace("MachineStatus:", "", 1).strip().upper()
|
|
134
|
+
if "BUILDING_FROM_SD" in machine_status:
|
|
135
|
+
self.machine_status = MachineStatus.BUILDING_FROM_SD
|
|
136
|
+
elif "BUILDING_COMPLETED" in machine_status:
|
|
137
|
+
self.machine_status = MachineStatus.BUILDING_COMPLETED
|
|
138
|
+
elif "PAUSED" in machine_status:
|
|
139
|
+
self.machine_status = MachineStatus.PAUSED
|
|
140
|
+
elif "READY" in machine_status or "IDLE" in machine_status:
|
|
141
|
+
self.machine_status = MachineStatus.READY
|
|
142
|
+
elif "BUSY" in machine_status:
|
|
143
|
+
self.machine_status = MachineStatus.BUSY
|
|
144
|
+
else:
|
|
145
|
+
print(f"EndstopStatus: Encountered unknown MachineStatus: {machine_status}")
|
|
146
|
+
self.machine_status = MachineStatus.DEFAULT
|
|
147
|
+
elif line.startswith("MoveMode:"):
|
|
148
|
+
move_mode = line.replace("MoveMode:", "", 1).strip().upper()
|
|
149
|
+
if "MOVING" in move_mode:
|
|
150
|
+
self.move_mode = MoveMode.MOVING
|
|
151
|
+
elif "PAUSED" in move_mode:
|
|
152
|
+
self.move_mode = MoveMode.PAUSED
|
|
153
|
+
elif "READY" in move_mode or move_mode in {"0", "0.0"}:
|
|
154
|
+
self.move_mode = MoveMode.READY
|
|
155
|
+
elif "WAIT_ON_TOOL" in move_mode:
|
|
156
|
+
self.move_mode = MoveMode.WAIT_ON_TOOL
|
|
157
|
+
elif "HOMING" in move_mode:
|
|
158
|
+
self.move_mode = MoveMode.HOMING
|
|
159
|
+
else:
|
|
160
|
+
print(f"EndstopStatus: Encountered unknown MoveMode: {move_mode}")
|
|
161
|
+
self.move_mode = MoveMode.DEFAULT
|
|
162
|
+
elif line.startswith("Status "):
|
|
163
|
+
self.status = Status(line)
|
|
164
|
+
elif line.startswith("FilamentStatus:"):
|
|
165
|
+
self.filament_status = line.replace("FilamentStatus:", "", 1).strip()
|
|
166
|
+
elif line.startswith("LEDStatus:"):
|
|
167
|
+
led_status = line.replace("LEDStatus:", "", 1).strip().lower()
|
|
168
|
+
self.led_enabled = led_status == "on"
|
|
169
|
+
elif line.startswith("LED:"):
|
|
170
|
+
led_str = line.replace("LED:", "", 1).strip()
|
|
171
|
+
try:
|
|
172
|
+
self.led_enabled = int(led_str) == 1
|
|
173
|
+
except ValueError:
|
|
174
|
+
self.led_enabled = False
|
|
175
|
+
elif line.startswith("CurrentFile:"):
|
|
176
|
+
current_file = line.replace("CurrentFile:", "", 1).strip()
|
|
177
|
+
self.current_file = current_file if current_file else None
|
|
178
|
+
elif line.startswith("PrintFileName:"):
|
|
179
|
+
current_file = line.replace("PrintFileName:", "", 1).strip()
|
|
180
|
+
self.current_file = current_file if current_file else None
|
|
212
181
|
|
|
213
182
|
return self
|
|
214
183
|
|
|
@@ -221,7 +190,7 @@ class EndstopStatus:
|
|
|
221
190
|
def is_print_complete(self) -> bool:
|
|
222
191
|
"""
|
|
223
192
|
Checks if the machine status indicates that a print has been completed.
|
|
224
|
-
|
|
193
|
+
|
|
225
194
|
Returns:
|
|
226
195
|
True if machine_status is BUILDING_COMPLETED, False otherwise
|
|
227
196
|
"""
|
|
@@ -230,7 +199,7 @@ class EndstopStatus:
|
|
|
230
199
|
def is_printing(self) -> bool:
|
|
231
200
|
"""
|
|
232
201
|
Checks if the machine status indicates that a print is currently in progress from SD.
|
|
233
|
-
|
|
202
|
+
|
|
234
203
|
Returns:
|
|
235
204
|
True if machine_status is BUILDING_FROM_SD, False otherwise
|
|
236
205
|
"""
|
|
@@ -239,35 +208,37 @@ class EndstopStatus:
|
|
|
239
208
|
def is_ready(self) -> bool:
|
|
240
209
|
"""
|
|
241
210
|
Checks if the printer is in a ready state (both move mode and machine status are READY).
|
|
242
|
-
|
|
211
|
+
|
|
243
212
|
Returns:
|
|
244
213
|
True if the printer is ready, False otherwise
|
|
245
214
|
"""
|
|
246
|
-
return
|
|
247
|
-
self.machine_status == MachineStatus.READY)
|
|
215
|
+
return self.move_mode == MoveMode.READY and self.machine_status == MachineStatus.READY
|
|
248
216
|
|
|
249
217
|
def is_paused(self) -> bool:
|
|
250
218
|
"""
|
|
251
219
|
Checks if the printer is currently paused (either machine status or move mode is PAUSED).
|
|
252
|
-
|
|
220
|
+
|
|
253
221
|
Returns:
|
|
254
222
|
True if the printer is paused, False otherwise
|
|
255
223
|
"""
|
|
256
|
-
return
|
|
257
|
-
self.move_mode == MoveMode.PAUSED)
|
|
224
|
+
return self.machine_status == MachineStatus.PAUSED or self.move_mode == MoveMode.PAUSED
|
|
258
225
|
|
|
259
226
|
def __str__(self) -> str:
|
|
260
227
|
"""String representation of the endstop status."""
|
|
261
|
-
return (
|
|
262
|
-
|
|
263
|
-
|
|
228
|
+
return (
|
|
229
|
+
f"EndstopStatus(machine={self.machine_status.value}, "
|
|
230
|
+
f"move={self.move_mode.value}, led={self.led_enabled}, "
|
|
231
|
+
f"file='{self.current_file}')"
|
|
232
|
+
)
|
|
264
233
|
|
|
265
234
|
def __repr__(self) -> str:
|
|
266
235
|
"""Detailed string representation for debugging."""
|
|
267
|
-
return (
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
236
|
+
return (
|
|
237
|
+
f"EndstopStatus("
|
|
238
|
+
f"endstop={self.endstop}, "
|
|
239
|
+
f"machine_status={self.machine_status}, "
|
|
240
|
+
f"move_mode={self.move_mode}, "
|
|
241
|
+
f"status={self.status}, "
|
|
242
|
+
f"led_enabled={self.led_enabled}, "
|
|
243
|
+
f"current_file='{self.current_file}')"
|
|
244
|
+
)
|