Skip to content

Commit 4a51961

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 <[email protected]>
1 parent 0b3babf commit 4a51961

File tree

13 files changed

+212
-3
lines changed

13 files changed

+212
-3
lines changed

doc/configuration.rst

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,28 @@ Arguments:
410410
Used by:
411411
- `TasmotaPowerDriver`_
412412

413+
TenmaSerialPort
414+
++++++++++++++++
415+
A :any:`TenmaSerialPort` describes a *Tenma* as supported by
416+
`tenma-serial <https://github.com/kxtells/tenma-serial>`_.
417+
418+
.. code-block:: yaml
419+
420+
TenmaSerialPort:
421+
match:
422+
ID_PATH: pci-0000:00:15.0-usb-0:3.2:1.0
423+
index: 1
424+
425+
426+
The example describes port 1 on the hub with the ID_PATH
427+
``pci-0000:00:15.0-usb-0:3.2:1.0``.
428+
429+
Arguments:
430+
- match (dict): key and value pairs for a udev match, see `udev Matching`_
431+
432+
Used by:
433+
- `TenmaSerialDriver`_
434+
413435
Digital Outputs
414436
~~~~~~~~~~~~~~~
415437

@@ -2293,6 +2315,36 @@ Implements:
22932315
Arguments:
22942316
- delay (float, default=2.0): delay in seconds between off and on
22952317

2318+
TenmaSerialDriver
2319+
~~~~~~~~~~~~~~~~~~
2320+
A :any:`TenmaSerialDriver` controls a `TenmaSerialPort`_, allowing control of the
2321+
target power state without user interaction.
2322+
2323+
Binds to:
2324+
port:
2325+
- `TenmaSerialPort`_
2326+
- NetworkTenmaSerialPort
2327+
2328+
Implements:
2329+
- :any:`PowerProtocol`
2330+
- :any:`ResetProtocol`
2331+
2332+
.. code-block:: yaml
2333+
2334+
TenmaSerialDriver:
2335+
delay: 10.0
2336+
ovp: true
2337+
ocp: true
2338+
voltage: 12000
2339+
current: 2000
2340+
2341+
Arguments:
2342+
- delay (float, default=2.0): delay in seconds between off and on
2343+
- ovp (bool, default=False): overvoltage protection
2344+
- ocp (bool, default=False): overcurrent protection
2345+
- voltage (int, default=12000): set mV
2346+
- current (int, default=2000): set mA
2347+
22962348
TasmotaPowerDriver
22972349
~~~~~~~~~~~~~~~~~~
22982350
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
@@ -109,6 +110,81 @@ def get(self):
109110
return False
110111
raise ExecutionError(f"Did not find port status in sispmctl output ({repr(output)})")
111112

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

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

labgrid/remote/client.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -883,7 +883,12 @@ def power(self):
883883
name = self.args.name
884884
target = self._get_target(place)
885885
from ..resource.power import NetworkPowerPort, PDUDaemonPort
886-
from ..resource.remote import NetworkUSBPowerPort, NetworkSiSPMPowerPort, NetworkSysfsGPIO
886+
from ..resource.remote import (
887+
NetworkUSBPowerPort,
888+
NetworkSiSPMPowerPort,
889+
NetworkSysfsGPIO,
890+
NetworkTenmaSerialPort,
891+
)
887892
from ..resource import TasmotaPowerPort, NetworkYKUSHPowerPort
888893

889894
drv = None
@@ -899,6 +904,8 @@ def power(self):
899904
drv = self._get_driver_or_new(target, "USBPowerDriver", name=name)
900905
elif isinstance(resource, NetworkSiSPMPowerPort):
901906
drv = self._get_driver_or_new(target, "SiSPMPowerDriver", name=name)
907+
elif isinstance(resource, NetworkTenmaSerialPort):
908+
drv = self._get_driver_or_new(target, "TenmaSerialDriver", name=name)
902909
elif isinstance(resource, PDUDaemonPort):
903910
drv = self._get_driver_or_new(target, "PDUDaemonDriver", name=name)
904911
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"""
@@ -564,6 +585,7 @@ def __attrs_post_init__(self):
564585
exports["USBAudioInput"] = USBAudioInputExport
565586
exports["USBTMC"] = USBGenericExport
566587
exports["SiSPMPowerPort"] = SiSPMPowerPortExport
588+
exports["TenmaSerialPort"] = TenmaSerialExport
567589
exports["USBPowerPort"] = USBPowerPortExport
568590
exports["DeditecRelais8"] = USBDeditecRelaisExport
569591
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
@@ -260,6 +260,16 @@ def __attrs_post_init__(self):
260260
super().__attrs_post_init__()
261261

262262

263+
@target_factory.reg_resource
264+
@attr.s(eq=False)
265+
class NetworkTenmaSerialPort(RemoteUSBResource):
266+
"""The TenmaSerialPort describes a remotely accessible tenma-contro power port"""
267+
index = attr.ib(default=None, validator=attr.validators.instance_of(int))
268+
def __attrs_post_init__(self):
269+
self.timeout = 10.0
270+
super().__attrs_post_init__()
271+
272+
263273
@target_factory.reg_resource
264274
@attr.s(eq=False)
265275
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
AlteraUSBBlaster,
@@ -45,6 +46,7 @@ def __init__(self, args):
4546
self.resources.append(IMXUSBLoader(**args))
4647
self.resources.append(AndroidUSBFastboot(**args))
4748
self.resources.append(DFUDevice(**args))
49+
self.resources.append(TenmaSerialPort(**args))
4850
self.resources.append(USBMassStorage(**args))
4951
self.resources.append(USBSDMuxDevice(**args))
5052
self.resources.append(USBSDWireDevice(**args))

labgrid/resource/udev.py

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

709+
@target_factory.reg_resource
710+
@attr.s(eq=False)
711+
class TenmaSerialPort(USBResource):
712+
"""This resource describes a tenma-serial power port"""
713+
714+
def __attrs_post_init__(self):
715+
self.match['SUBSYSTEM'] = 'tty'
716+
super().__attrs_post_init__()
717+
718+
@property
719+
def path(self):
720+
if self.device is not None:
721+
return self.device.device_node
722+
723+
return None
709724

710725
@target_factory.reg_resource
711726
@attr.s(eq=False)

man/labgrid-device-config.5

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,10 @@ See: <https://github.com/openssh/openssh\-portable>
156156
Path to the sshfs binary, used by the SSHDriver.
157157
See: <https://github.com/libfuse/sshfs>
158158
.TP
159+
.B \fBtenma\-serial\fP
160+
Path to the tenma\-control binary, used by the TenmaSerialDriver.
161+
See: <https://github.com/kxtells/tenma\-serial>
162+
.TP
159163
.B \fBuhubctl\fP
160164
Path to the uhubctl binary, used by the USBPowerDriver.
161165
See: <https://github.com/mvp/uhubctl>

0 commit comments

Comments
 (0)