Client APIs

The easiest way to communicate with the VisionAppster Application Engine (AE) is to use one of the provided client API libraries:

Application engine services

The services provided by the VisionAppster Engine are implemented as remote objects that can be accessed through an API that (mostly) follows REST principles. The central services of the AE are listed below. Note that you need to have the VisionAppster Engine running locally to be able to access the links.

Application engine info
is mapped to /info/ on the server. It provides general information of the running AE instance, such as the version number.
Application manager
is responsible for managing installed apps and app repositories. It is also used for starting and stopping the apps. In the remote interface, it is available at /manager/.
Cameras
are controlled by the AE using device-specific camera drivers. All found cameras are published as remote objects at /cameras/.
App APIs
are published under /apis/. A VisionAppster app can publish any number of API objects, and they will appear under this URI. If no apps are running on the AE, the directory will be empty.

To be able to access any of these services you need to connect your client to the service's root URI. To gain deeper understanding of the underlying technology, read on.

Remote object interface

To get started with the client APIs, it is important to understand some central concepts and how they are mapped to the remote object API. To get the most out of this document you should make sure you have the VisionAppster Application Engine running on your computer. The embedded links can then be used to inspect and control it.

The VisionAppster Application Engine provides a built-in HTTP server that makes objects accessible through HTTP requests. The remote object API is modeled after REST principles as far as possible. Function calls and signals don't however naturally fit into the REST model and are handled in a different manner. Standard protocols and data formats are however used for all communication.

The remote object system support a few different encoding schemes. In most cases, the most convenient way of encoding request and response bodies is JSON. When submitting a JSON request, the client will specify "application/json" as the "Content-Type" header. A JSON response is requested by also setting the "Accept" header to "application/json". When calling functions or setting properties, the platform automatically performs safe type conversions (such as integer to double).

Objects

VisionAppster apps are composed of objects. Each object may have an arbitrary number of properties, functions and signals. These concepts can be found in many programming languages in a form or another and are assumed to be familiar to the reader.

In the HTTP interface, each object has a base URI relative to the of the server's root. For example, an instance of an Application Manager object is mapped to manager/. A GET request to this URI will produce a "directory" listing that shows the structure of a remote object.

Each object instance has a globally unique ID that can be retrieved by a GET request. The ID will change if a server goes down and the object is recreated.

The ping URI is for application-level connectivity checking and for keeping a dynamically created temporary object instance alive if no other requests are made.

Properties

Properties are variables that control the behavior or appearance of an object. Generally, properties describe the object's current state, and they can be thought of as the member variables or fields of an object. Property values are specific to a single object instance.

Although properties are similar to member variables, they are technically implemented in a different way. The value of a property is set through a setter function that may validate the value before accepting it. Therefore, it is not guaranteed that a property actually assumes the value one sets to it. Properties also usually have a change notifier signal, which lets one to conveniently and efficiently detect changes.

While changing the value of a property may seemingly cause an action (such as an animation in a user interface), properties aren't used for invoking functionality. That is what functions are for.

Submitting a GET request to properties/ will list properties. Each property declaration contains optional qualifier flags such as const and volatile, a type name, a unique property name and optionally a change signal. A property may be const and thus non-settable but still change value and emit a change signal.

The current value of a property can be read by sending a GET request to properties/propertyname. For example, a GET request to properties/allApps will return a list of all installed applications.

Submitting a POST request to a property's URI will change the value of the property. The new value is sent in the request body, usually as JSON. If the final value of the property after the PUT request is different from the received value, the server will respond with the final value.

When the value of a property changes, a change notification signal will be sent to all registered clients. A client can avoid receiving an unnecessary signal when setting a property by submitting an "X-Client-ID" request header with a randomly generated client identifier. See signals below for further details.

Functions

Functions provide a way to invoke actions on an object. A function can take an arbitrary number of input parameters and optionally return a value. Asynchronous functions may return nothing but emit a signal once they are finished.

The functions of a remote object are listed at functions/. Each declaration consists of an optional return type, a name and a (possibly empty) list of parameter types. There may be multiple overloaded versions of the same function, each taking a different set of parameters. When an overloaded function is called, the server uses passed parameter types to find a matching overload.

When a function is called, its arguments are passed as an array in a POST request body. It is also possible to pass function arguments as query parameter in a GET request, but POST requests should be preferred. A GET request is mainly useful when no arguments are passed to the function call. For example, the hasError() and clearError() functions can be called by sending a GET request.

It is possible that a function declares default values for its parameters. Such parameters can be left out of the parameter list when calling a function.

It is also possible that a function declares names for its arguments. Such functions can also be called with a map of arguments instead of an array. For example, a function setName(string name) can be called with a JSON object as the POST request body: {"name": "John"}.

Signals

Signals are used as a way to notify listeners about changes in an object's state or to emit the results of an asynchronous function call. A signal may pass any number of (including zero) values, and it can be connected to any number of functions with a matching parameter set.

Most programming languages do not provide signals as a built-in concept. The same functionality can be achieved for example with Java-style listener interfaces. Signals are however conceptually cleaner and easier to use in practice.

Submitting a GET request to signals/ will produce a list of signals. Signal declarations are similar to functions, but there is no return value.

Property change signals are a special kind of a signal that will be emitted whenever the value of a property changes. If there is a change signal associated with a property, the signature of the signal will appear in the notifier field of the property description.

Callback functions

Callback functions provide a way for the remote object to request data from the client. The mechanism for delivering callbacks is similar to signals, but only one listener is allowed per function. Unlike signals, callbacks may return a value, which will be passed back to the server through the return channel. Even if a callback function returns no value, the server receives an acknowledgement once the function call finishes. Callbacks are listed at callbacks/.

Pushable sources and channels

The remote object system has a concept of a pushable source that lets the programmer to define many different types of data that be streamed to the client from the server side. Pushable sources are identified by their relative URIs on the server and may present many different kinds of data sources. Signals are the most commonly used type of a pushable source.

To be able to receive push notifications, the client must first open a channel. This happens by establishing a WebSocket connection to channels/new. The WebSocket URL will be, for example, ws://localhost:2015/manager/channels/new?client=3dcbff79-3f15-4a80-b7ca-b669cc0b3209. The client ID can be passed in the "X-Client-ID" request header if your WebSocket library allows it (browsers don't). The opened socket will then be used whenever a notification needs to be sent for the client.

The client ID should be a cryptographically strong UUID. This makes it unlikely that another client will guess it and steal or modify the channel. Once the channel has been established, it can be controlled by making requests to channels/<client-id>. In the example above, the root URI of the new channel would be /manager/channels/3dcbff79-3f15-4a80-b7ca-b669cc0b3209.

The client is in control of selecting which notifications to receive. It can add a pushable source to the channel by issuing a PUT request to channels/<channel-id>/sources/<source-id>. For example, a PUT request to /manager/channels/3dcbff79-3f15-4a80-b7ca-b669cc0b3209/sources/0 with the JSON request body {"sourceUri": "signals/runningAppsChanged"} would tell the server to notify the client whenever the runningAppsChanged signal is emitted.

Note that just like with functions, there may be multiple overloaded versions of a signal. In such a case the client must give the full signature of the signal as the sourceUri parameter. For example:

PUT /myobject/channels/<channel-id>/sources/0 HTTP/1.1
Host: localhost:2015
Content-Type: application/json
Content-Length: 44

{"sourceUri":"signals/booleanChanged(bool)"}

The client is responsible for picking a unique, non-negative 32-bit integer (0-2147483647) as a source ID that identifies the signal. In the example, the source ID is zero. The server will send the source ID when it pushes data to the channel so that the client can identify the signal. A DELETE request to /manager/channels/3dcbff79-3f15-4a80-b7ca-b669cc0b3209/sources/0 will tell the server to stop pushing that signal.

Connecting to multiple signals happens by giving each signal a unique ID number in the context of the channel. For example, to subscribe to the barCode and image signals, one can issue the following requests (channel ID is "d351a1612998" for illustration):

PUT /myobject/channels/d351a1612998/sources/0 HTTP/1.1
Host: localhost:2015
Content-Type: application/json
Content-Length: 31

{"sourceUri":"signals/barCode"}

PUT /myobject/channels/d351a1612998/sources/1 HTTP/1.1
Host: localhost:2015
Content-Type: application/json
Content-Length: 56

{"sourceUri":"signals/image","mediaType":["image/jpeg"]}

The mediaType parameter specifies how the parameters of the signal should be encoded by the server. See details below.

Connected sources can be listed by sending a GET request to channels/<channel-id>/sources/. A channel is killed by sending a DELETE request to channels/<channel-id>. Finally, a broken connection can be re-established by initiating a WebSocket connection to channels/<channel-id>. The server will automatically delete the channel if a broken connection isn't re-established within a "reasonable" time.

Receiving signals and callbacks

The server sends notifications to the client in WebSocket binary frames. The first eight bytes of each frame are a header with the following format:

  • The source ID the client gave when registering a source as a 32-bit big-endian integer. The MSB (bit 31) is reserved and must be zero.
  • An unsigned 16-bit big-endian sequence number that is specific to the source. This lets the client to detect dropped messages in case the server runs out of bandwidth.
  • An unsigned 16-bit big-endian flag word that is used for control purposes. Currently, the LSB (bit 0) indicates partial content. If this bit is zero, the WebSocket frame (or a sequence of fragmented WebSocket frames) is a final message from the server. If it is one, the following frames are related until the bit is zero. This mechanism is used to pass related items that need to be encoded in different ways, for example function parameters that cannot be encoded in JSON.

The rest of the frame contains encoded data. By default, the arguments of a signal or a callback function are encoded as a JSON array in the order they appear in the signal's or callback's declaration and sent in a single WebSocket frame (which may get fragmented in transport). When registering a pushable source to a channel, the client can however request splitting arguments to successive frames by giving a preferred encoding scheme for each argument with the mediaType parameter. For example:

PUT /myobject/channels/<channel-id>/sources/0 HTTP/1.1
Host: localhost:2015
Content-Type: application/json
Content-Length: 54

{"sourceUri":"signals/image","mediaType":["image/jpeg"]}

This will tell the server to connect to a signal called "image" and to encode the signal's single parameter as a JPEG. When the signal is received, the client can decode the body of the WebSocket frame (after skipping the eight header bytes) as a JPEG image.

If a signal has many parameters, they will be sent in successive WebSocket frames in the order the parameters appear in the declaration of the signal. It should be noted that "image/jpeg" requests encoding the whole parameter array as an image (which won't work) whereas ["image/jpeg"] causes the image parameter itself to encoded and sent as a separate WebSocket frame.

The mediaType parameter can also be used to selectively pick signal parameters. Let's assume the object has a signal with the signature foobar(v1: int32, v2: Image). To receive only the image one can give null as the media type for the first parameter:

PUT /myobject/channels/<channel-id>/sources/1 HTTP/1.1
Host: localhost:2015
Content-Type: application/json
Content-Length: 61

{"sourceUri":"signals/foobar","mediaType":[null,"image/png"]}

Specifying a null media type for all parameters disables the signal.

Callbacks are connected to in a similar manner, but the sourceUri is "callbacks/callbackName".