embedded-lab-control 0.2.0__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 (64) hide show
  1. embedded_lab_control-0.2.0.dist-info/METADATA +855 -0
  2. embedded_lab_control-0.2.0.dist-info/RECORD +64 -0
  3. embedded_lab_control-0.2.0.dist-info/WHEEL +5 -0
  4. embedded_lab_control-0.2.0.dist-info/entry_points.txt +2 -0
  5. embedded_lab_control-0.2.0.dist-info/licenses/LICENSE +21 -0
  6. embedded_lab_control-0.2.0.dist-info/top_level.txt +1 -0
  7. labctl/__init__.py +8 -0
  8. labctl/actuators/__init__.py +56 -0
  9. labctl/actuators/base.py +183 -0
  10. labctl/actuators/lcus1.py +232 -0
  11. labctl/actuators/mock.py +81 -0
  12. labctl/actuators/runtime.py +1178 -0
  13. labctl/cli.py +7485 -0
  14. labctl/core/__init__.py +36 -0
  15. labctl/core/admin_files.py +388 -0
  16. labctl/core/audit.py +269 -0
  17. labctl/core/claim_keepalive.py +342 -0
  18. labctl/core/config.py +1068 -0
  19. labctl/core/database.py +880 -0
  20. labctl/core/images.py +503 -0
  21. labctl/core/manager.py +2815 -0
  22. labctl/core/models.py +815 -0
  23. labctl/core/safe_open.py +109 -0
  24. labctl/extras.py +128 -0
  25. labctl/health/__init__.py +43 -0
  26. labctl/health/alerts.py +275 -0
  27. labctl/health/checks.py +533 -0
  28. labctl/health/daemon.py +397 -0
  29. labctl/mcp_server.py +5254 -0
  30. labctl/power/__init__.py +13 -0
  31. labctl/power/base.py +145 -0
  32. labctl/power/kasa.py +446 -0
  33. labctl/power/shelly.py +89 -0
  34. labctl/power/tasmota.py +95 -0
  35. labctl/sdwire/__init__.py +10 -0
  36. labctl/sdwire/controller.py +1116 -0
  37. labctl/sdwire/power_guard.py +190 -0
  38. labctl/serial/__init__.py +18 -0
  39. labctl/serial/boot_test.py +342 -0
  40. labctl/serial/capture.py +430 -0
  41. labctl/serial/keys.py +89 -0
  42. labctl/serial/proxy.py +802 -0
  43. labctl/serial/ser2net.py +338 -0
  44. labctl/serial/udev.py +273 -0
  45. labctl/services.py +210 -0
  46. labctl/web/__init__.py +9 -0
  47. labctl/web/activity_broadcaster.py +204 -0
  48. labctl/web/api.py +1003 -0
  49. labctl/web/app.py +202 -0
  50. labctl/web/auth.py +97 -0
  51. labctl/web/claims.py +232 -0
  52. labctl/web/static/css/style.css +477 -0
  53. labctl/web/static/js/app.js +76 -0
  54. labctl/web/templates/activity.html +202 -0
  55. labctl/web/templates/base.html +49 -0
  56. labctl/web/templates/claim_refused.html +38 -0
  57. labctl/web/templates/console.html +237 -0
  58. labctl/web/templates/dashboard.html +100 -0
  59. labctl/web/templates/login.html +25 -0
  60. labctl/web/templates/sbc_detail.html +306 -0
  61. labctl/web/templates/settings.html +78 -0
  62. labctl/web/templates/status_history.html +65 -0
  63. labctl/web/views.py +496 -0
  64. labctl/web/websocket.py +208 -0
@@ -0,0 +1,95 @@
1
+ """
2
+ Tasmota power controller.
3
+
4
+ Controls Tasmota-flashed smart plugs via HTTP API.
5
+ """
6
+
7
+ import logging
8
+ from typing import Optional
9
+
10
+ import requests
11
+
12
+ from labctl.power.base import PowerController, PowerState
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class TasmotaController(PowerController):
18
+ """
19
+ Power controller for Tasmota devices.
20
+
21
+ Uses Tasmota HTTP API:
22
+ - Power On: http://<ip>/cm?cmnd=Power<index>%20On
23
+ - Power Off: http://<ip>/cm?cmnd=Power<index>%20Off
24
+ - Status: http://<ip>/cm?cmnd=Power<index>
25
+ """
26
+
27
+ def _command(self, cmnd: str) -> Optional[dict]:
28
+ """
29
+ Send command to Tasmota device.
30
+
31
+ Args:
32
+ cmnd: Command string (e.g., "Power1 On")
33
+
34
+ Returns:
35
+ JSON response dict or None on error
36
+ """
37
+ url = f"http://{self.address}/cm"
38
+ params = {"cmnd": cmnd}
39
+
40
+ try:
41
+ response = requests.get(url, params=params, timeout=self.timeout)
42
+ response.raise_for_status()
43
+ return response.json()
44
+ except Exception as e:
45
+ logger.warning(
46
+ "Tasmota command '%s' failed for %s: %s", cmnd, self.address, e
47
+ )
48
+ return None
49
+
50
+ def _power_key(self) -> str:
51
+ """Get the power key for responses (POWER or POWER1, POWER2, etc.)."""
52
+ if self.plug_index == 1:
53
+ return "POWER"
54
+ return f"POWER{self.plug_index}"
55
+
56
+ def power_on(self) -> bool:
57
+ """Turn power on."""
58
+ cmnd = f"Power{self.plug_index} On"
59
+ result = self._command(cmnd)
60
+
61
+ if result is None:
62
+ return False
63
+
64
+ # Tasmota returns {"POWER": "ON"} or {"POWER1": "ON"}
65
+ power_key = self._power_key()
66
+ return result.get(power_key, "").upper() == "ON"
67
+
68
+ def power_off(self) -> bool:
69
+ """Turn power off."""
70
+ cmnd = f"Power{self.plug_index} Off"
71
+ result = self._command(cmnd)
72
+
73
+ if result is None:
74
+ return False
75
+
76
+ power_key = self._power_key()
77
+ return result.get(power_key, "").upper() == "OFF"
78
+
79
+ def get_state(self) -> PowerState:
80
+ """Get current power state."""
81
+ cmnd = f"Power{self.plug_index}"
82
+ result = self._command(cmnd)
83
+
84
+ if result is None:
85
+ return PowerState.UNKNOWN
86
+
87
+ power_key = self._power_key()
88
+ state = result.get(power_key, "").upper()
89
+
90
+ if state == "ON":
91
+ return PowerState.ON
92
+ elif state == "OFF":
93
+ return PowerState.OFF
94
+ else:
95
+ return PowerState.UNKNOWN
@@ -0,0 +1,10 @@
1
+ """
2
+ SDWire SD card multiplexer support for lab controller.
3
+
4
+ Controls SDWireC devices to switch SD cards between DUT (target SBC)
5
+ and host (dev machine) for automated flashing.
6
+ """
7
+
8
+ from labctl.sdwire.controller import SDWireController
9
+
10
+ __all__ = ["SDWireController"]