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
39 changes: 6 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
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
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,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you use close_port_after_each_call=True?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I remember correctly: closing after each call leaves the port accessible for other users, otherwise it is locked.

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)
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):
Comment thread
flxzt marked this conversation as resolved.
"""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
Loading