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
flashforge/__init__.py ADDED
@@ -0,0 +1,188 @@
1
+ """
2
+ FlashForge Python API
3
+
4
+ A Python library for controlling FlashForge 3D printers via HTTP and TCP APIs.
5
+ Provides comprehensive async support for printer control, job management, file operations,
6
+ temperature control, and real-time communication.
7
+
8
+ Example:
9
+ Basic usage example:
10
+
11
+ ```python
12
+ import asyncio
13
+ from flashforge import FlashForgeClient
14
+
15
+ async def main():
16
+ async with FlashForgeClient("192.168.1.100", "serial123", "check456") as client:
17
+ if await client.initialize():
18
+ print(f"Connected to {client.printer_name}")
19
+
20
+ # Get printer status
21
+ status = await client.get_printer_status()
22
+ print(f"Status: {status.machine_state}")
23
+
24
+ # Control operations
25
+ await client.control.set_led_on()
26
+ await client.control.home_axes()
27
+
28
+ # Temperature monitoring
29
+ temps = await client.get_temperatures()
30
+ print(f"Extruder: {temps.extruder_temp}°C")
31
+
32
+ asyncio.run(main())
33
+ ```
34
+ """
35
+
36
+ from .api.constants.commands import Commands
37
+ from .api.constants.endpoints import Endpoints
38
+
39
+ # Import control classes for advanced usage
40
+ from .api.controls import (
41
+ Control,
42
+ Files,
43
+ Info,
44
+ JobControl,
45
+ TempControl,
46
+ )
47
+
48
+ # Import utility classes
49
+ from .api.network.utils import NetworkUtils
50
+ from .api.network.fnet_code import FNetCode
51
+ from .api.filament import Filament
52
+ from .api.misc import Temperature as TempWrapper, format_scientific_notation
53
+ from .client import FlashForgeClient
54
+
55
+ # Import discovery classes
56
+ from .discovery import (
57
+ FlashForgePrinter,
58
+ FlashForgePrinterDiscovery,
59
+ )
60
+
61
+ # Import key models for convenience
62
+ from .models import (
63
+ DetailResponse,
64
+ FFMachineInfo,
65
+ FFPrinterDetail,
66
+ FFGcodeFileEntry,
67
+ FFGcodeToolData,
68
+ FilamentArgs,
69
+ GenericResponse,
70
+ MachineState,
71
+ Product,
72
+ ProductResponse,
73
+ Temperature,
74
+ SlotInfo,
75
+ MatlStationInfo,
76
+ IndepMatlInfo,
77
+ AD5XMaterialMapping,
78
+ AD5XLocalJobParams,
79
+ AD5XSingleColorJobParams,
80
+ AD5XUploadParams,
81
+ GCodeListResponse,
82
+ ThumbnailResponse,
83
+ )
84
+ from .tcp import (
85
+ Endstop,
86
+ EndstopStatus,
87
+ FlashForgeTcpClient,
88
+ GCodeController,
89
+ GCodes,
90
+ LocationInfo,
91
+ MachineStatus,
92
+ MoveMode,
93
+ PrinterInfo,
94
+ PrintStatus,
95
+ Status,
96
+ TempData,
97
+ TempInfo,
98
+ ThumbnailInfo,
99
+ )
100
+
101
+ # Import TCP classes for low-level access
102
+ from .tcp import (
103
+ FlashForgeTcpClient as TcpClient,
104
+ )
105
+
106
+ __version__ = "1.0.0"
107
+ __author__ = "FlashForge Python API Contributors"
108
+ __email__ = "notghosttypes@gmail.com"
109
+ __description__ = "Python library for controlling FlashForge 3D printers"
110
+
111
+ # Public API - Main classes most users will need
112
+ __all__ = [
113
+ # Main client class
114
+ "FlashForgeClient",
115
+
116
+ # Data models
117
+ "FFMachineInfo",
118
+ "FFPrinterDetail",
119
+ "FFGcodeFileEntry",
120
+ "FFGcodeToolData",
121
+ "MachineState",
122
+ "Temperature",
123
+ "GenericResponse",
124
+ "DetailResponse",
125
+ "ProductResponse",
126
+ "Product",
127
+ "FilamentArgs",
128
+
129
+ # AD5X models
130
+ "SlotInfo",
131
+ "MatlStationInfo",
132
+ "IndepMatlInfo",
133
+ "AD5XMaterialMapping",
134
+ "AD5XLocalJobParams",
135
+ "AD5XSingleColorJobParams",
136
+ "AD5XUploadParams",
137
+ "GCodeListResponse",
138
+ "ThumbnailResponse",
139
+
140
+ # Control classes for advanced usage
141
+ "Control",
142
+ "JobControl",
143
+ "Info",
144
+ "Files",
145
+ "TempControl",
146
+
147
+ # TCP classes for low-level operations
148
+ "TcpClient",
149
+ "FlashForgeTcpClient",
150
+ "PrinterInfo",
151
+ "TempInfo",
152
+ "TempData",
153
+ "LocationInfo",
154
+ "EndstopStatus",
155
+ "MachineStatus",
156
+ "MoveMode",
157
+ "Status",
158
+ "Endstop",
159
+ "PrintStatus",
160
+ "ThumbnailInfo",
161
+ "GCodes",
162
+ "GCodeController",
163
+
164
+ # Discovery classes
165
+ "FlashForgePrinter",
166
+ "FlashForgePrinterDiscovery",
167
+
168
+ # Utilities
169
+ "NetworkUtils",
170
+ "FNetCode",
171
+ "Filament",
172
+ "TempWrapper",
173
+ "format_scientific_notation",
174
+ "Endpoints",
175
+ "Commands",
176
+
177
+ # Package metadata
178
+ "__version__",
179
+ "__author__",
180
+ "__email__",
181
+ "__description__",
182
+ ]
183
+
184
+ # Convenience imports for common patterns
185
+ from .models.machine_info import MachineState as State
186
+
187
+ # Add version info accessible as flashforge.version
188
+ version = __version__
@@ -0,0 +1,17 @@
1
+ """
2
+ FlashForge Python API - API Package
3
+ """
4
+ from .constants import Commands, Endpoints
5
+ from .controls import Control, Files, Info, JobControl, TempControl
6
+ from .network import NetworkUtils
7
+
8
+ __all__ = [
9
+ "Commands",
10
+ "Endpoints",
11
+ "Control",
12
+ "Files",
13
+ "Info",
14
+ "JobControl",
15
+ "TempControl",
16
+ "NetworkUtils",
17
+ ]
@@ -0,0 +1,10 @@
1
+ """
2
+ FlashForge Python API - Constants Package
3
+ """
4
+ from .commands import Commands
5
+ from .endpoints import Endpoints
6
+
7
+ __all__ = [
8
+ "Commands",
9
+ "Endpoints",
10
+ ]
@@ -0,0 +1,10 @@
1
+ class Commands:
2
+ """
3
+ Commands used in the "new" HTTP API
4
+ """
5
+ LIGHT_CONTROL_CMD = "lightControl_cmd"
6
+ PRINTER_CONTROL_CMD = "printerCtl_cmd"
7
+ JOB_CONTROL_CMD = "jobCtl_cmd"
8
+ CIRCULATION_CONTROL_CMD = "circulateCtl_cmd"
9
+ CAMERA_CONTROL_CMD = "streamCtrl_cmd"
10
+ TEMP_CONTROL_CMD = "temperatureCtl_cmd"
@@ -0,0 +1,11 @@
1
+ class Endpoints:
2
+ """
3
+ Endpoints for the "new" HTTP API
4
+ """
5
+ CONTROL = "/control"
6
+ DETAIL = "/detail"
7
+ GCODE_LIST = "/gcodeList"
8
+ GCODE_PRINT = "/printGcode"
9
+ GCODE_THUMB = "/gcodeThumb"
10
+ PRODUCT = "/product"
11
+ UPLOAD_FILE = "/uploadGcode"
@@ -0,0 +1,16 @@
1
+ """
2
+ FlashForge Python API - Controls Package
3
+ """
4
+ from .control import Control
5
+ from .files import Files
6
+ from .info import Info
7
+ from .job_control import JobControl
8
+ from .temp_control import TempControl
9
+
10
+ __all__ = [
11
+ "Control",
12
+ "Files",
13
+ "Info",
14
+ "JobControl",
15
+ "TempControl",
16
+ ]
@@ -0,0 +1,357 @@
1
+ """
2
+ FlashForge Python API - Control Module
3
+ """
4
+ from typing import TYPE_CHECKING, Any, Dict, Optional
5
+
6
+ import aiohttp
7
+
8
+ from ...models.responses import FilamentArgs
9
+ from ..constants.commands import Commands
10
+ from ..constants.endpoints import Endpoints
11
+ from ..network.utils import NetworkUtils
12
+
13
+ if TYPE_CHECKING:
14
+ from ...client import FlashForgeClient
15
+ from ...tcp.ff_client import FlashForgeClient as TcpClient
16
+
17
+
18
+ class Control:
19
+ """
20
+ Provides methods for controlling various aspects of the FlashForge 3D printer.
21
+ This includes homing axes, controlling filtration, camera, speed, Z-axis offset,
22
+ fans, LEDs, and filament operations.
23
+ """
24
+
25
+ def __init__(self, client: "FlashForgeClient"):
26
+ """
27
+ Creates an instance of the Control class.
28
+
29
+ Args:
30
+ client: The FlashForgeClient instance used for communication with the printer.
31
+ """
32
+ self.client = client
33
+ self._tcp_client: Optional[TcpClient] = None
34
+
35
+ @property
36
+ def tcp_client(self) -> "TcpClient":
37
+ """Get the TCP client instance."""
38
+ if self._tcp_client is None:
39
+ self._tcp_client = self.client.tcp_client
40
+ return self._tcp_client
41
+
42
+ async def home_axes(self) -> bool:
43
+ """
44
+ Homes the X, Y, and Z axes of the printer.
45
+
46
+ Returns:
47
+ True if the command is successful, False otherwise.
48
+ """
49
+ return await self.tcp_client.home_axes()
50
+
51
+ async def home_axes_rapid(self) -> bool:
52
+ """
53
+ Performs a rapid homing of the X, Y, and Z axes.
54
+
55
+ Returns:
56
+ True if the command is successful, False otherwise.
57
+ """
58
+ return await self.tcp_client.rapid_home()
59
+
60
+ async def set_external_filtration_on(self) -> bool:
61
+ """
62
+ Turns on the external filtration system.
63
+ Requires the printer to have filtration control.
64
+
65
+ Returns:
66
+ True if the command is successful, False otherwise.
67
+ """
68
+ if self.client.filtration_control:
69
+ return await self._send_filtration_command(FilamentArgs(False, True))
70
+ print("SetExternalFiltrationOn() error, filtration not equipped.")
71
+ return False
72
+
73
+ async def set_internal_filtration_on(self) -> bool:
74
+ """
75
+ Turns on the internal filtration system.
76
+ Requires the printer to have filtration control.
77
+
78
+ Returns:
79
+ True if the command is successful, False otherwise.
80
+ """
81
+ if self.client.filtration_control:
82
+ return await self._send_filtration_command(FilamentArgs(True, False))
83
+ print("SetInternalFiltrationOn() error, filtration not equipped.")
84
+ return False
85
+
86
+ async def set_filtration_off(self) -> bool:
87
+ """
88
+ Turns off both internal and external filtration systems.
89
+ Requires the printer to have filtration control.
90
+
91
+ Returns:
92
+ True if the command is successful, False otherwise.
93
+ """
94
+ if self.client.filtration_control:
95
+ return await self._send_filtration_command(FilamentArgs(False, False))
96
+ print("SetFiltrationOff() error, filtration not equipped.")
97
+ return False
98
+
99
+ async def turn_camera_on(self) -> bool:
100
+ """
101
+ Turns on the printer's camera.
102
+ Only applicable for Pro models.
103
+
104
+ Returns:
105
+ True if the command is successful, False otherwise.
106
+ """
107
+ if not self.client.is_pro:
108
+ return False
109
+ return await self._send_camera_command(True)
110
+
111
+ async def turn_camera_off(self) -> bool:
112
+ """
113
+ Turns off the printer's camera.
114
+ Only applicable for Pro models.
115
+
116
+ Returns:
117
+ True if the command is successful, False otherwise.
118
+ """
119
+ if not self.client.is_pro:
120
+ return False
121
+ return await self._send_camera_command(False)
122
+
123
+ async def set_speed_override(self, speed: int) -> bool:
124
+ """
125
+ Sets the print speed override.
126
+
127
+ Args:
128
+ speed: The desired print speed percentage (e.g., 100 for normal speed).
129
+
130
+ Returns:
131
+ True if the command is successful, False otherwise.
132
+ """
133
+ return await self._send_printer_control_cmd(print_speed=speed)
134
+
135
+ async def set_z_axis_override(self, offset: float) -> bool:
136
+ """
137
+ Sets the Z-axis offset override.
138
+
139
+ Args:
140
+ offset: The Z-axis offset value.
141
+
142
+ Returns:
143
+ True if the command is successful, False otherwise.
144
+ """
145
+ return await self._send_printer_control_cmd(z_offset=offset)
146
+
147
+ async def set_chamber_fan_speed(self, speed: int) -> bool:
148
+ """
149
+ Sets the chamber fan speed.
150
+
151
+ Args:
152
+ speed: The desired chamber fan speed percentage.
153
+
154
+ Returns:
155
+ True if the command is successful, False otherwise.
156
+ """
157
+ return await self._send_printer_control_cmd(chamber_fan_speed=speed)
158
+
159
+ async def set_cooling_fan_speed(self, speed: int) -> bool:
160
+ """
161
+ Sets the cooling fan speed.
162
+
163
+ Args:
164
+ speed: The desired cooling fan speed percentage.
165
+
166
+ Returns:
167
+ True if the command is successful, False otherwise.
168
+ """
169
+ return await self._send_printer_control_cmd(cooling_fan_speed=speed)
170
+
171
+ async def set_led_on(self) -> bool:
172
+ """
173
+ Turns on the printer's LED lights.
174
+ Requires the printer to have LED control.
175
+
176
+ Returns:
177
+ True if the command is successful, False otherwise.
178
+ """
179
+ if self.client.led_control:
180
+ return await self.send_control_command(Commands.LIGHT_CONTROL_CMD, {"status": "open"})
181
+ print("SetLedOn() error, LEDs not equipped.")
182
+ return False
183
+
184
+ async def set_led_off(self) -> bool:
185
+ """
186
+ Turns off the printer's LED lights.
187
+ Requires the printer to have LED control.
188
+
189
+ Returns:
190
+ True if the command is successful, False otherwise.
191
+ """
192
+ if self.client.led_control:
193
+ return await self.send_control_command(Commands.LIGHT_CONTROL_CMD, {"status": "close"})
194
+ print("SetLedOff() error, LEDs not equipped.")
195
+ return False
196
+
197
+ async def turn_runout_sensor_on(self) -> bool:
198
+ """
199
+ Turns on the filament runout sensor.
200
+
201
+ Returns:
202
+ True if the command is successful, False otherwise.
203
+ """
204
+ return await self.tcp_client.turn_runout_sensor_on()
205
+
206
+ async def turn_runout_sensor_off(self) -> bool:
207
+ """
208
+ Turns off the filament runout sensor.
209
+
210
+ Returns:
211
+ True if the command is successful, False otherwise.
212
+ """
213
+ return await self.tcp_client.turn_runout_sensor_off()
214
+
215
+ async def send_control_command(self, command: str, args: Dict[str, Any]) -> bool:
216
+ """
217
+ Sends a generic control command to the printer via HTTP POST.
218
+
219
+ Args:
220
+ command: The specific command string to send.
221
+ args: The arguments or payload specific to the command.
222
+
223
+ Returns:
224
+ True if the command is acknowledged with a success code, False otherwise.
225
+ """
226
+ payload = {
227
+ "serialNumber": self.client.serial_number,
228
+ "checkCode": self.client.check_code,
229
+ "payload": {
230
+ "cmd": command,
231
+ "args": args
232
+ }
233
+ }
234
+
235
+ print(f"SendControlCommand:\n{payload}")
236
+
237
+ try:
238
+ await self.client.is_http_client_busy()
239
+
240
+ async with aiohttp.ClientSession() as session:
241
+ async with session.post(
242
+ self.client.get_endpoint(Endpoints.CONTROL),
243
+ json=payload,
244
+ headers={"Content-Type": "application/json"}
245
+ ) as response:
246
+ # Fix for FlashForge printer's malformed Content-Type header
247
+ # Some printers return "appliation/json" instead of "application/json"
248
+ try:
249
+ data = await response.json()
250
+ except aiohttp.ContentTypeError:
251
+ # Fallback: manually parse as JSON if Content-Type is malformed
252
+ text = await response.text()
253
+ import json
254
+ data = json.loads(text)
255
+
256
+ print(f"Command reply: {data}")
257
+
258
+ return NetworkUtils.is_ok(data)
259
+
260
+ except Exception as e:
261
+ print(f"Error in send_control_command: {e}")
262
+ return False
263
+ finally:
264
+ self.client.release_http_client()
265
+
266
+ async def send_job_control_cmd(self, command: str) -> bool:
267
+ """
268
+ Sends a job control command.
269
+
270
+ Args:
271
+ command: The job control command to send.
272
+
273
+ Returns:
274
+ True if the command is successful, False otherwise.
275
+ """
276
+ payload = {
277
+ "jobID": "", # jobID seems to be optional or not strictly enforced
278
+ "action": command
279
+ }
280
+
281
+ return await self.send_control_command(Commands.JOB_CONTROL_CMD, payload)
282
+
283
+ async def _send_printer_control_cmd(
284
+ self,
285
+ z_offset: float = 0.0,
286
+ print_speed: int = 100,
287
+ chamber_fan_speed: int = 100,
288
+ cooling_fan_speed: int = 100
289
+ ) -> bool:
290
+ """
291
+ Sends a command to control various printer settings during a print.
292
+
293
+ Args:
294
+ z_offset: The Z-axis compensation offset.
295
+ print_speed: The print speed percentage.
296
+ chamber_fan_speed: The chamber fan speed percentage.
297
+ cooling_fan_speed: The cooling fan speed percentage.
298
+
299
+ Returns:
300
+ True if the command is successful, False otherwise.
301
+ """
302
+ info = await self.client.info.get()
303
+
304
+ if info and info.current_print_layer < 2:
305
+ # Don't accidentally turn on the fans in the initial layers
306
+ chamber_fan_speed = 0
307
+ cooling_fan_speed = 0
308
+
309
+ if not self._is_printing(info):
310
+ raise Exception("Attempted to send printerCtl_cmd with no active job")
311
+
312
+ payload = {
313
+ "zAxisCompensation": z_offset,
314
+ "speed": print_speed,
315
+ "chamberFan": chamber_fan_speed,
316
+ "coolingFan": cooling_fan_speed,
317
+ "coolingLeftFan": 0 # This is unused
318
+ }
319
+
320
+ return await self.send_control_command(Commands.PRINTER_CONTROL_CMD, payload)
321
+
322
+ async def _send_filtration_command(self, args: FilamentArgs) -> bool:
323
+ """
324
+ Sends a command to control the printer's filtration system.
325
+
326
+ Args:
327
+ args: The filtration arguments specifying internal and external fan states.
328
+
329
+ Returns:
330
+ True if the command is successful, False otherwise.
331
+ """
332
+ return await self.send_control_command(Commands.CIRCULATION_CONTROL_CMD, args.dict())
333
+
334
+ async def _send_camera_command(self, enabled: bool) -> bool:
335
+ """
336
+ Sends a command to control the printer's camera.
337
+
338
+ Args:
339
+ enabled: True to turn the camera on ("open"), false to turn it off ("close").
340
+
341
+ Returns:
342
+ True if the command is successful, False otherwise.
343
+ """
344
+ payload = {"action": "open" if enabled else "close"}
345
+ return await self.send_control_command(Commands.CAMERA_CONTROL_CMD, payload)
346
+
347
+ def _is_printing(self, info: Any) -> bool:
348
+ """
349
+ Checks if the printer is currently printing based on its status information.
350
+
351
+ Args:
352
+ info: The printer information object.
353
+
354
+ Returns:
355
+ True if the printer status is "printing", False otherwise.
356
+ """
357
+ return info and getattr(info, "status", "") == "printing"