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.
Files changed (43) hide show
  1. flashforge/__init__.py +70 -29
  2. flashforge/api/__init__.py +1 -0
  3. flashforge/api/constants/__init__.py +1 -0
  4. flashforge/api/constants/commands.py +1 -0
  5. flashforge/api/constants/endpoints.py +7 -0
  6. flashforge/api/controls/__init__.py +1 -0
  7. flashforge/api/controls/control.py +64 -61
  8. flashforge/api/controls/files.py +28 -26
  9. flashforge/api/controls/info.py +84 -85
  10. flashforge/api/controls/job_control.py +120 -82
  11. flashforge/api/controls/temp_control.py +14 -11
  12. flashforge/api/misc/__init__.py +1 -1
  13. flashforge/api/network/__init__.py +2 -1
  14. flashforge/api/network/utils.py +7 -8
  15. flashforge/client.py +185 -64
  16. flashforge/discovery/__init__.py +30 -3
  17. flashforge/discovery/discovery.py +641 -308
  18. flashforge/models/__init__.py +11 -10
  19. flashforge/models/machine_info.py +225 -100
  20. flashforge/models/responses.py +103 -43
  21. flashforge/tcp/__init__.py +30 -17
  22. flashforge/tcp/a3_client.py +408 -0
  23. flashforge/tcp/a4_client.py +300 -0
  24. flashforge/tcp/ff_client.py +93 -90
  25. flashforge/tcp/gcode/__init__.py +4 -2
  26. flashforge/tcp/gcode/a3_gcode_controller.py +109 -0
  27. flashforge/tcp/gcode/gcode_controller.py +48 -36
  28. flashforge/tcp/gcode/gcodes.py +7 -1
  29. flashforge/tcp/parsers/__init__.py +11 -11
  30. flashforge/tcp/parsers/endstop_status.py +103 -132
  31. flashforge/tcp/parsers/location_info.py +26 -13
  32. flashforge/tcp/parsers/print_status.py +49 -56
  33. flashforge/tcp/parsers/printer_info.py +38 -48
  34. flashforge/tcp/parsers/temp_info.py +46 -47
  35. flashforge/tcp/parsers/thumbnail_info.py +65 -40
  36. flashforge/tcp/tcp_client.py +296 -293
  37. flashforge_python_api-1.2.0.dist-info/METADATA +133 -0
  38. flashforge_python_api-1.2.0.dist-info/RECORD +46 -0
  39. {flashforge_python_api-1.0.2.dist-info → flashforge_python_api-1.2.0.dist-info}/WHEEL +1 -1
  40. flashforge_python_api-1.0.2.dist-info/METADATA +0 -284
  41. flashforge_python_api-1.0.2.dist-info/RECORD +0 -43
  42. {flashforge_python_api-1.0.2.dist-info → flashforge_python_api-1.2.0.dist-info}/entry_points.txt +0 -0
  43. {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 ..ff_client import FlashForgeClient
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: 'FlashForgeClient') -> None:
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(self, target_temp: int, cooling: bool = False, timeout: int = 600) -> bool:
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(f"Timeout waiting for bed temperature {'cooling to' if cooling else 'heating to'} {target_temp}°C")
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
  """
@@ -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
- 'PrinterInfo',
17
- 'TempInfo',
18
- 'TempData',
19
- 'LocationInfo',
20
- 'EndstopStatus',
21
- 'MachineStatus',
22
- 'MoveMode',
23
- 'Status',
24
- 'Endstop',
25
- 'PrintStatus',
26
- 'ThumbnailInfo',
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
- input_str: The string to search within
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
- pattern = rf"{key}:(\d+)"
62
- match = re.search(pattern, input_str)
63
- if match and match.group(1):
64
- return int(match.group(1))
65
- return -1
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
- input_str: The string to search within
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
- pattern = rf"{key}:(\d+)"
98
- match = re.search(pattern, input_str)
99
- if match and match.group(1):
100
- return int(match.group(1))
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: Optional[Endstop] = None
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: Optional[Status] = None
92
+ self.status: Status | None = None
93
+ self.filament_status: str = ""
119
94
  self.led_enabled: bool = False
120
- self.current_file: Optional[str] = None
95
+ self.current_file: str | None = None
121
96
 
122
- def from_replay(self, replay: str) -> Optional['EndstopStatus']:
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
- data = replay.split('\n')
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
- # Check if the data looks like a valid M119 response
154
- # Should contain "Endstop" in the second line
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
- # Parse endstop data (line 1)
159
- if len(data) > 1:
160
- self.endstop = Endstop(data[1])
161
-
162
- # Parse machine status (line 2)
163
- if len(data) > 2:
164
- machine_status = data[2].replace("MachineStatus: ", "").strip()
165
- if "BUILDING_FROM_SD" in machine_status:
166
- self.machine_status = MachineStatus.BUILDING_FROM_SD
167
- elif "BUILDING_COMPLETED" in machine_status:
168
- self.machine_status = MachineStatus.BUILDING_COMPLETED
169
- elif "PAUSED" in machine_status:
170
- self.machine_status = MachineStatus.PAUSED
171
- elif "READY" in machine_status:
172
- self.machine_status = MachineStatus.READY
173
- elif "BUSY" in machine_status:
174
- self.machine_status = MachineStatus.BUSY
175
- else:
176
- print(f"EndstopStatus: Encountered unknown MachineStatus: {machine_status}")
177
- self.machine_status = MachineStatus.DEFAULT
178
-
179
- # Parse move mode (line 3)
180
- if len(data) > 3:
181
- move_mode = data[3].replace("MoveMode: ", "").strip()
182
- if "MOVING" in move_mode:
183
- self.move_mode = MoveMode.MOVING
184
- elif "PAUSED" in move_mode:
185
- self.move_mode = MoveMode.PAUSED
186
- elif "READY" in move_mode:
187
- self.move_mode = MoveMode.READY
188
- elif "WAIT_ON_TOOL" in move_mode:
189
- self.move_mode = MoveMode.WAIT_ON_TOOL
190
- elif "HOMING" in move_mode:
191
- self.move_mode = MoveMode.HOMING
192
- else:
193
- print(f"EndstopStatus: Encountered unknown MoveMode: {move_mode}")
194
- self.move_mode = MoveMode.DEFAULT
195
-
196
- # Parse status flags (line 4)
197
- if len(data) > 4:
198
- self.status = Status(data[4])
199
-
200
- # Parse LED status (line 5)
201
- if len(data) > 5:
202
- led_str = data[5].replace("LED: ", "").strip()
203
- try:
204
- self.led_enabled = int(led_str) == 1
205
- except ValueError:
206
- self.led_enabled = False
207
-
208
- # Parse current file (line 6)
209
- if len(data) > 6:
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 (self.move_mode == MoveMode.READY and
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 (self.machine_status == MachineStatus.PAUSED or
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 (f"EndstopStatus(machine={self.machine_status.value}, "
262
- f"move={self.move_mode.value}, led={self.led_enabled}, "
263
- f"file='{self.current_file}')")
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 (f"EndstopStatus("
268
- f"endstop={self.endstop}, "
269
- f"machine_status={self.machine_status}, "
270
- f"move_mode={self.move_mode}, "
271
- f"status={self.status}, "
272
- f"led_enabled={self.led_enabled}, "
273
- f"current_file='{self.current_file}')")
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
+ )