MODBUS·SANDBOX Free public server

RUEN

Docs

Beginner's guide

Modbus Sandbox is a free server on the internet that pretends to be ten Modbus devices at once. Point your client - the program that polls field devices - at it and see how it reads and writes data. No sign-up needed, everything happens in real time.

What to know before you start:

Your first check, step by step:

  1. Open ModbusToolkit (or any other Modbus client) and create a Modbus TCP connection to host modbus-sendbox.electroman.by port 5020.
  2. Set unit ID 1 and read 10 holding registers from address 1 with function 3. A list of numbers comes back.
  3. Write your own value to the first register with function 6 and read it again - the value matches.
  4. Open the «Register map» section of this site and find the same address: you see the same value. Every connected user sees it too, and anyone may overwrite it.
  5. Look at «Behaviors» to assign a generator (counter, saw, sine) to a register, and at «Injections» to give a slave an error or a delayed response.

A quick map of the site sections:

Usage examples

Below are short client checks against live data; each takes a few minutes. The examples use ModbusToolkit, but any Modbus client works. Demo slots fill themselves in, and free registers are shared - another participant may overwrite them.

Check 32-bit value packing (uint32 CDAB)

The CDAB word order is the most common mistake: the high word is stored second. Open the «Register map», slave 3, and find demo slot 1009 or the sine at slot 1015. With function 3 read two registers from that address (device_id=3), assemble the value the way your client does, and compare it with the UI. A match means your word order is right. Then write your own value with function 6 into the free register at address 1 and read it back.

Check the reaction to a missing reply (timeout)

A client must not hang when the device goes silent. In the «Injections» section add a timeout injection on slave 1 with a 30 s TTL and run a read loop in your client with a 1-2 s timeout. While the injection lives, no replies come: the client must report an error instead of freezing. When the TTL ends, reading recovers on its own, no client restart needed. A useful variation is an exception injection with code 0x06: the client should handle Device Busy properly.

Check floats with swapped bytes (float DCBA)

The DCBA order is easy to miss: garbage numbers can look plausible. Take slave 9 and demo slot 1015 with the sine, read two registers in your client and assemble a float. Then paste the same two registers into the converter in «Tools»: it shows the value in all four orders. Your value should match the DCBA row and the live slot value.

Check time and time zones (slave 10)

The «Time zones» section shows 27 zones with date, time and UTC offset, plus the Unix timestamp at registers 300..301. Read them from your client and compare with the system clock: a drift of more than a couple of seconds means the uint32 or time zone parsing is wrong.

Format specification

Each unit ID emulates its own HR/IR data format. A 32-bit value occupies two registers N and N+1; the value address is the address of the high register (grid 1, 3, …, 1023). ABCD letters are the value bytes from the high byte (A) to the low byte (D).

Unit IDHR/IR formatHRIRCLDI
1uint161..10241..10241..641..64
2uint32 ABCD1,3,…,10231,3,…,10231..641..64
3uint32 CDAB1,3,…,10231,3,…,10231..641..64
4uint32 BADC1,3,…,10231,3,…,10231..641..64
5uint32 DCBA1,3,…,10231,3,…,10231..641..64
6float ABCD1,3,…,10231,3,…,10231..641..64
7float CDAB1,3,…,10231,3,…,10231..641..64
8float BADC1,3,…,10231,3,…,10231..641..64
9float DCBA1,3,…,10231,3,…,10231..641..64
10uint16-1..268, 300..301--

Slave 10 serves IR only. Addresses 1..268 hold metrics for 27 time zones (8 registers per zone, unused block registers are zero), addresses 300..301 hold the Unix timestamp (uint32 ABCD, 0.5 s accuracy). The full specification lives in docs/HRIR_SPEC.md of the repository.

Limits

Multiplayer rules

API

The REST API is described in Swagger UI at /api/docs (prefix /api/modbus/). WebSocket /ws/state streams live slave state, /ws/events streams journal events. Connection examples are collected below.

Connect from ModbusToolkit

ModbusToolkit is a separate desktop Modbus client by the same author; Modbus Sandbox is not tied to it and works with any client. Create a Modbus TCP connection to the sandbox and test your client code against live data.

  1. Create a Modbus TCP connection: host modbus-sendbox.electroman.by, port 5020, Unit ID 1..10.
  2. Read 10 holding registers of slave 1 with function 3 from address 1. Slave 1 values use the uint16 format.
  3. Write a value with function 6 and see that the data is shared. Every connected client sees the write (shared last-write-wins).

Download ModbusToolkit at modbustoolkit.electroman.by. Product docs: docs.html. News and support: t.me/modbustoolkit.

Code examples

Ready snippets for pymodbus, modbus-serial and NModbus. Addressing follows docs/HRIR_SPEC.md with no conversion; unit ID is 1..10. HR/CL are shared across all connections (shared last-write-wins).

Python · pymodbus

# pip install "pymodbus>=3.6"
from pymodbus.client import ModbusTcpClient

client = ModbusTcpClient("modbus-sendbox.electroman.by", port=5020)
if not client.connect():
    raise SystemExit("no connection to the server")

# FC 3: read 10 holding registers of slave 1 (uint16) from address 1
result = client.read_holding_registers(1, count=10, device_id=1)
if result.isError():
    raise SystemExit(f"Modbus error: {result}")
print(result.registers)

# FC 4: input registers (slave 10 has only these)
result = client.read_input_registers(1, count=8, device_id=10)

# FC 5 / FC 6 / FC 16: write coils and holding registers (values are shared!)
client.write_coil(1, True, device_id=1)
client.write_register(1, 42, device_id=1)
client.write_registers(1, [1, 2], device_id=1)  # FC 16, max 2 registers per write

client.close()

Node.js · modbus-serial

// npm install modbus-serial
const ModbusRTU = require("modbus-serial");

async function main() {
  const client = new ModbusRTU();
  await client.connectTCP("modbus-sendbox.electroman.by", { port: 5020 });
  client.setID(1); // unit ID 1..10

  // FC 3: 10 holding registers from address 1
  const result = await client.readHoldingRegisters(1, 10);
  console.log(result.data);

  // FC 4: input registers (slave 10 has only these)
  client.setID(10);
  console.log((await client.readInputRegisters(1, 8)).data);

  // FC 5 / FC 6: write coils and holding registers (values are shared!)
  client.setID(1);
  await client.writeCoil(1, true);
  await client.writeRegister(1, 42);

  client.close();
}

main().catch(console.error);

C# · NModbus

// dotnet add package NModbus
using System.Net.Sockets;
using NModbus;

var tcp = new TcpClient("modbus-sendbox.electroman.by", 5020);
var client = new ModbusFactory().CreateMaster(tcp);

// FC 3: read 10 holding registers of slave 1 (uint16) from address 1
ushort[] regs = client.ReadHoldingRegisters(slaveId: 1, startAddress: 1, numberOfPoints: 10);
Console.WriteLine(string.Join(", ", regs));

// FC 4: input registers (slave 10 has only these)
ushort[] ir = client.ReadInputRegisters(1, 1, 8);

// FC 5 / FC 6: write coils and holding registers (values are shared!)
client.WriteSingleCoil(1, 1, true);
client.WriteSingleRegister(1, 1, 42);

Source code and your own server

The server code is open and lives on GitHub: StarLeG/modbustoolkit-sandbox. Run your own copy on an isolated bench network without internet access, or wherever injections and writes must not affect other users.

1. Clone the repository

git clone https://github.com/StarLeG/modbustoolkit-sandbox.git
cd modbustoolkit-sandbox

2. Run with uv (recommended)

# requires Python 3.11+ and uv: https://docs.astral.sh/uv/
uv sync
uv run python -m app

…or in Docker

cp .env.example .env   # adjust MODBUS_PUBLIC_HOST and the ports
docker compose up -d --build

…or with pip, without uv

python -m venv .venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install .
python -m app

Once started, the web UI opens at http://127.0.0.1:8765 and Modbus listens on port 5020 on all interfaces (variables WEB_HOST/WEB_PORT and MODBUS_TCP_PORT). Set MODBUS_PUBLIC_HOST to your machine's IP or name, otherwise «Overview» and the config generator show the public server address. Only Docker Compose reads the .env file; with python -m app set the variables in the environment. All variables are listed in .env.example, nginx and systemd setups are in docs/DEPLOY.md.

The server is free and stays that way. We are raising money for a test bench with real instruments to run every ModbusToolkit build against.