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,39 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FlashForge Python API - Filament Module
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Filament:
|
|
7
|
+
"""
|
|
8
|
+
Represents a type of filament used in a 3D printer.
|
|
9
|
+
It stores information about the filament's name and its recommended loading temperature.
|
|
10
|
+
This class can be used to define specific filament types for printer operations
|
|
11
|
+
like loading or preheating.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, name: str, load_temp: float = 220.0):
|
|
15
|
+
"""
|
|
16
|
+
Creates an instance of the Filament class.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
name: The name of the filament type (e.g., "PLA", "ABS", "PETG").
|
|
20
|
+
load_temp: The recommended loading temperature for the filament in Celsius. Defaults to 220°C.
|
|
21
|
+
"""
|
|
22
|
+
self._name = name
|
|
23
|
+
self._load_temp = load_temp
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def name(self) -> str:
|
|
27
|
+
"""The name of the filament type."""
|
|
28
|
+
return self._name
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def load_temp(self) -> float:
|
|
32
|
+
"""The recommended loading temperature for this filament in Celsius."""
|
|
33
|
+
return self._load_temp
|
|
34
|
+
|
|
35
|
+
def __repr__(self) -> str:
|
|
36
|
+
return f"Filament(name='{self._name}', load_temp={self._load_temp})"
|
|
37
|
+
|
|
38
|
+
def __str__(self) -> str:
|
|
39
|
+
return f"{self._name} @ {self._load_temp}°C"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FlashForge Python API - Scientific Notation Formatter
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def format_scientific_notation(value: float) -> str:
|
|
7
|
+
"""
|
|
8
|
+
Formats a number into a string, using scientific notation if the number is
|
|
9
|
+
very small (absolute value < 0.001) or very large (absolute value >= 10000).
|
|
10
|
+
Otherwise, it returns the standard string representation of the number.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
value: The number to format.
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
A string representation of the number, potentially in scientific notation.
|
|
17
|
+
|
|
18
|
+
Examples:
|
|
19
|
+
>>> format_scientific_notation(0.000123)
|
|
20
|
+
'1.23e-04'
|
|
21
|
+
>>> format_scientific_notation(12345)
|
|
22
|
+
'1.2345e+04'
|
|
23
|
+
>>> format_scientific_notation(12.34)
|
|
24
|
+
'12.34'
|
|
25
|
+
"""
|
|
26
|
+
if abs(value) < 0.001 or abs(value) >= 10000:
|
|
27
|
+
return f"{value:e}"
|
|
28
|
+
return str(value)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FlashForge Python API - Temperature Utility Class
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Temperature:
|
|
7
|
+
"""
|
|
8
|
+
Represents a temperature value.
|
|
9
|
+
This class is a simple wrapper around a numeric temperature value,
|
|
10
|
+
providing methods to get the value and its string representation.
|
|
11
|
+
It's a general temperature representation that can be used for various purposes.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, value: float):
|
|
15
|
+
"""
|
|
16
|
+
Creates an instance of the Temperature class.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
value: The numeric temperature value, typically in Celsius.
|
|
20
|
+
"""
|
|
21
|
+
self._value = value
|
|
22
|
+
|
|
23
|
+
def get_value(self) -> float:
|
|
24
|
+
"""
|
|
25
|
+
Gets the numeric temperature value.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
The temperature value.
|
|
29
|
+
"""
|
|
30
|
+
return self._value
|
|
31
|
+
|
|
32
|
+
def __str__(self) -> str:
|
|
33
|
+
"""
|
|
34
|
+
Gets the string representation of the temperature value.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
The temperature value as a string.
|
|
38
|
+
"""
|
|
39
|
+
return str(self._value)
|
|
40
|
+
|
|
41
|
+
def __repr__(self) -> str:
|
|
42
|
+
return f"Temperature({self._value})"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FlashForge Python API - Network Operation Codes
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from enum import Enum
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FNetCode(Enum):
|
|
9
|
+
"""
|
|
10
|
+
Represents network operation codes, typically used in API responses
|
|
11
|
+
to indicate the success or failure of a requested operation.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
OK = 0 # Indicates that the network operation was successful
|
|
15
|
+
ERROR = 1 # Indicates that an error occurred during the network operation
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FlashForge Python API - Network Utilities
|
|
3
|
+
"""
|
|
4
|
+
from typing import Optional, Union
|
|
5
|
+
|
|
6
|
+
from ...models.responses import GenericResponse
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class NetworkUtils:
|
|
10
|
+
"""Utility class for handling network responses and status codes."""
|
|
11
|
+
|
|
12
|
+
@staticmethod
|
|
13
|
+
def is_ok(response: Optional[Union[GenericResponse, dict]]) -> bool:
|
|
14
|
+
"""
|
|
15
|
+
Checks if a response indicates success.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
response: The response object to check
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
True if the response indicates success, False otherwise
|
|
22
|
+
"""
|
|
23
|
+
if response is None:
|
|
24
|
+
return False
|
|
25
|
+
|
|
26
|
+
# Handle dictionary responses
|
|
27
|
+
if isinstance(response, dict):
|
|
28
|
+
code = response.get("code", -1)
|
|
29
|
+
else:
|
|
30
|
+
# Handle GenericResponse objects
|
|
31
|
+
code = getattr(response, "code", -1)
|
|
32
|
+
|
|
33
|
+
# Success codes: 0 or 200
|
|
34
|
+
return code in (0, 200)
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def get_error_message(response: Optional[Union[GenericResponse, dict]]) -> str:
|
|
38
|
+
"""
|
|
39
|
+
Extracts error message from a response.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
response: The response object to extract message from
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Error message string, or empty string if none found
|
|
46
|
+
"""
|
|
47
|
+
if response is None:
|
|
48
|
+
return "No response received"
|
|
49
|
+
|
|
50
|
+
# Handle dictionary responses
|
|
51
|
+
if isinstance(response, dict):
|
|
52
|
+
return response.get("message", "Unknown error")
|
|
53
|
+
else:
|
|
54
|
+
# Handle GenericResponse objects
|
|
55
|
+
return getattr(response, "message", "Unknown error")
|
flashforge/client.py
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FlashForge Python API - Main Unified Client
|
|
3
|
+
|
|
4
|
+
This module provides the main FlashForgeClient class that orchestrates both HTTP and TCP
|
|
5
|
+
communication layers for controlling FlashForge 3D printers.
|
|
6
|
+
"""
|
|
7
|
+
import asyncio
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
import aiohttp
|
|
11
|
+
|
|
12
|
+
from .api.constants.endpoints import Endpoints
|
|
13
|
+
from .api.controls import Control, Files, Info, JobControl, TempControl
|
|
14
|
+
from .api.controls.info import MachineInfoParser
|
|
15
|
+
from .api.network.utils import NetworkUtils
|
|
16
|
+
from .models import FFMachineInfo, ProductResponse
|
|
17
|
+
from .tcp import FlashForgeClient as TcpClient
|
|
18
|
+
from .tcp import PrinterInfo
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class FlashForgeClient:
|
|
22
|
+
"""
|
|
23
|
+
Main client for interacting with a FlashForge 3D printer.
|
|
24
|
+
|
|
25
|
+
This class provides methods for controlling the printer, managing print jobs,
|
|
26
|
+
retrieving information, and handling file operations. It orchestrates both
|
|
27
|
+
HTTP and TCP communication layers to provide a unified interface.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, ip_address: str, serial_number: str, check_code: str):
|
|
31
|
+
"""
|
|
32
|
+
Creates an instance of FlashForgeClient.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
ip_address: The IP address of the printer
|
|
36
|
+
serial_number: The serial number of the printer
|
|
37
|
+
check_code: The check code for the printer
|
|
38
|
+
"""
|
|
39
|
+
# Connection parameters
|
|
40
|
+
self.ip_address = ip_address
|
|
41
|
+
self.serial_number = serial_number
|
|
42
|
+
self.check_code = check_code
|
|
43
|
+
|
|
44
|
+
# Constants
|
|
45
|
+
self._PORT = 8898
|
|
46
|
+
self._HTTP_TIMEOUT = 5.0
|
|
47
|
+
|
|
48
|
+
# HTTP client state
|
|
49
|
+
self._http_session: Optional[aiohttp.ClientSession] = None
|
|
50
|
+
self._http_client_busy = False
|
|
51
|
+
|
|
52
|
+
# TCP client setup
|
|
53
|
+
self.tcp_client = TcpClient(ip_address)
|
|
54
|
+
|
|
55
|
+
# Control instances
|
|
56
|
+
self.control = Control(self)
|
|
57
|
+
self.job_control = JobControl(self)
|
|
58
|
+
self.info = Info(self)
|
|
59
|
+
self.files = Files(self)
|
|
60
|
+
self.temp_control = TempControl(self)
|
|
61
|
+
|
|
62
|
+
# Printer information cache
|
|
63
|
+
self.printer_name: str = ""
|
|
64
|
+
self.is_pro: bool = False
|
|
65
|
+
self._is_ad5x: bool = False
|
|
66
|
+
self.firmware_version: str = ""
|
|
67
|
+
self.firmware_ver: str = ""
|
|
68
|
+
self.mac_address: str = ""
|
|
69
|
+
self.flash_cloud_code: str = ""
|
|
70
|
+
self.polar_cloud_code: str = ""
|
|
71
|
+
self.lifetime_print_time: str = ""
|
|
72
|
+
self.lifetime_filament_meters: str = ""
|
|
73
|
+
|
|
74
|
+
# Control states
|
|
75
|
+
self.led_control: bool = False
|
|
76
|
+
self.filtration_control: bool = False
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def is_ad5x(self) -> bool:
|
|
80
|
+
"""
|
|
81
|
+
Indicates if the printer is an AD5X model.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
True if the printer is AD5X, False otherwise
|
|
85
|
+
"""
|
|
86
|
+
return self._is_ad5x
|
|
87
|
+
|
|
88
|
+
async def __aenter__(self):
|
|
89
|
+
"""Async context manager entry."""
|
|
90
|
+
await self._ensure_http_session()
|
|
91
|
+
return self
|
|
92
|
+
|
|
93
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
94
|
+
"""Async context manager exit."""
|
|
95
|
+
await self.dispose()
|
|
96
|
+
|
|
97
|
+
async def _ensure_http_session(self) -> aiohttp.ClientSession:
|
|
98
|
+
"""
|
|
99
|
+
Ensures that an HTTP session is available.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
The HTTP session instance
|
|
103
|
+
"""
|
|
104
|
+
if self._http_session is None or self._http_session.closed:
|
|
105
|
+
timeout = aiohttp.ClientTimeout(total=self._HTTP_TIMEOUT)
|
|
106
|
+
self._http_session = aiohttp.ClientSession(
|
|
107
|
+
timeout=timeout,
|
|
108
|
+
headers={"Accept": "*/*"}
|
|
109
|
+
)
|
|
110
|
+
return self._http_session
|
|
111
|
+
|
|
112
|
+
async def initialize(self) -> bool:
|
|
113
|
+
"""
|
|
114
|
+
Initializes the FlashForgeClient and verifies the connection to the printer.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
True if initialization is successful, False otherwise
|
|
118
|
+
"""
|
|
119
|
+
connected = await self.verify_connection()
|
|
120
|
+
if connected:
|
|
121
|
+
return True
|
|
122
|
+
print("Failed to connect to printer")
|
|
123
|
+
return False
|
|
124
|
+
|
|
125
|
+
async def is_http_client_busy(self) -> bool:
|
|
126
|
+
"""
|
|
127
|
+
Checks if the HTTP client is currently busy.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
True if the HTTP client is busy, False otherwise
|
|
131
|
+
"""
|
|
132
|
+
# Wait a bit if busy to prevent tight loops
|
|
133
|
+
while self._http_client_busy:
|
|
134
|
+
await asyncio.sleep(0.01)
|
|
135
|
+
return self._http_client_busy
|
|
136
|
+
|
|
137
|
+
def release_http_client(self) -> None:
|
|
138
|
+
"""Releases the HTTP client, allowing it to be used for new requests."""
|
|
139
|
+
self._http_client_busy = False
|
|
140
|
+
|
|
141
|
+
async def init_control(self) -> bool:
|
|
142
|
+
"""
|
|
143
|
+
Initializes the control interface with the printer.
|
|
144
|
+
|
|
145
|
+
This involves sending a product command and initializing TCP control.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
True if control initialization is successful, False otherwise
|
|
149
|
+
"""
|
|
150
|
+
if await self.send_product_command():
|
|
151
|
+
return await self.tcp_client.init_control()
|
|
152
|
+
print("New API control failed!")
|
|
153
|
+
return False
|
|
154
|
+
|
|
155
|
+
async def dispose(self) -> None:
|
|
156
|
+
"""
|
|
157
|
+
Disposes of the FlashForgeClient instance, stopping keep-alive messages
|
|
158
|
+
and cleaning up resources.
|
|
159
|
+
"""
|
|
160
|
+
# Stop TCP keep-alive and dispose
|
|
161
|
+
if hasattr(self.tcp_client, 'stop_keep_alive'):
|
|
162
|
+
await self.tcp_client.stop_keep_alive(True)
|
|
163
|
+
if hasattr(self.tcp_client, 'dispose'):
|
|
164
|
+
await self.tcp_client.dispose()
|
|
165
|
+
|
|
166
|
+
# Close HTTP session
|
|
167
|
+
if self._http_session and not self._http_session.closed:
|
|
168
|
+
await self._http_session.close()
|
|
169
|
+
|
|
170
|
+
def cache_details(self, info: Optional[FFMachineInfo]) -> bool:
|
|
171
|
+
"""
|
|
172
|
+
Caches machine details from the provided FFMachineInfo object.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
info: The FFMachineInfo object containing printer details
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
True if caching is successful, False otherwise
|
|
179
|
+
"""
|
|
180
|
+
if not info:
|
|
181
|
+
return False
|
|
182
|
+
|
|
183
|
+
# Cache all printer information
|
|
184
|
+
self.printer_name = info.name or ""
|
|
185
|
+
self.firmware_version = info.firmware_version or ""
|
|
186
|
+
self.firmware_ver = info.firmware_version.split('-')[0] if info.firmware_version else ""
|
|
187
|
+
self.mac_address = info.mac_address or ""
|
|
188
|
+
self.flash_cloud_code = info.flash_cloud_register_code or ""
|
|
189
|
+
self.polar_cloud_code = info.polar_cloud_register_code or ""
|
|
190
|
+
self.lifetime_print_time = info.formatted_total_run_time or ""
|
|
191
|
+
self._is_ad5x = info.is_ad5x
|
|
192
|
+
|
|
193
|
+
# Format filament usage
|
|
194
|
+
if info.cumulative_filament is not None:
|
|
195
|
+
self.lifetime_filament_meters = f"{info.cumulative_filament:.2f}m"
|
|
196
|
+
else:
|
|
197
|
+
self.lifetime_filament_meters = "0.00m"
|
|
198
|
+
|
|
199
|
+
return True
|
|
200
|
+
|
|
201
|
+
def get_endpoint(self, endpoint: str) -> str:
|
|
202
|
+
"""
|
|
203
|
+
Constructs the full API endpoint URL.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
endpoint: The specific API endpoint path
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
The full URL for the API endpoint
|
|
210
|
+
"""
|
|
211
|
+
return f"http://{self.ip_address}:{self._PORT}{endpoint}"
|
|
212
|
+
|
|
213
|
+
async def verify_connection(self) -> bool:
|
|
214
|
+
"""
|
|
215
|
+
Verifies the connection to the printer by retrieving machine details and TCP information.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
True if the connection is verified, False otherwise
|
|
219
|
+
"""
|
|
220
|
+
try:
|
|
221
|
+
# Get HTTP API response
|
|
222
|
+
response = await self.info.get_detail_response()
|
|
223
|
+
if not response or not NetworkUtils.is_ok(response):
|
|
224
|
+
print("Failed to get valid response from printer API")
|
|
225
|
+
return False
|
|
226
|
+
|
|
227
|
+
# Parse machine info from detail response
|
|
228
|
+
machine_info = MachineInfoParser.from_detail(response.detail)
|
|
229
|
+
if not machine_info:
|
|
230
|
+
print("Failed to parse machine info from detail response")
|
|
231
|
+
return False
|
|
232
|
+
|
|
233
|
+
# Get TCP printer information to check for Pro model
|
|
234
|
+
tcp_info: Optional[PrinterInfo] = await self.tcp_client.get_printer_info()
|
|
235
|
+
if tcp_info:
|
|
236
|
+
if "Pro" in tcp_info.type_name:
|
|
237
|
+
self.is_pro = True
|
|
238
|
+
else:
|
|
239
|
+
print("Warning: Unable to get PrinterInfo from TCP API, some features might not work")
|
|
240
|
+
|
|
241
|
+
# Cache the details
|
|
242
|
+
return self.cache_details(machine_info)
|
|
243
|
+
|
|
244
|
+
except Exception as error:
|
|
245
|
+
print(f"Error in verify_connection: {error}")
|
|
246
|
+
return False
|
|
247
|
+
|
|
248
|
+
async def send_product_command(self) -> bool:
|
|
249
|
+
"""
|
|
250
|
+
Sends a product command to the printer to retrieve control states.
|
|
251
|
+
|
|
252
|
+
This method sets the http_client_busy flag while the request is in progress.
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
True if the product command is sent successfully and valid data is received,
|
|
256
|
+
False otherwise
|
|
257
|
+
"""
|
|
258
|
+
self._http_client_busy = True
|
|
259
|
+
|
|
260
|
+
payload = {
|
|
261
|
+
"serialNumber": self.serial_number,
|
|
262
|
+
"checkCode": self.check_code
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
try:
|
|
266
|
+
session = await self._ensure_http_session()
|
|
267
|
+
async with session.post(
|
|
268
|
+
self.get_endpoint(Endpoints.PRODUCT),
|
|
269
|
+
json=payload,
|
|
270
|
+
headers={"Content-Type": "application/json"}
|
|
271
|
+
) as response:
|
|
272
|
+
|
|
273
|
+
if response.status != 200:
|
|
274
|
+
return False
|
|
275
|
+
|
|
276
|
+
# Fix for FlashForge printer's malformed Content-Type header
|
|
277
|
+
# Some printers return "appliation/json" instead of "application/json"
|
|
278
|
+
try:
|
|
279
|
+
data = await response.json()
|
|
280
|
+
except aiohttp.ContentTypeError:
|
|
281
|
+
# Fallback: manually parse as JSON if Content-Type is malformed
|
|
282
|
+
text = await response.text()
|
|
283
|
+
import json
|
|
284
|
+
data = json.loads(text)
|
|
285
|
+
|
|
286
|
+
# Validate response structure
|
|
287
|
+
if not NetworkUtils.is_ok(data):
|
|
288
|
+
return False
|
|
289
|
+
|
|
290
|
+
# Parse product response and set control states
|
|
291
|
+
product_response = ProductResponse(**data)
|
|
292
|
+
if product_response and product_response.product:
|
|
293
|
+
product = product_response.product
|
|
294
|
+
self.led_control = product.lightCtrlState != 0
|
|
295
|
+
self.filtration_control = not (
|
|
296
|
+
product.internalFanCtrlState == 0 or
|
|
297
|
+
product.externalFanCtrlState == 0
|
|
298
|
+
)
|
|
299
|
+
return True
|
|
300
|
+
|
|
301
|
+
except Exception as error:
|
|
302
|
+
print(f"Error in send_product_command: {error}")
|
|
303
|
+
return False
|
|
304
|
+
finally:
|
|
305
|
+
self._http_client_busy = False
|
|
306
|
+
|
|
307
|
+
return False
|
|
308
|
+
|
|
309
|
+
async def get_http_session(self) -> aiohttp.ClientSession:
|
|
310
|
+
"""
|
|
311
|
+
Gets the HTTP session for making requests.
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
The HTTP session instance
|
|
315
|
+
"""
|
|
316
|
+
return await self._ensure_http_session()
|
|
317
|
+
|
|
318
|
+
# Additional convenience methods for direct access to common operations
|
|
319
|
+
|
|
320
|
+
async def get_printer_status(self) -> Optional[FFMachineInfo]:
|
|
321
|
+
"""
|
|
322
|
+
Gets the current printer status and information.
|
|
323
|
+
|
|
324
|
+
Returns:
|
|
325
|
+
FFMachineInfo object with current printer status, or None if failed
|
|
326
|
+
"""
|
|
327
|
+
return await self.info.get()
|
|
328
|
+
|
|
329
|
+
async def get_temperatures(self):
|
|
330
|
+
"""
|
|
331
|
+
Gets current temperature readings from the printer.
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
Temperature information from the TCP client
|
|
335
|
+
"""
|
|
336
|
+
return await self.tcp_client.get_temp_info()
|
|
337
|
+
|
|
338
|
+
async def home_all_axes(self) -> bool:
|
|
339
|
+
"""
|
|
340
|
+
Homes all axes (X, Y, Z) of the printer.
|
|
341
|
+
|
|
342
|
+
Returns:
|
|
343
|
+
True if successful, False otherwise
|
|
344
|
+
"""
|
|
345
|
+
return await self.control.home_axes()
|
|
346
|
+
|
|
347
|
+
async def emergency_stop(self) -> bool:
|
|
348
|
+
"""
|
|
349
|
+
Performs an emergency stop of the printer.
|
|
350
|
+
|
|
351
|
+
Returns:
|
|
352
|
+
True if successful, False otherwise
|
|
353
|
+
"""
|
|
354
|
+
return await self.job_control.cancel_print_job()
|
|
355
|
+
|
|
356
|
+
async def pause_print(self) -> bool:
|
|
357
|
+
"""
|
|
358
|
+
Pauses the current print job.
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
True if successful, False otherwise
|
|
362
|
+
"""
|
|
363
|
+
return await self.job_control.pause_print_job()
|
|
364
|
+
|
|
365
|
+
async def resume_print(self) -> bool:
|
|
366
|
+
"""
|
|
367
|
+
Resumes a paused print job.
|
|
368
|
+
|
|
369
|
+
Returns:
|
|
370
|
+
True if successful, False otherwise
|
|
371
|
+
"""
|
|
372
|
+
return await self.job_control.resume_print_job()
|
|
373
|
+
|
|
374
|
+
def __repr__(self) -> str:
|
|
375
|
+
"""String representation of the client."""
|
|
376
|
+
return (
|
|
377
|
+
f"FlashForgeClient(ip={self.ip_address}, "
|
|
378
|
+
f"printer='{self.printer_name}', "
|
|
379
|
+
f"pro={self.is_pro}, "
|
|
380
|
+
f"firmware='{self.firmware_ver}')"
|
|
381
|
+
)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FlashForge Python API - Discovery Package
|
|
3
|
+
|
|
4
|
+
UDP-based printer discovery for finding FlashForge printers on the local network.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .discovery import FlashForgePrinter, FlashForgePrinterDiscovery
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
'FlashForgePrinter',
|
|
11
|
+
'FlashForgePrinterDiscovery',
|
|
12
|
+
]
|