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

Создание и настройка Mesh-сети на собственном опыте, а также немного цифр и аналитики

Время на прочтение6 мин
Количество просмотров6.8K

Все началось с того, что на очередном обсуждении дальнейшей судьбы проекта, над которым я тогда работал, кто-то сказал: «А давайте прикрутим меш-сети, ведь это круто, модно и молодёжно!». И именно с этого момента началась моя неравная борьба с меш-сетями, из которой мы с товарищем вышли победителями. Хоть и с небольшой оговоркой.


image

Итак, что у нас имелось на текущий момент: несколько умных устройств (на базе nrf52840), уже умевших общаться с телефоном по «блютусу», странная среда разработки и я, абсолютно непонимающий, что такое меш-сети. То, как я неделю колдовал с бубном, чтобы arm-овский компилятор съел NRF mesh SDK – это отдельная история. Но после поражения в этой битве и переход на GCC дела пошли быстрее.


Давайте для начала разберемся в том, что такое меш-сеть. Это распределенная сеть, в которой каждый узел может отправлять и принимать сообщения. Узлы, по сути своей, делятся на два типа – проивижионер (создатель сети) и провижиони (рядовой узел сети). В чем их отличие: провижионер – это создатель сети, тот, кто выдает всем адреса, конфигурирует остальные узлы и вообще следит за тем, жива сеть или нет. Если по какой-то причине такой узел выпадает из сети, то сеть не умирает, но пропадает возможность удалять и добавлять узлы. В моем случае — это было равносильно смерти всей сети. Провижиони сам ничего не умеет, он способен только отправлять и принимать сообщения и то, при условии, что провижионер дал ему адрес. Выделяют два отдельных процесса: провижионинг (добавление в сеть и выделение адреса) и конфигурирование, при котором узлу сообщается кому он может слать свои сообщения. Сами сообщения бывают двух видов: юникаст и бродкаст. Каждый узел знает группу, к которой он принадлежит. Адрес этой группы задает проивижионер при конфигурировании. На основе этого адреса он фильтрует входящие сообщения, предназначенные только его группе.


На каждом узле (железке) меш-сети должна находиться модель. Модель – это сущность, которой провижионер присваивает уникальный адрес, и которая может, если она клиент, отправлять запросы, а если сервер – то отвечать на них. Для моей задачи требовалось сделать возможным, чтобы модель могла бы и принимать сообщения, и отправлять их сама. В принципе, никто не запрещает вам на одной железке создать несколько моделей с разными адресами, одна из которых будет отвечать за передачу сообщений, а другая – за прием. Единственное, о чем вам стоит помнить, что конфигурация узла происходит в полуручном режиме. То есть вы не можете написать в коде провижионера: if (state == Configure) {makegood();}. К сожалению, это является ограничением Mesh SDK, и нам нужно каждую сущность, которая «сидит» у нас на железке, конфигурировать вручную, то есть, хардкодом. Благо есть примеры из SDK. Однако идущее в комплекте приложения под android уже становится бесполезным, так как оно тоже ожидает на вашем устройстве определенные сущности и не сможет с ними работать, если они отличаются от сущностей из примеров. Но стоит отметить, что сами примеры хорошие, и вы можете без особого труда посмотреть, как происходит процесс провижионинга и конфигурирования.


Кстати, я говорил, что на вашем узле может располагаться несколько сущностей, и по умолчанию, у вас уже будет находиться health-client/server, config-client/server. Health-client находится на провижионере и отвечает за опрос health-server на обычных узлах. Этот уровень абстракции позволяет провижионеру контролировать состояние сети, кто жив, а кто мертв. Между девайсами постоянно будут летать сообщения с подобными опросами. Config-client также находится на провижионере, и его задача – просто последовательно выполнить те команды конфигурации, которые вы ему задали изначально. Никакой гибкости здесь не предполагается – что написано один раз, будет выполняться для каждого узла независимо от внешних параметров системы. Аналогично, config-server находится на стандартных узлах, и его цель – отвечать на запросы клиентов.


Итак, когда основные моменты рассмотрены, давайте перейдем к архитектуре. Я в то время находился под сильным впечатлением от прочтения Code Complete, да и уровень сокомандников не позволял написать откровенный говнокод. Тогда я решил акцентировать внимание на подобии архитектуры, а не просто запихнуть весь код в один крутой мега-класс. Ну, или сделал вид.

Вот что из этого получилось:

  • У нас есть класс BLEMesh, у которого есть методы Send и Receive, соответственно для приема и отправки сообщений по меш-сети. Также, есть метод SetGroup, с помощью которого можно выставить, к какой группе принадлежит узел. StartProvisioning, как нетрудно догадаться, делает узел провижионером и начинает создавать сеть, последовательно добавляя в нее всех соседей.
  • Чтобы не засорять главный интерфейс реализацией, а уж тем более обращениями к SDK, был создан класс BLEMEshImpl. Инстанс этого класса является полем BLEMesh, который, в свою очередь, просто обращается к нему и вызывает соответствующие методы. Здесь реализованы методы по инициализации и запуску всего стека BLE и BLE Mesh, а также регистрируется куча колбеков на разные случаи жизни. Здесь же инициализируется третья сущность – модель.
  • Модель в моем случае представляет из себя класс, отвечающий только за передачу и прием данных по меш-сети. При попытке отправить сообщение, модель проверяет, содержится ли в сообщении уникальный адрес или адрес группы и, в зависимости от этого, вызывает разные функции из SDK. Если уникальный адрес не выставлен – сообщение отправляется в группу, то есть всем.


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


В процессе разработки также выяснилось несколько интересных моментов. Класс, который у нас все время занимался записью данных во флэш, перестал работать. Почему? Да потому что BLE Mesh забирал себе все процессорное время, и бедный StorageManager бессильно возвращал коды ошибок. Предложенный же библиотекой MeshStorageManager представлял из себя обычную мапу (словарь) и не мог удовлетворить наших потребностей. Нам нужно было не просто сохранять данные куда-то в память. Нам важно было самим обозначить конкретный адрес. И поэтому, мы не нашли ничего лучше, чем просто отключать BLE Mesh на время записи во флэш.

Следующее, что стало для нас открытием – провижиони не может в ран-тайме становиться профижионером и требует перезагрузки всего узла. Возможно, все-таки существует какой-нибудь способ, но мы его не нашли.


Третий пункт плавно вытекает из нашей задачи: прикрутить BLE Mesh к уже работающему девайсу. А ведь уже был написан код, позволяющий общаться с устройством по блютус с телефона. Как же нам совместить два таких разных SDK? Хорошо, что в комплекте с SDK шел пример их сосуществования, позволивший немного упростить нашу задачу. Почему немного, а потому что она до сих пор не была нами решена. Поколдовав с настройками и приоритетами, мы пришли к следующему. Если устройство когда-то было спэйрино с телефоном, но на телефоне были удалены настройки, то повторный пейринг не проходит. Скорее всего это проблема конкретно нашей реализации, но пока это лечится только отключением пересылки событий SoftDevice в меш стек, как описано здесь. Костыли наше все. Во всех остальных случаях нет никаких проблем с пейрингом устройства и приложения, установлением подключения и работы с Mesh в роли профижионера или профижиони. Может быть в дальнейшем получится исправить эту проблему, и я добавлю заветный UPD.


Ну и настало время аналитики, которую мы делали для себя. Моим коллегой был собран следующий тестовый стенд: четыре узла, один из которых подключен проводом к ПК (через переходник USB-UART). Он будет являться провижионером в нашей сети, а также именно с него будут уходить все запросы в сеть. В том числе у нас есть планшет, с помощью которого мы будем измерять помехи в работе меш-сети, при использовании обычного блютуса. На ПК используется модифицированная программа modpoll, которая реализует тестовые сценарии и выводит в файл результаты измерений. В программе реализовано измерение круговой задержки (RTT) — интервала времени между отправкой запроса и получением ответа. Есть ограничение для круговой задержки — все что больше 1000 мс считается потерей.



Очевидно, что при совместном сосуществовании BLE и Mesh невозможно обойтись без потерь. В наше случае мы рассматривали простейший случай, когда провижионер опрашивает одного провижиони без пересылок. Для оценки выполнялось 100 запросов подряд.

В «case 1» ни одно устройство не подключено к приложению, в «case 2» провижиони подключен к приложению и имеет место некоторый трафик через BLE, а в «case 3» провижионер подключен к приложению. Первый график показывает распределение потерь, второй – распределение круговой задержки. Как мы видим из первого графика — работа провижионера на «два фронта» обходится в дополнительные 5% потерь. В случае с быстродействием получается, что, когда установлено соединение, снижается нагрузка на стек BLE (прекращается сканирование эфира), и обработка событий Mesh происходит быстрее.





Надеюсь, этот короткий обзор Mesh-сетей и анализа их производительности был вам полезен и интересен. Еще увидимся!

Теги:
Хабы:
+7
Комментарии6

Публикации

Изменить настройки темы

Истории

Работа

Программист C++
118 вакансий
QT разработчик
6 вакансий

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

PG Bootcamp 2024
Дата16 апреля
Время09:30 – 21:00
Место
МинскОнлайн
EvaConf 2024
Дата16 апреля
Время11:00 – 16:00
Место
МоскваОнлайн
Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн