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,68 @@
1
+ """
2
+ Location information parser for FlashForge 3D printers.
3
+
4
+ This module parses the response from M114 command to extract current
5
+ X, Y, Z coordinates of the printer's print head.
6
+ """
7
+
8
+ import logging
9
+ from typing import Optional
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class LocationInfo:
15
+ """
16
+ Represents the current X, Y, and Z coordinates of the printer's print head.
17
+
18
+ This information is typically parsed from the response of an M114 G-code command,
19
+ which reports the current position.
20
+ """
21
+
22
+ def __init__(self) -> None:
23
+ """Initialize empty location info."""
24
+ self.x: str = ""
25
+ """The current X-axis coordinate as a string (e.g., "10.00")."""
26
+
27
+ self.y: str = ""
28
+ """The current Y-axis coordinate as a string (e.g., "20.50")."""
29
+
30
+ self.z: str = ""
31
+ """The current Z-axis coordinate as a string (e.g., "5.25")."""
32
+
33
+ def from_replay(self, replay: str) -> Optional['LocationInfo']:
34
+ """
35
+ Parse a raw string replay from M114 command to populate coordinate info.
36
+
37
+ The parsing logic assumes the replay is a multi-line string where the second line
38
+ (data[1]) contains the coordinate data in a format like "X:10.00 Y:20.50 Z:5.25 ...".
39
+ It splits this line by spaces and then extracts the values for X, Y, and Z by
40
+ removing the prefixes "X:", "Y:", and "Z:".
41
+
42
+ Args:
43
+ replay: The raw multi-line string response from the printer
44
+
45
+ Returns:
46
+ The populated LocationInfo instance, or None if parsing fails
47
+ """
48
+ try:
49
+ data = replay.split('\n')
50
+ # The first line (data[0]) is often the command echo (e.g., "ok M114") or similar,
51
+ # actual coordinate data is expected on the second line.
52
+ loc_data = data[1].split(' ')
53
+ self.x = loc_data[0].replace("X:", "").strip()
54
+ self.y = loc_data[1].replace("Y:", "").strip()
55
+ self.z = loc_data[2].replace("Z:", "").strip()
56
+ return self
57
+ except Exception:
58
+ logger.error("LocationInfo replay has bad/null data")
59
+ return None
60
+
61
+ def __str__(self) -> str:
62
+ """
63
+ Return a string representation of the location information.
64
+
65
+ Returns:
66
+ A string in the format "X: [X_value] Y: [Y_value] Z: [Z_value]"
67
+ """
68
+ return f"X: {self.x} Y: {self.y} Z: {self.z}"
@@ -0,0 +1,182 @@
1
+ """
2
+ FlashForge Python API - Print Status Parser
3
+
4
+ Parses print progress information from M27 command responses.
5
+ """
6
+ from typing import Optional
7
+
8
+
9
+ class PrintStatus:
10
+ """
11
+ Represents the status of an ongoing print job, including SD card byte progress and layer progress.
12
+
13
+ This information is typically parsed from the response of an M27 G-code command,
14
+ which reports the print progress from the SD card.
15
+ """
16
+
17
+ def __init__(self):
18
+ """Initialize a new PrintStatus instance."""
19
+ self.sd_current: str = ""
20
+ self.sd_total: str = ""
21
+ self.layer_current: str = ""
22
+ self.layer_total: str = ""
23
+
24
+ def from_replay(self, replay: str) -> Optional['PrintStatus']:
25
+ """
26
+ Parses a raw string replay (typically from an M27 command) to populate
27
+ the print status properties of this instance.
28
+
29
+ The parsing logic expects a multi-line string:
30
+ - Line 1 (data[0]): Usually a command echo, ignored
31
+ - Line 2 (data[1]): Contains SD card progress, e.g., "SD printing byte 12345/67890"
32
+ It extracts the current and total bytes
33
+ - Line 3 (data[2]): Contains layer progress, e.g., "Layer: 10/250"
34
+ It extracts the current and total layers
35
+
36
+ Args:
37
+ replay: The raw multi-line string response from the printer
38
+
39
+ Returns:
40
+ The populated PrintStatus instance, or None if parsing fails
41
+ """
42
+ if not replay:
43
+ return None
44
+
45
+ try:
46
+ data = replay.split('\n')
47
+
48
+ # Parse SD progress (line 1)
49
+ if len(data) > 1:
50
+ # Example: "SD printing byte 12345/67890"
51
+ sd_progress = data[1].replace("SD printing byte ", "").strip()
52
+ sd_progress_data = sd_progress.split('/')
53
+
54
+ if len(sd_progress_data) >= 2:
55
+ self.sd_current = sd_progress_data[0].strip()
56
+ self.sd_total = sd_progress_data[1].strip()
57
+ else:
58
+ print("PrintStatus: Invalid SD progress format")
59
+ return None
60
+
61
+ # Parse layer progress (line 2)
62
+ if len(data) > 2:
63
+ try:
64
+ # Example: "Layer: 10/250"
65
+ layer_progress = data[2].replace("Layer: ", "").strip()
66
+ except Exception:
67
+ print("PrintStatus: Bad layer progress")
68
+ print(f"Raw printer replay: {replay}")
69
+ return None
70
+
71
+ try:
72
+ lp_data = layer_progress.split('/')
73
+ if len(lp_data) >= 2:
74
+ self.layer_current = lp_data[0].strip()
75
+ self.layer_total = lp_data[1].strip()
76
+ else:
77
+ print("PrintStatus: Invalid layer progress format")
78
+ print(f"layerProgress: {layer_progress}")
79
+ return None
80
+ except Exception:
81
+ print("PrintStatus: Bad layer progress parsing")
82
+ print(f"layerProgress: {layer_progress}")
83
+ return None
84
+
85
+ return self
86
+
87
+ except Exception as e:
88
+ print(f"Error parsing print status: {e}")
89
+ return None
90
+
91
+ def get_print_percent(self) -> float:
92
+ """
93
+ Calculates the print progress percentage based on the current and total layers.
94
+ The result is clamped between 0 and 100.
95
+
96
+ Returns:
97
+ The print progress percentage (0-100), rounded to the nearest integer.
98
+ Returns NaN if layer information is not available or invalid.
99
+ """
100
+ try:
101
+ current_layer = int(self.layer_current)
102
+ total_layers = int(self.layer_total)
103
+
104
+ if total_layers == 0:
105
+ return float('nan')
106
+
107
+ perc = (current_layer / total_layers) * 100
108
+ return round(min(100, max(0, perc))) # Clamp between 0 and 100
109
+
110
+ except (ValueError, TypeError):
111
+ return float('nan')
112
+
113
+ def get_layer_progress(self) -> str:
114
+ """
115
+ Gets the layer progress as a string.
116
+
117
+ Returns:
118
+ A string in the format "currentLayer/totalLayers"
119
+ """
120
+ return f"{self.layer_current}/{self.layer_total}"
121
+
122
+ def get_sd_progress(self) -> str:
123
+ """
124
+ Gets the SD card byte progress as a string.
125
+
126
+ Returns:
127
+ A string in the format "currentBytes/totalBytes"
128
+ """
129
+ return f"{self.sd_current}/{self.sd_total}"
130
+
131
+ def get_sd_percent(self) -> float:
132
+ """
133
+ Calculates the SD card progress percentage based on current and total bytes.
134
+
135
+ Returns:
136
+ The SD progress percentage (0-100), or NaN if data is invalid
137
+ """
138
+ try:
139
+ current_bytes = int(self.sd_current)
140
+ total_bytes = int(self.sd_total)
141
+
142
+ if total_bytes == 0:
143
+ return float('nan')
144
+
145
+ perc = (current_bytes / total_bytes) * 100
146
+ return round(min(100, max(0, perc))) # Clamp between 0 and 100
147
+
148
+ except (ValueError, TypeError):
149
+ return float('nan')
150
+
151
+ def is_complete(self) -> bool:
152
+ """
153
+ Checks if the print is complete based on layer progress.
154
+
155
+ Returns:
156
+ True if current layer equals total layers, False otherwise
157
+ """
158
+ try:
159
+ current = int(self.layer_current)
160
+ total = int(self.layer_total)
161
+ return current >= total and total > 0
162
+ except (ValueError, TypeError):
163
+ return False
164
+
165
+ def __str__(self) -> str:
166
+ """String representation of the print status."""
167
+ layer_perc = self.get_print_percent()
168
+ sd_perc = self.get_sd_percent()
169
+
170
+ layer_str = "nan%" if layer_perc != layer_perc else f"{layer_perc}%" # Check for NaN
171
+ sd_str = "nan%" if sd_perc != sd_perc else f"{sd_perc}%" # Check for NaN
172
+
173
+ return (f"PrintStatus(layer={self.get_layer_progress()} [{layer_str}], "
174
+ f"sd={self.get_sd_progress()} [{sd_str}])")
175
+
176
+ def __repr__(self) -> str:
177
+ """Detailed string representation for debugging."""
178
+ return (f"PrintStatus("
179
+ f"sd_current='{self.sd_current}', "
180
+ f"sd_total='{self.sd_total}', "
181
+ f"layer_current='{self.layer_current}', "
182
+ f"layer_total='{self.layer_total}')")
@@ -0,0 +1,154 @@
1
+ """
2
+ Printer information parser for FlashForge 3D printers.
3
+
4
+ This module parses the response from M115 command to extract printer details
5
+ like model, firmware version, serial number, and capabilities.
6
+ """
7
+
8
+ import logging
9
+ from typing import Optional
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class PrinterInfo:
15
+ """
16
+ Represents general information about the FlashForge 3D printer.
17
+
18
+ This information is typically parsed from the response of an M115 G-code command,
19
+ which provides details about the printer's firmware and capabilities.
20
+ """
21
+
22
+ def __init__(self) -> None:
23
+ """Initialize empty printer info."""
24
+ self.type_name: str = ""
25
+ """The machine type or model name (e.g., "FlashForge Adventurer 5M Pro")."""
26
+
27
+ self.name: str = ""
28
+ """The user-assigned name of the printer."""
29
+
30
+ self.firmware_version: str = ""
31
+ """The firmware version currently installed on the printer."""
32
+
33
+ self.serial_number: str = ""
34
+ """The unique serial number of the printer."""
35
+
36
+ self.dimensions: str = ""
37
+ """The build dimensions of the printer (e.g., "X:220 Y:220 Z:220")."""
38
+
39
+ self.mac_address: str = ""
40
+ """The MAC address of the printer's network interface."""
41
+
42
+ self.tool_count: str = ""
43
+ """The number of tools (extruders) the printer has."""
44
+
45
+ def from_replay(self, replay: str) -> Optional['PrinterInfo']:
46
+ """
47
+ Parse a raw string replay from M115 command to populate printer info.
48
+
49
+ The M115 response is expected to be a multi-line string where each line
50
+ provides a piece of information in a "Key: Value" format.
51
+
52
+ Expected format:
53
+ - Line 1: Command echo/header (ignored)
54
+ - Line 2: "Machine Type: [TypeName]"
55
+ - Line 3: "Machine Name: [Name]"
56
+ - Line 4: "Firmware: [FirmwareVersion]"
57
+ - Line 5: "SN: [SerialNumber]"
58
+ - Line 6: Dimensions string (e.g., "X:220 Y:220 Z:220")
59
+ - Line 7: "Tool count: [ToolCount]"
60
+ - Line 8: "Mac Address:[MacAddress]"
61
+
62
+ Args:
63
+ replay: The raw multi-line string response from the M115 command
64
+
65
+ Returns:
66
+ The populated PrinterInfo instance, or None if parsing fails
67
+ """
68
+ if not replay:
69
+ return None
70
+
71
+ try:
72
+ data = replay.split('\n')
73
+
74
+ # Parse machine type (line 2)
75
+ name = self._get_right(data[1]) # Expected: "Machine Type: Adventurer 5M Pro"
76
+ if name is None:
77
+ logger.error("PrinterInfo replay has null Machine Type")
78
+ return None
79
+ self.type_name = name
80
+
81
+ # Parse machine name (line 3)
82
+ nick = self._get_right(data[2]) # Expected: "Machine Name: MyPrinter"
83
+ if nick is None:
84
+ logger.error("PrinterInfo replay has null Machine Name")
85
+ return None
86
+ self.name = nick
87
+
88
+ # Parse firmware version (line 4)
89
+ fw = self._get_right(data[3]) # Expected: "Firmware: V1.2.3"
90
+ if fw is None:
91
+ logger.error("PrinterInfo replay has null firmware version")
92
+ return None
93
+ self.firmware_version = fw
94
+
95
+ # Parse serial number (line 5)
96
+ sn = self._get_right(data[4]) # Expected: "SN: SN12345"
97
+ if sn is None:
98
+ logger.error("PrinterInfo replay has null serial number")
99
+ return None
100
+ self.serial_number = sn
101
+
102
+ # Parse dimensions (line 6) - direct string
103
+ if len(data) > 5:
104
+ self.dimensions = data[5].strip() # Expected: "X:220 Y:220 Z:220"
105
+
106
+ # Parse tool count (line 7)
107
+ if len(data) > 6:
108
+ tcs = self._get_right(data[6]) # Expected: "Tool count: 1"
109
+ if tcs is None:
110
+ logger.error("PrinterInfo replay has null tool count")
111
+ return None
112
+ self.tool_count = tcs
113
+
114
+ # Parse MAC address (line 8)
115
+ if len(data) > 7:
116
+ self.mac_address = data[7].replace("Mac Address:", "").strip()
117
+
118
+ return self
119
+
120
+ except Exception as e:
121
+ logger.error(f"Error creating PrinterInfo instance from replay: {e}")
122
+ return None
123
+
124
+ def _get_right(self, rp_data: str) -> Optional[str]:
125
+ """
126
+ Helper function to extract the value part of a "Key: Value" string.
127
+
128
+ Args:
129
+ rp_data: The input string (e.g., "Machine Type: Adventurer 5M Pro")
130
+
131
+ Returns:
132
+ The extracted value string (e.g., "Adventurer 5M Pro"), or None if parsing fails
133
+ """
134
+ try:
135
+ return rp_data.split(':', 1)[1].strip()
136
+ except (IndexError, AttributeError):
137
+ return None
138
+
139
+ def __str__(self) -> str:
140
+ """
141
+ Return a string representation of the printer information.
142
+
143
+ Returns:
144
+ A multi-line string detailing the printer's properties
145
+ """
146
+ return (
147
+ f"Printer Type: {self.type_name}\n"
148
+ f"Name: {self.name}\n"
149
+ f"Firmware: {self.firmware_version}\n"
150
+ f"Serial Number: {self.serial_number}\n"
151
+ f"Print Dimensions: {self.dimensions}\n"
152
+ f"Tool Count: {self.tool_count}\n"
153
+ f"MAC Address: {self.mac_address}"
154
+ )
@@ -0,0 +1,217 @@
1
+ """
2
+ Temperature information parser for FlashForge 3D printers.
3
+
4
+ This module parses the response from M105 command to extract temperature data
5
+ for the extruder and print bed.
6
+ """
7
+
8
+ import logging
9
+ from typing import Optional
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class TempData:
15
+ """
16
+ Represents temperature data for a single component (e.g., extruder or bed).
17
+
18
+ Includes current temperature and target (set) temperature.
19
+ Temperatures are stored as strings but can be retrieved as numbers.
20
+ """
21
+
22
+ def __init__(self, data: str) -> None:
23
+ """
24
+ Create a TempData instance by parsing a temperature string.
25
+
26
+ The input string can be in the format "current/set" (e.g., "210/210")
27
+ or just "current" (e.g., "25") if the target temperature is not specified.
28
+ It also handles and removes a trailing "/0.0" if present from some printer firmwares.
29
+ All temperatures are rounded to the nearest integer.
30
+
31
+ Args:
32
+ data: The temperature data string (e.g., "210/210", "25", "60/60/0.0")
33
+ """
34
+ # Handle potential formatting issues by removing any non-relevant part
35
+ data = data.replace('/0.0', '') # Remove trailing '/0.0' if exists
36
+
37
+ if "/" in data:
38
+ # Replay has current/set temps
39
+ split_temps = data.split('/')
40
+ self._current = self._parse_temp_data(split_temps[0].strip())
41
+ self._set = self._parse_temp_data(split_temps[1].strip()) if len(split_temps) > 1 else None
42
+ else:
43
+ # Replay only has current temp (when printer is idle)
44
+ self._current = self._parse_temp_data(data)
45
+ self._set = None
46
+
47
+ def _parse_temp_data(self, data: str) -> str:
48
+ """
49
+ Parse a raw temperature string value, round it, and return as string.
50
+
51
+ Args:
52
+ data: The raw temperature string (e.g., "210.5", "60")
53
+
54
+ Returns:
55
+ The rounded temperature as a string
56
+ """
57
+ if "." in data:
58
+ data = data.split('.')[0].strip() # Truncate decimal part before rounding
59
+ temp = round(float(data))
60
+ return str(temp)
61
+
62
+ def get_full(self) -> str:
63
+ """
64
+ Get the full temperature string, including current and set temperatures.
65
+
66
+ Returns:
67
+ A string in the format "current/set" or just "current" if set temperature is not available
68
+ """
69
+ if self._set is None:
70
+ return self._current
71
+ return f"{self._current}/{self._set}"
72
+
73
+ def get_current(self) -> int:
74
+ """
75
+ Get the current temperature as a number.
76
+
77
+ Returns:
78
+ The current temperature in Celsius
79
+ """
80
+ return int(self._current)
81
+
82
+ def get_set(self) -> int:
83
+ """
84
+ Get the target (set) temperature as a number.
85
+
86
+ Returns:
87
+ The set temperature in Celsius, or 0 if not set
88
+ """
89
+ return int(self._set) if self._set else 0
90
+
91
+
92
+ class TempInfo:
93
+ """
94
+ Represents the temperature information for the printer's extruder and bed.
95
+
96
+ This data is typically parsed from the response of an M105 G-code command,
97
+ which reports the current and target temperatures.
98
+ """
99
+
100
+ def __init__(self) -> None:
101
+ """Initialize empty temperature info."""
102
+ self._extruder_temp: Optional[TempData] = None
103
+ """Temperature data for the extruder."""
104
+
105
+ self._bed_temp: Optional[TempData] = None
106
+ """Temperature data for the print bed."""
107
+
108
+ def from_replay(self, replay: str) -> Optional['TempInfo']:
109
+ """
110
+ Parse a raw string replay from M105 command to populate temperature info.
111
+
112
+ The M105 response format is usually a single line (after the "ok" or command echo)
113
+ containing temperature segments like "T0:25/0" or "T:210/210 B:60/60".
114
+ This method splits the relevant line by spaces and then parses each segment.
115
+ It looks for segments starting with "T0:", "T):", or "T:" for extruder temperature,
116
+ and "B:" for bed temperature.
117
+
118
+ Args:
119
+ replay: The raw multi-line string response from the printer
120
+
121
+ Returns:
122
+ The populated TempInfo instance, or None if parsing fails
123
+ """
124
+ if not replay:
125
+ return None
126
+
127
+ try:
128
+ data = replay.split('\n')
129
+ if len(data) <= 1:
130
+ logger.error(f"TempInfo replay has invalid data: {data}")
131
+ return None
132
+
133
+ # Relevant temperature data is usually on the second line (data[1])
134
+ # e.g., "T0:25/0 B:28/0 @:0 B@:0" or "T:210/210 B:60/60"
135
+ temp_data = data[1].split(' ')
136
+ extruder_data_str = None
137
+ bed_data_str = None
138
+
139
+ # Parse each temperature segment
140
+ for segment in temp_data:
141
+ # Check for extruder temperature (T0, T, or T) for some printers)
142
+ if segment.startswith('T0:'):
143
+ extruder_data_str = segment.replace('T0:', '')
144
+ elif segment.startswith('T):'): # Some printers might use T):
145
+ extruder_data_str = segment.replace('T):', '')
146
+ elif segment.startswith('T:'): # General case for T:
147
+ extruder_data_str = segment.replace('T:', '')
148
+ # Check for bed temperature
149
+ elif segment.startswith('B:'):
150
+ bed_data_str = segment.replace('B:', '')
151
+
152
+ # If we found extruder data, create TempData object
153
+ if extruder_data_str:
154
+ self._extruder_temp = TempData(extruder_data_str)
155
+ else:
156
+ logger.error(f"No extruder temperature found in replay data: {replay}")
157
+ return None # Extruder temp is critical
158
+
159
+ # If we found bed data, create TempData object; otherwise, default to 0/0
160
+ if bed_data_str:
161
+ self._bed_temp = TempData(bed_data_str)
162
+ else:
163
+ logger.warning(f"No bed temperature found in replay data, defaulting to 0/0: {replay}")
164
+ self._bed_temp = TempData('0/0') # Default if not present
165
+
166
+ return self
167
+
168
+ except Exception as e:
169
+ logger.error(f"Unable to create TempInfo instance from replay: {e}")
170
+ logger.error(f"Raw replay data: {replay}")
171
+ return None
172
+
173
+ def get_extruder_temp(self) -> Optional[TempData]:
174
+ """
175
+ Get the extruder temperature data.
176
+
177
+ Returns:
178
+ A TempData object for the extruder, or None if not available
179
+ """
180
+ return self._extruder_temp
181
+
182
+ def get_bed_temp(self) -> Optional[TempData]:
183
+ """
184
+ Get the print bed temperature data.
185
+
186
+ Returns:
187
+ A TempData object for the bed, or None if not available
188
+ """
189
+ return self._bed_temp
190
+
191
+ def is_cooled(self) -> bool:
192
+ """
193
+ Check if both the bed and extruder are cooled down to relatively low temperatures.
194
+
195
+ Bed temperature <= 40°C and extruder temperature <= 200°C (though 200 is still hot).
196
+ Use with caution, as "cooled" here is relative and 200C is still very hot for an extruder.
197
+
198
+ Returns:
199
+ True if temperatures are at or below the defined thresholds, False otherwise
200
+ """
201
+ bed_temp = self._bed_temp.get_current() if self._bed_temp else 0
202
+ extruder_temp = self._extruder_temp.get_current() if self._extruder_temp else 0
203
+ return bed_temp <= 40 and extruder_temp <= 200
204
+
205
+ def are_temps_safe(self) -> bool:
206
+ """
207
+ Check if the current temperatures are within a generally safe operating range.
208
+
209
+ Prevents overheating (extruder < 250°C, bed < 100°C).
210
+ These are arbitrary "safe" limits and might need adjustment based on specific printer/material.
211
+
212
+ Returns:
213
+ True if temperatures are below the defined "safe" thresholds, False otherwise
214
+ """
215
+ bed_temp = self._bed_temp.get_current() if self._bed_temp else 0
216
+ extruder_temp = self._extruder_temp.get_current() if self._extruder_temp else 0
217
+ return extruder_temp < 250 and bed_temp < 100