Набросок серверной части система сбора информации с устройств.

Введение

ESD v0.1 - это набросок приложения, осуществляющего сбор информации с различных устройств. Приложение написано для ОС Linux с использованием С++11.

Не стоит рассматривать проект как имеющий какую-либо практическую ценность. Несомненно, гораздо практичнее будет использовать любую из множество других существующих систем. Например, из бесплатных имеется OpenSCADA. Это же лишь изложение концепции системы сбора информации, с частичным обоснованием принятых решений. Можно его использовать как отправную точку для своей системы, но предварительно стоит уделить внимание описанным недостаткам.

Описание файлов и классов примера

Core - база приложения (основные интерфейсы).

  • IDevice - для возможности манипулирования устройствами (в т.ч. загружаемыми из библиотек расширений) все они должны иметь некой общий интерфейс.
  • IDeviceFactory - фабрика классов очевидна. Изоляция кода создания устройства от его клиентов.
  • IDeviceValuesPicker - унифицированный интерфейс получения данных от устройства. Позволит абстрагироваться от способа передачи данных (будь то бинарный, текстовый, XML или JSON для удалённых клиентов, или SQL для сохранения в БД).
  • ISettings - интерфейс получения настроек. Позволит абстрагироваться от способа их хранения (текстовые или двоичные файлы, архивы, базы данных или удалённая загрузка).
В моей реализации IDeviceValuesPicker идентификация параметров осуществляется целым числом. Лучшей гибкости и читаемости можно было бы добиться при использовании строковых идентификаторов. Не стану оправдывать такое решение "производительностью", я просто не стал использовать шаблоны в интерфейсах.

Device - всё то, что позволит оперировать устройствами.

  • DeviceConfig - Вспомогательный класс для конкретной реализации DeviceManager для передачи настроек создаваемым устройствам. Обусловлен необходимостью добавить к настройкам ID устройства.
  • DeviceIdImpl - реализация вспомогательных средств для двух типов идентификаторов устройств: целое число и строка текста. Выбор осуществляется на стадии компиляции. Примесь выделена в сущность с целью минимизировать изменения в остальном коде, при переключение на другие варианты идентификаторов (например, GUID);
  • DeviceManager - реализация контейнера для устройств с одним потоком опроса их всех.
  • DeviceValuesPicker2OStream - класс получения данных от устройства с передачей их в поток вывода.
  • SimplestDeviceFactory - самая простая реализация фабрики устройств, с жёстко прописанными всеми поддерживаемыми типами.
Вариантов реализации DeviceManager множество. Всё зависит от желаемой простоты реализации, количества поддерживаемых устройств и зависимостей между ними. Так, например, создание ~1000 потоков в системе может вызвать трудности, и быть избыточным. С другой стороны, последовательный опрос медленных устройств (большой ping для сетевых, или низкая скорость передачи по RS232/485) не желателен, так как приведёт к существенному увеличению времени обновления данных. А подключение нескольких устройств к одной шине MODBUS не позволит производить их опрос одновременно (нельзя отправить второй запрос, пока не получен ответ на первый, или не вышел его тайм-аут). После получения дополнительной информации можно произвести адаптацию класса, и возможно создать их коллекции для достижения большей гибкости. А на данной стадии все существующие устройства опрашиваются мгновенно, поэтому один поток на всех является самым рациональным.

Devices - реализация устройств.

  • DeviceBase - общая часть для всех поддерживаемых устройств. Позволяет оставить интерфейс IDevice чистым, и избежать дублирования кода в классах.
  • DeviceRnd - демонстрационный пример устройства. Формирует случайное число в заданном диапазоне.
  • UnknownDevice - заглушка для неподдерживаемых или неизвестных типов устройств. Можно использовать для редактирования настроек или исключения из опроса устройств, созданных в другой версии приложения.
Следует отметить причины создания класса UnknownDevice. С одной стороны, вполне логично аварийное завершение приложения, при ошибке в конфигурации. Если она есть, значит дальнейшая работа приложения не является безопасной (аналог синего экрана в ОС Windows). С другой стороны, вполне возможно, что в результате смены версии системы или отключения загрузки дополнительных библиотек, какие-то типы устройств стали недоступны. Логично предоставить пользователю возможность получить о них информацию или отключить их стандартными средствами системы (своеобразный kernel panic в ОС Linux - что-то не так, но у вас есть возможность продолжать работу).

Net - средства работы с сетью.

  • Socket - обёртка над системными сокетами. Для удобства использования с коде C++ и для локализации изменений при переходе на другую ОС (например, Windows).
  • TcpClient - класс для работы с клиентским TCP/IP подключением. На данный момент не используется.
  • TcpServer - класс серверного TCP/IP сокета. Текущее ограничение IPv4.
Ограничение IPv4 введено умышленно, но может быть снято. Ввиду простоты системы и отсутствия авторизации, а так же незащищённости большинства промышленного оборудования, является небезопасной их работа в глобальной сети Интернет. Так многие счётчики электроэнергии, дизельные установки, кондиционеры и прочее оборудование не имеют паролей доступа или передают их открытым текстом. Такие устройства можно подключать только в закрытых сетях предприятий. При необходимости же получения доступа к самому серверу сбора информации через сеть интернет c IPv6 можно организовать VPN или доступ через прокси-сервер. Такое решение дополнительно позволит зашифровать трафик.

Threading - многопоточность.

  • PThreadMutexLockable - обёртка над pthread_mutex. Для блокировок в многопоточной среде, посредством Utils::Lock.
  • Thread - обёртка над pthread. Для локализации изменений при переходе на другую ОС (например, Windows).
Класс Thread был создан, когда ещё не планировалось ограничиваться C++11, а следовательно и был недоступен std::thread. В дальнейшем переделывать не стал, т.к. интерфейс данного класса ещё не определён. В общем и целом, вся эта директория требует серьёзной переработки по мере развития проекта.

User - удалённое подключение пользователей к приложению.

  • TelnetPacketBuilder - формирователь запросов из потока данных. Для конкретной реализации UserManager. Отправленные по TCP/IP данные могут быть разбиты на несколько пакетов, или наоборот быть объединены. Необходимо средство для выделения отдельных запросов пользователя.
  • UserManager - обслуживание удалённых подключений. В текущей реализации в один момент времени обрабатывается только одно подключение типа telnet. Множество одновременных подключений нет.
Класс UserManager в данной реализации совмещает множество разных сущностей. В дальнейшем их надо будет выделить (в том числе и интерфейсы, как пользователя, так и менеджера). По мере развития предполагается изменение протокола обмена, или одновременное использование нескольких. Имеет смысл переход на JSON или XML, или же реализация FastCGI. Всё зависит от выдвигаемых к системе требований.

Utils - набор утилит. Более общий код.

  • CommandExecutor - класс реализации командной строки. Разбивает текстовую строку на аргументы, выполняет поиск среди зарегистрированных команд и вызывает соответствующую функцию. Используется в примере для консоли и для удалённого подключения.
  • Lock - вспомогательный класс (шаблонный) для упрощения написания кода с блокировкой объекта в многопоточной среде. Сами средства блокировки должен предоставлять блокируемый объект, например, наследуясь от Threading::PThreadMutexLockable.
  • SettingsFile - реализация класса для доступа к настройкам, хранящимся в файле (набор строк: ключ = значение).
  • SettingsGroupProxy - вспомогательный класс для доступа к группе настроек (группа.ключ = значение). Позволяет так же при обращении скрыть несколько уровней иерархии настроек.
  • Utils - набор функций преобразования. Регистр шрифта, целых, времени и строк. В разных версиях C++ такие преобразования выполняются по разному.
Классы SettingsFile и SettingsGroupProxy имеет смысл перенести в пространство имен Settings.

Остались нерассмотренными ещё несколько файлов.

  • esd0.cpp - содержит функцию main, а в ней много строк кода. Это тестовый пример.
  • config.h - определяет только тип DeviceId.
  • esd.cfg - файл конфигурации для пробного запуска.

У рассмотренной модели отсутствует ещё несколько необходимых компонентов.

  • Нет механизма установки сбоев по показателям устройств.
  • Нет механизма протоколирования показателей устройств.
  • Нет механизма передачи устройствам управляющих воздействий.
Без данных компонентов, такая система фактически бессмысленна.

Недостатки реализации

Самые существенные недостатки приведённого примера:

  • нет обработки ошибок выполнения;
  • нет возможности управлять составом опрашиваемых устройств и их настройками во время работы приложения;
  • отсутствие какой-либо проверки прав доступа для удалённых запросов;
  • отсутствие шифрования каналов связи;
  • отсутствие журналов аварий и сбоев;
  • отсутствие журналов показателей устройств;
  • отсутствие возможности передачи команд устройствам;
  • нет ни единого поддерживаемого реального устройства;
  • приложение не выполняет какой-либо полезной работы;
  • и ещё множество других недостатков.

Да, написав столько кода, мы по прежнему имеем сырое и бесполезное приложение. Каждый файл, каждый компонент требует доработки, а точнее серьёзной переработки. В итоговом приложение от данного примера не должно остаться практически ничего.

Скачать

Исходный код esd0_src.tar.gz (27 кБ).
Собранный проект esd0_test.tar.gz (503 кБ)
Лицензия BSD (без гарантий, с возможностью коммерческого использования, не выдавать мой код за написанный вами).

Навигация по разделу