legchenkov

 
<<< Назад

Фитнес браслет как маркер присутствия. ESP32+MQTT

Настройка MajorDoMo и контроллеров ESP32 с прошивкой Tasmota_bluetooth для определения того, кто в какой комнате находится.

Целью затеи было создание системы, которая бы более точно определяла присутствие пользователей/жителей дома, чтобы исключить ложные сообщения о том, что кто-то покинул территорию из-за "плавания" GPS без раздувания радиуса вокруг объекта. Но эта тема в данной статье не раскрыта. Это ещё предстоит, если вообще получится. Также здесь не рассматриваются вопросы питания ESP32 и размещения их в комнатах, хотя это тоже достаточно важные вопросы. Настройка MQTT тоже выходит за рамки статьи.

Фитнес браслеты у нас есть у всех, кроме самого младшего, поэтому я решил, что надо попробовать этим воспользоваться. Они используют блютуз с низким энергопотреблением - BLE, поэтому и инструменты нужно использовать соответствующие. И, как оказалось, Апельсинкой вполне можно обнаруживать BLE устройства которые меня интересовали. В Orange Pi PC 3 LTS оказался встроенный Bluetooth v5 адаптер (когда я его покупал меня этот вопрос не интересовал), и я решил попробовать с ним поиграться. Запустил на нём сканер и обнаружил, что эфир кишит всякого рода устройствами, которых не видно если, например, включить обнаружение блютуз устройств на телефоние. В результатах сканирования, кроме соседских фитнес браслетов, умных часов, игровых контроллеров, iBeacons и прочего, вплоть до стоящих на ближайшей парковке авто я обнаружил и свой MiBand 4.
Выяснилось, что смартфоны в целях конфиденциальности регулярно меняют свои Bluetooth MAC-адреса, делая их в этом контексте бесполезными. Но есть приложения, которые позволяют эмулировать iBeacon (например nRF Connect). Также выяснилось, что из имеющихся у нас фитнес браслетов самый разговорчивый и с сильным сигналом - это MiBand5. MiBand4 тоже хорошо обнаруживается, но с меньшего расстояния. Ещё у нас есть MiBand7Pro и он обнаруживается только если на телефоне, к которому он подключен, выключить блютуз. Либо если его перезагрузить - тогда он один раз обнаружит себя. Иначе он партизанит и общается только со своим телефоном.

Поставил я Апельсин в коридоре, с мыслью о том, что он будет видеть всех домочадцев и этого мне будет достаточно. Но оказалось, что нет. Радиус действия браслетов достаточен на пару комнат, но не на всю квартиру. Даже если Апельсинка находится в центре квартиры браслеты всё равно терялись. А MiBand7Pro однажды потерялся и не появлялся вообще. Только после заказа ESP32 c Bluetooth для пробы я разобрался, что MiBand7Pro теряется потому что скрытный, а не потому, что у Апельсинки нехватает способностей. :)

Заказал сразу три WeAct ESP32 Development Board TYPE-C CH340K WiFi+B... просто потому что понравились внешне. Подумал, что если разложить их по комнатам, то можно будет узнать кто именно в какой комнате сидит. Изначально у меня не было мысли это определять. Не то, чтобы это было нужно, но это явно интереснее, чем просто знать, что кто-то есть дома.

Первым я решил опробовать модуль BLETool, но оказалось, что он работает с определённым набором устройств, и в целом предназначен для другого. И вообще он не заработал. :) Потом я попробовал использовать модуль Bluetooth devices. Он тоже не заработал. Разобравшись почему он не работает я заставил его работать с комощью костыля (BLETool не работал по той же причине). Но этот модуль тоже мне не подошел, так как чтобы использовать данные от него нужно было писать скрипт на php с обращением к базе данных, а это сложно/лениво/не умею - нужное подчеркнуть всё. :) Возможно, напишу отдельно о том, как заставил этот модуль работать. Если коротко, то hcitool устарел и больше не поддерживается - используйте bluetoothctl.

Когда приехали мои ESP32 то я сразу прошил на них Tasmota_bluetooth. Только потому, что раньше я с этой прошивкой имел дело, а с другими нет. Как прошить можно узнать на официальном сайте. Она способна прослушивать анонсы BLE-устройств и читать их расширенные свойства, при условии, что устройства это позволяют. Также позволяет сохранять и использовать алиасы для MAC-адресов устройств, что упрощает обращение к Bluetooth-устройствам. И это оказалось очень полезно. У браслетов мак адреса постоянные, поэтому я присвоил им алиасы совпадающие с именами пользователей в классе пользователей. Топики mqtt, как и адаптеры, я назвал соответственно комнатам в классе комнат. Скриншотов не будет, так как Тасмоту я настраивал из её консоли.

Сначала нужно создать правило, которое срабатывает при загрузке системы. Оно прописывает алиасы для маков, включает фильтр маков, чтобы не видеть лишних устройств (ради интереса можно установить фильтр 3 и посмотреть сколько вокруг всего), устанавливает период сканирования, период устаревания инфы о найденных устройствах, период вывода информации о них же, включение блютуз и включение активного сканирования.
Ещё раз подчеркну: имя алиаса в Tasmota должно соответствовать имени объекта пользователя.

Rule1 ON System#Boot DO Backlog BLEAlias AAAAAAAAAAAA=Valera BBBBBBBBBBBB=Anton CCCCCCCCCCCC=Taya; BLEAddrFilter 0; BLEPeriod 30; BLEMaxAge 30; TelePeriod 30; so115 1; BLEScan0 1 endon; Rule1 1

Потом настраивются остальные параметры. Время (в примере время для Берлина), общее имя, имя устройства, имя хоста, mqtt сервер, название топика. Последний параметр для того, чтобы управлять диодом на плате. После выполнения этой команды устройство перезагрузится автоматически. Если настройку GPIO убрать, то не перезагрузится. Но перезагрузка всё равно нужна, чтобы сработали настройки из правила.

Backlog Timezone 99; TimeStd 0,0,10,1,3,60; TimeDst 0,0,3,1,2,120; FriendlyName1 Bedroom; DeviceName Bedroom; Hostname Bedroom; MqttHost 192.168.39.38; Topic Bedroom; GPIO22 256

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

Теперь тасмота будет по mqtt присылает всякое в MajorDoMo и среди прочего JSON строку, в которой есть инфа о найденных устройствах (МАК, считанное название устройства, RSSI и алиас). Сканер видит не только наши браслеты, а и другие устройства, но алиасы есть только у наших браслетов, а если алиас для мака не настроен, то в JSON не будет соответствующего ключа со значением. Это я использовал в скрипте, для фильтрации.

Далее необходимо настроить объекты в MajorDoMo для хранения требуемых свойств.
В класс "Rooms" я добавил свойство "BLEData" и метод "BLEDataUpdate", который вызывается при изменении этого свойства.
В класс "Users" я добавил свойства "InRoom" (для хранения комнаты, где находится пользователь) и "wearableDeviceRSSI" (для хранения уровня сигнала браслета).

Нужная JSON-строка приходит с устройств в топик "tele/'имя_комнаты'/BLE/BLEDevices". Соостветственно, в свойствах mqtt нужно настроить сохранение этих данных в созданных для этого свойствах комнат "BLEData". Для каждой комнаты своё устройство. При поступлении данных вызывается метод "BLEDataUpdate", который считывает свойство "BLEData" из всех комнат у которых оно не пустое и заполняет массивы для комнат и для устройств, чтобы выбрать максимальное значение RSSI для каждого пользователя и записать его и комнату, где оно было найдено, в соответствующие свойства пользователя. Кроме того, метод считывает список всех пользователей и для тех, кого не удалось обнаружить на любом из сканеров, очищаются свойства "InRoom" и "wearableDeviceRSSI". При анализе устройств, обнаруженных сканером, метод идентифицирует пользователей по алиасу, установленному в Tasmota. Если алиаса не обнаружено, то устройство игнорируется. Таким образом отсеивается лишнее.

Код метода BLEDataUpdate:

// Глобальный массив для хранения текущих значений RSSI и комнаты
$globalRSSI = [];
$globalRoom = [];

// Получение списка всех комнат с непустым свойством BLEData
$rooms = getObjectsByClass('Rooms');

// Получение списка всех пользователей
$users = getObjectsByClass('Users');

if ($users === null) {
    DebMes("No users found in class 'Users'", 'BLETracking');
} else {
    DebMes("Found users in class 'Users': " . count($users), 'BLETracking');
}

$allUserNames = array_map(function($user) {
    DebMes("User ID: " . $user['ID'] . ", User Title: " . $user['TITLE'], 'BLETracking');
    return $user['TITLE'];
}, $users);

foreach ($rooms as $roomArray) {
    $room = getObject($roomArray['TITLE']);
    $jsonString = $room->getProperty('BLEData');

    if (!empty($jsonString)) {
        DebMes("Processing room: " . $room->object_title, 'BLETracking');

        $jsonArray = json_decode($jsonString, true);

        foreach ($jsonArray as $key => $value) {
            if (isset($value['a'])) {
                $userName = $value['a'];
                $newRSSI = $value['r'];

                DebMes("Found user: $userName with RSSI: $newRSSI", 'BLETracking');

                if (!isset($globalRSSI[$userName]) || $newRSSI > $globalRSSI[$userName]) {
                    $globalRSSI[$userName] = $newRSSI;
                    $globalRoom[$userName] = $room->object_title;
                }
            }
        }
    }
}

// Обработка случая, когда пользователь не найден ни одним сканером
foreach ($allUserNames as $userName) {
    if (!isset($globalRSSI[$userName])) {
        $userObject = getObject("Users." . $userName);
        if ($userObject) {
            $userObject->setProperty('InRoom', '');
            $userObject->setProperty('wearableDeviceRSSI', '');
            DebMes("User $userName not found by any scanner. Clearing InRoom and wearableDeviceRSSI property.", 'BLETracking');
        }
    }
}

foreach ($globalRSSI as $userName => $maxRSSI) {
    DebMes("Max RSSI for user $userName is $maxRSSI", 'BLETracking');

    $userObject = getObject("Users." . $userName);

    if ($userObject) {
        DebMes("Updating wearableDeviceRSSI for user $userName", 'BLETracking');

        $userObject->setProperty('wearableDeviceRSSI', $maxRSSI);
        $userObject->setProperty('InRoom', $globalRoom[$userName]);
    } else {
        DebMes("User object for $userName not found", 'BLETracking');
    }
}

Некоторые вещи можно переделать, но это будет позже. Я планирую дополнить информацию, так как уже немного поменял некоторые вещи и обнаружил некоторые нюансы.
Например, так получилось, что я выключил сканер одной комнаты (мне понадобился usb шнур) и ушел из дома. Так как в другие комнаты я не заходил, то уровень сигнала моего браслета в отключенной комнате остался высоким и я в ней "застрял". Подключен датчик или нет - скрипту неизвестно (но можно и это сделать) и по этому последние данные отключенного датчика учитываются скриптом, что может привести к ошибкам.
Нужно подумать над тем, как обнулять свойство "BLEData" комнаты, сканер которой не выходит на связь.

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

Но хранить или не хранить историю изменений - личное дело каждого.

Оборудование:
Orange Pi PC 3 LTS
WeAct ESP32 CH340K WiFi+Bluetooth
MiBand4
MiBand5
MiBand7Pro

Софт:
MajorDoMo
Tasmota_bluetooth

И немного по поводу использования разных браслетов. Мой личный опыт ограничивается тремя имеющимися в нашей семье. Проблема на данный момент существует только с одним - MiBand7Pro. Если браслет подключен к телефону и блютуз на телефоне включен, то браслет BLE сканерами больше не обнаруживается. В приложении Zepp Life я не нашел настройки, которые бы позволяли обнаруживать его. Но если блютуз на телефоне выключить, то браслет снова начинает анонсировать себя. С MiBand4 и 5 проблем не было. Они обнаруживались с самого начала, независимо от того, были ли они сопряжены с телефоном и был ли на телефоне включен блютуз.

Обсуждение (13) (6)

Нюрнберг, Германия