anycubic-cloud-api 0.2.0__tar.gz → 0.3.0__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.2.0/src/anycubic_cloud_api.egg-info → anycubic_cloud_api-0.3.0}/PKG-INFO +1 -1
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/pyproject.toml +1 -1
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/data_models/printer.py +160 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0/src/anycubic_cloud_api.egg-info}/PKG-INFO +1 -1
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api.egg-info/SOURCES.txt +1 -0
- anycubic_cloud_api-0.3.0/tests/test_lan_reports.py +214 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/LICENSE +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/README.md +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/setup.cfg +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/__init__.py +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/anycubic_api.py +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/api/base.py +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/api/functions.py +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/api/mqtt.py +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/const/api_endpoints.py +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/const/const.py +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/const/enums.py +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/const/lan.py +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/const/mqtt.py +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/data_models/consumable.py +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/data_models/files.py +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/data_models/gcode_file.py +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/data_models/orders.py +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/data_models/print_response.py +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/data_models/print_speed_mode.py +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/data_models/printer_properties.py +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/data_models/printing_settings.py +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/data_models/project.py +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/exceptions/error_strings.py +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/exceptions/exceptions.py +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/helpers/helpers.py +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/lan/__init__.py +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/lan/client.py +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/lan/handshake.py +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/models/auth.py +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/models/cloud_upload.py +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/models/http.py +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/resources/anycubic_mqqt_tls_ca.crt +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/resources/anycubic_mqqt_tls_client.crt +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/resources/anycubic_mqqt_tls_client.key +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api.egg-info/dependency_links.txt +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api.egg-info/requires.txt +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api.egg-info/top_level.txt +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/tests/test_axis_position.py +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/tests/test_lan_client.py +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/tests/test_lan_handshake.py +0 -0
- {anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/tests/test_partial_reports.py +0 -0
{anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/data_models/printer.py
RENAMED
|
@@ -49,8 +49,34 @@ if TYPE_CHECKING:
|
|
|
49
49
|
from .printer_properties import AnycubicDryingStatus, AnycubicMaterialMapping
|
|
50
50
|
|
|
51
51
|
|
|
52
|
+
def _drain(consumable: Any) -> dict[str, Any]:
|
|
53
|
+
"""Read every remaining key, returning them as a plain dict.
|
|
54
|
+
|
|
55
|
+
Reading is what marks a key consumed, and the payload contract fails loudly
|
|
56
|
+
on anything left behind -- so blocks whose keys vary by model are taken
|
|
57
|
+
wholesale rather than named one by one.
|
|
58
|
+
"""
|
|
59
|
+
if not consumable:
|
|
60
|
+
return {}
|
|
61
|
+
|
|
62
|
+
drained = dict(consumable.remaining_data)
|
|
63
|
+
|
|
64
|
+
for key in list(drained):
|
|
65
|
+
consumable.get(key)
|
|
66
|
+
|
|
67
|
+
return drained
|
|
68
|
+
|
|
69
|
+
|
|
52
70
|
class AnycubicPrinter:
|
|
53
71
|
__slots__ = (
|
|
72
|
+
"_camera_stream_url",
|
|
73
|
+
"_file_upload_url",
|
|
74
|
+
"_features",
|
|
75
|
+
"_ai_settings",
|
|
76
|
+
"_chamber_temp",
|
|
77
|
+
"_target_chamber_temp",
|
|
78
|
+
"_local_firmware_version",
|
|
79
|
+
"_local_ip",
|
|
54
80
|
"_ignore_init_errors",
|
|
55
81
|
"_initialisation_error",
|
|
56
82
|
"_api_parent",
|
|
@@ -1188,6 +1214,134 @@ class AnycubicPrinter:
|
|
|
1188
1214
|
'brightness': int(light.get('brightness') or 0),
|
|
1189
1215
|
}
|
|
1190
1216
|
|
|
1217
|
+
|
|
1218
|
+
def _process_mqtt_update_info(
|
|
1219
|
+
self,
|
|
1220
|
+
action: str,
|
|
1221
|
+
state: str,
|
|
1222
|
+
payload: AnycubicConsumableData,
|
|
1223
|
+
) -> None:
|
|
1224
|
+
"""A whole-printer snapshot.
|
|
1225
|
+
|
|
1226
|
+
Only the local connection sends this; over the cloud the same ground is
|
|
1227
|
+
covered by the separate status, temperature and fan reports. It is the
|
|
1228
|
+
only place the printer names its own stream and upload endpoints, and
|
|
1229
|
+
the only place it lists which features it physically has.
|
|
1230
|
+
"""
|
|
1231
|
+
data = payload['data']
|
|
1232
|
+
|
|
1233
|
+
if not data:
|
|
1234
|
+
return
|
|
1235
|
+
|
|
1236
|
+
urls = data.get('urls')
|
|
1237
|
+
|
|
1238
|
+
if urls:
|
|
1239
|
+
self._camera_stream_url = urls.get('rtspUrl')
|
|
1240
|
+
self._file_upload_url = urls.get('fileUploadurl')
|
|
1241
|
+
|
|
1242
|
+
self._features = _drain(data.get('features')) or self.features
|
|
1243
|
+
|
|
1244
|
+
temp = data.get('temp')
|
|
1245
|
+
|
|
1246
|
+
if temp:
|
|
1247
|
+
if self.parameter:
|
|
1248
|
+
self.parameter.update_current_temps(
|
|
1249
|
+
temp.get('curr_hotbed_temp'),
|
|
1250
|
+
temp.get('curr_nozzle_temp'),
|
|
1251
|
+
)
|
|
1252
|
+
# A Kobra S1 reports chamber temperatures of zero because it has no
|
|
1253
|
+
# chamber sensor, so they are kept raw rather than turned into an
|
|
1254
|
+
# always-zero entity.
|
|
1255
|
+
self._chamber_temp = temp.get('curr_chamber_temp')
|
|
1256
|
+
self._target_chamber_temp = temp.get('target_chamber_temp')
|
|
1257
|
+
_drain(temp)
|
|
1258
|
+
|
|
1259
|
+
if 'fan_speed_pct' in data:
|
|
1260
|
+
self._fan_speed = int(data['fan_speed_pct'])
|
|
1261
|
+
if 'print_speed_mode' in data:
|
|
1262
|
+
self._print_speed_mode = int(data['print_speed_mode'])
|
|
1263
|
+
|
|
1264
|
+
self._local_firmware_version = data.get('version')
|
|
1265
|
+
self._local_ip = data.get('ip')
|
|
1266
|
+
|
|
1267
|
+
# Consumed but not modelled: the printer's own name and model strings
|
|
1268
|
+
# duplicate what the device already carries, and the project blocks are
|
|
1269
|
+
# covered by the print report.
|
|
1270
|
+
data.get('printerName')
|
|
1271
|
+
data.get('model')
|
|
1272
|
+
data.get('state')
|
|
1273
|
+
data.get('project')
|
|
1274
|
+
data.get('last_project')
|
|
1275
|
+
data.get('aux_fan_speed_pct')
|
|
1276
|
+
data.get('box_fan_level')
|
|
1277
|
+
_drain(data)
|
|
1278
|
+
|
|
1279
|
+
def _process_mqtt_update_ai_settings(
|
|
1280
|
+
self,
|
|
1281
|
+
action: str,
|
|
1282
|
+
state: str,
|
|
1283
|
+
payload: AnycubicConsumableData,
|
|
1284
|
+
) -> None:
|
|
1285
|
+
"""Foreign-object and first-layer detection settings."""
|
|
1286
|
+
data = payload['data']
|
|
1287
|
+
|
|
1288
|
+
if not data:
|
|
1289
|
+
return
|
|
1290
|
+
|
|
1291
|
+
self._ai_settings = _drain(data.get('ai_settings')) or self.ai_settings
|
|
1292
|
+
|
|
1293
|
+
_drain(data)
|
|
1294
|
+
|
|
1295
|
+
|
|
1296
|
+
@property
|
|
1297
|
+
def camera_stream_url(self) -> str | None:
|
|
1298
|
+
"""Where the printer serves its camera, when it says.
|
|
1299
|
+
|
|
1300
|
+
Only reported over the local connection.
|
|
1301
|
+
"""
|
|
1302
|
+
return getattr(self, '_camera_stream_url', None)
|
|
1303
|
+
|
|
1304
|
+
@property
|
|
1305
|
+
def file_upload_url(self) -> str | None:
|
|
1306
|
+
"""Signed endpoint for uploading gcode straight to the printer."""
|
|
1307
|
+
return getattr(self, '_file_upload_url', None)
|
|
1308
|
+
|
|
1309
|
+
@property
|
|
1310
|
+
def features(self) -> dict[str, Any]:
|
|
1311
|
+
"""What this model says it can physically do.
|
|
1312
|
+
|
|
1313
|
+
Reported only over the local connection, and the most reliable way to
|
|
1314
|
+
tell models apart without owning one.
|
|
1315
|
+
"""
|
|
1316
|
+
return dict(getattr(self, '_features', {}) or {})
|
|
1317
|
+
|
|
1318
|
+
@property
|
|
1319
|
+
def ai_settings(self) -> dict[str, Any]:
|
|
1320
|
+
"""Foreign-object / first-layer detection settings."""
|
|
1321
|
+
return dict(getattr(self, '_ai_settings', {}) or {})
|
|
1322
|
+
|
|
1323
|
+
@property
|
|
1324
|
+
def ai_detection_enabled(self) -> bool | None:
|
|
1325
|
+
settings = self.ai_settings
|
|
1326
|
+
return bool(settings['status']) if 'status' in settings else None
|
|
1327
|
+
|
|
1328
|
+
@property
|
|
1329
|
+
def local_firmware_version(self) -> str | None:
|
|
1330
|
+
return getattr(self, '_local_firmware_version', None)
|
|
1331
|
+
|
|
1332
|
+
@property
|
|
1333
|
+
def chamber_temperature(self) -> float | None:
|
|
1334
|
+
"""Chamber temperature, where the printer has a sensor for it.
|
|
1335
|
+
|
|
1336
|
+
An open-frame or unsensored machine reports zero here, which is why
|
|
1337
|
+
this is not turned into an entity without checking it first.
|
|
1338
|
+
"""
|
|
1339
|
+
return getattr(self, '_chamber_temp', None)
|
|
1340
|
+
|
|
1341
|
+
@property
|
|
1342
|
+
def target_chamber_temperature(self) -> float | None:
|
|
1343
|
+
return getattr(self, '_target_chamber_temp', None)
|
|
1344
|
+
|
|
1191
1345
|
def process_mqtt_update(
|
|
1192
1346
|
self,
|
|
1193
1347
|
topic: str,
|
|
@@ -1242,6 +1396,12 @@ class AnycubicPrinter:
|
|
|
1242
1396
|
elif msg_type == 'light':
|
|
1243
1397
|
self._process_mqtt_update_light(action, state, payload)
|
|
1244
1398
|
|
|
1399
|
+
elif msg_type == 'info':
|
|
1400
|
+
self._process_mqtt_update_info(action, state, payload)
|
|
1401
|
+
|
|
1402
|
+
elif msg_type == 'aiSettings':
|
|
1403
|
+
self._process_mqtt_update_ai_settings(action, state, payload)
|
|
1404
|
+
|
|
1245
1405
|
else:
|
|
1246
1406
|
raise AnycubicMQTTUnknownUpdate(ErrorsMQTTUpdate.unknown.format(msg_type))
|
|
1247
1407
|
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""Parsing the reports only the local connection sends.
|
|
2
|
+
|
|
3
|
+
The payloads here are captured verbatim from a Kobra S1 on firmware 2.7.2.7 in
|
|
4
|
+
LAN Mode. The payload contract fails loudly on any key left unread, so these
|
|
5
|
+
double as a check that nothing in a real report goes unhandled.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from unittest.mock import MagicMock
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
from anycubic_cloud_api.data_models.consumable import AnycubicConsumableData
|
|
13
|
+
from anycubic_cloud_api.data_models.printer import AnycubicPrinter
|
|
14
|
+
|
|
15
|
+
INFO_REPORT = {
|
|
16
|
+
"type": "info",
|
|
17
|
+
"action": "report",
|
|
18
|
+
"timestamp": 244516,
|
|
19
|
+
"msgid": "b85ca099-9792-4e2d-83df-cdd6f1a7296d",
|
|
20
|
+
"state": "done",
|
|
21
|
+
"code": 200,
|
|
22
|
+
"msg": "done",
|
|
23
|
+
"data": {
|
|
24
|
+
"printerName": "Anycubic Kobra S1",
|
|
25
|
+
"urls": {
|
|
26
|
+
"fileUploadurl": "http://10.0.66.28:18910/gcode_upload?s=SIGNED",
|
|
27
|
+
"rtspUrl": "http://10.0.66.28:18088/flv",
|
|
28
|
+
},
|
|
29
|
+
"project": None,
|
|
30
|
+
"last_project": None,
|
|
31
|
+
"model": "Anycubic Kobra S1",
|
|
32
|
+
"ip": "10.0.66.28",
|
|
33
|
+
"version": "2.7.2.7",
|
|
34
|
+
"state": "free",
|
|
35
|
+
"temp": {
|
|
36
|
+
"curr_hotbed_temp": 31,
|
|
37
|
+
"curr_nozzle_temp": 34,
|
|
38
|
+
"target_hotbed_temp": 0,
|
|
39
|
+
"target_nozzle_temp": 0,
|
|
40
|
+
},
|
|
41
|
+
"print_speed_mode": 2,
|
|
42
|
+
"fan_speed_pct": 0,
|
|
43
|
+
"aux_fan_speed_pct": 0,
|
|
44
|
+
"box_fan_level": 0,
|
|
45
|
+
"features": {
|
|
46
|
+
"auto_leveling_support": True,
|
|
47
|
+
"vibration_compensation_support": True,
|
|
48
|
+
"flow_calibration_support": True,
|
|
49
|
+
"drying_first_support": True,
|
|
50
|
+
"camera_timelapse_support": True,
|
|
51
|
+
"gcode_3mf_support": True,
|
|
52
|
+
"delete_batch_support": True,
|
|
53
|
+
"preheating_support": True,
|
|
54
|
+
"fod_support": True,
|
|
55
|
+
"shengwang_rtc_support": True,
|
|
56
|
+
"pre_cancel_support": True,
|
|
57
|
+
"shengwang_rdt_support": True,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
AI_SETTINGS_REPORT = {
|
|
63
|
+
"type": "aiSettings",
|
|
64
|
+
"action": "query",
|
|
65
|
+
"timestamp": 248130,
|
|
66
|
+
"msgid": "21af7a88-0205-421f-8f21-3a355bd7e9ff",
|
|
67
|
+
"state": "done",
|
|
68
|
+
"code": 200,
|
|
69
|
+
"msg": "done",
|
|
70
|
+
"data": {
|
|
71
|
+
"ai_settings": {
|
|
72
|
+
"status": 0,
|
|
73
|
+
"type": 2,
|
|
74
|
+
"count": 60,
|
|
75
|
+
"notice_type": [0, 1],
|
|
76
|
+
"sensitivity_level": [1, 1],
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
TOPIC = "anycubic/anycubicCloud/v1/printer/public/20025/DEVICE/info"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def make_printer():
|
|
85
|
+
return AnycubicPrinter(
|
|
86
|
+
api_parent=MagicMock(),
|
|
87
|
+
machine_type=20025,
|
|
88
|
+
machine_name="Anycubic Kobra S1",
|
|
89
|
+
id=1,
|
|
90
|
+
ignore_init_errors=True,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def apply(printer, report):
|
|
95
|
+
printer.process_mqtt_update(TOPIC, AnycubicConsumableData(report))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class TestInfoReport:
|
|
99
|
+
def test_the_real_report_is_fully_consumed(self):
|
|
100
|
+
"""Anything unread raises -- so this passing means nothing was missed."""
|
|
101
|
+
apply(make_printer(), INFO_REPORT)
|
|
102
|
+
|
|
103
|
+
def test_the_camera_url_is_picked_up(self):
|
|
104
|
+
printer = make_printer()
|
|
105
|
+
|
|
106
|
+
apply(printer, INFO_REPORT)
|
|
107
|
+
|
|
108
|
+
assert printer.camera_stream_url == "http://10.0.66.28:18088/flv"
|
|
109
|
+
|
|
110
|
+
def test_the_upload_endpoint_is_picked_up(self):
|
|
111
|
+
printer = make_printer()
|
|
112
|
+
|
|
113
|
+
apply(printer, INFO_REPORT)
|
|
114
|
+
|
|
115
|
+
assert printer.file_upload_url.endswith("/gcode_upload?s=SIGNED")
|
|
116
|
+
|
|
117
|
+
def test_the_capability_map_is_kept(self):
|
|
118
|
+
"""The most reliable way to tell models apart without owning one."""
|
|
119
|
+
printer = make_printer()
|
|
120
|
+
|
|
121
|
+
apply(printer, INFO_REPORT)
|
|
122
|
+
|
|
123
|
+
assert printer.features["fod_support"] is True
|
|
124
|
+
assert printer.features["camera_timelapse_support"] is True
|
|
125
|
+
assert len(printer.features) == 12
|
|
126
|
+
|
|
127
|
+
def test_the_firmware_version_is_kept(self):
|
|
128
|
+
printer = make_printer()
|
|
129
|
+
|
|
130
|
+
apply(printer, INFO_REPORT)
|
|
131
|
+
|
|
132
|
+
assert printer.local_firmware_version == "2.7.2.7"
|
|
133
|
+
|
|
134
|
+
def test_fan_and_speed_are_applied(self):
|
|
135
|
+
printer = make_printer()
|
|
136
|
+
|
|
137
|
+
apply(printer, INFO_REPORT)
|
|
138
|
+
|
|
139
|
+
assert printer._fan_speed == 0
|
|
140
|
+
assert printer._print_speed_mode == 2
|
|
141
|
+
|
|
142
|
+
def test_a_chamberless_printer_reports_nothing_for_it(self):
|
|
143
|
+
"""A Kobra S1 has no chamber sensor and simply omits the fields."""
|
|
144
|
+
printer = make_printer()
|
|
145
|
+
|
|
146
|
+
apply(printer, INFO_REPORT)
|
|
147
|
+
|
|
148
|
+
assert printer.chamber_temperature is None
|
|
149
|
+
|
|
150
|
+
def test_a_chamber_reading_is_kept_when_present(self):
|
|
151
|
+
printer = make_printer()
|
|
152
|
+
report = {**INFO_REPORT, "data": {**INFO_REPORT["data"]}}
|
|
153
|
+
report["data"]["temp"] = {**report["data"]["temp"], "curr_chamber_temp": 41}
|
|
154
|
+
|
|
155
|
+
apply(printer, report)
|
|
156
|
+
|
|
157
|
+
assert printer.chamber_temperature == 41
|
|
158
|
+
|
|
159
|
+
@pytest.mark.parametrize(
|
|
160
|
+
"missing", ["urls", "features", "temp", "version", "print_speed_mode"]
|
|
161
|
+
)
|
|
162
|
+
def test_a_report_missing_a_block_still_parses(self, missing):
|
|
163
|
+
"""Other models will not send all of these."""
|
|
164
|
+
report = {**INFO_REPORT, "data": {**INFO_REPORT["data"]}}
|
|
165
|
+
del report["data"][missing]
|
|
166
|
+
|
|
167
|
+
apply(make_printer(), report)
|
|
168
|
+
|
|
169
|
+
def test_an_empty_report_is_harmless(self):
|
|
170
|
+
apply(make_printer(), {**INFO_REPORT, "data": {}})
|
|
171
|
+
|
|
172
|
+
def test_a_later_report_without_features_keeps_the_known_ones(self):
|
|
173
|
+
printer = make_printer()
|
|
174
|
+
apply(printer, INFO_REPORT)
|
|
175
|
+
|
|
176
|
+
stripped = {**INFO_REPORT, "data": {**INFO_REPORT["data"]}}
|
|
177
|
+
del stripped["data"]["features"]
|
|
178
|
+
apply(printer, stripped)
|
|
179
|
+
|
|
180
|
+
assert len(printer.features) == 12
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class TestAiSettings:
|
|
184
|
+
def test_the_real_report_is_fully_consumed(self):
|
|
185
|
+
apply(make_printer(), AI_SETTINGS_REPORT)
|
|
186
|
+
|
|
187
|
+
def test_the_settings_are_kept(self):
|
|
188
|
+
printer = make_printer()
|
|
189
|
+
|
|
190
|
+
apply(printer, AI_SETTINGS_REPORT)
|
|
191
|
+
|
|
192
|
+
assert printer.ai_settings["count"] == 60
|
|
193
|
+
assert printer.ai_settings["sensitivity_level"] == [1, 1]
|
|
194
|
+
|
|
195
|
+
def test_detection_off_reads_false_not_none(self):
|
|
196
|
+
printer = make_printer()
|
|
197
|
+
|
|
198
|
+
apply(printer, AI_SETTINGS_REPORT)
|
|
199
|
+
|
|
200
|
+
assert printer.ai_detection_enabled is False
|
|
201
|
+
|
|
202
|
+
def test_detection_on_reads_true(self):
|
|
203
|
+
printer = make_printer()
|
|
204
|
+
report = {"data": {"ai_settings": {"status": 1}}, **{
|
|
205
|
+
k: v for k, v in AI_SETTINGS_REPORT.items() if k != "data"
|
|
206
|
+
}}
|
|
207
|
+
|
|
208
|
+
apply(printer, report)
|
|
209
|
+
|
|
210
|
+
assert printer.ai_detection_enabled is True
|
|
211
|
+
|
|
212
|
+
def test_an_unseen_printer_reports_nothing(self):
|
|
213
|
+
assert make_printer().ai_detection_enabled is None
|
|
214
|
+
assert make_printer().ai_settings == {}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/anycubic_api.py
RENAMED
|
File without changes
|
|
File without changes
|
{anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/api/functions.py
RENAMED
|
File without changes
|
|
File without changes
|
{anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/const/api_endpoints.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/data_models/files.py
RENAMED
|
File without changes
|
|
File without changes
|
{anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/data_models/orders.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/data_models/project.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/helpers/helpers.py
RENAMED
|
File without changes
|
{anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/lan/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/lan/handshake.py
RENAMED
|
File without changes
|
|
File without changes
|
{anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api/models/cloud_upload.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api.egg-info/requires.txt
RENAMED
|
File without changes
|
{anycubic_cloud_api-0.2.0 → anycubic_cloud_api-0.3.0}/src/anycubic_cloud_api.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|