Skip to content

Commit 5679921

Browse files
author
Nico Orlando
committed
driver: Add TenmaSerial power driver and integration support
This patch introduces a new power driver, TenmaSerial, providing control support for Tenma power devices. Integration includes: - Implementation of TenmaSerial in powerdriver.py - Exporter and remote client support for the new driver - Configuration documentation in configuration.rst - New unit tests added in test_tenmaserial.py - Minor updates to pyproject.toml and man pages This initial draft enables basic functionality and lays the groundwork for full Tenma device intration Signed-off-by: Nico Orlando <nico.orlando@dave.eu>
1 parent df51556 commit 5679921

12 files changed

Lines changed: 208 additions & 2 deletions

File tree

doc/configuration.rst

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,28 @@ Arguments:
420420
Used by:
421421
- `TasmotaPowerDriver`_
422422

423+
TenmaSerialPort
424+
++++++++++++++++
425+
A :any:`TenmaSerialPort` describes a *Tenma* as supported by
426+
`tenma-serial <https://github.com/kxtells/tenma-serial>`_.
427+
428+
.. code-block:: yaml
429+
430+
TenmaSerialPort:
431+
match:
432+
ID_PATH: pci-0000:00:15.0-usb-0:3.2:1.0
433+
index: 1
434+
435+
436+
The example describes port 1 on the hub with the ID_PATH
437+
``pci-0000:00:15.0-usb-0:3.2:1.0``.
438+
439+
Arguments:
440+
- match (dict): key and value pairs for a udev match, see `udev Matching`_
441+
442+
Used by:
443+
- `TenmaSerialDriver`_
444+
423445
Digital Outputs
424446
~~~~~~~~~~~~~~~
425447

@@ -2336,6 +2358,36 @@ Implements:
23362358
Arguments:
23372359
- delay (float, default=2.0): delay in seconds between off and on
23382360

2361+
TenmaSerialDriver
2362+
~~~~~~~~~~~~~~~~~~
2363+
A :any:`TenmaSerialDriver` controls a `TenmaSerialPort`_, allowing control of the
2364+
target power state without user interaction.
2365+
2366+
Binds to:
2367+
port:
2368+
- `TenmaSerialPort`_
2369+
- NetworkTenmaSerialPort
2370+
2371+
Implements:
2372+
- :any:`PowerProtocol`
2373+
- :any:`ResetProtocol`
2374+
2375+
.. code-block:: yaml
2376+
2377+
TenmaSerialDriver:
2378+
delay: 10.0
2379+
ovp: true
2380+
ocp: true
2381+
voltage: 12000
2382+
current: 2000
2383+
2384+
Arguments:
2385+
- delay (float, default=2.0): delay in seconds between off and on
2386+
- ovp (bool, default=False): overvoltage protection
2387+
- ocp (bool, default=False): overcurrent protection
2388+
- voltage (int, default=12000): set mV
2389+
- current (int, default=2000): set mA
2390+
23392391
TasmotaPowerDriver
23402392
~~~~~~~~~~~~~~~~~~
23412393
A :any:`TasmotaPowerDriver` controls a `TasmotaPowerPort`_, allowing the outlet

labgrid/driver/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from .powerdriver import ManualPowerDriver, ExternalPowerDriver, \
1616
DigitalOutputPowerDriver, YKUSHPowerDriver, \
1717
USBPowerDriver, SiSPMPowerDriver, NetworkPowerDriver, \
18-
PDUDaemonDriver
18+
PDUDaemonDriver, TenmaSerialDriver
1919
from .usbloader import MXSUSBDriver, IMXUSBDriver, BDIMXUSBDriver, RKUSBDriver, UUUDriver
2020
from .usbsdmuxdriver import USBSDMuxDriver
2121
from .usbsdwiredriver import USBSDWireDriver

labgrid/driver/powerdriver.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import shlex
22
import time
33
import math
4+
import ast
45
from importlib import import_module
56

67
import attr
@@ -110,6 +111,81 @@ def get(self):
110111
return False
111112
raise ExecutionError(f"Did not find port status in sispmctl output ({repr(output)})")
112113

114+
@target_factory.reg_driver
115+
@attr.s(eq=False)
116+
class TenmaSerialDriver(Driver, PowerResetMixin, PowerProtocol):
117+
"""TenmaSerialDriver - Driver using a Single Output Programmable to control a
118+
target's power using the tenma-serial tool https://github.com/kxtells/tenma-serial/"""
119+
120+
bindings = {"port": {"TenmaSerialPort", "NetworkTenmaSerialPort"}, }
121+
delay = attr.ib(default=2.0, validator=attr.validators.instance_of(float))
122+
ovp = attr.ib(default=False, validator=attr.validators.instance_of(bool))
123+
ocp = attr.ib(default=False, validator=attr.validators.instance_of(bool))
124+
voltage = attr.ib(default=12000, validator=attr.validators.instance_of(int))
125+
current = attr.ib(default=2000, validator=attr.validators.instance_of(int))
126+
127+
def __attrs_post_init__(self):
128+
super().__attrs_post_init__()
129+
if self.target.env:
130+
self.tool = self.target.env.config.get_tool('tenma-control')
131+
else:
132+
self.tool = 'tenma-control'
133+
134+
def _get_tenmaserial_prefix(self):
135+
options = []
136+
137+
# overvoltage protection (bool)
138+
if self.ovp:
139+
options.append('--ovp-enable')
140+
else:
141+
options.append('--ovp-disable')
142+
143+
# overcurrent protection (bool)
144+
if self.ocp:
145+
options.append('--ocp-enable')
146+
else:
147+
options.append('--ocp-disable')
148+
149+
# set mV (int)
150+
options.append(f'-v {self.voltage}')
151+
152+
# set mA (int)
153+
options.append(f'-c {self.current}')
154+
155+
return self.port.command_prefix + [
156+
self.tool,
157+
str(self.port.path),
158+
] + options
159+
160+
@Driver.check_active
161+
@step()
162+
def on(self):
163+
cmd = ['--on']
164+
processwrapper.check_output(self._get_tenmaserial_prefix() + cmd)
165+
166+
@Driver.check_active
167+
@step()
168+
def off(self):
169+
cmd = ['--off']
170+
processwrapper.check_output(self._get_tenmaserial_prefix() + cmd)
171+
172+
@Driver.check_active
173+
@step()
174+
def cycle(self):
175+
self.off()
176+
time.sleep(self.delay)
177+
self.on()
178+
179+
@Driver.check_active
180+
@step()
181+
def get(self):
182+
cmd = ['-S']
183+
output = processwrapper.check_output(self._get_tenmaserial_prefix() + cmd)
184+
status = ast.literal_eval(output.decode('utf-8').strip().splitlines()[1])
185+
if status['outEnabled']:
186+
return True
187+
else:
188+
return False
113189

114190
@target_factory.reg_driver
115191
@attr.s(eq=False)

labgrid/remote/client.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -938,7 +938,12 @@ def power(self):
938938
name = self.args.name
939939
target = self._get_target(place)
940940
from ..resource.power import NetworkPowerPort, PDUDaemonPort
941-
from ..resource.remote import NetworkUSBPowerPort, NetworkSiSPMPowerPort, NetworkSysfsGPIO
941+
from ..resource.remote import (
942+
NetworkUSBPowerPort,
943+
NetworkSiSPMPowerPort,
944+
NetworkSysfsGPIO,
945+
NetworkTenmaSerialPort,
946+
)
942947
from ..resource import TasmotaPowerPort, NetworkYKUSHPowerPort
943948

944949
drv = None
@@ -954,6 +959,8 @@ def power(self):
954959
drv = self._get_driver_or_new(target, "USBPowerDriver", name=name)
955960
elif isinstance(resource, NetworkSiSPMPowerPort):
956961
drv = self._get_driver_or_new(target, "SiSPMPowerDriver", name=name)
962+
elif isinstance(resource, NetworkTenmaSerialPort):
963+
drv = self._get_driver_or_new(target, "TenmaSerialDriver", name=name)
957964
elif isinstance(resource, PDUDaemonPort):
958965
drv = self._get_driver_or_new(target, "PDUDaemonDriver", name=name)
959966
elif isinstance(resource, TasmotaPowerPort):

labgrid/remote/exporter.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,27 @@ def release(self, *args, **kwargs):
184184
self.poll()
185185

186186

187+
@attr.s(eq=False)
188+
class TenmaSerialExport(ResourceExport):
189+
def __attrs_post_init__(self):
190+
super().__attrs_post_init__()
191+
192+
def _get_params(self):
193+
"""Helper function to return parameters"""
194+
return {
195+
"host": self.host,
196+
"busnum": self.local.busnum,
197+
"devnum": self.local.devnum,
198+
"path": self.local.path,
199+
"vendor_id": self.local.vendor_id,
200+
"model_id": self.local.model_id,
201+
"index": self.local.index,
202+
}
203+
204+
205+
exports["TenmaSerialPort"] = TenmaSerialExport
206+
207+
187208
@attr.s(eq=False)
188209
class SerialPortExport(ResourceExport):
189210
"""ResourceExport for a USB or Raw SerialPort"""
@@ -586,6 +607,7 @@ def __attrs_post_init__(self):
586607
exports["USBAudioInput"] = USBAudioInputExport
587608
exports["USBTMC"] = USBGenericExport
588609
exports["SiSPMPowerPort"] = SiSPMPowerPortExport
610+
exports["TenmaSerialPort"] = TenmaSerialExport
589611
exports["USBPowerPort"] = USBPowerPortExport
590612
exports["DeditecRelais8"] = USBDeditecRelaisExport
591613
exports["HIDRelay"] = USBHIDRelayExport

labgrid/resource/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
AndroidUSBFastboot,
1313
DFUDevice,
1414
DeditecRelais8,
15+
TenmaSerialPort,
1516
HIDRelay,
1617
IMXUSBLoader,
1718
LXAUSBMux,

labgrid/resource/remote.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,16 @@ def __attrs_post_init__(self):
272272
super().__attrs_post_init__()
273273

274274

275+
@target_factory.reg_resource
276+
@attr.s(eq=False)
277+
class NetworkTenmaSerialPort(RemoteUSBResource):
278+
"""The TenmaSerialPort describes a remotely accessible tenma-contro power port"""
279+
index = attr.ib(default=None, validator=attr.validators.instance_of(int))
280+
def __attrs_post_init__(self):
281+
self.timeout = 10.0
282+
super().__attrs_post_init__()
283+
284+
275285
@target_factory.reg_resource
276286
@attr.s(eq=False)
277287
class NetworkUSBPowerPort(RemoteUSBResource):

labgrid/resource/suggest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
IMXUSBLoader,
1313
AndroidUSBFastboot,
1414
DFUDevice,
15+
TenmaSerialPort,
1516
USBSDMuxDevice,
1617
USBSDWireDevice,
1718
USBSDWire3Device,
@@ -46,6 +47,7 @@ def __init__(self, args):
4647
self.resources.append(IMXUSBLoader(**args))
4748
self.resources.append(AndroidUSBFastboot(**args))
4849
self.resources.append(DFUDevice(**args))
50+
self.resources.append(TenmaSerialPort(**args))
4951
self.resources.append(USBMassStorage(**args))
5052
self.resources.append(USBSDMuxDevice(**args))
5153
self.resources.append(USBSDWireDevice(**args))

labgrid/resource/udev.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,21 @@ def __attrs_post_init__(self):
755755
self.match['ID_MODEL'] = 'DEDITEC_USB-OPT_REL-8'
756756
super().__attrs_post_init__()
757757

758+
@target_factory.reg_resource
759+
@attr.s(eq=False)
760+
class TenmaSerialPort(USBResource):
761+
"""This resource describes a tenma-serial power port"""
762+
763+
def __attrs_post_init__(self):
764+
self.match['SUBSYSTEM'] = 'tty'
765+
super().__attrs_post_init__()
766+
767+
@property
768+
def path(self):
769+
if self.device is not None:
770+
return self.device.device_node
771+
772+
return None
758773

759774
@target_factory.reg_resource
760775
@attr.s(eq=False)

man/labgrid-device-config.5

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,10 @@ See: \X'tty: link https://github.com/openssh/openssh-portable'\fI\%https://githu
157157
Path to the sshfs binary, used by the SSHDriver.
158158
See: \X'tty: link https://github.com/libfuse/sshfs'\fI\%https://github.com/libfuse/sshfs\fP\X'tty: link'
159159
.TP
160+
.B \fBtenma\-serial\fP
161+
Path to the tenma\-control binary, used by the TenmaSerialDriver.
162+
See: <https://github.com/kxtells/tenma\-serial>
163+
.TP
160164
.B \fBuhubctl\fP
161165
Path to the uhubctl binary, used by the USBPowerDriver.
162166
See: \X'tty: link https://github.com/mvp/uhubctl'\fI\%https://github.com/mvp/uhubctl\fP\X'tty: link'

0 commit comments

Comments
 (0)