Инверсия — великая вещь! Изобрети что-то одно, а потом возьми и выверни его наизнанку, получишь не менее интересный результат. Я сначала провернул такое с одной штукой, и только потом увидел, что в ТРИЗ (теория решения изобретательских задач) есть такой прием "инверсия или обратная аналогия". Век живи, век учись.
Но это все теория, а практика ставит всё на свои места...
Маяки Bluetooth Low Energy или iBeacon теперь не что-то из ряда вон. Их можно встретить на вокзалах, в аэропортах, в музеях и в торговых центрах. Как радио-инженер я участвовал в проектировании маяков и, в особенности, антенн к ним. Дело это, по-началу интересное, потом становится скучным. Нечем выделиться, ничего особо нового не изобретешь. И тут меня осенило!
Я взял свой пеленгатор (раз и два) и посмотрел на него с обратной стороны. А что, если сделать его маяком? Здесь нужно напомнить читателю, что этот пеленгатор состоит из двух антенн: одна с плавной диаграммой направленности, другая с резко меняющейся.
Это срез диаграммы направленности. В 3D она выглядит так:
Пеленгатор "наводит фокус" по разнице уровней этих двух антенн. Если интересно подробно, то можно посмотреть на Github.
Приведём небольшие фрагменты кода с логикой работы пеленгатора:
Получаем уровни с обеих антенн. Полученные уровни требуется усреднить, а после этого посчитать разницу. На самом деле это не разница сигналов, а их отношение. Но если измерять в децибелах, то будет разница.
public boolean handleInfo(WFPacket data)
{
if (data.apName.equals(ssid) && data.mac.equals(mac)) {
int idx = data.antIdx;
if (0 <= idx && idx <= 1) {
mLevels.get(idx).addLast(data.power);
while (mLevels.get(idx).size() > avgCount) {
mLevels.get(idx).removeFirst();
}
needRecalc = true;
print();
} else {
Log.d(TAG, "LevelCalculator.HandleInfo() Bad rcvIdx: " + data.antIdx);
}
} else {
return false;
}
return needRecalc;
}
public double getAvg() {
if (needRecalc) {
for (int idx = 0; idx < 2; idx++) {
double sum = 0d;
for (Double x : mLevels.get(idx)) {
sum += x;
}
int count = mLevels.get(idx).size();
if (count != 0) {
sum /= count;
}
avgLevels[idx] = sum;
}
avgDiff = Math.pow(10.0, (avgLevels[1] - avgLevels[0]) * 0.1 + 2.5); //Переводим обратно из децибелов
needRecalc = false;
}
return avgDiff;
}
Обработка "разницы". Если уровни на обеих антеннах различаются "сильно", то мы с некоторой точностью (плюс минус лапоть) направлены на источник. Чему равно это "сильно" на данный момент определяется методом научного тыка экспериментально.
private void updateLevelDiff(double levelDiff) {
long deltaTime = System.currentTimeMillis() - lastUpdateTime;
int progress = (int) Math.floor(100.0 * levelDiff); // Масштабирование для красивого отображения на экране
// Сохраняем пеленг
if (deltaTime > TIME_PERIOD) { // Мы не хотим сохранять пеленги слишком часто
if (progress < mThreshold) { // Если разница в уровнях больше порога, то мы как раз направлены на источник сигнала
addBearing();
numUpdates++;
}
lastUpdateTime = System.currentTimeMillis();
}
//Далее идёт обновление GUI
}
А теперь ИНВЕРТИРУЕМ!
Пусть на одну антенну будет излучаться маяк iBeacon с одним номером, а на другую — с другим. Тогда на мобильном устройстве можно измерить уровни обоих маяков и по разнице определить насколько близко оно находится к фокусу антенн маяка. Получается позиционирование по направлению прихода волны.
В стандарте Bluetooth версии 5 даже анонсирован похожий способ высокоточного позиционирования — Angle of Departure. До точного описания этого способа они еще не дошли, обещают в следующих версиях.
В рафинированном виде работу можно проиллюстрировать роликами: раз и два.
В приложении устанавливает порог по разнице уровней, по которому определяется, что мобильное устройство находится в воображаемом конусе с осью, совпадающей с нормалью к плоскости антенны.
Сам маяк выглядит так:
А вот рендеры внутренностей:
Красавец, не правда ли?! Внутри антенна, как в пеленгаторе WiFi, и Bluetooth SoC nRF51822. Но все было тщетно...
Далее история переходит в факап, который заключается в том, что это работает на смартфоне Nexus 5 и найти другой гаджет, работающий хотя бы так же, оказалось не очень просто. Нет, они есть, Samsung Galaxy S7, Lenovo Phab 2 Pro, и на этом список пока заканчивается. Больше "хороших" гаджетов найти у друзей и знакомых не удалось. Из "плохих" можно отметить Samsung S4 mini.
Конечно, был проверен маяк. Он излучает пакеты на две антенны по очереди с минимальным интервалом. Маленький интервал нужен, чтобы измерения относились к моментам времени, отстоящим друг от друга незначительно. Иначе нельзя будет соотнести их друг с другом.
Был записан лог с Bluetooth-снифера с использованием чудесного WireShark. Анализ лога показывает, что излучается все правильно, временные интервалы и уровни в норме. Осциллограф тоже не показал ничего неучтенного на выходе маяка.
Тем не менее, на большом числе гаджетов работает плохо. Проблема в потерях измерений. В Android-приложение была встроена диагностика приема пар пакетов. Показателем качества был сделан процент парных пакетов. Так вот, показатель от 80 до 100 наблюдался лишь на некоторых гаджетах. На остальной протестированной выборке мобильных устройств показатель был от 20 до 60. В движении соотношение уровней измерялось неточно. Была попытка скомпенсировать это увеличением частоты излучения пакетов, но это результата не дало. Что-то внутри Андроида препятствует нормальным измерениям.
Исходники всего этого безобразия доступны на Github.
Есть небольшая надежда, на то, что на iOS ситуация может быть лучше. По крайней мере однороднее на спектре мобильных устройств.
Есть также надежда, что найдется специалист, который поймет в чем проблема и подскажет решение.
А мне сейчас очень жаль, что эта идея не работает.
Автор: Игорь Царик (Igor Tsarik)