Skip to main content

2 posts tagged with "mbscan"

View All Tags

Утилита modbus-slave. Эмулятор Modbus RTU датчиков

· 4 min read
dmn
maintainer

Когда разрабатываешь систему мониторинга или SCADA, часто нужно протестировать опрос датчиков — но реального оборудования под рукой нет. Или нужно показать демо заказчику без физических устройств. Или хочется отладить логику мастера не выезжая на объект.

Именно для этого мы написали modbus_slave — эмулятор Modbus RTU slave устройств на C, который работает на Linux и Windows, не требует зависимостей и умеет отдавать реальные данные из файлов.

Что умеет

  • Эмулирует до 30 независимых Modbus RTU устройств на одном последовательном порту
  • Каждое устройство отвечает на FC03 (Read Holding Registers), 20 регистров
  • Значения регистров — случайные (для тестирования) или из файла (реальные данные)
  • Работает на Linux x86_64, aarch64 (NAPI2, RK3568, Raspberry Pi) и Windows x64
  • Поддерживает RS-485 — RTS direction control через DTS/GPIO
  • Режим демона с логами в syslog и systemd service для автозапуска
  • Защита от устаревших данных — если скрипт-источник упал, регистры возвращают нули
  • Один статический бинарь без зависимостей — скопировал и запустил

Архитектура

Идея простая: один процесс слушает RS-485 шину и отвечает на запросы от любого из 30 адресов. Для мастера это выглядит как несколько физических устройств на линии — он не видит разницы.

Мастер (PC/ПЛК)          RS-485 шина          modbus_slave (NAPI2)
mbpoll -a 1 ──────────────────────────► slave ID 1 → /tmp/cpu.dat
mbpoll -a 2 ──────────────────────────► slave ID 2 → /tmp/time.dat
mbpoll -a 3 ──────────────────────────► slave ID 3 → random

Регистры читаются из обычных текстовых файлов — одно число на строку. Файлы живут в /tmp (tmpfs — RAM диск), SD карта не изнашивается.

Быстрый старт

Установка на NAPI2 / aarch64

# Скачать статический бинарь
wget https://github.com/lab240/modpoll-slave/raw/main/bin/modbus_slave_aarch64
chmod +x modbus_slave_aarch64

# Запустить — 3 датчика, порт ttyS7
./modbus_slave_aarch64 -p /dev/ttyS7 -b 115200 -a 3

Запуск на Windows

modbus_slave.exe -p COM4 -b 115200 -a 3

Проверка с mbpoll

mbpoll -m rtu -b 115200 -P none -a 1 -r 1 -c 20 /dev/ttyUSB0

Реальные данные из файлов

Самое интересное — каждому slave можно привязать файл с реальными значениями. Формат простой: одно целое число на строку.

# Запуск: slave 1 читает данные CPU, slave 2 — время, slave 3 — рандом
./modbus_slave -p /dev/ttyS7 -b 115200 -a 3 \
-f 1:/tmp/cpu.dat \
-f 2:/tmp/time.dat

Файл обновляется внешним скриптом атомарно через mv — никакой гонки данных:

# Температура ядер CPU (°C × 100)
while true; do
for zone in /sys/class/thermal/thermal_zone*/temp; do
val=$(( $(cat $zone) / 10 ))
echo $val
done > /tmp/cpu.tmp
mv /tmp/cpu.tmp /tmp/cpu.dat
sleep 5
done

Значение 4523 в регистре означает 45.23 °C — стандартное соглашение для передачи дробных чисел в Modbus.

Защита от падения скриптов

Если скрипт обновления данных упал — файл перестаёт обновляться, но данные в нём остаются старые. Мастер продолжал бы читать устаревшие значения.

Параметр -t задаёт максимальный возраст файла:

./modbus_slave -p /dev/ttyS7 -b 115200 -f 1:/tmp/cpu.dat -t 10

Если файл не обновлялся более 10 секунд — все регистры возвращают 0. Это сразу видно мастеру и SCADA системе. В лог пишется предупреждение (не чаще раза в минуту чтобы не спамить):

file /tmp/cpu.dat is stale (45s > 10s), returning zeros

По умолчанию t=10. Отключить: -t 0.

Режим демона и systemd

# Запуск как демон
./modbus_slave -d -p /dev/ttyS7 -b 115200 -a 3 -f 1:/tmp/cpu.dat

# Статус
./modbus_slave -s

# Остановка
./modbus_slave -k

# Логи
journalctl -t modbus_slave -f

Для автозапуска при загрузке — systemd service:

sudo cp modbus_slave /usr/local/bin/
sudo cp service/modbus_slave.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now modbus_slave

Статистика запросов пишется в лог каждые 60 секунд:

stats: slaves=3 total_ok=12480 total_err=0

Windows

На Windows всё то же самое, только порт называется COM4 вместо /dev/ttyS7, файлы в C:\temp\ вместо /tmp/, и -bg вместо -d:

modbus_slave.exe -bg -p COM4 -b 115200 -a 3 ^
-f 1:C:\temp\cpu.dat ^
-f 2:C:\temp\time.dat

Логи пишутся в C:\temp\modbus_slave.log.

Сборка из исходников

Код разделён на три файла:

ФайлСодержимое
modbus_core.hВся логика Modbus: CRC16, FC03, файлы, статистика
modbus_slave.cLinux: termios, fork, syslog, /proc
modbus_slave_win.cWindows: Win32 API, CreateFile, DCB
# Linux x86_64
gcc -O2 -Wall -o modbus_slave src/modbus_slave.c

# Linux aarch64 статический
aarch64-linux-gnu-gcc -O2 -Wall -static -o modbus_slave_aarch64 src/modbus_slave.c

# Windows .exe с Linux
x86_64-w64-mingw32-gcc -O2 -Wall -o modbus_slave.exe src/modbus_slave_win.c

Итог

modbus_slave решает конкретную задачу — дать возможность разрабатывать и тестировать Modbus мастер без реального железа, или превратить одноплатный компьютер в многоканальный шлюз данных в Modbus.

Код открытый, собирается одной командой, работает на том же NAPI2 где и всё остальное.

Репозиторий: github.com/lab240/modpoll-slave

Утилита mbscan - быстрый поиск Modbus устройств на линии

· 5 min read
dmn
maintainer

247 адресов за 2.5 секунды, ноль зависимостей, один .c файл. Рассказываем, зачем мы написали свой сканер Modbus-шины и как он работает.


Проблема: «а что вообще висит на шине?»

Кто работал с Modbus RTU, знает ситуацию: подключаешь шлюз к RS-485 шине, а там десяток устройств с неизвестными адресами. Или один датчик, но кто-то поставил ему адрес 117 вместо документированного 1. Или устройство просто не отвечает — и непонятно, проблема в адресе, скорости, чётности или в самом устройстве.

Стандартный подход - mbpoll или любой Modbus-клиент, которым вручную перебираешь адреса. Это работает, но медленно и неудобно: 247 возможных адресов, на каждый нужно отправить запрос, подождать таймаут, проверить ответ.

Мы решили автоматизировать это одной утилитой.

Репозиторий: github.com/lab240/mbscan


Что такое mbscan

mbscan - консольная утилита для сканирования Modbus RTU шины. Открывает последовательный порт, последовательно опрашивает диапазон адресов функцией FC03 (Read Holding Registers) и выводит найденные устройства с содержимым регистров.

Один файл на C, никаких внешних библиотек. Встроенная реализация CRC16, POSIX-совместимый код. Работает на Linux x86_64, aarch64, OpenWrt, Raspberry Pi — везде, где есть терминальный API POSIX.

Быстрый старт

# Сканируем всё на /dev/ttyUSB0 (по умолчанию: 115200-8N1, таймаут 100мс)
mbscan -p /dev/ttyUSB0

# Быстрый скан с таймаутом 10мс
mbscan -p /dev/ttyUSB0 -o 10

# Конкретный диапазон, читаем 4 регистра
mbscan -p /dev/ttyUSB0 -f 1 -t 30 -c 4

# 9600 бод, чётность 8E1
mbscan -p /dev/ttyS1 -b 9600 -d 8E1

Вывод выглядит так:

mbscan: scanning /dev/ttyUSB0 115200-8N1, addresses 1-247, timeout 100ms
mbscan: reading 4 register(s) starting at 0

Found slave 125: [0]=125 [1]=1 [2]=830 [3]=794

mbscan: done. Found 1 device(s).

Нашёл устройство на адресе 125, прочитал 4 регистра — готово.


Параметры

mbscan -p PORT [опции]

-p PORT Последовательный порт (обязательный)
-b BAUD Скорость (по умолчанию: 115200)
-d PARAMS Формат данных: 8N1, 8E1, 8O1, 7E1 и т.д. (по умолчанию: 8N1)
-f FROM Начальный адрес (по умолчанию: 1)
-t TO Конечный адрес (по умолчанию: 247)
-o MS Таймаут на адрес в мс (по умолчанию: 100)
-r REG Начальный регистр, 0-based (по умолчанию: 0)
-c COUNT Количество регистров для чтения (по умолчанию: 1)
-v Подробный вывод
-h Справка

Как это работает внутри

Алгоритм прямолинейный, но дьявол в деталях:

  1. Открывает последовательный порт, настраивает скорость, чётность, количество стоп-битов через termios.

  2. Для каждого адреса в диапазоне:

    • Сбрасывает буфер порта от предыдущих данных
    • Формирует 8-байтовый запрос Modbus RTU FC03 с CRC16
    • Отправляет запрос и ждёт ответ с настроенным таймаутом
    • Валидирует ответ: проверяет CRC, адрес slave, код функции
    • Если всё сходится — выводит найденное устройство с содержимым регистров
  3. Между запросами выдерживает межкадровую паузу Modbus (3.5 символьных времени) — это требование протокола, без него устройства могут путать конец одного кадра и начало другого.

CRC16 реализован встроенный - нет зависимости от libmodbus или других библиотек. Весь код в одном файле mbscan.c.


Скорость сканирования

Скорость определяется таймаутом на адрес. Если устройство не отвечает — ждём полный таймаут. Если отвечает — переходим к следующему сразу после получения ответа.

ТаймаутПолный скан (1–247)Когда использовать
10 мс~2.5 секКороткие кабели, лабораторный стенд
50 мс~12 секБольшинство установок
100 мс~25 секПо умолчанию, надёжно
200 мс~50 секДлинные линии RS-485

На практике 10 мс хватает для стенда с коротким кабелем. Для промышленных линий с десятками метров RS-485 лучше ставить 50-100 мс — на длинных линиях задержки растут из-за переотражений и ёмкости кабеля.


Сборка

Нативная компиляция

cd src
gcc -O2 -Wall -o mbscan mbscan.c

Статическая сборка (один бинарник без зависимостей):

gcc -O2 -Wall -static -o mbscan mbscan.c

Пакет для OpenWrt

Каталог mbscan кладётся в дерево пакетов OpenWrt:

cp -r mbscan /path/to/openwrt/package/
cd /path/to/openwrt
echo "CONFIG_PACKAGE_mbscan=y" >> .config
make package/mbscan/compile -j$(nproc)

Результат — .ipk (или .apk) пакет в bin/packages/*/base/.

Кросс-компиляция для aarch64

Если есть тулчейн OpenWrt:

/path/to/openwrt/staging_dir/toolchain-aarch64_generic_gcc-*/bin/aarch64-openwrt-linux-gcc \
-O2 -Wall -static -o mbscan-linux-aarch64 src/mbscan.c

Готовые бинарники для x86_64 и aarch64 доступны на странице Releases.


Интеграция с luci-app-mbpoll

mbscan - не просто самостоятельная утилита. Он используется как бэкенд для вкладки Scan Bus в веб-интерфейсе luci-app-mbpoll - нашем LuCI-приложении для опроса Modbus-устройств.

Схема простая: пользователь задаёт параметры порта и диапазон адресов в браузере, LuCI вызывает mbscan на устройстве, парсит вывод и отображает найденные устройства в таблице. Не нужно заходить по SSH, не нужно помнить синтаксис - всё через веб-интерфейс.

Репозиторий luci-app-mbpoll: github.com/lab240/luci-app-mbpoll


Где используется

Основная платформа - промышленные IoT-шлюзы NapiLab Napi на базе Rockchip RK3308 под управлением OpenWrt. Napi имеет встроенный RS-485 на /dev/ttyS1 и два USB-порта для дополнительных адаптеров — типичная конфигурация для Modbus-шлюза.

Но mbscan работает на любом Linux с последовательным портом: обычный x86_64 с USB-RS485 адаптером (CH341, CP2102, FTDI), Raspberry Pi, любая embedded-плата.


Лицензия

GPL-2.0 - как и OpenWrt, как и остальные наши инструменты.