Intermediate 25 min

Step 4: Build Python Device Simulator

Now let’s build the device simulator. It will publish telemetry every 2 seconds with realistic sensor values.

Install Dependencies

First, install the MQTT client library:

pip install paho-mqtt

Or add to requirements.txt:

paho-mqtt==1.6.1

Complete Simulator Code

Here’s the complete simulator:


              #!/usr/bin/env python3
"""
MQTT IoT Device Simulator
Publishes telemetry data to MQTT broker every 2 seconds.
"""

import json
import time
import random
from datetime import datetime, timezone
import paho.mqtt.client as mqtt

# Configuration
BROKER_HOST = "localhost"
BROKER_PORT = 1883
DEVICE_ID = "sensor-01"
TOPIC = f"iot/devices/{DEVICE_ID}/telemetry"
PUBLISH_INTERVAL = 2  # seconds

# Simulated sensor state
temperature = 20.0
humidity = 50.0
battery = 100.0
sequence = 0


def create_telemetry_message():
  """Create a telemetry message following our schema."""
  global temperature, humidity, battery, sequence
  
  # Simulate realistic sensor drift
  temperature += random.uniform(-0.5, 0.5)
  temperature = max(15.0, min(30.0, temperature))  # Clamp to reasonable range
  
  humidity += random.uniform(-2.0, 2.0)
  humidity = max(30.0, min(80.0, humidity))
  
  # Battery slowly drains
  battery -= random.uniform(0.01, 0.05)
  battery = max(0.0, min(100.0, battery))
  
  sequence += 1
  
  return {
      "deviceId": DEVICE_ID,
      "ts": datetime.now(timezone.utc).isoformat(),
      "metrics": {
          "temperatureC": round(temperature, 1),
          "humidityPct": round(humidity, 1),
          "batteryPct": round(battery, 1)
      },
      "seq": sequence
  }


def on_connect(client, userdata, flags, rc):
  """Callback when connected to broker."""
  if rc == 0:
      print(f"✅ Connected to MQTT broker at {BROKER_HOST}:{BROKER_PORT}")
  else:
      print(f"❌ Connection failed with code {rc}")


def on_publish(client, userdata, mid):
  """Callback when message is published."""
  print(f"📤 Published message {mid}")


def on_disconnect(client, userdata, rc):
  """Callback when disconnected."""
  if rc != 0:
      print(f"⚠️ Unexpected disconnection (rc={rc})")
  else:
      print("✅ Disconnected cleanly")


def main():
  """Main loop."""
  # Create MQTT client
  client = mqtt.Client(client_id=DEVICE_ID)
  client.on_connect = on_connect
  client.on_publish = on_publish
  client.on_disconnect = on_disconnect
  
  try:
      # Connect to broker
      print(f"🔌 Connecting to {BROKER_HOST}:{BROKER_PORT}...")
      client.connect(BROKER_HOST, BROKER_PORT, keepalive=60)
      client.loop_start()
      
      # Wait for connection
      time.sleep(1)
      
      # Publish loop
      print(f"📡 Publishing to topic: {TOPIC}")
      print("Press Ctrl+C to stop
")
      
      while True:
          message = create_telemetry_message()
          payload = json.dumps(message)
          
          # Publish with QoS 1 (at least once delivery)
          result = client.publish(TOPIC, payload, qos=1)
          
          if result.rc == mqtt.MQTT_ERR_SUCCESS:
              print(f"📊 [{message['seq']}] "
                    f"Temp: {message['metrics']['temperatureC']}°C, "
                    f"Humidity: {message['metrics']['humidityPct']}%, "
                    f"Battery: {message['metrics']['batteryPct']}%")
          else:
              print(f"❌ Failed to publish: {result.rc}")
          
          time.sleep(PUBLISH_INTERVAL)
          
  except KeyboardInterrupt:
      print("
🛑 Stopping simulator...")
  except Exception as e:
      print(f"❌ Error: {e}")
  finally:
      client.loop_stop()
      client.disconnect()
      print("👋 Simulator stopped")


if __name__ == "__main__":
  main()
            

Understanding the Code

1. MQTT Client Setup

client = mqtt.Client(client_id=DEVICE_ID)

Creates a client with a unique ID. The broker uses this to track the device.

2. Connection Callbacks

  • on_connect: Called when connected (or fails)
  • on_publish: Called when message is published
  • on_disconnect: Called when disconnected

3. Sensor Simulation The simulator generates realistic values:

  • Temperature drifts slowly (15-30°C)
  • Humidity varies (30-80%)
  • Battery drains gradually (0-100%)

4. Message Publishing

client.publish(TOPIC, payload, qos=1)
  • qos=1: At least once delivery (guaranteed, may duplicate)
  • Topic follows our pattern: iot/devices/{deviceId}/telemetry

Run the Simulator

Save the code as simulator.py and run it:

python simulator.py

You should see:

🔌 Connecting to localhost:1883...
✅ Connected to MQTT broker at localhost:1883
📡 Publishing to topic: iot/devices/sensor-01/telemetry
Press Ctrl+C to stop

📊 [1] Temp: 20.3°C, Humidity: 50.2%, Battery: 99.8%
📊 [2] Temp: 20.1°C, Humidity: 51.5%, Battery: 99.6%
📊 [3] Temp: 19.8°C, Humidity: 49.8%, Battery: 99.4%
...

Verify Messages are Published

In another terminal, subscribe to see the messages:

mosquitto_sub -h localhost -p 1883 -t iot/devices/+/telemetry

You should see JSON messages appearing every 2 seconds:

{"deviceId":"sensor-01","ts":"2025-12-16T10:30:45.123Z","metrics":{"temperatureC":20.3,"humidityPct":50.2,"batteryPct":99.8},"seq":1}
{"deviceId":"sensor-01","ts":"2025-12-16T10:30:47.456Z","metrics":{"temperatureC":20.1,"humidityPct":51.5,"batteryPct":99.6},"seq":2}

Test the Simulator

Try this interactive example:

🐍 Python Message Generator
📟 Console Output
Run code to see output...

Multiple Devices

To simulate multiple devices, run multiple instances with different DEVICE_ID:

# Terminal 1
DEVICE_ID=sensor-01 python simulator.py

# Terminal 2
DEVICE_ID=sensor-02 python simulator.py

# Terminal 3
DEVICE_ID=sensor-03 python simulator.py

Subscribe to all devices:

mosquitto_sub -h localhost -p 1883 -t iot/devices/+/telemetry

Checkpoint ✅

You should have:

  • ✅ Simulator running and publishing messages
  • ✅ Messages visible in mosquitto_sub
  • ✅ Messages following the schema
  • ✅ Realistic sensor values changing over time

If something’s wrong:

  • Check broker is running: docker-compose ps
  • Check connection: Look for “Connected to MQTT broker” message
  • Check topic: Verify topic matches subscription

What’s Next?

In the next page, you’ll set up Node-RED to subscribe to these messages, validate the data, and create a dashboard. This is where the “digital twin” comes to life!