A while back I reverse engineered Twang32
And even further back, I made a mobile pong game.
So I made a mobile Twang.
Now I can let passersby play Twang.
Help page
At the WHY2025 hackers event, we used bigred’s antenna pole to make a huge sign using leds.
It gave me the idea to make a pole which displays digital “fireworks” using leds.
Fireworks are becoming illegal the next year in the Netherlands, I think.
So why not going digital?
12 Meter pole, 300 Leds.
There is a QR code and a website link mentioned on a page at the gate.
10 presets to select via a website.
Notification on my TV
While waiting for the first visitor I made a magnetic game.
Using a bunch of magnets, and 3D printed hexagon rings.
Two players try to place magnets in a small area without moving the others. If magnets slam together, you have more pieces to place.
(Below, last player has to take those 6 pieces)
CODE for website index.html
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>WLED Mast</title>
<style>
body {
background: #111;
color: #fff;
font-family: Arial;
margin: 0;
padding: 20px;
text-align: center;
}
.grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 18px;
}
button {
padding: 25px;
font-size: 22px;
border-radius: 20px;
border: none;
background: #333;
color: #fff;
font-weight: bold;
}
button:active {
background: #555;
}
</style>
</head>
<body>
<h1>WLED Mast</h1>
<div class="grid">
<button onclick="run('b1')">off</button>
<button onclick="run('b2')">fireworks</button>
<button onclick="run('b3')">sound<BR>reactive</button>
<button onclick="run('b4')">bounch-balls</button>
<button onclick="run('b5')">fire</button>
<button onclick="run('b6')">juggle</button>
<button onclick="run('b7')">swing</button>
<button onclick="run('b8')">sparkle</button>
<button onclick="run('b9')">popcorn</button>
<button onclick="run('b10')">comet</button>
</div>
<script>
const API_KEY = "misuse-prevent";
function run(action) {
fetch(`/action.php?action=${action}&key=${API_KEY}`)
.then(r => r.json())
.then(j => console.log(j))
.catch(err => alert("Error"));
}
</script>
</body>
</html>
CODE for Wled API
<?php
$public_api_key = "misuse-prevent";
if (!isset($_GET['key']) || $_GET['key'] !== $public_api_key) {
http_response_code(401);
die("Unauthorized");
}
$action = $_GET['action'] ?? null;
// Map 10 buttons → WLED preset numbers
$allowed_actions = [
"b1" => 19,
"b2" => 22,
"b3" => 23,
"b4" => 20,
"b5" => 21,
"b6" => 3,
"b7" => 8,
"b8" => 2,
"b9" => 4,
"b10" => 14
];
if (!$action || !isset($allowed_actions[$action])) {
http_response_code(400);
die("Invalid action");
}
$preset = $allowed_actions[$action];
// ---- WLED IP address ----
$wled_ip = "http://WLEDDEVICEIP"; // <-- CHANGE THIS
// API endpoint
$url = $wled_ip . "/win&PL=" . $preset;
// Call WLED directly
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 2);
curl_setopt($ch, CURLOPT_TIMEOUT, 2);
$response = curl_exec($ch);
$error = curl_error($ch);
curl_close($ch);
header("Content-Type: application/json");
if ($error) {
echo json_encode(["ok" => false, "error" => $error]);
} else {
echo json_encode(["ok" => true, "preset" => $preset]);
}
Working on my garden lights
Working 12V relay bottom left, and upper right the Raspberry $ compute module board with NodeRed.
I made a little board to program the ATTiny85.
The RS485 chip I wanted to use (SN65HVD3082) came as SMD, luckily I have some SMD to THT/DIL boards. (breakoff)
Above on the breadboard : The SN65HVD3082EP on a little pcb, the ATTiny85 .
4×4 WS2812 led matrix will be my dimmable RGB garden light.
Several years ago, I used some radar boards to detect movement.
After that I used a MMWave board
These are amazing, I’m using this in my Lab to switch on my big LED panel. You don’t need to move, it will detect human presence.
Another one I’m using in my living room.
It can power off all media at night (like TV, amplifier and lights)
It’s part of my home alarm system when we are away.
Now I got a new MMWave sensor (hlk-ld2450) , it can detect up to 3 persons and their location.
Screenshot from a Bluetooth phone app reading the sensor.
New version of :
New schematic using ESPHOME and has reset button (pressing the lid).
(Resets the LED when you’ve put the trash at the street curb.)

Above, a simplified version. I’m using 4 ws2812 leds now.
ESPHOME code:
esphome:
name: afvalwemos
friendly_name: AfvalWemos
on_boot:
priority: 800
then:
- delay: 1s
- light.turn_off: main_leds
esp8266:
board: d1_mini
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "bT76oR8vOxxxxxxxxxxxxxxxxxxxxxxxxQbyjY6M="
ota:
- platform: esphome
password: "c1dbxxxxxxxxxxxxxxxxxxxxxxxxxx36e75"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Afvalwemos Fallback Hotspot"
password: "vVxxxxxxxxxxxxxxaw"
captive_portal:
# LED strip definition
light:
- platform: neopixelbus
type: RGB
variant: WS2812
pin: D5
num_leds: 4
name: "All LEDs"
id: main_leds
restore_mode: ALWAYS_OFF # ensures all LEDs start off
# Individual LED partitions
- platform: partition
name: "LED 1"
segments:
- id: main_leds
from: 0
to: 0
- platform: partition
name: "LED 2"
segments:
- id: main_leds
from: 1
to: 1
- platform: partition
name: "LED 3"
segments:
- id: main_leds
from: 2
to: 2
- platform: partition
name: "LED 4"
segments:
- id: main_leds
from: 3
to: 3
# Physical button on D6 to turn off all LEDs
binary_sensor:
- platform: gpio
pin:
number: D6
mode: INPUT_PULLUP
inverted: True
name: "All Off Button"
on_press:
- logger.log: "Button pressed — turning all LEDs OFF"
- light.turn_off: main_leds
Home Assistant automation:
alias: Afvalwijzer Plastic
description: Afvalwijzer leds
triggers:
- at: "18:00:00"
trigger: time
conditions:
- condition: template
value_template: >
{% set raw = states('sensor.gad_pmd') %} {% if raw not in ['unknown',
'unavailable', 'none', ''] %}
{% set clean = raw.replace('Tomorrow, ', '').replace('Today, ', '').strip() %}
{% set parts = clean.split(', ') %}
{% set date_part = parts[-1] if parts|length > 1 else clean %}
{% set pmd_date = strptime(date_part, '%d-%m-%Y').date() %}
{% set tomorrow = (now().date() + timedelta(days=1)) %}
{{ pmd_date == tomorrow }}
{% else %}
false
{% endif %}
actions:
- action: light.turn_on
target:
entity_id: light.afvalwemos_led_3
data:
rgb_color:
- 236
- 199
- 14
mode: single
Remember those expensive touch lights you can buy?

This is a less than 5 euro version.
Warning : Some tricks I used
TTP223 sensors are only a few cents!
And react even without touching (< 5mm)
So, you can build this in a case or behind fabric!
NOTE: If you are using an ESP32 you can configure a pin as touch!!!
So no TTP223 needed.
But ESP32 are more expensive as Wemos mini.
Code:
#define TOUCH_PIN D2 // My video has D7
#define LED_PIN D6
#define FAKE_GND D5
// Brightness steps (0 = off, 255 = full bright)
int brightnessLevels[] = {0, 25, 125, 255};
int currentLevel = 0;
bool lastTouchState = LOW;
void setup() {
pinMode(TOUCH_PIN, INPUT);
pinMode(LED_PIN, OUTPUT);
pinMode(FAKE_GND, OUTPUT);
digitalWrite(FAKE_GND, LOW);
analogWrite(LED_PIN, brightnessLevels[currentLevel]); // start OFF
}
void loop() {
bool touchState = digitalRead(TOUCH_PIN);
if (touchState == HIGH && lastTouchState == LOW) {
// Advance brightness step
currentLevel = (currentLevel + 1) % 4; // 0-3 steps
int pwmValue = brightnessLevels[currentLevel];
analogWrite(LED_PIN, pwmValue);
}
lastTouchState = touchState;
}
Dim levels less obvious on recording, but you can change the levels in the code!
We are planning to redo our garden. And I am making a water and light plan for it.
I thought I could do it myself using 12V and RS485/Modbus.
So these are my plans. (NOTE, this is a work in progress)
I’m going to put 4-wire ground cable in our garden, and a RS485/Modbus master controller in my shed.
4 Wires will have 12V low voltage, ground and RS485 A/B wires.
This way I can control till 64 devices on a single cable.
Below, a USB stick to connect the RS485 cables to a Raspberry Pi?
Software is probably going to be a NodeRed instance connected to Home Assistant.
On/Off lights using a RS485 board and relay. These can be bought on a single PCB and can control 220V. I am probably going to use generic outside lamps and refit them for 12V led or 220v, with those RS485 controllers.


The above left part will be encased in resin or alike.
Right PCB is for testing only.
For dimming RGB lights, I made the below design.
12V to 5V using a 7805, RS485 8pin DIL/DIP and a ATTiny85 8pin DIL/DIP. Plus a 4×4 RGB Matrix.
These also encased in resin.
More information on the ATTiny85 and programmer can be found here:
Modbus using NodeRed (I’ve used this to control my RD6006 Lab Power Supply)
Bare minimal to control the relay.
HA control via MQTT
December is coming, time for candles.
The plan is to make a flame sensor, with in combination with my presence sensor will alert me when we leave the room and candles are on, give us a notification.
Above is the schematic. A Wemos mini (left over from another project), an analog multiplexer and a cheap 5 times flame detector.
There is a potentiometer on this board to change the sensitivity.
Search “Infrared Ir Flame Sensor Detector Fire Detection Module 5 Channel” on Aliexpress. These are 1 euro.
Presence sensor:


Here is some Arduino code.
#define MUX_A D5
#define MUX_B D6
#define MUX_C D7
#define ENABLE D1
#define ANALOG_INPUT A0
void setup() {
Serial.begin(9600);
//Define output pins for Mux
pinMode(MUX_A, OUTPUT);
pinMode(MUX_B, OUTPUT);
pinMode(MUX_C, OUTPUT);
pinMode(ENABLE, OUTPUT);
digitalWrite(ENABLE, LOW); // No need to switch, can be permanenty low
}
void changeMux(int c, int b, int a) {
digitalWrite(MUX_A, a);
digitalWrite(MUX_B, b);
digitalWrite(MUX_C, c);
}
void loop() {
float value;
changeMux(LOW, LOW, HIGH);
value = analogRead(ANALOG_INPUT); //Value of the sensor connected Option 1 pin of Mux
Serial.print("Variable_1:");
Serial.print(value);
Serial.print(",");
changeMux(LOW, HIGH, LOW);
value = analogRead(ANALOG_INPUT); //Value of the sensor connected Option 2 pin of Mux
Serial.print("Variable_2:");
Serial.print(value);
Serial.print(",");
changeMux(LOW, HIGH, HIGH);
value = analogRead(ANALOG_INPUT); //Value of the sensor connected Option 3 pin of Mux
Serial.print("Variable_3:");
Serial.print(value);
Serial.print(",");
changeMux(HIGH, LOW, LOW);
value = analogRead(ANALOG_INPUT); //Value of the sensor connected Option 4 pin of Mux
Serial.print("Variable_4:");
Serial.print(value);
Serial.print(",");
changeMux(HIGH, LOW, HIGH);
value = analogRead(ANALOG_INPUT); //Value of the sensor connected Option 5 pin of Mux
Serial.print("Variable_5:");
Serial.println(value);
}
ESPHOME Code
esphome:
name: flamedetector
friendly_name: FlameDetector
on_boot:
priority: 600
then:
- switch.turn_off: enable
esp8266:
board: d1_mini
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "BMHbLdN6Rxxxxxxxxxxxxxxxxxxxxv0LE="
ota:
- platform: esphome
password: "780fe0xxxxxxxxxxxxxxxxxxxxx7859"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Flamedetector Fallback Hotspot"
password: "cm1xxxxxxxxxxxxNX"
captive_portal:
switch:
- platform: gpio
id: enable
pin:
number: D1
cd74hc4067:
- id: cd74hc4067_1
pin_s0: D5
pin_s1: D6
pin_s2: D7
pin_s3: D2
globals:
- id: last_flame_state
type: bool
restore_value: no
initial_value: 'false'
- id: trigger_on
type: bool
restore_value: no
initial_value: 'false'
sensor:
- platform: adc
id: adc_sensor
pin: A0
name: flameanalog
update_interval: 10s
- platform: cd74hc4067
id: flame_1_1
name: flame_1_1
number: 1
sensor: adc_sensor
update_interval: 10s
- platform: cd74hc4067
id: flame_1_2
name: flame_1_2
number: 2
sensor: adc_sensor
update_interval: 10s
- platform: cd74hc4067
id: flame_1_3
name: flame_1_3
number: 3
sensor: adc_sensor
update_interval: 10s
- platform: cd74hc4067
id: flame_1_4
name: flame_1_4
number: 4
sensor: adc_sensor
update_interval: 10s
- platform: cd74hc4067
id: flame_1_5
name: flame_1_5
number: 5
sensor: adc_sensor
update_interval: 10s
interval:
- interval: 10s
then:
- lambda: |-
const float threshold = 0.01;
bool any_above =
id(flame_1_1).state > threshold ||
id(flame_1_2).state > threshold ||
id(flame_1_3).state > threshold ||
id(flame_1_4).state > threshold ||
id(flame_1_5).state > threshold;
// Store the result
if (any_above != id(last_flame_state)) {
id(last_flame_state) = any_above;
if (any_above) {
ESP_LOGI("flame_check", "Flame detected (turning ON)");
id(trigger_on) = true;
} else {
ESP_LOGI("flame_check", "No flame detected (turning OFF)");
id(trigger_on) = false;
}
}
- interval: 1s
then:
- if:
condition:
lambda: 'return id(trigger_on);'
then:
- homeassistant.service:
service: homeassistant.turn_on
data:
entity_id: input_boolean.testtoggle
else:
- homeassistant.service:
service: homeassistant.turn_off
data:
entity_id: input_boolean.testtoggle
A while ago I made a little notify thingy using an LCD display, LED, button and a buzzer.


Some friends of mine made one also, and today I was talking to a new guy.
I could not find this project on my site .. again .. so I’ll post it now.
Some things it does right now
Doorbell pressed: Led starts blinking, backlight LCD enabled, text displayed on LCD, Buzzer sounds (or plays a RTTTL ringtone)
LCD backlight on and buzzer beep until acknowledge button pressed.
Heating for brewing: temperature on display, led on when temperature reached. Press acknowledge to start timer.
….. etc

CODE:
esphome:
name: lcdalarm
friendly_name: LCDAlarm
on_boot:
priority: -100.0
then:
- lambda: 'id(lcddisplay).no_backlight();'
esp8266:
board: d1_mini
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "OOpDzMu0VvfWAnJXXXXXXXXXXXXXXXXXXXXXXXXKNDa74OJ1Cc="
ota:
- platform: esphome
password: "627992017XXXXXXXXXXXXXXX738c2a3"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Lcdalarm Fallback Hotspot"
password: "z9dZXXXXXXXX7a"
captive_portal:
web_server:
port: 80
i2c:
sda: D2
scl: D1
scan: true
frequency: 400kHz
light:
- platform: binary
name: "Status LED"
output: light_output
icon: "mdi:led-on"
id: led
binary_sensor:
- platform: gpio
pin:
inverted: true
number: D5
mode:
input: true
pullup: true
name: "LCD Button"
text_sensor:
# Create text input helper in homeassistant and enter id below
- platform: homeassistant
entity_id: input_text.lcd_display_text
id: lcd_display_text
- platform: wifi_info
ip_address:
id: ip_address
name: IP address
mac_address:
id: mac_address
name: Mac address
ssid:
id: connected_ssid
name: Network SSID
bssid:
id: connected_
name: Network Mac
sensor:
- platform: wifi_signal
id: wifisignal
name: wifi-signal
update_interval: 300s
globals:
- id: backlight_on
type: bool
initial_value: 'false'
- id: flash_beep_on
type: bool
initial_value: 'false'
switch:
- platform: template
name: "LCD Backlight"
id: backlight
restore_mode: RESTORE_DEFAULT_OFF
turn_on_action:
- globals.set:
id: backlight_on
value: 'true'
- globals.set:
id: flash_beep_on
value: 'false'
- lambda: |-
id(lcddisplay).backlight();
- delay: 5s
turn_off_action:
- globals.set:
id: backlight_on
value: 'false'
- lambda: |-
id(lcddisplay).no_backlight();
lambda: |-
return id(backlight_on);
- platform: template
name: "Flash and Beep"
id: flash_beep
restore_mode: RESTORE_DEFAULT_OFF
turn_on_action:
- globals.set:
id: flash_beep_on
value: 'true'
- globals.set:
id: backlight_on
value: 'false'
- lambda: |-
id(lcddisplay).backlight();
- lambda: |-
id(buzzer).turn_on();
- delay: 0.75s
- lambda: |-
id(lcddisplay).no_backlight();
- lambda: |-
id(buzzer).turn_off();
- delay: 0.75s
- lambda: |-
id(lcddisplay).backlight();
- lambda: |-
id(buzzer).turn_on();
- delay: 0.75s
- lambda: |-
id(lcddisplay).no_backlight();
- lambda: |-
id(buzzer).turn_off();
- delay: 0.75s
- lambda: |-
id(lcddisplay).backlight();
- lambda: |-
id(buzzer).turn_on();
- delay: 0.75s
- lambda: |-
id(lcddisplay).no_backlight();
- lambda: |-
id(buzzer).turn_off();
- delay: 0.75s
- lambda: |-
id(lcddisplay).backlight();
turn_off_action:
- globals.set:
id: flash_beep_on
value: 'false'
- lambda: |-
id(lcddisplay).no_backlight();
- lambda: |-
id(buzzer).turn_off();
lambda: |-
return id(flash_beep_on);
- platform: restart
name: "Restart ESPhome"
output:
- platform: gpio
pin: D3
id: buzzer
- platform: gpio
pin: D6
id: light_output
display:
- platform: lcd_pcf8574
id: lcddisplay
dimensions: 16x2
address: 0x27
lambda: |-
if(id(wifisignal).has_state()) {
if (id(lcd_display_text).state != "") {
it.print(0, 0, id(lcd_display_text).state.c_str());
}
} else {
it.print(" ESPhome booting... ");
}
CODE: RTTTL (Use passive buzzer!!!!)
esphome:
name: lablcd
friendly_name: LabLCD
esp8266:
board: d1_mini
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "ga17mNCtxdiXXXXXXXXXXXXXXXXXXXXX70a5W0VPZR51Q="
actions:
- action: rtttl_play
variables:
song_str: string
then:
- rtttl.play:
rtttl: !lambda 'return song_str;'
ota:
- platform: esphome
password: "84013b9XXXXXXXXXXXXXXX5401039f7c"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Lablcd Fallback Hotspot"
password: "nnzXXXXXXXXXXXXXXXXXXXCb"
rtttl:
output: rtttl_out
on_finished_playback:
- logger.log: 'Song ended!'
captive_portal:
i2c:
sda: D2
scl: D1
scan: true
frequency: 400kHz
light:
- platform: status_led
name: "Status LED"
icon: "mdi:led-on"
pin:
number: D5
inverted: true
binary_sensor:
- platform: gpio
name: "Push Button"
pin:
number: D4
inverted: true
mode:
input: true
pullup: true
on_press:
- homeassistant.service:
service: homeassistant.toggle
data:
entity_id: input_boolean.esphome_notification_lcdlab_push_button
text_sensor:
# Create text input helper in homeassistant and enter id below
- platform: homeassistant
entity_id: input_text.esphome_notification_lcdlcd_display_text
id: lcd_display_text
- platform: wifi_info
ip_address:
id: ip_address
name: IP address
mac_address:
id: mac_address
name: Mac address
ssid:
id: connected_ssid
name: Network SSID
bssid:
id: connected_
name: Network Mac
sensor:
- platform: wifi_signal
id: wifisignal
name: wifi-signal
update_interval: 300s
globals:
- id: backlight_on
type: bool
initial_value: 'false'
- id: flash_beep_on
type: bool
initial_value: 'false'
switch:
- platform: template
name: "LCD Backlight Lab"
id: backlight
restore_mode: RESTORE_DEFAULT_OFF
turn_on_action:
- globals.set:
id: backlight_on
value: 'true'
- globals.set:
id: flash_beep_on
value: 'false'
- lambda: |-
id(lcddisplay).backlight();
turn_off_action:
- globals.set:
id: backlight_on
value: 'false'
- lambda: |-
id(lcddisplay).no_backlight();
lambda: |-
return id(backlight_on);
- platform: restart
name: "Restart ESPhome"
output:
- platform: esp8266_pwm
pin: D8
id: rtttl_out
display:
- platform: lcd_pcf8574
id: lcddisplay
dimensions: 16x2
address: 0x27
lambda: |-
if(id(wifisignal).has_state()) {
if (id(lcd_display_text).state != "") {
it.print(0, 0, id(lcd_display_text).state.c_str());
}
} else {
it.print(" ESPhome booting... ");
}
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();
}
}