Bekijk alle lessen
Deel 1
Basis
Deel 2
Modbus RTU
- Les 5RS485 uitgelegd: A, B, common en terminatie20 min
- Les 6Baudrate en pariteit: waarom 8E1 de default is16 min
- Les 7Het Modbus RTU frame byte voor byte, plus CRC18 min
- Les 8Meerdere Modbus apparaten op een bus zetten16 min
- Les 9Je eerste Modbus apparaat uitlezen met mbpoll22 min
- Les 10Modbus RTU storing zoeken: symptoom en oorzaak20 min
Deel 3
Modbus TCP
Deel 4
Gevorderd
- Les 16Veilig naar een Modbus apparaat schrijven18 min
- Les 17Word order en float: dezelfde bytes, andere waarde22 min
- Les 18Pollinterval en busbelasting zelf berekenen18 min
- Les 19Modbus beveiligen: het protocol doet het niet16 min
- Les 20Modbus integreren: PLC, Home Assistant of cloud20 min
- Les 21Modbus checklist voor oplevering en overdracht18 min
Modbus TCP oefenen met een gratis simulator
Met een gratis Modbus simulator oefen je zonder hardware. Je start een server op poort 5020, leest er registers uit en lokt bewust een echte exception uit.
Wat deze les behandelt
- Een server op poort 5020 en je eerste lezing tegen 127.0.0.1
- Bewust een exception uitlokken en een 32-bits waarde samenstellen
- Een eigen server in vijftien regels Python, en wat hij niet laat zien
Lees eerst: Een Modbus registerkaart lezen zonder gokken, Poort 502, de MBAP header en de unit id
Een Modbus simulator is een programma dat zich op je eigen laptop gedraagt als een Modbus-server: het luistert op een poort, beantwoordt je verzoeken en geeft registerwaarden terug die je van tevoren kent. Daarmee oefen je adressering, function codes, exceptions en datatypes zonder dat er een apparaat of een kabel aan te pas komt. Na deze les heb je een server draaien, een register gelezen, een exception uitgelokt en een dataset gebouwd die je zelf hebt bedacht.
Waarom je zonder hardware verder komt dan je denkt
Alles wat in het protocol zit kun je op een lege laptop oefenen, alles wat in de draad zit niet. Een server op loopback stuurt echte frames: een echte MBAP header, een echte function code, echte registerbytes en een echt exception-antwoord. Wat je client daarvan maakt is precies wat hij van een apparaat op een dak zou maken.
De grens ligt bij de fysieke laag. Baudrate, pariteit, afsluitweerstanden, reflecties en busbelasting bestaan niet op loopback, en een simulator antwoordt bovendien vrijwel meteen, terwijl een echte energiemeter er milliseconden over doet. Wil je weten welke apparaten er op een bestaand netwerk staan, dan hoort daar een scan bij; de tools daarvoor staan in het artikel over een Modbus-netwerk scannen. Voor deze les heb je alleen Python nodig.
De simulator installeren en starten
Twee commando's en je hebt een server. De simulator zit in pymodbus 3.15.0, een BSD-3 gelicenseerde bibliotheek die gratis te gebruiken is, ook zakelijk.
python -m pip install "pymodbus[serial,simulator]==3.15.0"
Op Linux en macOS maak je eerst een virtualenv, anders weigert het systeem de installatie met "externally managed environment":
python3 -m venv ~/modbus-cursus
source ~/modbus-cursus/bin/activate
Starten doe je zo:
pymodbus.simulator --modbus_server server --modbus_device device
Je krijgt een Modbus TCP-server op 0.0.0.0:5020 en een webinterface op http://127.0.0.1:8081/, waarin je de registerwaarden ook kunt inzien en aanpassen.
Je eerste lezing tegen 127.0.0.1
De meegeleverde dataset heeft op PDU-adres 3 een uint16 met de waarde 17001. Die waarde staat zo in de setup.json die met de simulator meekomt, met de actie uitdrukkelijk op niets gezet, dus hij verandert niet tussen twee lezingen:
from pymodbus.client import ModbusTcpClient
client = ModbusTcpClient("127.0.0.1", port=5020)
client.connect()
rr = client.read_holding_registers(address=3, count=1, device_id=1)
print(rr.registers)
Let op device_id=1. Dat is het unit id uit de MBAP header, het veld dat in de opbouw van een Modbus TCP-frame de plaats inneemt van het serveradres op RS485. Bij een apparaat dat rechtstreeks aan het netwerk hangt beveelt de standaard 0xFF aan, en wordt 0x00 ook geaccepteerd. Achter een gateway staat er juist het echte serveradres tussen 1 en 247. De simulator doet zich voor als apparaat 1, dus dat vul je hier in. Dezelfde lezing met mbpoll 1.5.4 (GPLv3, gratis te gebruiken, ook zakelijk) ziet er zo uit:
mbpoll -m tcp -p 5020 -a 1 -t 4 -r 3 -c 1 -0 -1 127.0.0.1
De -0 zet mbpoll op 0-gebaseerde adressering, zodat -r 3 echt PDU-adres 3 aanspreekt en niet het volgende register.
Een exception uitlokken
Lees PDU-adres 1 en je krijgt geen waarde maar een weigering: function_code=131 met exception_code=2. Die 131 is 0x83, en dat is 0x03 plus 0x80. Een exception-antwoord is namelijk altijd de oorspronkelijke function code met de hoogste bit gezet, gevolgd door precies een byte met de exception code. Over de lijn zijn dat twee bytes: 83 02.
Exception 02 heet ILLEGAL DATA ADDRESS. De standaard omschrijft hem als een ongeldige combinatie van startadres en aantal, dus niet alleen het startadres telt maar ook startadres plus lengte. In de praktijk is dit verreweg de exception die je het vaakst ziet, meestal doordat een datasheetnummer als 40001 ongewijzigd het frame in gaat terwijl daar PDU-adres 0 had moeten staan. De volledige lijst van negen codes en wat ze uitsluiten staat in de les over function codes en exception codes.
Een exception is trouwens goed nieuws. Hij bewijst dat je verzoek is aangekomen, is gelezen en is beantwoord, dus poort, verbinding en unit id kloppen en alleen het adres is fout. Krijg je helemaal niets terug, dan zit het probleem een laag lager.
Belangrijk: dat deze simulator ook een exception 02 geeft als je over een typegrens heen leest, bijvoorbeeld address=3, count=2, is een keuze van de simulator. In de meegeleverde setup.json staat daarvoor de schakelaar "type exception": true. Dat is geen eigenschap van Modbus.
Een 32-bits waarde in elkaar zetten
Lees PDU-adres 4 met count=2 en je krijgt [9, 27177]. Twee registers van 16 bits, samen een uint32. Het eerste register draagt de hoogste 16 bits, dus reken je 9 maal 65536 is 589824, plus 27177 is 617001.
De regel erachter: binnen een register is Modbus big-endian, maar de volgorde van twee registers ten opzichte van elkaar legt de standaard niet vast. Dat is een afspraak van de fabrikant, en daarom heeft elke client een schakelaar voor word order. In pymodbus geef je het datatype expliciet mee:
rr = client.read_holding_registers(address=4, count=2, device_id=1)
value = client.convert_from_registers(rr.registers, data_type=client.DATATYPE.UINT32)
Het tweede voorbeeld wijkt op precies een punt af. PDU-adres 6 met count=2 is geen uint32 maar een float32, die op 404,17 begint en bij elke lezing oploopt. Zelfde twee registers, ander datatype, compleet andere uitkomst. Wat er gebeurt als je de woordvolgorde verkeerd kiest, en waarom dat bij een int32 gevaarlijker is dan bij een float32, staat in de les over datatypes en word order.
Je eigen server met je eigen registers
Wacht je op een apparaat dat nog geleverd moet worden, dan bouw je met de registerkaart uit de datasheet alvast een oefenkopie. Vijftien regels volstaan:
from pymodbus.server import StartTcpServer
from pymodbus.simulator import DataType, SimData, SimDevice
block = SimData(0, values=[1234, 5678, 42], datatype=DataType.REGISTERS)
rest = SimData(3, count=97, datatype=DataType.REGISTERS)
device = SimDevice(1, simdata=[block, rest])
StartTcpServer(context=device, address=("0.0.0.0", 5020))
SimData(0, values=...) legt drie holding registers neer vanaf PDU-adres 0, rest vult het blok aan tot honderd registers zodat je ook kunt testen wat er gebeurt als je eroverheen leest. SimDevice(1, ...) bepaalt het unit id. Lees daarna PDU-adres 0 met count=3 en je krijgt [1234, 5678, 42] terug.
Wat de simulator je niet leert
Alles wat je client-instellingen betreft is overdraagbaar, alles wat de bus betreft niet. Adressering, function codes, datatypes, word order en exception-afhandeling gedragen zich hier hetzelfde als op een echte installatie, en dat is precies het deel waar de meeste tijd in gaat zitten.
Buiten beeld blijven: bekabeling, baudrate en pariteit, afsluitweerstanden, reflecties, biasing en busbelasting. Ook responstijden en timeouts zijn hier onrealistisch gunstig, dus een pollinterval dat op loopback prima werkt kan op een RS485-segment met tien apparaten te krap zijn.
Veelgemaakte fouten
Deprecation-waarschuwingen voor een fout aanzien. De simulator logt in 3.15.0 waarschuwingen over ModbusSimulatorContext en ModbusServerContext. Dat is normaal gedrag van deze versie en er is niets stuk. Ga daar niet op troubleshooten, want dan zoek je in werkende code.
Code uit oude blogposts overnemen. pymodbus accepteert unit= sinds 3.1.0 niet meer in clientaanroepen, hernoemde de parameter in 3.3.0 naar slave= en maakte daar in 3.10.0 device_id= van. In diezelfde 3.10.0 verdween BinaryPayloadDecoder, vervangen door convert_from_registers. Een voorbeeld van een paar jaar oud draait dus niet meer. Controleer altijd tegen de documentatie van de versie die je hebt geïnstalleerd.
ModbusSequentialDataBlock(0, ...) gebruiken. Sinds 3.13.0 trekt pymodbus intern 1 van dat eerste argument af, dus een 0 wordt adres -1 en je krijgt TypeError: 0 <= address < 65535. Gebruik SimData en SimDevice, of begin bij 1.
Simulatorgedrag voor protocolgedrag aanzien. Wat deze simulator doet bij een lezing over een typegrens heen is een implementatiekeuze. Wat de standaard voorschrijft over exception 02 is dat niet. Houd die twee uit elkaar, anders neem je een aanname mee het veld in.
Aan de slag
Je hebt aan het eind een draaiende server, een gelezen register, een uitgelokte exception en een eigen dataset.
- 1
Installeer pymodbus
Draai
python -m pip install "pymodbus[serial,simulator]==3.15.0". Op Linux en macOS eerst een virtualenv aanmaken en activeren. - 2
Start de simulator
Draai
pymodbus.simulator --modbus_server server --modbus_device deviceen openhttp://127.0.0.1:8081/in je browser om te zien wat er in de registers staat. - 3
Lees PDU-adres 3
Een register, met
device_id=1. Verwachte waarde: 17001. Lukt dit, dan staat je hele keten van client tot server. - 4
Lok een exception uit
Lees PDU-adres 1. Je krijgt function code 131 met exception code 2. Schrijf op welke twee bytes dat over de lijn zijn.
- 5
Zet twee registers om naar een uint32
Lees PDU-adres 4 met
count=2. Je krijgt[9, 27177]. Reken zelf na dat 9 maal 65536 plus 27177 gelijk is aan 617001, en controleer het daarna metconvert_from_registers. - 6
Lees de oplopende float
Lees PDU-adres 6 met
count=2en interpreteer het als float32. Lees twee keer achter elkaar en let op dat de waarde vanaf 404,17 oploopt. - 7
Bouw je eigen server
Neem het
SimData-voorbeeld uit deze les over, stop er drie waarden in die je zelf kiest, en lees ze terug vanaf PDU-adres 0.
Heb je wel hardware? Doe stap 3 tot en met 6 daarna nog een keer tegen je eigen apparaat en vergelijk. De frames hebben dezelfde vorm, alleen de adressen en de waarden verschillen. Wil je dezelfde registers daarna in een domoticasysteem zetten, dan is de vertaalslag naar YAML beschreven in het artikel over de Home Assistant Modbus-integratie. Wil je diezelfde frames echt over de lijn zien lopen, dan legt de les over foutzoeken met Wireshark er een capture naast.
Samenvatting
- Een Modbus simulator geeft je echte frames en echte exception-antwoorden, dus je oefent er adressering, function codes en datatypes mee zonder een apparaat aan te sluiten.
- De pymodbus-simulator luistert op poort 5020 in plaats van de geregistreerde poort 502, zodat je geen root-rechten nodig hebt.
- Een exception-antwoord is de function code plus
0x80, gevolgd door een byte:83 02betekent een geweigerd FC03-verzoek met exception 02, ILLEGAL DATA ADDRESS. - Twee registers vormen samen een 32-bits waarde door het eerste register maal 65536 bij het tweede op te tellen, zolang de hoogste 16 bits vooraan staan.
- Wat een simulator niet kan tonen is de fysieke laag: bekabeling, baudrate, terminatie en busbelasting oefen je alleen op een echte RS485-bus.
Controleer jezelf
Vier vragen over deze les. Je krijgt bij elk antwoord uitleg.
Vraag 1 van 4
Zelf zien hoe het werkt?
De ModbusCloud Gateway leest de apparaten uit deze cursus uit zonder dat je registers hoeft te programmeren.