← BlogTutorial · Week 3 of 13

building a raspberry pi fleet observer

every hardware project starts with one device and ends with three. cloud-direct works fine for one or two — that’s week 1’s setup. around the third device you start wanting one local thing to coordinate, buffer, and forward instead of N independent connections to the internet.

this tutorial builds that. a $35 raspberry pi running mosquitto and a 40-line python bridge, two ESP32s publishing temperature over MQTT, everything landing in plexus as separate sources. ~30 minutes.

by ann schulte~14 min read
What you’ll build
ESP32 #1 (greenhouse) ─┐
                       │  MQTT       raspberry pi
                       ├─────────▶   mosquitto    ─HTTPS─▶  plexus
ESP32 #2 (fridge)   ───┘             bridge.py
                                                          (each ESP32
                                                           lands as
                                                           its own source)

three boxes, two pipes. each ESP32 publishes temperature over MQTT to a topic that includes its source_id — plexus/esp32-greenhouse/temperature, plexus/esp32-fridge/temperature. mosquitto on the pi relays MQTT locally, no plexus knowledge. bridge.py subscribes to plexus/+/+, parses the topic, and forwards each message to plexus with the right source_id attached.

the result: each ESP32 shows up in plexus as its own source, with the same metric names as week 1. week 1’s threshold monitor and week 2’s anomaly detector work unchanged — they query by source_id, and now there are real per-device source_ids.

this is one valid topology, not the only one. cloud-direct from week 1 is fine for one or two devices and one less thing to maintain. the pi earns its keep when you want local buffering during WAN flakes, a single egress connection instead of N, or somewhere to run edge logic without round-tripping to the cloud.

What you need
  • a raspberry pi 3B+ or newer~$35 new, less used. Pi 4 and Pi 5 fine, even overkill. 1 GB RAM is plenty.
  • a microSD card16 GB+, class 10. ~$8.
  • a power supplyUSB-C for Pi 4/5, micro-USB for Pi 3. official ones are reliable; cheap ones aren't.
  • week 1's ESP32 setupsame hardware, you'll re-flash it once. (no week 1 yet?)
  • the plexus API key from week 1

total new spend on the pi side: ~$50.

Step 1 of 4

flash the pi

raspberry pi imager handles this — we won’t tutorialize it.

  1. 01install raspberry pi imager, pick Raspberry Pi OS Lite (64-bit).
  2. 02click the gear icon. set hostname (e.g., plexus-pi.local), enable SSH, set username/password, pre-fill WiFi credentials.
  3. 03write the SD card. insert it, power on, wait ~60 seconds.
bash
ssh pi@plexus-pi.local

stuck on headless setup? the pi imager docs cover the screen-and-keyboard path too.

Step 2 of 4

install mosquitto and plexus-python

three things on the pi: mosquitto (MQTT broker), plexus-python (the SDK), and bridge.py (next step).

bash
sudo apt update
sudo apt install -y mosquitto mosquitto-clients

python3 -m venv ~/plexus-bridge
source ~/plexus-bridge/bin/activate
pip install plexus-python paho-mqtt

mosquitto needs to accept LAN connections — drop a small override:

bash
echo "listener 1883
allow_anonymous true" | sudo tee /etc/mosquitto/conf.d/local.conf
sudo systemctl restart mosquitto

allow_anonymous true is fine on a home LAN. for production, see going further.

verify mosquitto is listening:

bash
mosquitto_sub -h localhost -t '#' -v

leave that running in a second terminal — you’ll watch ESP32 messages arrive there in step 4.

Step 3 of 4

drop the bridge

bridge.py subscribes to MQTT, parses topics shaped plexus/<source_id>/<metric>, and forwards each message to plexus with the right source_id attached.

bridge.py
# ~/bridge.py
import json
import os
import paho.mqtt.client as mqtt
from plexus import Plexus

MQTT_HOST = os.environ.get("MQTT_HOST", "localhost")
TOPIC = os.environ.get("MQTT_TOPIC", "plexus/+/+")

clients = {}


def get_client(source_id):
    if source_id not in clients:
        clients[source_id] = Plexus(source_id=source_id)
    return clients[source_id]


def on_message(_c, _u, msg):
    parts = msg.topic.split("/")
    if len(parts) != 3 or parts[0] != "plexus":
        return
    _, source_id, metric = parts
    payload = msg.payload.decode("utf-8", errors="replace")
    try:
        value = float(payload)
    except ValueError:
        try:
            value = json.loads(payload)
        except ValueError:
            value = payload
    get_client(source_id).send(metric, value)


c = mqtt.Client()
c.on_message = on_message
c.connect(MQTT_HOST)
c.subscribe(TOPIC)
print(f"bridging {TOPIC} → plexus")
c.loop_forever()

systemd unit so it auto-starts on boot:

ini
# /etc/systemd/system/plexus-bridge.service
[Unit]
Description=Plexus MQTT bridge
After=network-online.target mosquitto.service
Wants=mosquitto.service

[Service]
Type=simple
User=pi
Environment="PLEXUS_API_KEY=plx_your_key_here"
ExecStart=/home/pi/plexus-bridge/bin/python /home/pi/bridge.py
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

then:

bash
sudo systemctl daemon-reload
sudo systemctl enable --now plexus-bridge
sudo systemctl status plexus-bridge

journalctl -u plexus-bridge -f tails the logs.

Step 4 of 4

re-flash the ESP32s for MQTT

same BME280_Dashboard sketch from week 1, with one extra line uncommented at the top:

cpp
#define PLEXUS_TRANSPORT_MQTT   // week 3: route through the pi

const char* WIFI_SSID      = "YourWiFiSSID";
const char* WIFI_PASSWORD  = "YourWiFiPassword";
const char* MQTT_HOST      = "192.168.1.42";        // your pi's LAN IP
const char* SOURCE_ID      = "esp32-greenhouse";    // unique per device

install one extra library: PubSubClient by Nick O’Leary, via Sketch → Include Library → Manage Libraries.

flash both ESP32s, each with its own SOURCE_ID (e.g., esp32-greenhouse, esp32-fridge). serial monitor shows:

serial monitor
WiFi connected. IP: 192.168.1.78
MQTT connected to 192.168.1.42:1883
PUB plexus/esp32-greenhouse/temperature 22.3

your second terminal (running mosquitto_sub) now shows the messages arriving at the pi:

mosquitto_sub
plexus/esp32-greenhouse/temperature 22.3
plexus/esp32-fridge/temperature 4.8
plexus/esp32-greenhouse/humidity 54.1

both ESP32s now show up in plexus as their own sources, streaming temperature, humidity, pressure. week 2’s anomaly detector points at any of them by changing one constant.

Going further

three directions

  • multi-pi setups. one pi per site (lab, greenhouse, warehouse). each pi runs its own bridge with its own API key; nothing else changes.
  • edge-side filtering. bridge.py is plain python — add a filter in on_message to drop noisy metrics before they leave the LAN, or downsample 10 Hz traffic to 1 Hz before forwarding. saves bandwidth and storage.
  • when to put compute on the pi vs the cloud. rule of thumb: if the logic depends on local state (per-device baselines, smoothing, cross-sensor correlations on this site), the pi is the natural home. if it depends on fleet-wide context, the cloud is.

next week: time-series storage. how plexus stores all this, and what your options are if you want more of it locally.

Get the code

clone, run, file an issue if it breaks.

bridge.py, plexus-bridge.service, the modified BME280_Dashboard.ino with the MQTT switch, and a README live in the plexus-tutorials repo on GitHub.

Building a Raspberry Pi fleet observer | Plexus