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.
- flashforge/__init__.py +188 -0
- flashforge/api/__init__.py +17 -0
- flashforge/api/constants/__init__.py +10 -0
- flashforge/api/constants/commands.py +10 -0
- flashforge/api/constants/endpoints.py +11 -0
- flashforge/api/controls/__init__.py +16 -0
- flashforge/api/controls/control.py +357 -0
- flashforge/api/controls/files.py +171 -0
- flashforge/api/controls/info.py +292 -0
- flashforge/api/controls/job_control.py +561 -0
- flashforge/api/controls/temp_control.py +89 -0
- flashforge/api/filament/__init__.py +7 -0
- flashforge/api/filament/filament.py +39 -0
- flashforge/api/misc/__init__.py +8 -0
- flashforge/api/misc/scientific_notation.py +28 -0
- flashforge/api/misc/temperature.py +42 -0
- flashforge/api/network/__init__.py +10 -0
- flashforge/api/network/fnet_code.py +15 -0
- flashforge/api/network/utils.py +55 -0
- flashforge/client.py +381 -0
- flashforge/discovery/__init__.py +12 -0
- flashforge/discovery/discovery.py +388 -0
- flashforge/models/__init__.py +50 -0
- flashforge/models/machine_info.py +247 -0
- flashforge/models/responses.py +123 -0
- flashforge/tcp/__init__.py +41 -0
- flashforge/tcp/ff_client.py +587 -0
- flashforge/tcp/gcode/__init__.py +11 -0
- flashforge/tcp/gcode/gcode_controller.py +296 -0
- flashforge/tcp/gcode/gcodes.py +93 -0
- flashforge/tcp/parsers/__init__.py +27 -0
- flashforge/tcp/parsers/endstop_status.py +273 -0
- flashforge/tcp/parsers/location_info.py +68 -0
- flashforge/tcp/parsers/print_status.py +182 -0
- flashforge/tcp/parsers/printer_info.py +154 -0
- flashforge/tcp/parsers/temp_info.py +217 -0
- flashforge/tcp/parsers/thumbnail_info.py +242 -0
- flashforge/tcp/tcp_client.py +448 -0
- flashforge_python_api-1.0.0.dist-info/METADATA +123 -0
- flashforge_python_api-1.0.0.dist-info/RECORD +43 -0
- flashforge_python_api-1.0.0.dist-info/WHEEL +4 -0
- flashforge_python_api-1.0.0.dist-info/entry_points.txt +2 -0
- flashforge_python_api-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FlashForge Python API - Files Module
|
|
3
|
+
"""
|
|
4
|
+
import base64
|
|
5
|
+
from typing import TYPE_CHECKING, List, Optional
|
|
6
|
+
|
|
7
|
+
import aiohttp
|
|
8
|
+
|
|
9
|
+
from ...models.machine_info import FFGcodeFileEntry
|
|
10
|
+
from ...models.responses import GenericResponse, GCodeListResponse, ThumbnailResponse
|
|
11
|
+
from ..constants.endpoints import Endpoints
|
|
12
|
+
from ..network.utils import NetworkUtils
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from ...client import FlashForgeClient
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Files:
|
|
19
|
+
"""
|
|
20
|
+
Provides methods for managing files on the FlashForge 3D printer.
|
|
21
|
+
This includes retrieving file lists and thumbnails.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, client: "FlashForgeClient"):
|
|
25
|
+
"""
|
|
26
|
+
Creates an instance of the Files class.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
client: The FlashForgeClient instance used for communication with the printer.
|
|
30
|
+
"""
|
|
31
|
+
self.client = client
|
|
32
|
+
|
|
33
|
+
async def get_file_list(self) -> List[str]:
|
|
34
|
+
"""
|
|
35
|
+
Retrieves a list of files stored locally on the printer.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
A list of file names, or empty list if retrieval fails.
|
|
39
|
+
"""
|
|
40
|
+
# This method uses the TCP client to get file list
|
|
41
|
+
if hasattr(self.client, 'tcp_client') and self.client.tcp_client:
|
|
42
|
+
return await self.client.tcp_client.get_file_list_async()
|
|
43
|
+
return []
|
|
44
|
+
|
|
45
|
+
async def get_local_file_list(self) -> List[str]:
|
|
46
|
+
"""
|
|
47
|
+
Retrieves a list of files stored locally on the printer.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
A list of file names, or empty list if retrieval fails.
|
|
51
|
+
"""
|
|
52
|
+
return await self.get_file_list()
|
|
53
|
+
|
|
54
|
+
async def get_recent_file_list(self) -> List[FFGcodeFileEntry]:
|
|
55
|
+
"""
|
|
56
|
+
Retrieves a list of the 10 most recently printed files from the printer's API.
|
|
57
|
+
For AD5X and newer printers, returns detailed file entries with material info.
|
|
58
|
+
For older printers, returns basic file entries with normalized data.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
A list of FFGcodeFileEntry objects. Returns an empty list if the request fails or an error occurs.
|
|
62
|
+
"""
|
|
63
|
+
payload = {
|
|
64
|
+
"serialNumber": self.client.serial_number,
|
|
65
|
+
"checkCode": self.client.check_code
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
async with aiohttp.ClientSession() as session:
|
|
70
|
+
async with session.post(
|
|
71
|
+
self.client.get_endpoint(Endpoints.GCODE_LIST),
|
|
72
|
+
json=payload,
|
|
73
|
+
headers={"Content-Type": "application/json"}
|
|
74
|
+
) as response:
|
|
75
|
+
if response.status != 200:
|
|
76
|
+
return []
|
|
77
|
+
|
|
78
|
+
# Fix for FlashForge printer's malformed Content-Type header
|
|
79
|
+
# Some printers return "appliation/json" instead of "application/json"
|
|
80
|
+
try:
|
|
81
|
+
data = await response.json()
|
|
82
|
+
except aiohttp.ContentTypeError:
|
|
83
|
+
# Fallback: manually parse as JSON if Content-Type is malformed
|
|
84
|
+
text = await response.text()
|
|
85
|
+
import json
|
|
86
|
+
data = json.loads(text)
|
|
87
|
+
|
|
88
|
+
if not NetworkUtils.is_ok(data):
|
|
89
|
+
print(f"Error retrieving file list: {NetworkUtils.get_error_message(data)}")
|
|
90
|
+
return []
|
|
91
|
+
|
|
92
|
+
# Parse the response using GCodeListResponse
|
|
93
|
+
result = GCodeListResponse(**data)
|
|
94
|
+
|
|
95
|
+
# AD5X and newer printers provide detailed info in gcodeListDetail
|
|
96
|
+
if result.gcode_list_detail and len(result.gcode_list_detail) > 0:
|
|
97
|
+
return result.gcode_list_detail
|
|
98
|
+
|
|
99
|
+
# Fallback for older printers using gcodeList
|
|
100
|
+
if result.gcode_list and len(result.gcode_list) > 0:
|
|
101
|
+
# Check if it's a list of strings or already FFGcodeFileEntry objects
|
|
102
|
+
first_item = result.gcode_list[0]
|
|
103
|
+
|
|
104
|
+
if isinstance(first_item, str):
|
|
105
|
+
# Convert string array to FFGcodeFileEntry objects
|
|
106
|
+
return [
|
|
107
|
+
FFGcodeFileEntry(
|
|
108
|
+
gcode_file_name=file_name,
|
|
109
|
+
printing_time=0
|
|
110
|
+
)
|
|
111
|
+
for file_name in result.gcode_list
|
|
112
|
+
]
|
|
113
|
+
else:
|
|
114
|
+
# Already FFGcodeFileEntry objects
|
|
115
|
+
return result.gcode_list
|
|
116
|
+
|
|
117
|
+
return []
|
|
118
|
+
|
|
119
|
+
except Exception as err:
|
|
120
|
+
print(f"GetRecentFileList error: {err}")
|
|
121
|
+
return []
|
|
122
|
+
|
|
123
|
+
async def get_gcode_thumbnail(self, file_name: str) -> Optional[bytes]:
|
|
124
|
+
"""
|
|
125
|
+
Retrieves the thumbnail image for a specified G-code file.
|
|
126
|
+
The image data is returned as bytes.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
file_name: The name of the G-code file (e.g., "my_print.gcode") for which to retrieve the thumbnail.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
Bytes containing the thumbnail image data (decoded from base64),
|
|
133
|
+
or None if the request fails, the file has no thumbnail, or an error occurs.
|
|
134
|
+
"""
|
|
135
|
+
payload = {
|
|
136
|
+
"serialNumber": self.client.serial_number,
|
|
137
|
+
"checkCode": self.client.check_code,
|
|
138
|
+
"fileName": file_name
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
async with aiohttp.ClientSession() as session:
|
|
143
|
+
async with session.post(
|
|
144
|
+
self.client.get_endpoint(Endpoints.GCODE_THUMB),
|
|
145
|
+
json=payload,
|
|
146
|
+
headers={"Content-Type": "application/json"}
|
|
147
|
+
) as response:
|
|
148
|
+
if response.status != 200:
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
# Fix for FlashForge printer's malformed Content-Type header
|
|
152
|
+
# Some printers return "appliation/json" instead of "application/json"
|
|
153
|
+
try:
|
|
154
|
+
data = await response.json()
|
|
155
|
+
except aiohttp.ContentTypeError:
|
|
156
|
+
# Fallback: manually parse as JSON if Content-Type is malformed
|
|
157
|
+
text = await response.text()
|
|
158
|
+
import json
|
|
159
|
+
data = json.loads(text)
|
|
160
|
+
|
|
161
|
+
if NetworkUtils.is_ok(data):
|
|
162
|
+
# Parse response and return decoded image bytes
|
|
163
|
+
result = ThumbnailResponse(**data)
|
|
164
|
+
return base64.b64decode(result.image_data)
|
|
165
|
+
else:
|
|
166
|
+
print(f"Error retrieving thumbnail: {NetworkUtils.get_error_message(data)}")
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
except Exception as err:
|
|
170
|
+
print(f"GetGcodeThumbnail error: {err}")
|
|
171
|
+
return None
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FlashForge Python API - Info Module
|
|
3
|
+
"""
|
|
4
|
+
from typing import TYPE_CHECKING, Optional
|
|
5
|
+
|
|
6
|
+
import aiohttp
|
|
7
|
+
|
|
8
|
+
from ...models.machine_info import FFMachineInfo, MachineState
|
|
9
|
+
from ...models.responses import DetailResponse
|
|
10
|
+
from ..constants.endpoints import Endpoints
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from ...client import FlashForgeClient
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MachineInfoParser:
|
|
17
|
+
"""
|
|
18
|
+
Transforms printer detail data from the API response format into a structured FFMachineInfo object.
|
|
19
|
+
This class centralizes the logic for mapping and calculating various properties of the printer's state
|
|
20
|
+
and capabilities based on the raw data received from the printer.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def from_detail(detail) -> Optional[FFMachineInfo]:
|
|
25
|
+
"""
|
|
26
|
+
Converts printer details from the API response format to our internal FFMachineInfo model.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
detail: The FFPrinterDetail object received from the printer's API.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
An FFMachineInfo object containing structured and formatted printer information,
|
|
33
|
+
or None if the input detail is None or an error occurs during processing.
|
|
34
|
+
"""
|
|
35
|
+
if not detail:
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
# Helper function to format time from seconds
|
|
40
|
+
def format_time_from_seconds(seconds: int) -> str:
|
|
41
|
+
try:
|
|
42
|
+
valid_seconds = seconds if isinstance(seconds, int) else 0
|
|
43
|
+
hours = valid_seconds // 3600
|
|
44
|
+
minutes = (valid_seconds % 3600) // 60
|
|
45
|
+
return f"{hours:02d}:{minutes:02d}"
|
|
46
|
+
except Exception:
|
|
47
|
+
return "00:00"
|
|
48
|
+
|
|
49
|
+
# Helper function to get machine state
|
|
50
|
+
def get_machine_state(status: str) -> MachineState:
|
|
51
|
+
valid_status = status.lower() if isinstance(status, str) else ""
|
|
52
|
+
state_mapping = {
|
|
53
|
+
"ready": MachineState.READY,
|
|
54
|
+
"busy": MachineState.BUSY,
|
|
55
|
+
"calibrate_doing": MachineState.CALIBRATING,
|
|
56
|
+
"error": MachineState.ERROR,
|
|
57
|
+
"heating": MachineState.HEATING,
|
|
58
|
+
"printing": MachineState.PRINTING,
|
|
59
|
+
"pausing": MachineState.PAUSING,
|
|
60
|
+
"paused": MachineState.PAUSED,
|
|
61
|
+
"cancel": MachineState.CANCELLED,
|
|
62
|
+
"completed": MachineState.COMPLETED,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if valid_status in state_mapping:
|
|
66
|
+
return state_mapping[valid_status]
|
|
67
|
+
|
|
68
|
+
if valid_status:
|
|
69
|
+
print(f"Unknown machine status received: '{status}'")
|
|
70
|
+
return MachineState.UNKNOWN
|
|
71
|
+
|
|
72
|
+
# Calculate derived values
|
|
73
|
+
print_eta = format_time_from_seconds(getattr(detail, "estimated_time", 0) or 0)
|
|
74
|
+
formatted_run_time = format_time_from_seconds(getattr(detail, "print_duration", 0) or 0)
|
|
75
|
+
|
|
76
|
+
total_minutes = getattr(detail, "cumulative_print_time", 0) or 0
|
|
77
|
+
hours = total_minutes // 60
|
|
78
|
+
minutes = total_minutes % 60
|
|
79
|
+
formatted_total_run_time = f"{hours}h:{minutes}m"
|
|
80
|
+
|
|
81
|
+
# Boolean status conversions
|
|
82
|
+
auto_shutdown = (getattr(detail, "auto_shutdown", "") or "") == "open"
|
|
83
|
+
door_open = (getattr(detail, "door_status", "") or "") == "open"
|
|
84
|
+
external_fan_on = (getattr(detail, "external_fan_status", "") or "") == "open"
|
|
85
|
+
internal_fan_on = (getattr(detail, "internal_fan_status", "") or "") == "open"
|
|
86
|
+
lights_on = (getattr(detail, "light_status", "") or "") == "open"
|
|
87
|
+
|
|
88
|
+
# Calculate filament estimates
|
|
89
|
+
total_job_filament_meters = (getattr(detail, "estimated_right_len", 0) or 0) / 1000.0
|
|
90
|
+
print_progress = getattr(detail, "print_progress", 0) or 0
|
|
91
|
+
est_length = total_job_filament_meters * print_progress
|
|
92
|
+
est_weight = (getattr(detail, "estimated_right_weight", 0) or 0) * print_progress
|
|
93
|
+
|
|
94
|
+
# Build the FFMachineInfo object
|
|
95
|
+
machine_info = FFMachineInfo(
|
|
96
|
+
# Auto-shutdown settings
|
|
97
|
+
auto_shutdown=auto_shutdown,
|
|
98
|
+
auto_shutdown_time=getattr(detail, "auto_shutdown_time", 0) or 0,
|
|
99
|
+
|
|
100
|
+
# Camera
|
|
101
|
+
camera_stream_url=getattr(detail, "camera_stream_url", "") or "",
|
|
102
|
+
|
|
103
|
+
# Fan speeds
|
|
104
|
+
chamber_fan_speed=getattr(detail, "chamber_fan_speed", 0) or 0,
|
|
105
|
+
cooling_fan_speed=getattr(detail, "cooling_fan_speed", 0) or 0,
|
|
106
|
+
cooling_fan_left_speed=getattr(detail, "cooling_fan_left_speed", None),
|
|
107
|
+
|
|
108
|
+
# Cumulative stats
|
|
109
|
+
cumulative_filament=getattr(detail, "cumulative_filament", 0) or 0,
|
|
110
|
+
cumulative_print_time=getattr(detail, "cumulative_print_time", 0) or 0,
|
|
111
|
+
|
|
112
|
+
# Current print speed
|
|
113
|
+
current_print_speed=getattr(detail, "current_print_speed", 0) or 0,
|
|
114
|
+
|
|
115
|
+
# Disk space
|
|
116
|
+
free_disk_space=f"{(getattr(detail, 'remaining_disk_space', 0) or 0):.2f}",
|
|
117
|
+
|
|
118
|
+
# Door and error status
|
|
119
|
+
door_open=door_open,
|
|
120
|
+
error_code=getattr(detail, "error_code", "") or "",
|
|
121
|
+
|
|
122
|
+
# Current print estimates
|
|
123
|
+
est_length=est_length,
|
|
124
|
+
est_weight=est_weight,
|
|
125
|
+
estimated_time=getattr(detail, "estimated_time", 0) or 0,
|
|
126
|
+
|
|
127
|
+
# Fans & LED status
|
|
128
|
+
external_fan_on=external_fan_on,
|
|
129
|
+
internal_fan_on=internal_fan_on,
|
|
130
|
+
lights_on=lights_on,
|
|
131
|
+
|
|
132
|
+
# Network
|
|
133
|
+
ip_address=getattr(detail, "ip_addr", "") or "",
|
|
134
|
+
mac_address=getattr(detail, "mac_addr", "") or "",
|
|
135
|
+
|
|
136
|
+
# Print settings
|
|
137
|
+
fill_amount=getattr(detail, "fill_amount", 0) or 0,
|
|
138
|
+
firmware_version=getattr(detail, "firmware_version", "") or "",
|
|
139
|
+
name=getattr(detail, "name", "") or "",
|
|
140
|
+
is_pro="Pro" in (getattr(detail, "name", "") or ""),
|
|
141
|
+
is_ad5x="AD5X" in (getattr(detail, "name", "") or "").upper(),
|
|
142
|
+
nozzle_size=getattr(detail, "nozzle_model", "") or "",
|
|
143
|
+
|
|
144
|
+
# Temperatures
|
|
145
|
+
print_bed={
|
|
146
|
+
"current": getattr(detail, "plat_temp", 0) or 0,
|
|
147
|
+
"set": getattr(detail, "plat_target_temp", 0) or 0
|
|
148
|
+
},
|
|
149
|
+
extruder={
|
|
150
|
+
"current": getattr(detail, "right_temp", 0) or 0,
|
|
151
|
+
"set": getattr(detail, "right_target_temp", 0) or 0
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
# Current print stats
|
|
155
|
+
print_duration=getattr(detail, "print_duration", 0) or 0,
|
|
156
|
+
print_file_name=getattr(detail, "print_file_name", "") or "",
|
|
157
|
+
print_file_thumb_url=getattr(detail, "print_file_thumb_url", "") or "",
|
|
158
|
+
current_print_layer=getattr(detail, "print_layer", 0) or 0,
|
|
159
|
+
print_progress=print_progress,
|
|
160
|
+
print_progress_int=int(print_progress * 100),
|
|
161
|
+
print_speed_adjust=getattr(detail, "print_speed_adjust", 0) or 0,
|
|
162
|
+
filament_type=getattr(detail, "right_filament_type", "") or "",
|
|
163
|
+
|
|
164
|
+
# Machine state
|
|
165
|
+
machine_state=get_machine_state(getattr(detail, "status", "") or ""),
|
|
166
|
+
status=getattr(detail, "status", "") or "",
|
|
167
|
+
total_print_layers=getattr(detail, "target_print_layer", 0) or 0,
|
|
168
|
+
tvoc=getattr(detail, "tvoc", 0) or 0,
|
|
169
|
+
z_axis_compensation=getattr(detail, "z_axis_compensation", 0) or 0,
|
|
170
|
+
|
|
171
|
+
# Cloud codes
|
|
172
|
+
flash_cloud_register_code=getattr(detail, "flash_register_code", "") or "",
|
|
173
|
+
polar_cloud_register_code=getattr(detail, "polar_register_code", "") or "",
|
|
174
|
+
|
|
175
|
+
# Extras
|
|
176
|
+
print_eta=print_eta,
|
|
177
|
+
formatted_run_time=formatted_run_time,
|
|
178
|
+
formatted_total_run_time=formatted_total_run_time,
|
|
179
|
+
|
|
180
|
+
# AD5X Material Station
|
|
181
|
+
has_matl_station=getattr(detail, "has_matl_station", None),
|
|
182
|
+
matl_station_info=getattr(detail, "matl_station_info", None),
|
|
183
|
+
indep_matl_info=getattr(detail, "indep_matl_info", None),
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
return machine_info
|
|
187
|
+
|
|
188
|
+
except Exception as error:
|
|
189
|
+
print(f"Error in MachineInfoParser.from_detail: {error}")
|
|
190
|
+
print(f"Detail object causing error: {detail}")
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class Info:
|
|
195
|
+
"""
|
|
196
|
+
Provides methods for retrieving various information and status details from the FlashForge 3D printer.
|
|
197
|
+
This includes general machine information, printing status, and raw detail responses.
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
def __init__(self, client: "FlashForgeClient"):
|
|
201
|
+
"""
|
|
202
|
+
Creates an instance of the Info class.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
client: The FlashForgeClient instance used for communication with the printer.
|
|
206
|
+
"""
|
|
207
|
+
self.client = client
|
|
208
|
+
|
|
209
|
+
async def get(self) -> Optional[FFMachineInfo]:
|
|
210
|
+
"""
|
|
211
|
+
Retrieves comprehensive machine information, processed into the FFMachineInfo model.
|
|
212
|
+
This method fetches detailed data from the printer and transforms it.
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
An FFMachineInfo object, or None if an error occurs or no data is returned.
|
|
216
|
+
"""
|
|
217
|
+
detail_response = await self.get_detail_response()
|
|
218
|
+
if detail_response and detail_response.detail:
|
|
219
|
+
return MachineInfoParser.from_detail(detail_response.detail)
|
|
220
|
+
return None
|
|
221
|
+
|
|
222
|
+
async def is_printing(self) -> bool:
|
|
223
|
+
"""
|
|
224
|
+
Checks if the printer is currently in the "printing" state.
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
True if the printer is printing, False otherwise or if status cannot be determined.
|
|
228
|
+
"""
|
|
229
|
+
info = await self.get()
|
|
230
|
+
return info.status == "printing" if info else False
|
|
231
|
+
|
|
232
|
+
async def get_status(self) -> Optional[str]:
|
|
233
|
+
"""
|
|
234
|
+
Retrieves the raw status string of the printer (e.g., "ready", "printing", "error").
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
The status string, or None if it cannot be determined.
|
|
238
|
+
"""
|
|
239
|
+
info = await self.get()
|
|
240
|
+
return info.status if info else None
|
|
241
|
+
|
|
242
|
+
async def get_machine_state(self) -> Optional[MachineState]:
|
|
243
|
+
"""
|
|
244
|
+
Retrieves the machine state as a MachineState enum value.
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
A MachineState enum value, or None if it cannot be determined.
|
|
248
|
+
"""
|
|
249
|
+
info = await self.get()
|
|
250
|
+
return info.machine_state if info else None
|
|
251
|
+
|
|
252
|
+
async def get_detail_response(self) -> Optional[DetailResponse]:
|
|
253
|
+
"""
|
|
254
|
+
Retrieves the raw detailed response from the printer's detail endpoint.
|
|
255
|
+
This contains a wealth of information about the printer's current state.
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
A DetailResponse object containing the raw printer details,
|
|
259
|
+
or None if the request fails or an error occurs.
|
|
260
|
+
"""
|
|
261
|
+
payload = {
|
|
262
|
+
"serialNumber": self.client.serial_number,
|
|
263
|
+
"checkCode": self.client.check_code
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
try:
|
|
267
|
+
async with aiohttp.ClientSession() as session:
|
|
268
|
+
async with session.post(
|
|
269
|
+
self.client.get_endpoint(Endpoints.DETAIL),
|
|
270
|
+
json=payload,
|
|
271
|
+
headers={"Content-Type": "application/json"}
|
|
272
|
+
) as response:
|
|
273
|
+
if response.status != 200:
|
|
274
|
+
print(f"Non-200 status from detail endpoint: {response.status}")
|
|
275
|
+
return None
|
|
276
|
+
|
|
277
|
+
# Fix for FlashForge printer's malformed Content-Type header
|
|
278
|
+
# Some printers return "appliation/json" instead of "application/json"
|
|
279
|
+
# We'll manually parse the text as JSON to bypass Content-Type validation
|
|
280
|
+
try:
|
|
281
|
+
data = await response.json()
|
|
282
|
+
except aiohttp.ContentTypeError:
|
|
283
|
+
# Fallback: manually parse as JSON if Content-Type is malformed
|
|
284
|
+
text = await response.text()
|
|
285
|
+
import json
|
|
286
|
+
data = json.loads(text)
|
|
287
|
+
|
|
288
|
+
return DetailResponse(**data)
|
|
289
|
+
|
|
290
|
+
except Exception as error:
|
|
291
|
+
print(f"GetDetailResponse Request error: {error}")
|
|
292
|
+
return None
|