Кроссплатформенный терминал Modbus TCP / RTU / ASCII с открытым исходным кодом: Часть 2
Прошло уже достаточно времени с публикации предыдущей статьи. За это время я значительно улучшил приложение. Миграция проекта с WPF на Avalonia UI, обновленный дизайн, работа с числами типа float, а также другие возможности появились в новой версии моего Modbus терминала.
Начнем с главного. Теперь программа работает и под Windows, и под Linux. Windows версия распространяется как и раньше, обернутой в инсталлер. А Linux версия обычным zip-архивом.
Основные нововведения:
Проект перенесен с WPF на AvaloniaUI.
Изменен дизайн.
Добавлен Modbus сканер.
Modbus: для каждой функции записи сделан свой вариант дизайна.
Modbus: добавлено ведение истории обмена.
Modbus: добавлена возможность работы с бинарными данными.
Modbus: добавлена возможность работы с данными типа float.
Исправлены ошибки версии 2.7.0.
Ссылки для скачивания вы можете найти в конце статьи. Но не спешите перематывать :) Советую сначала ознакомиться с материалом ниже.
Инструкция
Для режима работы «Без протокола», думаю, пояснения не нужны. Он работает как текстовый терминал. Просто и пока без изысков.
Из прошлой версии приложения перекочевали два способа взаимодействия с хостом: «Обычный» и «Цикличный опрос». Между ними можно переключаться во время работы. Данные на вкладках не теряются при переключении.
Важно: если переключиться в обычный режим пока идет цикличный опрос, то сам опрос прекратится.
Версия для режима «Без протокола»:
«Без протокола» — Обычный режим работы
«Без протокола» — Режим цикличного опроса
Версия для режима Modbus:
Modbus — Обычный режим работы
Modbus — Режим цикличного опроса
Теперь давайте поподробнее рассмотрим обычный режим работы Modbus. В этом режиме все сводится к чтению и записи регистров.
Чтение регистров Modbus
С чтением все просто. Выбираем функцию, начальный адрес, количество регистров и нажимаем кнопку «Прочитать»
Запись регистров Modbus
С записью все интереснее. Для каждой функции предусмотрен свой вариант дизайна.
Начальным адресом для всех функций является значение из поля «Адрес».
0×05 Запись одного флага
Согласно документации на протокол, в поле данных должно находится только одно из двух значений. 0×0000 — это логический ноль, а 0xFF00 — это логическая единица. Поэтому выбираем желаемое значение и нажимаем кнопку «Записать».
0×0F Запись нескольких флагов
Похожая функция, но в отличии от предыдущей позволяет записать сразу несколько флагов. С помощью кнопки «Добавить регистр» создаем нужное количество флагов, задаем значение и нажимаем кнопку «Записать».
Слева от значений регистров у нас находятся значения смещения относительно начального адреса.
Справа находятся кнопки удаления для каждого регистра.
0×06 Запись одного регистра
С помощью этой функции мы можем записывать в 16-ти разрядные регистры.
Формат записываемого числа выбирается в выпадающем списке справа от поля ввода. При смене формата число автоматически преобразуется.
0×10 Запись нескольких регистров
Управление тут аналогично функции »0×0F Запись нескольких флагов».
Из интересного у нас появляется возможность записи чисел типа float.
Как мы знаем такие числа занимают 2 слова или же 4 байта. Поэтому у следующего регистра смещение уже не +1, а +2 адреса.
Иногда бывает, что устройство может использовать нетипичный формат для расшифровки чисел типа float. И чтобы подстроиться под конкретное устройство в настройках можно выбрать нужный формат записи.
Представления
Просто значения регистров можно посмотреть в табличном представлении. Но, к сожалению, в этих числах не всегда есть смысл. И иногда их требуется «расшифровать». Поэтому для интерпретации данных в терминале предусмотрена область с представлениями.
Первым идет представление последнего запроса. Тут можно увидеть просто байты запроса и ответа. Это удобно использовать, например, когда идет отладка устройства с самодельной реализацией протокола. Или же когда полученный результат показался сомнительным, и хочется посмотреть детали обмена.
Представление последнего запроса
Название следующего представления говорит само за себя. Из интересного можно отметить разве только время отправки запроса / получения ответа. Иногда бывает полезно посмотреть при подключении по последовательному порту.
Представление истории обмена
Бинарное представление в основном нужно для удобной работы с масками. А чтобы в конце рабочего дня единицы не сливались с нулями, я решил помимо группировки, разделить биты цветом, в зависимости от их значения.
Бинарное представление
И пожалуй, пока что самое полезное представление — это представление числа типа float. Для каждых двух слов представлены все комбинации. Если же среди всех четырех вариантов не нашлось «нормального» числа (например, как в 4 адресе), то скорее всего эта пара слов не содержит float числа или же оно вот такое странное.
Представление числа типа float
При интерпретации результатов не стоит обделять вниманием табличное представление. Иногда, в совокупности с остальными, оно может дать больше полезной информации.
Немного о разработке
С точки зрения кода основное нововведение это миграция проекта с WPF на Avalonia UI.
Вот основные причины на это:
Кроссплатформенность. Это, пожалуй, самая главная причина :) Программа теперь запускается на Windows и Linux.
Современный дизайн и гибкость. Из коробки нам доступна тема Fluent, которая выглядит эстетично и современно. В сети можно найти и другие темы, что позволяет нам делать красивый дизайн. Также встроенные контролы имеют более широкие настройки. Например, чтобы сделать кнопку со скругленными краями в WPF мне пришлось переопределять шаблон кнопки, а в Avalonia UI я просто задал CornerRadius у самой кнопки.
Возможности стилизации. В Avalonia UI используются CSS-подобные селекторы. Сразу после перехода с WPF это довольно непривычно, но со временем оказывается, что применение таких селекторов очень удобно. С помощью них, можно создавать типовые стили для контролов.
Меньший размер проекта. Приведу небольшое сравнение версий для Windows.
Версия 2.7.0 WPF: инсталлер — 47 Мб, на диске — 160 Мб.
Версия 3.0.0 Avalonia UI: инсталлер — 32 Мб, на диске — 103 Мб.
Живое сообщество. Avalonia UI активно развивается сообществом. В проект добавляется новый функционал, правятся баги. Ознакомится с проектом можно в репозитории на GitHub, а попросить помощи или просто обсудить можно в русскоязычном чате в Telegram.
Далее хочу поделиться личным опытом переноса проектов с WPF на Avalonia UI.
Для начала нужно понять насколько проект большой. Если он состоит из пары полей ввода и одной кнопки, то дальше можно не читать :) Ну, а если у вас все сложнее, то первым делом необходимо переписать проект согласно паттерну MVVM… Да, я понимаю, что это может быть сложно, но без этого мигрировать проект и развивать его дальше будет еще сложнее. Поэтому из двух зол выбираем меньшее.
Для успешной миграции нам нужно заранее выделить всё что связанно с WPF в отдельные проекты.
Важно отметить, что в Avalonia нет стандартного MessageBox, как в WPF. Поэтому логику работы с ним тоже выносим в отдельный проект. Или же, как вариант, можно найти реализацию MessageBox на GitHub.
Следующим шагом мы просто удаляем все проекты с WPF. В том числе и из папки с решением.
Теперь собираем решение. Если все прошло успешно, то это значит, что у нас нет лишних зависимостей, и мы все сделали правильно.
Далее добавляем новый проект Avalonia UI в свое решение. Проводим стандартные манипуляции. Не забываем проверить версию .NET во всех проектах, а также удалить теперь ненужные NuGet пакеты связанные с WPF. Например, ReactiveUI.WPF заменяем на Avalonia.ReactiveUI.
Теперь выбираем в качестве запускаемого проект Desktop (моем случае это TerminalProgram.Desktop) и запускаем его. Появляется окошко. Ура! Мы все сделали правильно.
Настала очередь переноса UI. Важно помнить, что некоторые контролы из WPF отсутствуют в Avalonia UI. Но как правило можно найти аналог. Например, вместо Frame и Page можно использовать ContentControl и UserControl соответственно.
Также приведу несколько полезных ссылок:
Как уже было написано выше, Avalonia активно развивается сообществом. Важно понимать, что это сравнительно молодой GUI фреймворк. Поэтому в нем можно наткнуться на баги. Чаще всего они не доставляют проблем, но бывают и исключения.
Приведу пример. Я хотел, чтобы мое приложение выглядело одинаково на всех ОС. Поэтому мне пришлось сделать свой вариант окна. В Avalonia есть классная возможность отключить неклиентскую часть окна (WindowChrome). Но при этом оставить его границы, с помощью которых можно масштабировать окно (SystemDecorations=«BorderOnly»). В версии 11.2.0 эта функция замечательно себя чувствует на Windows 10/11, и совершенно отказывается работать на дистрибутивах Linux и внезапно на Windows 7. Это не критичный баг. Но он хорошо иллюстрирует, что на них можно наткнуться при разработке. Надеюсь, его исправят в ближайших обновлениях.
В своем приложении я решил просто создать аналог ResizeGrip из WPF.
Вообще для разработки кастомных окон вам будут полезны два метода класса Window:
BeginMoveDrag (PointerPressedEventArgs e) — для перетаскивания окна вслед за курсором мыши.
BeginResizeDrag (WindowEdge edge, PointerPressedEventArgs e) — для масштабирования окна вслед за курсором мыши.
Читая этот раздел, у вас может возникнуть вопрос: «А что же в итоге выбрать — Avalonia UI или WPF?» «Все зависит от задачи», — отвечу я вам. Если вам нужна кроссплатформенность, гибкость, красивый дизайн, и вы готовы мериться с редкими небольшими багами, то смотрите в сторону Avalonia UI. А если же у вас корпоративный софт на Windows машинах с дизайном «чтобы просто было», то может проще использовать Windows Forms? :)
Итого
Версия приложения 3.0.0 писалась довольно долго. Основные фичи этого обновления — это все же добавление кроссплатформенности и новый дизайн. Также я прислушался к пожеланиям пользователей в комментариях к предыдущей статье. Часть из них совпадали с моими пожеланиями и возможностями. Что и вылилось в новую версию терминала. Надеюсь, вам понравилось мое приложение. Буду рад обратной связи в комментариях.
Приложение тестировалось на Windows 10/11, Ubuntu и Astra Linux.
Смотрите также:
Терминал Modbus TCP / RTU / ASCII с открытым исходным кодом: Часть 1