Диплом специалиста ИБ. Часть №4 — Мобильное приложение Smart Connect

от автора

Небольшое предисловие

Привет, Хабр!

Данная статья является четвертой в цикле “Диплом специалиста ИБ”, в рамках которого я рассказываю про свой опыт написания выпускной квалификационной работы на программе высшего образования “Компьютерная безопасность”. В предыдущей статье я описывал процесс разработки портативного устройства IoT “SmartPulse” с реализацией механизмов защиты из методики обеспечения безопасности устройств Интернета вещей, которая была предложена мной в первой части данного цикла статей. В текущей статье речь пойдет про создание мобильного приложения Smart Connect, с помощью которого я реализовал управление разработанными ранее устройствами Интернета вещей.

Если вы не успели ознакомиться с предыдущими статьями данного цикла, советую сначала прочитать их для того, чтобы лучше понимать контекст происходящего.

Мобильное приложение Smart Connect

Мобильное приложение для IoT-устройств по версии нейросети DALL-E

Мобильное приложение для IoT-устройств по версии нейросети DALL-E

После того, как были разработаны и собраны два устройства Интернета вещей: SmartLight без реализации механизмов защиты и SmartPulse с реализацией данных механизмов, возникла потребность создания приложения, с помощью которого можно было бы наиболее комфортным образом взаимодействовать с ними.

Так как мне раньше не приходилось писать какой-либо софт под мобильные платформы, я решил остановиться именно на мобильном приложении. Мне хотелось, чтобы в рамках написания выпускной квалификационной работы, я смог приобрести новый опыт, а не просто сделать “на отвали” ради оценки. Поэтому я понял, что буду пытаться разобраться в мобильной разработке, чтобы этот новый опыт получить. Ну и на мой взгляд, с устройствами Интернета вещей наилучшим и наиболее логичным образом взаимодействовать именно посредством смартфона.

Концепт мобильного приложения

Идея мобильного приложения Smart Connect в моем понимании должна была заключаться в раздельном управлении устройствами. Мне бы хотелось проработать концепцию дашборда с отображением информации по всем устройствам на одном экране, но в рамках дипломной работы я ограничился двумя отдельными профилями. То есть, сначала на главном экране мобильного приложения должны быть отображены устройства SmartLight и SmartPulse, а затем пользователь может перейти в панель управления нужного ему устройства для дальнейшего взаимодействия с ним.

Язык программирования и фреймворк

Когда мной было принято решение писать мобильное приложение, я начал выбирать то, с чем хотел бы больше поработать. В основном выбор был между React Native или Flutter. Swift я не рассматривал в принципе из-за узкой направленности на устройства Apple (хотя в будущем думаю чуть ближе с ним ознакомиться). Знаю, что выбор звучит как-то достаточно смешно, но по факту это не так, потому что на тот момент у меня был опыт работы с React.js. Так как я никогда не пробовал свои силы в мобильной разработке, React Native мне виделся чем-то более привычным и знакомым.

В итоге я все-таки выбрал Flutter и, соответственно, язык программирования Dart. Мой выбор обусловлен наибольшей популярностью Flutter, что в итоге все же взяло верх надо мной. Могу сказать, что я совершенно не пожалел о своем решении.

Операционная система и другие тонкости

Так вышло, что я являюсь владельцем iPhone 12 Pro Max, поэтому у меня не оставалось другого выбора, кроме как писать мобильное приложение под операционную систему iOS 17.1.2.

Для того, чтобы собрать и запустить мобильное приложение Flutter на устройстве с операционной системой iOS, необходимо установить Xcode. Помимо официальной среды разработки, также нужен аккаунт разработчика Apple Developer.

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

К разрабатываемому приложению необходимо сгенерировать Bundle ID, но в этом ничего сложного нет. Главное помнить, что не стоит генерировать новый идентификатор после каждой новой сборки (если у кого-то зачем-то возникает желание так делать), так как Apple выдает ограниченное количество ID на одного разработчика в течение недели.

Больше никаких предварительных процедур для разработки под iOS мне не потребовалось. Про установку Flutter, настройку окружения и тд писать не вижу смысла. Оставляю ссылку на официальную документацию Flutter с разработкой под iOS.

Код приложения SmartConnect

По уже устоявшейся традиции код целиком можно увидеть в репозитории на GitHub, а также в приложениях к моей ВКР. В рамках статьи разберем наиболее интересные фрагменты. Привычным образом разобьем весь код на структурные блоки.

Структурный блок

Назначение и функционал

Библиотеки

Импорт библиотек

Класс MyApp

Основной класс приложения, наследующий StatelessWidget. Создание UI приложения с темной темой и начальным экраном FindDevicesScreen

Класс FindDevicesScreen

Отображение и сканирование доступных Bluetooth-устройств. Включает AppBar, RefreshIndicator и StreamBuilder

Переход к экранам управления устройствами

Логика для перехода на экраны устройств (DeviceScreen, SmartPulseDeviceScreen) в зависимости от выбора пользователя

Класс DeviceScreen

Класс для взаимодействия с SmartLight

Класс SmartPulseDeviceScreen

Класс для взаимодействия с SmartPulse

Управление Состоянием и Данными

Функции для отправки команд и чтения данных с устройств

Виджеты

Определение виджетов для отображения информации, текстовых полей и кнопок

Для реализации передачи данных по BLE я использовал flutter_blue, которую изначально необходимо прописать в pubspec.yaml. Здесь же сразу добавляем flutter_launcher_icons, чтобы в дальнейшем заменить дефолтную иконку приложения Flutter на свою.

dependencies:   flutter:     sdk: flutter   cupertino_icons: ^1.0.2   flutter_blue: ^0.8.0  dev_dependencies:   flutter_test:     sdk: flutter   flutter_lints: ^2.0.0   flutter_launcher_icons: ^0.13.1

Далее, после обновления всех зависимостей через flutter pub get, можно приступать к написанию основного кода мобильного приложения.

Так как мобильное приложение Smart Connect необходимо исключительно для взаимодействия с ранее разработанными SmartLight и SmartPulse, во время сканирования BLE-серверов мобильным устройством, искусственно ограничиваем вывод перечня доступных для подключения устройств до двух конкретных. В разработке каждого из данных устройств я в явном виде указывал наименование BLE-сервера, поэтому мы можем реализовать данный процесс просто по названию. Хотя в случае проектирования коммерческих устройств я бы не советовал так делать из-за возможных проблем, связанных с одинаковыми именами профилей устройств.

// Обработчик нажатия на элемент списка onTap: () {    if (device.device.name == 'SmartLight') {    // Осуществляем переход на соответствующий экран     Navigator.of(context).push(MaterialPageRoute(       // Переход на экран DeviceScreen       builder: (context) =>        DeviceScreen(device: device.device),     ));   } else if (device.device.name == 'SmartPulse') {       Navigator.of(context).push(MaterialPageRoute(         // Переход на экран SmartPulseDeviceScreen         builder: (context) =>           SmartPulseDeviceScreen(device: device.device),       ));   } }

Для взаимодействия с устройством SmartPulse я создал отдельный класс, в котором отделил логику управления устройством от всего остального кода.

В нем можно видеть процесс чтения значения характеристики датчика пульса. Раз в секунду считывается новое значение характеристики, идентификатор которой указан в явном виде. Затем оно записывается в переменную pulseRate. В дальнейшем значение этой переменной будем выводить на экран смартфона.

// Создание подписки на поток данных о пульсе _pulserateSubscription =          Stream.periodic(Duration(seconds: 1)).asyncMap((_) async {         // Чтение характеристики устройства (пульс)         List<int> value = await readCharacteristic(              widget.device, Guid('0000fef4-0000-1000-8000-00805f9b34fb'));           int pulseRate = (value[0]);           return pulseRate;     }).listen((pulseRate) {       // Обновление состояния виджета с новым значением пульса       setState(() {          this.pulseRate = pulseRate;       });     });

Для того, чтобы в принципе был осуществим процесс чтения содержимого какой-либо BLE-характеристики, создаем асинхронную операцию.

// Метод для чтения характеристики Future<List<int>> readCharacteristic(        BluetoothDevice device, Guid characteristicGuid) async {     // Получение списка сервисов Bluetooth устройства     List<BluetoothService> services = await device.discoverServices();     // Перебор сервисов и их характеристик     for (BluetoothService service in services) {       for (BluetoothCharacteristic characteristic in service.characteristics) {         // Проверка соответствия UUID характеристики         if (characteristic.uuid == characteristicGuid) {            // Чтение значения характеристики           List<int> value = await characteristic.read();            return value;         }       }     }     // Вывод исключения, если характеристика не найдена     throw Exception('Characteristic not found: $characteristicGuid');    }

Аналогичным образом нужно было поступить и для реализации функционала перезаписи значения BLE-характеристики, так как и в случае взаимодействия с устройством SmartPulse, и в рамках управления SmartLight, должна быть реализована возможность передачи собственных значений. В пульсометре данный функционал завязан на записи аутентификационных данных в виде пин-кода в одну из характеристик, а в светильнике таким образом реализован функционал переключения режимов работы устройства, а также все функции в ручном режиме. Если говорить про сам код, то логика тут такая же, как и в случае чтения значений характеристики. Схожесть кода как структурно, так и логически заметна невооруженным глазом.

// Метод для записи в характеристику Future<void> writeCharacteristic(        BluetoothDevice device, Guid characteristicGuid, List<int> value) async {     List<BluetoothService> services = await device.discoverServices();     // Перебор сервисов и их характеристик     for (BluetoothService service in services) {        for (BluetoothCharacteristic characteristic in service.characteristics) {         if (characteristic.uuid == characteristicGuid) {           // Запись значения в характеристику           await characteristic.write(value);            return;         }       }     }   }

Так как я продемонстрировал процесс чтения значения характеристики, то покажу и запись в характеристику.

// Метод для отправки сообщения Future<void> sendTextMessage() async {     // Получение значения из текстового поля     String text = textController.text;     // Кодирование текста (UTF-8)     List<int> value = utf8.encode(text);      try {       // Попытка записи в характеристику       await writeCharacteristic(          widget.device,         Guid('0000fef3-0000-1000-8000-00805f9b34fb'),         value,       );     } catch (e) {       // Вывод ошибки       print("Error writing to characteristic: $e");      }   }

Так выглядит передача аутентификационного пин-кода пользователя на устройство SmartPulse. После того, как пользователь запишет значение пин-кода в характеристику с указанным идентификатором, в прошивке устройства сработает скрипт проверки данного значения. Если совпадение будет найдено, то устройство запустит рассылку пакетов с характеристиками датчика пульса и заряда аккумулятора на подключенное устройство. В приложении они также отобразятся и будут выведены на экран в соответствующих полях.

После того, как получилось обеспечить возможность работы с BLE, дело было за малым — дать пользователю возможность видеть значения характеристик и передавать свои собственные. Создаем страницу устройства SmartPulse, на которую мы сможем перейти при подключении к нему. В соответствующих полях будем выводить значения переменных пульса и заряда аккумулятора, которые изначально получили из характеристик при подключении к устройству. Также оставляем текстовое поле для возможности записи в него аутентификационного пин-кода, который будет отправлен по кнопке, в обработчике события которой прописываем вызов sendTextMessage(). Данную операцию мы уже рассматривали выше; она реализует процесс записи значения в необходимую характеристику.

  @override    Widget build(BuildContext context) {     // Использование виджета Scaffold для создания базовой структуры экрана     return Scaffold(       // AppBar для отображения заголовка экрана       appBar: AppBar(          title: Text('Управление SmartPulse'),       ),       // Основное содержимое экрана, размещенное по центру       body: Center(          child: Column(           // Выравнивание элементов колонки           mainAxisAlignment: MainAxisAlignment.center,            children: <Widget>[             // Отображение текущего пульса             Text('Пульс: $pulseRate', style: TextStyle(fontSize: 24)),              // Отображение текущего уровня заряда батареи             Text('Заряд аккумулятора: $batteryLevel%',                 style: TextStyle(fontSize: 24)),             // Поле для ввода текста             Padding(                padding: const EdgeInsets.all(8.0),               child: TextField(                 // Контроллер для управления текстовым полем                 controller: textController,                  decoration: InputDecoration(                   border: OutlineInputBorder(),                   labelText: 'Пароль для аутентификации',                 ),               ),             ),             // Кнопка для отправки текстового сообщения             ElevatedButton(                onPressed: () {                 sendTextMessage();               },               child: Text('Подтвердить'),             ),             // Кнопка для отключения от устройства             ElevatedButton(                onPressed: isDeviceConnected ? disconnectDevice : null,               child: Text('Отключиться от устройства'),             ),           ],         ),       ),     );   } }

Для SmartLight все реализовано по той же логике, что и для SmartPulse. В его функционале подразумевается работа с большим числом характеристик, поэтому код получился значительно объемнее, чем в случае работы с пульсометром, но по факту структурно в нем нет ничего отличного от продемонстрированных выше фрагментов кода.

Чтобы было возможным использование BLE при запуске и работе мобильного приложения, необходимо открыть файл Runner.xcworkspace Flutter-проекта, который находится в папке ios, в Xcode. В открывшемся проекте в среде разработки переходим в info.plist и добавляем строки со значениями "NSBluetoothPeripheralUsageDescription".

Содержимое info.plist

Содержимое info.plist

Также прошу прощения у всех опытных и знающих разработчиков мобильных приложений и кому было больно смотреть на мой код. Это был мой первый опыт работы с Flutter, прошу строго меня за это не судить.

Интерфейс мобильного приложения

По сути, приложение состоит из главной страницы, на которой размещаются «плитки» устройств, к которым можно подключиться. То есть, после запуска приложения происходит сканирование всех BLE-устройств поблизости и поиск двух наших устройств по наименованию BLE-сервера (никогда так не делайте). Если находим какое-либо из устройств, то выводим плитку с ним на главный экран, чтобы можно было перейти на экран управления устройством и, собственно, взаимодействовать с ним.

Установленное приложение Smart Connect и стартовая страница с IoT-устройствамиПо сути приложение состоит из главной страницы, на которой размещаются "плитки" 

Установленное приложение Smart Connect и стартовая страница с IoT-устройствами

Меню управления SmartLight

Интерфейс меню управления SmartLight включает в себя переключение между режимами работы устройства. При включении автоматического режима (который установлен по умолчанию при подключении к устройству), ручной режим блокируется, и аналогичная ситуация происходит при переключении на ручной режим управления.

В автоматическом режиме я оставил только кнопку переключения на ручной режим, а также вывод информации с датчика освещенности.

В ручном режиме реализовано включение светового элемента на SmartLight с помощью кнопки «Белый свет» и включение цветового динамического градиента по кнопке «Градиент». Универсальная кнопка «Выключить» нужна для выключения светового элемента устройства вне зависимости от запущенного на нем эффекта.

Для включения и выключения электрохромной пленки я решил использовать переключатель (toggle switch), который также работает при любом запущенном эффекте на светодиодной матрице устройства.

Для отправки на устройство пользовательского сообщения существует текстовое поле и кнопка «Отправить сообщение». После ввода сообщения и нажатия на данную кнопку будет запущен сценарий устройства, при котором электрохромная пленка автоматически переключится на прозрачный режим, а пользовательское сообщение будет передаваться на светодиодную матрицу устройства в виде бегущей строки. После окончания отображения сообщения, устройство самостоятельно выключит электрохромную пленку и перейдет в режим ожидания следующей команды со стороны пользователя.

Кнопка «Отключиться от устройства» просто разрывает соединение со SmartLight. При этом устройство все равно будет доступно для подключения к нему через плитку на главном экране приложения.

Интерфейс меню устройства SmartLight в автоматическом и ручном режимах управления

Интерфейс меню устройства SmartLight в автоматическом и ручном режимах управления

Меню управления SmartPulse

Для взаимодействия с пульсометром SmartPulse я разместил поле для ввода аутентификационного пин-кода, который поступит на устройство в виде записи в отдельную BLE-характеристику. После подтверждения корректности пин-кода на стороне устройства в соответствующих полях на экране управления отобразятся значения пульса и уровня заряда аккумулятора портативного устройства. По кнопке «Отключиться от устройства» происходит разрыв соединения с пульсометром. При этом в данном случае повторно подключиться к устройству получится только после физической перезагрузки SmartPulse, что было сделано в рамках ограничения количества подключений к устройству в качестве одного из механизмов защиты из методики обеспечения безопасности, описанной в первой части цикла статей.

Меню устройства SmartPulse до аутентификации и после

Меню устройства SmartPulse до аутентификации и после

Демонстрация устройств и приложения

Для наглядной демонстрации взаимодействия с устройствами Интернета вещей посредством мобильного приложения я отснял и смонтировал два видео со всем реализованным функционалом.

Чтобы в процессе просмотра видео не возникало вопросов из разряда «Что тут происходит?», я решил добавить тайм-коды (они ведут на соответствующие видео на YouTube) с подробным описанием и пояснением всего, что происходит у вас на экране. Начнем с устройства SmartLight.

Устройство SmartLight

00:03-00:39 Сценарий приветствия устройства SmartLight при его включении. Происходит автоматическое включение электрохромной пленки для того, чтобы можно было прочитать приветствие, которое отображается на устройстве в виде бегущей строки. После завершения сценария, устройство выключает пленку и переходит в автоматический режим работы.

00:40-00:59 Процесс подключения к устройству начинается при открытии приложения Smart Connect, когда можно увидеть главный экран пустым. Для отображения на нем доступных для подключения устройств нужно запустить сканирование, что делается по кнопке в нижнем углу экрана. После завершения сканирования отображается устройство SmartLight, по плитке с названием которого можно перейти на экран управления устройством. При подключении к устройству на SmartLight светодиод-индикатор подключения по BLE сменит цвет свечения с синего на зеленый, что можно увидеть в видео, если немного присмотреться. Так как устройство по умолчанию переведено в автоматический режим работы, можно наблюдать, что в приложение сразу после подключения было передано значение уровня освещенности (87).

01:00-01:05 Включение светодиодной матрицы устройства при недостаточной освещенности в автоматическом режиме работы. Значения уровня освещенности также упали с 87 до 50, что видно в приложении.

01:06-01:20 Переключение режима работы устройства с автоматического на ручной и включение/выключение светодиодной матрицы по кнопке в приложении.

01:21-01:38 Запуск эффекта градиентного свечения на устройстве.

01:39-01:53 Включение и выключение электрохромной пленки с помощью приложения.

01:54-02:25 Передача пользовательского сообщения на светодиодную матрицу устройства в виде бегущей строки. В текстовое поле на экране мобильного приложения пишем сообщение «Привет, Хабр!» и отправляем его по кнопке на устройство. Срабатывает похожий на приветствие сценарий с автоматическим включением и выключением электрохромной пленки.

02:26-02:30 Разрыв соединение с устройством по кнопке в приложении.

Устройство SmartPulse

Для демонстрации работы приложения с пульсометром я вновь подключил Аню (@ShabrovaAS), потому что так было удобнее снимать, а она не особо этому сопротивлялась.

00:04-00:14 Включение устройства SmartPulse. При загрузке устройства был сгенерирован пин-код «298980» для аутентификации в приложении.

00:15-00:19 Определение пульса и заряда аккумулятора с выводом информации на OLED дисплей устройства по прикосновению к touch сенсору.

00:20-00:30 Подключение к устройству с помощью мобильного приложения. Тут все аналогично тому же этапу в демонстрации SmartLight. Информация в приложении по пульсу и уровню заряда не отображается, так как еще не был введен аутентификационный пин-код.

00:31-00:49 Аутентификация по сгенерированному пин-коду. Вводим в соответствующее поле пин-код «298980» и записываем его в характеристику с помощью кнопки. После проверки на корректность происходит запуск рассылки сервисов с характеристиками пульса и заряда аккумулятора, значение которых отображаются на экране управления SmartPulse в приложении.

00:50-00:54 Отключение от устройства. На экране ничего не очищается (надо было сделать, но я не сделал), но новые значения более не отображаются в приложении. Если присмотреться к дисплею устройства, можно увидеть, что пульс изменился, но после отключения от SmartPulse в приложении, новое значение уже не было передано на смартфон.


В следующей части данного цикла статей будет рассмотрен процесс получения несанкционированного доступа к ранее созданными IoT-устройствами SmartLight и SmartPulse, а также подведены итоги защиты и написания моей дипломной работы. 

Я благодарен всем, кто уделил время для прочтения данной статьи. Был бы безумно рад получить фидбек в виде комментариев, чтобы улучшать качество материала.

Что дальше?


ссылка на оригинал статьи https://habr.com/ru/articles/792684/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *