Mac Address scanner for my home.

Last Updated or created 2025-08-27

I bought 4x Xiao-S3 mini controllers. I want to place these all over my house to scan for Bluetooth and Wifi Clients. So I can do a location search for Mobile Phones, Keys and more.

Also the Bluetooth tags I used for the Scanner Game can be used!

I want to post a location to Home Assistant, But I also played with 3D view.

Using MQTT I can subscribe to the topic locator/scanner1/ble or locator/scanner1/wifi_clients

Problems I ran into.

Too many duplicates, fixed.
Can not scan Wifi when connected, so I connect every 30s.
Could not find all wifi clients, needed to scan all channels!

CODE

#include <WiFi.h>
#include <PubSubClient.h>
#include <BLEDevice.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>
#include "esp_wifi.h"

const char* mqtt_server = "MQTTSERVER";   // change to your broker
const int   mqtt_port   = 1883;
const char* mqtt_user   = "";
const char* mqtt_pass   = "";

const char* ssid     = "MYWIFI";
const char* password = "MYWIFIPASSWD";

WiFiClient espClient;
PubSubClient client(espClient);

#define MAX_BUFFER 200

struct DeviceRecord {
  String type;   // "wifi_client" or "ble"
  String mac;
  int rssi;
};

DeviceRecord buffer[MAX_BUFFER];
int bufferCount = 0;

// ====== BLE ======
BLEScan* pBLEScan;
const int bleScanTime = 3; // seconds

typedef struct {
  unsigned frame_ctrl:16;
  unsigned duration_id:16;
  uint8_t addr1[6];
  uint8_t addr2[6];
  uint8_t addr3[6];
  uint16_t sequence_ctrl;
  uint8_t addr4[6];
} wifi_ieee80211_mac_hdr_t;

typedef struct {
  wifi_ieee80211_mac_hdr_t hdr;
  uint8_t payload[0];
} wifi_ieee80211_packet_t;

void formatMAC(const uint8_t *addr, char *buf) {
  sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X",
          addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
}

// ====== Dedup ======
void addToBuffer(String type, String mac, int rssi) {
  for (int i = 0; i < bufferCount; i++) {
    if (buffer[i].mac == mac && buffer[i].type == type) {
      buffer[i].rssi = rssi;  // update latest RSSI
      return;
    }
  }
  if (bufferCount < MAX_BUFFER) {
    buffer[bufferCount].type = type;
    buffer[bufferCount].mac = mac;
    buffer[bufferCount].rssi = rssi;
    bufferCount++;
  }
}

// ====== Sniffer ======
void wifi_sniffer_packet_handler(void* buf, wifi_promiscuous_pkt_type_t type) {
  if (type != WIFI_PKT_MGMT && type != WIFI_PKT_DATA) return;

  const wifi_promiscuous_pkt_t *ppkt = (wifi_promiscuous_pkt_t *)buf;
  const wifi_ieee80211_packet_t *ipkt = (wifi_ieee80211_packet_t *)ppkt->payload;
  const wifi_ieee80211_mac_hdr_t *hdr = &ipkt->hdr;

  char mac[18];
  formatMAC(hdr->addr2, mac);
  int rssi = ppkt->rx_ctrl.rssi;

  addToBuffer("wifi_client", String(mac), rssi);
}

// ====== BLE CALLBACK ======
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    String mac  = advertisedDevice.getAddress().toString().c_str();
    int rssi    = advertisedDevice.getRSSI();
    addToBuffer("ble", mac, rssi);
  }
};

void startSniffer() {
  WiFi.mode(WIFI_STA);        // keep STA mode
  WiFi.disconnect();          // ensure not connected

  wifi_promiscuous_filter_t filter = {
    .filter_mask = WIFI_PROMIS_FILTER_MASK_MGMT | WIFI_PROMIS_FILTER_MASK_DATA
  };
  esp_wifi_set_promiscuous_filter(&filter);
  esp_wifi_set_promiscuous_rx_cb(&wifi_sniffer_packet_handler);
  esp_wifi_set_promiscuous(true);

  esp_wifi_set_channel(1, WIFI_SECOND_CHAN_NONE);
  Serial.println("Sniffer ON (starting on channel 1)");
}

void stopSniffer() {
  esp_wifi_set_promiscuous(false);
  Serial.println("Sniffer OFF");
}

void publishBuffer() {
  Serial.println("Connecting to WiFi for MQTT...");
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  unsigned long startAttempt = millis();
  while (WiFi.status() != WL_CONNECTED && millis() - startAttempt < 10000) {
    delay(200);
    Serial.print(".");
  }
  Serial.println(WiFi.isConnected() ? "\nWiFi connected" : "\nWiFi connect failed");

  if (WiFi.isConnected()) {
    client.setServer(mqtt_server, mqtt_port);
    if (client.connect("ESP32Scanner4", mqtt_user, mqtt_pass)) {
      Serial.println("MQTT connected");
      for (int i = 0; i < bufferCount; i++) {
        String payload = "{";
        payload += "\"type\":\"" + buffer[i].type + "\",";
        payload += "\"mac\":\"" + buffer[i].mac + "\",";
        payload += "\"rssi\":" + String(buffer[i].rssi);
        payload += "}";
        if (buffer[i].type == "ble")
          client.publish("locator/scanner4/ble", payload.c_str());
        else
          client.publish("locator/scanner4/wifi_clients", payload.c_str());
        delay(5);
      }
    } else {
      Serial.println("MQTT connect failed");
    }
  }
  client.disconnect();
  WiFi.disconnect(true, true);
  bufferCount = 0; // clear buffer
  Serial.println("Published & WiFi disconnected");
}

// ====== Setup ======
void setup() {
  Serial.begin(115200);

  // BLE init
  BLEDevice::init("");
  pBLEScan = BLEDevice::getScan();
  pBLEScan->setActiveScan(true);
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());

  startSniffer();
}

unsigned long lastPublish = 0;
const unsigned long publishInterval = 30000; // 30s
unsigned long lastChannelHop = 0;
uint8_t currentChannel = 1;

void loop() {
  // BLE scan while sniffing
  pBLEScan->start(bleScanTime, false);
  pBLEScan->clearResults();

  // Channel hopping !
  if (millis() - lastChannelHop > 500) {
    lastChannelHop = millis();
    currentChannel++;
    if (currentChannel > 13) currentChannel = 1;
    esp_wifi_set_channel(currentChannel, WIFI_SECOND_CHAN_NONE);
    // Serial.printf("Hopped to channel %d\n", currentChannel);
  }

  // Every 30s
  if (millis() - lastPublish > publishInterval) {
    lastPublish = millis();
    stopSniffer();
    publishBuffer();
    startSniffer();
  }
}
Spread the love

Leave a Reply

Your email address will not be published. Required fields are marked *