Modbus quickstart
In this quickstart you run a Modbus TCP server and a client on one computer. The client writes 42 to holding register 0 and reads it back. The exchange uses a real TCP socket, so the results report source as wire.
terminal A: headless server 127.0.0.1:1502 <---- Modbus TCP ----> terminal B: C++ or C# clientBefore you begin
Section titled “Before you begin”You need:
- The PDComm Modbus SDK for your operating system and the PDComm examples (
examples/) - For C++: CMake 3.20 or later and a C++17 compiler
- For C#: the .NET 8 runtime and a .NET SDK that builds
net8.0 - Python 3, to run the example server script
peer.py - An evaluation or developer license file. The client uses the SDK and needs it; the headless simulator that acts as the server doesn’t. See Install and check a license.
The SDK folder contains bin/, lib/, include/, bindings/, licenses/ and manifest.json. Keep the folder together.
Write and read a register
Section titled “Write and read a register”-
In two terminals, point to the SDK and the examples. Replace the paths with the folders you extracted.
터미널 창 export MODBUS_SDK="$HOME/pdcomm/pdcomm-modbus"export MODBUS_EXAMPLES="$HOME/pdcomm/examples"export PDCOMM_LICENSE_FILE="$HOME/pdcomm/pdcomm.license"The commands on this page are for macOS and Linux shells. In Windows PowerShell, set the same values as
$env:MODBUS_SDK,$env:MODBUS_EXAMPLES, and$env:PDCOMM_LICENSE_FILE, and expect.exeand.dllfile names. -
In terminal A, start the server.
peer.pyruns the SDK’spdcomm-modbus-headless, configures it as a TCP server and opens the listener.터미널 창 python3 "$MODBUS_EXAMPLES/modbus-tutorial/peer.py" --sdk "$MODBUS_SDK" --address 127.0.0.1:1502The server is waiting when it prints two JSON lines and the second one shows
"connected":true. Leave terminal A open.{"ok":true,"result":{"config":{"address":"127.0.0.1:1502",...,"role":"server",...},"connected":true,...}}If you see
Io(AddrInUse, ...), another program holds port 1502. Use the same free port, such as127.0.0.1:1503, in both terminals. -
In terminal B, build and start a client console.
터미널 창 cmake -S "$MODBUS_EXAMPLES/modbus-tutorial" -B build/modbus-tutorial -DCMAKE_BUILD_TYPE=Release -DPDCOMM_MODBUS_SDK="$MODBUS_SDK"cmake --build build/modbus-tutorial --config Release./build/modbus-tutorial/pdcomm-modbus-tutorialOn Windows, the Visual Studio generator puts the program in
build\modbus-tutorial\Release\pdcomm-modbus-tutorial.exe, and the SDK’slibfolder must be onPATHso the program findspdcomm_modbus_ffi.dll.터미널 창 dotnet build "$MODBUS_EXAMPLES/modbus-tutorial/dotnet/ModbusTutorial.csproj" -c Release -p:PdcommModbusSdk="$MODBUS_SDK" -o build/modbus-dotnetdotnet build/modbus-dotnet/ModbusTutorial.dll --native "$MODBUS_SDK/lib/libpdcomm_modbus_ffi.dylib"--nativetakes the absolute path of the native library:libpdcomm_modbus_ffi.soon Linux,pdcomm_modbus_ffi.dllon Windows.The console prints
Modbus C++ tutorial: JSON lines; quit or EOF to finish.(orModbus C# tutorial: ...) on standard error and waits for input. -
In terminal B, enter these commands one line at a time. Each line is one JSON object.
{"action":"configure","config":{"transport":"tcp","role":"client","address":"127.0.0.1:1502","port":"","unit":1,"baud":19200,"parity":"even","stop_bits":1,"timeout_ms":1000}}{"action":"connect"}{"action":"request","pdu":"060000002A"}{"action":"request","pdu":"0300000001"}The last two commands return:
{"ok":true,"result":{"response":[6,0,0,0,42],"source":"wire","status":"success"}}{"ok":true,"result":{"response":[3,2,0,42],"source":"wire","status":"success"}}060000002Ais a function 06 request that writes 42 (002A) to holding register 0 (0000). The server echoes the same bytes.0300000001is a function 03 request that reads one register from address 0. The response[3,2,0,42]is function 3, a byte count of 2 and the value0x002A.
-
In terminal A, enter
{"action":"trace"}to see the requests the server received:{"ok":true,"result":{"entries":[{"request":[6,0,0,0,42],"response":[6,0,0,0,42],"sequence":1,"source":"wire","status":"success","unit":1},{"request":[3,0,0,0,1],"response":[3,2,0,42],"sequence":2,"source":"wire","status":"success","unit":1}]}} -
Clean up: enter
quitin terminal B to end the console, thenquitin terminal A to stop the server. Both exit with code 0.
If something goes wrong
Section titled “If something goes wrong”| Symptom | Cause and fix |
|---|---|
Command error: {"error":"Io(ConnectionRefused, ...)","ok":false} |
The server isn’t running or the address differs. Run configure and connect again with the address of the server in terminal A. |
Command error: {"error":"Timeout","ok":false} |
The server didn’t answer. Check that the server process is alive, then run connect again. |
CMake stops with Set PDCOMM_MODBUS_SDK to the extracted SDK root |
Point MODBUS_SDK at the SDK’s top folder, the one that contains include/. |
C# throws DllNotFoundException or BadImageFormatException |
Check that --native is an absolute path and that the SDK matches your operating system and CPU. |
The C++ console prints the error JSON of a failed command and exits with code 1 if any command failed. See Troubleshooting for more cases.
The example code
Section titled “The example code”The C++ console creates one Endpoint and calls Endpoint::call for every input line.
#include <pdcomm/modbus/pdcomm_modbus.hpp>#include <iostream>#include <string>
// A client/local-model console: the separate simulator polls the server.// Enter one service JSON command per line, then "quit" or EOF to release it.int main() { try { pdcomm::modbus::Endpoint endpoint; bool failed = false; std::string line; std::cerr << "Modbus C++ tutorial: JSON lines; quit or EOF to finish.\n"; while (std::getline(std::cin, line)) { if (line == "quit") break; if (line.find_first_not_of(" \t\r") == std::string::npos) continue; try { std::cout << endpoint.call(line) << std::endl; } catch (const std::exception &error) { failed = true; std::cerr << "Command error: " << error.what() << std::endl; } } endpoint.call("{\"action\":\"disconnect\"}"); return failed ? 1 : 0; } catch (const std::exception &error) { std::cerr << "Fatal error: " << error.what() << std::endl; return 1; }}The C# console sets the native library path with NativeLibrary.SetDllImportResolver, then calls Endpoint.Call.
using System.Runtime.InteropServices;using System.Text.Json;using PDComm.Modbus;
if (args.Length != 2 || args[0] != "--native" || !Path.IsPathFullyQualified(args[1])){ Console.Error.WriteLine("Usage: ModbusTutorial --native <absolute native library path>"); return 2;}try{ string nativePath = Path.GetFullPath(args[1]); NativeLibrary.SetDllImportResolver(typeof(Endpoint).Assembly, (name, assembly, search) => name == "pdcomm_modbus_ffi" ? NativeLibrary.Load(nativePath) : IntPtr.Zero); using var endpoint = new Endpoint(); bool failed = false; Console.Error.WriteLine("Modbus C# tutorial: JSON lines; quit or EOF to finish."); while (Console.ReadLine() is string line) { if (line == "quit") break; if (string.IsNullOrWhiteSpace(line)) continue; try { using var command = JsonDocument.Parse(line); var result = endpoint.Call(command.RootElement); Console.WriteLine(JsonSerializer.Serialize(new { ok = true, result })); } catch (Exception error) { failed = true; Console.Error.WriteLine($"Command error: {error.Message}"); } } endpoint.Call(new { action = "disconnect" }); return failed ? 1 : 0;}catch (Exception error){ Console.Error.WriteLine($"Fatal error: {error.Message}"); return 1;}Next steps
Section titled “Next steps”- A closer look at the first TCP exchange: SDK layout, API ownership and error handling.
- The 19 function codes and diagnostics: run every function code.
- Data model and addresses: convert manual addresses such as 40001 to PDU addresses.