Skip to content

compose-eu/Appcelerator

Repository files navigation

compose.io - COMPOSE JS library

compose.io is the COMPOSE JavaScript library designed to be used with Titanium Appcelerator platform, node.js and modern browsers.

#Topics


#Installation

##Appcelerator Titanium Mobile

First you will need to install the native mqtt module for Titanium

Extract the archive to <project>/modules and add to your tiapp.xml the module reference

<modules>
    <module platform="android">it.uhopper.mqtt</module>
</modules>

Add the compose.io javascript library inside the Resources folder (or app/lib if you use Alloy) in your project, then in the code

var compose = require('compose.io/index')

##Node.js

Install the module from the git repository

npm i compose-eu/Appcelerator

and then import it in your code

var compose = require('compose.io')

##Browser

You can link to the index.js script inside your page

<script src="js/compose.io/index.js"></script>

The library will self-load all its dependencies on setup call (see later)

console.log(window.compose || window.Compose);

If you wish to use the library in an AMD-enable setup (like with require.js) some configuration are required in order to load the correct resources.

(This will be better handled in future releases)

require.config({
    paths: {

        "compose.io": 'compose.io/index',
        "utils/List": 'compose.io/utils/List',
        "bluebird": 'compose.io/vendors/bluebird/browser/bluebird',
        "stompjs": 'compose.io/vendors/stompjs/stomp.min',
        "client": 'compose.io/client',
        "WebObject": 'compose.io/WebObject',
        "ServiceObject": 'compose.io/ServiceObject',

        "platforms/stomp/browser": "compose.io/platforms/stomp/browser",
        "platforms/http/browser": "compose.io/platforms/http/browser"
        "platforms/mqtt/browser": "compose.io/platforms/mqtt/browser",

    }
});

Once done, just request the module

var compose = require('compose.io')

The library is also configured to be used with browserify to support UMD node-like require. To generate the whole library as a bundle use eg.

browserify index.js > compose-bundle.js

#Library configuration

The minimal configuration required is the apiKey to access the API.

Please refer to the Online demo section on servioticy.com to request your api key.

Note From v0.4.0 a call to compose.setup will return a promise, introducing a breaking change. Please upgrade your code accordingly!

compose.setup('your api key 1').then(function(api1) {
    // api1 is the instance of your first api key
});

// load another apiKey
compose.setup('other api key').then(function(api2) { /* another api key  */ });

Details of available options:

compose.setup({

    // api key
    apiKey: '<api key>',

    // transport type, one of http, mqtt, stomp
    transport: 'mqtt'

    // All optional from here on

    // Compose API endpoint
    url: 'http://api.servioticy.com'

    // Additional configuration to be passed to sub-modules handling data transmission
    // can be passed by adding a properties matching the transport name
    mqtt: {
        proto: 'mqtt', // or 'mqtts'
        host: 'api.servioticy.com',
        port: 1883
        user: 'compose',
        password: 'shines'
    }
    stomp: { /* see above.. */}
})
.then(function(api) {  console.log("Ready!");  })
.catch(function(e) {  console.error("Error!", e);  });

#Example usage

##List all Service Objects


api.list()
    .then(function(list) {

        console.info("List loaded, " + list.length + " elements");

    })

    // .catch is optional, will report errors, if any occurs
    .catch(function(e) {
        console.warn("An error occured!");
    })
    // .finally is optional too, will run after the request is completed (either if failed)
    .finally(function() {
        console.log("Done");
    });

Load all the Service Objects in the list.

api.list().map(api.load).then(function(list) {
    // list is an array containing ServiceObject instances
    list.forEach(function(so) {
        console.log(so.id, so.toString());
    })
})
// .catch(fn).finally(fn)
;

Get the data from all the Service Objects in the list

api.list().map(api.load).map(function(so) {
    // return a Promise to use further chainability
    return so.getStream("location") && so.getStream("location").pull();
})
.then(function(res) {
    // res is now a list of DataBag
    res.forEach(function(dataset) {
        console.log( "Last position registered for " +
            dataset.container() // Stream reference
                .container() // ServiceObject reference
                    .id +
            + " is " + dataset.last().get('latitude') + ", " + dataset.last().get('longitude') );
    });

})
// .catch(fn).finally(fn)
;

Delete all the ServiceObject

api.list().map(api.delete).then(function() {
    console.log("Done");
})
// .catch(fn).finally(fn)
;

##Create a Service Object

Follows a pseudo drone definition as per COMPOSE spec.

The location stream will keep track of the movement of the drone

var droneDefinition = {
   "name": "Drone",
   "description": "My amazing drone",
   "public":"false",
   "streams": {
         "location": {
            "channels": {
                "latitude": {
                    "type": "Number",
                    "unit": "degrees"
                },
                "longitude": {
                    "type": "Number",
                    "unit": "degrees"
                }
            },
            "description": "GPS location",
            "type": "sensor"
        }
    },
    "customFields": {
        model: 'drone-001',
        colors: ['red', 'blue']
    },
    "actions": [],
    "properties": []
}

Create the drone Service Object on the backend


api.create(droneDefinition)
    .then(function(drone) {

        // drone is the new ServiceObject create
        console.info("Drone ServiceObject created, id" + drone.id);
        console.info(drone.toString());

        // see below how to use the drone object to send and receive data

    }).catch(function(e) {

        console.warn("An error occured!");
        console.error(e);

    });


##Load a Service Object definition

The json definition can be stored in the ./definitions folder (eg ./definitions/drone.json) The definition path can be specified either as a path eg. ../so/definitions/drone.json

// use just the json filename
api.getDefinition("drone")
    .then(api.create) // enjoy Promise
    .then(function(drone) {
        console.log("Drone SO loaded!");
    });

##Sending data update

First you have to select the stream you want to use, location in our case, and the send the data with the push method.

The first argument is a list of key/value pair as channel name / channel value;

The second argument (optional, default is set to now) is a readable date value for the channels data to send

drone.getStream('location').push({
    latitude: 11.234,
    longitude: 45.432
}, new Date()).then(successCallback);

##Loading a Service Object by ID

Imagine now to work on a mobile application to control the drone.

var soid = '<ServiceObject id>';
api.load(soid)
    .then(function(drone) {

        // drone is the new ServiceObject
        console.info("Drone ServiceObject created, id" + drone.id);
        console.info(drone.toString());

    })
//  .catch(fn)
//  .finally(fn)
    ;

##Retrieving data from a Service Object

Load the drone Service Object by its ID (or load the list and search for it)

The returned value is a DataBag object which expose some simplified methods to use the data from the stream


drone.getStream("location")
    .pull().then(function(data) {

        console.log("Data for stream loaded " + data.size());

        // iterate results
        while(data.next()) {
            // current return the data stored at the position of the internal cursor
            var value = data.current();
            console.log("Data loaded " + value.get("latitude") + ", " + value.get("longitude"));
        }

        // Stream reference
        var StreamRef = data.container();
        // ServiceObject reference
        var ServiceObjectRef = StreamRef.container();

        console.log("Data for " + data.container().container().name + "." + data.container().name);
        // will print `Data for Drone.location`

        // count the data list
        var count = data.size();

        // get the current index (position in the list)
        var index = data.index();

        // reset internal cursor
        // data.index() will return 0
        data.reset();

        // first data stored
        data.first();

        // last data stored
        data.last();

        // get data at a certain index
        var item = data.at(index);

        console.log(item);
        // the original format of the data
        // { channels: { latitude: { current-value: 'val' } } }

        console.log(item.asObject());
        // simple js object with the data
        // { latitude: 'val', longitude: 'val' }

        // shorthand to get the values
        var lat = item.get("latitude"),
            lng = item.get("longitude");
        console.log( lat , lng );

        //get a value from the list
        // data.get(index, channel_name, defaultValue)
        var lng1 = data.get(data.size()-1, "longitude", -1);

        console.log( (lng === lng1) ? "It works!" : "Something went wrong.." );

    });

##Search for data in a Stream

Methods to search for data in a stream. All search method returns promises

Available search types are

###Numeric Range

Search for data in a stream matching a numeric range constrain

drone.getStream('stream name').searchByNumber("channel name", { from: 'val1', to: 'val2' });
drone.getStream('stream name').searchByNumber("channel name", val_from, val_to });

To combine with other filters

drone.getStream('stream name').search({
    numeric: {
        channel: 'channel name',
        from: 'val1'
        to: 'val2'
    }
});

###Time Range

Search for data in a time range, creation date (lastUpdate) value will be used to match the search

// timeFrom / timeTo can be any value readable as a javascript `Date`
drone.getStream('stream name').searchByTime(timeFrom, timeTo);
drone.getStream('stream name').searchByTime("Tue May 13 2014 10:21:18 GMT+0200 (CEST)", new Date());

To combine with other filters

drone.getStream('stream name').search({
    time: {
        from: 1368433278000,
        to:   1399969278000
    }
});

###Match

Search for a matching value in a provided channel

drone.getStream('stream name').searchByText("channel name", "string to search");

To combine with other filters

drone.getStream('stream name').search({
    match: {
        channel: "channel name",
        string: "string to search"
    }
});

###Bounding box

Search by a delimiting bounding box

This search type will look to match a channel named location with a geojson value. See API docs

drone.getStream('stream name').searchByBoundingBox([
    // upper point
    { latitude: '', longitude: '' },
    // lower point
    { latitude: '', longitude: '' }
]);

To combine with other filters (incompatible with distance, if both provided bbox will be used )

drone.getStream('stream name').search({
    bbox: {
        coords: [
            // upper point
            { latitude: '', longitude: '' },
            // lower point
            { latitude: '', longitude: '' }
        ]
        // or
        // coords: [ toplat, toplon, bottomlat, bottomlon ]
    }
});

###Distance

Search data by distance

// default unit is km
drone.getStream('stream name').searchByDistance({ latitude: 11,longitude: 46 }, 10);

// specifying a unit
drone.getStream('stream name').searchByDistance({ latitude: 11,longitude: 46 }, 1000, 'm');

To combine with other filters (incompatible with bbox, if both provided bbox will be used )

drone.getStream('stream name').search({
    distance: {
        position: { latitude: 11, longitude: 46 },
        // or
        // position: [11, 46],
        value: 1,
        unit: 'km'
    }
});

#Getting realtime updates

Realtime updates works only with mqtt and stomp transport types as two-way communication is available. To use http please see the subproject examples/subscriptions to setup a base http server to receive subscriptions

##Listening for updates to a stream

It is possible to get real time updates for a specific stream by subscribinf to the stream

droid.getStream('stream name').subscribe(function(data) {
    console.log("Stream updated!");
    console.log(data);
}) // .then().catch().finally()

To stop listening

droid.getStream('stream name').unubscribe(); // .then().catch().finally()

Under the hood, the library will take care to retrieve a fresh list of available subscriptions, create a new pubsub subscription if not already available and subscribe to the dedicated topic.

##Listening for all the updates

In some case could be useful to receive all the notifications available, to do so use listen to the data event on the ServiceObject

// register to updates
droid.on("data", function(data, raw) {
    console.log("Received data ", data);
    console.log("Raw message was ", raw);
})

// unregister from updates
droid.off("data")


#Additional notes

Browser support has been tested on latest Firefox, Chrome and IE 11 (any feedback is appreciated)

##Async impl

Async request are implemented as Promise, using the bluebird library

Notes:

To debug easier your code try setting api.lib.Promise.longStackTraces(true) to see more details of exceptions thrown (* Titanium seems not supporting it)

##API support

Current status of the library follows the Servioticy docs reference implementation.

Service Objects

  • All available CRUD operation are supported
  • List of SO

Streams

  • refresh - load a fresh list of streams
  • push - send data
  • pull - receive data list (filtered search adapted support TBD)
  • search - partially tested, implemented

Subscriptions

  • partially tested, implemented
  • http subscription
  • pubsub subscriptions (per stream)

Actuations

  • untested, implemented

#Tests

Unit tests are available in spec/ and use Jasmine Spec

For node.js jasmine-node is used.

Titanium tests support are under development and will use tishadow tests enviroment (jasmine)

Browser tests are undefined at the moment, but will be covered

npm test

#Contributing

Any help is welcome!

Feel free to open an issue or contact us to discuss the library status and future development.

#Docs

API docs needs review. Can be generated using jsdoc and will be added to the repository once the library has a stable release.

npm install -g jsdoc

jsdoc ./

#Changelog

v0.4.1

  • Added argument to actuation.invoke

v0.4

  • Subscription via stomp over ws and mqtt are better supported
  • minor bug fixes

v0.3

  • API break: compose.setup() returns now a Promise with a library instance as argument
  • require refactoring, compatible with UMD loading (browserify)
  • dropped "plain" ws in favor of stomp-over-js

v0.2

  • subscription support
  • stomp support

v0.1

  • Initial release

#License

Apache2

Copyright CREATE-NET Developed for COMPOSE project (compose-project.eu)

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •