A practical workshop for streaming manufacturing sensor data from MQTT to TimescaleDB on Tiger Cloud.
This project demonstrates a real-time data pipeline:
- MQTT Broker — Publishes sensor data from manufacturing equipment
- Python Application — Subscribes to MQTT topics and processes messages
- TimescaleDB — Stores time-series sensor readings and metadata
The architecture separates concerns into modular components:
config.py— Environment configuration and secretsdatabase.py— Database operations and schema managementmqtt.py— MQTT subscription and message handlingmain.py— Application lifecycle and signal handling
- Python 3.8+
mosquitto-clients(for MQTT testing)psql(PostgreSQL client, for database testing)- TimescaleDB connection details (host, port, credentials)
- MQTT broker access
The devcontainer installs everything automatically, so start by confirming each tool is available. Run the verification script:
./verify_setup.shIt prints Success when everything is ready. The script runs these checks:
- Python + dependencies —
paho-mqtt,psycopg2,python-dotenvimport cleanly - Mosquitto MQTT client —
mosquitto_subis installed - PostgreSQL client —
psqlis installed - Grafana — responding on port 3000
If any check fails, the script stops and prints which tool is missing.
cp .env.example .env# Edit .env with:
PGPASSWORD=your-password
PGUSER=your-username
PGDATABASE=sensor_data
PGHOST=your-timescale-host
PGPORT=5432
# Optional: Override default MQTT broker
MQTT_HOST=your-mqtt-host
MQTT_PORT=1883Open a terminal and subscribe to the manufacturing sensor topics:
# Subscribe to all manufacturing sensors
mosquitto_sub -h 54.160.239.103 -p 1883 -t "UNS/manufacturing/#" -vBecause the credentials file uses the standard PG* variable names, you can
load it into your shell and run psql with no arguments — libpq reads the
connection details (including the password) straight from the environment:
# Export everything in the credentials file, then verify the connection
set -a
source .env
set +a
# Connect, print connection info, and exit
psql -c '\conninfo'Instead of letting the Python application create the tables on startup, you can
load them manually. Run the files in order — tag_meta.sql first, since
tag_history has a foreign key to it. Assuming your credentials are already
exported (see above), psql picks up the connection details automatically:
psql -f mqtt_to_timescaledb/sql/tag_meta.sql
psql -f mqtt_to_timescaledb/sql/tag_history.sqlpython mqtt_to_timescaledb.pyThe application will:
- Load configuration from environment variables
- Connect to TimescaleDB and initialize tables/hypertable
- Connect to MQTT broker and subscribe to
UNS/manufacturing/# - Process incoming messages and store readings in the database
- Log all activities to console
Press Ctrl+C to trigger graceful shutdown:
- Stops MQTT subscription
- Closes database connection
- Logs shutdown information
Running in a browser-based Codespace?
Ctrl+Cmay be intercepted by the browser and never reach the application. If pressingCtrl+Cdoes nothing, the simplest fix is to kill the terminal — click the trash-can icon on the terminal panel (or run Terminal: Kill the Active Terminal Instance from the Command Palette) and open a new one. Opening the Codespace in the VS Code desktop app also restores normalCtrl+Cbehavior.
The application logs important events:
2024-07-11 10:15:23 - mqtt_to_timescaledb - INFO - Connected to TimescaleDB
2024-07-11 10:15:24 - mqtt_to_timescaledb - INFO - Connected to MQTT broker
2024-07-11 10:15:25 - mqtt_to_timescaledb - INFO - Inserted plant1/area1/machine1/bearing_temperature: 65.3 °C at 2024-07-11 10:30:00
Sign in to an interactive psql session:
psqlThen run the queries:
-- Get the last 10 readings
SELECT time, value, tag_id
FROM tag_history
ORDER BY time DESC
LIMIT 10;
-- Minute-by-minute averages, joined with tag metadata
SELECT time_bucket('1 minute', h.time) AS minute,
h.tag_id,
m.tag_name,
m.unit,
avg(h.value) AS avg_value
FROM tag_history h
JOIN tag_meta m ON m.tag_id = h.tag_id
WHERE h.tag_id = 'plant1/area1/machine1/bearing_temperature'
GROUP BY minute, h.tag_id, m.tag_name, m.unit
ORDER BY minute DESC;When you're done, sign out of the session:
\qGrafana runs alongside the app and is forwarded on port 3000. Open with the 'open in browser' icon in the PORTS tab. Log in with username admin and password admin (see .devcontainer/docker-compose.yml).
-
In Grafana, go to Connections → Data sources → Add data source → PostgreSQL.
-
Fill in your Tiger Cloud credentials (the same
PG*values from.env):- Host:
PGHOST:PGPORT(e.g.your-host:39171) - Database:
PGDATABASE(e.g.tsdb) - User:
PGUSER(e.g.tsdbadmin) - Password:
PGPASSWORD - TLS/SSL Mode:
require
⚠️ The Host field must include the port, joined with a colon (your-host:39171). Tiger Cloud does not use the default5432, and Grafana has no separate port field — if you enter only the hostname the connection will fail. Also set TLS/SSL Mode torequire; Tiger Cloud rejects unencrypted connections. - Host:
-
Click Save & test — you should see a success message.
- Go to Dashboards → New → Import.
- Click Upload dashboard JSON file and choose grafana/sensor_readings_dashboard.json (or paste its contents).
- When prompted, select the TimescaleDB data source you created above.
- Click Import.
The dashboard shows a time-series panel of sensor value over time, one series
per tag_id, auto-refreshing every 10 seconds. Make sure the Python reader is
running so there's fresh data to plot.
# Test MQTT broker connectivity
mosquitto_sub -h 54.160.239.103 -p 1883 -t '$SYS/#' -W 1
# Check if broker is responding
timeout 2 bash -c 'cat < /dev/null > /dev/tcp/54.160.239.103/1883' && echo "Port is open"# Test connection manually
psql -h your-timescale-host -U your-username -d sensor_data -c "SELECT version();"
# Verify credentials in environment file
cat .env | grep PG-
Verify MQTT messages are being published:
mosquitto_sub -h 54.160.239.103 -p 1883 -t "UNS/manufacturing/#" -v -
Check Python application logs for errors in message parsing
-
Verify message format — must include
timestampandvaluefields -
Check database tables exist:
SELECT table_name FROM information_schema.tables WHERE table_schema='public';
- Clone or open in Codespaces
- Install dependencies (
pip install -r requirements.txt) - Configure TimescaleDB credentials
- Run the Python application
- Publish a test MQTT message
- Verify data in database with
psql
- Subscribe to MQTT topics with
mosquitto_sub - Publish multiple sensor readings with different tags
- Query data using TimescaleDB time-bucket aggregations
- Modify the message format and re-run application
- Create additional SQL views for common queries
- Add continuous aggregates in TimescaleDB
- Modify the Python code to handle additional payload fields
- Set up monitoring/alerting for sensor thresholds
mqtt_to_timescaledb.py # Top-level entry point script
mqtt_to_timescaledb/
├── __init__.py # Package initialization
├── __main__.py # Module entry point
├── config.py # Configuration and env loading
├── database.py # TimescaleDB manager
├── mqtt.py # MQTT reader and message handling
├── main.py # Application entry point
└── sql/
├── tag_meta.sql # tag_meta table DDL
└── tag_history.sql # tag_history hypertable DDL
grafana/
└── sensor_readings_dashboard.json # Importable Grafana dashboard
| Variable | Default | Purpose |
|---|---|---|
MQTT_HOST |
54.160.239.103 |
MQTT broker hostname |
MQTT_PORT |
1883 |
MQTT broker port |
PGHOST |
localhost |
TimescaleDB hostname |
PGPORT |
5432 |
TimescaleDB port |
PGDATABASE |
sensor_data |
Database name |
PGUSER |
postgres |
Database user |
PGPASSWORD |
password |
Database password |
- Scale the pipeline: Add message queuing, batch inserts, or compression
- Visualization: Connect Grafana or similar tools to TimescaleDB
- Real-time alerts: Implement threshold monitoring and notifications
- Data retention: Configure TimescaleDB compression and data retention policies
- Testing: Add unit tests for message parsing and database operations
For questions or issues:
- Check the Troubleshooting section above
- Review application logs for error messages
- Test MQTT and database connectivity separately
- Reach out to the workshop facilitator