Работа с Modbus рано или поздно упирается в одно и то же: ты сидишь перед стендом, что-то не отвечает, и нужно быстро понять — проблема в проводе, в адресе, в карте регистров или в твоём коде. Инструментов для этого много, но каждый раз что-то не складывалось: одни заточены только под опрос устройств (master) и не умеют ничего больше, другие живут в мире «только Windows», третьи платные, четвёртые выглядят так, будто их последний раз обновляли в эпоху Windows XP, а у пятых slave-симулятор и master — это два разных продукта, которые нужно ставить и настраивать по отдельности. А я работаю на macOS и хотел один инструмент на все случаи жизни.

В какой-то момент я решил: хватит приспосабливаться, напишу свой. Так появился Modbus Connector — кроссплатформенный (macOS / Windows / Linux) GUI-инструмент для отладки Modbus-шины и разработки Modbus-устройств. В этой статье расскажу, почему он выглядит именно так, какие функции в него закладывались и как шла разработка.

Главное окно
Главное окно

Что мне было нужно

Требования выкристаллизовались из практики, и главное из них — три режима работы в одной программе, переключаемые на лету:

Master — классика: подключился к устройству по TCP или RTU, составил таблицу регистров, опрашиваешь с интервалом, пишешь значения. Здесь важны мелочи: человекочитаемые форматы (float32/64 с разным порядком байт, ASCII, signed/unsigned), масштабирование с единицами измерения, подсветка изменившихся значений, разные Unit ID и интервалы поллинга на строку — когда на одной шине RS-485 висит пять устройств, без этого никак.

Slave — встроенный симулятор устройства. Когда разрабатываешь master-часть (скажем, интеграцию с хабом умного дома), реального устройства под рукой часто нет. Я хотел редактируемую карту значений, чтобы мой же софт мог её опрашивать и записывать — плюс правила-выражения, которые меняют значения по формуле на каждом тике: [temperature] * 1.1, счётчик на prev + 1, случайные значения через rand(). Получился «виртуальный стенд» в одной вкладке.

Sniffer — пассивное прослушивание RTU-шины через отдельный serial-адаптер. Это режим «подключись к чужой работающей паре мастер-слейв и посмотри, что там вообще происходит»: программа сама разбирает кадры, восстанавливает карту регистров из подслушанного трафика и раскладывает устройства по вкладкам — у каждого Unit ID своя таблица с актуальными значениями, спарклайнами и своим логом. Найденную карту можно выгрузить в CSV и тут же импортировать в master-вкладку.

Сниффер
Сниффер

Поверх этого хотелось повседневных мелочей: живые графики с маркерами и статистикой, выражения над значениями строк, алармы с правилами и звуком, логирование в CSV/JSONL, сканер Unit-адресов и адресов регистров, сравнение снапшотов «было/стало» (бесценно, когда ищешь, какой регистр меняется при нажатии кнопки на устройстве), сырой лог трафика, тёмная тема и русский/английский интерфейс.

Отдельная история — шаблоны устройств. Карты регистров популярных устройств (счётчики Eastron, частотники Delta, инверторы Huawei, контроллеры заряда EPEver, модули Wiren Board) лежат в программе из коробки: выбрал шаблон — получил новую вкладку с заполненной таблицей и настройками подключения. Причём статусные и аварийные регистры аннотированы по документации производителя: вместо 0x0016 ты видишь имя ошибки, а битовые поля аварий раскладываются в именованные флаги. Из этой же оперы — пользовательские «имена значений»: 0 = Stopped, 1 = Running — и значение в таблице превращается в выпадающий список.

Как я это писал

Стек. Python 3.11 + PySide6 (Qt) — хотелось родной внешний вид на всех трёх платформах и быструю разработку. Modbus-стек — pymodbus, причём строго синхронные клиенты: вся протокольная логика живёт в отдельных модулях без Qt (backend.py, sim_backend.py, sniffer_backend.py), а Qt-слой — тонкие worker-объекты в своих QThread, общающиеся с виджетами сигналами. Никакой Modbus-логики в виджетах — это правило я соблюдал жёстко, и оно окупилось.

Тестируемость с первого дня. Вся доменная логика — форматы, парсинг, планировщик объединённых чтений, выражения, алармы, CSV — написана чистым Python без Qt и покрывается тестами без GUI. Для backend-тестов поднимается настоящий Modbus TCP-сервер на pymodbus в отдельном потоке — то есть тесты гоняют реальный протокол по реальному сокету, а не моки. Сейчас в проекте 689 тестов, и они не раз ловили регрессии до релиза. Qt-тесты гоняются offscreen, в CI — по одному процессу на тестовый файл: у Qt на CI случаются флаки-падения, изоляция процессов это лечит.

Скорость опроса. Полезная оптимизация: автообъединение чтений. Строки таблицы с соседними адресами (одинаковый Unit ID и область) склеиваются в один Modbus-запрос — вместо 30 запросов на 30 регистров уходит один-два. На медленной шине 9600 бод разница в темпе опроса — разительная. Если устройство отказывается читать «окном» — автоматический откат на поштучные чтения.

Кроссплатформенная сборка. Программа должна запускаться у коллеги без Python вообще. Поэтому CI на каждый тег собирает: .app + DMG для macOS, zip с exe для Windows, AppImage и deb-пакет для Linux, плюс публикация в PyPI (pip install modbus-connector). Всё это гоняется GitHub Actions, руки не касаются артефактов.

Что получилось

Примерно месяц работы, ~190 коммитов, ~13 тысяч строк кода, 26 файлов тестов. Программой я пользуюсь сам каждый день — собственно, многие функции (сниффер, snapshot diff, именованные биты) родились из конкретных задач на стенде: «блин, опять пришлось считать биты в уме — всё, делаю нормально».

Из ближайших планов — подпись Windows-сборок (SmartScreen пока ворчит на неподписанный exe), публикация в winget и Homebrew.

Приглашаю

Проект открытый (MIT), живёт на GitHub: github.com/cramen/modbus_connector. Буду рад ишью и пулл-реквестам — особенно новым шаблонам устройств: если ты отладил карту регистров своего железа, её добавление в каталог — это один JSON-файл. Баг-репорты, идеи функций и замечания по UX тоже приветствуются.

Если ты работаешь с Modbus — попробуй. Буду рад, если инструмент сэкономит тебе пару вечеров.