Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions doc/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,11 @@ Currently available are:
<https://github.com/labgrid-project/labgrid/blob/master/labgrid/driver/power/rest.py>`__
for details.

``secomp``
Controls *Secomp* PDUs via HTTP. This is a 19" Rackmount PDU sold and
rebranded under various names e.g. VALUE IP, Argus, Intellinet, or
LogiLink 8p01.

``sentry``
Controls *Sentry PDUs* via SNMP using Sentry3-MIB.
It was tested on *CW-24VDD* and *4805-XLS-16*.
Expand Down
69 changes: 69 additions & 0 deletions labgrid/driver/power/secomp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""
This driver was tested on the model: LogiLink PDU8P01
but should be working on all similar devices implementing this SECOMP chip.

The driver is pretty simply and generic, it requires http(s), needs provided
authentication and uses a '0' for on.
"""
import requests
import xml.etree.ElementTree as ET
from urllib.parse import urlparse
from ..exception import ExecutionError

def _send_request(host, url_path, params=None):
"""
Helper to handle host string which might have credentials stripped
or scheme changed
"""
if not host.startswith('http'):
host = f"http://{host}"
parsed = urlparse(host)

base_url = f"{parsed.scheme}://{parsed.netloc.split('@')[-1]}"
full_url = f"{base_url}/{url_path}"

auth = None
if parsed.username and parsed.password:
auth = (parsed.username, parsed.password)

try:
response = requests.get(full_url, params=params, auth=auth, timeout=10)
if response.status_code == 401:
raise ExecutionError(f"Secomp: Authentication failed (401) for {full_url}. Check YAML credentials.")
response.raise_for_status()
return response
except requests.exceptions.RequestException as e:
raise ExecutionError(f"Secomp: HTTP Request failed: {e}")

def power_set(host, port, index, value):
"""
host: the IP or URL
port: the TCP port (usually 80)
index: the outlet number (1-8)
value: True (ON) or False (OFF)
"""
# Secomp 0816 logic: 0 is ON, 1 is OFF
action_code = 0 if value else 1
params = {
f"outlet{int(index)-1}": 1,
"op": action_code,
"submit": "Apply"
}

try:
response = _send_request(host, "control_outlet.htm", params=params)
if "Apply" not in response.text:
raise ExecutionError(f"Secomp: PDU failed to set port {index}")
except requests.exceptions.RequestException as e:
raise ExecutionError(f"Secomp: HTTP Request failed: {e}")

def power_get(host, port, index):
response = _send_request(host, "status.xml")
try:
xml = ET.fromstring(response.content)
outlet_node = xml.find(f'outletStat{int(index)-1}')
if outlet_node is None:
raise ExecutionError(f"Secomp: Could not find status for port {index}")
return outlet_node.text.lower() == "on"
except Exception as e:
raise ExecutionError(f"Secomp: Failed to get status: {e}")