MQTT topic matcher

Discussion about programs, libraries and tools that work with MicroPython. Mostly these are provided by a third party.
Target audience: All users and developers of MicroPython.
Post Reply
kevinkk525
Posts: 969
Joined: Sat Feb 03, 2018 7:02 pm

MQTT topic matcher

Post by kevinkk525 » Fri Sep 13, 2019 3:00 pm

Everyone working with mqtt will probably handle his subscriptions one way or another but we all need to check, which subscription the incoming topic matches.
Until now I only supported multi-level wildcards because it was easy and I couldn't find an example that was fast enough on micropython (20ms was not an option) but now I wrote an optimized matcher that supports single-level wildcards too and it finishes all operations in about 1ms on the esp8266.

Code: Select all

    def matchesSubscription(subscription, topic):
            if topic == subscription:
                return True
            if subscription.endswith("/#") or subscription.endswith("/+"):
            lens = len(subscription)
            if memoryview(topic)[:lens - 2] == memoryview(subscription)[:-2]:
                if subscription.endswith("/#"):
                    if len(topic) == lens - 2 or memoryview(topic)[lens - 2:lens - 1] == b"/":
                        # check if identifier matches subscription or has sublevel
                        # (home/test/# does not listen to home/testing but to home/test)
                        return True
                else:
                    if topic.count("/") == subscription.count("/"):
                        # only the same sublevel matches
                        return True
    pl = subscription.find("/+/")
    if pl != -1:
        st = topic.find("/", pl + 1) + 1
        if memoryview(subscription)[:pl + 1] == memoryview(topic)[:pl + 1]:
            if memoryview(subscription)[pl + 3:] == memoryview(topic)[st:]:
                return True
        return False
    return False
You can test the behaviour with this:

Code: Select all

import time


def timeit(f, *args):
    a = time.ticks_us()
    r = f(*args)
    b = time.ticks_us()
    return b - a, r


def match(sub, topic):
    print("-------------------")
    print(sub, topic)
    gc.collect()
    t, r = timeit(matchesSubscription, sub, topic)
    print("Result:", r, t, "us")

match("home/kevin/set", "home/kevin")
match("home/kevin/set", "home/kevin/set")
match("home/kevin/gpio/+/set", "home/kevin/gpio/13/set2")
match("home/kevin/gpio/+/set", "home/kevin/gpio/13/set")
match("home/kevin/gpio/+/set", "home/kevin/gpio/13")
match("home/kevin/#", "home/kevin/test")
match("home/kevin/#", "home/kevin")
match("home/kevin/#", "home/kevinNot")
match("home/88c3b900/gpio/14/set", "home/88c3b900/gpio/14/set")
match("home/88c3b900/+/14/set", "home/88c3b900/gpio/14")
I welcome any feedback, questions, criticism, optimizations, ...
Kevin Köck
Micropython Smarthome Firmware (with Home-Assistant integration): https://github.com/kevinkk525/pysmartnode

Post Reply