Skip to content
Start free evaluation

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# client

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.

  1. 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 .exe and .dll file names.

  2. In terminal A, start the server. peer.py runs the SDK’s pdcomm-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:1502

    The 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 as 127.0.0.1:1503, in both terminals.

  3. 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-tutorial

    On Windows, the Visual Studio generator puts the program in build\modbus-tutorial\Release\pdcomm-modbus-tutorial.exe, and the SDK’s lib folder must be on PATH so the program finds pdcomm_modbus_ffi.dll.

    The console prints Modbus C++ tutorial: JSON lines; quit or EOF to finish. (or Modbus C# tutorial: ...) on standard error and waits for input.

  4. 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"}}
    • 060000002A is a function 06 request that writes 42 (002A) to holding register 0 (0000). The server echoes the same bytes.
    • 0300000001 is 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 value 0x002A.
  5. 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}]}}
  6. Clean up: enter quit in terminal B to end the console, then quit in terminal A to stop the server. Both exit with code 0.

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 C++ console creates one Endpoint and calls Endpoint::call for every input line.

examples/modbus-tutorial/main.cpp
#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.

examples/modbus-tutorial/dotnet/Program.cs
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;
}