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
@@ -1,28 +1,29 @@
1
1
  """
2
2
  FlashForge Python API - Models Package
3
3
  """
4
+
4
5
  from .machine_info import (
5
- FFMachineInfo,
6
- FFPrinterDetail,
7
6
  FFGcodeFileEntry,
8
7
  FFGcodeToolData,
8
+ FFMachineInfo,
9
+ FFPrinterDetail,
10
+ IndepMatlInfo,
9
11
  MachineState,
10
- Temperature,
11
- SlotInfo,
12
12
  MatlStationInfo,
13
- IndepMatlInfo,
13
+ SlotInfo,
14
+ Temperature,
14
15
  )
15
16
  from .responses import (
17
+ AD5XLocalJobParams,
18
+ AD5XMaterialMapping,
19
+ AD5XSingleColorJobParams,
20
+ AD5XUploadParams,
16
21
  DetailResponse,
17
22
  FilamentArgs,
23
+ GCodeListResponse,
18
24
  GenericResponse,
19
25
  Product,
20
26
  ProductResponse,
21
- AD5XMaterialMapping,
22
- AD5XLocalJobParams,
23
- AD5XSingleColorJobParams,
24
- AD5XUploadParams,
25
- GCodeListResponse,
26
27
  ThumbnailResponse,
27
28
  )
28
29
 
@@ -1,17 +1,18 @@
1
1
  """
2
2
  FlashForge Python API - Data Models
3
3
  """
4
+
4
5
  from __future__ import annotations
5
6
 
6
7
  from datetime import datetime
7
8
  from enum import Enum
8
- from typing import Optional
9
9
 
10
- from pydantic import BaseModel, Field
10
+ from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
11
11
 
12
12
 
13
13
  class MachineState(Enum):
14
14
  """Enumerates the possible operational states of the FlashForge 3D printer."""
15
+
15
16
  READY = "ready"
16
17
  BUSY = "busy"
17
18
  CALIBRATING = "calibrating"
@@ -27,68 +28,168 @@ class MachineState(Enum):
27
28
 
28
29
  class Temperature(BaseModel):
29
30
  """Represents a pair of current and target temperatures for a component like an extruder or print bed."""
30
- current: float = Field(default=0.0, description="The current temperature in Celsius")
31
- set: float = Field(default=0.0, description="The target (set) temperature in Celsius")
31
+
32
+ model_config = ConfigDict(extra="forbid")
33
+
34
+ current: float = Field(
35
+ default=0.0, ge=-50, le=500, description="The current temperature in Celsius"
36
+ )
37
+ set: float = Field(
38
+ default=0.0, ge=-50, le=500, description="The target (set) temperature in Celsius"
39
+ )
32
40
 
33
41
 
34
42
  class SlotInfo(BaseModel):
35
43
  """Information about a single slot in the material station."""
36
- has_filament: bool = Field(alias="hasFilament", description="Indicates if filament is present in this slot")
37
- material_color: str = Field(alias="materialColor", description="Color of the material in this slot (e.g., '#FFFFFF')")
38
- material_name: str = Field(alias="materialName", description="Name of the material in this slot (e.g., 'PLA')")
39
- slot_id: int = Field(alias="slotId", description="Identifier for this slot")
40
44
 
41
- class Config:
42
- populate_by_name = True
45
+ model_config = ConfigDict(extra="forbid", populate_by_name=True)
46
+
47
+ has_filament: bool = Field(
48
+ alias="hasFilament", description="Indicates if filament is present in this slot"
49
+ )
50
+ material_color: str = Field(
51
+ alias="materialColor", description="Color of the material in this slot (e.g., '#FFFFFF')"
52
+ )
53
+ material_name: str = Field(
54
+ alias="materialName", description="Name of the material in this slot (e.g., 'PLA')"
55
+ )
56
+ slot_id: int = Field(alias="slotId", ge=1, le=4, description="Identifier for this slot (1-4)")
57
+
58
+ @field_validator("material_color")
59
+ @classmethod
60
+ def validate_hex_color(cls, v: str) -> str:
61
+ """Validate that the color is a valid hex color code or empty string."""
62
+ import re
63
+
64
+ if v == "" or re.match(r"^#[0-9A-Fa-f]{6}$", v):
65
+ return v
66
+ raise ValueError(f"Invalid hex color format: {v}")
43
67
 
44
68
 
45
69
  class MatlStationInfo(BaseModel):
46
70
  """Detailed information about the material station."""
47
- current_load_slot: int = Field(alias="currentLoadSlot", description="Currently loading slot ID (0 if none)")
48
- current_slot: int = Field(alias="currentSlot", description="Currently active/printing slot ID (0 if none)")
49
- slot_cnt: int = Field(alias="slotCnt", description="Total number of slots in the station")
50
- slot_infos: list[SlotInfo] = Field(default_factory=list, alias="slotInfos", description="Array of information for each slot")
51
- state_action: int = Field(alias="stateAction", description="Current action state of the material station")
52
- state_step: int = Field(alias="stateStep", description="Current step within the state action")
53
71
 
54
- class Config:
55
- populate_by_name = True
72
+ model_config = ConfigDict(extra="forbid", populate_by_name=True)
73
+
74
+ current_load_slot: int = Field(
75
+ alias="currentLoadSlot", ge=0, le=4, description="Currently loading slot ID (0 if none)"
76
+ )
77
+ current_slot: int = Field(
78
+ alias="currentSlot", ge=0, le=4, description="Currently active/printing slot ID (0 if none)"
79
+ )
80
+ slot_cnt: int = Field(
81
+ alias="slotCnt", ge=1, le=4, description="Total number of slots in the station"
82
+ )
83
+ slot_infos: list[SlotInfo] = Field(
84
+ min_length=1,
85
+ max_length=4,
86
+ default_factory=list,
87
+ alias="slotInfos",
88
+ description="Array of information for each slot",
89
+ )
90
+ state_action: int = Field(
91
+ alias="stateAction", ge=0, description="Current action state of the material station"
92
+ )
93
+ state_step: int = Field(
94
+ alias="stateStep", ge=0, description="Current step within the state action"
95
+ )
56
96
 
57
97
 
58
98
  class IndepMatlInfo(BaseModel):
59
99
  """Information related to independent material loading, often used when a single extruder printer has a material station."""
100
+
101
+ model_config = ConfigDict(extra="forbid", populate_by_name=True)
102
+
60
103
  material_color: str = Field(alias="materialColor", description="Color of the material")
61
- material_name: str = Field(alias="materialName", description="Name of the material (can be '?' if unknown)")
62
- state_action: int = Field(alias="stateAction", description="Current action state")
63
- state_step: int = Field(alias="stateStep", description="Current step within the state action")
104
+ material_name: str = Field(
105
+ alias="materialName", description="Name of the material (can be '?' if unknown)"
106
+ )
107
+ state_action: int = Field(alias="stateAction", ge=0, description="Current action state")
108
+ state_step: int = Field(
109
+ alias="stateStep", ge=0, description="Current step within the state action"
110
+ )
111
+
112
+ @field_validator("material_color")
113
+ @classmethod
114
+ def validate_hex_color(cls, v: str) -> str:
115
+ """Validate that the color is a valid hex color code or empty string."""
116
+ import re
64
117
 
65
- class Config:
66
- populate_by_name = True
118
+ if v == "" or re.match(r"^#[0-9A-Fa-f]{6}$", v):
119
+ return v
120
+ raise ValueError(f"Invalid hex color format: {v}")
67
121
 
68
122
 
69
123
  class FFGcodeToolData(BaseModel):
70
124
  """Represents data for a single tool/material used in a G-code file, typically part of a multi-material print."""
71
- filament_weight: float = Field(alias="filamentWeight", description="Calculated filament weight for this tool/material in the print")
72
- material_color: str = Field(alias="materialColor", description="Material color hex string (e.g., '#FFFF00')")
73
- material_name: str = Field(alias="materialName", description="Name of the material (e.g., 'PLA')")
74
- slot_id: int = Field(alias="slotId", description="Slot ID from the material station, if applicable (0 if not or direct)")
75
- tool_id: int = Field(alias="toolId", description="Tool ID or extruder number")
76
125
 
77
- class Config:
78
- populate_by_name = True
126
+ model_config = ConfigDict(extra="forbid", populate_by_name=True)
127
+
128
+ filament_weight: float = Field(
129
+ alias="filamentWeight",
130
+ ge=0,
131
+ description="Calculated filament weight for this tool/material in the print",
132
+ )
133
+ material_color: str = Field(
134
+ alias="materialColor", description="Material color hex string (e.g., '#FFFF00')"
135
+ )
136
+ material_name: str = Field(
137
+ alias="materialName", description="Name of the material (e.g., 'PLA')"
138
+ )
139
+ slot_id: int = Field(
140
+ alias="slotId",
141
+ ge=0,
142
+ le=4,
143
+ description="Slot ID from the material station, if applicable (0 if not or direct)",
144
+ )
145
+ tool_id: int = Field(alias="toolId", ge=0, le=3, description="Tool ID or extruder number (0-3)")
146
+
147
+ @field_validator("material_color")
148
+ @classmethod
149
+ def validate_hex_color(cls, v: str) -> str:
150
+ """Validate that the color is a valid hex color code or empty string."""
151
+ import re
152
+
153
+ if v == "" or re.match(r"^#[0-9A-Fa-f]{6}$", v):
154
+ return v
155
+ raise ValueError(f"Invalid hex color format: {v}")
79
156
 
80
157
 
81
158
  class FFGcodeFileEntry(BaseModel):
82
159
  """Represents a single G-code file entry as returned by the /gcodeList endpoint, especially for printers like AD5X that provide detailed material info."""
83
- gcode_file_name: str = Field(alias="gcodeFileName", description="The name of the G-code file (e.g., 'FISH_PLA.3mf')")
84
- gcode_tool_cnt: Optional[int] = Field(default=None, alias="gcodeToolCnt", description="Number of tools/materials used in this G-code file")
85
- gcode_tool_datas: Optional[list[FFGcodeToolData]] = Field(default=None, alias="gcodeToolDatas", description="Array of detailed information for each tool/material")
86
- printing_time: int = Field(alias="printingTime", description="Estimated printing time in seconds")
87
- total_filament_weight: Optional[float] = Field(default=None, alias="totalFilamentWeight", description="Total estimated filament weight for the print")
88
- use_matl_station: Optional[bool] = Field(default=None, alias="useMatlStation", description="Indicates if the G-code file is intended for use with a material station")
89
160
 
90
- class Config:
91
- populate_by_name = True
161
+ model_config = ConfigDict(extra="forbid", populate_by_name=True)
162
+
163
+ gcode_file_name: str = Field(
164
+ alias="gcodeFileName", description="The name of the G-code file (e.g., 'FISH_PLA.3mf')"
165
+ )
166
+ gcode_tool_cnt: int | None = Field(
167
+ default=None,
168
+ ge=0,
169
+ le=4,
170
+ alias="gcodeToolCnt",
171
+ description="Number of tools/materials used in this G-code file",
172
+ )
173
+ gcode_tool_datas: list[FFGcodeToolData] | None = Field(
174
+ default=None,
175
+ max_length=4,
176
+ alias="gcodeToolDatas",
177
+ description="Array of detailed information for each tool/material",
178
+ )
179
+ printing_time: int = Field(
180
+ alias="printingTime", ge=0, description="Estimated printing time in seconds"
181
+ )
182
+ total_filament_weight: float | None = Field(
183
+ default=None,
184
+ ge=0,
185
+ alias="totalFilamentWeight",
186
+ description="Total estimated filament weight for the print",
187
+ )
188
+ use_matl_station: bool | None = Field(
189
+ default=None,
190
+ alias="useMatlStation",
191
+ description="Indicates if the G-code file is intended for use with a material station",
192
+ )
92
193
 
93
194
 
94
195
  class FFPrinterDetail(BaseModel):
@@ -97,64 +198,78 @@ class FFPrinterDetail(BaseModel):
97
198
  Properties are often in the printer's native naming format and may include string representations
98
199
  of boolean states (e.g., "open", "close").
99
200
  """
100
- auto_shutdown: Optional[str] = Field(default=None, alias="autoShutdown")
101
- auto_shutdown_time: Optional[int] = Field(default=None, alias="autoShutdownTime")
102
- camera_stream_url: Optional[str] = Field(default=None, alias="cameraStreamUrl")
103
- chamber_fan_speed: Optional[int] = Field(default=None, alias="chamberFanSpeed")
104
- chamber_target_temp: Optional[float] = Field(default=None, alias="chamberTargetTemp")
105
- chamber_temp: Optional[float] = Field(default=None, alias="chamberTemp")
106
- cooling_fan_speed: Optional[int] = Field(default=None, alias="coolingFanSpeed")
107
- cooling_fan_left_speed: Optional[int] = Field(default=None, alias="coolingFanLeftSpeed")
108
- cumulative_filament: Optional[float] = Field(default=None, alias="cumulativeFilament")
109
- cumulative_print_time: Optional[int] = Field(default=None, alias="cumulativePrintTime")
110
- current_print_speed: Optional[int] = Field(default=None, alias="currentPrintSpeed")
111
- door_status: Optional[str] = Field(default=None, alias="doorStatus")
112
- error_code: Optional[str] = Field(default=None, alias="errorCode")
113
- estimated_left_len: Optional[float] = Field(default=None, alias="estimatedLeftLen")
114
- estimated_left_weight: Optional[float] = Field(default=None, alias="estimatedLeftWeight")
115
- estimated_right_len: Optional[float] = Field(default=None, alias="estimatedRightLen")
116
- estimated_right_weight: Optional[float] = Field(default=None, alias="estimatedRightWeight")
117
- estimated_time: Optional[float] = Field(default=None, alias="estimatedTime")
118
- external_fan_status: Optional[str] = Field(default=None, alias="externalFanStatus")
119
- fill_amount: Optional[float] = Field(default=None, alias="fillAmount")
120
- firmware_version: Optional[str] = Field(default=None, alias="firmwareVersion")
121
- flash_register_code: Optional[str] = Field(default=None, alias="flashRegisterCode")
122
- has_matl_station: Optional[bool] = Field(default=None, alias="hasMatlStation")
123
- matl_station_info: Optional[MatlStationInfo] = Field(default=None, alias="matlStationInfo")
124
- indep_matl_info: Optional[IndepMatlInfo] = Field(default=None, alias="indepMatlInfo")
125
- has_left_filament: Optional[bool] = Field(default=None, alias="hasLeftFilament")
126
- has_right_filament: Optional[bool] = Field(default=None, alias="hasRightFilament")
127
- internal_fan_status: Optional[str] = Field(default=None, alias="internalFanStatus")
128
- ip_addr: Optional[str] = Field(default=None, alias="ipAddr")
129
- left_filament_type: Optional[str] = Field(default=None, alias="leftFilamentType")
130
- left_target_temp: Optional[float] = Field(default=None, alias="leftTargetTemp")
131
- left_temp: Optional[float] = Field(default=None, alias="leftTemp")
132
- light_status: Optional[str] = Field(default=None, alias="lightStatus")
133
- location: Optional[str] = Field(default=None, alias="location")
134
- mac_addr: Optional[str] = Field(default=None, alias="macAddr")
135
- measure: Optional[str] = Field(default=None, alias="measure")
136
- name: Optional[str] = Field(default=None, alias="name")
137
- nozzle_cnt: Optional[int] = Field(default=None, alias="nozzleCnt")
138
- nozzle_model: Optional[str] = Field(default=None, alias="nozzleModel")
139
- nozzle_style: Optional[int] = Field(default=None, alias="nozzleStyle")
140
- pid: Optional[int] = Field(default=None, alias="pid")
141
- plat_target_temp: Optional[float] = Field(default=None, alias="platTargetTemp")
142
- plat_temp: Optional[float] = Field(default=None, alias="platTemp")
143
- polar_register_code: Optional[str] = Field(default=None, alias="polarRegisterCode")
144
- print_duration: Optional[int] = Field(default=None, alias="printDuration")
145
- print_file_name: Optional[str] = Field(default=None, alias="printFileName")
146
- print_file_thumb_url: Optional[str] = Field(default=None, alias="printFileThumbUrl")
147
- print_layer: Optional[int] = Field(default=None, alias="printLayer")
148
- print_progress: Optional[float] = Field(default=None, alias="printProgress")
149
- print_speed_adjust: Optional[int] = Field(default=None, alias="printSpeedAdjust")
150
- remaining_disk_space: Optional[float] = Field(default=None, alias="remainingDiskSpace")
151
- right_filament_type: Optional[str] = Field(default=None, alias="rightFilamentType")
152
- right_target_temp: Optional[float] = Field(default=None, alias="rightTargetTemp")
153
- right_temp: Optional[float] = Field(default=None, alias="rightTemp")
154
- status: Optional[str] = Field(default=None, alias="status")
155
- target_print_layer: Optional[int] = Field(default=None, alias="targetPrintLayer")
156
- tvoc: Optional[float] = Field(default=None, alias="tvoc")
157
- z_axis_compensation: Optional[float] = Field(default=None, alias="zAxisCompensation")
201
+
202
+ model_config = ConfigDict(extra="allow")
203
+
204
+ auto_shutdown: str | None = Field(default=None, alias="autoShutdown")
205
+ auto_shutdown_time: int | None = Field(default=None, ge=0, alias="autoShutdownTime")
206
+ camera: int | None = Field(default=None, ge=0, alias="camera")
207
+ camera_stream_url: str | None = Field(default=None, alias="cameraStreamUrl")
208
+ chamber_fan_speed: int | None = Field(default=None, ge=0, le=100, alias="chamberFanSpeed")
209
+ chamber_target_temp: float | None = Field(
210
+ default=None, ge=-50, le=500, alias="chamberTargetTemp"
211
+ )
212
+ chamber_temp: float | None = Field(default=None, ge=-50, le=500, alias="chamberTemp")
213
+ clear_fan_status: str | None = Field(default=None, alias="clearFanStatus")
214
+ cooling_fan_speed: int | None = Field(default=None, ge=0, le=100, alias="coolingFanSpeed")
215
+ cooling_fan_left_speed: int | None = Field(
216
+ default=None, ge=0, le=100, alias="coolingFanLeftSpeed"
217
+ )
218
+ coordinate: list[float] | None = Field(default=None, alias="coordinate")
219
+ cumulative_filament: float | None = Field(default=None, ge=0, alias="cumulativeFilament")
220
+ cumulative_print_time: int | None = Field(default=None, ge=0, alias="cumulativePrintTime")
221
+ current_print_speed: int | None = Field(default=None, ge=0, le=200, alias="currentPrintSpeed")
222
+ door_status: str | None = Field(default=None, alias="doorStatus")
223
+ error_code: str | None = Field(default=None, alias="errorCode")
224
+ estimated_left_len: float | None = Field(default=None, ge=0, alias="estimatedLeftLen")
225
+ estimated_left_weight: float | None = Field(default=None, ge=0, alias="estimatedLeftWeight")
226
+ estimated_right_len: float | None = Field(default=None, ge=0, alias="estimatedRightLen")
227
+ estimated_right_weight: float | None = Field(default=None, ge=0, alias="estimatedRightWeight")
228
+ estimated_time: float | None = Field(default=None, ge=0, alias="estimatedTime")
229
+ extrude_ctrl: int | None = Field(default=None, ge=0, alias="extrudeCtrl")
230
+ external_fan_status: str | None = Field(default=None, alias="externalFanStatus")
231
+ fill_amount: float | None = Field(default=None, ge=0, le=100, alias="fillAmount")
232
+ firmware_version: str | None = Field(default=None, alias="firmwareVersion")
233
+ flash_register_code: str | None = Field(default=None, alias="flashRegisterCode")
234
+ has_matl_station: bool | None = Field(default=None, alias="hasMatlStation")
235
+ matl_station_info: MatlStationInfo | None = Field(default=None, alias="matlStationInfo")
236
+ indep_matl_info: IndepMatlInfo | None = Field(default=None, alias="indepMatlInfo")
237
+ has_left_filament: bool | None = Field(default=None, alias="hasLeftFilament")
238
+ has_right_filament: bool | None = Field(default=None, alias="hasRightFilament")
239
+ internal_fan_status: str | None = Field(default=None, alias="internalFanStatus")
240
+ ip_addr: str | None = Field(default=None, alias="ipAddr")
241
+ left_filament_type: str | None = Field(default=None, alias="leftFilamentType")
242
+ left_target_temp: float | None = Field(default=None, ge=-50, le=500, alias="leftTargetTemp")
243
+ left_temp: float | None = Field(default=None, ge=-50, le=500, alias="leftTemp")
244
+ light_status: str | None = Field(default=None, alias="lightStatus")
245
+ location: str | None = Field(default=None, alias="location")
246
+ mac_addr: str | None = Field(default=None, alias="macAddr")
247
+ measure: str | None = Field(default=None, alias="measure")
248
+ move_ctrl: int | None = Field(default=None, ge=0, alias="moveCtrl")
249
+ name: str | None = Field(default=None, alias="name")
250
+ nozzle_cnt: int | None = Field(default=None, ge=1, le=4, alias="nozzleCnt")
251
+ nozzle_model: str | None = Field(default=None, alias="nozzleModel")
252
+ nozzle_style: int | None = Field(default=None, ge=0, alias="nozzleStyle")
253
+ pid: int | None = Field(default=None, ge=0, alias="pid")
254
+ plat_target_temp: float | None = Field(default=None, ge=-50, le=500, alias="platTargetTemp")
255
+ plat_temp: float | None = Field(default=None, ge=-50, le=500, alias="platTemp")
256
+ polar_register_code: str | None = Field(default=None, alias="polarRegisterCode")
257
+ print_duration: int | None = Field(default=None, ge=0, alias="printDuration")
258
+ print_file_name: str | None = Field(default=None, alias="printFileName")
259
+ print_file_thumb_url: str | None = Field(default=None, alias="printFileThumbUrl")
260
+ print_layer: int | None = Field(default=None, ge=0, alias="printLayer")
261
+ print_progress: float | None = Field(default=None, ge=0, le=100, alias="printProgress")
262
+ print_speed_adjust: int | None = Field(default=None, ge=0, le=200, alias="printSpeedAdjust")
263
+ remaining_disk_space: float | None = Field(default=None, ge=0, alias="remainingDiskSpace")
264
+ right_filament_type: str | None = Field(default=None, alias="rightFilamentType")
265
+ right_target_temp: float | None = Field(default=None, ge=-50, le=500, alias="rightTargetTemp")
266
+ right_temp: float | None = Field(default=None, ge=-50, le=500, alias="rightTemp")
267
+ status: str | None = Field(default=None, alias="status")
268
+ target_print_layer: int | None = Field(default=None, ge=0, alias="targetPrintLayer")
269
+ tvoc: float | None = Field(default=None, ge=0, alias="tvoc")
270
+ z_axis_compensation: float | None = Field(
271
+ default=None, ge=-10, le=10, alias="zAxisCompensation"
272
+ )
158
273
 
159
274
 
160
275
  class FFMachineInfo(BaseModel):
@@ -162,6 +277,9 @@ class FFMachineInfo(BaseModel):
162
277
  Represents a structured and user-friendly model of the printer's information and state.
163
278
  This interface is populated by transforming data from FFPrinterDetail.
164
279
  """
280
+
281
+ model_config = ConfigDict(extra="forbid")
282
+
165
283
  # Auto-shutdown settings
166
284
  auto_shutdown: bool = False
167
285
  auto_shutdown_time: int = 0
@@ -172,7 +290,7 @@ class FFMachineInfo(BaseModel):
172
290
  # Fan speeds
173
291
  chamber_fan_speed: int = 0
174
292
  cooling_fan_speed: int = 0
175
- cooling_fan_left_speed: Optional[int] = None
293
+ cooling_fan_left_speed: int | None = None
176
294
 
177
295
  # Cumulative stats
178
296
  cumulative_filament: float = 0.0
@@ -242,6 +360,13 @@ class FFMachineInfo(BaseModel):
242
360
  formatted_total_run_time: str = "0h:0m"
243
361
 
244
362
  # AD5X Material Station
245
- has_matl_station: Optional[bool] = None
246
- matl_station_info: Optional[MatlStationInfo] = None
247
- indep_matl_info: Optional[IndepMatlInfo] = None
363
+ has_matl_station: bool | None = None
364
+ matl_station_info: MatlStationInfo | None = None
365
+ indep_matl_info: IndepMatlInfo | None = None
366
+
367
+ @model_validator(mode="after")
368
+ def validate_print_progress(self) -> FFMachineInfo:
369
+ """Ensure print progress integer matches float value."""
370
+ if self.print_progress is not None:
371
+ self.print_progress_int = max(0, min(100, int(self.print_progress * 100)))
372
+ return self