Note that the following notations in the code examples below must be replaced by actual values from Franca:
// "<Attribute>" the Franca name of the attribute
// "<AttributeType>" the Franca name of the attribute type
// "<broadcast>" the Franca name of the broadcast, starting with a lowercase letter
// "<Broadcast>" the Franca name of the broadcast, starting with capital letter
// "BroadcastFilter<Attribute>" Attribute is the Franca attributes name
// "<Filter>" the Franca name of the broadcast filter
// "<interface>" the Franca interface name, starting with a lowercase letter
// "<Interface>" the Franca interface name, starting with capital letter
// "<method>" the Franca method name, starting with a lowercase letter
// "<Method>" the Franca method name, starting with capital letter
// "<OutputType>" the Franca broadcast output type name
// "<Package>" the Franca package name
// "<ProviderDomain>" the provider domain name used by provider and client
// "<ReturnType>" the Franca return type name
The Franca <Package>
will be transformed to the Javascript module joynr.<Package>
.
The Franca <TypeCollection>
is not used in the generated code. It is therefore important that
all typeNames within a particular package be unique.
Any Franca complex type <TypeCollection>.<Type>
will result in the creation of an object
joynr.<Package>.<Type>
(see above).
The same <Type>
will be used for all elements in the event that this type is used as an
element of other complex types, as a method input or output argument, or as a broadcast output
argument.
The Franca <Interface>
will be used as a prefix to create the following JavaScript objects:
<Interface>Provider
<Interface>Proxy
In Javascript the names of attributes, methods and broadcasts within the same interface must be unique, as each name will become a property of the Proxy object.
A Javascript joynr consumer application must "require" or otherwise load the joynr.js
module
and call its joynr.load()
method with provisioning arguments in order to create a joynr
object.
Next, for all Franca interfaces that are to be used, a proxy must be created using the
build()
method of the joynr.proxyBuilder
object, with the provider's domain passed as
an argument.
Once the proxy has been successfully created, the application can add any attribute or broadcast subscriptions it needs, and then enter its event loop where it can call the interface methods.
The following Javascript modules must be made available using require or some other loading mechanism:
// for each type <Type>
"import" js/joynr/<Package>/<Type>.js
// for each interface <Interface>
"import" js/joynr/<Package>/<Interface>Proxy.js
"import" js/joynr.js
"import" js/joynrprovisioning.common.js
"import" js/joynrprovisioning.consumer.js
The Javascript application must load and initialize the joynr runtime environment prior to calling any other Joynr API.
joynr.load(provisioning).then(function(loadedJoynr) {
joynr = loadedJoynr;
// build one or more proxies and optionally set up event handlers
}).catch(function(error) {
// error handling
});
The DiscoveryQos
configures how the search for a provider will be handled. It has the
following members:
- discoveryTimeout Timeout for the discovery process (milliseconds). A timeout triggers an exception.
- cacheMaxAge Defines the maximum allowed age of cached entries (milliseconds); only younger entries will be considered. If no suitable providers are found, depending on the discoveryScope, a remote global lookup may be triggered.
- arbitrationStrategy (details see below)
- additionalParameters special application-specific parameters that must match, e.g. a keyword
- discoveryRetryDelay The time to wait between discovery retries after encountering a discovery error.
- discoveryScope (details see below)
The discoveryScope defines whether a suitable provider will be searched only in the local capabilities directory or also in the global one.
Available values are as follows:
- LOCAL_ONLY Only entries from local capability directory will be searched
- LOCAL_THEN_GLOBAL Entries will be taken from local capabilities directory, unless no such entries exist, in which case global entries will be considered as well.
- LOCAL_AND_GLOBAL Entries will be taken from local capabilities directory and from global capabilities directory.
- GLOBAL_ONLY Only the global entries will be looked at.
Whenever global entries are involved, they are first searched in the local cache. In case no global entries are found in the cache, a remote lookup is triggered.
The arbitration strategy defines how the results of the scoped lookup will be sorted
and / or filtered. The arbitration strategy is a function with one parameter (the array of
capability entries found). It can either be selected from the predefined arbitration strategies
in ArbitrationStrategyCollection or provided as user-defined function. If this user-defined
function myFunction
needs additional filter criteria like the arbitration strategy Keyword,
the result of myFunction.bind(myParam)
has to be used as arbitration strategy.
Predefined arbitration strategies:
- ArbitrationStrategyCollection.Nothing
- ArbitrationStrategyCollection.HighestPriority Highest priority provider will be selected
- ArbitrationStrategyCollection.Keyword Only a Provider that has keyword set will be selected
The priority used by the arbitration strategy HighestPriority is set by the provider in its providerQos settings.
Example for setting up a DiscoveryQos
object:
// additionalParameters unclear
// there is currently no ArbitrationConstants in Javascript
// { "keyword" : "someKeyword" }
// { "fixedParticipantId" : "someParticipantId" }
// { }
//
var discoveryQos = new joynr.system.DiscoveryQos({
discoveryTimeout : 30000,
cacheMaxAge : 0,
arbitrationStrategy : ArbitrationStrategyCollection.HighestPriority,
additionalParameters: {},
providerMustSupportOnChange: true,
discoveryScope : DiscoveryScope.LOCAL\_ONLY,
retryInterval : 1000
});
The MesssagingQos
object defines the roundtrip timeout for RPC requests in milliseconds.
If no specific setting is given, the default is 60 seconds.
Example:
var messagingQos = new joynr.messaging.MessagingQos({
ttl: 60000
});
Proxy creation is necessary before services from a provider can be called:
- call its methods (RPC) asynchronously
- subscribe or unsubscribe to its attributes or update a subscription
- subscribe or unsubscribe to its broadcasts or update a subscription
The call requires messagingQos and discoveryQos settings as well as the provider's domain.
var messagingQos, discoveryQos;
// setup messagingQos, discoveryQos
var domain = "<ProviderDomain>";
joynr.proxyBuilder.build(<Interface>Proxy, {
domain: domain,
discoveryQos: discoveryQos, // optional
messagingQos: messagingQos // optional
}).then(function(<interface>Proxy) {
// subscribe to attributes (optional)
// subscribe to broadcasts (optional)
// call methods or setup event handlers which call methods
}).catch(function(error) {
// handle error
});
In Javascript all method calls are asynchronous. Since the local proxy method returns a Promise, the reaction to the resolving or rejecting of the Promise can be immediately defined. Note that the message order on Joynr RPCs will not be preserved; if calling order is required, then the subsequent dependent call should be made in the following then() call.
<interface>Proxy.<method>(... optional arguments ...).then(function(response) {
// call successful, handle response value
}).catch(function(error) {
// call failed, execute error handling
});
A subscription quality of service setting is required for subscriptions to broadcasts or attribute changes. The following sections cover the 4 quality of service objects available.
SubscriptionQos
has the following members:
- expiry date Absolute Time until notifications will be send (in milliseconds)
- publicationTtl Lifespan of a notification (in milliseconds), the notification will be deleted afterwards
var subscriptionQos = new joynr.proxy.SubscriptionQos({
expiryDate : 1000,
publicationTtl : 1000
});
The default values are as follows:
{
expiryDate: SubscriptionQos.NO\_EXPIRY\_DATE, // 0
publicationTtl : SubscriptionQos.DEFAULT\_PUBLICATION\_TTL // 10000
}
PeriodicSubscriptionQos
has the following additional members:
- period defines how long to wait before sending an update even if the value did not change
- alertAfterInterval Timeout for notifications, afterwards a missed publication notification will be sent (milliseconds)
This object can be used for subscriptions to attributes. Note that updates will be sent only based on the specified interval, and not as a result of an attribute change.
var subscriptionQosPeriodic = new joynr.proxy.PeriodicSubscriptionQos({
period : 1000,
alertAfterInterval : 1000
});
The default values are as follows:
{
period: PeriodicSubscriptionQos.MIN\_PERIOD // 50
alertAfterInterval: PeriodicSubscriptionQos.NEVER\_ALERT // 0
}
The object OnChangeSubscriptionQos
inherits from SubscriptionQos
and has the following
additional members:
- minInterval Minimum time to wait between successive notifications (milliseconds)
This object should be used for subscriptions to broadcasts. It can also be used for subscriptions to attributes if no periodic update is required.
Example:
var subscriptionQosOnChange = new joynr.proxy.OnChangeSubscriptionQos({
minInterval : 50
});
The default is as follows:
{
minInterval: OnChangeSubscriptionQos.MIN\_INTERVAL // 50
}
The object OnChangeWithKeepAliveSubscriptionQos
inherits from OnChangeSubscriptionQos
and has the following additional members:
- maxInterval Maximum time to wait between notifications, if value has not changed
- alertAfterInterval Timeout for notifications, afterwards a missed publication notification will be sent (milliseconds)
This object can be used for subscriptions to attributes. Updates will then be sent both periodically and after a change (i.e. this acts like a combination of PeriodicSubscriptionQos and OnChangeSubscriptionQos).
Using it for subscriptions to broadcasts is theoretically possible because of inheritance but makes no sense (in this case the additional members will be ignored).
Example:
var subscriptionQosOnChangeWithKeepAlive = new joynr.proxy.OnChangeWithKeepAliveSubscriptionQos({
maxInterval : 2000
});
The default is as follows:
{
maxInterval : 0
alertAfterInterval: OnChangeWithKeepAliveSubscriptionQos.NEVER\_ALERT // 0
}
Attribute subscription - depending on the subscription quality of service settings used - informs an application either periodically and / or on change of an attribute about the current value. The subscriptionId returned asynchronously in case of a successful call can be used later to update the subscription or to unsubscribe from it.
<interface>Proxy.<Attribute>.subscribe({
subscriptionQos : subscriptionQosOnChange,
onReceive : function(value) {
// handle subscription broadcast
},
onError: function(error) {
// handle subscription error
}
}).then(function(subscriptionId) {
// subscription successful, store subscriptionId for later use
}).catch(function(error) {
// handle error case
});
The subscribe()
method can also be used to update an existing subscription, by passing the
subscriptionId as an additional parameter as follows:
// subscriptionId from earlier subscribe call
<interface>Proxy.<Attribute>.subscribe({
subscriptionQos : subscriptionQosOnChange,
subscriptionId: subscriptionId,
onReceive : function(value) {
// handle subscription broadcast
},
onError: function(error) {
// handle subscription error
}
}).then(function(subscriptionId) {
// subscription update successful, the subscriptionId should be the same as before
}).catch(function(error) {
// handle error case
});
Unsubscribing from an attribute subscription requires the subscriptionId returned by the ealier subscribe call.
<interface>Proxy.<Attribute>.unsubscribe({
subscriptionId: subscriptionId
}).then(function() {
// handle success case
}).catch(function(error) {
// handle error case
});
Broadcast subscription informs the application in case a broadcast is fired from provider side and provides the output values via a callback function. The subscriptionId returned by the call can be used later to update the subscription or to unsubscribe.
<interface>Proxy.<Broadcast>.subscribe({
subscriptionQos : subscriptionQosOnChange,
onReceive : function(value) {
// handle subscription broadcast
},
onError: function(error) {
// handle subscription error
}
}).then(function(subscriptionId) {
// subscription successful, store subscriptionId for later use
}).catch(function(error) {
// handle error case
});
The subscribe()
method can also be used to update an existing subscription, when the
subscriptionId is passed as an additional parameter as follows:
<interface>Proxy.<Broadcast>.subscribe({
subscriptionQos : subscriptionQosOnChange,
subscriptionId: subscriptionId,
onReceive : function(value) {
// handle subscription broadcast
},
onError: function(error) {
// handle subscription error
}
}).then(function(subscriptionId) {
// subscription update successful, the subscriptionId should be the same as before
}).catch(function(error) {
// handle error case
});
Broadcast subscription with a filter informs the application in case a selected broadcast which matches filter criteria is fired from the provider side. The output values are returned via callback. The subscriptionId returned by the call can be used later to update the subscription or to unsubscribe.
var fParam = <interface>Proxy.<broadcast>.createFilterParameters();
// for each parameter
fParam.set<Parameter>(parameterValue);
<interface>Proxy.<Broadcast>.subscribe({
subscriptionQos : subscriptionQosOnChange,
filterParameters : fParam,
onReceive : function(value) {
// handle subscription broadcast
},
onError: function(error) {
// handle subscription error
}
}).then(function(subscriptionId) {
// subscription successful, store subscriptionId for later use
}).catch(function(error) {
// handle error case
});
The subscribeTo method can also be used to update an existing subscription, by passing the subscriptionId as an additional parameter as follows:
var fParam = <interface>Proxy.<broadcast>.createFilterParameters();
// for each parameter
fParam.set<Parameter>(parameterValue);
<interface>Proxy.<Broadcast>.subscribe({
subscriptionQos : subscriptionQosOnChange,
subscriptionId: subscriptionId,
filterParameters : fParam,
onReceive : function(value) {
// handle subscription broadcast
},
onError: function(error) {
// handle subscription error
}
}).then(function(subscriptionId) {
// subscription update successful, the subscriptionId should be the same as before
}).catch(function(error) {
// handle error case
});
Unsubscribing from a broadcast subscription requires the subscriptionId returned asynchronously by the earlier subscribe call.
<interface>Proxy.<Broadcast>.unsubscribe({
subscriptionId : subscriptionId,
}).then(function() {
// call successful
}).catch(function(error) {
// handle error case
});
A Javascript joynr provider application must "require" or otherwise load the joynr.js
module and call its joynr.load()
method with provisioning arguments in order to create a
joynr object.
Next, for all Franca interfaces that are being implemented, a providerBuilder must be created
using the build()
method of the joynr.providerBuilder
object supplying the providers
domain as argument.
Upon successful creation, the application can register all Capabilities it provides, and enter its event loop where it can handle calls from the proxy side.
The following Javascript modules must be made "required" or otherwise loaded:
// for each type <Type>
"import" js/joynr/<Package>/<Type>.js
// for each interface <Interface>
"import" js/joynr/<Package>/<Interface>Proxy.js
"import" js/joynr.js
"import" js/joynrprovisioning.common.js
"import" js/joynrprovisioning.provider.js
The ProviderQos
has the following members:
- customParameters e.g. the key-value for the arbitration strategy Keyword during discovery
- providerVersion the version of the provider
- priority the priority used for arbitration strategy HighestPriority during discovery
- scope the scope (see below), used in discovery
- onChangeSubscriptions whether the provider supports subscriptions on changes
The scope can be
- LOCAL The provider will be registered in the local capability directory
- GLOBAL The provider will be registered in the local and global capability directory
Example:
var providerQos = new joynr.types.ProviderQos({
customParameters: [],
providerVersion: 1,
priority : 100,
scope: joynr.types.ProviderScope.GLOBAL,
onChangeSubscription : true
});
A provider application must load joynr and when this has been successfully finished, it can register a Provider implementation for each Franca interface it implements. It is also possible to unregister that implementation again, e.g. on shutdown.
While the implementation is registered, the provider will respond to any method calls from outside, can report any value changes via publications to subscribed consumers, and may fire broadcasts, as defined in the Franca interface.
$(function() {
// for each <Interface> where a Provider should be registered for later
var <interface>provider = null;
var <interface>ProviderImpl = new <Interface>ProviderImpl();
var provisioning = {};
provisioning.channelId = "someChannel";
joynr.load(provisioning).then(function(loadedJoynr) {
joynr = loadedJoynr;
// when applications starts up:
// register <Interface>provider
...
// main loop here
...
// when application ends:
// unregister <Interface>provider
}).catch(function(error){
if (error) {
throw error;
}
});
})();
When registering a provider implementation for a specific Franca interface, the object implementing the interface, the provider's domain and the provider's quality of service settings are passed as parameters.
var <interface>providerQos;
<interface>Provider = joynr.providerBuilder.build(<Interface>Provider, <interface>ProviderImpl);
// for any filter of a broadcast with filter
<interface>Provider.<broadcast>.addBroadcastFilter(new <Filter>BroadcastFilter());
// setup <interface>ProviderQos
joynr.capabilities.registerCapability(
"Provider.authToken",
domain,
<interface>Provider,
<interface>ProviderQos
).then(function() {
// registration successful
}).catch(function() {
// registration failed
});
Unregistering a previously registered provider requires the provider's domain and the object that represents the provider implementation.
// provider should have been set and registered previously
joynr.capabilities.unregisterCapability(
"<Interface>Provider.authToken",
domain,
<Interface>provider
).then(function() {
// unregistration successful
}).catch(function() {
// unregistration failed
});
The function implementing the interface must provide code for all its methods and a getter function for every attribute.
var <Interface>ProviderImpl =
function <Interface>ProviderImpl() {
var self = this;
// define <method> handler
// define internal representation of <attribute> and
// getter handlers per <attribute>
// wrappers to fire broadcasts
};
Each handler for a Franca method for a specific interface is implemented as a function object member of this. The parameters are provided as objects. The implementation can be done either asynchronously or synchronously.
this.<method> = function(parameters) {
// handle method, return returnValue of type <returnType>
return returnValue;
};
this.<method> = function(parameters) {
var result = new Promise(function(resolve, reject) {
// handle method, then either return the value
// of type <returnType> with
// resolve(returnValue);
// - or -
// reject(errorEnumerationValue);
// - or -
// reject(new ProviderRuntimeException({ detailMessage: "reason" }));
});
// handle method, return returnValue of type <returnType>
return result;
};
For each Franca attribute of an interface, a member of this named after the
<attribute>
has to be created which consists of an object which includes a getter function
as attribute that returns the current value of the <attribute>
. Also an internal
representation of the Franca attribute value has to be created and properly intialized.
// for each <attribute> of the <interface> provide an internal representation
// and a getter
var internal<Attribute> = <initialValue>;
this.<attribute> = {
get: function() {
return attributeValue;
}
};
The provider implementation must inform about any change of an attribute by calling valueChanged on the given attribute.
this.<attribute>.valueChanged(newValue);
For each Franca broadcast, a member of this named after the <broadcast>
has to be created which consists of an empty object.
this.<broadcast> = {};
The broadcast can then later be fired using
this.fire<Broadcast> = function() {
var outputParameters;
outputParameters = self.<broadcast>.createBroadcastOutputParameters();
// foreach output parameter of the broadcast
outputParameters.set<Parameter>(value);
self.<broadcast>.fire(outputParameters);
}
In contrast to unfiltered broadcasts, to realize selective (filtered) broadcasts, the filter logic has to be implemented and registered by the provider. If multiple filters are registered on the same provider and broadcast, all filters are applied in a chain and the broadcast is only delivered if all filters in the chain return true.
A broadcast filter object implements a filtering function called filter()
which returns a
boolean value indicating whether the broadcast should be delivered. The input parameters of the
filter()
method consist of the output parameters of the broadcast and the filter parameters
used by the consumer on subscription.
(function(undefined) {
var <Filter>BroadcastFilter = function <Filter>BroadcastFilter() {
if (!(this instanceof <Filter>BroadcastFilter)) {
return new <Filter>BroadcastFilter();
}
Object.defineProperty(this, 'filter', {
enumerable: false,
value: function(broadcastOutputParameters, filterParameters) {
// Parameter value can be evaluated by calling getter functions, e.g.
// broadcastOutputParameters.get<OutputParameter>()
// filterParameters can be evaluated by using properties, e.g.
// filterParameters.<property>
//
// Evaluate whether the broadcastOutputParameters fulfill
// the filterParameter here, then return true, if this is
// the case and the publication should be done, false
// otherwise.
return <booleanValue>;
};
});
};
return <Filter>BroadcastFilter;
}());