Tracking the location of Bluetooth beacons using ESP32 and a home made 2.4 GHz directional antenna
To make the experience fit your profile, pick a username and tell us what interests you.
We found and based on your interests.
directional.mp4Demonstrating the directional antenna with an Esp32 sending iBeacon packetsMPEG-4 Video - 2.18 MB - 01/13/2023 at 23:41 |
|
I mostly copied the design from Here.
Using the calculator for a frequency of 2440 MHz (Bluetooth goes from 2400 to 2483) and 13 turns to get a beam angle of 30 degrees.
I fixed the PVC tube to the center of the PCB copper clad plate with a few tie wraps. The copper side is on the same side as the tube, but I only had double sided plate so that's what I used.
I put a mark on the tube at 1-2 mm from the plate near the hole for the cable, then every 27.6 mm.
I recomment coiling the #12 AWG wire on a slightly smaller tube to shape it before putting it over the PCV tube.
I placed the wire to approximately the correct spacing on the tube, then glued the bottom of the wire near the plate, leaving a small bit to be soldered on the coax UF.L cable. The cable core is soldered to the helix, the cable shield is soldered to the copper plate.
After this I aligned the wire on the marks and put some electrical tape and hot glue to hold it in place. Then drilled two holes in the plate to fix it to a piece of 1.5"x1.5" wood.
This step is a bit tricky, some boards already come with an external antenna, but most Esp32 board are by default connected to the small onboard pcb antenna. You need to remove the small resistor (it's a 0 ohm resistor) and solder the two other traces instead.
I used a very small copper wire and a magnifying glass to get it done, solder alone didn't want to connect the two dots.
There are some Esp32-CAM kits that already have the correct external antenna connection.
This is the code for everything on a single Esp32 Board.
Since I didn't have the correct board, I needed to use two of them, and they communicate through the serial port.
The Bluetooth packets address and raw data get printed to the Arduino Serial Monitor.
The starting point was the BLE_Beacon_Scanner example code.
#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>
#include <BLEEddystoneURL.h>
#include <BLEEddystoneTLM.h>
#include <BLEBeacon.h>
#include <MCUFRIEND_kbv.h>
// Connections between 3.5" TFT and ESP32-WROOM
/*#define LCD_RD 2 //LED
#define LCD_WR 4
#define LCD_RS 15 //hard-wired to A2 (GPIO35)
#define LCD_CS 33 //hard-wired to A3 (GPIO34)
#define LCD_RESET 32 //hard-wired to A4 (GPIO36)
#define LCD_D0 12
#define LCD_D1 13
#define LCD_D2 26
#define LCD_D3 25
#define LCD_D4 17
#define LCD_D5 16
#define LCD_D6 27
#define LCD_D7 14*/
#define BLACK 0x0000
#define BLUE 0x001F
#define RED 0xF800
#define GREEN 0x07E0
#define CYAN 0x07FF
#define MAGENTA 0xF81F
#define YELLOW 0xFFE0
#define WHITE 0xFFFF
#define MinRSSI -100 // Ignore devices below this signal strength, -80 is better if there's too many signals
#define MaxRSSI -30 // Full length rssi bar will be displayed at this rssi value
#define RangeRSSI (MaxRSSI-MinRSSI)
#define MaxBarLength 100 // 100 pixel rssi bar
#define BarCol 210 // Bar x position on screen
#define MaxAge 20 // Forget device after some time of inactivity = MaxAge*LoopTime
#define NTrackMax 30 // Max number of devices displayed depending on screen and text size
#define LoopTime 500 // display refresh period in milliseconds
MCUFRIEND_kbv tft;
SemaphoreHandle_t dataSemaphore; // For data exchange between Bluetooth and Display threads
uint32_t previousT, currentT;
#define ENDIAN_CHANGE_U16(x) ((((x)&0xFF00) >> 8) + (((x)&0xFF) << 8))
int scanTime = 10; //In seconds, not important since it's in a loop anyway
BLEScan *pBLEScan;
struct Device{
char Address[18];
int rssi;
uint8_t V;
uint8_t age;
bool allocated;
bool updated;
// uint8_t mData[10];
// uint8_t Nbytes;
};
Device DeviceList[NTrackMax]; // List of displayed devices
Device DeviceList2[NTrackMax];
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks
{
void onResult(BLEAdvertisedDevice advertisedDevice) // Executed every time a BLE device is detected
{
char Address[18];
int rssi = -200;
uint8_t mData[100];
uint8_t V = 0;
sprintf(Address, "%s", advertisedDevice.getAddress().toString().c_str());
Serial.print(Address);
Serial.print(" ");
if(advertisedDevice.haveRSSI() == true){
rssi = advertisedDevice.getRSSI();
Serial.printf("RSSI: %d ", rssi);
}
uint8_t *payLoad = advertisedDevice.getPayload();
size_t payLoadLen = advertisedDevice.getPayloadLength();
Serial.print("DATA: ");
for (int idx = 0; idx < payLoadLen; idx++)
{
Serial.printf("%X ", payLoad[idx]);
}
/* if(advertisedDevice.haveServiceUUID()){
BLEUUID devUUID = advertisedDevice.getServiceUUID();
Serial.print("UUID ");
Serial.print(devUUID.toString().c_str());
Serial.print(" ");
}
if(advertisedDevice.haveName()){
Serial.println(advertisedDevice.getName().c_str());
Serial.print(" ");
} */
if(advertisedDevice.haveManufacturerData() == true){ // Manufacturer data is the same as the payload but without the first two bytes
std::string strManufacturerData = advertisedDevice.getManufacturerData();
int Nbytes = strManufacturerData.length();
strManufacturerData.copy((char *)mData, Nbytes, 0);
/* if(Nbytes == 25 && mData[0] == 0x4C && mData[1] == 0x00){
Serial.print("iBeacon ");
}
for (int i = 0; i < Nbytes; i++){
Serial.printf("%X ", mData[i]);
} */
if(Nbytes <= 10 && mData[0] == 0x4C){ // Might depends on country
V = 1;
}
}
Serial.println();
if(rssi >= MinRSSI){ // && V == 1){ // If true, add device to the list, if it's not full already, && V to only display V
xSemaphoreTake(dataSemaphore, portMAX_DELAY);
bool found = false;
for(int i=0; i<NTrackMax; i++){
if(strcmp(DeviceList[i].Address, Address) == 0){ // If address found in list
found = true;
DeviceList[i].rssi = rssi;
DeviceList[i].V = V;
DeviceList[i].age = 0;
DeviceList[i].allocated = true; // In case we are in a deleted slot of the same device
DeviceList[i].updated = true;
}
}
if(!found){ // New device not found in the list placed in first unallocated slot
for(int i=0; i<NTrackMax; i++){
if(DeviceList[i].allocated == 0){
strcpy(DeviceList[i].Address, Address);
DeviceList[i].rssi = rssi;
DeviceList[i].V = V;
DeviceList[i].age = 0;
DeviceList[i].allocated = true;
DeviceList[i].updated = true;
break;
}
}
}
xSemaphoreGive(dataSemaphore);
}
}
};
void setup()
{
Serial.begin(115200);
BLEDevice::init("");
pBLEScan = BLEDevice::getScan(); //create new scan
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks(), true); // true = don't ignore duplicate device packet within a scan
pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster
pBLEScan->setInterval(100);
pBLEScan->setWindow(99); // less or equal setInterval value
uint16_t ID = tft.readID();
tft.begin(ID);
tft.fillScreen(BLACK);
tft.setTextColor(WHITE, BLACK);
tft.setTextSize(2);
tft.setTextWrap(false);
dataSemaphore = xSemaphoreCreateMutex();
xSemaphoreGive(dataSemaphore);
previousT = millis();
xTaskCreatePinnedToCore(DisplayTask, "DisplayTask", 10000, NULL, 2, NULL, 0); // Create a task on core 0 for the display
delay(500);
}
void loop()
{
BLEScanResults foundDevices = pBLEScan->start(scanTime, false); // flase = clear previously scanned devices
}
void DisplayTask( void * pvParameters ){
while(true){
// do{ // Maintain constant Loop Time
// currentT = millis();
// }while(currentT-previousT < LoopTime);
// previousT = currentT;
delay(500);
// Copy the data to be displayed, Bluetooth thread can write in DeviceList anytime.
xSemaphoreTake(dataSemaphore, portMAX_DELAY);
memcpy(DeviceList2, DeviceList, sizeof(DeviceList));
for(int i=0; i<NTrackMax; i++){
if(DeviceList[i].allocated){
DeviceList[i].age++;
if(DeviceList[i].age > MaxAge){
DeviceList[i].allocated = false; // Delete devices who didn't emit in past 10 second
}
DeviceList[i].updated = false;
}
}
xSemaphoreGive(dataSemaphore);
uint16_t row, barLength, color;
for(int i = 0; i < NTrackMax; i++){ // Concaténer des string à la place
row = i*16;
if(DeviceList2[i].allocated){
if(DeviceList2[i].updated){
tft.setCursor(0, row);
tft.print(DeviceList2[i].Address);
barLength = (DeviceList2[i].rssi-MinRSSI)*MaxBarLength;
barLength = constrain(barLength/RangeRSSI, 1, MaxBarLength);
if(DeviceList2[i].V == 0){
tft.fillRect(BarCol, row+2, barLength, 12, GREEN);
}else{
tft.fillRect(BarCol, row+2, barLength, 12, RED);
}
tft.fillRect(BarCol+barLength, row+2, MaxBarLength-barLength, 12, BLACK);
}
}else{
tft.fillRect(0, row, tft.width(), 16, BLACK); // Erase line
}
}
}
}
Create an account to leave a comment. Already have an account? Log In.
Become a member to follow this project and never miss any updates