cremalink 0.1.0b5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. cremalink/__init__.py +33 -0
  2. cremalink/clients/__init__.py +10 -0
  3. cremalink/clients/cloud.py +130 -0
  4. cremalink/core/__init__.py +6 -0
  5. cremalink/core/binary.py +102 -0
  6. cremalink/crypto/__init__.py +142 -0
  7. cremalink/devices/AY008ESP1.json +114 -0
  8. cremalink/devices/__init__.py +116 -0
  9. cremalink/domain/__init__.py +11 -0
  10. cremalink/domain/device.py +245 -0
  11. cremalink/domain/factory.py +98 -0
  12. cremalink/local_server.py +76 -0
  13. cremalink/local_server_app/__init__.py +20 -0
  14. cremalink/local_server_app/api.py +272 -0
  15. cremalink/local_server_app/config.py +64 -0
  16. cremalink/local_server_app/device_adapter.py +96 -0
  17. cremalink/local_server_app/jobs.py +104 -0
  18. cremalink/local_server_app/logging.py +116 -0
  19. cremalink/local_server_app/models.py +76 -0
  20. cremalink/local_server_app/protocol.py +135 -0
  21. cremalink/local_server_app/state.py +358 -0
  22. cremalink/parsing/__init__.py +7 -0
  23. cremalink/parsing/monitor/__init__.py +22 -0
  24. cremalink/parsing/monitor/decode.py +79 -0
  25. cremalink/parsing/monitor/extractors.py +69 -0
  26. cremalink/parsing/monitor/frame.py +132 -0
  27. cremalink/parsing/monitor/model.py +42 -0
  28. cremalink/parsing/monitor/profile.py +144 -0
  29. cremalink/parsing/monitor/view.py +196 -0
  30. cremalink/parsing/properties/__init__.py +9 -0
  31. cremalink/parsing/properties/decode.py +53 -0
  32. cremalink/resources/__init__.py +10 -0
  33. cremalink/resources/api_config.json +14 -0
  34. cremalink/resources/api_config.py +30 -0
  35. cremalink/resources/lang.json +223 -0
  36. cremalink/transports/__init__.py +7 -0
  37. cremalink/transports/base.py +94 -0
  38. cremalink/transports/cloud/__init__.py +9 -0
  39. cremalink/transports/cloud/transport.py +166 -0
  40. cremalink/transports/local/__init__.py +9 -0
  41. cremalink/transports/local/transport.py +164 -0
  42. cremalink-0.1.0b5.dist-info/METADATA +138 -0
  43. cremalink-0.1.0b5.dist-info/RECORD +47 -0
  44. cremalink-0.1.0b5.dist-info/WHEEL +5 -0
  45. cremalink-0.1.0b5.dist-info/entry_points.txt +2 -0
  46. cremalink-0.1.0b5.dist-info/licenses/LICENSE +661 -0
  47. cremalink-0.1.0b5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,164 @@
1
+ """
2
+ This module provides the `LocalTransport` class, which handles communication
3
+ with a coffee machine over the local network (LAN) via a proxy server.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import json
8
+ from typing import Any, Optional
9
+ from datetime import datetime
10
+
11
+ import requests
12
+
13
+ from cremalink.parsing.monitor.decode import build_monitor_snapshot
14
+ from cremalink.parsing.properties.decode import PropertiesSnapshot
15
+ from cremalink.transports.base import DeviceTransport
16
+
17
+
18
+ class LocalTransport(DeviceTransport):
19
+ """
20
+ A transport for communicating with a device on the local network.
21
+
22
+ This transport does not connect to the device directly. Instead, it sends
23
+ requests to a local proxy server (`cremalink.local_server`), which then
24
+ forwards them to the coffee machine. This architecture simplifies direct
25
+ device communication and authentication.
26
+ """
27
+
28
+ def __init__(
29
+ self,
30
+ dsn: str,
31
+ lan_key: str,
32
+ device_ip: Optional[str],
33
+ server_host: str = "127.0.0.1",
34
+ server_port: int = 10280,
35
+ device_scheme: str = "http",
36
+ auto_configure: bool = False,
37
+ command_map: Optional[dict[str, Any]] = None,
38
+ property_map: Optional[dict[str, Any]] = None,
39
+ ) -> None:
40
+ """
41
+ Initializes the LocalTransport.
42
+
43
+ Args:
44
+ dsn: The Device Serial Number.
45
+ lan_key: The key for LAN authentication.
46
+ device_ip: The IP address of the coffee machine.
47
+ server_host: The hostname or IP of the local proxy server.
48
+ server_port: The port of the local proxy server.
49
+ device_scheme: The protocol to use for device communication (e.g., 'http').
50
+ auto_configure: If True, automatically configures the server on init.
51
+ command_map: A pre-loaded map of device commands.
52
+ property_map: A pre-loaded map of device properties.
53
+ """
54
+ self.dsn = dsn
55
+ self.lan_key = lan_key
56
+ self.device_ip = device_ip
57
+ self.device_scheme = device_scheme
58
+ self.server_base_url = f"http://{server_host}:{server_port}"
59
+ self._configured = False
60
+ self.command_map = command_map or {}
61
+ self.property_map = property_map or {}
62
+ self._auto_configure = auto_configure
63
+ if auto_configure:
64
+ self.configure()
65
+
66
+ # ---- helpers ----
67
+ def _post_server(self, path: str, body: dict, timeout: int = 10) -> requests.Response:
68
+ """Helper for making POST requests to the local proxy server."""
69
+ return requests.post(
70
+ url=f"{self.server_base_url}{path}",
71
+ headers={"Content-Type": "application/json"},
72
+ json=body,
73
+ timeout=timeout,
74
+ )
75
+
76
+ def _get_server(self, path: str, timeout: int = 10) -> requests.Response:
77
+ """Helper for making GET requests to the local proxy server."""
78
+ return requests.get(f"{self.server_base_url}{path}", timeout=timeout)
79
+
80
+ # ---- DeviceTransport Implementation ----
81
+ def configure(self) -> None:
82
+ """
83
+ Configures the local proxy server with the device's connection details.
84
+ This must be called before other methods can be used.
85
+ """
86
+ monitor_prop_name = self.property_map.get("monitor", "d302_monitor")
87
+ payload = {
88
+ "dsn": self.dsn,
89
+ "device_ip": self.device_ip,
90
+ "lan_key": self.lan_key,
91
+ "device_scheme": self.device_scheme,
92
+ "monitor_property_name": monitor_prop_name,
93
+ }
94
+ try:
95
+ resp = self._post_server("/configure", payload)
96
+ except requests.RequestException as exc:
97
+ raise ConnectionError(
98
+ f"Could not reach local server at {self.server_base_url} during configure. "
99
+ f"Start the server (python -m cremalink.local_server) or adjust server_host/server_port. "
100
+ f"Original error: {exc}"
101
+ ) from exc
102
+ if resp.status_code not in (200, 201):
103
+ raise ValueError(f"Failed to configure server: {resp.status_code} {resp.text}")
104
+ self._configured = True
105
+
106
+ def send_command(self, command: str) -> dict[str, Any]:
107
+ """Sends a command to the device via the local proxy server."""
108
+ if not self._configured:
109
+ self.configure()
110
+ resp = self._post_server("/command", {"command": command})
111
+ resp.raise_for_status()
112
+ return resp.json()
113
+
114
+ def get_properties(self) -> PropertiesSnapshot:
115
+ """Retrieves all device properties from the local proxy server."""
116
+ resp = self._get_server("/get_properties")
117
+ resp.raise_for_status()
118
+ payload = resp.json()
119
+ received = payload.get("received_at")
120
+ received_dt = datetime.fromtimestamp(received) if received else None
121
+ return PropertiesSnapshot(raw=payload.get("properties", payload), received_at=received_dt)
122
+
123
+ def get_property(self, name: str) -> Any:
124
+ """Retrieves a single property value from the local proxy server."""
125
+ # First, try to get it from the bulk properties snapshot
126
+ snapshot = self.get_properties()
127
+ value = snapshot.get(name)
128
+ if value is None:
129
+ # If not found, request it individually
130
+ resp = self._get_server(f"/properties/{name}")
131
+ resp.raise_for_status()
132
+ return resp.json().get("value")
133
+ return value
134
+
135
+ def get_monitor(self) -> Any:
136
+ """Retrieves and parses the device's monitoring data."""
137
+ resp = self._get_server("/get_monitor")
138
+ resp.raise_for_status()
139
+ try:
140
+ payload = resp.json()
141
+ except json.JSONDecodeError as exc:
142
+ raise ValueError(f"Failed to parse monitor payload: {resp.text}") from exc
143
+ return build_monitor_snapshot(payload, source="local", device_id=self.dsn)
144
+
145
+ def refresh_monitor(self) -> None:
146
+ """Requests a refresh of the monitoring data via the local proxy server."""
147
+ resp = self._get_server("/refresh_monitor")
148
+ resp.raise_for_status()
149
+
150
+ def health(self) -> str:
151
+ """Checks the health of the local proxy server."""
152
+ return self._get_server("/health").text
153
+
154
+ def set_mappings(self, command_map: dict[str, Any], property_map: dict[str, Any]) -> None:
155
+ """
156
+ Sets the command and property maps and re-configures the server if needed.
157
+ If the name of the monitoring property changes, the server is reconfigured.
158
+ """
159
+ previous_monitor = self.property_map.get("monitor", "d302_monitor")
160
+ self.command_map = command_map
161
+ self.property_map = property_map
162
+ updated_monitor = self.property_map.get("monitor", "d302_monitor")
163
+ if self._auto_configure and (not self._configured or previous_monitor != updated_monitor):
164
+ self.configure()
@@ -0,0 +1,138 @@
1
+ Metadata-Version: 2.4
2
+ Name: cremalink
3
+ Version: 0.1.0b5
4
+ Summary: Python library and local API server for monitoring and controlling IoT coffee machines.
5
+ Author-email: Midian Tekle Elfu <developer@midian.tekleelfu.de>
6
+ Maintainer-email: Midian Tekle Elfu <developer@midian.tekleelfu.de>
7
+ License-Expression: AGPL-3.0-or-later
8
+ Project-URL: homepage, https://github.com/miditkl/cremalink
9
+ Project-URL: source, https://github.com/miditkl/cremalink.git
10
+ Project-URL: issues, https://github.com/miditkl/cremalink/issues
11
+ Project-URL: download, https://github.com/miditkl/cremalink/archive/refs/heads/main.zip
12
+ Project-URL: funding, https://github.com/sponsors/miditkl
13
+ Keywords: Local Control,Cloud Control,Coffee Machine,IoT
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: End Users/Desktop
17
+ Classifier: Intended Audience :: Information Technology
18
+ Classifier: Topic :: Software Development
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Classifier: Topic :: System :: Monitoring
21
+ Classifier: Topic :: System :: Networking :: Monitoring
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
+ Classifier: Programming Language :: Python :: 3.15
25
+ Classifier: Operating System :: OS Independent
26
+ Requires-Python: >=3.13
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Requires-Dist: requests==2.32.5
30
+ Requires-Dist: pycryptodome==3.23.0
31
+ Requires-Dist: fastapi==0.128.0
32
+ Requires-Dist: uvicorn==0.40.0
33
+ Requires-Dist: httpx==0.28.1
34
+ Requires-Dist: pydantic==2.12.5
35
+ Requires-Dist: pydantic-settings==2.12.0
36
+ Provides-Extra: dev
37
+ Requires-Dist: cremalink[test]; extra == "dev"
38
+ Requires-Dist: cremalink[docs]; extra == "dev"
39
+ Requires-Dist: ipykernel==7.1.0; extra == "dev"
40
+ Requires-Dist: notebook==7.5.1; extra == "dev"
41
+ Provides-Extra: test
42
+ Requires-Dist: pytest==9.0.2; extra == "test"
43
+ Requires-Dist: pytest-asyncio==1.3.0; extra == "test"
44
+ Provides-Extra: docs
45
+ Requires-Dist: sphinx==8.2.3; extra == "docs"
46
+ Requires-Dist: sphinxcontrib-websupport==2.0.0; extra == "docs"
47
+ Requires-Dist: sphinx-rtd-theme==3.0.2; extra == "docs"
48
+ Dynamic: license-file
49
+
50
+ # ☕ Cremalink
51
+
52
+ **A high-performance Python library and local API server for monitoring and controlling IoT coffee machines.**
53
+
54
+ [![PyPI version](https://img.shields.io/pypi/v/cremalink.svg?style=for-the-badge&color=blue)](https://pypi.org/project/cremalink/)
55
+ [![Python Version](https://img.shields.io/pypi/pyversions/cremalink.svg?style=for-the-badge&color=FFE169&labelColor=3776AB)](https://pypi.org/project/cremalink/)
56
+ [![License](https://img.shields.io/github/license/miditkl/cremalink?style=for-the-badge&color=success)](LICENSE)
57
+ [![Downloads](https://img.shields.io/pypi/dm/cremalink.svg?style=for-the-badge&color=orange)](https://pypi.org/project/cremalink/)
58
+ [![Source Code](https://img.shields.io/badge/Source-GitHub-black?style=for-the-badge&logo=github)](https://github.com/miditkl/cremalink)
59
+
60
+ ---
61
+
62
+ ## ✨ Overview
63
+
64
+ Cremalink provides a unified interface to interact with smart coffee machines via **Local LAN control** or **Cloud API**. It allows for real-time state monitoring and precise command execution.
65
+
66
+ > [!TIP]
67
+ > For detailed guides, advanced configuration, and developer deep-dives, please visit our **[Project Wiki](https://github.com/miditkl/cremalink/wiki)**.
68
+
69
+ ---
70
+
71
+ ## 🚀 Installation
72
+
73
+ Install the package via `pip` (Cremalink requires **Python 3.13+**):
74
+
75
+ ```bash
76
+ pip install cremalink
77
+
78
+ ```
79
+
80
+ ### Optional Dependencies
81
+
82
+ To include tools for development or testing:
83
+
84
+ ```bash
85
+ pip install "cremalink[dev]" # For notebooks and kernel support
86
+ pip install "cremalink[test]" # For running pytest suites
87
+
88
+ ```
89
+
90
+ ---
91
+
92
+ ## 🛠 Usage
93
+
94
+ ### Integrated API Server
95
+
96
+ Cremalink includes a FastAPI-based server for headless environments:
97
+
98
+ ```bash
99
+ # Start the server (default port 10800)
100
+ cremalink-server --ip 0.0.0.0 --port 10800
101
+ ```
102
+ > More information: [Local Server Setup](https://github.com/miditkl/cremalink/wiki/3.-Local-Server-Setup)
103
+
104
+ ### Python API (Local Control)
105
+
106
+ Connect to your machine directly via your local network for the lowest latency
107
+
108
+ > More information: [Local Device]()
109
+
110
+ ---
111
+
112
+ ## 🛠 Development
113
+
114
+ ### Testing
115
+
116
+ Run the comprehensive test suite using `pytest`:
117
+
118
+ ```bash
119
+ pytest tests/
120
+
121
+ ```
122
+
123
+ ### Contributing
124
+
125
+ Contributions are welcome! If you have a machine profile not yet supported, please check the [Wiki: 5. Adding Custom Devices](https://github.com/miditkl/cremalink/wiki/) on how to add new `.json` device definitions.
126
+
127
+ Currently supported devices:
128
+
129
+ - `De'Longhi PrimaDonna Soul (AY008ESP1)`
130
+ ---
131
+
132
+ ## 📄 License
133
+
134
+ Distributed under the **AGPL-3.0-or-later** License. See `LICENSE` for more information.
135
+
136
+ ---
137
+
138
+ *Developed by [Midian Tekle Elfu](mailto:developer@midian.tekleelfu.de). Supported by the community.*
@@ -0,0 +1,47 @@
1
+ cremalink/__init__.py,sha256=2W2cTIvMMkpq3x8kqtuawcEgjL4cX19qhBtTcjWgC4M,1080
2
+ cremalink/local_server.py,sha256=KfokRoehbzOxFLILnojvTVh_VUR_KQ3wDWxm2-NY3FM,2279
3
+ cremalink/clients/__init__.py,sha256=vncISOmt3xy-T4ouOBwoCvp546QNIdGXFUErmNDltHw,267
4
+ cremalink/clients/cloud.py,sha256=yQBJHpLJ2CAUaRDrNZridK7E13j_RJc9FrCSKjA9ois,4793
5
+ cremalink/core/__init__.py,sha256=ySQEhr2Le72t9opwaXarLf_m5EwRuMM9_CxXNxabWxU,128
6
+ cremalink/core/binary.py,sha256=ue0tIM2sZrKUYncYYXu3kjc-HShBe2EDXBz6MOh3lOU,3003
7
+ cremalink/crypto/__init__.py,sha256=15-cM__grOMPB1B2sQmUN0IZG4k4tcINyw6L0GXg7-I,3788
8
+ cremalink/devices/AY008ESP1.json,sha256=e7vIetxeMsfdmgWCYxa2UQpJHqFWkX2xLIZpNKuSsIY,2629
9
+ cremalink/devices/__init__.py,sha256=T4Wsw0QQug6z1cQE85G7BASTObde_8kNWHCW2poMZPs,3528
10
+ cremalink/domain/__init__.py,sha256=2U7XWBTBQTZ2DfL2ekYY4ct9gk2g4apqR6HW3zwUVqo,479
11
+ cremalink/domain/device.py,sha256=fTCswGb853pTSy2gaFZVJvjBRy9nQrBzxI5MkeqP9MM,9047
12
+ cremalink/domain/factory.py,sha256=h4AO-I_S0cAy8LwLPrjeooFFBT_sbjB_bLN9iq2b5GM,3467
13
+ cremalink/local_server_app/__init__.py,sha256=02AKAXMDZEayZrvN9jRiyaBRiOYppCLx4oljbwmN0qQ,605
14
+ cremalink/local_server_app/api.py,sha256=S9p-_7htlBSic99ym1wgdsaCX0NTVxkDjX5E0RjzABQ,11703
15
+ cremalink/local_server_app/config.py,sha256=v_btFsV2D3FtUNuawUUH0Xv3CBc4zpz_yOwHCTGZabE,3779
16
+ cremalink/local_server_app/device_adapter.py,sha256=Dbdse8ENcKYAnHU1zP42IjLXDOl3OqeP7hu48g-IE-s,3488
17
+ cremalink/local_server_app/jobs.py,sha256=nxMIDXSg-0fLSZPHMQVT-UKkocxiLFD7CIY6uwsK1T8,4051
18
+ cremalink/local_server_app/logging.py,sha256=w04NNNwXvIfTr4rUXJWEmbtw8OvsFFrHksk0qPk6zRc,3499
19
+ cremalink/local_server_app/models.py,sha256=ibUmbLLYm7s5ddMtsgItyYVpSeYtjSy9ZHZGzm_ekX8,2161
20
+ cremalink/local_server_app/protocol.py,sha256=xdxhqDDSxMrEQSiXZ5ZEjzhBePmiXtIu9di5N2IeGwM,4772
21
+ cremalink/local_server_app/state.py,sha256=HPhmZ7BtSo1AEfNfK6mWXz91oXBATfj3EwjA_HFpxLw,14652
22
+ cremalink/parsing/__init__.py,sha256=DCrsPd9G94qfMfd-CMPfdqHvGdrYyaX97y3WCq8jVtA,223
23
+ cremalink/parsing/monitor/__init__.py,sha256=Bsg68VQErYxHW7xX0PgSY97-6A7_yttGrbu2vW_H1hs,856
24
+ cremalink/parsing/monitor/decode.py,sha256=JsX6S6pmLkV56PWPgpu1ZLabv73ept50ozknC5WW7Sk,2744
25
+ cremalink/parsing/monitor/extractors.py,sha256=6EHdb32fBpzWc_kgRh-DcTUZ5E3MKikpse0SiZZ1LWc,2385
26
+ cremalink/parsing/monitor/frame.py,sha256=xKDzW1-CWMXz5R1ZtDk6BlJoxM4FqS-mu6jfD_65kNU,4234
27
+ cremalink/parsing/monitor/model.py,sha256=wNwVsHs8TucZpztnsN1ZEBsoFuad5cyv_D8CTDpF3Ow,1635
28
+ cremalink/parsing/monitor/profile.py,sha256=zqaGVhhAQvsKmIddMM59p0bpcR4uzZc3PWvg5_r7Mck,5317
29
+ cremalink/parsing/monitor/view.py,sha256=GSR-F9thscJERovP0qGAGAhl7tcz8Iq5DpvvBS9jhgM,7986
30
+ cremalink/parsing/properties/__init__.py,sha256=Ux-F8aB9WDQeVQqBzfD7SGhiHT1upv5v_YaH27Utsgg,330
31
+ cremalink/parsing/properties/decode.py,sha256=Y2tctsERRS-meqHtrptmw4_dyFWgxKZEQc6xRsr71C0,1965
32
+ cremalink/resources/__init__.py,sha256=VYwjbH_pvUsaKCyzInMcdY4aChyxCwnzMrTHOS_Jhfc,344
33
+ cremalink/resources/api_config.json,sha256=dyrKzA4W_zcENOQlt34OjMWhgkumPdZAJRKuPUPP0no,523
34
+ cremalink/resources/api_config.py,sha256=iktoT7H_1mlgHVWyKL4BbDfkOFzUxUEsWWTBQr9bC70,987
35
+ cremalink/resources/lang.json,sha256=g_NpYxPQ8C-lbRwBIb2CcN49KNzQtZZzcExzDAI4Kv0,5739
36
+ cremalink/transports/__init__.py,sha256=HSViUbkcK6BE6H_TI2chJcaKzs_kAQ4PZ1pGWf8b5mc,267
37
+ cremalink/transports/base.py,sha256=c9LWZHabdI1fQb7zN_DUTbnoYS6R2Pv-Jqaj9Usu_3A,2657
38
+ cremalink/transports/cloud/__init__.py,sha256=o6JbkvGHq6d7bF6g5uyUJ_I4heyf3rmenE5bat22ibo,263
39
+ cremalink/transports/cloud/transport.py,sha256=jMi4xFJjmAomF3Ke0XO7mXBvQqzFKYPw1IjRU1uXpA4,6509
40
+ cremalink/transports/local/__init__.py,sha256=GvbBp8eynoKyV5m7q8y-_gAi2SUoC-4QIlRJ_WNo0lA,278
41
+ cremalink/transports/local/transport.py,sha256=uSkBX1vyg2qX3W7mTg82g6UrRMAvkq6coVBJU8_nlhM,6845
42
+ cremalink-0.1.0b5.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
43
+ cremalink-0.1.0b5.dist-info/METADATA,sha256=ReJVT_vNerBI9Mmep54H2kIauy0gSoMzItEw40gaaw0,4819
44
+ cremalink-0.1.0b5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
45
+ cremalink-0.1.0b5.dist-info/entry_points.txt,sha256=Cj-gxeRohv8ao9jp2oM4KiG8BQYyS_L1V1WfSMq6C_c,65
46
+ cremalink-0.1.0b5.dist-info/top_level.txt,sha256=DwLIQZWW38UI0ixg6ZNSNvM0aSlyAhIUf7uE3sqJHOQ,10
47
+ cremalink-0.1.0b5.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ cremalink-server = cremalink.local_server:main