logo elektroda
logo elektroda
X
logo elektroda

A tiny MQTT broker/server hosted directly on an OBK/Beken/ESP32 device

p.kaczmarek2 174 0
ADVERTISEMENT
Treść została przetłumaczona polish » english Zobacz oryginalną wersję tematu
📢 Listen (AI):
  • Green glowing lamp next to a smartphone showing the TasmotaRoom control interface with toggle buttons
    As a rule of thumb, it has been accepted that the MQTT broker must be a high-performance server, a cloud service, or at least a Raspberry Pi-style microcomputer. The question, however, is - are you sure? Wouldn't it be possible to realise a simple MQTT server on a chip like the ESP32 or BK7231, with a limited RAM size of, say, a dozen or so kilobytes? In this topic, I will try to show that it is possible and that MQTT is not at all as resource-intensive a protocol as it may seem. I will present here my implementation considerations, the problems I encountered and the treatments I used to reduce memory usage, and then I will present the results of my work.

    I will develop the server in my OBK environment, which means it will run on 32 platforms (including BK7231, ESP32 and Windows):
    Multi-platform IoT firmware supporting up to 32 platforms - OBK 2025 summary

    What is MQTT?
    MQTT (Message Queuing Telemetry Transport) is a lightweight application layer communication protocol designed to exchange messages in a subscription-based model. Communication is via a broker that mediates between clients that publish data and clients that subscribe to specific topics, eliminating the need for direct connections between the two and providing clients with only the data that pertains to them. In addition, subscriptions can contain special characters, giving greater control over what information is communicated.

    Architecture of a simple MQTT server
    One MQTT server serves multiple clients, and multiple clients means multiple threads, isn't that a logical implication? Well, no - I implemented my server based on non-blocking sockets (sockets). This significantly reduces the resource requirement on the operating system side. I first create a single non-blocking socket that waits for new connections:
    Code: C / C++
    Log in, to see the code

    Then, in the "QuickTick" function (kind of the equivalent of a loop - refresh), I do a so-called polling to receive new connections.
    Code: C / C++
    Log in, to see the code

    For new connections I only allocate the client structure. I keep the client structures in a queue rather than a list - that way I don't have to know the size of the queue in advance. To make an array, I would either have to specify its size (e.g. MAX_CLIENTS) or dynamically expand it by realloc, and the queue is implemented so that each client simply has a pointer to the next one, which reduces the memory overhead,
    Similarly, this is how the clients are handled and the queue iterated:
    Code: C / C++
    Log in, to see the code

    The rest is just about processing packets according to the MQTT standard, password checking, etc., but the data structures used can still be quoted. I also use the queue for the subscription list:
    Code: C / C++
    Log in, to see the code



    Problems with Tasmota
    Initial tests showed that the MQTT client from OpenBeken works, while the one from Tasmota does not show subscribed topics. OpenBeken uses MQTT with LWIP, while Tasmota uses PubSubClient. I have started to diagnose step by step where the difference is and where the communication breaks down.
    
    Info:GEN:MQTTS: parse offset=0 lenBytes=2 remLen=138 pktTotal=141 raw=[10 8A 01 00]
    Info:GEN:MQTTS: pkt type=1 flags=0x0 remLen=138 from ''
    Info:GEN:MQTTS: client 'DVES_476739' connected
    Info:GEN:MQTTS: recv 27 bytes from 'DVES_476739' [31 1F 00 17 74 ...]
    Info:GEN:MQTTS: recv 139 bytes from 'DVES_476739' [4F 6E 6C 69 6E ...]
    Info:GEN:MQTTS: parse offset=0 lenBytes=1 remLen=110 pktTotal=112 raw=[4F 6E 6C 69]
    Info:GEN:MQTTS: pkt type=4 flags=0xf remLen=110 from 'DVES_476739'
    Info:GEN:MQTTS: recv 724 bytes from 'DVES_476739' [31 FA 04 00 25 ...]
    Info:GEN:MQTTS: parse offset=0 lenBytes=2 remLen=634 pktTotal=637 raw=[31 FA 04 00]
    Info:GEN:MQTTS: pkt type=3 flags=0x1 remLen=634 from 'DVES_476739'
    Info:GEN:MQTTS: parse offset=637 lenBytes=1 remLen=85 pktTotal=87 raw=[31 55 00 26]
    Info:GEN:MQTTS: pkt type=3 flags=0x1 remLen=85 from 'DVES_476739'
    

    It quickly became apparent that packets from Tasmota are combined into a single sequence of bytes , TCP probably fragments them further. TCP doesn't guarantee that the whole thing will arrive in one frame, recv may read part of the packet and some will arrive later. As a result, I had to give up the global buffer for recv and move this buffer to the client structure:
    Code: C / C++
    Log in, to see the code

    I allocate and expand the buffer on an ongoing basis as needed. As a rule, after a few reallocations this buffer settles down. It's a bit of a shame that there is a need for this, as it increases RAM usage, but it's hard.



    Practical presentation
    We compile ( possible online ) OBK with the MQTTServer driver enabled in obk_config.h:
    
    #define ENABLE_DRIVER_MQTTSERVER				1
    

    I also recommend compiling with Berry, as Berry in OBK already has integrations of this server.
    The driver to be started - startDriver MQTTServer. Preferably in autoexec.bat:



    It can also be configured:
    - ms_publish Topic Value
    - ms_user UserName
    - ms_pass Pass
    - ms_port 123
    Only one user is supported. We enter the set data on other devices - I've tested with OBK and Tasmota - it should appear on the panel with us, although this list of them is here for testing and debugging only:
    Screenshot of an MQTT Server dashboard listing 3 connected devices and blue test buttons like “Send Toggle” and color sends.
    The list includes buttons that send Tasmota commands (also OBK-compatible), but this too is only for testing.
    More possibilities open up for us with the Berry scripting language. Below is a sample autoexec.be script listening for selected topics from connected devices (command ms_subscribe ):
    
    autoexec = module('autoexec')
    
    autoexec.init = def()
            autoexec.power_sub = ms_subscribe("cmnd/+/POWER", def (topic, payload)
                    print("Berry POWER: " + topic + " = " + payload)
            end)
            autoexec.get_sub = ms_subscribe("+/+/get", def (topic, payload)
                    print("Berry GET: " + topic + " = " + payload)
            end)
             autoexec.ip_sub = ms_subscribe("+/ip", def (topic, payload)
                     print("Berry Got IP Report: " + topic + " = " + payload)
    		end) 
    		autoexec.uptime_sub = ms_subscribe("+/uptime", def (topic, payload)
    			print("Berry Got UpTime Report: " + topic + " = " + payload)
    		end)
            autoexec.ha_sub = ms_subscribe("homeassistant/+", def (topic,payload) 
    			print("Berry HA Discovery: " + topic + " = " + payload)
    		end)
            autoexec.con_sub = ms_subscribe("+/connected", def (topic, payload)
    			print("Berry Connected: " + topic + " = " + payload)
    		end)
            autoexec.sensor_sub = ms_subscribe("stat/+/SENSOR", def (topic, payload)
                    print("Berry SENSOR: " + topic + " = " + payload)
            end)
            autoexec.stat_sub = ms_subscribe("stat/+/+", def (topic,payload)
    			print("Berry STAT: " + topic + " = " + payload)
    		end)
    end
    
    return autoexec
    

    Results:
    Screenshot of logs showing MQTT TCP connection, BERRY messages, and uptime reports for device obk174083A4
    Console log screenshot showing “Info:BERRY:Berry POWER STATE” entries and MQTT topics.
    Berry is not just about listening, however. We can also publish data (command ms_publish ). Various automations can be done in this way. For example, the script below transmits the state of the second relay on the light switch to the LED light. It is worth mentioning here that this switch, I have mounted in a box with wires to one lamp, and yet it has as many as three buttons - one of them controls the lamp, the other two are for automation.
    
    autoexec = module('autoexec')
    
    autoexec.init = def()
    	autoexec.power_state_sub = ms_subscribe("stat/tasmota_476739/POWER2", def (topic, payload)
    		ms_publish("cmnd/obk174083A4/POWER",payload);
    		print("Berry POWER STATE: " + topic + " = " + payload)
    	end)
    end
    
    return autoexec
    
    

    Video:



    More complex mechanisms can also be made this way, for example:
    - button reactions (OBK supports button events, single click, double click, etc., press)
    - staggered events (Berry in OBK has a wait function)
    - events assigned to given times (OBK has a calendar system - addClockEvent)

    Summary
    There is no problem running a simple MQTT server directly on a chip such as the BK7231, and if necessary you can switch to an ESP32 with additional PSRAM (you then gain a few megabytes from 100 kB of RAM). Flash memory is essentially not limiting at all here, and it's not too bad with RAM either, although the requirement to keep separate buffers for packets for each client increases consumption somewhat. Apart from that, the client and subscription lists themselves are quite lightweight and don't take up much.
    The developed MQTT server works and is not at all that demanding - I have implemented everything without threads thanks to the use of non-blocking sockets, and the client and subscription lists are queue-based, so they don't allocate excessive amounts of memory at start-up and yet are not limited in size.
    An interesting observation might be that the MQTT standard with OBK, where each value is a separate 'publish' is much lighter in terms of RAM than the Tasmota standard, where the whole thing is published as a single JSON, which has to be held in memory for a while for processing.
    The additional integration with Berry makes the possibilities get really big. You can program yourself any logic you like and mirror the operation of the Home Assistant on a tiny microcontroller.
    Further plans:
    - add more examples of the Berry scripting language for MQTT support
    - add a separate driver that, like a Home Assistant, receives HASS Discovery and shows the devices on the OBK panel itself, without any scripting and without writing any code
    - check the server driver on different platforms and investigate how many devices can be supported on the BK7231, which usually has about 100 kB RAM free, and how many on the ESP32 with PSRAM (8 MB RAM or more)
    Do you see any uses for such a smaller MQTT server? It seems to me that this could be a good alternative for people who don't want to put up a full Home Assistant.

    Related and documentation:
    https://github.com/openshwprojects/OpenBK7231T_App
    https://openbekeniot.github.io/webapp/devicesList.html
    Berry in OpenBeken
    Online Builds (compilation) in OBK
    Multi-platform IoT firmware supporting up to 32 platforms - a summary of OBK 2025

    Cool? Ranking DIY
    Helpful post? Buy me a coffee.
    About Author
    p.kaczmarek2
    Moderator Smart Home
    Offline 
    p.kaczmarek2 wrote 14080 posts with rating 11907, helped 637 times. Been with us since 2014 year.
  • ADVERTISEMENT
📢 Listen (AI):
ADVERTISEMENT