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:
- Place a trusted thermometer next to the Pi and let both settle for 20 minutes.
- Compare the reference with
temperature_rawandcpu_tempfrom the MQTT messages. - Adjust
CPU_FACTORuntiltemperaturematches the reference. - 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:
- Tilt alarm: have a second script subscribe to the
imutopic and publish a warning when pitch or roll exceeds a limit, a simple stand-in for monitoring a machine that should not move. - Remote status board: publish short status strings from a CI pipeline, a home automation flow or a server health check to the
displaytopic and use the matrix as a desk notifier. - Joystick as a remote control: subscribe to the
joysticktopic from Node-RED and map each direction to a scene, a relay or a dashboard page. - Threshold alerts without code: on a private ConnectMQ broker, set an alert rule in the portal on the humidity value of the
envtopic and review triggered alerts in the alerts log. - 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.



