Modbus – protokół komunikacyjny stworzony przez firmę Modicon. Służy do komunikacji z programowalnymi kontrolerami tej firmy, a także innych producentów. Umożliwia zarządzanie siecią takich urządzeń jak np. system sterowania temperatury i wilgotności. Powstały wersje dla portu szeregowego i dla sieci IP. W sieciach IP używany jest protokół TCP na porcie 502. Modbus jest protokołem typu Master-Slave. Modbus jest obecnie standardem otwartym.
Poniżej znajduje się struktura systemu

Rodzaje protokołów komunikacyjnych Modbus:
Rodzaje protokołów komunikacyjnych Modbus:
1. Modbus TCP/IP – klasyczny Ethernet TCP/IP o szybkości 10/100 Mbit/s.
2. Modbus RTU – szeregowy binarny protokół typu Master/Slave
3. Modbus ASCII – szeregowy ASCII protokół typu Master/Slave
4. JBUS – ograniczony zbór wiadomości komunikacyjnych typu Modbus RTU
5. MODBUS PLUS – deterministyczny token LAN, protokół peer to peer, 1Mbit/s.
Filmik prezentujący
Opis działania naszej implementacji:
Nasza aplikacja, którą zaimplementowaliśmy jest przykładem protokołu komunikacyjnego typu Modbus TCP/IP w języku Python, która ma za zadanie odczytywać z pliku tekstowego pierwszą linię i zamieniać ją na zmienną liczbową. Następnie klient łączy się i pobiera tę zmienną. Plik tekstowy jest monitorowany w czasie rzeczywistym.

Zajmując się protokołem Modbus warto wiedzieć, że ramka protokołu Modbus ADU (application data unit) znajduje się w warstwie aplikacji modelu sieciowego TCP/IP. Takie usytuowanie ramki oznacza, że gdy wysyłamy dane przy pomocy protokołu Modbus TCP dane przechodzą przez każdą następną warstwę modu sieciowego i są zaopatrywane w kolejne nagłówki protokołów tych warstw. Proces ten nazywamy hermetyzacją danych (ang. encapsulated). Gdy ramka dojdzie do najniższej warstwy modelu sieciowego (warstwy fizycznej) dane zamieniane są na sygnały elektryczne i przesyłane do punktu docelowego. W urządzeniu docelowym odbywa się odwrotny proces, to znaczy dane są odpakowywane aż w warstwie aplikacji zostanie jedynie ramka protokołu Modbus – ADU.

Listing
#!/usr/bin/env python
# --------------------------------------------------------------------------- #
# import the various server implementations
# --------------------------------------------------------------------------- #
from pymodbus.server.asynchronous import StartTcpServer
from pymodbus.datastore import ModbusSparseDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from twisted.internet import threads
# --------------------------------------------------------------------------- #
# configure the service logging
# --------------------------------------------------------------------------- #
import logging
FORMAT = ('%(asctime)-15s %(threadName)-15s'
' %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s')
logging.basicConfig(format=FORMAT)
log = logging.getLogger()
log.setLevel(logging.DEBUG)
# --------------------------------------------------------------------------- #
# Set up class for data
# --------------------------------------------------------------------------- #
class Device(object):
def __init__(self, path, device_type, callback):
self.path = path
self.device_type = device_type
self.callback = callback
self.value = -1
def update(self):
self.value = self.callback(self.path)
# --------------------------------------------------------------------------- #
# Read value from file
# --------------------------------------------------------------------------- #
def read_value_from_file(file_path):
try:
f = open(file_path)
except IOError as e:
return -1
lines = f.readlines()
f.close()
value = int(lines[0])
return value
# --------------------------------------------------------------------------- #
# Data block - stores value in memory and it passes operation to a queue for future
# --------------------------------------------------------------------------- #
class CallbackDataBlock(ModbusSparseDataBlock):
def __init__(self, devices):
self.devices = devices
self.devices[0xbeef] = len(self.devices) # the number of devices
self.get_line()
# values = [k:0 ]
self.values = {k: 0 for k in self.devices.keys()}
# self.values = {k: 0 for k in self.devices.}
super(CallbackDataBlock, self).__init__(self.values)
def get_lines(self, devices):
values = {}
for device in devices:
device.update()
values[device.register] = device.value
print(device.path, device.value)
return values
def get_line(self):
print('getting value from file')
temp_files = []
devices_registers = filter(lambda d: d != 0xbeef, self.devices)
for registers in devices_registers:
if self.devices[registers].device_type == 'file':
self.devices[registers].register = registers
temp_files.append(self.devices[registers])
d = threads.deferToThread(self.get_lines, temp_files)
d.addCallback(self.update_line_value)
def update_line_value(self, values):
for register in values:
self.values[register] = values[register]
self.get_line()
# --------------------------------------------------------------------------- #
# Mapping devices
# --------------------------------------------------------------------------- #
def get_devices_map():
devices = {
0x0001: Device("/home/pi/modbus/data.dat", "file", read_value_from_file)
}
return devices
# --------------------------------------------------------------------------- #
# Run method
# --------------------------------------------------------------------------- #
if __name__ == "__main__":
devices = get_devices_map()
block = CallbackDataBlock(devices)
store = ModbusSlaveContext(di=None, co=None, hr=None, ir=block)
context = ModbusServerContext(slaves=store, single=True)
StartTcpServer(context, address=("0.0.0.0", 5020))
from pymodbus.client.sync import ModbusTcpClient as ModbusClient
if __name__ == "__main__":
client = ModbusClient("172.20.10.5", 5020)
line = client.read_input_registers(0, 1)
print(line)
print(line.registers)
