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.
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:
- Cloud to Cloud: If you're a manufacturer of IoT devices, with a whole infrastructure built to support communication with them, then this is a cloud to cloud type of integration. The key aspect of this integration is building a "bridge", that allows our cloud to communicate with yours and vice-versa.
- Cloud to Device: You're just looking for a way to control your device without the hassle of building a whole system with mobile apps? Well, fortunately, we support that too. Leave the boring logic to us and just worry with connecting you device with the web.
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:
Selfcare - This is where manufacturers like yourself will register your devices with Muzzley. What type of device is it? How do we interact with it? What interface does it use? These are all configurations you set up here.
Muzzley MQTT Realtime Interface - At the core of Muzzley, we have a realtime message system trough which users interact with their devices and vice-versa. We support the MQTT PubSub messaging protocol so that integrating with us isn't platform or language dependent in any way.
Muzzley HTTP REST API - Side by side with our realtime system, to help us manage users, subscriptions and other kinds of data, we have a private HTTP REST API.
Manager - To help our cloud communicate with other manufacturers, we use a component to manage the communication between these two ends. We call this a Manager (yeah, we gave much thought on the naming...). This is the key element to integrate any device with us, as it will be responsible to address any requests made by Muzzley for a specific type of device as well as process the responses from the manufacturer cloud/device. A Manager must communicate with the Muzzley MQTT Realtime Interface and Muzzley HTTP REST API according to the specifications stated in the Selfcare registration, but more on that later.
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:
As the schematic shows you will have to develop two related components:
A Manager that will act as the frontier between our logic and yours. This is where you'll establish any necessary logic you need to make your cloud/device interact with our cloud.
Selfcare site registration, with all the specifications of your device so that our cloud can interact with it.
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:
Details of the integration. This is where you'll specify all the generic information about your devices, including integration type, HTTP URLs used, interface used, etc.
Ontological specifications that will determine how Muzzley communicates with your device. This is where you'll establish relations between the different components and properties of your device, so that the user can better interact with it and so that the Muzzley can interpret its properties and actions.
Interface used to interact with your device. Here at Muzzley all of our implementations are independent from the interface, so that the manufacturers can use one of their choice or even create one if they prefer.
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:
Let's take a look at how our hierarchy works:
profile
- A unique identifier of the device type so the Manager can processes the requests and responses for that type of devices (e.g. the identifier for the Awesome IoT A Manager). This identifier is automatically generated by us once you register your device.channel
- The default approach for this field is to think of it as the unique identifier that allows us to communicate with a certain concrete device. For example, for the type A devices, where you've got a bridge connecting a series of different devices, the default approach would be to consider thebridge identifier
the channel, as for the type B it would be theuser identifier
, as each one has a different profile associated with the device. Lastly, the type C, where the channel would be the identifier for the device itself. Feeling a little lost? Well, that's because there's no easy way of putting this, as each device has its own proper ecosystem and this will always assume some responsibility on the side of the developer to decide what's the best approach for each case.component
- The identifier for a certain component of a given channel. For example, imagine that the type C device is a cooling system with three components, front fan, bottom fan and thermostat. Our cooling system components would befront_fan
,bottom_fan
andthermostat
. You may have situations where there's only one component (e.g. an IoT coffee maker without any special component besides itself).property
- The identifier for a certain property of a component. Consider our cooling system, if ourfront_fan
had two propertiesspeed
andangle
, then those would be properties in our system. You may have situations where there's only one property (e.g. an IoT switch with a propertystatus
), or even situations where a certain property is shared by multiple components (e.g. a type of fan in our cooling system that hadspeed
but noangle
property).
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:
- Integration type: Are you integrating your cloud with our own? Or are you a developer trying to integrate your device with our cloud.
- HTTP URL's: These are the URLs used by our app to communicate and get information about your device/cloud. This will vary according to the type of integration you're doing and will be explained further.
- Interface UUID: The identifier of the interface you selected to be used by our app when users interact with your device(s). This will be explained in detail further.
- Required capability: Here is where you'll select how your devices are added to Muzzley. They can either be added through a web interface with username/password or through UPnP lookup in the user's local network. This property allows us to show your device only in Muzzley clients (our smartphone application, for instance) which support the selected capability.
- Email access list: While your integration is not made public and becomes visible to all Muzzley users, you can use this email whitelist to indicate which Muzzley users should be able to view your device type. Hint: provide your development team members' email addresses.
- Auth: These fields will be automatically filled for you and will contain important data that will allow you to communicate with our HTTTP API. More on that later.
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:
- id: A unique identifier for this component type.
- label: A label to describe it.
- classes: A JSON array containing a set of classes that describe this component. A class is a way of uniquely identifying this type of component. For example, imagine you have a
thermostat
in a room cooling system and in an oven. These two, despite being thermostats, will be a different kinds ofthermostat
. The correct provisioning of this field allows our Machine Learning systems to better understand your system.
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.
First the general configuration :
- id: The identifier for this property. It should be unique amongst the properties of the same component.
- label: A label to describe it.
- classes: A JSON array containing a set of classes that describe this property. Like in the component class field, the property class is a way of classifying this type of property in our ontological IoT definition. For example, imagine you have a property
temperature
on a room cooling system and on a oven. These two, despite being both temperatures, will be a different kind atemperature
which can have different expected minimum and maximum values, and so on. - schema: This a valid data schema URL from the set we described above. As we've stated, keep in mind that whatever you choose must be followed by any subsequent messages for this property.
- schema extension: This is, as the name states, an extension to the schema used. It's a way to complement, override or constrain the base schema. You can provide a JSON structure which will be merged with (and override) the JSON Schema of the schema URL provided in the previous field. For instance, if you have a schema that indicates a temperature in celsius, you can use the schema extension to indicate that the minimum temperature is 9 and the maximum is 32 (°C).
- __ triggerable: Can this property act as a trigger of Muzzley's automation engine?
- __ actionable: Can this property act as an action of Muzzley's automation engine?
- io: Whether this property is readable -
r
, writeable -w
, subscribable -s
, or any combination of this. - components: A JSON array containing the list of components this property belongs to.
- rate limit: The frequency through which your devices will be queried by the state of this property in an hourly basis. This is particularly useful so that Muzzley may know the whole state of its ecosystem. If 0 no queries will be made.
- on change: Used together with the previous field, this tells our system if it should only broadcast changes to this property or if it should always broadcast the current state no matter what changed or not.
Next what makes our automation system :
triggers: If your property is triggerable then you have to set up which triggers it supports.
- condition: The condition for your trigger.
- predicate label: The label that will appear on the automation interface stating what kind of condition you're selecting. For example on the sentence "Temperature drops bellow {value}", the predicate would be "drops bellow".
- label: A description of the trigger.
- inputs label: The label that will appear on the automation interface for the given input. For example on the sentence "Temperature drops bellow ", the inputs label would be "".
actions: If your property is actionable then you have to set up what actions does it support.
- label: A description of the action.
- inputs label: The label that will appear on the automation interface for the given input. For example on the sentence "Temperature drops bellow ", the inputs label would be "".
inputs: This will be used for every
trigger
andaction
, stating the inputs that these need.- id: The id of the value needed. This is only valid for
condition
inputs. - control interface id: Specify the id of the
control interface
to use on this input. - path: An array mapping the interface paths and the corresponding sent
data
field. Remember that this map should be done according to what you've specified in theproperty
schema.
- id: The id of the value needed. This is only valid for
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:
- An HTTP Interface, that will make and receive requests from/to the Muzzley HTTP REST API.
- A Realtime Communication Interface using MQTT, so that our users can interact with your IoT device(s).
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.
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.
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:
{
"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.
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:
v1
: The topic structure's version. It's alwaysv1
.iot
: The topic's namespace. In this case, it's alwaysiot
.<profileId>
: The manufacturer's Profile Id (its device type identifier). This identifier is generated when you register your device type at http://www.muzzley.com.<channelId>
: A device's unique identifier as chosen by the manufacturer. Usually it's something like the device's serial number.<componentId>
: This is the identifier of a device’s component. For instance,bulb-1
could be the component identifier for a device that represents a group of light bulbs.<propertyId>
: A property of the device-component combo. Examples could bebrightness
,temperature
, etc.
Example : v1/iot/profiles/501234/channels/1234abc/components/bulb-1/properties/brightness
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:
"io"
: An indication of the message's input/output type. The possible values are"r"
(read),"w"
(write),"i"
(inform)."_cid"
: A correlation identifier for remote procedure call (RPC) messages. This is only valid forio=r
messages."u"
: An object containing thename
andid
of the Muzzley user who sent the message. This property is only present for"w"
and"r"
messages."data"
: An object indicating the device's property state/values. This property is only present for"w"
requests and"i"
messages.
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:
"w"
: The client is writing a [new] state to a given device and property. For instance, it's setting a bulb's brightness to 50%."r"
: The client is explicitly requesting to know the state of a given device property (indicated in the message topic). In order to correlate the request and the subsequent response, the requesting party must include a"_cid"
(correlation identifier) field and the responding party must include it in its response. See below for more details on RPC.
These are the message types your Manager will receive.
Servier-side managers have a single possible "io"
value:
"i"
: When the server-side manager becomes aware that a given device's state has changed, be it through Muzzley-initiated state change (a"w"
message) or through any manufacturer-specific API, it must notify the Muzzley platform by publishing an informative message with the new state.
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:
- Topic:
"v1/iot/profiles/501234/channels/1234abc/components/bulb-1/properties/brightness"
- Payload:
{
"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:
- Keep the topic and the
"_cid"
property exactly the same. - The
"io"
must be"i"
- The
"success"
field, a boolean, should reflect if the RPC call was successful or if any error ocurred, being respectivelytrue
orfalse
. - The
"message"
field, a string, is an optional field that you should always send whensuccess=false
containing a clear message to why the RPC failed. - The
"data"
should be the requested device's property-specific state representation.
The response to the above request example would be something similar to the following:
- Topic:
"v1/iot/profiles/501234/channels/1234abc/components/bulb-1/properties/brightness"
- Payload:
{
"io": "i",
"_cid": "233?r=26c4241d-77d1-4a5b-a632-72119378f856",
"success": true,
"data": {
"value": 0.8
}
}
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.
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.
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.
message
: A JSON-stringified object with the following properties:namespace
: A namespace string. Currently we only use"iot"
.payload
: The subscribe payload object. This states to whichprofile
,channel
, etc. you're subscribing to. Example:{ profile: '55abc', component: '...' }
callback
: An optional callback function that is called when a message to the specified subscription is received. Its signature isfunction(response, data)
and its arguments are:response
: An optional human-readable description of the success state.data
: An object with the response data.
- returns: A communication Channel type object.
muzzley.publish(message[, callback])
This method provides a way to publish information to a given Muzzley pub/sub channel.
message
: A JSON-stringified object with the following properties:namespace
: A namespace string. Currently we only use"iot"
.payload
: The publish payload object. Example:{ profile: '55abc', component: '...' }
callback
: An optional callback function that is called when the publish response is received. Its signature isfunction(response, data)
and its arguments are:response
: An optional human-readable description of the success state.data
: An object with the response data.
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
subscribe
: Emitted when the subscription is successfully performed.- handler function signature:
function ()
.
- handler function signature:
message
: Emitted each time a message of the particular subscription is received. Themessage
is aPubSubMessage
instance.- handler function signature:
function (message[, response])
. - The
response
argument is a function, only available when the message requires a response. This is the case forio="r"
type messages where the client is asking the Manager for the current status of a particular property. It has the following signature:function (success, message, data)
.success
: A boolean.message
: An optional human-readable description of the success state.data
: An object with the response data.
- handler function signature:
error
: Emitted when an error regarding the subscription occurs.- handler function signature:
function (err)
.
- handler function signature:
channel.off(event, handler)
Remove a specific event listener.
event
: Event you're removing the listener from.handler
: Previously assigned handler.
channel.unsubscribe(callback)
This method allows unsubscribing to the publish events of a given Muzzley channel.
callback
: An optional callback function that is called when the unsubscribe response is received. Its signature isfunction(err)
and its arguments are:err
: An optional argument stating if any error occurred.
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.
- returns: A string stating the namespace.
pubSubMessage.getPayload()
Get the payload of the message.
- returns: The JSON payload.