aiometric 0.1.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.
@@ -0,0 +1,192 @@
1
+ Metadata-Version: 2.4
2
+ Name: aiometric
3
+ Version: 0.1.0
4
+ Summary: Asynchronous Python client for interacting with the LaMetric and the LaMetric Cloud API
5
+ License: MIT
6
+ Keywords: lametric,lametric sky,lametric time,lametric api,async,client
7
+ Author: Caspar Weber
8
+ Maintainer: Caspar Weber
9
+ Requires-Python: >=3.11,<4.0
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Framework :: AsyncIO
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Natural Language :: English
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Dist: aiohttp (>=3.0.0)
22
+ Requires-Dist: awesomeversion (>=21.10.1)
23
+ Requires-Dist: backoff (>=2.2.0)
24
+ Requires-Dist: mashumaro (>=3.10)
25
+ Requires-Dist: orjson (>=3.9.8)
26
+ Requires-Dist: yarl (>=1.6.0)
27
+ Project-URL: Bug Tracker, https://github.com/ElectroAttacks/aiometric/issues
28
+ Project-URL: Changelog, https://github.com/ElectroAttacks/aiometric/releases
29
+ Project-URL: Documentation, https://github.com/ElectroAttacks/aiometric
30
+ Project-URL: Homepage, https://github.com/ElectroAttacks/aiometric
31
+ Project-URL: Repository, https://github.com/ElectroAttacks/aiometric
32
+ Description-Content-Type: text/markdown
33
+
34
+ # aiometric
35
+
36
+ Asynchronous Python client for the LaMetric local device API and LaMetric Cloud API.
37
+
38
+ aiometric gives you typed models, async-first device/cloud clients, and practical helpers for common tasks such as reading device state, controlling display/audio, sending notifications, and querying cloud-connected devices.
39
+
40
+ ## Features
41
+
42
+ - Async clients for local and cloud APIs
43
+ - Typed request/response models (dataclasses)
44
+ - Retries for transient connection failures
45
+ - Clear error classes for auth, timeout, and API version issues
46
+ - Optional streaming support via LMSP UDP frames
47
+
48
+ ## Requirements
49
+
50
+ - Python 3.11+
51
+
52
+ ## Installation
53
+
54
+ Install from PyPI:
55
+
56
+ ```bash
57
+ pip install aiometric
58
+ ```
59
+
60
+ Or with Poetry:
61
+
62
+ ```bash
63
+ poetry add aiometric
64
+ ```
65
+
66
+ ## Quick Start
67
+
68
+ ### Example 1: Connect to a local device and send a notification
69
+
70
+ ```python
71
+ import asyncio
72
+
73
+ from aiometric import (
74
+ LaMetricDevice,
75
+ Notification,
76
+ NotificationModel,
77
+ SimpleFrame,
78
+ )
79
+
80
+
81
+ async def main() -> None:
82
+ # Get API key from the LaMetric developer page / app settings.
83
+ host = "192.168.1.100"
84
+ api_key = "YOUR_DEVICE_API_KEY"
85
+
86
+ async with LaMetricDevice(host=host, api_key=api_key) as device:
87
+ state = await device.get_device_state()
88
+ print(f"Connected to: {state.name} ({state.model})")
89
+
90
+ # Update display brightness.
91
+ display = await device.get_or_set_display_state(brightness=40)
92
+ print(f"Brightness is now: {display.brightness}")
93
+
94
+ # Send a simple notification.
95
+ notification = Notification(
96
+ model=NotificationModel(
97
+ frames=[SimpleFrame(text="Hello from aiometric")],
98
+ cycles=1,
99
+ repeat=1,
100
+ )
101
+ )
102
+ notification_id = await device.notify(notification)
103
+ print(f"Notification sent: {notification_id}")
104
+
105
+
106
+ asyncio.run(main())
107
+ ```
108
+
109
+ ### Example 2: Use LaMetric Cloud API
110
+
111
+ ```python
112
+ import asyncio
113
+
114
+ from aiometric import LaMetricCloud
115
+
116
+
117
+ async def main() -> None:
118
+ token = "YOUR_CLOUD_ACCESS_TOKEN"
119
+
120
+ async with LaMetricCloud(token=token) as cloud:
121
+ user = await cloud.get_current_user()
122
+ print(f"User: {user.email}")
123
+
124
+ devices = await cloud.get_devices()
125
+ print(f"Cloud devices: {len(devices)}")
126
+ for device in devices:
127
+ print(f"- {device.id}: {device.name} ({device.state})")
128
+
129
+
130
+ asyncio.run(main())
131
+ ```
132
+
133
+ ## Common Device Operations
134
+
135
+ Local client methods in `LaMetricDevice` include:
136
+
137
+ - `get_device_state()`
138
+ - `get_or_set_display_state(...)`
139
+ - `get_or_set_audio_state(...)`
140
+ - `get_or_set_bluetooth_state(...)`
141
+ - `get_wifi_state()`
142
+ - `get_apps()`, `get_app(package)`
143
+ - `notify(...)`, `get_notifications()`, `dismiss_notification(...)`
144
+ - `get_streaming_state()`, `start_stream(...)`, `send_stream_frame(...)`
145
+
146
+ Cloud client methods in `LaMetricCloud` include:
147
+
148
+ - `get_current_user()`
149
+ - `get_devices()`
150
+ - `get_device(device_id)`
151
+
152
+ ## Error Handling
153
+
154
+ aiometric raises specific exception types you can catch:
155
+
156
+ - `LaMetricAuthenticationError`
157
+ - `LaMetricConnectionTimeoutError`
158
+ - `LaMetricConnectionError`
159
+ - `LaMetricInvalidApiVersionError`
160
+ - `LaMetricError` (base class)
161
+
162
+ Example:
163
+
164
+ ```python
165
+ from aiometric import LaMetricAuthenticationError, LaMetricConnectionTimeoutError
166
+
167
+ try:
168
+ ...
169
+ except LaMetricAuthenticationError:
170
+ print("Invalid token or API key")
171
+ except LaMetricConnectionTimeoutError:
172
+ print("Request timed out")
173
+ ```
174
+
175
+ ## Development
176
+
177
+ Run tests:
178
+
179
+ ```bash
180
+ poetry run pytest
181
+ ```
182
+
183
+ Run all checks:
184
+
185
+ ```bash
186
+ poetry run pre-commit run --all-files
187
+ ```
188
+
189
+ ## License
190
+
191
+ MIT
192
+
@@ -0,0 +1,158 @@
1
+ # aiometric
2
+
3
+ Asynchronous Python client for the LaMetric local device API and LaMetric Cloud API.
4
+
5
+ aiometric gives you typed models, async-first device/cloud clients, and practical helpers for common tasks such as reading device state, controlling display/audio, sending notifications, and querying cloud-connected devices.
6
+
7
+ ## Features
8
+
9
+ - Async clients for local and cloud APIs
10
+ - Typed request/response models (dataclasses)
11
+ - Retries for transient connection failures
12
+ - Clear error classes for auth, timeout, and API version issues
13
+ - Optional streaming support via LMSP UDP frames
14
+
15
+ ## Requirements
16
+
17
+ - Python 3.11+
18
+
19
+ ## Installation
20
+
21
+ Install from PyPI:
22
+
23
+ ```bash
24
+ pip install aiometric
25
+ ```
26
+
27
+ Or with Poetry:
28
+
29
+ ```bash
30
+ poetry add aiometric
31
+ ```
32
+
33
+ ## Quick Start
34
+
35
+ ### Example 1: Connect to a local device and send a notification
36
+
37
+ ```python
38
+ import asyncio
39
+
40
+ from aiometric import (
41
+ LaMetricDevice,
42
+ Notification,
43
+ NotificationModel,
44
+ SimpleFrame,
45
+ )
46
+
47
+
48
+ async def main() -> None:
49
+ # Get API key from the LaMetric developer page / app settings.
50
+ host = "192.168.1.100"
51
+ api_key = "YOUR_DEVICE_API_KEY"
52
+
53
+ async with LaMetricDevice(host=host, api_key=api_key) as device:
54
+ state = await device.get_device_state()
55
+ print(f"Connected to: {state.name} ({state.model})")
56
+
57
+ # Update display brightness.
58
+ display = await device.get_or_set_display_state(brightness=40)
59
+ print(f"Brightness is now: {display.brightness}")
60
+
61
+ # Send a simple notification.
62
+ notification = Notification(
63
+ model=NotificationModel(
64
+ frames=[SimpleFrame(text="Hello from aiometric")],
65
+ cycles=1,
66
+ repeat=1,
67
+ )
68
+ )
69
+ notification_id = await device.notify(notification)
70
+ print(f"Notification sent: {notification_id}")
71
+
72
+
73
+ asyncio.run(main())
74
+ ```
75
+
76
+ ### Example 2: Use LaMetric Cloud API
77
+
78
+ ```python
79
+ import asyncio
80
+
81
+ from aiometric import LaMetricCloud
82
+
83
+
84
+ async def main() -> None:
85
+ token = "YOUR_CLOUD_ACCESS_TOKEN"
86
+
87
+ async with LaMetricCloud(token=token) as cloud:
88
+ user = await cloud.get_current_user()
89
+ print(f"User: {user.email}")
90
+
91
+ devices = await cloud.get_devices()
92
+ print(f"Cloud devices: {len(devices)}")
93
+ for device in devices:
94
+ print(f"- {device.id}: {device.name} ({device.state})")
95
+
96
+
97
+ asyncio.run(main())
98
+ ```
99
+
100
+ ## Common Device Operations
101
+
102
+ Local client methods in `LaMetricDevice` include:
103
+
104
+ - `get_device_state()`
105
+ - `get_or_set_display_state(...)`
106
+ - `get_or_set_audio_state(...)`
107
+ - `get_or_set_bluetooth_state(...)`
108
+ - `get_wifi_state()`
109
+ - `get_apps()`, `get_app(package)`
110
+ - `notify(...)`, `get_notifications()`, `dismiss_notification(...)`
111
+ - `get_streaming_state()`, `start_stream(...)`, `send_stream_frame(...)`
112
+
113
+ Cloud client methods in `LaMetricCloud` include:
114
+
115
+ - `get_current_user()`
116
+ - `get_devices()`
117
+ - `get_device(device_id)`
118
+
119
+ ## Error Handling
120
+
121
+ aiometric raises specific exception types you can catch:
122
+
123
+ - `LaMetricAuthenticationError`
124
+ - `LaMetricConnectionTimeoutError`
125
+ - `LaMetricConnectionError`
126
+ - `LaMetricInvalidApiVersionError`
127
+ - `LaMetricError` (base class)
128
+
129
+ Example:
130
+
131
+ ```python
132
+ from aiometric import LaMetricAuthenticationError, LaMetricConnectionTimeoutError
133
+
134
+ try:
135
+ ...
136
+ except LaMetricAuthenticationError:
137
+ print("Invalid token or API key")
138
+ except LaMetricConnectionTimeoutError:
139
+ print("Request timed out")
140
+ ```
141
+
142
+ ## Development
143
+
144
+ Run tests:
145
+
146
+ ```bash
147
+ poetry run pytest
148
+ ```
149
+
150
+ Run all checks:
151
+
152
+ ```bash
153
+ poetry run pre-commit run --all-files
154
+ ```
155
+
156
+ ## License
157
+
158
+ MIT
@@ -0,0 +1,162 @@
1
+ [tool.poetry]
2
+ authors = ["Caspar Weber"]
3
+ classifiers = [
4
+ "Development Status :: 4 - Beta",
5
+ "Framework :: AsyncIO",
6
+ "Intended Audience :: Developers",
7
+ "Natural Language :: English",
8
+ "Programming Language :: Python :: 3.11",
9
+ "Programming Language :: Python :: 3.12",
10
+ "Programming Language :: Python :: 3.13",
11
+ "Programming Language :: Python :: 3",
12
+ "Topic :: Software Development :: Libraries :: Python Modules",
13
+ ]
14
+ description = "Asynchronous Python client for interacting with the LaMetric and the LaMetric Cloud API"
15
+ documentation = "https://github.com/ElectroAttacks/aiometric"
16
+ homepage = "https://github.com/ElectroAttacks/aiometric"
17
+ keywords = ["lametric","lametric sky", "lametric time", "lametric api", "async", "client"]
18
+ license = "MIT"
19
+ maintainers = ["Caspar Weber"]
20
+ name = "aiometric"
21
+ packages = [{ include = "aiometric", from = "src" }]
22
+ readme = "README.md"
23
+ repository = "https://github.com/ElectroAttacks/aiometric"
24
+ version = "0.1.0"
25
+
26
+ [tool.poetry.dependencies]
27
+ aiohttp = ">=3.0.0"
28
+ awesomeversion = ">=21.10.1"
29
+ backoff = ">=2.2.0"
30
+ mashumaro = ">=3.10"
31
+ orjson = ">=3.9.8"
32
+ python = "^3.11"
33
+ yarl = ">=1.6.0"
34
+
35
+ [tool.poetry.urls]
36
+ "Bug Tracker" = "https://github.com/ElectroAttacks/aiometric/issues"
37
+ Changelog = "https://github.com/ElectroAttacks/aiometric/releases"
38
+
39
+ [tool.poetry.group.dev.dependencies]
40
+ aresponses = "3.0.0"
41
+ codespell = "2.4.2"
42
+ covdefaults = "2.3.0"
43
+ coverage = { version = "7.13.5", extras = ["toml"] }
44
+ mypy = "1.19.1"
45
+ pre-commit = "4.5.1"
46
+ pre-commit-hooks = "6.0.0"
47
+ pylint = "4.0.5"
48
+ pytest = "9.0.2"
49
+ pytest-asyncio = "1.3.0"
50
+ pytest-cov = "7.1.0"
51
+ ruff = "0.15.8"
52
+ yamllint = "1.38.0"
53
+ syrupy = "5.1.0"
54
+
55
+ [tool.coverage.run]
56
+ plugins = ["covdefaults"]
57
+ source = ["aiometric"]
58
+
59
+ [tool.coverage.report]
60
+ fail_under = 90
61
+ show_missing = true
62
+
63
+ [tool.mypy]
64
+ # Specify the target platform details in config, so your developers are
65
+ # free to run mypy on Windows, Linux, or macOS and get consistent
66
+ # results.
67
+ platform = "linux"
68
+ plugins = []
69
+ python_version = "3.11"
70
+
71
+ # flake8-mypy expects the two following for sensible formatting
72
+ show_column_numbers = true
73
+
74
+ # show error messages from unrelated files
75
+ follow_imports = "normal"
76
+
77
+ # suppress errors about unsatisfied imports
78
+ ignore_missing_imports = true
79
+
80
+ # be strict
81
+ check_untyped_defs = true
82
+ disallow_any_generics = true
83
+ disallow_incomplete_defs = true
84
+ disallow_subclassing_any = true
85
+ disallow_untyped_calls = true
86
+ disallow_untyped_decorators = true
87
+ disallow_untyped_defs = true
88
+ no_implicit_optional = true
89
+ no_implicit_reexport = true
90
+ strict_optional = true
91
+ warn_incomplete_stub = true
92
+ warn_no_return = true
93
+ warn_redundant_casts = true
94
+ warn_return_any = true
95
+ warn_unused_configs = true
96
+ warn_unused_ignores = true
97
+
98
+ [tool.pylint.MASTER]
99
+ ignore = ["tests"]
100
+
101
+ [tool.pylint.BASIC]
102
+ good-names = ["_", "ex", "fp", "i", "id", "j", "k", "on", "Run", "T", "wv"]
103
+
104
+ [tool.pylint."MESSAGES CONTROL"]
105
+ disable = [
106
+ "too-few-public-methods",
107
+ "duplicate-code",
108
+ "format",
109
+ "unsubscriptable-object",
110
+ ]
111
+
112
+ [tool.pylint.SIMILARITIES]
113
+ ignore-imports = true
114
+
115
+ [tool.pylint.FORMAT]
116
+ max-line-length = 88
117
+
118
+ [tool.pylint.DESIGN]
119
+ max-attributes = 20
120
+
121
+ [tool.pytest.ini_options]
122
+ addopts = "--cov"
123
+ asyncio_mode = "auto"
124
+
125
+ [tool.ruff.lint]
126
+ ignore = [
127
+ "ANN401", # Opinioated warning on disallowing dynamically typed expressions
128
+ "D203", # Conflicts with other rules
129
+ "D213", # Conflicts with other rules
130
+ "D417", # False positives in some occasions
131
+ "PLR2004", # Just annoying, not really useful
132
+
133
+
134
+ # Conflicts with the Ruff formatter
135
+ "COM812",
136
+ "ISC001",
137
+ ]
138
+ select = ["ALL"]
139
+
140
+ [tool.ruff.lint.flake8-pytest-style]
141
+ fixture-parentheses = false
142
+ mark-parentheses = false
143
+
144
+ [tool.ruff.lint.isort]
145
+ known-first-party = ["aiometric"]
146
+
147
+ [tool.ruff.lint.flake8-type-checking]
148
+ runtime-evaluated-base-classes = [
149
+ "mashumaro.mixins.orjson.DataClassORJSONMixin",
150
+ ]
151
+
152
+ [tool.ruff.lint.mccabe]
153
+ max-complexity = 25
154
+
155
+ [tool.ruff.lint.per-file-ignores]
156
+ "src/demetriek/models.py" = ["TCH002", "TCH003"]
157
+ "tests/**/*.py" = ["D103", "S101", "SLF001"]
158
+ "tests/fixtures.py" = ["S105"]
159
+
160
+ [build-system]
161
+ build-backend = "poetry.core.masonry.api"
162
+ requires = ["poetry-core>=1.0.0"]
@@ -0,0 +1,118 @@
1
+ """Public package interface for lametric_websocket."""
2
+
3
+ from .cloud import LaMetricCloud
4
+ from .const import (
5
+ AlarmSound,
6
+ CanvasFillType,
7
+ CanvasRenderMode,
8
+ CloudDeviceState,
9
+ DeviceMode,
10
+ DeviceModel,
11
+ DisplayMode,
12
+ DisplayType,
13
+ EffectType,
14
+ IconType,
15
+ IpAddressMode,
16
+ NotificationPriority,
17
+ NotificationSound,
18
+ NotificationType,
19
+ PostProcessingType,
20
+ SoundCategory,
21
+ StreamingState,
22
+ WifiEncryptionType,
23
+ )
24
+ from .device import LaMetricDevice
25
+ from .errors import (
26
+ LaMetricAuthenticationError,
27
+ LaMetricConnectionError,
28
+ LaMetricConnectionTimeoutError,
29
+ LaMetricError,
30
+ LaMetricInvalidApiVersionError,
31
+ )
32
+ from .models import (
33
+ ActionParameter,
34
+ App,
35
+ AppWidget,
36
+ AudioState,
37
+ BluetoothState,
38
+ BuiltinSound,
39
+ Canvas,
40
+ CanvasArea,
41
+ CloudDevice,
42
+ CloudUser,
43
+ DeviceState,
44
+ DisplayState,
45
+ FadingPixelsParameter,
46
+ GoalFrame,
47
+ GoalFrameData,
48
+ Notification,
49
+ NotificationModel,
50
+ PostProcessing,
51
+ PostProcessingParameter,
52
+ Screensaver,
53
+ SimpleFrame,
54
+ SpikeChartFrame,
55
+ StreamState,
56
+ TimeBasedScreensaver,
57
+ Update,
58
+ ValueRange,
59
+ WebSound,
60
+ WhenDarkScreensaver,
61
+ WifiState,
62
+ )
63
+
64
+ __all__ = [
65
+ "ActionParameter",
66
+ "AlarmSound",
67
+ "App",
68
+ "AppWidget",
69
+ "AudioState",
70
+ "BluetoothState",
71
+ "BuiltinSound",
72
+ "Canvas",
73
+ "CanvasArea",
74
+ "CanvasFillType",
75
+ "CanvasRenderMode",
76
+ "CloudDevice",
77
+ "CloudDeviceState",
78
+ "CloudUser",
79
+ "DeviceMode",
80
+ "DeviceModel",
81
+ "DeviceState",
82
+ "DisplayMode",
83
+ "DisplayState",
84
+ "DisplayType",
85
+ "EffectType",
86
+ "FadingPixelsParameter",
87
+ "GoalFrame",
88
+ "GoalFrameData",
89
+ "IconType",
90
+ "IpAddressMode",
91
+ "LaMetricAuthenticationError",
92
+ "LaMetricConnectionError",
93
+ "LaMetricConnectionTimeoutError",
94
+ "LaMetricDevice",
95
+ "LaMetricError",
96
+ "LaMetricInvalidApiVersionError",
97
+ "Notification",
98
+ "NotificationModel",
99
+ "NotificationPriority",
100
+ "NotificationSound",
101
+ "NotificationType",
102
+ "PostProcessing",
103
+ "PostProcessingParameter",
104
+ "PostProcessingType",
105
+ "Screensaver",
106
+ "SimpleFrame",
107
+ "SoundCategory",
108
+ "SpikeChartFrame",
109
+ "StreamState",
110
+ "StreamingState",
111
+ "TimeBasedScreensaver",
112
+ "Update",
113
+ "ValueRange",
114
+ "WebSound",
115
+ "WhenDarkScreensaver",
116
+ "WifiEncryptionType",
117
+ "WifiState",
118
+ ]