Integrating with Muzzley

Revision: 0.3.0

Intro

The core purpose of this technical overview is to facilitate the integration process between device manufacturers/developers and Muzzley. This documentation is constantly shaping so that we can better meet your needs, so if you got any suggestions, questions or just want to chat with very kind and nice people always eager to help, drop us a line on our support page.

Muzzley

Muzzley is the single point of entry for all your connected devices, allowing its users to manage, control, interact and even establish relations between devices. All of this while learning and adapting to how they use our platform. Here is an overview of how our system works.

Overview

Before we get started, you must first have a Muzzley account so that you can register your device as well as get access to all the developer goodies we offer. Now that we're ready, let's dive in!

Integration

Integration is the name we use to describe the process of bringing a new type of device to the Muzzley ecosystem. Generally speaking, an integration can be of two types:

Now that you now what we can do for you, there are four key elements that you must be familiar with so that you can easily integrate with us:

Intro

Automation

An awesome perk of integrating any device with Muzzley is access to an automation engine that allows users to establish rules with it (using different triggers like time, location and even other devices). This comes straight out of the box, only requiring you to properly configure your device information with us, so that we may know how it acts, what it triggers and what are its actions.

For more information on this theme please refer to the selfcare documentation, mainly your device profile specifications area.

Manufacturer Logic

So, where do you come in? Well, we already gave some clues up here, but to illustrate what we mean, here's a clear division of concerns:

Manufacturer Logic

As the schematic shows you will have to develop two related components:

Keep reading for more detailed info on this two aspects.

Selfcare

The selfcare site is part of our effort towards an easy integration process. It's through it that you'll register and configure your device with us.

Device type Registration

To register a new device you must first have a Muzzley account (if you don't have one already you may register here). At our site you'll find a way to register new devices with us.

It's here you'll be setting up how Muzzley and your devices will communicate. The specifications are divided into three groups:

Now that you have some notions of what you can do through the selfcare site, you'll have to get acquainted with how we organize our communication and how we structure our connections.

Device Hierarchy

As you might imagine we've got plenty of devices integrated with Muzzley, each one of them with their special and unique manufacturer identifiers. On our ecosystem we have the need to uniquely identify each one of these devices and, using manufacturer specs for each one of these is surely not the best solution for a couple of reasons (e.g. manufacturer A uses the same identification method as manufacturer B).

For the sake of the example picture the following scenario where the manufacturer Awesome IoT Devices has the three following devices:

Device Types Example

Let's take a look at how our hierarchy works:

Device Hierarchy

Imagining that our cooling system in the Awesome IoT cloud had a serial number abcdefgh. If we would want to identify the property speed from the front_fan component we would end up with something like this:

{
  "profile": "awesome-iot-cooling-manager-identifier",
  "channel": "abcdefgh",
  "component": "front_fan",
  "property": "speed"
}

Schemas

The schemas help us specify the type and structure the data on our realtime messages must obey to. They're defined using JSON schemas that are used to validate incoming messages to our users' devices. Because of that, it's on the developer side to always make sure that the messages introduced in the muzzley ecosystem, on behalf of its IoT device, obey to the JSON schema defined on each property on the profile specs configuration page.

These are the schemas we support. If you would for instance, send a message for a color property of a certain IoT bulb using RGB schema you would have to follow the color-rgb schema which means the message would have to be something like this:

{
  "r": 10,  // value between 0 and 255
  "g": 126,  // value between 0 and 255
  "b": 200   // value between 0 and 255
}

Integration Details

Here you should state all the generic information about your integration. These are the key concepts you should be familiar with:

Profile Specs

As the name states, the profile specs define the specifications that your profile (your IoT device(s)) must ensure so that Muzzley and your device(s) can interact. It is the ontology of your devices and is there that you'll specify your channel, components and properties accordingly, establish relations between them, configure triggers, actions and much more.

There are two types of elements to consider here, components and properties , each one of them requiring different kinds of configuration.

Components:

Example:

{
  "id": "color-bulb",
  "label": "Bulb with Color Support",
  "classes": ["com.muzzley.components.bulb","com.muzzley.components.bulb.color"]
}

Properties:

The specifications of properties are made of two separate elements.

Example:

{
  "id":"status",
  "label":"Status",
  "classes":"[\"com.muzzley.properties.status\"]",
  "schema":"https://ontology.muzzley.com/schemas/v1/status-onoff",
  "schemaExtension":"{}",
  "isTriggerable":true,
  "isActionable":true,
  "controlInterfaces":[
    {
      "id":"id1",
      "controlInterface":"toggle-picker",
      "config":"{\"labelTrue\":\"On\",\"labelFalse\":\"Off\"}"
    }
  ],
  "triggers":[
    {
      "id":"53a09d82-7958-46fd-9174-00b91625af74",
      "condition":"equals",
      "predicateLabel":"is equal to",
      "inputsLabel":"",
      "inputs":[
        {
          "id":"value",
          "controlInterfaceId":"id1",
          "path":"[{\"source\":\"selection.value\",\"target\":\"data.value\"}]"
        }
      ],
      "label":"Status equals"
    }
  ],
  "actions":[
    {
      "id":"65f339a7-725d-4872-8e3d-2a89a9e72345",
      "inputsLabel":"",
      "inputs":[
        {
          "controlInterfaceId":"id1",
          "path":"[{\"source\":\"selection.value\",\"target\":\"data.value\"}]"
        }
      ],
      "label":"Set status to..."
    }
  ],
  "io":"rws",
  "onChange":false,
  "rateLimit":0,
  "components":"[\"color-bulb\",\"dimmable-bulb\"]"
}

Interfaces

Muzzley allows you to create your own interfaces that will be used in our app. In order to create a new interface for your integration you must go to the interfaces area of the muzzley site, provide a name and an optional description and you're good to go.

After creating a new interface you'll be provided with an editor for HTML, CSS and Javascript.

To connect your integration with your new interface you must use its uuid and copy it to your profile-specs page.

For more information on the creation of an interface, proceed to the interfaces section below.

Manager

The Manager is where all your manufacturer-specific logic will reside.

A cloud to cloud integration type manager has the following two major components:

Manager

Each of these components will be properly explained in the next lines.

HTTP Interface

It is through HTTP that our cloud will interact with your manager when a realtime communication isn't needed. There are different situations where this occurs, but the great majority of it is during the initial device setup by the end user. Before going deeper, one must first be familiar with how we assure a safe HTTP communication channel between the Manager our HTTP API.

Hawk Authentication

To help us provide a safe communication channel we use Hawk authentication. Similar to digest authentication, Hawk provides partial HTTP request cryptographic verification using a message authentication code (MAC) algorithm. For more information visit Hawk official page.

In order for your Manager to communicate with us you're going to need both inbound (to make requests to our HTTP API) and outbound (to receive requests from our HTTP API) credentials. These are generated on the selfcare page. You may reset this keys later but be warned that generating new keys will invalidate the previous ones .

Despite being a relatively new authentication scheme it has a lot of libraries on different programming languages that can help you out. You can find some examples here.

Authorization flow

In order for a user to add a new device to his Muzzley account, our HTTP API must have a way of telling if the user is authenticated and grant authorization to access that device/manufacturer account. This is the flow used to authenticate a user adding a new device.

Manager HTTP Auth

As you can see, if your platform can act as an OAuth provider, this flow will merge seamlessly with it.

Let's detail the requests received and made by the Manager:

1) A request made by our HTTP API to:

property value description
endpoint <your_manager_url> The URL for your Manager HTTP server
method GET
path <specified_authorization_path> Endpoint you specified at our selfcare page on the authorization section
query parameter user Muzzley id of the user trying to add the device

Example: GET https://muzzley-manager.awesomeiot.com/authorization?user=1

The response to this request is the URL to which the user should be redirected so that it may login on your platform. The response should be in JSON format with the field authorizationUrl containing the URL to redirect.

Example:

{
  "authorizationUrl": "https://awesomeiot.com/oauth2/auth?client_id=this_is_my_client_id&state=1&response_type=code"
}

2) During this phase the user will be presented with the respective redirect page (sent above) and all the manufacturer login logic will take place. When this is done a request must be made to a callback endpoint on the Manager, where it shall be provided details if the user authorization was successful as well as the necessary information (such as access_token) so that he can interact with the manufacturer cloud.

All of this is highly dependent on how you structure your cloud, sessions and 3rd party logins. As such, we let you decide what's the best approach for your use case.

3) When the authorization process on the manufacturer platform is concluded and the Manager has access to the session information from the manufacturer cloud, it should redirect the user to our HTTP API with information stating whether or not the authorization was successful.

Our HTTP API is expecting a request as follows:

property value description
endpoint https://channels.muzzley.com
method GET
path /authorization
query parameter user Muzzley id of the user trying to add the device
query parameter success true / false depending on the user login authorization process

Example: GET https://channels.muzzley.com/authorization?user=1&success=true

By now your Manager is allowed and able to make requests on behalf of the user. Yet, on the general flow of adding a new device to the Muzzley account (or a channel like we call it), we're not done.

Get channels

Right after the user has successfully authorized the Muzzley application (and consequently your manager) to access information on his account on your cloud, the user mobile device will request for all the channels that the user can create with this session. He can then choose which ones he wants to add to his Muzzley account.

Note: In order to have a clear understanding of what is needed at this stage one should be familiar with our device hierarchy.

This request is made to the Manager, which will be expecting something as follows:

property value description
endpoint <your_manager_url> The URL for your Manager HTTP server
method GET
path <specified_resource_path> Endpoint you specified at our selfcare page for the Resource URL
query parameter user Muzzley id of the user

Example: GET https://muzzley-manager.awesomeiot.com/channels?user=1

When this request arrives, the Manager must contact the manufacturer cloud in order to fetch all the necessary information that it needs to provide all the possible channels for this user account on the manufacturer platform. When the Manager has this information it must reply with a well formatted JSON array where all its elements are channels with the following format:

Note: It is very important that the structure used here is aligned with what you defined in the profile spec.
{
  "id": <String>,       // Your channel id that uniquely identifies this device in your cloud
  "content": <String>,  // The name that will be shown on your channel list in the Muzzley App
  "components": [       // The array of components of your channel
    {
      "id": <String>,   // Id that uniquely identifies this component of your device
      "type": <String>, // The id you used to specify this type of component on the profile specs
      "label": <String> // The name that will be shown for this component on your channel interface
    }
  ]
}

Subscriptions

Whenever a user subscribes/unsubscribes to a certain channel (adds or removes it), your manager will receive an HTTP Request with the stated operation. When a user subscribes to a device and your manager is notified of it, you might want to store that information on your manager's side as well - think of it as a local cache. You must , however, validate every request made here so that independently of your usage for the subscribe/unsubscribe info, our HTTP API receives information if the subscribe process went OK or not. Situations where somehow you receive an unsubscribe request for a user who's not subscribed to any channel, according to the records on your manager, should always reply with an HTTP 200 (OK), so that we may always maintain coherence across data records. When you receive an unsubscribe request, you must remove all associated information that you might be storing locally.

The request will be as follows:

property value description
endpoint <your_manager_url> The URL for your Manager HTTP server
method POST
path <specified_subscriptions_path> Endpoint you specified at our selfcare page for the Subscriptions URL
query parameter user Muzzley id of the user
payload A JSON object as follows:
{
  "channels": [
    {
      "id": <channel-id>,
      "status": <String - "on"/"off">  // optional, if none should consider status "on"
    }
  ]
}

Example: POST https://muzzley-manager.awesomeiot.com/subscriptions?user=1 - Payload:

{
  "channels": [
    {
      "id": "123456"
    },
    {
      "id": "654321"
      "status": "off"
    }
  ]
}

Revoke

Whenever you feel that the user session registered in the beginning is no longer valid and you can no longer make requests to the manufacturer cloud on the user's behalf (e.g. the access token expired and there's no way of refreshing it) you should revoke the user subscription through an HTTP request to our HTTP API.

Note: This situation is an exception and it should always be avoided, you should try to automatically renew sessions on your side if you got the ability to do it.

The endpoint used to revoke a given user's authentication for a certain channel is users/<user id>/channels/<channel id>/revoke. This endpoint expects a POST HTTP method.

Update Components

If the components of your device change in some way (e.g. you can add/remove components such as light bulbs or you can change its name) you should have a way to identify these changes and notify our HTTP API.

The endpoint used is as follows:

property value description
endpoint https://channels.muzzley.com
method PUT
path /profiles/<profileId>/channels/<channelId>/components The profile Id and channel Id used to identify the connection
payload A JSON object as follows:
{
  "components": [
    {
      "id": <String>,
      "type": <String>,
      "label": <String>
    }
  ]
}

Example: PUT https://channels.muzzley.com/profiles/abcde789456/channels/123456/components - Payload:

{
  "components": [
    {
      "id": "fan1",
      "type": "fan",
      "label": "Front Fan"
    },
    {
      "id": "fan2",
      "type": "fan",
      "label": "Bottom Fan"
    },
    {
      "id": "fan3",
      "type": "thermostat",
      "label": "Thermostat"
    }
  ]
}

Realtime MQTT Interface

Muzzley relies on bi-directional real-time communication to control IoT devices. We support the MQTT standard.

MQTT is a lightweight publish/subscribe messaging protocol that is ideal for the IoT world. As it's an open protocol, there are already implementations for many programming languages such as C, Java, Python, JavaScript, Ruby, etc. You can find some of the available libraries here.

The publish/subscribe messaging pattern allows users and IoT manufacturers to communicate in a large scale without explicitly communicating directly with one another. As a device manufacturer, you subscribe to messages that are directed at your devices and inform any interested user of your devices' updated states.

Topics

MQTT topics are strings that hierarchically describe a subject of interest much like a file system path. An example could be manufacturer-246/thermostat-model-A/device-123/environment-temperature.

The Muzzley platform has a well-defined topic pattern:

v1/iot/profiles/<profileId>/channels/<channelId>/components/<componentId>/properties/<propertyId>

The base elements of the topic are:

Example : v1/iot/profiles/501234/channels/1234abc/components/bulb-1/properties/brightness

Note: A recommended subscription pattern for your server side Manager would be v1/iot/profiles/501234/#, being that you're interested in subscribing to all the messages of all your devices. Thus, you should subscribe to your profile id and add a multi-level wildcard.

Message Payload

Muzzley MQTT messages are serialized JSON objects. They might contain the following properties:

Read on to understand these properties in more detail.

Input/Output indication

The "io" property allows clients (user-side clients and server-side managers) to clearly express what the message is intended to do.

User-side clients can use two "io" types:

These are the message types your Manager will receive.

Servier-side managers have a single possible "io" value:

These are the message types your manager will send.

Remote Procedure Calls

Although MQTT is a publish/subscribe pattern protocol, our experience tells us that there simply are times when a client needs to ask the server-side manager what a given device's property's state is and get a private response back. This usually happens when users load the interaction interface and need the current state of the device. For instance, the interface might be asking "what is the brightness of bulb 3 so I can update the corresponding UI slider?".

An incoming request might look like this:

{
  "io": "r",
  "_cid": "233?r=26c4241d-77d1-4a5b-a632-72119378f856",
  "u": {
    "id": "10000",
    "name": "Test User"
  }
}

Your manager must be able to interpret these requests and reply as expected. The basic steps for a correct response are:

The response to the above request example would be something similar to the following:

{
  "io": "i",
  "_cid": "233?r=26c4241d-77d1-4a5b-a632-72119378f856",
  "success": true,
  "data": {
    "value": 0.8
  }
}
Note: Only clients may initiate RPC messages and only managers are allowed to respond to them.

Examples

Some quick examples using some of the available MQTT libraries.

var mqtt = require('mqtt');

// Your device manager's credentials
var username = 'a57bca24-16fc-49f9-a61a-d8927067c989';
var password = '80f9c74df1fc05392292a37fc9c4a637c4';

var profileId = '5433fb0a1a02aa650400000b'; // Your device type's identifier
var topicBase = 'v1/iot/profiles/' + profileId;

var client  = mqtt.connect('mqtts://geoplatform.muzzley.com', { username: username, password: password });

client.on('connect', function () {
  console.log('Connected to Muzzley. Subscribing to all communication directed at my devices...');
  var topicDevices = topicBase + '/#';
  client.subscribe(topicBase ,function (err, granted) {
    console.log('Subscription result:', err ? err : 'success.', 'Grant:', granted);
  });
});

function lightStateChanged(deviceSerialNumber, value) {
  // We detected that a light bulb was turned on or off.
  // Let's inform the Muzzley platform of it. Consequently,
  // all users that have our interaction interface open in
  // their smartphones will immediately know that the bulb
  // was either turned on or off.

  client.publish(
    topicBase + '/channels/' + deviceSerialNumber + '/components/bulb-1/properties/status',
    JSON.stringify({ io: 'i', data: { value: value }})
  );
}

function rpcResponse(err, topic, value) {
  // An rpc request was made, let us prepare a proper response
  var msg = success ? '' : 'An error ocurred ' + err;
  var success = err ? true : false;

  client.publish(
    topic,
    JSON.stringify({ io: 'i', success: success, msg: msg, data: { value: value }, _cid: cid})
  );
}

client.on('message', function (topic, message) {
  console.log('Message received at topic "' + topic + '": ' + message.toString());

  var msg = JSON.parse(message);

  // Get the
  // Topic format:
  // v1/iot/profiles/<profile id>/channels/<device serial number>/components/<component id>/properties/<property id>
  var topicParts = topic.split('/');
  var deviceSerialNumber = topicParts[5];
  var devicePartId = topicParts[7];
  var propertyId = topicParts[9];

  if (msg.io === 'w') {
    switch (propertyId) {
      case 'status':
        setBulbStatus(deviceSerialNumber, devicePartId, msg.data.value);
        break;
      case 'brightness':
        setBulbBrightness(deviceSerialNumber, devicePartId, msg.data.value);
        break;
    }
    return lightStateChanged(deviceSerialNumber, msg.data.value);
  }

  if (msg.io === 'r') {
    switch (propertyId) {
      case 'status':
        return getBulbStatus(deviceSerialNumber, devicePartId, function (err, result) {
          rpcResponse(err, topic, result);
        });
      case 'brightness':
        return getBulbBrightness(deviceSerialNumber, devicePartId, function (err, result) {
          rpcResponse(err, topic, result);
        });
    }
  }
});
/*
Copyright (c) 2014, Muzzley

Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all
copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.*/

/**
 * This example uses the mosquitto C library (http://mosquitto.org).
 * In Ubuntu based systems is installable by executing:
 * $ sudo apt-get install libmosquitto0 libmosquitto0-dev
 *
 * Compile with '-lmosquitto'.
 */
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <mosquitto.h>

typedef struct MQTTDataStruct {
  struct mosquitto * _mosq;
} MQTTData;

void on_connect(void * _ptr, int _rc) {
  MQTTData * _self = (MQTTData *) _ptr;
  printf("connected to server...\n");
  mosquitto_subscribe(_self->_mosq, NULL, "v1/iot/profiles/muzzle/channels/_demo-channel_/#", 0);
}
void on_disconnect(void * _ptr) {
  MQTTData * _self = (MQTTData *) _ptr;
  /**
   * Destroy and clean up.
   * - http://mosquitto.org/api/files/mosquitto-h.html#mosquitto_destroy
   * - http://mosquitto.org/api/files/mosquitto-h.html#mosquitto_lib_cleanup
   */
  mosquitto_destroy(_self->_mosq);
  mosquitto_lib_cleanup();
  exit(0);
}
void on_publish(void * _ptr, uint16_t _mid) {
  MQTTData * _self = (MQTTData *) _ptr;
  printf("published successfuly...\n");
}
void on_message(void * _ptr, const struct mosquitto_message * _message) {
  MQTTData * _self = (MQTTData *) _ptr;
  printf("received a message on topic %s:\n%s", _message->topic, (char*) _message->payload);
}
void on_subscribe(void * _ptr, uint16_t _mid, int _qos_count, const uint8_t * _granted_qos) {
  MQTTData * _self = (MQTTData *) _ptr;
  printf("subscribed successfuly...\n");
}
void on_unsubscribe(void * _ptr, uint16_t _mid) {
  MQTTData * _self = (MQTTData *) _ptr;
  printf("unsubscribed successfuly...\n");
}

int main(int argc, char* argv[]) {
  //const char * _username = "a57bca24-16fc-49f9-a61a-d8927067c989";
  //const char * _password = "80f9c74df1fc05392292a37fc9c4a637c4";
  const char * _username = "b20f2e66-dbbd-49da-a8c8-d9eaf6b5b87d";
  const char * _password = "3cbe7ebaaff95d22";
  const char * _profile_id = "muzzley";
  const char * _topic_base = "v1/iot/profiles/muzzley";
  MQTTData _mqtt;

  /**
   * Init mosquitto.
   * - http://mosquitto.org/api/files/mosquitto-h.html#mosquitto_lib_init
   * - http://mosquitto.org/api/files/mosquitto-h.html#mosquitto_new
   */
  mosquitto_lib_init();
  struct mosquitto * _mosq = mosquitto_new(_profile_id, & _mqtt);
  _mqtt._mosq = _mosq;

  /**
   * Register the delegating callbacks.
   * - http://mosquitto.org/api/files/mosquitto-h.html#mosquitto_connect_callback_set
   * - http://mosquitto.org/api/files/mosquitto-h.html#mosquitto_disconnect_callback_set
   * - http://mosquitto.org/api/files/mosquitto-h.html#mosquitto_publish_callback_set
   * - http://mosquitto.org/api/files/mosquitto-h.html#mosquitto_message_callback_set
   * - http://mosquitto.org/api/files/mosquitto-h.html#mosquitto_subscribe_callback_set
   * - http://mosquitto.org/api/files/mosquitto-h.html#mosquitto_unsubscribe_callback_set
   */
  mosquitto_connect_callback_set(_mosq, on_connect);
  mosquitto_disconnect_callback_set(_mosq, on_disconnect);
  mosquitto_publish_callback_set(_mosq, on_publish);
  mosquitto_message_callback_set(_mosq, on_message);
  mosquitto_subscribe_callback_set(_mosq, on_subscribe);
  mosquitto_unsubscribe_callback_set(_mosq, on_unsubscribe);

  /**
   * Sets MQTT server access credentials.
   * - http://mosquitto.org/api/files/mosquitto-h.html#mosquitto_username_pw_set
   */
  mosquitto_username_pw_set(_mosq, _username, _password);
  /**
   * Connects to the MQTT server.
   * - http://mosquitto.org/api/files/mosquitto-h.html#mosquitto_connect
   */
  //mosquitto_connect(_mosq, "geoplatform.muzzley.com", 1883, 30, true);
  mosquitto_connect(_mosq, "10.10.140.200", 1883, 30, true);

  for (; true; ) {
    /**
     * Checks if some data is available from MQTT server.
     * - http://mosquitto.org/api/files/mosquitto-h.html#mosquitto_loop
     */
    mosquitto_loop(_mosq, -1);
  }

  return 0;
}
/*
Copyright (c) 2014, Muzzley

Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all
copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.*/

/**
 * This example uses the mosquitto C library (http://mosquitto.org).
 * In Ubuntu based systems is installable by executing:
 * $ sudo apt-get install libmosquitto0 libmosquitto0-dev
 *
 * Compile with '-lmosquitto'.
 */
#include <unistd.h>
#include <iostream>
#include <functional>
#include <memory>
#include <map>
#include <string>
#include <utility>
#include <vector>
#include <mosquitto.h>

using namespace std;
#if !defined __APPLE__
using namespace __gnu_cxx;
#endif

class MQTT;

/**
 * Data structure that will hold the data for callbacks.
 * Attributes will be instantiated according to the callback type being registered.
 * For more info, see libmosquitto documentation on setting callbacks (http://mosquitto.org/api/files/mosquitto-h.html#mosquitto_connect_callback_set)
 */
typedef struct MQTTDataStruct {
  int _rc = 0;
  uint16_t _mid = 0;
  std::string * _topic = nullptr;
  std::string * _message = nullptr;
  int _qos_count = 0;
  const uint8_t * _granted_qos = nullptr;
} MQTTData;

/**
 * Smart pointer to the callback data.
 */
typedef std::shared_ptr<MQTTData> MQTTDataPtr;
/**
 * Lambda style callback definition.
 */
typedef std::function< void (MQTTDataPtr& _data, MQTT& _mqtt) > MQTTCallback;
/**
 * Map for holding the differente callbacks registered for each different event type.
 */
typedef std::map< string, std::vector<MQTTCallback> > MQTTCallbackList;

class MQTT {
  public:
    inline MQTT(std::string _id) {
      /**
       * Init mosquitto.
       * - http://mosquitto.org/api/files/mosquitto-h.html#mosquitto_lib_init
       * - http://mosquitto.org/api/files/mosquitto-h.html#mosquitto_new
       */
      mosquitto_lib_init();
      this->__mosq = mosquitto_new(_id.data(), this);

      /**
       * Register the delegating callbacks.
       * - http://mosquitto.org/api/files/mosquitto-h.html#mosquitto_connect_callback_set
       * - http://mosquitto.org/api/files/mosquitto-h.html#mosquitto_disconnect_callback_set
       * - http://mosquitto.org/api/files/mosquitto-h.html#mosquitto_publish_callback_set
       * - http://mosquitto.org/api/files/mosquitto-h.html#mosquitto_message_callback_set
       * - http://mosquitto.org/api/files/mosquitto-h.html#mosquitto_subscribe_callback_set
       * - http://mosquitto.org/api/files/mosquitto-h.html#mosquitto_unsubscribe_callback_set
       */
      mosquitto_connect_callback_set(this->__mosq, MQTT::on_connect);
      mosquitto_disconnect_callback_set(this->__mosq, MQTT::on_disconnect);
      mosquitto_publish_callback_set(this->__mosq, MQTT::on_publish);
      mosquitto_message_callback_set(this->__mosq, MQTT::on_message);
      mosquitto_subscribe_callback_set(this->__mosq, MQTT::on_subscribe);
      mosquitto_unsubscribe_callback_set(this->__mosq, MQTT::on_unsubscribe);
    };
    inline virtual ~MQTT() {
      /**
       * Destroy and clean up.
       * - http://mosquitto.org/api/files/mosquitto-h.html#mosquitto_destroy
       * - http://mosquitto.org/api/files/mosquitto-h.html#mosquitto_lib_cleanup
       */
      mosquitto_destroy(this->__mosq);
      mosquitto_lib_cleanup();
    };

    inline virtual void credentials(std::string _user, std::string _passwd) {
      /**
       * Sets MQTT server access credentials.
       * - http://mosquitto.org/api/files/mosquitto-h.html#mosquitto_username_pw_set
       */
      mosquitto_username_pw_set(this->__mosq, _user.data(), _passwd.data());
    }

    inline virtual void connect(std::string _host, int _port = 1883, int _keep_alive = 30, bool _clean_session = true) {
      /**
       * Connects to the MQTT server.
       * - http://mosquitto.org/api/files/mosquitto-h.html#mosquitto_connect
       */
      mosquitto_connect(this->__mosq, _host.data(), _port, _keep_alive, _clean_session);
    };

    inline virtual uint16_t subscribe(std::string _topic) {
      uint16_t _return;

      /**
       * Subscribes to a given topic. See also http://mosquitto.org/man/mqtt-7.html for topic subscription patterns.
       * - http://mosquitto.org/api/files/mosquitto-h.html#mosquitto_subscribe
       */
      mosquitto_subscribe(this->__mosq, & _return, _topic.data(), 0);
      return _return;
    };

    inline virtual uint16_t publish(std::string _topic, std::string _payload) {
      uint16_t _return;
      /**
       * Publishes a message to a given topic. See also http://mosquitto.org/man/mqtt-7.html for topic subscription patterns.
       * - http://mosquitto.org/api/files/mosquitto-h.html#mosquitto_publish
       */
      mosquitto_publish(this->__mosq, & _return, _topic.data(), _payload.length(), (const uint8_t *) _payload.data(), 0, false);
      return _return;
    };

    inline virtual void on(std::string _event, MQTTCallback _callback) {
      /**
       * Add to the callback list, the callback *_callback*, attached to the event type *_event*.
       */
      MQTTCallbackList::iterator _found = this->__callbacks.find(_event);
      if (_found != this->__callbacks.end()) {
        _found->second.push_back(_callback);
      }
      else {
        std::vector<MQTTCallback> _callbacks;
        _callbacks.push_back(_callback);
        this->__callbacks.insert(pair<std::string, std::vector<MQTTCallback> >(_event, _callbacks));
      }
    };

    inline virtual void trigger(std::string _event, MQTTDataPtr& _data) {
      /**
       * Searches for and executes registered callbacks under the event type *_event*.
       */
      MQTTCallbackList::iterator _found = this->__callbacks.find(_event);
      if (_found != this->__callbacks.end()) {
        for( auto _c : _found->second) {
          _c(_data, * this);
        }
      }
    };

    inline virtual void loop() {
      for (; true; ) {
        /**
         * Checks if some data is available from MQTT server.
         * - http://mosquitto.org/api/files/mosquitto-h.html#mosquitto_loop
         */
        mosquitto_loop(this->__mosq, -1);
      }
    };

  private:
    inline static void on_connect(void * _ptr, int _rc) {
      MQTT * _self = (MQTT *) _ptr;
      MQTTDataPtr _data(new MQTTData());
      _data->_rc = _rc;
      _self->trigger("connect", _data);
    };
    inline static void on_disconnect(void * _ptr) {
      MQTT * _self = (MQTT *) _ptr;
      MQTTDataPtr _data(new MQTTData());
      _self->trigger("disconnect", _data);
    };
    inline static void on_publish(void * _ptr, uint16_t _mid) {
      MQTT * _self = (MQTT *) _ptr;
      MQTTDataPtr _data(new MQTTData());
      _data->_mid = _mid;
      _self->trigger("publish", _data);
    };
    inline static void on_message(void * _ptr, const struct mosquitto_message * _message) {
      MQTT * _self = (MQTT *) _ptr;
      MQTTDataPtr _data(new MQTTData());
      _data->_message = new std::string((char*) _message->payload, _message->payloadlen);
      _data->_topic = new std::string(_message->topic);
      _self->trigger("message", _data);
      delete _data->_message;
      delete _data->_topic;
    };
    inline static void on_subscribe(void * _ptr, uint16_t _mid, int _qos_count, const uint8_t * _granted_qos) {
      MQTT * _self = (MQTT *) _ptr;
      MQTTDataPtr _data(new MQTTData());
      _data->_mid = _mid;
      _data->_qos_count = _qos_count;
      _data->_granted_qos = _granted_qos;
      _self->trigger("subscribe", _data);
    };
    inline static void on_unsubscribe(void * _ptr, uint16_t _mid) {
      MQTT * _self = (MQTT *) _ptr;
      MQTTDataPtr _data(new MQTTData());
      _data->_mid = _mid;
      _self->trigger("unsubscribe", _data);
    };
    inline static void on_error(void * _ptr) {
      MQTT * _self = (MQTT *) _ptr;
      MQTTDataPtr _data(new MQTTData());
      _self->trigger("error", _data);
    };

    struct mosquitto * __mosq;
    MQTTCallbackList __callbacks;
};


int main(int argc, char* argv[]) {
  std::string _username("a57bca24-16fc-49f9-a61a-d8927067c989");
  std::string _password("80f9c74df1fc05392292a37fc9c4a637c4");
  std::string _profile_id("muzzley");
  std::string _topic_base(string("v1/iot/profiles/") + _profile_id);

  MQTT _mqtt(_profile_id);
  _mqtt.credentials(_username, _password);
  _mqtt.connect("geoplatform.muzzley.com");

  _mqtt.on("connect", [ & _topic_base ] (MQTTDataPtr& _data, MQTT& _mqtt) -> void {
    cout << "connected to server..." << endl << flush;
    _mqtt.subscribe(_topic_base + string("/channels/_demo-channel_/#"));
  });

  _mqtt.on("subscribe", [ & _topic_base ] (MQTTDataPtr& _data, MQTT& _mqtt) -> void {
    cout << "subscribed to " << _topic_base << string("/#") << endl << flush;
  });

  _mqtt.on("message", [] (MQTTDataPtr& _data, MQTT& _mqtt) -> void {
    cout << "received message on topic " << * _data->_topic << endl << flush;
    cout << * _data->_message << endl << flush;

    size_t _idx = string::npos;
    if ((_idx = _data->_message->find("\"io\":\"w\"")) != string::npos) {
      std::string _payload(_data->_message->data());
      _payload[_idx + 6] = 'i';
      _mqtt.publish(* _data->_topic, _payload);
    }
  });

  _mqtt.on("disconnect", [] (MQTTDataPtr& _data, MQTT& _mqtt) -> void {
    cout << "exiting..." << endl << flush;
    exit(-1);
  });

  _mqtt.loop();
  return 0;
}

Interfaces

After creating a new interface in the selfcare page you'll be provided with an editor for HTML, CSS and Javascript.

Webview editor

Three of the areas of the editor are editable (HTML, CSS, Javascript) and its contents are merged and shown in the output area. The content is merged using the following structure.

<html>
<head>
  <script type="text/javascript" src="jquery.min.js"></script>
</head>
<body>
  <style type="text/css">
    /* CSS */
  </style>
    <!-- HTML -->
  <script>
    /* Javascript */
  </script>
</body>
</html>

To allow interaction between the user and the device, a global object, named muzzley, is made available to the Javascript code.

Note: Only the Javascript area should contain Javascript code.

muzzley

The muzzley object is a global object that can be accessed from JS code box.

muzzley.ready(handler)

When the web view is ready, the handler is called. The handler receives the options object that is injected by the mobile Muzzley applications.

muzzley.ready(function (options) {
  alert(JSON.stringify(options));
});

muzzley.subscribe(options[, callback])

This method allows subscribing to the publish events of a given Muzzley channel.

muzzley.publish(message[, callback])

This method provides a way to publish information to a given Muzzley pub/sub channel.

Channel

A Channel (not to be confused with the Channels that represent devices in the Device Architecture) represents a self-contained virtual communication channel where you'll get messages that are relative to a subscription you performed.

channel.on(event, handler)

This method provides a way to register handlers of specific events.

Events

channel.off(event, handler)

Remove a specific event listener.

channel.unsubscribe(callback)

This method allows unsubscribing to the publish events of a given Muzzley channel.

PubSubMessage

A PubSubMessage is a type of object to help manage the incoming messages from the Muzzley connections.

pubSubMessage.getNamespace()

Get the namespace of the message.

pubSubMessage.getPayload()

Get the payload of the message.