Skip to content

Commit e89c0b2

Browse files
committed
driver/power/secomp: add support for Secomp PDUs
This PDU is sold and rebranded under various names, such as LogiLink PDU8P01 or Secomp 8016. This is a rather generic 19" PDU, tested was the 8-port version. It takes HTTP(s) with username and password; '0' turns a particular outlet to "on"; confirms by "applied". Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
1 parent 3912486 commit e89c0b2

1 file changed

Lines changed: 69 additions & 0 deletions

File tree

labgrid/driver/power/secomp.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""
2+
This driver was tested on the model: LogiLink PDU8P01
3+
but should be working on all similar devices implementing this SECOMP chip.
4+
5+
The driver is pretty simply and generic, it requires http(s), needs provided
6+
authentication and uses a '0' for on.
7+
"""
8+
import requests
9+
import xml.etree.ElementTree as ET
10+
from urllib.parse import urlparse
11+
from ..exception import ExecutionError
12+
13+
def _send_request(host, url_path, params=None):
14+
"""
15+
Helper to handle host string which might have credentials stripped
16+
or scheme changed
17+
"""
18+
if not host.startswith('http'):
19+
host = f"http://{host}"
20+
parsed = urlparse(host)
21+
22+
base_url = f"{parsed.scheme}://{parsed.netloc.split('@')[-1]}"
23+
full_url = f"{base_url}/{url_path}"
24+
25+
auth = None
26+
if parsed.username and parsed.password:
27+
auth = (parsed.username, parsed.password)
28+
29+
try:
30+
response = requests.get(full_url, params=params, auth=auth, timeout=10)
31+
if response.status_code == 401:
32+
raise ExecutionError(f"Secomp: Authentication failed (401) for {full_url}. Check YAML credentials.")
33+
response.raise_for_status()
34+
return response
35+
except requests.exceptions.RequestException as e:
36+
raise ExecutionError(f"Secomp: HTTP Request failed: {e}")
37+
38+
def power_set(host, port, index, value):
39+
"""
40+
host: the IP or URL
41+
port: the TCP port (usually 80)
42+
index: the outlet number (1-8)
43+
value: True (ON) or False (OFF)
44+
"""
45+
# Secomp 0816 logic: 0 is ON, 1 is OFF
46+
action_code = 0 if value else 1
47+
params = {
48+
f"outlet{int(index)-1}": 1,
49+
"op": action_code,
50+
"submit": "Apply"
51+
}
52+
53+
try:
54+
response = _send_request(host, "control_outlet.htm", params=params)
55+
if "Apply" not in response.text:
56+
raise ExecutionError(f"Secomp: PDU failed to set port {index}")
57+
except requests.exceptions.RequestException as e:
58+
raise ExecutionError(f"Secomp: HTTP Request failed: {e}")
59+
60+
def power_get(host, port, index):
61+
response = _send_request(host, "status.xml")
62+
try:
63+
xml = ET.fromstring(response.content)
64+
outlet_node = xml.find(f'outletStat{int(index)-1}')
65+
if outlet_node is None:
66+
raise ExecutionError(f"Secomp: Could not find status for port {index}")
67+
return outlet_node.text.lower() == "on"
68+
except Exception as e:
69+
raise ExecutionError(f"Secomp: Failed to get status: {e}")

0 commit comments

Comments
 (0)