Raspberry Pi Pico 2 W ThingsBoard IoT is a runnable microcontroller simulation example on SensBench — open the wired circuit, inspect the code, and compile real firmware in your browser, no install needed. Last updated Aug 14, 2026.

FREE SIMULATOR EXAMPLE · INTERMEDIATE

Raspberry Pi Pico 2 W ThingsBoard IoT

This example publishes telemetry from the Raspberry Pi Pico 2 W to ThingsBoard over MQTT — sensor readings become dashboard charts on the open-source IoT platform. The device-to-platform pattern.

Board Raspberry Pi Pico W (RP2040 + WiFi)
Difficulty intermediate
Category communication
Libraries None — just the Arduino core
Runs in Sensbench browser simulator — free, no account
Raspberry Pi Pico 2 W publishing telemetry to ThingsBoard IoT in the Sensbench simulator

The exact circuit you get when you open this example — already wired, ready to run.

The sketch

# main.py
import network
import time
import json
import dht
from machine import Pin
from umqttsimple import MQTTClient

# --- Configuration ---
WIFI_SSID = "SENSBENCH-GUEST"   # SENSBENCH simulator's built-in WiFi network
WIFI_PASSWORD = ""           # the simulator AP is open
THINGSBOARD_HOST = "eu.thingsboard.cloud"
ACCESS_TOKEN = "tokenn"      # replace with YOUR ThingsBoard device token

# --- Hardware Setup ---
# Using "LED" for Pico W onboard LED, or Pin(15) for an external LED
led = Pin("LED", Pin.OUT) 
sensor = dht.DHT11(Pin(4)) 
led_state = False

# --- MQTT Callback (Handles Switch Commands) ---
def on_message(topic, msg):
    global led_state
    print(f"\n[RPC] Message received: {msg}")
    
    try:
        data = json.loads(msg)
        method = data.get("method")
        params = data.get("params")

        # Handling 'setState' from your ThingsBoard Switch
        if method == "setState":
            # This check ensures both boolean and string 'true' work
            if params is True or params == "true" or params == 1:
                led_state = True
                led.value(1)
            else:
                led_state = False
                led.value(0)
            
            print(f"Action: LED is now {'ON' if led_state else 'OFF'}")
            
        # Handling 'getState' to sync the dashboard switch
        elif method == "getState":
            request_id = topic.decode().split('/')[-1]
            client.publish(f"v1/devices/me/rpc/response/{request_id}", json.dumps(led_state))
            print("Action: Synced state with Dashboard")

    except Exception as e:
        print(f"RPC Error: {e}")

# --- Connection Functions ---
def connect_wifi():
    sta_if = network.WLAN(network.STA_IF)
    sta_if.active(True)
    sta_if.connect(WIFI_SSID, WIFI_PASSWORD)
    print("Connecting to WiFi", end="")
    while not sta_if.isconnected():
        print(".", end="")
        time.sleep(0.5)
    print(f"\nConnected! IP: {sta_if.ifconfig()[0]}")

def connect_mqtt():
    client = MQTTClient("Pico_W_Cuttack", THINGSBOARD_HOST, user=ACCESS_TOKEN, password="")
    client.set_callback(on_message)
    client.connect()
    client.subscribe(b"v1/devices/me/rpc/request/+")
    print("Connected to ThingsBoard MQTT!")
    return client

# --- Main Logic ---
connect_wifi()
client = connect_mqtt()

last_telemetry = 0

print("System Ready. Waiting for commands...")

while True:
    try:
        # Check for incoming RPC (Switch) messages
        client.check_msg() 

        # Send Telemetry every 10 seconds (standard for cloud stability)
        if (time.time() - last_telemetry) > 10:
            sensor.measure()
            t = sensor.temperature()
            h = sensor.humidity()
            
            payload = json.dumps({
                "temperature": t, 
                "humidity": h, 
                "ledStatus": led_state
            })
            
            client.publish("v1/devices/me/telemetry", payload)
            print(f"Published: {payload}")
            last_telemetry = time.time()

    except Exception as e:
        print(f"Error in Loop: {e}")
        time.sleep(5)
        import machine
        machine.reset() 


# umqttsimple.py
try:
    import usocket as socket
except:
    import socket
import ustruct as struct
from ubinascii import hexlify

class MQTTException(Exception):
    pass

class MQTTClient:

    def __init__(self, client_id, server, port=0, user=None, password=None, keepalive=0,
                 ssl=False, ssl_params={}):
        if port == 0:
            port = 8883 if ssl else 1883
        self.client_id = client_id
        self.sock = None
        self.server = server
        self.port = port
        self.ssl = ssl
        self.ssl_params = ssl_params
        self.pid = 0
        self.cb = None
        self.user = user
        self.pswd = password
        self.keepalive = keepalive
        self.lw_topic = None
        self.lw_msg = None
        self.lw_qos = 0
        self.lw_retain = False

    def _send_str(self, s):
        self.sock.write(struct.pack("!H", len(s)))
        self.sock.write(s)

    def _recv_len(self):
        n = 0
        sh = 0
        while 1:
            b = self.sock.read(1)[0]
            n |= (b & 0x7f) << sh
            if not b & 0x80:
                return n
            sh += 7

    def set_callback(self, f):
        self.cb = f

    def set_last_will(self, topic, msg, retain=False, qos=0):
        assert 0 <= qos <= 2
        assert topic
        self.lw_topic = topic
        self.lw_msg = msg
        self.lw_qos = qos
        self.lw_retain = retain

    def connect(self, clean_session=True):
        self.sock = socket.socket()
        addr = socket.getaddrinfo(self.server, self.port)[0][-1]
        self.sock.connect(addr)
        if self.ssl:
            import ussl
            self.sock = ussl.wrap_socket(self.sock, **self.ssl_params)
        premsg = bytearray(b"\x10\0\0\0\0\0")
        msg = bytearray(b"\x04MQTT\x04\x02\0\0")

        sz = 10 + 2 + len(self.client_id)
        msg[6] = clean_session << 1
        if self.user is not None:
            sz += 2 + len(self.user) + 2 + len(self.pswd)
            msg[6] |= 0xC0
        if self.keepalive:
            assert self.keepalive < 65536
            msg[7] |= self.keepalive >> 8
            msg[8] |= self.keepalive & 0x00FF
        if self.lw_topic:
            sz += 2 + len(self.lw_topic) + 2 + len(self.lw_msg)
            msg[6] |= 0x4 | (self.lw_qos & 0x1) << 3 | (self.lw_qos & 0x2) << 3
            msg[6] |= self.lw_retain << 5

        i = 1
        while sz > 0x7f:
            premsg[i] = (sz & 0x7f) | 0x80
            sz >>= 7
            i += 1
        premsg[i] = sz

        self.sock.write(premsg, i + 2)
        self.sock.write(msg)
        #print(hex(len(msg)), hexlify(msg, ":"))
        self._send_str(self.client_id)
        if self.lw_topic:
            self._send_str(self.lw_topic)
            self._send_str(self.lw_msg)
        if self.user is not None:
            self._send_str(self.user)
            self._send_str(self.pswd)
        resp = self.sock.read(4)
        assert resp[0] == 0x20 and resp[1] == 0x02
        if resp[3] != 0:
            raise MQTTException(resp[3])
        return resp[2] & 1

    def disconnect(self):
        self.sock.write(b"\xe0\0")
        self.sock.close()

    def ping(self):
        self.sock.write(b"\xc0\0")

    def publish(self, topic, msg, retain=False, qos=0):
        pkt = bytearray(b"\x30\0\0\0")
        pkt[0] |= qos << 1 | retain
        sz = 2 + len(topic) + len(msg)
        if qos > 0:
            sz += 2
        assert sz < 2097152
        i = 1
        while sz > 0x7f:
            pkt[i] = (sz & 0x7f) | 0x80
            sz >>= 7
            i += 1
        pkt[i] = sz
        #print(hex(len(pkt)), hexlify(pkt, ":"))
        self.sock.write(pkt, i + 1)
        self._send_str(topic)
        if qos > 0:
            self.pid += 1
            pid = self.pid
            struct.pack_into("!H", pkt, 0, pid)
            self.sock.write(pkt, 2)
        self.sock.write(msg)
        if qos == 1:
            while 1:
                op = self.wait_msg()
                if op == 0x40:
                    sz = self.sock.read(1)
                    assert sz == b"\x02"
                    rcv_pid = self.sock.read(2)
                    rcv_pid = rcv_pid[0] << 8 | rcv_pid[1]
                    if pid == rcv_pid:
                        return
        elif qos == 2:
            assert 0

    def subscribe(self, topic, qos=0):
        assert self.cb is not None, "Subscribe callback is not set"
        pkt = bytearray(b"\x82\0\0\0")
        self.pid += 1
        struct.pack_into("!BH", pkt, 1, 2 + 2 + len(topic) + 1, self.pid)
        #print(hex(len(pkt)), hexlify(pkt, ":"))
        self.sock.write(pkt)
        self._send_str(topic)
        self.sock.write(qos.to_bytes(1, "little"))
        while 1:
            op = self.wait_msg()
            if op == 0x90:
                resp = self.sock.read(4)
                #print(resp)
                assert resp[1] == pkt[2] and resp[2] == pkt[3]
                if resp[3] == 0x80:
                    raise MQTTException(resp[3])
                return

    # Wait for a single incoming MQTT message and process it.
    # Subscribed messages are delivered to a callback previously
    # set by .set_callback() method. Other (internal) MQTT
    # messages processed internally.
    def wait_msg(self):
        res = self.sock.read(1)
        self.sock.setblocking(True)
        if res is None:
            return None
        if res == b"":
            raise OSError(-1)
        if res == b"\xd0":  # PINGRESP
            sz = self.sock.read(1)[0]
            assert sz == 0
            return None
        op = res[0]
        if op & 0xf0 != 0x30:
            return op
        sz = self._recv_len()
        topic_len = self.sock.read(2)
        topic_len = (topic_len[0] << 8) | topic_len[1]
        topic = self.sock.read(topic_len)
        sz -= topic_len + 2
        if op & 6:
            pid = self.sock.read(2)
            pid = pid[0] << 8 | pid[1]
            sz -= 2
        msg = self.sock.read(sz)
        self.cb(topic, msg)
        if op & 6 == 2:
            pkt = bytearray(b"\x40\x02\0\0")
            struct.pack_into("!H", pkt, 2, pid)
            self.sock.write(pkt)
        elif op & 6 == 4:
            assert 0

    # Checks whether a pending message from server is available.
    # If not, returns immediately with None. Otherwise, does
    # the same processing as wait_msg.
    def check_msg(self):
        self.sock.setblocking(False)
        return self.wait_msg()

How it works

The firmware connects to WiFi, authenticates with ThingsBoard over MQTT using a device access token, and publishes telemetry — sensor readings wrapped in JSON — on a schedule. The payload structure maps directly to the dashboard’s data keys.

The MQTT client loop keeps the connection alive between publishes, and the JSON payload is the contract: each key in the published object becomes a chartable field on the platform. The code’s job is measurement plus formatting; the platform does the rest.

Before you build it for real

The access token is a credential — in the source, it is readable by anyone with the firmware, and a leaked token lets strangers publish fake telemetry as your device. The MQTT keepalive must be pumped — without the client loop, the connection silently drops and the dashboard flatlines. And the payload keys are a contract: rename a JSON field in the firmware and the dashboard’s widgets silently stop updating, which is the classic integration bug between device and platform.

Get the parts

Related examples

FAQ

What is ThingsBoard?

An open-source IoT platform: devices publish telemetry, and the platform stores, visualizes and rules on it. Compared to Blynk’s app-first approach, ThingsBoard is dashboard-first — charts, tables and alarms built around device data streams.

How does the Pico 2 W talk to it?

Over MQTT with an access token: the device connects to the ThingsBoard broker, authenticates with its token, and publishes JSON payloads to its telemetry topic. The platform then turns the JSON fields into dashboard widgets.