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