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
65 changes: 32 additions & 33 deletions doc/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,34 +68,6 @@ Arguments:

Used by:
- `SerialDriver`_

ModbusRTU
+++++++++
A :any:`ModbusRTU` resource is required to use the `ModbusRTUDriver`_.
`Modbus RTU <https://en.wikipedia.org/wiki/Modbus>`_ is a communication
protocol used to control many different kinds of electronic systems, such as
thermostats, power plants, etc.
Modbus is normally implemented on top of RS-485, though this is not strictly
necessary, as long as the Modbus network only has one master (and up to 256
slaves).

This resource only supports local usage and will not work with an exporter.

.. code-block:: yaml

ModbusRTU:
port: '/dev/ttyUSB0'
address: 16
speed: 115200
timeout: 0.25

Arguments:
- port (str): tty the instrument is connected to, e.g. ``/dev/ttyUSB0``
- address (int): slave address on the modbus, e.g. 16
- speed (int, default=115200): baud rate of the serial port
- timeout (float, default=0.25): timeout in seconds

Used by:
- `ModbusRTUDriver`_

USBSerialPort
Expand Down Expand Up @@ -2007,9 +1979,7 @@ Arguments:

ModbusRTUDriver
~~~~~~~~~~~~~~~
A :any:`ModbusRTUDriver` connects to a ModbusRTU resource. This driver only
supports local usage and will not work with an exporter.

A :any:`ModbusRTUDriver` connects to a SerialPort or NetworkSerialPort resource.
The driver is implemented using the
`minimalmodbus <https://minimalmodbus.readthedocs.io/en/stable/>`_ Python
library.
Expand All @@ -2022,13 +1992,16 @@ network.

Binds to:
resource:
- `ModbusRTU`_
- `NetworkSerialPort`_
- `RawSerialPort`_
- `USBSerialPort`_

Implements:
- None (yet)

Arguments:
- None
- address (int): slave address on the modbus, e.g. 16
- timeout (float): 0.5

ShellDriver
~~~~~~~~~~~
Expand Down Expand Up @@ -2791,6 +2764,32 @@ Implements:
Arguments:
- None

WaveshareRTURelaisDriver
~~~~~~~~~~~~~~~~~~~~~~~~
A :any:`WaveshareRTURelaisDriver` controls a *Waveshare* Modbus-RTU relay.

Binds to:
relais:
- `NetworkSerialPort`_
- `RawSerialPort`_
- `USBSerialPort`_

Implements:
- :any:`DigitalOutputProtocol`

.. code-block:: yaml

WaveshareRTURelaisDriver:
address: 1
relais: 2
no_channel: 8

Arguments:
- address (int): slave address on the modbus, e.g. 16
- timeout (float): 0.5
- relais (int): the register address/index of the relais channel that will be controlled
- no_channel (int, default=8): the total number of channels on this relay

MXSUSBDriver
~~~~~~~~~~~~
An :any:`MXSUSBDriver` is used to upload an image into a device in the *MXS USB
Expand Down
8 changes: 4 additions & 4 deletions examples/modbusrtu/env.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
targets:
main:
resources:
ModbusRTU:
SerialPort:
port: "/dev/ttyUSB0"
address: 16
speed: 115200
timeout: 0.25
drivers:
ModbusRTUDriver: {}
ModbusRTUDriver:
address: 16
timeout: 0.25
1 change: 1 addition & 0 deletions labgrid/driver/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,4 @@
LAAButtonDriver, LAALedDriver, LAATempDriver, LAAWattDriver, \
LAAProviderDriver
from .adb import ADBDriver
from .wavesharerturelaisdriver import WaveshareRTURelaisDriver
91 changes: 72 additions & 19 deletions labgrid/driver/modbusrtudriver.py
Original file line number Diff line number Diff line change
@@ -1,54 +1,107 @@
from importlib import import_module

import attr
import serial
import serial.rfc2217

from ..factory import target_factory
from ..resource import SerialPort
from ..util.proxy import proxymanager
from .common import Driver


@target_factory.reg_driver
@attr.s(eq=False)
class ModbusRTUDriver(Driver):
bindings = {"resource": "ModbusRTU"}
bindings = {"resource": {"SerialPort", "NetworkSerialPort"}}

timeout = attr.ib(default=0.25, validator=attr.validators.instance_of(float))
address = attr.ib(default=0, validator=attr.validators.instance_of(int))

def __attrs_post_init__(self):
super().__attrs_post_init__()
self._modbus = import_module('minimalmodbus')
self._modbus = import_module("minimalmodbus")
self.instrument = None

def on_activate(self):
self.instrument = self._modbus.Instrument(
self.resource.port,
self.resource.address,
debug=False)
if isinstance(self.resource, SerialPort):
self.instrument = self._modbus.Instrument(
self.resource.port, self.address, debug=False
)
else:
if self.resource.protocol == "rfc2217":
serial_if = serial.rfc2217.Serial()
elif self.resource.protocol == "raw":
serial_if = serial.serial_for_url("socket://", do_not_open=True)
else:
raise Exception("ModbusRTUDriver: unknown protocol")

host, port = proxymanager.get_host_and_port(self.resource)
if self.resource.protocol == "rfc2217":
serial_if.port = (
f"rfc2217://{host}:{port}?ign_set_control&timeout={self.timeout}"
)
elif self.resource.protocol == "raw":
serial_if.port = f"socket://{host}:{port}/"
else:
raise Exception("ModbusRTUDriver: unknown protocol")
serial_if.baudrate = self.resource.speed
serial_if.open()

self.instrument = self._modbus.Instrument(
serial_if,
slaveaddress=self.address,
close_port_after_each_call=True,
debug=False,
)

self.instrument.serial.baudrate = self.resource.speed
self.instrument.serial.timeout = self.resource.timeout
self.instrument.serial.timeout = self.timeout

self.instrument.mode = self._modbus.MODE_RTU
self.instrument.clear_buffers_before_each_transaction = True

def on_deactivate(self):
self.instrument = None

def read_register(self, *args, **kwargs):
return self.instrument.read_register(*args, **kwargs)

def write_register(self, *args, **kwargs):
return self.instrument.write_register(*args, **kwargs)

def read_registers(self, *args, **kwargs):
return self.instrument.read_registers(*args, **kwargs)

def write_registers(self, *args, **kwargs):
return self.instrument.write_registers(*args, **kwargs)

def read_bit(self, *args, **kwargs):
return self.instrument.read_bit(*args, **kwargs)

def write_bit(self, *args, **kwargs):
return self.instrument.write_bit(*args, **kwargs)

def read_bits(self, *args, **kwargs):
return self.instrument.read_bits(*args, **kwargs)

def write_bits(self, *args, **kwargs):
return self.instrument.write_bits(*args, **kwargs)

def read_long(self, *args, **kwargs):
return self.instrument.read_long(*args, **kwargs)

def write_long(self, *args, **kwargs):
return self.instrument.write_long(*args, **kwargs)

def read_float(self, *args, **kwargs):
return self.instrument.read_float(*args, **kwargs)

def write_float(self, *args, **kwargs):
return self.instrument.write_float(*args, **kwargs)

def read_string(self, *args, **kwargs):
return self.instrument.read_string(*args, **kwargs)

def write_string(self, *args, **kwargs):
return self.instrument.write_string(*args, **kwargs)

def read_register(self, *args, **kwargs):
return self.instrument.read_register(*args, **kwargs)

def write_register(self, *args, **kwargs):
return self.instrument.write_register(*args, **kwargs)

def read_registers(self, *args, **kwargs):
return self.instrument.read_registers(*args, **kwargs)

def write_registers(self, *args, **kwargs):
return self.instrument.write_registers(*args, **kwargs)
37 changes: 37 additions & 0 deletions labgrid/driver/wavesharerturelaisdriver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import attr

from .common import Driver
from .modbusrtudriver import ModbusRTUDriver
from ..factory import target_factory
from ..step import step
from ..protocol import DigitalOutputProtocol


@target_factory.reg_driver
@attr.s(eq=False)
class WaveshareRTURelaisDriver(ModbusRTUDriver, DigitalOutputProtocol):
bindings = {"resource": {"SerialPort", "NetworkSerialPort"}}

relais = attr.ib(default=0, validator=attr.validators.instance_of(int))
no_channel = attr.ib(default=8, validator=attr.validators.instance_of(int))

def __attrs_post_init__(self):
super().__attrs_post_init__()

def on_activate(self):
super().on_activate()

def on_deactivate(self):
super().on_deactivate()

@Driver.check_active
@step(args=["status"])
def set(self, status):
_status = 1 if status else 0
self.write_bit(self.relais, _status)

@Driver.check_active
@step(result=True)
def get(self):
status = self.read_bits(0x00, number_of_bits=self.no_channel, functioncode=1)
return status[self.relais]
31 changes: 10 additions & 21 deletions labgrid/resource/modbusrtu.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,18 @@
import warnings

import attr

from ..factory import target_factory
from .common import Resource
from .base import SerialPort


@target_factory.reg_resource
@attr.s(eq=False)
class ModbusRTU(SerialPort, Resource):
"""This resource describes Modbus RTU instrument.

Args:
port (str): tty the instrument is connected to, e.g. '/dev/ttyUSB0'
speed (int): optional, default is 115200
address (int): slave address on the modbus, e.g. 16
timeout (float): optional, timeout in seconds. Default is 0.25 s
"""

address = attr.ib(default=None, validator=attr.validators.instance_of(int))
timeout = attr.ib(default=0.25,
validator=attr.validators.instance_of(float))

def __attrs_post_init__(self):
super().__attrs_post_init__()
if self.port is None:
raise ValueError("ModbusRTU must be configured with a port")
if self.address is None:
raise ValueError("ModbusRTU must be configured with an slave address")
class ModbusRTU():
def __new__(cls, *args, **kwargs):
warnings.warn(
"The ModbusRTU class is deprecated. Use SerialPort instead.",
DeprecationWarning,
stacklevel=2,
)
return SerialPort(*args, **kwargs)
33 changes: 9 additions & 24 deletions tests/test_modbusrtudriver.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,20 @@
from labgrid.resource.modbusrtu import ModbusRTU
from labgrid.driver.modbusrtudriver import ModbusRTUDriver

import pytest

def test_resource_with_minimum_argument(target):
dut = ModbusRTU(target, name=None, port="/dev/tty1", address=10)

assert dut.port == "/dev/tty1"
assert dut.address == 10
assert dut.speed == 115200
assert dut.timeout == 0.25


def test_resource_with_non_default_argument(target):
dut = ModbusRTU(target, name=None, port="/dev/tty1", address=10,
speed=9600, timeout=0.5)

assert dut.port == "/dev/tty1"
assert dut.address == 10
assert dut.speed == 9600
assert dut.timeout == 0.5
from labgrid.driver.modbusrtudriver import ModbusRTUDriver
from labgrid.resource.serialport import SerialPort


def test_driver(target, mocker):
pytest.importorskip("minimalmodbus")
mocker.patch('serial.Serial')
mocker.patch("serial.Serial")

SerialPort(target, name=None, port="/dev/tty0")
driver = ModbusRTUDriver(target, address=10, timeout=0.5, name=None)

ModbusRTU(target, name=None, port="/dev/tty0", address=10)
driver = ModbusRTUDriver(target, name=None)
assert driver.address == 10
assert driver.timeout == 0.5

target.activate(driver)

assert driver.instrument.serial.baudrate == 115200
assert driver.instrument.serial.timeout == 0.25
assert driver.instrument.serial.timeout == 0.5
23 changes: 23 additions & 0 deletions tests/test_wavesharerturelaisdriver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from labgrid.resource.serialport import SerialPort
from labgrid.driver.wavesharerturelaisdriver import WaveshareRTURelaisDriver

import pytest


def test_wavesharerturelais_driver(target, mocker):
pytest.importorskip("minimalmodbus")
mocker.patch("serial.Serial")

SerialPort(target, name=None, port="/dev/tty0")
driver = WaveshareRTURelaisDriver(
target, address=0x01, relais=3, no_channel=32, timeout=0.5, name=None
)

assert driver.address == 0x01
assert driver.relais == 3
assert driver.no_channel == 32

target.activate(driver)

assert driver.instrument.serial.baudrate == 115200
assert driver.instrument.serial.timeout == 0.5
Loading