Messaging in HangarControl
I have a lot of detail to cover in this episode, so I am taking a break from the podcast and going "old school". It seems that everyone has been jumping on the MQTT, or more affectionately "mosquitto", bandwagon. I decided to go a different route and will be using a communication system called xPL to handle machine communication in HangarControl. The xPL Project has been around since 2003 and was an early entry in the home automation space. Read more at their website,The xPL Project, or just follow along with me.
What is xPL?
xPL is an open protocol intended to permit the control and monitoring of home automation devices. The primary design goal of xPL is to provide a rich set of features and functionality, whilst maintaining an elegant, uncomplicated message structure. The protocol includes complete discovery and auto-configuration capabilities which support a fully “plug-n-play” architecture – essential to ensure a good end-user experience. xPL benefits from a strongly-specified message structure, required to ensure that xPL-enabled devices from different vendors are able to communicate without the risk of incompatibilities.
Minimal Configuration
Auto discovery and configuration were capabilities that I wanted to use in my implementation. Because the HangarControl environment would span a number of locations across an airport and the aircraft and containing hangars were transient in nature, well at least their use is transient, I wanted a system that would allow for devices to "come and go". I did not want to have to provide pre-configured equipment for each airplane or hangar.
Client-Server or Server-Client?
Client and Server is an arbitrary distinction and clients can be servers and servers can be clients. Bascally, any XPL device can send messages or receive them or both. All xPL messages are broadcast via UDP so any client or any server can receive any message (promiscuous) or just what they should respond to.
xPL Message Structure
You'll see many commonalities between xPL and MQTT. Let's take a look at an xPL message.
Field | Description |
---|---|
source | xPL device sending the message. Always fully filled in. Format is vendor-device.instance (See notes on device idents later.) |
target | xPL device that should receive message OR *. For *, all receivers can receive it and this is most often used with event triggers and status messages (commands tend to be directed to a specific target). Think "broadcast". |
type | xPL messages are either commands (xpl-cmnd), status (xpl-stat) or triggers (xpl-trig). Command messages generally tell someone to do something. Status are messages that indicate status, but are usually sent because someone asked for current status. Trigger messages are generally sent because something happened (event). Trigger and status are often similar, but status indicates current state (possibly for hours/days) and triggers mean this just happened. |
schema | Schema (which is a class.type) is used to indicate what sort of contents the message has. In short, it's how to know what you should expect to see/send in the body of the message. The class is a general 'class' of messages and 'type' is a specific type within that class. It's all pretty arbitrary, but each combination of a schema class/type should uniquely describe the contents of the message body. |
message body | This is the main payload. It consists of a series of name/value pairs. What named values you see in the body are specified by the messages schema class.type. Message body names are up to 16 characters long and both name and value have to be ASCII text (not control chars). |
A given xPL device may support multiple schema class/types or it may just support a particular schema class. You can't make assumptions about what a xPL device supports. Device IDs are just 'endpoints' and not indicators of capabilities. capabilities are only defined by schema class/type.
xPL device Idents have 3 parts -- a vendor code, a device code and an instance code. Instances are the only part that a particular thing can configure -- vendor and device are "hardcoded". Instance IDs must be unique (they are what make the 3 parts a unique identifier on the network). For an unconfigured device, a random instance ID is created at first and later on, can be changed to something more meaningful. Nothing enforces device uniquness, so you have to make sure you don't muck things up with dups.
All xPL devices send a heartbeat periodically (usually when starting up and every 5 minutes). This is done automatically by the xPL code. You can listen for broadcasts of the schema type 'hbeat.app' to track new devices, but generally, the device should announce itself when starting with something more specific. Ideally, you track each device you care about and reset a timer whenever you see any message, including a hbeat message. If you don't hear from the device in 10+ minutes, you'd generally consider it 'dead'. That much tracking is not really needed for this project, but available if you want it.
In the case of HangarControl, it's all pretty simple, but a given node or hangar can potentially send and receive messages from several different schemas if the device supports it. Think of schema class as a capability and type indicating a function within that capability.
xPL in HangarControl
In HangarControl, we are going to use a schema class of 'heater'. I hope it makes sense since the driving purpose is managing engin preheaters during cold weather operations. The commands will be of type 'basic' (basic type is the most common and usually describes commands). Status updates from the devices use a type of 'report'. So even though "schema", "class", and "type" all sound confusing, a real world implementation makes quick sense of the terminology.
Example Message Structure
Schema heater.basic (generally sent to heater clients) Message type: command Message body: request=start|stop|restart|status Schema heater.report (generally sent by clients when state or time changes) Message type: trigger or status Message body: heater=on|off remaining=#minutes
A Status Message
Each hangar device will periodically send a "heartbeat" to let other concerned listeners know that it is still available.
- xPL_MSG TYPE="xpl-stat", SOURCE="rsh-heater.59", TARGET=*, class="hbeat", TYPE="app"
When a heater needs to be turned on, a request is sent from HangarControl to a single hangar.
- xPL_MSG TYPE="xpl-cmnd", SOURCE="rsh-heater.server", TARGET="rsh-heater.59", class="heater", TYPE="basic"
BODY="request=start"
When a new hangar comes online, it appears as an "unnamed" device. At this time, the operations manager will configure the node using the HangarControl web application. The xPL message sent to the hangar will be something as below, which the hangar node will the save to its local configuration:
- xPL_MSG TYPE="xpl-cmnd", SOURCE="rsh-heater.server", TARGET="rsh-heater.59", class="heater", TYPE="config"
BODY="name='N1234T' description='Cessna 182' default-time=120m' gpioPin=17' "
A Little Less Typing and a Lot More Action!
The xPL protocol has been codified and comes in at a paltry 796 lines of (mostly) readable Python! I have attached it for your perusal. While I don't imagine you want to read it all, some interesting points would be:
- Lines 36+
An xPL message is, for all intents and purposes, a text message. This makes monitoring, packet sniffing, and debug/logging much easier. - Lines 50+
I implement the __str__ method for a message. This lets you (well, the programmer in "you") get an easily readable and formatted message to use in logging, etc. - Lines 241+
The encodeMessage() method wraps up all of the pieces into a simple string packet (as discussed above in lines 36+). - Lines 644+
We setup a simple thread to listen for UDP packets on port 3865 or if this are many devices (hangars, heaters, etc.) running on this RPi then start at port 50000 and work our way up til we find a free port to listen on. - Lines 322+ (Yeah, I jumped backwards. That's becuase it's more interesting once we know how we get messages as discussed in lines 644+!)
The XPLListener class proves interesting because here we have the heart of the message parsing and identification. There's nothing fancy going on -- It's simply matching strings and optional wildcards.
Final Words
I chose to use xPL primarily because all of this is a learning experience. Having the core of a messaging system in an easily readable and concise file made the chore of implementation much more approachable.
I hope that you find this useful and find some tidbits of code that you may lift and use in your own projects.
Until next time,
Rick
Top Comments