Подробное описание всех компонентов модуля заняло бы слишком много места и навряд ли имеет практическую ценность, поэтому мы будем рассматривать самые общие принципы функционирования с акцентом на использование обратных вызовов. Полностью проект можно посмотреть здесь: https://github.com/Tkachenko-vitaliy/Callbacks/tree/master/Sensor.
6.1. Разработка архитектуры
6.1.1. Техническое задание
Первый вопрос, который должен быть задан перед началом разработки чего бы то ни было, звучит следующим образом: что мы будем разрабатывать и что мы хотим в итоге получить? Этот вопрос совсем не тривиальный, как может показаться вначале. Без ясного осознания конечной цели, без четкого понимания свойств и характеристик, которыми должна обладать проектируемая система, разработка может растянуться до бесконечности: происходят постоянные переделки, доработки, хаотичная реализация все новых и новых функций с не очень понятной ценностью, и т. п. В итоге, вместо результата мы сосредотачиваемся на процессе, а конечная цель пропадает где-то за горизонтом. Не сталкивались с такими проектами? Что ж, вам крупно повезло; чтобы также везло в дальнейшем, и подобные проекты в вашей карьере отсутствовали, любое проектирование нужно начинать с постановки целей, которые выражаются в требованиях, предъявляемых к системе. В нашем случае они будут следующими.
Разработать модуль управления датчиками, который должен обеспечивать:
1. Настройку конфигурации датчиков и возможность ее изменения в процессе работы.
2. Отслеживание состояния и определение неисправности датчиков.
3. Считывание показаний отдельных датчиков.
4. Считывание показаний всех работоспособных датчиков.
5. Асинхронный опрос показаний.
6. Возможность получения минимальных и максимальных значений для группы датчиков.
7. Настройка пороговых значений показателей и уведомление при их превышении.
8. Возможность работы как с реальными физическими датчиками, так и с их программными моделями.
6.1.2. Сценарий функционирования
Базовый сценарий функционирования модуля следующий.
Основным компонентом, поставляющим информацию, являются датчики. Они могут производить измерения трех типов: текущее, сглаженное и производное. Для идентификации датчикам присваиваются уникальные номера.
Перед началом работы производится настройка, т. е. определяется состав датчиков, с которых будут сниматься показания. Настройка не статическая, она может изменяться в процессе работы.
В любой момент приложение может запросить показания датчиков как в синхронном, так и в асинхронном режиме. Показания возвращаются только для функционирующих датчиков, в приложении должна иметься возможность проверить их работоспособность.
Коммуникация с датчиками осуществляется через протокол USB либо Ethernet путем пересылки / получения команд в соответствии с заданным протоколом.
В процессе работы модуль должен отслеживать и уведомлять приложение о том, что некоторые показатели превышают заданные пороговое значение. Состав измеряемых значений и их предельные величины настраиваются приложением.
В соответствии с описанием структура системы может быть представлена следующим образом (Рис. 26).
Рис. 26. Структурная схема
Приложение через интерфейс обращается к функциям модуля. В зависимости от вызываемой функции интерфейс обращается к соответствующим компонентам и возвращает результат.
Компонент «Асинхронный вызов» предназначен для выполнения асинхронных вызовов. «Наблюдатель» предназначен для отслеживания пороговых значений. «Контейнер» хранит список датчиков. Компонент «Датчик» через компонент «драйвер» обращается к аппаратному обеспечению.
6.1.3. Декомпозиция системы
Итак, в соответствии методологией объектно-ориентированного анализа необходимо определить состав классов и связи между ними, отражающие предметную область. Нам будут необходимы следующие классы:
• класс для работы с датчиком;
• контейнер для хранения указанных классов;
• драйвер, обеспечивающий низкоуровневое взаимодействие с аппаратурой;
• очередь для выполнения асинхронных запросов;
• класс для отслеживания пороговых значений;
• интерфейсный класс, который будет взаимодействовать с приложением для вызовов соответствующих функций модуля.
Обобщенная диаграмма классов модуля представлена на Рис. 2735.
Рис. 27. Обобщенная диаграмма классов
Класс ISensorControl объявляет интерфейс модуля, класс SensorControl реализует указанный интерфейс. SensorControl содержит классы Observer (отслеживает пороговые значения), CommandQueue (очередь комманд для асинхронных запросов), SensorContainer (реализует контейнер для хранения классов для работы с датчиком).