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();
}
}




















