ble2wled.mqtt - MQTT Beacon Listener

MQTT listener for BLE beacon data from espresense.

This module provides the EspresenseBeaconListener class for receiving beacon data from espresense via MQTT.

espresense is a distributed Bluetooth scanner that publishes BLE beacon locations to MQTT. This listener subscribes to beacon topics and updates the beacon state with RSSI values.

MQTT Topic Structure:

espresense/devices/{beacon_id}/{location}

Example

Listen for beacons from a specific location:

from ble2wled.mqtt import EspresenseBeaconListener
from ble2wled.states import BeaconState

state = BeaconState()
listener = EspresenseBeaconListener(
    state,
    broker='192.168.1.100',
    location='living_room',
    port=1883
)
listener.start()
class ble2wled.mqtt.EspresenseBeaconListener(state: BeaconState, broker: str, location: str = 'balkon', port: int = 1883, username: str | None = None, password: str | None = None)[source]

Bases: object

Listens for BLE beacon data from espresense MQTT broker.

Subscribes to espresense/devices/{beacon_id}/{location} topics and filters for messages from a specific location. Extracts beacon ID and RSSI from the topic and JSON payload.

state

BeaconState instance to update with beacon data.

Type:

BeaconState

location

Location name to filter for (e.g., ‘balkon’).

Type:

str

base_topic

Base MQTT topic prefix (‘espresense/devices’).

Type:

str

__init__(state: BeaconState, broker: str, location: str = 'balkon', port: int = 1883, username: str | None = None, password: str | None = None)[source]

Initialize espresense beacon listener.

Connects to MQTT broker and subscribes to beacon topics.

Parameters:
  • state (BeaconState) – BeaconState instance to update with beacon data.

  • broker (str) – MQTT broker hostname or IP address.

  • location (str) – Location name to filter for (e.g., ‘balkon’). Default: ‘balkon’.

  • port (int) – MQTT broker port. Default: 1883.

  • username (str) – MQTT broker username for authentication. Default: None (no authentication).

  • password (str) – MQTT broker password for authentication. Default: None (no authentication).

Example

Create and start listener without authentication:

listener = EspresenseBeaconListener(
    state,
    broker='192.168.1.100',
    location='bedroom',
    port=1883
)
listener.start()

Create and start listener with authentication:

listener = EspresenseBeaconListener(
    state,
    broker='192.168.1.100',
    location='bedroom',
    port=1883,
    username='mqtt_user',
    password='mqtt_pass'
)
listener.start()
on_connect(client: Client, userdata: object, flags: dict, rc: int) None[source]

Handle MQTT connection.

Called when the client connects to the broker. Subscribes to beacon topics and logs connection status.

Parameters:
  • client (mqtt.Client) – MQTT client instance.

  • userdata (object) – User data (unused).

  • flags (dict) – Connection flags (unused).

  • rc (int) – Connection result code (unused).

Note

This is a callback method called by paho-mqtt library.

on_message(client: Client, userdata: object, msg: MQTTMessage) None[source]

Handle incoming MQTT message from espresense.

Parses topic and payload to extract beacon ID and RSSI. Validates data consistency and updates beacon state.

Topic format: espresense/devices/{beacon_id}/{location} Payload format: {“id”: “…”, “rssi”: -50.5, …}

Parameters:
  • client (mqtt.Client) – MQTT client instance (unused).

  • userdata (object) – User data (unused).

  • msg (mqtt.MQTTMessage) – MQTT message with topic and payload.

Example

This method is called automatically by the MQTT library when messages arrive on subscribed topics.

Note

This is a callback method called by paho-mqtt library.

start() None[source]

Start listening in background thread.

Starts the MQTT client loop in a background daemon thread. Blocks until connection is established.

Example

Start listening for beacons:

listener.start()
# Listener runs in background, updating beacon state
ble2wled.mqtt.BeaconMQTTListener

alias of EspresenseBeaconListener

Overview

The mqtt module provides MQTT listener for receiving beacon data from espresense devices. It:

  • Connects to MQTT broker

  • Subscribes to beacon topic

  • Updates beacon state with position and signal strength

  • Handles connection failures gracefully

  • Supports username/password authentication

Quick Example

from ble2wled.mqtt import EspresenseBeaconListener
from ble2wled.states import BeaconState

# Create beacon state tracker
state = BeaconState()

# Create MQTT listener
listener = EspresenseBeaconListener(
    state,
    broker='localhost',
    location='living_room'
)

# Start listening (non-blocking, runs in background)
listener.start()

# Beacon state is updated automatically as messages arrive
import time
time.sleep(5)
print(f"Active beacons: {len(state.beacons)}")

# Stop listener
listener.stop()

Classes

EspresenseBeaconListener

Listens for beacon data from espresense devices via MQTT.

Attributes:

  • beacon_state (BeaconState) - Beacon state to update

  • broker (str) - MQTT broker hostname or IP

  • port (int, default 1883) - MQTT port

  • location (str) - Location/room identifier

  • username (str, optional) - MQTT username

  • password (str, optional) - MQTT password

  • client - paho-mqtt client instance

Methods:

  • start() - Start listening (non-blocking, runs in background thread)

  • stop() - Stop listening and disconnect

Behavior:

Subscribes to topic: espresense/devices/{location}/#

Handles message format:

{
  "distance": 2.5,
  "id": "device_id",
  "mac": "aa:bb:cc:dd:ee:ff",
  "rssi": -45,
  "name": "Device Name",
  "timestamp": 1234567890
}

Example:

from ble2wled.mqtt import EspresenseBeaconListener
from ble2wled.states import BeaconState

state = BeaconState()

# Basic listener
listener = EspresenseBeaconListener(
    state,
    broker='192.168.1.50',
    location='bedroom'
)
listener.start()

# Listener with authentication
listener_auth = EspresenseBeaconListener(
    state,
    broker='192.168.1.50',
    port=1883,
    location='bedroom',
    username='mqtt_user',
    password='mqtt_password'
)
listener_auth.start()

Configuration

The MQTT topic is derived from location:

  • Location: living_room

  • Topic: espresense/devices/living_room/#

This subscribes to all devices reporting from that location.

Topic format:

espresense/devices/{location}/{mac_address}

Messages contain beacon distance, RSSI, and device info.

MQTT Message Format

espresense messages contain:

{
  "distance": 2.5,
  "id": "device_id",
  "mac": "aa:bb:cc:dd:ee:ff",
  "rssi": -45,
  "name": "Device Name",
  "timestamp": 1234567890
}

The listener extracts:

  • id - Unique beacon identifier

  • distance - Estimated distance from device (meters)

  • rssi - Signal strength (dBm)

Examples

Basic MQTT Listening

from ble2wled.mqtt import EspresenseBeaconListener
from ble2wled.states import BeaconState
import time

# Setup
state = BeaconState()
listener = EspresenseBeaconListener(
    state,
    broker='localhost',
    location='test'
)

# Start listening
listener.start()
print("Listening for beacon data...")

# Let it collect data
time.sleep(10)

# Check collected beacons
print(f"Received {len(state.beacons)} beacons")
for beacon_id, beacon in state.beacons.items():
    print(f"  {beacon_id}: position={beacon.position}, rssi={beacon.rssi}")

# Stop listening
listener.stop()

With Configuration

from ble2wled.config import Config
from ble2wled.mqtt import EspresenseBeaconListener
from ble2wled.states import BeaconState

# Load configuration from .env
config = Config('.env')

# Create listener with config
state = BeaconState(
    timeout_seconds=config.beacon_timeout_seconds,
    fade_out_seconds=config.beacon_fade_out_seconds
)

listener = EspresenseBeaconListener(
    state,
    broker=config.mqtt_broker,
    port=config.mqtt_port,
    location=config.mqtt_location,
    username=config.mqtt_username,
    password=config.mqtt_password
)

listener.start()

With WLED Animation

from ble2wled.mqtt import EspresenseBeaconListener
from ble2wled.states import BeaconState
from ble2wled.wled import WLEDUDPController
from ble2wled.animation import run_wled_beacons

# Setup
state = BeaconState()
listener = EspresenseBeaconListener(
    state,
    broker='localhost',
    location='living_room'
)
controller = WLEDUDPController('192.168.1.100', 60)

# Start MQTT listener
listener.start()

# Run animation (blocks)
try:
    run_wled_beacons(
        controller,
        led_count=60,
        beacon_state=state
    )
finally:
    listener.stop()

With Authentication

from ble2wled.mqtt import EspresenseBeaconListener
from ble2wled.states import BeaconState

state = BeaconState()

# With credentials for secured MQTT broker
listener = EspresenseBeaconListener(
    state,
    broker='192.168.1.50',
    port=1883,
    location='kitchen',
    username='mqtt_user',
    password='secure_password'
)

listener.start()

Multiple Locations

Monitor multiple locations with separate listeners:

from ble2wled.mqtt import EspresenseBeaconListener
from ble2wled.states import BeaconState

# Create state for each location
living_room_state = BeaconState()
kitchen_state = BeaconState()

# Create listeners
living_room_listener = EspresenseBeaconListener(
    living_room_state,
    broker='localhost',
    location='living_room'
)

kitchen_listener = EspresenseBeaconListener(
    kitchen_state,
    broker='localhost',
    location='kitchen'
)

# Start both
living_room_listener.start()
kitchen_listener.start()

# Process both state streams...

# Stop both
living_room_listener.stop()
kitchen_listener.stop()

Error Handling

Connection Failures

The listener handles connection failures gracefully:

from ble2wled.mqtt import EspresenseBeaconListener
from ble2wled.states import BeaconState

state = BeaconState()
listener = EspresenseBeaconListener(
    state,
    broker='192.168.1.100',  # Non-existent broker
    location='test'
)

listener.start()  # Doesn't raise, just logs error
# Check logs for connection errors

Authentication Failures

from ble2wled.mqtt import EspresenseBeaconListener
from ble2wled.states import BeaconState

state = BeaconState()
listener = EspresenseBeaconListener(
    state,
    broker='localhost',
    location='test',
    username='wrong_user',
    password='wrong_password'
)

listener.start()  # Doesn't raise, but won't receive messages
# Check logs: "MQTT authentication failed"

Troubleshooting

No Beacons Received

  1. Check MQTT broker is running:

    mosquitto_sub -h localhost -t "espresense/devices/test/#"
    

    If this doesn’t show messages, broker isn’t receiving espresense data.

  2. Verify location matches espresense configuration

  3. Check authentication credentials

  4. Check network connectivity:

    ping <mqtt-broker-ip>
    

Connection Refused

  1. Verify broker is running and accessible

  2. Check port (default 1883)

  3. Verify IP/hostname is correct

  4. Check firewall rules

Authentication Failed

  1. Verify username and password are correct

  2. Check user account exists in MQTT broker:

    # Mosquitto
    sudo mosquitto_passwd -l /etc/mosquitto/passwd
    
  3. Check broker authentication is enabled

Performance

Message Rate

MQTT listener receives messages for each detected beacon. Typical rates:

  • 1-5 beacons: 10-50 messages/second

  • 10+ beacons: 100+ messages/second

The listener is non-blocking and handles high message rates efficiently.

State Updates

Beacon state is updated immediately as messages arrive. No polling needed.

Examples per Second

  • Low: 1-2 messages/second (1-2 beacons with slow scan)

  • Medium: 10-20 messages/second (3-5 beacons)

  • High: 100+ messages/second (10+ beacons with fast scan)

See Also