Как стать автором
Обновить

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

Уровень сложностиПростой
Время на прочтение13 мин
Количество просмотров2.4K

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

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

Данная статья является четвертой в цикле “Диплом специалиста ИБ”, в рамках которого я рассказываю про свой опыт написания выпускной квалификационной работы на программе высшего образования “Компьютерная безопасность”. В предыдущей статье я описывал процесс разработки портативного устройства 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, а также подведены итоги защиты и написания моей дипломной работы. 

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

Что дальше?

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

  1. Диплом специалиста ИБ. Часть №1 - Методика обеспечения безопасности устройств Интернета вещей 

  2. Диплом специалиста ИБ. Часть №2 - Стационарное устройство SmartLight 

  3. Диплом специалиста ИБ. Часть №3 - Портативное устройство SmartPulse 

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

  5. Диплом специалиста ИБ. Часть №5 - Несанкционированный доступ к IoT-устройствам с BLE

С оригиналом ВКР можно ознакомиться тут

Теги:
Хабы:
Всего голосов 4: ↑4 и ↓0+4
Комментарии0

Публикации

Истории

Работа

iOS разработчик
17 вакансий
Swift разработчик
18 вакансий

Ближайшие события

15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань