sc-smart-device 3.2.0__tar.gz → 3.2.2__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.
- {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/PKG-INFO +5 -5
- {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/README.md +1 -1
- {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/pyproject.toml +4 -4
- {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/providers/shelly_provider.py +11 -1
- {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/providers/tasmota_provider.py +16 -2
- {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/shelly_models.json +1 -1
- {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/smart_device.py +5 -3
- {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/smart_device_worker.py +75 -0
- {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device.egg-info/PKG-INFO +5 -5
- {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device.egg-info/requires.txt +1 -1
- {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/tests/test_get_x_value.py +2 -2
- {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/setup.cfg +0 -0
- {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/__init__.py +0 -0
- {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/models/__init__.py +0 -0
- {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/models/capabilities.py +0 -0
- {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/models/smart_device_status.py +0 -0
- {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/models/smart_device_view.py +0 -0
- {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/models/worker_types.py +0 -0
- {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/providers/__init__.py +0 -0
- {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/providers/base_provider.py +0 -0
- {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/validation/__init__.py +0 -0
- {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/validation/smart_devices_validator.py +0 -0
- {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/webhooks/__init__.py +0 -0
- {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/webhooks/shelly_webhook_server.py +0 -0
- {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device.egg-info/SOURCES.txt +0 -0
- {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device.egg-info/dependency_links.txt +0 -0
- {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device.egg-info/not-zip-safe +0 -0
- {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device.egg-info/top_level.txt +0 -0
- {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/tests/test_basic.py +0 -0
- {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/tests/test_smart_device_view.py +0 -0
- {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/tests/test_tasmota.py +0 -0
- {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/tests/test_worker.py +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sc-smart-device
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.2
|
|
4
4
|
Summary: Provides a simple, consistent access to Shelly and Tasmota smart switches and energy meters.
|
|
5
|
-
Project-URL: Homepage, https://github.com/
|
|
6
|
-
Project-URL: Repository, https://github.com/
|
|
5
|
+
Project-URL: Homepage, https://github.com/Spello-Consulting
|
|
6
|
+
Project-URL: Repository, https://github.com/Spello-Consulting/sc-smart-device
|
|
7
7
|
Requires-Python: >=3.13
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
|
-
Requires-Dist: sc-foundation-services>=3.0.
|
|
9
|
+
Requires-Dist: sc-foundation-services>=3.0.4
|
|
10
10
|
Requires-Dist: requests>=2.32.0
|
|
11
11
|
Provides-Extra: dev
|
|
12
12
|
Requires-Dist: pytest>=8.3.5; extra == "dev"
|
|
@@ -27,7 +27,7 @@ Requires-Dist: sc-smart-device[dev,docs]; extra == "all"
|
|
|
27
27
|
|
|
28
28
|
Provides a simple, consistent access to Shelly and Tasmota smart switches and energy meters.
|
|
29
29
|
|
|
30
|
-
Please see the [GitHub pages](https://
|
|
30
|
+
Please see the [GitHub pages](https://spello-consulting.github.io/sc-smart-device/) for complete documentation.
|
|
31
31
|
|
|
32
32
|
## Development Environment
|
|
33
33
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Provides a simple, consistent access to Shelly and Tasmota smart switches and energy meters.
|
|
4
4
|
|
|
5
|
-
Please see the [GitHub pages](https://
|
|
5
|
+
Please see the [GitHub pages](https://spello-consulting.github.io/sc-smart-device/) for complete documentation.
|
|
6
6
|
|
|
7
7
|
## Development Environment
|
|
8
8
|
|
|
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sc-smart-device"
|
|
7
|
-
version = "3.2.
|
|
7
|
+
version = "3.2.2"
|
|
8
8
|
description = "Provides a simple, consistent access to Shelly and Tasmota smart switches and energy meters."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13"
|
|
11
11
|
dependencies = [
|
|
12
|
-
"sc-foundation-services>=3.0.
|
|
12
|
+
"sc-foundation-services>=3.0.4",
|
|
13
13
|
"requests>=2.32.0",
|
|
14
14
|
]
|
|
15
15
|
|
|
@@ -33,8 +33,8 @@ all = [
|
|
|
33
33
|
]
|
|
34
34
|
|
|
35
35
|
[project.urls]
|
|
36
|
-
Homepage = "https://github.com/
|
|
37
|
-
Repository = "https://github.com/
|
|
36
|
+
Homepage = "https://github.com/Spello-Consulting"
|
|
37
|
+
Repository = "https://github.com/Spello-Consulting/sc-smart-device"
|
|
38
38
|
|
|
39
39
|
[tool.setuptools]
|
|
40
40
|
package-dir = { "" = "src" }
|
{sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/providers/shelly_provider.py
RENAMED
|
@@ -65,6 +65,7 @@ class ShellyProvider(BaseProvider):
|
|
|
65
65
|
self.webhook_server: ThreadingHTTPServer | None = None
|
|
66
66
|
|
|
67
67
|
# Model library (populated by _import_models)
|
|
68
|
+
self.provider_name = "shelly"
|
|
68
69
|
self.models: list[dict] = []
|
|
69
70
|
self._import_models()
|
|
70
71
|
|
|
@@ -1280,7 +1281,7 @@ class ShellyProvider(BaseProvider):
|
|
|
1280
1281
|
if device.get("Protocol") == "RPC":
|
|
1281
1282
|
caps.add(DeviceCapability.WEBHOOKS)
|
|
1282
1283
|
caps.add(DeviceCapability.DEVICE_LOCATION)
|
|
1283
|
-
|
|
1284
|
+
base = {
|
|
1284
1285
|
"ID": device["ID"],
|
|
1285
1286
|
"Name": device["Name"],
|
|
1286
1287
|
"Online": device.get("Online", False),
|
|
@@ -1291,6 +1292,11 @@ class ShellyProvider(BaseProvider):
|
|
|
1291
1292
|
"TotalEnergy": device.get("TotalEnergy", 0.0),
|
|
1292
1293
|
"Capabilities": caps,
|
|
1293
1294
|
}
|
|
1295
|
+
internal = {"customkeylist", "Outputs", "Inputs", "Meters", "TempProbes"}
|
|
1296
|
+
for key, value in device.items():
|
|
1297
|
+
if key not in base and key not in internal:
|
|
1298
|
+
base[key] = value
|
|
1299
|
+
return base
|
|
1294
1300
|
|
|
1295
1301
|
@staticmethod
|
|
1296
1302
|
def _normalize_component(component: dict) -> dict:
|
|
@@ -1317,4 +1323,8 @@ class ShellyProvider(BaseProvider):
|
|
|
1317
1323
|
elif obj_type == "temp_probe":
|
|
1318
1324
|
base["Temperature"] = component.get("Temperature")
|
|
1319
1325
|
base["LastReadingTime"] = component.get("LastReadingTime")
|
|
1326
|
+
internal = {"customkeylist"}
|
|
1327
|
+
for key, value in component.items():
|
|
1328
|
+
if key not in base and key not in internal:
|
|
1329
|
+
base[key] = value
|
|
1320
1330
|
return base
|
{sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/providers/tasmota_provider.py
RENAMED
|
@@ -50,6 +50,8 @@ class TasmotaProvider(BaseProvider):
|
|
|
50
50
|
self.ping_allowed = True
|
|
51
51
|
self.simulation_file_folder: Path | None = None
|
|
52
52
|
|
|
53
|
+
self.provider_name = "tasmota"
|
|
54
|
+
|
|
53
55
|
@staticmethod
|
|
54
56
|
def _raise_runtime_error(message: str) -> NoReturn:
|
|
55
57
|
raise RuntimeError(message)
|
|
@@ -185,7 +187,10 @@ class TasmotaProvider(BaseProvider):
|
|
|
185
187
|
return None
|
|
186
188
|
|
|
187
189
|
def print_model_library(self, _mode_str: str = "brief", _model_id: str | None = None) -> str: # noqa: PLR6301
|
|
188
|
-
|
|
190
|
+
lines = ["Tasmota Model Library:"]
|
|
191
|
+
lines.append(" Tasmota")
|
|
192
|
+
return "\n".join(lines)
|
|
193
|
+
|
|
189
194
|
|
|
190
195
|
def print_device_status(self, device_identity: int | str | None = None) -> str:
|
|
191
196
|
device_index = None
|
|
@@ -666,7 +671,7 @@ class TasmotaProvider(BaseProvider):
|
|
|
666
671
|
caps.add(DeviceCapability.TEMPERATURE)
|
|
667
672
|
if device.get("Simulate"):
|
|
668
673
|
caps.add(DeviceCapability.SIMULATION)
|
|
669
|
-
|
|
674
|
+
base = {
|
|
670
675
|
"ID": device["ID"],
|
|
671
676
|
"Name": device["Name"],
|
|
672
677
|
"Online": device.get("Online", False),
|
|
@@ -677,6 +682,11 @@ class TasmotaProvider(BaseProvider):
|
|
|
677
682
|
"TotalEnergy": device.get("TotalEnergy", 0.0),
|
|
678
683
|
"Capabilities": caps,
|
|
679
684
|
}
|
|
685
|
+
internal = {"customkeylist", "Outputs", "Inputs", "Meters", "TempProbes", "Tasmota"}
|
|
686
|
+
for key, value in device.items():
|
|
687
|
+
if key not in base and key not in internal:
|
|
688
|
+
base[key] = value
|
|
689
|
+
return base
|
|
680
690
|
|
|
681
691
|
@staticmethod
|
|
682
692
|
def _normalize_component(component: dict) -> dict:
|
|
@@ -700,6 +710,10 @@ class TasmotaProvider(BaseProvider):
|
|
|
700
710
|
elif obj_type == "temp_probe":
|
|
701
711
|
base["Temperature"] = component.get("Temperature")
|
|
702
712
|
base["LastReadingTime"] = component.get("LastReadingTime")
|
|
713
|
+
internal = {"customkeylist"}
|
|
714
|
+
for key, value in component.items():
|
|
715
|
+
if key not in base and key not in internal:
|
|
716
|
+
base[key] = value
|
|
703
717
|
return base
|
|
704
718
|
|
|
705
719
|
# ── Helpers ───────────────────────────────────────────────────────────────────
|
|
@@ -488,7 +488,7 @@ class SCSmartDevice: # noqa: PLR0904
|
|
|
488
488
|
) -> None:
|
|
489
489
|
"""Install a webhook on a device component.
|
|
490
490
|
|
|
491
|
-
See https://
|
|
491
|
+
See https://spello-consulting.github.io/sc-smart-device/shelly_webhooks/ for details on supported events and payloads.
|
|
492
492
|
|
|
493
493
|
Args:
|
|
494
494
|
event: Shelly event name, e.g. ``"input.toggle_on"``.
|
|
@@ -584,17 +584,19 @@ class SCSmartDevice: # noqa: PLR0904
|
|
|
584
584
|
parts = [p.print_device_status() for p in self._providers if p.get_normalized_status().devices]
|
|
585
585
|
return "\n".join(part for part in parts if part)
|
|
586
586
|
|
|
587
|
-
def print_model_library(self, mode_str: str = "brief", model_id: str | None = None) -> str:
|
|
587
|
+
def print_model_library(self, mode_str: str = "brief", model_id: str | None = None, provider_id: str | None = None) -> str:
|
|
588
588
|
"""Prints the model library for all providers that have one.
|
|
589
589
|
|
|
590
590
|
Args:
|
|
591
591
|
mode_str (str, optional): The mode of printing. Can be "brief" or "detailed". Defaults to "brief".
|
|
592
592
|
model_id (Optional (str), optional): If provided, filters the models by this model name. If None, prints all models.
|
|
593
|
+
provider_id (Optional (str), optional): If provided, filters the models by this provider name ('shelly' or 'tasmota'). If None, prints models from all providers.
|
|
593
594
|
|
|
594
595
|
Returns:
|
|
595
596
|
library_info (str): A string representation of the model library.
|
|
596
597
|
"""
|
|
597
|
-
|
|
598
|
+
provider_id = provider_id.lower() if provider_id else None
|
|
599
|
+
parts = [p.print_model_library(mode_str, model_id) for p in self._providers if (not provider_id or getattr(p, "provider_name", "").lower() == provider_id)]
|
|
598
600
|
return "\n".join(part for part in parts if part)
|
|
599
601
|
|
|
600
602
|
# ── Lifecycle ────────────────────────────────────────────────────────────
|
|
@@ -231,6 +231,81 @@ class SmartDeviceWorker:
|
|
|
231
231
|
)
|
|
232
232
|
return self.submit(req)
|
|
233
233
|
|
|
234
|
+
# ── SCSmartDevice pass-throughs ──────────────────────────────────────────
|
|
235
|
+
|
|
236
|
+
def is_device_online(self, device_identity: dict | int | str | None = None) -> bool:
|
|
237
|
+
"""See if a device is alive by pinging it.
|
|
238
|
+
|
|
239
|
+
Returns the result and updates the device's online status. If we are in simulation mode, always returns True.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
device_identity: The actual device object, device component object, device ID or device name of the device to check. If None, checks all devices.
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
True if the device is online, False otherwise. If all devices are checked, returns True if all devices are online.
|
|
246
|
+
|
|
247
|
+
Raises:
|
|
248
|
+
RuntimeError: If the device is not found in the list of devices.
|
|
249
|
+
""" # noqa: DOC502
|
|
250
|
+
return self._smart_device.is_device_online(device_identity)
|
|
251
|
+
|
|
252
|
+
def pull_webhook_event(self) -> dict | None:
|
|
253
|
+
"""Return the oldest queued webhook event and remove it from the queue.
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
Event dict with keys ``timestamp``, ``Event``, ``Device``,
|
|
257
|
+
``Component``, etc.; or None if the queue is empty.
|
|
258
|
+
"""
|
|
259
|
+
return self._smart_device.pull_webhook_event()
|
|
260
|
+
|
|
261
|
+
def does_device_have_webhooks(self, device: dict) -> bool:
|
|
262
|
+
"""Return True if any component of the device has webhooks enabled."""
|
|
263
|
+
return self._smart_device.does_device_have_webhooks(device)
|
|
264
|
+
|
|
265
|
+
def get_device_information(
|
|
266
|
+
self, device_identity: dict | int | str, refresh_status: bool = False
|
|
267
|
+
) -> dict:
|
|
268
|
+
"""Return a consolidated copy of one device and all its components.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
device_identity: Device dict, ID, or name.
|
|
272
|
+
refresh_status: If True, fetch fresh state from hardware first.
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
Device dict augmented with ``Inputs``, ``Outputs``, ``Meters``,
|
|
276
|
+
and ``TempProbes`` sub-lists.
|
|
277
|
+
|
|
278
|
+
Raises:
|
|
279
|
+
RuntimeError: If the device is not found in the list of devices or if there is an error getting the status.
|
|
280
|
+
""" # noqa: DOC502
|
|
281
|
+
return self._smart_device.get_device_information(device_identity, refresh_status)
|
|
282
|
+
|
|
283
|
+
def print_device_status(self, device_identity: int | str | None = None) -> str:
|
|
284
|
+
"""Print the status of a device or all devices.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
device_identity: The ID or name of the device to check. If None, checks all devices.
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
A string representation of the device status.
|
|
291
|
+
|
|
292
|
+
Raises:
|
|
293
|
+
RuntimeError: If the device is not found in the list of devices.
|
|
294
|
+
""" # noqa: DOC502
|
|
295
|
+
return self._smart_device.print_device_status(device_identity)
|
|
296
|
+
|
|
297
|
+
def print_model_library(self, mode_str: str = "brief", model_id: str | None = None) -> str:
|
|
298
|
+
"""Print the model library for all providers that have one.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
mode_str: The mode of printing. Can be ``"brief"`` or ``"detailed"``. Defaults to ``"brief"``.
|
|
302
|
+
model_id: If provided, filters the models by this model name. If None, prints all models.
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
A string representation of the model library.
|
|
306
|
+
"""
|
|
307
|
+
return self._smart_device.print_model_library(mode_str, model_id)
|
|
308
|
+
|
|
234
309
|
# ── Worker loop ──────────────────────────────────────────────────────────
|
|
235
310
|
|
|
236
311
|
def run(self) -> None:
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sc-smart-device
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.2
|
|
4
4
|
Summary: Provides a simple, consistent access to Shelly and Tasmota smart switches and energy meters.
|
|
5
|
-
Project-URL: Homepage, https://github.com/
|
|
6
|
-
Project-URL: Repository, https://github.com/
|
|
5
|
+
Project-URL: Homepage, https://github.com/Spello-Consulting
|
|
6
|
+
Project-URL: Repository, https://github.com/Spello-Consulting/sc-smart-device
|
|
7
7
|
Requires-Python: >=3.13
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
|
-
Requires-Dist: sc-foundation-services>=3.0.
|
|
9
|
+
Requires-Dist: sc-foundation-services>=3.0.4
|
|
10
10
|
Requires-Dist: requests>=2.32.0
|
|
11
11
|
Provides-Extra: dev
|
|
12
12
|
Requires-Dist: pytest>=8.3.5; extra == "dev"
|
|
@@ -27,7 +27,7 @@ Requires-Dist: sc-smart-device[dev,docs]; extra == "all"
|
|
|
27
27
|
|
|
28
28
|
Provides a simple, consistent access to Shelly and Tasmota smart switches and energy meters.
|
|
29
29
|
|
|
30
|
-
Please see the [GitHub pages](https://
|
|
30
|
+
Please see the [GitHub pages](https://spello-consulting.github.io/sc-smart-device/) for complete documentation.
|
|
31
31
|
|
|
32
32
|
## Development Environment
|
|
33
33
|
|
|
@@ -46,8 +46,8 @@ def test_view_get_output_value_missing_key():
|
|
|
46
46
|
"""get_output_value() should return the default for absent keys (e.g. custom keys)."""
|
|
47
47
|
view = smart_device.get_view()
|
|
48
48
|
output_id = view.get_output_id(OUTPUT_1_NAME)
|
|
49
|
-
assert view.get_output_value(output_id, "
|
|
50
|
-
assert view.get_output_value(output_id, "
|
|
49
|
+
assert view.get_output_value(output_id, "XXX") is None
|
|
50
|
+
assert view.get_output_value(output_id, "XXX", "Ungrouped") == "Ungrouped"
|
|
51
51
|
|
|
52
52
|
|
|
53
53
|
def test_view_get_output_value_invalid_id():
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/models/smart_device_status.py
RENAMED
|
File without changes
|
{sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/models/smart_device_view.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/providers/base_provider.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|