Mqtt blocks

Heya, all,

I want to be able to use Snap! to send and receive mqtt messages. My specific desire is to build an interface in Snap so I can send control messages and receive sensor readings from esp8266-based iOT devices (and esp8266 robots!)-- but the sky's the limit with mqtt...

Played with David Ochao's "snap-mqtt gateway", which works well enough for publishing mqtt messages. But, I couldn't figure out a reasonable and manageable and scalable way to subscribe to topics and receive mqtt messages back into Snap.

Then-- in working with websockets (mentioned in a post here or so) to communicate with the gateway-- I realized that it should be possible to build mqtt blocks.

So-- some working mqtt blocks are here. (Is there a way to share a Snap! project so that it doesn't open with the stage maximized?) These blocks utilize the Paho mqtt javascript client library.

I suggest to use the javascript console (in Chrome, dev tools) to see what's going on...

Caveats:

Initialization doesn't work the way I want it to, quite: my intent is that the mqtt library should be loaded when the [init mqtt] block is run, but that loading is skipped if it that library has already been loaded (in other words, if the program has already been run in this instance.) However, instead, first run loads the external libraries (adds as script src in header), then subsequent runs skip that loading and actually create the mqtt client. I'm not sure why it's not working the way I want. I see that other Snap! libraries load external js libraries in different ways, and am open to suggestions!

I'm also not sure that I'm doing "when message received" the best way. At this point, the onMessageArrived js function adds each new message to the end of a "messagequeue" js array, then I have a reporter block that array shifts and returns the oldest message from that messagequeuereturns. (I've set the messagequeue to hold a max of 200 messages- it dumps the oldest when new messages are received after 200. This could easily be changed...) It seems to handle a steady stream of messages pretty well (subscribe to "#" on test.mosquitto.org and watch the javascript console!)

But since "message arrival" is sort of an interrupt-- I wonder if maybe alternatively, it would be good to have a way for the "onMessageArrived" function set a Snap! variable then send a Snap! broadcast so that Snap! could handle the message receipt

Is there such a mechanism-- to make a javascript function set a Snap variable (not as a return, because the function wasn't called per se), and/or to send a Snap broadcast?

This is all just pretty much duct-tape coding, but-- it's functioning and I'm rather proud of that... : )

Please test and give feedback!

Mike

2 Likes

Hi
Just confirming that it works on someone else's setup. :slight_smile:

I wrote a Scratch 1.4 / Python mashup to do MQTT but it would be really nice to have this working online as I can see it being an easy way of making IoT Dashboards without people having to learn HTML/CSS

When I did it on Scratch - I just made it create new sensor blocks for each topic but I don't know how to translate that concept into Snap methodology

Simon
(SimpleScratch on Scratch forums)

I have no idea if this is actually possible to implement, but from the perspective of a user, the ability to pass a callback would be cool:

mqttblock-10%20script%20pic

hmm-- well, "MQTT is data-agnostic so it's possible to send images, texts in any encoding, encrypted data, and virtually every type of data in binary format." (1). So, conceivably yes something like this could be done. Practically, though, I'm not sure that would be very effective.

(The block idea you posted should be an mqtt_pub block: the mqtt_sub block subscribes to a topic and only takes one argument. Instead, we'd use the mqtt_pub block to publish a message to a topic...)

The example you presented publishes this message:
snapmqtttest: Context >> a CommandBlockMorph ("move %n steps...") a VariableFrame {}

Really, what messages to send and receive is completely up to you-- what is done on the receiving end is dependent on how you parse out and respond to the received message!

Mike

You can do anything with JSFunction. The trick is, you have to read the source code to figure out how. So for example, I know that the implementation of command blocks is generally called doWhatever(), so I searched through the src directory for "doSetVar" and sure enough I found Process.prototype,doSetVar(name, value) in threads.js. Unfortunately, "this" in a JSFunction block is a sprite, not a process, so you have to know that JSFunction calls your function with an extra argument, the current process. So:


I haven't carefully debugged this; for example, I should make sure it does the right thing with script variables.

I am not a Snap! internals wizard. "Duct-tape coding" is a pretty good description of all my efforts to contribute code to Snap!. (Jens yells at me a lot about my code, correctly.) I knew how to find the process in a JSFunction because Jens explained it once in a forum post. I should really make Jens write a Program Logic Manual, When Things Slow Down™.

I also found a "Process.prototype.doBroadcast" in threads.js, so I wrote this:


I know, you want to call these from Javascript, not from Snap!. So I'm guessing you have to create a new Process to carry out the computation in. But that's as far down this rabbit hole as I have time for...

Thanks, Brian. I've got things working kind-of the way I like by just using a "forever" loop to poll for incoming messages... I guess, as I think about it more, I prefer having Snap! in change of message processing rather than a javascript block, at this point.

Have done some tweaking today. My current version is here.

Have solved my problem of needing to "init" twice by breaking down the "init" and "connect" into two steps-- adding a moment between them allows the external javascript to get loaded and enabled.

Have added a "poll for incoming messages" block which does a broadcast, and have added a broadcast receiver as an example of how one might parse incoming messages.

Interesting to ponder: if multiple people are running this Snap! app at the same time and are connected to the same mqtt broker and are subscribed and publishing to the same channels-- things could be done in Snap! simultaneously on all of those machines... In other words, if you and I are both connected at the same time running this program as it is, and one of use sends a "move" command-- both of us will see our sprites move at the same time... (Works across multiple browser tabs, too... : )

Mike

1 Like

Interesting. Do you know about NetsBlox (https://editor.netsblox.org/)? They added a feature to let two people edit the same project at the same time. Maybe you should compare notes.

Nice! I recall seeing NetsBlox at one point a year or two ago. Certainly a different model and tool than mqtt, but-- powerful stuff!

Great progress :slight_smile:

I've used your framework to make a simple cheerlights display

https://snap.berkeley.edu/snapsource/snap.html#present:Username=cymplecy&ProjectName=mqttblock-11sw

Woot! That's awesome!

I've found a limitation: on my machine, when I subscribe to a really busy topic like "#" (all topics) on test.mosquitto.org, the messagequeue buffer fills faster than Snap can process the queue. Eventually, the mqtt connection was closed (apparently due to some kind of error)... So, there's that...

Also, I selected "&&" as a delimiter between topic and payload for when the JavaScript function returns a message to Snap. I don't know if it's possible for an mqtt topic to contain '&&', so I don't know if this is a safe delimiter. I'll do some digging in this...

Mike

Cute application... We really should queue events so that you can rely on a hat block. It's on the list...

fwiw, found this good article on mqtt topic naming best practices... There aren't really any forbidden characters per se in mqtt parlance. However, there are a couple of wildcards ("#" and "+") that can be used when subscribing, but therefore could never be part of a topic name. So, I think I'll switch that delimiter to "###" instead...

The article also mentions that-- it's not recommended to subscribe to the "#" topic of busy servers because most clients would be overwhelmed... So, this isn't a "Snap" issue... : )

Moving forward,as I tweak and adjust this-- I'll use this sketch as the "master"...

Mike

I always feel like "wait 1 secs" as a synchronization mechanism is a sign of a missing feature. The Scratch way to do this would be a "broadcast and wait." But I guess what you're waiting on isn't another script, but an external event, so... Ah, we're back at the discussion of using Snap! scripts as callbacks in JS code.

See my feature request

Javascript can return a List so no need to assemble and then disasemble :slight_smile:

if (messagequeue.length > 0) {
  thismessage = messagequeue.shift();
  console.log("retreived queued message= "+thismessage.destinationName+": "+thismessage.payloadString);
} else {
  console.log("No messages in queue");
}
thismessagedest=thismessage.destinationName;
thismessagepayload=thismessage.payloadString;
return new List([thismessagedest,thismessagepayload]);

image

Awesome! Returning the list object is perfect! Have updated the "master"... : )

Mike

1 Like

I also realized that my logic for determining when to broadcast "newmessagereceived" was flawed: it ignores multiple sequential mqtt messages that are the same, which could be a legitimate situation (for example, if a sensor is reporting temperature every few minutes, it's likely that many readings in a row would be the same. However, if I wanted to gather and graph that data, I'd want each reading even if it is that same!)

So, I've changed the underlying messagequeue processor to add a "new message" flag that's passed up to Snap... Is all still contained in the poll_for_mqtt_messages block.

The "master" has been updated...

Mike