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.
Files changed (32) hide show
  1. {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/PKG-INFO +5 -5
  2. {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/README.md +1 -1
  3. {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/pyproject.toml +4 -4
  4. {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/providers/shelly_provider.py +11 -1
  5. {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/providers/tasmota_provider.py +16 -2
  6. {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/shelly_models.json +1 -1
  7. {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/smart_device.py +5 -3
  8. {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/smart_device_worker.py +75 -0
  9. {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device.egg-info/PKG-INFO +5 -5
  10. {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device.egg-info/requires.txt +1 -1
  11. {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/tests/test_get_x_value.py +2 -2
  12. {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/setup.cfg +0 -0
  13. {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/__init__.py +0 -0
  14. {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/models/__init__.py +0 -0
  15. {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/models/capabilities.py +0 -0
  16. {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/models/smart_device_status.py +0 -0
  17. {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/models/smart_device_view.py +0 -0
  18. {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/models/worker_types.py +0 -0
  19. {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/providers/__init__.py +0 -0
  20. {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/providers/base_provider.py +0 -0
  21. {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/validation/__init__.py +0 -0
  22. {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/validation/smart_devices_validator.py +0 -0
  23. {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/webhooks/__init__.py +0 -0
  24. {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device/webhooks/shelly_webhook_server.py +0 -0
  25. {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device.egg-info/SOURCES.txt +0 -0
  26. {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device.egg-info/dependency_links.txt +0 -0
  27. {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device.egg-info/not-zip-safe +0 -0
  28. {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/src/sc_smart_device.egg-info/top_level.txt +0 -0
  29. {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/tests/test_basic.py +0 -0
  30. {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/tests/test_smart_device_view.py +0 -0
  31. {sc_smart_device-3.2.0 → sc_smart_device-3.2.2}/tests/test_tasmota.py +0 -0
  32. {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.0
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/NickElseySpelloC
6
- Project-URL: Repository, https://github.com/NickElseySpelloC/sc-smart-device
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.3
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://nickelseyspelloc.github.io/sc-smart-device/) for complete documentation.
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://nickelseyspelloc.github.io/sc-smart-device/) for complete documentation.
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.0"
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.3",
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/NickElseySpelloC"
37
- Repository = "https://github.com/NickElseySpelloC/sc-smart-device"
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" }
@@ -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
- return {
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
@@ -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
- return "" # Tasmota has no model file — config-driven
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
- return {
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 ───────────────────────────────────────────────────────────────────
@@ -83,7 +83,7 @@
83
83
  "protocol": "RPC",
84
84
  "inputs": 2,
85
85
  "outputs": 1,
86
- "meters": 2,
86
+ "meters": 1,
87
87
  "meters_seperate": false,
88
88
  "temperature_monitoring": true
89
89
  },
@@ -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://nickelseyspelloc.github.io/sc-smart-device/shelly_webhooks/ for details on supported events and payloads.
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
- parts = [p.print_model_library(mode_str, model_id) for p in self._providers]
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.0
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/NickElseySpelloC
6
- Project-URL: Repository, https://github.com/NickElseySpelloC/sc-smart-device
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.3
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://nickelseyspelloc.github.io/sc-smart-device/) for complete documentation.
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
 
@@ -1,4 +1,4 @@
1
- sc-foundation-services>=3.0.3
1
+ sc-foundation-services>=3.0.4
2
2
  requests>=2.32.0
3
3
 
4
4
  [all]
@@ -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, "Group") is None
50
- assert view.get_output_value(output_id, "Group", "Ungrouped") == "Ungrouped"
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():