-
-
Notifications
You must be signed in to change notification settings - Fork 7.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
esp8266: Implement MQTT client support #2055
Comments
Finding a decent Python MQTT client to reference API to is a challenge. Google doesn't know about anything but xxx-ported-from-javascript Paho. Searching directly on PyPI gives more hits of cause, but finding something decent is still a problem. Here's at least asyncio-based impl: http://hbmqtt.readthedocs.io/en/latest/references/mqttclient.html . The question which interests me though is as simple whether to use nice short |
Initial implementation pushed as https://github.com/micropython/micropython-lib/tree/mqtt/umqtt (branch rebased). Thinsg to decide are as usual division of functionality, structure, naming. I'd like to add auto-reconnect support, and that may be too bloated for simple client, so would be a separate module with inheriting class => need to switch to package structure. Maybe also publish-only and subscribe-only clients, but that seems like spreading too thin (publish-only is ~5 lines of socket.read/write). Tested on linux. Reliable working on esp8266 would depend on #2056 |
Where is a good place to put my 2 cents worth on MQTT? |
A few quick comments: In the example, the order for server and id is reversed from the mqtt module where client id comes first. Also, at least under python 3 (can't test under micropython right now) - the various strings needed to be encoded into bytes so for example:
and later:
|
|
Working version of example client: c = MQTTClient("testclient", "192.168.10.159") Note that it is still has a wrong name for the module (IMHO) of uqmtt |
Tell that there were last-minute changes ;-). Fixed.
Nope, instead bytes should be used right away ;-). But MicroPython has relaxed stance on bytes vs str interoperatability, so one can be passed where another is expected (as string are guaranteed to be utf-8). That's of course not CPython compatible, I discourage everyone else from doing that :-P. |
Contrary, I don't need my button to subscribe to anything and my temperature display to publish anything. esp8266 devices in particular would largely be used these ways (there's simply not enough memory to bloat them to do both).
Contrary, normal way to use MQTT is to connect-publish-disconnect. Subscribers are order(s) of magnitude less numbered, and usually belong to more powerful devices (for example, temperature display mentioned above is likely a web page [open on an android device nailed to the wall]). How they handle reconnect is outside of the topic of this ticket. |
I certainly don't want a flame war, but I have to disagree about the 'normal' use of MQTT. |
Because the last 4 bits of the initial byte of a message from a subscribed topic can be set or not (relating to things like retain and QoS), you may want to generalize the assertion in wait_msg to
|
Or probably clearer:
Since the documentation will say that Publish (sent or received) has a control value of 3. |
Some suggestions for wait_msg. If we don't want to deal with QoS > 0 then I believe that you still want to pick up messages the come with the retain flag set, which modified the assertion below. This version also handles messages longer than 127 bytes by looking for a continuation (i.e., first byte is greater than 127) and reading the next byte if it is there and then using it in the calculation of remaining length (var sz).
|
I'm +1 on calling the methods .publish() and .subscribe(). I'd say keep both these methods in the basic client class. A separate derived class with auto-reconnect might be a good idea if such functionality requires many lines of code. |
On a more general note, given that MQTT was the top rated goal, it would be nice to have |
@mkarliner : Feel free to drive that discussion, and not be surprised (or discouraged) if there's lack of critical mass to keep it up - some time may be needed to gather it. |
@pfalcon -- makes sense that those issues need to be addressed. Nice that even the initial client module works and is usable for basic stuff. @mkarliner - my vote would be to keep the implementation pretty lean but would definitely add a ping method but let the user worry about when to call it. |
See the updated starting message for important information about running mosquitto with persistent session store enabled. |
There otherwise were (and probably will be) updates to the branch mentioned above. The basis for auto-retries will be server-side persistent sessions (clean_session=False). |
Ok, per the plans above, "umqtt" is a package now, with 2 submodules: umqtt.simple and umqtt.robust. There're individual commits so far to let interested parties track step-by-step progress, by everything will be squashed together soon. |
@pfalcon Just to report a 'non-issue'. My first smoke test on the publish side has now sent 2.7M messages today at full tilt without any crashes. Good stuff! Ignorant question: Can I use asyncio safely with mqtt, ie: publish and subscribe on different tasks? |
On what hardware?
Do you mean CPython's asyncio or MicroPython uasyncio? Well, the "umqtt" module discussed here isn't compatible with CPython (that's by intent, as esp8266 port for which it's intended is rather low on memory, so any compatibility tricks are expensive). And this module is intended for standalone usage, it won't work with uasyncio. (Any good I mean, it uses blocking I/O, so while it waits for any I/O completion, no other task can run.) |
As I say, it was just a smoke test, but the test rig was as follows. micropython code as follows.
|
Further design discussion: Thinking of a good way to support publishing and subscribing at the same time, I come to conclusion that the "easiest" way to deal with subscribed message is to feed them to a user callback after all. That's because subscribed message can arrive "out of band" at any time. For example, with PUBLISH qos>0, a .publish() function may wait for PUBACK message, and instead get subscribed message. The only alternative to passing it to callback would be queuing it. The simplicity of callback approach is illusory of course. For example, if a callback will decide to publish with qos>0, then callback may be easily called recursively and must be reenterable. And if there's any hiccup, there may be growing recursion depth, leading to stack overflow and application abort. Compare that with a queuing approach, where queue depth can be much easily controlled. Writing all this, I think about possibility mentioned above - move all the complexity to a server, where it belongs when we talk about simple resource-constrained endpoint devices. And that's supporting either publishing or subscribing clients. If an application needs both, well, it's welcome to instantiate both. It will need 2 MQTT client_id's and there will be 2 connections open to server. But they will save MQTT client from the need to demultiplex incoming messages - and such demultiplexion costs memory, as you need to store "unexpected" messages. With separate connections, all the flow control is handled by TCP. Comments welcome, but I love this last solution, so it has high chance of being adopted ;-). |
I'm still not sure - do you run test on ESP8266? To make program, etc. code readable, you need to enclose it in 3 tildes up and down (other people use 3 single quotes):
|
And if yes, how much real time it took to send those 2.7M messages? |
@pfalcon , Addressing your comments in order: The idea of an MQTT server with either publish or subscribe clients seems a very reasonable one, and I accept that it may be a good solution, however.... taken in the context of the larger MQTT ecosystem, I do see problems. Many of the IoT platform require specific client ids, which have to be pre-registered, which means twice the admin on the platform end, and with IBM Bluemix, Amazon etc, that can be a pain. However, that is just nuisance and does not invalidate the solution. Of more concern are the platforms that have a one-to-one mapping between a client_id and a 'device', ie: https://www.opensensors.io. I'm probably being a bit of a pain in the ass, but IMHO I think that micropython is a serious platform and, as far as possible, be suitable for real, industrial use, which includes taking the major platforms' idiosyncrasies into consideration. The code I posted was what I ran on the ESP. I saw your release while I was on holiday and spent the flight back starting to plan a series of tests. I this case, I started with your publish example, and put it in a loop with some delays, and then tried decrementing the delay, expecting some sort of buffering problem, which I'm impressed to report didn't happen. I wasn't doing any formal timing, especially as the current module prints debug messages to the repl on publish, which I assume slows it down a fair amount. I think the run took around 5 hours. |
What is working for me is to expand check_msg a bit so it handles more than receiving a message on a subscribed topic. So for example below, since I am sending a periodic ping, it's also looking for a pingresp -- if it gets something that isn't a published msg or a ping response it just returns the raw response to the calling program that runs a loop that calls check_msg and does whatever it needs to based on what is returned. I realize this probably isn't a generalizable solution but it has been very stable and at the least accounts for a ping response, which is essential if you are subscribing to topic that is low volume and you want to maintain the connection.
|
If I understand you, check msg now gives me all incoming messages, We make an MQTT client GUI design app called ThingStudio, I don't know it it will help the design process but, here are a couple StateSliderSwitch: This is a control typically used for say a light switch. ContinuousSlider: This is a slider control that emits changeValue Hope that helps. cheers On 30/05/2016 23:19, slzatz wrote:
|
That's the idea. The main program runs a while loop and calls check_msg as frequently as it needs to and deals with whatever response it gets. check_msg can do a little processing on some key types of messages but if it's not a message that it recognizes, it just returns whatever it received and the main loop can either try to determine what the message was or ignore it. This may be too much of a hack for an "official" micropython program but just wanted to point out that it's working for my purposes. |
No, and I very much appreciate both your attention to MicroPython in general and comments on this topic in particular. But as you note yourself, issues you raise are at best tangent to MicroPython. And we have core MicroPython issues. The meta issue is that we're doing "impossible" thing of trying to run very high-level language on resource-constrained devices. It's "impossible" because the way VHLLs are used on "big" machines isn't productive or even enabling to their usage on constrained devices. Typical anti-patterns (for constrained devices again) are over-abstraction ("it's already high-level, so let's make it even higher") and creeping featuritis ("resources are free and unlimited, so let's stuff more features - because we can"). MQTT support is currently developed for ESP8266, which is very resource-limited system (as for system which does networking). Saying "I want a library to provide more features" is equivalent to saying "I want other people not to be able to use it in real applications". Because the equation is simple: It's not speculation, it's real. Here's example for much bigger (than esp8266) system: #2057 . ESP8266: http://forum.micropython.org/viewtopic.php?f=16&t=1876 , https://www.kickstarter.com/projects/214379695/micropython-on-the-esp8266-beautifully-easy-iot/posts/1568461 (there were more, it's something I could easily find now). You yourself unlikely would be happy to find out that esp8266 can't run any real MQTT app beyond your smoke test. So, people ask to optimize memory usage, and that's what we will optimize for, not for ease of management with IBM Bluemix. One thing is however top-level requirement - try to do as much as possible in Python, because Python code is easy to tweak and extend. The current topic of work is however to provide the most minimal implementation which still can do useful things. A wireless button and a wireless indicator (publish-only and subscribe-only) are pretty useful, and again, we shoot for being able to do "a wireless sensor" (not just a button) and "a wireless display" (not just a LED). Once that aim is achieved, further aims can be set (likely, by the community, because MQTT is just one task of many, so we need to deliver MQTT support v1.0 and move on to other tasks). Btw, you can read more of MicroPython development philosophy in https://github.com/micropython/micropython/wiki/ContributorGuidelines |
Indeed, it is. There's not much of an "MQTT client" then, your app is MQTT client. Indeed, that's possible - it's well-known that MQTT is rather simple protocol (well, it has many standalone-useful simple subsets). But that's against the idea of a module which abstracts away MQTT protocol details and provides a generic PubSub API to a user. |
Ok, I guess it moves to finalization. I didn't go for splitting pub/sub after all, instead switched subscription processing to callback-based design. Also, wait_msg() actually started to return type of message it couldn't handle itself, but that's internal implementation detail. QOS 1 implemented and works in my testing. After looking at QOS 2, I "remembered" that its usecase is that "exactly once delivery", I somehow lately was thinking that it's to provide detailed reporting to a client of "message received" vs "message actually processed". MQTT spec explicitly says that MQTT signalling alone is not enough to convey "message actually processed" (that would be done by a separate application-level message). So, it explains why many projects ignore QOS 2, and I'm going to do the same, at least for now, and from purely resource limits, as MQTT client is already 5K of Python source. It's still not rebased so far to easy review, but will be soon. Comments are welcome. |
The branch was squashed, with parts moving to their intended locations. |
I performed testing on ESP8266, everything works as expected so far, so I'm merging implementation to micropython-lib master. |
Hi folks, how about putting "ussl" in? (micropython module for TLS). I suppose it's just about wrapping entire thing into socket during "connect" part and adding connect option? |
With offcially released firmware I tested MQTT module on nodemcu 1.0, then I had problem. Calling publish one by one is good, receiving the message by the mosquitto broker on my raspberrypi. However, whenever I try to call publish repeatedly in a while loop, the following error has beed occurred after normally sending the message several times.
Then I had to call connect by c.connect() in order to publish again. |
Is it possible that you have WiFi connection issues? This (very hacky) test publishes every 5 seconds and is currently on 1444 repetitions. Try trapping OSError and re-establishing the connection: import time
import ubinascii
from simple import MQTTClient
from machine import Pin, unique_id
SERVER = "192.168.0.23"
CLIENT_ID = ubinascii.hexlify(unique_id())
TOPIC = b"huzzah"
def main(server=SERVER):
c = MQTTClient(CLIENT_ID, server)
c.connect()
print("Connected to %s, waiting for timer" % server)
fail = False
count = 0
while True:
count += 1
time.sleep_ms(5000)
print("Time to publish")
try:
if fail:
print('Attempt to reconnect')
c.connect()
print('Reconnected to Huzzah')
except OSError:
print('Reconnect fail')
try:
c.publish(TOPIC, ('count '+str(count)).encode('UTF8'))
print('Publish')
fail = False
except OSError:
fail = True
c.disconnect() |
Great! It works! Thank you so much. Now I can publish messages from dht sensor to my home-assistant home server. |
As this issue is likely to crop up again I've put a tidier version of the code in the forum for general information or comment. |
@peterhinch : If you didn't read the discussion in this ticket, robust connections are provided by umqtt.robust module. |
Somebody who can help me? I just downloaded and tried the examples: Everything else like subscribing works. |
I'm not familiar with the lib, but the error indicates that the method is static, and you are calling it on an instance of the class, which is passing 'self' implicitly.
|
It is a method of the MQTTClient class so the code posted by @rudydevolder should work. It does here with the same calling pattern. |
Implement and use RUN_BACKGROUND_TASKS in place of MICROPY_VM_HOOK_LOOP
The |
The basic plan is: start with implementing in Python, if places requiring optimization are spotted, implement them in C.
The reference server/broker: Mosquitto 1.4.8 http://mosquitto.org/download/ (implemented in C). Built from source without changes. Run as
./mosquitto -v
. Default builtin configuration has on-disk persistence disabled. It needs to be enabled for client stored sessions (the basis of efficient robust (re)connection handling) to work as expected. So, have a file mosquitto.conf:And run as:
./mosquitto -c mosquitto.conf -v
. The database file will be mosquitto.db in the same dir as executable. Expected output on start followed by SIGTERM:The text was updated successfully, but these errors were encountered: