Categorieën bekijken

Raspberry Pico – Ethernet LAN with W5500 + MQTT

Raspberry Pi Pico ARM microcontroller RP2040 micro-usb

This example demonstrates to use a cheap normal Pico with a W5500 module to send MQTT messages over the network.

Note: This does not make use of the “adafruit_wiznet5k” library wich runs on CircuitPython (you need CircuitPython) but uses the image from
https://micropython.org/download/W5500_EVB_PICO


Connect the W5500 #

We use the compact WIZ5500 Ethernet LAN module:


Ethernet LAN Netwerk mini module W5500 USR-ES1

And connect it to the Pico like this:


Download the UF2 image #

We use the W5500 image (wich has built-in support for the W5500), you can get the latest version here:

https://micropython.org/download/W5500_EVB_PICO


MQTT libraries #

Create a folder named “umqtt” on the pico with these 2 files:

robust.py

import time
from . import simple


class MQTTClient(simple.MQTTClient):
    DELAY = 2
    DEBUG = False

    def delay(self, i):
        time.sleep(self.DELAY)

    def log(self, in_reconnect, e):
        if self.DEBUG:
            if in_reconnect:
                print("mqtt reconnect: %r" % e)
            else:
                print("mqtt: %r" % e)

    def reconnect(self):
        i = 0
        while 1:
            try:
                return super().connect(False)
            except OSError as e:
                self.log(True, e)
                i += 1
                self.delay(i)

    def publish(self, topic, msg, retain=False, qos=0):
        while 1:
            try:
                return super().publish(topic, msg, retain, qos)
            except OSError as e:
                self.log(False, e)
            self.reconnect()

    def wait_msg(self):
        while 1:
            try:
                return super().wait_msg()
            except OSError as e:
                self.log(False, e)
            self.reconnect()

    def check_msg(self, attempts=2):
        while attempts:
            self.sock.setblocking(False)
            try:
                return super().wait_msg()
            except OSError as e:
                self.log(False, e)
            self.reconnect()
            attempts -= 1

simple.py

import socket
import struct
from binascii import hexlify


class MQTTException(Exception):
    pass


class MQTTClient:
    def __init__(
        self,
        client_id,
        server,
        port=0,
        user=None,
        password=None,
        keepalive=0,
        ssl=None,
    ):
        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.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:
            self.sock = self.ssl.wrap_socket(self.sock, server_hostname=self.server)
        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:
            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:
            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
        return op

    # 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()

Example script – MQTT #

The script below gives an example to reset the W5500 first, and then tries to connect to the network and send MQTT messages to a broker.

(Create/enable an MQTT broker on your network, adjust the address in the code below).

main.py

from umqtt.simple import MQTTClient
from usocket import socket
import socket
from machine import Pin, SPI
import network
import time

# MQTT config
mqtt_server = '192.168.2.152'
client_id = 'wiz'
topic_pub = b'hello'
topic_msg = b'Hello from Pico!'
# example lsiten on pi:
# mosquitto_sub -v -t 'hello'

# Pins voor W5500
rst = Pin(20, Pin.OUT)

# W5500 reset
print("Reset W5500...")
rst.value(0)
time.sleep(0.1)
rst.value(1)
time.sleep(4)
print("OK!")

# W5x00 init
def w5x00_init():
    spi = SPI(0, 5_000_000, mosi=Pin(19), miso=Pin(16), sck=Pin(18))
    nic = network.WIZNET5K(spi, Pin(17), Pin(20))  # SPI, CS, RESET pin
    nic.active(True)
    time.sleep(2)

    # DHCP
    nic.ifconfig('dhcp')
    print('IP address:', nic.ifconfig())
    print(socket.getaddrinfo(mqtt_server, 1883))

    while not nic.isconnected():
        time.sleep(1)
        print('Waiting for Ethernet...')

    return nic

# MQTT connect functie
def mqtt_connect():
    client = MQTTClient(client_id, mqtt_server, keepalive=60)
    client.connect()
    print('Connected to MQTT Broker:', mqtt_server)
    return client

# Main loop
def main():
    counter = 0
    nic = w5x00_init()
    client = None

    while True:
        try:
            if client is None:
                client = mqtt_connect()

            payload = topic_msg + b": " + str(counter).encode()
            client.publish(topic_pub, payload)
            print("Published:", payload)

            counter += 1
            time.sleep(1)

        except OSError as e:
            print('MQTT Error:', e)

            try:
                if client is not None:
                    client.disconnect()
            except:
                pass

            client = None  # Forceer herverbinden
            print('Reconnecting MQTT in 5 seconds...')
            time.sleep(5)

        # Check Ethernet status
        if not nic.isconnected():
            print('Ethernet disconnected! Reconnecting Ethernet...')
            try:
                nic.active(False)
                time.sleep(2)
                nic.active(True)
                time.sleep(2)
                nic.ifconfig('dhcp')
                print('Ethernet reconnected:', nic.ifconfig())
            except OSError as e:
                print('Ethernet reconnect failed:', e)
                time.sleep(5)

if __name__ == "__main__":
    main()

Output:

MPY: soft reboot
Reset W5500...
OK!
IP address: ('192.168.2.153', '255.255.255.0', '192.168.2.254', '192.168.2.254')
[(2, 1, 0, '', ('192.168.2.152', 1883))]
Connected to MQTT Broker: 192.168.2.152
Published: b'Hello from Pico!: 0'
Published: b'Hello from Pico!: 1'
Published: b'Hello from Pico!: 2'
Published: b'Hello from Pico!: 3'
Published: b'Hello from Pico!: 4'
Published: b'Hello from Pico!: 5'

On a MQTT broker you can start listening with.

mosquitto_sub -v -t 'hello'

You will revcieve:

hello Hello from Pico!: 0
hello Hello from Pico!: 1
hello Hello from Pico!: 2
hello Hello from Pico!: 3
hello Hello from Pico!: 4
hello Hello from Pico!: 5


Products #