anycubic-cloud-api 0.4.28__tar.gz → 0.4.32__tar.gz
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.
- {anycubic_cloud_api-0.4.28/src/anycubic_cloud_api.egg-info → anycubic_cloud_api-0.4.32}/PKG-INFO +1 -1
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/pyproject.toml +1 -1
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/api/functions.py +4 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/data_models/printer.py +84 -11
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/data_models/project.py +5 -2
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32/src/anycubic_cloud_api.egg-info}/PKG-INFO +1 -1
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api.egg-info/SOURCES.txt +2 -1
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/tests/test_axis_position.py +9 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/tests/test_lan_reports.py +79 -0
- anycubic_cloud_api-0.4.32/tests/test_secondary_ace.py +111 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/COPYRIGHT +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/LICENSE +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/MANIFEST.in +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/README.md +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/setup.cfg +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/__init__.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/anycubic_api.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/api/base.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/api/mqtt.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/const/api_endpoints.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/const/const.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/const/enums.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/const/error_codes.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/const/lan.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/const/mqtt.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/const/regions.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/data_models/consumable.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/data_models/files.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/data_models/gcode_file.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/data_models/orders.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/data_models/print_response.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/data_models/print_speed_mode.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/data_models/printer_properties.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/data_models/printing_settings.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/exceptions/error_strings.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/exceptions/exceptions.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/helpers/helpers.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/lan/__init__.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/lan/client.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/lan/commands.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/lan/handshake.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/models/auth.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/models/cloud_upload.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/models/http.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/resources/anycubic_mqqt_tls_ca.crt +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/resources/anycubic_mqqt_tls_client.crt +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/resources/anycubic_mqqt_tls_client.key +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api.egg-info/dependency_links.txt +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api.egg-info/requires.txt +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api.egg-info/top_level.txt +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/tests/test_auth_mode_fallback.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/tests/test_endpoint_urls.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/tests/test_lan_client.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/tests/test_lan_commands.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/tests/test_lan_handshake.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/tests/test_null_tolerant_parsing.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/tests/test_partial_reports.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/tests/test_printer_error_codes.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/tests/test_project_null_status.py +0 -0
- {anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/tests/test_regions.py +0 -0
{anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/api/functions.py
RENAMED
|
@@ -1807,8 +1807,12 @@ class AnycubicAPIFunctions(AnycubicAPIBase):
|
|
|
1807
1807
|
return None
|
|
1808
1808
|
|
|
1809
1809
|
if box_id >= 0:
|
|
1810
|
+
# The box has to be named: the sender numbers unnamed boxes by
|
|
1811
|
+
# position, so a lone {'status': 0} always stopped box 0 and a
|
|
1812
|
+
# second ACE could never be stopped from here (#39).
|
|
1810
1813
|
order_params: list[dict[str, Any]] | dict[str, Any] = {
|
|
1811
1814
|
'status': 0,
|
|
1815
|
+
'box_id': box_id,
|
|
1812
1816
|
}
|
|
1813
1817
|
else:
|
|
1814
1818
|
order_params = [
|
|
@@ -150,6 +150,7 @@ class AnycubicPrinter:
|
|
|
150
150
|
"_axis_position",
|
|
151
151
|
"_multi_color_box",
|
|
152
152
|
"_latest_project",
|
|
153
|
+
"_lan_last_job",
|
|
153
154
|
"_fan_speed",
|
|
154
155
|
"_aux_fan_speed",
|
|
155
156
|
"_box_fan_level",
|
|
@@ -260,6 +261,11 @@ class AnycubicPrinter:
|
|
|
260
261
|
self._set_multi_color_box(multi_color_box)
|
|
261
262
|
|
|
262
263
|
self._latest_project: AnycubicProject | None = None
|
|
264
|
+
# The printer's own record of the job that finished last, from the
|
|
265
|
+
# local info report. Kept because the running job is cleared the moment
|
|
266
|
+
# the printer goes idle, and without this a finished LAN print could
|
|
267
|
+
# never be charged to its spool (hass-anycubic G1).
|
|
268
|
+
self._lan_last_job: dict[str, Any] | None = None
|
|
263
269
|
# None until the printer says so. It used to default to 0, which
|
|
264
270
|
# is indistinguishable from a fan that is genuinely stopped.
|
|
265
271
|
self._fan_speed: int | None = None
|
|
@@ -445,6 +451,36 @@ class AnycubicPrinter:
|
|
|
445
451
|
|
|
446
452
|
self._parameter.update_current_temps(hotbed, nozzle)
|
|
447
453
|
|
|
454
|
+
def _set_lan_last_job(self, data: dict[str, Any]) -> None:
|
|
455
|
+
if not data:
|
|
456
|
+
return
|
|
457
|
+
|
|
458
|
+
def as_int(value: Any) -> int | None:
|
|
459
|
+
try:
|
|
460
|
+
return int(value)
|
|
461
|
+
except (TypeError, ValueError):
|
|
462
|
+
return None
|
|
463
|
+
|
|
464
|
+
def as_float(value: Any) -> float | None:
|
|
465
|
+
try:
|
|
466
|
+
return float(value)
|
|
467
|
+
except (TypeError, ValueError):
|
|
468
|
+
return None
|
|
469
|
+
|
|
470
|
+
self._lan_last_job = {
|
|
471
|
+
'task_id': as_int(data.get('task_id')),
|
|
472
|
+
# Millimetres of filament the printer says it extruded.
|
|
473
|
+
'supplies_usage_mm': as_float(data.get('supplies_usage')),
|
|
474
|
+
'print_status': as_int(data.get('print_status')),
|
|
475
|
+
'state': data.get('state') if isinstance(data.get('state'), str) else None,
|
|
476
|
+
'filename': data.get('filename') if isinstance(data.get('filename'), str) else None,
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
@property
|
|
480
|
+
def lan_last_job(self) -> dict[str, Any] | None:
|
|
481
|
+
"""The last finished job as the printer reported it locally, if any."""
|
|
482
|
+
return self._lan_last_job
|
|
483
|
+
|
|
448
484
|
def _apply_lan_project(self, project: Any) -> None:
|
|
449
485
|
"""Track the running job from the printer's own report of it.
|
|
450
486
|
|
|
@@ -480,8 +516,19 @@ class AnycubicPrinter:
|
|
|
480
516
|
if self._latest_project is None:
|
|
481
517
|
return
|
|
482
518
|
|
|
519
|
+
# A Kobra X sends print_status 0 mid-print, and 0 is not a status.
|
|
520
|
+
# Raising on it threw away the whole info report -- temperatures,
|
|
521
|
+
# progress, layers -- 20 times in two minutes (#38). An unrecognised
|
|
522
|
+
# status now leaves the job's status as it was and applies the rest.
|
|
523
|
+
try:
|
|
524
|
+
print_status: AnycubicPrintStatus | None = AnycubicPrintStatus(
|
|
525
|
+
int(data.get('print_status')) # type: ignore[arg-type]
|
|
526
|
+
)
|
|
527
|
+
except (TypeError, ValueError):
|
|
528
|
+
print_status = None
|
|
529
|
+
|
|
483
530
|
self._latest_project.update_with_mqtt_print_status_data(
|
|
484
|
-
|
|
531
|
+
print_status,
|
|
485
532
|
data,
|
|
486
533
|
paused=data.get('pause'),
|
|
487
534
|
)
|
|
@@ -1027,13 +1074,16 @@ class AnycubicPrinter:
|
|
|
1027
1074
|
) -> None:
|
|
1028
1075
|
if action == 'query' and state == 'done':
|
|
1029
1076
|
data = payload['data']
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1077
|
+
# A Kobra X answers the query without coordinates at all during a
|
|
1078
|
+
# print. No reading is better than a raise that loses the report.
|
|
1079
|
+
coords = data.get('coordinates')
|
|
1080
|
+
|
|
1081
|
+
if coords:
|
|
1082
|
+
self._axis_position = AnycubicAxisPosition(
|
|
1083
|
+
x=coords['x'],
|
|
1084
|
+
y=coords['y'],
|
|
1085
|
+
z=coords['z'],
|
|
1086
|
+
)
|
|
1037
1087
|
|
|
1038
1088
|
# Nested payloads are only released once emptied, and anything left
|
|
1039
1089
|
# behind raises AnycubicMQTTUnhandledData at the end of dispatch.
|
|
@@ -1499,9 +1549,9 @@ class AnycubicPrinter:
|
|
|
1499
1549
|
data.get('model')
|
|
1500
1550
|
self._apply_lan_project(data.get('project'))
|
|
1501
1551
|
|
|
1502
|
-
#
|
|
1503
|
-
#
|
|
1504
|
-
_drain(data.get('last_project'))
|
|
1552
|
+
# The job that finished last. Null until one has, and then it stays --
|
|
1553
|
+
# so a null here never clears what is already known.
|
|
1554
|
+
self._set_lan_last_job(_drain(data.get('last_project')))
|
|
1505
1555
|
_drain(data)
|
|
1506
1556
|
|
|
1507
1557
|
def _process_mqtt_update_ai_settings(
|
|
@@ -2365,6 +2415,29 @@ class AnycubicPrinter:
|
|
|
2365
2415
|
|
|
2366
2416
|
return None
|
|
2367
2417
|
|
|
2418
|
+
@property
|
|
2419
|
+
def secondary_multi_color_box_loaded_slot(self) -> int | None:
|
|
2420
|
+
"""Which slot is feeding on the second ACE, or None.
|
|
2421
|
+
|
|
2422
|
+
Mirrors primary_multi_color_box_loaded_slot. A printer with two boxes
|
|
2423
|
+
reported this all along -- it was parsed and then had no accessor, so
|
|
2424
|
+
the only way to know which slot the second unit was feeding from was
|
|
2425
|
+
to not know (hass-anycubic #33).
|
|
2426
|
+
"""
|
|
2427
|
+
box = self.secondary_multi_color_box
|
|
2428
|
+
|
|
2429
|
+
if not box:
|
|
2430
|
+
return None
|
|
2431
|
+
|
|
2432
|
+
slot = box.loaded_slot
|
|
2433
|
+
|
|
2434
|
+
# -1 means nothing is loaded; surface that as unknown rather than a
|
|
2435
|
+
# slot number that doesn't exist.
|
|
2436
|
+
if slot is not None and slot >= 0:
|
|
2437
|
+
return slot
|
|
2438
|
+
|
|
2439
|
+
return None
|
|
2440
|
+
|
|
2368
2441
|
@property
|
|
2369
2442
|
def secondary_multi_color_box_current_temperature(self) -> int:
|
|
2370
2443
|
if self.secondary_multi_color_box:
|
|
@@ -683,14 +683,17 @@ class AnycubicProject:
|
|
|
683
683
|
|
|
684
684
|
def update_with_mqtt_print_status_data(
|
|
685
685
|
self,
|
|
686
|
-
|
|
686
|
+
# None when the printer sent a status that is not one: the job keeps
|
|
687
|
+
# the status it had, and everything else in the report still applies.
|
|
688
|
+
print_status: AnycubicPrintStatus | None,
|
|
687
689
|
# Only read from, by key -- so a plain report dict off the wire is as
|
|
688
690
|
# good as the wrapped form, and both reach here.
|
|
689
691
|
mqtt_data: Mapping[str, Any] | None = None,
|
|
690
692
|
paused: int | None = None,
|
|
691
693
|
reason: str | None = None,
|
|
692
694
|
) -> None:
|
|
693
|
-
|
|
695
|
+
if print_status is not None:
|
|
696
|
+
self._print_status = int(print_status)
|
|
694
697
|
|
|
695
698
|
if paused is not None:
|
|
696
699
|
self._set_pause(paused)
|
|
@@ -68,6 +68,15 @@ class TestMqttDispatch:
|
|
|
68
68
|
|
|
69
69
|
assert payload.is_empty, f"left behind: {payload.remaining_data}"
|
|
70
70
|
|
|
71
|
+
def test_a_reply_without_coordinates_is_harmless(self):
|
|
72
|
+
"""A Kobra X answers the query with no coordinates mid-print (#38)."""
|
|
73
|
+
printer = self._printer()
|
|
74
|
+
payload = self._msg(data={})
|
|
75
|
+
|
|
76
|
+
printer.process_mqtt_update("a/b/c/d/e/f/g/axis/report", payload)
|
|
77
|
+
|
|
78
|
+
assert printer.axis_position is None
|
|
79
|
+
|
|
71
80
|
def test_an_unexpected_action_is_reported(self):
|
|
72
81
|
from anycubic_cloud_api import AnycubicMQTTUnknownUpdate
|
|
73
82
|
|
|
@@ -678,6 +678,7 @@ class TestTheRunningJob:
|
|
|
678
678
|
assert printer.latest_project_print_in_progress is True
|
|
679
679
|
assert printer.latest_project_print_is_paused is False
|
|
680
680
|
|
|
681
|
+
|
|
681
682
|
def test_a_paused_job_reads_as_paused(self):
|
|
682
683
|
printer = make_printer()
|
|
683
684
|
|
|
@@ -775,3 +776,81 @@ class TestTheFinishedJobBlock:
|
|
|
775
776
|
apply(printer, self._report())
|
|
776
777
|
|
|
777
778
|
assert printer.latest_project is None
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
class TestAStatusThatIsNotOne:
|
|
782
|
+
"""A Kobra X reports print_status 0 mid-print, which is not a status.
|
|
783
|
+
|
|
784
|
+
Raising on it discarded the whole info report -- temperatures, progress,
|
|
785
|
+
layers -- 20 times in two minutes (#38).
|
|
786
|
+
"""
|
|
787
|
+
|
|
788
|
+
@pytest.mark.parametrize("status", [0, None, 8])
|
|
789
|
+
def test_the_rest_of_the_report_still_applies(self, status):
|
|
790
|
+
printer = make_printer()
|
|
791
|
+
job = deepcopy(LAN_JOB)
|
|
792
|
+
job["print_status"] = status
|
|
793
|
+
job["progress"] = 75
|
|
794
|
+
|
|
795
|
+
apply(printer, info_with_job(job))
|
|
796
|
+
|
|
797
|
+
assert printer.latest_project_progress_percentage == 75
|
|
798
|
+
|
|
799
|
+
def test_the_status_already_known_is_kept(self):
|
|
800
|
+
printer = make_printer()
|
|
801
|
+
apply(printer, info_with_job(LAN_JOB))
|
|
802
|
+
job = deepcopy(LAN_JOB)
|
|
803
|
+
job["print_status"] = 0
|
|
804
|
+
|
|
805
|
+
apply(printer, info_with_job(job))
|
|
806
|
+
|
|
807
|
+
assert printer.latest_project_print_in_progress is True
|
|
808
|
+
|
|
809
|
+
|
|
810
|
+
class TestTheLastFinishedJob:
|
|
811
|
+
"""hass-anycubic G1: over LAN the running job is cleared as soon as the
|
|
812
|
+
printer goes idle, so the finished job is only ever in `last_project`."""
|
|
813
|
+
|
|
814
|
+
FINISHED = {
|
|
815
|
+
"task_id": 119231778,
|
|
816
|
+
"filename": ".3mf_temp/0622-1002-Spectacular Wolt (1)_plate(01)_PLA_0.2_45s.gcode",
|
|
817
|
+
"progress": 100, "curr_layer": 5, "total_layers": 5, "print_time": 2,
|
|
818
|
+
"remain_time": 0, "supplies_usage": 65, "pause": 0, "state": "finished",
|
|
819
|
+
"print_status": 2, "print_speed_mode": 2, "project_type": 2,
|
|
820
|
+
"localtask": "aff63521-084c-4dfc-9cf7-d112c7159551",
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
def _idle_with_last(self, last):
|
|
824
|
+
report = info_with_job(None)
|
|
825
|
+
report["data"]["last_project"] = deepcopy(last)
|
|
826
|
+
return report
|
|
827
|
+
|
|
828
|
+
def test_it_is_kept(self):
|
|
829
|
+
printer = make_printer()
|
|
830
|
+
apply(printer, self._idle_with_last(self.FINISHED))
|
|
831
|
+
|
|
832
|
+
job = printer.lan_last_job
|
|
833
|
+
assert job["task_id"] == 119231778
|
|
834
|
+
assert job["supplies_usage_mm"] == 65.0
|
|
835
|
+
assert job["print_status"] == 2
|
|
836
|
+
assert job["state"] == "finished"
|
|
837
|
+
|
|
838
|
+
def test_a_later_null_does_not_forget_it(self):
|
|
839
|
+
printer = make_printer()
|
|
840
|
+
apply(printer, self._idle_with_last(self.FINISHED))
|
|
841
|
+
apply(printer, self._idle_with_last(None))
|
|
842
|
+
|
|
843
|
+
assert printer.lan_last_job["task_id"] == 119231778
|
|
844
|
+
|
|
845
|
+
def test_nothing_before_the_first_finish(self):
|
|
846
|
+
printer = make_printer()
|
|
847
|
+
apply(printer, info_with_job(None))
|
|
848
|
+
|
|
849
|
+
assert printer.lan_last_job is None
|
|
850
|
+
|
|
851
|
+
def test_the_report_is_still_fully_consumed(self):
|
|
852
|
+
printer = make_printer()
|
|
853
|
+
report = AnycubicConsumableData(self._idle_with_last(self.FINISHED))
|
|
854
|
+
printer.process_mqtt_update("a/b/c/d/e/f/g/info/report", report)
|
|
855
|
+
|
|
856
|
+
assert report.is_empty, report.remaining_data
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""The second ACE unit, which the library parsed and then hid.
|
|
2
|
+
|
|
3
|
+
hass-anycubic #33: a Kobra S1 can take two ACE Pro units and a Kobra X up to
|
|
4
|
+
four. The boxes are parsed into a list of whatever the printer reports, and
|
|
5
|
+
connected_ace_units counts them correctly -- but only the first two have
|
|
6
|
+
accessors, and until now the second had no way to report which slot was
|
|
7
|
+
feeding.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from unittest.mock import MagicMock
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
from anycubic_cloud_api.data_models.printer import AnycubicPrinter
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _box(box_id: int, loaded_slot: int = -1) -> dict:
|
|
18
|
+
return {
|
|
19
|
+
"id": box_id, "status": 1, "model_id": 40002, "auto_feed": 0,
|
|
20
|
+
"loaded_slot": loaded_slot, "temp": 25, "slots": [],
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _printer(*boxes: dict) -> AnycubicPrinter:
|
|
25
|
+
return AnycubicPrinter(
|
|
26
|
+
api_parent=MagicMock(), machine_type=20025, machine_name="x", id=1,
|
|
27
|
+
ignore_init_errors=True, multi_color_box=list(boxes) or None,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TestHowManyBoxesAreSeen:
|
|
32
|
+
@pytest.mark.parametrize("count", [1, 2, 3, 4])
|
|
33
|
+
def test_every_reported_box_is_counted(self, count):
|
|
34
|
+
"""Three and four are parsed even though only two are reachable --
|
|
35
|
+
so the count is honest about hardware the accessors cannot reach."""
|
|
36
|
+
printer = _printer(*[_box(i) for i in range(count)])
|
|
37
|
+
|
|
38
|
+
assert printer.connected_ace_units == count
|
|
39
|
+
|
|
40
|
+
def test_no_boxes_is_zero_not_an_error(self):
|
|
41
|
+
assert _printer().connected_ace_units == 0
|
|
42
|
+
|
|
43
|
+
@pytest.mark.parametrize("count", [3, 4])
|
|
44
|
+
def test_more_than_two_boxes_does_not_break_the_accessors(self, count):
|
|
45
|
+
"""A four-unit printer must not raise; the extra boxes are merely
|
|
46
|
+
invisible until the entity scheme is indexed rather than named."""
|
|
47
|
+
printer = _printer(*[_box(i) for i in range(count)])
|
|
48
|
+
|
|
49
|
+
assert printer.primary_multi_color_box is not None
|
|
50
|
+
assert printer.secondary_multi_color_box is not None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class TestTheSecondBoxCanNameItsLoadedSlot:
|
|
54
|
+
def test_it_reports_the_feeding_slot(self):
|
|
55
|
+
printer = _printer(_box(0, loaded_slot=0), _box(1, loaded_slot=2))
|
|
56
|
+
|
|
57
|
+
assert printer.primary_multi_color_box_loaded_slot == 0
|
|
58
|
+
assert printer.secondary_multi_color_box_loaded_slot == 2
|
|
59
|
+
|
|
60
|
+
def test_nothing_feeding_is_unknown_not_minus_one(self):
|
|
61
|
+
"""-1 is the printer's way of saying nothing is loaded. Reporting it
|
|
62
|
+
as a slot number would put slot -1 on someone's dashboard."""
|
|
63
|
+
printer = _printer(_box(0), _box(1, loaded_slot=-1))
|
|
64
|
+
|
|
65
|
+
assert printer.secondary_multi_color_box_loaded_slot is None
|
|
66
|
+
|
|
67
|
+
def test_a_single_box_printer_has_no_second_slot(self):
|
|
68
|
+
printer = _printer(_box(0, loaded_slot=1))
|
|
69
|
+
|
|
70
|
+
assert printer.secondary_multi_color_box_loaded_slot is None
|
|
71
|
+
|
|
72
|
+
def test_no_ace_at_all_is_not_an_error(self):
|
|
73
|
+
assert _printer().secondary_multi_color_box_loaded_slot is None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class TestStoppingTheDryer:
|
|
77
|
+
"""hass-anycubic #39: stopping the second ACE's dryer did nothing.
|
|
78
|
+
|
|
79
|
+
The stop order named no box, and the sender numbers unnamed boxes by
|
|
80
|
+
position, so every single-box stop went to box 0.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
async def _sent_boxes(self, **kwargs):
|
|
84
|
+
from unittest.mock import AsyncMock
|
|
85
|
+
|
|
86
|
+
from anycubic_cloud_api.anycubic_api import AnycubicAPI
|
|
87
|
+
|
|
88
|
+
api = AnycubicAPI(session=MagicMock(), cookie_jar=MagicMock())
|
|
89
|
+
api._send_anycubic_order = AsyncMock(return_value="msgid")
|
|
90
|
+
printer = _printer(_box(0), _box(1))
|
|
91
|
+
|
|
92
|
+
await api.multi_color_box_drying_stop(printer, **kwargs)
|
|
93
|
+
|
|
94
|
+
order = api._send_anycubic_order.await_args.kwargs["order_request"]
|
|
95
|
+
return order.order_data["multi_color_box"]
|
|
96
|
+
|
|
97
|
+
async def test_the_second_box_is_the_one_stopped(self):
|
|
98
|
+
boxes = await self._sent_boxes(box_id=1)
|
|
99
|
+
|
|
100
|
+
assert [box["id"] for box in boxes] == [1]
|
|
101
|
+
assert boxes[0]["drying_status"]["status"] == 0
|
|
102
|
+
|
|
103
|
+
async def test_the_first_box_is_still_stopped_by_name(self):
|
|
104
|
+
boxes = await self._sent_boxes(box_id=0)
|
|
105
|
+
|
|
106
|
+
assert [box["id"] for box in boxes] == [0]
|
|
107
|
+
|
|
108
|
+
async def test_no_box_named_stops_every_box(self):
|
|
109
|
+
boxes = await self._sent_boxes()
|
|
110
|
+
|
|
111
|
+
assert [box["id"] for box in boxes] == [0, 1]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/anycubic_api.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/const/const.py
RENAMED
|
File without changes
|
{anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/const/enums.py
RENAMED
|
File without changes
|
{anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/const/error_codes.py
RENAMED
|
File without changes
|
|
File without changes
|
{anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/const/mqtt.py
RENAMED
|
File without changes
|
{anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/const/regions.py
RENAMED
|
File without changes
|
|
File without changes
|
{anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/data_models/files.py
RENAMED
|
File without changes
|
|
File without changes
|
{anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/data_models/orders.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/helpers/helpers.py
RENAMED
|
File without changes
|
{anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/lan/__init__.py
RENAMED
|
File without changes
|
{anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/lan/client.py
RENAMED
|
File without changes
|
{anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/lan/commands.py
RENAMED
|
File without changes
|
{anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/lan/handshake.py
RENAMED
|
File without changes
|
{anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/models/auth.py
RENAMED
|
File without changes
|
|
File without changes
|
{anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api/models/http.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{anycubic_cloud_api-0.4.28 → anycubic_cloud_api-0.4.32}/src/anycubic_cloud_api.egg-info/requires.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|