anycubic-cloud-api 0.3.2__tar.gz → 0.4.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.
Files changed (47) hide show
  1. {anycubic_cloud_api-0.3.2/src/anycubic_cloud_api.egg-info → anycubic_cloud_api-0.4.0}/PKG-INFO +1 -1
  2. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/pyproject.toml +1 -1
  3. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api/data_models/printer.py +50 -14
  4. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0/src/anycubic_cloud_api.egg-info}/PKG-INFO +1 -1
  5. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/tests/test_lan_reports.py +119 -0
  6. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/LICENSE +0 -0
  7. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/README.md +0 -0
  8. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/setup.cfg +0 -0
  9. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api/__init__.py +0 -0
  10. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api/anycubic_api.py +0 -0
  11. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api/api/base.py +0 -0
  12. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api/api/functions.py +0 -0
  13. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api/api/mqtt.py +0 -0
  14. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api/const/api_endpoints.py +0 -0
  15. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api/const/const.py +0 -0
  16. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api/const/enums.py +0 -0
  17. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api/const/lan.py +0 -0
  18. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api/const/mqtt.py +0 -0
  19. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api/data_models/consumable.py +0 -0
  20. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api/data_models/files.py +0 -0
  21. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api/data_models/gcode_file.py +0 -0
  22. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api/data_models/orders.py +0 -0
  23. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api/data_models/print_response.py +0 -0
  24. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api/data_models/print_speed_mode.py +0 -0
  25. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api/data_models/printer_properties.py +0 -0
  26. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api/data_models/printing_settings.py +0 -0
  27. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api/data_models/project.py +0 -0
  28. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api/exceptions/error_strings.py +0 -0
  29. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api/exceptions/exceptions.py +0 -0
  30. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api/helpers/helpers.py +0 -0
  31. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api/lan/__init__.py +0 -0
  32. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api/lan/client.py +0 -0
  33. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api/lan/handshake.py +0 -0
  34. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api/models/auth.py +0 -0
  35. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api/models/cloud_upload.py +0 -0
  36. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api/models/http.py +0 -0
  37. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api/resources/anycubic_mqqt_tls_ca.crt +0 -0
  38. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api/resources/anycubic_mqqt_tls_client.crt +0 -0
  39. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api/resources/anycubic_mqqt_tls_client.key +0 -0
  40. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api.egg-info/SOURCES.txt +0 -0
  41. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api.egg-info/dependency_links.txt +0 -0
  42. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api.egg-info/requires.txt +0 -0
  43. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/src/anycubic_cloud_api.egg-info/top_level.txt +0 -0
  44. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/tests/test_axis_position.py +0 -0
  45. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/tests/test_lan_client.py +0 -0
  46. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/tests/test_lan_handshake.py +0 -0
  47. {anycubic_cloud_api-0.3.2 → anycubic_cloud_api-0.4.0}/tests/test_partial_reports.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: anycubic-cloud-api
3
- Version: 0.3.2
3
+ Version: 0.4.0
4
4
  Summary: Async client for the Anycubic Cloud API and its MQTT stream
5
5
  Author: Nino Bondonno
6
6
  Maintainer: Nino Bondonno
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "anycubic-cloud-api"
7
- version = "0.3.2"
7
+ version = "0.4.0"
8
8
  description = "Async client for the Anycubic Cloud API and its MQTT stream"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -274,6 +274,22 @@ class AnycubicPrinter:
274
274
  else:
275
275
  self._type_function_ids = list()
276
276
 
277
+ def set_material_type_from_device_type(self, device_type: str | None) -> None:
278
+ """Infer what the printer prints from what kind of machine it says it is.
279
+
280
+ The cloud states this outright. A printer reached over its own network
281
+ does not, but its discovery document names a device type -- and `fdm`
282
+ means filament as surely as the cloud saying so. Without it every
283
+ filament entity is filtered out as belonging to a different machine.
284
+ """
285
+ if self._material_type is not None or not device_type:
286
+ return
287
+
288
+ if str(device_type).lower() == "fdm":
289
+ self._set_material_type("filament")
290
+ elif str(device_type).lower() in ("lcd", "dlp", "resin"):
291
+ self._set_material_type("resin")
292
+
277
293
  def _set_material_type(self, material_type: str | None) -> None:
278
294
  self._material_type: AnycubicPrinterMaterialType | str | None = None
279
295
 
@@ -375,6 +391,26 @@ class AnycubicPrinter:
375
391
  if not self._ignore_init_errors:
376
392
  raise e
377
393
 
394
+ def _apply_current_temps(self, hotbed: Any, nozzle: Any) -> None:
395
+ """Record the live temperatures, creating the holder if need be.
396
+
397
+ The cloud builds this object when the printer is first fetched. With
398
+ only a local connection there is no such fetch, so it has to be made
399
+ from the first report that carries temperatures -- otherwise every
400
+ temperature entity reads unavailable on a working printer.
401
+ """
402
+ if hotbed is None or nozzle is None:
403
+ return
404
+
405
+ if self._parameter is None:
406
+ self._set_parameter({
407
+ 'curr_hotbed_temp': hotbed,
408
+ 'curr_nozzle_temp': nozzle,
409
+ })
410
+ return
411
+
412
+ self._parameter.update_current_temps(hotbed, nozzle)
413
+
378
414
  def _set_parameter(
379
415
  self,
380
416
  parameter: dict[str, Any] | None,
@@ -893,8 +929,7 @@ class AnycubicPrinter:
893
929
  self._chamber_temp = data.get('curr_chamber_temp')
894
930
  self._target_chamber_temp = data.get('target_chamber_temp')
895
931
 
896
- if self.parameter:
897
- self.parameter.update_current_temps(curr_hotbed, curr_nozzle)
932
+ self._apply_current_temps(curr_hotbed, curr_nozzle)
898
933
 
899
934
  if self._latest_project:
900
935
  self._latest_project.update_target_temps(target_hotbed, target_nozzle)
@@ -1021,12 +1056,8 @@ class AnycubicPrinter:
1021
1056
  # each is applied only if it is actually there.
1022
1057
  settings = data.get('settings') or {}
1023
1058
 
1024
- if (
1025
- self.parameter
1026
- and 'curr_hotbed_temp' in data
1027
- and 'curr_nozzle_temp' in data
1028
- ):
1029
- self.parameter.update_current_temps(
1059
+ if 'curr_hotbed_temp' in data and 'curr_nozzle_temp' in data:
1060
+ self._apply_current_temps(
1030
1061
  data['curr_hotbed_temp'],
1031
1062
  data['curr_nozzle_temp'],
1032
1063
  )
@@ -1257,11 +1288,10 @@ class AnycubicPrinter:
1257
1288
  temp = data.get('temp')
1258
1289
 
1259
1290
  if temp:
1260
- if self.parameter:
1261
- self.parameter.update_current_temps(
1262
- temp.get('curr_hotbed_temp'),
1263
- temp.get('curr_nozzle_temp'),
1264
- )
1291
+ self._apply_current_temps(
1292
+ temp.get('curr_hotbed_temp'),
1293
+ temp.get('curr_nozzle_temp'),
1294
+ )
1265
1295
  # A Kobra S1 reports chamber temperatures of zero because it has no
1266
1296
  # chamber sensor, so they are kept raw rather than turned into an
1267
1297
  # always-zero entity.
@@ -1693,7 +1723,13 @@ class AnycubicPrinter:
1693
1723
 
1694
1724
  @property
1695
1725
  def supports_function_multi_color_box(self) -> bool:
1696
- return AnycubicFunctionID.MULTI_COLOR_BOX in self._type_function_ids
1726
+ if AnycubicFunctionID.MULTI_COLOR_BOX in self._type_function_ids:
1727
+ return True
1728
+
1729
+ # The function list comes from the cloud, and a printer reached only
1730
+ # over its own network has none. One that is reporting a box plainly
1731
+ # supports one, whatever any list says.
1732
+ return self.connected_ace_units > 0
1697
1733
 
1698
1734
  @property
1699
1735
  def supports_function_ai_detection(self) -> bool:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: anycubic-cloud-api
3
- Version: 0.3.2
3
+ Version: 0.4.0
4
4
  Summary: Async client for the Anycubic Cloud API and its MQTT stream
5
5
  Author: Nino Bondonno
6
6
  Maintainer: Nino Bondonno
@@ -296,3 +296,122 @@ class TestAvailabilityFromLocalReports:
296
296
  apply(printer, report)
297
297
 
298
298
  assert printer.printer_online is True
299
+
300
+
301
+ class TestTemperaturesWithoutTheCloud:
302
+ """The cloud builds the temperature holder; a local-only printer has none."""
303
+
304
+ def test_an_info_report_creates_it(self):
305
+ printer = make_printer()
306
+
307
+ apply(printer, INFO_REPORT)
308
+
309
+ assert printer.parameter is not None
310
+ assert printer.curr_nozzle_temp == 34
311
+ assert printer.curr_hotbed_temp == 31
312
+
313
+ def test_a_temperature_report_creates_it(self):
314
+ printer = make_printer()
315
+
316
+ apply(printer, TestQueryActionReplies.TEMPERATURE)
317
+
318
+ assert printer.curr_nozzle_temp == 34
319
+
320
+ def test_later_reports_update_it(self):
321
+ printer = make_printer()
322
+ apply(printer, INFO_REPORT)
323
+
324
+ hotter = {**INFO_REPORT, "data": {**INFO_REPORT["data"]}}
325
+ hotter["data"]["temp"] = {**hotter["data"]["temp"], "curr_nozzle_temp": 210}
326
+ apply(printer, hotter)
327
+
328
+ assert printer.curr_nozzle_temp == 210
329
+
330
+ def test_a_report_without_temperatures_leaves_it_alone(self):
331
+ printer = make_printer()
332
+ report = {**INFO_REPORT, "data": {**INFO_REPORT["data"]}}
333
+ del report["data"]["temp"]
334
+
335
+ apply(printer, report)
336
+
337
+ assert printer.parameter is None
338
+
339
+
340
+ class TestWhatTheCloudWouldHaveSaid:
341
+ """Two facts only the cloud states, which entity filtering depends on.
342
+
343
+ Without them every filament entity is filtered out as belonging to a
344
+ different kind of machine, and every ACE entity as unsupported -- so a
345
+ printer reached locally ends up with almost no entities at all.
346
+ """
347
+
348
+ def test_an_fdm_device_type_means_filament(self):
349
+ printer = make_printer()
350
+
351
+ printer.set_material_type_from_device_type("fdm")
352
+
353
+ assert str(printer.material_type) == "Filament"
354
+
355
+ @pytest.mark.parametrize("device_type", ["lcd", "dlp", "resin", "LCD"])
356
+ def test_a_resin_device_type_means_resin(self, device_type):
357
+ printer = make_printer()
358
+
359
+ printer.set_material_type_from_device_type(device_type)
360
+
361
+ assert str(printer.material_type) == "Resin"
362
+
363
+ @pytest.mark.parametrize("device_type", [None, "", "something-new"])
364
+ def test_an_unknown_device_type_leaves_it_unset(self, device_type):
365
+ """Better unset than wrong -- a wrong guess hides the right entities."""
366
+ printer = make_printer()
367
+
368
+ printer.set_material_type_from_device_type(device_type)
369
+
370
+ assert printer.material_type is None
371
+
372
+ def test_the_cloud_wins_when_it_has_already_spoken(self):
373
+ printer = AnycubicPrinter(
374
+ api_parent=MagicMock(),
375
+ machine_type=20025,
376
+ machine_name="x",
377
+ id=1,
378
+ material_type="resin",
379
+ ignore_init_errors=True,
380
+ )
381
+
382
+ printer.set_material_type_from_device_type("fdm")
383
+
384
+ assert str(printer.material_type) == "Resin"
385
+
386
+ def test_a_reported_box_means_the_printer_supports_one(self):
387
+ """The cloud's function list is absent on a local connection."""
388
+ printer = AnycubicPrinter(
389
+ api_parent=MagicMock(),
390
+ machine_type=20025,
391
+ machine_name="x",
392
+ id=1,
393
+ multi_color_box=[{"id": 0, "box_id": 0, "slots": []}],
394
+ ignore_init_errors=True,
395
+ )
396
+
397
+ assert printer.connected_ace_units == 1
398
+ assert printer.supports_function_multi_color_box is True
399
+
400
+ def test_no_box_means_no_support(self):
401
+ printer = make_printer()
402
+
403
+ assert printer.connected_ace_units == 0
404
+ assert printer.supports_function_multi_color_box is False
405
+
406
+ def test_the_cloud_function_list_still_counts_with_no_box_attached(self):
407
+ """A supported-but-detached ACE must not read as unsupported."""
408
+ printer = AnycubicPrinter(
409
+ api_parent=MagicMock(),
410
+ machine_type=20025,
411
+ machine_name="x",
412
+ id=1,
413
+ type_function_ids=[2006],
414
+ ignore_init_errors=True,
415
+ )
416
+
417
+ assert printer.supports_function_multi_color_box is True