A simple SMS bot to check meeting room availability

Among all scarce resources on this planet, meeting rooms should definitely be considered one of them. Even if you have a room reservation system, it can be cumbersome to use whatever web site or application to check if a room is available right now, as you need it (open the laptop, wait for it to connect on the network, open Outlook, check the meeting room status, etc). On top of this, a room may be flagged as booked in a reservation system while being actually free, as the people who booked it changed their plans (hello co-working spaces ?).

At Proximus API Solutions, we started to deploy IoT LoRa sensors in our open space as well as meeting rooms. We collect all sensor data on EnCo, decode LoRa payloads using our CloudEngine then redirect reformatted sensor data to the IoT Factory platform, from where we can consult a nice historical dashboard.

EnCo Dashboard on IoT Factory
Part of EnCo dashboards on IoT Factory

Need for a simple query interface

Although presence in the meeting rooms is reported on our dashboard, getting the meeting’s room availability still requires to consult the dashboard. This is why, using the CloudEngine, we have built a very simple SMS “bot” which we can query for room availability while leveraging the data stored in the IoT Factory platform and ability to query it using APIs (web services).

SMS bot for meeting rooms
SMS bot for meeting rooms

The flow to support this solution is pretty simple and straightforward :

SMS “bot” flow

Let’s see step by step how this has been implemented using CloudEngine’s scripting capabilities.

1. Sending SMS to EnCo

We are sending a SMS with a predefined sentence “enco meet <meeting room name>” to the shared short number 8397 associated to the Proximus SMS API. Our CloudEngine script has a single inbound SMS endpoint, which triggers on the keyword “enco”. Note that this keyword has been reserved and associated to our EnCo account.

GOOD TO KNOW : reserved SMS keywords are a billable feature of the Proximus SMS API. When multiple customers are using the same short shared number (such as our default 8937), using a reserved keyword ensures that all incoming messages starting with this specific keyword will be routed to a particular customer. This feature can be very powerful to support marketing campaigns or specific services that need to be activated or initiated from the mobile terminal over SMS. The next level of personalization and control with SMS is to opt for reserved short or long SMS numbers. If you want to know more about these features, please contact enco@proximus.com

In the CloudEngine script which you will find at the bottom of this post :

  • we store the received SMS message in a variable
  • we use the regular expression module (regex) to parse the message and match the string against a defined template
  • if the regular expression is not matched, we send back an error message over SMS to the sender address
  • if the regular expression is matched, we’ll just extract the meeting room number out of the message

2. Querying IoT Factory for sensor data

Now that we know which is the meeting room the user wants to query for availability, we need to get the latest measured presence metric from the sensor in that room. We are using a new local “config store” capability of the cloudengine to keep a simple table matching meeting rooms to devices.It’s a simple shortcut, we could have used the ability of IoT platforms such as IoT factory to link devices to other assets such as meeting rooms.

We defined a local function called getroomdata which :

  • gets an authorization token from the IoT Factory platform, extracting the right token variable out of the json structure received from a first API call;
  • uses the authorization token to query the IoT Factory platform for the device’s latest data;
  • parses the received data structure until it gets to the “presence” data, and extract it together with the timestamp, which is reformated to something more human readable;
  • constructs the outgoing SMS message

3. Sending back to the originator

Last step is simple and consists in sending back the message constructed by our getroomdata function to the original SMS sender.

Conclusion

As you will see from the script code below, the length of the whole script mostly comes from performing HTTP queries and json structure parsing on received data. The core logic remains however very simple to implement. Without an external platform like IoT Factory to store our sensor data, we could have used “key values” – another feature of the CloudEngine scripting – to persist the latest presence sensor data in other cloudengine scripts which decodes our sensor payloads and thereby suppress any dependency to external platforms.

This is however a good exemple showing how easy it can be to add that little extra feature over an IoT setup (devices, transport, data storage, dashboard) which will make your users happier, spending less than 2 hours in script development and without the need to spawn any additional application server or software stack.

Sample script
# Basic imports
object json = create("Json");
object array = create("Array");
object debug = create("Debug");
object text = create("Text");
object config = create("Config");

# Global vars
object combinedData = [];

# Main function
function run(object data, object tags, string asset) {
 string smsMessage = data["message"];
 object regex = create("Regex", "ENCO\\s(MEET)\\s(\\S+)", text.upperCase(smsMessage));
 object found = regex.find();
 
 if(!found) {
 object dispatchError = create("SMS", data["senderAddress"], "For meeting room enquiries, send : \"ENCO MEET MEETINGROOM_NAME\" to query a specific meeting room");
 dispatchError.send();
 } else {
 string dump = regex.group(1);
 string meetingroom = regex.group(2);
 
 # Get room matching device from our configuration store
 string smsout = getroomdata(config.get(meetingroom).getString());
 
 # Send outgoing SMS to originating mobile
 object dispatchSMS0 = create("SMS", data["senderAddress"], smsout);
 dispatchSMS0.send();
 }
}

# A user defined function to interact with IoT Factory platform
function getroomdata(string devid) {
 # Get authorization token from ioT Factory platform
 object loginhttp = create("HTTP", "https://report.iotfactory.eu/login", "POST");
 loginhttp.setContentType("application/json");
 loginhttp.setData("{\"email\":\"userlogingoeshere\",\"password\":\"passwordgoeshere\"}");
 object logindata = loginhttp.send();
 object loginjsonparsed = json.parse(logindata["BODY"]);
 string iotftoken = loginjsonparsed.get("_id");
 # Fetch latest device data from IoT Factory platform
 string geturl = "http://report.iotfactory.eu/api/measurements/last/"+devid;
 object httpgetdata = create("HTTP", geturl, "GET");httpgetdata.setContentType("application/json");
 httpgetdata.addHeader("Authorization", "Session "+iotftoken);
 object devdata = httpgetdata.send();
 object devdatajson = devdata["BODY"];
 object devdataparsed = json.parse(devdatajson);
 # Search for latest received sensor data until we get to the presence sensor
 int i=0;
 object DevArrayData;
 while (i<5) {
 DevArrayData = devdataparsed.get("parent").expression("/last/"+i);
 if (DevArrayData.get("type")=="presence"){
 break;
 }
 i++;
 } 
 # format timestamp from payload in local time
 object dtModule = create("DateTime", "HH:mm");
 object dtFormatted = dtModule.format(DevArrayData.get("timestamp"),"CET");
 
 if (DevArrayData.get("value")==0) {
 string outmessage = "This room seems free and available, as seen at "+dtFormatted;
 } else {
 string outmessage = "This room seems busy as seen at "+dtFormatted;
 }
 return outmessage; 
}