The Abrobot is a cheap ESP23 board with tiny display, widely available on Aliexpress.My project called abrobot uses this board in ESPHOME and is a normal ESPHOME Bluetooth proxy… bluetooth_proxy: active: true connection_slots: 3 It is also a BLE activity counter The OLED shows the number of devices heard during the preceding 120 seconds.and an MQTT BLE event reporter It publishes: abrobot/ble with JSON output such as: { "event": "new", "mac": "5C:53:10:AB:60:99", "name": "IoTorero Sensor", "rssi": -83 } or: { "event": "rejoined", "mac": "AA:BB:CC:DD:EE:FF", "name": "", "rssi": -57 } rejoined doesn’t mean the physical device was switched off and back on.
It means that the Abrobot had previously seen the MAC, subsequently heard nothing from it for 120 seconds, removed it from the active cache, and then saw it again There’s a Web UI display and LED.The Web UI gives you: Onboard LED and Display Toggle Clearly you get very little info on that display (which can be turned on and off along with the blue LED, in the WebUI.If you want to see new and rejoined devices in detail – an MQTT subscription to abrobat/ble will do the job.
Here is a sample output from the WebUI Here is the complete ESPHOME code.Just a point here – I used to be lazy and put passwords in my code – I still could as no-one else in my home would know what to do with them – but honestly – just having a secrets file in ESPHOME keeps the lot in one place.If using ESPHOME within Home Assistant, don’t confuse that secrets file with the one in Home Assistant itself.
# ======================================================== # Abrobot-based BLE Proxy with display # August 2026 Peter Scargill and credit to ChatGPT # v1.0 - prior to developing the TTGO version with scanner # https://tech.scargill.net/ttgo-t-display-esphome-ble-scanner-and-bluetooth-proxy/ # ======================================================== esphome: name: abrobot friendly_name: abrobot esp32: board: esp32-c3-devkitm-1 framework: type: esp-idf # Enable logging #logger: # level: INFO logger: level: DEBUG # Enable Home Assistant API api: ota: - platform: esphome password: !secret abrobot_ota_password wifi: ssid: !secret wifi_ssid password: !secret wifi_password # Enable fallback hotspot (captive portal) in case wifi connection fails ap: ssid: "Abrobot Fallback Hotspot" password: "ih2TLODxSA25" mqtt: id: mqtt_client broker: !secret mqtt_broker username: !secret mqtt_username password: !secret mqtt_password topic_prefix: abrobot log_topic: null captive_portal: web_server: port: 80 local: true i2c: id: bus_i2c sda: GPIO5 scl: GPIO6 scan: true frequency: 800kHz globals: - id: display_on type: bool restore_value: yes initial_value: 'true' - id: ble_device_count type: int restore_value: no initial_value: '0' - id: onboard_led_state type: bool restore_value: no initial_value: 'true' time: - platform: sntp id: current_time timezone: Europe/Brussels servers: - 0.pool.ntp.org - 1.pool.ntp.org - 2.pool.ntp.org display: - platform: ssd1306_i2c id: ssd1306_display model: "SSD1306 72x40" rotation: 0 update_interval: 1s lambda: |- if (!id(display_on)) { return; } if (id(onboard_led_state)) { // BLE activity display it.printf(22, 0, id(font3), "BLE"); // Centre the device count char count[8]; snprintf(count, sizeof(count), "%d", id(ble_device_count)); int x; if (id(ble_device_count) < 10) { x = 30; } else if (id(ble_device_count) < 100) { x = 23; } else { x = 16; } it.printf(x, 15, id(font1), "%s", count); } else { // Normal clock display it.strftime(4, 1, id(font1), "%H:%M", id(current_time).now()); it.strftime(7, 23, id(font2), "%d/%m/%y", id(current_time).now()); } // Flashing underline if (id(current_time).now().second % 2 == 0) { it.horizontal_line(0, 39, 72); } font: - id: font1 file: "fonts/Roboto-Black.ttf" size: 25 - id: font2 file: "fonts/Roboto-Black.ttf" size: 13 - id: font3 file: "fonts/Roboto-Black.ttf" size: 18 output: - platform: gpio pin: number: GPIO8 inverted: true id: gpio_8 light: - platform: binary name: "Onboard LED" id: onboard_led output: gpio_8 restore_mode: ALWAYS_ON on_turn_on: - lambda: |- id(onboard_led_state) = true; on_turn_off: - lambda: |- id(onboard_led_state) = false; switch: - platform: template name: "Display Toggle" id: display_toggle restore_mode: RESTORE_DEFAULT_ON lambda: |- if (id(display_on)) { return true; } else { return false; } turn_on_action: - globals.set: id: display_on value: 'true' turn_off_action: - globals.set: id: display_on value: 'false' esp32_ble_tracker: scan_parameters: interval: 1100ms window: 1100ms active: true on_ble_advertise: then: - lambda: |- // Active devices: devices heard within the last 120 seconds static std::map<std::string, unsigned long> devices_seen; // Historical devices: everything seen since boot static std::map<std::string, bool> devices_known; unsigned long now = millis(); std::string mac = x.address_str(); std::string name = x.get_name(); int rssi = x.get_rssi(); // Was this device already active? bool was_active = (devices_seen.find(mac) != devices_seen.end()); // Have we ever seen this device since boot? bool was_known = (devices_known.find(mac) != devices_known.end()); // ------------------------------------------------ // MQTT EVENT // ------------------------------------------------ if (!was_known) { // First time we've ever seen this MAC char payload[256]; snprintf( payload, sizeof(payload), "{\"event\":\"new\",\"mac\":\"%s\",\"name\":\"%s\",\"rssi\":%d}", mac.c_str(), name.c_str(), rssi ); id(mqtt_client).publish( "abrobot/ble", payload ); ESP_LOGI( "ble_mqtt", "NEW BLE: %s %s RSSI %d", mac.c_str(), name.c_str(), rssi ); // Add to historical list devices_known[mac] = true; } else if (!was_active) { // We've seen it before, but it wasn't active // in the last 120 seconds: it has REJOINED.char payload[256]; snprintf( payload, sizeof(payload), "{\"event\":\"rejoined\",\"mac\":\"%s\",\"name\":\"%s\",\"rssi\":%d}", mac.c_str(), name.c_str(), rssi ); id(mqtt_client).publish( "abrobot/ble", payload ); ESP_LOGI( "ble_mqtt", "REJOINED BLE: %s %s RSSI %d", mac.c_str(), name.c_str(), rssi ); } // ------------------------------------------------ // UPDATE ACTIVE DEVICE CACHE // ------------------------------------------------ devices_seen[mac] = now; // ------------------------------------------------ // REMOVE DEVICES NOT HEARD FOR 120 SECONDS // ------------------------------------------------ for (auto it = devices_seen.begin(); it != devices_seen.end(); ) { if (now - it->second >= 120000) { it = devices_seen.erase(it); } else { ++it; } } // ------------------------------------------------ // UPDATE DISPLAY COUNT // ------------------------------------------------ int new_count = devices_seen.size(); if (new_count != id(ble_device_count)) { id(ble_device_count) = new_count; ESP_LOGI( "ble_count", "BLE devices in last 120 seconds: %d", new_count ); } bluetooth_proxy: active: true connection_slots: 3
Read More