Raspberry Pi

Raspberry Pi Sense HAT MQTT: Publish Environment and IMU Data, Show Messages on the LED Matrix

Turn a Raspberry Pi Sense HAT into a two-way MQTT device: publish environment and orientation readings as JSON, and scroll messages sent from any MQTT client across the 8x8 LED matrix.

Raspberry Pi with a Sense HAT scrolling a message on its LED matrix while sending sensor data to the cloud in a raspberry pi sense hat mqtt project
Run this on your own MQTT brokerGet a private, managed MQTT broker on the Free plan and start from ready-made code samples.
Try it free Code samples No card required

A Raspberry Pi Sense HAT MQTT project is one of the quickest ways to build a real two-way IoT device: the board already carries temperature, humidity and pressure sensors, an accelerometer, gyroscope and magnetometer, a joystick and an 8x8 RGB LED matrix. In this tutorial you will publish environment and IMU data as JSON to an MQTT broker and scroll any message sent to the Pi across the LED matrix, all in one Python script.

What the Sense HAT Gives You

Sensor or output Python call Units
Temperature sense.get_temperature() degrees Celsius
Relative humidity sense.get_humidity() percent
Pressure sense.get_pressure() millibar (hPa)
Orientation sense.get_orientation() pitch, roll, yaw in degrees
Acceleration sense.get_accelerometer_raw() x, y, z in g
Joystick sense.stick.get_events() direction and action
LED matrix sense.show_message(), sense.clear() 8x8 RGB

The HAT plugs straight onto the 40-pin header of any Pi with that header, including the Pi 4 and Pi 5. No wiring is needed, which makes it ideal for classrooms, demos and quick prototypes. The official Sense HAT documentation covers the hardware in depth.

Installing the Software

The Sense HAT library ships as a Debian package. Install it system-wide, then create a virtual environment that can see it:

sudo apt update
sudo apt install -y sense-hat python3-venv
mkdir -p ~/sensehat-mqtt && cd ~/sensehat-mqtt
python3 -m venv --system-site-packages .venv
source .venv/bin/activate
pip install "paho-mqtt>=2.0"

Check that the HAT is detected before writing any MQTT code:

python -c "from sense_hat import SenseHat; s = SenseHat(); print(s.get_temperature(), s.get_humidity()); s.show_message('OK')"

If you see two numbers and "OK" scrolls across the matrix, the hardware is ready. If you get an error about the framebuffer or I2C, confirm the HAT is fully seated and that I2C is enabled in sudo raspi-config under Interface Options.

Topic Layout

Keep the Sense HAT topics under one unique prefix so nothing collides with other users on the shared test broker:

Topic Direction Rate Retained
connectmq/demo/5d82be/sensehat/env Pi publishes Every 10 s No
connectmq/demo/5d82be/sensehat/imu Pi publishes Every 1 s No
connectmq/demo/5d82be/sensehat/joystick Pi publishes On event No
connectmq/demo/5d82be/sensehat/display Pi subscribes On demand No
connectmq/demo/5d82be/sensehat/status Pi publishes and last will On connect Yes

Replace 5d82be with your own random string. The public broker allows 10 messages per second per client, so one IMU message per second plus occasional environment and joystick events stays comfortably under the limit.

The Raspberry Pi Sense HAT MQTT Script

The important design point is that show_message() blocks while the text scrolls. If you call it from the MQTT callback, the network loop stalls and the client can miss keepalives. The script below puts incoming messages into a queue and lets a separate display thread scroll them, while the main thread reads sensors and publishes. Save it as sensehat_mqtt.py:

#!/usr/bin/env python3
"""Publish Sense HAT data over MQTT and show incoming messages on the LED matrix."""
import json
import os
import queue
import threading
import time
import uuid

import paho.mqtt.client as mqtt
from sense_hat import SenseHat

BROKER = os.getenv("MQTT_HOST", "broker.connectmq.com")
PORT = int(os.getenv("MQTT_PORT", "1883"))
USERNAME = os.getenv("MQTT_USERNAME")
PASSWORD = os.getenv("MQTT_PASSWORD")
CLIENT_ID = os.getenv("MQTT_CLIENT_ID", f"sensehat-{uuid.uuid4().hex[:8]}")
PREFIX = os.getenv("MQTT_PREFIX", "connectmq/demo/5d82be/sensehat")
ENV_INTERVAL = 10
IMU_INTERVAL = 1
CPU_FACTOR = 2.0  # calibrate against a reference thermometer

sense = SenseHat()
sense.set_imu_config(True, True, True)  # compass, gyroscope, accelerometer
sense.low_light = True
messages = queue.Queue(maxsize=20)


def cpu_temperature():
    with open("/sys/class/thermal/thermal_zone0/temp") as f:
        return int(f.read()) / 1000.0


def corrected_temperature():
    raw = sense.get_temperature_from_humidity()
    cpu = cpu_temperature()
    # The HAT sits above the CPU, so its sensors read warm. This simple
    # heuristic subtracts part of the difference; tune CPU_FACTOR for your case.
    return raw - (cpu - raw) / CPU_FACTOR, raw


def display_worker():
    while True:
        text, colour = messages.get()
        try:
            sense.show_message(text, scroll_speed=0.06, text_colour=colour)
        finally:
            sense.clear()


def on_connect(client, userdata, flags, reason_code, properties):
    if reason_code.is_failure:
        print(f"Connection refused: {reason_code}")
        return
    print(f"Connected as {CLIENT_ID}")
    client.publish(f"{PREFIX}/status", "online", qos=1, retain=True)
    client.subscribe(f"{PREFIX}/display", qos=1)


def on_message(client, userdata, msg):
    raw = msg.payload.decode(errors="ignore").strip()
    colour = [255, 255, 255]
    text = raw
    if raw.startswith("{"):
        try:
            data = json.loads(raw)
            text = str(data.get("text", ""))
            colour = data.get("colour", colour)
        except json.JSONDecodeError:
            pass
    text = text[:80]
    if text:
        try:
            messages.put_nowait((text, colour))
        except queue.Full:
            print("Display queue full, dropping message")


client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=CLIENT_ID)
client.on_connect = on_connect
client.on_message = on_message
client.will_set(f"{PREFIX}/status", "offline", qos=1, retain=True)
client.reconnect_delay_set(min_delay=1, max_delay=60)
if USERNAME:
    client.username_pw_set(USERNAME, PASSWORD)
if PORT == 8883:
    client.tls_set()

threading.Thread(target=display_worker, daemon=True).start()
client.connect(BROKER, PORT, keepalive=30)
client.loop_start()

last_env = 0.0
last_imu = 0.0
try:
    while True:
        now = time.time()
        # Poll orientation often so the sensor fusion stays accurate.
        orientation = sense.get_orientation()

        if now - last_imu >= IMU_INTERVAL:
            accel = sense.get_accelerometer_raw()
            imu = {
                "pitch": round(orientation["pitch"], 1),
                "roll": round(orientation["roll"], 1),
                "yaw": round(orientation["yaw"], 1),
                "ax": round(accel["x"], 3),
                "ay": round(accel["y"], 3),
                "az": round(accel["z"], 3),
                "ts": int(now),
            }
            client.publish(f"{PREFIX}/imu", json.dumps(imu), qos=0)
            last_imu = now

        if now - last_env >= ENV_INTERVAL:
            temp, raw_temp = corrected_temperature()
            env = {
                "temperature": round(temp, 1),
                "temperature_raw": round(raw_temp, 1),
                "humidity": round(sense.get_humidity(), 1),
                "pressure": round(sense.get_pressure(), 1),
                "cpu_temp": round(cpu_temperature(), 1),
                "ts": int(now),
            }
            client.publish(f"{PREFIX}/env", json.dumps(env), qos=1)
            last_env = now

        for event in sense.stick.get_events():
            if event.action == "pressed":
                payload = {"direction": event.direction, "ts": int(event.timestamp)}
                client.publish(f"{PREFIX}/joystick", json.dumps(payload), qos=1)

        time.sleep(0.05)
except KeyboardInterrupt:
    pass
finally:
    client.publish(f"{PREFIX}/status", "offline", qos=1, retain=True).wait_for_publish(2)
    client.loop_stop()
    client.disconnect()
    sense.clear()

Run it with python sensehat_mqtt.py. The main loop polls orientation every 50 ms because the Sense HAT's sensor fusion needs frequent reads to keep pitch, roll and yaw stable, but it publishes only once per second.

Sending Messages to the LED Matrix

From any machine, subscribe to the data first:

mosquitto_sub -h broker.connectmq.com -p 1883 -i viewer-5d82be \
  -t 'connectmq/demo/5d82be/sensehat/#' -v

Then send plain text or a JSON message with a colour:

mosquitto_pub -h broker.connectmq.com -p 1883 -i sender-5d82be \
  -t 'connectmq/demo/5d82be/sensehat/display' -m 'Hello from MQTT'

mosquitto_pub -h broker.connectmq.com -p 1883 -i sender-5d82be \
  -t 'connectmq/demo/5d82be/sensehat/display' \
  -m '{"text":"Door open","colour":[255,80,0]}'

The message scrolls in white or orange while temperature and orientation keep streaming. Tilt the Pi and watch pitch and roll change in the subscriber window; press the joystick to see events arrive. The display topic is deliberately not retained, otherwise the last message would replay every time the Pi reconnects.

Getting Honest Temperature Readings

The Sense HAT sits directly above the processor, so its temperature sensors read several degrees high, more so on a busy Pi 4 or Pi 5. The script publishes both the raw value and a corrected estimate. To calibrate the correction:

  1. Place a trusted thermometer next to the Pi and let both settle for 20 minutes.
  2. Compare the reference with temperature_raw and cpu_temp from the MQTT messages.
  3. Adjust CPU_FACTOR until temperature matches the reference.
  4. For better accuracy, connect the HAT through a 40-pin ribbon extension so it sits away from the CPU.

If you need precise room temperature rather than a demo, a dedicated probe is the better tool. Our guides on the Raspberry Pi DHT22 with MQTT and the DS18B20 1-Wire probe show how to publish external sensors with the same topic style.

Visualising the Data

Because everything travels as compact JSON over MQTT, any subscriber can use it. Node-RED can draw a live gauge, Telegraf can write it to InfluxDB, and Grafana can chart months of history; the Raspberry Pi Grafana and InfluxDB guide builds that stack. For a quick look without installing anything, the in-browser MQTT client and live topic explorer in the ConnectMQ portal show each env and imu message as it arrives.

From Demo to Private Broker

The public MQTT test broker is readable by everyone, which is fine for a scrolling greeting but not for a sensor in your home or classroom. ConnectMQ gives you a private broker with generated credentials and TLS. Set four environment variables and the script connects securely:

export MQTT_HOST=broker.connectmq.com
export MQTT_PORT=8883
export MQTT_USERNAME=YOUR_USERNAME
export MQTT_PASSWORD=YOUR_PASSWORD
export MQTT_CLIENT_ID=YOUR_CLIENT_ID
export MQTT_PREFIX=classroom/lab1/sensehat
python sensehat_mqtt.py

To keep the script running after a reboot, wrap it in a systemd service as shown in the Raspberry Pi MQTT tutorial.

Ideas to Extend the Project

Once the basic loop works, the Sense HAT becomes a handy test bench for MQTT patterns you will reuse on real devices:

  1. Tilt alarm: have a second script subscribe to the imu topic and publish a warning when pitch or roll exceeds a limit, a simple stand-in for monitoring a machine that should not move.
  2. Remote status board: publish short status strings from a CI pipeline, a home automation flow or a server health check to the display topic and use the matrix as a desk notifier.
  3. Joystick as a remote control: subscribe to the joystick topic from Node-RED and map each direction to a scene, a relay or a dashboard page.
  4. Threshold alerts without code: on a private ConnectMQ broker, set an alert rule in the portal on the humidity value of the env topic and review triggered alerts in the alerts log.
  5. Multiple classrooms: give each Pi its own prefix level, such as school/room12/sensehat, and subscribe with a + wildcard to compare rooms side by side.

Each idea needs only a new subscriber or a new topic. The Pi script stays the same, which is exactly the decoupling that makes MQTT pleasant to work with.

Troubleshooting

  • OSError: Cannot detect RPi-Sense FB device: the HAT is not detected; reseat it with the Pi powered off.
  • Yaw drifts or jumps: calibrate the magnetometer and keep the Pi away from motors and speakers; orientation also needs frequent polling.
  • Messages scroll but sensor data stops: something is blocking the main loop; never call show_message() from a callback.
  • Humidity reads 0: the humidity sensor needs a moment after start; discard the first reading.

Get Started with ConnectMQ

Move your Sense HAT from the public sandbox to a private broker in a few minutes. Create a free Serverless broker, no credit card required, and connect over TLS with the generated credentials. You get a hosted MQTT broker that supports MQTT 3.1.1 and 5.0, plus a portal where you can send display messages and watch every reading live.

Frequently Asked Questions

Why is the Sense HAT temperature higher than the room temperature?

Heat from the Raspberry Pi's processor warms the HAT. Use the CPU-based correction in this tutorial, a ribbon extension cable, or an external sensor for accurate readings.

Can I publish Sense HAT data faster than once per second?

Yes, but stay within your broker's rate limits; the public test broker allows 10 messages per second per client. For high-rate motion data, batch several samples into one JSON message.

Does the Sense HAT work on a Raspberry Pi 5?

Yes, it uses the standard 40-pin header and the sense-hat package from Raspberry Pi OS. Expect larger temperature offsets because the Pi 5 runs warmer.

How do I send coloured text to the LED matrix?

Publish JSON such as {"text":"Hi","colour":[0,255,0]} to the display topic. The script reads the colour as an RGB list and falls back to white for plain text.

Related articles

Pico W vs Pico 2 W for MQTT: RP2040 vs RP2350 Performance, Memory and TLS

The Pico 2 W keeps the Pico W's footprint and Wi-Fi chip but swaps the RP2040 for the faster RP2350. This comparison looks at what that means for MQTT devices: CPU, memory, TLS, security features, software support, and which board to buy for which project.

8 min read

Raspberry Pi RFID MQTT Access Logging with the RC522 Reader

Wire an RC522 RFID reader to a Raspberry Pi, read tags in Python, check them against a local allowlist and publish every access attempt as an MQTT event that loggers, dashboards and alerting tools can consume.

7 min read

All articles →

Your private MQTT broker is a few clicks away

Create an account, get your broker credentials and connect your first device — no servers to install, patch or scale.