anycubic-cloud-api 0.4.34__tar.gz → 0.4.36__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.34/src/anycubic_cloud_api.egg-info → anycubic_cloud_api-0.4.36}/PKG-INFO +1 -1
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/pyproject.toml +1 -1
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/api/mqtt.py +6 -5
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/data_models/printer.py +81 -18
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/data_models/printer_properties.py +18 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/helpers/helpers.py +28 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36/src/anycubic_cloud_api.egg-info}/PKG-INFO +1 -1
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api.egg-info/SOURCES.txt +2 -0
- anycubic_cloud_api-0.4.36/tests/test_filament_change_0435.py +133 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/tests/test_print_reports_0434.py +9 -4
- anycubic_cloud_api-0.4.36/tests/test_printer_slot_0436.py +149 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/COPYRIGHT +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/LICENSE +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/MANIFEST.in +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/README.md +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/setup.cfg +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/__init__.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/anycubic_api.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/api/base.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/api/functions.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/const/api_endpoints.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/const/const.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/const/enums.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/const/error_codes.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/const/lan.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/const/mqtt.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/const/regions.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/data_models/consumable.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/data_models/files.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/data_models/gcode_file.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/data_models/orders.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/data_models/print_response.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/data_models/print_speed_mode.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/data_models/printing_settings.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/data_models/project.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/exceptions/error_strings.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/exceptions/exceptions.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/lan/__init__.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/lan/client.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/lan/commands.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/lan/handshake.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/models/auth.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/models/cloud_upload.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/models/http.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/resources/anycubic_mqqt_tls_ca.crt +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/resources/anycubic_mqqt_tls_client.crt +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/resources/anycubic_mqqt_tls_client.key +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api.egg-info/dependency_links.txt +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api.egg-info/requires.txt +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api.egg-info/top_level.txt +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/tests/test_auth_mode_fallback.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/tests/test_axis_position.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/tests/test_endpoint_urls.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/tests/test_exchange_rate_limit.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/tests/test_lan_client.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/tests/test_lan_commands.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/tests/test_lan_handshake.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/tests/test_lan_reports.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/tests/test_null_tolerant_parsing.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/tests/test_partial_reports.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/tests/test_printer_error_codes.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/tests/test_project_null_status.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/tests/test_regions.py +0 -0
- {anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/tests/test_secondary_ace.py +0 -0
|
@@ -42,6 +42,7 @@ from ..helpers.helpers import (
|
|
|
42
42
|
get_mqtt_ssl_path_key,
|
|
43
43
|
get_part_from_mqtt_topic,
|
|
44
44
|
get_ssl_cert_directory,
|
|
45
|
+
redact_for_log,
|
|
45
46
|
redact_part_from_mqtt_topic,
|
|
46
47
|
)
|
|
47
48
|
from .functions import AnycubicAPIFunctions
|
|
@@ -151,7 +152,7 @@ class AnycubicMQTTAPI(AnycubicAPIFunctions):
|
|
|
151
152
|
if self._mqtt_topic_is_user_topic(topic):
|
|
152
153
|
self._log_to_debug(
|
|
153
154
|
f"Anycubic MQTT USER Msg Received on `{topic}`:\n"
|
|
154
|
-
f" {payload}"
|
|
155
|
+
f" {redact_for_log(payload)}"
|
|
155
156
|
)
|
|
156
157
|
|
|
157
158
|
else:
|
|
@@ -182,7 +183,7 @@ class AnycubicMQTTAPI(AnycubicAPIFunctions):
|
|
|
182
183
|
f" with type: {e.unhandled_mqtt_type}, "
|
|
183
184
|
f"action: {e.unhandled_mqtt_action}, "
|
|
184
185
|
f"state: {e.unhandled_mqtt_state}\n"
|
|
185
|
-
f" unhandled data: {e.unhandled_mqtt_data}"
|
|
186
|
+
f" unhandled data: {redact_for_log(e.unhandled_mqtt_data)}"
|
|
186
187
|
)
|
|
187
188
|
|
|
188
189
|
except AnycubicMQTTUnknownUpdate as e:
|
|
@@ -193,7 +194,7 @@ class AnycubicMQTTAPI(AnycubicAPIFunctions):
|
|
|
193
194
|
self._log_to_debug(
|
|
194
195
|
f"Anycubic MQTT Message not understood: {e}\n"
|
|
195
196
|
f" on MQTT topic: {redacted_topic}\n"
|
|
196
|
-
f" {payload}"
|
|
197
|
+
f" {redact_for_log(payload)}"
|
|
197
198
|
)
|
|
198
199
|
|
|
199
200
|
except Exception as e:
|
|
@@ -203,7 +204,7 @@ class AnycubicMQTTAPI(AnycubicAPIFunctions):
|
|
|
203
204
|
self._log_to_error(
|
|
204
205
|
f"Anycubic MQTT Message error: {error_type}: {e}\n"
|
|
205
206
|
f" on MQTT topic: {redacted_topic}\n"
|
|
206
|
-
f" {payload}\n"
|
|
207
|
+
f" {redact_for_log(payload)}\n"
|
|
207
208
|
f"{tb}"
|
|
208
209
|
)
|
|
209
210
|
|
|
@@ -230,7 +231,7 @@ class AnycubicMQTTAPI(AnycubicAPIFunctions):
|
|
|
230
231
|
self._log_to_debug(
|
|
231
232
|
f"Anycubic MQTT Message processed on topic: "
|
|
232
233
|
f"{redact_part_from_mqtt_topic(topic, 6)}\n"
|
|
233
|
-
f" {payload}"
|
|
234
|
+
f" {redact_for_log(payload)}"
|
|
234
235
|
)
|
|
235
236
|
|
|
236
237
|
def _mqtt_publish_on_topic(
|
|
@@ -64,6 +64,20 @@ _PRINT_PROGRESS_KEYS = (
|
|
|
64
64
|
'supplies_usage',
|
|
65
65
|
)
|
|
66
66
|
|
|
67
|
+
# The steps of a filament change, as a Kobra X with an ACE 2 Pro reports
|
|
68
|
+
# them (#33). The ACE unfeed is the mirror of the feed, not yet seen.
|
|
69
|
+
_FILAMENT_CHANGE_STATES = frozenset({
|
|
70
|
+
'extfilbox_filament_feeding',
|
|
71
|
+
'extfilbox_filament_unfeeding',
|
|
72
|
+
'ace_filament_feeding',
|
|
73
|
+
'ace_filament_unfeeding',
|
|
74
|
+
'nozzle_purging',
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
# Printers with a feeder of their own, which shares the box list with any
|
|
78
|
+
# ACE fitted: the Kobra X.
|
|
79
|
+
_PRINTERS_WITH_OWN_FEEDER = frozenset({20030})
|
|
80
|
+
|
|
67
81
|
# How a printer words a refused order when it gives no code.
|
|
68
82
|
_REFUSED_STATES = frozenset({'failed', 'fail', 'error', 'refused'})
|
|
69
83
|
|
|
@@ -438,6 +452,12 @@ class AnycubicPrinter:
|
|
|
438
452
|
updated.get(box.box_id, box) for box in known_boxes
|
|
439
453
|
])
|
|
440
454
|
|
|
455
|
+
if known_boxes:
|
|
456
|
+
previous = {box.box_id: box for box in known_boxes}
|
|
457
|
+
for box in self._multi_color_box:
|
|
458
|
+
if (known := previous.get(box.box_id)) is not None and known is not box:
|
|
459
|
+
box.keep_printer_slot_from(known)
|
|
460
|
+
|
|
441
461
|
except Exception as e:
|
|
442
462
|
self._initialisation_error = True
|
|
443
463
|
# Swallowing this leaves the ACE list empty, which presents as
|
|
@@ -1317,13 +1337,23 @@ class AnycubicPrinter:
|
|
|
1317
1337
|
data.get('settings')
|
|
1318
1338
|
|
|
1319
1339
|
if 'channel_no' in data:
|
|
1320
|
-
self._apply_active_channel(data['channel_no'])
|
|
1340
|
+
self._apply_active_channel(data['channel_no'], data.get('unit_no'))
|
|
1321
1341
|
|
|
1322
1342
|
# A Kobra X reports progress this way between info reports, with
|
|
1323
1343
|
# no task id. It was logged as unhandled and dropped (#34).
|
|
1324
1344
|
if any(key in data for key in _PRINT_PROGRESS_KEYS):
|
|
1325
1345
|
self._apply_print_progress(project_id, data)
|
|
1326
1346
|
return
|
|
1347
|
+
elif action == 'start' and state in _FILAMENT_CHANGE_STATES:
|
|
1348
|
+
# Each step of a filament change, naming the channel (and, for an
|
|
1349
|
+
# ACE, the unit). Only a feed says what is now loaded (#33).
|
|
1350
|
+
data = payload.get('data') or {}
|
|
1351
|
+
# Read on every step, so the ones that change nothing are not
|
|
1352
|
+
# reported as unhandled.
|
|
1353
|
+
channel_no, unit_no = data.get('channel_no'), data.get('unit_no')
|
|
1354
|
+
if state.endswith('_filament_feeding'):
|
|
1355
|
+
self._apply_active_channel(channel_no, unit_no)
|
|
1356
|
+
return
|
|
1327
1357
|
elif action in ['start', 'stop'] and state in ['failed']:
|
|
1328
1358
|
err_msg = payload.get('msg')
|
|
1329
1359
|
self._is_printing = 1
|
|
@@ -1336,31 +1366,37 @@ class AnycubicPrinter:
|
|
|
1336
1366
|
else:
|
|
1337
1367
|
raise AnycubicMQTTUnknownUpdate(ErrorsMQTTUpdate.job_status)
|
|
1338
1368
|
|
|
1339
|
-
def _apply_active_channel(self, channel_no: Any) -> None:
|
|
1369
|
+
def _apply_active_channel(self, channel_no: Any, unit_no: Any = None) -> None:
|
|
1340
1370
|
"""Follow the filament channel the printer says it is printing from.
|
|
1341
1371
|
|
|
1342
1372
|
A Kobra X reports each filament change as a print update carrying
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1373
|
+
``channel_no``, and stops sending the box's own ``loaded_slot`` after
|
|
1374
|
+
the first few changes of a print, which is why the loaded slot froze
|
|
1375
|
+
(#34). Each box numbers its channels from 1; an ACE adds ``unit_no``
|
|
1376
|
+
and the printer's own feeder sends none (#33, Kobra X + ACE 2 Pro).
|
|
1377
|
+
|
|
1378
|
+
With one box the channel can only mean that box. With several, an
|
|
1379
|
+
ACE's ``unit_no`` picks it on a printer whose boxes are all ACEs.
|
|
1380
|
+
On a Kobra X with an ACE, which list entry is the printer's own
|
|
1381
|
+
feeder is not yet confirmed, so nothing is guessed there: a wrong
|
|
1382
|
+
slot is worse than a stale one.
|
|
1348
1383
|
"""
|
|
1349
1384
|
channel = as_int(channel_no, 0)
|
|
1385
|
+
boxes = self._multi_color_box
|
|
1350
1386
|
|
|
1351
|
-
if channel < 1 or not
|
|
1387
|
+
if channel < 1 or not boxes:
|
|
1352
1388
|
return
|
|
1353
1389
|
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
if index < slot_count:
|
|
1360
|
-
box.set_slot_loaded(index)
|
|
1390
|
+
if len(boxes) == 1:
|
|
1391
|
+
box = boxes[0]
|
|
1392
|
+
else:
|
|
1393
|
+
unit = as_int(unit_no, 0)
|
|
1394
|
+
if unit < 1 or unit > len(boxes) or self._machine_type in _PRINTERS_WITH_OWN_FEEDER:
|
|
1361
1395
|
return
|
|
1396
|
+
box = boxes[unit - 1]
|
|
1362
1397
|
|
|
1363
|
-
|
|
1398
|
+
if channel <= len(box.slots):
|
|
1399
|
+
box.set_slot_loaded(channel - 1)
|
|
1364
1400
|
|
|
1365
1401
|
def _apply_print_progress(
|
|
1366
1402
|
self,
|
|
@@ -1466,8 +1502,12 @@ class AnycubicPrinter:
|
|
|
1466
1502
|
continue
|
|
1467
1503
|
|
|
1468
1504
|
assert self._multi_color_box
|
|
1469
|
-
|
|
1470
|
-
|
|
1505
|
+
# An ACE 2 Pro's periodic drying report carries no `temp`, and
|
|
1506
|
+
# reading it raised every six minutes, so drying never updated
|
|
1507
|
+
# (#33). Keep the last reading rather than inventing one.
|
|
1508
|
+
if 'temp' in box:
|
|
1509
|
+
self._multi_color_box[box_id].set_current_temperature(box['temp'])
|
|
1510
|
+
self._multi_color_box[box_id].set_drying_status(box.get('drying_status'))
|
|
1471
1511
|
return
|
|
1472
1512
|
elif action == 'setDry' and self._report_refused_drying(state, payload):
|
|
1473
1513
|
return
|
|
@@ -1508,9 +1548,32 @@ class AnycubicPrinter:
|
|
|
1508
1548
|
if self.external_shelves:
|
|
1509
1549
|
self.external_shelves.update_with_mqtt_data(data)
|
|
1510
1550
|
return
|
|
1551
|
+
elif action == 'auto' and state == 'done':
|
|
1552
|
+
# Sent as a filament change completes, naming the feeding slot as
|
|
1553
|
+
# "<box>-<slot>", both counted from 0 ("0-3" while the screen shows
|
|
1554
|
+
# slot 4 on a Kobra X, #34); empty while the filament is retracted.
|
|
1555
|
+
data = payload.get('data') or {}
|
|
1556
|
+
self._apply_current_filament(data.get('current_filament'))
|
|
1557
|
+
return
|
|
1511
1558
|
else:
|
|
1512
1559
|
raise AnycubicMQTTUnknownUpdate(ErrorsMQTTUpdate.shelves)
|
|
1513
1560
|
|
|
1561
|
+
def _apply_current_filament(self, current_filament: Any) -> None:
|
|
1562
|
+
"""Follow the printer's own "<box>-<slot>" reading, where the box is certain.
|
|
1563
|
+
|
|
1564
|
+
Only with a single box: which list entry the printer's box number
|
|
1565
|
+
means beside an ACE is not confirmed (#33). An empty reading is a
|
|
1566
|
+
retract for calibration or wiping, not a change of slot, so the last
|
|
1567
|
+
slot is kept.
|
|
1568
|
+
"""
|
|
1569
|
+
boxes = self._multi_color_box
|
|
1570
|
+
if not isinstance(current_filament, str) or not boxes or len(boxes) != 1:
|
|
1571
|
+
return
|
|
1572
|
+
box_no, _, slot_no = current_filament.partition('-')
|
|
1573
|
+
box_index, slot_index = as_int(box_no, -1), as_int(slot_no, -1)
|
|
1574
|
+
if box_index == 0 and 0 <= slot_index < len(boxes[0].slots):
|
|
1575
|
+
boxes[0].set_slot_loaded(slot_index)
|
|
1576
|
+
|
|
1514
1577
|
def _process_mqtt_update_file(
|
|
1515
1578
|
self,
|
|
1516
1579
|
action: str,
|
|
@@ -702,6 +702,7 @@ class AnycubicMultiColorBox:
|
|
|
702
702
|
"_model_id",
|
|
703
703
|
"_auto_feed",
|
|
704
704
|
"_loaded_slot",
|
|
705
|
+
"_slot_from_printer",
|
|
705
706
|
"_feed_status",
|
|
706
707
|
"_temp",
|
|
707
708
|
"_humidity",
|
|
@@ -734,6 +735,9 @@ class AnycubicMultiColorBox:
|
|
|
734
735
|
self._model_id: int = as_int(model_id)
|
|
735
736
|
self._auto_feed: int = as_int(auto_feed)
|
|
736
737
|
self._loaded_slot: int = as_int(loaded_slot, -1)
|
|
738
|
+
# Whether the printer itself has reported the loaded slot, as opposed
|
|
739
|
+
# to a cloud record that only knew it at some earlier point.
|
|
740
|
+
self._slot_from_printer = False
|
|
737
741
|
self.set_feed_status(feed_status)
|
|
738
742
|
self.set_current_temperature(temp)
|
|
739
743
|
self._humidity: float | None = (
|
|
@@ -759,7 +763,21 @@ class AnycubicMultiColorBox:
|
|
|
759
763
|
self._feed_status = AnycubicFeedStatus.from_json(feed_status)
|
|
760
764
|
|
|
761
765
|
def set_slot_loaded(self, slot_num: int) -> None:
|
|
766
|
+
"""A loaded slot reported by the printer (MQTT or LAN)."""
|
|
762
767
|
self._loaded_slot = as_int(slot_num, -1)
|
|
768
|
+
self._slot_from_printer = True
|
|
769
|
+
|
|
770
|
+
def keep_printer_slot_from(self, previous: AnycubicMultiColorBox) -> None:
|
|
771
|
+
"""Carry over a slot the printer reported, past a cloud refresh.
|
|
772
|
+
|
|
773
|
+
The cloud's record of the loaded slot goes stale during a print: a
|
|
774
|
+
Kobra X kept reporting slot 4 while each cloud poll said slot 2, and
|
|
775
|
+
the sensor flipped to 2 mid-print (#34). What the printer said last
|
|
776
|
+
wins until it says something else.
|
|
777
|
+
"""
|
|
778
|
+
if previous._slot_from_printer:
|
|
779
|
+
self._loaded_slot = previous._loaded_slot
|
|
780
|
+
self._slot_from_printer = True
|
|
763
781
|
|
|
764
782
|
def set_current_temperature(self, temp: int) -> None:
|
|
765
783
|
# Reached from the constructor as well as from MQTT, so a null
|
{anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/helpers/helpers.py
RENAMED
|
@@ -164,6 +164,34 @@ def get_part_from_mqtt_topic(topic: str, part: int) -> str | None:
|
|
|
164
164
|
return split_topic[part]
|
|
165
165
|
|
|
166
166
|
|
|
167
|
+
# Identifiers a printer puts in its own reports. A debug log gets pasted into
|
|
168
|
+
# public issues, and only the topic's printer key was being redacted: a
|
|
169
|
+
# Kobra X's PrintStart report carried its cn_code in the clear (#34).
|
|
170
|
+
_LOG_REDACT_KEYS = frozenset({
|
|
171
|
+
'cn_code', 'cn', 'sn', 'serial', 'machine_sn', 'mac', 'machine_mac',
|
|
172
|
+
'ip', 'key', 'printer_key', 'device_id', 'deviceId', 'user_id', 'email',
|
|
173
|
+
})
|
|
174
|
+
_LOG_REDACT_PARTS = ('token', 'secret', 'password')
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def redact_for_log(data: Any) -> Any:
|
|
178
|
+
"""A copy of an MQTT payload that is safe to put in a log."""
|
|
179
|
+
if isinstance(data, dict):
|
|
180
|
+
return {
|
|
181
|
+
key: (
|
|
182
|
+
"**REDACTED**"
|
|
183
|
+
if isinstance(key, str)
|
|
184
|
+
and data[key] not in (None, '')
|
|
185
|
+
and (key in _LOG_REDACT_KEYS or any(part in key.lower() for part in _LOG_REDACT_PARTS))
|
|
186
|
+
else redact_for_log(data[key])
|
|
187
|
+
)
|
|
188
|
+
for key in data
|
|
189
|
+
}
|
|
190
|
+
if isinstance(data, list):
|
|
191
|
+
return [redact_for_log(item) for item in data]
|
|
192
|
+
return data
|
|
193
|
+
|
|
194
|
+
|
|
167
195
|
def redact_part_from_mqtt_topic(topic: str, part: int) -> str:
|
|
168
196
|
split_topic = topic.split("/")
|
|
169
197
|
new_chunk = list()
|
{anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api.egg-info/SOURCES.txt
RENAMED
|
@@ -47,6 +47,7 @@ tests/test_auth_mode_fallback.py
|
|
|
47
47
|
tests/test_axis_position.py
|
|
48
48
|
tests/test_endpoint_urls.py
|
|
49
49
|
tests/test_exchange_rate_limit.py
|
|
50
|
+
tests/test_filament_change_0435.py
|
|
50
51
|
tests/test_lan_client.py
|
|
51
52
|
tests/test_lan_commands.py
|
|
52
53
|
tests/test_lan_handshake.py
|
|
@@ -55,6 +56,7 @@ tests/test_null_tolerant_parsing.py
|
|
|
55
56
|
tests/test_partial_reports.py
|
|
56
57
|
tests/test_print_reports_0434.py
|
|
57
58
|
tests/test_printer_error_codes.py
|
|
59
|
+
tests/test_printer_slot_0436.py
|
|
58
60
|
tests/test_project_null_status.py
|
|
59
61
|
tests/test_regions.py
|
|
60
62
|
tests/test_secondary_ace.py
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Filament-change reports and the ACE 2 Pro's drying report (0.4.35).
|
|
2
|
+
|
|
3
|
+
From a Kobra X with an ACE 2 Pro (#33). Each step of a filament change
|
|
4
|
+
arrives as a print ``start`` naming ``channel_no``, counted from 1 within
|
|
5
|
+
its box, plus ``unit_no`` when the feed comes from an ACE. These were
|
|
6
|
+
dropped as unhandled. The ACE 2 Pro's periodic drying report has no
|
|
7
|
+
``temp``, and reading it raised every six minutes.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from unittest.mock import MagicMock
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
from anycubic_cloud_api.data_models.consumable import AnycubicConsumableData
|
|
15
|
+
from anycubic_cloud_api.data_models.printer import AnycubicPrinter
|
|
16
|
+
|
|
17
|
+
KOBRA_X = 20030
|
|
18
|
+
KOBRA_S1 = 20025
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def slot(index):
|
|
22
|
+
return {"index": index, "sku": "", "type": "PLA", "color": [1, 2, 3], "status": 5, "edit_status": 1}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def box(box_id, slots=4):
|
|
26
|
+
return {"id": box_id, "loaded_slot": -1, "temp": 21, "slots": [slot(i) for i in range(slots)]}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def make_printer(boxes, machine_type=KOBRA_X):
|
|
30
|
+
return AnycubicPrinter(
|
|
31
|
+
api_parent=MagicMock(),
|
|
32
|
+
machine_type=machine_type,
|
|
33
|
+
machine_name="Printer",
|
|
34
|
+
id=1,
|
|
35
|
+
multi_color_box=boxes,
|
|
36
|
+
ignore_init_errors=True,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def message(msg_type, action, state, data):
|
|
41
|
+
return AnycubicConsumableData({
|
|
42
|
+
"type": msg_type, "action": action, "state": state,
|
|
43
|
+
"code": 200, "msg": "done", "msgid": "m", "timestamp": 1, "data": data,
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def topic(machine_type, kind):
|
|
48
|
+
return f"anycubic/anycubicCloud/v1/printer/public/{machine_type}/key/{kind}/report"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def feed(printer, state, channel_no, unit_no=None, machine_type=KOBRA_X):
|
|
52
|
+
data = {"channel_no": channel_no, "taskid": "t"}
|
|
53
|
+
if unit_no is not None:
|
|
54
|
+
data["unit_no"] = unit_no
|
|
55
|
+
printer.process_mqtt_update(topic(machine_type, "print"), message("print", "start", state, data))
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def loaded(printer):
|
|
59
|
+
return [b.loaded_slot for b in printer.multi_color_box]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class TestFilamentChange:
|
|
63
|
+
@pytest.mark.parametrize(("state", "unit_no"), [
|
|
64
|
+
("extfilbox_filament_feeding", None),
|
|
65
|
+
("ace_filament_feeding", 1),
|
|
66
|
+
])
|
|
67
|
+
def test_one_box_follows_the_feed(self, state, unit_no):
|
|
68
|
+
printer = make_printer([box(0)])
|
|
69
|
+
|
|
70
|
+
feed(printer, state, 3, unit_no)
|
|
71
|
+
|
|
72
|
+
assert loaded(printer) == [2]
|
|
73
|
+
|
|
74
|
+
@pytest.mark.parametrize("state", [
|
|
75
|
+
"extfilbox_filament_unfeeding", "ace_filament_unfeeding", "nozzle_purging",
|
|
76
|
+
])
|
|
77
|
+
def test_other_steps_are_read_but_change_nothing(self, state):
|
|
78
|
+
"""Consumed, so not logged as unhandled; only a feed says what is loaded."""
|
|
79
|
+
printer = make_printer([box(0)])
|
|
80
|
+
printer.multi_color_box[0].set_slot_loaded(1)
|
|
81
|
+
|
|
82
|
+
feed(printer, state, 3, 1)
|
|
83
|
+
|
|
84
|
+
assert loaded(printer) == [1]
|
|
85
|
+
|
|
86
|
+
@pytest.mark.parametrize(("unit_no", "expected"), [(1, [1, -1]), (2, [-1, 1])])
|
|
87
|
+
def test_an_ace_unit_picks_its_box_on_a_printer_of_aces(self, unit_no, expected):
|
|
88
|
+
"""A Kobra S1 has no feeder of its own: unit 1 is the first ACE."""
|
|
89
|
+
printer = make_printer([box(0), box(1)], machine_type=KOBRA_S1)
|
|
90
|
+
|
|
91
|
+
feed(printer, "ace_filament_feeding", 2, unit_no, machine_type=KOBRA_S1)
|
|
92
|
+
|
|
93
|
+
assert loaded(printer) == expected
|
|
94
|
+
|
|
95
|
+
@pytest.mark.parametrize(("state", "unit_no"), [
|
|
96
|
+
("extfilbox_filament_feeding", None),
|
|
97
|
+
("ace_filament_feeding", 1),
|
|
98
|
+
])
|
|
99
|
+
def test_a_kobra_x_with_an_ace_guesses_nothing(self, state, unit_no):
|
|
100
|
+
"""Which list entry is the Kobra X's own feeder is unconfirmed (#33)."""
|
|
101
|
+
printer = make_printer([box(0), box(1)])
|
|
102
|
+
|
|
103
|
+
feed(printer, state, 1, unit_no)
|
|
104
|
+
|
|
105
|
+
assert loaded(printer) == [-1, -1]
|
|
106
|
+
|
|
107
|
+
@pytest.mark.parametrize(("channel_no", "unit_no"), [(5, 1), (0, 1), (2, 3), ("x", 1), (2, None)])
|
|
108
|
+
def test_a_channel_or_unit_that_fits_no_box_changes_nothing(self, channel_no, unit_no):
|
|
109
|
+
printer = make_printer([box(0), box(1)], machine_type=KOBRA_S1)
|
|
110
|
+
|
|
111
|
+
feed(printer, "ace_filament_feeding", channel_no, unit_no, machine_type=KOBRA_S1)
|
|
112
|
+
|
|
113
|
+
assert loaded(printer) == [-1, -1]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class TestAce2ProDryingReport:
|
|
117
|
+
REPORT = {"multi_color_box": [{
|
|
118
|
+
"drying_status": {"duration": 14400, "humidity": 28, "remain_time": 162, "status": 1, "target_temp": 50},
|
|
119
|
+
"id": 0,
|
|
120
|
+
}]}
|
|
121
|
+
|
|
122
|
+
def test_a_report_without_temp_still_updates_drying(self):
|
|
123
|
+
printer = make_printer([box(0)])
|
|
124
|
+
|
|
125
|
+
printer.process_mqtt_update(
|
|
126
|
+
topic(KOBRA_X, "multiColorBox"),
|
|
127
|
+
message("multiColorBox", "autoUpdateDryStatus", "success", self.REPORT),
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
ace = printer.multi_color_box[0]
|
|
131
|
+
assert ace.drying_status.is_drying
|
|
132
|
+
assert ace.drying_status.target_temperature == 50
|
|
133
|
+
assert ace.current_temperature == 21 # the last reading is kept
|
|
@@ -90,13 +90,18 @@ class TestActiveChannel:
|
|
|
90
90
|
|
|
91
91
|
assert printer.primary_multi_color_box_loaded_slot == slot_index
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
@pytest.mark.parametrize("channel_no", [1, 5])
|
|
94
|
+
def test_with_two_boxes_and_no_unit_nothing_is_guessed(self, channel_no):
|
|
95
|
+
"""Channels are numbered per box (#33), so without a unit the box is unknown.
|
|
96
|
+
|
|
97
|
+
0.4.34 counted across boxes and put channel 5 in the second box.
|
|
98
|
+
"""
|
|
95
99
|
printer = make_printer([box(0), box(1)])
|
|
100
|
+
before = [b.loaded_slot for b in printer.multi_color_box]
|
|
96
101
|
|
|
97
|
-
self._channel(printer,
|
|
102
|
+
self._channel(printer, channel_no)
|
|
98
103
|
|
|
99
|
-
assert printer.
|
|
104
|
+
assert [b.loaded_slot for b in printer.multi_color_box] == before
|
|
100
105
|
|
|
101
106
|
@pytest.mark.parametrize("channel_no", [0, -1, 9, None, "x"])
|
|
102
107
|
def test_a_channel_with_no_slot_changes_nothing(self, channel_no):
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""The printer's own loaded slot, and logs safe to paste (0.4.36).
|
|
2
|
+
|
|
3
|
+
From a Kobra X printing from slot 4 (#34): the sensor flipped to 2 mid-print
|
|
4
|
+
with no message from the printer. Each cloud poll rebuilt the box from the
|
|
5
|
+
cloud's record, whose loaded slot was stale. The printer also names the
|
|
6
|
+
feeding slot itself in its extfilbox report, and its PrintStart report put
|
|
7
|
+
the printer's cn_code into the debug log.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
from test_filament_change_0435 import (
|
|
12
|
+
KOBRA_S1,
|
|
13
|
+
KOBRA_X,
|
|
14
|
+
box,
|
|
15
|
+
make_printer,
|
|
16
|
+
message,
|
|
17
|
+
topic,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from anycubic_cloud_api.helpers.helpers import redact_for_log
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def cloud_info(boxes, machine_type=KOBRA_X):
|
|
24
|
+
return {
|
|
25
|
+
"id": 1, "name": "Printer", "key": "k", "machine_type": machine_type, "model": "Printer",
|
|
26
|
+
"img": "", "device_status": 1, "is_printing": 2, "type_function_ids": [], "version": None,
|
|
27
|
+
"multi_color_box": boxes,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def stale_box(box_id, loaded_slot):
|
|
32
|
+
b = box(box_id)
|
|
33
|
+
b["loaded_slot"] = loaded_slot
|
|
34
|
+
return b
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def loaded(printer):
|
|
38
|
+
return [b.loaded_slot for b in printer.multi_color_box]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class TestCloudPollKeepsThePrintersSlot:
|
|
42
|
+
def test_a_slot_the_printer_reported_survives_a_cloud_poll(self):
|
|
43
|
+
printer = make_printer([box(0)])
|
|
44
|
+
printer.multi_color_box[0].set_slot_loaded(3)
|
|
45
|
+
|
|
46
|
+
printer.update_from_info_json(cloud_info([stale_box(0, 1)]))
|
|
47
|
+
|
|
48
|
+
assert loaded(printer) == [3]
|
|
49
|
+
|
|
50
|
+
def test_without_a_printer_report_the_cloud_value_is_used(self):
|
|
51
|
+
printer = make_printer([box(0)])
|
|
52
|
+
|
|
53
|
+
printer.update_from_info_json(cloud_info([stale_box(0, 1)]))
|
|
54
|
+
|
|
55
|
+
assert loaded(printer) == [1]
|
|
56
|
+
|
|
57
|
+
def test_the_next_printer_report_still_wins(self):
|
|
58
|
+
printer = make_printer([box(0)])
|
|
59
|
+
printer.multi_color_box[0].set_slot_loaded(3)
|
|
60
|
+
printer.update_from_info_json(cloud_info([stale_box(0, 1)]))
|
|
61
|
+
|
|
62
|
+
printer.multi_color_box[0].set_slot_loaded(0)
|
|
63
|
+
|
|
64
|
+
assert loaded(printer) == [0]
|
|
65
|
+
|
|
66
|
+
def test_each_box_keeps_its_own(self):
|
|
67
|
+
printer = make_printer([box(0), box(1)], machine_type=KOBRA_S1)
|
|
68
|
+
printer.multi_color_box[1].set_slot_loaded(2)
|
|
69
|
+
|
|
70
|
+
printer.update_from_info_json(cloud_info([stale_box(0, 1), stale_box(1, 0)], machine_type=KOBRA_S1))
|
|
71
|
+
|
|
72
|
+
assert loaded(printer) == [1, 2]
|
|
73
|
+
|
|
74
|
+
def test_other_cloud_fields_still_update(self):
|
|
75
|
+
printer = make_printer([box(0)])
|
|
76
|
+
printer.multi_color_box[0].set_slot_loaded(3)
|
|
77
|
+
refreshed = stale_box(0, 1)
|
|
78
|
+
refreshed["temp"] = 33
|
|
79
|
+
|
|
80
|
+
printer.update_from_info_json(cloud_info([refreshed]))
|
|
81
|
+
|
|
82
|
+
assert printer.multi_color_box[0].current_temperature == 33
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def extfilbox(printer, current_filament, machine_type=KOBRA_X):
|
|
86
|
+
printer.process_mqtt_update(
|
|
87
|
+
topic(machine_type, "extfilbox"),
|
|
88
|
+
message("extfilbox", "auto", "done", {"current_filament": current_filament}),
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class TestCurrentFilament:
|
|
93
|
+
@pytest.mark.parametrize(("reading", "slot"), [("0-3", 3), ("0-0", 0)])
|
|
94
|
+
def test_one_box_follows_the_printer(self, reading, slot):
|
|
95
|
+
printer = make_printer([box(0)])
|
|
96
|
+
|
|
97
|
+
extfilbox(printer, reading)
|
|
98
|
+
|
|
99
|
+
assert loaded(printer) == [slot]
|
|
100
|
+
|
|
101
|
+
@pytest.mark.parametrize("reading", ["", None, "0-9", "1-2", "x", "0-", "-1"])
|
|
102
|
+
def test_a_retract_or_unknown_reading_keeps_the_slot(self, reading):
|
|
103
|
+
printer = make_printer([box(0)])
|
|
104
|
+
printer.multi_color_box[0].set_slot_loaded(3)
|
|
105
|
+
|
|
106
|
+
extfilbox(printer, reading)
|
|
107
|
+
|
|
108
|
+
assert loaded(printer) == [3]
|
|
109
|
+
|
|
110
|
+
def test_with_two_boxes_nothing_is_guessed(self):
|
|
111
|
+
printer = make_printer([box(0), box(1)])
|
|
112
|
+
|
|
113
|
+
extfilbox(printer, "0-3")
|
|
114
|
+
|
|
115
|
+
assert loaded(printer) == [-1, -1]
|
|
116
|
+
|
|
117
|
+
def test_it_survives_the_next_cloud_poll(self):
|
|
118
|
+
printer = make_printer([box(0)])
|
|
119
|
+
extfilbox(printer, "0-3")
|
|
120
|
+
|
|
121
|
+
printer.update_from_info_json(cloud_info([stale_box(0, 1)]))
|
|
122
|
+
|
|
123
|
+
assert loaded(printer) == [3]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class TestRedactForLog:
|
|
127
|
+
def test_printer_identifiers_are_redacted_wherever_they_are(self):
|
|
128
|
+
payload = {"type": "buried", "data": {"cn_code": "CN123", "nested": [{"machine_mac": "AA:BB"}], "ip": "10.0.0.2"}}
|
|
129
|
+
|
|
130
|
+
assert redact_for_log(payload) == {
|
|
131
|
+
"type": "buried",
|
|
132
|
+
"data": {"cn_code": "**REDACTED**", "nested": [{"machine_mac": "**REDACTED**"}], "ip": "**REDACTED**"},
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
@pytest.mark.parametrize("key", ["token", "casdoor_user_token", "rtc_token", "app_secret", "password"])
|
|
136
|
+
def test_credentials_are_redacted(self, key):
|
|
137
|
+
assert redact_for_log({key: "secret-value"}) == {key: "**REDACTED**"}
|
|
138
|
+
|
|
139
|
+
def test_ordinary_fields_and_empty_values_pass_through(self):
|
|
140
|
+
payload = {"progress": 42, "taskid": "131448158", "cn_code": "", "state": "printing", "slots": [1, 2]}
|
|
141
|
+
|
|
142
|
+
assert redact_for_log(payload) == payload
|
|
143
|
+
|
|
144
|
+
def test_the_input_is_not_changed(self):
|
|
145
|
+
payload = {"data": {"cn_code": "CN123"}}
|
|
146
|
+
|
|
147
|
+
redact_for_log(payload)
|
|
148
|
+
|
|
149
|
+
assert payload == {"data": {"cn_code": "CN123"}}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/anycubic_api.py
RENAMED
|
File without changes
|
|
File without changes
|
{anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/api/functions.py
RENAMED
|
File without changes
|
|
File without changes
|
{anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/const/const.py
RENAMED
|
File without changes
|
{anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/const/enums.py
RENAMED
|
File without changes
|
{anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/const/error_codes.py
RENAMED
|
File without changes
|
|
File without changes
|
{anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/const/mqtt.py
RENAMED
|
File without changes
|
{anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/const/regions.py
RENAMED
|
File without changes
|
|
File without changes
|
{anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/data_models/files.py
RENAMED
|
File without changes
|
|
File without changes
|
{anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/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.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/lan/__init__.py
RENAMED
|
File without changes
|
{anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/lan/client.py
RENAMED
|
File without changes
|
{anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/lan/commands.py
RENAMED
|
File without changes
|
{anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/lan/handshake.py
RENAMED
|
File without changes
|
{anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/src/anycubic_cloud_api/models/auth.py
RENAMED
|
File without changes
|
|
File without changes
|
{anycubic_cloud_api-0.4.34 → anycubic_cloud_api-0.4.36}/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.34 → anycubic_cloud_api-0.4.36}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|