Sniff is a "Scratch-like" programming language that's designed to help Scratchers move gently from Scratch to more conventional languages. They can start writing programs, without having to learn a new language because Sniff is based on Scratch. They learn a little more about variables, compiling, syntax errors (!), and they can have fun controlling real hardware while they're doing it.

Friday, 14 October 2016

MQTT Part 3: Fancy publishing

So far with MQTT we've published and subscribed to basic messages from both desktop and embedded Sniff.

make spi device
make ethernet device D10
make networkMAC string "b6:ee:63:ed:95:cb"
make networkIP string ""
make networkConnected boolean
make networkPort number
make networkPeer string

make mqtt device
make clientid string
make message string
make topic string
make retain boolean

when start
.set clientid to networkMAC
.set networkPeer to "" #No DNS on Arduino!
.repeat until networkConnected
..tell mqtt to "connect"
.broadcast measureStuff
..tell mqtt to "loop"

That's the basic framework we need to create a connection to the broker, and keep everything ticking over. However this time around we're going to publish the temperature and humidity from a dht11 temperature and humidity sensor out to the world.

make retain boolean

make thermometer dht11 device D2
make temperature number
make humidity number

when measureStuff
..tell thermometer to "read"
..set retain to yes
..set topic to "office/temperature"
..set message to [temperature]
..tell mqtt to "publish" 
..set retain to yes
..set topic to "office/humidity"
..set message to [humidity]
..tell mqtt to "publish" 
..wait 60 secs

We've already included a broadcast in the start script to kick this script off, so all we need to do is measure the temperature by telling the thermometer to "read", and publish that data. Then we wait a minute and do it again.

Topic Hierarchy

However there are a couple of new tricks here that are worth looking at. Firstly look at the names of my topics - they've got a / in them, which allows us to group topics together. In this case I'm reporting  information about my office, so I'm grouping then under that name.

When someone subscribes they can subscribe to specific topics, but they can also subscribe to groups of topics, so "office/#" means every topic that begins "office/". That would include office/temperature and office/humidity but it would also include "office/lights/1/brightness". You can only use # as the last character of a subscription as it basically means "everything in this category".

On the other hand, maybe I'm interested in the temperature of lots if different rooms. In which case I can subscribe to "+/temperature", or if I want the brightness of all the lights in the house "+/lights/+/brightness". Each + represents exactly one level in the hierarchy, but you can use it anywhere in the path.

Retained Messages

The other thing to consider is that we're only publishing a measurement once per minute. That's fairly sensible, as air temperature doesn't change that quickly, so no need to flood the network. However what if a client connects to the broker and subscribes to  "+/temperature" to work out the average temperature of the house. It would have to wait up to a minute before it got messages from all the sensors, which would be frustrating. We could go further and have sensors push out values only when they change, which would leave our averaging program waiting for as long as it took before it gets all the data it needs (and it shouldn't need to know how many sensors there are either, so it would never know its got them all).

To get round this problem we've created a variable called "retained", and set that to yes before each call to publish. When a message is published in retained mode the broker sends it out to subscribers as normal, but keeps a copy. If any new subscribers come along, then they immediately get the last retained message on that topic, so in this case subscribing to "+/temperature" would immediately generate a set of messages with the last known temperature of every sensor. 

Last Will and Testament

Now that messages can be retained, we've created a new problem: My office publishes a message in the middle of the night, and tells the broker that its 16 degrees. It then gets disconnected from the network because a ferret unplugs the network cable (yes that really did happen to me!). The next morning its warmer, but when the heating subscribes to check the temperature, the last good value sent says its cold, so the heating turns on full blast until I reset the sensor.

Fortunately MQTT has a mechanism to check that everything is still running smoothly. Even when its not publishing, the client and broker occasionally send messages to each other just to make sure that they're still alive. If the broker spots that a client isn't working any more it can clean up a little bit.

However as the client is already disconnected the only way it can control what happens during this cleanup is by leaving a will - instructions for a message that should be sent upon its death. To make this happen our connection code might looks like:

.set retain to yes
.set topic to "office/temperature"
.set message to ""
.repeat until networkConnected
..tell mqtt to "connect"

When we connect this message (an empty string) gets sent to the broker, but the broker doesn't forward it to clients. Rather it hangs onto it, and if at some point in the future it looses connection to the client unexpectedly then it publishes the will-message on the dead clients behalf. Here I've set the message to "" to erase any previous temperature recorded, so it won't be used hours later, when its invalid.

If we had a client which published messages to control some kind of actuator, then the will-message could be used to reset the actuator to some kind of safe/recoverable state.

Note that will messages work with retain so if the will is retained, then its value will persist forever, while a non-retained-will, just gets send out at the time of failure (and an older retained value may persist and get sent to new subscribers!).

If you want to deliberately shut down cleanly then you can tell mqtt to "disconnect" which doesn't trigger the will, but in that scenario you can send whatever messages you like to leave the system in valid state.

There are still a few tricks that MQTT can do, but that's most of what you should need to start generating useful MQTT data sources. Next time I'll look at hooking that up to something server side.

No comments:

Post a Comment