anycubic-cloud-api 0.1.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 (33) hide show
  1. anycubic_cloud_api/__init__.py +62 -0
  2. anycubic_cloud_api/anycubic_api.py +7 -0
  3. anycubic_cloud_api/api/base.py +400 -0
  4. anycubic_cloud_api/api/functions.py +2055 -0
  5. anycubic_cloud_api/api/mqtt.py +449 -0
  6. anycubic_cloud_api/const/api_endpoints.py +106 -0
  7. anycubic_cloud_api/const/const.py +47 -0
  8. anycubic_cloud_api/const/enums.py +97 -0
  9. anycubic_cloud_api/const/mqtt.py +13 -0
  10. anycubic_cloud_api/data_models/consumable.py +116 -0
  11. anycubic_cloud_api/data_models/files.py +429 -0
  12. anycubic_cloud_api/data_models/gcode_file.py +107 -0
  13. anycubic_cloud_api/data_models/orders.py +369 -0
  14. anycubic_cloud_api/data_models/print_response.py +71 -0
  15. anycubic_cloud_api/data_models/print_speed_mode.py +61 -0
  16. anycubic_cloud_api/data_models/printer.py +2753 -0
  17. anycubic_cloud_api/data_models/printer_properties.py +1107 -0
  18. anycubic_cloud_api/data_models/printing_settings.py +130 -0
  19. anycubic_cloud_api/data_models/project.py +1234 -0
  20. anycubic_cloud_api/exceptions/error_strings.py +340 -0
  21. anycubic_cloud_api/exceptions/exceptions.py +91 -0
  22. anycubic_cloud_api/helpers/helpers.py +240 -0
  23. anycubic_cloud_api/models/auth.py +396 -0
  24. anycubic_cloud_api/models/cloud_upload.py +249 -0
  25. anycubic_cloud_api/models/http.py +29 -0
  26. anycubic_cloud_api/resources/anycubic_mqqt_tls_ca.crt +24 -0
  27. anycubic_cloud_api/resources/anycubic_mqqt_tls_client.crt +24 -0
  28. anycubic_cloud_api/resources/anycubic_mqqt_tls_client.key +28 -0
  29. anycubic_cloud_api-0.1.0.dist-info/METADATA +79 -0
  30. anycubic_cloud_api-0.1.0.dist-info/RECORD +33 -0
  31. anycubic_cloud_api-0.1.0.dist-info/WHEEL +5 -0
  32. anycubic_cloud_api-0.1.0.dist-info/licenses/LICENSE +674 -0
  33. anycubic_cloud_api-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,2055 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import json
5
+ from typing import (
6
+ Any,
7
+ Literal,
8
+ overload,
9
+ )
10
+
11
+ from ..const.api_endpoints import API_ENDPOINT
12
+ from ..const.const import (
13
+ MAX_PROJECT_IMAGE_SEARCH_COUNT,
14
+ MAX_PROJECT_LIST_RESULTS,
15
+ AnycubicServerMessage,
16
+ )
17
+ from ..const.enums import (
18
+ AnycubicFeedType,
19
+ AnycubicOrderID,
20
+ AnycubicPrintStatus,
21
+ )
22
+ from ..data_models.files import AnycubicCloudFile, AnycubicCloudStore
23
+ from ..data_models.gcode_file import AnycubicGcodeFile
24
+ from ..data_models.orders import (
25
+ AnycubicBaseOrderRequest,
26
+ AnycubicBaseProjectOrderRequest,
27
+ AnycubicBaseStartPrintRequest,
28
+ AnycubicCameraToken,
29
+ AnycubicProjectCtrlOrderRequest,
30
+ AnycubicProjectOrderRequest,
31
+ AnycubicStartPrintRequestCloud,
32
+ AnycubicStartPrintRequestLocal,
33
+ )
34
+ from ..data_models.print_response import AnycubicPrintResponse
35
+ from ..data_models.printer import AnycubicPrinter
36
+ from ..data_models.printer_properties import AnycubicMaterialColor, AnycubicMaterialMapping
37
+ from ..data_models.printing_settings import AnycubicPrintingSettings
38
+ from ..data_models.project import AnycubicProject
39
+ from ..exceptions.error_strings import (
40
+ ErrorsAPIParsing,
41
+ ErrorsCloudUpload,
42
+ ErrorsDataParsing,
43
+ ErrorsFileNotFound,
44
+ ErrorsGcodeParsing,
45
+ ErrorsGeneral,
46
+ )
47
+ from ..exceptions.exceptions import (
48
+ AnycubicAPIError,
49
+ AnycubicAPIParsingError,
50
+ AnycubicCloudUploadError,
51
+ AnycubicDataParsingError,
52
+ AnycubicFileNotFoundError,
53
+ AnycubicGcodeParsingError,
54
+ )
55
+ from ..models.cloud_upload import AnycubicCloudUpload
56
+ from .base import AnycubicAPIBase
57
+
58
+
59
+ class AnycubicAPIFunctions(AnycubicAPIBase):
60
+
61
+ #
62
+ #
63
+ # General API Calls
64
+ # ------------------------------------------
65
+
66
+ async def _lock_storage_space(
67
+ self,
68
+ file_size: int,
69
+ file_name: str | None,
70
+ is_temp_file: int | bool = 0,
71
+ raw_data: bool = False,
72
+ ) -> dict[str, Any]:
73
+ params = {
74
+ 'size': file_size,
75
+ 'name': file_name,
76
+ 'is_temp_file': int(is_temp_file),
77
+ }
78
+ resp = await self._fetch_api_resp(endpoint=API_ENDPOINT.lock_storage_space, params=params)
79
+ if raw_data:
80
+ return resp
81
+
82
+ data: dict[str, Any] = resp['data']
83
+
84
+ return data
85
+
86
+ async def _unlock_storage_space(
87
+ self,
88
+ file_id: int,
89
+ is_delete_cos: int | bool = 0,
90
+ raw_data: bool = False,
91
+ ) -> dict[str, Any] | str:
92
+ params = {
93
+ 'id': file_id,
94
+ 'is_delete_cos': int(is_delete_cos),
95
+ }
96
+ resp = await self._fetch_api_resp(endpoint=API_ENDPOINT.unlock_storage_space, params=params)
97
+ if raw_data:
98
+ return resp
99
+
100
+ data: dict[str, Any] = resp['data']
101
+
102
+ return data
103
+
104
+ @overload
105
+ async def _claim_file_upload_from_aws(
106
+ self,
107
+ file_id: int,
108
+ ) -> int: ...
109
+
110
+ @overload
111
+ async def _claim_file_upload_from_aws(
112
+ self,
113
+ file_id: int,
114
+ raw_data: Literal[True] = ...,
115
+ ) -> dict[str, Any]: ...
116
+
117
+ async def _claim_file_upload_from_aws(
118
+ self,
119
+ file_id: int,
120
+ raw_data: bool = False,
121
+ ) -> dict[str, Any] | int:
122
+ params = {
123
+ 'user_lock_space_id': file_id,
124
+ }
125
+ resp = await self._fetch_api_resp(endpoint=API_ENDPOINT.new_file_upload, params=params)
126
+ if raw_data:
127
+ return resp
128
+
129
+ if (
130
+ not resp or (
131
+ 'data' not in resp
132
+ or not resp['data']
133
+ or 'id' not in resp['data']
134
+ )
135
+ ):
136
+ raise AnycubicCloudUploadError(
137
+ ErrorsCloudUpload.file_claim_failed.format(resp)
138
+ )
139
+
140
+ data = resp['data']
141
+
142
+ return int(data['id'])
143
+
144
+ async def _set_printer_name(
145
+ self,
146
+ printer_id: int,
147
+ new_name: str,
148
+ raw_data: bool = False,
149
+ ) -> dict[str, Any]:
150
+ params = {
151
+ 'id': str(printer_id),
152
+ 'name': str(new_name),
153
+ }
154
+ resp = await self._fetch_api_resp(
155
+ endpoint=API_ENDPOINT.printer_update_name,
156
+ params=params
157
+ )
158
+ if raw_data:
159
+ return resp
160
+
161
+ data: dict[str, Any] = resp['data']
162
+
163
+ return data
164
+
165
+ async def _update_printer_firmware(
166
+ self,
167
+ printer_id: int,
168
+ current_fw_version: str,
169
+ raw_data: bool = False,
170
+ ) -> dict[str, Any]:
171
+ query = {
172
+ 'id': str(printer_id),
173
+ 'target_version': str(current_fw_version),
174
+ }
175
+ resp = await self._fetch_api_resp(
176
+ endpoint=API_ENDPOINT.printer_firmware_update,
177
+ query=query
178
+ )
179
+ if raw_data:
180
+ return resp
181
+
182
+ data: dict[str, Any] = resp['data']
183
+
184
+ return data
185
+
186
+ async def _update_muli_color_box_firmware(
187
+ self,
188
+ printer_id: int,
189
+ box_id: int,
190
+ raw_data: bool = False,
191
+ ) -> dict[str, Any]:
192
+ params = {
193
+ 'id': int(printer_id),
194
+ 'box_id': int(box_id),
195
+ }
196
+ resp = await self._fetch_api_resp(
197
+ endpoint=API_ENDPOINT.printer_multi_color_box_firmware_update,
198
+ params=params,
199
+ )
200
+ if raw_data:
201
+ return resp
202
+
203
+ data: dict[str, Any] = resp['data']
204
+
205
+ return data
206
+
207
+ @overload
208
+ async def get_user_cloud_store(
209
+ self,
210
+ ) -> AnycubicCloudStore | None: ...
211
+
212
+ @overload
213
+ async def get_user_cloud_store(
214
+ self,
215
+ raw_data: Literal[True] = ...,
216
+ ) -> dict[str, Any]: ...
217
+
218
+ async def get_user_cloud_store(
219
+ self,
220
+ raw_data: bool = False,
221
+ ) -> AnycubicCloudStore | None | dict[str, Any]:
222
+ resp = await self._fetch_api_resp(endpoint=API_ENDPOINT.user_store)
223
+ if raw_data:
224
+ return resp
225
+
226
+ data = resp['data']
227
+
228
+ return AnycubicCloudStore.from_json(data)
229
+
230
+ @overload
231
+ async def get_user_cloud_files(
232
+ self,
233
+ printable: int | None = None,
234
+ machine_type: int | None = None,
235
+ page: int = 1,
236
+ limit: int = 10,
237
+ ) -> list[AnycubicCloudFile] | None: ...
238
+
239
+ @overload
240
+ async def get_user_cloud_files(
241
+ self,
242
+ printable: int | None = None,
243
+ machine_type: int | None = None,
244
+ page: int = 1,
245
+ limit: int = 10,
246
+ raw_data: Literal[True] = ...,
247
+ ) -> dict[str, Any]: ...
248
+
249
+ async def get_user_cloud_files(
250
+ self,
251
+ printable: int | None = None,
252
+ machine_type: int | None = None,
253
+ page: int = 1,
254
+ limit: int = 10,
255
+ raw_data: bool = False,
256
+ ) -> list[AnycubicCloudFile] | None | dict[str, Any]:
257
+ params = {
258
+ 'page': page,
259
+ 'limit': limit,
260
+ }
261
+ if printable is not None:
262
+ params['printable'] = int(printable)
263
+ if machine_type is not None:
264
+ params['machine_type'] = int(machine_type)
265
+
266
+ resp = await self._fetch_api_resp(endpoint=API_ENDPOINT.user_files, params=params)
267
+ if raw_data:
268
+ return resp
269
+
270
+ data = resp['data']
271
+
272
+ if not data:
273
+ return list()
274
+
275
+ file_list = list()
276
+
277
+ for x in data:
278
+ file = AnycubicCloudFile.from_json(x)
279
+ if file:
280
+ file_list.append(file)
281
+ else:
282
+ raise AnycubicDataParsingError(ErrorsDataParsing.user_cloud_files.format(data))
283
+
284
+ return file_list
285
+
286
+ async def get_user_cloud_files_data_object(
287
+ self,
288
+ printable: int | None = None,
289
+ machine_type: int | None = None,
290
+ page: int = 1,
291
+ limit: int = 10,
292
+ ) -> list[dict[str, Any]] | None:
293
+ user_files = await self.get_user_cloud_files(
294
+ printable=printable,
295
+ machine_type=machine_type,
296
+ page=page,
297
+ limit=limit,
298
+ )
299
+
300
+ if not user_files or len(user_files) < 1:
301
+ return None
302
+
303
+ file_list = list([
304
+ file.data_object for file in user_files
305
+ ])
306
+ return file_list
307
+
308
+ @overload
309
+ async def fetch_project_gcode_info_fdm(
310
+ self,
311
+ project_id: int,
312
+ ) -> AnycubicProject | None: ...
313
+
314
+ @overload
315
+ async def fetch_project_gcode_info_fdm(
316
+ self,
317
+ project_id: int,
318
+ raw_data: Literal[True] = ...,
319
+ ) -> dict[str, Any]: ...
320
+
321
+ async def fetch_project_gcode_info_fdm(
322
+ self,
323
+ project_id: int,
324
+ raw_data: bool = False,
325
+ ) -> AnycubicProject | None | dict[str, Any]:
326
+ query = {
327
+ 'id': str(project_id),
328
+ }
329
+ resp = await self._fetch_api_resp(
330
+ endpoint=API_ENDPOINT.project_gcode_info_fdm,
331
+ query=query
332
+ )
333
+ if raw_data:
334
+ return resp
335
+
336
+ data = resp['data']
337
+
338
+ return AnycubicProject.from_gcode_json(self, data)
339
+
340
+ @overload
341
+ async def delete_file_from_cloud(
342
+ self,
343
+ file_id: int,
344
+ ) -> bool: ...
345
+
346
+ @overload
347
+ async def delete_file_from_cloud(
348
+ self,
349
+ file_id: int,
350
+ raw_data: Literal[True] = ...,
351
+ ) -> dict[str, Any]: ...
352
+
353
+ async def delete_file_from_cloud(
354
+ self,
355
+ file_id: int,
356
+ raw_data: bool = False,
357
+ ) -> bool | dict[str, Any]:
358
+ params = {
359
+ 'idArr': [file_id],
360
+ }
361
+ resp = await self._fetch_api_resp(endpoint=API_ENDPOINT.delete_cloud_file, params=params)
362
+ if raw_data:
363
+ return resp
364
+
365
+ data = resp['data']
366
+
367
+ return True if data == '' else False
368
+
369
+ #
370
+ #
371
+ # WIP Unused API Calls
372
+ # ------------------------------------------
373
+
374
+ async def _get_print_history(self) -> None:
375
+ resp = await self._fetch_api_resp(endpoint=API_ENDPOINT.print_history)
376
+ self._log_to_debug(f"print_history output:\n{json.dumps(resp)}")
377
+
378
+ async def _get_project_monitor(self, project_id: int) -> None:
379
+ query = {
380
+ 'id': str(project_id)
381
+ }
382
+ resp = await self._fetch_api_resp(endpoint=API_ENDPOINT.project_monitor, query=query)
383
+ self._log_to_debug(f"project_monitor output:\n{json.dumps(resp)}")
384
+
385
+ #
386
+ #
387
+ # ORDER Functions
388
+ # ------------------------------------------
389
+
390
+ @overload
391
+ async def _send_anycubic_order(
392
+ self,
393
+ order_request: AnycubicBaseOrderRequest,
394
+ ) -> str | None: ...
395
+
396
+ @overload
397
+ async def _send_anycubic_order(
398
+ self,
399
+ order_request: AnycubicBaseOrderRequest,
400
+ raw_data: Literal[True] = ...
401
+ ) -> dict[str, Any]: ...
402
+
403
+ @overload
404
+ async def _send_anycubic_order(
405
+ self,
406
+ order_request: AnycubicBaseOrderRequest,
407
+ raw_data: bool = False,
408
+ ) -> str | None | dict[str, Any]: ...
409
+
410
+ async def _send_anycubic_order(
411
+ self,
412
+ order_request: AnycubicBaseOrderRequest,
413
+ raw_data: bool = False,
414
+ ) -> str | None | dict[str, Any]:
415
+ params = order_request.order_request_data
416
+
417
+ resp = await self._fetch_api_resp(endpoint=API_ENDPOINT.send_order, params=params)
418
+ if raw_data:
419
+ return resp
420
+
421
+ error_message = resp.get('msg') if resp is not None else None
422
+
423
+ if resp is None or resp.get('data') is None:
424
+ if resp is not None and resp.get('data') is None and error_message == AnycubicServerMessage.FILE_NOT_FOUND:
425
+ raise AnycubicFileNotFoundError(ErrorsFileNotFound.in_cloud)
426
+ else:
427
+ raise AnycubicAPIError(ErrorsGeneral.send_order_fail.format(error_message))
428
+
429
+ data: str | None = resp['data'].get('msgid')
430
+
431
+ if data is None:
432
+ self._log_to_error(f"Empty reply when sending order to Anycubic Cloud, message: {error_message}")
433
+
434
+ return data
435
+
436
+ async def _send_order_multi_color_box_set_slot(
437
+ self,
438
+ printer: AnycubicPrinter,
439
+ slot_index: int,
440
+ slot_color: AnycubicMaterialColor,
441
+ slot_material_type: str,
442
+ box_id: int = 0,
443
+ ) -> str | None:
444
+ if not printer:
445
+ return None
446
+
447
+ slot_params = {
448
+ 'color': slot_color.data,
449
+ 'index': slot_index,
450
+ 'type': slot_material_type,
451
+ }
452
+
453
+ order_params = {
454
+ 'id': box_id,
455
+ 'slots': [
456
+ slot_params,
457
+ ]
458
+ }
459
+
460
+ order_data = {
461
+ 'multi_color_box': [
462
+ order_params,
463
+ ]
464
+ }
465
+
466
+ return await self._send_anycubic_order(
467
+ order_request=AnycubicProjectOrderRequest(
468
+ order_id=AnycubicOrderID.MULTI_COLOR_BOX_SET_SLOT,
469
+ printer_id=printer.id,
470
+ project_id=0,
471
+ order_data=order_data,
472
+ ),
473
+ )
474
+
475
+ async def _send_order_multi_color_box_feed_filament(
476
+ self,
477
+ printer: AnycubicPrinter,
478
+ slot_index: int,
479
+ feed_type: int,
480
+ box_id: int = 0,
481
+ ) -> str | None:
482
+ if not printer:
483
+ return None
484
+
485
+ if feed_type == AnycubicFeedType.Feed and slot_index < 0:
486
+ return None
487
+
488
+ if feed_type == AnycubicFeedType.Retract:
489
+ slot_index = -1
490
+
491
+ feed_params = {
492
+ 'slot_index': slot_index,
493
+ 'type': feed_type,
494
+ }
495
+
496
+ order_params = {
497
+ 'id': box_id,
498
+ 'feed_status': feed_params
499
+ }
500
+
501
+ order_data = {
502
+ 'multi_color_box': [
503
+ order_params,
504
+ ]
505
+ }
506
+
507
+ return await self._send_anycubic_order(
508
+ order_request=AnycubicProjectOrderRequest(
509
+ order_id=AnycubicOrderID.FEED_FILAMENT,
510
+ printer_id=printer.id,
511
+ project_id=0,
512
+ order_data=order_data,
513
+ ),
514
+ )
515
+
516
+ async def _send_order_multi_color_box_dry(
517
+ self,
518
+ printer: AnycubicPrinter,
519
+ order_params: dict[str, Any] | list[dict[str, Any]],
520
+ ) -> str | None:
521
+ if not printer:
522
+ return None
523
+
524
+ if not isinstance(order_params, list):
525
+ order_params = [order_params]
526
+
527
+ box_list = list()
528
+
529
+ for box_id, order in enumerate(order_params):
530
+ box_list.append({
531
+ 'drying_status': {
532
+ 'duration': int(order.get('duration', 0)),
533
+ 'remain_time': None,
534
+ 'status': int(order.get('status', 0)),
535
+ 'target_temp': int(order.get('target_temp', 40)),
536
+ },
537
+ 'id': int(order.get('box_id', box_id)),
538
+ })
539
+
540
+ order_data = {
541
+ 'multi_color_box': box_list
542
+ }
543
+
544
+ return await self._send_anycubic_order(
545
+ order_request=AnycubicProjectOrderRequest(
546
+ order_id=AnycubicOrderID.MULTI_COLOR_BOX_DRY,
547
+ printer_id=printer.id,
548
+ project_id=0,
549
+ order_data=order_data,
550
+ ),
551
+ )
552
+
553
+ async def _send_order_multi_color_auto_feed(
554
+ self,
555
+ printer: AnycubicPrinter,
556
+ enabled: bool,
557
+ box_id: int = 0,
558
+ ) -> str | None:
559
+ if not printer:
560
+ return None
561
+
562
+ box_list = list([
563
+ {
564
+ 'id': box_id,
565
+ 'auto_feed': int(enabled),
566
+ }
567
+ ])
568
+
569
+ order_data = {
570
+ 'multi_color_box': box_list
571
+ }
572
+
573
+ return await self._send_anycubic_order(
574
+ order_request=AnycubicProjectOrderRequest(
575
+ order_id=AnycubicOrderID.MULTI_COLOR_BOX_AUTO_FEED,
576
+ printer_id=printer.id,
577
+ project_id=0,
578
+ order_data=order_data,
579
+ ),
580
+ )
581
+
582
+ async def _send_order_start_print(
583
+ self,
584
+ printer: AnycubicPrinter,
585
+ print_request: AnycubicBaseStartPrintRequest,
586
+ ams_box_mapping: list[AnycubicMaterialMapping] | None = None,
587
+ ) -> str | None:
588
+ if not printer:
589
+ return None
590
+
591
+ return await self._send_anycubic_order(
592
+ order_request=AnycubicProjectCtrlOrderRequest(
593
+ order_id=AnycubicOrderID.START_PRINT,
594
+ printer_id=printer.id,
595
+ project_id=0,
596
+ order_data=print_request.data,
597
+ ams_box_mapping=ams_box_mapping,
598
+ print_settings=None,
599
+ ),
600
+ )
601
+
602
+ async def _send_order_pause_print(
603
+ self,
604
+ printer: AnycubicPrinter,
605
+ project: AnycubicProject,
606
+ ) -> str | None:
607
+ if not printer:
608
+ return None
609
+
610
+ if not project:
611
+ return None
612
+
613
+ return await self._send_anycubic_order(
614
+ order_request=AnycubicProjectCtrlOrderRequest(
615
+ order_id=AnycubicOrderID.PAUSE_PRINT,
616
+ printer_id=printer.id,
617
+ project_id=project.id,
618
+ order_data=None,
619
+ ams_box_mapping=None,
620
+ print_settings=None,
621
+ ),
622
+ )
623
+
624
+ async def _send_order_resume_print(
625
+ self,
626
+ printer: AnycubicPrinter,
627
+ project: AnycubicProject,
628
+ ) -> str | None:
629
+ if not printer:
630
+ return None
631
+
632
+ if not project:
633
+ return None
634
+
635
+ return await self._send_anycubic_order(
636
+ order_request=AnycubicProjectCtrlOrderRequest(
637
+ order_id=AnycubicOrderID.RESUME_PRINT,
638
+ printer_id=printer.id,
639
+ project_id=project.id,
640
+ order_data=None,
641
+ ams_box_mapping=None,
642
+ print_settings=None,
643
+ ),
644
+ )
645
+
646
+ async def _send_order_stop_print(
647
+ self,
648
+ printer: AnycubicPrinter,
649
+ project: AnycubicProject,
650
+ ) -> str | None:
651
+ if not printer:
652
+ return None
653
+
654
+ if not project:
655
+ return None
656
+
657
+ return await self._send_anycubic_order(
658
+ order_request=AnycubicProjectCtrlOrderRequest(
659
+ order_id=AnycubicOrderID.STOP_PRINT,
660
+ printer_id=printer.id,
661
+ project_id=project.id,
662
+ order_data=None,
663
+ ams_box_mapping=None,
664
+ print_settings=None,
665
+ ),
666
+ )
667
+
668
+ async def _send_order_change_print_settings(
669
+ self,
670
+ printer: AnycubicPrinter,
671
+ print_settings: AnycubicPrintingSettings,
672
+ project: AnycubicProject | None = None,
673
+ ) -> str | None:
674
+ if not printer:
675
+ return None
676
+
677
+ if not project and not printer.latest_project:
678
+ return None
679
+
680
+ if not project:
681
+ project = printer.latest_project
682
+
683
+ assert project
684
+
685
+ project.validate_new_print_settings(print_settings)
686
+
687
+ order_data = {
688
+ 'settings': print_settings.settings_data
689
+ }
690
+
691
+ return await self._send_anycubic_order(
692
+ order_request=AnycubicProjectOrderRequest(
693
+ order_id=AnycubicOrderID.PRINT_SETTINGS,
694
+ printer_id=printer.id,
695
+ project_id=project.id,
696
+ order_data=order_data,
697
+ ),
698
+ )
699
+
700
+ async def _send_order_list_local_files(
701
+ self,
702
+ printer: AnycubicPrinter,
703
+ ) -> str | None:
704
+ """ Response sent via MQTT """
705
+ if not printer:
706
+ return None
707
+
708
+ return await self._send_anycubic_order(
709
+ order_request=AnycubicProjectOrderRequest(
710
+ order_id=AnycubicOrderID.LIST_LOCAL_FILES,
711
+ printer_id=printer.id,
712
+ project_id=0,
713
+ ),
714
+ )
715
+
716
+ async def _send_order_list_udisk_files(
717
+ self,
718
+ printer: AnycubicPrinter,
719
+ ) -> str | None:
720
+ """ Response sent via MQTT """
721
+ if not printer:
722
+ return None
723
+
724
+ return await self._send_anycubic_order(
725
+ order_request=AnycubicProjectOrderRequest(
726
+ order_id=AnycubicOrderID.LIST_UDISK_FILES,
727
+ printer_id=printer.id,
728
+ project_id=0,
729
+ ),
730
+ )
731
+
732
+ async def _send_order_delete_local_file(
733
+ self,
734
+ printer: AnycubicPrinter,
735
+ file_name: str,
736
+ ) -> str | None:
737
+ """ Response sent via MQTT """
738
+ if not printer:
739
+ return None
740
+
741
+ order_data = {
742
+ 'filename': file_name,
743
+ 'filetype': -1,
744
+ 'path': '/',
745
+ }
746
+
747
+ return await self._send_anycubic_order(
748
+ order_request=AnycubicProjectOrderRequest(
749
+ order_id=AnycubicOrderID.DELETE_LOCAL_FILE,
750
+ printer_id=printer.id,
751
+ project_id=0,
752
+ order_data=order_data,
753
+ ),
754
+ )
755
+
756
+ async def _send_order_delete_udisk_file(
757
+ self,
758
+ printer: AnycubicPrinter,
759
+ file_name: str,
760
+ ) -> str | None:
761
+ """ Response sent via MQTT """
762
+ if not printer:
763
+ return None
764
+
765
+ order_data = {
766
+ 'filename': file_name,
767
+ 'filetype': -1,
768
+ 'path': '/',
769
+ }
770
+
771
+ return await self._send_anycubic_order(
772
+ order_request=AnycubicProjectOrderRequest(
773
+ order_id=AnycubicOrderID.DELETE_UDISK_FILE,
774
+ printer_id=printer.id,
775
+ project_id=0,
776
+ order_data=order_data,
777
+ ),
778
+ )
779
+
780
+ async def _send_order_query_peripherals(
781
+ self,
782
+ printer: AnycubicPrinter,
783
+ ) -> str | None:
784
+ """
785
+ Response is sent over MQTT.
786
+ """
787
+ if not printer:
788
+ return None
789
+
790
+ return await self._send_anycubic_order(
791
+ order_request=AnycubicBaseProjectOrderRequest(
792
+ order_id=AnycubicOrderID.QUERY_PERIPHERALS,
793
+ printer_id=printer.id,
794
+ project_id=0,
795
+ ),
796
+ )
797
+
798
+ async def _send_order_get_light_status(
799
+ self,
800
+ printer: AnycubicPrinter,
801
+ project: AnycubicProject,
802
+ ) -> str | None:
803
+ """
804
+ Response is sent over MQTT.
805
+ """
806
+ if not printer:
807
+ return None
808
+
809
+ if not project:
810
+ return None
811
+
812
+ return await self._send_anycubic_order(
813
+ order_request=AnycubicBaseProjectOrderRequest(
814
+ order_id=AnycubicOrderID.GET_LIGHT_STATUS,
815
+ printer_id=printer.id,
816
+ project_id=project.id,
817
+ ),
818
+ )
819
+
820
+ #
821
+ #
822
+ # WIP Unused ORDER Functions
823
+ # ------------------------------------------
824
+
825
+ async def _send_anycubic_camera_open_order(
826
+ self,
827
+ printer: AnycubicPrinter,
828
+ raw_data: bool = False,
829
+ ) -> AnycubicCameraToken | None | dict[str, Any]:
830
+ if not printer:
831
+ return None
832
+
833
+ order_request = AnycubicBaseOrderRequest(
834
+ order_id=int(AnycubicOrderID.CAMERA_OPEN),
835
+ printer_id=printer.id,
836
+ )
837
+ resp = await self._fetch_api_resp(
838
+ endpoint=API_ENDPOINT.send_order,
839
+ params=order_request.order_request_data
840
+ )
841
+ if raw_data:
842
+ return resp
843
+
844
+ data = resp['data']
845
+
846
+ token = AnycubicCameraToken(
847
+ secret_id=data['token']['tmpSecretId'],
848
+ secret_key=data['token']['tmpSecretKey'],
849
+ session_token=data['token']['sessionToken'],
850
+ region=data['token']['region'],
851
+ msg_id=data['msgid'],
852
+ )
853
+ return token
854
+
855
+ async def _send_order_multi_color_box_get_info(
856
+ self,
857
+ printer: AnycubicPrinter,
858
+ ) -> str | None:
859
+ """
860
+ Response is sent over MQTT.
861
+ """
862
+ if not printer:
863
+ return None
864
+
865
+ return await self._send_anycubic_order(
866
+ order_request=AnycubicBaseProjectOrderRequest(
867
+ order_id=AnycubicOrderID.MULTI_COLOR_BOX_GET_INFO,
868
+ printer_id=printer.id,
869
+ project_id=0,
870
+ ),
871
+ )
872
+
873
+ async def _send_order_set_light_status(
874
+ self,
875
+ printer: AnycubicPrinter,
876
+ project: AnycubicProject,
877
+ light_on: bool,
878
+ light_type: int | None = None,
879
+ brightness: int | None = None,
880
+ ) -> str | None:
881
+ if not printer:
882
+ return None
883
+
884
+ if not project:
885
+ return None
886
+
887
+ # Printers report their own light type (the Kobra S1 uses 2), so prefer
888
+ # that over guessing; only fall back to 1 if nothing has been reported.
889
+ if light_type is None:
890
+ light_type = printer.light_type if printer.light_type is not None else 1
891
+
892
+ order_data = {
893
+ 'type': light_type,
894
+ 'status': 1 if light_on else 0,
895
+ 'brightness': int(brightness if brightness is not None else 100) if light_on else 0,
896
+ }
897
+
898
+ return await self._send_anycubic_order(
899
+ order_request=AnycubicProjectOrderRequest(
900
+ order_id=AnycubicOrderID.SET_LIGHT_STATUS,
901
+ printer_id=printer.id,
902
+ project_id=project.id,
903
+ order_data=order_data,
904
+ ),
905
+ )
906
+
907
+ async def _send_order_print_local_file(
908
+ self,
909
+ printer: AnycubicPrinter,
910
+ file_name: str,
911
+ file_path: str = "",
912
+ ) -> str | None:
913
+ if not printer:
914
+ return None
915
+
916
+ print_request = AnycubicStartPrintRequestLocal(
917
+ filename=file_name,
918
+ filepath=file_path,
919
+ )
920
+
921
+ return await self._send_order_start_print(
922
+ printer=printer,
923
+ print_request=print_request
924
+ )
925
+
926
+ #
927
+ #
928
+ # Main API Functions
929
+ # ------------------------------------------
930
+
931
+ async def set_printer_name(
932
+ self,
933
+ printer: AnycubicPrinter,
934
+ new_name: str,
935
+ ) -> str | None:
936
+ if not printer:
937
+ return None
938
+
939
+ if not new_name or len(str(new_name)) < 1:
940
+ return None
941
+
942
+ new_name = str(new_name)
943
+
944
+ old_name = printer.name
945
+
946
+ resp = await self._set_printer_name(
947
+ printer_id=printer.id,
948
+ new_name=new_name,
949
+ )
950
+
951
+ if resp.get('name') == new_name:
952
+ return new_name
953
+
954
+ elif resp.get('name') == old_name:
955
+ raise AnycubicAPIError(ErrorsGeneral.name_change_fail_revert)
956
+
957
+ else:
958
+ raise AnycubicAPIError(ErrorsGeneral.name_change_fail_unknown)
959
+
960
+ async def update_printer_firmware(
961
+ self,
962
+ printer: AnycubicPrinter,
963
+ ) -> str | None:
964
+ if not printer:
965
+ return None
966
+
967
+ if not printer.fw_version:
968
+ return None
969
+
970
+ if not printer.fw_version.update_available:
971
+ return None
972
+
973
+ expected_version = printer.fw_version.available_version
974
+
975
+ resp = await self._update_printer_firmware(
976
+ printer_id=printer.id,
977
+ current_fw_version=printer.fw_version.firmware_version,
978
+ )
979
+
980
+ if resp.get('update_status') == 1:
981
+ return expected_version
982
+
983
+ return None
984
+
985
+ async def update_printer_multi_color_box_firmware(
986
+ self,
987
+ printer: AnycubicPrinter,
988
+ box_id: int = -1,
989
+ ) -> str | None:
990
+ if not printer:
991
+ return None
992
+
993
+ if not printer.primary_multi_color_box:
994
+ return None
995
+
996
+ if box_id < 0:
997
+ box_id = 0
998
+
999
+ if (
1000
+ not printer.multi_color_box_fw_version or
1001
+ len(printer.multi_color_box_fw_version) < (box_id + 1)
1002
+ ):
1003
+ return None
1004
+
1005
+ if (
1006
+ not printer.multi_color_box_fw_version[box_id].update_available
1007
+ ):
1008
+ return None
1009
+
1010
+ expected_version = printer.multi_color_box_fw_version[box_id].available_version
1011
+
1012
+ resp = await self._update_muli_color_box_firmware(
1013
+ printer_id=printer.id,
1014
+ box_id=box_id,
1015
+ )
1016
+
1017
+ target_version = resp.get('target_version')
1018
+
1019
+ if expected_version != target_version:
1020
+ return None
1021
+
1022
+ return target_version
1023
+
1024
+ async def update_printer_all_multi_color_box_firmware(
1025
+ self,
1026
+ printer: AnycubicPrinter,
1027
+ ) -> list[str | None] | None:
1028
+ if (
1029
+ not printer.multi_color_box_fw_version or
1030
+ len(printer.multi_color_box_fw_version) < 0
1031
+ ):
1032
+ return None
1033
+
1034
+ updated_versions = list()
1035
+
1036
+ for x in range(len(printer.multi_color_box_fw_version)):
1037
+ resp = await self.update_printer_multi_color_box_firmware(
1038
+ printer,
1039
+ x
1040
+ )
1041
+ updated_versions.append(resp)
1042
+
1043
+ return updated_versions
1044
+
1045
+ async def get_latest_cloud_file(
1046
+ self,
1047
+ ) -> AnycubicCloudFile | None:
1048
+ cloud_files = await self.get_user_cloud_files(
1049
+ printable=True,
1050
+ machine_type=0,
1051
+ )
1052
+
1053
+ return cloud_files[0] if cloud_files and len(cloud_files) > 0 else None
1054
+
1055
+ async def pause_print(
1056
+ self,
1057
+ printer: AnycubicPrinter,
1058
+ project: AnycubicProject | None = None,
1059
+ ) -> str | None:
1060
+ if not printer:
1061
+ return None
1062
+
1063
+ if not project and not printer.latest_project:
1064
+ return None
1065
+
1066
+ if not project:
1067
+ project = printer.latest_project
1068
+
1069
+ assert project
1070
+
1071
+ resp = await self._send_order_pause_print(
1072
+ printer,
1073
+ project,
1074
+ )
1075
+
1076
+ return resp
1077
+
1078
+ async def resume_print(
1079
+ self,
1080
+ printer: AnycubicPrinter,
1081
+ project: AnycubicProject | None = None,
1082
+ ) -> str | None:
1083
+ if not printer:
1084
+ return None
1085
+
1086
+ if not project and not printer.latest_project:
1087
+ return None
1088
+
1089
+ if not project:
1090
+ project = printer.latest_project
1091
+
1092
+ assert project
1093
+
1094
+ resp = await self._send_order_resume_print(
1095
+ printer,
1096
+ project,
1097
+ )
1098
+
1099
+ return resp
1100
+
1101
+ async def cancel_print(
1102
+ self,
1103
+ printer: AnycubicPrinter,
1104
+ project: AnycubicProject | None = None,
1105
+ ) -> str | None:
1106
+ if not printer:
1107
+ return None
1108
+
1109
+ if not project and not printer.latest_project:
1110
+ return None
1111
+
1112
+ if not project:
1113
+ project = printer.latest_project
1114
+
1115
+ assert project
1116
+
1117
+ resp = await self._send_order_stop_print(
1118
+ printer,
1119
+ project,
1120
+ )
1121
+
1122
+ return resp
1123
+
1124
+ async def multi_color_box_feed_filament(
1125
+ self,
1126
+ printer: AnycubicPrinter,
1127
+ slot_index: int,
1128
+ box_id: int = -1,
1129
+ finish: bool = False,
1130
+ ) -> str | None:
1131
+ """
1132
+ Must send a finish command when done.
1133
+ """
1134
+ if not printer:
1135
+ return None
1136
+
1137
+ if not printer.primary_multi_color_box:
1138
+ return None
1139
+
1140
+ if box_id < 0:
1141
+ box_id = 0
1142
+
1143
+ resp = await self._send_order_multi_color_box_feed_filament(
1144
+ printer=printer,
1145
+ slot_index=slot_index,
1146
+ feed_type=(
1147
+ AnycubicFeedType.Feed
1148
+ if not finish else
1149
+ AnycubicFeedType.Finish
1150
+ ),
1151
+ box_id=box_id,
1152
+ )
1153
+
1154
+ return resp
1155
+
1156
+ async def multi_color_box_retract_filament(
1157
+ self,
1158
+ printer: AnycubicPrinter,
1159
+ box_id: int = -1,
1160
+ ) -> str | None:
1161
+ if not printer:
1162
+ return None
1163
+
1164
+ if not printer.primary_multi_color_box:
1165
+ return None
1166
+
1167
+ if box_id < 0:
1168
+ box_id = 0
1169
+
1170
+ resp = await self._send_order_multi_color_box_feed_filament(
1171
+ printer=printer,
1172
+ slot_index=-1,
1173
+ feed_type=AnycubicFeedType.Retract,
1174
+ box_id=box_id,
1175
+ )
1176
+
1177
+ return resp
1178
+
1179
+ async def multi_color_box_set_auto_feed(
1180
+ self,
1181
+ printer: AnycubicPrinter,
1182
+ enabled: bool,
1183
+ box_id: int = -1,
1184
+ ) -> str | None:
1185
+ if not printer:
1186
+ return None
1187
+
1188
+ if not printer.primary_multi_color_box:
1189
+ return None
1190
+
1191
+ if box_id < 0:
1192
+ box_id = 0
1193
+
1194
+ resp = await self._send_order_multi_color_auto_feed(
1195
+ printer,
1196
+ enabled,
1197
+ box_id,
1198
+ )
1199
+
1200
+ return resp
1201
+
1202
+ async def multi_color_box_toggle_auto_feed(
1203
+ self,
1204
+ printer: AnycubicPrinter,
1205
+ box_id: int = -1,
1206
+ ) -> str | None:
1207
+ if not printer:
1208
+ return None
1209
+
1210
+ if not printer.primary_multi_color_box:
1211
+ return None
1212
+
1213
+ if box_id < 0:
1214
+ box_id = 0
1215
+
1216
+ assert printer.multi_color_box
1217
+
1218
+ current_auto_feed = bool(printer.multi_color_box[box_id].auto_feed)
1219
+
1220
+ printer.multi_color_box[box_id].set_auto_feed(not current_auto_feed)
1221
+
1222
+ resp = await self._send_order_multi_color_auto_feed(
1223
+ printer,
1224
+ (not current_auto_feed),
1225
+ box_id,
1226
+ )
1227
+
1228
+ return resp
1229
+
1230
+ async def multi_color_box_switch_on_auto_feed(
1231
+ self,
1232
+ printer: AnycubicPrinter,
1233
+ box_id: int = -1,
1234
+ ) -> str | None:
1235
+ if not printer:
1236
+ return None
1237
+
1238
+ if not printer.primary_multi_color_box:
1239
+ return None
1240
+
1241
+ if box_id < 0:
1242
+ box_id = 0
1243
+
1244
+ assert printer.multi_color_box
1245
+
1246
+ current_auto_feed = bool(printer.multi_color_box[box_id].auto_feed)
1247
+
1248
+ if current_auto_feed:
1249
+ return None
1250
+
1251
+ printer.multi_color_box[box_id].set_auto_feed(True)
1252
+
1253
+ resp = await self._send_order_multi_color_auto_feed(
1254
+ printer,
1255
+ True,
1256
+ box_id,
1257
+ )
1258
+
1259
+ return resp
1260
+
1261
+ async def multi_color_box_switch_off_auto_feed(
1262
+ self,
1263
+ printer: AnycubicPrinter,
1264
+ box_id: int = -1,
1265
+ ) -> str | None:
1266
+ if not printer:
1267
+ return None
1268
+
1269
+ if not printer.primary_multi_color_box:
1270
+ return None
1271
+
1272
+ if box_id < 0:
1273
+ box_id = 0
1274
+
1275
+ assert printer.multi_color_box
1276
+
1277
+ current_auto_feed = bool(printer.multi_color_box[box_id].auto_feed)
1278
+
1279
+ if not current_auto_feed:
1280
+ return None
1281
+
1282
+ printer.multi_color_box[box_id].set_auto_feed(False)
1283
+
1284
+ resp = await self._send_order_multi_color_auto_feed(
1285
+ printer,
1286
+ False,
1287
+ box_id,
1288
+ )
1289
+
1290
+ return resp
1291
+
1292
+ async def multi_color_box_set_slot(
1293
+ self,
1294
+ printer: AnycubicPrinter,
1295
+ slot_index: int,
1296
+ slot_color: AnycubicMaterialColor | None = None,
1297
+ slot_material_type: str = "PLA",
1298
+ slot_color_red: int | None = None,
1299
+ slot_color_green: int | None = None,
1300
+ slot_color_blue: int | None = None,
1301
+ box_id: int = 0,
1302
+ ) -> str | None:
1303
+ if (
1304
+ slot_color is None and
1305
+ any([x is None for x in [
1306
+ slot_color_red,
1307
+ slot_color_green,
1308
+ slot_color_blue
1309
+ ]])
1310
+ ):
1311
+ raise AnycubicAPIError(ErrorsGeneral.set_slot_color_invalid)
1312
+
1313
+ if slot_color is None:
1314
+ assert slot_color_red
1315
+ assert slot_color_green
1316
+ assert slot_color_blue
1317
+ slot_color = AnycubicMaterialColor(
1318
+ slot_color_red,
1319
+ slot_color_green,
1320
+ slot_color_blue,
1321
+ )
1322
+
1323
+ return await self._send_order_multi_color_box_set_slot(
1324
+ printer=printer,
1325
+ slot_index=slot_index,
1326
+ slot_color=slot_color,
1327
+ slot_material_type=slot_material_type,
1328
+ box_id=box_id,
1329
+ )
1330
+
1331
+ async def multi_color_box_set_pla_slot(
1332
+ self,
1333
+ printer: AnycubicPrinter,
1334
+ slot_index: int,
1335
+ slot_color: AnycubicMaterialColor,
1336
+ box_id: int = 0,
1337
+ ) -> str | None:
1338
+
1339
+ return await self._send_order_multi_color_box_set_slot(
1340
+ printer=printer,
1341
+ slot_index=slot_index,
1342
+ slot_color=slot_color,
1343
+ slot_material_type="PLA",
1344
+ box_id=box_id,
1345
+ )
1346
+
1347
+ async def multi_color_box_set_petg_slot(
1348
+ self,
1349
+ printer: AnycubicPrinter,
1350
+ slot_index: int,
1351
+ slot_color: AnycubicMaterialColor,
1352
+ box_id: int = 0,
1353
+ ) -> str | None:
1354
+
1355
+ return await self._send_order_multi_color_box_set_slot(
1356
+ printer=printer,
1357
+ slot_index=slot_index,
1358
+ slot_color=slot_color,
1359
+ slot_material_type="PETG",
1360
+ box_id=box_id,
1361
+ )
1362
+
1363
+ async def multi_color_box_set_abs_slot(
1364
+ self,
1365
+ printer: AnycubicPrinter,
1366
+ slot_index: int,
1367
+ slot_color: AnycubicMaterialColor,
1368
+ box_id: int = 0,
1369
+ ) -> str | None:
1370
+
1371
+ return await self._send_order_multi_color_box_set_slot(
1372
+ printer=printer,
1373
+ slot_index=slot_index,
1374
+ slot_color=slot_color,
1375
+ slot_material_type="ABS",
1376
+ box_id=box_id,
1377
+ )
1378
+
1379
+ async def multi_color_box_set_pacf_slot(
1380
+ self,
1381
+ printer: AnycubicPrinter,
1382
+ slot_index: int,
1383
+ slot_color: AnycubicMaterialColor,
1384
+ box_id: int = 0,
1385
+ ) -> str | None:
1386
+
1387
+ return await self._send_order_multi_color_box_set_slot(
1388
+ printer=printer,
1389
+ slot_index=slot_index,
1390
+ slot_color=slot_color,
1391
+ slot_material_type="PACF",
1392
+ box_id=box_id,
1393
+ )
1394
+
1395
+ async def multi_color_box_set_pc_slot(
1396
+ self,
1397
+ printer: AnycubicPrinter,
1398
+ slot_index: int,
1399
+ slot_color: AnycubicMaterialColor,
1400
+ box_id: int = 0,
1401
+ ) -> str | None:
1402
+
1403
+ return await self._send_order_multi_color_box_set_slot(
1404
+ printer=printer,
1405
+ slot_index=slot_index,
1406
+ slot_color=slot_color,
1407
+ slot_material_type="PC",
1408
+ box_id=box_id,
1409
+ )
1410
+
1411
+ async def multi_color_box_set_asa_slot(
1412
+ self,
1413
+ printer: AnycubicPrinter,
1414
+ slot_index: int,
1415
+ slot_color: AnycubicMaterialColor,
1416
+ box_id: int = 0,
1417
+ ) -> str | None:
1418
+
1419
+ return await self._send_order_multi_color_box_set_slot(
1420
+ printer=printer,
1421
+ slot_index=slot_index,
1422
+ slot_color=slot_color,
1423
+ slot_material_type="ASA",
1424
+ box_id=box_id,
1425
+ )
1426
+
1427
+ async def multi_color_box_set_hips_slot(
1428
+ self,
1429
+ printer: AnycubicPrinter,
1430
+ slot_index: int,
1431
+ slot_color: AnycubicMaterialColor,
1432
+ box_id: int = 0,
1433
+ ) -> str | None:
1434
+
1435
+ return await self._send_order_multi_color_box_set_slot(
1436
+ printer=printer,
1437
+ slot_index=slot_index,
1438
+ slot_color=slot_color,
1439
+ slot_material_type="HIPS",
1440
+ box_id=box_id,
1441
+ )
1442
+
1443
+ async def multi_color_box_set_pa_slot(
1444
+ self,
1445
+ printer: AnycubicPrinter,
1446
+ slot_index: int,
1447
+ slot_color: AnycubicMaterialColor,
1448
+ box_id: int = 0,
1449
+ ) -> str | None:
1450
+
1451
+ return await self._send_order_multi_color_box_set_slot(
1452
+ printer=printer,
1453
+ slot_index=slot_index,
1454
+ slot_color=slot_color,
1455
+ slot_material_type="PA",
1456
+ box_id=box_id,
1457
+ )
1458
+
1459
+ async def multi_color_box_set_pla_se_slot(
1460
+ self,
1461
+ printer: AnycubicPrinter,
1462
+ slot_index: int,
1463
+ slot_color: AnycubicMaterialColor,
1464
+ box_id: int = 0,
1465
+ ) -> str | None:
1466
+
1467
+ return await self._send_order_multi_color_box_set_slot(
1468
+ printer=printer,
1469
+ slot_index=slot_index,
1470
+ slot_color=slot_color,
1471
+ slot_material_type="PLA SE",
1472
+ box_id=box_id,
1473
+ )
1474
+
1475
+ async def multi_color_box_drying_start(
1476
+ self,
1477
+ printer: AnycubicPrinter,
1478
+ duration: int,
1479
+ target_temp: int,
1480
+ box_id: int = -1,
1481
+ ) -> str | None:
1482
+ if not printer:
1483
+ return None
1484
+
1485
+ if not printer.primary_multi_color_box:
1486
+ return None
1487
+
1488
+ order_params = {
1489
+ 'duration': duration,
1490
+ 'target_temp': target_temp,
1491
+ 'status': 1,
1492
+ }
1493
+ if box_id > 0:
1494
+ order_params['box_id'] = box_id
1495
+
1496
+ resp = await self._send_order_multi_color_box_dry(
1497
+ printer,
1498
+ order_params,
1499
+ )
1500
+
1501
+ return resp
1502
+
1503
+ async def multi_color_box_drying_stop(
1504
+ self,
1505
+ printer: AnycubicPrinter,
1506
+ box_id: int = -1,
1507
+ ) -> str | None:
1508
+ if not printer:
1509
+ return None
1510
+
1511
+ if not printer.primary_multi_color_box:
1512
+ return None
1513
+
1514
+ if box_id >= 0:
1515
+ order_params: list[dict[str, Any]] | dict[str, Any] = {
1516
+ 'status': 0,
1517
+ }
1518
+ else:
1519
+ order_params = [
1520
+ {
1521
+ 'status': 0
1522
+ } for x in range(printer.connected_ace_units)
1523
+ ]
1524
+
1525
+ resp = await self._send_order_multi_color_box_dry(
1526
+ printer,
1527
+ order_params,
1528
+ )
1529
+
1530
+ return resp
1531
+
1532
+ async def anycubic_full_available_printer_list(
1533
+ self,
1534
+ ) -> list[AnycubicPrinter]:
1535
+ resp = await self._fetch_api_resp(endpoint=API_ENDPOINT.printer_all)
1536
+ data = list([AnycubicPrinter.from_basic_json(self, x) for x in resp['data']['printer_type']])
1537
+ return data
1538
+
1539
+ async def list_printers_status(
1540
+ self,
1541
+ ) -> list[AnycubicPrinter]:
1542
+ resp = await self._fetch_api_resp(endpoint=API_ENDPOINT.printers_status)
1543
+ data = list([AnycubicPrinter.from_status_json(self, x) for x in resp['data']])
1544
+ return data
1545
+
1546
+ @overload
1547
+ async def list_my_printers(
1548
+ self,
1549
+ ignore_init_errors: bool = False,
1550
+ ) -> list[AnycubicPrinter]: ...
1551
+
1552
+ @overload
1553
+ async def list_my_printers(
1554
+ self,
1555
+ ignore_init_errors: bool = False,
1556
+ raw_data: Literal[True] = ...
1557
+ ) -> dict[str, Any]: ...
1558
+
1559
+ async def list_my_printers(
1560
+ self,
1561
+ ignore_init_errors: bool = False,
1562
+ raw_data: bool = False,
1563
+ ) -> list[AnycubicPrinter] | dict[str, Any]:
1564
+ resp = await self._fetch_api_resp(endpoint=API_ENDPOINT.printer_get_printers)
1565
+ if raw_data:
1566
+ return resp
1567
+
1568
+ data = list([
1569
+ AnycubicPrinter.from_status_json(
1570
+ self,
1571
+ x,
1572
+ ignore_init_errors=ignore_init_errors,
1573
+ ) for x in resp['data']
1574
+ ])
1575
+ if ignore_init_errors:
1576
+ for x in data:
1577
+ if x is None or x.initialisation_error:
1578
+ self._log_to_error(
1579
+ f"Failed to load data for printer list from response: {resp}"
1580
+ )
1581
+ break
1582
+
1583
+ return data
1584
+
1585
+ async def printer_status_for_id(
1586
+ self,
1587
+ printer_id: int,
1588
+ ) -> None:
1589
+ query = {
1590
+ 'id': str(printer_id)
1591
+ }
1592
+ resp = await self._fetch_api_resp(endpoint=API_ENDPOINT.printer_status, query=query)
1593
+ data = resp['data']
1594
+ self._log_to_debug(f"printer_status output:\n{json.dumps(data)}")
1595
+
1596
+ @overload
1597
+ async def printer_info_for_id(
1598
+ self,
1599
+ printer_id: int,
1600
+ update_object: AnycubicPrinter | None = None,
1601
+ ignore_init_errors: bool = False,
1602
+ ) -> AnycubicPrinter | None: ...
1603
+
1604
+ @overload
1605
+ async def printer_info_for_id(
1606
+ self,
1607
+ printer_id: int,
1608
+ update_object: AnycubicPrinter | None = None,
1609
+ ignore_init_errors: bool = False,
1610
+ raw_data: Literal[True] = ...
1611
+ ) -> dict[str, Any]: ...
1612
+
1613
+ async def printer_info_for_id(
1614
+ self,
1615
+ printer_id: int,
1616
+ update_object: AnycubicPrinter | None = None,
1617
+ ignore_init_errors: bool = False,
1618
+ raw_data: bool = False,
1619
+ ) -> AnycubicPrinter | None | dict[str, Any]:
1620
+ query = {
1621
+ 'id': str(printer_id)
1622
+ }
1623
+ resp = await self._fetch_api_resp(endpoint=API_ENDPOINT.printer_info, query=query)
1624
+
1625
+ if raw_data:
1626
+ return resp
1627
+
1628
+ if update_object is not None:
1629
+ update_object.update_from_info_json(resp['data'])
1630
+ return None
1631
+
1632
+ try:
1633
+ data = AnycubicPrinter.from_info_json(
1634
+ self,
1635
+ resp['data'],
1636
+ ignore_init_errors=ignore_init_errors,
1637
+ )
1638
+
1639
+ if ignore_init_errors:
1640
+ if data is None or data.initialisation_error:
1641
+ self._log_to_error(
1642
+ f"Failed to load data for printer list from response: {resp}"
1643
+ )
1644
+ except Exception as e:
1645
+ if resp and (
1646
+ resp_msg := resp.get('msg')
1647
+ ):
1648
+ if resp_msg == 'request error':
1649
+ raise AnycubicAPIParsingError(ErrorsAPIParsing.api_error_rate_limited)
1650
+
1651
+ self._log_to_error(f"Failed to load printer from anycubic response: {resp}")
1652
+ raise e
1653
+
1654
+ return data
1655
+
1656
+ @overload
1657
+ async def list_all_projects(
1658
+ self,
1659
+ page: int = 1,
1660
+ print_status: AnycubicPrintStatus | None = None,
1661
+ ) -> list[AnycubicProject]: ...
1662
+
1663
+ @overload
1664
+ async def list_all_projects(
1665
+ self,
1666
+ page: int = 1,
1667
+ print_status: AnycubicPrintStatus | None = None,
1668
+ raw_data: Literal[True] = ...
1669
+ ) -> dict[str, Any]: ...
1670
+
1671
+ async def list_all_projects(
1672
+ self,
1673
+ page: int = 1,
1674
+ print_status: AnycubicPrintStatus | None = None,
1675
+ raw_data: bool = False
1676
+ ) -> list[AnycubicProject] | dict[str, Any]:
1677
+ query = {
1678
+ 'page': str(int(page)),
1679
+ 'limit': MAX_PROJECT_LIST_RESULTS,
1680
+ }
1681
+ if print_status is not None:
1682
+ query['print_status'] = str(int(print_status))
1683
+
1684
+ resp = await self._fetch_api_resp(endpoint=API_ENDPOINT.project_get_projects, query=query)
1685
+ if raw_data:
1686
+ return resp
1687
+
1688
+ if resp is None or resp.get('data') is None:
1689
+ return list()
1690
+
1691
+ proj_list = list()
1692
+ for x in resp['data']:
1693
+ proj = AnycubicProject.from_list_json(self, x)
1694
+ if proj:
1695
+ proj_list.append(proj)
1696
+ else:
1697
+ raise AnycubicDataParsingError(ErrorsDataParsing.projects.format(resp['data']))
1698
+
1699
+ return proj_list
1700
+
1701
+ async def project_info_for_id(
1702
+ self,
1703
+ project_id: int,
1704
+ ) -> dict[str, Any]:
1705
+ query = {
1706
+ 'id': str(project_id)
1707
+ }
1708
+ resp = await self._fetch_api_resp(endpoint=API_ENDPOINT.project_info, query=query)
1709
+ data: dict[str, Any] = resp['data']
1710
+ return data
1711
+
1712
+ async def get_latest_project(
1713
+ self,
1714
+ printer_id: int | None = None,
1715
+ project_to_update: AnycubicProject | None = None,
1716
+ ) -> AnycubicProject | None:
1717
+ projects = await self.list_all_projects()
1718
+
1719
+ latest_project = None
1720
+
1721
+ image_search_counter = 0
1722
+
1723
+ if projects and len(projects) > 0:
1724
+ for proj in projects:
1725
+ # Look for matching project and fill image URL from previous cloud print if available
1726
+ if (
1727
+ latest_project is None and
1728
+ (printer_id is None or proj.printer_id == printer_id)
1729
+ ):
1730
+ if project_to_update and project_to_update.update_with_project(proj):
1731
+ latest_project = project_to_update
1732
+ else:
1733
+ latest_project = proj
1734
+
1735
+ if latest_project.image_url or not latest_project.name:
1736
+ break
1737
+
1738
+ elif latest_project and image_search_counter < MAX_PROJECT_IMAGE_SEARCH_COUNT:
1739
+ image_search_counter += 1
1740
+
1741
+ if proj.name == latest_project.name and proj.image_url:
1742
+ latest_project.set_image_url(proj.image_url)
1743
+ break
1744
+
1745
+ elif latest_project:
1746
+ break
1747
+
1748
+ if latest_project:
1749
+ extra_proj_data = await self.project_info_for_id(latest_project.id)
1750
+ latest_project.update_extra_data(extra_proj_data)
1751
+ return latest_project
1752
+
1753
+ async def read_gcode_file(
1754
+ self,
1755
+ full_file_path: str | None = None,
1756
+ file_name: str | None = None,
1757
+ file_bytes: bytes | None = None,
1758
+ ) -> AnycubicGcodeFile:
1759
+ if (not full_file_path or len(full_file_path) <= 0) and file_bytes is None:
1760
+ raise AnycubicGcodeParsingError(ErrorsGcodeParsing.invalid_path)
1761
+
1762
+ elif (
1763
+ (full_file_path and not full_file_path.endswith('.gcode'))
1764
+ or
1765
+ (file_name and not file_name.endswith('.gcode'))
1766
+ ):
1767
+ raise AnycubicGcodeParsingError(ErrorsGcodeParsing.invalid_ext)
1768
+
1769
+ return await AnycubicGcodeFile.async_read_from_file(
1770
+ full_file_path=full_file_path,
1771
+ file_bytes=file_bytes,
1772
+ )
1773
+
1774
+ async def upload_file_to_cloud(
1775
+ self,
1776
+ full_file_path: str | None = None,
1777
+ file_name: str | None = None,
1778
+ file_bytes: bytes | None = None,
1779
+ temp_file: bool = False,
1780
+ ) -> int:
1781
+ file_upload = AnycubicCloudUpload(
1782
+ api_parent=self,
1783
+ full_file_path=full_file_path,
1784
+ file_name=file_name,
1785
+ file_bytes=file_bytes,
1786
+ is_temp_file=temp_file
1787
+ )
1788
+
1789
+ return await file_upload.async_process_upload()
1790
+
1791
+ async def print_with_cloud_file_id(
1792
+ self,
1793
+ printer: AnycubicPrinter,
1794
+ cloud_file_id: int,
1795
+ ams_box_mapping: list[AnycubicMaterialMapping] | None = None,
1796
+ temp_file: bool = False,
1797
+ ) -> str | None:
1798
+ if printer is None:
1799
+ raise AnycubicAPIError(ErrorsGeneral.no_printer_to_print)
1800
+
1801
+ if ams_box_mapping and not printer.primary_multi_color_box:
1802
+ raise AnycubicAPIError(ErrorsGeneral.no_ace_for_map)
1803
+
1804
+ if ams_box_mapping is None and printer.primary_multi_color_box:
1805
+ raise AnycubicAPIError(ErrorsGeneral.no_map_for_ace)
1806
+
1807
+ print_request = AnycubicStartPrintRequestCloud(
1808
+ file_id=cloud_file_id,
1809
+ is_delete_file=temp_file,
1810
+ )
1811
+
1812
+ result = None
1813
+ try_count = 0
1814
+
1815
+ while result is None and try_count < 3:
1816
+ try:
1817
+ result = await self._send_order_start_print(
1818
+ printer=printer,
1819
+ print_request=print_request,
1820
+ ams_box_mapping=ams_box_mapping,
1821
+ )
1822
+ except AnycubicFileNotFoundError:
1823
+ await asyncio.sleep(3)
1824
+
1825
+ try_count += 1
1826
+
1827
+ return result
1828
+
1829
+ async def print_with_cloud_gcode_id(
1830
+ self,
1831
+ printer: AnycubicPrinter,
1832
+ gcode_id: int,
1833
+ slot_index_list: list[int] | None = None,
1834
+ file_name: str | None = None,
1835
+ ) -> AnycubicPrintResponse:
1836
+ if printer is None:
1837
+ raise AnycubicAPIError(ErrorsGeneral.no_printer_to_print)
1838
+
1839
+ if slot_index_list is not None and not printer.primary_multi_color_box:
1840
+ raise AnycubicAPIError(ErrorsGeneral.no_ace_for_slot_list)
1841
+
1842
+ if slot_index_list is None and printer.primary_multi_color_box:
1843
+ raise AnycubicAPIError(ErrorsGeneral.no_slot_list_for_ace)
1844
+
1845
+ proj = await self.fetch_project_gcode_info_fdm(gcode_id)
1846
+
1847
+ if not proj:
1848
+ raise AnycubicGcodeParsingError(ErrorsGcodeParsing.fetch_failed)
1849
+
1850
+ if slot_index_list is not None:
1851
+
1852
+ material_list = proj.slice_material_info_list
1853
+
1854
+ if not material_list:
1855
+ raise AnycubicGcodeParsingError(ErrorsGcodeParsing.material_list_empty)
1856
+
1857
+ if len(slot_index_list) != len(material_list):
1858
+ raise AnycubicGcodeParsingError(ErrorsGcodeParsing.slot_material_list_mismatch.format(
1859
+ len(slot_index_list),
1860
+ len(material_list),
1861
+ ))
1862
+
1863
+ ams_box_mapping = printer.build_mapping_for_material_list(
1864
+ slot_index_list=slot_index_list,
1865
+ material_list=material_list,
1866
+ )
1867
+
1868
+ else:
1869
+ ams_box_mapping = None
1870
+
1871
+ order_msg_id = await self.print_with_cloud_file_id(
1872
+ printer=printer,
1873
+ cloud_file_id=proj.id,
1874
+ ams_box_mapping=ams_box_mapping,
1875
+ )
1876
+
1877
+ return AnycubicPrintResponse(
1878
+ order_msg_id=str(order_msg_id),
1879
+ printer_id=printer.id,
1880
+ saved_in_cloud=True,
1881
+ file_name=file_name,
1882
+ cloud_file_id=proj.id,
1883
+ gcode_id=gcode_id,
1884
+ material_list=proj.slice_material_info_list,
1885
+ ams_box_mapping=ams_box_mapping,
1886
+ )
1887
+
1888
+ async def print_and_upload_save_in_cloud(
1889
+ self,
1890
+ printer: AnycubicPrinter,
1891
+ full_file_path: str | None = None,
1892
+ file_name: str | None = None,
1893
+ file_bytes: bytes | None = None,
1894
+ slot_index_list: list[int] | None = None,
1895
+ ) -> AnycubicPrintResponse:
1896
+ if printer is None:
1897
+ raise AnycubicAPIError(ErrorsGeneral.no_printer_to_print)
1898
+
1899
+ if slot_index_list is not None and not printer.primary_multi_color_box:
1900
+ raise AnycubicAPIError(ErrorsGeneral.no_ace_for_slot_list)
1901
+
1902
+ if slot_index_list is None and printer.primary_multi_color_box:
1903
+ raise AnycubicAPIError(ErrorsGeneral.no_slot_list_for_ace)
1904
+
1905
+ cloud_file_id = await self.upload_file_to_cloud(
1906
+ full_file_path=full_file_path,
1907
+ file_name=file_name,
1908
+ file_bytes=file_bytes,
1909
+ )
1910
+ latest_cloud_file = await self.get_latest_cloud_file()
1911
+
1912
+ if not latest_cloud_file or latest_cloud_file.id != cloud_file_id:
1913
+ raise AnycubicAPIError(ErrorsGeneral.file_upload_mismatch)
1914
+
1915
+ if latest_cloud_file.gcode_id is None:
1916
+ raise AnycubicAPIError(ErrorsGeneral.cloud_file_no_gcodeid)
1917
+
1918
+ return await self.print_with_cloud_gcode_id(
1919
+ printer=printer,
1920
+ gcode_id=latest_cloud_file.gcode_id,
1921
+ slot_index_list=slot_index_list,
1922
+ file_name=file_name,
1923
+ )
1924
+
1925
+ async def print_and_upload_no_cloud_save(
1926
+ self,
1927
+ printer: AnycubicPrinter,
1928
+ full_file_path: str | None = None,
1929
+ file_name: str | None = None,
1930
+ file_bytes: bytes | None = None,
1931
+ slot_index_list: list[int] | None = None,
1932
+ ) -> AnycubicPrintResponse:
1933
+ if printer is None:
1934
+ raise AnycubicAPIError(ErrorsGeneral.no_printer_to_print)
1935
+
1936
+ if slot_index_list is not None and not printer.primary_multi_color_box:
1937
+ raise AnycubicAPIError(ErrorsGeneral.no_ace_for_slot_list)
1938
+
1939
+ if slot_index_list is None and printer.primary_multi_color_box:
1940
+ raise AnycubicAPIError(ErrorsGeneral.no_slot_list_for_ace)
1941
+
1942
+ if slot_index_list is not None:
1943
+ gcode_file = await self.read_gcode_file(
1944
+ full_file_path=full_file_path,
1945
+ file_name=file_name,
1946
+ file_bytes=file_bytes,
1947
+ )
1948
+ material_list = gcode_file.material_list
1949
+
1950
+ if not material_list:
1951
+ raise AnycubicGcodeParsingError(ErrorsGcodeParsing.material_list_empty)
1952
+
1953
+ if len(slot_index_list) != len(material_list):
1954
+ raise AnycubicGcodeParsingError(ErrorsGcodeParsing.slot_material_list_mismatch.format(
1955
+ len(slot_index_list),
1956
+ len(material_list),
1957
+ ))
1958
+
1959
+ ams_box_mapping = printer.build_mapping_for_material_list(
1960
+ slot_index_list=slot_index_list,
1961
+ material_list=material_list,
1962
+ )
1963
+
1964
+ else:
1965
+ material_list = None
1966
+ ams_box_mapping = None
1967
+
1968
+ cloud_file_id = await self.upload_file_to_cloud(
1969
+ full_file_path=full_file_path,
1970
+ file_name=file_name,
1971
+ file_bytes=file_bytes,
1972
+ temp_file=True,
1973
+ )
1974
+
1975
+ order_msg_id = await self.print_with_cloud_file_id(
1976
+ printer=printer,
1977
+ cloud_file_id=cloud_file_id,
1978
+ ams_box_mapping=ams_box_mapping,
1979
+ temp_file=True,
1980
+ )
1981
+
1982
+ return AnycubicPrintResponse(
1983
+ order_msg_id=str(order_msg_id),
1984
+ printer_id=printer.id,
1985
+ saved_in_cloud=False,
1986
+ file_name=file_name,
1987
+ cloud_file_id=cloud_file_id,
1988
+ material_list=material_list,
1989
+ ams_box_mapping=ams_box_mapping,
1990
+ )
1991
+
1992
+ async def query_printer_options(
1993
+ self,
1994
+ printer: AnycubicPrinter,
1995
+ project: AnycubicProject | None = None,
1996
+ ) -> None:
1997
+ """ Responses are sent via MQTT """
1998
+
1999
+ if not printer:
2000
+ return None
2001
+
2002
+ await self._send_order_query_peripherals(
2003
+ printer=printer,
2004
+ )
2005
+
2006
+ if not project and not printer.latest_project:
2007
+ return None
2008
+
2009
+ if not project:
2010
+ project = printer.latest_project
2011
+
2012
+ assert project
2013
+
2014
+ await self._send_order_get_light_status(
2015
+ printer=printer,
2016
+ project=project,
2017
+ )
2018
+
2019
+ async def multi_color_box_get_info(
2020
+ self,
2021
+ printer: AnycubicPrinter,
2022
+ ) -> None:
2023
+ """Ask the ACE to re-report its slots. Response is sent via MQTT."""
2024
+
2025
+ if not printer:
2026
+ return None
2027
+
2028
+ await self._send_order_multi_color_box_get_info(
2029
+ printer=printer,
2030
+ )
2031
+
2032
+ async def set_printer_light(
2033
+ self,
2034
+ printer: AnycubicPrinter,
2035
+ light_on: bool,
2036
+ brightness: int | None = None,
2037
+ project: AnycubicProject | None = None,
2038
+ ) -> None:
2039
+ """Turn the printer light on/off. Response is sent via MQTT."""
2040
+
2041
+ if not printer:
2042
+ return None
2043
+
2044
+ if not project:
2045
+ project = printer.latest_project
2046
+
2047
+ if not project:
2048
+ return None
2049
+
2050
+ await self._send_order_set_light_status(
2051
+ printer=printer,
2052
+ project=project,
2053
+ light_on=light_on,
2054
+ brightness=brightness,
2055
+ )