MODBUS·SANDBOX Бесплатный публичный сервер

RUEN

Документация

Руководство для начинающих

Modbus Sandbox - бесплатный сервер в интернете, который притворяется сразу десятью Modbus-устройствами. К нему подключают свой клиент - программу, опрашивающую приборы, - и проверяют, как она читает и пишет данные. Регистрация не нужна, всё происходит в реальном времени.

Что нужно знать перед стартом:

Первая проверка по шагам:

  1. Откройте ModbusToolkit (или любой другой Modbus-клиент) и создайте соединение Modbus TCP с хостом modbus-sendbox.electroman.by и портом 5020.
  2. Укажите unit ID 1 и прочитайте 10 holding registers с адреса 1 функцией 3. В ответ придёт список чисел.
  3. Запишите в первый регистр своё значение функцией 6 и прочитайте снова - значение совпадёт.
  4. Откройте раздел «Карта регистров» этого сайта и найдите тот же адрес: вы увидите то же значение. Его видят все подключённые пользователи, и любой может его перезаписать.
  5. Загляните в «Поведения», чтобы назначить регистру генератор (счётчик, пила, синус), и в «Инъекции», чтобы устроить слейву ошибку или задержку ответа.

Коротко о разделах сайта:

Примеры использования

Ниже - короткие сценарии проверки клиента на живых данных, каждый занимает несколько минут. Примеры написаны для ModbusToolkit, но подойдёт любой Modbus-клиент. Демо-слоты заполняют себя сами, а свободные регистры общие - их может перезаписать другой участник.

Проверить сборку 32-битных значений (uint32 CDAB)

Порядок слов CDAB - самая частая ошибка: старшее слово хранится вторым. Откройте «Карту регистров», slave 3, и найдите демо-слот 1009 или синус на слоте 1015. Прочитайте из клиента функцией 3 два регистра с этого адреса (device_id=3), соберите из них значение так, как умеет ваш клиент, и сверьте с тем, что показывает интерфейс. Совпало - порядок слов у вас правильный. Затем запишите своё значение функцией 6 в свободный регистр с адресом 1 и прочитайте обратно.

Проверить реакцию на пропавший ответ (timeout)

Клиент не должен зависать, когда устройство молчит. В разделе «Инъекции» добавьте инъекцию типа timeout на slave 1 с TTL 30 секунд и запустите в клиенте цикл чтения с тайм-аутом 1-2 секунды. Пока инъекция жива, ответов нет: клиент обязан показать ошибку, а не висеть. Когда TTL закончится, чтение восстановится само, без перезапуска клиента. Полезная вариация - инъекция exception с кодом 0x06: клиент должен корректно обработать ответ Device Busy.

Проверить float с перевёрнутыми байтами (float DCBA)

Порядок DCBA легко пропустить: мусорное число иногда выглядит правдоподобно. Возьмите slave 9 и демо-слот 1015 с синусом, прочитайте из клиента два регистра и соберите float. Затем в разделе «Инструменты» вставьте эти же два регистра в конвертер: он покажет значение во всех четырёх порядках. Ваше значение должно совпасть со строкой DCBA и с живым значением слота.

Проверить время и часовые пояса (slave 10)

Раздел «Часовые пояса» показывает 27 зон с датой, временем и смещением UTC, а также Unix timestamp в регистрах 300..301. Прочитайте их из клиента и сверьте с системным временем: расхождение больше пары секунд означает ошибку в разборе uint32 или часового пояса.

Спецификация форматов

Каждый unit ID эмулирует свой формат данных для HR/IR. Значение 32-битного формата занимает два регистра N и N+1, адрес значения - адрес старшего регистра (сетка 1, 3, …, 1023). Буквы ABCD - это байты значения от старшего (A) к младшему (D).

Unit IDФормат HR/IRHRIRCLDI
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 отдаёт только IR. Адреса 1..268 - метрики 27 часовых поясов (8 регистров на зону, незанятые регистры блока равны нулю), адреса 300..301 - Unix timestamp (uint32 ABCD, погрешность 0.5 с). Полная спецификация лежит в docs/HRIR_SPEC.md репозитория.

Ограничения

Правила многопользовательскости

API

REST-интерфейс описан в Swagger UI по адресу /api/docs (префикс /api/modbus/). WebSocket /ws/state отдаёт live-состояние слейва, /ws/events - события журнала. Примеры подключения собраны ниже.

Подключение из ModbusToolkit

ModbusToolkit - отдельный десктопный Modbus-клиент того же автора, Modbus Sandbox к нему не привязан и работает с любым клиентом. Создайте Modbus TCP соединение к песочнице и проверьте клиентский код на живых данных.

  1. Создайте соединение Modbus TCP: хост modbus-sendbox.electroman.by, порт 5020, Unit ID 1..10.
  2. Прочитайте 10 регистров HR слейва 1 функцией 3 с адреса 1. Формат значений slave 1 - uint16.
  3. Запишите значение функцией 6 и убедитесь, что данные общие. Запись видна всем подключённым клиентам (shared last-write-wins).

Скачать ModbusToolkit можно на modbustoolkit.electroman.by. Документация продукта: docs.html. Новости и поддержка: t.me/modbustoolkit.

Примеры кода

Готовые сниппеты для pymodbus, modbus-serial и NModbus. Адресация - как в docs/HRIR_SPEC.md, без пересчёта; unit ID - 1..10. HR/CL общие для всех подключений (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("нет соединения с сервером")

# FC 3: читаем 10 регистров HR слейва 1 (uint16) с адреса 1
result = client.read_holding_registers(1, count=10, device_id=1)
if result.isError():
    raise SystemExit(f"ошибка Modbus: {result}")
print(result.registers)

# FC 4: input registers (на slave 10 только они)
result = client.read_input_registers(1, count=8, device_id=10)

# FC 5 / FC 6 / FC 16: запись CL и HR (значения общие для всех!)
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, не более 2 регистров

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 регистров HR с адреса 1
  const result = await client.readHoldingRegisters(1, 10);
  console.log(result.data);

  // FC 4: input registers (на slave 10 только они)
  client.setID(10);
  console.log((await client.readInputRegisters(1, 8)).data);

  // FC 5 / FC 6: запись CL и HR (значения общие для всех!)
  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: 10 регистров HR слейва 1 (uint16) с адреса 1
ushort[] regs = client.ReadHoldingRegisters(slaveId: 1, startAddress: 1, numberOfPoints: 10);
Console.WriteLine(string.Join(", ", regs));

// FC 4: input registers (на slave 10 только они)
ushort[] ir = client.ReadInputRegisters(1, 1, 8);

// FC 5 / FC 6: запись CL и HR (значения общие для всех!)
client.WriteSingleCoil(1, 1, true);
client.WriteSingleRegister(1, 1, 42);

Исходный код и свой сервер

Код сервера открыт и лежит на GitHub: StarLeG/modbustoolkit-sandbox. Свою копию удобно поднять в закрытой сети стенда без доступа в интернет или там, где инъекции и записи не должны задевать других пользователей.

1. Клонировать репозиторий

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

2. Запустить через uv (рекомендуется)

# нужен Python 3.11+ и uv: https://docs.astral.sh/uv/
uv sync
uv run python -m app

…или в Docker

cp .env.example .env   # поправьте MODBUS_PUBLIC_HOST и порты
docker compose up -d --build

…или через pip, без uv

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

После запуска web UI открывается на http://127.0.0.1:8765, Modbus слушает порт 5020 на всех интерфейсах (переменные WEB_HOST/WEB_PORT и MODBUS_TCP_PORT). Задайте MODBUS_PUBLIC_HOST - IP или имя вашей машины, иначе «Обзор» и генератор конфигов покажут адрес публичного сервера. Файл .env читает только Docker Compose; при запуске через python -m app переменные задаются в окружении. Все переменные перечислены в .env.example, варианты с nginx и systemd - в docs/DEPLOY.md.

Сервер бесплатный и останется таким. Сейчас собираем на испытательный стенд с реальными приборами, на котором будет проверяться каждая версия ModbusToolkit.